给Hangfire的webjob增加callback和动态判断返回结果功能设计
背景介绍
通常业务中需要用到定时执行功能,我用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表达式都可以准确的判断出来执行成功还是失败,根据回调功能很方便的执行链式调用和错误通知!
给Hangfire的webjob增加callback和动态判断返回结果功能设计的更多相关文章
- NGINX 加载动态模块(NGINX 1.9.11开始增加加载动态模块支持)
NGINX 1.9.11开始增加加载动态模块支持,从此不再需要替换nginx文件即可增加第三方扩展.目前官方只有几个模块支持动态加载,第三方模块需要升级支持才可编译成模块. tinywan@tinyw ...
- Js~动态判断PC和手机浏览器
这个只是一个小知识,也是在网上找的,挺好用! 动态判断浏览器是PC还是移动端! <script> var browser={ versions:function(){ var u = na ...
- Mysql有没有语法可以在增加列前进行判断该列是否存在
Mysql没有直接的语法可以在增加列前进行判断该列是否存在,需要写一个存储过程完成同样任务,下面例子是:在sales_order表中增加一列has_sent列 drop procedure if ex ...
- ajax实现注册用户名时动态显示用户名是否已经被注册(1、ajax可以实现我们常见的注册用户名动态判断)(2、jquery里面的ajax也是类似我们这样封装了的函数)
ajax实现注册用户名时动态显示用户名是否已经被注册(1.ajax可以实现我们常见的注册用户名动态判断)(2.jquery里面的ajax也是类似我们这样封装了的函数) 一.总结 1.ajax可以实现我 ...
- ZooKeeper动态增加Server(动态增加节点)的研究(待实践)
说明:是动态增加Server,不是动态增加连接到ZK Server的Client. 场景如下(转自外文): 1.在t=t_1->[peer-1(Leader),peer-2],peer-1是主节 ...
- Bootstrap技术: 如何给nav导航组件的tab页增加关闭按钮以及动态的添加和关闭tab页
先给出示例html代码 <div> <!-- Nav tabs --> <ul class="nav nav-tabs" role="tab ...
- [f]动态判断js加载完成
在正常的加载过程中,js文件的加载是同步的,也就是说在js加载的过程中,浏览器会阻塞接下来的内容的解析.这时候,动态加载便显得尤为重要了,由于它是异步加载,因此,它可以在后台自动下载,并不会妨碍其它内 ...
- 08Mybatis_入门程序——增加用户的操作以及返回自增主键的功能以及返回非自增主键的功能
本文要实现的功能是:给user表增加一个用户. 建表如下:
- js动态判断密码强度&&实用的 jQuery 代码片段
// 网上拷贝的代码,效果不太好需要自己调整<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" &q ...
随机推荐
- 【转】Algorithms -离散概率值(discrete)和重置、洗牌(shuffle)算法及代码
离散概率值(discrete) 和 重置\洗牌(shuffle) 算法 及 代码 本文地址: http://blog.csdn.net/caroline_wendy/article/details/1 ...
- BitSet 的使用
BitSet 的简单介绍 BitSet,即位图,是位操作的对象,值只有 0 或 1(即 false 或 true). Java 的 BitSet 内部维护着一个 long 数组,默认初始化时数组的长度 ...
- Java控制台五子棋(纯算法)
Java五子棋小游戏 本方案是基于控制台写的一个代码 没有花里胡哨的界面,只为研究算法 仅仅用了200行代码 下面是的是运行结果 游戏运行结果 这里我就很简单的复制了一个结果 第9回合,下子方:玩家2 ...
- C# WPF 嵌入网页版WebGL油田三维可视化监控
0x00 楔子 最近做的一个项目,是一个油田三维可视化监控的场景编辑和监控的系统,和三维组态有些类似,不过主要用于油田上. 效果如下图所示: 首先当然是上模型,设计人员跟进. 有了相关的模型,使用我们 ...
- path_info和get_full_path()的区别
1.get_full_path() 获取的url路径包含参数 2.path_info 获取的路径不包含参数 注意:获取的路径都不包含协议 IP 和端口 3.补充 sesssion http://127 ...
- GStreamer基础教程13 - 调试Pipeline
摘要 在很多情况下,我们需要对GStreamer创建的Pipeline进行调试,来了解其运行机制以解决所遇到的问题.为此,GStreamer提供了相应的调试机制,方便我们快速定位问题. 查看调试日志 ...
- GP工作室-团队项目Beta冲刺
GP工作室-团队项目Beta冲刺 这个作业属于哪个课程 https://edu.cnblogs.com/campus/xnsy/GeographicInformationScience/ 这个作业要求 ...
- Dynamics 365 CRM Action 和 workflow 的区别
workflow 总是需要一个record作为起始点(create, update, delete or on-demand) 但是action 不需要. 例如我们需要action来创建一个发送ema ...
- Hbase与Maven工程的Spring配置笔记
1.HBase基本操作 hbase shell: 连接到正在运行的HBase实例 help: 显示一些基本的使用信息以及命令示例. 需要注意的是: 表名, 行, 列都必须使用引号括起来 create ...
- ElasticSearch入门篇Ⅰ --- ES核心知识概括
C01.什么是Elasticsearch 1.什么是搜索 垂直搜索(站内搜索) 互联网的搜索:电商网站,招聘网站,各种app IT系统的搜索:OA软件,办公自动化软件,会议管理,员工管理,后台管理系 ...