原文地址
前后端接口规范
随着前后端分离越来越普遍,后端接口规范也就越来越重要了.一套良好的接口规范可以提升工作效率,减少沟通障碍.
通常我们都会采用REST方式来提供接口,使用JSON来传输数据.
前端 | Web前端, APP端, 桌面端等一切属于用户界面的这一层 |
后端 | 即服务器端, 指一切属于用户界面之下的这一层 |
前后端接口 | 前端与后端进行数据交互的统称, 也叫做数据接口, 属于一种远程调用, 一般指前端通过HTTP(ajax)请求获取到的数据或者执行的某项操作. 为确保前后端(工程师)的协作沟通, 一般由前端和后端一起来定义接口的规范, 规范的内容一般包含接口的地址, 接口的输入参数和输出的数据格式(结构), 最终由后端来实现这些规范, 为前端提供符合规范的接口 |
[前端]
--------
^
|
|
前后端接口
|
|
--------
[后端]
前后端接口协作流程
以免出现前后端分离之后最容易出现的扯皮现象.特别是当你碰到做事不主动(无责任感)的后端,什么都要前端来催.比如什么接口又缺了一个字段没有提供啦,什么又少了一个接口啦,等等诸如此类.后端不去熟悉业务,也不看界面原型和需求,只管把接口做完,任务完成就万事大吉了,每天除了等前端通知哪里要修改,自己就像没事人一样.
所以说定好接口,前后端一起来确认好接口是多么的重要,不然你就等着干着急吧.当然了,想一次性完美地将所有接口都定义出来,有点不太现实,需要调整的情况在所难免,所以还是希望后端能够主动一点,前后端沟通的时候就轻松得多,大家的效率就都提高了.
准备环境
安装Nodjs安装puer-mock
接口规范
由前端(APP端)和后端一起协定接口规范的内容,确定每一个接口的地址(UR,输入(request)和输出(respons,必要的时候详细注释每一个字段的含义和数据类型.
具体需要定义哪些接口,可以按照下面的思路来整理
资源接口:系统涉及到哪些资源,按照RESTful方式定义的细粒度接口操作接口:页面涉及到哪些操作,例如修改购物车中商品的数量,更换优惠券等等,也可以使用RESTful方式来定义页面接口:页面涉及到太多接口,如果是一个个地调用,会需要很多次请求,有可以影响到前端的性能和用户感知(特别是首屏的体验),因此可能需要将这些接口的数据合并到一起,作成一个聚合型接口提供给前端来使用
接口协商要点
接口必须返回统一的数据结构,参考后端接口通用规范中接口返回的数据结构接口查询不到数据时,即空数据的情况下返回给前端怎样的数据建议返回非null的对应数据类型初始值,例如对象类型的返回空对象({}),数组类型的返回空数组([]),其他原始数据类型(string/number/boolean…)也使用对应的默认值这样可以减少前端很多琐碎的非空判断,直接使用接口中的数据例如:result.fieldName如果result为null,可想而知会报错UncaughtTypeError:Cannotreadproperty'fieldName'ofnull调用接口业务失败的常用错误码,例如未授权时调用需要授权的接口返回'status':1接口需要登录时如何处理,特别是同时涉及到Web端/微信端/App端,需要前端针对运行环境判断如何跳转到登录页面返回数据中片URL是完整的还是部分的http://res.com/path/to/impng这就是完整的,前端直接使用这个URL/path/to/impng这就是部分的,一般省略域名部分,前端需要自己拼接后才能使用'http://res.com'+'/path/to/impng'返回数据中页面跳转的URL是给完整的还是部分的内部页面返回部分的,或者只给ID,由前端自己拼接,例如只给出商品ID,让前端自己拼接商品详情页的URL外部页面返回完整的,例如广告位要跳转去谷歌返回数据中日期的格式,是使用时间戳还是格式化好的文字对于需要前端再次处理的日期值(例如根据日期计算倒计时),可以使用时间戳(简单暴力),例如:1458885313711,或者参考DatprototyptoJSON提供ISO标准格式(例如需要考虑时区时)对于纯展示用的日期值,推荐返回为格式化好的文字,例如:2017年1月1日对于大数字(例如Java的long类型),返回给前端时需要设置为字符串类型,否则JavaScript会发生溢出,造成得到的数值错误例如:返回JSON数据{'id':362909601374617692}前端拿到的值却是:362909601374617660分页参数和分页信息如何限制只返回N条数据(limit参数)如何控制每页的数据条数(pageSize参数)如何加载某一页的数据(page参数)第一页是从0开始还是从1开始如何避免无限滚动加载可能出现的重复数据(采用lastId分页方式,来避免传统分页方式的弊端)假设数据是按照新增时间倒序排列的首先加载2页的数据等了很久期间新增了很多数据再获取第3页数据此时就可能出现重复数据的情况,因为新增的数据都排在最前面,后面会接着已经加载过数据分页信息包含什么(total,page,pageSiz分页信息何时表明已经是最后一页了请求某页数据时返回的数据条数 接口定义 具体puer-mock的详细使用手册和_mockserver.json如何配置接口请参考puer-mock项目,或者参考项目中已经配置好的其他接口. 接口协作 由于接口规范的定义和接口的实际实现是分开的两个部分,而且涉及到多人协作,因此在开发过程中可能出现接口规范与实现不同步,最终造成实际的接口不符合规范的定义,接口规范就会慢慢失去存在的意义. 为了尽量避免这种问题,后端在实现接口的过程中应该确保与接口规范保持一致,一旦出现分歧,必须同步修改接口规范,尽可能保持沟通. 接口文档(示例) 后端接口通用规范 接口地址和请求方式 接口地址即接口的URL,定义时使用相对路径(即不用带上域名信息),建议分模块来定义,推荐REST风格,例如 GET/user/:id表示获取用户信息POST/user表示新增用户 接口参数 向接口传递参数时,如果是少量参数可以作为URLquerystring追加到接口的URL中,或者作为Content-Type:application/x-www-form-urlencoded放在请求体(body)中(即表单提交的方式) 对于复杂的接口参数(例如嵌套了多层的数据结构),推荐在HTTP请求体(body)中包含一个JSON字符串作为接口的参数,并设置Content-Type:application/json;charset=utf- 例如 查询VIP用户的接口 接口返回的数据结构 返回的响应体类型推荐为Content-Type:application/json;charset=utf-8,返回的数据包含在HTTP响应体中,是一个JSONObject.该Object可能包含3个字段data,status,statusInfo 例如 接口处理成功时接口返回的数据{'data':'apiresult''status':0}接口处理失败时接口返回的数据{'status':1,'statusInfo':{'message':'服务器正忙','detail':{'exception':'javutiList'}}} 这样我们就可以非常容易地通过判断status来处理数据了 错误码规范:status字段该如何取值 采用前后端分离开发模式的项目越来越多,前端负责调用后端的接口来展现界面,如果有界面显示异常,需要有快速方便的手段来排查线上错误和定位出职责范围 综合了经验总结和行业实践,最简单有效的手段是制定出一套统一的错误码规范,协助多方人员来排查出接口的错误 例如 用户发现错误,可以截错误码的,就能够提供有效的信息帮助开发人员排查错误测试人员发现错误,可以通过错误码,快速定位是前端的问题还是后端接口的问题 因此我们确定提示信息规范为:当后端接口调用出错时,接口提供一个用户可以理解的错误提示,前端展示给用户错误提示和错误码,给予用户反馈 对于错误码的规范,参考行业实践,大致有两种方案 做显性的类型区分,快速定位错误的类别,例如通过字母划分类型:A101,B131StandardISOResponseCodes固定位数,设定区间(例如手机号码,身份证号码)来划分不同的错误类型HTTPStatusCodeDefinitionsSystemErrorCodes 具体实践如下 错误码固定长度,以区间来划分错误类型(例如HTTP的状态码)例如:10404表示HTTP请求404错误,20000表示API调用失败,30000代表业务错误,31000表示业务A错误,32000表示业务B错误错误码可不固定长度,以首字母来划分错误类型,可扩展性更好,但实际运作还是需要划分区间例如:H404表示HTTP请求404错误,A100表示API调用失败,B100表示业务A错误,B200表示业务B错误 关于错误分类的原则,我们可以根据发送请求的最终状态来划分 发送失败(即请求根本就没有发送出去)发送成功HTTP异常状态(例如404/500…)HTTP正常状态(例如200)接口调用成功接口调用失败(业务错误,即接口规范中status非0的情况) 最终规范 错误码可不固定长度,整体格式为:字母+数字,字母作为错误类型,可扩展性更好,数字建议划分区间来细分错误 例如: AforAPI:API调用失败(请求发送失败)的错误,例如A100表示URL非法HforHTTP,HTTP异常状态的错误,例如H404表示HTTP请求404错误Bforbackendorbusiness,接口调用失败的错误,例如B100业务A错误,B200业务B错误CforClient:客户端错误,例如C100表示解析JSON失败 统一错误提示 错误日志接口调用出错(${错误码})${HTTP方法}${HTTPURL}${请求参数}${请求选项}${请求返回结果}例如:接口调用出错(H40GEThttps://domaicom{foo:bar}{option1:'test'}{status:404}给用户的提示消息(参考自QQ的错误提示消息)提示消息(错误码:xxx)提示消息和错误码之间用换行隔开错误码整块内容建议弱化使用灰色字例如 规范实现:weapp-backend-api 接口实现建议 接口实现的大方向建议遵循RESTful风格HTTP动词:获取数据用GET,新增/修改/发送数据用POST例如:获取用户数据的接口用GET,修改用户数据的接口用POST对于资源的操作类型,使用HTTP动词来指定,减少接口URL的数量例如:GET/contact获取联系人,POST/contact新增/修改联系人对外的ID字段使用字符串类型特别核心数据的ID字段,不要使用自增的数字类型,建议使用无规则的字符串类型(例如UUI,避免核心数据被轻易抓取避免使用大数字类型(Lon,因为前端可能承载不了这个精度而溢出得到另外一个数值例如:Java中的Long类型的数值:362909601374617692,作为JSON数据返回给前端,前端拿到的值变成了362909601374617660接口字段建议同时给出ID字段和用于显示字段,前端提交数据时只提交ID字段例如:{'sex':1,'sexText':'男'}片的URL建议返回完整的URL例如:{'pic':'https://domaicom/png'}时间字段建议同时返回时间戳的原始值(或ISO标准格式)和用于统一显示的格式化文本由后端接口集中控制各端的显示,提供的原始值兼顾前端的自定义显示或者计算(例如倒计时)的需求避免每个端(例如H5/APP/小程序)都需要对时间做统一的格式化实现,一旦需要调整,需要各个端都调整一遍例如:{'createTime':1543195480357,'createTimeText':'2018年11月26日'}统一分页的数据格式分页请求的参数和分页结果的数据结构 注意 Version跨域CORSJSONP避免中文乱码 参考 E-JSON数据传输标准有范云协作让项目的协作姿势更有范儿交互阶段说明交互设计师根据产品方的需求对产品进行行为设计和界面设计的阶段,主要产出物为交互设计稿开发工程师需要做的事情是针对产品需求、交互设计稿中的内容进行技术评审,为产品方、交互设计师提供可行技术实现解决方案,对于多种不同解决方案需针对各种解决方案做分析说明,务必准确传达各种方案的优缺点,并根据需求给出建议方案系统设计说明各端开发工程师针对产品需求说明、交互设计稿开始设计系统架构、拆分子系统、划分子系统模块、协调端与端之间的接口规范,这个阶段各端根据实际情况输出若干系统设计说明书等文档除此之外更重要的是输出端与端之间通信的接口规范,而这个规范则可以借助NEI平台来完成编码阶段说明开发工程师根据系统设计阶段的输出,用代码来实现这样的系统,包括技术方案的选型、项目框架的搭建、工具及环境的配置等其中有些工作可以借助于有范云协作提供的自动化工具NEI-Toolkit来完成,比如项目的初始结构代码、在NEI平台上定义好的接口规范等自测阶段说明各个端的工程师验证自己编写的代码的正确性,按角色不同,测试方式也有所有不同对于前端和移动端工程师来说,主要是需要测试各种可能的值会不会影响界面展示对于服务端工程师来说,主要是测试提供给客户端工程师使用的接口的正确性,对于不同的输入参数是否返回了预期的结果联调阶段说明主要是连测试环境进行测试对于前端和移动端工程师来说,主要是需要将本地容器提供的接口换成测试环境的接口测试阶段说明开发工程师开发完成后提测的过程,是产品上线前的最后环节测试工程师会对接NEI平台生成接口测试用例代码并集成到自动化测试平台运行,如果NEI平台的接口定义与实际提测的项目不符则此次提测失败,需由开发对照NEI平台检查接口实现情况,所以可以保证NEI平台上的接口定义始终与线上保持一致客户端API请求规范参数名说明imei国际移动设备身份码imsi客户端用户标识tTIMESTAMP,请求的时间戳appkey由服务端颁发的appkeysignmd5签名串。为了减轻非法恶意请求,每次来自APP的请求都需要对请求参数进行签名以实现安全认证lng手机上获取的经度lat手机上获取的纬度ci渠道标识,格式为:channelId@应用名平台客户端版本,例如:1001@nzaom_android_0,其中1001表示应用宝GitHubAPI|微博API|淘宝开放平台APIJSend|JSONAPI|JSONSchema|JSON-RPC|JWT|OAuthTypeDescriptionRequiredKeysOptionalKeyssuccessAllwentwell,and(usually)somedatawasreturnestatus,datafailTherewasaproblemwiththedatasubmitted,orsomepre-conditionoftheAPIcallwasn’tsatisfiedstatus,dataerrorAnerroroccurredinprocessingtherequest,anexceptionwasthrownstatus,messagecode,dataGoogleJSONStyleGuide最佳实践:更好的设计你的RESTAPI|RESTfulAPI设计指南|BestPracticesforDesigningaPragmaticRESTfulAPI|HTTPAPIDesignGuide|TheRESTfulCookbook|RESTfulAPI编写指南RestletStudio-WebIDEforAPIdesign|Swagger|ReDoc|RAML|APIBlueprint 文章为作者独立观点,不代表 股票程序化软件自动交易接口观点POST /users?limit=10 HTTP/1.1
Content-Type: application/json; charset=utf-8
{
'name': 'hanmeimei',
'isVip': true
}
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{
'data': {},
'status': 0,
'statusInfo': {
'message': '给用户的提示信息',
'detail': '用于排查错误的详细错误信息'
}
}
data 业务数据
必须是任意 JSON 数据类型(number/string/boolean/object/array).
推荐始终返回一个 object (即再包一层)以便于扩展字段.
例如: 用户数据应该返回 {'user':{'name':'test'}}
, 而不是直接为 {'name':'test'}
status 状态码
必须是 >= 0
的 JSON Number 整数.
0
表示请求处理成功, 此时可以省略 status
字段, 省略时和为 0
时表示同一含义.非 0
表示发生错误时的错误码, 此时可以省略 data
字段, 并视情况输出 statusInfo
字段作为补充信息statusInfo 状态信息
必须是任意 JSON 数据类型.
推荐始终返回一个 object 包含 message
和 detail
字段
message
字段作为接口处理失败时, 给予用户的友好的提示信息, 即所有给用户的提示信息都统一由后端来处理.detail
字段用来放置接口处理失败时的详细错误信息. 只是为了方便排查错误, 前端无需使用.if (!response.status) {
// status 为 0 或者没有 status 字段时表示接口成功返回了数据
console.log(response.data);
} else {
// 失败
console.error(response.status, response.statusInfo);
// 统一由服务端返回给用户的提示信息
alert(response.statusInfo.message);
}