using Microsoft.EntityFrameworkCore; using MilliSim.Common.Dtos; using MilliSim.Common.Entities; using MilliSim.Common.Models; using MilliSim.Data; using MilliSim.Services.Infrastructure; namespace MilliSim.Services; public class AuthService : IAuthService { private readonly MilliSimDbContext _db; private readonly IJwtService _jwtService; private readonly ICaptchaService _captchaService; private readonly ICurrentUserService _currentUser; private readonly IFileStorageService _fileStorage; private readonly IUploadTrackingService _uploadTracking; public AuthService( MilliSimDbContext db, IJwtService jwtService, ICaptchaService captchaService, ICurrentUserService currentUser, IFileStorageService fileStorage, IUploadTrackingService uploadTracking) { _db = db; _jwtService = jwtService; _captchaService = captchaService; _currentUser = currentUser; _fileStorage = fileStorage; _uploadTracking = uploadTracking; } public Task> GetCaptchaAsync() { var (captchaId, svg) = _captchaService.GenerateCaptcha(); var result = new CaptchaResult { CaptchaId = captchaId, Svg = svg }; return Task.FromResult(ApiResponse.Success(result)); } public async Task> LoginAsync(LoginRequest request) { // 验证码校验 if (!_captchaService.ValidateCaptcha(request.CaptchaId, request.CaptchaCode)) { return ApiResponse.Fail("验证码错误或已失效", 400); } if (string.IsNullOrWhiteSpace(request.Username) || string.IsNullOrWhiteSpace(request.Password)) { return ApiResponse.Fail("用户名或密码不能为空", 400); } var user = await _db.Users.FirstOrDefaultAsync(u => u.Username == request.Username); if (user == null) { return ApiResponse.Fail("用户名或密码错误", 400); } if (!BCryptHelper.VerifyPassword(request.Password, user.Password)) { return ApiResponse.Fail("用户名或密码错误", 400); } if (user.Status == UserStatus.Disabled) { return ApiResponse.Fail("账号已被禁用,请联系管理员", 403); } var token = _jwtService.GenerateToken(user); var response = new LoginResponse { Token = token, UserId = user.Id, Username = user.Username, Nickname = user.Nickname, Avatar = string.IsNullOrEmpty(user.Avatar) ? "" : _fileStorage.GetSignedUrl(user.Avatar), Role = user.Role, RoleName = user.Role.ToString() }; return ApiResponse.Success(response); } public async Task ChangePasswordAsync(ChangePasswordRequest request) { var userId = _currentUser.UserId; if (userId == 0) { return ApiResponse.Fail("用户未登录", 401); } var user = await _db.Users.FirstOrDefaultAsync(u => u.Id == userId); if (user == null) { return ApiResponse.Fail("用户不存在", 404); } if (!BCryptHelper.VerifyPassword(request.OldPassword, user.Password)) { return ApiResponse.Fail("原密码错误", 400); } if (string.IsNullOrWhiteSpace(request.NewPassword)) { return ApiResponse.Fail("新密码不能为空", 400); } if (request.NewPassword == request.OldPassword) { return ApiResponse.Fail("新密码不能与原密码相同", 400); } var strengthError = ValidatePasswordStrength(request.NewPassword); if (strengthError != null) { return ApiResponse.Fail(strengthError, 400); } user.Password = BCryptHelper.HashPassword(request.NewPassword); user.UpdatedAt = DateTime.Now; await _db.SaveChangesAsync(); return ApiResponse.Success("密码修改成功"); } public async Task> GetProfileAsync() { var userId = _currentUser.UserId; if (userId == 0) { return ApiResponse.Fail("用户未登录", 401); } var user = await _db.Users.FirstOrDefaultAsync(u => u.Id == userId); if (user == null) { return ApiResponse.Fail("用户不存在", 404); } return ApiResponse.Success(MapToDto(user, _fileStorage)); } public async Task> UpdateProfileAsync(UpdateProfileRequest request) { var userId = _currentUser.UserId; if (userId == 0) { return ApiResponse.Fail("用户未登录", 401); } var user = await _db.Users.FirstOrDefaultAsync(u => u.Id == userId); if (user == null) { return ApiResponse.Fail("用户不存在", 404); } // 记录旧头像 key(用于替换时删除旧文件) var oldAvatarKey = _fileStorage.ExtractKey(user.Avatar); var newAvatarKey = oldAvatarKey; if (!string.IsNullOrWhiteSpace(request.Nickname)) { user.Nickname = request.Nickname; } if (request.Avatar != null) { // 存储 COS 对象键而非签名URL,避免 URL 过期后无法刷新 user.Avatar = _fileStorage.ExtractKey(request.Avatar); newAvatarKey = _fileStorage.ExtractKey(request.Avatar); } if (request.Phone != null) { user.Phone = request.Phone; } if (request.Email != null) { user.Email = request.Email; } user.UpdatedAt = DateTime.Now; await _db.SaveChangesAsync(); // 头像替换:删除旧文件 if (!string.IsNullOrEmpty(oldAvatarKey) && oldAvatarKey != newAvatarKey) { await _fileStorage.DeleteAsync(oldAvatarKey); } // 消费新上传的头像 if (request.Avatar != null) { await _uploadTracking.ConsumeAsync(newAvatarKey); } return ApiResponse.Success(MapToDto(user, _fileStorage)); } /// /// 密码强度校验:8位以上含字母和数字 /// internal static string? ValidatePasswordStrength(string password) { if (password.Length < 8) { return "密码长度不能少于8位"; } if (!password.Any(char.IsLetter)) { return "密码必须包含字母"; } if (!password.Any(char.IsDigit)) { return "密码必须包含数字"; } return null; } internal static UserDto MapToDto(User user, IFileStorageService fileStorage) { return new UserDto { Id = user.Id, Username = user.Username, Nickname = user.Nickname, // 头像存储为 COS 对象键,读取时刷新为签名URL Avatar = string.IsNullOrEmpty(user.Avatar) ? "" : fileStorage.GetSignedUrl(user.Avatar), Phone = user.Phone, Email = user.Email, Role = user.Role, RoleName = user.Role.ToString(), Status = user.Status, CreatedBy = user.CreatedBy, CreatedAt = user.CreatedAt }; } }