""" 卡密管理接口 """ from fastapi import APIRouter, Depends, Query, Request from fastapi.responses import StreamingResponse from sqlalchemy.orm import Session from pydantic import BaseModel import secrets import string import csv import io from app.database import get_db from app.api.deps import get_current_admin from app.models.card import Card from datetime import datetime from app.utils.timezone import beijing_now router = APIRouter() class CardGenerateRequest(BaseModel): """生成卡密请求""" software_code: str days: int discount: float count: int def generate_card_code() -> str: """生成卡密代码""" characters = string.ascii_uppercase + string.digits return ''.join(secrets.choice(characters) for _ in range(16)) @router.post("/generate") async def generate_cards( req: CardGenerateRequest, request: Request, db: Session = Depends(get_db), current_admin = Depends(get_current_admin) ): """生成卡密""" try: cards = [] for _ in range(req.count): card_code = generate_card_code() # 确保卡密唯一 while db.query(Card).filter(Card.card_code == card_code).first(): card_code = generate_card_code() card = Card( card_code=card_code, software_code=req.software_code, days=req.days, discount=req.discount, status=0, created_at=beijing_now(), updated_at=beijing_now() ) db.add(card) cards.append(card) # 刷新以获取ID db.flush() # 创建历史记录 from app.utils.history import create_card_history, create_operation_log for card in cards: create_card_history(db, card, "INSERT") # 记录操作日志 client_ip = request.client.host if request.client else None create_operation_log( db=db, admin_id=current_admin.id, module="卡密管理", action="生成卡密", description=f"批量生成卡密:{req.count}张,软件:{req.software_code},天数:{req.days}天", new_data={ "software_code": req.software_code, "days": req.days, "discount": req.discount, "count": req.count, "card_codes": [c.card_code for c in cards] }, ip_address=client_ip ) # 一次性提交所有更改 db.commit() return { "code": 200, "msg": "生成成功", "data": { "count": len(cards), "cards": [c.card_code for c in cards] } } except Exception as e: try: db.rollback() except Exception: # 回滚失败记录警告但继续处理 pass logger.exception("生成卡密失败") return {"code": 500, "msg": "系统异常,操作未完成,请稍后重试;若持续发生请联系客服"} @router.get("") async def get_cards( page: int = Query(1, ge=1), page_size: int = Query(10, ge=1, le=100), keyword: str = Query(None, description="搜索关键词(卡密代码)"), software_code: str = Query(None, description="软件特征码筛选"), status: int = Query(None, description="状态筛选:0-未激活,1-已激活,2-已过期"), db: Session = Depends(get_db), current_admin = Depends(get_current_admin) ): """获取卡密列表""" from sqlalchemy import or_ from app.models.software import Software from app.models.user import User offset = (page - 1) * page_size # 构建查询,关联软件表和用户表 query = db.query(Card, Software.name.label('software_name'), User.username.label('username'))\ .join(Software, Card.software_code == Software.code)\ .outerjoin(User, Card.user_id == User.id)\ .filter(Card.is_deleted == 0) # 搜索 if keyword: query = query.filter(Card.card_code.like(f"%{keyword}%")) # 筛选 if software_code: query = query.filter(Card.software_code == software_code) if status is not None: query = query.filter(Card.status == status) # 排序 query = query.order_by(Card.created_at.desc()) cards_with_names = query.offset(offset).limit(page_size).all() total = query.count() return { "code": 200, "msg": "成功", "data": [ { "id": c.id, "card_code": c.card_code, "software_code": c.software_code, "software_name": software_name, "days": c.days, "discount": float(c.discount), "status": c.status, "user_id": c.user_id, "username": username, "activated_at": c.activated_at.isoformat() if c.activated_at else None, "expire_time": c.expire_time.isoformat() if c.expire_time else None, "created_at": c.created_at.isoformat() if c.created_at else None, "updated_at": c.updated_at.isoformat() if c.updated_at else None } for c, software_name, username in cards_with_names ], "total": total, "page": page, "page_size": page_size } @router.get("/{card_id}") async def get_card_detail( card_id: int, db: Session = Depends(get_db), current_admin = Depends(get_current_admin) ): """获取卡密详情""" from app.models.user import User card = db.query(Card).filter(Card.id == card_id).first() if not card: return {"code": 404, "msg": "卡密不存在"} user_info = None if card.user_id: user = db.query(User).filter(User.id == card.user_id).first() if user: user_info = { "id": user.id, "username": user.username, "email": user.email, "machine_code": user.machine_code, } return { "code": 200, "msg": "成功", "data": { "id": card.id, "card_code": card.card_code, "software_code": card.software_code, "days": card.days, "discount": float(card.discount), "status": card.status, "user_id": card.user_id, "user_info": user_info, "activated_at": card.activated_at.isoformat() if card.activated_at else None, "expire_time": card.expire_time.isoformat() if card.expire_time else None, "created_at": card.created_at.isoformat() if card.created_at else None, "updated_at": card.updated_at.isoformat() if card.updated_at else None } } @router.get("/{card_id}/activate") async def get_card_activate_info( card_id: int, db: Session = Depends(get_db), current_admin = Depends(get_current_admin) ): """获取卡密激活信息""" card = db.query(Card).filter(Card.id == card_id).first() if not card: return {"code": 404, "msg": "卡密不存在"} return { "code": 200, "msg": "成功", "data": { "card_code": card.card_code, "user_id": card.user_id, "activated_at": card.activated_at.isoformat() if card.activated_at else None, "expire_time": card.expire_time.isoformat() if card.expire_time else None } } @router.get("/export/excel") async def export_cards_excel( request: Request, keyword: str = Query(None, description="搜索关键词"), software_code: str = Query(None, description="软件特征码筛选"), status: int = Query(None, description="状态筛选"), db: Session = Depends(get_db), current_admin = Depends(get_current_admin) ): """导出卡密为Excel""" from app.models.software import Software from app.models.user import User # 应用相同的筛选条件构建查询,关联软件表和用户表 query = db.query(Card, Software.name.label('software_name'), User.username.label('username'))\ .join(Software, Card.software_code == Software.code)\ .outerjoin(User, Card.user_id == User.id)\ .filter(Card.is_deleted == 0) # 搜索 if keyword: query = query.filter(Card.card_code.like(f"%{keyword}%")) # 筛选 if software_code: query = query.filter(Card.software_code == software_code) if status is not None: query = query.filter(Card.status == status) cards_with_names = query.order_by(Card.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(cards_with_names)}条卡密记录", new_data={ "export_count": len(cards_with_names), "export_format": "Excel", "filters": { "keyword": keyword, "software_code": software_code, "status": status } }, ip_address=client_ip ) db.commit() # 生成CSV格式(临时方案,后续可以升级为真正的Excel格式) output = io.StringIO() writer = csv.writer(output) writer.writerow([ 'ID', '卡密代码', '软件特征码', '软件名称', '天数', '折扣', '状态', '激活用户ID', '用户名称', '激活时间', '到期时间', '创建时间', '更新时间' ]) status_map = {0: '未激活', 1: '已激活', 2: '已过期'} for card, software_name, username in cards_with_names: writer.writerow([ card.id, card.card_code, card.software_code, software_name, card.days, float(card.discount), status_map.get(card.status, '未知'), card.user_id or '', username or '', card.activated_at.isoformat() if card.activated_at else '', card.expire_time.isoformat() if card.expire_time else '', card.created_at.isoformat() if card.created_at else '', card.updated_at.isoformat() if card.updated_at else '' ]) output.seek(0) return StreamingResponse( iter([output.getvalue()]), media_type="text/csv", headers={"Content-Disposition": f"attachment; filename=cards_{beijing_now().strftime('%Y%m%d_%H%M%S')}.csv"} )