支持路由细粒度ACL和用户功能
This commit is contained in:
parent
cf25e63de7
commit
7fe9d05ffa
13 changed files with 1416 additions and 32 deletions
45
src/Attributes/AuthorizeAttribute.cs
Normal file
45
src/Attributes/AuthorizeAttribute.cs
Normal 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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,8 +4,10 @@ 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
|
||||
|
|
607
src/Controllers/UserController.cs
Normal file
607
src/Controllers/UserController.cs
Normal 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;
|
||||
}
|
||||
}
|
58
src/Middleware/JwtMiddleware.cs
Normal file
58
src/Middleware/JwtMiddleware.cs
Normal 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
|
||||
{
|
||||
// 令牌验证失败,不附加用户信息
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
135
src/Models/User.cs
Normal file
135
src/Models/User.cs
Normal 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;
|
||||
}
|
||||
}
|
|
@ -3,6 +3,9 @@ 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
|
||||
{
|
||||
|
@ -24,7 +27,7 @@ namespace iFileProxy
|
|||
builder =>
|
||||
{
|
||||
builder
|
||||
.WithOrigins("http://localhost:3000") // ゴ傷華硊
|
||||
.WithOrigins("http://localhost:3000", "http://admin.gitdl.cn", "https://admin.gitdl.cn","http://47.243.56.137:50050") // 前端地址
|
||||
.AllowAnyMethod()
|
||||
.AllowAnyHeader()
|
||||
.AllowCredentials();
|
||||
|
@ -52,6 +55,26 @@ namespace iFileProxy
|
|||
|
||||
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);
|
||||
|
@ -97,6 +120,9 @@ namespace iFileProxy
|
|||
app.Services.GetRequiredService<Dictionary<string, Dictionary<string, uint>>>(),
|
||||
AppConfig.GetCurrConfig().SecurityOptions.DailyRequestLimitPerIP);
|
||||
|
||||
// 添加中间件
|
||||
app.UseMiddleware<JwtMiddleware>();
|
||||
|
||||
app.MapControllers();
|
||||
|
||||
app.Run();
|
||||
|
|
|
@ -15,7 +15,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
|
|||
<_TargetId>Folder</_TargetId>
|
||||
<SiteUrlToLaunchAfterPublish />
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
<RuntimeIdentifier>linux-x64</RuntimeIdentifier>
|
||||
<ProjectGuid>e343bd8a-27ed-47e2-b50d-e3000730e65e</ProjectGuid>
|
||||
<SelfContained>false</SelfContained>
|
||||
<PublishSingleFile>true</PublishSingleFile>
|
||||
|
|
141
src/Services/AuthService.cs
Normal file
141
src/Services/AuthService.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,6 +6,7 @@ using MySql.Data.MySqlClient;
|
|||
using iFileProxy.Models;
|
||||
using Newtonsoft.Json;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace iFileProxy.Services
|
||||
{
|
||||
|
@ -17,6 +18,52 @@ namespace iFileProxy.Services
|
|||
|
||||
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...");
|
||||
|
@ -113,9 +160,11 @@ namespace iFileProxy.Services
|
|||
/// <returns></returns>
|
||||
public static string QueryTableData(MySqlCommand sqlCmd)
|
||||
{
|
||||
DataTable dataTable = new();
|
||||
using (MySqlDataAdapter adapter = new (sqlCmd))
|
||||
adapter.Fill(dataTable);
|
||||
var dataTable = new DataTable();
|
||||
using (var adapter = new MySqlDataAdapter(sqlCmd))
|
||||
{
|
||||
adapter.Fill(dataTable);
|
||||
}
|
||||
return JsonConvert.SerializeObject(dataTable);
|
||||
}
|
||||
|
||||
|
@ -367,37 +416,24 @@ namespace iFileProxy.Services
|
|||
|
||||
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");
|
||||
|
||||
using var conn = GetAndOpenDBConn("iFileProxy_Db");
|
||||
try
|
||||
{
|
||||
using MySqlCommand cmd = new(sql, conn);
|
||||
cmd.ExecuteNonQuery();
|
||||
// 创建表
|
||||
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)
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error($"Db Init Fai.l");
|
||||
_logger.Error($"数据库初始化失败: {ex.Message}");
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
conn.Close();
|
||||
}
|
||||
}
|
||||
public T ExecuteScalar<T>(string sql, Dictionary<string, object> parameters)
|
||||
{
|
||||
|
@ -574,5 +610,265 @@ namespace iFileProxy.Services
|
|||
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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
67
src/document/iFileProxy.sql
Normal file
67
src/document/iFileProxy.sql
Normal 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;
|
|
@ -14,6 +14,8 @@
|
|||
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
|
||||
<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>
|
||||
|
|
Loading…
Reference in a new issue