From 512c511707c15119a735e0d0cbd0f111e2b5272a Mon Sep 17 00:00:00 2001 From: xiaolipro <2357729423@qq.com> Date: Fri, 6 Dec 2024 15:30:41 +0800 Subject: [PATCH] temp --- common.props | 3 +- .../InterfaceForwardApiModule.cs | 1 - src/InterfaceForward.Api/appsettings.json | 10 +- .../Dtos/Interface/SaveParameterMapsInput.cs | 5 + .../ForwardCore/ForwardCoreContext.cs | 24 +- .../InterfaceRelayForwardReplayEvent.cs | 2 +- .../ForwardCore/DefaultForwardFlow.cs | 177 +++++------ .../Helpers/CommonExtensions.cs | 2 - .../Helpers/StringHelper.cs | 51 ++++ .../HostServices/AppSubscribeEvent.cs | 41 +++ .../HostServices/AppSubscribeEventHandler.cs | 95 ++++++ .../HostServices/AppSubscribeHostService.cs | 69 +++++ .../InterfaceForward.Application.csproj | 1 + .../InterfaceForwardApplicationModule.cs | 2 + .../Rabbit/BatchForwardEvent.cs | 20 ++ .../Rabbit/BatchForwardEventHandler.cs | 96 ++++++ .../Rabbit/RabbitClient.cs | 32 ++ .../Services/InterfaceForwardCommon.cs | 199 +++++++------ .../Services/InterfaceForwardService.cs | 105 ++++++- .../InterfaceMapPublishService.cs | 67 ++--- .../InterfaceMapService.Details.cs | 245 ++++++++++++++++ .../InterfaceMap/InterfaceMapService.cs | 274 ++---------------- .../Dtos/ContextFeature.cs | 9 +- .../Dtos/FixedFieldWithValueDto.cs | 8 +- .../Dtos/ForwardCoreContextCache.cs | 26 +- .../Dtos/InterfaceDto.cs | 10 +- .../Dtos/InterfaceFormFieldDto.cs | 6 +- .../Dtos/InterfaceReturnConfigDto.cs | 4 +- .../Dtos/RequestLogDto.cs | 23 +- .../RequestServiceProviderFailedCommon.cs | 2 + .../Dtos/ServiceProviderDto.cs | 10 +- .../GlobalConst.cs | 5 +- .../Interface/Entitys/InterfaceMapEntity.cs | 16 +- .../Services/IInterfaceMapRepository.cs | 4 +- .../Services/InterfaceMapRepository.cs | 21 +- .../ValueObjects/EditMapDetailsOutput.cs | 17 +- .../ValueObjects/GetMapListInputVO.cs | 12 + .../InterfaceMapPublishedDifferenceVO.cs | 6 +- .../InterfaceForwardQuery.cs | 25 +- 39 files changed, 1134 insertions(+), 591 deletions(-) create mode 100644 src/InterfaceForward.Application/Helpers/StringHelper.cs create mode 100644 src/InterfaceForward.Application/HostServices/AppSubscribeEvent.cs create mode 100644 src/InterfaceForward.Application/HostServices/AppSubscribeEventHandler.cs create mode 100644 src/InterfaceForward.Application/HostServices/AppSubscribeHostService.cs create mode 100644 src/InterfaceForward.Application/Rabbit/BatchForwardEvent.cs create mode 100644 src/InterfaceForward.Application/Rabbit/BatchForwardEventHandler.cs create mode 100644 src/InterfaceForward.Application/Rabbit/RabbitClient.cs create mode 100644 src/InterfaceForward.Application/Services/InterfaceMap/InterfaceMapService.Details.cs create mode 100644 src/InterfaceForward.Repositories/Interface/ValueObjects/GetMapListInputVO.cs diff --git a/common.props b/common.props index 5bdca0a..073c53d 100644 --- a/common.props +++ b/common.props @@ -1,8 +1,9 @@ latest - + enable enable + CS1591 diff --git a/src/InterfaceForward.Api/InterfaceForwardApiModule.cs b/src/InterfaceForward.Api/InterfaceForwardApiModule.cs index a2acc56..704199d 100644 --- a/src/InterfaceForward.Api/InterfaceForwardApiModule.cs +++ b/src/InterfaceForward.Api/InterfaceForwardApiModule.cs @@ -29,7 +29,6 @@ public class InterfaceForwardApiModule : FakeModule services.AddFakeSwaggerGen() .AddFakeExceptionFilter() .AddFakeValidationActionFilter() - .AddFakeUnitOfWorkActionFilter() .AddFakeAspNetCoreAuditing(); services.AddCors(options => diff --git a/src/InterfaceForward.Api/appsettings.json b/src/InterfaceForward.Api/appsettings.json index 0a4208d..f1a689d 100644 --- a/src/InterfaceForward.Api/appsettings.json +++ b/src/InterfaceForward.Api/appsettings.json @@ -27,11 +27,19 @@ "http://localhost:8888" ] }, - "ConnectionStrings":{ + "ConnectionStrings": { "Default": "server=baget.xiaolipro.cn;database=interface_forward;uid=dev;pwd=itd!@#123;charset=utf8mb4;" }, "FeiShuNotice": { "Title": "InterfaceForward-Dev", "Webhook": "https://open.feishu.cn/open-apis/bot/v2/hook/255c44ff-5891-4902-9b6a-6d0250e745f7" + }, + "RabbitMQ": { + "Default": { + "Host": "baget.xiaolipro.cn", + "Port": 5672, + "UserName": "dev", + "Password": "itd!@#123" + } } } diff --git a/src/InterfaceForward.Application.Contracts/Dtos/Interface/SaveParameterMapsInput.cs b/src/InterfaceForward.Application.Contracts/Dtos/Interface/SaveParameterMapsInput.cs index f419311..9df9a97 100644 --- a/src/InterfaceForward.Application.Contracts/Dtos/Interface/SaveParameterMapsInput.cs +++ b/src/InterfaceForward.Application.Contracts/Dtos/Interface/SaveParameterMapsInput.cs @@ -39,6 +39,11 @@ public class MapItem /// public string PostfixScript { get; set; } + /// + /// 自由映射(支持不对等映射) + /// + public bool FreeMap { get; set; } + /// /// 入参映射明细 /// diff --git a/src/InterfaceForward.Application.Contracts/ForwardCore/ForwardCoreContext.cs b/src/InterfaceForward.Application.Contracts/ForwardCore/ForwardCoreContext.cs index 2453001..ab61fb7 100644 --- a/src/InterfaceForward.Application.Contracts/ForwardCore/ForwardCoreContext.cs +++ b/src/InterfaceForward.Application.Contracts/ForwardCore/ForwardCoreContext.cs @@ -9,27 +9,27 @@ public class ForwardCoreContext /// /// 目标服务商接口 /// - public InterfaceDto TargetInterface { get; set; } + public InterfaceDto TargetInterface { get; set; } = null!; /// /// 服务商接口固定字段列表 /// - public List FixedFieldList { get; set; } + public List FixedFieldList { get; set; } = []; /// /// 服务商账户字段列表 /// - public List AccountFieldList { get; set; } + public List AccountFieldList { get; set; } = []; /// /// 服务商接口返回配置列表 /// - public List ReturnConfigList { get; set; } + public List ReturnConfigList { get; set; } = []; /// /// 表单字段列表 /// - public List FormFieldList { get; set; } + public List FormFieldList { get; set; } = []; /// /// 入参映射列表(树形结构) @@ -44,12 +44,12 @@ public class ForwardCoreContext /// /// 请求前置脚本 /// - public string PrefixScript { get; set; } + public string? PrefixScript { get; set; } /// /// 请求后置脚本 /// - public string PostfixScript { get; set; } + public string? PostfixScript { get; set; } public bool AddHeader(string key, string value) { @@ -87,27 +87,27 @@ public class ForwardCoreContext /// /// 业务系统传参json /// - public JToken OriginalInterfaceInput { get; set; } + public JToken OriginalInterfaceInput { get; set; } = null!; /// /// 业务系统传参映射后的json /// - public JToken OriginalInterfaceMappedInput { get; set; } + public JToken OriginalInterfaceMappedInput { get; set; } = null!; /// /// 目标接口返回的json /// - public JToken TargetInterfaceOutput { get; set; } + public JToken TargetInterfaceOutput { get; set; } = null!; /// /// 请求服务商接口真实传参字符串 /// - public string TargetInterfaceInput { get; set; } + public string TargetInterfaceInput { get; set; } = null!; /// /// 服务商日志 /// - public RequestLogDto ServiceProviderRequestLog { get; set; } + public RequestLogDto ServiceProviderRequestLog { get; set; } = null!; /// /// 专用feature diff --git a/src/InterfaceForward.Application.Contracts/ForwardCore/InterfaceRelayForwardReplayEvent.cs b/src/InterfaceForward.Application.Contracts/ForwardCore/InterfaceRelayForwardReplayEvent.cs index 9617709..4ad88fa 100644 --- a/src/InterfaceForward.Application.Contracts/ForwardCore/InterfaceRelayForwardReplayEvent.cs +++ b/src/InterfaceForward.Application.Contracts/ForwardCore/InterfaceRelayForwardReplayEvent.cs @@ -25,7 +25,7 @@ public class InterfaceRelayForwardReplayEvent /// /// 原报文 /// - public string OriginalMessage { get; set; } + public string? OriginalMessage { get; set; } /// /// 附加数据 diff --git a/src/InterfaceForward.Application/ForwardCore/DefaultForwardFlow.cs b/src/InterfaceForward.Application/ForwardCore/DefaultForwardFlow.cs index 6fb0ec7..a7206c7 100644 --- a/src/InterfaceForward.Application/ForwardCore/DefaultForwardFlow.cs +++ b/src/InterfaceForward.Application/ForwardCore/DefaultForwardFlow.cs @@ -1,14 +1,15 @@ -using System.Text; +using System.Collections.Concurrent; +using System.Diagnostics; +using System.Text; using System.Text.RegularExpressions; using System.Web; using System.Xml; -using InterfaceForward.Application.Contracts; using InterfaceForward.Application.Contracts.ForwardCore; using InterfaceForward.Application.Helpers; using InterfaceForward.Application.OSS; using InterfaceForward.Domain.Shared; -using InterfaceForward.Domain.Shared.Dtos; using InterfaceForward.Domain.Shared.Enum; +using Microsoft.AspNetCore.Components; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -19,16 +20,11 @@ namespace InterfaceForward.Application.ForwardCore; /// public class DefaultForwardFlow : IForwardFlow { - public IHttpClientFactory ClientFactory { get; set; } + private static readonly ConcurrentDictionary DelegateDic = new(); + [Inject] public IHttpClientFactory ClientFactory { get; set; } = null!; public virtual Task InParamRestructure(ForwardCoreContext context) { - if (context.OriginalInterfaceInput is JArray) - { - // 虚构系统入参数组路径 - VirtualizePath(context.InParamTreeList); - } - var res = JsonHelper.Restructure(context.OriginalInterfaceInput, context.InParamTreeList, context.TargetInterface.MappedParamType); @@ -40,18 +36,7 @@ public class DefaultForwardFlow : IForwardFlow // 填充占位符 ReplacePlaceholder(context); - // 使用Natasha构建动态表达式 - if (!context.PrefixScript.IsNullOrEmpty()) - { - if (context.PrefixScript.Contains("await")) - { - await NDelegate.RandomDomain().AsyncFunc(context.PrefixScript)(context); - } - else - { - NDelegate.RandomDomain().Action(context.PrefixScript)(context); - } - } + await ExecuteScriptAsync(context, true); // 将body序列化成对应的格式字符串 context.TargetInterfaceInput = SerializationBody(context); @@ -64,17 +49,7 @@ public class DefaultForwardFlow : IForwardFlow // 将响应的字符串反序列化成jtoken context.TargetInterfaceOutput = DeserializationResponse(context, res); - if (!context.PostfixScript.IsNullOrEmpty()) - { - if (context.PostfixScript.Contains("await")) - { - await NDelegate.RandomDomain().AsyncFunc(context.PostfixScript)(context); - } - else - { - NDelegate.RandomDomain().Action(context.PostfixScript)(context); - } - } + await ExecuteScriptAsync(context, false); // 校验状态码,失败发飞书 VerifyStatusCode(context); @@ -84,12 +59,6 @@ public class DefaultForwardFlow : IForwardFlow public virtual async Task OutParamRestructure(ForwardCoreContext context, JToken response) { - if (context.TargetInterfaceOutput is JArray) - { - // 虚构系统入参数组路径 - VirtualizePath(context.OutParamTreeList); - } - // 映射 var mappedRes = JsonHelper.Restructure(response, context.OutParamTreeList); // 非结构化 @@ -119,18 +88,18 @@ public class DefaultForwardFlow : IForwardFlow var tokens = response.SelectTokens($"{context.TargetInterface.Code}.{pdfLabelConfig.Path}").ToList(); if (tokens.Count == 0) tokens = response.SelectTokens($"{pdfLabelConfig.Path}").ToList(); if (tokens.Count == 0) return mappedRes; - var token = tokens[0]; - UploadResultDto oss = null; + var token = tokens.First(); + UploadResultDto oss = null!; switch (Enum.Parse(pdfLabelConfig.Value)) { case LabelType.Url: - { - string url = token.Value(); - using var r = await ClientFactory.CreateClient().GetAsync(url); - await using var stream = await r.Content.ReadAsStreamAsync(); - oss = OSSHelper.Upload(stream, Guid.NewGuid().ToString("D") + ".pdf"); - break; - } + { + string url = token.Value()!; + using var r = await ClientFactory.CreateClient().GetAsync(url); + await using var stream = await r.Content.ReadAsStreamAsync(); + oss = OSSHelper.Upload(stream, Guid.NewGuid().ToString("D") + ".pdf"); + break; + } case LabelType.Base64: byte[] bytes = Convert.FromBase64String(token.ToString()); if (bytes.Length > 0) @@ -143,8 +112,8 @@ public class DefaultForwardFlow : IForwardFlow throw new ArgumentOutOfRangeException(); } - mappedRes["oss_key"] = oss!.Key; - mappedRes["oss_url"] = oss!.TempUrl; + mappedRes["oss_key"] = oss.Key; + mappedRes["oss_url"] = oss.TempUrl; } #endregion @@ -159,7 +128,7 @@ public class DefaultForwardFlow : IForwardFlow /// public virtual void AfterRequest(ForwardCoreContext context) { - if (context.ServiceProviderRequestLog.Response == RequestLogDto.Init) + if (context.ServiceProviderRequestLog.Response == null) { ThrowBusinessException(context, "请注意维护服务商日志!"); } @@ -205,9 +174,8 @@ public class DefaultForwardFlow : IForwardFlow catch (Exception) { ThrowBusinessException(context, - $"期望返回类型:{context.TargetInterface.ResponseContentType},解析过程中发生异常", - RequestServiceProviderFailedCommon.DeSerializeFailedCode); - return default; + $"期望返回类型:{context.TargetInterface.ResponseContentType},解析过程中发生异常"); + return default!; } } @@ -241,7 +209,7 @@ public class DefaultForwardFlow : IForwardFlow } // 失败 - string errorMsg = null; + string? errorMsg = null; var errorConfig = context.ReturnConfigList .FirstOrDefault(x => x.ConfigType == InterfaceReturnConfigType.ErrorMsg); if (errorConfig != null) @@ -250,8 +218,7 @@ public class DefaultForwardFlow : IForwardFlow } // 业务失败发飞书 - ThrowBusinessException(context, errorMsg ?? "未通过状态码验证。", - RequestServiceProviderFailedCommon.VerifyStatusFailedCode); + ThrowBusinessException(context, errorMsg ?? "未通过状态码验证。"); } /// @@ -259,10 +226,8 @@ public class DefaultForwardFlow : IForwardFlow /// /// 上下文 /// 异常消息 - /// 异常状态码 /// - protected virtual void ThrowBusinessException(ForwardCoreContext context, string msg, - string code = "400") + protected virtual void ThrowBusinessException(ForwardCoreContext context, string msg) { if (context.ServiceProviderRequestLog.Cost == default) { @@ -334,8 +299,7 @@ public class DefaultForwardFlow : IForwardFlow msg.Headers.Remove(item.FieldName); if (!msg.Headers.TryAddWithoutValidation(item.FieldName, item.FieldValue)) { - ThrowBusinessException(context, $"请求头无法插入 {item.FieldName}:{item.FieldValue}", - RequestServiceProviderFailedCommon.HeaderInsertFailed); + ThrowBusinessException(context, $"请求头无法插入 {item.FieldName}:{item.FieldValue}"); } } @@ -348,16 +312,14 @@ public class DefaultForwardFlow : IForwardFlow catch (TaskCanceledException ex) { // 捕获超时异常 - ThrowBusinessException(context, "请求超时," + ex.Message, - RequestServiceProviderFailedCommon.RequestFailedCode); + ThrowBusinessException(context, "请求超时," + ex.Message); } catch (HttpRequestException rex) { - ThrowBusinessException(context, "请求失败," + rex.Message, - RequestServiceProviderFailedCommon.RequestFailedCode); + ThrowBusinessException(context, "请求失败," + rex.Message); } - return default; + return default!; } protected virtual async Task RequestCoreAsync(ForwardCoreContext context, HttpRequestMessage msg) @@ -368,21 +330,18 @@ public class DefaultForwardFlow : IForwardFlow client.Timeout = TimeSpan.FromSeconds((double)context.TargetInterface.Timeout); } - var qps = context.TargetInterface.Qps <= 0 - ? context.Feature.ServiceProviderDefaultQps // 默认qps上限 - : context.TargetInterface.Qps; if (context.Feature.IsWait) { - while (!await CanVisitInterfaceAsync(context.TargetInterface.Code, qps)) + while (!await CanVisitInterfaceAsync(context)) { await Task.Delay(context.Feature.SpinPeriod); } } else { - if (!await CanVisitInterfaceAsync(context.TargetInterface.Code, qps)) + if (!await CanVisitInterfaceAsync(context)) { - throw new BusinessException($"服务商接口qps已达到上限{qps},稍后请求"); + throw new BusinessException($"服务商接口qps已达到上限{context.Feature.TargetInterfaceQps},稍后请求"); } } @@ -445,7 +404,8 @@ public class DefaultForwardFlow : IForwardFlow } else { - nameValueCollection = JsonConvert.DeserializeObject>(body); + nameValueCollection = JsonConvert.DeserializeObject>(body) ?? + new Dictionary(); } msg.Content = new FormUrlEncodedContent(nameValueCollection); @@ -485,7 +445,7 @@ public class DefaultForwardFlow : IForwardFlow case ContentType.Xml: // json转xml var jsonStr = JsonConvert.SerializeObject(body); var xmlDoc = JsonConvert.DeserializeXmlNode(jsonStr); - data = xmlDoc?.OuterXml; + data = xmlDoc?.OuterXml ?? string.Empty; break; case ContentType.FormUrlEncoded: data = JsonConvert.SerializeObject(body); //到请求时解析成Dictionary @@ -535,7 +495,7 @@ public class DefaultForwardFlow : IForwardFlow } } - item.FieldValue = context.GetAccountFieldValueOrNull(fieldName); + item.FieldValue = context.GetAccountFieldValueOrNull(fieldName) ?? string.Empty; break; case GlobalConst.Url: item.FieldValue = context.TargetInterface.RequestAddress; @@ -553,7 +513,7 @@ public class DefaultForwardFlow : IForwardFlow : (timeStamp / 1000).ToString(), // s GlobalConst.DateTime => TurnDate(now, item.Description), GlobalConst.Url => context.TargetInterface.RequestAddress, - GlobalConst.FromAccount => context.GetAccountFieldValueOrNull(item.Name), + GlobalConst.FromAccount => context.GetAccountFieldValueOrNull(item.Name) ?? string.Empty, _ => item.Value // 不处理 }; } @@ -574,21 +534,21 @@ public class DefaultForwardFlow : IForwardFlow } - string TurnDate(DateTime dateTime, string dateStyle) + string TurnDate(DateTime dateTime, string? dateStyle) { - if (!dateStyle.IsNullOrWhiteSpace()) + if (dateStyle.IsNullOrWhiteSpace()) { - try - { - return dateTime.ToString(dateStyle); - } - catch (Exception) - { - return dateTime.ToString(GlobalConst.DefaultDateStyle); - } + return dateTime.ToString(GlobalConst.DefaultDateStyle); } - return dateTime.ToString(GlobalConst.DefaultDateStyle); + try + { + return dateTime.ToString(dateStyle); + } + catch (Exception) + { + return dateTime.ToString(GlobalConst.DefaultDateStyle); + } } } @@ -651,24 +611,14 @@ public class DefaultForwardFlow : IForwardFlow } } - void VirtualizePath(List list) + async Task CanVisitInterfaceAsync(ForwardCoreContext context) { - if (list == null || list.Count == 0) return; - foreach (var item in list) - { - if (!string.IsNullOrEmpty(item.MapAlias)) - { - item.MapAlias = "[*]." + item.MapAlias; - } + string interfaceCode = context.TargetInterface.Code; + int qps = context.Feature.TargetInterfaceQps; + var hash = context.Feature.AccountHashCode; - VirtualizePath(item.Children); - } - } - - async Task CanVisitInterfaceAsync(string interfaceCode, int qps) - { // REDIS限流 - string redisKey = $"{GlobalConst.ServiceProviderInterfaceQPSKeyPrefix}:{interfaceCode}"; + string redisKey = $"{GlobalConst.ServiceProviderInterfaceQpsKeyPrefix}:{interfaceCode}:{hash}"; var num = await RedisHelper.Client.EvalAsync(""" local redisKey=ARGV[1]; local qps=ARGV[2]; @@ -685,4 +635,25 @@ public class DefaultForwardFlow : IForwardFlow """, null, redisKey, qps); return (long)num != 0; } + + static async Task ExecuteScriptAsync(ForwardCoreContext context, bool isPrefix) + { + var script = isPrefix ? context.PrefixScript : context.PostfixScript; + if (script.IsNullOrEmpty()) return; + + Debug.Assert(script != null, nameof(script) + " != null"); + var scriptHash = StringHelper.GetHash(script); + if (script.StartsWith("//async") || script.Contains("await")) + { + var @delegate = DelegateDic.GetOrAdd(scriptHash, + static (_, arg) => NDelegate.RandomDomain().AsyncFunc(arg), script); + await (Task)@delegate.DynamicInvoke(context)!; + } + else + { + var @delegate = DelegateDic.GetOrAdd(scriptHash, + static (_, arg) => NDelegate.RandomDomain().Action(arg), script); + @delegate.DynamicInvoke(context); + } + } } \ No newline at end of file diff --git a/src/InterfaceForward.Application/Helpers/CommonExtensions.cs b/src/InterfaceForward.Application/Helpers/CommonExtensions.cs index 58f7a90..802ceea 100644 --- a/src/InterfaceForward.Application/Helpers/CommonExtensions.cs +++ b/src/InterfaceForward.Application/Helpers/CommonExtensions.cs @@ -4,8 +4,6 @@ namespace InterfaceForward.Application.Helpers; public static class CommonExtensions { - - public static async Task GetAllBytesAsync(this IFormFile file) { using (var stream = file.OpenReadStream()) diff --git a/src/InterfaceForward.Application/Helpers/StringHelper.cs b/src/InterfaceForward.Application/Helpers/StringHelper.cs new file mode 100644 index 0000000..b99243d --- /dev/null +++ b/src/InterfaceForward.Application/Helpers/StringHelper.cs @@ -0,0 +1,51 @@ +using System.Security.Cryptography; +using System.Text; +using System.Text.RegularExpressions; + +namespace InterfaceForward.Application.Helpers; + +public class StringHelper +{ + /// + /// 只能输入英文字符和键盘上常用的符号 + /// + /// + /// + public static bool IsValidEnglishString(string input) + { + if (string.IsNullOrEmpty(input)) + { + return false; + } + + // Regular expression to match only English letters and specified symbols + var regex = new Regex(@"^[a-zA-Z\(\)@!#$%^&*_\-+=;:""'/><,.\s]+$"); + return regex.IsMatch(input); + } + + + /// 获取字符串的hash值 + /// + /// + public static string GetHash(string str) + { + if (string.IsNullOrEmpty(str)) return string.Empty; + + // 将字符串转换为字节 + byte[] data = Encoding.UTF8.GetBytes(str); + + // 创建SHA256实例 + using SHA256 sha256 = SHA256.Create(); + // 计算哈希值 + byte[] hash = sha256.ComputeHash(data); + + // 将哈希值转换为16进制字符串 + StringBuilder sb = new StringBuilder(); + foreach (byte b in hash) + { + sb.Append(b.ToString("x2")); + } + + return sb.ToString(); + } +} \ No newline at end of file diff --git a/src/InterfaceForward.Application/HostServices/AppSubscribeEvent.cs b/src/InterfaceForward.Application/HostServices/AppSubscribeEvent.cs new file mode 100644 index 0000000..827e052 --- /dev/null +++ b/src/InterfaceForward.Application/HostServices/AppSubscribeEvent.cs @@ -0,0 +1,41 @@ +using InterfaceForward.Domain.Shared.Enum; + +namespace InterfaceForward.Application.HostServices; + +public class AppSubscribeEvent +{ + /// + /// 操作类型 + /// + public OptionType OptionType { get; set; } + + /// + /// 应用key + /// + public string AppKey { get; set; } + + /// + /// 系统接口code + /// + public string SystemInterfaceCode { get; set; } + + /// + /// 服务商code + /// + public string ServiceProviderCode { get; set; } + + /// + /// 【慎重!】mq预取数量(服务并发度,给0就是默认4) + /// + public ushort FetchCount { get; set; } + + /// + /// 【慎重!】失败重回队列(一般false,即接口通不做重试,否则业务错误可能死循环) + /// + public bool FailedRequeue { get; set; } + + /// + /// 【慎重!】删除队列(删除专用,一般false,即接口通只解除绑定,不删除队列) + /// + public bool IsDeleteQueue { get; set; } +} \ No newline at end of file diff --git a/src/InterfaceForward.Application/HostServices/AppSubscribeEventHandler.cs b/src/InterfaceForward.Application/HostServices/AppSubscribeEventHandler.cs new file mode 100644 index 0000000..f3934cb --- /dev/null +++ b/src/InterfaceForward.Application/HostServices/AppSubscribeEventHandler.cs @@ -0,0 +1,95 @@ +using System.Net; +using Fake.DependencyInjection; +using InterfaceForward.Application.Rabbit; +using InterfaceForward.Domain.Shared; +using InterfaceForward.Domain.Shared.Enum; +using Microsoft.IdentityModel.Logging; +using RabbitMQ.Client; + +namespace InterfaceForward.Application.HostServices; + +public class AppSubscribeEventHandler : ITransientDependency +{ + public const string AppSubscribeEventSubscribe = "sjzy.interface.relay.app.subscribe"; + private readonly RabbitClient _rabbitClient; + + public AppSubscribeEventHandler(RabbitClient rabbitClient) + { + _rabbitClient = rabbitClient; + } + + public Task Handle(AppSubscribeEvent @event) + { + switch (@event.OptionType) + { + case OptionType.无: + break; + case OptionType.新增: + var res = DoAdd(@event); + LogHelper.Info($"{Dns.GetHostName()}\n{@event.ToJson()}\n{res}", true); + break; + case OptionType.修改: + var res1 = DoUpdate(@event); + LogHelper.Info($"{Dns.GetHostName()}\n{@event.ToJson()}\n{res1}", true); + break; + case OptionType.删除: + var res2 = DoDelete(@event); + LogHelper.Info($"{Dns.GetHostName()}\n{@event.ToJson()}\n{res2}", true); + break; + default: + throw new ArgumentOutOfRangeException(); + } + + return Task.CompletedTask; + } + + private string DoUpdate(AppSubscribeEvent @event) + { + var queueName = + $"{BatchForwardEventHandler.BatchForwardEventSubscribe}.{@event.AppKey}.{@event.SystemInterfaceCode}.{@event.ServiceProviderCode}"; + _rabbitClient.UnSubscribe(queueName, GlobalConst.InterfaceRelayExchange, queueName, @event.IsDeleteQueue); + + _rabbitClient.Subscribe(new ConsumeOptions + { + Queue = queueName, + FetchCount = @event.FetchCount, // 并发控制 + FailedRequeue = @event.FailedRequeue, + Declaration = o => + { + o.ExchangeDeclare(GlobalConst.InterfaceRelayExchange, ExchangeType.Direct, true); + o.QueueDeclare(queueName, true); + o.QueueBind(queueName, GlobalConst.InterfaceRelayExchange, queueName); + } + }); + + return $"已成功更新队列:{queueName}的消费者参数,可以通过/AppSubscribe/GetList查询所有订阅"; + } + + private string DoDelete(AppSubscribeEvent @event) + { + var queueName = + $"{BatchForwardEventHandler.BatchForwardEventSubscribe}.{@event.AppKey}.{@event.SystemInterfaceCode}.{@event.ServiceProviderCode}"; + _rabbitClient.UnSubscribe(queueName, GlobalConst.InterfaceRelayExchange, queueName, @event.IsDeleteQueue); + return $"已成功{(@event.IsDeleteQueue ? "删除" : "解绑")}队列:{queueName},可以通过/AppSubscribe/GetList查询所有订阅"; + } + + private string DoAdd(AppSubscribeEvent @event) + { + var queueName = + $"{BatchForwardEventHandler.BatchForwardEventSubscribe}.{@event.AppKey}.{@event.SystemInterfaceCode}.{@event.ServiceProviderCode}"; + _rabbitClient.Subscribe(new ConsumeOptions + { + Queue = queueName, + FetchCount = @event.FetchCount, // 并发控制 + FailedRequeue = @event.FailedRequeue, + Declaration = o => + { + o.ExchangeDeclare(GlobalConst.InterfaceRelayExchange, ExchangeType.Direct, true); + o.QueueDeclare(queueName, true); + o.QueueBind(queueName, GlobalConst.InterfaceRelayExchange, queueName); + } + }); + + return $"已成功绑定队列:{queueName},可以通过/AppSubscribe/GetList查询所有订阅"; + } +} \ No newline at end of file diff --git a/src/InterfaceForward.Application/HostServices/AppSubscribeHostService.cs b/src/InterfaceForward.Application/HostServices/AppSubscribeHostService.cs new file mode 100644 index 0000000..6f71358 --- /dev/null +++ b/src/InterfaceForward.Application/HostServices/AppSubscribeHostService.cs @@ -0,0 +1,69 @@ +using Fake.Helpers; +using InterfaceForward.Application.Helpers; +using InterfaceForward.Application.Rabbit; +using InterfaceForward.Domain.Shared; +using InterfaceForward.Repositories; +using Microsoft.Extensions.Hosting; +using Microsoft.IdentityModel.Logging; +using Newtonsoft.Json; +using RabbitMQ.Client; + +namespace InterfaceForward.Application.HostServices; + +public class AppSubscribeHostService : IHostedService +{ + private readonly AppSubscribeEventHandler _appSubscribeEventHandler; + private readonly RabbitClient _rabbitClient; + + public AppSubscribeHostService(IBasicRepository appSubscribeConfigRepository, + RabbitClient rabbitClient, AppSubscribeEventHandler appSubscribeEventHandler) + { + _appSubscribeConfigRepository = appSubscribeConfigRepository; + _rabbitClient = rabbitClient; + _appSubscribeEventHandler = appSubscribeEventHandler; + } + + public async Task StartAsync(CancellationToken cancellationToken) + { + // todo:redis注册连接恢复订阅 + RedisHelper.Client.Subscribe(AppSubscribeEventHandler.AppSubscribeEventSubscribe, AppSubScribe); + + // 根据应用订阅表,绑定消费者 + var subscribes = await _appSubscribeConfigRepository.GetListAsync(x => true); + foreach (var subscribe in subscribes) + { + var queueName = $"{BatchForwardEventHandler.BatchForwardEventSubscribe}.{subscribe.AppKey}.{subscribe + .SystemInterfaceCode}.{subscribe.ServiceProviderCode}"; + _rabbitClient.Subscribe(new ConsumeOptions + { + Queue = queueName, + FetchCount = subscribe.FetchCount, // 并发控制 + FailedRequeue = subscribe.FailedRequeue, + Declaration = o => + { + o.ExchangeDeclare(GlobalConst.InterfaceRelayExchange, ExchangeType.Direct, true); + o.QueueDeclare(queueName, true); + o.QueueBind(queueName, GlobalConst.InterfaceRelayExchange, queueName); + } + }); + } + } + + public Task StopAsync(CancellationToken cancellationToken) + { + RedisHelper.Client.UnSubscribe(AppSubscribeEventHandler.AppSubscribeEventSubscribe); + return Task.CompletedTask; + } + + public void AppSubScribe(string chan, object msg) + { +#if DEBUG + LogHelper.Info($"接受到来自redis的订阅:{chan}\n{msg.ToJson()}", true); +#endif + if (chan == AppSubscribeEventHandler.AppSubscribeEventSubscribe && msg is string str) + { + var @event = JsonConvert.DeserializeObject(str); + AsyncHelper.RunSync(() => _appSubscribeEventHandler.Handle(@event)); + } + } +} \ No newline at end of file diff --git a/src/InterfaceForward.Application/InterfaceForward.Application.csproj b/src/InterfaceForward.Application/InterfaceForward.Application.csproj index 48745e6..e8ef809 100644 --- a/src/InterfaceForward.Application/InterfaceForward.Application.csproj +++ b/src/InterfaceForward.Application/InterfaceForward.Application.csproj @@ -18,6 +18,7 @@ + diff --git a/src/InterfaceForward.Application/InterfaceForwardApplicationModule.cs b/src/InterfaceForward.Application/InterfaceForwardApplicationModule.cs index 7c38515..d8595b7 100644 --- a/src/InterfaceForward.Application/InterfaceForwardApplicationModule.cs +++ b/src/InterfaceForward.Application/InterfaceForwardApplicationModule.cs @@ -1,5 +1,6 @@ using System.Security.Authentication; using Fake.AspNetCore; +using Fake.EventBus.RabbitMQ; using Fake.Modularity; using Fake.ObjectMapping.AutoMapper; using InterfaceForward.Application.Contracts; @@ -10,6 +11,7 @@ namespace InterfaceForward.Application; [DependsOn(typeof(FakeAspNetCoreModule))] [DependsOn(typeof(FakeObjectMappingAutoMapperModule))] +[DependsOn(typeof(FakeEventBusRabbitMqModule))] [DependsOn(typeof(InterfaceForwardRepositoriesModule))] public class InterfaceForwardApplicationModule:FakeModule { diff --git a/src/InterfaceForward.Application/Rabbit/BatchForwardEvent.cs b/src/InterfaceForward.Application/Rabbit/BatchForwardEvent.cs new file mode 100644 index 0000000..3516c0a --- /dev/null +++ b/src/InterfaceForward.Application/Rabbit/BatchForwardEvent.cs @@ -0,0 +1,20 @@ +namespace InterfaceForward.Application.Rabbit; + +public class BatchForwardEvent +{ + public Guid RequestId { get; set; } + + public int AppId { get; set; } + + public string UpStreamCode { get; set; } + + public string ServiceProviderCode { get; set; } + + public string Attach { get; set; } + + public string ResSubscribeName { get; set; } + + public bool IsReturnMessage { get; set; } + + public string Data { get; set; } +} \ No newline at end of file diff --git a/src/InterfaceForward.Application/Rabbit/BatchForwardEventHandler.cs b/src/InterfaceForward.Application/Rabbit/BatchForwardEventHandler.cs new file mode 100644 index 0000000..fed2d28 --- /dev/null +++ b/src/InterfaceForward.Application/Rabbit/BatchForwardEventHandler.cs @@ -0,0 +1,96 @@ +using System.Text; +using Fake.DependencyInjection; +using InterfaceForward.Application.Contracts.ForwardCore; +using InterfaceForward.Application.Services; +using InterfaceForward.Domain.Shared; +using InterfaceForward.Domain.Shared.FeiShu; +using InterfaceForward.Repositories.Log.Services; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using SJZY.InterfaceRelay.Application.Contracts; + +namespace InterfaceForward.Application.Rabbit; + +public class BatchForwardEventHandler( + InterfaceForwardCommon forwardCommon, + RabbitClient rabbitClient, + ILogRepository logRepository, + ILogger logger, + IFeiShuNoticer feiShuNoticer) + : ITransientDependency, IRabbitHandler +{ + public const string BatchForwardEventSubscribe = "sjzy.interface.relay.batch"; + private readonly RabbitClient _rabbitClient = rabbitClient; + + public bool Enable(ConsumeOptions options) + { + return options.Queue.StartsWith(BatchForwardEventSubscribe); + } + + public async Task Handle(IServiceProvider sp, BasicDeliverEventArgs args, ConsumeOptions options) + { + var msg = Encoding.UTF8.GetString(args.Body.ToArray()); + logger.LogDebug($"rabbit on queue({options.Queue}) received msg"); + + var @event = System.Text.Json.JsonSerializer.Deserialize(msg); + + // important:恢复应用现场 全局贯彻 + logRepository.AppId = @event.AppId; + logRepository.AppRequestLog.RequestId = @event.RequestId; + + var contextList = await forwardCommon.VerifyBusinessAndBuildForwardContexts(@event.UpStreamCode, + @event.ServiceProviderCode); + + // 深拷贝解决多线程上下文数据干扰问题 + foreach (var item in contextList) + { + item.ServiceProviderRequestLog.RequestId = @event.RequestId; + item.Feature.IsWait = true; + } + + await HandleReplayAsync(contextList, @event); + } + + private async Task HandleReplayAsync(List contextList, BatchForwardEvent @event) + { + var req = JToken.Parse(@event.Data); + + var context = contextList.First(); + + var replayEvent = new InterfaceRelayForwardReplayEvent + { + RequestId = context.ServiceProviderRequestLog.RequestId, + RequestParams = JsonConvert.SerializeObject(req), + Attach = @event.Attach, + Message = "请求成功" + }; + try + { + context.OriginalInterfaceInput = contextList.Count == 1 + ? req + : new JObject + { + [@event.UpStreamCode!] = req + }; + + var res = contextList.Count == 1 + ? await forwardCommon.InternalForwardAsync(context) + : await forwardCommon.InternalForward2Async(contextList); + + replayEvent.OriginalMessage = @event.IsReturnMessage ? logRepository.ServiceProviderRequestLog.Response : null; + replayEvent.Data = JsonConvert.SerializeObject(res); // 解决cap序列化object问题 + replayEvent.IsSuccess = true; + } + catch (Exception ex) + { + replayEvent.Message = ex.Message; + await feiShuNoticer.NoticeAsync(ex.ToString()); + } + finally + { + var data = System.Text.Json.JsonSerializer.SerializeToUtf8Bytes(replayEvent); + _rabbitClient.Publish(GlobalConst.InterfaceRelayExchange, @event.ResSubscribeName, data); + } + } +} \ No newline at end of file diff --git a/src/InterfaceForward.Application/Rabbit/RabbitClient.cs b/src/InterfaceForward.Application/Rabbit/RabbitClient.cs new file mode 100644 index 0000000..6913ab7 --- /dev/null +++ b/src/InterfaceForward.Application/Rabbit/RabbitClient.cs @@ -0,0 +1,32 @@ +using Fake.RabbitMQ; + +namespace InterfaceForward.Application.Rabbit; + +public class RabbitClient(IRabbitMqConnector connector) +{ + public void Subscribe(ConsumeOptions consumeOptions) + { + using var channel = connector.CreateChannel(); + var consumer = new EventingBasicConsumer(channel); + consumer.Received += (model, ea) => + { + var body = ea.Body.ToArray(); + var message = Encoding.UTF8.GetString(body); + Console.WriteLine(" [x] Received {0}", message); + }; + channel.BasicConsume(queue: consumeOptions.Queue, + autoAck: true, + consumer: consumer); + } +} + +public class ConsumeOptions +{ + public string Queue { get; set; } + + public ushort FetchCount { get; set; } + + public bool FailedRequeue { get; set; } + + public Action +} \ No newline at end of file diff --git a/src/InterfaceForward.Application/Services/InterfaceForwardCommon.cs b/src/InterfaceForward.Application/Services/InterfaceForwardCommon.cs index 607395a..43de9bf 100644 --- a/src/InterfaceForward.Application/Services/InterfaceForwardCommon.cs +++ b/src/InterfaceForward.Application/Services/InterfaceForwardCommon.cs @@ -1,4 +1,7 @@ -using System.Runtime.ExceptionServices; +using System.Diagnostics; +using System.Runtime.ExceptionServices; +using System.Security.Cryptography; +using System.Text; using Fake.DependencyInjection; using InterfaceForward.Application.Contracts; using InterfaceForward.Application.Contracts.ForwardCore; @@ -16,93 +19,85 @@ using SJZY.InterfaceRelay.Repository.Log.ValueObjects; namespace InterfaceForward.Application.Services; -public class InterfaceForwardCommon : ITransientDependency +public class InterfaceForwardCommon( + InterfaceForwardQuery interfaceForwardQuery, + ILogRepository logRepository, + IServiceProvider serviceProvider, + IOptionsSnapshot options) + : ITransientDependency { /// - /// 上下文缓存数据:映射,账户 + /// 缓存数据:映射,账户 默认过期时间 /// - public static readonly int ContextCacheTtl = int.MaxValue; + public static readonly int DefaultCacheTtl = int.MaxValue; private readonly Dictionary _flows = new(); - private readonly InterfaceForwardQuery _interfaceForwardQuery; - private readonly InterfaceRelayOptions _interfaceRelayOptions; - private readonly ILogRepository _logRepository; - // private readonly IEntrySegmentContextAccessor _segContext; - private readonly IServiceProvider _serviceProvider; - - public InterfaceForwardCommon(InterfaceForwardQuery interfaceForwardQuery - , ILogRepository logRepository - // , IEntrySegmentContextAccessor segContext - , IServiceProvider serviceProvider - , IOptions options) - { - _interfaceForwardQuery = interfaceForwardQuery; - _logRepository = logRepository; - // _segContext = segContext; - _serviceProvider = serviceProvider; - _interfaceRelayOptions = options.Value; - } + private readonly InterfaceRelayOptions _options = options.Value; public async Task> VerifyBusinessAndBuildForwardContexts(string upStreamCode, string serviceProviderCode) { + // todo:字符串优化 // tips:上下文的缓存不维护,在发布时统一替代。 var contextCacheKey = GetContextCacheKey(upStreamCode, serviceProviderCode); - // _segContext.Context.Span.AddLog(LogEvent.Message("【转发核心上下文】开始构建")); var cacheContext = (await RedisHelper.Client.GetAsync(contextCacheKey)) .ToObject(); if (cacheContext == null) { - // _segContext.Context.Span.AddLog(LogEvent.Message("【转发核心上下文】没能命中缓存,重新构建")); - - var systemInterface = await _interfaceForwardQuery.GetInterfaceByCodeAsync(upStreamCode); + var systemInterface = await interfaceForwardQuery.GetInterfaceByCodeAsync(upStreamCode); if (systemInterface == null) throw new BusinessException(message: "系统接口不存在"); - _logRepository.AppRequestLog.InterfaceCode = systemInterface.Code; - _logRepository.AppRequestLog.InterfaceName = systemInterface.Name; + logRepository.AppRequestLog.InterfaceCode = systemInterface.Code; + logRepository.AppRequestLog.InterfaceName = systemInterface.Name; - var serviceProvider = await _interfaceForwardQuery.GetServiceProviderByCodeAsync(serviceProviderCode); - if (serviceProvider == null) throw new BusinessException(message: "服务商不存在"); - cacheContext = await _interfaceForwardQuery.BuildForwardCoreContext(systemInterface, serviceProvider); - await RedisHelper.Client.SetAsync(contextCacheKey, cacheContext.ToJson(), ContextCacheTtl); + var serviceProviderDto = await interfaceForwardQuery.GetServiceProviderByCodeAsync(serviceProviderCode); + if (serviceProviderDto == null) throw new BusinessException(message: "服务商不存在"); + cacheContext = await interfaceForwardQuery.BuildForwardCoreContext(systemInterface, serviceProviderDto); + await RedisHelper.Client.SetAsync(contextCacheKey, cacheContext.ToJson(), DefaultCacheTtl); } - // _segContext.Context.Span.AddLog(LogEvent.Message("【转发核心上下文】构建完成")); // 记录系统接口日志 - _logRepository.AppRequestLog.InterfaceCode = cacheContext.OriginalInterface.Code; - _logRepository.AppRequestLog.InterfaceName = cacheContext.OriginalInterface.Name; + logRepository.AppRequestLog.InterfaceCode = cacheContext.OriginalInterface.Code; + logRepository.AppRequestLog.InterfaceName = cacheContext.OriginalInterface.Name; // tips:服务商主体账户的缓存实时维护 - // _segContext.Context.Span.AddLog(LogEvent.Message("【服务商应用账户】开始构建")); - var accountCacheKey = GetAccountCacheKey(serviceProviderCode, _logRepository.AppId); + var accountCacheKey = GetAccountCacheKey(serviceProviderCode, logRepository.AppId); var accountFieldCache = (await RedisHelper.Client.GetAsync(accountCacheKey)) .ToObject>(); if (accountFieldCache == null) { - // _segContext.Context.Span.AddLog(LogEvent.Message("【服务商账户】没能命中缓存,重新构建")); - var serviceProvider = cacheContext.TargetServiceProvider; - var accountId = await _interfaceForwardQuery.GetAccountIdAsync(serviceProvider.Id, _logRepository.AppId); + var serviceProviderDto = cacheContext.TargetServiceProvider; + var accountId = await interfaceForwardQuery.GetAccountIdAsync(serviceProviderDto.Id, logRepository.AppId); // 服务商授权 - if (!serviceProvider.IsDisableAuth && accountId == default) + if (!serviceProviderDto.IsDisableAuth && accountId == default) { - throw new BusinessException(message: $"服务商:{serviceProvider.Name} 下不存在此账户,请联系管理员或前往接口通创建"); + throw new BusinessException(message: $"服务商:{serviceProviderDto.Name} 下不存在此账户,请联系管理员或前往接口通创建"); } - accountFieldCache = await _interfaceForwardQuery.GetAccountFieldListAsync(accountId); + accountFieldCache = await interfaceForwardQuery.GetAccountFieldListAsync(accountId); + accountFieldCache.Add(new ServiceProviderAccountFieldWithValueDto + { + FieldName = GlobalConst.AccountHashCode, + // 根据字段值计算hash,请求时作为qps key依据,即相同的账户会共享一个qps + FieldValue = GetHash(accountFieldCache.Select(x => x.FieldValue).JoinAsString("_")) + }); var ttl = await HandleAuthInterfaceAsync(cacheContext, accountFieldCache); await RedisHelper.Client.SetAsync(accountCacheKey, accountFieldCache.ToJson(), ttl); } - // _segContext.Context.Span.AddLog(LogEvent.Message("【服务商账户】构建完成")); - // 组装转发上下文 var res = new List(); foreach (var item in cacheContext.TargetInterfaces) { item.TargetInterface.Timeout ??= cacheContext.TargetServiceProvider.Timeout; + item.TargetInterface.FlowCode = item.TargetInterface.FlowCode.IsNullOrWhiteSpace() + ? cacheContext.TargetServiceProvider.FlowCode.IsNullOrWhiteSpace() + ? nameof(DefaultForwardFlow) + : cacheContext.TargetServiceProvider.FlowCode + : item.TargetInterface.FlowCode; var context = new ForwardCoreContext { TargetInterface = item.TargetInterface, @@ -117,42 +112,37 @@ public class InterfaceForwardCommon : ITransientDependency var token = context.FixedFieldList.FirstOrDefault(x => x.FieldValue == GlobalConst.Token); if (token != default) { - token.FieldValue = context.GetAccountFieldValueOrNull(GlobalConst.Token); + token.FieldValue = context.GetAccountFieldValueOrNull(GlobalConst.Token)!; } - context.ServiceProviderRequestLog = new RequestLogDto(_logRepository.AppRequestLog.RequestId); + context.Feature.AccountHashCode = context.GetAccountFieldValueOrNull(GlobalConst.AccountHashCode)!; + + context.ServiceProviderRequestLog = new RequestLogDto(logRepository.AppRequestLog.RequestId); context.ServiceProviderRequestLog.Name = cacheContext.TargetServiceProvider.Name; context.ServiceProviderRequestLog.Address = context.TargetInterface.RequestAddress; context.ServiceProviderRequestLog.InterfaceCode = context.TargetInterface.Code; context.ServiceProviderRequestLog.InterfaceName = context.TargetInterface.Name; - var flow = GetForwardFlow(context.TargetInterface.FlowCode, cacheContext.TargetServiceProvider.FlowCode); + Debug.Assert(context.TargetInterface.FlowCode != null, "context.TargetInterface.FlowCode != null"); + var flow = GetForwardFlow(context.TargetInterface.FlowCode); if (flow == default) throw new BusinessException(message: "找不到接口转发流程" + context.TargetInterface.FlowCode); _flows.Add(context.TargetInterface.Code, flow); - res.Add(context); } // tips:把出参映射树放到最后一个上下文中,是为了在流程结束时执行出参映射 res.Last().OutParamTreeList = cacheContext.OutParamTreeList; - // _segContext.Context.Span.AddLog(LogEvent.Message("【转发上下文】全部组装完成")); - return res; } // 优先用服务商接口指定流程,其次使用服务商的,再其次是默认的 - public IForwardFlow GetForwardFlow(string interfaceFlowCode, string serviceProviderFlowCode) + public IForwardFlow GetForwardFlow(string flowCode) { - var flowCode = interfaceFlowCode.IsNullOrWhiteSpace() - ? serviceProviderFlowCode.IsNullOrWhiteSpace() - ? nameof(DefaultForwardFlow) - : serviceProviderFlowCode - : interfaceFlowCode; - return _serviceProvider.GetServices() - .FirstOrDefault(x => x.GetType().Name == flowCode); + return serviceProvider.GetServices() + .FirstOrDefault(x => x.GetType().Name == flowCode)?? throw new BusinessException(""); } /// @@ -170,7 +160,7 @@ public class InterfaceForwardCommon : ITransientDependency if (interfaceDto == default) { - return ContextCacheTtl; + return DefaultCacheTtl; } var configs = cacheContext.ServiceProviderAuthConfigs; @@ -179,10 +169,16 @@ public class InterfaceForwardCommon : ITransientDependency var body = new JObject(); foreach (var item in configs.Where(x => x.Flag == AuthParameterFlag.InParameter)) { + Debug.Assert(item.ParameterAlias != null, "item.ParameterAlias != null"); body[item.ParameterAlias] = item.Value1; } // 构建授权上下文 + interfaceDto.TargetInterface.FlowCode = interfaceDto.TargetInterface.FlowCode.IsNullOrWhiteSpace() + ? cacheContext.TargetServiceProvider.FlowCode.IsNullOrWhiteSpace() + ? nameof(DefaultForwardFlow) + : cacheContext.TargetServiceProvider.FlowCode + : interfaceDto.TargetInterface.FlowCode; var authContext = new ForwardCoreContext { TargetInterface = interfaceDto.TargetInterface, @@ -191,12 +187,12 @@ public class InterfaceForwardCommon : ITransientDependency ReturnConfigList = interfaceDto.ReturnConfigList, InParamTreeList = null, OutParamTreeList = null, - PrefixScript = null, - PostfixScript = null, + PrefixScript = interfaceDto.PrefixScript, + PostfixScript = interfaceDto.PostfixScript, OriginalInterfaceInput = body, FormFieldList = interfaceDto.FormFieldList // normal case is null }; - authContext.ServiceProviderRequestLog = new RequestLogDto(_logRepository.AppRequestLog.RequestId) + authContext.ServiceProviderRequestLog = new RequestLogDto(logRepository.AppRequestLog.RequestId) { Name = cacheContext.TargetServiceProvider.Name, Address = authContext.TargetInterface.RequestAddress, @@ -204,22 +200,16 @@ public class InterfaceForwardCommon : ITransientDependency InterfaceName = authContext.TargetInterface.Name }; - // 走动态脚本 - var scriptConfig = configs.FirstOrDefault(x => x.Flag == AuthParameterFlag.Script); - if (scriptConfig != default && !scriptConfig.Value1.IsNullOrEmpty()) - { - // 使用Natasha构建动态表达式 - return NDelegate.RandomDomain().Func(scriptConfig.Value1)(authContext); - } - // 授权也走主流程 if (!cacheContext.TargetServiceProvider.AuthInterfaceCode.IsNullOrWhiteSpace()) { - var flow = GetForwardFlow(interfaceDto.TargetInterface.FlowCode, - cacheContext.TargetServiceProvider.FlowCode); + Debug.Assert(cacheContext.ServiceProviderAuthInterface != null, "cacheContext.ServiceProviderAuthInterface != null"); + var flow = GetForwardFlow(cacheContext.ServiceProviderAuthInterface.TargetInterface.FlowCode!); if (flow == default) - throw new BusinessException(message: "找不到接口转发流程" + interfaceDto.TargetInterface.FlowCode); - _flows.Add(interfaceDto.TargetInterface.Code, flow); + throw new BusinessException(message: "找不到接口转发流程" + + cacheContext.ServiceProviderAuthInterface.TargetInterface + .FlowCode); + _flows.Add(cacheContext.ServiceProviderAuthInterface.TargetInterface.Code, flow); } // 请求授权接口 @@ -229,12 +219,14 @@ public class InterfaceForwardCommon : ITransientDependency var tokenConfig = configs.FirstOrDefault(x => x.Flag == AuthParameterFlag.Token); if (tokenConfig != default) { + Debug.Assert(tokenConfig.ParameterAlias != null, "tokenConfig.ParameterAlias != null"); accountFieldCache.Add(new ServiceProviderAccountFieldWithValueDto { FieldName = GlobalConst.Token, FieldValue = tokenConfig.Value1.IsNullOrWhiteSpace() - ? response[tokenConfig.ParameterAlias]?.ToString() - : tokenConfig.Value1.Trim() + " " + response[tokenConfig.ParameterAlias] + ? response.SelectToken(tokenConfig.ParameterAlias)?.ToString() ?? + throw new BusinessException($"授权接口返回报文中未找到{tokenConfig.ParameterAlias}") + : tokenConfig.Value1!.Trim() + " " + response.SelectToken(tokenConfig.ParameterAlias) }); } @@ -247,8 +239,10 @@ public class InterfaceForwardCommon : ITransientDependency return ttl; } - if (int.TryParse(response[effectTimeConfig.ParameterAlias]?.ToString(), out var expiredValue)) + Debug.Assert(effectTimeConfig.ParameterAlias != null, "effectTimeConfig.ParameterAlias != null"); + if (int.TryParse(response.SelectToken(effectTimeConfig.ParameterAlias)?.ToString(), out var expiredValue)) { + Debug.Assert(effectTimeConfig.Value1 != null, "effectTimeConfig.Value1 != null"); ttl = Enum.Parse(effectTimeConfig.Value1) switch { EffectTimeUnit.Second => expiredValue, @@ -357,20 +351,23 @@ public class InterfaceForwardCommon : ITransientDependency { try { + context.Feature.TargetInterfaceQps = context.TargetInterface.Qps <= 0 + ? _options.ServiceProviderDefaultQps // 默认qps上限 + : context.TargetInterface.Qps; + context.Feature.SpinPeriod = _options.SpinPeriod; // 异步自旋周期 + var flow = _flows[context.TargetInterface.Code]; - // _segContext.Context.Span.AddLog(LogEvent.Message("开始入参重构")); + context.OriginalInterfaceMappedInput = await flow.InParamRestructure(context); - // _segContext.Context.Span.AddLog(LogEvent.Message("* 开始请求")); - context.Feature.ServiceProviderDefaultQps = _interfaceRelayOptions.ServiceProviderDefaultQps; - context.Feature.SpinPeriod = _interfaceRelayOptions.SpinPeriod; var response = await flow.ForwardAsync(context); - // _segContext.Context.Span.AddLog(LogEvent.Message("* 请求结束")); if (isMulti) return response; // 一对多不在这映射 var res = await flow.OutParamRestructure(context, response); - - // _segContext.Context.Span.AddLog(LogEvent.Message("完成反参重构")); + if (context.Feature.IsWait) + { + context.ServiceProviderRequestLog.Response2 = res.ToJson(); + } return res; } @@ -384,10 +381,15 @@ public class InterfaceForwardCommon : ITransientDependency } finally { + if (context.ServiceProviderRequestLog.RequestTime == default) + { + context.ServiceProviderRequestLog.RequestTime = DateTime.Now; + } + // 关联当前服务商日志 - _logRepository.ServiceProviderRequestLog = context.ServiceProviderRequestLog; + logRepository.ServiceProviderRequestLog = context.ServiceProviderRequestLog; // 写服务商日志 - await _logRepository.AddLogAsync(context.ServiceProviderRequestLog); + await logRepository.AddLogAsync(context.ServiceProviderRequestLog); } } @@ -416,4 +418,27 @@ public class InterfaceForwardCommon : ITransientDependency return objectArray; } + + /// 获取字符串的hash值 + /// + /// + public static string GetHash(string str) + { + // 将字符串转换为字节 + byte[] data = Encoding.UTF8.GetBytes(str); + + // 创建SHA256实例 + using SHA256 sha256 = SHA256.Create(); + // 计算哈希值 + byte[] hash = sha256.ComputeHash(data); + + // 将哈希值转换为16进制字符串 + StringBuilder sb = new StringBuilder(); + foreach (byte b in hash) + { + sb.Append(b.ToString("x2")); + } + + return sb.ToString(); + } } \ No newline at end of file diff --git a/src/InterfaceForward.Application/Services/InterfaceForwardService.cs b/src/InterfaceForward.Application/Services/InterfaceForwardService.cs index 8bd6612..7635852 100644 --- a/src/InterfaceForward.Application/Services/InterfaceForwardService.cs +++ b/src/InterfaceForward.Application/Services/InterfaceForwardService.cs @@ -5,8 +5,11 @@ using Newtonsoft.Json.Linq; using InterfaceForward.Application.Contracts; using InterfaceForward.Application.Contracts.ForwardCore; using InterfaceForward.Application.Filters; +using InterfaceForward.Application.Rabbit; +using InterfaceForward.Domain.Shared; using InterfaceForward.Repositories.Log.Services; using Microsoft.AspNetCore.Mvc; +using RabbitMQ.Client; namespace InterfaceForward.Application.Services; @@ -21,29 +24,100 @@ public class InterfaceForwardService : BaseService, IInterfaceForwardService { private readonly InterfaceForwardCommon _forwardCommon; private readonly ILogRepository _logRepository; - private readonly InterfaceRelayOptions _options; + private readonly RabbitClient _rabbitClient; /// - public InterfaceForwardService( - ILogRepository logRepository - , InterfaceForwardCommon forwardCommon - , IOptionsSnapshot options - ) + public InterfaceForwardService(ILogRepository logRepository, + InterfaceForwardCommon forwardCommon, RabbitClient rabbitClient) { _logRepository = logRepository; _forwardCommon = forwardCommon; - _options = options.Value; + _rabbitClient = rabbitClient; } + /// + [HttpPost("BatchForwardThenPushToQueue")] + public async Task BatchForwardThenPushToQueueAsync( + [FromQuery] [Required] string upStreamCode, + [FromQuery] [Required] string serviceProviderCode, + [FromBody] [Required] List data, + [FromHeader] [Required] string resSubscribeName, + [FromHeader] string attach = null, + [FromHeader] bool isReturnMessage = false) + { + var contextList = await _forwardCommon.VerifyBusinessAndBuildForwardContexts(upStreamCode, + serviceProviderCode); + + var @event = new BatchForwardEvent + { + RequestId = _logRepository.AppRequestLog.RequestId, + AppId = _logRepository.AppId, + UpStreamCode = upStreamCode, + ServiceProviderCode = serviceProviderCode, + Attach = attach, + IsReturnMessage = isReturnMessage, + ResSubscribeName = resSubscribeName + }; + + var queueName = $"{BatchForwardEventHandler.BatchForwardEventSubscribe}.{SJZYABPCore.HttpContext.Request + .Headers["appKey"]}.{upStreamCode}.{serviceProviderCode}"; + + // tips:订阅需要持久化 + _rabbitClient.Subscribe(new ConsumeOptions + { + Queue = queueName, + FetchCount = AppSubscribeService.DefaultFetchCount, // 并发控制 + FailedRequeue = AppSubscribeService.DefaultFailedRequeue, + Declaration = o => + { + o.ExchangeDeclare(GlobalConst.InterfaceRelayExchange, ExchangeType.Direct, true); + o.QueueDeclare(queueName, true); + o.QueueBind(queueName, GlobalConst.InterfaceRelayExchange, queueName); + } + }); + + // 手工切割任务数量 + var chunk = data.Count; + var minLimit = contextList.Min(x => x.TargetInterface.Limit); + var limit = minLimit > 0 ? minLimit : 1; + if (limit > 1) + { + chunk /= limit; + if (data.Count % limit != 0) chunk++; + } + + for (int x = 0; x < chunk; x++) + { + var arr = new JArray(); + for (int i = 0; i < limit && x * limit + i < data.Count; i++) + { + arr.Add(data[x * limit + i]); + } + + var subEvent = @event.DeepCopy(); + + subEvent.Data = arr.ToJson(); // always arr + var eventBytes = System.Text.Json.JsonSerializer.SerializeToUtf8Bytes(subEvent); + _rabbitClient.Publish(GlobalConst.InterfaceRelayExchange, queueName, eventBytes); + } + + return new InterfaceRelayUnifyResultDto(true, _logRepository.AppRequestLog.RequestId) + { + RequestId = _logRepository.AppRequestLog.RequestId, + OriginalMessage = null, + Attach = attach + }; + } + + [CustomerResult] [HttpPost("ForwardWithSubscribe")] public async Task ForwardWithSubscribeAsync( [FromQuery] [Required] string upStreamCode, [FromQuery] [Required] string serviceProviderCode, - [FromQuery] int mainBodyId, [FromBody] [Required] object data, - [FromHeader] string? attach = null, + [FromHeader] string attach = null, [FromHeader] bool isReturnMessage = false, - [FromHeader] string? failedSubscribeName = null) + [FromHeader] string failedSubscribeName = null) { if (JToken.FromObject(data) is not JObject jobject) { @@ -64,7 +138,7 @@ public class InterfaceForwardService : BaseService, IInterfaceForwardService ? await _forwardCommon.InternalForwardAsync(context) : await _forwardCommon.InternalForward2Async(contextList); - return new InterfaceRelayUnifyResultDto + return new InterfaceRelayUnifyResultDto(true, res) { RequestId = context.ServiceProviderRequestLog.RequestId, OriginalMessage = isReturnMessage ? context.ServiceProviderRequestLog.Response : null, @@ -76,12 +150,11 @@ public class InterfaceForwardService : BaseService, IInterfaceForwardService public async Task ReForwardAsync( [FromQuery] [Required] string upStreamCode, [FromQuery] [Required] string serviceProviderCode, - [FromQuery] int mainBodyId, [FromHeader] [Required] Guid requestId, [FromBody] [Required] object data, - [FromHeader] string? attach = null, + [FromHeader] string attach = null, [FromHeader] bool isReturnMessage = false, - [FromHeader] string? failedSubscribeName = null) + [FromHeader] string failedSubscribeName = null) { if (JToken.FromObject(data) is not JObject jobject) { @@ -100,7 +173,7 @@ public class InterfaceForwardService : BaseService, IInterfaceForwardService ? jobject : new JObject { - [upStreamCode] = jobject + [upStreamCode!] = jobject }; // 从第一个失败的开始重试 @@ -109,7 +182,7 @@ public class InterfaceForwardService : BaseService, IInterfaceForwardService ? await _forwardCommon.InternalForwardAsync(context) : await _forwardCommon.InternalForward3Async(contextList, requestLogs); - return new InterfaceRelayUnifyResultDto + return new InterfaceRelayUnifyResultDto(true, res) { RequestId = context.ServiceProviderRequestLog.RequestId, OriginalMessage = isReturnMessage ? _logRepository.ServiceProviderRequestLog.Response : null, diff --git a/src/InterfaceForward.Application/Services/InterfaceMap/InterfaceMapPublishService.cs b/src/InterfaceForward.Application/Services/InterfaceMap/InterfaceMapPublishService.cs index 2f5a027..1314a9d 100644 --- a/src/InterfaceForward.Application/Services/InterfaceMap/InterfaceMapPublishService.cs +++ b/src/InterfaceForward.Application/Services/InterfaceMap/InterfaceMapPublishService.cs @@ -2,17 +2,16 @@ using DiffPlex; using DiffPlex.DiffBuilder; using DiffPlex.DiffBuilder.Model; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; using InterfaceForward.Application.Contracts.Dtos.Interface; using InterfaceForward.Application.Helpers; using InterfaceForward.Domain.Shared.Dtos; -using InterfaceForward.Domain.Shared.Helpers; using InterfaceForward.Repositories; using InterfaceForward.Repositories.Interface.Entitys; using InterfaceForward.Repositories.Interface.Services; using InterfaceForward.Repositories.Interface.ValueObjects; using Microsoft.AspNetCore.Mvc; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; namespace InterfaceForward.Application.Services.InterfaceMap; @@ -20,27 +19,19 @@ namespace InterfaceForward.Application.Services.InterfaceMap; /// 接口映射发布修改 /// [ApiExplorerSettings(GroupName = "接口映射服务")] -public class InterfaceMapPublishService : BaseService +public class InterfaceMapPublishService( + IInterfaceMapPublishRepository interfaceMapPublishRepository, + InterfaceForwardQuery interfaceForwardQuery, + IInterfaceMapRepository interfaceMapRepository) + : ApplicationService { - private readonly static JsonSerializerSettings Settings = new JsonSerializerSettings + private static readonly JsonSerializerSettings Settings = new() { Formatting = Formatting.Indented, // 设置格式化输出 Converters = { new StringEnumConverter() }, ContractResolver = new CustomContractResolver() // 设置自定义的ContractResolver }; - private readonly InterfaceForwardQuery _interfaceForwardQuery; - private readonly IInterfaceMapPublishRepository _interfaceMapPublishRepository; - private readonly IInterfaceMapRepository _interfaceMapRepository; - - public InterfaceMapPublishService(IInterfaceMapPublishRepository interfaceMapPublishRepository, - InterfaceForwardQuery interfaceForwardQuery, IInterfaceMapRepository interfaceMapRepository) - { - _interfaceMapPublishRepository = interfaceMapPublishRepository; - _interfaceForwardQuery = interfaceForwardQuery; - _interfaceMapRepository = interfaceMapRepository; - } - /// /// 获取接口映射 发布记录列表 /// @@ -53,7 +44,7 @@ public class InterfaceMapPublishService : BaseService public async Task> GetPublishedListAsync([Required] int interfaceId, [Required] int serviceProviderId, int pageIndex = 1, int pageSize = 50) { - return await _interfaceMapPublishRepository.GetPublishedListAsync(interfaceId, serviceProviderId, pageIndex, + return await interfaceMapPublishRepository.GetPublishedListAsync(interfaceId, serviceProviderId, pageIndex, pageSize); } @@ -68,18 +59,18 @@ public class InterfaceMapPublishService : BaseService public async Task GetPublishCompareAsync([Required] string interfaceCode, [Required] int serviceProviderId, string version) { - var systemInterface = await _interfaceForwardQuery.GetInterfaceByCodeAsync(interfaceCode); + var systemInterface = await interfaceForwardQuery.GetInterfaceByCodeAsync(interfaceCode); if (systemInterface == null) throw new BusinessException(message: "系统接口不存在"); - var serviceProvider = await _interfaceForwardQuery.GetServiceProviderByIdAsync(serviceProviderId); + var serviceProvider = await interfaceForwardQuery.GetServiceProviderByIdAsync(serviceProviderId); if (serviceProvider == null) throw new BusinessException(message: "服务商不存在"); // 没有指定比对的版本,就是比对数据库最新数据 - string dbContent = version.IsNullOrWhiteSpace() + var dbContent = version.IsNullOrWhiteSpace() ? JsonConvert.SerializeObject( - await _interfaceForwardQuery.BuildForwardCoreContext(systemInterface, serviceProvider), + await interfaceForwardQuery.BuildForwardCoreContext(systemInterface, serviceProvider), Settings) - : await _interfaceMapPublishRepository.GetFirstAsync(a => + : await interfaceMapPublishRepository.GetFirstAsync(a => a.InterfaceId == systemInterface.Id && a.ServiceProviderId == serviceProviderId && a.PublishedVersion == version, a => a.PublishedContent); @@ -88,7 +79,7 @@ public class InterfaceMapPublishService : BaseService string redisContent = JsonConvert.SerializeObject(cache, Settings); - var differenceContent = CompareTexts(redisContent, dbContent ?? ""); + var differenceContent = CompareTexts(redisContent, dbContent); return new InterfaceMapPublishedDifferenceVO { @@ -107,21 +98,21 @@ public class InterfaceMapPublishService : BaseService [HttpPost("InterfaceMap/Publish")] public async Task PublishAsync(InterfaceMapPublishInput input) { - var systemInterface = await _interfaceForwardQuery.GetInterfaceByCodeAsync(input.InterfaceCode); + var systemInterface = await interfaceForwardQuery.GetInterfaceByCodeAsync(input.InterfaceCode); if (systemInterface == null) throw new BusinessException(message: "系统接口不存在"); - var serviceProvider = await _interfaceForwardQuery.GetServiceProviderByIdAsync(input.ServiceProviderId); + var serviceProvider = await interfaceForwardQuery.GetServiceProviderByIdAsync(input.ServiceProviderId); if (serviceProvider == null) throw new BusinessException(message: "服务商不存在"); // 确认比对期间数据是否被篡改 var differ = new Differ(); var inlineBuilder = new InlineDiffBuilder(differ); - var context = await _interfaceForwardQuery.BuildForwardCoreContext(systemInterface, serviceProvider); + var context = await interfaceForwardQuery.BuildForwardCoreContext(systemInterface, serviceProvider); var content = JsonConvert.SerializeObject(context, Settings); var diffResult = inlineBuilder.BuildDiffModel(content, input.DBContent); if (diffResult.HasDifferences) throw new BusinessException(message: "数据库已经发生变更,请重新获取最新数据进行比对"); - var mapsEntities = await _interfaceMapRepository.GetListAsync(x => + var mapsEntities = await interfaceMapRepository.GetListAsync(x => x.UpStreamId == systemInterface.Id && x.ServiceProviderId == serviceProvider.Id); var publishEntity = new InterfaceMapPublishedEntity { @@ -137,15 +128,15 @@ public class InterfaceMapPublishService : BaseService using var ts = TransacationHelper.GetReadCommitted(); - await _interfaceMapPublishRepository.InsertAsync(publishEntity); + await interfaceMapPublishRepository.InsertAsync(publishEntity); // 更新版本号 mapsEntities.ForEach(x => x.PublishedVersion = publishEntity.PublishedVersion); - await _interfaceMapRepository.UpdateRangeAsync(mapsEntities, x => x.PublishedVersion); + await interfaceMapRepository.UpdateRangeAsync(mapsEntities, x => x.PublishedVersion); // update cache var redisKey = InterfaceForwardCommon.GetContextCacheKey(systemInterface.Code, serviceProvider.Code); - await RedisHelper.Client.SetAsync(redisKey, content, InterfaceForwardCommon.ContextCacheTtl); + await RedisHelper.Client.SetAsync(redisKey, content, InterfaceForwardCommon.DefaultCacheTtl); ts.Complete(); return true; @@ -160,19 +151,19 @@ public class InterfaceMapPublishService : BaseService [HttpPost("InterfaceMap/PublishRollback")] public async Task PublishRollbackAsync(InterfaceMapRollbackInput input) { - var systemInterface = await _interfaceForwardQuery.GetInterfaceByCodeAsync(input.InterfaceCode); + var systemInterface = await interfaceForwardQuery.GetInterfaceByCodeAsync(input.InterfaceCode); if (systemInterface == null) throw new BusinessException(message: "系统接口不存在"); - var serviceProvider = await _interfaceForwardQuery.GetServiceProviderByIdAsync(input.ServiceProviderId); + var serviceProvider = await interfaceForwardQuery.GetServiceProviderByIdAsync(input.ServiceProviderId); if (serviceProvider == null) throw new BusinessException(message: "服务商不存在"); - var mapsEntities = await _interfaceMapRepository.GetListAsync(x => + var mapsEntities = await interfaceMapRepository.GetListAsync(x => x.UpStreamId == systemInterface.Id && x.ServiceProviderId == input.ServiceProviderId); if (mapsEntities == null || mapsEntities.Count == 0) { throw new BusinessException(message: "映射不存在"); } - var targetVersionContent = await _interfaceMapPublishRepository.GetFirstAsync(a => + var targetVersionContent = await interfaceMapPublishRepository.GetFirstAsync(a => a.InterfaceId == systemInterface.Id && a.ServiceProviderId == input.ServiceProviderId && a.PublishedVersion == input.Version, a => a.PublishedContent); // tips:空是合法的,虽然在业务上不存在 @@ -198,14 +189,14 @@ public class InterfaceMapPublishService : BaseService using var ts = TransacationHelper.GetReadCommitted(); - await _interfaceMapPublishRepository.InsertAsync(publishEntity); + await interfaceMapPublishRepository.InsertAsync(publishEntity); // 更新版本号 mapsEntities.ForEach(x => x.PublishedVersion = publishEntity.PublishedVersion); - await _interfaceMapRepository.UpdateRangeAsync(mapsEntities, x => x.PublishedVersion); + await interfaceMapRepository.UpdateRangeAsync(mapsEntities, x => x.PublishedVersion); await RedisHelper.Client.SetAsync(redisKey, publishEntity.PublishedContent, - InterfaceForwardCommon.ContextCacheTtl); + InterfaceForwardCommon.DefaultCacheTtl); ts.Complete(); diff --git a/src/InterfaceForward.Application/Services/InterfaceMap/InterfaceMapService.Details.cs b/src/InterfaceForward.Application/Services/InterfaceMap/InterfaceMapService.Details.cs new file mode 100644 index 0000000..3bd560e --- /dev/null +++ b/src/InterfaceForward.Application/Services/InterfaceMap/InterfaceMapService.Details.cs @@ -0,0 +1,245 @@ +using System.ComponentModel.DataAnnotations; +using InterfaceForward.Application.Contracts.Dtos.Interface; +using InterfaceForward.Application.Helpers; +using InterfaceForward.Domain.Shared.Enum; +using InterfaceForward.Repositories.Interface.Entitys; +using InterfaceForward.Repositories.Interface.ValueObjects; +using InterfaceForward.Repositories.ServiceProvider.Entitys; +using Microsoft.AspNetCore.Mvc; + +namespace InterfaceForward.Application.Services.InterfaceMap; + +public partial class InterfaceMapService +{ + /// + /// 获取映射详情(编辑页) + /// + /// 系统接口id + /// 服务商id + /// + [HttpGet("InterfaceMap/EditMapDetails")] + public async Task EditMapDetailsAsync([Required] int interfaceId, + [Required] int serviceProviderId) + { + var maps = await _interfaceMapRepository.GetListAsync(x => + x.UpStreamId == interfaceId && x.ServiceProviderId == serviceProviderId, + x => new { x.Id, x.DownStreamId, x.PrefixScript, x.PostfixScript, x.FreeMap }); + if (maps == null || maps.Count == 0) + { + throw new BusinessException(message: "映射不存在"); + } + + var serviceProviderInterfaceIds = maps.Select(x => x.DownStreamId).ToList(); + var serviceProviderInterfaces = + await _interfaceRepository.GetListAsync(x => serviceProviderInterfaceIds.Contains(x.Id), + x => new { x.Id, x.Name }); + var res = new EditMapDetailsOutput(); + + var mapIds = maps.Select(x => x.Id).ToList(); + // 服务商入参 多个 + var inParaMaps = await _interfaceMapRepository.GetParameterMapListAsync(mapIds, true); + foreach (var map in maps) + { + var serviceProviderInterface = serviceProviderInterfaces.First(x => x.Id == map.DownStreamId); + res.InParaMapList.Add(new MapSummary + { + InterfaceMapId = map.Id, + InterfaceId = serviceProviderInterface.Id, + InterfaceName = serviceProviderInterface.Name, + PrefixScript = map.PrefixScript, + PostfixScript = map.PostfixScript, + FreeMap = map.FreeMap, + MapDetails = inParaMaps.Where(x => x.InterfaceId == serviceProviderInterface.Id).ToList() + }); + } + + // 系统出参 1个 + res.MapDetails = await _interfaceMapRepository.GetParameterMapListAsync(mapIds, false); + + return res; + } + + /// + /// 获取映射参数级联列表 + /// + /// 系统接口id + /// 服务商id + /// + [HttpGet("InterfaceMap/GetMapParameterCascadedList")] + public async Task> + GetServiceProviderInterfaceCascadedListAsync([Required] int interfaceId, + [Required] int serviceProviderId) + { + var serviceProviderInterfaceIds = await _interfaceMapRepository.GetListAsync(x => + x.UpStreamId == interfaceId && x.ServiceProviderId == serviceProviderId, + x => x.DownStreamId); + if (serviceProviderInterfaceIds == null || serviceProviderInterfaceIds.Count == 0) + { + throw new BusinessException(message: "映射不存在"); + } + + var interfaceIds = new List(serviceProviderInterfaceIds) { interfaceId }; + var interfaces = await _interfaceRepository.GetListAsync(x => interfaceIds.Contains(x.Id), + x => new + { + x.Id, + x.Code, + x.Name + }); + if (interfaces == null || interfaces.Count != interfaceIds.Count) + { + throw new BusinessException(message: "映射接口已丢失,请联系管理员"); + } + + var res = new List(); + + // 一个系统接口入参 + var inParams = await _parameterQueries.GetListAsync( + x => x.InterfaceId == interfaceId && x.IsInPara && x.Type != ParameterType.Array && + x.Type != ParameterType.Object); + var systemInterface = interfaces.First(x => x.Id == interfaceId); + res.Add(new MapParameterCascadedOutput + { + Name = "系统接口", + InterfaceId = systemInterface.Id, + InterfaceCode = systemInterface.Code, + Parameters = ObjectMapper.Map, List>(inParams + .OrderBy(x => x.Sort) + .ToList()) + }); + + // 所有服务商接口的出参 + var outParams = + await _parameterQueries.GetListAsync( + x => serviceProviderInterfaceIds.Contains(x.InterfaceId) && !x.IsInPara); + foreach (var id in serviceProviderInterfaceIds) + { + var @interface = interfaces.First(x => x.Id == id); + var @params = outParams.Where(x => x.InterfaceId == id) + .OrderBy(x => x.Sort) + .ToList(); + res.Add(new MapParameterCascadedOutput + { + Name = @interface.Name, + InterfaceId = @interface.Id, + InterfaceCode = @interface.Code, + Parameters = ObjectMapper.Map, List>(@params) + }); + } + + return res; + } + + /// + /// 保存参数映射 + /// + /// + /// + [HttpPost("InterfaceMap/SaveParameterMaps")] + public async Task SaveParameterMapsAsync(SaveParameterMapsInput input) + { + var mapIds = input.InParaMapList.Select(x => x.InterfaceMapId).ToList(); + if (mapIds.Count == 0 || mapIds.Count != await _interfaceMapRepository.CountAsync(x => mapIds.Contains(x.Id))) + { + throw new BusinessException(message: "非法保存"); + } + + var entitiesForUpdate = new List(); + var entitiesForInsert = new List(); + var fixedValues = new List(); + VerifyAndFillEntities(input, entitiesForInsert, entitiesForUpdate, fixedValues); + + using var ts = TransacationHelper.GetReadCommitted(); + + // 更新脚本 + await _interfaceMapRepository.UpdateRangeAsync(input.InParaMapList.Select(x => new InterfaceMapEntity + { + Id = x.InterfaceMapId, + PrefixScript = x.PrefixScript, + PostfixScript = x.PostfixScript, + FreeMap = x.FreeMap + }).ToList(), x => new + { + x.PrefixScript, x.PostfixScript, x.FreeMap, x.UpdateTime, x.UpdateUserId + }); + + // 更新参数映射 + await _interfaceMapDetailRepository.UpdateRangeAsync(entitiesForUpdate, x => new + { + x.MappedParaId, x.UpdateTime, x.UpdateUserId + }); + await _interfaceMapDetailRepository.InsertRangeAsync(entitiesForInsert); + + // 维护参数固定值 + var delList = fixedValues.Where(x => x.Id != 0 && x.FieldValue.IsNullOrEmpty()).ToList(); + await _fixedParameterRepository.SoftDeleteAsync(x => delList.Select(t => t.Id).Contains(x.Id)); + fixedValues.RemoveAll(delList); + + var updList = fixedValues.Where(x => x.Id != 0).ToList(); + await _fixedParameterRepository.UpdateRangeAsync(updList, + x => new { x.FieldName, x.FieldValue, x.UpdateTime, x.UpdateUserId }); + fixedValues.RemoveAll(updList); + + var insertList = fixedValues.Where(x => !x.FieldValue.IsNullOrEmpty()).ToList(); + await _fixedParameterRepository.InsertRangeAsync(insertList); + + ts.Complete(); + return true; + } + + /// + /// 校验必填项,并填充实体 + /// + /// + /// + /// + /// + /// + private static void VerifyAndFillEntities(SaveParameterMapsInput input, + List entitiesForInsert, List entitiesForUpdate, + List fixedParameterEntities) + { + var details = input.InParaMapList.SelectMany(x => x.MapDetails).Concat(input.MapDetails).ToList(); + + foreach (var x in details) + { + if (x.IsRequired) + { + if (x.MappedParaId == null && x.FixedValue.IsNullOrEmpty()) + throw new BusinessException(message: $"{x.Alias}必填不能空"); + } + + if (x.InterfaceMapDetailId == null) + { + entitiesForInsert.Add(new InterfaceMapDetailEntity + { + InterfaceMapId = x.InterfaceMapId, + ParaId = x.ParaId, + MappedParaId = x.MappedParaId, + IsInPara = x.IsInPara + }); + } + else + { + entitiesForUpdate.Add(new InterfaceMapDetailEntity + { + Id = (int)x.InterfaceMapDetailId, + MappedParaId = x.MappedParaId, + }); + } + } + + foreach (var item in input.InParaMapList) + { + fixedParameterEntities.AddRange(item.MapDetails.Select(x => new FixedParameterEntity + { + Id = x.FixedId, + InterfaceId = item.InterfaceId, + FieldName = x.Alias, + FieldValue = x.FixedValue, + FieldPositions = FieldPosition.Body.ToString(), + Description = x.Description + })); + } + } +} \ No newline at end of file diff --git a/src/InterfaceForward.Application/Services/InterfaceMap/InterfaceMapService.cs b/src/InterfaceForward.Application/Services/InterfaceMap/InterfaceMapService.cs index 45e5553..c9a6c49 100644 --- a/src/InterfaceForward.Application/Services/InterfaceMap/InterfaceMapService.cs +++ b/src/InterfaceForward.Application/Services/InterfaceMap/InterfaceMapService.cs @@ -2,7 +2,6 @@ using System.Diagnostics; using InterfaceForward.Application.Contracts.Dtos.Interface; using InterfaceForward.Application.Helpers; -using InterfaceForward.Domain.Shared.Enum; using InterfaceForward.Repositories; using InterfaceForward.Repositories.Interface.Entitys; using InterfaceForward.Repositories.Interface.Services; @@ -16,13 +15,14 @@ namespace InterfaceForward.Application.Services.InterfaceMap; /// 接口映射服务 /// [ApiExplorerSettings(GroupName = "接口映射服务")] -public class InterfaceMapService : BaseService +public partial class InterfaceMapService : ApplicationService { + private readonly IBasicRepository _fixedParameterRepository; + private readonly IBasicRepository _interfaceMapDetailRepository; private readonly IInterfaceMapRepository _interfaceMapRepository; private readonly IBasicRepository _interfaceRepository; private readonly IBasicRepository _parameterQueries; - private readonly IBasicRepository _interfaceMapDetailRepository; - private readonly IBasicRepository _fixedParameterRepository; + private readonly IBasicRepository _serviceProviderQueries; /// public InterfaceMapService( @@ -30,17 +30,17 @@ public class InterfaceMapService : BaseService , IBasicRepository interfaceRepository , IBasicRepository parameterQueries , IBasicRepository interfaceMapDetailRepository - , IBasicRepository fixedParameterRepository) + , IBasicRepository fixedParameterRepository + , IBasicRepository serviceProviderQueries) { _interfaceMapRepository = interfaceMapRepository; _interfaceRepository = interfaceRepository; _parameterQueries = parameterQueries; _interfaceMapDetailRepository = interfaceMapDetailRepository; _fixedParameterRepository = fixedParameterRepository; + _serviceProviderQueries = serviceProviderQueries; } - #region 接口映射 - /// /// 创建接口映射 /// @@ -84,7 +84,7 @@ public class InterfaceMapService : BaseService PrefixScript = null, PostfixScript = null }).ToList(); - + var maps2 = input.DownStreamIds.Select(id => maps.Single(x => x.DownStreamId == id)).ToList(); return await _interfaceMapRepository.InsertRangeAsync(maps2); @@ -93,12 +93,12 @@ public class InterfaceMapService : BaseService /// /// 获取接口映射列表 /// - /// 系统接口id + /// /// - [HttpGet("InterfaceMap/GetMapListById")] - public async Task> GetMapListByIdAsync([Required] int interfaceId) + [HttpGet("InterfaceMap/GetMapList")] + public async Task> GetMapListByIdAsync(GetMapListInputVO inputVo) { - return await _interfaceMapRepository.GetMapListByIdAsync(interfaceId); + return await _interfaceMapRepository.GetMapListAsync(inputVo); } /// @@ -111,6 +111,19 @@ public class InterfaceMapService : BaseService [HttpPost("InterfaceMap/Delete")] public async Task DeleteAsync([Required] int interfaceId, [Required] int serviceProviderId) { + var systemInterfaceCode = await _interfaceRepository.GetFirstAsync(x => x.Id == interfaceId, x => x.Code); + if (systemInterfaceCode.IsNullOrEmpty()) + { + throw new BusinessException(message: "系统接口不存在"); + } + + var serviceProviderCode = + await _serviceProviderQueries.GetFirstAsync(x => x.Id == serviceProviderId, x => x.Code); + if (serviceProviderCode.IsNullOrEmpty()) + { + throw new BusinessException(message: "服务商不存在!"); + } + var mapIds = await _interfaceMapRepository.GetListAsync(x => x.UpStreamId == interfaceId && x.ServiceProviderId == serviceProviderId, x => x.Id); if (mapIds == null || mapIds.Count == 0) throw new BusinessException(message: "映射不存在"); @@ -124,240 +137,9 @@ public class InterfaceMapService : BaseService ts.Complete(); + var contextCacheKey = InterfaceForwardCommon.GetContextCacheKey(systemInterfaceCode, serviceProviderCode); + await RedisHelper.Client.DelAsync(contextCacheKey); + return true; } - - #endregion - - #region 映射详情 - - /// - /// 获取映射详情(编辑页) - /// - /// 系统接口id - /// 服务商id - /// - [HttpGet("InterfaceMap/EditMapDetails")] - public async Task EditMapDetailsAsync([Required] int interfaceId, - [Required] int serviceProviderId) - { - var maps = await _interfaceMapRepository.GetListAsync(x => - x.UpStreamId == interfaceId && x.ServiceProviderId == serviceProviderId, - x => new { x.Id, x.DownStreamId, x.PrefixScript, x.PostfixScript }); - if (maps == null || maps.Count == 0) - { - throw new BusinessException(message: "映射不存在"); - } - - var serviceProviderInterfaceIds = maps.Select(x => x.DownStreamId).ToList(); - var serviceProviderInterfaces = - await _interfaceRepository.GetListAsync(x => serviceProviderInterfaceIds.Contains(x.Id), - x => new { x.Id, x.Name }); - var res = new EditMapDetailsOutput(); - - var mapIds = maps.Select(x => x.Id).ToList(); - // 服务商入参 多个 - var inParaMaps = await _interfaceMapRepository.GetParameterMapListAsync(mapIds, true); - foreach (var map in maps) - { - var serviceProviderInterface = serviceProviderInterfaces.First(x => x.Id == map.DownStreamId); - res.InParaMapList.Add(new MapSummary - { - InterfaceMapId = map.Id, - InterfaceId = serviceProviderInterface.Id, - InterfaceName = serviceProviderInterface.Name, - PrefixScript = map.PrefixScript, - PostfixScript = map.PostfixScript, - MapDetails = inParaMaps.Where(x => x.InterfaceId == serviceProviderInterface.Id).ToList() - }); - } - - // 系统出参 1个 - res.MapDetails = await _interfaceMapRepository.GetParameterMapListAsync(mapIds, false); - - return res; - } - - /// - /// 获取映射参数级联列表 - /// - /// 系统接口id - /// 服务商id - /// - [HttpGet("InterfaceMap/GetMapParameterCascadedList")] - public async Task> - GetServiceProviderInterfaceCascadedListAsync([Required] int interfaceId, - [Required] int serviceProviderId) - { - var serviceProviderInterfaceIds = await _interfaceMapRepository.GetListAsync(x => - x.UpStreamId == interfaceId && x.ServiceProviderId == serviceProviderId, - x => x.DownStreamId); - if (serviceProviderInterfaceIds == null || serviceProviderInterfaceIds.Count == 0) - { - throw new BusinessException(message: "映射不存在"); - } - - var interfaceIds = new List(serviceProviderInterfaceIds) { interfaceId }; - var interfaces = await _interfaceRepository.GetListAsync(x => interfaceIds.Contains(x.Id), - x => new - { - x.Id, - x.Code, - x.Name - }); - if (interfaces == null || interfaces.Count != interfaceIds.Count) - { - throw new BusinessException(message: "映射接口已丢失,请联系管理员"); - } - - var res = new List(); - - // 一个系统接口入参 - var inParams = await _parameterQueries.GetListAsync( - x => x.InterfaceId == interfaceId && x.IsInPara && x.Type != ParameterType.Array && - x.Type != ParameterType.Object); - var systemInterface = interfaces.First(x => x.Id == interfaceId); - res.Add(new MapParameterCascadedOutput - { - Name = "系统接口", - InterfaceId = systemInterface.Id, - InterfaceCode = systemInterface.Code, - Parameters = ObjectMapper.Map, List>(inParams - .OrderBy(x => x.Sort) - .ToList()) - }); - - // 所有服务商接口的出参 - var outParams = - await _parameterQueries.GetListAsync( - x => serviceProviderInterfaceIds.Contains(x.InterfaceId) && !x.IsInPara); - foreach (var id in serviceProviderInterfaceIds) - { - var @interface = interfaces.First(x => x.Id == id); - var @params = outParams.Where(x => x.InterfaceId == id) - .OrderBy(x => x.Sort) - .ToList(); - res.Add(new MapParameterCascadedOutput - { - Name = @interface.Name, - InterfaceId = @interface.Id, - InterfaceCode = @interface.Code, - Parameters = ObjectMapper.Map, List>(@params) - }); - } - - return res; - } - - /// - /// 保存参数映射 - /// - /// - /// - [HttpPost("InterfaceMap/SaveParameterMaps")] - public async Task SaveParameterMapsAsync(SaveParameterMapsInput input) - { - var mapIds = input.InParaMapList.Select(x => x.InterfaceMapId).ToList(); - if (mapIds.Count == 0 || mapIds.Count != await _interfaceMapRepository.CountAsync(x => mapIds.Contains(x.Id))) - { - throw new BusinessException(message: "非法保存"); - } - - var entitiesForUpdate = new List(); - var entitiesForInsert = new List(); - var fixedValues = new List(); - VerifyAndFillEntities(input, entitiesForInsert, entitiesForUpdate, fixedValues); - - using var ts = TransacationHelper.GetReadCommitted(); - - // 更新脚本 - await _interfaceMapRepository.UpdateRangeAsync(input.InParaMapList.Select(x => new InterfaceMapEntity - { - Id = x.InterfaceMapId, - PrefixScript = x.PrefixScript, - PostfixScript = x.PostfixScript - }).ToList(), x => new - { - x.PrefixScript, - x.PostfixScript - }); - - // 更新参数映射 - await _interfaceMapDetailRepository.UpdateRangeAsync(entitiesForUpdate, x => x.MappedParaId); - await _interfaceMapDetailRepository.InsertRangeAsync(entitiesForInsert); - - // 维护参数固定值 - var delList = fixedValues.Where(x => x.Id != 0 && x.FieldValue.IsNullOrEmpty()).ToList(); - await _fixedParameterRepository.SoftDeleteAsync(x => delList.Select(t => t.Id).Contains(x.Id)); - fixedValues.RemoveAll(delList); - - var updList = fixedValues.Where(x => x.Id != 0).ToList(); - await _fixedParameterRepository.UpdateRangeAsync(updList, - x => new { x.FieldName, x.FieldValue, x.UpdateTime, x.UpdateUserId }); - fixedValues.RemoveAll(updList); - - var insertList = fixedValues.Where(x => !x.FieldValue.IsNullOrEmpty()).ToList(); - await _fixedParameterRepository.InsertRangeAsync(insertList); - - ts.Complete(); - return true; - } - - /// - /// 校验必填项,并填充实体 - /// - /// - /// - /// - /// - /// - private static void VerifyAndFillEntities(SaveParameterMapsInput input, - List entitiesForInsert, List entitiesForUpdate, - List fixedParameterEntities) - { - var details = input.InParaMapList.SelectMany(x => x.MapDetails).Concat(input.MapDetails).ToList(); - - foreach (var x in details) - { - if (x.IsRequired) - { - if (x.MappedParaId == null && x.FixedValue.IsNullOrEmpty()) - throw new BusinessException(message: $"{x.Alias}必填不能空"); - } - - if (x.InterfaceMapDetailId == null) - { - entitiesForInsert.Add(new InterfaceMapDetailEntity - { - InterfaceMapId = x.InterfaceMapId, - ParaId = x.ParaId, - MappedParaId = x.MappedParaId, - IsInPara = x.IsInPara - }); - } - else - { - entitiesForUpdate.Add(new InterfaceMapDetailEntity - { - Id = (int)x.InterfaceMapDetailId, - MappedParaId = x.MappedParaId, - }); - } - } - - foreach (var item in input.InParaMapList) - { - fixedParameterEntities.AddRange(item.MapDetails.Select(x => new FixedParameterEntity - { - Id = x.FixedId, - InterfaceId = item.InterfaceId, - FieldName = x.Alias, - FieldValue = x.FixedValue, - FieldPositions = FieldPosition.Body.ToString(), - Description = x.Description - })); - } - } - - #endregion } \ No newline at end of file diff --git a/src/InterfaceForward.Domain.Shared/Dtos/ContextFeature.cs b/src/InterfaceForward.Domain.Shared/Dtos/ContextFeature.cs index e4a0df8..e81c0d8 100644 --- a/src/InterfaceForward.Domain.Shared/Dtos/ContextFeature.cs +++ b/src/InterfaceForward.Domain.Shared/Dtos/ContextFeature.cs @@ -3,9 +3,9 @@ public class ContextFeature { /// - /// 服务商接口默认QPS + /// 目标接口QPS /// - public int ServiceProviderDefaultQps { get; set; } + public int TargetInterfaceQps { get; set; } /// /// 是否等待 @@ -16,4 +16,9 @@ public class ContextFeature /// 自旋周期ms /// public int SpinPeriod { get; set; } + + /// + /// 账户hash + /// + public string AccountHashCode { get; set; } = null!; } \ No newline at end of file diff --git a/src/InterfaceForward.Domain.Shared/Dtos/FixedFieldWithValueDto.cs b/src/InterfaceForward.Domain.Shared/Dtos/FixedFieldWithValueDto.cs index 5399080..abddb9d 100644 --- a/src/InterfaceForward.Domain.Shared/Dtos/FixedFieldWithValueDto.cs +++ b/src/InterfaceForward.Domain.Shared/Dtos/FixedFieldWithValueDto.cs @@ -8,16 +8,16 @@ public class FixedFieldWithValueDto /// 字段所属 /// public FieldOwner FieldOwner { get; set; } - + /// /// 字段名称 /// - public string FieldName { get; set; } + public string FieldName { get; set; } = null!; /// /// 字段值 /// - public string FieldValue { get; set; } + public string FieldValue { get; set; } = null!; /// /// 字段位置 @@ -27,7 +27,7 @@ public class FixedFieldWithValueDto /// /// 字段描述 /// - public string Description { get; set; } + public string? Description { get; set; } } public enum FieldOwner diff --git a/src/InterfaceForward.Domain.Shared/Dtos/ForwardCoreContextCache.cs b/src/InterfaceForward.Domain.Shared/Dtos/ForwardCoreContextCache.cs index 0d0897a..13bb303 100644 --- a/src/InterfaceForward.Domain.Shared/Dtos/ForwardCoreContextCache.cs +++ b/src/InterfaceForward.Domain.Shared/Dtos/ForwardCoreContextCache.cs @@ -26,7 +26,7 @@ public class ForwardCoreContextCache public List OutParamTreeList { get; set; } /// - /// 目标服务商授权接口 + /// 目标服务商授权接口(serviceProvider.AuthInterfaceCode存在时,一定不为null) /// public TargetInterfaceSummary? ServiceProviderAuthInterface { get; set; } @@ -46,13 +46,13 @@ public class ServiceProviderAuthConfigDto /// /// 参数路径 /// - public string ParameterAlias { get; set; } + public string? ParameterAlias { get; set; } - public string Value1 { get; set; } + public string? Value1 { get; set; } - public string Value2 { get; set; } + public string? Value2 { get; set; } - public string Value3 { get; set; } + public string? Value3 { get; set; } } public class TargetInterfaceSummary @@ -60,35 +60,35 @@ public class TargetInterfaceSummary /// /// 目标服务商接口 /// - public InterfaceDto TargetInterface { get; set; } + public InterfaceDto TargetInterface { get; set; } = null!; /// /// 请求前置脚本 /// - public string PrefixScript { get; set; } + public string? PrefixScript { get; set; } /// /// 请求后置脚本 /// - public string PostfixScript { get; set; } - + public string? PostfixScript { get; set; } + /// /// 入参映射列表(树形结构) /// - public List InParamTreeList { get; set; } + public List InParamTreeList { get; set; } = []; /// /// 固定值列表 /// - public List FixedFieldList { get; set; } + public List FixedFieldList { get; set; } = []; /// /// 返回配置列表 /// - public List ReturnConfigList { get; set; } + public List ReturnConfigList { get; set; } = []; /// /// 表单字段列表 /// - public List FormFieldList { get; set; } + public List FormFieldList { get; set; } = []; } \ No newline at end of file diff --git a/src/InterfaceForward.Domain.Shared/Dtos/InterfaceDto.cs b/src/InterfaceForward.Domain.Shared/Dtos/InterfaceDto.cs index a45ab5b..53ca5e7 100644 --- a/src/InterfaceForward.Domain.Shared/Dtos/InterfaceDto.cs +++ b/src/InterfaceForward.Domain.Shared/Dtos/InterfaceDto.cs @@ -12,7 +12,7 @@ public class InterfaceDto /// /// 接口名 /// - public string Name { get; set; } + public string Name { get; set; } = null!; /// /// 服务商id @@ -22,12 +22,12 @@ public class InterfaceDto /// /// 接口代码 /// - public string Code { get; set; } + public string Code { get; set; } = null!; /// /// 请求地址 /// - public string RequestAddress { get; set; } + public string RequestAddress { get; set; } = null!; /// /// 请求协议 @@ -37,7 +37,7 @@ public class InterfaceDto /// /// 请求方式 /// - public string RequestMethod { get; set; } + public string RequestMethod { get; set; } = null!; /// /// 内容类型 @@ -67,7 +67,7 @@ public class InterfaceDto /// /// 流程代码 /// - public string FlowCode { get; set; } + public string? FlowCode { get; set; } /// /// 是否禁用授权 diff --git a/src/InterfaceForward.Domain.Shared/Dtos/InterfaceFormFieldDto.cs b/src/InterfaceForward.Domain.Shared/Dtos/InterfaceFormFieldDto.cs index 15fe01b..897fd79 100644 --- a/src/InterfaceForward.Domain.Shared/Dtos/InterfaceFormFieldDto.cs +++ b/src/InterfaceForward.Domain.Shared/Dtos/InterfaceFormFieldDto.cs @@ -5,15 +5,15 @@ public class InterfaceFormFieldDto /// /// 参数名 /// - public string Name { get; set; } + public string Name { get; set; } = null!; /// /// 参数值 /// - public string Value { get; set; } + public string Value { get; set; } = null!; /// /// 参数描述 /// - public string Description { get; set; } + public string? Description { get; set; } } \ No newline at end of file diff --git a/src/InterfaceForward.Domain.Shared/Dtos/InterfaceReturnConfigDto.cs b/src/InterfaceForward.Domain.Shared/Dtos/InterfaceReturnConfigDto.cs index 4bbce2a..0afec98 100644 --- a/src/InterfaceForward.Domain.Shared/Dtos/InterfaceReturnConfigDto.cs +++ b/src/InterfaceForward.Domain.Shared/Dtos/InterfaceReturnConfigDto.cs @@ -20,11 +20,11 @@ namespace InterfaceForward.Domain.Shared.Dtos /// /// 路径 /// - public string Path { get; set; } + public string Path { get; set; } = null!; /// /// 值 /// - public string Value { get; set; } + public string Value { get; set; } = null!; } } diff --git a/src/InterfaceForward.Domain.Shared/Dtos/RequestLogDto.cs b/src/InterfaceForward.Domain.Shared/Dtos/RequestLogDto.cs index 0018d40..78916fd 100644 --- a/src/InterfaceForward.Domain.Shared/Dtos/RequestLogDto.cs +++ b/src/InterfaceForward.Domain.Shared/Dtos/RequestLogDto.cs @@ -2,8 +2,6 @@ public class RequestLogDto(Guid requestId) { - public const string Init = null!; - /// /// 请求Id /// @@ -22,7 +20,7 @@ public class RequestLogDto(Guid requestId) /// /// 应用/服务商名称 /// - public string Name { get; set; } + public string? Name { get; set; } /// /// 发起请求IP @@ -32,27 +30,27 @@ public class RequestLogDto(Guid requestId) /// /// 接口代码 /// - public string InterfaceCode { get; set; } + public string? InterfaceCode { get; set; } /// /// 请求接口名称 /// - public string InterfaceName { get; set; } + public string? InterfaceName { get; set; } /// /// 请求地址 /// - public string Address { get; set; } + public string? Address { get; set; } /// /// 请求头 /// - public string Headers { get; set; } + public string? Headers { get; set; } /// /// 请求体 /// - public string Content { get; set; } + public string? Content { get; set; } /// /// 响应耗时ms @@ -62,7 +60,12 @@ public class RequestLogDto(Guid requestId) /// /// 响应结果 /// - public string Response { get; set; } = Init; + public string? Response { get; set; } + + /// + /// 响应结果映射后 + /// + public string? Response2 { get; set; } /// /// 是否成功调用 @@ -72,5 +75,5 @@ public class RequestLogDto(Guid requestId) /// /// 异常 /// - public string Exception { get; set; } + public string? Exception { get; set; } } \ No newline at end of file diff --git a/src/InterfaceForward.Domain.Shared/Dtos/RequestServiceProviderFailedCommon.cs b/src/InterfaceForward.Domain.Shared/Dtos/RequestServiceProviderFailedCommon.cs index ce61376..19edcca 100644 --- a/src/InterfaceForward.Domain.Shared/Dtos/RequestServiceProviderFailedCommon.cs +++ b/src/InterfaceForward.Domain.Shared/Dtos/RequestServiceProviderFailedCommon.cs @@ -11,6 +11,8 @@ public static class RequestServiceProviderFailedCommon public static string VerifyStatusFailedCode = "4003"; public static string HeaderInsertFailed = "4004"; + + public static string QpsOutLimit = "4005"; public static bool IsRequestServiceProviderFailed(string code) { diff --git a/src/InterfaceForward.Domain.Shared/Dtos/ServiceProviderDto.cs b/src/InterfaceForward.Domain.Shared/Dtos/ServiceProviderDto.cs index 2b3f6c2..9628334 100644 --- a/src/InterfaceForward.Domain.Shared/Dtos/ServiceProviderDto.cs +++ b/src/InterfaceForward.Domain.Shared/Dtos/ServiceProviderDto.cs @@ -10,12 +10,12 @@ public class ServiceProviderDto /// /// 服务商名称 /// - public string Name { get; set; } - + public string Name { get; set; } = null!; + /// /// 服务商代码 /// - public string Code { get; set; } + public string Code { get; set; } = null!; /// /// 是否禁用授权 @@ -25,12 +25,12 @@ public class ServiceProviderDto /// /// 流程代码 /// - public string FlowCode { get; set; } + public string? FlowCode { get; set; } /// /// 授权接口Code /// - public string AuthInterfaceCode { get; set; } + public string? AuthInterfaceCode { get; set; } /// /// 请求超时(s) diff --git a/src/InterfaceForward.Domain.Shared/GlobalConst.cs b/src/InterfaceForward.Domain.Shared/GlobalConst.cs index 7aca7c1..fd0236c 100644 --- a/src/InterfaceForward.Domain.Shared/GlobalConst.cs +++ b/src/InterfaceForward.Domain.Shared/GlobalConst.cs @@ -8,6 +8,7 @@ public static class GlobalConst public const string DateTime = "$DateTime"; public const string Url = "$Url"; public const string Token = "$token"; + public const string AccountHashCode = "$hash"; /// /// 不执行动态代码 @@ -27,5 +28,7 @@ public static class GlobalConst /// /// 服务商接口qps redis key前缀 /// - public const string ServiceProviderInterfaceQPSKeyPrefix = "InterfaceQPS"; + public const string ServiceProviderInterfaceQpsKeyPrefix = "InterfaceQPS"; + + public const string InterfaceRelayExchange = "sjzy.interface.relay.direct"; } \ No newline at end of file diff --git a/src/InterfaceForward.Repositories/Interface/Entitys/InterfaceMapEntity.cs b/src/InterfaceForward.Repositories/Interface/Entitys/InterfaceMapEntity.cs index 5dfacb0..8fd20fe 100644 --- a/src/InterfaceForward.Repositories/Interface/Entitys/InterfaceMapEntity.cs +++ b/src/InterfaceForward.Repositories/Interface/Entitys/InterfaceMapEntity.cs @@ -23,7 +23,7 @@ /// 系统接口Code /// [SugarColumn(ColumnName = "UpStreamCode")] - public string UpStreamCode { get; set; } + public string UpStreamCode { get; set; } = null!; /// /// 服务商ID @@ -41,24 +41,30 @@ /// 服务商接口Code /// [SugarColumn(ColumnName = "DownStreamCode")] - public string DownStreamCode { get; set; } + public string DownStreamCode { get; set; } = null!; /// /// 当前线上版本 /// [SugarColumn(ColumnName = "PublishedVersion")] - public string PublishedVersion { get; set; } + public string? PublishedVersion { get; set; } /// /// 前置脚本 /// [SugarColumn(ColumnName = "PrefixScript")] - public string PrefixScript { get; set; } + public string? PrefixScript { get; set; } /// /// 后置脚本 /// [SugarColumn(ColumnName = "PostfixScript")] - public string PostfixScript { get; set; } + public string? PostfixScript { get; set; } + + /// + /// 支持不对等映射(自由映射) + /// + [SugarColumn(ColumnName = "FreeMap")] + public bool FreeMap { get; set; } } } \ No newline at end of file diff --git a/src/InterfaceForward.Repositories/Interface/Services/IInterfaceMapRepository.cs b/src/InterfaceForward.Repositories/Interface/Services/IInterfaceMapRepository.cs index 8c24409..57d8b50 100644 --- a/src/InterfaceForward.Repositories/Interface/Services/IInterfaceMapRepository.cs +++ b/src/InterfaceForward.Repositories/Interface/Services/IInterfaceMapRepository.cs @@ -11,9 +11,9 @@ namespace InterfaceForward.Repositories.Interface.Services /// /// 获取接口映射列表 /// - /// 系统接口id + /// /// - Task> GetMapListByIdAsync(int upstreamId); + Task> GetMapListAsync(GetMapListInputVO inputVo); /// /// 获取参数映射列表 diff --git a/src/InterfaceForward.Repositories/Interface/Services/InterfaceMapRepository.cs b/src/InterfaceForward.Repositories/Interface/Services/InterfaceMapRepository.cs index a29bc6a..e10d580 100644 --- a/src/InterfaceForward.Repositories/Interface/Services/InterfaceMapRepository.cs +++ b/src/InterfaceForward.Repositories/Interface/Services/InterfaceMapRepository.cs @@ -13,9 +13,9 @@ public class InterfaceMapRepository : BasicRepository, IScop /// /// 获取映射列表 /// - /// 上游接口id + /// /// - public async Task> GetMapListByIdAsync(int upstreamId) + public async Task> GetMapListAsync(GetMapListInputVO inputVo) { // 上下游一对多关系 var query = Context.Queryable() @@ -23,8 +23,10 @@ public class InterfaceMapRepository : BasicRepository, IScop .LeftJoin((a, b, c) => a.DownStreamId == c.Id) // 服务商接口 N .LeftJoin((a, b, c, d) => c.ServiceProviderId == d.Id) .Filter(null, true) // 禁用全局过滤器 - .Where(a => a.UpStreamId == upstreamId) + .Where(a => a.UpStreamId == inputVo.InterfaceId) .Where((a, b, c, d) => !a.IsDeleted && !b.IsDeleted && !c.IsDeleted && !d.IsDeleted) + .WhereIF(!inputVo.NameOrCode.IsNullOrWhiteSpace(), + (a, b, c, d) => d.Code.Contains(inputVo.NameOrCode) || d.Name.Contains(inputVo.NameOrCode)) .GroupBy(a => new { a.UpStreamId, a.ServiceProviderId }) .OrderBy(a => a.UpdateTime, OrderByType.Desc); @@ -40,24 +42,25 @@ public class InterfaceMapRepository : BasicRepository, IScop InterfaceCode = SqlFunc.MappingColumn("GROUP_CONCAT(c.Code ORDER BY a.id SEPARATOR ',')"), UpdateTime = a.UpdateTime, PublishedVersion = a.PublishedVersion - }).ToListAsync(); + }).ToPagedListAsync(inputVo.PageIndex, inputVo.PageSize); } /// - public async Task> GetParameterMapListAsync(List mapIds, bool isInParaMap) + public async Task> GetParameterMapListAsync(List mapIds, + bool isInParaMap) { /* -select `a`.`Id` AS `InterfaceMapDetailId` , `t`.`Id` AS `InterfaceMapId` , `b`.`Id` AS `Id`, b.InterfaceId, `b`.`Alias` AS `Alias` , `b`.`CnName` AS `CnName` , `e`.`Name` AS `Type` , `b`.`IsInPara` AS `IsInPara`, `b`.`IsRequired` AS `IsRequired` , `b`.`Description` AS `Description` , `d`.`Id` AS `FixedId` , `d`.`FieldValue` AS `FixedValue` , `a`.`MappedParaId` AS `MapId` , `c`.`Alias` AS `MapAlias`, `c`.`CnName` AS `MapCnName` , `c`.`Description` AS `MapDescription` -from t_interface_map t +select `a`.`Id` AS `InterfaceMapDetailId` , `t`.`Id` AS `InterfaceMapId` , `b`.`Id` AS `Id`, b.InterfaceId, `b`.`Alias` AS `Alias` , `b`.`CnName` AS `CnName` , `e`.`Name` AS `Type` , `b`.`IsInPara` AS `IsInPara`, `b`.`IsRequired` AS `IsRequired` , `b`.`Description` AS `Description` , `d`.`Id` AS `FixedId` , `d`.`FieldValue` AS `FixedValue` , `a`.`MappedParaId` AS `MapId` , `c`.`Alias` AS `MapAlias`, `c`.`CnName` AS `MapCnName` , `c`.`Description` AS `MapDescription` +from t_interface_map t LEFT JOIN `t_parameter` `b` ON ( `t`.`UpStreamId` = `b`.`InterfaceId` ) and b.IsInPara = false AND ( `b`.`Type` <> 12 ) AND ( `b`.`Type` <> 10 ) and `b`.`IsDeleted` = 0 Left JOIN `t_interface_map_detail` `a` ON ( `t`.`Id` = `a`.`InterfaceMapId` ) and b.id = a.ParaId and a.IsDeleted = 0 Left JOIN `t_parameter` `c` ON ( `a`.`MappedParaId` = `c`.`Id` ) AND ( `c`.`IsDeleted` = 0 ) Left JOIN `t_parameter_fixed` `d` ON d.InterfaceId = b.InterfaceId and `b`.`Alias` = `d`.`FieldName` AND `d`.`FieldPositions` like concat('%', CAST(N'Body' AS CHAR),'%') AND d.IsDeleted = 0 -Left JOIN `t_dictionary` `e` ON (( `b`.`Type` = `e`.`Id` ) AND ( `e`.`Type` = N'ParameterType' )) +Left JOIN `t_dictionary` `e` ON (( `b`.`Type` = `e`.`Id` ) AND ( `e`.`Type` = N'ParameterType' )) WHERE (( `t`.`UpStreamId` = 30 ) AND ( `t`.`ServiceProviderId` = 137 )) and t.IsDeleted = 0 and t.DownStreamId = 509 ORDER BY `b`.`Sort` ASC */ - + var ids = mapIds.JoinAsString(","); // 因为入参映射有多个 出参映射只有一个(挂在第一个映射) return await Context.SqlQueryable(@$" diff --git a/src/InterfaceForward.Repositories/Interface/ValueObjects/EditMapDetailsOutput.cs b/src/InterfaceForward.Repositories/Interface/ValueObjects/EditMapDetailsOutput.cs index d0ef245..977b0fa 100644 --- a/src/InterfaceForward.Repositories/Interface/ValueObjects/EditMapDetailsOutput.cs +++ b/src/InterfaceForward.Repositories/Interface/ValueObjects/EditMapDetailsOutput.cs @@ -5,12 +5,12 @@ public class EditMapDetailsOutput /// /// 入参映射 /// - public List InParaMapList { get; set; } = new(); + public List InParaMapList { get; set; } = []; /// /// 出参映射明细 /// - public List MapDetails { get; set; } = new(); + public List MapDetails { get; set; } = []; } public class MapSummary @@ -24,21 +24,26 @@ public class MapSummary /// 接口id /// public int InterfaceId { get; set; } - + /// /// 接口Name /// - public string InterfaceName { get; set; } + public string InterfaceName { get; set; } = null!; /// /// 前置脚本 /// - public string PrefixScript { get; set; } + public string? PrefixScript { get; set; } /// /// 后置脚本 /// - public string PostfixScript { get; set; } + public string? PostfixScript { get; set; } + + /// + /// 自由映射(不对等映射) + /// + public bool FreeMap { get; set; } /// /// 映射明细 diff --git a/src/InterfaceForward.Repositories/Interface/ValueObjects/GetMapListInputVO.cs b/src/InterfaceForward.Repositories/Interface/ValueObjects/GetMapListInputVO.cs new file mode 100644 index 0000000..203a38f --- /dev/null +++ b/src/InterfaceForward.Repositories/Interface/ValueObjects/GetMapListInputVO.cs @@ -0,0 +1,12 @@ +using System.ComponentModel.DataAnnotations; +using InterfaceForward.Domain.Shared.Dtos; + +namespace InterfaceForward.Repositories.Interface.ValueObjects; + +public class GetMapListInputVO : PagedQueryInput +{ + [Required] + public int InterfaceId { get; set; } + + public string NameOrCode { get; set; } +} \ No newline at end of file diff --git a/src/InterfaceForward.Repositories/Interface/ValueObjects/InterfaceMapPublishedDifferenceVO.cs b/src/InterfaceForward.Repositories/Interface/ValueObjects/InterfaceMapPublishedDifferenceVO.cs index e19956a..3a93876 100644 --- a/src/InterfaceForward.Repositories/Interface/ValueObjects/InterfaceMapPublishedDifferenceVO.cs +++ b/src/InterfaceForward.Repositories/Interface/ValueObjects/InterfaceMapPublishedDifferenceVO.cs @@ -5,15 +5,15 @@ public class InterfaceMapPublishedDifferenceVO /// /// 线上的缓存 /// - public string PublishedContent { get; set; } + public string? PublishedContent { get; set; } /// /// 数据库的内容 /// - public string DbContent { get; set; } + public string? DbContent { get; set; } /// /// 与线上版本的差异内容 /// - public string DifferenceContent { get; set; } + public string? DifferenceContent { get; set; } } \ No newline at end of file diff --git a/src/InterfaceForward.Repositories/InterfaceForwardQuery.cs b/src/InterfaceForward.Repositories/InterfaceForwardQuery.cs index f5a6f0d..c9069c5 100644 --- a/src/InterfaceForward.Repositories/InterfaceForwardQuery.cs +++ b/src/InterfaceForward.Repositories/InterfaceForwardQuery.cs @@ -144,7 +144,7 @@ ORDER BY `b`.`Sort` ASC").ToListAsync(); /// /// /// - public async Task GetInterfaceByCodeAsync(string interfaceCode) + public async Task GetInterfaceByCodeAsync(string interfaceCode) { var res = await GetInterfaceByCodesAsync(interfaceCode); return res.FirstOrDefault(); @@ -193,22 +193,21 @@ ORDER BY `b`.`Sort` ASC").ToListAsync(); ) // 构建系统返参数树形结构 }; - if (!serviceProvider.AuthInterfaceCode.IsNullOrEmpty()) + if (serviceProvider.AuthInterfaceCode != null) { var authInterface = await GetInterfaceByCodeAsync(serviceProvider.AuthInterfaceCode); - if (authInterface != null) + if (authInterface == null) { - context.ServiceProviderAuthInterface = new TargetInterfaceSummary - { - TargetInterface = authInterface, - PrefixScript = null, - PostfixScript = null, - InParamTreeList = null, - FixedFieldList = await GetAllFixedFieldListAsync(serviceProvider.Id, authInterface.Id), - ReturnConfigList = await GetInterfaceReturnConfigListAsync(authInterface.Id), - FormFieldList = await GetFormFieldListAsync(authInterface.Id), - }; + throw new BusinessException($"授权接口{serviceProvider.AuthInterfaceCode}不存在"); } + + context.ServiceProviderAuthInterface = new TargetInterfaceSummary + { + TargetInterface = authInterface, + FixedFieldList = await GetAllFixedFieldListAsync(serviceProvider.Id, authInterface.Id), + ReturnConfigList = await GetInterfaceReturnConfigListAsync(authInterface.Id), + FormFieldList = await GetFormFieldListAsync(authInterface.Id), + }; } var inParaMaps = await GetParameterMapListAsync(mapIds, true);