- 后端新增管理员、商品、分类聚合模型 - 实现积分规则、礼品、订单、会员等完整功能 - 添加管理员认证和权限管理 - 完善数据库迁移和实体配置 - 前端管理后台实现登录、仪表盘、积分规则、礼品、订单、会员等页面 - 集成shadcn-vue UI组件库 - 添加前后端功能文档和截图
110 lines
3.6 KiB
C#
110 lines
3.6 KiB
C#
using Fengling.Backend.Domain.AggregatesModel.AdminAggregate;
|
|
using Fengling.Backend.Infrastructure.Repositories;
|
|
using Microsoft.IdentityModel.Tokens;
|
|
using System.IdentityModel.Tokens.Jwt;
|
|
using System.Security.Claims;
|
|
using System.Text;
|
|
|
|
namespace Fengling.Backend.Web.Application.Commands.AdminAuth;
|
|
|
|
/// <summary>
|
|
/// 管理员登录命令
|
|
/// </summary>
|
|
public record AdminLoginCommand(string Username, string Password) : ICommand<AdminLoginResponse>;
|
|
|
|
/// <summary>
|
|
/// 管理员登录响应
|
|
/// </summary>
|
|
public record AdminLoginResponse(AdminId AdminId, string Username, string Token, DateTime ExpiresAt);
|
|
|
|
/// <summary>
|
|
/// 管理员登录命令验证器
|
|
/// </summary>
|
|
public class AdminLoginCommandValidator : AbstractValidator<AdminLoginCommand>
|
|
{
|
|
public AdminLoginCommandValidator()
|
|
{
|
|
RuleFor(x => x.Username)
|
|
.NotEmpty().WithMessage("用户名不能为空")
|
|
.MaximumLength(50).WithMessage("用户名长度不能超过50个字符");
|
|
|
|
RuleFor(x => x.Password)
|
|
.NotEmpty().WithMessage("密码不能为空");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 管理员登录命令处理器
|
|
/// </summary>
|
|
public class AdminLoginCommandHandler(
|
|
IAdminRepository adminRepository,
|
|
IConfiguration configuration)
|
|
: ICommandHandler<AdminLoginCommand, AdminLoginResponse>
|
|
{
|
|
public async Task<AdminLoginResponse> Handle(AdminLoginCommand command, CancellationToken cancellationToken)
|
|
{
|
|
// 1. 通过用户名查询管理员
|
|
var admin = await adminRepository.GetByUsernameAsync(command.Username, cancellationToken);
|
|
if (admin is null)
|
|
{
|
|
throw new KnownException("用户名或密码错误");
|
|
}
|
|
|
|
// 2. 检查状态
|
|
if (admin.Status == AdminStatus.Disabled)
|
|
{
|
|
throw new KnownException("账号已被禁用");
|
|
}
|
|
|
|
// 3. 验证密码
|
|
if (!admin.VerifyPassword(command.Password))
|
|
{
|
|
throw new KnownException("用户名或密码错误");
|
|
}
|
|
|
|
// 4. 记录登录时间
|
|
admin.RecordLogin();
|
|
|
|
// 5. 生成 JWT Token
|
|
var token = GenerateJwtToken(admin.Id, admin.Username);
|
|
var expiresAt = DateTime.UtcNow.AddHours(24);
|
|
|
|
return new AdminLoginResponse(admin.Id, admin.Username, token, expiresAt);
|
|
}
|
|
|
|
private string GenerateJwtToken(AdminId adminId, string username)
|
|
{
|
|
var appConfig = configuration.GetSection("AppConfiguration").Get<Utils.AppConfiguration>()
|
|
?? new Utils.AppConfiguration
|
|
{
|
|
JwtIssuer = "FenglingBackend",
|
|
JwtAudience = "FenglingBackend",
|
|
Secret = "YourVerySecretKeyForJwtTokenGeneration12345!"
|
|
};
|
|
|
|
var claims = new[]
|
|
{
|
|
new Claim(ClaimTypes.NameIdentifier, adminId.ToString()),
|
|
new Claim(ClaimTypes.Name, username),
|
|
new Claim(ClaimTypes.Role, "Admin"),
|
|
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
|
|
};
|
|
|
|
// 使用对称密钥
|
|
var secret = appConfig.Secret.Length >= 32 ? appConfig.Secret : "YourVerySecretKeyForJwtTokenGeneration12345!";
|
|
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secret));
|
|
|
|
var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
|
|
|
|
var token = new JwtSecurityToken(
|
|
issuer: appConfig.JwtIssuer,
|
|
audience: appConfig.JwtAudience,
|
|
claims: claims,
|
|
expires: DateTime.UtcNow.AddHours(24),
|
|
signingCredentials: credentials
|
|
);
|
|
|
|
return new JwtSecurityTokenHandler().WriteToken(token);
|
|
}
|
|
}
|