RESTFul API 设计规范

0. API设计满足关键点

  1. API应当基于 web 标准来设计
  2. API应当对开发者友好并且便于在浏览器地址栏中浏览和探索
  3. API应当是简单、直观和一致的,使它用起来方便和舒适
  4. API应当是高效的,同时要维持和其他需求之间的平衡

1. API命名

1.1 根地址

好的RESTful API要基于HTTPS来发布 API规模不大时,在域名后面增加 api 目录,如:https://www.trawe.cn/api/ API规模很大时,使用以api开头的二级域名,如:https://api.trawe.cn/

1.2 版本问题

  1. 新版本尽量对旧版本作兼容

  2. 版本信息放在URL中

https://api.trawe.cn/v1.2/users/123
  1. 协议报文中增加version字段
{
    version: "1.0",.... ....
}
  1. HTTP Header中增加版本信息
使用已的HTTPHeader:Accept Header:Accept: application/json+v1.2
自定义 Header:             X-Api-Version: 1.2

1.3 端点设计原则

1) 命名

  1. CRUD操作一律使用名词,不使用动词
  2. url一律使用小写字母
  3. url命名方式不使用 camel方式,采用 - 连接两个单词,如:app-setups,而不是appSetups
  4. 请求参数命名方式使用 下划线 “_” 连接两个单词(Javascript规范),如:user_name,而不是userName或user-name
  5. 在url中不要出现 get / add / delete / put / modify / update 等动词,使用HTTP Method来替代

2) 无论对于单个资源还是集合,名词都使用复数形式,这样便于风格的统一

GET  /tickets      # 获取 tickets 列表
GET  /tickets/12     # 获取一个单独的 ticket
POST /tickets        # 创建一个新的 ticket
PUT  /tickets/12         # 更新 ticket #12
PATCH /tickets/12     # 部分更新 ticket #12
DELETE /tickets/12   #  删除 ticket #12
GET /tickets/12/messages     #  获取ticket #12下的消息列表
GET /tickets/12/messages/5     #  获取ticket #12下的编号为5的消息
POST /tickets/12/messages     #  为ticket #12创建一个新消息
PUT /tickets/12/messages/5     #  更新ticket #12下的编号为5的消息
PATCH /tickets/12/messages/5     #  部分更新ticket #12下的编号为5的消息
DELETE /tickets/12/messages/5    #  删除ticket #12下的编号为5的消息

3) 对于非CRUD的操作 有很非CRUD服务,可以把这些服务看成资源,计算的结果是资源的presentation,按服务属性选择合适的HTTP方法。

1. 重新构造这个Action,使得它像一个资源的操作。
    这种方法在Action不包含参数的情况下可以奏效。例如一个有效的action可以映射成布尔类型field,并且可以通过PATCH更新资源。
2. 利用RESTful原则像处理子资源一样处理它。
    例如:Github的API让你通过PUT /gists/:id/star 来 star a gist ,而通过DELETE /gists/:id/star来进行 unstar 。
3. 有时候你实在是没有办法将Action映射到任何有意义的RESTful结构。
    例如:多资源搜索没办法真正地映射到任何一个资源接入点。这种情况,/search 将非常有意义,虽然它不是一个名词,但是这样做没有问题,只需要从API消费者的角度做正确的事,并确保所做的一切都用文档清晰记录下来了即可。

4) 查询过滤 过滤: 对每一个字段使用一个唯一查询参数,就可以实现过滤。 例如: 当通过 /tickets 终端来请求一个票据列表时,我们需要增加一些限定来查询那些在售的票。可以使用 GET /tickets?state=open 这样的请求来实现。这里“state”是一个实现了过滤功能的查询参数。

对于常用的查询,有以下两种处理:

  1. 可以单独将查询包装为一个独立的API 如:GET /trades?status=closed&sort=created,desc 可以包装GET /trades/recently-closed
  2. 查询结果标签化 将经常使用的、复杂的查询标签化,降低维护成本。如:GET /trades?status=closed&sort=created,desc 可以标签化为 GET /trades#recently-closed

