This commit is contained in:
xiaolipro 2024-12-06 15:30:41 +08:00
parent 8f418b4fd7
commit 512c511707
39 changed files with 1134 additions and 591 deletions

View File

@ -1,8 +1,9 @@
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
<!-- <Nullable>enable</Nullable>--> <Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<NoWarn>CS1591</NoWarn>
<!-- <WarningsAsErrors>Nullable</WarningsAsErrors>--> <!-- <WarningsAsErrors>Nullable</WarningsAsErrors>-->
</PropertyGroup> </PropertyGroup>
</Project> </Project>

View File

@ -29,7 +29,6 @@ public class InterfaceForwardApiModule : FakeModule
services.AddFakeSwaggerGen() services.AddFakeSwaggerGen()
.AddFakeExceptionFilter() .AddFakeExceptionFilter()
.AddFakeValidationActionFilter() .AddFakeValidationActionFilter()
.AddFakeUnitOfWorkActionFilter()
.AddFakeAspNetCoreAuditing(); .AddFakeAspNetCoreAuditing();
services.AddCors(options => services.AddCors(options =>

View File

@ -27,11 +27,19 @@
"http://localhost:8888" "http://localhost:8888"
] ]
}, },
"ConnectionStrings":{ "ConnectionStrings": {
"Default": "server=baget.xiaolipro.cn;database=interface_forward;uid=dev;pwd=itd!@#123;charset=utf8mb4;" "Default": "server=baget.xiaolipro.cn;database=interface_forward;uid=dev;pwd=itd!@#123;charset=utf8mb4;"
}, },
"FeiShuNotice": { "FeiShuNotice": {
"Title": "InterfaceForward-Dev", "Title": "InterfaceForward-Dev",
"Webhook": "https://open.feishu.cn/open-apis/bot/v2/hook/255c44ff-5891-4902-9b6a-6d0250e745f7" "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"
}
} }
} }

View File

@ -39,6 +39,11 @@ public class MapItem
/// </summary> /// </summary>
public string PostfixScript { get; set; } public string PostfixScript { get; set; }
/// <summary>
/// 自由映射(支持不对等映射)
/// </summary>
public bool FreeMap { get; set; }
/// <summary> /// <summary>
/// 入参映射明细 /// 入参映射明细
/// </summary> /// </summary>

View File

