feat: 升级审计装饰器,支持自动抓取并记录 API 请求体作为变更明细

This commit is contained in:
DXC
2026-03-10 17:36:02 +08:00
parent cd192624b9
commit ac97c6066b

View File

@ -95,7 +95,45 @@ def audit_log(module: str, action: str = None, get_target_id_fn=None, get_target
审计日志装饰器 审计日志装饰器
用法: @audit_log(module='inbound_buy', action='create') 用法: @audit_log(module='inbound_buy', action='create')
@audit_log(module='bom', action='update', get_target_id_fn=lambda: ..., get_details_fn=lambda req, resp: ...) @audit_log(module='bom', action='update', get_target_id_fn=lambda: ..., get_details_fn=lambda req, resp: ...)
升级特性:
- 自动捕获请求 Payload 作为变更明细
- 自动过滤过长的 Base64 图片数据
- 支持自定义 get_details_fn 覆盖默认行为
""" """
# 需要过滤的图片字段
IMAGE_FIELDS = {'arrival_photo', 'product_photo', 'photo', 'image', 'signature', 'borrow_signature', 'return_signature'}
def _filter_payload(payload):
"""过滤 Payload 中的大字段,防止数据库膨胀"""
if not payload or not isinstance(payload, dict):
return payload
filtered = {}
for key, value in payload.items():
if key.lower() in IMAGE_FIELDS and isinstance(value, str) and len(value) > 100:
filtered[key] = '[图片数据已省略]'
elif isinstance(value, dict):
filtered[key] = _filter_payload(value)
elif isinstance(value, list):
filtered[key] = [
_filter_payload(item) if isinstance(item, dict) else item
for item in value
]
else:
filtered[key] = value
return filtered
def _get_payload():
"""自动获取请求 Payload"""
# 尝试 JSON
payload = request.get_json(silent=True)
if payload:
return payload
# 尝试 Form Data
if request.form:
return request.form.to_dict()
return None
def wrapper(fn): def wrapper(fn):
@wraps(fn) @wraps(fn)
def decorator(*args, **kwargs): def decorator(*args, **kwargs):
@ -109,7 +147,7 @@ def audit_log(module: str, action: str = None, get_target_id_fn=None, get_target
ip_address = request.headers.get('X-Forwarded-For') or request.remote_addr or '' ip_address = request.headers.get('X-Forwarded-For') or request.remote_addr or ''
if ip_address and ',' in ip_address: if ip_address and ',' in ip_address:
ip_address = ip_address.split(',')[0].strip() ip_address = ip_address.split(',')[0].strip()
# 获取请求信息 # 获取请求信息
http_method = request.method http_method = request.method
url = request.url url = request.url
@ -120,6 +158,10 @@ def audit_log(module: str, action: str = None, get_target_id_fn=None, get_target
if callable(action): if callable(action):
final_action = action() final_action = action()
# 预先获取 Payload用于后续 details 记录)
raw_payload = _get_payload()
filtered_payload = _filter_payload(raw_payload) if raw_payload else None
# 执行原函数 # 执行原函数
response = fn(*args, **kwargs) response = fn(*args, **kwargs)
@ -157,10 +199,14 @@ def audit_log(module: str, action: str = None, get_target_id_fn=None, get_target
# 获取 details # 获取 details
details = None details = None
if get_details_fn: if get_details_fn:
# 优先使用自定义差异对比函数
try: try:
details = get_details_fn(request, response) details = get_details_fn(request, response)
except Exception: except Exception:
pass pass
elif filtered_payload:
# 默认:记录请求 Payload
details = {'payload': filtered_payload}
# 保存日志 # 保存日志
log_entry = AuditLog( log_entry = AuditLog(