完善一部分核心功能
This commit is contained in:
parent
5fd60ad9f3
commit
be8303cd63
15 changed files with 552 additions and 66 deletions
|
@ -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))
|
||||
{
|
||||
|
|
|
@ -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 });
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
55
src/Middleware/IPAccessLimitMiddleware.cs
Normal file
55
src/Middleware/IPAccessLimitMiddleware.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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)
|
||||
//{
|
||||
|
||||
|
|
|
@ -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
155
src/wwwroot/index.html
Normal 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>
|
6
src/wwwroot/static/css/bootstarp/5/bootstrap.min.css
vendored
Normal file
6
src/wwwroot/static/css/bootstarp/5/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
7
src/wwwroot/static/js/bootstarp/5/bootstrap.bundle.min.js
vendored
Normal file
7
src/wwwroot/static/js/bootstarp/5/bootstrap.bundle.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
4
src/wwwroot/static/js/jquery/2.1.4/jquery.min.js
vendored
Normal file
4
src/wwwroot/static/js/jquery/2.1.4/jquery.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue