feat: 升级审计装饰器,支持自动抓取并记录 API 请求体作为变更明细
This commit is contained in:
@ -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(
|
||||||
|
|||||||
Reference in New Issue
Block a user