用vetr.x寫一個HTTP介面適配器, 對接各種形式介面

  • 2019 年 10 月 3 日
  • 筆記

用vetr.x寫一個HTTP介面適配器, 對接各種形式介面

項目地址:https://github.com/hjx601496320/transmit

業務說明

在日常開發工作中,我們經常會遇到要和各種第三方調試介面的情況,如果是簡單的幾個介面還好,程式碼寫起來很快就寫好了。但是如果在某一種業務情況下,比如支付,我們對接了很多家第三方的支付公司,每一家的支付介面都不一樣,這時就需要針對多家不同的介面文檔編寫不同的程式碼。又或者我們作為介面提供方提供一套標準的介面,但是某些客戶會比較強硬,要求你提供的介面需要按照對方的要求來做,這時就又需要苦哈哈的寫一個適配他們的程式碼。這個過程就十分的難受了。

我所在的項目就遇到了這種問題,我在的項目是做保險業務的,現在需要對接多家保險公司的介面,每家數據還是那些常見的數據,但是數據結構都不相同。有些是XML的,有些是JSON的。像性別,證件類型的枚舉值也都不大相同。

所以根據現有的情況,我開發了一個HTTP介面適配工具。用於支援各種類型的HTTP介面轉發,轉換請求數據,轉換響應數據的需求。

項目說明

業務需求

  1. 可配置,可配置,可配置(重要的說3邊)
  2. 可以接受常見的HTTP請求, 比如POST+JSON,POST+XML,GET,POST+表單等
  3. 數據轉換過程不需要寫java程式碼。
  4. 可以實現介面加密解密等功能
  5. 數據落庫。

開發思路

  1. 因為這個可配置是很重要的需求,但是我又不想寫頁面,所以定義一個配置文件是必不可少的,這裡配置文件我使用json格式。
  2. 對於介面資訊的描述,可以寫在配置文件中,比如定義一個接受請求的地址,再定義一個轉發請求的地址,另外加上他們的數據格式和請求類型的描述。
  3. 數據轉換這裡,因為涉及到xml和json格式的報文,每個報文的節點和層級深度都是不一樣的,另外還要做到能夠互相轉換。所以我的做法是將他們深度遍歷之後,做成Map<String, Object>這種的數據類型,其中Object 可以使另外一個Map, 也可以是一個List。總之就是一個樹狀結構。解析完數據之後,將數據放進freemarker模板文件中完成轉換。當然這個模板是需要自己編寫的。
  4. 再能夠完成數據轉換之後需要考慮的是參數加密和簽名的步驟,這可以設計一個介面,做成插件的形式,然後自己實現簽名和驗簽的步驟。就類似jmeter的插件一樣,做成一個jar包放在項目里就可以用了。
  5. 數據落庫這裡很簡單,將接受到的原始數據和介面返回的原始數據保存入庫,並記錄調用開始時間和調用結束時間就好了。
  6. 項目框架上我選擇的是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

Tags: