diff --git a/Services/RoleService.cs b/Services/RoleService.cs new file mode 100644 index 0000000..eade92b --- /dev/null +++ b/Services/RoleService.cs @@ -0,0 +1,267 @@ +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 IRoleService +{ + Task<(IEnumerable Items, int TotalCount)> GetRolesAsync(int page, int pageSize, string? name = null, string? tenantId = null); + Task GetRoleAsync(long id); + Task> GetRoleUsersAsync(long id); + Task CreateRoleAsync(CreateRoleDto dto); + Task UpdateRoleAsync(long id, UpdateRoleDto dto); + Task DeleteRoleAsync(long id); + Task RemoveUserFromRoleAsync(long roleId, long userId); +} + +public class RoleService : IRoleService +{ + private readonly IRoleRepository _repository; + private readonly UserManager _userManager; + private readonly RoleManager _roleManager; + private readonly ApplicationDbContext _context; + private readonly IHttpContextAccessor _httpContextAccessor; + + public RoleService( + IRoleRepository repository, + UserManager userManager, + RoleManager roleManager, + ApplicationDbContext context, + IHttpContextAccessor httpContextAccessor) + { + _repository = repository; + _userManager = userManager; + _roleManager = roleManager; + _context = context; + _httpContextAccessor = httpContextAccessor; + } + + public async Task<(IEnumerable Items, int TotalCount)> GetRolesAsync(int page, int pageSize, string? name = null, string? tenantId = null) + { + var roles = await _repository.GetPagedAsync(page, pageSize, name, tenantId); + var totalCount = await _repository.CountAsync(name, tenantId); + + var roleDtos = new List(); + foreach (var role in roles) + { + var users = await _userManager.GetUsersInRoleAsync(role.Name!); + roleDtos.Add(new RoleDto + { + Id = role.Id, + Name = role.Name, + DisplayName = role.DisplayName, + Description = role.Description, + TenantId = role.TenantId, + IsSystem = role.IsSystem, + Permissions = role.Permissions, + UserCount = users.Count, + CreatedAt = role.CreatedTime + }); + } + + return (roleDtos, totalCount); + } + + public async Task GetRoleAsync(long id) + { + var role = await _repository.GetByIdAsync(id); + if (role == null) return null; + + return new RoleDto + { + Id = role.Id, + Name = role.Name, + DisplayName = role.DisplayName, + Description = role.Description, + TenantId = role.TenantId, + IsSystem = role.IsSystem, + Permissions = role.Permissions, + UserCount = (await _userManager.GetUsersInRoleAsync(role.Name!)).Count, + CreatedAt = role.CreatedTime + }; + } + + public async Task> GetRoleUsersAsync(long id) + { + var role = await _repository.GetByIdAsync(id); + if (role == null) + { + throw new KeyNotFoundException($"Role with ID {id} not found"); + } + + var users = await _userManager.GetUsersInRoleAsync(role.Name!); + 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, + 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; + } + + public async Task CreateRoleAsync(CreateRoleDto dto) + { + var role = new ApplicationRole + { + Name = dto.Name, + DisplayName = dto.DisplayName, + Description = dto.Description, + TenantId = dto.TenantId, + Permissions = dto.Permissions, + IsSystem = false, + CreatedTime = DateTime.UtcNow + }; + + var result = await _roleManager.CreateAsync(role); + if (!result.Succeeded) + { + throw new InvalidOperationException(string.Join(", ", result.Errors.Select(e => e.Description))); + } + + await CreateAuditLog("role", "create", "Role", role.Id, role.DisplayName, null, System.Text.Json.JsonSerializer.Serialize(dto)); + + return new RoleDto + { + Id = role.Id, + Name = role.Name, + DisplayName = role.DisplayName, + Description = role.Description, + TenantId = role.TenantId, + IsSystem = role.IsSystem, + Permissions = role.Permissions, + UserCount = 0, + CreatedAt = role.CreatedTime + }; + } + + public async Task UpdateRoleAsync(long id, UpdateRoleDto dto) + { + var role = await _repository.GetByIdAsync(id); + if (role == null) + { + throw new KeyNotFoundException($"Role with ID {id} not found"); + } + + if (role.IsSystem) + { + throw new InvalidOperationException("系统角色不能修改"); + } + + var oldValue = System.Text.Json.JsonSerializer.Serialize(role); + + role.DisplayName = dto.DisplayName; + role.Description = dto.Description; + role.Permissions = dto.Permissions; + + await _repository.UpdateAsync(role); + + await CreateAuditLog("role", "update", "Role", role.Id, role.DisplayName, oldValue, System.Text.Json.JsonSerializer.Serialize(role)); + + var users = await _userManager.GetUsersInRoleAsync(role.Name!); + return new RoleDto + { + Id = role.Id, + Name = role.Name, + DisplayName = role.DisplayName, + Description = role.Description, + TenantId = role.TenantId, + IsSystem = role.IsSystem, + Permissions = role.Permissions, + UserCount = users.Count, + CreatedAt = role.CreatedTime + }; + } + + public async Task DeleteRoleAsync(long id) + { + var role = await _repository.GetByIdAsync(id); + if (role == null) + { + throw new KeyNotFoundException($"Role with ID {id} not found"); + } + + if (role.IsSystem) + { + throw new InvalidOperationException("系统角色不能删除"); + } + + var oldValue = System.Text.Json.JsonSerializer.Serialize(role); + var users = await _userManager.GetUsersInRoleAsync(role.Name!); + + foreach (var user in users) + { + await _userManager.RemoveFromRoleAsync(user, role.Name!); + } + + await _repository.DeleteAsync(role); + + await CreateAuditLog("role", "delete", "Role", role.Id, role.DisplayName, oldValue); + } + + public async Task RemoveUserFromRoleAsync(long roleId, long userId) + { + var role = await _repository.GetByIdAsync(roleId); + if (role == null) + { + throw new KeyNotFoundException($"Role with ID {roleId} not found"); + } + + var user = await _userManager.FindByIdAsync(userId.ToString()); + if (user == null) + { + throw new KeyNotFoundException($"User with ID {userId} not found"); + } + + var result = await _userManager.RemoveFromRoleAsync(user, role.Name!); + if (!result.Succeeded) + { + throw new InvalidOperationException(string.Join(", ", result.Errors.Select(e => e.Description))); + } + + await CreateAuditLog("role", "update", "UserRole", null, $"{role.Name} - {user.UserName}"); + } + + 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(); + } +}