背景介紹
通常業務中需要用到定時執行功能,我用hangfire搭建了一個調度服務,這個調度服務是獨立於業務邏輯的,具體可以參考文章:https://github.com/yuzd/Hangfire.HttpJob/wiki
也就是說只要我有了這個調度服務后,只要提供給我的接口 我就可以調度它(比如在xx點xx分運行,或者每隔xx分運行,或者每周一8點運行等等)。
但是有一個問題,對方的接口是調用成功還是失敗完全取決於對方的接口設計!
有的接口被設計成 請求的StatusCode 是200的是代表接口成功,非200的代表接口失敗。
有的接口被設計成返回的json結構有一個特定的字段來代表接口調用成功還是失敗,例如:success字段。比如返回的結構大概這樣子: {"success":false,"data":"xxxx"}
等等,這些都是case by case ,不同的寫接口的人定的規則可能不一樣,通過webjob的調用方式如何動態的驗證成功還是失敗呢?
如上面提到動態的驗證結果,我分了2種情況處理:要么看Response返回的statuscode,要么是看返回的結果里面的指定字段來判斷!
1. 根據Response返回的statuscode
statuscode很好做,我在設計Hangfire.HttpJob這個擴展插件時是可以在外部設置一個驗證委托
默認的返回的statuscode 小於 400 則認為http請求是成功的,否則失敗
2. 返回的結果里面的指定字段來判斷
我采用EL表達式來實現的,EL表達式將請求的返回體設置為json變量,然后在表達式中可以直接以屬性的方式到值,表達式返回布爾類型。
針對不同的接口,我可以設置一個獨立的表達式來進行判斷!
首先在job添加的時候設置el表達式!如下圖
如何寫EL表達式:(很簡單,返回的結構體是什么字段就可以用什么字段)
接口的返回體是一個string。我先將這個String轉成json類型(dynamic)
然后是采用Spring.EL表達式實現的。 CallbackEL表達式的返回類型是布爾類型
返回體在表達式里面是有下面2個變量:
- #resultBody 是返回體的 string
- #result 是返回體的 json體(根據上面轉的,如果上面是非json格式的那就不能使用這個變量了)
比如說我調用的httpjob 返回體是
{"Success":false,"Info":"test"}
那么我可以這么寫
"CallbackEL": "#result.Success"
也可以這么寫
"CallbackEL": "#result.Info.Equals('ok')"
表達式如何運行的?
Spring.EL是我從Spring.Net里面剝離出來的一個組件,可以從nuget里面引用,支持net45和netstandard2.0
//檢查是否有設置EL表達式 if (!string.IsNullOrEmpty(item.CallbackEL)) { var elResult = InvokeSpringElCondition(item.CallbackEL, result, context, new Dictionary<string, object> { { "resultBody", result } }); if (!elResult) { throw new HttpStatusCodeException(item.CallbackEL, result); } RunWithTry(() => context.WriteLine($"【{Strings.CallbackELExcuteResult}:Ok 】" + item.CallbackEL)); }
/// <summary> /// 用EL表達式動態判斷是否執行成功 /// </summary> /// <returns></returns> private static bool InvokeSpringElCondition(string placeholder,string result, PerformContext context,Dictionary<string, object> param) { try { try { param["result"] = JsonConvert.DeserializeObject<ExpandoObject>(result); } catch (Exception) { //ignore } var parameterValue = ExpressionEvaluator.GetValue(null, placeholder, param); return (bool)parameterValue; } catch (Exception e) { context.WriteLine($"【{Strings.CallbackELExcuteError}】" + placeholder); context.WriteLine(e); return false; } }
調用對象 ExpressionEvaluator 傳 Dictionary<string, object> param 作為參數,使用#參數來引用。如果你的參數是string 那么可以寫c#中string的所有方法比如 StarsWith,EndsWith,Equals 等等
如果你的參數類型是一個dynamic,那你就可以直接像使用js的對象屬性一樣
Callback功能設計
舉例:
我們調用了A接口,如果A接口成功我們想把A接口的返回值作為請求參數再去調用B接口。
如果A接口失敗在調用C接口通知錯誤!
參考ajax的callback設計
如上圖 可以自行添加 Success 或者 Fail 作為回調
如果定義了Success 那么父job執行成功沒有報錯則運行 Success回調
如果定義了Fail 那么父job執行失敗 則運行 Fail回調
Success 里面還可以定義 Success 和 Fail
Fail 里面還可以定義 Success 和 Fail 如下圖:
回調的Json參數
字段 | 說明 |
---|---|
Url | 請求Url |
Method | Post,Get |
Data | Post時可以填,支持占位符(具體請看下面的介紹) |
ContentType | application/json |
Timeout | 超時(毫秒) |
BasicUserName | basicauth用戶名 |
BasicPassword | basicauth密碼 |
AgentClass | 基於jobAgent開發的httpjob需要填 |
Headers | key:value 的jsonstring "{"key":,"value"}" |
回調執行的邏輯
注意:回調不是作為新的的HangfireHttpJob執行的,是依附在最頂級的父Job的!
舉例:
如果:JobA -》 Fail B -> Success BB
JobA本身執行錯誤的話則會走重試邏輯(如果開啟重試的話),重試到頂后 進入 Fail B, Fail B 執行成功 則進入 Success BB。如果Success BB 執行成功,那么 JobA 則認為是成功的,否則認為失敗!
如果:JobA -》 Fail B -> Fail BB
JobA本身執行錯誤的話則會走重試邏輯(如果開啟重試的話),重試到頂后 進入 Fail B, Fail B 執行失敗 則進入 Fail BB 。Fail BB 執行失敗,那么 JobA 認為失敗!
Fail B 執行成功 進入 Success C, Success C,執行成功 認為 JobA 認為成功,否則 Job A 認為失敗!
總結:如果回調 則會按照設置的回調一路走下去,看最后一個回調是否成功。如果成功 則認為整個鏈路執行成功,否則認為失敗!
回調的代碼實現是一個遞歸的方式調用
為了實現回調能夠把上一個運行的結果最為參數,開發了占位符(placeholder)功能
也為了更好的擴展占位符功能,
首先要介紹下 dashbord里面的 全局配置 功能 如下圖:
- 全局配置 :存儲在當前目錄下的 hangfire_global.json 文件(可以在StartUp代碼修改HangfireHttpJobOptions.GlobalSettingJsonFilePath值指定其他地方
這個功能為了介紹重復的配置,可以集中配置一些參數,然后給各個job去使用!
占位符功能采用Spring.EL表達式實現的。
字符串中placeholder替換邏輯
- 第一步:把字符串中的 ${xxx} 的xxx全部替換成 全局配置里面的值
- 第二步:把字符串中的 #{yyy} 的yyy全部按照SpringEL表達式邏輯運行后的值進行替換
比如:上圖中你在全局配置了一個參數叫test
Data:"你好呀:${test}"
在運行時會被替換成 =》 你好呀:1
例如:使用父job的返回值傳給 callback
如果運行失敗傳給callback是報錯信息
例如:使用時間替換
可以直接在 #{} 方法里面用DateTime這個變量 這個變量和c#一樣的功能
比如
- #{DateTime.Now} 代表運行時的當前時間+時分秒
- #{DateTime.Today} 代表運行時的當天
- #{DateTime.Today.AddDays(-1)} 代表運行時的昨天
- #{DateTime.Today.AddDays(1)} 代表運行時的明天
總結:
以上 hangfire的webjob調度擴展組件(https://github.com/yuzd/Hangfire.HttpJob/wiki)
已經非常靈活了,基於hangfire的核心調度功能,加上webjob的調用方式,很方便的把業務邏輯分離出來!
不管業務接口如何寫,基於EL表達式都可以准確的判斷出來執行成功還是失敗,根據回調功能很方便的執行鏈式調用和錯誤通知!