短链接服务错误码规范

基于 RFC 7807: Problem Details for HTTP APIs

RFC 7807 是 W3C 标准,定义了一种在 HTTP API 中携带机器可读错误详情的格式。本规范基于该标准,结合短链接服务特点进行了扩展。

目录


一、规范依据

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错误码设计
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