Skip to content

Commit adc2012

Browse files
committed
refactor: 去除 UserMixin 审计字段外键约束以消除循环外键
sys_user.dept_id 与 sys_dept(UserMixin 审计字段)互为外键形成循环依赖。原 dept_id 用 use_alter=True 规避,但 use_alter 在 Alembic autogenerate 下会被内联进 op.create_table,PostgreSQL 编译时跳过导致外键静默丢失。 移除 created_id/updated_id/deleted_id 三个审计字段指向 sys_user 的外键约束(ondelete=SET NULL 已允许悬空,审计字段无需强引用),并为 created_by/updated_by/deleted_by 关系显式补充 primaryjoin。循环依赖消除后,dept_id 的 use_alter 亦可移除,autogenerate 可正确生成迁移。
1 parent c5e45db commit adc2012

2 files changed

Lines changed: 23 additions & 11 deletions

File tree

‎backend/app/core/base_model.py‎

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from datetime import UTC, datetime
22

3-
from sqlalchemy import Boolean, DateTime, ForeignKey, Index, Integer, MetaData, String
3+
from sqlalchemy import Boolean, DateTime, Index, Integer, MetaData, String
44
from sqlalchemy.ext.asyncio import AsyncAttrs
55
from sqlalchemy.orm import DeclarativeBase, Mapped, declared_attr, mapped_column, relationship
66

@@ -132,15 +132,13 @@ class UserMixin(MappedBase):
132132

133133
created_id: Mapped[int | None] = mapped_column(
134134
Integer,
135-
ForeignKey("sys_user.id", ondelete="SET NULL", onupdate="CASCADE"),
136135
default=None,
137136
nullable=True,
138137
index=True,
139138
comment="创建人ID",
140139
)
141140
updated_id: Mapped[int | None] = mapped_column(
142141
Integer,
143-
ForeignKey("sys_user.id", ondelete="SET NULL", onupdate="CASCADE"),
144142
default=None,
145143
nullable=True,
146144
index=True,
@@ -149,7 +147,6 @@ class UserMixin(MappedBase):
149147

150148
deleted_id: Mapped[int | None] = mapped_column(
151149
Integer,
152-
ForeignKey("sys_user.id", ondelete="SET NULL", onupdate="CASCADE"),
153150
default=None,
154151
nullable=True,
155152
index=True,
@@ -161,7 +158,7 @@ def created_by(self):
161158
"""创建人关联关系"""
162159
return relationship(
163160
"UserModel",
164-
foreign_keys=lambda: self.created_id, # pyright: ignore[reportArgumentType]
161+
primaryjoin=f"foreign({self.__name__}.created_id) == remote(UserModel.id)",
165162
uselist=False,
166163
)
167164

@@ -170,7 +167,7 @@ def updated_by(self):
170167
"""更新人关联关系"""
171168
return relationship(
172169
"UserModel",
173-
foreign_keys=lambda: self.updated_id, # pyright: ignore[reportArgumentType]
170+
primaryjoin=f"foreign({self.__name__}.updated_id) == remote(UserModel.id)",
174171
uselist=False,
175172
)
176173

@@ -179,6 +176,6 @@ def deleted_by(self):
179176
"""删除人关联关系"""
180177
return relationship(
181178
"UserModel",
182-
foreign_keys=lambda: self.deleted_id, # pyright: ignore[reportArgumentType]
179+
primaryjoin=f"foreign({self.__name__}.deleted_id) == remote(UserModel.id)",
183180
uselist=False,
184181
)

‎backend/app/modules/system/user/model.py‎

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,25 @@ class UserModel(ModelMixin, UserMixin):
8181
status: Mapped[int] = mapped_column(Integer, default=0, nullable=False, comment="状态(0:启动 1:停用)")
8282
description: Mapped[str | None] = mapped_column(Text, default=None, nullable=True, comment="备注")
8383

84-
dept_id: Mapped[int | None] = mapped_column(Integer, ForeignKey("sys_dept.id", ondelete="SET NULL", onupdate="CASCADE", use_alter=True), nullable=True, index=True, comment="部门ID")
84+
dept_id: Mapped[int | None] = mapped_column(Integer, ForeignKey("sys_dept.id", ondelete="SET NULL", onupdate="CASCADE"), nullable=True, index=True, comment="部门ID")
8585
dept: Mapped["DeptModel | None"] = relationship(back_populates="users", foreign_keys=[dept_id])
8686
roles: Mapped[list["RoleModel"]] = relationship(secondary="sys_user_roles", back_populates="users")
8787
positions: Mapped[list["PositionModel"]] = relationship(secondary="sys_user_positions", back_populates="users")
88-
created_by: Mapped["UserModel | None"] = relationship("UserModel", foreign_keys="UserModel.created_id", remote_side="UserModel.id", uselist=False, viewonly=True)
89-
updated_by: Mapped["UserModel | None"] = relationship("UserModel", foreign_keys="UserModel.updated_id", remote_side="UserModel.id", uselist=False, viewonly=True)
90-
deleted_by: Mapped["UserModel | None"] = relationship("UserModel", foreign_keys="UserModel.deleted_id", remote_side="UserModel.id", uselist=False, viewonly=True)
88+
created_by: Mapped["UserModel | None"] = relationship(
89+
"UserModel",
90+
primaryjoin="foreign(UserModel.created_id) == remote(UserModel.id)",
91+
uselist=False,
92+
viewonly=True,
93+
)
94+
updated_by: Mapped["UserModel | None"] = relationship(
95+
"UserModel",
96+
primaryjoin="foreign(UserModel.updated_id) == remote(UserModel.id)",
97+
uselist=False,
98+
viewonly=True,
99+
)
100+
deleted_by: Mapped["UserModel | None"] = relationship(
101+
"UserModel",
102+
primaryjoin="foreign(UserModel.deleted_id) == remote(UserModel.id)",
103+
uselist=False,
104+
viewonly=True,
105+
)

0 commit comments

Comments
 (0)