From 7303862e53820b3ded72daa4e6d69134cbfc1fe0 Mon Sep 17 00:00:00 2001 From: token <239573049@qq.com> Date: Thu, 11 Apr 2024 01:36:50 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E9=99=90=E6=B5=81?= =?UTF-8?q?=E8=AE=BE=E8=AE=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/FastGateway.Core/RateLimitType.cs | 24 +++++++++++ src/FastGateway/Domain/RateLimit.cs | 62 +++++++++++++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 src/FastGateway.Core/RateLimitType.cs create mode 100644 src/FastGateway/Domain/RateLimit.cs diff --git a/src/FastGateway.Core/RateLimitType.cs b/src/FastGateway.Core/RateLimitType.cs new file mode 100644 index 0000000..7a15e2a --- /dev/null +++ b/src/FastGateway.Core/RateLimitType.cs @@ -0,0 +1,24 @@ +namespace FastGateway.Core; + +public enum RateLimitType : byte +{ + /// + /// 并发策略 + /// + Concurrent = 1, + + /// + /// 令牌桶 + /// + TokenBucket = 2, + + /// + /// 滑动窗口 + /// + SlidingWindow = 3, + + /// + /// 固定窗口 + /// + FixedWindow = 4 +} \ No newline at end of file diff --git a/src/FastGateway/Domain/RateLimit.cs b/src/FastGateway/Domain/RateLimit.cs new file mode 100644 index 0000000..9cd438e --- /dev/null +++ b/src/FastGateway/Domain/RateLimit.cs @@ -0,0 +1,62 @@ +using System.Threading.RateLimiting; + +namespace FastGateway.Domain; + +public sealed class RateLimit +{ + public RateLimitType Type { get; set; } + + #region 固定窗口限制器 + + public int? PermitLimit { get; set; } + + /// + /// 限流时间窗口 + /// + public TimeSpan? Window { get; set; } + + /// + /// QueueProcessingOrder + /// + public QueueProcessingOrder? QueueProcessingOrder { get; set; } + + /// + /// QueueLimit + /// + public int? QueueLimit { get; set; } + + #endregion + + #region 滑动窗口限制器 + + /// + /// SegmentsPerWindow + /// + public int? SegmentsPerWindow { get; set; } + + #endregion + + #region 令牌桶限制器 + + /// + /// TokenLimit + /// + public int? TokenLimit { get; set; } + + /// + /// TokensPerPeriod + /// + public int? TokensPerPeriod { get; set; } + + /// + /// AutoReplenishment + /// + public bool? AutoReplenishment { get; set; } + + #endregion + + /// + /// 拒绝响应状态码 + /// + public int RejectionStatusCode { get; set; } +} \ No newline at end of file From e0d2ae510060165eacb852db160085e1f1973cca Mon Sep 17 00:00:00 2001 From: token <239573049@qq.com> Date: Thu, 11 Apr 2024 18:56:29 +0800 Subject: [PATCH 2/3] =?UTF-8?q?-=20=E5=AE=9E=E7=8E=B0=E6=9C=8D=E5=8A=A1?= =?UTF-8?q?=E9=99=90=E6=B5=81=E5=8A=9F=E8=83=BD=20-=20=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E8=87=AA=E5=AE=9A=E4=B9=89=E9=99=90=E6=B5=81=20-=20=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E9=99=90=E6=B5=81=E8=87=AA=E5=AE=9A=E4=B9=89=E6=A8=A1?= =?UTF-8?q?=E6=9D=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../StatisticsBackgroundService.cs | 2 +- src/FastGateway/Domain/Location.cs | 3 +- src/FastGateway/Domain/RateLimit.cs | 85 ++++-- src/FastGateway/Domain/Service.cs | 8 +- src/FastGateway/Domain/StatisticIp.cs | 3 + .../Domain/StatisticRequestCount.cs | 3 + src/FastGateway/Dto/ServiceInput.cs | 2 + src/FastGateway/FastGateway.csproj | 1 + src/FastGateway/Program.cs | 36 ++- src/FastGateway/Services/ApiServiceService.cs | 82 ++++- src/FastGateway/Services/RateLimitService.cs | 62 ++++ web/src/App.tsx | 5 + web/src/layouts/AdminLayout.tsx | 12 +- web/src/module.ts | 9 + .../http-proxy/features/CreateHttpProxy.tsx | 49 +++ .../http-proxy/features/UpdateHttpProxy.tsx | 33 ++ web/src/pages/protection/index.tsx | 1 - .../rate-limit/features/CreateRateLimit.tsx | 281 +++++++++++++++++ .../rate-limit/features/UpdateRateLimit.tsx | 286 ++++++++++++++++++ web/src/pages/rate-limit/index.tsx | 139 +++++++++ web/src/services/RateLimitService.ts | 22 ++ 21 files changed, 1084 insertions(+), 40 deletions(-) create mode 100644 src/FastGateway/Services/RateLimitService.cs create mode 100644 web/src/pages/rate-limit/features/CreateRateLimit.tsx create mode 100644 web/src/pages/rate-limit/features/UpdateRateLimit.tsx create mode 100644 web/src/pages/rate-limit/index.tsx create mode 100644 web/src/services/RateLimitService.ts diff --git a/src/FastGateway/BackgroundServices/StatisticsBackgroundService.cs b/src/FastGateway/BackgroundServices/StatisticsBackgroundService.cs index 5dd70db..9f859d5 100644 --- a/src/FastGateway/BackgroundServices/StatisticsBackgroundService.cs +++ b/src/FastGateway/BackgroundServices/StatisticsBackgroundService.cs @@ -11,7 +11,7 @@ public sealed class StatisticsBackgroundService(IServiceProvider serviceProvider Channel.CreateUnbounded(); private static readonly ConcurrentDictionary RequestCountDic = new(-1, 5); - private static readonly ConcurrentDictionary IpDic = new(); + private static readonly ConcurrentDictionary IpDic = new(-1,5); private static readonly Channel StatisticIpChannel = Channel.CreateUnbounded(); diff --git a/src/FastGateway/Domain/Location.cs b/src/FastGateway/Domain/Location.cs index 70c48bd..7bdc791 100644 --- a/src/FastGateway/Domain/Location.cs +++ b/src/FastGateway/Domain/Location.cs @@ -4,6 +4,7 @@ /// 服务节点管理 /// [Table(Name = "location")] +[Index("location_serviceid", "ServiceId")] public class Location { public string Id { get; set; } @@ -35,7 +36,7 @@ public sealed class LocationService /// [Column(MapType = typeof(string), StringLength = -1)] public Dictionary AddHeader { get; set; } = new(); - + /// /// 静态文件或目录 /// diff --git a/src/FastGateway/Domain/RateLimit.cs b/src/FastGateway/Domain/RateLimit.cs index 9cd438e..888d2ee 100644 --- a/src/FastGateway/Domain/RateLimit.cs +++ b/src/FastGateway/Domain/RateLimit.cs @@ -1,62 +1,91 @@ -using System.Threading.RateLimiting; - -namespace FastGateway.Domain; +namespace FastGateway.Domain; public sealed class RateLimit { - public RateLimitType Type { get; set; } - - #region 固定窗口限制器 + /// + /// 限流策略名称 + /// + [Column(IsIdentity = true)] + public string Name { get; set; } - public int? PermitLimit { get; set; } + /// + /// 是否启用 + /// + public bool Enable { get; set; } /// - /// 限流时间窗口 + /// 通用规则列表 /// - public TimeSpan? Window { get; set; } + [Column(MapType = typeof(string), StringLength = -1)] + public List GeneralRules { get; set; } /// - /// QueueProcessingOrder + /// 端点白名单 /// - public QueueProcessingOrder? QueueProcessingOrder { get; set; } + [Column(MapType = typeof(string), StringLength = -1)] + public List EndpointWhitelist { get; set; } /// - /// QueueLimit + /// 客户端ID头部 /// - public int? QueueLimit { get; set; } + public string ClientIdHeader { get; set; } = "X-ClientId"; - #endregion + /// + /// 客户端白名单 + /// + [Column(MapType = typeof(string), StringLength = -1)] + public List ClientWhitelist { get; set; } - #region 滑动窗口限制器 + /// + /// 真实IP头部 + /// + public string RealIpHeader { get; set; } = "X-Real-IP"; /// - /// SegmentsPerWindow + /// IP白名单 /// - public int? SegmentsPerWindow { get; set; } + [Column(MapType = typeof(string), StringLength = -1)] + public List IpWhitelist { get; set; } - #endregion + /// + /// HTTP状态码 + /// + public int HttpStatusCode { get; set; } = 429; - #region 令牌桶限制器 + /// + /// 超出配额消息 + /// + public string QuotaExceededMessage { get; set; } /// - /// TokenLimit + /// 错误内容类型 /// - public int? TokenLimit { get; set; } + public string RateLimitContentType { get; set; } = "text/html"; /// - /// TokensPerPeriod + /// 速率限制计数器前缀 /// - public int? TokensPerPeriod { get; set; } + public string RateLimitCounterPrefix { get; set; } = "crlc"; /// - /// AutoReplenishment + /// 启用端点速率限制 /// - public bool? AutoReplenishment { get; set; } + public bool EnableEndpointRateLimiting { get; set; } - #endregion + /// + /// 禁用速率限制头部 + /// + public bool DisableRateLimitHeaders { get; set; } /// - /// 拒绝响应状态码 + /// 启用正则规则匹配 /// - public int RejectionStatusCode { get; set; } + public bool EnableRegexRuleMatching { get; set; } +} + +public class GeneralRules +{ + public string Endpoint { get; set; } + public string Period { get; set; } + public int Limit { get; set; } } \ No newline at end of file diff --git a/src/FastGateway/Domain/Service.cs b/src/FastGateway/Domain/Service.cs index 035fac3..cd88a66 100644 --- a/src/FastGateway/Domain/Service.cs +++ b/src/FastGateway/Domain/Service.cs @@ -1,4 +1,5 @@ -using FreeSql.DataAnnotations; +using System.ComponentModel.DataAnnotations; +using FreeSql.DataAnnotations; namespace FastGateway.Domain; @@ -56,4 +57,9 @@ public class Service /// [Navigate(nameof(Location.ServiceId))] public virtual List Locations { get; set; } + + public string? RateLimitName { get; set; } + + [Column(IsIgnore = true)] + public virtual RateLimit? RateLimit { get; set; } } \ No newline at end of file diff --git a/src/FastGateway/Domain/StatisticIp.cs b/src/FastGateway/Domain/StatisticIp.cs index 5c82a14..47da0ca 100644 --- a/src/FastGateway/Domain/StatisticIp.cs +++ b/src/FastGateway/Domain/StatisticIp.cs @@ -1,6 +1,9 @@ namespace FastGateway.Domain; [Table(Name = "statistic_ip")] +[Index("statistic_ip_year", "Year")] +[Index("statistic_ip_month", "Month")] +[Index("statistic_ip_day", "Day")] public sealed class StatisticIp { [Column(IsIdentity = true)] diff --git a/src/FastGateway/Domain/StatisticRequestCount.cs b/src/FastGateway/Domain/StatisticRequestCount.cs index 99351d0..b864ecc 100644 --- a/src/FastGateway/Domain/StatisticRequestCount.cs +++ b/src/FastGateway/Domain/StatisticRequestCount.cs @@ -1,6 +1,9 @@ namespace FastGateway.Domain; [Table(Name = "statistic_request_count")] +[Index("statistic_request_count_year", "Year")] +[Index("statistic_request_count_month", "Month")] +[Index("statistic_request_count_day", "Day")] public sealed class StatisticRequestCount { [Column(IsIdentity = true)] diff --git a/src/FastGateway/Dto/ServiceInput.cs b/src/FastGateway/Dto/ServiceInput.cs index 7318dd9..0eeaa62 100644 --- a/src/FastGateway/Dto/ServiceInput.cs +++ b/src/FastGateway/Dto/ServiceInput.cs @@ -40,6 +40,8 @@ public class ServiceInput /// public bool EnableTunnel { get; set; } + public string RateLimitName { get; set; } + /// /// 服务配置 /// diff --git a/src/FastGateway/FastGateway.csproj b/src/FastGateway/FastGateway.csproj index b395fdd..9896753 100644 --- a/src/FastGateway/FastGateway.csproj +++ b/src/FastGateway/FastGateway.csproj @@ -19,6 +19,7 @@ + diff --git a/src/FastGateway/Program.cs b/src/FastGateway/Program.cs index e1b5f9e..8a4908c 100644 --- a/src/FastGateway/Program.cs +++ b/src/FastGateway/Program.cs @@ -48,12 +48,12 @@ } var freeSql = new FreeSqlBuilder() - .UseConnectionString(DataType.Sqlite, @default) + .UseConnectionString(DataType.Sqlite, @default) #if DEBUG - .UseMonitorCommand(cmd => Console.WriteLine($"Sql:{cmd.CommandText}")) + .UseMonitorCommand(cmd => Console.WriteLine($"Sql:{cmd.CommandText}")) #endif - .UseAutoSyncStructure(true) - .Build(); + .UseAutoSyncStructure(true) + .Build(); return freeSql; }); @@ -92,6 +92,9 @@ Utils.TypeHandlers.TryAdd(typeof(List), new LocationServiceHandler()); +Utils.TypeHandlers.TryAdd(typeof(List) + , new StringJsonHandler>()); + #endregion freeSql.CodeFirst.SyncStructure(); @@ -100,6 +103,7 @@ freeSql.CodeFirst.SyncStructure(); freeSql.CodeFirst.SyncStructure(); freeSql.CodeFirst.SyncStructure(); +freeSql.CodeFirst.SyncStructure(); await ProtectionService.LoadBlacklistAndWhitelistAsync(freeSql); @@ -264,6 +268,30 @@ #endregion + +#region RateLimit + +var rateLimitService = app.MapGroup("/api/v1/RateLimit") + .RequireAuthorization() + .AddEndpointFilter(); + +rateLimitService.MapPost(string.Empty, + RateLimitService.CreateAsync); + +rateLimitService.MapPut("{name}", + RateLimitService.UpdateAsync); + +rateLimitService.MapDelete("{name}", + RateLimitService.DeleteAsync); + +rateLimitService.MapGet("/List", + RateLimitService.GetListAsync); + +rateLimitService.MapGet("/Names", + RateLimitService.GetNamesAsync); + +#endregion + FastContext.SetQpsService(app.Services.GetRequiredService(), app.Services.GetRequiredService()); diff --git a/src/FastGateway/Services/ApiServiceService.cs b/src/FastGateway/Services/ApiServiceService.cs index 039f7bb..bd25932 100644 --- a/src/FastGateway/Services/ApiServiceService.cs +++ b/src/FastGateway/Services/ApiServiceService.cs @@ -1,7 +1,10 @@ using System.Net; using System.Security.Cryptography.X509Certificates; +using System.Threading.RateLimiting; +using AspNetCoreRateLimit; using FastGateway.Infrastructures; using FastGateway.Middlewares; +using Microsoft.AspNetCore.RateLimiting; using Microsoft.AspNetCore.Server.Kestrel.Core; using Yarp.ReverseProxy.Configuration; using Yarp.ReverseProxy.Transforms; @@ -26,8 +29,15 @@ public static async Task LoadServices(IFreeSql freeSql) .IncludeMany(x => x.Locations) .ToListAsync(); + var rateLimitIds = services.Select(x => x.RateLimitName).Distinct().ToArray(); + + var rateLimits = await freeSql.Select() + .Where(x => rateLimitIds.Contains(x.Name)) + .ToListAsync(); + foreach (var service in services) { + service.RateLimit = rateLimits.FirstOrDefault(x => x.Name == service.RateLimitName); await Task.Factory.StartNew(BuilderService, service); } } @@ -55,6 +65,7 @@ public static async Task CreateAsync(ServiceInput input, IFreeSql freeSql) EnableFlowMonitoring = input.EnableFlowMonitoring, EnableTunnel = input.EnableTunnel, EnableWhitelist = input.EnableWhitelist, + RateLimitName = input.RateLimitName, EnableBlacklist = input.EnableBlacklist, IsHttps = input.IsHttps, Listen = input.Listen, @@ -84,8 +95,6 @@ public static async Task CreateAsync(ServiceInput input, IFreeSql freeSql) await freeSql.Insert(service).ExecuteAffrowsAsync(); await freeSql.Insert(service.Locations).ExecuteAffrowsAsync(); - - await Task.Factory.StartNew(BuilderService, service); } [Authorize] @@ -99,6 +108,7 @@ await freeSql.Update() .Set(x => x.EnableWhitelist, input.EnableWhitelist) .Set(x => x.Enable, input.Enable) .Set(x => x.EnableTunnel, input.EnableTunnel) + .Set(x => x.RateLimitName, input.RateLimitName) .Set(x => x.EnableFlowMonitoring, input.EnableFlowMonitoring) .ExecuteAffrowsAsync(); @@ -178,10 +188,19 @@ public static ResultDto CheckDirectoryExistenceAsync(string path) [Authorize] public static async Task GetAsync(string id, IFreeSql freeSql) { - return await freeSql.Select() + var service = await freeSql.Select() .IncludeMany(x => x.Locations) .Where(x => x.Id == id) .FirstAsync(); + + if (service == null) + return default; + + service.RateLimit = await freeSql.Select() + .Where(x => x.Name == service.RateLimitName) + .FirstAsync(); + + return service; } [Authorize] @@ -333,6 +352,41 @@ private static async Task BuilderService(object state) ServiceId = service.Id, }).AddSingleton(); + if (service.RateLimit != null && service.RateLimit.Enable) + { + builder.Services.AddMemoryCache(); + builder.Services.Configure + (options => + { + options.GeneralRules = service.RateLimit.GeneralRules.Select(x => new RateLimitRule() + { + Endpoint = x.Endpoint, + Period = x.Period, + Limit = x.Limit, + }).ToList(); + + options.ClientWhitelist = service.RateLimit.ClientWhitelist; + options.ClientIdHeader = service.RateLimit.ClientIdHeader; + options.DisableRateLimitHeaders = service.RateLimit.DisableRateLimitHeaders; + options.EnableEndpointRateLimiting = service.RateLimit.EnableEndpointRateLimiting; + options.EnableRegexRuleMatching = service.RateLimit.EnableRegexRuleMatching; + options.EndpointWhitelist = service.RateLimit.EndpointWhitelist; + options.IpWhitelist = service.RateLimit.IpWhitelist; + options.RealIpHeader = service.RateLimit.RealIpHeader; + options.RateLimitCounterPrefix = service.RateLimit.RateLimitCounterPrefix; + options.RequestBlockedBehaviorAsync = async (context, _, _, _) => + { + context.Response.StatusCode = service.RateLimit.HttpStatusCode; + context.Response.ContentType = service.RateLimit.RateLimitContentType; + await context.Response.WriteAsync(service.RateLimit.QuotaExceededMessage); + }; + }); + builder.Services.AddSingleton(); + builder.Services.AddInMemoryRateLimiting(); + } + + builder.Services.AddReverseProxy() .LoadFromMemory(routes, clusters) // 删除所有代理的前缀 @@ -347,6 +401,28 @@ private static async Task BuilderService(object state) var app = builder.Build(); + if (service.RateLimit is { Enable: true }) + { + app.Use((async (context, next) => + { + // 获取ip + var ip = context.Connection.RemoteIpAddress?.MapToIPv4()?.ToString(); + + // 如果请求头中包含X-Forwarded-For则使用X-Forwarded-For + if (context.Request.Headers.TryGetValue("X-Forwarded-For", out var forwardedFor)) + { + ip = forwardedFor; + } + + // 设置到 X-Real-IP + context.Request.Headers["X-Real-IP"] = ip; + + await next(context); + })); + + app.UseIpRateLimiting(); + } + WebApplications.Add(service.Id, app); // 如果启用白名单则添加中间件 diff --git a/src/FastGateway/Services/RateLimitService.cs b/src/FastGateway/Services/RateLimitService.cs new file mode 100644 index 0000000..5e00a77 --- /dev/null +++ b/src/FastGateway/Services/RateLimitService.cs @@ -0,0 +1,62 @@ +namespace FastGateway.Services; + +public class RateLimitService +{ + public static async Task CreateAsync(IFreeSql freeSql, RateLimit rateLimit) + { + if (await freeSql.Select().AnyAsync(x => x.Name == rateLimit.Name)) + { + return ResultDto.ErrorResult("限流策略名称已存在"); + } + + await freeSql.Insert(rateLimit).ExecuteAffrowsAsync(); + + return ResultDto.SuccessResult(); + } + + public static async Task UpdateAsync(IFreeSql freeSql, string name, RateLimit rateLimit) + { + await freeSql.Update() + .Where(x => x.Name == name) + .Set(x => x.Enable, rateLimit.Enable) + .Set(x => x.GeneralRules, rateLimit.GeneralRules) + .Set(x => x.EndpointWhitelist, rateLimit.EndpointWhitelist) + .Set(x => x.ClientIdHeader, rateLimit.ClientIdHeader) + .Set(x => x.ClientWhitelist, rateLimit.ClientWhitelist) + .Set(x => x.RealIpHeader, rateLimit.RealIpHeader) + .Set(x => x.IpWhitelist, rateLimit.IpWhitelist) + .Set(x => x.HttpStatusCode, rateLimit.HttpStatusCode) + .Set(x => x.QuotaExceededMessage, rateLimit.QuotaExceededMessage) + .Set(x=>x.RateLimitContentType,rateLimit.RateLimitContentType) + .Set(x => x.RateLimitCounterPrefix, rateLimit.RateLimitCounterPrefix) + .Set(x=>x.GeneralRules,rateLimit.GeneralRules) + .ExecuteAffrowsAsync(); + + + return ResultDto.SuccessResult(); + } + + public static async Task DeleteAsync(IFreeSql freeSql, string name) + { + await freeSql.Delete() + .Where(x => x.Name == name) + .ExecuteAffrowsAsync(); + + return ResultDto.SuccessResult(); + } + + public static async Task> GetListAsync(IFreeSql freeSql, int page, int pageSize) + { + var items = await freeSql.Select() + .Count(out var total) + .Page(page, pageSize) + .ToListAsync(); + + return new PageResultDto(total: total, items: items); + } + + public static async Task> GetNamesAsync(IFreeSql freeSql) + { + return await freeSql.Select().ToListAsync(x => x.Name); + } +} \ No newline at end of file diff --git a/web/src/App.tsx b/web/src/App.tsx index 17db27c..65dae71 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -7,6 +7,7 @@ import HttpProxy from './pages/http-proxy'; import Setting from './pages/setting'; import Cert from './pages/cert'; import Protection from './pages/protection'; +import RateLimit from './pages/rate-limit'; const theme = localStorage.getItem('theme'); const body = document.body; @@ -41,6 +42,10 @@ const routes = createBrowserRouter([{ path: '/protection', element: }, + { + path: '/rate-limit', + element: + }, ] }, { path: '/login', diff --git a/web/src/layouts/AdminLayout.tsx b/web/src/layouts/AdminLayout.tsx index 9c4f8ad..5f03276 100644 --- a/web/src/layouts/AdminLayout.tsx +++ b/web/src/layouts/AdminLayout.tsx @@ -28,6 +28,8 @@ export default function AdminLayout() { setKey('cert'); } else if (path === '/protection') { setKey('protection'); + } else if (path === '/rate-limit') { + setKey('rate-limit'); } }, []); @@ -85,7 +87,7 @@ export default function AdminLayout() { marginRight: '20px', }} checkedText={} uncheckedText={} size="large" /> - Gateway + FG @@ -129,6 +131,14 @@ export default function AdminLayout() { selectKey('protection', '/protection'); } }, + { + itemKey: 'rate-limit', + text: '限流管理', + icon: , + onClick: () => { + selectKey('rate-limit', '/rate-limit'); + } + }, { itemKey: 'Setting', text: '设置', diff --git a/web/src/module.ts b/web/src/module.ts index 320368c..8e6e09b 100644 --- a/web/src/module.ts +++ b/web/src/module.ts @@ -12,6 +12,7 @@ export interface ServiceInput { enableTunnel: boolean; enableBlacklist: boolean; enableWhitelist: boolean; + rateLimitName: string | null; locations: LocationInput[]; } export interface LocationInput { @@ -120,4 +121,12 @@ export interface BlacklistAndWhitelist { export enum ProtectionType { Blacklist = 1, Whitelist = 2 +} + + +export enum RateLimitType { + Concurrent = 1, + TokenBucket = 2, + SlidingWindow = 3, + FixedWindow = 4 } \ No newline at end of file diff --git a/web/src/pages/http-proxy/features/CreateHttpProxy.tsx b/web/src/pages/http-proxy/features/CreateHttpProxy.tsx index e0b08fe..70c8d0a 100644 --- a/web/src/pages/http-proxy/features/CreateHttpProxy.tsx +++ b/web/src/pages/http-proxy/features/CreateHttpProxy.tsx @@ -1,6 +1,9 @@ import { Button, Col, Collapse, Divider, Form, InputGroup, Modal, Notification, Row, Tag } from "@douyinfe/semi-ui"; import { LoadType, LocationInput, ServiceInput } from "../../../module"; import { CheckDirecotryExistence, CreateApiService } from "../../../services/ApiServiceService"; +import { useEffect, useState } from "react"; +import { GetNames } from "../../../services/RateLimitService"; + interface ICreateHttpProxyProps { visible: boolean; @@ -22,6 +25,17 @@ export default function CreateHttpProxy({ onOk, }: ICreateHttpProxyProps) { + const [names, setNames] = useState([]); + + useEffect(() => { + if (visible) { + GetNames() + .then((res) => { + setNames(res); + }) + } + }, [visible]); + return ( + { + return { + label: x, + value: x, + } + })} + initValue={values.rateLimitName} + />
-
void; + onOk: (data: RateLimit) => void; // Assuming onOk will receive the form data +} + +interface RateLimit { + // Omitting some properties for brevity + name: string; + enable: boolean; + endpointWhitelist: string[]; + clientIdHeader: string; + clientWhitelist: string[]; + realIpHeader: string; + ipWhitelist: string[]; + httpStatusCode: number; + quotaExceededMessage: string; + rateLimitCounterPrefix: string; + enableEndpointRateLimiting: boolean; + disableRateLimitHeaders: boolean; + enableRegexRuleMatching: boolean; + generalRules: GeneralRule[]; +} + +interface GeneralRule { + endpoint: string; + period: string; + limit: number; +} + +const { Input, Checkbox, Select,TextArea,InputNumber } = Form; + +export default function CreateRateLimitPage({ + visible, + onClose, + onOk +}: ICreateRateLimitProps) { + + function handleSubmit(values: RateLimit) { + if (values.generalRules.length === 0) { + Notification.error({ + title: '请添加速率限制规则', + }); + return; + } + + CreateRateLimit(values).then(() => { + Notification.success({ + title: '创建成功', + }); + onOk(values); + } + ).catch(() => { + Notification.error({ + title: '创建失败', + }); + }); + } + + return ( + +
{ + handleSubmit(values); + }} + style={{ + padding: '20px', + border: '1px solid var(--semi-color-border)', + borderRadius: '8px', + overflow: 'auto', + height: 'calc(100vh - 150px)' + }} + > + { + (({ values, formApi }) => { + return (<> + + + + + + + + +