# 用户服务文档规范修复:移除外键约束 **日期**: 2026-01-27 **类型**: 文档修复 **影响范围**: 用户服务文档 ## 概述 修复用户服务文档 (`docs/requirements/backend/04-services/user/user-service.md`),使其符合项目技术栈规范,移除所有数据库外键约束,改为应用层验证引用完整性。 ## 变更内容 ### 1. 移除数据库外键约束 #### users 表 **修改前**: ```sql avatar_id UUID REFERENCES attachments(attachment_id) ON DELETE SET NULL ``` **修改后**: ```sql avatar_id UUID, -- 关联 attachments 表,应用层验证 ``` #### user_sessions 表 **修改前**: ```sql user_id UUID NOT NULL REFERENCES users(user_id) ON DELETE CASCADE ``` **修改后**: ```sql user_id UUID NOT NULL, -- 关联 users 表,应用层验证 ``` ### 2. 优化唯一性约束策略 **移除的表级约束**: - `users_phone_unique` - `users_email_unique` - `users_wechat_openid_unique` **保留的表级约束**: - `users_username_unique` (必填字段) **改用条件唯一索引**: ```sql -- 仅索引未删除且非空的记录 CREATE UNIQUE INDEX idx_users_phone ON users (phone, country_code) WHERE deleted_at IS NULL AND phone IS NOT NULL; CREATE UNIQUE INDEX idx_users_email_lower ON users (LOWER(email)) WHERE deleted_at IS NULL AND email IS NOT NULL; CREATE UNIQUE INDEX idx_users_wechat_openid ON users (wechat_openid, wechat_platform) WHERE deleted_at IS NULL AND wechat_openid IS NOT NULL; ``` ### 3. 添加表和列注释 ```sql COMMENT ON TABLE users IS '用户表 - 应用层保证引用完整性'; COMMENT ON COLUMN users.avatar_id IS '头像附件ID - 应用层验证,关联 attachments.attachment_id'; COMMENT ON TABLE user_sessions IS '用户会话表 - 应用层保证引用完整性'; COMMENT ON COLUMN user_sessions.user_id IS '用户ID - 应用层验证,关联 users.user_id'; ``` ### 4. Service 层添加引用完整性验证 #### upload_avatar 方法 ```python async def upload_avatar(self, user_id: UUID, file_content: bytes, filename: str): # 上传文件 avatar_url = await storage_service.upload_file(...) # 如果使用 avatar_id,验证附件是否存在(应用层引用完整性) # if avatar_id: # attachment = await attachment_repo.get_by_id(avatar_id) # if not attachment: # raise NotFoundError("附件不存在") return await self.repository.update(user_id, {'avatar_url': avatar_url}) ``` #### _create_session 方法 ```python async def _create_session(self, user_id: UUID, ...): """创建会话(应用层验证用户存在)""" # 验证用户是否存在(应用层引用完整性) user = await self.repository.get_by_id(user_id) if not user: raise NotFoundError("用户不存在") session = UserSession(...) return await self.repository.create_session(session) ``` ### 5. Python Model 更新 #### User 模型 ```python avatar_id: Optional[UUID] = Field( default=None, description="头像附件ID - 应用层验证,关联 attachments.attachment_id" ) ``` #### UserSession 模型 ```python user_id: UUID = Field( description="用户ID - 应用层验证,关联 users.user_id" ) ``` ### 6. Schema 层完善序列化 ```python class UserResponse(BaseModel): wechat_platform: Optional[str] = None @field_serializer('wechat_platform') def serialize_platform(self, value: Optional[int]) -> Optional[str]: """API 输出:整数 → 字符串""" if value is None: return None from app.models.user import WechatPlatform return WechatPlatform(value).to_string() ``` ## 技术原理 ### 为什么移除外键约束? 1. **性能优势**: 写入性能提升 15-30%,减少锁竞争 2. **扩展性**: 为分库分表、微服务化做准备 3. **灵活性**: 复杂业务逻辑在应用层更易实现 4. **迁移简单**: 表结构变更无需处理外键依赖 5. **行业实践**: 大型互联网公司的标准做法 ### 三层保证机制 1. **Repository 层**: 提供 `exists()` 方法检查记录是否存在 2. **Service 层**: 业务逻辑中验证所有引用关系 3. **后台任务**: 定期检查孤儿记录并告警 ## 影响评估 ### 文档层面 - ✅ 符合项目技术栈规范 - ✅ 与其他服务文档保持一致 - ✅ 提供完整的应用层验证示例 ### 代码层面 - ⚠️ 需要确保实际代码实现与文档一致 - ⚠️ 需要在 Service 层补充引用完整性验证 - ⚠️ 建议添加后台任务检查孤儿记录 ### 数据库层面 - ✅ 提升写入性能 - ✅ 简化表结构变更 - ✅ 为分库分表做准备 ## 后续建议 1. **代码实现对齐**: 确保 `server/app/services/user_service.py` 实现与文档一致 2. **添加后台任务**: 实现定期检查孤儿记录的 Celery 任务 3. **完善测试用例**: 添加引用完整性验证的单元测试 4. **监控告警**: 配置孤儿记录检测告警 ## 相关文档 - [技术栈规范 - 数据库设计](../../architecture/tech-stack.md) - [数据库设计规范 - 引用完整性](../../../requirements/backend/README.md) - [用户服务文档](../../../requirements/backend/04-services/user/user-service.md) ## 检查清单 - [x] 移除 users.avatar_id 外键约束 - [x] 移除 user_sessions.user_id 外键约束 - [x] 优化唯一性约束为条件索引 - [x] 添加表和列注释 - [x] Service 层添加验证逻辑 - [x] 更新 Python Model - [x] 完善 Schema 序列化 - [x] 更新文档版本号和变更记录 - [x] 创建 Changelog 文档 --- **修复人员**: Kiro AI **审核状态**: 待审核 **文档版本**: v2.3