API设计规范

1. 原则

    原则是规范的基本设计思路, 在规范中无法找到相应的设计细节时,则应该按照设计的初衷,思路或者原则来判断应该如何进行设计

  • 接口应该尽量的少, 后端接口不应该因前端的简单格式调整或者查询字段的多少有多调整,请注意一个后端接口不应该仅仅服务于一个前端需求,而应该是服务于一堆类似的各种可能的前端资源请求需求
    理由:因前端需求变更通常大于后端变更,接口设计时应考虑多种设计需求,后端接口尽量不受前端需求变更影响
  • 出于前端或后端性能考虑而增加冗余接口是允许的
  • 规范为避归这种问题而设计, 很理想, 各个项目可根据自身实际情况和综合考虑成本与可行性进行适当的裁减或部分采纳甚至修改,但建议尽量多的遵从

2. API命名

2.1 API命名风格的选择

    关于接口风格的设计一直争论不休, 请看这里 https://www.zhihu.com/question/28570307

    根据接口设计目标的不同, 可以主要分成以下两类:

  • 给项目内部前端开发调内部接口
    设计目标: 部分接口对性能要求高, 接口逻辑场景复杂
    建议设计: 注意区分资源型接口与非资源型接口, 建议优先采用Restful设计, 但注意不要被Restful束缚, 性能是最重要的
    设计理由: Restful有太多的优点, 可以促进对系统设计的深入分析, 可以方便的生成文档, 更加具体的规范和约束, 有利于接口的一致性, 方遍形成外部接口, 但因它的设计并未优先考虑性能, 所以需要有所取舍和折中,不要为了Rest而Rest
  • 给第三方调用的外部开放接口
  • 服务间调用的内部接口

2.2 路径名

  • 路径名称不允许使用/结尾(需注意检查/结尾的路径是否是有效的)
    理由:搜索引擎优化角度考虑,一个同样的页面不应该有两个地址,会被搜索引擎认为是作弊,搜索引擎对无/的地址支持更加友好

  • 蛇底式命名风格(snake_case)
    关于分割 分隔符"-“与”_"的选择:

    1. -分隔符从大小写的键盘录入的角度属于小写(因不需要按Shift键),与URL中保持小写原则相一致(url的特定部分不区分大小写,且小写字母更容易录入)【缺点】
      -其实更容易识别 _会与下划线格式混淆(尤其在doc中书写地址的时候)【缺点】
    2. -用于连接和_用于连接语义上有区别,_通常用于连接含义相关的部分,而-用于连接相互较为独立的部分,这样看起来_语义上更接近 【优点】
      2.1 Google SEO 遵循这个语义 把-当空格处理,认为是三个关键词 会把_直接去掉,认为整体是一个单词
      2.2 大部分文本编辑器也遵循这个语义,双击选择时 _连接会认为是同一个单词同时选择,而-分隔的两个单词不会同时选中
      2.3 鉴于get传参与post传参的一致性,post body中的json也建议采用同样的命名规则。便于程序处理【可考究】
    3. 目前蛇底式与驼峰式较为常见,而烤串式不常见【优点】

    snake_case: 微信开放平台,腾讯开放平台,开源中国 百度,阿里云 gitee.com
    camelCase: 京东,百度,爱奇艺
    小写无连字符:百度百科

2.2.1 资源类接口(resource) 【RestFul风格】

    资源名(resources_name)既为接口名称,名词复数形式(批量获取时语义更加),尽量一个单词(简洁) ;

    RestFul风格, 基于资源的设计,HTTP Method作为动词,表示要对资源执行的动作

  • 单资源怎删改查
【GET】          /users/1           # 查看某个用户信息
【POST】         /users             # 新建用户信息【非幂等, 多次执行可能会新建多个用户】
【PUT】          /users/1           # 更新用户信息【幂等, 同一请求多次执行结果一定相同】, 局部替换或追加资源的字段, PUT也可以用于新增,但需是由客户端能够确定唯一主键的场景
【PATCH】        /users/1           # 对用户信息执行某一个动作【非幂等, 同一请求多次执行可能获得不同结果】, 例如登录错误计数+1 多次执行资源产生的结果不同
【DELETE】       /users/1           # 删除用户信息

    设计理由: PUT用于局部更新并不符合HTTP1.1和RFC5789规范,但依然这样设计,理由如下

  • GITLAB这样去实现, 虽然它不符合规范, Kuberenetes API则遵循了标准

  • 如果PUT只能用于全部替换, 那这个动词似乎没什么意义,而只是PATCH的一个特例

  • 简单批处理请求 (batch)

    同时处理多个资源, 但对每个资源的处理是一致的,比如更新同样的资源

