From 993152aa565f3d17b2e569df9f0e544a5ec57547 Mon Sep 17 00:00:00 2001 From: Sam <315859133@qq.com> Date: Thu, 5 Feb 2026 14:16:40 +0800 Subject: [PATCH] feat(console): add UserService with audit logging --- Services/UserService.cs | 271 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 271 insertions(+) create mode 100644 Services/UserService.cs diff --git a/Services/UserService.cs b/Services/UserService.cs new file mode 100644 index 0000000..e07fc85 --- /dev/null +++ b/Services/UserService.cs @@ -0,0 +1,271 @@ +using Fengling.AuthService.Data; +using Fengling.AuthService.Models; +using Fengling.Console.Models.Dtos; +using Fengling.Console.Repositories; +using Microsoft.AspNetCore.Identity; +using System.Security.Claims; + +namespace Fengling.Console.Services; + +public interface IUserService +{ + Task<(IEnumerable Items, int TotalCount)> GetUsersAsync(int page, int pageSize, string? userName = null, string? email = null, string? tenantId = null); + Task GetUserAsync(long id); + Task CreateUserAsync(CreateUserDto dto); + Task UpdateUserAsync(long id, UpdateUserDto dto); + Task ResetPasswordAsync(long id, ResetPasswordDto dto); + Task DeleteUserAsync(long id); +} + +public class UserService : IUserService +{ + private readonly IUserRepository _repository; + private readonly ITenantRepository _tenantRepository; + private readonly UserManager _userManager; + private readonly RoleManager _roleManager; + private readonly ApplicationDbContext _context; + private readonly IHttpContextAccessor _httpContextAccessor; + + public UserService( + IUserRepository repository, + ITenantRepository tenantRepository, + UserManager userManager, + RoleManager roleManager, + ApplicationDbContext context, + IHttpContextAccessor httpContextAccessor) + { + _repository = repository; + _tenantRepository = tenantRepository; + _userManager = userManager; + _roleManager = roleManager; + _context = context; + _httpContextAccessor = httpContextAccessor; + } + + public async Task<(IEnumerable Items, int TotalCount)> GetUsersAsync(int page, int pageSize, string? userName = null, string? email = null, string? tenantId = null) + { + var users = await _repository.GetPagedAsync(page, pageSize, userName, email, tenantId); + var totalCount = await _repository.CountAsync(userName, email, tenantId); + + var userDtos = new List(); + foreach (var user in users) + { + var roles = await _userManager.GetRolesAsync(user); + userDtos.Add(new UserDto + { + Id = user.Id, + UserName = user.UserName, + Email = user.Email, + RealName = user.RealName, + Phone = user.Phone, + TenantId = user.TenantInfo.Id, + TenantName = user.TenantInfo.Name, + Roles = roles.ToList(), + EmailConfirmed = user.EmailConfirmed, + IsActive = !user.LockoutEnabled || user.LockoutEnd == null || user.LockoutEnd < DateTimeOffset.UtcNow, + CreatedAt = user.CreatedTime + }); + } + + return (userDtos, totalCount); + } + + public async Task GetUserAsync(long id) + { + var user = await _repository.GetByIdAsync(id); + if (user == null) return null; + + var roles = await _userManager.GetRolesAsync(user); + return new UserDto + { + Id = user.Id, + UserName = user.UserName, + Email = user.Email, + RealName = user.RealName, + Phone = user.Phone, + TenantId = user.TenantInfo.Id, + TenantName = user.TenantInfo.Name, + Roles = roles.ToList(), + EmailConfirmed = user.EmailConfirmed, + IsActive = !user.LockoutEnabled || user.LockoutEnd == null || user.LockoutEnd < DateTimeOffset.UtcNow, + CreatedAt = user.CreatedTime + }; + } + + public async Task CreateUserAsync(CreateUserDto dto) + { + var tenantId = dto.TenantId ?? 0; + Tenant? tenant = null; + + if (tenantId != 0) + { + tenant = await _tenantRepository.GetByIdAsync(tenantId); + if (tenant == null) + { + throw new InvalidOperationException("Invalid tenant ID"); + } + } + + var user = new ApplicationUser + { + UserName = dto.UserName, + Email = dto.Email, + RealName = dto.RealName, + Phone = dto.Phone, + TenantInfo = new TenantInfo(tenantId, tenant?.TenantId ?? "default", tenant?.Name ?? "默认租户"), + EmailConfirmed = dto.EmailConfirmed, + CreatedTime = DateTime.UtcNow + }; + + var result = await _userManager.CreateAsync(user, dto.Password); + if (!result.Succeeded) + { + throw new InvalidOperationException(string.Join(", ", result.Errors.Select(e => e.Description))); + } + + if (dto.RoleIds != null && dto.RoleIds.Any()) + { + foreach (var roleId in dto.RoleIds) + { + var role = await _roleManager.FindByIdAsync(roleId.ToString()); + if (role != null) + { + await _userManager.AddToRoleAsync(user, role.Name!); + } + } + } + + if (!dto.IsActive) + { + await _userManager.SetLockoutEnabledAsync(user, true); + await _userManager.SetLockoutEndDateAsync(user, DateTimeOffset.MaxValue); + } + + await CreateAuditLog("user", "create", "User", user.Id, user.UserName, null, System.Text.Json.JsonSerializer.Serialize(dto)); + + var roles = await _userManager.GetRolesAsync(user); + return new UserDto + { + Id = user.Id, + UserName = user.UserName, + Email = user.Email, + RealName = user.RealName, + Phone = user.Phone, + TenantId = user.TenantInfo.Id, + TenantName = user.TenantInfo.Name, + Roles = roles.ToList(), + EmailConfirmed = user.EmailConfirmed, + IsActive = !user.LockoutEnabled || user.LockoutEnd == null || user.LockoutEnd < DateTimeOffset.UtcNow, + CreatedAt = user.CreatedTime + }; + } + + public async Task UpdateUserAsync(long id, UpdateUserDto dto) + { + var user = await _repository.GetByIdAsync(id); + if (user == null) + { + throw new KeyNotFoundException($"User with ID {id} not found"); + } + + var oldValue = System.Text.Json.JsonSerializer.Serialize(user); + + user.Email = dto.Email; + user.RealName = dto.RealName; + user.Phone = dto.Phone; + user.EmailConfirmed = dto.EmailConfirmed; + user.UpdatedTime = DateTime.UtcNow; + + if (dto.IsActive) + { + await _userManager.SetLockoutEnabledAsync(user, false); + await _userManager.SetLockoutEndDateAsync(user, null); + } + else + { + await _userManager.SetLockoutEnabledAsync(user, true); + await _userManager.SetLockoutEndDateAsync(user, DateTimeOffset.MaxValue); + } + + await _context.SaveChangesAsync(); + + await CreateAuditLog("user", "update", "User", user.Id, user.UserName, oldValue, System.Text.Json.JsonSerializer.Serialize(user)); + + var roles = await _userManager.GetRolesAsync(user); + return new UserDto + { + Id = user.Id, + UserName = user.UserName, + Email = user.Email, + RealName = user.RealName, + Phone = user.Phone, + TenantId = user.TenantInfo.Id, + TenantName = user.TenantInfo.Name, + Roles = roles.ToList(), + EmailConfirmed = user.EmailConfirmed, + IsActive = !user.LockoutEnabled || user.LockoutEnd == null || user.LockoutEnd < DateTimeOffset.UtcNow, + CreatedAt = user.CreatedTime + }; + } + + public async Task ResetPasswordAsync(long id, ResetPasswordDto dto) + { + var user = await _userManager.FindByIdAsync(id.ToString()); + if (user == null) + { + throw new KeyNotFoundException($"User with ID {id} not found"); + } + + var token = await _userManager.GeneratePasswordResetTokenAsync(user); + var result = await _userManager.ResetPasswordAsync(user, token, dto.NewPassword); + + if (!result.Succeeded) + { + throw new InvalidOperationException(string.Join(", ", result.Errors.Select(e => e.Description))); + } + + await CreateAuditLog("user", "reset_password", "User", user.Id, user.UserName); + } + + public async Task DeleteUserAsync(long id) + { + var user = await _repository.GetByIdAsync(id); + if (user == null) + { + throw new KeyNotFoundException($"User with ID {id} not found"); + } + + var oldValue = System.Text.Json.JsonSerializer.Serialize(user); + user.IsDeleted = true; + user.UpdatedTime = DateTime.UtcNow; + await _context.SaveChangesAsync(); + + await CreateAuditLog("user", "delete", "User", user.Id, user.UserName, oldValue); + } + + private async Task CreateAuditLog(string operation, string action, string targetType, long? targetId, string? targetName, string? oldValue = null, string? newValue = null) + { + var httpContext = _httpContextAccessor.HttpContext; + var userName = httpContext?.User?.FindFirstValue(ClaimTypes.NameIdentifier) ?? httpContext?.User?.Identity?.Name ?? "system"; + var tenantId = httpContext?.User?.FindFirstValue("TenantId"); + + var log = new AuditLog + { + Operator = userName, + TenantId = tenantId, + Operation = operation, + Action = action, + TargetType = targetType, + TargetId = targetId, + TargetName = targetName, + IpAddress = httpContext?.Connection?.RemoteIpAddress?.ToString() ?? "unknown", + Status = "success", + OldValue = oldValue, + NewValue = newValue, + CreatedAt = DateTime.UtcNow + }; + + _context.AuditLogs.Add(log); + await _context.SaveChangesAsync(); + } +}