對接第三方系統實操經驗分享
前言
爲使得指示性更強,有以下名詞說明
- 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;
- 解釋
- 類似
fastjson
的@JSONField
註解一樣,對字段進行重命名,根據目標系統的字段名進行關聯 - 在重命名的同時,亦可以對字段值的類型進行標記,爲字段值轉換機制打上標記
- 可以按照對接框架的需要附上所需要的屬性,比如說 字段是否需要強轉成數字形式,單位是多少,保留多少位小數等屬性,也是爲了字段值轉換機制打基礎
- 優點:
- 註解寫在字段上,清晰易懂,維護直接在代碼中
- 反射也更好獲取——工具類更好寫
- 缺點:
- 由於字段之間的映射關係已經以 註解的形式 硬編碼寫在了代碼裏,想要 修改就必須重構代碼,而重構往往對於 生產環境是不可容忍的
- 有可能污染了代碼,尤其是 pojo(可以通過 新建一個vo類專門用於對接)
- 參考代碼片段:一個詳細的註解
方案二:使用數據庫建立字段關聯關係
其實也就是將註解的內容 放進數據庫裏進行維護,通過如前端填寫註解表格進行 對接交互,可實時增刪改,更爲靈活
- 優點:
- 靈活,不使用硬編碼
- 如果不涉及值轉換機制的新增,基本可以實現 不用寫任何代碼,不用重構代碼,即可新對接一張表單。
- 缺點
- 反射工具類書寫難度加大:需要考慮到 字符串的拼寫、合法性以及嵌套字段名解析等問題
- 參考代碼:a2b對接腳手架項目
字段值轉換方案
我們知道 A系統中供應商id 爲 333
的供應商 = B系統中 供應商唯一主鍵 爲 S303
的供應商,我們需要在值轉換階段,將其轉換成 B系統能夠接受的值
建議實現步驟
- 在字段名轉換階段,給字段打上 值轉換類型屬性(如上述註解中的
dataType
屬性) - 維護值轉換 策略模式集合,各種轉換策略實現同一個接口
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),
//....
}
- 在值轉換階段,根據值轉換類型進入對應的轉換策略中
轉換策略推薦實現方案——以員工信息爲例
- 輸入的參數含有
需要轉換的A系統的員工id值 : A23
,要轉換成 B系統的值 爲B100567
- 以下 值映射關係 是指 A系統的值 => B系統的值,如A系統員工id A23 => B系統 的 B100567
值映射關係不穩定
也就是說
A23 => B100567
在某時刻是成立的,但是之後又不成立了
此種情況只能通過 B系統開發轉換接口,然後A系統每次轉換值時 調用接口 實時查詢對應的值
值映射關係穩定
- 根據員工id查找數據庫,如果可以找到映射關係則立即返回
- 根據B系統提供的查詢接口(如果有),則根據一定的篩選條件獲取對應的B系統的值(如根據員工工號 查詢B系統的員工id),如果查詢成功,則保存映射關係保存在數據庫中(以便再次查詢),然後立即返回
- 如果還不行,則根據B系統提供的新增表單接口(如果有),則以A系統的員工 在B系統創建 該員工的信息,一般創建成功 B系統 會返回能唯一確定這個員工的 B系統id,此時也將映射關係保存到數據庫中,然後返回
- 否則,則報錯,手動處理
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;
}
}
重試機制
前文我們提到過 任何一個網絡請求 都可能出現 失敗情況,因此我們必須要考慮加入重試功能
建議實現方案
-
推送服務接收到 數據後,立即將能唯一確定數據的 信息保存在數據庫,然後將 原始數據 臨時緩存在 Redis等緩存容器
保存在數據庫的信息,必須能保證足夠可以用從 緩存容器 中重新取出 原始數據
-
最後再進行 值轉換、發送網絡請求等操作
-
所有操作成功後,將緩存刪除;否則,延長 緩存時間
記得緩存時間 要大於 預期下一次定時任務處理時間,否則還沒等到 定時任務來處理自己,緩存的數據就沒了
觸發機制
定時任務觸發
設置原因
- 解決由於非數據錯誤導致的失敗,如網絡波動
- 減少人工處理的壓力
注意事項
- 設置最大失敗次數、緩存超時機制,以便淘汰由於數據錯誤導致的對接失敗
- 設置每次最大處理量,保證不會使得 A系統的服務壓力猛增 ,同時 B系統也不一定能夠同時處理那麼多內容
人工立即觸發
設置原因
- 肯定有等不到定時任務,緊急需要重試 的需求
- 方便測試
注意事項
- 不需要跟定時任務一樣處理多條任務,往往用戶只需要 緊急處理 少量的單據
源頭手動觸發
設置原因
- 方便測試
- 緩存已經超時,需要重新提交數據
- 數據源有可能已經修改了——尤其在 由於數據錯誤導致失敗時,更需要使用
注意事項
- 源頭重新發起後,如果舊數據仍留在 緩存中,應該用新數據覆蓋
- 源頭重新發起後,舊數據對接產生的日誌記錄不應該被抹去,應該被保留下來,以便追蹤問題
系統數據一致性(冪等)機制實現
但是如果重試,有可能就會出現以下情況
問題分析
爲什麼會出現上圖的結果?關鍵在於 A系統在請求失敗後無法判斷請求是否成功了
想一下,如果你是B系統的設計者,你要怎樣才能讓 A系統 知道是否成功了呢?
冪等機制支持關鍵點
舉例
冪等機制需要 B系統來支持,什麼樣的系統才能支持冪等機制?我以 金蝶K3Cloud ERP 中銷售訂單表單(下稱 SO單)舉例,以下是簡介:
- so單中的訂單號(字段名:
FbillNo
) 是唯一的,支持創建的時候自定義訂單號(關鍵1),如不自定義則自動生成 - 除此之外,每一張創建的單據有另外一個唯一的自增id,稱爲
FerpId
吧 - erp提供了查詢接口,可以根據 訂單號 查詢 表單信息,包括
FerpId
(關鍵2)
關鍵點分析: 存在一個字段,可以在創建階段指定值並且可以據此值查詢到表單是否存在
如果提供以上的條件,就可以實現冪等機制:因爲你可以根據上次請求預期結果,來查詢上次預期結果是否成功。
可以在創建階段指定值 的字段
問:這種字段非常多,能傳值的字段都是可以指定值的字段,是否需要挑選值做了唯一索引的呢?
答:有更好,沒有也可以。爲什麼呢?因爲此值是 A系統自己控制的,A系統在傳值時完全可以構造一個全局唯一的 值,比如加入時間戳、全局id生成器之類的。
再問:如果我選的 K 字段沒有唯一索引,A系統傳的值爲 :k123-20200613
,此時保存成功了但A系統沒有收到結果,超時失敗了;但是B系統的前端用戶想搞事情,也創建了一張 K 字段爲 k123-20200613
,保存成功了。後來A系統重試時,查詢時發現了兩張單,不知道哪張單是它推送過去的了,這怎麼辦?
再答:這就是爲啥說 有唯一索引更好 的原因。個人覺得程序無法解決,只能人工來處理,揪出這個”搗蛋鬼”了。
可以據指定值查詢到單據是否存在 的字段
這個是冪等機制的基礎!因爲沒有 這個字段或者查詢接口不支持 ,則無法在重試前查詢 上一次推送的結果是否成功了
實現示例
- A系統在每次重試so單推送erp前,需要根據 so單號 去查詢此 so單是否存在於erp
- 如果存在,則將
FerpId
存入數據庫,並標記爲已成功 - 如果沒有查詢成功(網絡又失敗了),則放棄重試
- 如果查詢結果說明不存在,則重新推送
注意事項
- 不是所有第三方系統都符合實現冪等機制的條件,也不是所有的 A系統都有實現冪等機制的需求,如發送郵件、短信——多一條感覺也沒什麼關係(反正都是垃圾郵件/郵箱)
完整的日誌機制
推送日誌
推送日誌是 事後定位bug 重要內容,最好不要只停留在日誌文件中,數據庫中應該也保留最近一次的錯誤原因等重要信息
日誌基本元素
- 推送時間
- 錯誤原因
- 推送人(如果有)
- 推送數據(如果是json就得打印,webservice 就算了吧)
日誌打印時間點
- 構造數據前:開始構造數據了
- 網絡請求前:請求參數
- 網絡請求後:請求結果,catch 超時異常
日誌打印原則
- 重要操作
- 易錯操作
- 反映程序執行鏈路,也就是可追蹤
回調日誌
如果B系統會在某個時間回調 A系統的某些接口,一定要打印接收到的原始數據,因爲這是 追責的重要依據。