简介:《API接口设计规范》文档是一份面向后端开发与前后端协作团队的标准参考,旨在统一API接口的开发、实现与维护约定,解决接口风格不一、参数混乱、错误处理分散等常见问题。文档在数据格式、路径版本、请求/响应参数等层面给出细化规则,例如key采用驼峰命名、金额以元为单位且显示格式化、空对象/列表用[]、避免null与undefined,以及每次接口携带requestId用于追踪问题;路径统一为/{版本}/{业务功能}/{表名}/{行为},请求参数支持limit、offset、page、per_page、sort_by、order,响应统一为code、success、message、data结构,并明确200、400、401、403等错误码语义。安全性方面则覆盖token身份验证、权限控制与HTTPS数据加密。资源包共1个docx文件,仅20KB,内容精炼、便于直接查阅和团队分享,已有3578人学习。对于需要快速建立接口规范或提升接口质量的团队,这份文档可作为落地模板,帮助减少前后端联调成本并提高可维护性。
1. 需求背景:接口格式不统一,联调就像开盲盒
前后端联调最怕的不是功能做不完,而是接口格式各写各的。上个月做的一个项目,后端把空列表返回成 null,前端一渲染直接白屏,两边对着同一个接口查了一下午,最后发现是接口文档里根本没约定空值怎么返回。这份 api 接口设计规范要解决的就是这类问题:把 JSON 格式、金额单位、时间格式、路径结构、错误码全部提前写成硬性约定,前后端照着同一套规则开发,联调时不用再互相猜。它适合正在做前后端分离项目的开发团队,也适合需要提交可落地接口设计方案的毕设和课设项目——拿它当模板,照着改就能用。
2. 全局数据格式约定:JSON、金额、时间与空值的统一口径
一份接口规范最先要立住的是数据格式。格式不统一,后面所有接口设计都是空中楼阁。这一章讲清楚后端必须保证的 JSON 合法性、金额单位、时间格式、空值规则和 key 命名习惯,这些约定属于全局基线,任何一个接口都要遵守。
2.1 JSON 合法性与 Content-Type:合法性是后端的责任
约定一:后端需要保证 JSON 格式的合法性,前端不对格式的合法性做判断。这句话看起来很绝对,实际上是划清责任边界。接口返回的 JSON 是由后端序列化产生的,后端拥有完整控制权,序列化层出了问题应该后端修复,而不是让每个前端调用方各自做容错解析。
响应头 Content-Type 固定为application/json; charset=UTF-8。这里 charset=UTF-8 不能丢,否则中文可能出现乱码;也不要顺手改成 text/plain 或 application/x-www-form-urlencoded,前端的 axios 和 fetch 默认解析逻辑依赖这个头。
{ "code": 200, "success": true, "message": "操作成功", "data": { "list": [], "total": 0 }, "timestamp": 1742000000000 }这个响应体里 code 是数字状态码,success 表示业务是否成功,message 是给人看的提示文案,data 里放业务数据,timestamp 是服务端返回时间。前端拦截器通常会先判断 success 再进业务逻辑,所以这两个字段必须稳定,不能因为某个接口特殊就改名。如果后端返回的是非法 JSON,前端解析直接抛错,这类问题必须在后端日志里暴露,而不是让用户看到白屏。
2.2 金额、时间与数字:单位统一,展示和计算分离
金额字段是接口里最容易翻车的。规范约定:所有金额以元为单位,显示性的后台返回的是格式化之后的字符串,例如6,800。也就是说,展示层字段如 formattedAmount 是带千分位的字符串,计算层字段如 amount 是纯数字,二者分工明确。如果前后端各用一套单位,订单总额小数位对不上,排查起来非常痛苦。
时间格式统一为字符串:2019-01-01 12:12:12。注意这里是字符串,不是时间戳。虽然时间戳传输上更轻量,但开发时打开响应看到 1742000000000 完全不知道是几点,而字符串一眼能看懂,省去在线转换工具。涉及时区时,建议在字段注释里写明是服务器本地时间还是 UTC;后端实体类上用@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")保证输出一致。
默认数字使用 0,不要返回 null。比如用户积分字段,数据库没有值就返回 0,前端可以直接做数值运算,不需要再判空。常见的误用是在 SQL 里用SUM()聚合,结果为空时返回 null,这个问题可以在 mapper 层用COALESCE(SUM(amount), 0)解决。
2.3 空值策略:空对象、空列表、空字符串各归其位
这条规范最反直觉的地方在空值处理:空对象请使用[],空列表也使用[],空字符串使用'',默认数字使用0,尽量避免使用 null 和 undefined。严格来说 JSON 的空对象是{},但这份规范把空对象和空列表统一收敛到[],目的是让前端判断逻辑足够简单——看到数组且 length === 0 就知道是空,不用再区分类型。
我一般会建议把这条当代码评审的硬性检查项:所有接口返回的字段,要么有值,要么是[]、''、0,不允许出现 null。理由很实在:前端模板引擎遇到 null 会抛异常或显示空白,而[]和''天然安全。如果某个字段确实是对象类型且为空,可以在字段注释里约定返回{},但必须写清楚,不能让人猜。
// 空列表兜底,防止序列化后输出 null if (CollectionUtils.isEmpty(result.getList())) { result.setList(Collections.emptyList()); }这段兜底逻辑放在 service 层,谁调谁都安全。把空值策略定死之后,前端所有组件可以默认响应里没有 null,代码里彻底少掉一半判空逻辑。
2.4 key 命名、requestId 与敏感字段脱敏
key 统一使用驼峰命名,首字母小写,例如userId、orderList、displayName。严禁出现user_id或UserId,否则每个接口前端都要写一层映射。数据接口中定义的 key 集合是后端返回的子集,意思是后端可以返回更多字段,但规范中承诺的 key 一个都不能缺——这是为了防止前端拿到一个缺字段的对象后渲染异常。
每个接口应该携带 requestId,作为一次请求的唯一标识,用来追踪问题。用户反馈接口异常时,前端把 requestId 贴给后端,后端在日志里 grep 到这个 ID 就能定位整条调用链路,不用再问"你是几点几分操作的"。requestId 可以由网关生成,也可以在后端入口过滤器里用 UUID 生成后放入响应头或响应体。
涉及用户隐私的字段需要加*号脱敏,比如手机号返回138****8000,身份证号返回310***********1234,邮箱、支付账号、邮寄地址同样处理。脱敏规则最好统一,不要每个接口各自实现,否则有的接口脱敏、有的不脱敏,等于没脱敏。通常的做法是写一个序列化注解统一处理,业务代码里完全不用关心脱敏逻辑;同时对敏感度高的接口参数做签名匹配,防止参数在请求过程中被抓取篡改。
注意:脱敏和签名是两件事。脱敏解决的是展示层隐私泄露,签名解决的是传输层参数篡改,二者都要做,不能互相替代。
3. 路径与行为设计:从 /{版本}/{业务}/{表名}/{行为} 看接口的语义化拆分
路径设计决定了接口好不好找、好不好维护。这份规范把路径模板定成path = /{版本}/{具体的业务功能}/{表名}/{行为},下面逐段拆开讲,并给出具体示例和路由解析顺序。
3.1 路径模板:四段式结构与缺省行为
模板完整拼出来是:/{版本}/{业务功能}/{表名}/{行为}。版本在最前面,是为了未来升级不破坏老客户端;业务功能用来划分模块;表名对应数据库物理表;行为表示对资源做什么操作。看几个原文里的例子:
/api/v1/acs/users/表示访问所有的用户资源。/api/v1/acs/users/1表示访问 Id 为 1 的用户。/api/v1/acs/users/group/iws-tech/minAge/30表示访问 iws-tech 组中最小年龄 30 岁的用户。/api/v1/acs/accounts/UpdateAllUserFlags表示更新所有用户的某个标识。
注意第二个例子,路径停在/users/1,没有显式行为名。这里其实省略了默认行为 getById,属于资源型接口的变体,并不是路径写错了。我的处理建议是:如果路径以资源 ID 结尾,默认行为是 get;如果后面跟的是行为名,则执行对应方法;如果带了筛选条件如group/iws-tech/minAge/30,默认走 list。路由解析优先级为:行为名 > 资源 ID > 筛选条件。
这样设计的好处是可以直接从 URL 猜出接口语义。看UpdateAllUserFlags就知道是批量更新操作,看结尾是数字就知道在访问某个具体资源,不需要翻文档确认。筛选型参数可以继续追加,例如/api/v1/acs/users/group/iws-tech/minAge/30/status/1,每对参数表示一个维度,解析逻辑保持一致。
3.2 版本号与业务功能命名:数据库前缀剥掉后的语义收敛
版本号:开始时全部为 V1。我一般建议格式是 V 加大写数字,升级大版本时变成 V2,小版本升级不改变路径,在响应体的版本字段标记即可,不必每次改 URL 导致老客户端失效。
业务功能这一段值得细看。原文说:App 的 setting,数据库命名为app_setting,那么具体的业务功能为 setting;架构组的 wechat,数据库命名为arch_wechat,那么具体的业务功能为 wechat。这里藏着一条潜规则:数据库表前缀app_、arch_是物理归属标识,而暴露给前端的业务功能名把前缀剥掉,只保留语义部分。
这么做有两个好处。第一,前端不需要知道app_、arch_这种内部实现细节,业务功能和数据库表结构解耦;第二,一个业务功能下可以挂多张表,比如 setting 业务功能下可以有app_setting、setting_log、setting_template多张表,URL 层面仍然统一收纳在/setting/下,后续做权限控制也只需要对业务功能粒度的路径做规则配置。
3.3 HTTP 方法与行为映射:一张表把七个动作定死
规范给出了七个核心行为,每个行为绑定一个 HTTP 方法和固定路径后缀,避免同一个操作在不同接口里叫法不同:
| HTTP 方法 | 行为(方法名) | 路径后缀 | 说明 |
|---|---|---|---|
| POST | add(POJO) | /add | 添加对象,请求体接收 JSON |
| GET | insert(param...) | /insert?param=? | 与 add 作用相同,参数走 query |
| GET | deleteById(long) | /delete_by_id?id=? | 逻辑删除,数据库不物理删行 |
| POST | update(POJO) | /update | 整体更新对象,接收 JSON |
| GET | updateById(long) | /update_by_id?id=? | 按 ID 更新 |
| GET | getById(long) | /get_by_id?id=? | 按 ID 查询 |
| GET | listByParam(Object) | /list_by_param?param=?&page=2 | 列表查询,支持分页排序 |
原文的list_by_param拼写作list_bu_param,规范里应统一为list_by_param,与驼峰命名对齐。还有两点值得说明:deleteById 是逻辑删除,数据库里不删除数据,但业务上有删除语义,这是为了历史数据可追溯;update 和 add 都要求接收 JSON,适合前端提交复杂对象,insert 和 updateById 走 query 参数,适合调试时直接在浏览器里验证。
设计这套映射时我一般遵循一个原则:动作尽量少,语义尽量显式。如果团队里每个人都自己发明动词,比如 remove、del、modify,前端对接成本会成倍上升。七个动作覆盖了绝大多数 CRUD 场景,遇到批量操作补一个/batch后缀即可。为什么不严格用 REST 风格的 PUT/DELETE?很务实的原因:部分网关和代理环境对 PUT/DELETE 支持不友好,GET 和 POST 最稳定,而且团队成员读方法名就能理解行为,学习成本更低。
4. 请求参数与响应封装:从分页参数到 Result<T> 的实现细节
接口参数和响应体是前后端直接接触的部分,设计得好联调效率高,设计不好就变成黑匣子。这一章把通用查询参数、统一响应结构和 Java 错误码枚举的落地方式完整过一遍。
4.1 通用查询参数:limit、offset、page、per_page 怎么选
规范里给出了两组翻页方式:limit + offset 和 page + per_page,再加上排序参数 sort_by + order,以及筛选条件 product_type(原文写作 producy_type,应为笔误,规范中按正确拼写处理)。
limit=10:指定返回记录的数量。offset=10:指定返回记录的开始位置。page=2&per_page=100:指定第几页,以及每页的记录数。sort_by=name&order=asc:按 name 属性升序排序。product_type=1:指定筛选条件。
具体怎么选?offset/limit 适合数据量小、深度翻页要求不高的场景,SQL 直接LIMIT 10 OFFSET 10;page/per_page 适合后台管理列表,页面上有页码选择器,后端计算 offset 的方式是(page - 1) * per_page。深度翻页超过一万条时,这两种方式都会变慢,通常的做法是换游标方式,但规范里仍保留 offset 作为兜底。
排序参数必须白名单化,后端不能直接把用户传来的字段名拼进 ORDER BY,否则存在 SQL 注入风险。比如允许排序的字段只有name、created_at、amount,后端收到 sort_by 后先查白名单,不在名单里直接忽略排序,返回默认顺序。响应数据总条数统一放在data.total里,前端分页组件需要用它计算总页数。
4.2 统一响应体:Result<T> 的结构与前端拦截器
规范贴出的响应体 JSON 结构如下:code 是数字,success 是布尔,message 是提示,data 里放 list 数组和 total 总数,timestamp 是服务端时间戳。这套结构解决了一个常见问题:每个接口各自定义{status: 1}、{ret: "ok"},前端根本无法统一处理错误。
统一之后,前端 axios 拦截器可以收敛成一份代码:
// axios 响应拦截器 axios.interceptors.response.use( (response) => { const res = response.data if (res.code === 200 && res.success) { return res.data } // 非 200 统一走错误提示 alert(res.message) return Promise.reject(new Error(res.message)) } )这段拦截器的逻辑很简单:HTTP 层成功后再看业务层的 code 和 success,只要业务失败就统一提示并 reject。前端业务代码里拿到的已经是res.data,不用再关心外层包装字段,错误处理也收敛到一个地方。
这里有一个容易踩的坑:HTTP 状态码和业务 code 是两回事。200 表示 HTTP 层请求成功,但业务可能失败,所以必须再看 success 字段。比如登录过期,HTTP 返回 200,但业务 code 是 401,拦截器里需要单独处理跳转登录页,而不是直接 alert。
4.3 错误码枚举与 Result<T> 的 Java 落地
规范给出了 ResultCode 枚举和 Result 泛型类的源码。先看枚举:
public enum ResultCode { SC_OK_200(200, "操作成功"), SC_MISSING_ARGUMENT_400(400, "请求格式或参数不对"), SC_UNAUTHORIZED_401(401, "未授权"), SC_VALID_EXCEPTION(403, "请求参数校验失败"), SC_NOT_FOUND_404(404, "请求的地址未找到"), SC_METHOD_NOT_ALLOWED_405(405, "不支持的 HTTP 方法"), SC_INTERNAL_SERVER_ERROR_500(500, "内部程序错误"); private final Integer code; private final String msg; ResultCode(Integer code, String msg) { this.code = code; this.msg = msg; } public Integer getCode() { return code; } public String getMsg() { return msg; } }这个枚举做的事情是把状态码和提示语绑定,避免每个接口里散落魔法数字。注意 403 在这里的语义是参数验证错误,而不是权限不足,和 Spring Security 的 403 概念不一样,写代码时要在注释里特别标明,免得后端同事误用。
再看 Result 类的静态工厂方法。业务代码里只需要这样用:
// 查询用户列表 public Result<Object> userList() { List<User> list = userService.list(); return Result.ok(list); } // 参数校验失败 public Result<Object> badParam() { return Result.validException("用户ID不能为空"); }这段代码把组装响应结构这件事从业务方法里彻底解放出来:业务代码只关心返回什么数据和抛出什么错误,统一包装交给 Result 类。需要补充的是,实际项目里异常不是每个方法手动抛,而是写一个全局异常处理器,捕获参数校验异常、业务异常和未知异常,分别映射到 400、403、500,这样整个项目对错误码的使用才是可控的。
5. 落地避坑:这套接口规范最容易翻车的五个细节
规范写出来容易,落下去难。下面五个问题是我在项目里真实遇到过的,按「现象 → 原因 → 解决」来写,覆盖空值、金额、时间、路径和隐私五个高频翻车点。
5.1 空对象返回 null,前端渲染直接白屏
现象:接口没有数据时,data.list返回了 null,前端模板里一遍历就报错,页面直接白屏。测试环境有数据所以发现不了,生产环境某用户没有订单记录就触发了。
原因:后端在 service 层写了return null,序列化后变成"list": null,而规范要求空列表必须返回[]。问题根源是规范没有被强制执行,空值兜底只存在于文档里。
解决:在 service 层强制空列表返回Collections.emptyList(),或者在统一响应包装层做兜底,发现 list 为 null 就替换成空数组。这样无论哪个业务方法漏了,最终响应都不会出现 null。前端也约定好:只要接口按规范返回,view 层不再写判空逻辑。
5.2 金额单位没统一,账单金额差一百倍
现象:订单详情接口返回6800,积分接口返回68.00,前端展示时有的当元、有的除以 100,金额全对不上,上线第一天就收到客诉。
原因:有人习惯用「分」存金额,有人用「元」存,接口文档没有在字段注释里写死单位。这类问题隐蔽性很强,因为单看数字看不出问题,只有跨模块联调时才会暴露。
解决:按规范把金额单位统一为元。展示字段用字符串且带千分位,如6,800;计算字段用数字类型,单位为元。所有金额字段在接口文档里必须带单位注释,代码评审时凡是没写单位注释的金额字段直接打回。数据库存分的表在出接口时换算,换算逻辑集中在一个工具类里。
5.3 时间格式各传各的,排查问题全靠猜
现象:一部分接口返回2019-01-01 12:12:12,另一部分返回1546301532,前端为了兼容写了两个格式化函数,后端排查线上问题时还得先确认这是字符串还是时间戳。
原因:没有在规范层面规定时间格式,数据库里 datetime 类型序列化后是字符串,而某些接口直接返回了 long 时间戳,两边没有对齐。
解决:统一用字符串yyyy-MM-dd HH:mm:ss。后端实体类的日期字段上加序列化注解强制格式,前端封装一个全局的日期解析函数,接口文档里所有时间字段统一标注为字符串。如果涉及时区,在字段注释里写清楚 GMT+8,不要留给调用方猜。
5.4 路径语义混乱,接口一多就失控
现象:项目里有/getAllUser、/api/user_list、/users/queryAll三种风格共存,新同学根本不知道去哪找接口,同一个用户的查询逻辑散落在三个文件里。
原因:没有路径模板约束,每个人按自己习惯命名,有人用驼峰方法名,有人用下划线,还有人直接写动词开头。接口数量一多,整个模块变成无法维护的黑匣子。
解决:强制按/{版本}/{业务功能}/{表名}/{行为}设计路径,例如/api/v1/acs/users/list_by_param?page=1&per_page=10。发现不符合规范的路径,在评审阶段打回,不要等上线后统一重构。这个检查可以用脚本完成:扫描后端所有接口注解里的路径,正则校验是否匹配模板,不匹配的输出警告,建议放进 CI 每次提交自动跑一遍。
5.5 敏感字段明文返回,隐私数据裸奔
现象:手机号、身份证号原样返回,被第三方抓包后直接泄露,安全测试把这个问题标记为高危漏洞。排查发现脱敏规则每个接口各写各的,有的接口压根没写。
原因:没有在接口层做统一脱敏,数据库存什么就返回什么;只有个别前端发现需要脱敏时才在响应里处理,属于典型的控制面失控。
解决:按规范对手机号、身份证、邮箱、支付账号、邮寄地址统一加星号。通常的做法是写一个序列化注解,在字段上标注后自动脱敏,业务代码完全不用感知。同时对敏感度高的接口参数做签名匹配,客户端和服务器通过约定算法对参数值签名,防止参数在请求过程中被抓取篡改。签名校验不过的请求直接返回 400,不进入业务逻辑。
注意:脱敏是展示层兜底,签名是传输层兜底。两者配合使用才能在日志、数据库、网络链路三个环节都控制住隐私数据扩散。
6. 验收与答辩:怎么证明这套规范真的落地了
写规范的最终目的是被遵守。验收时我一般不做通读检查,而是建一份可勾选的清单,每一条对应一个可验证的动作,挨个过一遍更高效。
6.1 一份可执行的接口验收清单
| 检查项 | 验证动作 | 达标标准 |
|---|---|---|
| JSON 合法性 | 调用所有接口,用 JSON 解析器解析响应体 | 全部能按 application/json 解析 |
| 空值策略 | 构造空数据场景(空列表、空字符串) | 响应里不出现 null/undefined |
| 金额单位 | 抽查金额字段并对比数据库 | 单位统一为元,展示字段带千分位 |
| 路径模板 | 扫描全部接口路径 | 全部匹配 /{版本}/{业务}/{表名}/{行为} |
| 错误码 | 触发 400、401、403、404、500 | code 与提示语与规范一致 |
| 隐私脱敏 | 抓包检查手机号、身份证字段 | 均按规则打星号 |
更快的做法是写一段小脚本,把后端所有接口注解里的路径扫出来,正则校验路径模板是否合规,不合规的打印警告。这个脚本建议放进 CI,每次提交都跑一遍,把规范从纸面变成机器检查。接口数量多的时候,人工通读规范是最低效的验收方式,机器扫描十秒就能完成。
6.2 答辩时怎么讲这套设计
如果你在毕设或课设答辩中用到这套规范,记住三个讲法:先讲痛点,接口格式不统一导致联调成本高;再讲方案,四段式路径、统一响应体、错误码枚举、脱敏签名;最后拿一个真实接口完整走一遍,从请求到响应把规范点串起来,比空讲设计原则有说服力得多。
答辩老师最常问的问题是「为什么用 GET 做删除而不是 DELETE」。回答思路是:这套规范优先保证网关兼容性和团队可读性,逻辑删除本身不需要真正 DELETE 语义,GET 可以直接在浏览器验证;如果业务场景确实需要严格 RESTful 风格,可以留一个适配层做转换,不影响前端对接。
从那以后,我每接一个新的前后端分离项目,第一件事不是写业务,而是先把接口规范整体读一遍,强制自己跑通一个最小闭环再动手写功能。很多联调事故,其实在按规范跑通最小闭环那一刻就已经躲开了。希望帮到你。
本文还有配套的精品资源,点击获取