"""审计日志查询接口,仅超级管理员可访问。""" from __future__ import annotations import json from datetime import datetime from fastapi import APIRouter, Depends, HTTPException, Query from sqlalchemy.orm import Session from ..database import get_db from ..deps import get_current_user from ..models import Account, AuditLog, DouyuTask, User router = APIRouter(prefix="/api/audit-logs", tags=["审计日志"]) def require_super_admin(current: User = Depends(get_current_user)) -> User: """审计日志属于高敏感运维数据,只允许超级管理员查看。""" if current.role != "super_admin": raise HTTPException(status_code=403, detail="仅超级管理员可查看审计日志") return current def _audit_log_out(db: Session, row: AuditLog) -> dict: """序列化审计记录,并为旧斗鱼直充批次补齐可安全展示的账号身份。""" detail_text = row.detail or "" if row.action == "recharge:douyu:create" and row.target.startswith("douyu_batch:"): try: detail = json.loads(detail_text) if detail_text else {} except json.JSONDecodeError: detail = None if isinstance(detail, dict) and not detail.get("recharge_accounts"): batch_id = row.target.removeprefix("douyu_batch:") accounts = ( db.query(DouyuTask, Account) .join(Account, Account.id == DouyuTask.account_id) .filter(DouyuTask.batch_id == batch_id, DouyuTask.task_type == "create_gold_qr") .order_by(DouyuTask.id.asc()) .limit(100) .all() ) if accounts: detail["recharge_accounts"] = [ { "task_id": task.id, "username": account.username, "douyu_uid": account.uid or "", "douyu_nickname": account.nickname or "", } for task, account in accounts ] detail["recharge_accounts_truncated"] = len(accounts) == 100 if isinstance(detail, dict) and not detail.get("payment_method"): batch_id = row.target.removeprefix("douyu_batch:") task = ( db.query(DouyuTask) .filter(DouyuTask.batch_id == batch_id, DouyuTask.task_type == "create_gold_qr") .order_by(DouyuTask.id.asc()) .first() ) task_result = task.result if task and isinstance(task.result, dict) else {} recharge_channel = str(task_result.get("recharge_channel") or "wechat_qr") detail["recharge_channel"] = recharge_channel detail["payment_method"] = "API 直充支付" if recharge_channel == "supplier_api" else "微信扫码支付" if isinstance(detail, dict): detail_text = json.dumps(detail, ensure_ascii=False, separators=(",", ":")) return { "id": row.id, "user_id": row.user_id, "username": row.username or "", "action": row.action, "target": row.target or "", "detail": detail_text, "success": row.success, "created_at": row.created_at, } @router.get("") def list_audit_logs( page: int = Query(1, ge=1), page_size: int = Query(50, ge=1, le=200), username: str | None = Query(None, max_length=64), action: str | None = Query(None, max_length=128), keyword: str | None = Query(None, max_length=128), success: bool | None = Query(None), start_time: datetime | None = Query(None), end_time: datetime | None = Query(None), db: Session = Depends(get_db), _: User = Depends(require_super_admin), ): """分页查询审计记录,不提供修改和删除能力。""" query = db.query(AuditLog) if username: query = query.filter(AuditLog.username.ilike(f"%{username.strip()}%")) if action: query = query.filter(AuditLog.action == action.strip()) if keyword: pattern = f"%{keyword.strip()}%" query = query.filter((AuditLog.target.ilike(pattern)) | (AuditLog.detail.ilike(pattern))) if success is not None: query = query.filter(AuditLog.success == success) if start_time: query = query.filter(AuditLog.created_at >= start_time) if end_time: query = query.filter(AuditLog.created_at <= end_time) total = query.count() rows = query.order_by(AuditLog.id.desc()).offset((page - 1) * page_size).limit(page_size).all() return { "items": [_audit_log_out(db, row) for row in rows], "total": total, "page": page, "page_size": page_size, }