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}