@ -9,27 +9,27 @@ public class ForwardCoreContext
/// <summary> /// <summary>
/// 目标服务商接口 /// 目标服务商接口
/// </summary> /// </summary>
public InterfaceDto TargetInterface { get; set; } public InterfaceDto TargetInterface { get; set; } = null!;
/// <summary> /// <summary>
/// 服务商接口固定字段列表 /// 服务商接口固定字段列表
/// </summary> /// </summary>
public List<FixedFieldWithValueDto> FixedFieldList { get; set; } public List<FixedFieldWithValueDto> FixedFieldList { get; set; } = [];
/// <summary> /// <summary>
/// 服务商账户字段列表 /// 服务商账户字段列表
/// </summary> /// </summary>
public List<ServiceProviderAccountFieldWithValueDto> AccountFieldList { get; set; } public List<ServiceProviderAccountFieldWithValueDto> AccountFieldList { get; set; } = [];
/// <summary> /// <summary>
/// 服务商接口返回配置列表 /// 服务商接口返回配置列表
/// </summary> /// </summary>
public List<InterfaceReturnConfigDto> ReturnConfigList { get; set; } public List<InterfaceReturnConfigDto> ReturnConfigList { get; set; } = [];
/// <summary> /// <summary>
/// 表单字段列表 /// 表单字段列表
/// </summary> /// </summary>
public List<InterfaceFormFieldDto> FormFieldList { get; set; } public List<InterfaceFormFieldDto> FormFieldList { get; set; } = [];
/// <summary> /// <summary>
/// 入参映射列表(树形结构) /// 入参映射列表(树形结构)
@ -44,12 +44,12 @@ public class ForwardCoreContext
/// <summary> /// <summary>
/// 请求前置脚本 /// 请求前置脚本
/// </summary> /// </summary>
public string PrefixScript { get; set; } public string? PrefixScript { get; set; }
/// <summary> /// <summary>
/// 请求后置脚本 /// 请求后置脚本
/// </summary> /// </summary>
public string PostfixScript { get; set; } public string? PostfixScript { get; set; }
public bool AddHeader(string key, string value) public bool AddHeader(string key, string value)
{ {
@ -87,27 +87,27 @@ public class ForwardCoreContext
/// <summary> /// <summary>
/// 业务系统传参json /// 业务系统传参json
/// </summary> /// </summary>
public JToken OriginalInterfaceInput { get; set; } public JToken OriginalInterfaceInput { get; set; } = null!;
/// <summary> /// <summary>
/// 业务系统传参映射后的json /// 业务系统传参映射后的json
/// </summary> /// </summary>
public JToken OriginalInterfaceMappedInput { get; set; } public JToken OriginalInterfaceMappedInput { get; set; } = null!;
/// <summary> /// <summary>
/// 目标接口返回的json /// 目标接口返回的json
/// </summary> /// </summary>
public JToken TargetInterfaceOutput { get; set; } public JToken TargetInterfaceOutput { get; set; } = null!;
/// <summary> /// <summary>
/// 请求服务商接口真实传参字符串 /// 请求服务商接口真实传参字符串
/// </summary> /// </summary>
public string TargetInterfaceInput { get; set; } public string TargetInterfaceInput { get; set; } = null!;
/// <summary> /// <summary>
/// 服务商日志 /// 服务商日志
/// </summary> /// </summary>
public RequestLogDto ServiceProviderRequestLog { get; set; } public RequestLogDto ServiceProviderRequestLog { get; set; } = null!;
/// <summary> /// <summary>
/// 专用feature /// 专用feature

View File

@ -25,7 +25,7 @@ public class InterfaceRelayForwardReplayEvent
/// <summary> /// <summary>
/// 原报文 /// 原报文
/// </summary> /// </summary>
public string OriginalMessage { get; set; } public string? OriginalMessage { get; set; }
/// <summary> /// <summary>
/// 附加数据 /// 附加数据

View File

@ -1,14 +1,15 @@
using System.Text; using System.Collections.Concurrent;
using System.Diagnostics;
using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Web; using System.Web;
using System.Xml; using System.Xml;
using InterfaceForward.Application.Contracts;
using InterfaceForward.Application.Contracts.ForwardCore; using InterfaceForward.Application.Contracts.ForwardCore;
using InterfaceForward.Application.Helpers; using InterfaceForward.Application.Helpers;
using InterfaceForward.Application.OSS; using InterfaceForward.Application.OSS;
using InterfaceForward.Domain.Shared; using InterfaceForward.Domain.Shared;
using InterfaceForward.Domain.Shared.Dtos;
using InterfaceForward.Domain.Shared.Enum; using InterfaceForward.Domain.Shared.Enum;
using Microsoft.AspNetCore.Components;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
@ -19,16 +20,11 @@ namespace InterfaceForward.Application.ForwardCore;
/// </summary> /// </summary>
public class DefaultForwardFlow : IForwardFlow public class DefaultForwardFlow : IForwardFlow
{ {
public IHttpClientFactory ClientFactory { get; set; } private static readonly ConcurrentDictionary<string, Delegate> DelegateDic = new();
[Inject] public IHttpClientFactory ClientFactory { get; set; } = null!;
public virtual Task<JToken> InParamRestructure(ForwardCoreContext context) public virtual Task<JToken> InParamRestructure(ForwardCoreContext context)
{ {
if (context.OriginalInterfaceInput is JArray)
{
// 虚构系统入参数组路径
VirtualizePath(context.InParamTreeList);
}
var res = JsonHelper.Restructure(context.OriginalInterfaceInput, context.InParamTreeList, var res = JsonHelper.Restructure(context.OriginalInterfaceInput, context.InParamTreeList,
context.TargetInterface.MappedParamType); context.TargetInterface.MappedParamType);
@ -40,18 +36,7 @@ public class DefaultForwardFlow : IForwardFlow
// 填充占位符 // 填充占位符
ReplacePlaceholder(context); ReplacePlaceholder(context);
// 使用Natasha构建动态表达式 await ExecuteScriptAsync(context, true);
if (!context.PrefixScript.IsNullOrEmpty())
{
if (context.PrefixScript.Contains("await"))
{
await NDelegate.RandomDomain().AsyncFunc<ForwardCoreContext, Task>(context.PrefixScript)(context);
}
else
{
NDelegate.RandomDomain().Action<ForwardCoreContext>(context.PrefixScript)(context);
}
}
// 将body序列化成对应的格式字符串 // 将body序列化成对应的格式字符串
context.TargetInterfaceInput = SerializationBody(context); context.TargetInterfaceInput = SerializationBody(context);
@ -64,17 +49,7 @@ public class DefaultForwardFlow : IForwardFlow
// 将响应的字符串反序列化成jtoken // 将响应的字符串反序列化成jtoken
context.TargetInterfaceOutput = DeserializationResponse(context, res); context.TargetInterfaceOutput = DeserializationResponse(context, res);
if (!context.PostfixScript.IsNullOrEmpty()) await ExecuteScriptAsync(context, false);
{
if (context.PostfixScript.Contains("await"))
{
await NDelegate.RandomDomain().AsyncFunc<ForwardCoreContext, Task>(context.PostfixScript)(context);
}
else
{
NDelegate.RandomDomain().Action<ForwardCoreContext>(context.PostfixScript)(context);
}
}
// 校验状态码,失败发飞书 // 校验状态码,失败发飞书
VerifyStatusCode(context); VerifyStatusCode(context);
@ -84,12 +59,6 @@ public class DefaultForwardFlow : IForwardFlow
public virtual async Task<JToken> OutParamRestructure(ForwardCoreContext context, JToken response) public virtual async Task<JToken> OutParamRestructure(ForwardCoreContext context, JToken response)
{ {
if (context.TargetInterfaceOutput is JArray)
{
// 虚构系统入参数组路径
VirtualizePath(context.OutParamTreeList);
}
// 映射 // 映射
var mappedRes = JsonHelper.Restructure(response, 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(); var tokens = response.SelectTokens($"{context.TargetInterface.Code}.{pdfLabelConfig.Path}").ToList();
if (tokens.Count == 0) tokens = response.SelectTokens($"{pdfLabelConfig.Path}").ToList(); if (tokens.Count == 0) tokens = response.SelectTokens($"{pdfLabelConfig.Path}").ToList();
if (tokens.Count == 0) return mappedRes; if (tokens.Count == 0) return mappedRes;
var token = tokens[0]; var token = tokens.First();
UploadResultDto oss = null; UploadResultDto oss = null!;
switch (Enum.Parse<LabelType>(pdfLabelConfig.Value)) switch (Enum.Parse<LabelType>(pdfLabelConfig.Value))
{ {
case LabelType.Url: case LabelType.Url:
{ {
string url = token.Value<string>(); string url = token.Value<string>()!;
using var r = await ClientFactory.CreateClient().GetAsync(url); using var r = await ClientFactory.CreateClient().GetAsync(url);
await using var stream = await r.Content.ReadAsStreamAsync(); await using var stream = await r.Content.ReadAsStreamAsync();
oss = OSSHelper.Upload(stream, Guid.NewGuid().ToString("D") + ".pdf"); oss = OSSHelper.Upload(stream, Guid.NewGuid().ToString("D") + ".pdf");
break; break;
} }
case LabelType.Base64: case LabelType.Base64:
byte[] bytes = Convert.FromBase64String(token.ToString()); byte[] bytes = Convert.FromBase64String(token.ToString());
if (bytes.Length > 0) if (bytes.Length > 0)
@ -143,8 +112,8 @@ public class DefaultForwardFlow : IForwardFlow
throw new ArgumentOutOfRangeException(); throw new ArgumentOutOfRangeException();
} }
mappedRes["oss_key"] = oss!.Key; mappedRes["oss_key"] = oss.Key;
mappedRes["oss_url"] = oss!.TempUrl; mappedRes["oss_url"] = oss.TempUrl;
} }
#endregion #endregion
@ -159,7 +128,7 @@ public class DefaultForwardFlow : IForwardFlow
/// <exception cref="BusinessException"></exception> /// <exception cref="BusinessException"></exception>
public virtual void AfterRequest(ForwardCoreContext context) public virtual void AfterRequest(ForwardCoreContext context)
{ {
if (context.ServiceProviderRequestLog.Response == RequestLogDto.Init) if (context.ServiceProviderRequestLog.Response == null)
{ {
ThrowBusinessException(context, "请注意维护服务商日志!"); ThrowBusinessException(context, "请注意维护服务商日志!");
} }
@ -205,9 +174,8 @@ public class DefaultForwardFlow : IForwardFlow
catch (Exception) catch (Exception)
{ {
ThrowBusinessException(context, ThrowBusinessException(context,
$"期望返回类型:{context.TargetInterface.ResponseContentType},解析过程中发生异常", $"期望返回类型:{context.TargetInterface.ResponseContentType},解析过程中发生异常");
RequestServiceProviderFailedCommon.DeSerializeFailedCode); return default!;
return default;
} }
} }
@ -241,7 +209,7 @@ public class DefaultForwardFlow : IForwardFlow
} }
// 失败 // 失败
string errorMsg = null; string? errorMsg = null;
var errorConfig = context.ReturnConfigList var errorConfig = context.ReturnConfigList
.FirstOrDefault(x => x.ConfigType == InterfaceReturnConfigType.ErrorMsg); .FirstOrDefault(x => x.ConfigType == InterfaceReturnConfigType.ErrorMsg);
if (errorConfig != null) if (errorConfig != null)
@ -250,8 +218,7 @@ public class DefaultForwardFlow : IForwardFlow
} }
// 业务失败发飞书 // 业务失败发飞书
ThrowBusinessException(context, errorMsg ?? "未通过状态码验证。", ThrowBusinessException(context, errorMsg ?? "未通过状态码验证。");
RequestServiceProviderFailedCommon.VerifyStatusFailedCode);
} }
/// <summary> /// <summary>
@ -259,10 +226,8 @@ public class DefaultForwardFlow : IForwardFlow
/// </summary> /// </summary>
/// <param name="context">上下文</param> /// <param name="context">上下文</param>
/// <param name="msg">异常消息</param> /// <param name="msg">异常消息</param>
/// <param name="code">异常状态码</param>
/// <exception cref="BusinessException"></exception> /// <exception cref="BusinessException"></exception>
protected virtual void ThrowBusinessException(ForwardCoreContext context, string msg, protected virtual void ThrowBusinessException(ForwardCoreContext context, string msg)
string code = "400")
{ {
if (context.ServiceProviderRequestLog.Cost == default) if (context.ServiceProviderRequestLog.Cost == default)
{ {
@ -334,8 +299,7 @@ public class DefaultForwardFlow : IForwardFlow
msg.Headers.Remove(item.FieldName); msg.Headers.Remove(item.FieldName);
if (!msg.Headers.TryAddWithoutValidation(item.FieldName, item.FieldValue)) if (!msg.Headers.TryAddWithoutValidation(item.FieldName, item.FieldValue))
{ {
ThrowBusinessException(context, $"请求头无法插入 {item.FieldName}{item.FieldValue}", ThrowBusinessException(context, $"请求头无法插入 {item.FieldName}{item.FieldValue}");
RequestServiceProviderFailedCommon.HeaderInsertFailed);
} }
} }
@ -348,16 +312,14 @@ public class DefaultForwardFlow : IForwardFlow
catch (TaskCanceledException ex) catch (TaskCanceledException ex)
{ {
// 捕获超时异常 // 捕获超时异常
ThrowBusinessException(context, "请求超时," + ex.Message, ThrowBusinessException(context, "请求超时," + ex.Message);
RequestServiceProviderFailedCommon.RequestFailedCode);
} }
catch (HttpRequestException rex) catch (HttpRequestException rex)
{ {
ThrowBusinessException(context, "请求失败," + rex.Message, ThrowBusinessException(context, "请求失败," + rex.Message);
RequestServiceProviderFailedCommon.RequestFailedCode);
} }
return default; return default!;
} }
protected virtual async Task<string> RequestCoreAsync(ForwardCoreContext context, HttpRequestMessage msg) protected virtual async Task<string> RequestCoreAsync(ForwardCoreContext context, HttpRequestMessage msg)
@ -368,21 +330,18 @@ public class DefaultForwardFlow : IForwardFlow
client.Timeout = TimeSpan.FromSeconds((double)context.TargetInterface.Timeout); 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) if (context.Feature.IsWait)
{ {
while (!await CanVisitInterfaceAsync(context.TargetInterface.Code, qps)) while (!await CanVisitInterfaceAsync(context))
{ {
await Task.Delay(context.Feature.SpinPeriod); await Task.Delay(context.Feature.SpinPeriod);
} }
} }
else 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 else
{ {
nameValueCollection = JsonConvert.DeserializeObject<Dictionary<string, string>>(body); nameValueCollection = JsonConvert.DeserializeObject<Dictionary<string, string>>(body) ??
new Dictionary<string, string>();
} }
msg.Content = new FormUrlEncodedContent(nameValueCollection); msg.Content = new FormUrlEncodedContent(nameValueCollection);
@ -485,7 +445,7 @@ public class DefaultForwardFlow : IForwardFlow
case ContentType.Xml: // json转xml case ContentType.Xml: // json转xml
var jsonStr = JsonConvert.SerializeObject(body); var jsonStr = JsonConvert.SerializeObject(body);
var xmlDoc = JsonConvert.DeserializeXmlNode(jsonStr); var xmlDoc = JsonConvert.DeserializeXmlNode(jsonStr);
data = xmlDoc?.OuterXml; data = xmlDoc?.OuterXml ?? string.Empty;
break; break;
case ContentType.FormUrlEncoded: case ContentType.FormUrlEncoded:
data = JsonConvert.SerializeObject(body); //到请求时解析成Dictionary<string, string> data = JsonConvert.SerializeObject(body); //到请求时解析成Dictionary<string, string>
@ -535,7 +495,7 @@ public class DefaultForwardFlow : IForwardFlow
} }
} }
item.FieldValue = context.GetAccountFieldValueOrNull(fieldName); item.FieldValue = context.GetAccountFieldValueOrNull(fieldName) ?? string.Empty;
break; break;
case GlobalConst.Url: case GlobalConst.Url:
item.FieldValue = context.TargetInterface.RequestAddress; item.FieldValue = context.TargetInterface.RequestAddress;
@ -553,7 +513,7 @@ public class DefaultForwardFlow : IForwardFlow
: (timeStamp / 1000).ToString(), // s : (timeStamp / 1000).ToString(), // s
GlobalConst.DateTime => TurnDate(now, item.Description), GlobalConst.DateTime => TurnDate(now, item.Description),
GlobalConst.Url => context.TargetInterface.RequestAddress, GlobalConst.Url => context.TargetInterface.RequestAddress,
GlobalConst.FromAccount => context.GetAccountFieldValueOrNull(item.Name), GlobalConst.FromAccount => context.GetAccountFieldValueOrNull(item.Name) ?? string.Empty,
_ => item.Value // 不处理 _ => 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(GlobalConst.DefaultDateStyle);
{
return dateTime.ToString(dateStyle);
}
catch (Exception)
{
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<ParameterNode> list) async Task<bool> CanVisitInterfaceAsync(ForwardCoreContext context)
{ {
if (list == null || list.Count == 0) return; string interfaceCode = context.TargetInterface.Code;
foreach (var item in list) int qps = context.Feature.TargetInterfaceQps;
{ var hash = context.Feature.AccountHashCode;
if (!string.IsNullOrEmpty(item.MapAlias))
{
item.MapAlias = "[*]." + item.MapAlias;
}
VirtualizePath(item.Children);
}
}
async Task<bool> CanVisitInterfaceAsync(string interfaceCode, int qps)
{
// REDIS限流 // REDIS限流
string redisKey = $"{GlobalConst.ServiceProviderInterfaceQPSKeyPrefix}:{interfaceCode}"; string redisKey = $"{GlobalConst.ServiceProviderInterfaceQpsKeyPrefix}:{interfaceCode}:{hash}";
var num = await RedisHelper.Client.EvalAsync(""" var num = await RedisHelper.Client.EvalAsync("""
local redisKey=ARGV[1]; local redisKey=ARGV[1];
local qps=ARGV[2]; local qps=ARGV[2];
@ -685,4 +635,25 @@ public class DefaultForwardFlow : IForwardFlow
""", null, redisKey, qps); """, null, redisKey, qps);
return (long)num != 0; 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<ForwardCoreContext, Task>(arg), script);
await (Task)@delegate.DynamicInvoke(context)!;
}
else
{
var @delegate = DelegateDic.GetOrAdd(scriptHash,
static (_, arg) => NDelegate.RandomDomain().Action<ForwardCoreContext>(arg), script);
@delegate.DynamicInvoke(context);
}
}
} }

View File

