對接第三方系統實操經驗分享

對接第三方系統實操經驗分享

前言

爲使得指示性更強,有以下名詞說明

  • A系統:是指要發起對接的我方系統,可以理解成 Client
  • B系統:是要對接的第三方系統,可以理解成 Server

對接第三方的特殊性

  • 請求方式不同。比較老的系統有可能使用 webservice 接受參數,比較人性化的系統會使用 http + json 方式接受參數
  • 登錄方式不同。有些系統壓根就不需要登錄,有些系統通過 token登錄,有些系統通過 cookie 登錄
  • 字段名不同。除非雙方共同開發,否則字段名不一致的概率幾乎爲 100%
  • 基礎資料不同。就算是同一個供應商,在A系統的唯一id 爲 333,在B系統的唯一id 爲 supplier301,這纔是棘手之處
  • 網絡請求不可靠,數據一致性(事務)無法保證。對接過程中,你想實現分佈式事務的可能性基本爲0,因爲你基本沒有可能能使得對面的數據庫按照你的意願 回滾。示例如下圖:

    服務器成功了,但客戶端失敗

對接框架必備功能

  • 字段名轉換工具類
  • 字段值轉換機制
  • 重試機制
  • 系統數據一致性(冪等)機制
  • 完整的日誌機制

必備功能實現參考方案

字段名轉換工具類

方案一:使用註解建立字段關聯關係

  • 例子:
	@ErpDataField(name = "FSaler", dataType = ErpBaseInfoTypeEnum.Employee,keyName = "FSTAFFNUMBER")
	private Integer fsaleAid;

    @ErpDataField(name = "F_PAEZ_Text4")
    private String fsoNumber;
  • 解釋
  1. 類似fastjson@JSONField 註解一樣,對字段進行重命名,根據目標系統的字段名進行關聯
  2. 在重命名的同時,亦可以對字段值的類型進行標記,爲字段值轉換機制打上標記
  3. 可以按照對接框架的需要附上所需要的屬性,比如說 字段是否需要強轉成數字形式,單位是多少,保留多少位小數等屬性,也是爲了字段值轉換機制打基礎
  • 優點:
  1. 註解寫在字段上,清晰易懂,維護直接在代碼中
  2. 反射也更好獲取——工具類更好寫
  • 缺點:
  1. 由於字段之間的映射關係已經以 註解的形式 硬編碼寫在了代碼裏,想要 修改就必須重構代碼,而重構往往對於 生產環境是不可容忍的
  2. 有可能污染了代碼,尤其是 pojo(可以通過 新建一個vo類專門用於對接)

方案二:使用數據庫建立字段關聯關係

其實也就是將註解的內容 放進數據庫裏進行維護,通過如前端填寫註解表格進行 對接交互,可實時增刪改,更爲靈活

  • 優點:
  1. 靈活,不使用硬編碼
  2. 如果不涉及值轉換機制的新增,基本可以實現 不用寫任何代碼,不用重構代碼,即可新對接一張表單。
  • 缺點
  1. 反射工具類書寫難度加大:需要考慮到 字符串的拼寫、合法性以及嵌套字段名解析等問題

字段值轉換方案

我們知道 A系統中供應商id 爲 333 的供應商 = B系統中 供應商唯一主鍵 爲 S303 的供應商,我們需要在值轉換階段,將其轉換成 B系統能夠接受的值

建議實現步驟

  1. 在字段名轉換階段,給字段打上 值轉換類型屬性(如上述註解中的dataType屬性)
  2. 維護值轉換 策略模式集合,各種轉換策略實現同一個接口
public interface ErpBaseInfoHandler {
    String handleField(ErpJsonField jsonField,String extInfo);
}
public enum ErpBaseInfoTypeEnumAdmin{
    None(0,"不是基本信息", NotBaseInfoHandler.class),
    Channel(1,"渠道客戶", ChannelBaseInfoHandler.class),
    XyCompany(2,"公司主體Id",  XyCompanyBaseInfoHandler.class),
    Employee(3,"員工faid", EmployeeBaseInfoHandler.class),
    Currency(4,"幣種", CurrencyBaseInfoHandler.class),
    //....
 }
  1. 在值轉換階段,根據值轉換類型進入對應的轉換策略中

