""" 用户管理接口 """ from typing import List, Optional from fastapi import APIRouter, Depends, Query, Request from fastapi.responses import StreamingResponse from sqlalchemy.orm import Session from sqlalchemy import or_, and_ from datetime import datetime from app.utils.timezone import beijing_now from app.core.logger import logger import csv import io from app.database import get_db from app.api.deps import get_current_admin from app.models.user import User from app.models.admin import Admin from app.models.card import Card from app.models.software import Software from app.models.user_steam_account_limit import UserSteamAccountLimit router = APIRouter() DEFAULT_MAX_STEAM_ACCOUNTS = 3 @router.get("") async def get_users( page: int = Query(1, ge=1), page_size: int = Query(10, ge=1, le=100), keyword: str = Query(None, description="搜索关键词(用户名、邮箱、机器码)"), status: int = Query(None, description="状态筛选:1-正常,0-禁用"), expire_status: str = Query(None, description="有效期状态:valid-有效,expired-已过期,none-无有效期"), machine_code: str = Query(None, description="机器码筛选"), software_ids: Optional[List[int]] = Query(None, description="软件ID多选筛选,仅展示当前卡密属于所选软件的用户"), db: Session = Depends(get_db), current_admin: Admin = Depends(get_current_admin) ): """获取用户列表,含当前卡密对应软件名称;支持按软件ID多选筛选""" offset = (page - 1) * page_size # 左连 Card、Software 以带出软件名称;有 software_ids 时改为内连并过滤 if software_ids: query = ( db.query(User, Software.name.label("software_name")) .select_from(User) .join(Card, User.card_id == Card.id) .join(Software, Card.software_code == Software.code) .filter(User.is_deleted == 0, Software.id.in_(software_ids)) ) else: query = ( db.query(User, Software.name.label("software_name")) .select_from(User) .outerjoin(Card, User.card_id == Card.id) .outerjoin(Software, Card.software_code == Software.code) .filter(User.is_deleted == 0) ) # 搜索 if keyword: query = query.filter( or_( User.username.like(f"%{keyword}%"), User.email.like(f"%{keyword}%"), User.machine_code.like(f"%{keyword}%") ) ) # 筛选 if status is not None: query = query.filter(User.status == status) if machine_code: query = query.filter(User.machine_code == machine_code) # 有效期状态筛选 now = beijing_now() if expire_status == "valid": query = query.filter(User.expire_time > now) elif expire_status == "expired": query = query.filter(User.expire_time <= now) elif expire_status == "none": query = query.filter(User.expire_time.is_(None)) # 排序 query = query.order_by(User.created_at.desc()) total = query.count() rows = query.offset(offset).limit(page_size).all() return { "code": 200, "msg": "成功", "data": [ { "id": u.id, "username": u.username, "email": u.email, "machine_code": u.machine_code, "software_name": software_name, "expire_time": u.expire_time.isoformat() if u.expire_time else None, "last_login_time": u.last_login_time.isoformat() if u.last_login_time else None, "last_login_ip": u.last_login_ip, "status": u.status, "created_at": u.created_at.isoformat() if u.created_at else None } for u, software_name in rows ], "total": total, "page": page, "page_size": page_size } @router.get("/{user_id}/steam-account-limit") async def get_user_steam_account_limit( user_id: int, db: Session = Depends(get_db), current_admin: Admin = Depends(get_current_admin) ): """获取某用户允许绑定的最大 Steam 账号数(无配置时默认 3)""" user = db.query(User).filter(User.id == user_id, User.is_deleted == 0).first() if not user: return {"code": 404, "msg": "用户不存在"} row = db.query(UserSteamAccountLimit).filter(UserSteamAccountLimit.user_id == user_id).first() max_steam_accounts = row.max_steam_accounts if row else DEFAULT_MAX_STEAM_ACCOUNTS return {"code": 200, "msg": "成功", "data": {"user_id": user_id, "max_steam_accounts": max_steam_accounts}} @router.put("/{user_id}/steam-account-limit") async def update_user_steam_account_limit( user_id: int, request: Request, max_steam_accounts: int = Query(..., ge=1, le=100, description="该用户最多可绑定的 Steam 账号数"), db: Session = Depends(get_db), current_admin: Admin = Depends(get_current_admin) ): """设置某用户允许绑定的最大 Steam 账号数""" from app.utils.history import create_operation_log user = db.query(User).filter(User.id == user_id, User.is_deleted == 0).first() if not user: return {"code": 404, "msg": "用户不存在"} try: row = db.query(UserSteamAccountLimit).filter(UserSteamAccountLimit.user_id == user_id).first() if row: old_val = row.max_steam_accounts row.max_steam_accounts = max_steam_accounts row.updated_at = beijing_now() else: row = UserSteamAccountLimit(user_id=user_id, max_steam_accounts=max_steam_accounts) db.add(row) old_val = None client_ip = request.client.host if request and request.client else None create_operation_log( db=db, admin_id=current_admin.id, module="用户管理", action="设置用户Steam账号上限", description=f"用户 {user.username}(id={user_id}) 最大Steam账号数: {old_val} -> {max_steam_accounts}", old_data={"max_steam_accounts": old_val}, new_data={"max_steam_accounts": max_steam_accounts}, ip_address=client_ip ) db.commit() return {"code": 200, "msg": "设置成功", "data": {"user_id": user_id, "max_steam_accounts": max_steam_accounts}} except Exception as e: try: db.rollback() except Exception: pass logger.exception("设置用户Steam账号上限失败") return {"code": 500, "msg": "系统异常,请稍后重试"} @router.get("/{user_id}") async def get_user( user_id: int, db: Session = Depends(get_db), current_admin: Admin = Depends(get_current_admin) ): """获取用户详情""" from app.models.card import Card from app.models.recharge_record import RechargeRecord user = db.query(User).filter(User.id == user_id).first() if not user: return {"code": 404, "msg": "用户不存在"} # 获取当前卡密信息 card_info = None if user.card_id: card = db.query(Card).filter(Card.id == user.card_id).first() if card: card_info = { "id": card.id, "card_code": card.card_code, "software_code": card.software_code, "days": card.days, "activated_at": card.activated_at.isoformat() if card.activated_at else None, "expire_time": card.expire_time.isoformat() if card.expire_time else None, } # 获取充值记录 recharge_records = db.query(RechargeRecord).filter( RechargeRecord.user_id == user_id ).order_by(RechargeRecord.created_at.desc()).limit(10).all() # 该用户允许绑定的最大 Steam 账号数(无配置时默认 3) limit_row = db.query(UserSteamAccountLimit).filter(UserSteamAccountLimit.user_id == user_id).first() max_steam_accounts = limit_row.max_steam_accounts if limit_row else DEFAULT_MAX_STEAM_ACCOUNTS return { "code": 200, "msg": "成功", "data": { "id": user.id, "username": user.username, "email": user.email, "machine_code": user.machine_code, "card_id": user.card_id, "card_info": card_info, "max_steam_accounts": max_steam_accounts, "expire_time": user.expire_time.isoformat() if user.expire_time else None, "last_login_time": user.last_login_time.isoformat() if user.last_login_time else None, "last_login_ip": user.last_login_ip, "status": user.status, "created_at": user.created_at.isoformat() if user.created_at else None, "updated_at": user.updated_at.isoformat() if user.updated_at else None, "recharge_records": [ { "id": r.id, "card_code": r.card.card_code if r.card else None, "software_code": r.card.software_code if r.card else None, "days": r.card.days if r.card else None, "activated_at": r.created_at.isoformat() if r.created_at else None, } for r in recharge_records ] } } @router.put("/{user_id}/status") async def update_user_status( user_id: int, status: int = Query(..., description="状态:1-正常,0-禁用"), request: Request = None, db: Session = Depends(get_db), current_admin: Admin = Depends(get_current_admin) ): """启用/禁用用户""" try: user = db.query(User).filter(User.id == user_id).first() if not user: return {"code": 404, "msg": "用户不存在"} old_status = user.status user.status = status user.updated_at = beijing_now() # 创建历史记录 from app.utils.history import create_user_history, create_operation_log create_user_history(db, user, "UPDATE") # 记录操作日志 client_ip = request.client.host if request and request.client else None status_text = "启用" if status == 1 else "禁用" create_operation_log( db=db, admin_id=current_admin.id, module="用户管理", action="更新用户状态", description=f"{status_text}用户账号:{user.username}", old_data={"status": old_status}, new_data={"status": status}, ip_address=client_ip ) # 一次性提交所有更改 db.commit() return {"code": 200, "msg": "状态更新成功"} except Exception as e: try: db.rollback() except Exception: pass logger.exception("更新用户状态失败") return {"code": 500, "msg": "系统异常,操作未完成,请稍后重试;若持续发生请联系客服"} @router.delete("/{user_id}") async def delete_user( user_id: int, request: Request, db: Session = Depends(get_db), current_admin: Admin = Depends(get_current_admin) ): """删除用户(软删除)""" try: user = db.query(User).filter(User.id == user_id, User.is_deleted == 0).first() if not user: return {"code": 404, "msg": "用户不存在"} user.is_deleted = 1 user.updated_at = beijing_now() # 创建历史记录 from app.utils.history import create_user_history, create_operation_log create_user_history(db, user, "DELETE") # 记录操作日志 client_ip = request.client.host if request.client else None create_operation_log( db=db, admin_id=current_admin.id, module="用户管理", action="删除用户", description=f"删除用户账号:{user.username}", old_data={ "username": user.username, "email": user.email, "machine_code": user.machine_code, "status": user.status }, ip_address=client_ip ) # 一次性提交所有更改 db.commit() return {"code": 200, "msg": "删除成功"} except Exception as e: try: db.rollback() except Exception: pass logger.exception("删除用户失败") return {"code": 500, "msg": "系统异常,操作未完成,请稍后重试;若持续发生请联系客服"} @router.get("/export/excel") async def export_users_excel( request: Request, keyword: str = Query(None, description="搜索关键词"), status: int = Query(None, description="状态筛选"), expire_status: str = Query(None, description="有效期状态"), machine_code: str = Query(None, description="机器码筛选"), software_ids: Optional[List[int]] = Query(None, description="软件ID多选筛选"), db: Session = Depends(get_db), current_admin: Admin = Depends(get_current_admin) ): """导出用户为Excel(含软件名称列,支持按软件多选筛选)""" if software_ids: query = ( db.query(User, Software.name.label("software_name")) .select_from(User) .join(Card, User.card_id == Card.id) .join(Software, Card.software_code == Software.code) .filter(User.is_deleted == 0, Software.id.in_(software_ids)) ) else: query = ( db.query(User, Software.name.label("software_name")) .select_from(User) .outerjoin(Card, User.card_id == Card.id) .outerjoin(Software, Card.software_code == Software.code) .filter(User.is_deleted == 0) ) if keyword: query = query.filter( or_( User.username.like(f"%{keyword}%"), User.email.like(f"%{keyword}%"), User.machine_code.like(f"%{keyword}%") ) ) if status is not None: query = query.filter(User.status == status) if machine_code: query = query.filter(User.machine_code == machine_code) now = beijing_now() if expire_status == "valid": query = query.filter(User.expire_time > now) elif expire_status == "expired": query = query.filter(User.expire_time <= now) elif expire_status == "none": query = query.filter(User.expire_time.is_(None)) rows = query.order_by(User.created_at.desc()).all() from app.utils.history import create_operation_log client_ip = request.client.host if request.client else None create_operation_log( db=db, admin_id=current_admin.id, module="用户管理", action="导出用户Excel", description=f"导出用户数据为Excel,共导出{len(rows)}条用户记录", new_data={ "export_count": len(rows), "export_format": "Excel", "filters": { "keyword": keyword, "status": status, "expire_status": expire_status, "machine_code": machine_code, "software_ids": software_ids } }, ip_address=client_ip ) db.commit() output = io.StringIO() writer = csv.writer(output) writer.writerow([ 'ID', '用户名', '邮箱', '机器码', '软件', '到期时间', '最后登录时间', '最后登录IP', '状态', '注册时间', '更新时间' ]) for user, software_name in rows: writer.writerow([ user.id, user.username, user.email or '', user.machine_code or '', software_name or '', user.expire_time.isoformat() if user.expire_time else '', user.last_login_time.isoformat() if user.last_login_time else '', user.last_login_ip or '', '正常' if user.status == 1 else '禁用', user.created_at.isoformat() if user.created_at else '', user.updated_at.isoformat() if user.updated_at else '' ]) output.seek(0) return StreamingResponse( iter([output.getvalue()]), media_type="text/csv", headers={"Content-Disposition": f"attachment; filename=users_{beijing_now().strftime('%Y%m%d_%H%M%S')}.csv"} )