【GET】          /users             # 查询用户信息列表
【PUT】          /users/1,2,3       # 更新用户信息【幂等, 同一请求多次执行结果一定相同】, 局部替换资源的字段,
【PATCH】        /users/1,2,3       # 对用户信息执行某一个动作【非幂等, 同一请求多次执行可能获得不同结果】, 例如登录错误计数+1 多次执行资源产生的结果不同
【DELETE】       /users/1,2,3       # 删除用户信息
  • 复杂批处理请求/混合请求(mixed)

    使用RFC6902 JSON Patch规范完成批量处理请求 https://tools.ietf.org/html/rfc6902

    原理:同时处理多个资源,但对每个资源的处理是不一致的,更新不一样的字段,甚至处理的动作也是不一致的
建议将动作封装到body中并组合成一个新的body发送给后端
PATCH JSON Patch规范进行批量操作:

  • 聚合请求/页面级请求

    用于首页聚合显示,展板展厅类页面
待研究… 目前建议后端封装专用接口,前端一次请求

2.2.2 非资源类接口(non-resource)【JSON-RPC风格】
禁止使用其它HTTP Method
POST  数据提交(意图修改服务器端的数据)
GET   数据获取  

    接口名使用动宾结构 通常接口对应一个动作或状态而不是操作一个资源, (例如:健康检查, 日志查看,附件上传)
可考虑名称细节继续规范化

2.2.3 资源间逻辑关系

    两个资源之间可能存在关系,例如班级的学生,老师的学生,用户的角色
    目前在资源从属关系上没有太多的实践经验,建议参考gitlab资源接口设计,gitlab从存在较为复杂的逻辑关系

通常建议:

  • 具有明确从属关系资源且不具备全局特点的接口,可以设计为类似于resource/subsource的形式
  • 具有全局特点的资源优先实现顶层接口, 优先设计全局接口resouces1, 可选设计为resource2/subsource1形式,用于应对多对多关系,后者只返回具有从属关系的部分数据
  • 建议设计为子资源而不是资源的一个属性(可以是数组类型的属性),应考虑子资源是否有一定的复杂性(具备多个字段的对象),应考虑子资源是否需要被独立检索

2.3 扩展名

  • 接口扩展名不允许带有框架相关信息
    例如: .action .do
    理由: 安全角度考虑,将保留后台实现方案,黑客可根据框架日志漏洞进行针对性攻击
  • 接口扩展名可以使用返回值格式作为扩展名或无扩展名实现, 建议无扩展名是默认返回格式为json
    例如: .json .xml
    理由: 前端语言对json最为友好,毕竟JSON是JavaScript Object Notation

2.4 传参规范

2.4.1 参数位置
  1. 用于排序,搜索,过滤等请求条件参数应放在请求字符串列表,不允许放在body中(尤其是post请求)
    理由:便于用户视觉识别或快速的手动在地址栏修改
  2. 用户排序,搜索,过滤等请求条件参数应在浏览器地址栏中显示(尤其适用react-route技术实现的应注意)
    理由:便于用户对连接进行分享
  3. 涉及用户敏感隐私的字段不允许出现在浏览器地址栏中,应放在请求body中
    例如:密码,Token
    理由:防止被用户身旁的人意外观察而被记忆
2.4.2 参数名称
  • 分页及排序参数

    参数规范名参数作用位置默认值备注
    page当前页码地址栏1
    per_page每页条目数地址栏20这个值必须有,用以计算需要返回的总条目数
    limit单次请求总条目数限制地址栏100防止意外查询过量数据, 后台也应做最大值限制
    sort升序或降序地址栏desc降序通常常用,更关心最近操作的条目
    order_by排序列地址栏id建议不要乱序或随机, 更关心最近插入的条目

理由:
    sort与order_by的参考gitlab api设计 order_by后面加上by可以避免与sort之间发生歧义 明确表示是排序字段或者排序列

  • 条目筛选参数(行筛选, 返回满足条件的行)

    行筛选的筛选参数名应与资源数据返回中的参数名一致,故建议采用蛇底式(snake_case)命名规范

  • 内容筛选参数(列筛选或关系筛选)

    内容筛选性参数均为布尔型,注意遵循2.3.3中对布尔值,未传值和传空值两种特殊情况的处理规范

    参数规范名参数作用位置备注
    simple只返回简要信息地址栏参考5.1.1 两分组列筛选设计规范
    [groupname]附加分组列信息地址栏参考5.1.2 多分组列筛选设计规范
