HangFire多集群切换及DashBoard登录验证
项目中是有多个集群的,现在存在一个是:在切换web集群时,如何切换HangFire的周期性任务。
先采取的解决办法是:
- 每个集群分一个队列,在周期性任务入队时分配当前web集群的集群id单做队列名称。
- 之前已存在的周期性任务,在其入队时修正到正确的集群执行
通过BackgroundJobServerOptions配置,只监听当前web集群的队列(ps:可参考文档:https://docs.hangfire.io/en/latest/background-processing/configuring-queues.html)
//只监听当前集群的队列
var options = new BackgroundJobServerOptions()
{
Queues = new[] { GlobalConfigSection.Current.WebClusterId }
};
HangfireAspNet.Use(() => new[] { new BackgroundJobServer(options) });
通过实现IElectStateFilter,重写OnStateElection方法,在任务入队前修正其入当前集群队列执行。
配置使用自定义熟悉
GlobalJobFilters.Filters.Add(new CustomJobFilterAttribute());
重写OnStateElection方法,通过修改enqueuedState的queue熟悉修正队列
/// <summary>
/// HangFire Filter
/// </summary>
public class CustomJobFilterAttribute : JobFilterAttribute, IElectStateFilter
{
public void OnStateElection(ElectStateContext context)
{
if (context.CandidateState is EnqueuedState enqueuedState)
{
var tenantConfigProvider = ObjectContainer.GetService<ITenantConfigProvider>();
var contextMessage = context.GetJobParameter<ContextMessage>("_ld_contextMessage");
var webClusterId = tenantConfigProvider.GetWebClusterIdByTenant(contextMessage.TenantId);
if (enqueuedState.Queue != webClusterId)//修正队列
{
enqueuedState.Queue = webClusterId;
}
}
} }
ps(更多的filter可以参考文档:https://docs.hangfire.io/en/latest/extensibility/using-job-filters.html)
附上HangFire执行失败记录日志实现

/// <summary>
/// HangFire Filter
/// </summary>
public class CustomJobFilterAttribute : JobFilterAttribute, IServerFilter
{ public void OnPerforming(PerformingContext filterContext)
{ } /// <summary>
/// 未成功执行的
/// </summary>
/// <param name="filterContext"></param>
public void OnPerformed(PerformedContext filterContext)
{
var error = filterContext.Exception;
if (error==null)
{
return;
}
//记录日志到后台
ILoggerFactory loggerFactory = ObjectContainer.GetService<ILoggerFactory>();
ILogger logger;
if (error.TargetSite != null && error.TargetSite.DeclaringType != null)
{
logger = loggerFactory.Create(error.TargetSite.DeclaringType.GetUnProxyType());
}
else
{
logger = loggerFactory.Create(GetType());
}
var contextMessage = filterContext.GetJobParameter<ContextMessage>("_ld_contextMessage");
var message = GetLogMessage(contextMessage, error.ToString(), filterContext.BackgroundJob.Id); var logLevel = ErrorLevelType.Fatal; if (error.InnerException is AppException ex)
{
logLevel = ex.ErrorLevel;
} switch (logLevel)
{
case ErrorLevelType.Info:
logger.Info(message, error);
break;
case ErrorLevelType.Warning:
logger.Warn(message, error);
break;
case ErrorLevelType.Error:
logger.Error(message, error);
break;
default:
logger.Fatal(message, error);
break;
}
} /// <summary>
/// 获取当前日志对象
/// </summary>
/// <returns></returns>
private LogMessage GetLogMessage(ContextMessage contextMessage, string errorMessage, string backgroundJobId)
{
var logMessage = new LogMessage(contextMessage, errorMessage)
{
RawUrl = backgroundJobId
};
return logMessage;
} }
现在还有一个问题是,HangFire DashBoard 默认只支持localhost访问,现在我需要可以很方便的在外网通过web集群就能访问到其对应的HangFire DashBoard。
通过文档https://docs.hangfire.io/en/latest/configuration/using-dashboard.html,可以知道其提供了一个登录验证的实现,但是由于其是直接写死了密码在代码中的,觉得不好。(ps:具体的实现可以参考:https://gitee.com/LucasDot/Hangfire.Dashboard.Authorization,https://www.cnblogs.com/lightmao/p/7910139.html)
我实现的思路是,在web集群界面直接打开标签页访问。在web集群后台生成token并在url中携带,在hangfire站点中校验token,验证通过则放行。同时校验url是否携带可修改的参数,如果有的话设置IsReadOnlyFunc放回false。(ps:可参考文档:https://docs.hangfire.io/en/latest/configuration/using-dashboard.html)
在startup页面配置使用dashboard,通过DashboardOptions选择配置我们自己实现的身份验证,以及是否只读设置。

public void Configuration(IAppBuilder app)
{
try
{ Bootstrapper.Instance.Start(); var dashboardOptions = new DashboardOptions()
{
Authorization = new[] { new HangFireAuthorizationFilter() },
IsReadOnlyFunc = HangFireIsReadOnly
};
app.UseHangfireDashboard("/hangfire", dashboardOptions); }
catch (Exception ex)
{
Debug.WriteLine(ex);
} } /// <summary>
/// HangFire仪表盘是否只读
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
private bool HangFireIsReadOnly(DashboardContext context)
{
var owinContext = new OwinContext(context.GetOwinEnvironment());
if (owinContext.Request.Host.ToString().StartsWith("localhost"))
{
return false;
} try
{
var cookie = owinContext.Request.Cookies["Ld.HangFire"];
char[] spilt = { ',' };
var userData = FormsAuthentication.Decrypt(cookie)?.UserData.Split(spilt, StringSplitOptions.RemoveEmptyEntries);
if (userData != null)
{
var isAdmin = userData[].Replace("isAdmin:", "");
return !bool.Parse(isAdmin);
}
}
catch (Exception e)
{ } return true;
}
在HangFireAuthorizationFilter的具体实现中,先校验是否已存在验证后的cookie如果有就不再验证,或者如果是通过localhost访问也不进行校验,否则验证签名是否正确,如果正确就将信息写入cookie。
/// <summary>
/// HangFire身份验证
/// </summary>
public class HangFireAuthorizationFilter : IDashboardAuthorizationFilter
{
public bool Authorize(DashboardContext context)
{
var owinContext = new OwinContext(context.GetOwinEnvironment()); if (owinContext.Request.Host.ToString().StartsWith("localhost")|| owinContext.Request.Cookies["Ld.HangFire"] != null)//通过localhost访问不校验,与cookie也不校验
{
return true;
} var cluster = owinContext.Request.Query.Get("cluster");//集群名称
var isAdminS = owinContext.Request.Query.Get("isAdmin");//是否管理员(是则允许修改hangfire)
var token = owinContext.Request.Query.Get("token");
var t = owinContext.Request.Query.Get("t");//时间戳
if (!string.IsNullOrEmpty(token) && bool.TryParse(isAdminS, out var isAdmin) && long.TryParse(t, out var timestamp))
{
try
{
var isValid = LicenceHelper.ValidSignature($"{cluster}_{isAdmin}", token, timestamp, TimeSpan.FromMinutes());//五分钟有效
if (isValid)
{
var ticket = new FormsAuthenticationTicket(, cluster, DateTime.Now, DateTime.Now + FormsAuthentication.Timeout, false, $"isAdmin:{isAdmin}");
var authToken = FormsAuthentication.Encrypt(ticket); owinContext.Response.Cookies.Append("Ld.HangFire", authToken, new CookieOptions()
{
HttpOnly = true,
Path = "/hangfire"
});
return true;
}
}
catch (Exception ex)
{ }
}
return false; } }
在web的管理后台具体实现为,同选中集群点击后台任务直接访问改集群的HangFire DashBoard


点击后台任务按钮,后台放回token相关信息,然后js打开一个新的标签页展示dashboard
public ActionResult GetHangFireToken(string clusterName)
{
var isAdmin=WorkContext.User.IsAdmin;
var timestamp = DateTime.UtcNow.Ticks;
var token = LicenceHelper.Signature($"{clusterName}_{isAdmin}", timestamp);
return this.Direct(new
{
isAdmin,
token,
timestamp=timestamp.ToString()
});
}
openHangFire:function() {
var me = this, rows = me.getGridSelection('查看后台任务的集群', true);
if (!rows) {
return;
}
if (rows.length > 1) {
me.alert('只能选择一行');
return;
}
var clusterName = rows[0].get('ClusterName');
var opts = {
actionName: 'GetHangFireToken',
extraParams: {
clusterName: clusterName
},
success: function (result) {
var data = result.result;
var url = Ext.String.format("{0}/hangfire?cluster={1}&isAdmin={2}&token={3}&t={4}",
rows[0].get("AccessUrl"),
clusterName,
data.isAdmin,
data.token,
data.timestamp);
window.open(url, clusterName);
}
};
me.directRequest(opts);
}
HangFire多集群切换及DashBoard登录验证的更多相关文章
- Nginx集群之WCF分布式身份验证(支持Soap)
目录 1 大概思路... 1 2 Nginx集群之WCF分布式身份验证... 1 3 BasicHttpBinding.ws2007HttpBinding. 2 4 ...
- Zookeeper 集群的安装及高可用性验证已完成!
安装包 kafka_2.12-0.10.2.0.tgz zookeeper-3.3.5.tar.gz Java 环境 Zookeeper 和 Kafka 的运行都需要 Java 环境,Kafka 默认 ...
- 【原创】Linux服务器集群通过SSH无密码登录
SSH 无密码授权访问slave集群机器 1. 安装SSH,所有集群机器,都要安装SSH环境介绍: Master : CNT06BIG01 192.168.3.61 SLAVE 1: CNT06BI ...
- spring cloud: Hystrix(八):turbine集群监控(dashboard)
turbine是聚合服务器发送事件流数据的一个工具,hystrix的监控中,只能监控单个节点,实际生产中都为集群, 因此可以通过turbine来监控集群下hystrix的metrics情况,通过eur ...
- [Kubernetes]集群配置免密登录Permission denied (publickey,password) 解决办法
在用ansible部署Kubernetes集群是需要配置免密登录,但是遇到Permission denied (publickey,password)的问题 首先推断可能是sshd_config的配置 ...
- KingbaseES R3 受限dba影响集群切换
一.受限dba功能说明(参考自官方文档) 受限DBA 受限DBA可以对当前DBA的权限进行一定限制.当功能开启后DBA将不能更改以下对象: Table Database Function(by n ...
- Spring Cloud项目之断路器集群监控Hystrix Dashboard
微服务(Microservices Architecture)是一种架构风格,一个大型复杂软件应用由一个或多个微服务组成.系统中的各个微服务可被独立部署,各个微服务之间是松耦合的.每个微服务仅关注于完 ...
- 【ssh免登录】设置集群环境ssh免登录步骤
1.每台机器都需要执行,生成自己的密钥 # ssh-keygen -t rsa 过程中遇到选项,全部enter #cd ~/.ssh # cat id_rsa.pub > authorized_ ...
- centos7生成密钥及集群之间免密登录
1.在本地生成密钥 命令:ssh-keygen -t dsa -P '' -f ~/.ssh/id_dsa 2.进入.ssh文件夹,创建保存密钥的文件 命令:touch authorized_keys ...
随机推荐
- os.remove() 删除文件
概述 os.remove() 方法用于删除指定路径的文件.如果指定的路径是一个目录,将抛出OSError. 在Unix, Windows中有效 语法 remove()方法语法格式如下: os.remo ...
- 049.Kubernetes集群管理-集群监控Metrics
一 集群监控 1.1 Metrics Kubernetes的早期版本依靠Heapster来实现完整的性能数据采集和监控功能,Kubernetes从1.8版本开始,性能数据开始以Metrics API的 ...
- 解决Mac上打开txt文件乱码问题
出处:https://www.jianshu.com/p/f55ddf1e9839 经常会在Mac上打开一个txt文件,发现里面的中文都是乱码,问题是在Windows和手机上看都完全是正常的,这就十分 ...
- WEB应用的常见安全漏洞
01. SQL 注入 SQL 注入就是通过给 web 应用接口传入一些特殊字符,达到欺骗服务器执行恶意的 SQL 命令.SQL 注入漏洞属于后端的范畴,但前端也可做体验上的优化.原因:当使用外部不 ...
- Spring整合JDBC temple
一.Spring对Jdbc的支持 Spring为了提供对Jdbc的支持,在Jdbc API的基础上封装了一套实现,以此建立一个 JDBC 存取框架. 作为 Spring JDBC 框架的核心, JDB ...
- Python中用OpenPyXL处理Excel表格 - 单元格格式设置
官方文档: http://openpyxl.readthedocs.io/en/default/ OpenPyXL库 --单元格样式设置 单元格样式的控制,依赖openpyxl.style包,其中定义 ...
- Docker精华 ,超全文档!
我们的口号是:再小的帆也能远航,人生不设限!! 学习规划:继续上篇 <Docker入门>https://www.cnblogs.com/dk1024/p/13121389.html ...
- docker安装mysql,设置mysql初始密码
docker安装mysql,只需要2分钟就可以完成 docker search mysql 拉取mysql镜像(https://hub.docker.com/_/mysql) docker pull ...
- Newtonsoft 六个超简单又实用的特性,值得一试 【下篇】
一:讲故事 上一篇介绍的 6 个特性从园子里的反馈来看效果不错,那这一篇就再带来 6 个特性同大家一起欣赏. 二:特性分析 1. 像弱类型语言一样解析 json 大家都知道弱类型的语言有很多,如: n ...
- xutils工具上传日志文件
首先下载xutils java包: 添加到项目的工程中: 第二在新建一个类继承application package logback.ecmapplication.cetcs.com.myapplic ...