【HTTP】短链接服务错误码规范
短链接服务错误码规范
基于 RFC 7807: Problem Details for HTTP APIs
RFC 7807 是 W3C 标准,定义了一种在 HTTP API 中携带机器可读错误详情的格式。本规范基于该标准,结合短链接服务特点进行了扩展。
目录
- 一、规范依据
- 二、错误码概述
- 三、RFC 7807 核心字段
- 四、握手阶段错误码(1xxx)
- 五、结果解析阶段错误码(2xxx)
- 六、自定义业务错误码(3xxx)
- 七、系统级错误码(5xxx)
- 八、错误响应格式
- 九、使用示例
- 附录:完整错误码列表
一、规范依据
1.1 RFC 7807 标准(小白话版)
本规范遵循 RFC 7807: Problem Details for HTTP APIs 标准。
什么是 RFC 7807?
想象一下:
❌ 旧方式:每个API返回的错误格式都不一样
{"error": "Invalid key"}
{"msg": "API Key错误"}
{"code": 401, "info": "认证失败"}
→ 客户端处理起来很麻烦
✅ RFC 7807:大家约定一套统一的错误返回格式
{"type": "...", "title": "...", "status": 401, "detail": "..."}
→ 客户端写一次代码,就能处理所有符合标准的API
RFC 7807 的好处(大白话):
1️⃣ 机器好读:type 字段像身份证号,程序一看到就知道是什么错误
2️⃣ 人也好读:title 给人看,detail 给开发者看
3️⃣ 全世界统一:谁都用这套格式,不搞"方言"
4️⃣ 可以加料:标准字段不够用?自己加扩展字段
5️⃣ 支持多语言:title 和 detail 可以返回中文、英文等
1.2 调整说明(对比表:原来 vs 现在)
| 你原来用的 | RFC 7807 规定 | 为什么要改?(大白话) |
|---|---|---|
code: 1001 |
type: "https://..." |
数字不够直观,用网址(URI)可以点开看详细文档 |
message: "..." |
title: "..." |
国际标准用 title,更统一 |
description: "..." |
detail: "..." |
国际标准用 detail,更统一 |
| 没有状态码 | status: 401 必须有 |
HTTP 本身有状态码,响应里再写一次,防止不一致 |
request_id: "..." |
instance: "https://..." |
用 URI 格式,可以指向日志链接 |
success: false |
看状态码就行 | 状态码是 2xx 就是成功,不需要单独字段 |
1.3 HTTP 状态码映射(小白话版)
什么是 HTTP 状态码?
当你访问网站,服务器会返回一个3位数字:
- 200: 成功 ✅
- 301: 搬家了,去新地址 🏠
- 401: 没带身份证,不让进 🚫
- 404: 找不到这个页面 ❓
- 500: 服务器自己挂了 💥
我们的错误码怎么对应状态码:
| 你的错误码 | 类似什么 | HTTP 状态码 | 举个例子 |
|---|---|---|---|
| 1xxx (握手错误) | 没带身份证/门禁卡 | 401 / 403 | API Key 不对 = 401 |
| 2xxx (解析错误) | 你填的表单有问题 | 400 / 422 | URL 格式错 = 400 |
| 3xxx (业务错误) | 找不到东西/冲突了 | 404 / 409 | 短链不存在 = 404 |
| 5xxx (系统错误) | 服务器出问题了 | 500 / 503 | 数据库挂了 = 500 |
为什么要对应?
因为 HTTP 协议本身就规定好了这些状态码,我们乖乖遵守就好,
这样别人一看状态码就知道大概是什么问题。
二、错误码概述
错误码结构
错误码格式:1XXX / 2XXX / 3XXX / 5XXX
数字含义:
- 1xxx:握手阶段错误(认证、授权、请求验证)
- 2xxx:结果解析阶段错误(URL验证、DNS解析、连接)
- 3xxx:自定义业务错误(短链业务逻辑)
- 5xxx:系统级错误(服务器、数据库、第三方服务)
注:4xxx 预留给未来的客户端错误扩展
双重标识体系
本规范采用 RFC 7807 type URI + 内部错误码 的双重标识:
┌─────────────────────────────────────────────────────────┐
│ RFC 7807 标准标识 │
├─────────────────────────────────────────────────────────┤
│ type: "https://api.example.com/errors/missing-api-key" │
│ → 符合 RFC 7807,机器可读,全球唯一 │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ 内部错误码标识(扩展字段) │
├─────────────────────────────────────────────────────────┤
│ errorCode: 1001 │
│ → 内部使用,便于日志分析和监控 │
└─────────────────────────────────────────────────────────┘
设计依据:
- RFC 7807 §4:允许在标准字段之外添加扩展字段
- type URI 确保跨服务的唯一性和标准化
- 内部错误码便于系统内部快速分类和处理
三、RFC 7807 核心字段(大白话解析版)
RFC 7807 规定了几个必须/可选的字段,用大白话解释:
3.1 type(身份证)- 必须
{
"type": "https://api.example.com/errors/missing-api-key"
}
小白话解释:
就像身份证号,唯一标识这是什么类型的错误。
为什么要用网址(URI)?
→ 理论上你可以点开这个链接,跳转到错误说明文档
→ 比如 https://api.example.com/errors/missing-api-key 可以打开网页看详细说明
格式:https://你的域名/errors/错误名称(全小写,用短横线分隔)
官方规定:
- 必须:一个 URI 引用(就是网址格式)
- 用途:唯一标识这个错误类型
- 如果没啥文档可写,可以用
about:blank表示"无额外信息"
3.2 title(标题)- 必须
{
"title": "API密钥缺失"
}
小白话解释:
给人看的简短标题,就像新闻标题一样。
特点:
- 简短:不超过一行字
- 稳定:同样的错误永远返回同样的标题
- 友好:让用户一眼就知道出啥事了
官方规定:
- 必须:简短的人类可读标题
- 建议:不应该随每次请求变化(保持稳定)
3.3 status(状态码)- 必须
{
"status": 401
}
小白话解释:
HTTP 状态码,就是那个 200、404、500 之类的三位数。
为什么要重复写?
→ HTTP 响应头里已经有了状态码(比如 HTTP/1.1 401 Unauthorized)
→ 但在 JSON 里再写一次,方便客户端代码处理
重要:这个数字必须和 HTTP 响应的状态码一致!
不能:HTTP 是 200,JSON 里写 401 → 这样会搞混
官方规定:
- 必须:HTTP 状态码
- 约束:必须和 HTTP 响应的状态码匹配
3.4 detail(详细说明)- 可选
{
"detail": "请求头中缺少 X-API-Key 字段,请在请求中包含有效的 API 密钥"
}
小白话解释:
给开发者看的详细说明,比 title 更具体。
区别:
- title: "API密钥缺失" → 给普通用户看
- detail: "请求头中缺少 X-API-Key 字段..." → 给开发者看,告诉他怎么修
可以包含具体信息:
- 是哪个字段错了?
- 正确的格式是什么?
- 怎么修复这个问题?
官方规定:
- 可选:详细的人类可读描述
- 可以包含具体实例的信息
3.5 instance(本次唯一标识)- 可选
{
"instance": "https://api.example.com/errors/req_abc123"
}
小白话解释:
这次错误的具体"身份证号"。
用途:
→ 用户报错时,把这个 ID 发给客服
→ 客服在后台一搜,就能找到这次请求的完整日志
→ 方便排查问题
格式:通常用请求 ID (request_id) 组成的 URI
官方规定:
- 可选:URI 引用
- 用途:标识这个错误的具体实例
3.6 扩展字段(自己加的)- 随便加
{
"errorCode": 1001,
"timestamp": "2024-01-01T12:00:00Z",
"path": "/api/v1/shorten",
"retry_after": 60
}
小白话解释:
RFC 7807 规定的字段不够用?自己加!
我们加的字段:
├── errorCode: 1001
│ └→ 内部用的数字错误码,方便日志统计和监控
│
├── timestamp: "2024-01-01T12:00:00Z"
│ └→ 错误发生时间(标准格式:ISO 8601)
│
├── path: "/api/v1/shorten"
│ └→ 请求的路径
│
└── retry_after: 60
└→ 多少秒后可以重试(针对频率限制等错误)
官方规定:
- RFC 7807 §4:允许添加扩展字段
- 建议:用驼峰命名法(camelCase)
总结:一个完整的错误响应长这样
{
"type": "https://api.example.com/errors/missing-api-key", // ← 错误类型(身份证)
"title": "API密钥缺失", // ← 简短标题(给用户看)
"status": 401, // ← HTTP 状态码(必须匹配)
"detail": "请求头中缺少 X-API-Key 字段,请在请求中包含有效的 API 密钥", // ← 详细说明(给开发者看)
"instance": "https://api.example.com/errors/req_abc123", // ← 本次唯一标识(追踪用)
"errorCode": 1001, // ← 我们自己加的:数字错误码
"timestamp": "2024-01-01T12:00:00Z", // ← 我们自己加的:时间
"path": "/api/v1/shorten" // ← 我们自己加的:路径
}
哪些是 RFC 7807 规定的?
- ✅ type, title, status, detail, instance
哪些是我们自己扩展的?
- 🔧 errorCode, timestamp, path, retry_after 等
四、握手阶段错误码(1xxx)
这是什么阶段?
就像进小区要过门禁:
1️⃣ 先检查你有没有门禁卡(API Key)
2️⃣ 再检查门禁卡是不是过期了
3️⃣ 然后看你是不是被拉黑了
4️⃣ 最后看你有没有权限进这个区域
对应的 HTTP 状态码:
401 Unauthorized - 没带身份证/门禁卡
403 Forbidden - 带了但不让进
429 Too Many Requests - 访问太频繁,限流了
握手阶段错误码详细说明
握手阶段包括:
- 身份验证(API Key、Token)
- 请求合法性检查
- 基本参数验证
- 请求格式验证
对应的 HTTP 状态码:401 Unauthorized, 403 Forbidden, 429 Too Many Requests
握手阶段错误码 → HTTP 状态码对照表
| 错误码 | 错误名称 | HTTP 状态码 | 用大白话解释 |
|---|---|---|---|
| 1001 | API密钥缺失 | 401 | 就像没带门禁卡 |
| 1002 | API密钥无效 | 401 | 门禁卡是假的或过期了 |
| 1003 | 签名验证失败 | 401 | 签名对不上 |
| 1004 | Token无效 | 401 | 临时通行证过期了 |
| 1005 | 权限不足 | 403 | 有门禁卡但不能进这个区域 |
| 1006 | 请求频率超限 | 429 | 刷太快了,被限流 |
| 1007 | IP被封禁 | 403 | 这个人被拉黑了 |
| 1008 | 请求方法不支持 | 405 | 用错方法了(比如只能 GET 你用了 POST) |
| 1009 | Content-Type不支持 | 415 | 发的数据格式不对 |
| 1010 | 请求体过大 | 413 | 数据太多了,吃不消 |
1001: API密钥缺失 (missing-api-key)
{
"type": "https://api.example.com/errors/missing-api-key",
"title": "API密钥缺失",
"status": 401,
"detail": "请求头中缺少 X-API-Key 字段,请在请求中包含有效的 API 密钥",
"instance": "https://api.example.com/errors/req_abc123",
"errorCode": 1001,
"timestamp": "2024-01-01T12:00:00Z",
"path": "/api/v1/shorten"
}
RFC 7807 映射说明:
| RFC 7807 字段 | 值 | 说明 |
|---|---|---|
type |
https://api.example.com/errors/missing-api-key |
唯一标识此错误类型,可解析为文档 |
title |
API密钥缺失 |
人类可读的简短标题 |
status |
401 |
对应 HTTP 401 Unauthorized |
detail |
请求头中缺少... |
详细描述和解决建议 |
instance |
.../req_abc123 |
此次请求的唯一标识 |
HTTP 状态码映射依据: RFC 7235 §3.1 - 401 Unauthorized 用于表示缺少有效认证凭据
场景:
- 请求头中没有
X-API-Key - API Key 参数为空
1002: API密钥无效
{
"type": "https://api.example.com/errors/invalid-api-key",
"title": "API密钥无效",
"status": 401,
"detail": "提供的API密钥不存在、已被禁用或已过期",
"instance": "https://api.example.com/errors/req_abc123",
"errorCode": 1002,
"timestamp": "2024-01-01T12:00:00Z",
"path": "/api/v1/shorten"
}
HTTP 状态码映射依据: RFC 7235 §3.1 - 401 Unauthorized
大白话:就像你到小区门口,保安说"请出示门禁卡"
但你没带,那就返回 401,不让你进
- API Key 不在数据库中
- API Key 已被禁用
- API Key 已过期
1003: 签名验证失败
{
"type": "https://api.example.com/errors/signature-verification-failed",
"title": "签名验证失败",
"status": 401,
"detail": "请求签名验证不通过,可能原因:签名算法不匹配、签名过期或签名计算错误",
"instance": "https://api.example.com/errors/req_abc123",
"errorCode": 1003,
"timestamp": "2024-01-01T12:00:00Z",
"path": "/api/v1/shorten"
}
HTTP 状态码映射依据: RFC 7235 §3.1 - 401 Unauthorized
大白话:就像你到小区门口,保安说"请出示门禁卡"
但你没带,那就返回 401,不让你进
- 签名算法不匹配
- 签名过期
- 签名计算错误
1004: Token无效
{
"type": "https://api.example.com/errors/invalid-token",
"title": "访问令牌无效",
"status": 401,
"detail": "提供的访问令牌无效或已过期",
"instance": "https://api.example.com/errors/req_abc123",
"errorCode": 1004,
"timestamp": "2024-01-01T12:00:00Z",
"path": "/api/v1/shorten"
}
HTTP 状态码映射依据: RFC 7235 §3.1 - 401 Unauthorized
大白话:就像你到小区门口,保安说"请出示门禁卡"
但你没带,那就返回 401,不让你进
- JWT Token 过期
- JWT Token 签名无效
- Bearer Token 不存在
1005: 权限不足
{
"type": "https://api.example.com/errors/insufficient-permissions",
"title": "权限不足",
"status": 403,
"detail": "当前API密钥没有执行此操作的权限,或已超出配额限制",
"instance": "https://api.example.com/errors/req_abc123",
"errorCode": 1005,
"timestamp": "2024-01-01T12:00:00Z",
"path": "/api/v1/shorten"
}
HTTP 状态码映射依据: RFC 7231 §6.5.3 - 403 Forbidden(已认证但权限不足)
场景:
- 尝试访问需要更高权限的接口
- 超出配额限制
1006: 请求频率超限
{
"type": "https://api.example.com/errors/rate-limit-exceeded",
"title": "请求频率超限",
"status": 429,
"detail": "请求频率超过限制,请在 60 秒后重试",
"instance": "https://api.example.com/errors/req_abc123",
"errorCode": 1006,
"timestamp": "2024-01-01T12:00:00Z",
"path": "/api/v1/shorten",
"retry_after": 60
}
HTTP 状态码映射依据: RFC 6585 §4 - 429 Too Many Requests
扩展字段说明:
retry_after: 符合 RFC 7231 §7.1.3 Retry-After header,表示多少秒后可以重试
场景:
- 短时间内请求过多
- 超过API调用配额
1007: IP地址被封禁
{
"type": "https://api.example.com/errors/ip-banned",
"title": "IP地址被封禁",
"status": 403,
"detail": "您的IP地址已被封禁,封禁至 2024-01-02T00:00:00Z",
"instance": "https://api.example.com/errors/req_abc123",
"errorCode": 1007,
"timestamp": "2024-01-01T12:00:00Z",
"path": "/api/v1/shorten",
"ban_until": "2024-01-02T00:00:00Z"
}
HTTP 状态码映射依据: RFC 7231 §6.5.3 - 403 Forbidden
扩展字段说明:
ban_until: 封禁到期时间(ISO 8601 格式)
场景:
- 恶意请求
- 异常行为
- 违反服务条款
1008: 请求方法不支持
{
"type": "https://api.example.com/errors/method-not-allowed",
"title": "请求方法不支持",
"status": 405,
"detail": "该接口不支持此请求方法,允许的方法:GET, POST",
"instance": "https://api.example.com/errors/req_abc123",
"errorCode": 1008,
"timestamp": "2024-01-01T12:00:00Z",
"path": "/api/v1/shorten",
"allowed_methods": ["GET", "POST"]
}
HTTP 状态码映射依据: RFC 7231 §6.5.5 - 405 Method Not Allowed
扩展字段说明:
allowed_methods: 符合 RFC 7231 §7.1.1 Allow header,列出允许的方法
场景:
- 用 GET 访问需要 POST 的接口
- 用 POST 访问只支持 GET 的接口
1009: Content-Type不支持
{
"type": "https://api.example.com/errors/unsupported-media-type",
"title": "内容类型不支持",
"status": 415,
"detail": "不支持的内容类型,支持的类型:application/json, application/x-www-form-urlencoded",
"instance": "https://api.example.com/errors/req_abc123",
"errorCode": 1009,
"timestamp": "2024-01-01T12:00:00Z",
"path": "/api/v1/shorten",
"supported_types": ["application/json", "application/x-www-form-urlencoded"]
}
HTTP 状态码映射依据: RFC 7231 §6.5.13 - 415 Unsupported Media Type
扩展字段说明:
supported_types: 列出支持的内容类型
场景:
- 发送了 XML 但只支持 JSON
- 缺少必要的 Content-Type 头
1010: 请求体过大
{
"type": "https://api.example.com/errors/payload-too-large",
"title": "请求体过大",
"status": 413,
"detail": "请求体超过最大限制,最大允许 1048576 字节,实际为 2097152 字节",
"instance": "https://api.example.com/errors/req_abc123",
"errorCode": 1010,
"timestamp": "2024-01-01T12:00:00Z",
"path": "/api/v1/shorten",
"max_size": 1048576,
"actual_size": 2097152
}
HTTP 状态码映射依据: RFC 7231 §6.5.11 - 413 Payload Too Large
扩展字段说明:
max_size: 最大允许大小(字节)actual_size: 实际大小(字节)
场景:
- URL 或参数过长
- POST 请求体超过限制
1003: 签名验证失败
{
"code": 1003,
"message": "签名验证失败",
"error": "SIGNATURE_VERIFICATION_FAILED",
"description": "请求签名验证不通过"
}
场景:
- 签名算法不匹配
- 签名过期
- 签名计算错误
1004: Token无效
{
"code": 1004,
"message": "访问令牌无效",
"error": "INVALID_TOKEN",
"description": "提供的访问令牌无效或已过期"
}
场景:
- JWT Token 过期
- JWT Token 签名无效
- Bearer Token 不存在
1005: 权限不足
{
"code": 1005,
"message": "权限不足",
"error": "INSUFFICIENT_PERMISSIONS",
"description": "当前API密钥没有执行此操作的权限"
}
场景:
- 尝试访问需要更高权限的接口
- 超出配额限制
1006: 请求频率超限
{
"code": 1006,
"message": "请求频率超限",
"error": "RATE_LIMIT_EXCEEDED",
"description": "请求频率超过限制",
"retry_after": 60
}
场景:
- 短时间内请求过多
- 超过API调用配额
额外字段:
retry_after: 多少秒后可以重试
1007: IP地址被封禁 (ip-banned)
{
"type": "https://api.example.com/errors/ip-banned",
"title": "IP地址被封禁",
"status": 403,
"detail": "您的IP地址已被封禁,封禁至 2024-01-02T00:00:00Z",
"instance": "https://api.example.com/errors/req_abc123",
"errorCode": 1007,
"timestamp": "2024-01-01T12:00:00Z",
"path": "/api/v1/shorten",
"ban_until": "2024-01-02T00:00:00Z"
}
HTTP 状态码映射依据: RFC 7231 §6.5.3 - 403 Forbidden
大白话:你被拉黑了,就像商场门口贴着"此人不得入内"
场景:
- 恶意请求
- 异常行为
- 违反服务条款
1008: 请求方法不支持 (method-not-allowed)
{
"type": "https://api.example.com/errors/method-not-allowed",
"title": "请求方法不支持",
"status": 405,
"detail": "该接口不支持此请求方法,允许的方法:GET, POST",
"instance": "https://api.example.com/errors/req_abc123",
"errorCode": 1008,
"timestamp": "2024-01-01T12:00:00Z",
"path": "/api/v1/shorten",
"allowed_methods": ["GET", "POST"]
}
HTTP 状态码映射依据: RFC 7231 §6.5.5 - 405 Method Not Allowed
大白话:就像单向车道,你逆行了
场景:
- 用 GET 访问需要 POST 的接口
- 用 POST 访问只支持 GET 的接口
1009: Content-Type不支持 (unsupported-media-type)
{
"type": "https://api.example.com/errors/unsupported-media-type",
"title": "内容类型不支持",
"status": 415,
"detail": "不支持的内容类型,支持的类型:application/json, application/x-www-form-urlencoded",
"instance": "https://api.example.com/errors/req_abc123",
"errorCode": 1009,
"timestamp": "2024-01-01T12:00:00Z",
"path": "/api/v1/shorten",
"supported_types": ["application/json", "application/x-www-form-urlencoded"]
}
HTTP 状态码映射依据: RFC 7231 §6.5.13 - 415 Unsupported Media Type
大白话:就像你寄快递,包装不符合要求
场景:
- 发送了 XML 但只支持 JSON
- 缺少必要的 Content-Type 头
1010: 请求体过大 (payload-too-large)
{
"type": "https://api.example.com/errors/payload-too-large",
"title": "请求体过大",
"status": 413,
"detail": "请求体超过最大限制,最大允许 1048576 字节,实际为 2097152 字节",
"instance": "https://api.example.com/errors/req_abc123",
"errorCode": 1010,
"timestamp": "2024-01-01T12:00:00Z",
"path": "/api/v1/shorten",
"max_size": 1048576,
"actual_size": 2097152
}
HTTP 状态码映射依据: RFC 7231 §6.5.11 - 413 Payload Too Large
大白话:就像搬家时东西太多,卡车装不下
场景:
- URL 或参数过长
- POST 请求体超过限制
三、结果解析阶段错误码(2xxx)
结果解析阶段说明(小白话版)
这个阶段就像快递员送快递:
1️⃣ 先看地址对不对(URL格式检查)
2️⃣ 再查这个地址存不存在(DNS解析)
3️⃣ 然后过去送(建立HTTP连接)
4️⃣ 最后看收件人在不在(检查HTTP响应)
对应的 HTTP 状态码:
400 Bad Request - 你给的东西有问题
408 Request Timeout - 等太久超时了
422 Unprocessable Entity - 格式对了但内容有问题
2001: URL参数缺失 (missing-url-parameter)
{
"type": "https://api.example.com/errors/missing-url-parameter",
"title": "URL参数缺失",
"status": 400,
"detail": "请求中缺少必需的URL参数,请提供原始URL",
"instance": "https://api.example.com/errors/req_abc123",
"errorCode": 2001,
"timestamp": "2024-01-01T12:00:00Z",
"path": "/api/v1/shorten"
}
HTTP 状态码映射依据: RFC 7231 §6.5.1 - 400 Bad Request
大白话:就像你去寄快递,但没有填收件地址
场景:
- 短链创建时没有提供原始URL
- URL 参数为空
2002: URL格式无效 (invalid-url-format)
{
"type": "https://api.example.com/errors/invalid-url-format",
"title": "URL格式无效",
"status": 400,
"detail": "提供的URL格式不正确,提供的URL: htp://invalid-url",
"instance": "https://api.example.com/errors/req_abc123",
"errorCode": 2002,
"timestamp": "2024-01-01T12:00:00Z",
"path": "/api/v1/shorten",
"provided_url": "htp://invalid-url"
}
HTTP 状态码映射依据: RFC 7231 §6.5.1 - 400 Bad Request
大白话:就像地址写错了,"北京市东城区"写成了"北京省东荒区"
场景:
- URL 格式错误(拼写错误)
- 不是有效的URL格式
- 缺少必要的URL部分
2003: URL协议不支持 (unsupported-url-protocol)
{
"type": "https://api.example.com/errors/unsupported-url-protocol",
"title": "URL协议不支持",
"status": 400,
"detail": "只支持 http 和 https 协议,提供的协议: ftp",
"instance": "https://api.example.com/errors/req_abc123",
"errorCode": 2003,
"timestamp": "2024-01-01T12:00:00Z",
"path": "/api/v1/shorten",
"provided_protocol": "ftp"
}
HTTP 状态码映射依据: RFC 7231 §6.5.1 - 400 Bad Request
大白话:就像快递公司只送国内快递,你非要寄到火星
场景:
- 使用了 ftp://、mailto:// 等协议
- 只支持 http:// 和 https://
2004: URL在黑名单中 (url-blocked)
{
"type": "https://api.example.com/errors/url-blocked",
"title": "URL在黑名单中",
"status": 403,
"detail": "提供的URL在服务黑名单中: https://example.com",
"instance": "https://api.example.com/errors/req_abc123",
"errorCode": 2004,
"timestamp": "2024-01-01T12:00:00Z",
"path": "/api/v1/shorten",
"blocked_url": "https://example.com"
}
HTTP 状态码映射依据: RFC 7231 §6.5.3 - 403 Forbidden
大白话:就像这个地址在快递黑名单上,不给送
场景:
- URL 被标记为恶意网站
- URL 包含违禁内容
- URL 域名被封禁
2005: DNS解析失败 (dns-resolution-failed)
{
"type": "https://api.example.com/errors/dns-resolution-failed",
"title": "DNS解析失败",
"status": 400,
"detail": "无法解析域名: example.com",
"instance": "https://api.example.com/errors/req_abc123",
"errorCode": 2005,
"timestamp": "2024-01-01T12:00:00Z",
"path": "/api/v1/shorten",
"domain": "example.com"
}
HTTP 状态码映射依据: RFC 7231 §6.5.1 - 400 Bad Request
大白话:就像导航找不到这个地址,根本不存在
场景:
- 域名不存在
- DNS服务器无响应
- 域名配置错误
2006: 连接超时 (connection-timeout)
{
"type": "https://api.example.com/errors/connection-timeout",
"title": "连接超时",
"status": 408,
"detail": "连接目标URL超时,超时时间: 10000毫秒",
"instance": "https://api.example.com/errors/req_abc123",
"errorCode": 2006,
"timestamp": "2024-01-01T12:00:00Z",
"path": "/api/v1/shorten",
"timeout": 10000,
"retry_after": 60
}
HTTP 状态码映射依据: RFC 7231 §6.5.7 - 408 Request Timeout
大白话:就像快递员去送件,等了10分钟都没人开门
场景:
- 目标服务器响应慢
- 网络问题
- 防火墙拦截
2007: 连接被拒绝 (connection-refused)
{
"type": "https://api.example.com/errors/connection-refused",
"title": "连接被拒绝",
"status": 400,
"detail": "目标服务器拒绝连接: example.com:443",
"instance": "https://api.example.com/errors/req_abc123",
"errorCode": 2007,
"timestamp": "2024-01-01T12:00:00Z",
"path": "/api/v1/shorten",
"target_host": "example.com",
"target_port": 443
}
HTTP 状态码映射依据: RFC 7231 §6.5.1 - 400 Bad Request
大白话:就像快递员去了,但对方说"我们不收快递!"
场景:
- 目标服务器关闭
- 端口被关闭
- 防火墙拦截
2008: SSL证书验证失败 (ssl-certificate-verify-failed)
{
"type": "https://api.example.com/errors/ssl-certificate-verify-failed",
"title": "SSL证书验证失败",
"status": 400,
"detail": "目标URL的SSL证书验证失败: example.com",
"instance": "https://api.example.com/errors/req_abc123",
"errorCode": 2008,
"timestamp": "2024-01-01T12:00:00Z",
"path": "/api/v1/shorten",
"domain": "example.com"
}
HTTP 状态码映射依据: RFC 7231 §6.5.1 - 400 Bad Request
大白话:就像对方的身份证是假的,不敢信
场景:
- SSL 证书过期
- SSL 证书域名不匹配
- SSL 证书不被信任
2009: 返回状态码异常 (abnormal-http-status)
{
"type": "https://api.example.com/errors/abnormal-http-status",
"title": "返回状态码异常",
"status": 400,
"detail": "目标URL返回异常状态码: 404",
"instance": "https://api.example.com/errors/req_abc123",
"errorCode": 2009,
"timestamp": "2024-01-01T12:00:00Z",
"path": "/api/v1/shorten",
"status_code": 404,
"target_url": "https://example.com/notfound"
}
HTTP 状态码映射依据: RFC 7231 §6.5.1 - 400 Bad Request
大白话:就像快递员送到了,但收件人说"查无此人"
场景:
- 目标URL返回4xx错误
- 目标URL返回5xx错误
- 页面不存在
2010: 响应内容为空 (empty-response)
{
"type": "https://api.example.com/errors/empty-response",
"title": "响应内容为空",
"status": 400,
"detail": "目标URL没有返回任何内容",
"instance": "https://api.example.com/errors/req_abc123",
"errorCode": 2010,
"timestamp": "2024-01-01T12:00:00Z",
"path": "/api/v1/shorten"
}
HTTP 状态码映射依据: RFC 7231 §6.5.1 - 400 Bad Request
大白话:就像快递员到了,但打开箱子一看,啥都没有
场景:
- 目标服务器返回空响应
- 连接中断
2011: 响应内容过大 (response-too-large)
{
"type": "https://api.example.com/errors/response-too-large",
"title": "响应内容过大",
"status": 400,
"detail": "目标URL返回的内容过大,最大允许 10485760 字节,实际为 20971520 字节",
"instance": "https://api.example.com/errors/req_abc123",
"errorCode": 2011,
"timestamp": "2024-01-01T12:00:00Z",
"path": "/api/v1/shorten",
"max_size": 10485760,
"actual_size": 20971520
}
HTTP 状态码映射依据: RFC 7231 §6.5.1 - 400 Bad Request
大白话:就像对方回了一本书回来,我们只收明信片
场景:
- 目标页面太大
- 超过处理限制
2012: 内容类型不支持 (unsupported-content-type)
{
"type": "https://api.example.com/errors/unsupported-content-type",
"title": "内容类型不支持",
"status": 400,
"detail": "目标URL返回的内容类型不支持: application/pdf",
"instance": "https://api.example.com/errors/req_abc123",
"errorCode": 2012,
"timestamp": "2024-01-01T12:00:00Z",
"path": "/api/v1/shorten",
"content_type": "application/pdf"
}
HTTP 状态码映射依据: RFC 7231 §6.5.1 - 400 Bad Request
大白话:就像对方发了个视频过来,但我们只看得到文字
场景:
- 目标URL返回图片、视频等二进制内容
- 只支持HTML/文本内容
2013: 重定向次数过多 (too-many-redirects)
{
"type": "https://api.example.com/errors/too-many-redirects",
"title": "重定向次数过多",
"status": 400,
"detail": "目标URL重定向次数超过限制,最大允许5次,实际6次",
"instance": "https://api.example.com/errors/req_abc123",
"errorCode": 2013,
"timestamp": "2024-01-01T12:00:00Z",
"path": "/api/v1/shorten",
"max_redirects": 5,
"actual_redirects": 6
}
HTTP 状态码映射依据: RFC 7231 §6.5.1 - 400 Bad Request
大白话:就像地址一直跳来跳去,最后绕晕了,不知道要去哪
场景:
- 重定向循环
- 重定向链过长
2014: URL包含非法字符 (invalid-url-characters)
{
"type": "https://api.example.com/errors/invalid-url-characters",
"title": "URL包含非法字符",
"status": 400,
"detail": "URL包含不允许的字符,可能存在安全风险",
"instance": "https://api.example.com/errors/req_abc123",
"errorCode": 2014,
"timestamp": "2024-01-01T12:00:00Z",
"path": "/api/v1/shorten",
"invalid_characters": "<script>"
}
HTTP 状态码映射依据: RFC 7231 §6.5.1 - 400 Bad Request
大白话:就像地址里写着"炸弹",肯定不对劲
场景:
- URL 包含 XSS 字符
- URL 包含特殊控制字符
2015: 网络错误 (network-error)
{
"type": "https://api.example.com/errors/network-error",
"title": "网络错误",
"status": 400,
"detail": "发生网络错误: Connection reset by peer",
"instance": "https://api.example.com/errors/req_abc123",
"errorCode": 2015,
"timestamp": "2024-01-01T12:00:00Z",
"path": "/api/v1/shorten",
"error_detail": "Connection reset by peer"
}
HTTP 状态码映射依据: RFC 7231 §6.5.1 - 400 Bad Request
大白话:就像网线断了,连不上网
场景:
- 网络中断
- 连接重置
- 其他网络问题
四、自定义业务错误码(3xxx)
自定义业务错误说明
自定义业务错误包括:
- 短链创建业务错误
- 短链访问业务错误
- 用户配额错误
- 自定义域名错误
3001: 短链已存在 (short-link-already-exists)
{
"type": "https://api.example.com/errors/short-link-already-exists",
"title": "短链已存在",
"status": 409,
"detail": "该短链代码已被使用: abc123",
"instance": "https://api.example.com/errors/req_abc123",
"errorCode": 3001,
"timestamp": "2024-01-01T12:00:00Z",
"path": "/api/v1/shorten",
"short_code": "abc123"
}
HTTP 状态码映射依据: RFC 7231 §6.5.8 - 409 Conflict(冲突)
大白话:就像你想注册"admin"这个用户名,但别人已经用了
场景:
- 创建自定义短链时代码冲突
- 该短链代码已被占用
3002: 短链不存在 (short-link-not-found)
{
"type": "https://api.example.com/errors/short-link-not-found",
"title": "短链不存在",
"status": 404,
"detail": "指定的短链不存在: abc123",
"instance": "https://api.example.com/errors/req_abc123",
"errorCode": 3002,
"timestamp": "2024-01-01T12:00:00Z",
"path": "/api/v1/shorten",
"short_code": "abc123"
}
HTTP 状态码映射依据: RFC 7231 §6.5.4 - 404 Not Found
大白话:就像你找地址"幸福路123号",但根本没这个地址
场景:
- 访问不存在的短链
- 查询不存在的短链信息
3003: 短链已过期 (short-link-expired)
{
"type": "https://api.example.com/errors/short-link-expired",
"title": "短链已过期",
"status": 410,
"detail": "短链已过期: abc123,过期时间: 2024-01-01T00:00:00Z",
"instance": "https://api.example.com/errors/req_abc123",
"errorCode": 3003,
"timestamp": "2024-01-01T12:00:00Z",
"path": "/api/v1/shorten",
"short_code": "abc123",
"expired_at": "2024-01-01T00:00:00Z"
}
HTTP 状态码映射依据: RFC 7231 §6.5.9 - 410 Gone(曾经存在,但现在没了)
大白话:就像这家店曾经存在,但现在已经关门了
场景:
- 短链设置了有效期
- 过期后访问失败
3004: 自定义域名未验证 (custom-domain-not-verified)
{
"type": "https://api.example.com/errors/custom-domain-not-verified",
"title": "自定义域名未验证",
"status": 403,
"detail": "自定义域名未通过DNS验证: links.example.com",
"instance": "https://api.example.com/errors/req_abc123",
"errorCode": 3004,
"timestamp": "2024-01-01T12:00:00Z",
"path": "/api/v1/shorten",
"domain": "links.example.com"
}
HTTP 状态码映射依据: RFC 7231 §6.5.3 - 403 Forbidden
大白话:就像你想开分店,但总店还没审核通过
场景:
- 使用未验证的自定义域名
- DNS 配置不正确
3005: 自定义域名不存在 (custom-domain-not-found)
{
"type": "https://api.example.com/errors/custom-domain-not-found",
"title": "自定义域名不存在",
"status": 404,
"detail": "自定义域名不存在或未添加: links.example.com",
"instance": "https://api.example.com/errors/req_abc123",
"errorCode": 3005,
"timestamp": "2024-01-01T12:00:00Z",
"path": "/api/v1/shorten",
"domain": "links.example.com"
}
HTTP 状态码映射依据: RFC 7231 §6.5.4 - 404 Not Found
大白话:就像你说要用"xx.com"这个域名,但我们系统里根本没加这个
场景:
- 使用了未添加的自定义域名
- 域名已被删除
3006: 短链代码格式无效 (invalid-short-code-format)
{
"type": "https://api.example.com/errors/invalid-short-code-format",
"title": "短链代码格式无效",
"status": 400,
"detail": "短链代码格式不符合要求,提供的代码: abc@123,要求格式: ^[a-zA-Z0-9]{4,10}$",
"instance": "https://api.example.com/errors/req_abc123",
"errorCode": 3006,
"timestamp": "2024-01-01T12:00:00Z",
"path": "/api/v1/shorten",
"provided_code": "abc@123",
"required_format": "^[a-zA-Z0-9]{4,10}$"
}
HTTP 状态码映射依据: RFC 7231 §6.5.1 - 400 Bad Request
大白话:就像你填的电话号码里有字母,格式不对
场景:
- 短链代码包含非法字符
- 短链代码长度不符合要求
3007: 短链代码保留 (short-code-reserved)
{
"type": "https://api.example.com/errors/short-code-reserved",
"title": "短链代码被保留",
"status": 409,
"detail": "该短链代码被系统保留,不能使用: admin",
"instance": "https://api.example.com/errors/req_abc123",
"errorCode": 3007,
"timestamp": "2024-01-01T12:00:00Z",
"path": "/api/v1/shorten",
"short_code": "admin"
}
HTTP 状态码映射依据: RFC 7231 §6.5.8 - 409 Conflict
大白话:就像你想注册"admin"这个用户名,但系统说"这个是保留的,不能用"
场景:
- 尝试使用系统保留的代码
- 如 “admin”、“api” 等
3008: 配额已用完 (quota-exceeded)
{
"type": "https://api.example.com/errors/quota-exceeded",
"title": "配额已用完",
"status": 429,
"detail": "您的配额已用完,配额类型: daily_shorten_limit,限制: 1000,已用: 1000",
"instance": "https://api.example.com/errors/req_abc123",
"errorCode": 3008,
"timestamp": "2024-01-01T12:00:00Z",
"path": "/api/v1/shorten",
"quota_type": "daily_shorten_limit",
"quota_limit": 1000,
"quota_used": 1000,
"retry_after": 86400
}
HTTP 状态码映射依据: RFC 6585 §4 - 429 Too Many Requests
大白话:就像你的免费流量用完了,要么充值要么等明天
场景:
- 超过每日创建短链限制
- 超过每月配额
3009: 域名黑名单 (domain-blocked)
{
"type": "https://api.example.com/errors/domain-blocked",
"title": "域名在黑名单中",
"status": 403,
"detail": "域名在服务黑名单中: spam.com",
"instance": "https://api.example.com/errors/req_abc123",
"errorCode": 3009,
"timestamp": "2024-01-01T12:00:00Z",
"path": "/api/v1/shorten",
"domain": "spam.com"
}
HTTP 状态码映射依据: RFC 7231 §6.5.3 - 403 Forbidden
大白话:就像这个域名被拉黑了,不让用
场景:
- 自定义域名被封禁
- 域名涉及违规内容
3010: 短链已被禁用 (short-link-disabled)
{
"type": "https://api.example.com/errors/short-link-disabled",
"title": "短链已被禁用",
"status": 403,
"detail": "短链已被管理员禁用: abc123,原因: 违规内容",
"instance": "https://api.example.com/errors/req_abc123",
"errorCode": 3010,
"timestamp": "2024-01-01T12:00:00Z",
"path": "/api/v1/shorten",
"short_code": "abc123",
"disabled_reason": "违规内容"
}
HTTP 状态码映射依据: RFC 7231 §6.5.3 - 403 Forbidden
大白话:就像这个短链被封了,因为有人举报它有问题
场景:
- 短链违反服务条款
- 短链被举报并禁用
3011: 标题过长 (title-too-long)
{
"type": "https://api.example.com/errors/title-too-long",
"title": "标题过长",
"status": 400,
"detail": "短链标题超过最大长度,最大长度: 200,提供长度: 250",
"instance": "https://api.example.com/errors/req_abc123",
"errorCode": 3011,
"timestamp": "2024-01-01T12:00:00Z",
"path": "/api/v1/shorten",
"max_length": 200,
"provided_length": 250
}
HTTP 状态码映射依据: RFC 7231 §6.5.1 - 400 Bad Request
大白话:就像你写的标题太长了,一行放不下
场景:
- 设置短链标题时超过长度限制
3012: 标签数量超限 (too-many-tags)
{
"type": "https://api.example.com/errors/too-many-tags",
"title": "标签数量超限",
"status": 400,
"detail": "标签数量超过限制,最大标签数: 10,提供标签数: 15",
"instance": "https://api.example.com/errors/req_abc123",
"errorCode": 3012,
"timestamp": "2024-01-01T12:00:00Z",
"path": "/api/v1/shorten",
"max_tags": 10,
"provided_tags": 15
}
HTTP 状态码映射依据: RFC 7231 §6.5.1 - 400 Bad Request
大白话:就像你给商品贴了太多标签,太多了
场景:
- 为短链添加的标签过多
场景:
- 为短链添加的标签过多
五、系统级错误码(5xxx)
这是什么阶段?
这是"服务器自己出问题了"阶段:
- 服务器代码崩了
- 数据库连不上
- 缓存服务挂了
对应的 HTTP 状态码:
500 Internal Server Error - 服务器内部错误(程序崩了)
502 Bad Gateway - 网关错误(调用的服务出问题)
503 Service Unavailable - 服务暂时不可用(维护中)
系统级错误说明(小白话版)
系统级错误就是"不是你的错,是我们的错":
- 就像商场突然停电了
- 就像收银机坏了
- 就像网线被挖断了
5001: 服务器内部错误 (internal-server-error)
{
"type": "https://api.example.com/errors/internal-server-error",
"title": "服务器内部错误",
"status": 500,
"detail": "服务器发生内部错误,请稍后重试。问题ID: req_abc123",
"instance": "https://api.example.com/errors/req_abc123",
"errorCode": 5001,
"timestamp": "2024-01-01T12:00:00Z",
"path": "/api/v1/shorten"
}
HTTP 状态码映射依据: RFC 7231 §6.6.1 - 500 Internal Server Error
大白话:就像商场突然停电了,不是你的问题,是我们的问题
场景:
- 代码异常
- 未处理的错误
5002: 数据库连接错误 (database-connection-error)
{
"type": "https://api.example.com/errors/database-connection-error",
"title": "数据库连接错误",
"status": 500,
"detail": "无法连接到数据库,请稍后重试",
"instance": "https://api.example.com/errors/req_abc123",
"errorCode": 5002,
"timestamp": "2024-01-01T12:00:00Z",
"path": "/api/v1/shorten"
}
HTTP 状态码映射依据: RFC 7231 §6.6.1 - 500 Internal Server Error
大白话:就像收银机连不上后台系统
场景:
- 数据库服务不可用
- 连接池耗尽
5003: 数据库查询错误 (database-query-error)
{
"type": "https://api.example.com/errors/database-query-error",
"title": "数据库查询错误",
"status": 500,
"detail": "数据库查询失败,请稍后重试",
"instance": "https://api.example.com/errors/req_abc123",
"errorCode": 5003,
"timestamp": "2024-01-01T12:00:00Z",
"path": "/api/v1/shorten"
}
HTTP 状态码映射依据: RFC 7231 §6.6.1 - 500 Internal Server Error
大白话:就像收银机在查询商品信息时出错了
场景:
- SQL 语法错误
- 查询超时
5004: 缓存服务错误 (cache-service-error)
{
"type": "https://api.example.com/errors/cache-service-error",
"title": "缓存服务错误",
"status": 500,
"detail": "缓存服务不可用,请稍后重试",
"instance": "https://api.example.com/errors/req_abc123",
"errorCode": 5004,
"timestamp": "2024-01-01T12:00:00Z",
"path": "/api/v1/shorten"
}
HTTP 状态码映射依据: RFC 7231 §6.6.1 - 500 Internal Server Error
大白话:就像临时储物柜坏了
场景:
- Redis 连接失败
- Memcached 错误
5005: 存储服务错误 (storage-service-error)
{
"type": "https://api.example.com/errors/storage-service-error",
"title": "存储服务错误",
"status": 500,
"detail": "对象存储服务错误,请稍后重试",
"instance": "https://api.example.com/errors/req_abc123",
"errorCode": 5005,
"timestamp": "2024-01-01T12:00:00Z",
"path": "/api/v1/shorten"
}
HTTP 状态码映射依据: RFC 7231 §6.6.1 - 500 Internal Server Error
大白话:就像仓库系统坏了,存不了货
场景:
- S3/OSS 连接失败
- 存储空间不足
5006: 消息队列错误 (message-queue-error)
{
"type": "https://api.example.com/errors/message-queue-error",
"title": "消息队列错误",
"status": 500,
"detail": "消息队列服务错误,请稍后重试",
"instance": "https://api.example.com/errors/req_abc123",
"errorCode": 5006,
"timestamp": "2024-01-01T12:00:00Z",
"path": "/api/v1/shorten"
}
HTTP 状态码映射依据: RFC 7231 §6.6.1 - 500 Internal Server Error
大白话:就像传话筒坏了,消息传不过去
场景:
- Kafka/RabbitMQ 连接失败
- 消息发送失败
5007: 第三方API错误 (third-party-api-error)
{
"type": "https://api.example.com/errors/third-party-api-error",
"title": "第三方API错误",
"status": 502,
"detail": "调用第三方API失败,服务: Google Safe Browsing",
"instance": "https://api.example.com/errors/req_abc123",
"errorCode": 5007,
"timestamp": "2024-01-01T12:00:00Z",
"path": "/api/v1/shorten",
"service": "Google Safe Browsing"
}
HTTP 状态码映射依据: RFC 7231 §6.6.3 - 502 Bad Gateway
大白话:就像我们帮你联系第三方,但第三方联系不上
场景:
- 调用恶意网站检测API失败
- 调用其他第三方服务失败
5008: 服务暂时不可用 (service-unavailable)
{
"type": "https://api.example.com/errors/service-unavailable",
"title": "服务暂时不可用",
"status": 503,
"detail": "服务正在维护中,请稍后重试",
"instance": "https://api.example.com/errors/req_abc123",
"errorCode": 5008,
"timestamp": "2024-01-01T12:00:00Z",
"path": "/api/v1/shorten",
"retry_after": 300
}
HTTP 状态码映射依据: RFC 7231 §6.6.4 - 503 Service Unavailable
大白话:就像商场打烊了,挂个牌子"营业中暂停服务"
场景:
- 系统维护
- 服务降级
5009: 配置错误 (configuration-error)
{
"type": "https://api.example.com/errors/configuration-error",
"title": "配置错误",
"status": 500,
"detail": "系统配置错误,请联系管理员",
"instance": "https://api.example.com/errors/req_abc123",
"errorCode": 5009,
"timestamp": "2024-01-01T12:00:00Z",
"path": "/api/v1/shorten"
}
HTTP 状态码映射依据: RFC 7231 §6.6.1 - 500 Internal Server Error
大白话:就像收银机的设置写错了,需要管理员来修
场景:
- 环境变量缺失
- 配置文件错误
六、错误响应格式(RFC 7807 标准格式)
RFC 7807 标准错误响应(完整版)
{
"type": "https://api.example.com/errors/invalid-api-key",
"title": "API密钥无效",
"status": 401,
"detail": "提供的API密钥不存在或已过期",
"instance": "https://api.example.com/errors/req_abc123",
"errorCode": 1002,
"timestamp": "2024-01-01T12:00:00Z",
"path": "/api/v1/shorten"
}
小白话解释:
这是完整的错误响应,包含了所有RFC 7807规定的字段:
- type: 错误类型的"身份证号"(网址格式)
- title: 给人看的简短标题
- status: HTTP状态码(和响应头一致)
- detail: 给开发者看的详细说明
- instance: 这次错误的唯一ID
- errorCode: 我们自己用的数字错误码
- timestamp: 发生时间
- path: 请求路径
简化错误响应(最小字段)
{
"type": "https://api.example.com/errors/invalid-api-key",
"title": "API密钥无效",
"status": 401
}
小白话解释:
这是最简单的版本,只包含RFC 7807的必需字段。
如果你的API很简单,用这个就够了。
带扩展信息的错误响应
{
"type": "https://api.example.com/errors/rate-limit-exceeded",
"title": "请求频率超限",
"status": 429,
"detail": "请求频率超过限制,请在 60 秒后重试",
"instance": "https://api.example.com/errors/req_abc123",
"errorCode": 1006,
"timestamp": "2024-01-01T12:00:00Z",
"path": "/api/v1/shorten",
"retry_after": 60,
"limit": 100,
"remaining": 0,
"reset": "2024-01-01T13:00:00Z"
}
小白话解释:
这个版本多了几个扩展字段:
- retry_after: 多少秒后可以重试
- limit: 你的限制是多少
- remaining: 还剩多少次
- reset: 什么时候重置
RFC 7807 与旧格式对比
| RFC 7807 格式(现在) | 旧格式(之前) | 说明 |
|---|---|---|
type: "https://..." |
code: 1001 |
用URI代替数字,更规范 |
title: "API密钥无效" |
message: "API密钥无效" |
字段名统一 |
status: 401 |
无 | 新增:HTTP状态码 |
detail: "..." |
description: "..." |
字段名统一 |
instance: "https://..." |
request_id: "..." |
用URI格式 |
errorCode: 1001 |
code: 1001 |
保留内部数字码 |
握手阶段错误示例(1001 API密钥缺失)
请求:
POST /api/v1/shorten HTTP/1.1
Host: api.shorten.com
Content-Type: application/json
{
"url": "https://example.com"
}
HTTP 响应头:
HTTP/1.1 401 Unauthorized
Content-Type: application/problem+json
响应体(RFC 7807 格式):
{
"type": "https://api.example.com/errors/missing-api-key",
"title": "API密钥缺失",
"status": 401,
"detail": "请求头中缺少 X-API-Key 字段,请在请求中包含有效的 API 密钥",
"instance": "https://api.example.com/errors/req_abc123",
"errorCode": 1001,
"timestamp": "2024-01-01T12:00:00Z",
"path": "/api/v1/shorten"
}
注意: Content-Type: application/problem+json 是 RFC 7807 规定的标准 MIME 类型。
结果解析阶段错误示例(2009 返回状态码异常)
请求:
POST /api/v1/shorten HTTP/1.1
Host: api.shorten.com
X-API-Key: your_api_key
Content-Type: application/json
{
"url": "https://example.com/notfound"
}
HTTP 响应头:
HTTP/1.1 400 Bad Request
Content-Type: application/problem+json
响应体(RFC 7807 格式):
{
"type": "https://api.example.com/errors/abnormal-http-status",
"title": "返回状态码异常",
"status": 400,
"detail": "目标URL返回异常状态码: 404",
"instance": "https://api.example.com/errors/req_abc123",
"errorCode": 2009,
"timestamp": "2024-01-01T12:00:00Z",
"path": "/api/v1/shorten",
"status_code": 404,
"target_url": "https://example.com/notfound"
}
自定义业务错误示例(3007 短链代码保留)
请求:
POST /api/v1/shorten HTTP/1.1
Host: api.shorten.com
X-API-Key: your_api_key
Content-Type: application/json
{
"url": "https://example.com",
"custom_slug": "admin"
}
HTTP 响应头:
HTTP/1.1 409 Conflict
Content-Type: application/problem+json
响应体(RFC 7807 格式):
{
"type": "https://api.example.com/errors/short-code-reserved",
"title": "短链代码被保留",
"status": 409,
"detail": "该短链代码被系统保留,不能使用: admin",
"instance": "https://api.example.com/errors/req_abc123",
"errorCode": 3007,
"timestamp": "2024-01-01T12:00:00Z",
"path": "/api/v1/shorten",
"short_code": "admin"
}
系统级错误示例(5008 服务暂时不可用)
请求:
GET /api/v1/shorten/abc123 HTTP/1.1
Host: api.shorten.com
X-API-Key: your_api_key
HTTP 响应头:
HTTP/1.1 503 Service Unavailable
Content-Type: application/problem+json
Retry-After: 300
响应体(RFC 7807 格式):
{
"type": "https://api.example.com/errors/service-unavailable",
"title": "服务暂时不可用",
"status": 503,
"detail": "服务正在维护中,请稍后重试",
"instance": "https://api.example.com/errors/req_abc123",
"errorCode": 5008,
"timestamp": "2024-01-01T12:00:00Z",
"path": "/api/v1/shorten/abc123",
"retry_after": 300
}
注意: Retry-After: 300 是 RFC 7231 规定的 HTTP 响应头。
附录:完整错误码列表(RFC 7807 版本)
完整错误码表
| 错误码 | Type URI(RFC 7807) | HTTP 状态码 | 错误名称 | 大白话解释 |
|---|---|---|---|---|
| 握手阶段(1xxx) | ||||
| 1001 | missing-api-key |
401 | API密钥缺失 | 没带门禁卡 |
| 1002 | invalid-api-key |
401 | API密钥无效 | 门禁卡是假的 |
| 1003 | signature-verification-failed |
401 | 签名验证失败 | 签名对不上 |
| 1004 | invalid-token |
401 | Token无效 | 临时通行证过期 |
| 1005 | insufficient-permissions |
403 | 权限不足 | 有卡但不能进这个区 |
| 1006 | rate-limit-exceeded |
429 | 请求频率超限 | 访问太快,被限流 |
| 1007 | ip-banned |
403 | IP被封禁 | 你被拉黑了 |
| 1008 | method-not-allowed |
405 | 请求方法不支持 | 用错方法了 |
| 1009 | unsupported-media-type |
415 | Content-Type不支持 | 数据格式不对 |
| 1010 | payload-too-large |
413 | 请求体过大 | 数据太多了 |
| 结果解析阶段(2xxx) | ||||
| 2001 | missing-url-parameter |
400 | URL参数缺失 | 没填地址 |
| 2002 | invalid-url-format |
400 | URL格式无效 | 地址写错了 |
| 2003 | unsupported-url-protocol |
400 | URL协议不支持 | 不送这种快递 |
| 2004 | url-blocked |
403 | URL在黑名单中 | 地址被封了 |
| 2005 | dns-resolution-failed |
400 | DNS解析失败 | 找不到这个地址 |
| 2006 | connection-timeout |
408 | 连接超时 | 等太久没开门 |
| 2007 | connection-refused |
400 | 连接被拒绝 | 对方说"不送!" |
| 2008 | ssl-certificate-verify-failed |
400 | SSL证书验证失败 | 身份证是假的 |
| 2009 | abnormal-http-status |
400 | 返回状态码异常 | 对方说"查无此人" |
| 2010 | empty-response |
400 | 响应内容为空 | 打开箱子啥都没有 |
| 2011 | response-too-large |
400 | 响应内容过大 | 回来的东西太多了 |
| 2012 | unsupported-content-type |
400 | 内容类型不支持 | 发来了视频只认文字 |
| 2013 | too-many-redirects |
400 | 重定向次数过多 | 地址跳来跳去晕了 |
| 2014 | invalid-url-characters |
400 | URL包含非法字符 | 地址里有奇怪的东西 |
| 2015 | network-error |
400 | 网络错误 | 网线断了 |
| 自定义业务错误(3xxx) | ||||
| 3001 | short-link-already-exists |
409 | 短链已存在 | 用户名被占用了 |
| 3002 | short-link-not-found |
404 | 短链不存在 | 找不到这个地址 |
| 3003 | short-link-expired |
410 | 短链已过期 | 店已经关门了 |
| 3004 | custom-domain-not-verified |
403 | 自定义域名未验证 | 分店还没审核通过 |
| 3005 | custom-domain-not-found |
404 | 自定义域名不存在 | 系统里没加这个域名 |
| 3006 | invalid-short-code-format |
400 | 短链代码格式无效 | 格式不对 |
| 3007 | short-code-reserved |
409 | 短链代码保留 | 这是保留的不能用 |
| 3008 | quota-exceeded |
429 | 配额已用完 | 免费流量用完了 |
| 3009 | domain-blocked |
403 | 域名黑名单 | 域名被封了 |
| 3010 | short-link-disabled |
403 | 短链已被禁用 | 短链被封了 |
| 3011 | title-too-long |
400 | 标题过长 | 标题太长了 |
| 3012 | too-many-tags |
400 | 标签数量超限 | 标签太多了 |
| 系统级错误(5xxx) | ||||
| 5001 | internal-server-error |
500 | 服务器内部错误 | 商场停电了 |
| 5002 | database-connection-error |
500 | 数据库连接错误 | 收银机连不上系统 |
| 5003 | database-query-error |
500 | 数据库查询错误 | 查询时出错了 |
| 5004 | cache-service-error |
500 | 缓存服务错误 | 临时储物柜坏了 |
| 5005 | storage-service-error |
500 | 存储服务错误 | 仓库系统坏了 |
| 5006 | message-queue-error |
500 | 消息队列错误 | 传话筒坏了 |
| 5007 | third-party-api-error |
502 | 第三方API错误 | 第三方联系不上 |
| 5008 | service-unavailable |
503 | 服务暂时不可用 | 商场打烊了 |
| 5009 | configuration-error |
500 | 配置错误 | 配置写错了 |
Type URI 完整列表
所有 Type URI 格式:https://api.example.com/errors/{error-name}
完整列表:
https://api.example.com/errors/missing-api-key
https://api.example.com/errors/invalid-api-key
https://api.example.com/errors/signature-verification-failed
https://api.example.com/errors/invalid-token
https://api.example.com/errors/insufficient-permissions
https://api.example.com/errors/rate-limit-exceeded
https://api.example.com/errors/ip-banned
https://api.example.com/errors/method-not-allowed
https://api.example.com/errors/unsupported-media-type
https://api.example.com/errors/payload-too-large
https://api.example.com/errors/missing-url-parameter
https://api.example.com/errors/invalid-url-format
https://api.example.com/errors/unsupported-url-protocol
https://api.example.com/errors/url-blocked
https://api.example.com/errors/dns-resolution-failed
https://api.example.com/errors/connection-timeout
https://api.example.com/errors/connection-refused
https://api.example.com/errors/ssl-certificate-verify-failed
https://api.example.com/errors/abnormal-http-status
https://api.example.com/errors/empty-response
https://api.example.com/errors/response-too-large
https://api.example.com/errors/unsupported-content-type
https://api.example.com/errors/too-many-redirects
https://api.example.com/errors/invalid-url-characters
https://api.example.com/errors/network-error
https://api.example.com/errors/short-link-already-exists
https://api.example.com/errors/short-link-not-found
https://api.example.com/errors/short-link-expired
https://api.example.com/errors/custom-domain-not-verified
https://api.example.com/errors/custom-domain-not-found
https://api.example.com/errors/invalid-short-code-format
https://api.example.com/errors/short-code-reserved
https://api.example.com/errors/quota-exceeded
https://api.example.com/errors/domain-blocked
https://api.example.com/errors/short-link-disabled
https://api.example.com/errors/title-too-long
https://api.example.com/errors/too-many-tags
https://api.example.com/errors/internal-server-error
https://api.example.com/errors/database-connection-error
https://api.example.com/errors/database-query-error
https://api.example.com/errors/cache-service-error
https://api.example.com/errors/storage-service-error
https://api.example.com/errors/message-queue-error
https://api.example.com/errors/third-party-api-error
https://api.example.com/errors/service-unavailable
https://api.example.com/errors/configuration-error
文档版本:v2.0(基于 RFC 7807)
最后更新:2024年6月
适用场景:短链接服务API错误码设计
遵循标准:RFC 7807 (Problem Details for HTTP APIs)
客户端实现建议(RFC 7807)
如何解析 RFC 7807 错误响应
// JavaScript/TypeScript 示例
async function callApi(url, options) {
const response = await fetch(url, options);
if (!response.ok) {
const contentType = response.headers.get('content-type');
// 检查是否是 RFC 7807 格式
if (contentType && contentType.includes('application/problem+json')) {
const problem = await response.json();
// 根据 type URI 处理不同错误
switch (problem.type) {
case 'https://api.example.com/errors/rate-limit-exceeded':
// 等待 retry_after 秒后重试
setTimeout(() => callApi(url, options), problem.retry_after * 1000);
break;
case 'https://api.example.com/errors/invalid-api-key':
// 提示用户重新配置 API Key
alert('API Key 无效,请检查配置');
break;
default:
// 显示 detail 信息
alert(problem.detail || problem.title);
}
// 使用 errorCode 进行日志统计
console.error(`API Error [${problem.errorCode}]: ${problem.title}`);
}
}
return response.json();
}
服务器实现建议
// Node.js/Express 示例
function rfc7807Error(type, title, status, detail, extra = {}) {
const error = {
type: `https://api.example.com/errors/${type}`,
title,
status,
detail,
instance: `https://api.example.com/errors/${req.id}`,
errorCode: extra.errorCode,
timestamp: new Date().toISOString(),
path: req.path,
...extra
};
return res.status(status).contentType('application/problem+json').json(error);
}
// 使用示例
app.post('/api/v1/shorten', (req, res) => {
const apiKey = req.headers['x-api-key'];
if (!apiKey) {
return rfc7807Error(
'missing-api-key',
'API密钥缺失',
401,
'请求头中缺少 X-API-Key 字段',
{ errorCode: 1001 }
);
}
// ... 其他逻辑
});
| 1006 | RATE_LIMIT_EXCEEDED | 请求频率超限 |
| 1007 | IP_BANNED | IP地址被封禁 |
| 1008 | METHOD_NOT_ALLOWED | 请求方法不支持 |
| 1009 | UNSUPPORTED_CONTENT_TYPE | Content-Type不支持 |
| 1010 | REQUEST_BODY_TOO_LARGE | 请求体过大 |
| 结果解析阶段(2xxx) |
| 2001 | MISSING_URL_PARAMETER | URL参数缺失 |
| 2002 | INVALID_URL_FORMAT | URL格式无效 |
| 2003 | UNSUPPORTED_URL_PROTOCOL | URL协议不支持 |
| 2004 | URL_BLOCKED | URL在黑名单中 |
| 2005 | DNS_RESOLUTION_FAILED | DNS解析失败 |
| 2006 | CONNECTION_TIMEOUT | 连接超时 |
| 2007 | CONNECTION_REFUSED | 连接被拒绝 |
| 2008 | SSL_CERTIFICATE_VERIFY_FAILED | SSL证书验证失败 |
| 2009 | ABNORMAL_HTTP_STATUS | 返回状态码异常 |
| 2010 | EMPTY_RESPONSE | 响应内容为空 |
| 2011 | RESPONSE_TOO_LARGE | 响应内容过大 |
| 2012 | UNSUPPORTED_CONTENT_TYPE | 内容类型不支持 |
| 2013 | TOO_MANY_REDIRECTS | 重定向次数过多 |
| 2014 | INVALID_URL_CHARACTERS | URL包含非法字符 |
| 2015 | NETWORK_ERROR | 网络错误 |
| 自定义业务错误(3xxx) |
| 3001 | SHORT_LINK_ALREADY_EXISTS | 短链已存在 |
| 3002 | SHORT_LINK_NOT_FOUND | 短链不存在 |
| 3003 | SHORT_LINK_EXPIRED | 短链已过期 |
| 3004 | CUSTOM_DOMAIN_NOT_VERIFIED | 自定义域名未验证 |
| 3005 | CUSTOM_DOMAIN_NOT_FOUND | 自定义域名不存在 |
| 3006 | INVALID_SHORT_CODE_FORMAT | 短链代码格式无效 |
| 3007 | SHORT_CODE_RESERVED | 短链代码保留 |
| 3008 | QUOTA_EXCEEDED | 配额已用完 |
| 3009 | DOMAIN_BLOCKED | 域名黑名单 |
| 3010 | SHORT_LINK_DISABLED | 短链已被禁用 |
| 3011 | TITLE_TOO_LONG | 标题过长 |
| 3012 | TOO_MANY_TAGS | 标签数量超限 |
| 系统级错误(5xxx) |
| 5001 | INTERNAL_SERVER_ERROR | 服务器内部错误 |
| 5002 | DATABASE_CONNECTION_ERROR | 数据库连接错误 |
| 5003 | DATABASE_QUERY_ERROR | 数据库查询错误 |
| 5004 | CACHE_SERVICE_ERROR | 缓存服务错误 |
| 5005 | STORAGE_SERVICE_ERROR | 存储服务错误 |
| 5006 | MESSAGE_QUEUE_ERROR | 消息队列错误 |
| 5007 | THIRD_PARTY_API_ERROR | 第三方API错误 |
| 5008 | SERVICE_UNAVAILABLE | 服务暂时不可用 |
| 5009 | CONFIGURATION_ERROR | 配置错误 |
文档版本:v1.0
最后更新:2024年6月
适用场景:短链接服务API错误码设计
使用建议
错误码使用原则
1. ✅ 明确性:错误码含义清晰,无歧义
2. ✅ 一致性:同一错误场景使用同一错误码
3. ✅ 可扩展:预留空间,便于扩展
4. ✅ 可追踪:包含足够的调试信息
5. ✅ 友好性:提供清晰的错误描述
错误处理建议
客户端处理建议:
✅ 检查 success 字段
✅ 根据 error.code 进行分类处理
✅ 记录 error.request_id 用于调试
✅ 对可重试的错误实现重试逻辑
✅ 向用户展示友好的错误信息
服务端处理建议:
✅ 统一错误响应格式
✅ 记录详细的错误日志
✅ 监控错误发生频率
✅ 设置告警机制
✅ 定期review错误码设计
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)