支持任务队列
This commit is contained in:
parent
e530cb46fe
commit
2327a7ec3e
11 changed files with 259 additions and 81 deletions
|
@ -76,6 +76,7 @@ namespace iFileProxy.Config
|
||||||
public uint ThreadNum { get; set; } = 1;
|
public uint ThreadNum { get; set; } = 1;
|
||||||
public uint MaxAllowedFileSize { get; set; }
|
public uint MaxAllowedFileSize { get; set; }
|
||||||
public uint MaxParallelTasks { get; set; } = 4;
|
public uint MaxParallelTasks { get; set; } = 4;
|
||||||
|
public uint MaxQueueLength {get; set; } = 60;
|
||||||
public string Aria2cPath { get; set; } = "./bin/aria2c";
|
public string Aria2cPath { get; set; } = "./bin/aria2c";
|
||||||
public int CacheLifetime { get; set; } = 3600;
|
public int CacheLifetime { get; set; } = 3600;
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,8 @@ namespace iFileProxy.Controllers
|
||||||
TaskAddState.ErrIPForbidden => (ActionResult<CommonRsp>)Ok(new CommonRsp() { retcode = (int)TaskAddState.ErrIPForbidden, message = "请求次数超过限制!" }),
|
TaskAddState.ErrIPForbidden => (ActionResult<CommonRsp>)Ok(new CommonRsp() { retcode = (int)TaskAddState.ErrIPForbidden, message = "请求次数超过限制!" }),
|
||||||
TaskAddState.ErrTargetHostForbidden => (ActionResult<CommonRsp>)Ok(new CommonRsp() { retcode = (int)TaskAddState.ErrTargetHostForbidden, message = "目标主机不在服务白名单内!" }),
|
TaskAddState.ErrTargetHostForbidden => (ActionResult<CommonRsp>)Ok(new CommonRsp() { retcode = (int)TaskAddState.ErrTargetHostForbidden, message = "目标主机不在服务白名单内!" }),
|
||||||
TaskAddState.ErrGetFileInfo => (ActionResult<CommonRsp>)Ok(new CommonRsp() { retcode = (int)TaskAddState.ErrGetFileInfo, message = "目标文件信息获取失败!" }),
|
TaskAddState.ErrGetFileInfo => (ActionResult<CommonRsp>)Ok(new CommonRsp() { retcode = (int)TaskAddState.ErrGetFileInfo, message = "目标文件信息获取失败!" }),
|
||||||
|
TaskAddState.ErrQueueLengthLimit => (ActionResult<CommonRsp>)Ok(new CommonRsp() { retcode = (int)TaskAddState.ErrQueueLengthLimit, message = "服务器任务队列已满 请稍候重试!" }),
|
||||||
|
TaskAddState.Pending => (ActionResult<CommonRsp>)Ok(new CommonRsp() { retcode = (int)TaskAddState.Pending, message = "已经添加到任务队列!" }),
|
||||||
|
|
||||||
_ => (ActionResult<CommonRsp>)Ok(new CommonRsp() { retcode = (int)TaskAddState.Success, message = "succ default" }),
|
_ => (ActionResult<CommonRsp>)Ok(new CommonRsp() { retcode = (int)TaskAddState.Success, message = "succ default" }),
|
||||||
};
|
};
|
||||||
|
@ -42,7 +44,15 @@ namespace iFileProxy.Controllers
|
||||||
[Route("/GetMyTasks")]
|
[Route("/GetMyTasks")]
|
||||||
public ActionResult<CommonRsp> GetMyTasks()
|
public ActionResult<CommonRsp> GetMyTasks()
|
||||||
{
|
{
|
||||||
return Ok(new CommonRsp() { retcode = 0, data = taskManager.GetTaskListByIpAddr(HttpContext),message = "succ" });
|
var data = taskManager.GetTaskListByIpAddr(HttpContext);
|
||||||
|
foreach (var d in data)
|
||||||
|
{
|
||||||
|
if (d.Status == TaskState.Queuing)
|
||||||
|
{
|
||||||
|
d.QueuePosition = taskManager.GetQueuePosition(d.TaskId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Ok(new CommonRsp() { retcode = 0, data = data ,message = "succ" });
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
|
|
|
@ -88,7 +88,5 @@ namespace iFileProxy.Helpers
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,6 +48,10 @@ namespace iFileProxy.Models
|
||||||
[JsonProperty("tag")]
|
[JsonProperty("tag")]
|
||||||
[JsonPropertyName("tag")]
|
[JsonPropertyName("tag")]
|
||||||
public string Tag { get; set; }
|
public string Tag { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("queue_position")]
|
||||||
|
[JsonPropertyName("queue_position")]
|
||||||
|
public int QueuePosition { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class DbConfigName {
|
public class DbConfigName {
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
End = 3, // 任务正常结束
|
End = 3, // 任务正常结束
|
||||||
Cached = 4, // 要下载的内容已经缓存
|
Cached = 4, // 要下载的内容已经缓存
|
||||||
Cleaned =5, // 内容过期已被清理
|
Cleaned =5, // 内容过期已被清理
|
||||||
|
Queuing = 6, // 正在排队
|
||||||
}
|
}
|
||||||
public enum TaskAddState {
|
public enum TaskAddState {
|
||||||
Success = 0,
|
Success = 0,
|
||||||
|
@ -20,7 +21,9 @@
|
||||||
ErrTargetHostForbidden = 8,
|
ErrTargetHostForbidden = 8,
|
||||||
ErrFileNameForbidden = 9,
|
ErrFileNameForbidden = 9,
|
||||||
ErrIPForbidden = 10,
|
ErrIPForbidden = 10,
|
||||||
ErrGetFileInfo = 11
|
ErrGetFileInfo = 11,
|
||||||
|
Pending = 12,
|
||||||
|
ErrQueueLengthLimit = 13,
|
||||||
}
|
}
|
||||||
public enum FileHashAlgorithm
|
public enum FileHashAlgorithm
|
||||||
{
|
{
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
{
|
{
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using Serilog.Events;
|
using Serilog.Events;
|
||||||
using Serilog.Filters;
|
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
|
||||||
public static class SerilogConfig
|
public static class SerilogConfig
|
||||||
|
@ -12,7 +11,11 @@
|
||||||
var filePath = Path.Combine(AppContext.BaseDirectory, $"logs/dispatch.api.log");
|
var filePath = Path.Combine(AppContext.BaseDirectory, $"logs/dispatch.api.log");
|
||||||
|
|
||||||
Log.Logger = new LoggerConfiguration()
|
Log.Logger = new LoggerConfiguration()
|
||||||
|
#if RELEASE
|
||||||
|
.MinimumLevel.Information()
|
||||||
|
#else
|
||||||
.MinimumLevel.Debug()
|
.MinimumLevel.Debug()
|
||||||
|
#endif
|
||||||
.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()
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
using iFileProxy.Config;
|
using iFileProxy.Config;
|
||||||
using iFileProxy.Helpers;
|
using iFileProxy.Helpers;
|
||||||
using iFileProxy.Models;
|
using iFileProxy.Models;
|
||||||
|
using Org.BouncyCastle.Asn1.Tsp;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Security.Policy;
|
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
|
||||||
namespace iFileProxy.Services
|
namespace iFileProxy.Services
|
||||||
|
@ -13,10 +13,48 @@ namespace iFileProxy.Services
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class TaskManager
|
public class TaskManager
|
||||||
{
|
{
|
||||||
|
// 定义事件
|
||||||
|
public event EventHandler<TaskInfo>? TaskCompleted;
|
||||||
|
|
||||||
|
protected virtual void OnTaskCompleted(TaskInfo taskInfo)
|
||||||
|
{
|
||||||
|
EventHandler<TaskInfo>? handler = TaskCompleted; // 创建事件的副本
|
||||||
|
handler?.Invoke(this, taskInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 事件处理程序:任务完成后调度下一任务
|
||||||
|
private void HandleTaskCompleted(object? sender, TaskInfo taskInfo)
|
||||||
|
{
|
||||||
|
_logger.Debug($"[TaskId: {taskInfo.TaskId}] End.");
|
||||||
|
_logger.Information($"Running Task Num: {_runningTasks.Count}");
|
||||||
|
_logger.Information($"Queue Task Num: {_pendingTasks.Count}");
|
||||||
|
|
||||||
|
// 等待队列中有内容并且当前正在运行的任务小于最大并行任务
|
||||||
|
if (_pendingTasks.Count > 0 && _runningTasks.Count < _appConfig.DownloadOptions.MaxParallelTasks)
|
||||||
|
{
|
||||||
|
lock (_taskLock) // 线程安全
|
||||||
|
{
|
||||||
|
int add_task_num = (int)(_appConfig.DownloadOptions.MaxParallelTasks - _runningTasks.Count);
|
||||||
|
if (add_task_num > _pendingTasks.Count)
|
||||||
|
add_task_num = _pendingTasks.Count;
|
||||||
|
for (int i = 0; i < add_task_num; i++) // 运行的任务中不足最大并行数并且有正在队列的task时候添加足够多的任务
|
||||||
|
{
|
||||||
|
TaskInfo nextTask = _pendingTasks.Dequeue();
|
||||||
|
Task.Run(() => StartTask(nextTask)).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private readonly static Serilog.ILogger _logger = Log.Logger.ForContext<TaskManager>();
|
private readonly static Serilog.ILogger _logger = Log.Logger.ForContext<TaskManager>();
|
||||||
private readonly AppConfig? _appConfig = AppConfig.GetCurrConfig("iFileProxy.json");
|
private readonly AppConfig? _appConfig = AppConfig.GetCurrConfig("iFileProxy.json");
|
||||||
private readonly DatabaseHelper _dbHelper;
|
private readonly DatabaseHelper _dbHelper;
|
||||||
private Dictionary<string, TaskInfo> runningTasks = [];
|
private Dictionary<string, TaskInfo> _runningTasks = [];
|
||||||
|
private Queue<TaskInfo> _pendingTasks = new();
|
||||||
|
private readonly object _taskLock = new();
|
||||||
|
|
||||||
public TaskManager()
|
public TaskManager()
|
||||||
{
|
{
|
||||||
_logger.Information("Initializing TaskManager...");
|
_logger.Information("Initializing TaskManager...");
|
||||||
|
@ -27,6 +65,11 @@ namespace iFileProxy.Services
|
||||||
_logger.Fatal($"Failed to load application configuration");
|
_logger.Fatal($"Failed to load application configuration");
|
||||||
Environment.Exit(1);
|
Environment.Exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 绑定任务完成事件的处理程序
|
||||||
|
TaskCompleted -= HandleTaskCompleted;
|
||||||
|
TaskCompleted += HandleTaskCompleted;
|
||||||
|
|
||||||
_logger.Information("TaskManager init succ.");
|
_logger.Information("TaskManager init succ.");
|
||||||
}
|
}
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -39,10 +82,18 @@ namespace iFileProxy.Services
|
||||||
string? clientIp = MasterHelper.GetClientIPAddr(c);
|
string? clientIp = MasterHelper.GetClientIPAddr(c);
|
||||||
string? t_url = c.Request.Query["url"].FirstOrDefault() ?? c.Request.Form["url"].FirstOrDefault();
|
string? t_url = c.Request.Query["url"].FirstOrDefault() ?? c.Request.Form["url"].FirstOrDefault();
|
||||||
|
|
||||||
if (_appConfig.DownloadOptions.MaxParallelTasks != 0 && runningTasks.Count >= _appConfig.DownloadOptions.MaxParallelTasks)
|
bool queue_task = false;
|
||||||
return TaskAddState.ErrMaxParallelTasksLimit;
|
|
||||||
|
|
||||||
if (runningTasks.Where(x => x.Value.Url == t_url).Any())
|
// 如果当前并行任务量已经达到设定并行任务和列队上限
|
||||||
|
if (_appConfig.DownloadOptions.MaxParallelTasks != 0 && _runningTasks.Count >= _appConfig.DownloadOptions.MaxParallelTasks)
|
||||||
|
{
|
||||||
|
if (_pendingTasks.Count >= _appConfig.DownloadOptions.MaxQueueLength)
|
||||||
|
return TaskAddState.ErrMaxParallelTasksLimit;
|
||||||
|
else
|
||||||
|
queue_task = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_runningTasks.Values.Any(x => x.Url == t_url))
|
||||||
return TaskAddState.ErrUrlRepeat;
|
return TaskAddState.ErrUrlRepeat;
|
||||||
|
|
||||||
if (!MasterHelper.CheckUrlIsValid(t_url))
|
if (!MasterHelper.CheckUrlIsValid(t_url))
|
||||||
|
@ -73,6 +124,19 @@ namespace iFileProxy.Services
|
||||||
UpdateTime = DateTime.Now
|
UpdateTime = DateTime.Now
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 如果是等待中任务或者列队不是空
|
||||||
|
if (queue_task || _pendingTasks.Count != 0) // 判断一下队列长度 防止被插队
|
||||||
|
{
|
||||||
|
lock(_taskLock)
|
||||||
|
if (_pendingTasks.Count >= _appConfig.DownloadOptions.MaxQueueLength)
|
||||||
|
return TaskAddState.ErrQueueLengthLimit;
|
||||||
|
|
||||||
|
_pendingTasks.Enqueue(taskInfo); // 加入等待队列
|
||||||
|
var status = AddTaskInfoToDb(taskInfo,true);
|
||||||
|
_logger.Information($"[TaskId: {taskInfo.TaskId}] Queuing...");
|
||||||
|
if (status != TaskAddState.Success) { return status; }
|
||||||
|
return TaskAddState.Pending;
|
||||||
|
}
|
||||||
|
|
||||||
if (MasterHelper.CheckDownloadFileExists(taskInfo.FileName))
|
if (MasterHelper.CheckDownloadFileExists(taskInfo.FileName))
|
||||||
{
|
{
|
||||||
|
@ -85,30 +149,48 @@ namespace iFileProxy.Services
|
||||||
taskInfo.Tag = $"REDIRECT:{r.TaskId}";
|
taskInfo.Tag = $"REDIRECT:{r.TaskId}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return AddTaskInfoToDb(taskInfo);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public TaskAddState AddTaskInfoToDb(TaskInfo taskInfo, bool queuing = false)
|
||||||
|
{
|
||||||
if (_dbHelper.InsertTaskData(taskInfo))
|
if (_dbHelper.InsertTaskData(taskInfo))
|
||||||
|
{
|
||||||
|
if (!queuing) // 如果不是正在排队的任务
|
||||||
{
|
{
|
||||||
if (taskInfo.Status != TaskState.Cached)
|
if (taskInfo.Status != TaskState.Cached)
|
||||||
{
|
{
|
||||||
StartTask(taskInfo);
|
StartTask(taskInfo);
|
||||||
taskInfo.Status = TaskState.Running;
|
taskInfo.Status = TaskState.Running;
|
||||||
_dbHelper.UpdateTaskStatus(taskInfo);
|
|
||||||
}
|
}
|
||||||
_logger.Debug("任务添加成功.");
|
_logger.Debug($"[TaskId: {taskInfo.TaskId}] Add to Database Successful.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
taskInfo.Status = TaskState.Queuing;
|
||||||
|
}
|
||||||
|
_dbHelper.UpdateTaskStatus(taskInfo);
|
||||||
|
|
||||||
return TaskAddState.Success;
|
return TaskAddState.Success;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
return TaskAddState.ErrDbFail;
|
return TaskAddState.ErrDbFail;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async void StartTask(TaskInfo taskInfo)
|
public async Task StartTask(TaskInfo taskInfo)
|
||||||
{
|
{
|
||||||
if (runningTasks.ContainsKey(taskInfo.TaskId))
|
if (_runningTasks.ContainsKey(taskInfo.TaskId))
|
||||||
{
|
{
|
||||||
_logger.Error($"指定的task已经存在!!!");
|
_logger.Error($"指定的task已经存在!!!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Process aria2c = new();
|
|
||||||
aria2c.StartInfo = new ProcessStartInfo
|
Process aria2c = new()
|
||||||
|
{
|
||||||
|
StartInfo = new ProcessStartInfo
|
||||||
{
|
{
|
||||||
FileName = _appConfig.DownloadOptions.Aria2cPath,
|
FileName = _appConfig.DownloadOptions.Aria2cPath,
|
||||||
WorkingDirectory = _appConfig.DownloadOptions.SavePath,
|
WorkingDirectory = _appConfig.DownloadOptions.SavePath,
|
||||||
|
@ -118,17 +200,26 @@ namespace iFileProxy.Services
|
||||||
RedirectStandardInput = true,
|
RedirectStandardInput = true,
|
||||||
UseShellExecute = false,
|
UseShellExecute = false,
|
||||||
Environment = { { "TaskId", taskInfo.TaskId } }
|
Environment = { { "TaskId", taskInfo.TaskId } }
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
_runningTasks.Add(taskInfo.TaskId, taskInfo);
|
||||||
aria2c.Start();
|
aria2c.Start();
|
||||||
|
if (taskInfo.Status != TaskState.Running)
|
||||||
|
{
|
||||||
|
taskInfo.Status = TaskState.Running;
|
||||||
|
_dbHelper.UpdateTaskStatus(taskInfo);
|
||||||
|
}
|
||||||
|
_logger.Information($"[TaskId: {taskInfo.TaskId}] Started.");
|
||||||
aria2c.BeginOutputReadLine();
|
aria2c.BeginOutputReadLine();
|
||||||
aria2c.BeginErrorReadLine();
|
aria2c.BeginErrorReadLine();
|
||||||
aria2c.OutputDataReceived += Aria2c_OutputDataReceived;
|
aria2c.OutputDataReceived += Aria2c_OutputDataReceived;
|
||||||
aria2c.ErrorDataReceived += Aria2c_ErrorDataReceived;
|
aria2c.ErrorDataReceived += Aria2c_ErrorDataReceived;
|
||||||
runningTasks.Add(taskInfo.TaskId, taskInfo);
|
|
||||||
await aria2c.WaitForExitAsync();
|
await aria2c.WaitForExitAsync();
|
||||||
runningTasks.Remove(taskInfo.TaskId);
|
_runningTasks.Remove(taskInfo.TaskId);
|
||||||
|
|
||||||
if (aria2c.ExitCode != 0)
|
if (aria2c.ExitCode != 0)
|
||||||
{
|
{
|
||||||
|
@ -143,14 +234,18 @@ namespace iFileProxy.Services
|
||||||
taskInfo.Hash = MasterHelper.GetFileHash(Path.Combine(_appConfig.DownloadOptions.SavePath, taskInfo.FileName), FileHashAlgorithm.MD5);
|
taskInfo.Hash = MasterHelper.GetFileHash(Path.Combine(_appConfig.DownloadOptions.SavePath, taskInfo.FileName), FileHashAlgorithm.MD5);
|
||||||
_dbHelper.UpdateTaskHash(taskInfo);
|
_dbHelper.UpdateTaskHash(taskInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 触发任务完成事件
|
||||||
|
OnTaskCompleted(taskInfo);
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.Fatal("执行下载任务时候出现致命问题");
|
_logger.Fatal($"执行下载任务时候出现致命问题: {ex.Message}");
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void Aria2c_ErrorDataReceived(object sender, DataReceivedEventArgs e)
|
private void Aria2c_ErrorDataReceived(object sender, DataReceivedEventArgs e)
|
||||||
{
|
{
|
||||||
if (e.Data == null || e.Data.Trim() == "")
|
if (e.Data == null || e.Data.Trim() == "")
|
||||||
|
@ -179,22 +274,31 @@ namespace iFileProxy.Services
|
||||||
return JsonSerializer.Deserialize<List<TaskInfo>>(_dbHelper.GetTaskInfoByTid(taskId)) ?? [];
|
return JsonSerializer.Deserialize<List<TaskInfo>>(_dbHelper.GetTaskInfoByTid(taskId)) ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Dictionary<string, TaskInfo> GetRunningTasks() => _runningTasks;
|
||||||
|
|
||||||
|
public int GetQueuePosition(string taskId)
|
||||||
|
{
|
||||||
|
int position = -1; // 默认值表示未找到
|
||||||
|
int index = 0;
|
||||||
|
|
||||||
|
Queue<TaskInfo> tempQueue = new( _pendingTasks);
|
||||||
|
|
||||||
|
while (tempQueue.Count > 0)
|
||||||
|
{
|
||||||
|
TaskInfo current = tempQueue.Dequeue();
|
||||||
|
if (current.TaskId == taskId)
|
||||||
|
{
|
||||||
|
position = index;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return position;
|
||||||
|
}
|
||||||
//public bool DeleteTask(HttpContext c)
|
//public bool DeleteTask(HttpContext c)
|
||||||
//{
|
//{
|
||||||
|
|
||||||
//}
|
//}
|
||||||
|
|
||||||
//public bool UpdateTaskStatus(HttpContext c)
|
|
||||||
//{
|
|
||||||
//}
|
|
||||||
//public List<TaskInfo> GetAllTaskInfo(HttpContext c)
|
|
||||||
//{
|
|
||||||
|
|
||||||
//}
|
|
||||||
|
|
||||||
//public TaskInfo GetTaskInfo(HttpContext c)
|
|
||||||
//{
|
|
||||||
|
|
||||||
//}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
"ThreadNum": 4, // 下载线程数
|
"ThreadNum": 4, // 下载线程数
|
||||||
"MaxAllowedFileSize": 65536, // 允许代理的最大文件尺寸
|
"MaxAllowedFileSize": 65536, // 允许代理的最大文件尺寸
|
||||||
"MaxParallelTasks": 4, // 同一时间最大并行任务数
|
"MaxParallelTasks": 4, // 同一时间最大并行任务数
|
||||||
|
"MaxQueueLength": 60, // 最大等待队列长度
|
||||||
"Aria2cPath": "./lib/aria2c",
|
"Aria2cPath": "./lib/aria2c",
|
||||||
"CacheLifetime": 3600 // 缓存生命周期(秒) 超出此范围的缓存文件将被删除
|
"CacheLifetime": 3600 // 缓存生命周期(秒) 超出此范围的缓存文件将被删除
|
||||||
},
|
},
|
||||||
|
|
|
@ -14,12 +14,13 @@
|
||||||
|
|
||||||
<div class="container mt-5">
|
<div class="container mt-5">
|
||||||
<div class="row justify-content-center">
|
<div class="row justify-content-center">
|
||||||
|
<div id="loading-mask" style="display: none;">
|
||||||
|
<div class="loading-spinner"></div>
|
||||||
|
<p class="loading-text">数据提交中,请稍等...</p>
|
||||||
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div id="from_data" class="card-body">
|
<div id="from_data" class="card-body">
|
||||||
<div id="loading-mask" style="display: none;">
|
|
||||||
<div class="loading-spinner"><p>加载中</p></div>
|
|
||||||
</div>
|
|
||||||
<h5 class="card-title text-center">Github文件下载加速</h5>
|
<h5 class="card-title text-center">Github文件下载加速</h5>
|
||||||
|
|
||||||
<!-- URL 输入框 -->
|
<!-- URL 输入框 -->
|
||||||
|
|
|
@ -30,9 +30,6 @@
|
||||||
|
|
||||||
.table {
|
.table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 100%;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
background-color: transparent;
|
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
border: 1px solid #dee2e6;
|
border: 1px solid #dee2e6;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
|
@ -41,21 +38,21 @@
|
||||||
|
|
||||||
.table th,
|
.table th,
|
||||||
.table td {
|
.table td {
|
||||||
padding: 15px;
|
padding: 10px;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
border: 1px solid #dee2e6;
|
border: 1px solid #dee2e6;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
max-width: 256px;
|
|
||||||
/* 设置最大宽度 */
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.table thead th {
|
.table th {
|
||||||
background-color: #007bff;
|
background-color: #007bff;
|
||||||
color: white;
|
color: white;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table td {
|
||||||
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table tbody tr:nth-child(even) {
|
.table tbody tr:nth-child(even) {
|
||||||
|
@ -66,40 +63,85 @@
|
||||||
background-color: #e2e6ea;
|
background-color: #e2e6ea;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hidden-on-small {
|
||||||
|
display: table-cell;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
|
|
||||||
|
.table th,
|
||||||
|
.table td {
|
||||||
|
font-size: 0.9em;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden-on-small {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table td {
|
||||||
|
white-space: normal;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.statusData {
|
||||||
|
width: 20%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 576px) {
|
||||||
|
|
||||||
|
.table th,
|
||||||
|
.table td {
|
||||||
|
font-size: 0.8em;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
.table-responsive {
|
.table-responsive {
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table th,
|
|
||||||
.table td {
|
|
||||||
padding: 10px;
|
|
||||||
font-size: 0.9em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.hidden-on-small {
|
@media (min-width: 1200px) {
|
||||||
display: none !important;
|
.timeData {
|
||||||
|
width: 16%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.hashData {
|
||||||
|
width: 25%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.containerCustom {
|
||||||
|
max-width: 95%
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container containerCustom">
|
||||||
<h1 class="text-center">离线下载任务管理</h1>
|
<h1 class="text-center">离线下载任务管理</h1>
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<div id="loading-mask" style="display: none;">
|
<div id="loading-mask" style="display: none;">
|
||||||
<div class="loading-spinner"></div>
|
<div class="loading-spinner"></div>
|
||||||
|
<p class="loading-text">数据加载中,请稍等...</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<table id="data-table" class="table table-striped table-bordered">
|
<table id="data-table" class="table table-striped table-bordered">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>文件名</th>
|
<th>文件名</th>
|
||||||
<th class="hidden-on-small">大小</th>
|
<th style="width: 10%" class="hidden-on-small">大小</th>
|
||||||
<th>提交时间</th>
|
<th class="timeData">提交时间</th>
|
||||||
<th>状态</th>
|
<th class="timeData hidden-on-small">结束时间</th>
|
||||||
<th class="hidden-on-small">哈希</th>
|
|
||||||
|
<th class="statusData">状态</th>
|
||||||
|
<th class="hidden-on-small hashData">哈希</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="taskTableBody">
|
<tbody id="taskTableBody">
|
||||||
|
@ -107,11 +149,9 @@
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<p class="more-content"><a href="/index.html">返回主页</a> | 捐赠</p>
|
<p class="more-content"><a href="/index.html">返回主页</a> | 捐赠</p>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 优先加载jq一类的三方库 -->
|
|
||||||
<script src="static/js/bootstarp/5/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
|
<script src="static/js/bootstarp/5/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
|
||||||
<script src="static/js/jquery/2.1.4/jquery.min.js"></script>
|
<script src="static/js/jquery/2.1.4/jquery.min.js"></script>
|
||||||
<script src="static/js/custom/Common.js"></script>
|
<script src="static/js/custom/Common.js"></script>
|
||||||
|
@ -124,8 +164,9 @@
|
||||||
[2, "错误"],
|
[2, "错误"],
|
||||||
[3, "已完成"],
|
[3, "已完成"],
|
||||||
[4, "已缓存"],
|
[4, "已缓存"],
|
||||||
[5, "已被清理"]
|
[5, "已被清理"],
|
||||||
])
|
[6, "排队中"]
|
||||||
|
]);
|
||||||
data = [];
|
data = [];
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: "GET",
|
type: "GET",
|
||||||
|
@ -133,18 +174,18 @@
|
||||||
dataType: "json",
|
dataType: "json",
|
||||||
success: function (response) {
|
success: function (response) {
|
||||||
hideLoadingMask();
|
hideLoadingMask();
|
||||||
if (response.retcode == 0) {
|
if (response.retcode === 0) {
|
||||||
data = response.data;
|
data = response.data;
|
||||||
populateTable(data);
|
populateTable(data);
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
alert(response.message);
|
alert(response.message);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
error(xhr, status, error) {
|
error(xhr, status, error) {
|
||||||
alert(error);
|
alert(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function formatBytes(bytes, decimals = 2) {
|
function formatBytes(bytes, decimals = 2) {
|
||||||
if (bytes === 0) return '0 Bytes';
|
if (bytes === 0) return '0 Bytes';
|
||||||
const k = 1024;
|
const k = 1024;
|
||||||
|
@ -161,16 +202,16 @@
|
||||||
data.forEach(item => {
|
data.forEach(item => {
|
||||||
const row = document.createElement('tr');
|
const row = document.createElement('tr');
|
||||||
row.innerHTML = `
|
row.innerHTML = `
|
||||||
<td>${item.status == 3 || item.status == 4 ? `<a href="/Download/${item.tid}">${item.file_name}</a>` : item.file_name}</td>
|
<td>${item.status === 3 || item.status === 4 ? `<a href="/Download/${item.tid}">${item.file_name}</a>` : item.file_name}</td>
|
||||||
<td class="hidden-on-small">${formatBytes(item.size)}</td>
|
<td class="hidden-on-small">${formatBytes(item.size)}</td>
|
||||||
<td>${item.add_time}</td>
|
<td>${item.add_time}</td>
|
||||||
<td>${statusStrDict.get(item.status)}</td>
|
<td class="hidden-on-small">${item.update_time}</td>
|
||||||
|
<td>${statusStrDict.get(item.status) + (item.status == 6 ? " #" + (item.queue_position + 1) : "")}</td>
|
||||||
<td class="hidden-on-small">${item.hash || 'N/A'}</td>
|
<td class="hidden-on-small">${item.hash || 'N/A'}</td>
|
||||||
`;
|
`;
|
||||||
tableBody.appendChild(row);
|
tableBody.appendChild(row);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -14,10 +14,20 @@ a {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background-color: rgba(255, 255, 255, 0.8);
|
background-color: rgba(255, 255, 255, 0.8);
|
||||||
|
backdrop-filter: blur(7px);
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center; /* 水平居中 */
|
||||||
align-items: center;
|
align-items: center; /* 垂直居中 */
|
||||||
|
flex-direction: column; /* 确保垂直方向排列,加载动画在上,文字在下 */
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
|
text-align: center; /* 文字居中 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-text {
|
||||||
|
margin-top: 10px; /* 加载动画和文字之间的间距 */
|
||||||
|
font-size: 1.2em;
|
||||||
|
color: #333;
|
||||||
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.loading-spinner {
|
.loading-spinner {
|
||||||
|
@ -31,6 +41,8 @@ a {
|
||||||
animation: spin 2s linear infinite;
|
animation: spin 2s linear infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@keyframes spin {
|
@keyframes spin {
|
||||||
0% {
|
0% {
|
||||||
transform: rotate(0deg);
|
transform: rotate(0deg);
|
||||||
|
|
Loading…
Reference in a new issue