# RESTful 接口开发规范 版本:v1.0 时间:2019-01-28 [TOC] ## REST概念 REST是Representational State Transfer的英文缩写,翻译成中文是"表述性状态转移"。具体含义下一章节会做详细解释 REST是一种软件架构风格,不是一种标准,它提供一组设计原则和约束条件,满足这些设计原则和约束条件的架构设计就是RESTful架构风格 ## RESTful Architectural Style ### RESTful架构风格特点 - 资源 - URI - 统一接口 - 无状态 **资源** 资源就是网络上的一个实体,可以是一个对象,也可以是一种关系。资源需要通过某种载体来表现其内容,例如:图片可以用JPG格式表现,也可以用PNG格式表现,而JSON数据格式是现在最常用的资源表现形式。 **URI(统一资源定位符)** 每一个资源可以用一个特定的URI指向它,要获取这个资源,访问它的URI就可以,因此URI成了每一个资源的独一无二的地址或识别符。 **统一接口** 数据的元操作,即CRUD(create、 read、 update、delete),分别基于HTTP方法:POST,GET,PUT,DELETE统一了数据操作的接口,仅通过HTTP方法,就可以完成对数据的所有操作 **无状态** 状态应该区分应用状态和资源状态,客户端负责应用状态,服务端负责资源状态。客户端与服务端的交互是无状态的,在每一次请求中包含处理该请求所需的一切信息。服务端不需要在请求间保留应用状态。这种无状态通信原则,使得在多次请求中,同一客户端不再需要依赖于同一服务器,可实现高可扩展和高可用性的服务端。 回过头来,再来理解一下REST的定义"表述性状态转移",其实它忽略了一个主语"资源" - 资源表述性状态转移。因此结合RESTful架构风格的特点,可以理解REST就是:我们可以通过URI来唯一的定位一个资源,资源通过例如JSON对象来表示,当发生前后端无状态交互时,通过HTTP的GET,POST,PUT,DELETE方法对应create、 read、 update、delete来修改资源的状态。 ### RESTful架构风格优势 java几种典型的软件架构 - 分布式对象(Distributed Objects,简称DO) 架构实例有CORBA/RMI/EJB/DCOM, 面向对象,语言相关,客户端服务器紧耦合,API接口不统一 - 远程过程调用(Remote Procedure Call,简称RPC) 架构实例有SOAP/XML-RPC/DWR,面向过程,语言相关,客户端服务器紧耦合,API接口不统一 - 表述性状态转移(Representational State Transfer,简称REST) 架构实例有HTTP/WebDAV,面向资源,语言无关,客户端服务器松耦合,API接口统一 当今的软件开发过程中,往往需要与许多外部系统集成或者调用众多外部服务,REST这种与开发语言无关,仅仅基于HTTP的统一接口就能完成数据交互的特点,再加之客户端服务器松耦合,无疑带来了巨大的便利以及开发效率的提升。 ## RESTful API开发规范 ### API设计的基本准则 - 当准则合理,且满足条件时,遵守准则 - API应该面向资源定义 - API应该保证GET,PUT,POST,DELETE分别对应query,update,create,delete操作 - API应该对程序员友好,并且在浏览器地址栏容易输入 - API应该简单,直观,容易使用,并且尽量保证优雅 - API应该具有足够的灵活性来支持前端业务需求 ``` 说明一点:API的就是程序员的UI,和其他UI一样,必须仔细考虑它的用户体验!!! ``` ### RESTful API设计和开发细则 下面将详细列出一些RESTful API的设计和开发规范: 1. 文档 ``` 文档和API一样重要,前期RESTful API文档插件调研,以选择apiggs作为内部RESTful API文档生成器,具体使用方法和文档模板,请参考 forever-play > resourcers 下面的 《Apiggs RESTful API文档生成工具使用指南》。 最终所有API的接口文档必须生成放在项目指定apiDocs目录 ``` 2. **使用https协议** ``` 使用https安全,如果不使用https,请使用其他安全方案,比如使用token等其他认证方案。 ``` 3. **版本号放入URL** ``` 在API上加入版本信息可以有效的防止用户访问已经更新的API,同时也能让不同主要版本之间平稳过渡 get api/v1/users/good 获得用户列表 v表示版本 1版本号.URL中不使用1.1带小版本的子版本号,如果需要放入HEAD中 ``` 4. **请求头公共参数** ``` 每个请求公有的参数,比如token、请求客户端类型、请求来源(前台展示还是后台管理); 公共参数放在请求header中: 请求头参数|是否必须 | 说明 |---|---|---| |x-app | 是 | 客户端类型:*-ios,*-android,*-pc,*-h5| |x-lang | 是 | 客户端语言:'en-us','zh-cn'等,默认中文| |x-orginal | 是 | 请求来源:0-前台展示调用,默认;1-后台管理| |x-token | 是 | 认证凭证| 其他,后续根据实际情况添加. ``` 5. **只提供json返回格式(其实不仅仅返回json)** ``` 对于响应返回的格式,JSON 因为它的可读性、紧凑性以及多种语言支持等优点,成为了 HTTP API 最常用的返回格式,因此采用JSON 作为返回内容的格式。如果用户需要其他格式,比如xml,应该在请求头部 Accept 中指定。 对于不支持的格式,服务端需要返回正确的status code,并给出详细的说明。 Accept:application/json 告诉服务器客户端接受JSON格式返回 Content-Type:application/json 告诉服务器请求内容为JSON格式 ``` 6. **使用自定义错误编码**(~~使用http状态码作为错误提示~~) ``` 使用http状态码作为错误提示是符合rest接口规范。 业界现在有两种做法: 1. 使用http状态码 比如: 200 请求成功接收并处理,一般响应中都会有 body 404 客户端要访问的资源不存在,链接失效或者客户端伪造 URL 的时候回遇到这个情况 ... 2. 使用自定义返回的错误码 比如: 1000 系统错误 2001 用户不存在 2002 用户名称已经存在 ... 建议使用自定义的状态。 原因: http状态码可能不够用(暂时肯定够); 使用自定义返回的错误码更加灵活; 便于后台代码统一处理; 推荐而已; ``` 7. **URI Path尽量使用名词,不使用动词,把每个URL看成一个资源** ``` get /getUserList // bad 获得所有用户列表 get /getActiveUserList //bad 获得所有激活的用户列表 get /api/v1/users // good 获得所有用户列表 get /api/v1/users?state=active // good 获得所有激活的用户列表 rest只是一种接口规范不是标准.尽量遵守即可.有的接口不好用名词描述或者描述会让人很难理解,特殊情况我们还是可以使用动词进行描述. 比如:登录|注销 post /api/v1/session //登录接口 delete /api/v1/session //注销接口 十分不易与理解接口的真实含义,此时用大家非常熟悉的词语,即使是动词也未尝不可,只要注意在文档中写清楚就可以了。 post /api/v1/login //登录接口 delete /api/v1/logout // 注销接口 get /api/v1/search // 搜索 请记住,应该简单,直观,容易使用,并且尽量保证优雅 ``` 8. **避免URI Path最后带'/'** ``` get /api/v1/users/ // bad get /api/v1/users // good 最后带'/'与不带意思完全不一样,甚至两个不同的接口 ``` 9. **资源名称推荐用复数名词** ``` get /api/v1/users // good 查询用户列表 get /api/v1/users/1 // good 查询用户1的信息 get /api/v1/user // bad 查询用户列表 get /api/v1/user/1 // bad 查询用户1的信息 事实上,这是个人爱好问题,但复数形式更为常见。 此外,在资源集合URL上用GET方法,它更直观,特别是get /api/v1/users?state=active。 但最重要的是:避免复数和单数名词混合使用,这显得非常混乱且容易出错。 ``` 10. **为关系使用子资源** ``` 多级资源命名: get /api/v1/users/1/roles //查看用户1的角色信息 get /api/v1/roles/admin/users // 查看管理员的角色列表 如果资源层级太深,可以使用除了第一级,其他级别都用查询字符串表达。 get /api/v1/roles?users=1 //查看用户1的角色列表 get /api/v1/users?roles=admin // 查看管理员的角色列表 ``` 11. **使用小驼峰命名法** ``` 使用小驼峰命名法作为属性标识符。 { "yearOfBirth": 1982 } 不要使用下划线(year_of_birth)或大驼峰命名法(YearOfBirth)。 通常,RESTful Web服务将被JavaScript编写的客户端使用。 客户端会将JSON响应转换为JavaScript对象(通过调用var person = JSON.parse(response)),然后调用其属性。 因此,最好遵循JavaScript代码通用规范。 person.year_of_birth // 不推荐,违反JavaScript代码通用规范 person.YearOfBirth // 不推荐,JavaScript构造方法命名 person.yearOfBirth // 推荐 ``` 12. **对可选的、复杂的查询参数,使用查询字符串(?)** ``` get /activeUsers //bad get /disableUsers //bad 为了让你的URL更小、更简洁。 为资源设置一个基本URL,将可选的、复杂的参数用查询字符串表示。 get /api/v1/users?state=active&sex=male //good ``` 13. **使用HTTP动词(GET,POST,PUT,DELETE)作为action操作URL资源** ``` get 查询 post 添加 patch 修改 put 修改(PUT 是幂等的,而 PATCH 不是幂等的,一般用put就ok) delete 删除 推荐 get /api/v1/users // good 查询用户列表 post /api/v1/users //good 添加用户 put /api/v1/users //good 编辑用户 delete /api/v1/users/1 //good 删除参数 get请求传参使用url请求参数传参 post|put请求使用body传参,json格式 delete请求使用url路径传参,如果是批量删除使用body传参,json格式 get /api/v1/users?state=active //查询激活用户列表 post /api/v1/users //添加用户 body: { id:1, name:'duanledexianxian', state:'active', .... } put /api/v1/users //修改用户 body: { id:1, name:'duanledexianxian', state:'active', .... } delete /api/v1/users/1 //删除参数 1是用户id delete /api/v1/users //批量删除参数 or delete /api/v1/users/batch //批量删除参数 { ids:[1,2,....] } ``` 14. **Query Filter信息** ``` 例如: pageNum:第几页,默认0 pageSize:每页条数,默认10 sort:column1,column2排序字段 orderby:排序规则,desc或asc q:搜索关键字(uri encode之后的) 建议将查询参数封装成QueryCondition的dto对象,通过filter放入TreadLocal中,用的时候直接从TreadLocal获取. ``` 15. **返回结果** ``` 1. GET:返回资源对象 2. POST:返回新生成的资源对象 3. PUT:返回完整的资源对象 4. DELETE:返回一个空文档 统一返回格式: { code:'SUCCESS',//成功返回'SUCCESS',否则返回自定义的错误编码 message:'错误描述信息',//如果错误,则还有错误描述信息属性字段 data:{}// 返回数据内容 } 分页查询返回结果格式 统一返回格式: { code:'SUCCESS',//成功返回'SUCCESS',否则返回自定义的错误编码 data:{ pageNum:0,//分页页面 pageSize:10,//分页大小 total:100,//记录总数 pages:10,//分页页数 list:[...]//列表内容 }// 返回数据内容 } 新增操作需要返回新增资源的资源唯一编号,比如:新增用户 { code:'SUCCESS',//成功返回'SUCCESS',否则返回自定义的错误编码 data:{ id:1,//用户编号 name:'duanledexianxianxian',//用户名称 ... }// 返回数据内容 } ``` 16. **速率限制(暂时不需要)** ``` 1. X-RateLimit-Limit: 每个IP每个时间窗口最大请求数 2. X-RateLimit-Remaining: 当前时间窗口剩余请求数 3. X-RateLimit-Reset: 下次更新时间窗口的时间(UNIX时间戳),达到下个时间窗口时,Remaining恢复为Limit 如果对访问的次数不加控制,很可能会造成 API 被滥用,甚至被 DDos 攻击。 根据使用者不同的身份对其进行限流,可以防止这些情况,减少服务器的压力。 暂时不需要,以后做限流控制、访问控制的时候会用的上 ``` 17. **限制返回值的域,fields=id,subject,customerName** ``` 比如:/api/v1/users?fields=id,name,....//获取用户列表,包含的字段包括id、名称.可以一起处理 ``` *注意* ``` 对于REST未覆盖的内容: 1. Hypermedia API(HATEOAS),通过接口URL获取接口地址及帮助文档地址信息, 暂时不需要,如果需要实现前后台都需要改动 2. 缓存,使用ETag和Last-Modified 暂时不需要,做性能优化的时候,根据需要添加 3. 文件上传,下载 * 未完待续..... ``` ## Reference 1. RESTful API 设计最佳实践 2. RESTful Representational State Transfer 表现层状态转化 https://blog.csdn.net/flancklin/article/details/52311505 3. RESTful设计原则和样例 https://my.oschina.net/u/2330859/blog/468829