diff --git a/backend/app/api/v1/module_system/dept/service.py b/backend/app/api/v1/module_system/dept/service.py
index 9940c4ff..2c61c36f 100644
--- a/backend/app/api/v1/module_system/dept/service.py
+++ b/backend/app/api/v1/module_system/dept/service.py
@@ -86,9 +86,6 @@ class DeptService:
obj = await DeptCRUD(auth).get(code=data.code)
if obj:
raise CustomException(msg='创建失败,编码已存在')
- # 检测循环引用
- if data.parent_id:
- await cls._check_circular_reference(auth, data.parent_id, id=None)
dept = await DeptCRUD(auth).create(data=data)
return DeptOutSchema.model_validate(dept).model_dump()
@@ -114,45 +111,9 @@ class DeptService:
exist_dept = await DeptCRUD(auth).get(name=data.name)
if exist_dept and exist_dept.id != id:
raise CustomException(msg='更新失败,部门名称重复')
- # 检测循环引用
- if data.parent_id is not None:
- await cls._check_circular_reference(auth, data.parent_id, id=id)
dept = await DeptCRUD(auth).update(id=id, data=data)
return DeptOutSchema.model_validate(dept).model_dump()
- @classmethod
- async def _check_circular_reference(cls, auth: AuthSchema, parent_id: int, id: int | None = None) -> None:
- """
- 检测部门层级循环引用
-
- 参数:
- - auth (AuthSchema): 认证对象
- - parent_id (int): 父部门ID
- - id (int | None): 当前部门ID(用于更新时检测)
-
- 异常:
- - CustomException: 当检测到循环引用时抛出
- """
- if parent_id == id:
- raise CustomException(msg='更新失败,不能将部门设置为自己的子部门')
-
- # 获取所有部门信息用于检测循环引用
- all_depts = await DeptCRUD(auth).get_list_crud()
- parent_map = {dept.id: dept.parent_id for dept in all_depts}
-
- # 检查循环引用
- current_id = parent_id
- visited = set()
-
- while current_id and current_id not in visited:
- visited.add(current_id)
- if current_id == id:
- raise CustomException(msg='更新失败,检测到部门层级循环引用')
- current_id = parent_map.get(current_id)
-
- if current_id == id:
- raise CustomException(msg='更新失败,检测到部门层级循环引用')
-
@classmethod
async def delete_dept_service(cls, auth: AuthSchema, ids: list[int]) -> None:
"""
diff --git a/backend/app/api/v1/module_system/role/model.py b/backend/app/api/v1/module_system/role/model.py
index 832d41a1..6bbb8b71 100644
--- a/backend/app/api/v1/module_system/role/model.py
+++ b/backend/app/api/v1/module_system/role/model.py
@@ -59,13 +59,13 @@ class RoleDeptsModel(MappedBase):
)
-class RoleModel(ModelMixin, UserMixin):
+class RoleModel(ModelMixin):
"""
角色模型
"""
__tablename__: str = "sys_role"
__table_args__: dict[str, str] = ({'comment': '角色表'})
- __loader_options__: list[str] = ["menus", "depts", "created_by", "updated_by"]
+ __loader_options__: list[str] = ["menus", "depts"]
name: Mapped[str] = mapped_column(String(40), nullable=False, comment="角色名称")
code: Mapped[str | None] = mapped_column(String(20), nullable=True, index=True, comment="角色编码")
diff --git a/backend/app/api/v1/module_system/role/schema.py b/backend/app/api/v1/module_system/role/schema.py
index 1fb0349d..73b27787 100644
--- a/backend/app/api/v1/module_system/role/schema.py
+++ b/backend/app/api/v1/module_system/role/schema.py
@@ -4,7 +4,7 @@ from fastapi import Query
from pydantic import BaseModel, ConfigDict, Field, model_validator, field_validator
from app.core.validator import DateTimeStr
-from app.core.base_schema import BaseSchema, UserBySchema
+from app.core.base_schema import BaseSchema
from app.core.validator import role_permission_request_validator
from ..dept.schema import DeptOutSchema
@@ -50,7 +50,7 @@ class RoleUpdateSchema(RoleCreateSchema):
...
-class RoleOutSchema(RoleCreateSchema, BaseSchema, UserBySchema):
+class RoleOutSchema(RoleCreateSchema, BaseSchema):
"""角色信息响应模型"""
model_config = ConfigDict(from_attributes=True)
@@ -67,16 +67,12 @@ class RoleQueryParam:
status: str | None = Query(None, description="是否可用"),
created_time: list[DateTimeStr] | None = Query(None, description="创建时间范围", example=["2025-01-01 00:00:00", "2025-12-31 23:59:59"]),
updated_time: list[DateTimeStr] | None = Query(None, description="更新时间范围", example=["2025-01-01 00:00:00", "2025-12-31 23:59:59"]),
- created_id: int | None = Query(None, description="创建人"),
- updated_id: int | None = Query(None, description="更新人"),
) -> None:
# 模糊查询字段
self.name = ("like", name)
# 精确查询字段
- self.created_id = created_id
- self.updated_id = updated_id
self.status = status
# 时间范围查询
diff --git a/backend/app/api/v1/module_system/user/model.py b/backend/app/api/v1/module_system/user/model.py
index 0ac6c17d..0bd4833a 100644
--- a/backend/app/api/v1/module_system/user/model.py
+++ b/backend/app/api/v1/module_system/user/model.py
@@ -104,3 +104,22 @@ class UserModel(ModelMixin, UserMixin):
back_populates="users",
lazy="selectin"
)
+
+ # 覆盖 UserMixin 的关系定义,显式指定 foreign_keys 避免自引用混淆
+ created_by: Mapped["UserModel | None"] = relationship(
+ "UserModel",
+ foreign_keys="UserModel.created_id",
+ remote_side="UserModel.id",
+ lazy="selectin",
+ uselist=False,
+ viewonly=True # 防止级联操作
+ )
+
+ updated_by: Mapped["UserModel | None"] = relationship(
+ "UserModel",
+ foreign_keys="UserModel.updated_id",
+ remote_side="UserModel.id",
+ lazy="selectin",
+ uselist=False,
+ viewonly=True # 防止级联操作
+ )
diff --git a/backend/app/core/base_crud.py b/backend/app/core/base_crud.py
index c9e50fa4..2ff36a94 100644
--- a/backend/app/core/base_crud.py
+++ b/backend/app/core/base_crud.py
@@ -5,7 +5,7 @@ from typing import TypeVar, Sequence, Generic, Dict, Any, List, Optional, Type,
from sqlalchemy.sql.elements import ColumnElement
from sqlalchemy.orm import selectinload
from sqlalchemy.engine import Result
-from sqlalchemy import asc, func, select, delete, Select, desc, update, or_, and_
+from sqlalchemy import asc, func, select, delete, Select, desc, update
from sqlalchemy import inspect as sa_inspect
from app.core.base_model import MappedBase
@@ -160,8 +160,16 @@ class CRUDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]):
sql = sql.options(opt)
sql = await self.__filter_permissions(sql)
- # 获取总数
- count_sql = select(func.count()).select_from(self.model)
+ # 优化count查询:使用主键计数而非全表扫描
+ mapper = sa_inspect(self.model)
+ pk_cols = list(getattr(mapper, "primary_key", []))
+ if pk_cols:
+ # 使用主键的第一列进行计数(主键必定非NULL,性能更好)
+ count_sql = select(func.count(pk_cols[0])).select_from(self.model)
+ else:
+ # 降级方案:使用count(*)
+ count_sql = select(func.count()).select_from(self.model)
+
if conditions:
count_sql = count_sql.where(*conditions)
count_sql = await self.__filter_permissions(count_sql)
@@ -244,6 +252,14 @@ class CRUDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]):
await self.auth.db.flush()
await self.auth.db.refresh(obj)
+
+ # 权限二次确认:flush后再次验证对象仍在权限范围内
+ # 防止并发修改导致的权限逃逸(如其他事务修改了created_id)
+ verify_obj = await self.get(id=id)
+ if not verify_obj:
+ # 对象已被删除或权限已失效
+ raise CustomException(msg="更新失败,对象不存在或无权限访问")
+
return obj
except Exception as e:
raise CustomException(msg=f"更新失败: {str(e)}")
@@ -259,13 +275,27 @@ class CRUDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]):
- CustomException: 删除失败时抛出异常
"""
try:
+ # 先查询确认权限,避免删除无权限的数据
+ objs = await self.list(search={"id": ("in", ids)})
+ accessible_ids = [obj.id for obj in objs]
+
+ # 检查是否所有ID都有权限访问
+ inaccessible_count = len(ids) - len(accessible_ids)
+ if inaccessible_count > 0:
+ raise CustomException(msg=f"无权限删除{inaccessible_count}条数据")
+
+ if not accessible_ids:
+ return # 没有可删除的数据
+
mapper = sa_inspect(self.model)
pk_cols = list(getattr(mapper, "primary_key", []))
if not pk_cols:
raise CustomException(msg="模型缺少主键,无法删除")
if len(pk_cols) > 1:
raise CustomException(msg="暂不支持复合主键的批量删除")
- sql = delete(self.model).where(pk_cols[0].in_(ids))
+
+ # 只删除有权限的数据
+ sql = delete(self.model).where(pk_cols[0].in_(accessible_ids))
await self.auth.db.execute(sql)
await self.auth.db.flush()
except Exception as e:
@@ -297,15 +327,31 @@ class CRUDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]):
- CustomException: 更新失败时抛出异常
"""
try:
+ # 先查询确认权限,避免更新无权限的数据
+ objs = await self.list(search={"id": ("in", ids)})
+ accessible_ids = [obj.id for obj in objs]
+
+ # 检查是否所有ID都有权限访问
+ inaccessible_count = len(ids) - len(accessible_ids)
+ if inaccessible_count > 0:
+ raise CustomException(msg=f"无权限更新{inaccessible_count}条数据")
+
+ if not accessible_ids:
+ return # 没有可更新的数据
+
mapper = sa_inspect(self.model)
pk_cols = list(getattr(mapper, "primary_key", []))
if not pk_cols:
raise CustomException(msg="模型缺少主键,无法更新")
if len(pk_cols) > 1:
raise CustomException(msg="暂不支持复合主键的批量更新")
- sql = update(self.model).where(pk_cols[0].in_(ids)).values(**kwargs)
+
+ # 只更新有权限的数据
+ sql = update(self.model).where(pk_cols[0].in_(accessible_ids)).values(**kwargs)
await self.auth.db.execute(sql)
await self.auth.db.flush()
+ except CustomException:
+ raise
except Exception as e:
raise CustomException(msg=f"批量更新失败: {str(e)}")
@@ -356,8 +402,16 @@ class CRUDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]):
conditions.append(attr.between(val[0], val[1]))
elif seq == "!=" and val:
conditions.append(attr != val)
- elif seq in [">", ">=", "<=", "=="] and val:
- conditions.append(getattr(attr, seq.replace("==", "__eq__"))(val))
+ elif seq == ">" and val:
+ conditions.append(attr > val)
+ elif seq == ">=" and val:
+ conditions.append(attr >= val)
+ elif seq == "<" and val:
+ conditions.append(attr < val)
+ elif seq == "<=" and val:
+ conditions.append(attr <= val)
+ elif seq == "==" and val:
+ conditions.append(attr == val)
else:
conditions.append(attr == value)
return conditions
diff --git a/backend/app/core/base_model.py b/backend/app/core/base_model.py
index ec614c2e..027acecf 100644
--- a/backend/app/core/base_model.py
+++ b/backend/app/core/base_model.py
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
+from typing import Optional
from datetime import datetime
from sqlalchemy import DateTime, String, Integer, Text, ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, DeclarativeBase, declared_attr, relationship
@@ -93,29 +94,25 @@ class UserMixin(MappedBase):
)
@declared_attr
- def created_by(cls) -> Mapped["UserModel"]:
+ def created_by(cls) -> Mapped[Optional["UserModel"]]:
"""
创建人关联关系(延迟加载,避免循环依赖)
"""
return relationship(
"UserModel",
- primaryjoin=f"{cls.__name__}.created_id == UserModel.id",
lazy="selectin",
- foreign_keys=lambda: [cls.created_id],
- viewonly=True,
+ foreign_keys=lambda: cls.created_id,
uselist=False
)
@declared_attr
- def updated_by(cls) -> Mapped["UserModel"]:
+ def updated_by(cls) -> Mapped[Optional["UserModel"]]:
"""
更新人关联关系(延迟加载,避免循环依赖)
"""
return relationship(
"UserModel",
- primaryjoin=f"{cls.__name__}.updated_id == UserModel.id",
lazy="selectin",
- foreign_keys=lambda: [cls.updated_id],
- viewonly=True,
+ foreign_keys=lambda: cls.updated_id,
uselist=False
)
diff --git a/backend/app/core/base_schema.py b/backend/app/core/base_schema.py
index 24ba16c3..600fa621 100644
--- a/backend/app/core/base_schema.py
+++ b/backend/app/core/base_schema.py
@@ -9,9 +9,9 @@ class UserInfoSchema(BaseModel):
"""用户信息模型"""
model_config = ConfigDict(from_attributes=True)
- id: int = Field(description="用户ID")
- name: str = Field(description="用户姓名")
- username: str = Field(description="用户名")
+ id: int | None = Field(default=None, description="用户ID")
+ name: str | None = Field(default=None, description="用户姓名")
+ username: str | None = Field(default=None, description="用户名")
class CommonSchema(BaseModel):
diff --git a/backend/app/core/dependencies.py b/backend/app/core/dependencies.py
index b1e8ec1b..2a27cc11 100644
--- a/backend/app/core/dependencies.py
+++ b/backend/app/core/dependencies.py
@@ -93,7 +93,7 @@ async def get_current_user(
username=username,
preload=[
"dept",
- selectinload(UserModel.roles).selectinload(RoleModel.created_by),
+ selectinload(UserModel.roles),
"positions",
"created_by"
]
diff --git a/backend/app/core/logger.py b/backend/app/core/logger.py
index 8402e0c4..197f6112 100644
--- a/backend/app/core/logger.py
+++ b/backend/app/core/logger.py
@@ -2,12 +2,16 @@
import logging
import sys
+import atexit
from typing_extensions import override
from loguru import logger
from app.config.path_conf import LOG_DIR
from app.config.setting import settings
+# 全局变量记录日志处理器ID
+_logger_handlers = []
+
class InterceptHandler(logging.Handler):
"""
@@ -39,6 +43,22 @@ class InterceptHandler(logging.Handler):
)
+def cleanup_logging():
+ """
+ 清理日志资源
+ 在程序退出时调用,确保所有日志处理器被正确关闭
+ """
+ global _logger_handlers
+
+ for handler_id in _logger_handlers:
+ try:
+ logger.remove(handler_id)
+ except Exception:
+ pass
+
+ _logger_handlers.clear()
+
+
def setup_logging():
"""
配置日志系统
@@ -47,8 +67,10 @@ def setup_logging():
1. 控制台彩色输出
2. 文件日志轮转
3. 错误日志单独存储
- 4. 异步日志记录
+ 4. 智能异步策略:开发环境同步(避免reload资源泄漏),生产环境异步(高性能)
"""
+ global _logger_handlers
+
# 添加上下文信息
_ = logger.configure(extra={"app_name": "FastapiAdmin"})
# 步骤1:移除默认处理器
@@ -66,16 +88,20 @@ def setup_logging():
"{message}"
)
+ # 智能选择异步策略:开发环境禁用异步(避免reload时资源泄漏),生产环境启用异步(提升性能)
+ use_async = not settings.DEBUG
+
# 步骤3:配置控制台输出
- _ = logger.add(
+ handler_id = logger.add(
sys.stdout,
format=log_format,
level="DEBUG" if settings.DEBUG else "INFO",
- enqueue=True, # 启用异步写入
+ enqueue=use_async, # 开发同步,生产异步
backtrace=True, # 显示完整的异常回溯
diagnose=True, # 显示变量值等诊断信息
colorize=True # 启用彩色输出
)
+ _logger_handlers.append(handler_id)
# 步骤4:创建日志目录
log_dir = LOG_DIR
@@ -83,7 +109,7 @@ def setup_logging():
log_dir.mkdir(parents=True, exist_ok=True)
# 步骤5:配置常规日志文件
- _ = logger.add(
+ handler_id = logger.add(
str(log_dir / "info.log"),
format=log_format,
level="INFO",
@@ -91,11 +117,12 @@ def setup_logging():
retention=30, # 日志保留天数,超过此天数的日志文件将被自动清理
compression="gz",
encoding="utf-8",
- enqueue=True
+ enqueue=use_async # 开发同步,生产异步
)
+ _logger_handlers.append(handler_id)
# 步骤6:配置错误日志文件
- _ = logger.add(
+ handler_id = logger.add(
str(log_dir / "error.log"),
format=log_format,
level="ERROR",
@@ -103,18 +130,23 @@ def setup_logging():
retention=30, # 日志保留天数,超过此天数的日志文件将被自动清理
compression="gz",
encoding="utf-8",
- enqueue=True,
+ enqueue=use_async, # 开发同步,生产异步
backtrace=True,
diagnose=True
)
+ _logger_handlers.append(handler_id)
# 步骤7:配置标准库日志
logging.basicConfig(handlers=[InterceptHandler()], level="DEBUG" if settings.DEBUG else "INFO", force=True)
logger_name_list = [name for name in logging.root.manager.loggerDict]
+
# 步骤8:配置第三方库日志
for logger_name in logger_name_list:
_logger = logging.getLogger(logger_name)
_logger.handlers = [InterceptHandler()]
_logger.propagate = False
+
+ # 注册退出清理函数
+ atexit.register(cleanup_logging)
log = logger
\ No newline at end of file
diff --git a/backend/app/core/permission.py b/backend/app/core/permission.py
index f8308bf1..e86a37e9 100644
--- a/backend/app/core/permission.py
+++ b/backend/app/core/permission.py
@@ -2,10 +2,9 @@
from typing import Any
from sqlalchemy.sql.elements import ColumnElement
-from sqlalchemy import select, and_
+from sqlalchemy import select
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
@@ -59,7 +58,11 @@ class Permission:
3. 本部门及以下数据权限 - 可以查看本部门及所有子部门的数据
4. 全部数据权限 - 可以查看所有数据
5. 自定义数据权限 - 通过role_dept_relation表定义可访问的部门列表
- 构造权限过滤表达式,返回None表示不限制。
+
+ 权限处理原则:
+ - 多个角色的权限取并集(最宽松原则)
+ - 优先级:全部数据 > 部门权限(2、3、5的并集)> 仅本人
+ - 构造权限过滤表达式,返回None表示不限制
"""
# 如果不需要检查数据权限,则不限制
if not self.auth.user:
@@ -69,89 +72,88 @@ class Permission:
if not self.auth.check_data_scope:
return None
- # 如果模型没有创建人creator_id字段,则不限制
- if not hasattr(self.model, "creator_id"):
+ # 如果模型没有创建人created_id字段,则不限制
+ if not hasattr(self.model, "created_id"):
return None
# 超级管理员可以查看所有数据
if self.auth.user.is_superuser:
return None
- # 如果用户没有部门或角色,则只能查看自己的数据
- if not getattr(self.auth.user, "dept_id", None) or not getattr(self.auth.user, "roles", None):
- creator_id_attr = getattr(self.model, "creator_id", None)
- if creator_id_attr is not None:
- return creator_id_attr == self.auth.user.id
+ # 如果用户没有角色,则只能查看自己的数据
+ roles = getattr(self.auth.user, "roles", []) or []
+ if not roles:
+ created_id_attr = getattr(self.model, "created_id", None)
+ if created_id_attr is not None:
+ return created_id_attr == self.auth.user.id
return None
# 获取用户所有角色的权限范围
data_scopes = set()
- dept_ids = set()
- roles = getattr(self.auth.user, "roles", []) or []
+ custom_dept_ids = set() # 自定义权限(data_scope=5)关联的部门ID集合
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)
+ # 收集自定义权限(data_scope=5)关联的部门ID
+ if role.data_scope == self.DATA_SCOPE_CUSTOM and hasattr(role, 'depts') and role.depts:
+ for dept in role.depts:
+ custom_dept_ids.add(dept.id)
- # 如果有全部数据权限,直接返回
+ # 权限优先级处理:全部数据权限最高优先级
if self.DATA_SCOPE_ALL in data_scopes:
- # 全部数据权限
return None
- # 如果有自定义数据权限且部门ID存在,优先处理
- if self.DATA_SCOPE_CUSTOM 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:
- creator_id_attr = getattr(self.model, "creator_id", None)
- if creator_id_attr is not None:
- return creator_id_attr == self.auth.user.id
- return None
-
- # 处理其他数据权限范围
- dept_id_val = getattr(self.auth.user, "dept_id", None)
+ # 收集所有可访问的部门ID(2、3、5权限的并集)
+ accessible_dept_ids = set()
+ user_dept_id = getattr(self.auth.user, "dept_id", None)
+ # 处理自定义数据权限(5)
+ if self.DATA_SCOPE_CUSTOM in data_scopes:
+ accessible_dept_ids.update(custom_dept_ids)
+
+ # 处理本部门数据权限(2)
+ if self.DATA_SCOPE_DEPT in data_scopes:
+ if user_dept_id is not None:
+ accessible_dept_ids.add(user_dept_id)
+
+ # 处理本部门及以下数据权限(3)
+ if self.DATA_SCOPE_DEPT_AND_CHILD in data_scopes:
+ if user_dept_id is not None:
+ try:
+ # 查询所有部门并递归获取子部门
+ dept_sql = select(DeptModel)
+ dept_result = await self.auth.db.execute(dept_sql)
+ dept_objs = dept_result.scalars().all()
+ id_map = get_child_id_map(dept_objs)
+ # get_child_recursion返回的结果已包含自身ID和所有子部门ID
+ dept_with_children_ids = get_child_recursion(id=user_dept_id, id_map=id_map)
+ accessible_dept_ids.update(dept_with_children_ids)
+ except Exception:
+ # 查询失败时降级到本部门
+ accessible_dept_ids.add(user_dept_id)
+
+ # 如果有部门权限(2、3、5任一),使用部门过滤
+ if accessible_dept_ids:
+ creator_rel = getattr(self.model, "created_by", None)
+ # 优先使用关系过滤(性能更好)
+ if creator_rel is not None and hasattr(UserModel, 'dept_id'):
+ return creator_rel.has(getattr(UserModel, 'dept_id').in_(list(accessible_dept_ids)))
+ # 降级方案:如果模型没有created_by关系但有created_id,则只能查看自己的数据
+ else:
+ created_id_attr = getattr(self.model, "created_id", None)
+ if created_id_attr is not None:
+ return created_id_attr == self.auth.user.id
+ return None
+
+ # 处理仅本人数据权限(1)
if self.DATA_SCOPE_SELF in data_scopes:
- # 仅本人数据
- creator_id_attr = getattr(self.model, "creator_id", None)
- if creator_id_attr is not None:
- return creator_id_attr == self.auth.user.id
+ created_id_attr = getattr(self.model, "created_id", None)
+ if created_id_attr is not None:
+ return created_id_attr == self.auth.user.id
return None
- if self.DATA_SCOPE_DEPT in data_scopes and dept_id_val is not None:
- # 本部门数据
- dept_ids.add(dept_id_val)
-
- if self.DATA_SCOPE_DEPT_AND_CHILD in data_scopes and dept_id_val is not None:
- # 本部门及以下数据(查询所有部门并递归)
- dept_sql = select(DeptModel)
- dept_result = await self.auth.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 (self.DATA_SCOPE_DEPT in data_scopes or self.DATA_SCOPE_DEPT_AND_CHILD 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:
- creator_id_attr = getattr(self.model, "creator_id", None)
- if creator_id_attr is not None:
- return creator_id_attr == self.auth.user.id
- return None
-
- # 默认情况下,只能查看自己的数据
- creator_id_attr = getattr(self.model, "creator_id", None)
- if creator_id_attr is not None:
- return creator_id_attr == self.auth.user.id
+ # 默认情况:如果用户有角色但没有任何有效权限范围,只能查看自己的数据
+ created_id_attr = getattr(self.model, "created_id", None)
+ if created_id_attr is not None:
+ return created_id_attr == self.auth.user.id
return None
\ No newline at end of file
diff --git a/backend/app/scripts/data/sys_role.json b/backend/app/scripts/data/sys_role.json
index 50ba4569..e60818e7 100644
--- a/backend/app/scripts/data/sys_role.json
+++ b/backend/app/scripts/data/sys_role.json
@@ -5,18 +5,6 @@
"status": "0",
"order": 1,
"data_scope": 4,
- "description": "初始化角色",
- "created_id": 1,
- "updated_id": 1
- },
- {
- "name": "普通角色",
- "code": "COMMON",
- "status": "0",
- "order": 1,
- "data_scope": 3,
- "description": "初始化角色",
- "created_id": 1,
- "updated_id": 1
+ "description": "初始化角色"
}
]
\ No newline at end of file
diff --git a/backend/app/scripts/initialize.py b/backend/app/scripts/initialize.py
index 758a3392..3f7ea111 100644
--- a/backend/app/scripts/initialize.py
+++ b/backend/app/scripts/initialize.py
@@ -32,10 +32,10 @@ class InitializeData:
MenuModel,
ParamsModel,
DeptModel,
+ RoleModel,
DictTypeModel,
DictDataModel,
UserModel,
- RoleModel,
UserRolesModel,
]
diff --git a/backend/env/.env.dev b/backend/env/.env.dev
index 6c4ef152..94987f52 100644
--- a/backend/env/.env.dev
+++ b/backend/env/.env.dev
@@ -22,7 +22,7 @@ DESCRIPTION = "该项目是一个基于python的web服务框架,基于fastapi
DEMO_ENABLE = False
# 数据库配置
-DATABASE_TYPE = "postgres" # mysql、postgres
+DATABASE_TYPE = "postgres" # mysql、postgres
# 数据库配置
DATABASE_HOST = "localhost"
diff --git a/backend/main.py b/backend/main.py
index 0782cbfa..8e92235a 100755
--- a/backend/main.py
+++ b/backend/main.py
@@ -84,6 +84,9 @@ def run(env: Annotated[EnvironmentEnum, typer.Option("--env", help="运行环境
display_shutdown_info()
except Exception as e:
raise
+ finally:
+ from app.core.logger import cleanup_logging
+ cleanup_logging()
@fastapiadmin_cli.command(name="revision", help="生成新的 Alembic 迁移脚本, 运行 python main.py revision --env=dev")
def revision(env: Annotated[EnvironmentEnum, typer.Option("--env", help="运行环境 (dev, prod)")] = EnvironmentEnum.DEV) -> None:
diff --git a/frontend/src/api/module_system/role.ts b/frontend/src/api/module_system/role.ts
index fe6dc1fb..48c6c5e9 100644
--- a/frontend/src/api/module_system/role.ts
+++ b/frontend/src/api/module_system/role.ts
@@ -73,8 +73,6 @@ export default RoleAPI;
export interface TablePageQuery extends PageQuery {
name?: string;
status?: string;
- created_id?: number;
- updated_id?: number;
created_time?: string[];
updated_time?: string[];
}
@@ -87,8 +85,6 @@ export interface RoleTable extends BaseType {
data_scope?: number;
menus?: permissionMenuType[];
depts?: permissionDeptType[];
- created_by?: creatorType;
- updated_by?: updatorType;
}
export interface RoleForm extends BaseFormType {
diff --git a/frontend/src/views/module_gencode/demo/index.vue b/frontend/src/views/module_gencode/demo/index.vue
index 14332993..757f1baf 100644
--- a/frontend/src/views/module_gencode/demo/index.vue
+++ b/frontend/src/views/module_gencode/demo/index.vue
@@ -282,6 +282,16 @@
{{ scope.row.created_by?.name }}
+
+
+ {{ scope.row.updated_by?.name }}
+
+
-
-
-
-
- {{ detailFormData.created_by?.name }}
-
-
- {{ detailFormData.updated_by?.name }}
-
{{ detailFormData.created_time }}
@@ -503,7 +490,6 @@ defineOptions({
import { ElMessage, ElMessageBox } from "element-plus";
import RoleAPI, { RoleTable, RoleForm, TablePageQuery } from "@/api/module_system/role";
import { useUserStore } from "@/store";
-import UserTableSelect from "@/views/module_system/user/components/UserTableSelect.vue";
import ExportModal from "@/components/CURD/ExportModal.vue";
import type { IContentConfig } from "@/components/CURD/types";
import { QuestionFilled, ArrowUp, ArrowDown } from "@element-plus/icons-vue";
@@ -551,8 +537,6 @@ const queryFormData = reactive({
page_size: 10,
name: "",
status: undefined,
- created_id: undefined,
- updated_id: undefined,
created_time: undefined,
updated_time: undefined,
});
@@ -606,11 +590,6 @@ async function handleQuery() {
loadingData();
}
-// 选择创建人后触发查询
-function handleConfirm() {
- handleQuery();
-}
-
// 行复选框选中项变化
async function handleSelectionChange(selection: any) {
selectIds.value = selection.map((item: any) => item.id);