Files
MuMuAINovel/backend/app/api/relationships.py
T

236 lines
7.6 KiB
Python
Raw Normal View History

2025-10-30 11:14:43 +08:00
"""关系管理API"""
2025-11-10 21:16:55 +08:00
from fastapi import APIRouter, Depends, HTTPException, Query, Request
2025-10-30 11:14:43 +08:00
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, or_, and_
from typing import List, Optional
from app.database import get_db
from app.models.relationship import (
RelationshipType,
CharacterRelationship,
Organization,
OrganizationMember
)
from app.models.character import Character
2025-11-10 21:16:55 +08:00
from app.models.project import Project
2025-10-30 11:14:43 +08:00
from app.schemas.relationship import (
RelationshipTypeResponse,
CharacterRelationshipCreate,
CharacterRelationshipUpdate,
CharacterRelationshipResponse,
RelationshipGraphData,
RelationshipGraphNode,
RelationshipGraphLink
)
from app.logger import get_logger
from app.api.common import verify_project_access
2025-10-30 11:14:43 +08:00
router = APIRouter(prefix="/relationships", tags=["关系管理"])
logger = get_logger(__name__)
@router.get("/types", response_model=List[RelationshipTypeResponse], summary="获取关系类型列表")
async def get_relationship_types(db: AsyncSession = Depends(get_db)):
"""获取所有预定义的关系类型"""
result = await db.execute(select(RelationshipType).order_by(RelationshipType.category, RelationshipType.id))
types = result.scalars().all()
return types
@router.get("/project/{project_id}", response_model=List[CharacterRelationshipResponse], summary="获取项目的所有关系")
async def get_project_relationships(
project_id: str,
2025-11-10 21:16:55 +08:00
request: Request,
2025-10-30 11:14:43 +08:00
character_id: Optional[str] = Query(None, description="筛选特定角色的关系"),
db: AsyncSession = Depends(get_db)
):
2025-11-10 21:16:55 +08:00
# 验证用户权限
user_id = getattr(request.state, 'user_id', None)
await verify_project_access(project_id, user_id, db)
2025-10-30 11:14:43 +08:00
"""
获取项目中的所有角色关系
- 如果提供character_id,则只返回与该角色相关的关系(作为发起方或接收方)
- 否则返回项目中的所有关系
"""
query = select(CharacterRelationship).where(
CharacterRelationship.project_id == project_id
)
if character_id:
query = query.where(
or_(
CharacterRelationship.character_from_id == character_id,
CharacterRelationship.character_to_id == character_id
)
)
query = query.order_by(CharacterRelationship.created_at.desc())
result = await db.execute(query)
relationships = result.scalars().all()
logger.info(f"获取项目 {project_id} 的关系列表,共 {len(relationships)}")
return relationships
@router.get("/graph/{project_id}", response_model=RelationshipGraphData, summary="获取关系图谱数据")
async def get_relationship_graph(
project_id: str,
2025-11-10 21:16:55 +08:00
request: Request,
2025-10-30 11:14:43 +08:00
db: AsyncSession = Depends(get_db)
):
2025-11-10 21:16:55 +08:00
# 验证用户权限
user_id = getattr(request.state, 'user_id', None)
await verify_project_access(project_id, user_id, db)
2025-10-30 11:14:43 +08:00
"""
获取用于可视化的关系图谱数据
返回格式:
- nodes: 角色节点列表
- links: 关系连线列表
"""
# 获取所有角色(节点)
chars_result = await db.execute(
select(Character).where(Character.project_id == project_id)
)
characters = chars_result.scalars().all()
nodes = [
RelationshipGraphNode(
id=c.id,
name=c.name,
type="organization" if c.is_organization else "character",
role_type=c.role_type,
avatar=c.avatar_url
)
for c in characters
]
# 获取所有关系(边)
rels_result = await db.execute(
select(CharacterRelationship).where(
CharacterRelationship.project_id == project_id
)
)
relationships = rels_result.scalars().all()
links = [
RelationshipGraphLink(
source=r.character_from_id,
target=r.character_to_id,
relationship=r.relationship_name or "未知关系",
intimacy=r.intimacy_level,
status=r.status
)
for r in relationships
]
logger.info(f"获取项目 {project_id} 的关系图谱:{len(nodes)} 个节点,{len(links)} 条关系")
return RelationshipGraphData(nodes=nodes, links=links)
@router.post("/", response_model=CharacterRelationshipResponse, summary="创建角色关系")
async def create_relationship(
relationship: CharacterRelationshipCreate,
2025-11-10 21:16:55 +08:00
request: Request,
2025-10-30 11:14:43 +08:00
db: AsyncSession = Depends(get_db)
):
"""
手动创建角色关系
- 需要提供角色A和角色B的ID
- 可以指定预定义的关系类型或自定义关系名称
- 可以设置亲密度、状态等属性
"""
2025-11-10 21:16:55 +08:00
# 验证用户权限
user_id = getattr(request.state, 'user_id', None)
await verify_project_access(relationship.project_id, user_id, db)
2025-10-30 11:14:43 +08:00
# 验证角色是否存在
char_from = await db.execute(
select(Character).where(Character.id == relationship.character_from_id)
)
char_to = await db.execute(
select(Character).where(Character.id == relationship.character_to_id)
)
if not char_from.scalar_one_or_none():
raise HTTPException(status_code=404, detail=f"角色AID: {relationship.character_from_id})不存在")
if not char_to.scalar_one_or_none():
raise HTTPException(status_code=404, detail=f"角色BID: {relationship.character_to_id})不存在")
# 创建关系
db_relationship = CharacterRelationship(
**relationship.model_dump(),
source="manual"
)
db.add(db_relationship)
await db.commit()
await db.refresh(db_relationship)
logger.info(f"创建关系成功:{relationship.character_from_id} -> {relationship.character_to_id}")
return db_relationship
@router.put("/{relationship_id}", response_model=CharacterRelationshipResponse, summary="更新关系")
async def update_relationship(
relationship_id: str,
relationship: CharacterRelationshipUpdate,
2025-11-10 21:16:55 +08:00
request: Request,
2025-10-30 11:14:43 +08:00
db: AsyncSession = Depends(get_db)
):
"""更新角色关系的属性(亲密度、状态等)"""
result = await db.execute(
select(CharacterRelationship).where(
CharacterRelationship.id == relationship_id
)
)
db_rel = result.scalar_one_or_none()
if not db_rel:
raise HTTPException(status_code=404, detail="关系不存在")
2025-11-10 21:16:55 +08:00
# 验证用户权限
user_id = getattr(request.state, 'user_id', None)
await verify_project_access(db_rel.project_id, user_id, db)
2025-10-30 11:14:43 +08:00
# 更新字段
update_data = relationship.model_dump(exclude_unset=True)
for field, value in update_data.items():
setattr(db_rel, field, value)
await db.commit()
await db.refresh(db_rel)
logger.info(f"更新关系成功:{relationship_id}")
return db_rel
@router.delete("/{relationship_id}", summary="删除关系")
async def delete_relationship(
relationship_id: str,
2025-11-10 21:16:55 +08:00
request: Request,
2025-10-30 11:14:43 +08:00
db: AsyncSession = Depends(get_db)
):
"""删除角色关系"""
result = await db.execute(
select(CharacterRelationship).where(
CharacterRelationship.id == relationship_id
)
)
db_rel = result.scalar_one_or_none()
if not db_rel:
raise HTTPException(status_code=404, detail="关系不存在")
2025-11-10 21:16:55 +08:00
# 验证用户权限
user_id = getattr(request.state, 'user_id', None)
await verify_project_access(db_rel.project_id, user_id, db)
2025-10-30 11:14:43 +08:00
await db.delete(db_rel)
await db.commit()
logger.info(f"删除关系成功:{relationship_id}")
return {"message": "关系删除成功", "id": relationship_id}