业务说明

  在日常开发工作中,我们经常会遇到要和各种第三方调试接口的情况,如果是简单的几个接口还好,代码写起来很快就写好了。但是如果在某一种业务情况下,比如支付,我们对接了很多家第三方的支付公司,每一家的支付接口都不一样,这时就需要针对多家不同的接口文档编写不同的代码。又或者我们作为接口提供方提供一套标准的接口,但是某些客户会比较强硬,要求你提供的接口需要按照对方的要求来做,这时就又需要苦哈哈的写一个适配他们的代码。这个过程就十分的难受了。

  我所在的项目就遇到了这种问题,我在的项目是做保险业务的,现在需要对接多家保险公司的接口,每家数据还是那些常见的数据,但是数据结构都不相同。有些是XML的,有些是JSON的。像性别,证件类型的枚举值也都不大相同。

  所以根据现有的情况,我开发了一个HTTP接口适配工具。用于支持各种类型的HTTP接口转发,转换请求数据,转换响应数据的需求。

  项目说明

  业务需求

  可配置,可配置,可配置(重要的说3边)

  可以接受常见的HTTP请求, 比如POST+JSON,POST+XML,GET,POST+表单等

  数据转换过程不需要写java代码。

  可以实现接口加密解密等功能

  数据落库。

  开发思路

  因为这个可配置是很重要的需求,但是我又不想写页面,所以定义一个配置文件是必不可少的,这里配置文件我使用json格式。

  对于接口信息的描述,可以写在配置文件中,比如定义一个接受请求的地址,再定义一个转发请求的地址,另外加上他们的数据格式和请求类型的描述。

  数据转换这里,因为涉及到xml和json格式的报文,每个报文的节点和层级深度都是不一样的,另外还要做到能够互相转换。所以我的做法是将他们深度遍历之后,做成Map这种的数据类型,其中Object 可以使另外一个Map, 也可以是一个List。总之就是一个树状结构。解析完数据之后,将数据放进freemarker模板文件中完成转换。当然这个模板是需要自己编写的。

  再能够完成数据转换之后需要考虑的是参数加密和签名的步骤,这可以设计一个接口,做成插件的形式,然后自己实现签名和验签的步骤。就类似jmeter的插件一样,做成一个jar包放在项目里就可以用了。

  数据落库这里很简单,将接受到的原始数据和接口返回的原始数据保存入库,并记录调用开始时间和调用结束时间就好了。

  项目框架上我选择的是vert.x,他是一个事件驱动和非阻塞的java框架,根据网上看到的测试结果,效率非常的高,很适合做这种类似中间件的工具。

  项目介绍

  因为是一个已经开发完成的工具,在公司项目中使用良好。下面说一下如何使用。

  安装

  git clone https://github.com/hjx601496320/transmit.git

  cd transmit/

  mvn package

  cd target/

  //解压

  tar -zxvf transmit.tar.gz

  //启动

  sh bin/start.sh

  这里启动用的是linux的脚本,因为Windows脚本我不会写,所以只有linux的。

  也可以在项目中直接启动Main.java中的main方法。

  项目结构

  ├── bin 启动脚本

  │ ├── restart.sh 重启

  │ ├── start.sh 启动

  │ └── stop.sh 停止

  ├── config 配置

  │ ├── config.json 启动时加载的配置文件

  │ └── logback.xml 日志配置,使用logback

  ├── lib

  │ ├── commons-cli-1.4.jar 依赖,自己添加的插件jar可以放在这里

  ......

  │ ├── transmit-1.0-SNAPSHOT.jar

  │ ├── vertx-web-client-3.8.0.jar

  │ └── vertx-web-common-3.8.0.jar

  ├── log 日志

  │ ├── debug

  │ │ └── debug.2019-09-17.log

  │ ├── error

  │ │ └── error.2019-09-17.log

  │ └── info

  │ └── info.2019-09-17.log

  └── sout.log 启动时输出的日志

  配置说明

  {

  其他配置

  "config": {

  是否缓存模板文件,默认true. 关闭的话每次请求会重新加载模板,方便调试.

  "cache": true,

  系统端口号

  "port": 9090,

  引用其他的配置文件的文件路径

  "import": [

  ],

  其他组件加载, 执行CLass.forName, 可以加载自己定义的一些插件, 完成类似数据入库, 接口签名之类的功能

  "ext": [

  "com.hebaibai.ctrt.Driver"

  ],

  数据库配置, 用于保存接口请求日志

  "db": {

  "host": "127.0.0.1",

  "database": "dbname",

  "port": 3306,

  "username": "root",

  "password": "root"

  }

  },

  配置示例

  "config-demo": {

  接受请求

  "request": {

  接受请求的地址 127.0.0.1:9090/download

  "path": "/download",

  请求的方式

  "method": "GET",

  请求参数类型: FORM(表单提交), JSON(json), QUERY(?key=value&key2=value), TEXT(文本), XML(xml),

  "request-type": "QUERY",

  返回参数类型

  "response-type": "TEXT"

  },

  转发的接口配置

  "api": {

  接口请求地址

  "url": "http://127.0.0.1:9003/api/download",

  插件编号, 在ext中加载来的

  "extCode": "null",

  接口请求地址

  "method": "GET",

  请求参数类型

  "request-type": "QUERY",

  请求超时设置,默认3000 ms, 单位ms

  "timeout": 1,

  返回参数类型

  "response-type": "TEXT",

  请求参数转换模板

  "request-ftl": "/home/hjx/work/transmit/file/download-req.ftl",

  响应参数转换模板

  "response-ftl": "/home/hjx/work/transmit/file/download-res.ftl"

  }

  }

  }

  数据转换流程

  

