Compare commits

...

4 commits

Author SHA1 Message Date
root
7fe9d05ffa 支持路由细粒度ACL和用户功能 2024-12-01 01:27:28 +08:00
root
cf25e63de7 完善功能 2024-11-30 19:24:00 +08:00
root
c3a4faae8f 实现了管理员后台 但是还未添加任何鉴权手段 2024-11-30 17:58:20 +08:00
root
bc589471f3 使代码更加合理 2024-11-30 12:11:40 +08:00
22 changed files with 2845 additions and 466 deletions

2
.gitignore vendored
View file

@ -1,5 +1,7 @@
src/bin
src/.vs
src/obj
src/lib
src/download
src/.config
*.user

View file

@ -0,0 +1,45 @@
using iFileProxy.Models;
using iFileProxy.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
namespace iFileProxy.Attributes
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class AuthorizeAttribute : Attribute, IAuthorizationFilter
{
private readonly UserMask[] _allowedMasks;
public AuthorizeAttribute(params UserMask[] allowedMasks)
{
_allowedMasks = allowedMasks;
}
public void OnAuthorization(AuthorizationFilterContext context)
{
var userId = context.HttpContext.Items["User"]?.ToString();
if (string.IsNullOrEmpty(userId))
{
context.Result = new JsonResult(new CommonRsp { Message = "未授权", Retcode = -1 })
{
StatusCode = StatusCodes.Status401Unauthorized
};
return;
}
if (_allowedMasks.Length > 0)
{
var dbGateService = context.HttpContext.RequestServices.GetRequiredService<DatabaseGateService>();
var user = dbGateService.GetUserByIdAsync(userId).Result;
if (user == null || !_allowedMasks.Contains(user.Mask) )
{
context.Result = new JsonResult(new CommonRsp { Message = "权限不足" ,Retcode = 1})
{
StatusCode = StatusCodes.Status403Forbidden
};
}
}
}
}
}

View file

