refactor: ai+人工重构优化 framework
This commit is contained in:
@@ -5,40 +5,72 @@ using Volo.Abp.Uow;
|
||||
|
||||
namespace Yi.Framework.BackgroundWorkers.Hangfire;
|
||||
|
||||
public class UnitOfWorkHangfireFilter : IServerFilter, ISingletonDependency
|
||||
/// <summary>
|
||||
/// Hangfire 工作单元过滤器
|
||||
/// 用于管理后台任务的事务处理
|
||||
/// </summary>
|
||||
public sealed class UnitOfWorkHangfireFilter : IServerFilter, ISingletonDependency
|
||||
{
|
||||
private const string CurrentJobUow = "HangfireUnitOfWork";
|
||||
private const string UnitOfWorkItemKey = "HangfireUnitOfWork";
|
||||
private readonly IUnitOfWorkManager _unitOfWorkManager;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化工作单元过滤器
|
||||
/// </summary>
|
||||
/// <param name="unitOfWorkManager">工作单元管理器</param>
|
||||
public UnitOfWorkHangfireFilter(IUnitOfWorkManager unitOfWorkManager)
|
||||
{
|
||||
_unitOfWorkManager = unitOfWorkManager;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 任务执行前的处理
|
||||
/// </summary>
|
||||
/// <param name="context">执行上下文</param>
|
||||
public void OnPerforming(PerformingContext context)
|
||||
{
|
||||
// 开启一个工作单元并存储到上下文中
|
||||
var uow = _unitOfWorkManager.Begin();
|
||||
context.Items.Add(CurrentJobUow, uow);
|
||||
context.Items.Add(UnitOfWorkItemKey, uow);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 任务执行后的处理
|
||||
/// </summary>
|
||||
/// <param name="context">执行上下文</param>
|
||||
public void OnPerformed(PerformedContext context)
|
||||
{
|
||||
AsyncHelper.RunSync(()=>OnPerformedAsync(context));
|
||||
AsyncHelper.RunSync(() => OnPerformedAsync(context));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 任务执行后的异步处理
|
||||
/// </summary>
|
||||
/// <param name="context">执行上下文</param>
|
||||
private async Task OnPerformedAsync(PerformedContext context)
|
||||
{
|
||||
if (context.Items.TryGetValue(CurrentJobUow, out var obj)
|
||||
&& obj is IUnitOfWork uow)
|
||||
if (!context.Items.TryGetValue(UnitOfWorkItemKey, out var obj) ||
|
||||
obj is not IUnitOfWork uow)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// 如果没有异常且工作单元未完成,则提交事务
|
||||
if (context.Exception == null && !uow.IsCompleted)
|
||||
{
|
||||
await uow.CompleteAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
// 否则回滚事务
|
||||
await uow.RollbackAsync();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
// 确保工作单元被释放
|
||||
uow.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Volo.Abp.BackgroundJobs.Hangfire" Version="$(AbpVersion)" />
|
||||
<PackageReference Include="Volo.Abp.BackgroundWorkers.Hangfire" Version="$(AbpVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -2,53 +2,82 @@
|
||||
using Hangfire;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Volo.Abp.BackgroundJobs.Hangfire;
|
||||
using Volo.Abp.BackgroundWorkers;
|
||||
using Volo.Abp.BackgroundWorkers.Hangfire;
|
||||
using Volo.Abp.DynamicProxy;
|
||||
|
||||
namespace Yi.Framework.BackgroundWorkers.Hangfire;
|
||||
|
||||
[DependsOn(typeof(AbpBackgroundWorkersHangfireModule))]
|
||||
public class YiFrameworkBackgroundWorkersHangfireModule : AbpModule
|
||||
/// <summary>
|
||||
/// Hangfire 后台任务模块
|
||||
/// </summary>
|
||||
[DependsOn(typeof(AbpBackgroundWorkersHangfireModule),
|
||||
typeof(AbpBackgroundJobsHangfireModule))]
|
||||
public sealed class YiFrameworkBackgroundWorkersHangfireModule : AbpModule
|
||||
{
|
||||
/// <summary>
|
||||
/// 配置服务前的预处理
|
||||
/// </summary>
|
||||
/// <param name="context">服务配置上下文</param>
|
||||
public override void PreConfigureServices(ServiceConfigurationContext context)
|
||||
{
|
||||
// 添加 Hangfire 后台任务约定注册器
|
||||
context.Services.AddConventionalRegistrar(new YiHangfireConventionalRegistrar());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 应用程序初始化
|
||||
/// </summary>
|
||||
/// <param name="context">应用程序初始化上下文</param>
|
||||
public override async Task OnApplicationInitializationAsync(ApplicationInitializationContext context)
|
||||
{
|
||||
//定时任务自动注入,Abp默认只有在Quartz才实现
|
||||
// 获取后台任务管理器和所有 Hangfire 后台任务
|
||||
var backgroundWorkerManager = context.ServiceProvider.GetRequiredService<IBackgroundWorkerManager>();
|
||||
var works = context.ServiceProvider.GetServices<IHangfireBackgroundWorker>();
|
||||
var workers = context.ServiceProvider.GetServices<IHangfireBackgroundWorker>();
|
||||
|
||||
// 获取配置
|
||||
var configuration = context.ServiceProvider.GetRequiredService<IConfiguration>();
|
||||
//【特殊,为了兼容内存模式,由于内存模式任务,不能使用队列】
|
||||
bool.TryParse(configuration["Redis:IsEnabled"], out var redisEnabled);
|
||||
foreach (var work in works)
|
||||
|
||||
// 检查是否启用 Redis
|
||||
var isRedisEnabled = configuration.GetValue<bool>("Redis:IsEnabled");
|
||||
|
||||
foreach (var worker in workers)
|
||||
{
|
||||
//如果为空,默认使用服务器本地上海时间
|
||||
work.TimeZone = TimeZoneInfo.Local;
|
||||
if (redisEnabled)
|
||||
// 设置时区为本地时区(上海)
|
||||
worker.TimeZone = TimeZoneInfo.Local;
|
||||
|
||||
if (isRedisEnabled)
|
||||
{
|
||||
await backgroundWorkerManager.AddAsync(work);
|
||||
// Redis 模式:使用 ABP 后台任务管理器
|
||||
await backgroundWorkerManager.AddAsync(worker);
|
||||
}
|
||||
else
|
||||
{
|
||||
object unProxyWorker = ProxyHelper.UnProxy((object)work);
|
||||
RecurringJob.AddOrUpdate(work.RecurringJobId,
|
||||
(Expression<Func<Task>>)(() =>
|
||||
((IHangfireBackgroundWorker)unProxyWorker).DoWorkAsync(default(CancellationToken))),
|
||||
work.CronExpression, new RecurringJobOptions()
|
||||
// 内存模式:直接使用 Hangfire
|
||||
var unProxyWorker = ProxyHelper.UnProxy(worker);
|
||||
|
||||
// 添加或更新循环任务
|
||||
RecurringJob.AddOrUpdate(
|
||||
worker.RecurringJobId,
|
||||
(Expression<Func<Task>>)(() =>
|
||||
((IHangfireBackgroundWorker)unProxyWorker).DoWorkAsync(default)),
|
||||
worker.CronExpression,
|
||||
new RecurringJobOptions
|
||||
{
|
||||
TimeZone = work.TimeZone
|
||||
TimeZone = worker.TimeZone
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 应用程序初始化前的预处理
|
||||
/// </summary>
|
||||
/// <param name="context">应用程序初始化上下文</param>
|
||||
public override void OnPreApplicationInitialization(ApplicationInitializationContext context)
|
||||
{
|
||||
// 添加工作单元过滤器
|
||||
var services = context.ServiceProvider;
|
||||
GlobalJobFilters.Filters.Add(services.GetRequiredService<UnitOfWorkHangfireFilter>());
|
||||
}
|
||||
|
||||
@@ -3,18 +3,32 @@ using Volo.Abp.DependencyInjection;
|
||||
|
||||
namespace Yi.Framework.BackgroundWorkers.Hangfire;
|
||||
|
||||
public class YiHangfireConventionalRegistrar : DefaultConventionalRegistrar
|
||||
/// <summary>
|
||||
/// Hangfire 后台任务约定注册器
|
||||
/// </summary>
|
||||
public sealed class YiHangfireConventionalRegistrar : DefaultConventionalRegistrar
|
||||
{
|
||||
/// <summary>
|
||||
/// 检查类型是否禁用约定注册
|
||||
/// </summary>
|
||||
/// <param name="type">要检查的类型</param>
|
||||
/// <returns>如果类型不是 IHangfireBackgroundWorker 或已被禁用则返回 true</returns>
|
||||
protected override bool IsConventionalRegistrationDisabled(Type type)
|
||||
{
|
||||
return !typeof(IHangfireBackgroundWorker).IsAssignableFrom(type) || base.IsConventionalRegistrationDisabled(type);
|
||||
return !typeof(IHangfireBackgroundWorker).IsAssignableFrom(type) ||
|
||||
base.IsConventionalRegistrationDisabled(type);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取要暴露的服务类型列表
|
||||
/// </summary>
|
||||
/// <param name="type">实现类型</param>
|
||||
/// <returns>服务类型列表</returns>
|
||||
protected override List<Type> GetExposedServiceTypes(Type type)
|
||||
{
|
||||
return new List<Type>()
|
||||
{
|
||||
typeof(IHangfireBackgroundWorker)
|
||||
};
|
||||
return new List<Type>
|
||||
{
|
||||
typeof(IHangfireBackgroundWorker)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,116 +6,141 @@ using Volo.Abp.Users;
|
||||
|
||||
namespace Yi.Framework.BackgroundWorkers.Hangfire;
|
||||
|
||||
public class YiTokenAuthorizationFilter : IDashboardAsyncAuthorizationFilter, ITransientDependency
|
||||
/// <summary>
|
||||
/// Hangfire 仪表盘的令牌认证过滤器
|
||||
/// </summary>
|
||||
public sealed class YiTokenAuthorizationFilter : IDashboardAsyncAuthorizationFilter, ITransientDependency
|
||||
{
|
||||
private const string Bearer = "Bearer: ";
|
||||
private string RequireUser { get; set; } = "cc";
|
||||
private TimeSpan ExpiresTime { get; set; } = TimeSpan.FromMinutes(10);
|
||||
private IServiceProvider _serviceProvider;
|
||||
private const string BearerPrefix = "Bearer ";
|
||||
private const string TokenCookieKey = "Token";
|
||||
private const string HtmlContentType = "text/html";
|
||||
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private string _requiredUsername = "cc";
|
||||
private TimeSpan _tokenExpiration = TimeSpan.FromMinutes(10);
|
||||
|
||||
/// <summary>
|
||||
/// 初始化令牌认证过滤器
|
||||
/// </summary>
|
||||
/// <param name="serviceProvider">服务提供者</param>
|
||||
public YiTokenAuthorizationFilter(IServiceProvider serviceProvider)
|
||||
{
|
||||
_serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
public YiTokenAuthorizationFilter SetRequireUser(string userName)
|
||||
/// <summary>
|
||||
/// 设置需要的用户名
|
||||
/// </summary>
|
||||
/// <param name="username">允许访问的用户名</param>
|
||||
/// <returns>当前实例,支持链式调用</returns>
|
||||
public YiTokenAuthorizationFilter SetRequiredUsername(string username)
|
||||
{
|
||||
RequireUser = userName;
|
||||
_requiredUsername = username ?? throw new ArgumentNullException(nameof(username));
|
||||
return this;
|
||||
}
|
||||
|
||||
public YiTokenAuthorizationFilter SetExpiresTime(TimeSpan expiresTime)
|
||||
/// <summary>
|
||||
/// 设置令牌过期时间
|
||||
/// </summary>
|
||||
/// <param name="expiration">过期时间间隔</param>
|
||||
/// <returns>当前实例,支持链式调用</returns>
|
||||
public YiTokenAuthorizationFilter SetTokenExpiration(TimeSpan expiration)
|
||||
{
|
||||
ExpiresTime = expiresTime;
|
||||
_tokenExpiration = expiration;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 授权验证
|
||||
/// </summary>
|
||||
/// <param name="context">仪表盘上下文</param>
|
||||
/// <returns>是否通过授权</returns>
|
||||
public bool Authorize(DashboardContext context)
|
||||
{
|
||||
var httpContext = context.GetHttpContext();
|
||||
var _currentUser = _serviceProvider.GetRequiredService<ICurrentUser>();
|
||||
//如果验证通过,设置cookies
|
||||
if (_currentUser.IsAuthenticated)
|
||||
var currentUser = _serviceProvider.GetRequiredService<ICurrentUser>();
|
||||
|
||||
if (!currentUser.IsAuthenticated)
|
||||
{
|
||||
var cookieOptions = new CookieOptions
|
||||
{
|
||||
Expires = DateTimeOffset.Now + ExpiresTime, // 设置 cookie 过期时间,10分钟
|
||||
};
|
||||
|
||||
|
||||
var authorization = httpContext.Request.Headers["Authorization"].ToString();
|
||||
if (!string.IsNullOrWhiteSpace(authorization))
|
||||
{
|
||||
var token = httpContext.Request.Headers["Authorization"].ToString().Substring(Bearer.Length - 1);
|
||||
httpContext.Response.Cookies.Append("Token", token, cookieOptions);
|
||||
}
|
||||
|
||||
if (_currentUser.UserName == RequireUser)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
SetChallengeResponse(httpContext);
|
||||
return false;
|
||||
}
|
||||
|
||||
SetChallengeResponse(httpContext);
|
||||
return false;
|
||||
// 如果验证通过,设置 cookie
|
||||
var authorization = httpContext.Request.Headers.Authorization.ToString();
|
||||
if (!string.IsNullOrWhiteSpace(authorization) && authorization.StartsWith(BearerPrefix))
|
||||
{
|
||||
var token = authorization[BearerPrefix.Length..];
|
||||
SetTokenCookie(httpContext, token);
|
||||
}
|
||||
|
||||
return currentUser.UserName == _requiredUsername;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置认证挑战响应
|
||||
/// 当用户未认证时,返回一个包含令牌输入表单的HTML页面
|
||||
/// </summary>
|
||||
/// <param name="httpContext">HTTP 上下文</param>
|
||||
private void SetChallengeResponse(HttpContext httpContext)
|
||||
{
|
||||
httpContext.Response.StatusCode = 401;
|
||||
httpContext.Response.ContentType = "text/html; charset=utf-8";
|
||||
string html = """
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Token 输入</title>
|
||||
<script>
|
||||
function sendToken() {
|
||||
// 获取输入的 token
|
||||
var token = document.getElementById("tokenInput").value;
|
||||
token = token.replace('Bearer ','');
|
||||
// 构建请求 URL
|
||||
var url = "/hangfire";
|
||||
// 发送 GET 请求
|
||||
fetch(url,{
|
||||
headers: {
|
||||
'Content-Type': 'application/json', // 设置内容类型为 JSON
|
||||
'Authorization': 'Bearer '+encodeURIComponent(token), // 设置授权头,例如使用 Bearer token
|
||||
},
|
||||
})
|
||||
.then(response => {
|
||||
if (response.ok) {
|
||||
return response.text(); // 或使用 response.json() 如果返回的是 JSON
|
||||
}
|
||||
throw new Error('Network response was not ok.');
|
||||
})
|
||||
.then(data => {
|
||||
// 处理成功返回的数据
|
||||
document.open();
|
||||
document.write(data);
|
||||
document.close();
|
||||
})
|
||||
.catch(error => {
|
||||
// 处理错误
|
||||
console.error('There has been a problem with your fetch operation:', error);
|
||||
alert("请求失败: " + error.message);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body style="text-align: center;">
|
||||
<h1>Yi-hangfire</h1>
|
||||
<h1>输入您的Token,我们将验证您是否为管理员</h1>
|
||||
<textarea id="tokenInput" placeholder="请输入 token" style="width: 80%;height: 120px;margin: 0 10%;"></textarea>
|
||||
<button onclick="sendToken()">校验</button>
|
||||
</body>
|
||||
</html>
|
||||
""";
|
||||
httpContext.Response.ContentType = HtmlContentType;
|
||||
|
||||
var html = @"
|
||||
<html>
|
||||
<head>
|
||||
<title>Hangfire Dashboard Authorization</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; margin: 40px; }
|
||||
.container { max-width: 400px; margin: 0 auto; }
|
||||
.form-group { margin-bottom: 15px; }
|
||||
input[type='text'] { width: 100%; padding: 8px; }
|
||||
button { background: #337ab7; color: white; border: none; padding: 10px 15px; cursor: pointer; }
|
||||
button:hover { background: #286090; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class='container'>
|
||||
<h2>Authorization Required</h2>
|
||||
<div class='form-group'>
|
||||
<input type='text' id='token' placeholder='Enter your Bearer token...' />
|
||||
</div>
|
||||
<button onclick='authorize()'>Authorize</button>
|
||||
</div>
|
||||
<script>
|
||||
function authorize() {
|
||||
var token = document.getElementById('token').value;
|
||||
if (token) {
|
||||
document.cookie = 'Token=' + token + '; path=/';
|
||||
window.location.reload();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>";
|
||||
|
||||
httpContext.Response.WriteAsync(html);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置令牌 Cookie
|
||||
/// </summary>
|
||||
/// <param name="httpContext">HTTP 上下文</param>
|
||||
/// <param name="token">令牌值</param>
|
||||
private void SetTokenCookie(HttpContext httpContext, string token)
|
||||
{
|
||||
var cookieOptions = new CookieOptions
|
||||
{
|
||||
Expires = DateTimeOffset.Now.Add(_tokenExpiration),
|
||||
HttpOnly = true,
|
||||
Secure = httpContext.Request.IsHttps,
|
||||
SameSite = SameSiteMode.Lax
|
||||
};
|
||||
|
||||
httpContext.Response.Cookies.Append(TokenCookieKey, token, cookieOptions);
|
||||
}
|
||||
|
||||
public Task<bool> AuthorizeAsync(DashboardContext context)
|
||||
{
|
||||
return Task.FromResult(Authorize(context));
|
||||
|
||||
Reference in New Issue
Block a user