""" 用户管理接口 """ 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 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 router = APIRouter() @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="机器码筛选"), db: Session = Depends(get_db), current_admin: Admin = Depends(get_current_admin) ): """获取用户列表""" offset = (page - 1) * page_size query = db.query(User).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()) users = query.offset(offset).limit(page_size).all() total = query.count() return { "code": 200, "msg": "成功", "data": [ { "id": u.id, "username": u.username, "email": u.email, "machine_code": u.machine_code, "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 in users ], "total": total, "page": page, "page_size": page_size } @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() 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, "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="机器码筛选"), db: Session = Depends(get_db), current_admin: Admin = Depends(get_current_admin) ): """导出用户为Excel""" # 应用相同的筛选条件构建查询 query = db.query(User).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)) users = 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(users)}条用户记录", new_data={ "export_count": len(users), "export_format": "Excel", "filters": { "keyword": keyword, "status": status, "expire_status": expire_status, "machine_code": machine_code } }, ip_address=client_ip ) db.commit() # 生成CSV格式(临时方案,后续可以升级为真正的Excel格式) output = io.StringIO() writer = csv.writer(output) writer.writerow([ 'ID', '用户名', '邮箱', '机器码', '到期时间', '最后登录时间', '最后登录IP', '状态', '注册时间', '更新时间' ]) for user in users: writer.writerow([ user.id, user.username, user.email or '', user.machine_code 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"} )