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

错误响应对比:优化前 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 格式,或者不传此字段"
                                }
                            ]
                        }
                    }
                }
            }
        }
    }
)