完善一部分核心功能

This commit is contained in:
root 2024-11-20 20:18:26 +08:00
parent 5fd60ad9f3
commit be8303cd63
15 changed files with 552 additions and 66 deletions

View file

@ -22,7 +22,7 @@ namespace iFileProxy.Config
[JsonPropertyName("Database")]
public Database Database { get; set; }
public static AppConfig? GetCurrConfig(string configPath)
public static AppConfig? GetCurrConfig(string configPath = "iFileProxy.json")
{
if (File.Exists(configPath))
{

View file

@ -1,18 +1,22 @@
using iFileProxy.Models;
using iFileProxy.Config;
using iFileProxy.Helpers;
using iFileProxy.Models;
using iFileProxy.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.StaticFiles;
namespace iFileProxy.Controllers
{
public class iProxyController : ControllerBase
{
static readonly TaskManager taskManager = new ();
static Dictionary<string, Dictionary<string, uint>> IPAccessCountDict = [];
[HttpPost]
[Route("/AddOfflineTask")]
public ActionResult<CommonRsp> AddOfflineTask()
{
{
return taskManager.AddTask(HttpContext) switch
{
TaskAddState.Success => (ActionResult<CommonRsp>)Ok(new CommonRsp() { retcode = (int)TaskAddState.Success, message = "succ" }),
@ -21,6 +25,13 @@ namespace iFileProxy.Controllers
TaskAddState.ErrTaskIdRepeat => (ActionResult<CommonRsp>)Ok(new CommonRsp() { retcode = (int)TaskAddState.ErrTaskIdRepeat, message = "TaskIdRepeat!!!" }),
TaskAddState.ErrUrlInvalid => (ActionResult<CommonRsp>)Ok(new CommonRsp() { retcode = (int)TaskAddState.ErrUrlInvalid, message = "非法Url" }),
TaskAddState.ErrDbFail => (ActionResult<CommonRsp>)Ok(new CommonRsp() { retcode = (int)TaskAddState.ErrDbFail, message = "数据库数据提交失败!" }),
TaskAddState.ErrMaxParallelTasksLimit => (ActionResult<CommonRsp>)Ok(new CommonRsp() { retcode = (int)TaskAddState.ErrMaxParallelTasksLimit, message = "服务端并行数量达到上限!" }),
TaskAddState.ErrFileSizeLimit => (ActionResult<CommonRsp>)Ok(new CommonRsp() { retcode = (int)TaskAddState.ErrFileSizeLimit, message = "指定的文件大小超过系统最大限制!" }),
TaskAddState.ErrFileNameForbidden => (ActionResult<CommonRsp>)Ok(new CommonRsp() { retcode = (int)TaskAddState.ErrFileNameForbidden, 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.ErrGetFileInfo => (ActionResult<CommonRsp>)Ok(new CommonRsp() { retcode = (int)TaskAddState.ErrGetFileInfo, message = "目标文件信息获取失败!" }),
_ => (ActionResult<CommonRsp>)Ok(new CommonRsp() { retcode = (int)TaskAddState.Success, message = "succ default" }),
};
}
@ -30,7 +41,40 @@ namespace iFileProxy.Controllers
[Route("/GetMyTasks")]
public ActionResult<CommonRsp> GetMyTasks()
{
return Ok(new CommonRsp() { retcode = 0, message = "succ" });
return Ok(new CommonRsp() { retcode = 0, data = taskManager.GetTaskListByIpAddr(HttpContext),message = "succ" });
}
[HttpGet]
[HttpPost]
[Route("/Download/{taskID}")]
public async Task<IActionResult> DownloadFile(string taskID)
{
string fileName = "";
var d = taskManager.GetTaskListByIpAddr(HttpContext);
if (d.Where(x => x.TaskId == taskID).Any())
{
fileName = d.Where(x => x.TaskId == taskID).FirstOrDefault()?.FileName;
var filePath = Path.Combine(AppConfig.GetCurrConfig().DownloadOptions.SavePath, fileName);
if (!System.IO.File.Exists(filePath))
{
return Ok(new CommonRsp() { message = "file not exists", retcode = 1 });
}
// 获取文件的 MIME 类型
var provider = new FileExtensionContentTypeProvider();
string contentType;
if (!provider.TryGetContentType(filePath, out contentType))
{
contentType = "application/octet-stream"; // 默认 MIME 类型
}
// 读取文件内容
var fileContents = await System.IO.File.ReadAllBytesAsync(filePath);
// 返回文件内容
return File(fileContents, contentType, Path.GetFileName(filePath));
}
return Ok(new CommonRsp() { message = "task_id not exists", retcode = -1 });
}
}
}

View file

@ -4,6 +4,7 @@ using System.Data;
using System.Text.Json;
using MySql.Data.MySqlClient;
using iFileProxy.Models;
using Newtonsoft.Json;
namespace iFileProxy.Helpers
{
@ -49,7 +50,7 @@ namespace iFileProxy.Helpers
/// <returns></returns>
/// <exception cref="Exception">若某些不允许为空的字段出现空值 则抛出此异常</exception>
///
public MySqlConnection GetDBConn(string db_desc)
public MySqlConnection GetAndOpenDBConn(string db_desc)
{
if (!_dbDictionary.TryGetValue(db_desc, out DB Db))
{
@ -86,27 +87,42 @@ namespace iFileProxy.Helpers
/// <returns></returns>
public static string GetTableData(string sql, MySqlConnection conn)
{
DataTable dataTable = new DataTable();
DataTable dataTable = new();
using (MySqlCommand queryAllUser = new MySqlCommand(sql, conn))
using (MySqlCommand getAllData = new (sql, conn))
{
using (MySqlDataAdapter adapter = new MySqlDataAdapter(queryAllUser))
using (MySqlDataAdapter adapter = new (getAllData))
adapter.Fill(dataTable);
}
return JsonSerializer.Serialize(dataTable);
return JsonConvert.SerializeObject(dataTable);
}
public string GetTaskListByIP(string ipAddr)
{
string sql = $"SELECT * FROM t_tasks_info WHERE client_ip = \"{ipAddr}\"";
MySqlConnection conn = GetAndOpenDBConn("iFileProxy_Db");
try
{
return GetTableData(sql, conn);
}
catch (Exception e)
{
_logger.Error($"无法获取任务列表: {e.Message}");
throw;
}
finally { conn.Close(); }
}
public bool InsertTaskData(TaskInfo taskInfo)
{
_logger.Debug(JsonSerializer.Serialize(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)";
MySqlConnection conn = GetDBConn("iFileProxy_Db");
MySqlConnection conn = GetAndOpenDBConn("iFileProxy_Db");
try
{
using MySqlCommand sqlCmd = new MySqlCommand(sql, conn);
using MySqlCommand sqlCmd = new(sql, conn);
sqlCmd.Parameters.AddWithValue("@tid", taskInfo.TaskId);
sqlCmd.Parameters.AddWithValue("@file_name", taskInfo.FileName);
sqlCmd.Parameters.AddWithValue("@client_ip", taskInfo.ClientIp);
@ -116,7 +132,7 @@ namespace iFileProxy.Helpers
sqlCmd.Parameters.AddWithValue("@url", taskInfo.Url);
sqlCmd.Parameters.AddWithValue("@size", taskInfo.Size);
sqlCmd.Parameters.AddWithValue("@hash", taskInfo.Hash);
sqlCmd.ExecuteNonQuery();
}
catch (Exception)
@ -125,25 +141,102 @@ namespace iFileProxy.Helpers
throw;
}
finally
{
{
conn.Close();
}
return true;
}
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");
try
{
using MySqlCommand sqlCmd = new(sql, conn);
sqlCmd.Parameters.AddWithValue("@data",val);
sqlCmd.Parameters.AddWithValue("@tid",key);
if (sqlCmd.ExecuteNonQuery() == 1)
{
return true;
}
else
return false;
}
catch (Exception)
{
_logger.Fatal($"Task data update error.");
throw;
}
finally
{
conn.Close();
}
}
public bool UpdateTaskStatus(TaskInfo taskInfo)
{
string sql = @"UPDATE t_tasks_info set `status` = @status WHERE `tid` = @tid";
MySqlConnection conn = GetAndOpenDBConn("iFileProxy_Db");
try
{
using MySqlCommand sqlCmd = new (sql, conn);
sqlCmd.Parameters.AddWithValue("@status", taskInfo.Status);
sqlCmd.Parameters.AddWithValue("@tid", taskInfo.TaskId);
if (sqlCmd.ExecuteNonQuery() == 1)
{
_logger.Debug($"Task: {taskInfo.TaskId} Task Status Change to {taskInfo.Status}");
return true;
}
else
_logger.Warning($"Task: {taskInfo.TaskId} Task Status Change Failed.");
return false;
}
catch (Exception)
{
_logger.Fatal($"Task status update error.");
throw;
}
finally
{
conn.Close ();
}
}
public bool UpdateTaskHash(TaskInfo taskInfo)
{
return UpdateFieldsData("hash", taskInfo.TaskId, MasterHelper.GetFileHash(Path.Combine(_appConfig.DownloadOptions.SavePath, taskInfo.FileName), FileHashAlgorithm.MD5));
}
public void TryInitialDB()
{
string sql = "ALTER TABLE `t_region_config` ADD COLUMN `stop_server_info_str` varchar(255) CHARACTER SET utf8mb4 NOT NULL AFTER `stop_server_config_str`";
MySqlConnection conn = GetDBConn("deploy_config");
string sql = """
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(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`client_ip` varchar(15) 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(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;
""";
MySqlConnection conn = GetAndOpenDBConn("iFileProxy_Db");
try
{
using MySqlCommand cmd = new(sql, conn);
cmd.ExecuteNonQuery();
}
catch { }
catch (Exception)
{
_logger.Error($"Db Init Fai.l");
throw;
}
finally
{
conn.Close();

View file

@ -1,4 +1,5 @@
using iFileProxy.Models;
using Serilog;
using System.Net.Http.Headers;
using System.Text.Json;
@ -6,35 +7,44 @@ namespace iFileProxy.Helpers
{
public class FileDownloadHelper
{
private readonly static Serilog.ILogger _logger = Log.Logger.ForContext<FileDownloadHelper>();
public static DownloadFileInfo GetDownloadFileInfo(string url)
public static DownloadFileInfo? GetDownloadFileInfo(string url)
{
var fileInfo = new DownloadFileInfo();
var _httpClient = new HttpClient();
using (var request = new HttpRequestMessage(HttpMethod.Head, url))
try
{
using (var response = _httpClient.Send(request))
using (var request = new HttpRequestMessage(HttpMethod.Head, url))
{
response.EnsureSuccessStatusCode();
// 获取文件大小
if (response.Content.Headers.TryGetValues("Content-Length", out var values))
using (var response = _httpClient.Send(request))
{
if (long.TryParse(values.First(), out long fileSize))
response.EnsureSuccessStatusCode();
// 获取文件大小
if (response.Content.Headers.TryGetValues("Content-Length", out var values))
{
fileInfo.Size = fileSize;
if (long.TryParse(values.First(), out long fileSize))
{
fileInfo.Size = fileSize;
}
else
fileInfo.Size = -1;
}
else
fileInfo.Size = -1;
}
else
fileInfo.Size = -1;
// 获取文件名,优先从 Content-Disposition 中提取,如果没有再从 URL 提取
fileInfo.FileName = ExtractFileNameFromContentDisposition(response.Content.Headers.ContentDisposition)
?? ExtractFileNameFromUrl(url);
// 获取文件名,优先从 Content-Disposition 中提取,如果没有再从 URL 提取
fileInfo.FileName = ExtractFileNameFromContentDisposition(response.Content.Headers.ContentDisposition)
?? ExtractFileNameFromUrl(url);
}
}
}
catch (Exception e)
{
_logger.Error($"Get Download File Information Error: \n {e.Message}");
return null;
}
return fileInfo;
}

View file

@ -1,5 +1,8 @@
using System.Net.Http;
using iFileProxy.Models;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using static System.Runtime.InteropServices.JavaScript.JSType;
namespace iFileProxy.Helpers
{
@ -28,6 +31,55 @@ namespace iFileProxy.Helpers
return false;
}
/// <summary>
/// 获取一个给定的范围随机数
/// </summary>
/// <param name="minValue">最小值</param>
/// <param name="maxValue">最大值</param>
/// <returns></returns>
public static int GenerateRandomNumber(int minValue, int maxValue)
{
Random random = new Random(); // 创建一个新的Random实例
int randomNumber = random.Next(minValue, maxValue + 1); // 生成位于[minValue, maxValue]范围内的随机数
return randomNumber;
}
public static string? GetClientIPAddr(HttpContext c)
{
// 尝试从 X-Forwarded-For 请求头获取客户端IP地址
string? clientIp = c.Request.Headers["X-Forwarded-For"].FirstOrDefault();
// 如果 X-Forwarded-For 头不存在,回退到 RemoteIpAddress
if (string.IsNullOrEmpty(clientIp))
{
clientIp = c.Connection.RemoteIpAddress?.ToString();
}
return clientIp;
}
public static string GetFileHash(string fileName, FileHashAlgorithm algorithm)
{
byte[] hash = [];
if (File.Exists(fileName))
{
hash = algorithm switch
{
FileHashAlgorithm.MD5 => MD5.HashData(File.ReadAllBytes(fileName)),
FileHashAlgorithm.SHA1 => SHA1.HashData(File.ReadAllBytes(fileName)),
FileHashAlgorithm.SHA256 => SHA256.HashData(File.ReadAllBytes(fileName)),
FileHashAlgorithm.SHA384 => SHA384.HashData(File.ReadAllBytes(fileName)),
FileHashAlgorithm.SHA512 => SHA512.HashData(File.ReadAllBytes(fileName)),
_ => MD5.HashData(File.ReadAllBytes(fileName)),
};
StringBuilder sBuilder = new StringBuilder();
for (int i = 0; i < hash.Length; i++)
{
sBuilder.Append(hash[i].ToString("x2"));
}
return sBuilder.ToString();
}
return null;
}
}

View file

@ -2,8 +2,10 @@
namespace iFileProxy
{
using iFileProxy.Helpers;
using iFileProxy.Models;
using Microsoft.AspNetCore.Http;
using Serilog;
using System;
using System.Net;
using System.Text.Json;
@ -13,6 +15,9 @@ namespace iFileProxy
{
private readonly RequestDelegate _next;
private readonly static Serilog.ILogger _logger = Log.Logger.ForContext<ErrorHandlerMiddleware>();
public ErrorHandlerMiddleware(RequestDelegate next)
{
_next = next;
@ -39,9 +44,10 @@ namespace iFileProxy
private static Task HandleExceptionAsync(HttpContext context, Exception exception)
{
var code = HttpStatusCode.InternalServerError; // 500 if unexpected
_logger.Error("Crash Data: {exception}",exception);
switch (exception)
{
case NotImplementedException:
code = HttpStatusCode.NotImplemented; // 501
break;

View file

@ -0,0 +1,55 @@
namespace iFileProxy.Middleware
{
using iFileProxy.Helpers;
using iFileProxy.Models;
using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Threading.Tasks;
public class IPAccessLimitMiddleware
{
private readonly RequestDelegate _next;
private readonly Dictionary<string, Dictionary<string, uint>> _IPAccessCountDict;
private readonly int _dailyRequestLimitPerIP;
public IPAccessLimitMiddleware(RequestDelegate next, Dictionary<string, Dictionary<string, uint>> IPAccessCountDict, int dailyRequestLimitPerIP)
{
_next = next;
_IPAccessCountDict = IPAccessCountDict;
_dailyRequestLimitPerIP = dailyRequestLimitPerIP;
}
public async Task InvokeAsync(HttpContext context)
{
string dateStr = DateTime.Now.ToString("yyyy-MM-dd");
string ipStr = MasterHelper.GetClientIPAddr(context);
if (_IPAccessCountDict.ContainsKey(dateStr))
{
if (_IPAccessCountDict[dateStr].ContainsKey(ipStr))
{
if (_IPAccessCountDict[dateStr][ipStr] >= _dailyRequestLimitPerIP)
{
context.Response.StatusCode = 200;
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(JsonSerializer.Serialize(new CommonRsp { retcode = 403, message = "请求次数超过限制!" }));
return;
}
_IPAccessCountDict[dateStr][ipStr]++;
}
else
{
_IPAccessCountDict[dateStr].Add(ipStr, 1);
}
}
else
{
_IPAccessCountDict.Add(dateStr, new Dictionary<string, uint> { { ipStr, 1 } });
}
await _next(context);
}
}
}

View file

@ -12,7 +12,21 @@
ErrUrlRepeat = 2,
ErrTaskIdRepeat = 3,
ErrUrlInvalid = 4,
ErrDbFail = 5
ErrDbFail = 5,
ErrMaxParallelTasksLimit = 6,
ErrFileSizeLimit = 7,
ErrTargetHostForbidden = 8,
ErrFileNameForbidden = 9,
ErrIPForbidden = 10,
ErrGetFileInfo = 11
}
public enum FileHashAlgorithm
{
MD5,
SHA1,
SHA256,
SHA384,
SHA512,
}
public class DownloadFileInfo {
public string FileName { get; set; }

View file

@ -1,4 +1,6 @@
using iFileProxy.Config;
using iFileProxy.Middleware;
using Serilog;
namespace iFileProxy
@ -42,8 +44,21 @@ namespace iFileProxy
app.UseHttpsRedirection();
// 配置默认文件选项
var defaultFilesOptions = new DefaultFilesOptions();
defaultFilesOptions.DefaultFileNames.Clear(); // 清除默认列表
defaultFilesOptions.DefaultFileNames.Add("index.html"); // 添加自定义默认文件
app.UseDefaultFiles(defaultFilesOptions);
app.UseStaticFiles();
app.UseAuthorization();
var IPAccessCountDict = new Dictionary<string, Dictionary<string, uint>>();
var dailyRequestLimitPerIP = AppConfig.GetCurrConfig().SecurityOptions.DailyRequestLimitPerIP;
app.UseMiddleware<IPAccessLimitMiddleware>(IPAccessCountDict, dailyRequestLimitPerIP);
app.UseMiddleware<ErrorHandlerMiddleware>(); // 错误处理中间件
app.MapControllers();

View file

@ -4,6 +4,7 @@ using iFileProxy.Models;
using Serilog;
using System.Diagnostics;
using System.Security.Policy;
using System.Text.Json;
namespace iFileProxy.Services
{
@ -16,7 +17,7 @@ namespace iFileProxy.Services
private readonly AppConfig? _appConfig = AppConfig.GetCurrConfig("iFileProxy.json");
private readonly DatabaseHelper _dbHelper;
private Dictionary<string, TaskInfo> runningTasks = [];
public TaskManager()
public TaskManager()
{
_logger.Information("Initializing TaskManager...");
if (_appConfig != null)
@ -33,30 +34,34 @@ namespace iFileProxy.Services
/// </summary>
/// <param name="c">HttpContext</param>
/// <returns></returns>
public TaskAddState AddTask(HttpContext c)
public TaskAddState AddTask(HttpContext c)
{
// 尝试从 X-Forwarded-For 请求头获取客户端IP地址
string? clientIp = c.Request.Headers["X-Forwarded-For"].FirstOrDefault();
// 如果 X-Forwarded-For 头不存在,回退到 RemoteIpAddress
if (string.IsNullOrEmpty(clientIp))
{
clientIp = c.Connection.RemoteIpAddress?.ToString();
}
string? clientIp = MasterHelper.GetClientIPAddr(c);
string? t_url = c.Request.Query["url"].FirstOrDefault();
foreach (var t in runningTasks)
{
if (t.Value.Url == t_url)
{
return TaskAddState.ErrUrlRepeat;
}
}
if (_appConfig.DownloadOptions.MaxParallelTasks != 0 && runningTasks.Count >= _appConfig.DownloadOptions.MaxParallelTasks)
return TaskAddState.ErrMaxParallelTasksLimit;
if (runningTasks.Where(x => x.Value.Url == t_url).Any())
return TaskAddState.ErrUrlRepeat;
if (!MasterHelper.CheckUrlIsValid(t_url))
return TaskAddState.ErrUrlInvalid;
DownloadFileInfo fileInfo = FileDownloadHelper.GetDownloadFileInfo(t_url);
TaskInfo taskInfo = new()
if (_appConfig.SecurityOptions.BlockedHost.IndexOf(new Uri(t_url).Host) != -1)
return TaskAddState.ErrTargetHostForbidden;
DownloadFileInfo? fileInfo = FileDownloadHelper.GetDownloadFileInfo(t_url);
if (fileInfo == null)
return TaskAddState.ErrGetFileInfo;
if (fileInfo.Size == 0 || fileInfo.Size > _appConfig.DownloadOptions.MaxAllowedFileSize)
return TaskAddState.ErrFileSizeLimit;
if (_appConfig.SecurityOptions.BlockedFileName.IndexOf(fileInfo.FileName) != -1)
return TaskAddState.ErrFileNameForbidden;
TaskInfo taskInfo = new()
{
Url = t_url,
TaskId = Guid.NewGuid().ToString(),
@ -64,12 +69,14 @@ namespace iFileProxy.Services
FileName = fileInfo.FileName,
ClientIp = clientIp,
Size = fileInfo.Size,
Status = TaskState.Running,
Status = TaskState.NoInit,
UpdateTime = DateTime.Now
} ;
};
if (_dbHelper.InsertTaskData(taskInfo))
{
StartTask(taskInfo);
taskInfo.Status = TaskState.Running;
_dbHelper.UpdateTaskStatus(taskInfo);
_logger.Debug("数据插入成功");
return TaskAddState.Success;
}
@ -77,24 +84,24 @@ namespace iFileProxy.Services
return TaskAddState.ErrDbFail;
}
public async void StartTask(TaskInfo task_info)
public async void StartTask(TaskInfo taskInfo)
{
if (runningTasks.ContainsKey(task_info.TaskId))
if (runningTasks.ContainsKey(taskInfo.TaskId))
{
_logger.Error($"指定的task已经存在!!!");
return;
}
}
Process aria2c = new();
aria2c.StartInfo = new ProcessStartInfo
{
FileName = _appConfig.DownloadOptions.Aria2cPath,
WorkingDirectory = _appConfig.DownloadOptions.SavePath,
Arguments = $"-x {_appConfig.DownloadOptions.ThreadNum} -s {_appConfig.DownloadOptions.ThreadNum} {task_info.Url}",
RedirectStandardOutput = true ,
RedirectStandardError = true ,
RedirectStandardInput = true ,
Arguments = $"-x {_appConfig.DownloadOptions.ThreadNum} -s {_appConfig.DownloadOptions.ThreadNum} {taskInfo.Url}",
RedirectStandardOutput = true,
RedirectStandardError = true,
RedirectStandardInput = true,
UseShellExecute = false,
Environment = { { "TaskId", task_info.TaskId } }
Environment = { { "TaskId", taskInfo.TaskId } }
};
try
{
@ -103,12 +110,23 @@ namespace iFileProxy.Services
aria2c.BeginErrorReadLine();
aria2c.OutputDataReceived += Aria2c_OutputDataReceived;
aria2c.ErrorDataReceived += Aria2c_ErrorDataReceived;
runningTasks.Add(task_info.TaskId, task_info);
runningTasks.Add(taskInfo.TaskId, taskInfo);
await aria2c.WaitForExitAsync();
runningTasks.Remove(taskInfo.TaskId);
if (aria2c.ExitCode != 0)
_logger.Error($"task: {task_info.TaskId} 进程退出状态异常 ExitCode: {aria2c.ExitCode}");
{
_logger.Error($"task: {taskInfo.TaskId} 进程退出状态异常 ExitCode: {aria2c.ExitCode}");
taskInfo.Status = TaskState.Error;
_dbHelper.UpdateTaskStatus(taskInfo);
}
else
runningTasks.Remove(task_info.TaskId);
{
taskInfo.Status = TaskState.End;
_dbHelper.UpdateTaskStatus(taskInfo);
taskInfo.Hash = MasterHelper.GetFileHash(Path.Combine(_appConfig.DownloadOptions.SavePath, taskInfo.FileName), FileHashAlgorithm.MD5);
_dbHelper.UpdateTaskHash(taskInfo);
}
}
catch (Exception)
{
@ -133,6 +151,12 @@ namespace iFileProxy.Services
_logger.Debug($"[TaskId: {c.StartInfo.Environment["TaskId"]}] {e.Data}");
}
public List<TaskInfo> GetTaskListByIpAddr(HttpContext c)
{
string? clientIp = MasterHelper.GetClientIPAddr(c);
return JsonSerializer.Deserialize<List<TaskInfo>>(_dbHelper.GetTaskListByIP(clientIp)) ?? [];
}
//public bool DeleteTask(HttpContext c)
//{

View file

@ -8,6 +8,7 @@
<ItemGroup>
<PackageReference Include="MySql.Data" Version="9.1.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Serilog" Version="4.1.0" />
<PackageReference Include="Serilog.AspNetCore" Version="8.0.3" />
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />

155
src/wwwroot/index.html Normal file
View file

@ -0,0 +1,155 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>离线下载任务管理</title>
<link href="static/css/bootstarp/5/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
<style>
body {
padding: 20px;
background-color: #f8f9fa;
}
.container {
max-width: 1000px;
margin: 0 auto;
}
h1 {
text-align: center;
color: #343a40;
}
.table-responsive {
overflow-x: auto;
}
.table {
width: 100%;
max-width: 100%;
margin-bottom: 1rem;
background-color: transparent;
border-collapse: collapse;
border: 1px solid #dee2e6;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
.table th, .table td {
padding: 15px;
vertical-align: middle;
border: 1px solid #dee2e6;
text-align: center;
max-width: 90px; /* 设置最大宽度 */
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.table thead th {
background-color: #007bff;
color: white;
font-weight: bold;
}
.table tbody tr:nth-child(even) {
background-color: #f1f3f5;
}
.table tbody tr:hover {
background-color: #e2e6ea;
}
@media (max-width: 768px) {
.table-responsive {
overflow-x: auto;
}
.table th, .table td {
padding: 10px;
font-size: 0.9em;
}
.hidden-on-small {
display: none !important;
}
}
a {
text-decoration: none;
}
</style>
</head>
<body>
<div class="container">
<h1 class="text-center">离线下载任务管理</h1>
<div class="table-responsive">
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>文件名</th>
<th class="hidden-on-small">大小</th>
<th>提交时间</th>
<th>状态</th>
<th class="hidden-on-small">哈希</th>
</tr>
</thead>
<tbody id="taskTableBody">
<!-- 数据将在这里动态填充 -->
</tbody>
</table>
</div>
</div>
<!-- 优先加载jq一类的三方库 -->
<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>
let statusStrDict = new Map([
[0,"排队中"],
[1,"进行中"],
[2,"错误"],
[3,"已完成"]
])
data = [];
$.ajax({
type: "GET",
url: "/GetMyTasks",
dataType: "json",
success: function (response) {
if (response.retcode == 0) {
data = response.data;
populateTable(data);
}
else
alert(response.message);
},
error(xhr, status, error) {
alert(error);
}
});
function formatBytes(bytes, decimals = 2) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const dm = decimals || 2;
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
}
function populateTable(data) {
const tableBody = document.getElementById('taskTableBody');
tableBody.innerHTML = ''; // 清空现有内容
data.forEach(item => {
const row = document.createElement('tr');
row.innerHTML = `
<td><a href="/Download/${item.tid}"> ${item.file_name}</td>
<td class="hidden-on-small">${formatBytes(item.size)}</td>
<td>${item.add_time}</td>
<td>${statusStrDict.get(item.status)}</td>
<td class="hidden-on-small">${item.hash || 'N/A'}</td>
`;
tableBody.appendChild(row);
});
}
</script>
</body>
</html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long