深度优先

这个家伙好懒,除了文章什么都没留下

0%

AspNetCoreRateLimit介绍:

AspNetCoreRateLimit是ASP.NET核心速率限制框架,能够对WebApi,Mvc中控制限流,AspNetCoreRateLimit包包含IpRateLimit中间件和ClientRateLimit中间件,每个中间件都可以为不同的场景设置多个限,该框架的作者是stefanprodan,项目nuget地址是https://github.com/stefanprodan/AspNetCoreRateLimit

对客户端IP限流控制。

首先nuget安装 Install-Package AspNetCoreRateLimit ,在Startup中Code以下代码,添加服务和注入,其中的配置是什么;注释都有了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
//添加appsettings.json
services.AddOptions();
//需要存储速率和ip规则
services.AddMemoryCache();
//加载appsettings.json中的配置项 ,下面三项是加载general,rules
services.Configure<IpRateLimitOptions>(Configuration.GetSection("IpRateLimiting"));
services.Configure<IpRateLimitPolicies>(Configuration.GetSection("IpRateLimitPolicies"));
//注入计时器和规则
services.AddSingleton<IIpPolicyStore, MemoryCacheIpPolicyStore>();
services.AddSingleton<IRateLimitCounterStore, MemoryCacheRateLimitCounterStore>();
//添加框架服务
services.AddMvc();
}

在Configure中配置RateLimit的启动

1
2
3
4
5
6
7
8
9
10
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env,ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
app.UseIpRateLimiting();
if (env.IsDevelopment())
app.UseDeveloperExceptionPage();
app.UseMvc();
}

我们还需要再appsettings.json中写入配置和规则:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
"IpRateLimiting": {
"EnableEndpointRateLimiting": false,
"StackBlockedRequests": false,
"RealIpHeader": "X-Real-IP",
"ClientIdHeader": "X-ClientId",
"HttpStatusCode": 429,
"IpWhitelist": [ "127.0.0.1", "::1/10", "192.168.0.0/24" ],
"EndpointWhitelist": [ "get:/api/license", "*:/api/status" ],
"ClientWhitelist": [ "dev-id-1", "dev-id-2" ],
"GeneralRules": [
{
"Endpoint": "*",
"Period": "1s",
"Limit": 2
},
{
"Endpoint": "*",
"Period": "15m",
"Limit": 100
},
{
"Endpoint": "*",
"Period": "12h",
"Limit": 1000
},
{
"Endpoint": "*",
"Period": "7d",
"Limit": 10000
}
]
}

如果EnableEndpointRateLimiting设置为false,那么这些限制就全局使用,例如,如果设置每秒5次调用的限制,对任何端口的任何http调用都计入这个限制,反之,如果它是true,那么该限制将应用于{端口}{path}中的每个端点。

如果stackblockedrequest设置为false,则不会将拒绝调用添加到节流阀计数器,如果你要拒绝你必须设置为true;

ClientidHeader用于提取白清单的客户端id,如果客户端id在这个里面,就不会应用速率限制。

覆盖特定IP一般规则:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
"IpRateLimitPolicies": {
"IpRules": [
{
"Ip": "84.247.85.224",
"Rules": [
{
"Endpoint": "*",
"Period": "1s",
"Limit": 10
},
{
"Endpoint": "*",
"Period": "15m",
"Limit": 200
}
]
},
{
"Ip": "192.168.3.22/25",
"Rules": [
{
"Endpoint": "*",
"Period": "1s",
"Limit": 5
},
{
"Endpoint": "*",
"Period": "15m",
"Limit": 150
},
{
"Endpoint": "*",
"Period": "12h",
"Limit": 500
}
]
}
]
}

IP字段支持IP v4和v6值,我们还需要去定义速率限制规则

规则由端点,期间和限制组成,示例(将所有的端点的速率限制每秒2次呼叫),那么定义如下:

1
{ "Endpoint": "*", "Period": "1s", "Limit": 2 }

如果在同一端点,例如get/values在一秒中你调用了3次,那么第三次将会被阻止;但是如果说你在同一秒内还调用了Put/values那么不会阻止,因为他们不是在同一端点之中。在期间(Period)中,还有单位 s m h 等.