用vetr.x写一个HTTP接口适配器, 对接各种形式接口_第1张图片


  配置示例

  这里说一下,比如需要根据一个第三方接口添加一个转换, 第三方接口信息如下:

  请求地址:http://xxx.xxx.com/text/getOrder

  请求方式:POST

  参数类型:JSON

  参数示例:

  接口请求参数

  {

  "header": {

  "code": "123123123",

  "date": "2019-09-19 14:28:57"

  },

  "body": {

  "orderCode": "O1231231231231231"

  }

  }

  接口返回参数

  {

  "code":1

  "msg":"success"

  }

  而你能够发送的数据是这样的:

  请求方式:POST

  参数类型:XML

  参数示例:

  请求数据

  

  

  XXX-1

  d83a011a-958d-4310-a51b-0fb3a4228ef5

  2017-11-15 16:57:36

  

  

  0

  123123123

  asdasdasd

  1

  

  

  需要返回的数据

  

  

  1

  

  

  success

  

  

  这时你需要添加如下配置

  {

  "config":{

  "port":9527,

  "import":[

  ],

  "ext":[

  ],

  "db":{

  "host":"127.0.0.1",

  "database":"dbname",

  "port":3306,

  "username":"root",

  "password":"root"

  }

  },

  "getOrder":{

  "request":{

  "path":"/getOrder",

  "method":"POST",

  "request-type":"XML",

  "response-type":"XML"

  },

  "api":{

  "url":"http://xxx.xxx.com/text/getOrder",

  "method":"POST",

  "request-type":"JSON",

  "response-type":"JSON",

  "request-ftl":"/home/hjx/work/transmit/file/getOrder-req.json",

  "response-ftl":"/home/hjx/work/transmit/file/getOrder-res.xml"

  }

  }

  }

  请求转换/home/hjx/work/transmit/file/getOrder-req.xml

  {

  "header": {

  "code": "${ROOT.Info.Code}",

  "date": "${ROOT.Info.Time}"

  },

  "body": {

  "orderCode": "${ROOT.Order.OrderCode}"

  }

  }

  响应转换模板/home/hjx/work/transmit/file/getOrder-res.xml

  

  

  ${ROOT.code}

  

  

  ${ROOT.msg}

  

  

  配置完成后,启动transmit,向http://127.0.0.1:9527/getOrder发送post请求,就可以转换你的请求参数,并完成对http://xxx.xxx.com/text/getOrder接口的调用了。

  关于模板中原始数据的访问

  原始数据:

  

  

  XXX-1

  d83a011a-958d-4310-a51b-0fb3a4228ef5

  2017-11-15 16:57:36

  

  

  0

  123123123

  asdasdasd

  1

  

  

  对应的节点

  ${ROOT.Info.Code}=XXX-1

  ${ROOT.Info.UUID}=d83a011a-958d-4310-a51b-0fb3a4228ef5

  ${ROOT.Info.Time}=2017-11-15 16:57:36

  ${ROOT.Order.SerialNo}=0

  ${ROOT.Order.OrderNo}=123123123

  ${ROOT.Order.OrderCode}=asdasdasd

  ${ROOT.Order.Result}=1

  插件编写

  插件有两种。

  一种

  是实现接口com.hebaibai.ctrt.transmit.util.ext。这个接口可以处理一些签名之类的操作,其中有四个方法。

  /**

  * 用于判断是否使用该插件,extCode为配置文件中的配置

  * @param extCode

  * @return

  */

  boolean support(String extCode);

  /**

  * 获取插件编号

  *

  * @return

  */

  String getCode();

  /**

  * 在api请求前前执行

  *

  * @param value 完整的数据

  * @param valueMap 放进freemarker的数据

  * @return 插件处理后的数据

  */

  String beforRequest(String value, MapvalueMap) throws Exception;

  /**

  * 在api响应后执行

  *

  * @param value 完整的数据

  * @param valueMap 放进freemarker的数据

  * @return

  */

  String afterResponse(String value, MapvalueMap) throws Exception;

  单独新建项目,实现该接口,添加一个类:

  package com.hebaibai.ctrt;

  import com.hebaibai.ctrt.ext.sign.PICC_XM_testSign;

  import com.hebaibai.ctrt.transmit.util.CrtrUtils;

  /**

  * 驱动

  */

  public class Driver {

  /**

  * 将插件添加进项目

  */

  static {

  System.out.println("加载 签名插件...");

  CrtrUtils.EXT_LIST.add(你编写的插件实例对象);

  }

  }

  之后将这个项目单独打成jar包,放进项目的lib文件夹中。之后修改配置:

  。。。

  "ext":[郑州妇科:http://www.zztjfk.com/

  "com.hebaibai.ctrt.Driver"

  ],

  。。。

  就完成了。

  另一种

  添加freemarker自定义指令,操作和上面的插件差不多,需要实现freemarker.template.TemplateDirectiveModel接口,然后

  package com.hebaibai.ctrt;

  import com.hebaibai.ctrt.ext.sign.PICC_XM_testSign;

  import com.hebaibai.ctrt.transmit.util.CrtrUtils;

  /**

  * 驱动

  */

  public class Driver {

  /**

  * 将插件添加进项目

  */

  static {

  System.out.println("加载 签名插件...");

  CrtrUtils.EXT_LIST.add(你编写的插件实例对象);

  //freeMarker 自定义指令

  CrtrUtils.FREEMARKER_DIRECTIVE_MODEL.put("has", new Has());

  }

  }

  就完成了。

  没了