You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

9.2 KiB

用户服务文档完善与代码修复

变更日期:2026-01-28
变更类型:文档更新 + 代码修复
影响范围docs/requirements/backend/04-services/user/user-service.md


变更概述

补充用户服务文档中的应用层关联关系处理说明,修复 Service 代码中的安全漏洞,提供完整的实现指导。


变更内容

1. 新增"引用完整性保证"专门章节

位置:在"服务实现"章节之后

内容

  1. 架构约束说明

    • 禁止数据库物理外键约束的原因
    • 应用层验证的必要性
  2. 关联关系表

    • 列出所有关联关系
    • 明确验证策略和删除策略
  3. 验证策略实现

    • 创建会话时验证用户存在
    • 上传头像时验证附件存在
    • 登出时验证会话所有权
    • 删除用户时级联删除会话
  4. Repository 层完整实现

    • 提供完整的 UserRepository 类定义
    • 包含所有 CRUD 方法
    • 包含级联删除方法
  5. 性能优化建议

    • 索引策略
    • 批量操作优化
  6. 错误处理说明

    • 常见错误场景表格
    • API 错误响应示例

2. 修复 Service 代码安全漏洞

2.1 修复 logout 方法

问题:没有验证会话是否属于该用户,存在安全漏洞

修复前

async def logout(
    self,
    user_id: UUID,
    access_token: str
) -> None:
    """用户登出(应用层级联删除会话)"""
    # 删除会话
    session = await self.repository.get_session_by_token(access_token)
    if session:
        await self.repository.delete_session(session.id)

修复后

async def logout(
    self,
    user_id: UUID,
    access_token: str
) -> None:
    """用户登出(应用层验证会话所有权)"""
    # 查找会话并验证所有权
    session = await self.repository.get_session_by_token(access_token)
    if not session:
        raise NotFoundError("会话不存在")
    
    # 验证会话是否属于该用户(安全性)
    if session.user_id != user_id:
        raise AuthenticationError("无权删除该会话")
    
    # 删除会话
    await self.repository.delete_session(session.session_id)

修复说明

  • 验证会话是否存在
  • 验证会话是否属于该用户
  • 防止用户删除其他用户的会话

2.2 补充 delete_user 方法

问题:缺少用户删除方法,没有说明级联删除逻辑

新增代码

async def delete_user(
    self,
    user_id: UUID
) -> None:
    """删除用户(应用层级联删除)"""
    # 验证用户是否存在
    user = await self.repository.get_by_id(user_id)
    if not user:
        raise NotFoundError("用户不存在")
    
    # 级联删除:删除用户的所有会话
    await self.repository.delete_sessions_by_user_id(user_id)
    
    # 软删除用户
    await self.repository.update(user_id, {
        'deleted_at': datetime.utcnow()
    })
    
    # 注意:不删除用户的头像附件(可能被其他用户使用)
    # 注意:不删除用户的积分记录(保留审计日志)

说明

  • 软删除用户(设置 deleted_at 字段)
  • 级联删除用户的所有会话
  • 不删除头像附件(可能被其他用户使用)
  • 不删除积分记录(保留审计日志)

2.3 优化 upload_avatar 注释

修改前

# 如果使用 avatar_id,验证附件是否存在(应用层引用完整性)

修改后

# 如果使用 avatar_id 关联附件表,需要验证附件是否存在
# 应用层引用完整性验证

说明

  • 更清晰地说明验证的目的
  • 强调应用层引用完整性验证

3. 补充 Repository 层完整实现

新增内容