有的时候我们对拦截有一定的自定义需求的时候,我们可以继承IpRateLimitMiddleware,如以下定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class CustomizationLimitMiddleware : IpRateLimitMiddleware
{
private readonly IpRateLimitOptions _options;
private readonly IIpPolicyStore _ipPolicyStore;

public CustomizationLimitMiddleware(RequestDelegate next, IOptions<IpRateLimitOptions> options, IRateLimitCounterStore counterStore, IIpPolicyStore policyStore, ILogger<IpRateLimitMiddleware> logger, IIpAddressParser ipParser = null) : base(next, options, counterStore, policyStore, logger, ipParser)
{
_options = options.Value;
_ipPolicyStore = policyStore;
}
public override ClientRequestIdentity SetIdentity(HttpContext httpContext)
{
var clientId = "anon";
if (httpContext.Request.Headers.Keys.Contains(_options.ClientIdHeader, StringComparer.CurrentCultureIgnoreCase))
{
clientId = httpContext.Request.Headers[_options.ClientIdHeader].First();
}

return new ClientRequestIdentity
{
Path = httpContext.Request.Path.ToString().ToLowerInvariant(),
HttpVerb = httpContext.Request.Method.ToLowerInvariant(),
ClientId = clientId
};
}
}

行为

当客户端进行HTTP调用时,IpRateLimitMiddleware执行以下操作:

  • 从请求体中获取IP,客户端IP,Http信息,和一些URL,如果需要修改自己的提取逻辑,可以覆盖IpRateLimitMiddleware.SetIdentity。
  • 在白名单中搜索IP,客户端ID和URL,如果有匹配则不执行任何操作
  • 在IP规则中搜索匹配项,所有适用的规则按期间分组,对于每个期间使用最严格的规则
  • 在匹配的一般规则中搜索,如果匹配的一般规则具有IP规则中不存在的定义时间段,则也使用此一般规则
  • 对于每个匹配规则,速率限制计数器递增,如果计数器值大于规则限制,则请求被阻止 如果请求被阻止,则客户端会收到如下文本响应:
1
2
3
Status Code: 429
Retry-After: 58
Content: API calls quota exceeded! maximum admitted 2 per 1m.

如果请求没有得到速率限制,那么匹配规则中定义的最长周期用于组成X-Rate-Limit标头,这些标头将在响应中注入:

1
2
3
X-Rate-Limit-Limit: the rate limit period (eg. 1m, 12h, 1d)
X-Rate-Limit-Remaining: number of request remaining
X-Rate-Limit-Reset: UTC date time (ISO 8601) when the limits resets

默认情况下,组织了客户端的调用我们都会记录到日志中,那么我们可以使用Microsoft.Extensions.Logging.ILogger,这个就略过了。

我们有的时候需要添加ip规则或者更新速率,如一下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class IpRateLimitController : Controller
{
private readonly IpRateLimitOptions _options;
private readonly IIpPolicyStore _ipPolicyStore;

public IpRateLimitController(IOptions<IpRateLimitOptions> optionsAccessor, IIpPolicyStore ipPolicyStore)
{
_options = optionsAccessor.Value;
_ipPolicyStore = ipPolicyStore;
}

[HttpGet]
public IpRateLimitPolicies Get()
{
return _ipPolicyStore.Get(_options.IpPolicyPrefix);
}

[HttpPost]
public void Post()
{
var pol = _ipPolicyStore.Get(_options.IpPolicyPrefix);
          //add
pol.IpRules.Add(new IpRateLimitPolicy
{
Ip = "8.8.4.4",
Rules = new List<RateLimitRule>(new RateLimitRule[] {
new RateLimitRule {
Endpoint = "*:/api/testupdate",
Limit = 100,
Period = "1d" }
})
});
          //update
_ipPolicyStore.Set(_options.IpPolicyPrefix, pol);
}
}

这样呢,你可以将ip限制的规则放到数据库中再推送到缓存中。

描述

给定一整型数列{a1,a2…,an},找出连续非空子串{ax,ax+1,…,ay},使得该子序列的和最大,其中,1<=x<=y<=n。

输入

第一行是一个整数N(N<=10)表示测试数据的组数)
每组测试数据的第一行是一个整数n表示序列中共有n个整数,随后的一行里有n个整数I(-100=<I<=100),表示数列中的所有元素。(0<n<=1000000)

输出

对于每组测试数据输出和最大的连续子串的和。

