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

优化部分代码实现细节
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")] [JsonPropertyName("MaxConnectionPoolSize")]
public uint MaxConnectionPoolSize { get; set; } = 50; public uint MaxConnectionPoolSize { get; set; } = 50;
[JsonPropertyName("ConnectionLifeTime")]
public uint ConnectionLifeTime { get; set; } = 120;
[JsonPropertyName("Common")] [JsonPropertyName("Common")]
public Common Common { get; set; } public Common Common { get; set; }

View file

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

View file

@ -4,12 +4,13 @@ using Microsoft.AspNetCore.Mvc;
using Serilog; using Serilog;
using System.Web; using System.Web;
using System.Net; using System.Net;
using iFileProxy.Helpers; // 用于 URL 解码 using iFileProxy.Helpers;
using iFileProxy.Services; // 用于 URL 解码
namespace iFileProxy.Controllers namespace iFileProxy.Controllers
{ {
[Route("/")] [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 readonly IHttpClientFactory _httpClientFactory = httpClientFactory;
private long SizeLimit = appConfig.StreamProxyOptions.SizeLimit; private long SizeLimit = appConfig.StreamProxyOptions.SizeLimit;
@ -151,12 +152,25 @@ namespace iFileProxy.Controllers
// 设置响应头:告诉浏览器这是一个文件下载 // 设置响应头:告诉浏览器这是一个文件下载
var fileName = HttpUtility.UrlEncode(Path.GetFileName(new Uri(targetUrl).LocalPath)); // 对文件名进行编码 防止报错 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"; Response.ContentType = response.Content.Headers.ContentType?.ToString() ?? "application/octet-stream";
if (contentLength > 0) if (contentLength > 0)
Response.ContentLength = contentLength; 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()) using (var stream = await response.Content.ReadAsStreamAsync())
{ {

View file

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

View file

@ -16,7 +16,10 @@
public async Task InvokeAsync(HttpContext context) 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; context.Response.StatusCode = 403;
await context.Response.WriteAsJsonAsync(new CommonRsp { Retcode = 403, Message = "你的IP地址已经被管理员拉入黑名单!"}); await context.Response.WriteAsJsonAsync(new CommonRsp { Retcode = 403, Message = "你的IP地址已经被管理员拉入黑名单!"});
@ -26,16 +29,6 @@
// 获取需要跟踪的路由列表 // 获取需要跟踪的路由列表
var routesToTrack = appConfig.SecurityOptions.RoutesToTrack; 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))) 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 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 (_IPAccessCountDict[dateStr][ipStr] >= appConfig.SecurityOptions.DailyRequestLimitPerIP) if (ipCounts[ipStr] >= appConfig.SecurityOptions.DailyRequestLimitPerIP)
{ {
context.Response.StatusCode = 200; context.Response.StatusCode = 200;
context.Response.ContentType = "application/json"; context.Response.ContentType = "application/json";
await context.Response.WriteAsync(JsonSerializer.Serialize(new CommonRsp { Retcode = 403, Message = "日请求次数超过限制! (GMT+8 时间00:00重置)" })); await context.Response.WriteAsync(JsonSerializer.Serialize(new CommonRsp { Retcode = 403, Message = "日请求次数超过限制! (GMT+8 时间00:00重置)" }));
return; return;
} }
_IPAccessCountDict[dateStr][ipStr]++; ipCounts[ipStr] = ++value;
} }
else else
{ {
_IPAccessCountDict[dateStr].Add(ipStr, 1); ipCounts.Add(ipStr, 1);
}
}
else
{
_IPAccessCountDict.Add(dateStr, new Dictionary<string, uint> { { ipStr, 1 } });
} }
await _next(context); await _next(context);

View file

@ -1,4 +1,5 @@
using iFileProxy.Helpers; using iFileProxy.Helpers;
using iFileProxy.Models;
using Microsoft.IdentityModel.Tokens; using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt; using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims; using System.Security.Claims;
@ -6,16 +7,10 @@ using System.Text;
namespace iFileProxy.Middleware namespace iFileProxy.Middleware
{ {
public class JwtMiddleware public class JwtMiddleware(RequestDelegate next, IConfiguration configuration)
{ {
private readonly RequestDelegate _next; private readonly RequestDelegate _next = next;
private readonly IConfiguration _configuration; private readonly IConfiguration _configuration = configuration;
public JwtMiddleware(RequestDelegate next, IConfiguration configuration)
{
_next = next;
_configuration = configuration;
}
public async Task InvokeAsync(HttpContext context) public async Task InvokeAsync(HttpContext context)
{ {
@ -56,6 +51,7 @@ namespace iFileProxy.Middleware
(ip != tokenIp && !IsLocalNetwork(ip, tokenIp))) // 允许本地网络IP变化 (ip != tokenIp && !IsLocalNetwork(ip, tokenIp))) // 允许本地网络IP变化
{ {
context.Response.StatusCode = 401; context.Response.StatusCode = 401;
await context.Response.WriteAsJsonAsync(new CommonRsp { Message = "环境发生变化, 请重新验证", Retcode = 401});
return; return;
} }
@ -71,7 +67,7 @@ namespace iFileProxy.Middleware
await _next(context); 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; if (string.IsNullOrEmpty(ip1) || string.IsNullOrEmpty(ip2)) return false;

View file

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

View file

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

View file

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

View file

@ -51,7 +51,7 @@ namespace iFileProxy.Services
// 获取数据库中超出生命周期的缓存数据 // 获取数据库中超出生命周期的缓存数据
string sql = $@"SELECT * FROM t_tasks_info string sql = $@"SELECT * FROM t_tasks_info
WHERE UNIX_TIMESTAMP(NOW()) - UNIX_TIMESTAMP(update_time) > {CACHE_LIFETIME} 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; List<TaskInfo>? taskInfos;
using (var conn = _dbGateService.GetAndOpenDBConn(DbConfigName.iFileProxy)) using (var conn = _dbGateService.GetAndOpenDBConn(DbConfigName.iFileProxy))

View file

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

View file

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