using Fengling.Console.Models.Dtos; using Fengling.Platform.Domain.AggregatesModel.UserAggregate; using Fengling.Platform.Domain.AggregatesModel.RoleAggregate; using Fengling.Platform.Domain.AggregatesModel.TenantAggregate; using Fengling.Platform.Infrastructure; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; 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? tenantCode = 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( ITenantManager tenantManager, UserManager userManager, RoleManager roleManager, PlatformDbContext context, IHttpContextAccessor httpContextAccessor) : IUserService { public async Task<(IEnumerable Items, int TotalCount)> GetUsersAsync(int page, int pageSize, string? userName = null, string? email = null, string? tenantCode = null) { var query = context.Users.AsQueryable(); if (!string.IsNullOrEmpty(userName)) { query = query.Where(u => u.UserName != null && u.UserName.Contains(userName)); } if (!string.IsNullOrEmpty(email)) { query = query.Where(u => u.Email != null && u.Email.Contains(email)); } if (!string.IsNullOrEmpty(tenantCode)) { query = query.Where(u => u.TenantInfo != null && u.TenantInfo.TenantCode != null && u.TenantInfo.TenantCode.Contains(tenantCode)); } var totalCount = await query.CountAsync(); var users = await query .OrderByDescending(u => u.CreatedTime) .Skip((page - 1) * pageSize) .Take(pageSize) .ToListAsync(); var userDtos = new List(); foreach (var user in users) { var roles = await userManager.GetRolesAsync(user); var tenant = user.TenantInfo?.TenantId > 0 ? await tenantManager.FindByIdAsync(user.TenantInfo.TenantId) : null; userDtos.Add(new UserDto { Id = user.Id, UserName = user.UserName, Email = user.Email, RealName = user.RealName, PhoneNumber = user.PhoneNumber, TenantCode = user.TenantInfo?.TenantCode, TenantName = tenant?.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 context.Users.FindAsync(id); if (user == null) return null; var roles = await userManager.GetRolesAsync(user); var tenant = user.TenantInfo?.TenantId > 0 ? await tenantManager.FindByIdAsync(user.TenantInfo.TenantId) : null; return new UserDto { Id = user.Id, UserName = user.UserName, Email = user.Email, RealName = user.RealName, PhoneNumber = user.PhoneNumber, TenantCode = user.TenantInfo?.TenantCode, TenantName = tenant?.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) { Tenant? tenant = null; if (dto.TenantId.HasValue && dto.TenantId.Value > 0) { tenant = await tenantManager.FindByIdAsync(dto.TenantId.Value); if (tenant == null) { throw new InvalidOperationException("Invalid tenant ID"); } } var tenantInfo = tenant != null ? new TenantInfo(tenant.Id, tenant.TenantCode, tenant.Name) : new TenantInfo(0, "", ""); var user = new ApplicationUser { UserName = dto.UserName, Email = dto.Email, RealName = dto.RealName, PhoneNumber = dto.PhoneNumber, TenantInfo = tenantInfo, 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.Count != 0) { 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, PhoneNumber = user.PhoneNumber, TenantCode = user.TenantInfo.TenantId.ToString(), TenantName = tenant?.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 context.Users.FindAsync(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.PhoneNumber = dto.PhoneNumber; user.EmailConfirmed = dto.EmailConfirmed; user.UpdatedTime = DateTime.UtcNow; await userManager.UpdateAsync(user); 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); } var tenant = user.TenantInfo?.TenantId > 0 ? await tenantManager.FindByIdAsync(user.TenantInfo.TenantId) : null; 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, PhoneNumber = user.PhoneNumber, TenantCode = user.TenantInfo?.TenantCode, TenantName = tenant?.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 context.Users.FindAsync(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 tenantIdClaim = httpContext?.User?.FindFirstValue("TenantId"); var log = new AuditLog { Operator = userName, TenantId = tenantIdClaim, 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(); } }