排序: 跟过滤类似,使用排序参数字段来描述排序的规则。 为适应复杂排序需求,让排序参数采取逗号分隔的字段列表的形式,每一个字段前都可能有一个负号来表示按降序排序。 例如:

排序字段前面的 +表示升序   -表示降序  默认为升序
GET /tickets?sort=-priority  # 获取票据列表,按优先级字段降序排序
GET /tickets?sort=-priority,created_at  # 获取票据列表,按“priority”字段降序排序。在一个特定的优先级内,较早的票排在前面。

5) 减少层级深度 /在url中表达层级,用于按实体关联关系进行对象导航,一般根据id导航。 过深的导航容易导致url膨胀,不易维护,如 GET /zoos/1/areas/3/animals/4,尽量使用查询参数代替路径中的实体导航,如GET /animals?zoo=1&area=3

6) 限制返回哪些字段 使用一个字段查询参数,它包含一个 用逗号隔开的字段列表。例如,下列请求获得的信息将刚刚足够展示一个在售票的有序列表: GET /tickets?fields=id,subject,customer_name,updated_at&state=open&sort=-updated_at

7) 关于返回结果 通常情况下只返回JSON格式结果。 返回结果支持gzip压缩:进行gzip压缩的数据可节省50%以上的带宽 对于需要返回不同格式资源的情况:

  1. 在HTTP Header中指定格式 如:Accept:application/xml;q=0.6,application/atom+xml;q=1.0
  2. URL后缀增加扩展名: 如:/users/1.xml

8) 分页 常用的分页字段如下:

limit=10:指定返回记录的数量
offset=10:指定返回记录的开始位置。
page=2&per_page=100:指定第几页,以及每页的记录数。

9) 缓存 ETag: 当产生一个请求时,包含一个HTTP 头,ETag会在里面置入一个和表达内容对应的哈希值或校验值。这个值应当跟随表达内容的变化而变化。现在,如果一个入站HTTP请求包含了一个If-None-Match头和一个匹配的ETag值,API应当返回一个304未修改状态码,而不是返回请求的资源。 Last-Modified: 基本上像ETag那样工作,不同的是它使用时间戳。在响应头中,Last-Modified包含了一个RFC 1123格式的时间戳,它使用If-Modified-Since来进行验证。注意,HTTP规范已经有了 3 种不同的可接受的日期格式 ,服务器应当准备好接收其中的任何一种。

2. HTTP方法

常用:

GET (选择):从服务器上获取一个具体的资源或者一个资源列表。
POST (创建): 在服务器上创建一个新的资源。
PUT (更新):以整体的方式更新服务器上的一个资源。
PATCH (更新):只更新服务器上一个资源的一个属性。
DELETE (删除):删除服务器上的一个资源。

不常用:

HEAD : 获取一个资源的元数据,如数据的哈希值或最后的更新时间。
OPTIONS:获取客户端能对资源做什么操作的信息。

3. 数据报文

  1. 统一报文格式
请求使用JSON格式时:
{
    method: 'testMethod',// 请求方法名称
    version: '1.0',// 接口版本
    token: '0X十六进制',//  Token
    sign_type: 'MD5',//  签名算法
    sign: '0X十六进制',//  签名
    timestamp: '12345678'    // 请求的时间戳
    
}

响应:
{
    code: 0,// 返回码。 -1:失败  0:成功  其它:具体业务代码
    message: '处理成功',// 返回码描述信息
    sign: '0X十六进制',// 签名
    ...                                   // 业务数据
}

响应数据不作多余包装,如下为错误示例:

{
    code: 0,data: {userName: "用户名"} // 这里面的data没有业务含意,仅仅是为了包装,所以应该去掉
}

需要进行包装的情况:

  1. 使用JSONP进行跨域请求
  2. 当客户端没有能力处理HTTP头信息时
  1. 对于日期类型的字段处理
  1. 一种方式为:转为一定格式的字符串,如 yyyy-MM-dd HH:mm:ss.SSS
  2. 另一种方式为:转为长整型数字时间戳

