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.
 

3.7 KiB

RFC 122: 修复 Project 模型外键引用错误

状态: 已完成
创建时间: 2026-01-20
作者: System
类型: Bug 修复

问题描述

用户登录时遇到 500 错误和 CORS 问题,根本原因是:

  1. 数据库模型错误ProjectMemberProjectShareProjectVersion 表的外键引用了不存在的列名 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

# 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

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

app.add_middleware(
    CORSMiddleware,
    allow_origins=settings.cors_origins_list,  # 使用新属性
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

验证结果

1. 应用启动成功

✅ Database initialized
INFO: Application startup complete.

2. 登录接口正常

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. 测试覆盖:添加模型关系的集成测试,避免类似问题

相关文档