@ -1,7 +1,8 @@
using iFileProxy.Helpers;
using iFileProxy.Services;
using Serilog;
using System.Text.Json;
using System.Text.Json.Serialization;
using MySql.Data.MySqlClient;
namespace iFileProxy.Config
{
@ -23,7 +24,7 @@ namespace iFileProxy.Config
[JsonPropertyName("Database")]
public Database Database { get; set; }
public static AppConfig? GetCurrConfig(string configPath = "iFileProxy.json")
public static AppConfig GetCurrConfig(string configPath = "iFileProxy.json")
{
if (File.Exists(configPath))
{
@ -41,7 +42,7 @@ namespace iFileProxy.Config
_logger.Fatal($"Config File: {configPath} not exists!");
return null;
}
public static void CheckAppConfig()
public static void CheckAppConfig(IServiceProvider serviceProvider)
{
AppConfig? c = GetCurrConfig();
if (c != null)
@ -59,7 +60,7 @@ namespace iFileProxy.Config
Environment.Exit(1);
}
DatabaseHelper databaseHelper = new(c);
var databaseHelper = serviceProvider.GetRequiredService<DatabaseGateService>();
databaseHelper.TestDbConfig();
}
else
@ -74,7 +75,7 @@ namespace iFileProxy.Config
{
public string SavePath { get; set; } = "./proxy_tmp/";
public uint ThreadNum { get; set; } = 1;
public uint MaxAllowedFileSize { get; set; }
public long MaxAllowedFileSize { get; set; }
public uint MaxParallelTasks { get; set; } = 4;
public uint MaxQueueLength {get; set; } = 60;
public string Aria2cPath { get; set; } = "./bin/aria2c";
@ -96,7 +97,7 @@ namespace iFileProxy.Config
public Common Common { get; set; }
[JsonPropertyName("Databases")]
public DB[] Databases { get; set; }
public List<DB> Databases { get; set; }
}
public partial class Common

View file

@ -0,0 +1,361 @@
using iFileProxy.Config;
using iFileProxy.Models;
using iFileProxy.Services;
using Microsoft.AspNetCore.Mvc;
using Serilog;
using System.Text.Json;
using iFileProxy.Attributes;
namespace iFileProxy.Controllers
{
[Authorize(UserMask.Admin,UserMask.SuperAdmin)]
[Route("[controller]")]
[ApiController]
public class ManagementController(TaskManager taskManager, DatabaseGateService dbGateService) : ControllerBase
{
public TaskManager _taskManager = taskManager;
public DatabaseGateService _dbGateService = dbGateService;
private readonly Serilog.ILogger _logger = Log.Logger.ForContext<DatabaseGateService>();
// 查看任务详情
// 删除任务
// 停止任务
// 查看任务Process信息
// 立即执行任务
// 查看系统配置
// 获取全部任务信息
/// <summary>
/// 获取任务列表(支持分页)
/// </summary>
/// <param name="page">页码从1开始</param>
/// <param name="pageSize">每页数量</param>
/// <param name="status">可选的任务状态过滤</param>
/// <returns>分页后的任务列表</returns>
[HttpGet("GetTaskList")]
public ActionResult<CommonRsp> GetTaskList([FromQuery] int page = 1, [FromQuery] int pageSize = 10, [FromQuery] TaskState? status = null)
{
try
{
var result = _dbGateService.GetPagedTaskList(page, pageSize, status);
result.Data.ForEach(task =>
{
if (task.Status == TaskState.Queuing)
task.QueuePosition = _taskManager.GetQueuePosition(task.TaskId);
});
return Ok(new CommonRsp
{
Retcode = 0,
Message = "success",
Data = result
});
}
catch (Exception ex)
{
_logger.Error($"获取任务列表失败: {ex.Message}");
return Ok(new CommonRsp
{
Retcode = 1,
Message = "获取任务列表失败"
});
}
}
[HttpGet("GetSystemConfig")]
public ActionResult<CommonRsp> GetSystemConfig()
{
var c = AppConfig.GetCurrConfig();
c.Database.Databases.ForEach(db => {
db.Password = "**********";
});
return Ok(new CommonRsp { Retcode = 0 , Data = c,Message = "succ"});
}
/// <summary>
/// 删除指定任务
/// </summary>
/// <param name="taskId">任务ID</param>
/// <returns>删除结果</returns>
[HttpDelete("DeleteTask/{taskId}")]
public ActionResult<CommonRsp> DeleteTask(string taskId)
{
try
{
// 先检查任务是否存在
var taskInfo = JsonSerializer.Deserialize<List<TaskInfo>>(_dbGateService.GetTaskInfoByTid(taskId))[0];
if (taskInfo == null)
{
return Ok(new CommonRsp
{
Retcode = 1,
Message = "任务不存在"
});
}
// 如果任务正在运行,先尝试中断
if (taskInfo.Status == TaskState.Running && _taskManager.GetRunningTaskInfo(taskId) != null)
{
_taskManager.TryKillTask(_taskManager.GetRunningTaskInfo(taskId));
}
// 删除任务记录
var result = _dbGateService.DeleteTaskByTid(taskId);
return Ok(new CommonRsp
{
Retcode = result ? 0 : 1,
Message = result ? "删除成功" : "删除失败"
});
}
catch (Exception ex)
{
_logger.Error($"删除任务失败: {ex.Message}");
return Ok(new CommonRsp
{
Retcode = 1,
Message = "删除任务失败"
});
}
}
/// <summary>
/// 获取任务详细信息
/// </summary>
/// <param name="taskId">任务ID</param>
/// <returns>任务详细信息</returns>
[HttpGet("GetTaskDetail/{taskId}")]
public ActionResult<CommonRsp> GetTaskDetail(string taskId)
{
try
{
var taskInfo = _dbGateService.GetTaskDetail(taskId);
if (taskInfo == null)
{
return Ok(new CommonRsp
{
Retcode = 1,
Message = "任务不存在"
});
}
return Ok(new CommonRsp
{
Retcode = 0,
Message = "success",
Data = taskInfo
});
}
catch (Exception ex)
{
_logger.Error($"获取任务详情失败: {ex.Message}");
return Ok(new CommonRsp
{
Retcode = 1,
Message = "获取任务详情失败"
});
}
}
/// <summary>
/// 终止正在运行的任务
/// </summary>
/// <param name="taskId">任务ID</param>
/// <returns>操作结果</returns>
[HttpPost("KillTask/{taskId}")]
public ActionResult<CommonRsp> KillTask(string taskId)
{
try
{
// 先获取任务信息
var taskInfo = _taskManager.GetRunningTaskInfo(taskId);
if (taskInfo == null)
{
return Ok(new CommonRsp
{
Retcode = 1,
Message = "任务不存在"
});
}
// 检查任务是否在运行
if (taskInfo.Status != TaskState.Running)
{
return Ok(new CommonRsp
{
Retcode = 1,
Message = "任务不在运行状态"
});
}
// 尝试终止任务
_taskManager.TryKillTask(taskInfo);
return Ok(new CommonRsp
{
Retcode = 0,
Message = "任务已终止"
});
}
catch (Exception ex)
{
_logger.Error($"终止任务失败: {ex.Message}");
return Ok(new CommonRsp
{
Retcode = 1,
Message = "终止任务失败"
});
}
}
/// <summary>
/// 获取任务状态概览数据
/// </summary>
/// <returns>各状态任务的数量统计</returns>
[HttpGet("GetTaskOverview")]
public ActionResult<CommonRsp> GetTaskOverview()
{
try
{
var overview = _dbGateService.GetTaskStatusOverview();
return Ok(new CommonRsp
{
Retcode = 0,
Message = "success",
Data = overview
});
}
catch (Exception ex)
{
_logger.Error($"获取任务概览失败: {ex.Message}");
return Ok(new CommonRsp
{
Retcode = 1,
Message = "获取任务概览失败"
});
}
}
/// <summary>
/// 获取正在运行任务的进程信息
/// </summary>
/// <param name="taskId">任务ID</param>
/// <returns>进程详细信息</returns>
[HttpGet("GetProcessInfo/{taskId}")]
public ActionResult<CommonRsp> GetProcessInfo(string taskId)
{
try
{
var processInfo = _taskManager.GetProcessInfo(taskId);
if (processInfo == null)
{
return Ok(new CommonRsp
{
Retcode = 1,
Message = "任务不存在或未在运行状态"
});
}
return Ok(new CommonRsp
{
Retcode = 0,
Message = "success",
Data = processInfo
});
}
catch (Exception ex)
{
_logger.Error($"获取进程信息失败: {ex.Message}");
return Ok(new CommonRsp
{
Retcode = 1,
Message = "获取进程信息失败"
});
}
}
/// <summary>
/// 重试失败的任务
/// </summary>
/// <param name="taskId">任务ID</param>
/// <returns>重试结果</returns>
[HttpPost("RetryTask/{taskId}")]
public ActionResult<CommonRsp> RetryTask(string taskId)
{
try
{
var result = _taskManager.RetryTask(taskId);
return Ok(new CommonRsp
{
Retcode = result ? 0 : 1,
Message = result ? "任务重试已开始" : "任务重试失败"
});
}
catch (Exception ex)
{
_logger.Error($"重试任务失败: {ex.Message}");
return Ok(new CommonRsp
{
Retcode = 1,
Message = "重试任务失败"
});
}
}
/// <summary>
/// 将任务移动到等待队列最前面
/// </summary>
/// <param name="taskId">任务ID</param>
/// <returns>操作结果</returns>
[HttpPost("PrioritizeTask/{taskId}")]
public ActionResult<CommonRsp> PrioritizeTask(string taskId)
{
try
{
var result = _taskManager.PrioritizeTask(taskId);
return Ok(new CommonRsp
{
Retcode = result ? 0 : 1,
Message = result ? "任务已移至队列首位" : "调整任务优先级失败"
});
}
catch (Exception ex)
{
_logger.Error($"调整任务优先级失败: {ex.Message}");
return Ok(new CommonRsp
{
Retcode = 1,
Message = "调整任务优先级失败"
});
}
}
/// <summary>
/// 立即执行指定任务
/// </summary>
/// <param name="taskId">任务ID</param>
/// <returns>操作结果</returns>
[HttpPost("ExecuteImmediately/{taskId}")]
public ActionResult<CommonRsp> ExecuteImmediately(string taskId)
{
try
{
var result = _taskManager.ExecuteImmediately(taskId);
return Ok(new CommonRsp
{
Retcode = result ? 0 : 1,
Message = result ? "任务已开始执行" : "立即执行任务失败"
});
}
catch (Exception ex)
{
_logger.Error($"立即执行任务失败: {ex.Message}");
return Ok(new CommonRsp
{
Retcode = 1,
Message = "立即执行任务失败"
});
}
}
}
}

View file

@ -0,0 +1,607 @@
using iFileProxy.Attributes;
using iFileProxy.Models;
using iFileProxy.Services;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
namespace iFileProxy.Controllers
{
[Route("[controller]")]
[ApiController]
public class UserController : ControllerBase
{
private readonly AuthService _authService;
private readonly ILogger<UserController> _logger;
public UserController(AuthService authService, ILogger<UserController> logger)
{
_authService = authService;
_logger = logger;
}
/// <summary>
/// 用户注册
/// </summary>
[HttpPost("register")]
public async Task<ActionResult<CommonRsp>> Register([FromBody] RegisterRequest request)
{
try
{
var ip = HttpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown";
var (success, message) = await _authService.RegisterAsync(request.Username, request.Password, ip, request.NickName);
return Ok(new CommonRsp
{
Retcode = success ? 0 : 1,
Message = message
});
}
catch (Exception ex)
{
_logger.LogError(ex, "注册失败");
return Ok(new CommonRsp
{
Retcode = 1,
Message = "注册失败"
});
}
}
/// <summary>
/// 用户登录
/// </summary>
[HttpPost("login")]
public async Task<ActionResult<CommonRsp>> Login([FromBody] LoginRequest request)
{
try
{
var ip = HttpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown";
var (success, token, message) = await _authService.LoginAsync(request.Username, request.Password, ip);
return Ok(new CommonRsp
{
Retcode = success ? 0 : 1,
Message = message,
Data = success ? new { token } : null
});
}
catch (Exception ex)
{
_logger.LogError(ex, "登录失败");
return Ok(new CommonRsp
{
Retcode = 1,
Message = "登录失败"
});
}
}
/// <summary>
/// 获取当前用户信息
/// </summary>
[Authorize]
[HttpGet("info")]
public async Task<ActionResult<CommonRsp>> GetUserInfo()
{
try
{
var userId = HttpContext.Items["User"]?.ToString();
if (string.IsNullOrEmpty(userId))
{
return Ok(new CommonRsp
{
Retcode = 1,
Message = "未登录"
});
}
var dbGateService = HttpContext.RequestServices.GetRequiredService<DatabaseGateService>();
var user = await dbGateService.GetUserByIdAsync(userId);
if (user == null)
{
return Ok(new CommonRsp
{
Retcode = 1,
Message = "用户不存在"
});
}
_logger.LogInformation(JsonConvert.SerializeObject(user));
// 不返回敏感信息
return Ok(new CommonRsp
{
Retcode = 0,
Message = "success",
Data = new
{
user.UserId,
user.Username,
user.Mask,
user.State,
user.CreateTime,
user.LastLoginTime,
user.LastLoginIP,
user.Nickname
}
});
}
catch (Exception ex)
{
_logger.LogError(ex, "获取用户信息失败");
return Ok(new CommonRsp
{
Retcode = 1,
Message = "获取用户信息失败"
});
}
}
/// <summary>
/// 修改用户权限
/// </summary>
[Authorize(UserMask.SuperAdmin)]
[HttpPost("updateMask/{userId}")]
public async Task<ActionResult<CommonRsp>> UpdateUserMask(string userId, [FromBody] UpdateUserMaskRequest request)
{
try
{
var dbGateService = HttpContext.RequestServices.GetRequiredService<DatabaseGateService>();
// 获取目标用户
var targetUser = await dbGateService.GetUserByIdAsync(userId);
if (targetUser == null)
{
return Ok(new CommonRsp
{
Retcode = 1,
Message = "用户不存在"
});
}
// 不允许修改超级管理员的权限
if (targetUser.Mask == UserMask.SuperAdmin)
{
return Ok(new CommonRsp
{
Retcode = 1,
Message = "不能修改超级管理员的权限"
});
}
// 更新权限
targetUser.Mask = request.NewMask;
var success = await dbGateService.UpdateUserAsync(targetUser);
// 记录操作事件
var operatorId = HttpContext.Items["User"]?.ToString();
if (success && !string.IsNullOrEmpty(operatorId))
{
var userEvent = new UserEvent
{
UserId = targetUser.UserId,
EventType = UserEventType.UpdateMask,
EventIP = HttpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown",
EventDetail = $"权限被修改为 {request.NewMask}操作者ID: {operatorId}"
};
await dbGateService.CreateUserEventAsync(userEvent);
}
return Ok(new CommonRsp
{
Retcode = success ? 0 : 1,
Message = success ? "权限修改成功" : "权限修改失败"
});
}
catch (Exception ex)
{
_logger.LogError(ex, "修改用户权限失败");
return Ok(new CommonRsp
{
Retcode = 1,
Message = "修改用户权限失败"
});
}
}
/// <summary>
/// 获取用户列表(分页)
/// </summary>
[Authorize(UserMask.SuperAdmin)]
[HttpGet("list")]
public async Task<ActionResult<CommonRsp>> GetUserList([FromQuery] int page = 1, [FromQuery] int pageSize = 10, [FromQuery] UserMask? mask = null)
{
try
{
var dbGateService = HttpContext.RequestServices.GetRequiredService<DatabaseGateService>();
var result = await dbGateService.GetPagedUserListAsync(page, pageSize, mask);
// 移除敏感信息
foreach (var user in result.Data)
{
user.PasswordHash = "***************";
}
return Ok(new CommonRsp
{
Retcode = 0,
Message = "success",
Data = result
});
}
catch (Exception ex)
{
_logger.LogError(ex, "获取用户列表失败");
return Ok(new CommonRsp
{
Retcode = 1,
Message = "获取用户列表失败"
});
}
}
/// <summary>
/// 修改个人昵称
/// </summary>
[Authorize]
[HttpPost("updateNickname")]
public async Task<ActionResult<CommonRsp>> UpdateNickname([FromBody] UpdateNicknameRequest request)
{
try
{
var userId = HttpContext.Items["User"]?.ToString();
if (string.IsNullOrEmpty(userId))
{
return Ok(new CommonRsp
{
Retcode = 1,
Message = "未登录"
});
}
var dbGateService = HttpContext.RequestServices.GetRequiredService<DatabaseGateService>();
var user = await dbGateService.GetUserByIdAsync(userId);
if (user == null)
{
return Ok(new CommonRsp
{
Retcode = 1,
Message = "用户不存在"
});
}
user.Nickname = request.NewNickname;
var success = await dbGateService.UpdateUserAsync(user);
return Ok(new CommonRsp
{
Retcode = success ? 0 : 1,
Message = success ? "昵称修改成功" : "昵称修改失败"
});
}
catch (Exception ex)
{
_logger.LogError(ex, "修改昵称失败");
return Ok(new CommonRsp
{
Retcode = 1,
Message = "修改昵称失败"
});
}
}
/// <summary>
/// 修改密码
/// </summary>
[Authorize]
[HttpPost("updatePassword")]
public async Task<ActionResult<CommonRsp>> UpdatePassword([FromBody] UpdatePasswordRequest request)
{
try
{
var userId = HttpContext.Items["User"]?.ToString();
if (string.IsNullOrEmpty(userId))
{
return Ok(new CommonRsp
{
Retcode = 1,
Message = "未登录"
});
}
var dbGateService = HttpContext.RequestServices.GetRequiredService<DatabaseGateService>();
var user = await dbGateService.GetUserByIdAsync(userId);
if (user == null)
{
return Ok(new CommonRsp
{
Retcode = 1,
Message = "用户不存在"
});
}
// 验证旧密码
if (!BCrypt.Net.BCrypt.Verify(request.OldPassword, user.PasswordHash))
{
return Ok(new CommonRsp
{
Retcode = 1,
Message = "旧密码错误"
});
}
// 更新密码
user.PasswordHash = BCrypt.Net.BCrypt.HashPassword(request.NewPassword);
var success = await dbGateService.UpdateUserAsync(user);
// 记录密码修改事件
if (success)
{
var userEvent = new UserEvent
{
UserId = user.UserId,
EventType = UserEventType.UpdatePassword,
EventIP = HttpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown",
EventDetail = "密码已修改"
};
await dbGateService.CreateUserEventAsync(userEvent);
}
return Ok(new CommonRsp
{
Retcode = success ? 0 : 1,
Message = success ? "密码修改成功" : "密码修改失败"
});
}
catch (Exception ex)
{
_logger.LogError(ex, "修改密码失败");
return Ok(new CommonRsp
{
Retcode = 1,
Message = "修改密码失败"
});
}
}
/// <summary>
/// 超级管理员修改用户密码
/// </summary>
[Authorize(UserMask.SuperAdmin)]
[HttpPost("resetPassword/{userId}")]
public async Task<ActionResult<CommonRsp>> ResetUserPassword(string userId, [FromBody] ResetPasswordRequest request)
{
try
{
var dbGateService = HttpContext.RequestServices.GetRequiredService<DatabaseGateService>();
// 获取目标用户
var targetUser = await dbGateService.GetUserByIdAsync(userId);
if (targetUser == null)
{
return Ok(new CommonRsp
{
Retcode = 1,
Message = "用户不存在"
});
}
// 不允许修改超级管理员的密码
if (targetUser.Mask == UserMask.SuperAdmin && targetUser.UserId != HttpContext.Items["User"]?.ToString())
{
return Ok(new CommonRsp
{
Retcode = 1,
Message = "不能修改其他超级管理员的密码"
});
}
// 更新密码
targetUser.PasswordHash = BCrypt.Net.BCrypt.HashPassword(request.NewPassword);
var success = await dbGateService.UpdateUserAsync(targetUser);
// 记录密码重置事件
if (success)
{
var operatorId = HttpContext.Items["User"]?.ToString();
var userEvent = new UserEvent
{
UserId = targetUser.UserId,
EventType = UserEventType.UpdatePassword,
EventIP = HttpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown",
EventDetail = $"密码被管理员重置操作者ID: {operatorId}"
};
await dbGateService.CreateUserEventAsync(userEvent);
}
return Ok(new CommonRsp
{
Retcode = success ? 0 : 1,
Message = success ? "密码重置成功" : "密码重置失败"
});
}
catch (Exception ex)
{
_logger.LogError(ex, "重置密码失败");
return Ok(new CommonRsp
{
Retcode = 1,
Message = "重置密码失败"
});
}
}
/// <summary>
/// 获取用户事件列表
/// </summary>
[Authorize]
[HttpGet("events")]
public async Task<ActionResult<CommonRsp>> GetUserEvents(
[FromQuery] int page = 1,
[FromQuery] int pageSize = 10,
[FromQuery] string? keyword = null,
[FromQuery] string? userId = null)
{
try
{
var currentUserId = HttpContext.Items["User"]?.ToString();
if (string.IsNullOrEmpty(currentUserId))
{
return Ok(new CommonRsp
{
Retcode = 1,
Message = "未登录"
});
}
var dbGateService = HttpContext.RequestServices.GetRequiredService<DatabaseGateService>();
var currentUser = await dbGateService.GetUserByIdAsync(currentUserId);
// 如果不是超级管理员,只能查看自己的事件
if (currentUser?.Mask != UserMask.SuperAdmin)
{
userId = currentUserId;
}
// 如果是管理员且未指定用户ID则查看所有事件
else if (string.IsNullOrEmpty(userId))
{
userId = null; // 查询所有用户的事件
}
var result = await dbGateService.GetPagedUserEventsAsync(page, pageSize, userId, keyword);
return Ok(new CommonRsp
{
Retcode = 0,
Message = "success",
Data = result
});
}
catch (Exception ex)
{
_logger.LogError(ex, "获取用户事件失败");
return Ok(new CommonRsp
{
Retcode = 1,
Message = "获取用户事件失败"
});
}
}
/// <summary>
/// 管理员修改用户昵称
/// </summary>
[Authorize(UserMask.Admin, UserMask.SuperAdmin)]
[HttpPost("updateNickname/{userId}")]
public async Task<ActionResult<CommonRsp>> UpdateUserNickname(string userId, [FromBody] UpdateNicknameRequest request)
{
try
{
var dbGateService = HttpContext.RequestServices.GetRequiredService<DatabaseGateService>();
// 获取目标用户
var targetUser = await dbGateService.GetUserByIdAsync(userId);
if (targetUser == null)
{
return Ok(new CommonRsp
{
Retcode = 1,
Message = "用户不存在"
});
}
// 获取当前操作者信息
var operatorId = HttpContext.Items["User"]?.ToString();
var operator_ = await dbGateService.GetUserByIdAsync(operatorId);
// 如果目标用户是超级管理员,只有超级管理员自己能修改自己的昵称
if (targetUser.Mask == UserMask.SuperAdmin &&
(operator_?.Mask != UserMask.SuperAdmin || targetUser.UserId != operatorId))
{
return Ok(new CommonRsp
{
Retcode = 1,
Message = "不能修改超级管理员的昵称"
});
}
// 如果目标用户是管理员,只有超级管理员能修改
if (targetUser.Mask == UserMask.Admin && operator_?.Mask != UserMask.SuperAdmin)
{
return Ok(new CommonRsp
{
Retcode = 1,
Message = "只有超级管理员能修改管理员的昵称"
});
}
// 更新昵称
targetUser.Nickname = request.NewNickname;
var success = await dbGateService.UpdateUserAsync(targetUser);
// 记录操作事件
if (success)
{
var userEvent = new UserEvent
{
UserId = targetUser.UserId,
EventType = UserEventType.UpdateNickname, // 需要添加这个事件类型
EventIP = HttpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown",
EventDetail = $"昵称被修改为 {request.NewNickname}操作者ID: {operatorId}"
};
await dbGateService.CreateUserEventAsync(userEvent);
}
return Ok(new CommonRsp
{
Retcode = success ? 0 : 1,
Message = success ? "昵称修改成功" : "昵称修改失败"
});
}
catch (Exception ex)
{
_logger.LogError(ex, "修改用户昵称失败");
return Ok(new CommonRsp
{
Retcode = 1,
Message = "修改用户昵称失败"
});
}
}
public class UpdateUserMaskRequest
{
public UserMask NewMask { get; set; }
}
public class UpdateNicknameRequest
{
public string NewNickname { get; set; } = string.Empty;
}
public class UpdatePasswordRequest
{
public string OldPassword { get; set; } = string.Empty;
public string NewPassword { get; set; } = string.Empty;
}
public class ResetPasswordRequest
{
public string NewPassword { get; set; } = string.Empty;
}
}
public class RegisterRequest
{
public string Username { get; set; } = string.Empty;
public string Password { get; set; } = string.Empty;
public string NickName { get; set; } = string.Empty;
}
public class LoginRequest
{
public string Username { get; set; } = string.Empty;
public string Password { get; set; } = string.Empty;
}
}

View file

@ -1,4 +1,5 @@
using iFileProxy.Helpers;
using iFileProxy.Services;
using iFileProxy.Models;
using iFileProxy.Config;
using Microsoft.AspNetCore.Mvc;
@ -14,7 +15,11 @@ namespace iFileProxy.Controllers
[ApiController]
public class VisitorManagementController : ControllerBase
{
static DatabaseHelper _dbHelper = new DatabaseHelper(AppConfig.GetCurrConfig());
private readonly DatabaseGateService _dbGateService;
public VisitorManagementController(DatabaseGateService dbGate)
{
_dbGateService = dbGate;
}
private readonly static Serilog.ILogger _logger = Log.Logger.ForContext<VisitorManagementController>();
@ -22,14 +27,14 @@ namespace iFileProxy.Controllers
public ActionResult<CommonRsp> DeleteInfo()
{
string ipAddr = MasterHelper.GetClientIPAddr(HttpContext);
var d = JsonConvert.DeserializeObject<List<TaskInfo>>(_dbHelper.GetTaskListByStateAndIp(ipAddr, TaskState.Running));
var d = JsonConvert.DeserializeObject<List<TaskInfo>>(_dbGateService.GetTaskListByStateAndIp(ipAddr, TaskState.Running));
int rd = (d != null) ? d.Count : 0;
if (rd <= 0)
{
d = JsonConvert.DeserializeObject<List<TaskInfo>>(_dbHelper.GetTaskListByIP(ipAddr));
d = JsonConvert.DeserializeObject<List<TaskInfo>>(_dbGateService.GetTaskListByIP(ipAddr));
foreach (var taskInfo in d)
{
var dep = _dbHelper.CheckCacheDependencies(taskInfo.TaskId,ipAddr);
var dep = _dbGateService.CheckCacheDependencies(taskInfo.TaskId,ipAddr);
if (dep.Count <= 0)
{
if (MasterHelper.CheckDownloadFileExists(taskInfo.FileName))
@ -42,7 +47,7 @@ namespace iFileProxy.Controllers
_logger.Warning($"准备删除的文件正在被以下Task所依赖: {JsonConvert.SerializeObject(dep)} 文件将不会被删除");
}
return Ok(new CommonRsp() { Retcode = 0, Message = "succ", Data = _dbHelper.DeleteTaskInfoByIpAddr(ipAddr)});
return Ok(new CommonRsp() { Retcode = 0, Message = "succ", Data = _dbGateService.DeleteTaskInfoByIpAddr(ipAddr)});
}
else
return Ok(new CommonRsp() {Retcode = -1, Message = "你还有正在运行的任务, 你现在不能删除你的数据, 请稍后等待任务完成后重试", Data = rd });

View file

@ -10,14 +10,20 @@ namespace iFileProxy.Controllers
{
public class iProxyController : ControllerBase
{
static readonly TaskManager taskManager = new ();
static AppConfig? appConfig = AppConfig.GetCurrConfig();
private readonly TaskManager _taskManager;
private readonly AppConfig _appConfig;
public iProxyController(TaskManager taskManager, AppConfig appConfig)
{
_taskManager = taskManager;
_appConfig = appConfig;
}
[HttpPost]
[Route("/AddOfflineTask")]
public ActionResult<CommonRsp> AddOfflineTask()
{
return taskManager.AddTask(HttpContext) switch
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!" }),
@ -43,12 +49,12 @@ namespace iFileProxy.Controllers
[Route("/GetMyTasks")]
public ActionResult<CommonRsp> GetMyTasks()
{
var data = taskManager.GetTaskListByIpAddr(HttpContext);
var data = _taskManager.GetTaskListByIpAddr(HttpContext);
foreach (var d in data)
{
if (d.Status == TaskState.Queuing)
{
d.QueuePosition = taskManager.GetQueuePosition(d.TaskId);
d.QueuePosition = _taskManager.GetQueuePosition(d.TaskId);
}
}
return Ok(new CommonRsp() { Retcode = 0, Data = data ,Message = "succ" });
@ -58,7 +64,7 @@ namespace iFileProxy.Controllers
[HttpGet]
[Route("/GetServerLoad")]
public ActionResult<CommonRsp> GetServerLoad() {
return Ok(new CommonRsp { Retcode = 0, Message = "succ", Data = taskManager.GetServerTaskLoadInfo() });
return Ok(new CommonRsp { Retcode = 0, Message = "succ", Data = _taskManager.GetServerTaskLoadInfo() });
}
[HttpGet]
@ -67,12 +73,12 @@ namespace iFileProxy.Controllers
public async Task<IActionResult> DownloadFile(string taskID)
{
string fileName = "";
var d = taskManager.GetTaskListByIpAddr(HttpContext);
var taskInfo = taskManager.GetTaskInfo(taskID);
var d = _taskManager.GetTaskListByIpAddr(HttpContext);
var taskInfo = _taskManager.GetTaskInfo(taskID);
if ((!appConfig.SecurityOptions.AllowDifferentIPsForDownload && d.Where(x => x.TaskId == taskID).Any()) || taskInfo != null)
if ((!_appConfig.SecurityOptions.AllowDifferentIPsForDownload && d.Where(x => x.TaskId == taskID).Any()) || taskInfo != null)
{
if (appConfig.SecurityOptions.AllowDifferentIPsForDownload)
if (_appConfig.SecurityOptions.AllowDifferentIPsForDownload)
{
fileName = taskInfo[0].FileName;
}
@ -83,7 +89,7 @@ namespace iFileProxy.Controllers
if (fileName == null)
return Ok(new CommonRsp() { Message = "file not exists or taskId error", Retcode = -1 });
var filePath = Path.Combine(AppConfig.GetCurrConfig().DownloadOptions.SavePath, fileName);
var filePath = Path.Combine(_appConfig.DownloadOptions.SavePath, fileName);
if (!MasterHelper.CheckDownloadFileExists(fileName))
{
return Ok(new CommonRsp() { Message = "file not exists", Retcode = 1 });

View file

@ -1,402 +0,0 @@
using iFileProxy.Config;
using Serilog;
using System.Data;
using System.Text.Json;
using MySql.Data.MySqlClient;
using iFileProxy.Models;
using Newtonsoft.Json;
namespace iFileProxy.Helpers
{
public class DatabaseHelper
{
Database _db;
AppConfig _appConfig;
private readonly static Serilog.ILogger _logger = Log.Logger.ForContext<DatabaseHelper>();
Dictionary<string, DB> _dbDictionary = [];
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 GetAndOpenDBConn(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;
}
public bool TestDbConfig()
{
foreach (var db in _dbDictionary)
{
_logger.Information($"[程序启动前配置验证] 正在测试数据库配置: {db.Key} ...");
MySqlConnection dbConn = new();
try
{
dbConn = GetAndOpenDBConn(db.Key);
_logger.Information($"succ.");
}
catch (Exception)
{
_logger.Fatal($"=========== 数据库: {db.Key} 测试失败! ===========");
return false;
}
finally
{
dbConn.Close();
}
}
return true;
}
/// <summary>
/// 获取一个json格式的数据表
/// </summary>
/// <param name="sql"></param>
/// <param name="conn"></param>
/// <returns></returns>
public static string QueryTableData(MySqlCommand sqlCmd)
{
DataTable dataTable = new();
using (MySqlDataAdapter adapter = new (sqlCmd))
adapter.Fill(dataTable);
return JsonConvert.SerializeObject(dataTable);
}
/// <summary>
/// 获取一个json格式的数据表
/// </summary>
/// <param name="sql"></param>
/// <param name="conn"></param>
/// <returns></returns>
public string QueryTableData(string sql,string dbConfName)
{
DataTable dataTable = new();
using (MySqlDataAdapter adapter = new(new MySqlCommand(sql,GetAndOpenDBConn(dbConfName))))
adapter.Fill(dataTable);
return JsonConvert.SerializeObject(dataTable);
}
/// <summary>
/// 内部查询数据专用 当此方法暴露给C端可能造成sql注入等安全问题
/// </summary>
/// <param name="sql">SQL语句</param>
/// <param name="dbConfName">配置文件中的Description字段</param>
/// <returns>影响的行数</returns>
public int Query(string sql, string dbConfName)
{
using MySqlCommand sqlCmd = new (sql, GetAndOpenDBConn(dbConfName));
int n = sqlCmd.ExecuteNonQuery();
_logger.Debug($"查询完成, 受影响的行数: {n}");
return n;
}
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");
try
{
using MySqlCommand sqlCmd = new(sql, conn);
sqlCmd.Parameters.AddWithValue("@tag", $"REDIRECT:{taskId}");
sqlCmd.Parameters.AddWithValue("@status", TaskState.Cached);
sqlCmd.Parameters.AddWithValue("@ip_addr", ipAddr);
return JsonConvert.DeserializeObject<List<TaskInfo>>( QueryTableData(sqlCmd));
}
catch (Exception e)
{
_logger.Error($"无法获取任务列表: {e.Message}");
throw;
}
finally { conn.Close(); }
}
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");
try
{
using MySqlCommand sqlCmd = new(sql, conn);
sqlCmd.Parameters.AddWithValue("@ip_addr", ipAddr);
sqlCmd.Parameters.AddWithValue ("@status", status);
return QueryTableData(sqlCmd);
}
catch (Exception e)
{
_logger.Error($"无法获取任务列表: {e.Message}");
throw;
}
finally { conn.Close(); }
}
public string GetTaskListByIP(string ipAddr)
{
string sql = $"SELECT * FROM t_tasks_info WHERE client_ip = @ip_addr";
MySqlConnection conn = GetAndOpenDBConn("iFileProxy_Db");
try
{
using MySqlCommand sqlCmd = new (sql,conn);
sqlCmd.Parameters.AddWithValue("@ip_addr", ipAddr);
return QueryTableData(sqlCmd);
}
catch (Exception e)
{
_logger.Error($"无法获取任务列表: {e.Message}");
throw;
}
finally { conn.Close(); }
}
public string GetTaskInfoByTid(string tid)
{
string sql = $"SELECT * FROM t_tasks_info WHERE `tid` =@tid";
MySqlConnection conn = GetAndOpenDBConn("iFileProxy_Db");
try
{
using MySqlCommand sqlCmd = new(sql, conn);
sqlCmd.Parameters.AddWithValue("@tid", tid);
return QueryTableData(sqlCmd);
}
catch (Exception e)
{
_logger.Error($"无法获取任务信息: {e.Message}");
throw;
}
finally { conn.Close(); }
}
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");
try {
MySqlCommand sqlCmd = new MySqlCommand(sql, conn);
sqlCmd.Parameters.AddWithValue("@url", url);
sqlCmd.Parameters.AddWithValue("@size", size);
sqlCmd.Parameters.AddWithValue("@status", status);
sqlCmd.Parameters.AddWithValue("@fileName", fileName);
string result = QueryTableData(sqlCmd);
List<TaskInfo>? r = JsonConvert.DeserializeObject<List<TaskInfo>>(result);
if (r != null)
{
return r[0];
}
else
return null;
}
catch (Exception e){
return null;
}
finally {
conn.Close();
}
}
public bool InsertTaskData(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");
try
{
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);
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.Parameters.AddWithValue("@tag", taskInfo.Tag);
sqlCmd.ExecuteNonQuery();
}
catch (Exception)
{
_logger.Fatal($"插入数据时出现问题");
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 , update_time = Now() 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} Status Change to {taskInfo.Status}");
return true;
}
else
_logger.Warning($"Task: {taskInfo.TaskId} 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));
}
/// <summary>
/// 删除指定IP的任务信息
/// </summary>
/// <param name="c"></param>
/// <returns></returns>
public int DeleteTaskInfoByIpAddr(string ipAddr)
{
try
{
string sql = $"DELETE FROM `t_tasks_info` WHERE `client_ip` = '{ipAddr}'";
return Query(sql,DbConfigName.iFileProxy);
}
catch (Exception)
{
return -1;
}
}
public void TryInitialDB()
{
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 (Exception)
{
_logger.Error($"Db Init Fai.l");
throw;
}
finally
{
conn.Close();
}
}
}
}

View file

@ -0,0 +1,58 @@
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
namespace iFileProxy.Middleware
{
public class JwtMiddleware
{
private readonly RequestDelegate _next;
private readonly IConfiguration _configuration;
public JwtMiddleware(RequestDelegate next, IConfiguration configuration)
{
_next = next;
_configuration = configuration;
}
public async Task InvokeAsync(HttpContext context)
{
var token = context.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last();
if (token != null)
await AttachUserToContext(context, token);
await _next(context);
}
private async Task AttachUserToContext(HttpContext context, string token)
{
try
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_configuration["Jwt:Key"]);
tokenHandler.ValidateToken(token, new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = true,
ValidateAudience = true,
ValidIssuer = _configuration["Jwt:Issuer"],
ValidAudience = _configuration["Jwt:Audience"],
ClockSkew = TimeSpan.Zero
}, out SecurityToken validatedToken);
var jwtToken = (JwtSecurityToken)validatedToken;
var userId = jwtToken.Claims.First(x => x.Type == ClaimTypes.NameIdentifier).Value;
// 将用户信息附加到上下文中
context.Items["User"] = userId;
}
catch
{
// 令牌验证失败,不附加用户信息
}
}
}
}

View file

@ -6,7 +6,7 @@
public class CommonRsp
{
public string Message { get; set; } = "def msg";
public object Data { get; set; } = new object();
public object Data { get; set; } = null;
public int Retcode { get; set; }
}
}

31
src/Models/PagedResult.cs Normal file
View file

@ -0,0 +1,31 @@
/// <summary>
/// 通用分页结果模型
/// </summary>
/// <typeparam name="T">数据项类型</typeparam>
public class PagedResult<T>
{
/// <summary>
/// 总记录数
/// </summary>
public long Total { get; set; }
/// <summary>
/// 每页数量
/// </summary>
public int PageSize { get; set; }
/// <summary>
/// 当前页码
/// </summary>
public int CurrentPage { get; set; }
/// <summary>
/// 总页数
/// </summary>
public int TotalPages { get; set; }
/// <summary>
/// 当前页的数据列表
/// </summary>
public List<T> Data { get; set; } = new();
}

77
src/Models/ProcessInfo.cs Normal file
View file

@ -0,0 +1,77 @@
using System.Diagnostics;
using ThreadState = System.Diagnostics.ThreadState;
namespace iFileProxy.Models
{
/// <summary>
/// 进程详细信息模型
/// </summary>
public class ProcessInfo
{
/// <summary>
/// 进程ID
/// </summary>
public int ProcessId { get; set; }
/// <summary>
/// 进程名称
/// </summary>
public string ProcessName { get; set; } = string.Empty;
/// <summary>
/// 启动时间
/// </summary>
public DateTime StartTime { get; set; }
/// <summary>
/// 运行时长(秒)
/// </summary>
public double RunningTime { get; set; }
/// <summary>
/// 线程数
/// </summary>
public int ThreadCount { get; set; }
/// <summary>
/// 命令行参数
/// </summary>
public string CommandLine { get; set; } = string.Empty;
/// <summary>
/// 进程优先级
/// </summary>
public ProcessPriorityClass PriorityClass { get; set; }
/// <summary>
/// 工作目录
/// </summary>
public string WorkingDirectory { get; set; } = string.Empty;
/// <summary>
/// 线程信息列表
/// </summary>
public List<ThreadInfo> Threads { get; set; } = new();
}
/// <summary>
/// 线程信息模型
/// </summary>
public class ThreadInfo
{
/// <summary>
/// 线程ID
/// </summary>
public int ThreadId { get; set; }
/// <summary>
/// 线程状态
/// </summary>
public ThreadState ThreadState { get; set; }
/// <summary>
/// 线程优先级
/// </summary>
public ThreadPriorityLevel Priority { get; set; }
}
}

View file

@ -32,6 +32,14 @@
/// 正在排队
/// </summary>
Queuing = 6,
/// <summary>
/// 任务因为各种原因被取消
/// </summary>
Canceled = 7,
/// <summary>
/// 其他
/// </summary>
Other = 999
}
/// <summary>
/// 任务添加状态

135
src/Models/User.cs Normal file
View file

@ -0,0 +1,135 @@
using Newtonsoft.Json;
namespace iFileProxy.Models
{
public enum UserMask
{
User = 0,
Admin = 1,
SuperAdmin = 2 // 超级管理员
}
public enum UserState
{
None = 0,
Blocked = 1
}
public enum UserEventType
{
Login = 0,
Logout = 1,
Registry = 2,
UpdateMask = 3, // 添加权限修改事件类型
UpdatePassword = 4, // 添加密码修改事件类型
UpdateNickname = 5, // 添加昵称修改事件类型
}
public class User
{
/// <summary>
/// 用户ID
/// </summary>
[JsonProperty("user_id")]
public string UserId { get; set; } = Guid.NewGuid().ToString();
/// <summary>
/// 昵称
/// </summary>
[JsonProperty("nickname")]
public string Nickname { get; set; } = string.Empty;
/// <summary>
/// 用户名
/// </summary>
[JsonProperty("username")]
public string Username { get; set; } = string.Empty;
/// <summary>
/// 密码哈希
/// </summary>
[JsonProperty("password_hash")]
public string PasswordHash { get; set; } = string.Empty;
/// <summary>
/// 用户权限掩码
/// </summary>
[JsonProperty("mask")]
public UserMask Mask { get; set; } = UserMask.User;
/// <summary>
/// 用户状态
/// </summary>
[JsonProperty("state")]
public UserState State { get; set; } = UserState.None;
/// <summary>
/// 创建时间
/// </summary>
[JsonProperty("create_time")]
public DateTime CreateTime { get; set; } = DateTime.Now;
/// <summary>
/// 最后登录时间
/// </summary>
[JsonProperty("last_login_time")]
public DateTime LastLoginTime { get; set; }
/// <summary>
/// 最后登录IP
/// </summary>
[JsonProperty("last_login_ip")]
public string LastLoginIP { get; set; } = string.Empty;
}
public class UserEvent
{
/// <summary>
/// 事件ID
/// </summary>
[JsonProperty("event_id")]
public string EventId { get; set; } = Guid.NewGuid().ToString();
/// <summary>
/// 用户ID
/// </summary>
[JsonProperty("user_id")]
public string UserId { get; set; } = string.Empty;
/// <summary>
/// 事件类型
/// </summary>
[JsonProperty("event_type")]
public UserEventType EventType { get; set; }
/// <summary>
/// 事件时间
/// </summary>
[JsonProperty("event_time")]
public DateTime EventTime { get; set; } = DateTime.Now;
/// <summary>
/// 事件IP
/// </summary>
[JsonProperty("event_ip")]
public string EventIP { get; set; } = string.Empty;
/// <summary>
/// 事件详情
/// </summary>
[JsonProperty("event_detail")]
public string EventDetail { get; set; } = string.Empty;
}
public class UserEventDetail : UserEvent
{
/// <summary>
/// 用户名
/// </summary>
[JsonProperty("username")]
public string Username { get; set; } = string.Empty;
/// <summary>
/// 昵称
/// </summary>
[JsonProperty("nickname")]
public string Nickname { get; set; } = string.Empty;
}
}

View file

@ -1,9 +1,11 @@
using iFileProxy.Config;
using iFileProxy.Helpers;
using iFileProxy.Middleware;
using iFileProxy.Services;
using Serilog;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;
namespace iFileProxy
{
@ -16,13 +18,23 @@ namespace iFileProxy
Console.Write(" "); // 强迫症,看着开始的那条日志不对齐不得劲
AppConfig.CheckAppConfig();
// 初始化缓存管理服务
LocalCacheManager localCacheManager = new ();
var builder = WebApplication.CreateBuilder(args);
// 添加 CORS 服务
builder.Services.AddCors(options =>
{
options.AddPolicy("AllowFrontend",
builder =>
{
builder
.WithOrigins("http://localhost:3000", "http://admin.gitdl.cn", "https://admin.gitdl.cn","http://47.243.56.137:50050") // 前端地址
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials();
});
});
// Add services to the container.
builder.Services.AddControllers();
@ -32,8 +44,43 @@ namespace iFileProxy
builder.Host.UseSerilog(logger: Log.Logger);
builder.Services.AddSingleton<Dictionary<string, Dictionary<string, uint>>>(serviceProvider =>
{
return new Dictionary<string, Dictionary<string, uint>>();
});
builder.Services.AddSingleton(AppConfig.GetCurrConfig());
builder.Services.AddSingleton<DatabaseGateService>();
builder.Services.AddSingleton<TaskManager>();
// 添加认证服务
builder.Services.AddScoped<AuthService>();
// 添加JWT认证
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = builder.Configuration["Jwt:Issuer"],
ValidAudience = builder.Configuration["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]))
};
});
var app = builder.Build();
AppConfig.CheckAppConfig(app.Services);
LocalCacheManager localCacheManager = new(app.Services);
app.UseSerilogRequestLogging(options =>
{
options.EnrichDiagnosticContext = (diagCtx, httpCtx) =>
@ -51,6 +98,11 @@ namespace iFileProxy
app.UseSwaggerUI();
}
// 错误处理中间件
app.UseMiddleware<ErrorHandlerMiddleware>();
app.UseCors("AllowFrontend");
app.UseHttpsRedirection();
// 配置默认文件选项
@ -64,11 +116,12 @@ namespace iFileProxy
app.UseAuthorization();
var IPAccessCountDict = new Dictionary<string, Dictionary<string, uint>>();
var dailyRequestLimitPerIP = AppConfig.GetCurrConfig().SecurityOptions.DailyRequestLimitPerIP;
app.UseMiddleware<IPAccessLimitMiddleware>(IPAccessCountDict, dailyRequestLimitPerIP);
app.UseMiddleware<IPAccessLimitMiddleware>(
app.Services.GetRequiredService<Dictionary<string, Dictionary<string, uint>>>(),
AppConfig.GetCurrConfig().SecurityOptions.DailyRequestLimitPerIP);
app.UseMiddleware<ErrorHandlerMiddleware>(); // 错误处理中间件
// 添加中间件
app.UseMiddleware<JwtMiddleware>();
app.MapControllers();

141
src/Services/AuthService.cs Normal file
View file

@ -0,0 +1,141 @@
using BCrypt.Net;
using iFileProxy.Models;
using System.Security.Claims;
using System.IdentityModel.Tokens.Jwt;
using Microsoft.IdentityModel.Tokens;
using System.Text;
namespace iFileProxy.Services
{
public class AuthService
{
private readonly DatabaseGateService _dbGateService;
private readonly IConfiguration _configuration;
private readonly ILogger<AuthService> _logger;
public AuthService(DatabaseGateService dbGateService, IConfiguration configuration, ILogger<AuthService> logger)
{
_dbGateService = dbGateService;
_configuration = configuration;
_logger = logger;
}
public async Task<(bool success, string message)> RegisterAsync(string username, string password, string ip, string nickname)
{
try
{
// 检查用户名是否已存在
if (await _dbGateService.UserExistsAsync(username))
{
return (false, "用户名已存在");
}
// 检查是否是第一个用户
var isFirstUser = await _dbGateService.GetUserCountAsync() == 0;
// 创建新用户
var user = new User
{
Username = username,
PasswordHash = BCrypt.Net.BCrypt.HashPassword(password),
LastLoginIP = ip,
// 如果是第一个用户,设置为超级管理员
Mask = isFirstUser ? UserMask.SuperAdmin : UserMask.User,
Nickname = nickname
};
// 保存用户
await _dbGateService.CreateUserAsync(user);
// 记录注册事件
var userEvent = new UserEvent
{
UserId = user.UserId,
EventType = UserEventType.Registry,
EventIP = ip,
EventDetail = isFirstUser ? "管理员注册" : "用户注册"
};
await _dbGateService.CreateUserEventAsync(userEvent);
return (true, isFirstUser ? "管理员注册成功" : "注册成功");
}
catch (Exception ex)
{
_logger.LogError(ex, "用户注册失败");
return (false, "注册失败");
}
}
public async Task<(bool success, string token, string message)> LoginAsync(string username, string password, string ip)
{
try
{
var user = await _dbGateService.GetUserByUsernameAsync(username);
if (user == null)
{
return (false, string.Empty, "用户不存在");
}
if (user.State == UserState.Blocked)
{
return (false, string.Empty, "账户已被封禁");
}
if (!BCrypt.Net.BCrypt.Verify(password, user.PasswordHash))
{
return (false, string.Empty, "密码错误");
}
// 更新登录信息
user.LastLoginTime = DateTime.Now;
user.LastLoginIP = ip;
await _dbGateService.UpdateUserAsync(user);
// 记录登录事件
var userEvent = new UserEvent
{
UserId = user.UserId,
EventType = UserEventType.Login,
EventIP = ip,
EventDetail = "用户登录"
};
await _dbGateService.CreateUserEventAsync(userEvent);
// 生成JWT令牌
var token = GenerateJwtToken(user);
return (true, token, "登录成功");
}
catch (Exception ex)
{
_logger.LogError(ex, "用户登录失败");
return (false, string.Empty, "登录失败");
}
}
private string GenerateJwtToken(User user)
{
var claims = new[]
{
new Claim(ClaimTypes.NameIdentifier, user.UserId),
new Claim(ClaimTypes.Name, user.Username),
new Claim(ClaimTypes.Role, user.Mask.ToString())
};
// 确保密钥至少32字节长
var key = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(_configuration["Jwt:Key"] ??
"iFileProxy-JWT-Secret-Key-2024-Very-Long-Secret-Key-For-Security"));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
issuer: _configuration["Jwt:Issuer"] ?? "iFileProxy",
audience: _configuration["Jwt:Audience"] ?? "iFileProxy.Client",
claims: claims,
expires: DateTime.Now.AddDays(1),
signingCredentials: creds);
return new JwtSecurityTokenHandler().WriteToken(token);
}
}
}