4. 安全

  1. 使用Https传输数据
  2. 使用Token(如JWT)来标识用户状态并设置失效时间
  3. Token失效后客户端自动重新登录获取新的Token
  4. 发送请求时对参数按ASCII排序计算签名(Hash算法或对称加密算法,可以所有接口统一密钥,也可以一个接口一个密钥),接收到请求后先验证签名
  5. 每个端(Android、iOS、微信服务号、Web网站)生成一个AppKey
  6. 不设置密码,登录时使用手机+验证码方式登录

5. 文档

  1. 文档须提供从请求到响应整个循环的示例;
  2. 请求应该是可粘贴的例子,要么是可以贴到浏览器的链接,要么是可以贴到终端里的curl示例 ;
  3. 一旦发布一个公开的API,必须承诺 在没有通知的前提下,不会更改API的功能
  4. 对于外部可见API的更新,文档必须包含任何将废弃的API的时间表和详情;

6. HTTP状态代码

HTTP定义了一套可以从API返回的有意义的状态代码。 这些代码能够用来帮助API使用者对不同的响应做出相应处理。

200 OK (成功)  -  对一次成功的GET,PUT,PATCH 或 DELETE的响应。也能够用于一次未产生创建活动的POST
201 Created (已创建)  -  对一次导致创建活动的POST的响应。 同时结合使用一个位置头信息指向新资源的位置
204 No Content (没有内容) - 对一次没有返回主体信息(像一次DELETE请求)的请求的响应
304 Not Modified (未修改) - 当使用HTTP缓存头信息时使用304
400 Bad Request (错误的请求) - 请求是畸形的,比如无法解析请求体
401 Unauthorized (未授权) - 当没有提供或提供了无效认证细节时。如果从浏览器使用API,也可以用来触发弹出一次认证请求
403 Forbidden (禁止访问) - 当认证成功但是认证用户无权访问该资源时
404 Not Found (未找到) - 当一个不存在的资源被请求时
405 Method Not Allowed (方法被禁止) - 当一个对认证用户禁止的HTTP方法被请求时
410 Gone (已删除) - 表示资源在终端不再可用。当访问老版本API时,作为一个通用响应很有用
415 Unsupported Media Type (不支持的媒体类型) - 如果请求中包含了不正确的内容类型
422 Unprocessable Entity (无法处理的实体) - 出现验证错误时使用
429 Too Many Requests (请求过多) - 当请求由于访问速率限制而被拒绝时

7. 错误处理

原则

  1. 不要发生了错误但给2xx响应,客户端可能会缓存成功的http请求;
  2. 正确设置http状态码,不要自定义;
  3. Response body 提供 1) 错误的代码(日志/问题追查);2) 错误的描述文本(展示给用户)。

API 可能抛出两类异常:业务异常和非业务异常。 业务异常由自己的业务代码抛出,表示一个用例的前置条件不满足、业务规则冲突等,比如参数校验不通过、权限校验失败。 非业务类异常表示不在预期内的问题,通常由类库、框架抛出,或由于自己的代码逻辑错误导致,比如数据库连接失败、空指针异常、除0错误等等。

业务类异常必须提供2种信息:

  1. 如果抛出该类异常,HTTP 响应状态码应该设成什么;
  2. 异常的文本描述;

在Controller层使用统一的异常拦截器:

  1. 设置 HTTP 响应状态码:对业务类异常,用它指定的 HTTP code;对非业务类异常,统一500;
  2. Response Body 的错误码:异常类名
  3. Response Body 的错误描述:
  4. 对业务类异常,用它指定的错误文本;
  5. 对非业务类异常,线上可以统一文案如“服务器端错误,请稍后再试”;
  6. 开发或测试环境中用异常的 stacktrace,服务器端提供该行为的开关。

8. 超媒体API

