# RFC 122: 修复 Project 模型外键引用错误 **状态**: ✅ 已完成 **创建时间**: 2026-01-20 **作者**: System **类型**: Bug 修复 ## 问题描述 用户登录时遇到 500 错误和 CORS 问题,根本原因是: 1. **数据库模型错误**:`ProjectMember`、`ProjectShare`、`ProjectVersion` 表的外键引用了不存在的列名 `projects.project_id` 2. **CORS 配置问题**:`.env` 中的 `CORS_ORIGINS` 字符串格式无法被 Pydantic 正确解析为列表 ### 错误日志 ``` sqlalchemy.exc.InvalidRequestError: Could not initialize target column for ForeignKey 'projects.project_id' on table 'project_members': table 'projects' has no column named 'project_id' ``` ### 根本原因 `Project` 模型使用了 `alias="project_id"`,但这只是 API 层面的别名,数据库实际列名仍是 `id`。外键定义错误地引用了别名而非实际列名。 ## 解决方案 ### 1. 修复外键引用 将所有外键从 `foreign_key="projects.project_id"` 改为 `foreign_key="projects.id"`: **修改文件**: `server/app/models/project.py` ```python # ProjectMember project_id: UUID = Field(foreign_key="projects.id", index=True) # ProjectShare project_id: UUID = Field(foreign_key="projects.id", index=True) # ProjectVersion project_id: UUID = Field(foreign_key="projects.id", index=True) ``` ### 2. 修复 CORS 配置解析 **修改文件**: `server/app/core/config.py` ```python class Settings(BaseSettings): # CORS 配置 CORS_ORIGINS: str = '["http://localhost:6160"]' @property def cors_origins_list(self) -> List[str]: """解析 CORS_ORIGINS 为列表""" import json if isinstance(self.CORS_ORIGINS, str): try: return json.loads(self.CORS_ORIGINS) except json.JSONDecodeError: return [self.CORS_ORIGINS] return self.CORS_ORIGINS if isinstance(self.CORS_ORIGINS, list) else [self.CORS_ORIGINS] ``` **修改文件**: `server/app/main.py` ```python app.add_middleware( CORSMiddleware, allow_origins=settings.cors_origins_list, # 使用新属性 allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) ``` ## 验证结果 ### 1. 应用启动成功 ```bash ✅ Database initialized INFO: Application startup complete. ``` ### 2. 登录接口正常 ```bash curl -X POST http://192.168.31.178:6170/api/v1/auth/login/phone \ -H "Content-Type: application/json" \ -d '{"phone":"13800138000","country_code":"+86","code":"6666"}' # 返回 200 OK,包含正确的 CORS 头 access-control-allow-origin: http://192.168.31.178:6160 ``` ## 技术要点 ### SQLModel 字段别名 vs 数据库列名 - `Field(alias="xxx")` 只影响 API 序列化/反序列化 - 数据库列名由字段名决定(除非使用 `sa_column=Column(name="xxx")`) - 外键引用必须使用实际数据库列名 ### Pydantic 环境变量解析 - 复杂类型(List、Dict)在 `.env` 中需要 JSON 字符串格式 - 使用 `@property` 提供灵活的类型转换 - 支持字符串和列表两种输入格式 ## 影响范围 - ✅ 修复用户登录功能 - ✅ 修复 CORS 跨域问题 - ✅ 修复项目相关表的外键约束 - ✅ 无需数据迁移(表结构未变) ## 后续建议 1. **统一命名规范**:避免在模型中使用 `alias`,保持字段名与数据库列名一致 2. **配置验证**:在应用启动时验证 CORS 配置是否正确解析 3. **测试覆盖**:添加模型关系的集成测试,避免类似问题 ## 相关文档 - [RFC 121: Project Service Implementation](./121-project-service-implementation.md) - [ADR 003: Screenplay Resource Architecture](../../architecture/adrs/003-screenplay-resource-architecture.md)