Files
medical_training/config/exceptions.py
T

78 lines
2.4 KiB
Python

import uuid
import logging
from rest_framework.views import exception_handler as drf_exception_handler
from rest_framework.response import Response
from rest_framework.exceptions import APIException, ValidationError
logger = logging.getLogger(__name__)
_STATUS_TO_CODE = {
400: 'BAD_REQUEST',
401: 'AUTH_UNAUTHORIZED',
403: 'PERMISSION_DENIED',
404: 'NOT_FOUND',
405: 'METHOD_NOT_ALLOWED',
429: 'SYS_RATE_LIMIT',
500: 'SYS_INTERNAL',
503: 'SYS_DEPENDENCY_DOWN',
}
def custom_exception_handler(exc, context):
trace_id = uuid.uuid4().hex[:12]
response = drf_exception_handler(exc, context)
if response is None:
logger.exception('Unhandled server error trace_id=%s', trace_id)
return Response(
{'code': 'SYS_INTERNAL', 'message': '服务器内部错误', 'details': None, 'trace_id': trace_id},
status=500,
)
data = response.data
status_code = response.status_code
# Already structured by AppError or our custom exceptions
if isinstance(data, dict) and 'code' in data and 'message' in data:
response.data = {**data, 'trace_id': trace_id}
return response
# DRF ValidationError: {'field': ['error msg']}
if isinstance(exc, ValidationError):
response.data = {
'code': 'VALIDATION_ERROR',
'message': '请求参数不合法',
'details': data,
'trace_id': trace_id,
}
return response
# Standard DRF exceptions (AuthenticationFailed, PermissionDenied, Throttled, etc.)
message = _extract_message(data)
response.data = {
'code': _STATUS_TO_CODE.get(status_code, 'SYS_INTERNAL'),
'message': message,
'details': None,
'trace_id': trace_id,
}
return response
def _extract_message(data):
if isinstance(data, dict):
return str(data.get('detail', data))
if isinstance(data, list) and data:
return str(data[0])
return str(data)
class AppError(APIException):
"""统一业务异常。视图中 raise AppError('CODE', '消息', details, status_code) 即可。"""
def __init__(self, code, message, details=None, status_code=400):
self.status_code = status_code
super().__init__(detail=code)
# 绕过 DRF _get_error_details,防止 None 被转成字符串 "None"
self.detail = {'code': code, 'message': message, 'details': details}