from apiflask import APIBlueprint from iti.applications.extensions import db, sys_log from iti.applications.common.utils import success, page_schema, page from iti.applications.models import ( User, UserSchema, Role, SysDept, sys_user_role, sys_user_dept, ) from flask_jwt_extended import jwt_required, current_user from iti.applications.common import ModelFilter from .schemas.user import ( UserCreateRequest, UserUpdatePasswordRequest, UserPasswordRequestType, UserUpdateRequest, UserQuery, ) from iti.applications.common.enums import LogType from iti.applications.common.exceptions.biz_exp import BizException from iti.applications.service.sys.verification_code import ( check_verification_code, VerificationCodeUsage, ) from sqlalchemy import select, delete, exists from sqlalchemy.orm import noload from iti.applications.common.events import UserEvents, UserRelEvents from iti.applications.extensions import eventbus from iti.applications.common import permission bp = APIBlueprint("sys_user", __name__, url_prefix="/user", tag="系统.用户管理") @bp.get("/current") @jwt_required() @bp.doc(security="JWT") @bp.output(UserSchema) def get_current_user(): """ 获取当前用户 """ return success(current_user) @bp.get("/list") @jwt_required() @bp.doc(security="JWT") @permission("system:user:list") @bp.input(UserQuery.Schema(partial=True), location="query") @bp.output(UserSchema(many=True)) def list_user(query_data: UserQuery): """ 获取用户列表 """ return success(get_list_or_page(query_data)) @bp.get("/page") @jwt_required() @bp.doc(security="JWT") @permission("system:user:list") @bp.input(UserQuery.Schema(partial=True), location="query") @bp.output(page_schema(UserSchema(roles_type="id"))) def page_user(query_data: UserQuery): """ 分页获取用户列表 """ return page(get_list_or_page(query_data)) def get_list_or_page(query_data: UserQuery): """ 获取用户列表或分页 """ query = select(User).order_by(User.created_at.desc()) if query_data.keyword: kw = ModelFilter.escape_like(query_data.keyword) query = query.filter( User.username.like(f"%{kw}%") | User.phone.like(f"%{kw}%") | User.email.like(f"%{kw}%") | User.realname.like(f"%{kw}%") ) else: if query_data.username: query = query.filter(User.username.like(f"%{query_data.username}%")) if query_data.phone: query = query.filter(User.phone.like(f"%{query_data.phone}%")) if query_data.email: query = query.filter(User.email.like(f"%{query_data.email}%")) if query_data.realname: query = query.filter(User.realname.like(f"%{query_data.realname}%")) if query_data.gender: query = query.filter(User.gender == query_data.gender) if query_data.status: query = query.filter(User.status == query_data.status) if query_data.createdAt and len(query_data.createdAt) >= 2: query = query.filter( User.created_at.between(query_data.createdAt[0], query_data.createdAt[1]) ) if query_data.updatedAt and len(query_data.updatedAt) >= 2: query = query.filter( User.updated_at.between(query_data.updatedAt[0], query_data.updatedAt[1]) ) if query_data.page and query_data.size: return db.paginate(query, page=query_data.page, per_page=query_data.size) else: return db.session.scalars(query).all() @bp.post("") @jwt_required() @bp.doc(security="JWT") @permission("system:user:create") @bp.input(UserCreateRequest, location="json") @bp.output(UserSchema) @sys_log( name="创建用户", desc="创建用户", type=LogType.OPERATION, save_db=True, execute_time=True, ) def create_user(json_data: dict): """ 创建用户 """ # 从 json_data 中提取 roles 和 depts,避免传递给 User 构造函数 roles_ids = json_data.pop("roles", None) depts_ids = json_data.pop("depts", None) user = User(**json_data) user.password = json_data.get("password") or None # 绑定角色 if roles_ids: user.roles = db.session.scalars( select(Role) .filter(Role.id.in_(roles_ids)) .options(noload(Role.menus), noload(Role.users)) ).all() # 绑定部门 if depts_ids: user.depts = db.session.scalars( select(SysDept) .filter(SysDept.id.in_(depts_ids)) .options(noload(SysDept.users)) ).all() db.session.add(user) db.session.commit() return success(user) @bp.put("/") @jwt_required() @bp.doc(security="JWT") @permission("system:user:edit") @bp.input(UserUpdateRequest(partial=True), location="json") def update_user(id: str, json_data: dict): """ 更新用户 """ user = db.session.scalar(select(User).filter_by(id=id)) if not user: raise BizException("用户不存在") # 前置检查:用户名、手机号、邮箱不能重复 if json_data.get("username") is not None: if db.session.scalar( select( exists().where( User.username == json_data.get("username"), User.id == id ) ) ): raise BizException("用户名已存在") if json_data.get("phone") is not None: if db.session.scalar( select(exists().where(User.phone == json_data.get("phone"), User.id == id)) ): raise BizException("手机号已存在") if json_data.get("email") is not None: if db.session.scalar( select(exists().where(User.email == json_data.get("email"), User.id == id)) ): raise BizException("邮箱已存在") # 更新用户 roles_updated = False depts_updated = False old_roles = user.roles old_depts = user.depts for key, value in json_data.items(): if key == "roles" and value is not None: user.roles = db.session.scalars( select(Role) .filter(Role.id.in_(value)) .options(noload(Role.menus), noload(Role.users)) ).all() roles_updated = True continue if key == "depts" and value is not None: user.depts = db.session.scalars( select(SysDept) .filter(SysDept.id.in_(value)) .options(noload(SysDept.users)) ).all() depts_updated = True continue if value is not None: setattr(user, key, value) # 提交事务 db.session.commit() # 触发用户事件 eventbus.emit(UserEvents.USER_UPDATED.value, user) if roles_updated: eventbus.emit(UserRelEvents.USER_ROLES_UPDATED.value, user, old_roles) if depts_updated: eventbus.emit(UserRelEvents.USER_DEPTS_UPDATED.value, user, old_depts) return success() @bp.delete("/") @jwt_required() @bp.doc(security="JWT") @permission("system:user:delete") @sys_log( name="删除用户", desc="删除用户", type=LogType.OPERATION, save_db=True, execute_time=True, ) def delete_user(id: str): """ 删除用户 """ user = db.session.scalar(select(User).filter_by(id=id)) if not user: raise BizException("用户不存在") try: # 删除用户关联关系 db.session.execute(delete(sys_user_role).filter_by(user_id=id)) db.session.execute(delete(sys_user_dept).filter_by(user_id=id)) # 删除用户 db.session.delete(user) db.session.commit() except Exception as e: db.session.rollback() raise BizException(f"删除用户失败: {str(e)}") return success() @bp.put("/password") @jwt_required() @bp.doc(security="JWT") @permission("system:user:resetpwd") @bp.input(UserUpdatePasswordRequest.Schema, location="json") @sys_log( name="修改或重置密码", desc="修改或重置密码", type=LogType.SECURITY, save_db=True, execute_time=True, ) def update_password(json_data: UserUpdatePasswordRequest): """ 修改或重置密码 """ user = current_user reqType = json_data.type if reqType == UserPasswordRequestType.UPDATE_BY_OLD_PASSWORD: if not user.check_password(json_data.old_password): raise BizException("密码错误") user.password = json_data.new_password elif reqType == UserPasswordRequestType.UPDATE_BY_VERIFY_CODE: if not check_verification_code( user.phone if user.phone else user.email, json_data.code, VerificationCodeUsage.UPDATE_PASSWORD, ): raise BizException("验证码错误") user.password = json_data.new_password elif reqType == UserPasswordRequestType.RESET_PASSWORD: if not check_verification_code( user.phone if user.phone else user.email, json_data.code, VerificationCodeUsage.RESET_PASSWORD, ): raise BizException("验证码错误") user.password = json_data.new_password else: raise BizException(f"密码请求类型: {reqType} 没有对应处理器", code=500) # 提交事务 db.session.commit() # 触发用户密码更新事件 eventbus.emit(UserEvents.USER_PASSWORD_UPDATED.value, user) return success()