Files
yingqing/core/backend/apps/accounts/audit.py
T
seaislee1209andClaude Opus 4.8 39b4467258 feat(admin): Phase 7 计费审计+4层额度策略 — 流水浏览/手动调额/额度策略CRUD+拦截
后端:adminpanel ledgers(全局流水+筛)+ adjust(手动调额,落 ADJUSTMENT)+ quota-policies CRUD;
额度拦截 _enforce_quota_policy(单任务/月度/项目)接入 reserve_credit,仅团队有 active 策略才生效(无策略零回归);
adjust_credit helper。修真 bug:log_admin_action 加 savepoint + JSON 安全化(UUID/Decimal),
修复传 serializer.data 致审计报错污染外层事务 → TransactionManagementError。
前端:adminApi 计费/额度系列;计费审计页(流水表+手动调额弹窗)+ 额度策略页(CRUD 弹窗,团队下拉+三类上限+启用)。
测试:adminpanel 44 + billing + accounts = 70 单测过(调额±/超额拒/额度拦截 per_task+monthly/无策略零回归);
无头 e2e _admin-p7.mjs 6 断言过 + 0 console error(一次性团队,不动 demo 余额);tsc+build 绿。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 22:33:52 +08:00

62 lines
2.1 KiB
Python

"""平台超管审计写入 helper。失败绝不阻断主流程(审计是旁路)。"""
def _client_ip(request):
if request is None:
return None
forwarded = request.META.get("HTTP_X_FORWARDED_FOR", "")
if forwarded:
return forwarded.split(",")[0].strip()
return request.META.get("REMOTE_ADDR") or None
def _json_safe(value):
"""把任意值压成 JSONField 可存的结构(UUID/Decimal/datetime 等转字符串)。
失败兜底成 {_repr}。审计字段绝不能因不可序列化而抛错、污染外层事务。"""
import json
from django.core.serializers.json import DjangoJSONEncoder
if value is None:
return None
try:
return json.loads(json.dumps(value, cls=DjangoJSONEncoder))
except Exception: # noqa: BLE001
return {"_repr": str(value)}
def log_admin_action(
request,
action,
*,
target_type="",
target_id="",
target_name="",
before=None,
after=None,
):
"""记录一条平台超管操作审计。action 用动词短语(如 'invite.issue' / 'team.disable')。
operator_name 取当前登录用户名快照。**用 savepoint 包裹**:审计写入失败只回滚自身,
绝不把调用方所在事务标脏(否则后续查询会 TransactionManagementError)。"""
from django.db import transaction
from .models import AdminAuditLog
try:
user = getattr(request, "user", None)
operator = user if (user is not None and getattr(user, "is_authenticated", False)) else None
with transaction.atomic():
AdminAuditLog.objects.create(
operator=operator,
operator_name=(getattr(operator, "username", "") or ""),
action=action,
target_type=target_type or "",
target_id=str(target_id or ""),
target_name=target_name or "",
before=_json_safe(before),
after=_json_safe(after),
ip_address=_client_ip(request),
)
except Exception: # noqa: BLE001 — 审计失败不应阻断主流程
pass