轉換策略推薦實現方案——以員工信息爲例

  • 輸入的參數含有 需要轉換的A系統的員工id值 : A23,要轉換成 B系統的值 爲 B100567
  • 以下 值映射關係 是指 A系統的值 => B系統的值,如A系統員工id A23 => B系統 的 B100567

值映射關係不穩定

也就是說 A23 => B100567 在某時刻是成立的,但是之後又不成立了

此種情況只能通過 B系統開發轉換接口,然後A系統每次轉換值時 調用接口 實時查詢對應的值

值映射關係穩定

  1. 根據員工id查找數據庫,如果可以找到映射關係則立即返回
  2. 根據B系統提供的查詢接口(如果有),則根據一定的篩選條件獲取對應的B系統的值(如根據員工工號 查詢B系統的員工id),如果查詢成功,則保存映射關係保存在數據庫中(以便再次查詢),然後立即返回
  3. 如果還不行,則根據B系統提供的新增表單接口(如果有),則以A系統的員工 在B系統創建 該員工的信息,一般創建成功 B系統 會返回能唯一確定這個員工的 B系統id,此時也將映射關係保存到數據庫中,然後返回
  4. 否則,則報錯,手動處理
public class EmployeeBaseInfoHandler implements ErpBaseInfoHandler{
    @Override
    public String handleField(ErpJsonField jsonField,String extInfo){
        //在數據庫中查詢
        String dbFnumber = this.searchInDb(jsonField);
        if (!StringUtils.isEmpty(dbFnumber)) {
            return dbFnumber;
        }
        String fname = this.getFname(jsonField);
        //在第三方系統,如 erp 中查詢
        String fnumberByName = this.searchInErpByFname(jsonField, fname);
        if (!StringUtils.isEmpty(fnumberByName)){
            return fnumberByName;
        }
        //使用現有的員工信息,保存至第三方系統
        String fnumber = saveToGetFnumber(jsonField, fname);
        return fnumber;
    }
}

重試機制

前文我們提到過 任何一個網絡請求 都可能出現 失敗情況,因此我們必須要考慮加入重試功能

建議實現方案

  1. 推送服務接收到 數據後,立即將能唯一確定數據的 信息保存在數據庫,然後將 原始數據 臨時緩存在 Redis等緩存容器

    保存在數據庫的信息,必須能保證足夠可以用從 緩存容器 中重新取出 原始數據

  2. 最後再進行 值轉換、發送網絡請求等操作

  3. 所有操作成功後,將緩存刪除;否則,延長 緩存時間

    記得緩存時間 要大於 預期下一次定時任務處理時間,否則還沒等到 定時任務來處理自己,緩存的數據就沒了

觸發機制

定時任務觸發
設置原因
  1. 解決由於非數據錯誤導致的失敗,如網絡波動
  2. 減少人工處理的壓力
注意事項
  1. 設置最大失敗次數、緩存超時機制,以便淘汰由於數據錯誤導致的對接失敗
  2. 設置每次最大處理量,保證不會使得 A系統的服務壓力猛增 ,同時 B系統也不一定能夠同時處理那麼多內容
人工立即觸發
設置原因
  1. 肯定有等不到定時任務,緊急需要重試 的需求
  2. 方便測試
注意事項
  1. 不需要跟定時任務一樣處理多條任務,往往用戶只需要 緊急處理 少量的單據
源頭手動觸發
設置原因
  1. 方便測試
  2. 緩存已經超時,需要重新提交數據
  3. 數據源有可能已經修改了——尤其在 由於數據錯誤導致失敗時,更需要使用
