You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
8.1 KiB
8.1 KiB
错误响应对比:优化前 vs 优化后
场景 1: UUID 格式错误
❌ 优化前(暴露技术细节)
{
"success": false,
"code": 422,
"message": "body -> folderId: Input should be a valid UUID, invalid character: expected an optional prefix of `urn:uuid:` followed by [0-9a-fA-F-], found `v` at 1",
"data": {
"errors": [
{
"type": "uuid_parsing", // ⚠️ 暴露 Pydantic 内部类型
"loc": ["body", "folderId"],
"msg": "Input should be a valid UUID, invalid character: expected an optional prefix of `urn:uuid:` followed by [0-9a-fA-F-], found `v` at 1",
"input": "virtual-mine",
"ctx": { // ⚠️ 暴露验证上下文
"error": "invalid character: expected an optional prefix of `urn:uuid:` followed by [0-9a-fA-F-], found `v` at 1"
},
"url": "https://errors.pydantic.dev/2.12/v/uuid_parsing" // ⚠️ 暴露框架版本
}
]
},
"timestamp": "2026-02-05T08:00:04.953349+00:00"
}
问题点:
- 🔴 暴露使用 Pydantic 2.12
- 🔴 技术术语
uuid_parsing对用户无意义 - 🔴 错误消息过于技术化
- 🔴 提供了 Pydantic 文档链接(不必要)
✅ 优化后(用户友好)
{
"success": false,
"code": 422,
"message": "folderId: 必须是有效的 UUID 格式,或者不传此字段",
"data": {
"errors": [
{
"field": "folderId",
"message": "必须是有效的 UUID 格式,或者不传此字段"
}
]
},
"timestamp": "2026-02-05T08:00:04.953349+00:00"
}
改进点:
- ✅ 隐藏了技术实现细节
- ✅ 清晰的字段名
- ✅ 易懂的错误说明
- ✅ 提供了解决建议
场景 2: 数值范围错误
❌ 优化前
{
"success": false,
"code": 422,
"message": "body -> plannedDuration: Input should be greater than 0",
"data": {
"errors": [
{
"type": "greater_than", // ⚠️ 技术术语
"loc": ["body", "plannedDuration"],
"msg": "Input should be greater than 0",
"input": 0,
"ctx": { // ⚠️ 暴露验证规则
"gt": 0
},
"url": "https://errors.pydantic.dev/2.12/v/greater_than"
}
]
},
"timestamp": "2026-02-05T08:00:04.953349+00:00"
}
✅ 优化后
{
"success": false,
"code": 422,
"message": "plannedDuration: 必须大于 0,或者不传此字段",
"data": {
"errors": [
{
"field": "plannedDuration",
"message": "必须大于 0,或者不传此字段"
}
]
},
"timestamp": "2026-02-05T08:00:04.953349+00:00"
}
场景 3: 多个字段错误
❌ 优化前
{
"success": false,
"code": 422,
"message": "body -> folderId: Input should be a valid UUID...",
"data": {
"errors": [
{
"type": "uuid_parsing",
"loc": ["body", "folderId"],
"msg": "Input should be a valid UUID, invalid character...",
"input": "virtual-mine",
"ctx": { "error": "..." },
"url": "https://errors.pydantic.dev/2.12/v/uuid_parsing"
},
{
"type": "greater_than",
"loc": ["body", "plannedDuration"],
"msg": "Input should be greater than 0",
"input": 0,
"ctx": { "gt": 0 },
"url": "https://errors.pydantic.dev/2.12/v/greater_than"
}
]
},
"timestamp": "2026-02-05T08:00:04.953349+00:00"
}
问题:
- 每个错误都包含大量技术细节
- 用户需要自己理解并转换这些信息
- 增加了响应体积
✅ 优化后
{
"success": false,
"code": 422,
"message": "参数验证失败,共 2 个错误",
"data": {
"errors": [
{
"field": "folderId",
"message": "必须是有效的 UUID 格式,或者不传此字段"
},
{
"field": "plannedDuration",
"message": "必须大于 0,或者不传此字段"
}
]
},
"timestamp": "2026-02-05T08:00:04.953349+00:00"
}
改进:
- 简洁的错误列表
- 统一的消息格式
- 减少了响应体积
- 更容易在前端展示
场景 4: 字符串长度错误
✅ 优化后示例
长度过短
{
"success": false,
"code": 422,
"message": "name: 长度不能少于 1 个字符",
"data": {
"errors": [
{
"field": "name",
"message": "长度不能少于 1 个字符"
}
]
}
}
长度过长
{
"success": false,
"code": 422,
"message": "name: 长度不能超过 255 个字符",
"data": {
"errors": [
{
"field": "name",
"message": "长度不能超过 255 个字符"
}
]
}
}
场景 5: 必填字段缺失
✅ 优化后
{
"success": false,
"code": 422,
"message": "name: 此字段为必填项",
"data": {
"errors": [
{
"field": "name",
"message": "此字段为必填项"
}
]
}
}
前端集成示例
Vue 3 + Element Plus
<template>
<el-form ref="formRef" :model="form" :rules="rules">
<el-form-item label="项目名称" prop="name">
<el-input v-model="form.name" />
</el-form-item>
<el-form-item label="文件夹" prop="folderId">
<el-select v-model="form.folderId">
<el-option label="我的项目" :value="null" />
<el-option
v-for="folder in folders"
:key="folder.id"
:label="folder.name"
:value="folder.id"
/>
</el-select>
</el-form-item>
<el-form-item label="计划时长" prop="plannedDuration">
<el-input-number v-model="form.plannedDuration" :min="1" />
</el-form-item>
<el-button @click="handleSubmit">创建</el-button>
</el-form>
</template>
<script setup>
import { ref } from 'vue';
import { ElMessage } from 'element-plus';
const formRef = ref();
const form = ref({
name: '',
folderId: null,
plannedDuration: null
});
const handleSubmit = async () => {
try {
await createProject(form.value);
ElMessage.success('创建成功');
} catch (error) {
if (error.code === 422 && error.data?.errors) {
// 显示字段级错误
error.data.errors.forEach(err => {
ElMessage.error(`${err.field}: ${err.message}`);
});
} else {
ElMessage.error(error.message || '创建失败');
}
}
};
</script>
对比总结
| 维度 | 优化前 | 优化后 |
|---|---|---|
| 安全性 | ⚠️ 暴露技术栈和版本 | ✅ 隐藏实现细节 |
| 用户体验 | ⚠️ 技术术语难懂 | ✅ 清晰易懂的提示 |
| 响应大小 | ⚠️ 包含大量冗余信息 | ✅ 精简高效 |
| 前端集成 | ⚠️ 需要额外处理 | ✅ 直接使用 |
| 调试能力 | ✅ 详细信息 | ✅ 日志中保留详情 |
| 可维护性 | ⚠️ 依赖框架输出 | ✅ 统一可控 |
额外建议
1. 生产环境配置
# .env.production
DEBUG=false
ENVIRONMENT=production
2. 错误监控
# 在异常处理器中添加错误监控
import sentry_sdk
if settings.ENVIRONMENT == "production":
logger.error(f"验证错误: {exc.errors()}")
sentry_sdk.capture_exception(exc)
3. API 文档
在 Swagger/OpenAPI 文档中说明错误响应格式:
@router.post(
"",
responses={
422: {
"description": "参数验证失败",
"content": {
"application/json": {
"example": {
"success": False,
"code": 422,
"message": "folderId: 必须是有效的 UUID 格式",
"data": {
"errors": [
{
"field": "folderId",
"message": "必须是有效的 UUID 格式,或者不传此字段"
}
]
}
}
}
}
}
}
)