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

充值服务 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"]),因为用户可以选择:

  1. 购买预设套餐(package_id 有值)
  2. 自定义金额充值(package_idNone

使用示例

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.pyproject.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 直接访问关联的用户和套餐对象。修改符合项目架构约束,代码质量高,为后续开发提供了便利。

关键改进

  1. 补充了与 User 和 CreditPackage 的 Relationship 定义
  2. 使用 primaryjoin 明确指定关联条件
  3. 使用 TYPE_CHECKING 避免循环导入
  4. 符合项目"禁止物理外键"的架构约束
  5. 提升了代码可读性和可维护性