@ -4,8 +4,6 @@ namespace InterfaceForward.Application.Helpers;
public static class CommonExtensions public static class CommonExtensions
{ {
public static async Task<byte[]> GetAllBytesAsync(this IFormFile file) public static async Task<byte[]> GetAllBytesAsync(this IFormFile file)
{ {
using (var stream = file.OpenReadStream()) using (var stream = file.OpenReadStream())

View File

@ -0,0 +1,51 @@
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
namespace InterfaceForward.Application.Helpers;
public class StringHelper
{
/// <summary>
/// 只能输入英文字符和键盘上常用的符号
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
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);
}
/// <summary>获取字符串的hash值</summary>
/// <param name="str"></param>
/// <returns></returns>
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();
}
}

View File

@ -0,0 +1,41 @@
using InterfaceForward.Domain.Shared.Enum;
namespace InterfaceForward.Application.HostServices;
public class AppSubscribeEvent
{
/// <summary>
/// 操作类型
/// </summary>
public OptionType OptionType { get; set; }
/// <summary>
/// 应用key
/// </summary>
public string AppKey { get; set; }
/// <summary>
/// 系统接口code
/// </summary>
public string SystemInterfaceCode { get; set; }
/// <summary>
/// 服务商code
/// </summary>
public string ServiceProviderCode { get; set; }
/// <summary>
/// 【慎重】mq预取数量服务并发度给0就是默认4
/// </summary>
public ushort FetchCount { get; set; }
/// <summary>
/// 【慎重】失败重回队列一般false即接口通不做重试否则业务错误可能死循环
/// </summary>
public bool FailedRequeue { get; set; }
/// <summary>
/// 【慎重】删除队列删除专用一般false即接口通只解除绑定不删除队列
/// </summary>
public bool IsDeleteQueue { get; set; }
}

View File

@ -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查询所有订阅";
}
}

View File

@ -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<AppSubscribeConfigEntity> appSubscribeConfigRepository,
RabbitClient rabbitClient, AppSubscribeEventHandler appSubscribeEventHandler)
{
_appSubscribeConfigRepository = appSubscribeConfigRepository;
_rabbitClient = rabbitClient;
_appSubscribeEventHandler = appSubscribeEventHandler;
}
public async Task StartAsync(CancellationToken cancellationToken)
{
// todoredis注册连接恢复订阅
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<AppSubscribeEvent>(str);
AsyncHelper.RunSync(() => _appSubscribeEventHandler.Handle(@event));
}
}
}

View File

@ -18,6 +18,7 @@
<PackageReference Include="Aliyun.OSS.SDK.NetCore" Version="2.13.0" /> <PackageReference Include="Aliyun.OSS.SDK.NetCore" Version="2.13.0" />
<PackageReference Include="DotNetCore.Natasha.CSharp" Version="5.2.2.1"/> <PackageReference Include="DotNetCore.Natasha.CSharp" Version="5.2.2.1"/>
<PackageReference Include="Fake.AspNetCore" Version="8.0.0-preview8.5" /> <PackageReference Include="Fake.AspNetCore" Version="8.0.0-preview8.5" />
<PackageReference Include="Fake.EventBus.RabbitMQ" Version="1.0.0" />
<PackageReference Include="Fake.ObjectMapping.AutoMapper" Version="8.0.0-preview8.5" /> <PackageReference Include="Fake.ObjectMapping.AutoMapper" Version="8.0.0-preview8.5" />
<PackageReference Include="FreeRedis" Version="1.2.15" /> <PackageReference Include="FreeRedis" Version="1.2.15" />
</ItemGroup> </ItemGroup>

View File

@ -1,5 +1,6 @@
using System.Security.Authentication; using System.Security.Authentication;
using Fake.AspNetCore; using Fake.AspNetCore;
using Fake.EventBus.RabbitMQ;
using Fake.Modularity; using Fake.Modularity;
using Fake.ObjectMapping.AutoMapper; using Fake.ObjectMapping.AutoMapper;
using InterfaceForward.Application.Contracts; using InterfaceForward.Application.Contracts;
@ -10,6 +11,7 @@ namespace InterfaceForward.Application;
[DependsOn(typeof(FakeAspNetCoreModule))] [DependsOn(typeof(FakeAspNetCoreModule))]
[DependsOn(typeof(FakeObjectMappingAutoMapperModule))] [DependsOn(typeof(FakeObjectMappingAutoMapperModule))]
[DependsOn(typeof(FakeEventBusRabbitMqModule))]
[DependsOn(typeof(InterfaceForwardRepositoriesModule))] [DependsOn(typeof(InterfaceForwardRepositoriesModule))]
public class InterfaceForwardApplicationModule:FakeModule public class InterfaceForwardApplicationModule:FakeModule
{ {

View File

@ -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; }
}

View File

@ -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<BatchForwardEventHandler> 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<BatchForwardEvent>(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<ForwardCoreContext> 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);
}
}
}

View File

@ -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<IDeclaration>
}

View File

