From a4882bf95e8a84283166f0e442e8645f3a9ca08f Mon Sep 17 00:00:00 2001 From: damon Date: Fri, 2 Feb 2024 23:35:16 +0800 Subject: [PATCH] =?UTF-8?q?=E7=AE=A1=E7=90=86=E5=91=98=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Application/ApplicationServiceModule.cs | 3 +- .../Identity/AdministratorAssignCommand.cs | 10 ++ .../Identity/AdministratorDeleteCommand.cs | 13 ++ .../IAdministratorApplicationService.cs | 15 +++ .../Handlers/AdministratorCommandHandler.cs | 49 +++++++ .../AdministratorApplicationService.cs | 46 +++++++ ...ngProfile.cs => IdentityMappingProfile.cs} | 18 ++- .../Mappings/UserMappingProfile.cs | 22 --- .../Domain/Aggregates/Administrator.cs | 2 +- .../Business/AdministratorGeneralBusiness.cs | 125 ++++++++++++++++++ .../AdministratorSpecification.cs | 49 ++++++- .../Admins/AdministratorAssignUseCase.cs | 37 ++++++ .../Admins/AdministratorCountUseCase.cs | 49 +++++++ .../Admins/AdministratorDeleteUseCase.cs | 36 +++++ .../Admins/AdministratorQueryUseCase.cs | 49 +++++++ .../UseCases/GenericQueryInput.cs | 22 +++ .../Identity/GrantWithPasswordUseCase.cs | 2 +- .../Identity/GrantWithRefreshTokenUseCase.cs | 2 +- .../Identity/AdministratorAssignDto.cs | 17 +++ .../Identity/AdministratorCriteria.cs | 15 +++ .../Identity/AdministratorItemDto.cs | 37 ++++++ .../Controllers/AdministratorController.cs | 78 +++++++++++ 22 files changed, 665 insertions(+), 31 deletions(-) create mode 100644 Source/Starfish.Service/Application/Commands/Identity/AdministratorAssignCommand.cs create mode 100644 Source/Starfish.Service/Application/Commands/Identity/AdministratorDeleteCommand.cs create mode 100644 Source/Starfish.Service/Application/Contracts/IAdministratorApplicationService.cs create mode 100644 Source/Starfish.Service/Application/Handlers/AdministratorCommandHandler.cs create mode 100644 Source/Starfish.Service/Application/Implements/AdministratorApplicationService.cs rename Source/Starfish.Service/Application/Mappings/{TeamMappingProfile.cs => IdentityMappingProfile.cs} (56%) delete mode 100644 Source/Starfish.Service/Application/Mappings/UserMappingProfile.cs create mode 100644 Source/Starfish.Service/Domain/Business/AdministratorGeneralBusiness.cs create mode 100644 Source/Starfish.Service/UseCases/Admins/AdministratorAssignUseCase.cs create mode 100644 Source/Starfish.Service/UseCases/Admins/AdministratorCountUseCase.cs create mode 100644 Source/Starfish.Service/UseCases/Admins/AdministratorDeleteUseCase.cs create mode 100644 Source/Starfish.Service/UseCases/Admins/AdministratorQueryUseCase.cs create mode 100644 Source/Starfish.Service/UseCases/GenericQueryInput.cs create mode 100644 Source/Starfish.Transit/Identity/AdministratorAssignDto.cs create mode 100644 Source/Starfish.Transit/Identity/AdministratorCriteria.cs create mode 100644 Source/Starfish.Transit/Identity/AdministratorItemDto.cs create mode 100644 Source/Starfish.Webapi/Controllers/AdministratorController.cs diff --git a/Source/Starfish.Service/Application/ApplicationServiceModule.cs b/Source/Starfish.Service/Application/ApplicationServiceModule.cs index 93d1035..cabe786 100644 --- a/Source/Starfish.Service/Application/ApplicationServiceModule.cs +++ b/Source/Starfish.Service/Application/ApplicationServiceModule.cs @@ -30,11 +30,10 @@ public override void AheadConfigureServices(ServiceConfigurationContext context) { Configure(options => { - options.AddProfile(); + options.AddProfile(); options.AddProfile(); options.AddProfile(); options.AddProfile(); - options.AddProfile(); }); } diff --git a/Source/Starfish.Service/Application/Commands/Identity/AdministratorAssignCommand.cs b/Source/Starfish.Service/Application/Commands/Identity/AdministratorAssignCommand.cs new file mode 100644 index 0000000..07d704a --- /dev/null +++ b/Source/Starfish.Service/Application/Commands/Identity/AdministratorAssignCommand.cs @@ -0,0 +1,10 @@ +using Nerosoft.Euonia.Domain; + +namespace Nerosoft.Starfish.Application; + +public class AdministratorAssignCommand : Command +{ + public string UserId { get; set; } + + public List Roles { get; set; } +} \ No newline at end of file diff --git a/Source/Starfish.Service/Application/Commands/Identity/AdministratorDeleteCommand.cs b/Source/Starfish.Service/Application/Commands/Identity/AdministratorDeleteCommand.cs new file mode 100644 index 0000000..2a30718 --- /dev/null +++ b/Source/Starfish.Service/Application/Commands/Identity/AdministratorDeleteCommand.cs @@ -0,0 +1,13 @@ +using Nerosoft.Euonia.Domain; + +namespace Nerosoft.Starfish.Application; + +public class AdministratorDeleteCommand : Command +{ + public AdministratorDeleteCommand(string userId) + { + UserId = userId; + } + + public string UserId { get; set; } +} \ No newline at end of file diff --git a/Source/Starfish.Service/Application/Contracts/IAdministratorApplicationService.cs b/Source/Starfish.Service/Application/Contracts/IAdministratorApplicationService.cs new file mode 100644 index 0000000..f0fd3df --- /dev/null +++ b/Source/Starfish.Service/Application/Contracts/IAdministratorApplicationService.cs @@ -0,0 +1,15 @@ +using Nerosoft.Euonia.Application; +using Nerosoft.Starfish.Transit; + +namespace Nerosoft.Starfish.Application; + +public interface IAdministratorApplicationService : IApplicationService +{ + Task> QueryAsync(AdministratorCriteria criteria, int skip, int count, CancellationToken cancellationToken = default); + + Task CountAsync(AdministratorCriteria criteria, CancellationToken cancellationToken = default); + + Task AssignAsync(AdministratorAssignDto data, CancellationToken cancellationToken = default); + + Task DeleteAsync(string userId, CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/Source/Starfish.Service/Application/Handlers/AdministratorCommandHandler.cs b/Source/Starfish.Service/Application/Handlers/AdministratorCommandHandler.cs new file mode 100644 index 0000000..b390baa --- /dev/null +++ b/Source/Starfish.Service/Application/Handlers/AdministratorCommandHandler.cs @@ -0,0 +1,49 @@ +using Nerosoft.Euonia.Bus; +using Nerosoft.Euonia.Business; +using Nerosoft.Euonia.Repository; +using Nerosoft.Starfish.Domain; +using Nerosoft.Starfish.Service; + +namespace Nerosoft.Starfish.Application; + +public class AdministratorCommandHandler : CommandHandlerBase, + IHandler, + IHandler +{ + public AdministratorCommandHandler(IUnitOfWorkManager unitOfWork, IObjectFactory factory) + : base(unitOfWork, factory) + { + } + + public Task HandleAsync(AdministratorAssignCommand message, MessageContext context, CancellationToken cancellationToken = default) + { + return ExecuteAsync(async () => + { + var business = await Factory.FetchAsync(message.UserId, cancellationToken); + business.UserId = message.UserId; + business.SetRoles(message.Roles); + if (business.Aggregate == null) + { + business.MarkAsInsert(); + } + else + { + business.MarkAsUpdate(); + } + + _ = await business.SaveAsync(false, cancellationToken); + }); + } + + public Task HandleAsync(AdministratorDeleteCommand message, MessageContext context, CancellationToken cancellationToken = default) + { + return ExecuteAsync(async () => + { + var business = await Factory.FetchAsync(message.UserId, cancellationToken); + + business.MarkAsDelete(); + + _ = await business.SaveAsync(false, cancellationToken); + }); + } +} \ No newline at end of file diff --git a/Source/Starfish.Service/Application/Implements/AdministratorApplicationService.cs b/Source/Starfish.Service/Application/Implements/AdministratorApplicationService.cs new file mode 100644 index 0000000..44afdc4 --- /dev/null +++ b/Source/Starfish.Service/Application/Implements/AdministratorApplicationService.cs @@ -0,0 +1,46 @@ +using Nerosoft.Euonia.Application; +using Nerosoft.Starfish.Transit; +using Nerosoft.Starfish.UseCases; + +namespace Nerosoft.Starfish.Application; + +internal class AdministratorApplicationService : BaseApplicationService, IAdministratorApplicationService +{ + public Task> QueryAsync(AdministratorCriteria criteria, int skip, int count, CancellationToken cancellationToken = default) + { + var input = new GenericQueryInput(criteria, skip, count); + var useCase = LazyServiceProvider.GetRequiredService(); + return useCase.ExecuteAsync(input, cancellationToken).ContinueWith(task => + { + task.WaitAndUnwrapException(cancellationToken); + return task.Result.Result; + }, cancellationToken); + } + + public Task CountAsync(AdministratorCriteria criteria, CancellationToken cancellationToken = default) + { + var input = new AdministratorCountInput(criteria); + var useCase = LazyServiceProvider.GetRequiredService(); + return useCase.ExecuteAsync(input, cancellationToken).ContinueWith(task => + { + task.WaitAndUnwrapException(cancellationToken); + return task.Result.Result; + }, cancellationToken); + } + + public Task AssignAsync(AdministratorAssignDto data, CancellationToken cancellationToken = default) + { + var input = new AdministratorAssignInput(data); + var useCase = LazyServiceProvider.GetRequiredService(); + return useCase.ExecuteAsync(input, cancellationToken) + .ContinueWith(task => task.WaitAndUnwrapException(cancellationToken), cancellationToken); + } + + public Task DeleteAsync(string userId, CancellationToken cancellationToken = default) + { + var input = new AdministratorDeleteInput(userId); + var useCase = LazyServiceProvider.GetRequiredService(); + return useCase.ExecuteAsync(input, cancellationToken) + .ContinueWith(task => task.WaitAndUnwrapException(cancellationToken), cancellationToken); + } +} \ No newline at end of file diff --git a/Source/Starfish.Service/Application/Mappings/TeamMappingProfile.cs b/Source/Starfish.Service/Application/Mappings/IdentityMappingProfile.cs similarity index 56% rename from Source/Starfish.Service/Application/Mappings/TeamMappingProfile.cs rename to Source/Starfish.Service/Application/Mappings/IdentityMappingProfile.cs index 9b5aa8c..3dfcd3b 100644 --- a/Source/Starfish.Service/Application/Mappings/TeamMappingProfile.cs +++ b/Source/Starfish.Service/Application/Mappings/IdentityMappingProfile.cs @@ -4,10 +4,21 @@ namespace Nerosoft.Starfish.Application; -internal class TeamMappingProfile : Profile +/// +/// 用户映射配置 +/// +internal class IdentityMappingProfile : Profile { - public TeamMappingProfile() + /// + /// + /// + public IdentityMappingProfile() { + CreateMap(); + CreateMap(); + CreateMap(); + CreateMap(); + CreateMap(); CreateMap(); @@ -16,5 +27,8 @@ public TeamMappingProfile() .ForMember(dest => dest.NickName, options => options.MapFrom(src => src.User.NickName)) .ForMember(dest => dest.Email, options => options.MapFrom(src => src.User.Email)) .ForMember(dest => dest.Phone, options => options.MapFrom(src => src.User.Phone)); + + CreateMap(); + CreateMap(); } } \ No newline at end of file diff --git a/Source/Starfish.Service/Application/Mappings/UserMappingProfile.cs b/Source/Starfish.Service/Application/Mappings/UserMappingProfile.cs deleted file mode 100644 index 8951d04..0000000 --- a/Source/Starfish.Service/Application/Mappings/UserMappingProfile.cs +++ /dev/null @@ -1,22 +0,0 @@ -using AutoMapper; -using Nerosoft.Starfish.Domain; -using Nerosoft.Starfish.Transit; - -namespace Nerosoft.Starfish.Application; - -/// -/// 用户映射配置 -/// -internal class UserMappingProfile : Profile -{ - /// - /// - /// - public UserMappingProfile() - { - CreateMap(); - CreateMap(); - CreateMap(); - CreateMap(); - } -} \ No newline at end of file diff --git a/Source/Starfish.Service/Domain/Aggregates/Administrator.cs b/Source/Starfish.Service/Domain/Aggregates/Administrator.cs index 99355b8..8bcf181 100644 --- a/Source/Starfish.Service/Domain/Aggregates/Administrator.cs +++ b/Source/Starfish.Service/Domain/Aggregates/Administrator.cs @@ -8,5 +8,5 @@ public class Administrator : Aggregate public User User { get; set; } - public string Scopes { get; set; } + public string Roles { get; set; } } \ No newline at end of file diff --git a/Source/Starfish.Service/Domain/Business/AdministratorGeneralBusiness.cs b/Source/Starfish.Service/Domain/Business/AdministratorGeneralBusiness.cs new file mode 100644 index 0000000..8b8909f --- /dev/null +++ b/Source/Starfish.Service/Domain/Business/AdministratorGeneralBusiness.cs @@ -0,0 +1,125 @@ +using Microsoft.EntityFrameworkCore; +using Nerosoft.Euonia.Business; +using Nerosoft.Starfish.Service; + +// ReSharper disable MemberCanBePrivate.Global + +namespace Nerosoft.Starfish.Domain; + +internal sealed class AdministratorGeneralBusiness : EditableObjectBase +{ + [Inject] + public IUserRepository UserRepository { get; set; } + + [Inject] + public IAdministratorRepository AdministratorRepository { get; set; } + + public Administrator Aggregate { get; private set; } + + public static readonly PropertyInfo UserIdProperty = RegisterProperty(p => p.UserId); + + public string UserId + { + get => GetProperty(UserIdProperty); + set => SetProperty(UserIdProperty, value); + } + + public static readonly PropertyInfo> RolesProperty = RegisterProperty>(p => p.Roles, []); + + public HashSet Roles + { + get => GetProperty(RolesProperty); + set => SetProperty(RolesProperty, value); + } + + protected override void AddRules() + { + Rules.AddRule(new UserCheckRule()); + } + + public void SetRoles(IEnumerable roles) + { + if (Roles.SetEquals(roles)) + { + return; + } + + Roles.Clear(); + if (roles.Any()) + { + foreach (var role in roles) + { + Roles.Add(role); + } + } + + ChangedProperties.Add(RolesProperty); + } + + [FactoryFetch] + internal async Task FetchAsync(string userId, CancellationToken cancellationToken = default) + { + var aggregate = await AdministratorRepository.GetAsync(t => t.UserId == userId, query => query.AsTracking(), cancellationToken); + + if (aggregate != null) + { + using (BypassRuleChecks) + { + UserId = aggregate.UserId; + Roles = [..aggregate.Roles.Split(",")]; + } + + Aggregate = aggregate; + } + } + + [FactoryInsert] + protected override async Task InsertAsync(CancellationToken cancellationToken = default) + { + Aggregate = new Administrator + { + UserId = UserId, + Roles = Roles.JoinAsString(",") + }; + + await AdministratorRepository.InsertAsync(Aggregate, true, cancellationToken); + } + + [FactoryUpdate] + protected override async Task UpdateAsync(CancellationToken cancellationToken = default) + { + if (ChangedProperties.Contains(RolesProperty)) + { + Aggregate.Roles = Roles.JoinAsString(","); + } + + await AdministratorRepository.UpdateAsync(Aggregate, true, cancellationToken); + } + + [FactoryDelete] + protected override async Task DeleteAsync(CancellationToken cancellationToken = default) + { + if (Aggregate == null) + { + throw new NotFoundException(); + } + + await AdministratorRepository.DeleteAsync(Aggregate, true, cancellationToken); + } + + public class UserCheckRule : RuleBase + { + public override async Task ExecuteAsync(IRuleContext context, CancellationToken cancellationToken = default) + { + var target = (AdministratorGeneralBusiness)context.Target; + if (target.ChangedProperties.Contains(UserIdProperty)) + { + var exists = await target.UserRepository.AnyAsync(t => t.Id == target.UserId, query => query.AsNoTracking(), cancellationToken); + if (exists) + { + context.AddErrorResult(string.Format(Resources.IDS_ERROR_USER_NOT_EXISTS, target.UserId)); + } + } + } + } +} \ No newline at end of file diff --git a/Source/Starfish.Service/Repository/Specifications/AdministratorSpecification.cs b/Source/Starfish.Service/Repository/Specifications/AdministratorSpecification.cs index d6388c2..43e49ae 100644 --- a/Source/Starfish.Service/Repository/Specifications/AdministratorSpecification.cs +++ b/Source/Starfish.Service/Repository/Specifications/AdministratorSpecification.cs @@ -1,6 +1,51 @@ -namespace Nerosoft.Starfish.Repository; +using Nerosoft.Euonia.Linq; +using Nerosoft.Starfish.Domain; + +namespace Nerosoft.Starfish.Repository; internal static class AdministratorSpecification { - + public static Specification UserNameContains(string userName) + { + userName = userName.Normalize(TextCaseType.Lower); + return new DirectSpecification(t => t.User.UserName.Contains(userName)); + } + + public static Specification NickNameContains(string nickName) + { + nickName = nickName.Normalize(TextCaseType.Lower); + return new DirectSpecification(t => t.User.NickName.ToLower().Contains(nickName)); + } + + public static Specification EmailContains(string email) + { + email = email.Normalize(TextCaseType.Lower); + return new DirectSpecification(t => t.User.Email.Contains(email)); + } + + public static Specification PhoneContains(string email) + { + email = email.Normalize(TextCaseType.Lower); + return new DirectSpecification(t => t.User.Phone.Contains(email)); + } + + public static Specification Matches(string keyword) + { + if (string.IsNullOrEmpty(keyword)) + { + return new TrueSpecification(); + } + + keyword = keyword.Normalize(TextCaseType.Lower); + + ISpecification[] specifications = + [ + UserNameContains(keyword), + NickNameContains(keyword), + EmailContains(keyword), + PhoneContains(keyword) + ]; + + return new CompositeSpecification(PredicateOperator.OrElse, specifications); + } } \ No newline at end of file diff --git a/Source/Starfish.Service/UseCases/Admins/AdministratorAssignUseCase.cs b/Source/Starfish.Service/UseCases/Admins/AdministratorAssignUseCase.cs new file mode 100644 index 0000000..ed6efc1 --- /dev/null +++ b/Source/Starfish.Service/UseCases/Admins/AdministratorAssignUseCase.cs @@ -0,0 +1,37 @@ +using Nerosoft.Euonia.Application; +using Nerosoft.Euonia.Bus; +using Nerosoft.Euonia.Claims; +using Nerosoft.Euonia.Mapping; +using Nerosoft.Starfish.Application; +using Nerosoft.Starfish.Transit; + +namespace Nerosoft.Starfish.UseCases; + +internal interface IAdministratorAssignUseCase : INonOutputUseCase; + +internal record AdministratorAssignInput(AdministratorAssignDto Data); + +internal class AdministratorAssignUseCase : IAdministratorAssignUseCase +{ + private readonly IBus _bus; + private readonly UserPrincipal _user; + + public AdministratorAssignUseCase(IBus bus, UserPrincipal user) + { + _bus = bus; + _user = user; + } + + public Task ExecuteAsync(AdministratorAssignInput input, CancellationToken cancellationToken = default) + { + _user.EnsureInRoles(["SA"]); + + if (string.Equals(_user.UserId, input.Data.UserId)) + { + throw new InvalidOperationException(); + } + + var command = TypeAdapter.ProjectedAs(input.Data); + return _bus.SendAsync(command, cancellationToken); + } +} \ No newline at end of file diff --git a/Source/Starfish.Service/UseCases/Admins/AdministratorCountUseCase.cs b/Source/Starfish.Service/UseCases/Admins/AdministratorCountUseCase.cs new file mode 100644 index 0000000..4cb8b2c --- /dev/null +++ b/Source/Starfish.Service/UseCases/Admins/AdministratorCountUseCase.cs @@ -0,0 +1,49 @@ +using System.Security.Authentication; +using Microsoft.EntityFrameworkCore; +using Nerosoft.Euonia.Application; +using Nerosoft.Euonia.Claims; +using Nerosoft.Starfish.Domain; +using Nerosoft.Starfish.Repository; +using Nerosoft.Starfish.Transit; + +namespace Nerosoft.Starfish.UseCases; + +internal interface IAdministratorCountUseCase : IUseCase; + +internal record AdministratorCountInput(AdministratorCriteria Criteria); + +internal record AdministratorCountOutput(int Result); + +internal class AdministratorCountUseCase : IAdministratorCountUseCase +{ + private readonly IAdministratorRepository _repository; + private readonly UserPrincipal _user; + + public AdministratorCountUseCase(IAdministratorRepository repository, UserPrincipal user) + { + _repository = repository; + _user = user; + } + + public Task ExecuteAsync(AdministratorCountInput input, CancellationToken cancellationToken = default) + { + if (_user.IsAuthenticated) + { + throw new AuthenticationException(Resources.IDS_MESSAGE_LOGS_AUTH_FAILED); + } + + if (!_user.IsInRoles(["SA"])) + { + throw new UnauthorizedAccessException(Resources.IDS_ERROR_COMMON_UNAUTHORIZED_ACCESS); + } + + var predicate = AdministratorSpecification.Matches(input.Criteria.Keyword).Satisfy(); + + return _repository.CountAsync(predicate, query => query.AsNoTracking().Include(t => t.User), cancellationToken) + .ContinueWith(task => + { + task.WaitAndUnwrapException(cancellationToken); + return new AdministratorCountOutput(task.Result); + }, cancellationToken); + } +} \ No newline at end of file diff --git a/Source/Starfish.Service/UseCases/Admins/AdministratorDeleteUseCase.cs b/Source/Starfish.Service/UseCases/Admins/AdministratorDeleteUseCase.cs new file mode 100644 index 0000000..cdf5930 --- /dev/null +++ b/Source/Starfish.Service/UseCases/Admins/AdministratorDeleteUseCase.cs @@ -0,0 +1,36 @@ +using Nerosoft.Euonia.Application; +using Nerosoft.Euonia.Bus; +using Nerosoft.Euonia.Claims; +using Nerosoft.Starfish.Application; + +namespace Nerosoft.Starfish.UseCases; + +internal interface IAdministratorDeleteUseCase : INonOutputUseCase; + +internal record AdministratorDeleteInput(string UserId) : IUseCaseInput; + +internal class AdministratorDeleteUseCase : IAdministratorDeleteUseCase +{ + private readonly IBus _bus; + private readonly UserPrincipal _user; + + public AdministratorDeleteUseCase(IBus bus, UserPrincipal user) + { + _bus = bus; + _user = user; + } + + public Task ExecuteAsync(AdministratorDeleteInput input, CancellationToken cancellationToken = new CancellationToken()) + { + _user.EnsureInRoles(["SA"]); + + if (string.Equals(_user.UserId, input.UserId)) + { + throw new InvalidOperationException(); + } + + var command = new AdministratorDeleteCommand(input.UserId); + + return _bus.SendAsync(command, cancellationToken); + } +} \ No newline at end of file diff --git a/Source/Starfish.Service/UseCases/Admins/AdministratorQueryUseCase.cs b/Source/Starfish.Service/UseCases/Admins/AdministratorQueryUseCase.cs new file mode 100644 index 0000000..4167d73 --- /dev/null +++ b/Source/Starfish.Service/UseCases/Admins/AdministratorQueryUseCase.cs @@ -0,0 +1,49 @@ +using System.Security.Authentication; +using Microsoft.EntityFrameworkCore; +using Nerosoft.Euonia.Application; +using Nerosoft.Euonia.Claims; +using Nerosoft.Euonia.Mapping; +using Nerosoft.Starfish.Domain; +using Nerosoft.Starfish.Repository; +using Nerosoft.Starfish.Transit; + +namespace Nerosoft.Starfish.UseCases; + +internal interface IAdministratorQueryUseCase : IUseCase, AdministratorQueryOutput>; + +internal record AdministratorQueryOutput(List Result); + +internal class AdministratorQueryUseCase : IAdministratorQueryUseCase +{ + private readonly IAdministratorRepository _repository; + private readonly UserPrincipal _user; + + public AdministratorQueryUseCase(IAdministratorRepository repository, UserPrincipal user) + { + _repository = repository; + _user = user; + } + + public Task ExecuteAsync(GenericQueryInput input, CancellationToken cancellationToken = default) + { + if (_user.IsAuthenticated) + { + throw new AuthenticationException(Resources.IDS_MESSAGE_LOGS_AUTH_FAILED); + } + + if (!_user.IsInRoles(["SA"])) + { + throw new UnauthorizedAccessException(Resources.IDS_ERROR_COMMON_UNAUTHORIZED_ACCESS); + } + + var predicate = AdministratorSpecification.Matches(input.Criteria.Keyword).Satisfy(); + + return _repository.FindAsync(predicate, query => query.AsNoTracking().Include(t => t.User), input.Skip, input.Count, cancellationToken) + .ContinueWith(task => + { + task.WaitAndUnwrapException(cancellationToken); + var result = TypeAdapter.ProjectedAs>(task.Result); + return new AdministratorQueryOutput(result); + }, cancellationToken); + } +} \ No newline at end of file diff --git a/Source/Starfish.Service/UseCases/GenericQueryInput.cs b/Source/Starfish.Service/UseCases/GenericQueryInput.cs new file mode 100644 index 0000000..a164368 --- /dev/null +++ b/Source/Starfish.Service/UseCases/GenericQueryInput.cs @@ -0,0 +1,22 @@ +using Nerosoft.Euonia.Application; + +namespace Nerosoft.Starfish.UseCases; + +public class GenericQueryInput : IUseCaseInput +{ + public GenericQueryInput(TCriteria criteria, int skip, int count) + { + ArgumentOutOfRangeException.ThrowIfNegative(skip); + ArgumentOutOfRangeException.ThrowIfNegativeOrZero(count); + + Criteria = criteria; + Skip = skip; + Count = count; + } + + public TCriteria Criteria { get; } + + public int Skip { get; } + + public int Count { get; } +} \ No newline at end of file diff --git a/Source/Starfish.Service/UseCases/Identity/GrantWithPasswordUseCase.cs b/Source/Starfish.Service/UseCases/Identity/GrantWithPasswordUseCase.cs index 65492de..fbb4432 100644 --- a/Source/Starfish.Service/UseCases/Identity/GrantWithPasswordUseCase.cs +++ b/Source/Starfish.Service/UseCases/Identity/GrantWithPasswordUseCase.cs @@ -93,7 +93,7 @@ public async Task ExecuteAsync(GrantWithPassword } var administrator = await AdminRepository.GetByUserIdAsync(user.Id, cancellationToken); - var roles = administrator?.Scopes?.Split(","); + var roles = administrator?.Roles?.Split(","); var (accessToken, refreshToken, issuesAt, expiresAt) = Component.GenerateAccessToken(user.Id, user.UserName, roles); @events.Add(new UserAuthSucceedEvent diff --git a/Source/Starfish.Service/UseCases/Identity/GrantWithRefreshTokenUseCase.cs b/Source/Starfish.Service/UseCases/Identity/GrantWithRefreshTokenUseCase.cs index dc57398..9778335 100644 --- a/Source/Starfish.Service/UseCases/Identity/GrantWithRefreshTokenUseCase.cs +++ b/Source/Starfish.Service/UseCases/Identity/GrantWithRefreshTokenUseCase.cs @@ -98,7 +98,7 @@ public async Task ExecuteAsync(GrantWithRefr } var administrator = await AdminRepository.GetByUserIdAsync(user.Id, cancellationToken); - var roles = administrator?.Scopes?.Split(","); + var roles = administrator?.Roles?.Split(","); var (accessToken, refreshToken, issuesAt, expiresAt) = Component.GenerateAccessToken(user.Id, user.UserName, roles); @events.Add(new UserAuthSucceedEvent diff --git a/Source/Starfish.Transit/Identity/AdministratorAssignDto.cs b/Source/Starfish.Transit/Identity/AdministratorAssignDto.cs new file mode 100644 index 0000000..c8ef616 --- /dev/null +++ b/Source/Starfish.Transit/Identity/AdministratorAssignDto.cs @@ -0,0 +1,17 @@ +namespace Nerosoft.Starfish.Transit; + +/// +/// 管理员编辑数据传输对象 +/// +public class AdministratorAssignDto +{ + /// + /// 用户Id + /// + public string UserId { get; set; } + + /// + /// 角色 + /// + public List Roles { get; set; } +} diff --git a/Source/Starfish.Transit/Identity/AdministratorCriteria.cs b/Source/Starfish.Transit/Identity/AdministratorCriteria.cs new file mode 100644 index 0000000..917e576 --- /dev/null +++ b/Source/Starfish.Transit/Identity/AdministratorCriteria.cs @@ -0,0 +1,15 @@ +namespace Nerosoft.Starfish.Transit; + +/// +/// 管理员搜索条件 +/// +public class AdministratorCriteria +{ + /// + /// 关键字 + /// + /// + /// 搜索用户名、昵称、邮箱、电话号码 + /// + public string Keyword { get; set; } +} \ No newline at end of file diff --git a/Source/Starfish.Transit/Identity/AdministratorItemDto.cs b/Source/Starfish.Transit/Identity/AdministratorItemDto.cs new file mode 100644 index 0000000..b7b52f1 --- /dev/null +++ b/Source/Starfish.Transit/Identity/AdministratorItemDto.cs @@ -0,0 +1,37 @@ +namespace Nerosoft.Starfish.Transit; + +/// +/// 管理员列表项数据传输对象 +/// +public sealed class AdministratorItemDto +{ + /// + /// 用户Id + /// + public string UserId { get; set; } + + /// + /// 用户名 + /// + public string UserName { get; set; } + + /// + /// 昵称 + /// + public string NickName { get; set; } + + /// + /// 邮箱 + /// + public string Email { get; set; } + + /// + /// 电话号码 + /// + public string Phone { get; set; } + + /// + /// 角色 + /// + public List Roles { get; set; } +} \ No newline at end of file diff --git a/Source/Starfish.Webapi/Controllers/AdministratorController.cs b/Source/Starfish.Webapi/Controllers/AdministratorController.cs new file mode 100644 index 0000000..d7aa7a9 --- /dev/null +++ b/Source/Starfish.Webapi/Controllers/AdministratorController.cs @@ -0,0 +1,78 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Nerosoft.Starfish.Application; +using Nerosoft.Starfish.Transit; + +namespace Nerosoft.Starfish.Webapi.Controllers; + +/// +/// 管理员管理Controller +/// +[Route("api/[controller]")] +[ApiController, ApiExplorerSettings(GroupName = "identity")] +[Authorize] +public class AdministratorController : ControllerBase +{ + private readonly IAdministratorApplicationService _service; + + /// + /// 构造函数 + /// + /// + public AdministratorController(IAdministratorApplicationService service) + { + _service = service; + } + + /// + /// 查询管理员列表 + /// + /// + /// + /// + /// + [HttpGet] + [Produces(typeof(List))] + public async Task QueryAsync([FromQuery] AdministratorCriteria criteria, int skip = Constants.Query.Skip, int count = Constants.Query.Count) + { + var result = await _service.QueryAsync(criteria, skip, count, HttpContext.RequestAborted); + return Ok(result); + } + + /// + /// 查询管理员数量 + /// + /// + /// + [HttpGet("count")] + [Produces(typeof(int))] + public async Task CountAsync([FromQuery] AdministratorCriteria criteria) + { + var result = await _service.CountAsync(criteria, HttpContext.RequestAborted); + return Ok(result); + } + + /// + /// 创建管理员 + /// + /// + /// + [HttpPost] + public async Task AssignAsync([FromBody] AdministratorAssignDto data) + { + await _service.AssignAsync(data, HttpContext.RequestAborted); + return Ok(); + } + + /// + /// 删除管理员 + /// + /// + /// + [HttpDelete("{userId}")] + public async Task DeleteAsync(string userId) + { + await _service.DeleteAsync(userId, HttpContext.RequestAborted); + return Ok(); + } +} \ No newline at end of file