- 后端新增管理员、商品、分类聚合模型 - 实现积分规则、礼品、订单、会员等完整功能 - 添加管理员认证和权限管理 - 完善数据库迁移和实体配置 - 前端管理后台实现登录、仪表盘、积分规则、礼品、订单、会员等页面 - 集成shadcn-vue UI组件库 - 添加前后端功能文档和截图
104 lines
3.5 KiB
C#
104 lines
3.5 KiB
C#
using Fengling.Backend.Domain.AggregatesModel.MemberAggregate;
|
|
using Fengling.Backend.Infrastructure.Repositories;
|
|
using Microsoft.IdentityModel.Tokens;
|
|
using System.IdentityModel.Tokens.Jwt;
|
|
using System.Security.Claims;
|
|
using System.Text;
|
|
using Fengling.Backend.Web.Utils;
|
|
|
|
namespace Fengling.Backend.Web.Application.Commands.Members;
|
|
|
|
/// <summary>
|
|
/// 会员登录命令
|
|
/// </summary>
|
|
public record LoginMemberCommand(string Phone, string Password) : ICommand<LoginMemberResponse>;
|
|
|
|
/// <summary>
|
|
/// 登录响应
|
|
/// </summary>
|
|
public record LoginMemberResponse(MemberId MemberId, string Token);
|
|
|
|
/// <summary>
|
|
/// 会员登录命令验证器
|
|
/// </summary>
|
|
public class LoginMemberCommandValidator : AbstractValidator<LoginMemberCommand>
|
|
{
|
|
public LoginMemberCommandValidator()
|
|
{
|
|
RuleFor(x => x.Phone)
|
|
.NotEmpty().WithMessage("手机号不能为空");
|
|
|
|
RuleFor(x => x.Password)
|
|
.NotEmpty().WithMessage("密码不能为空");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 会员登录命令处理器
|
|
/// </summary>
|
|
public class LoginMemberCommandHandler(
|
|
IMemberRepository memberRepository,
|
|
IConfiguration configuration)
|
|
: ICommandHandler<LoginMemberCommand, LoginMemberResponse>
|
|
{
|
|
public async Task<LoginMemberResponse> Handle(LoginMemberCommand command, CancellationToken cancellationToken)
|
|
{
|
|
// 查询会员
|
|
var member = await memberRepository.GetByPhoneAsync(command.Phone, cancellationToken)
|
|
?? throw new KnownException("手机号或密码错误");
|
|
|
|
// 验证密码
|
|
var hashedPassword = HashPassword(command.Password);
|
|
if (member.Password != hashedPassword)
|
|
throw new KnownException("手机号或密码错误");
|
|
|
|
// 检查状态
|
|
if (member.Status == MemberStatus.Disabled)
|
|
throw new KnownException("该账号已被禁用");
|
|
|
|
// 生成JWT Token
|
|
var token = GenerateJwtToken(member.Id, member.Phone);
|
|
|
|
return new LoginMemberResponse(member.Id, token);
|
|
}
|
|
|
|
private static string HashPassword(string password)
|
|
{
|
|
return Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(password));
|
|
}
|
|
|
|
private string GenerateJwtToken(MemberId memberId, string phone)
|
|
{
|
|
var appConfig = configuration.GetSection("AppConfiguration").Get<AppConfiguration>()
|
|
?? new AppConfiguration
|
|
{
|
|
JwtIssuer = "FenglingBackend",
|
|
JwtAudience = "FenglingBackend",
|
|
Secret = "YourVerySecretKeyForJwtTokenGeneration12345!",
|
|
TokenExpiryInMinutes = 1440 // 24小时
|
|
};
|
|
|
|
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(
|
|
appConfig.Secret.Length >= 32 ? appConfig.Secret : "YourVerySecretKeyForJwtTokenGeneration12345!"));
|
|
|
|
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
|
|
|
|
var claims = new[]
|
|
{
|
|
new Claim(ClaimTypes.NameIdentifier, memberId.ToString()),
|
|
new Claim(ClaimTypes.Name, phone),
|
|
new Claim(ClaimTypes.Role, "Member"),
|
|
new Claim("member_id", memberId.ToString())
|
|
};
|
|
|
|
var token = new JwtSecurityToken(
|
|
issuer: appConfig.JwtIssuer,
|
|
audience: appConfig.JwtAudience,
|
|
claims: claims,
|
|
expires: DateTime.UtcNow.AddMinutes(appConfig.TokenExpiryInMinutes),
|
|
signingCredentials: creds);
|
|
|
|
return new JwtSecurityTokenHandler().WriteToken(token);
|
|
}
|
|
}
|