@ -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 Fake.DependencyInjection;
using InterfaceForward.Application.Contracts; using InterfaceForward.Application.Contracts;
using InterfaceForward.Application.Contracts.ForwardCore; using InterfaceForward.Application.Contracts.ForwardCore;
@ -16,93 +19,85 @@ using SJZY.InterfaceRelay.Repository.Log.ValueObjects;
namespace InterfaceForward.Application.Services; namespace InterfaceForward.Application.Services;
public class InterfaceForwardCommon : ITransientDependency public class InterfaceForwardCommon(
InterfaceForwardQuery interfaceForwardQuery,
ILogRepository logRepository,
IServiceProvider serviceProvider,
IOptionsSnapshot<InterfaceRelayOptions> options)
: ITransientDependency
{ {
/// <summary> /// <summary>
/// 上下文缓存数据:映射,账户 /// 缓存数据:映射,账户 默认过期时间
/// </summary> /// </summary>
public static readonly int ContextCacheTtl = int.MaxValue; public static readonly int DefaultCacheTtl = int.MaxValue;
private readonly Dictionary<string, IForwardFlow> _flows = new(); private readonly Dictionary<string, IForwardFlow> _flows = new();
private readonly InterfaceForwardQuery _interfaceForwardQuery; private readonly InterfaceRelayOptions _options = options.Value;
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<InterfaceRelayOptions> options)
{
_interfaceForwardQuery = interfaceForwardQuery;
_logRepository = logRepository;
// _segContext = segContext;
_serviceProvider = serviceProvider;
_interfaceRelayOptions = options.Value;
}
public async Task<List<ForwardCoreContext>> VerifyBusinessAndBuildForwardContexts(string upStreamCode, public async Task<List<ForwardCoreContext>> VerifyBusinessAndBuildForwardContexts(string upStreamCode,
string serviceProviderCode) string serviceProviderCode)
{ {
// todo字符串优化
// tips上下文的缓存不维护在发布时统一替代。 // tips上下文的缓存不维护在发布时统一替代。
var contextCacheKey = GetContextCacheKey(upStreamCode, serviceProviderCode); var contextCacheKey = GetContextCacheKey(upStreamCode, serviceProviderCode);
// _segContext.Context.Span.AddLog(LogEvent.Message("【转发核心上下文】开始构建"));
var cacheContext = (await RedisHelper.Client.GetAsync<string>(contextCacheKey)) var cacheContext = (await RedisHelper.Client.GetAsync<string>(contextCacheKey))
.ToObject<ForwardCoreContextCache>(); .ToObject<ForwardCoreContextCache>();
if (cacheContext == null) 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: "系统接口不存在"); if (systemInterface == null) throw new BusinessException(message: "系统接口不存在");
_logRepository.AppRequestLog.InterfaceCode = systemInterface.Code; logRepository.AppRequestLog.InterfaceCode = systemInterface.Code;
_logRepository.AppRequestLog.InterfaceName = systemInterface.Name; logRepository.AppRequestLog.InterfaceName = systemInterface.Name;
var serviceProvider = await _interfaceForwardQuery.GetServiceProviderByCodeAsync(serviceProviderCode); var serviceProviderDto = await interfaceForwardQuery.GetServiceProviderByCodeAsync(serviceProviderCode);
if (serviceProvider == null) throw new BusinessException(message: "服务商不存在"); if (serviceProviderDto == null) throw new BusinessException(message: "服务商不存在");
cacheContext = await _interfaceForwardQuery.BuildForwardCoreContext(systemInterface, serviceProvider); cacheContext = await interfaceForwardQuery.BuildForwardCoreContext(systemInterface, serviceProviderDto);
await RedisHelper.Client.SetAsync(contextCacheKey, cacheContext.ToJson(), ContextCacheTtl); await RedisHelper.Client.SetAsync(contextCacheKey, cacheContext.ToJson(), DefaultCacheTtl);
} }
// _segContext.Context.Span.AddLog(LogEvent.Message("【转发核心上下文】构建完成"));
// 记录系统接口日志 // 记录系统接口日志
_logRepository.AppRequestLog.InterfaceCode = cacheContext.OriginalInterface.Code; logRepository.AppRequestLog.InterfaceCode = cacheContext.OriginalInterface.Code;
_logRepository.AppRequestLog.InterfaceName = cacheContext.OriginalInterface.Name; logRepository.AppRequestLog.InterfaceName = cacheContext.OriginalInterface.Name;
// tips服务商主体账户的缓存实时维护 // 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<string>(accountCacheKey)) var accountFieldCache = (await RedisHelper.Client.GetAsync<string>(accountCacheKey))
.ToObject<List<ServiceProviderAccountFieldWithValueDto>>(); .ToObject<List<ServiceProviderAccountFieldWithValueDto>>();
if (accountFieldCache == null) if (accountFieldCache == null)
{ {
// _segContext.Context.Span.AddLog(LogEvent.Message("【服务商账户】没能命中缓存,重新构建")); var serviceProviderDto = cacheContext.TargetServiceProvider;
var serviceProvider = cacheContext.TargetServiceProvider; var accountId = await interfaceForwardQuery.GetAccountIdAsync(serviceProviderDto.Id, logRepository.AppId);
var accountId = await _interfaceForwardQuery.GetAccountIdAsync(serviceProvider.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); var ttl = await HandleAuthInterfaceAsync(cacheContext, accountFieldCache);
await RedisHelper.Client.SetAsync(accountCacheKey, accountFieldCache.ToJson(), ttl); await RedisHelper.Client.SetAsync(accountCacheKey, accountFieldCache.ToJson(), ttl);
} }
// _segContext.Context.Span.AddLog(LogEvent.Message("【服务商账户】构建完成"));
// 组装转发上下文 // 组装转发上下文
var res = new List<ForwardCoreContext>(); var res = new List<ForwardCoreContext>();
foreach (var item in cacheContext.TargetInterfaces) foreach (var item in cacheContext.TargetInterfaces)
{ {
item.TargetInterface.Timeout ??= cacheContext.TargetServiceProvider.Timeout; 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 var context = new ForwardCoreContext
{ {
TargetInterface = item.TargetInterface, TargetInterface = item.TargetInterface,
@ -117,42 +112,37 @@ public class InterfaceForwardCommon : ITransientDependency
var token = context.FixedFieldList.FirstOrDefault(x => x.FieldValue == GlobalConst.Token); var token = context.FixedFieldList.FirstOrDefault(x => x.FieldValue == GlobalConst.Token);
if (token != default) 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.Name = cacheContext.TargetServiceProvider.Name;
context.ServiceProviderRequestLog.Address = context.TargetInterface.RequestAddress; context.ServiceProviderRequestLog.Address = context.TargetInterface.RequestAddress;
context.ServiceProviderRequestLog.InterfaceCode = context.TargetInterface.Code; context.ServiceProviderRequestLog.InterfaceCode = context.TargetInterface.Code;
context.ServiceProviderRequestLog.InterfaceName = context.TargetInterface.Name; 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) if (flow == default)
throw new BusinessException(message: "找不到接口转发流程" + context.TargetInterface.FlowCode); throw new BusinessException(message: "找不到接口转发流程" + context.TargetInterface.FlowCode);
_flows.Add(context.TargetInterface.Code, flow); _flows.Add(context.TargetInterface.Code, flow);
res.Add(context); res.Add(context);
} }
// tips把出参映射树放到最后一个上下文中是为了在流程结束时执行出参映射 // tips把出参映射树放到最后一个上下文中是为了在流程结束时执行出参映射
res.Last().OutParamTreeList = cacheContext.OutParamTreeList; res.Last().OutParamTreeList = cacheContext.OutParamTreeList;
// _segContext.Context.Span.AddLog(LogEvent.Message("【转发上下文】全部组装完成"));
return res; return res;
} }
// 优先用服务商接口指定流程,其次使用服务商的,再其次是默认的 // 优先用服务商接口指定流程,其次使用服务商的,再其次是默认的
public IForwardFlow GetForwardFlow(string interfaceFlowCode, string serviceProviderFlowCode) public IForwardFlow GetForwardFlow(string flowCode)
{ {
var flowCode = interfaceFlowCode.IsNullOrWhiteSpace() return serviceProvider.GetServices<IForwardFlow>()
? serviceProviderFlowCode.IsNullOrWhiteSpace() .FirstOrDefault(x => x.GetType().Name == flowCode)?? throw new BusinessException("");
? nameof(DefaultForwardFlow)
: serviceProviderFlowCode
: interfaceFlowCode;
return _serviceProvider.GetServices<IForwardFlow>()
.FirstOrDefault(x => x.GetType().Name == flowCode);
} }
/// <summary> /// <summary>
@ -170,7 +160,7 @@ public class InterfaceForwardCommon : ITransientDependency
if (interfaceDto == default) if (interfaceDto == default)
{ {
return ContextCacheTtl; return DefaultCacheTtl;
} }
var configs = cacheContext.ServiceProviderAuthConfigs; var configs = cacheContext.ServiceProviderAuthConfigs;
@ -179,10 +169,16 @@ public class InterfaceForwardCommon : ITransientDependency
var body = new JObject(); var body = new JObject();
foreach (var item in configs.Where(x => x.Flag == AuthParameterFlag.InParameter)) foreach (var item in configs.Where(x => x.Flag == AuthParameterFlag.InParameter))
{ {
Debug.Assert(item.ParameterAlias != null, "item.ParameterAlias != null");
body[item.ParameterAlias] = item.Value1; 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 var authContext = new ForwardCoreContext
{ {
TargetInterface = interfaceDto.TargetInterface, TargetInterface = interfaceDto.TargetInterface,
@ -191,12 +187,12 @@ public class InterfaceForwardCommon : ITransientDependency
ReturnConfigList = interfaceDto.ReturnConfigList, ReturnConfigList = interfaceDto.ReturnConfigList,
InParamTreeList = null, InParamTreeList = null,
OutParamTreeList = null, OutParamTreeList = null,
PrefixScript = null, PrefixScript = interfaceDto.PrefixScript,
PostfixScript = null, PostfixScript = interfaceDto.PostfixScript,
OriginalInterfaceInput = body, OriginalInterfaceInput = body,
FormFieldList = interfaceDto.FormFieldList // normal case is null 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, Name = cacheContext.TargetServiceProvider.Name,
Address = authContext.TargetInterface.RequestAddress, Address = authContext.TargetInterface.RequestAddress,
@ -204,22 +200,16 @@ public class InterfaceForwardCommon : ITransientDependency
InterfaceName = authContext.TargetInterface.Name InterfaceName = authContext.TargetInterface.Name
}; };
// 走动态脚本
var scriptConfig = configs.FirstOrDefault(x => x.Flag == AuthParameterFlag.Script);
if (scriptConfig != default && !scriptConfig.Value1.IsNullOrEmpty())
{
// 使用Natasha构建动态表达式
return NDelegate.RandomDomain().Func<ForwardCoreContext, int>(scriptConfig.Value1)(authContext);
}
// 授权也走主流程 // 授权也走主流程
if (!cacheContext.TargetServiceProvider.AuthInterfaceCode.IsNullOrWhiteSpace()) if (!cacheContext.TargetServiceProvider.AuthInterfaceCode.IsNullOrWhiteSpace())
{ {
var flow = GetForwardFlow(interfaceDto.TargetInterface.FlowCode, Debug.Assert(cacheContext.ServiceProviderAuthInterface != null, "cacheContext.ServiceProviderAuthInterface != null");
cacheContext.TargetServiceProvider.FlowCode); var flow = GetForwardFlow(cacheContext.ServiceProviderAuthInterface.TargetInterface.FlowCode!);
if (flow == default) if (flow == default)
throw new BusinessException(message: "找不到接口转发流程" + interfaceDto.TargetInterface.FlowCode); throw new BusinessException(message: "找不到接口转发流程" +
_flows.Add(interfaceDto.TargetInterface.Code, flow); 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); var tokenConfig = configs.FirstOrDefault(x => x.Flag == AuthParameterFlag.Token);
if (tokenConfig != default) if (tokenConfig != default)
{ {
Debug.Assert(tokenConfig.ParameterAlias != null, "tokenConfig.ParameterAlias != null");
accountFieldCache.Add(new ServiceProviderAccountFieldWithValueDto accountFieldCache.Add(new ServiceProviderAccountFieldWithValueDto
{ {
FieldName = GlobalConst.Token, FieldName = GlobalConst.Token,
FieldValue = tokenConfig.Value1.IsNullOrWhiteSpace() FieldValue = tokenConfig.Value1.IsNullOrWhiteSpace()
? response[tokenConfig.ParameterAlias]?.ToString() ? response.SelectToken(tokenConfig.ParameterAlias)?.ToString() ??
: tokenConfig.Value1.Trim() + " " + response[tokenConfig.ParameterAlias] throw new BusinessException($"授权接口返回报文中未找到{tokenConfig.ParameterAlias}")
: tokenConfig.Value1!.Trim() + " " + response.SelectToken(tokenConfig.ParameterAlias)
}); });
} }
@ -247,8 +239,10 @@ public class InterfaceForwardCommon : ITransientDependency
return ttl; 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<EffectTimeUnit>(effectTimeConfig.Value1) switch ttl = Enum.Parse<EffectTimeUnit>(effectTimeConfig.Value1) switch
{ {
EffectTimeUnit.Second => expiredValue, EffectTimeUnit.Second => expiredValue,
@ -357,20 +351,23 @@ public class InterfaceForwardCommon : ITransientDependency
{ {
try 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]; var flow = _flows[context.TargetInterface.Code];
// _segContext.Context.Span.AddLog(LogEvent.Message("开始入参重构"));
context.OriginalInterfaceMappedInput = await flow.InParamRestructure(context); 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); var response = await flow.ForwardAsync(context);
// _segContext.Context.Span.AddLog(LogEvent.Message("* 请求结束"));
if (isMulti) return response; // 一对多不在这映射 if (isMulti) return response; // 一对多不在这映射
var res = await flow.OutParamRestructure(context, response); var res = await flow.OutParamRestructure(context, response);
if (context.Feature.IsWait)
// _segContext.Context.Span.AddLog(LogEvent.Message("完成反参重构")); {
context.ServiceProviderRequestLog.Response2 = res.ToJson();
}
return res; return res;
} }
@ -384,10 +381,15 @@ public class InterfaceForwardCommon : ITransientDependency
} }
finally 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; return objectArray;
} }
/// <summary>获取字符串的hash值</summary>
/// <param name="str"></param>
/// <returns></returns>
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();
}
} }

View File