2.4.3 参数值类型
参数类型取值规范备注
布尔型真: true或1 假: false或0未传值,按假值处理,传空值,按真值处理
数组型(GET传参)1,2,3可用于多条目修改删除, post传参时按相关content-type格式执行
时间日期型(优先)11位 GMT/UTC时间戳不带有时区信息,防止在多语言操作系统中造成服务器与客户端理解不一致,防止前端或后端的设计依赖特定的时间格式, 缺点不能飙到1970年前的时间
时间日期型(备选)ISO8601规范需要表达1970年以前的时间时考虑
枚举型枚举值或枚举名是不太判断得出哪个优先,可实践后获得结论
待补充…

3. 请求规范

  • GET请求不允许携带Body
    理由: 不符合HTTP规范,有些代理服务器会直接将GET请求的Body丢弃(这些代理服务器或中间服务器不一定在我们能控制的范围内)

  • 请求接口是应准确携带content-type头部, 用于表征请求的body中的数据类型,后端需要具此来解析对应body中的数据
    建议: 请求json格式的接口。建议携带content-type=application/json

    其它常用content-type值参考下表

    Content-Type:application/json                      (推荐,JsonBody提交, 尤其是参数复杂时, 可清晰的表达嵌套数据,可能是趋势,案例:京东,csdn,airbnb,bilibili)
    |-如果是base64个是的图片, 建议图片编码为字符串之后以json格式返回给前端
    Content-Type:application/x-www-form-urlencoded     (推荐,普通表单提交, 目前这种方式仍然居多)
    Content-Type:multipart/form-data                   (文件上传时, 用于带有文件上传的表单提交)
    Content-Type:application/x-protobuf                 Google ProtoBuf格式, 在前端使用protobuf格式目前仍存在争议, 但仍然可见使用案例(例如: 知乎前端数据分析接口)
    Content-Type:text/plain                             普通文本
    Content-Type:text/html; charset=utf-8               HTML文件
    Content-Type:text/css                               CSS文件
    Content-Type:application/javascript; charset=utf-8  JS文件
    Content-Type:image/png                              PNG图片
    Content-Type:image/gif                              GIF图片
    Content-Type:image/x-icon                           ico图片
    Content-Type:image/webp                             WEBP图片
    content-type:image/svg+xml                          SVG图片
    
    

4. 响应规范

  • 账户授权等敏感数据应存储于Cookie当中, 并设置HTTP-only属性,用以阻止js脚本读取权限,禁止存储于LocalStorage等其它存储技术
    理由: 目前只有cookie存储具备安全防御机制,也就是Http-only, 防御可能的跨站请求攻击(风险极大)

  • 响应后content-type必须与响应body的数据类型一致,前端会据此对响应body进行类型转换
    例如: json的body如果返回text/plain类型 那么前端接受时将识别body为字符串而不是json对象
    常用content-type值参考3.

    最合适的Ajax内容编码类型 https://segmentfault.com/a/1190000006871099

4.1 数据分页的响应规范

    建议采用 Link Header机制返回分页元信息, 以此不去破坏返回Body中存储的是资源内容的restful设计思想
https://git.d.com/help/api/README.md#pagination-link-header

4.2 错误码

4.2.1 返回码设计
  • 设计目标
    为了便于分析统计软件可以快速的统计异常
    为了快速确定谁要对错误进行处理
    为了快速定位产生异常的模块甚至代码位置
    尽量不破坏HTTP返回码的语义
    前端程序分类处理的便利

    错误码为7位数据

  • 0表示正常
    理由: 因正常只有一种,而错误有很多种, 0为正常便于程序中用if判断

  • 第1位表示错误来源
    2 客户端错误(用户自行解决,或管理员协助通过系统配置解决)
    4 客户端错误(需要研发介入,修复需要前端修改代码)
    5 服务端错误(临时异常,不需要研发介入,修复不需要修改代码)
    6 服务端错误(软件bug)

    第2-3位表示服务代码(便于统计)
    第4-7位表示具体错误(前端引导提示和后端查询)

