315 lines
11 KiB
Python
315 lines
11 KiB
Python
# users.py - 用户管理路由
|
||
# Version: 0.0.1 (2026-04-19)
|
||
# 更新:新增 dealCount 字段,从 Information 表统计用户发布的行情数量
|
||
|
||
from typing import Optional
|
||
from fastapi import APIRouter, Depends, HTTPException, status, Query, Body
|
||
from sqlalchemy.orm import Session
|
||
from app.core.database import get_db
|
||
from app.core.auth import get_current_user
|
||
from app.models.models import User, Collection, Information
|
||
from app.schemas.schemas import UserResponse, UserUpdate
|
||
|
||
router = APIRouter(prefix="/api", tags=["用户"])
|
||
|
||
# ============ 当前用户接口 ============
|
||
|
||
@router.get("/users/me") # 无 response_model,避免 Pydantic 序列化问题
|
||
def get_current_user_info(
|
||
current_user: User = Depends(get_current_user)
|
||
):
|
||
"""获取当前登录用户信息"""
|
||
return {
|
||
"id": current_user.f99_90_id,
|
||
"username": current_user.f01_01_name,
|
||
"f99_90_id": current_user.f99_90_id,
|
||
"f01_01_name": current_user.f01_01_name,
|
||
"email": current_user.email,
|
||
"phone": current_user.phone,
|
||
"avatar": current_user.avatar,
|
||
"address": current_user.address,
|
||
"bio": current_user.bio,
|
||
"role": current_user.role,
|
||
"level": current_user.f99_94_level,
|
||
"aiCount": current_user.f99_95_ai_count or 0,
|
||
"searchCount": current_user.f99_96_search_count or 0,
|
||
"collectionCount": current_user.f99_97_collection_count or 0,
|
||
"phoneVerified": current_user.f01_06_phone_verified or False,
|
||
"loginCount": current_user.f99_98_login_count or 0,
|
||
"lastLogin": current_user.f99_99_last_login.isoformat() if current_user.f99_99_last_login else None,
|
||
"gender": current_user.f01_07_gender,
|
||
"birthday": current_user.f01_08_birthday.isoformat() if current_user.f01_08_birthday else None,
|
||
"region": current_user.f01_09_region,
|
||
"realnameVerified": current_user.f01_10_realname_verified or False,
|
||
"points": current_user.f99_100_points or 0,
|
||
"balance": float(current_user.f01_11_balance) if current_user.f01_11_balance else 0,
|
||
"totalAmount": float(current_user.f01_12_total_amount) if current_user.f01_12_total_amount else 0,
|
||
"inviteCode": current_user.f01_13_invite_code,
|
||
"user_code": current_user.user_code,
|
||
"f99_92_created_at": current_user.f99_92_created_at.isoformat() if current_user.f99_92_created_at else None,
|
||
"f99_93_updated_at": current_user.f99_93_updated_at.isoformat() if current_user.f99_93_updated_at else None
|
||
}
|
||
|
||
@router.put("/users/me") # 无 response_model,避免 Pydantic 序列化问题
|
||
def update_current_user(
|
||
user_update: UserUpdate,
|
||
current_user: User = Depends(get_current_user),
|
||
db: Session = Depends(get_db)
|
||
):
|
||
"""更新当前用户信息"""
|
||
import logging
|
||
logger = logging.getLogger(__name__)
|
||
|
||
# 获取用户ID
|
||
user_id = current_user.f99_90_id
|
||
logger.info(f"Updating user {user_id}, data={user_update.model_dump()}")
|
||
|
||
# 在当前session中重新查询用户
|
||
user = db.query(User).filter(User.f99_90_id == user_id).first()
|
||
if not user:
|
||
raise HTTPException(status_code=404, detail="用户不存在")
|
||
|
||
# 更新字段
|
||
update_data = user_update.model_dump(exclude_unset=True)
|
||
for field, value in update_data.items():
|
||
if field == 'f01_01_name':
|
||
user.f01_01_name = value
|
||
elif field == 'username':
|
||
pass # skip, already handled as f01_01_name
|
||
elif hasattr(user, field):
|
||
setattr(user, field, value)
|
||
|
||
# 强制刷新以确保更新被提交
|
||
db.flush()
|
||
db.commit()
|
||
db.refresh(user)
|
||
logger.info(f"After commit, user email={user.email}")
|
||
|
||
return user
|
||
|
||
# ============ 管理员用户管理 ============
|
||
|
||
admin_router = APIRouter(prefix="/api/admin/users", tags=["用户管理"])
|
||
|
||
|
||
@admin_router.get("")
|
||
def get_users(
|
||
page: int = Query(1, ge=1),
|
||
limit: int = Query(20, ge=1, le=100),
|
||
current_user: User = Depends(get_current_user),
|
||
db: Session = Depends(get_db)
|
||
):
|
||
"""获取用户列表(仅管理员)"""
|
||
if current_user.role not in ["admin", "editor"]:
|
||
raise HTTPException(status_code=403, detail="无权访问")
|
||
|
||
total = db.query(User).count()
|
||
from sqlalchemy import case
|
||
users = db.query(User).order_by(
|
||
case(
|
||
(User.user_code == None, 1),
|
||
else_=0
|
||
),
|
||
User.user_code.asc()
|
||
).offset((page-1)*limit).limit(limit).all()
|
||
|
||
user_list = []
|
||
for u in users:
|
||
# 统计每个用户的藏品数量
|
||
count = db.query(Collection).filter(Collection.f99_91_user_id == u.f99_90_id).count()
|
||
# 统计每个用户发布的行情数量(info_type='deal')
|
||
deal_count = db.query(Information).filter(
|
||
Information.user_id == u.f99_90_id,
|
||
Information.info_type == "deal"
|
||
).count()
|
||
user_list.append({
|
||
"id": u.f99_90_id,
|
||
"username": u.f01_01_name,
|
||
"email": u.email,
|
||
"phone": u.phone,
|
||
"role": u.role,
|
||
"user_code": u.user_code,
|
||
"created_at": u.f99_92_created_at.isoformat() if u.f99_92_created_at else None,
|
||
"collectionCount": count,
|
||
"dealCount": deal_count,
|
||
"level": u.f99_94_level,
|
||
"aiCount": u.f99_95_ai_count,
|
||
"searchCount": u.f99_96_search_count,
|
||
"loginCount": u.f99_98_login_count,
|
||
"points": u.f99_100_points,
|
||
"balance": float(u.f01_11_balance) if u.f01_11_balance else 0,
|
||
"totalAmount": float(u.f01_12_total_amount) if u.f01_12_total_amount else 0,
|
||
"phoneVerified": u.f01_06_phone_verified,
|
||
})
|
||
|
||
return user_list
|
||
|
||
|
||
@admin_router.get("/{user_id}")
|
||
def get_user(
|
||
user_id: str,
|
||
current_user: User = Depends(get_current_user),
|
||
db: Session = Depends(get_db)
|
||
):
|
||
"""获取单个用户信息"""
|
||
if current_user.role not in ["admin", "editor"]:
|
||
raise HTTPException(status_code=403, detail="无权访问")
|
||
|
||
user = db.query(User).filter(User.id == user_id).first()
|
||
if not user:
|
||
raise HTTPException(status_code=404, detail="用户不存在")
|
||
|
||
return {
|
||
"id": user.id,
|
||
"username": user.username,
|
||
"email": user.email,
|
||
"phone": user.phone,
|
||
"role": user.role,
|
||
"created_at": user.created_at.isoformat() if user.created_at else None
|
||
}
|
||
|
||
|
||
@admin_router.get("/{user_id}/collections")
|
||
def get_user_collections(
|
||
user_id: str,
|
||
limit: int = Query(100, ge=1, le=100),
|
||
current_user: User = Depends(get_current_user),
|
||
db: Session = Depends(get_db)
|
||
):
|
||
"""获取指定用户的藏品列表"""
|
||
if current_user.role not in ["admin", "editor"]:
|
||
raise HTTPException(status_code=403, detail="无权访问")
|
||
|
||
collections = db.query(Collection).filter(
|
||
Collection.user_id == user_id
|
||
).limit(limit).all()
|
||
|
||
return [c.code for c in collections]
|
||
|
||
|
||
@admin_router.get("/{user_id}/count")
|
||
def get_user_collection_count(
|
||
user_id: str,
|
||
current_user: User = Depends(get_current_user),
|
||
db: Session = Depends(get_db)
|
||
):
|
||
"""获取指定用户的藏品数量"""
|
||
if current_user.role not in ["admin", "editor"]:
|
||
raise HTTPException(status_code=403, detail="E00050: 仅管理员可访问")
|
||
|
||
count = db.query(Collection).filter(Collection.user_id == user_id).count()
|
||
return {"count": count}
|
||
|
||
|
||
@admin_router.put("/{user_id}")
|
||
def update_user(
|
||
user_id: str,
|
||
username: Optional[str] = Body(None),
|
||
email: Optional[str] = Body(None),
|
||
role: Optional[str] = Body(None),
|
||
password: Optional[str] = Body(None),
|
||
user_code: Optional[str] = Body(None),
|
||
level: Optional[str] = Body(None),
|
||
points: Optional[int] = Body(None),
|
||
balance: Optional[float] = Body(None),
|
||
totalAmount: Optional[float] = Body(None),
|
||
aiCount: Optional[int] = Body(None),
|
||
searchCount: Optional[int] = Body(None),
|
||
current_user: User = Depends(get_current_user),
|
||
db: Session = Depends(get_db)
|
||
):
|
||
"""更新用户信息(仅管理员)"""
|
||
if current_user.role not in ["admin", "editor"]:
|
||
raise HTTPException(status_code=403, detail="E00050: 仅管理员可访问")
|
||
|
||
user = db.query(User).filter(User.f99_90_id == user_id).first()
|
||
if not user:
|
||
raise HTTPException(status_code=404, detail="E00051: 用户不存在")
|
||
|
||
# 更新基本信息
|
||
if username:
|
||
user.f01_01_name = username
|
||
if email:
|
||
user.email = email
|
||
if role is not None:
|
||
user.role = role
|
||
|
||
# 更新会员等级
|
||
if level is not None:
|
||
user.f99_94_level = level
|
||
|
||
# 更新积分
|
||
if points is not None:
|
||
user.f99_100_points = points
|
||
|
||
# 更新余额
|
||
if balance is not None:
|
||
user.f01_11_balance = balance
|
||
|
||
# 更新累计金额
|
||
if totalAmount is not None:
|
||
user.f01_12_total_amount = totalAmount
|
||
|
||
# 更新AI识别次数
|
||
if aiCount is not None:
|
||
user.f99_95_ai_count = aiCount
|
||
|
||
# 更新寻号次数
|
||
if searchCount is not None:
|
||
user.f99_96_search_count = searchCount
|
||
|
||
# 更新用户编码
|
||
if user_code is not None:
|
||
# 只有非空字符串才检查唯一性
|
||
user_code_str = user_code.strip() if user_code else ''
|
||
if user_code_str:
|
||
existing = db.query(User).filter(
|
||
User.user_code == user_code_str,
|
||
User.f99_90_id != user_id
|
||
).first()
|
||
if existing:
|
||
raise HTTPException(status_code=400, detail="E00052: 该用户编码已被其他用户使用")
|
||
user.user_code = user_code_str
|
||
else:
|
||
user.user_code = None
|
||
|
||
# 更新密码
|
||
if password and password.strip():
|
||
from app.core.auth import get_password_hash
|
||
user.password = get_password_hash(password)
|
||
|
||
db.commit()
|
||
db.refresh(user)
|
||
|
||
return {
|
||
"id": user.f99_90_id,
|
||
"username": user.f01_01_name,
|
||
"email": user.email,
|
||
"role": user.role,
|
||
"message": "更新成功"
|
||
}
|
||
|
||
|
||
@admin_router.delete("/{user_id}")
|
||
def delete_user(
|
||
user_id: str,
|
||
current_user: User = Depends(get_current_user),
|
||
db: Session = Depends(get_db)
|
||
):
|
||
"""删除用户(仅管理员)"""
|
||
if current_user.role not in ["admin", "editor"]:
|
||
raise HTTPException(status_code=403, detail="E00050: 仅管理员可访问")
|
||
|
||
# 不能删除自己
|
||
if user_id == str(current_user.f99_90_id):
|
||
raise HTTPException(status_code=400, detail="E00052: 不能删除自己")
|
||
|
||
user = db.query(User).filter(User.f99_90_id == user_id).first()
|
||
if not user:
|
||
raise HTTPException(status_code=404, detail="E00051: 用户不存在")
|
||
|
||
db.delete(user)
|
||
db.commit()
|
||
|
||
return {"message": "删除成功"}
|