@ -5,8 +5,11 @@ using Newtonsoft.Json.Linq;
using InterfaceForward.Application.Contracts; using InterfaceForward.Application.Contracts;
using InterfaceForward.Application.Contracts.ForwardCore; using InterfaceForward.Application.Contracts.ForwardCore;
using InterfaceForward.Application.Filters; using InterfaceForward.Application.Filters;
using InterfaceForward.Application.Rabbit;
using InterfaceForward.Domain.Shared;
using InterfaceForward.Repositories.Log.Services; using InterfaceForward.Repositories.Log.Services;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using RabbitMQ.Client;
namespace InterfaceForward.Application.Services; namespace InterfaceForward.Application.Services;
@ -21,29 +24,100 @@ public class InterfaceForwardService : BaseService, IInterfaceForwardService
{ {
private readonly InterfaceForwardCommon _forwardCommon; private readonly InterfaceForwardCommon _forwardCommon;
private readonly ILogRepository _logRepository; private readonly ILogRepository _logRepository;
private readonly InterfaceRelayOptions _options; private readonly RabbitClient _rabbitClient;
/// <inheritdoc /> /// <inheritdoc />
public InterfaceForwardService( public InterfaceForwardService(ILogRepository logRepository,
ILogRepository logRepository InterfaceForwardCommon forwardCommon, RabbitClient rabbitClient)
, InterfaceForwardCommon forwardCommon
, IOptionsSnapshot<InterfaceRelayOptions> options
)
{ {
_logRepository = logRepository; _logRepository = logRepository;
_forwardCommon = forwardCommon; _forwardCommon = forwardCommon;
_options = options.Value; _rabbitClient = rabbitClient;
} }
/// <inheritdoc />
[HttpPost("BatchForwardThenPushToQueue")]
public async Task<InterfaceRelayUnifyResultDto> BatchForwardThenPushToQueueAsync(
[FromQuery] [Required] string upStreamCode,
[FromQuery] [Required] string serviceProviderCode,
[FromBody] [Required] List<object> 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")] [HttpPost("ForwardWithSubscribe")]
public async Task<InterfaceRelayUnifyResultDto> ForwardWithSubscribeAsync( public async Task<InterfaceRelayUnifyResultDto> ForwardWithSubscribeAsync(
[FromQuery] [Required] string upStreamCode, [FromQuery] [Required] string upStreamCode,
[FromQuery] [Required] string serviceProviderCode, [FromQuery] [Required] string serviceProviderCode,
[FromQuery] int mainBodyId,
[FromBody] [Required] object data, [FromBody] [Required] object data,
[FromHeader] string? attach = null, [FromHeader] string attach = null,
[FromHeader] bool isReturnMessage = false, [FromHeader] bool isReturnMessage = false,
[FromHeader] string? failedSubscribeName = null) [FromHeader] string failedSubscribeName = null)
{ {
if (JToken.FromObject(data) is not JObject jobject) if (JToken.FromObject(data) is not JObject jobject)
{ {
@ -64,7 +138,7 @@ public class InterfaceForwardService : BaseService, IInterfaceForwardService
? await _forwardCommon.InternalForwardAsync(context) ? await _forwardCommon.InternalForwardAsync(context)
: await _forwardCommon.InternalForward2Async(contextList); : await _forwardCommon.InternalForward2Async(contextList);
return new InterfaceRelayUnifyResultDto return new InterfaceRelayUnifyResultDto(true, res)
{ {
RequestId = context.ServiceProviderRequestLog.RequestId, RequestId = context.ServiceProviderRequestLog.RequestId,
OriginalMessage = isReturnMessage ? context.ServiceProviderRequestLog.Response : null, OriginalMessage = isReturnMessage ? context.ServiceProviderRequestLog.Response : null,
@ -76,12 +150,11 @@ public class InterfaceForwardService : BaseService, IInterfaceForwardService
public async Task<InterfaceRelayUnifyResultDto> ReForwardAsync( public async Task<InterfaceRelayUnifyResultDto> ReForwardAsync(
[FromQuery] [Required] string upStreamCode, [FromQuery] [Required] string upStreamCode,
[FromQuery] [Required] string serviceProviderCode, [FromQuery] [Required] string serviceProviderCode,
[FromQuery] int mainBodyId,
[FromHeader] [Required] Guid requestId, [FromHeader] [Required] Guid requestId,
[FromBody] [Required] object data, [FromBody] [Required] object data,
[FromHeader] string? attach = null, [FromHeader] string attach = null,
[FromHeader] bool isReturnMessage = false, [FromHeader] bool isReturnMessage = false,
[FromHeader] string? failedSubscribeName = null) [FromHeader] string failedSubscribeName = null)
{ {
if (JToken.FromObject(data) is not JObject jobject) if (JToken.FromObject(data) is not JObject jobject)
{ {
@ -100,7 +173,7 @@ public class InterfaceForwardService : BaseService, IInterfaceForwardService
? jobject ? jobject
: new JObject : new JObject
{ {
[upStreamCode] = jobject [upStreamCode!] = jobject
}; };
// 从第一个失败的开始重试 // 从第一个失败的开始重试
@ -109,7 +182,7 @@ public class InterfaceForwardService : BaseService, IInterfaceForwardService
? await _forwardCommon.InternalForwardAsync(context) ? await _forwardCommon.InternalForwardAsync(context)
: await _forwardCommon.InternalForward3Async(contextList, requestLogs); : await _forwardCommon.InternalForward3Async(contextList, requestLogs);
return new InterfaceRelayUnifyResultDto return new InterfaceRelayUnifyResultDto(true, res)
{ {
RequestId = context.ServiceProviderRequestLog.RequestId, RequestId = context.ServiceProviderRequestLog.RequestId,
OriginalMessage = isReturnMessage ? _logRepository.ServiceProviderRequestLog.Response : null, OriginalMessage = isReturnMessage ? _logRepository.ServiceProviderRequestLog.Response : null,

View File

@ -2,17 +2,16 @@
using DiffPlex; using DiffPlex;
using DiffPlex.DiffBuilder; using DiffPlex.DiffBuilder;
using DiffPlex.DiffBuilder.Model; using DiffPlex.DiffBuilder.Model;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using InterfaceForward.Application.Contracts.Dtos.Interface; using InterfaceForward.Application.Contracts.Dtos.Interface;
using InterfaceForward.Application.Helpers; using InterfaceForward.Application.Helpers;
using InterfaceForward.Domain.Shared.Dtos; using InterfaceForward.Domain.Shared.Dtos;
using InterfaceForward.Domain.Shared.Helpers;
using InterfaceForward.Repositories; using InterfaceForward.Repositories;
using InterfaceForward.Repositories.Interface.Entitys; using InterfaceForward.Repositories.Interface.Entitys;
using InterfaceForward.Repositories.Interface.Services; using InterfaceForward.Repositories.Interface.Services;
using InterfaceForward.Repositories.Interface.ValueObjects; using InterfaceForward.Repositories.Interface.ValueObjects;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
namespace InterfaceForward.Application.Services.InterfaceMap; namespace InterfaceForward.Application.Services.InterfaceMap;
@ -20,27 +19,19 @@ namespace InterfaceForward.Application.Services.InterfaceMap;
/// 接口映射发布修改 /// 接口映射发布修改
/// </summary> /// </summary>
[ApiExplorerSettings(GroupName = "接口映射服务")] [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, // 设置格式化输出 Formatting = Formatting.Indented, // 设置格式化输出
Converters = { new StringEnumConverter() }, Converters = { new StringEnumConverter() },
ContractResolver = new CustomContractResolver() // 设置自定义的ContractResolver 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;
}
/// <summary> /// <summary>
/// 获取接口映射 发布记录列表 /// 获取接口映射 发布记录列表
/// </summary> /// </summary>
@ -53,7 +44,7 @@ public class InterfaceMapPublishService : BaseService
public async Task<List<InterfaceMapPublishedOutPutVO>> GetPublishedListAsync([Required] int interfaceId, public async Task<List<InterfaceMapPublishedOutPutVO>> GetPublishedListAsync([Required] int interfaceId,
[Required] int serviceProviderId, int pageIndex = 1, int pageSize = 50) [Required] int serviceProviderId, int pageIndex = 1, int pageSize = 50)
{ {
return await _interfaceMapPublishRepository.GetPublishedListAsync(interfaceId, serviceProviderId, pageIndex, return await interfaceMapPublishRepository.GetPublishedListAsync(interfaceId, serviceProviderId, pageIndex,
pageSize); pageSize);
} }
@ -68,18 +59,18 @@ public class InterfaceMapPublishService : BaseService
public async Task<InterfaceMapPublishedDifferenceVO> GetPublishCompareAsync([Required] string interfaceCode, public async Task<InterfaceMapPublishedDifferenceVO> GetPublishCompareAsync([Required] string interfaceCode,
[Required] int serviceProviderId, string version) [Required] int serviceProviderId, string version)
{ {
var systemInterface = await _interfaceForwardQuery.GetInterfaceByCodeAsync(interfaceCode); var systemInterface = await interfaceForwardQuery.GetInterfaceByCodeAsync(interfaceCode);
if (systemInterface == null) throw new BusinessException(message: "系统接口不存在"); 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: "服务商不存在"); if (serviceProvider == null) throw new BusinessException(message: "服务商不存在");
// 没有指定比对的版本,就是比对数据库最新数据 // 没有指定比对的版本,就是比对数据库最新数据
string dbContent = version.IsNullOrWhiteSpace() var dbContent = version.IsNullOrWhiteSpace()
? JsonConvert.SerializeObject( ? JsonConvert.SerializeObject(
await _interfaceForwardQuery.BuildForwardCoreContext(systemInterface, serviceProvider), await interfaceForwardQuery.BuildForwardCoreContext(systemInterface, serviceProvider),
Settings) Settings)
: await _interfaceMapPublishRepository.GetFirstAsync(a => : await interfaceMapPublishRepository.GetFirstAsync(a =>
a.InterfaceId == systemInterface.Id && a.ServiceProviderId == serviceProviderId && a.InterfaceId == systemInterface.Id && a.ServiceProviderId == serviceProviderId &&
a.PublishedVersion == version, a => a.PublishedContent); a.PublishedVersion == version, a => a.PublishedContent);
@ -88,7 +79,7 @@ public class InterfaceMapPublishService : BaseService
string redisContent = JsonConvert.SerializeObject(cache, Settings); string redisContent = JsonConvert.SerializeObject(cache, Settings);
var differenceContent = CompareTexts(redisContent, dbContent ?? ""); var differenceContent = CompareTexts(redisContent, dbContent);
return new InterfaceMapPublishedDifferenceVO return new InterfaceMapPublishedDifferenceVO
{ {
@ -107,21 +98,21 @@ public class InterfaceMapPublishService : BaseService
[HttpPost("InterfaceMap/Publish")] [HttpPost("InterfaceMap/Publish")]
public async Task<bool> PublishAsync(InterfaceMapPublishInput input) public async Task<bool> PublishAsync(InterfaceMapPublishInput input)
{ {
var systemInterface = await _interfaceForwardQuery.GetInterfaceByCodeAsync(input.InterfaceCode); var systemInterface = await interfaceForwardQuery.GetInterfaceByCodeAsync(input.InterfaceCode);
if (systemInterface == null) throw new BusinessException(message: "系统接口不存在"); 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: "服务商不存在"); if (serviceProvider == null) throw new BusinessException(message: "服务商不存在");
// 确认比对期间数据是否被篡改 // 确认比对期间数据是否被篡改
var differ = new Differ(); var differ = new Differ();
var inlineBuilder = new InlineDiffBuilder(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 content = JsonConvert.SerializeObject(context, Settings);
var diffResult = inlineBuilder.BuildDiffModel(content, input.DBContent); var diffResult = inlineBuilder.BuildDiffModel(content, input.DBContent);
if (diffResult.HasDifferences) throw new BusinessException(message: "数据库已经发生变更,请重新获取最新数据进行比对"); 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); x.UpStreamId == systemInterface.Id && x.ServiceProviderId == serviceProvider.Id);
var publishEntity = new InterfaceMapPublishedEntity var publishEntity = new InterfaceMapPublishedEntity
{ {
@ -137,15 +128,15 @@ public class InterfaceMapPublishService : BaseService
using var ts = TransacationHelper.GetReadCommitted(); using var ts = TransacationHelper.GetReadCommitted();
await _interfaceMapPublishRepository.InsertAsync(publishEntity); await interfaceMapPublishRepository.InsertAsync(publishEntity);
// 更新版本号 // 更新版本号
mapsEntities.ForEach(x => x.PublishedVersion = publishEntity.PublishedVersion); mapsEntities.ForEach(x => x.PublishedVersion = publishEntity.PublishedVersion);
await _interfaceMapRepository.UpdateRangeAsync(mapsEntities, x => x.PublishedVersion); await interfaceMapRepository.UpdateRangeAsync(mapsEntities, x => x.PublishedVersion);
// update cache // update cache
var redisKey = InterfaceForwardCommon.GetContextCacheKey(systemInterface.Code, serviceProvider.Code); 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(); ts.Complete();
return true; return true;
@ -160,19 +151,19 @@ public class InterfaceMapPublishService : BaseService
[HttpPost("InterfaceMap/PublishRollback")] [HttpPost("InterfaceMap/PublishRollback")]
public async Task<bool> PublishRollbackAsync(InterfaceMapRollbackInput input) public async Task<bool> PublishRollbackAsync(InterfaceMapRollbackInput input)
{ {
var systemInterface = await _interfaceForwardQuery.GetInterfaceByCodeAsync(input.InterfaceCode); var systemInterface = await interfaceForwardQuery.GetInterfaceByCodeAsync(input.InterfaceCode);
if (systemInterface == null) throw new BusinessException(message: "系统接口不存在"); 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: "服务商不存在"); 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); x.UpStreamId == systemInterface.Id && x.ServiceProviderId == input.ServiceProviderId);
if (mapsEntities == null || mapsEntities.Count == 0) if (mapsEntities == null || mapsEntities.Count == 0)
{ {
throw new BusinessException(message: "映射不存在"); 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.InterfaceId == systemInterface.Id && a.ServiceProviderId == input.ServiceProviderId &&
a.PublishedVersion == input.Version, a => a.PublishedContent); a.PublishedVersion == input.Version, a => a.PublishedContent);
// tips空是合法的虽然在业务上不存在 // tips空是合法的虽然在业务上不存在
@ -198,14 +189,14 @@ public class InterfaceMapPublishService : BaseService
using var ts = TransacationHelper.GetReadCommitted(); using var ts = TransacationHelper.GetReadCommitted();
await _interfaceMapPublishRepository.InsertAsync(publishEntity); await interfaceMapPublishRepository.InsertAsync(publishEntity);
// 更新版本号 // 更新版本号
mapsEntities.ForEach(x => x.PublishedVersion = publishEntity.PublishedVersion); 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, await RedisHelper.Client.SetAsync(redisKey, publishEntity.PublishedContent,
InterfaceForwardCommon.ContextCacheTtl); InterfaceForwardCommon.DefaultCacheTtl);
ts.Complete(); ts.Complete();

View File

@ -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
{
/// <summary>
/// 获取映射详情(编辑页)
/// </summary>
/// <param name="interfaceId">系统接口id</param>
/// <param name="serviceProviderId">服务商id</param>
/// <returns></returns>
[HttpGet("InterfaceMap/EditMapDetails")]
public async Task<EditMapDetailsOutput> 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;
}
/// <summary>
/// 获取映射参数级联列表
/// </summary>
/// <param name="interfaceId">系统接口id</param>
/// <param name="serviceProviderId">服务商id</param>
/// <returns></returns>
[HttpGet("InterfaceMap/GetMapParameterCascadedList")]
public async Task<List<MapParameterCascadedOutput>>
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<int>(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<MapParameterCascadedOutput>();
// 一个系统接口入参
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<ParameterEntity>, List<MapParameterCascadedItem>>(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<ParameterEntity>, List<MapParameterCascadedItem>>(@params)
});
}
return res;
}
/// <summary>
/// 保存参数映射
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[HttpPost("InterfaceMap/SaveParameterMaps")]
public async Task<bool> 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<InterfaceMapDetailEntity>();
var entitiesForInsert = new List<InterfaceMapDetailEntity>();
var fixedValues = new List<FixedParameterEntity>();
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;
}
/// <summary>
/// 校验必填项,并填充实体
/// </summary>
/// <param name="input"></param>
/// <param name="entitiesForInsert"></param>
/// <param name="entitiesForUpdate"></param>
/// <param name="fixedParameterEntities"></param>
/// <exception cref="BusinessException"></exception>
private static void VerifyAndFillEntities(SaveParameterMapsInput input,
List<InterfaceMapDetailEntity> entitiesForInsert, List<InterfaceMapDetailEntity> entitiesForUpdate,
List<FixedParameterEntity> 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
}));
}
}
}

View File

@ -2,7 +2,6 @@
using System.Diagnostics; using System.Diagnostics;
using InterfaceForward.Application.Contracts.Dtos.Interface; using InterfaceForward.Application.Contracts.Dtos.Interface;
using InterfaceForward.Application.Helpers; using InterfaceForward.Application.Helpers;
using InterfaceForward.Domain.Shared.Enum;
using InterfaceForward.Repositories; using InterfaceForward.Repositories;
using InterfaceForward.Repositories.Interface.Entitys; using InterfaceForward.Repositories.Interface.Entitys;
using InterfaceForward.Repositories.Interface.Services; using InterfaceForward.Repositories.Interface.Services;
@ -16,13 +15,14 @@ namespace InterfaceForward.Application.Services.InterfaceMap;
/// 接口映射服务 /// 接口映射服务
/// </summary> /// </summary>
[ApiExplorerSettings(GroupName = "接口映射服务")] [ApiExplorerSettings(GroupName = "接口映射服务")]
public class InterfaceMapService : BaseService public partial class InterfaceMapService : ApplicationService
{ {
private readonly IBasicRepository<FixedParameterEntity> _fixedParameterRepository;
private readonly IBasicRepository<InterfaceMapDetailEntity> _interfaceMapDetailRepository;
private readonly IInterfaceMapRepository _interfaceMapRepository; private readonly IInterfaceMapRepository _interfaceMapRepository;
private readonly IBasicRepository<InterfaceEntity> _interfaceRepository; private readonly IBasicRepository<InterfaceEntity> _interfaceRepository;
private readonly IBasicRepository<ParameterEntity> _parameterQueries; private readonly IBasicRepository<ParameterEntity> _parameterQueries;
private readonly IBasicRepository<InterfaceMapDetailEntity> _interfaceMapDetailRepository; private readonly IBasicRepository<ServiceProviderEntity> _serviceProviderQueries;
private readonly IBasicRepository<FixedParameterEntity> _fixedParameterRepository;
/// <inheritdoc /> /// <inheritdoc />
public InterfaceMapService( public InterfaceMapService(
@ -30,17 +30,17 @@ public class InterfaceMapService : BaseService
, IBasicRepository<InterfaceEntity> interfaceRepository , IBasicRepository<InterfaceEntity> interfaceRepository
, IBasicRepository<ParameterEntity> parameterQueries , IBasicRepository<ParameterEntity> parameterQueries
, IBasicRepository<InterfaceMapDetailEntity> interfaceMapDetailRepository , IBasicRepository<InterfaceMapDetailEntity> interfaceMapDetailRepository
, IBasicRepository<FixedParameterEntity> fixedParameterRepository) , IBasicRepository<FixedParameterEntity> fixedParameterRepository
, IBasicRepository<ServiceProviderEntity> serviceProviderQueries)
{ {
_interfaceMapRepository = interfaceMapRepository; _interfaceMapRepository = interfaceMapRepository;
_interfaceRepository = interfaceRepository; _interfaceRepository = interfaceRepository;
_parameterQueries = parameterQueries; _parameterQueries = parameterQueries;
_interfaceMapDetailRepository = interfaceMapDetailRepository; _interfaceMapDetailRepository = interfaceMapDetailRepository;
_fixedParameterRepository = fixedParameterRepository; _fixedParameterRepository = fixedParameterRepository;
_serviceProviderQueries = serviceProviderQueries;
} }
#region
/// <summary> /// <summary>
/// 创建接口映射 /// 创建接口映射
/// </summary> /// </summary>
@ -84,7 +84,7 @@ public class InterfaceMapService : BaseService
PrefixScript = null, PrefixScript = null,
PostfixScript = null PostfixScript = null
}).ToList(); }).ToList();
var maps2 = input.DownStreamIds.Select(id => maps.Single(x => x.DownStreamId == id)).ToList(); var maps2 = input.DownStreamIds.Select(id => maps.Single(x => x.DownStreamId == id)).ToList();
return await _interfaceMapRepository.InsertRangeAsync(maps2); return await _interfaceMapRepository.InsertRangeAsync(maps2);
@ -93,12 +93,12 @@ public class InterfaceMapService : BaseService
/// <summary> /// <summary>
/// 获取接口映射列表 /// 获取接口映射列表
/// </summary> /// </summary>
/// <param name="interfaceId">系统接口id</param> /// <param name="inputVo"></param>
/// <returns></returns> /// <returns></returns>
[HttpGet("InterfaceMap/GetMapListById")] [HttpGet("InterfaceMap/GetMapList")]
public async Task<List<InterfaceMapOutputValueObject>> GetMapListByIdAsync([Required] int interfaceId) public async Task<PagedListDto<InterfaceMapOutputValueObject>> GetMapListByIdAsync(GetMapListInputVO inputVo)
{ {
return await _interfaceMapRepository.GetMapListByIdAsync(interfaceId); return await _interfaceMapRepository.GetMapListAsync(inputVo);
} }
/// <summary> /// <summary>
@ -111,6 +111,19 @@ public class InterfaceMapService : BaseService
[HttpPost("InterfaceMap/Delete")] [HttpPost("InterfaceMap/Delete")]
public async Task<bool> DeleteAsync([Required] int interfaceId, [Required] int serviceProviderId) public async Task<bool> 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 => var mapIds = await _interfaceMapRepository.GetListAsync(x =>
x.UpStreamId == interfaceId && x.ServiceProviderId == serviceProviderId, x => x.Id); x.UpStreamId == interfaceId && x.ServiceProviderId == serviceProviderId, x => x.Id);
if (mapIds == null || mapIds.Count == 0) throw new BusinessException(message: "映射不存在"); if (mapIds == null || mapIds.Count == 0) throw new BusinessException(message: "映射不存在");
@ -124,240 +137,9 @@ public class InterfaceMapService : BaseService
ts.Complete(); ts.Complete();
var contextCacheKey = InterfaceForwardCommon.GetContextCacheKey(systemInterfaceCode, serviceProviderCode);
await RedisHelper.Client.DelAsync(contextCacheKey);
return true; return true;
} }
#endregion
#region
/// <summary>
/// 获取映射详情(编辑页)
/// </summary>
/// <param name="interfaceId">系统接口id</param>
/// <param name="serviceProviderId">服务商id</param>
/// <returns></returns>
[HttpGet("InterfaceMap/EditMapDetails")]
public async Task<EditMapDetailsOutput> 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;
}
/// <summary>
/// 获取映射参数级联列表
/// </summary>
/// <param name="interfaceId">系统接口id</param>
/// <param name="serviceProviderId">服务商id</param>
/// <returns></returns>
[HttpGet("InterfaceMap/GetMapParameterCascadedList")]
public async Task<List<MapParameterCascadedOutput>>
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<int>(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<MapParameterCascadedOutput>();
// 一个系统接口入参
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<ParameterEntity>, List<MapParameterCascadedItem>>(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<ParameterEntity>, List<MapParameterCascadedItem>>(@params)
});
}
return res;
}
/// <summary>
/// 保存参数映射
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[HttpPost("InterfaceMap/SaveParameterMaps")]
public async Task<bool> 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<InterfaceMapDetailEntity>();
var entitiesForInsert = new List<InterfaceMapDetailEntity>();
var fixedValues = new List<FixedParameterEntity>();
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;
}
/// <summary>
/// 校验必填项,并填充实体
/// </summary>
/// <param name="input"></param>
/// <param name="entitiesForInsert"></param>
/// <param name="entitiesForUpdate"></param>
/// <param name="fixedParameterEntities"></param>
/// <exception cref="BusinessException"></exception>
private static void VerifyAndFillEntities(SaveParameterMapsInput input,
List<InterfaceMapDetailEntity> entitiesForInsert, List<InterfaceMapDetailEntity> entitiesForUpdate,
List<FixedParameterEntity> 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
} }

