temp
This commit is contained in:
parent
8f418b4fd7
commit
512c511707
@ -1,8 +1,9 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<!-- <Nullable>enable</Nullable>-->
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<NoWarn>CS1591</NoWarn>
|
||||
<!-- <WarningsAsErrors>Nullable</WarningsAsErrors>-->
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
||||
@ -29,7 +29,6 @@ public class InterfaceForwardApiModule : FakeModule
|
||||
services.AddFakeSwaggerGen()
|
||||
.AddFakeExceptionFilter()
|
||||
.AddFakeValidationActionFilter()
|
||||
.AddFakeUnitOfWorkActionFilter()
|
||||
.AddFakeAspNetCoreAuditing();
|
||||
|
||||
services.AddCors(options =>
|
||||
|
||||
@ -27,11 +27,19 @@
|
||||
"http://localhost:8888"
|
||||
]
|
||||
},
|
||||
"ConnectionStrings":{
|
||||
"ConnectionStrings": {
|
||||
"Default": "server=baget.xiaolipro.cn;database=interface_forward;uid=dev;pwd=itd!@#123;charset=utf8mb4;"
|
||||
},
|
||||
"FeiShuNotice": {
|
||||
"Title": "InterfaceForward-Dev",
|
||||
"Webhook": "https://open.feishu.cn/open-apis/bot/v2/hook/255c44ff-5891-4902-9b6a-6d0250e745f7"
|
||||
},
|
||||
"RabbitMQ": {
|
||||
"Default": {
|
||||
"Host": "baget.xiaolipro.cn",
|
||||
"Port": 5672,
|
||||
"UserName": "dev",
|
||||
"Password": "itd!@#123"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -39,6 +39,11 @@ public class MapItem
|
||||
/// </summary>
|
||||
public string PostfixScript { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 自由映射(支持不对等映射)
|
||||
/// </summary>
|
||||
public bool FreeMap { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 入参映射明细
|
||||
/// </summary>
|
||||
|
||||
@ -9,27 +9,27 @@ public class ForwardCoreContext
|
||||
/// <summary>
|
||||
/// 目标服务商接口
|
||||
/// </summary>
|
||||
public InterfaceDto TargetInterface { get; set; }
|
||||
public InterfaceDto TargetInterface { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// 服务商接口固定字段列表
|
||||
/// </summary>
|
||||
public List<FixedFieldWithValueDto> FixedFieldList { get; set; }
|
||||
public List<FixedFieldWithValueDto> FixedFieldList { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// 服务商账户字段列表
|
||||
/// </summary>
|
||||
public List<ServiceProviderAccountFieldWithValueDto> AccountFieldList { get; set; }
|
||||
public List<ServiceProviderAccountFieldWithValueDto> AccountFieldList { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// 服务商接口返回配置列表
|
||||
/// </summary>
|
||||
public List<InterfaceReturnConfigDto> ReturnConfigList { get; set; }
|
||||
public List<InterfaceReturnConfigDto> ReturnConfigList { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// 表单字段列表
|
||||
/// </summary>
|
||||
public List<InterfaceFormFieldDto> FormFieldList { get; set; }
|
||||
public List<InterfaceFormFieldDto> FormFieldList { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// 入参映射列表(树形结构)
|
||||
@ -44,12 +44,12 @@ public class ForwardCoreContext
|
||||
/// <summary>
|
||||
/// 请求前置脚本
|
||||
/// </summary>
|
||||
public string PrefixScript { get; set; }
|
||||
public string? PrefixScript { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 请求后置脚本
|
||||
/// </summary>
|
||||
public string PostfixScript { get; set; }
|
||||
public string? PostfixScript { get; set; }
|
||||
|
||||
public bool AddHeader(string key, string value)
|
||||
{
|
||||
@ -87,27 +87,27 @@ public class ForwardCoreContext
|
||||
/// <summary>
|
||||
/// 业务系统传参json
|
||||
/// </summary>
|
||||
public JToken OriginalInterfaceInput { get; set; }
|
||||
public JToken OriginalInterfaceInput { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// 业务系统传参映射后的json
|
||||
/// </summary>
|
||||
public JToken OriginalInterfaceMappedInput { get; set; }
|
||||
public JToken OriginalInterfaceMappedInput { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// 目标接口返回的json
|
||||
/// </summary>
|
||||
public JToken TargetInterfaceOutput { get; set; }
|
||||
public JToken TargetInterfaceOutput { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// 请求服务商接口真实传参字符串
|
||||
/// </summary>
|
||||
public string TargetInterfaceInput { get; set; }
|
||||
public string TargetInterfaceInput { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// 服务商日志
|
||||
/// </summary>
|
||||
public RequestLogDto ServiceProviderRequestLog { get; set; }
|
||||
public RequestLogDto ServiceProviderRequestLog { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// 专用feature
|
||||
|
||||
@ -25,7 +25,7 @@ public class InterfaceRelayForwardReplayEvent
|
||||
/// <summary>
|
||||
/// 原报文
|
||||
/// </summary>
|
||||
public string OriginalMessage { get; set; }
|
||||
public string? OriginalMessage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 附加数据
|
||||
|
||||
@ -1,14 +1,15 @@
|
||||
using System.Text;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Web;
|
||||
using System.Xml;
|
||||
using InterfaceForward.Application.Contracts;
|
||||
using InterfaceForward.Application.Contracts.ForwardCore;
|
||||
using InterfaceForward.Application.Helpers;
|
||||
using InterfaceForward.Application.OSS;
|
||||
using InterfaceForward.Domain.Shared;
|
||||
using InterfaceForward.Domain.Shared.Dtos;
|
||||
using InterfaceForward.Domain.Shared.Enum;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
@ -19,16 +20,11 @@ namespace InterfaceForward.Application.ForwardCore;
|
||||
/// </summary>
|
||||
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)
|
||||
{
|
||||
if (context.OriginalInterfaceInput is JArray)
|
||||
{
|
||||
// 虚构系统入参数组路径
|
||||
VirtualizePath(context.InParamTreeList);
|
||||
}
|
||||
|
||||
var res = JsonHelper.Restructure(context.OriginalInterfaceInput, context.InParamTreeList,
|
||||
context.TargetInterface.MappedParamType);
|
||||
|
||||
@ -40,18 +36,7 @@ public class DefaultForwardFlow : IForwardFlow
|
||||
// 填充占位符
|
||||
ReplacePlaceholder(context);
|
||||
|
||||
// 使用Natasha构建动态表达式
|
||||
if (!context.PrefixScript.IsNullOrEmpty())
|
||||
{
|
||||
if (context.PrefixScript.Contains("await"))
|
||||
{
|
||||
await NDelegate.RandomDomain().AsyncFunc<ForwardCoreContext, Task>(context.PrefixScript)(context);
|
||||
}
|
||||
else
|
||||
{
|
||||
NDelegate.RandomDomain().Action<ForwardCoreContext>(context.PrefixScript)(context);
|
||||
}
|
||||
}
|
||||
await ExecuteScriptAsync(context, true);
|
||||
|
||||
// 将body序列化成对应的格式字符串
|
||||
context.TargetInterfaceInput = SerializationBody(context);
|
||||
@ -64,17 +49,7 @@ public class DefaultForwardFlow : IForwardFlow
|
||||
// 将响应的字符串反序列化成jtoken
|
||||
context.TargetInterfaceOutput = DeserializationResponse(context, res);
|
||||
|
||||
if (!context.PostfixScript.IsNullOrEmpty())
|
||||
{
|
||||
if (context.PostfixScript.Contains("await"))
|
||||
{
|
||||
await NDelegate.RandomDomain().AsyncFunc<ForwardCoreContext, Task>(context.PostfixScript)(context);
|
||||
}
|
||||
else
|
||||
{
|
||||
NDelegate.RandomDomain().Action<ForwardCoreContext>(context.PostfixScript)(context);
|
||||
}
|
||||
}
|
||||
await ExecuteScriptAsync(context, false);
|
||||
|
||||
// 校验状态码,失败发飞书
|
||||
VerifyStatusCode(context);
|
||||
@ -84,12 +59,6 @@ public class DefaultForwardFlow : IForwardFlow
|
||||
|
||||
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);
|
||||
// 非结构化
|
||||
@ -119,13 +88,13 @@ public class DefaultForwardFlow : IForwardFlow
|
||||
var tokens = response.SelectTokens($"{context.TargetInterface.Code}.{pdfLabelConfig.Path}").ToList();
|
||||
if (tokens.Count == 0) tokens = response.SelectTokens($"{pdfLabelConfig.Path}").ToList();
|
||||
if (tokens.Count == 0) return mappedRes;
|
||||
var token = tokens[0];
|
||||
UploadResultDto oss = null;
|
||||
var token = tokens.First();
|
||||
UploadResultDto oss = null!;
|
||||
switch (Enum.Parse<LabelType>(pdfLabelConfig.Value))
|
||||
{
|
||||
case LabelType.Url:
|
||||
{
|
||||
string url = token.Value<string>();
|
||||
string url = token.Value<string>()!;
|
||||
using var r = await ClientFactory.CreateClient().GetAsync(url);
|
||||
await using var stream = await r.Content.ReadAsStreamAsync();
|
||||
oss = OSSHelper.Upload(stream, Guid.NewGuid().ToString("D") + ".pdf");
|
||||
@ -143,8 +112,8 @@ public class DefaultForwardFlow : IForwardFlow
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
mappedRes["oss_key"] = oss!.Key;
|
||||
mappedRes["oss_url"] = oss!.TempUrl;
|
||||
mappedRes["oss_key"] = oss.Key;
|
||||
mappedRes["oss_url"] = oss.TempUrl;
|
||||
}
|
||||
|
||||
#endregion
|
||||
@ -159,7 +128,7 @@ public class DefaultForwardFlow : IForwardFlow
|
||||
/// <exception cref="BusinessException"></exception>
|
||||
public virtual void AfterRequest(ForwardCoreContext context)
|
||||
{
|
||||
if (context.ServiceProviderRequestLog.Response == RequestLogDto.Init)
|
||||
if (context.ServiceProviderRequestLog.Response == null)
|
||||
{
|
||||
ThrowBusinessException(context, "请注意维护服务商日志!");
|
||||
}
|
||||
@ -205,9 +174,8 @@ public class DefaultForwardFlow : IForwardFlow
|
||||
catch (Exception)
|
||||
{
|
||||
ThrowBusinessException(context,
|
||||
$"期望返回类型:{context.TargetInterface.ResponseContentType},解析过程中发生异常",
|
||||
RequestServiceProviderFailedCommon.DeSerializeFailedCode);
|
||||
return default;
|
||||
$"期望返回类型:{context.TargetInterface.ResponseContentType},解析过程中发生异常");
|
||||
return default!;
|
||||
}
|
||||
}
|
||||
|
||||
@ -241,7 +209,7 @@ public class DefaultForwardFlow : IForwardFlow
|
||||
}
|
||||
|
||||
// 失败
|
||||
string errorMsg = null;
|
||||
string? errorMsg = null;
|
||||
var errorConfig = context.ReturnConfigList
|
||||
.FirstOrDefault(x => x.ConfigType == InterfaceReturnConfigType.ErrorMsg);
|
||||
if (errorConfig != null)
|
||||
@ -250,8 +218,7 @@ public class DefaultForwardFlow : IForwardFlow
|
||||
}
|
||||
|
||||
// 业务失败发飞书
|
||||
ThrowBusinessException(context, errorMsg ?? "未通过状态码验证。",
|
||||
RequestServiceProviderFailedCommon.VerifyStatusFailedCode);
|
||||
ThrowBusinessException(context, errorMsg ?? "未通过状态码验证。");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -259,10 +226,8 @@ public class DefaultForwardFlow : IForwardFlow
|
||||
/// </summary>
|
||||
/// <param name="context">上下文</param>
|
||||
/// <param name="msg">异常消息</param>
|
||||
/// <param name="code">异常状态码</param>
|
||||
/// <exception cref="BusinessException"></exception>
|
||||
protected virtual void ThrowBusinessException(ForwardCoreContext context, string msg,
|
||||
string code = "400")
|
||||
protected virtual void ThrowBusinessException(ForwardCoreContext context, string msg)
|
||||
{
|
||||
if (context.ServiceProviderRequestLog.Cost == default)
|
||||
{
|
||||
@ -334,8 +299,7 @@ public class DefaultForwardFlow : IForwardFlow
|
||||
msg.Headers.Remove(item.FieldName);
|
||||
if (!msg.Headers.TryAddWithoutValidation(item.FieldName, item.FieldValue))
|
||||
{
|
||||
ThrowBusinessException(context, $"请求头无法插入 {item.FieldName}:{item.FieldValue}",
|
||||
RequestServiceProviderFailedCommon.HeaderInsertFailed);
|
||||
ThrowBusinessException(context, $"请求头无法插入 {item.FieldName}:{item.FieldValue}");
|
||||
}
|
||||
}
|
||||
|
||||
@ -348,16 +312,14 @@ public class DefaultForwardFlow : IForwardFlow
|
||||
catch (TaskCanceledException ex)
|
||||
{
|
||||
// 捕获超时异常
|
||||
ThrowBusinessException(context, "请求超时," + ex.Message,
|
||||
RequestServiceProviderFailedCommon.RequestFailedCode);
|
||||
ThrowBusinessException(context, "请求超时," + ex.Message);
|
||||
}
|
||||
catch (HttpRequestException rex)
|
||||
{
|
||||
ThrowBusinessException(context, "请求失败," + rex.Message,
|
||||
RequestServiceProviderFailedCommon.RequestFailedCode);
|
||||
ThrowBusinessException(context, "请求失败," + rex.Message);
|
||||
}
|
||||
|
||||
return default;
|
||||
return default!;
|
||||
}
|
||||
|
||||
protected virtual async Task<string> RequestCoreAsync(ForwardCoreContext context, HttpRequestMessage msg)
|
||||
@ -368,21 +330,18 @@ public class DefaultForwardFlow : IForwardFlow
|
||||
client.Timeout = TimeSpan.FromSeconds((double)context.TargetInterface.Timeout);
|
||||
}
|
||||
|
||||
var qps = context.TargetInterface.Qps <= 0
|
||||
? context.Feature.ServiceProviderDefaultQps // 默认qps上限
|
||||
: context.TargetInterface.Qps;
|
||||
if (context.Feature.IsWait)
|
||||
{
|
||||
while (!await CanVisitInterfaceAsync(context.TargetInterface.Code, qps))
|
||||
while (!await CanVisitInterfaceAsync(context))
|
||||
{
|
||||
await Task.Delay(context.Feature.SpinPeriod);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!await CanVisitInterfaceAsync(context.TargetInterface.Code, qps))
|
||||
if (!await CanVisitInterfaceAsync(context))
|
||||
{
|
||||
throw new BusinessException($"服务商接口qps已达到上限{qps},稍后请求");
|
||||
throw new BusinessException($"服务商接口qps已达到上限{context.Feature.TargetInterfaceQps},稍后请求");
|
||||
}
|
||||
}
|
||||
|
||||
@ -445,7 +404,8 @@ public class DefaultForwardFlow : IForwardFlow
|
||||
}
|
||||
else
|
||||
{
|
||||
nameValueCollection = JsonConvert.DeserializeObject<Dictionary<string, string>>(body);
|
||||
nameValueCollection = JsonConvert.DeserializeObject<Dictionary<string, string>>(body) ??
|
||||
new Dictionary<string, string>();
|
||||
}
|
||||
|
||||
msg.Content = new FormUrlEncodedContent(nameValueCollection);
|
||||
@ -485,7 +445,7 @@ public class DefaultForwardFlow : IForwardFlow
|
||||
case ContentType.Xml: // json转xml
|
||||
var jsonStr = JsonConvert.SerializeObject(body);
|
||||
var xmlDoc = JsonConvert.DeserializeXmlNode(jsonStr);
|
||||
data = xmlDoc?.OuterXml;
|
||||
data = xmlDoc?.OuterXml ?? string.Empty;
|
||||
break;
|
||||
case ContentType.FormUrlEncoded:
|
||||
data = JsonConvert.SerializeObject(body); //到请求时解析成Dictionary<string, string>
|
||||
@ -535,7 +495,7 @@ public class DefaultForwardFlow : IForwardFlow
|
||||
}
|
||||
}
|
||||
|
||||
item.FieldValue = context.GetAccountFieldValueOrNull(fieldName);
|
||||
item.FieldValue = context.GetAccountFieldValueOrNull(fieldName) ?? string.Empty;
|
||||
break;
|
||||
case GlobalConst.Url:
|
||||
item.FieldValue = context.TargetInterface.RequestAddress;
|
||||
@ -553,7 +513,7 @@ public class DefaultForwardFlow : IForwardFlow
|
||||
: (timeStamp / 1000).ToString(), // s
|
||||
GlobalConst.DateTime => TurnDate(now, item.Description),
|
||||
GlobalConst.Url => context.TargetInterface.RequestAddress,
|
||||
GlobalConst.FromAccount => context.GetAccountFieldValueOrNull(item.Name),
|
||||
GlobalConst.FromAccount => context.GetAccountFieldValueOrNull(item.Name) ?? string.Empty,
|
||||
_ => item.Value // 不处理
|
||||
};
|
||||
}
|
||||
@ -574,10 +534,13 @@ public class DefaultForwardFlow : IForwardFlow
|
||||
}
|
||||
|
||||
|
||||
string TurnDate(DateTime dateTime, string dateStyle)
|
||||
string TurnDate(DateTime dateTime, string? dateStyle)
|
||||
{
|
||||
if (!dateStyle.IsNullOrWhiteSpace())
|
||||
if (dateStyle.IsNullOrWhiteSpace())
|
||||
{
|
||||
return dateTime.ToString(GlobalConst.DefaultDateStyle);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return dateTime.ToString(dateStyle);
|
||||
@ -587,9 +550,6 @@ public class DefaultForwardFlow : IForwardFlow
|
||||
return dateTime.ToString(GlobalConst.DefaultDateStyle);
|
||||
}
|
||||
}
|
||||
|
||||
return dateTime.ToString(GlobalConst.DefaultDateStyle);
|
||||
}
|
||||
}
|
||||
|
||||
void ReplaceCore(JToken obj, string path, JToken value)
|
||||
@ -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;
|
||||
foreach (var item in list)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(item.MapAlias))
|
||||
{
|
||||
item.MapAlias = "[*]." + item.MapAlias;
|
||||
}
|
||||
string interfaceCode = context.TargetInterface.Code;
|
||||
int qps = context.Feature.TargetInterfaceQps;
|
||||
var hash = context.Feature.AccountHashCode;
|
||||
|
||||
VirtualizePath(item.Children);
|
||||
}
|
||||
}
|
||||
|
||||
async Task<bool> CanVisitInterfaceAsync(string interfaceCode, int qps)
|
||||
{
|
||||
// REDIS限流
|
||||
string redisKey = $"{GlobalConst.ServiceProviderInterfaceQPSKeyPrefix}:{interfaceCode}";
|
||||
string redisKey = $"{GlobalConst.ServiceProviderInterfaceQpsKeyPrefix}:{interfaceCode}:{hash}";
|
||||
var num = await RedisHelper.Client.EvalAsync("""
|
||||
local redisKey=ARGV[1];
|
||||
local qps=ARGV[2];
|
||||
@ -685,4 +635,25 @@ public class DefaultForwardFlow : IForwardFlow
|
||||
""", null, redisKey, qps);
|
||||
return (long)num != 0;
|
||||
}
|
||||
|
||||
static async Task ExecuteScriptAsync(ForwardCoreContext context, bool isPrefix)
|
||||
{
|
||||
var script = isPrefix ? context.PrefixScript : context.PostfixScript;
|
||||
if (script.IsNullOrEmpty()) return;
|
||||
|
||||
Debug.Assert(script != null, nameof(script) + " != null");
|
||||
var scriptHash = StringHelper.GetHash(script);
|
||||
if (script.StartsWith("//async") || script.Contains("await"))
|
||||
{
|
||||
var @delegate = DelegateDic.GetOrAdd(scriptHash,
|
||||
static (_, arg) => NDelegate.RandomDomain().AsyncFunc<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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -4,8 +4,6 @@ namespace InterfaceForward.Application.Helpers;
|
||||
|
||||
public static class CommonExtensions
|
||||
{
|
||||
|
||||
|
||||
public static async Task<byte[]> GetAllBytesAsync(this IFormFile file)
|
||||
{
|
||||
using (var stream = file.OpenReadStream())
|
||||
|
||||
51
src/InterfaceForward.Application/Helpers/StringHelper.cs
Normal file
51
src/InterfaceForward.Application/Helpers/StringHelper.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
@ -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; }
|
||||
}
|
||||
@ -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查询所有订阅";
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
{
|
||||
// todo:redis注册连接恢复订阅
|
||||
RedisHelper.Client.Subscribe(AppSubscribeEventHandler.AppSubscribeEventSubscribe, AppSubScribe);
|
||||
|
||||
// 根据应用订阅表,绑定消费者
|
||||
var subscribes = await _appSubscribeConfigRepository.GetListAsync(x => true);
|
||||
foreach (var subscribe in subscribes)
|
||||
{
|
||||
var queueName = $"{BatchForwardEventHandler.BatchForwardEventSubscribe}.{subscribe.AppKey}.{subscribe
|
||||
.SystemInterfaceCode}.{subscribe.ServiceProviderCode}";
|
||||
_rabbitClient.Subscribe(new ConsumeOptions
|
||||
{
|
||||
Queue = queueName,
|
||||
FetchCount = subscribe.FetchCount, // 并发控制
|
||||
FailedRequeue = subscribe.FailedRequeue,
|
||||
Declaration = o =>
|
||||
{
|
||||
o.ExchangeDeclare(GlobalConst.InterfaceRelayExchange, ExchangeType.Direct, true);
|
||||
o.QueueDeclare(queueName, true);
|
||||
o.QueueBind(queueName, GlobalConst.InterfaceRelayExchange, queueName);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
RedisHelper.Client.UnSubscribe(AppSubscribeEventHandler.AppSubscribeEventSubscribe);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public void AppSubScribe(string chan, object msg)
|
||||
{
|
||||
#if DEBUG
|
||||
LogHelper.Info($"接受到来自redis的订阅:{chan}\n{msg.ToJson()}", true);
|
||||
#endif
|
||||
if (chan == AppSubscribeEventHandler.AppSubscribeEventSubscribe && msg is string str)
|
||||
{
|
||||
var @event = JsonConvert.DeserializeObject<AppSubscribeEvent>(str);
|
||||
AsyncHelper.RunSync(() => _appSubscribeEventHandler.Handle(@event));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -18,6 +18,7 @@
|
||||
<PackageReference Include="Aliyun.OSS.SDK.NetCore" Version="2.13.0" />
|
||||
<PackageReference Include="DotNetCore.Natasha.CSharp" Version="5.2.2.1"/>
|
||||
<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="FreeRedis" Version="1.2.15" />
|
||||
</ItemGroup>
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
using System.Security.Authentication;
|
||||
using Fake.AspNetCore;
|
||||
using Fake.EventBus.RabbitMQ;
|
||||
using Fake.Modularity;
|
||||
using Fake.ObjectMapping.AutoMapper;
|
||||
using InterfaceForward.Application.Contracts;
|
||||
@ -10,6 +11,7 @@ namespace InterfaceForward.Application;
|
||||
|
||||
[DependsOn(typeof(FakeAspNetCoreModule))]
|
||||
[DependsOn(typeof(FakeObjectMappingAutoMapperModule))]
|
||||
[DependsOn(typeof(FakeEventBusRabbitMqModule))]
|
||||
[DependsOn(typeof(InterfaceForwardRepositoriesModule))]
|
||||
public class InterfaceForwardApplicationModule:FakeModule
|
||||
{
|
||||
|
||||
20
src/InterfaceForward.Application/Rabbit/BatchForwardEvent.cs
Normal file
20
src/InterfaceForward.Application/Rabbit/BatchForwardEvent.cs
Normal 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; }
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
32
src/InterfaceForward.Application/Rabbit/RabbitClient.cs
Normal file
32
src/InterfaceForward.Application/Rabbit/RabbitClient.cs
Normal 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>
|
||||
}
|
||||
@ -1,4 +1,7 @@
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using Fake.DependencyInjection;
|
||||
using InterfaceForward.Application.Contracts;
|
||||
using InterfaceForward.Application.Contracts.ForwardCore;
|
||||
@ -16,93 +19,85 @@ using SJZY.InterfaceRelay.Repository.Log.ValueObjects;
|
||||
|
||||
namespace InterfaceForward.Application.Services;
|
||||
|
||||
public class InterfaceForwardCommon : ITransientDependency
|
||||
public class InterfaceForwardCommon(
|
||||
InterfaceForwardQuery interfaceForwardQuery,
|
||||
ILogRepository logRepository,
|
||||
IServiceProvider serviceProvider,
|
||||
IOptionsSnapshot<InterfaceRelayOptions> options)
|
||||
: ITransientDependency
|
||||
{
|
||||
/// <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 InterfaceForwardQuery _interfaceForwardQuery;
|
||||
private readonly InterfaceRelayOptions _interfaceRelayOptions;
|
||||
private readonly ILogRepository _logRepository;
|
||||
// private readonly IEntrySegmentContextAccessor _segContext;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
|
||||
public InterfaceForwardCommon(InterfaceForwardQuery interfaceForwardQuery
|
||||
, ILogRepository logRepository
|
||||
// , IEntrySegmentContextAccessor segContext
|
||||
, IServiceProvider serviceProvider
|
||||
, IOptions<InterfaceRelayOptions> options)
|
||||
{
|
||||
_interfaceForwardQuery = interfaceForwardQuery;
|
||||
_logRepository = logRepository;
|
||||
// _segContext = segContext;
|
||||
_serviceProvider = serviceProvider;
|
||||
_interfaceRelayOptions = options.Value;
|
||||
}
|
||||
private readonly InterfaceRelayOptions _options = options.Value;
|
||||
|
||||
public async Task<List<ForwardCoreContext>> VerifyBusinessAndBuildForwardContexts(string upStreamCode,
|
||||
string serviceProviderCode)
|
||||
{
|
||||
// todo:字符串优化
|
||||
// tips:上下文的缓存不维护,在发布时统一替代。
|
||||
var contextCacheKey = GetContextCacheKey(upStreamCode, serviceProviderCode);
|
||||
// _segContext.Context.Span.AddLog(LogEvent.Message("【转发核心上下文】开始构建"));
|
||||
var cacheContext = (await RedisHelper.Client.GetAsync<string>(contextCacheKey))
|
||||
.ToObject<ForwardCoreContextCache>();
|
||||
if (cacheContext == null)
|
||||
{
|
||||
// _segContext.Context.Span.AddLog(LogEvent.Message("【转发核心上下文】没能命中缓存,重新构建"));
|
||||
|
||||
var systemInterface = await _interfaceForwardQuery.GetInterfaceByCodeAsync(upStreamCode);
|
||||
var systemInterface = await interfaceForwardQuery.GetInterfaceByCodeAsync(upStreamCode);
|
||||
if (systemInterface == null) throw new BusinessException(message: "系统接口不存在");
|
||||
|
||||
_logRepository.AppRequestLog.InterfaceCode = systemInterface.Code;
|
||||
_logRepository.AppRequestLog.InterfaceName = systemInterface.Name;
|
||||
logRepository.AppRequestLog.InterfaceCode = systemInterface.Code;
|
||||
logRepository.AppRequestLog.InterfaceName = systemInterface.Name;
|
||||
|
||||
var serviceProvider = await _interfaceForwardQuery.GetServiceProviderByCodeAsync(serviceProviderCode);
|
||||
if (serviceProvider == null) throw new BusinessException(message: "服务商不存在");
|
||||
cacheContext = await _interfaceForwardQuery.BuildForwardCoreContext(systemInterface, serviceProvider);
|
||||
await RedisHelper.Client.SetAsync(contextCacheKey, cacheContext.ToJson(), ContextCacheTtl);
|
||||
var serviceProviderDto = await interfaceForwardQuery.GetServiceProviderByCodeAsync(serviceProviderCode);
|
||||
if (serviceProviderDto == null) throw new BusinessException(message: "服务商不存在");
|
||||
cacheContext = await interfaceForwardQuery.BuildForwardCoreContext(systemInterface, serviceProviderDto);
|
||||
await RedisHelper.Client.SetAsync(contextCacheKey, cacheContext.ToJson(), DefaultCacheTtl);
|
||||
}
|
||||
|
||||
// _segContext.Context.Span.AddLog(LogEvent.Message("【转发核心上下文】构建完成"));
|
||||
// 记录系统接口日志
|
||||
_logRepository.AppRequestLog.InterfaceCode = cacheContext.OriginalInterface.Code;
|
||||
_logRepository.AppRequestLog.InterfaceName = cacheContext.OriginalInterface.Name;
|
||||
logRepository.AppRequestLog.InterfaceCode = cacheContext.OriginalInterface.Code;
|
||||
logRepository.AppRequestLog.InterfaceName = cacheContext.OriginalInterface.Name;
|
||||
|
||||
// tips:服务商主体账户的缓存实时维护
|
||||
// _segContext.Context.Span.AddLog(LogEvent.Message("【服务商应用账户】开始构建"));
|
||||
var accountCacheKey = GetAccountCacheKey(serviceProviderCode, _logRepository.AppId);
|
||||
var accountCacheKey = GetAccountCacheKey(serviceProviderCode, logRepository.AppId);
|
||||
var accountFieldCache = (await RedisHelper.Client.GetAsync<string>(accountCacheKey))
|
||||
.ToObject<List<ServiceProviderAccountFieldWithValueDto>>();
|
||||
if (accountFieldCache == null)
|
||||
{
|
||||
// _segContext.Context.Span.AddLog(LogEvent.Message("【服务商账户】没能命中缓存,重新构建"));
|
||||
var serviceProvider = cacheContext.TargetServiceProvider;
|
||||
var accountId = await _interfaceForwardQuery.GetAccountIdAsync(serviceProvider.Id, _logRepository.AppId);
|
||||
var serviceProviderDto = cacheContext.TargetServiceProvider;
|
||||
var accountId = await interfaceForwardQuery.GetAccountIdAsync(serviceProviderDto.Id, logRepository.AppId);
|
||||
// 服务商授权
|
||||
if (!serviceProvider.IsDisableAuth && accountId == default)
|
||||
if (!serviceProviderDto.IsDisableAuth && accountId == default)
|
||||
{
|
||||
throw new BusinessException(message: $"服务商:{serviceProvider.Name} 下不存在此账户,请联系管理员或前往接口通创建");
|
||||
throw new BusinessException(message: $"服务商:{serviceProviderDto.Name} 下不存在此账户,请联系管理员或前往接口通创建");
|
||||
}
|
||||
|
||||
accountFieldCache = await _interfaceForwardQuery.GetAccountFieldListAsync(accountId);
|
||||
accountFieldCache = await interfaceForwardQuery.GetAccountFieldListAsync(accountId);
|
||||
accountFieldCache.Add(new ServiceProviderAccountFieldWithValueDto
|
||||
{
|
||||
FieldName = GlobalConst.AccountHashCode,
|
||||
// 根据字段值计算hash,请求时作为qps key依据,即相同的账户会共享一个qps
|
||||
FieldValue = GetHash(accountFieldCache.Select(x => x.FieldValue).JoinAsString("_"))
|
||||
});
|
||||
var ttl = await HandleAuthInterfaceAsync(cacheContext, accountFieldCache);
|
||||
|
||||
await RedisHelper.Client.SetAsync(accountCacheKey, accountFieldCache.ToJson(), ttl);
|
||||
}
|
||||
|
||||
// _segContext.Context.Span.AddLog(LogEvent.Message("【服务商账户】构建完成"));
|
||||
|
||||
// 组装转发上下文
|
||||
var res = new List<ForwardCoreContext>();
|
||||
|
||||
foreach (var item in cacheContext.TargetInterfaces)
|
||||
{
|
||||
item.TargetInterface.Timeout ??= cacheContext.TargetServiceProvider.Timeout;
|
||||
item.TargetInterface.FlowCode = item.TargetInterface.FlowCode.IsNullOrWhiteSpace()
|
||||
? cacheContext.TargetServiceProvider.FlowCode.IsNullOrWhiteSpace()
|
||||
? nameof(DefaultForwardFlow)
|
||||
: cacheContext.TargetServiceProvider.FlowCode
|
||||
: item.TargetInterface.FlowCode;
|
||||
var context = new ForwardCoreContext
|
||||
{
|
||||
TargetInterface = item.TargetInterface,
|
||||
@ -117,42 +112,37 @@ public class InterfaceForwardCommon : ITransientDependency
|
||||
var token = context.FixedFieldList.FirstOrDefault(x => x.FieldValue == GlobalConst.Token);
|
||||
if (token != default)
|
||||
{
|
||||
token.FieldValue = context.GetAccountFieldValueOrNull(GlobalConst.Token);
|
||||
token.FieldValue = context.GetAccountFieldValueOrNull(GlobalConst.Token)!;
|
||||
}
|
||||
|
||||
context.ServiceProviderRequestLog = new RequestLogDto(_logRepository.AppRequestLog.RequestId);
|
||||
context.Feature.AccountHashCode = context.GetAccountFieldValueOrNull(GlobalConst.AccountHashCode)!;
|
||||
|
||||
context.ServiceProviderRequestLog = new RequestLogDto(logRepository.AppRequestLog.RequestId);
|
||||
context.ServiceProviderRequestLog.Name = cacheContext.TargetServiceProvider.Name;
|
||||
context.ServiceProviderRequestLog.Address = context.TargetInterface.RequestAddress;
|
||||
context.ServiceProviderRequestLog.InterfaceCode = context.TargetInterface.Code;
|
||||
context.ServiceProviderRequestLog.InterfaceName = context.TargetInterface.Name;
|
||||
|
||||
var flow = GetForwardFlow(context.TargetInterface.FlowCode, cacheContext.TargetServiceProvider.FlowCode);
|
||||
Debug.Assert(context.TargetInterface.FlowCode != null, "context.TargetInterface.FlowCode != null");
|
||||
var flow = GetForwardFlow(context.TargetInterface.FlowCode);
|
||||
if (flow == default)
|
||||
throw new BusinessException(message: "找不到接口转发流程" + context.TargetInterface.FlowCode);
|
||||
_flows.Add(context.TargetInterface.Code, flow);
|
||||
|
||||
|
||||
res.Add(context);
|
||||
}
|
||||
|
||||
// tips:把出参映射树放到最后一个上下文中,是为了在流程结束时执行出参映射
|
||||
res.Last().OutParamTreeList = cacheContext.OutParamTreeList;
|
||||
|
||||
// _segContext.Context.Span.AddLog(LogEvent.Message("【转发上下文】全部组装完成"));
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
// 优先用服务商接口指定流程,其次使用服务商的,再其次是默认的
|
||||
public IForwardFlow GetForwardFlow(string interfaceFlowCode, string serviceProviderFlowCode)
|
||||
public IForwardFlow GetForwardFlow(string flowCode)
|
||||
{
|
||||
var flowCode = interfaceFlowCode.IsNullOrWhiteSpace()
|
||||
? serviceProviderFlowCode.IsNullOrWhiteSpace()
|
||||
? nameof(DefaultForwardFlow)
|
||||
: serviceProviderFlowCode
|
||||
: interfaceFlowCode;
|
||||
return _serviceProvider.GetServices<IForwardFlow>()
|
||||
.FirstOrDefault(x => x.GetType().Name == flowCode);
|
||||
return serviceProvider.GetServices<IForwardFlow>()
|
||||
.FirstOrDefault(x => x.GetType().Name == flowCode)?? throw new BusinessException("");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -170,7 +160,7 @@ public class InterfaceForwardCommon : ITransientDependency
|
||||
|
||||
if (interfaceDto == default)
|
||||
{
|
||||
return ContextCacheTtl;
|
||||
return DefaultCacheTtl;
|
||||
}
|
||||
|
||||
var configs = cacheContext.ServiceProviderAuthConfigs;
|
||||
@ -179,10 +169,16 @@ public class InterfaceForwardCommon : ITransientDependency
|
||||
var body = new JObject();
|
||||
foreach (var item in configs.Where(x => x.Flag == AuthParameterFlag.InParameter))
|
||||
{
|
||||
Debug.Assert(item.ParameterAlias != null, "item.ParameterAlias != null");
|
||||
body[item.ParameterAlias] = item.Value1;
|
||||
}
|
||||
|
||||
// 构建授权上下文
|
||||
interfaceDto.TargetInterface.FlowCode = interfaceDto.TargetInterface.FlowCode.IsNullOrWhiteSpace()
|
||||
? cacheContext.TargetServiceProvider.FlowCode.IsNullOrWhiteSpace()
|
||||
? nameof(DefaultForwardFlow)
|
||||
: cacheContext.TargetServiceProvider.FlowCode
|
||||
: interfaceDto.TargetInterface.FlowCode;
|
||||
var authContext = new ForwardCoreContext
|
||||
{
|
||||
TargetInterface = interfaceDto.TargetInterface,
|
||||
@ -191,12 +187,12 @@ public class InterfaceForwardCommon : ITransientDependency
|
||||
ReturnConfigList = interfaceDto.ReturnConfigList,
|
||||
InParamTreeList = null,
|
||||
OutParamTreeList = null,
|
||||
PrefixScript = null,
|
||||
PostfixScript = null,
|
||||
PrefixScript = interfaceDto.PrefixScript,
|
||||
PostfixScript = interfaceDto.PostfixScript,
|
||||
OriginalInterfaceInput = body,
|
||||
FormFieldList = interfaceDto.FormFieldList // normal case is null
|
||||
};
|
||||
authContext.ServiceProviderRequestLog = new RequestLogDto(_logRepository.AppRequestLog.RequestId)
|
||||
authContext.ServiceProviderRequestLog = new RequestLogDto(logRepository.AppRequestLog.RequestId)
|
||||
{
|
||||
Name = cacheContext.TargetServiceProvider.Name,
|
||||
Address = authContext.TargetInterface.RequestAddress,
|
||||
@ -204,22 +200,16 @@ public class InterfaceForwardCommon : ITransientDependency
|
||||
InterfaceName = authContext.TargetInterface.Name
|
||||
};
|
||||
|
||||
// 走动态脚本
|
||||
var scriptConfig = configs.FirstOrDefault(x => x.Flag == AuthParameterFlag.Script);
|
||||
if (scriptConfig != default && !scriptConfig.Value1.IsNullOrEmpty())
|
||||
{
|
||||
// 使用Natasha构建动态表达式
|
||||
return NDelegate.RandomDomain().Func<ForwardCoreContext, int>(scriptConfig.Value1)(authContext);
|
||||
}
|
||||
|
||||
// 授权也走主流程
|
||||
if (!cacheContext.TargetServiceProvider.AuthInterfaceCode.IsNullOrWhiteSpace())
|
||||
{
|
||||
var flow = GetForwardFlow(interfaceDto.TargetInterface.FlowCode,
|
||||
cacheContext.TargetServiceProvider.FlowCode);
|
||||
Debug.Assert(cacheContext.ServiceProviderAuthInterface != null, "cacheContext.ServiceProviderAuthInterface != null");
|
||||
var flow = GetForwardFlow(cacheContext.ServiceProviderAuthInterface.TargetInterface.FlowCode!);
|
||||
if (flow == default)
|
||||
throw new BusinessException(message: "找不到接口转发流程" + interfaceDto.TargetInterface.FlowCode);
|
||||
_flows.Add(interfaceDto.TargetInterface.Code, flow);
|
||||
throw new BusinessException(message: "找不到接口转发流程" +
|
||||
cacheContext.ServiceProviderAuthInterface.TargetInterface
|
||||
.FlowCode);
|
||||
_flows.Add(cacheContext.ServiceProviderAuthInterface.TargetInterface.Code, flow);
|
||||
}
|
||||
|
||||
// 请求授权接口
|
||||
@ -229,12 +219,14 @@ public class InterfaceForwardCommon : ITransientDependency
|
||||
var tokenConfig = configs.FirstOrDefault(x => x.Flag == AuthParameterFlag.Token);
|
||||
if (tokenConfig != default)
|
||||
{
|
||||
Debug.Assert(tokenConfig.ParameterAlias != null, "tokenConfig.ParameterAlias != null");
|
||||
accountFieldCache.Add(new ServiceProviderAccountFieldWithValueDto
|
||||
{
|
||||
FieldName = GlobalConst.Token,
|
||||
FieldValue = tokenConfig.Value1.IsNullOrWhiteSpace()
|
||||
? response[tokenConfig.ParameterAlias]?.ToString()
|
||||
: tokenConfig.Value1.Trim() + " " + response[tokenConfig.ParameterAlias]
|
||||
? response.SelectToken(tokenConfig.ParameterAlias)?.ToString() ??
|
||||
throw new BusinessException($"授权接口返回报文中未找到{tokenConfig.ParameterAlias}")
|
||||
: tokenConfig.Value1!.Trim() + " " + response.SelectToken(tokenConfig.ParameterAlias)
|
||||
});
|
||||
}
|
||||
|
||||
@ -247,8 +239,10 @@ public class InterfaceForwardCommon : ITransientDependency
|
||||
return ttl;
|
||||
}
|
||||
|
||||
if (int.TryParse(response[effectTimeConfig.ParameterAlias]?.ToString(), out var expiredValue))
|
||||
Debug.Assert(effectTimeConfig.ParameterAlias != null, "effectTimeConfig.ParameterAlias != null");
|
||||
if (int.TryParse(response.SelectToken(effectTimeConfig.ParameterAlias)?.ToString(), out var expiredValue))
|
||||
{
|
||||
Debug.Assert(effectTimeConfig.Value1 != null, "effectTimeConfig.Value1 != null");
|
||||
ttl = Enum.Parse<EffectTimeUnit>(effectTimeConfig.Value1) switch
|
||||
{
|
||||
EffectTimeUnit.Second => expiredValue,
|
||||
@ -357,20 +351,23 @@ public class InterfaceForwardCommon : ITransientDependency
|
||||
{
|
||||
try
|
||||
{
|
||||
context.Feature.TargetInterfaceQps = context.TargetInterface.Qps <= 0
|
||||
? _options.ServiceProviderDefaultQps // 默认qps上限
|
||||
: context.TargetInterface.Qps;
|
||||
context.Feature.SpinPeriod = _options.SpinPeriod; // 异步自旋周期
|
||||
|
||||
var flow = _flows[context.TargetInterface.Code];
|
||||
// _segContext.Context.Span.AddLog(LogEvent.Message("开始入参重构"));
|
||||
|
||||
context.OriginalInterfaceMappedInput = await flow.InParamRestructure(context);
|
||||
|
||||
// _segContext.Context.Span.AddLog(LogEvent.Message("* 开始请求"));
|
||||
context.Feature.ServiceProviderDefaultQps = _interfaceRelayOptions.ServiceProviderDefaultQps;
|
||||
context.Feature.SpinPeriod = _interfaceRelayOptions.SpinPeriod;
|
||||
var response = await flow.ForwardAsync(context);
|
||||
// _segContext.Context.Span.AddLog(LogEvent.Message("* 请求结束"));
|
||||
|
||||
if (isMulti) return response; // 一对多不在这映射
|
||||
var res = await flow.OutParamRestructure(context, response);
|
||||
|
||||
// _segContext.Context.Span.AddLog(LogEvent.Message("完成反参重构"));
|
||||
if (context.Feature.IsWait)
|
||||
{
|
||||
context.ServiceProviderRequestLog.Response2 = res.ToJson();
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
@ -384,10 +381,15 @@ public class InterfaceForwardCommon : ITransientDependency
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (context.ServiceProviderRequestLog.RequestTime == default)
|
||||
{
|
||||
context.ServiceProviderRequestLog.RequestTime = DateTime.Now;
|
||||
}
|
||||
|
||||
// 关联当前服务商日志
|
||||
_logRepository.ServiceProviderRequestLog = context.ServiceProviderRequestLog;
|
||||
logRepository.ServiceProviderRequestLog = context.ServiceProviderRequestLog;
|
||||
// 写服务商日志
|
||||
await _logRepository.AddLogAsync(context.ServiceProviderRequestLog);
|
||||
await logRepository.AddLogAsync(context.ServiceProviderRequestLog);
|
||||
}
|
||||
}
|
||||
|
||||
@ -416,4 +418,27 @@ public class InterfaceForwardCommon : ITransientDependency
|
||||
|
||||
return objectArray;
|
||||
}
|
||||
|
||||
/// <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();
|
||||
}
|
||||
}
|
||||
@ -5,8 +5,11 @@ using Newtonsoft.Json.Linq;
|
||||
using InterfaceForward.Application.Contracts;
|
||||
using InterfaceForward.Application.Contracts.ForwardCore;
|
||||
using InterfaceForward.Application.Filters;
|
||||
using InterfaceForward.Application.Rabbit;
|
||||
using InterfaceForward.Domain.Shared;
|
||||
using InterfaceForward.Repositories.Log.Services;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using RabbitMQ.Client;
|
||||
|
||||
namespace InterfaceForward.Application.Services;
|
||||
|
||||
@ -21,29 +24,100 @@ public class InterfaceForwardService : BaseService, IInterfaceForwardService
|
||||
{
|
||||
private readonly InterfaceForwardCommon _forwardCommon;
|
||||
private readonly ILogRepository _logRepository;
|
||||
private readonly InterfaceRelayOptions _options;
|
||||
private readonly RabbitClient _rabbitClient;
|
||||
|
||||
/// <inheritdoc />
|
||||
public InterfaceForwardService(
|
||||
ILogRepository logRepository
|
||||
, InterfaceForwardCommon forwardCommon
|
||||
, IOptionsSnapshot<InterfaceRelayOptions> options
|
||||
)
|
||||
public InterfaceForwardService(ILogRepository logRepository,
|
||||
InterfaceForwardCommon forwardCommon, RabbitClient rabbitClient)
|
||||
{
|
||||
_logRepository = logRepository;
|
||||
_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")]
|
||||
public async Task<InterfaceRelayUnifyResultDto> ForwardWithSubscribeAsync(
|
||||
[FromQuery] [Required] string upStreamCode,
|
||||
[FromQuery] [Required] string serviceProviderCode,
|
||||
[FromQuery] int mainBodyId,
|
||||
[FromBody] [Required] object data,
|
||||
[FromHeader] string? attach = null,
|
||||
[FromHeader] string attach = null,
|
||||
[FromHeader] bool isReturnMessage = false,
|
||||
[FromHeader] string? failedSubscribeName = null)
|
||||
[FromHeader] string failedSubscribeName = null)
|
||||
{
|
||||
if (JToken.FromObject(data) is not JObject jobject)
|
||||
{
|
||||
@ -64,7 +138,7 @@ public class InterfaceForwardService : BaseService, IInterfaceForwardService
|
||||
? await _forwardCommon.InternalForwardAsync(context)
|
||||
: await _forwardCommon.InternalForward2Async(contextList);
|
||||
|
||||
return new InterfaceRelayUnifyResultDto
|
||||
return new InterfaceRelayUnifyResultDto(true, res)
|
||||
{
|
||||
RequestId = context.ServiceProviderRequestLog.RequestId,
|
||||
OriginalMessage = isReturnMessage ? context.ServiceProviderRequestLog.Response : null,
|
||||
@ -76,12 +150,11 @@ public class InterfaceForwardService : BaseService, IInterfaceForwardService
|
||||
public async Task<InterfaceRelayUnifyResultDto> ReForwardAsync(
|
||||
[FromQuery] [Required] string upStreamCode,
|
||||
[FromQuery] [Required] string serviceProviderCode,
|
||||
[FromQuery] int mainBodyId,
|
||||
[FromHeader] [Required] Guid requestId,
|
||||
[FromBody] [Required] object data,
|
||||
[FromHeader] string? attach = null,
|
||||
[FromHeader] string attach = null,
|
||||
[FromHeader] bool isReturnMessage = false,
|
||||
[FromHeader] string? failedSubscribeName = null)
|
||||
[FromHeader] string failedSubscribeName = null)
|
||||
{
|
||||
if (JToken.FromObject(data) is not JObject jobject)
|
||||
{
|
||||
@ -100,7 +173,7 @@ public class InterfaceForwardService : BaseService, IInterfaceForwardService
|
||||
? jobject
|
||||
: new JObject
|
||||
{
|
||||
[upStreamCode] = jobject
|
||||
[upStreamCode!] = jobject
|
||||
};
|
||||
|
||||
// 从第一个失败的开始重试
|
||||
@ -109,7 +182,7 @@ public class InterfaceForwardService : BaseService, IInterfaceForwardService
|
||||
? await _forwardCommon.InternalForwardAsync(context)
|
||||
: await _forwardCommon.InternalForward3Async(contextList, requestLogs);
|
||||
|
||||
return new InterfaceRelayUnifyResultDto
|
||||
return new InterfaceRelayUnifyResultDto(true, res)
|
||||
{
|
||||
RequestId = context.ServiceProviderRequestLog.RequestId,
|
||||
OriginalMessage = isReturnMessage ? _logRepository.ServiceProviderRequestLog.Response : null,
|
||||
|
||||
@ -2,17 +2,16 @@
|
||||
using DiffPlex;
|
||||
using DiffPlex.DiffBuilder;
|
||||
using DiffPlex.DiffBuilder.Model;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using InterfaceForward.Application.Contracts.Dtos.Interface;
|
||||
using InterfaceForward.Application.Helpers;
|
||||
using InterfaceForward.Domain.Shared.Dtos;
|
||||
using InterfaceForward.Domain.Shared.Helpers;
|
||||
using InterfaceForward.Repositories;
|
||||
using InterfaceForward.Repositories.Interface.Entitys;
|
||||
using InterfaceForward.Repositories.Interface.Services;
|
||||
using InterfaceForward.Repositories.Interface.ValueObjects;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
|
||||
namespace InterfaceForward.Application.Services.InterfaceMap;
|
||||
|
||||
@ -20,27 +19,19 @@ namespace InterfaceForward.Application.Services.InterfaceMap;
|
||||
/// 接口映射发布修改
|
||||
/// </summary>
|
||||
[ApiExplorerSettings(GroupName = "接口映射服务")]
|
||||
public class InterfaceMapPublishService : BaseService
|
||||
public class InterfaceMapPublishService(
|
||||
IInterfaceMapPublishRepository interfaceMapPublishRepository,
|
||||
InterfaceForwardQuery interfaceForwardQuery,
|
||||
IInterfaceMapRepository interfaceMapRepository)
|
||||
: ApplicationService
|
||||
{
|
||||
private readonly static JsonSerializerSettings Settings = new JsonSerializerSettings
|
||||
private static readonly JsonSerializerSettings Settings = new()
|
||||
{
|
||||
Formatting = Formatting.Indented, // 设置格式化输出
|
||||
Converters = { new StringEnumConverter() },
|
||||
ContractResolver = new CustomContractResolver() // 设置自定义的ContractResolver
|
||||
};
|
||||
|
||||
private readonly InterfaceForwardQuery _interfaceForwardQuery;
|
||||
private readonly IInterfaceMapPublishRepository _interfaceMapPublishRepository;
|
||||
private readonly IInterfaceMapRepository _interfaceMapRepository;
|
||||
|
||||
public InterfaceMapPublishService(IInterfaceMapPublishRepository interfaceMapPublishRepository,
|
||||
InterfaceForwardQuery interfaceForwardQuery, IInterfaceMapRepository interfaceMapRepository)
|
||||
{
|
||||
_interfaceMapPublishRepository = interfaceMapPublishRepository;
|
||||
_interfaceForwardQuery = interfaceForwardQuery;
|
||||
_interfaceMapRepository = interfaceMapRepository;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取接口映射 发布记录列表
|
||||
/// </summary>
|
||||
@ -53,7 +44,7 @@ public class InterfaceMapPublishService : BaseService
|
||||
public async Task<List<InterfaceMapPublishedOutPutVO>> GetPublishedListAsync([Required] int interfaceId,
|
||||
[Required] int serviceProviderId, int pageIndex = 1, int pageSize = 50)
|
||||
{
|
||||
return await _interfaceMapPublishRepository.GetPublishedListAsync(interfaceId, serviceProviderId, pageIndex,
|
||||
return await interfaceMapPublishRepository.GetPublishedListAsync(interfaceId, serviceProviderId, pageIndex,
|
||||
pageSize);
|
||||
}
|
||||
|
||||
@ -68,18 +59,18 @@ public class InterfaceMapPublishService : BaseService
|
||||
public async Task<InterfaceMapPublishedDifferenceVO> GetPublishCompareAsync([Required] string interfaceCode,
|
||||
[Required] int serviceProviderId, string version)
|
||||
{
|
||||
var systemInterface = await _interfaceForwardQuery.GetInterfaceByCodeAsync(interfaceCode);
|
||||
var systemInterface = await interfaceForwardQuery.GetInterfaceByCodeAsync(interfaceCode);
|
||||
if (systemInterface == null) throw new BusinessException(message: "系统接口不存在");
|
||||
|
||||
var serviceProvider = await _interfaceForwardQuery.GetServiceProviderByIdAsync(serviceProviderId);
|
||||
var serviceProvider = await interfaceForwardQuery.GetServiceProviderByIdAsync(serviceProviderId);
|
||||
if (serviceProvider == null) throw new BusinessException(message: "服务商不存在");
|
||||
|
||||
// 没有指定比对的版本,就是比对数据库最新数据
|
||||
string dbContent = version.IsNullOrWhiteSpace()
|
||||
var dbContent = version.IsNullOrWhiteSpace()
|
||||
? JsonConvert.SerializeObject(
|
||||
await _interfaceForwardQuery.BuildForwardCoreContext(systemInterface, serviceProvider),
|
||||
await interfaceForwardQuery.BuildForwardCoreContext(systemInterface, serviceProvider),
|
||||
Settings)
|
||||
: await _interfaceMapPublishRepository.GetFirstAsync(a =>
|
||||
: await interfaceMapPublishRepository.GetFirstAsync(a =>
|
||||
a.InterfaceId == systemInterface.Id && a.ServiceProviderId == serviceProviderId &&
|
||||
a.PublishedVersion == version, a => a.PublishedContent);
|
||||
|
||||
@ -88,7 +79,7 @@ public class InterfaceMapPublishService : BaseService
|
||||
string redisContent = JsonConvert.SerializeObject(cache, Settings);
|
||||
|
||||
|
||||
var differenceContent = CompareTexts(redisContent, dbContent ?? "");
|
||||
var differenceContent = CompareTexts(redisContent, dbContent);
|
||||
|
||||
return new InterfaceMapPublishedDifferenceVO
|
||||
{
|
||||
@ -107,21 +98,21 @@ public class InterfaceMapPublishService : BaseService
|
||||
[HttpPost("InterfaceMap/Publish")]
|
||||
public async Task<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: "系统接口不存在");
|
||||
|
||||
var serviceProvider = await _interfaceForwardQuery.GetServiceProviderByIdAsync(input.ServiceProviderId);
|
||||
var serviceProvider = await interfaceForwardQuery.GetServiceProviderByIdAsync(input.ServiceProviderId);
|
||||
if (serviceProvider == null) throw new BusinessException(message: "服务商不存在");
|
||||
|
||||
// 确认比对期间数据是否被篡改
|
||||
var differ = new Differ();
|
||||
var inlineBuilder = new InlineDiffBuilder(differ);
|
||||
var context = await _interfaceForwardQuery.BuildForwardCoreContext(systemInterface, serviceProvider);
|
||||
var context = await interfaceForwardQuery.BuildForwardCoreContext(systemInterface, serviceProvider);
|
||||
var content = JsonConvert.SerializeObject(context, Settings);
|
||||
var diffResult = inlineBuilder.BuildDiffModel(content, input.DBContent);
|
||||
if (diffResult.HasDifferences) throw new BusinessException(message: "数据库已经发生变更,请重新获取最新数据进行比对");
|
||||
|
||||
var mapsEntities = await _interfaceMapRepository.GetListAsync(x =>
|
||||
var mapsEntities = await interfaceMapRepository.GetListAsync(x =>
|
||||
x.UpStreamId == systemInterface.Id && x.ServiceProviderId == serviceProvider.Id);
|
||||
var publishEntity = new InterfaceMapPublishedEntity
|
||||
{
|
||||
@ -137,15 +128,15 @@ public class InterfaceMapPublishService : BaseService
|
||||
|
||||
|
||||
using var ts = TransacationHelper.GetReadCommitted();
|
||||
await _interfaceMapPublishRepository.InsertAsync(publishEntity);
|
||||
await interfaceMapPublishRepository.InsertAsync(publishEntity);
|
||||
|
||||
// 更新版本号
|
||||
mapsEntities.ForEach(x => x.PublishedVersion = publishEntity.PublishedVersion);
|
||||
await _interfaceMapRepository.UpdateRangeAsync(mapsEntities, x => x.PublishedVersion);
|
||||
await interfaceMapRepository.UpdateRangeAsync(mapsEntities, x => x.PublishedVersion);
|
||||
|
||||
// update cache
|
||||
var redisKey = InterfaceForwardCommon.GetContextCacheKey(systemInterface.Code, serviceProvider.Code);
|
||||
await RedisHelper.Client.SetAsync(redisKey, content, InterfaceForwardCommon.ContextCacheTtl);
|
||||
await RedisHelper.Client.SetAsync(redisKey, content, InterfaceForwardCommon.DefaultCacheTtl);
|
||||
ts.Complete();
|
||||
|
||||
return true;
|
||||
@ -160,19 +151,19 @@ public class InterfaceMapPublishService : BaseService
|
||||
[HttpPost("InterfaceMap/PublishRollback")]
|
||||
public async Task<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: "系统接口不存在");
|
||||
var serviceProvider = await _interfaceForwardQuery.GetServiceProviderByIdAsync(input.ServiceProviderId);
|
||||
var serviceProvider = await interfaceForwardQuery.GetServiceProviderByIdAsync(input.ServiceProviderId);
|
||||
if (serviceProvider == null) throw new BusinessException(message: "服务商不存在");
|
||||
|
||||
var mapsEntities = await _interfaceMapRepository.GetListAsync(x =>
|
||||
var mapsEntities = await interfaceMapRepository.GetListAsync(x =>
|
||||
x.UpStreamId == systemInterface.Id && x.ServiceProviderId == input.ServiceProviderId);
|
||||
if (mapsEntities == null || mapsEntities.Count == 0)
|
||||
{
|
||||
throw new BusinessException(message: "映射不存在");
|
||||
}
|
||||
|
||||
var targetVersionContent = await _interfaceMapPublishRepository.GetFirstAsync(a =>
|
||||
var targetVersionContent = await interfaceMapPublishRepository.GetFirstAsync(a =>
|
||||
a.InterfaceId == systemInterface.Id && a.ServiceProviderId == input.ServiceProviderId &&
|
||||
a.PublishedVersion == input.Version, a => a.PublishedContent);
|
||||
// tips:空是合法的,虽然在业务上不存在
|
||||
@ -198,14 +189,14 @@ public class InterfaceMapPublishService : BaseService
|
||||
|
||||
using var ts = TransacationHelper.GetReadCommitted();
|
||||
|
||||
await _interfaceMapPublishRepository.InsertAsync(publishEntity);
|
||||
await interfaceMapPublishRepository.InsertAsync(publishEntity);
|
||||
|
||||
// 更新版本号
|
||||
mapsEntities.ForEach(x => x.PublishedVersion = publishEntity.PublishedVersion);
|
||||
await _interfaceMapRepository.UpdateRangeAsync(mapsEntities, x => x.PublishedVersion);
|
||||
await interfaceMapRepository.UpdateRangeAsync(mapsEntities, x => x.PublishedVersion);
|
||||
|
||||
await RedisHelper.Client.SetAsync(redisKey, publishEntity.PublishedContent,
|
||||
InterfaceForwardCommon.ContextCacheTtl);
|
||||
InterfaceForwardCommon.DefaultCacheTtl);
|
||||
|
||||
ts.Complete();
|
||||
|
||||
|
||||
@ -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
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2,7 +2,6 @@
|
||||
using System.Diagnostics;
|
||||
using InterfaceForward.Application.Contracts.Dtos.Interface;
|
||||
using InterfaceForward.Application.Helpers;
|
||||
using InterfaceForward.Domain.Shared.Enum;
|
||||
using InterfaceForward.Repositories;
|
||||
using InterfaceForward.Repositories.Interface.Entitys;
|
||||
using InterfaceForward.Repositories.Interface.Services;
|
||||
@ -16,13 +15,14 @@ namespace InterfaceForward.Application.Services.InterfaceMap;
|
||||
/// 接口映射服务
|
||||
/// </summary>
|
||||
[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 IBasicRepository<InterfaceEntity> _interfaceRepository;
|
||||
private readonly IBasicRepository<ParameterEntity> _parameterQueries;
|
||||
private readonly IBasicRepository<InterfaceMapDetailEntity> _interfaceMapDetailRepository;
|
||||
private readonly IBasicRepository<FixedParameterEntity> _fixedParameterRepository;
|
||||
private readonly IBasicRepository<ServiceProviderEntity> _serviceProviderQueries;
|
||||
|
||||
/// <inheritdoc />
|
||||
public InterfaceMapService(
|
||||
@ -30,17 +30,17 @@ public class InterfaceMapService : BaseService
|
||||
, IBasicRepository<InterfaceEntity> interfaceRepository
|
||||
, IBasicRepository<ParameterEntity> parameterQueries
|
||||
, IBasicRepository<InterfaceMapDetailEntity> interfaceMapDetailRepository
|
||||
, IBasicRepository<FixedParameterEntity> fixedParameterRepository)
|
||||
, IBasicRepository<FixedParameterEntity> fixedParameterRepository
|
||||
, IBasicRepository<ServiceProviderEntity> serviceProviderQueries)
|
||||
{
|
||||
_interfaceMapRepository = interfaceMapRepository;
|
||||
_interfaceRepository = interfaceRepository;
|
||||
_parameterQueries = parameterQueries;
|
||||
_interfaceMapDetailRepository = interfaceMapDetailRepository;
|
||||
_fixedParameterRepository = fixedParameterRepository;
|
||||
_serviceProviderQueries = serviceProviderQueries;
|
||||
}
|
||||
|
||||
#region 接口映射
|
||||
|
||||
/// <summary>
|
||||
/// 创建接口映射
|
||||
/// </summary>
|
||||
@ -93,12 +93,12 @@ public class InterfaceMapService : BaseService
|
||||
/// <summary>
|
||||
/// 获取接口映射列表
|
||||
/// </summary>
|
||||
/// <param name="interfaceId">系统接口id</param>
|
||||
/// <param name="inputVo"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("InterfaceMap/GetMapListById")]
|
||||
public async Task<List<InterfaceMapOutputValueObject>> GetMapListByIdAsync([Required] int interfaceId)
|
||||
[HttpGet("InterfaceMap/GetMapList")]
|
||||
public async Task<PagedListDto<InterfaceMapOutputValueObject>> GetMapListByIdAsync(GetMapListInputVO inputVo)
|
||||
{
|
||||
return await _interfaceMapRepository.GetMapListByIdAsync(interfaceId);
|
||||
return await _interfaceMapRepository.GetMapListAsync(inputVo);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -111,6 +111,19 @@ public class InterfaceMapService : BaseService
|
||||
[HttpPost("InterfaceMap/Delete")]
|
||||
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 =>
|
||||
x.UpStreamId == interfaceId && x.ServiceProviderId == serviceProviderId, x => x.Id);
|
||||
if (mapIds == null || mapIds.Count == 0) throw new BusinessException(message: "映射不存在");
|
||||
@ -124,240 +137,9 @@ public class InterfaceMapService : BaseService
|
||||
|
||||
ts.Complete();
|
||||
|
||||
var contextCacheKey = InterfaceForwardCommon.GetContextCacheKey(systemInterfaceCode, serviceProviderCode);
|
||||
await RedisHelper.Client.DelAsync(contextCacheKey);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 映射详情
|
||||
|
||||
/// <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
|
||||
}
|
||||
@ -3,9 +3,9 @@
|
||||
public class ContextFeature
|
||||
{
|
||||
/// <summary>
|
||||
/// 服务商接口默认QPS
|
||||
/// 目标接口QPS
|
||||
/// </summary>
|
||||
public int ServiceProviderDefaultQps { get; set; }
|
||||
public int TargetInterfaceQps { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否等待
|
||||
@ -16,4 +16,9 @@ public class ContextFeature
|
||||
/// 自旋周期ms
|
||||
/// </summary>
|
||||
public int SpinPeriod { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 账户hash
|
||||
/// </summary>
|
||||
public string AccountHashCode { get; set; } = null!;
|
||||
}
|
||||
@ -12,12 +12,12 @@ public class FixedFieldWithValueDto
|
||||
/// <summary>
|
||||
/// 字段名称
|
||||
/// </summary>
|
||||
public string FieldName { get; set; }
|
||||
public string FieldName { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// 字段值
|
||||
/// </summary>
|
||||
public string FieldValue { get; set; }
|
||||
public string FieldValue { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// 字段位置
|
||||
@ -27,7 +27,7 @@ public class FixedFieldWithValueDto
|
||||
///<summary>
|
||||
/// 字段描述
|
||||
///</summary>
|
||||
public string Description { get; set; }
|
||||
public string? Description { get; set; }
|
||||
}
|
||||
|
||||
public enum FieldOwner
|
||||
|
||||
@ -26,7 +26,7 @@ public class ForwardCoreContextCache
|
||||
public List<ParameterNode> OutParamTreeList { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 目标服务商授权接口
|
||||
/// 目标服务商授权接口(serviceProvider.AuthInterfaceCode存在时,一定不为null)
|
||||
/// </summary>
|
||||
public TargetInterfaceSummary? ServiceProviderAuthInterface { get; set; }
|
||||
|
||||
@ -46,13 +46,13 @@ public class ServiceProviderAuthConfigDto
|
||||
///<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
|
||||
@ -60,35 +60,35 @@ public class TargetInterfaceSummary
|
||||
/// <summary>
|
||||
/// 目标服务商接口
|
||||
/// </summary>
|
||||
public InterfaceDto TargetInterface { get; set; }
|
||||
public InterfaceDto TargetInterface { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// 请求前置脚本
|
||||
/// </summary>
|
||||
public string PrefixScript { get; set; }
|
||||
public string? PrefixScript { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 请求后置脚本
|
||||
/// </summary>
|
||||
public string PostfixScript { get; set; }
|
||||
public string? PostfixScript { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 入参映射列表(树形结构)
|
||||
/// </summary>
|
||||
public List<ParameterNode> InParamTreeList { get; set; }
|
||||
public List<ParameterNode> InParamTreeList { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// 固定值列表
|
||||
/// </summary>
|
||||
public List<FixedFieldWithValueDto> FixedFieldList { get; set; }
|
||||
public List<FixedFieldWithValueDto> FixedFieldList { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// 返回配置列表
|
||||
/// </summary>
|
||||
public List<InterfaceReturnConfigDto> ReturnConfigList { get; set; }
|
||||
public List<InterfaceReturnConfigDto> ReturnConfigList { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// 表单字段列表
|
||||
/// </summary>
|
||||
public List<InterfaceFormFieldDto> FormFieldList { get; set; }
|
||||
public List<InterfaceFormFieldDto> FormFieldList { get; set; } = [];
|
||||
}
|
||||
@ -12,7 +12,7 @@ public class InterfaceDto
|
||||
///<summary>
|
||||
/// 接口名
|
||||
///</summary>
|
||||
public string Name { get; set; }
|
||||
public string Name { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// 服务商id
|
||||
@ -22,12 +22,12 @@ public class InterfaceDto
|
||||
///<summary>
|
||||
/// 接口代码
|
||||
///</summary>
|
||||
public string Code { get; set; }
|
||||
public string Code { get; set; } = null!;
|
||||
|
||||
///<summary>
|
||||
/// 请求地址
|
||||
///</summary>
|
||||
public string RequestAddress { get; set; }
|
||||
public string RequestAddress { get; set; } = null!;
|
||||
|
||||
///<summary>
|
||||
/// 请求协议
|
||||
@ -37,7 +37,7 @@ public class InterfaceDto
|
||||
///<summary>
|
||||
/// 请求方式
|
||||
///</summary>
|
||||
public string RequestMethod { get; set; }
|
||||
public string RequestMethod { get; set; } = null!;
|
||||
|
||||
///<summary>
|
||||
/// 内容类型
|
||||
@ -67,7 +67,7 @@ public class InterfaceDto
|
||||
///<summary>
|
||||
/// 流程代码
|
||||
///</summary>
|
||||
public string FlowCode { get; set; }
|
||||
public string? FlowCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否禁用授权
|
||||
|
||||
@ -5,15 +5,15 @@ public class InterfaceFormFieldDto
|
||||
///<summary>
|
||||
/// 参数名
|
||||
///</summary>
|
||||
public string Name { get; set; }
|
||||
public string Name { get; set; } = null!;
|
||||
|
||||
///<summary>
|
||||
/// 参数值
|
||||
///</summary>
|
||||
public string Value { get; set; }
|
||||
public string Value { get; set; } = null!;
|
||||
|
||||
///<summary>
|
||||
/// 参数描述
|
||||
///</summary>
|
||||
public string Description { get; set; }
|
||||
public string? Description { get; set; }
|
||||
}
|
||||
@ -20,11 +20,11 @@ namespace InterfaceForward.Domain.Shared.Dtos
|
||||
///<summary>
|
||||
/// 路径
|
||||
///</summary>
|
||||
public string Path { get; set; }
|
||||
public string Path { get; set; } = null!;
|
||||
|
||||
///<summary>
|
||||
/// 值
|
||||
///</summary>
|
||||
public string Value { get; set; }
|
||||
public string Value { get; set; } = null!;
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,8 +2,6 @@
|
||||
|
||||
public class RequestLogDto(Guid requestId)
|
||||
{
|
||||
public const string Init = null!;
|
||||
|
||||
/// <summary>
|
||||
/// 请求Id
|
||||
/// </summary>
|
||||
@ -22,7 +20,7 @@ public class RequestLogDto(Guid requestId)
|
||||
///<summary>
|
||||
/// 应用/服务商名称
|
||||
///</summary>
|
||||
public string Name { get; set; }
|
||||
public string? Name { get; set; }
|
||||
|
||||
///<summary>
|
||||
/// 发起请求IP
|
||||
@ -32,27 +30,27 @@ public class RequestLogDto(Guid requestId)
|
||||
///<summary>
|
||||
/// 接口代码
|
||||
///</summary>
|
||||
public string InterfaceCode { get; set; }
|
||||
public string? InterfaceCode { get; set; }
|
||||
|
||||
///<summary>
|
||||
/// 请求接口名称
|
||||
///</summary>
|
||||
public string InterfaceName { get; set; }
|
||||
public string? InterfaceName { get; set; }
|
||||
|
||||
///<summary>
|
||||
/// 请求地址
|
||||
///</summary>
|
||||
public string Address { get; set; }
|
||||
public string? Address { get; set; }
|
||||
|
||||
///<summary>
|
||||
/// 请求头
|
||||
///</summary>
|
||||
public string Headers { get; set; }
|
||||
public string? Headers { get; set; }
|
||||
|
||||
///<summary>
|
||||
/// 请求体
|
||||
///</summary>
|
||||
public string Content { get; set; }
|
||||
public string? Content { get; set; }
|
||||
|
||||
///<summary>
|
||||
/// 响应耗时ms
|
||||
@ -62,7 +60,12 @@ public class RequestLogDto(Guid requestId)
|
||||
///<summary>
|
||||
/// 响应结果
|
||||
///</summary>
|
||||
public string Response { get; set; } = Init;
|
||||
public string? Response { get; set; }
|
||||
|
||||
///<summary>
|
||||
/// 响应结果映射后
|
||||
///</summary>
|
||||
public string? Response2 { get; set; }
|
||||
|
||||
///<summary>
|
||||
/// 是否成功调用
|
||||
@ -72,5 +75,5 @@ public class RequestLogDto(Guid requestId)
|
||||
/// <summary>
|
||||
/// 异常
|
||||
/// </summary>
|
||||
public string Exception { get; set; }
|
||||
public string? Exception { get; set; }
|
||||
}
|
||||
@ -12,6 +12,8 @@ public static class RequestServiceProviderFailedCommon
|
||||
|
||||
public static string HeaderInsertFailed = "4004";
|
||||
|
||||
public static string QpsOutLimit = "4005";
|
||||
|
||||
public static bool IsRequestServiceProviderFailed(string code)
|
||||
{
|
||||
return code == RequestFailedCode || code == DeSerializeFailedCode || code == VerifyStatusFailedCode || code == HeaderInsertFailed;
|
||||
|
||||
@ -10,12 +10,12 @@ public class ServiceProviderDto
|
||||
///<summary>
|
||||
/// 服务商名称
|
||||
///</summary>
|
||||
public string Name { get; set; }
|
||||
public string Name { get; set; } = null!;
|
||||
|
||||
///<summary>
|
||||
/// 服务商代码
|
||||
///</summary>
|
||||
public string Code { get; set; }
|
||||
public string Code { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// 是否禁用授权
|
||||
@ -25,12 +25,12 @@ public class ServiceProviderDto
|
||||
///<summary>
|
||||
/// 流程代码
|
||||
///</summary>
|
||||
public string FlowCode { get; set; }
|
||||
public string? FlowCode { get; set; }
|
||||
|
||||
///<summary>
|
||||
/// 授权接口Code
|
||||
///</summary>
|
||||
public string AuthInterfaceCode { get; set; }
|
||||
public string? AuthInterfaceCode { get; set; }
|
||||
|
||||
///<summary>
|
||||
/// 请求超时(s)
|
||||
|
||||
@ -8,6 +8,7 @@ public static class GlobalConst
|
||||
public const string DateTime = "$DateTime";
|
||||
public const string Url = "$Url";
|
||||
public const string Token = "$token";
|
||||
public const string AccountHashCode = "$hash";
|
||||
|
||||
/// <summary>
|
||||
/// 不执行动态代码
|
||||
@ -27,5 +28,7 @@ public static class GlobalConst
|
||||
/// <summary>
|
||||
/// 服务商接口qps redis key前缀
|
||||
/// </summary>
|
||||
public const string ServiceProviderInterfaceQPSKeyPrefix = "InterfaceQPS";
|
||||
public const string ServiceProviderInterfaceQpsKeyPrefix = "InterfaceQPS";
|
||||
|
||||
public const string InterfaceRelayExchange = "sjzy.interface.relay.direct";
|
||||
}
|
||||
@ -23,7 +23,7 @@
|
||||
/// 系统接口Code
|
||||
///</summary>
|
||||
[SugarColumn(ColumnName = "UpStreamCode")]
|
||||
public string UpStreamCode { get; set; }
|
||||
public string UpStreamCode { get; set; } = null!;
|
||||
|
||||
///<summary>
|
||||
/// 服务商ID
|
||||
@ -41,24 +41,30 @@
|
||||
/// 服务商接口Code
|
||||
///</summary>
|
||||
[SugarColumn(ColumnName = "DownStreamCode")]
|
||||
public string DownStreamCode { get; set; }
|
||||
public string DownStreamCode { get; set; } = null!;
|
||||
|
||||
///<summary>
|
||||
/// 当前线上版本
|
||||
///</summary>
|
||||
[SugarColumn(ColumnName = "PublishedVersion")]
|
||||
public string PublishedVersion { get; set; }
|
||||
public string? PublishedVersion { get; set; }
|
||||
|
||||
///<summary>
|
||||
/// 前置脚本
|
||||
///</summary>
|
||||
[SugarColumn(ColumnName = "PrefixScript")]
|
||||
public string PrefixScript { get; set; }
|
||||
public string? PrefixScript { get; set; }
|
||||
|
||||
///<summary>
|
||||
/// 后置脚本
|
||||
///</summary>
|
||||
[SugarColumn(ColumnName = "PostfixScript")]
|
||||
public string PostfixScript { get; set; }
|
||||
public string? PostfixScript { get; set; }
|
||||
|
||||
///<summary>
|
||||
/// 支持不对等映射(自由映射)
|
||||
///</summary>
|
||||
[SugarColumn(ColumnName = "FreeMap")]
|
||||
public bool FreeMap { get; set; }
|
||||
}
|
||||
}
|
||||
@ -11,9 +11,9 @@ namespace InterfaceForward.Repositories.Interface.Services
|
||||
/// <summary>
|
||||
/// 获取接口映射列表
|
||||
/// </summary>
|
||||
/// <param name="upstreamId">系统接口id</param>
|
||||
/// <param name="inputVo"></param>
|
||||
/// <returns></returns>
|
||||
Task<List<InterfaceMapOutputValueObject>> GetMapListByIdAsync(int upstreamId);
|
||||
Task<PagedListDto<InterfaceMapOutputValueObject>> GetMapListAsync(GetMapListInputVO inputVo);
|
||||
|
||||
/// <summary>
|
||||
/// 获取参数映射列表
|
||||
|
||||
@ -13,9 +13,9 @@ public class InterfaceMapRepository : BasicRepository<InterfaceMapEntity>, IScop
|
||||
/// <summary>
|
||||
/// 获取映射列表
|
||||
/// </summary>
|
||||
/// <param name="upstreamId">上游接口id</param>
|
||||
/// <param name="inputVo"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<List<InterfaceMapOutputValueObject>> GetMapListByIdAsync(int upstreamId)
|
||||
public async Task<PagedListDto<InterfaceMapOutputValueObject>> GetMapListAsync(GetMapListInputVO inputVo)
|
||||
{
|
||||
// 上下游一对多关系
|
||||
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<ServiceProviderEntity>((a, b, c, d) => c.ServiceProviderId == d.Id)
|
||||
.Filter(null, true) // 禁用全局过滤器
|
||||
.Where(a => a.UpStreamId == upstreamId)
|
||||
.Where(a => a.UpStreamId == inputVo.InterfaceId)
|
||||
.Where((a, b, c, d) => !a.IsDeleted && !b.IsDeleted && !c.IsDeleted && !d.IsDeleted)
|
||||
.WhereIF(!inputVo.NameOrCode.IsNullOrWhiteSpace(),
|
||||
(a, b, c, d) => d.Code.Contains(inputVo.NameOrCode) || d.Name.Contains(inputVo.NameOrCode))
|
||||
.GroupBy(a => new { a.UpStreamId, a.ServiceProviderId })
|
||||
.OrderBy(a => a.UpdateTime, OrderByType.Desc);
|
||||
|
||||
@ -40,11 +42,12 @@ public class InterfaceMapRepository : BasicRepository<InterfaceMapEntity>, IScop
|
||||
InterfaceCode = SqlFunc.MappingColumn<string>("GROUP_CONCAT(c.Code ORDER BY a.id SEPARATOR ',')"),
|
||||
UpdateTime = a.UpdateTime,
|
||||
PublishedVersion = a.PublishedVersion
|
||||
}).ToListAsync();
|
||||
}).ToPagedListAsync(inputVo.PageIndex, inputVo.PageSize);
|
||||
}
|
||||
|
||||
/// <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`
|
||||
|
||||
@ -5,12 +5,12 @@ public class EditMapDetailsOutput
|
||||
/// <summary>
|
||||
/// 入参映射
|
||||
/// </summary>
|
||||
public List<MapSummary> InParaMapList { get; set; } = new();
|
||||
public List<MapSummary> InParaMapList { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// 出参映射明细
|
||||
/// </summary>
|
||||
public List<InterfaceParameterMapOutputValueObject> MapDetails { get; set; } = new();
|
||||
public List<InterfaceParameterMapOutputValueObject> MapDetails { get; set; } = [];
|
||||
}
|
||||
|
||||
public class MapSummary
|
||||
@ -28,17 +28,22 @@ public class MapSummary
|
||||
/// <summary>
|
||||
/// 接口Name
|
||||
/// </summary>
|
||||
public string InterfaceName { get; set; }
|
||||
public string InterfaceName { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// 前置脚本
|
||||
/// </summary>
|
||||
public string PrefixScript { get; set; }
|
||||
public string? PrefixScript { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 后置脚本
|
||||
/// </summary>
|
||||
public string PostfixScript { get; set; }
|
||||
public string? PostfixScript { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 自由映射(不对等映射)
|
||||
/// </summary>
|
||||
public bool FreeMap { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 映射明细
|
||||
|
||||
@ -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; }
|
||||
}
|
||||
@ -5,15 +5,15 @@ public class InterfaceMapPublishedDifferenceVO
|
||||
///<summary>
|
||||
/// 线上的缓存
|
||||
///</summary>
|
||||
public string PublishedContent { get; set; }
|
||||
public string? PublishedContent { get; set; }
|
||||
|
||||
///<summary>
|
||||
/// 数据库的内容
|
||||
///</summary>
|
||||
public string DbContent { get; set; }
|
||||
public string? DbContent { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 与线上版本的差异内容
|
||||
/// </summary>
|
||||
public string DifferenceContent { get; set; }
|
||||
public string? DifferenceContent { get; set; }
|
||||
}
|
||||
@ -144,7 +144,7 @@ ORDER BY `b`.`Sort` ASC").ToListAsync();
|
||||
/// </summary>
|
||||
/// <param name="interfaceCode"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<InterfaceDto> GetInterfaceByCodeAsync(string interfaceCode)
|
||||
public async Task<InterfaceDto?> GetInterfaceByCodeAsync(string interfaceCode)
|
||||
{
|
||||
var res = await GetInterfaceByCodesAsync(interfaceCode);
|
||||
return res.FirstOrDefault();
|
||||
@ -193,23 +193,22 @@ ORDER BY `b`.`Sort` ASC").ToListAsync();
|
||||
) // 构建系统返参数树形结构
|
||||
};
|
||||
|
||||
if (!serviceProvider.AuthInterfaceCode.IsNullOrEmpty())
|
||||
if (serviceProvider.AuthInterfaceCode != null)
|
||||
{
|
||||
var authInterface = await GetInterfaceByCodeAsync(serviceProvider.AuthInterfaceCode);
|
||||
if (authInterface != null)
|
||||
if (authInterface == null)
|
||||
{
|
||||
throw new BusinessException($"授权接口{serviceProvider.AuthInterfaceCode}不存在");
|
||||
}
|
||||
|
||||
context.ServiceProviderAuthInterface = new TargetInterfaceSummary
|
||||
{
|
||||
TargetInterface = authInterface,
|
||||
PrefixScript = null,
|
||||
PostfixScript = null,
|
||||
InParamTreeList = null,
|
||||
FixedFieldList = await GetAllFixedFieldListAsync(serviceProvider.Id, authInterface.Id),
|
||||
ReturnConfigList = await GetInterfaceReturnConfigListAsync(authInterface.Id),
|
||||
FormFieldList = await GetFormFieldListAsync(authInterface.Id),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
var inParaMaps = await GetParameterMapListAsync(mapIds, true);
|
||||
foreach (var map in maps)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user