# app/repositories/user_repository.py
class UserRepository:
    def __init__(self, db: AsyncSession):
        self.db = db

    # ==================== User CRUD ====================
    async def create(self, user: User) -> User: ...
    async def get_by_id(self, user_id: UUID) -> Optional[User]: ...
    async def get_by_phone(self, phone: str, country_code: str) -> Optional[User]: ...
    async def get_by_email(self, email: str) -> Optional[User]: ...
    async def get_by_username(self, username: str) -> Optional[User]: ...
    async def get_by_wechat_openid(self, openid: str, platform: int) -> Optional[User]: ...
    async def update(self, user_id: UUID, data: dict) -> User: ...

    # ==================== Session CRUD ====================
    async def create_session(self, session: UserSession) -> UserSession: ...
    async def get_session_by_token(self, token: str) -> Optional[UserSession]: ...
    async def get_session_by_refresh_token(self, refresh_token: str) -> Optional[UserSession]: ...
    async def get_sessions_by_user_id(self, user_id: UUID) -> List[UserSession]: ...
    async def update_session(self, session_id: UUID, data: dict) -> UserSession: ...
    async def delete_session(self, session_id: UUID) -> None: ...
    async def delete_sessions_by_user_id(self, user_id: UUID) -> None: ...

说明

  • 提供完整的 Repository 层实现
  • 包含所有必要的查询方法
  • 包含级联删除方法 delete_sessions_by_user_id

4. 补充 API 接口文档

4.1 新增"删除用户"接口

DELETE /api/v1/users/me

响应

{
  "message": "用户已删除"
}

说明

  • 软删除用户(设置 deleted_at 字段)
  • 级联删除用户的所有会话
  • 不删除用户的头像附件(可能被其他用户使用)
  • 不删除用户的积分记录(保留审计日志)

5. 修复 Model 定义

5.1 修正 UserSession.user_id 字段

问题:缺少 sa_column 定义,导致类型不明确

修复前

user_id: UUID = Field(
    description="用户ID - 应用层验证,关联 users.user_id"
)

修复后

user_id: UUID = Field(
    sa_column=Column(PG_UUID(as_uuid=True)),
    description="用户ID - 应用层验证,关联 users.user_id"
)

说明

  • 明确指定 UUID 类型
  • 确保数据库字段类型正确

关联关系表

表名 字段 关联目标 验证策略 删除策略
users avatar_id attachments.attachment_id 创建/更新时验证附件存在 用户删除时不删除附件(可能被其他用户使用)
user_sessions user_id users.user_id 创建会话时验证用户存在 用户删除时级联删除所有会话

错误处理

常见错误场景

场景 错误类型 HTTP 状态码 错误消息
创建会话时用户不存在 NotFoundError 404 "用户不存在"
上传头像时附件不存在 NotFoundError 404 "附件不存在"
登出时会话不存在 NotFoundError 404 "会话不存在"
登出时会话不属于该用户 AuthenticationError 403 "无权删除该会话"
删除用户时用户不存在 NotFoundError 404 "用户不存在"

API 错误响应示例

404 Not Found

{
  "error": {
    "code": "NOT_FOUND",
    "message": "用户不存在",
    "details": {
      "user_id": "550e8400-e29b-41d4-a716-446655440000"
    }
  }
}

403 Forbidden

{
  "error": {
    "code": "FORBIDDEN",
    "message": "无权删除该会话",
    "details": {
      "session_id": "660e8400-e29b-41d4-a716-446655440001",
      "user_id": "550e8400-e29b-41d4-a716-446655440000"
    }
  }
}

验证结果

文档完整性检查

架构约束说明完整
关联关系表清晰
验证策略实现完整
Repository 层实现完整
错误处理说明完整
API 接口文档完整

代码安全性检查

logout 方法验证会话所有权
delete_user 方法实现级联删除
_create_session 方法验证用户存在
upload_avatar 方法注释清晰


后续工作

代码实现建议

  1. 实现 UserRepository

    • 参考文档中的完整实现
    • 确保所有方法都已实现
  2. 实现 UserService

    • 补充 delete_user 方法
    • 修复 logout 方法的安全漏洞
    • 优化 upload_avatar 方法
  3. 添加单元测试

    • 测试会话所有权验证
    • 测试级联删除逻辑
    • 测试错误处理
  4. 添加集成测试

    • 测试完整的用户删除流程
    • 测试登出流程
    • 测试头像上传流程

相关文档


总结

本次变更补充了用户服务文档中缺失的应用层关联关系处理说明,修复了 Service 代码中的安全漏洞,提供了完整的 Repository 层实现和错误处理说明。文档现在提供了清晰的实现指导,确保开发人员能够正确实现应用层引用完整性验证。