RESTful API最好做到Hypermedia,即返回结果中提供链接,连向其他API方法,使得用户不查文档,也知道下一步应该做什么。 比如,当用户向api.example.com的根目录发出请求,会得到这样一个文档。

{"link": {
  "rel":   "collection https://www.example.com/zoos","href":  "https://api.example.com/zoos","title": "List of zoos","type":  "application/vnd.yourformat+json"
}}

上面代码表示,文档中有一个link属性,用户读取这个属性就知道下一步该调用什么API了。rel表示这个API与当前网址的关系(collection关系,并给出该collection的网址),href表示API的路径,title表示API的标题,type表示返回类型。 Hypermedia API的设计被称为HATEOAS。Github的API就是这种设计,访问api.github.com会得到一个所有可用API的网址列表。

{
  "current_user_url": "https://api.github.com/user","authorizations_url": "https://api.github.com/authorizations",// ...
}

从上面可以看到,如果想获取当前用户的信息,应该去访问api.github.com/user,然后就得到了下面结果。

{
  "message": "Requires authentication","documentation_url": "https://developer.github.com/v3"
}

上面代码表示,服务器给出了提示信息,以及文档的网址。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


什么是设计模式一套被反复使用、多数人知晓的、经过分类编目的、代码 设计经验 的总结;使用设计模式是为了 可重用 代码、让代码 更容易 被他人理解、保证代码 可靠性;设计模式使代码编制  真正工程化;设计模式使软件工程的 基石脉络, 如同大厦的结构一样;并不直接用来完成代码的编写,而是 描述 在各种不同情况下,要怎么解决问题的一种方案;能使不稳定依赖于相对稳定、具体依赖于相对抽象,避免引
单一职责原则定义(Single Responsibility Principle,SRP)一个对象应该只包含 单一的职责,并且该职责被完整地封装在一个类中。Every  Object should have  a single responsibility, and that responsibility should be entirely encapsulated by t
动态代理和CGLib代理分不清吗,看看这篇文章,写的非常好,强烈推荐。原文截图*************************************************************************************************************************原文文本************
适配器模式将一个类的接口转换成客户期望的另一个接口,使得原本接口不兼容的类可以相互合作。
策略模式定义了一系列算法族,并封装在类中,它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
设计模式讲的是如何编写可扩展、可维护、可读的高质量代码,它是针对软件开发中经常遇到的一些设计问题,总结出来的一套通用的解决方案。
模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中,使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
迭代器模式提供了一种方法,用于遍历集合对象中的元素,而又不暴露其内部的细节。
外观模式又叫门面模式,它提供了一个统一的(高层)接口,用来访问子系统中的一群接口,使得子系统更容易使用。
单例模式(Singleton Design Pattern)保证一个类只能有一个实例,并提供一个全局访问点。
组合模式可以将对象组合成树形结构来表示“整体-部分”的层次结构,使得客户可以用一致的方式处理个别对象和对象组合。
装饰者模式能够更灵活的,动态的给对象添加其它功能,而不需要修改任何现有的底层代码。
观察者模式(Observer Design Pattern)定义了对象之间的一对多依赖,当对象状态改变的时候,所有依赖者都会自动收到通知。
代理模式为对象提供一个代理,来控制对该对象的访问。代理模式在不改变原始类代码的情况下,通过引入代理类来给原始类附加功能。
工厂模式(Factory Design Pattern)可细分为三种,分别是简单工厂,工厂方法和抽象工厂,它们都是为了更好的创建对象。
状态模式允许对象在内部状态改变时,改变它的行为,对象看起来好像改变了它的类。
命令模式将请求封装为对象,能够支持请求的排队执行、记录日志、撤销等功能。
备忘录模式(Memento Pattern)保存一个对象的某个状态,以便在适当的时候恢复对象。备忘录模式属于行为型模式。 基本介绍 **意图:**在不破坏封装性的前提下,捕获一个对象的内部状态,并在该
顾名思义,责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为
享元模式(Flyweight Pattern)(轻量级)(共享元素)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结