diff --git a/Attributes/SimApiAuthAttribute.cs b/Attributes/SimApiAuthAttribute.cs index 1dff006..db0a229 100644 --- a/Attributes/SimApiAuthAttribute.cs +++ b/Attributes/SimApiAuthAttribute.cs @@ -43,7 +43,7 @@ public class SimApiAuthAttribute : ActionFilterAttribute public override void OnActionExecuting(ActionExecutingContext context) { - var loginInfo = (SimApiLoginItem)context.HttpContext.Items["LoginInfo"]; + var loginInfo = (SimApiLoginItem)context.HttpContext.Items["LoginInfo"]!; //检测是否登录 if (loginInfo == null) { diff --git a/Attributes/SynapseEventAttribute.cs b/Attributes/SynapseEventAttribute.cs index 173c983..f30bc0a 100644 --- a/Attributes/SynapseEventAttribute.cs +++ b/Attributes/SynapseEventAttribute.cs @@ -3,12 +3,7 @@ using System; namespace SimApi.Attributes; [AttributeUsage(AttributeTargets.Method)] -public class SynapseEventAttribute : Attribute +public class SynapseEventAttribute(string? name = null) : Attribute { - public string Name { get; } - - public SynapseEventAttribute(string name) - { - Name = name; - } + public string? Name { get; } = name; } \ No newline at end of file diff --git a/Attributes/SynapseRpcAttribute.cs b/Attributes/SynapseRpcAttribute.cs index b82a5dd..14a388c 100644 --- a/Attributes/SynapseRpcAttribute.cs +++ b/Attributes/SynapseRpcAttribute.cs @@ -5,7 +5,7 @@ namespace SimApi.Attributes; [AttributeUsage(AttributeTargets.Method)] public class SynapseRpcAttribute : Attribute { - public string Name { get; } + public string? Name { get; } public SynapseRpcAttribute() { diff --git a/Communications/SimApiBaseRequest.cs b/Communications/SimApiBaseRequest.cs index 982d423..f4c6670 100644 --- a/Communications/SimApiBaseRequest.cs +++ b/Communications/SimApiBaseRequest.cs @@ -15,7 +15,7 @@ public class SimApiIdOnlyRequest /// public class SimApiStringIdOnlyRequest { - [Required] public string Id { get; set; } + [Required] public string? Id { get; set; } } /// @@ -24,7 +24,7 @@ public class SimApiStringIdOnlyRequest /// public class SimApiOneFieldRequest { - [Required] public T Data { get; set; } + [Required] public T? Data { get; set; } } /// diff --git a/Communications/SimApiLoginItem.cs b/Communications/SimApiLoginItem.cs index f336b97..3ad8fea 100644 --- a/Communications/SimApiLoginItem.cs +++ b/Communications/SimApiLoginItem.cs @@ -5,4 +5,4 @@ namespace SimApi.Communications; /// /// 登录信息中间件 /// -public record SimApiLoginItem(string Id, string[] Type,Dictionary Meta = null); \ No newline at end of file +public record SimApiLoginItem(string Id, string[] Type,Dictionary? Meta = null); \ No newline at end of file diff --git a/Configurations/SimApiStorageOptions.cs b/Configurations/SimApiStorageOptions.cs index d58f717..2c91bb0 100644 --- a/Configurations/SimApiStorageOptions.cs +++ b/Configurations/SimApiStorageOptions.cs @@ -5,19 +5,19 @@ public class SimApiStorageOptions /// /// S3 服务器入口地址 /// - public string Endpoint { get; set; } + public string? Endpoint { get; set; } /// /// S3 服务器文件访问地址 /// - public string ServeUrl { get; set; } + public string? ServeUrl { get; set; } /// /// S3服务 Bucket /// - public string Bucket { get; set; } + public string? Bucket { get; set; } - public string AccessKey { get; set; } + public string? AccessKey { get; set; } - public string SecretKey { get; set; } + public string? SecretKey { get; set; } } \ No newline at end of file diff --git a/Configurations/SimApiSynapseOptions.cs b/Configurations/SimApiSynapseOptions.cs index bd3dc45..a490813 100644 --- a/Configurations/SimApiSynapseOptions.cs +++ b/Configurations/SimApiSynapseOptions.cs @@ -5,20 +5,15 @@ public class SimApiSynapseOptions /// /// Mqtt服务器的Websocket地址 /// - public string Websocket { get; set; } - - public string Username { get; set; } - - public string Password { get; set; } - - public string SysName { get; set; } - public string AppName { get; set; } - - public string AppId { get; set; } - + public string? Websocket { get; set; } + public string? Username { get; set; } + public string? Password { get; set; } + public string? SysName { get; set; } + public string? AppName { get; set; } + public string? AppId { get; set; } public int RpcTimeout { get; set; } = 3; + public bool EnableConfigStore { get; set; } = true; public bool DisableEventClient { get; set; } = false; - public bool DisableRpcClient { get; set; } = false; } \ No newline at end of file diff --git a/Controllers/SimApiBaseController.cs b/Controllers/SimApiBaseController.cs index 00c7001..700aa22 100644 --- a/Controllers/SimApiBaseController.cs +++ b/Controllers/SimApiBaseController.cs @@ -18,7 +18,7 @@ public class SimApiBaseController : Controller /// /// 当前登录用户的ID /// - protected SimApiLoginItem LoginInfo => (SimApiLoginItem)HttpContext.Items["LoginInfo"]; + protected SimApiLoginItem LoginInfo => (SimApiLoginItem)HttpContext.Items["LoginInfo"]!; /// /// 验证请求参数 @@ -93,7 +93,7 @@ public class SimApiBaseController : Controller /// 检测条件 /// 错误代码 /// 错误描述 - protected static void ErrorWhenNull(object condition, int code = 404, string message = "请求的资源不存在") + protected static void ErrorWhenNull(object? condition, int code = 404, string message = "请求的资源不存在") { ErrorWhen(condition == null, code, message); } diff --git a/Controllers/SimApiCommonController.cs b/Controllers/SimApiCommonController.cs index 3b8e35f..adfa6fe 100644 --- a/Controllers/SimApiCommonController.cs +++ b/Controllers/SimApiCommonController.cs @@ -41,7 +41,7 @@ public class SimApiCommonController(SimApiAuth auth) : SimApiBaseController [HttpPost("/auth/logout"), SimApiDoc("认证", "退出登陆")] public SimApiBaseResponse Logout() { - string token = null; + string? token = null; if (Request.Headers.TryGetValue("Token", out var value)) { diff --git a/Helpers/SimApiStorage.cs b/Helpers/SimApiStorage.cs index a9c01a9..470def3 100644 --- a/Helpers/SimApiStorage.cs +++ b/Helpers/SimApiStorage.cs @@ -26,10 +26,10 @@ public class SimApiStorage { var options = apiOptions.SimApiStorageOptions; HttpContextAccessor = httpContextAccessor; - Endpoint = options.Endpoint; + Endpoint = options.Endpoint!; var useSsl = false; string endpoint; - if (options.Endpoint.StartsWith("http://")) + if (options.Endpoint!.StartsWith("http://")) { endpoint = options.Endpoint.Replace("http://", string.Empty); } @@ -43,9 +43,9 @@ public class SimApiStorage throw new Exception("SimApiStorage: Error Endpoint"); } - ServeUrl = options.ServeUrl; + ServeUrl = options.ServeUrl!; if (ServeUrl.EndsWith('/')) throw new Exception("SimApiStorage: ServeUrl must not end with /"); - Bucket = options.Bucket; + Bucket = options.Bucket!; var mcb = new MinioClient().WithEndpoint(endpoint) .WithCredentials(options.AccessKey, options.SecretKey); if (useSsl) diff --git a/Logger/SimApiLogger.cs b/Logger/SimApiLogger.cs index bd2a5d8..21e8730 100644 --- a/Logger/SimApiLogger.cs +++ b/Logger/SimApiLogger.cs @@ -10,7 +10,7 @@ public class SimApiLogger(string name) : ILogger public bool IsEnabled(LogLevel logLevel) => true; - public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) { Console.ForegroundColor = logLevel switch @@ -23,7 +23,7 @@ public class SimApiLogger(string name) : ILogger _ => ConsoleColor.White }; var message = - $"[ {name} ][ {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:ffff")} ][ {logLevel.ToString()} ]\n{state}\n"; + $"[ {name} ][ {DateTime.Now:yyyy-MM-dd HH:mm:ss:ffff} ][ {logLevel.ToString()} ]\n{state}\n"; if (exception != null) { message += $"{exception}\n"; diff --git a/Logger/SimApiLoggerConfiguration.cs b/Logger/SimApiLoggerConfiguration.cs deleted file mode 100644 index 3fb6bb4..0000000 --- a/Logger/SimApiLoggerConfiguration.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Collections.Generic; -using Microsoft.Extensions.Logging; - -namespace SimApi.Logger; - -public class SimApiLoggerConfiguration -{ - public int EventId { get; set; } - - public Dictionary LogLevels { get; set; } = new() - { - [LogLevel.Information] = ConsoleColor.Green - }; -} \ No newline at end of file diff --git a/Middlewares/SimApiAuthMiddleware.cs b/Middlewares/SimApiAuthMiddleware.cs index 809737c..d9d02f8 100644 --- a/Middlewares/SimApiAuthMiddleware.cs +++ b/Middlewares/SimApiAuthMiddleware.cs @@ -14,21 +14,18 @@ public class SimApiAuthMiddleware(RequestDelegate next) { public Task Invoke(HttpContext httpContext, IDistributedCache cache, SimApiAuth auth) { - string token = null; + string? token = null; if (httpContext.Request.Headers.TryGetValue("Token", out var header)) { token = header; } - if (!string.IsNullOrEmpty(token)) + if (string.IsNullOrEmpty(token)) return next(httpContext); + var login = auth.GetLogin(token); + if (login != null) { - var login = auth.GetLogin(token); - if (login != null) - { - httpContext.Items.Add("LoginInfo", login); - } + httpContext.Items.Add("LoginInfo", login); } - return next(httpContext); } } \ No newline at end of file diff --git a/Models/SimApiBaseModel.cs b/Models/SimApiBaseModel.cs index cb0fea4..2bd38db 100644 --- a/Models/SimApiBaseModel.cs +++ b/Models/SimApiBaseModel.cs @@ -20,7 +20,7 @@ public class SimApiBaseModel public void MapData(TS source, bool mapAll = false) { //获取要赋值的源数据不为null的项目 - var sourceProps = source.GetType().GetProperties().Where(x => x.GetValue(source) != null) + var sourceProps = source!.GetType().GetProperties().Where(x => x.GetValue(source) != null) .Select(x => new { x.Name, @@ -49,7 +49,7 @@ public class SimApiBaseModel public void MapData(TS source, string[] mapFields) { //获取要赋值的源数据不为null的项目 - var sourceProps = source.GetType().GetProperties().Where(x => x.GetValue(source) != null) + var sourceProps = source!.GetType().GetProperties().Where(x => x.GetValue(source) != null) .Select(x => new { x.Name, diff --git a/SimApi.csproj b/SimApi.csproj index bd0efdc..f3700f4 100644 --- a/SimApi.csproj +++ b/SimApi.csproj @@ -15,6 +15,7 @@ true 5.0.2 net8.0 + enable diff --git a/Synapse/ConfigStore.cs b/Synapse/ConfigStore.cs new file mode 100644 index 0000000..3f12c18 --- /dev/null +++ b/Synapse/ConfigStore.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using MQTTnet; +using MQTTnet.Protocol; + +namespace SimApi; + +public partial class Synapse +{ + public event EventHandler? OnConfigChanged; + private Dictionary CurrentConfig { get; } = new(); + + private bool FireSetConfig(string key, string value) + { + var topic = $"{Options.SysName}/synapse-config-store/{key}"; + var message = new MqttApplicationMessageBuilder() + .WithTopic(topic) + .WithPayload(value) + .WithRetainFlag() + .Build(); + if (!Client!.IsConnected) return false; + Client!.PublishAsync(message, CancellationToken.None).Wait(); + logger.LogDebug("Synapse Config Set: {Config} => {Data}", key, value); + return true; + } + + private string? FireGetConfig(string key) + { + return CurrentConfig.GetValueOrDefault(key); + } + + private void RunConfigStoreServer() + { + var csTopicPrefix = $"{Options.SysName}/synapse-config-store/"; + var eventSubOpts = MqttFactory.CreateSubscribeOptionsBuilder() + .WithTopicFilter(o => + o.WithTopic($"{csTopicPrefix}+").WithRetainHandling(MqttRetainHandling.SendAtSubscribe)) + .Build(); + Client!.ApplicationMessageReceivedAsync += e => + { + if (!e.ApplicationMessage.Topic.StartsWith(csTopicPrefix)) return Task.CompletedTask; + var reqBody = e.ApplicationMessage.ConvertPayloadToString(); + var eventName = e.ApplicationMessage.Topic.Replace(csTopicPrefix, string.Empty); + CurrentConfig[eventName] = reqBody; + OnConfigChanged?.Invoke(this, new ConfigStoreItem(eventName, reqBody)); + logger.LogDebug("Synapse Config Changed: {Config} => {Data}", eventName, reqBody); + return Task.CompletedTask; + }; + Client.SubscribeAsync(eventSubOpts).Wait(); + } + + public record ConfigStoreItem(string Key, string Value); +} \ No newline at end of file diff --git a/Synapse/EventClient.cs b/Synapse/EventClient.cs index 1b6f89c..7280be6 100644 --- a/Synapse/EventClient.cs +++ b/Synapse/EventClient.cs @@ -8,18 +8,26 @@ namespace SimApi; public partial class Synapse { - private bool FireEvent(string eventName, object param, bool retain = false) + private bool FireEvent(string eventName, object? param) { - var paramJson = JsonSerializer.Serialize(param, SimApiUtil.JsonOption); - var topic = $"{Options.SysName}/{Options.AppName}/event/{eventName}"; + string paramJson; + if (param is string strParam) + { + paramJson = strParam; + } + else + { + paramJson = JsonSerializer.Serialize(param, SimApiUtil.JsonOption); + } + var topic = $"{Options.SysName}/event/{Options.AppName}/{eventName}"; var message = new MqttApplicationMessageBuilder() .WithTopic(topic) .WithPayload(paramJson) - .WithRetainFlag(retain) + .WithRetainFlag(false) .Build(); if (!Client!.IsConnected) return false; - Client!.PublishAsync(message, CancellationToken.None).Wait(); - logger.LogDebug("Event Publish: {Event}@{App} {Json}", eventName, Options.AppName, paramJson); + Client.PublishAsync(message, CancellationToken.None).Wait(); + logger.LogDebug("Synapse Event Publish: {Event}@{App} {Json}", eventName, Options.AppName, paramJson); return true; } } \ No newline at end of file diff --git a/Synapse/EventServer.cs b/Synapse/EventServer.cs index 4dfb91c..2add285 100644 --- a/Synapse/EventServer.cs +++ b/Synapse/EventServer.cs @@ -1,11 +1,11 @@ using System; using System.Linq; using System.Text.Json; +using System.Text.RegularExpressions; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using MQTTnet; -using MQTTnet.Protocol; using SimApi.Helpers; namespace SimApi; @@ -14,36 +14,50 @@ public partial class Synapse { private void RunEventServer() { - var esTopicPrefix = $"{Options.SysName}/{Options.AppName}/event/"; - var eventSubOpts = MqttFactory.CreateSubscribeOptionsBuilder() - .WithTopicFilter(o => - o.WithTopic($"$queue/{esTopicPrefix}+").WithRetainHandling(MqttRetainHandling.SendAtSubscribe)) - .Build(); - Client.ApplicationMessageReceivedAsync += e => + var esTopicPrefix = $"{Options.SysName}/event/"; + Client!.ApplicationMessageReceivedAsync += e => { if (!e.ApplicationMessage.Topic.StartsWith(esTopicPrefix)) return Task.CompletedTask; var reqBody = e.ApplicationMessage.ConvertPayloadToString(); var eventName = e.ApplicationMessage.Topic.Replace(esTopicPrefix, string.Empty); - logger.LogDebug("Synapse Event Receive: {AppName}.{EventName}\n{Body}", - Options.AppName, eventName, reqBody); + logger.LogDebug("Synapse Event Receive: {EventName}\n{Body}", eventName, reqBody); + var methods = EventRegistry + .Where(x => Regex.IsMatch(eventName, + "^" + Regex.Escape(x.Key!).Replace("\\+", "[^/]+").Replace("\\#", ".*") + "$")) + .ToArray(); + foreach (var method in methods) + { + var callClass = sp.CreateScope().ServiceProvider.GetRequiredService(method.Class!); + var mt = callClass.GetType().GetMethod(method.Method!); + try + { + if (mt!.GetParameters().Length == 2) + { + var pt = mt!.GetParameters()[0].ParameterType; + mt.Invoke(callClass, pt == typeof(string) + ? [eventName, reqBody] + : [eventName, JsonSerializer.Deserialize(reqBody, pt, SimApiUtil.JsonOption)]); + } + else + { + mt.Invoke(callClass, [eventName]); + } + } + catch (Exception ex) + { + logger.LogError("Synapse Event Processor Error: {Err}", ex.InnerException); + } + } - var method = EventRegistry.FirstOrDefault(x => x.Key == eventName); - if (method == null) return Task.CompletedTask; - var callClass = Sp.CreateScope().ServiceProvider.GetRequiredService(method!.Class); - var mt = callClass.GetType().GetMethod(method.Method); - var pt = mt!.GetParameters()[0].ParameterType; - try - { - mt.Invoke(callClass, pt == typeof(string) - ? new object[] { reqBody } - : new[] { JsonSerializer.Deserialize(reqBody, pt, SimApiUtil.JsonOption) }); - } - catch (Exception ex) - { - logger.LogError("SynapseEvent Processor Error: {Err}", ex.InnerException); - } return Task.CompletedTask; }; - Client.SubscribeAsync(eventSubOpts).Wait(); + foreach (var ev in EventRegistry) + { + var topic = $"$queue/{esTopicPrefix}{ev.Key}"; + var evSubOpts = MqttFactory.CreateSubscribeOptionsBuilder() + .WithTopicFilter(o => o.WithTopic(topic)).Build(); + Client.SubscribeAsync(evSubOpts).Wait(); + logger.LogDebug("Synapse Event Register Event Success: {EventName}\nFull Topic: {Topic}", ev.Key, topic); + } } } \ No newline at end of file diff --git a/Synapse/RpcClient.cs b/Synapse/RpcClient.cs index efbba91..83836a0 100644 --- a/Synapse/RpcClient.cs +++ b/Synapse/RpcClient.cs @@ -19,7 +19,7 @@ public partial class Synapse var rcTopic = $"{Options.SysName}/{Options.AppName}/rpc/client/{Options.AppId}/"; var rcSubOpts = MqttFactory.CreateSubscribeOptionsBuilder() .WithTopicFilter(o => o.WithTopic($"{rcTopic}+")).Build(); - Client.ApplicationMessageReceivedAsync += e => + Client!.ApplicationMessageReceivedAsync += e => { if (!e.ApplicationMessage.Topic.StartsWith(rcTopic)) return Task.CompletedTask; var reqBody = e.ApplicationMessage.ConvertPayloadToString(); @@ -32,9 +32,18 @@ public partial class Synapse Client.SubscribeAsync(rcSubOpts).Wait(); } - private string FireRpc(string app, string action, object param) + private string? FireRpc(string app, string action, object? param) { - var paramJson = JsonSerializer.Serialize(param, SimApiUtil.JsonOption); + string paramJson; + if (param is string strParam) + { + paramJson = strParam; + } + else + { + paramJson = JsonSerializer.Serialize(param, SimApiUtil.JsonOption); + } + var topic = $"{Options.SysName}/{app}/rpc/server/{action}"; var messageId = Guid.NewGuid().ToString(); var tcs = new TaskCompletionSource(); @@ -47,7 +56,7 @@ public partial class Synapse .WithRetainFlag(false) .Build(); if (!Client!.IsConnected) return null; - Client!.PublishAsync(message, CancellationToken.None).Wait(); + Client.PublishAsync(message, CancellationToken.None).Wait(); logger.LogDebug( "Synapse RPC Client Request: ({PropsMessageId}) {OptionsAppName} -> {Action}@{App}\n{ParamJson}", messageId, Options.AppName, action, app, paramJson); diff --git a/Synapse/RpcServer.cs b/Synapse/RpcServer.cs index e983729..22a36f7 100644 --- a/Synapse/RpcServer.cs +++ b/Synapse/RpcServer.cs @@ -23,7 +23,7 @@ public partial class Synapse .WithTopicFilter(o => o.WithTopic($"$queue/{rsTopicPrefix}+").WithRetainHandling(MqttRetainHandling.SendAtSubscribe)) .Build(); - Client.ApplicationMessageReceivedAsync += e => + Client!.ApplicationMessageReceivedAsync += e => { if (!e.ApplicationMessage.Topic.StartsWith(rsTopicPrefix)) return Task.CompletedTask; var reqBody = e.ApplicationMessage.ConvertPayloadToString(); @@ -33,19 +33,29 @@ public partial class Synapse "Synapse RPC Server Receive: ({BasicPropertiesMessageId}) {BasicPropertiesReplyTo} -> {BasicPropertiesType}@{OptionsAppName}\n{S}", e.ApplicationMessage.ContentType, appInfo[0], action, Options.AppName, reqBody); - var res = new SimApiBaseResponse(404, "method not found"); + SimApiBaseResponse res; var method = RpcRegistry.FirstOrDefault(x => x.Key == action); if (method == null) return Task.CompletedTask; - var callClass = Sp.CreateScope().ServiceProvider.GetRequiredService(method.Class); - var mt = callClass.GetType().GetMethod(method.Method); + var callClass = sp.CreateScope().ServiceProvider.GetRequiredService(method.Class!); + var mt = callClass.GetType().GetMethod(method.Method!); try { - var pt = mt!.GetParameters()[0].ParameterType; - var param = pt == typeof(string) - ? [reqBody] - : new[] { JsonSerializer.Deserialize(reqBody, pt, SimApiUtil.JsonOption) }; - var ret = mt.Invoke(callClass, param); - res = new SimApiBaseResponse + var methodParams = mt!.GetParameters(); + object? ret; + if (methodParams.Length == 0) + { + ret = mt.Invoke(callClass, []); + } + else + { + var pt = mt.GetParameters()[0].ParameterType; + var param = pt == typeof(string) + ? [reqBody] + : new[] { JsonSerializer.Deserialize(reqBody, pt, SimApiUtil.JsonOption) }; + ret = mt.Invoke(callClass, param); + } + + res = new SimApiBaseResponse { Data = ret }; @@ -67,6 +77,7 @@ public partial class Synapse logger.LogDebug("Synapse RPC调用失败: {Err}", ex.Message); res = new SimApiBaseResponse(500, ex.Message); } + var returnJson = JsonSerializer.Serialize((object)res, SimApiUtil.JsonOption); var reply = $"{Options.SysName}/{appInfo[0]}/rpc/client/{appInfo[1]}/{e.ApplicationMessage.ContentType}"; var message = new MqttApplicationMessageBuilder() @@ -74,8 +85,8 @@ public partial class Synapse .WithPayload(returnJson) .WithRetainFlag(false) .Build(); - if (!Client!.IsConnected) return Task.CompletedTask; - Client!.PublishAsync(message, CancellationToken.None).Wait(); + if (!Client.IsConnected) return Task.CompletedTask; + Client.PublishAsync(message, CancellationToken.None).Wait(); logger.LogDebug( "Synapse Rpc Server Return: ({BasicPropertiesMessageId}) {BasicPropertiesType}@{OptionsAppName} -> {BasicPropertiesReplyTo}\n{ReturnJson}", e.ApplicationMessage.ContentType, action, Options.AppName, appInfo[0], returnJson); diff --git a/Synapse/Synapse.cs b/Synapse/Synapse.cs index 8382c36..b089c66 100644 --- a/Synapse/Synapse.cs +++ b/Synapse/Synapse.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Text.Json; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using MQTTnet; using MQTTnet.Client; @@ -18,20 +19,17 @@ namespace SimApi; public partial class Synapse(SimApiOptions simApiOptions, ILogger logger, IServiceProvider sp) { - private IServiceProvider Sp { get; } = sp; - private SimApiSynapseOptions Options { get; } = simApiOptions.SimApiSynapseOptions; private MqttFactory MqttFactory { get; } = new(); - public IMqttClient Client { get; set; } + public IMqttClient? Client { get; set; } - private List EventRegistry { get; set; } + private List EventRegistry { get; set; } = new(); - private List RpcRegistry { get; set; } + private List RpcRegistry { get; set; } = new(); public void Init() { - ProcessAttribute(); logger.LogDebug("Synapse初始化配置信息: {Json}", SimApiUtil.Json(Options)); if (string.IsNullOrEmpty(Options.AppName) || string.IsNullOrEmpty(Options.SysName)) { @@ -42,6 +40,7 @@ public partial class Synapse(SimApiOptions simApiOptions, ILogger logge logger.LogInformation("Synapse Sys Name: {SysName}\nSynapse App Name: {AppName}\nSynapse App Id: {AppId}", Options.SysName, Options.AppName, Options.AppId); CreateConnection(); + ProcessAttribute(); //事件客户端 if (Options.DisableEventClient) { @@ -72,10 +71,24 @@ public partial class Synapse(SimApiOptions simApiOptions, ILogger logge { RunEventServer(); } + + if (Options.EnableConfigStore) + { + RunConfigStoreServer(); + logger.LogInformation("Synapse Config Store Ready [{SysName}] ...", Options.SysName); + } } - public SimApiBaseResponse Rpc(string appName, string method, dynamic param) + /// + /// 调用RPC使用明确的返回值类型 + /// + /// + /// + /// + /// + /// + public SimApiBaseResponse Rpc(string appName, string method, dynamic? param = null) { var res = new SimApiBaseResponse(500, "Synapse Rpc Client Disabled!"); if (Options.DisableRpcClient) @@ -88,24 +101,56 @@ public partial class Synapse(SimApiOptions simApiOptions, ILogger logge res = JsonSerializer.Deserialize>(data, SimApiUtil.JsonOption); } - return res as SimApiBaseResponse; + return (res as SimApiBaseResponse)!; } - public SimApiBaseResponse Rpc(string appName, string method, dynamic param) + /// + /// 调用RPC使用object作为返回值类型 + /// + /// + /// + /// + /// + public SimApiBaseResponse Rpc(string appName, string method, dynamic? param = null) { return Rpc(appName, method, param); } - public void Event(string eventName, dynamic param) + /// + /// + /// + /// + /// + public bool Event(string eventName, dynamic? param = null) { - if (Options.DisableEventClient) - { - logger.LogError("Synapse Event Client Disabled!"); - } - else - { - FireEvent(eventName, param); - } + if (!Options.DisableEventClient) return FireEvent(eventName, param); + logger.LogError("Synapse Event Client Disabled!"); + return false; + } + + /// + /// 设置一个系统配置项 + /// + /// + /// + /// + public bool SetConfig(string key, string value) + { + if (Options.EnableConfigStore) return FireSetConfig(key, value); + logger.LogError("Synapse Config Store Disabled!"); + return false; + } + + /// + /// 读取一个配置项,如果没有则为空 + /// + /// + /// + public string? GetConfig(string key) + { + if (Options.EnableConfigStore) return FireGetConfig(key); + logger.LogError("Synapse Config Store Disabled!"); + return null; } private void CreateConnection() @@ -141,8 +186,6 @@ public partial class Synapse(SimApiOptions simApiOptions, ILogger logge private void ProcessAttribute() { - EventRegistry = []; - RpcRegistry = []; var stackTrace = new StackTrace(); var callingMethod = stackTrace.GetFrame(stackTrace.FrameCount - 1)?.GetMethod(); var assembly = callingMethod?.DeclaringType?.Assembly; @@ -155,49 +198,84 @@ public partial class Synapse(SimApiOptions simApiOptions, ILogger logge if (method.IsDefined(typeof(SynapseEventAttribute), false)) { var attribute = - (SynapseEventAttribute)Attribute.GetCustomAttribute(method, typeof(SynapseEventAttribute)); - if (attribute != null) + (SynapseEventAttribute)Attribute.GetCustomAttribute(method, typeof(SynapseEventAttribute))!; + var tmp = new RegisterItem { - EventRegistry.Add(new RegisterItem - { - Key = attribute.Name ?? method.Name, - Class = type, - Method = method.Name - }); + Key = attribute.Name ?? method.Name, + Class = type, + Method = method.Name + }; + if (tmp.Key.StartsWith('/') || tmp.Key.EndsWith('/')) + { + logger.LogError("Synapse Event Register Error: {Key} Can't start or end of '/'", tmp.Key); + continue; } + + var callClass = sp.CreateScope().ServiceProvider.GetRequiredService(tmp.Class!); + var mt = callClass.GetType().GetMethod(tmp.Method); + if (mt!.GetParameters().Length > 2 || mt.GetParameters().Length<1) + { + logger.LogError( + "Synapse Event Register Error: Only one or two parameter supported. {Key} -> {Method}@{Class}", + tmp.Key, tmp.Method, tmp.Class.Name); + continue; + } + + EventRegistry.Add(tmp); } if (method.IsDefined(typeof(SynapseRpcAttribute), false)) { var attribute = - (SynapseRpcAttribute)Attribute.GetCustomAttribute(method, typeof(SynapseRpcAttribute)); - if (attribute != null) + (SynapseRpcAttribute)Attribute.GetCustomAttribute(method, typeof(SynapseRpcAttribute))!; + var tmp = new RegisterItem { - RpcRegistry.Add(new RegisterItem - { - Key = attribute.Name ?? $"{type.Name}.{method.Name}", - Class = type, - Method = method.Name - }); + Key = attribute.Name ?? $"{type.Name}.{method.Name}", + Class = type, + Method = method.Name + }; + if (tmp.Key.Contains('/') || tmp.Key.Contains('#') || tmp.Key.Contains('+')) + { + logger.LogError("Synapse Rpc Register Error: {Key} contains '/' , '#' , '+'", tmp.Key); + continue; } + + var callClass = sp.CreateScope().ServiceProvider.GetRequiredService(tmp.Class!); + var mt = callClass.GetType().GetMethod(tmp.Method); + if (mt!.GetParameters().Length > 1) + { + logger.LogError( + "Synapse Rpc Register Error: Only one or none parameter supported. {Key} -> {Method}@{Class}", + tmp.Key, tmp.Method, tmp.Class.Name); + continue; + } + + if (RpcRegistry.Any(x => x.Key == tmp.Key)) + { + logger.LogError("Synapse Rpc Register Error: {Key} Already Exists -> {Method}@{Class}", tmp.Key, + tmp.Method, tmp.Class.Name); + continue; + } + + RpcRegistry.Add(tmp); } } } var events = EventRegistry.Aggregate(string.Empty, - (current, ev) => current + $"\n |- {ev.Key} -> {ev.Method}@{ev.Class.Name}"); + (current, ev) => current + $"\n |- {ev.Key} -> {ev.Method}@{ev.Class!.Name}"); var rpcList = RpcRegistry.Aggregate(string.Empty, - (current, ev) => current + $"\n |- {ev.Key} -> {ev.Method}@{ev.Class.Name}"); - if (EventRegistry.Count > 0) logger.LogInformation(">>> Synapse System 读取Event方法:{Event}", events); - if (RpcRegistry.Count > 0) logger.LogInformation(">>> Synapse System 读取RPC方法:{Rpc}", rpcList); + (current, ev) => current + $"\n |- {ev.Key} -> {ev.Method}@{ev.Class!.Name}"); + if (EventRegistry.Count > 0) logger.LogInformation(" >> Synapse System 读取Event方法:{Event}", events); + if (RpcRegistry.Count > 0) logger.LogInformation(" >> Synapse System 读取RPC方法:{Rpc}", rpcList); } } public class RegisterItem { - public string Key { get; init; } + public string? Key { get; init; } - public Type Class { get; init; } + public Type? Class { get; init; } - public string Method { get; init; } + public string? Method { get; init; } } \ No newline at end of file