feat:
1. 支持系统接口返回参数类型控制 2. 优化异常日志 3. 支持shopee定制化 4. 支持动态路由
This commit is contained in:
parent
fe2b26c76c
commit
5ac381f4f1
@ -5,6 +5,7 @@ using Fake.AspNetCore.Mvc.Filters;
|
||||
using Fake.Autofac;
|
||||
using Fake.Modularity;
|
||||
using InterfaceForward.Application;
|
||||
using InterfaceForward.Application.Filters;
|
||||
using InterfaceForward.Domain.Shared.Helpers;
|
||||
|
||||
namespace InterfaceForward.Api;
|
||||
|
||||
@ -14,7 +14,10 @@
|
||||
],
|
||||
"WriteTo": [
|
||||
{
|
||||
"Name": "Console"
|
||||
"Name": "Console",
|
||||
"Args": {
|
||||
"outputTemplate": "[{Timestamp:HH:mm:ss} {Level:u3}] {SourceContext} - {Message:lj}{NewLine}{Exception}"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@ -8,7 +8,7 @@ namespace InterfaceForward.Application.Contracts.Dtos.Interface;
|
||||
public class ParameterParseInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 参数解析类型(0:json,1:xml)
|
||||
/// 参数解析类型(1:json,2:xml)
|
||||
/// </summary>
|
||||
public ParameterParseTypeEnum ParameterParseType { get; set; }
|
||||
|
||||
|
||||
@ -51,6 +51,11 @@ public class ForwardCoreContext
|
||||
/// </summary>
|
||||
public string? PostfixScript { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 返回参数 映射后参数类型(默认Object)
|
||||
/// </summary>
|
||||
public ParameterType MappedParamType { get; set; }
|
||||
|
||||
public bool AddHeader(string key, string value)
|
||||
{
|
||||
if (FixedFieldList.Any(x => x.FieldName == key && x.FieldPosition == FieldPosition.Header))
|
||||
|
||||
@ -1,14 +1,70 @@
|
||||
using System.Text;
|
||||
using Fake.DependencyInjection;
|
||||
using Fake.ExceptionHandling;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace InterfaceForward.Application.Filters;
|
||||
|
||||
public class ExceptionNotifier : IExceptionNotifier, ITransientDependency
|
||||
{
|
||||
public Task NotifyAsync(ExceptionNotificationContext context)
|
||||
public async Task NotifyAsync(ExceptionNotificationContext context)
|
||||
{
|
||||
LogHelper.Error(context.Exception.ToString());
|
||||
var httpContext = context.ServiceProvider.GetRequiredService<IHttpContextAccessor>().HttpContext;
|
||||
if (httpContext is null)
|
||||
{
|
||||
LogHelper.Error($@"
|
||||
|-报错时间:{DateTime.Now:yyyy-MM-dd HH:mm:ss ffff}
|
||||
|-异常堆栈:{context.Exception} ");
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
var request = httpContext!.Request;
|
||||
|
||||
// 1. 读取请求路径
|
||||
var path = request.Path; // 例如: /api/users
|
||||
var queryString = request.QueryString; // 例如: ?id=123
|
||||
var fullPath = $"{path}{queryString}";
|
||||
|
||||
// 2. 读取请求方法
|
||||
var method = request.Method; // GET, POST, PUT, etc.
|
||||
|
||||
// 3. 读取请求头
|
||||
var headers = request.Headers;
|
||||
|
||||
// 4. 读取 Body (重要:需要启用缓冲)
|
||||
request.EnableBuffering();
|
||||
|
||||
// 如果 Body 已经被其他中间件读取过,需要先重置:
|
||||
request.Body.Position = 0;
|
||||
|
||||
string body;
|
||||
using (var reader = new StreamReader(
|
||||
request.Body,
|
||||
encoding: Encoding.UTF8,
|
||||
detectEncodingFromByteOrderMarks: false,
|
||||
leaveOpen: true))
|
||||
{
|
||||
body = await reader.ReadToEndAsync();
|
||||
request.Body.Position = 0; // 重置流位置,以便后续中间件可以读取
|
||||
}
|
||||
|
||||
if (context.Exception is BusinessException)
|
||||
{
|
||||
LogHelper.Warn($@"
|
||||
|-请求路径:{method} {fullPath}
|
||||
|-报错时间:{DateTime.Now:yyyy-MM-dd HH:mm:ss ffff}
|
||||
|-请求用户:{headers.Authorization}
|
||||
|-请求参数:{body}
|
||||
|-业务异常:{context.Exception}", true);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogHelper.Error($@"
|
||||
|-请求路径:{method} {fullPath}
|
||||
|-报错时间:{DateTime.Now:yyyy-MM-dd HH:mm:ss ffff}
|
||||
|-请求用户:{headers.Authorization}
|
||||
|-请求参数:{body}
|
||||
|-异常堆栈:{context.Exception} ");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,6 @@
|
||||
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;
|
||||
@ -13,8 +12,7 @@ namespace InterfaceForward.Application.Filters;
|
||||
|
||||
public class ForwardExceptionFilter(
|
||||
ILogRepository logRepository,
|
||||
IOptions<InterfaceRelayOptions> options,
|
||||
RabbitClient rabbitClient)
|
||||
IOptions<InterfaceRelayOptions> options)
|
||||
: IAsyncExceptionFilter
|
||||
{
|
||||
private readonly InterfaceRelayOptions _options = options.Value;
|
||||
|
||||
@ -61,7 +61,7 @@ public class DefaultForwardFlow : IForwardFlow, ITransientDependency
|
||||
public virtual async Task<JToken> OutParamRestructure(ForwardCoreContext context, JToken response)
|
||||
{
|
||||
// 映射
|
||||
var mappedRes = JsonHelper.Restructure(response, context.OutParamTreeList);
|
||||
var mappedRes = JsonHelper.Restructure(response, context.OutParamTreeList, context.MappedParamType);
|
||||
// 非结构化
|
||||
if (context.TargetInterface.ResponseContentType is ResponseContentType.csv or ResponseContentType.pdf
|
||||
or ResponseContentType.Base64)
|
||||
|
||||
@ -94,7 +94,7 @@ public class AppsService : ApplicationService
|
||||
var redisKey = nameof(GetKeyAndSecret) + "_" + date;
|
||||
var num = await RedisHelper.Client.IncrByAsync(redisKey, 1);
|
||||
if (num > 999) throw new BusinessException("超过每月设计的数量");
|
||||
var key = "BSI" + date + num.ToString().PadLeft(3, '0');
|
||||
var key = "W" + date + num.ToString().PadLeft(3, '0');
|
||||
|
||||
//创建一个StringBuilder对象存储密码
|
||||
StringBuilder secret = new StringBuilder();
|
||||
|
||||
@ -0,0 +1,43 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace InterfaceForward.Application.Services.Customized;
|
||||
|
||||
[ApiExplorerSettings(GroupName = "接口转发服务")]
|
||||
[TypeFilter(typeof(ShopeeForwardAuthorizeFilter))]
|
||||
[TypeFilter(typeof(ShopeeForwardExceptionFilter))]
|
||||
public class Shopee(InterfaceForwardCommon forwardCommon): ApplicationService
|
||||
{
|
||||
/// <summary>
|
||||
/// 动态路由:上游接口code/服务商code/定制化后缀
|
||||
/// </summary>
|
||||
/// <param name="upStreamCode"></param>
|
||||
/// <param name="serviceProviderCode"></param>
|
||||
/// <param name="data"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="BusinessException"></exception>
|
||||
// "services/ilh_shipment/create"
|
||||
[HttpPost("{upStreamCode}/{serviceProviderCode}/{postfix}")]
|
||||
public async Task<JToken> ForwardWithSubscribeAsync(
|
||||
[FromRoute] [Required] string upStreamCode,
|
||||
[FromRoute] [Required] string serviceProviderCode,
|
||||
[FromBody] [Required] JToken data)
|
||||
{
|
||||
var contextList = await forwardCommon.VerifyBusinessAndBuildForwardContexts(upStreamCode, serviceProviderCode);
|
||||
|
||||
var context = contextList.First();
|
||||
context.OriginalInterfaceInput = contextList.Count == 1
|
||||
? data
|
||||
: new JObject
|
||||
{
|
||||
[upStreamCode] = data
|
||||
};
|
||||
|
||||
var res = contextList.Count == 1
|
||||
? await forwardCommon.InternalForwardAsync(context)
|
||||
: await forwardCommon.InternalForward2Async(contextList);
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,87 @@
|
||||
using System.Text;
|
||||
using Fake.AspNetCore.Http;
|
||||
using InterfaceForward.Repositories;
|
||||
using InterfaceForward.Repositories.Interface.Entitys;
|
||||
using InterfaceForward.Repositories.Log.Services;
|
||||
using Microsoft.AspNetCore.Http.Extensions;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace InterfaceForward.Application.Services.Customized;
|
||||
|
||||
public class ShopeeForwardAuthorizeFilter(
|
||||
InterfaceForwardQuery interfaceForwardQuery,
|
||||
ILogRepository logRepository,
|
||||
IHttpClientInfoProvider httpClientInfoProvider,
|
||||
IBasicRepository<InterfaceEntity> interfaceRepository)
|
||||
: IAsyncActionFilter
|
||||
{
|
||||
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
|
||||
{
|
||||
var start = DateTime.Now;
|
||||
var log = logRepository.AppRequestLog;
|
||||
log.RequestTime = start;
|
||||
|
||||
var httpContext = context.HttpContext;
|
||||
|
||||
log.IsApp = true;
|
||||
|
||||
log.ClientIP = httpClientInfoProvider.ClientIpAddress;
|
||||
log.Address = httpContext.Request.GetDisplayUrl();
|
||||
|
||||
// body
|
||||
//重置RequestBody的Position为0
|
||||
context.HttpContext.Request.Body.Seek(0, SeekOrigin.Begin);
|
||||
using (var reader = new StreamReader(context.HttpContext.Request.Body, Encoding.UTF8))
|
||||
{
|
||||
log.Content = await reader.ReadToEndAsync();
|
||||
}
|
||||
|
||||
var headers = httpContext.Request.Headers;
|
||||
log.Headers = JsonConvert.SerializeObject(headers);
|
||||
|
||||
// string? appKey = headers["appKey"], appSecret = headers["appSecret"];
|
||||
// if (appKey.IsNullOrWhiteSpace() || appSecret.IsNullOrWhiteSpace())
|
||||
// {
|
||||
// throw new BusinessException(message: "应用授权失败,AppKey和AppSecret是必填的");
|
||||
// }
|
||||
//
|
||||
// var app = await interfaceForwardQuery.FindAppAsync(appKey!, appSecret!);
|
||||
// if (app == null)
|
||||
// throw new BusinessException(message: "应用授权失败,请检查AppKey或AppSecret是否正确");
|
||||
|
||||
// 先写死的
|
||||
var app = await interfaceForwardQuery.FindAppAsync("W2507080", "G1rA80mAfGovH7j53t5bIH425M4vKreT");
|
||||
log.Name = app.Name;
|
||||
if (!app.IsOnline)
|
||||
throw new BusinessException(message: "应用已被禁用,请检查应用状态");
|
||||
|
||||
logRepository.AppId = app.Id;
|
||||
|
||||
var upStreamCode = httpContext.Request.Path.ToUriComponent().Split('/')[1];
|
||||
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();
|
||||
|
||||
if (actionContext.Exception == null)
|
||||
{
|
||||
log.Cost = (int)(DateTime.Now - start).TotalMilliseconds;
|
||||
|
||||
if (actionContext.Result is ObjectResult objectResult)
|
||||
{
|
||||
log.Response = JsonConvert.SerializeObject(objectResult.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
log.Response = actionContext.Result?.ToString()?? string.Empty;
|
||||
}
|
||||
|
||||
_ = logRepository.WriteAppLog();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,79 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using InterfaceForward.Application.Contracts;
|
||||
using InterfaceForward.Application.Contracts.ForwardCore;
|
||||
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.Services.Customized;
|
||||
|
||||
public class ShopeeForwardExceptionFilter(
|
||||
ILogRepository logRepository,
|
||||
IOptions<InterfaceRelayOptions> options)
|
||||
: 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();
|
||||
}
|
||||
}
|
||||
@ -107,7 +107,8 @@ public class InterfaceForwardCommon(
|
||||
FormFieldList = item.FormFieldList,
|
||||
InParamTreeList = item.InParamTreeList,
|
||||
PrefixScript = item.PrefixScript,
|
||||
PostfixScript = item.PostfixScript
|
||||
PostfixScript = item.PostfixScript,
|
||||
MappedParamType = cacheContext.OriginalInterface.MappedParamType
|
||||
};
|
||||
var token = context.FixedFieldList.FirstOrDefault(x => x.FieldValue == GlobalConst.Token);
|
||||
if (token != default)
|
||||
|
||||
@ -20,7 +20,6 @@ namespace InterfaceForward.Application.Services;
|
||||
[TypeFilter(typeof(ForwardAuthorizeFilter))]
|
||||
[TypeFilter(typeof(ForwardExceptionFilter))]
|
||||
[AllowAnonymous]
|
||||
[Route("InterfaceForward")]
|
||||
public class InterfaceForwardService : ApplicationService, IInterfaceForwardService
|
||||
{
|
||||
private readonly InterfaceForwardCommon _forwardCommon;
|
||||
@ -38,7 +37,7 @@ public class InterfaceForwardService : ApplicationService, IInterfaceForwardServ
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
}
|
||||
|
||||
[HttpPost("BatchForwardThenPushToQueue")]
|
||||
[HttpPost("InterfaceForward/BatchForwardThenPushToQueue")]
|
||||
public async Task<InterfaceRelayUnifyResultDto> BatchForwardThenPushToQueueAsync(
|
||||
[FromQuery] [Required] string upStreamCode,
|
||||
[FromQuery] [Required] string serviceProviderCode,
|
||||
@ -111,7 +110,7 @@ public class InterfaceForwardService : ApplicationService, IInterfaceForwardServ
|
||||
};
|
||||
}
|
||||
|
||||
[HttpPost("ForwardWithSubscribe")]
|
||||
[HttpPost("InterfaceForward/ForwardWithSubscribe")]
|
||||
public async Task<InterfaceRelayUnifyResultDto> ForwardWithSubscribeAsync(
|
||||
[FromQuery] [Required] string upStreamCode,
|
||||
[FromQuery] [Required] string serviceProviderCode,
|
||||
@ -147,7 +146,7 @@ public class InterfaceForwardService : ApplicationService, IInterfaceForwardServ
|
||||
};
|
||||
}
|
||||
|
||||
[HttpPost("ReForward")]
|
||||
[HttpPost("InterfaceForward/ReForward")]
|
||||
public async Task<InterfaceRelayUnifyResultDto> ReForwardAsync(
|
||||
[FromQuery] [Required] string upStreamCode,
|
||||
[FromQuery] [Required] string serviceProviderCode,
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Fake.Auditing;
|
||||
using Fake.UnitOfWork;
|
||||
using InterfaceForward.Application.Contracts.Dtos.ServiceProvider;
|
||||
using InterfaceForward.Application.Helpers;
|
||||
@ -14,7 +15,7 @@ namespace InterfaceForward.Application.Services.Parameter;
|
||||
/// 固定参数配置
|
||||
/// </summary>
|
||||
[ApiExplorerSettings(GroupName = "参数服务")]
|
||||
public class FixedParameterService : ApplicationService
|
||||
public class FixedParameterService : ApplicationService, IAuditingEnabled
|
||||
{
|
||||
private readonly IFixedParameterRepository _fixedParameterRepository;
|
||||
private readonly IServiceProviderRepository _serviceProviderRepository;
|
||||
@ -65,7 +66,7 @@ public class FixedParameterService : ApplicationService
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("FixedParameter/MaintainParameter")]
|
||||
public async Task<bool> MaintainParameterAsync([FromBody] FixedParameterInput input)
|
||||
public virtual async Task<bool> MaintainParameterAsync([FromBody] FixedParameterInput input)
|
||||
{
|
||||
// 校验服务商是否存在
|
||||
if (!await _serviceProviderRepository.IsAnyAsync(x => x.Id == input.ServiceProviderId))
|
||||
|
||||
Loading…
Reference in New Issue
Block a user