- 配置AuthService使用OpenIddict reference tokens - 添加fengling-api客户端用于introspection验证 - 配置Console API通过OpenIddict验证reference tokens - 实现Tenant/Users/Roles/OAuthClients CRUD API - 添加GatewayController服务注册API - 重构Repository和Service层支持多租户 BREAKING CHANGE: API认证现在使用OpenIddict reference tokens
298 lines
9.9 KiB
C#
298 lines
9.9 KiB
C#
using Fengling.Console.Models.Dtos;
|
|
using Fengling.Console.Repositories;
|
|
using Microsoft.AspNetCore.Identity;
|
|
using System.Security.Claims;
|
|
using Fengling.Console.Datas;
|
|
using Fengling.Console.Models.Entities;
|
|
|
|
namespace Fengling.Console.Services;
|
|
|
|
public interface IRoleService
|
|
{
|
|
Task<(IEnumerable<RoleDto> Items, int TotalCount)> GetRolesAsync(int page, int pageSize, string? name = null,
|
|
string? tenantId = null);
|
|
|
|
Task<RoleDto?> GetRoleAsync(long id);
|
|
Task<IEnumerable<UserDto>> GetRoleUsersAsync(long id);
|
|
Task<RoleDto> CreateRoleAsync(CreateRoleDto dto);
|
|
Task<RoleDto> UpdateRoleAsync(long id, UpdateRoleDto dto);
|
|
Task DeleteRoleAsync(long id);
|
|
Task AddUserToRoleAsync(long roleId, long userId);
|
|
Task RemoveUserFromRoleAsync(long roleId, long userId);
|
|
}
|
|
|
|
public class RoleService : IRoleService
|
|
{
|
|
private readonly IRoleRepository _repository;
|
|
private readonly UserManager<ApplicationUser> _userManager;
|
|
private readonly RoleManager<ApplicationRole> _roleManager;
|
|
private readonly ApplicationDbContext _context;
|
|
private readonly IHttpContextAccessor _httpContextAccessor;
|
|
|
|
public RoleService(
|
|
IRoleRepository repository,
|
|
UserManager<ApplicationUser> userManager,
|
|
RoleManager<ApplicationRole> roleManager,
|
|
ApplicationDbContext context,
|
|
IHttpContextAccessor httpContextAccessor)
|
|
{
|
|
_repository = repository;
|
|
_userManager = userManager;
|
|
_roleManager = roleManager;
|
|
_context = context;
|
|
_httpContextAccessor = httpContextAccessor;
|
|
}
|
|
|
|
public async Task<(IEnumerable<RoleDto> 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<RoleDto>();
|
|
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<RoleDto?> 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<IEnumerable<UserDto>> 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<UserDto>();
|
|
|
|
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<RoleDto> 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<RoleDto> 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 AddUserToRoleAsync(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.AddToRoleAsync(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}");
|
|
}
|
|
|
|
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();
|
|
}
|
|
} |