# 充值服务 Model Relationship 配置补充 > **变更日期**:2026-01-28 > **变更类型**:功能完善 > **影响范围**:`server/app/models/recharge/order.py` --- ## 变更概述 为充值订单 Model 补充 Relationship 配置,明确定义与 User 和 CreditPackage 的关联关系,符合项目"禁止物理外键"的架构约束。 --- ## 变更背景 ### 问题 充值订单 Model (`RechargeOrder`) 缺少 Relationship 配置,无法通过 ORM 直接访问关联的用户和套餐对象。 ### 原因 项目移除了所有物理外键约束后,SQLAlchemy 无法自动推断关联关系,必须通过 `primaryjoin` 参数明确指定。 --- ## 变更内容 ### 1. 添加 TYPE_CHECKING 导入 ```python from typing import Optional, TYPE_CHECKING if TYPE_CHECKING: from app.models.user import User from app.models.credit import CreditPackage ``` **作用**: - 避免循环导入问题 - 仅在类型检查时导入相关模型 --- ### 2. 添加 Relationship 导入 ```python from sqlmodel import SQLModel, Field, Column, Index, Relationship ``` --- ### 3. 补充 Relationship 定义 ```python class RechargeOrder(SQLModel, table=True): # ... 字段定义 ... # 关系(无物理外键,使用 primaryjoin 明确指定关联条件) user: Optional["User"] = Relationship( sa_relationship_kwargs={ "primaryjoin": "RechargeOrder.user_id == User.user_id", "foreign_keys": "[RechargeOrder.user_id]", } ) package: Optional["CreditPackage"] = Relationship( sa_relationship_kwargs={ "primaryjoin": "RechargeOrder.package_id == CreditPackage.package_id", "foreign_keys": "[RechargeOrder.package_id]", } ) ``` --- ## 技术细节 ### primaryjoin 参数 明确指定两个表之间的关联条件: ```python "RechargeOrder.user_id == User.user_id" ``` **格式**:`"LeftTable.left_column == RightTable.right_column"` ### foreign_keys 参数 指定外键列,帮助 SQLAlchemy 推断关联方向: ```python "[RechargeOrder.user_id]" ``` ### 可选关系 `package` 关系是可选的(`Optional["CreditPackage"]`),因为用户可以选择: 1. 购买预设套餐(`package_id` 有值) 2. 自定义金额充值(`package_id` 为 `None`) --- ## 使用示例 ### 1. 通过 ORM 访问关联对象 ```python # 查询订单及其关联的用户 order = await session.get(RechargeOrder, order_id) if order.user: print(f"订单所属用户: {order.user.username}") # 查询订单及其关联的套餐 if order.package: print(f"套餐名称: {order.package.name}") print(f"套餐价格: {order.package.price}") ``` ### 2. 预加载关联对象(优化性能) ```python from sqlmodel import select from sqlalchemy.orm import selectinload # 一次查询加载订单和用户 statement = select(RechargeOrder).options( selectinload(RechargeOrder.user) ).where(RechargeOrder.order_id == order_id) result = await session.execute(statement) order = result.scalar_one_or_none() # 此时访问 order.user 不会触发额外查询 print(order.user.username) ``` ### 3. 反向查询(从 User 查询订单) 虽然 `User` Model 中没有定义反向关系,但可以通过查询实现: ```python # 查询用户的所有充值订单 statement = select(RechargeOrder).where( RechargeOrder.user_id == user_id ).order_by(RechargeOrder.created_at.desc()) result = await session.execute(statement) orders = result.scalars().all() ``` --- ## 验证结果 ### 代码检查 ```bash # 语法检查通过 ✅ server/app/models/recharge/order.py: No diagnostics found ``` ### 符合规范 ✅ 使用 `TYPE_CHECKING` 避免循环导入 ✅ 使用 `primaryjoin` 明确指定关联条件 ✅ 使用 `foreign_keys` 指定外键列 ✅ 符合项目"禁止物理外键"的架构约束 ✅ 与 `folder.py` 和 `project.py` 的 Relationship 配置模式一致 --- ## 影响范围 ### 修改的文件 - `server/app/models/recharge/order.py` ### 不影响的功能 - ✅ 数据库表结构不变 - ✅ 应用层引用完整性验证逻辑不变 - ✅ Repository 和 Service 代码不变 - ✅ API 接口不变 ### 新增功能 - ✅ 可以通过 ORM 直接访问关联对象 - ✅ 支持预加载优化查询性能 - ✅ 代码可读性提升 --- ## 最佳实践 ### 无物理外键项目的 Relationship 配置标准模式 ```python # 1. TYPE_CHECKING 导入 from typing import TYPE_CHECKING if TYPE_CHECKING: from app.models.related_model import RelatedModel # 2. 定义 Relationship class CurrentModel(SQLModel, table=True): # 关联字段 related_id: UUID = Field( sa_column=Column(PG_UUID(as_uuid=True), nullable=False, index=True), description="关联 ID - 应用层验证" ) # 关系定义 related: Optional["RelatedModel"] = Relationship( sa_relationship_kwargs={ "primaryjoin": "CurrentModel.related_id == RelatedModel.id", "foreign_keys": "[CurrentModel.related_id]", } ) ``` --- ## 相关文档 - [Model Relationship 配置修复](./2026-01-28-model-relationship-primaryjoin.md) - [充值服务文档](../../requirements/backend/04-services/user/recharge-service.md) - [数据库设计规范](../../../.claude/skills/jointo-tech-stack/references/database.md) --- ## 总结 成功为充值订单 Model 补充了 Relationship 配置,现在可以通过 ORM 直接访问关联的用户和套餐对象。修改符合项目架构约束,代码质量高,为后续开发提供了便利。 **关键改进**: 1. ✅ 补充了与 User 和 CreditPackage 的 Relationship 定义 2. ✅ 使用 `primaryjoin` 明确指定关联条件 3. ✅ 使用 `TYPE_CHECKING` 避免循环导入 4. ✅ 符合项目"禁止物理外键"的架构约束 5. ✅ 提升了代码可读性和可维护性