样例输入

1
2
3
4
1
5
1 2 -1 3 -2

样例输出

1
5

提示

输入数据很多,推荐使用scanf进行输入

题目不难,但是有一定意义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import java.util.Scanner;

public class 子串和 {

public static void main(String[] args) {

Scanner sc=new Scanner(System.in);
while(sc.hasNext()){
int N=sc.nextInt();
int[] data=new int[N];

for (int i = 0; i < N; i++) {

int n=sc.nextInt();

int max=sc.nextInt();

int sum=max;
n--;
while(n-->0)
{
int num=sc.nextInt();

if(sum>0)
sum += num;
else
sum = num;

if(sum>max)
max = sum;
}
data[i]=max;
}

for (int i = 0; i < N; i++) {
System.out.println(data[i]);
}
}
sc.close();
}
}

前言

EntityFramework Core每一次版本的迭代和更新都会带给我们惊喜,每次都会尽量满足大部分使用者的需求。在EF Core 2.0版本中出现了全局过滤新特性即HasQueryFilter,它出现的意义在哪里?能够解决什么问题呢?这是我们需要思考的问题。通过HasQueryFilter方法来创建过滤器能够允许我们对访问特定数据库表的所有查询额外添加一模一样的过滤器。它主要用于软删除(soft-delete)场景,即用户并不想返回那些被标记为已删除但是尚未从数据库中做物理删除的数据行。

HasQueryFilter全局过滤器

在文章开头我们讲述了HasQueryFilter特性所解决的问题,下面我们利用实际例子来讲述一下。在许多场景中我们并不会从物理上删除数据,而只是仅仅改变数据的状态。这个时候就凸显了HasQueryFilter特性的作用。比如我们创建一个博客实体。

1
2
3
4
5
6
7
8
9
10
public class Blog
{
public int Id { get; set; }
public string Name { get; set; }
public string Url { get; set; }
public bool IsDeleted { get; set; }
public DateTime CreatedTime { get; set; }
public DateTime ModifiedTime { get; set; }
public ICollection<Post> Posts { get; set; }
}

是不是因为我们本节讲述全局过滤所以才会加IsDeleted字段呢?显然不是,这个是有其应用场景,当我们管理自己的博客时,可能瞌睡没睡好(当然也有其他原因,不必纠结于此)导致一个不小心删除了某一篇文章即使在博客后台有友好提示【是否删除该博客】,但是你依然手滑删除了,这个时候我们找到博客园运营反应,然后分分钟就还原了我们所删除的博客,就像将文件扔到了回收站再还原一样。接下来我们在OnModelCreating方法或者单独建立的映射类中配置过滤未被删除的博客,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
modelBuilder.Entity<Blog>(e => 
{
e.HasQueryFilter(f => !f.IsDeleted);
});

或者

public class BlogConfiguration : IEntityTypeConfiguration<Blog>
{
public void Configure(EntityTypeBuilder<Blog> builder)
{
builder.HasQueryFilter(f => !f.IsDeleted);
......
}
}

要是我们对于特殊场景需要禁用查询过滤或者说忽略查询过滤又该如何呢?通过对应的APi(IgnoreQueryFilters)即可,如下:

1
2
3
4
5
6
7
8
using (var context = new EFCoreDbContext())
{
var blogs = context.Blogs
.Include(d => d.Posts)
.IgnoreQueryFilters()
.AsNoTracking()
.ToList();
}

使用HasQueryFilter进行查询过滤是不是就是如此简单呢?是不是问题到此就这样结束了呢?看过Jeff博客的童鞋知道,Jeff经常问这样的问题且自问自答,肯定不止于此,我们继续往下探索。上述我们只是应用一个博客实体,我们还存在发表实体且二者存在关联关系,我们同上在Post中定义IsDeleted字段,此时我们在对Blog进行过滤的同时也对Post进行过滤,如下:

1
builder.HasQueryFilter(f => !f.IsDeleted && f.Posts.All(w => !w.IsDeleted));

由上得知使用HasQueryFilter进行过滤筛选的局限性之一。

局限性一:HasQueryFilter方法过滤筛选无法应用于导航属性。

接下来我们再来看一个例子,首先我们定义如下继承关系:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public abstract class Payment
{
public int PaymentId { get; set; }
public decimal Amount { get; set; }
public string Name { get; set; }
public bool IsDeleted { get; set; }
}

