diff --git a/Directory.Packages.props b/Directory.Packages.props index 5df0b8e..0bf3d86 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -2,13 +2,14 @@ true true - 8.1.23 + 8.1.24 + 4.7.2 - + @@ -29,38 +30,37 @@ - - - - - - - - - + + + + + + + + + - + - - - - - + + + + - + - + - - - - + + + + - + @@ -72,7 +72,7 @@ - + @@ -108,7 +108,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Documents/SCRIPTS_MSSQL.md b/Documents/SCRIPTS_MSSQL.md index 4ab7373..25c5e26 100644 --- a/Documents/SCRIPTS_MSSQL.md +++ b/Documents/SCRIPTS_MSSQL.md @@ -5,7 +5,7 @@ CREATE TABLE [operate_log] ( [Id] bigint NOT NULL, [Module] varchar(20) NOT NULL, [Type] varchar(50) NOT NULL, - [Description] varchar(2000) NULL, + [Content] varchar(2000) NULL, [UserName] varchar(64) NULL, [OperateTime] datetime DEFAULT getdate() NOT NULL, [Error] varchar(2000) NULL, diff --git a/Documents/SCRIPTS_MYSQL.md b/Documents/SCRIPTS_MYSQL.md index 15290ec..9a702ab 100644 --- a/Documents/SCRIPTS_MYSQL.md +++ b/Documents/SCRIPTS_MYSQL.md @@ -5,7 +5,7 @@ CREATE TABLE `operate_log` ( `Id` bigint NOT NULL, `Module` varchar(20) NOT NULL, `Type` varchar(50) NOT NULL, - `Description` varchar(2000) NULL DEFAULT NULL, + `Content` varchar(2000) NULL DEFAULT NULL, `UserName` varchar(64) NULL DEFAULT NULL, `OperateTime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `Error` varchar(2000) NULL DEFAULT NULL, diff --git a/Documents/SCRIPTS_PGSQL.md b/Documents/SCRIPTS_PGSQL.md index c16ba7a..b0aa356 100644 --- a/Documents/SCRIPTS_PGSQL.md +++ b/Documents/SCRIPTS_PGSQL.md @@ -5,7 +5,7 @@ CREATE TABLE "public"."operate_log" ( "Id" int8 NOT NULL, "Module" varchar(20) COLLATE "pg_catalog"."default" NOT NULL, "Type" varchar(50) COLLATE "pg_catalog"."default" NOT NULL, - "Description" varchar(2000) COLLATE "pg_catalog"."default", + "Content" varchar(2000) COLLATE "pg_catalog"."default", "UserName" varchar(255) COLLATE "pg_catalog"."default", "OperateTime" timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, "Error" varchar(2000) COLLATE "pg_catalog"."default", @@ -86,10 +86,10 @@ CREATE TABLE "public"."configuration_item" ( ; CREATE INDEX "IDX_CONFIG_ITEM_FK" ON "public"."configuration_item" USING btree ( - "ConfigurationId" "pg_catalog"."int8_ops" ASC NULLS LAST + "ConfigurationId" COLLATE "pg_catalog"."default" "pg_catalog"."text_ops" ASC NULLS LAST ); CREATE UNIQUE INDEX "IDX_CONFIG_ITEM_UNIQUE" ON "public"."configuration_item" USING btree ( - "ConfigurationId" "pg_catalog"."int8_ops" ASC NULLS LAST, + "ConfigurationId" COLLATE "pg_catalog"."default" "pg_catalog"."text_ops" ASC NULLS LAST, "Key" COLLATE "pg_catalog"."default" "pg_catalog"."text_ops" ASC NULLS LAST ); ``` @@ -110,7 +110,7 @@ CREATE TABLE "public"."configuration_revision" ( ; CREATE INDEX "IDS_CONFIG_REVISION_FK" ON "public"."configuration_revision" USING btree ( - "ConfigurationId" "pg_catalog"."int8_ops" ASC NULLS LAST + "ConfigurationId" COLLATE "pg_catalog"."default" "pg_catalog"."text_ops" ASC NULLS LAST ); ``` diff --git a/Documents/SCRIPTS_SQLITE.md b/Documents/SCRIPTS_SQLITE.md index 330aeef..f68f195 100644 --- a/Documents/SCRIPTS_SQLITE.md +++ b/Documents/SCRIPTS_SQLITE.md @@ -5,7 +5,7 @@ CREATE TABLE "operate_log" ( "Id" integer NOT NULL, "Module" text NOT NULL, "Type" text NOT NULL, - "Description" text, + "Content" text, "UserName" text, "OperateTime" text NOT NULL, "Error" text, diff --git a/README.md b/README.md index a683d3c..e87cb5c 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ # Overview/概览 ⚡ -[![Build Status](https://img.shields.io/github/actions/workflow/status/NerosoftDev/Starfish/dotnet.yml)]() +[![Build Status](https://img.shields.io/github/actions/workflow/status/NerosoftDev/Starfish/dotnet.yml)](https://github.com/NerosoftDev/Starfish/actions) [![License](https://img.shields.io/badge/license-AGPL--3.0-blue.svg)](LICENSE) -[![GitHub release](https://img.shields.io/github/release/NerosoftDev/Starfish.svg)]() -[![GitHub stars](https://img.shields.io/github/stars/NerosoftDev/Starfish.svg)]() +[![GitHub release](https://img.shields.io/github/release/NerosoftDev/Starfish.svg)](https://github.com/NerosoftDev/Starfish/releases) +[![GitHub stars](https://img.shields.io/github/stars/NerosoftDev/Starfish.svg)](https://github.com/NerosoftDev/Starfish/stargazers) Starfish is a lightweight powerful distributed configuration server for .NET application. diff --git a/Source/Starfish.Common/Constants.cs b/Source/Starfish.Common/Constants.cs index 4d7fecf..41cdbc6 100644 --- a/Source/Starfish.Common/Constants.cs +++ b/Source/Starfish.Common/Constants.cs @@ -37,5 +37,6 @@ public static class Configuration public static class RegexPattern { public const string Secret = @"^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,32}$"; + public const string Password = @"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[\x00-\xff]{8,32}$"; } } \ No newline at end of file diff --git a/Source/Starfish.Service/Application/Commands/Identity/ChangePasswordCommand.cs b/Source/Starfish.Service/Application/Commands/Identity/ChangePasswordCommand.cs index d21d04d..2333a0e 100644 --- a/Source/Starfish.Service/Application/Commands/Identity/ChangePasswordCommand.cs +++ b/Source/Starfish.Service/Application/Commands/Identity/ChangePasswordCommand.cs @@ -11,11 +11,12 @@ public ChangePasswordCommand() { } - public ChangePasswordCommand(string userId, string password) + public ChangePasswordCommand(string userId, string password, string actionType) : this() { UserId = userId; Password = password; + ActionType = actionType; } /// @@ -27,4 +28,10 @@ public ChangePasswordCommand(string userId, string password) /// 新密码 /// public string Password { get; set; } + + /// + /// 操作方式 + /// + /// change-修改;reset-重置 + public string ActionType { get; set; } } \ No newline at end of file diff --git a/Source/Starfish.Service/Application/Handlers/UserCommandHandler.cs b/Source/Starfish.Service/Application/Handlers/UserCommandHandler.cs index 9a35d2d..c28aceb 100644 --- a/Source/Starfish.Service/Application/Handlers/UserCommandHandler.cs +++ b/Source/Starfish.Service/Application/Handlers/UserCommandHandler.cs @@ -10,10 +10,10 @@ namespace Nerosoft.Starfish.Application; /// 用户命令处理器 /// public sealed class UserCommandHandler : CommandHandlerBase, - IHandler, - IHandler, - IHandler, - IHandler + IHandler, + IHandler, + IHandler, + IHandler { /// /// 初始化. @@ -58,7 +58,7 @@ public Task HandleAsync(UserUpdateCommand message, MessageContext context, Cance business.IsAdmin = message.Item2.IsAdmin; business.MarkAsUpdate(); - + await business.SaveAsync(true, cancellationToken); }); } @@ -68,11 +68,7 @@ public Task HandleAsync(ChangePasswordCommand message, MessageContext context, C { return ExecuteAsync(async () => { - var business = await Factory.FetchAsync(message.UserId, cancellationToken); - - business.Password = message.Password; - business.MarkAsUpdate(); - await business.SaveAsync(true, cancellationToken); + _ = await Factory.ExecuteAsync(message.UserId, message.Password, message.ActionType, cancellationToken); }); } diff --git a/Source/Starfish.Service/Application/Mappings/LogsMappingProfile.cs b/Source/Starfish.Service/Application/Mappings/LogsMappingProfile.cs index 1dc330f..167682c 100644 --- a/Source/Starfish.Service/Application/Mappings/LogsMappingProfile.cs +++ b/Source/Starfish.Service/Application/Mappings/LogsMappingProfile.cs @@ -15,17 +15,20 @@ internal class LogsMappingProfile : Profile public LogsMappingProfile() { CreateMap() - .ForMember(dest => dest.Type, opt => opt.MapFrom(src => GetTypeName(src.Type))); + .ForMember(dest => dest.Description, opt => opt.MapFrom(GetDescription)); } - private static string GetTypeName(string type) + private static string GetDescription(OperateLog source, OperateLogDto destination, object obj, ResolutionContext context) { - return type.ToLowerInvariant() switch + var key = $"IDS_LOG_MESSAGE_{source.Module}_{source.Type}".Normalize(TextCaseType.Upper).Replace(".", "_"); + + var value = Resources.ResourceManager.GetString(key); + + if (string.IsNullOrEmpty(value)) { - "auth.password" => "密码登录", - "auth.refresh_token" => "刷新Token", - "auth.otp" => "验证码登录", - _ => type - }; + return value; + } + + return string.Format(value, source.Content); } } \ No newline at end of file diff --git a/Source/Starfish.Service/Application/Subscribers/LoggingEventSubscriber.Auth.cs b/Source/Starfish.Service/Application/Subscribers/LoggingEventSubscriber.Auth.cs new file mode 100644 index 0000000..b556022 --- /dev/null +++ b/Source/Starfish.Service/Application/Subscribers/LoggingEventSubscriber.Auth.cs @@ -0,0 +1,54 @@ +using Nerosoft.Euonia.Bus; +using Nerosoft.Starfish.Domain; + +namespace Nerosoft.Starfish.Application; + +internal partial class LoggingEventSubscriber +{ + private const string MODULE_AUTH = "auth"; + + /// + /// 处理用户认证成功事件 + /// + /// + /// + /// + /// + [Subscribe] + public Task HandleAsync(UserAuthSucceedEvent @event, MessageContext context, CancellationToken cancellationToken = default) + { + var command = new OperateLogCreateCommand + { + Module = MODULE_AUTH, + Type = @event.AuthType, + UserName = @event.UserName, + OperateTime = DateTime.Now, + Description = Resources.IDS_MESSAGE_LOGS_AUTH_SUCCEED, + RequestTraceId = context.RequestTraceId + }; + return _bus.SendAsync(command, new SendOptions { RequestTraceId = context.RequestTraceId }, null, cancellationToken); + } + + /// + /// 处理用户认证失败事件 + /// + /// + /// + /// + /// + [Subscribe] + public Task HandleAsync(UserAuthFailedEvent @event, MessageContext context, CancellationToken cancellationToken = default) + { + var command = new OperateLogCreateCommand + { + Module = MODULE_AUTH, + Type = @event.AuthType, + Description = Resources.IDS_MESSAGE_LOGS_AUTH_FAILED, + OperateTime = DateTime.Now, + RequestTraceId = context.RequestTraceId, + Error = @event.Error + }; + + return _bus.SendAsync(command, new SendOptions { RequestTraceId = context.RequestTraceId }, null, cancellationToken); + } +} diff --git a/Source/Starfish.Service/Application/Subscribers/LoggingEventSubscriber.Config.cs b/Source/Starfish.Service/Application/Subscribers/LoggingEventSubscriber.Config.cs new file mode 100644 index 0000000..d309122 --- /dev/null +++ b/Source/Starfish.Service/Application/Subscribers/LoggingEventSubscriber.Config.cs @@ -0,0 +1,179 @@ +using Nerosoft.Euonia.Bus; +using Nerosoft.Starfish.Domain; + +namespace Nerosoft.Starfish.Application; + +internal partial class LoggingEventSubscriber +{ + private const string MODULE_CONFIG = "config"; + + /// + /// 处理配置启用事件 + /// + /// + /// + /// + /// + [Subscribe] + public Task HandleAsync(ConfigurationEnabledEvent @event, MessageContext context, CancellationToken cancellationToken = default) + { + var aggregate = @event.GetAggregate(); + var command = new OperateLogCreateCommand + { + Module = MODULE_CONFIG, + Type = "status.enable", + Description = string.Format(Resources.IDS_MESSAGE_LOGS_CONFIG_ENABLE, aggregate.Id, aggregate.Name), + OperateTime = DateTime.Now, + RequestTraceId = context.RequestTraceId, + UserName = context.User?.Identity?.Name + }; + + return _bus.SendAsync(command, new SendOptions { RequestTraceId = context.RequestTraceId }, null, cancellationToken); + } + + /// + /// 处理配置禁用事件 + /// + /// + /// + /// + /// + [Subscribe] + public Task HandleAsync(ConfigurationDisableEvent @event, MessageContext context, CancellationToken cancellationToken = default) + { + var aggregate = @event.GetAggregate(); + var command = new OperateLogCreateCommand + { + Module = MODULE_CONFIG, + Type = "status.disable", + Description = string.Format(Resources.IDS_MESSAGE_LOGS_CONFIG_DISABLE, aggregate.Id, aggregate.Name), + OperateTime = DateTime.Now, + RequestTraceId = context.RequestTraceId, + UserName = context.User?.Identity?.Name + }; + + return _bus.SendAsync(command, new SendOptions { RequestTraceId = context.RequestTraceId }, null, cancellationToken); + } + + /// + /// 处理配置密钥重置事件 + /// + /// + /// + /// + /// + [Subscribe] + public Task HandleAsync(ConfigurationSecretChangedEvent @event, MessageContext context, CancellationToken cancellationToken = default) + { + var aggregate = @event.GetAggregate(); + var command = new OperateLogCreateCommand + { + Module = MODULE_CONFIG, + Type = "secret", + Description = string.Format(Resources.IDS_MESSAGE_LOGS_CONFIG_RESET_SECRET, aggregate.Id, aggregate.Name), + OperateTime = DateTime.Now, + RequestTraceId = context.RequestTraceId, + UserName = context.User?.Identity?.Name + }; + + return _bus.SendAsync(command, new SendOptions { RequestTraceId = context.RequestTraceId }, null, cancellationToken); + } + + /// + /// 处理配置更新事件 + /// + /// + /// + /// + /// + [Subscribe] + public Task HandleAsync(ConfigurationUpdatedEvent @event, MessageContext context, CancellationToken cancellationToken = default) + { + var aggregate = @event.GetAggregate(); + var command = new OperateLogCreateCommand + { + Module = MODULE_CONFIG, + Type = "update", + Description = string.Format(Resources.IDS_MESSAGE_LOGS_CONFIG_UPDATE, aggregate.Id, aggregate.Name), + OperateTime = DateTime.Now, + RequestTraceId = context.RequestTraceId, + UserName = context.User?.Identity?.Name + }; + + return _bus.SendAsync(command, new SendOptions { RequestTraceId = context.RequestTraceId }, null, cancellationToken); + } + + /// + /// 处理配置节点创建事件 + /// + /// + /// + /// + /// + [Subscribe] + public Task HandleAsync(ConfigurationCreatedEvent @event, MessageContext context, CancellationToken cancellationToken = default) + { + var description = string.Format(Resources.IDS_MESSAGE_LOGS_CONFIG_CREATE, @event.Configuration.Id, @event.Configuration.Name); + + var command = new OperateLogCreateCommand + { + Module = MODULE_CONFIG, + Type = "create", + Description = description, + OperateTime = DateTime.Now, + RequestTraceId = context.RequestTraceId, + UserName = context.User?.Identity?.Name + }; + return _bus.SendAsync(command, new SendOptions { RequestTraceId = context.RequestTraceId }, null, cancellationToken); + } + + /// + /// 处理配置节点删除事件 + /// + /// + /// + /// + /// + [Subscribe] + public Task HandleAsync(ConfigurationDeletedEvent @event, MessageContext context, CancellationToken cancellationToken = default) + { + var aggregate = @event.GetAggregate(); + var description = string.Format(Resources.IDS_MESSAGE_LOGS_CONFIG_DELETE, aggregate.Id, aggregate.Name); + + var command = new OperateLogCreateCommand + { + Module = MODULE_CONFIG, + Type = "delete", + Description = description, + OperateTime = DateTime.Now, + RequestTraceId = context.RequestTraceId, + UserName = context.User?.Identity?.Name + }; + return _bus.SendAsync(command, new SendOptions { RequestTraceId = context.RequestTraceId }, null, cancellationToken); + } + + /// + /// 处理配置发布事件 + /// + /// + /// + /// + /// + [Subscribe] + public Task HandleAsync(ConfigurationPublishedEvent @event, MessageContext context, CancellationToken cancellationToken = default) + { + var aggregate = @event.GetAggregate(); + var description = string.Format(Resources.IDS_MESSAGE_LOGS_CONFIG_PUBLISH, aggregate.Id, aggregate.Name); + + var command = new OperateLogCreateCommand + { + Module = MODULE_CONFIG, + Type = "publish", + Description = description, + OperateTime = DateTime.Now, + RequestTraceId = context.RequestTraceId, + UserName = context.User?.Identity?.Name + }; + return _bus.SendAsync(command, new SendOptions { RequestTraceId = context.RequestTraceId }, null, cancellationToken); + } +} diff --git a/Source/Starfish.Service/Application/Subscribers/LoggingEventSubscriber.Team.cs b/Source/Starfish.Service/Application/Subscribers/LoggingEventSubscriber.Team.cs new file mode 100644 index 0000000..fb45ab2 --- /dev/null +++ b/Source/Starfish.Service/Application/Subscribers/LoggingEventSubscriber.Team.cs @@ -0,0 +1,54 @@ +using Nerosoft.Euonia.Bus; +using Nerosoft.Starfish.Domain; + +namespace Nerosoft.Starfish.Application; + +internal partial class LoggingEventSubscriber +{ + private const string MODULE_TEAM = "team"; + + [Subscribe] + public async Task HandleAsync(TeamCreatedEvent @event, MessageContext context, CancellationToken cancellationToken = default) + { + var aggregate = @event.GetAggregate(); + var command = new OperateLogCreateCommand + { + Module = MODULE_TEAM, + Type = "create", + OperateTime = DateTime.Now, + UserName = context.User?.Identity?.Name, + RequestTraceId = context.RequestTraceId + }; + await _bus.SendAsync(command, new SendOptions { RequestTraceId = context.RequestTraceId }, null, cancellationToken); + } + + [Subscribe] + public async Task HandleAsync(TeamMemberAppendedEvent @event, MessageContext context, CancellationToken cancellationToken = default) + { + var aggregate = @event.GetAggregate(); + var command = new OperateLogCreateCommand + { + Module = MODULE_TEAM, + Type = "member.append", + OperateTime = DateTime.Now, + UserName = context.User?.Identity?.Name, + RequestTraceId = context.RequestTraceId + }; + await _bus.SendAsync(command, new SendOptions { RequestTraceId = context.RequestTraceId }, null, cancellationToken); + } + + [Subscribe] + public async Task HandleAsync(TeamMemberRemovedEvent @event, MessageContext context, CancellationToken cancellationToken = default) + { + var aggregate = @event.GetAggregate(); + var command = new OperateLogCreateCommand + { + Module = MODULE_TEAM, + Type = "member.remove", + OperateTime = DateTime.Now, + UserName = context.User?.Identity?.Name, + RequestTraceId = context.RequestTraceId + }; + await _bus.SendAsync(command, new SendOptions { RequestTraceId = context.RequestTraceId }, null, cancellationToken); + } +} diff --git a/Source/Starfish.Service/Application/Subscribers/LoggingEventSubscriber.User.cs b/Source/Starfish.Service/Application/Subscribers/LoggingEventSubscriber.User.cs new file mode 100644 index 0000000..1e2933e --- /dev/null +++ b/Source/Starfish.Service/Application/Subscribers/LoggingEventSubscriber.User.cs @@ -0,0 +1,38 @@ +using Nerosoft.Euonia.Bus; +using Nerosoft.Starfish.Domain; + +namespace Nerosoft.Starfish.Application; + +internal sealed partial class LoggingEventSubscriber +{ + private const string MODULE_USER = "user"; + + [Subscribe] + public async Task HandleAsync(UserCreatedEvent @event, MessageContext context, CancellationToken cancellationToken = default) + { + var command = new OperateLogCreateCommand + { + Module = MODULE_USER, + Type = "create", + UserName = @event.UserName, + OperateTime = DateTime.Now, + RequestTraceId = context.RequestTraceId + }; + await _bus.SendAsync(command, new SendOptions { RequestTraceId = context.RequestTraceId }, null, cancellationToken); + } + + [Subscribe] + public async Task HandleAsync(UserPasswordChangedEvent @event, MessageContext context, CancellationToken cancellationToken = default) + { + var aggregate = @event.GetAggregate(); + var command = new OperateLogCreateCommand + { + Module = MODULE_USER, + Type = "password", + UserName = aggregate.UserName, + OperateTime = DateTime.Now, + RequestTraceId = context.RequestTraceId + }; + await _bus.SendAsync(command, new SendOptions { RequestTraceId = context.RequestTraceId }, null, cancellationToken); + } +} diff --git a/Source/Starfish.Service/Application/Subscribers/LoggingEventSubscriber.cs b/Source/Starfish.Service/Application/Subscribers/LoggingEventSubscriber.cs index 9b90848..bd35a7d 100644 --- a/Source/Starfish.Service/Application/Subscribers/LoggingEventSubscriber.cs +++ b/Source/Starfish.Service/Application/Subscribers/LoggingEventSubscriber.cs @@ -6,229 +6,12 @@ namespace Nerosoft.Starfish.Application; /// /// 应用日志收集处理器 /// -public sealed class LoggingEventSubscriber +internal sealed partial class LoggingEventSubscriber { - private const string MODULE_CONFIG = "config"; - private readonly IBus _bus; public LoggingEventSubscriber(IBus bus) { _bus = bus; } - - /// - /// 处理用户认证成功事件 - /// - /// - /// - /// - /// - [Subscribe] - public Task HandleAsync(UserAuthSucceedEvent @event, MessageContext context, CancellationToken cancellationToken = default) - { - var command = new OperateLogCreateCommand - { - Module = "auth", - Type = @event.AuthType, - UserName = @event.UserName, - OperateTime = DateTime.Now, - Description = Resources.IDS_MESSAGE_LOGS_AUTH_SUCCEED, - RequestTraceId = context.RequestTraceId - }; - return _bus.SendAsync(command, new SendOptions { RequestTraceId = context.RequestTraceId }, null, cancellationToken); - } - - /// - /// 处理用户认证失败事件 - /// - /// - /// - /// - /// - [Subscribe] - public Task HandleAsync(UserAuthFailedEvent @event, MessageContext context, CancellationToken cancellationToken = default) - { - var command = new OperateLogCreateCommand - { - Module = "auth", - Type = @event.AuthType, - Description = Resources.IDS_MESSAGE_LOGS_AUTH_FAILED, - OperateTime = DateTime.Now, - RequestTraceId = context.RequestTraceId, - Error = @event.Error - }; - - return _bus.SendAsync(command, new SendOptions { RequestTraceId = context.RequestTraceId }, null, cancellationToken); - } - - /// - /// 处理配置启用事件 - /// - /// - /// - /// - /// - [Subscribe] - public Task HandleAsync(ConfigurationEnabledEvent @event, MessageContext context, CancellationToken cancellationToken = default) - { - var aggregate = @event.GetAggregate(); - var command = new OperateLogCreateCommand - { - Module = MODULE_CONFIG, - Type = "status", - Description = string.Format(Resources.IDS_MESSAGE_LOGS_CONFIG_ENABLE, aggregate.Id, aggregate.Name), - OperateTime = DateTime.Now, - RequestTraceId = context.RequestTraceId, - UserName = context.User?.Identity?.Name - }; - - return _bus.SendAsync(command, new SendOptions { RequestTraceId = context.RequestTraceId }, null, cancellationToken); - } - - /// - /// 处理配置禁用事件 - /// - /// - /// - /// - /// - [Subscribe] - public Task HandleAsync(ConfigurationDisableEvent @event, MessageContext context, CancellationToken cancellationToken = default) - { - var aggregate = @event.GetAggregate(); - var command = new OperateLogCreateCommand - { - Module = MODULE_CONFIG, - Type = "status", - Description = string.Format(Resources.IDS_MESSAGE_LOGS_CONFIG_DISABLE, aggregate.Id, aggregate.Name), - OperateTime = DateTime.Now, - RequestTraceId = context.RequestTraceId, - UserName = context.User?.Identity?.Name - }; - - return _bus.SendAsync(command, new SendOptions { RequestTraceId = context.RequestTraceId }, null, cancellationToken); - } - - /// - /// 处理配置密钥重置事件 - /// - /// - /// - /// - /// - [Subscribe] - public Task HandleAsync(ConfigurationSecretChangedEvent @event, MessageContext context, CancellationToken cancellationToken = default) - { - var aggregate = @event.GetAggregate(); - var command = new OperateLogCreateCommand - { - Module = MODULE_CONFIG, - Type = "secret", - Description = string.Format(Resources.IDS_MESSAGE_LOGS_CONFIG_RESET_SECRET, aggregate.Id, aggregate.Name), - OperateTime = DateTime.Now, - RequestTraceId = context.RequestTraceId, - UserName = context.User?.Identity?.Name - }; - - return _bus.SendAsync(command, new SendOptions { RequestTraceId = context.RequestTraceId }, null, cancellationToken); - } - - /// - /// 处理配置更新事件 - /// - /// - /// - /// - /// - [Subscribe] - public Task HandleAsync(ConfigurationUpdatedEvent @event, MessageContext context, CancellationToken cancellationToken = default) - { - var aggregate = @event.GetAggregate(); - var command = new OperateLogCreateCommand - { - Module = MODULE_CONFIG, - Type = "update", - Description = string.Format(Resources.IDS_MESSAGE_LOGS_CONFIG_UPDATE, aggregate.Id, aggregate.Name), - OperateTime = DateTime.Now, - RequestTraceId = context.RequestTraceId, - UserName = context.User?.Identity?.Name - }; - - return _bus.SendAsync(command, new SendOptions { RequestTraceId = context.RequestTraceId }, null, cancellationToken); - } - - /// - /// 处理配置节点创建事件 - /// - /// - /// - /// - /// - [Subscribe] - public Task HandleAsync(ConfigurationCreatedEvent @event, MessageContext context, CancellationToken cancellationToken = default) - { - var description = string.Format(Resources.IDS_MESSAGE_LOGS_CONFIG_CREATE, @event.Configuration.Id, @event.Configuration.Name); - - var command = new OperateLogCreateCommand - { - Module = MODULE_CONFIG, - Type = "create", - Description = description, - OperateTime = DateTime.Now, - RequestTraceId = context.RequestTraceId, - UserName = context.User?.Identity?.Name - }; - return _bus.SendAsync(command, new SendOptions { RequestTraceId = context.RequestTraceId }, null, cancellationToken); - } - - /// - /// 处理配置节点删除事件 - /// - /// - /// - /// - /// - [Subscribe] - public Task HandleAsync(ConfigurationDeletedEvent @event, MessageContext context, CancellationToken cancellationToken = default) - { - var aggregate = @event.GetAggregate(); - var description = string.Format(Resources.IDS_MESSAGE_LOGS_CONFIG_DELETE, aggregate.Id, aggregate.Name); - - var command = new OperateLogCreateCommand - { - Module = MODULE_CONFIG, - Type = "delete", - Description = description, - OperateTime = DateTime.Now, - RequestTraceId = context.RequestTraceId, - UserName = context.User?.Identity?.Name - }; - return _bus.SendAsync(command, new SendOptions { RequestTraceId = context.RequestTraceId }, null, cancellationToken); - } - - /// - /// 处理配置发布事件 - /// - /// - /// - /// - /// - [Subscribe] - public Task HandleAsync(ConfigurationPublishedEvent @event, MessageContext context, CancellationToken cancellationToken = default) - { - var aggregate = @event.GetAggregate(); - var description = string.Format(Resources.IDS_MESSAGE_LOGS_CONFIG_PUBLISH, aggregate.Id, aggregate.Name); - - var command = new OperateLogCreateCommand - { - Module = MODULE_CONFIG, - Type = "publish", - Description = description, - OperateTime = DateTime.Now, - RequestTraceId = context.RequestTraceId, - UserName = context.User?.Identity?.Name - }; - return _bus.SendAsync(command, new SendOptions { RequestTraceId = context.RequestTraceId }, null, cancellationToken); - } } \ No newline at end of file diff --git a/Source/Starfish.Service/Application/TokenGenerator.cs b/Source/Starfish.Service/Application/TokenGenerator.cs index 1b8412f..ad4b525 100644 --- a/Source/Starfish.Service/Application/TokenGenerator.cs +++ b/Source/Starfish.Service/Application/TokenGenerator.cs @@ -189,7 +189,7 @@ public string Build() Issuer = Issuer, Audience = Audience, IssuedAt = IssueTime, - SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) + SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), Algorithm) }; var token = handler.CreateToken(descriptor); diff --git a/Source/Starfish.Service/Domain/Aggregates/OperateLog.cs b/Source/Starfish.Service/Domain/Aggregates/OperateLog.cs index 61735e5..dd91898 100644 --- a/Source/Starfish.Service/Domain/Aggregates/OperateLog.cs +++ b/Source/Starfish.Service/Domain/Aggregates/OperateLog.cs @@ -24,7 +24,7 @@ private OperateLog() /// /// 描述 /// - public string Description { get; set; } + public string Content { get; set; } /// /// 用户名 @@ -46,13 +46,13 @@ private OperateLog() /// public string RequestTraceId { get; set; } - internal static OperateLog Create(string module, string type, string description, string userName, DateTime operateTime, string error, string requestTraceId) + internal static OperateLog Create(string module, string type, string content, string userName, DateTime operateTime, string error, string requestTraceId) { return new OperateLog { Module = module, Type = type, - Description = description, + Content = content, UserName = userName, OperateTime = operateTime, Error = error, diff --git a/Source/Starfish.Service/Domain/Aggregates/Team.cs b/Source/Starfish.Service/Domain/Aggregates/Team.cs index 1386b0b..8319d24 100644 --- a/Source/Starfish.Service/Domain/Aggregates/Team.cs +++ b/Source/Starfish.Service/Domain/Aggregates/Team.cs @@ -7,14 +7,26 @@ namespace Nerosoft.Starfish.Domain; /// 团队聚合根对象 /// public sealed class Team : Aggregate, - IAuditing + IAuditing { + /// + /// 团队名称 + /// public string Name { get; set; } + /// + /// 团队描述 + /// public string Description { get; set; } + /// + /// 团队负责人Id + /// public string OwnerId { get; set; } + /// + /// 团队成员数 + /// public int MemberCount { get; set; } public DateTime CreateTime { get; set; } @@ -35,10 +47,15 @@ internal static Team Create(string name, string description, string ownerId) Description = description, OwnerId = ownerId }; - team.AddMember(ownerId); + team.AppendMember(ownerId); + team.RaiseEvent(new TeamCreatedEvent()); return team; } + /// + /// 设置团队名称 + /// + /// internal void SetName(string name) { if (string.Equals(Name, name)) @@ -49,6 +66,10 @@ internal void SetName(string name) Name = name; } + /// + /// 设置团队描述 + /// + /// internal void SetDescription(string description) { if (string.Equals(Description, description)) @@ -59,7 +80,11 @@ internal void SetDescription(string description) Description = description; } - internal void AddMember(string userId) + /// + /// 添加团队成员 + /// + /// + internal void AppendMember(string userId) { Members ??= []; @@ -71,8 +96,18 @@ internal void AddMember(string userId) Members.Add(TeamMember.Create(userId)); MemberCount++; + + if (!string.IsNullOrEmpty(Id)) + { + RaiseEvent(new TeamMemberAppendedEvent { UserId = userId }); + } } + /// + /// 移除团队成员 + /// + /// + /// internal void RemoveMember(string userId) { if (Members == null || Members.All(t => t.UserId != userId)) @@ -88,5 +123,7 @@ internal void RemoveMember(string userId) Members.RemoveWhere(t => t.UserId == userId); MemberCount--; + + RaiseEvent(new TeamMemberRemovedEvent { UserId = userId }); } } \ No newline at end of file diff --git a/Source/Starfish.Service/Domain/Aggregates/User.cs b/Source/Starfish.Service/Domain/Aggregates/User.cs index 04141ab..4fb6b9f 100644 --- a/Source/Starfish.Service/Domain/Aggregates/User.cs +++ b/Source/Starfish.Service/Domain/Aggregates/User.cs @@ -21,14 +21,13 @@ private User() /// 初始化用户聚合根 /// /// - /// - /// - private User(string userName, string passwordHash, string passwordSalt) + /// + /// + private User(string userName, string password) : this() { UserName = userName; - PasswordHash = passwordHash; - PasswordSalt = passwordSalt; + SetPassword(password); } /// @@ -115,22 +114,25 @@ private User(string userName, string passwordHash, string passwordSalt) /// internal static User Create(string userName, string password) { - var salt = RandomUtility.GenerateUniqueId(); - var hash = Cryptography.DES.Encrypt(password, Encoding.UTF8.GetBytes(salt)); - var entity = new User(userName, hash, salt); + var entity = new User(userName, password); return entity; } /// - /// 修改密码 + /// 设置密码 /// /// - internal void ChangePassword(string password) + /// + internal void SetPassword(string password, string actionType = null) { var salt = RandomUtility.GenerateUniqueId(); var hash = Cryptography.DES.Encrypt(password, Encoding.UTF8.GetBytes(salt)); PasswordHash = hash; PasswordSalt = salt; + if (!string.IsNullOrWhiteSpace(actionType)) + { + RaiseEvent(new UserPasswordChangedEvent { Type = actionType }); + } } /// diff --git a/Source/Starfish.Service/Domain/Business/TeamMemberBusiness.cs b/Source/Starfish.Service/Domain/Business/TeamMemberBusiness.cs index d1f4f33..a8ee16e 100644 --- a/Source/Starfish.Service/Domain/Business/TeamMemberBusiness.cs +++ b/Source/Starfish.Service/Domain/Business/TeamMemberBusiness.cs @@ -43,7 +43,7 @@ protected override Task InsertAsync(CancellationToken cancellationToken = defaul { foreach (var userId in UserIds) { - Aggregate.AddMember(userId); + Aggregate.AppendMember(userId); } return TeamRepository.UpdateAsync(Aggregate, true, cancellationToken); diff --git a/Source/Starfish.Service/Domain/Business/UserGeneralBusiness.cs b/Source/Starfish.Service/Domain/Business/UserGeneralBusiness.cs index 44ed584..00e2944 100644 --- a/Source/Starfish.Service/Domain/Business/UserGeneralBusiness.cs +++ b/Source/Starfish.Service/Domain/Business/UserGeneralBusiness.cs @@ -160,11 +160,6 @@ protected override Task UpdateAsync(CancellationToken cancellationToken = defaul Aggregate.SetNickName(NickName); } - if (ChangedProperties.Contains(PasswordProperty)) - { - Aggregate.ChangePassword(Password); - } - if (ChangedProperties.Contains(IsAdminProperty)) { Aggregate.SetIsAdmin(IsAdmin); @@ -272,8 +267,6 @@ public override async Task ExecuteAsync(IRuleContext context, CancellationToken public class PasswordStrengthRule : RuleBase { - private const string REGEX_PATTERN = @"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[\x00-\xff]{8,32}$"; - public override Task ExecuteAsync(IRuleContext context, CancellationToken cancellationToken = default) { var target = (UserGeneralBusiness)context.Target; @@ -284,12 +277,12 @@ public override Task ExecuteAsync(IRuleContext context, CancellationToken cancel { context.AddErrorResult(Resources.IDS_ERROR_USER_RULE_PASSWORD_REQUIRED); } - else if (!Regex.IsMatch(target.Password, REGEX_PATTERN)) + else if (!Regex.IsMatch(target.Password, Constants.RegexPattern.Password)) { context.AddErrorResult(Resources.IDS_ERROR_USER_RULE_PASSWORD_NOT_MARCHED_RULES); } } - else if (target.IsUpdate && target.ChangedProperties.Contains(PasswordProperty) && !Regex.IsMatch(target.Password, REGEX_PATTERN)) + else if (target.IsUpdate && target.ChangedProperties.Contains(PasswordProperty) && !Regex.IsMatch(target.Password, Constants.RegexPattern.Password)) { context.AddErrorResult(Resources.IDS_ERROR_USER_RULE_PASSWORD_NOT_MARCHED_RULES); } diff --git a/Source/Starfish.Service/Domain/Business/UserPasswordBusiness.cs b/Source/Starfish.Service/Domain/Business/UserPasswordBusiness.cs new file mode 100644 index 0000000..b21ebf4 --- /dev/null +++ b/Source/Starfish.Service/Domain/Business/UserPasswordBusiness.cs @@ -0,0 +1,36 @@ +using System.Text.RegularExpressions; +using Nerosoft.Euonia.Business; +using Nerosoft.Euonia.Domain; +using Nerosoft.Starfish.Service; + +namespace Nerosoft.Starfish.Domain; + +internal class UserPasswordBusiness : CommandObjectBase, IDomainService +{ + [Inject] + public IUserRepository Repository { get; set; } + + [FactoryExecute] + protected async Task ExecuteAsync(string id, string password, string actionType, CancellationToken cancellationToken = default) + { + if (string.IsNullOrWhiteSpace(password)) + { + throw new ValidationException(Resources.IDS_ERROR_USER_RULE_PASSWORD_REQUIRED); + } + else if (!Regex.IsMatch(password, Constants.RegexPattern.Password)) + { + throw new ValidationException(Resources.IDS_ERROR_USER_RULE_PASSWORD_NOT_MARCHED_RULES); + } + + var aggregate = await Repository.GetAsync(id, true, cancellationToken); + + if (aggregate == null) + { + throw new UserNotFoundException(id); + } + + aggregate.SetPassword(password, actionType); + + await Repository.UpdateAsync(aggregate, true, cancellationToken); + } +} diff --git a/Source/Starfish.Service/Domain/Events/TeamCreatedEvent.cs b/Source/Starfish.Service/Domain/Events/TeamCreatedEvent.cs new file mode 100644 index 0000000..34fd9b4 --- /dev/null +++ b/Source/Starfish.Service/Domain/Events/TeamCreatedEvent.cs @@ -0,0 +1,7 @@ +using Nerosoft.Euonia.Domain; + +namespace Nerosoft.Starfish.Domain; + +internal sealed class TeamCreatedEvent : DomainEvent +{ +} diff --git a/Source/Starfish.Service/Domain/Events/TeamMemberAppendedEvent.cs b/Source/Starfish.Service/Domain/Events/TeamMemberAppendedEvent.cs new file mode 100644 index 0000000..5ac46e5 --- /dev/null +++ b/Source/Starfish.Service/Domain/Events/TeamMemberAppendedEvent.cs @@ -0,0 +1,8 @@ +using Nerosoft.Euonia.Domain; + +namespace Nerosoft.Starfish.Domain; + +internal sealed class TeamMemberAppendedEvent : DomainEvent +{ + public string UserId { get; set; } +} diff --git a/Source/Starfish.Service/Domain/Events/TeamMemberRemovedEvent.cs b/Source/Starfish.Service/Domain/Events/TeamMemberRemovedEvent.cs new file mode 100644 index 0000000..b079cf0 --- /dev/null +++ b/Source/Starfish.Service/Domain/Events/TeamMemberRemovedEvent.cs @@ -0,0 +1,8 @@ +using Nerosoft.Euonia.Domain; + +namespace Nerosoft.Starfish.Domain; + +internal class TeamMemberRemovedEvent : DomainEvent +{ + public string UserId { get; set; } +} diff --git a/Source/Starfish.Service/Domain/Events/UserCreatedEvent.cs b/Source/Starfish.Service/Domain/Events/UserCreatedEvent.cs index 6a7c829..4a835ee 100644 --- a/Source/Starfish.Service/Domain/Events/UserCreatedEvent.cs +++ b/Source/Starfish.Service/Domain/Events/UserCreatedEvent.cs @@ -5,7 +5,7 @@ namespace Nerosoft.Starfish.Domain; /// /// 用户创建事件 /// -public sealed class UserCreatedEvent : DomainEvent +internal sealed class UserCreatedEvent : DomainEvent { /// /// 用户名 diff --git a/Source/Starfish.Service/Domain/Events/UserPasswordChangedEvent.cs b/Source/Starfish.Service/Domain/Events/UserPasswordChangedEvent.cs index e18874f..e788d69 100644 --- a/Source/Starfish.Service/Domain/Events/UserPasswordChangedEvent.cs +++ b/Source/Starfish.Service/Domain/Events/UserPasswordChangedEvent.cs @@ -7,4 +7,12 @@ namespace Nerosoft.Starfish.Domain; /// public sealed class UserPasswordChangedEvent : DomainEvent { + /// + /// 操作类型 + /// + /// + /// - change + /// - reset + /// + public string Type { get; set; } } \ No newline at end of file diff --git a/Source/Starfish.Service/Properties/Resources.resx b/Source/Starfish.Service/Properties/Resources.resx index 10f0254..45f5b53 100644 --- a/Source/Starfish.Service/Properties/Resources.resx +++ b/Source/Starfish.Service/Properties/Resources.resx @@ -258,6 +258,39 @@ UserName '{0}' not available. + + Login with one-time-password + + + Login with password + + + Refresh token + + + Create team + + + Add new member + + + Remove member + + + Update team + + + Create user + + + Change password + + + Reset password + + + Update user + User authenticate failed. diff --git a/Source/Starfish.Service/UseCases/Identity/ChangePasswordUseCase.cs b/Source/Starfish.Service/UseCases/Identity/ChangePasswordUseCase.cs index 8bd518b..11a7215 100644 --- a/Source/Starfish.Service/UseCases/Identity/ChangePasswordUseCase.cs +++ b/Source/Starfish.Service/UseCases/Identity/ChangePasswordUseCase.cs @@ -45,7 +45,7 @@ public async Task ExecuteAsync(ChangePasswordInput input, CancellationToken canc throw new BadRequestException(Resources.IDS_ERROR_PASSWORD_INCORRECT); } - var command = new ChangePasswordCommand(user.Id, input.NewPassword); + var command = new ChangePasswordCommand(user.Id, input.NewPassword, "change"); await _bus.SendAsync(command, cancellationToken); } } \ No newline at end of file diff --git a/Source/Starfish.Service/UseCases/Identity/ResetPasswordUseCase.cs b/Source/Starfish.Service/UseCases/Identity/ResetPasswordUseCase.cs index 1240a2f..77ab0db 100644 --- a/Source/Starfish.Service/UseCases/Identity/ResetPasswordUseCase.cs +++ b/Source/Starfish.Service/UseCases/Identity/ResetPasswordUseCase.cs @@ -19,7 +19,7 @@ public ResetPasswordUseCase(IBus bus) public Task ExecuteAsync(ResetPasswordInput input, CancellationToken cancellationToken = default) { - var command = new ChangePasswordCommand(input.Id, input.Password); + var command = new ChangePasswordCommand(input.Id, input.Password, "reset"); return _bus.SendAsync(command, cancellationToken); } } \ No newline at end of file diff --git a/Source/Starfish.Transit/Logging/OperateLogDto.cs b/Source/Starfish.Transit/Logging/OperateLogDto.cs index 5b07802..b30eef2 100644 --- a/Source/Starfish.Transit/Logging/OperateLogDto.cs +++ b/Source/Starfish.Transit/Logging/OperateLogDto.cs @@ -10,16 +10,6 @@ public class OperateLogDto /// public long Id { get; set; } - /// - /// 模块 - /// - public string Module { get; set; } - - /// - /// 类型 - /// - public string Type { get; set; } - /// /// 描述 /// diff --git a/Source/Starfish.Webapi/Controllers/UserController.cs b/Source/Starfish.Webapi/Controllers/UserController.cs index b43c90e..8b8810e 100644 --- a/Source/Starfish.Webapi/Controllers/UserController.cs +++ b/Source/Starfish.Webapi/Controllers/UserController.cs @@ -102,7 +102,7 @@ public async Task DeleteAsync(string id) await _service.DeleteAsync(id, HttpContext.RequestAborted); return Ok(); } - + /// /// 重置指定用户密码 /// diff --git a/Tests/common.props b/Tests/common.props index 4eeba74..e177f4c 100644 --- a/Tests/common.props +++ b/Tests/common.props @@ -13,13 +13,14 @@ + - runtime; build; native; contentfiles; analyzers; buildtransitive - all + all + runtime; build; native; contentfiles; analyzers; buildtransitive - runtime; build; native; contentfiles; analyzers; buildtransitive - all + all + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/global.props b/global.props index a0a653a..ad45b6d 100644 --- a/global.props +++ b/global.props @@ -1,6 +1,6 @@ - 1.0.4 + 1.0.5 damon Nerosoft Ltd. Starfish