title: 一声笑接口文档
date: 2019-03-03 21:44:43
tags:
Author:Leguang
E-Mail:langmanleguang@qq.com
目标
- 接口“粒度”争取设计得足够小,争取在业务发生变化后,后台接口不需要增减,只需要前端组合接口仍然能满足新的业务需求。
- 任何一个接口都可以获取到数据,哪怕没传参数。
要求
命名规范
一名二姓三风水,四积阴德五读书,名不正则言不顺,言不顺则事难成。软件开发其实就是门命名的艺术,所以首先定义一些规范,提出一些硬性要求,大家在命名的时候尽量多花点心思,多参考优秀的命名风格。
- 强烈推荐参考:参考阿里巴巴Java开发手册,Android开发规范,iOS开发手册。
- 一个单词尽量选择5--7个字母的,这样才最优美。
- 首字母缩写的单词尽量每个字母都用大写,例如ID。用个小写,人家还以为是一个单词。当然uri、url、urn这种除外,因为大家都知道这个是什么。
- 规范并统一公司的基础包名与项目的关系。
- 前后端的某些名称概念要统一用某一个单词,比如支付的统一订单,支付宝用的是order,微信用的是unifiedorder,那我们统一对订单这个概念用order这个词。再比如主机:后台用gateway,现在我们统一用host。这单词不统一很容易分裂。
uri规范
uri 表示资源,资源一般对应服务器端领域模型中的实体类,要求如下:
- 不用大写。
- 尽量不用横杠分隔符,万一要用,请使用中杠“-”不用下杠“_”。
- 参数列表要encode。
- uri中的名词表示资源集合,使用复数形式。
- 路径仅表示资源的路径(位置),以及一些特殊的actions操作。
- 以复数(名词)进行命名资源,不管返回单个或者多个资源。
- 资源的路径从父到子依次如:/{resource}/{resource_id}/{sub_resource}/{sub_resource_id}/{sub_resource_property}。
- 使用?来进行资源的过滤、搜索以及分页等。
- 使用版本号,且版本号在资源路径之前。
- 优先使用内容协商来区分表述格式,而不是使用后缀来区分表述格式。
- url最好越简短越好,结果过滤,排序,搜索相关的功能都应该通过参数实现。
- url失效则返回404 not found 或 410 gone;对迁移的API,返回 301 重定向。
JSON规范
暂时只考虑json的数据格式,要求如下:
- 不要使用缩写。
- 统一用驼峰命名法。
- 不要使用“_”或者“-”。
- 用名词复数表示集合类型。
- 为了方便以后的扩展兼容,如果返回的是数组,强烈建议用一个包含如items属性的对象进行包裹。如:
{"items":[{},{}]}
。 - 建议对每个字段设置默认值(数组型可设置为[],字符串型可设置为””,数值可设置为0,对象可设置为{}),这一条是为了方便前端/客户端进行判断字段存不存在操作。
- 建议资源使用UUID最为唯一标识。同时建议命名为id或者uid。
- 采用UTF-8编码。
- 数据应该拿来就能用,不应该还要进行转换操作。
草稿:JSON返回的格式是分门别类按对象来划分,还是铺大饼的形式铺开,两者利弊各异,比如url,可能一个接口中返回多个url,如果分json对象装的话,则key都可以叫url,否则的话key就得命名成xxxUrl。这个有待商议
HTTP部分
使用场景
- App的初始化数据尽量都用http协议获取。
- 页面的初始化数据尽量都用http协议获取。
URL结构
https://{serviceRoot}/{module}/{collection}/{id}
- {serviceRoot} – 域名+端口号 (site URL) + 根目录
- {module} – 模块名称
- {collection} – 要访问的资源
- {id} – 要访问的资源的唯一编号
公共请求头
通过Content-Type指定请求与返回的数据格式有JSON和XML,暂时我们只管json的。其中请求数据还要指定Accept。其中额外添加的请求头里的参数注意大小写。
Accept: application/json
Content-Type: application/json;charset=UTF-8
Token: token_G34G34G34G34G35G5
Application: 应用:版本
Platform: Android
Locale: zh
params | 类型 | 是否必要 | 描述 |
---|---|---|---|
Token | String | 是 | 这个不用解释了吧? |
Application | String | 是 | App应用名称(名称有官方指定)与当前版本的版本名称,中间用半角冒号隔开 |
Platform | String | 是 | Android表示Android平台,iOS表示iOS平台 |
Locale | String | 否 | 用于针对接口的国际化,不同的值代表不同的语言,可以不传,不传默认表示中文简体。值和语言如下表所示: |
暂时只考虑4门语言
值 | 意义 |
---|---|
zh | 中文(简体) |
en | 英文 |
ja | 日文 |
ko | 韩文 |
公共参数(部分公共参数建议公放到请求头里)
公共参数是指每一个接口都可能有的,为了减少篇幅,我在这里统一定义,同时后端要指定公共参数的默认值,且要保证没有传公共参数不会报错,所以需要一定的容错性,比如priceDes这个参数值,如果是用的是全部小写的,只要是不冲突,则可认为是准确的参数并且表达了按价格降序排列这个语意。常用公共参数如下:
params | 类型 | 是否必须 | 描述 |
---|---|---|---|
keyword | String | 否 | 用于检索,不传或者传空,表示默认,默认不检索该关键字 |
sort | String | 否 | 用于对列表排序,不传则表示默认,默认按配置顺序,asc升序,desc降序 |
page | int | 否 | 用于分页描述,不传则表示默认,默认是第1页 |
pageSize | int | 否 | 表示该页显示的条数,不传则表示默认,默认为20条 |
个性参数
个性参数就是除了公共参数之外的,看能否考虑统一用JSON将公共参数和个性参数浓缩成一个参数,把想要表达的参数通过json中的key-value形式传递。例如:https://api.xxx.com/both/v1/search/products?params={"extra":"你想填什么呢","token":"token_523523523","data":{"uid":"23523523523523"}} 或者考虑与业务相关的参数就用json形式包装,而与业务无关的个性参数就还是用传统的方式另立一个参数。例如:https://api.xxx.com/both/v1/search/products?limit=10&offset=10¶ms={"keyword":"方便面","sort":"des"}
通过商议将个性参数统一按原始的URL传参形式传递,不考虑上面所说的那种方式。
公共响应头
Content-Type:application/json; charset=utf-8
Status:200 OK
其中状态码要与公共响应体里的json中的code字段一样。
公共响应体
默认会有以下字段,不需要全部都有。
{
"message": "居然被你查询成功了",
"code": 200,
"page": 0,
"pageSize": 20,
"first": "https://...",
"next": "https://...",
"previous": "https://...",
"last": "https://...",
"data": {
"uid": "6565656565665"
}
}
key | 类型 | 是否必须 | 描述 |
---|---|---|---|
message | String | 是 | 返回给接口调用者的描述,有可能用于显示到界面上,需要进行国际化处理 |
code | int | 是 | 这个与请求头中的状态码一致,是为了满足部分开发者的习惯 |
page | int | 否 | 分页请求中请求的当前页的页码 |
pageSize | int | 否 | 分页请求中一页的个数,默认为20 |
first | String | 否 | 分页请求中第一页的url ,如果没有则返回空字符串 |
next | String | 否 | 分页请求中下一页的url,如果没有则返回空字符串 |
previous | String | 否 | 分页请求中上一页的url,如果没有则返回空字符串 |
last | String | 否 | 分页请求中最后一页的url,如果没有则返回空字符串 |
data | object | 否 | 当前接口的具体数据由该json对象承载 |
uid | String | 否 | 对于每一个资源对象,在返回的时候,都应该返回操作这个资源对象的唯一码 |
HTTP动词表示操作。
常用的HTTP动词有下面五个(括号里是对应的SQL命令)。
- GET(SELECT):从服务器取出资源(一项或多项)。
例如:GET /zoos:列出所有动物园。 - POST(CREATE):在服务器新建一个资源。
例如:POST /zoos:新建一个动物园。 - PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)。
例如:PUT /zoos/ID:更新某个指定动物园的信息(提供该动物园的全部信息)。 - DELETE(DELETE):从服务器删除资源。
例如:DELETE /zoos/ID:删除某个动物园。
状态码
作为API的设计者,正确的将API执行结果和失败原因用清晰简洁的方式传达给客户程序是十分关键的一步。我们确实可以在HTTP的相应内容中描述是否成功,如果出错是因为什么,然而,这就意味着用户需要进行内容解析,才知道执行结果和错误原因。因此,HTTP响应代码可以保证客户端在第一时间用最高效的方式获知 API 运行结果,并采取相应动作。下表列出了比较常用的响应代码。
常用的http状态码及使用场景:
响应代码 | 代码含义 |
---|---|
200 | 已创建,请求成功且服务器已创建了新的资源。 |
201 | 是否只显示处于警告状态的应用实例。 |
301 | 重定向 , 请求的网页已被永久移动到新位置。服务器返回此响应时,会自动将请求者转到新位置。 |
302 | 重定向 , 请求的网页临时移动到新位置,但求者应继续使用原有位置来进行以后的请求。302 会自动将请求者转到不同的临时位置。 |
304 | 未修改,自从上次请求后,请求的网页未被修改过。服务器返回此响应时,不会返回网页内容。 |
400 | 错误请求 , 服务器不理解请求的语法。 |
401 | 未授权 , 请求要求进行身份验证。 |
403 | 已禁止 , 服务器拒绝请求。 |
404 | 未找到 , 服务器找不到请求的网页。 |
405 | 方法禁用 , 禁用请求中所指定的方法。 |
406 | 不接受 , 无法使用请求的内容特性来响应请求的网页。 |
408 | 请求超时 , 服务器等候请求时超时。 |
410 | 已删除 , 如果请求的资源已被永久删除,那么,服务器会返回此响应。 |
412 | 未满足前提条件 , 服务器未满足请求者在请求中设置的其中一个前提条件。 |
415 | 不支持的媒体类型 , 请求的格式不受请求页面的支持。 |
500 | 内部服务器错误。 |
分页
分页适用于GET类型且返回集合数据的请求,根据如下参数进行分页操作。分页返回的数据见公共响应体。
{
"extra": "你想填什么就填什么",
"page": 0,
"pageSize": 20,
}
params | 类型 | 是否必须 | 描述 |
---|---|---|---|
page | int | 否 | 同上 |
pageSize | int | 否 | 同上 |
错误/异常处理
- 不要发生了错误但给2xx响应,客户端可能会缓存成功的HTTP请求;
- 正确设置http状态码,不要自定义;
- Response body 提供 1) 错误的代码(日志/问题追查);2) 错误的描述文本(展示给用户)。
对第三点的实现稍微多说一点:
Java 服务器端一般用异常表示 RESTful API 的错误。API 可能抛出两类异常:业务异常和非业务异常。业务异常由自己的业务代码抛出,表示一个用例的前置条件不满足、业务规则冲突等,比如参数校验不通过、权限校验失败。非业务类异常表示不在预期内的问题,通常由类库、框架抛出,或由于自己的代码逻辑错误导致,比如数据库连接失败、空指针异常、除0错误等等。
业务类异常必须提供2种信息:
- 如果抛出该类异常,HTTP响应状态码应该设成什么;
- 异常的文本描述;
错误描述
{
"message": "特么的又错了",
"code": 500,
"document": "https://developer.xxx.com/v1/errors/500",
"exception": [
{
"code": "NullValue",
"target": "PhoneNumber",
"message": "Phone number must not be null"
},
{
"code": "NullValue",
"target": "LastName",
"message": "Last name must not be null"
},
{
"code": "MalformedValue",
"target": "Address",
"message": "Address is not valid"
}
]
}
错误请求头
{
"date": "Mon, 08 Jan 2018 03:07:08 GMT",
"server": "nginx/1.10.3 (Ubuntu)",
"connection": "keep-alive",
"content-length": "237",
"content-type": "application/json"
}
错误状态码(非200)
通过分析发现,接口的错误无非调用端传递给后台的信息与预期不符,其中包括公共参数和个性参数不符,不符包括缺失和内容错误(或格式错误等)两种,还有是部分特殊业务的特殊性质造成非正常结果,如:重复申请、重复签到等。经商议决定将错误码分为四大类:
错误码 | 类型 | 描述 | 举例 |
---|---|---|---|
XXX | int | 用于描述缺少参数,同时message做出相应的辅助描述,告诉调用者到底是缺少哪个参数,这些message只有调用者看得到,用户看不到 | 需要a、b、c三个参数,结果只收到a和b参数,或者收到c参数,但c参数为空字符串,且c参数又是必传的,因此可返回该错误码,同时messgae中提示:缺少c参数,或者c参数不能为空 |
XXX | int | 用于描述有参数但参数错误,可能是内容错误或格式错误,同时message做出相应的辅助描述,告诉调用者到底是哪个参数错误,这些message只有调用者看得到,用户看不到 | 需要传a参数为String类型的11位手机号码,结果收到的是long类型,或只有10位的String,则可返回该错误码,同时message提示a参数错误 |
XXX | int | 描述某些特定业务场景,比如重复申请、重复签到等,每一个场景对应一个错误码,同时message会提示相应错误场景,这些message会给用户看到的 | 比如重复申请注册奖励,返回特定码,前端做出相应显示,或者跳转,或者隐藏即可 |
XXX | int | 描述某些公共功能,调用者会做出相应公共处理 | 比如token失效,直接跳转到登录界面 |
以上友好的message提示方便了我们,同时也方便了非法调用者,为阻碍非法调用,决定设置一个开关,该开关是系统自带的,根据编译环境变更,在调试阶段就用以上友好提示,在正式部署上线后就改成模糊提示。
url失效
随着系统发展,总有一些API失效或者迁移,对失效的API,返回404 not found 或 410 gone;对迁移的API,返回 301 重定向。
对于后台文档的要求
文档要求描述详尽,尽可能的引导接口使用者理解接口设计,这样才能减少接口的改动,又能适应多变的业务。
容错性(健壮性)
考虑到如果测试或者运营中有变动,要求每一个Web页面得有容错机制,比如关停该功能,则web必须的有相应的页面展示。
国际化
此处的国际化是指语言的国际化,因此前后端都要考虑在界面的文字提示上要做国际化处理,暂时我们只做4种文字的国际化:中文(简体)、英文、日文和韩文,经商议,通过两方面来传递国际化信息:
- 专门的locale设置接口
- 请求头中多一个locale字段
由于有两个入口设置语言,因此有优先级处理,请求头中locale字段优先级大于locale设置接口,两者不传,默认都表示简体中文。locale接口默认为简体中文,如果有设置其他语言,则为其他语言,请求头里如果没有locale字段,则依据locale接口,如果请求头里带了locale字段,则依据请求头为准。