2026-06-01 17:59:23 +08:00
|
|
|
|
using BCrypt.Net;
|
|
|
|
|
|
using Microsoft.Extensions.Configuration;
|
|
|
|
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
|
|
using QYZH.InteractiveMagazine.Infrastructure.Auth;
|
|
|
|
|
|
using QYZH.InteractiveMagazine.Infrastructure.Cache;
|
|
|
|
|
|
using QYZH.InteractiveMagazine.IService;
|
|
|
|
|
|
using QYZH.InteractiveMagazine.IService.Dto;
|
|
|
|
|
|
using QYZH.InteractiveMagazine.Models.Common;
|
2026-06-02 14:10:43 +08:00
|
|
|
|
using QYZH.InteractiveMagazine.Models.Dto;
|
2026-06-01 17:59:23 +08:00
|
|
|
|
using QYZH.InteractiveMagazine.Models.Entity;
|
|
|
|
|
|
using QYZH.InteractiveMagazine.Models.Settings;
|
|
|
|
|
|
using QYZH.InteractiveMagazine.Repository;
|
|
|
|
|
|
|
|
|
|
|
|
namespace QYZH.InteractiveMagazine.Service;
|
|
|
|
|
|
|
2026-06-02 14:10:43 +08:00
|
|
|
|
public class AdminAuthService(BaseRepository<AdminUser> adminUserRepository, IConfiguration configuration, ILogger<AdminAuthService> logger) : BaseRepository<AdminUser>, IAdminAuthService
|
2026-06-01 17:59:23 +08:00
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
private const string TokenKeyPrefix = "InteractiveMagazine:AdminAuth:Token";
|
|
|
|
|
|
private const string UserInfoKeyPrefix = "InteractiveMagazine:AdminAuth:UserInfo";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public async Task<AdminLoginOutput> LoginAsync(AdminLoginInput input)
|
|
|
|
|
|
{
|
2026-06-02 14:10:43 +08:00
|
|
|
|
logger.LogInformation("管理员登录尝试,用户名: {UserName}", input.UserName);
|
2026-06-01 17:59:23 +08:00
|
|
|
|
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(input.UserName))
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new BusinessException("用户名不能为空", 400);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(input.Password))
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new BusinessException("密码不能为空", 400);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-02 14:10:43 +08:00
|
|
|
|
var adminUser = await adminUserRepository.GetFirstAsync(a => a.UserName == input.UserName);
|
2026-06-01 17:59:23 +08:00
|
|
|
|
if (adminUser == null)
|
|
|
|
|
|
{
|
2026-06-02 14:10:43 +08:00
|
|
|
|
logger.LogWarning("管理员登录失败,用户名不存在: {UserName}", input.UserName);
|
2026-06-01 17:59:23 +08:00
|
|
|
|
throw new BusinessException("用户名或密码错误", 401);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!BCrypt.Net.BCrypt.Verify(input.Password, adminUser.PasswordHash))
|
|
|
|
|
|
{
|
2026-06-02 14:10:43 +08:00
|
|
|
|
logger.LogWarning("管理员登录失败,密码错误: {UserName}", input.UserName);
|
2026-06-01 17:59:23 +08:00
|
|
|
|
throw new BusinessException("用户名或密码错误", 401);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-02 17:58:10 +08:00
|
|
|
|
if (adminUser.Status != 1)
|
2026-06-01 17:59:23 +08:00
|
|
|
|
{
|
2026-06-02 14:10:43 +08:00
|
|
|
|
logger.LogWarning("管理员登录失败,账号已禁用: {UserName}", input.UserName);
|
2026-06-01 17:59:23 +08:00
|
|
|
|
throw new BusinessException("账号已被禁用,请联系系统管理员", 403);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var jwtSettings = GetJwtSettings();
|
|
|
|
|
|
|
|
|
|
|
|
var token = JwtHelper.GenerateToken((long)adminUser.Id, adminUser.UserName, jwtSettings);
|
|
|
|
|
|
|
|
|
|
|
|
await RedisHelper.StringSetAsync($"{TokenKeyPrefix}:{adminUser.Id}", token, TimeSpan.FromMinutes(jwtSettings.ExpiryMinutes));
|
|
|
|
|
|
|
2026-06-02 14:10:43 +08:00
|
|
|
|
logger.LogInformation("管理员登录成功,用户名: {UserName}, ID: {UserId}", input.UserName, adminUser.Id);
|
2026-06-01 17:59:23 +08:00
|
|
|
|
|
|
|
|
|
|
return new AdminLoginOutput
|
|
|
|
|
|
{
|
|
|
|
|
|
Token = token,
|
|
|
|
|
|
UserId = (long)adminUser.Id,
|
|
|
|
|
|
UserName = adminUser.UserName,
|
|
|
|
|
|
Type = adminUser.Type
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public async Task LogoutAsync(long userId)
|
|
|
|
|
|
{
|
2026-06-02 14:10:43 +08:00
|
|
|
|
logger.LogInformation("管理员登出,ID: {UserId}", userId);
|
2026-06-01 17:59:23 +08:00
|
|
|
|
|
|
|
|
|
|
await RedisHelper.KeyDeleteAsync($"{TokenKeyPrefix}:{userId}");
|
|
|
|
|
|
|
2026-06-02 14:10:43 +08:00
|
|
|
|
logger.LogInformation("管理员登出成功,ID: {UserId}", userId);
|
2026-06-01 17:59:23 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public async Task<AdminUserInfoOutput> GetAdminInfoAsync(long userId)
|
|
|
|
|
|
{
|
2026-06-02 14:10:43 +08:00
|
|
|
|
logger.LogInformation("获取管理员信息,ID: {UserId}", userId);
|
2026-06-01 17:59:23 +08:00
|
|
|
|
|
2026-06-02 14:10:43 +08:00
|
|
|
|
var adminUser = await adminUserRepository.GetByIdAsync(userId);
|
2026-06-01 17:59:23 +08:00
|
|
|
|
if (adminUser == null)
|
|
|
|
|
|
{
|
2026-06-02 14:10:43 +08:00
|
|
|
|
logger.LogWarning("未找到管理员,ID: {UserId}", userId);
|
2026-06-01 17:59:23 +08:00
|
|
|
|
throw new BusinessException("用户不存在", 404);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return new AdminUserInfoOutput
|
|
|
|
|
|
{
|
|
|
|
|
|
UserId = adminUser.Id,
|
|
|
|
|
|
UserName = adminUser.UserName,
|
|
|
|
|
|
Type = adminUser.Type,
|
|
|
|
|
|
Status = adminUser.Status
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public async Task ChangePasswordAsync(long userId, string oldPassword, string newPassword)
|
|
|
|
|
|
{
|
2026-06-02 14:10:43 +08:00
|
|
|
|
logger.LogInformation("管理员修改密码尝试,ID: {UserId}", userId);
|
2026-06-01 17:59:23 +08:00
|
|
|
|
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(oldPassword))
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new BusinessException("原密码不能为空", 400);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(newPassword))
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new BusinessException("新密码不能为空", 400);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-02 14:10:43 +08:00
|
|
|
|
var adminUser = await adminUserRepository.GetByIdAsync(userId);
|
2026-06-01 17:59:23 +08:00
|
|
|
|
if (adminUser == null)
|
|
|
|
|
|
{
|
2026-06-02 14:10:43 +08:00
|
|
|
|
logger.LogWarning("未找到管理员,ID: {UserId}", userId);
|
2026-06-01 17:59:23 +08:00
|
|
|
|
throw new BusinessException("用户不存在", 404);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!BCrypt.Net.BCrypt.Verify(oldPassword, adminUser.PasswordHash))
|
|
|
|
|
|
{
|
2026-06-02 14:10:43 +08:00
|
|
|
|
logger.LogWarning("管理员修改密码失败,原密码错误,ID: {UserId}", userId);
|
2026-06-01 17:59:23 +08:00
|
|
|
|
throw new BusinessException("原密码错误", 400);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
adminUser.PasswordHash = BCrypt.Net.BCrypt.HashPassword(newPassword);
|
|
|
|
|
|
|
2026-06-02 14:10:43 +08:00
|
|
|
|
var result = await adminUserRepository.UpdateAsync(adminUser);
|
2026-06-01 17:59:23 +08:00
|
|
|
|
if (!result)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new BusinessException("修改密码失败", 500);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
await RedisHelper.KeyDeleteAsync($"{TokenKeyPrefix}:{userId}");
|
|
|
|
|
|
|
2026-06-02 14:10:43 +08:00
|
|
|
|
logger.LogInformation("管理员修改密码成功,ID: {UserId}", userId);
|
2026-06-01 17:59:23 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private JwtSettings GetJwtSettings()
|
|
|
|
|
|
{
|
2026-06-02 14:10:43 +08:00
|
|
|
|
var jwtSettings = configuration.GetSection("JwtSettings").Get<JwtSettings>()
|
2026-06-01 17:59:23 +08:00
|
|
|
|
?? new JwtSettings
|
|
|
|
|
|
{
|
|
|
|
|
|
Issuer = "QYZH.InteractiveMagazine",
|
|
|
|
|
|
Audience = "QYZH.InteractiveMagazine",
|
|
|
|
|
|
SecretKey = "your-256-bit-secret-key-here-change-in-production",
|
|
|
|
|
|
ExpiryMinutes = 120
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(jwtSettings.SecretKey))
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new BusinessException("JWT 配置不完整", 500);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return jwtSettings;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|