This commit is contained in:
xiaolipro 2024-12-20 10:55:59 +08:00
parent 4262004ae6
commit d40e83ba9c
58 changed files with 759 additions and 412 deletions

View File

@ -16,9 +16,10 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\InterfaceForward.Application\InterfaceForward.Application.csproj" /> <ProjectReference Include="..\InterfaceForward.Application\InterfaceForward.Application.csproj" />
<ProjectReference Include="..\InterfaceForward.ServiceDefaults\InterfaceForward.ServiceDefaults.csproj"/> <ProjectReference Include="..\InterfaceForward.ServiceDefaults\InterfaceForward.ServiceDefaults.csproj"/>
<PackageReference Include="Fake.Autofac" Version="8.0.1" /> <PackageReference Include="Fake.Autofac" Version="8.0.0-preview.3" />
<PackageReference Include="Fake.DomainDrivenDesign" Version="8.0.0-preview.3" />
<PackageReference Include="Serilog.AspNetCore" Version="8.0.1" /> <PackageReference Include="Serilog.AspNetCore" Version="8.0.0-preview.3" />
<PackageReference Include="Serilog.Sinks.Elasticsearch" Version="10.0.0" /> <PackageReference Include="Serilog.Sinks.Elasticsearch" Version="10.0.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.1" /> <PackageReference Include="Serilog.Sinks.Console" Version="5.0.1" />
</ItemGroup> </ItemGroup>

View File

