利用中间件+特性解决minimal api 项目拦截功能
parent
5023f5ed31
commit
77a11cacc2
|
@ -102,7 +102,7 @@ builder.Services.AddControllers(options =>
|
|||
// Panda动态WebApi + UnifiedApiResultFilter + 省掉控制器代码
|
||||
builder.Services.AddDynamicWebApiSetup();
|
||||
//MinimalAPI
|
||||
builder.Services.AddMasaMinimalAPIs();
|
||||
builder.Services.AddMasaMinimalAPiSetUp();
|
||||
|
||||
//AutoMapper
|
||||
builder.Services.AddAutoMapperSetup();
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using IRaCIS.Core.Application.Service.BusinessFilter;
|
||||
using IRaCIS.Core.Application.BusinessFilter;
|
||||
using IRaCIS.Core.Application.Service.BusinessFilter;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
@ -44,6 +45,7 @@ namespace IRaCIS.Core.API
|
|||
options.RouteHandlerBuilder = t => {
|
||||
t.RequireAuthorization()
|
||||
.AddEndpointFilter<LimitUserRequestAuthorizationEndpointFilter>()
|
||||
.AddEndpointFilter<TrialGlobalLimitEndpointFilter>()
|
||||
//.AddEndpointFilter<ModelValidationEndpointFilter>()
|
||||
.AddEndpointFilter<UnifiedApiResultEndpointFilter>()
|
||||
.WithGroupName("Institution").DisableAntiforgery();
|
||||
|
|
|
@ -0,0 +1,184 @@
|
|||
using IRaCIS.Core.Application.BusinessFilter;
|
||||
using IRaCIS.Core.Application.Helper;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using static IRaCIS.Core.Domain.Share.StaticData;
|
||||
|
||||
namespace IRaCIS.Core.Application.Filter;
|
||||
|
||||
public class TrialGlobalLimitActionFilter(IFusionCache _fusionCache, IUserInfo _userInfo, IRepository<Trial> _trialRepository) : IAsyncActionFilter
|
||||
{
|
||||
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
|
||||
{
|
||||
// 检查当前 endpoint 是否包含 TrialGlobalLimitAttribute 特性
|
||||
var trialLimitAttribute = context.HttpContext.GetEndpoint()?.Metadata.GetMetadata<TrialGlobalLimitAttribute>();
|
||||
|
||||
if (trialLimitAttribute != null)
|
||||
{
|
||||
var optActions = trialLimitAttribute._optActions;
|
||||
|
||||
|
||||
// 这里开始执行原有的逻辑
|
||||
var requestHost = context.HttpContext.Request.Host;
|
||||
|
||||
// 检查请求是否来自 localhost:6100
|
||||
if (requestHost.Host == "localhost" && (requestHost.Port == 6100 || requestHost.Port == 3305))
|
||||
{
|
||||
await next();
|
||||
return;
|
||||
}
|
||||
|
||||
#region 特殊用户类型拦截
|
||||
// 用户类型检查
|
||||
if (_userInfo.UserTypeEnumInt == (int)UserTypeEnum.CRA && _userInfo.RequestUrl.ToLower() != "TrialDocument/userConfirm".ToLower())
|
||||
{
|
||||
//对不起,您的账户没有操作权限
|
||||
context.Result = new JsonResult(ResponseOutput.NotOk(I18n.T("TrialResource_NoAccessPermission")));
|
||||
|
||||
return;
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region 从多种途径取TrialId
|
||||
//TrialId 传递的途径多种,可能在path 可能在body 可能在数组中,也可能在对象中,可能就在url
|
||||
var trialIdStr = string.Empty;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(context.HttpContext.Request.Query["trialId"]))
|
||||
{
|
||||
trialIdStr = context.HttpContext.Request.Query["trialId"];
|
||||
}
|
||||
|
||||
//先尝试从path中取TrialId
|
||||
else if (context.HttpContext.Request.RouteValues.Keys.Any(t => t.Contains("trialId")))
|
||||
{
|
||||
var index = context.HttpContext.Request.RouteValues.Keys.ToList().IndexOf("trialId");
|
||||
trialIdStr = context.HttpContext.Request.RouteValues.Values.ToList()[index] as string;
|
||||
}
|
||||
else if (context.HttpContext.Request.Headers["Referer"].ToString().Contains("trialId"))
|
||||
{
|
||||
var headerStr = context.HttpContext.Request.Headers["Referer"].ToString();
|
||||
|
||||
var trialIdIndex = headerStr.IndexOf("trialId");
|
||||
|
||||
|
||||
var matchResult = Regex.Match(headerStr.Substring(trialIdIndex), @"[0-9a-fA-F]{8}(-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}");
|
||||
|
||||
if (matchResult.Success)
|
||||
{
|
||||
trialIdStr = matchResult.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
//---正则取请求Refer 中trialId 失败,请联系开发人员核查
|
||||
context.Result = new JsonResult(ResponseOutput.NotOk(I18n.T("TrialResource_ReferTrialIdFailed")));
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
#region body 中取数据
|
||||
|
||||
//设置可以多次读
|
||||
context.HttpContext.Request.EnableBuffering();
|
||||
var reader = new StreamReader(context.HttpContext.Request.Body);
|
||||
var contentFromBody = await reader.ReadToEndAsync();
|
||||
//读取后,流的位置还原
|
||||
context.HttpContext.Request.Body.Seek(0, SeekOrigin.Begin);
|
||||
//context.HttpContext.Request.Body.Position = 0;
|
||||
|
||||
//找到参数位置在字符串中的索引
|
||||
var trialIdIndex = contentFromBody.IndexOf("\"TrialId\"", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
if (trialIdIndex > -1)
|
||||
{
|
||||
// (?<="trialId" *: *").*?(?=",)
|
||||
|
||||
//使用正则 [0-9a-fA-F]{8}(-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}
|
||||
|
||||
var matchResult = Regex.Match(contentFromBody.Substring(trialIdIndex), @"[0-9a-fA-F]{8}(-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}");
|
||||
|
||||
if (matchResult.Success)
|
||||
{
|
||||
//有可能匹配错误 "trialId":"","documentId":"b8180000-3e2c-0016-9fe0-08da33f96236" 从缓存里面验证下
|
||||
|
||||
trialIdStr = matchResult.Value;
|
||||
|
||||
var trialStatusStr = await _fusionCache.GetOrSetAsync(CacheKeys.Trial(trialIdStr), _ => CacheHelper.GetTrialStatusAsync(Guid.Parse(trialIdStr), _trialRepository), TimeSpan.FromDays(7));
|
||||
|
||||
if (string.IsNullOrWhiteSpace(trialStatusStr))
|
||||
{
|
||||
|
||||
//数据库 检查该项目Id不对
|
||||
context.Result = new JsonResult(ResponseOutput.NotOk(I18n.T("TrialResource_ReferTrialIdFailed")));
|
||||
return;
|
||||
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//---正则取请求Refer 中trialId 失败,请联系开发人员核查
|
||||
context.Result = new JsonResult(ResponseOutput.NotOk(I18n.T("TrialResource_ReferTrialIdFailed")));
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
//使用字符串取 如果是swagger 可能有时取的不对 因为空格的原因
|
||||
//trialIdStr = contentFromBody.Substring(trialIdIndex + "TrialId".Length + 4, 3
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
//通过path 或者body 找到trialId 了
|
||||
if (!string.IsNullOrWhiteSpace(trialIdStr))
|
||||
{
|
||||
var trialStatusStr = await _fusionCache.GetOrSetAsync(CacheKeys.Trial(trialIdStr), _ => CacheHelper.GetTrialStatusAsync(Guid.Parse(trialIdStr), _trialRepository), TimeSpan.FromDays(7));
|
||||
|
||||
// 这里是统一拦截 项目有关的操作允许情况(特殊的地方,比如项目配置(有的在多种状态(初始化,ongoing)都可以操作,有的仅仅在Initializing)还有 项目添加和更新,不走这里,特殊处理,不然在这里显得很乱,判断是哪个接口)
|
||||
if (trialStatusStr == StaticData.TrialState.TrialOngoing || optActions.Any(t => t == TrialOpt.BeforeOngoingCantOpt))
|
||||
{
|
||||
|
||||
await next();
|
||||
|
||||
}
|
||||
// 项目停止、或者完成 不允许操作
|
||||
else
|
||||
{
|
||||
//---本次请求被配置规则拦截:项目状态处于进行中时,才允许操作,若此处逻辑有误,请联系开发人员修改
|
||||
context.Result = new JsonResult(ResponseOutput.NotOk(I18n.T("TrialResource_InterceptedProjectStatusRule")));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
//添加项目 签名系统文档的时候 不做拦截 但是更新项目 签名项目文档的时候需要拦截
|
||||
else if (optActions.Any(t => t == TrialOpt.AddOrUpdateTrial || t == TrialOpt.SignSystemDocNoTrialId))
|
||||
{
|
||||
await next();
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
//如果项目相关接口没有传递trialId 会来到这里,提醒,以便修改
|
||||
|
||||
//---该接口参数中,没有传递项目编号,请核对。
|
||||
context.Result = new JsonResult(ResponseOutput.NotOk(I18n.T("TrialResource_MissingProjectNumber")));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 如果没有 TrialGlobalLimitAttribute,则直接执行后续逻辑
|
||||
await next();
|
||||
}
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
using Microsoft.AspNetCore.Http;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IRaCIS.Core.Application.BusinessFilter;
|
||||
|
||||
public class TrialGlobalLimitEndpointFilter : IEndpointFilter
|
||||
|
||||
{
|
||||
public ValueTask<object?> InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,190 @@
|
|||
using IRaCIS.Core.Application.Helper;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using static IRaCIS.Core.Domain.Share.StaticData;
|
||||
|
||||
namespace IRaCIS.Core.Application.BusinessFilter;
|
||||
|
||||
public class TrialGlobalLimitAttribute : Attribute
|
||||
{
|
||||
public readonly string[] _optActions;
|
||||
|
||||
public TrialGlobalLimitAttribute(params string[] optActions)
|
||||
{
|
||||
_optActions = optActions;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public class TrialGlobalLimitEndpointFilter(IFusionCache _fusionCache, IUserInfo _userInfo, IRepository<Trial> _trialRepository) : IEndpointFilter
|
||||
|
||||
{
|
||||
public async ValueTask<object?> InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next)
|
||||
{
|
||||
// 检查当前 endpoint 是否包含 TrialGlobalLimitAttribute 特性
|
||||
var trialLimitAttribute = context.HttpContext.GetEndpoint()?.Metadata.GetMetadata<TrialGlobalLimitAttribute>();
|
||||
|
||||
if (trialLimitAttribute != null)
|
||||
{
|
||||
var optActions = trialLimitAttribute._optActions;
|
||||
|
||||
|
||||
// 这里开始执行原有的逻辑
|
||||
var requestHost = context.HttpContext.Request.Host;
|
||||
|
||||
// 检查请求是否来自 localhost:6100
|
||||
if (requestHost.Host == "localhost" && (requestHost.Port == 6100 || requestHost.Port == 3305))
|
||||
{
|
||||
return await next(context);
|
||||
}
|
||||
|
||||
#region 特殊用户类型拦截
|
||||
// 用户类型检查
|
||||
if (_userInfo.UserTypeEnumInt == (int)UserTypeEnum.CRA && _userInfo.RequestUrl.ToLower() != "TrialDocument/userConfirm".ToLower())
|
||||
{
|
||||
//对不起,您的账户没有操作权限
|
||||
return Results.Json(ResponseOutput.NotOk(I18n.T("TrialResource_NoAccessPermission")));
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region 从多种途径取TrialId
|
||||
//TrialId 传递的途径多种,可能在path 可能在body 可能在数组中,也可能在对象中,可能就在url
|
||||
var trialIdStr = string.Empty;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(context.HttpContext.Request.Query["trialId"]))
|
||||
{
|
||||
trialIdStr = context.HttpContext.Request.Query["trialId"];
|
||||
}
|
||||
|
||||
//先尝试从path中取TrialId
|
||||
else if (context.HttpContext.Request.RouteValues.Keys.Any(t => t.Contains("trialId")))
|
||||
{
|
||||
var index = context.HttpContext.Request.RouteValues.Keys.ToList().IndexOf("trialId");
|
||||
trialIdStr = context.HttpContext.Request.RouteValues.Values.ToList()[index] as string;
|
||||
}
|
||||
else if (context.HttpContext.Request.Headers["Referer"].ToString().Contains("trialId"))
|
||||
{
|
||||
var headerStr = context.HttpContext.Request.Headers["Referer"].ToString();
|
||||
|
||||
var trialIdIndex = headerStr.IndexOf("trialId");
|
||||
|
||||
|
||||
var matchResult = Regex.Match(headerStr.Substring(trialIdIndex), @"[0-9a-fA-F]{8}(-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}");
|
||||
|
||||
if (matchResult.Success)
|
||||
{
|
||||
trialIdStr = matchResult.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
//---正则取请求Refer 中trialId 失败,请联系开发人员核查
|
||||
return Results.Json(ResponseOutput.NotOk(I18n.T("TrialResource_ReferTrialIdFailed")));
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
#region body 中取数据
|
||||
|
||||
//设置可以多次读
|
||||
context.HttpContext.Request.EnableBuffering();
|
||||
var reader = new StreamReader(context.HttpContext.Request.Body);
|
||||
var contentFromBody = await reader.ReadToEndAsync();
|
||||
//读取后,流的位置还原
|
||||
context.HttpContext.Request.Body.Seek(0, SeekOrigin.Begin);
|
||||
//context.HttpContext.Request.Body.Position = 0;
|
||||
|
||||
//找到参数位置在字符串中的索引
|
||||
var trialIdIndex = contentFromBody.IndexOf("\"TrialId\"", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
if (trialIdIndex > -1)
|
||||
{
|
||||
// (?<="trialId" *: *").*?(?=",)
|
||||
|
||||
//使用正则 [0-9a-fA-F]{8}(-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}
|
||||
|
||||
var matchResult = Regex.Match(contentFromBody.Substring(trialIdIndex), @"[0-9a-fA-F]{8}(-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}");
|
||||
|
||||
if (matchResult.Success)
|
||||
{
|
||||
//有可能匹配错误 "trialId":"","documentId":"b8180000-3e2c-0016-9fe0-08da33f96236" 从缓存里面验证下
|
||||
|
||||
trialIdStr = matchResult.Value;
|
||||
|
||||
var trialStatusStr = await _fusionCache.GetOrSetAsync(CacheKeys.Trial(trialIdStr), _ => CacheHelper.GetTrialStatusAsync(Guid.Parse(trialIdStr), _trialRepository), TimeSpan.FromDays(7));
|
||||
|
||||
if (string.IsNullOrWhiteSpace(trialStatusStr))
|
||||
{
|
||||
|
||||
//数据库 检查该项目Id不对
|
||||
return Results.Json(ResponseOutput.NotOk(I18n.T("TrialResource_ReferTrialIdFailed")));
|
||||
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//---正则取请求Refer 中trialId 失败,请联系开发人员核查
|
||||
return Results.Json(ResponseOutput.NotOk(I18n.T("TrialResource_ReferTrialIdFailed")));
|
||||
|
||||
}
|
||||
|
||||
//使用字符串取 如果是swagger 可能有时取的不对 因为空格的原因
|
||||
//trialIdStr = contentFromBody.Substring(trialIdIndex + "TrialId".Length + 4, 3
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
//通过path 或者body 找到trialId 了
|
||||
if (!string.IsNullOrWhiteSpace(trialIdStr))
|
||||
{
|
||||
var trialStatusStr = await _fusionCache.GetOrSetAsync(CacheKeys.Trial(trialIdStr), _ => CacheHelper.GetTrialStatusAsync(Guid.Parse(trialIdStr), _trialRepository), TimeSpan.FromDays(7));
|
||||
|
||||
// 这里是统一拦截 项目有关的操作允许情况(特殊的地方,比如项目配置(有的在多种状态(初始化,ongoing)都可以操作,有的仅仅在Initializing)还有 项目添加和更新,不走这里,特殊处理,不然在这里显得很乱,判断是哪个接口)
|
||||
if (trialStatusStr == StaticData.TrialState.TrialOngoing || optActions.Any(t => t == TrialOpt.BeforeOngoingCantOpt))
|
||||
{
|
||||
|
||||
return await next(context);
|
||||
|
||||
}
|
||||
// 项目停止、或者完成 不允许操作
|
||||
else
|
||||
{
|
||||
//---本次请求被配置规则拦截:项目状态处于进行中时,才允许操作,若此处逻辑有误,请联系开发人员修改
|
||||
return Results.Json(ResponseOutput.NotOk(I18n.T("TrialResource_InterceptedProjectStatusRule")));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
//添加项目 签名系统文档的时候 不做拦截 但是更新项目 签名项目文档的时候需要拦截
|
||||
else if (optActions.Any(t => t == TrialOpt.AddOrUpdateTrial || t == TrialOpt.SignSystemDocNoTrialId))
|
||||
{
|
||||
return await next(context);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
//如果项目相关接口没有传递trialId 会来到这里,提醒,以便修改
|
||||
|
||||
//---该接口参数中,没有传递项目编号,请核对。
|
||||
return Results.Json(ResponseOutput.NotOk(I18n.T("TrialResource_MissingProjectNumber")));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 如果没有 TrialGlobalLimitAttribute,则直接执行后续逻辑
|
||||
return await next(context);
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
using Microsoft.AspNetCore.Authorization;
|
||||
using IRaCIS.Core.Application.BusinessFilter;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System;
|
||||
|
@ -24,7 +25,7 @@ namespace IRaCIS.Core.Application.Service.MinimalApiService
|
|||
{
|
||||
|
||||
|
||||
|
||||
[TrialGlobalLimit("AddOrUpdateTrial", "BeforeOngoingCantOpt", "AfterStopCannNotOpt")]
|
||||
public Task<List<string>> GetProjectList1Async()
|
||||
{
|
||||
var list = new List<string>()
|
||||
|
@ -37,6 +38,7 @@ namespace IRaCIS.Core.Application.Service.MinimalApiService
|
|||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
[TrialGlobalLimit( "BeforeOngoingCantOpt")]
|
||||
public IResponseOutput GetTest()
|
||||
{
|
||||
|
||||
|
|
Loading…
Reference in New Issue