DRF视图集自定义方法错误处理规范与前端适配指南
DRF视图集自定义方法错误处理规范与前端适配指南
一、DRF后端错误处理最佳实践
1.1 异常类体系与应用场景
DRF提供完整的异常处理机制,推荐使用内置异常类而非直接返回Response对象:
|
异常类 |
状态码 |
适用场景 |
示例 |
|
ValidationError |
400 |
数据验证失败 |
表单字段错误、数据格式非法 |
|
ParseError |
400 |
请求解析失败 |
文件格式错误、JSON解析异常 |
|
AuthenticationFailed |
401 |
认证失败 |
Token过期、未提供凭证 |
|
PermissionDenied |
403 |
权限不足 |
无操作权限、IP被封禁 |
|
NotFound |
404 |
资源不存在 |
访问不存在的ID记录 |
|
MethodNotAllowed |
405 |
方法不允许 |
GET请求访问POST端点 |
|
NotAcceptable |
406 |
不支持的响应格式 |
请求XML但仅支持JSON |
|
UnsupportedMediaType |
415 |
不支持的媒体类型 |
上传非允许格式的文件 |
|
APIException |
500 |
服务器内部错误 |
数据库异常、第三方服务故障 |
1.2 批量上传端点错误处理实现
完整企业级代码示例
import logging
from django.db import transaction
from rest_framework import viewsets, status
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.exceptions import ParseError, APIException
from rest_framework import serializers
logger = logging.getLogger(__name__)
class BatchUploadSerializer(serializers.Serializer):
"""批量上传数据验证序列化器"""
files = serializers.ListField(
child=serializers.FileField(max_length=1024*1024*20, allow_empty_file=False),
min_length=1,
max_length=20,
error_messages={
'max_length': '一次最多只能上传20个文件',
'min_length': '至少需要上传1个文件',
'empty': '文件列表不能为空'
}
)
category = serializers.CharField(max_length=100, required=False)
class ResourceViewSet(viewsets.ModelViewSet):
"""资源管理视图集,包含批量上传功能"""
@action(detail=False, methods=['post'], url_path='batch')
def batch_upload(self, request):
"""
批量上传文件端点
支持一次上传1-20个文件,单个文件最大20MB
错误处理遵循DRF标准异常体系,返回结构化错误信息
"""
# 1. 文件数量预检(业务规则验证)
files = request.FILES.getlist('files')
if len(files) > 20:
# 使用ParseError处理请求格式错误
raise ParseError({
'error': '文件数量超限',
'detail': '一次最多只能上传20个文件',
'limit': 20,
'received': len(files)
})
# 2. 数据验证(使用序列化器)
serializer = BatchUploadSerializer(data=request.data)
try:
serializer.is_valid(raise_exception=True) # 验证失败直接抛出ValidationError
except serializers.ValidationError as e:
# 记录验证错误日志
logger.warning(
f"批量上传验证失败: user={request.user.username}, "
f"errors={e.detail}, ip={request.META.get('REMOTE_ADDR')}"
)
raise # 重新抛出异常,由DRF统一处理格式
# 3. 业务逻辑处理(带事务支持)
try:
with transaction.atomic(): # 确保数据一致性
# 处理文件上传逻辑
result = self._process_files(
files=serializer.validated_data['files'],
category=serializer.validated_data.get('category'),
user=request.user
)
return Response({
'status': 'success',
'message': f"{len(files)}个文件上传成功",
'data': result
}, status=status.HTTP_201_CREATED)
except Exception as e:
# 记录系统异常日志(包含详细堆栈)
logger.error(
f"批量上传业务处理失败: user={request.user.username}",
exc_info=True # 记录完整堆栈信息
)
# 抛出APIException,隐藏内部错误详情
raise APIException({
'error': '服务器处理失败',
'detail': '批量上传过程中发生错误,请稍后重试',
'request_id': self._generate_request_id() # 生成唯一请求ID便于排查
})
def _process_files(self, files, category=None, user=None):
"""文件处理内部方法"""
results = []
for file in files:
# 实际文件处理逻辑
file_id = self._save_file(file, user)
results.append({
'filename': file.name,
'file_id': file_id,
'size': file.size,
'category': category or 'default'
})
return results
def _generate_request_id(self):
"""生成唯一请求ID用于追踪"""
import uuid
return str(uuid.uuid4())
1.3 异常处理核心优势
1.** 格式一致性 **:所有异常自动转换为标准化响应格式
{
"error": "文件数量超限",
"detail": "一次最多只能上传20个文件",
"limit": 20,
"received": 25
}
2.** 状态码自动映射 **:异常类与HTTP状态码自动关联,无需手动指定
3.** 错误堆栈记录 **:生产环境记录完整错误堆栈,响应中仅返回用户友好信息
4.** 与DRF生态兼容 **:自动集成到DRF的异常处理中间件,支持自定义异常处理器
二、自定义异常处理器配置
2.1 全局异常处理器实现
# core/exceptions.py
from rest_framework.views import exception_handler
from rest_framework.response import Response
import logging
import traceback
import uuid
logger = logging.getLogger(__name__)
def custom_exception_handler(exc, context):
"""DRF全局异常处理器"""
# 调用DRF默认异常处理器获取标准响应
response = exception_handler(exc, context)
# 生成唯一请求ID
request_id = str(uuid.uuid4())
# 记录异常详情
logger.error(
f"请求处理异常 [request_id={request_id}]: {str(exc)}\n"
f"请求信息: {context['request'].method} {context['request'].path}\n"
f"堆栈信息: {traceback.format_exc()}"
)
# 如果DRF无法处理的异常(如数据库异常)
if response is None:
return Response(
{
'error': '服务器内部错误',
'detail': '系统暂时无法处理请求,请稍后重试',
'request_id': request_id
},
status=500
)
# 为标准异常添加request_id
if isinstance(response.data, dict):
response.data['request_id'] = request_id
else:
response.data = {
'error': response.data,
'request_id': request_id
}
return response
2.2 配置生效
# settings.py
REST_FRAMEWORK = {
# 配置自定义异常处理器
'EXCEPTION_HANDLER': 'core.exceptions.custom_exception_handler',
# 其他配置...
}
三、Quasar前端错误处理实现
3.1 错误处理工具函数
// src/utils/api/errorHandler.js
import { Notify } from 'quasar'
/**
* 标准化API错误处理
* @param {Error} error - Axios捕获的错误对象
* @param {Object} options - 配置选项
* @param {Boolean} options.silent - 是否静默处理(不显示通知)
* @param {Function} options.customHandler - 自定义错误处理函数
* @returns {Object} 标准化错误信息
*/
export function handleApiError(error, options = {}) {
const { silent = false, customHandler } = options
let errorInfo = {
code: null,
message: '未知错误',
details: null,
requestId: null
}
// 1. 网络错误(无响应)
if (!error.response) {
errorInfo.message = '网络连接异常,请检查网络设置'
errorInfo.details = error.message || '无法连接到服务器'
// 2. 服务器响应错误
} else {
const { status, data } = error.response
errorInfo.code = status
// 提取标准化错误信息
if (typeof data === 'object') {
errorInfo.message = data.error || '操作失败'
errorInfo.details = data.detail || data.details || null
errorInfo.requestId = data.request_id || null
// DRF非字段错误处理
if (data.non_field_errors && Array.isArray(data.non_field_errors)) {
errorInfo.message = data.non_field_errors[0]
}
// 字段验证错误处理
if (status === 400 && !errorInfo.message && Object.keys(data).length) {
const firstField = Object.keys(data)[0]
errorInfo.message = Array.isArray(data[firstField])
? data[firstField][0]
: data[firstField]
}
} else {
// 非JSON响应
errorInfo.message = data || `服务器返回${status}错误`
}
}
// 3. 自定义错误处理
if (customHandler && typeof customHandler === 'function') {
customHandler(errorInfo, error)
return errorInfo
}
// 4. 显示错误通知
if (!silent) {
Notify.create({
type: 'negative',
message: errorInfo.message,
caption: errorInfo.requestId ? `请求ID: ${errorInfo.requestId}` : null,
timeout: errorInfo.code === 500 ? 10000 : 5000, // 服务器错误显示更久
position: 'top'
})
}
// 5. 控制台输出详细错误(开发环境)
if (process.env.DEV) {
console.groupCollapsed(`API Error [${errorInfo.code || 'NETWORK'}]`)
console.error('错误信息:', errorInfo)
console.error('原始错误:', error)
console.groupEnd()
}
return errorInfo
}
3.2 API调用与错误处理集成
// src/api/fileUpload.js
import { api } from 'boot/axios' // 配置好的Axios实例
import { handleApiError } from '../utils/api/errorHandler'
/**
* 批量上传文件
* @param {FormData} formData - 包含文件的FormData对象
* @param {Object} options - 上传选项
* @returns {Promise<Object>} 上传结果
*/
export async function batchUploadFiles(formData, options = {}) {
try {
const response = await api.post('/resources/batch/', formData, {
headers: {
'Content-Type': 'multipart/form-data'
},
onUploadProgress: options.onProgress // 进度回调
})
// 成功响应处理
return {
success: true,
data: response.data
}
} catch (error) {
// 调用错误处理工具函数
const errorInfo = handleApiError(error, {
// 可选的自定义处理
customHandler: (err) => {
// 特定错误码处理
if (err.code === 413) { // 请求实体过大
// 显示特殊处理界面
options.onTooLarge?.()
}
}
})
return {
success: false,
error: errorInfo
}
}
}
3.3 组件中使用示例
<!-- src/pages/FileUpload.vue -->
<template>
<q-page class="row q-pa-md">
<q-uploader
label="批量上传文件"
:multiple="true"
:max-files="20"
accept="*/*"
@add-files="onFilesAdded"
class="col-12"
/>
<q-progress
v-if="uploadProgress > 0 && uploadProgress < 100"
:value="uploadProgress"
label="上传中..."
class="col-12 q-mt-md"
/>
</q-page>
</template>
<script setup>
import { ref } from 'vue'
import { batchUploadFiles } from '../api/fileUpload'
const uploadProgress = ref(0)
async function onFilesAdded(files) {
// 创建FormData
const formData = new FormData()
// 添加文件
files.forEach(file => {
formData.append('files', file)
})
// 添加额外参数
formData.append('category', 'document')
// 执行上传
const result = await batchUploadFiles(formData, {
onProgress: (e) => {
if (e.total > 0) {
uploadProgress.value = Math.round((e.loaded * 100) / e.total)
}
},
onTooLarge: () => {
// 处理文件过大情况
this.$q.dialog({
title: '文件过大',
message: '单个文件不能超过20MB,请压缩后重试',
ok: '确定'
})
}
})
// 重置进度
uploadProgress.value = 0
// 处理上传结果
if (result.success) {
this.$q.notify({
type: 'positive',
message: `成功上传${result.data.data.length}个文件`
})
// 刷新文件列表
this.$emit('upload-complete', result.data.data)
}
}
</script>
四、错误处理最佳实践总结
4.1 后端开发规范
1.** 异常抛出原则 **- 数据验证使用ValidationError
- 请求格式错误使用ParseError
- 业务规则错误使用自定义异常类
- 系统异常使用APIException(隐藏内部细节)
2.** 日志记录要求 **- 所有异常必须记录exc_info=True(生产环境)
- 包含用户ID、IP、请求路径等上下文信息
- 生成唯一request_id便于问题追踪
- 敏感操作需记录详细审计日志
3.** 安全注意事项 **- 禁止向客户端返回堆栈信息
- 错误信息避免泄露系统实现细节
- 对敏感操作错误使用模糊提示(如"用户名或密码错误"而非"用户名不存在")
4.2 前后端协作规范
1.** 响应格式标准 **```json
{
"error": "简短错误描述", // 必选,用于用户展示
"detail": "详细错误信息", // 可选,用于问题排查
"request_id": "唯一请求ID", // 可选,用于日志追踪
"fields": { // 可选,字段验证错误
"username": ["用户名已存在"]
}
}
2.** HTTP状态码使用规范 **- 400: 请求错误(参数错误、验证失败)
- 401: 认证失败(未登录、Token过期)
- 403: 权限不足(禁止访问)
- 404: 资源不存在
- 409: 资源冲突(如重复创建)
- 413: 请求实体过大(文件太大)
- 415: 不支持的媒体类型(错误的Content-Type)
- 500: 服务器内部错误(后端代码异常)
3.** 错误处理流程 **- 后端负责错误标准化与日志记录
-前端负责错误展示与用户引导
- 关键业务错误需实现错误恢复机制(如重试、备用方案)
### 4.3 常见问题解决方案
| 问题场景 | 解决方案 | 代码示例 |
|----------|----------|----------|
| 文件上传过大 | 前端预检查+后端验证+Nginx配置 | `client_max_body_size 200M;`(Nginx) |
| 网络超时 | 实现重试机制+超时提示 | `axios-retry`插件配置 |
| 并发冲突 | 使用乐观锁+重试逻辑 | `version`字段+`catch (IntegrityError)` |
| 批量操作部分失败 | 实现事务回滚或部分提交机制 | `transaction.atomic()`+保存点 |
| 第三方服务依赖失败 | 熔断器模式+降级处理 | 使用`tenacity`实现重试+备用数据源 |
通过上述规范实现的错误处理机制,可确保前后端错误信息传递的一致性、安全性和可维护性,同时提供良好的用户体验和高效的问题排查能力。
浙公网安备 33010602011771号