239 lines
7.5 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<ApiResponse<CaptchaResult>> GetCaptchaAsync()
{
var (captchaId, svg) = _captchaService.GenerateCaptcha();
var result = new CaptchaResult { CaptchaId = captchaId, Svg = svg };
return Task.FromResult(ApiResponse<CaptchaResult>.Success(result));
}
public async Task<ApiResponse<LoginResponse>> LoginAsync(LoginRequest request)
{
// 验证码校验
if (!_captchaService.ValidateCaptcha(request.CaptchaId, request.CaptchaCode))
{
return ApiResponse<LoginResponse>.Fail("验证码错误或已失效", 400);
}
if (string.IsNullOrWhiteSpace(request.Username) || string.IsNullOrWhiteSpace(request.Password))
{
return ApiResponse<LoginResponse>.Fail("用户名或密码不能为空", 400);
}
var user = await _db.Users.FirstOrDefaultAsync(u => u.Username == request.Username);
if (user == null)
{
return ApiResponse<LoginResponse>.Fail("用户名或密码错误", 400);
}
if (!BCryptHelper.VerifyPassword(request.Password, user.Password))
{
return ApiResponse<LoginResponse>.Fail("用户名或密码错误", 400);
}
if (user.Status == UserStatus.Disabled)
{
return ApiResponse<LoginResponse>.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<LoginResponse>.Success(response);
}
public async Task<ApiResponse> 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<ApiResponse<UserDto>> GetProfileAsync()
{
var userId = _currentUser.UserId;
if (userId == 0)
{
return ApiResponse<UserDto>.Fail("用户未登录", 401);
}
var user = await _db.Users.FirstOrDefaultAsync(u => u.Id == userId);
if (user == null)
{
return ApiResponse<UserDto>.Fail("用户不存在", 404);
}
return ApiResponse<UserDto>.Success(MapToDto(user, _fileStorage));
}
public async Task<ApiResponse<UserDto>> UpdateProfileAsync(UpdateProfileRequest request)
{
var userId = _currentUser.UserId;
if (userId == 0)
{
return ApiResponse<UserDto>.Fail("用户未登录", 401);
}
var user = await _db.Users.FirstOrDefaultAsync(u => u.Id == userId);
if (user == null)
{
return ApiResponse<UserDto>.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<UserDto>.Success(MapToDto(user, _fileStorage));
}
/// <summary>
/// 密码强度校验8位以上含字母和数字
/// </summary>
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
};
}
}