V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
xuyt
V2EX  ›  程序员

x-http-wrapper: 如何解决每次发版时,修改 http 相关代码造成的错误!(Android、iOS、h5)

  •  
  •   xuyt ·
    xuyt11 · 2017-01-16 20:45:17 +08:00 · 2062 次点击
    这是一个创建于 2657 天前的主题,其中的信息可能已经有所发展或是发生改变。

    其实是我做了个开源工具(^__^),拿出来给大家鉴赏下,欢迎大家提意见
    项目: https://github.com/xuyt11/x-http-wrapper 欢迎关注和 star 。
    功能:这是一个 http 相关代码的创建工具。

    现在我们每一次发版,基本上都会涉及到 http 相关的修改,以此来满足发版的业务需求。
    而在其中需要添加或修改的有 http request 、 http request param 、 http response entity 等其他相关的 http 代码。
    而在多次的修改中,若前后端没有协调好,就有可能会造成之后的返工、重复修改与线上 bug 量的增加等问题。

    现在的痛点

    如何解决每次发版时,都需要新增、修改 http 相关代码!
    如何解决每次发版时,修改 http 相关代码造成的错误!

    解决思路: 规范

    1. 其实很简单,就是一个词“规范”,任何事情,只要我们有了一定的规范,就会有一定的流程、可追踪并且降低难度。
      我相信 99.99%的公司,都会有相关的 http 接口文档提供给前端同学,而且也会自己的一套规范(不论是我现在依赖的 apidocjs ,还是上家公司的 doc 文件)。
      当然,肯定也有口头约定的情况,但这需要在之后,立即将约定转化为文档,提供给前端的同学。 git 、 http 都可以作为提供的形式。

    2. 我们依赖这个 http 的规范,就可以将 http 接口文档去解析转义为 x-http-wrapper 内部的 API 数据。

    3. 再来就是依赖一定的规范(x-http-wrapper 的模板文件规范),将内部 API 数据转化为 http 相关文件。这样,每次只要接口文档更新过后,我们就可以根据文档生成各个程序内部可以运行的代码。
      这个功能与现在 IDE 中的 getter 、 setter 方法生成器功能其实是相同的原理!

    x-http-wrapper 介绍

    1. 这是一个 http 相关代码的创建工具。
    2. 现在能创建的 http 相关的文件类型有: http 请求分类, http 请求,请求方法参数,响应实体,响应实体中状态码列表和基础响应实体类。
      • HttpApi( http 请求分类): 所有 API 请求的统一调用入口,统合所有的请求类别的接口,防止 API 接口分散。
      public class HttpApi {
      
         private static Account account;
         private static Data data;
         private static Message message;
      
         public static Account account() {
             if (null == account) {
                 account = Account.getInstance();
             }
             return account;
         }
      
         public static Data data() {
             if (null == data) {
                 data = Data.getInstance();
             }
             return data;
         }
      
         public static Message message() {
             if (null == message) {
                 message = Message.getInstance();
             }
             return message;
         }
      }
      
      • Request( http 请求): 单个请求分组中,所有的请求方法。
      public class Account extends BaseApi {
      
          public static Account getInstance() {
              return Helper.instance;
          }
      
          private static class Helper {
              public static final Account instance = new Account();
          }
      
          private Account() {
              super();
          }
      
          /**
           * @version 2.0.0
           * @requestUrl 
           * @title 初始化账号信息
           *
           */
          public RequestHandle init(Context cxt001,
              ResponseHandlerInterface response) {
              // hide implementation
          }
      
          /**
           * @version 2.0.0
           * @title 扫二维码到 web 端进行操作
           *
           * @param context String desc
           * @param project_id isOptional Integer desc
           * @param scene isOptional String desc
           * @param uuid_rand String desc
           */
          public RequestHandle qrcodeConfirm(Context cxt001,
              String context, Integer project_id, String scene, String uuid_rand, 
              ResponseHandlerInterface response) {
              // hide implementation
          }
      
          /**
           * 缩略请求方法
           */
          public RequestHandle qrcodeConfirm(Context cxt001,
              QrcodeConfirmRP.Parameter parameter, 
              ResponseHandlerInterface response) {
              return qrcodeConfirm(cxt001,
              parameter.context, parameter.project_id, parameter.scene, parameter.uuid_rand, 
              response);
          }
      
      }
      
      • RequestParam(请求方法参数): 请求参数分组归类,对应单个请求,用于请求参数较多的情况,生成请求参数的分类实体类(请求参数也肯能有多个分类),减少请求方法的输入参数。
      /**
       * 请求方法参数
       */
      public class QrcodeConfirmRP implements Serializable {
      
         public static final class Parameter implements Serializable {
             /**
              * type: String<br>
              * isOptional : false<br>
              * desc: <p>扫码场景,枚举值</p>
              */
             public String context;
             /**
              * type: Integer<br>
              * isOptional : true<br>
              * desc: <p>业务参数: 根据 context 的不同而不同</p>
              */
             public Integer project_id;
             /**
              * type: String<br>
              * isOptional : true<br>
              * desc: <p>身份信息: 服务端会优先使用客户端传入的身份信息,当为”投资人“的时候必传</p>
              */
             public String scene;
             /**
              * type: String<br>
              * isOptional : false<br>
              * desc: <p>从二维码扫描得到的唯一码</p>
              */
             public String uuid_rand;
         }
      
      }
      
      • Response(响应实体): 请求的相应数据 model
      public class Init {
      
          private long member_id;
          private long member_role;
          private long member_status;
          private String ry_token;
          private long step;
      
          public long getMemberId() {return member_id;}
          public long getMemberRole() {return member_role;}
          public long getMemberStatus() {return member_status;}
          public String getRyToken() {return ry_token;}
          public long getStep() {return step;}
          public void setMemberId(long member_id) {this.member_id = member_id;}
          public void setMemberRole(long member_role) {this.member_role = member_role;}
          public void setMemberStatus(long member_status) {this.member_status = member_status;}
          public void setRyToken(String ry_token) {this.ry_token = ry_token;}
          public void setStep(long step) {this.step = step;}
      
      }
      
      • StatusCode(响应实体中状态码列表): 响应中所有状态码的枚举类
      public class StatusCode {
      
          /** '') */
          public static final int OK = 0;
      
          /** '登录状态已过期,请重新登入') */
          public static final int UNAUTHORIZED = 101;
      
          /** '您没有权限查看') */
          public static final int FORBIDDEN = 102;
      
          /** '资源未找到') */
          public static final int NOT_FOUND = 103;
      
          /** '客户端请求错误') # 4XX 客户端错误 */
          public static final int CLIENT_ERROR = 228;
      
          /** '服务器错误') # 5XX 服务器错误 */
          public static final int SERVER_ERROR = 229;
      
          /** '参数错误') */
          public static final int PARAM_ERROR = 230;
      
          /** '登录失败,请检查您的邮箱地址是否正确') */
          public static final int LOGIN_FAIL_EMAIL_NOT_EXIST = 332;
      
          /** '登录失败,请确认您的手机号是否正确') */
          public static final int LOGIN_FAIL_MOBILE_NOT_EXIST = 333;
      
          /** '登录失败,请检查密码是否正确') */
          public static final int LOGIN_FAIL_PASSWORD_ERROR = 334;
      
      }
      
      • BaseResponse(基础响应实体类): 基础的响应实体类
      public class ResponseEntity<T> {
      
          private int status_code;
          private String message;
          private Error error;
          private T data;
      
          public int getStatusCode() {return status_code;}
          public void setStatusCode(int status_code) {this.status_code = status_code;}
          public String getMessage() {return message;}
          public void setMessage(String message) {this.message = message;}
          public Error getError() {return error;}
          public void setError(Error error) {this.error = error;}
          public T getData() {return data;}
          public void setData(T data) {this.data = data;}
      
          public static class Error {
              private String detail;
              private List<String> device_token;
              private List<String> content;
              private List<String> followed_id;
      
              public String getDetail() {return detail;}
              public void setDetail(String detail) {this.detail = detail;}
              public List<String> getDeviceToken() {return device_token;}
              public void setDeviceToken(List<String> device_token) {this.device_token = device_token;}
              public List<String> getContent() {return content;}
              public void setContent(List<String> content) {this.content = content;}
              public List<String> getFollowedId() {return followed_id;}
              public void setFollowedId(List<String> followed_id) {this.followed_id = followed_id;}
          }
      
      }
      
    3. http 的数据来源,现阶段只有 apidocjs 这一个
      • 若有其他数据来源,可以配置 api_data.source 属性,然后添加对应的解析器,解析为 xhw 的 model 。

    工具环境与依赖

    • 命令行运行 jar 文件: 需要 java8 及以上的版本
    • 开发环境:
      • Java 的版本: java8 及以上的版本
      • 开发平台: intellij idea
      • 依赖的 jar: gson:2.8.0, rxjava:1.2.2, junit:4.12

    快速使用入门

    1. 下载项目的 Zip 包,解压缩,从 xhwt 文件夹下,选取其中的一个包装器模板文件夹,作为目标包装器的配置,该文件夹在下面都叫做target dir
      • 例如: xhwt/asynchttp/non_version(这是 android-async-http 库的一个模板与配置);
    2. 获取接口数据文件(api_data.json:存储 apidocjs 生成的 API 文档的数据)的路径;
      • 例如: guide 文件夹中的 api_data.json 的绝对路径
    3. 修改 target dir 下配置文件(x-http-wrapper.json)中 api_data.file_path_infos 的配置信息,将 api_data.json 的绝对路径添加上去;
        "api_data": {
          "source": "apidocjs",
          "file_path_type": "file",
          "file_path_infos": [
            {
              "os_name": "Mac OS X",
              "path": "api_data.json 的绝对路径"
            },
            {
              "os_name": "Windows",
              "path": "api_data.json 的绝对路径"
            }
          ],
          "file_charset": "UTF-8"
        }
      
    4. 修改 target dir 中, API 的模板文件中<t:header></t:header>标签内,生成文件的目标路径;
      • API 的模板文件是以.xhwt 为后缀的文件,是生成各个 http 相关文件的模板;
      • <t:header></t:header>标签内,保存的是模板文件生成文件的文件名称与文件地址;
        • 例如:
        {
            "file_name":"HttpApi.swift",
            "file_dirs":[
                {
                    "os_name":"Windows",
                    "path":"生成文件的目标路径(绝对路径)"
                },
                {
                    "os_name":"Mac OS X",
                    "path":"生成文件的目标路径(绝对路径)"
                }
            ]
        }
        
    5. 修改 target dir 下配置文件(x-http-wrapper.json)中 template_file_infos 中的 need_generate 属性,用于开启、关闭生成文件的功能;
      • 例如:若你想生成 HttpApi 类型的文件,就需要将 template_file_infos.HttpApi.need_generate 设置为 true ,并要修改了 xxx-httpapi.xhwt 文件中 header 标签内的地址;
        "template_file_infos": {
          "HttpApi": {
            "need_generate": true,
            "path": "ncm_ios_n-httpapi.xhwt"
          },
          ...
        }
      
    6. 命令行生成相关 http 文件
      • 命令行运行: java -jar (jar 文件的路径) (配置文件的绝对路径)
      • jar 文件的路径:在 guide 文件夹下有最新的 jar(x-http-wrapper.jar)
      • 配置文件的绝对路径:配置文件(x-http-wrapper.json)的绝对路径
      java -jar x-http-wrapper.jar xxxx/x-http-wrapper.json
      

    api 的数据源:apidocjs

    • api_data.json 就是使用 apidocjs 工具生成的数据文件;

    工作流程

    1. 解析 x-http-wrapper.json 这个配置文件;
    2. 在配置文件中,有 API 数据文件(在 api_data 中),再根据配置数据,将 API 数据解析为 x-http-wrapper 中的 model 数据;
    3. 在配置文件中,有所有的 x-http-wrapper 的 template 文件(在 template_file_infos 中),根据 template 文件中的内容与 model datas 和配置一起,生成目标文件;

    最新的 jar

    1. 使用方式:
    java -jar x-http-wrapper.jar xxx/x-http-wrapper.json
    
    1. x-http-wrapper.json 文件,必须是绝对路径,该文件是整个 wrapper 的配置文件;
    2. 若有多个 json 文件,也可以(如:有多个程序(ios,android)需要生成代码);

    wrapper 的配置文件:

    wrapper 内部 api 数据模型

    1. BaseModel:
      • 所有的 model 都需要继承 BaseModel
      • BaseModel 中有一个泛型用于存储更高一级的 BaseModel
      • 在 template engine 中,反射只认 BaseModel ,不是 BaseModel 的 model 不能反射
      • template engine 在反射调用时,若没有在反射的对象中找到方法,会从 higherLevel 中去找,直到没有 higherLevel 为止;
    2. model 的结构:
      • VersionModel-->StatusCodeGroup, RequestGroup
      • StatusCodeGroup-->StatusCode
      • RequestGroup-->Request-->Url,Header,Input,Response
      • Response-->Response File,Response Message

    wrapper 模板文件的类型

    1. 所有的类别都在 XHWTFileType 枚举中,现阶段共有 6 个类别;
      • HttpApi, Request, RequestParam, Response, StatusCode, BaseResponse
    2. 且在该枚举中也有该模板类别所需数据的获取过滤功能(getReflectiveDatas 方法);

    wrapper 模板标签

    1. 生成的文件内容由该文件类型获取到的 API 数据与标签两者来驱动
    2. 头部标签<t:header></t:header>: 用于标示该模板文件,生成的目标文件路径和名称;
      • file_dirs:目标文件路径
      • file_name:目标文件名称
    3. 现阶段只有 7 个标签类型:使用反射来进行数据的加工
      • text, foreach, retain, list_single_line, if_else, list_replace, list_attach
      • 标签内部的匹配都为反射的方法名称;
        • 例如:在 foreach 标签中
        <t:foreach each="request_groups">
        </t:foreach>
        
        匹配的 request_groups 即为反射后去 request_groups 方法的数据,然后利用该数据去遍历;
    目前尚无回复
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   997 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 22:33 · PVG 06:33 · LAX 15:33 · JFK 18:33
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.