refactor: ai+人工重构优化 framework

This commit is contained in:
橙子
2025-02-23 03:06:06 +08:00
parent f9341fd2ac
commit 3e07ca822a
61 changed files with 2604 additions and 1260 deletions

View File

@@ -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();
}
}

View File

@@ -10,6 +10,7 @@
<ItemGroup>
<PackageReference Include="Volo.Abp.BackgroundJobs.Hangfire" Version="$(AbpVersion)" />
<PackageReference Include="Volo.Abp.BackgroundWorkers.Hangfire" Version="$(AbpVersion)" />
</ItemGroup>

View File

@@ -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>());
}

View File

@@ -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)
};
}
}

View File

@@ -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));