新增配置文件热重载功能
This commit is contained in:
parent
8a38a79af0
commit
5090f7e7a6
12 changed files with 190 additions and 40 deletions
|
@ -1,5 +1,6 @@
|
|||
using iFileProxy.Services;
|
||||
using Serilog;
|
||||
using System.Security.Policy;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
|
@ -38,7 +39,9 @@ namespace iFileProxy.Config
|
|||
{
|
||||
try
|
||||
{
|
||||
return JsonSerializer.Deserialize<AppConfig>(File.ReadAllText(configPath),options);
|
||||
using FileStream fs = new FileStream(configPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
using StreamReader sr = new StreamReader(fs, System.Text.Encoding.Default);
|
||||
return JsonSerializer.Deserialize<AppConfig>(sr.ReadToEnd(),options);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
|
|
@ -5,17 +5,36 @@ using Serilog;
|
|||
using System.Web;
|
||||
using System.Net;
|
||||
using iFileProxy.Helpers;
|
||||
using iFileProxy.Services; // 用于 URL 解码
|
||||
using iFileProxy.Services;
|
||||
|
||||
namespace iFileProxy.Controllers
|
||||
{
|
||||
[Route("/")]
|
||||
public class StreamProxyController(IHttpClientFactory httpClientFactory, AppConfig appConfig, DatabaseGateService dbGateService) : ControllerBase
|
||||
public class StreamProxyController : ControllerBase
|
||||
{
|
||||
private readonly IHttpClientFactory _httpClientFactory = httpClientFactory;
|
||||
private long SizeLimit = appConfig.StreamProxyOptions.SizeLimit;
|
||||
private AppConfig _appConfig ;
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly DatabaseGateService _dbGateService;
|
||||
private long SizeLimit;
|
||||
private readonly static Serilog.ILogger _logger = Log.Logger.ForContext<StreamProxyController>();
|
||||
|
||||
public StreamProxyController(IHttpClientFactory httpClientFactory, AppConfigService appConfigService, DatabaseGateService dbGateService)
|
||||
{
|
||||
_appConfig = appConfigService.AppConfig;
|
||||
_httpClientFactory = httpClientFactory;
|
||||
SizeLimit = _appConfig.StreamProxyOptions.SizeLimit;
|
||||
_dbGateService = dbGateService;
|
||||
|
||||
appConfigService.AppConfigurationChanged += AppConfigService_AppConfigurationChanged;
|
||||
}
|
||||
|
||||
private void AppConfigService_AppConfigurationChanged(object sender, AppConfig appConfig)
|
||||
{
|
||||
_appConfig = appConfig;
|
||||
SizeLimit = _appConfig.StreamProxyOptions.SizeLimit;
|
||||
}
|
||||
|
||||
|
||||
// 匹配文件代理请求
|
||||
[HttpGet("{*proxyUrl}")]
|
||||
[HttpPost("{*proxyUrl}")]
|
||||
|
@ -34,7 +53,7 @@ namespace iFileProxy.Controllers
|
|||
else
|
||||
if (!proxyUrl.StartsWith("http://") && !proxyUrl.StartsWith("https://")) // 不带协议头 直接扔进垃圾桶
|
||||
return StatusCode(400, "非法Url! 除GitHubUrl外其他代理请求必须携带协议头!");
|
||||
foreach (var keywords in appConfig.SecurityOptions.BlockedKeyword)
|
||||
foreach (var keywords in _appConfig.SecurityOptions.BlockedKeyword)
|
||||
{
|
||||
if (proxyUrl.Contains(keywords, StringComparison.CurrentCulture))
|
||||
return StatusCode((int)HttpStatusCode.Forbidden, "Keyword::Forbidden");
|
||||
|
@ -158,7 +177,7 @@ namespace iFileProxy.Controllers
|
|||
if (contentLength > 0)
|
||||
Response.ContentLength = contentLength;
|
||||
|
||||
await dbGateService.AddTaskInfoDataAsync(new TaskInfo
|
||||
await _dbGateService.AddTaskInfoDataAsync(new TaskInfo
|
||||
{
|
||||
FileName = fileName,
|
||||
Size = contentLength ?? -1L,
|
||||
|
|
|
@ -11,17 +11,24 @@ namespace iFileProxy.Controllers
|
|||
public class iProxyController : ControllerBase
|
||||
{
|
||||
private readonly TaskManager _taskManager;
|
||||
private readonly AppConfig _appConfig;
|
||||
private AppConfig _appConfig;
|
||||
private readonly DatabaseGateService _dbGate;
|
||||
|
||||
private readonly static Serilog.ILogger _logger = Log.Logger.ForContext<iProxyController>();
|
||||
|
||||
|
||||
public iProxyController(TaskManager taskManager, AppConfig appConfig, DatabaseGateService databaseGateService)
|
||||
public iProxyController(TaskManager taskManager, AppConfigService appConfigService, DatabaseGateService databaseGateService)
|
||||
{
|
||||
_taskManager = taskManager;
|
||||
_appConfig = appConfig;
|
||||
_appConfig = appConfigService.AppConfig;
|
||||
_dbGate = databaseGateService;
|
||||
|
||||
appConfigService.AppConfigurationChanged += AppConfigService_AppConfigurationChanged;
|
||||
}
|
||||
|
||||
private void AppConfigService_AppConfigurationChanged(object sender, AppConfig appConfig)
|
||||
{
|
||||
_appConfig = appConfig;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
|
|
|
@ -5,7 +5,7 @@ namespace iFileProxy.Handlers
|
|||
{
|
||||
public class CmdArgsHandler
|
||||
{
|
||||
public static void ParseArgs(string[] args)
|
||||
public static void ParseArgs(string[] args, IServiceProvider serviceProvider)
|
||||
{
|
||||
if (args.Length == 0)
|
||||
return;
|
||||
|
@ -29,8 +29,7 @@ namespace iFileProxy.Handlers
|
|||
|
||||
if (cmdlineHelper.GetBooleanValue("--init-db"))
|
||||
{
|
||||
DatabaseGateService databaseGateService = new(AppConfig.GetCurrConfig());
|
||||
databaseGateService.TryInitialDB();
|
||||
serviceProvider.GetRequiredService<DatabaseGateService>().TryInitialDB();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ namespace iFileProxy.Middlewares
|
|||
{
|
||||
private readonly RequestDelegate _next = next;
|
||||
|
||||
private readonly static ILogger _logger = Log.Logger.ForContext<ErrorHandlerMiddleware>();
|
||||
private readonly static Serilog.ILogger _logger = Log.Logger.ForContext<ErrorHandlerMiddleware>();
|
||||
|
||||
public async Task InvokeAsync(HttpContext context)
|
||||
{
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
using iFileProxy.Config;
|
||||
using iFileProxy.Helpers;
|
||||
using iFileProxy.Models;
|
||||
using iFileProxy.Services;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace iFileProxy.Middlewares
|
||||
{
|
||||
public class IPAccessLimitMiddleware(RequestDelegate next, Dictionary<string, Dictionary<string, uint>> IPAccessCountDict, AppConfig appConfig)
|
||||
public class IPAccessLimitMiddleware(RequestDelegate next, Dictionary<string, Dictionary<string, uint>> IPAccessCountDict, AppConfigService appConfigService)
|
||||
{
|
||||
private readonly RequestDelegate _next = next;
|
||||
private readonly Dictionary<string, Dictionary<string, uint>> _IPAccessCountDict = IPAccessCountDict;
|
||||
|
@ -15,7 +16,7 @@ namespace iFileProxy.Middlewares
|
|||
string ipStr = MasterHelper.GetClientIPAddr(context);
|
||||
|
||||
// 处理配置文件拉黑IP
|
||||
if (appConfig.SecurityOptions.BlockedClientIP.IndexOf(ipStr) != -1)
|
||||
if (appConfigService.AppConfig.SecurityOptions.BlockedClientIP.IndexOf(ipStr) != -1)
|
||||
{
|
||||
context.Response.StatusCode = 403;
|
||||
await context.Response.WriteAsJsonAsync(new CommonRsp { Retcode = 403, Message = "你的IP地址已经被管理员拉入黑名单!" });
|
||||
|
@ -23,7 +24,7 @@ namespace iFileProxy.Middlewares
|
|||
}
|
||||
|
||||
// 获取需要跟踪的路由列表
|
||||
var routesToTrack = appConfig.SecurityOptions.RoutesToTrack;
|
||||
var routesToTrack = appConfigService.AppConfig.SecurityOptions.RoutesToTrack;
|
||||
|
||||
// 如果没有匹配到需要跟踪的路由,直接调用下一个中间件
|
||||
if (!routesToTrack.Any(p => context.Request.Path.ToString().StartsWith(p, StringComparison.OrdinalIgnoreCase)))
|
||||
|
@ -42,7 +43,7 @@ namespace iFileProxy.Middlewares
|
|||
|
||||
if (ipCounts.TryGetValue(ipStr, out uint value))
|
||||
{
|
||||
if (ipCounts[ipStr] >= appConfig.SecurityOptions.DailyRequestLimitPerIP)
|
||||
if (ipCounts[ipStr] >= appConfigService.AppConfig.SecurityOptions.DailyRequestLimitPerIP)
|
||||
{
|
||||
context.Response.StatusCode = 200;
|
||||
context.Response.ContentType = "application/json";
|
||||
|
|
|
@ -19,12 +19,12 @@ namespace iFileProxy
|
|||
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
|
||||
SerilogConfig.CreateLogger(args: args);
|
||||
|
||||
CommandLineArgsHelper argsHelper = new (args);
|
||||
Serilog.ILogger logger = Log.Logger.ForContext<Program>();
|
||||
|
||||
CommandLineArgsHelper argsHelper = new(args);
|
||||
|
||||
Console.Write(" "); // 补全日志第一行开头的空白
|
||||
|
||||
CmdArgsHandler.ParseArgs(args);
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// CORS配置
|
||||
|
@ -46,9 +46,11 @@ namespace iFileProxy
|
|||
builder.Services.AddEndpointsApiExplorer();
|
||||
builder.Services.AddSwaggerGen();
|
||||
|
||||
|
||||
builder.Host.UseSerilog(logger: Log.Logger);
|
||||
|
||||
// 注入依赖
|
||||
builder.Services.AddSingleton<AppConfigService>();
|
||||
builder.Services.AddSingleton(AppConfig.GetCurrConfig());
|
||||
builder.Services.AddSingleton<DatabaseGateService>();
|
||||
builder.Services.AddSingleton<TaskManager>();
|
||||
|
@ -79,6 +81,13 @@ namespace iFileProxy
|
|||
// 初始化缓存管理服务
|
||||
LocalCacheManager localCacheManager = new(app.Services);
|
||||
|
||||
// 解析命令行参数
|
||||
CmdArgsHandler.ParseArgs(args, app.Services);
|
||||
|
||||
// 开启配置热重载
|
||||
app.Services.GetRequiredService<AppConfigService>().EnableHotReload();
|
||||
|
||||
|
||||
if (!argsHelper.GetBooleanValue("disable-startup-check"))
|
||||
// 初始化验证配置文件
|
||||
AppConfig.CheckAppConfig(app.Services);
|
||||
|
@ -99,6 +108,7 @@ namespace iFileProxy
|
|||
// 4. 强制使用HTTPS(要在Routing之前)
|
||||
app.UseHttpsRedirection();
|
||||
|
||||
|
||||
// 5. 请求日志记录(在Routing之前)
|
||||
app.UseSerilogRequestLogging(options =>
|
||||
{
|
||||
|
@ -110,6 +120,7 @@ namespace iFileProxy
|
|||
};
|
||||
});
|
||||
|
||||
|
||||
// 6. 路由配置
|
||||
app.UseRouting();
|
||||
|
||||
|
@ -128,7 +139,7 @@ namespace iFileProxy
|
|||
|
||||
// 11. 启动应用
|
||||
var dbGateService = app.Services.GetRequiredService<DatabaseGateService>();
|
||||
SerilogConfig.CreateLogger(dbGateService,args);
|
||||
SerilogConfig.CreateLogger(dbGateService, args);
|
||||
|
||||
if (!argsHelper.GetStringValue("url").IsNullOrEmpty())
|
||||
app.Run(argsHelper.GetStringValue("url"));
|
||||
|
@ -147,7 +158,7 @@ namespace iFileProxy
|
|||
{
|
||||
if (mySqlEx.Message.Contains("is not allowed to connect to this MySQL server", StringComparison.CurrentCulture))
|
||||
{
|
||||
logger.Fatal( "远程主机不允许你连接到该数据库, 请检查数据库服务端是否将本机IP加白! ");
|
||||
logger.Fatal("远程主机不允许你连接到该数据库, 请检查数据库服务端是否将本机IP加白! ");
|
||||
}
|
||||
else if (mySqlEx.Message.Contains("Unable to connect to any of the specified MySQL hosts"))
|
||||
{
|
||||
|
|
85
src/Services/AppConfigService.cs
Normal file
85
src/Services/AppConfigService.cs
Normal file
|
@ -0,0 +1,85 @@
|
|||
using iFileProxy.Config;
|
||||
using iFileProxy.Helpers;
|
||||
using Newtonsoft.Json;
|
||||
using Serilog;
|
||||
using System.Configuration;
|
||||
namespace iFileProxy.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// 应用程序配置服务,用于获取当前配置、配置热重载等
|
||||
/// </summary>
|
||||
public class AppConfigService
|
||||
{
|
||||
public delegate void AppConfigurationChangeEventHandler(object sender, AppConfig appConfig);
|
||||
/// <summary>
|
||||
/// 当程序配置文件发生变化时触发此事件
|
||||
/// </summary>
|
||||
public event AppConfigurationChangeEventHandler? AppConfigurationChanged;
|
||||
|
||||
private readonly Serilog.ILogger _logger = Log.Logger.ForContext<AppConfigService>();
|
||||
|
||||
public AppConfig AppConfig { get; private set; } = new();
|
||||
readonly string _appConfigPath;
|
||||
FileSystemWatcher _watcher = new();
|
||||
|
||||
DateTimeOffset _last_change_time = DateTimeOffset.Now;
|
||||
|
||||
|
||||
|
||||
public AppConfigService(string configFilePath = "iFileProxy.json")
|
||||
{
|
||||
_appConfigPath = configFilePath;
|
||||
LoadAppConfig();
|
||||
}
|
||||
|
||||
|
||||
public void LoadAppConfig()
|
||||
{
|
||||
if (File.Exists(_appConfigPath))
|
||||
{
|
||||
try
|
||||
{
|
||||
AppConfig = AppConfig.GetCurrConfig(_appConfigPath);
|
||||
_logger.Information("Configuration loaded successfully.");
|
||||
AppConfigurationChanged?.Invoke(this, AppConfig);
|
||||
_logger.Debug("app configuration change event invoke succ.");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Error(e, "配置文件解析失败!!!");
|
||||
throw;
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Error($"配置文件: {_appConfigPath} 目标不存在!");
|
||||
}
|
||||
}
|
||||
|
||||
public void EnableHotReload()
|
||||
{
|
||||
_logger.Information($"App Configuration File Hotreload has enabled.");
|
||||
_watcher = new()
|
||||
{
|
||||
Filter = Path.GetFileName(_appConfigPath),
|
||||
Path = Path.GetDirectoryName(_appConfigPath) != "" && Path.GetDirectoryName(_appConfigPath) !=null ? Path.GetDirectoryName(_appConfigPath) : AppDomain.CurrentDomain.BaseDirectory,
|
||||
// IncludeSubdirectories = true
|
||||
};
|
||||
_logger.Information($"Filter: {_watcher.Filter} Path: {_watcher.Path}");
|
||||
|
||||
_watcher.Changed += (s, e) =>
|
||||
{
|
||||
// 防抖处理 防止多次触发change事件
|
||||
if (DateTimeOffset.Now.ToUnixTimeSeconds() - _last_change_time.ToUnixTimeSeconds() > 1)
|
||||
{
|
||||
_last_change_time = DateTimeOffset.Now;
|
||||
_logger.Information($"Configuration file has changed, program configuration is being reloaded...");
|
||||
LoadAppConfig();
|
||||
}
|
||||
};
|
||||
|
||||
_watcher.EnableRaisingEvents = true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -26,20 +26,22 @@ namespace iFileProxy.Services
|
|||
{"下载历史",@"CREATE TABLE `t_download_history` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增主键', `tid` varchar(48) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '任务UUID', `time` datetime NULL DEFAULT NULL COMMENT '触发时间', `client_ip` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '客户端IP', PRIMARY KEY (`id`) USING BTREE, INDEX `tid`(`tid`) USING BTREE, CONSTRAINT `t_download_history_ibfk_1` FOREIGN KEY (`tid`) REFERENCES `t_tasks_info` (`tid`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE = InnoDB AUTO_INCREMENT = 532 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;" }
|
||||
};
|
||||
|
||||
public DatabaseGateService(AppConfig appConfig)
|
||||
public DatabaseGateService(AppConfigService appConfigService)
|
||||
{
|
||||
_logger.Information("Initializing DatabaseGateService...");
|
||||
_db = appConfig.Database;
|
||||
_appConfig = appConfigService.AppConfig;
|
||||
_db = _appConfig.Database;
|
||||
appConfigService.AppConfigurationChanged += AppConfigService_AppConfigurationChanged;
|
||||
|
||||
LoadDbDict();
|
||||
_logger.Information("Done.");
|
||||
|
||||
}
|
||||
|
||||
private void AppConfigService_AppConfigurationChanged(object sender, AppConfig appConfig)
|
||||
{
|
||||
_appConfig = appConfig;
|
||||
try
|
||||
{
|
||||
LoadDbDict();
|
||||
_logger.Information("Done.");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Fatal($"程序异常: {e.Message}");
|
||||
}
|
||||
_db = _appConfig.Database;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -414,7 +416,7 @@ namespace iFileProxy.Services
|
|||
foreach (var sql in createDBSqls)
|
||||
{
|
||||
_logger.Information($"尝试创建 {sql.Key} 数据表....");
|
||||
using MySqlCommand sqlcmd = new (sql.Value,conn);
|
||||
using MySqlCommand sqlcmd = new(sql.Value, conn);
|
||||
sqlcmd.ExecuteNonQuery();
|
||||
_logger.Information("Done.");
|
||||
}
|
||||
|
|
|
@ -18,10 +18,10 @@ namespace iFileProxy.Services
|
|||
private readonly AppConfig _appConfig = AppConfig.GetCurrConfig();
|
||||
private readonly static Serilog.ILogger _logger = Log.Logger.ForContext<LocalCacheManager>();
|
||||
|
||||
private readonly object _lock = new object();
|
||||
private readonly object _lock = new ();
|
||||
private readonly Timer _timer;
|
||||
private readonly DatabaseGateService _dbGateService;
|
||||
private readonly int CACHE_LIFETIME;
|
||||
private int CACHE_LIFETIME;
|
||||
|
||||
/// <summary>
|
||||
/// 缓存管理器
|
||||
|
@ -31,6 +31,9 @@ namespace iFileProxy.Services
|
|||
_logger.Information("Initializing LocalCacheManager.");
|
||||
CACHE_LIFETIME = _appConfig.DownloadOptions.CacheLifetime;
|
||||
_dbGateService = serviceProvider.GetRequiredService<DatabaseGateService>();
|
||||
|
||||
serviceProvider.GetRequiredService<AppConfigService>().AppConfigurationChanged += LocalCacheManager_AppConfigurationChange;
|
||||
|
||||
// 开始定时清理任务
|
||||
_timer = new Timer((obj) =>
|
||||
{
|
||||
|
@ -42,6 +45,11 @@ namespace iFileProxy.Services
|
|||
_logger.Information("succ.");
|
||||
}
|
||||
|
||||
private void LocalCacheManager_AppConfigurationChange(object sender, AppConfig appConfig)
|
||||
{
|
||||
CACHE_LIFETIME = appConfig.DownloadOptions.CacheLifetime;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查并且清理缓存数据
|
||||
/// </summary>
|
||||
|
|
|
@ -61,17 +61,19 @@ namespace iFileProxy.Services
|
|||
|
||||
|
||||
private readonly static Serilog.ILogger _logger = Log.Logger.ForContext<TaskManager>();
|
||||
private readonly AppConfig? _appConfig = AppConfig.GetCurrConfig("iFileProxy.json");
|
||||
private AppConfig _appConfig = AppConfig.GetCurrConfig("iFileProxy.json");
|
||||
private readonly DatabaseGateService _dbGateService;
|
||||
private Dictionary<string, TaskInfo> _runningTasks = [];
|
||||
private Queue<TaskInfo> _pendingTasks = new();
|
||||
private readonly object _taskLock = new();
|
||||
|
||||
public TaskManager(IServiceProvider serviceProvider)
|
||||
public TaskManager(DatabaseGateService dbGateService, AppConfigService appConfigService)
|
||||
{
|
||||
_logger.Information("Initializing TaskManager...");
|
||||
if (_appConfig != null)
|
||||
_dbGateService = serviceProvider.GetRequiredService<DatabaseGateService>();
|
||||
{
|
||||
_dbGateService = dbGateService;
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Fatal($"Failed to load application configuration");
|
||||
|
@ -82,8 +84,20 @@ namespace iFileProxy.Services
|
|||
TaskCompleted -= HandleTaskCompleted;
|
||||
TaskCompleted += HandleTaskCompleted;
|
||||
|
||||
// 订阅程序配置更改事件
|
||||
appConfigService.AppConfigurationChanged += AppConfigService_AppConfigurationChanged;
|
||||
|
||||
|
||||
_logger.Information("TaskManager init succ.");
|
||||
}
|
||||
|
||||
private void AppConfigService_AppConfigurationChanged(object sender, AppConfig appConfig)
|
||||
{
|
||||
_appConfig = appConfig;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 添加一个新的下载任务
|
||||
/// </summary>
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
Loading…
Reference in a new issue