refactor(database): 重构系统表命名及数据隔离逻辑

- 将系统表前缀从'system_'统一改为'sys_',包括用户、角色、部门等核心表
- 优化数据权限检查逻辑,支持初始化场景跳过权限验证
- 更新初始化脚本和模型模板,确保与新的表结构兼容
- 修复字典服务中is_default字段类型定义问题
- 调整控制台显示的数据库类型描述
- 添加业务逻辑文档,详细说明系统架构和核心功能

docs: 添加系统业务逻辑文档和多租户设计参考
This commit is contained in:
zhangtao
2025-11-29 14:54:40 +08:00
parent a96c2f32a8
commit 7e4eda0671
48 changed files with 6627 additions and 8868 deletions
@@ -280,7 +280,8 @@ class SchedulerUtil:
scheduler.start()
async with async_db_session() as session:
async with session.begin():
auth = AuthSchema(db=session)
# 在初始化过程中,不需要检查数据权限
auth = AuthSchema(db=session, check_data_scope=False)
job_list = await JobCRUD(auth).get_obj_list_crud()
for item in job_list:
@@ -71,7 +71,7 @@ class GenTableModel(ModelMixin):
return class_name.strip()
class GenTableColumnModel(ModelMixin, UserMixin, TenantMixin):
class GenTableColumnModel(ModelMixin):
"""
代码生成表字段
@@ -9,16 +9,16 @@ from sqlalchemy.orm import relationship
from typing import Optional
from sqlalchemy.orm import Mapped, mapped_column
from app.core.base_model import CreatorMixin
from app.core.base_model import ModelMixin, UserMixin, TenantMixin, CustomerMixin
class {{ class_name }}Model(CreatorMixin):
class {{ class_name }}Model(ModelMixin, UserMixin, TenantMixin, CustomerMixin):
"""
{{ function_name }}表
"""
__tablename__ = '{{ table_name }}'
__table_args__ = {'comment': '{{ function_name }}'}
__loader_options__ = ["creator"]
__loader_options__: list[str] = ["created_by", "updated_by", "tenant", "customer"]
{% for column in columns %}
{% if column.column_name not in ['id', 'created_id', 'description', 'created_time', 'updated_time'] %}
@@ -1,45 +0,0 @@
# -*- coding: utf-8 -*-
from typing import Optional
from fastapi import Query
from app.core.validator import DateTimeStr
class {{ class_name }}QueryParam:
"""{{ function_name }}查询参数"""
def __init__(
self,
{% for column in columns %}
{% if column.query_type == 'LIKE' %}
{{ column.column_name }}: Optional[{{ column.python_type }}] = Query(None, description="{{ column.column_comment }}"),
{% endif %}
{% endfor %}
{% for column in columns %}
{% if column.query_type == 'EQ' %}
{{ column.column_name }}: Optional[{{ column.python_type }}] = Query(None, description="{{ column.column_comment }}"),
{% endif %}
{% endfor %}
creator: Optional[int] = Query(None, description="创建人"),
start_time: Optional[DateTimeStr] = Query(None, description="开始时间", example="2025-01-01 00:00:00"),
end_time: Optional[DateTimeStr] = Query(None, description="结束时间", example="2025-12-31 23:59:59"),
) -> None:
# 模糊查询字段
{% for column in columns %}
{% if column.query_type == 'LIKE' %}
self.{{ column.column_name }} = ("like", {{ column.column_name }})
{% endif %}
{% endfor %}
# 精确查询字段
{% for column in columns %}
{% if column.query_type == 'EQ' %}
self.{{ column.column_name }} = {{ column.column_name }}
{% endif %}
{% endfor %}
self.created_id = creator
# 时间范围查询
if start_time and end_time:
self.created_time = ("between", (start_time, end_time))
@@ -7,7 +7,9 @@ from typing import Optional
{% endif %}
from pydantic import BaseModel, ConfigDict, Field
from app.core.base_schema import BaseSchema
from fastapi import Query
from app.core.validator import DateTimeStr
from app.core.base_schema import BaseSchema, UserBySchema, TenantSchema, CustomerSchema
class {{ class_name }}CreateSchema(BaseModel):
"""
@@ -32,3 +34,42 @@ class {{ class_name }}OutSchema({{ class_name }}CreateSchema, BaseSchema):
{{ function_name }}响应模型
"""
model_config = ConfigDict(from_attributes=True)
class {{ class_name }}QueryParam:
"""{{ function_name }}查询参数"""
def __init__(
self,
{% for column in columns %}
{% if column.query_type == 'LIKE' %}
{{ column.column_name }}: Optional[{{ column.python_type }}] = Query(None, description="{{ column.column_comment }}"),
{% endif %}
{% endfor %}
{% for column in columns %}
{% if column.query_type == 'EQ' %}
{{ column.column_name }}: Optional[{{ column.python_type }}] = Query(None, description="{{ column.column_comment }}"),
{% endif %}
{% endfor %}
created_id: Optional[int] = Query(None, description="创建人"),
created_time: Optional[list[DateTimeStr]] = Query(None, description="创建时间范围", example=["2025-01-01 00:00:00", "2025-12-31 23:59:59"]),
) -> None:
# 模糊查询字段
{% for column in columns %}
{% if column.query_type == 'LIKE' %}
self.{{ column.column_name }} = ("like", {{ column.column_name }})
{% endif %}
{% endfor %}
# 精确查询字段
{% for column in columns %}
{% if column.query_type == 'EQ' %}
self.{{ column.column_name }} = {{ column.column_name }}
{% endif %}
{% endfor %}
self.created_id = created_id
# 时间范围查询
if created_time and len(created_time) == 2:
self.created_time = ("between", (created_time[0], created_time[1]))
@@ -5,8 +5,6 @@ from sqlalchemy import String
from sqlalchemy.orm import Mapped, mapped_column, relationship, validates
from app.core.base_model import ModelMixin, UserMixin, TenantMixin
if TYPE_CHECKING:
from app.api.v1.module_system.user.model import UserModel
@@ -25,7 +23,7 @@ class CustomerModel(ModelMixin, UserMixin, TenantMixin):
- 代理商系统: 租户=总公司, 客户=各地代理商
- SaaS平台: 租户=企业, 客户=企业下的子公司/部门
"""
__tablename__: str = 'system_customer'
__tablename__: str = 'sys_customer'
__table_args__: dict[str, str] = ({'comment': '客户表'})
__loader_options__: list[str] = ["created_by", "updated_by", "tenant"]
@@ -20,7 +20,7 @@ class DeptModel(ModelMixin, TenantMixin):
- 部门不属于客户(customer_id不需要)
- 支持无限层级嵌套的树形结构
"""
__tablename__: str = "system_dept"
__tablename__: str = "sys_dept"
__table_args__: dict[str, str] = ({'comment': '部门表'})
__loader_options__: list[str] = ["tenant"]
@@ -34,7 +34,7 @@ class DeptModel(ModelMixin, TenantMixin):
# 树形结构字段
parent_id: Mapped[int | None] = mapped_column(
Integer,
ForeignKey("system_dept.id", ondelete="SET NULL", onupdate="CASCADE"),
ForeignKey("sys_dept.id", ondelete="SET NULL", onupdate="CASCADE"),
default=None,
index=True,
comment="父级部门ID"
@@ -52,7 +52,7 @@ class DeptModel(ModelMixin, TenantMixin):
lazy="selectin"
)
roles: Mapped[list["RoleModel"]] = relationship(
secondary="system_role_depts",
secondary="sys_role_depts",
back_populates="depts",
lazy="selectin"
)
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
from sqlalchemy import String, Integer, Boolean, ForeignKey, Index
from sqlalchemy import String, Integer, Boolean, ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.core.base_model import ModelMixin
@@ -10,7 +10,7 @@ class DictTypeModel(ModelMixin):
"""
字典类型表
"""
__tablename__: str = "system_dict_type"
__tablename__: str = "sys_dict_type"
__table_args__: dict[str, str] = ({'comment': '字典类型表'})
dict_name: Mapped[str] = mapped_column(String(255), nullable=False, comment='字典名称')
@@ -24,7 +24,7 @@ class DictDataModel(ModelMixin):
"""
字典数据表
"""
__tablename__: str = "system_dict_data"
__tablename__: str = "sys_dict_data"
__table_args__: dict[str, str] = ({'comment': '字典数据表'})
dict_sort: Mapped[int] = mapped_column(Integer, nullable=False, default=0, comment='字典排序')
@@ -38,7 +38,7 @@ class DictDataModel(ModelMixin):
# 添加外键关系,同时保留dict_type字段用于业务查询
dict_type_id: Mapped[int] = mapped_column(
Integer,
ForeignKey('system_dict_type.id', ondelete='CASCADE'),
ForeignKey('sys_dict_type.id', ondelete='CASCADE'),
nullable=False,
comment='字典类型ID'
)
@@ -80,7 +80,7 @@ class DictDataCreateSchema(BaseModel):
dict_type_id: int = Field(..., description='字典类型ID')
css_class: Optional[str] = Field(default=None, max_length=100, description='样式属性(其他样式扩展)')
list_class: Optional[str] = Field(default=None, description='表格回显样式')
is_default: Optional[str] = Field(default=None, description='是否默认(Y是 N否)')
is_default: Optional[bool] = Field(default=None, description='是否默认(Y是 N否)')
status: Optional[str] = Field(default=None, description='状态(1正常 0停用)')
description: Optional[str] = Field(default=None, max_length=255, description="描述")
@@ -129,6 +129,7 @@ class DictTypeService:
dict_label=item.dict_label,
dict_value=item.dict_value,
dict_type=data.dict_type,
dict_type_id=item.dict_type_id,
css_class=item.css_class,
list_class=item.list_class,
is_default=item.is_default,
@@ -291,7 +292,8 @@ class DictDataService:
try:
async with async_db_session() as session:
async with session.begin():
auth = AuthSchema(db=session)
# 在初始化过程中,不需要检查数据权限
auth = AuthSchema(db=session, check_data_scope=False)
obj_list = await DictTypeCRUD(auth).get_obj_list_crud()
if not obj_list:
log.warning("未找到任何字典类型数据")
@@ -25,7 +25,7 @@ class OperationLogModel(ModelMixin, UserMixin, TenantMixin, CustomerMixin):
- 1: 登录日志
- 2: 操作日志
"""
__tablename__: str = "system_log"
__tablename__: str = "sys_log"
__table_args__: dict[str, str] = ({'comment': '系统日志表'})
__loader_options__: list[str] = ["created_by", "updated_by", "tenant", "customer"]
@@ -37,7 +37,7 @@ class MenuModel(ModelMixin):
支持树形结构(通过parent_id自关联)
"""
__tablename__: str = "system_menu"
__tablename__: str = "sys_menu"
__table_args__: dict[str, str] = ({'comment': '菜单表'})
__loader_options__: list[str] = ["roles"]
@@ -60,7 +60,7 @@ class MenuModel(ModelMixin):
# 树形结构
parent_id: Mapped[int | None] = mapped_column(
Integer,
ForeignKey('system_menu.id', ondelete='SET NULL'),
ForeignKey('sys_menu.id', ondelete='SET NULL'),
default=None,
index=True,
comment='父菜单ID'
@@ -79,7 +79,7 @@ class MenuModel(ModelMixin):
order_by="MenuModel.order"
)
roles: Mapped[list["RoleModel"]] = relationship(
secondary="system_role_menus",
secondary="sys_role_menus",
back_populates="menus",
lazy="selectin"
)
@@ -29,7 +29,7 @@ class NoticeModel(ModelMixin, UserMixin, TenantMixin, CustomerMixin):
- 创建人和创建时间
- 通知的可见范围和发布状态
"""
__tablename__: str = "system_notice"
__tablename__: str = "sys_notice"
__table_args__: dict[str, str] = ({'comment': '通知公告表'})
__loader_options__: list[str] = ["created_by", "updated_by", "tenant", "customer"]
@@ -30,7 +30,7 @@ class ParamsModel(ModelMixin):
- 租户级别参数
- 配置项的名称值和类型等信息
"""
__tablename__: str = "system_param"
__tablename__: str = "sys_param"
__table_args__: dict[str, str] = ({'comment': '系统参数表'})
config_name: Mapped[str] = mapped_column(String(500), nullable=False, comment='参数名称')
@@ -278,7 +278,8 @@ class ParamsService:
"""
async with async_db_session() as session:
async with session.begin():
auth = AuthSchema(db=session)
# 在初始化过程中,不需要检查数据权限
auth = AuthSchema(db=session, check_data_scope=False)
config_obj = await ParamsCRUD(auth).get_obj_list_crud()
if not config_obj:
raise CustomException(msg="系统配置不存在")
@@ -25,7 +25,7 @@ class PositionModel(ModelMixin, UserMixin, TenantMixin):
- 权限控制: 可以根据岗位分配特定权限
- 业务流程: 某些审批流可以指定特定岗位处理
"""
__tablename__: str = "system_position"
__tablename__: str = "sys_position"
__table_args__: dict[str, str] = ({'comment': '岗位表'})
__loader_options__: list[str] = ["users", "created_by", "updated_by", "tenant"]
@@ -34,7 +34,7 @@ class PositionModel(ModelMixin, UserMixin, TenantMixin):
# 关联关系 (继承自UserMixin和TenantMixin)
users: Mapped[list["UserModel"]] = relationship(
secondary="system_user_positions",
secondary="sys_user_positions",
back_populates="positions",
lazy="selectin"
)
+11 -11
View File
@@ -18,18 +18,18 @@ class RoleMenusModel(MappedBase):
定义角色与菜单的多对多关系用于权限控制
"""
__tablename__: str = "system_role_menus"
__tablename__: str = "sys_role_menus"
__table_args__: dict[str, str] = ({'comment': '角色菜单关联表'})
role_id: Mapped[int] = mapped_column(
Integer,
ForeignKey("system_role.id", ondelete="CASCADE", onupdate="CASCADE"),
ForeignKey("sys_role.id", ondelete="CASCADE", onupdate="CASCADE"),
primary_key=True,
comment="角色ID"
)
menu_id: Mapped[int] = mapped_column(
Integer,
ForeignKey("system_menu.id", ondelete="CASCADE", onupdate="CASCADE"),
ForeignKey("sys_menu.id", ondelete="CASCADE", onupdate="CASCADE"),
primary_key=True,
comment="菜单ID"
)
@@ -42,18 +42,18 @@ class RoleDeptsModel(MappedBase):
定义角色与部门的多对多关系用于数据权限控制
仅当角色的data_scope=5(自定义数据权限)时使用此表
"""
__tablename__: str = "system_role_depts"
__tablename__: str = "sys_role_depts"
__table_args__: dict[str, str] = ({'comment': '角色部门关联表'})
role_id: Mapped[int] = mapped_column(
Integer,
ForeignKey("system_role.id", ondelete="CASCADE", onupdate="CASCADE"),
ForeignKey("sys_role.id", ondelete="CASCADE", onupdate="CASCADE"),
primary_key=True,
comment="角色ID"
)
dept_id: Mapped[int] = mapped_column(
Integer,
ForeignKey("system_dept.id", ondelete="CASCADE", onupdate="CASCADE"),
ForeignKey("sys_dept.id", ondelete="CASCADE", onupdate="CASCADE"),
primary_key=True,
comment="部门ID"
)
@@ -88,7 +88,7 @@ class RoleModel(ModelMixin, UserMixin, TenantMixin):
* 注意: 客户用户即使有此权限也只能看本客户数据
- 5: 自定义数据权限
* 实现: WHERE dept_id IN (SELECT dept_id FROM system_role_depts WHERE role_id IN current_user.role_ids)
* 实现: WHERE dept_id IN (SELECT dept_id FROM sys_role_depts WHERE role_id IN current_user.role_ids)
* 场景: 跨部门权限,如人事可以看多个指定部门
* 使用: 通过role_depts关联表指定可访问的部门列表
@@ -97,7 +97,7 @@ class RoleModel(ModelMixin, UserMixin, TenantMixin):
- 取所有角色data_scope的最大值(4>3>2>5>1)
- 5(自定义)需要合并所有角色关联的部门
"""
__tablename__: str = "system_role"
__tablename__: str = "sys_role"
__table_args__: dict[str, str] = ({'comment': '角色表'})
__loader_options__: list[str] = ["menus", "depts", "created_by", "updated_by", "tenant"]
@@ -113,18 +113,18 @@ class RoleModel(ModelMixin, UserMixin, TenantMixin):
# 关联关系 (继承自UserMixin和TenantMixin)
menus: Mapped[list["MenuModel"]] = relationship(
secondary="system_role_menus",
secondary="sys_role_menus",
back_populates="roles",
lazy="selectin",
order_by="MenuModel.order"
)
depts: Mapped[list["DeptModel"]] = relationship(
secondary="system_role_depts",
secondary="sys_role_depts",
back_populates="roles",
lazy="selectin"
)
users: Mapped[list["UserModel"]] = relationship(
secondary="system_user_roles",
secondary="sys_user_roles",
back_populates="roles",
lazy="selectin"
)
@@ -21,7 +21,7 @@ class TenantModel(ModelMixin):
- 租户表不需要customer_id字段(租户不属于客户)
- 但需要created_id/updated_id用于审计追踪
"""
__tablename__: str = 'system_tenant'
__tablename__: str = 'sys_tenant'
__table_args__: dict[str, str] = {'comment': '租户表'}
name: Mapped[str] = mapped_column(String(100), nullable=False, unique=True, comment='租户名称')
+10 -10
View File
@@ -19,18 +19,18 @@ class UserRolesModel(MappedBase):
定义用户与角色的多对多关系
"""
__tablename__: str = "system_user_roles"
__tablename__: str = "sys_user_roles"
__table_args__: dict[str, str] = ({'comment': '用户角色关联表'})
user_id: Mapped[int] = mapped_column(
Integer,
ForeignKey("system_user.id", ondelete="CASCADE", onupdate="CASCADE"),
ForeignKey("sys_user.id", ondelete="CASCADE", onupdate="CASCADE"),
primary_key=True,
comment="用户ID"
)
role_id: Mapped[int] = mapped_column(
Integer,
ForeignKey("system_role.id", ondelete="CASCADE", onupdate="CASCADE"),
ForeignKey("sys_role.id", ondelete="CASCADE", onupdate="CASCADE"),
primary_key=True,
comment="角色ID"
)
@@ -42,18 +42,18 @@ class UserPositionsModel(MappedBase):
定义用户与岗位的多对多关系
"""
__tablename__: str = "system_user_positions"
__tablename__: str = "sys_user_positions"
__table_args__: dict[str, str] = ({'comment': '用户岗位关联表'})
user_id: Mapped[int] = mapped_column(
Integer,
ForeignKey("system_user.id", ondelete="CASCADE", onupdate="CASCADE"),
ForeignKey("sys_user.id", ondelete="CASCADE", onupdate="CASCADE"),
primary_key=True,
comment="用户ID"
)
position_id: Mapped[int] = mapped_column(
Integer,
ForeignKey("system_position.id", ondelete="CASCADE", onupdate="CASCADE"),
ForeignKey("sys_position.id", ondelete="CASCADE", onupdate="CASCADE"),
primary_key=True,
comment="岗位ID"
)
@@ -97,7 +97,7 @@ class UserModel(ModelMixin, UserMixin, TenantMixin, CustomerMixin):
客户用户额外限制:
- 无论data_scope如何,都必须加上: AND customer_id = current_user.customer_id
"""
__tablename__: str = "system_user"
__tablename__: str = "sys_user"
__table_args__: dict[str, str] = ({'comment': '用户表'})
__loader_options__: list[str] = ["dept", "roles", "positions", "created_by", "updated_by", "tenant", "customer"]
@@ -120,7 +120,7 @@ class UserModel(ModelMixin, UserMixin, TenantMixin, CustomerMixin):
dept_id: Mapped[int | None] = mapped_column(
Integer,
ForeignKey('system_dept.id', ondelete="SET NULL", onupdate="CASCADE"),
ForeignKey('sys_dept.id', ondelete="SET NULL", onupdate="CASCADE"),
nullable=True,
index=True,
comment="部门ID"
@@ -131,12 +131,12 @@ class UserModel(ModelMixin, UserMixin, TenantMixin, CustomerMixin):
lazy="selectin"
)
roles: Mapped[list["RoleModel"]] = relationship(
secondary="system_user_roles",
secondary="sys_user_roles",
back_populates="users",
lazy="selectin"
)
positions: Mapped[list["PositionModel"]] = relationship(
secondary="system_user_positions",
secondary="sys_user_positions",
back_populates="users",
lazy="selectin"
)