初次提交
This commit is contained in:
commit
5fd60ad9f3
22 changed files with 956 additions and 0 deletions
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
src/bin
|
||||
src/.vs
|
||||
src/obj
|
||||
src/.config
|
||||
*.user
|
1
README.md
Normal file
1
README.md
Normal file
|
@ -0,0 +1 @@
|
|||
### 文件代理下载工具
|
108
src/Config/AppConfig.cs
Normal file
108
src/Config/AppConfig.cs
Normal file
|
@ -0,0 +1,108 @@
|
|||
using Serilog;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace iFileProxy.Config
|
||||
{
|
||||
public class AppConfig
|
||||
{
|
||||
static readonly Serilog.ILogger _logger = Log.Logger.ForContext<AppConfig>();
|
||||
readonly static JsonSerializerOptions options = new()
|
||||
{
|
||||
ReadCommentHandling = JsonCommentHandling.Skip, // 允许注释
|
||||
AllowTrailingCommas = true // 允许尾随逗号
|
||||
};
|
||||
|
||||
[JsonPropertyName("Download")]
|
||||
public DownloadOptions DownloadOptions { get; set; } = new();
|
||||
|
||||
[JsonPropertyName("Security")]
|
||||
public SecurityOptions SecurityOptions { get; set; } = new();
|
||||
|
||||
[JsonPropertyName("Database")]
|
||||
public Database Database { get; set; }
|
||||
|
||||
public static AppConfig? GetCurrConfig(string configPath)
|
||||
{
|
||||
if (File.Exists(configPath))
|
||||
{
|
||||
try
|
||||
{
|
||||
return JsonSerializer.Deserialize<AppConfig>(File.ReadAllText(configPath),options);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
_logger.Error("Config Parse Error!");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
else
|
||||
_logger.Fatal($"Config File: {configPath} not exists!");
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
public class DownloadOptions
|
||||
{
|
||||
public string SavePath { get; set; } = "./proxy_tmp/";
|
||||
public uint ThreadNum { get; set; } = 1;
|
||||
public uint MaxAllowedFileSize { get; set; }
|
||||
public uint MaxParallelTasks { get; set; } = 4;
|
||||
public string Aria2cPath { get; set; } = "./bin/aria2c";
|
||||
}
|
||||
public class SecurityOptions
|
||||
{
|
||||
public List<string> BlockedHost { get; set; } = [];
|
||||
public List<string> BlockedFileName { get; set; } = [];
|
||||
public List<string> BlockedClientIP { get; set; } = [];
|
||||
public int DailyRequestLimitPerIP { get; set; } = -1;
|
||||
}
|
||||
|
||||
public partial class Database
|
||||
{
|
||||
[JsonPropertyName("Common")]
|
||||
public Common Common { get; set; }
|
||||
|
||||
[JsonPropertyName("Databases")]
|
||||
public DB[] Databases { get; set; }
|
||||
}
|
||||
|
||||
public partial class Common
|
||||
{
|
||||
[JsonPropertyName("Host")]
|
||||
public string Host { get; set; }
|
||||
|
||||
[JsonPropertyName("Port")]
|
||||
public int Port { get; set; } = 3306;
|
||||
|
||||
[JsonPropertyName("User")]
|
||||
public string User { get; set; }
|
||||
|
||||
[JsonPropertyName("Password")]
|
||||
public string Password { get; set; }
|
||||
}
|
||||
public partial class DB
|
||||
{
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
[JsonPropertyName("Host")]
|
||||
public string Host { get; set; }
|
||||
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
[JsonPropertyName("Port")]
|
||||
public long? Port { get; set; } = 3306;
|
||||
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
[JsonPropertyName("User")]
|
||||
public string User { get; set; }
|
||||
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
[JsonPropertyName("Password")]
|
||||
public string Password { get; set; }
|
||||
|
||||
[JsonPropertyName("DatabaseName")]
|
||||
public string DatabaseName { get; set; }
|
||||
|
||||
[JsonPropertyName("Description")]
|
||||
public string Description { get; set; }
|
||||
}
|
||||
}
|
36
src/Controllers/iProxyController.cs
Normal file
36
src/Controllers/iProxyController.cs
Normal file
|
@ -0,0 +1,36 @@
|
|||
using iFileProxy.Models;
|
||||
using iFileProxy.Services;
|
||||
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace iFileProxy.Controllers
|
||||
{
|
||||
public class iProxyController : ControllerBase
|
||||
{
|
||||
static readonly TaskManager taskManager = new ();
|
||||
|
||||
[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" }),
|
||||
TaskAddState.Fail => (ActionResult<CommonRsp>)Ok(new CommonRsp() { retcode = (int)TaskAddState.Fail, message = "unkown error!" }),
|
||||
TaskAddState.ErrUrlRepeat => (ActionResult<CommonRsp>)Ok(new CommonRsp() { retcode = (int)TaskAddState.ErrUrlRepeat, message = "此url已经在任务队列中,请勿重复提交" }),
|
||||
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 = "数据库数据提交失败!" }),
|
||||
_ => (ActionResult<CommonRsp>)Ok(new CommonRsp() { retcode = (int)TaskAddState.Success, message = "succ default" }),
|
||||
};
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[HttpGet]
|
||||
[Route("/GetMyTasks")]
|
||||
public ActionResult<CommonRsp> GetMyTasks()
|
||||
{
|
||||
return Ok(new CommonRsp() { retcode = 0, message = "succ" });
|
||||
}
|
||||
}
|
||||
}
|
63
src/ErrorHandler.cs
Normal file
63
src/ErrorHandler.cs
Normal file
|
@ -0,0 +1,63 @@
|
|||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace iFileProxy
|
||||
{
|
||||
using iFileProxy.Models;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public class ErrorHandlerMiddleware
|
||||
{
|
||||
private readonly RequestDelegate _next;
|
||||
|
||||
public ErrorHandlerMiddleware(RequestDelegate next)
|
||||
{
|
||||
_next = next;
|
||||
}
|
||||
|
||||
public async Task InvokeAsync(HttpContext context)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _next(context);
|
||||
if (context.Response.StatusCode == 404)
|
||||
{
|
||||
context.Response.ContentType = "application/json";
|
||||
await context.Response.WriteAsync(JsonSerializer.Serialize(new CommonRsp { retcode = 404, message = "this route not exists." }));
|
||||
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await HandleExceptionAsync(context, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static Task HandleExceptionAsync(HttpContext context, Exception exception)
|
||||
{
|
||||
var code = HttpStatusCode.InternalServerError; // 500 if unexpected
|
||||
|
||||
switch (exception)
|
||||
{
|
||||
case NotImplementedException:
|
||||
code = HttpStatusCode.NotImplemented; // 501
|
||||
break;
|
||||
case UnauthorizedAccessException:
|
||||
code = HttpStatusCode.Unauthorized; // 401
|
||||
break;
|
||||
case ArgumentException:
|
||||
code = HttpStatusCode.BadRequest; // 400
|
||||
break;
|
||||
// Add more cases for different types of exceptions
|
||||
}
|
||||
|
||||
context.Response.ContentType = "application/json";
|
||||
context.Response.StatusCode = (int)code;
|
||||
|
||||
return context.Response.WriteAsync(JsonSerializer.Serialize(new CommonRsp { retcode = 1, message = "server internal error" }));
|
||||
}
|
||||
}
|
||||
}
|
153
src/Helpers/DatabaseHelper.cs
Normal file
153
src/Helpers/DatabaseHelper.cs
Normal file
|
@ -0,0 +1,153 @@
|
|||
using iFileProxy.Config;
|
||||
using Serilog;
|
||||
using System.Data;
|
||||
using System.Text.Json;
|
||||
using MySql.Data.MySqlClient;
|
||||
using iFileProxy.Models;
|
||||
|
||||
namespace iFileProxy.Helpers
|
||||
{
|
||||
public class DatabaseHelper
|
||||
{
|
||||
Database _db;
|
||||
AppConfig _appConfig;
|
||||
private readonly static Serilog.ILogger _logger = Log.Logger.ForContext<DatabaseHelper>();
|
||||
|
||||
Dictionary<string, DB> _dbDictionary = new Dictionary<string, DB>();
|
||||
|
||||
public DatabaseHelper(AppConfig appConfig)
|
||||
{
|
||||
_logger.Information("Initializing DatabaseHelper...");
|
||||
_db = appConfig.Database;
|
||||
_appConfig = appConfig;
|
||||
try
|
||||
{
|
||||
_logger.Information("Done.");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Fatal($"程序异常: {e.Message}");
|
||||
}
|
||||
LoadDbDict();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加载数据库描述字典
|
||||
/// </summary>
|
||||
public void LoadDbDict()
|
||||
{
|
||||
foreach (DB item in _db.Databases)
|
||||
{
|
||||
_dbDictionary.Add(item.Description, item);
|
||||
_logger.Debug($"Db Config: {item.Description} <=> {item.DatabaseName} Loaded.");
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 获取一个指定数据库的连接
|
||||
/// </summary>
|
||||
/// <param name="db_desc">数据库描述字段 对应AppConfig的description字段</param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="Exception">若某些不允许为空的字段出现空值 则抛出此异常</exception>
|
||||
///
|
||||
public MySqlConnection GetDBConn(string db_desc)
|
||||
{
|
||||
if (!_dbDictionary.TryGetValue(db_desc, out DB Db))
|
||||
{
|
||||
throw new Exception($"未找到与 {db_desc} 相匹配的数据库配置");
|
||||
}
|
||||
|
||||
var db_user = Db.User ?? _db.Common.User;
|
||||
var db_password = Db.Password ?? _db.Common.Password;
|
||||
var db_host = Db.Host ?? _db.Common.Host;
|
||||
var db_port = Db.Port ?? _db.Common.Port;
|
||||
|
||||
if (db_user == null || db_password == null || db_host == null || db_port == null)
|
||||
throw new NoNullAllowedException("数据库配置获取失败,不允许为空的字段出现空值");
|
||||
|
||||
string db_connstr = $"server={db_host};user={db_user};database={Db.DatabaseName};port={db_port};password={db_password};Pooling=true;MaximumPoolSize=500;";
|
||||
MySqlConnection conn;
|
||||
try
|
||||
{
|
||||
conn = new MySqlConnection(db_connstr);
|
||||
conn.Open();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Fatal($"获取Mysql连接时出现异常:{ex.Message}");
|
||||
throw;
|
||||
}
|
||||
return conn;
|
||||
}
|
||||
/// <summary>
|
||||
/// 获取一个json格式的数据表
|
||||
/// </summary>
|
||||
/// <param name="sql"></param>
|
||||
/// <param name="conn"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetTableData(string sql, MySqlConnection conn)
|
||||
{
|
||||
DataTable dataTable = new DataTable();
|
||||
|
||||
using (MySqlCommand queryAllUser = new MySqlCommand(sql, conn))
|
||||
{
|
||||
using (MySqlDataAdapter adapter = new MySqlDataAdapter(queryAllUser))
|
||||
adapter.Fill(dataTable);
|
||||
}
|
||||
return JsonSerializer.Serialize(dataTable);
|
||||
|
||||
}
|
||||
|
||||
public bool InsertTaskData(TaskInfo taskInfo)
|
||||
{
|
||||
_logger.Debug(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");
|
||||
|
||||
try
|
||||
{
|
||||
using MySqlCommand sqlCmd = new MySqlCommand(sql, conn);
|
||||
sqlCmd.Parameters.AddWithValue("@tid", taskInfo.TaskId);
|
||||
sqlCmd.Parameters.AddWithValue("@file_name", taskInfo.FileName);
|
||||
sqlCmd.Parameters.AddWithValue("@client_ip", taskInfo.ClientIp);
|
||||
sqlCmd.Parameters.AddWithValue("@add_time", taskInfo.AddTime.ToString("yyyy-MM-dd HH:mm:ss"));
|
||||
sqlCmd.Parameters.AddWithValue("@update_time", taskInfo.UpdateTime.ToString("yyyy-MM-dd HH:mm:ss"));
|
||||
sqlCmd.Parameters.AddWithValue("@status", taskInfo.Status);
|
||||
sqlCmd.Parameters.AddWithValue("@url", taskInfo.Url);
|
||||
sqlCmd.Parameters.AddWithValue("@size", taskInfo.Size);
|
||||
sqlCmd.Parameters.AddWithValue("@hash", taskInfo.Hash);
|
||||
|
||||
sqlCmd.ExecuteNonQuery();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
_logger.Fatal($"插入数据时出现问题");
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
conn.Close();
|
||||
}
|
||||
return true;
|
||||
|
||||
|
||||
}
|
||||
|
||||
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");
|
||||
|
||||
try
|
||||
{
|
||||
using MySqlCommand cmd = new(sql, conn);
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
catch { }
|
||||
finally
|
||||
{
|
||||
conn.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
62
src/Helpers/FileDownloadHelper.cs
Normal file
62
src/Helpers/FileDownloadHelper.cs
Normal file
|
@ -0,0 +1,62 @@
|
|||
using iFileProxy.Models;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace iFileProxy.Helpers
|
||||
{
|
||||
public class FileDownloadHelper
|
||||
{
|
||||
|
||||
public static DownloadFileInfo GetDownloadFileInfo(string url)
|
||||
{
|
||||
var fileInfo = new DownloadFileInfo();
|
||||
var _httpClient = new HttpClient();
|
||||
using (var request = new HttpRequestMessage(HttpMethod.Head, url))
|
||||
{
|
||||
using (var response = _httpClient.Send(request))
|
||||
{
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
// 获取文件大小
|
||||
if (response.Content.Headers.TryGetValues("Content-Length", out var values))
|
||||
{
|
||||
if (long.TryParse(values.First(), out long fileSize))
|
||||
{
|
||||
fileInfo.Size = fileSize;
|
||||
}
|
||||
else
|
||||
fileInfo.Size = -1;
|
||||
}
|
||||
else
|
||||
fileInfo.Size = -1;
|
||||
|
||||
// 获取文件名,优先从 Content-Disposition 中提取,如果没有再从 URL 提取
|
||||
fileInfo.FileName = ExtractFileNameFromContentDisposition(response.Content.Headers.ContentDisposition)
|
||||
?? ExtractFileNameFromUrl(url);
|
||||
}
|
||||
}
|
||||
|
||||
return fileInfo;
|
||||
}
|
||||
|
||||
private static string ExtractFileNameFromContentDisposition(ContentDispositionHeaderValue contentDisposition)
|
||||
{
|
||||
if (contentDisposition != null)
|
||||
{
|
||||
// 检查是否有文件名
|
||||
var fileName = contentDisposition.FileName ?? contentDisposition.FileNameStar;
|
||||
return fileName;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string ExtractFileNameFromUrl(string url)
|
||||
{
|
||||
// 从 URL 中提取文件名,通常是 URL 路径的最后一部分
|
||||
Uri uri = new Uri(url);
|
||||
string fileName = Path.GetFileName(uri.LocalPath);
|
||||
return fileName;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
34
src/Helpers/MasterHelper.cs
Normal file
34
src/Helpers/MasterHelper.cs
Normal file
|
@ -0,0 +1,34 @@
|
|||
using System.Net.Http;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace iFileProxy.Helpers
|
||||
{
|
||||
public class MasterHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// 检测链接是否为合法的网址格式
|
||||
/// </summary>
|
||||
/// <param name="uri">待检测的链接</param>
|
||||
/// <returns></returns>
|
||||
public static bool CheckUrlIsValid(string? uri)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(uri))
|
||||
return false;
|
||||
|
||||
var regex = @"(http://)?([\w-]+\.)+[\w-]+(/[\w- ./?%&=]*)?";
|
||||
Regex re = new Regex(regex);
|
||||
return re.IsMatch(uri);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
9
src/Models/CommonRsp.cs
Normal file
9
src/Models/CommonRsp.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
namespace iFileProxy.Models
|
||||
{
|
||||
public class CommonRsp
|
||||
{
|
||||
public string message { get; set; }
|
||||
public object data { get; set; }
|
||||
public int retcode { get; set; }
|
||||
}
|
||||
}
|
37
src/Models/Db.cs
Normal file
37
src/Models/Db.cs
Normal file
|
@ -0,0 +1,37 @@
|
|||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
namespace iFileProxy.Models
|
||||
{
|
||||
public class TaskInfo
|
||||
{
|
||||
[JsonPropertyName("id")]
|
||||
public uint Id { get; set; }
|
||||
|
||||
[JsonPropertyName("tid")]
|
||||
public string TaskId { get; set; }
|
||||
|
||||
[JsonPropertyName("file_name")]
|
||||
public string FileName { get; set; }
|
||||
|
||||
[JsonPropertyName("client_ip")]
|
||||
public string? ClientIp { get; set; }
|
||||
|
||||
[JsonPropertyName("add_time")]
|
||||
public DateTime AddTime { get; set; }
|
||||
|
||||
[JsonPropertyName("update_time")]
|
||||
public DateTime UpdateTime { get; set; }
|
||||
|
||||
[JsonPropertyName("status")]
|
||||
public TaskState Status { get; set; }
|
||||
|
||||
[JsonPropertyName("url")]
|
||||
public string Url { get; set; }
|
||||
|
||||
[JsonPropertyName("size")]
|
||||
public long Size { get; set; }
|
||||
|
||||
[JsonPropertyName("hash")]
|
||||
public string Hash { get; set; }
|
||||
}
|
||||
}
|
21
src/Models/Task.cs
Normal file
21
src/Models/Task.cs
Normal file
|
@ -0,0 +1,21 @@
|
|||
namespace iFileProxy.Models
|
||||
{
|
||||
public enum TaskState {
|
||||
NoInit = 0, // 还未初始化
|
||||
Running = 1, // 正在进行
|
||||
Error = 2, // 任务执行时候发生错误 已经结束
|
||||
End = 3, // 任务正常结束
|
||||
}
|
||||
public enum TaskAddState {
|
||||
Success = 0,
|
||||
Fail = 1,
|
||||
ErrUrlRepeat = 2,
|
||||
ErrTaskIdRepeat = 3,
|
||||
ErrUrlInvalid = 4,
|
||||
ErrDbFail = 5
|
||||
}
|
||||
public class DownloadFileInfo {
|
||||
public string FileName { get; set; }
|
||||
public long Size { get; set; }
|
||||
}
|
||||
}
|
54
src/Program.cs
Normal file
54
src/Program.cs
Normal file
|
@ -0,0 +1,54 @@
|
|||
|
||||
using Serilog;
|
||||
|
||||
namespace iFileProxy
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
SerilogConfig.CreateLogger();
|
||||
Serilog.ILogger logger = Log.Logger.ForContext<Program>();
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// Add services to the container.
|
||||
|
||||
builder.Services.AddControllers();
|
||||
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
builder.Services.AddSwaggerGen();
|
||||
|
||||
builder.Host.UseSerilog(logger: Log.Logger);
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
app.UseSerilogRequestLogging(options =>
|
||||
{
|
||||
options.EnrichDiagnosticContext = (diagCtx, httpCtx) =>
|
||||
{
|
||||
diagCtx.Set("ClientIp", httpCtx.Connection.RemoteIpAddress?.ToString());
|
||||
diagCtx.Set("contentType", httpCtx.Request.ContentType);
|
||||
diagCtx.Set("queryString", httpCtx.Request.QueryString);
|
||||
};
|
||||
});
|
||||
|
||||
// Configure the HTTP request pipeline.
|
||||
if (app.Environment.IsDevelopment())
|
||||
{
|
||||
app.UseSwagger();
|
||||
app.UseSwaggerUI();
|
||||
}
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
|
||||
app.UseAuthorization();
|
||||
|
||||
app.UseMiddleware<ErrorHandlerMiddleware>(); // ´íÎó´¦ÀíÖмä¼þ
|
||||
|
||||
app.MapControllers();
|
||||
|
||||
app.Run();
|
||||
}
|
||||
}
|
||||
}
|
23
src/Properties/PublishProfiles/FolderProfile.pubxml
Normal file
23
src/Properties/PublishProfiles/FolderProfile.pubxml
Normal file
|
@ -0,0 +1,23 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
https://go.microsoft.com/fwlink/?LinkID=208121.
|
||||
-->
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<DeleteExistingFiles>false</DeleteExistingFiles>
|
||||
<ExcludeApp_Data>false</ExcludeApp_Data>
|
||||
<LaunchSiteAfterPublish>true</LaunchSiteAfterPublish>
|
||||
<LastUsedBuildConfiguration>Release</LastUsedBuildConfiguration>
|
||||
<LastUsedPlatform>Any CPU</LastUsedPlatform>
|
||||
<PublishProvider>FileSystem</PublishProvider>
|
||||
<PublishUrl>bin\Release\net8.0\publish\</PublishUrl>
|
||||
<WebPublishMethod>FileSystem</WebPublishMethod>
|
||||
<_TargetId>Folder</_TargetId>
|
||||
<SiteUrlToLaunchAfterPublish />
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
<ProjectGuid>e343bd8a-27ed-47e2-b50d-e3000730e65e</ProjectGuid>
|
||||
<SelfContained>false</SelfContained>
|
||||
<PublishSingleFile>true</PublishSingleFile>
|
||||
</PropertyGroup>
|
||||
</Project>
|
41
src/Properties/launchSettings.json
Normal file
41
src/Properties/launchSettings.json
Normal file
|
@ -0,0 +1,41 @@
|
|||
{
|
||||
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:9876",
|
||||
"sslPort": 44309
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"applicationUrl": "http://localhost:5098",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"https": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"applicationUrl": "https://localhost:7272;http://localhost:5098",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
54
src/SerilogConfig.cs
Normal file
54
src/SerilogConfig.cs
Normal file
|
@ -0,0 +1,54 @@
|
|||
namespace iFileProxy
|
||||
{
|
||||
using Serilog;
|
||||
using Serilog.Events;
|
||||
using Serilog.Filters;
|
||||
using System.Net;
|
||||
|
||||
public static class SerilogConfig
|
||||
{
|
||||
public static void CreateLogger()
|
||||
{
|
||||
var filePath = Path.Combine(AppContext.BaseDirectory, $"logs/dispatch.api.log");
|
||||
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
.MinimumLevel.Debug()
|
||||
.MinimumLevel.Override("Microsoft", LogEventLevel.Information)
|
||||
.MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Warning)
|
||||
.Enrich.FromLogContext()
|
||||
.WriteTo.Console(
|
||||
outputTemplate: "{Timestamp:HH:mm:ss.fff} [{Level:u3}] [{SourceContext}] {ClientIp} {Message:lj} {contentType}{NewLine} {Exception}")
|
||||
.WriteTo.File(filePath,
|
||||
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] [{SourceContext}] {ClientIp} {Message:lj} {contentType} {queryString}{NewLine}{Exception}",
|
||||
rollingInterval: RollingInterval.Day,
|
||||
fileSizeLimitBytes: 1073741824) //1GB
|
||||
.Enrich.WithProperty("node_ip", GetIpAddress())
|
||||
.CreateLogger();
|
||||
}
|
||||
|
||||
public static void RefreshLogger()
|
||||
{
|
||||
if (Log.Logger != null)
|
||||
{
|
||||
Log.CloseAndFlush();
|
||||
}
|
||||
CreateLogger();
|
||||
}
|
||||
|
||||
private static string GetIpAddress()
|
||||
{
|
||||
string ipAddress = "127.0.0.1";
|
||||
IPAddress[] ips = Dns.GetHostAddresses(Dns.GetHostName());
|
||||
foreach (IPAddress ip in ips)
|
||||
{
|
||||
if (ip.AddressFamily.ToString().ToLower().Equals("internetwork"))
|
||||
{
|
||||
ipAddress = ip.ToString();
|
||||
return ipAddress;
|
||||
}
|
||||
}
|
||||
|
||||
return ipAddress;
|
||||
}
|
||||
}
|
||||
}
|
154
src/Services/TaskManager.cs
Normal file
154
src/Services/TaskManager.cs
Normal file
|
@ -0,0 +1,154 @@
|
|||
using iFileProxy.Config;
|
||||
using iFileProxy.Helpers;
|
||||
using iFileProxy.Models;
|
||||
using Serilog;
|
||||
using System.Diagnostics;
|
||||
using System.Security.Policy;
|
||||
|
||||
namespace iFileProxy.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// 下载任务管理器
|
||||
/// </summary>
|
||||
public class TaskManager
|
||||
{
|
||||
private readonly static Serilog.ILogger _logger = Log.Logger.ForContext<TaskManager>();
|
||||
private readonly AppConfig? _appConfig = AppConfig.GetCurrConfig("iFileProxy.json");
|
||||
private readonly DatabaseHelper _dbHelper;
|
||||
private Dictionary<string, TaskInfo> runningTasks = [];
|
||||
public TaskManager()
|
||||
{
|
||||
_logger.Information("Initializing TaskManager...");
|
||||
if (_appConfig != null)
|
||||
_dbHelper = new DatabaseHelper(_appConfig);
|
||||
else
|
||||
{
|
||||
_logger.Fatal($"Failed to load application configuration");
|
||||
Environment.Exit(1);
|
||||
}
|
||||
_logger.Information("TaskManager init succ.");
|
||||
}
|
||||
/// <summary>
|
||||
/// 添加一个新的下载任务
|
||||
/// </summary>
|
||||
/// <param name="c">HttpContext</param>
|
||||
/// <returns></returns>
|
||||
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? t_url = c.Request.Query["url"].FirstOrDefault();
|
||||
|
||||
foreach (var t in runningTasks)
|
||||
{
|
||||
if (t.Value.Url == t_url)
|
||||
{
|
||||
return TaskAddState.ErrUrlRepeat;
|
||||
}
|
||||
}
|
||||
|
||||
if (!MasterHelper.CheckUrlIsValid(t_url))
|
||||
return TaskAddState.ErrUrlInvalid;
|
||||
|
||||
DownloadFileInfo fileInfo = FileDownloadHelper.GetDownloadFileInfo(t_url);
|
||||
TaskInfo taskInfo = new()
|
||||
{
|
||||
Url = t_url,
|
||||
TaskId = Guid.NewGuid().ToString(),
|
||||
AddTime = DateTime.Now,
|
||||
FileName = fileInfo.FileName,
|
||||
ClientIp = clientIp,
|
||||
Size = fileInfo.Size,
|
||||
Status = TaskState.Running,
|
||||
UpdateTime = DateTime.Now
|
||||
} ;
|
||||
if (_dbHelper.InsertTaskData(taskInfo))
|
||||
{
|
||||
StartTask(taskInfo);
|
||||
_logger.Debug("数据插入成功");
|
||||
return TaskAddState.Success;
|
||||
}
|
||||
else
|
||||
return TaskAddState.ErrDbFail;
|
||||
}
|
||||
|
||||
public async void StartTask(TaskInfo task_info)
|
||||
{
|
||||
if (runningTasks.ContainsKey(task_info.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 ,
|
||||
UseShellExecute = false,
|
||||
Environment = { { "TaskId", task_info.TaskId } }
|
||||
};
|
||||
try
|
||||
{
|
||||
aria2c.Start();
|
||||
aria2c.BeginOutputReadLine();
|
||||
aria2c.BeginErrorReadLine();
|
||||
aria2c.OutputDataReceived += Aria2c_OutputDataReceived;
|
||||
aria2c.ErrorDataReceived += Aria2c_ErrorDataReceived;
|
||||
runningTasks.Add(task_info.TaskId, task_info);
|
||||
await aria2c.WaitForExitAsync();
|
||||
if (aria2c.ExitCode != 0)
|
||||
_logger.Error($"task: {task_info.TaskId} 进程退出状态异常 ExitCode: {aria2c.ExitCode}");
|
||||
else
|
||||
runningTasks.Remove(task_info.TaskId);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
_logger.Fatal("执行下载任务时候出现致命问题");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private void Aria2c_ErrorDataReceived(object sender, DataReceivedEventArgs e)
|
||||
{
|
||||
if (e.Data == null || e.Data.Trim() == "")
|
||||
return;
|
||||
Process c = (Process)sender;
|
||||
_logger.Error($"[TaskId: {c.StartInfo.Environment["TaskId"]}] {e.Data}");
|
||||
}
|
||||
|
||||
private void Aria2c_OutputDataReceived(object sender, DataReceivedEventArgs e)
|
||||
{
|
||||
if (e.Data == null || e.Data.Trim() == "")
|
||||
return;
|
||||
Process c = (Process)sender;
|
||||
_logger.Debug($"[TaskId: {c.StartInfo.Environment["TaskId"]}] {e.Data}");
|
||||
}
|
||||
|
||||
//public bool DeleteTask(HttpContext c)
|
||||
//{
|
||||
|
||||
//}
|
||||
|
||||
//public bool UpdateTaskStatus(HttpContext c)
|
||||
//{
|
||||
//}
|
||||
//public List<TaskInfo> GetAllTaskInfo(HttpContext c)
|
||||
//{
|
||||
|
||||
//}
|
||||
|
||||
//public TaskInfo GetTaskInfo(HttpContext c)
|
||||
//{
|
||||
|
||||
//}
|
||||
}
|
||||
}
|
8
src/appsettings.Development.json
Normal file
8
src/appsettings.Development.json
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
9
src/appsettings.json
Normal file
9
src/appsettings.json
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
18
src/iFileProxy.csproj
Normal file
18
src/iFileProxy.csproj
Normal file
|
@ -0,0 +1,18 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MySql.Data" Version="9.1.0" />
|
||||
<PackageReference Include="Serilog" Version="4.1.0" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="8.0.3" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
6
src/iFileProxy.http
Normal file
6
src/iFileProxy.http
Normal file
|
@ -0,0 +1,6 @@
|
|||
@iFileProxy_HostAddress = http://localhost:5098
|
||||
|
||||
GET {{iFileProxy_HostAddress}}/weatherforecast/
|
||||
Accept: application/json
|
||||
|
||||
###
|
35
src/iFileProxy.json
Normal file
35
src/iFileProxy.json
Normal file
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
"Database": {
|
||||
"Common": {
|
||||
"Host": "47.243.56.137",
|
||||
"Port": 3306,
|
||||
"User": "iFileProxy",
|
||||
"Password": "i4TwYJeEt5pRfJze"
|
||||
},
|
||||
"Databases": [
|
||||
{
|
||||
"DatabaseName": "iFileProxy",
|
||||
"Description": "iFileProxy_Db"
|
||||
}
|
||||
]
|
||||
},
|
||||
"Download": {
|
||||
"SavePath": "./download/", // 临时文件保存位置
|
||||
"ThreadNum": 4, // 下载线程数
|
||||
"MaxAllowedFileSize": 65536, // 允许代理的最大文件尺寸
|
||||
"MaxParallelTasks": 4, // 同一时间最大并行任务数
|
||||
"Aria2cPath": "./lib/aria2c"
|
||||
},
|
||||
"Security": {
|
||||
"BlockedHost": [ // 禁止代理的主机
|
||||
"github.com"
|
||||
],
|
||||
"BlockedFileName": [ // 禁止代理的文件名
|
||||
"a.txt"
|
||||
],
|
||||
"BlockedClientIP": [ // 禁止使用服务的客户端IP
|
||||
"127.0.0.1"
|
||||
],
|
||||
"DailyRequestLimitPerIP": 200 // 单个IP每日最大请求次数限制
|
||||
}
|
||||
}
|
25
src/iFileProxy.sln
Normal file
25
src/iFileProxy.sln
Normal file
|
@ -0,0 +1,25 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.11.35327.3
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "iFileProxy", "iFileProxy.csproj", "{E343BD8A-27ED-47E2-B50D-E3000730E65E}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{E343BD8A-27ED-47E2-B50D-E3000730E65E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{E343BD8A-27ED-47E2-B50D-E3000730E65E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{E343BD8A-27ED-47E2-B50D-E3000730E65E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{E343BD8A-27ED-47E2-B50D-E3000730E65E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {B34F63CF-43DA-4E66-885E-5B935910E00E}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
Loading…
Reference in a new issue