| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859 |
- """
- 卡密管理接口
- """
- import logging
- from fastapi import APIRouter, Depends, Query, Request
- from fastapi.responses import StreamingResponse
- from sqlalchemy.orm import Session
- from typing import Optional
- from pydantic import BaseModel, Field
- 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
- logger = logging.getLogger(__name__)
- router = APIRouter()
- class CardGenerateRequest(BaseModel):
- """生成卡密请求"""
- software_code: str
- days: int
- discount: float
- count: int
- is_gift: int = Field(0, ge=0, le=1, description="0-收费卡密,1-赠送卡密")
- remark: Optional[str] = Field(None, max_length=500, description="备注,可选")
- class CardBatchExtractRequest(BaseModel):
- """批量提取卡密请求"""
- ids: list[int] = Field(..., min_length=1, description="卡密ID列表")
- class CardIssueRequest(BaseModel):
- """发放卡密请求"""
- issue_price: float = Field(..., ge=0, description="发放价格")
- issued_to: str = Field(..., min_length=1, max_length=200, description="发放给谁(文本)")
- issue_remark: Optional[str] = Field(None, max_length=500, description="发放备注,可选")
- class CardBatchIssueRequest(BaseModel):
- """批量发放卡密请求"""
- ids: list[int] = Field(..., min_length=1, description="卡密ID列表")
- issue_price: float = Field(..., ge=0, description="发放价格")
- issued_to: str = Field(..., min_length=1, max_length=200, description="发放给谁(文本)")
- issue_remark: Optional[str] = Field(None, max_length=500, description="发放备注,可选")
- class CardRevokeRequest(BaseModel):
- """注销卡密请求"""
- revoked_reason: Optional[str] = Field(None, max_length=500, description="注销原因,可选")
- class CardBatchRevokeRequest(BaseModel):
- """批量注销卡密请求"""
- ids: list[int] = Field(..., min_length=1, description="卡密ID列表")
- revoked_reason: Optional[str] = Field(None, max_length=500, description="注销原因,可选")
- class CardBatchSpecialCloseRequest(BaseModel):
- """批量设置特殊关闭请求"""
- ids: list[int] = Field(..., min_length=1, description="卡密ID列表")
- special_close: int = Field(..., ge=0, le=1, description="特殊关闭:0-关闭,1-打开")
- 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:
- remark_clean = (req.remark or "").strip() or None
- 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,
- is_gift=req.is_gift,
- remark=remark_clean,
- 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
- gift_label = "赠送" if req.is_gift == 1 else "收费"
- create_operation_log(
- db=db,
- admin_id=current_admin.id,
- module="卡密管理",
- action="生成卡密",
- description=f"批量生成卡密:{req.count}张({gift_label}),软件:{req.software_code},天数:{req.days}天",
- new_data={
- "software_code": req.software_code,
- "days": req.days,
- "discount": req.discount,
- "count": req.count,
- "is_gift": req.is_gift,
- "remark": remark_clean,
- "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-已过期"),
- is_gift: int = Query(None, ge=0, le=1, description="类型筛选:0-收费,1-赠送"),
- is_extracted: int = Query(None, ge=0, le=1, description="提取状态筛选:0-未提取,1-已提取"),
- is_revoked: int = Query(None, ge=0, le=1, description="注销筛选:0-未注销,1-已注销"),
- special_close: int = Query(None, ge=0, le=1, description="特殊关闭筛选:0-关闭,1-打开"),
- 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:
- like = f"%{keyword}%"
- query = query.filter(
- or_(Card.card_code.like(like), Card.remark.like(like))
- )
- # 筛选
- if software_code:
- query = query.filter(Card.software_code == software_code)
- if status is not None:
- query = query.filter(Card.status == status)
- if is_gift is not None:
- query = query.filter(Card.is_gift == is_gift)
- if is_extracted is not None:
- query = query.filter(Card.is_extracted == is_extracted)
- if is_revoked is not None:
- query = query.filter(Card.is_revoked == is_revoked)
- if special_close is not None:
- query = query.filter(Card.special_close == special_close)
- # 排序(同创建时间时按 id 降序,保证更新后顺序稳定)
- query = query.order_by(Card.created_at.desc(), Card.id.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,
- "is_gift": c.is_gift,
- "is_extracted": c.is_extracted,
- "special_close": c.special_close,
- "issue_price": float(c.issue_price) if c.issue_price is not None else None,
- "issued_to": c.issued_to,
- "issue_remark": c.issue_remark,
- "issued_at": c.issued_at.isoformat() if c.issued_at else None,
- "issued_admin_id": c.issued_admin_id,
- "remark": c.remark,
- "is_revoked": c.is_revoked,
- "revoked_at": c.revoked_at.isoformat() if c.revoked_at else None,
- "revoked_reason": c.revoked_reason,
- "revoked_admin_id": c.revoked_admin_id,
- "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.put("/{card_id}/extract")
- async def extract_card(
- card_id: int,
- request: Request,
- db: Session = Depends(get_db),
- current_admin = Depends(get_current_admin)
- ):
- """提取单个卡密(标记为已提取)"""
- from app.utils.history import create_card_history, create_operation_log
- try:
- card = db.query(Card).filter(Card.id == card_id, Card.is_deleted == 0).first()
- if not card:
- return {"code": 404, "msg": "卡密不存在"}
- card.is_extracted = 1
- card.updated_at = beijing_now()
- create_card_history(db, card, "UPDATE")
- client_ip = request.client.host if request.client else None
- create_operation_log(
- db=db,
- admin_id=current_admin.id,
- module="卡密管理",
- action="提取卡密",
- description=f"提取卡密:{card.card_code}",
- new_data={"card_id": card.id, "card_code": card.card_code, "is_extracted": 1},
- ip_address=client_ip
- )
- db.commit()
- return {
- "code": 200,
- "msg": "提取成功",
- "data": {"id": card.id, "card_code": card.card_code, "is_extracted": card.is_extracted}
- }
- except Exception:
- db.rollback()
- logger.exception("提取卡密失败")
- return {"code": 500, "msg": "系统异常,操作未完成,请稍后重试;若持续发生请联系客服"}
- @router.put("/{card_id}/issue")
- async def issue_card(
- card_id: int,
- req: CardIssueRequest,
- request: Request,
- db: Session = Depends(get_db),
- current_admin = Depends(get_current_admin)
- ):
- """发放单个卡密(记录发放信息)"""
- from app.utils.history import create_card_history, create_operation_log
- try:
- card = db.query(Card).filter(Card.id == card_id, Card.is_deleted == 0).first()
- if not card:
- return {"code": 404, "msg": "卡密不存在"}
- old_data = {
- "issue_price": float(card.issue_price) if card.issue_price is not None else None,
- "issued_to": card.issued_to,
- "issue_remark": card.issue_remark,
- "issued_at": card.issued_at.isoformat() if card.issued_at else None,
- "issued_admin_id": card.issued_admin_id,
- }
- now = beijing_now()
- card.issue_price = req.issue_price
- card.issued_to = req.issued_to.strip()
- card.issue_remark = (req.issue_remark or "").strip() or None
- card.issued_at = now
- card.issued_admin_id = current_admin.id
- card.updated_at = now
- create_card_history(db, card, "UPDATE")
- client_ip = request.client.host if request.client else None
- create_operation_log(
- db=db,
- admin_id=current_admin.id,
- module="卡密管理",
- action="发放卡密",
- description=f"发放卡密:{card.card_code}",
- old_data=old_data,
- new_data={
- "card_id": card.id,
- "card_code": card.card_code,
- "issue_price": req.issue_price,
- "issued_to": card.issued_to,
- "issue_remark": card.issue_remark,
- "issued_at": now.isoformat(),
- "issued_admin_id": current_admin.id,
- },
- ip_address=client_ip
- )
- db.commit()
- return {"code": 200, "msg": "发放成功"}
- except Exception:
- db.rollback()
- logger.exception("发放卡密失败")
- return {"code": 500, "msg": "系统异常,操作未完成,请稍后重试;若持续发生请联系客服"}
- @router.post("/issue")
- async def issue_cards_batch(
- req: CardBatchIssueRequest,
- request: Request,
- db: Session = Depends(get_db),
- current_admin = Depends(get_current_admin)
- ):
- """批量发放卡密(记录发放信息)"""
- from app.utils.history import create_card_history, create_operation_log
- try:
- cards = db.query(Card).filter(
- Card.id.in_(req.ids),
- Card.is_deleted == 0
- ).all()
- if not cards:
- return {"code": 404, "msg": "未找到可发放的卡密"}
- now = beijing_now()
- issued_to_clean = req.issued_to.strip()
- issue_remark_clean = (req.issue_remark or "").strip() or None
- card_codes = []
- for card in cards:
- card.issue_price = req.issue_price
- card.issued_to = issued_to_clean
- card.issue_remark = issue_remark_clean
- card.issued_at = now
- card.issued_admin_id = current_admin.id
- card.updated_at = now
- card_codes.append(card.card_code)
- create_card_history(db, card, "UPDATE")
- client_ip = request.client.host if request.client else None
- create_operation_log(
- db=db,
- admin_id=current_admin.id,
- module="卡密管理",
- action="批量发放卡密",
- description=f"批量发放卡密:{len(card_codes)}张",
- new_data={
- "card_ids": req.ids,
- "card_codes": card_codes,
- "issue_price": req.issue_price,
- "issued_to": issued_to_clean,
- "issue_remark": issue_remark_clean,
- "issued_at": now.isoformat(),
- "issued_admin_id": current_admin.id,
- },
- ip_address=client_ip
- )
- db.commit()
- return {"code": 200, "msg": "批量发放成功", "data": {"count": len(card_codes)}}
- except Exception:
- db.rollback()
- logger.exception("批量发放卡密失败")
- return {"code": 500, "msg": "系统异常,操作未完成,请稍后重试;若持续发生请联系客服"}
- @router.post("/extract")
- async def extract_cards_batch(
- req: CardBatchExtractRequest,
- request: Request,
- db: Session = Depends(get_db),
- current_admin = Depends(get_current_admin)
- ):
- """批量提取卡密(标记为已提取)"""
- from app.utils.history import create_card_history, create_operation_log
- try:
- cards = db.query(Card).filter(
- Card.id.in_(req.ids),
- Card.is_deleted == 0
- ).all()
- if not cards:
- return {"code": 404, "msg": "未找到可提取的卡密"}
- now = beijing_now()
- card_codes = []
- for card in cards:
- card.is_extracted = 1
- card.updated_at = now
- card_codes.append(card.card_code)
- create_card_history(db, card, "UPDATE")
- client_ip = request.client.host if request.client else None
- create_operation_log(
- db=db,
- admin_id=current_admin.id,
- module="卡密管理",
- action="批量提取卡密",
- description=f"批量提取卡密:{len(card_codes)}张",
- new_data={"card_ids": req.ids, "card_codes": card_codes, "is_extracted": 1},
- ip_address=client_ip
- )
- db.commit()
- return {
- "code": 200,
- "msg": "批量提取成功",
- "data": {"count": len(card_codes), "cards": card_codes}
- }
- except Exception:
- db.rollback()
- logger.exception("批量提取卡密失败")
- return {"code": 500, "msg": "系统异常,操作未完成,请稍后重试;若持续发生请联系客服"}
- @router.post("/special-close")
- async def batch_set_special_close(
- req: CardBatchSpecialCloseRequest,
- request: Request,
- db: Session = Depends(get_db),
- current_admin = Depends(get_current_admin)
- ):
- """批量设置卡密特殊关闭标识(0-关闭,1-打开)"""
- from app.utils.history import create_card_history, create_operation_log
- try:
- cards = db.query(Card).filter(
- Card.id.in_(req.ids),
- Card.is_deleted == 0
- ).all()
- if not cards:
- return {"code": 404, "msg": "未找到可操作的卡密"}
- now = beijing_now()
- card_codes = []
- for card in cards:
- card.special_close = req.special_close
- card.updated_at = now
- card_codes.append(card.card_code)
- create_card_history(db, card, "UPDATE")
- action_label = "打开" if req.special_close == 1 else "关闭"
- client_ip = request.client.host if request.client else None
- create_operation_log(
- db=db,
- admin_id=current_admin.id,
- module="卡密管理",
- action=f"批量特殊关闭{action_label}",
- description=f"批量特殊关闭{action_label}:{len(card_codes)}张",
- new_data={
- "card_ids": req.ids,
- "card_codes": card_codes,
- "special_close": req.special_close,
- },
- ip_address=client_ip
- )
- db.commit()
- return {
- "code": 200,
- "msg": f"批量特殊关闭{action_label}成功",
- "data": {"count": len(card_codes), "special_close": req.special_close}
- }
- except Exception:
- db.rollback()
- logger.exception("批量设置特殊关闭失败")
- return {"code": 500, "msg": "系统异常,操作未完成,请稍后重试;若持续发生请联系客服"}
- @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,
- "is_gift": card.is_gift,
- "is_extracted": card.is_extracted,
- "special_close": card.special_close,
- "issue_price": float(card.issue_price) if card.issue_price is not None else None,
- "issued_to": card.issued_to,
- "issue_remark": card.issue_remark,
- "issued_at": card.issued_at.isoformat() if card.issued_at else None,
- "issued_admin_id": card.issued_admin_id,
- "remark": card.remark,
- "is_revoked": card.is_revoked,
- "revoked_at": card.revoked_at.isoformat() if card.revoked_at else None,
- "revoked_reason": card.revoked_reason,
- "revoked_admin_id": card.revoked_admin_id,
- "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.put("/{card_id}/revoke")
- async def revoke_card(
- card_id: int,
- req: CardRevokeRequest,
- request: Request,
- db: Session = Depends(get_db),
- current_admin = Depends(get_current_admin),
- ):
- """注销单个卡密(标记为不可再使用)"""
- from app.utils.history import create_card_history, create_operation_log
- try:
- card = db.query(Card).filter(Card.id == card_id, Card.is_deleted == 0).first()
- if not card:
- return {"code": 404, "msg": "卡密不存在"}
- if card.is_revoked == 1:
- return {"code": 400, "msg": "该卡密已注销"}
- if card.status != 0:
- return {"code": 400, "msg": "仅支持注销未激活的卡密"}
- old_data = {
- "is_revoked": card.is_revoked,
- "revoked_at": card.revoked_at.isoformat() if card.revoked_at else None,
- "revoked_reason": card.revoked_reason,
- "revoked_admin_id": card.revoked_admin_id,
- }
- now = beijing_now()
- card.is_revoked = 1
- card.is_deleted = 1
- card.revoked_at = now
- card.revoked_reason = (req.revoked_reason or "").strip() or None
- card.revoked_admin_id = current_admin.id
- card.updated_at = now
- create_card_history(db, card, "UPDATE")
- client_ip = request.client.host if request.client else None
- create_operation_log(
- db=db,
- admin_id=current_admin.id,
- module="卡密管理",
- action="注销卡密",
- description=f"注销卡密:{card.card_code}",
- old_data=old_data,
- new_data={
- "card_id": card.id,
- "card_code": card.card_code,
- "is_revoked": 1,
- "is_deleted": 1,
- "revoked_at": now.isoformat(),
- "revoked_reason": card.revoked_reason,
- "revoked_admin_id": current_admin.id,
- },
- ip_address=client_ip,
- )
- db.commit()
- return {"code": 200, "msg": "注销成功"}
- except Exception:
- db.rollback()
- logger.exception("注销卡密失败")
- return {"code": 500, "msg": "系统异常,操作未完成,请稍后重试;若持续发生请联系客服"}
- @router.post("/revoke")
- async def revoke_cards_batch(
- req: CardBatchRevokeRequest,
- request: Request,
- db: Session = Depends(get_db),
- current_admin = Depends(get_current_admin),
- ):
- """批量注销卡密(标记为不可再使用)"""
- from app.utils.history import create_card_history, create_operation_log
- try:
- cards = db.query(Card).filter(
- Card.id.in_(req.ids),
- Card.is_deleted == 0,
- ).all()
- if not cards:
- return {"code": 404, "msg": "未找到可注销的卡密"}
- now = beijing_now()
- reason_clean = (req.revoked_reason or "").strip() or None
- revoked_codes: list[str] = []
- skipped: list[dict] = []
- for card in cards:
- if card.is_revoked == 1:
- skipped.append({"id": card.id, "card_code": card.card_code, "reason": "已注销"})
- continue
- if card.status != 0:
- skipped.append({"id": card.id, "card_code": card.card_code, "reason": "非未激活"})
- continue
- card.is_revoked = 1
- card.is_deleted = 1
- card.revoked_at = now
- card.revoked_reason = reason_clean
- card.revoked_admin_id = current_admin.id
- card.updated_at = now
- revoked_codes.append(card.card_code)
- create_card_history(db, card, "UPDATE")
- if not revoked_codes:
- return {"code": 400, "msg": "没有可注销的卡密", "data": {"skipped": skipped}}
- client_ip = request.client.host if request.client else None
- create_operation_log(
- db=db,
- admin_id=current_admin.id,
- module="卡密管理",
- action="批量注销卡密",
- description=f"批量注销卡密:{len(revoked_codes)}张",
- new_data={
- "card_ids": req.ids,
- "revoked_card_codes": revoked_codes,
- "revoked_at": now.isoformat(),
- "revoked_reason": reason_clean,
- "revoked_admin_id": current_admin.id,
- "is_deleted": 1,
- "skipped": skipped,
- },
- ip_address=client_ip,
- )
- db.commit()
- return {"code": 200, "msg": "批量注销成功", "data": {"count": len(revoked_codes), "skipped": skipped}}
- except Exception:
- db.rollback()
- logger.exception("批量注销卡密失败")
- return {"code": 500, "msg": "系统异常,操作未完成,请稍后重试;若持续发生请联系客服"}
- @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,
- "issue_price": float(card.issue_price) if card.issue_price is not None else None,
- "issued_to": card.issued_to,
- "issue_remark": card.issue_remark,
- "issued_at": card.issued_at.isoformat() if card.issued_at else None,
- "issued_admin_id": card.issued_admin_id,
- }
- }
- @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="状态筛选"),
- is_gift: int = Query(None, ge=0, le=1, description="类型筛选:0-收费,1-赠送"),
- is_extracted: int = Query(None, ge=0, le=1, description="提取状态筛选:0-未提取,1-已提取"),
- is_revoked: int = Query(None, ge=0, le=1, description="注销筛选:0-未注销,1-已注销"),
- special_close: int = Query(None, ge=0, le=1, description="特殊关闭筛选:0-关闭,1-打开"),
- db: Session = Depends(get_db),
- current_admin = Depends(get_current_admin)
- ):
- """导出卡密为Excel"""
- from sqlalchemy import or_
- 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:
- like = f"%{keyword}%"
- query = query.filter(
- or_(Card.card_code.like(like), Card.remark.like(like))
- )
- # 筛选
- if software_code:
- query = query.filter(Card.software_code == software_code)
- if status is not None:
- query = query.filter(Card.status == status)
- if is_gift is not None:
- query = query.filter(Card.is_gift == is_gift)
- if is_extracted is not None:
- query = query.filter(Card.is_extracted == is_extracted)
- if is_revoked is not None:
- query = query.filter(Card.is_revoked == is_revoked)
- if special_close is not None:
- query = query.filter(Card.special_close == special_close)
- cards_with_names = query.order_by(Card.created_at.desc(), Card.id.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,
- "is_gift": is_gift,
- "is_extracted": is_extracted,
- "is_revoked": is_revoked,
- "special_close": special_close,
- }
- },
- ip_address=client_ip
- )
- db.commit()
- # 生成CSV格式(临时方案,后续可以升级为真正的Excel格式)
- output = io.StringIO()
- writer = csv.writer(output)
- writer.writerow([
- 'ID', '卡密代码', '软件特征码', '软件名称', '天数', '折扣', '状态', '卡密类型', '提取状态', '特殊关闭',
- '注销状态', '注销时间', '注销原因', '注销管理员ID',
- '发放价格', '发放对象', '发放备注', '发放时间', '发放管理员ID',
- '备注',
- '激活用户ID', '用户名称', '激活时间', '到期时间', '创建时间', '更新时间'
- ])
- status_map = {0: '未激活', 1: '已激活', 2: '已过期'}
- gift_map = {0: '收费', 1: '赠送'}
- extracted_map = {0: '未提取', 1: '已提取'}
- special_close_map = {0: '关闭', 1: '打开'}
- revoked_map = {0: '未注销', 1: '已注销'}
- 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, '未知'),
- gift_map.get(card.is_gift, '未知'),
- extracted_map.get(card.is_extracted, '未知'),
- special_close_map.get(getattr(card, "special_close", 0) or 0, '未知'),
- revoked_map.get(getattr(card, "is_revoked", 0), '未知'),
- card.revoked_at.isoformat() if getattr(card, "revoked_at", None) else '',
- (card.revoked_reason or '') if getattr(card, "revoked_reason", None) is not None else '',
- card.revoked_admin_id or '',
- float(card.issue_price) if card.issue_price is not None else '',
- card.issued_to or '',
- card.issue_remark or '',
- card.issued_at.isoformat() if card.issued_at else '',
- card.issued_admin_id or '',
- card.remark or '',
- 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"}
- )
|