diff --git a/tests/test_audit_logs.py b/tests/test_audit_logs.py index 944deb7..5fa11a2 100644 --- a/tests/test_audit_logs.py +++ b/tests/test_audit_logs.py @@ -97,6 +97,8 @@ class AuditLogTests(unittest.TestCase): detail = json.loads(result["items"][0]["detail"]) self.assertEqual(detail["recharge_accounts"][0]["username"], "douyu-login") self.assertEqual(detail["recharge_accounts"][0]["douyu_uid"], "10001") + self.assertEqual(detail["recharge_channel"], "wechat_qr") + self.assertEqual(detail["payment_method"], "微信扫码支付") if __name__ == "__main__": diff --git a/web/backend/routers/audit.py b/web/backend/routers/audit.py index 6f7aa02..a62f5dd 100644 --- a/web/backend/routers/audit.py +++ b/web/backend/routers/audit.py @@ -52,7 +52,20 @@ def _audit_log_out(db: Session, row: AuditLog) -> dict: for task, account in accounts ] detail["recharge_accounts_truncated"] = len(accounts) == 100 - detail_text = json.dumps(detail, ensure_ascii=False, separators=(",", ":")) + 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, diff --git a/web/backend/routers/douyu.py b/web/backend/routers/douyu.py index 0a66a13..0b5feed 100644 --- a/web/backend/routers/douyu.py +++ b/web/backend/routers/douyu.py @@ -497,6 +497,11 @@ async def create_task_batch( raise HTTPException(status_code=400, detail="没有可执行的斗鱼账号,请先登录获取 Cookie") if req.task_type == "create_gold_qr": + config = ensure_douyu_config(db) + recharge_channel = str( + douyu_config_value("gold_recharge_channel", config.gold_recharge_channel) + ).strip() + payment_method = "API 直充支付" if recharge_channel == "supplier_api" else "微信扫码支付" recharge_accounts = ( db.query(DouyuTask, Account) .join(Account, Account.id == DouyuTask.account_id) @@ -510,6 +515,8 @@ async def create_task_batch( detail={ "batch_id": batch_id, "task_type": req.task_type, "count": count, "account_count": len(req.account_ids), "handbook_scope": req.handbook_scope, + "recharge_channel": recharge_channel, + "payment_method": payment_method, # 仅记录充值身份,禁止在审计中写入 Cookie、密码等凭据。 "recharge_accounts": [ { diff --git a/web/frontend/src/pages/AuditLogsPage.tsx b/web/frontend/src/pages/AuditLogsPage.tsx index bd1a06b..72e0218 100644 --- a/web/frontend/src/pages/AuditLogsPage.tsx +++ b/web/frontend/src/pages/AuditLogsPage.tsx @@ -15,7 +15,7 @@ const ACTION_OPTIONS = [ { value: 'recharge:yyb:payment', label: '应用宝生成付款码' }, { value: 'recharge:yyb:payment_check', label: '应用宝检测到账' }, { value: 'recharge:yyb:stop', label: '应用宝停止任务' }, - { value: 'recharge:douyu:create', label: '斗鱼创建直充批次' }, + { value: 'recharge:douyu:create', label: '斗鱼创建充值批次' }, { value: 'recharge:douyu:callback', label: '斗鱼供应商回调' }, { value: 'recharge:douyu:stop', label: '斗鱼停止直充批次' }, { value: 'recharge:douyu:config', label: '斗鱼直充配置' }, @@ -24,10 +24,25 @@ const ACTION_OPTIONS = [ { value: 'recharge:huya:config', label: '虎牙充值配置' }, ]; +const ACTION_LABELS = new Map(ACTION_OPTIONS.map((item) => [item.value, item.label])); + function formatTime(value: string): string { return new Date(value).toLocaleString('zh-CN', { hour12: false }); } +function actionLabel(action: string): string { + return ACTION_LABELS.get(action) || action; +} + +function paymentMethod(detail: string): string { + try { + const data = JSON.parse(detail) as Record; + return typeof data.payment_method === 'string' ? data.payment_method : '-'; + } catch { + return '-'; + } +} + function detailSummary(detail: string): string { try { const data = JSON.parse(detail) as Record; @@ -90,8 +105,12 @@ export default function AuditLogsPage() { const columns: ColumnsType = [ { title: '时间', dataIndex: 'created_at', width: 180, render: formatTime }, { title: '操作者', dataIndex: 'username', width: 120, render: (value) => value || '-' }, - { title: '操作', dataIndex: 'action', width: 210 }, + { title: '操作', dataIndex: 'action', width: 180, render: actionLabel }, { title: '目标', dataIndex: 'target', width: 210, ellipsis: true }, + { + title: '充值方式', dataIndex: 'detail', width: 130, + render: (detail: string) => paymentMethod(detail), + }, { title: '结果', dataIndex: 'success', width: 88, render: (value: boolean | null) => ( @@ -144,8 +163,9 @@ export default function AuditLogsPage() { {formatTime(selected.created_at)} {selected.username || '-'} - {selected.action} + {actionLabel(selected.action)} {selected.target || '-'} + {paymentMethod(selected.detail)} {selected.success === null ? '历史记录' : selected.success ? '成功' : '失败'}
{formatDetail(selected.detail)}