美文网首页
客户端后端研发的必修课

客户端后端研发的必修课

作者: coder_小刀 | 来源:发表于2018-01-29 10:28 被阅读0次

    前言

    本文从APP后端研发同学的角度,带来一些我们的思考和总结。

    接口规范

    客户端和服务端通常采用Http(s) 协议,JSON(P)数据格式交互。通常定义的JSON格式如下:

    {
        "code": 0, //接口状态值
        "randomcode": "1515121404158",//随机状态码
        "md5": null, //用来判断数据是否更新
        "codeMsg": "成功", // 提示语,Toast使用
        "data": { //业务数据
        }
    }
    

    我们对JSON数据格式的基本要求如下:

    1. JSON数据格式要保持良好的结构,符合标准的JSON规范
      • key必须为字符串, 使用双引号而不是单引号 , 字段命名尽量做到见名知意,命名规则建议采用驼峰命名
      • 当Value值为null时,也需要返回对应的Key ,制定协议时包含哪些字段,都需要返回
    2. data 值的数据类型为Object,不允许修改。
    {
        "data": {"list":[]} //good
        "data": []   // bad
        "data": "123123"  // bad
    }
    
    1. status需要表示状态值时,字段从1+开始,0是一种未赋值的默认状态,1:进行中,2:待支付,3:已完成,4:已取消
    2. 客户端尽量只负责展示逻辑,不处理业务逻辑。
    3. 接口返回字段除了约定的必要字段,不允许返回多余的字段。
      反例:不定义Model,直接把底层的数据库表对应的Bean返回给前端,导致多传输很多不必要的字段。
    4. 不允许拿变量命名json的key值
    //good
    [{"name":"书籍1","chapters":500},{"name":""书籍2","chapters":180},{...},...]
    //bad
    [{"书籍1":500},{"书籍2":180},{...},...]
    
    1. 禁止程序拼接json字符串,防止包含特殊转义字符,导致客户端解析json失败

    说明:此处没有统一iOS、Android、Server的JSON解析框架

    通用设计

    APP的后端系统有些设计是通用的,各APP可以相互借鉴。

    1. 通用参数
      对于一些客户端每次请求都需要的参数信息,我们暂且称之为通用参数,譬如:os、appid、appversion、channel、imei 等。对于这些信息客户端统一放在Header中。

    2. 接口签名
      接口签名的目的是保证服务端收到的请求都来自客户端,一定程度增加接口被抓取的难度。
      我们采用的方法是使用拦截器处理,客户端和服务端可以约定一些通用参数、时间戳等,前后端分别计算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;
    }
    
    1. 配置中心
      动态化是一个APP的标配,小到一个icon的图标,大到一个页面模块的布局。特别是针对客户端这种 C/S 结构 ,各种开关、三方入口、动态链接等都需要从配置中心获取,不写死是我们的基本原则。
      另外,动态配置,从产品的角度,也可以灵活调整运营策略,达到快速试错的目的。
      整体架构见:


      配置中心.png
    1. 跨域问题
      跨域问题是由于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");
    
    1. 节省流量
      对于某些不经常变动的数据,特别是配置数据,譬如:城市列表数据等,我们可以将接口返回的数据缓存在客户端。服务端对比接口返回数据的md5值是否变化,如果无变化,不返回数据,客户端直接使用本地缓存数据。这样可以减少接口网络数据量的传输,节省用户流量。
      整体流程见:


      数据接口缓存.png
    2. 多版本支持
      APP后端的API比前后端分离系统的API生命周期更长,一般我们的客户端在线上至少保留3-4个版本,还不包含我们针对一些特殊渠道定制的安装包。这样就要求我们后端接口在设计更加通用。
      我们通常的做法:
      对于一些修改较小的接口通过app版本号判断做数据兼容
      对于一些修改较大的接口,通过新增接口 api/v56/order/list 的方式解决
      对于业务逻辑部分,通过app版本号获取对应的实现类

    3. 接口管理
      与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}
    

    相关文章

      网友评论

          本文标题:客户端后端研发的必修课

          本文链接:https://www.haomeiwen.com/subject/abseoxtx.html