eagleye

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`实现重试+备用数据源 |

通过上述规范实现的错误处理机制,可确保前后端错误信息传递的一致性、安全性和可维护性,同时提供良好的用户体验和高效的问题排查能力。

 

posted on 2025-08-26 12:42  GoGrid  阅读(12)  评论(0)    收藏  举报

导航