Initial commit

This commit is contained in:
shiyu
2026-02-09 13:19:28 +08:00
commit 17d13999b0
300 changed files with 48565 additions and 0 deletions
+4
View File
@@ -0,0 +1,4 @@
from .service import UserService
__all__ = ["UserService"]
+79
View File
@@ -0,0 +1,79 @@
from typing import Annotated
from fastapi import APIRouter, Depends
from domain.auth.service import get_current_active_user
from domain.auth.types import User
from domain.permission import require_system_permission
from domain.permission.types import SystemPermission
from .service import UserService
from .types import UserCreate, UserDetail, UserInfo, UserRoleAssign, UserUpdate
router = APIRouter(prefix="/api", tags=["user"])
@router.get("/users", response_model=list[UserInfo])
@require_system_permission(SystemPermission.USER_LIST)
async def list_users(
current_user: Annotated[User, Depends(get_current_active_user)]
) -> list[UserInfo]:
return await UserService.get_all_users()
@router.get("/users/{user_id}", response_model=UserDetail)
@require_system_permission(SystemPermission.USER_LIST)
async def get_user(
user_id: int,
current_user: Annotated[User, Depends(get_current_active_user)],
) -> UserDetail:
return await UserService.get_user(user_id)
@router.post("/users", response_model=UserDetail)
@require_system_permission(SystemPermission.USER_CREATE)
async def create_user(
data: UserCreate,
current_user: Annotated[User, Depends(get_current_active_user)],
) -> UserDetail:
return await UserService.create_user(data, current_user.id)
@router.put("/users/{user_id}", response_model=UserDetail)
@require_system_permission(SystemPermission.USER_EDIT)
async def update_user(
user_id: int,
data: UserUpdate,
current_user: Annotated[User, Depends(get_current_active_user)],
) -> UserDetail:
return await UserService.update_user(user_id, data, current_user.id)
@router.delete("/users/{user_id}")
@require_system_permission(SystemPermission.USER_DELETE)
async def delete_user(
user_id: int,
current_user: Annotated[User, Depends(get_current_active_user)],
) -> dict:
await UserService.delete_user(user_id, current_user.id)
return {"success": True}
@router.post("/users/{user_id}/roles", response_model=list[str])
@require_system_permission(SystemPermission.USER_EDIT)
async def set_user_roles(
user_id: int,
data: UserRoleAssign,
current_user: Annotated[User, Depends(get_current_active_user)],
) -> list[str]:
return await UserService.set_user_roles(user_id, data.role_ids)
@router.delete("/users/{user_id}/roles/{role_id}", response_model=list[str])
@require_system_permission(SystemPermission.USER_EDIT)
async def remove_user_role(
user_id: int,
role_id: int,
current_user: Annotated[User, Depends(get_current_active_user)],
) -> list[str]:
return await UserService.remove_user_role(user_id, role_id)
+190
View File
@@ -0,0 +1,190 @@
from typing import List
from fastapi import HTTPException
from domain.auth.service import AuthService
from domain.permission.service import PermissionService
from models.database import Role, UserAccount, UserRole
from .types import UserCreate, UserDetail, UserInfo, UserUpdate
class UserService:
"""用户管理服务"""
@classmethod
async def get_all_users(cls) -> List[UserInfo]:
users = await UserAccount.all().order_by("id")
return [
UserInfo(
id=u.id,
username=u.username,
email=u.email,
full_name=u.full_name,
disabled=u.disabled,
is_admin=u.is_admin,
created_at=u.created_at,
last_login=u.last_login,
)
for u in users
]
@classmethod
async def get_user(cls, user_id: int) -> UserDetail:
user = await UserAccount.get_or_none(id=user_id).prefetch_related("created_by")
if not user:
raise HTTPException(404, detail="用户不存在")
user_roles = await UserRole.filter(user_id=user_id).prefetch_related("role")
roles = [ur.role.name for ur in user_roles]
created_by_username = None
if user.created_by_id:
creator = await UserAccount.get_or_none(id=user.created_by_id)
if creator:
created_by_username = creator.username
return UserDetail(
id=user.id,
username=user.username,
email=user.email,
full_name=user.full_name,
disabled=user.disabled,
is_admin=user.is_admin,
created_at=user.created_at,
last_login=user.last_login,
roles=roles,
created_by_username=created_by_username,
)
@classmethod
async def get_users_by_role(cls, role_id: int) -> List[UserInfo]:
role = await Role.get_or_none(id=role_id)
if not role:
raise HTTPException(404, detail="角色不存在")
user_roles = await UserRole.filter(role_id=role_id).prefetch_related("user")
users = [ur.user for ur in user_roles if ur.user]
users.sort(key=lambda u: u.id)
return [
UserInfo(
id=u.id,
username=u.username,
email=u.email,
full_name=u.full_name,
disabled=u.disabled,
is_admin=u.is_admin,
created_at=u.created_at,
last_login=u.last_login,
)
for u in users
]
@classmethod
async def create_user(cls, data: UserCreate, creator_id: int) -> UserDetail:
existing = await UserAccount.get_or_none(username=data.username)
if existing:
raise HTTPException(400, detail="用户名已存在")
if data.email:
existing_email = await UserAccount.get_or_none(email=data.email)
if existing_email:
raise HTTPException(400, detail="邮箱已被使用")
hashed_password = AuthService.get_password_hash(data.password)
user = await UserAccount.create(
username=data.username,
email=data.email,
full_name=data.full_name,
hashed_password=hashed_password,
disabled=data.disabled,
is_admin=data.is_admin,
created_by_id=creator_id,
)
if data.role_ids:
for role_id in data.role_ids:
role = await Role.get_or_none(id=role_id)
if role:
await UserRole.create(user_id=user.id, role_id=role_id)
return await cls.get_user(user.id)
@classmethod
async def update_user(cls, user_id: int, data: UserUpdate, operator_id: int) -> UserDetail:
user = await UserAccount.get_or_none(id=user_id)
if not user:
raise HTTPException(404, detail="用户不存在")
if data.is_admin is not None and user_id == operator_id:
raise HTTPException(400, detail="不能修改自己的管理员状态")
if data.email is not None:
existing = await UserAccount.filter(email=data.email).exclude(id=user_id).first()
if existing:
raise HTTPException(400, detail="邮箱已被使用")
user.email = data.email
if data.full_name is not None:
user.full_name = data.full_name
if data.password is not None:
user.hashed_password = AuthService.get_password_hash(data.password)
if data.is_admin is not None:
user.is_admin = data.is_admin
if data.disabled is not None:
if user_id == operator_id and data.disabled:
raise HTTPException(400, detail="不能禁用自己")
user.disabled = data.disabled
await user.save()
PermissionService.clear_cache(user_id)
return await cls.get_user(user_id)
@classmethod
async def delete_user(cls, user_id: int, operator_id: int) -> None:
if user_id == operator_id:
raise HTTPException(400, detail="不能删除自己")
user = await UserAccount.get_or_none(id=user_id)
if not user:
raise HTTPException(404, detail="用户不存在")
await UserRole.filter(user_id=user_id).delete()
await user.delete()
PermissionService.clear_cache(user_id)
@classmethod
async def set_user_roles(cls, user_id: int, role_ids: List[int]) -> List[str]:
user = await UserAccount.get_or_none(id=user_id)
if not user:
raise HTTPException(404, detail="用户不存在")
roles = await Role.filter(id__in=role_ids)
valid_role_ids = {r.id for r in roles}
invalid_ids = set(role_ids) - valid_role_ids
if invalid_ids:
raise HTTPException(400, detail=f"无效的角色ID: {invalid_ids}")
await UserRole.filter(user_id=user_id).delete()
for role_id in role_ids:
await UserRole.create(user_id=user_id, role_id=role_id)
PermissionService.clear_cache(user_id)
return [r.name for r in roles if r.id in role_ids]
@classmethod
async def remove_user_role(cls, user_id: int, role_id: int) -> List[str]:
user = await UserAccount.get_or_none(id=user_id)
if not user:
raise HTTPException(404, detail="用户不存在")
await UserRole.filter(user_id=user_id, role_id=role_id).delete()
PermissionService.clear_cache(user_id)
user_roles = await UserRole.filter(user_id=user_id).prefetch_related("role")
return [ur.role.name for ur in user_roles]
+42
View File
@@ -0,0 +1,42 @@
from datetime import datetime
from pydantic import BaseModel
class UserInfo(BaseModel):
id: int
username: str
email: str | None = None
full_name: str | None = None
disabled: bool
is_admin: bool
created_at: datetime
last_login: datetime | None = None
class UserDetail(UserInfo):
roles: list[str]
created_by_username: str | None = None
class UserCreate(BaseModel):
username: str
password: str
email: str | None = None
full_name: str | None = None
is_admin: bool = False
disabled: bool = False
role_ids: list[int] = []
class UserUpdate(BaseModel):
email: str | None = None
full_name: str | None = None
password: str | None = None
is_admin: bool | None = None
disabled: bool | None = None
class UserRoleAssign(BaseModel):
role_ids: list[int]