"""系统管理模块 Repository 层 负责用户、角色、菜单等系统基础数据的 CRUD 操作, 包括 RBAC 权限模型中的角色-菜单关联管理。 被 SystemService(系统管理服务)、AuthService(认证服务)调用。 """ from sqlalchemy import select from sqlalchemy.orm import Session from backend.app.models.system import Menu, Role, RoleMenu, User class SystemRepository: """系统管理数据仓储 封装用户、角色、菜单的增删改查以及角色-菜单关联管理。 被 SystemService(系统管理服务)、AuthService(认证服务)调用。 """ def get_user_by_username(self, session: Session, username: str) -> User | None: """根据用户名查询用户 参数: session: 数据库会话 username: 用户名 返回: 匹配的用户对象,不存在则返回 None """ stmt = select(User).where(User.username == username) return session.execute(stmt).scalar_one_or_none() def get_user_by_mobile(self, session: Session, mobile: str) -> User | None: """根据手机号查询启用状态的用户 参数: session: 数据库会话 mobile: 手机号 返回: 匹配的启用状态用户对象,不存在则返回 None """ stmt = select(User).where(User.mobile == mobile, User.status == 1) return session.execute(stmt).scalar_one_or_none() def get_user(self, session: Session, user_id: int) -> User | None: """根据 ID 查询用户 参数: session: 数据库会话 user_id: 用户 ID 返回: 匹配的用户对象,不存在则返回 None """ stmt = select(User).where(User.id == user_id) return session.execute(stmt).scalar_one_or_none() def create_user(self, session: Session, payload: dict) -> User: """创建新用户 参数: session: 数据库会话 payload: 用户数据字典,包含 username / password_hash / real_name / mobile / role_id / status 等字段 返回: 新创建的用户对象 """ user = User( username=payload["username"], password_hash=payload["password_hash"], real_name=payload["real_name"], mobile=payload.get("mobile") or "", role_id=payload["role_id"], status=payload.get("status", 1), ) session.add(user) session.flush() return user def update_user(self, user: User, payload: dict) -> User: """更新用户基本信息 参数: user: 待更新的用户对象(已从数据库加载) payload: 更新数据字典,包含 real_name / mobile / role_id / status 返回: 更新后的用户对象 """ user.real_name = payload["real_name"] user.mobile = payload.get("mobile") or "" user.role_id = payload["role_id"] user.status = payload.get("status", 1) return user def reset_password(self, user: User, password_hash: str) -> User: """重置用户密码 参数: user: 待重置密码的用户对象 password_hash: 新密码的哈希值 返回: 密码已更新的用户对象 """ user.password_hash = password_hash return user def get_role(self, session: Session, role_id: int) -> Role | None: """根据 ID 查询角色 参数: session: 数据库会话 role_id: 角色 ID 返回: 匹配的角色对象,不存在则返回 None """ stmt = select(Role).where(Role.id == role_id) return session.execute(stmt).scalar_one_or_none() def get_role_by_code(self, session: Session, role_code: str) -> Role | None: """根据角色编码查询角色 参数: session: 数据库会话 role_code: 角色编码(如 admin / sales 等) 返回: 匹配的角色对象,不存在则返回 None """ stmt = select(Role).where(Role.role_code == role_code) return session.execute(stmt).scalar_one_or_none() def create_role(self, session: Session, payload: dict) -> Role: """创建新角色 参数: session: 数据库会话 payload: 角色数据字典,包含 role_name / role_code / status / remark 返回: 新创建的角色对象 """ role = Role( role_name=payload["role_name"], role_code=payload["role_code"], status=payload.get("status", 1), remark=payload.get("remark"), ) session.add(role) session.flush() return role def update_role(self, role: Role, payload: dict) -> Role: """更新角色信息 参数: role: 待更新的角色对象 payload: 更新数据字典,包含 role_name / status / remark 返回: 更新后的角色对象 """ role.role_name = payload["role_name"] role.status = payload.get("status", 1) role.remark = payload.get("remark") return role def list_role_menu_relations(self, session: Session, role_id: int) -> list[RoleMenu]: """查询指定角色的菜单关联列表 参数: session: 数据库会话 role_id: 角色 ID 返回: 该角色关联的菜单关系列表,按 id 升序排列 """ stmt = select(RoleMenu).where(RoleMenu.role_id == role_id).order_by(RoleMenu.id.asc()) return list(session.execute(stmt).scalars()) def replace_role_menu_relations(self, session: Session, role_id: int, menu_ids: list[int]) -> None: """替换角色的菜单关联关系 先删除该角色原有的所有菜单关联,再批量插入新的关联。 用于角色权限配置的全量更新。 参数: session: 数据库会话 role_id: 角色 ID menu_ids: 新的菜单 ID 列表 """ existed = self.list_role_menu_relations(session, role_id) for relation in existed: session.delete(relation) for menu_id in menu_ids: session.add(RoleMenu(role_id=role_id, menu_id=menu_id)) session.flush() def get_menu(self, session: Session, menu_id: int) -> Menu | None: """根据 ID 查询菜单 参数: session: 数据库会话 menu_id: 菜单 ID 返回: 匹配的菜单对象,不存在则返回 None """ stmt = select(Menu).where(Menu.id == menu_id) return session.execute(stmt).scalar_one_or_none() def get_menu_by_permission_code(self, session: Session, permission_code: str) -> Menu | None: """根据权限编码查询菜单 参数: session: 数据库会话 permission_code: 权限编码(如 user:list / order:create) 返回: 匹配的菜单对象,不存在则返回 None """ stmt = select(Menu).where(Menu.permission_code == permission_code) return session.execute(stmt).scalar_one_or_none() def create_menu(self, session: Session, payload: dict) -> Menu: """创建新菜单 参数: session: 数据库会话 payload: 菜单数据字典,包含 parent_id / menu_name / menu_path / menu_type / permission_code / icon / sort_no / status 返回: 新创建的菜单对象 """ menu = Menu( parent_id=payload.get("parent_id", 0), menu_name=payload["menu_name"], menu_path=payload.get("menu_path") or "", menu_type=payload.get("menu_type", "page"), permission_code=payload["permission_code"], icon=payload.get("icon"), sort_no=payload.get("sort_no", 0), status=payload.get("status", 1), ) session.add(menu) session.flush() return menu def update_menu(self, menu: Menu, payload: dict) -> Menu: """更新菜单信息 参数: menu: 待更新的菜单对象 payload: 更新数据字典,包含 parent_id / menu_name / menu_path / menu_type / permission_code / icon / sort_no / status 返回: 更新后的菜单对象 """ menu.parent_id = payload.get("parent_id", 0) menu.menu_name = payload["menu_name"] menu.menu_path = payload.get("menu_path") or "" menu.menu_type = payload.get("menu_type", "page") menu.permission_code = payload["permission_code"] menu.icon = payload.get("icon") menu.sort_no = payload.get("sort_no", 0) menu.status = payload.get("status", 1) return menu def list_users(self, session: Session, filters: dict) -> list[tuple[User, Role | None]]: """查询用户列表(关联角色信息) 联表查询用户及其关联角色,支持按用户名、姓名、手机号、角色和状态过滤。 参数: session: 数据库会话 filters: 过滤条件字典,支持 username / real_name / mobile / role_id / status 返回: (用户对象, 角色对象) 的元组列表,按 id 升序排列 """ stmt = select(User, Role).outerjoin(Role, User.role_id == Role.id) if filters.get("username"): stmt = stmt.where(User.username.contains(filters["username"])) if filters.get("real_name"): stmt = stmt.where(User.real_name.contains(filters["real_name"])) if filters.get("mobile"): stmt = stmt.where(User.mobile.contains(filters["mobile"])) if filters.get("role_id") is not None: stmt = stmt.where(User.role_id == filters["role_id"]) if filters.get("status") is not None: stmt = stmt.where(User.status == filters["status"]) stmt = stmt.order_by(User.id.asc()) return list(session.execute(stmt).all()) def list_roles(self, session: Session, filters: dict) -> list[Role]: """查询角色列表 支持按角色名称、角色编码和状态过滤。 参数: session: 数据库会话 filters: 过滤条件字典,支持 role_name / role_code / status 返回: 角色列表,按 id 升序排列 """ stmt = select(Role) if filters.get("role_name"): stmt = stmt.where(Role.role_name.contains(filters["role_name"])) if filters.get("role_code"): stmt = stmt.where(Role.role_code.contains(filters["role_code"])) if filters.get("status") is not None: stmt = stmt.where(Role.status == filters["status"]) stmt = stmt.order_by(Role.id.asc()) return list(session.execute(stmt).scalars()) def count_active_users_by_role(self, session: Session, role_id: int) -> int: """统计指定角色下启用状态的用户数量 用于删除角色前检查是否仍有活跃用户关联。 参数: session: 数据库会话 role_id: 角色 ID 返回: 启用状态的用户数量 """ stmt = select(User).where(User.role_id == role_id, User.status == 1) return len(list(session.execute(stmt).scalars())) def list_menus_by_ids(self, session: Session, menu_ids: list[int]) -> list[Menu]: """根据菜单 ID 列表批量查询菜单 按 sort_no 和 id 升序排列,空列表时直接返回空结果。 参数: session: 数据库会话 menu_ids: 菜单 ID 列表 返回: 匹配的菜单列表 """ if not menu_ids: return [] stmt = select(Menu).where(Menu.id.in_(menu_ids)).order_by(Menu.sort_no.asc(), Menu.id.asc()) return list(session.execute(stmt).scalars()) def list_menus(self, session: Session, filters: dict) -> list[Menu]: """查询菜单列表 支持按状态和菜单类型过滤,按 sort_no 和 id 升序排列。 参数: session: 数据库会话 filters: 过滤条件字典,支持 status / menu_type 返回: 菜单列表 """ stmt = select(Menu) if filters.get("status") is not None: stmt = stmt.where(Menu.status == filters["status"]) if filters.get("menu_type"): stmt = stmt.where(Menu.menu_type == filters["menu_type"]) stmt = stmt.order_by(Menu.sort_no.asc(), Menu.id.asc()) return list(session.execute(stmt).scalars())