diff --git a/Configurations/SimApiJobOptions.cs b/Configurations/SimApiJobOptions.cs
new file mode 100644
index 0000000..ccfef12
--- /dev/null
+++ b/Configurations/SimApiJobOptions.cs
@@ -0,0 +1,22 @@
+namespace SimApi.Configurations;
+
+public class SimApiJobOptions
+{
+ ///
+ /// WebUi地址,设置为null表示不启用
+ ///
+ public string? DashboardUrl { get; set; } = "/jobs";
+
+ public string DashboardAuthUser { get; set; } = "admin";
+ public string DashboardAuthPass { get; set; } = "Admin@123!";
+ public string? RedisConfiguration { get; set; }
+
+ public int? Database { get; set; } = null;
+ public SimApiJobServerConfig[] Servers { get; set; } = [new()];
+}
+
+public class SimApiJobServerConfig()
+{
+ public string[] Queues { get; set; } = ["default"];
+ public int WorkerNum { get; set; } = 50;
+}
\ No newline at end of file
diff --git a/Configurations/SimApiOptions.cs b/Configurations/SimApiOptions.cs
index 3609ce1..fe23e9e 100644
--- a/Configurations/SimApiOptions.cs
+++ b/Configurations/SimApiOptions.cs
@@ -5,6 +5,13 @@ namespace SimApi.Configurations;
public class SimApiOptions
{
+ public string? RedisConfiguration { get; set; }
+
+ ///
+ /// 是否启用后台任务系统 *基于Hangfire
+ ///
+ public bool EnableJob { get; set; } = false;
+
///
/// 启用全部Cors,对于开发前后分离的时候很有用。
/// default: true
@@ -36,7 +43,7 @@ public class SimApiOptions
/// default: true
///
public bool EnableSimApiException { get; set; } = true;
-
+
///
/// 启用返回结果拦截
///
@@ -71,6 +78,10 @@ public class SimApiOptions
///
public bool EnableSynapse { get; set; }
+ ///
+ /// 配置Job
+ ///
+ public SimApiJobOptions SimApiJobOptions { get; set; } = new();
///
/// Swagger文档相关配置,需要启用 EnableSimApiDoc
@@ -103,4 +114,9 @@ public class SimApiOptions
{
options?.Invoke(SimApiStorageOptions);
}
+
+ public void ConfigureSimApiJob(Action? options = null)
+ {
+ options?.Invoke(SimApiJobOptions);
+ }
}
\ No newline at end of file
diff --git a/Helpers/SimApiCache.cs b/Helpers/SimApiCache.cs
new file mode 100644
index 0000000..4d4926f
--- /dev/null
+++ b/Helpers/SimApiCache.cs
@@ -0,0 +1,32 @@
+using System.Text.Json;
+using Microsoft.Extensions.Caching.Distributed;
+
+namespace SimApi.Helpers;
+
+public class SimApiCache(IDistributedCache cache)
+{
+ private const string Prefix = "SimApi:Cache";
+
+ public void Set(string key, object value, DistributedCacheEntryOptions? options = null)
+ {
+ if (options is not null)
+ {
+ cache.SetString(Prefix + key, SimApiUtil.Json(value), options);
+ }
+ else
+ {
+ cache.SetString(Prefix + key, SimApiUtil.Json(value));
+ }
+ }
+
+ public string? Get(string key)
+ {
+ return cache.GetString(Prefix + key);
+ }
+
+ public T? Get(string key)
+ {
+ var data = cache.GetString(Prefix + key);
+ return data == null ? default : JsonSerializer.Deserialize(data);
+ }
+}
\ No newline at end of file
diff --git a/Helpers/SimApiJobWebAuth.cs b/Helpers/SimApiJobWebAuth.cs
new file mode 100644
index 0000000..4b70d6c
--- /dev/null
+++ b/Helpers/SimApiJobWebAuth.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Linq;
+using System.Text;
+using Hangfire.Dashboard;
+using Microsoft.AspNetCore.Http;
+
+namespace SimApi.Helpers;
+
+public class SimApiJobWebAuth(string user, string pass) : IDashboardAuthorizationFilter
+{
+ public bool Authorize(DashboardContext context)
+ {
+ var httpContext = context.GetHttpContext();
+ var authHeader = httpContext.Request.Headers["Authorization"].FirstOrDefault();
+ if (authHeader != null && authHeader.StartsWith("Basic "))
+ {
+ var encodedUsernamePassword = authHeader.Split(' ', 2, StringSplitOptions.RemoveEmptyEntries)[1]?.Trim();
+ var decodedUsernamePassword = Encoding.UTF8.GetString(Convert.FromBase64String(encodedUsernamePassword));
+ var username = decodedUsernamePassword.Split(':', 2)[0];
+ var password = decodedUsernamePassword.Split(':', 2)[1];
+
+ if (username == user && password == pass)
+ {
+ return true;
+ }
+ }
+
+ httpContext.Response.StatusCode = 401;
+ httpContext.Response.Headers.WWWAuthenticate = "Basic realm=\"SimApiBasicAuth\"";
+ httpContext.Response.WriteAsync("").Wait();
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/SimApi.csproj b/SimApi.csproj
index 8e004ac..ca15b53 100644
--- a/SimApi.csproj
+++ b/SimApi.csproj
@@ -23,10 +23,14 @@
-
-
-
-
+
+
+
+
+
+
+
+
diff --git a/SimApiExtensions.cs b/SimApiExtensions.cs
index 8bbc8db..2c0b8fa 100644
--- a/SimApiExtensions.cs
+++ b/SimApiExtensions.cs
@@ -3,6 +3,9 @@ using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text.Json;
+using Hangfire;
+using Hangfire.Console;
+using Hangfire.Redis.StackExchange;
using SimApi.Helpers;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
@@ -29,6 +32,12 @@ public static class SimApiExtensions
{
var simApiOptions = new SimApiOptions();
options?.Invoke(simApiOptions);
+ if (simApiOptions.RedisConfiguration != null)
+ {
+ builder.AddStackExchangeRedisCache(x => x.Configuration = simApiOptions.RedisConfiguration);
+ builder.AddSingleton();
+ }
+
if (simApiOptions.EnableLogger)
{
builder.AddLogging(logger =>
@@ -49,6 +58,30 @@ public static class SimApiExtensions
builder.AddSingleton();
}
+ if (simApiOptions.EnableJob)
+ {
+ builder.AddHangfire(x =>
+ {
+ var redisOption = new RedisStorageOptions();
+ if (simApiOptions.SimApiJobOptions.Database.HasValue)
+ {
+ redisOption.Db = simApiOptions.SimApiJobOptions.Database.Value;
+ }
+
+ x.UseRedisStorage(simApiOptions.SimApiJobOptions.RedisConfiguration ??
+ simApiOptions.RedisConfiguration, redisOption);
+ x.UseConsole();
+ });
+ foreach (var server in simApiOptions.SimApiJobOptions.Servers)
+ {
+ builder.AddHangfireServer(hfs =>
+ {
+ hfs.Queues = server.Queues;
+ hfs.WorkerCount = server.WorkerNum;
+ });
+ }
+ }
+
if (simApiOptions.EnableCors)
{
builder.AddCors(cors => cors.AddPolicy("any",
@@ -228,6 +261,11 @@ public static class SimApiExtensions
logger.LogInformation("当前时区: {LocalId}", TimeZoneInfo.Local.Id);
+ if (options.RedisConfiguration != null)
+ {
+ logger.LogInformation("开始配置 RedisCache ...");
+ }
+
//请求一下检测存储错误
if (options.EnableSimApiStorage)
{
@@ -235,11 +273,6 @@ public static class SimApiExtensions
builder.Services.GetService();
}
- if (options.EnableSimApiResponseFilter)
- {
- logger.LogInformation("开始配置SimApiResponseFilter...");
- }
-
if (options.EnableCoceSdk)
{
logger.LogInformation("开始配置CoceAppSdk...\nApi入口: {ApiUrl}\nAuth入口:{AuthUrl}n\nAppId: {AppId}",
@@ -247,17 +280,17 @@ public static class SimApiExtensions
options.CoceSdkOptions.AppId);
}
- if (options.EnableLowerUrl)
- {
- logger.LogInformation("开始配置使用URL小写...");
- }
-
if (options.EnableSynapse)
{
var synapse = builder.Services.GetRequiredService();
synapse.Init();
}
+ if (options.EnableJob)
+ {
+ logger.LogInformation("开始配置 SimApiJob ...");
+ }
+
return builder;
}
@@ -269,10 +302,8 @@ public static class SimApiExtensions
public static WebApplication UseSimApi(this WebApplication builder)
{
var options = builder.Services.GetRequiredService();
-
var logger = builder.Services.GetRequiredService>();
-
- logger.LogInformation("当前时区: {LocalId}", TimeZoneInfo.Local.Id);
+ UseSimApi((IHost)builder);
if (options.EnableForwardHeaders)
{
logger.LogInformation("开始配置ForwardedHeaders...");
@@ -285,6 +316,11 @@ public static class SimApiExtensions
builder.UseCors("any");
}
+ if (options.EnableSimApiResponseFilter)
+ {
+ logger.LogInformation("开始配置SimApiResponseFilter...");
+ }
+
if (options.EnableSimApiAuth)
{
logger.LogInformation("开始配置SimApiAuth...");
@@ -333,22 +369,22 @@ public static class SimApiExtensions
builder.UseMiddleware();
}
- //请求一下检测存储错误
- if (options.EnableSimApiStorage)
- {
- logger.LogInformation("开始配置SimApiStorage...");
- builder.Services.GetService();
- }
-
if (options.EnableLowerUrl)
{
logger.LogInformation("开始配置使用URL小写...");
}
- if (options.EnableSynapse)
+ if (options is { EnableJob: true, SimApiJobOptions.DashboardUrl: not null })
{
- var synapse = builder.Services.GetRequiredService();
- synapse.Init();
+ logger.LogInformation("开始配置 SimApiJob Web控制台...");
+ builder.UseHangfireDashboard(options.SimApiJobOptions.DashboardUrl, new DashboardOptions
+ {
+ Authorization =
+ [
+ new SimApiJobWebAuth(options.SimApiJobOptions.DashboardAuthUser,
+ options.SimApiJobOptions.DashboardAuthPass)
+ ]
+ });
}
return builder;
diff --git a/Synapse/Synapse.cs b/Synapse/Synapse.cs
index 6073dc5..9211ee1 100644
--- a/Synapse/Synapse.cs
+++ b/Synapse/Synapse.cs
@@ -8,7 +8,6 @@ using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using MQTTnet;
-using MQTTnet.Client;
using MQTTnet.Formatter;
using SimApi.Attributes;
using SimApi.Communications;
@@ -22,7 +21,7 @@ public partial class Synapse(SimApiOptions simApiOptions, ILogger logge
{
private SimApiSynapseOptions Options { get; } = simApiOptions.SimApiSynapseOptions;
- private MqttFactory MqttFactory { get; } = new();
+ private MqttClientFactory MqttFactory { get; } = new();
public IMqttClient? Client { get; set; }
private List EventRegistry { get; set; } = new();