|
|
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("/<string:id>")
|
|
|
@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("/<string:id>")
|
|
|
@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()
|