注意事項
  1. 源頭重新發起後,如果舊數據仍留在 緩存中,應該用新數據覆蓋
  2. 源頭重新發起後,舊數據對接產生的日誌記錄不應該被抹去,應該被保留下來,以便追蹤問題

系統數據一致性(冪等)機制實現

但是如果重試,有可能就會出現以下情況
創建了兩張單

問題分析

爲什麼會出現上圖的結果?關鍵在於 A系統在請求失敗後無法判斷請求是否成功了

想一下,如果你是B系統的設計者,你要怎樣才能讓 A系統 知道是否成功了呢?

冪等機制支持關鍵點

舉例

冪等機制需要 B系統來支持,什麼樣的系統才能支持冪等機制?我以 金蝶K3Cloud ERP 中銷售訂單表單(下稱 SO單)舉例,以下是簡介:

  1. so單中的訂單號(字段名:FbillNo) 是唯一的,支持創建的時候自定義訂單號(關鍵1),如不自定義則自動生成
  2. 除此之外,每一張創建的單據有另外一個唯一的自增id,稱爲 FerpId
  3. erp提供了查詢接口,可以根據 訂單號 查詢 表單信息,包括 FerpId(關鍵2)
關鍵點分析: 存在一個字段,可以在創建階段指定值並且可以據此值查詢到表單是否存在

如果提供以上的條件,就可以實現冪等機制:因爲你可以根據上次請求預期結果,來查詢上次預期結果是否成功

可以在創建階段指定值 的字段

問:這種字段非常多,能傳值的字段都是可以指定值的字段,是否需要挑選值做了唯一索引的呢?
答:有更好,沒有也可以。爲什麼呢?因爲此值是 A系統自己控制的,A系統在傳值時完全可以構造一個全局唯一的 值,比如加入時間戳、全局id生成器之類的。
再問:如果我選的 K 字段沒有唯一索引,A系統傳的值爲 :k123-20200613,此時保存成功了但A系統沒有收到結果,超時失敗了;但是B系統的前端用戶想搞事情,也創建了一張 K 字段爲 k123-20200613,保存成功了。後來A系統重試時,查詢時發現了兩張單,不知道哪張單是它推送過去的了,這怎麼辦?
再答:這就是爲啥說 有唯一索引更好 的原因。個人覺得程序無法解決,只能人工來處理,揪出這個”搗蛋鬼”了。

可以據指定值查詢到單據是否存在 的字段

這個是冪等機制的基礎!因爲沒有 這個字段或者查詢接口不支持 ,則無法在重試前查詢 上一次推送的結果是否成功

實現示例
  1. A系統在每次重試so單推送erp前,需要根據 so單號 去查詢此 so單是否存在於erp
  2. 如果存在,則將 FerpId存入數據庫,並標記爲已成功
  3. 如果沒有查詢成功(網絡又失敗了),則放棄重試
  4. 如果查詢結果說明不存在,則重新推送
注意事項
  1. 不是所有第三方系統都符合實現冪等機制的條件,也不是所有的 A系統都有實現冪等機制的需求,如發送郵件、短信——多一條感覺也沒什麼關係(反正都是垃圾郵件/郵箱)

完整的日誌機制

推送日誌

推送日誌是 事後定位bug 重要內容,最好不要只停留在日誌文件中,數據庫中應該也保留最近一次的錯誤原因等重要信息

日誌基本元素
  1. 推送時間
  2. 錯誤原因
  3. 推送人(如果有)
  4. 推送數據(如果是json就得打印,webservice 就算了吧)
日誌打印時間點
  1. 構造數據前:開始構造數據了
  2. 網絡請求前:請求參數
  3. 網絡請求後:請求結果,catch 超時異常
日誌打印原則
  1. 重要操作
  2. 易錯操作
  3. 反映程序執行鏈路,也就是可追蹤

回調日誌

如果B系統會在某個時間回調 A系統的某些接口,一定要打印接收到的原始數據,因爲這是 追責的重要依據。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章