public class CashPayment : Payment
{
}

public class CreditcardPayment : Payment
{
public string CreditcardNumber { get; set; }
}

上述我们定义支付基类Payment,然后派生出现金支付CashPayment和信用卡支付CreditcardPayment子类,接下来我们配置如下查询过滤:

1
builder.HasQueryFilter(f => !(f is CreditcardPayment && f.IsDeleted && ((CreditcardPayment)f).IsDeleted));

接下来我们进行如下查询:

1
using (var context = new EFCoreDbContext()) { var payments = context.Payments.ToList(); }

没毛病,接下来我们查询子类CreditcardPayment,如下:

1
using (var context = new EFCoreDbContext()) { var payments = context.Payments.OfType<CashPayment>().ToList(); }

由上得知使用HasQueryFilter进行过滤筛选的局限性之二。

局限性二:HasQueryFilter方法过滤筛选只能定义在基类中,无法对子类进行过滤。

HasQueryFilter通用解决方案

不知我们是否思考过一个问题,若有很多实体都有其软删除场景,那么我们就都需要加上IsDeleted字段,同时还需要配置全局过滤器,如此重复性动作我们是否觉得厌烦,搬砖的我们的单从程序角度来看,搬砖的本源就是为了解放劳动生产率,提高工作效率,说的更通俗一点则是解决重复性劳动。此时为了解决上述所延伸出的问题,我们完全可抽象出一个软删除接口,如下:

1
public interface ISoftDleteBaseEntity { bool IsDeleted { get; set; } }

接下来让所有需要进行软删除的实体继承该接口,比如博客实体,如下:

1
2
3
4
5
6
7
8
public class Blog : ISoftDleteBaseEntity
{
public int Id { get; set; }
public string Name { get; set; }
public string Url { get; set; }
public bool IsDeleted { get; set; }
......
}

为了解决重复性配置劳动,我们让所有继承自上述接口一次性在OnModelCreating方法中配置全局过滤,如此一来则免去了在每个对应实体映射类中进行配置,如下:

1
2
3
4
5
6
Expression<Func<ISoftDleteBaseEntity, bool>> expression = e => !e.IsDeleted;
foreach (var entityType in modelBuilder.Model.GetEntityTypes()
.Where(e => typeof(ISoftDleteBaseEntity).IsAssignableFrom(e.ClrType)))
{
modelBuilder.Entity(entityType.ClrType).HasQueryFilter(expression);
}

愿望是美好的,结果尼玛抛出异常不支持该操作。看到上述lambda表达式都没有翻译过来,此时我们转到定义看看。

1
public virtual EntityTypeBuilder HasQueryFilter([CanBeNullAttribute] LambdaExpression filter);

居然参数类型不是以表达式树的形式给出,如果是如下形式则肯定可以。

1
public virtual EntityTypeBuilder HasQueryFilter([CanBeNullAttribute] Expression<Func<TEntity, TProperty>> filter);

所以问题出在无法翻译lambda表达式,那么我们就来自动构建lambda表达式参数和主体,如下:

1
2
3
4
5
6
7
8
9
10
foreach (var entityType in modelBuilder.Model.GetEntityTypes()
.Where(e => typeof(ISoftDleteBaseEntity).IsAssignableFrom(e.ClrType)))
{
modelBuilder.Entity(entityType.ClrType).Property<Boolean>("IsDeleted");
var parameter = Expression.Parameter(entityType.ClrType, "e");
var body = Expression.Equal(
Expression.Call(typeof(EF), nameof(EF.Property), new[] { typeof(bool) }, parameter, Expression.Constant("IsDeleted")),
Expression.Constant(false));
modelBuilder.Entity(entityType.ClrType).HasQueryFilter(Expression.Lambda(body, parameter));
}

至此美好愿望得意实现,算是给出了一种通用解决方案。

总结

本节我们讲述了EntityFramework Core 2.0中的新特性HasQueryFilter,使用此特性仍然存在局限性无法对导航属性属性进行过滤,对实体为继承次模型,那么过滤筛选只能定义在基类中,但是最后我们给出了通用解决方案解决重复定义查询过滤问题,希望给阅读的您一点帮助。精简的内容,简单的讲解,希望对阅读的您有所帮助,我们明天再会。