4.2.2 全局结构
{
  "meta":{
    "code":"10000",   
    "msg": "ok";
  }
  "data": {} or [], # 内部格式由具体业务决定
}
4.2.3 异常结构
4.2.3.1 服务端异常结构(仅开发环境)
  • 多行文本类异常信息(仅开发环境)
        有些异常的反馈信息文本有多行,多行文本直接以字符串格式包装进json的话,前端的显示非常的丑陋(出现大量的\r\n)符号,因此建议将多行文本封装为字符串列表,以优化浏览器端显示

    ...
    "data": {
      "type":"text",
      "ip": "暴异常的结点IP",
      "exception": [
        "line1",
        "line2",
        "line3"
      ];
    }
    ...
    
  • 异常栈类型异常结构(仅开发环境)

    异常栈列表是有序的, 所以后端实现时应注意保序

...
"data": {
  "type":"exceptions",
  "ip": "暴异常的结点IP",
  "exception": {
    "exception_name1": "exception_message1",
    "exception_name2": "exception_message2",
    "exception_nam32": "exception_message3",
  }
}
...
  • 栈追踪(stacktrace)类型异常结构(仅开发环境)

    ...
    "data": {
      "type":"stacktrace",
      "ip": "暴异常的结点IP",
      "exception": 待定...
    }
    ...
    
  • 其它对象类型异常结构(仅开发环境)

    ...
    "data": {
      "type":"error",
      "ip": "暴异常的结点IP",
      "exception": {
         自定义Object
      }
    }
    ...
    
4.2.3.2 参数错误异常结构(全部环境)

    错误码: 2000301

    参数错误信息反馈在data域, data是一个数组,表示一组参数的错误,field字段值应该与参数名称一致,用于帮助前端实现时定位错误的输入框,message可供前端参考提示,但前端可以选择提示此信息也可以考虑以更人性化的方式给出合适的提示

...
"data": [{
  "field":"username",
  "message": "用户名不满足安全要求";
},{
  "field":"mobile",
  "message": "手机号格式不正确";
}]
...

5. 其它API功能设计

5.1 内容筛选(列筛选或关系筛选)

5.1.1 两分组列筛选

    接口返回的列字段中,可以分成两组,一组是简要信息,另外一组是扩展信息,建议使用布尔型参数simple进行列筛选

  • simple=true时 返回简要信息
  • simple=false时 返回完整信息(简要信息+扩展信息)
  • simple未传参时 返回完整信息(例如,符合语义,未传参未设置false)

    场景示例: 获取用户列表接口, simple=true时只返回用户信息,simple=false时,返回用户信息及用户所属的资源信息(用户的项目,用户的…)

5.1.2 多分组列筛选

    接口返回的列字段中,会被分为多个分组,为每个列分组其一个有含义的名称,当传参groupname=true或传递空值时 ,则返回,未传参或传递false时,不返回属于此分组的列

例如:
GET users?role=&project=
则返回用户基本信息,用户所属用户组信息,用户拥有项目信息

5.1.3 复杂字段筛选

    复杂字段筛选,可能会要求控制粒度到每个字段级别,建议使用GraphQL技术实现

A 返回码与HTTP状态值表

0表示正常【可选实现, 不确定这么做有什么实质性的好处】
0(200) 通用正常, 如果不知道用什么, 就是用这个
0(201) 创建成功, 可用于Rest接口的POST新增对象 
0(202) 可用于异步请求, 长任务, 利于短信, 邮件发送, 提示浏览器可以不必保持连接
0(204) 服务器成功响应,但是没有返回数据回, 通常可用于Rest接口的Delete功能
错误码为7位数据
第1位表示错误来源(放置于第一位, 便于程序判断和统计分类)  
 |-2 客户端错误(用户自行解决,或管理员协助通过系统配置解决)
 |-4 客户端错误(需要研发介入,修复需要前端修改代码)
 |-5 服务端错误(临时异常,不需要研发介入,修复不需要修改底阿妈)
 |-6 服务端错误(软件bug)
