用vetr.x写一个HTTP接口适配器, 对接各种形式接口
用vetr.x写一个http接口适配器, 对接各种形式接口
项目地址:
业务说明
在日常开发工作中,我们经常会遇到要和各种第三方调试接口的情况,如果是简单的几个接口还好,代码写起来很快就写好了。但是如果在某一种业务情况下,比如支付,我们对接了很多家第三方的支付公司,每一家的支付接口都不一样,这时就需要针对多家不同的接口文档编写不同的代码。又或者我们作为接口提供方提供一套标准的接口,但是某些客户会比较强硬,要求你提供的接口需要按照对方的要求来做,这时就又需要苦哈哈的写一个适配他们的代码。这个过程就十分的难受了。
我所在的项目就遇到了这种问题,我在的项目是做保险业务的,现在需要对接多家保险公司的接口,每家数据还是那些常见的数据,但是数据结构都不相同。有些是xml的,有些是json的。像性别,证件类型的枚举值也都不大相同。
所以根据现有的情况,我开发了一个http接口适配工具。用于支持各种类型的http接口转发,转换请求数据,转换响应数据的需求。
项目说明
业务需求
- 可配置,可配置,可配置(重要的说3边)
- 可以接受常见的http请求, 比如post+json,post+xml,get,post+表单等
- 数据转换过程不需要写java代码。
- 可以实现接口加密解密等功能
- 数据落库。
开发思路
- 因为这个可配置是很重要的需求,但是我又不想写页面,所以定义一个配置文件是必不可少的,这里配置文件我使用json格式。
- 对于接口信息的描述,可以写在配置文件中,比如定义一个接受请求的地址,再定义一个转发请求的地址,另外加上他们的数据格式和请求类型的描述。
- 数据转换这里,因为涉及到xml和json格式的报文,每个报文的节点和层级深度都是不一样的,另外还要做到能够互相转换。所以我的做法是将他们深度遍历之后,做成map<string, object>这种的数据类型,其中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" } } }
数据转换流程
配置示例
这里说一下,比如需要根据一个第三方接口添加一个转换, 第三方接口信息如下:
请求地址: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
参数示例:
请求数据 <demo> <info> <code>xxx-1</code> <uuid>d83a011a-958d-4310-a51b-0fb3a4228ef5</uuid> <time>2017-11-15 16:57:36</time> </info> <order> <serialno>0</serialno> <orderno>123123123</orderno> <ordercode>asdasdasd</ordercode> <result>1</result> </order> </demo> 需要返回的数据 <demo> <info> <code>1</code> </info> <order> <msg>success</msg> </order> </demo>
这时你需要添加如下配置
{ "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
<demo> <info> <code>${root.code}</code> </info> <order> <msg>${root.msg}</msg> </order> </demo>
配置完成后,启动transmit,向http://127.0.0.1:9527/getorder发送post请求,就可以转换你的请求参数,并完成对http://xxx.xxx.com/text/getorder接口的调用了。
关于模板中原始数据的访问
原始数据:
<demo> <info> <code>xxx-1</code> <uuid>d83a011a-958d-4310-a51b-0fb3a4228ef5</uuid> <time>2017-11-15 16:57:36</time> </info> <order> <serialno>0</serialno> <orderno>123123123</orderno> <ordercode>asdasdasd</ordercode> <result>1</result> </order> </demo>
对应的节点
${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, map<string, object> valuemap) throws exception; /** * 在api响应后执行 * * @param value 完整的数据 * @param valuemap 放进freemarker的数据 * @return */ string afterresponse(string value, map<string, object> valuemap) 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":[ "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()); } }
就完成了。
没了
最后项目地址: