根因:PUT /admin/users/register-settings 参数定义为 phone_required: bool = None 等独立query参数,
但前端发送的是 JSON body {"phone_required":true,"email_required":true}。
FastAPI无法把JSON body映射到独立参数 → 全部收到None → 循环跳过 → DB无更新(提示成功但无效)。
修复:参数改为 body: dict 统一接收JSON,用 body.get() 取值更新 RegisterSetting 表。
326 lines
11 KiB
Python
326 lines
11 KiB
Python
"""
|
||
用户管理 API(管理员)
|
||
"""
|
||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||
from sqlalchemy.orm import Session
|
||
from sqlalchemy import or_
|
||
from pydantic import BaseModel, Field
|
||
from typing import Optional
|
||
from ...database import get_db, SessionLocal
|
||
from ...models import User, RegisterSetting
|
||
from ...auth import get_current_admin, hash_password, verify_password
|
||
from ...middleware.log_service import LogService
|
||
from datetime import datetime
|
||
|
||
|
||
class AdminChangePasswordRequest(BaseModel):
|
||
old_password: str
|
||
new_password: str = Field(min_length=6, max_length=100)
|
||
|
||
|
||
class ResetUserPasswordRequest(BaseModel):
|
||
new_password: str = Field(min_length=6, max_length=100)
|
||
import uuid
|
||
from datetime import datetime
|
||
|
||
router = APIRouter()
|
||
|
||
|
||
@router.get("/")
|
||
async def get_users(
|
||
status: str = Query(None, description="用户状态筛选: active/banned"),
|
||
membership_level: int = Query(None, description="会员等级筛选"),
|
||
search: str = Query(None, description="搜索用户名或邮箱"),
|
||
page: int = Query(1, ge=1),
|
||
page_size: int = Query(20, ge=1, le=100),
|
||
admin_user=Depends(get_current_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
"""获取用户列表(管理员)"""
|
||
query = db.query(User)
|
||
|
||
if status:
|
||
query = query.filter(User.status == status)
|
||
if membership_level is not None:
|
||
query = query.filter(User.membership_level == membership_level)
|
||
if search:
|
||
query = query.filter(
|
||
or_(
|
||
User.username.ilike(f"%{search}%"),
|
||
User.email.ilike(f"%{search}%"),
|
||
)
|
||
)
|
||
|
||
total = query.count()
|
||
users = query.order_by(User.created_at.desc()).offset(
|
||
(page - 1) * page_size
|
||
).limit(page_size).all()
|
||
|
||
return {
|
||
"total": total,
|
||
"page": page,
|
||
"page_size": page_size,
|
||
"items": [
|
||
{
|
||
"id": u.id,
|
||
"username": u.username,
|
||
"email": u.email,
|
||
"phone": u.phone,
|
||
"role": u.role,
|
||
"status": u.status,
|
||
"real_name_status": u.real_name_status,
|
||
"real_name": u.real_name,
|
||
"membership_level": u.membership_level,
|
||
"membership_expire_at": u.membership_expire_at.isoformat() if u.membership_expire_at else None,
|
||
"auto_renew": u.auto_renew,
|
||
"created_at": u.created_at.isoformat() if u.created_at else None,
|
||
"updated_at": u.updated_at.isoformat() if u.updated_at else None,
|
||
}
|
||
for u in users
|
||
],
|
||
}
|
||
|
||
|
||
# ============================================================
|
||
# 静态路由 - 必须放在 /{id} 动态路由之前
|
||
# ============================================================
|
||
|
||
@router.get("/register-settings")
|
||
async def get_register_settings(
|
||
admin_user=Depends(get_current_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
"""获取注册设置"""
|
||
setting = db.query(RegisterSetting).first()
|
||
if not setting:
|
||
return {
|
||
"phone_required": True,
|
||
"phone_sms_required": True,
|
||
"email_required": True,
|
||
"email_verify_required": True,
|
||
"real_name_required": False,
|
||
"aliyun_access_key": "",
|
||
"aliyun_endpoint": "https://dysmsapi.aliyuncs.com",
|
||
"sms_sign_name": "",
|
||
"sms_template_code": "",
|
||
}
|
||
|
||
return {
|
||
"id": setting.id,
|
||
"phone_required": setting.phone_required,
|
||
"phone_sms_required": setting.phone_sms_required,
|
||
"email_required": setting.email_required,
|
||
"email_verify_required": setting.email_verify_required,
|
||
"real_name_required": setting.real_name_required,
|
||
"aliyun_access_key": setting.aliyun_access_key,
|
||
"aliyun_endpoint": setting.aliyun_endpoint,
|
||
"sms_sign_name": getattr(setting, 'sms_sign_name', ''),
|
||
"sms_template_code": getattr(setting, 'sms_template_code', ''),
|
||
}
|
||
|
||
|
||
@router.put("/register-settings")
|
||
async def update_register_settings(
|
||
body: dict,
|
||
admin_user=Depends(get_current_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
"""更新注册设置(接收 JSON Body)"""
|
||
setting = db.query(RegisterSetting).first()
|
||
if not setting:
|
||
setting = RegisterSetting(
|
||
id=str(uuid.uuid4()),
|
||
phone_required=body.get("phone_required", True),
|
||
phone_sms_required=body.get("phone_sms_required", True),
|
||
email_required=body.get("email_required", True),
|
||
email_verify_required=body.get("email_verify_required", True),
|
||
real_name_required=body.get("real_name_required", False),
|
||
aliyun_access_key=body.get("aliyun_access_key"),
|
||
aliyun_secret_key=body.get("aliyun_secret_key"),
|
||
aliyun_endpoint=body.get("aliyun_endpoint"),
|
||
sms_sign_name=body.get("sms_sign_name"),
|
||
sms_template_code=body.get("sms_template_code"),
|
||
)
|
||
db.add(setting)
|
||
else:
|
||
if "phone_required" in body:
|
||
setting.phone_required = body["phone_required"]
|
||
if "phone_sms_required" in body:
|
||
setting.phone_sms_required = body["phone_sms_required"]
|
||
if "email_required" in body:
|
||
setting.email_required = body["email_required"]
|
||
if "email_verify_required" in body:
|
||
setting.email_verify_required = body["email_verify_required"]
|
||
if "real_name_required" in body:
|
||
setting.real_name_required = body["real_name_required"]
|
||
if "aliyun_access_key" in body:
|
||
setting.aliyun_access_key = body["aliyun_access_key"]
|
||
if "aliyun_secret_key" in body:
|
||
setting.aliyun_secret_key = body["aliyun_secret_key"]
|
||
if "aliyun_endpoint" in body:
|
||
setting.aliyun_endpoint = body["aliyun_endpoint"]
|
||
if "sms_sign_name" in body:
|
||
setting.sms_sign_name = body["sms_sign_name"]
|
||
if "sms_template_code" in body:
|
||
setting.sms_template_code = body["sms_template_code"]
|
||
setting.updated_at = datetime.now()
|
||
|
||
db.commit()
|
||
|
||
LogService.log_audit(
|
||
admin_id=admin_user.id,
|
||
admin_name=admin_user.username,
|
||
action="update_register_settings",
|
||
target_type="register_setting",
|
||
detail="更新注册设置",
|
||
)
|
||
|
||
return {"message": "注册设置已更新"}
|
||
|
||
|
||
# ============================================================
|
||
# 动态路由
|
||
# ============================================================
|
||
|
||
@router.get("/{id}")
|
||
async def get_user_detail(
|
||
id: str,
|
||
admin_user=Depends(get_current_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
"""获取用户详情"""
|
||
user = db.query(User).filter(User.id == id).first()
|
||
if not user:
|
||
raise HTTPException(status_code=404, detail="用户不存在")
|
||
|
||
return {
|
||
"id": user.id,
|
||
"username": user.username,
|
||
"email": user.email,
|
||
"phone": user.phone,
|
||
"avatar": user.avatar,
|
||
"role": user.role,
|
||
"status": user.status,
|
||
"real_name_status": user.real_name_status,
|
||
"real_name": user.real_name,
|
||
"id_card": user.id_card,
|
||
"membership_plan_id": user.membership_plan_id,
|
||
"membership_level": user.membership_level,
|
||
"membership_expire_at": user.membership_expire_at.isoformat() if user.membership_expire_at else None,
|
||
"auto_renew": user.auto_renew,
|
||
"created_at": user.created_at.isoformat() if user.created_at else None,
|
||
"updated_at": user.updated_at.isoformat() if user.updated_at else None,
|
||
}
|
||
|
||
|
||
@router.put("/{id}/ban")
|
||
async def ban_user(
|
||
id: str,
|
||
admin_user=Depends(get_current_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
"""封禁用户"""
|
||
user = db.query(User).filter(User.id == id).first()
|
||
if not user:
|
||
raise HTTPException(status_code=404, detail="用户不存在")
|
||
if user.role == "admin":
|
||
raise HTTPException(status_code=400, detail="不能封禁管理员账号")
|
||
|
||
user.status = "banned"
|
||
user.updated_at = datetime.now()
|
||
db.commit()
|
||
|
||
LogService.log_audit(
|
||
admin_id=admin_user.id,
|
||
admin_name=admin_user.username,
|
||
action="ban_user",
|
||
target_type="user",
|
||
target_id=id,
|
||
detail=f"封禁用户 {user.username}",
|
||
)
|
||
|
||
return {"message": "用户已封禁"}
|
||
|
||
|
||
@router.put("/{id}/unban")
|
||
async def unban_user(
|
||
id: str,
|
||
admin_user=Depends(get_current_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
"""解封用户"""
|
||
user = db.query(User).filter(User.id == id).first()
|
||
if not user:
|
||
raise HTTPException(status_code=404, detail="用户不存在")
|
||
|
||
user.status = "active"
|
||
user.updated_at = datetime.now()
|
||
db.commit()
|
||
|
||
LogService.log_audit(
|
||
admin_id=admin_user.id,
|
||
admin_name=admin_user.username,
|
||
action="unban_user",
|
||
target_type="user",
|
||
target_id=id,
|
||
detail=f"解封用户 {user.username}",
|
||
)
|
||
|
||
return {"message": "用户已解封"}
|
||
|
||
|
||
# ============================================================
|
||
# 密码管理
|
||
# ============================================================
|
||
|
||
@router.put("/change-password")
|
||
def admin_change_password(
|
||
req: AdminChangePasswordRequest,
|
||
admin_user=Depends(get_current_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
"""管理员修改自己的密码"""
|
||
db_user = db.query(User).filter(User.id == admin_user.id).first()
|
||
if not db_user:
|
||
raise HTTPException(status_code=404, detail="用户不存在")
|
||
|
||
if not verify_password(req.old_password, db_user.password_hash):
|
||
raise HTTPException(status_code=400, detail="原密码错误")
|
||
|
||
db_user.password_hash = hash_password(req.new_password)
|
||
db_user.updated_at = datetime.now()
|
||
|
||
LogService.log_operation(
|
||
user_id=admin_user.id, username=admin_user.username, action="admin_change_password",
|
||
target="user", target_id=admin_user.id,
|
||
detail="管理员修改了自己的密码",
|
||
)
|
||
db.commit()
|
||
|
||
return {"code": 0, "message": "密码修改成功"}
|
||
|
||
|
||
@router.put("/{id}/reset-password")
|
||
def admin_reset_user_password(
|
||
id: str,
|
||
req: ResetUserPasswordRequest,
|
||
admin_user=Depends(get_current_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
"""管理员重置用户密码"""
|
||
user = db.query(User).filter(User.id == id).first()
|
||
if not user:
|
||
raise HTTPException(status_code=404, detail="用户不存在")
|
||
|
||
user.password_hash = hash_password(req.new_password)
|
||
user.updated_at = datetime.now()
|
||
|
||
LogService.log_operation(
|
||
user_id=admin_user.id, username=admin_user.username, action="reset_user_password",
|
||
target="user", target_id=id,
|
||
detail=f"管理员重置了用户 {user.username} 的密码",
|
||
)
|
||
db.commit()
|
||
|
||
return {"code": 0, "message": "密码已重置"}
|