后端: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>
62 lines
2.1 KiB
Python
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
|