diff --git a/README.md b/README.md index ddf2e58..16c3399 100644 --- a/README.md +++ b/README.md @@ -9,5 +9,5 @@ - [x] 基于IP查询提交的任务状态 - [x] 基于IP和路由的请求次数限制 - [ ] 任务队列 -- [ ] 已经存在对应文件时候直接跳过代理下载 直接下载已经缓存的内容 +- [x] 已经存在对应文件时候直接跳过代理下载 直接下载已经缓存的内容 - [ ] 捐赠 \ No newline at end of file diff --git a/src/Helpers/DatabaseHelper.cs b/src/Helpers/DatabaseHelper.cs index 1bfddaa..e79d919 100644 --- a/src/Helpers/DatabaseHelper.cs +++ b/src/Helpers/DatabaseHelper.cs @@ -110,7 +110,7 @@ namespace iFileProxy.Helpers /// /// /// - public static string GetTableData(MySqlCommand sqlCmd) + public static string QueryTableData(MySqlCommand sqlCmd) { DataTable dataTable = new(); using (MySqlDataAdapter adapter = new (sqlCmd)) @@ -118,6 +118,34 @@ namespace iFileProxy.Helpers return JsonConvert.SerializeObject(dataTable); } + /// + /// 获取一个json格式的数据表 + /// + /// + /// + /// + public string QueryTableData(string sql,string dbConfName) + { + DataTable dataTable = new(); + using (MySqlDataAdapter adapter = new(new MySqlCommand(sql,GetAndOpenDBConn(dbConfName)))) + adapter.Fill(dataTable); + return JsonConvert.SerializeObject(dataTable); + } + + /// + /// 内部查询数据专用 当此方法暴露给C端可能造成sql注入等安全问题 + /// + /// SQL语句 + /// 配置文件中的Description字段 + /// 影响的行数 + public int Query(string sql, string dbConfName) + { + using MySqlCommand sqlCmd = new (sql, GetAndOpenDBConn(dbConfName)); + int n = sqlCmd.ExecuteNonQuery(); + _logger.Debug($"查询完成, 受影响的行数: {n}"); + return n; + } + public string GetTaskListByIP(string ipAddr) { string sql = $"SELECT * FROM t_tasks_info WHERE client_ip = @ip_addr"; @@ -126,7 +154,7 @@ namespace iFileProxy.Helpers { using MySqlCommand sqlCmd = new (sql,conn); sqlCmd.Parameters.AddWithValue("@ip_addr", ipAddr); - return GetTableData(sqlCmd); + return QueryTableData(sqlCmd); } catch (Exception e) { @@ -143,7 +171,7 @@ namespace iFileProxy.Helpers { using MySqlCommand sqlCmd = new(sql, conn); sqlCmd.Parameters.AddWithValue("@tid", tid); - return GetTableData(sqlCmd); + return QueryTableData(sqlCmd); } catch (Exception e) { @@ -153,13 +181,39 @@ namespace iFileProxy.Helpers finally { conn.Close(); } } + 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"); + try { + MySqlCommand sqlCmd = new MySqlCommand(sql, conn); + sqlCmd.Parameters.AddWithValue("@url", url); + sqlCmd.Parameters.AddWithValue("@size", size); + sqlCmd.Parameters.AddWithValue("@status", status); + sqlCmd.Parameters.AddWithValue("@fileName", fileName); + string result = QueryTableData(sqlCmd); + List? r = JsonConvert.DeserializeObject>(result); + if (r != null) + { + return r[0]; + } + else + return null; + } + catch (Exception e){ + return null; + } + finally { + conn.Close(); + } + } public bool InsertTaskData(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`) " + - "VALUES (@tid, @file_name, @client_ip, @add_time, @update_time, @status, @url, @size, @hash)"; + 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"); try @@ -174,6 +228,7 @@ namespace iFileProxy.Helpers sqlCmd.Parameters.AddWithValue("@url", taskInfo.Url); sqlCmd.Parameters.AddWithValue("@size", taskInfo.Size); sqlCmd.Parameters.AddWithValue("@hash", taskInfo.Hash); + sqlCmd.Parameters.AddWithValue("@tag", taskInfo.Tag); sqlCmd.ExecuteNonQuery(); } @@ -225,13 +280,13 @@ namespace iFileProxy.Helpers using MySqlCommand sqlCmd = new (sql, conn); sqlCmd.Parameters.AddWithValue("@status", taskInfo.Status); sqlCmd.Parameters.AddWithValue("@tid", taskInfo.TaskId); - if (sqlCmd.ExecuteNonQuery() == 1) + if (sqlCmd.ExecuteNonQuery() >= 1) { - _logger.Debug($"Task: {taskInfo.TaskId} Task Status Change to {taskInfo.Status}"); + _logger.Debug($"Task: {taskInfo.TaskId} Status Change to {taskInfo.Status}"); return true; } else - _logger.Warning($"Task: {taskInfo.TaskId} Task Status Change Failed."); + _logger.Warning($"Task: {taskInfo.TaskId} Status Change Failed."); return false; } catch (Exception) diff --git a/src/Helpers/MasterHelper.cs b/src/Helpers/MasterHelper.cs index 606bed6..15e741b 100644 --- a/src/Helpers/MasterHelper.cs +++ b/src/Helpers/MasterHelper.cs @@ -1,4 +1,5 @@ -using iFileProxy.Models; +using iFileProxy.Config; +using iFileProxy.Models; using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; @@ -56,6 +57,13 @@ namespace iFileProxy.Helpers return clientIp; } + public static bool CheckDownloadFileExists(string fileName) + { + if (File.Exists(Path.Combine(AppConfig.GetCurrConfig().DownloadOptions.SavePath,fileName))) + return true; + return false; + } + public static string GetFileHash(string fileName, FileHashAlgorithm algorithm) { byte[] hash = []; diff --git a/src/Models/Db.cs b/src/Models/Db.cs index 2c3e6ff..cba23a4 100644 --- a/src/Models/Db.cs +++ b/src/Models/Db.cs @@ -1,37 +1,57 @@ -using System.Text.Json; +using Newtonsoft.Json; +using System.Text.Json; using System.Text.Json.Serialization; namespace iFileProxy.Models { public class TaskInfo { + [JsonProperty("id")] [JsonPropertyName("id")] public uint Id { get; set; } + [JsonProperty("tid")] [JsonPropertyName("tid")] public string TaskId { get; set; } + [JsonProperty("file_name")] [JsonPropertyName("file_name")] public string FileName { get; set; } + [JsonProperty("client_ip")] [JsonPropertyName("client_ip")] public string? ClientIp { get; set; } + [JsonProperty("add_time")] [JsonPropertyName("add_time")] public DateTime AddTime { get; set; } + [JsonProperty("update_time")] [JsonPropertyName("update_time")] public DateTime UpdateTime { get; set; } + [JsonProperty("status")] [JsonPropertyName("status")] public TaskState Status { get; set; } + [JsonProperty("url")] [JsonPropertyName("url")] public string Url { get; set; } + [JsonProperty("size")] [JsonPropertyName("size")] public long Size { get; set; } + [JsonProperty("hash")] [JsonPropertyName("hash")] public string Hash { get; set; } + + [JsonProperty("tag")] + [JsonPropertyName("tag")] + public string Tag { get; set; } + } + + public class DbConfigName { + public static string iFileProxy = "iFileProxy_Db"; + } } diff --git a/src/Models/Task.cs b/src/Models/Task.cs index 1ccbd1c..3055a0f 100644 --- a/src/Models/Task.cs +++ b/src/Models/Task.cs @@ -6,6 +6,7 @@ Error = 2, // 任务执行时候发生错误 已经结束 End = 3, // 任务正常结束 Cached = 4, // 要下载的内容已经缓存 + Cleaned =5, // 内容过期已被清理 } public enum TaskAddState { Success = 0, diff --git a/src/Program.cs b/src/Program.cs index 18b09c4..60e67e5 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -1,6 +1,7 @@ using iFileProxy.Config; using iFileProxy.Middleware; +using iFileProxy.Services; using Serilog; namespace iFileProxy @@ -12,10 +13,13 @@ namespace iFileProxy SerilogConfig.CreateLogger(); Serilog.ILogger logger = Log.Logger.ForContext(); - Console.Write(" "); + Console.Write(" "); // ǿ֢ſʼ־벻þ AppConfig.CheckAppConfig(); + // ʼ + LocalCacheManager localCacheManager = new (); + var builder = WebApplication.CreateBuilder(args); // Add services to the container. diff --git a/src/Properties/PublishProfiles/FolderProfile.pubxml b/src/Properties/PublishProfiles/FolderProfile.pubxml index 8285a16..1296e0c 100644 --- a/src/Properties/PublishProfiles/FolderProfile.pubxml +++ b/src/Properties/PublishProfiles/FolderProfile.pubxml @@ -15,7 +15,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121. <_TargetId>Folder net8.0 - win-x64 + linux-x64 e343bd8a-27ed-47e2-b50d-e3000730e65e false true diff --git a/src/Services/LocalCacheManager.cs b/src/Services/LocalCacheManager.cs index 97133d3..8434d69 100644 --- a/src/Services/LocalCacheManager.cs +++ b/src/Services/LocalCacheManager.cs @@ -1,9 +1,74 @@ -namespace iFileProxy.Services +using iFileProxy.Config; +using iFileProxy.Helpers; +using iFileProxy.Models; +using Newtonsoft.Json; +using Serilog; + +namespace iFileProxy.Services { /// /// 本地缓存管理器 /// public class LocalCacheManager { + // 禁用空警告 因为初始化的时候就已经检查过相关的内容了 +#pragma warning disable CS8601 + private readonly AppConfig _appConfig = AppConfig.GetCurrConfig(); + private readonly static Serilog.ILogger _logger = Log.Logger.ForContext(); + + private readonly Timer _timer; + private readonly DatabaseHelper _dbHelper; + private readonly int CACHE_LIFETIME; + + public LocalCacheManager() + { + _logger.Information("Initializing LocalCacheManager."); + CACHE_LIFETIME = _appConfig.DownloadOptions.CacheLifetime; + _dbHelper = new DatabaseHelper(_appConfig); + _timer = new Timer(CheckAndCleanCache, null, TimeSpan.FromSeconds(6), TimeSpan.FromSeconds(60)); + _logger.Information("succ."); + } + + /// + /// 检查并且清理缓存数据 + /// + public void CheckAndCleanCache(object state) + { + // 获取数据库中超出生命周期的缓存数据 + string result = _dbHelper.QueryTableData($"SELECT * FROM t_tasks_info WHERE UNIX_TIMESTAMP(NOW()) - UNIX_TIMESTAMP(update_time) > {CACHE_LIFETIME} AND (tag <> 'CLEANED' OR tag IS NULL)", DbConfigName.iFileProxy); + List? taskInfos = JsonConvert.DeserializeObject>(result); + if (taskInfos != null) + { + foreach (TaskInfo taskInfo in taskInfos) + { + string cacheFileName = Path.Combine(_appConfig.DownloadOptions.SavePath, taskInfo.FileName); + if (File.Exists(cacheFileName)) + { + _logger.Information($"正在清理缓存文件: {cacheFileName}"); + try + { + File.Delete(cacheFileName); + } + catch (Exception e) + { + _logger.Error("缓存文件删除失败: {e}", e); + throw; + } + } + _dbHelper.Query($"UPDATE t_tasks_info SET `tag` = \"CLEANED\" WHERE `tid` = '{taskInfo.TaskId}'", DbConfigName.iFileProxy); + taskInfo.Status = TaskState.Cleaned; + _dbHelper.UpdateTaskStatus(taskInfo); + } + } + } + + /// + /// 停止定时清理服务 + /// + public void StopScheduledCleanupService() + { + _timer.Dispose(); + } + } } diff --git a/src/Services/TaskManager.cs b/src/Services/TaskManager.cs index cc2b3bf..772ca32 100644 --- a/src/Services/TaskManager.cs +++ b/src/Services/TaskManager.cs @@ -61,7 +61,7 @@ namespace iFileProxy.Services if (_appConfig.SecurityOptions.BlockedFileName.IndexOf(fileInfo.FileName) != -1) return TaskAddState.ErrFileNameForbidden; - TaskInfo taskInfo = new() + TaskInfo taskInfo = new() { Url = t_url, TaskId = Guid.NewGuid().ToString(), @@ -72,12 +72,28 @@ namespace iFileProxy.Services Status = TaskState.NoInit, UpdateTime = DateTime.Now }; + + + if (MasterHelper.CheckDownloadFileExists(taskInfo.FileName)) + { + var r = _dbHelper.QueryTaskInfo(taskInfo.FileName, taskInfo.Url, taskInfo.Size, TaskState.End); + if (r != null) + { + taskInfo.Status = TaskState.Cached; + taskInfo.Hash = r.Hash; + taskInfo.Size = r.Size; + taskInfo.Tag = $"REDIRECT:{r.TaskId}"; + } + } if (_dbHelper.InsertTaskData(taskInfo)) { - StartTask(taskInfo); - taskInfo.Status = TaskState.Running; - _dbHelper.UpdateTaskStatus(taskInfo); - _logger.Debug("数据插入成功"); + if (taskInfo.Status != TaskState.Cached) + { + StartTask(taskInfo); + taskInfo.Status = TaskState.Running; + _dbHelper.UpdateTaskStatus(taskInfo); + } + _logger.Debug("任务添加成功."); return TaskAddState.Success; } else @@ -160,7 +176,7 @@ namespace iFileProxy.Services public List GetTaskInfo(string taskId) { _logger.Debug(_dbHelper.GetTaskInfoByTid(taskId)); - return JsonSerializer.Deserialize>(_dbHelper.GetTaskInfoByTid(taskId)) ?? [] ; + return JsonSerializer.Deserialize>(_dbHelper.GetTaskInfoByTid(taskId)) ?? []; } //public bool DeleteTask(HttpContext c) diff --git a/src/wwwroot/query_download_task.html b/src/wwwroot/query_download_task.html index cc9ab3e..5c09dd4 100644 --- a/src/wwwroot/query_download_task.html +++ b/src/wwwroot/query_download_task.html @@ -122,7 +122,9 @@ [0, "排队中"], [1, "进行中"], [2, "错误"], - [3, "已完成"] + [3, "已完成"], + [4, "已缓存"], + [5, "已被清理"] ]) data = []; $.ajax({ @@ -159,7 +161,7 @@ data.forEach(item => { const row = document.createElement('tr'); row.innerHTML = ` - ${item.status == 3 ? `${item.file_name}` : item.file_name} + ${item.status == 3 || item.status == 4 ? `${item.file_name}` : item.file_name} ${formatBytes(item.size)} ${item.add_time} ${statusStrDict.get(item.status)}