View File

@ -3,9 +3,9 @@
public class ContextFeature public class ContextFeature
{ {
/// <summary> /// <summary>
/// 服务商接口默认QPS /// 目标接口QPS
/// </summary> /// </summary>
public int ServiceProviderDefaultQps { get; set; } public int TargetInterfaceQps { get; set; }
/// <summary> /// <summary>
/// 是否等待 /// 是否等待
@ -16,4 +16,9 @@ public class ContextFeature
/// 自旋周期ms /// 自旋周期ms
/// </summary> /// </summary>
public int SpinPeriod { get; set; } public int SpinPeriod { get; set; }
/// <summary>
/// 账户hash
/// </summary>
public string AccountHashCode { get; set; } = null!;
} }

View File

@ -8,16 +8,16 @@ public class FixedFieldWithValueDto
/// 字段所属 /// 字段所属
/// </summary> /// </summary>
public FieldOwner FieldOwner { get; set; } public FieldOwner FieldOwner { get; set; }
/// <summary> /// <summary>
/// 字段名称 /// 字段名称
/// </summary> /// </summary>
public string FieldName { get; set; } public string FieldName { get; set; } = null!;
/// <summary> /// <summary>
/// 字段值 /// 字段值
/// </summary> /// </summary>
public string FieldValue { get; set; } public string FieldValue { get; set; } = null!;
/// <summary> /// <summary>
/// 字段位置 /// 字段位置
@ -27,7 +27,7 @@ public class FixedFieldWithValueDto
///<summary> ///<summary>
/// 字段描述 /// 字段描述
///</summary> ///</summary>
public string Description { get; set; } public string? Description { get; set; }
} }
public enum FieldOwner public enum FieldOwner

