修复因数据库连接未及时释放而导致的频繁程序崩溃问题

优化部分代码实现细节
This commit is contained in:
root 2024-12-11 22:45:45 +08:00
parent 308787072d
commit db1b7bbb7d
12 changed files with 82 additions and 86 deletions

View file

@ -107,6 +107,9 @@ namespace iFileProxy.Config
[JsonPropertyName("MaxConnectionPoolSize")]
public uint MaxConnectionPoolSize { get; set; } = 50;
[JsonPropertyName("ConnectionLifeTime")]
public uint ConnectionLifeTime { get; set; } = 120;
[JsonPropertyName("Common")]
public Common Common { get; set; }

View file

@ -18,15 +18,6 @@ namespace iFileProxy.Controllers
public DatabaseGateService _dbGateService = dbGateService;
private readonly Serilog.ILogger _logger = Log.Logger.ForContext<ManagementController>();
// 查看任务详情
// 删除任务
// 停止任务
// 查看任务Process信息
// 立即执行任务
// 查看系统配置
// 获取全部任务信息
/// <summary>
/// 获取任务列表(支持分页)
/// </summary>

View file

@ -4,12 +4,13 @@ using Microsoft.AspNetCore.Mvc;
using Serilog;
using System.Web;
using System.Net;
using iFileProxy.Helpers; // 用于 URL 解码
using iFileProxy.Helpers;
using iFileProxy.Services; // 用于 URL 解码
namespace iFileProxy.Controllers
{
[Route("/")]
public class StreamProxyController(IHttpClientFactory httpClientFactory, AppConfig appConfig) : ControllerBase
public class StreamProxyController(IHttpClientFactory httpClientFactory, AppConfig appConfig, DatabaseGateService dbGateService) : ControllerBase
{
private readonly IHttpClientFactory _httpClientFactory = httpClientFactory;
private long SizeLimit = appConfig.StreamProxyOptions.SizeLimit;
@ -151,12 +152,25 @@ namespace iFileProxy.Controllers
// 设置响应头:告诉浏览器这是一个文件下载
var fileName = HttpUtility.UrlEncode(Path.GetFileName(new Uri(targetUrl).LocalPath)); // 对文件名进行编码 防止报错
Response.Headers.Add("Content-Disposition", $"attachment; filename={fileName}");
Response.Headers.Append("Content-Disposition", $"attachment; filename={fileName}");
Response.ContentType = response.Content.Headers.ContentType?.ToString() ?? "application/octet-stream";
if (contentLength > 0)
Response.ContentLength = contentLength;
await dbGateService.AddTaskInfoDataAsync(new TaskInfo
{
FileName = fileName,
Size = contentLength ?? -1L,
ClientIp = MasterHelper.GetClientIPAddr(HttpContext),
AddTime = DateTime.UtcNow,
UpdateTime = DateTime.UtcNow,
Status = TaskState.Stream,
TaskId = Guid.NewGuid().ToString(),
Url = proxyUrl,
Tag = "STREAM"
});
// 流式转发文件
using (var stream = await response.Content.ReadAsStreamAsync())
{

View file

@ -26,9 +26,9 @@ namespace iFileProxy.Controllers
[HttpPost]
[Route("/AddOfflineTask")]
public ActionResult<CommonRsp> AddOfflineTask()
public async Task<ActionResult<CommonRsp>> AddOfflineTask()
{
return _taskManager.AddTask(HttpContext) switch
return await _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!" }),
@ -122,8 +122,8 @@ namespace iFileProxy.Controllers
var fileInfo = new FileInfo(filePath);
// 设置响应头
Response.Headers.Add("Content-Disposition", $"attachment; filename=\"{fileName}\"");
Response.Headers.Add("Content-Length", fileInfo.Length.ToString());
Response.Headers.Append("Content-Disposition", $"attachment; filename=\"{fileName}\"");
Response.Headers.Append("Content-Length", fileInfo.Length.ToString());
// 使用 FileStream 进行流式传输
var fileStream = new FileStream(

View file

@ -16,7 +16,10 @@
public async Task InvokeAsync(HttpContext context)
{
if (appConfig.SecurityOptions.BlockedClientIP.IndexOf(MasterHelper.GetClientIPAddr(context)) != -1)
string ipStr = MasterHelper.GetClientIPAddr(context);
// 处理配置文件拉黑IP
if (appConfig.SecurityOptions.BlockedClientIP.IndexOf(ipStr) != -1)
{
context.Response.StatusCode = 403;
await context.Response.WriteAsJsonAsync(new CommonRsp { Retcode = 403, Message = "你的IP地址已经被管理员拉入黑名单!"});
@ -26,16 +29,6 @@
// 获取需要跟踪的路由列表
var routesToTrack = appConfig.SecurityOptions.RoutesToTrack;
// 检查当前请求的路径是否在需要跟踪的路由列表中
foreach (var p in routesToTrack)
{
if (context.Request.Path.ToString().StartsWith(p, StringComparison.OrdinalIgnoreCase))
{
// 如果匹配到需要跟踪的路由,继续处理请求
break;
}
}
// 如果没有匹配到需要跟踪的路由,直接调用下一个中间件
if (!routesToTrack.Any(p => context.Request.Path.ToString().StartsWith(p, StringComparison.OrdinalIgnoreCase)))
{
@ -44,29 +37,27 @@
}
string dateStr = DateTime.Now.ToString("yyyy-MM-dd");
string ipStr = MasterHelper.GetClientIPAddr(context);
if (_IPAccessCountDict.ContainsKey(dateStr))
if (!_IPAccessCountDict.TryGetValue(dateStr, out var ipCounts))
{
if (_IPAccessCountDict[dateStr].ContainsKey(ipStr))
ipCounts = [];
_IPAccessCountDict[dateStr] = ipCounts;
}
if (ipCounts.TryGetValue(ipStr, out uint value))
{
if (ipCounts[ipStr] >= appConfig.SecurityOptions.DailyRequestLimitPerIP)
{
if (_IPAccessCountDict[dateStr][ipStr] >= appConfig.SecurityOptions.DailyRequestLimitPerIP)
{
context.Response.StatusCode = 200;
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(JsonSerializer.Serialize(new CommonRsp { Retcode = 403, Message = "日请求次数超过限制! (GMT+8 时间00:00重置)" }));
return;
}
_IPAccessCountDict[dateStr][ipStr]++;
}
else
{
_IPAccessCountDict[dateStr].Add(ipStr, 1);
context.Response.StatusCode = 200;
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(JsonSerializer.Serialize(new CommonRsp { Retcode = 403, Message = "日请求次数超过限制! (GMT+8 时间00:00重置)" }));
return;
}
ipCounts[ipStr] = ++value;
}
else
{
_IPAccessCountDict.Add(dateStr, new Dictionary<string, uint> { { ipStr, 1 } });
ipCounts.Add(ipStr, 1);
}
await _next(context);

View file

@ -1,4 +1,5 @@
using iFileProxy.Helpers;
using iFileProxy.Models;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
@ -6,16 +7,10 @@ using System.Text;
namespace iFileProxy.Middleware
{
public class JwtMiddleware
public class JwtMiddleware(RequestDelegate next, IConfiguration configuration)
{
private readonly RequestDelegate _next;
private readonly IConfiguration _configuration;
public JwtMiddleware(RequestDelegate next, IConfiguration configuration)
{
_next = next;
_configuration = configuration;
}
private readonly RequestDelegate _next = next;
private readonly IConfiguration _configuration = configuration;
public async Task InvokeAsync(HttpContext context)
{
@ -30,7 +25,7 @@ namespace iFileProxy.Middleware
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_configuration["Jwt:Key"]);
// 验证token
tokenHandler.ValidateToken(token, new TokenValidationParameters
{
@ -44,18 +39,19 @@ namespace iFileProxy.Middleware
}, out SecurityToken validatedToken);
var jwtToken = (JwtSecurityToken)validatedToken;
// 验证客户端信息
var tokenFingerprint = jwtToken.Claims.First(x => x.Type == "fingerprint").Value;
var tokenUserAgent = jwtToken.Claims.First(x => x.Type == "userAgent").Value;
var tokenIp = jwtToken.Claims.First(x => x.Type == "ip").Value;
// 如果客户端信息不匹配,则拒绝访问
if (fingerprint != tokenFingerprint ||
userAgent != tokenUserAgent ||
if (fingerprint != tokenFingerprint ||
userAgent != tokenUserAgent ||
(ip != tokenIp && !IsLocalNetwork(ip, tokenIp))) // 允许本地网络IP变化
{
context.Response.StatusCode = 401;
await context.Response.WriteAsJsonAsync(new CommonRsp { Message = "环境发生变化, 请重新验证", Retcode = 401});
return;
}
@ -71,14 +67,14 @@ namespace iFileProxy.Middleware
await _next(context);
}
private bool IsLocalNetwork(string? ip1, string? ip2)
private static bool IsLocalNetwork(string? ip1, string? ip2)
{
if (string.IsNullOrEmpty(ip1) || string.IsNullOrEmpty(ip2)) return false;
// 检查是否都是内网IP
return (ip1.StartsWith("192.168.") && ip2.StartsWith("192.168.")) ||
(ip1.StartsWith("10.") && ip2.StartsWith("10.")) ||
(ip1.StartsWith("172.") && ip2.StartsWith("172."));
}
}
}
}

View file

@ -37,6 +37,10 @@
/// </summary>
Canceled = 7,
/// <summary>
/// 流任务
/// </summary>
Stream = 8,
/// <summary>
/// 其他
/// </summary>
Other = 999

View file

@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using Serilog;
using System.Text;
using System.Diagnostics;
namespace iFileProxy
{

View file

@ -148,8 +148,8 @@ namespace iFileProxy.Services
Password = db_password,
Pooling = true,
MinimumPoolSize = 1,
MaximumPoolSize = AppConfig.GetCurrConfig().Database.MaxConnectionPoolSize,
ConnectionLifeTime = 300 // 连接最大生命周期(秒)
MaximumPoolSize = _appConfig.Database.MaxConnectionPoolSize,
ConnectionLifeTime = _appConfig.Database.ConnectionLifeTime // 连接最大生命周期(秒)
};
try
@ -170,10 +170,9 @@ namespace iFileProxy.Services
foreach (var db in _dbDictionary)
{
_logger.Information($"[程序启动前配置验证] 正在测试数据库配置: {db.Key} ...");
MySqlConnection dbConn = new();
try
{
dbConn = GetAndOpenDBConn(db.Key);
using MySqlConnection dbConn = GetAndOpenDBConn(db.Key);
_logger.Information($"succ.");
}
catch (Exception)
@ -181,10 +180,6 @@ namespace iFileProxy.Services
_logger.Fatal($"=========== 数据库: {db.Key} 测试失败! ===========");
return false;
}
finally
{
dbConn.Close();
}
}
return true;
}
@ -250,7 +245,7 @@ namespace iFileProxy.Services
public List<TaskInfo> CheckCacheDependencies(string taskId,string ipAddr)
{
string sql = $"SELECT * FROM t_tasks_info WHERE `status` = @status AND `tag` = @tag AND `client_ip` <> @ip_addr";
MySqlConnection conn = GetAndOpenDBConn("iFileProxy_Db");
using MySqlConnection conn = GetAndOpenDBConn("iFileProxy_Db");
try
{
using MySqlCommand sqlCmd = new(sql, conn);
@ -271,7 +266,7 @@ namespace iFileProxy.Services
public string GetTaskListByStateAndIp(string ipAddr, TaskState status)
{
string sql = $"SELECT * FROM t_tasks_info WHERE client_ip = @ip_addr AND `status` = @status";
MySqlConnection conn = GetAndOpenDBConn("iFileProxy_Db");
using MySqlConnection conn = GetAndOpenDBConn("iFileProxy_Db");
try
{
using MySqlCommand sqlCmd = new(sql, conn);
@ -291,7 +286,7 @@ namespace iFileProxy.Services
public string GetTaskListByIP(string ipAddr)
{
string sql = $"SELECT * FROM t_tasks_info WHERE client_ip = @ip_addr";
MySqlConnection conn = GetAndOpenDBConn("iFileProxy_Db");
using MySqlConnection conn = GetAndOpenDBConn("iFileProxy_Db");
try
{
using MySqlCommand sqlCmd = new (sql,conn);
@ -308,7 +303,7 @@ namespace iFileProxy.Services
public string GetTaskInfoByTid(string tid)
{
string sql = $"SELECT * FROM t_tasks_info WHERE `tid` = @tid";
MySqlConnection conn = GetAndOpenDBConn("iFileProxy_Db");
using MySqlConnection conn = GetAndOpenDBConn("iFileProxy_Db");
try
{
using MySqlCommand sqlCmd = new(sql, conn);
@ -326,7 +321,7 @@ namespace iFileProxy.Services
public TaskInfo? QueryTaskInfo(string fileName, string url, long size, TaskState status)
{
string sql = $"SELECT * FROM t_tasks_info WHERE url = @url AND size = @size AND `status` = @status AND file_name = @fileName";
MySqlConnection conn = GetAndOpenDBConn("iFileProxy_Db");
using MySqlConnection conn = GetAndOpenDBConn("iFileProxy_Db");
try {
MySqlCommand sqlCmd = new MySqlCommand(sql, conn);
sqlCmd.Parameters.AddWithValue("@url", url);
@ -351,12 +346,12 @@ namespace iFileProxy.Services
}
public bool InsertTaskData(TaskInfo taskInfo)
public async Task<bool> AddTaskInfoDataAsync(TaskInfo 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`, `tag`) " +
"VALUES (@tid, @file_name, @client_ip, @add_time, @update_time, @status, @url, @size, @hash, @tag)";
MySqlConnection conn = GetAndOpenDBConn("iFileProxy_Db");
using MySqlConnection conn = GetAndOpenDBConn("iFileProxy_Db");
try
{
@ -372,7 +367,7 @@ namespace iFileProxy.Services
sqlCmd.Parameters.AddWithValue("@hash", taskInfo.Hash);
sqlCmd.Parameters.AddWithValue("@tag", taskInfo.Tag);
sqlCmd.ExecuteNonQuery();
await sqlCmd.ExecuteNonQueryAsync();
}
catch (Exception)
{
@ -388,7 +383,7 @@ namespace iFileProxy.Services
public bool UpdateFieldsData(string fieldsName, string taskUUID,object val)
{
string sql = $"UPDATE t_tasks_info set `{fieldsName}` = @Data WHERE `tid` = @tid";
MySqlConnection conn = GetAndOpenDBConn("iFileProxy_Db");
using MySqlConnection conn = GetAndOpenDBConn("iFileProxy_Db");
try
{
using MySqlCommand sqlCmd = new(sql, conn);
@ -531,7 +526,7 @@ namespace iFileProxy.Services
public PagedResult<TaskInfo> GetPagedTaskList(int page, int pageSize, TaskState? status = null)
{
// 构建基础SQL
var sql = new StringBuilder("SELECT * FROM t_tasks_info");
var sql = new StringBuilder($"SELECT * FROM t_tasks_info WHERE `status` <> {(int)TaskState.Stream}");
var parameters = new Dictionary<string, object>();
// 添加状态过滤
@ -547,7 +542,7 @@ namespace iFileProxy.Services
parameters.Add("@limit", pageSize);
// 获取总记录数
var countSql = "SELECT COUNT(*) FROM t_tasks_info" +
var countSql = $"SELECT COUNT(*) FROM t_tasks_info WHERE `status` <> {(int)TaskState.Stream}" +
(status.HasValue ? " AND status = @status" : "");
var totalCount = ExecuteScalar<long>(countSql, parameters);

View file

@ -51,7 +51,7 @@ namespace iFileProxy.Services
// 获取数据库中超出生命周期的缓存数据
string sql = $@"SELECT * FROM t_tasks_info
WHERE UNIX_TIMESTAMP(NOW()) - UNIX_TIMESTAMP(update_time) > {CACHE_LIFETIME}
AND (tag <> 'CLEANED' OR tag IS NULL)";
AND (tag <> 'CLEANED' AND tag <> 'STREAM' AND `status` <> {(int)TaskState.Stream} OR tag IS NULL )";
List<TaskInfo>? taskInfos;
using (var conn = _dbGateService.GetAndOpenDBConn(DbConfigName.iFileProxy))

View file

@ -89,7 +89,7 @@ namespace iFileProxy.Services
/// </summary>
/// <param name="c">HttpContext</param>
/// <returns></returns>
public TaskAddState AddTask(HttpContext c)
public async Task<TaskAddState> AddTask(HttpContext c)
{
string? clientIp = MasterHelper.GetClientIPAddr(c);
string? t_url = c.Request.Query["url"].FirstOrDefault() ?? c.Request.Form["url"].FirstOrDefault();
@ -152,7 +152,7 @@ namespace iFileProxy.Services
return TaskAddState.ErrQueueLengthLimit;
_pendingTasks.Enqueue(taskInfo); // 加入等待队列
var status = AddTaskInfoToDb(taskInfo,true);
var status = await AddTaskInfoToDb(taskInfo,true);
_logger.Information($"[TaskId: {taskInfo.TaskId}] Queuing...");
if (status != TaskAddState.Success) { return status; }
return TaskAddState.Pending;
@ -170,7 +170,7 @@ namespace iFileProxy.Services
}
}
return AddTaskInfoToDb(taskInfo);
return await AddTaskInfoToDb(taskInfo);
}
@ -181,9 +181,9 @@ namespace iFileProxy.Services
/// <param name="taskInfo">任务信息</param>
/// <param name="queuing">是否正在排队 若正在排队则不开始任务 等待任务调度</param>
/// <returns></returns>
public TaskAddState AddTaskInfoToDb(TaskInfo taskInfo, bool queuing = false)
public async Task<TaskAddState> AddTaskInfoToDb(TaskInfo taskInfo, bool queuing = false)
{
if (_dbGateService.InsertTaskData(taskInfo))
if (await _dbGateService.AddTaskInfoDataAsync(taskInfo))
{
if (!queuing) // 如果不是正在排队的任务
{

View file

@ -1,6 +1,7 @@
{
"Database": {
"MaxConnectionPoolSize": 100,
"MaxConnectionPoolSize": 100,
"ConnectionLifeTime": 120,
"Common": {
"Host": "47.243.56.137",
"Port": 3306,