- UserPassword 默认值改为空串(防默认口令,须经注册/重置流程设置) - exception_handler print→logging(日志可审计) - ip_filter 默认 disabled 语义注释(生产需显式配置并挂载)
55 lines
2.3 KiB
Python
55 lines
2.3 KiB
Python
import logging
|
|
from rest_framework.views import exception_handler
|
|
from rest_framework.response import Response
|
|
from rest_framework import status
|
|
from django.conf import settings
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def custom_exception_handler(exc, context):
|
|
# 始终记录异常堆栈(走 logging,日志落盘可审计,替代 print)
|
|
view = context.get('view', None) if context else None
|
|
view_name = view.__class__.__name__ if view else 'Unknown'
|
|
logger.error('[EXCEPTION] %s: %s', view_name, exc, exc_info=True)
|
|
|
|
response = exception_handler(exc, context)
|
|
|
|
if response is not None:
|
|
data = {
|
|
'code': response.status_code,
|
|
'msg': str(exc) if settings.DEBUG else '请求处理失败',
|
|
'data': None,
|
|
}
|
|
if isinstance(response.data, dict):
|
|
if 'detail' in response.data:
|
|
# 401/403 的 detail(如 "身份认证信息未提供"/"Token 已过期")对前端是
|
|
# 必要的业务信息,不属于敏感堆栈,即使在生产环境也直接暴露。
|
|
if response.status_code in (status.HTTP_401_UNAUTHORIZED,
|
|
status.HTTP_403_FORBIDDEN):
|
|
data['msg'] = str(response.data['detail'])
|
|
else:
|
|
data['msg'] = str(response.data['detail']) if settings.DEBUG else '请求处理失败'
|
|
elif 'msg' in response.data:
|
|
data['msg'] = response.data['msg']
|
|
else:
|
|
if settings.DEBUG:
|
|
errors = []
|
|
for field, messages in response.data.items():
|
|
if isinstance(messages, list):
|
|
errors.extend([f"{field}: {m}" for m in messages])
|
|
else:
|
|
errors.append(f"{field}: {messages}")
|
|
data['msg'] = '; '.join(errors) if errors else str(exc)
|
|
else:
|
|
data['msg'] = '请求参数错误'
|
|
response.data = data
|
|
else:
|
|
# 未被 DRF 捕获的异常(如 Django FieldError、AttributeError 等)
|
|
response = Response(
|
|
{'code': 500, 'msg': str(exc) if settings.DEBUG else '服务器内部错误', 'data': None},
|
|
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
)
|
|
|
|
return response
|