From cfe1655313f9165c86b9c6b45a768921f4219ebb Mon Sep 17 00:00:00 2001 From: damon Date: Thu, 28 Dec 2023 00:19:34 +0800 Subject: [PATCH 01/22] =?UTF-8?q?=E6=8F=90=E5=8F=96=E5=B8=B8=E9=87=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Source/Starfish.Client/Constants.cs | 9 ++- .../Starfish.Client/Properties/Resources.resx | 3 + .../StarfishConfigurationProvider.cs | 2 +- .../EtcdConfigurationProvider.cs | 79 +++++++++++++++++++ .../Starfish.Etcd/EtcdConfigurationSource.cs | 13 +++ Source/Starfish.Etcd/EtcdOptions.cs | 29 +++++++ Source/Starfish.Webapi/Constants.cs | 12 +++ .../Controllers/EventStreamController.cs | 8 +- .../Controllers/WebSocketController.cs | 8 +- 9 files changed, 150 insertions(+), 13 deletions(-) create mode 100644 Source/Starfish.Etcd/EtcdConfigurationProvider.cs create mode 100644 Source/Starfish.Etcd/EtcdConfigurationSource.cs create mode 100644 Source/Starfish.Etcd/EtcdOptions.cs create mode 100644 Source/Starfish.Webapi/Constants.cs diff --git a/Source/Starfish.Client/Constants.cs b/Source/Starfish.Client/Constants.cs index 6c009d4..57d84d8 100644 --- a/Source/Starfish.Client/Constants.cs +++ b/Source/Starfish.Client/Constants.cs @@ -4,8 +4,9 @@ internal class Constants { public class RequestHeaders { - public const string AppId = "app-id"; - public const string AppSecret = "app-secret"; - public const string AppEnv = "app-env"; + public const string TeamId = "starfish-team-id"; + public const string AppId = "starfish-app-id"; + public const string AppSecret = "starfish-app-secret"; + public const string AppEnv = "starfish-app-env"; } -} +} \ No newline at end of file diff --git a/Source/Starfish.Client/Properties/Resources.resx b/Source/Starfish.Client/Properties/Resources.resx index f7fae12..0aab035 100644 --- a/Source/Starfish.Client/Properties/Resources.resx +++ b/Source/Starfish.Client/Properties/Resources.resx @@ -126,4 +126,7 @@ Starfish section not found in configuration. + + Schema {0} is not supported. + \ No newline at end of file diff --git a/Source/Starfish.Client/StarfishConfigurationProvider.cs b/Source/Starfish.Client/StarfishConfigurationProvider.cs index e166330..8ef9fa5 100644 --- a/Source/Starfish.Client/StarfishConfigurationProvider.cs +++ b/Source/Starfish.Client/StarfishConfigurationProvider.cs @@ -60,7 +60,7 @@ private async void OnHostChanged(object sender, HostChangedEventArgs args) { "http" or "https" => new HttpConfigurationClient(uri, _options.AppId, _options.AppSecret, _options.Environment), "ws" or "wss" => new SocketConfigurationClient(uri, _options.AppId, _options.AppSecret, _options.Environment), - _ => throw new NotSupportedException($"Schema {uri.Scheme} is not supported."), + _ => throw new NotSupportedException(string.Format(Resources.IDS_ERROR_SCHEMA_NOT_SUPPORTED, uri.Scheme)), }; try { diff --git a/Source/Starfish.Etcd/EtcdConfigurationProvider.cs b/Source/Starfish.Etcd/EtcdConfigurationProvider.cs new file mode 100644 index 0000000..ebe4eb0 --- /dev/null +++ b/Source/Starfish.Etcd/EtcdConfigurationProvider.cs @@ -0,0 +1,79 @@ +using Google.Protobuf; +using Microsoft.Extensions.Configuration; + +namespace Nerosoft.Starfish.Etcd; +public class EtcdConfigurationProvider : ConfigurationProvider +{ + private readonly string _path; + private readonly bool _reloadOnChange; + private readonly EtcdClient _client; + + public EtcdConfigurationProvider(EtcdOptions options) + { + _client = new EtcdClient(options.Address); + _client.Authenticate(new AuthenticateRequest { Name = options.UserName, Password = options.PassWord }); + _path = options.Path; + _reloadOnChange = options.ReloadOnChange; + } + + /// + /// 重写加载方法 + /// + public override void Load() + { + //读取数据 + LoadData(); + //数据发生变化是否重新加载 + if (_reloadOnChange) + { + ReloadData(); + } + } + + private void LoadData() + { + //读取Etcd里的数据 + string result = _client.GetValAsync(_path).GetAwaiter().GetResult(); + if (string.IsNullOrEmpty(result)) + { + return; + } + //转换一下数据结构,这里我使用的是json格式 + //读取的数据只要赋值到Data属性上即可,IConfiguration真正读取的数据就是存储到Data的字典数据 + Data = ConvertData(result); + } + + private IDictionary ConvertData(string result) + { + byte[] array = Encoding.UTF8.GetBytes(result); + MemoryStream stream = new MemoryStream(array); + //return JsonConfigurationFileParser.Parse(stream); + return null; + } + + private void ReloadData() + { + WatchRequest request = new WatchRequest() + { + CreateRequest = new WatchCreateRequest() + { + //需要转换一个格式,因为etcd v3版本的接口都包含在grpc的定义中 + Key = ByteString.CopyFromUtf8(_path) + } + }; + //监听Etcd节点变化,获取变更数据,更新配置 + _client.Watch(request, rsp => + { + if (rsp.Events.Any()) + { + var @event = rsp.Events[0]; + //需要转换一个格式,因为etcd v3版本的接口都包含在grpc的定义中 + Data = ConvertData(@event.Kv.Value.ToStringUtf8()); + //需要调用ConfigurationProvider的OnReload方法触发ConfigurationReloadToken通知 + //这样才能对使用Configuration的类发送数据变更通知 + //比如IOptionsMonitor就是通过ConfigurationReloadToken通知变更数据的 + OnReload(); + } + }); + } +} \ No newline at end of file diff --git a/Source/Starfish.Etcd/EtcdConfigurationSource.cs b/Source/Starfish.Etcd/EtcdConfigurationSource.cs new file mode 100644 index 0000000..5868930 --- /dev/null +++ b/Source/Starfish.Etcd/EtcdConfigurationSource.cs @@ -0,0 +1,13 @@ +using Microsoft.Extensions.Configuration; + +namespace Nerosoft.Starfish.Etcd; + +public class EtcdConfigurationSource : IConfigurationSource +{ + public EtcdOptions EtcdOptions { get; set; } + + public IConfigurationProvider Build(IConfigurationBuilder builder) + { + return new EtcdConfigurationProvider(EtcdOptions); + } +} diff --git a/Source/Starfish.Etcd/EtcdOptions.cs b/Source/Starfish.Etcd/EtcdOptions.cs new file mode 100644 index 0000000..454efac --- /dev/null +++ b/Source/Starfish.Etcd/EtcdOptions.cs @@ -0,0 +1,29 @@ +namespace Nerosoft.Starfish.Etcd; + +public class EtcdOptions +{ + /// + /// Etcd地址 + /// + public string Address { get; set; } + + /// + /// Etcd访问用户名 + /// + public string UserName { get; set; } + + /// + /// Etcd访问密码 + /// + public string PassWord { get; set; } + + /// + /// Etcd读取路径 + /// + public string Path { get; set; } + + /// + /// 数据变更是否刷新读取 + /// + public bool ReloadOnChange { get; set; } +} diff --git a/Source/Starfish.Webapi/Constants.cs b/Source/Starfish.Webapi/Constants.cs new file mode 100644 index 0000000..71caaeb --- /dev/null +++ b/Source/Starfish.Webapi/Constants.cs @@ -0,0 +1,12 @@ +namespace Nerosoft.Starfish.Webapi; + +internal class Constants +{ + public class RequestHeaders + { + public const string TeamId = "starfish-team-id"; + public const string AppId = "starfish-app-id"; + public const string AppSecret = "starfish-app-secret"; + public const string AppEnv = "starfish-app-env"; + } +} \ No newline at end of file diff --git a/Source/Starfish.Webapi/Controllers/EventStreamController.cs b/Source/Starfish.Webapi/Controllers/EventStreamController.cs index 8cf9ff1..8cb2aa8 100644 --- a/Source/Starfish.Webapi/Controllers/EventStreamController.cs +++ b/Source/Starfish.Webapi/Controllers/EventStreamController.cs @@ -34,7 +34,7 @@ public EventStreamController(ConnectionContainer container, ILoggerFactory logge public async Task HandleAsync() { var appId = await AuthAsync(); - var environment = HttpContext.Request.Headers["app-env"]; + var environment = HttpContext.Request.Headers[Constants.RequestHeaders.AppEnv]; Response.Headers.Append(HeaderNames.ContentType, "text/event-stream"); Response.Headers.Append(HeaderNames.Connection, "close"); try @@ -71,10 +71,10 @@ public async Task HandleAsync() private Task AuthAsync() { - var appCode = HttpContext.Request.Headers["app-id"]; - var appSecret = HttpContext.Request.Headers["app-secret"]; + var appId = HttpContext.Request.Headers[Constants.RequestHeaders.AppId]; + var appSecret = HttpContext.Request.Headers[Constants.RequestHeaders.AppSecret]; var service = HttpContext.RequestServices.GetRequiredService(); - return service.AuthorizeAsync(appCode, appSecret, HttpContext.RequestAborted); + return service.AuthorizeAsync(appId, appSecret, HttpContext.RequestAborted); } } \ No newline at end of file diff --git a/Source/Starfish.Webapi/Controllers/WebSocketController.cs b/Source/Starfish.Webapi/Controllers/WebSocketController.cs index 05294c6..6bef908 100644 --- a/Source/Starfish.Webapi/Controllers/WebSocketController.cs +++ b/Source/Starfish.Webapi/Controllers/WebSocketController.cs @@ -36,7 +36,7 @@ public async Task Handle() var appId = await AuthAsync(); using var socket = await HttpContext.WebSockets.AcceptWebSocketAsync(); - var environment = HttpContext.Request.Headers["app-env"]; + var environment = HttpContext.Request.Headers[Constants.RequestHeaders.AppEnv]; var connection = _container.GetOrAdd(appId, environment, HttpContext.Connection.Id); @@ -94,10 +94,10 @@ async Task MonitorClientAsync(WebSocket webSocket) private Task AuthAsync() { - var appCode = HttpContext.Request.Headers["app-id"]; - var appSecret = HttpContext.Request.Headers["app-secret"]; + var appId = HttpContext.Request.Headers[Constants.RequestHeaders.AppId]; + var appSecret = HttpContext.Request.Headers[Constants.RequestHeaders.AppSecret]; var service = HttpContext.RequestServices.GetRequiredService(); - return service.AuthorizeAsync(appCode, appSecret, HttpContext.RequestAborted); + return service.AuthorizeAsync(appId, appSecret, HttpContext.RequestAborted); } } \ No newline at end of file From 4d017b80bfb8526d8952b578c24bf5e842d0d619 Mon Sep 17 00:00:00 2001 From: damon Date: Mon, 1 Jan 2024 17:37:31 +0800 Subject: [PATCH 02/22] [WIP] Refactoring --- .../Commands/SettingCreateCommand.cs | 26 ++ ...leteCommand.cs => SettingDeleteCommand.cs} | 4 +- .../Commands/SettingLeafNodeCreateCommand.cs | 39 -- .../Commands/SettingNodeSetKeyCommand.cs | 14 - .../Commands/SettingNodeUpdateCommand.cs | 31 -- ...ishCommand.cs => SettingPublishCommand.cs} | 4 +- .../Commands/SettingRevisionCreateCommand.cs | 2 +- .../Contracts/ISettingApplicationService.cs | 51 +-- .../Handlers/SettingArchiveCommandHandler.cs | 33 +- .../Handlers/SettingCommandHandler.cs | 54 +++ .../Handlers/SettingNodeCommandHandler.cs | 187 --------- .../Handlers/SettingRevisionCommandHandler.cs | 30 +- .../Handlers/UserCommandHandler.cs | 2 +- .../Implements/SettingApplicationService.cs | 69 +--- .../JsonConfigurationFileParser.cs | 8 + .../Mappings/SettingMappingProfile.cs | 28 +- .../Subscribers/LoggingEventSubscriber.cs | 41 +- .../Subscribers/SettingEventSubscriber.cs | 14 + .../Subscribers/SettingNodeEventSubscriber.cs | 21 - .../SettingRevisionEventSubscriber.cs | 2 +- .../TextConfigurationFileParser.cs | 55 +++ .../Domain/Aggregates/Setting.cs | 125 ++++++ .../Domain/Aggregates/SettingNode.cs | 361 +----------------- .../Domain/Aggregates/SettingRevision.cs | 20 +- .../Domain/Business/SettingGeneralBusiness.cs | 137 +++++++ .../Business/SettingNodeSetKeyBusiness.cs | 88 ----- .../Business/SettingNodeUpdateBusiness.cs | 115 ------ .../Domain/Business/SettingPublishBusiness.cs | 43 +-- ...{SettingNodeStatus.cs => SettingStatus.cs} | 8 +- .../Domain/Events/SettingCreatedEvent.cs | 17 + ...DeletedEvent.cs => SettingDeletedEvent.cs} | 2 +- .../Domain/Events/SettingNodeCreatedEvent.cs | 30 -- .../Domain/Events/SettingNodeRenamedEvent.cs | 44 --- .../Events/SettingNodeStatusChangedEvent.cs | 10 - .../Events/SettingStatusChangedEvent.cs | 23 ++ .../Exceptions/SettingDisabledException.cs | 9 + ...ception.cs => SettingNotFoundException.cs} | 6 +- .../Repositories/ISettingNodeRepository.cs | 59 --- .../Domain/Repositories/ISettingRepository.cs | 36 ++ .../Properties/Resources.resx | 24 +- .../Properties/Resources.zh-CN.resx | 150 ++++---- .../Repository/Contexts/DataContext.cs | 25 ++ .../Repository/Contexts/SqliteModelBuilder.cs | 49 ++- .../Repository/CriteriaExtensions.cs | 10 +- .../Repositories/SettingNodeRepository.cs | 61 --- .../Repositories/SettingRepository.cs | 26 ++ .../Repository/RepositoryModule.cs | 2 +- .../SettingNodeSpecification.cs | 141 ------- .../Specifications/SettingSpecification.cs | 39 ++ Source/Starfish.Service/Seedwork/IAuditing.cs | 10 + .../UseCases/Identity/UserDetailUseCase.cs | 2 +- .../UseCases/Identity/UserSetRoleUseCase.cs | 2 +- ...CountUseCase.cs => SettingCountUseCase.cs} | 16 +- .../UseCases/Setting/SettingCreateUseCase.cs | 36 ++ ...leteUseCase.cs => SettingDeleteUseCase.cs} | 12 +- ...lUseCase.cs => SettingGetDetailUseCase.cs} | 18 +- .../Setting/SettingLeafNodeCreationUseCase.cs | 51 --- .../SettingNodeUpdateDescriptionUseCase.cs | 36 -- .../Setting/SettingNodeUpdateNameUseCase.cs | 40 -- .../Setting/SettingNodeUpdateValueUseCase.cs | 40 -- ...ishUseCase.cs => SettingPublishUseCase.cs} | 12 +- .../Setting/SettingRootNodeCreationUseCase.cs | 52 --- ...archUseCase.cs => SettingSearchUseCase.cs} | 22 +- ...ngNodeCreateDto.cs => SettingCreateDto.cs} | 19 +- ...tingNodeCriteria.cs => SettingCriteria.cs} | 12 +- .../Setting/SettingDetailDto.cs | 52 +++ .../Setting/SettingItemDto.cs | 47 +++ ...NodePublishDto.cs => SettingPublishDto.cs} | 2 +- ...NodeController.cs => SettingController.cs} | 71 +--- 69 files changed, 1042 insertions(+), 1885 deletions(-) create mode 100644 Source/Starfish.Service/Application/Commands/SettingCreateCommand.cs rename Source/Starfish.Service/Application/Commands/{SettingNodeDeleteCommand.cs => SettingDeleteCommand.cs} (72%) delete mode 100644 Source/Starfish.Service/Application/Commands/SettingLeafNodeCreateCommand.cs delete mode 100644 Source/Starfish.Service/Application/Commands/SettingNodeSetKeyCommand.cs delete mode 100644 Source/Starfish.Service/Application/Commands/SettingNodeUpdateCommand.cs rename Source/Starfish.Service/Application/Commands/{SettingNodePublishCommand.cs => SettingPublishCommand.cs} (66%) create mode 100644 Source/Starfish.Service/Application/Handlers/SettingCommandHandler.cs delete mode 100644 Source/Starfish.Service/Application/Handlers/SettingNodeCommandHandler.cs create mode 100644 Source/Starfish.Service/Application/Subscribers/SettingEventSubscriber.cs delete mode 100644 Source/Starfish.Service/Application/Subscribers/SettingNodeEventSubscriber.cs create mode 100644 Source/Starfish.Service/Application/TextConfigurationFileParser.cs create mode 100644 Source/Starfish.Service/Domain/Aggregates/Setting.cs create mode 100644 Source/Starfish.Service/Domain/Business/SettingGeneralBusiness.cs delete mode 100644 Source/Starfish.Service/Domain/Business/SettingNodeSetKeyBusiness.cs delete mode 100644 Source/Starfish.Service/Domain/Business/SettingNodeUpdateBusiness.cs rename Source/Starfish.Service/Domain/Enums/{SettingNodeStatus.cs => SettingStatus.cs} (59%) create mode 100644 Source/Starfish.Service/Domain/Events/SettingCreatedEvent.cs rename Source/Starfish.Service/Domain/Events/{SettingNodeDeletedEvent.cs => SettingDeletedEvent.cs} (73%) delete mode 100644 Source/Starfish.Service/Domain/Events/SettingNodeCreatedEvent.cs delete mode 100644 Source/Starfish.Service/Domain/Events/SettingNodeRenamedEvent.cs delete mode 100644 Source/Starfish.Service/Domain/Events/SettingNodeStatusChangedEvent.cs create mode 100644 Source/Starfish.Service/Domain/Events/SettingStatusChangedEvent.cs create mode 100644 Source/Starfish.Service/Domain/Exceptions/SettingDisabledException.cs rename Source/Starfish.Service/Domain/Exceptions/{SettingNodeNotFoundException.cs => SettingNotFoundException.cs} (51%) delete mode 100644 Source/Starfish.Service/Domain/Repositories/ISettingNodeRepository.cs create mode 100644 Source/Starfish.Service/Domain/Repositories/ISettingRepository.cs delete mode 100644 Source/Starfish.Service/Repository/Repositories/SettingNodeRepository.cs create mode 100644 Source/Starfish.Service/Repository/Repositories/SettingRepository.cs delete mode 100644 Source/Starfish.Service/Repository/Specifications/SettingNodeSpecification.cs create mode 100644 Source/Starfish.Service/Repository/Specifications/SettingSpecification.cs create mode 100644 Source/Starfish.Service/Seedwork/IAuditing.cs rename Source/Starfish.Service/UseCases/Setting/{SettingNodeCountUseCase.cs => SettingCountUseCase.cs} (57%) create mode 100644 Source/Starfish.Service/UseCases/Setting/SettingCreateUseCase.cs rename Source/Starfish.Service/UseCases/Setting/{SettingNodeDeleteUseCase.cs => SettingDeleteUseCase.cs} (52%) rename Source/Starfish.Service/UseCases/Setting/{SettingNodeGetDetailUseCase.cs => SettingGetDetailUseCase.cs} (58%) delete mode 100644 Source/Starfish.Service/UseCases/Setting/SettingLeafNodeCreationUseCase.cs delete mode 100644 Source/Starfish.Service/UseCases/Setting/SettingNodeUpdateDescriptionUseCase.cs delete mode 100644 Source/Starfish.Service/UseCases/Setting/SettingNodeUpdateNameUseCase.cs delete mode 100644 Source/Starfish.Service/UseCases/Setting/SettingNodeUpdateValueUseCase.cs rename Source/Starfish.Service/UseCases/Setting/{SettingNodePublishUseCase.cs => SettingPublishUseCase.cs} (59%) delete mode 100644 Source/Starfish.Service/UseCases/Setting/SettingRootNodeCreationUseCase.cs rename Source/Starfish.Service/UseCases/Setting/{SettingNodeSearchUseCase.cs => SettingSearchUseCase.cs} (60%) rename Source/Starfish.Transit/Setting/{SettingNodeCreateDto.cs => SettingCreateDto.cs} (63%) rename Source/Starfish.Transit/Setting/{SettingNodeCriteria.cs => SettingCriteria.cs} (78%) create mode 100644 Source/Starfish.Transit/Setting/SettingDetailDto.cs create mode 100644 Source/Starfish.Transit/Setting/SettingItemDto.cs rename Source/Starfish.Transit/Setting/{SettingNodePublishDto.cs => SettingPublishDto.cs} (88%) rename Source/Starfish.Webapi/Controllers/{SettingNodeController.cs => SettingController.cs} (52%) diff --git a/Source/Starfish.Service/Application/Commands/SettingCreateCommand.cs b/Source/Starfish.Service/Application/Commands/SettingCreateCommand.cs new file mode 100644 index 0000000..22ed331 --- /dev/null +++ b/Source/Starfish.Service/Application/Commands/SettingCreateCommand.cs @@ -0,0 +1,26 @@ +using Nerosoft.Euonia.Domain; + +namespace Nerosoft.Starfish.Application; + +public class SettingCreateCommand : Command +{ + /// + /// 应用Id + /// + public long AppId { get; set; } + + /// + /// 环境名称 + /// + public string Environment { get; set; } + + /// + /// 描述 + /// + public string Description { get; set; } + + /// + /// 配置项内容 + /// + public IDictionary Data { get; set; } +} \ No newline at end of file diff --git a/Source/Starfish.Service/Application/Commands/SettingNodeDeleteCommand.cs b/Source/Starfish.Service/Application/Commands/SettingDeleteCommand.cs similarity index 72% rename from Source/Starfish.Service/Application/Commands/SettingNodeDeleteCommand.cs rename to Source/Starfish.Service/Application/Commands/SettingDeleteCommand.cs index f195eb8..b448596 100644 --- a/Source/Starfish.Service/Application/Commands/SettingNodeDeleteCommand.cs +++ b/Source/Starfish.Service/Application/Commands/SettingDeleteCommand.cs @@ -5,13 +5,13 @@ namespace Nerosoft.Starfish.Application; /// /// 删除配置节点命令 /// -public class SettingNodeDeleteCommand : Command +public class SettingDeleteCommand : Command { /// /// 构造函数 /// /// 配置节点Id - public SettingNodeDeleteCommand(long id) + public SettingDeleteCommand(long id) : base(id) { } diff --git a/Source/Starfish.Service/Application/Commands/SettingLeafNodeCreateCommand.cs b/Source/Starfish.Service/Application/Commands/SettingLeafNodeCreateCommand.cs deleted file mode 100644 index 355dcc3..0000000 --- a/Source/Starfish.Service/Application/Commands/SettingLeafNodeCreateCommand.cs +++ /dev/null @@ -1,39 +0,0 @@ -using Nerosoft.Euonia.Domain; -using Nerosoft.Starfish.Domain; -using Nerosoft.Starfish.Transit; - -namespace Nerosoft.Starfish.Application; - -/// -/// 新增子节点命令 -/// -public class SettingLeafNodeCreateCommand : Command -{ - /// - /// 构造函数 - /// - /// - /// - /// - public SettingLeafNodeCreateCommand(long id, SettingNodeType type, SettingNodeCreateDto data) - { - Id = id; - Type = type; - Data = data; - } - - /// - /// 父节点Id - /// - public long Id { get; set; } - - /// - /// 节点类型 - /// - public SettingNodeType Type { get; set; } - - /// - /// 节点数据 - /// - public SettingNodeCreateDto Data { get; set; } -} \ No newline at end of file diff --git a/Source/Starfish.Service/Application/Commands/SettingNodeSetKeyCommand.cs b/Source/Starfish.Service/Application/Commands/SettingNodeSetKeyCommand.cs deleted file mode 100644 index 9cc80db..0000000 --- a/Source/Starfish.Service/Application/Commands/SettingNodeSetKeyCommand.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Nerosoft.Euonia.Domain; - -namespace Nerosoft.Starfish.Application; - -/// -/// 设置节点Key设置命令 -/// -public class SettingNodeSetKeyCommand : Command -{ - public SettingNodeSetKeyCommand(long id, string oldName, string newName) - : base(id, oldName, newName) - { - } -} \ No newline at end of file diff --git a/Source/Starfish.Service/Application/Commands/SettingNodeUpdateCommand.cs b/Source/Starfish.Service/Application/Commands/SettingNodeUpdateCommand.cs deleted file mode 100644 index de061eb..0000000 --- a/Source/Starfish.Service/Application/Commands/SettingNodeUpdateCommand.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Nerosoft.Euonia.Domain; - -namespace Nerosoft.Starfish.Application; - -/// -/// 更新配置节点命令 -/// -public class SettingNodeUpdateCommand : Command -{ - public SettingNodeUpdateCommand(long id, string intent, string value) - { - Id = id; - Intent = intent; - Value = value; - } - - /// - /// 节点Id - /// - public long Id { get; set; } - - /// - /// 更新的属性 - /// - public string Intent { get; set; } - - /// - /// 节点值 - /// - public string Value { get; set; } -} \ No newline at end of file diff --git a/Source/Starfish.Service/Application/Commands/SettingNodePublishCommand.cs b/Source/Starfish.Service/Application/Commands/SettingPublishCommand.cs similarity index 66% rename from Source/Starfish.Service/Application/Commands/SettingNodePublishCommand.cs rename to Source/Starfish.Service/Application/Commands/SettingPublishCommand.cs index f1a9272..7d230fc 100644 --- a/Source/Starfish.Service/Application/Commands/SettingNodePublishCommand.cs +++ b/Source/Starfish.Service/Application/Commands/SettingPublishCommand.cs @@ -5,9 +5,9 @@ namespace Nerosoft.Starfish.Application; /// /// 配置节点发布命令 /// -public class SettingNodePublishCommand : Command +public class SettingPublishCommand : Command { - public SettingNodePublishCommand(long id) + public SettingPublishCommand(long id) { Id = id; } diff --git a/Source/Starfish.Service/Application/Commands/SettingRevisionCreateCommand.cs b/Source/Starfish.Service/Application/Commands/SettingRevisionCreateCommand.cs index fdcaf97..b91cde7 100644 --- a/Source/Starfish.Service/Application/Commands/SettingRevisionCreateCommand.cs +++ b/Source/Starfish.Service/Application/Commands/SettingRevisionCreateCommand.cs @@ -10,7 +10,7 @@ public class SettingRevisionCreateCommand : Command /// /// 配置根节点Id /// - public long RootId { get; set; } + public long SettingId { get; set; } /// /// 说明 diff --git a/Source/Starfish.Service/Application/Contracts/ISettingApplicationService.cs b/Source/Starfish.Service/Application/Contracts/ISettingApplicationService.cs index ab3e7be..8aedd4e 100644 --- a/Source/Starfish.Service/Application/Contracts/ISettingApplicationService.cs +++ b/Source/Starfish.Service/Application/Contracts/ISettingApplicationService.cs @@ -1,5 +1,4 @@ using Nerosoft.Euonia.Application; -using Nerosoft.Starfish.Domain; using Nerosoft.Starfish.Transit; namespace Nerosoft.Starfish.Application; @@ -17,7 +16,7 @@ public interface ISettingApplicationService : IApplicationService /// /// /// - Task> SearchAsync(SettingNodeCriteria criteria, int page, int size, CancellationToken cancellationToken = default); + Task> SearchAsync(SettingCriteria criteria, int page, int size, CancellationToken cancellationToken = default); /// /// 获取符合条件的配置数量 @@ -25,7 +24,7 @@ public interface ISettingApplicationService : IApplicationService /// /// /// - Task CountAsync(SettingNodeCriteria criteria, CancellationToken cancellationToken = default); + Task CountAsync(SettingCriteria criteria, CancellationToken cancellationToken = default); /// /// 获取配置详情 @@ -33,53 +32,15 @@ public interface ISettingApplicationService : IApplicationService /// /// /// - Task GetAsync(long id, CancellationToken cancellationToken = default); + Task GetAsync(long id, CancellationToken cancellationToken = default); /// - /// 新增根节点 + /// 新建配置 /// - /// 应用Id - /// 环境名称 - /// - /// - Task CreateRootNodeAsync(long appId, string environment, CancellationToken cancellationToken = default); - - /// - /// 新增子节点 - /// - /// - /// /// /// /// - Task CreateLeafNodeAsync(long parentId, SettingNodeType type, SettingNodeCreateDto data, CancellationToken cancellationToken = default); - - /// - /// 更新配置节点 - /// - /// - /// - /// - /// - Task UpdateValueAsync(long id, string value, CancellationToken cancellationToken = default); - - /// - /// 重命名节点 - /// - /// - /// - /// - /// - Task UpdateNameAsync(long id, string name, CancellationToken cancellationToken = default); - - /// - /// 更新节点描述 - /// - /// - /// - /// - /// - Task UpdateDescriptionAsync(long id, string description, CancellationToken cancellationToken = default); + Task CreateAsync(SettingCreateDto data, CancellationToken cancellationToken = default); /// /// 删除节点 @@ -96,7 +57,7 @@ public interface ISettingApplicationService : IApplicationService /// /// /// - Task PublishAsync(long id, SettingNodePublishDto data, CancellationToken cancellationToken = default); + Task PublishAsync(long id, SettingPublishDto data, CancellationToken cancellationToken = default); /// /// 获取已发布的配置 diff --git a/Source/Starfish.Service/Application/Handlers/SettingArchiveCommandHandler.cs b/Source/Starfish.Service/Application/Handlers/SettingArchiveCommandHandler.cs index d338a0d..995b9fe 100644 --- a/Source/Starfish.Service/Application/Handlers/SettingArchiveCommandHandler.cs +++ b/Source/Starfish.Service/Application/Handlers/SettingArchiveCommandHandler.cs @@ -1,10 +1,8 @@ using System.IO.Compression; using Nerosoft.Euonia.Bus; using Nerosoft.Euonia.Business; -using Nerosoft.Euonia.Linq; using Nerosoft.Euonia.Repository; using Nerosoft.Starfish.Domain; -using Nerosoft.Starfish.Repository; using Nerosoft.Starfish.Service; using Newtonsoft.Json; @@ -32,37 +30,18 @@ public Task HandleAsync(SettingArchiveCreateCommand message, MessageContext cont }); } - private async Task>> GetNodesAsync(long rootId, CancellationToken cancellationToken = default) + private async Task>> GetNodesAsync(long id, CancellationToken cancellationToken = default) { - var repository = UnitOfWork.Current.GetService(); + var repository = UnitOfWork.Current.GetService(); - var root = await repository.GetAsync(rootId, false, [], cancellationToken); + var aggregate = await repository.GetAsync(id, false, [nameof(Setting.Nodes)], cancellationToken); - if (root == null) + if (aggregate == null) { - throw new SettingNodeNotFoundException(rootId); + throw new SettingNotFoundException(id); } - var types = new[] - { - SettingNodeType.String, - SettingNodeType.Boolean, - SettingNodeType.Number, - SettingNodeType.Referer - }; - - ISpecification[] specifications = - { - SettingNodeSpecification.AppIdEquals(root.AppId), - SettingNodeSpecification.EnvironmentEquals(root.Environment), - SettingNodeSpecification.TypeIn(types) - }; - - var predicate = new CompositeSpecification(PredicateOperator.AndAlso, specifications).Satisfy(); - - var leaves = await repository.FindAsync(predicate, false, [], cancellationToken); - - return Tuple.Create(root.AppId, root.AppCode, root.Environment, leaves); + return Tuple.Create(aggregate.AppId, aggregate.AppCode, aggregate.Environment, aggregate.Nodes.ToList()); } private async Task SaveArchiveAsync(long appId, string appCode, string environment, string data, string userName, CancellationToken cancellationToken = default) diff --git a/Source/Starfish.Service/Application/Handlers/SettingCommandHandler.cs b/Source/Starfish.Service/Application/Handlers/SettingCommandHandler.cs new file mode 100644 index 0000000..35fd534 --- /dev/null +++ b/Source/Starfish.Service/Application/Handlers/SettingCommandHandler.cs @@ -0,0 +1,54 @@ +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 SettingCommandHandler : CommandHandlerBase, + IHandler, + IHandler, + IHandler +{ + public SettingCommandHandler(IUnitOfWorkManager unitOfWork, IObjectFactory factory) + : base(unitOfWork, factory) + { + } + + public Task HandleAsync(SettingCreateCommand message, MessageContext context, CancellationToken cancellationToken = default) + { + return ExecuteAsync(async () => + { + var business = await Factory.CreateAsync(cancellationToken); + business.AppId = message.AppId; + business.Environment = message.Environment; + business.Description = message.Description; + var _ = await business.SaveAsync(false, cancellationToken); + return business.Id; + }, context.Response); + } + + /// + public Task HandleAsync(SettingDeleteCommand message, MessageContext context, CancellationToken cancellationToken = default) + { + return ExecuteAsync(async () => + { + var business = await Factory.FetchAsync(message.Item1, cancellationToken); + business.MarkAsDelete(); + await business.SaveAsync(false, cancellationToken); + }); + } + + /// + public Task HandleAsync(SettingPublishCommand message, MessageContext context, CancellationToken cancellationToken = default) + { + return ExecuteAsync(async () => + { + await Factory.ExecuteAsync(message.Id, cancellationToken); + }); + } +} \ No newline at end of file diff --git a/Source/Starfish.Service/Application/Handlers/SettingNodeCommandHandler.cs b/Source/Starfish.Service/Application/Handlers/SettingNodeCommandHandler.cs deleted file mode 100644 index 059f45a..0000000 --- a/Source/Starfish.Service/Application/Handlers/SettingNodeCommandHandler.cs +++ /dev/null @@ -1,187 +0,0 @@ -using Nerosoft.Euonia.Bus; -using Nerosoft.Euonia.Business; -using Nerosoft.Euonia.Linq; -using Nerosoft.Euonia.Repository; -using Nerosoft.Starfish.Domain; -using Nerosoft.Starfish.Repository; -using Nerosoft.Starfish.Service; - -namespace Nerosoft.Starfish.Application; - -/// -/// 应用配置命令处理器 -/// -public class SettingNodeCommandHandler : CommandHandlerBase, - IHandler, - IHandler, - IHandler, - IHandler, - IHandler, - IHandler -{ - private readonly IServiceProvider _provider; - - private ISettingNodeRepository _settingRepository; - private ISettingNodeRepository SettingRepository => _settingRepository ??= _provider.GetService(); - - private IAppInfoRepository _appInfoRepository; - private IAppInfoRepository AppInfoRepository => _appInfoRepository ??= _provider.GetService(); - - public SettingNodeCommandHandler(IUnitOfWorkManager unitOfWork, IObjectFactory factory, IServiceProvider provider) - : base(unitOfWork, factory) - { - _provider = provider; - } - - /// - public Task HandleAsync(SettingRootNodeCreateCommand message, MessageContext context, CancellationToken cancellationToken = default) - { - return ExecuteAsync(async () => - { - var appinfo = await AppInfoRepository.GetAsync(message.AppId, cancellationToken); - if (appinfo == null) - { - throw new AppInfoNotFoundException(message.AppId); - } - - var exists = await SettingRepository.ExistsAsync(message.AppId, message.Environment, cancellationToken); - if (exists) - { - throw new ConflictException(Resources.IDS_ERROR_SETTING_NODE_DUPLICATE_ROOT); - } - - var entity = SettingNode.CreateRoot(appinfo.Id, appinfo.Code, message.Environment); - await SettingRepository.InsertAsync(entity, true, cancellationToken); - return entity.Id; - }, context.Response); - } - - /// - public Task HandleAsync(SettingLeafNodeCreateCommand message, MessageContext context, CancellationToken cancellationToken = default) - { - return ExecuteAsync(async () => - { - var aggregate = await SettingRepository.GetAsync(message.Id, cancellationToken); - if (aggregate == null) - { - throw new SettingNodeNotFoundException(message.Id); - } - - SettingNode entity; - switch (message.Type) - { - case SettingNodeType.Array: - aggregate.AddArrayNode(message.Data.Name, out entity); - break; - case SettingNodeType.Object: - aggregate.AddObjectNode(message.Data.Name, out entity); - break; - case SettingNodeType.String: - aggregate.AddStringNode(message.Data.Name, message.Data.Value, out entity); - break; - case SettingNodeType.Number: - aggregate.AddNumberNode(message.Data.Name, message.Data.Value, out entity); - break; - case SettingNodeType.Boolean: - aggregate.AddBooleanNode(message.Data.Name, message.Data.Value, out entity); - break; - case SettingNodeType.Root: - throw new InvalidOperationException(Resources.IDS_ERROR_NOT_ALLOW_ADD_ROOT_IN_CHILD_NODE); - case SettingNodeType.Referer: - default: - throw new ArgumentOutOfRangeException(nameof(message.Type), message.Type, null); - } - - await SettingRepository.UpdateAsync(aggregate, true, cancellationToken); - return entity?.Id ?? 0; - }, context.Response); - } - - /// - public Task HandleAsync(SettingNodeUpdateCommand message, MessageContext context, CancellationToken cancellationToken = default) - { - return ExecuteAsync(async () => - { - var business = await Factory.FetchAsync(message.Id, cancellationToken); - business.Intent = message.Intent; - business.Value = message.Value; - business.MarkAsUpdate(); - await business.SaveAsync(true, cancellationToken); - }); - } - - /// - public Task HandleAsync(SettingNodeDeleteCommand message, MessageContext context, CancellationToken cancellationToken = default) - { - return ExecuteAsync(async () => - { - var aggregate = await SettingRepository.GetAsync(message.Item1, cancellationToken); - if (aggregate == null) - { - throw new SettingNodeNotFoundException(message.Item1); - } - - List nodes = [aggregate]; - - var specifications = new List>(); - - switch (aggregate.Type) - { - case SettingNodeType.Root: - { - var types = new[] - { - SettingNodeType.Array, - SettingNodeType.Object, - SettingNodeType.String, - SettingNodeType.Boolean, - SettingNodeType.Number, - SettingNodeType.Referer - }; - specifications.Add(SettingNodeSpecification.AppIdEquals(aggregate.AppId)); - specifications.Add(SettingNodeSpecification.EnvironmentEquals(aggregate.Environment)); - specifications.Add(SettingNodeSpecification.TypeIn(types)); - } - break; - case SettingNodeType.Object: - case SettingNodeType.Array: - { - specifications.Add(SettingNodeSpecification.AppIdEquals(aggregate.AppId)); - specifications.Add(SettingNodeSpecification.EnvironmentEquals(aggregate.Environment)); - specifications.Add(SettingNodeSpecification.KeyStartsWith(aggregate.Key)); - } - break; - } - - if (specifications.Count > 0) - { - var predicate = new CompositeSpecification(PredicateOperator.AndAlso, specifications.ToArray()).Satisfy(); - - var leaves = await SettingRepository.FindAsync(predicate, true, [], cancellationToken); - nodes.AddRange(leaves); - } - - await SettingRepository.DeleteAsync(nodes, true, cancellationToken); - }); - } - - /// - public Task HandleAsync(SettingNodePublishCommand message, MessageContext context, CancellationToken cancellationToken = default) - { - return ExecuteAsync(async () => - { - await Factory.ExecuteAsync(message.Id, cancellationToken); - }); - } - - /// - public Task HandleAsync(SettingNodeSetKeyCommand message, MessageContext context, CancellationToken cancellationToken = default) - { - return ExecuteAsync(async () => - { - var business = await Factory.CreateAsync(message.Item1, message.Item2, message.Item3); - business.MarkAsUpdate(); - await business.SaveAsync(true, cancellationToken); - }); - } -} \ No newline at end of file diff --git a/Source/Starfish.Service/Application/Handlers/SettingRevisionCommandHandler.cs b/Source/Starfish.Service/Application/Handlers/SettingRevisionCommandHandler.cs index 1397824..85ca7cc 100644 --- a/Source/Starfish.Service/Application/Handlers/SettingRevisionCommandHandler.cs +++ b/Source/Starfish.Service/Application/Handlers/SettingRevisionCommandHandler.cs @@ -1,9 +1,7 @@ using Nerosoft.Euonia.Bus; using Nerosoft.Euonia.Business; -using Nerosoft.Euonia.Linq; using Nerosoft.Euonia.Repository; using Nerosoft.Starfish.Domain; -using Nerosoft.Starfish.Repository; using Nerosoft.Starfish.Service; using Newtonsoft.Json; @@ -21,13 +19,11 @@ public Task HandleAsync(SettingRevisionCreateCommand message, MessageContext con { return ExecuteAsync(async () => { - var (appId, appCode, environment, nodes) = await GetNodesAsync(message.RootId, cancellationToken); + var nodes = await GetNodesAsync(message.SettingId, cancellationToken); var entity = new SettingRevision { - AppId = appId, - AppCode = appCode, - Environment = environment, + SettingId = message.SettingId, Version = message.Version, Data = JsonConvert.SerializeObject(nodes, new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore }), Operator = context?.User?.Identity?.Name, @@ -38,28 +34,18 @@ public Task HandleAsync(SettingRevisionCreateCommand message, MessageContext con }); } - private async Task>> GetNodesAsync(long rootId, CancellationToken cancellationToken = default) + private async Task> GetNodesAsync(long id, CancellationToken cancellationToken = default) { - var repository = UnitOfWork.Current.GetService(); + var repository = UnitOfWork.Current.GetService(); - var root = await repository.GetAsync(rootId, false, [], cancellationToken); + var aggregate = await repository.GetAsync(id, false, [nameof(Setting.Nodes)], cancellationToken); - if (root == null) + if (aggregate == null) { - throw new SettingNodeNotFoundException(rootId); + throw new SettingNotFoundException(id); } - ISpecification[] specifications = - { - SettingNodeSpecification.AppIdEquals(root.AppId), - SettingNodeSpecification.EnvironmentEquals(root.Environment), - }; - - var predicate = new CompositeSpecification(PredicateOperator.AndAlso, specifications).Satisfy(); - - var leaves = await repository.FindAsync(predicate, false, Array.Empty(), cancellationToken); - - return Tuple.Create(root.AppId, root.AppCode, root.Environment, leaves); + return aggregate.Nodes.ToList(); } private Task SaveRevisionAsync(SettingRevision entity, CancellationToken cancellationToken = default) diff --git a/Source/Starfish.Service/Application/Handlers/UserCommandHandler.cs b/Source/Starfish.Service/Application/Handlers/UserCommandHandler.cs index 6525536..7e27855 100644 --- a/Source/Starfish.Service/Application/Handlers/UserCommandHandler.cs +++ b/Source/Starfish.Service/Application/Handlers/UserCommandHandler.cs @@ -83,7 +83,7 @@ public Task HandleAsync(UserDeleteCommand message, MessageContext context, Cance } /// - public Task HandleAsync(UserSetRoleCommand message, MessageContext context, CancellationToken cancellationToken = new CancellationToken()) + public Task HandleAsync(UserSetRoleCommand message, MessageContext context, CancellationToken cancellationToken = default) { return ExecuteAsync(async () => { diff --git a/Source/Starfish.Service/Application/Implements/SettingApplicationService.cs b/Source/Starfish.Service/Application/Implements/SettingApplicationService.cs index 59a5348..3ca96d2 100644 --- a/Source/Starfish.Service/Application/Implements/SettingApplicationService.cs +++ b/Source/Starfish.Service/Application/Implements/SettingApplicationService.cs @@ -11,87 +11,50 @@ namespace Nerosoft.Starfish.Application; public class SettingApplicationService : BaseApplicationService, ISettingApplicationService { /// - public Task> SearchAsync(SettingNodeCriteria criteria, int page, int size, CancellationToken cancellationToken = default) + public Task> SearchAsync(SettingCriteria criteria, int page, int size, CancellationToken cancellationToken = default) { - var useCase = LazyServiceProvider.GetRequiredService(); - var input = new SettingNodeSearchInput(criteria, page, size); + var useCase = LazyServiceProvider.GetRequiredService(); + var input = new SettingSearchInput(criteria, page, size); return useCase.ExecuteAsync(input, cancellationToken) .ContinueWith(t => t.Result.Result, cancellationToken); } /// - public Task CountAsync(SettingNodeCriteria criteria, CancellationToken cancellationToken = default) + public Task CountAsync(SettingCriteria criteria, CancellationToken cancellationToken = default) { - var useCase = LazyServiceProvider.GetRequiredService(); - var input = new SettingNodeCountInput(criteria); + var useCase = LazyServiceProvider.GetRequiredService(); + var input = new SettingCountInput(criteria); return useCase.ExecuteAsync(input, cancellationToken) .ContinueWith(t => t.Result.Result, cancellationToken); } /// - public Task GetAsync(long id, CancellationToken cancellationToken = default) + public Task GetAsync(long id, CancellationToken cancellationToken = default) { - var useCase = LazyServiceProvider.GetRequiredService(); - var input = new SettingNodeGetDetailInput(id); + var useCase = LazyServiceProvider.GetRequiredService(); + var input = new SettingGetDetailInput(id); return useCase.ExecuteAsync(input, cancellationToken) .ContinueWith(t => t.Result.Result, cancellationToken); } - /// - public Task CreateRootNodeAsync(long appId, string environment, CancellationToken cancellationToken = default) - { - var useCase = LazyServiceProvider.GetRequiredService(); - var input = new SettingRootNodeCreationInput(appId, environment); - return useCase.ExecuteAsync(input, cancellationToken) - .ContinueWith(t => t.Result.Id, cancellationToken); - } - - /// - public Task CreateLeafNodeAsync(long parentId, SettingNodeType type, SettingNodeCreateDto data, CancellationToken cancellationToken = default) - { - var useCase = LazyServiceProvider.GetRequiredService(); - var input = new SettingLeafNodeCreationInput(parentId, type, data); - return useCase.ExecuteAsync(input, cancellationToken) - .ContinueWith(t => t.Result.Result, cancellationToken); - } - - /// - public Task UpdateValueAsync(long id, string value, CancellationToken cancellationToken = default) + public async Task CreateAsync(SettingCreateDto data, CancellationToken cancellationToken = default) { - var useCase = LazyServiceProvider.GetRequiredService(); - var input = new SettingNodeUpdateValueInput(id, value); - return useCase.ExecuteAsync(input, cancellationToken); - } - - /// - public Task UpdateNameAsync(long id, string name, CancellationToken cancellationToken = default) - { - var useCase = LazyServiceProvider.GetRequiredService(); - var input = new SettingNodeUpdateNameInput(id, name); - return useCase.ExecuteAsync(input, cancellationToken); - } - - /// - public Task UpdateDescriptionAsync(long id, string description, CancellationToken cancellationToken = default) - { - var useCase = LazyServiceProvider.GetRequiredService(); - var input = new SettingNodeUpdateDescriptionInput(id, description); - return useCase.ExecuteAsync(input, cancellationToken); + throw new NotImplementedException(); } /// public Task DeleteAsync(long id, CancellationToken cancellationToken = default) { - var useCase = LazyServiceProvider.GetRequiredService(); - var input = new SettingNodeDeleteInput(id); + var useCase = LazyServiceProvider.GetRequiredService(); + var input = new SettingDeleteInput(id); return useCase.ExecuteAsync(input, cancellationToken); } /// - public Task PublishAsync(long id, SettingNodePublishDto data, CancellationToken cancellationToken = default) + public Task PublishAsync(long id, SettingPublishDto data, CancellationToken cancellationToken = default) { - var useCase = LazyServiceProvider.GetRequiredService(); - var input = new SettingNodePublishInput(id, data); + var useCase = LazyServiceProvider.GetRequiredService(); + var input = new SettingPublishInput(id, data); return useCase.ExecuteAsync(input, cancellationToken); } diff --git a/Source/Starfish.Service/Application/JsonConfigurationFileParser.cs b/Source/Starfish.Service/Application/JsonConfigurationFileParser.cs index 1ff9173..2b41b08 100644 --- a/Source/Starfish.Service/Application/JsonConfigurationFileParser.cs +++ b/Source/Starfish.Service/Application/JsonConfigurationFileParser.cs @@ -14,6 +14,14 @@ private JsonConfigurationFileParser() private readonly Dictionary _data = new(StringComparer.OrdinalIgnoreCase); private readonly Stack _paths = new(); + public static IDictionary Parse(string json) + { + using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(json))) + { + return Parse(stream); + } + } + public static IDictionary Parse(Stream input) => new JsonConfigurationFileParser().ParseStream(input); private Dictionary ParseStream(Stream input) diff --git a/Source/Starfish.Service/Application/Mappings/SettingMappingProfile.cs b/Source/Starfish.Service/Application/Mappings/SettingMappingProfile.cs index 138948e..3cd7b4d 100644 --- a/Source/Starfish.Service/Application/Mappings/SettingMappingProfile.cs +++ b/Source/Starfish.Service/Application/Mappings/SettingMappingProfile.cs @@ -12,26 +12,22 @@ public class SettingMappingProfile : Profile /// public SettingMappingProfile() { - CreateMap() - .ForMember(dest => dest.TypeDescription, options => options.MapFrom(src => GetTypeDescription(src.Type))) - .ForMember(dest => dest.IsRoot, options => options.MapFrom(src => src.Type == SettingNodeType.Root)); - CreateMap() - .ForMember(dest => dest.TypeDescription, options => options.MapFrom(src => GetTypeDescription(src.Type))) - .ForMember(dest => dest.IsRoot, options => options.MapFrom(src => src.Type == SettingNodeType.Root)); + CreateMap() + .ForMember(dest => dest.StatusDescription, options => options.MapFrom(src => GetStatusDescription(src.Status))) + .ForMember(dest => dest.AppName, options => options.MapFrom(src => src.App.Name)); + CreateMap() + .ForMember(dest => dest.StatusDescription, options => options.MapFrom(src => GetStatusDescription(src.Status))) + .ForMember(dest => dest.AppName, options => options.MapFrom(src => src.App.Name)); } - private static string GetTypeDescription(SettingNodeType type) + private static string GetStatusDescription(SettingStatus status) { - return type switch + return status switch { - SettingNodeType.Root => Resources.IDS_ENUM_SETTING_NODE_TYPE_ROOT, - SettingNodeType.Object => Resources.IDS_ENUM_SETTING_NODE_TYPE_OBJECT, - SettingNodeType.Array => Resources.IDS_ENUM_SETTING_NODE_TYPE_ARRAY, - SettingNodeType.String => Resources.IDS_ENUM_SETTING_NODE_TYPE_STRING, - SettingNodeType.Number => Resources.IDS_ENUM_SETTING_NODE_TYPE_NUMBER, - SettingNodeType.Boolean => Resources.IDS_ENUM_SETTING_NODE_TYPE_BOOLEAN, - SettingNodeType.Referer => Resources.IDS_ENUM_SETTING_NODE_TYPE_REFERER, - _ => type.ToString() + SettingStatus.Disabled => Resources.IDS_ENUM_SETTING_STATUS_DISABLED, + SettingStatus.Pending => Resources.IDS_ENUM_SETTING_STATUS_PENDING, + SettingStatus.Published => Resources.IDS_ENUM_SETTING_STATUS_PUBLISHED, + _ => status.ToString() }; } } \ No newline at end of file diff --git a/Source/Starfish.Service/Application/Subscribers/LoggingEventSubscriber.cs b/Source/Starfish.Service/Application/Subscribers/LoggingEventSubscriber.cs index 292456e..c9887ca 100644 --- a/Source/Starfish.Service/Application/Subscribers/LoggingEventSubscriber.cs +++ b/Source/Starfish.Service/Application/Subscribers/LoggingEventSubscriber.cs @@ -10,7 +10,7 @@ public sealed class LoggingEventSubscriber { private readonly IBus _bus; private readonly IServiceProvider _provider; - + public LoggingEventSubscriber(IBus bus, IServiceProvider provider) { _bus = bus; @@ -141,9 +141,9 @@ public Task HandleAsync(AppInfoDisableEvent message, MessageContext context, Can /// /// [Subscribe] - public Task HandleAsync(SettingNodeCreatedEvent @event, MessageContext context, CancellationToken cancellationToken = default) + public Task HandleAsync(SettingCreatedEvent @event, MessageContext context, CancellationToken cancellationToken = default) { - var description = $"创建配置节点({@event.Node.Type}) {@event.Node.Name}, AppId: {@event.Node.AppId}, AppCode: {@event.Node.AppCode}, Environment: {@event.Node.Environment}"; + var description = $"创建配置节点, App: {@event.Setting.AppCode}, Environment: {@event.Setting.Environment}"; var command = new OperateLogCreateCommand { @@ -165,10 +165,10 @@ public Task HandleAsync(SettingNodeCreatedEvent @event, MessageContext context, /// /// [Subscribe] - public Task HandleAsync(SettingNodeDeletedEvent @event, MessageContext context, CancellationToken cancellationToken = default) + public Task HandleAsync(SettingDeletedEvent @event, MessageContext context, CancellationToken cancellationToken = default) { - var aggregate = @event.GetAggregate(); - var description = $"删除配置节点({aggregate.Type}) {aggregate.Name}, AppId: {aggregate.AppId}, AppCode: {aggregate.AppCode}, Environment: {aggregate.Environment}"; + var aggregate = @event.GetAggregate(); + var description = $"删除配置节点({aggregate.Id}), App: {aggregate.AppCode}, Environment: {aggregate.Environment}"; var command = new OperateLogCreateCommand { @@ -182,39 +182,16 @@ public Task HandleAsync(SettingNodeDeletedEvent @event, MessageContext context, return _bus.SendAsync(command, new SendOptions { RequestTraceId = context.RequestTraceId }, null, cancellationToken); } - /// - /// 处理配置节点重命名事件 - /// - /// - /// - /// - /// - [Subscribe] - public Task HandleAsync(SettingNodeRenamedEvent @event, MessageContext context, CancellationToken cancellationToken = default) - { - var aggregate = @event.GetAggregate(); - var command = new OperateLogCreateCommand - { - Module = "setting", - Type = "rename", - Description = $"节点{@event.OldName}重命名为{@event.NewName}, AppId: {aggregate.AppId}, AppCode: {aggregate.AppCode}, Environment: {aggregate.Environment}", - OperateTime = DateTime.Now, - RequestTraceId = context.RequestTraceId, - UserName = context.User?.Identity?.Name - }; - return _bus.SendAsync(command, new SendOptions { RequestTraceId = context.RequestTraceId }, null, cancellationToken); - } - [Subscribe] public async Task HandleAsync(SettingPublishedEvent @event, MessageContext context, CancellationToken cancellationToken = default) { - var repository = _provider.GetService(); - var node = await repository.GetAsync(@event.Id, false, [], cancellationToken); + var repository = _provider.GetService(); + var setting = await repository.GetAsync(@event.Id, false, [], cancellationToken); var command = new OperateLogCreateCommand { Module = "setting", Type = "publish", - Description = $"发布配置({@event.Version}),AppId: {node.AppId}, AppCode: {node.AppCode}, Environment: {node.Environment}", + Description = $"发布配置({@event.Version}),App: {setting.AppCode}, Environment: {setting.Environment}", OperateTime = DateTime.Now, RequestTraceId = context.RequestTraceId, UserName = context.User?.Identity?.Name diff --git a/Source/Starfish.Service/Application/Subscribers/SettingEventSubscriber.cs b/Source/Starfish.Service/Application/Subscribers/SettingEventSubscriber.cs new file mode 100644 index 0000000..9034ce0 --- /dev/null +++ b/Source/Starfish.Service/Application/Subscribers/SettingEventSubscriber.cs @@ -0,0 +1,14 @@ +using Nerosoft.Euonia.Bus; +using Nerosoft.Starfish.Domain; + +namespace Nerosoft.Starfish.Application; + +public class SettingEventSubscriber +{ + private readonly IBus _bus; + + public SettingEventSubscriber(IBus bus) + { + _bus = bus; + } +} \ No newline at end of file diff --git a/Source/Starfish.Service/Application/Subscribers/SettingNodeEventSubscriber.cs b/Source/Starfish.Service/Application/Subscribers/SettingNodeEventSubscriber.cs deleted file mode 100644 index 011c213..0000000 --- a/Source/Starfish.Service/Application/Subscribers/SettingNodeEventSubscriber.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Nerosoft.Euonia.Bus; -using Nerosoft.Starfish.Domain; - -namespace Nerosoft.Starfish.Application; - -public class SettingNodeEventSubscriber : IHandler -{ - private readonly IBus _bus; - - public SettingNodeEventSubscriber(IBus bus) - { - _bus = bus; - } - - /// - public Task HandleAsync(SettingNodeRenamedEvent message, MessageContext context, CancellationToken cancellationToken = default) - { - var command = new SettingNodeSetKeyCommand(message.Id, message.OldName, message.NewName); - return _bus.SendAsync(command, new SendOptions { RequestTraceId = context.RequestTraceId }, null, cancellationToken); - } -} \ No newline at end of file diff --git a/Source/Starfish.Service/Application/Subscribers/SettingRevisionEventSubscriber.cs b/Source/Starfish.Service/Application/Subscribers/SettingRevisionEventSubscriber.cs index eb5e318..f677176 100644 --- a/Source/Starfish.Service/Application/Subscribers/SettingRevisionEventSubscriber.cs +++ b/Source/Starfish.Service/Application/Subscribers/SettingRevisionEventSubscriber.cs @@ -26,7 +26,7 @@ public Task HandleAsync(SettingPublishedEvent message, MessageContext context, C { var command = new SettingRevisionCreateCommand { - RootId = message.Id, + SettingId = message.Id, Version = message.Version, Comment = message.Comment }; diff --git a/Source/Starfish.Service/Application/TextConfigurationFileParser.cs b/Source/Starfish.Service/Application/TextConfigurationFileParser.cs new file mode 100644 index 0000000..2bf5e08 --- /dev/null +++ b/Source/Starfish.Service/Application/TextConfigurationFileParser.cs @@ -0,0 +1,55 @@ +namespace Nerosoft.Starfish.Application; + +public class TextConfigurationFileParser +{ + private TextConfigurationFileParser() + { + } + + private readonly Dictionary _data = new(StringComparer.OrdinalIgnoreCase); + private readonly Stack _paths = new(); + + public static IDictionary Parse(string json) + { + using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(json))) + { + return Parse(stream); + } + } + + public static IDictionary Parse(Stream input) => new TextConfigurationFileParser().ParseStream(input); + + private Dictionary ParseStream(Stream input) + { + var data = new Dictionary(); + + using (var reader = new StreamReader(input)) + { + while (reader.Peek() > 0) + { + var line = reader.ReadLine(); + if (string.IsNullOrWhiteSpace(line)) + { + continue; + } + + var index = line.IndexOf('='); + if (index == -1) + { + continue; + } + + var key = line[..index].Trim(); + var value = line[(index + 1)..]; + if (string.IsNullOrWhiteSpace(key)) + { + continue; + } + + data[key] = value; + } + } + + return data; + } +} \ No newline at end of file diff --git a/Source/Starfish.Service/Domain/Aggregates/Setting.cs b/Source/Starfish.Service/Domain/Aggregates/Setting.cs new file mode 100644 index 0000000..a56517e --- /dev/null +++ b/Source/Starfish.Service/Domain/Aggregates/Setting.cs @@ -0,0 +1,125 @@ +using Nerosoft.Euonia.Domain; +using Nerosoft.Starfish.Service; + +namespace Nerosoft.Starfish.Domain; + +/// +/// 设置聚合根 +/// +public class Setting : Aggregate, IAuditing +{ + private Setting() + { + Register(@event => + { + Status = @event.NewStatus; + }); + } + + /// + /// 应用Id + /// + public long AppId { get; set; } + + /// + /// 应用编码 + /// + public string AppCode { get; set; } + + /// + /// 环境名称 + /// + public string Environment { get; set; } + + /// + /// 描述 + /// + public string Description { get; set; } + + /// + /// 状态 + /// + public SettingStatus Status { get; set; } + + public DateTime CreateTime { get; set; } + + public DateTime UpdateTime { get; set; } + + /// + /// 创建人账号 + /// + public string CreatedBy { get; set; } + + /// + /// 更新人账号 + /// + public string UpdatedBy { get; set; } + + /// + /// 设置项 + /// + public HashSet Nodes { get; set; } + + /// + /// 历史版本 + /// + public HashSet Revisions { get; set; } + + /// + /// 应用信息 + /// + public AppInfo App { get; set; } + + internal static Setting Create(long appId, string appCode, string environment, string description, IDictionary nodes) + { + var setting = new Setting + { + AppId = appId, + AppCode = appCode, + Environment = environment, + Status = SettingStatus.Pending, + Description = description, + Nodes = [] + }; + + foreach (var (key, value) in nodes) + { + setting.AddNode(key, value); + } + + setting.RaiseEvent(new SettingCreatedEvent(setting)); + return setting; + } + + internal void AddNode(string key, string value) + { + Nodes ??= []; + if (Nodes.Any(x => x.Key == key)) + { + throw new ConflictException(string.Format(Resources.IDS_ERROR_SETTING_NODE_KEY_EXISTS, key)); + } + + var node = new SettingNode(key, value); + Nodes.Add(node); + } + + internal void SetStatus(SettingStatus status) + { + if (Status == status) + { + return; + } + + RaiseEvent(new SettingStatusChangedEvent(Status, status)); + } + + internal void SetDescription(string description) + { + if (string.Equals(Description, description)) + { + return; + } + + Description = description; + } +} \ No newline at end of file diff --git a/Source/Starfish.Service/Domain/Aggregates/SettingNode.cs b/Source/Starfish.Service/Domain/Aggregates/SettingNode.cs index 2c36be0..3020a50 100644 --- a/Source/Starfish.Service/Domain/Aggregates/SettingNode.cs +++ b/Source/Starfish.Service/Domain/Aggregates/SettingNode.cs @@ -7,371 +7,24 @@ namespace Nerosoft.Starfish.Domain; /// /// 配置信息 /// -public class SettingNode : Aggregate, - IHasCreateTime, - IHasUpdateTime +public class SettingNode : Entity { - private readonly SettingNodeType[] _sealedTypes = - [ - SettingNodeType.String, - SettingNodeType.Number, - SettingNodeType.Boolean, - SettingNodeType.Referer - ]; - private SettingNode() { } - /// - /// 根节点Id - /// - public long RootId { get; set; } - - /// - /// 父节点Id - /// - /// - /// 0表示根节点 - /// - public long ParentId { get; set; } - - /// - /// 应用Id - /// - public long AppId { get; set; } - - /// - /// 应用唯一编码 - /// - public string AppCode { get; set; } - - /// - /// 环境名称 - /// - public string Environment { get; set; } - - /// - /// 配置名称 - /// - public string Name { get; set; } - - /// - /// 配置值 - /// - public string Value { get; set; } - - /// - /// 节点唯一键 - /// - public string Key { get; set; } - - /// - /// 配置描述 - /// - public string Description { get; set; } - - /// - /// 配置节点类型 - /// - public SettingNodeType Type { get; set; } - - /// - /// 配置节点排序 - /// - public int Sort { get; set; } - - /// - /// 配置节点父节点 - /// - public virtual SettingNode Parent { get; set; } - - /// - /// 配置节点子节点 - /// - public virtual HashSet Children { get; set; } - - /// - /// 配置节点所属应用 - /// - public virtual AppInfo App { get; set; } - - /// - /// 配置节点创建时间 - /// - public DateTime CreateTime { get; set; } - - /// - /// - /// - public DateTime UpdateTime { get; set; } - - internal static SettingNode CreateRoot(long appId, string appCode, string environment) - { - var entity = new SettingNode - { - AppId = appId, - AppCode = appCode, - Environment = environment, - Name = $"{appCode}-{environment}", - Type = SettingNodeType.Root - }; - entity.RaiseEvent(new SettingNodeCreatedEvent(entity)); - return entity; - } - - internal void AddArrayNode(string name, out SettingNode entity) - { - CheckSealed(); - - Children ??= []; - - entity = new SettingNode - { - RootId = Type == SettingNodeType.Root ? Id : RootId, - AppId = AppId, - AppCode = AppCode, - Environment = Environment, - ParentId = Id, - Type = SettingNodeType.Array, - Sort = Children.Count + 1, - Key = GenerateKey(name, Children.Count + 1) - }; - - if (Type != SettingNodeType.Array) - { - CheckName(name); - entity.Name = name; - } - - Children.Add(entity); - - RaiseEvent(new SettingNodeCreatedEvent(entity)); - } - - internal void AddObjectNode(string name, out SettingNode entity) - { - CheckSealed(); - - Children ??= []; - - entity = new SettingNode - { - RootId = Type == SettingNodeType.Root ? Id : RootId, - AppId = AppId, - AppCode = AppCode, - Environment = Environment, - ParentId = Id, - Type = SettingNodeType.Object, - Sort = Children.Count + 1, - Key = GenerateKey(name, Children.Count + 1) - }; - - if (Type != SettingNodeType.Array) - { - CheckName(name); - entity.Name = name; - } - - Children.Add(entity); - - RaiseEvent(new SettingNodeCreatedEvent(entity)); - } - - internal void AddStringNode(string name, string value, out SettingNode entity) - { - CheckSealed(); - - Children ??= []; - - entity = new SettingNode - { - RootId = Type == SettingNodeType.Root ? Id : RootId, - AppId = AppId, - AppCode = AppCode, - Environment = Environment, - ParentId = Id, - Value = value, - Type = SettingNodeType.String, - Sort = Children.Count + 1, - Key = GenerateKey(name, Children.Count + 1) - }; - - if (Type != SettingNodeType.Array) - { - CheckName(name); - entity.Name = name; - } - - Children.Add(entity); - - RaiseEvent(new SettingNodeCreatedEvent(entity)); - } - - internal void AddBooleanNode(string name, string value, out SettingNode entity) - { - CheckSealed(); - - Children ??= []; - - if (!bool.TryParse(value, out var result)) - { - throw new BadRequestException(Resources.IDS_ERROR_SETTING_NODE_VALUE_NOT_BOOLEAN); - } - - entity = new SettingNode - { - RootId = Type == SettingNodeType.Root ? Id : RootId, - AppId = AppId, - AppCode = AppCode, - Environment = Environment, - ParentId = Id, - Value = result.ToString(), - Type = SettingNodeType.Boolean, - Sort = Children.Count + 1, - Key = GenerateKey(name, Children.Count + 1) - }; - - if (Type != SettingNodeType.Array) - { - CheckName(name); - entity.Name = name; - } - - Children.Add(entity); - - RaiseEvent(new SettingNodeCreatedEvent(entity)); - } - - internal void AddNumberNode(string name, string value, out SettingNode entity) - { - CheckSealed(); - - Children ??= []; - - if (!value.IsDecimal()) - { - throw new BadRequestException(Resources.IDS_ERROR_SETTING_NODE_VALUE_NOT_NUMBER); - } - - entity = new SettingNode - { - RootId = Type == SettingNodeType.Root ? Id : RootId, - AppId = AppId, - AppCode = AppCode, - Environment = Environment, - ParentId = Id, - Value = value, - Type = SettingNodeType.Number, - Sort = Children.Count + 1, - Key = GenerateKey(name, Children.Count + 1) - }; - - if (Type != SettingNodeType.Array) - { - CheckName(name); - entity.Name = name; - } - - Children.Add(entity); - - RaiseEvent(new SettingNodeCreatedEvent(entity)); - } - - internal void SetName(string newName) - { - var oldName = Name; - Name = newName; - RaiseEvent(new SettingNodeRenamedEvent(Id, oldName, newName)); - } - - internal void SetKey(string key) + internal SettingNode(string key, string value) + : this() { - if (string.Equals(Key, key)) - { - return; - } - Key = key; - } - - internal void SetValue(string value) - { - if (!_sealedTypes.Contains(Type)) - { - throw new BadRequestException(string.Format(Resources.IDS_ERROR_SETTING_NODE_NOT_ALLOW_SET_VALUE, Type)); - } - - if (string.Equals(Value, value)) - { - return; - } - - if (!string.IsNullOrWhiteSpace(value)) - { - switch (Type) - { - case SettingNodeType.Number: - if (!value.IsDecimal()) - { - throw new BadRequestException(Resources.IDS_ERROR_SETTING_NODE_VALUE_NOT_NUMBER); - } - - Value = value; - break; - case SettingNodeType.Boolean: - if (!bool.TryParse(value, out var booleanValue)) - { - throw new BadRequestException(Resources.IDS_ERROR_SETTING_NODE_VALUE_NOT_BOOLEAN); - } - - value = booleanValue.ToString(); - break; - } - } - Value = value; } - internal void SetDescription(string description) - { - if (string.Equals(Description, description)) - { - return; - } - - Description = description; - } - - private void CheckName(string name) - { - if (string.IsNullOrWhiteSpace(name)) - { - throw new BadRequestException(Resources.IDS_ERROR_SETTING_NODE_NAME_REQUIRED); - } + public long SettingId { get; set; } - if (Children.Any(t => string.Equals(t.Name, name, StringComparison.OrdinalIgnoreCase))) - { - throw new ConflictException(Resources.IDS_ERROR_SETTING_NODE_NAME_EXISTS); - } - } + public string Key { get; set; } - private void CheckSealed() - { - if (_sealedTypes.Contains(Type)) - { - throw new InvalidOperationException("该节点类型不允许添加子节点"); - } - } + public string Value { get; set; } - internal string GenerateKey(string name, int sort) - { - return Type switch - { - SettingNodeType.Root => name, - SettingNodeType.Array => $"{Key}:{sort - 1}", - SettingNodeType.Object => $"{Key}:{name}", - _ => string.Empty - }; - } + public Setting Setting { get; set; } } \ No newline at end of file diff --git a/Source/Starfish.Service/Domain/Aggregates/SettingRevision.cs b/Source/Starfish.Service/Domain/Aggregates/SettingRevision.cs index c3ebb4f..e70d538 100644 --- a/Source/Starfish.Service/Domain/Aggregates/SettingRevision.cs +++ b/Source/Starfish.Service/Domain/Aggregates/SettingRevision.cs @@ -8,20 +8,7 @@ namespace Nerosoft.Starfish.Domain; public class SettingRevision : Entity, IHasCreateTime { - /// - /// 应用Id - /// - public long AppId { get; set; } - - /// - /// 应用唯一编码 - /// - public string AppCode { get; set; } - - /// - /// 环境 - /// - public string Environment { get; set; } + public long SettingId { get; set; } /// /// 配置数据 @@ -47,4 +34,9 @@ public class SettingRevision : Entity, /// 归档时间 /// public DateTime CreateTime { get; set; } + + /// + /// + /// + public Setting Setting { get; set; } } \ No newline at end of file diff --git a/Source/Starfish.Service/Domain/Business/SettingGeneralBusiness.cs b/Source/Starfish.Service/Domain/Business/SettingGeneralBusiness.cs new file mode 100644 index 0000000..1d0297e --- /dev/null +++ b/Source/Starfish.Service/Domain/Business/SettingGeneralBusiness.cs @@ -0,0 +1,137 @@ +using Nerosoft.Euonia.Business; +using Nerosoft.Starfish.Service; + +// ReSharper disable MemberCanBePrivate.Global + +namespace Nerosoft.Starfish.Domain; + +internal class SettingGeneralBusiness : EditableObjectBase +{ + [Inject] + public IAppInfoRepository AppInfoRepository { get; set; } + + [Inject] + public ISettingRepository SettingRepository { get; set; } + + internal Setting Aggregate { get; private set; } + + public static readonly PropertyInfo IdProperty = RegisterProperty(p => p.Id); + public static readonly PropertyInfo AppIdProperty = RegisterProperty(p => p.AppId); + public static readonly PropertyInfo EnvironmentProperty = RegisterProperty(p => p.Environment); + public static readonly PropertyInfo DescriptionProperty = RegisterProperty(p => p.Description); + public static readonly PropertyInfo StatusProperty = RegisterProperty(p => p.Status); + public static readonly PropertyInfo> NodesProperty = RegisterProperty>(p => p.Nodes); + + public long Id + { + get => ReadProperty(IdProperty); + set => LoadProperty(IdProperty, value); + } + + public long AppId + { + get => GetProperty(AppIdProperty); + set => SetProperty(AppIdProperty, value); + } + + public string Environment + { + get => GetProperty(EnvironmentProperty); + set => SetProperty(EnvironmentProperty, value); + } + + public string Description + { + get => GetProperty(DescriptionProperty); + set => SetProperty(DescriptionProperty, value); + } + + public SettingStatus Status + { + get => GetProperty(StatusProperty); + set => SetProperty(StatusProperty, value); + } + + public IDictionary Nodes + { + get => GetProperty(NodesProperty); + set => SetProperty(NodesProperty, value); + } + + protected override void AddRules() + { + Rules.AddRule(new DuplicateCheckRule()); + } + + [FactoryCreate] + protected override async Task CreateAsync(CancellationToken cancellationToken = default) + { + await Task.CompletedTask; + } + + [FactoryFetch] + protected async Task FetchAsync(long id, CancellationToken cancellationToken = default) + { + var aggregate = await SettingRepository.GetAsync(id, false, Array.Empty(), cancellationToken); + + Aggregate = aggregate ?? throw new SettingNotFoundException(id); + + using (BypassRuleChecks) + { + Id = aggregate.Id; + AppId = aggregate.AppId; + Environment = aggregate.Environment; + Description = aggregate.Description; + } + } + + [FactoryInsert] + protected override async Task InsertAsync(CancellationToken cancellationToken = default) + { + var appInfo = await AppInfoRepository.GetAsync(AppId, cancellationToken); + var aggregate = Setting.Create(AppId, appInfo.Code, Environment, Description, Nodes); + await SettingRepository.InsertAsync(aggregate, true, cancellationToken); + Id = aggregate.Id; + } + + [FactoryUpdate] + protected override async Task UpdateAsync(CancellationToken cancellationToken = default) + { + if (ChangedProperties.Contains(DescriptionProperty)) + { + Aggregate.SetDescription(Description); + } + + if (ChangedProperties.Contains(StatusProperty)) + { + Aggregate.SetStatus(Status); + } + + await SettingRepository.UpdateAsync(Aggregate, true, cancellationToken); + } + + [FactoryDelete] + protected override Task DeleteAsync(CancellationToken cancellationToken = default) + { + Aggregate.RaiseEvent(new SettingDeletedEvent()); + return SettingRepository.DeleteAsync(Aggregate, true, cancellationToken); + } + + public class DuplicateCheckRule : RuleBase + { + public override async Task ExecuteAsync(IRuleContext context, CancellationToken cancellationToken = new CancellationToken()) + { + var target = (SettingGeneralBusiness)context.Target; + if (!target.IsInsert) + { + return; + } + + var exists = await target.SettingRepository.ExistsAsync(target.AppId, target.Environment, cancellationToken); + if (exists) + { + context.AddErrorResult(string.Format(Resources.IDS_ERROR_SETTING_DUPLICATE, target.AppId, target.Environment)); + } + } + } +} \ No newline at end of file diff --git a/Source/Starfish.Service/Domain/Business/SettingNodeSetKeyBusiness.cs b/Source/Starfish.Service/Domain/Business/SettingNodeSetKeyBusiness.cs deleted file mode 100644 index 08ddb6a..0000000 --- a/Source/Starfish.Service/Domain/Business/SettingNodeSetKeyBusiness.cs +++ /dev/null @@ -1,88 +0,0 @@ -using Nerosoft.Euonia.Business; -using Nerosoft.Euonia.Domain; - -// ReSharper disable MemberCanBePrivate.Global - -namespace Nerosoft.Starfish.Domain; - -public class SettingNodeSetKeyBusiness : EditableObject, IDomainService -{ - private readonly ISettingNodeRepository _repository; - - public SettingNodeSetKeyBusiness(ISettingNodeRepository repository) - { - _repository = repository; - } - - #region Properties - - public static readonly PropertyInfo IdProperty = RegisterProperty(p => p.Id); - public static readonly PropertyInfo OldNameProperty = RegisterProperty(p => p.OldName); - public static readonly PropertyInfo NewNameProperty = RegisterProperty(p => p.NewName); - - public long Id - { - get => ReadProperty(IdProperty); - private set => LoadProperty(IdProperty, value); - } - - public string OldName - { - get => ReadProperty(OldNameProperty); - private set => LoadProperty(OldNameProperty, value); - } - - public string NewName - { - get => ReadProperty(NewNameProperty); - private set => LoadProperty(NewNameProperty, value); - } - - #endregion - - [FactoryCreate] - protected void Create(long id, string oldName, string newName) - { - Id = id; - OldName = oldName; - NewName = newName; - } - - [FactoryUpdate] - protected override async Task UpdateAsync(CancellationToken cancellationToken = default) - { - var aggregate = await _repository.GetAsync(Id, true, Array.Empty(), cancellationToken); - if (aggregate == null) - { - throw new SettingNodeNotFoundException(Id); - } - - var parent = await _repository.GetAsync(aggregate.ParentId, cancellationToken); - - if (parent == null) - { - throw new SettingNodeNotFoundException(aggregate.ParentId); - } - - var entities = new List(); - - var newKey = parent.GenerateKey(NewName, aggregate.Sort); - - if (aggregate.Type is SettingNodeType.Array or SettingNodeType.Object) - { - var leaves = await _repository.GetLeavesAsync(aggregate.AppId, aggregate.Environment, aggregate.Key, cancellationToken); - - foreach (var node in leaves) - { - node.SetKey(newKey + node.Key[aggregate.Key.Length..]); - } - - entities.AddRange(leaves); - } - - aggregate.SetKey(newKey); - entities.Add(aggregate); - - await _repository.UpdateAsync(entities); - } -} \ No newline at end of file diff --git a/Source/Starfish.Service/Domain/Business/SettingNodeUpdateBusiness.cs b/Source/Starfish.Service/Domain/Business/SettingNodeUpdateBusiness.cs deleted file mode 100644 index 1ff3e43..0000000 --- a/Source/Starfish.Service/Domain/Business/SettingNodeUpdateBusiness.cs +++ /dev/null @@ -1,115 +0,0 @@ -using Nerosoft.Euonia.Business; -using Nerosoft.Euonia.Domain; - -// ReSharper disable MemberCanBePrivate.Global - -namespace Nerosoft.Starfish.Domain; - -/// -/// 配置节点重命名领域服务 -/// -public class SettingNodeUpdateBusiness : EditableObject, IDomainService -{ - private readonly ISettingNodeRepository _repository; - - public SettingNodeUpdateBusiness(ISettingNodeRepository repository) - { - _repository = repository; - } - - #region Register properties - - public static readonly PropertyInfo IdProperty = RegisterProperty(p => p.Id); - - public static readonly PropertyInfo ValueProperty = RegisterProperty(p => p.Value); - - public static readonly PropertyInfo IntentProperty = RegisterProperty(p => p.Intent); - - #endregion - - #region Defines properties - - public long Id - { - get => ReadProperty(IdProperty); - private set => LoadProperty(IdProperty, value); - } - - public string Value - { - get => ReadProperty(ValueProperty); - internal set => LoadProperty(ValueProperty, value); - } - - public string Intent - { - get => ReadProperty(IntentProperty); - internal set => LoadProperty(IntentProperty, value); - } - - public SettingNode Aggregate { get; private set; } - - #endregion - - [FactoryFetch] - protected async Task FetchAsync(long id, CancellationToken cancellationToken = default) - { - var aggregate = await _repository.GetAsync(Id, true, [], cancellationToken); - - Aggregate = aggregate ?? throw new SettingNodeNotFoundException(Id); - - Id = id; - } - - [FactoryUpdate] - protected override async Task UpdateAsync(CancellationToken cancellationToken = default) - { - Func action = Intent switch - { - "name" => UpdateNameAsync, - "desc" => UpdateDescriptionAsync, - "description" => UpdateDescriptionAsync, - "value" => UpdateValueAsync, - _ => _ => Task.CompletedTask - }; - - await action(Aggregate); - - await _repository.UpdateAsync(Aggregate, true, cancellationToken); - } - - private async Task UpdateNameAsync(SettingNode aggregate) - { - if (aggregate.Type == SettingNodeType.Root) - { - throw new ForbiddenException(Resources.IDS_ERROR_NOT_ALLOW_RENAME_ROOT_NODE); - } - - var parent = await _repository.GetAsync(aggregate.ParentId); - - if (parent is { Type: SettingNodeType.Array }) - { - throw new ForbiddenException(Resources.IDS_ERROR_NOT_ALLOW_RENAME_ARRAY_CHILD_NODE); - } - - var exists = await _repository.CheckChildNodeNameAsync(aggregate.Id, aggregate.ParentId, Value); - if (exists) - { - throw new ForbiddenException(Resources.IDS_ERROR_SETTING_NODE_NAME_EXISTS); - } - - aggregate.SetName(Value); - } - - private Task UpdateValueAsync(SettingNode aggregate) - { - aggregate.SetValue(Value); - return Task.CompletedTask; - } - - private Task UpdateDescriptionAsync(SettingNode aggregate) - { - aggregate.SetDescription(Value); - return Task.CompletedTask; - } -} \ No newline at end of file diff --git a/Source/Starfish.Service/Domain/Business/SettingPublishBusiness.cs b/Source/Starfish.Service/Domain/Business/SettingPublishBusiness.cs index 9e7ad18..55a782d 100644 --- a/Source/Starfish.Service/Domain/Business/SettingPublishBusiness.cs +++ b/Source/Starfish.Service/Domain/Business/SettingPublishBusiness.cs @@ -1,7 +1,5 @@ using Nerosoft.Euonia.Business; using Nerosoft.Euonia.Domain; -using Nerosoft.Euonia.Linq; -using Nerosoft.Starfish.Repository; // ReSharper disable UnusedMember.Global @@ -12,9 +10,9 @@ namespace Nerosoft.Starfish.Domain; /// public class SettingPublishBusiness : CommandObject, IDomainService { - private readonly ISettingNodeRepository _repository; + private readonly ISettingRepository _repository; - public SettingPublishBusiness(ISettingNodeRepository repository) + public SettingPublishBusiness(ISettingRepository repository) { _repository = repository; } @@ -26,46 +24,21 @@ protected async Task ExecuteAsync(long id, CancellationToken cancellationToken = if (aggregate == null) { - throw new SettingNodeNotFoundException(id); + throw new SettingNotFoundException(id); } if (aggregate == null) { - throw new SettingNodeNotFoundException(id); + throw new SettingNotFoundException(id); } - if (aggregate.Type != SettingNodeType.Root) + if (aggregate.Status == SettingStatus.Disabled) { - throw new InvalidOperationException(Resources.IDS_ERROR_ONLY_ALLOW_PUBLISH_ROOT_NODE); + throw new SettingDisabledException(id); } - List nodes = [aggregate]; + aggregate.SetStatus(SettingStatus.Published); - var types = new[] - { - SettingNodeType.String, - SettingNodeType.Boolean, - SettingNodeType.Number, - SettingNodeType.Referer - }; - - ISpecification[] specifications = - { - SettingNodeSpecification.AppIdEquals(aggregate.AppId), - SettingNodeSpecification.EnvironmentEquals(aggregate.Environment), - SettingNodeSpecification.TypeIn(types) - }; - - var predicate = new CompositeSpecification(PredicateOperator.AndAlso, specifications).Satisfy(); - - var leaves = await _repository.FindAsync(predicate, false, Array.Empty(), cancellationToken); - nodes.AddRange(leaves); - - foreach (var node in nodes) - { - node.ClearEvents(); - } - - await _repository.UpdateAsync(nodes, true, cancellationToken); + await _repository.UpdateAsync(aggregate, true, cancellationToken); } } \ No newline at end of file diff --git a/Source/Starfish.Service/Domain/Enums/SettingNodeStatus.cs b/Source/Starfish.Service/Domain/Enums/SettingStatus.cs similarity index 59% rename from Source/Starfish.Service/Domain/Enums/SettingNodeStatus.cs rename to Source/Starfish.Service/Domain/Enums/SettingStatus.cs index aecfd7e..ca4f73a 100644 --- a/Source/Starfish.Service/Domain/Enums/SettingNodeStatus.cs +++ b/Source/Starfish.Service/Domain/Enums/SettingStatus.cs @@ -5,7 +5,7 @@ namespace Nerosoft.Starfish.Domain; /// /// 配置节点状态 /// -public enum SettingNodeStatus +public enum SettingStatus { /// /// @@ -15,18 +15,18 @@ public enum SettingNodeStatus /// /// 待发布 /// - [Description(nameof(Resources.IDS_ENUM_SETTING_NODE_STATUS_PENDING))] + [Description(nameof(Resources.IDS_ENUM_SETTING_STATUS_PENDING))] Pending = 1, /// /// 已发布 /// - [Description(nameof(Resources.IDS_ENUM_SETTING_NODE_STATUS_PUBLISHED))] + [Description(nameof(Resources.IDS_ENUM_SETTING_STATUS_PUBLISHED))] Published = 2, /// /// 禁用 /// - [Description(nameof(Resources.IDS_ENUM_SETTING_NODE_STATUS_DISABLED))] + [Description(nameof(Resources.IDS_ENUM_SETTING_STATUS_DISABLED))] Disabled = 3 } diff --git a/Source/Starfish.Service/Domain/Events/SettingCreatedEvent.cs b/Source/Starfish.Service/Domain/Events/SettingCreatedEvent.cs new file mode 100644 index 0000000..2a4e957 --- /dev/null +++ b/Source/Starfish.Service/Domain/Events/SettingCreatedEvent.cs @@ -0,0 +1,17 @@ +using Nerosoft.Euonia.Domain; + +namespace Nerosoft.Starfish.Domain; + +public class SettingCreatedEvent : DomainEvent +{ + public SettingCreatedEvent() + { + } + + public SettingCreatedEvent(Setting setting) + { + Setting = setting; + } + + public Setting Setting { get; set; } +} \ No newline at end of file diff --git a/Source/Starfish.Service/Domain/Events/SettingNodeDeletedEvent.cs b/Source/Starfish.Service/Domain/Events/SettingDeletedEvent.cs similarity index 73% rename from Source/Starfish.Service/Domain/Events/SettingNodeDeletedEvent.cs rename to Source/Starfish.Service/Domain/Events/SettingDeletedEvent.cs index 9f5f879..641ca19 100644 --- a/Source/Starfish.Service/Domain/Events/SettingNodeDeletedEvent.cs +++ b/Source/Starfish.Service/Domain/Events/SettingDeletedEvent.cs @@ -5,6 +5,6 @@ namespace Nerosoft.Starfish.Domain; /// /// 配置节点删除领域事件 /// -public class SettingNodeDeletedEvent : DomainEvent +public class SettingDeletedEvent : DomainEvent { } \ No newline at end of file diff --git a/Source/Starfish.Service/Domain/Events/SettingNodeCreatedEvent.cs b/Source/Starfish.Service/Domain/Events/SettingNodeCreatedEvent.cs deleted file mode 100644 index 64f34f6..0000000 --- a/Source/Starfish.Service/Domain/Events/SettingNodeCreatedEvent.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Nerosoft.Euonia.Domain; - -namespace Nerosoft.Starfish.Domain; - -/// -/// 配置节点创建领域事件 -/// -public class SettingNodeCreatedEvent : DomainEvent -{ - /// - /// 构造函数 - /// - public SettingNodeCreatedEvent() - { - } - - /// - /// 构造函数 - /// - /// - public SettingNodeCreatedEvent(SettingNode node) - { - Node = node; - } - - /// - /// 配置节点 - /// - public SettingNode Node { get; set; } -} \ No newline at end of file diff --git a/Source/Starfish.Service/Domain/Events/SettingNodeRenamedEvent.cs b/Source/Starfish.Service/Domain/Events/SettingNodeRenamedEvent.cs deleted file mode 100644 index f43c67a..0000000 --- a/Source/Starfish.Service/Domain/Events/SettingNodeRenamedEvent.cs +++ /dev/null @@ -1,44 +0,0 @@ -using Nerosoft.Euonia.Domain; - -namespace Nerosoft.Starfish.Domain; - -/// -/// 配置节点重命名领域事件 -/// -public class SettingNodeRenamedEvent : DomainEvent -{ - /// - /// - /// - public SettingNodeRenamedEvent() - { - } - - /// - /// - /// - /// - /// - /// - public SettingNodeRenamedEvent(long id, string oldName, string newName) - { - Id = id; - OldName = oldName; - NewName = newName; - } - - /// - /// 节点Id - /// - public long Id { get; set; } - - /// - /// 旧名称 - /// - public string OldName { get; set; } - - /// - /// 新名称 - /// - public string NewName { get; set; } -} \ No newline at end of file diff --git a/Source/Starfish.Service/Domain/Events/SettingNodeStatusChangedEvent.cs b/Source/Starfish.Service/Domain/Events/SettingNodeStatusChangedEvent.cs deleted file mode 100644 index 73206c2..0000000 --- a/Source/Starfish.Service/Domain/Events/SettingNodeStatusChangedEvent.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Nerosoft.Euonia.Domain; - -namespace Nerosoft.Starfish.Domain; - -/// -/// 配置节点状态变更领域事件 -/// -public class SettingNodeStatusChangedEvent : DomainEvent -{ -} \ No newline at end of file diff --git a/Source/Starfish.Service/Domain/Events/SettingStatusChangedEvent.cs b/Source/Starfish.Service/Domain/Events/SettingStatusChangedEvent.cs new file mode 100644 index 0000000..c05c63b --- /dev/null +++ b/Source/Starfish.Service/Domain/Events/SettingStatusChangedEvent.cs @@ -0,0 +1,23 @@ +using Nerosoft.Euonia.Domain; + +namespace Nerosoft.Starfish.Domain; + +/// +/// 配置节点状态变更领域事件 +/// +public class SettingStatusChangedEvent : DomainEvent +{ + public SettingStatusChangedEvent() + { + } + + public SettingStatusChangedEvent(SettingStatus oldStatus, SettingStatus newStatus) + { + OldStatus = oldStatus; + NewStatus = newStatus; + } + + public SettingStatus OldStatus { get; set; } + + public SettingStatus NewStatus { get; set; } +} \ No newline at end of file diff --git a/Source/Starfish.Service/Domain/Exceptions/SettingDisabledException.cs b/Source/Starfish.Service/Domain/Exceptions/SettingDisabledException.cs new file mode 100644 index 0000000..41b69e9 --- /dev/null +++ b/Source/Starfish.Service/Domain/Exceptions/SettingDisabledException.cs @@ -0,0 +1,9 @@ +namespace Nerosoft.Starfish.Domain; + +public class SettingDisabledException : BadRequestException +{ + public SettingDisabledException(long id) + : base(string.Format(Resources.IDS_ERROR_SETTING_DISABLED, id)) + { + } +} \ No newline at end of file diff --git a/Source/Starfish.Service/Domain/Exceptions/SettingNodeNotFoundException.cs b/Source/Starfish.Service/Domain/Exceptions/SettingNotFoundException.cs similarity index 51% rename from Source/Starfish.Service/Domain/Exceptions/SettingNodeNotFoundException.cs rename to Source/Starfish.Service/Domain/Exceptions/SettingNotFoundException.cs index 1c41ccf..3caacd8 100644 --- a/Source/Starfish.Service/Domain/Exceptions/SettingNodeNotFoundException.cs +++ b/Source/Starfish.Service/Domain/Exceptions/SettingNotFoundException.cs @@ -3,14 +3,14 @@ /// /// 配置信息不存在异常 /// -public class SettingNodeNotFoundException : NotFoundException +public class SettingNotFoundException : NotFoundException { /// /// 构造函数 /// /// - public SettingNodeNotFoundException(long id) - : base(string.Format(Resources.IDS_ERROR_SETTING_NODE_NOT_EXISTS, id)) + public SettingNotFoundException(long id) + : base(string.Format(Resources.IDS_ERROR_SETTING_NOT_EXISTS, id)) { } } \ No newline at end of file diff --git a/Source/Starfish.Service/Domain/Repositories/ISettingNodeRepository.cs b/Source/Starfish.Service/Domain/Repositories/ISettingNodeRepository.cs deleted file mode 100644 index 5dc4d41..0000000 --- a/Source/Starfish.Service/Domain/Repositories/ISettingNodeRepository.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System.Linq.Expressions; -using Nerosoft.Euonia.Repository; - -namespace Nerosoft.Starfish.Domain; - -/// -/// 设置节点仓储 -/// -public interface ISettingNodeRepository : IRepository -{ - /// - /// 检查根节点配置是否存在 - /// - /// - /// - /// - /// - Task ExistsAsync(long appId, string environment, CancellationToken cancellationToken = default); - - /// - /// 获取指定节点 - /// - /// - /// - /// - /// - /// - Task GetAsync(long id, bool tracking, string[] properties, CancellationToken cancellationToken = default); - - /// - /// 根据Key查询所有叶子节点 - /// - /// - /// - /// - /// - /// - Task> GetLeavesAsync(long appId, string environment, string key, CancellationToken cancellationToken = default); - - /// - /// 查询同级节点名称是否存在 - /// - /// - /// - /// - /// - /// - Task CheckChildNodeNameAsync(long id, long parentId, string name, CancellationToken cancellationToken = default); - - /// - /// 查询符合条件的节点列表 - /// - /// - /// - /// - /// - /// - Task> FindAsync(Expression> predicate, bool tracking, string[] properties, CancellationToken cancellationToken = default); -} \ No newline at end of file diff --git a/Source/Starfish.Service/Domain/Repositories/ISettingRepository.cs b/Source/Starfish.Service/Domain/Repositories/ISettingRepository.cs new file mode 100644 index 0000000..c4c303f --- /dev/null +++ b/Source/Starfish.Service/Domain/Repositories/ISettingRepository.cs @@ -0,0 +1,36 @@ +using System.Linq.Expressions; +using Nerosoft.Euonia.Repository; + +namespace Nerosoft.Starfish.Domain; + +public interface ISettingRepository : IRepository +{ + /// + /// 检查配置是否存在 + /// + /// + /// + /// + /// + Task ExistsAsync(long appId, string environment, CancellationToken cancellationToken = default); + + /// + /// 获取指定配置 + /// + /// + /// + /// + /// + /// + Task GetAsync(long id, bool tracking, string[] properties, CancellationToken cancellationToken = default); + + /// + /// 查询符合条件的配置列表 + /// + /// + /// + /// + /// + /// + Task> FindAsync(Expression> predicate, bool tracking, string[] properties, CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/Source/Starfish.Service/Properties/Resources.resx b/Source/Starfish.Service/Properties/Resources.resx index b37298a..a4a1738 100644 --- a/Source/Starfish.Service/Properties/Resources.resx +++ b/Source/Starfish.Service/Properties/Resources.resx @@ -192,13 +192,13 @@ Unsupported service bus provider '{0}'. - + Disabled - + Pending - + Published @@ -222,19 +222,13 @@ String - - Not allow to rename array child node. - - - Not allow to rename root node. - - - Child node name exists. + + Setting key '{0}' exists. Node name is required. - + Setting node not exists, [Id]:{0}. @@ -270,4 +264,10 @@ Email '{0}' not available. + + Setting was disabled, [Id]:{0}. + + + Setting was duplicated ,[App]:{0}, [Env]:{1}. + \ No newline at end of file diff --git a/Source/Starfish.Service/Properties/Resources.zh-CN.resx b/Source/Starfish.Service/Properties/Resources.zh-CN.resx index 87ff91e..fc5a763 100644 --- a/Source/Starfish.Service/Properties/Resources.zh-CN.resx +++ b/Source/Starfish.Service/Properties/Resources.zh-CN.resx @@ -1,76 +1,96 @@  + mimetype: application/x-microsoft.net.object.bytearray.base64 + value : The object must be serialized into a byte array + : using a System.ComponentModel.TypeConverter + : and then encoded with base64 encoding. + --> + + + + + + + + + + + + + + + + + + - + + @@ -89,13 +109,13 @@ text/microsoft-resx - 1.3 + 2.0 - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 页码必须大于0。 @@ -103,7 +123,7 @@ 数量必须大于0。 - + 用户名或密码错误。 @@ -115,7 +135,7 @@ 用户已锁定。 - + 用户名'{0}'不可用。 @@ -172,13 +192,13 @@ 启用 - + 禁用 - + 待发布 - + 已发布 diff --git a/Source/Starfish.Service/Repository/Contexts/DataContext.cs b/Source/Starfish.Service/Repository/Contexts/DataContext.cs index 65c0f1f..6273d3b 100644 --- a/Source/Starfish.Service/Repository/Contexts/DataContext.cs +++ b/Source/Starfish.Service/Repository/Contexts/DataContext.cs @@ -53,6 +53,31 @@ public DataContext(DbContextOptions options, IModelBuilder builder, /// public override async Task SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default) { + var entries = ChangeTracker.Entries().ToList(); + + foreach (var entry in entries) + { + if (entry.Entity is not IAuditing auditing) + { + continue; + } + + switch (entry.State) + { + case EntityState.Unchanged: + continue; + case EntityState.Added: + auditing.UpdatedBy = auditing.CreatedBy = _request.Context.User?.Identity?.Name; + break; + case EntityState.Modified: + auditing.UpdatedBy = _request.Context.User?.Identity?.Name; + break; + case EntityState.Deleted: + auditing.UpdatedBy = _request.Context.User?.Identity?.Name; + break; + } + } + var events = GetTrackedEvents(); var result = await base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken); if (result > 0 && events.Count > 0) diff --git a/Source/Starfish.Service/Repository/Contexts/SqliteModelBuilder.cs b/Source/Starfish.Service/Repository/Contexts/SqliteModelBuilder.cs index 84ff25a..0de291b 100644 --- a/Source/Starfish.Service/Repository/Contexts/SqliteModelBuilder.cs +++ b/Source/Starfish.Service/Repository/Contexts/SqliteModelBuilder.cs @@ -59,27 +59,55 @@ public void Configure(ModelBuilder modelBuilder) .HasValueGenerator(); }); - modelBuilder.Entity(entity => + modelBuilder.Entity(entity => { - entity.ToTable("setting_node"); + entity.ToTable("setting"); entity.HasKey(t => t.Id); - entity.HasIndex(t => t.ParentId); entity.HasIndex(t => t.AppId); entity.HasIndex(t => t.AppCode); entity.HasIndex(t => t.Environment); + entity.HasIndex([nameof(Setting.AppId), nameof(Setting.Environment)], "IDX_SETTING_UNIQUE") + .IsUnique(); + entity.Property(t => t.Id) .IsRequired() .HasValueGenerator(); - entity.HasMany(t => t.Children) + entity.HasOne(t => t.App) + .WithMany() + .HasForeignKey(t => t.AppId) + .OnDelete(DeleteBehavior.Cascade); + + entity.HasMany(t => t.Nodes) .WithOne() - .HasForeignKey(t => t.ParentId) + .HasForeignKey(t => t.SettingId) .OnDelete(DeleteBehavior.Cascade); - entity.HasOne(t => t.Parent) + entity.HasMany(t => t.Revisions) + .WithOne() + .HasForeignKey(t => t.SettingId) + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity(entity => + { + entity.ToTable("setting_item"); + entity.HasKey(t => t.Id); + entity.HasIndex(t => t.SettingId); + + entity.HasIndex(t => t.Key); + + entity.HasIndex([nameof(SettingNode.SettingId), nameof(SettingNode.Key)], "IDX_SETTING_ITEM_UNIQUE") + .IsUnique(); + + entity.Property(t => t.Id) + .IsRequired() + .HasValueGenerator(); + + entity.HasOne(t => t.Setting) .WithMany() - .HasForeignKey(t => t.ParentId) + .HasForeignKey(t => t.SettingId) .OnDelete(DeleteBehavior.Cascade); }); @@ -87,11 +115,16 @@ public void Configure(ModelBuilder modelBuilder) { entity.ToTable("setting_revision"); entity.HasKey(t => t.Id); - entity.HasIndex(t => t.AppId); + entity.HasIndex(t => t.SettingId); entity.Property(t => t.Id) .IsRequired() .HasValueGenerator(); + + entity.HasOne(t => t.Setting) + .WithMany() + .HasForeignKey(t => t.SettingId) + .OnDelete(DeleteBehavior.Cascade); }); modelBuilder.Entity(entity => diff --git a/Source/Starfish.Service/Repository/CriteriaExtensions.cs b/Source/Starfish.Service/Repository/CriteriaExtensions.cs index a4eeb9c..9ca4702 100644 --- a/Source/Starfish.Service/Repository/CriteriaExtensions.cs +++ b/Source/Starfish.Service/Repository/CriteriaExtensions.cs @@ -86,22 +86,22 @@ public static Specification GetSpecification(this AppInfoCriteria crite /// /// /// - public static Specification GetSpecification(this SettingNodeCriteria criteria) + public static Specification GetSpecification(this SettingCriteria criteria) { - Specification specification = new TrueSpecification(); + Specification specification = new TrueSpecification(); if (criteria == null) { return specification; } - if (criteria.ParentId > 0) + if (!string.IsNullOrWhiteSpace(criteria.AppCode)) { - specification &= SettingNodeSpecification.ParentIdEquals(criteria.ParentId); + specification &= SettingSpecification.AppCodeEquals(criteria.AppCode); } if (!string.IsNullOrWhiteSpace(criteria.Environment)) { - specification &= SettingNodeSpecification.EnvironmentEquals(criteria.Environment); + specification &= SettingSpecification.EnvironmentEquals(criteria.Environment); } return specification; diff --git a/Source/Starfish.Service/Repository/Repositories/SettingNodeRepository.cs b/Source/Starfish.Service/Repository/Repositories/SettingNodeRepository.cs deleted file mode 100644 index 9d4e216..0000000 --- a/Source/Starfish.Service/Repository/Repositories/SettingNodeRepository.cs +++ /dev/null @@ -1,61 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Nerosoft.Euonia.Repository; -using Nerosoft.Starfish.Domain; -using Nerosoft.Starfish.Service; - -namespace Nerosoft.Starfish.Repository; - -/// -/// 设置节点仓储 -/// -public class SettingNodeRepository : BaseRepository, ISettingNodeRepository -{ - /// - /// 构造函数 - /// - /// - public SettingNodeRepository(IContextProvider provider) - : base(provider) - { - } - - /// - public Task ExistsAsync(long appId, string environment, CancellationToken cancellationToken = default) - { - var specification = SettingNodeSpecification.AppIdEquals(appId) & SettingNodeSpecification.EnvironmentEquals(environment); - var predicate = specification.Satisfy(); - return base.ExistsAsync(predicate, cancellationToken); - } - - /// - public Task> GetLeavesAsync(long appId, string environment, string key, CancellationToken cancellationToken = default) - { - var specification = SettingNodeSpecification.KeyStartsWith($"{key}:"); - specification &= SettingNodeSpecification.AppIdEquals(appId); - specification &= SettingNodeSpecification.EnvironmentEquals(environment); - var predicate = specification.Satisfy(); - return base.FindAsync(predicate, false, Array.Empty(), cancellationToken); - } - - /// - public Task CheckChildNodeNameAsync(long id, long parentId, string name, CancellationToken cancellationToken = default) - { - var specification = SettingNodeSpecification.ParentIdEquals(parentId) - & SettingNodeSpecification.NameEquals(name) - & SettingNodeSpecification.IdNotEquals(id); - var predicate = specification.Satisfy(); - var set = Context.Set() - .AsNoTracking(); - return set.AnyAsync(predicate, cancellationToken); - } - - /// - public override Task GetAsync(long id, CancellationToken cancellationToken = default) - { - var specification = SettingNodeSpecification.IdEquals(id); - var predicate = specification.Satisfy(); - var set = Context.Set() - .Include(t => t.Children); - return set.FirstOrDefaultAsync(predicate, cancellationToken); - } -} \ No newline at end of file diff --git a/Source/Starfish.Service/Repository/Repositories/SettingRepository.cs b/Source/Starfish.Service/Repository/Repositories/SettingRepository.cs new file mode 100644 index 0000000..7f10c0b --- /dev/null +++ b/Source/Starfish.Service/Repository/Repositories/SettingRepository.cs @@ -0,0 +1,26 @@ +using Nerosoft.Euonia.Linq; +using Nerosoft.Euonia.Repository; +using Nerosoft.Starfish.Domain; +using Nerosoft.Starfish.Service; + +namespace Nerosoft.Starfish.Repository; + +public class SettingRepository : BaseRepository, ISettingRepository +{ + public SettingRepository(IContextProvider provider) + : base(provider) + { + } + + public Task ExistsAsync(long appId, string environment, CancellationToken cancellationToken = default) + { + ISpecification[] specifications = + [ + SettingSpecification.AppIdEquals(appId), + SettingSpecification.EnvironmentEquals(environment) + ]; + + var predicate = new CompositeSpecification(PredicateOperator.AndAlso, specifications).Satisfy(); + return ExistsAsync(predicate, cancellationToken); + } +} \ No newline at end of file diff --git a/Source/Starfish.Service/Repository/RepositoryModule.cs b/Source/Starfish.Service/Repository/RepositoryModule.cs index 4b4643d..679ded8 100644 --- a/Source/Starfish.Service/Repository/RepositoryModule.cs +++ b/Source/Starfish.Service/Repository/RepositoryModule.cs @@ -122,7 +122,7 @@ public override void ConfigureServices(ServiceConfigurationContext context) .AddScoped() .AddScoped() .AddScoped() - .AddScoped() + .AddScoped() .AddScoped() .AddScoped(); } diff --git a/Source/Starfish.Service/Repository/Specifications/SettingNodeSpecification.cs b/Source/Starfish.Service/Repository/Specifications/SettingNodeSpecification.cs deleted file mode 100644 index fef40aa..0000000 --- a/Source/Starfish.Service/Repository/Specifications/SettingNodeSpecification.cs +++ /dev/null @@ -1,141 +0,0 @@ -using Nerosoft.Euonia.Linq; -using Nerosoft.Starfish.Domain; - -namespace Nerosoft.Starfish.Repository; - -/// -/// 设置节点规约 -/// -public static class SettingNodeSpecification -{ - /// - /// Id等于 - /// - /// - /// - public static Specification IdEquals(long id) - { - return new DirectSpecification(x => x.Id == id); - } - - /// - /// Id在中 - /// - /// - /// - public static Specification IdIn(IEnumerable ids) - { - return new DirectSpecification(x => ids.Contains(x.Id)); - } - - /// - /// Id不等于 - /// - /// - /// - public static Specification IdNotEquals(long id) - { - return new DirectSpecification(x => x.Id != id); - } - - /// - /// 父节点Id等于 - /// - /// 父节点Id - /// - public static Specification ParentIdEquals(long parentId) - { - return new DirectSpecification(x => x.ParentId == parentId); - } - - /// - /// AppId等于 - /// - /// - /// - public static Specification AppIdEquals(long appId) - { - return new DirectSpecification(x => x.AppId == appId); - } - - /// - /// AppCode等于 - /// - /// - /// - public static Specification AppCodeEquals(string appCode) - { - return new DirectSpecification(x => x.AppCode == appCode); - } - - /// - /// 环境等于 - /// - /// - /// - public static Specification EnvironmentEquals(string environment) - { - return new DirectSpecification(x => x.Environment == environment); - } - - /// - /// 类型等于 - /// - /// - /// - public static Specification TypeEquals(SettingNodeType type) - { - return new DirectSpecification(x => x.Type == type); - } - - /// - /// 类型在中 - /// - /// - /// - public static Specification TypeIn(IEnumerable types) - { - return new DirectSpecification(x => types.Contains(x.Type)); - } - - /// - /// Key等于 - /// - /// - /// - public static Specification KeyEquals(string key) - { - return new DirectSpecification(x => x.Key == key); - } - - /// - /// Key以开头 - /// - /// - /// - public static Specification KeyStartsWith(string key) - { - return new DirectSpecification(x => x.Key.StartsWith(key)); - } - - /// - /// 名称等于 - /// - /// - /// - public static Specification NameEquals(string name) - { - name = name.Normalize(TextCaseType.Lower); - return new DirectSpecification(x => x.Name.ToLower() == name); - } - - /// - /// 根节点Id等于. - /// - /// - /// - public static Specification RootIdEquals(long rootId) - { - return new DirectSpecification(x => x.RootId == rootId); - } -} \ No newline at end of file diff --git a/Source/Starfish.Service/Repository/Specifications/SettingSpecification.cs b/Source/Starfish.Service/Repository/Specifications/SettingSpecification.cs new file mode 100644 index 0000000..7891cdd --- /dev/null +++ b/Source/Starfish.Service/Repository/Specifications/SettingSpecification.cs @@ -0,0 +1,39 @@ +using Nerosoft.Euonia.Linq; +using Nerosoft.Starfish.Domain; + +namespace Nerosoft.Starfish.Repository; + +public static class SettingSpecification +{ + public static Specification IdEquals(long id) + { + return new DirectSpecification(x => x.Id == id); + } + + public static Specification AppIdEquals(long appId) + { + return new DirectSpecification(x => x.AppId == appId); + } + + public static Specification EnvironmentEquals(string environment) + { + return new DirectSpecification(x => x.Environment == environment); + } + + public static Specification AppCodeEquals(string appCode) + { + appCode = appCode.Normalize(TextCaseType.Lower); + return new DirectSpecification(x => x.AppCode == appCode); + } + + public static Specification AppCodeContains(string appCode) + { + appCode = appCode.Normalize(TextCaseType.Lower); + return new DirectSpecification(x => x.AppCode.Contains(appCode)); + } + + public static Specification StatusEquals(SettingStatus status) + { + return new DirectSpecification(x => x.Status == status); + } +} \ No newline at end of file diff --git a/Source/Starfish.Service/Seedwork/IAuditing.cs b/Source/Starfish.Service/Seedwork/IAuditing.cs new file mode 100644 index 0000000..25064bd --- /dev/null +++ b/Source/Starfish.Service/Seedwork/IAuditing.cs @@ -0,0 +1,10 @@ +using Nerosoft.Euonia.Domain; + +namespace Nerosoft.Starfish.Service; + +public interface IAuditing : IHasCreateTime, IHasUpdateTime +{ + string CreatedBy { get; set; } + + string UpdatedBy { get; set; } +} \ No newline at end of file diff --git a/Source/Starfish.Service/UseCases/Identity/UserDetailUseCase.cs b/Source/Starfish.Service/UseCases/Identity/UserDetailUseCase.cs index 3c8db23..fbc72c3 100644 --- a/Source/Starfish.Service/UseCases/Identity/UserDetailUseCase.cs +++ b/Source/Starfish.Service/UseCases/Identity/UserDetailUseCase.cs @@ -21,7 +21,7 @@ public UserDetailUseCase(IUserRepository repository) _repository = repository; } - public Task ExecuteAsync(UserDetailInput input, CancellationToken cancellationToken = new CancellationToken()) + public Task ExecuteAsync(UserDetailInput input, CancellationToken cancellationToken = default) { return _repository.GetAsync(input.Id, false, query => query.Include(nameof(User.Roles)), cancellationToken) .ContinueWith(task => diff --git a/Source/Starfish.Service/UseCases/Identity/UserSetRoleUseCase.cs b/Source/Starfish.Service/UseCases/Identity/UserSetRoleUseCase.cs index 15e17e8..fda11e0 100644 --- a/Source/Starfish.Service/UseCases/Identity/UserSetRoleUseCase.cs +++ b/Source/Starfish.Service/UseCases/Identity/UserSetRoleUseCase.cs @@ -17,7 +17,7 @@ public UserSetRoleUseCase(IBus bus) _bus = bus; } - public Task ExecuteAsync(UserSetRoleInput input, CancellationToken cancellationToken = new CancellationToken()) + public Task ExecuteAsync(UserSetRoleInput input, CancellationToken cancellationToken = default) { var command = new UserSetRoleCommand(input.Id, input.Roles); return _bus.SendAsync(command, cancellationToken); diff --git a/Source/Starfish.Service/UseCases/Setting/SettingNodeCountUseCase.cs b/Source/Starfish.Service/UseCases/Setting/SettingCountUseCase.cs similarity index 57% rename from Source/Starfish.Service/UseCases/Setting/SettingNodeCountUseCase.cs rename to Source/Starfish.Service/UseCases/Setting/SettingCountUseCase.cs index e6aee32..6ea3b76 100644 --- a/Source/Starfish.Service/UseCases/Setting/SettingNodeCountUseCase.cs +++ b/Source/Starfish.Service/UseCases/Setting/SettingCountUseCase.cs @@ -8,42 +8,42 @@ namespace Nerosoft.Starfish.UseCases; /// /// 获取符合条件的配置数量用例接口 /// -public interface ISettingNodeCountUseCase : IUseCase; +public interface ISettingCountUseCase : IUseCase; /// /// 获取符合条件的配置数量用例输出 /// /// -public record SettingNodeCountOutput(int Result) : IUseCaseOutput; +public record SettingCountOutput(int Result) : IUseCaseOutput; /// /// 获取符合条件的配置数量用例输入 /// /// -public record SettingNodeCountInput(SettingNodeCriteria Criteria) : IUseCaseInput; +public record SettingCountInput(SettingCriteria Criteria) : IUseCaseInput; /// /// 获取符合条件的配置数量用例 /// -public class SettingNodeCountUseCase : ISettingNodeCountUseCase +public class SettingCountUseCase : ISettingCountUseCase { - private readonly ISettingNodeRepository _repository; + private readonly ISettingRepository _repository; /// /// 构造函数 /// /// - public SettingNodeCountUseCase(ISettingNodeRepository repository) + public SettingCountUseCase(ISettingRepository repository) { _repository = repository; } /// - public Task ExecuteAsync(SettingNodeCountInput input, CancellationToken cancellationToken = default) + public Task ExecuteAsync(SettingCountInput input, CancellationToken cancellationToken = default) { var specification = input.Criteria.GetSpecification(); var predicate = specification.Satisfy(); return _repository.CountAsync(predicate, cancellationToken) - .ContinueWith(t => new SettingNodeCountOutput(t.Result), cancellationToken); + .ContinueWith(t => new SettingCountOutput(t.Result), cancellationToken); } } \ No newline at end of file diff --git a/Source/Starfish.Service/UseCases/Setting/SettingCreateUseCase.cs b/Source/Starfish.Service/UseCases/Setting/SettingCreateUseCase.cs new file mode 100644 index 0000000..a9b78f9 --- /dev/null +++ b/Source/Starfish.Service/UseCases/Setting/SettingCreateUseCase.cs @@ -0,0 +1,36 @@ +using Nerosoft.Euonia.Application; +using Nerosoft.Euonia.Bus; +using Nerosoft.Euonia.Mapping; +using Nerosoft.Starfish.Application; +using Nerosoft.Starfish.Transit; + +namespace Nerosoft.Starfish.UseCases; + +public interface ISettingCreateUseCase : IUseCase; + +public class SettingCreateUseCase : ISettingCreateUseCase +{ + private readonly IBus _bus; + + public SettingCreateUseCase(IBus bus) + { + _bus = bus; + } + + public Task ExecuteAsync(SettingCreateDto input, CancellationToken cancellationToken = default) + { + var command = TypeAdapter.ProjectedAs(input); + command.Data = input.DataType switch + { + "json" => JsonConfigurationFileParser.Parse(input.ItemsData), + "text" => TextConfigurationFileParser.Parse(input.ItemsData), + _ => default + }; + return _bus.SendAsync(command, cancellationToken) + .ContinueWith(task => + { + task.WaitAndUnwrapException(cancellationToken); + return task.Result; + }, cancellationToken); + } +} \ No newline at end of file diff --git a/Source/Starfish.Service/UseCases/Setting/SettingNodeDeleteUseCase.cs b/Source/Starfish.Service/UseCases/Setting/SettingDeleteUseCase.cs similarity index 52% rename from Source/Starfish.Service/UseCases/Setting/SettingNodeDeleteUseCase.cs rename to Source/Starfish.Service/UseCases/Setting/SettingDeleteUseCase.cs index 89b3f66..6a2be8c 100644 --- a/Source/Starfish.Service/UseCases/Setting/SettingNodeDeleteUseCase.cs +++ b/Source/Starfish.Service/UseCases/Setting/SettingDeleteUseCase.cs @@ -7,29 +7,29 @@ namespace Nerosoft.Starfish.UseCases; /// /// 删除配置节点用例接口 /// -public interface ISettingNodeDeleteUseCase : INonOutputUseCase; +public interface ISettingDeleteUseCase : INonOutputUseCase; /// /// 删除配置节点输入 /// /// -public record SettingNodeDeleteInput(long Id) : IUseCaseInput; +public record SettingDeleteInput(long Id) : IUseCaseInput; /// /// 删除配置节点用例 /// -public class SettingNodeDeleteUseCase : ISettingNodeDeleteUseCase +public class SettingDeleteUseCase : ISettingDeleteUseCase { private readonly IBus _bus; - public SettingNodeDeleteUseCase(IBus bus) + public SettingDeleteUseCase(IBus bus) { _bus = bus; } - public Task ExecuteAsync(SettingNodeDeleteInput input, CancellationToken cancellationToken = default) + public Task ExecuteAsync(SettingDeleteInput input, CancellationToken cancellationToken = default) { - var command = new SettingNodeDeleteCommand(input.Id); + var command = new SettingDeleteCommand(input.Id); return _bus.SendAsync(command, cancellationToken); } } \ No newline at end of file diff --git a/Source/Starfish.Service/UseCases/Setting/SettingNodeGetDetailUseCase.cs b/Source/Starfish.Service/UseCases/Setting/SettingGetDetailUseCase.cs similarity index 58% rename from Source/Starfish.Service/UseCases/Setting/SettingNodeGetDetailUseCase.cs rename to Source/Starfish.Service/UseCases/Setting/SettingGetDetailUseCase.cs index 1a42edf..1527951 100644 --- a/Source/Starfish.Service/UseCases/Setting/SettingNodeGetDetailUseCase.cs +++ b/Source/Starfish.Service/UseCases/Setting/SettingGetDetailUseCase.cs @@ -8,45 +8,45 @@ namespace Nerosoft.Starfish.UseCases; /// /// 获取配置节点详情用例接口 /// -public interface ISettingNodeGetDetailUseCase : IUseCase; +public interface ISettingGetDetailUseCase : IUseCase; /// /// 获取配置节点详情用例输出 /// /// -public record SettingNodeGetDetailOutput(SettingNodeDetailDto Result) : IUseCaseOutput; +public record SettingGetDetailOutput(SettingDetailDto Result) : IUseCaseOutput; /// /// 获取配置节点详情用例输入 /// /// -public record SettingNodeGetDetailInput(long Id) : IUseCaseInput; +public record SettingGetDetailInput(long Id) : IUseCaseInput; /// /// 获取配置节点详情用例 /// -public class SettingNodeGetDetailUseCase : ISettingNodeGetDetailUseCase +public class SettingGetDetailUseCase : ISettingGetDetailUseCase { - private readonly ISettingNodeRepository _repository; + private readonly ISettingRepository _repository; /// /// 构造函数 /// /// - public SettingNodeGetDetailUseCase(ISettingNodeRepository repository) + public SettingGetDetailUseCase(ISettingRepository repository) { _repository = repository; } /// - public Task ExecuteAsync(SettingNodeGetDetailInput input, CancellationToken cancellationToken = default) + public Task ExecuteAsync(SettingGetDetailInput input, CancellationToken cancellationToken = default) { return _repository.GetAsync(input.Id, false, Array.Empty(), cancellationToken) .ContinueWith(task => { task.WaitAndUnwrapException(cancellationToken); - var result = TypeAdapter.ProjectedAs(task.Result); - return new SettingNodeGetDetailOutput(result); + var result = TypeAdapter.ProjectedAs(task.Result); + return new SettingGetDetailOutput(result); }, cancellationToken); } } \ No newline at end of file diff --git a/Source/Starfish.Service/UseCases/Setting/SettingLeafNodeCreationUseCase.cs b/Source/Starfish.Service/UseCases/Setting/SettingLeafNodeCreationUseCase.cs deleted file mode 100644 index 46e6eac..0000000 --- a/Source/Starfish.Service/UseCases/Setting/SettingLeafNodeCreationUseCase.cs +++ /dev/null @@ -1,51 +0,0 @@ -using Nerosoft.Euonia.Application; -using Nerosoft.Euonia.Bus; -using Nerosoft.Starfish.Application; -using Nerosoft.Starfish.Domain; -using Nerosoft.Starfish.Transit; - -namespace Nerosoft.Starfish.UseCases; - -/// -/// 应用配置节点更新用例接口 -/// -public interface ISettingLeafNodeCreateUseCase : IUseCase; - -/// -/// 应用配置子节点创建输出 -/// -/// -public record SettingLeafNodeCreationOutput(long Result) : IUseCaseOutput; - -/// -/// 应用配置子节点创建输入 -/// -/// -/// -/// -public record SettingLeafNodeCreationInput(long ParentId, SettingNodeType Type, SettingNodeCreateDto Data) : IUseCaseInput; - -/// -/// 应用配置子节点创建用例 -/// -public class SettingLeafNodeCreateUseCase : ISettingLeafNodeCreateUseCase -{ - private readonly IBus _bus; - - /// - /// 构造函数 - /// - /// - public SettingLeafNodeCreateUseCase(IBus bus) - { - _bus = bus; - } - - /// - public Task ExecuteAsync(SettingLeafNodeCreationInput input, CancellationToken cancellationToken = default) - { - var command = new SettingLeafNodeCreateCommand(input.ParentId, input.Type, input.Data); - return _bus.SendAsync(command, cancellationToken) - .ContinueWith(t => new SettingLeafNodeCreationOutput(t.Result), cancellationToken); - } -} \ No newline at end of file diff --git a/Source/Starfish.Service/UseCases/Setting/SettingNodeUpdateDescriptionUseCase.cs b/Source/Starfish.Service/UseCases/Setting/SettingNodeUpdateDescriptionUseCase.cs deleted file mode 100644 index 144abcb..0000000 --- a/Source/Starfish.Service/UseCases/Setting/SettingNodeUpdateDescriptionUseCase.cs +++ /dev/null @@ -1,36 +0,0 @@ -using Nerosoft.Euonia.Application; -using Nerosoft.Euonia.Bus; -using Nerosoft.Starfish.Application; - -namespace Nerosoft.Starfish.UseCases; - -/// -/// 配置节点更新用例接口 -/// -public interface ISettingNodeUpdateDescriptionUseCase : INonOutputUseCase; - -/// -/// 配置节点更新输入 -/// -/// -/// -public record SettingNodeUpdateDescriptionInput(long Id, string Description) : IUseCaseInput; - -/// -/// 配置节点更新用例 -/// -public class SettingNodeUpdateDescriptionUseCase : ISettingNodeUpdateDescriptionUseCase -{ - private readonly IBus _bus; - - public SettingNodeUpdateDescriptionUseCase(IBus bus) - { - _bus = bus; - } - - public Task ExecuteAsync(SettingNodeUpdateDescriptionInput valueInput, CancellationToken cancellationToken = default) - { - var command = new SettingNodeUpdateCommand(valueInput.Id, "description", valueInput.Description); - return _bus.SendAsync(command, cancellationToken); - } -} \ No newline at end of file diff --git a/Source/Starfish.Service/UseCases/Setting/SettingNodeUpdateNameUseCase.cs b/Source/Starfish.Service/UseCases/Setting/SettingNodeUpdateNameUseCase.cs deleted file mode 100644 index 5d0ea9a..0000000 --- a/Source/Starfish.Service/UseCases/Setting/SettingNodeUpdateNameUseCase.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Nerosoft.Euonia.Application; -using Nerosoft.Euonia.Bus; -using Nerosoft.Starfish.Application; - -namespace Nerosoft.Starfish.UseCases; - -/// -/// 重命名配置节点用例接口 -/// -public interface ISettingNodeUpdateNameUseCase : INonOutputUseCase; - -/// -/// 重命名配置节点输入 -/// -/// -/// -public record SettingNodeUpdateNameInput(long Id, string Name) : IUseCaseInput; - -/// -/// 重命名配置节点用例 -/// -public class SettingNodeUpdateNameUseCase : ISettingNodeUpdateNameUseCase -{ - private readonly IBus _bus; - - /// - /// 构造函数 - /// - /// - public SettingNodeUpdateNameUseCase(IBus bus) - { - _bus = bus; - } - - public Task ExecuteAsync(SettingNodeUpdateNameInput input, CancellationToken cancellationToken = default) - { - var command = new SettingNodeUpdateCommand(input.Id, "name", input.Name); - return _bus.SendAsync(command, cancellationToken); - } -} \ No newline at end of file diff --git a/Source/Starfish.Service/UseCases/Setting/SettingNodeUpdateValueUseCase.cs b/Source/Starfish.Service/UseCases/Setting/SettingNodeUpdateValueUseCase.cs deleted file mode 100644 index 8648d47..0000000 --- a/Source/Starfish.Service/UseCases/Setting/SettingNodeUpdateValueUseCase.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Nerosoft.Euonia.Application; -using Nerosoft.Euonia.Bus; -using Nerosoft.Starfish.Application; - -namespace Nerosoft.Starfish.UseCases; - -/// -/// 配置节点更新用例接口 -/// -public interface ISettingNodeUpdateValueUseCase : INonOutputUseCase; - -/// -/// 配置节点更新输入 -/// -/// -/// -public record SettingNodeUpdateValueInput(long Id, string Value) : IUseCaseInput; - -/// -/// 配置节点更新用例 -/// -public class SettingNodeUpdateValueUseCase : ISettingNodeUpdateValueUseCase -{ - private readonly IBus _bus; - - /// - /// 构造函数 - /// - /// - public SettingNodeUpdateValueUseCase(IBus bus) - { - _bus = bus; - } - - public Task ExecuteAsync(SettingNodeUpdateValueInput valueInput, CancellationToken cancellationToken = default) - { - var command = new SettingNodeUpdateCommand(valueInput.Id, "Value", valueInput.Value); - return _bus.SendAsync(command, cancellationToken); - } -} \ No newline at end of file diff --git a/Source/Starfish.Service/UseCases/Setting/SettingNodePublishUseCase.cs b/Source/Starfish.Service/UseCases/Setting/SettingPublishUseCase.cs similarity index 59% rename from Source/Starfish.Service/UseCases/Setting/SettingNodePublishUseCase.cs rename to Source/Starfish.Service/UseCases/Setting/SettingPublishUseCase.cs index c43e34c..b588062 100644 --- a/Source/Starfish.Service/UseCases/Setting/SettingNodePublishUseCase.cs +++ b/Source/Starfish.Service/UseCases/Setting/SettingPublishUseCase.cs @@ -8,29 +8,29 @@ namespace Nerosoft.Starfish.UseCases; /// /// 配置节点发布用例接口 /// -public interface ISettingNodePublishUseCase : INonOutputUseCase; +public interface ISettingPublishUseCase : INonOutputUseCase; /// /// 配置节点发布输入 /// /// -public record SettingNodePublishInput(long Id, SettingNodePublishDto Data) : IUseCaseInput; +public record SettingPublishInput(long Id, SettingPublishDto Data) : IUseCaseInput; /// /// 配置节点发布用例 /// -public class SettingNodePublishUseCase : ISettingNodePublishUseCase +public class SettingPublishUseCase : ISettingPublishUseCase { private readonly IBus _bus; - public SettingNodePublishUseCase(IBus bus) + public SettingPublishUseCase(IBus bus) { _bus = bus; } - public async Task ExecuteAsync(SettingNodePublishInput input, CancellationToken cancellationToken = default) + public async Task ExecuteAsync(SettingPublishInput input, CancellationToken cancellationToken = default) { - var command = new SettingNodePublishCommand(input.Id); + var command = new SettingPublishCommand(input.Id); await _bus.SendAsync(command, cancellationToken); var @event = new SettingPublishedEvent(input.Id) diff --git a/Source/Starfish.Service/UseCases/Setting/SettingRootNodeCreationUseCase.cs b/Source/Starfish.Service/UseCases/Setting/SettingRootNodeCreationUseCase.cs deleted file mode 100644 index 3b3b222..0000000 --- a/Source/Starfish.Service/UseCases/Setting/SettingRootNodeCreationUseCase.cs +++ /dev/null @@ -1,52 +0,0 @@ -using Nerosoft.Euonia.Application; -using Nerosoft.Euonia.Bus; -using Nerosoft.Starfish.Application; - -namespace Nerosoft.Starfish.UseCases; - -/// -/// 新增根节点用例接口 -/// -public interface ISettingRootNodeCreateUseCase : IUseCase; - -/// -/// 新增根节点输出 -/// -/// -public record SettingRootNodeCreationOutput(long Id) : IUseCaseOutput; - -/// -/// 新增根节点输入 -/// -/// -/// -public record SettingRootNodeCreationInput(long AppId, string Environment) : IUseCaseInput; - -/// -/// 新增根节点用例 -/// -public class SettingRootNodeCreateUseCase : ISettingRootNodeCreateUseCase -{ - private readonly IBus _bus; - - /// - /// 构造函数 - /// - /// - public SettingRootNodeCreateUseCase(IBus bus) - { - _bus = bus; - } - - /// - public Task ExecuteAsync(SettingRootNodeCreationInput input, CancellationToken cancellationToken = default) - { - var command = new SettingRootNodeCreateCommand - { - AppId = input.AppId, - Environment = input.Environment - }; - return _bus.SendAsync(command, cancellationToken) - .ContinueWith(t => new SettingRootNodeCreationOutput(t.Result), cancellationToken); - } -} \ No newline at end of file diff --git a/Source/Starfish.Service/UseCases/Setting/SettingNodeSearchUseCase.cs b/Source/Starfish.Service/UseCases/Setting/SettingSearchUseCase.cs similarity index 60% rename from Source/Starfish.Service/UseCases/Setting/SettingNodeSearchUseCase.cs rename to Source/Starfish.Service/UseCases/Setting/SettingSearchUseCase.cs index 27ee689..438cf2c 100644 --- a/Source/Starfish.Service/UseCases/Setting/SettingNodeSearchUseCase.cs +++ b/Source/Starfish.Service/UseCases/Setting/SettingSearchUseCase.cs @@ -8,13 +8,13 @@ namespace Nerosoft.Starfish.UseCases; /// /// 获取符合条件的配置列表用例接口 /// -public interface ISettingNodeSearchUseCase : IUseCase; +public interface ISettingSearchUseCase : IUseCase; /// /// 获取符合条件的配置列表用例输出 /// /// -public record SettingNodeSearchOutput(List Result) : IUseCaseOutput; +public record SettingSearchOutput(List Result) : IUseCaseOutput; /// /// 获取符合条件的配置列表用例输入 @@ -22,26 +22,26 @@ public record SettingNodeSearchOutput(List Result) : IUseCas /// /// /// -public record SettingNodeSearchInput(SettingNodeCriteria Criteria, int Page, int Size) : IUseCaseInput; +public record SettingSearchInput(SettingCriteria Criteria, int Page, int Size) : IUseCaseInput; /// /// 获取符合条件的配置列表用例 /// -public class SettingNodeSearchUseCase : ISettingNodeSearchUseCase +public class SettingSearchUseCase : ISettingSearchUseCase { - private readonly ISettingNodeRepository _repository; + private readonly ISettingRepository _repository; /// /// 构造函数 /// /// - public SettingNodeSearchUseCase(ISettingNodeRepository repository) + public SettingSearchUseCase(ISettingRepository repository) { _repository = repository; } /// - public Task ExecuteAsync(SettingNodeSearchInput input, CancellationToken cancellationToken = default) + public Task ExecuteAsync(SettingSearchInput input, CancellationToken cancellationToken = default) { var specification = input.Criteria.GetSpecification(); var predicate = specification.Satisfy(); @@ -49,16 +49,14 @@ public Task ExecuteAsync(SettingNodeSearchInput input, .ContinueWith(task => { task.WaitAndUnwrapException(cancellationToken); - var result = task.Result.ProjectedAsCollection(); - return new SettingNodeSearchOutput(result); + var result = task.Result.ProjectedAsCollection(); + return new SettingSearchOutput(result); }, cancellationToken); } - private static IOrderedQueryable Collator(IQueryable query) + private static IOrderedQueryable Collator(IQueryable query) { return query.OrderByDescending(t => t.AppId) - .ThenBy(t => t.ParentId) - .ThenBy(t => t.Sort) .ThenBy(t => t.Id); } } \ No newline at end of file diff --git a/Source/Starfish.Transit/Setting/SettingNodeCreateDto.cs b/Source/Starfish.Transit/Setting/SettingCreateDto.cs similarity index 63% rename from Source/Starfish.Transit/Setting/SettingNodeCreateDto.cs rename to Source/Starfish.Transit/Setting/SettingCreateDto.cs index c1a8739..d911ec8 100644 --- a/Source/Starfish.Transit/Setting/SettingNodeCreateDto.cs +++ b/Source/Starfish.Transit/Setting/SettingCreateDto.cs @@ -1,9 +1,9 @@ namespace Nerosoft.Starfish.Transit; /// -/// 创建配置节点(根节点) +/// 配置创建Dto /// -public class SettingNodeCreateDto +public class SettingCreateDto { /// /// 应用Id @@ -14,19 +14,20 @@ public class SettingNodeCreateDto /// 环境名称 /// public string Environment { get; set; } - + /// - /// 节点名称 + /// 描述 /// - public string Name { get; set; } + public string Description { get; set; } /// - /// 节点配置值 + /// 数据类型 /// - public string Value { get; set; } + /// 可选值json|text + public string DataType { get; set; } /// - /// 节点描述 + /// 配置项内容 /// - public string Description { get; set; } + public string ItemsData { get; set; } } \ No newline at end of file diff --git a/Source/Starfish.Transit/Setting/SettingNodeCriteria.cs b/Source/Starfish.Transit/Setting/SettingCriteria.cs similarity index 78% rename from Source/Starfish.Transit/Setting/SettingNodeCriteria.cs rename to Source/Starfish.Transit/Setting/SettingCriteria.cs index b3661eb..f94946d 100644 --- a/Source/Starfish.Transit/Setting/SettingNodeCriteria.cs +++ b/Source/Starfish.Transit/Setting/SettingCriteria.cs @@ -3,8 +3,13 @@ /// /// 配置节点查询条件 /// -public class SettingNodeCriteria +public class SettingCriteria { + /// + /// 团队Id + /// + public long TeamId { get; set; } + /// /// App唯一编码 /// @@ -14,9 +19,4 @@ public class SettingNodeCriteria /// 环境 /// public string Environment { get; set; } - - /// - /// 上级Id - /// - public long ParentId { get; set; } } \ No newline at end of file diff --git a/Source/Starfish.Transit/Setting/SettingDetailDto.cs b/Source/Starfish.Transit/Setting/SettingDetailDto.cs new file mode 100644 index 0000000..e8ea482 --- /dev/null +++ b/Source/Starfish.Transit/Setting/SettingDetailDto.cs @@ -0,0 +1,52 @@ +namespace Nerosoft.Starfish.Transit; + +/// +/// 配置详情Dto +/// +public class SettingDetailDto +{ + /// + /// Id + /// + public long Id { get; set; } + + /// + /// 应用Id + /// + public long AppId { get; set; } + + /// + /// 应用唯一编码 + /// + public string AppCode { get; set; } + + /// + /// 应用名称 + /// + public string AppName { get; set; } + + /// + /// 环境名称 + /// + public string Environment { get; set; } + + /// + /// 描述 + /// + public string Description { get; set; } + + /// + /// 状态 + /// + public string Status { get; set; } + + /// + /// 状态名称 + /// + public string StatusDescription { get; set; } + + /// + /// 配置项 + /// + public Dictionary Items { get; set; } +} \ No newline at end of file diff --git a/Source/Starfish.Transit/Setting/SettingItemDto.cs b/Source/Starfish.Transit/Setting/SettingItemDto.cs new file mode 100644 index 0000000..5614805 --- /dev/null +++ b/Source/Starfish.Transit/Setting/SettingItemDto.cs @@ -0,0 +1,47 @@ +namespace Nerosoft.Starfish.Transit; + +/// +/// 设置列表Dto +/// +public class SettingItemDto +{ + /// + /// Id + /// + public long Id { get; set; } + + /// + /// 应用Id + /// + public long AppId { get; set; } + + /// + /// 应用唯一编码 + /// + public string AppCode { get; set; } + + /// + /// 应用名称 + /// + public string AppName { get; set; } + + /// + /// 环境名称 + /// + public string Environment { get; set; } + + /// + /// 描述 + /// + public string Description { get; set; } + + /// + /// 状态 + /// + public string Status { get; set; } + + /// + /// 状态名称 + /// + public string StatusDescription { get; set; } +} \ No newline at end of file diff --git a/Source/Starfish.Transit/Setting/SettingNodePublishDto.cs b/Source/Starfish.Transit/Setting/SettingPublishDto.cs similarity index 88% rename from Source/Starfish.Transit/Setting/SettingNodePublishDto.cs rename to Source/Starfish.Transit/Setting/SettingPublishDto.cs index bc0ab7a..b18af72 100644 --- a/Source/Starfish.Transit/Setting/SettingNodePublishDto.cs +++ b/Source/Starfish.Transit/Setting/SettingPublishDto.cs @@ -3,7 +3,7 @@ /// /// 配置节点发布Dto /// -public class SettingNodePublishDto +public class SettingPublishDto { /// /// 版本号 diff --git a/Source/Starfish.Webapi/Controllers/SettingNodeController.cs b/Source/Starfish.Webapi/Controllers/SettingController.cs similarity index 52% rename from Source/Starfish.Webapi/Controllers/SettingNodeController.cs rename to Source/Starfish.Webapi/Controllers/SettingController.cs index 965a47d..237aa24 100644 --- a/Source/Starfish.Webapi/Controllers/SettingNodeController.cs +++ b/Source/Starfish.Webapi/Controllers/SettingController.cs @@ -1,7 +1,6 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Nerosoft.Starfish.Application; -using Nerosoft.Starfish.Domain; using Nerosoft.Starfish.Transit; namespace Nerosoft.Starfish.Webapi.Controllers; @@ -9,10 +8,10 @@ namespace Nerosoft.Starfish.Webapi.Controllers; /// /// 应用配置管理接口 /// -[Route("api/setting/node")] +[Route("api/[controller]/node")] [ApiController, ApiExplorerSettings(GroupName = "setting")] [Authorize] -public class SettingNodeController : ControllerBase +public class SettingController : ControllerBase { private readonly ISettingApplicationService _service; @@ -20,7 +19,7 @@ public class SettingNodeController : ControllerBase /// 构造函数 /// /// - public SettingNodeController(ISettingApplicationService service) + public SettingController(ISettingApplicationService service) { _service = service; } @@ -33,7 +32,7 @@ public SettingNodeController(ISettingApplicationService service) /// /// [HttpGet] - public async Task SearchAsync([FromQuery] SettingNodeCriteria criteria, int page = 1, int size = 10) + public async Task SearchAsync([FromQuery] SettingCriteria criteria, int page = 1, int size = 10) { var result = await _service.SearchAsync(criteria, page, size, HttpContext.RequestAborted); return Ok(result); @@ -45,7 +44,7 @@ public async Task SearchAsync([FromQuery] SettingNodeCriteria cri /// /// [HttpGet("count")] - public async Task CountAsync([FromQuery] SettingNodeCriteria criteria) + public async Task CountAsync([FromQuery] SettingCriteria criteria) { var result = await _service.CountAsync(criteria, HttpContext.RequestAborted); return Ok(result); @@ -69,67 +68,13 @@ public async Task GetAsync(long id) /// /// [HttpPost] - public async Task CreateRootAsync([FromBody] SettingNodeCreateDto data) + public async Task CreateAsync([FromBody] SettingCreateDto data) { - var result = await _service.CreateRootNodeAsync(data.AppId, data.Environment, HttpContext.RequestAborted); + var result = await _service.CreateAsync(data, HttpContext.RequestAborted); Response.Headers.Append("Entry", $"{result}"); return Ok(); } - /// - /// 新增子节点 - /// - /// 父节点Id - /// - /// - /// - [HttpPost("{id:long}/{type}")] - public async Task CreateLeafAsync(long id, SettingNodeType type, [FromBody] SettingNodeCreateDto data) - { - var result = await _service.CreateLeafNodeAsync(id, type, data, HttpContext.RequestAborted); - Response.Headers.Append("Entry", $"{result}"); - return Ok(); - } - - /// - /// 更新配置节点 - /// - /// 节点Id - /// - /// - [HttpPut("{id:long}/value")] - public async Task UpdateValueAsync(long id, [FromBody] SettingNodeUpdateDto data) - { - await _service.UpdateValueAsync(id, data.Value, HttpContext.RequestAborted); - return Ok(); - } - - /// - /// 重命名配置节点 - /// - /// 节点Id - /// - /// - [HttpPut("{id:long}/name")] - public async Task UpdateNameAsync(long id, [FromBody] SettingNodeUpdateDto data) - { - await _service.UpdateNameAsync(id, data.Name, HttpContext.RequestAborted); - return Ok(); - } - - /// - /// 设置配置节点描述 - /// - /// - /// - /// - [HttpPut("{id:long}/description")] - public async Task UpdateDescriptionAsync(long id, [FromBody] SettingNodeUpdateDto data) - { - await _service.UpdateDescriptionAsync(id, data.Description, HttpContext.RequestAborted); - return Ok(); - } - /// /// 删除配置节点 /// @@ -149,7 +94,7 @@ public async Task DeleteAsync(long id) /// /// [HttpPost("{id:long}/publish")] - public async Task PublishAsync(long id, [FromBody] SettingNodePublishDto data) + public async Task PublishAsync(long id, [FromBody] SettingPublishDto data) { await _service.PublishAsync(id, data, HttpContext.RequestAborted); return Ok(); From f371439fc51a0eeaa840f3b36960ab2b45780ee2 Mon Sep 17 00:00:00 2001 From: damon Date: Tue, 2 Jan 2024 00:25:41 +0800 Subject: [PATCH 03/22] [WIP] Refactoring --- .../Commands/SettingRootNodeCreateCommand.cs | 20 ------ .../Commands/SettingUpdateCommand.cs | 15 ++++ .../IDictionaryApplicationService.cs | 2 +- .../Contracts/ISettingApplicationService.cs | 19 +++++ .../Handlers/SettingArchiveCommandHandler.cs | 6 +- .../Handlers/SettingCommandHandler.cs | 15 +++- .../Handlers/SettingRevisionCommandHandler.cs | 38 ++-------- .../DictionaryApplicationService.cs | 26 +++---- .../Implements/SettingApplicationService.cs | 27 +++++-- .../TextConfigurationFileParser.cs | 3 - .../Domain/Aggregates/Setting.cs | 67 ++++++++++++------ .../{SettingNode.cs => SettingItem.cs} | 11 ++- .../Domain/Aggregates/SettingRevision.cs | 12 ++++ .../Domain/Business/SettingGeneralBusiness.cs | 33 +++------ ...{SettingNodeType.cs => SettingItemType.cs} | 2 +- .../Domain/Repositories/ISettingRepository.cs | 7 +- .../Properties/Resources.resx | 46 +++--------- .../Properties/Resources.zh-CN.resx | 14 ++-- .../Repository/Contexts/SqliteModelBuilder.cs | 6 +- .../Repositories/SettingRepository.cs | 16 ++++- .../UseCases/Setting/SettingCountUseCase.cs | 4 +- .../UseCases/Setting/SettingCreateUseCase.cs | 8 ++- .../Setting/SettingGetDetailUseCase.cs | 2 +- .../UseCases/Setting/SettingSearchUseCase.cs | 4 +- .../UseCases/Setting/SettingUpdateUseCase.cs | 40 +++++++++++ .../Setting/SettingCreateDto.cs | 4 +- .../Setting/SettingDetailDto.cs | 4 +- .../Setting/SettingItemDto.cs | 4 +- .../Setting/SettingNodeDetailDto.cs | 70 ------------------- .../Setting/SettingNodeItemDto.cs | 57 --------------- .../Setting/SettingNodeUpdateDto.cs | 25 ------- .../Setting/SettingUpdateDto.cs | 17 +++++ .../Controllers/DictionaryController.cs | 2 +- .../Controllers/SettingController.cs | 34 ++++++++- 34 files changed, 314 insertions(+), 346 deletions(-) delete mode 100644 Source/Starfish.Service/Application/Commands/SettingRootNodeCreateCommand.cs create mode 100644 Source/Starfish.Service/Application/Commands/SettingUpdateCommand.cs rename Source/Starfish.Service/Domain/Aggregates/{SettingNode.cs => SettingItem.cs} (69%) rename Source/Starfish.Service/Domain/Enums/{SettingNodeType.cs => SettingItemType.cs} (94%) create mode 100644 Source/Starfish.Service/UseCases/Setting/SettingUpdateUseCase.cs delete mode 100644 Source/Starfish.Transit/Setting/SettingNodeDetailDto.cs delete mode 100644 Source/Starfish.Transit/Setting/SettingNodeItemDto.cs delete mode 100644 Source/Starfish.Transit/Setting/SettingNodeUpdateDto.cs create mode 100644 Source/Starfish.Transit/Setting/SettingUpdateDto.cs diff --git a/Source/Starfish.Service/Application/Commands/SettingRootNodeCreateCommand.cs b/Source/Starfish.Service/Application/Commands/SettingRootNodeCreateCommand.cs deleted file mode 100644 index 08d4110..0000000 --- a/Source/Starfish.Service/Application/Commands/SettingRootNodeCreateCommand.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Nerosoft.Euonia.Bus; -using Nerosoft.Euonia.Domain; - -namespace Nerosoft.Starfish.Application; - -/// -/// 新增根节点命令 -/// -public class SettingRootNodeCreateCommand : Command, IQueue -{ - /// - /// 应用Id - /// - public long AppId { get; set; } - - /// - /// 环境名称 - /// - public string Environment { get; set; } -} \ No newline at end of file diff --git a/Source/Starfish.Service/Application/Commands/SettingUpdateCommand.cs b/Source/Starfish.Service/Application/Commands/SettingUpdateCommand.cs new file mode 100644 index 0000000..da0f296 --- /dev/null +++ b/Source/Starfish.Service/Application/Commands/SettingUpdateCommand.cs @@ -0,0 +1,15 @@ +using Nerosoft.Euonia.Domain; + +namespace Nerosoft.Starfish.Application; + +public class SettingUpdateCommand : Command +{ + public SettingUpdateCommand(long id) + { + Id = id; + } + + public long Id { get; set; } + + public IDictionary Data { get; set; } +} \ No newline at end of file diff --git a/Source/Starfish.Service/Application/Contracts/IDictionaryApplicationService.cs b/Source/Starfish.Service/Application/Contracts/IDictionaryApplicationService.cs index cca6d9d..dbc3c84 100644 --- a/Source/Starfish.Service/Application/Contracts/IDictionaryApplicationService.cs +++ b/Source/Starfish.Service/Application/Contracts/IDictionaryApplicationService.cs @@ -34,5 +34,5 @@ public interface IDictionaryApplicationService : IApplicationService /// /// /// - Task> GetSettingNodeTypeItemsAsync(CancellationToken cancellationToken = default); + Task> GetSettingItemTypeItemsAsync(CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/Source/Starfish.Service/Application/Contracts/ISettingApplicationService.cs b/Source/Starfish.Service/Application/Contracts/ISettingApplicationService.cs index 8aedd4e..8fc796d 100644 --- a/Source/Starfish.Service/Application/Contracts/ISettingApplicationService.cs +++ b/Source/Starfish.Service/Application/Contracts/ISettingApplicationService.cs @@ -42,6 +42,15 @@ public interface ISettingApplicationService : IApplicationService /// Task CreateAsync(SettingCreateDto data, CancellationToken cancellationToken = default); + /// + /// 更新配置 + /// + /// + /// + /// + /// + Task UpdateAsync(long id, SettingUpdateDto data, CancellationToken cancellationToken = default); + /// /// 删除节点 /// @@ -50,6 +59,16 @@ public interface ISettingApplicationService : IApplicationService /// Task DeleteAsync(long id, CancellationToken cancellationToken = default); + /// + /// 更新配置项 + /// + /// + /// + /// + /// + /// + Task UpdateAsync(long id, string key, string value, CancellationToken cancellationToken = default); + /// /// 发布配置 /// diff --git a/Source/Starfish.Service/Application/Handlers/SettingArchiveCommandHandler.cs b/Source/Starfish.Service/Application/Handlers/SettingArchiveCommandHandler.cs index 995b9fe..e41db2b 100644 --- a/Source/Starfish.Service/Application/Handlers/SettingArchiveCommandHandler.cs +++ b/Source/Starfish.Service/Application/Handlers/SettingArchiveCommandHandler.cs @@ -30,18 +30,18 @@ public Task HandleAsync(SettingArchiveCreateCommand message, MessageContext cont }); } - private async Task>> GetNodesAsync(long id, CancellationToken cancellationToken = default) + private async Task>> GetNodesAsync(long id, CancellationToken cancellationToken = default) { var repository = UnitOfWork.Current.GetService(); - var aggregate = await repository.GetAsync(id, false, [nameof(Setting.Nodes)], cancellationToken); + var aggregate = await repository.GetAsync(id, false, [nameof(Setting.Items)], cancellationToken); if (aggregate == null) { throw new SettingNotFoundException(id); } - return Tuple.Create(aggregate.AppId, aggregate.AppCode, aggregate.Environment, aggregate.Nodes.ToList()); + return Tuple.Create(aggregate.AppId, aggregate.AppCode, aggregate.Environment, aggregate.Items.ToList()); } private async Task SaveArchiveAsync(long appId, string appCode, string environment, string data, string userName, CancellationToken cancellationToken = default) diff --git a/Source/Starfish.Service/Application/Handlers/SettingCommandHandler.cs b/Source/Starfish.Service/Application/Handlers/SettingCommandHandler.cs index 35fd534..6661525 100644 --- a/Source/Starfish.Service/Application/Handlers/SettingCommandHandler.cs +++ b/Source/Starfish.Service/Application/Handlers/SettingCommandHandler.cs @@ -11,6 +11,7 @@ namespace Nerosoft.Starfish.Application; /// public class SettingCommandHandler : CommandHandlerBase, IHandler, + IHandler, IHandler, IHandler { @@ -26,12 +27,22 @@ public Task HandleAsync(SettingCreateCommand message, MessageContext context, Ca var business = await Factory.CreateAsync(cancellationToken); business.AppId = message.AppId; business.Environment = message.Environment; - business.Description = message.Description; - var _ = await business.SaveAsync(false, cancellationToken); + business.Items = message.Data; + _ = await business.SaveAsync(false, cancellationToken); return business.Id; }, context.Response); } + public Task HandleAsync(SettingUpdateCommand message, MessageContext context, CancellationToken cancellationToken = new CancellationToken()) + { + return ExecuteAsync(async () => + { + var business = await Factory.FetchAsync(message.Id, cancellationToken); + business.Items = message.Data; + _ = await business.SaveAsync(true, cancellationToken); + }); + } + /// public Task HandleAsync(SettingDeleteCommand message, MessageContext context, CancellationToken cancellationToken = default) { diff --git a/Source/Starfish.Service/Application/Handlers/SettingRevisionCommandHandler.cs b/Source/Starfish.Service/Application/Handlers/SettingRevisionCommandHandler.cs index 85ca7cc..074cd15 100644 --- a/Source/Starfish.Service/Application/Handlers/SettingRevisionCommandHandler.cs +++ b/Source/Starfish.Service/Application/Handlers/SettingRevisionCommandHandler.cs @@ -3,7 +3,6 @@ using Nerosoft.Euonia.Repository; using Nerosoft.Starfish.Domain; using Nerosoft.Starfish.Service; -using Newtonsoft.Json; namespace Nerosoft.Starfish.Application; @@ -19,39 +18,16 @@ public Task HandleAsync(SettingRevisionCreateCommand message, MessageContext con { return ExecuteAsync(async () => { - var nodes = await GetNodesAsync(message.SettingId, cancellationToken); + var repository = UnitOfWork.Current.GetService(); - var entity = new SettingRevision + var aggregate = await repository.GetAsync(message.SettingId, false, [nameof(Setting.Items), nameof(Setting.Revisions)], cancellationToken); + + if (aggregate == null) { - SettingId = message.SettingId, - Version = message.Version, - Data = JsonConvert.SerializeObject(nodes, new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore }), - Operator = context?.User?.Identity?.Name, - Comment = message.Comment - }; + throw new SettingNotFoundException(message.SettingId); + } - await SaveRevisionAsync(entity, cancellationToken); + aggregate.CreateRevision(message.Version, message.Comment, context.User?.Identity?.Name); }); } - - private async Task> GetNodesAsync(long id, CancellationToken cancellationToken = default) - { - var repository = UnitOfWork.Current.GetService(); - - var aggregate = await repository.GetAsync(id, false, [nameof(Setting.Nodes)], cancellationToken); - - if (aggregate == null) - { - throw new SettingNotFoundException(id); - } - - return aggregate.Nodes.ToList(); - } - - private Task SaveRevisionAsync(SettingRevision entity, CancellationToken cancellationToken = default) - { - var repository = UnitOfWork.Current.GetService(); - - return repository.InsertAsync(entity, true, cancellationToken); - } } \ No newline at end of file diff --git a/Source/Starfish.Service/Application/Implements/DictionaryApplicationService.cs b/Source/Starfish.Service/Application/Implements/DictionaryApplicationService.cs index 554d922..a321ba9 100644 --- a/Source/Starfish.Service/Application/Implements/DictionaryApplicationService.cs +++ b/Source/Starfish.Service/Application/Implements/DictionaryApplicationService.cs @@ -107,39 +107,39 @@ public Task> GetDatabaseTypeItemsAsync(CancellationToken } /// - public Task> GetSettingNodeTypeItemsAsync(CancellationToken cancellationToken = default) + public Task> GetSettingItemTypeItemsAsync(CancellationToken cancellationToken = default) { var items = new List { new() { - Name = nameof(SettingNodeType.Root), - Description = Resources.IDS_ENUM_SETTING_NODE_TYPE_ROOT + Name = nameof(SettingItemType.Root), + Description = Resources.IDS_ENUM_SETTING_ITEM_TYPE_ROOT }, new() { - Name = nameof(SettingNodeType.Array), - Description = Resources.IDS_ENUM_SETTING_NODE_TYPE_ARRAY + Name = nameof(SettingItemType.Array), + Description = Resources.IDS_ENUM_SETTING_ITEM_TYPE_ARRAY }, new() { - Name = nameof(SettingNodeType.Object), - Description = Resources.IDS_ENUM_SETTING_NODE_TYPE_OBJECT + Name = nameof(SettingItemType.Object), + Description = Resources.IDS_ENUM_SETTING_ITEM_TYPE_OBJECT }, new() { - Name = nameof(SettingNodeType.String), - Description = Resources.IDS_ENUM_SETTING_NODE_TYPE_STRING + Name = nameof(SettingItemType.String), + Description = Resources.IDS_ENUM_SETTING_ITEM_TYPE_STRING }, new() { - Name = nameof(SettingNodeType.Number), - Description = Resources.IDS_ENUM_SETTING_NODE_TYPE_NUMBER + Name = nameof(SettingItemType.Number), + Description = Resources.IDS_ENUM_SETTING_ITEM_TYPE_NUMBER }, new() { - Name = nameof(SettingNodeType.Boolean), - Description = Resources.IDS_ENUM_SETTING_NODE_TYPE_BOOLEAN + Name = nameof(SettingItemType.Boolean), + Description = Resources.IDS_ENUM_SETTING_ITEM_TYPE_BOOLEAN } }; return Task.FromResult(items); diff --git a/Source/Starfish.Service/Application/Implements/SettingApplicationService.cs b/Source/Starfish.Service/Application/Implements/SettingApplicationService.cs index 3ca96d2..491313f 100644 --- a/Source/Starfish.Service/Application/Implements/SettingApplicationService.cs +++ b/Source/Starfish.Service/Application/Implements/SettingApplicationService.cs @@ -1,5 +1,4 @@ using Nerosoft.Euonia.Application; -using Nerosoft.Starfish.Domain; using Nerosoft.Starfish.Transit; using Nerosoft.Starfish.UseCases; @@ -16,7 +15,7 @@ public Task> SearchAsync(SettingCriteria criteria, int page var useCase = LazyServiceProvider.GetRequiredService(); var input = new SettingSearchInput(criteria, page, size); return useCase.ExecuteAsync(input, cancellationToken) - .ContinueWith(t => t.Result.Result, cancellationToken); + .ContinueWith(t => t.Result.Result, cancellationToken); } /// @@ -25,7 +24,7 @@ public Task CountAsync(SettingCriteria criteria, CancellationToken cancella var useCase = LazyServiceProvider.GetRequiredService(); var input = new SettingCountInput(criteria); return useCase.ExecuteAsync(input, cancellationToken) - .ContinueWith(t => t.Result.Result, cancellationToken); + .ContinueWith(t => t.Result.Result, cancellationToken); } /// @@ -34,12 +33,21 @@ public Task GetAsync(long id, CancellationToken cancellationTo var useCase = LazyServiceProvider.GetRequiredService(); var input = new SettingGetDetailInput(id); return useCase.ExecuteAsync(input, cancellationToken) - .ContinueWith(t => t.Result.Result, cancellationToken); + .ContinueWith(t => t.Result.Result, cancellationToken); } - public async Task CreateAsync(SettingCreateDto data, CancellationToken cancellationToken = default) + public Task CreateAsync(SettingCreateDto data, CancellationToken cancellationToken = default) { - throw new NotImplementedException(); + var useCase = LazyServiceProvider.GetRequiredService(); + return useCase.ExecuteAsync(data, cancellationToken) + .ContinueWith(t => t.Result, cancellationToken); + } + + public async Task UpdateAsync(long id, SettingUpdateDto data, CancellationToken cancellationToken = default) + { + var useCase = LazyServiceProvider.GetRequiredService(); + var input = new SettingUpdateInput(id, data); + await useCase.ExecuteAsync(input, cancellationToken); } /// @@ -50,6 +58,11 @@ public Task DeleteAsync(long id, CancellationToken cancellationToken = default) return useCase.ExecuteAsync(input, cancellationToken); } + public Task UpdateAsync(long id, string key, string value, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + /// public Task PublishAsync(long id, SettingPublishDto data, CancellationToken cancellationToken = default) { @@ -64,6 +77,6 @@ public Task GetSettingRawAsync(long appId, string environment, Cancellat var useCase = LazyServiceProvider.GetRequiredService(); var input = new GetSettingRawUseCaseInput(appId, environment); return useCase.ExecuteAsync(input, cancellationToken) - .ContinueWith(t => t.Result.Result, cancellationToken); + .ContinueWith(t => t.Result.Result, cancellationToken); } } \ No newline at end of file diff --git a/Source/Starfish.Service/Application/TextConfigurationFileParser.cs b/Source/Starfish.Service/Application/TextConfigurationFileParser.cs index 2bf5e08..f8ed714 100644 --- a/Source/Starfish.Service/Application/TextConfigurationFileParser.cs +++ b/Source/Starfish.Service/Application/TextConfigurationFileParser.cs @@ -6,9 +6,6 @@ private TextConfigurationFileParser() { } - private readonly Dictionary _data = new(StringComparer.OrdinalIgnoreCase); - private readonly Stack _paths = new(); - public static IDictionary Parse(string json) { using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(json))) diff --git a/Source/Starfish.Service/Domain/Aggregates/Setting.cs b/Source/Starfish.Service/Domain/Aggregates/Setting.cs index a56517e..0eaff3c 100644 --- a/Source/Starfish.Service/Domain/Aggregates/Setting.cs +++ b/Source/Starfish.Service/Domain/Aggregates/Setting.cs @@ -1,5 +1,6 @@ using Nerosoft.Euonia.Domain; using Nerosoft.Starfish.Service; +using Newtonsoft.Json; namespace Nerosoft.Starfish.Domain; @@ -32,14 +33,14 @@ private Setting() public string Environment { get; set; } /// - /// 描述 + /// 状态 /// - public string Description { get; set; } + public SettingStatus Status { get; set; } /// - /// 状态 + /// 当前版本号 /// - public SettingStatus Status { get; set; } + public string Version { get; set; } public DateTime CreateTime { get; set; } @@ -58,7 +59,7 @@ private Setting() /// /// 设置项 /// - public HashSet Nodes { get; set; } + public HashSet Items { get; set; } /// /// 历史版本 @@ -70,37 +71,46 @@ private Setting() /// public AppInfo App { get; set; } - internal static Setting Create(long appId, string appCode, string environment, string description, IDictionary nodes) + internal static Setting Create(long appId, string appCode, string environment, IDictionary items) { var setting = new Setting { AppId = appId, AppCode = appCode, Environment = environment, - Status = SettingStatus.Pending, - Description = description, - Nodes = [] + Status = SettingStatus.Pending }; - foreach (var (key, value) in nodes) - { - setting.AddNode(key, value); - } + setting.AddOrUpdateItem(items); setting.RaiseEvent(new SettingCreatedEvent(setting)); return setting; } - internal void AddNode(string key, string value) + internal void AddOrUpdateItem(IDictionary items) { - Nodes ??= []; - if (Nodes.Any(x => x.Key == key)) + if (Status == SettingStatus.Disabled) + { + throw new InvalidOperationException(""); + } + + Items ??= []; + Items.RemoveAll(t => !items.ContainsKey(t.Key)); + foreach (var (key, value) in items) { - throw new ConflictException(string.Format(Resources.IDS_ERROR_SETTING_NODE_KEY_EXISTS, key)); + var item = Items.FirstOrDefault(t => t.Key == key); + if (item == null) + { + item = new SettingItem(key, value); + Items.Add(item); + } + else + { + item.Value = value; + } } - var node = new SettingNode(key, value); - Nodes.Add(node); + Status = SettingStatus.Pending; } internal void SetStatus(SettingStatus status) @@ -113,13 +123,26 @@ internal void SetStatus(SettingStatus status) RaiseEvent(new SettingStatusChangedEvent(Status, status)); } - internal void SetDescription(string description) + internal void CreateRevision(string version, string comment, string @operator) { - if (string.Equals(Description, description)) + if (Items == null || Items.Count == 0) { return; } - Description = description; + Revisions ??= []; + + if (Revisions.Any(t => string.Equals(t.Version, version, StringComparison.OrdinalIgnoreCase))) + { + throw new InvalidOperationException($"版本号 {version} 已存在"); + } + + var data = JsonConvert.SerializeObject(Items, new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore }); + + var revision = new SettingRevision(version, comment, data, @operator); + + Revisions.Add(revision); + + Version = version; } } \ No newline at end of file diff --git a/Source/Starfish.Service/Domain/Aggregates/SettingNode.cs b/Source/Starfish.Service/Domain/Aggregates/SettingItem.cs similarity index 69% rename from Source/Starfish.Service/Domain/Aggregates/SettingNode.cs rename to Source/Starfish.Service/Domain/Aggregates/SettingItem.cs index 3020a50..0748005 100644 --- a/Source/Starfish.Service/Domain/Aggregates/SettingNode.cs +++ b/Source/Starfish.Service/Domain/Aggregates/SettingItem.cs @@ -7,13 +7,18 @@ namespace Nerosoft.Starfish.Domain; /// /// 配置信息 /// -public class SettingNode : Entity +public class SettingItem : Entity { - private SettingNode() + private SettingItem() { } - internal SettingNode(string key, string value) + public SettingItem(string key) + { + Key = key; + } + + internal SettingItem(string key, string value) : this() { Key = key; diff --git a/Source/Starfish.Service/Domain/Aggregates/SettingRevision.cs b/Source/Starfish.Service/Domain/Aggregates/SettingRevision.cs index e70d538..b7b4a71 100644 --- a/Source/Starfish.Service/Domain/Aggregates/SettingRevision.cs +++ b/Source/Starfish.Service/Domain/Aggregates/SettingRevision.cs @@ -8,6 +8,18 @@ namespace Nerosoft.Starfish.Domain; public class SettingRevision : Entity, IHasCreateTime { + private SettingRevision() + { + } + + internal SettingRevision(string version, string comment, string data, string @operator) + { + Version = version; + Comment = comment; + Data = data; + Operator = @operator; + } + public long SettingId { get; set; } /// diff --git a/Source/Starfish.Service/Domain/Business/SettingGeneralBusiness.cs b/Source/Starfish.Service/Domain/Business/SettingGeneralBusiness.cs index 1d0297e..42810ae 100644 --- a/Source/Starfish.Service/Domain/Business/SettingGeneralBusiness.cs +++ b/Source/Starfish.Service/Domain/Business/SettingGeneralBusiness.cs @@ -18,9 +18,7 @@ internal class SettingGeneralBusiness : EditableObjectBase IdProperty = RegisterProperty(p => p.Id); public static readonly PropertyInfo AppIdProperty = RegisterProperty(p => p.AppId); public static readonly PropertyInfo EnvironmentProperty = RegisterProperty(p => p.Environment); - public static readonly PropertyInfo DescriptionProperty = RegisterProperty(p => p.Description); - public static readonly PropertyInfo StatusProperty = RegisterProperty(p => p.Status); - public static readonly PropertyInfo> NodesProperty = RegisterProperty>(p => p.Nodes); + public static readonly PropertyInfo> ItemsProperty = RegisterProperty>(p => p.Items); public long Id { @@ -40,22 +38,10 @@ public string Environment set => SetProperty(EnvironmentProperty, value); } - public string Description + public IDictionary Items { - get => GetProperty(DescriptionProperty); - set => SetProperty(DescriptionProperty, value); - } - - public SettingStatus Status - { - get => GetProperty(StatusProperty); - set => SetProperty(StatusProperty, value); - } - - public IDictionary Nodes - { - get => GetProperty(NodesProperty); - set => SetProperty(NodesProperty, value); + get => GetProperty(ItemsProperty); + set => SetProperty(ItemsProperty, value); } protected override void AddRules() @@ -81,7 +67,6 @@ protected async Task FetchAsync(long id, CancellationToken cancellationToken = d Id = aggregate.Id; AppId = aggregate.AppId; Environment = aggregate.Environment; - Description = aggregate.Description; } } @@ -89,7 +74,7 @@ protected async Task FetchAsync(long id, CancellationToken cancellationToken = d protected override async Task InsertAsync(CancellationToken cancellationToken = default) { var appInfo = await AppInfoRepository.GetAsync(AppId, cancellationToken); - var aggregate = Setting.Create(AppId, appInfo.Code, Environment, Description, Nodes); + var aggregate = Setting.Create(AppId, appInfo.Code, Environment, Items); await SettingRepository.InsertAsync(aggregate, true, cancellationToken); Id = aggregate.Id; } @@ -97,14 +82,14 @@ protected override async Task InsertAsync(CancellationToken cancellationToken = [FactoryUpdate] protected override async Task UpdateAsync(CancellationToken cancellationToken = default) { - if (ChangedProperties.Contains(DescriptionProperty)) + if (!HasChangedProperties) { - Aggregate.SetDescription(Description); + return; } - if (ChangedProperties.Contains(StatusProperty)) + if (ChangedProperties.Contains(ItemsProperty)) { - Aggregate.SetStatus(Status); + Aggregate.AddOrUpdateItem(Items); } await SettingRepository.UpdateAsync(Aggregate, true, cancellationToken); diff --git a/Source/Starfish.Service/Domain/Enums/SettingNodeType.cs b/Source/Starfish.Service/Domain/Enums/SettingItemType.cs similarity index 94% rename from Source/Starfish.Service/Domain/Enums/SettingNodeType.cs rename to Source/Starfish.Service/Domain/Enums/SettingItemType.cs index 824a72f..0b58bf6 100644 --- a/Source/Starfish.Service/Domain/Enums/SettingNodeType.cs +++ b/Source/Starfish.Service/Domain/Enums/SettingItemType.cs @@ -3,7 +3,7 @@ /// /// 配置节点类型 /// -public enum SettingNodeType +public enum SettingItemType { /// /// 根节点 diff --git a/Source/Starfish.Service/Domain/Repositories/ISettingRepository.cs b/Source/Starfish.Service/Domain/Repositories/ISettingRepository.cs index c4c303f..5dcf244 100644 --- a/Source/Starfish.Service/Domain/Repositories/ISettingRepository.cs +++ b/Source/Starfish.Service/Domain/Repositories/ISettingRepository.cs @@ -28,9 +28,10 @@ public interface ISettingRepository : IRepository /// 查询符合条件的配置列表 /// /// - /// - /// + /// + /// + /// /// /// - Task> FindAsync(Expression> predicate, bool tracking, string[] properties, CancellationToken cancellationToken = default); + Task> FindAsync(Expression> predicate, Func, IQueryable> action, int page, int size, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/Source/Starfish.Service/Properties/Resources.resx b/Source/Starfish.Service/Properties/Resources.resx index a4a1738..b898c04 100644 --- a/Source/Starfish.Service/Properties/Resources.resx +++ b/Source/Starfish.Service/Properties/Resources.resx @@ -201,56 +201,35 @@ Published - + Array - + Boolean - + Number - + Object - + Referer - + Root - + String - + Setting key '{0}' exists. - - Node name is required. - - Setting node not exists, [Id]:{0}. - - - The node value is not valid boolean type. - - - The node value is not valid number type. - - - Not allow to add Root type node in child node. + Setting not exists, [Id]:{0}. - - No pending node to publish. - - - Only allow publish root node. - - - Can not add child node in node of type '{0}'. - - - Not allow to set value on node type of '{0}'. + + No pending item to publish. The secret not matches rule. @@ -258,9 +237,6 @@ App secret is required. - - Root node already exists. - Email '{0}' not available. diff --git a/Source/Starfish.Service/Properties/Resources.zh-CN.resx b/Source/Starfish.Service/Properties/Resources.zh-CN.resx index fc5a763..c05aa50 100644 --- a/Source/Starfish.Service/Properties/Resources.zh-CN.resx +++ b/Source/Starfish.Service/Properties/Resources.zh-CN.resx @@ -201,25 +201,25 @@ 已发布 - + 集合 - + 布尔 - + 数字 - + 对象 - + 引用 - + 根节点 - + 字符串 \ No newline at end of file diff --git a/Source/Starfish.Service/Repository/Contexts/SqliteModelBuilder.cs b/Source/Starfish.Service/Repository/Contexts/SqliteModelBuilder.cs index 0de291b..0ef75f7 100644 --- a/Source/Starfish.Service/Repository/Contexts/SqliteModelBuilder.cs +++ b/Source/Starfish.Service/Repository/Contexts/SqliteModelBuilder.cs @@ -79,7 +79,7 @@ public void Configure(ModelBuilder modelBuilder) .HasForeignKey(t => t.AppId) .OnDelete(DeleteBehavior.Cascade); - entity.HasMany(t => t.Nodes) + entity.HasMany(t => t.Items) .WithOne() .HasForeignKey(t => t.SettingId) .OnDelete(DeleteBehavior.Cascade); @@ -90,7 +90,7 @@ public void Configure(ModelBuilder modelBuilder) .OnDelete(DeleteBehavior.Cascade); }); - modelBuilder.Entity(entity => + modelBuilder.Entity(entity => { entity.ToTable("setting_item"); entity.HasKey(t => t.Id); @@ -98,7 +98,7 @@ public void Configure(ModelBuilder modelBuilder) entity.HasIndex(t => t.Key); - entity.HasIndex([nameof(SettingNode.SettingId), nameof(SettingNode.Key)], "IDX_SETTING_ITEM_UNIQUE") + entity.HasIndex([nameof(SettingItem.SettingId), nameof(SettingItem.Key)], "IDX_SETTING_ITEM_UNIQUE") .IsUnique(); entity.Property(t => t.Id) diff --git a/Source/Starfish.Service/Repository/Repositories/SettingRepository.cs b/Source/Starfish.Service/Repository/Repositories/SettingRepository.cs index 7f10c0b..cdbd6c9 100644 --- a/Source/Starfish.Service/Repository/Repositories/SettingRepository.cs +++ b/Source/Starfish.Service/Repository/Repositories/SettingRepository.cs @@ -1,4 +1,6 @@ -using Nerosoft.Euonia.Linq; +using System.Linq.Expressions; +using Microsoft.EntityFrameworkCore; +using Nerosoft.Euonia.Linq; using Nerosoft.Euonia.Repository; using Nerosoft.Starfish.Domain; using Nerosoft.Starfish.Service; @@ -23,4 +25,16 @@ public Task ExistsAsync(long appId, string environment, CancellationToken var predicate = new CompositeSpecification(PredicateOperator.AndAlso, specifications).Satisfy(); return ExistsAsync(predicate, cancellationToken); } + + public Task> FindAsync(Expression> predicate, Func, IQueryable> action, int page, int size, CancellationToken cancellationToken = default) + { + var query = Context.Set().AsQueryable(); + query = query.Where(predicate); + if (action != null) + { + query = action(query); + } + + return query.Paginate(page, size).ToListAsync(cancellationToken); + } } \ No newline at end of file diff --git a/Source/Starfish.Service/UseCases/Setting/SettingCountUseCase.cs b/Source/Starfish.Service/UseCases/Setting/SettingCountUseCase.cs index 6ea3b76..4dc554a 100644 --- a/Source/Starfish.Service/UseCases/Setting/SettingCountUseCase.cs +++ b/Source/Starfish.Service/UseCases/Setting/SettingCountUseCase.cs @@ -1,4 +1,5 @@ using Nerosoft.Euonia.Application; +using Nerosoft.Euonia.Repository.EfCore; using Nerosoft.Starfish.Domain; using Nerosoft.Starfish.Repository; using Nerosoft.Starfish.Transit; @@ -43,7 +44,8 @@ public Task ExecuteAsync(SettingCountInput input, Cancellati { var specification = input.Criteria.GetSpecification(); var predicate = specification.Satisfy(); - return _repository.CountAsync(predicate, cancellationToken) + return _repository.Include(t => t.App) + .CountAsync(predicate, cancellationToken) .ContinueWith(t => new SettingCountOutput(t.Result), cancellationToken); } } \ No newline at end of file diff --git a/Source/Starfish.Service/UseCases/Setting/SettingCreateUseCase.cs b/Source/Starfish.Service/UseCases/Setting/SettingCreateUseCase.cs index a9b78f9..ec7088a 100644 --- a/Source/Starfish.Service/UseCases/Setting/SettingCreateUseCase.cs +++ b/Source/Starfish.Service/UseCases/Setting/SettingCreateUseCase.cs @@ -2,6 +2,7 @@ using Nerosoft.Euonia.Bus; using Nerosoft.Euonia.Mapping; using Nerosoft.Starfish.Application; +using Nerosoft.Starfish.Common; using Nerosoft.Starfish.Transit; namespace Nerosoft.Starfish.UseCases; @@ -19,11 +20,12 @@ public SettingCreateUseCase(IBus bus) public Task ExecuteAsync(SettingCreateDto input, CancellationToken cancellationToken = default) { + var data = Cryptography.Base64.Decrypt(input.Data); var command = TypeAdapter.ProjectedAs(input); - command.Data = input.DataType switch + command.Data = input.Type switch { - "json" => JsonConfigurationFileParser.Parse(input.ItemsData), - "text" => TextConfigurationFileParser.Parse(input.ItemsData), + "json" => JsonConfigurationFileParser.Parse(data), + "text" => TextConfigurationFileParser.Parse(data), _ => default }; return _bus.SendAsync(command, cancellationToken) diff --git a/Source/Starfish.Service/UseCases/Setting/SettingGetDetailUseCase.cs b/Source/Starfish.Service/UseCases/Setting/SettingGetDetailUseCase.cs index 1527951..74dc197 100644 --- a/Source/Starfish.Service/UseCases/Setting/SettingGetDetailUseCase.cs +++ b/Source/Starfish.Service/UseCases/Setting/SettingGetDetailUseCase.cs @@ -41,7 +41,7 @@ public SettingGetDetailUseCase(ISettingRepository repository) /// public Task ExecuteAsync(SettingGetDetailInput input, CancellationToken cancellationToken = default) { - return _repository.GetAsync(input.Id, false, Array.Empty(), cancellationToken) + return _repository.GetAsync(input.Id, false, [nameof(Setting.App)], cancellationToken) .ContinueWith(task => { task.WaitAndUnwrapException(cancellationToken); diff --git a/Source/Starfish.Service/UseCases/Setting/SettingSearchUseCase.cs b/Source/Starfish.Service/UseCases/Setting/SettingSearchUseCase.cs index 438cf2c..64f3dba 100644 --- a/Source/Starfish.Service/UseCases/Setting/SettingSearchUseCase.cs +++ b/Source/Starfish.Service/UseCases/Setting/SettingSearchUseCase.cs @@ -1,4 +1,5 @@ using Nerosoft.Euonia.Application; +using Nerosoft.Euonia.Repository.EfCore; using Nerosoft.Starfish.Domain; using Nerosoft.Starfish.Repository; using Nerosoft.Starfish.Transit; @@ -45,7 +46,8 @@ public Task ExecuteAsync(SettingSearchInput input, Cancella { var specification = input.Criteria.GetSpecification(); var predicate = specification.Satisfy(); - return _repository.FetchAsync(predicate, Collator, input.Page, input.Size, cancellationToken) + return _repository.Include(t => t.App) + .FetchAsync(predicate, Collator, input.Page, input.Size, cancellationToken) .ContinueWith(task => { task.WaitAndUnwrapException(cancellationToken); diff --git a/Source/Starfish.Service/UseCases/Setting/SettingUpdateUseCase.cs b/Source/Starfish.Service/UseCases/Setting/SettingUpdateUseCase.cs new file mode 100644 index 0000000..1b397b6 --- /dev/null +++ b/Source/Starfish.Service/UseCases/Setting/SettingUpdateUseCase.cs @@ -0,0 +1,40 @@ +using Nerosoft.Euonia.Application; +using Nerosoft.Euonia.Bus; +using Nerosoft.Starfish.Application; +using Nerosoft.Starfish.Common; +using Nerosoft.Starfish.Transit; + +namespace Nerosoft.Starfish.UseCases; + +public interface ISettingUpdateUseCase : INonOutputUseCase; + +public record SettingUpdateInput(long Id, SettingUpdateDto Data); + +public class SettingUpdateUseCase : ISettingUpdateUseCase +{ + private readonly IBus _bus; + + public SettingUpdateUseCase(IBus bus) + { + _bus = bus; + } + + public Task ExecuteAsync(SettingUpdateInput input, CancellationToken cancellationToken = new CancellationToken()) + { + var data = Cryptography.Base64.Decrypt(input.Data.Data); + var command = new SettingUpdateCommand(input.Id) + { + Data = input.Data.Type switch + { + "json" => JsonConfigurationFileParser.Parse(data), + "text" => TextConfigurationFileParser.Parse(data), + _ => default + } + }; + return _bus.SendAsync(command, cancellationToken) + .ContinueWith(task => + { + task.WaitAndUnwrapException(cancellationToken); + }, cancellationToken); + } +} \ No newline at end of file diff --git a/Source/Starfish.Transit/Setting/SettingCreateDto.cs b/Source/Starfish.Transit/Setting/SettingCreateDto.cs index d911ec8..1a65198 100644 --- a/Source/Starfish.Transit/Setting/SettingCreateDto.cs +++ b/Source/Starfish.Transit/Setting/SettingCreateDto.cs @@ -24,10 +24,10 @@ public class SettingCreateDto /// 数据类型 /// /// 可选值json|text - public string DataType { get; set; } + public string Type { get; set; } /// /// 配置项内容 /// - public string ItemsData { get; set; } + public string Data { get; set; } } \ No newline at end of file diff --git a/Source/Starfish.Transit/Setting/SettingDetailDto.cs b/Source/Starfish.Transit/Setting/SettingDetailDto.cs index e8ea482..260fa04 100644 --- a/Source/Starfish.Transit/Setting/SettingDetailDto.cs +++ b/Source/Starfish.Transit/Setting/SettingDetailDto.cs @@ -31,9 +31,9 @@ public class SettingDetailDto public string Environment { get; set; } /// - /// 描述 + /// 当前版本号 /// - public string Description { get; set; } + public string Version { get; set; } /// /// 状态 diff --git a/Source/Starfish.Transit/Setting/SettingItemDto.cs b/Source/Starfish.Transit/Setting/SettingItemDto.cs index 5614805..852129c 100644 --- a/Source/Starfish.Transit/Setting/SettingItemDto.cs +++ b/Source/Starfish.Transit/Setting/SettingItemDto.cs @@ -31,9 +31,9 @@ public class SettingItemDto public string Environment { get; set; } /// - /// 描述 + /// 当前版本号 /// - public string Description { get; set; } + public string Version { get; set; } /// /// 状态 diff --git a/Source/Starfish.Transit/Setting/SettingNodeDetailDto.cs b/Source/Starfish.Transit/Setting/SettingNodeDetailDto.cs deleted file mode 100644 index dde9ac5..0000000 --- a/Source/Starfish.Transit/Setting/SettingNodeDetailDto.cs +++ /dev/null @@ -1,70 +0,0 @@ -namespace Nerosoft.Starfish.Transit; - -/// -/// 配置节点详情Dto -/// -public class SettingNodeDetailDto -{ - /// - /// 配置节点Id - /// - public long Id { get; set; } - - /// - /// 父节点Id - /// - /// - /// 0表示根节点 - /// - public long ParentId { get; set; } - - /// - /// 应用Id - /// - public long AppId { get; set; } - - /// - /// 环境名称 - /// - public string Environment { get; set; } - - /// - /// 配置名称 - /// - public string Name { get; set; } - - /// - /// 配置值 - /// - public string Value { get; set; } - - /// - /// 配置描述 - /// - public string Description { get; set; } - - /// - /// 配置节点类型 - /// - public string Type { get; set; } - - /// - /// 节点类型描述 - /// - public string TypeDescription { get; set; } - - /// - /// 是否是根节点 - /// - public string IsRoot { get; set; } - - /// - /// 配置节点排序 - /// - public int Sort { get; set; } - - /// - /// 配置唯一键 - /// - public string Key { get; set; } -} \ No newline at end of file diff --git a/Source/Starfish.Transit/Setting/SettingNodeItemDto.cs b/Source/Starfish.Transit/Setting/SettingNodeItemDto.cs deleted file mode 100644 index 72223d0..0000000 --- a/Source/Starfish.Transit/Setting/SettingNodeItemDto.cs +++ /dev/null @@ -1,57 +0,0 @@ -namespace Nerosoft.Starfish.Transit; - -/// -/// 配置节点Dto -/// -public class SettingNodeItemDto -{ - /// - /// 配置节点Id - /// - public long Id { get; set; } - - /// - /// 应用唯一编码 - /// - public string AppCode { get; set; } - - /// - /// 环境名称 - /// - public string Environment { get; set; } - - /// - /// 配置名称 - /// - public string Name { get; set; } - - /// - /// 配置值 - /// - public string Value { get; set; } - - /// - /// 配置描述 - /// - public string Description { get; set; } - - /// - /// 配置节点类型 - /// - public string Type { get; set; } - - /// - /// 节点类型描述 - /// - public string TypeDescription { get; set; } - - /// - /// 是否是根节点 - /// - public string IsRoot { get; set; } - - /// - /// 配置唯一键 - /// - public string Key { get; set; } -} \ No newline at end of file diff --git a/Source/Starfish.Transit/Setting/SettingNodeUpdateDto.cs b/Source/Starfish.Transit/Setting/SettingNodeUpdateDto.cs deleted file mode 100644 index cad296f..0000000 --- a/Source/Starfish.Transit/Setting/SettingNodeUpdateDto.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace Nerosoft.Starfish.Transit; - -/// -/// 配置节点更新Dto -/// -public class SettingNodeUpdateDto -{ - /// - /// 名称 - /// - public string Name { get; set; } - - /// - /// 配置值 - /// - public string Value { get; set; } - - /// - /// 配置描述 - /// - /// - /// 仅在更新节点描述时有效 - /// - public string Description { get; set; } -} \ No newline at end of file diff --git a/Source/Starfish.Transit/Setting/SettingUpdateDto.cs b/Source/Starfish.Transit/Setting/SettingUpdateDto.cs new file mode 100644 index 0000000..4379f7c --- /dev/null +++ b/Source/Starfish.Transit/Setting/SettingUpdateDto.cs @@ -0,0 +1,17 @@ +namespace Nerosoft.Starfish.Transit; + +/// +/// 配置更新Dto +/// +public class SettingUpdateDto +{ + /// + /// 数据类型 + /// + public string Type { get; set; } + + /// + /// 配置内容 + /// + public string Data { get; set; } +} \ No newline at end of file diff --git a/Source/Starfish.Webapi/Controllers/DictionaryController.cs b/Source/Starfish.Webapi/Controllers/DictionaryController.cs index b520106..8bdd4ea 100644 --- a/Source/Starfish.Webapi/Controllers/DictionaryController.cs +++ b/Source/Starfish.Webapi/Controllers/DictionaryController.cs @@ -63,7 +63,7 @@ public async Task GetDatabaseTypeItemsAsync() [HttpGet("setting-node-type")] public async Task GetSettingNodeTypeItemsAsync() { - var result = await _service.GetSettingNodeTypeItemsAsync(); + var result = await _service.GetSettingItemTypeItemsAsync(); return Ok(result); } } \ No newline at end of file diff --git a/Source/Starfish.Webapi/Controllers/SettingController.cs b/Source/Starfish.Webapi/Controllers/SettingController.cs index 237aa24..ce05a8f 100644 --- a/Source/Starfish.Webapi/Controllers/SettingController.cs +++ b/Source/Starfish.Webapi/Controllers/SettingController.cs @@ -1,4 +1,5 @@ -using Microsoft.AspNetCore.Authorization; +using System.Web; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Nerosoft.Starfish.Application; using Nerosoft.Starfish.Transit; @@ -8,7 +9,7 @@ namespace Nerosoft.Starfish.Webapi.Controllers; /// /// 应用配置管理接口 /// -[Route("api/[controller]/node")] +[Route("api/[controller]")] [ApiController, ApiExplorerSettings(GroupName = "setting")] [Authorize] public class SettingController : ControllerBase @@ -75,6 +76,19 @@ public async Task CreateAsync([FromBody] SettingCreateDto data) return Ok(); } + /// + /// 更新配置 + /// + /// + /// + /// + [HttpPut("{id:long}")] + public async Task UpdateAsync(long id, [FromBody] SettingUpdateDto data) + { + await _service.UpdateAsync(id, data, HttpContext.RequestAborted); + return Ok(); + } + /// /// 删除配置节点 /// @@ -87,6 +101,22 @@ public async Task DeleteAsync(long id) return Ok(); } + /// + /// 更新配置项的值 + /// + /// 配置Id + /// 完整Key名称 + /// + /// + [HttpPut("{id:long}/{key}")] + [Consumes("plain/text")] + public async Task UpdateItemAsync(long id, string key, [FromBody] string value) + { + key = HttpUtility.UrlDecode(key); + await _service.UpdateAsync(id, key, value, HttpContext.RequestAborted); + return Ok(); + } + /// /// 发布配置 /// From f7c0fa4fa4d7f8cf33c817558ac05cdf75290f22 Mon Sep 17 00:00:00 2001 From: damon Date: Tue, 2 Jan 2024 01:13:34 +0800 Subject: [PATCH 04/22] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=B0=86=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E9=A1=B9=E5=8F=8D=E5=90=91=E8=BD=AC=E6=8D=A2=E4=B8=BA?= =?UTF-8?q?Json=E7=9A=84=E6=96=B9=E6=B3=95=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Handlers/SettingArchiveCommandHandler.cs | 6 +- .../JsonConfigurationFileParser.cs | 79 ++++++++++++++++++- .../TextConfigurationFileParser.cs | 16 +++- .../Controllers/DictionaryController.cs | 4 +- 4 files changed, 98 insertions(+), 7 deletions(-) diff --git a/Source/Starfish.Service/Application/Handlers/SettingArchiveCommandHandler.cs b/Source/Starfish.Service/Application/Handlers/SettingArchiveCommandHandler.cs index e41db2b..7b06771 100644 --- a/Source/Starfish.Service/Application/Handlers/SettingArchiveCommandHandler.cs +++ b/Source/Starfish.Service/Application/Handlers/SettingArchiveCommandHandler.cs @@ -20,8 +20,8 @@ public Task HandleAsync(SettingArchiveCreateCommand message, MessageContext cont { return ExecuteAsync(async () => { - var (appId, appCode, environment, nodes) = await GetNodesAsync(message.RootId, cancellationToken); - var data = nodes.ToDictionary(t => t.Key, t => t.Value); + var (appId, appCode, environment, items) = await GetItemsAsync(message.RootId, cancellationToken); + var data = items.ToDictionary(t => t.Key, t => t.Value); var json = JsonConvert.SerializeObject(data); var userName = context?.User?.Identity?.Name; @@ -30,7 +30,7 @@ public Task HandleAsync(SettingArchiveCreateCommand message, MessageContext cont }); } - private async Task>> GetNodesAsync(long id, CancellationToken cancellationToken = default) + private async Task>> GetItemsAsync(long id, CancellationToken cancellationToken = default) { var repository = UnitOfWork.Current.GetService(); diff --git a/Source/Starfish.Service/Application/JsonConfigurationFileParser.cs b/Source/Starfish.Service/Application/JsonConfigurationFileParser.cs index 2b41b08..14a5538 100644 --- a/Source/Starfish.Service/Application/JsonConfigurationFileParser.cs +++ b/Source/Starfish.Service/Application/JsonConfigurationFileParser.cs @@ -3,7 +3,7 @@ namespace Nerosoft.Starfish.Application; -public class JsonConfigurationFileParser +internal partial class JsonConfigurationFileParser { private const string KEY_DELIMITER = ":"; @@ -121,4 +121,81 @@ private void VisitValue(JsonElement value) private void EnterContext(string context) => _paths.Push(_paths.Count > 0 ? _paths.Peek() + KEY_DELIMITER + context : context); private void ExitContext() => _paths.Pop(); +} + +internal partial class JsonConfigurationFileParser +{ + public static string InvertParsed(IDictionary data) + { + var root = new Dictionary(StringComparer.OrdinalIgnoreCase); + + foreach (var entry in data) + { + var path = entry.Key.Split(":"); + var current = root; + + for (var i = 0; i < path.Length - 1; i++) + { + if (!current.TryGetValue(path[i], out var child)) + { + child = new Dictionary(StringComparer.OrdinalIgnoreCase); + current[path[i]] = child; + } + + current = (Dictionary)child; + } + + current[path[^1]] = entry.Value; + } + + var result = Rebuild(root); + + return JsonSerializer.Serialize(result, new JsonSerializerOptions { WriteIndented = true }); + + object Rebuild(object source) + { + switch (source) + { + case IDictionary dict: + if (DetectArray(dict)) + { + object array = ConvertToArray(dict); + array = Rebuild(array); + return array; + } + else + { + var keys = dict.Keys.Select(x => x).ToList(); + foreach (var key in keys) + { + var val = dict[key]; + dict[key] = Rebuild(val); + } + + return dict; + } + + case object[] array: + for (var index = 0; index < array.Length; index++) + { + array[index] = Rebuild(array[index]); + } + + return array; + } + + return source; + } + + bool DetectArray(IDictionary dictionary) + { + var keys = Enumerable.Range(0, dictionary.Count).Select(t => t.ToString()); + return dictionary.Keys.All(key => keys.Contains(key)); + } + + object[] ConvertToArray(IDictionary dictionary) + { + return dictionary.Values.ToArray(); + } + } } \ No newline at end of file diff --git a/Source/Starfish.Service/Application/TextConfigurationFileParser.cs b/Source/Starfish.Service/Application/TextConfigurationFileParser.cs index f8ed714..3ec5390 100644 --- a/Source/Starfish.Service/Application/TextConfigurationFileParser.cs +++ b/Source/Starfish.Service/Application/TextConfigurationFileParser.cs @@ -1,6 +1,6 @@ namespace Nerosoft.Starfish.Application; -public class TextConfigurationFileParser +public partial class TextConfigurationFileParser { private TextConfigurationFileParser() { @@ -49,4 +49,18 @@ private Dictionary ParseStream(Stream input) return data; } +} + +public partial class TextConfigurationFileParser +{ + public static string InvertParsed(IDictionary data) + { + var builder = new StringBuilder(); + foreach (var (key, value) in data) + { + builder.AppendLine($"{key}={value}"); + } + + return builder.ToString(); + } } \ No newline at end of file diff --git a/Source/Starfish.Webapi/Controllers/DictionaryController.cs b/Source/Starfish.Webapi/Controllers/DictionaryController.cs index 8bdd4ea..b399e51 100644 --- a/Source/Starfish.Webapi/Controllers/DictionaryController.cs +++ b/Source/Starfish.Webapi/Controllers/DictionaryController.cs @@ -60,8 +60,8 @@ public async Task GetDatabaseTypeItemsAsync() /// 获取支持的配置节点类型列表 /// /// - [HttpGet("setting-node-type")] - public async Task GetSettingNodeTypeItemsAsync() + [HttpGet("setting-item-type")] + public async Task GetSettingItemTypeItemsAsync() { var result = await _service.GetSettingItemTypeItemsAsync(); return Ok(result); From d2a2f3070fbb785eaa2b7fe232e0cb95645731fd Mon Sep 17 00:00:00 2001 From: damon Date: Tue, 2 Jan 2024 18:06:42 +0800 Subject: [PATCH 05/22] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Directory.Packages.props | 60 +++++++++++-------- Samples/common.props | 10 +--- Source/Starfish.Client/Starfish.Client.csproj | 1 + Source/Starfish.Etcd/Starfish.Etcd.csproj | 1 + Source/Starfish.Redis/Starfish.Redis.csproj | 1 + Source/Starfish.Webapi/Starfish.Webapi.csproj | 2 +- Source/common.props | 11 +--- Starfish.sln | 1 + Tests/common.props | 10 +--- global.props | 15 +++++ 10 files changed, 60 insertions(+), 52 deletions(-) create mode 100644 global.props diff --git a/Directory.Packages.props b/Directory.Packages.props index 1935835..8df8b9b 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -5,6 +5,7 @@ 8.1.15 + @@ -31,9 +32,16 @@ + + + + + + + @@ -44,57 +52,57 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - + + + + + + + + + - - - - - - - - + + + + + + + + + - - - - - - - - + + + + + + + + diff --git a/Samples/common.props b/Samples/common.props index 2e79eb2..43f88b6 100644 --- a/Samples/common.props +++ b/Samples/common.props @@ -1,14 +1,8 @@ + + Nerosoft.Starfish.Sample - 1.0.0 - damon - Nerosoft Co., Ltd. - Starfish - © 2018-2023 Nerosoft. All Rights Reserved. - enable - disable - latest A lightweight powerful distributed configuration server for .NET application. https://github.com/NerosoftDev/Starfish/ diff --git a/Source/Starfish.Client/Starfish.Client.csproj b/Source/Starfish.Client/Starfish.Client.csproj index 70053fc..2720ca3 100644 --- a/Source/Starfish.Client/Starfish.Client.csproj +++ b/Source/Starfish.Client/Starfish.Client.csproj @@ -3,6 +3,7 @@ + net6.0;net7.0;net8.0 true Sratfish configuration client README.md diff --git a/Source/Starfish.Etcd/Starfish.Etcd.csproj b/Source/Starfish.Etcd/Starfish.Etcd.csproj index 0319c3f..09208f8 100644 --- a/Source/Starfish.Etcd/Starfish.Etcd.csproj +++ b/Source/Starfish.Etcd/Starfish.Etcd.csproj @@ -3,6 +3,7 @@ + net6.0;net7.0;net8.0 true Microsoft.Extensions.Configuration.Etcd README.md diff --git a/Source/Starfish.Redis/Starfish.Redis.csproj b/Source/Starfish.Redis/Starfish.Redis.csproj index b7d69f4..c858fdc 100644 --- a/Source/Starfish.Redis/Starfish.Redis.csproj +++ b/Source/Starfish.Redis/Starfish.Redis.csproj @@ -3,6 +3,7 @@ + net6.0;net7.0;net8.0 true Microsoft.Extensions.Configuration.Redis README.md diff --git a/Source/Starfish.Webapi/Starfish.Webapi.csproj b/Source/Starfish.Webapi/Starfish.Webapi.csproj index 25e7bc1..aa4a0bc 100644 --- a/Source/Starfish.Webapi/Starfish.Webapi.csproj +++ b/Source/Starfish.Webapi/Starfish.Webapi.csproj @@ -3,7 +3,7 @@ - net7.0;net8.0 + net8.0 disable enable true diff --git a/Source/common.props b/Source/common.props index 04ec62d..65fda54 100644 --- a/Source/common.props +++ b/Source/common.props @@ -1,15 +1,8 @@ + + - net6.0;net7.0;net8.0 Nerosoft.$(MSBuildProjectName.Replace(" ", "_")) - 1.0.2 - damon - Nerosoft Ltd. - Starfish - © 2018-2023 Nerosoft. All Rights Reserved. - enable - disable - latest https://github.com/NerosoftDev/Starfish/ https://github.com/NerosoftDev/Starfish/ .net, asp.net, core, configuration, config, appsettings, configuration-management, configuration-service, configuration-server, configuration-center diff --git a/Starfish.sln b/Starfish.sln index d3392cf..2468735 100644 --- a/Starfish.sln +++ b/Starfish.sln @@ -7,6 +7,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution", "Solution", "{9B ProjectSection(SolutionItems) = preProject Directory.Packages.props = Directory.Packages.props README.md = README.md + global.props = global.props EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Source", "Source", "{31CE15C0-0BE0-4444-AC72-9E3C2B87BD7C}" diff --git a/Tests/common.props b/Tests/common.props index bdf5caa..9d81458 100644 --- a/Tests/common.props +++ b/Tests/common.props @@ -1,15 +1,9 @@ + + net7.0;net8.0 Nerosoft.$(MSBuildProjectName.Replace(" ", "_")) - 1.0.0 - damon - Nerosoft Co., Ltd. - Starfish - © 2018-2023 Nerosoft. All Rights Reserved. - enable - disable - latest false true diff --git a/global.props b/global.props new file mode 100644 index 0000000..f9187d7 --- /dev/null +++ b/global.props @@ -0,0 +1,15 @@ + + + 1.0.3 + damon + Nerosoft Ltd. + Starfish + © 2018-2024 Nerosoft. All Rights Reserved. + + + + enable + disable + latest + + From cbec48b7573f4409efc6c69202f7d9decfe4353c Mon Sep 17 00:00:00 2001 From: damon Date: Tue, 2 Jan 2024 18:17:16 +0800 Subject: [PATCH 06/22] =?UTF-8?q?=E5=88=A0=E9=99=A4=E9=87=8D=E5=A4=8D?= =?UTF-8?q?=E7=9A=84=E9=85=8D=E7=BD=AE=E9=A1=B9=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Source/Starfish.Common/Starfish.Common.csproj | 2 -- Source/Starfish.Service/Starfish.Service.csproj | 2 -- Source/Starfish.Transit/Starfish.Transit.csproj | 2 -- Source/Starfish.Webapi/Starfish.Webapi.csproj | 2 -- 4 files changed, 8 deletions(-) diff --git a/Source/Starfish.Common/Starfish.Common.csproj b/Source/Starfish.Common/Starfish.Common.csproj index ee8ab51..779ac8d 100644 --- a/Source/Starfish.Common/Starfish.Common.csproj +++ b/Source/Starfish.Common/Starfish.Common.csproj @@ -4,8 +4,6 @@ netstandard2.1;net6.0;net7.0;net8.0 - enable - disable false diff --git a/Source/Starfish.Service/Starfish.Service.csproj b/Source/Starfish.Service/Starfish.Service.csproj index a804ec2..8fbd624 100644 --- a/Source/Starfish.Service/Starfish.Service.csproj +++ b/Source/Starfish.Service/Starfish.Service.csproj @@ -4,8 +4,6 @@ net7.0;net8.0 - enable - disable false diff --git a/Source/Starfish.Transit/Starfish.Transit.csproj b/Source/Starfish.Transit/Starfish.Transit.csproj index 77c31ef..392d3bd 100644 --- a/Source/Starfish.Transit/Starfish.Transit.csproj +++ b/Source/Starfish.Transit/Starfish.Transit.csproj @@ -4,8 +4,6 @@ netstandard2.1;net6.0;net7.0;net8.0 - enable - disable false diff --git a/Source/Starfish.Webapi/Starfish.Webapi.csproj b/Source/Starfish.Webapi/Starfish.Webapi.csproj index aa4a0bc..4e3a81b 100644 --- a/Source/Starfish.Webapi/Starfish.Webapi.csproj +++ b/Source/Starfish.Webapi/Starfish.Webapi.csproj @@ -4,8 +4,6 @@ net8.0 - disable - enable true A lightweight powerful distributed configuration server for .NET application. From 2a3d1c7443eef815843da3b9f92b5f26e8b2c051 Mon Sep 17 00:00:00 2001 From: damon Date: Tue, 2 Jan 2024 18:38:48 +0800 Subject: [PATCH 07/22] =?UTF-8?q?=E6=B7=BB=E5=8A=A0Webapp=E9=A1=B9?= =?UTF-8?q?=E7=9B=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Directory.Packages.props | 3 + Source/Starfish.Webapp/App.razor | 12 +++ .../Starfish.Webapp/Layout/EmptyLayout.razor | 3 + .../Starfish.Webapp/Layout/MainLayout.razor | 3 + Source/Starfish.Webapp/Pages/Home.razor | 7 ++ Source/Starfish.Webapp/Program.cs | 17 ++++ .../Properties/launchSettings.json | 41 ++++++++++ Source/Starfish.Webapp/Starfish.Webapp.csproj | 56 +++++++++++++ Source/Starfish.Webapp/_Imports.razor | 10 +++ Source/Starfish.Webapp/wwwroot/css/app.css | 77 ++++++++++++++++++ Source/Starfish.Webapp/wwwroot/icon-192.png | Bin 0 -> 2626 bytes Source/Starfish.Webapp/wwwroot/index.html | 29 +++++++ Starfish.sln | 7 ++ 13 files changed, 265 insertions(+) create mode 100644 Source/Starfish.Webapp/App.razor create mode 100644 Source/Starfish.Webapp/Layout/EmptyLayout.razor create mode 100644 Source/Starfish.Webapp/Layout/MainLayout.razor create mode 100644 Source/Starfish.Webapp/Pages/Home.razor create mode 100644 Source/Starfish.Webapp/Program.cs create mode 100644 Source/Starfish.Webapp/Properties/launchSettings.json create mode 100644 Source/Starfish.Webapp/Starfish.Webapp.csproj create mode 100644 Source/Starfish.Webapp/_Imports.razor create mode 100644 Source/Starfish.Webapp/wwwroot/css/app.css create mode 100644 Source/Starfish.Webapp/wwwroot/icon-192.png create mode 100644 Source/Starfish.Webapp/wwwroot/index.html diff --git a/Directory.Packages.props b/Directory.Packages.props index 8df8b9b..0f10496 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -35,6 +35,9 @@ + + + diff --git a/Source/Starfish.Webapp/App.razor b/Source/Starfish.Webapp/App.razor new file mode 100644 index 0000000..6fd3ed1 --- /dev/null +++ b/Source/Starfish.Webapp/App.razor @@ -0,0 +1,12 @@ + + + + + + + Not found + +

Sorry, there's nothing at this address.

+
+
+
diff --git a/Source/Starfish.Webapp/Layout/EmptyLayout.razor b/Source/Starfish.Webapp/Layout/EmptyLayout.razor new file mode 100644 index 0000000..53a4e95 --- /dev/null +++ b/Source/Starfish.Webapp/Layout/EmptyLayout.razor @@ -0,0 +1,3 @@ +@inherits LayoutComponentBase + +@Body \ No newline at end of file diff --git a/Source/Starfish.Webapp/Layout/MainLayout.razor b/Source/Starfish.Webapp/Layout/MainLayout.razor new file mode 100644 index 0000000..e1a9a75 --- /dev/null +++ b/Source/Starfish.Webapp/Layout/MainLayout.razor @@ -0,0 +1,3 @@ +@inherits LayoutComponentBase + +@Body diff --git a/Source/Starfish.Webapp/Pages/Home.razor b/Source/Starfish.Webapp/Pages/Home.razor new file mode 100644 index 0000000..9001e0b --- /dev/null +++ b/Source/Starfish.Webapp/Pages/Home.razor @@ -0,0 +1,7 @@ +@page "/" + +Home + +

Hello, world!

+ +Welcome to your new app. diff --git a/Source/Starfish.Webapp/Program.cs b/Source/Starfish.Webapp/Program.cs new file mode 100644 index 0000000..af8cc8d --- /dev/null +++ b/Source/Starfish.Webapp/Program.cs @@ -0,0 +1,17 @@ +using Microsoft.AspNetCore.Components.Web; +using Microsoft.AspNetCore.Components.WebAssembly.Hosting; + +namespace Nerosoft.Starfish.Webapp; +public class Program +{ + public static async Task Main(string[] args) + { + var builder = WebAssemblyHostBuilder.CreateDefault(args); + builder.RootComponents.Add("#app"); + builder.RootComponents.Add("head::after"); + + builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); + + await builder.Build().RunAsync(); + } +} diff --git a/Source/Starfish.Webapp/Properties/launchSettings.json b/Source/Starfish.Webapp/Properties/launchSettings.json new file mode 100644 index 0000000..d1e5941 --- /dev/null +++ b/Source/Starfish.Webapp/Properties/launchSettings.json @@ -0,0 +1,41 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:5090", + "sslPort": 7090 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", + "applicationUrl": "http://localhost:5090", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", + "applicationUrl": "https://localhost:7090;http://localhost:5090", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/Source/Starfish.Webapp/Starfish.Webapp.csproj b/Source/Starfish.Webapp/Starfish.Webapp.csproj new file mode 100644 index 0000000..a4c69a6 --- /dev/null +++ b/Source/Starfish.Webapp/Starfish.Webapp.csproj @@ -0,0 +1,56 @@ + + + + + + net8.0 + true + A lightweight powerful distributed configuration server for .NET application. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/Starfish.Webapp/_Imports.razor b/Source/Starfish.Webapp/_Imports.razor new file mode 100644 index 0000000..0ded3aa --- /dev/null +++ b/Source/Starfish.Webapp/_Imports.razor @@ -0,0 +1,10 @@ +@using System.Net.Http +@using System.Net.Http.Json +@using Microsoft.AspNetCore.Components.Forms +@using Microsoft.AspNetCore.Components.Routing +@using Microsoft.AspNetCore.Components.Web +@using Microsoft.AspNetCore.Components.Web.Virtualization +@using Microsoft.AspNetCore.Components.WebAssembly.Http +@using Microsoft.JSInterop +@using Nerosoft.Starfish.Webapp +@using Nerosoft.Starfish.Webapp.Layout diff --git a/Source/Starfish.Webapp/wwwroot/css/app.css b/Source/Starfish.Webapp/wwwroot/css/app.css new file mode 100644 index 0000000..1cb4e4e --- /dev/null +++ b/Source/Starfish.Webapp/wwwroot/css/app.css @@ -0,0 +1,77 @@ +.valid.modified:not([type=checkbox]) { + outline: 1px solid #26b050; +} + +.invalid { + outline: 1px solid red; +} + +.validation-message { + color: red; +} + +#blazor-error-ui { + background: lightyellow; + bottom: 0; + box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); + display: none; + left: 0; + padding: 0.6rem 1.25rem 0.7rem 1.25rem; + position: fixed; + width: 100%; + z-index: 1000; +} + + #blazor-error-ui .dismiss { + cursor: pointer; + position: absolute; + right: 0.75rem; + top: 0.5rem; + } + +.blazor-error-boundary { + background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121; + padding: 1rem 1rem 1rem 3.7rem; + color: white; +} + + .blazor-error-boundary::after { + content: "An error has occurred." + } + +.loading-progress { + position: relative; + display: block; + width: 8rem; + height: 8rem; + margin: 20vh auto 1rem auto; +} + + .loading-progress circle { + fill: none; + stroke: #e0e0e0; + stroke-width: 0.6rem; + transform-origin: 50% 50%; + transform: rotate(-90deg); + } + + .loading-progress circle:last-child { + stroke: #1b6ec2; + stroke-dasharray: calc(3.141 * var(--blazor-load-percentage, 0%) * 0.8), 500%; + transition: stroke-dasharray 0.05s ease-in-out; + } + +.loading-progress-text { + position: absolute; + text-align: center; + font-weight: bold; + inset: calc(20vh + 3.25rem) 0 auto 0.2rem; +} + + .loading-progress-text:after { + content: var(--blazor-load-percentage-text, "Loading"); + } + +code { + color: #c02d76; +} diff --git a/Source/Starfish.Webapp/wwwroot/icon-192.png b/Source/Starfish.Webapp/wwwroot/icon-192.png new file mode 100644 index 0000000000000000000000000000000000000000..166f56da7612ea74df6a297154c8d281a4f28a14 GIT binary patch literal 2626 zcmV-I3cdA-P)v0A9xRwxP|bki~~&uFk>U z#P+PQh zyZ;-jwXKqnKbb6)@RaxQz@vm={%t~VbaZrdbaZrdbaeEeXj>~BG?&`J0XrqR#sSlO zg~N5iUk*15JibvlR1f^^1czzNKWvoJtc!Sj*G37QXbZ8LeD{Fzxgdv#Q{x}ytfZ5q z+^k#NaEp>zX_8~aSaZ`O%B9C&YLHb(mNtgGD&Kezd5S@&C=n~Uy1NWHM`t07VQP^MopUXki{2^#ryd94>UJMYW|(#4qV`kb7eD)Q=~NN zaVIRi@|TJ!Rni8J=5DOutQ#bEyMVr8*;HU|)MEKmVC+IOiDi9y)vz=rdtAUHW$yjt zrj3B7v(>exU=IrzC<+?AE=2vI;%fafM}#ShGDZx=0Nus5QHKdyb9pw&4>4XCpa-o?P(Gnco1CGX|U> z$f+_tA3+V~<{MU^A%eP!8R*-sD9y<>Jc7A(;aC5hVbs;kX9&Sa$JMG!W_BLFQa*hM zri__C@0i0U1X#?)Y=)>JpvTnY6^s;fu#I}K9u>OldV}m!Ch`d1Vs@v9 zb}w(!TvOmSzmMBa9gYvD4xocL2r0ds6%Hs>Z& z#7#o9PGHDmfG%JQq`O5~dt|MAQN@2wyJw_@``7Giyy(yyk(m8U*kk5$X1^;3$a3}N^Lp6hE5!#8l z#~NYHmKAs6IAe&A;bvM8OochRmXN>`D`{N$%#dZCRxp4-dJ?*3P}}T`tYa3?zz5BA zTu7uE#GsDpZ$~j9q=Zq!LYjLbZPXFILZK4?S)C-zE1(dC2d<7nO4-nSCbV#9E|E1MM|V<9>i4h?WX*r*ul1 z5#k6;po8z=fdMiVVz*h+iaTlz#WOYmU^SX5#97H~B32s-#4wk<1NTN#g?LrYieCu> zF7pbOLR;q2D#Q`^t%QcY06*X-jM+ei7%ZuanUTH#9Y%FBi*Z#22({_}3^=BboIsbg zR0#jJ>9QR8SnmtSS6x($?$}6$x+q)697#m${Z@G6Ujf=6iO^S}7P`q8DkH!IHd4lB zDzwxt3BHsPAcXFFY^Fj}(073>NL_$A%v2sUW(CRutd%{G`5ow?L`XYSO*Qu?x+Gzv zBtR}Y6`XF4xX7)Z04D+fH;TMapdQFFameUuHL34NN)r@aF4RO%x&NApeWGtr#mG~M z6sEIZS;Uj1HB1*0hh=O@0q1=Ia@L>-tETu-3n(op+97E z#&~2xggrl(LA|giII;RwBlX2^Q`B{_t}gxNL;iB11gEPC>v` zb4SJ;;BFOB!{chn>?cCeGDKuqI0+!skyWTn*k!WiPNBf=8rn;@y%( znhq%8fj2eAe?`A5mP;TE&iLEmQ^xV%-kmC-8mWao&EUK_^=GW-Y3z ksi~={si~={skwfB0gq6itke#r1ONa407*qoM6N<$g11Kq@c;k- literal 0 HcmV?d00001 diff --git a/Source/Starfish.Webapp/wwwroot/index.html b/Source/Starfish.Webapp/wwwroot/index.html new file mode 100644 index 0000000..42cbf4e --- /dev/null +++ b/Source/Starfish.Webapp/wwwroot/index.html @@ -0,0 +1,29 @@ + + + + + + + Starfish + + + + + +
+ + + + +
+
+ +
+ An unhandled error has occurred. + Reload + 🗙 +
+ + + + diff --git a/Starfish.sln b/Starfish.sln index 2468735..048dd99 100644 --- a/Starfish.sln +++ b/Starfish.sln @@ -49,6 +49,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Starfish.Redis", "Source\St EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Starfish.Etcd", "Source\Starfish.Etcd\Starfish.Etcd.csproj", "{88EE02A3-5A1F-48F9-AA0A-8733D555BB98}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Starfish.Webapp", "Source\Starfish.Webapp\Starfish.Webapp.csproj", "{10681F31-2EBE-49B1-9152-AB7A5844DFD3}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -105,6 +107,10 @@ Global {88EE02A3-5A1F-48F9-AA0A-8733D555BB98}.Debug|Any CPU.Build.0 = Debug|Any CPU {88EE02A3-5A1F-48F9-AA0A-8733D555BB98}.Release|Any CPU.ActiveCfg = Release|Any CPU {88EE02A3-5A1F-48F9-AA0A-8733D555BB98}.Release|Any CPU.Build.0 = Release|Any CPU + {10681F31-2EBE-49B1-9152-AB7A5844DFD3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {10681F31-2EBE-49B1-9152-AB7A5844DFD3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {10681F31-2EBE-49B1-9152-AB7A5844DFD3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {10681F31-2EBE-49B1-9152-AB7A5844DFD3}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -122,6 +128,7 @@ Global {A0B1FBB3-F4E5-4462-B855-DB8044D7D03E} = {5FFE10B6-8023-4475-9A43-D4EF7AC99009} {4EF74AB0-401F-4FEF-BF5C-25CF1C8F5ECE} = {31CE15C0-0BE0-4444-AC72-9E3C2B87BD7C} {88EE02A3-5A1F-48F9-AA0A-8733D555BB98} = {31CE15C0-0BE0-4444-AC72-9E3C2B87BD7C} + {10681F31-2EBE-49B1-9152-AB7A5844DFD3} = {31CE15C0-0BE0-4444-AC72-9E3C2B87BD7C} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {BFF17094-761E-48C4-96A9-16248CBF46BA} From beb16d6a200ac5b917aaa9935d06751eb5b5ce23 Mon Sep 17 00:00:00 2001 From: damon Date: Tue, 2 Jan 2024 21:33:03 +0800 Subject: [PATCH 08/22] =?UTF-8?q?=E5=BC=95=E5=85=A5FluentUI=20Components?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Source/Starfish.Webapp/App.razor | 41 +++++-- Source/Starfish.Webapp/Program.cs | 3 + Source/Starfish.Webapp/Starfish.Webapp.csproj | 3 + Source/Starfish.Webapp/_Imports.razor | 4 +- Source/Starfish.Webapp/wwwroot/css/app.css | 115 ++++++++++++++++-- Source/Starfish.Webapp/wwwroot/index.html | 4 + 6 files changed, 149 insertions(+), 21 deletions(-) diff --git a/Source/Starfish.Webapp/App.razor b/Source/Starfish.Webapp/App.razor index 6fd3ed1..1dfc854 100644 --- a/Source/Starfish.Webapp/App.razor +++ b/Source/Starfish.Webapp/App.razor @@ -1,12 +1,29 @@ - - - - - - - Not found - -

Sorry, there's nothing at this address.

-
-
-
+ + + + + + @if (context.User.Identity?.IsAuthenticated != true) + { + @* *@ + } + else + { +

You are not authorized to access this resource.

+ } +
+
+ +
+ + Not found + +

Sorry, there's nothing at this address.

+
+
+
+ + + + +
\ No newline at end of file diff --git a/Source/Starfish.Webapp/Program.cs b/Source/Starfish.Webapp/Program.cs index af8cc8d..094323d 100644 --- a/Source/Starfish.Webapp/Program.cs +++ b/Source/Starfish.Webapp/Program.cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.Components.WebAssembly.Hosting; +using Microsoft.FluentUI.AspNetCore.Components; namespace Nerosoft.Starfish.Webapp; public class Program @@ -10,6 +11,8 @@ public static async Task Main(string[] args) builder.RootComponents.Add("#app"); builder.RootComponents.Add("head::after"); + builder.Services.AddFluentUIComponents(); + builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); await builder.Build().RunAsync(); diff --git a/Source/Starfish.Webapp/Starfish.Webapp.csproj b/Source/Starfish.Webapp/Starfish.Webapp.csproj index a4c69a6..2e4b95d 100644 --- a/Source/Starfish.Webapp/Starfish.Webapp.csproj +++ b/Source/Starfish.Webapp/Starfish.Webapp.csproj @@ -10,6 +10,8 @@ + + @@ -21,6 +23,7 @@ + diff --git a/Source/Starfish.Webapp/_Imports.razor b/Source/Starfish.Webapp/_Imports.razor index 0ded3aa..62f6323 100644 --- a/Source/Starfish.Webapp/_Imports.razor +++ b/Source/Starfish.Webapp/_Imports.razor @@ -1,10 +1,12 @@ @using System.Net.Http @using System.Net.Http.Json +@using Microsoft.AspNetCore.Components.Authorization @using Microsoft.AspNetCore.Components.Forms @using Microsoft.AspNetCore.Components.Routing @using Microsoft.AspNetCore.Components.Web @using Microsoft.AspNetCore.Components.Web.Virtualization @using Microsoft.AspNetCore.Components.WebAssembly.Http +@using Microsoft.FluentUI.AspNetCore.Components @using Microsoft.JSInterop @using Nerosoft.Starfish.Webapp -@using Nerosoft.Starfish.Webapp.Layout +@using Nerosoft.Starfish.Webapp.Layout \ No newline at end of file diff --git a/Source/Starfish.Webapp/wwwroot/css/app.css b/Source/Starfish.Webapp/wwwroot/css/app.css index 1cb4e4e..2581598 100644 --- a/Source/Starfish.Webapp/wwwroot/css/app.css +++ b/Source/Starfish.Webapp/wwwroot/css/app.css @@ -1,15 +1,65 @@ -.valid.modified:not([type=checkbox]) { - outline: 1px solid #26b050; +@import '/_content/Microsoft.FluentUI.AspNetCore.Components/css/reboot.css'; + +body { + --body-font: "Segoe UI Variable", "Segoe UI", sans-serif; + font-family: var(--body-font); + font-size: var(--type-ramp-base-font-size); + line-height: var(--type-ramp-base-line-height); + margin: 0; +} + +.navmenu-icon { + display: none; +} + +.main { + height: calc(100dvh - 86px); + color: var(--neutral-foreground-rest); +} + +.content { + padding: 0.5rem 1.5rem; } -.invalid { - outline: 1px solid red; +footer { + display: grid; + grid-template-columns: 10px auto auto 10px; + background: var(--neutral-layer-4); + color: var(--neutral-foreground-rest); + align-items: center; + padding: 10px 10px; } -.validation-message { - color: red; + footer .link1 { + grid-column: 2; + justify-content: start; + } + + footer .link2 { + grid-column: 3; + justify-self: end; + } + + footer a { + color: var(--neutral-foreground-rest); + text-decoration: none; + } + + footer a:focus { + outline: 1px dashed; + outline-offset: 3px; + } + + footer a:hover { + text-decoration: underline; + } + +.alert { + border: 1px dashed var(--accent-fill-rest); + padding: 5px; } + #blazor-error-ui { background: lightyellow; bottom: 0; @@ -20,6 +70,7 @@ position: fixed; width: 100%; z-index: 1000; + margin: 20px 0; } #blazor-error-ui .dismiss { @@ -35,8 +86,8 @@ color: white; } - .blazor-error-boundary::after { - content: "An error has occurred." + .blazor-error-boundary::before { + content: "An error has occurred. " } .loading-progress { @@ -75,3 +126,51 @@ code { color: #c02d76; } + +@media (max-width: 600px) { + .main { + flex-direction: column !important; + row-gap: 0 !important; + } + + nav.sitenav { + width: 100%; + height: 100%; + } + + #main-menu { + width: 100% !important; + } + + #main-menu > div:first-child:is(.expander) { + display: none; + } + + .navmenu { + width: 100%; + } + + #navmenu-toggle { + appearance: none; + } + + #navmenu-toggle ~ nav { + display: none; + } + + #navmenu-toggle:checked ~ nav { + display: block; + } + + .navmenu-icon { + cursor: pointer; + z-index: 10; + display: block; + position: absolute; + top: 15px; + right: 20px; + width: 20px; + height: 20px; + border: none; + } +} diff --git a/Source/Starfish.Webapp/wwwroot/index.html b/Source/Starfish.Webapp/wwwroot/index.html index 42cbf4e..643ecf5 100644 --- a/Source/Starfish.Webapp/wwwroot/index.html +++ b/Source/Starfish.Webapp/wwwroot/index.html @@ -6,10 +6,14 @@ Starfish + + + +
From e63a494804a1732b58c6ad803381dff56a16fa9d Mon Sep 17 00:00:00 2001 From: damon Date: Thu, 4 Jan 2024 21:11:49 +0800 Subject: [PATCH 09/22] =?UTF-8?q?=E9=87=8D=E5=91=BD=E5=90=8DDto?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Identity/{LoginRequestDto.cs => AuthRequestDto.cs} | 2 +- Source/Starfish.Webapi/Controllers/IdentityController.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename Source/Starfish.Transit/Identity/{LoginRequestDto.cs => AuthRequestDto.cs} (89%) diff --git a/Source/Starfish.Transit/Identity/LoginRequestDto.cs b/Source/Starfish.Transit/Identity/AuthRequestDto.cs similarity index 89% rename from Source/Starfish.Transit/Identity/LoginRequestDto.cs rename to Source/Starfish.Transit/Identity/AuthRequestDto.cs index 3ff5ab9..e286497 100644 --- a/Source/Starfish.Transit/Identity/LoginRequestDto.cs +++ b/Source/Starfish.Transit/Identity/AuthRequestDto.cs @@ -3,7 +3,7 @@ /// /// 登录请求Dto /// -public class LoginRequestDto +public class AuthRequestDto { /// /// 用户名 diff --git a/Source/Starfish.Webapi/Controllers/IdentityController.cs b/Source/Starfish.Webapi/Controllers/IdentityController.cs index 26bc5f0..177e512 100644 --- a/Source/Starfish.Webapi/Controllers/IdentityController.cs +++ b/Source/Starfish.Webapi/Controllers/IdentityController.cs @@ -30,7 +30,7 @@ public IdentityController(IIdentityApplicationService service) /// /// [HttpPost("grant")] - public async Task GrantTokenAsync([FromBody] LoginRequestDto request) + public async Task GrantTokenAsync([FromBody] AuthRequestDto request) { var data = new Dictionary { From f99c9d1c2b77eedf47806f1f74aa853de22a1ed5 Mon Sep 17 00:00:00 2001 From: damon Date: Thu, 4 Jan 2024 21:12:53 +0800 Subject: [PATCH 10/22] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E8=B5=84=E6=BA=90?= =?UTF-8?q?=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Starfish.Webapp/Properties/Resources.resx | 183 ++++++++++++++++++ Source/Starfish.Webapp/Starfish.Webapp.csproj | 17 +- Starfish.sln | 4 +- 3 files changed, 197 insertions(+), 7 deletions(-) create mode 100644 Source/Starfish.Webapp/Properties/Resources.resx diff --git a/Source/Starfish.Webapp/Properties/Resources.resx b/Source/Starfish.Webapp/Properties/Resources.resx new file mode 100644 index 0000000..4eaad94 --- /dev/null +++ b/Source/Starfish.Webapp/Properties/Resources.resx @@ -0,0 +1,183 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cancel + + + Save + + + Login + + + Login + + + Password + + + User name + + + One time password + + + User name & password + + + Apps + + + Home + + + Logs + + + Team + + + User + + + Add user + + + Add user + + + Edit user + + + Email + + + Nick name + + + Password + + + User name + + + Keyword to search + + \ No newline at end of file diff --git a/Source/Starfish.Webapp/Starfish.Webapp.csproj b/Source/Starfish.Webapp/Starfish.Webapp.csproj index 2e4b95d..f1b9947 100644 --- a/Source/Starfish.Webapp/Starfish.Webapp.csproj +++ b/Source/Starfish.Webapp/Starfish.Webapp.csproj @@ -10,6 +10,7 @@ + @@ -43,17 +44,23 @@ - + - + + True + True + Resources.resx + - + + ResXFileCodeGenerator + Resources.Designer.cs + - + - diff --git a/Starfish.sln b/Starfish.sln index 048dd99..b09cb76 100644 --- a/Starfish.sln +++ b/Starfish.sln @@ -6,8 +6,8 @@ MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution", "Solution", "{9B36D7D2-4471-4286-8271-5CB679A71051}" ProjectSection(SolutionItems) = preProject Directory.Packages.props = Directory.Packages.props - README.md = README.md global.props = global.props + README.md = README.md EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Source", "Source", "{31CE15C0-0BE0-4444-AC72-9E3C2B87BD7C}" @@ -49,7 +49,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Starfish.Redis", "Source\St EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Starfish.Etcd", "Source\Starfish.Etcd\Starfish.Etcd.csproj", "{88EE02A3-5A1F-48F9-AA0A-8733D555BB98}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Starfish.Webapp", "Source\Starfish.Webapp\Starfish.Webapp.csproj", "{10681F31-2EBE-49B1-9152-AB7A5844DFD3}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Starfish.Webapp", "Source\Starfish.Webapp\Starfish.Webapp.csproj", "{10681F31-2EBE-49B1-9152-AB7A5844DFD3}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution From f3e41d5817deda811df8dfab7fea1ec57bd831a5 Mon Sep 17 00:00:00 2001 From: damon Date: Thu, 4 Jan 2024 21:14:01 +0800 Subject: [PATCH 11/22] =?UTF-8?q?=E9=A1=B9=E7=9B=AE=E5=9F=BA=E7=A1=80?= =?UTF-8?q?=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Source/Starfish.Webapp/App.razor | 7 +- .../Starfish.Webapp/Layout/EmptyLayout.razor | 2 + .../Starfish.Webapp/Layout/MainLayout.razor | 68 ++++++++- Source/Starfish.Webapp/Seedwork/Constants.cs | 10 ++ Source/Starfish.Webapp/Seedwork/EventUtil.cs | 59 ++++++++ .../Seedwork/ExceptionExtensions.cs | 34 +++++ .../Starfish.Webapp/Seedwork/HostAccessor.cs | 8 ++ .../IdentityAuthenticationStateProvider.cs | 46 ++++++ .../Seedwork/ServiceLocator.cs | 54 ++++++++ .../Starfish.Webapp/Seedwork/TokenHelper.cs | 131 ++++++++++++++++++ Source/Starfish.Webapp/Shared/Redirect.razor | 23 +++ .../Starfish.Webapp/wwwroot/appsettings.json | 6 + Source/Starfish.Webapp/wwwroot/index.html | 1 + 13 files changed, 444 insertions(+), 5 deletions(-) create mode 100644 Source/Starfish.Webapp/Seedwork/Constants.cs create mode 100644 Source/Starfish.Webapp/Seedwork/EventUtil.cs create mode 100644 Source/Starfish.Webapp/Seedwork/ExceptionExtensions.cs create mode 100644 Source/Starfish.Webapp/Seedwork/HostAccessor.cs create mode 100644 Source/Starfish.Webapp/Seedwork/IdentityAuthenticationStateProvider.cs create mode 100644 Source/Starfish.Webapp/Seedwork/ServiceLocator.cs create mode 100644 Source/Starfish.Webapp/Seedwork/TokenHelper.cs create mode 100644 Source/Starfish.Webapp/Shared/Redirect.razor create mode 100644 Source/Starfish.Webapp/wwwroot/appsettings.json diff --git a/Source/Starfish.Webapp/App.razor b/Source/Starfish.Webapp/App.razor index 1dfc854..242cb8d 100644 --- a/Source/Starfish.Webapp/App.razor +++ b/Source/Starfish.Webapp/App.razor @@ -5,7 +5,7 @@ @if (context.User.Identity?.IsAuthenticated != true) { - @* *@ + } else { @@ -22,8 +22,7 @@ - - + @* - + *@ \ No newline at end of file diff --git a/Source/Starfish.Webapp/Layout/EmptyLayout.razor b/Source/Starfish.Webapp/Layout/EmptyLayout.razor index 53a4e95..c2ac447 100644 --- a/Source/Starfish.Webapp/Layout/EmptyLayout.razor +++ b/Source/Starfish.Webapp/Layout/EmptyLayout.razor @@ -1,3 +1,5 @@ @inherits LayoutComponentBase + + @Body \ No newline at end of file diff --git a/Source/Starfish.Webapp/Layout/MainLayout.razor b/Source/Starfish.Webapp/Layout/MainLayout.razor index e1a9a75..820f15f 100644 --- a/Source/Starfish.Webapp/Layout/MainLayout.razor +++ b/Source/Starfish.Webapp/Layout/MainLayout.razor @@ -1,3 +1,69 @@ @inherits LayoutComponentBase -@Body +@attribute [Authorize] + + + + + + Starfish + +
+ + + + + + + +
+ + + + + @(Resources.IDS_MENU_TEXT_HOME) + @(Resources.IDS_MENU_TEXT_APPS) + @(Resources.IDS_MENU_TEXT_USER) + @(Resources.IDS_MENU_TEXT_TEAM) + @(Resources.IDS_MENU_TEXT_LOGS) + + + + +
+ @Body +
+
+ +
@ex.Message
+
+
+
+
+ + + + + + + +@code { + private bool ShowPersonaMenu { get; set; } + + protected override Task OnInitializedAsync() + { + return base.OnInitializedAsync(); + } + + private void OnPersonaClicked() + { + ShowPersonaMenu = true; + } +} \ No newline at end of file diff --git a/Source/Starfish.Webapp/Seedwork/Constants.cs b/Source/Starfish.Webapp/Seedwork/Constants.cs new file mode 100644 index 0000000..0a83681 --- /dev/null +++ b/Source/Starfish.Webapp/Seedwork/Constants.cs @@ -0,0 +1,10 @@ +namespace Nerosoft.Starfish.Webapp; + +internal class Constants +{ + public static class LocalStorage + { + public const string AccessToken = "session_access_token"; + public const string RefreshToken = "session_refresh_token"; + } +} \ No newline at end of file diff --git a/Source/Starfish.Webapp/Seedwork/EventUtil.cs b/Source/Starfish.Webapp/Seedwork/EventUtil.cs new file mode 100644 index 0000000..4f6b52f --- /dev/null +++ b/Source/Starfish.Webapp/Seedwork/EventUtil.cs @@ -0,0 +1,59 @@ +using Microsoft.AspNetCore.Components; + +namespace Nerosoft.Starfish.Webapp; + +public static class EventUtil +{ + public static Action AsNonRenderingEventHandler(Action callback) + { + return new SyncReceiver(callback).Invoke; + } + + public static Action AsNonRenderingEventHandler( + Action callback) + { + return new SyncReceiver(callback).Invoke; + } + + public static Func AsNonRenderingEventHandler(Func callback) + { + return new AsyncReceiver(callback).Invoke; + } + + public static Func AsNonRenderingEventHandler(Func callback) + { + return new AsyncReceiver(callback).Invoke; + } + + private record SyncReceiver(Action Callback) + : ReceiverBase + { + public void Invoke() => Callback(); + } + + private record SyncReceiver(Action Callback) + : ReceiverBase + { + public void Invoke(T arg) => Callback(arg); + } + + private record AsyncReceiver(Func Callback) + : ReceiverBase + { + public Task Invoke() => Callback(); + } + + private record AsyncReceiver(Func Callback) + : ReceiverBase + { + public Task Invoke(T arg) => Callback(arg); + } + + private record ReceiverBase : IHandleEvent + { + public Task HandleEventAsync(EventCallbackWorkItem item, object arg) + { + return item.InvokeAsync(arg); + } + } +} \ No newline at end of file diff --git a/Source/Starfish.Webapp/Seedwork/ExceptionExtensions.cs b/Source/Starfish.Webapp/Seedwork/ExceptionExtensions.cs new file mode 100644 index 0000000..1482abb --- /dev/null +++ b/Source/Starfish.Webapp/Seedwork/ExceptionExtensions.cs @@ -0,0 +1,34 @@ +using Nerosoft.Starfish.Webapp.Rest; +using Refit; + +namespace Nerosoft.Starfish.Webapp; + +internal static class ExceptionExtensions +{ + public static Exception UnwrapException(this Exception exception) + { + if (exception is AggregateException aggregateException) + { + return aggregateException.InnerException; + } + + return exception; + } + + public static string GetPromptMessage(this Exception exception) + { + while (exception.InnerException != null) + { + exception = exception.InnerException; + } + + return exception switch + { + HttpRequestException _ => "Unable to connect to the server", + TaskCanceledException _ => "The request has timed out", + OperationCanceledException _ => "The request has timed out", + ApiException ex => ex.GetDetail()?.Message ?? ex.Message, + _ => exception.Message + }; + } +} \ No newline at end of file diff --git a/Source/Starfish.Webapp/Seedwork/HostAccessor.cs b/Source/Starfish.Webapp/Seedwork/HostAccessor.cs new file mode 100644 index 0000000..8d57230 --- /dev/null +++ b/Source/Starfish.Webapp/Seedwork/HostAccessor.cs @@ -0,0 +1,8 @@ +namespace Nerosoft.Starfish.Webapp; + +internal class HostAccessor +{ + public IServiceProvider ServiceProvider { get; init; } + + public IConfiguration Configuration { get; init; } +} \ No newline at end of file diff --git a/Source/Starfish.Webapp/Seedwork/IdentityAuthenticationStateProvider.cs b/Source/Starfish.Webapp/Seedwork/IdentityAuthenticationStateProvider.cs new file mode 100644 index 0000000..573acd5 --- /dev/null +++ b/Source/Starfish.Webapp/Seedwork/IdentityAuthenticationStateProvider.cs @@ -0,0 +1,46 @@ +using Blazored.LocalStorage; +using Microsoft.AspNetCore.Components.Authorization; +using System.Security.Claims; + +namespace Nerosoft.Starfish.Webapp; + +public class IdentityAuthenticationStateProvider : AuthenticationStateProvider +{ + private readonly ILocalStorageService _storageService; + + public IdentityAuthenticationStateProvider(ILocalStorageService storageService) + { + _storageService = storageService; + } + + public async Task SetAuthenticationStateAsync(string accessToken, string refreshToken) + { + await _storageService.SetItemAsStringAsync(Constants.LocalStorage.AccessToken, accessToken); + await _storageService.SetItemAsStringAsync(Constants.LocalStorage.RefreshToken, refreshToken); + + var jwt = TokenHelper.Resolve(accessToken); + var identity = new ClaimsIdentity(jwt.Claims, "jwt"); + var user = new ClaimsPrincipal(identity); + + var authState = Task.FromResult(new AuthenticationState(user)); + NotifyAuthenticationStateChanged(authState); + } + + public override async Task GetAuthenticationStateAsync() + { + ClaimsIdentity identity; + + var token = await _storageService.GetItemAsStringAsync(Constants.LocalStorage.AccessToken); + var jwt = TokenHelper.Resolve(token); + if (jwt != null && jwt.ValidTo > DateTime.UtcNow) + { + identity = new ClaimsIdentity(jwt.Claims, "jwt"); + } + else + { + identity = new ClaimsIdentity(); + } + + return new AuthenticationState(new ClaimsPrincipal(identity)); + } +} diff --git a/Source/Starfish.Webapp/Seedwork/ServiceLocator.cs b/Source/Starfish.Webapp/Seedwork/ServiceLocator.cs new file mode 100644 index 0000000..eba960e --- /dev/null +++ b/Source/Starfish.Webapp/Seedwork/ServiceLocator.cs @@ -0,0 +1,54 @@ +namespace Nerosoft.Starfish.Webapp; + +internal static class ServiceLocator +{ + private static IServiceProvider Current => Singleton.Instance.ServiceProvider; + + /// + /// 获取的实例. + /// + /// + /// 是否创建新的Scope + /// + public static TService GetService(bool newScope = false) + { + var provider = newScope ? Current.CreateScope().ServiceProvider : Current; + return GetService(provider); + } + + /// + /// 从指定的获取的实例. + /// + /// + /// + /// + public static TService GetService(IServiceProvider provider) + { + provider ??= Current; + return provider.GetService(); + } + + /// + /// 从获取的实例或创建一个新的实例. + /// + /// + /// + /// + public static TService GetServiceOrCreateInstance(IServiceProvider provider = null) + { + provider ??= Current; + return ActivatorUtilities.GetServiceOrCreateInstance(provider); + } + + public static object GetService(Type serviceType, bool newScope = false) + { + var provider = newScope ? Current.CreateScope().ServiceProvider : Current; + return provider.GetService(serviceType); + } + + public static object GetService(Type serviceType, IServiceProvider provider) + { + provider ??= Current; + return provider.GetService(serviceType); + } +} \ No newline at end of file diff --git a/Source/Starfish.Webapp/Seedwork/TokenHelper.cs b/Source/Starfish.Webapp/Seedwork/TokenHelper.cs new file mode 100644 index 0000000..853cbbe --- /dev/null +++ b/Source/Starfish.Webapp/Seedwork/TokenHelper.cs @@ -0,0 +1,131 @@ +using Microsoft.IdentityModel.Tokens; +using System.Diagnostics; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; + +namespace Nerosoft.Starfish.Webapp; + +internal static class TokenHelper +{ + public static JwtSecurityToken Resolve(string value) + { + if (string.IsNullOrEmpty(value)) + { + return null; + } + + try + { + var handler = new JwtSecurityTokenHandler(); + var token = handler.ReadJwtToken(value); + return token; + } + catch + { + return null; + } + } + + public static long GetSubject(string value) + { + if (string.IsNullOrEmpty(value)) + { + return 0; + } + + try + { + var token = Resolve(value); + return long.Parse(token.Subject); + } + catch + { + return 0; + } + } + + public static bool Validate(string value) + { + if (string.IsNullOrEmpty(value)) + { + return false; + } + + try + { + var token = Resolve(value); + return token.ValidTo > DateTime.UtcNow; + } + catch (Exception exception) + { + Debug.WriteLine(exception.Message); + return false; + } + } + + public static DateTime GetExpiration(string value) + { + if (string.IsNullOrEmpty(value)) + { + return DateTime.MinValue; + } + + try + { + var token = Resolve(value); + return token.ValidTo; + } + catch (Exception exception) + { + Debug.WriteLine(exception.Message); + return DateTime.MinValue; + } + } + + public static DateTime? GetIssueTime(string value) + { + if (string.IsNullOrEmpty(value)) + { + return null; + } + + try + { + var token = Resolve(value); + return token.IssuedAt; + } + catch (Exception exception) + { + Debug.WriteLine(exception.Message); + return null; + } + } + + public static string Generate(DateTime expires, Dictionary claims) + { + return Generate(expires, claims.Select(t => new Claim(t.Key, t.Value?.ToString() ?? ""))); + } + + public static string Generate(DateTime expires, IEnumerable claims) + { + if (expires <= DateTime.UtcNow.AddDays(1)) + { + expires = DateTime.UtcNow.AddDays(1); + } + + var identity = new ClaimsIdentity(claims); + + return Generate(expires, identity); + } + + public static string Generate(DateTime expires, ClaimsIdentity identity) + { + var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(new string('a', 256))); + + var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256Signature); + var handler = new JwtSecurityTokenHandler(); + var token = handler.WriteToken(handler.CreateJwtSecurityToken(notBefore: new DateTime(1), expires: expires, issuedAt: DateTime.UtcNow, subject: identity, signingCredentials: credentials)); + + return token; + } +} diff --git a/Source/Starfish.Webapp/Shared/Redirect.razor b/Source/Starfish.Webapp/Shared/Redirect.razor new file mode 100644 index 0000000..a5737c1 --- /dev/null +++ b/Source/Starfish.Webapp/Shared/Redirect.razor @@ -0,0 +1,23 @@ +@using System.Web + +@inject NavigationManager Navigation + +@code { + + [Parameter] + public string Uri { get; set; } + + [Parameter] + public bool ForceLoad { get; set; } + + [Parameter] + public bool Replace { get; set; } + + [Parameter] + public RouteData RouteData { get; set; } + + protected override void OnInitialized() + { + Navigation.NavigateTo($"{Uri}?redirect={HttpUtility.UrlEncode(Navigation.Uri)}", ForceLoad, Replace); + } +} diff --git a/Source/Starfish.Webapp/wwwroot/appsettings.json b/Source/Starfish.Webapp/wwwroot/appsettings.json new file mode 100644 index 0000000..fdf43b8 --- /dev/null +++ b/Source/Starfish.Webapp/wwwroot/appsettings.json @@ -0,0 +1,6 @@ +{ + "Api": { + "BaseUrl": "http://wx.zhaorong.pro", + "Timeout": 10000 + } +} \ No newline at end of file diff --git a/Source/Starfish.Webapp/wwwroot/index.html b/Source/Starfish.Webapp/wwwroot/index.html index 643ecf5..f698d7e 100644 --- a/Source/Starfish.Webapp/wwwroot/index.html +++ b/Source/Starfish.Webapp/wwwroot/index.html @@ -8,6 +8,7 @@ + From 7b01991fffefb5a477e32c1b2b8a5831a035dee9 Mon Sep 17 00:00:00 2001 From: damon Date: Thu, 4 Jan 2024 21:16:05 +0800 Subject: [PATCH 12/22] =?UTF-8?q?=E5=A2=9E=E5=8A=A0Api=E5=AE=9A=E4=B9=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Source/Starfish.Webapp/Program.cs | 33 ++++++- .../Starfish.Webapp/Rest/ApiResponseDetail.cs | 12 +++ .../Rest/ApiResponseExtension.cs | 39 ++++++++ .../Starfish.Webapp/Rest/Defines/IAppsApis.cs | 5 + .../Rest/Defines/IIdentityApi.cs | 13 +++ .../Starfish.Webapp/Rest/Defines/ILogsApi.cs | 6 ++ .../Rest/Defines/ISettingApi.cs | 6 ++ .../Starfish.Webapp/Rest/Defines/IUserApi.cs | 25 +++++ .../Rest/Handlers/AuthorizationHandler.cs | 25 +++++ .../Rest/Handlers/LoggingHandler.cs | 13 +++ .../Rest/Handlers/SlowRequestHandler.cs | 21 ++++ .../Rest/RestServiceOptions.cs | 22 +++++ .../Rest/ServiceCollectionExtensions.cs | 95 +++++++++++++++++++ 13 files changed, 312 insertions(+), 3 deletions(-) create mode 100644 Source/Starfish.Webapp/Rest/ApiResponseDetail.cs create mode 100644 Source/Starfish.Webapp/Rest/ApiResponseExtension.cs create mode 100644 Source/Starfish.Webapp/Rest/Defines/IAppsApis.cs create mode 100644 Source/Starfish.Webapp/Rest/Defines/IIdentityApi.cs create mode 100644 Source/Starfish.Webapp/Rest/Defines/ILogsApi.cs create mode 100644 Source/Starfish.Webapp/Rest/Defines/ISettingApi.cs create mode 100644 Source/Starfish.Webapp/Rest/Defines/IUserApi.cs create mode 100644 Source/Starfish.Webapp/Rest/Handlers/AuthorizationHandler.cs create mode 100644 Source/Starfish.Webapp/Rest/Handlers/LoggingHandler.cs create mode 100644 Source/Starfish.Webapp/Rest/Handlers/SlowRequestHandler.cs create mode 100644 Source/Starfish.Webapp/Rest/RestServiceOptions.cs create mode 100644 Source/Starfish.Webapp/Rest/ServiceCollectionExtensions.cs diff --git a/Source/Starfish.Webapp/Program.cs b/Source/Starfish.Webapp/Program.cs index 094323d..e44b8ef 100644 --- a/Source/Starfish.Webapp/Program.cs +++ b/Source/Starfish.Webapp/Program.cs @@ -1,8 +1,12 @@ +using Blazored.LocalStorage; +using Microsoft.AspNetCore.Components.Authorization; using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.Components.WebAssembly.Hosting; using Microsoft.FluentUI.AspNetCore.Components; +using Nerosoft.Starfish.Webapp.Rest; namespace Nerosoft.Starfish.Webapp; + public class Program { public static async Task Main(string[] args) @@ -13,8 +17,31 @@ public static async Task Main(string[] args) builder.Services.AddFluentUIComponents(); - builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); + builder.Services.AddOptions(); + builder.Services.AddAuthorizationCore(); + builder.Services + .AddScoped() + .AddScoped(provider => provider.GetRequiredService()) + .AddBlazoredLocalStorageAsSingleton() + .AddHttpClientApi(options => + { + var baseUrl = builder.Configuration.GetValue("Api:BaseUrl"); + var timeout = builder.Configuration.GetValue("Api:Timeout"); + if (string.IsNullOrEmpty(baseUrl)) + { + baseUrl = builder.HostEnvironment.BaseAddress; + } + + options.BaseUrl = baseUrl; + options.Timeout = TimeSpan.FromMilliseconds(timeout); + }); - await builder.Build().RunAsync(); + var host = builder.Build(); + Singleton.Get(() => new HostAccessor + { + ServiceProvider = host.Services, + Configuration = host.Configuration + }); + await host.RunAsync(); } -} +} \ No newline at end of file diff --git a/Source/Starfish.Webapp/Rest/ApiResponseDetail.cs b/Source/Starfish.Webapp/Rest/ApiResponseDetail.cs new file mode 100644 index 0000000..8a9b2fb --- /dev/null +++ b/Source/Starfish.Webapp/Rest/ApiResponseDetail.cs @@ -0,0 +1,12 @@ +namespace Nerosoft.Starfish.Webapp.Rest; + +internal class ApiResponseDetail +{ + public string Title { get; set; } + + public int StatusCode { get; set; } + + public string Message { get; set; } + + public Dictionary Errors { get; set; } +} \ No newline at end of file diff --git a/Source/Starfish.Webapp/Rest/ApiResponseExtension.cs b/Source/Starfish.Webapp/Rest/ApiResponseExtension.cs new file mode 100644 index 0000000..9a74e01 --- /dev/null +++ b/Source/Starfish.Webapp/Rest/ApiResponseExtension.cs @@ -0,0 +1,39 @@ +using Newtonsoft.Json; +using Refit; + +namespace Nerosoft.Starfish.Webapp.Rest; + +internal static class ApiResponseExtension +{ + public static TContent EnsureSuccess(this IApiResponse response) + { + if (response.IsSuccessStatusCode) + { + return response.Content ?? default; + } + + throw response.Error!; + } + + public static void EnsureSuccess(this IApiResponse response) + { + if (!response.IsSuccessStatusCode) + { + throw response.Error!; + } + } + + public static ApiResponseDetail GetDetail(this ApiException exception) + { + var content = exception.Content; + + if (content == null) + { + return new ApiResponseDetail { Message = exception.Message, StatusCode = (int)exception.StatusCode }; + } + + var response = JsonConvert.DeserializeObject(content); + + return response; + } +} \ No newline at end of file diff --git a/Source/Starfish.Webapp/Rest/Defines/IAppsApis.cs b/Source/Starfish.Webapp/Rest/Defines/IAppsApis.cs new file mode 100644 index 0000000..46fd49d --- /dev/null +++ b/Source/Starfish.Webapp/Rest/Defines/IAppsApis.cs @@ -0,0 +1,5 @@ +namespace Nerosoft.Starfish.Webapp.Rest; + +internal interface IAppsApis +{ +} \ No newline at end of file diff --git a/Source/Starfish.Webapp/Rest/Defines/IIdentityApi.cs b/Source/Starfish.Webapp/Rest/Defines/IIdentityApi.cs new file mode 100644 index 0000000..21af7cf --- /dev/null +++ b/Source/Starfish.Webapp/Rest/Defines/IIdentityApi.cs @@ -0,0 +1,13 @@ +using Nerosoft.Starfish.Transit; +using Refit; + +namespace Nerosoft.Starfish.Webapp.Rest; + +internal interface IIdentityApi +{ + [Post("/api/identity/grant")] + Task> GrantTokenAsync([Body] AuthRequestDto request, CancellationToken cancellationToken = default); + + [Post("/api/identity/refresh")] + Task> RefreshTokenAsync(string token, CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/Source/Starfish.Webapp/Rest/Defines/ILogsApi.cs b/Source/Starfish.Webapp/Rest/Defines/ILogsApi.cs new file mode 100644 index 0000000..c03959a --- /dev/null +++ b/Source/Starfish.Webapp/Rest/Defines/ILogsApi.cs @@ -0,0 +1,6 @@ +namespace Nerosoft.Starfish.Webapp.Rest; + +internal interface ILogsApi +{ + +} \ No newline at end of file diff --git a/Source/Starfish.Webapp/Rest/Defines/ISettingApi.cs b/Source/Starfish.Webapp/Rest/Defines/ISettingApi.cs new file mode 100644 index 0000000..d060c6a --- /dev/null +++ b/Source/Starfish.Webapp/Rest/Defines/ISettingApi.cs @@ -0,0 +1,6 @@ +namespace Nerosoft.Starfish.Webapp.Rest; + +internal interface ISettingApi +{ + +} \ No newline at end of file diff --git a/Source/Starfish.Webapp/Rest/Defines/IUserApi.cs b/Source/Starfish.Webapp/Rest/Defines/IUserApi.cs new file mode 100644 index 0000000..5bc8cf3 --- /dev/null +++ b/Source/Starfish.Webapp/Rest/Defines/IUserApi.cs @@ -0,0 +1,25 @@ +using Nerosoft.Starfish.Transit; +using Refit; + +namespace Nerosoft.Starfish.Webapp.Rest; + +internal interface IUserApi +{ + [Get("/api/user")] + Task>> SearchAsync([Query] UserCriteria criteria, int page, int size, CancellationToken cancellationToken = default); + + [Get("/api/user/count")] + Task> CountAsync([Query] UserCriteria criteria, CancellationToken cancellationToken = default); + + [Get("/api/user/{id}")] + Task> GetAsync(int id, CancellationToken cancellationToken = default); + + [Post("/api/user")] + Task CreateAsync([Body] UserCreateDto data, CancellationToken cancellationToken = default); + + [Put("/api/user/{id}")] + Task UpdateAsync(int id, [Body] UserUpdateDto data, CancellationToken cancellationToken = default); + + [Delete("/api/user/{id}")] + Task DeleteAsync(int id, CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/Source/Starfish.Webapp/Rest/Handlers/AuthorizationHandler.cs b/Source/Starfish.Webapp/Rest/Handlers/AuthorizationHandler.cs new file mode 100644 index 0000000..b88c8ff --- /dev/null +++ b/Source/Starfish.Webapp/Rest/Handlers/AuthorizationHandler.cs @@ -0,0 +1,25 @@ +using System.Net.Http.Headers; +using Blazored.LocalStorage; + +namespace Nerosoft.Starfish.Webapp.Rest; + +internal class AuthorizationHandler : DelegatingHandler +{ + private readonly ILocalStorageService _storageService; + + public AuthorizationHandler(ILocalStorageService storageService) + { + _storageService = storageService; + } + + protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + var token = await _storageService.GetItemAsStringAsync(Constants.LocalStorage.AccessToken, cancellationToken); + if (!string.IsNullOrWhiteSpace(token)) + { + request.Headers!.Authorization = new AuthenticationHeaderValue("Bearer", token); + } + + return await base.SendAsync(request, cancellationToken); + } +} \ No newline at end of file diff --git a/Source/Starfish.Webapp/Rest/Handlers/LoggingHandler.cs b/Source/Starfish.Webapp/Rest/Handlers/LoggingHandler.cs new file mode 100644 index 0000000..b7a552c --- /dev/null +++ b/Source/Starfish.Webapp/Rest/Handlers/LoggingHandler.cs @@ -0,0 +1,13 @@ +using System.Diagnostics; + +namespace Nerosoft.Starfish.Webapp.Rest; + +internal class LoggingHandler : DelegatingHandler +{ + protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + var response = await base.SendAsync(request, cancellationToken); + Debug.WriteLineIf(!response.IsSuccessStatusCode, $"[{request.Method}]{request.RequestUri} ({response.StatusCode})"); + return response; + } +} \ No newline at end of file diff --git a/Source/Starfish.Webapp/Rest/Handlers/SlowRequestHandler.cs b/Source/Starfish.Webapp/Rest/Handlers/SlowRequestHandler.cs new file mode 100644 index 0000000..9a9f08d --- /dev/null +++ b/Source/Starfish.Webapp/Rest/Handlers/SlowRequestHandler.cs @@ -0,0 +1,21 @@ +using System.Diagnostics; + +namespace Nerosoft.Starfish.Webapp.Rest; + +internal class SlowRequestHandler : DelegatingHandler +{ + protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + var stopwatch = Stopwatch.StartNew(); + stopwatch.Start(); + var response = await base.SendAsync(request, cancellationToken); + stopwatch.Stop(); + + if (stopwatch.Elapsed.TotalSeconds > 3) + { + Debug.WriteLine($"SlowRequest ({stopwatch.Elapsed.TotalSeconds}s) {request.RequestUri}"); + } + + return response; + } +} \ No newline at end of file diff --git a/Source/Starfish.Webapp/Rest/RestServiceOptions.cs b/Source/Starfish.Webapp/Rest/RestServiceOptions.cs new file mode 100644 index 0000000..96559d9 --- /dev/null +++ b/Source/Starfish.Webapp/Rest/RestServiceOptions.cs @@ -0,0 +1,22 @@ +namespace Nerosoft.Starfish.Webapp.Rest; + +internal class RestServiceOptions +{ + private readonly Dictionary _urls = new(); + + public string BaseUrl { get; set; } + + public TimeSpan Timeout { get; set; } = TimeSpan.FromSeconds(10); + + public Func> TokenFactory { get; set; } + + public void SetUrl(string name, string url) + { + _urls[name] = url; + } + + public string GetUrl(string name) + { + return _urls.GetValueOrDefault(name, BaseUrl); + } +} \ No newline at end of file diff --git a/Source/Starfish.Webapp/Rest/ServiceCollectionExtensions.cs b/Source/Starfish.Webapp/Rest/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..b3824d3 --- /dev/null +++ b/Source/Starfish.Webapp/Rest/ServiceCollectionExtensions.cs @@ -0,0 +1,95 @@ +using System.Net; +using System.Net.Sockets; +using System.Security.Authentication; +using Microsoft.Extensions.Options; +using Polly; +using Polly.Extensions.Http; +using Polly.Timeout; +using Refit; + +namespace Nerosoft.Starfish.Webapp.Rest; + +internal static class ServiceCollectionExtensions +{ + private const string HTTP_CLIENT_NAME = "starfish"; + + private static readonly RefitSettings _refitSettings = new() + { + ContentSerializer = new NewtonsoftJsonContentSerializer() + //ContentSerializer = new SystemTextJsonContentSerializer(new JsonSerializerOptions + //{ + // NumberHandling = JsonNumberHandling.AllowReadingFromString, + // Converters = + // { + // new DateTimeConverter(), + // new NullableDateTimeConverter(), + // new JsonStringEnumConverter() + // } + //}) + }; + + public static IServiceCollection AddHttpClientApi(this IServiceCollection services, Action config) + { + services.Configure(config); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + + services.AddTransient(provider => provider.GetRestService(HTTP_CLIENT_NAME)) + .AddTransient(provider => provider.GetRestService(HTTP_CLIENT_NAME)) + .AddTransient(provider => provider.GetRestService(HTTP_CLIENT_NAME)) + .AddTransient(provider => provider.GetRestService(HTTP_CLIENT_NAME)) + .AddTransient(provider => provider.GetRestService(HTTP_CLIENT_NAME)); + + services.AddHttpClient(HTTP_CLIENT_NAME, (provider, client) => + { + var options = provider.GetService>().Value; + client.BaseAddress = new Uri(options.BaseUrl); + client.Timeout = options.Timeout; + }) + .SetHandlerLifetime(TimeSpan.FromMinutes(5)) + .AddHttpMessageHandler() + .AddHttpMessageHandler() + .AddHttpMessageHandler(); + // .AddHttpMessageHandler(provider => + // { + // var handler = new ProgressMessageHandler(); + // handler.HttpSendProgress += (sender, args) => + // { + // Console.WriteLine($"Send progress: {args.ProgressPercentage}"); + // }; + // return handler; + // }); + + return services; + } + + private static HttpClient GetHttpClient(this IServiceProvider provider, string name) + { + var factory = provider.GetService(); + var client = factory.CreateClient(name); + return client; + } + + private static TService GetRestService(this IServiceProvider provider, string name) + { + var client = provider.GetHttpClient(name); + return RestService.For(client, _refitSettings); + } + + private static IAsyncPolicy GetTimeoutPolicy() + { + return Policy.TimeoutAsync(TimeSpan.FromSeconds(15)); + } + + private static IAsyncPolicy GetRetryPolicy() + { + return HttpPolicyExtensions.HandleTransientHttpError() + .Or() + .Or() + .OrResult(response => response.StatusCode == HttpStatusCode.NotFound) + .OrResult(response => response.StatusCode == HttpStatusCode.ServiceUnavailable) + .WaitAndRetryAsync(5, + retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))); + } +} \ No newline at end of file From 504085dcb3582db1c2438bdebfc1fc99d7124f9e Mon Sep 17 00:00:00 2001 From: damon Date: Sun, 7 Jan 2024 18:42:49 +0800 Subject: [PATCH 13/22] =?UTF-8?q?=E5=9B=A2=E9=98=9F=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Application/ApplicationServiceModule.cs | 1 + .../Application/Commands/TeamCreateCommand.cs | 14 +- .../Commands/TeamMemberEditCommand.cs | 19 +++ .../Application/Commands/TeamUpdateCommand.cs | 17 +++ .../Contracts/ITeamApplicationService.cs | 23 +++ .../Handlers/AppInfoCommandHandler.cs | 2 +- .../Handlers/SettingCommandHandler.cs | 2 +- .../Handlers/TeamCommandHandler.cs | 72 ++++++++++ .../Handlers/UserCommandHandler.cs | 1 + .../Implements/TeamApplicationService.cs | 68 +++++++++ .../Implements/UserApplicationService.cs | 3 +- .../Mappings/AppsMappingProfile.cs | 2 +- .../Mappings/LogsMappingProfile.cs | 2 +- .../Mappings/SettingMappingProfile.cs | 2 +- .../Mappings/TeamMappingProfile.cs | 20 +++ .../Mappings/UserMappingProfile.cs | 2 +- .../Domain/Aggregates/AppInfo.cs | 6 +- .../Domain/Aggregates/Team.cs | 86 ++++++++++- .../Domain/Aggregates/TeamMember.cs | 28 +++- .../Domain/Aggregates/User.cs | 14 ++ .../Domain/Business/SettingGeneralBusiness.cs | 2 +- .../Domain/Business/TeamGeneralBusiness.cs | 133 ++++++++++++++++++ .../Domain/Business/TeamMemberBusiness.cs | 101 +++++++++++++ .../Domain/Business/UserGeneralBusiness.cs | 44 ++++++ .../Domain/Repositories/ITeamRepository.cs | 28 ++++ .../Domain/Repositories/IUserRepository.cs | 11 ++ .../Repository/Contexts/SqliteModelBuilder.cs | 34 ++++- .../Repository/CriteriaExtensions.cs | 16 +++ .../Repository/Repositories/TeamRepository.cs | 46 ++++++ .../Repository/Repositories/UserRepository.cs | 15 +- .../Repository/RepositoryModule.cs | 1 + .../Specifications/TeamSpecification.cs | 52 +++++++ .../Specifications/UserSpecification.cs | 4 +- .../UseCases/Identity/UserCountUseCase.cs | 30 ++++ .../UseCases/Setting/SettingUpdateUseCase.cs | 2 +- .../UseCases/Team/TeamCountUseCase.cs | 45 ++++++ .../UseCases/Team/TeamCreateUseCase.cs | 33 +++++ .../UseCases/Team/TeamMemberAppendUseCase.cs | 25 ++++ .../UseCases/Team/TeamMemberQueryUseCase.cs | 32 +++++ .../UseCases/Team/TeamMemberQuitUseCase.cs | 25 ++++ .../UseCases/Team/TeamMemberRemoveUseCase.cs | 25 ++++ .../UseCases/Team/TeamQueryUseCase.cs | 45 ++++++ .../UseCases/Team/TeamUpdateUseCase.cs | 26 ++++ .../Starfish.Transit/Apps/AppInfoCreateDto.cs | 5 + .../Identity/UserCreateDto.cs | 5 + .../Identity/UserDetailDto.cs | 5 + .../Starfish.Transit/Identity/UserItemDto.cs | 5 + Source/Starfish.Transit/Team/TeamCriteria.cs | 12 ++ Source/Starfish.Transit/Team/TeamDetailDto.cs | 37 +++++ Source/Starfish.Transit/Team/TeamEditDto.cs | 23 +++ Source/Starfish.Transit/Team/TeamItemDto.cs | 37 +++++ Source/Starfish.Transit/Team/TeamMemberDto.cs | 32 +++++ .../Controllers/TeamController.cs | 131 +++++++++++++++++ .../Controllers/UserController.cs | 6 +- 54 files changed, 1429 insertions(+), 28 deletions(-) create mode 100644 Source/Starfish.Service/Application/Commands/TeamMemberEditCommand.cs create mode 100644 Source/Starfish.Service/Application/Commands/TeamUpdateCommand.cs create mode 100644 Source/Starfish.Service/Application/Contracts/ITeamApplicationService.cs create mode 100644 Source/Starfish.Service/Application/Handlers/TeamCommandHandler.cs create mode 100644 Source/Starfish.Service/Application/Implements/TeamApplicationService.cs create mode 100644 Source/Starfish.Service/Application/Mappings/TeamMappingProfile.cs create mode 100644 Source/Starfish.Service/Domain/Business/TeamGeneralBusiness.cs create mode 100644 Source/Starfish.Service/Domain/Business/TeamMemberBusiness.cs create mode 100644 Source/Starfish.Service/Domain/Repositories/ITeamRepository.cs create mode 100644 Source/Starfish.Service/Repository/Repositories/TeamRepository.cs create mode 100644 Source/Starfish.Service/Repository/Specifications/TeamSpecification.cs create mode 100644 Source/Starfish.Service/UseCases/Identity/UserCountUseCase.cs create mode 100644 Source/Starfish.Service/UseCases/Team/TeamCountUseCase.cs create mode 100644 Source/Starfish.Service/UseCases/Team/TeamCreateUseCase.cs create mode 100644 Source/Starfish.Service/UseCases/Team/TeamMemberAppendUseCase.cs create mode 100644 Source/Starfish.Service/UseCases/Team/TeamMemberQueryUseCase.cs create mode 100644 Source/Starfish.Service/UseCases/Team/TeamMemberQuitUseCase.cs create mode 100644 Source/Starfish.Service/UseCases/Team/TeamMemberRemoveUseCase.cs create mode 100644 Source/Starfish.Service/UseCases/Team/TeamQueryUseCase.cs create mode 100644 Source/Starfish.Service/UseCases/Team/TeamUpdateUseCase.cs create mode 100644 Source/Starfish.Transit/Team/TeamCriteria.cs create mode 100644 Source/Starfish.Transit/Team/TeamDetailDto.cs create mode 100644 Source/Starfish.Transit/Team/TeamEditDto.cs create mode 100644 Source/Starfish.Transit/Team/TeamItemDto.cs create mode 100644 Source/Starfish.Transit/Team/TeamMemberDto.cs create mode 100644 Source/Starfish.Webapi/Controllers/TeamController.cs diff --git a/Source/Starfish.Service/Application/ApplicationServiceModule.cs b/Source/Starfish.Service/Application/ApplicationServiceModule.cs index b690ca5..f2ac9dd 100644 --- a/Source/Starfish.Service/Application/ApplicationServiceModule.cs +++ b/Source/Starfish.Service/Application/ApplicationServiceModule.cs @@ -34,6 +34,7 @@ public override void AheadConfigureServices(ServiceConfigurationContext context) options.AddProfile(); options.AddProfile(); options.AddProfile(); + options.AddProfile(); }); } diff --git a/Source/Starfish.Service/Application/Commands/TeamCreateCommand.cs b/Source/Starfish.Service/Application/Commands/TeamCreateCommand.cs index c7db43a..fedbb65 100644 --- a/Source/Starfish.Service/Application/Commands/TeamCreateCommand.cs +++ b/Source/Starfish.Service/Application/Commands/TeamCreateCommand.cs @@ -1,4 +1,5 @@ using Nerosoft.Euonia.Domain; +using Nerosoft.Starfish.Transit; namespace Nerosoft.Starfish.Application; @@ -7,13 +8,10 @@ namespace Nerosoft.Starfish.Application; ///
public sealed class TeamCreateCommand : Command { - /// - /// 名称 - /// - public string Name { get; set; } + public TeamCreateCommand(TeamEditDto data) + { + Data = data; + } - /// - /// 描述 - /// - public string Description { get; set; } + public TeamEditDto Data { get; set; } } \ No newline at end of file diff --git a/Source/Starfish.Service/Application/Commands/TeamMemberEditCommand.cs b/Source/Starfish.Service/Application/Commands/TeamMemberEditCommand.cs new file mode 100644 index 0000000..42dbf11 --- /dev/null +++ b/Source/Starfish.Service/Application/Commands/TeamMemberEditCommand.cs @@ -0,0 +1,19 @@ +using Nerosoft.Euonia.Domain; + +namespace Nerosoft.Starfish.Application; + +public class TeamMemberEditCommand : Command +{ + public TeamMemberEditCommand(int teamId, List userIds, string type) + { + TeamId = teamId; + UserIds = userIds; + Type = type; + } + + public int TeamId { get; set; } + + public List UserIds { get; set; } + + public string Type { get; set; } +} \ No newline at end of file diff --git a/Source/Starfish.Service/Application/Commands/TeamUpdateCommand.cs b/Source/Starfish.Service/Application/Commands/TeamUpdateCommand.cs new file mode 100644 index 0000000..fa583ae --- /dev/null +++ b/Source/Starfish.Service/Application/Commands/TeamUpdateCommand.cs @@ -0,0 +1,17 @@ +using Nerosoft.Euonia.Domain; +using Nerosoft.Starfish.Transit; + +namespace Nerosoft.Starfish.Application; + +public class TeamUpdateCommand : Command +{ + public TeamUpdateCommand(int id, TeamEditDto data) + { + Id = id; + Data = data; + } + + public int Id { get; set; } + + public TeamEditDto Data { get; set; } +} \ No newline at end of file diff --git a/Source/Starfish.Service/Application/Contracts/ITeamApplicationService.cs b/Source/Starfish.Service/Application/Contracts/ITeamApplicationService.cs new file mode 100644 index 0000000..dce55e0 --- /dev/null +++ b/Source/Starfish.Service/Application/Contracts/ITeamApplicationService.cs @@ -0,0 +1,23 @@ +using Nerosoft.Euonia.Application; +using Nerosoft.Starfish.Transit; + +namespace Nerosoft.Starfish.Application; + +public interface ITeamApplicationService : IApplicationService +{ + Task> QueryAsync(TeamCriteria criteria, int page, int size, CancellationToken cancellationToken = default); + + Task CountAsync(TeamCriteria criteria, CancellationToken cancellationToken = default); + + Task CreateAsync(TeamEditDto data, CancellationToken cancellationToken = default); + + Task UpdateAsync(int id, TeamEditDto data, CancellationToken cancellationToken = default); + + Task> QueryMembersAsync(int id, CancellationToken cancellationToken = default); + + Task AppendMembersAsync(int id, List userIds, CancellationToken cancellationToken = default); + + Task RemoveMembersAsync(int id, List userIds, CancellationToken cancellationToken = default); + + Task QuitAsync(int id, CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/Source/Starfish.Service/Application/Handlers/AppInfoCommandHandler.cs b/Source/Starfish.Service/Application/Handlers/AppInfoCommandHandler.cs index 5017906..adc122e 100644 --- a/Source/Starfish.Service/Application/Handlers/AppInfoCommandHandler.cs +++ b/Source/Starfish.Service/Application/Handlers/AppInfoCommandHandler.cs @@ -36,7 +36,7 @@ public Task HandleAsync(AppInfoCreateCommand message, MessageContext context, Ca return ExecuteAsync(async () => { await CheckCodeAsync(message.Item1.Code); - var entity = AppInfo.Create(message.Item1.Name, message.Item1.Code); + var entity = AppInfo.Create(message.Item1.TeamId, message.Item1.Name, message.Item1.Code); entity.SetSecret(message.Item1.Secret); if (!string.IsNullOrWhiteSpace(message.Item1.Description)) { diff --git a/Source/Starfish.Service/Application/Handlers/SettingCommandHandler.cs b/Source/Starfish.Service/Application/Handlers/SettingCommandHandler.cs index 6661525..7ba0aa9 100644 --- a/Source/Starfish.Service/Application/Handlers/SettingCommandHandler.cs +++ b/Source/Starfish.Service/Application/Handlers/SettingCommandHandler.cs @@ -33,7 +33,7 @@ public Task HandleAsync(SettingCreateCommand message, MessageContext context, Ca }, context.Response); } - public Task HandleAsync(SettingUpdateCommand message, MessageContext context, CancellationToken cancellationToken = new CancellationToken()) + public Task HandleAsync(SettingUpdateCommand message, MessageContext context, CancellationToken cancellationToken = default) { return ExecuteAsync(async () => { diff --git a/Source/Starfish.Service/Application/Handlers/TeamCommandHandler.cs b/Source/Starfish.Service/Application/Handlers/TeamCommandHandler.cs new file mode 100644 index 0000000..ccb18e8 --- /dev/null +++ b/Source/Starfish.Service/Application/Handlers/TeamCommandHandler.cs @@ -0,0 +1,72 @@ +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 TeamCommandHandler : CommandHandlerBase, + IHandler, + IHandler, + IHandler +{ + public TeamCommandHandler(IUnitOfWorkManager unitOfWork, IObjectFactory factory) + : base(unitOfWork, factory) + { + } + + public Task HandleAsync(TeamCreateCommand message, MessageContext context, CancellationToken cancellationToken = default) + { + return ExecuteAsync(async () => + { + var business = await Factory.CreateAsync(cancellationToken); + business.Alias = message.Data.Alias; + business.Name = message.Data.Name; + business.Description = message.Data.Description; + business.MarkAsInsert(); + _ = await business.SaveAsync(false, cancellationToken); + return business.Id; + }, context.Response); + } + + public Task HandleAsync(TeamUpdateCommand message, MessageContext context, CancellationToken cancellationToken = default) + { + return ExecuteAsync(async () => + { + var business = await Factory.FetchAsync(message.Id, cancellationToken); + business.Alias = message.Data.Alias; + business.Name = message.Data.Name; + business.Description = message.Data.Description; + business.MarkAsUpdate(); + _ = await business.SaveAsync(true, cancellationToken); + }); + } + + public Task HandleAsync(TeamMemberEditCommand message, MessageContext context, CancellationToken cancellationToken = default) + { + return ExecuteAsync(async () => + { + if (message.UserIds == null || message.UserIds.Count == 0) + { + throw new ArgumentException("UserIds is empty"); + } + + var business = await Factory.FetchAsync(message.TeamId, cancellationToken); + business.UserIds = message.UserIds.ToArray(); + switch (message.Type) + { + case "+": + business.MarkAsInsert(); + break; + case "-": + business.MarkAsDelete(); + break; + default: + throw new InvalidOperationException(); + } + + _ = await business.SaveAsync(false, cancellationToken); + }); + } +} \ 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 7e27855..aff369f 100644 --- a/Source/Starfish.Service/Application/Handlers/UserCommandHandler.cs +++ b/Source/Starfish.Service/Application/Handlers/UserCommandHandler.cs @@ -36,6 +36,7 @@ public Task HandleAsync(UserCreateCommand message, MessageContext context, Cance business.Password = message.Item1.Password; business.NickName = message.Item1.NickName; business.Email = message.Item1.Email; + business.Phone = message.Item1.Phone; business.Roles = message.Item1.Roles; await business.SaveAsync(false, cancellationToken); diff --git a/Source/Starfish.Service/Application/Implements/TeamApplicationService.cs b/Source/Starfish.Service/Application/Implements/TeamApplicationService.cs new file mode 100644 index 0000000..9b28d60 --- /dev/null +++ b/Source/Starfish.Service/Application/Implements/TeamApplicationService.cs @@ -0,0 +1,68 @@ +using Nerosoft.Euonia.Application; +using Nerosoft.Starfish.Transit; +using Nerosoft.Starfish.UseCases; + +namespace Nerosoft.Starfish.Application; + +public class TeamApplicationService : BaseApplicationService, ITeamApplicationService +{ + public Task> QueryAsync(TeamCriteria criteria, int page, int size, CancellationToken cancellationToken = default) + { + var userCase = LazyServiceProvider.GetService(); + var input = new TeamQueryInput(criteria, page, size); + return userCase.ExecuteAsync(input, cancellationToken) + .ContinueWith(task => task.Result.Result, cancellationToken); + } + + public Task CountAsync(TeamCriteria criteria, CancellationToken cancellationToken = default) + { + var useCase = LazyServiceProvider.GetService(); + var input = new TeamCountInput(criteria); + return useCase.ExecuteAsync(input, cancellationToken) + .ContinueWith(task => task.Result.Result, cancellationToken); + } + + public Task CreateAsync(TeamEditDto data, CancellationToken cancellationToken = default) + { + var useCase = LazyServiceProvider.GetService(); + var input = new TeamCreateInput(data); + return useCase.ExecuteAsync(input, cancellationToken) + .ContinueWith(task => task.Result.Result, cancellationToken); + } + + public Task UpdateAsync(int id, TeamEditDto data, CancellationToken cancellationToken = default) + { + var useCase = LazyServiceProvider.GetService(); + var input = new TeamUpdateInput(id, data); + return useCase.ExecuteAsync(input, cancellationToken); + } + + public Task> QueryMembersAsync(int id, CancellationToken cancellationToken = default) + { + var useCase = LazyServiceProvider.GetService(); + var input = new TeamMemberQueryInput(id); + return useCase.ExecuteAsync(input, cancellationToken) + .ContinueWith(task => task.Result.Result, cancellationToken); + } + + public Task AppendMembersAsync(int id, List userIds, CancellationToken cancellationToken = default) + { + var useCase = LazyServiceProvider.GetService(); + var input = new TeamMemberAppendInput(id, userIds); + return useCase.ExecuteAsync(input, cancellationToken); + } + + public Task RemoveMembersAsync(int id, List userIds, CancellationToken cancellationToken = default) + { + var useCase = LazyServiceProvider.GetService(); + var input = new TeamMemberRemoveInput(id, userIds); + return useCase.ExecuteAsync(input, cancellationToken); + } + + public Task QuitAsync(int id, CancellationToken cancellationToken = default) + { + var useCase = LazyServiceProvider.GetService(); + var input = new TeamMemberQuitInput(id, User.GetUserIdOfInt32()); + return useCase.ExecuteAsync(input, cancellationToken); + } +} \ No newline at end of file diff --git a/Source/Starfish.Service/Application/Implements/UserApplicationService.cs b/Source/Starfish.Service/Application/Implements/UserApplicationService.cs index c30efa8..f8f1e5b 100644 --- a/Source/Starfish.Service/Application/Implements/UserApplicationService.cs +++ b/Source/Starfish.Service/Application/Implements/UserApplicationService.cs @@ -38,7 +38,8 @@ public Task> SearchAsync(UserCriteria criteria, int page, int /// public Task CountAsync(UserCriteria criteria, CancellationToken cancellationToken = default) { - throw new NotImplementedException(); + var useCase = LazyServiceProvider.GetService(); + return useCase.ExecuteAsync(criteria, cancellationToken); } /// diff --git a/Source/Starfish.Service/Application/Mappings/AppsMappingProfile.cs b/Source/Starfish.Service/Application/Mappings/AppsMappingProfile.cs index 8e21017..9b81f32 100644 --- a/Source/Starfish.Service/Application/Mappings/AppsMappingProfile.cs +++ b/Source/Starfish.Service/Application/Mappings/AppsMappingProfile.cs @@ -7,7 +7,7 @@ namespace Nerosoft.Starfish.Application; /// /// 应用信息映射配置 /// -public class AppsMappingProfile : Profile +internal class AppsMappingProfile : Profile { /// /// 构造函数 diff --git a/Source/Starfish.Service/Application/Mappings/LogsMappingProfile.cs b/Source/Starfish.Service/Application/Mappings/LogsMappingProfile.cs index b624f65..1dc330f 100644 --- a/Source/Starfish.Service/Application/Mappings/LogsMappingProfile.cs +++ b/Source/Starfish.Service/Application/Mappings/LogsMappingProfile.cs @@ -7,7 +7,7 @@ namespace Nerosoft.Starfish.Application; /// /// 日志映射配置 /// -public class LogsMappingProfile : Profile +internal class LogsMappingProfile : Profile { /// /// 初始化日志映射配置 diff --git a/Source/Starfish.Service/Application/Mappings/SettingMappingProfile.cs b/Source/Starfish.Service/Application/Mappings/SettingMappingProfile.cs index 3cd7b4d..187831d 100644 --- a/Source/Starfish.Service/Application/Mappings/SettingMappingProfile.cs +++ b/Source/Starfish.Service/Application/Mappings/SettingMappingProfile.cs @@ -7,7 +7,7 @@ namespace Nerosoft.Starfish.Application; /// /// 配置节点映射配置 /// -public class SettingMappingProfile : Profile +internal class SettingMappingProfile : Profile { /// public SettingMappingProfile() diff --git a/Source/Starfish.Service/Application/Mappings/TeamMappingProfile.cs b/Source/Starfish.Service/Application/Mappings/TeamMappingProfile.cs new file mode 100644 index 0000000..9b5aa8c --- /dev/null +++ b/Source/Starfish.Service/Application/Mappings/TeamMappingProfile.cs @@ -0,0 +1,20 @@ +using AutoMapper; +using Nerosoft.Starfish.Domain; +using Nerosoft.Starfish.Transit; + +namespace Nerosoft.Starfish.Application; + +internal class TeamMappingProfile : Profile +{ + public TeamMappingProfile() + { + CreateMap(); + CreateMap(); + + CreateMap() + .ForMember(dest => dest.UserName, options => options.MapFrom(src => src.User.UserName)) + .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)); + } +} \ No newline at end of file diff --git a/Source/Starfish.Service/Application/Mappings/UserMappingProfile.cs b/Source/Starfish.Service/Application/Mappings/UserMappingProfile.cs index d102b96..69421c7 100644 --- a/Source/Starfish.Service/Application/Mappings/UserMappingProfile.cs +++ b/Source/Starfish.Service/Application/Mappings/UserMappingProfile.cs @@ -7,7 +7,7 @@ namespace Nerosoft.Starfish.Application; /// /// 用户映射配置 /// -public class UserMappingProfile : Profile +internal class UserMappingProfile : Profile { /// /// diff --git a/Source/Starfish.Service/Domain/Aggregates/AppInfo.cs b/Source/Starfish.Service/Domain/Aggregates/AppInfo.cs index 5ab950b..c0c7aaa 100644 --- a/Source/Starfish.Service/Domain/Aggregates/AppInfo.cs +++ b/Source/Starfish.Service/Domain/Aggregates/AppInfo.cs @@ -23,7 +23,7 @@ private AppInfo() /// /// 团队Id /// - public long? TeamId { get; set; } + public int TeamId { get; set; } /// /// 名称 @@ -63,13 +63,15 @@ private AppInfo() /// /// 创建应用 /// + /// /// /// /// - internal static AppInfo Create(string name, string code) + internal static AppInfo Create(int teamId, string name, string code) { var entity = new AppInfo { + TeamId = teamId, Name = name, Code = code.Normalize(TextCaseType.Lower) }; diff --git a/Source/Starfish.Service/Domain/Aggregates/Team.cs b/Source/Starfish.Service/Domain/Aggregates/Team.cs index f3eb8a7..4cc4e84 100644 --- a/Source/Starfish.Service/Domain/Aggregates/Team.cs +++ b/Source/Starfish.Service/Domain/Aggregates/Team.cs @@ -7,5 +7,89 @@ namespace Nerosoft.Starfish.Domain; /// public sealed class Team : Aggregate { + public string Alias { get; set; } -} + public string Name { get; set; } + + public string Description { get; set; } + + public int OwnerId { get; set; } + + public int MemberCount { get; set; } + + public HashSet Members { get; set; } + + internal static Team Create(string name, string description, int ownerId) + { + var team = new Team + { + Name = name, + Description = description, + OwnerId = ownerId + }; + team.AddMember(ownerId); + return team; + } + + internal void SetAlias(string alias) + { + alias = alias.Normalize(TextCaseType.Lower); + if (string.Equals(Alias, alias)) + { + return; + } + + Alias = alias; + } + + internal void SetName(string name) + { + if (string.Equals(Name, name)) + { + return; + } + + Name = name; + } + + internal void SetDescription(string description) + { + if (string.Equals(Description, description)) + { + return; + } + + Description = description; + } + + internal void AddMember(int userId) + { + Members ??= []; + + if (Members.Any(t => t.UserId == userId)) + { + return; + } + + Members.Add(TeamMember.Create(userId)); + + MemberCount++; + } + + internal void RemoveMember(int userId) + { + if (Members == null || Members.All(t => t.UserId != userId)) + { + return; + } + + if (userId == OwnerId) + { + throw new InvalidOperationException("团队所有者不能被移除"); + } + + Members.RemoveWhere(t => t.UserId == userId); + + MemberCount--; + } +} \ No newline at end of file diff --git a/Source/Starfish.Service/Domain/Aggregates/TeamMember.cs b/Source/Starfish.Service/Domain/Aggregates/TeamMember.cs index 2da2124..ea07b0d 100644 --- a/Source/Starfish.Service/Domain/Aggregates/TeamMember.cs +++ b/Source/Starfish.Service/Domain/Aggregates/TeamMember.cs @@ -5,6 +5,30 @@ namespace Nerosoft.Starfish.Domain; /// /// 团队成员实体 /// -public sealed class TeamMember : Entity +public sealed class TeamMember : Entity, IHasCreateTime { -} + private TeamMember() + { + } + + private TeamMember(int userId) + : this() + { + UserId = userId; + } + + public int UserId { get; set; } + + public int TeamId { get; set; } + + public DateTime CreateTime { get; set; } + + public User User { get; set; } + + public Team Team { get; set; } + + internal static TeamMember Create(int userId) + { + return new TeamMember(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 b1ac5f1..24f8f77 100644 --- a/Source/Starfish.Service/Domain/Aggregates/User.cs +++ b/Source/Starfish.Service/Domain/Aggregates/User.cs @@ -57,6 +57,11 @@ private User(string userName, string passwordHash, string passwordSalt) /// public string Email { get; set; } + /// + /// 电话 + /// + public string Phone { get; set; } + /// /// 授权失败次数 /// @@ -153,6 +158,15 @@ internal void SetEmail(string email) Email = email.Normalize(TextCaseType.Lower); } + /// + /// 设置电话 + /// + /// + internal void SetPhone(string phone) + { + Phone = phone; + } + /// /// 设置用户昵称 /// diff --git a/Source/Starfish.Service/Domain/Business/SettingGeneralBusiness.cs b/Source/Starfish.Service/Domain/Business/SettingGeneralBusiness.cs index 42810ae..57bd66f 100644 --- a/Source/Starfish.Service/Domain/Business/SettingGeneralBusiness.cs +++ b/Source/Starfish.Service/Domain/Business/SettingGeneralBusiness.cs @@ -104,7 +104,7 @@ protected override Task DeleteAsync(CancellationToken cancellationToken = defaul public class DuplicateCheckRule : RuleBase { - public override async Task ExecuteAsync(IRuleContext context, CancellationToken cancellationToken = new CancellationToken()) + public override async Task ExecuteAsync(IRuleContext context, CancellationToken cancellationToken = default) { var target = (SettingGeneralBusiness)context.Target; if (!target.IsInsert) diff --git a/Source/Starfish.Service/Domain/Business/TeamGeneralBusiness.cs b/Source/Starfish.Service/Domain/Business/TeamGeneralBusiness.cs new file mode 100644 index 0000000..d359467 --- /dev/null +++ b/Source/Starfish.Service/Domain/Business/TeamGeneralBusiness.cs @@ -0,0 +1,133 @@ +using Nerosoft.Euonia.Business; +using Nerosoft.Euonia.Claims; +using Nerosoft.Starfish.Service; + +// ReSharper disable MemberCanBePrivate.Global + +namespace Nerosoft.Starfish.Domain; + +public class TeamGeneralBusiness : EditableObjectBase +{ + [Inject] + public ITeamRepository TeamRepository { get; set; } + + [Inject] + public UserPrincipal Identity { get; set; } + + private Team Aggregate { get; set; } + + public static readonly PropertyInfo IdProperty = RegisterProperty(p => p.Id); + public static readonly PropertyInfo NameProperty = RegisterProperty(p => p.Name); + public static readonly PropertyInfo AliasProperty = RegisterProperty(p => p.Alias); + public static readonly PropertyInfo DescriptionProperty = RegisterProperty(p => p.Description); + + public int Id + { + get => GetProperty(IdProperty); + private set => LoadProperty(IdProperty, value); + } + + public string Name + { + get => GetProperty(NameProperty); + set => SetProperty(NameProperty, value); + } + + public string Alias + { + get => GetProperty(AliasProperty); + set => SetProperty(AliasProperty, value); + } + + public string Description + { + get => GetProperty(DescriptionProperty); + set => SetProperty(DescriptionProperty, value); + } + + protected override void AddRules() + { + Rules.AddRule(new TeamOwnerCheckRule()); + } + + [FactoryCreate] + protected override Task CreateAsync(CancellationToken cancellationToken = default) + { + return Task.CompletedTask; + } + + [FactoryFetch] + protected async Task FetchAsync(int id, CancellationToken cancellationToken = default) + { + var aggregate = await TeamRepository.GetAsync(id, true, cancellationToken); + + Aggregate = aggregate ?? throw new SettingNotFoundException(id); + + using (BypassRuleChecks) + { + Id = aggregate.Id; + Name = aggregate.Name; + Alias = aggregate.Alias; + Description = aggregate.Description; + } + } + + [FactoryInsert] + protected override Task InsertAsync(CancellationToken cancellationToken = default) + { + var team = Team.Create(Name, Description, Identity.GetUserIdOfInt32()); + if (!string.IsNullOrWhiteSpace(Alias)) + { + team.SetAlias(Alias); + } + + return TeamRepository.InsertAsync(team, true, cancellationToken) + .ContinueWith(task => + { + task.WaitAndUnwrapException(cancellationToken); + Id = task.Result.Id; + }, cancellationToken); + } + + [FactoryUpdate] + protected override Task UpdateAsync(CancellationToken cancellationToken = default) + { + if (ChangedProperties.Contains(NameProperty)) + { + Aggregate.SetName(Name); + } + + if (ChangedProperties.Contains(DescriptionProperty)) + { + Aggregate.SetDescription(Description); + } + + if (ChangedProperties.Contains(AliasProperty)) + { + Aggregate.SetAlias(Alias); + } + + return TeamRepository.UpdateAsync(Aggregate, true, cancellationToken); + } + + public class TeamOwnerCheckRule : RuleBase + { + public override Task ExecuteAsync(IRuleContext context, CancellationToken cancellationToken = default) + { + var target = (TeamGeneralBusiness)context.Target; + + if (!target.IsInsert) + { + if (target.Aggregate.OwnerId != target.Identity.GetUserIdOfInt32()) + { + context.AddErrorResult("只有团队拥有者才能修改信息"); + } + } + + { + } + + return Task.CompletedTask; + } + } +} \ No newline at end of file diff --git a/Source/Starfish.Service/Domain/Business/TeamMemberBusiness.cs b/Source/Starfish.Service/Domain/Business/TeamMemberBusiness.cs new file mode 100644 index 0000000..51578bc --- /dev/null +++ b/Source/Starfish.Service/Domain/Business/TeamMemberBusiness.cs @@ -0,0 +1,101 @@ +using Nerosoft.Euonia.Business; +using Nerosoft.Euonia.Claims; +using Nerosoft.Starfish.Service; + +// ReSharper disable MemberCanBePrivate.Global + +namespace Nerosoft.Starfish.Domain; + +public class TeamMemberBusiness : EditableObjectBase +{ + [Inject] + public ITeamRepository TeamRepository { get; set; } + + [Inject] + public IUserRepository UserRepository { get; set; } + + [Inject] + public UserPrincipal Identity { get; set; } + + private Team Aggregate { get; set; } + + public static readonly PropertyInfo UserIdsProperty = RegisterProperty(p => p.UserIds); + + public int[] UserIds + { + get => GetProperty(UserIdsProperty); + set => SetProperty(UserIdsProperty, value); + } + + protected override void AddRules() + { + Rules.AddRule(new TeamOwnerCheckRule()); + Rules.AddRule(new UserExistCheckRule()); + } + + [FactoryFetch] + protected async Task FetchAsync(int id, CancellationToken cancellationToken = default) + { + var aggregate = await TeamRepository.GetAsync(id, true, cancellationToken); + + Aggregate = aggregate ?? throw new SettingNotFoundException(id); + } + + [FactoryInsert] + protected override Task InsertAsync(CancellationToken cancellationToken = default) + { + foreach (var userId in UserIds) + { + Aggregate.AddMember(userId); + } + + return TeamRepository.UpdateAsync(Aggregate, true, cancellationToken); + } + + [FactoryDelete] + protected override Task DeleteAsync(CancellationToken cancellationToken = default) + { + foreach (var userId in UserIds) + { + Aggregate.RemoveMember(userId); + } + + return TeamRepository.UpdateAsync(Aggregate, true, cancellationToken); + } + + public class UserExistCheckRule : RuleBase + { + public override async Task ExecuteAsync(IRuleContext context, CancellationToken cancellationToken = default) + { + var target = (TeamMemberBusiness)context.Target; + if (target.State == ObjectEditState.Delete) + { + return; + } + + var users = await target.UserRepository.GetAsync(target.UserIds, false, cancellationToken); + + var notExists = target.UserIds.Except(users.Select(t => t.Id)).ToArray(); + + if (notExists.Length != 0) + { + context.AddErrorResult($"用户[{notExists.JoinAsString(", ")}]不存在"); + } + } + } + + public class TeamOwnerCheckRule : RuleBase + { + public override Task ExecuteAsync(IRuleContext context, CancellationToken cancellationToken = default) + { + var target = (TeamMemberBusiness)context.Target; + + if (target.Aggregate.OwnerId != target.Identity.GetUserIdOfInt32()) + { + context.AddErrorResult("只有团队拥有者才能添加/移除成员"); + } + + return Task.CompletedTask; + } + } +} \ No newline at end of file diff --git a/Source/Starfish.Service/Domain/Business/UserGeneralBusiness.cs b/Source/Starfish.Service/Domain/Business/UserGeneralBusiness.cs index 675315c..f2e94e4 100644 --- a/Source/Starfish.Service/Domain/Business/UserGeneralBusiness.cs +++ b/Source/Starfish.Service/Domain/Business/UserGeneralBusiness.cs @@ -26,6 +26,7 @@ public UserGeneralBusiness(IServiceProvider provider) public static readonly PropertyInfo PasswordProperty = RegisterProperty(p => p.Password); public static readonly PropertyInfo NickNameProperty = RegisterProperty(p => p.NickName); public static readonly PropertyInfo EmailProperty = RegisterProperty(p => p.Email); + public static readonly PropertyInfo PhoneProperty = RegisterProperty(p => p.Phone); public static readonly PropertyInfo> RolesProperty = RegisterProperty>(p => p.Roles); public int Id @@ -58,6 +59,12 @@ public string Email set => SetProperty(EmailProperty, value); } + public string Phone + { + get => GetProperty(PhoneProperty); + set => SetProperty(PhoneProperty, value); + } + public List Roles { get => GetProperty(RolesProperty); @@ -68,6 +75,7 @@ protected override void AddRules() { Rules.AddRule(new DuplicateUserNameCheckRule()); Rules.AddRule(new DuplicateEmailCheckRule()); + Rules.AddRule(new DuplicatePhoneCheckRule()); Rules.AddRule(new PasswordStrengthRule()); } @@ -90,6 +98,7 @@ protected async Task FetchAsync(int id, CancellationToken cancellationToken = de UserName = user.UserName; NickName = user.NickName; Email = user.Email; + Phone = user.Phone; Roles = user.Roles.Select(t => t.Name).ToList(); } } @@ -103,6 +112,11 @@ protected override Task InsertAsync(CancellationToken cancellationToken = defaul user.SetEmail(Email); } + if (!string.IsNullOrWhiteSpace(Phone)) + { + user.SetPhone(Phone); + } + user.SetNickName(NickName ?? UserName); if (Roles?.Count > 0) { @@ -125,6 +139,11 @@ protected override Task UpdateAsync(CancellationToken cancellationToken = defaul Aggregate.SetEmail(Email); } + if (ChangedProperties.Contains(PhoneProperty)) + { + Aggregate.SetPhone(Phone); + } + if (ChangedProperties.Contains(NickNameProperty)) { Aggregate.SetNickName(NickName); @@ -198,6 +217,31 @@ public override async Task ExecuteAsync(IRuleContext context, CancellationToken } } + public class DuplicatePhoneCheckRule : RuleBase + { + public override async Task ExecuteAsync(IRuleContext context, CancellationToken cancellationToken = default) + { + var target = (UserGeneralBusiness)context.Target; + if (string.IsNullOrWhiteSpace(target.Phone)) + { + return; + } + + var changed = target.ChangedProperties.Contains(PhoneProperty); + if (!changed) + { + return; + } + + var repository = target.Repository; + var exists = await repository.CheckPhoneExistsAsync(target.Phone, target.Id, cancellationToken); + if (exists) + { + context.AddErrorResult(string.Format(Resources.IDS_ERROR_USER_EMAIL_UNAVAILABLE, target.Email)); + } + } + } + public class PasswordStrengthRule : RuleBase { private const string REGEX_PATTERN = @"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[\x00-\xff]{8,32}$"; diff --git a/Source/Starfish.Service/Domain/Repositories/ITeamRepository.cs b/Source/Starfish.Service/Domain/Repositories/ITeamRepository.cs new file mode 100644 index 0000000..078651d --- /dev/null +++ b/Source/Starfish.Service/Domain/Repositories/ITeamRepository.cs @@ -0,0 +1,28 @@ +using System.Linq.Expressions; +using Nerosoft.Euonia.Repository; + +namespace Nerosoft.Starfish.Domain; + +public interface ITeamRepository : IRepository +{ + /// + /// 根据ID查询团队信息 + /// + /// + /// + /// + /// + Task GetAsync(int id, bool tracking, CancellationToken cancellationToken = default); + + /// + /// 查询指定用户所属团队 + /// + /// + /// + /// + Task> GetTeamsOfUserAsync(int userId, CancellationToken cancellationToken = default); + + Task> FindAsync(Expression> predicate, Func, IQueryable> builder, int page, int size, CancellationToken cancellationToken = default); + + Task> GetMembersAsync(int id, CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/Source/Starfish.Service/Domain/Repositories/IUserRepository.cs b/Source/Starfish.Service/Domain/Repositories/IUserRepository.cs index 34e627f..9ff76ef 100644 --- a/Source/Starfish.Service/Domain/Repositories/IUserRepository.cs +++ b/Source/Starfish.Service/Domain/Repositories/IUserRepository.cs @@ -43,6 +43,15 @@ public interface IUserRepository : IRepository /// Task CheckEmailExistsAsync(string email, int ignoreId, CancellationToken cancellationToken = default); + /// + /// 检查手机号是否存在 + /// + /// + /// + /// + /// + Task CheckPhoneExistsAsync(string phone, int ignoreId, CancellationToken cancellationToken = default); + /// /// 查询用户 /// @@ -55,4 +64,6 @@ public interface IUserRepository : IRepository Task> FindAsync(Expression> predicate, Func, IQueryable> builder, int page, int size, CancellationToken cancellationToken = default); Task GetAsync(int id, bool tracking, Func, IQueryable> propertyAction, CancellationToken cancellationToken = default); + + Task> GetAsync(IEnumerable ids, bool tracking, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/Source/Starfish.Service/Repository/Contexts/SqliteModelBuilder.cs b/Source/Starfish.Service/Repository/Contexts/SqliteModelBuilder.cs index 0ef75f7..2344f10 100644 --- a/Source/Starfish.Service/Repository/Contexts/SqliteModelBuilder.cs +++ b/Source/Starfish.Service/Repository/Contexts/SqliteModelBuilder.cs @@ -19,7 +19,8 @@ public void Configure(ModelBuilder modelBuilder) entity.HasKey(t => t.Id); - entity.HasIndex(t => t.UserName).IsUnique(); + entity.HasIndex(t => t.UserName) + .IsUnique(); entity.HasMany(t => t.Roles) .WithOne(t => t.User) @@ -141,5 +142,36 @@ public void Configure(ModelBuilder modelBuilder) .IsRequired() .HasValueGenerator(); }); + + modelBuilder.Entity(entity => + { + entity.ToTable("team"); + entity.HasKey(t => t.Id); + + entity.HasIndex(t => t.Alias).IsUnique(); + entity.HasIndex(t => t.Name); + entity.HasIndex(t => t.OwnerId); + + entity.HasMany(t => t.Members) + .WithOne(t => t.Team) + .HasForeignKey(t => t.TeamId); + }); + + modelBuilder.Entity(entity => + { + entity.ToTable("team_member"); + entity.HasKey(t => t.Id); + + entity.HasIndex([nameof(TeamMember.TeamId), nameof(TeamMember.UserId)], "IDX_TEAM_MEMBER_UNIQUE") + .IsUnique(); + + entity.HasOne(t => t.Team) + .WithMany(t => t.Members) + .HasForeignKey(t => t.TeamId); + + entity.HasOne(t => t.User) + .WithMany() + .HasForeignKey(t => t.UserId); + }); } } \ No newline at end of file diff --git a/Source/Starfish.Service/Repository/CriteriaExtensions.cs b/Source/Starfish.Service/Repository/CriteriaExtensions.cs index 9ca4702..4e4fd42 100644 --- a/Source/Starfish.Service/Repository/CriteriaExtensions.cs +++ b/Source/Starfish.Service/Repository/CriteriaExtensions.cs @@ -127,4 +127,20 @@ public static Specification GetSpecification(this UserCriteria criteria) return specification; } + + public static Specification GetSpecification(this TeamCriteria criteria) + { + Specification specification = new TrueSpecification(); + if (criteria == null) + { + return specification; + } + + if (!string.IsNullOrWhiteSpace(criteria.Keyword)) + { + specification &= TeamSpecification.Matches(criteria.Keyword); + } + + return specification; + } } \ No newline at end of file diff --git a/Source/Starfish.Service/Repository/Repositories/TeamRepository.cs b/Source/Starfish.Service/Repository/Repositories/TeamRepository.cs new file mode 100644 index 0000000..935aaed --- /dev/null +++ b/Source/Starfish.Service/Repository/Repositories/TeamRepository.cs @@ -0,0 +1,46 @@ +using System.Linq.Expressions; +using Microsoft.EntityFrameworkCore; +using Nerosoft.Euonia.Repository; +using Nerosoft.Starfish.Domain; +using Nerosoft.Starfish.Service; + +namespace Nerosoft.Starfish.Repository; + +public class TeamRepository : BaseRepository, ITeamRepository +{ + public TeamRepository(IContextProvider provider) + : base(provider) + { + } + + public Task> GetTeamsOfUserAsync(int userId, CancellationToken cancellationToken = default) + { + var memberSet = Context.Set(); + var teamSet = Context.Set(); + var query = from team in teamSet + join member in memberSet on team.Id equals member.TeamId + where member.UserId == userId + select team; + return query.ToListAsync(cancellationToken); + } + + public Task> FindAsync(Expression> predicate, Func, IQueryable> builder, int page, int size, CancellationToken cancellationToken = default) + { + var query = Context.Set().AsQueryable(); + if (builder != null) + { + query = builder(query); + } + + query = query.Where(predicate).Skip((page - 1) * size).Take(size); + return query.ToListAsync(cancellationToken); + } + + public Task> GetMembersAsync(int id, CancellationToken cancellationToken = default) + { + var query = Context.Set() + .Include(t => t.User) + .Where(x => x.TeamId == id); + return query.ToListAsync(cancellationToken); + } +} \ No newline at end of file diff --git a/Source/Starfish.Service/Repository/Repositories/UserRepository.cs b/Source/Starfish.Service/Repository/Repositories/UserRepository.cs index abdcec5..1c5c54c 100644 --- a/Source/Starfish.Service/Repository/Repositories/UserRepository.cs +++ b/Source/Starfish.Service/Repository/Repositories/UserRepository.cs @@ -39,10 +39,21 @@ public Task CheckUserNameExistsAsync(string userName, CancellationToken ca public Task CheckEmailExistsAsync(string email, int ignoreId, CancellationToken cancellationToken = default) { ISpecification[] specifications = - { + [ UserSpecification.EmailEquals(email), UserSpecification.IdNotEquals(ignoreId) - }; + ]; + var predicate = new CompositeSpecification(PredicateOperator.AndAlso, specifications).Satisfy(); + return ExistsAsync(predicate, cancellationToken); + } + + public Task CheckPhoneExistsAsync(string phone, int ignoreId, CancellationToken cancellationToken = default) + { + ISpecification[] specifications = + [ + UserSpecification.EmailEquals(phone), + UserSpecification.IdNotEquals(ignoreId) + ]; var predicate = new CompositeSpecification(PredicateOperator.AndAlso, specifications).Satisfy(); return ExistsAsync(predicate, cancellationToken); } diff --git a/Source/Starfish.Service/Repository/RepositoryModule.cs b/Source/Starfish.Service/Repository/RepositoryModule.cs index 679ded8..e78f1f1 100644 --- a/Source/Starfish.Service/Repository/RepositoryModule.cs +++ b/Source/Starfish.Service/Repository/RepositoryModule.cs @@ -121,6 +121,7 @@ public override void ConfigureServices(ServiceConfigurationContext context) .AddScoped() .AddScoped() .AddScoped() + .AddScoped() .AddScoped() .AddScoped() .AddScoped() diff --git a/Source/Starfish.Service/Repository/Specifications/TeamSpecification.cs b/Source/Starfish.Service/Repository/Specifications/TeamSpecification.cs new file mode 100644 index 0000000..268be5f --- /dev/null +++ b/Source/Starfish.Service/Repository/Specifications/TeamSpecification.cs @@ -0,0 +1,52 @@ +using Nerosoft.Euonia.Linq; +using Nerosoft.Starfish.Domain; + +namespace Nerosoft.Starfish.Repository; + +internal static class TeamSpecification +{ + public static Specification IdEquals(int id) + { + return new DirectSpecification(t => t.Id == id); + } + + public static Specification AliasEquals(string alias) + { + alias = alias.Normalize(TextCaseType.Lower); + return new DirectSpecification(t => t.Alias == alias); + } + + public static Specification NameEquals(string name) + { + name = name.Normalize(TextCaseType.Lower); + return new DirectSpecification(t => t.Name.ToLower() == name); + } + + public static Specification NameContains(string name) + { + name = name.Normalize(TextCaseType.Lower); + return new DirectSpecification(t => t.Name.ToLower().Contains(name)); + } + + public static Specification DescriptionContains(string description) + { + description = description.Normalize(TextCaseType.Lower); + return new DirectSpecification(t => t.Description.ToLower().Contains(description)); + } + + public static Specification Matches(string keyword) + { + ISpecification[] specifications = + [ + NameContains(keyword), + DescriptionContains(keyword) + ]; + + return new CompositeSpecification(PredicateOperator.OrElse, specifications); + } + + public static Specification HasMember(int userId) + { + return new DirectSpecification(t => t.Members.Any(m => m.UserId == userId)); + } +} \ No newline at end of file diff --git a/Source/Starfish.Service/Repository/Specifications/UserSpecification.cs b/Source/Starfish.Service/Repository/Specifications/UserSpecification.cs index b7ff8f3..90593a9 100644 --- a/Source/Starfish.Service/Repository/Specifications/UserSpecification.cs +++ b/Source/Starfish.Service/Repository/Specifications/UserSpecification.cs @@ -81,11 +81,11 @@ public static Specification InRoles(params string[] roles) public static Specification Matches(string keyword) { ISpecification[] specifications = - { + [ UserNameContains(keyword), NickNameContains(keyword), EmailContains(keyword) - }; + ]; return new CompositeSpecification(PredicateOperator.OrElse, specifications); } diff --git a/Source/Starfish.Service/UseCases/Identity/UserCountUseCase.cs b/Source/Starfish.Service/UseCases/Identity/UserCountUseCase.cs new file mode 100644 index 0000000..cc9ba11 --- /dev/null +++ b/Source/Starfish.Service/UseCases/Identity/UserCountUseCase.cs @@ -0,0 +1,30 @@ +using Nerosoft.Euonia.Application; +using Nerosoft.Starfish.Domain; +using Nerosoft.Starfish.Repository; +using Nerosoft.Starfish.Transit; + +namespace Nerosoft.Starfish.UseCases; + +public interface IUserCountUseCase : IUseCase; + +public class UserCountUseCase : IUserCountUseCase +{ + private readonly IUserRepository _repository; + + public UserCountUseCase(IUserRepository repository) + { + _repository = repository; + } + + public Task ExecuteAsync(UserCriteria input, CancellationToken cancellationToken = default) + { + var specification = input.GetSpecification(); + var predicate = specification.Satisfy(); + return _repository.CountAsync(predicate, cancellationToken) + .ContinueWith(task => + { + task.WaitAndUnwrapException(cancellationToken); + return task.Result; + }, cancellationToken); + } +} \ No newline at end of file diff --git a/Source/Starfish.Service/UseCases/Setting/SettingUpdateUseCase.cs b/Source/Starfish.Service/UseCases/Setting/SettingUpdateUseCase.cs index 1b397b6..c16451c 100644 --- a/Source/Starfish.Service/UseCases/Setting/SettingUpdateUseCase.cs +++ b/Source/Starfish.Service/UseCases/Setting/SettingUpdateUseCase.cs @@ -19,7 +19,7 @@ public SettingUpdateUseCase(IBus bus) _bus = bus; } - public Task ExecuteAsync(SettingUpdateInput input, CancellationToken cancellationToken = new CancellationToken()) + public Task ExecuteAsync(SettingUpdateInput input, CancellationToken cancellationToken = default) { var data = Cryptography.Base64.Decrypt(input.Data.Data); var command = new SettingUpdateCommand(input.Id) diff --git a/Source/Starfish.Service/UseCases/Team/TeamCountUseCase.cs b/Source/Starfish.Service/UseCases/Team/TeamCountUseCase.cs new file mode 100644 index 0000000..b52e0b2 --- /dev/null +++ b/Source/Starfish.Service/UseCases/Team/TeamCountUseCase.cs @@ -0,0 +1,45 @@ +using Nerosoft.Euonia.Application; +using Nerosoft.Euonia.Claims; +using Nerosoft.Euonia.Repository.EfCore; +using Nerosoft.Starfish.Domain; +using Nerosoft.Starfish.Repository; +using Nerosoft.Starfish.Transit; + +namespace Nerosoft.Starfish.UseCases; + +public interface ITeamCountUseCase : IUseCase; + +public record TeamCountInput(TeamCriteria Criteria) : IUseCaseInput; + +public record TeamCountOutput(int Result) : IUseCaseOutput; + +public class TeamCountUseCase : ITeamCountUseCase +{ + private readonly ITeamRepository _repository; + private readonly UserPrincipal _identity; + + public TeamCountUseCase(ITeamRepository repository, UserPrincipal identity) + { + _repository = repository; + _identity = identity; + } + + public Task ExecuteAsync(TeamCountInput input, CancellationToken cancellationToken = default) + { + var specification = input.Criteria.GetSpecification(); + if (!_identity.IsInRole("SA")) + { + specification &= TeamSpecification.HasMember(_identity.GetUserIdOfInt32()); + } + + var predicate = specification.Satisfy(); + + return _repository.Include(t => t.Members) + .CountAsync(predicate, cancellationToken) + .ContinueWith(task => + { + task.WaitAndUnwrapException(cancellationToken); + return new TeamCountOutput(task.Result); + }, cancellationToken); + } +} \ No newline at end of file diff --git a/Source/Starfish.Service/UseCases/Team/TeamCreateUseCase.cs b/Source/Starfish.Service/UseCases/Team/TeamCreateUseCase.cs new file mode 100644 index 0000000..b9e388b --- /dev/null +++ b/Source/Starfish.Service/UseCases/Team/TeamCreateUseCase.cs @@ -0,0 +1,33 @@ +using Nerosoft.Euonia.Application; +using Nerosoft.Euonia.Bus; +using Nerosoft.Starfish.Application; +using Nerosoft.Starfish.Transit; + +namespace Nerosoft.Starfish.UseCases; + +public interface ITeamCreateUseCase : IUseCase; + +public record TeamCreateInput(TeamEditDto Data) : IUseCaseInput; + +public record TeamCreateOutput(int Result) : IUseCaseOutput; + +public class TeamCreateUseCase : ITeamCreateUseCase +{ + private readonly IBus _bus; + + public TeamCreateUseCase(IBus bus) + { + _bus = bus; + } + + public Task ExecuteAsync(TeamCreateInput input, CancellationToken cancellationToken = default) + { + var command = new TeamCreateCommand(input.Data); + return _bus.SendAsync(command, cancellationToken) + .ContinueWith(task => + { + task.WaitAndUnwrapException(cancellationToken); + return new TeamCreateOutput(task.Result); + }, cancellationToken); + } +} \ No newline at end of file diff --git a/Source/Starfish.Service/UseCases/Team/TeamMemberAppendUseCase.cs b/Source/Starfish.Service/UseCases/Team/TeamMemberAppendUseCase.cs new file mode 100644 index 0000000..55dda6c --- /dev/null +++ b/Source/Starfish.Service/UseCases/Team/TeamMemberAppendUseCase.cs @@ -0,0 +1,25 @@ +using Nerosoft.Euonia.Application; +using Nerosoft.Euonia.Bus; +using Nerosoft.Starfish.Application; + +namespace Nerosoft.Starfish.UseCases; + +public interface ITeamMemberAppendUseCase : INonOutputUseCase; + +public record TeamMemberAppendInput(int Id, List UserIds) : IUseCaseInput; + +public class TeamMemberAppendUseCase : ITeamMemberAppendUseCase +{ + private readonly IBus _bus; + + public TeamMemberAppendUseCase(IBus bus) + { + _bus = bus; + } + + public Task ExecuteAsync(TeamMemberAppendInput input, CancellationToken cancellationToken = new CancellationToken()) + { + var command = new TeamMemberEditCommand(input.Id, input.UserIds, "+"); + return _bus.SendAsync(command, cancellationToken); + } +} \ No newline at end of file diff --git a/Source/Starfish.Service/UseCases/Team/TeamMemberQueryUseCase.cs b/Source/Starfish.Service/UseCases/Team/TeamMemberQueryUseCase.cs new file mode 100644 index 0000000..33e5c93 --- /dev/null +++ b/Source/Starfish.Service/UseCases/Team/TeamMemberQueryUseCase.cs @@ -0,0 +1,32 @@ +using Nerosoft.Euonia.Application; +using Nerosoft.Starfish.Domain; +using Nerosoft.Starfish.Transit; + +namespace Nerosoft.Starfish.UseCases; + +public interface ITeamMemberQueryUseCase : IUseCase; + +public record TeamMemberQueryOutput(List Result) : IUseCaseOutput; + +public record TeamMemberQueryInput(int Id) : IUseCaseInput; + +public class TeamMemberQueryUseCase : ITeamMemberQueryUseCase +{ + private readonly ITeamRepository _repository; + + public TeamMemberQueryUseCase(ITeamRepository repository) + { + _repository = repository; + } + + public Task ExecuteAsync(TeamMemberQueryInput input, CancellationToken cancellationToken = new CancellationToken()) + { + return _repository.GetMembersAsync(input.Id, cancellationToken) + .ContinueWith(task => + { + task.WaitAndUnwrapException(cancellationToken); + var resul = task.Result.ProjectedAsCollection(); + return new TeamMemberQueryOutput(resul); + }, cancellationToken); + } +} \ No newline at end of file diff --git a/Source/Starfish.Service/UseCases/Team/TeamMemberQuitUseCase.cs b/Source/Starfish.Service/UseCases/Team/TeamMemberQuitUseCase.cs new file mode 100644 index 0000000..d34737e --- /dev/null +++ b/Source/Starfish.Service/UseCases/Team/TeamMemberQuitUseCase.cs @@ -0,0 +1,25 @@ +using Nerosoft.Euonia.Application; +using Nerosoft.Euonia.Bus; +using Nerosoft.Starfish.Application; + +namespace Nerosoft.Starfish.UseCases; + +public interface ITeamMemberQuitUseCase : INonOutputUseCase; + +public record TeamMemberQuitInput(int TeamId, int UserId) : IUseCaseInput; + +public class TeamMemberQuitUseCase : ITeamMemberQuitUseCase +{ + private readonly IBus _bus; + + public TeamMemberQuitUseCase(IBus bus) + { + _bus = bus; + } + + public Task ExecuteAsync(TeamMemberQuitInput input, CancellationToken cancellationToken = new CancellationToken()) + { + var command = new TeamMemberEditCommand(input.TeamId, [input.UserId], "-"); + return _bus.SendAsync(command, cancellationToken); + } +} \ No newline at end of file diff --git a/Source/Starfish.Service/UseCases/Team/TeamMemberRemoveUseCase.cs b/Source/Starfish.Service/UseCases/Team/TeamMemberRemoveUseCase.cs new file mode 100644 index 0000000..ca9ba53 --- /dev/null +++ b/Source/Starfish.Service/UseCases/Team/TeamMemberRemoveUseCase.cs @@ -0,0 +1,25 @@ +using Nerosoft.Euonia.Application; +using Nerosoft.Euonia.Bus; +using Nerosoft.Starfish.Application; + +namespace Nerosoft.Starfish.UseCases; + +public interface ITeamMemberRemoveUseCase : INonOutputUseCase; + +public record TeamMemberRemoveInput(int Id, List UserIds) : IUseCaseInput; + +public class TeamMemberRemoveUseCase : ITeamMemberRemoveUseCase +{ + private readonly IBus _bus; + + public TeamMemberRemoveUseCase(IBus bus) + { + _bus = bus; + } + + public Task ExecuteAsync(TeamMemberRemoveInput input, CancellationToken cancellationToken = new CancellationToken()) + { + var command = new TeamMemberEditCommand(input.Id, input.UserIds, "-"); + return _bus.SendAsync(command, cancellationToken); + } +} \ No newline at end of file diff --git a/Source/Starfish.Service/UseCases/Team/TeamQueryUseCase.cs b/Source/Starfish.Service/UseCases/Team/TeamQueryUseCase.cs new file mode 100644 index 0000000..0eea0a8 --- /dev/null +++ b/Source/Starfish.Service/UseCases/Team/TeamQueryUseCase.cs @@ -0,0 +1,45 @@ +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; + +public interface ITeamQueryUseCase : IUseCase; + +public record TeamQueryInput(TeamCriteria Criteria, int Page, int Size) : IUseCaseInput; + +public record TeamQueryOutput(List Result) : IUseCaseOutput; + +public class TeamQueryUseCase : ITeamQueryUseCase +{ + private readonly ITeamRepository _repository; + private readonly UserPrincipal _identity; + + public TeamQueryUseCase(ITeamRepository repository, UserPrincipal identity) + { + _repository = repository; + _identity = identity; + } + + public Task ExecuteAsync(TeamQueryInput input, CancellationToken cancellationToken = default) + { + var specification = input.Criteria.GetSpecification(); + if (!_identity.IsInRole("SA")) + { + specification &= TeamSpecification.HasMember(_identity.GetUserIdOfInt32()); + } + + var predicate = specification.Satisfy(); + + return _repository.FindAsync(predicate, query => query.Include(nameof(Team.Members)).OrderByDescending(t => t.Id), input.Page, input.Size, cancellationToken) + .ContinueWith(task => + { + task.WaitAndUnwrapException(cancellationToken); + var result = task.Result.ProjectedAsCollection(); + return new TeamQueryOutput(result); + }, cancellationToken); + } +} \ No newline at end of file diff --git a/Source/Starfish.Service/UseCases/Team/TeamUpdateUseCase.cs b/Source/Starfish.Service/UseCases/Team/TeamUpdateUseCase.cs new file mode 100644 index 0000000..db86c71 --- /dev/null +++ b/Source/Starfish.Service/UseCases/Team/TeamUpdateUseCase.cs @@ -0,0 +1,26 @@ +using Nerosoft.Euonia.Application; +using Nerosoft.Euonia.Bus; +using Nerosoft.Starfish.Application; +using Nerosoft.Starfish.Transit; + +namespace Nerosoft.Starfish.UseCases; + +public interface ITeamUpdateUseCase : INonOutputUseCase; + +public record TeamUpdateInput(int Id, TeamEditDto Data) : IUseCaseInput; + +public class TeamUpdateUseCase : ITeamUpdateUseCase +{ + private readonly IBus _bus; + + public TeamUpdateUseCase(IBus bus) + { + _bus = bus; + } + + public Task ExecuteAsync(TeamUpdateInput input, CancellationToken cancellationToken = default) + { + var command = new TeamUpdateCommand(input.Id, input.Data); + return _bus.SendAsync(command, cancellationToken); + } +} \ No newline at end of file diff --git a/Source/Starfish.Transit/Apps/AppInfoCreateDto.cs b/Source/Starfish.Transit/Apps/AppInfoCreateDto.cs index 2d38af1..8239a51 100644 --- a/Source/Starfish.Transit/Apps/AppInfoCreateDto.cs +++ b/Source/Starfish.Transit/Apps/AppInfoCreateDto.cs @@ -5,6 +5,11 @@ /// public class AppInfoCreateDto { + /// + /// 团队Id + /// + public int TeamId { get; set; } + /// /// 应用名称 /// diff --git a/Source/Starfish.Transit/Identity/UserCreateDto.cs b/Source/Starfish.Transit/Identity/UserCreateDto.cs index d55b460..ed09f75 100644 --- a/Source/Starfish.Transit/Identity/UserCreateDto.cs +++ b/Source/Starfish.Transit/Identity/UserCreateDto.cs @@ -20,6 +20,11 @@ public class UserCreateDto /// public string Email { get; set; } + /// + /// 电话 + /// + public string Phone { get; set; } + /// /// 昵称 /// diff --git a/Source/Starfish.Transit/Identity/UserDetailDto.cs b/Source/Starfish.Transit/Identity/UserDetailDto.cs index a94b448..c5f92d7 100644 --- a/Source/Starfish.Transit/Identity/UserDetailDto.cs +++ b/Source/Starfish.Transit/Identity/UserDetailDto.cs @@ -20,6 +20,11 @@ public class UserDetailDto ///
public string Email { get; set; } + /// + /// 电话 + /// + public string Phone { get; set; } + /// /// 昵称 /// diff --git a/Source/Starfish.Transit/Identity/UserItemDto.cs b/Source/Starfish.Transit/Identity/UserItemDto.cs index 6082671..ab62486 100644 --- a/Source/Starfish.Transit/Identity/UserItemDto.cs +++ b/Source/Starfish.Transit/Identity/UserItemDto.cs @@ -20,6 +20,11 @@ public class UserItemDto ///
public string Email { get; set; } + /// + /// 电话 + /// + public string Phone { get; set; } + /// /// 昵称 /// diff --git a/Source/Starfish.Transit/Team/TeamCriteria.cs b/Source/Starfish.Transit/Team/TeamCriteria.cs new file mode 100644 index 0000000..82c6b01 --- /dev/null +++ b/Source/Starfish.Transit/Team/TeamCriteria.cs @@ -0,0 +1,12 @@ +namespace Nerosoft.Starfish.Transit; + +/// +/// 团队查询条件 +/// +public class TeamCriteria +{ + /// + /// 关键字 + /// + public string Keyword { get; set; } +} \ No newline at end of file diff --git a/Source/Starfish.Transit/Team/TeamDetailDto.cs b/Source/Starfish.Transit/Team/TeamDetailDto.cs new file mode 100644 index 0000000..293afc0 --- /dev/null +++ b/Source/Starfish.Transit/Team/TeamDetailDto.cs @@ -0,0 +1,37 @@ +namespace Nerosoft.Starfish.Transit; + +/// +/// 团队详情Dto +/// +public class TeamDetailDto +{ + /// + /// Id + /// + public int Id { get; set; } + + /// + /// 别名 + /// + public string Alias { get; set; } + + /// + /// 名称 + /// + public string Name { get; set; } + + /// + /// 团队描述 + /// + public string Description { get; set; } + + /// + /// 团队所有者Id + /// + public int OwnerId { get; set; } + + /// + /// 成员数量 + /// + public int MemberCount { get; set; } +} \ No newline at end of file diff --git a/Source/Starfish.Transit/Team/TeamEditDto.cs b/Source/Starfish.Transit/Team/TeamEditDto.cs new file mode 100644 index 0000000..6932673 --- /dev/null +++ b/Source/Starfish.Transit/Team/TeamEditDto.cs @@ -0,0 +1,23 @@ +namespace Nerosoft.Starfish.Transit; + +/// +/// 团队编辑Dto +/// +public class TeamEditDto +{ + /// + /// 别名 + /// + /// 全局唯一 + public string Alias { get; set; } + + /// + /// 团队名称 + /// + public string Name { get; set; } + + /// + /// 团队描述 + /// + public string Description { get; set; } +} \ No newline at end of file diff --git a/Source/Starfish.Transit/Team/TeamItemDto.cs b/Source/Starfish.Transit/Team/TeamItemDto.cs new file mode 100644 index 0000000..48991ea --- /dev/null +++ b/Source/Starfish.Transit/Team/TeamItemDto.cs @@ -0,0 +1,37 @@ +namespace Nerosoft.Starfish.Transit; + +/// +/// 团队列表项Dto +/// +public class TeamItemDto +{ + /// + /// Id + /// + public int Id { get; set; } + + /// + /// 别名 + /// + public string Alias { get; set; } + + /// + /// 名称 + /// + public string Name { get; set; } + + /// + /// 团队描述 + /// + public string Description { get; set; } + + /// + /// 团队所有者Id + /// + public int OwnerId { get; set; } + + /// + /// 成员数量 + /// + public int MemberCount { get; set; } +} \ No newline at end of file diff --git a/Source/Starfish.Transit/Team/TeamMemberDto.cs b/Source/Starfish.Transit/Team/TeamMemberDto.cs new file mode 100644 index 0000000..4067798 --- /dev/null +++ b/Source/Starfish.Transit/Team/TeamMemberDto.cs @@ -0,0 +1,32 @@ +namespace Nerosoft.Starfish.Transit; + +/// +/// 团队成员Dto +/// +public class TeamMemberDto +{ + /// + /// 用户Id + /// + public int UserId { get; set; } + + /// + /// 用户名 + /// + public string UserName { get; set; } + + /// + /// 昵称 + /// + public string NickName { get; set; } + + /// + /// 邮箱 + /// + public string Email { get; set; } + + /// + /// 电话 + /// + public string Phone { get; set; } +} \ No newline at end of file diff --git a/Source/Starfish.Webapi/Controllers/TeamController.cs b/Source/Starfish.Webapi/Controllers/TeamController.cs new file mode 100644 index 0000000..f602e32 --- /dev/null +++ b/Source/Starfish.Webapi/Controllers/TeamController.cs @@ -0,0 +1,131 @@ +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 TeamController : ControllerBase +{ + private readonly ITeamApplicationService _service; + + /// + /// 构造函数 + /// + /// + public TeamController(ITeamApplicationService service) + { + _service = service; + } + + /// + /// 搜索团队 + /// + /// + /// + /// + /// + [HttpGet] + [Produces(typeof(List))] + public async Task QueryAsync([FromQuery] TeamCriteria criteria, int page = 1, int size = 10) + { + var result = await _service.QueryAsync(criteria, page, size, HttpContext.RequestAborted); + return Ok(result); + } + + /// + /// 获取团队数量 + /// + /// + /// + [HttpGet("count")] + [Produces(typeof(int))] + public async Task CountAsync([FromQuery] TeamCriteria criteria) + { + var result = await _service.CountAsync(criteria, HttpContext.RequestAborted); + return Ok(result); + } + + /// + /// 创建团队 + /// + /// + /// + [HttpPost] + public async Task CreateAsync([FromBody] TeamEditDto data) + { + var result = await _service.CreateAsync(data, HttpContext.RequestAborted); + Response.Headers.Append("Entry", $"{result}"); + return Ok(); + } + + /// + /// 更新团队 + /// + /// + /// + /// + [HttpPut("{id:int}")] + public async Task UpdateAsync(int id, [FromBody] TeamEditDto data) + { + await _service.UpdateAsync(id, data, HttpContext.RequestAborted); + return Ok(); + } + + /// + /// 查询团队成员 + /// + /// + /// + [HttpGet("{id:int}/member")] + [Produces(typeof(List))] + public async Task QueryMembersAsync(int id) + { + var result = await _service.QueryMembersAsync(id, HttpContext.RequestAborted); + return Ok(result); + } + + /// + /// 添加团队成员 + /// + /// + /// + /// + [HttpPost("{id:int}/member")] + public async Task AppendMembersAsync(int id, [FromBody] List userIds) + { + await _service.AppendMembersAsync(id, userIds, HttpContext.RequestAborted); + return Ok(); + } + + /// + /// 移除团队成员 + /// + /// + /// + /// + [HttpDelete("{id:int}/member")] + public async Task RemoveMembersAsync(int id, [FromBody] List userIds) + { + await _service.RemoveMembersAsync(id, userIds, HttpContext.RequestAborted); + return Ok(); + } + + /// + /// 退出团队 + /// + /// + /// + [HttpDelete("{id:int}/quit")] + public async Task QuitAsync(int id) + { + await _service.QuitAsync(id, HttpContext.RequestAborted); + return Ok(); + } +} \ No newline at end of file diff --git a/Source/Starfish.Webapi/Controllers/UserController.cs b/Source/Starfish.Webapi/Controllers/UserController.cs index 77daecc..e0b1dfa 100644 --- a/Source/Starfish.Webapi/Controllers/UserController.cs +++ b/Source/Starfish.Webapi/Controllers/UserController.cs @@ -32,6 +32,7 @@ public UserController(IUserApplicationService service) /// /// [HttpGet] + [Produces(typeof(List))] public async Task SearchAsync([FromQuery] UserCriteria criteria, int page = 1, int size = 10) { var result = await _service.SearchAsync(criteria, page, size, HttpContext.RequestAborted); @@ -44,6 +45,7 @@ public async Task SearchAsync([FromQuery] UserCriteria criteria, /// /// [HttpGet("count")] + [Produces(typeof(int))] public async Task CountAsync([FromQuery] UserCriteria criteria) { var result = await _service.CountAsync(criteria, HttpContext.RequestAborted); @@ -56,6 +58,7 @@ public async Task CountAsync([FromQuery] UserCriteria criteria) /// /// [HttpGet("{id:int}")] + [Produces(typeof(UserDetailDto))] public async Task GetAsync(int id) { var result = await _service.GetAsync(id, HttpContext.RequestAborted); @@ -71,7 +74,8 @@ public async Task GetAsync(int id) public async Task CreateAsync([FromBody] UserCreateDto data) { var result = await _service.CreateAsync(data, HttpContext.RequestAborted); - return Ok(result); + Response.Headers.Append("Entry", $"{result}"); + return Ok(); } /// From ed8b4c87f666b41031fec12d24a360504534c818 Mon Sep 17 00:00:00 2001 From: damon Date: Sun, 7 Jan 2024 21:19:55 +0800 Subject: [PATCH 14/22] =?UTF-8?q?=E5=8D=87=E7=BA=A7=E5=8C=85=E7=89=88?= =?UTF-8?q?=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Directory.Packages.props | 2 +- README.md | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 0f10496..5ed4e08 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -2,7 +2,7 @@ true true - 8.1.15 + 8.1.16 diff --git a/README.md b/README.md index 84b46cc..092abaf 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,10 @@ graph TD Starfish.Service --> Starfish.Common Starfish.Client --> Starfish.Common + + Starfish.Webapp --> Starfish.Client + Starfish.Webapp --> Starfish.Common + Starfish.Webapp --> Starfish.Transit ``` ## Requirements/环境要求 From c77e2564877a25f309be54efe377af3cdb2c57fc Mon Sep 17 00:00:00 2001 From: damon Date: Mon, 8 Jan 2024 00:15:06 +0800 Subject: [PATCH 15/22] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=B3=A8=E5=86=8C?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E9=A2=86=E5=9F=9F=E6=9C=8D=E5=8A=A1bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Directory.Packages.props | 1 + .../Contracts/ITeamApplicationService.cs | 2 + .../Handlers/UserCommandHandler.cs | 3 +- .../Implements/TeamApplicationService.cs | 8 ++++ .../Domain/Business/TeamGeneralBusiness.cs | 9 ++-- .../Domain/Business/TeamMemberBusiness.cs | 3 -- .../Domain/Business/UserGeneralBusiness.cs | 3 +- .../Seedwork/EditableObjectBase.cs | 2 +- .../Starfish.Service/Starfish.Service.csproj | 1 + .../UseCases/Team/TeamDetailUseCase.cs | 46 +++++++++++++++++++ .../UseCases/Team/TeamMemberAppendUseCase.cs | 2 +- .../UseCases/Team/TeamMemberQueryUseCase.cs | 2 +- .../UseCases/Team/TeamMemberQuitUseCase.cs | 2 +- .../UseCases/Team/TeamMemberRemoveUseCase.cs | 2 +- .../Controllers/TeamController.cs | 8 ++++ Source/Starfish.Webapi/HostServiceModule.cs | 2 + Source/Starfish.Webapi/appsettings.json | 3 ++ 17 files changed, 83 insertions(+), 16 deletions(-) create mode 100644 Source/Starfish.Service/UseCases/Team/TeamDetailUseCase.cs diff --git a/Directory.Packages.props b/Directory.Packages.props index 5ed4e08..b978a6e 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -35,6 +35,7 @@ + diff --git a/Source/Starfish.Service/Application/Contracts/ITeamApplicationService.cs b/Source/Starfish.Service/Application/Contracts/ITeamApplicationService.cs index dce55e0..1e776b8 100644 --- a/Source/Starfish.Service/Application/Contracts/ITeamApplicationService.cs +++ b/Source/Starfish.Service/Application/Contracts/ITeamApplicationService.cs @@ -9,6 +9,8 @@ public interface ITeamApplicationService : IApplicationService Task CountAsync(TeamCriteria criteria, CancellationToken cancellationToken = default); + Task GetAsync(int id, CancellationToken cancellationToken = default); + Task CreateAsync(TeamEditDto data, CancellationToken cancellationToken = default); Task UpdateAsync(int id, TeamEditDto data, CancellationToken cancellationToken = default); diff --git a/Source/Starfish.Service/Application/Handlers/UserCommandHandler.cs b/Source/Starfish.Service/Application/Handlers/UserCommandHandler.cs index aff369f..f13c9b5 100644 --- a/Source/Starfish.Service/Application/Handlers/UserCommandHandler.cs +++ b/Source/Starfish.Service/Application/Handlers/UserCommandHandler.cs @@ -31,13 +31,14 @@ public Task HandleAsync(UserCreateCommand message, MessageContext context, Cance { return ExecuteAsync(async () => { - var business = await Factory.CreateAsync(); + var business = await Factory.CreateAsync(cancellationToken); business.UserName = message.Item1.UserName; business.Password = message.Item1.Password; business.NickName = message.Item1.NickName; business.Email = message.Item1.Email; business.Phone = message.Item1.Phone; business.Roles = message.Item1.Roles; + business.MarkAsInsert(); await business.SaveAsync(false, cancellationToken); return business.Id; diff --git a/Source/Starfish.Service/Application/Implements/TeamApplicationService.cs b/Source/Starfish.Service/Application/Implements/TeamApplicationService.cs index 9b28d60..0f7a896 100644 --- a/Source/Starfish.Service/Application/Implements/TeamApplicationService.cs +++ b/Source/Starfish.Service/Application/Implements/TeamApplicationService.cs @@ -22,6 +22,14 @@ public Task CountAsync(TeamCriteria criteria, CancellationToken cancellatio .ContinueWith(task => task.Result.Result, cancellationToken); } + public Task GetAsync(int id, CancellationToken cancellationToken = default) + { + var useCase = LazyServiceProvider.GetService(); + var input = new TeamDetailInput(id); + return useCase.ExecuteAsync(input, cancellationToken) + .ContinueWith(task => task.Result.Result, cancellationToken); + } + public Task CreateAsync(TeamEditDto data, CancellationToken cancellationToken = default) { var useCase = LazyServiceProvider.GetService(); diff --git a/Source/Starfish.Service/Domain/Business/TeamGeneralBusiness.cs b/Source/Starfish.Service/Domain/Business/TeamGeneralBusiness.cs index d359467..ecc94cb 100644 --- a/Source/Starfish.Service/Domain/Business/TeamGeneralBusiness.cs +++ b/Source/Starfish.Service/Domain/Business/TeamGeneralBusiness.cs @@ -1,19 +1,16 @@ using Nerosoft.Euonia.Business; -using Nerosoft.Euonia.Claims; +using Nerosoft.Euonia.Domain; using Nerosoft.Starfish.Service; // ReSharper disable MemberCanBePrivate.Global namespace Nerosoft.Starfish.Domain; -public class TeamGeneralBusiness : EditableObjectBase +public class TeamGeneralBusiness : EditableObjectBase, IDomainService { [Inject] public ITeamRepository TeamRepository { get; set; } - - [Inject] - public UserPrincipal Identity { get; set; } - + private Team Aggregate { get; set; } public static readonly PropertyInfo IdProperty = RegisterProperty(p => p.Id); diff --git a/Source/Starfish.Service/Domain/Business/TeamMemberBusiness.cs b/Source/Starfish.Service/Domain/Business/TeamMemberBusiness.cs index 51578bc..b1123a6 100644 --- a/Source/Starfish.Service/Domain/Business/TeamMemberBusiness.cs +++ b/Source/Starfish.Service/Domain/Business/TeamMemberBusiness.cs @@ -14,9 +14,6 @@ public class TeamMemberBusiness : EditableObjectBase [Inject] public IUserRepository UserRepository { get; set; } - [Inject] - public UserPrincipal Identity { get; set; } - private Team Aggregate { get; set; } public static readonly PropertyInfo UserIdsProperty = RegisterProperty(p => p.UserIds); diff --git a/Source/Starfish.Service/Domain/Business/UserGeneralBusiness.cs b/Source/Starfish.Service/Domain/Business/UserGeneralBusiness.cs index f2e94e4..05ebee3 100644 --- a/Source/Starfish.Service/Domain/Business/UserGeneralBusiness.cs +++ b/Source/Starfish.Service/Domain/Business/UserGeneralBusiness.cs @@ -2,12 +2,13 @@ using Microsoft.EntityFrameworkCore; using Nerosoft.Euonia.Business; using Nerosoft.Euonia.Domain; +using Nerosoft.Starfish.Service; // ReSharper disable MemberCanBePrivate.Global namespace Nerosoft.Starfish.Domain; -internal class UserGeneralBusiness : EditableObject, IDomainService +internal class UserGeneralBusiness : EditableObjectBase, IDomainService { private readonly IServiceProvider _provider; diff --git a/Source/Starfish.Service/Seedwork/EditableObjectBase.cs b/Source/Starfish.Service/Seedwork/EditableObjectBase.cs index 485005a..d52bca5 100644 --- a/Source/Starfish.Service/Seedwork/EditableObjectBase.cs +++ b/Source/Starfish.Service/Seedwork/EditableObjectBase.cs @@ -18,5 +18,5 @@ public abstract class EditableObjectBase : EditableObject, IHasLazyService /// /// 当前授权用户信息 /// - protected virtual UserPrincipal User => LazyServiceProvider.GetRequiredService(); + protected virtual UserPrincipal Identity => LazyServiceProvider.GetRequiredService(); } \ No newline at end of file diff --git a/Source/Starfish.Service/Starfish.Service.csproj b/Source/Starfish.Service/Starfish.Service.csproj index 8fbd624..ccd0478 100644 --- a/Source/Starfish.Service/Starfish.Service.csproj +++ b/Source/Starfish.Service/Starfish.Service.csproj @@ -31,6 +31,7 @@ + diff --git a/Source/Starfish.Service/UseCases/Team/TeamDetailUseCase.cs b/Source/Starfish.Service/UseCases/Team/TeamDetailUseCase.cs new file mode 100644 index 0000000..4c1d6c2 --- /dev/null +++ b/Source/Starfish.Service/UseCases/Team/TeamDetailUseCase.cs @@ -0,0 +1,46 @@ +using Nerosoft.Euonia.Application; +using Nerosoft.Euonia.Claims; +using Nerosoft.Euonia.Mapping; +using Nerosoft.Euonia.Repository.EfCore; +using Nerosoft.Starfish.Domain; +using Nerosoft.Starfish.Repository; +using Nerosoft.Starfish.Transit; + +namespace Nerosoft.Starfish.UseCases; + +public interface ITeamDetailUseCase : IUseCase; + +public record TeamDetailInput(int Id) : IUseCaseInput; + +public record TeamDetailOutput(TeamDetailDto Result) : IUseCaseOutput; + +public class TeamDetailUseCase : ITeamDetailUseCase +{ + private readonly ITeamRepository _repository; + private readonly UserPrincipal _identity; + + public TeamDetailUseCase(ITeamRepository repository, UserPrincipal identity) + { + _repository = repository; + _identity = identity; + } + + public Task ExecuteAsync(TeamDetailInput input, CancellationToken cancellationToken = default) + { + var specification = TeamSpecification.IdEquals(input.Id); + if (!_identity.IsInRole("SA")) + { + specification &= TeamSpecification.HasMember(_identity.GetUserIdOfInt32()); + } + + var predicate = specification.Satisfy(); + return _repository.Include(t => t.Members) + .GetAsync(predicate, cancellationToken) + .ContinueWith(task => + { + task.WaitAndUnwrapException(cancellationToken); + var result = TypeAdapter.ProjectedAs(task.Result); + return new TeamDetailOutput(result); + }, cancellationToken); + } +} \ No newline at end of file diff --git a/Source/Starfish.Service/UseCases/Team/TeamMemberAppendUseCase.cs b/Source/Starfish.Service/UseCases/Team/TeamMemberAppendUseCase.cs index 55dda6c..d03e619 100644 --- a/Source/Starfish.Service/UseCases/Team/TeamMemberAppendUseCase.cs +++ b/Source/Starfish.Service/UseCases/Team/TeamMemberAppendUseCase.cs @@ -17,7 +17,7 @@ public TeamMemberAppendUseCase(IBus bus) _bus = bus; } - public Task ExecuteAsync(TeamMemberAppendInput input, CancellationToken cancellationToken = new CancellationToken()) + public Task ExecuteAsync(TeamMemberAppendInput input, CancellationToken cancellationToken = default) { var command = new TeamMemberEditCommand(input.Id, input.UserIds, "+"); return _bus.SendAsync(command, cancellationToken); diff --git a/Source/Starfish.Service/UseCases/Team/TeamMemberQueryUseCase.cs b/Source/Starfish.Service/UseCases/Team/TeamMemberQueryUseCase.cs index 33e5c93..6cd7d1c 100644 --- a/Source/Starfish.Service/UseCases/Team/TeamMemberQueryUseCase.cs +++ b/Source/Starfish.Service/UseCases/Team/TeamMemberQueryUseCase.cs @@ -19,7 +19,7 @@ public TeamMemberQueryUseCase(ITeamRepository repository) _repository = repository; } - public Task ExecuteAsync(TeamMemberQueryInput input, CancellationToken cancellationToken = new CancellationToken()) + public Task ExecuteAsync(TeamMemberQueryInput input, CancellationToken cancellationToken = default) { return _repository.GetMembersAsync(input.Id, cancellationToken) .ContinueWith(task => diff --git a/Source/Starfish.Service/UseCases/Team/TeamMemberQuitUseCase.cs b/Source/Starfish.Service/UseCases/Team/TeamMemberQuitUseCase.cs index d34737e..80a55d2 100644 --- a/Source/Starfish.Service/UseCases/Team/TeamMemberQuitUseCase.cs +++ b/Source/Starfish.Service/UseCases/Team/TeamMemberQuitUseCase.cs @@ -17,7 +17,7 @@ public TeamMemberQuitUseCase(IBus bus) _bus = bus; } - public Task ExecuteAsync(TeamMemberQuitInput input, CancellationToken cancellationToken = new CancellationToken()) + public Task ExecuteAsync(TeamMemberQuitInput input, CancellationToken cancellationToken = default) { var command = new TeamMemberEditCommand(input.TeamId, [input.UserId], "-"); return _bus.SendAsync(command, cancellationToken); diff --git a/Source/Starfish.Service/UseCases/Team/TeamMemberRemoveUseCase.cs b/Source/Starfish.Service/UseCases/Team/TeamMemberRemoveUseCase.cs index ca9ba53..bf970a3 100644 --- a/Source/Starfish.Service/UseCases/Team/TeamMemberRemoveUseCase.cs +++ b/Source/Starfish.Service/UseCases/Team/TeamMemberRemoveUseCase.cs @@ -17,7 +17,7 @@ public TeamMemberRemoveUseCase(IBus bus) _bus = bus; } - public Task ExecuteAsync(TeamMemberRemoveInput input, CancellationToken cancellationToken = new CancellationToken()) + public Task ExecuteAsync(TeamMemberRemoveInput input, CancellationToken cancellationToken = default) { var command = new TeamMemberEditCommand(input.Id, input.UserIds, "-"); return _bus.SendAsync(command, cancellationToken); diff --git a/Source/Starfish.Webapi/Controllers/TeamController.cs b/Source/Starfish.Webapi/Controllers/TeamController.cs index f602e32..70f03a5 100644 --- a/Source/Starfish.Webapi/Controllers/TeamController.cs +++ b/Source/Starfish.Webapi/Controllers/TeamController.cs @@ -52,6 +52,14 @@ public async Task CountAsync([FromQuery] TeamCriteria criteria) return Ok(result); } + [HttpGet("{id:int}")] + [Produces(typeof(TeamDetailDto))] + public async Task GetAsync(int id) + { + var result = await _service.GetAsync(id, HttpContext.RequestAborted); + return Ok(result); + } + /// /// 创建团队 /// diff --git a/Source/Starfish.Webapi/HostServiceModule.cs b/Source/Starfish.Webapi/HostServiceModule.cs index bb8f467..26603b5 100644 --- a/Source/Starfish.Webapi/HostServiceModule.cs +++ b/Source/Starfish.Webapi/HostServiceModule.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.Hosting.Server.Features; +using Microsoft.FeatureManagement; using Nerosoft.Euonia.Hosting; using Nerosoft.Euonia.Modularity; using Nerosoft.Starfish.Application; @@ -26,6 +27,7 @@ public override void ConfigureServices(ServiceConfigurationContext context) context.Services.AddControllers(); context.Services.AddAuthentication(Configuration); context.Services.AddSwagger(); + context.Services.AddFeatureManagement(); } /// diff --git a/Source/Starfish.Webapi/appsettings.json b/Source/Starfish.Webapi/appsettings.json index 49b6c04..71e5de8 100644 --- a/Source/Starfish.Webapi/appsettings.json +++ b/Source/Starfish.Webapi/appsettings.json @@ -27,6 +27,9 @@ "TopicName": "nerosoft.starfish.topic" } }, + "FeatureManagement": { + "UserRegistration": true + }, "Logging": { "LogLevel": { "Default": "Information", From c24347ca88bb6157b09c27332978ac84ed13766a Mon Sep 17 00:00:00 2001 From: damon Date: Mon, 8 Jan 2024 00:15:46 +0800 Subject: [PATCH 16/22] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=9B=A2=E9=98=9F?= =?UTF-8?q?=E7=AE=A1=E7=90=86Api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Starfish.Webapp/Rest/Defines/ITeamApi.cs | 34 +++++++++++++++++++ .../Rest/Handlers/AuthorizationHandler.cs | 2 +- .../Rest/ServiceCollectionExtensions.cs | 1 + .../Starfish.Webapp/wwwroot/appsettings.json | 2 +- Source/Starfish.Webapp/wwwroot/index.html | 1 + Source/Starfish.Webapp/wwwroot/logo.svg | 12 +++++++ 6 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 Source/Starfish.Webapp/Rest/Defines/ITeamApi.cs create mode 100644 Source/Starfish.Webapp/wwwroot/logo.svg diff --git a/Source/Starfish.Webapp/Rest/Defines/ITeamApi.cs b/Source/Starfish.Webapp/Rest/Defines/ITeamApi.cs new file mode 100644 index 0000000..9e0460a --- /dev/null +++ b/Source/Starfish.Webapp/Rest/Defines/ITeamApi.cs @@ -0,0 +1,34 @@ +using Nerosoft.Starfish.Transit; +using Refit; + +namespace Nerosoft.Starfish.Webapp.Rest; + +internal interface ITeamApi +{ + [Get("/api/team")] + Task>> QueryAsync([Query] TeamCriteria criteria, int page, int size, CancellationToken cancellationToken = default); + + [Get("/api/team/count")] + Task> CountAsync([Query] TeamCriteria criteria, CancellationToken cancellationToken = default); + + [Get("/api/team/{id}")] + Task> GetAsync(int id, CancellationToken cancellationToken = default); + + [Post("/api/team")] + Task CreateAsync([Body] TeamEditDto data, CancellationToken cancellationToken = default); + + [Put("/api/team/{id}")] + Task UpdateAsync(int id, [Body] TeamEditDto data, CancellationToken cancellationToken = default); + + [Get("/api/team/{id}/member")] + Task>> GetMembersAsync(int id, CancellationToken cancellationToken = default); + + [Post("/api/team/{id}/member")] + Task AppendMemberAsync(int id, [Body] List userIds, CancellationToken cancellationToken = default); + + [Delete("/api/team/{id}/member")] + Task RemoveMemberAsync(int id, [Body] List userIds, CancellationToken cancellationToken = default); + + [Delete("/api/team/{id}/quit")] + Task QuitAsync(int id, CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/Source/Starfish.Webapp/Rest/Handlers/AuthorizationHandler.cs b/Source/Starfish.Webapp/Rest/Handlers/AuthorizationHandler.cs index b88c8ff..0b0670d 100644 --- a/Source/Starfish.Webapp/Rest/Handlers/AuthorizationHandler.cs +++ b/Source/Starfish.Webapp/Rest/Handlers/AuthorizationHandler.cs @@ -15,7 +15,7 @@ public AuthorizationHandler(ILocalStorageService storageService) protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { var token = await _storageService.GetItemAsStringAsync(Constants.LocalStorage.AccessToken, cancellationToken); - if (!string.IsNullOrWhiteSpace(token)) + if (!string.IsNullOrWhiteSpace(token) && TokenHelper.Validate(token)) { request.Headers!.Authorization = new AuthenticationHeaderValue("Bearer", token); } diff --git a/Source/Starfish.Webapp/Rest/ServiceCollectionExtensions.cs b/Source/Starfish.Webapp/Rest/ServiceCollectionExtensions.cs index b3824d3..0e64eca 100644 --- a/Source/Starfish.Webapp/Rest/ServiceCollectionExtensions.cs +++ b/Source/Starfish.Webapp/Rest/ServiceCollectionExtensions.cs @@ -38,6 +38,7 @@ public static IServiceCollection AddHttpClientApi(this IServiceCollection servic services.AddTransient(provider => provider.GetRestService(HTTP_CLIENT_NAME)) .AddTransient(provider => provider.GetRestService(HTTP_CLIENT_NAME)) .AddTransient(provider => provider.GetRestService(HTTP_CLIENT_NAME)) + .AddTransient(provider => provider.GetRestService(HTTP_CLIENT_NAME)) .AddTransient(provider => provider.GetRestService(HTTP_CLIENT_NAME)) .AddTransient(provider => provider.GetRestService(HTTP_CLIENT_NAME)); diff --git a/Source/Starfish.Webapp/wwwroot/appsettings.json b/Source/Starfish.Webapp/wwwroot/appsettings.json index fdf43b8..cb0c733 100644 --- a/Source/Starfish.Webapp/wwwroot/appsettings.json +++ b/Source/Starfish.Webapp/wwwroot/appsettings.json @@ -1,6 +1,6 @@ { "Api": { - "BaseUrl": "http://wx.zhaorong.pro", + "BaseUrl": "http://localhost:5229", "Timeout": 10000 } } \ No newline at end of file diff --git a/Source/Starfish.Webapp/wwwroot/index.html b/Source/Starfish.Webapp/wwwroot/index.html index f698d7e..0391bda 100644 --- a/Source/Starfish.Webapp/wwwroot/index.html +++ b/Source/Starfish.Webapp/wwwroot/index.html @@ -6,6 +6,7 @@ Starfish + diff --git a/Source/Starfish.Webapp/wwwroot/logo.svg b/Source/Starfish.Webapp/wwwroot/logo.svg new file mode 100644 index 0000000..f9c765d --- /dev/null +++ b/Source/Starfish.Webapp/wwwroot/logo.svg @@ -0,0 +1,12 @@ + + + + + + + From 1c6fd9d58471ce58c92cf77e32b89d4af761d852 Mon Sep 17 00:00:00 2001 From: damon Date: Mon, 8 Jan 2024 23:20:17 +0800 Subject: [PATCH 17/22] =?UTF-8?q?=E5=9B=A2=E9=98=9F=E8=81=9A=E5=90=88?= =?UTF-8?q?=E6=A0=B9=E5=A2=9E=E5=8A=A0=E5=88=9B=E5=BB=BA=E6=97=B6=E9=97=B4?= =?UTF-8?q?=E7=AD=89=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Domain/Aggregates/Team.cs | 14 ++++++++++++- Source/Starfish.Transit/Team/TeamDetailDto.cs | 20 +++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/Source/Starfish.Service/Domain/Aggregates/Team.cs b/Source/Starfish.Service/Domain/Aggregates/Team.cs index 4cc4e84..f64e79e 100644 --- a/Source/Starfish.Service/Domain/Aggregates/Team.cs +++ b/Source/Starfish.Service/Domain/Aggregates/Team.cs @@ -1,11 +1,15 @@ using Nerosoft.Euonia.Domain; +using Nerosoft.Starfish.Service; namespace Nerosoft.Starfish.Domain; /// /// 团队聚合根对象 /// -public sealed class Team : Aggregate +public sealed class Team : Aggregate, + IHasCreateTime, + IHasUpdateTime, + IAuditing { public string Alias { get; set; } @@ -17,6 +21,14 @@ public sealed class Team : Aggregate public int MemberCount { get; set; } + public DateTime CreateTime { get; set; } + + public DateTime UpdateTime { get; set; } + + public string CreatedBy { get; set; } + + public string UpdatedBy { get; set; } + public HashSet Members { get; set; } internal static Team Create(string name, string description, int ownerId) diff --git a/Source/Starfish.Transit/Team/TeamDetailDto.cs b/Source/Starfish.Transit/Team/TeamDetailDto.cs index 293afc0..ecc2ea1 100644 --- a/Source/Starfish.Transit/Team/TeamDetailDto.cs +++ b/Source/Starfish.Transit/Team/TeamDetailDto.cs @@ -34,4 +34,24 @@ public class TeamDetailDto /// 成员数量 /// public int MemberCount { get; set; } + + /// + /// 创建时间 + /// + public DateTime CreateTime { get; set; } + + /// + /// 更新时间 + /// + public DateTime UpdateTime { get; set; } + + /// + /// 创建人 + /// + public string CreatedBy { get; set; } + + /// + /// 更新人 + /// + public string UpdatedBy { get; set; } } \ No newline at end of file From 0115a13173055ea4e6e3f6cd8f895b9698cc6ed7 Mon Sep 17 00:00:00 2001 From: damon Date: Mon, 8 Jan 2024 23:22:02 +0800 Subject: [PATCH 18/22] =?UTF-8?q?=E5=A2=9E=E5=8A=A0ObservableRangeCollecti?= =?UTF-8?q?on?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Source/Starfish.Common/AsyncHelper.cs | 125 ++++++++++++-- .../ObservableRangeCollection.cs | 160 ++++++++++++++++++ 2 files changed, 267 insertions(+), 18 deletions(-) create mode 100644 Source/Starfish.Common/ObservableRangeCollection.cs diff --git a/Source/Starfish.Common/AsyncHelper.cs b/Source/Starfish.Common/AsyncHelper.cs index 98c91bf..cc4acec 100644 --- a/Source/Starfish.Common/AsyncHelper.cs +++ b/Source/Starfish.Common/AsyncHelper.cs @@ -1,34 +1,123 @@ -using System.Globalization; +namespace Nerosoft.Starfish.Common; -namespace Nerosoft.Starfish.Common; /// /// Provides some helper methods to work with async methods. /// public static class AsyncHelper { - private static readonly TaskFactory _factory = new(CancellationToken.None, TaskCreationOptions.None, TaskContinuationOptions.None, TaskScheduler.Default); - public static TResult RunSync(Func> func) { - var cultureUi = CultureInfo.CurrentUICulture; - var culture = CultureInfo.CurrentCulture; - return _factory.StartNew(() => + var oldContext = SynchronizationContext.Current; + var context = new ExclusiveSynchronizationContext(); + SynchronizationContext.SetSynchronizationContext(context); + TResult result = default; + context.Post(async _ => { - Thread.CurrentThread.CurrentCulture = culture; - Thread.CurrentThread.CurrentUICulture = cultureUi; - return func(); - }).Unwrap().GetAwaiter().GetResult(); + try + { + result = await func(); + } + catch (Exception exception) + { + context.Exception = exception; + throw; + } + finally + { + context.EndMessageLoop(); + } + }, null); + context.BeginMessageLoop(); + SynchronizationContext.SetSynchronizationContext(oldContext); + return result; } public static void RunSync(Func func) { - var cultureUi = CultureInfo.CurrentUICulture; - var culture = CultureInfo.CurrentCulture; - _factory.StartNew(() => + var oldContext = SynchronizationContext.Current; + var context = new ExclusiveSynchronizationContext(); + SynchronizationContext.SetSynchronizationContext(context); + context.Post(async _ => + { + try + { + await func(); + } + catch (Exception exception) + { + context.Exception = exception; + throw; + } + finally + { + context.EndMessageLoop(); + } + }, null); + context.BeginMessageLoop(); + + SynchronizationContext.SetSynchronizationContext(oldContext); + } + + private class ExclusiveSynchronizationContext : SynchronizationContext + { + private bool done; + public Exception Exception { get; set; } + readonly AutoResetEvent workItemsWaiting = new(false); + readonly Queue> items = new(); + + public override void Send(SendOrPostCallback d, object state) + { + throw new NotSupportedException("We cannot send to our same thread"); + } + + public override void Post(SendOrPostCallback callback, object state) + { + lock (items) + { + items.Enqueue(Tuple.Create(callback, state)); + } + workItemsWaiting.Set(); + } + + public void EndMessageLoop() + { + Post(_ => done = true, null); + } + + public void BeginMessageLoop() + { + if (done) + { + return; + } + while (!done) + { + Tuple task = null; + lock (items) + { + if (items.Count > 0) + { + task = items.Dequeue(); + } + } + if (task != null) + { + task.Item1(task.Item2); + if (Exception != null) // the method threw an exception + { + throw new AggregateException("AsyncHelper.Run method threw an exception.", Exception); + } + } + else + { + workItemsWaiting.WaitOne(); + } + } + } + + public override SynchronizationContext CreateCopy() { - Thread.CurrentThread.CurrentCulture = culture; - Thread.CurrentThread.CurrentUICulture = cultureUi; - return func(); - }).Unwrap().GetAwaiter().GetResult(); + return this; + } } } diff --git a/Source/Starfish.Common/ObservableRangeCollection.cs b/Source/Starfish.Common/ObservableRangeCollection.cs new file mode 100644 index 0000000..fcba41a --- /dev/null +++ b/Source/Starfish.Common/ObservableRangeCollection.cs @@ -0,0 +1,160 @@ +using System.Collections.Specialized; +using System.ComponentModel; + +namespace System.Collections.ObjectModel; + +/// +/// Represents a dynamic data collection that provides notifications when items get added, removed, or when the whole list is refreshed. +/// +/// +public class ObservableRangeCollection : ObservableCollection +{ + /// + /// Initializes a new instance of the System.Collections.ObjectModel.ObservableCollection(Of T) class. + /// + public ObservableRangeCollection() + { + } + + /// + /// Initializes a new instance of the System.Collections.ObjectModel.ObservableCollection(Of T) class that contains elements copied from the specified collection. + /// + /// collection: The collection from which the elements are copied. + /// The collection parameter cannot be null. + public ObservableRangeCollection(IEnumerable collection) + : base(collection) + { + } + + /// + /// Adds the elements of the specified collection to the end of the ObservableCollection(Of T). + /// + public void AddRange(IEnumerable collection, NotifyCollectionChangedAction notificationMode = NotifyCollectionChangedAction.Add) + { + if (notificationMode != NotifyCollectionChangedAction.Add && notificationMode != NotifyCollectionChangedAction.Reset) + throw new ArgumentException("Mode must be either Add or Reset for AddRange.", nameof(notificationMode)); + if (collection == null) + throw new ArgumentNullException(nameof(collection)); + + CheckReentrancy(); + + var startIndex = Count; + + var itemsAdded = AddArrangeCore(collection); + + if (!itemsAdded) + return; + + if (notificationMode == NotifyCollectionChangedAction.Reset) + { + RaiseChangeNotificationEvents(action: NotifyCollectionChangedAction.Reset); + return; + } + + var changedItems = collection is List + ? (List)collection + : new List(collection); + + RaiseChangeNotificationEvents( + action: NotifyCollectionChangedAction.Add, + changedItems: changedItems, + startingIndex: startIndex); + } + + /// + /// Removes the first occurence of each item in the specified collection from ObservableCollection(Of T). NOTE: with notificationMode = Remove, removed items starting index is not set because items are not guaranteed to be consecutive. + /// + public void RemoveRange(IEnumerable collection, NotifyCollectionChangedAction notificationMode = NotifyCollectionChangedAction.Reset) + { + if (notificationMode != NotifyCollectionChangedAction.Remove && notificationMode != NotifyCollectionChangedAction.Reset) + throw new ArgumentException("Mode must be either Remove or Reset for RemoveRange.", nameof(notificationMode)); + if (collection == null) + throw new ArgumentNullException(nameof(collection)); + + CheckReentrancy(); + + if (notificationMode == NotifyCollectionChangedAction.Reset) + { + var raiseEvents = false; + foreach (var item in collection) + { + Items.Remove(item); + raiseEvents = true; + } + + if (raiseEvents) + RaiseChangeNotificationEvents(action: NotifyCollectionChangedAction.Reset); + + return; + } + + var changedItems = new List(collection); + for (var i = 0; i < changedItems.Count; i++) + { + if (!Items.Remove(changedItems[i])) + { + changedItems.RemoveAt(i); // Can't use a foreach because changedItems is intended to be (carefully) modified + i--; + } + } + + if (changedItems.Count == 0) + return; + + RaiseChangeNotificationEvents( + action: NotifyCollectionChangedAction.Remove, + changedItems: changedItems); + } + + /// + /// Clears the current collection and replaces it with the specified item. + /// + public void Replace(T item) => ReplaceRange(new[] { item }); + + /// + /// Clears the current collection and replaces it with the specified collection. + /// + public void ReplaceRange(IEnumerable collection) + { + if (collection == null) + throw new ArgumentNullException(nameof(collection)); + + CheckReentrancy(); + + var previouslyEmpty = Items.Count == 0; + + Items.Clear(); + + AddArrangeCore(collection); + + var currentlyEmpty = Items.Count == 0; + + if (previouslyEmpty && currentlyEmpty) + return; + + RaiseChangeNotificationEvents(action: NotifyCollectionChangedAction.Reset); + } + + private bool AddArrangeCore(IEnumerable collection) + { + var itemAdded = false; + foreach (var item in collection) + { + Items.Add(item); + itemAdded = true; + } + + return itemAdded; + } + + private void RaiseChangeNotificationEvents(NotifyCollectionChangedAction action, IList changedItems = null, int startingIndex = -1) + { + OnPropertyChanged(new PropertyChangedEventArgs(nameof(Count))); + OnPropertyChanged(new PropertyChangedEventArgs("Item[]")); + + if (changedItems == null) + OnCollectionChanged(new NotifyCollectionChangedEventArgs(action)); + else + OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, changedItems: changedItems, startingIndex: startingIndex)); + } +} \ No newline at end of file From bcd2926262c4a9211da740ac64b9db43bf1f7a81 Mon Sep 17 00:00:00 2001 From: damon Date: Mon, 8 Jan 2024 23:22:41 +0800 Subject: [PATCH 19/22] =?UTF-8?q?=E7=AE=A1=E7=90=86=E7=95=8C=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Starfish.Webapp/Layout/MainLayout.razor | 41 +++-- Source/Starfish.Webapp/Pages/Apps/Index.razor | 14 ++ Source/Starfish.Webapp/Pages/Home.razor | 2 + .../Starfish.Webapp/Pages/Setting/Index.razor | 5 + .../Pages/Team/AppendMember.razor | 79 +++++++++ .../Starfish.Webapp/Pages/Team/Detail.razor | 128 ++++++++++++++ Source/Starfish.Webapp/Pages/Team/Edit.razor | 130 ++++++++++++++ Source/Starfish.Webapp/Pages/Team/Index.razor | 122 ++++++++++++++ .../Starfish.Webapp/Pages/User/Detail.razor | 47 ++++++ Source/Starfish.Webapp/Pages/User/Edit.razor | 158 ++++++++++++++++++ Source/Starfish.Webapp/Pages/User/Index.razor | 111 ++++++++++++ Source/Starfish.Webapp/Pages/User/Login.razor | 117 +++++++++++++ .../Starfish.Webapp/Pages/User/Profile.razor | 5 + .../Starfish.Webapp/Pages/User/Register.razor | 95 +++++++++++ Source/Starfish.Webapp/Program.cs | 29 ++-- .../Starfish.Webapp/Properties/Resources.resx | 48 ++++++ .../Starfish.Webapp/Rest/Defines/IAppsApi.cs | 10 ++ .../Starfish.Webapp/Rest/Defines/IAppsApis.cs | 5 - .../Rest/ServiceCollectionExtensions.cs | 2 +- .../IdentityAuthenticationStateProvider.cs | 5 +- .../Shared/FluentDataGridPaginator.razor | 15 ++ Source/Starfish.Webapp/_Imports.razor | 11 +- Source/Starfish.Webapp/wwwroot/css/app.css | 4 + 23 files changed, 1143 insertions(+), 40 deletions(-) create mode 100644 Source/Starfish.Webapp/Pages/Apps/Index.razor create mode 100644 Source/Starfish.Webapp/Pages/Setting/Index.razor create mode 100644 Source/Starfish.Webapp/Pages/Team/AppendMember.razor create mode 100644 Source/Starfish.Webapp/Pages/Team/Detail.razor create mode 100644 Source/Starfish.Webapp/Pages/Team/Edit.razor create mode 100644 Source/Starfish.Webapp/Pages/Team/Index.razor create mode 100644 Source/Starfish.Webapp/Pages/User/Detail.razor create mode 100644 Source/Starfish.Webapp/Pages/User/Edit.razor create mode 100644 Source/Starfish.Webapp/Pages/User/Index.razor create mode 100644 Source/Starfish.Webapp/Pages/User/Login.razor create mode 100644 Source/Starfish.Webapp/Pages/User/Profile.razor create mode 100644 Source/Starfish.Webapp/Pages/User/Register.razor create mode 100644 Source/Starfish.Webapp/Rest/Defines/IAppsApi.cs delete mode 100644 Source/Starfish.Webapp/Rest/Defines/IAppsApis.cs create mode 100644 Source/Starfish.Webapp/Shared/FluentDataGridPaginator.razor diff --git a/Source/Starfish.Webapp/Layout/MainLayout.razor b/Source/Starfish.Webapp/Layout/MainLayout.razor index 820f15f..9a147eb 100644 --- a/Source/Starfish.Webapp/Layout/MainLayout.razor +++ b/Source/Starfish.Webapp/Layout/MainLayout.razor @@ -2,22 +2,21 @@ @attribute [Authorize] - - + Starfish - + @@ -26,7 +25,10 @@ @(Resources.IDS_MENU_TEXT_HOME) @(Resources.IDS_MENU_TEXT_APPS) - @(Resources.IDS_MENU_TEXT_USER) + @if (Identity?.IsInRole("SA") == true) + { + @(Resources.IDS_MENU_TEXT_USER) + } @(Resources.IDS_MENU_TEXT_TEAM) @(Resources.IDS_MENU_TEXT_LOGS) @@ -49,21 +51,28 @@ Documentation and demos
- About Blazor + + Nerosoft © 2024. All rights reserved. +
+ + +
@code { - private bool ShowPersonaMenu { get; set; } - protected override Task OnInitializedAsync() - { - return base.OnInitializedAsync(); - } + [CascadingParameter] + private Task AuthenticationState { get; set; } + + private UserPrincipal Identity { get; set; } - private void OnPersonaClicked() + protected override async Task OnInitializedAsync() { - ShowPersonaMenu = true; + var state = await AuthenticationState; + Identity = new UserPrincipal(state.User); + StateHasChanged(); } + } \ No newline at end of file diff --git a/Source/Starfish.Webapp/Pages/Apps/Index.razor b/Source/Starfish.Webapp/Pages/Apps/Index.razor new file mode 100644 index 0000000..97151b6 --- /dev/null +++ b/Source/Starfish.Webapp/Pages/Apps/Index.razor @@ -0,0 +1,14 @@ +@page "/apps" + + + + Home + + + Apps + + + +@code { + +} diff --git a/Source/Starfish.Webapp/Pages/Home.razor b/Source/Starfish.Webapp/Pages/Home.razor index 9001e0b..8ce0ae7 100644 --- a/Source/Starfish.Webapp/Pages/Home.razor +++ b/Source/Starfish.Webapp/Pages/Home.razor @@ -1,5 +1,7 @@ @page "/" +@attribute [Authorize] + Home

Hello, world!

diff --git a/Source/Starfish.Webapp/Pages/Setting/Index.razor b/Source/Starfish.Webapp/Pages/Setting/Index.razor new file mode 100644 index 0000000..a62cdbb --- /dev/null +++ b/Source/Starfish.Webapp/Pages/Setting/Index.razor @@ -0,0 +1,5 @@ +

Setting

+ +@code { + +} diff --git a/Source/Starfish.Webapp/Pages/Team/AppendMember.razor b/Source/Starfish.Webapp/Pages/Team/AppendMember.razor new file mode 100644 index 0000000..e1fb184 --- /dev/null +++ b/Source/Starfish.Webapp/Pages/Team/AppendMember.razor @@ -0,0 +1,79 @@ +@implements IDialogContentComponent + +@inject ITeamApi TeamApi +@inject IUserApi UserApi +@inject IToastService ToastService + + + + + @(Resources.IDS_COMMON_SAVE) + @(Resources.IDS_COMMON_CANCEL) + + +@code { + + [Parameter] + public int Content { get; set; } + + [CascadingParameter] + public FluentDialog Dialog { get; set; } = default!; + + private bool Loading { get; set; } + + private UserCriteria Criteria { get; } = new(); + + private IEnumerable _selectedItems = Array.Empty(); + + private async Task OnSearchAsync(OptionsSearchEventArgs e) + { + Criteria.Keyword = e.Text; + e.Items = await UserApi.SearchAsync(Criteria, 1, 20) + .ContinueWith(task => + { + task.WaitAndUnwrapException(); + return task.Result.EnsureSuccess(); + }); + } + + private async Task SaveAsync() + { + try + { + Loading = true; + + var userIds = _selectedItems.Select(t => t.Id).ToList(); + + await TeamApi.AppendMemberAsync(Content, userIds) + .ContinueWith(task => + { + task.WaitAndUnwrapException(); + task.Result.EnsureSuccess(); + }); + + await Dialog.CloseAsync(Content); + } + catch (Exception exception) + { + var message = exception.GetPromptMessage(); + ToastService.ShowError(message); + } + finally + { + Loading = false; + } + } + + private async Task CancelAsync() + { + await Dialog.CancelAsync(); + } + +} \ No newline at end of file diff --git a/Source/Starfish.Webapp/Pages/Team/Detail.razor b/Source/Starfish.Webapp/Pages/Team/Detail.razor new file mode 100644 index 0000000..7888371 --- /dev/null +++ b/Source/Starfish.Webapp/Pages/Team/Detail.razor @@ -0,0 +1,128 @@ +@page "/team/{id:int}" + +@inject ITeamApi TeamApi +@inject IAppsApi AppsApi +@inject IDialogService DialogService + + + Home + Team + Detail + + + + + + + Alias: @Data.Alias + Created at: @Data.CreateTime.Date + Updated at: @Data.CreateTime.Date + + @Data.Description + + Operations + + 编辑信息 + 添加成员 + + + + + + + @foreach (var member in Members) + { + + } + + + + + + + + + +@code { + + [Parameter] + public int Id { get; set; } + + private TeamDetailDto Data { get; } = new(); + + private ObservableRangeCollection Members { get; } = new(); + + protected override async Task OnInitializedAsync() + { + await LoadAsync(); + } + + private async Task LoadAsync() + { + var tasks = new List + { + LoadDetailAsync(), + LoadMembersAsync() + }; + + await InvokeAsync(() => Task.WhenAll(tasks)); + StateHasChanged(); + } + + private Task LoadDetailAsync() + { + return TeamApi.GetAsync(Id) + .ContinueWith(task => + { + var data = task.Result.EnsureSuccess(); + Data.Name = data.Name; + Data.Alias = data.Alias; + Data.Description = data.Description; + Data.CreateTime = data.CreateTime; + Data.UpdateTime = data.UpdateTime; + Data.CreatedBy = data.CreatedBy; + Data.UpdatedBy = data.UpdatedBy; + }); + } + + private Task LoadMembersAsync() + { + return TeamApi.GetMembersAsync(Id) + .ContinueWith(task => + { + var data = task.Result.EnsureSuccess(); + Members.ReplaceRange(data); + }); + } + + private async Task OnRemoveMemberClicked(int userId) + { + var confirmation = await DialogService.ShowConfirmationAsync("Are you sure to remove this member?", title: "Remove member"); + var result = await confirmation.Result; + if (!result.Cancelled) + { + await TeamApi.RemoveMemberAsync(Id, [userId]) + .ContinueWith(task => + { + task.WaitAndUnwrapException(); + task.Result.EnsureSuccess(); + }); + await LoadMembersAsync(); + StateHasChanged(); + } + } + + private async Task OnAppendMemberClicked() + { + var reference = await DialogService.ShowDialogAsync(Id, new DialogParameters { Title = "Add member", Modal = true }); + var result = await reference.Result; + if (!result.Cancelled) + { + await LoadMembersAsync(); + StateHasChanged(); + } + } + +} \ No newline at end of file diff --git a/Source/Starfish.Webapp/Pages/Team/Edit.razor b/Source/Starfish.Webapp/Pages/Team/Edit.razor new file mode 100644 index 0000000..9f213a3 --- /dev/null +++ b/Source/Starfish.Webapp/Pages/Team/Edit.razor @@ -0,0 +1,130 @@ +@implements IDialogContentComponent + +@inject ITeamApi Api +@inject IToastService ToastService + + + + + + @Dialog.Instance.Parameters.Title + + + + + + + + + + + + + + + @(Resources.IDS_COMMON_SAVE) + @(Resources.IDS_COMMON_CANCEL) + + +@code { + + [Parameter] + public int Content { get; set; } + + [CascadingParameter] + public FluentDialog Dialog { get; set; } = default!; + + private bool Loading { get; set; } + + private string Alias { get; set; } + + private string Name { get; set; } + + private string Description { get; set; } + + protected override async Task OnInitializedAsync() + { + await base.OnInitializedAsync(); + await LoadAsync(Content); + } + + private async Task SaveAsync() + { + try + { + Loading = true; + var request = new TeamEditDto() + { + Name = Name, + Alias = Alias, + Description = Description + }; + + if (Content > 0) + { + await Api.UpdateAsync(Content, request) + .ContinueWith(task => + { + task.WaitAndUnwrapException(); + task.Result.EnsureSuccess(); + }); + } + else + { + await Api.CreateAsync(request) + .ContinueWith(task => + { + task.WaitAndUnwrapException(); + task.Result.EnsureSuccess(); + }); + } + + await Dialog.CloseAsync(Content); + } + catch (Exception exception) + { + var message = exception.GetPromptMessage(); + ToastService.ShowError(message); + } + finally + { + Loading = false; + } + } + + private async Task CancelAsync() + { + await Dialog.CancelAsync(); + } + + private async Task LoadAsync(int id) + { + if (id <= 0) + { + return; + } + + await Api.GetAsync(id) + .ContinueWith(task => + { + task.WaitAndUnwrapException(); + var result = task.Result.EnsureSuccess(); + if (result == null) + { + return; + } + + Content = result.Id; + Alias = result.Alias; + Name = result.Name; + Description = result.Description; + }); + } + +} \ No newline at end of file diff --git a/Source/Starfish.Webapp/Pages/Team/Index.razor b/Source/Starfish.Webapp/Pages/Team/Index.razor new file mode 100644 index 0000000..344f639 --- /dev/null +++ b/Source/Starfish.Webapp/Pages/Team/Index.razor @@ -0,0 +1,122 @@ +@page "/team" + +@attribute [Authorize] + +@inject IDialogService DialogService +@inject NavigationManager Navigation +@inject ITeamApi Api + + + + Home + + + Team + + + + + + +
+ + @(Resources.IDS_COMMON_SEARCH) + + @(Resources.IDS_TEAM_INDEX_BUTTON_ADD) +
+
+ + + + + @(context.Name) + + + + + + + + + + + + + +
+
+ +@code { + + [CascadingParameter] + private Task AuthenticationState { get; set; } + + private UserPrincipal User { get; set; } + + private GridItemsProvider _provider; + + private PaginationState Pagination { get; } = new() { ItemsPerPage = 20 }; + + private TeamCriteria Criteria { get; } = new(); + + private int Total { get; set; } + + private const int SIZE = 20; + + protected override async Task OnInitializedAsync() + { + var user = await AuthenticationState; + + User = new UserPrincipal(user.User); + + _provider = async request => + { + List items = null; + var tasks = new List + { + Api.QueryAsync(Criteria, request.StartIndex + 1, SIZE, request.CancellationToken) + .ContinueWith(task => + { + task.WaitAndUnwrapException(request.CancellationToken); + items = task.Result.EnsureSuccess(); + }, request.CancellationToken) + }; + + if (request.StartIndex == 0) + { + tasks.Add( + Api.CountAsync(Criteria, request.CancellationToken) + .ContinueWith(task => + { + task.WaitAndUnwrapException(request.CancellationToken); + Total = task.Result.EnsureSuccess(); + }, request.CancellationToken) + ); + } + + await Task.WhenAll(tasks); + await Pagination.SetTotalItemCountAsync(Total); + return GridItemsProviderResult.From(items, Total); + }; + + await Task.CompletedTask; + } + + private async Task HandleDetailClicked(int id) + { + Navigation.NavigateTo($"/team/{id}"); + await Task.CompletedTask; + } + + private async Task HandleEditClicked(int id) + { + var title = id == 0 ? Resources.IDS_USER_DIALOG_TITLE_ADD : Resources.IDS_USER_DIALOG_TITLE_EDIT; + await DialogService.ShowDialogAsync(id, new DialogParameters { Title = title, Modal = true }); + } + + private async Task OnSearchClicked() + { + await Pagination.SetCurrentPageIndexAsync(0); + } + +} \ No newline at end of file diff --git a/Source/Starfish.Webapp/Pages/User/Detail.razor b/Source/Starfish.Webapp/Pages/User/Detail.razor new file mode 100644 index 0000000..88304d8 --- /dev/null +++ b/Source/Starfish.Webapp/Pages/User/Detail.razor @@ -0,0 +1,47 @@ +@page "/user/{Id:int}" + +@inject IUserApi Api + + + Home + Users + Detail + + + + + + + + @Data.UserName + @Data.CreateTime.Date + + Roles + + Blazor + Microsoft + Azure + + + Operations + + 编辑信息 + 修改密码 + 重置密码 + + + + + + + + + + +@code { + + [Parameter] + public int Id { get; set; } + + private UserDetailDto Data { get; } = new(); +} \ No newline at end of file diff --git a/Source/Starfish.Webapp/Pages/User/Edit.razor b/Source/Starfish.Webapp/Pages/User/Edit.razor new file mode 100644 index 0000000..de8ee84 --- /dev/null +++ b/Source/Starfish.Webapp/Pages/User/Edit.razor @@ -0,0 +1,158 @@ +@implements IDialogContentComponent + +@inject IUserApi Api +@inject IToastService ToastService + + + + + + @Dialog.Instance.Parameters.Title + + + + + + + + @if (Content == 0) + { + + + } + + + + + + + @(Resources.IDS_COMMON_SAVE) + @(Resources.IDS_COMMON_CANCEL) + + +@code { + + [Parameter] + public int Content { get; set; } + + [CascadingParameter] + public FluentDialog Dialog { get; set; } = default!; + + private bool Loading { get; set; } + + private string UserName { get; set; } + + private string Password { get; set; } + + private string Confirm { get; set; } + + private string NickName { get; set; } + + private string Email { get; set; } + + protected override async Task OnInitializedAsync() + { + await base.OnInitializedAsync(); + await LoadAsync(Content); + } + + private async Task SaveAsync() + { + try + { + Loading = true; + if (Content > 0) + { + + var request = new UserUpdateDto + { + NickName = NickName, + Email = Email + }; + + await Api.UpdateAsync(Content, request) + .ContinueWith(task => + { + task.WaitAndUnwrapException(); + task.Result.EnsureSuccess(); + }); + } + else + { + if (!string.Equals(Password, Confirm)) + { + throw new Exception("Password not match."); + } + + var request = new UserCreateDto + { + UserName = UserName, + Password = Password, + NickName = NickName, + Email = Email + }; + + await Api.CreateAsync(request) + .ContinueWith(task => + { + task.WaitAndUnwrapException(); + task.Result.EnsureSuccess(); + }); + } + await Dialog.CloseAsync(Content); + } + catch (Exception exception) + { + var message = exception.GetPromptMessage(); + ToastService.ShowError(message); + } + finally + { + Loading = false; + } + } + + private async Task CancelAsync() + { + await Dialog.CancelAsync(); + } + + private async Task LoadAsync(int id) + { + if (id <= 0) + { + return; + } + + await Api.GetAsync(id) + .ContinueWith(task => + { + task.WaitAndUnwrapException(); + var result = task.Result.EnsureSuccess(); + if (result == null) + { + return; + } + + Content = result.Id; + UserName = result.UserName; + NickName = result.NickName; + Email = result.Email; + }); + } + +} \ No newline at end of file diff --git a/Source/Starfish.Webapp/Pages/User/Index.razor b/Source/Starfish.Webapp/Pages/User/Index.razor new file mode 100644 index 0000000..eeda18c --- /dev/null +++ b/Source/Starfish.Webapp/Pages/User/Index.razor @@ -0,0 +1,111 @@ +@page "/user" + +@inject IDialogService DialogService +@inject NavigationManager Navigation +@inject IUserApi Api + + + + Home + + + User + + + + + + +
+ + @(Resources.IDS_COMMON_SEARCH) + + @(Resources.IDS_USER_BUTTON_TEXT_ADD) +
+
+ + + + + @(context.UserName) + + + + + + + + + + + + + + +
+
+ +@code { + private GridItemsProvider _provider; + + private PaginationState Pagination { get; } = new PaginationState { ItemsPerPage = SIZE }; + + private UserCriteria Criteria { get; } = new(); + + private int Total { get; set; } + + private const int SIZE = 20; + + protected override async Task OnInitializedAsync() + { + _provider = async request => + { + List items = null; + var tasks = new List + { + Api.SearchAsync(Criteria, request.StartIndex + 1, SIZE, request.CancellationToken) + .ContinueWith(task => + { + task.WaitAndUnwrapException(request.CancellationToken); + items = task.Result.EnsureSuccess(); + }, request.CancellationToken) + }; + + if (request.StartIndex == 0) + { + tasks.Add( + Api.CountAsync(Criteria, request.CancellationToken) + .ContinueWith(task => + { + task.WaitAndUnwrapException(request.CancellationToken); + Total = task.Result.EnsureSuccess(); + }, request.CancellationToken) + ); + } + + await Task.WhenAll(tasks); + await Pagination.SetTotalItemCountAsync(Total); + return GridItemsProviderResult.From(items, Total); + }; + + await Task.CompletedTask; + } + + private async Task OnDetailClicked(int id) + { + Navigation.NavigateTo($"/user/{id}"); + await Task.CompletedTask; + } + + private async Task OnEditClicked(int id) + { + var title = id == 0 ? Resources.IDS_USER_DIALOG_TITLE_ADD : Resources.IDS_USER_DIALOG_TITLE_EDIT; + await DialogService.ShowDialogAsync(id, new DialogParameters { Title = title, Modal = true }); + } + + private async Task OnSearchClicked() + { + await Pagination.SetCurrentPageIndexAsync(0); + } + +} \ No newline at end of file diff --git a/Source/Starfish.Webapp/Pages/User/Login.razor b/Source/Starfish.Webapp/Pages/User/Login.razor new file mode 100644 index 0000000..aa723fe --- /dev/null +++ b/Source/Starfish.Webapp/Pages/User/Login.razor @@ -0,0 +1,117 @@ +@page "/login" + +@layout EmptyLayout + +@inject NavigationManager Navigation +@inject IdentityAuthenticationStateProvider Authentication +@inject IJSRuntime Script +@inject IToastService ToastService +@inject IIdentityApi Api + +@(Resources.IDS_LOGIN_PAGE_TITLE) + +
+ +
+
+ + Starfish +
+
配置管理和服务发现
+
+ + + + + + + + + + + + + + + + + +
+ @(Resources.IDS_LOGIN_BUTTON_TEXT_LOGIN) + + Forget password? +
+ + Register new user? +
+
+
+ +
+ +@code { + + [Parameter] + [SupplyParameterFromQuery(Name = "redirect")] + public string Redirect { get; set; } + + private bool Loading { get; set; } + + private string UserName { get; set; } + + private string Password { get; set; } + + private readonly SemaphoreSlim _semaphoreSlim = new(1, 1); + + private async Task OnClick(MouseEventArgs args) + { + if (_semaphoreSlim.CurrentCount == 0) + { + return; + } + + var cancellationToken = new CancellationTokenSource(TimeSpan.FromSeconds(10)); + + try + { + Loading = true; + await _semaphoreSlim.WaitAsync(cancellationToken.Token); + var response = await SubmitAsync(cancellationToken.Token); + await Authentication.SetAuthenticationStateAsync(response.AccessToken, response.RefreshToken); + Navigation.NavigateTo(Redirect ?? "/"); + } + catch (Exception exception) + { + var message = exception.GetPromptMessage(); + ToastService.ShowError(message); + await Script.InvokeVoidAsync("console.log", cancellationToken.Token, message); + } + finally + { + _semaphoreSlim.Release(); + Loading = false; + StateHasChanged(); + } + } + + private async Task SubmitAsync(CancellationToken cancellationToken = default) + { + var dto = new AuthRequestDto + { + UserName = UserName, + Password = Password + }; + return await Api.GrantTokenAsync(dto, cancellationToken).ContinueWith(task => + { + task.WaitAndUnwrapException(cancellationToken); + return task.Result.EnsureSuccess(); + }, cancellationToken); + } + + private async Task OnRegisterClicked() + { + Navigation.NavigateTo("/register"); + await Task.CompletedTask; + } + +} \ No newline at end of file diff --git a/Source/Starfish.Webapp/Pages/User/Profile.razor b/Source/Starfish.Webapp/Pages/User/Profile.razor new file mode 100644 index 0000000..c54d5d2 --- /dev/null +++ b/Source/Starfish.Webapp/Pages/User/Profile.razor @@ -0,0 +1,5 @@ +@page "/profile/{Id}" + +@code { + +} diff --git a/Source/Starfish.Webapp/Pages/User/Register.razor b/Source/Starfish.Webapp/Pages/User/Register.razor new file mode 100644 index 0000000..50426e4 --- /dev/null +++ b/Source/Starfish.Webapp/Pages/User/Register.razor @@ -0,0 +1,95 @@ +@page "/register" + +@layout EmptyLayout + +@inject NavigationManager Navigation +@inject IJSRuntime Script +@inject IToastService ToastService + +@(Resources.IDS_REGISTER_PAGE_TITLE) + +
+ +
+
+ + Starfish +
+
配置管理和服务发现
+
+ + + + + + + + + + + + + + + + + + + + + + @(Resources.IDS_COMMON_SUBMIT) + +
+ +
+ +@code { + + private bool Loading { get; set; } + + private string UserName { get; set; } + + private string Password { get; set; } + + private string Confirm { get; set; } + + private string NickName { get; set; } + + private string Email { get; set; } + + private string Phone { get; set; } + + private readonly SemaphoreSlim _semaphoreSlim = new(1, 1); + + private async Task OnClick(MouseEventArgs args) + { + if (_semaphoreSlim.CurrentCount == 0) + { + return; + } + + var cancellationToken = new CancellationTokenSource(TimeSpan.FromSeconds(10)); + + try + { + Loading = true; + await _semaphoreSlim.WaitAsync(cancellationToken.Token); + + Navigation.NavigateTo("/login"); + } + catch (Exception exception) + { + var message = exception.GetPromptMessage(); + ToastService.ShowError(message); + await Script.InvokeVoidAsync("console.log", cancellationToken.Token, message); + } + finally + { + _semaphoreSlim.Release(); + Loading = false; + StateHasChanged(); + } + } + +} \ No newline at end of file diff --git a/Source/Starfish.Webapp/Program.cs b/Source/Starfish.Webapp/Program.cs index e44b8ef..e139ec6 100644 --- a/Source/Starfish.Webapp/Program.cs +++ b/Source/Starfish.Webapp/Program.cs @@ -20,21 +20,22 @@ public static async Task Main(string[] args) builder.Services.AddOptions(); builder.Services.AddAuthorizationCore(); builder.Services - .AddScoped() - .AddScoped(provider => provider.GetRequiredService()) - .AddBlazoredLocalStorageAsSingleton() - .AddHttpClientApi(options => - { - var baseUrl = builder.Configuration.GetValue("Api:BaseUrl"); - var timeout = builder.Configuration.GetValue("Api:Timeout"); - if (string.IsNullOrEmpty(baseUrl)) - { - baseUrl = builder.HostEnvironment.BaseAddress; - } + .AddScoped() + .AddScoped(provider => provider.GetRequiredService()) + .AddCascadingAuthenticationState() + .AddBlazoredLocalStorageAsSingleton() + .AddHttpClientApi(options => + { + var baseUrl = builder.Configuration.GetValue("Api:BaseUrl"); + var timeout = builder.Configuration.GetValue("Api:Timeout"); + if (string.IsNullOrEmpty(baseUrl)) + { + baseUrl = builder.HostEnvironment.BaseAddress; + } - options.BaseUrl = baseUrl; - options.Timeout = TimeSpan.FromMilliseconds(timeout); - }); + options.BaseUrl = baseUrl; + options.Timeout = TimeSpan.FromMilliseconds(timeout); + }); var host = builder.Build(); Singleton.Get(() => new HostAccessor diff --git a/Source/Starfish.Webapp/Properties/Resources.resx b/Source/Starfish.Webapp/Properties/Resources.resx index 4eaad94..1282c7c 100644 --- a/Source/Starfish.Webapp/Properties/Resources.resx +++ b/Source/Starfish.Webapp/Properties/Resources.resx @@ -120,9 +120,21 @@ Cancel + + Close + + + Keyword to search + Save + + Search + + + Submit + Login @@ -156,6 +168,39 @@ User + + Register + + + Confirm password + + + Email + + + Name to display + + + Password + + + Phone number + + + User name + + + Alias + + + Description + + + Name + + + Add new team + Add user @@ -165,6 +210,9 @@ Edit user + + Confirm password + Email diff --git a/Source/Starfish.Webapp/Rest/Defines/IAppsApi.cs b/Source/Starfish.Webapp/Rest/Defines/IAppsApi.cs new file mode 100644 index 0000000..b9071a3 --- /dev/null +++ b/Source/Starfish.Webapp/Rest/Defines/IAppsApi.cs @@ -0,0 +1,10 @@ +using Nerosoft.Starfish.Transit; +using Refit; + +namespace Nerosoft.Starfish.Webapp.Rest; + +internal interface IAppsApi +{ + [Get("/api/apps")] + Task>> QueryAsync([Query] AppInfoCriteria criteria, int page, int size, CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/Source/Starfish.Webapp/Rest/Defines/IAppsApis.cs b/Source/Starfish.Webapp/Rest/Defines/IAppsApis.cs deleted file mode 100644 index 46fd49d..0000000 --- a/Source/Starfish.Webapp/Rest/Defines/IAppsApis.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace Nerosoft.Starfish.Webapp.Rest; - -internal interface IAppsApis -{ -} \ No newline at end of file diff --git a/Source/Starfish.Webapp/Rest/ServiceCollectionExtensions.cs b/Source/Starfish.Webapp/Rest/ServiceCollectionExtensions.cs index 0e64eca..16d8799 100644 --- a/Source/Starfish.Webapp/Rest/ServiceCollectionExtensions.cs +++ b/Source/Starfish.Webapp/Rest/ServiceCollectionExtensions.cs @@ -39,7 +39,7 @@ public static IServiceCollection AddHttpClientApi(this IServiceCollection servic .AddTransient(provider => provider.GetRestService(HTTP_CLIENT_NAME)) .AddTransient(provider => provider.GetRestService(HTTP_CLIENT_NAME)) .AddTransient(provider => provider.GetRestService(HTTP_CLIENT_NAME)) - .AddTransient(provider => provider.GetRestService(HTTP_CLIENT_NAME)) + .AddTransient(provider => provider.GetRestService(HTTP_CLIENT_NAME)) .AddTransient(provider => provider.GetRestService(HTTP_CLIENT_NAME)); services.AddHttpClient(HTTP_CLIENT_NAME, (provider, client) => diff --git a/Source/Starfish.Webapp/Seedwork/IdentityAuthenticationStateProvider.cs b/Source/Starfish.Webapp/Seedwork/IdentityAuthenticationStateProvider.cs index 573acd5..3914686 100644 --- a/Source/Starfish.Webapp/Seedwork/IdentityAuthenticationStateProvider.cs +++ b/Source/Starfish.Webapp/Seedwork/IdentityAuthenticationStateProvider.cs @@ -1,4 +1,5 @@ using Blazored.LocalStorage; +using IdentityModel; using Microsoft.AspNetCore.Components.Authorization; using System.Security.Claims; @@ -19,7 +20,7 @@ public async Task SetAuthenticationStateAsync(string accessToken, string refresh await _storageService.SetItemAsStringAsync(Constants.LocalStorage.RefreshToken, refreshToken); var jwt = TokenHelper.Resolve(accessToken); - var identity = new ClaimsIdentity(jwt.Claims, "jwt"); + var identity = new ClaimsIdentity(jwt.Claims, "jwt", JwtClaimTypes.Name, JwtClaimTypes.Role); var user = new ClaimsPrincipal(identity); var authState = Task.FromResult(new AuthenticationState(user)); @@ -34,7 +35,7 @@ public override async Task GetAuthenticationStateAsync() var jwt = TokenHelper.Resolve(token); if (jwt != null && jwt.ValidTo > DateTime.UtcNow) { - identity = new ClaimsIdentity(jwt.Claims, "jwt"); + identity = new ClaimsIdentity(jwt.Claims, "jwt", JwtClaimTypes.Name, JwtClaimTypes.Role); } else { diff --git a/Source/Starfish.Webapp/Shared/FluentDataGridPaginator.razor b/Source/Starfish.Webapp/Shared/FluentDataGridPaginator.razor new file mode 100644 index 0000000..fe8f96a --- /dev/null +++ b/Source/Starfish.Webapp/Shared/FluentDataGridPaginator.razor @@ -0,0 +1,15 @@ + + + There are @(State.TotalItemCount ?? 0) rows + + + This is page @(State.CurrentPageIndex + 1) out of a total of @(State.LastPageIndex + 1) pages + + + +@code { + + [Parameter] + public PaginationState State { get; set; } + +} \ No newline at end of file diff --git a/Source/Starfish.Webapp/_Imports.razor b/Source/Starfish.Webapp/_Imports.razor index 62f6323..049db14 100644 --- a/Source/Starfish.Webapp/_Imports.razor +++ b/Source/Starfish.Webapp/_Imports.razor @@ -1,5 +1,7 @@ -@using System.Net.Http +@using System.Collections.ObjectModel +@using System.Net.Http @using System.Net.Http.Json +@using Microsoft.AspNetCore.Authorization @using Microsoft.AspNetCore.Components.Authorization @using Microsoft.AspNetCore.Components.Forms @using Microsoft.AspNetCore.Components.Routing @@ -8,5 +10,10 @@ @using Microsoft.AspNetCore.Components.WebAssembly.Http @using Microsoft.FluentUI.AspNetCore.Components @using Microsoft.JSInterop +@using Nerosoft.Euonia.Claims @using Nerosoft.Starfish.Webapp -@using Nerosoft.Starfish.Webapp.Layout \ No newline at end of file +@using Nerosoft.Starfish.Webapp.Layout +@using Nerosoft.Starfish.Webapp.Shared +@using Nerosoft.Starfish.Webapp.Rest + +@using Nerosoft.Starfish.Transit \ No newline at end of file diff --git a/Source/Starfish.Webapp/wwwroot/css/app.css b/Source/Starfish.Webapp/wwwroot/css/app.css index 2581598..651b976 100644 --- a/Source/Starfish.Webapp/wwwroot/css/app.css +++ b/Source/Starfish.Webapp/wwwroot/css/app.css @@ -174,3 +174,7 @@ code { border: none; } } + +nav[class*="paginator-nav"] { + background-color: unset !important; +} \ No newline at end of file From 34892697d6d71d45ae1ab620a5c8492e03ce0c6c Mon Sep 17 00:00:00 2001 From: damon Date: Tue, 9 Jan 2024 08:34:19 +0800 Subject: [PATCH 20/22] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E6=8A=A5=E9=94=99=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Domain/Business/TeamGeneralBusiness.cs | 4 ++-- .../Domain/Business/TeamMemberBusiness.cs | 3 ++- .../Domain/Business/UserGeneralBusiness.cs | 14 +++++++------- .../Domain/Repositories/ITeamRepository.cs | 3 ++- 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/Source/Starfish.Service/Domain/Business/TeamGeneralBusiness.cs b/Source/Starfish.Service/Domain/Business/TeamGeneralBusiness.cs index ecc94cb..d3f92d3 100644 --- a/Source/Starfish.Service/Domain/Business/TeamGeneralBusiness.cs +++ b/Source/Starfish.Service/Domain/Business/TeamGeneralBusiness.cs @@ -10,7 +10,7 @@ public class TeamGeneralBusiness : EditableObjectBase, IDom { [Inject] public ITeamRepository TeamRepository { get; set; } - + private Team Aggregate { get; set; } public static readonly PropertyInfo IdProperty = RegisterProperty(p => p.Id); @@ -56,7 +56,7 @@ protected override Task CreateAsync(CancellationToken cancellationToken = defaul [FactoryFetch] protected async Task FetchAsync(int id, CancellationToken cancellationToken = default) { - var aggregate = await TeamRepository.GetAsync(id, true, cancellationToken); + var aggregate = await TeamRepository.GetAsync(id, true, [], cancellationToken); Aggregate = aggregate ?? throw new SettingNotFoundException(id); diff --git a/Source/Starfish.Service/Domain/Business/TeamMemberBusiness.cs b/Source/Starfish.Service/Domain/Business/TeamMemberBusiness.cs index b1123a6..84e244e 100644 --- a/Source/Starfish.Service/Domain/Business/TeamMemberBusiness.cs +++ b/Source/Starfish.Service/Domain/Business/TeamMemberBusiness.cs @@ -1,5 +1,6 @@ using Nerosoft.Euonia.Business; using Nerosoft.Euonia.Claims; +using Nerosoft.Euonia.Repository.EfCore; using Nerosoft.Starfish.Service; // ReSharper disable MemberCanBePrivate.Global @@ -33,7 +34,7 @@ protected override void AddRules() [FactoryFetch] protected async Task FetchAsync(int id, CancellationToken cancellationToken = default) { - var aggregate = await TeamRepository.GetAsync(id, true, cancellationToken); + var aggregate = await TeamRepository.GetAsync(id, true, [nameof(Team.Members)], cancellationToken); Aggregate = aggregate ?? throw new SettingNotFoundException(id); } diff --git a/Source/Starfish.Service/Domain/Business/UserGeneralBusiness.cs b/Source/Starfish.Service/Domain/Business/UserGeneralBusiness.cs index 05ebee3..c3687eb 100644 --- a/Source/Starfish.Service/Domain/Business/UserGeneralBusiness.cs +++ b/Source/Starfish.Service/Domain/Business/UserGeneralBusiness.cs @@ -89,7 +89,7 @@ protected override Task CreateAsync(CancellationToken cancellationToken = defaul [FactoryFetch] protected async Task FetchAsync(int id, CancellationToken cancellationToken = default) { - var user = await _repository.GetAsync(id, true, query => query.Include(nameof(User.Roles)), cancellationToken); + var user = await Repository.GetAsync(id, true, query => query.Include(nameof(User.Roles)), cancellationToken); Aggregate = user ?? throw new UserNotFoundException(id); @@ -124,12 +124,12 @@ protected override Task InsertAsync(CancellationToken cancellationToken = defaul user.SetRoles(Roles.ToArray()); } - return _repository.InsertAsync(user, true, cancellationToken) - .ContinueWith(task => - { - task.WaitAndUnwrapException(cancellationToken); - Id = task.Result.Id; - }, cancellationToken); + return Repository.InsertAsync(user, true, cancellationToken) + .ContinueWith(task => + { + task.WaitAndUnwrapException(cancellationToken); + Id = task.Result.Id; + }, cancellationToken); } [FactoryUpdate] diff --git a/Source/Starfish.Service/Domain/Repositories/ITeamRepository.cs b/Source/Starfish.Service/Domain/Repositories/ITeamRepository.cs index 078651d..4eda40f 100644 --- a/Source/Starfish.Service/Domain/Repositories/ITeamRepository.cs +++ b/Source/Starfish.Service/Domain/Repositories/ITeamRepository.cs @@ -10,9 +10,10 @@ public interface ITeamRepository : IRepository /// /// /// + /// /// /// - Task GetAsync(int id, bool tracking, CancellationToken cancellationToken = default); + Task GetAsync(int id, bool tracking, string[] properties, CancellationToken cancellationToken = default); /// /// 查询指定用户所属团队 From aa47429cab0902f1ccc1c0621b0ae25891a7f60d Mon Sep 17 00:00:00 2001 From: damon Date: Tue, 9 Jan 2024 21:12:53 +0800 Subject: [PATCH 21/22] =?UTF-8?q?=E5=9B=A2=E9=98=9F=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2=E7=AD=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Domain/Aggregates/User.cs | 5 + .../Domain/Business/UserGeneralBusiness.cs | 2 +- .../Starfish.Webapp/Pages/Apps/Detail.razor | 12 + Source/Starfish.Webapp/Pages/Apps/Edit.razor | 207 ++++++++++++++++++ Source/Starfish.Webapp/Pages/Apps/Index.razor | 114 +++++++++- .../Starfish.Webapp/Pages/Team/Detail.razor | 110 +++++++--- Source/Starfish.Webapp/Pages/Team/Index.razor | 30 +-- .../Starfish.Webapp/Pages/User/Detail.razor | 22 +- Source/Starfish.Webapp/Pages/User/Index.razor | 18 +- .../Starfish.Webapp/Properties/Resources.resx | 63 ++++++ .../Starfish.Webapp/Rest/Defines/IAppsApi.cs | 12 + 11 files changed, 522 insertions(+), 73 deletions(-) create mode 100644 Source/Starfish.Webapp/Pages/Apps/Detail.razor create mode 100644 Source/Starfish.Webapp/Pages/Apps/Edit.razor diff --git a/Source/Starfish.Service/Domain/Aggregates/User.cs b/Source/Starfish.Service/Domain/Aggregates/User.cs index 24f8f77..db252a0 100644 --- a/Source/Starfish.Service/Domain/Aggregates/User.cs +++ b/Source/Starfish.Service/Domain/Aggregates/User.cs @@ -135,6 +135,11 @@ internal void ChangePassword(string password) /// internal void SetRoles(params string[] roles) { + if (roles == null) + { + return; + } + if (Reserved) { throw new UnauthorizedAccessException("预留账号不允许设置角色"); diff --git a/Source/Starfish.Service/Domain/Business/UserGeneralBusiness.cs b/Source/Starfish.Service/Domain/Business/UserGeneralBusiness.cs index c3687eb..c4fd94c 100644 --- a/Source/Starfish.Service/Domain/Business/UserGeneralBusiness.cs +++ b/Source/Starfish.Service/Domain/Business/UserGeneralBusiness.cs @@ -152,7 +152,7 @@ protected override Task UpdateAsync(CancellationToken cancellationToken = defaul if (ChangedProperties.Contains(RolesProperty)) { - Aggregate.SetRoles(Roles.ToArray()); + Aggregate.SetRoles(Roles?.ToArray()); } if (ChangedProperties.Contains(PasswordProperty)) diff --git a/Source/Starfish.Webapp/Pages/Apps/Detail.razor b/Source/Starfish.Webapp/Pages/Apps/Detail.razor new file mode 100644 index 0000000..1064f75 --- /dev/null +++ b/Source/Starfish.Webapp/Pages/Apps/Detail.razor @@ -0,0 +1,12 @@ +@page "/apps/{id:long}" + + +@code { + [Parameter] + public long Id { get; set; } + + [CascadingParameter] + private Task AuthenticationState { get; set; } + + private UserPrincipal User { get; set; } +} \ No newline at end of file diff --git a/Source/Starfish.Webapp/Pages/Apps/Edit.razor b/Source/Starfish.Webapp/Pages/Apps/Edit.razor new file mode 100644 index 0000000..c20a460 --- /dev/null +++ b/Source/Starfish.Webapp/Pages/Apps/Edit.razor @@ -0,0 +1,207 @@ +@implements IDialogContentComponent + +@inject IAppsApi AppsApi +@inject ITeamApi TeamApi +@inject IToastService ToastService + + + + + + @Dialog.Instance.Parameters.Title + + + + + + + @if (Content == 0) + { + + } + + + @if (Content == 0) + { + + + } + + + + + + @(Resources.IDS_COMMON_SAVE) + @(Resources.IDS_COMMON_CANCEL) + + +@code { + + [Parameter] + public long Content { get; set; } + + [CascadingParameter] + public FluentDialog Dialog { get; set; } = default!; + + private bool Loading { get; set; } + + private List Tesms { get; } = new(); + + /// + /// 团队Id + /// + private string TeamId { get; set; } + + /// + /// 应用名称 + /// + private string Name { get; set; } = default!; + + /// + /// 应用代码 + /// + private string Code { get; set; } = default!; + + /// + /// 密钥 + /// + private string Secret { get; set; } + + /// + /// 应用描述 + /// + private string Description { get; set; } + + protected override async Task OnInitializedAsync() + { + await base.OnInitializedAsync(); + + var tasks = new List + { + LoadDetailAsync(Content) + }; + if (Content == 0) + { + tasks.Add(LoadTeamsAsync()); + } + + await Task.WhenAll(tasks); + } + + private async Task SaveAsync() + { + try + { + Loading = true; + + if (Content > 0) + { + var request = new AppInfoUpdateDto() + { + Name = Name, + Description = Description + }; + await AppsApi.UpdateAsync(Content, request) + .ContinueWith(task => + { + task.WaitAndUnwrapException(); + task.Result.EnsureSuccess(); + }); + } + else + { + var request = new AppInfoCreateDto() + { + TeamId = int.Parse(TeamId), + Name = Name, + Code = Code, + Secret = Secret, + Description = Description + }; + await AppsApi.CreateAsync(request) + .ContinueWith(task => + { + task.WaitAndUnwrapException(); + task.Result.EnsureSuccess(); + }); + } + + await Dialog.CloseAsync(Content); + } + catch (Exception exception) + { + var message = exception.GetPromptMessage(); + ToastService.ShowError(message); + } + finally + { + Loading = false; + } + } + + private async Task CancelAsync() + { + await Dialog.CancelAsync(); + } + + private async Task LoadTeamsAsync() + { + await TeamApi.QueryAsync(new TeamCriteria(), 1, 100) + .ContinueWith(task => + { + task.WaitAndUnwrapException(); + var result = task.Result.EnsureSuccess(); + if (result == null) + { + return; + } + + Tesms.Clear(); + Tesms.AddRange(result); + if (result.Count > 0) + { + TeamId = result[0].Id.ToString(); + } + }); + } + + private async Task LoadDetailAsync(long id) + { + if (id <= 0) + { + return; + } + + await AppsApi.GetAsync(id) + .ContinueWith(task => + { + task.WaitAndUnwrapException(); + var result = task.Result.EnsureSuccess(); + if (result == null) + { + return; + } + + Content = result.Id; + Code = result.Code; + Name = result.Name; + Description = result.Description; + }); + } + +} \ No newline at end of file diff --git a/Source/Starfish.Webapp/Pages/Apps/Index.razor b/Source/Starfish.Webapp/Pages/Apps/Index.razor index 97151b6..20c4670 100644 --- a/Source/Starfish.Webapp/Pages/Apps/Index.razor +++ b/Source/Starfish.Webapp/Pages/Apps/Index.razor @@ -1,14 +1,114 @@ @page "/apps" +@attribute [Authorize] + +@inject IDialogService DialogService +@inject NavigationManager Navigation +@inject IAppsApi Api + - - Home - - - Apps - + @(Resources.IDS_MENU_TEXT_HOME) + @(Resources.IDS_MENU_TEXT_APPS) + + + +
+ + @(Resources.IDS_COMMON_SEARCH) + + @(Resources.IDS_APPS_INDEX_BUTTON_ADD) +
+
+ + + + + @(context.Name) + + + + + + + + + + + + + +
+
+ @code { -} + [CascadingParameter] + private Task AuthenticationState { get; set; } + + private UserPrincipal User { get; set; } + + private GridItemsProvider _provider; + + private PaginationState Pagination { get; } = new() { ItemsPerPage = 20 }; + + private AppInfoCriteria Criteria { get; } = new(); + + private int Total { get; set; } + + private const int SIZE = 20; + + protected override async Task OnInitializedAsync() + { + var user = await AuthenticationState; + + User = new UserPrincipal(user.User); + + _provider = async request => + { + List items = null; + var tasks = new List + { + Api.QueryAsync(Criteria, request.StartIndex + 1, SIZE, request.CancellationToken) + .ContinueWith(task => + { + task.WaitAndUnwrapException(request.CancellationToken); + items = task.Result.EnsureSuccess(); + }, request.CancellationToken) + }; + + if (request.StartIndex == 0) + { + tasks.Add(Api.CountAsync(Criteria, request.CancellationToken) + .ContinueWith(task => + { + task.WaitAndUnwrapException(request.CancellationToken); + Total = task.Result.EnsureSuccess(); + }, request.CancellationToken)); + } + + await Task.WhenAll(tasks); + await Pagination.SetTotalItemCountAsync(Total); + return GridItemsProviderResult.From(items, Total); + }; + } + + private async Task HandleDetailClicked(long id) + { + Navigation.NavigateTo($"/apps/{id}"); + await Task.CompletedTask; + } + + private async Task OnEditClicked(long id) + { + var title = id == 0 ? Resources.IDS_APPS_EDIT_TITLE_ADD : Resources.IDS_APPS_EDIT_TITLE_EDIT; + await DialogService.ShowDialogAsync(id, new DialogParameters { Title = title, Modal = true }); + } + + private async Task OnSearchClicked() + { + await Pagination.SetCurrentPageIndexAsync(0); + } + +} \ No newline at end of file diff --git a/Source/Starfish.Webapp/Pages/Team/Detail.razor b/Source/Starfish.Webapp/Pages/Team/Detail.razor index 7888371..87680c2 100644 --- a/Source/Starfish.Webapp/Pages/Team/Detail.razor +++ b/Source/Starfish.Webapp/Pages/Team/Detail.razor @@ -5,9 +5,9 @@ @inject IDialogService DialogService - Home - Team - Detail + @(Resources.IDS_MENU_TEXT_HOME) + @(Resources.IDS_MENU_TEXT_TEAM) + @(Resources.IDS_BREADCRUMB_TEAM_DETAIL) @@ -16,32 +16,49 @@ Alias: @Data.Alias Created at: @Data.CreateTime.Date - Updated at: @Data.CreateTime.Date + Updated at: @Data.UpdateTime.Date @Data.Description - Operations - - 编辑信息 - 添加成员 - - - - - + @if (Data.OwnerId != User?.GetUserIdOfInt32()) + { + @(Resources.IDS_COMMON_OPERATIONS) - @foreach (var member in Members) - { - - } + @(Resources.IDS_TEAM_DETAIL_BUTTON_EDIT) + @(Resources.IDS_TEAM_DETAIL_BUTTON_ADD_MEMBER) - - - - - + } + + +
+ + + + @foreach (var member in Members) + { + @if (Data.OwnerId != User?.GetUserIdOfInt32()) + { + + } + else + { + + } + } + + + + + + + + + + + +
@@ -50,12 +67,25 @@ [Parameter] public int Id { get; set; } + [CascadingParameter] + private Task AuthenticationState { get; set; } + + private UserPrincipal User { get; set; } + private TeamDetailDto Data { get; } = new(); - private ObservableRangeCollection Members { get; } = new(); + private ObservableRangeCollection Members { get; } = []; + + private ObservableRangeCollection Apps { get; } = []; + + private IQueryable QueryableApps => Apps.AsQueryable(); protected override async Task OnInitializedAsync() { + var user = await AuthenticationState; + + User = new UserPrincipal(user.User); + await LoadAsync(); } @@ -64,7 +94,8 @@ var tasks = new List { LoadDetailAsync(), - LoadMembersAsync() + LoadMembersAsync(), + LoadAppsAsync() }; await InvokeAsync(() => Task.WhenAll(tasks)); @@ -97,9 +128,28 @@ }); } + private Task LoadAppsAsync() + { + try + { + var criteria = new AppInfoCriteria { TeamId = Id }; + return AppsApi.QueryAsync(criteria, 1, 100) + .ContinueWith(task => + { + task.WaitAndUnwrapException(); + var data = task.Result.EnsureSuccess(); + Apps.ReplaceRange(data); + }); + } + catch + { + return Task.CompletedTask; + } + } + private async Task OnRemoveMemberClicked(int userId) { - var confirmation = await DialogService.ShowConfirmationAsync("Are you sure to remove this member?", title: "Remove member"); + var confirmation = await DialogService.ShowConfirmationAsync(Resources.IDS_TEAM_DETAIL_REMOVE_CONFIRMATION_MESSAGE, primaryText: Resources.IDS_COMMON_YES, secondaryText: Resources.IDS_COMMON_NO, title: Resources.IDS_TEAM_DETAIL_REMOVE_CONFIRMATION_TITLE); var result = await confirmation.Result; if (!result.Cancelled) { @@ -116,7 +166,7 @@ private async Task OnAppendMemberClicked() { - var reference = await DialogService.ShowDialogAsync(Id, new DialogParameters { Title = "Add member", Modal = true }); + var reference = await DialogService.ShowDialogAsync(Id, new DialogParameters { Title = Resources.IDS_TEAM_APPEND_MEMBER_DIALOG_TITLE, Modal = true }); var result = await reference.Result; if (!result.Cancelled) { @@ -125,4 +175,8 @@ } } + private async Task OnEditClicked() + { + await DialogService.ShowDialogAsync(Id, new DialogParameters { Title = Resources.IDS_TEAM_EDIT_TITLE_EDIT, Modal = true }); + } } \ No newline at end of file diff --git a/Source/Starfish.Webapp/Pages/Team/Index.razor b/Source/Starfish.Webapp/Pages/Team/Index.razor index 344f639..cc9584b 100644 --- a/Source/Starfish.Webapp/Pages/Team/Index.razor +++ b/Source/Starfish.Webapp/Pages/Team/Index.razor @@ -7,12 +7,8 @@ @inject ITeamApi Api - - Home - - - Team - + @(Resources.IDS_MENU_TEXT_HOME) + @(Resources.IDS_MENU_TEXT_TEAM) @@ -22,21 +18,21 @@ @(Resources.IDS_COMMON_SEARCH) - @(Resources.IDS_TEAM_INDEX_BUTTON_ADD) + @(Resources.IDS_TEAM_INDEX_BUTTON_ADD) - - + + @(context.Name) - - - - + + + + - + @@ -98,8 +94,6 @@ await Pagination.SetTotalItemCountAsync(Total); return GridItemsProviderResult.From(items, Total); }; - - await Task.CompletedTask; } private async Task HandleDetailClicked(int id) @@ -108,9 +102,9 @@ await Task.CompletedTask; } - private async Task HandleEditClicked(int id) + private async Task OnEditClicked(int id) { - var title = id == 0 ? Resources.IDS_USER_DIALOG_TITLE_ADD : Resources.IDS_USER_DIALOG_TITLE_EDIT; + var title = id == 0 ? Resources.IDS_TEAM_EDIT_TITLE_ADD : Resources.IDS_TEAM_EDIT_TITLE_EDIT; await DialogService.ShowDialogAsync(id, new DialogParameters { Title = title, Modal = true }); } diff --git a/Source/Starfish.Webapp/Pages/User/Detail.razor b/Source/Starfish.Webapp/Pages/User/Detail.razor index 88304d8..f8a039c 100644 --- a/Source/Starfish.Webapp/Pages/User/Detail.razor +++ b/Source/Starfish.Webapp/Pages/User/Detail.razor @@ -3,9 +3,9 @@ @inject IUserApi Api - Home - Users - Detail + @(Resources.IDS_MENU_TEXT_HOME) + @(Resources.IDS_MENU_TEXT_USER) + @(Resources.IDS_BREADCRUMB_USER_DETAIL) @@ -15,14 +15,20 @@ @Data.UserName @Data.CreateTime.Date - + Roles - Blazor - Microsoft - Azure + + Blazor + + + Microsoft + + + Azure + - + Operations 编辑信息 diff --git a/Source/Starfish.Webapp/Pages/User/Index.razor b/Source/Starfish.Webapp/Pages/User/Index.razor index eeda18c..e0c6e13 100644 --- a/Source/Starfish.Webapp/Pages/User/Index.razor +++ b/Source/Starfish.Webapp/Pages/User/Index.razor @@ -5,12 +5,8 @@ @inject IUserApi Api - - Home - - - User - + @(Resources.IDS_MENU_TEXT_HOME) + @(Resources.IDS_MENU_TEXT_USER) @@ -24,14 +20,14 @@ - - + + @(context.UserName) - - - + + + diff --git a/Source/Starfish.Webapp/Properties/Resources.resx b/Source/Starfish.Webapp/Properties/Resources.resx index 1282c7c..038d0f8 100644 --- a/Source/Starfish.Webapp/Properties/Resources.resx +++ b/Source/Starfish.Webapp/Properties/Resources.resx @@ -117,12 +117,51 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + Code + + + Description + + + Name + + + Secret + + + Select a team + + + Add app + + + Edit app + + + Add app + + + Detail + + + Detail + + + Detail + Cancel Close + + No + + + Operations + Keyword to search @@ -135,6 +174,9 @@ Submit + + Yes + Login @@ -189,6 +231,21 @@ User name + + Add member + + + Add member + + + Edit + + + Are you sure to remove this member? + + + Remove member + Alias @@ -198,6 +255,12 @@ Name + + Add team + + + Edit team + Add new team diff --git a/Source/Starfish.Webapp/Rest/Defines/IAppsApi.cs b/Source/Starfish.Webapp/Rest/Defines/IAppsApi.cs index b9071a3..d61fdf9 100644 --- a/Source/Starfish.Webapp/Rest/Defines/IAppsApi.cs +++ b/Source/Starfish.Webapp/Rest/Defines/IAppsApi.cs @@ -7,4 +7,16 @@ internal interface IAppsApi { [Get("/api/apps")] Task>> QueryAsync([Query] AppInfoCriteria criteria, int page, int size, CancellationToken cancellationToken = default); + + [Get("/api/apps/count")] + Task> CountAsync([Query] AppInfoCriteria criteria, CancellationToken cancellationToken = default); + + [Get("/api/apps/{id}")] + Task> GetAsync(long id, CancellationToken cancellationToken = default); + + [Post("/api/apps")] + Task CreateAsync([Body] AppInfoCreateDto data, CancellationToken cancellationToken = default); + + [Put("/api/apps/{id}")] + Task UpdateAsync(long id, [Body] AppInfoUpdateDto data, CancellationToken cancellationToken = default); } \ No newline at end of file From d71da6e30a545d274382099719d3f7090d8adcd9 Mon Sep 17 00:00:00 2001 From: damon Date: Wed, 10 Jan 2024 10:50:32 +0800 Subject: [PATCH 22/22] =?UTF-8?q?=E9=80=80=E5=87=BA=E7=99=BB=E5=BD=95?= =?UTF-8?q?=E7=AD=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Starfish.Webapp/Layout/MainLayout.razor | 49 +++++++++--- .../Starfish.Webapp/Pages/Apps/Detail.razor | 5 ++ Source/Starfish.Webapp/Pages/Apps/Index.razor | 33 +++++++- .../Pages/Apps/ResetSecret.razor | 77 +++++++++++++++++++ .../Starfish.Webapp/Pages/Team/Detail.razor | 8 +- Source/Starfish.Webapp/Pages/User/Login.razor | 2 +- Source/Starfish.Webapp/Program.cs | 4 +- .../Starfish.Webapp/Properties/Resources.resx | 12 +++ .../Starfish.Webapp/Rest/Defines/IAppsApi.cs | 6 ++ ...r.cs => JwtAuthenticationStateProvider.cs} | 13 +++- 10 files changed, 187 insertions(+), 22 deletions(-) create mode 100644 Source/Starfish.Webapp/Pages/Apps/ResetSecret.razor rename Source/Starfish.Webapp/Seedwork/{IdentityAuthenticationStateProvider.cs => JwtAuthenticationStateProvider.cs} (74%) diff --git a/Source/Starfish.Webapp/Layout/MainLayout.razor b/Source/Starfish.Webapp/Layout/MainLayout.razor index 9a147eb..bfadd21 100644 --- a/Source/Starfish.Webapp/Layout/MainLayout.razor +++ b/Source/Starfish.Webapp/Layout/MainLayout.razor @@ -1,7 +1,11 @@ @inherits LayoutComponentBase - +@implements IDisposable @attribute [Authorize] +@inject JwtAuthenticationStateProvider Authentication +@inject NavigationManager Navigation + + @@ -12,17 +16,16 @@ - - - + - + @(Resources.IDS_MENU_TEXT_HOME) @(Resources.IDS_MENU_TEXT_APPS) @if (Identity?.IsInRole("SA") == true) @@ -48,13 +51,15 @@ -
Nerosoft © 2024. All rights reserved.
+
@@ -73,6 +78,28 @@ var state = await AuthenticationState; Identity = new UserPrincipal(state.User); StateHasChanged(); + + Authentication.AuthenticationStateChanged += OnAuthenticationStateChanged; + } + + private async void OnAuthenticationStateChanged(Task task) + { + var state = await AuthenticationState; + if (state.User?.Identity?.IsAuthenticated != true) + { + Navigation.NavigateTo("/login"); + } + } + + private async Task OnLogoutClicked(MouseEventArgs args) + { + await Authentication.LogoutAsync(); + } + + public void Dispose() + { + Authentication.AuthenticationStateChanged -= OnAuthenticationStateChanged; + AuthenticationState?.Dispose(); } } \ No newline at end of file diff --git a/Source/Starfish.Webapp/Pages/Apps/Detail.razor b/Source/Starfish.Webapp/Pages/Apps/Detail.razor index 1064f75..5ab5e5a 100644 --- a/Source/Starfish.Webapp/Pages/Apps/Detail.razor +++ b/Source/Starfish.Webapp/Pages/Apps/Detail.razor @@ -1,5 +1,10 @@ @page "/apps/{id:long}" + + @(Resources.IDS_MENU_TEXT_HOME) + @(Resources.IDS_MENU_TEXT_APPS) + @(Resources.IDS_BREADCRUMB_APPS_DETAIL) + @code { [Parameter] diff --git a/Source/Starfish.Webapp/Pages/Apps/Index.razor b/Source/Starfish.Webapp/Pages/Apps/Index.razor index 20c4670..8464f1f 100644 --- a/Source/Starfish.Webapp/Pages/Apps/Index.razor +++ b/Source/Starfish.Webapp/Pages/Apps/Index.razor @@ -23,7 +23,7 @@
- + @(context.Name) @@ -33,6 +33,8 @@ + + @@ -103,7 +105,34 @@ private async Task OnEditClicked(long id) { var title = id == 0 ? Resources.IDS_APPS_EDIT_TITLE_ADD : Resources.IDS_APPS_EDIT_TITLE_EDIT; - await DialogService.ShowDialogAsync(id, new DialogParameters { Title = title, Modal = true }); + var dialog = await DialogService.ShowDialogAsync(id, new DialogParameters { Title = title, Modal = true }); + var result = await dialog.Result; + if (result.Cancelled) + { + await Pagination.SetCurrentPageIndexAsync(0); + } + } + + private async Task OnDeleteClicked(long id, string name) + { + var message = string.Format(Resources.IDS_APPS_INDEX_REMOVE_CONFIRMATION_MESSAGE, name); + var confirmation = await DialogService.ShowConfirmationAsync(message, primaryText: Resources.IDS_COMMON_YES, secondaryText: Resources.IDS_COMMON_NO, title: Resources.IDS_APPS_INDEX_REMOVE_CONFIRMATION_TITLE); + var result = await confirmation.Result; + if (!result.Cancelled) + { + await Api.DeleteAsync(id) + .ContinueWith(task => + { + task.WaitAndUnwrapException(); + task.Result.EnsureSuccess(); + }); + await Pagination.SetCurrentPageIndexAsync(0); + } + } + + private async Task OnResetSecretClicked(long id) + { + await DialogService.ShowDialogAsync(id, new DialogParameters { Modal = true }); } private async Task OnSearchClicked() diff --git a/Source/Starfish.Webapp/Pages/Apps/ResetSecret.razor b/Source/Starfish.Webapp/Pages/Apps/ResetSecret.razor new file mode 100644 index 0000000..e4bb95b --- /dev/null +++ b/Source/Starfish.Webapp/Pages/Apps/ResetSecret.razor @@ -0,0 +1,77 @@ +@implements IDialogContentComponent + +@inject IAppsApi AppsApi +@inject IToastService ToastService + + + + + + @(Resources.IDS_APPS_RESET_SECRET_DIALOG_TITLE) + + + + + + + + @(Resources.IDS_APPS_RESET_SECRET_DIALOG_TIPS) + + + + + @(Resources.IDS_COMMON_SAVE) + @(Resources.IDS_COMMON_CANCEL) + + +@code { + + [Parameter] + public long Content { get; set; } + + [CascadingParameter] + public FluentDialog Dialog { get; set; } = default!; + + private bool Loading { get; set; } + + private string Secret { get; set; } + + private async Task SaveAsync() + { + try + { + Loading = true; + + var data = new AppInfoSetSecretDto + { + Secret = Secret + }; + + await AppsApi.SetSecretAsync(Content, data) + .ContinueWith(task => + { + task.WaitAndUnwrapException(); + task.Result.EnsureSuccess(); + }); + + await Dialog.CloseAsync(Content); + } + catch (Exception exception) + { + var message = exception.GetPromptMessage(); + ToastService.ShowError(message); + } + finally + { + Loading = false; + } + } + + private async Task CancelAsync() + { + await Dialog.CancelAsync(); + } + +} \ No newline at end of file diff --git a/Source/Starfish.Webapp/Pages/Team/Detail.razor b/Source/Starfish.Webapp/Pages/Team/Detail.razor index 87680c2..6f77428 100644 --- a/Source/Starfish.Webapp/Pages/Team/Detail.razor +++ b/Source/Starfish.Webapp/Pages/Team/Detail.razor @@ -20,7 +20,7 @@ @Data.Description - @if (Data.OwnerId != User?.GetUserIdOfInt32()) + @if (Data.OwnerId != Identity?.GetUserIdOfInt32()) { @(Resources.IDS_COMMON_OPERATIONS) @@ -36,7 +36,7 @@ @foreach (var member in Members) { - @if (Data.OwnerId != User?.GetUserIdOfInt32()) + @if (Data.OwnerId != Identity?.GetUserIdOfInt32()) { AuthenticationState { get; set; } - private UserPrincipal User { get; set; } + private UserPrincipal Identity { get; set; } private TeamDetailDto Data { get; } = new(); @@ -84,7 +84,7 @@ { var user = await AuthenticationState; - User = new UserPrincipal(user.User); + Identity = new UserPrincipal(user.User); await LoadAsync(); } diff --git a/Source/Starfish.Webapp/Pages/User/Login.razor b/Source/Starfish.Webapp/Pages/User/Login.razor index aa723fe..474a3f2 100644 --- a/Source/Starfish.Webapp/Pages/User/Login.razor +++ b/Source/Starfish.Webapp/Pages/User/Login.razor @@ -3,7 +3,7 @@ @layout EmptyLayout @inject NavigationManager Navigation -@inject IdentityAuthenticationStateProvider Authentication +@inject JwtAuthenticationStateProvider Authentication @inject IJSRuntime Script @inject IToastService ToastService @inject IIdentityApi Api diff --git a/Source/Starfish.Webapp/Program.cs b/Source/Starfish.Webapp/Program.cs index e139ec6..1843259 100644 --- a/Source/Starfish.Webapp/Program.cs +++ b/Source/Starfish.Webapp/Program.cs @@ -20,8 +20,8 @@ public static async Task Main(string[] args) builder.Services.AddOptions(); builder.Services.AddAuthorizationCore(); builder.Services - .AddScoped() - .AddScoped(provider => provider.GetRequiredService()) + .AddScoped() + .AddScoped(provider => provider.GetRequiredService()) .AddCascadingAuthenticationState() .AddBlazoredLocalStorageAsSingleton() .AddHttpClientApi(options => diff --git a/Source/Starfish.Webapp/Properties/Resources.resx b/Source/Starfish.Webapp/Properties/Resources.resx index 038d0f8..41d8c81 100644 --- a/Source/Starfish.Webapp/Properties/Resources.resx +++ b/Source/Starfish.Webapp/Properties/Resources.resx @@ -141,6 +141,18 @@ Add app + + Are you sure to remove this app '{0}'? + + + Remove App + + + The system will encrypt the secret and can not be decrypted. You should keep the secret yourself. If you forget the origin secret, please reset it. + + + Reset secret + Detail diff --git a/Source/Starfish.Webapp/Rest/Defines/IAppsApi.cs b/Source/Starfish.Webapp/Rest/Defines/IAppsApi.cs index d61fdf9..bbd04da 100644 --- a/Source/Starfish.Webapp/Rest/Defines/IAppsApi.cs +++ b/Source/Starfish.Webapp/Rest/Defines/IAppsApi.cs @@ -19,4 +19,10 @@ internal interface IAppsApi [Put("/api/apps/{id}")] Task UpdateAsync(long id, [Body] AppInfoUpdateDto data, CancellationToken cancellationToken = default); + + [Put("/api/apps/{id}/secret")] + Task SetSecretAsync(long id, [Body] AppInfoSetSecretDto data, CancellationToken cancellationToken = default); + + [Delete("/api/apps/{id}")] + Task DeleteAsync(long id, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/Source/Starfish.Webapp/Seedwork/IdentityAuthenticationStateProvider.cs b/Source/Starfish.Webapp/Seedwork/JwtAuthenticationStateProvider.cs similarity index 74% rename from Source/Starfish.Webapp/Seedwork/IdentityAuthenticationStateProvider.cs rename to Source/Starfish.Webapp/Seedwork/JwtAuthenticationStateProvider.cs index 3914686..6fdc97a 100644 --- a/Source/Starfish.Webapp/Seedwork/IdentityAuthenticationStateProvider.cs +++ b/Source/Starfish.Webapp/Seedwork/JwtAuthenticationStateProvider.cs @@ -5,11 +5,11 @@ namespace Nerosoft.Starfish.Webapp; -public class IdentityAuthenticationStateProvider : AuthenticationStateProvider +public class JwtAuthenticationStateProvider : AuthenticationStateProvider { private readonly ILocalStorageService _storageService; - public IdentityAuthenticationStateProvider(ILocalStorageService storageService) + public JwtAuthenticationStateProvider(ILocalStorageService storageService) { _storageService = storageService; } @@ -27,6 +27,15 @@ public async Task SetAuthenticationStateAsync(string accessToken, string refresh NotifyAuthenticationStateChanged(authState); } + public async Task LogoutAsync() + { + await _storageService.RemoveItemAsync(Constants.LocalStorage.AccessToken); + await _storageService.RemoveItemAsync(Constants.LocalStorage.RefreshToken); + + var authState = Task.FromResult(new AuthenticationState(new ClaimsPrincipal())); + NotifyAuthenticationStateChanged(authState); + } + public override async Task GetAuthenticationStateAsync() { ClaimsIdentity identity;