From c3a4faae8fc9a71991f05c83a44fb39ca480a717 Mon Sep 17 00:00:00 2001 From: root Date: Sat, 30 Nov 2024 17:58:20 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E4=BA=86=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E5=91=98=E5=90=8E=E5=8F=B0=20=E4=BD=86=E6=98=AF=E8=BF=98?= =?UTF-8?q?=E6=9C=AA=E6=B7=BB=E5=8A=A0=E4=BB=BB=E4=BD=95=E9=89=B4=E6=9D=83?= =?UTF-8?q?=E6=89=8B=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Config/AppConfig.cs | 5 +- src/Controllers/ManagementController.cs | 359 ++++++++++++++++++++++++ src/Models/PagedResult.cs | 31 ++ src/Models/ProcessInfo.cs | 77 +++++ src/Models/Task.cs | 4 + src/Program.cs | 19 +- src/Services/DatabaseGateService.cs | 299 +++++++++++--------- src/Services/TaskManager.cs | 268 +++++++++++++++++- src/iFileProxy.csproj | 2 +- 9 files changed, 922 insertions(+), 142 deletions(-) create mode 100644 src/Controllers/ManagementController.cs create mode 100644 src/Models/PagedResult.cs create mode 100644 src/Models/ProcessInfo.cs diff --git a/src/Config/AppConfig.cs b/src/Config/AppConfig.cs index 9d514c0..94c6fc9 100644 --- a/src/Config/AppConfig.cs +++ b/src/Config/AppConfig.cs @@ -2,6 +2,7 @@ using Serilog; using System.Text.Json; using System.Text.Json.Serialization; +using MySql.Data.MySqlClient; namespace iFileProxy.Config { @@ -74,7 +75,7 @@ namespace iFileProxy.Config { public string SavePath { get; set; } = "./proxy_tmp/"; public uint ThreadNum { get; set; } = 1; - public uint MaxAllowedFileSize { get; set; } + public long MaxAllowedFileSize { get; set; } public uint MaxParallelTasks { get; set; } = 4; public uint MaxQueueLength {get; set; } = 60; public string Aria2cPath { get; set; } = "./bin/aria2c"; @@ -96,7 +97,7 @@ namespace iFileProxy.Config public Common Common { get; set; } [JsonPropertyName("Databases")] - public DB[] Databases { get; set; } + public List Databases { get; set; } } public partial class Common diff --git a/src/Controllers/ManagementController.cs b/src/Controllers/ManagementController.cs new file mode 100644 index 0000000..82d150b --- /dev/null +++ b/src/Controllers/ManagementController.cs @@ -0,0 +1,359 @@ +using iFileProxy.Config; +using iFileProxy.Models; +using iFileProxy.Services; +using Microsoft.AspNetCore.Mvc; +using Serilog; +using System.Text.Json; +namespace iFileProxy.Controllers +{ + [Route("[controller]")] + [ApiController] + public class ManagementController(TaskManager taskManager, DatabaseGateService dbGateService) : ControllerBase + { + public TaskManager _taskManager = taskManager; + public DatabaseGateService _dbGateService = dbGateService; + private readonly Serilog.ILogger _logger = Log.Logger.ForContext(); + + + // 查看任务详情 + // 删除任务 + // 停止任务 + // 查看任务Process信息 + // 立即执行任务 + // 查看系统配置 + // 获取全部任务信息 + + /// + /// 获取任务列表(支持分页) + /// + /// 页码,从1开始 + /// 每页数量 + /// 可选的任务状态过滤 + /// 分页后的任务列表 + [HttpGet("GetTaskList")] + public ActionResult GetTaskList([FromQuery] int page = 1, [FromQuery] int pageSize = 10, [FromQuery] TaskState? status = null) + { + try + { + var result = _dbGateService.GetPagedTaskList(page, pageSize, status); + result.Data.ForEach(task => + { + if (task.Status == TaskState.Queuing) + task.QueuePosition = _taskManager.GetQueuePosition(task.TaskId); + }); + return Ok(new CommonRsp + { + Retcode = 0, + Message = "success", + Data = result + }); + } + catch (Exception ex) + { + _logger.Error($"获取任务列表失败: {ex.Message}"); + return Ok(new CommonRsp + { + Retcode = 1, + Message = "获取任务列表失败" + }); + } + } + + [HttpGet("GetSystemConfig")] + public ActionResult GetSystemConfig() + { + var c = AppConfig.GetCurrConfig(); + c.Database.Databases.ForEach(db => { + db.Password = "**********"; + }); + return Ok(new CommonRsp { Retcode = 0 , Data = c,Message = "succ"}); + } + + /// + /// 删除指定任务 + /// + /// 任务ID + /// 删除结果 + [HttpDelete("DeleteTask/{taskId}")] + public ActionResult DeleteTask(string taskId) + { + try + { + // 先检查任务是否存在 + var taskInfo = JsonSerializer.Deserialize>(_dbGateService.GetTaskInfoByTid(taskId))[0]; + if (taskInfo == null) + { + return Ok(new CommonRsp + { + Retcode = 1, + Message = "任务不存在" + }); + } + + // 如果任务正在运行,先尝试中断 + if (taskInfo.Status == TaskState.Running) + { + _taskManager.TryKillTask(taskInfo); + } + + // 删除任务记录 + var result = _dbGateService.DeleteTaskByTid(taskId); + + return Ok(new CommonRsp + { + Retcode = result ? 0 : 1, + Message = result ? "删除成功" : "删除失败" + }); + } + catch (Exception ex) + { + _logger.Error($"删除任务失败: {ex.Message}"); + return Ok(new CommonRsp + { + Retcode = 1, + Message = "删除任务失败" + }); + } + } + + /// + /// 获取任务详细信息 + /// + /// 任务ID + /// 任务详细信息 + [HttpGet("GetTaskDetail/{taskId}")] + public ActionResult GetTaskDetail(string taskId) + { + try + { + var taskInfo = _dbGateService.GetTaskDetail(taskId); + if (taskInfo == null) + { + return Ok(new CommonRsp + { + Retcode = 1, + Message = "任务不存在" + }); + } + + return Ok(new CommonRsp + { + Retcode = 0, + Message = "success", + Data = taskInfo + }); + } + catch (Exception ex) + { + _logger.Error($"获取任务详情失败: {ex.Message}"); + return Ok(new CommonRsp + { + Retcode = 1, + Message = "获取任务详情失败" + }); + } + } + + /// + /// 终止正在运行的任务 + /// + /// 任务ID + /// 操作结果 + [HttpPost("KillTask/{taskId}")] + public ActionResult KillTask(string taskId) + { + try + { + // 先获取任务信息 + var taskInfo = _dbGateService.GetTaskDetail(taskId); + if (taskInfo == null) + { + return Ok(new CommonRsp + { + Retcode = 1, + Message = "任务不存在" + }); + } + + // 检查任务是否在运行 + if (taskInfo.Status != TaskState.Running) + { + return Ok(new CommonRsp + { + Retcode = 1, + Message = "任务不在运行状态" + }); + } + + // 尝试终止任务 + _taskManager.TryKillTask(taskInfo); + + return Ok(new CommonRsp + { + Retcode = 0, + Message = "任务已终止" + }); + } + catch (Exception ex) + { + _logger.Error($"终止任务失败: {ex.Message}"); + return Ok(new CommonRsp + { + Retcode = 1, + Message = "终止任务失败" + }); + } + } + + /// + /// 获取任务状态概览数据 + /// + /// 各状态任务的数量统计 + [HttpGet("GetTaskOverview")] + public ActionResult GetTaskOverview() + { + try + { + var overview = _dbGateService.GetTaskStatusOverview(); + + return Ok(new CommonRsp + { + Retcode = 0, + Message = "success", + Data = overview + }); + } + catch (Exception ex) + { + _logger.Error($"获取任务概览失败: {ex.Message}"); + return Ok(new CommonRsp + { + Retcode = 1, + Message = "获取任务概览失败" + }); + } + } + + /// + /// 获取正在运行任务的进程信息 + /// + /// 任务ID + /// 进程详细信息 + [HttpGet("GetProcessInfo/{taskId}")] + public ActionResult GetProcessInfo(string taskId) + { + try + { + var processInfo = _taskManager.GetProcessInfo(taskId); + if (processInfo == null) + { + return Ok(new CommonRsp + { + Retcode = 1, + Message = "任务不存在或未在运行状态" + }); + } + + return Ok(new CommonRsp + { + Retcode = 0, + Message = "success", + Data = processInfo + }); + } + catch (Exception ex) + { + _logger.Error($"获取进程信息失败: {ex.Message}"); + return Ok(new CommonRsp + { + Retcode = 1, + Message = "获取进程信息失败" + }); + } + } + + /// + /// 重试失败的任务 + /// + /// 任务ID + /// 重试结果 + [HttpPost("RetryTask/{taskId}")] + public ActionResult RetryTask(string taskId) + { + try + { + var result = _taskManager.RetryTask(taskId); + return Ok(new CommonRsp + { + Retcode = result ? 0 : 1, + Message = result ? "任务重试已开始" : "任务重试失败" + }); + } + catch (Exception ex) + { + _logger.Error($"重试任务失败: {ex.Message}"); + return Ok(new CommonRsp + { + Retcode = 1, + Message = "重试任务失败" + }); + } + } + + /// + /// 将任务移动到等待队列最前面 + /// + /// 任务ID + /// 操作结果 + [HttpPost("PrioritizeTask/{taskId}")] + public ActionResult PrioritizeTask(string taskId) + { + try + { + var result = _taskManager.PrioritizeTask(taskId); + return Ok(new CommonRsp + { + Retcode = result ? 0 : 1, + Message = result ? "任务已移至队列首位" : "调整任务优先级失败" + }); + } + catch (Exception ex) + { + _logger.Error($"调整任务优先级失败: {ex.Message}"); + return Ok(new CommonRsp + { + Retcode = 1, + Message = "调整任务优先级失败" + }); + } + } + + /// + /// 立即执行指定任务 + /// + /// 任务ID + /// 操作结果 + [HttpPost("ExecuteImmediately/{taskId}")] + public ActionResult ExecuteImmediately(string taskId) + { + try + { + var result = _taskManager.ExecuteImmediately(taskId); + return Ok(new CommonRsp + { + Retcode = result ? 0 : 1, + Message = result ? "任务已开始执行" : "立即执行任务失败" + }); + } + catch (Exception ex) + { + _logger.Error($"立即执行任务失败: {ex.Message}"); + return Ok(new CommonRsp + { + Retcode = 1, + Message = "立即执行任务失败" + }); + } + } + } +} diff --git a/src/Models/PagedResult.cs b/src/Models/PagedResult.cs new file mode 100644 index 0000000..a6a4a9b --- /dev/null +++ b/src/Models/PagedResult.cs @@ -0,0 +1,31 @@ +/// +/// 通用分页结果模型 +/// +/// 数据项类型 +public class PagedResult +{ + /// + /// 总记录数 + /// + public long Total { get; set; } + + /// + /// 每页数量 + /// + public int PageSize { get; set; } + + /// + /// 当前页码 + /// + public int CurrentPage { get; set; } + + /// + /// 总页数 + /// + public int TotalPages { get; set; } + + /// + /// 当前页的数据列表 + /// + public List Data { get; set; } = new(); +} \ No newline at end of file diff --git a/src/Models/ProcessInfo.cs b/src/Models/ProcessInfo.cs new file mode 100644 index 0000000..64daeed --- /dev/null +++ b/src/Models/ProcessInfo.cs @@ -0,0 +1,77 @@ +using System.Diagnostics; +using ThreadState = System.Diagnostics.ThreadState; + +namespace iFileProxy.Models +{ + /// + /// 进程详细信息模型 + /// + public class ProcessInfo + { + /// + /// 进程ID + /// + public int ProcessId { get; set; } + + /// + /// 进程名称 + /// + public string ProcessName { get; set; } = string.Empty; + + /// + /// 启动时间 + /// + public DateTime StartTime { get; set; } + + /// + /// 运行时长(秒) + /// + public double RunningTime { get; set; } + + /// + /// 线程数 + /// + public int ThreadCount { get; set; } + + /// + /// 命令行参数 + /// + public string CommandLine { get; set; } = string.Empty; + + /// + /// 进程优先级 + /// + public ProcessPriorityClass PriorityClass { get; set; } + + /// + /// 工作目录 + /// + public string WorkingDirectory { get; set; } = string.Empty; + + /// + /// 线程信息列表 + /// + public List Threads { get; set; } = new(); + } + + /// + /// 线程信息模型 + /// + public class ThreadInfo + { + /// + /// 线程ID + /// + public int ThreadId { get; set; } + + /// + /// 线程状态 + /// + public ThreadState ThreadState { get; set; } + + /// + /// 线程优先级 + /// + public ThreadPriorityLevel Priority { get; set; } + } +} \ No newline at end of file diff --git a/src/Models/Task.cs b/src/Models/Task.cs index 414978e..fcab32f 100644 --- a/src/Models/Task.cs +++ b/src/Models/Task.cs @@ -36,6 +36,10 @@ /// 任务因为各种原因被取消 /// Canceled = 7, + /// + /// 其他 + /// + Other = 999 } /// /// 任务添加状态 diff --git a/src/Program.cs b/src/Program.cs index 14f031a..31cbfec 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -17,6 +17,21 @@ namespace iFileProxy var builder = WebApplication.CreateBuilder(args); + // CORS + builder.Services.AddCors(options => + { + options.AddPolicy("AllowFrontend", + builder => + { + builder + .WithOrigins("http://localhost:3000") // ǰ˵ַ + .AllowAnyMethod() + .AllowAnyHeader() + .AllowCredentials(); + }); + }); + + // Add services to the container. builder.Services.AddControllers(); @@ -61,7 +76,9 @@ namespace iFileProxy } // м - app.UseMiddleware(); + app.UseMiddleware(); + + app.UseCors("AllowFrontend"); app.UseHttpsRedirection(); diff --git a/src/Services/DatabaseGateService.cs b/src/Services/DatabaseGateService.cs index 87a40fe..20f0b61 100644 --- a/src/Services/DatabaseGateService.cs +++ b/src/Services/DatabaseGateService.cs @@ -5,13 +5,10 @@ using iFileProxy.Helpers; using MySql.Data.MySqlClient; using iFileProxy.Models; using Newtonsoft.Json; +using System.Text; namespace iFileProxy.Services { - /// - /// 数据库访问网关服务 - /// 提供统一的数据库操作接口,管理数据库连接和查询 - /// public class DatabaseGateService { Database _db; @@ -20,10 +17,6 @@ namespace iFileProxy.Services Dictionary _dbDictionary = []; - /// - /// 初始化数据库网关服务 - /// - /// 应用程序配置 public DatabaseGateService(AppConfig appConfig) { _logger.Information("Initializing DatabaseGateService..."); @@ -41,7 +34,7 @@ namespace iFileProxy.Services } /// - /// 加载数据库配置字典 + /// 加载数据库描述字典 /// public void LoadDbDict() { @@ -51,14 +44,13 @@ namespace iFileProxy.Services _logger.Debug($"Db Config: {item.Description} <=> {item.DatabaseName} Loaded."); } } - /// - /// 获取并打开指定数据库的连接 + /// 获取一个指定数据库的连接 /// - /// 数据库描述符,对应配置文件中的 Description 字段 - /// 已打开的数据库连接 - /// 当找不到匹配的数据库配置时抛出 - /// 当必需的配置字段为空时抛出 + /// 数据库描述字段 对应AppConfig的description字段 + /// + /// 若某些不允许为空的字段出现空值 则抛出此异常 + /// public MySqlConnection GetAndOpenDBConn(string db_desc) { if (!_dbDictionary.TryGetValue(db_desc, out DB Db)) @@ -80,19 +72,15 @@ namespace iFileProxy.Services { conn = new MySqlConnection(db_connstr); conn.Open(); + return conn; } catch (Exception ex) { _logger.Fatal($"获取Mysql连接时出现异常:{ex.Message}"); throw; } - return conn; } - /// - /// 测试所有数据库配置的连接 - /// - /// 测试是否全部成功 public bool TestDbConfig() { foreach (var db in _dbDictionary) @@ -118,10 +106,11 @@ namespace iFileProxy.Services } /// - /// 执行查询并返回 JSON 格式的结果 + /// 获取一个json格式的数据表 /// - /// SQL 命令对象 - /// JSON 格式的查询结果 + /// + /// + /// public static string QueryTableData(MySqlCommand sqlCmd) { DataTable dataTable = new(); @@ -131,43 +120,34 @@ namespace iFileProxy.Services } /// - /// 执行查询并返回 JSON 格式的结果 + /// 获取一个json格式的数据表 /// - /// SQL 查询语句 - /// 数据库配置名称 - /// JSON 格式的查询结果 - public string QueryTableData(string sql, string dbConfName) + /// + /// + /// + public string QueryTableData(string sql,string dbConfName) { DataTable dataTable = new(); - using (MySqlDataAdapter adapter = new(new MySqlCommand(sql, GetAndOpenDBConn(dbConfName)))) + using (MySqlDataAdapter adapter = new(new MySqlCommand(sql,GetAndOpenDBConn(dbConfName)))) adapter.Fill(dataTable); return JsonConvert.SerializeObject(dataTable); } /// - /// 执行非查询SQL语句 + /// 内部查询数据专用 当此方法暴露给C端可能造成sql注入等安全问题 /// /// 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; } - /// - /// 检查缓存依赖关系 - /// - /// 任务ID - /// IP地址 - /// 相关的任务信息列表 - public List CheckCacheDependencies(string taskId, string ipAddr) + 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"); @@ -178,7 +158,7 @@ namespace iFileProxy.Services sqlCmd.Parameters.AddWithValue("@status", TaskState.Cached); sqlCmd.Parameters.AddWithValue("@ip_addr", ipAddr); - return JsonConvert.DeserializeObject>(QueryTableData(sqlCmd)); + return JsonConvert.DeserializeObject>( QueryTableData(sqlCmd)); } catch (Exception e) { @@ -188,12 +168,6 @@ namespace iFileProxy.Services finally { conn.Close(); } } - /// - /// 根据状态和IP地址获取任务列表 - /// - /// IP地址 - /// 任务状态 - /// JSON格式的任务列表 public string GetTaskListByStateAndIp(string ipAddr, TaskState status) { string sql = $"SELECT * FROM t_tasks_info WHERE client_ip = @ip_addr AND `status` = @status"; @@ -202,7 +176,7 @@ namespace iFileProxy.Services { using MySqlCommand sqlCmd = new(sql, conn); sqlCmd.Parameters.AddWithValue("@ip_addr", ipAddr); - sqlCmd.Parameters.AddWithValue("@status", status); + sqlCmd.Parameters.AddWithValue ("@status", status); return QueryTableData(sqlCmd); } catch (Exception e) @@ -213,11 +187,7 @@ namespace iFileProxy.Services finally { conn.Close(); } } - /// - /// 根据IP地址获取任务列表 - /// - /// IP地址 - /// JSON格式的任务列表 + public string GetTaskListByIP(string ipAddr) { string sql = $"SELECT * FROM t_tasks_info WHERE client_ip = @ip_addr"; @@ -235,15 +205,9 @@ namespace iFileProxy.Services } finally { conn.Close(); } } - - /// - /// 根据任务ID获取任务信息 - /// - /// 任务ID - /// JSON格式的任务信息 public string GetTaskInfoByTid(string tid) { - string sql = $"SELECT * FROM t_tasks_info WHERE `tid` =@tid"; + string sql = $"SELECT * FROM t_tasks_info WHERE `tid` = @tid"; MySqlConnection conn = GetAndOpenDBConn("iFileProxy_Db"); try { @@ -259,14 +223,6 @@ namespace iFileProxy.Services finally { conn.Close(); } } - /// - /// 查询任务信息 - /// - /// 文件名 - /// URL - /// 文件大小 - /// 任务状态 - /// 任务信息对象,如果未找到则返回null 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"; @@ -295,11 +251,6 @@ namespace iFileProxy.Services } - /// - /// 插入任务数据 - /// - /// 任务信息对象 - /// 是否插入成功 public bool InsertTaskData(TaskInfo taskInfo) { _logger.Debug(System.Text.Json.JsonSerializer.Serialize(taskInfo)); @@ -334,15 +285,7 @@ namespace iFileProxy.Services } return true; } - - /// - /// 更新指定字段的数据 - /// - /// 字段名 - /// 主键值 - /// 更新值 - /// 是否更新成功 - public bool UpdateFieldsData(string fieldsName, string key, string val) + public bool UpdateFieldsData(string fieldsName, string key,string val) { string sql = $"UPDATE t_tasks_info set `{fieldsName}` = @Data WHERE `tid` = @tid"; MySqlConnection conn = GetAndOpenDBConn("iFileProxy_Db"); @@ -370,11 +313,6 @@ namespace iFileProxy.Services } - /// - /// 更新任务状态 - /// - /// 任务信息对象 - /// 是否更新成功 public bool UpdateTaskStatus(TaskInfo taskInfo) { string sql = @"UPDATE t_tasks_info set `status` = @status , update_time = Now() WHERE `tid` = @tid"; @@ -404,21 +342,16 @@ namespace iFileProxy.Services } } - /// - /// 更新任务的哈希值 - /// - /// 任务信息对象 - /// 是否更新成功 public bool UpdateTaskHash(TaskInfo taskInfo) { return UpdateFieldsData("hash", taskInfo.TaskId, MasterHelper.GetFileHash(Path.Combine(_appConfig.DownloadOptions.SavePath, taskInfo.FileName), FileHashAlgorithm.MD5)); } /// - /// 删除指定IP地址的所有任务信息 + /// 删除指定IP的任务信息 /// - /// IP地址 - /// 受影响的行数,-1表示操作失败 + /// + /// public int DeleteTaskInfoByIpAddr(string ipAddr) { try @@ -432,12 +365,6 @@ namespace iFileProxy.Services } } - /// - /// 尝试初始化数据库 - /// - /// - /// 创建必要的数据库表结构 - /// public void TryInitialDB() { string sql = """ @@ -472,14 +399,6 @@ namespace iFileProxy.Services conn.Close(); } } - - /// - /// 执行标量查询 - /// - /// 返回值类型 - /// SQL查询语句 - /// 查询参数 - /// 查询结果 public T ExecuteScalar(string sql, Dictionary parameters) { using var conn = GetAndOpenDBConn("iFileProxy_Db"); @@ -488,16 +407,10 @@ namespace iFileProxy.Services { cmd.Parameters.AddWithValue(param.Key, param.Value); } + return (T)cmd.ExecuteScalar(); } - /// - /// 执行查询并返回实体列表 - /// - /// 实体类型 - /// SQL查询语句 - /// 查询参数 - /// 实体列表 public List ExecuteQuery(string sql, Dictionary parameters) { using var conn = GetAndOpenDBConn("iFileProxy_Db"); @@ -509,12 +422,6 @@ namespace iFileProxy.Services return JsonConvert.DeserializeObject>(QueryTableData(cmd)); } - /// - /// 执行非查询SQL命令 - /// - /// SQL命令 - /// 命令参数 - /// 受影响的行数 public int ExecuteNonQuery(string sql, Dictionary parameters) { using var conn = GetAndOpenDBConn("iFileProxy_Db"); @@ -525,5 +432,147 @@ namespace iFileProxy.Services } return cmd.ExecuteNonQuery(); } + + /// + /// 获取分页的任务列表 + /// + /// 页码,从1开始 + /// 每页数量 + /// 可选的任务状态过滤 + /// 包含分页信息的任务列表 + public PagedResult GetPagedTaskList(int page, int pageSize, TaskState? status = null) + { + // 构建基础SQL + var sql = new StringBuilder("SELECT * FROM t_tasks_info"); + var parameters = new Dictionary(); + + // 添加状态过滤 + if (status.HasValue) + { + sql.Append(" AND status = @status"); + parameters.Add("@status", (int)status.Value); + } + + // 添加分页 + sql.Append(" ORDER BY add_time DESC LIMIT @offset, @limit"); + parameters.Add("@offset", (page - 1) * pageSize); + parameters.Add("@limit", pageSize); + + // 获取总记录数 + var countSql = "SELECT COUNT(*) FROM t_tasks_info" + + (status.HasValue ? " AND status = @status" : ""); + var totalCount = ExecuteScalar(countSql, parameters); + + // 获取分页数据 + var tasks = ExecuteQuery(sql.ToString(), parameters); + + // 返回结果 + return new PagedResult + { + Total = totalCount, + PageSize = pageSize, + CurrentPage = page, + TotalPages = (int)Math.Ceiling(totalCount / (double)pageSize), + Data = tasks + }; + } + + /// + /// 根据任务ID删除任务 + /// + /// 任务ID + /// 是否删除成功 + public bool DeleteTaskByTid(string taskId) + { + try + { + var sql = "DELETE FROM t_tasks_info WHERE tid = @taskId"; + var parameters = new Dictionary + { + { "@taskId", taskId } + }; + + var affectedRows = ExecuteNonQuery(sql, parameters); + return affectedRows > 0; + } + catch (Exception ex) + { + _logger.Error($"删除任务失败 [TaskId: {taskId}]: {ex.Message}"); + return false; + } + } + + /// + /// 获取任务的详细信息 + /// + /// 任务ID + /// 任务详细信息 + public TaskInfo GetTaskDetail(string taskId) + { + try + { + var sql = """ + SELECT * FROM t_tasks_info WHERE tid = @taskId + """; + + var parameters = new Dictionary + { + { "@taskId", taskId } + }; + + var taskInfo = ExecuteQuery(sql, parameters).FirstOrDefault(); + return taskInfo; + } + catch (Exception ex) + { + _logger.Error($"获取任务详情失败 [TaskId: {taskId}]: {ex.Message}"); + throw; + } + } + + /// + /// 获取各状态任务的数量统计 + /// + /// 以任务状态为键,数量为值的字典 + public Dictionary GetTaskStatusOverview() + { + try + { + var sql = """ + SELECT + status, + COUNT(*) as count + FROM t_tasks_info + GROUP BY status + """; + + var result = new Dictionary(); + + // 初始化所有状态的计数为0 + foreach (TaskState state in Enum.GetValues(typeof(TaskState))) + { + result[state] = 0; + } + + // 获取数据库中的实际计数 + using var conn = GetAndOpenDBConn("iFileProxy_Db"); + using var cmd = new MySqlCommand(sql, conn); + using var reader = cmd.ExecuteReader(); + + while (reader.Read()) + { + var status = (TaskState)reader.GetInt32("status"); + var count = reader.GetInt32("count"); + result[status] = count; + } + + return result; + } + catch (Exception ex) + { + _logger.Error($"获取任务状态统计失败: {ex.Message}"); + throw; + } + } } } diff --git a/src/Services/TaskManager.cs b/src/Services/TaskManager.cs index 3365009..c44c169 100644 --- a/src/Services/TaskManager.cs +++ b/src/Services/TaskManager.cs @@ -39,6 +39,7 @@ namespace iFileProxy.Services add_task_num = _pendingTasks.Count; for (int i = 0; i < add_task_num; i++) // 运行的任务中不足最大并行数并且有正在队列的task时候添加足够多的任务 { + // 获取下一个任务信息 TaskInfo nextTask = _pendingTasks.Dequeue(); Task.Run(() => StartTaskAsync(nextTask)).ConfigureAwait(false); } @@ -190,7 +191,7 @@ namespace iFileProxy.Services /// /// 任务信息 /// - public async Task StartTaskAsync(TaskInfo taskInfo) + public async Task StartTaskAsync(TaskInfo taskInfo, bool OnTaskCompletedEvent = true) { if (_runningTasks.ContainsKey(taskInfo.TaskId)) { @@ -219,7 +220,7 @@ namespace iFileProxy.Services aria2c.Start(); _runningTasks[taskInfo.TaskId].Process = aria2c; if (taskInfo.Status != TaskState.Running) - { + { taskInfo.Status = TaskState.Running; _dbGateService.UpdateTaskStatus(taskInfo); } @@ -245,15 +246,23 @@ namespace iFileProxy.Services taskInfo.Hash = MasterHelper.GetFileHash(Path.Combine(_appConfig.DownloadOptions.SavePath, taskInfo.FileName), FileHashAlgorithm.MD5); _dbGateService.UpdateTaskHash(taskInfo); } - - // 触发任务完成事件 - OnTaskCompleted(taskInfo); } catch (Exception ex) { _logger.Fatal($"执行下载任务时候出现致命问题: {ex.Message}"); + taskInfo.Status= TaskState.Error; + + _dbGateService.UpdateTaskStatus(taskInfo); throw; } + finally + { + if (OnTaskCompletedEvent) + { + // 触发任务完成事件 + OnTaskCompleted(taskInfo); + } + } } @@ -301,7 +310,7 @@ namespace iFileProxy.Services /// 获取task在队列中的位置 /// /// - /// + /// 成功时返回队列中的位置 失败时返回-1 public int GetQueuePosition(string taskId) { int position = -1; // 默认值表示未找到 @@ -332,11 +341,6 @@ namespace iFileProxy.Services return new ServerTaskLoadInfo { Queuing = _pendingTasks.Count , Running = _runningTasks.Count}; } - /// - /// 尝试中断指定的任务 - /// - /// 要中断的任务信息 - /// 是否成功中断任务 public void TryKillTask(TaskInfo taskInfo) { try @@ -349,7 +353,7 @@ namespace iFileProxy.Services // 从运行任务字典中移除 _runningTasks.Remove(taskInfo.TaskId); - // 更新任务状态为错误 + // 更新任务状态为已取消 taskInfo.Status = TaskState.Canceled; _dbGateService.UpdateTaskStatus(taskInfo); @@ -358,7 +362,7 @@ namespace iFileProxy.Services if (File.Exists(filePath)) { File.Delete(filePath); - _logger.Debug($"已删除未完成的文件: {filePath}"); + _logger.Debug($"已删除未下载完成的文件: {filePath}"); } // 删除aria2c的临时文件 @@ -369,6 +373,7 @@ namespace iFileProxy.Services } _logger.Information($"任务 {taskInfo.TaskId} 已中断"); + OnTaskCompleted(taskInfo); } else { @@ -382,6 +387,243 @@ namespace iFileProxy.Services } } + /// + /// 获取任务的进程详细信息 + /// + /// 任务ID + /// 进程详细信息,如果任务不存在或未运行则返回null + public ProcessInfo? GetProcessInfo(string taskId) + { + try + { + if (!_runningTasks.TryGetValue(taskId, out var taskInfo) || taskInfo.Process == null) + { + return null; + } + + var process = taskInfo.Process; + // 刷新进程信息 + process.Refresh(); + + var processInfo = new ProcessInfo + { + ProcessId = process.Id, + ProcessName = process.ProcessName, + StartTime = process.StartTime, + RunningTime = (DateTime.Now - process.StartTime).TotalSeconds, + ThreadCount = process.Threads.Count, + PriorityClass = process.PriorityClass, + WorkingDirectory = process.StartInfo.WorkingDirectory, + CommandLine = process.StartInfo.Arguments + }; + + // 获取线程信息 + foreach (ProcessThread thread in process.Threads) + { + try + { + processInfo.Threads.Add(new ThreadInfo + { + ThreadId = thread.Id, + ThreadState = thread.ThreadState, + Priority = thread.PriorityLevel + }); + } + catch (Exception ex) + { + _logger.Warning($"获取线程信息失败: {ex.Message}"); + // 继续处理下一个线程 + continue; + } + } + + return processInfo; + } + catch (Exception ex) + { + _logger.Error($"获取进程信息失败: {ex.Message}"); + return null; + } + } + + /// + /// 重试失败的任务 + /// + /// 任务ID + /// 重试是否成功 + public bool RetryTask(string taskId) + { + try + { + var taskInfo = JsonSerializer.Deserialize>(_dbGateService.GetTaskInfoByTid(taskId))?[0]; + if (taskInfo == null) + { + _logger.Warning($"任务不存在: {taskId}"); + return false; + } + + // 检查任务状态是否为失败状态 + if (taskInfo.Status != TaskState.Error && taskInfo.Status != TaskState.Canceled) + { + _logger.Warning($"任务 {taskId} 状态为 {taskInfo.Status},不能重试"); + return false; + } + + // 重置任务状态 + taskInfo.Status = TaskState.NoInit; + taskInfo.UpdateTime = DateTime.Now; + _dbGateService.UpdateTaskStatus(taskInfo); + + lock (_taskLock) + { + // 如果有等待的任务或达到最大并行数,加入队列 + if (_pendingTasks.Count > 0 || _runningTasks.Count >= _appConfig.DownloadOptions.MaxParallelTasks) + { + _pendingTasks.Enqueue(taskInfo); + taskInfo.Status = TaskState.Queuing; + _dbGateService.UpdateTaskStatus(taskInfo); + _logger.Information($"任务 {taskId} 已加入等待队列"); + } + else + { + // 直接启动任务 + Task.Run(() => StartTaskAsync(taskInfo)).ConfigureAwait(false); + } + } + + return true; + } + catch (Exception ex) + { + _logger.Error($"重试任务失败: {ex.Message}"); + return false; + } + } + + /// + /// 将任务移动到等待队列最前面 + /// + /// 任务ID + /// 是否成功 + public bool PrioritizeTask(string taskId) + { + try + { + lock (_taskLock) + { + // 如果队列为空,无需处理 + if (_pendingTasks.Count == 0) + { + _logger.Warning("等待队列为空,无法进行优先级调整"); + return false; + } + + // 找到指定任务 + var tempQueue = new Queue(); + TaskInfo? targetTask = null; + + while (_pendingTasks.Count > 0) + { + var task = _pendingTasks.Dequeue(); + if (task.TaskId == taskId) + { + targetTask = task; + } + else + { + tempQueue.Enqueue(task); + } + } + + // 如果找到目标任务,将其放到新队列的最前面 + if (targetTask != null) + { + _pendingTasks.Enqueue(targetTask); + while (tempQueue.Count > 0) + { + _pendingTasks.Enqueue(tempQueue.Dequeue()); + } + _logger.Information($"任务 {taskId} 已移至队列首位"); + return true; + } + else + { + // 如果没找到,还原队列 + while (tempQueue.Count > 0) + { + _pendingTasks.Enqueue(tempQueue.Dequeue()); + } + _logger.Warning($"未在等待队列中找到任务: {taskId}"); + return false; + } + } + } + catch (Exception ex) + { + _logger.Error($"调整任务优先级失败: {ex.Message}"); + return false; + } + } + + /// + /// 立即执行指定任务 + /// + /// 任务ID + /// 是否成功 + public bool ExecuteImmediately(string taskId) + { + try + { + lock (_taskLock) + { + // 找到指定任务 + var tempQueue = new Queue(); + TaskInfo? targetTask = null; + + while (_pendingTasks.Count > 0) + { + var task = _pendingTasks.Dequeue(); + if (task.TaskId == taskId) + { + targetTask = task; + } + else + { + tempQueue.Enqueue(task); + } + } + + // 还原其他任务到队列 + while (tempQueue.Count > 0) + { + _pendingTasks.Enqueue(tempQueue.Dequeue()); + } + + // 如果找到目标任务 + if (targetTask != null) + { + _logger.Information($"正在立即执行任务: {taskId}"); + + + // 启动任务 + Task.Run(() => StartTaskAsync(targetTask, false)).ConfigureAwait(false); + return true; + } + else + { + _logger.Warning($"未在等待队列中找到任务: {taskId}"); + return false; + } + } + } + catch (Exception ex) + { + _logger.Error($"立即执行任务失败: {ex.Message}"); + return false; + } + } + + //public bool DeleteTask(HttpContext c) //{ diff --git a/src/iFileProxy.csproj b/src/iFileProxy.csproj index 6d9b078..f030dee 100644 --- a/src/iFileProxy.csproj +++ b/src/iFileProxy.csproj @@ -13,7 +13,7 @@ - +