refactor(字段命名): 统一将created_at和updated_at重命名为created_time和updated_time

重构字段命名以保持一致性,将时间相关字段从created_at/updated_at统一改为created_time/updated_time
同时调整相关查询参数、服务层映射和模板文件中的字段命名
优化权限过滤逻辑,提取为独立的Permission类
This commit is contained in:
zhangtao
2025-11-24 01:53:01 +08:00
parent c0d32a7eec
commit e32bc0660e
42 changed files with 1435 additions and 1380 deletions
+8 -104
View File
@@ -9,14 +9,10 @@ from sqlalchemy import asc, func, select, delete, Select, desc, update, or_, and
from sqlalchemy import inspect as sa_inspect
from app.core.base_model import MappedBase
from app.utils.common_util import get_child_id_map, get_child_recursion
from app.core.exceptions import CustomException
from app.core.permission import Permission
from app.common.request import PageResultSchema
from app.core.serialize import Serialize
from app.api.v1.module_system.auth.schema import AuthSchema
from app.api.v1.module_system.dept.model import DeptModel
from app.api.v1.module_system.user.model import UserModel
ModelType = TypeVar("ModelType", bound=MappedBase)
CreateSchemaType = TypeVar("CreateSchemaType", bound=BaseModel)
@@ -322,105 +318,13 @@ class CRUDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]):
"""
过滤数据权限(仅用于Select)。
"""
perm = await self.__permission_condition()
if perm is None:
return sql
return sql.where(perm)
async def __permission_condition(self) -> Optional[ColumnElement]:
"""
构造权限过滤表达式,返回None表示不限制。
"""
# 如果不需要检查数据权限,则不限制
if not self.current_user or not self.auth.check_data_scope:
return None
# 如果模型没有创建人created_id字段,则不限制
if not hasattr(self.model, "created_id"):
return None
# 超级管理员可以查看所有数据
if getattr(self.current_user, "is_superuser", False):
return None
# 如果用户没有部门或角色,则只能查看自己的数据
if not getattr(self.current_user, "dept_id", None) or not getattr(self.current_user, "roles", None):
created_id_attr = getattr(self.model, "created_id", None)
if created_id_attr is not None:
return created_id_attr == self.current_user.id
return None
# 获取用户所有角色的权限范围
data_scopes = set()
dept_ids = set()
roles = getattr(self.current_user, "roles", []) or []
for role in roles:
# 角色的部门集合
if hasattr(role, 'depts') and role.depts:
for dept in role.depts:
dept_ids.add(dept.id)
data_scopes.add(role.data_scope)
# 如果有全部数据权限,直接返回
if 4 in data_scopes:
# 全部数据权限
return None
# 如果有自定义数据权限且部门ID存在,优先处理
if 5 in data_scopes and dept_ids:
# 自定义数据权限
creator_rel = getattr(self.model, "creator", None)
if hasattr(UserModel, 'dept_id') and creator_rel is not None:
return creator_rel.has(getattr(UserModel, 'dept_id').in_(list(dept_ids)))
else:
created_id_attr = getattr(self.model, "created_id", None)
if created_id_attr is not None:
return created_id_attr == self.current_user.id
return None
# 处理其他数据权限范围
dept_id_val = getattr(self.current_user, "dept_id", None)
if 1 in data_scopes:
# 仅本人数据
created_id_attr = getattr(self.model, "created_id", None)
if created_id_attr is not None:
return created_id_attr == self.current_user.id
return None
if 2 in data_scopes and dept_id_val is not None:
# 本部门数据
dept_ids.add(dept_id_val)
if 3 in data_scopes and dept_id_val is not None:
# 本部门及以下数据(查询所有部门并递归)
dept_sql = select(DeptModel)
dept_result = await self.db.execute(dept_sql)
dept_objs = dept_result.scalars().all()
id_map = get_child_id_map(dept_objs)
dept_child_ids = get_child_recursion(id=dept_id_val, id_map=id_map)
dept_ids.add(dept_id_val) # 包含本部门
for child_id in dept_child_ids:
dept_ids.add(child_id)
# 处理2、3汇总的数据权限
if (2 in data_scopes or 3 in data_scopes) and dept_ids:
# 使用关系creator进行筛选(若存在),否则回退到仅本人数据
creator_rel = getattr(self.model, "creator", None)
if hasattr(UserModel, 'dept_id') and creator_rel is not None and dept_ids:
return creator_rel.has(getattr(UserModel, 'dept_id').in_(list(dept_ids)))
else:
created_id_attr = getattr(self.model, "created_id", None)
if created_id_attr is not None:
return created_id_attr == self.current_user.id
return None
# 默认情况下,只能查看自己的数据
created_id_attr = getattr(self.model, "created_id", None)
if created_id_attr is not None:
return created_id_attr == self.current_user.id
return None
filter = Permission(
db=self.db,
model=self.model,
current_user=self.current_user,
auth=self.auth
)
return await filter.filter_query(sql)
async def __build_conditions(self, **kwargs) -> List[ColumnElement]:
"""
+2 -2
View File
@@ -36,7 +36,7 @@ class PaginationQueryParam:
self.order_by.append({field.strip(): direction.strip().lower()})
except ValueError:
# 如果解析失败,使用默认排序
self.order_by = [{'updated_at': 'desc'}]
self.order_by = [{'updated_time': 'desc'}]
else:
self.order_by = [{'updated_at': 'desc'}]
self.order_by = [{'updated_time': 'desc'}]
+7 -3
View File
@@ -33,13 +33,17 @@ class BaseSchema(BaseModel):
description: Optional[str] = Field(default=None, description="描述")
created_time: Optional[DateTimeStr] = Field(default=None, description="创建时间")
updated_time: Optional[DateTimeStr] = Field(default=None, description="更新时间")
class BaseCreateSchema(BaseModel):
"""通用创建模型,包含基础字段和审计字段"""
model_config = ConfigDict(from_attributes=True)
created_id: Optional[int] = Field(default=None, description="创建人ID")
created_by: Optional[UserInfoSchema] = Field(default=None, description="创建人信息")
updated_id: Optional[int] = Field(default=None, description="更新人ID")
updated_by: Optional[UserInfoSchema] = Field(default=None, description="更新人信息")
class TenantSchema(BaseSchema):
class TenantSchema(BaseModel):
"""租户模型"""
model_config = ConfigDict(from_attributes=True)
@@ -47,7 +51,7 @@ class TenantSchema(BaseSchema):
tenant: Optional[CommonSchema] = Field(default=None, description="租户信息")
class CustomerSchema(BaseSchema):
class CustomerSchema(BaseModel):
"""客户模型"""
model_config = ConfigDict(from_attributes=True)
+2 -2
View File
@@ -94,9 +94,9 @@ async def get_current_user(
username=username,
preload=[
"dept",
selectinload(UserModel.roles).selectinload(RoleModel.creator),
selectinload(UserModel.roles).selectinload(RoleModel.created_by),
"positions",
"creator"
"created_by"
]
)
if not user:
+1 -1
View File
@@ -124,7 +124,7 @@ class RequestLogMiddleware(BaseHTTPMiddleware):
response_info = (
f"响应状态: {response.status_code}, "
f"响应内容长度: {content_length}, "
f"处理时间: {process_time * 1000}ms"
f"处理时间: {round(process_time * 1000, 3)}ms"
)
log.info(response_info)
+244 -281
View File
@@ -1,299 +1,262 @@
# -*- coding: utf-8 -*-
from typing import TypeVar, Generic
from sqlalchemy.orm import Session, Query
from typing import Optional, Set, List, Dict, Any, Union, Tuple
from sqlalchemy.sql.elements import ColumnElement
from sqlalchemy import select, and_
from sqlalchemy.ext.asyncio import AsyncSession
from app.api.v1.module_system.user.model import UserModel
from app.api.v1.module_system.dept.model import DeptModel
from app.api.v1.module_system.role.model import RoleModel
from app.api.v1.module_system.auth.schema import AuthSchema
from app.utils.common_util import get_child_id_map, get_child_recursion
T = TypeVar('T')
class PermissionMixin(Generic[T]):
class Permission:
"""
数据权限混入类
为业务模型提供数据权限过滤功能,支持五种权限类型:
1. 仅本人数据权限 - 只能查看自己创建的数据
2. 本部门数据权限 - 只能查看同部门的数据
3. 本部门及以下数据权限 - 可以查看本部门及所有子部门的数据
4. 全部数据权限 - 可以查看所有数据
5. 自定义数据权限 - 通过role_dept_relation表定义可访问的部门列表
所有需要数据权限控制的业务模型都应该继承此混入类
为业务模型提供数据权限过滤功能
"""
@classmethod
def get_data_permission_query(cls: type[T], db: Session, user_id: int, tenant_id: int | None = None, customer_id: int | None = None) -> Query:
# 数据权限常量定义,提高代码可读性
DATA_SCOPE_SELF = 1 # 仅本人数据
DATA_SCOPE_DEPT = 2 # 本部门数据
DATA_SCOPE_DEPT_AND_CHILD = 3 # 本部门及以下数据
DATA_SCOPE_ALL = 4 # 全部数据
DATA_SCOPE_CUSTOM = 5 # 自定义数据
def __init__(self, db: AsyncSession, model: Any, current_user: UserModel, auth: AuthSchema):
"""
获取数据权限过滤后的查询对象
初始化权限过滤器实例
Args:
db: 数据库会话
user_id: 用户ID
tenant_id: 租户ID(可选)
customer_id: 客户ID(可选)
model: 数据模型类
current_user: 当前用户对象
auth: 认证信息对象
"""
self.db = db
self.model = model
self.current_user = current_user
self.auth = auth
self.conditions: List[ColumnElement] = [] # 权限条件列表
async def get_permission_condition(self) -> Optional[ColumnElement]:
"""
异步构建权限过滤表达式,返回None表示不限制
Returns:
权限过滤表达式或None
"""
# 初始化条件列表
self.conditions = []
# 无用户时返回空条件
if not self.current_user:
return None
# 超级管理员跳过所有过滤
if self.current_user.is_superuser:
return None
# 租户级数据隔离
await self._apply_tenant_isolation()
# 客户级数据隔离
await self._apply_customer_isolation()
# 数据范围权限隔离
await self._apply_data_scope_isolation()
# 组合所有条件
return and_(*self.conditions) if self.conditions else None
async def _apply_tenant_isolation(self) -> None:
"""
应用租户级数据隔离
非系统用户只能查看本租户数据
"""
if (hasattr(self.model, "tenant_id") and
hasattr(self.current_user, "user_type") and
self.current_user.user_type != 0): # 非系统用户
user_tenant_id = getattr(self.current_user, "tenant_id", None)
if user_tenant_id is not None:
self.conditions.append(getattr(self.model, "tenant_id") == user_tenant_id)
async def _apply_customer_isolation(self) -> None:
"""
应用客户级数据隔离
客户用户只能查看自己客户的数据
"""
if hasattr(self.model, "customer_id"):
user_customer_id = getattr(self.current_user, "customer_id", None)
# 客户用户类型为2
if (hasattr(self.current_user, "user_type") and
self.current_user.user_type == 2 and
user_customer_id is not None):
self.conditions.append(getattr(self.model, "customer_id") == user_customer_id)
async def _apply_data_scope_isolation(self) -> None:
"""
应用数据范围权限隔离
基于角色的五种数据权限范围过滤
支持五种权限类型:
1. 仅本人数据权限 - 只能查看自己创建的数据
2. 本部门数据权限 - 只能查看同部门的数据
3. 本部门及以下数据权限 - 可以查看本部门及所有子部门的数据
4. 全部数据权限 - 可以查看所有数据
5. 自定义数据权限 - 通过role_dept_relation表定义可访问的部门列表
"""
# 只有在需要检查数据权限且模型有created_id字段时才应用数据范围过滤
if (self.auth.check_data_scope and
hasattr(self.model, "created_id") and
hasattr(self.current_user, "roles")):
# 获取用户的权限范围和部门ID集合
data_scopes, dept_ids = await self._get_user_data_scopes()
# 如果没有设置任何数据权限,默认只能查看自己的数据
if not data_scopes:
self._add_self_scope_condition()
return
# 如果拥有全部数据权限,不需要额外过滤
if self.DATA_SCOPE_ALL in data_scopes:
# 但仍需处理没有部门或角色的情况
if not getattr(self.current_user, "dept_id", None) or not self.current_user.roles:
self._add_self_scope_condition()
return
# 应用相应的数据范围过滤
if self.DATA_SCOPE_CUSTOM in data_scopes and dept_ids:
await self._add_custom_scope_condition(dept_ids)
elif self.DATA_SCOPE_SELF in data_scopes:
self._add_self_scope_condition()
elif (self.DATA_SCOPE_DEPT in data_scopes or self.DATA_SCOPE_DEPT_AND_CHILD in data_scopes):
await self._add_dept_scope_condition(data_scopes)
else:
# 默认情况下,用户只能查看自己的数据
self._add_self_scope_condition()
async def _get_user_data_scopes(self) -> Tuple[Set[int], Set[int]]:
"""
获取用户所有角色的权限范围和部门ID集合
Returns:
Tuple[Set[int], Set[int]]: (数据范围集合, 部门ID集合)
"""
data_scopes: Set[int] = set()
dept_ids: Set[int] = set()
roles = getattr(self.current_user, "roles", []) or []
for role in roles:
# 获取角色关联的部门
if hasattr(role, 'depts') and role.depts:
for dept in role.depts:
if hasattr(dept, 'id'):
dept_ids.add(dept.id)
# 获取角色的数据范围
if hasattr(role, 'data_scope') and role.data_scope:
try:
data_scopes.add(int(role.data_scope))
except (ValueError, TypeError):
# 数据范围格式错误,忽略此角色的数据范围
continue
return data_scopes, dept_ids
def _add_self_scope_condition(self) -> None:
"""
添加仅本人数据权限条件
"""
if hasattr(self.model, "created_id"):
self.conditions.append(getattr(self.model, "created_id") == self.current_user.id)
async def _add_custom_scope_condition(self, dept_ids: Set[int]) -> None:
"""
添加自定义数据权限条件
Args:
dept_ids: 允许访问的部门ID集合
"""
creator_rel = getattr(self.model, "created_id", None)
if (creator_rel is not None and
hasattr(UserModel, 'dept_id')):
# 通过creator关系过滤部门
self.conditions.append(
creator_rel.has(getattr(UserModel, 'dept_id').in_(list(dept_ids)))
)
else:
# 无法通过部门过滤时回退到仅本人数据
self._add_self_scope_condition()
async def _add_dept_scope_condition(self, data_scopes: Set[int]) -> None:
"""
添加部门相关的数据权限条件
Args:
data_scopes: 用户的数据权限范围集合
"""
dept_id_val = getattr(self.current_user, "dept_id", None)
# 无部门时回退到仅本人数据
if dept_id_val is None:
self._add_self_scope_condition()
return
# 获取部门ID集合
dept_ids = {dept_id_val} # 包含本部门
# 如果需要包含子部门数据
if self.DATA_SCOPE_DEPT_AND_CHILD in data_scopes:
child_dept_ids = await self._get_child_dept_ids(dept_id_val)
dept_ids.update(child_dept_ids)
# 应用部门过滤条件
creator_rel = getattr(self.model, "creator", None)
if (creator_rel is not None and
hasattr(UserModel, 'dept_id') and
dept_ids):
self.conditions.append(
creator_rel.has(getattr(UserModel, 'dept_id').in_(list(dept_ids)))
)
else:
# 无法通过部门过滤时回退到仅本人数据
self._add_self_scope_condition()
async def _get_child_dept_ids(self, dept_id: int) -> List[int]:
"""
获取指定部门的所有子部门ID
Args:
dept_id: 部门ID
Returns:
List[int]: 子部门ID列表
"""
try:
# 查询所有部门以构建部门树
dept_sql = select(DeptModel)
dept_result = await self.db.execute(dept_sql)
dept_objs = dept_result.scalars().all()
# 构建部门ID映射并递归获取子部门ID
id_map = get_child_id_map(dept_objs)
return get_child_recursion(id=dept_id, id_map=id_map)
except Exception:
# 异常情况下返回空列表,避免权限系统出错
return []
async def filter_query(self, query: Any) -> Any:
"""
异步过滤查询对象
Args:
query: SQLAlchemy查询对象
Returns:
过滤后的查询对象
"""
# 获取用户信息
user = db.query(UserModel).filter(UserModel.id == user_id).first()
if not user:
# 返回空查询
return db.query(cls).filter(~cls.id.in_([]))
# 构建基础查询
query = db.query(cls)
# 应用租户和客户数据隔离
if hasattr(cls, 'tenant_id'):
if tenant_id is not None:
query = query.filter(getattr(cls, 'tenant_id') == tenant_id)
elif user.tenant_id is not None:
query = query.filter(getattr(cls, 'tenant_id') == user.tenant_id)
if hasattr(cls, 'customer_id'):
if customer_id is not None:
query = query.filter(getattr(cls, 'customer_id') == customer_id)
elif user.user_type == 3 and user.customer_id is not None:
query = query.filter(getattr(cls, 'customer_id') == user.customer_id)
# 获取用户的角色数据权限范围
user_roles = db.query(RoleModel).join(UserModel.roles).filter(UserModel.id == user_id).all()
# 如果用户没有角色,默认只有本人权限
if not user_roles:
if hasattr(cls, 'created_id'):
return query.filter(getattr(cls, 'created_id') == user_id)
return query.filter(~cls.id.in_([]))
# 确定用户的数据权限范围(取最大权限)
data_scopes = []
for role in user_roles:
if role.data_scope:
data_scopes.append(role.data_scope)
# 构建权限过滤条件
if '4' in data_scopes: # 全部数据权限
return query
elif '3' in data_scopes and hasattr(user, 'dept_id') and user.dept_id: # 本部门及以下数据权限
return cls._filter_department_and_children(query, user.dept_id, db)
elif '2' in data_scopes and hasattr(user, 'dept_id') and user.dept_id: # 本部门数据权限
return cls._filter_department(query, user.dept_id, db)
elif '5' in data_scopes: # 自定义数据权限
return cls._filter_custom_departments(query, user_id, db)
else: # 仅本人数据权限(默认)
if hasattr(cls, 'created_id'):
return query.filter(getattr(cls, 'created_id') == user_id)
return query.filter(~cls.id.in_([]))
@classmethod
def _filter_department(cls, query: Query, dept_id: Union[int, None], db: Session) -> Query:
"""
过滤本部门数据
Args:
query: 原始查询
dept_id: 部门ID
db: 数据库会话
Returns:
过滤后的查询
"""
if not dept_id:
return query.filter(~cls.id.in_([])) # 没有部门时返回空查询
# 如果模型有dept_id字段,直接按部门ID过滤
if hasattr(cls, 'dept_id'):
return query.filter(getattr(cls, 'dept_id') == dept_id)
# 如果模型有created_id字段,按创建者过滤
if hasattr(cls, 'created_id'):
# 查询该部门的所有用户ID
dept_user_ids = (
db.query(UserModel.id)
.filter(UserModel.dept_id == dept_id)
.all()
)
user_ids = [user.id for user in dept_user_ids]
if user_ids:
return query.filter(getattr(cls, 'created_id').in_(user_ids))
return query.filter(~cls.id.in_([]))
@classmethod
def _filter_department_and_children(cls, query: Query, dept_id: Union[int, None], db: Session) -> Query:
"""
过滤本部门及所有子部门数据
Args:
query: 原始查询
dept_id: 部门ID
db: 数据库会话
Returns:
过滤后的查询
"""
if not dept_id:
return query.filter(~cls.id.in_([])) # 没有部门时返回空查询
# 递归获取所有子部门ID
def get_all_children_dept_ids(parent_id: int) -> list[int]:
result = [parent_id]
children = db.query(DeptModel.id).filter(DeptModel.parent_id == parent_id).all()
for child in children:
result.extend(get_all_children_dept_ids(child.id))
return result
all_dept_ids = get_all_children_dept_ids(dept_id)
# 如果模型有dept_id字段,直接按部门ID过滤
if hasattr(cls, 'dept_id'):
return query.filter(getattr(cls, 'dept_id').in_(all_dept_ids))
# 如果模型有created_id字段,按创建者过滤
if hasattr(cls, 'created_id'):
# 获取这些部门的所有用户ID
dept_users = (
db.query(UserModel.id)
.filter(UserModel.dept_id.in_(all_dept_ids))
.all()
)
user_ids = [user.id for user in dept_users]
if user_ids:
return query.filter(getattr(cls, 'created_id').in_(user_ids))
return query.filter(~cls.id.in_([]))
@classmethod
def _filter_custom_departments(cls, query: Query, user_id: int, db: Session) -> Query:
"""
过滤自定义部门数据
Args:
query: 原始查询
user_id: 用户ID
db: 数据库会话
Returns:
过滤后的查询
"""
# 获取用户角色
user_roles = db.query(RoleModel).join(UserModel.roles).filter(UserModel.id == user_id).all()
# 收集所有可访问的部门ID
accessible_dept_ids: set[int] = set()
for role in user_roles:
if role.data_scope == '5' and role.depts:
accessible_dept_ids.update([dept.id for dept in role.depts])
if not accessible_dept_ids:
# 如果没有自定义部门权限,默认返回本人权限
if hasattr(cls, 'created_id'):
return query.filter(getattr(cls, 'created_id') == user_id)
return query.filter(~cls.id.in_([]))
# 如果模型有dept_id字段,直接按部门ID过滤
if hasattr(cls, 'dept_id'):
return query.filter(getattr(cls, 'dept_id').in_(accessible_dept_ids))
# 如果模型有created_id字段,按创建者过滤
if hasattr(cls, 'created_id'):
# 获取这些部门的所有用户ID
dept_users = (
db.query(UserModel.id)
.filter(UserModel.dept_id.in_(accessible_dept_ids))
.all()
)
user_ids = [user.id for user in dept_users]
if user_ids:
return query.filter(getattr(cls, 'created_id').in_(user_ids))
# 默认返回本人权限
if hasattr(cls, 'created_id'):
return query.filter(getattr(cls, 'created_id') == user_id)
return query.filter(~cls.id.in_([]))
@classmethod
def has_permission_to_access(cls: Type[T], db: Session, user_id: int, record_id: int) -> bool:
"""
检查用户是否有权限访问特定记录
Args:
db: 数据库会话
user_id: 用户ID
record_id: 记录ID
Returns:
是否有权限访问
"""
# 获取用户信息
user = db.query(UserModel).filter(UserModel.id == user_id).first()
if not user:
return False
# 获取记录
record = db.query(cls).filter(cls.id == record_id).first()
if not record:
return False
# 检查租户和客户权限
if hasattr(record, 'tenant_id') and getattr(record, 'tenant_id') is not None and hasattr(user, 'tenant_id') and user.tenant_id != getattr(record, 'tenant_id'):
return False
if hasattr(record, 'customer_id') and getattr(record, 'customer_id') is not None and hasattr(user, 'customer_id') and user.customer_id != getattr(record, 'customer_id'):
return False
# 获取用户角色的数据权限
user_roles = db.query(RoleModel).join(UserModel.roles).filter(UserModel.id == user_id).all()
data_scopes = [role.data_scope for role in user_roles if role.data_scope]
# 全部数据权限
if '4' in data_scopes:
return True
# 仅本人数据权限
if hasattr(record, 'created_id') and getattr(record, 'created_id') == user_id:
return True
# 部门相关权限检查
if not hasattr(user, 'dept_id') or not user.dept_id:
return False
# 本部门权限
if '2' in data_scopes and hasattr(record, 'dept_id') and getattr(record, 'dept_id') == user.dept_id:
return True
# 本部门及以下权限
if '3' in data_scopes and hasattr(record, 'dept_id'):
# 检查记录的部门是否在用户部门的子树中
def is_descendant(dept_id: int, ancestor_id: int) -> bool:
dept = db.query(DeptModel).filter(DeptModel.id == dept_id).first()
if not dept:
return False
if dept.parent_id == ancestor_id:
return True
if dept.parent_id:
return is_descendant(dept.parent_id, ancestor_id)
return False
record_dept_id = getattr(record, 'dept_id')
if record_dept_id:
return is_descendant(record_dept_id, user.dept_id)
# 自定义数据权限
if '5' in data_scopes and hasattr(record, 'dept_id'):
# 收集所有可访问的部门ID
accessible_dept_ids: set[int] = set()
for role in user_roles:
if role.data_scope == '5' and role.depts:
accessible_dept_ids.update([dept.id for dept in role.depts])
record_dept_id = getattr(record, 'dept_id')
if record_dept_id in accessible_dept_ids:
return True
return False
condition = await self.get_permission_condition()
return query.where(condition) if condition is not None else query