feat: 添加用户权限洞察查询与示例
This commit is contained in:
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using TakeoutSaaS.Application.Identity.Contracts;
|
||||
using TakeoutSaaS.Shared.Abstractions.Results;
|
||||
|
||||
namespace TakeoutSaaS.Application.Identity.Abstractions;
|
||||
|
||||
@@ -13,4 +14,6 @@ public interface IAdminAuthService
|
||||
Task<TokenResponse> LoginAsync(AdminLoginRequest request, CancellationToken cancellationToken = default);
|
||||
Task<TokenResponse> RefreshTokenAsync(RefreshTokenRequest request, CancellationToken cancellationToken = default);
|
||||
Task<CurrentUserProfile> GetProfileAsync(long userId, CancellationToken cancellationToken = default);
|
||||
Task<UserPermissionDto?> GetUserPermissionsAsync(long userId, CancellationToken cancellationToken = default);
|
||||
Task<PagedResult<UserPermissionDto>> SearchUserPermissionsAsync(string? keyword, int page, int pageSize, string? sortBy, bool sortDescending, CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using System.Text.Json.Serialization;
|
||||
using TakeoutSaaS.Shared.Abstractions.Serialization;
|
||||
|
||||
namespace TakeoutSaaS.Application.Identity.Contracts;
|
||||
|
||||
/// <summary>
|
||||
/// 用户权限概览 DTO。
|
||||
/// </summary>
|
||||
public sealed class UserPermissionDto
|
||||
{
|
||||
/// <summary>
|
||||
/// 用户 ID(雪花,序列化为字符串)。
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(SnowflakeIdJsonConverter))]
|
||||
public long UserId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 租户 ID(雪花,序列化为字符串)。
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(SnowflakeIdJsonConverter))]
|
||||
public long TenantId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 商户 ID(雪花,序列化为字符串,可空)。
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(NullableSnowflakeIdJsonConverter))]
|
||||
public long? MerchantId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 登录账号。
|
||||
/// </summary>
|
||||
public string Account { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 展示名称。
|
||||
/// </summary>
|
||||
public string DisplayName { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 角色集合。
|
||||
/// </summary>
|
||||
public string[] Roles { get; init; } = Array.Empty<string>();
|
||||
|
||||
/// <summary>
|
||||
/// 权限集合。
|
||||
/// </summary>
|
||||
public string[] Permissions { get; init; } = Array.Empty<string>();
|
||||
|
||||
/// <summary>
|
||||
/// 创建时间(UTC)。
|
||||
/// </summary>
|
||||
public DateTime CreatedAt { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.Identity.Contracts;
|
||||
using TakeoutSaaS.Application.Identity.Queries;
|
||||
using TakeoutSaaS.Domain.Identity.Repositories;
|
||||
using TakeoutSaaS.Shared.Abstractions.Tenancy;
|
||||
|
||||
namespace TakeoutSaaS.Application.Identity.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 按用户 ID 获取权限概览处理器。
|
||||
/// </summary>
|
||||
public sealed class GetUserPermissionsQueryHandler(
|
||||
IIdentityUserRepository identityUserRepository,
|
||||
ITenantProvider tenantProvider)
|
||||
: IRequestHandler<GetUserPermissionsQuery, UserPermissionDto?>
|
||||
{
|
||||
private readonly IIdentityUserRepository _identityUserRepository = identityUserRepository;
|
||||
private readonly ITenantProvider _tenantProvider = tenantProvider;
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<UserPermissionDto?> Handle(GetUserPermissionsQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
var tenantId = _tenantProvider.GetCurrentTenantId();
|
||||
var user = await _identityUserRepository.FindByIdAsync(request.UserId, cancellationToken);
|
||||
if (user == null || user.TenantId != tenantId)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new UserPermissionDto
|
||||
{
|
||||
UserId = user.Id,
|
||||
TenantId = user.TenantId,
|
||||
MerchantId = user.MerchantId,
|
||||
Account = user.Account,
|
||||
DisplayName = user.DisplayName,
|
||||
Roles = user.Roles,
|
||||
Permissions = user.Permissions,
|
||||
CreatedAt = user.CreatedAt
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
using System.Linq;
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.Identity.Contracts;
|
||||
using TakeoutSaaS.Application.Identity.Queries;
|
||||
using TakeoutSaaS.Domain.Identity.Repositories;
|
||||
using TakeoutSaaS.Shared.Abstractions.Results;
|
||||
using TakeoutSaaS.Shared.Abstractions.Tenancy;
|
||||
|
||||
namespace TakeoutSaaS.Application.Identity.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 租户用户权限分页查询处理器。
|
||||
/// </summary>
|
||||
public sealed class SearchUserPermissionsQueryHandler(
|
||||
IIdentityUserRepository identityUserRepository,
|
||||
ITenantProvider tenantProvider)
|
||||
: IRequestHandler<SearchUserPermissionsQuery, PagedResult<UserPermissionDto>>
|
||||
{
|
||||
private readonly IIdentityUserRepository _identityUserRepository = identityUserRepository;
|
||||
private readonly ITenantProvider _tenantProvider = tenantProvider;
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<PagedResult<UserPermissionDto>> Handle(SearchUserPermissionsQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
var tenantId = _tenantProvider.GetCurrentTenantId();
|
||||
var users = await _identityUserRepository.SearchAsync(tenantId, request.Keyword, cancellationToken);
|
||||
|
||||
var sorted = SortUsers(users, request.SortBy, request.SortDescending);
|
||||
var paged = sorted
|
||||
.Skip((request.Page - 1) * request.PageSize)
|
||||
.Take(request.PageSize)
|
||||
.ToList();
|
||||
|
||||
var items = paged.Select(user => new UserPermissionDto
|
||||
{
|
||||
UserId = user.Id,
|
||||
TenantId = user.TenantId,
|
||||
MerchantId = user.MerchantId,
|
||||
Account = user.Account,
|
||||
DisplayName = user.DisplayName,
|
||||
Roles = user.Roles,
|
||||
Permissions = user.Permissions,
|
||||
CreatedAt = user.CreatedAt
|
||||
}).ToList();
|
||||
|
||||
return new PagedResult<UserPermissionDto>(items, request.Page, request.PageSize, users.Count);
|
||||
}
|
||||
|
||||
private static IOrderedEnumerable<Domain.Identity.Entities.IdentityUser> SortUsers(
|
||||
IReadOnlyCollection<Domain.Identity.Entities.IdentityUser> users,
|
||||
string? sortBy,
|
||||
bool sortDescending)
|
||||
{
|
||||
return sortBy?.ToLowerInvariant() switch
|
||||
{
|
||||
"account" => sortDescending
|
||||
? users.OrderByDescending(x => x.Account)
|
||||
: users.OrderBy(x => x.Account),
|
||||
"displayname" => sortDescending
|
||||
? users.OrderByDescending(x => x.DisplayName)
|
||||
: users.OrderBy(x => x.DisplayName),
|
||||
_ => sortDescending
|
||||
? users.OrderByDescending(x => x.CreatedAt)
|
||||
: users.OrderBy(x => x.CreatedAt)
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.Identity.Contracts;
|
||||
|
||||
namespace TakeoutSaaS.Application.Identity.Queries;
|
||||
|
||||
/// <summary>
|
||||
/// 按用户 ID 获取角色/权限概览。
|
||||
/// </summary>
|
||||
public sealed class GetUserPermissionsQuery : IRequest<UserPermissionDto?>
|
||||
{
|
||||
/// <summary>
|
||||
/// 用户 ID(雪花)。
|
||||
/// </summary>
|
||||
public long UserId { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.Identity.Contracts;
|
||||
using TakeoutSaaS.Shared.Abstractions.Results;
|
||||
|
||||
namespace TakeoutSaaS.Application.Identity.Queries;
|
||||
|
||||
/// <summary>
|
||||
/// 按租户分页查询用户的角色/权限概览。
|
||||
/// </summary>
|
||||
public sealed class SearchUserPermissionsQuery : IRequest<PagedResult<UserPermissionDto>>
|
||||
{
|
||||
/// <summary>
|
||||
/// 关键字(账号或展示名称)。
|
||||
/// </summary>
|
||||
public string? Keyword { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 页码,从 1 开始。
|
||||
/// </summary>
|
||||
public int Page { get; init; } = 1;
|
||||
|
||||
/// <summary>
|
||||
/// 每页条数。
|
||||
/// </summary>
|
||||
public int PageSize { get; init; } = 20;
|
||||
|
||||
/// <summary>
|
||||
/// 排序字段(account/displayName/createdAt)。
|
||||
/// </summary>
|
||||
public string? SortBy { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否倒序。
|
||||
/// </summary>
|
||||
public bool SortDescending { get; init; } = true;
|
||||
}
|
||||
@@ -5,6 +5,8 @@ using TakeoutSaaS.Domain.Identity.Entities;
|
||||
using TakeoutSaaS.Domain.Identity.Repositories;
|
||||
using TakeoutSaaS.Shared.Abstractions.Constants;
|
||||
using TakeoutSaaS.Shared.Abstractions.Exceptions;
|
||||
using TakeoutSaaS.Shared.Abstractions.Results;
|
||||
using TakeoutSaaS.Shared.Abstractions.Tenancy;
|
||||
|
||||
namespace TakeoutSaaS.Application.Identity.Services;
|
||||
|
||||
@@ -15,8 +17,11 @@ public sealed class AdminAuthService(
|
||||
IIdentityUserRepository userRepository,
|
||||
IPasswordHasher<IdentityUser> passwordHasher,
|
||||
IJwtTokenService jwtTokenService,
|
||||
IRefreshTokenStore refreshTokenStore) : IAdminAuthService
|
||||
IRefreshTokenStore refreshTokenStore,
|
||||
ITenantProvider tenantProvider) : IAdminAuthService
|
||||
{
|
||||
private readonly ITenantProvider _tenantProvider = tenantProvider;
|
||||
|
||||
/// <summary>
|
||||
/// 管理后台登录:验证账号密码并生成令牌。
|
||||
/// </summary>
|
||||
@@ -85,6 +90,78 @@ public sealed class AdminAuthService(
|
||||
return BuildProfile(user);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定用户的权限概览(校验当前租户)。
|
||||
/// </summary>
|
||||
public async Task<UserPermissionDto?> GetUserPermissionsAsync(long userId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var tenantId = _tenantProvider.GetCurrentTenantId();
|
||||
var user = await userRepository.FindByIdAsync(userId, cancellationToken);
|
||||
if (user == null || user.TenantId != tenantId)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new UserPermissionDto
|
||||
{
|
||||
UserId = user.Id,
|
||||
TenantId = user.TenantId,
|
||||
MerchantId = user.MerchantId,
|
||||
Account = user.Account,
|
||||
DisplayName = user.DisplayName,
|
||||
Roles = user.Roles,
|
||||
Permissions = user.Permissions,
|
||||
CreatedAt = user.CreatedAt
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 按租户分页查询用户权限概览。
|
||||
/// </summary>
|
||||
public async Task<PagedResult<UserPermissionDto>> SearchUserPermissionsAsync(
|
||||
string? keyword,
|
||||
int page,
|
||||
int pageSize,
|
||||
string? sortBy,
|
||||
bool sortDescending,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var tenantId = _tenantProvider.GetCurrentTenantId();
|
||||
var users = await userRepository.SearchAsync(tenantId, keyword, cancellationToken);
|
||||
|
||||
var sorted = sortBy?.ToLowerInvariant() switch
|
||||
{
|
||||
"account" => sortDescending
|
||||
? users.OrderByDescending(x => x.Account)
|
||||
: users.OrderBy(x => x.Account),
|
||||
"displayname" => sortDescending
|
||||
? users.OrderByDescending(x => x.DisplayName)
|
||||
: users.OrderBy(x => x.DisplayName),
|
||||
_ => sortDescending
|
||||
? users.OrderByDescending(x => x.CreatedAt)
|
||||
: users.OrderBy(x => x.CreatedAt)
|
||||
};
|
||||
|
||||
var paged = sorted
|
||||
.Skip((page - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.ToList();
|
||||
|
||||
var items = paged.Select(user => new UserPermissionDto
|
||||
{
|
||||
UserId = user.Id,
|
||||
TenantId = user.TenantId,
|
||||
MerchantId = user.MerchantId,
|
||||
Account = user.Account,
|
||||
DisplayName = user.DisplayName,
|
||||
Roles = user.Roles,
|
||||
Permissions = user.Permissions,
|
||||
CreatedAt = user.CreatedAt
|
||||
}).ToList();
|
||||
|
||||
return new PagedResult<UserPermissionDto>(items, page, pageSize, users.Count);
|
||||
}
|
||||
|
||||
private static CurrentUserProfile BuildProfile(IdentityUser user)
|
||||
=> new()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user