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.
 

4.7 KiB

错误处理优化说明

📋 优化目标

  1. 提升安全性:不暴露内部技术实现细节
  2. 改善用户体验:提供清晰、友好的错误提示
  3. 保持可调试性:开发环境下保留详细日志

⚠️ 优化前的问题

暴露的敏感信息

{
  "success": false,
  "code": 422,
  "message": "body -> folderId: Input should be a valid UUID...",
  "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:`..."
        },
        "url": "https://errors.pydantic.dev/2.12/v/uuid_parsing"  //  暴露框架版本
      }
    ]
  }
}

存在的安全隐患

  • ✖️ 暴露后端使用 Pydantic 2.12
  • ✖️ 暴露内部验证逻辑和规则
  • ✖️ 技术术语对普通用户不友好
  • ✖️ 提供了探测系统的信息

优化后的响应

友好的错误格式

{
  "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"
}

优势

  • 隐藏了技术实现细节
  • 提供清晰的字段名和错误说明
  • 给出了解决建议("或者不传此字段")
  • 只返回必要的业务信息
  • 更符合 RESTful API 最佳实践

🔒 错误类型映射表

Pydantic 错误类型 用户友好消息
uuid_parsing 必须是有效的 UUID 格式,或者不传此字段
greater_than 必须大于 {值},或者不传此字段
string_too_short 长度不能少于 {min} 个字符
string_too_long 长度不能超过 {max} 个字符
missing 此字段为必填项
string_pattern_mismatch 格式不正确
value_error 值不合法

🛠️ 开发调试

在开发环境中(DEBUG=True),详细的验证错误会记录在日志中:

WARNING - 验证错误详情: [{'type': 'uuid_parsing', 'loc': ('body', 'folderId'), ...}]

这样既保证了生产环境的安全性,又方便开发人员调试。

📝 使用建议

前端错误处理

try {
  const response = await createProject(data);
} catch (error) {
  if (error.code === 422 && error.data?.errors) {
    // 显示友好的错误提示
    error.data.errors.forEach(err => {
      showFieldError(err.field, err.message);
    });
  } else {
    showGeneralError(error.message);
  }
}

字段级错误提示示例

📍 folderId: 必须是有效的 UUID 格式,或者不传此字段
📍 plannedDuration: 必须大于 0,或者不传此字段

🔐 安全最佳实践

  1. 永远不要返回

    • 内部错误堆栈信息
    • 数据库错误详情
    • 框架版本和类型
    • 文件路径和系统信息
  2. 应该返回

    • 用户可操作的错误信息
    • 字段名称和位置
    • 验证规则的简要说明
    • 修复建议
  3. 分环境处理

    • 生产环境:简洁、安全的错误消息
    • 开发环境:详细的调试信息(仅在日志中)
    • 测试环境:保留适度的详细信息

📊 错误响应示例对比

单字段错误

// 优化后
{
  "success": false,
  "code": 422,
  "message": "folderId: 必须是有效的 UUID 格式,或者不传此字段",
  "data": {
    "errors": [
      {
        "field": "folderId",
        "message": "必须是有效的 UUID 格式,或者不传此字段"
      }
    ]
  },
  "timestamp": "2026-02-05T08:00:04.953349+00:00"
}

多字段错误

// 优化后
{
  "success": false,
  "code": 422,
  "message": "参数验证失败,共 3 个错误",
  "data": {
    "errors": [
      {
        "field": "name",
        "message": "此字段为必填项"
      },
      {
        "field": "folderId",
        "message": "必须是有效的 UUID 格式,或者不传此字段"
      },
      {
        "field": "plannedDuration",
        "message": "必须大于 0,或者不传此字段"
      }
    ]
  },
  "timestamp": "2026-02-05T08:00:04.953349+00:00"
}

🎯 总结

通过这次优化:

  • 提升了 API 的安全性
  • 改善了用户体验
  • 保持了开发调试的便利性
  • 符合生产环境的最佳实践