diff --git a/src/Config/AppConfig.cs b/src/Config/AppConfig.cs index 06ae7d0..4555ed4 100644 --- a/src/Config/AppConfig.cs +++ b/src/Config/AppConfig.cs @@ -107,6 +107,9 @@ namespace iFileProxy.Config [JsonPropertyName("MaxConnectionPoolSize")] public uint MaxConnectionPoolSize { get; set; } = 50; + [JsonPropertyName("ConnectionLifeTime")] + public uint ConnectionLifeTime { get; set; } = 120; + [JsonPropertyName("Common")] public Common Common { get; set; } diff --git a/src/Controllers/ManagementController.cs b/src/Controllers/ManagementController.cs index 74348f8..0283aef 100644 --- a/src/Controllers/ManagementController.cs +++ b/src/Controllers/ManagementController.cs @@ -18,15 +18,6 @@ namespace iFileProxy.Controllers public DatabaseGateService _dbGateService = dbGateService; private readonly Serilog.ILogger _logger = Log.Logger.ForContext(); - - // 查看任务详情 - // 删除任务 - // 停止任务 - // 查看任务Process信息 - // 立即执行任务 - // 查看系统配置 - // 获取全部任务信息 - /// /// 获取任务列表(支持分页) /// diff --git a/src/Controllers/StreamProxyController.cs b/src/Controllers/StreamProxyController.cs index a501eb3..52c7281 100644 --- a/src/Controllers/StreamProxyController.cs +++ b/src/Controllers/StreamProxyController.cs @@ -4,12 +4,13 @@ using Microsoft.AspNetCore.Mvc; using Serilog; using System.Web; using System.Net; -using iFileProxy.Helpers; // 用于 URL 解码 +using iFileProxy.Helpers; +using iFileProxy.Services; // 用于 URL 解码 namespace iFileProxy.Controllers { [Route("/")] - public class StreamProxyController(IHttpClientFactory httpClientFactory, AppConfig appConfig) : ControllerBase + public class StreamProxyController(IHttpClientFactory httpClientFactory, AppConfig appConfig, DatabaseGateService dbGateService) : ControllerBase { private readonly IHttpClientFactory _httpClientFactory = httpClientFactory; private long SizeLimit = appConfig.StreamProxyOptions.SizeLimit; @@ -151,12 +152,25 @@ namespace iFileProxy.Controllers // 设置响应头:告诉浏览器这是一个文件下载 var fileName = HttpUtility.UrlEncode(Path.GetFileName(new Uri(targetUrl).LocalPath)); // 对文件名进行编码 防止报错 - Response.Headers.Add("Content-Disposition", $"attachment; filename={fileName}"); + Response.Headers.Append("Content-Disposition", $"attachment; filename={fileName}"); Response.ContentType = response.Content.Headers.ContentType?.ToString() ?? "application/octet-stream"; if (contentLength > 0) Response.ContentLength = contentLength; + await dbGateService.AddTaskInfoDataAsync(new TaskInfo + { + FileName = fileName, + Size = contentLength ?? -1L, + ClientIp = MasterHelper.GetClientIPAddr(HttpContext), + AddTime = DateTime.UtcNow, + UpdateTime = DateTime.UtcNow, + Status = TaskState.Stream, + TaskId = Guid.NewGuid().ToString(), + Url = proxyUrl, + Tag = "STREAM" + }); + // 流式转发文件 using (var stream = await response.Content.ReadAsStreamAsync()) { diff --git a/src/Controllers/iProxyController.cs b/src/Controllers/iProxyController.cs index 1d8b102..06f8cbb 100644 --- a/src/Controllers/iProxyController.cs +++ b/src/Controllers/iProxyController.cs @@ -26,9 +26,9 @@ namespace iFileProxy.Controllers [HttpPost] [Route("/AddOfflineTask")] - public ActionResult AddOfflineTask() + public async Task> AddOfflineTask() { - return _taskManager.AddTask(HttpContext) switch + return await _taskManager.AddTask(HttpContext) switch { TaskAddState.Success => (ActionResult)Ok(new CommonRsp() { Retcode = (int)TaskAddState.Success, Message = "succ" }), TaskAddState.Fail => (ActionResult)Ok(new CommonRsp() { Retcode = (int)TaskAddState.Fail, Message = "unkown error!" }), @@ -122,8 +122,8 @@ namespace iFileProxy.Controllers var fileInfo = new FileInfo(filePath); // 设置响应头 - Response.Headers.Add("Content-Disposition", $"attachment; filename=\"{fileName}\""); - Response.Headers.Add("Content-Length", fileInfo.Length.ToString()); + Response.Headers.Append("Content-Disposition", $"attachment; filename=\"{fileName}\""); + Response.Headers.Append("Content-Length", fileInfo.Length.ToString()); // 使用 FileStream 进行流式传输 var fileStream = new FileStream( diff --git a/src/Middleware/IPAccessLimitMiddleware.cs b/src/Middleware/IPAccessLimitMiddleware.cs index 739b6cc..6068be3 100644 --- a/src/Middleware/IPAccessLimitMiddleware.cs +++ b/src/Middleware/IPAccessLimitMiddleware.cs @@ -16,7 +16,10 @@ public async Task InvokeAsync(HttpContext context) { - if (appConfig.SecurityOptions.BlockedClientIP.IndexOf(MasterHelper.GetClientIPAddr(context)) != -1) + string ipStr = MasterHelper.GetClientIPAddr(context); + + // 处理配置文件拉黑IP + if (appConfig.SecurityOptions.BlockedClientIP.IndexOf(ipStr) != -1) { context.Response.StatusCode = 403; await context.Response.WriteAsJsonAsync(new CommonRsp { Retcode = 403, Message = "你的IP地址已经被管理员拉入黑名单!"}); @@ -26,16 +29,6 @@ // 获取需要跟踪的路由列表 var routesToTrack = appConfig.SecurityOptions.RoutesToTrack; - // 检查当前请求的路径是否在需要跟踪的路由列表中 - foreach (var p in routesToTrack) - { - if (context.Request.Path.ToString().StartsWith(p, StringComparison.OrdinalIgnoreCase)) - { - // 如果匹配到需要跟踪的路由,继续处理请求 - break; - } - } - // 如果没有匹配到需要跟踪的路由,直接调用下一个中间件 if (!routesToTrack.Any(p => context.Request.Path.ToString().StartsWith(p, StringComparison.OrdinalIgnoreCase))) { @@ -44,29 +37,27 @@ } string dateStr = DateTime.Now.ToString("yyyy-MM-dd"); - string ipStr = MasterHelper.GetClientIPAddr(context); - - if (_IPAccessCountDict.ContainsKey(dateStr)) + + if (!_IPAccessCountDict.TryGetValue(dateStr, out var ipCounts)) { - if (_IPAccessCountDict[dateStr].ContainsKey(ipStr)) + ipCounts = []; + _IPAccessCountDict[dateStr] = ipCounts; + } + + if (ipCounts.TryGetValue(ipStr, out uint value)) + { + if (ipCounts[ipStr] >= appConfig.SecurityOptions.DailyRequestLimitPerIP) { - if (_IPAccessCountDict[dateStr][ipStr] >= appConfig.SecurityOptions.DailyRequestLimitPerIP) - { - context.Response.StatusCode = 200; - context.Response.ContentType = "application/json"; - await context.Response.WriteAsync(JsonSerializer.Serialize(new CommonRsp { Retcode = 403, Message = "日请求次数超过限制! (GMT+8 时间00:00重置)" })); - return; - } - _IPAccessCountDict[dateStr][ipStr]++; - } - else - { - _IPAccessCountDict[dateStr].Add(ipStr, 1); + context.Response.StatusCode = 200; + context.Response.ContentType = "application/json"; + await context.Response.WriteAsync(JsonSerializer.Serialize(new CommonRsp { Retcode = 403, Message = "日请求次数超过限制! (GMT+8 时间00:00重置)" })); + return; } + ipCounts[ipStr] = ++value; } else { - _IPAccessCountDict.Add(dateStr, new Dictionary { { ipStr, 1 } }); + ipCounts.Add(ipStr, 1); } await _next(context); diff --git a/src/Middleware/JwtMiddleware.cs b/src/Middleware/JwtMiddleware.cs index 1a3e75a..3966ee9 100644 --- a/src/Middleware/JwtMiddleware.cs +++ b/src/Middleware/JwtMiddleware.cs @@ -1,4 +1,5 @@ using iFileProxy.Helpers; +using iFileProxy.Models; using Microsoft.IdentityModel.Tokens; using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; @@ -6,16 +7,10 @@ using System.Text; namespace iFileProxy.Middleware { - public class JwtMiddleware + public class JwtMiddleware(RequestDelegate next, IConfiguration configuration) { - private readonly RequestDelegate _next; - private readonly IConfiguration _configuration; - - public JwtMiddleware(RequestDelegate next, IConfiguration configuration) - { - _next = next; - _configuration = configuration; - } + private readonly RequestDelegate _next = next; + private readonly IConfiguration _configuration = configuration; public async Task InvokeAsync(HttpContext context) { @@ -30,7 +25,7 @@ namespace iFileProxy.Middleware { var tokenHandler = new JwtSecurityTokenHandler(); var key = Encoding.ASCII.GetBytes(_configuration["Jwt:Key"]); - + // 验证token tokenHandler.ValidateToken(token, new TokenValidationParameters { @@ -44,18 +39,19 @@ namespace iFileProxy.Middleware }, out SecurityToken validatedToken); var jwtToken = (JwtSecurityToken)validatedToken; - + // 验证客户端信息 var tokenFingerprint = jwtToken.Claims.First(x => x.Type == "fingerprint").Value; var tokenUserAgent = jwtToken.Claims.First(x => x.Type == "userAgent").Value; var tokenIp = jwtToken.Claims.First(x => x.Type == "ip").Value; // 如果客户端信息不匹配,则拒绝访问 - if (fingerprint != tokenFingerprint || - userAgent != tokenUserAgent || + if (fingerprint != tokenFingerprint || + userAgent != tokenUserAgent || (ip != tokenIp && !IsLocalNetwork(ip, tokenIp))) // 允许本地网络IP变化 { context.Response.StatusCode = 401; + await context.Response.WriteAsJsonAsync(new CommonRsp { Message = "环境发生变化, 请重新验证", Retcode = 401}); return; } @@ -71,14 +67,14 @@ namespace iFileProxy.Middleware await _next(context); } - private bool IsLocalNetwork(string? ip1, string? ip2) + private static bool IsLocalNetwork(string? ip1, string? ip2) { if (string.IsNullOrEmpty(ip1) || string.IsNullOrEmpty(ip2)) return false; - + // 检查是否都是内网IP return (ip1.StartsWith("192.168.") && ip2.StartsWith("192.168.")) || (ip1.StartsWith("10.") && ip2.StartsWith("10.")) || (ip1.StartsWith("172.") && ip2.StartsWith("172.")); } } -} \ No newline at end of file +} \ No newline at end of file diff --git a/src/Models/Task.cs b/src/Models/Task.cs index f507cc7..e107903 100644 --- a/src/Models/Task.cs +++ b/src/Models/Task.cs @@ -37,6 +37,10 @@ /// Canceled = 7, /// + /// 流任务 + /// + Stream = 8, + /// /// 其他 /// Other = 999 diff --git a/src/Program.cs b/src/Program.cs index 8aa948b..0bb29cf 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.IdentityModel.Tokens; using Serilog; using System.Text; +using System.Diagnostics; namespace iFileProxy { diff --git a/src/Services/DatabaseGateService.cs b/src/Services/DatabaseGateService.cs index 53eab9d..1110dff 100644 --- a/src/Services/DatabaseGateService.cs +++ b/src/Services/DatabaseGateService.cs @@ -148,8 +148,8 @@ namespace iFileProxy.Services Password = db_password, Pooling = true, MinimumPoolSize = 1, - MaximumPoolSize = AppConfig.GetCurrConfig().Database.MaxConnectionPoolSize, - ConnectionLifeTime = 300 // 连接最大生命周期(秒) + MaximumPoolSize = _appConfig.Database.MaxConnectionPoolSize, + ConnectionLifeTime = _appConfig.Database.ConnectionLifeTime // 连接最大生命周期(秒) }; try @@ -170,10 +170,9 @@ namespace iFileProxy.Services foreach (var db in _dbDictionary) { _logger.Information($"[程序启动前配置验证] 正在测试数据库配置: {db.Key} ..."); - MySqlConnection dbConn = new(); try { - dbConn = GetAndOpenDBConn(db.Key); + using MySqlConnection dbConn = GetAndOpenDBConn(db.Key); _logger.Information($"succ."); } catch (Exception) @@ -181,10 +180,6 @@ namespace iFileProxy.Services _logger.Fatal($"=========== 数据库: {db.Key} 测试失败! ==========="); return false; } - finally - { - dbConn.Close(); - } } return true; } @@ -250,7 +245,7 @@ namespace iFileProxy.Services public List CheckCacheDependencies(string taskId,string ipAddr) { string sql = $"SELECT * FROM t_tasks_info WHERE `status` = @status AND `tag` = @tag AND `client_ip` <> @ip_addr"; - MySqlConnection conn = GetAndOpenDBConn("iFileProxy_Db"); + using MySqlConnection conn = GetAndOpenDBConn("iFileProxy_Db"); try { using MySqlCommand sqlCmd = new(sql, conn); @@ -271,7 +266,7 @@ namespace iFileProxy.Services public string GetTaskListByStateAndIp(string ipAddr, TaskState status) { string sql = $"SELECT * FROM t_tasks_info WHERE client_ip = @ip_addr AND `status` = @status"; - MySqlConnection conn = GetAndOpenDBConn("iFileProxy_Db"); + using MySqlConnection conn = GetAndOpenDBConn("iFileProxy_Db"); try { using MySqlCommand sqlCmd = new(sql, conn); @@ -291,7 +286,7 @@ namespace iFileProxy.Services public string GetTaskListByIP(string ipAddr) { string sql = $"SELECT * FROM t_tasks_info WHERE client_ip = @ip_addr"; - MySqlConnection conn = GetAndOpenDBConn("iFileProxy_Db"); + using MySqlConnection conn = GetAndOpenDBConn("iFileProxy_Db"); try { using MySqlCommand sqlCmd = new (sql,conn); @@ -308,7 +303,7 @@ namespace iFileProxy.Services public string GetTaskInfoByTid(string tid) { string sql = $"SELECT * FROM t_tasks_info WHERE `tid` = @tid"; - MySqlConnection conn = GetAndOpenDBConn("iFileProxy_Db"); + using MySqlConnection conn = GetAndOpenDBConn("iFileProxy_Db"); try { using MySqlCommand sqlCmd = new(sql, conn); @@ -326,7 +321,7 @@ namespace iFileProxy.Services public TaskInfo? QueryTaskInfo(string fileName, string url, long size, TaskState status) { string sql = $"SELECT * FROM t_tasks_info WHERE url = @url AND size = @size AND `status` = @status AND file_name = @fileName"; - MySqlConnection conn = GetAndOpenDBConn("iFileProxy_Db"); + using MySqlConnection conn = GetAndOpenDBConn("iFileProxy_Db"); try { MySqlCommand sqlCmd = new MySqlCommand(sql, conn); sqlCmd.Parameters.AddWithValue("@url", url); @@ -351,12 +346,12 @@ namespace iFileProxy.Services } - public bool InsertTaskData(TaskInfo taskInfo) + public async Task AddTaskInfoDataAsync(TaskInfo taskInfo) { _logger.Debug(System.Text.Json.JsonSerializer.Serialize(taskInfo)); string sql = "INSERT INTO `t_tasks_info` (`tid`, `file_name`, `client_ip`, `add_time`, `update_time`, `status`, `url`, `size`, `hash`, `tag`) " + "VALUES (@tid, @file_name, @client_ip, @add_time, @update_time, @status, @url, @size, @hash, @tag)"; - MySqlConnection conn = GetAndOpenDBConn("iFileProxy_Db"); + using MySqlConnection conn = GetAndOpenDBConn("iFileProxy_Db"); try { @@ -372,7 +367,7 @@ namespace iFileProxy.Services sqlCmd.Parameters.AddWithValue("@hash", taskInfo.Hash); sqlCmd.Parameters.AddWithValue("@tag", taskInfo.Tag); - sqlCmd.ExecuteNonQuery(); + await sqlCmd.ExecuteNonQueryAsync(); } catch (Exception) { @@ -388,7 +383,7 @@ namespace iFileProxy.Services public bool UpdateFieldsData(string fieldsName, string taskUUID,object val) { string sql = $"UPDATE t_tasks_info set `{fieldsName}` = @Data WHERE `tid` = @tid"; - MySqlConnection conn = GetAndOpenDBConn("iFileProxy_Db"); + using MySqlConnection conn = GetAndOpenDBConn("iFileProxy_Db"); try { using MySqlCommand sqlCmd = new(sql, conn); @@ -531,7 +526,7 @@ namespace iFileProxy.Services public PagedResult GetPagedTaskList(int page, int pageSize, TaskState? status = null) { // 构建基础SQL - var sql = new StringBuilder("SELECT * FROM t_tasks_info"); + var sql = new StringBuilder($"SELECT * FROM t_tasks_info WHERE `status` <> {(int)TaskState.Stream}"); var parameters = new Dictionary(); // 添加状态过滤 @@ -547,7 +542,7 @@ namespace iFileProxy.Services parameters.Add("@limit", pageSize); // 获取总记录数 - var countSql = "SELECT COUNT(*) FROM t_tasks_info" + + var countSql = $"SELECT COUNT(*) FROM t_tasks_info WHERE `status` <> {(int)TaskState.Stream}" + (status.HasValue ? " AND status = @status" : ""); var totalCount = ExecuteScalar(countSql, parameters); diff --git a/src/Services/LocalCacheManager.cs b/src/Services/LocalCacheManager.cs index 4df1130..c13ee0e 100644 --- a/src/Services/LocalCacheManager.cs +++ b/src/Services/LocalCacheManager.cs @@ -51,7 +51,7 @@ namespace iFileProxy.Services // 获取数据库中超出生命周期的缓存数据 string sql = $@"SELECT * FROM t_tasks_info WHERE UNIX_TIMESTAMP(NOW()) - UNIX_TIMESTAMP(update_time) > {CACHE_LIFETIME} - AND (tag <> 'CLEANED' OR tag IS NULL)"; + AND (tag <> 'CLEANED' AND tag <> 'STREAM' AND `status` <> {(int)TaskState.Stream} OR tag IS NULL )"; List? taskInfos; using (var conn = _dbGateService.GetAndOpenDBConn(DbConfigName.iFileProxy)) diff --git a/src/Services/TaskManager.cs b/src/Services/TaskManager.cs index f1d6b05..fbb2c9c 100644 --- a/src/Services/TaskManager.cs +++ b/src/Services/TaskManager.cs @@ -89,7 +89,7 @@ namespace iFileProxy.Services /// /// HttpContext /// - public TaskAddState AddTask(HttpContext c) + public async Task AddTask(HttpContext c) { string? clientIp = MasterHelper.GetClientIPAddr(c); string? t_url = c.Request.Query["url"].FirstOrDefault() ?? c.Request.Form["url"].FirstOrDefault(); @@ -152,7 +152,7 @@ namespace iFileProxy.Services return TaskAddState.ErrQueueLengthLimit; _pendingTasks.Enqueue(taskInfo); // 加入等待队列 - var status = AddTaskInfoToDb(taskInfo,true); + var status = await AddTaskInfoToDb(taskInfo,true); _logger.Information($"[TaskId: {taskInfo.TaskId}] Queuing..."); if (status != TaskAddState.Success) { return status; } return TaskAddState.Pending; @@ -170,7 +170,7 @@ namespace iFileProxy.Services } } - return AddTaskInfoToDb(taskInfo); + return await AddTaskInfoToDb(taskInfo); } @@ -181,9 +181,9 @@ namespace iFileProxy.Services /// 任务信息 /// 是否正在排队 若正在排队则不开始任务 等待任务调度 /// - public TaskAddState AddTaskInfoToDb(TaskInfo taskInfo, bool queuing = false) + public async Task AddTaskInfoToDb(TaskInfo taskInfo, bool queuing = false) { - if (_dbGateService.InsertTaskData(taskInfo)) + if (await _dbGateService.AddTaskInfoDataAsync(taskInfo)) { if (!queuing) // 如果不是正在排队的任务 { diff --git a/src/iFileProxy.json b/src/iFileProxy.json index 601947a..43eb3f0 100644 --- a/src/iFileProxy.json +++ b/src/iFileProxy.json @@ -1,6 +1,7 @@ { "Database": { - "MaxConnectionPoolSize": 100, + "MaxConnectionPoolSize": 100, + "ConnectionLifeTime": 120, "Common": { "Host": "47.243.56.137", "Port": 3306,