添加命令行参数支持和未处理异常提示

This commit is contained in:
root 2024-12-13 17:56:57 +08:00
parent db1b7bbb7d
commit 510c70cb83
10 changed files with 281 additions and 168 deletions

View file

@ -69,7 +69,10 @@ namespace iFileProxy.Config
} }
var databaseHelper = serviceProvider.GetRequiredService<DatabaseGateService>(); var databaseHelper = serviceProvider.GetRequiredService<DatabaseGateService>();
databaseHelper.TestDbConfig(); if (databaseHelper.TestDbConfig())
{
_logger.Information("数据库配置验证成功.");
}
} }
else else
{ {
@ -120,40 +123,40 @@ namespace iFileProxy.Config
public partial class Common public partial class Common
{ {
[JsonPropertyName("Host")] [JsonPropertyName("Host")]
public string Host { get; set; } public string Host { get; set; } = string.Empty;
[JsonPropertyName("Port")] [JsonPropertyName("Port")]
public int Port { get; set; } = 3306; public uint Port { get; set; } = 3306;
[JsonPropertyName("User")] [JsonPropertyName("User")]
public string User { get; set; } public string User { get; set; } = string.Empty;
[JsonPropertyName("Password")] [JsonPropertyName("Password")]
public string Password { get; set; } public string Password { get; set; } = string.Empty;
} }
public partial class DB public partial class DB
{ {
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[JsonPropertyName("Host")] [JsonPropertyName("Host")]
public string Host { get; set; } public string? Host { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[JsonPropertyName("Port")] [JsonPropertyName("Port")]
public long? Port { get; set; } = 3306; public uint? Port { get; set; } = null;
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[JsonPropertyName("User")] [JsonPropertyName("User")]
public string User { get; set; } public string? User { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[JsonPropertyName("Password")] [JsonPropertyName("Password")]
public string Password { get; set; } public string? Password { get; set; }
[JsonPropertyName("DatabaseName")] [JsonPropertyName("DatabaseName")]
public string DatabaseName { get; set; } public string? DatabaseName { get; set; }
[JsonPropertyName("Description")] [JsonPropertyName("Description")]
public string Description { get; set; } public string? Description { get; set; }
} }
public class StreamProxyOptions public class StreamProxyOptions

View file

@ -0,0 +1,37 @@
using iFileProxy.Config;
using iFileProxy.Helpers;
using iFileProxy.Services;
namespace iFileProxy.Handlers
{
public class CmdArgsHandler
{
public static void ParseArgs(string[] args)
{
if (args.Length == 0)
return;
Dictionary<string, string> helpStrings = [];
helpStrings.Add("--init-db", "初始化数据库");
helpStrings.Add("--dev-logger", "启用开发者logger");
helpStrings.Add("--disable-startup-check", "禁用启动时程序配置自检");
helpStrings.Add("--url=<Url>", "设置程序app.run() Url 如: http://0.0.0.0:1145");
if (args.Contains("-h") || args.Contains("--help"))
{
Console.WriteLine("命令行参数帮助: ");
foreach (var hs in helpStrings)
{
Console.WriteLine($"\t{hs.Key}\t{hs.Value}");
}
Environment.Exit(0);
}
CommandLineArgsHelper cmdlineHelper = new(args);
if (cmdlineHelper.GetBooleanValue("--init-db"))
{
DatabaseGateService databaseGateService = new(AppConfig.GetCurrConfig());
databaseGateService.TryInitialDB();
}
}
}
}

View file

@ -0,0 +1,51 @@
using Newtonsoft.Json;
using Serilog;
namespace iFileProxy.Helpers
{
public class CommandLineArgsHelper(string[] args)
{
private readonly Dictionary<string, string> _parameters = ParseArgs(args);
// 解析命令行参数
private static Dictionary<string, string> ParseArgs(string[] args)
{
var parameters = new Dictionary<string, string>();
for (int i = 0; i < args.Length; i++)
{
var arg = args[i];
// 如果参数是键值对形式
if (arg.Contains('='))
{
var parts = arg.Split(['='], 2);
if (parts.Length == 2)
{
parameters[parts[0].TrimStart('-')] = parts[1];
}
}
else
{
// 如果参数是没有值的标志性参数(比如 --dev-logger
parameters[arg.TrimStart('-')] = "true";
}
}
return parameters;
}
// 获取指定参数的值若无值则返回null
public string? GetStringValue(string key)
{
return _parameters.ContainsKey(key) ? _parameters[key] : null;
}
// 获取指定参数的布尔值,若参数值是 "true" 则返回true否则返回false
public bool GetBooleanValue(string key)
{
return _parameters.ContainsKey(key) && _parameters[key].Equals("true", StringComparison.CurrentCultureIgnoreCase);
}
}
}

View file

@ -24,17 +24,13 @@ namespace iFileProxy.Middleware
await _next(context); await _next(context);
if (context.Response.HasStarted) if (context.Response.HasStarted)
{ {
_logger.Debug($"响应已经开始 错误处理中间件无法再修改其响应内容"); _logger.Debug($"响应已经开始,无法再修改其响应内容");
return; return;
} }
if (context.Response.StatusCode == 404) if (context.Response.StatusCode == 404)
{ {
context.Response.OnStarting(async () => context.Response.ContentType = "application/json";
{ await context.Response.WriteAsync(JsonSerializer.Serialize(new CommonRsp { Retcode = 404, Message = "你正在请求的资源不存在!" }));
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(JsonSerializer.Serialize(new CommonRsp { Retcode = 404, Message = "你正在请求的资源不存在!" }));
}
);
} }
} }
catch (Exception ex) catch (Exception ex)
@ -46,7 +42,7 @@ namespace iFileProxy.Middleware
private static Task HandleExceptionAsync(HttpContext context, Exception exception) private static Task HandleExceptionAsync(HttpContext context, Exception exception)
{ {
var code = HttpStatusCode.InternalServerError; // 500 if unexpected var code = HttpStatusCode.InternalServerError; // 500 if unexpected
_logger.Fatal("崩溃数据: {exception}\n上下文信息: {context}", exception, JsonSerializer.Serialize(MasterHelper.ExtractDebugInfo(context))); _logger.Error("崩溃数据: {exception}\n上下文信息: {context}", exception, JsonSerializer.Serialize(MasterHelper.ExtractDebugInfo(context)));
switch (exception) switch (exception)
{ {

View file

@ -14,9 +14,9 @@ namespace iFileProxy.Middleware
public async Task InvokeAsync(HttpContext context) public async Task InvokeAsync(HttpContext context)
{ {
var token = context.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last(); var token = context.Request.Headers.Authorization.FirstOrDefault()?.Split(" ").Last();
var fingerprint = context.Request.Headers["X-Device-Fingerprint"].FirstOrDefault(); var fingerprint = context.Request.Headers["X-Device-Fingerprint"].FirstOrDefault();
var userAgent = context.Request.Headers["User-Agent"].FirstOrDefault(); var userAgent = context.Request.Headers.UserAgent.FirstOrDefault();
var ip = MasterHelper.GetClientIPAddr(context); var ip = MasterHelper.GetClientIPAddr(context);
if (token != null) if (token != null)

View file

@ -1,12 +1,13 @@
using iFileProxy.Config; using iFileProxy.Config;
using iFileProxy.Middleware; using iFileProxy.Middleware;
using iFileProxy.Helpers; using iFileProxy.Helpers;
using iFileProxy.Handlers;
using iFileProxy.Services; using iFileProxy.Services;
using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens; using Microsoft.IdentityModel.Tokens;
using Serilog; using Serilog;
using System.Text; using System.Text;
using System.Diagnostics; using MySql.Data.MySqlClient;
namespace iFileProxy namespace iFileProxy
{ {
@ -14,11 +15,16 @@ namespace iFileProxy
{ {
public static void Main(string[] args) public static void Main(string[] args)
{ {
SerilogConfig.CreateLogger(); // 绑定异常捕获事件
Serilog.ILogger logger = Log.Logger.ForContext<Program>(); AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
SerilogConfig.CreateLogger(args: args);
CommandLineArgsHelper argsHelper = new (args);
Console.Write(" "); // 补全日志第一行开头的空白 Console.Write(" "); // 补全日志第一行开头的空白
CmdArgsHandler.ParseArgs(args);
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
// CORS配置 // CORS配置
@ -73,6 +79,10 @@ namespace iFileProxy
// 初始化缓存管理服务 // 初始化缓存管理服务
LocalCacheManager localCacheManager = new(app.Services); LocalCacheManager localCacheManager = new(app.Services);
if (!argsHelper.GetBooleanValue("disable-startup-check"))
// 初始化验证配置文件
AppConfig.CheckAppConfig(app.Services);
// 1. 错误处理(放在请求管道的最前面) // 1. 错误处理(放在请求管道的最前面)
app.UseMiddleware<ErrorHandlerMiddleware>(); app.UseMiddleware<ErrorHandlerMiddleware>();
@ -118,9 +128,45 @@ namespace iFileProxy
// 11. 启动应用 // 11. 启动应用
var dbGateService = app.Services.GetRequiredService<DatabaseGateService>(); var dbGateService = app.Services.GetRequiredService<DatabaseGateService>();
SerilogConfig.CreateLogger(dbGateService); SerilogConfig.CreateLogger(dbGateService,args);
app.Run(); if (!argsHelper.GetStringValue("url").IsNullOrEmpty())
app.Run(argsHelper.GetStringValue("url"));
else
app.Run();
}
private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
var logger = Log.ForContext<AppDomain>();
Exception exc = (Exception)e.ExceptionObject;
// 直接记录原始异常信息,不重新抛出
if (exc is MySqlException mySqlEx)
{
if (mySqlEx.Message.Contains("is not allowed to connect to this MySQL server", StringComparison.CurrentCulture))
{
logger.Fatal( "远程主机不允许你连接到该数据库, 请检查数据库服务端是否将本机IP加白! ");
}
else if (mySqlEx.Message.Contains("Unable to connect to any of the specified MySQL hosts"))
{
logger.Fatal("无法连接到远程数据库, 因为远程主机不可达, 请检查你的网络或数据库配置!!");
}
}
else if (exc is IOException ioEx)
{
if (ioEx.Message.Contains("address already in use."))
{
logger.Fatal("程序服务端口被占用!!!");
}
}
// 记录所有未处理的异常信息
logger.Fatal(exc, "未处理的异常: {ExceptionMessage}", exc.Message);
// 优雅退出应用程序
Environment.Exit(1);
} }
} }
} }

View file

@ -6,12 +6,13 @@
using iFileProxy.Sinks; using iFileProxy.Sinks;
using iFileProxy.Services; using iFileProxy.Services;
using ZstdSharp.Unsafe; using ZstdSharp.Unsafe;
using iFileProxy.Helpers;
public static class SerilogConfig public static class SerilogConfig
{ {
private static DatabaseGateService? _dbGateService; private static DatabaseGateService? _dbGateService;
public static void CreateLogger(DatabaseGateService? dbGateService = null) public static void CreateLogger(DatabaseGateService? dbGateService = null, string[]? args = null)
{ {
_dbGateService = dbGateService; // 保存实例以供后续使用 _dbGateService = dbGateService; // 保存实例以供后续使用
var baseLogPath = Path.Combine(AppContext.BaseDirectory, "logs"); var baseLogPath = Path.Combine(AppContext.BaseDirectory, "logs");
@ -22,13 +23,17 @@
{ {
Directory.CreateDirectory(baseLogPath); Directory.CreateDirectory(baseLogPath);
} }
var loggerConfiguration = new LoggerConfiguration();
var loggerConfiguration = new LoggerConfiguration()
#if RELEASE if (new CommandLineArgsHelper(args).GetBooleanValue("dev-logger"))
.MinimumLevel.Information() {
#else loggerConfiguration.MinimumLevel.Debug();
.MinimumLevel.Debug() }
#endif else
loggerConfiguration.MinimumLevel.Information();
loggerConfiguration
.MinimumLevel.Override("Microsoft", LogEventLevel.Information) .MinimumLevel.Override("Microsoft", LogEventLevel.Information)
.MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Warning) .MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Warning)
.Enrich.FromLogContext() .Enrich.FromLogContext()
@ -43,7 +48,7 @@
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] [{SourceContext}] {ClientIp} {Message:lj} {contentType} {queryString}{NewLine}{Exception}", outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] [{SourceContext}] {ClientIp} {Message:lj} {contentType} {queryString}{NewLine}{Exception}",
rollingInterval: RollingInterval.Day, rollingInterval: RollingInterval.Day,
fileSizeLimitBytes: 1073741824)) // 1GB fileSizeLimitBytes: 1073741824)) // 1GB
// 警告日志写入单独的文件 // 警告日志写入单独的文件
.WriteTo.Logger(lc => lc .WriteTo.Logger(lc => lc
.Filter.ByIncludingOnly(evt => evt.Level == LogEventLevel.Warning) .Filter.ByIncludingOnly(evt => evt.Level == LogEventLevel.Warning)
.WriteTo.File( .WriteTo.File(
@ -69,6 +74,7 @@
fileSizeLimitBytes: 1073741824)) fileSizeLimitBytes: 1073741824))
.Enrich.WithProperty("node_ip", GetIpAddress()); .Enrich.WithProperty("node_ip", GetIpAddress());
// 只有在提供了 DatabaseGateService 时才添加数据库 Sink // 只有在提供了 DatabaseGateService 时才添加数据库 Sink
if (_dbGateService != null) if (_dbGateService != null)
{ {

View file

@ -18,77 +18,13 @@ namespace iFileProxy.Services
Dictionary<string, DB> _dbDictionary = []; Dictionary<string, DB> _dbDictionary = [];
private const string CREATE_TASKS_TABLE_SQL = """ private Dictionary<string, string> createDBSqls = new Dictionary<string, string> {
CREATE TABLE IF NOT EXISTS `t_tasks_info` ( { "任务信息",@"CREATE TABLE `t_tasks_info` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增键', `tid` varchar(48) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '任务id', `file_name` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '文件名', `client_ip` varchar(46) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '客户端IP', `add_time` datetime NOT NULL COMMENT '任务在何时被添加', `update_time` datetime NOT NULL COMMENT '任务状态更新时间', `status` int(1) NULL DEFAULT NULL COMMENT '任务状态', `url` varchar(8192) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '文件url', `size` bigint(32) NULL DEFAULT NULL COMMENT '文件大小', `hash` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '文件hash', `tag` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '标记', PRIMARY KEY (`id`) USING BTREE, UNIQUE INDEX `tid`(`tid`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 139915 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = DYNAMIC;"},
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '', { "用户", @"CREATE TABLE `t_users` ( `user_id` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, `nickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '昵称', `email` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '电子邮箱', `username` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户名', `password_hash` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '密码哈希值', `mask` int(11) NOT NULL DEFAULT 0 COMMENT '权限掩码', `state` int(11) NOT NULL DEFAULT 0 COMMENT '状态', `create_time` datetime NOT NULL COMMENT '账号创建时间', `last_login_time` datetime NULL DEFAULT NULL COMMENT '上次登录时间', `last_login_ip` varchar(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '上次登录IP', PRIMARY KEY (`user_id`) USING BTREE, UNIQUE INDEX `username`(`username`) USING BTREE, UNIQUE INDEX `email`(`email`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = DYNAMIC;"},
`tid` varchar(48) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'id', { "用户事件", @"CREATE TABLE `t_user_events` ( `event_id` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, `user_id` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, `event_type` int(11) NOT NULL, `event_time` datetime NOT NULL, `event_ip` varchar(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, `event_detail` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL, PRIMARY KEY (`event_id`) USING BTREE, INDEX `user_id`(`user_id`) USING BTREE, CONSTRAINT `t_user_events_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `t_users` (`user_id`) ON DELETE RESTRICT ON UPDATE RESTRICT ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = DYNAMIC;"},
`file_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, { "系统日志", @"CREATE TABLE `t_system_logs` ( `log_id` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, `level` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, `message` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, `exception` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL, `properties` json NULL, `timestamp` datetime NOT NULL, PRIMARY KEY (`log_id`) USING BTREE, INDEX `idx_timestamp`(`timestamp`) USING BTREE, INDEX `idx_level`(`level`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = DYNAMIC;" },
`client_ip` varchar(15) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'IP', {"下载历史",@"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;" }
`add_time` datetime NOT NULL COMMENT '', };
`update_time` datetime NOT NULL COMMENT '',
`status` int(11) NULL DEFAULT NULL COMMENT '',
`url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'url',
`size` int(11) NULL DEFAULT NULL COMMENT '',
`hash` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'hash',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 17 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
""";
private const string CREATE_USERS_TABLE_SQL = """
CREATE TABLE IF NOT EXISTS `t_users` (
`user_id` varchar(36) NOT NULL,
`username` varchar(50) NOT NULL,
`nickname` varchar(50) NOT NULL,
`email` varchar(100) NOT NULL,
`password_hash` varchar(255) NOT NULL,
`mask` int NOT NULL DEFAULT 0,
`state` int NOT NULL DEFAULT 0,
`create_time` datetime NOT NULL,
`last_login_time` datetime NULL,
`last_login_ip` varchar(45) NULL,
PRIMARY KEY (`user_id`),
UNIQUE KEY `username` (`username`),
UNIQUE KEY `email` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
""";
private const string CREATE_USER_EVENTS_TABLE_SQL = """
CREATE TABLE IF NOT EXISTS `t_user_events` (
`event_id` varchar(36) NOT NULL,
`user_id` varchar(36) NOT NULL,
`event_type` int NOT NULL,
`event_time` datetime NOT NULL,
`event_ip` varchar(45) NULL,
`event_detail` text NULL,
PRIMARY KEY (`event_id`),
KEY `user_id` (`user_id`),
CONSTRAINT `fk_user_events_user` FOREIGN KEY (`user_id`) REFERENCES `t_users` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
""";
private const string CREATE_SYSTEM_LOGS_TABLE_SQL = """
CREATE TABLE IF NOT EXISTS `t_system_logs` (
`log_id` varchar(36) NOT NULL,
`level` varchar(20) NOT NULL,
`message` text NOT NULL,
`exception` text,
`properties` json,
`timestamp` datetime NOT NULL,
PRIMARY KEY (`log_id`),
KEY `idx_timestamp` (`timestamp`),
KEY `idx_level` (`level`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
""";
private const string CREATE_DOWNLOAD_HISTORY_TABLE_SQL = """
CREATE TABLE IF NOT EXISTS `t_download_history` (
`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 (`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 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
""";
public DatabaseGateService(AppConfig appConfig) public DatabaseGateService(AppConfig appConfig)
{ {
@ -97,13 +33,13 @@ namespace iFileProxy.Services
_appConfig = appConfig; _appConfig = appConfig;
try try
{ {
LoadDbDict();
_logger.Information("Done."); _logger.Information("Done.");
} }
catch (Exception e) catch (Exception e)
{ {
_logger.Fatal($"程序异常: {e.Message}"); _logger.Fatal($"程序异常: {e.Message}");
} }
LoadDbDict();
} }
/// <summary> /// <summary>
@ -154,7 +90,7 @@ namespace iFileProxy.Services
try try
{ {
var conn = new MySqlConnection(builder.ConnectionString); var conn = new MySqlConnection(builder.ConnectionString);
conn.Open(); conn.Open();
return conn; return conn;
} }
@ -178,7 +114,7 @@ namespace iFileProxy.Services
catch (Exception) catch (Exception)
{ {
_logger.Fatal($"=========== 数据库: {db.Key} 测试失败! ==========="); _logger.Fatal($"=========== 数据库: {db.Key} 测试失败! ===========");
return false; throw;
} }
} }
return true; return true;
@ -206,10 +142,10 @@ namespace iFileProxy.Services
/// <param name="sql"></param> /// <param name="sql"></param>
/// <param name="conn"></param> /// <param name="conn"></param>
/// <returns></returns> /// <returns></returns>
public string QueryTableData(string sql,string dbConfName) public string QueryTableData(string sql, string dbConfName)
{ {
DataTable dataTable = new(); DataTable dataTable = new();
using (MySqlDataAdapter adapter = new(new MySqlCommand(sql,GetAndOpenDBConn(dbConfName)))) using (MySqlDataAdapter adapter = new(new MySqlCommand(sql, GetAndOpenDBConn(dbConfName))))
adapter.Fill(dataTable); adapter.Fill(dataTable);
return JsonConvert.SerializeObject(dataTable); return JsonConvert.SerializeObject(dataTable);
} }
@ -222,7 +158,7 @@ namespace iFileProxy.Services
/// <returns>影响的行数</returns> /// <returns>影响的行数</returns>
public int Query(string sql, string dbConfName) public int Query(string sql, string dbConfName)
{ {
using MySqlCommand sqlCmd = new (sql, GetAndOpenDBConn(dbConfName)); using MySqlCommand sqlCmd = new(sql, GetAndOpenDBConn(dbConfName));
int n = sqlCmd.ExecuteNonQuery(); int n = sqlCmd.ExecuteNonQuery();
_logger.Debug($"查询完成, 受影响的行数: {n}"); _logger.Debug($"查询完成, 受影响的行数: {n}");
return n; return n;
@ -242,7 +178,13 @@ namespace iFileProxy.Services
return n; return n;
} }
public List<TaskInfo> CheckCacheDependencies(string taskId,string ipAddr) /// <summary>
/// 检查缓存依赖
/// </summary>
/// <param name="taskId">任务ID</param>
/// <param name="ipAddr">排除的IP地址</param>
/// <returns>依赖任务信息列表</returns>
public List<TaskInfo> CheckCacheDependencies(string taskId, string ipAddr)
{ {
string sql = $"SELECT * FROM t_tasks_info WHERE `status` = @status AND `tag` = @tag AND `client_ip` <> @ip_addr"; string sql = $"SELECT * FROM t_tasks_info WHERE `status` = @status AND `tag` = @tag AND `client_ip` <> @ip_addr";
using MySqlConnection conn = GetAndOpenDBConn("iFileProxy_Db"); using MySqlConnection conn = GetAndOpenDBConn("iFileProxy_Db");
@ -253,7 +195,7 @@ namespace iFileProxy.Services
sqlCmd.Parameters.AddWithValue("@status", TaskState.Cached); sqlCmd.Parameters.AddWithValue("@status", TaskState.Cached);
sqlCmd.Parameters.AddWithValue("@ip_addr", ipAddr); sqlCmd.Parameters.AddWithValue("@ip_addr", ipAddr);
return JsonConvert.DeserializeObject<List<TaskInfo>>( QueryTableData(sqlCmd)); return JsonConvert.DeserializeObject<List<TaskInfo>>(QueryTableData(sqlCmd));
} }
catch (Exception e) catch (Exception e)
{ {
@ -271,7 +213,7 @@ namespace iFileProxy.Services
{ {
using MySqlCommand sqlCmd = new(sql, conn); using MySqlCommand sqlCmd = new(sql, conn);
sqlCmd.Parameters.AddWithValue("@ip_addr", ipAddr); sqlCmd.Parameters.AddWithValue("@ip_addr", ipAddr);
sqlCmd.Parameters.AddWithValue ("@status", status); sqlCmd.Parameters.AddWithValue("@status", status);
return QueryTableData(sqlCmd); return QueryTableData(sqlCmd);
} }
catch (Exception e) catch (Exception e)
@ -289,7 +231,7 @@ namespace iFileProxy.Services
using MySqlConnection conn = GetAndOpenDBConn("iFileProxy_Db"); using MySqlConnection conn = GetAndOpenDBConn("iFileProxy_Db");
try try
{ {
using MySqlCommand sqlCmd = new (sql,conn); using MySqlCommand sqlCmd = new(sql, conn);
sqlCmd.Parameters.AddWithValue("@ip_addr", ipAddr); sqlCmd.Parameters.AddWithValue("@ip_addr", ipAddr);
return QueryTableData(sqlCmd); return QueryTableData(sqlCmd);
} }
@ -322,7 +264,8 @@ namespace iFileProxy.Services
{ {
string sql = $"SELECT * FROM t_tasks_info WHERE url = @url AND size = @size AND `status` = @status AND file_name = @fileName"; string sql = $"SELECT * FROM t_tasks_info WHERE url = @url AND size = @size AND `status` = @status AND file_name = @fileName";
using MySqlConnection conn = GetAndOpenDBConn("iFileProxy_Db"); using MySqlConnection conn = GetAndOpenDBConn("iFileProxy_Db");
try { try
{
MySqlCommand sqlCmd = new MySqlCommand(sql, conn); MySqlCommand sqlCmd = new MySqlCommand(sql, conn);
sqlCmd.Parameters.AddWithValue("@url", url); sqlCmd.Parameters.AddWithValue("@url", url);
sqlCmd.Parameters.AddWithValue("@size", size); sqlCmd.Parameters.AddWithValue("@size", size);
@ -337,10 +280,12 @@ namespace iFileProxy.Services
else else
return null; return null;
} }
catch (Exception e){ catch (Exception e)
{
return null; return null;
} }
finally { finally
{
conn.Close(); conn.Close();
} }
@ -380,21 +325,21 @@ namespace iFileProxy.Services
} }
return true; return true;
} }
public bool UpdateFieldsData(string fieldsName, string taskUUID,object val) public bool UpdateFieldsData(string fieldsName, string taskUUID, object val)
{ {
string sql = $"UPDATE t_tasks_info set `{fieldsName}` = @Data WHERE `tid` = @tid"; string sql = $"UPDATE t_tasks_info set `{fieldsName}` = @Data WHERE `tid` = @tid";
using MySqlConnection conn = GetAndOpenDBConn("iFileProxy_Db"); using MySqlConnection conn = GetAndOpenDBConn("iFileProxy_Db");
try try
{ {
using MySqlCommand sqlCmd = new(sql, conn); using MySqlCommand sqlCmd = new(sql, conn);
sqlCmd.Parameters.AddWithValue("@Data",val); sqlCmd.Parameters.AddWithValue("@Data", val);
sqlCmd.Parameters.AddWithValue("@tid",taskUUID); sqlCmd.Parameters.AddWithValue("@tid", taskUUID);
if (sqlCmd.ExecuteNonQuery() == 1) if (sqlCmd.ExecuteNonQuery() == 1)
{ {
return true; return true;
} }
else else
return false; return false;
} }
catch (Exception) catch (Exception)
{ {
@ -415,7 +360,7 @@ namespace iFileProxy.Services
try try
{ {
using MySqlCommand sqlCmd = new (sql, conn); using MySqlCommand sqlCmd = new(sql, conn);
sqlCmd.Parameters.AddWithValue("@status", taskInfo.Status); sqlCmd.Parameters.AddWithValue("@status", taskInfo.Status);
sqlCmd.Parameters.AddWithValue("@tid", taskInfo.TaskId); sqlCmd.Parameters.AddWithValue("@tid", taskInfo.TaskId);
if (sqlCmd.ExecuteNonQuery() >= 1) if (sqlCmd.ExecuteNonQuery() >= 1)
@ -434,7 +379,7 @@ namespace iFileProxy.Services
} }
finally finally
{ {
conn.Close (); conn.Close();
} }
} }
@ -453,7 +398,7 @@ namespace iFileProxy.Services
try try
{ {
string sql = $"DELETE FROM `t_tasks_info` WHERE `client_ip` = '{ipAddr}'"; string sql = $"DELETE FROM `t_tasks_info` WHERE `client_ip` = '{ipAddr}'";
return Query(sql,DbConfigName.iFileProxy); return Query(sql, DbConfigName.iFileProxy);
} }
catch (Exception) catch (Exception)
{ {
@ -466,13 +411,13 @@ namespace iFileProxy.Services
using var conn = GetAndOpenDBConn("iFileProxy_Db"); using var conn = GetAndOpenDBConn("iFileProxy_Db");
try try
{ {
// 创建表 foreach (var sql in createDBSqls)
using (var cmd = new MySqlCommand(CREATE_TASKS_TABLE_SQL, conn)) {
cmd.ExecuteNonQuery(); _logger.Information($"尝试创建 {sql.Key} 数据表....");
using (var cmd = new MySqlCommand(CREATE_USERS_TABLE_SQL, conn)) using MySqlCommand sqlcmd = new (sql.Value,conn);
cmd.ExecuteNonQuery(); sqlcmd.ExecuteNonQuery();
using (var cmd = new MySqlCommand(CREATE_USER_EVENTS_TABLE_SQL, conn)) _logger.Information("Done.");
cmd.ExecuteNonQuery(); }
_logger.Information("数据库表结构初始化完成"); _logger.Information("数据库表结构初始化完成");
} }
@ -482,6 +427,13 @@ namespace iFileProxy.Services
throw; throw;
} }
} }
/// <summary>
/// 执行查询并获取单个指定类型的对象
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="sql"></param>
/// <param name="parameters"></param>
/// <returns></returns>
public T ExecuteScalar<T>(string sql, Dictionary<string, object> parameters) public T ExecuteScalar<T>(string sql, Dictionary<string, object> parameters)
{ {
using var conn = GetAndOpenDBConn("iFileProxy_Db"); using var conn = GetAndOpenDBConn("iFileProxy_Db");
@ -494,6 +446,13 @@ namespace iFileProxy.Services
return (T)cmd.ExecuteScalar(); return (T)cmd.ExecuteScalar();
} }
/// <summary>
/// 执行SQL查询
/// </summary>
/// <typeparam name="T">预期返回的数据类型</typeparam>
/// <param name="sql">SQL语句</param>
/// <param name="parameters">参数列表</param>
/// <returns>所指定类型的数据</returns>
public List<T> ExecuteQuery<T>(string sql, Dictionary<string, object> parameters) public List<T> ExecuteQuery<T>(string sql, Dictionary<string, object> parameters)
{ {
using var conn = GetAndOpenDBConn("iFileProxy_Db"); using var conn = GetAndOpenDBConn("iFileProxy_Db");
@ -505,6 +464,12 @@ namespace iFileProxy.Services
return JsonConvert.DeserializeObject<List<T>>(QueryTableData(cmd)); return JsonConvert.DeserializeObject<List<T>>(QueryTableData(cmd));
} }
/// <summary>
/// 执行SQL查询
/// </summary>
/// <param name="sql">SQL语句</param>
/// <param name="parameters">参数列表</param>
/// <returns>受影响的行数</returns>
public int ExecuteNonQuery(string sql, Dictionary<string, object> parameters) public int ExecuteNonQuery(string sql, Dictionary<string, object> parameters)
{ {
using var conn = GetAndOpenDBConn("iFileProxy_Db"); using var conn = GetAndOpenDBConn("iFileProxy_Db");

View file

@ -32,7 +32,8 @@ namespace iFileProxy.Services
CACHE_LIFETIME = _appConfig.DownloadOptions.CacheLifetime; CACHE_LIFETIME = _appConfig.DownloadOptions.CacheLifetime;
_dbGateService = serviceProvider.GetRequiredService<DatabaseGateService>(); _dbGateService = serviceProvider.GetRequiredService<DatabaseGateService>();
// 开始定时清理任务 // 开始定时清理任务
_timer = new Timer((obj) => { _timer = new Timer((obj) =>
{
lock (_lock) lock (_lock)
{ {
CheckAndCleanCache(); CheckAndCleanCache();
@ -58,7 +59,7 @@ namespace iFileProxy.Services
{ {
using var cmd = new MySqlCommand(sql, conn); using var cmd = new MySqlCommand(sql, conn);
using var adapter = new MySqlDataAdapter(cmd); using var adapter = new MySqlDataAdapter(cmd);
var dataTable = new DataTable(); using var dataTable = new DataTable();
adapter.Fill(dataTable); adapter.Fill(dataTable);
taskInfos = JsonConvert.DeserializeObject<List<TaskInfo>>( taskInfos = JsonConvert.DeserializeObject<List<TaskInfo>>(
JsonConvert.SerializeObject(dataTable) JsonConvert.SerializeObject(dataTable)
@ -69,6 +70,16 @@ namespace iFileProxy.Services
{ {
foreach (TaskInfo taskInfo in taskInfos) foreach (TaskInfo taskInfo in taskInfos)
{ {
var dependencies = _dbGateService.CheckCacheDependencies(taskInfo.TaskId, taskInfo.ClientIp);
// 获取依赖和已经过期的任务ID交集
var intersect = dependencies.Select(x => x.TaskId).Intersect(taskInfos.Select(x => x.TaskId)).ToList();
// 若依赖该缓存的任务也处于过期状态 则不进行缓存依赖检查
if (intersect.IndexOf(taskInfo.TaskId) == -1)
if (dependencies != null && dependencies.Count > 0)
{
_logger.Warning($"TaskId: {taskInfo.TaskId} 所产生的缓存文件正被 {string.Join(",", dependencies.Select(x => x.TaskId))} 所依赖, 不会继续执行清理任务");
continue;
}
string cacheFileName = Path.Combine(_appConfig.DownloadOptions.SavePath, taskInfo.FileName); string cacheFileName = Path.Combine(_appConfig.DownloadOptions.SavePath, taskInfo.FileName);
if (File.Exists(cacheFileName)) if (File.Exists(cacheFileName))
{ {
@ -85,22 +96,20 @@ namespace iFileProxy.Services
} }
// 更新数据库状态 // 更新数据库状态
using (var conn = _dbGateService.GetAndOpenDBConn(DbConfigName.iFileProxy)) using var conn = _dbGateService.GetAndOpenDBConn(DbConfigName.iFileProxy);
// 更新标签
using (var cmd = new MySqlCommand(
"UPDATE t_tasks_info SET tag = @tag WHERE tid = @tid",
conn))
{ {
// 更新标签 cmd.Parameters.AddWithValue("@tag", "CLEANED");
using (var cmd = new MySqlCommand( cmd.Parameters.AddWithValue("@tid", taskInfo.TaskId);
"UPDATE t_tasks_info SET tag = @tag WHERE tid = @tid", cmd.ExecuteNonQuery();
conn))
{
cmd.Parameters.AddWithValue("@tag", "CLEANED");
cmd.Parameters.AddWithValue("@tid", taskInfo.TaskId);
cmd.ExecuteNonQuery();
}
// 更新状态
taskInfo.Status = TaskState.Cleaned;
_dbGateService.UpdateTaskStatus(taskInfo);
} }
// 更新状态
taskInfo.Status = TaskState.Cleaned;
_dbGateService.UpdateTaskStatus(taskInfo);
} }
} }
} }