View File

@ -26,7 +26,7 @@ public class ForwardCoreContextCache
public List<ParameterNode> OutParamTreeList { get; set; } public List<ParameterNode> OutParamTreeList { get; set; }
/// <summary> /// <summary>
/// 目标服务商授权接口 /// 目标服务商授权接口serviceProvider.AuthInterfaceCode存在时一定不为null
/// </summary> /// </summary>
public TargetInterfaceSummary? ServiceProviderAuthInterface { get; set; } public TargetInterfaceSummary? ServiceProviderAuthInterface { get; set; }
@ -46,13 +46,13 @@ public class ServiceProviderAuthConfigDto
///<summary> ///<summary>
/// 参数路径 /// 参数路径
///</summary> ///</summary>
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 public class TargetInterfaceSummary
@ -60,35 +60,35 @@ public class TargetInterfaceSummary
/// <summary> /// <summary>
/// 目标服务商接口 /// 目标服务商接口
/// </summary> /// </summary>
public InterfaceDto TargetInterface { get; set; } public InterfaceDto TargetInterface { get; set; } = null!;
/// <summary> /// <summary>
/// 请求前置脚本 /// 请求前置脚本
/// </summary> /// </summary>
public string PrefixScript { get; set; } public string? PrefixScript { get; set; }
/// <summary> /// <summary>
/// 请求后置脚本 /// 请求后置脚本
/// </summary> /// </summary>
public string PostfixScript { get; set; } public string? PostfixScript { get; set; }
/// <summary> /// <summary>
/// 入参映射列表(树形结构) /// 入参映射列表(树形结构)
/// </summary> /// </summary>
public List<ParameterNode> InParamTreeList { get; set; } public List<ParameterNode> InParamTreeList { get; set; } = [];
/// <summary> /// <summary>
/// 固定值列表 /// 固定值列表
/// </summary> /// </summary>
public List<FixedFieldWithValueDto> FixedFieldList { get; set; } public List<FixedFieldWithValueDto> FixedFieldList { get; set; } = [];
/// <summary> /// <summary>
/// 返回配置列表 /// 返回配置列表
/// </summary> /// </summary>
public List<InterfaceReturnConfigDto> ReturnConfigList { get; set; } public List<InterfaceReturnConfigDto> ReturnConfigList { get; set; } = [];
/// <summary> /// <summary>
/// 表单字段列表 /// 表单字段列表
/// </summary> /// </summary>
public List<InterfaceFormFieldDto> FormFieldList { get; set; } public List<InterfaceFormFieldDto> FormFieldList { get; set; } = [];
} }

View File

@ -12,7 +12,7 @@ public class InterfaceDto
///<summary> ///<summary>
/// 接口名 /// 接口名
///</summary> ///</summary>
public string Name { get; set; } public string Name { get; set; } = null!;
/// <summary> /// <summary>
/// 服务商id /// 服务商id
@ -22,12 +22,12 @@ public class InterfaceDto
///<summary> ///<summary>
/// 接口代码 /// 接口代码
///</summary> ///</summary>
public string Code { get; set; } public string Code { get; set; } = null!;
///<summary> ///<summary>
/// 请求地址 /// 请求地址
///</summary> ///</summary>
public string RequestAddress { get; set; } public string RequestAddress { get; set; } = null!;
///<summary> ///<summary>
/// 请求协议 /// 请求协议
@ -37,7 +37,7 @@ public class InterfaceDto
///<summary> ///<summary>
/// 请求方式 /// 请求方式
///</summary> ///</summary>
public string RequestMethod { get; set; } public string RequestMethod { get; set; } = null!;
///<summary> ///<summary>
/// 内容类型 /// 内容类型
@ -67,7 +67,7 @@ public class InterfaceDto
///<summary> ///<summary>
/// 流程代码 /// 流程代码
///</summary> ///</summary>
public string FlowCode { get; set; } public string? FlowCode { get; set; }
/// <summary> /// <summary>
/// 是否禁用授权 /// 是否禁用授权

View File

@ -5,15 +5,15 @@ public class InterfaceFormFieldDto
///<summary> ///<summary>
/// 参数名 /// 参数名
///</summary> ///</summary>
public string Name { get; set; } public string Name { get; set; } = null!;
///<summary> ///<summary>
/// 参数值 /// 参数值
///</summary> ///</summary>
public string Value { get; set; } public string Value { get; set; } = null!;
///<summary> ///<summary>
/// 参数描述 /// 参数描述
///</summary> ///</summary>
public string Description { get; set; } public string? Description { get; set; }
} }

View File

@ -20,11 +20,11 @@ namespace InterfaceForward.Domain.Shared.Dtos
///<summary> ///<summary>
/// 路径 /// 路径
///</summary> ///</summary>
public string Path { get; set; } public string Path { get; set; } = null!;
///<summary> ///<summary>
/// 值 /// 值
///</summary> ///</summary>
public string Value { get; set; } public string Value { get; set; } = null!;
} }
} }

View File

@ -2,8 +2,6 @@
public class RequestLogDto(Guid requestId) public class RequestLogDto(Guid requestId)
{ {
public const string Init = null!;
/// <summary> /// <summary>
/// 请求Id /// 请求Id
/// </summary> /// </summary>
@ -22,7 +20,7 @@ public class RequestLogDto(Guid requestId)
///<summary> ///<summary>
/// 应用/服务商名称 /// 应用/服务商名称
///</summary> ///</summary>
public string Name { get; set; } public string? Name { get; set; }
///<summary> ///<summary>
/// 发起请求IP /// 发起请求IP
@ -32,27 +30,27 @@ public class RequestLogDto(Guid requestId)
///<summary> ///<summary>
/// 接口代码 /// 接口代码
///</summary> ///</summary>
public string InterfaceCode { get; set; } public string? InterfaceCode { get; set; }
///<summary> ///<summary>
/// 请求接口名称 /// 请求接口名称
///</summary> ///</summary>
public string InterfaceName { get; set; } public string? InterfaceName { get; set; }
///<summary> ///<summary>
/// 请求地址 /// 请求地址
///</summary> ///</summary>
public string Address { get; set; } public string? Address { get; set; }
///<summary> ///<summary>
/// 请求头 /// 请求头
///</summary> ///</summary>
public string Headers { get; set; } public string? Headers { get; set; }
///<summary> ///<summary>
/// 请求体 /// 请求体
///</summary> ///</summary>
public string Content { get; set; } public string? Content { get; set; }
///<summary> ///<summary>
/// 响应耗时ms /// 响应耗时ms
@ -62,7 +60,12 @@ public class RequestLogDto(Guid requestId)
///<summary> ///<summary>
/// 响应结果 /// 响应结果
///</summary> ///</summary>
public string Response { get; set; } = Init; public string? Response { get; set; }
///<summary>
/// 响应结果映射后
///</summary>
public string? Response2 { get; set; }
///<summary> ///<summary>
/// 是否成功调用 /// 是否成功调用
@ -72,5 +75,5 @@ public class RequestLogDto(Guid requestId)
/// <summary> /// <summary>
/// 异常 /// 异常
/// </summary> /// </summary>
public string Exception { get; set; } public string? Exception { get; set; }
} }

View File

