用vetr.x写一个HTTP接口适配器, 对接各种形式接口
用vetr.x写一个HTTP接口适配器, 对接各种形式接口
项目地址:https://github.com/hjx601496320/transmit
业务说明
在日常开发工作中,我们经常会遇到要和各种第三方调试接口的情况,如果是简单的几个接口还好,代码写起来很快就写好了。但是如果在某一种业务情况下,比如支付,我们对接了很多家第三方的支付公司,每一家的支付接口都不一样,这时就需要针对多家不同的接口文档编写不同的代码。又或者我们作为接口提供方提供一套标准的接口,但是某些客户会比较强硬,要求你提供的接口需要按照对方的要求来做,这时就又需要苦哈哈的写一个适配他们的代码。这个过程就十分的难受了。
我所在的项目就遇到了这种问题,我在的项目是做保险业务的,现在需要对接多家保险公司的接口,每家数据还是那些常见的数据,但是数据结构都不相同。有些是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());
}
}
就完成了。
没了
最后项目地址:https://github.com/hjx601496320/transmit
用vetr.x写一个HTTP接口适配器, 对接各种形式接口的更多相关文章
- Java基础-接口.编写2个接口:InterfaceA和InterfaceB;在接口InterfaceA中有个方法void printCapitalLetter();在接口InterfaceB中有个方法void printLowercaseLetter();然 后写一个类Print实现接口InterfaceA和InterfaceB,要求 方法 实现输出大写英文字母表的功能,printLowerca
#34.编写2个接口:InterfaceA和InterfaceB:在接口InterfaceA中有个方法void printCapitalLetter():在接口InterfaceB中有个方法void ...
- 我写了一个java实体类,implements了Serializable接口,然后我如何让serialversionUID自动生成
写了一个java实体类,implements了Serializable接口,让serialversionUID自动生成方法: 1.点击类旁边的警告符号: 2.选择Add generated seria ...
- 比最差的API(ETW)更差的API(LTTng)是如何炼成的, 谈如何写一个好的接口
最近这几天在帮柠檬看她的APM系统要如何收集.Net运行时的各种事件, 这些事件包括线程开始, JIT执行, GC触发等等. .Net在windows上(NetFramework, CoreCLR)通 ...
- 用openresty(Lua)写一个获取YouTube直播状态的接口
文章原发布于:https://www.chenxublog.com/2019/08/29/openresty-get-youtube-live-api.html 之前在QQ机器人上面加了个虚拟主播开播 ...
- 用flask写一个简单的接口
用falsk写一个简单的接口,这个接口的数据本来是爬虫爬取的数据,但是今天只写一个flask接口,数据就用测试数据好了. import random import re import time imp ...
- 第二篇 -- Django写一个接口并用Jmeter进行测试
第一节学习了Jmeter的下载和安装,那么第二节就来看看具体怎么使用. 本篇介绍的是使用Jmeter进行http接口测试,那么接口程序使用Django开发的一个小接口. 一.Django编写接口 这一 ...
- 深入理解spring中的AOP原理 —— 实现MethodInterceptor接口,自已动手写一个AOP
1.前言 AOP是面向切面编程,即“Aspect Oriented Programming”的缩写.面对切面,就是面向我们的关注面,不能让非关注面影响到我们的关注面.而现实中非关切面又必不可少,例 ...
- Spring系列之手写一个SpringMVC
目录 Spring系列之IOC的原理及手动实现 Spring系列之DI的原理及手动实现 Spring系列之AOP的原理及手动实现 Spring系列之手写注解与配置文件的解析 引言 在前面的几个章节中我 ...
- 如何写一个简单的http服务器
最近几天用C++写了一个简单的HTTP服务器,作为学习网络编程和Linux环境编程的练手项目,这篇文章记录我在写一个HTTP服务器过程中遇到的问题和学习到的知识. 服务器的源代码放在Github. H ...
随机推荐
- ABAP_增强点查找
*&---------------------------------------------------------------------* *& Report Z_FIND_EN ...
- (二十一)c#Winform自定义控件-气泡提示
前提 入行已经7,8年了,一直想做一套漂亮点的自定义控件,于是就有了本系列文章. 开源地址:https://gitee.com/kwwwvagaa/net_winform_custom_control ...
- mybatis的sql参数化查询
我们使用jdbc操作数据库的时候,都习惯性地使用参数化的sql与数据库交互.因为参数化的sql有两大有点,其一,防止sql注入:其二,提高sql的执行性能(同一个connection共用一个的sql编 ...
- java io读取性能对比
背景 从最早bio的只支持阻塞的bio(同步阻塞) 到默认阻塞支持非阻塞nio(同步非阻塞+同步阻塞)(此时加入mmap类) 再到aio(异步非阻塞) 虽然这些api改变了调用模式,但真正执行效率上是 ...
- File Compression and Archiving in linux (linux 中文件的归档)
1. Compressing Files at the Shell Prompt Red Hat Enterprise Linux provides the bzip2, gzip, and zip ...
- Element-UI 2.4.11 版本 使用注意(发现一点更新一点)
1.$Vue.$refs.addForm.resetFields() 的resetFields()方法重置到默认值并不是 ,你在form绑定对象上写的默认值 ,而是这个form被渲染出来之后第一次赋到 ...
- gin+vue的前后端分离开源项目
该项目是gin+vue的前后端分离项目,使用gorm访问MySQL,其中vue前端是使用vue-element-admin框架简单实现的: go后台使用jwt,对API接口进行权限控制.此外,Web页 ...
- IT人不仅要提升挣钱能力,更要拓展挣钱途径
前几天我上班路上,和小区门口开车的师傅闲聊,发现他们虽然学历不高,但挣钱的途径不少,比如固定接送多位客户,然后能通过朋友圈拓展新客户,而且通过客户口口相传,也能不断拉到生意,算下来每月也能挣1万5出头 ...
- input 上传图片
<!--多图上传--><input name="image_mortgage_property[]" type="file" multiple ...
- Java多线程之线程的生命周期
Java多线程之线程的生命周期 一.前言 当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态.在线程的生命周期中,它要经过新建(New).就绪(Runnable).运行(R ...