refactor: 移除多租户和客户相关代码及功能

refactor: 统一状态字段为字符串类型并更新相关组件
refactor: 更新模型基类移除租户和客户相关字段
refactor: 简化数据权限控制逻辑
refactor: 优化类型注解使用Python 3.10+语法
refactor: 清理无用导入和注释
docs: 更新文档移除多租户相关内容
This commit is contained in:
zhangtao
2025-12-01 00:36:05 +08:00
parent 463e2fca23
commit 3c12fa56eb
173 changed files with 3143 additions and 7672 deletions
@@ -1,14 +1,14 @@
# -*- coding: utf-8 -*-
import urllib.parse
from fastapi import APIRouter, Depends, Body, Path, Query, Form, File, UploadFile, Request
from fastapi import APIRouter, Depends, Body, Path, UploadFile, Request
from fastapi.responses import JSONResponse, StreamingResponse
from sqlalchemy.ext.asyncio import AsyncSession
from app.common.response import StreamResponse, SuccessResponse
from app.common.request import PaginationService
from app.utils.common_util import bytes2file_response
from app.core.router_class import OperationLogRoute
from app.utils.common_util import bytes2file_response
from app.core.dependencies import db_getter, get_current_user, AuthPermission
from app.core.base_params import PaginationQueryParam
from app.core.base_schema import BatchSetAvailable
+33 -41
View File
@@ -1,10 +1,9 @@
# -*- coding: utf-8 -*-
from typing import Dict, List, Optional, Sequence, Union, Any
from typing import Sequence, Any
from datetime import datetime
from app.core.base_crud import CRUDBase
from app.api.v1.module_system.auth.schema import AuthSchema
from .model import UserModel
from .schema import UserCreateSchema, UserForgetPasswordSchema, UserUpdateSchema
@@ -25,67 +24,65 @@ class UserCRUD(CRUDBase[UserModel, UserCreateSchema, UserUpdateSchema]):
self.auth = auth
super().__init__(model=UserModel, auth=auth)
async def get_by_id_crud(self, id: int, preload: Optional[List[Union[str, Any]]] = None) -> Optional[UserModel]:
async def get_by_id_crud(self, id: int, preload: list[str | Any] | None = None) -> UserModel | None:
"""
根据id获取用户信息
参数:
- id (int): 用户ID
- preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项
- preload (list[str | Any] | None): 预加载关系,未提供时使用模型默认项
返回:
- Optional[UserModel]: 用户信息,如果不存在则为None
- UserModel | None: 用户信息,如果不存在则为None
"""
return await self.get(
preload=preload,
id=id,
)
async def get_by_username_crud(self, username: str, preload: Optional[List[Union[str, Any]]] = None) -> Optional[UserModel]:
async def get_by_username_crud(self, username: str, preload: list[str | Any] | None = None) -> UserModel | None:
"""
根据用户名获取用户信息
参数:
- username (str): 用户名
- preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项
- preload (list[str | Any] | None): 预加载关系,未提供时使用模型默认项
返回:
- Optional[UserModel]: 用户信息,如果不存在则为None
- UserModel | None: 用户信息,如果不存在则为None
"""
return await self.get(
preload=preload,
username=username,
)
async def get_by_mobile_crud(self, mobile: str, preload: Optional[List[Union[str, Any]]] = None) -> Optional[UserModel]:
async def get_by_mobile_crud(self, mobile: str, preload: list[str | Any] | None = None) -> UserModel | None:
"""
根据手机号获取用户信息
参数:
- mobile (str): 手机号
- preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项
- preload (list[str | Any] | None): 预加载关系,未提供时使用模型默认项
返回:
- Optional[UserModel]: 用户信息,如果不存在则为None
- UserModel | None: 用户信息,如果不存在则为None
"""
return await self.get(
preload=preload,
mobile=mobile,
)
async def get_list_crud(self, search: Optional[Dict] = None, order_by: Optional[List[Dict[str, str]]] = None, preload: Optional[List[Union[str, Any]]] = None) -> Sequence[UserModel]:
async def get_list_crud(self, search: dict | None = None, order_by: list[dict[str, str]] | None = None, preload: list[str | Any] | None = None) -> Sequence[UserModel]:
"""
获取用户列表
参数:
- search (Dict | None): 查询参数对象。
- order_by (List[Dict[str, str]] | None): 排序参数列表。
- preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项
- search (dict | None): 查询参数对象。
- order_by (list[dict[str, str]] | None): 排序参数列表。
- preload (list[str | Any] | None): 预加载关系,未提供时使用模型默认项
返回:
Sequence[UserModel]: 用户列表
- Sequence[UserModel]: 用户列表
"""
return await self.list(
search=search,
@@ -93,7 +90,7 @@ class UserCRUD(CRUDBase[UserModel, UserCreateSchema, UserUpdateSchema]):
preload=preload,
)
async def update_last_login_crud(self, id: int) -> Optional[UserModel]:
async def update_last_login_crud(self, id: int) -> UserModel | None:
"""
更新用户最后登录时间
@@ -101,16 +98,16 @@ class UserCRUD(CRUDBase[UserModel, UserCreateSchema, UserUpdateSchema]):
- id (int): 用户ID
返回:
- Optional[UserModel]: 更新后的用户信息
- UserModel | None: 更新后的用户信息
"""
return await self.update(id=id, data={"last_login": datetime.now()})
async def set_available_crud(self, ids: List[int], status: str) -> None:
async def set_available_crud(self, ids: list[int], status: str) -> None:
"""
批量设置用户可用状态
参数:
- ids (List[int]): 用户ID列表
- ids (list[int]): 用户ID列表
- status (bool): 可用状态
返回:
@@ -118,13 +115,13 @@ class UserCRUD(CRUDBase[UserModel, UserCreateSchema, UserUpdateSchema]):
"""
await self.set(ids=ids, status=status)
async def set_user_roles_crud(self, user_ids: List[int], role_ids: List[int]) -> None:
async def set_user_roles_crud(self, user_ids: list[int], role_ids: list[int]) -> None:
"""
批量设置用户角色
参数:
- user_ids (List[int]): 用户ID列表
- role_ids (List[int]): 角色ID列表
- user_ids (list[int]): 用户ID列表
- role_ids (list[int]): 角色ID列表
返回:
- None:
@@ -139,16 +136,15 @@ class UserCRUD(CRUDBase[UserModel, UserCreateSchema, UserUpdateSchema]):
relationship = obj.roles
relationship.clear()
relationship.extend(role_objs)
await self.db.flush()
await self.auth.db.flush()
async def set_user_positions_crud(self, user_ids: List[int], position_ids: List[int]) -> None:
async def set_user_positions_crud(self, user_ids: list[int], position_ids: list[int]) -> None:
"""
批量设置用户岗位
参数:
- user_ids (List[int]): 用户ID列表
- position_ids (List[int]): 岗位ID列表
- user_ids (list[int]): 用户ID列表
- position_ids (list[int]): 岗位ID列表
返回:
- None:
@@ -163,9 +159,9 @@ class UserCRUD(CRUDBase[UserModel, UserCreateSchema, UserUpdateSchema]):
relationship = obj.positions
relationship.clear()
relationship.extend(position_objs)
await self.db.flush()
await self.auth.db.flush()
async def change_password_crud(self, id: int, password_hash: str) -> Optional[UserModel]:
async def change_password_crud(self, id: int, password_hash: str) -> UserModel:
"""
修改用户密码
@@ -174,13 +170,11 @@ class UserCRUD(CRUDBase[UserModel, UserCreateSchema, UserUpdateSchema]):
- password_hash (str): 密码哈希值
返回:
- Optional[UserModel]: 更新后的用户信息
- UserModel: 更新后的用户信息
"""
return await self.update(id=id, data=UserUpdateSchema(password=password_hash))
async def forget_password_crud(self, id: int, password_hash: str) -> Optional[UserModel]:
async def forget_password_crud(self, id: int, password_hash: str) -> UserModel:
"""
重置密码
@@ -189,11 +183,11 @@ class UserCRUD(CRUDBase[UserModel, UserCreateSchema, UserUpdateSchema]):
- password_hash (str): 密码哈希值
返回:
- Optional[UserModel]: 更新后的用户信息
- UserModel: 更新后的用户信息
"""
return await self.update(id=id, data=UserUpdateSchema(password=password_hash))
async def register_user_crud(self, data: UserForgetPasswordSchema) -> Optional[UserModel]:
async def register_user_crud(self, data: UserForgetPasswordSchema) -> UserModel:
"""
用户注册
@@ -201,8 +195,6 @@ class UserCRUD(CRUDBase[UserModel, UserCreateSchema, UserUpdateSchema]):
- data (UserForgetPasswordSchema): 用户注册信息
返回:
- Optional[UserModel]: 注册成功的用户信息,如果用户名已存在则返回None
- UserModel: 注册成功的用户信息
"""
if await self.get_by_username_crud(username=data.username):
return None
return await self.create(data=UserCreateSchema(**data.model_dump()))
+3 -39
View File
@@ -5,7 +5,7 @@ from datetime import datetime
from sqlalchemy import Boolean, String, Integer, DateTime, ForeignKey
from sqlalchemy.orm import relationship, Mapped, mapped_column
from app.core.base_model import MappedBase, ModelMixin, UserMixin, TenantMixin, CustomerMixin
from app.core.base_model import MappedBase, ModelMixin, UserMixin
if TYPE_CHECKING:
from app.api.v1.module_system.dept.model import DeptModel
@@ -59,47 +59,13 @@ class UserPositionsModel(MappedBase):
)
class UserModel(ModelMixin, UserMixin, TenantMixin, CustomerMixin):
class UserModel(ModelMixin, UserMixin):
"""
用户模型
用户类型与数据隔离关系:
====================
- 系统用户(user_type=0):
* tenant_id=1(系统租户)
* customer_id=None
* 用于平台管理,可管理所有租户
- 租户管理员(user_type=1):
* tenant_id>1
* customer_id=None
* 可管理本租户内所有数据(包括所有客户)
- 租户普通用户(user_type=1):
* tenant_id>1
* customer_id=None
* 数据权限由role.data_scope控制
- 客户用户(user_type=2):
* tenant_id>1
* customer_id>1
* 只能访问其所属客户的数据
数据权限实现机制:
==================
通过角色的data_scope字段和用户-部门-角色关系实现:
- 1(仅本人): WHERE created_id = current_user.id
- 2(本部门): WHERE user.dept_id = current_user.dept_id
- 3(本部门及以下): WHERE dept.tree_path LIKE 'current_user.dept.tree_path%'
- 4(全部数据): WHERE tenant_id = current_user.tenant_id (AND customer_id IS NULL OR customer_id = current_user.customer_id)
- 5(自定义): WHERE dept_id IN (SELECT dept_id FROM role_depts WHERE role_id IN current_user.role_ids)
客户用户额外限制:
- 无论data_scope如何,都必须加上: AND customer_id = current_user.customer_id
"""
__tablename__: str = "sys_user"
__table_args__: dict[str, str] = ({'comment': '用户表'})
__loader_options__: list[str] = ["dept", "roles", "positions", "created_by", "updated_by", "tenant", "customer"]
__loader_options__: list[str] = ["dept", "roles", "positions", "created_by", "updated_by"]
username: Mapped[str] = mapped_column(String(32), nullable=False, unique=True, comment="用户名/登录账号")
password: Mapped[str] = mapped_column(String(255), nullable=False, comment="密码哈希")
@@ -115,8 +81,6 @@ class UserModel(ModelMixin, UserMixin, TenantMixin, CustomerMixin):
github_login: Mapped[str | None] = mapped_column(String(32), nullable=True, comment="Github登录")
wx_login: Mapped[str | None] = mapped_column(String(32), nullable=True, comment="微信登录")
qq_login: Mapped[str | None] = mapped_column(String(32), nullable=True, comment="QQ登录")
user_type: Mapped[str] = mapped_column(String(32), nullable=False, default="0", comment="用户类型(0:系统用户 1:租户用户 2:客户用户)")
salt: Mapped[str | None] = mapped_column(String(255), nullable=True, comment="加密盐")
dept_id: Mapped[int | None] = mapped_column(
Integer,
+49 -50
View File
@@ -1,32 +1,32 @@
# -*- coding: utf-8 -*-
from typing import Optional, List
from fastapi import Query
from pydantic import BaseModel, ConfigDict, Field, EmailStr, field_validator
from urllib.parse import urlparse
from app.core.validator import DateTimeStr, mobile_validator
from app.core.base_schema import BaseSchema, CommonSchema, UserBySchema, TenantSchema, CustomerSchema
from app.core.base_schema import BaseSchema, CommonSchema, UserBySchema
from app.core.validator import DateTimeStr
from app.api.v1.module_system.menu.schema import MenuOutSchema
from app.api.v1.module_system.role.schema import RoleOutSchema
class CurrentUserUpdateSchema(BaseModel):
"""基础用户信息"""
name: Optional[str] = Field(default=None, max_length=32, description="名称")
mobile: Optional[str] = Field(default=None, description="手机号")
email: Optional[EmailStr] = Field(default=None, description="邮箱")
gender: Optional[str] = Field(default=None, description="性别")
avatar: Optional[str] = Field(default=None, description="头像")
name: str | None = Field(default=None, max_length=32, description="名称")
mobile: str | None = Field(default=None, description="手机号")
email: EmailStr | None = Field(default=None, description="邮箱")
gender: str | None = Field(default=None, description="性别")
avatar: str | None = Field(default=None, description="头像")
@field_validator("mobile")
@classmethod
def validate_mobile(cls, value: Optional[str]):
def validate_mobile(cls, value: str | None):
return mobile_validator(value)
@field_validator("avatar")
@classmethod
def validate_avatar(cls, value: Optional[str]):
def validate_avatar(cls, value: str | None):
if not value:
return value
parsed = urlparse(value)
@@ -37,18 +37,17 @@ class CurrentUserUpdateSchema(BaseModel):
class UserRegisterSchema(BaseModel):
"""注册"""
name: Optional[str] = Field(default=None, max_length=32, description="名称")
mobile: Optional[str] = Field(default=None, description="手机号")
name: str | None = Field(default=None, max_length=32, description="名称")
mobile: str | None = Field(default=None, description="手机号")
username: str = Field(..., max_length=32, description="账号")
password: str = Field(..., max_length=128, description="密码哈希值")
role_ids: Optional[List[int]] = Field(default=[1], description='角色ID')
created_id: Optional[int] = Field(default=1, description='创建人ID')
description: Optional[str] = Field(default=None, max_length=255, description="备注")
user_type: Optional[str] = Field(default="0", max_length=32, description="用户类型")
role_ids: list[int] | None = Field(default=[1], description='角色ID')
created_id: int | None = Field(default=1, description='创建人ID')
description: str | None = Field(default=None, max_length=255, description="备注")
@field_validator("mobile")
@classmethod
def validate_mobile(cls, value: Optional[str]):
def validate_mobile(cls, value: str | None):
return mobile_validator(value)
@field_validator("username")
@@ -68,11 +67,11 @@ class UserForgetPasswordSchema(BaseModel):
"""忘记密码"""
username: str = Field(..., max_length=32, description="用户名")
new_password: str = Field(..., max_length=128, description="新密码")
mobile: Optional[str] = Field(default=None, description="手机号")
mobile: str | None = Field(default=None, description="手机号")
@field_validator("mobile")
@classmethod
def validate_mobile(cls, value: Optional[str]):
def validate_mobile(cls, value: str | None):
return mobile_validator(value)
@@ -92,54 +91,50 @@ class UserCreateSchema(CurrentUserUpdateSchema):
"""新增"""
model_config = ConfigDict(from_attributes=True)
username: Optional[str] = Field(default=None, max_length=32, description="用户名")
password: Optional[str] = Field(default=None, max_length=128, description="密码哈希值")
username: str | None = Field(default=None, max_length=32, description="用户名")
password: str | None = Field(default=None, max_length=128, description="密码哈希值")
status: str = Field(default="0", description="是否可用")
description: Optional[str] = Field(default=None, max_length=255, description="备注")
user_type: Optional[str] = Field(default="0", max_length=32, description="用户类型")
is_superuser: Optional[bool] = Field(default=False, description="是否超管")
tenant_id: Optional[int] = Field(default=None, description='租户ID')
dept_id: Optional[int] = Field(default=None, description='部门ID')
role_ids: Optional[List[int]] = Field(default=[], description='角色ID')
position_ids: Optional[List[int]] = Field(default=[], description='岗位ID')
description: str | None = Field(default=None, max_length=255, description="备注")
is_superuser: bool | None = Field(default=False, description="是否超管")
dept_id: int | None = Field(default=None, description='部门ID')
role_ids: list[int] | None = Field(default=[], description='角色ID')
position_ids: list[int] | None = Field(default=[], description='岗位ID')
class UserUpdateSchema(UserCreateSchema):
"""更新"""
model_config = ConfigDict(from_attributes=True)
last_login: Optional[DateTimeStr] = Field(default=None, description="最后登录时间")
last_login: DateTimeStr | None = Field(default=None, description="最后登录时间")
class UserOutSchema(UserUpdateSchema, BaseSchema, UserBySchema, TenantSchema, CustomerSchema):
class UserOutSchema(UserUpdateSchema, BaseSchema, UserBySchema):
"""响应"""
model_config = ConfigDict(arbitrary_types_allowed=True, from_attributes=True)
is_superuser: bool = Field(default=False, description="是否超管")
gitee_login: Optional[str] = Field(default=None, max_length=32, description="Gitee登录")
github_login: Optional[str] = Field(default=None, max_length=32, description="Github登录")
wx_login: Optional[str] = Field(default=None, max_length=32, description="微信登录")
qq_login: Optional[str] = Field(default=None, max_length=32, description="QQ登录")
user_type: Optional[str] = Field(default="0", max_length=32, description="用户类型")
salt: Optional[str] = Field(default=None, max_length=255, description="加密盐")
dept_name: Optional[str] = Field(default=None, description='部门名称')
dept: Optional[CommonSchema] = Field(default=None, description='部门')
roles: Optional[List[RoleOutSchema]] = Field(default=[], description='角色')
positions: Optional[List[CommonSchema]] = Field(default=[], description='岗位')
gitee_login: str | None = Field(default=None, max_length=32, description="Gitee登录")
github_login: str | None = Field(default=None, max_length=32, description="Github登录")
wx_login: str | None = Field(default=None, max_length=32, description="微信登录")
qq_login: str | None = Field(default=None, max_length=32, description="QQ登录")
dept_name: str | None = Field(default=None, description='部门名称')
dept: CommonSchema | None = Field(default=None, description='部门')
positions: list[CommonSchema] | None = Field(default=[], description='岗位')
roles: list[RoleOutSchema] | None = Field(default=[], description='角色')
menus: list[MenuOutSchema] | None = Field(default=[], description='菜单')
class UserQueryParam:
"""用户管理查询参数"""
def __init__(
self,
username: Optional[str] = Query(None, description="用户名"),
name: Optional[str] = Query(None, description="名称"),
mobile: Optional[str] = Query(None, description="手机号", pattern=r'^1[3-9]\d{9}$'),
email: Optional[str] = Query(None, description="邮箱", pattern=r'^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$'),
dept_id: Optional[int] = Query(None, description="部门ID"),
status: Optional[str] = Query(None, description="是否可用"),
created_time: Optional[list[DateTimeStr]] = Query(None, description="创建时间范围", example=["2025-01-01 00:00:00", "2025-12-31 23:59:59"]),
created_id: Optional[int] = Query(None, description="创建人"),
username: str | None = Query(None, description="用户名"),
name: str | None = Query(None, description="名称"),
mobile: str | None = Query(None, description="手机号", pattern=r'^1[3-9]\d{9}$'),
email: str | None = Query(None, description="邮箱", pattern=r'^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$'),
dept_id: int | None = Query(None, description="部门ID"),
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:
# 模糊查询字段
@@ -151,8 +146,12 @@ class UserQueryParam:
# 精确查询字段
self.dept_id = dept_id
self.created_id = created_id
self.updated_id = updated_id
self.status = status
# 时间范围查询
if created_time and len(created_time) == 2:
self.created_time = ("between", (created_time[0], created_time[1]))
if updated_time and len(updated_time) == 2:
self.updated_time = ("between", (updated_time[0], updated_time[1]))
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
import io
from typing import Any, Dict, List, Optional
from typing import Any
from fastapi import UploadFile
import pandas as pd
@@ -19,7 +19,6 @@ from ..menu.crud import MenuCRUD
from ..dept.crud import DeptCRUD
from ..auth.schema import AuthSchema
from ..menu.schema import MenuOutSchema
from ..tenant.service import TenantService
from .crud import UserCRUD
from .schema import (
CurrentUserUpdateSchema,
@@ -38,7 +37,7 @@ class UserService:
"""用户模块服务层"""
@classmethod
async def get_detail_by_id_service(cls, auth: AuthSchema, id: int) -> Dict:
async def get_detail_by_id_service(cls, auth: AuthSchema, id: int) -> dict:
"""
根据ID获取用户详情
@@ -47,7 +46,7 @@ class UserService:
- id (int): 用户ID
返回:
- Dict: 用户详情字典
- dict: 用户详情字典
"""
user = await UserCRUD(auth).get_by_id_crud(id=id)
if not user:
@@ -59,22 +58,21 @@ class UserService:
UserOutSchema.dept_name = dept.name if dept else None
else:
UserOutSchema.dept_name = None
return UserOutSchema.model_validate(user).model_dump()
@classmethod
async def get_user_list_service(cls, auth: AuthSchema, search: Optional[UserQueryParam] = None, order_by: Optional[List[Dict[str, str]]] = None) -> List[Dict]:
async def get_user_list_service(cls, auth: AuthSchema, search: UserQueryParam | None = None, order_by: list[dict[str, str]] | None = None) -> list[dict]:
"""
获取用户列表
参数:
- auth (AuthSchema): 认证信息模型
- search (UserQueryParam | None): 查询参数对象。
- order_by (List[Dict[str, str]] | None): 排序参数列表。
- order_by (list[dict[str, str]] | None): 排序参数列表。
返回:
- List[Dict]: 用户详情字典列表
- list[dict]: 用户详情字典列表
"""
user_list = await UserCRUD(auth).get_list_crud(search=search.__dict__, order_by=order_by)
user_dict_list = []
@@ -85,7 +83,7 @@ class UserService:
return user_dict_list
@classmethod
async def create_user_service(cls, data: UserCreateSchema, auth: AuthSchema) -> Dict:
async def create_user_service(cls, data: UserCreateSchema, auth: AuthSchema) -> dict:
"""
创建用户
@@ -94,7 +92,7 @@ class UserService:
- auth (AuthSchema): 认证信息模型
返回:
- Dict: 创建后的用户详情字典
- dict: 创建后的用户详情字典
"""
if not data.username:
raise CustomException(msg="用户名不能为空")
@@ -111,32 +109,16 @@ class UserService:
dept = await DeptCRUD(auth).get_by_id_crud(id=data.dept_id)
if not dept:
raise CustomException(msg='部门不存在')
# 创建用户
if data.password:
data.password = PwdUtil.set_password_hash(password=data.password)
user_dict = data.model_dump(exclude_unset=True, exclude={"role_ids", "position_ids"})
# 多租户隔离:确保用户正确关联到创建者的租户和客户
# 非系统管理员创建的用户,自动继承创建者的租户和客户信息
if auth.user and not auth.user.is_superuser:
# 检查是否尝试为其他租户创建用户
if data.tenant_id and data.tenant_id != auth.user.tenant_id:
raise CustomException(msg='没有权限为其他租户创建用户')
# 自动设置租户ID
user_dict["tenant_id"] = auth.user.tenant_id
# 如果创建者是客户用户,自动设置客户ID
if auth.user.customer_id:
user_dict["customer_id"] = auth.user.customer_id
# 限制用户类型,非系统管理员只能创建普通用户
user_dict["user_type"] = "0"
# 创建用户
new_user = await UserCRUD(auth).create(data=user_dict)
# 设置角色和岗位
# 设置角色
if data.role_ids and len(data.role_ids) > 0:
await UserCRUD(auth).set_user_roles_crud(user_ids=[new_user.id], role_ids=data.role_ids)
# 设置岗位
if data.position_ids and len(data.position_ids) > 0:
await UserCRUD(auth).set_user_positions_crud(user_ids=[new_user.id], position_ids=data.position_ids)
@@ -144,7 +126,7 @@ class UserService:
return new_user_dict
@classmethod
async def update_user_service(cls, id: int, data: UserUpdateSchema, auth: AuthSchema) -> Dict:
async def update_user_service(cls, id: int, data: UserUpdateSchema, auth: AuthSchema) -> dict:
"""
更新用户
@@ -157,10 +139,8 @@ class UserService:
- Dict: 更新后的用户详情字典
"""
if not data.username:
raise CustomException(msg="用户名不能为空")
# 检查是否是超级管理员
if data.is_superuser:
raise CustomException(msg='超级管理员系统唯一')
raise CustomException(msg="账号不能为空")
# 检查用户是否存在
user = await UserCRUD(auth).get_by_id_crud(id=id)
if not user:
@@ -169,16 +149,11 @@ class UserService:
# 检查是否尝试修改超级管理员
if user.is_superuser:
raise CustomException(msg='超级管理员不允许修改')
# 多租户权限检查:非系统管理员只能修改同租户的用户
if auth.user and not auth.user.is_superuser:
if user.tenant_id != auth.user.tenant_id:
raise CustomException(msg='没有权限修改其他租户的用户')
# 检查用户名是否重复
exist_user = await UserCRUD(auth).get_by_username_crud(username=data.username)
if exist_user and exist_user.id != id:
raise CustomException(msg='已存在相同的用户名')
raise CustomException(msg='已存在相同的账号')
# 新增:检查手机号是否重复
if data.mobile:
exist_mobile_user = await UserCRUD(auth).get_by_mobile_crud(mobile=data.mobile)
@@ -203,7 +178,7 @@ class UserService:
update_dict['password'] = PwdUtil.set_password_hash(password=data.password)
# 更新用户 - 排除不应被修改的字段
user_dict = data.model_dump(exclude_unset=True, exclude={"role_ids", "position_ids", "last_login", "password", "tenant_id", "customer_id", "user_type"})
user_dict = data.model_dump(exclude_unset=True, exclude={"role_ids", "position_ids", "last_login", "password"})
user_dict.update(update_dict)
new_user = await UserCRUD(auth).update(id=id, data=user_dict)
@@ -253,11 +228,6 @@ class UserService:
raise CustomException(msg="用户已启用,不能删除")
if auth.user and auth.user.id == id:
raise CustomException(msg="不能删除当前登陆用户")
# 多租户权限检查:非系统管理员只能删除同租户的用户
if auth.user and not auth.user.is_superuser:
if user.tenant_id != auth.user.tenant_id:
raise CustomException(msg='没有权限删除其他租户的用户')
# 删除用户角色关联数据
await UserCRUD(auth).set_user_roles_crud(user_ids=ids, role_ids=[])
@@ -268,7 +238,7 @@ class UserService:
await UserCRUD(auth).delete(ids=ids)
@classmethod
async def get_current_user_info_service(cls, auth: AuthSchema) -> Dict:
async def get_current_user_info_service(cls, auth: AuthSchema) -> dict:
"""
获取当前用户信息
@@ -311,7 +281,7 @@ class UserService:
return user_dict
@classmethod
async def update_current_user_info_service(cls, auth: AuthSchema, data: CurrentUserUpdateSchema) -> Dict:
async def update_current_user_info_service(cls, auth: AuthSchema, data: CurrentUserUpdateSchema) -> dict:
"""
更新当前用户信息
@@ -364,7 +334,7 @@ class UserService:
await UserCRUD(auth).set_available_crud(ids=data.ids, status=data.status)
@classmethod
async def upload_avatar_service(cls, base_url: str, file: UploadFile) -> Dict:
async def upload_avatar_service(cls, base_url: str, file: UploadFile) -> dict:
"""
上传用户头像
@@ -375,8 +345,6 @@ class UserService:
返回:
- Dict: 上传头像响应字典
"""
if not file:
raise CustomException(msg="请选择要上传的文件")
filename, filepath, file_url = await UploadUtil.upload_file(file=file, base_url=base_url)
return UploadResponseSchema(
@@ -387,7 +355,7 @@ class UserService:
).model_dump()
@classmethod
async def change_user_password_service(cls, auth: AuthSchema, data: UserChangePasswordSchema) -> Dict:
async def change_user_password_service(cls, auth: AuthSchema, data: UserChangePasswordSchema) -> dict:
"""
修改用户密码
@@ -416,7 +384,7 @@ class UserService:
return UserOutSchema.model_validate(new_user).model_dump()
@classmethod
async def reset_user_password_service(cls, auth: AuthSchema, data: ResetPasswordSchema) -> Dict:
async def reset_user_password_service(cls, auth: AuthSchema, data: ResetPasswordSchema) -> dict:
"""
重置用户密码
@@ -445,7 +413,7 @@ class UserService:
return UserOutSchema.model_validate(new_user).model_dump()
@classmethod
async def register_user_service(cls, auth: AuthSchema, data: UserRegisterSchema) -> Dict:
async def register_user_service(cls, auth: AuthSchema, data: UserRegisterSchema) -> dict:
"""
用户注册
@@ -471,12 +439,6 @@ class UserService:
# 设置创建人ID
if auth.user and auth.user.id:
create_dict["created_id"] = auth.user.id
# 多租户隔离:如果是租户用户注册,自动关联到当前租户
if auth.user.tenant_id:
create_dict["tenant_id"] = auth.user.tenant_id
# 如果是客户用户注册,自动关联到当前客户
if auth.user.customer_id:
create_dict["customer_id"] = auth.user.customer_id
result = await UserCRUD(auth).create(data=create_dict)
if data.role_ids:
@@ -484,7 +446,7 @@ class UserService:
return UserOutSchema.model_validate(result).model_dump()
@classmethod
async def forget_password_service(cls, auth: AuthSchema, data: UserForgetPasswordSchema) -> Dict:
async def forget_password_service(cls, auth: AuthSchema, data: UserForgetPasswordSchema) -> dict:
"""
用户忘记密码
@@ -630,7 +592,7 @@ class UserService:
)
@classmethod
async def export_user_list_service(cls, user_list: List[Dict[str, Any]]) -> bytes:
async def export_user_list_service(cls, user_list: list[dict[str, Any]]) -> bytes:
"""
导出用户列表为Excel文件
@@ -659,14 +621,14 @@ class UserService:
'description': '备注',
'created_time': '创建时间',
'updated_time': '更新时间',
'creator': '创建者',
'updated_id': '更新者ID',
}
# 复制数据并转换
# creator = {'id': 1, 'name': '管理员', 'username': 'admin'}
data = user_list.copy()
for item in data:
item['status'] = '启用' if item.get('status') else '停用'
item['status'] = '启用' if item.get('status') == "0" else '停用'
gender = item.get('gender')
item['gender'] = '' if gender == '1' else ('' if gender == '2' else '未知')
item['is_superuser'] = '' if item.get('is_superuser') else ''