@ -11,6 +11,8 @@ public static class RequestServiceProviderFailedCommon
public static string VerifyStatusFailedCode = "4003"; public static string VerifyStatusFailedCode = "4003";
public static string HeaderInsertFailed = "4004"; public static string HeaderInsertFailed = "4004";
public static string QpsOutLimit = "4005";
public static bool IsRequestServiceProviderFailed(string code) public static bool IsRequestServiceProviderFailed(string code)
{ {

View File

@ -10,12 +10,12 @@ public class ServiceProviderDto
///<summary> ///<summary>
/// 服务商名称 /// 服务商名称
///</summary> ///</summary>
public string Name { get; set; } public string Name { get; set; } = null!;
///<summary> ///<summary>
/// 服务商代码 /// 服务商代码
///</summary> ///</summary>
public string Code { get; set; } public string Code { get; set; } = null!;
/// <summary> /// <summary>
/// 是否禁用授权 /// 是否禁用授权
@ -25,12 +25,12 @@ public class ServiceProviderDto
///<summary> ///<summary>
/// 流程代码 /// 流程代码
///</summary> ///</summary>
public string FlowCode { get; set; } public string? FlowCode { get; set; }
///<summary> ///<summary>
/// 授权接口Code /// 授权接口Code
///</summary> ///</summary>
public string AuthInterfaceCode { get; set; } public string? AuthInterfaceCode { get; set; }
///<summary> ///<summary>
/// 请求超时s /// 请求超时s

View File

@ -8,6 +8,7 @@ public static class GlobalConst
public const string DateTime = "$DateTime"; public const string DateTime = "$DateTime";
public const string Url = "$Url"; public const string Url = "$Url";
public const string Token = "$token"; public const string Token = "$token";
public const string AccountHashCode = "$hash";
/// <summary> /// <summary>
/// 不执行动态代码 /// 不执行动态代码
@ -27,5 +28,7 @@ public static class GlobalConst
/// <summary> /// <summary>
/// 服务商接口qps redis key前缀 /// 服务商接口qps redis key前缀
/// </summary> /// </summary>
public const string ServiceProviderInterfaceQPSKeyPrefix = "InterfaceQPS"; public const string ServiceProviderInterfaceQpsKeyPrefix = "InterfaceQPS";
public const string InterfaceRelayExchange = "sjzy.interface.relay.direct";
} }

View File

@ -23,7 +23,7 @@
/// 系统接口Code /// 系统接口Code
///</summary> ///</summary>
[SugarColumn(ColumnName = "UpStreamCode")] [SugarColumn(ColumnName = "UpStreamCode")]
public string UpStreamCode { get; set; } public string UpStreamCode { get; set; } = null!;
///<summary> ///<summary>
/// 服务商ID /// 服务商ID
@ -41,24 +41,30 @@
/// 服务商接口Code /// 服务商接口Code
///</summary> ///</summary>
[SugarColumn(ColumnName = "DownStreamCode")] [SugarColumn(ColumnName = "DownStreamCode")]
public string DownStreamCode { get; set; } public string DownStreamCode { get; set; } = null!;
///<summary> ///<summary>
/// 当前线上版本 /// 当前线上版本
///</summary> ///</summary>
[SugarColumn(ColumnName = "PublishedVersion")] [SugarColumn(ColumnName = "PublishedVersion")]
public string PublishedVersion { get; set; } public string? PublishedVersion { get; set; }
///<summary> ///<summary>
/// 前置脚本 /// 前置脚本
///</summary> ///</summary>
[SugarColumn(ColumnName = "PrefixScript")] [SugarColumn(ColumnName = "PrefixScript")]
public string PrefixScript { get; set; } public string? PrefixScript { get; set; }
///<summary> ///<summary>
/// 后置脚本 /// 后置脚本
///</summary> ///</summary>
[SugarColumn(ColumnName = "PostfixScript")] [SugarColumn(ColumnName = "PostfixScript")]
public string PostfixScript { get; set; } public string? PostfixScript { get; set; }
///<summary>
/// 支持不对等映射(自由映射)
///</summary>
[SugarColumn(ColumnName = "FreeMap")]
public bool FreeMap { get; set; }
} }
} }

View File

@ -11,9 +11,9 @@ namespace InterfaceForward.Repositories.Interface.Services
/// <summary> /// <summary>
/// 获取接口映射列表 /// 获取接口映射列表
/// </summary> /// </summary>
/// <param name="upstreamId">系统接口id</param> /// <param name="inputVo"></param>
/// <returns></returns> /// <returns></returns>
Task<List<InterfaceMapOutputValueObject>> GetMapListByIdAsync(int upstreamId); Task<PagedListDto<InterfaceMapOutputValueObject>> GetMapListAsync(GetMapListInputVO inputVo);
/// <summary> /// <summary>
/// 获取参数映射列表 /// 获取参数映射列表

View File

@ -13,9 +13,9 @@ public class InterfaceMapRepository : BasicRepository<InterfaceMapEntity>, IScop
/// <summary> /// <summary>
/// 获取映射列表 /// 获取映射列表
/// </summary> /// </summary>
/// <param name="upstreamId">上游接口id</param> /// <param name="inputVo"></param>
/// <returns></returns> /// <returns></returns>
public async Task<List<InterfaceMapOutputValueObject>> GetMapListByIdAsync(int upstreamId) public async Task<PagedListDto<InterfaceMapOutputValueObject>> GetMapListAsync(GetMapListInputVO inputVo)
{ {
// 上下游一对多关系 // 上下游一对多关系
var query = Context.Queryable<InterfaceMapEntity>() var query = Context.Queryable<InterfaceMapEntity>()
@ -23,8 +23,10 @@ public class InterfaceMapRepository : BasicRepository<InterfaceMapEntity>, IScop
.LeftJoin<InterfaceEntity>((a, b, c) => a.DownStreamId == c.Id) // 服务商接口 N .LeftJoin<InterfaceEntity>((a, b, c) => a.DownStreamId == c.Id) // 服务商接口 N
.LeftJoin<ServiceProviderEntity>((a, b, c, d) => c.ServiceProviderId == d.Id) .LeftJoin<ServiceProviderEntity>((a, b, c, d) => c.ServiceProviderId == d.Id)
.Filter(null, true) // 禁用全局过滤器 .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) .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 }) .GroupBy(a => new { a.UpStreamId, a.ServiceProviderId })
.OrderBy(a => a.UpdateTime, OrderByType.Desc); .OrderBy(a => a.UpdateTime, OrderByType.Desc);
@ -40,24 +42,25 @@ public class InterfaceMapRepository : BasicRepository<InterfaceMapEntity>, IScop
InterfaceCode = SqlFunc.MappingColumn<string>("GROUP_CONCAT(c.Code ORDER BY a.id SEPARATOR ',')"), InterfaceCode = SqlFunc.MappingColumn<string>("GROUP_CONCAT(c.Code ORDER BY a.id SEPARATOR ',')"),
UpdateTime = a.UpdateTime, UpdateTime = a.UpdateTime,
PublishedVersion = a.PublishedVersion PublishedVersion = a.PublishedVersion
}).ToListAsync(); }).ToPagedListAsync(inputVo.PageIndex, inputVo.PageSize);
} }
/// <inheritdoc /> /// <inheritdoc />
public async Task<List<InterfaceParameterMapOutputValueObject>> GetParameterMapListAsync(List<int> mapIds, bool isInParaMap) public async Task<List<InterfaceParameterMapOutputValueObject>> GetParameterMapListAsync(List<int> 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` 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 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_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_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` `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_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 WHERE (( `t`.`UpStreamId` = 30 ) AND ( `t`.`ServiceProviderId` = 137 )) and t.IsDeleted = 0 and t.DownStreamId = 509
ORDER BY `b`.`Sort` ASC ORDER BY `b`.`Sort` ASC
*/ */
var ids = mapIds.JoinAsString(","); var ids = mapIds.JoinAsString(",");
// 因为入参映射有多个 出参映射只有一个(挂在第一个映射) // 因为入参映射有多个 出参映射只有一个(挂在第一个映射)
return await Context.SqlQueryable<InterfaceParameterMapOutputValueObject>(@$" return await Context.SqlQueryable<InterfaceParameterMapOutputValueObject>(@$"

View File

@ -5,12 +5,12 @@ public class EditMapDetailsOutput
/// <summary> /// <summary>
/// 入参映射 /// 入参映射
/// </summary> /// </summary>
public List<MapSummary> InParaMapList { get; set; } = new(); public List<MapSummary> InParaMapList { get; set; } = [];
/// <summary> /// <summary>
/// 出参映射明细 /// 出参映射明细
/// </summary> /// </summary>
public List<InterfaceParameterMapOutputValueObject> MapDetails { get; set; } = new(); public List<InterfaceParameterMapOutputValueObject> MapDetails { get; set; } = [];
} }
public class MapSummary public class MapSummary
@ -24,21 +24,26 @@ public class MapSummary
/// 接口id /// 接口id
///</summary> ///</summary>
public int InterfaceId { get; set; } public int InterfaceId { get; set; }
/// <summary> /// <summary>
/// 接口Name /// 接口Name
/// </summary> /// </summary>
public string InterfaceName { get; set; } public string InterfaceName { get; set; } = null!;
/// <summary> /// <summary>
/// 前置脚本 /// 前置脚本
/// </summary> /// </summary>
public string PrefixScript { get; set; } public string? PrefixScript { get; set; }
/// <summary> /// <summary>
/// 后置脚本 /// 后置脚本
/// </summary> /// </summary>
public string PostfixScript { get; set; } public string? PostfixScript { get; set; }
/// <summary>
/// 自由映射(不对等映射)
/// </summary>
public bool FreeMap { get; set; }
/// <summary> /// <summary>
/// 映射明细 /// 映射明细

View File

@ -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; }
}

View File

@ -5,15 +5,15 @@ public class InterfaceMapPublishedDifferenceVO
///<summary> ///<summary>
/// 线上的缓存 /// 线上的缓存
///</summary> ///</summary>
public string PublishedContent { get; set; } public string? PublishedContent { get; set; }
///<summary> ///<summary>
/// 数据库的内容 /// 数据库的内容
///</summary> ///</summary>
public string DbContent { get; set; } public string? DbContent { get; set; }
/// <summary> /// <summary>
/// 与线上版本的差异内容 /// 与线上版本的差异内容
/// </summary> /// </summary>
public string DifferenceContent { get; set; } public string? DifferenceContent { get; set; }
} }

View File

@ -144,7 +144,7 @@ ORDER BY `b`.`Sort` ASC").ToListAsync();
/// </summary> /// </summary>
/// <param name="interfaceCode"></param> /// <param name="interfaceCode"></param>
/// <returns></returns> /// <returns></returns>
public async Task<InterfaceDto> GetInterfaceByCodeAsync(string interfaceCode) public async Task<InterfaceDto?> GetInterfaceByCodeAsync(string interfaceCode)
{ {
var res = await GetInterfaceByCodesAsync(interfaceCode); var res = await GetInterfaceByCodesAsync(interfaceCode);
return res.FirstOrDefault(); 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); var authInterface = await GetInterfaceByCodeAsync(serviceProvider.AuthInterfaceCode);
if (authInterface != null) if (authInterface == null)
{ {
context.ServiceProviderAuthInterface = new TargetInterfaceSummary throw new BusinessException($"授权接口{serviceProvider.AuthInterfaceCode}不存在");
{
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),
};
} }
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); var inParaMaps = await GetParameterMapListAsync(mapIds, true);