| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443 |
- """
- 用户管理接口
- """
- 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"}
- )
|