Files
wehub-resource-sync 1443d3fdf9
Ruff Format Check / Ruff Format & Lint (push) Failing after 7m39s
Deploy VitePress site to Pages / build (push) Failing after 9m11s
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:32:26 +08:00

245 lines
8.5 KiB
Python

"""
部门管理路由
提供部门的增删改查接口,仅超级管理员可访问
"""
import re
from fastapi import APIRouter, Depends, HTTPException, status, Request
from pydantic import BaseModel
from sqlalchemy import delete as sqlalchemy_delete, select, func
from sqlalchemy.ext.asyncio import AsyncSession
from yuxi.storage.postgres.models_business import APIKey, Department, User
from yuxi.repositories.department_repository import DepartmentRepository
from yuxi.repositories.user_repository import UserRepository
from server.utils.auth_middleware import get_superadmin_user, get_admin_user, get_db
from yuxi.utils.auth_utils import AuthUtils
from yuxi.services.operation_log_service import log_operation
from yuxi.services.user_identity_service import is_valid_phone_number
# 创建路由器
department = APIRouter(prefix="/departments", tags=["department"])
# =============================================================================
# === 请求和响应模型 ===
# =============================================================================
class DepartmentCreate(BaseModel):
"""创建部门请求"""
name: str
description: str | None = None
# 必需的管理员信息
admin_uid: str
admin_password: str
admin_phone: str | None = None
class DepartmentUpdate(BaseModel):
"""更新部门请求"""
name: str | None = None
description: str | None = None
class DepartmentResponse(BaseModel):
"""部门响应"""
id: int
name: str
description: str | None = None
created_at: str
user_count: int = 0
# =============================================================================
# === 部门管理路由 ===
# =============================================================================
@department.get("", response_model=list[DepartmentResponse])
async def get_departments(current_user: User = Depends(get_admin_user), db: AsyncSession = Depends(get_db)):
"""获取所有部门列表(管理员可访问)"""
dept_repo = DepartmentRepository()
return await dept_repo.list_with_user_count()
@department.get("/{department_id}", response_model=DepartmentResponse)
async def get_department(
department_id: int, current_user: User = Depends(get_superadmin_user), db: AsyncSession = Depends(get_db)
):
"""获取指定部门详情"""
result = await db.execute(select(Department).filter(Department.id == department_id))
department = result.scalar_one_or_none()
if not department:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="部门不存在")
# 获取部门下用户数量
user_count_result = await db.execute(
select(func.count(User.id)).filter(User.department_id == department_id, User.is_deleted == 0)
)
user_count = user_count_result.scalar()
return {**department.to_dict(), "user_count": user_count}
@department.post("", response_model=DepartmentResponse, status_code=status.HTTP_201_CREATED)
async def create_department(
department_data: DepartmentCreate,
request: Request,
current_user: User = Depends(get_superadmin_user),
db: AsyncSession = Depends(get_db),
):
"""创建新部门,同时创建该部门的管理员"""
dept_repo = DepartmentRepository()
user_repo = UserRepository()
# 检查部门名称是否已存在
if await dept_repo.exists_by_name(department_data.name):
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="部门名称已存在")
# 验证管理员 uid 格式
admin_uid = department_data.admin_uid
if not re.match(r"^[a-zA-Z0-9_]+$", admin_uid):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="用户ID只能包含字母、数字和下划线",
)
if len(admin_uid) < 3 or len(admin_uid) > 20:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="用户ID长度必须在3-20个字符之间",
)
# 检查 uid 是否已存在
if await user_repo.exists_by_uid(admin_uid):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="用户ID已存在",
)
# 检查手机号是否已存在(如果提供了)
admin_phone = department_data.admin_phone
if admin_phone:
if not is_valid_phone_number(admin_phone):
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="手机号格式不正确")
if await user_repo.exists_by_phone(admin_phone):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="手机号已存在",
)
# 创建部门
new_department = await dept_repo.create(
{
"name": department_data.name,
"description": department_data.description,
}
)
# 创建管理员用户
hashed_password = AuthUtils.hash_password(department_data.admin_password)
await user_repo.create(
{
"username": admin_uid,
"uid": admin_uid,
"phone_number": admin_phone,
"password_hash": hashed_password,
"role": "admin",
"department_id": new_department.id,
}
)
# 记录操作
await log_operation(
db, current_user.id, "创建部门", f"创建部门: {department_data.name},并创建管理员: {admin_uid}", request
)
return {**new_department.to_dict(), "user_count": 1}
@department.put("/{department_id}", response_model=DepartmentResponse)
async def update_department(
department_id: int,
department_data: DepartmentUpdate,
request: Request,
current_user: User = Depends(get_superadmin_user),
db: AsyncSession = Depends(get_db),
):
"""更新部门信息"""
result = await db.execute(select(Department).filter(Department.id == department_id))
department = result.scalar_one_or_none()
if not department:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="部门不存在")
# 如果要修改名称,检查新名称是否已存在
if department_data.name and department_data.name != department.name:
result = await db.execute(select(Department).filter(Department.name == department_data.name))
existing = result.scalar_one_or_none()
if existing:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="部门名称已存在")
department.name = department_data.name
if department_data.description is not None:
department.description = department_data.description
await db.commit()
await db.refresh(department)
# 记录操作
await log_operation(db, current_user.id, "更新部门", f"更新部门: {department.name}", request)
# 获取部门下用户数量
user_count_result = await db.execute(
select(func.count(User.id)).filter(User.department_id == department_id, User.is_deleted == 0)
)
user_count = user_count_result.scalar()
return {**department.to_dict(), "user_count": user_count}
@department.delete("/{department_id}", status_code=status.HTTP_200_OK)
async def delete_department(
department_id: int,
request: Request,
current_user: User = Depends(get_superadmin_user),
db: AsyncSession = Depends(get_db),
):
"""删除部门"""
# 检查部门是否存在
result = await db.execute(select(Department).filter(Department.id == department_id))
department = result.scalar_one_or_none()
if not department:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="部门不存在")
if department.id == 1: # 默认部门的ID为1
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="默认部门不允许删除")
department_name = department.name
result = await db.execute(select(User).filter(User.department_id == department_id))
department_users = result.scalars().all()
if department_users:
for user in department_users:
user.department_id = 1 # 将被删除部门的用户移至默认部门
await db.execute(sqlalchemy_delete(APIKey).where(APIKey.department_id == department_id))
await db.delete(department)
await db.commit()
# 记录操作
if department_users:
detail = f"删除部门: {department_name},迁移 {len(department_users)} 个用户到默认部门"
else:
detail = f"删除部门: {department_name}"
await log_operation(db, current_user.id, "删除部门", detail, request)
return {"success": True, "message": "部门已删除"}