View file

@ -0,0 +1,874 @@
using iFileProxy.Config;
using Serilog;
using System.Data;
using iFileProxy.Helpers;
using MySql.Data.MySqlClient;
using iFileProxy.Models;
using Newtonsoft.Json;
using System.Text;
using System.Threading.Tasks;
namespace iFileProxy.Services
{
public class DatabaseGateService
{
Database _db;
AppConfig _appConfig;
private readonly Serilog.ILogger _logger = Log.Logger.ForContext<DatabaseGateService>();
Dictionary<string, DB> _dbDictionary = [];
private const string CREATE_TASKS_TABLE_SQL = """
CREATE TABLE IF NOT EXISTS `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;
""";
private const string CREATE_USERS_TABLE_SQL = """
CREATE TABLE IF NOT EXISTS `t_users` (
`user_id` varchar(36) NOT NULL,
`username` varchar(50) NOT NULL,
`nickname` varchar(50) NOT NULL,
`password_hash` varchar(255) NOT NULL,
`mask` int NOT NULL DEFAULT 0,
`state` int NOT NULL DEFAULT 0,
`create_time` datetime NOT NULL,
`last_login_time` datetime NULL,
`last_login_ip` varchar(45) NULL,
PRIMARY KEY (`user_id`),
UNIQUE KEY `username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
""";
private const string CREATE_USER_EVENTS_TABLE_SQL = """
CREATE TABLE IF NOT EXISTS `t_user_events` (
`event_id` varchar(36) NOT NULL,
`user_id` varchar(36) NOT NULL,
`event_type` int NOT NULL,
`event_time` datetime NOT NULL,
`event_ip` varchar(45) NULL,
`event_detail` text NULL,
PRIMARY KEY (`event_id`),
KEY `user_id` (`user_id`),
CONSTRAINT `fk_user_events_user` FOREIGN KEY (`user_id`) REFERENCES `t_users` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
""";
public DatabaseGateService(AppConfig appConfig)
{
_logger.Information("Initializing DatabaseGateService...");
_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 GetAndOpenDBConn(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();
return conn;
}
catch (Exception ex)
{
_logger.Fatal($"获取Mysql连接时出现异常:{ex.Message}");
throw;
}
}
public bool TestDbConfig()
{
foreach (var db in _dbDictionary)
{
_logger.Information($"[程序启动前配置验证] 正在测试数据库配置: {db.Key} ...");
MySqlConnection dbConn = new();
try
{
dbConn = GetAndOpenDBConn(db.Key);
_logger.Information($"succ.");
}
catch (Exception)
{
_logger.Fatal($"=========== 数据库: {db.Key} 测试失败! ===========");
return false;
}
finally
{
dbConn.Close();
}
}
return true;
}
/// <summary>
/// 获取一个json格式的数据表
/// </summary>
/// <param name="sql"></param>
/// <param name="conn"></param>
/// <returns></returns>
public static string QueryTableData(MySqlCommand sqlCmd)
{
var dataTable = new DataTable();
using (var adapter = new MySqlDataAdapter(sqlCmd))
{
adapter.Fill(dataTable);
}
return JsonConvert.SerializeObject(dataTable);
}
/// <summary>
/// 获取一个json格式的数据表
/// </summary>
/// <param name="sql"></param>
/// <param name="conn"></param>
/// <returns></returns>
public string QueryTableData(string sql,string dbConfName)
{
DataTable dataTable = new();
using (MySqlDataAdapter adapter = new(new MySqlCommand(sql,GetAndOpenDBConn(dbConfName))))
adapter.Fill(dataTable);
return JsonConvert.SerializeObject(dataTable);
}
/// <summary>
/// 内部查询数据专用 当此方法暴露给C端可能造成sql注入等安全问题
/// </summary>
/// <param name="sql">SQL语句</param>
/// <param name="dbConfName">配置文件中的Description字段</param>
/// <returns>影响的行数</returns>
public int Query(string sql, string dbConfName)
{
using MySqlCommand sqlCmd = new (sql, GetAndOpenDBConn(dbConfName));
int n = sqlCmd.ExecuteNonQuery();
_logger.Debug($"查询完成, 受影响的行数: {n}");
return n;
}
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");
try
{
using MySqlCommand sqlCmd = new(sql, conn);
sqlCmd.Parameters.AddWithValue("@tag", $"REDIRECT:{taskId}");
sqlCmd.Parameters.AddWithValue("@status", TaskState.Cached);
sqlCmd.Parameters.AddWithValue("@ip_addr", ipAddr);
return JsonConvert.DeserializeObject<List<TaskInfo>>( QueryTableData(sqlCmd));
}
catch (Exception e)
{
_logger.Error($"无法获取任务列表: {e.Message}");
throw;
}
finally { conn.Close(); }
}
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");
try
{
using MySqlCommand sqlCmd = new(sql, conn);
sqlCmd.Parameters.AddWithValue("@ip_addr", ipAddr);
sqlCmd.Parameters.AddWithValue ("@status", status);
return QueryTableData(sqlCmd);
}
catch (Exception e)
{
_logger.Error($"无法获取任务列表: {e.Message}");
throw;
}
finally { conn.Close(); }
}
public string GetTaskListByIP(string ipAddr)
{
string sql = $"SELECT * FROM t_tasks_info WHERE client_ip = @ip_addr";
MySqlConnection conn = GetAndOpenDBConn("iFileProxy_Db");
try
{
using MySqlCommand sqlCmd = new (sql,conn);
sqlCmd.Parameters.AddWithValue("@ip_addr", ipAddr);
return QueryTableData(sqlCmd);
}
catch (Exception e)
{
_logger.Error($"无法获取任务列表: {e.Message}");
throw;
}
finally { conn.Close(); }
}
public string GetTaskInfoByTid(string tid)
{
string sql = $"SELECT * FROM t_tasks_info WHERE `tid` = @tid";
MySqlConnection conn = GetAndOpenDBConn("iFileProxy_Db");
try
{
using MySqlCommand sqlCmd = new(sql, conn);
sqlCmd.Parameters.AddWithValue("@tid", tid);
return QueryTableData(sqlCmd);
}
catch (Exception e)
{
_logger.Error($"无法获取任务信息: {e.Message}");
throw;
}
finally { conn.Close(); }
}
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");
try {
MySqlCommand sqlCmd = new MySqlCommand(sql, conn);
sqlCmd.Parameters.AddWithValue("@url", url);
sqlCmd.Parameters.AddWithValue("@size", size);
sqlCmd.Parameters.AddWithValue("@status", status);
sqlCmd.Parameters.AddWithValue("@fileName", fileName);
string result = QueryTableData(sqlCmd);
List<TaskInfo>? r = JsonConvert.DeserializeObject<List<TaskInfo>>(result);
if (r != null)
{
return r[0];
}
else
return null;
}
catch (Exception e){
return null;
}
finally {
conn.Close();
}
}
public bool InsertTaskData(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");
try
{
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);
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.Parameters.AddWithValue("@tag", taskInfo.Tag);
sqlCmd.ExecuteNonQuery();
}
catch (Exception)
{
_logger.Fatal($"插入数据时出现问题");
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 , update_time = Now() 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.Information($"Task: {taskInfo.TaskId} Status Change to {taskInfo.Status}");
return true;
}
else
_logger.Warning($"Task: {taskInfo.TaskId} 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));
}
/// <summary>
/// 删除指定IP的任务信息
/// </summary>
/// <param name="c"></param>
/// <returns></returns>
public int DeleteTaskInfoByIpAddr(string ipAddr)
{
try
{
string sql = $"DELETE FROM `t_tasks_info` WHERE `client_ip` = '{ipAddr}'";
return Query(sql,DbConfigName.iFileProxy);
}
catch (Exception)
{
return -1;
}
}
public void TryInitialDB()
{
using var conn = GetAndOpenDBConn("iFileProxy_Db");
try
{
// 创建表
using (var cmd = new MySqlCommand(CREATE_TASKS_TABLE_SQL, conn))
cmd.ExecuteNonQuery();
using (var cmd = new MySqlCommand(CREATE_USERS_TABLE_SQL, conn))
cmd.ExecuteNonQuery();
using (var cmd = new MySqlCommand(CREATE_USER_EVENTS_TABLE_SQL, conn))
cmd.ExecuteNonQuery();
_logger.Information("数据库表结构初始化完成");
}
catch (Exception ex)
{
_logger.Error($"数据库初始化失败: {ex.Message}");
throw;
}
}
public T ExecuteScalar<T>(string sql, Dictionary<string, object> parameters)
{
using var conn = GetAndOpenDBConn("iFileProxy_Db");
using var cmd = new MySqlCommand(sql, conn);
foreach (var param in parameters)
{
cmd.Parameters.AddWithValue(param.Key, param.Value);
}
return (T)cmd.ExecuteScalar();
}
public List<T> ExecuteQuery<T>(string sql, Dictionary<string, object> parameters)
{
using var conn = GetAndOpenDBConn("iFileProxy_Db");
using var cmd = new MySqlCommand(sql, conn);
foreach (var param in parameters)
{
cmd.Parameters.AddWithValue(param.Key, param.Value);
}
return JsonConvert.DeserializeObject<List<T>>(QueryTableData(cmd));
}
public int ExecuteNonQuery(string sql, Dictionary<string, object> parameters)
{
using var conn = GetAndOpenDBConn("iFileProxy_Db");
using var cmd = new MySqlCommand(sql, conn);
foreach (var param in parameters)
{
cmd.Parameters.AddWithValue(param.Key, param.Value);
}
return cmd.ExecuteNonQuery();
}
/// <summary>
/// 获取分页的任务列表
/// </summary>
/// <param name="page">页码从1开始</param>
/// <param name="pageSize">每页数量</param>
/// <param name="status">可选的任务状态过滤</param>
/// <returns>包含分页信息的任务列表</returns>
public PagedResult<TaskInfo> GetPagedTaskList(int page, int pageSize, TaskState? status = null)
{
// 构建基础SQL
var sql = new StringBuilder("SELECT * FROM t_tasks_info");
var parameters = new Dictionary<string, object>();
// 添加状态过滤
if (status.HasValue)
{
sql.Append(" AND status = @status");
parameters.Add("@status", (int)status.Value);
}
// 添加分页
sql.Append(" ORDER BY add_time DESC LIMIT @offset, @limit");
parameters.Add("@offset", (page - 1) * pageSize);
parameters.Add("@limit", pageSize);
// 获取总记录数
var countSql = "SELECT COUNT(*) FROM t_tasks_info" +
(status.HasValue ? " AND status = @status" : "");
var totalCount = ExecuteScalar<long>(countSql, parameters);
// 获取分页数据
var tasks = ExecuteQuery<TaskInfo>(sql.ToString(), parameters);
// 返回结果
return new PagedResult<TaskInfo>
{
Total = totalCount,
PageSize = pageSize,
CurrentPage = page,
TotalPages = (int)Math.Ceiling(totalCount / (double)pageSize),
Data = tasks
};
}
/// <summary>
/// 根据任务ID删除任务
/// </summary>
/// <param name="taskId">任务ID</param>
/// <returns>是否删除成功</returns>
public bool DeleteTaskByTid(string taskId)
{
try
{
var sql = "DELETE FROM t_tasks_info WHERE tid = @taskId";
var parameters = new Dictionary<string, object>
{
{ "@taskId", taskId }
};
var affectedRows = ExecuteNonQuery(sql, parameters);
return affectedRows > 0;
}
catch (Exception ex)
{
_logger.Error($"删除任务失败 [TaskId: {taskId}]: {ex.Message}");
return false;
}
}
/// <summary>
/// 获取任务的详细信息
/// </summary>
/// <param name="taskId">任务ID</param>
/// <returns>任务详细信息</returns>
public TaskInfo GetTaskDetail(string taskId)
{
try
{
var sql = """
SELECT * FROM t_tasks_info WHERE tid = @taskId
""";
var parameters = new Dictionary<string, object>
{
{ "@taskId", taskId }
};
var taskInfo = ExecuteQuery<TaskInfo>(sql, parameters).FirstOrDefault();
return taskInfo;
}
catch (Exception ex)
{
_logger.Error($"获取任务详情失败 [TaskId: {taskId}]: {ex.Message}");
throw;
}
}
/// <summary>
/// 获取各状态任务的数量统计
/// </summary>
/// <returns>以任务状态为键,数量为值的字典</returns>
public Dictionary<TaskState, int> GetTaskStatusOverview()
{
try
{
var sql = """
SELECT
status,
COUNT(*) as count
FROM t_tasks_info
GROUP BY status
""";
var result = new Dictionary<TaskState, int>();
// 初始化所有状态的计数为0
foreach (TaskState state in Enum.GetValues(typeof(TaskState)))
{
result[state] = 0;
}
// 获取数据库中的实际计数
using var conn = GetAndOpenDBConn("iFileProxy_Db");
using var cmd = new MySqlCommand(sql, conn);
using var reader = cmd.ExecuteReader();
while (reader.Read())
{
var status = (TaskState)reader.GetInt32("status");
var count = reader.GetInt32("count");
result[status] = count;
}
return result;
}
catch (Exception ex)
{
_logger.Error($"获取任务状态统计失败: {ex.Message}");
throw;
}
}
public async Task<User?> GetUserByIdAsync(string userId)
{
var sql = "SELECT * FROM t_users WHERE user_id = @userId";
var parameters = new Dictionary<string, object>
{
{ "@userId", userId }
};
var users = await ExecuteQueryAsync<User>(sql, parameters);
return users.FirstOrDefault();
}
public async Task<bool> UserExistsAsync(string username)
{
var sql = "SELECT COUNT(*) FROM t_users WHERE username = @username";
var parameters = new Dictionary<string, object>
{
{ "@username", username }
};
var count = await ExecuteScalarAsync<long>(sql, parameters);
return count > 0;
}
public async Task<bool> CreateUserAsync(User user)
{
var sql = @"INSERT INTO t_users (user_id, username, password_hash, mask, state, create_time, last_login_time, last_login_ip, nickname)
VALUES (@userId, @username, @passwordHash, @mask, @state, @createTime, @lastLoginTime, @lastLoginIp, @nickname)";
var parameters = new Dictionary<string, object>
{
{ "@userId", user.UserId },
{ "@username", user.Username },
{ "@passwordHash", user.PasswordHash },
{ "@mask", (int)user.Mask },
{ "@state", (int)user.State },
{ "@createTime", user.CreateTime },
{ "@lastLoginTime", user.LastLoginTime },
{ "@lastLoginIp", user.LastLoginIP },
{ "@nickname", user.Nickname },
};
var result = await ExecuteNonQueryAsync(sql, parameters);
return result > 0;
}
public async Task<bool> CreateUserEventAsync(UserEvent userEvent)
{
var sql = @"INSERT INTO t_user_events (event_id, user_id, event_type, event_time, event_ip, event_detail)
VALUES (@eventId, @userId, @eventType, @eventTime, @eventIp, @eventDetail)";
var parameters = new Dictionary<string, object>
{
{ "@eventId", userEvent.EventId },
{ "@userId", userEvent.UserId },
{ "@eventType", (int)userEvent.EventType },
{ "@eventTime", userEvent.EventTime },
{ "@eventIp", userEvent.EventIP },
{ "@eventDetail", userEvent.EventDetail }
};
var result = await ExecuteNonQueryAsync(sql, parameters);
return result > 0;
}
public async Task<User?> GetUserByUsernameAsync(string username)
{
var sql = "SELECT * FROM t_users WHERE username = @username";
var parameters = new Dictionary<string, object>
{
{ "@username", username }
};
var users = await ExecuteQueryAsync<User>(sql, parameters);
return users.FirstOrDefault();
}
public async Task<bool> UpdateUserAsync(User user)
{
var sql = @"UPDATE t_users SET
username = @username,
password_hash = @passwordHash,
mask = @mask,
state = @state,
last_login_time = @lastLoginTime,
last_login_ip = @lastLoginIp,
nickname = @nickname
WHERE user_id = @userId";
var parameters = new Dictionary<string, object>
{
{ "@userId", user.UserId },
{ "@username", user.Username },
{ "@passwordHash", user.PasswordHash },
{ "@mask", (int)user.Mask },
{ "@state", (int)user.State },
{ "@lastLoginTime", user.LastLoginTime },
{ "@lastLoginIp", user.LastLoginIP },
{ "@nickname", user.Nickname }
};
var result = await ExecuteNonQueryAsync(sql, parameters);
return result > 0;
}
private async Task<T> ExecuteScalarAsync<T>(string sql, Dictionary<string, object> parameters)
{
using var conn = GetAndOpenDBConn("iFileProxy_Db");
using var cmd = new MySqlCommand(sql, conn);
foreach (var param in parameters)
{
cmd.Parameters.AddWithValue(param.Key, param.Value);
}
return (T)await cmd.ExecuteScalarAsync();
}
private async Task<List<T>> ExecuteQueryAsync<T>(string sql, Dictionary<string, object> parameters)
{
using var conn = GetAndOpenDBConn("iFileProxy_Db");
using var cmd = new MySqlCommand(sql, conn);
foreach (var param in parameters)
{
cmd.Parameters.AddWithValue(param.Key, param.Value);
}
// 使用 DataTable 来避免 DataReader 冲突
var dataTable = new DataTable();
using (var adapter = new MySqlDataAdapter(cmd))
{
await Task.Run(() => adapter.Fill(dataTable));
}
return JsonConvert.DeserializeObject<List<T>>(
JsonConvert.SerializeObject(dataTable)
);
}
private async Task<int> ExecuteNonQueryAsync(string sql, Dictionary<string, object> parameters)
{
using var conn = GetAndOpenDBConn("iFileProxy_Db");
using var cmd = new MySqlCommand(sql, conn);
foreach (var param in parameters)
{
cmd.Parameters.AddWithValue(param.Key, param.Value);
}
return await cmd.ExecuteNonQueryAsync();
}
public async Task<long> GetUserCountAsync()
{
var sql = "SELECT COUNT(*) FROM t_users";
var parameters = new Dictionary<string, object>();
return await ExecuteScalarAsync<long>(sql, parameters);
}
public async Task<PagedResult<User>> GetPagedUserListAsync(int page, int pageSize, UserMask? mask = null)
{
// 构建基础SQL
var sql = new StringBuilder("SELECT * FROM t_users");
var parameters = new Dictionary<string, object>();
// 添加权限过滤
if (mask.HasValue)
{
sql.Append(" WHERE mask = @mask");
parameters.Add("@mask", (int)mask.Value);
}
// 添加分页
sql.Append(" ORDER BY create_time DESC LIMIT @offset, @limit");
parameters.Add("@offset", (page - 1) * pageSize);
parameters.Add("@limit", pageSize);
// 获取总记录数
var countSql = "SELECT COUNT(*) FROM t_users" +
(mask.HasValue ? " WHERE mask = @mask" : "");
var totalCount = await ExecuteScalarAsync<long>(countSql, parameters);
// 获取分页数据
var users = await ExecuteQueryAsync<User>(sql.ToString(), parameters);
return new PagedResult<User>
{
Total = totalCount,
PageSize = pageSize,
CurrentPage = page,
TotalPages = (int)Math.Ceiling(totalCount / (double)pageSize),
Data = users
};
}
public async Task<PagedResult<UserEventDetail>> GetPagedUserEventsAsync(
int page,
int pageSize,
string? userId = null,
string? keyword = null)
{
// 构建基础SQL
var sql = new StringBuilder(@"
SELECT e.*, u.username, u.nickname
FROM t_user_events e
LEFT JOIN t_users u ON e.user_id = u.user_id
WHERE 1=1");
var parameters = new Dictionary<string, object>();
// 添加用户ID过滤
if (!string.IsNullOrEmpty(userId))
{
sql.Append(" AND e.user_id = @userId");
parameters.Add("@userId", userId);
}
// 添加关键词搜索
if (!string.IsNullOrEmpty(keyword))
{
sql.Append(@" AND (
e.event_detail LIKE @keyword OR
u.username LIKE @keyword OR
u.nickname LIKE @keyword OR
e.event_ip LIKE @keyword
)");
parameters.Add("@keyword", $"%{keyword}%");
}
// 添加分页
sql.Append(" ORDER BY e.event_time DESC LIMIT @offset, @limit");
parameters.Add("@offset", (page - 1) * pageSize);
parameters.Add("@limit", pageSize);
// 获取总记录数
var countSql = @"
SELECT COUNT(*)
FROM t_user_events e
LEFT JOIN t_users u ON e.user_id = u.user_id
WHERE 1=1" +
(!string.IsNullOrEmpty(userId) ? " AND e.user_id = @userId" : "") +
(!string.IsNullOrEmpty(keyword) ? @" AND (
e.event_detail LIKE @keyword OR
u.username LIKE @keyword OR
u.nickname LIKE @keyword OR
e.event_ip LIKE @keyword
)" : "");
var totalCount = await ExecuteScalarAsync<long>(countSql, parameters);
// 获取分页数据
var events = await ExecuteQueryAsync<UserEventDetail>(sql.ToString(), parameters);
return new PagedResult<UserEventDetail>
{
Total = totalCount,
PageSize = pageSize,
CurrentPage = page,
TotalPages = (int)Math.Ceiling(totalCount / (double)pageSize),
Data = events
};
}
}
}