@ -32,7 +32,8 @@
}, },
"FeiShuNotice": { "FeiShuNotice": {
"Title": "InterfaceForward-Dev", "Title": "InterfaceForward-Dev",
"Webhook": "https://open.feishu.cn/open-apis/bot/v2/hook/255c44ff-5891-4902-9b6a-6d0250e745f7" "Webhook": "https://open.feishu.cn/open-apis/bot/v2/hook/255c44ff-5891-4902-9b6a-6d0250e745f7",
"Timeout": 20
}, },
"RabbitMQ": { "RabbitMQ": {
"Default": { "Default": {

View File

@ -10,6 +10,8 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Aspire.Hosting" Version="8.2.2" /> <PackageReference Include="Aspire.Hosting" Version="8.2.2" />
<PackageReference Include="Aspire.Hosting.AppHost" Version="8.2.2" />
<PackageReference Include="Fake.DomainDrivenDesign" Version="8.0.0-preview.3" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -0,0 +1,24 @@
namespace InterfaceForward.Application.Contracts.Dtos.App;
public class DeleteAppSubscribeConfigInput
{
/// <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>
/// 【慎重】删除队列删除专用一般false即接口通只解除绑定不删除队列
/// </summary>
public bool IsDeleteQueue { get; set; }
}

View File

@ -0,0 +1,29 @@
namespace InterfaceForward.Application.Contracts.Dtos.App;
public class UpsertAppSubscribeConfigInput
{
/// <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就是默认1
/// </summary>
public ushort FetchCount { get; set; }
/// <summary>
/// 【慎重】失败重回队列一般false即接口通不做重试否则业务错误可能死循环
/// </summary>
public bool FailedRequeue { get; set; }
}

View File

@ -13,14 +13,12 @@ public interface IInterfaceForwardService
/// </remarks> /// </remarks>
/// <param name="upStreamCode">系统接口code</param> /// <param name="upStreamCode">系统接口code</param>
/// <param name="serviceProviderCode">服务商code</param> /// <param name="serviceProviderCode">服务商code</param>
/// <param name="mainBodyId">主体id</param>
/// <param name="data">body</param> /// <param name="data">body</param>
/// <param name="attach">额外信息,原样返回</param> /// <param name="attach">额外信息,原样返回</param>
/// <param name="isReturnMessage">是否返回原报文</param> /// <param name="isReturnMessage">是否返回原报文</param>
/// <param name="failedSubscribeName">失败推送订阅名,不填就不推</param> /// <param name="failedSubscribeName">失败推送订阅名,不填就不推</param>
/// <returns></returns> /// <returns></returns>
Task<InterfaceRelayUnifyResultDto> ForwardWithSubscribeAsync(string upStreamCode, string serviceProviderCode, Task<InterfaceRelayUnifyResultDto> ForwardWithSubscribeAsync(string upStreamCode, string serviceProviderCode,
int mainBodyId,
object data, string? attach = null, bool isReturnMessage = false, string? failedSubscribeName = null); object data, string? attach = null, bool isReturnMessage = false, string? failedSubscribeName = null);
/// <summary> /// <summary>
@ -28,7 +26,6 @@ public interface IInterfaceForwardService
/// </summary> /// </summary>
/// <param name="upStreamCode">系统接口code</param> /// <param name="upStreamCode">系统接口code</param>
/// <param name="serviceProviderCode">服务商code</param> /// <param name="serviceProviderCode">服务商code</param>
/// <param name="mainBodyId">主体id</param>
/// <param name="requestId">请求id</param> /// <param name="requestId">请求id</param>
/// <param name="data">body</param> /// <param name="data">body</param>
/// <param name="attach">额外信息,原样返回</param> /// <param name="attach">额外信息,原样返回</param>
@ -36,6 +33,6 @@ public interface IInterfaceForwardService
/// <param name="failedSubscribeName">失败推送订阅名,不填就不推</param> /// <param name="failedSubscribeName">失败推送订阅名,不填就不推</param>
/// <returns></returns> /// <returns></returns>
Task<InterfaceRelayUnifyResultDto> ReForwardAsync(string upStreamCode, string serviceProviderCode, Task<InterfaceRelayUnifyResultDto> ReForwardAsync(string upStreamCode, string serviceProviderCode,
int mainBodyId, Guid requestId, object data, string? attach = null, Guid requestId, object data, string? attach = null,
bool isReturnMessage = false, string? failedSubscribeName = null); bool isReturnMessage = false, string? failedSubscribeName = null);
} }

View File

@ -1,4 +1,4 @@
namespace SJZY.InterfaceRelay.Application.Contracts; namespace InterfaceForward.Application.Contracts.ForwardCore;
public class InterfaceForwardOptions public class InterfaceForwardOptions
{ {

View File

@ -1,4 +1,4 @@
namespace SJZY.InterfaceRelay.Application.Contracts; namespace InterfaceForward.Application.Contracts.ForwardCore;
public class InterfaceRelayForwardReplayEvent public class InterfaceRelayForwardReplayEvent
{ {

View File

@ -3,16 +3,15 @@
[Serializable] [Serializable]
public class InterfaceRelayUnifyResultDto public class InterfaceRelayUnifyResultDto
{ {
public string Code { get; set; }
public string Msg { get; set; }
/// <summary> /// <summary>
/// 请求Id /// 请求Id
/// </summary> /// </summary>
public Guid RequestId { get; set; } public Guid RequestId { get; set; }
/// <summary>
/// 消息
/// </summary>
public string? Message { get; set; }
/// <summary> /// <summary>
/// 原报文 /// 原报文
/// </summary> /// </summary>

View File

@ -1,4 +1,4 @@
namespace SJZY.InterfaceRelay.Application.Contracts; namespace InterfaceForward.Application.Contracts;
public class RequestServiceProviderFailedEvent public class RequestServiceProviderFailedEvent
{ {

View File

@ -9,4 +9,8 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\InterfaceForward.Domain.Shared\InterfaceForward.Domain.Shared.csproj" /> <ProjectReference Include="..\InterfaceForward.Domain.Shared\InterfaceForward.Domain.Shared.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<PackageReference Include="Fake.DomainDrivenDesign" Version="8.0.0-preview.3" />
</ItemGroup>
</Project> </Project>

View File

@ -2,6 +2,7 @@
using Fake.Application; using Fake.Application;
using Fake.AspNetCore.Http; using Fake.AspNetCore.Http;
using InterfaceForward.Repositories; using InterfaceForward.Repositories;
using InterfaceForward.Repositories.Interface.Entitys;
using InterfaceForward.Repositories.Log.Services; using InterfaceForward.Repositories.Log.Services;
using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
@ -13,7 +14,8 @@ namespace InterfaceForward.Application.Filters;
public class ForwardAuthorizeFilter( public class ForwardAuthorizeFilter(
InterfaceForwardQuery interfaceForwardQuery, InterfaceForwardQuery interfaceForwardQuery,
ILogRepository logRepository, ILogRepository logRepository,
IHttpClientInfoProvider httpClientInfoProvider) IHttpClientInfoProvider httpClientInfoProvider,
IBasicRepository<InterfaceEntity> interfaceRepository)
: IAsyncActionFilter : IAsyncActionFilter
{ {
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
@ -27,7 +29,7 @@ public class ForwardAuthorizeFilter(
log.IsApp = true; log.IsApp = true;
log.ClientIp = httpClientInfoProvider.ClientIpAddress; log.ClientIP = httpClientInfoProvider.ClientIpAddress;
log.Address = httpContext.Request.GetDisplayUrl(); log.Address = httpContext.Request.GetDisplayUrl();
// body // body
@ -41,7 +43,6 @@ public class ForwardAuthorizeFilter(
var headers = httpContext.Request.Headers; var headers = httpContext.Request.Headers;
log.Headers = JsonConvert.SerializeObject(headers); log.Headers = JsonConvert.SerializeObject(headers);
// _segContext.Context.Span.AddLog(LogEvent.Message("开始应用授权"));
string? appKey = headers["appKey"], appSecret = headers["appSecret"]; string? appKey = headers["appKey"], appSecret = headers["appSecret"];
if (appKey.IsNullOrWhiteSpace() || appSecret.IsNullOrWhiteSpace()) if (appKey.IsNullOrWhiteSpace() || appSecret.IsNullOrWhiteSpace())
{ {
@ -56,6 +57,13 @@ public class ForwardAuthorizeFilter(
throw new BusinessException(message: "应用已被禁用,请检查应用状态"); throw new BusinessException(message: "应用已被禁用,请检查应用状态");
logRepository.AppId = app.Id; logRepository.AppId = app.Id;
var upStreamCode = httpContext.Request.Query["upStreamCode"].ToString();
var systemInterface = await interfaceRepository.GetFirstAsync(x => x.Code == upStreamCode,
x => new { x.Code, x.Name });
if (systemInterface == null) throw new BusinessException(message: "系统接口不存在");
logRepository.AppRequestLog.InterfaceCode = systemInterface.Code;
logRepository.AppRequestLog.InterfaceName = systemInterface.Name;
var actionContext = await next(); var actionContext = await next();
@ -72,7 +80,7 @@ public class ForwardAuthorizeFilter(
log.Response = actionContext.Result?.ToString()?? string.Empty; log.Response = actionContext.Result?.ToString()?? string.Empty;
} }
_ = logRepository.WriteAppLogAsync(); _ = logRepository.WriteAppLog();
} }
} }
} }

View File

@ -0,0 +1,81 @@
using System.ComponentModel.DataAnnotations;
using InterfaceForward.Application.Contracts;
using InterfaceForward.Application.Contracts.ForwardCore;
using InterfaceForward.Application.Rabbit;
using InterfaceForward.Repositories.Log.Services;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
namespace InterfaceForward.Application.Filters;
public class ForwardExceptionFilter(
ILogRepository logRepository,
IOptions<InterfaceRelayOptions> options,
RabbitClient rabbitClient)
: IAsyncExceptionFilter
{
private readonly InterfaceRelayOptions _options = options.Value;
public Task OnExceptionAsync(ExceptionContext context)
{
if (context.ExceptionHandled) return Task.CompletedTask;
/*
* ServiceProviderRequestLog设计的意义是
* 便
*/
var originMessage = bool.TryParse(context.HttpContext.Request.Headers["isReturnMessage"].ToString(),
out var isReturnMessage)
? isReturnMessage ? logRepository.ServiceProviderRequestLog.Response : null
: null;
var res = new InterfaceRelayUnifyResultDto
{
Code = StatusCodes.Status400BadRequest.ToString(),
RequestId = logRepository.AppRequestLog.RequestId,
OriginalMessage = originMessage,
Attach = context.HttpContext.Request.Headers["attach"]
};
if (context.Exception is ValidationException errors) //验证异常
{
res.Msg = errors.Message;
context.Result = new JsonResult(res);
}
else if (context.Exception is BusinessException business) //业务异常
{
res.Msg = business.Message;
context.Result = new JsonResult(res);
}
else //未处理异常
{
logRepository.SendFeiShu(context.Exception);
res.Code = StatusCodes.Status500InternalServerError.ToString();
res.Msg = "服务器发生未处理异常";
context.Result = new JsonResult(res);
}
if (NeedPush(context, out var subscribeName))
{
// todo要改成http回调的方式
}
// 异常情况也要记录app日志
logRepository.AppRequestLog.Response = JsonConvert.SerializeObject(res);
_ = logRepository.WriteAppLog(context.Exception);
context.ExceptionHandled = true;
return Task.CompletedTask;
}
private bool NeedPush(ExceptionContext context, out string subscribeName)
{
subscribeName = null;
string interfaceCode = context.HttpContext.Request.Query["upStreamCode"];
subscribeName = context.HttpContext.Request.Headers["failedSubscribeName"];
return _options.FailedNeedPush.Contains(interfaceCode) && !subscribeName.IsNullOrWhiteSpace();
}
}

View File

@ -1,55 +0,0 @@
using Fake.ExceptionHandling;
using InterfaceForward.Application.Contracts.ForwardCore;
using InterfaceForward.Domain.Shared.FeiShu;
using InterfaceForward.Repositories.Log.Services;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
namespace InterfaceForward.Application.Filters;
public class ForwardExceptionSubscriber : IExceptionSubscriber
{
public Task HandleAsync(ExceptionNotificationContext context)
{
var httpContext = context.ServiceProvider.GetRequiredService<IHttpContextAccessor>().HttpContext;
var logRepository = context.ServiceProvider.GetRequiredService<ILogRepository>();
var feiShuNoticer = context.ServiceProvider.GetRequiredService<IFeiShuNoticer>();
// if (httpContext == null)
// {
// feiShuNoticer.NoticeAsync($"""
// 日志请求id{ServiceProviderRequestLog.RequestId}
// 服务商--接口:{ServiceProviderRequestLog.Name}--{ServiceProviderRequestLog.InterfaceName}
// 异常原因:{Truncate(contextException.ToString())}
// 请求地址:{ServiceProviderRequestLog.Address}
// 原始报文:
// {Truncate(AppRequestLog.Content)}
// 请求报文:
// {Truncate(ServiceProviderRequestLog.Content)}
// 响应报文:
// {Truncate(ServiceProviderRequestLog.Response)}
// """)
// }
//
/*
* ServiceProviderRequestLog设计的意义是
* 便
*/
var originMessage = bool.TryParse(httpContext.Request.Headers["isReturnMessage"].ToString(),
out var isReturnMessage)
? isReturnMessage ? logRepository.ServiceProviderRequestLog.Response : null
: null;
var res = new InterfaceRelayUnifyResultDto
{
RequestId = logRepository.AppRequestLog.RequestId,
OriginalMessage = originMessage,
Message = context.Exception.Message,
Attach = httpContext.Request.Headers["attach"]
};
logRepository.AppRequestLog.Response = JsonConvert.SerializeObject(res);
// 异常情况也要记录app日志
_ = logRepository.WriteAppLogAsync(context.Exception);
return Task.CompletedTask;
}
}

View File

@ -249,7 +249,7 @@ public class DefaultForwardFlow : IForwardFlow
public virtual void BeforeRequest(ForwardCoreContext context) public virtual void BeforeRequest(ForwardCoreContext context)
{ {
var log = context.ServiceProviderRequestLog; var log = context.ServiceProviderRequestLog;
log.ClientIp = NetUtil.GetLanIp(); log.ClientIP = NetUtil.GetLanIp();
log.Content = context.TargetInterfaceInput; log.Content = context.TargetInterfaceInput;
context.ServiceProviderRequestLog.RequestTime = DateTime.Now; context.ServiceProviderRequestLog.RequestTime = DateTime.Now;

View File

@ -3,20 +3,13 @@ using Fake.DependencyInjection;
using InterfaceForward.Application.Rabbit; using InterfaceForward.Application.Rabbit;
using InterfaceForward.Domain.Shared; using InterfaceForward.Domain.Shared;
using InterfaceForward.Domain.Shared.Enum; using InterfaceForward.Domain.Shared.Enum;
using Microsoft.IdentityModel.Logging;
using RabbitMQ.Client; using RabbitMQ.Client;
namespace InterfaceForward.Application.HostServices; namespace InterfaceForward.Application.HostServices;
public class AppSubscribeEventHandler : ITransientDependency public class AppSubscribeEventHandler(RabbitClient rabbitClient) : ITransientDependency
{ {
public const string AppSubscribeEventSubscribe = "sjzy.interface.relay.app.subscribe"; public const string AppSubscribeEventSubscribe = "interface.relay.app.subscribe";
private readonly RabbitClient _rabbitClient;
public AppSubscribeEventHandler(RabbitClient rabbitClient)
{
_rabbitClient = rabbitClient;
}
public Task Handle(AppSubscribeEvent @event) public Task Handle(AppSubscribeEvent @event)
{ {
@ -26,15 +19,15 @@ public class AppSubscribeEventHandler : ITransientDependency
break; break;
case OptionType.: case OptionType.:
var res = DoAdd(@event); var res = DoAdd(@event);
LogHelper.Info($"{Dns.GetHostName()}\n{@event.ToJson()}\n{res}", true); LogHelper.Info($"{Dns.GetHostName()}\n{@event.ToJson()}\n{res}");
break; break;
case OptionType.: case OptionType.:
var res1 = DoUpdate(@event); var res1 = DoUpdate(@event);
LogHelper.Info($"{Dns.GetHostName()}\n{@event.ToJson()}\n{res1}", true); LogHelper.Info($"{Dns.GetHostName()}\n{@event.ToJson()}\n{res1}");
break; break;
case OptionType.: case OptionType.:
var res2 = DoDelete(@event); var res2 = DoDelete(@event);
LogHelper.Info($"{Dns.GetHostName()}\n{@event.ToJson()}\n{res2}", true); LogHelper.Info($"{Dns.GetHostName()}\n{@event.ToJson()}\n{res2}");
break; break;
default: default:
throw new ArgumentOutOfRangeException(); throw new ArgumentOutOfRangeException();
@ -47,9 +40,9 @@ public class AppSubscribeEventHandler : ITransientDependency
{ {
var queueName = var queueName =
$"{BatchForwardEventHandler.BatchForwardEventSubscribe}.{@event.AppKey}.{@event.SystemInterfaceCode}.{@event.ServiceProviderCode}"; $"{BatchForwardEventHandler.BatchForwardEventSubscribe}.{@event.AppKey}.{@event.SystemInterfaceCode}.{@event.ServiceProviderCode}";
_rabbitClient.UnSubscribe(queueName, GlobalConst.InterfaceRelayExchange, queueName, @event.IsDeleteQueue); rabbitClient.UnSubscribe(queueName, GlobalConst.InterfaceRelayExchange, queueName, @event.IsDeleteQueue);
_rabbitClient.Subscribe(new ConsumeOptions rabbitClient.Subscribe(new ConsumeOptions
{ {
Queue = queueName, Queue = queueName,
FetchCount = @event.FetchCount, // 并发控制 FetchCount = @event.FetchCount, // 并发控制
@ -69,7 +62,7 @@ public class AppSubscribeEventHandler : ITransientDependency
{ {
var queueName = var queueName =
$"{BatchForwardEventHandler.BatchForwardEventSubscribe}.{@event.AppKey}.{@event.SystemInterfaceCode}.{@event.ServiceProviderCode}"; $"{BatchForwardEventHandler.BatchForwardEventSubscribe}.{@event.AppKey}.{@event.SystemInterfaceCode}.{@event.ServiceProviderCode}";
_rabbitClient.UnSubscribe(queueName, GlobalConst.InterfaceRelayExchange, queueName, @event.IsDeleteQueue); rabbitClient.UnSubscribe(queueName, GlobalConst.InterfaceRelayExchange, queueName, @event.IsDeleteQueue);
return $"已成功{(@event.IsDeleteQueue ? "" : "")}队列:{queueName},可以通过/AppSubscribe/GetList查询所有订阅"; return $"已成功{(@event.IsDeleteQueue ? "" : "")}队列:{queueName},可以通过/AppSubscribe/GetList查询所有订阅";
} }
@ -77,7 +70,7 @@ public class AppSubscribeEventHandler : ITransientDependency
{ {
var queueName = var queueName =
$"{BatchForwardEventHandler.BatchForwardEventSubscribe}.{@event.AppKey}.{@event.SystemInterfaceCode}.{@event.ServiceProviderCode}"; $"{BatchForwardEventHandler.BatchForwardEventSubscribe}.{@event.AppKey}.{@event.SystemInterfaceCode}.{@event.ServiceProviderCode}";
_rabbitClient.Subscribe(new ConsumeOptions rabbitClient.Subscribe(new ConsumeOptions
{ {
Queue = queueName, Queue = queueName,
FetchCount = @event.FetchCount, // 并发控制 FetchCount = @event.FetchCount, // 并发控制

View File

@ -1,10 +1,10 @@
using Fake.Helpers; using Fake.SyncEx;
using InterfaceForward.Application.Helpers; using InterfaceForward.Application.Helpers;
using InterfaceForward.Application.Rabbit; using InterfaceForward.Application.Rabbit;
using InterfaceForward.Domain.Shared; using InterfaceForward.Domain.Shared;
using InterfaceForward.Repositories; using InterfaceForward.Repositories;
using InterfaceForward.Repositories.App.Entitys;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.IdentityModel.Logging;
using Newtonsoft.Json; using Newtonsoft.Json;
using RabbitMQ.Client; using RabbitMQ.Client;
@ -13,6 +13,7 @@ namespace InterfaceForward.Application.HostServices;
public class AppSubscribeHostService : IHostedService public class AppSubscribeHostService : IHostedService
{ {
private readonly AppSubscribeEventHandler _appSubscribeEventHandler; private readonly AppSubscribeEventHandler _appSubscribeEventHandler;
private readonly IBasicRepository<AppSubscribeConfigEntity> _appSubscribeConfigRepository;
private readonly RabbitClient _rabbitClient; private readonly RabbitClient _rabbitClient;
public AppSubscribeHostService(IBasicRepository<AppSubscribeConfigEntity> appSubscribeConfigRepository, public AppSubscribeHostService(IBasicRepository<AppSubscribeConfigEntity> appSubscribeConfigRepository,
@ -63,7 +64,7 @@ public class AppSubscribeHostService : IHostedService
if (chan == AppSubscribeEventHandler.AppSubscribeEventSubscribe && msg is string str) if (chan == AppSubscribeEventHandler.AppSubscribeEventSubscribe && msg is string str)
{ {
var @event = JsonConvert.DeserializeObject<AppSubscribeEvent>(str); var @event = JsonConvert.DeserializeObject<AppSubscribeEvent>(str);
AsyncHelper.RunSync(() => _appSubscribeEventHandler.Handle(@event)); SyncContext.Run(() => _appSubscribeEventHandler.Handle(@event!));
} }
} }
} }

View File

@ -17,9 +17,10 @@
<PackageReference Include="DiffPlex" Version="1.7.2" /> <PackageReference Include="DiffPlex" Version="1.7.2" />
<PackageReference Include="Aliyun.OSS.SDK.NetCore" Version="2.13.0" /> <PackageReference Include="Aliyun.OSS.SDK.NetCore" Version="2.13.0" />
<PackageReference Include="DotNetCore.Natasha.CSharp" Version="5.2.2.1"/> <PackageReference Include="DotNetCore.Natasha.CSharp" Version="5.2.2.1"/>
<PackageReference Include="Fake.AspNetCore" Version="8.0.1" /> <PackageReference Include="Fake.AspNetCore" Version="8.0.0-preview.3" />
<PackageReference Include="Fake.EventBus.RabbitMQ" Version="8.0.1" /> <PackageReference Include="Fake.DomainDrivenDesign" Version="8.0.0-preview.3" />
<PackageReference Include="Fake.ObjectMapping.AutoMapper" Version="8.0.1" /> <PackageReference Include="Fake.EventBus.RabbitMQ" Version="8.0.0-preview.3" />
<PackageReference Include="Fake.ObjectMapping.AutoMapper" Version="8.0.0-preview.3" />
<PackageReference Include="FreeRedis" Version="1.2.15" /> <PackageReference Include="FreeRedis" Version="1.2.15" />
</ItemGroup> </ItemGroup>

View File

@ -6,9 +6,9 @@ public class BatchForwardEvent
public int AppId { get; set; } public int AppId { get; set; }
public string UpStreamCode { get; set; } public string UpStreamCode { get; set; } = null!;
public string ServiceProviderCode { get; set; } public string ServiceProviderCode { get; set; } = null!;
public string Attach { get; set; } public string Attach { get; set; }
@ -16,5 +16,5 @@ public class BatchForwardEvent
public bool IsReturnMessage { get; set; } public bool IsReturnMessage { get; set; }
public string Data { get; set; } public string Data { get; set; } = null!;
} }

View File

@ -3,37 +3,35 @@ using Fake.DependencyInjection;
using InterfaceForward.Application.Contracts.ForwardCore; using InterfaceForward.Application.Contracts.ForwardCore;
using InterfaceForward.Application.Services; using InterfaceForward.Application.Services;
using InterfaceForward.Domain.Shared; using InterfaceForward.Domain.Shared;
using InterfaceForward.Domain.Shared.FeiShu;
using InterfaceForward.Repositories.Log.Services; using InterfaceForward.Repositories.Log.Services;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using SJZY.InterfaceRelay.Application.Contracts; using RabbitMQ.Client.Events;
namespace InterfaceForward.Application.Rabbit; namespace InterfaceForward.Application.Rabbit;
public class BatchForwardEventHandler( public class BatchForwardEventHandler(
InterfaceForwardCommon forwardCommon, InterfaceForwardCommon forwardCommon,
RabbitClient rabbitClient, RabbitClient rabbitClient,
ILogRepository logRepository, ILogRepository logRepository)
ILogger<BatchForwardEventHandler> logger,
IFeiShuNoticer feiShuNoticer)
: ITransientDependency, IRabbitHandler : ITransientDependency, IRabbitHandler
{ {
public const string BatchForwardEventSubscribe = "sjzy.interface.relay.batch"; public const string BatchForwardEventSubscribe = "interface.relay.batch";
private readonly RabbitClient _rabbitClient = rabbitClient;
public bool Enable(ConsumeOptions options) public bool Enable(ConsumeOptions options)
{ {
return options.Queue.StartsWith(BatchForwardEventSubscribe); return options.Queue.StartsWith(BatchForwardEventSubscribe);
} }
public async Task Handle(IServiceProvider sp, BasicDeliverEventArgs args, ConsumeOptions options) public async Task Handle(BasicDeliverEventArgs args)
{ {
var msg = Encoding.UTF8.GetString(args.Body.ToArray()); 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); var @event = System.Text.Json.JsonSerializer.Deserialize<BatchForwardEvent>(msg);
if (@event == null)
{
throw new BusinessException("消息格式错误");
}
// important恢复应用现场 全局贯彻 // important恢复应用现场 全局贯彻
logRepository.AppId = @event.AppId; logRepository.AppId = @event.AppId;
@ -71,7 +69,7 @@ public class BatchForwardEventHandler(
? req ? req
: new JObject : new JObject
{ {
[@event.UpStreamCode!] = req [@event.UpStreamCode] = req
}; };
var res = contextList.Count == 1 var res = contextList.Count == 1
@ -85,12 +83,12 @@ public class BatchForwardEventHandler(
catch (Exception ex) catch (Exception ex)
{ {
replayEvent.Message = ex.Message; replayEvent.Message = ex.Message;
await feiShuNoticer.NoticeAsync(ex.ToString()); LogHelper.Error(ex.ToString(), false);
} }
finally finally
{ {
var data = System.Text.Json.JsonSerializer.SerializeToUtf8Bytes(replayEvent); var data = System.Text.Json.JsonSerializer.SerializeToUtf8Bytes(replayEvent);
_rabbitClient.Publish(GlobalConst.InterfaceRelayExchange, @event.ResSubscribeName, data); rabbitClient.Publish(GlobalConst.InterfaceRelayExchange, @event.ResSubscribeName, data);
} }
} }
} }

View File

@ -0,0 +1,10 @@
using RabbitMQ.Client.Events;
namespace InterfaceForward.Application.Rabbit;
public interface IRabbitHandler
{
bool Enable(ConsumeOptions options);
Task Handle(BasicDeliverEventArgs args);
}

View File

@ -1,38 +1,68 @@
using System.Text;
using Fake.RabbitMQ; using Fake.RabbitMQ;
using InterfaceForward.Domain.Shared; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using RabbitMQ.Client; using RabbitMQ.Client;
using RabbitMQ.Client.Events; using RabbitMQ.Client.Events;
namespace InterfaceForward.Application.Rabbit; namespace InterfaceForward.Application.Rabbit;
public class RabbitClient(IRabbitMqChannelPool channelPool) public class RabbitClient(
IRabbitMqChannelPool channelPool,
IServiceScopeFactory serviceScopeFactory,
ILogger<RabbitClient> logger)
{ {
public void Subscribe(ConsumeOptions consumeOptions) public void Subscribe(ConsumeOptions consumeOptions)
{ {
using var channelAccessor = channelPool.Acquire(consumeOptions.Queue, configureChannel: channel => using var channelAccessor = channelPool.Acquire(consumeOptions.Queue);
{
channel.ExchangeDeclare(GlobalConst.InterfaceRelayExchange, ExchangeType.Direct, true);
channel.QueueDeclare(consumeOptions.Queue, true);
channel.QueueBind(consumeOptions.Queue, GlobalConst.InterfaceRelayExchange, consumeOptions.Queue);
if (consumeOptions.FetchCount > 0) var channel = channelAccessor.Channel;
consumeOptions.Declaration.Invoke(channel);
if (consumeOptions.FetchCount > 0)
{
channel.BasicQos(0, consumeOptions.FetchCount, false);
}
var consumer = new AsyncEventingBasicConsumer(channel);
consumer.Received += async (model, ea) =>
{
await using var scope = serviceScopeFactory.CreateAsyncScope();
foreach (var handler in scope.ServiceProvider.GetServices<IRabbitHandler>())
{ {
channel.BasicQos(0, consumeOptions.FetchCount, false); if (handler.Enable(consumeOptions))
}
var consumer = new AsyncEventingBasicConsumer(channel);
consumer.Received += async (model, ea) =>
{ {
var body = ea.Body.ToArray(); await handler.Handle(ea);
var message = Encoding.UTF8.GetString(body); }
Console.WriteLine(" [x] Received {0}", message); }
}; };
channel.BasicConsume(queue: consumeOptions.Queue, channel.BasicConsume(queue: consumeOptions.Queue,
autoAck: false, autoAck: false,
consumer: consumer); consumer: consumer);
}); }
public void Publish(string exchange, string routingKey, byte[] data, Action<IBasicProperties>? options = null)
{
using var channelAccessor = channelPool.Acquire(routingKey);
var props = channelAccessor.Channel.CreateBasicProperties();
options?.Invoke(props);
channelAccessor.Channel.BasicPublish(exchange, routingKey, props, data);
}
public void UnSubscribe(string queueName, string exchange, string routingKey, bool deleteQueue = false)
{
logger.LogInformation(
$"UnSubscribe queue: {queueName} exchange: {exchange} routingKey: {routingKey} deleteQueue: {deleteQueue}");
using var channelAccessor = channelPool.Acquire(queueName);
if (deleteQueue)
{
var passive = channelAccessor.Channel.QueueDeclarePassive(queueName);
logger.LogInformation($"Queue {queueName} exists, deleting... {passive.MessageCount} messages in queue");
channelAccessor.Channel.QueueDelete(queueName);
}
channelPool.Release(queueName);
} }
} }
@ -43,6 +73,6 @@ public class ConsumeOptions
public ushort FetchCount { get; set; } public ushort FetchCount { get; set; }
public bool FailedRequeue { get; set; } public bool FailedRequeue { get; set; }
public Action<IDeclaration> public Action<IModel> Declaration { get; set; }
} }

View File

@ -0,0 +1,133 @@
using InterfaceForward.Application.Contracts.Dtos.App;
using InterfaceForward.Application.Helpers;
using InterfaceForward.Application.HostServices;
using InterfaceForward.Domain.Shared.Enum;
using InterfaceForward.Repositories;
using InterfaceForward.Repositories.App.Entitys;
using InterfaceForward.Repositories.Interface.Entitys;
using InterfaceForward.Repositories.ServiceProvider.Entitys;
using Microsoft.AspNetCore.Mvc;
namespace InterfaceForward.Application.Services.App;
/// <summary>
/// 应用订阅
/// </summary>
[Route("AppSubscribe")]
[ApiExplorerSettings(GroupName = "应用服务")]
public class AppSubscribeService : ApplicationService
{
public const int DefaultFetchCount = 1;
public const bool DefaultFailedRequeue = false;
private readonly IBasicRepository<AppEntity> _appRepository;
private readonly IBasicRepository<AppSubscribeConfigEntity> _appSubscribeConfigRepository;
private readonly IBasicRepository<InterfaceEntity> _interfaceRepository;
private readonly IBasicRepository<ServiceProviderEntity> _serviceProviderRepository;
public AppSubscribeService(IBasicRepository<AppSubscribeConfigEntity> appSubscribeConfigRepository,
IBasicRepository<InterfaceEntity> interfaceRepository,
IBasicRepository<ServiceProviderEntity> serviceProviderRepository, IBasicRepository<AppEntity> appRepository)
{
_appSubscribeConfigRepository = appSubscribeConfigRepository;
_interfaceRepository = interfaceRepository;
_serviceProviderRepository = serviceProviderRepository;
_appRepository = appRepository;
}
/// <summary>
/// 订阅列表
/// </summary>
/// <returns></returns>
[HttpGet("GetList")]
public async Task<List<AppSubscribeConfigEntity>> GetList(string? appKey)
{
return await _appSubscribeConfigRepository.GetListAsync(x =>
string.IsNullOrWhiteSpace(appKey) || x.AppKey == appKey);
}
/// <summary>
/// 创建or更新
/// </summary>
/// <param name="input">入参</param>
/// <returns></returns>
[HttpPost("Upsert")]
public async Task<string> Upsert(UpsertAppSubscribeConfigInput input)
{
if (input.FetchCount == 0)
{
input.FetchCount = DefaultFetchCount;
}
if (!await _appRepository.IsAnyAsync(x => x.AppKey == input.AppKey))
{
return "应用不存在";
}
if (!await _interfaceRepository.IsAnyAsync(x => x.IsUpStream == true && x.Code == input.SystemInterfaceCode))
{
return "系统接口不存在";
}
if (!await _serviceProviderRepository.IsAnyAsync(x => x.Code == input.ServiceProviderCode))
{
return "服务商不存在";
}
var config = await _appSubscribeConfigRepository.GetFirstAsync(x =>
x.AppKey == input.AppKey && x.SystemInterfaceCode == input.SystemInterfaceCode &&
x.ServiceProviderCode == input.ServiceProviderCode);
if (config != null)
{
if (config.FetchCount != input.FetchCount || config.FailedRequeue != input.FailedRequeue)
{
config.FetchCount = input.FetchCount;
config.FailedRequeue = input.FailedRequeue;
await _appSubscribeConfigRepository.UpdateAsync(config);
var @event = ObjectMapper.Map<UpsertAppSubscribeConfigInput, AppSubscribeEvent>(input);
@event.OptionType = OptionType.;
await RedisHelper.Client.PublishAsync(AppSubscribeEventHandler.AppSubscribeEventSubscribe,
@event.ToJson());
return "订阅已更新,关注飞书消息,等待节点状态同步";
}
return "订阅已存在,且没有发生任何变化";
}
config = ObjectMapper.Map<UpsertAppSubscribeConfigInput, AppSubscribeConfigEntity>(input);
await _appSubscribeConfigRepository.InsertAsync(config);
var eventForAdd = ObjectMapper.Map<UpsertAppSubscribeConfigInput, AppSubscribeEvent>(input);
eventForAdd.OptionType = OptionType.;
await RedisHelper.Client.PublishAsync(AppSubscribeEventHandler.AppSubscribeEventSubscribe,
eventForAdd.ToJson());
return "新增订阅成功,关注飞书消息,等待节点状态同步";
}
/// <summary>
/// 删除
/// </summary>
/// <param name="input">入参</param>
/// <returns></returns>
[HttpPost("Delete")]
public async Task<string> Delete(DeleteAppSubscribeConfigInput input)
{
var hasChange = await _appSubscribeConfigRepository.SoftDeleteAsync(x =>
x.AppKey == input.AppKey && x.SystemInterfaceCode == input.SystemInterfaceCode &&
x.ServiceProviderCode == input.ServiceProviderCode);
if (hasChange)
{
var chan = AppSubscribeEventHandler.AppSubscribeEventSubscribe;
var @event = ObjectMapper.Map<DeleteAppSubscribeConfigInput, AppSubscribeEvent>(input);
@event.OptionType = OptionType.;
await RedisHelper.Client.PublishAsync(chan, @event.ToJson());
return "成功删除应用订阅,关注飞书消息,等待节点状态同步";
}
return "应用订阅不存在";
}
}

View File

@ -20,7 +20,7 @@ namespace InterfaceForward.Application.Services.App;
/// </summary> /// </summary>
[Route("App")] [Route("App")]
[ApiExplorerSettings(GroupName = "应用服务")] [ApiExplorerSettings(GroupName = "应用服务")]
public class AppsService : BaseService public class AppsService : ApplicationService
{ {
private readonly IAppRepository _appRepository; private readonly IAppRepository _appRepository;
private readonly IAppScopeRepository _appScopeRepository; private readonly IAppScopeRepository _appScopeRepository;

View File

@ -8,7 +8,7 @@ namespace InterfaceForward.Application.Services.App
/// </summary> /// </summary>
[Route("Dictionary")] [Route("Dictionary")]
[ApiExplorerSettings(GroupName = "应用服务")] [ApiExplorerSettings(GroupName = "应用服务")]
public class DictionaryService : BaseService public class DictionaryService : ApplicationService
{ {
private readonly IDictionaryRepository _dictionaryRepository; private readonly IDictionaryRepository _dictionaryRepository;

View File

@ -11,7 +11,7 @@ namespace InterfaceForward.Application.Services.App
/// </summary> /// </summary>
[Route("Log")] [Route("Log")]
[ApiExplorerSettings(GroupName = "应用服务")] [ApiExplorerSettings(GroupName = "应用服务")]
public class LogService : BaseService public class LogService : ApplicationService
{ {
private readonly ILogRepository _logRepository; private readonly ILogRepository _logRepository;

View File

@ -1,7 +0,0 @@
using Fake.Application;
namespace InterfaceForward.Application.Services;
public abstract class BaseService : ApplicationService
{
}

View File

@ -13,7 +13,7 @@ namespace InterfaceForward.Application.Services.Interface;
/// 接口返回配置 /// 接口返回配置
/// </summary> /// </summary>
[ApiExplorerSettings(GroupName = "接口服务")] [ApiExplorerSettings(GroupName = "接口服务")]
public class InterfaceReturnConfigService : BaseService public class InterfaceReturnConfigService : ApplicationService
{ {
private readonly IInterfaceReturnConfigRepository _interfaceReturnConfigRepository; private readonly IInterfaceReturnConfigRepository _interfaceReturnConfigRepository;
private readonly IInterfaceRepository _interfaceRepository; private readonly IInterfaceRepository _interfaceRepository;

View File

@ -18,7 +18,7 @@ namespace InterfaceForward.Application.Services.Interface;
/// 服务商接口 /// 服务商接口
/// </summary> /// </summary>
[ApiExplorerSettings(GroupName = "接口服务")] [ApiExplorerSettings(GroupName = "接口服务")]
public class ServiceProviderInterfaceService : BaseService public class ServiceProviderInterfaceService : ApplicationService
{ {
private ParameterService ParameterService => LazyServiceProvider.GetRequiredService<ParameterService>(); private ParameterService ParameterService => LazyServiceProvider.GetRequiredService<ParameterService>();

View File

@ -17,7 +17,7 @@ namespace InterfaceForward.Application.Services.Interface;
/// 系统接口 /// 系统接口
/// </summary> /// </summary>
[ApiExplorerSettings(GroupName = "接口服务")] [ApiExplorerSettings(GroupName = "接口服务")]
public class SystemInterfaceService : BaseService public class SystemInterfaceService : ApplicationService
{ {
#region ctor #region ctor

View File

@ -12,10 +12,10 @@ using InterfaceForward.Domain.Shared.Dtos;
using InterfaceForward.Domain.Shared.Enum; using InterfaceForward.Domain.Shared.Enum;
using InterfaceForward.Repositories; using InterfaceForward.Repositories;
using InterfaceForward.Repositories.Log.Services; using InterfaceForward.Repositories.Log.Services;
using InterfaceForward.Repositories.Log.ValueObjects;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using SJZY.InterfaceRelay.Repository.Log.ValueObjects;
namespace InterfaceForward.Application.Services; namespace InterfaceForward.Application.Services;

File diff suppressed because one or more lines are too long

View File

@ -23,7 +23,7 @@ public class InterfaceMapPublishService(
IInterfaceMapPublishRepository interfaceMapPublishRepository, IInterfaceMapPublishRepository interfaceMapPublishRepository,
InterfaceForwardQuery interfaceForwardQuery, InterfaceForwardQuery interfaceForwardQuery,
IInterfaceMapRepository interfaceMapRepository) IInterfaceMapRepository interfaceMapRepository)
: ApplicationService : Fake.Application.ApplicationService
{ {
private static readonly JsonSerializerSettings Settings = new() private static readonly JsonSerializerSettings Settings = new()
{ {

View File

@ -15,7 +15,7 @@ namespace InterfaceForward.Application.Services.InterfaceMap;
/// 接口映射服务 /// 接口映射服务
/// </summary> /// </summary>
[ApiExplorerSettings(GroupName = "接口映射服务")] [ApiExplorerSettings(GroupName = "接口映射服务")]
public partial class InterfaceMapService : ApplicationService public partial class InterfaceMapService : Fake.Application.ApplicationService
{ {
private readonly IBasicRepository<FixedParameterEntity> _fixedParameterRepository; private readonly IBasicRepository<FixedParameterEntity> _fixedParameterRepository;
private readonly IBasicRepository<InterfaceMapDetailEntity> _interfaceMapDetailRepository; private readonly IBasicRepository<InterfaceMapDetailEntity> _interfaceMapDetailRepository;

View File

@ -13,7 +13,7 @@ namespace InterfaceForward.Application.Services.Parameter;
/// 固定参数配置 /// 固定参数配置
/// </summary> /// </summary>
[ApiExplorerSettings(GroupName = "参数服务")] [ApiExplorerSettings(GroupName = "参数服务")]
public class FixedParameterService : BaseService public class FixedParameterService : ApplicationService
{ {
private readonly IFixedParameterRepository _fixedParameterRepository; private readonly IFixedParameterRepository _fixedParameterRepository;
private readonly IServiceProviderRepository _serviceProviderRepository; private readonly IServiceProviderRepository _serviceProviderRepository;

View File

@ -12,7 +12,7 @@ namespace InterfaceForward.Application.Services.Parameter;
/// 表单参数配置 /// 表单参数配置
/// </summary> /// </summary>
[ApiExplorerSettings(GroupName = "参数服务")] [ApiExplorerSettings(GroupName = "参数服务")]
public class FormParameterService : BaseService public class FormParameterService : ApplicationService
{ {
private readonly IBasicRepository<FormParameterEntity> _formParameterRepository; private readonly IBasicRepository<FormParameterEntity> _formParameterRepository;

View File

@ -18,7 +18,7 @@ namespace InterfaceForward.Application.Services.Parameter;
/// 参数服务 /// 参数服务
/// </summary> /// </summary>
[ApiExplorerSettings(GroupName = "参数服务")] [ApiExplorerSettings(GroupName = "参数服务")]
public class ParameterService : BaseService public class ParameterService : ApplicationService
{ {
private readonly IInterfaceRepository _interfaceRepository; private readonly IInterfaceRepository _interfaceRepository;
private readonly IParameterRepository _parameterRepository; private readonly IParameterRepository _parameterRepository;

View File

@ -14,7 +14,7 @@ namespace InterfaceForward.Application.Services.ServiceProvider;
/// 服务商账户 /// 服务商账户
/// </summary> /// </summary>
[ApiExplorerSettings(GroupName = "服务商服务")] [ApiExplorerSettings(GroupName = "服务商服务")]
public class ServiceProviderAccountService : BaseService public class ServiceProviderAccountService : ApplicationService
{ {
private readonly IServiceProviderAccountFieldRepository _serviceProviderAccountFieldRepository; private readonly IServiceProviderAccountFieldRepository _serviceProviderAccountFieldRepository;
private readonly IServiceProviderAccountFieldValueRepository _serviceProviderAccountFieldValueRepository; private readonly IServiceProviderAccountFieldValueRepository _serviceProviderAccountFieldValueRepository;

View File

@ -15,7 +15,7 @@ namespace InterfaceForward.Application.Services.ServiceProvider;
/// 服务商授权配置 /// 服务商授权配置
/// </summary> /// </summary>
[ApiExplorerSettings(GroupName = "服务商服务")] [ApiExplorerSettings(GroupName = "服务商服务")]
public class ServiceProviderAuthService : BaseService public class ServiceProviderAuthService : ApplicationService
{ {
private readonly IBasicRepository<InterfaceEntity> _interfaceRepository; private readonly IBasicRepository<InterfaceEntity> _interfaceRepository;
private readonly IBasicRepository<ParameterEntity> _parameterRepository; private readonly IBasicRepository<ParameterEntity> _parameterRepository;

View File

@ -21,7 +21,7 @@ namespace InterfaceForward.Application.Services.ServiceProvider;
/// 导入导出服务 /// 导入导出服务
/// </summary> /// </summary>
[ApiExplorerSettings(GroupName = "服务商服务")] [ApiExplorerSettings(GroupName = "服务商服务")]
public class ServiceProviderImportExportService : BaseService public class ServiceProviderImportExportService : ApplicationService
{ {
private readonly ISugarDbContextProvider<InterfaceForwardDbContext> _sqlSugarClientProvider; private readonly ISugarDbContextProvider<InterfaceForwardDbContext> _sqlSugarClientProvider;

View File

@ -15,7 +15,7 @@ namespace InterfaceForward.Application.Services.ServiceProvider;
/// 服务商服务 /// 服务商服务
/// </summary> /// </summary>
[ApiExplorerSettings(GroupName = "服务商服务")] [ApiExplorerSettings(GroupName = "服务商服务")]
public class ServiceProviderService : BaseService public class ServiceProviderService : ApplicationService
{ {
private readonly IBasicRepository<InterfaceEntity> _interfaceRepository; private readonly IBasicRepository<InterfaceEntity> _interfaceRepository;
private readonly IServiceProviderRepository _serviceProviderRepository; private readonly IServiceProviderRepository _serviceProviderRepository;

View File

@ -1,11 +1,19 @@
namespace InterfaceForward.Domain.Shared.Dtos; namespace InterfaceForward.Domain.Shared.Dtos;
public class RequestLogDto(Guid requestId) public class RequestLogDto
{ {
public const string Init = null;
public RequestLogDto(Guid requestId)
{
RequestId = requestId;
Response = Init;
}
/// <summary> /// <summary>
/// 请求Id /// 请求Id
/// </summary> /// </summary>
public Guid RequestId { get; set; } = requestId; public Guid RequestId { get; set; }
///<summary> ///<summary>
/// 请求时间(务必设置) /// 请求时间(务必设置)
@ -20,37 +28,37 @@ public class RequestLogDto(Guid requestId)
///<summary> ///<summary>
/// 应用/服务商名称 /// 应用/服务商名称
///</summary> ///</summary>
public string? Name { get; set; } public string Name { get; set; }
///<summary> ///<summary>
/// 发起请求IP /// 发起请求IP
///</summary> ///</summary>
public string? ClientIp { get; set; } public string ClientIP { get; set; }
///<summary> ///<summary>
/// 接口代码 /// 接口代码
///</summary> ///</summary>
public string? InterfaceCode { get; set; } public string InterfaceCode { get; set; }
///<summary> ///<summary>
/// 请求接口名称 /// 请求接口名称
///</summary> ///</summary>
public string? InterfaceName { get; set; } public string InterfaceName { get; set; }
///<summary> ///<summary>
/// 请求地址 /// 请求地址
///</summary> ///</summary>
public string? Address { get; set; } public string Address { get; set; }
///<summary> ///<summary>
/// 请求头 /// 请求头
///</summary> ///</summary>
public string? Headers { get; set; } public string Headers { get; set; }
///<summary> ///<summary>
/// 请求体 /// 请求体
///</summary> ///</summary>
public string? Content { get; set; } public string Content { get; set; }
///<summary> ///<summary>
/// 响应耗时ms /// 响应耗时ms
@ -60,12 +68,12 @@ public class RequestLogDto(Guid requestId)
///<summary> ///<summary>
/// 响应结果 /// 响应结果
///</summary> ///</summary>
public string? Response { get; set; } public string Response { get; set; }
///<summary> ///<summary>
/// 响应结果映射后 /// 响应结果映射后
///</summary> ///</summary>
public string? Response2 { get; set; } public string Response2 { get; set; }
///<summary> ///<summary>
/// 是否成功调用 /// 是否成功调用
@ -75,5 +83,5 @@ public class RequestLogDto(Guid requestId)
/// <summary> /// <summary>
/// 异常 /// 异常
/// </summary> /// </summary>
public string? Exception { get; set; } public string Exception { get; set; }
} }

View File

@ -2,7 +2,7 @@
public static class RequestServiceProviderFailedCommon public static class RequestServiceProviderFailedCommon
{ {
public const string RequestServiceProviderFailedSubscribeName = "SJZY.InterfaceRelay.RequestServiceProviderFailed"; public const string RequestServiceProviderFailedSubscribeName = "InterfaceForward.RequestServiceProviderFailed";
public static string RequestFailedCode = "4001"; public static string RequestFailedCode = "4001";

View File

@ -1,78 +0,0 @@
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace InterfaceForward.Domain.Shared.FeiShu;
public class FeiShuNoticer : IFeiShuNoticer
{
private readonly FeiShuNoticeOptions _options;
private readonly HttpClient _httpClient;
private readonly ILogger<FeiShuNoticer> _logger;
public FeiShuNoticer(IOptions<FeiShuNoticeOptions> options, HttpClient httpClient, ILogger<FeiShuNoticer> logger)
{
_options = options.Value;
_httpClient = httpClient;
_logger = logger;
}
public async Task NoticeAsync(string content, string subTitle = "")
{
try
{
if (_options.Webhook.IsNullOrWhiteSpace())
{
_logger.LogWarning("未配置飞书webhook");
return;
}
var title = subTitle.IsNullOrWhiteSpace()
? _options.TitlePrefix
: _options.TitlePrefix + "_" + subTitle;
await HandleNoticeAsync(content, title);
}
catch (Exception e)
{
// 这里失败不应该影响业务
_logger.LogWarning($"飞书通知失败,{e.Message}");
}
}
private async Task HandleNoticeAsync(string content, string title)
{
var message = new
{
msg_type = "post",
content = new
{
post = new
{
zh_cn = new
{
title,
content = new List<object>()
{
new List<object>()
{
new
{
tag = "text",
text = content + Environment.NewLine
}
}
}
}
}
}
};
var httpContent = new StringContent(JsonSerializer.Serialize(message), Encoding.UTF8);
httpContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
await _httpClient.PostAsync(_options.Webhook, httpContent);
}
}

View File

@ -1,8 +0,0 @@
using Fake.DependencyInjection;
namespace InterfaceForward.Domain.Shared.FeiShu;
public interface IFeiShuNoticer: ITransientDependency
{
public Task NoticeAsync(string content, string subTitle = "");
}

View File

@ -30,5 +30,5 @@ public static class GlobalConst
/// </summary> /// </summary>
public const string ServiceProviderInterfaceQpsKeyPrefix = "InterfaceQPS"; public const string ServiceProviderInterfaceQpsKeyPrefix = "InterfaceQPS";
public const string InterfaceRelayExchange = "sjzy.interface.relay.direct"; public const string InterfaceRelayExchange = "interface.relay.direct";
} }

View File

@ -0,0 +1,98 @@
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using Fake.SyncEx;
using InterfaceForward.Domain.Shared.Options;
using Serilog;
namespace InterfaceForward.Domain.Shared.Helpers;
public static class LogHelper
{
private static FeiShuNoticeOptions _options = null!;
public static void Init(FeiShuNoticeOptions options)
{
_options = options;
}
public static void Info(string msg, bool isSend = false)
{
Log.Information(msg);
if (!isSend)
return;
SyncContext.Run(() => NoticeAsync(msg, isSend, "Info"));
}
public static void Warn(string msg, bool isSend = false)
{
Log.Warning(msg);
if (!isSend)
return;
SyncContext.Run(() => NoticeAsync(msg, isSend, "Warn"));
}
public static void Error(string msg, bool isSend = true)
{
Log.Error(msg);
SyncContext.Run(() => NoticeAsync(msg, isSend, "Error"));
}
public static async Task NoticeAsync(string content, bool isSend, string subTitle = "")
{
#if DEBUG
// 调试模式下不发送通知
return;
#endif
if (!isSend)
{
return;
}
if (_options.Webhook.IsNullOrWhiteSpace())
{
Log.Warning("未配置飞书webhook");
return;
}
var title = subTitle.IsNullOrWhiteSpace()
? _options.TitlePrefix
: _options.TitlePrefix + "_" + subTitle;
await HandleNoticeAsync(content, title);
}
private static async Task HandleNoticeAsync(string content, string title)
{
var message = new
{
msg_type = "post",
content = new
{
post = new
{
zh_cn = new
{
title,
content = new List<object>()
{
new List<object>()
{
new
{
tag = "text",
text = content + Environment.NewLine
}
}
}
}
}
}
};
using var httpClient = new HttpClient();
httpClient.Timeout = TimeSpan.FromSeconds(_options.Timeout);
using var httpContent = new StringContent(JsonSerializer.Serialize(message), Encoding.UTF8);
httpContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
await httpClient.PostAsync(_options.Webhook, httpContent);
}
}

View File

@ -11,6 +11,6 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Fake.DomainDrivenDesign" Version="8.0.1" /> <PackageReference Include="Fake.DomainDrivenDesign" Version="8.0.0-preview.3" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -1,5 +1,7 @@
using Fake.Modularity; using Fake.Modularity;
using InterfaceForward.Domain.Shared.FeiShu; using InterfaceForward.Domain.Shared.Helpers;
using InterfaceForward.Domain.Shared.Options;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
namespace InterfaceForward.Domain.Shared namespace InterfaceForward.Domain.Shared
@ -8,8 +10,8 @@ namespace InterfaceForward.Domain.Shared
{ {
public override void ConfigureServices(ServiceConfigurationContext context) public override void ConfigureServices(ServiceConfigurationContext context)
{ {
var config = context.Services.GetConfiguration(); var configuration = context.Services.GetConfiguration();
context.Services.Configure<FeiShuNoticeOptions>(config.GetSection("FeiShuNotice")); LogHelper.Init(configuration.GetSection("FeiShuNotice").Get<FeiShuNoticeOptions>()!);
} }
} }
} }

View File

@ -1,4 +1,4 @@
namespace InterfaceForward.Domain.Shared.FeiShu; namespace InterfaceForward.Domain.Shared.Options;
public class FeiShuNoticeOptions public class FeiShuNoticeOptions
{ {
@ -11,4 +11,9 @@ public class FeiShuNoticeOptions
/// 消息标题前缀 /// 消息标题前缀
/// </summary> /// </summary>
public string TitlePrefix { get; set; } public string TitlePrefix { get; set; }
/// <summary>
/// 超时时间 秒
/// </summary>
public int Timeout { get; set; } = 20;
} }

View File

@ -1,9 +0,0 @@
namespace InterfaceForward.Domain.Shared.Options
{
public class NoticeOptions
{
public string Webhook { get; set; }
public string Title { get; set; }
}
}

View File

@ -0,0 +1,44 @@
namespace InterfaceForward.Repositories.App.Entitys;
///<summary>
/// 应用mq订阅配置表
///</summary>
[SugarTable("t_app_subscribe_config")]
public class AppSubscribeConfigEntity : FullAuditedAggregateRoot<int>
{
///<summary>
/// 自增主键
///</summary>
[SugarColumn(ColumnName = "Id", IsPrimaryKey = true, IsIdentity = true)]
public int Id { get; set; }
/// <summary>
/// 应用key
/// </summary>
[SugarColumn(ColumnName = "AppKey")]
public string AppKey { get; set; }
/// <summary>
/// 系统接口code
/// </summary>
[SugarColumn(ColumnName = "SystemInterfaceCode")]
public string SystemInterfaceCode { get; set; }
/// <summary>
/// 服务商code
/// </summary>
[SugarColumn(ColumnName = "ServiceProviderCode")]
public string ServiceProviderCode { get; set; }
/// <summary>
/// mq预取数量服务并发度
/// </summary>
[SugarColumn(ColumnName = "FetchCount")]
public ushort FetchCount { get; set; }
/// <summary>
/// 失败重回队列
/// </summary>
[SugarColumn(ColumnName = "FailedRequeue")]
public bool FailedRequeue { get; set; }
}

View File

@ -7,7 +7,8 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Fake.SqlSugarCore" Version="8.0.0" /> <PackageReference Include="Fake.DomainDrivenDesign" Version="8.0.0-preview.3" />
<PackageReference Include="Fake.SqlSugarCore" Version="8.0.0-preview.3" />
<PackageReference Include="NEST" Version="7.17.5" /> <PackageReference Include="NEST" Version="7.17.5" />
</ItemGroup> </ItemGroup>

View File

@ -29,8 +29,8 @@ public class LogIndex : IdBaseIndex
///<summary> ///<summary>
/// 发起请求IP /// 发起请求IP
///</summary> ///</summary>
public string? ClientIp { get; set; } public string ClientIP { get; set; }
///<summary> ///<summary>
/// 接口代码 /// 接口代码
///</summary> ///</summary>
@ -66,11 +66,16 @@ public class LogIndex : IdBaseIndex
///</summary> ///</summary>
public string Response { get; set; } public string Response { get; set; }
///<summary>
/// 响应结果映射后
///</summary>
public string Response2 { get; set; }
///<summary> ///<summary>
/// 是否成功调用 /// 是否成功调用
///</summary> ///</summary>
public bool IsSuccess { get; set; } public bool IsSuccess { get; set; }
/// <summary> /// <summary>
/// 异常 /// 异常
/// </summary> /// </summary>

View File

@ -1,70 +1,79 @@
using InterfaceForward.Domain.Shared.Dtos; using InterfaceForward.Domain.Shared.Dtos;
using Nest;
using InterfaceForward.Repositories.Log.Entitys; using InterfaceForward.Repositories.Log.Entitys;
using InterfaceForward.Repositories.Log.ValueObjects; using InterfaceForward.Repositories.Log.ValueObjects;
using SJZY.InterfaceRelay.Repository.Log.ValueObjects; using Nest;
namespace InterfaceForward.Repositories.Log.Services; namespace InterfaceForward.Repositories.Log.Services
/// <summary>
/// 日志仓储
/// </summary>
public interface ILogRepository
{ {
/// <summary> /// <summary>
/// 应用id /// 日志仓储
/// </summary> /// </summary>
int AppId { get; set; } public interface ILogRepository
{
/// <summary>
/// 应用id
/// important全局贯彻
/// </summary>
int AppId { get; set; }
/// <summary> /// <summary>
/// 应用日志 /// 应用日志
/// </summary> /// important全局贯彻
public RequestLogDto AppRequestLog { get; set; } /// </summary>
public RequestLogDto AppRequestLog { get; set; }
/// <summary> /// <summary>
/// 最后一条服务商日志 /// 最后一条服务商日志
/// </summary> /// important全局贯彻
public RequestLogDto ServiceProviderRequestLog { get; set; } /// </summary>
public RequestLogDto ServiceProviderRequestLog { get; set; }
/// <summary> /// <summary>
/// 写日志 /// 写日志
/// </summary> /// </summary>
/// <param name="exception">异常</param> /// <param name="exception">异常</param>
/// <returns></returns> /// <returns></returns>
Task WriteAppLogAsync(Exception? exception = null); Task WriteAppLog(Exception? exception = null);
/// <summary> /// <summary>
/// 添加日志 /// 发飞书
/// </summary> /// </summary>
/// <param name="requestLog"></param> /// <param name="contextException"></param>
/// <returns></returns> void SendFeiShu(Exception contextException);
Task AddLogAsync(RequestLogDto requestLog);
/// <summary> /// <summary>
/// 日志列表 /// 添加日志
/// </summary> /// </summary>
/// <param name="input">入参</param> /// <param name="requestLog"></param>
/// <returns></returns> /// <returns></returns>
Task<(List<LogIndex> list, long total)> GetListAsync(LogGetListInputValueObject input); Task AddLogAsync(RequestLogDto requestLog);
/// <summary> /// <summary>
/// 调用统计 /// 日志列表
/// </summary> /// </summary>
/// <param name="input">入参</param> /// <param name="input">入参</param>
/// <returns></returns> /// <returns></returns>
Task<ISearchResponse<LogIndex>> GetLogsStatisticsAsync(LogStatisticsInputValueObject input); Task<(List<LogIndex> list, long total)> GetListAsync(LogGetListInputValueObject input);
/// <summary> /// <summary>
/// 根据ID获取单条 /// 调用统计
/// </summary> /// </summary>
/// <param name="id"></param> /// <param name="input">入参</param>
/// <returns></returns> /// <returns></returns>
Task<LogIndex?> GetDetailsByIdAsync(string id); Task<ISearchResponse<LogIndex>> GetLogsStatisticsAsync(LogStatisticsInputValueObject input);
/// <summary> /// <summary>
/// 根据给定请求id获取请求日志 /// 根据ID获取单条
/// </summary> /// </summary>
/// <param name="requestId"></param> /// <param name="id"></param>
/// <returns></returns> /// <returns></returns>
Task<List<RequestLogSummary>> GetRequestLogsAsync(Guid requestId); Task<LogIndex?> GetDetailsByIdAsync(string id);
/// <summary>
/// 根据给定请求id获取请求日志
/// </summary>
/// <param name="requestId"></param>
/// <returns></returns>
Task<List<RequestLogSummary>> GetRequestLogsAsync(Guid requestId);
}
} }

View File

@ -1,11 +1,11 @@
using Nest; using InterfaceForward.Domain.Shared.Dtos;
using InterfaceForward.Domain.Shared.Dtos;
using InterfaceForward.Domain.Shared.Enum; using InterfaceForward.Domain.Shared.Enum;
using InterfaceForward.Domain.Shared.FeiShu; using InterfaceForward.Domain.Shared.Helpers;
using InterfaceForward.Repositories.Log.Entitys; using InterfaceForward.Repositories.Log.Entitys;
using InterfaceForward.Repositories.Log.ES; using InterfaceForward.Repositories.Log.ES;
using InterfaceForward.Repositories.Log.ValueObjects; using InterfaceForward.Repositories.Log.ValueObjects;
using SJZY.InterfaceRelay.Repository.Log.ValueObjects; using Nest;
using LogHelper = InterfaceForward.Domain.Shared.Helpers.LogHelper;
namespace InterfaceForward.Repositories.Log.Services; namespace InterfaceForward.Repositories.Log.Services;
@ -14,16 +14,11 @@ namespace InterfaceForward.Repositories.Log.Services;
/// </summary> /// </summary>
public class LogRepository : ElasticSearchContextAbstract<LogIndex>, ILogRepository, IScopedDependency public class LogRepository : ElasticSearchContextAbstract<LogIndex>, ILogRepository, IScopedDependency
{ {
private readonly IFeiShuNoticer _feiShuNoticer; public LogRepository(ConnectionFactory connection) : base(connection)
public LogRepository(ConnectionFactory connection, IFeiShuNoticer feiShuNoticer) : base(connection)
{ {
_feiShuNoticer = feiShuNoticer;
var guid = Guid.NewGuid(); var guid = Guid.NewGuid();
AppRequestLog = new RequestLogDto(guid) AppRequestLog = new RequestLogDto(guid);
{ AppRequestLog.IsSuccess = true; // 只有接口通发生未处理异常才算应用失败
IsSuccess = true // 只有接口通发生未处理异常才算应用失败
};
ServiceProviderRequestLog = new RequestLogDto(guid); ServiceProviderRequestLog = new RequestLogDto(guid);
} }
@ -31,7 +26,7 @@ public class LogRepository : ElasticSearchContextAbstract<LogIndex>, ILogReposit
public RequestLogDto AppRequestLog { get; set; } public RequestLogDto AppRequestLog { get; set; }
public RequestLogDto ServiceProviderRequestLog { get; set; } public RequestLogDto ServiceProviderRequestLog { get; set; }
public async Task WriteAppLogAsync(Exception? exception = null) public async Task WriteAppLog(Exception? exception = null)
{ {
// ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
if (exception != null) if (exception != null)
@ -44,7 +39,23 @@ public class LogRepository : ElasticSearchContextAbstract<LogIndex>, ILogReposit
await AddLogAsync(AppRequestLog); await AddLogAsync(AppRequestLog);
} }
public void SendFeiShu(Exception contextException)
{
LogHelper.Error($"""
id{ServiceProviderRequestLog.RequestId}
--{ServiceProviderRequestLog.Name}--{ServiceProviderRequestLog.InterfaceName}
{Truncate(contextException.ToString())}
{ServiceProviderRequestLog.Address}
{Truncate(AppRequestLog.Content)}
{Truncate(ServiceProviderRequestLog.Content)}
{Truncate(ServiceProviderRequestLog.Response)}
""");
}
public async Task AddLogAsync(RequestLogDto requestLog) public async Task AddLogAsync(RequestLogDto requestLog)
{ {
@ -56,7 +67,7 @@ public class LogRepository : ElasticSearchContextAbstract<LogIndex>, ILogReposit
RequestTime = requestLog.RequestTime == default ? DateTime.Now : requestLog.RequestTime, RequestTime = requestLog.RequestTime == default ? DateTime.Now : requestLog.RequestTime,
IsApp = requestLog.IsApp, IsApp = requestLog.IsApp,
Name = requestLog.Name, Name = requestLog.Name,
ClientIp = requestLog.ClientIp, ClientIP = requestLog.ClientIP,
InterfaceCode = requestLog.InterfaceCode, InterfaceCode = requestLog.InterfaceCode,
InterfaceName = requestLog.InterfaceName, InterfaceName = requestLog.InterfaceName,
Address = requestLog.Address, Address = requestLog.Address,
@ -64,67 +75,73 @@ public class LogRepository : ElasticSearchContextAbstract<LogIndex>, ILogReposit
Content = requestLog.Content, Content = requestLog.Content,
Cost = requestLog.Cost, Cost = requestLog.Cost,
Response = requestLog.Response, Response = requestLog.Response,
Response2 = requestLog.Response2,
IsSuccess = requestLog.IsSuccess, IsSuccess = requestLog.IsSuccess,
Exception = requestLog.Exception Exception = requestLog.Exception
}; };
IndexResponse response = await Context.IndexAsync(idx, try
request => request.Index($"sjzy_interface_forward-{DateTime.Now:yyyy.MM.dd}"));
if (!response.IsValid)
{ {
await _feiShuNoticer.NoticeAsync("数据添加失败,原因:" + response.DebugInformation); //await Context.IndexDocumentAsync(idx);
IndexResponse response = await Context.IndexAsync(idx,
request => request.Index($"interface_forward-{DateTime.Now:yyyy.MM.dd}"));
if (!response.IsValid) LogHelper.Error("数据添加失败,原因:" + response.DebugInformation);
}
catch (Exception ex)
{
LogHelper.Error($"日志插入ES失败{ex.Message}。{idx.ToJson()}");
} }
} }
public async Task<(List<LogIndex> list, long total)> GetListAsync(LogGetListInputValueObject input) public async Task<(List<LogIndex> list, long total)> GetListAsync(LogGetListInputValueObject input)
{ {
Func<QueryContainerDescriptor<LogIndex>, QueryContainer> query = input.RequestId.IsNullOrWhiteSpace()
? q =>
(input.Type != LogListInputTypeEnum. ? null : q.Term(p => p.IsApp, true))
&& (input.Type != LogListInputTypeEnum. ? null : q.Term(p => p.IsApp, false))
&& (input.InterfaceName.IsNullOrEmpty()
? null
: q.Wildcard(w => w.InterfaceName.Suffix("keyword"), "*" + input.InterfaceName + "*"))
&& (input.Name.IsNullOrEmpty()
? null
: q.Wildcard(w => w.Name.Suffix("keyword"), "*" + input.Name + "*"))
&& (input.Content.IsNullOrEmpty()
? null
: q.Wildcard(w => w.Content, "*" + input.Content?.ToLower() + "*"))
&& (input.IsSuccess == null
? null
: q.Term(w => w.IsSuccess, input.IsSuccess))
&& (input.Response.IsNullOrEmpty()
? null
: q.Match(m => m.Field(f => f.Response).Query(input.Response)))
&& ((input.RequestStartTime == null || input.RequestEndTime == null)
? null
: q.DateRange(r => r
.Field(f => f.RequestTime)
.GreaterThanOrEquals(input.RequestStartTime)
.LessThanOrEquals(input.RequestEndTime)
.TimeZone("+08:00"))
)
&& (input.CostStart == null
? null
: q.Range(t => t
.Field(f => f.Cost)
.GreaterThanOrEquals(input.CostStart))
)
&& (input.CostEnd == null
? null
: q.Range(t => t
.Field(f => f.Cost)
.LessThanOrEquals(input.CostEnd))
)
: q => q.Wildcard(w => w.RequestId.Suffix("keyword"), "*" + input.RequestId + "*");
var data = await Context.SearchAsync<LogIndex>(x => x var data = await Context.SearchAsync<LogIndex>(x => x
.Source(s => s.Includes(i => i.Fields( .Source(s => s.Includes(i => i.Fields(
f => f.Id, f => f.RequestId, f => f.RequestTime, f => f.IsApp, f => f.Id, f => f.RequestId, f => f.RequestTime, f => f.IsApp,
f => f.Name, f => f.ClientIp, f => f.InterfaceName, f => f.InterfaceCode, f => f.Address, f => f.Name, f => f.ClientIP, f => f.InterfaceName, f => f.InterfaceCode, f => f.Address,
f => f.Cost, f => f.IsSuccess))) f => f.Cost, f => f.IsSuccess)))
.Query(q => .Query(query)
(input.Type != LogListInputTypeEnum. ? null : q.Term(p => p.IsApp, true))
&& (input.Type != LogListInputTypeEnum. ? null : q.Term(p => p.IsApp, false))
&& (input.RequestId.IsNullOrWhiteSpace()
? null
: q.Wildcard(w => w.RequestId.Suffix("keyword"), "*" + input.RequestId + "*"))
&& (input.InterfaceName.IsNullOrEmpty()
? null
: q.Wildcard(w => w.InterfaceName.Suffix("keyword"), "*" + input.InterfaceName + "*"))
&& (input.Name.IsNullOrEmpty()
? null
: q.Wildcard(w => w.Name.Suffix("keyword"), "*" + input.Name + "*"))
&& (input.Content.IsNullOrEmpty()
? null
: q.Wildcard(w => w.Content, "*" + input.Content?.ToLower() + "*"))
&& (input.IsSuccess == null
? null
: q.Term(w => w.IsSuccess, input.IsSuccess))
&& (input.Response.IsNullOrEmpty()
? null
: q.Match(m => m.Field(f => f.Response).Query(input.Response)))
&& ((input.RequestStartTime == null || input.RequestEndTime == null)
? null
: q.DateRange(r => r
.Field(f => f.RequestTime)
.GreaterThanOrEquals(input.RequestStartTime)
.LessThanOrEquals(input.RequestEndTime)
.TimeZone("+08:00"))
)
&& (input.CostStart == null
? null
: q.Range(t => t
.Field(f => f.Cost)
.GreaterThanOrEquals(input.CostStart))
)
&& (input.CostEnd == null
? null
: q.Range(t => t
.Field(f => f.Cost)
.LessThanOrEquals(input.CostEnd))
)
)
.Sort(s => s.Field(f => f.RequestTime, SortOrder.Descending)) .Sort(s => s.Field(f => f.RequestTime, SortOrder.Descending))
.From((input.PageIndex - 1) * input.PageSize) .From((input.PageIndex - 1) * input.PageSize)
.Size(input.PageSize) .Size(input.PageSize)
@ -153,10 +170,9 @@ public class LogRepository : ElasticSearchContextAbstract<LogIndex>, ILogReposit
// 按请求时间搜索 // 按请求时间搜索
&& q.DateRange(r => r && q.DateRange(r => r
.Field(f => f.RequestTime) .Field(f => f.RequestTime)
.GreaterThanOrEquals(input.StartTime.ToString("yyyy-MM-dd"))) .GreaterThanOrEquals(input.StartTime.ToString("yyyy-MM-dd"))
&& q.DateRange(r => r .LessThanOrEquals(input.EndTime.ToString("yyyy-MM-dd"))
.Field(f => f.RequestTime) .TimeZone("+08:00"))
.LessThanOrEquals(input.EndTime.ToString("yyyy-MM-dd")))
) )
// 分组 类似SQL的Group By // 分组 类似SQL的Group By
.Aggregations(a => a .Aggregations(a => a
@ -218,10 +234,10 @@ public class LogRepository : ElasticSearchContextAbstract<LogIndex>, ILogReposit
IsApp = x.IsApp IsApp = x.IsApp
}).ToList(); }).ToList();
} }
private static string Truncate(string str, int maxLen = 500) private static string? Truncate(string? str, int maxLen = 500)
{ {
if (str.IsNullOrWhiteSpace()) return str; if (str.IsNullOrWhiteSpace()) return str;
return str.Length <= maxLen ? str : str.Left(maxLen) + "..."; return str!.Length <= maxLen ? str : str.Left(maxLen) + "...";
} }
} }

View File

@ -1,4 +1,4 @@
namespace SJZY.InterfaceRelay.Repository.Log.ValueObjects; namespace InterfaceForward.Repositories.Log.ValueObjects;
public class RequestLogSummary public class RequestLogSummary
{ {

View File

@ -10,6 +10,7 @@
<ItemGroup> <ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App"/> <FrameworkReference Include="Microsoft.AspNetCore.App"/>
<PackageReference Include="Fake.DomainDrivenDesign" Version="8.0.0-preview.3" />
<PackageReference Include="Microsoft.Extensions.Http.Resilience" Version="8.0.0"/> <PackageReference Include="Microsoft.Extensions.Http.Resilience" Version="8.0.0"/>
<PackageReference Include="Microsoft.Extensions.ServiceDiscovery" Version="8.0.0-preview.1.23557.2"/> <PackageReference Include="Microsoft.Extensions.ServiceDiscovery" Version="8.0.0-preview.1.23557.2"/>