117 lines
4.6 KiB
Python
117 lines
4.6 KiB
Python
"""审计日志查询接口,仅超级管理员可访问。"""
|
|
|
|
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,
|
|
}
|