第2-3位表示服务代码  
 |-00 系统错误(一般为后端bug或故障导致,或多个服务通用的错误)
   |-400xxxx         客户端错误(返回结构中给出错误细节)  
     |-4000xxx(4xx)    HTTP客户端标准错误映射(一般未请求格式错误,需研发介入修正)
       |-4000400(400)    其它未细分的请求格式不正确(需研发介入修正)
       |-4000404(404)    请求资源【接口】不存在(通常需要研发或运维介入修正, 注意与数据不存在区分)
       |-4000405(405)    不支持的http-method(method错误一定是研发疏忽,需要研发介入修正)
       |-4000415(415)    不支持的Content-Type(需要介入修正)
       |-4000601(400)    Json语法不正确(需前端研发接入修正)
   |-200xxxx         用户可自行修正的系统公共错误(考虑前端处理便利, 根据所需处理进行细分,可能存在不同逻辑用返回值区分,相同逻辑不同提示用错误细节区分)
     |-20001xx(401)    认证类问题(表达用户提供的身份信息的异常)
       |-2000100(401)    其它位置的认证类问题
       |-2000101(401)    需要认证类接口但未提供任何认证信息, 但不确定登录后是否有接口权限或数据权限,前端应根据情况适当进行登录引导  
       |-2000102(401)    认证信息已失效(例如Token)
       |-2000103(401)    认证信息不可用(例如Token)
     |-20002xx(200)    鉴权类问题(表达用户提供合理身份信息后对接口和数据的所有权限异常)
       |-2000201(200)    请求的【数据】无权限访问,但接口有权限,无法通过登录来解决问题,用户只能放弃访问,或联系管理员授权
       |-2000202(200)    请求的【接口】无权限访问,通常无权限的接口不应引导调用,但也可能引导用户申请权限  
     |-20003xx         请求数据的格式类问题
       |-2000301(200)    其它非细分的请求格式或参数不正确
       |-2000304(200)    请求的【数据】不存在,注意与接口不存在相区分,接口不存在需要研发介入,数据不存在用户可自行修正
   |-500xxxx   服务端错误(返回结构中开发环境给出细节,生产环境之给响应值,不给细节)
     |-50001xx(503)  数据库相关网络异常
       |-5000100(503)  数据库连接异常(配置错误,网络异常等)
       |-5000101(503)  数据库连接异常(数据库被正常关闭)
       |-5000102(503)  数据库连接异常(数据库异常退出)
     |-5000200(503)  负载均衡结点(无可用结点,结点配置错误或网络连接异常)
     |-50005xx(503)  服务端HTTP网络异常(临时网络故障[可能自动修复], 配置错误[不可自动修复])
       |-5000502(503)  HTTP协议网络连接异常 对应HTTP502
       |-5000503(503)  HTTP协议网络连接异常 对应HTTP503 与之上的区别是tcp可以是否可以连通
       |-5000504(503)  HTTP协议网络连接异常 对应HTTP504 (连接超时)
   |-600xxxx(500)   初步识别的软件bug(需程序员介入处理)
     |-6000000(500)   服务端未知错误(软件中未识别和处理的软件bug)
       |-6000001(500)   空指针异常
     |-60001xx(500)     数据库相关错误(未判断出来的)
       |-6000101(500)     SQL语法错误
       |-6000102(500)     数据库返回值不符合预期(SelectOne时返回多条记录)

 |-01 用户权限系统(非通用部分)
 |-02 文件管理/上传下载系统
 |-xx 其它服务代码(一般为用户参数输入错误,业务系统建议使用30以上代码)
第4-7位表示具体错误 
 |-xx 具体的错误代码(由具体服务定义)

B 参考文献

错误码设计 https://blog.csdn.net/yzzst/article/details/54799971
聊聊RESTful https://howardwchen.com/2017/09/18/talk-about-restful-popular-api-design-1/
Kubernetes API规约 https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#patch-operations
【主要参考】Restful风格的接口设计 https://juejin.im/entry/59b8d34c6fb9a00a4455dd04
腾讯开放平台 http://open.qq.com/
URI设计原则 https://stackoverflow.com/questions/1619152/how-to-create-rest-urls-without-verbs/1619677#1619677
Gitlab API V3 https://link.zhihu.com/?target=https%3A//developer.github.com/v3/
返回值的处理 https://www.v2ex.com/t/340607?p=2
RESTful API定义及使用规范 https://zhuanlan.zhihu.com/p/31298060
HTTP状态值https://zhuanlan.zhihu.com/p/31298060
http://wiki.open.qq.com/wiki/v3/user/get_info open.weibo.com
http://wiki.open.qq.com/wiki/website/%E5%BE%AE%E5%8D%9A%E7%A7%81%E6%9C%89%E8%BF%94%E5%9B%9E%E7%A0%81%E8%AF%B4%E6%98%8E
https://docs.open.alipay.com/common/105806
http://open.weibo.com/wiki/Error_code
https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419318634&token=&lang=zh_CN
http://lbs.amap.com/api/webservice/info/

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