前言
本文从APP后端研发同学的角度,带来一些我们的思考和总结。
接口规范
客户端和服务端通常采用Http(s) 协议,JSON(P)数据格式交互。通常定义的JSON格式如下:
{
"code": 0, //接口状态值
"randomcode": "1515121404158",//随机状态码
"md5": null, //用来判断数据是否更新
"codeMsg": "成功", // 提示语,Toast使用
"data": { //业务数据
}
}
我们对JSON数据格式的基本要求如下:
- JSON数据格式要保持良好的结构,符合标准的JSON规范
- key必须为字符串, 使用双引号而不是单引号 , 字段命名尽量做到见名知意,命名规则建议采用驼峰命名
- 当Value值为null时,也需要返回对应的Key ,制定协议时包含哪些字段,都需要返回
- data 值的数据类型为Object,不允许修改。
{
"data": {"list":[]} //good
"data": [] // bad
"data": "123123" // bad
}
- status需要表示状态值时,字段从1+开始,0是一种未赋值的默认状态,1:进行中,2:待支付,3:已完成,4:已取消
- 客户端尽量只负责展示逻辑,不处理业务逻辑。
- 接口返回字段除了约定的必要字段,不允许返回多余的字段。
反例:不定义Model,直接把底层的数据库表对应的Bean返回给前端,导致多传输很多不必要的字段。 - 不允许拿变量命名json的key值
//good
[{"name":"书籍1","chapters":500},{"name":""书籍2","chapters":180},{...},...]
//bad
[{"书籍1":500},{"书籍2":180},{...},...]
- 禁止程序拼接json字符串,防止包含特殊转义字符,导致客户端解析json失败
说明:此处没有统一iOS、Android、Server的JSON解析框架
通用设计
APP的后端系统有些设计是通用的,各APP可以相互借鉴。
-
通用参数
对于一些客户端每次请求都需要的参数信息,我们暂且称之为通用参数,譬如:os、appid、appversion、channel、imei 等。对于这些信息客户端统一放在Header中。 -
接口签名
接口签名的目的是保证服务端收到的请求都来自客户端,一定程度增加接口被抓取的难度。
我们采用的方法是使用拦截器处理,客户端和服务端可以约定一些通用参数、时间戳等,前后端分别计算md5值,然后对比。
示例代码如下:
@Override
public ActionResult preExecute(BeatContext beat) {
HttpServletRequest request = beat.getRequest();
String signVal = request.getHeader(HEADER_SIGN);
if (StringUtils.isBlank(signVal)) {
log.info("hsign is not exist.");
RespJsonResult jsonResult = RespJsonResult.creatFailEntity(ResponseCodeConsts.AUTH_ERR_HEADER_NOT_EXIST, "guess error lowest");
return new JsonViewResult(JsonHelper.toJSONString(jsonResult));
}
StringBuffer headerBuffer = new StringBuffer(100);
for (String headerKey : headerKeylist) {
String headerValue = request.getHeader(headerKey);
if (headerValue != null && headerValue.trim().length() > 0) {
headerBuffer.append(headerValue).append("_");
} else {
headerBuffer.append("null_");
}
}
String source = headerBuffer.append("你的秘钥").toString();
String localSign = MD5.encode(source);
if (!signVal.equals(localSign)) {
log.info("sign is illegal.source = " + source + ", localSign = " + localSign + ", hsign = " + signVal);
RespJsonResult jsonResult = RespJsonResult.creatFailEntity(ResponseCodeConsts.AUTH_ERR_HEADER_NOT_PERMITTED, "guess error lower");
return new JsonViewResult(JsonHelper.toJSONString(jsonResult));
}
return null;
}
-
配置中心
动态化是一个APP的标配,小到一个icon的图标,大到一个页面模块的布局。特别是针对客户端这种 C/S 结构 ,各种开关、三方入口、动态链接等都需要从配置中心获取,不写死是我们的基本原则。
另外,动态配置,从产品的角度,也可以灵活调整运营策略,达到快速试错的目的。
整体架构见:
配置中心.png
- 跨域问题
跨域问题是由于javascript语言安全限制中的同源策略造成的,简单来说,同源策略是指一段脚本只能读取来自同一来源的窗口和文档的属性,这里的同一来源指的是主机名、协议和端口号的组合。
解决跨域问题通常采用JSONP和Access-Control-Allow-Origin。
- JSONP
JSONP的原理就是利用<script>标签没有跨域限制,来达到与第三方通讯的目的。
示例代码如下:
String callback = RequestUtils.getStringPara(beat, "callback", null);
if (callback != null){
return new JsonpActionResult(callback + "("+text+")");
} else {
return new JsonActionResult(text);
}
- Access-Control-Allow-Origin
Access-Control-Allow-Origin是HTML5中定义的一种解决资源跨域的策略,他是通过服务器端返回带有Access-Control-Allow-Origin标识的Response header,用来解决资源的跨域权限问题。
示例代码如下:
beat.getResponse().addHeader("Access-Control-Allow-Origin","www.daojia.com");
-
节省流量
对于某些不经常变动的数据,特别是配置数据,譬如:城市列表数据等,我们可以将接口返回的数据缓存在客户端。服务端对比接口返回数据的md5值是否变化,如果无变化,不返回数据,客户端直接使用本地缓存数据。这样可以减少接口网络数据量的传输,节省用户流量。
整体流程见:
数据接口缓存.png -
多版本支持
APP后端的API比前后端分离系统的API生命周期更长,一般我们的客户端在线上至少保留3-4个版本,还不包含我们针对一些特殊渠道定制的安装包。这样就要求我们后端接口在设计更加通用。
我们通常的做法:
对于一些修改较小的接口通过app版本号判断做数据兼容
对于一些修改较大的接口,通过新增接口 api/v56/order/list 的方式解决
对于业务逻辑部分,通过app版本号获取对应的实现类 -
接口管理
与APP交互的后端web站点通常有多个,对此我们尽量做到统一收口。好处在于统一权限认证、接口签名、参数校验……
服务端统一收口.png
8.接口文档管理
接口文档管理包括接口的描述,接口入参出参字段说明,通常这一部分文档的形成在需求详设阶段。
这一块我们做的不好,目前也没有找到比较好的方案。
问题排查技巧
问题排查最困难的就是保留现场。
在APP测试过程中,我们怎么去判断出现问题归属前端还是后端?
后端RD:“ iOS没问题,android的有问题,那android的你们去查吧!” 这样对吗?
iOS/ Android RD:“ 我本地打断点,看你们返回的数据有问题呀 ”
QA:“这个bug到底提给后端RD,还是提给客户端RD呢? 容我喝杯茶想想? 抓个包看看吧! Fiddler 、Charles 走起……”
其实:客户端和服务端通信现在唯一依赖的就是服务端返回的JSON数据,保留住现场,就能立马确认问题归属。
怎么保留现场,系统实时记录接口返回值,通过接口返回的 result 来判断,如果result 符合,那问题归属端上,否则 服务端RD 乖乖去排查问题。
日志格式示例如下:
17:47:51,570 INFO jsonresult:45[51] - uid=123456789 , mobile=0 , uri = /api/guest/address/isopencity,result = {"data":{"centerLng":"39.9930060000","isOpenCity":"0","centerCityId":"1","centerCityName":"北京","centerLat":"116.3367130000","centerPoiName":"五道口"},"code":0,"codeMsg":"成功","randomcode":"1516268871570","md5":null}
18:12:25,883 INFO jsonresult:45[48] - uid=-1 , mobile=0, uri = /api/signalbomb/system/last/,result = {"data":{"lastSystem":{"id":953170805129486336,"title":"附近-服务提醒","pushTime":1516088603000,"content":"您与敏焕大号的订单-姐姐的小店,将于01月16日 15:32开始服务,记得准时服务哦~","url":"https://m-dop71.djtest.cn/user/order/detail?role=merchant&orderid=1122089194802467776"},"newCounts":"13"},"code":0,"codeMsg":"成功","randomcode":"1516270345883","md5":null}
网友评论