You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
iTi-Flask/iti/applications/routes/sys/user.py

299 lines
9.1 KiB
Python

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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.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.output(UserSchema)
def get_current_user():
"""
获取当前用户
"""
return success(current_user)
@bp.get("/list")
@jwt_required()
@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()
@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()
@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("/<string:id>")
@jwt_required()
@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("/<string:id>")
@jwt_required()
@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()
@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()