View file

@ -17,17 +17,17 @@ namespace iFileProxy.Services
private readonly static Serilog.ILogger _logger = Log.Logger.ForContext<LocalCacheManager>();
private readonly Timer _timer;
private readonly DatabaseHelper _dbHelper;
private readonly DatabaseGateService _dbGateService;
private readonly int CACHE_LIFETIME;
/// <summary>
/// 缓存管理器
/// </summary>
public LocalCacheManager()
public LocalCacheManager(IServiceProvider serviceProvider)
{
_logger.Information("Initializing LocalCacheManager.");
CACHE_LIFETIME = _appConfig.DownloadOptions.CacheLifetime;
_dbHelper = new DatabaseHelper(_appConfig);
_dbGateService = serviceProvider.GetRequiredService<DatabaseGateService>();
// 开始定时清理任务
_timer = new Timer(CheckAndCleanCache, null, TimeSpan.FromSeconds(6), TimeSpan.FromSeconds(60));
_logger.Information("succ.");
@ -39,7 +39,7 @@ namespace iFileProxy.Services
public void CheckAndCleanCache(object state)
{
// 获取数据库中超出生命周期的缓存数据
string result = _dbHelper.QueryTableData($"SELECT * FROM t_tasks_info WHERE UNIX_TIMESTAMP(NOW()) - UNIX_TIMESTAMP(update_time) > {CACHE_LIFETIME} AND (tag <> 'CLEANED' OR tag IS NULL)", DbConfigName.iFileProxy);
string result = _dbGateService.QueryTableData($"SELECT * FROM t_tasks_info WHERE UNIX_TIMESTAMP(NOW()) - UNIX_TIMESTAMP(update_time) > {CACHE_LIFETIME} AND (tag <> 'CLEANED' OR tag IS NULL)", DbConfigName.iFileProxy);
List<TaskInfo>? taskInfos = JsonConvert.DeserializeObject<List<TaskInfo>>(result);
if (taskInfos != null)
{
@ -59,9 +59,9 @@ namespace iFileProxy.Services
throw;
}
}
_dbHelper.Query($"UPDATE t_tasks_info SET `tag` = \"CLEANED\" WHERE `tid` = '{taskInfo.TaskId}'", DbConfigName.iFileProxy);
_dbGateService.Query($"UPDATE t_tasks_info SET `tag` = \"CLEANED\" WHERE `tid` = '{taskInfo.TaskId}'", DbConfigName.iFileProxy);
taskInfo.Status = TaskState.Cleaned;
_dbHelper.UpdateTaskStatus(taskInfo);
_dbGateService.UpdateTaskStatus(taskInfo);
}
}
}

