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.
5.8 KiB
5.8 KiB
充值服务 Model Relationship 配置补充
变更日期:2026-01-28
变更类型:功能完善
影响范围:server/app/models/recharge/order.py
变更概述
为充值订单 Model 补充 Relationship 配置,明确定义与 User 和 CreditPackage 的关联关系,符合项目"禁止物理外键"的架构约束。
变更背景
问题
充值订单 Model (RechargeOrder) 缺少 Relationship 配置,无法通过 ORM 直接访问关联的用户和套餐对象。
原因
项目移除了所有物理外键约束后,SQLAlchemy 无法自动推断关联关系,必须通过 primaryjoin 参数明确指定。
变更内容
1. 添加 TYPE_CHECKING 导入
from typing import Optional, TYPE_CHECKING
if TYPE_CHECKING:
from app.models.user import User
from app.models.credit import CreditPackage
作用:
- 避免循环导入问题
- 仅在类型检查时导入相关模型
2. 添加 Relationship 导入
from sqlmodel import SQLModel, Field, Column, Index, Relationship
3. 补充 Relationship 定义
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 参数
明确指定两个表之间的关联条件:
"RechargeOrder.user_id == User.user_id"
格式:"LeftTable.left_column == RightTable.right_column"
foreign_keys 参数
指定外键列,帮助 SQLAlchemy 推断关联方向:
"[RechargeOrder.user_id]"
可选关系
package 关系是可选的(Optional["CreditPackage"]),因为用户可以选择:
- 购买预设套餐(
package_id有值) - 自定义金额充值(
package_id为None)
使用示例
1. 通过 ORM 访问关联对象
# 查询订单及其关联的用户
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. 预加载关联对象(优化性能)
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 中没有定义反向关系,但可以通过查询实现:
# 查询用户的所有充值订单
statement = select(RechargeOrder).where(
RechargeOrder.user_id == user_id
).order_by(RechargeOrder.created_at.desc())
result = await session.execute(statement)
orders = result.scalars().all()
验证结果
代码检查
# 语法检查通过
✅ 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 配置标准模式
# 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 配置,现在可以通过 ORM 直接访问关联的用户和套餐对象。修改符合项目架构约束,代码质量高,为后续开发提供了便利。
关键改进:
- ✅ 补充了与 User 和 CreditPackage 的 Relationship 定义
- ✅ 使用
primaryjoin明确指定关联条件 - ✅ 使用
TYPE_CHECKING避免循环导入 - ✅ 符合项目"禁止物理外键"的架构约束
- ✅ 提升了代码可读性和可维护性