mirror of
https://github.com/fastapiadmin/FastapiAdmin.git
synced 2026-09-21 12:52:26 +00:00
refactor: 移除多租户和客户相关代码及功能
refactor: 统一状态字段为字符串类型并更新相关组件 refactor: 更新模型基类移除租户和客户相关字段 refactor: 简化数据权限控制逻辑 refactor: 优化类型注解使用Python 3.10+语法 refactor: 清理无用导入和注释 docs: 更新文档移除多租户相关内容
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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()))
|
||||
@@ -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,
|
||||
|
||||
@@ -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 '否'
|
||||
|
||||
Reference in New Issue
Block a user