View file

@ -1,7 +1,6 @@
using iFileProxy.Config;
using iFileProxy.Helpers;
using iFileProxy.Models;
using Org.BouncyCastle.Asn1.Tsp;
using Serilog;
using System.Diagnostics;
using System.Text.Json;
@ -40,6 +39,7 @@ namespace iFileProxy.Services
add_task_num = _pendingTasks.Count;
for (int i = 0; i < add_task_num; i++) // 运行的任务中不足最大并行数并且有正在队列的task时候添加足够多的任务
{
// 获取下一个任务信息
TaskInfo nextTask = _pendingTasks.Dequeue();
Task.Run(() => StartTaskAsync(nextTask)).ConfigureAwait(false);
}
@ -50,16 +50,16 @@ namespace iFileProxy.Services
private readonly static Serilog.ILogger _logger = Log.Logger.ForContext<TaskManager>();
private readonly AppConfig? _appConfig = AppConfig.GetCurrConfig("iFileProxy.json");
private readonly DatabaseHelper _dbHelper;
private readonly DatabaseGateService _dbGateService;
private Dictionary<string, TaskInfo> _runningTasks = [];
private Queue<TaskInfo> _pendingTasks = new();
private readonly object _taskLock = new();
public TaskManager()
public TaskManager(IServiceProvider serviceProvider)
{
_logger.Information("Initializing TaskManager...");
if (_appConfig != null)
_dbHelper = new DatabaseHelper(_appConfig);
_dbGateService = serviceProvider.GetRequiredService<DatabaseGateService>();
else
{
_logger.Fatal($"Failed to load application configuration");
@ -140,7 +140,7 @@ namespace iFileProxy.Services
if (MasterHelper.CheckDownloadFileExists(taskInfo.FileName))
{
var r = _dbHelper.QueryTaskInfo(taskInfo.FileName, taskInfo.Url, taskInfo.Size, TaskState.End);
var r = _dbGateService.QueryTaskInfo(taskInfo.FileName, taskInfo.Url, taskInfo.Size, TaskState.End);
if (r != null)
{
taskInfo.Status = TaskState.Cached;
@ -163,7 +163,7 @@ namespace iFileProxy.Services
/// <returns></returns>
public TaskAddState AddTaskInfoToDb(TaskInfo taskInfo, bool queuing = false)
{
if (_dbHelper.InsertTaskData(taskInfo))
if (_dbGateService.InsertTaskData(taskInfo))
{
if (!queuing) // 如果不是正在排队的任务
{
@ -178,7 +178,7 @@ namespace iFileProxy.Services
{
taskInfo.Status = TaskState.Queuing;
}
_dbHelper.UpdateTaskStatus(taskInfo);
_dbGateService.UpdateTaskStatus(taskInfo);
return TaskAddState.Success;
}
@ -191,7 +191,7 @@ namespace iFileProxy.Services
/// </summary>
/// <param name="taskInfo">任务信息</param>
/// <returns></returns>
public async Task StartTaskAsync(TaskInfo taskInfo)
public async Task StartTaskAsync(TaskInfo taskInfo, bool OnTaskCompletedEvent = true)
{
if (_runningTasks.ContainsKey(taskInfo.TaskId))
{
@ -220,9 +220,9 @@ namespace iFileProxy.Services
aria2c.Start();
_runningTasks[taskInfo.TaskId].Process = aria2c;
if (taskInfo.Status != TaskState.Running)
{
{
taskInfo.Status = TaskState.Running;
_dbHelper.UpdateTaskStatus(taskInfo);
_dbGateService.UpdateTaskStatus(taskInfo);
}
_logger.Information($"[TaskId: {taskInfo.TaskId}] Started.");
aria2c.BeginOutputReadLine();
@ -237,24 +237,31 @@ namespace iFileProxy.Services
{
_logger.Error($"task: {taskInfo.TaskId} 进程退出状态异常 ExitCode: {aria2c.ExitCode}");
taskInfo.Status = TaskState.Error;
_dbHelper.UpdateTaskStatus(taskInfo);
}
else
{
taskInfo.Status = TaskState.End;
_dbHelper.UpdateTaskStatus(taskInfo);
_dbGateService.UpdateTaskStatus(taskInfo);
taskInfo.Hash = MasterHelper.GetFileHash(Path.Combine(_appConfig.DownloadOptions.SavePath, taskInfo.FileName), FileHashAlgorithm.MD5);
_dbHelper.UpdateTaskHash(taskInfo);
_dbGateService.UpdateTaskHash(taskInfo);
}
// 触发任务完成事件
OnTaskCompleted(taskInfo);
}
catch (Exception ex)
{
_logger.Fatal($"执行下载任务时候出现致命问题: {ex.Message}");
taskInfo.Status= TaskState.Error;
_dbGateService.UpdateTaskStatus(taskInfo);
throw;
}
finally
{
if (OnTaskCompletedEvent)
{
// 触发任务完成事件
OnTaskCompleted(taskInfo);
}
}
}
@ -282,7 +289,7 @@ namespace iFileProxy.Services
public List<TaskInfo> GetTaskListByIpAddr(HttpContext c)
{
string? clientIp = MasterHelper.GetClientIPAddr(c);
return JsonSerializer.Deserialize<List<TaskInfo>>(_dbHelper.GetTaskListByIP(clientIp)) ?? [];
return JsonSerializer.Deserialize<List<TaskInfo>>(_dbGateService.GetTaskListByIP(clientIp)) ?? [];
}
/// <summary>
@ -292,8 +299,8 @@ namespace iFileProxy.Services
/// <returns></returns>
public List<TaskInfo> GetTaskInfo(string taskId)
{
_logger.Debug(_dbHelper.GetTaskInfoByTid(taskId));
return JsonSerializer.Deserialize<List<TaskInfo>>(_dbHelper.GetTaskInfoByTid(taskId)) ?? [];
_logger.Debug(_dbGateService.GetTaskInfoByTid(taskId));
return JsonSerializer.Deserialize<List<TaskInfo>>(_dbGateService.GetTaskInfoByTid(taskId)) ?? [];
}
public Dictionary<string, TaskInfo> GetRunningTasks() => _runningTasks;
@ -302,7 +309,7 @@ namespace iFileProxy.Services
/// 获取task在队列中的位置
/// </summary>
/// <param name="taskId"></param>
/// <returns></returns>
/// <returns>成功时返回队列中的位置 失败时返回-1</returns>
public int GetQueuePosition(string taskId)
{
int position = -1; // 默认值表示未找到
@ -333,11 +340,307 @@ namespace iFileProxy.Services
return new ServerTaskLoadInfo { Queuing = _pendingTasks.Count , Running = _runningTasks.Count};
}
public void TryKillTask(TaskInfo taskInfo)
{
public TaskInfo? GetRunningTaskInfo(string taskId)
{
if (_runningTasks.TryGetValue(taskId, out var taskInfo))
{
return taskInfo;
}
return null;
}
public void TryKillTask(TaskInfo taskInfo)
{
try
{
// 检查任务是否在运行中
if (_runningTasks.TryGetValue(taskInfo.TaskId, out var runningTask))
{
_logger.Information($"正在尝试中断任务: {taskInfo.TaskId}");
// 更新任务状态为已取消
taskInfo.Status = TaskState.Canceled;
_dbGateService.UpdateTaskStatus(taskInfo);
// 如果存在进程则kill掉
foreach (var p in Process.GetProcesses())
{
if (p.Id == taskInfo.Process.Id)
p.Kill();
}
// 从运行任务字典中移除
_runningTasks.Remove(taskInfo.TaskId);
// 删除未完成的文件
var filePath = Path.Combine(_appConfig.DownloadOptions.SavePath, taskInfo.FileName);
if (File.Exists(filePath))
{
File.Delete(filePath);
_logger.Debug($"已删除未下载完成的文件: {filePath}");
}
// 删除aria2c的临时文件
if (File.Exists(filePath + ".aria2"))
{
File.Delete(filePath + ".aria2");
_logger.Debug("已删除aria2临时文件");
}
_logger.Information($"任务 {taskInfo.TaskId} 已中断");
OnTaskCompleted(taskInfo);
}
else
{
_logger.Warning($"任务 {taskInfo.TaskId} 不在运行状态,无需中断");
}
}
catch (Exception ex)
{
_logger.Error("中断任务时发生错误: {ex}",ex);
throw;
}
}
/// <summary>
/// 获取任务的进程详细信息
/// </summary>
/// <param name="taskId">任务ID</param>
/// <returns>进程详细信息如果任务不存在或未运行则返回null</returns>
public ProcessInfo? GetProcessInfo(string taskId)
{
try
{
if (!_runningTasks.TryGetValue(taskId, out var taskInfo) || taskInfo.Process == null)
{
return null;
}
var process = taskInfo.Process;
// 刷新进程信息
process.Refresh();
var processInfo = new ProcessInfo
{
ProcessId = process.Id,
ProcessName = process.ProcessName,
StartTime = process.StartTime,
RunningTime = (DateTime.Now - process.StartTime).TotalSeconds,
ThreadCount = process.Threads.Count,
PriorityClass = process.PriorityClass,
WorkingDirectory = process.StartInfo.WorkingDirectory,
CommandLine = process.StartInfo.Arguments
};
// 获取线程信息
foreach (ProcessThread thread in process.Threads)
{
try
{
processInfo.Threads.Add(new ThreadInfo
{
ThreadId = thread.Id,
ThreadState = thread.ThreadState,
Priority = thread.PriorityLevel
});
}
catch (Exception ex)
{
_logger.Warning($"获取线程信息失败: {ex.Message}");
// 继续处理下一个线程
continue;
}
}
return processInfo;
}
catch (Exception ex)
{
_logger.Error($"获取进程信息失败: {ex.Message}");
return null;
}
}
/// <summary>
/// 重试失败的任务
/// </summary>
/// <param name="taskId">任务ID</param>
/// <returns>重试是否成功</returns>
public bool RetryTask(string taskId)
{
try
{
var taskInfo = JsonSerializer.Deserialize<List<TaskInfo>>(_dbGateService.GetTaskInfoByTid(taskId))?[0];
if (taskInfo == null)
{
_logger.Warning($"任务不存在: {taskId}");
return false;
}
// 检查任务状态是否为失败状态
if (taskInfo.Status != TaskState.Error && taskInfo.Status != TaskState.Canceled)
{
_logger.Warning($"任务 {taskId} 状态为 {taskInfo.Status},不能重试");
return false;
}
// 重置任务状态
taskInfo.Status = TaskState.NoInit;
taskInfo.UpdateTime = DateTime.Now;
_dbGateService.UpdateTaskStatus(taskInfo);
lock (_taskLock)
{
// 如果有等待的任务或达到最大并行数,加入队列
if (_pendingTasks.Count > 0 || _runningTasks.Count >= _appConfig.DownloadOptions.MaxParallelTasks)
{
_pendingTasks.Enqueue(taskInfo);
taskInfo.Status = TaskState.Queuing;
_dbGateService.UpdateTaskStatus(taskInfo);
_logger.Information($"任务 {taskId} 已加入等待队列");
}
else
{
// 直接启动任务
Task.Run(() => StartTaskAsync(taskInfo)).ConfigureAwait(false);
}
}
return true;
}
catch (Exception ex)
{
_logger.Error($"重试任务失败: {ex.Message}");
return false;
}
}
/// <summary>
/// 将任务移动到等待队列最前面
/// </summary>
/// <param name="taskId">任务ID</param>
/// <returns>是否成功</returns>
public bool PrioritizeTask(string taskId)
{
try
{
lock (_taskLock)
{
// 如果队列为空,无需处理
if (_pendingTasks.Count == 0)
{
_logger.Warning("等待队列为空,无法进行优先级调整");
return false;
}
// 找到指定任务
var tempQueue = new Queue<TaskInfo>();
TaskInfo? targetTask = null;
while (_pendingTasks.Count > 0)
{
var task = _pendingTasks.Dequeue();
if (task.TaskId == taskId)
{
targetTask = task;
}
else
{
tempQueue.Enqueue(task);
}
}
// 如果找到目标任务,将其放到新队列的最前面
if (targetTask != null)
{
_pendingTasks.Enqueue(targetTask);
while (tempQueue.Count > 0)
{
_pendingTasks.Enqueue(tempQueue.Dequeue());
}
_logger.Information($"任务 {taskId} 已移至队列首位");
return true;
}
else
{
// 如果没找到,还原队列
while (tempQueue.Count > 0)
{
_pendingTasks.Enqueue(tempQueue.Dequeue());
}
_logger.Warning($"未在等待队列中找到任务: {taskId}");
return false;
}
}
}
catch (Exception ex)
{
_logger.Error($"调整任务优先级失败: {ex.Message}");
return false;
}
}
/// <summary>
/// 立即执行指定任务
/// </summary>
/// <param name="taskId">任务ID</param>
/// <returns>是否成功</returns>
public bool ExecuteImmediately(string taskId)
{
try
{
lock (_taskLock)
{
// 找到指定任务
var tempQueue = new Queue<TaskInfo>();
TaskInfo? targetTask = null;
while (_pendingTasks.Count > 0)
{
var task = _pendingTasks.Dequeue();
if (task.TaskId == taskId)
{
targetTask = task;
}
else
{
tempQueue.Enqueue(task);
}
}
// 还原其他任务到队列
while (tempQueue.Count > 0)
{
_pendingTasks.Enqueue(tempQueue.Dequeue());
}
// 如果找到目标任务
if (targetTask != null)
{
_logger.Information($"正在立即执行任务: {taskId}");
// 启动任务
Task.Run(() => StartTaskAsync(targetTask, false)).ConfigureAwait(false);
return true;
}
else
{
_logger.Warning($"未在等待队列中找到任务: {taskId}");
return false;
}
}
}
catch (Exception ex)
{
_logger.Error($"立即执行任务失败: {ex.Message}");
return false;
}
}
//public bool DeleteTask(HttpContext c)
//{

View file

@ -5,5 +5,10 @@
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
"AllowedHosts": "*",
"Jwt": {
"Key": "iFileProxy-JWT-Secret-Key-2024-Very-Long-Secret-Key-For-Security",
"Issuer": "iFileProxy",
"Audience": "iFileProxy.Client"
}
}

View file

@ -0,0 +1,67 @@
/*
Navicat Premium Data Transfer
Source Server : _阿里云
Source Server Type : MySQL
Source Server Version : 50743
Source Host : 127.0.0.1:3306
Source Schema : iFileProxy
Target Server Type : MySQL
Target Server Version : 50743
File Encoding : 65001
Date: 30/11/2024 19:32:04
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for t_tasks_info
-- ----------------------------
DROP TABLE IF EXISTS `t_tasks_info`;
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 COMMENT '文件名',
`client_ip` varchar(46) 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(1) NULL DEFAULT NULL COMMENT '任务状态',
`url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '文件url',
`size` bigint(11) NULL DEFAULT NULL COMMENT '文件大小',
`hash` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '文件hash',
`tag` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '标记',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `tid`(`tid`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 8128 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for t_user_events
-- ----------------------------
DROP TABLE IF EXISTS `t_user_events`;
CREATE TABLE `t_user_events` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增键',
`username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户名',
`event_type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '事件类型',
`trigger_time` datetime NOT NULL COMMENT '触发时间(Server)',
`ip` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '触发者IP',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for t_users
-- ----------------------------
DROP TABLE IF EXISTS `t_users`;
CREATE TABLE `t_users` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增键',
`username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户名',
`password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '密码',
`email` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '电子邮件',
`role_mask` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '身份掩码',
`nickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '外显名称(昵称)',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;

View file

@ -13,7 +13,9 @@
<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" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="7.1.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.0" />
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
</ItemGroup>
</Project>