From ee9f6bacf321c695d1bccca03979a2470899bb59 Mon Sep 17 00:00:00 2001 From: "YUN-PC5\\user" Date: Fri, 22 Sep 2023 15:46:43 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E9=80=9F=E7=8E=87=E9=99=90?= =?UTF-8?q?=E5=88=B6=EF=BC=8C=E8=B0=83=E6=95=B4=E6=9C=AC=E5=9C=B0=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E4=B8=8A=E4=BC=A0=EF=BC=8C=E6=9C=8D=E5=8A=A1=E7=9B=91?= =?UTF-8?q?=E6=8E=A7IP=E5=8F=96=E6=9C=AC=E6=9C=BA=E7=BD=91=E7=BB=9CIP?= =?UTF-8?q?=EF=BC=8C=E4=BB=93=E5=82=A8=E5=A2=9E=E5=8A=A0=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../WebExtensions/EntityExtension.cs | 13 +- .../Controllers/CommonController.cs | 18 +- .../System/SysProfileController.cs | 15 +- .../System/monitor/IpRateLimitController.cs | 195 ++++++++++++++++++ .../System/monitor/MonitorController.cs | 10 +- ZR.Admin.WebApi/NLog.config | 7 +- ZR.Admin.WebApi/Program.cs | 30 ++- ZR.Repository/BaseRepository.cs | 56 ++++- ZR.Repository/IBaseRepository.cs | 16 +- .../Middleware/CustomIpRateLimitMiddleware.cs | 47 +++++ ZR.ServiceCore/Model/IpRateLimitLog.cs | 26 +++ ZR.ServiceCore/Model/IpRateLimitPolicy.cs | 17 ++ ZR.ServiceCore/Model/RateLimitRule.cs | 21 ++ ZR.ServiceCore/Model/SysLoginLimit.cs | 20 ++ .../IService/IIpRateLimitLogService.cs | 12 ++ .../IService/IIpRateLimitPolicyService.cs | 12 ++ .../IService/IRateLimitRuleService.cs | 11 + .../Services/IService/ISysFileService.cs | 2 +- .../IService/ISysLoginLimitService.cs | 11 + .../Services/IpRateLimitLogService.cs | 31 +++ .../Services/IpRateLimitPolicyService.cs | 27 +++ .../Services/RateLimitRuleService.cs | 51 +++++ ZR.ServiceCore/Services/SysFileService.cs | 4 +- .../Services/SysLoginLimitService.cs | 194 +++++++++++++++++ 24 files changed, 822 insertions(+), 24 deletions(-) create mode 100644 ZR.Admin.WebApi/Controllers/System/monitor/IpRateLimitController.cs create mode 100644 ZR.ServiceCore/Middleware/CustomIpRateLimitMiddleware.cs create mode 100644 ZR.ServiceCore/Model/IpRateLimitLog.cs create mode 100644 ZR.ServiceCore/Model/IpRateLimitPolicy.cs create mode 100644 ZR.ServiceCore/Model/RateLimitRule.cs create mode 100644 ZR.ServiceCore/Model/SysLoginLimit.cs create mode 100644 ZR.ServiceCore/Services/IService/IIpRateLimitLogService.cs create mode 100644 ZR.ServiceCore/Services/IService/IIpRateLimitPolicyService.cs create mode 100644 ZR.ServiceCore/Services/IService/IRateLimitRuleService.cs create mode 100644 ZR.ServiceCore/Services/IService/ISysLoginLimitService.cs create mode 100644 ZR.ServiceCore/Services/IpRateLimitLogService.cs create mode 100644 ZR.ServiceCore/Services/IpRateLimitPolicyService.cs create mode 100644 ZR.ServiceCore/Services/RateLimitRuleService.cs create mode 100644 ZR.ServiceCore/Services/SysLoginLimitService.cs diff --git a/Infrastructure/WebExtensions/EntityExtension.cs b/Infrastructure/WebExtensions/EntityExtension.cs index 3022abf..d468828 100644 --- a/Infrastructure/WebExtensions/EntityExtension.cs +++ b/Infrastructure/WebExtensions/EntityExtension.cs @@ -16,8 +16,10 @@ namespace Infrastructure types.GetProperty("CreateTime", flag)?.SetValue(source, DateTime.Now, null); types.GetProperty("AddTime", flag)?.SetValue(source, DateTime.Now, null); - types.GetProperty("CreateBy", flag)?.SetValue(source, context.GetName(), null); - types.GetProperty("Create_by", flag)?.SetValue(source, context.GetName(), null); + types.GetProperty("CreateBy", flag)?.SetValue(source, context.GetUId(), null); + types.GetProperty("CreateName", flag)?.SetValue(source, context.GetNickName(), null); + types.GetProperty("Create_by", flag)?.SetValue(source, context.GetUId(), null); + types.GetProperty("Create_name", flag)?.SetValue(source, context.GetNickName(), null); types.GetProperty("UserId", flag)?.SetValue(source, context.GetUId(), null); return source; @@ -31,9 +33,10 @@ namespace Infrastructure types.GetProperty("UpdateTime", flag)?.SetValue(source, DateTime.Now, null); types.GetProperty("Update_time", flag)?.SetValue(source, DateTime.Now, null); - types.GetProperty("UpdateBy", flag)?.SetValue(source, context.GetName(), null); - types.GetProperty("Update_by", flag)?.SetValue(source, context.GetName(), null); - + types.GetProperty("UpdateBy", flag)?.SetValue(source, context.GetUId(), null); + types.GetProperty("Update_by", flag)?.SetValue(source, context.GetUId(), null); + types.GetProperty("UpdateName", flag)?.SetValue(source, context.GetNickName(), null); + types.GetProperty("Update_name", flag)?.SetValue(source, context.GetNickName(), null); return source; } diff --git a/ZR.Admin.WebApi/Controllers/CommonController.cs b/ZR.Admin.WebApi/Controllers/CommonController.cs index b568eb6..543102e 100644 --- a/ZR.Admin.WebApi/Controllers/CommonController.cs +++ b/ZR.Admin.WebApi/Controllers/CommonController.cs @@ -1,4 +1,6 @@ -using Microsoft.AspNetCore.Mvc; +using System.Net; +using System.Net.Sockets; +using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; using Newtonsoft.Json; using ZR.Admin.WebApi.Filters; @@ -126,7 +128,16 @@ namespace ZR.Admin.WebApi.Controllers SysFile file = new(); string fileExt = Path.GetExtension(formFile.FileName);//文件后缀 double fileSize = Math.Round(formFile.Length / 1024.0, 2);//文件大小KB - + var scheme = HttpContext.Request.Scheme + "://"; + var serverIP = HttpContext.Request.Host.Value; + if (WebHostEnvironment.IsProduction()) + { + var host = await Dns.GetHostEntryAsync(Dns.GetHostName()); + var ip = host.AddressList + .FirstOrDefault(it => it.AddressFamily == AddressFamily.InterNetwork); + serverIP = ip + ":" + Request.HttpContext.Connection.LocalPort;//获取服务器IP + } + if (OptionsSetting.Upload.NotAllowedExt.Contains(fileExt)) { return ToResponse(ResultCode.CUSTOM_ERROR, "上传失败,未经允许上传类型"); @@ -147,7 +158,8 @@ namespace ZR.Admin.WebApi.Controllers { uploadDto.FileDir = OptionsSetting.Upload.LocalSavePath; } - file = await SysFileService.SaveFileToLocal(savePath, uploadDto.FileName, uploadDto.FileDir, HttpContext.GetName(), formFile); + file = await SysFileService.SaveFileToLocal(savePath, uploadDto.FileName, uploadDto.FileDir, + HttpContext.GetName(), formFile, scheme + serverIP); break; case StoreType.REMOTE: break; diff --git a/ZR.Admin.WebApi/Controllers/System/SysProfileController.cs b/ZR.Admin.WebApi/Controllers/System/SysProfileController.cs index ca2738a..4bac774 100644 --- a/ZR.Admin.WebApi/Controllers/System/SysProfileController.cs +++ b/ZR.Admin.WebApi/Controllers/System/SysProfileController.cs @@ -1,3 +1,5 @@ +using System.Net; +using System.Net.Sockets; using Microsoft.AspNetCore.Mvc; using ZR.Admin.WebApi.Filters; using ZR.Model.System; @@ -119,7 +121,18 @@ namespace ZR.Admin.WebApi.Controllers.System long userId = HttpContext.GetUId(); if (formFile == null) throw new CustomException("请选择文件"); - SysFile file = await FileService.SaveFileToLocal(hostEnvironment.WebRootPath, "", "avatar", HttpContext.GetName(), formFile); + var scheme = HttpContext.Request.Scheme + "://"; + var serverIP = HttpContext.Request.Host.Value; + if (hostEnvironment.IsProduction()) + { + var host = await Dns.GetHostEntryAsync(Dns.GetHostName()); + var ip = host.AddressList + .FirstOrDefault(it => it.AddressFamily == AddressFamily.InterNetwork); + serverIP = ip + ":" + Request.HttpContext.Connection.LocalPort;//获取服务器IP + } + + var file = await FileService.SaveFileToLocal(hostEnvironment.WebRootPath, "", "avatar", + HttpContext.GetName(), formFile, serverIP); UserService.UpdatePhoto(new SysUser() { Avatar = file.AccessUrl, UserId = userId }); return SUCCESS(new { imgUrl = file.AccessUrl }); diff --git a/ZR.Admin.WebApi/Controllers/System/monitor/IpRateLimitController.cs b/ZR.Admin.WebApi/Controllers/System/monitor/IpRateLimitController.cs new file mode 100644 index 0000000..b2c79e6 --- /dev/null +++ b/ZR.Admin.WebApi/Controllers/System/monitor/IpRateLimitController.cs @@ -0,0 +1,195 @@ +using AspNetCoreRateLimit; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; +using ZR.Admin.WebApi.Filters; +using ZR.Model; +using ZR.ServiceCore.Model; +using ZR.ServiceCore.Services.IService; +using IpRateLimitPolicy = ZR.ServiceCore.Model.IpRateLimitPolicy; + +namespace ZR.Admin.WebApi.Controllers.System.monitor; + +[Verify] +[Route("ip/route/limit")] +public class IpRateLimitController : BaseController +{ + private readonly IpRateLimitOptions _options; + private readonly IIpPolicyStore _ipPolicyStore; + private readonly IIpRateLimitPolicyService _ipRateLimitPolicyService; + private readonly IRateLimitRuleService _rateLimitRuleService; + private readonly IIpRateLimitLogService _ipRateLimitLogService; + + public IpRateLimitController(IOptions optionsAccessor, IIpPolicyStore ipPolicyStore, + IIpRateLimitPolicyService ipRateLimitPolicyService, IRateLimitRuleService rateLimitRuleService, + IIpRateLimitLogService ipRateLimitLogService) + { + _options = optionsAccessor.Value; + _ipPolicyStore = ipPolicyStore; + _ipRateLimitPolicyService = ipRateLimitPolicyService; + _rateLimitRuleService = rateLimitRuleService; + _ipRateLimitLogService = ipRateLimitLogService; + } + + /// + /// 获取限制规则 + /// + /// + [HttpGet("getIpRateLimitPolicyPage")] + public async Task GetIpRateLimitPolicyPage([FromQuery] IpRateLimitPolicy ipRateLimitPolicy, + PagerInfo pager) + { + var page = await _ipRateLimitPolicyService.SelectIpRateLimitPolicyPageAsync(ipRateLimitPolicy, pager); + return SUCCESS(page); + } + + [HttpPost("addIpRateLimitPolicy")] + public async Task AddIpRateLimitPolicy([FromBody] IpRateLimitPolicy ipRateLimitPolicy) + { + var isExist = await _ipRateLimitPolicyService.Queryable() + .Where(it => it.Ip == ipRateLimitPolicy.Ip) + .AnyAsync(); + if (isExist) throw new CustomException("该IP已存在"); + var res = await _ipRateLimitPolicyService.InsertNav(ipRateLimitPolicy) + .Include(it => it.Rules) + .ExecuteCommandAsync(); + return SUCCESS(res); + } + + [HttpPut("updateIpRateLimitPolicy")] + public async Task UpdateIpRateLimitPolicy( + [FromBody] IpRateLimitPolicy ipRateLimitPolicy) + { + var isEnable = await _ipRateLimitPolicyService + .Queryable() + .Where(it => it.Id == ipRateLimitPolicy.Id + && it.Flag == '1') + .AnyAsync(); + var res = await _ipRateLimitPolicyService.UpdateNav(ipRateLimitPolicy) + .Include(it => it.Rules) + .ExecuteCommandAsync(); + if (!isEnable) return SUCCESS(res); + { + await _ipPolicyStore.RemoveAsync(_options.IpPolicyPrefix); + var ipRateLimitPolicies = _ipRateLimitPolicyService.Queryable() + .Includes(it => it.Rules.Where(r => r.Flag == '1').ToList()) + .Where(it => it.Flag == '1') + .ToListAsync() + .GetAwaiter().GetResult() + .Adapt>(); + await _ipPolicyStore.SeedAsync(); + var pol = await _ipPolicyStore.GetAsync(_options.IpPolicyPrefix); + pol.IpRules.AddRange(ipRateLimitPolicies); + await _ipPolicyStore.SetAsync(_options.IpPolicyPrefix, pol); + } + return SUCCESS(res); + } + + [HttpPatch("enableIpRateLimitPolicy/{id}")] + public async Task EnableIpRateLimitPolicy([FromRoute] long id) + { + var isEnable = await _ipRateLimitPolicyService + .Queryable() + .Where(it => it.Id == id + && it.Flag == '1') + .AnyAsync(); + if (isEnable) + { + throw new CustomException("已启用,无法再次启用"); + } + await _ipRateLimitPolicyService.Updateable(new IpRateLimitPolicy + { + Id = id, + Flag = '1' + }).UpdateColumns(it => it.Flag) + .ExecuteCommandAsync(); + var ipRateLimitPolicy = await _ipRateLimitPolicyService.Queryable() + .Includes(it => it.Rules.Where(r => r.Flag == '1').ToList()) + .Where(it => it.Id == id) + .SingleAsync(); + var pol = await _ipPolicyStore.GetAsync(_options.IpPolicyPrefix); + pol.IpRules.Add(ipRateLimitPolicy.Adapt()); + await _ipPolicyStore.SetAsync(_options.IpPolicyPrefix, pol); + return SUCCESS('1'); + } + + [HttpPatch("disableIpRateLimitPolicy/{id}")] + public async Task DisableIpRateLimitPolicy([FromRoute] long id) + { + var isEnable = await _ipRateLimitPolicyService + .Queryable() + .Where(it => it.Id == id + && it.Flag == '1') + .AnyAsync(); + if (!isEnable) + { + throw new CustomException("已禁用,无法再次禁用"); + } + await _ipRateLimitPolicyService.Updateable(new IpRateLimitPolicy + { + Id = id, + Flag = '0' + }).UpdateColumns(it => it.Flag) + .ExecuteCommandAsync(); + var ipRateLimitPolicies = _ipRateLimitPolicyService.Queryable() + .Includes(it => it.Rules.Where(r => r.Flag == '1').ToList()) + .Where(it => it.Flag == '1') + .ToListAsync() + .GetAwaiter().GetResult() + .Adapt>(); + await _ipPolicyStore.RemoveAsync(_options.IpPolicyPrefix); + await _ipPolicyStore.SeedAsync(); + var pol = await _ipPolicyStore.GetAsync(_options.IpPolicyPrefix); + pol.IpRules.AddRange(ipRateLimitPolicies); + await _ipPolicyStore.SetAsync(_options.IpPolicyPrefix, pol); + return SUCCESS('0'); + } + + [HttpDelete("deleteIpRateLimitPolicy/{id}")] + public async Task DeleteIpRateLimitPolicyAsync([FromRoute] long id) + { + var isEnable = await _ipRateLimitPolicyService + .Queryable() + .Where(it => it.Id == id + && it.Flag == '1') + .AnyAsync(); + await _ipRateLimitPolicyService + .DeleteNav(it => it.Id == id) + .Include(it => it.Rules) + .ExecuteCommandAsync(); + if (isEnable) + { + var ipRateLimitPolicies = _ipRateLimitPolicyService.Queryable() + .Includes(it => it.Rules.Where(r => r.Flag == '1').ToList()) + .Where(it => it.Flag == '1') + .ToListAsync() + .GetAwaiter().GetResult() + .Adapt>(); + await _ipPolicyStore.RemoveAsync(_options.IpPolicyPrefix); + await _ipPolicyStore.SeedAsync(); + var pol = await _ipPolicyStore.GetAsync(_options.IpPolicyPrefix); + pol.IpRules.AddRange(ipRateLimitPolicies); + await _ipPolicyStore.SetAsync(_options.IpPolicyPrefix, pol); + } + } + + [HttpGet("getIpRateLimitLogPage")] + public async Task GetIpRateLimitLogPage([FromQuery] IpRateLimitLog ipRateLimitLog, PagerInfo pager) + { + return SUCCESS(await _ipRateLimitLogService.SelectIpRateLimitLogPageAsync(ipRateLimitLog, pager)); + } + + [HttpGet("get")] + public async Task Get() + { + return await _ipPolicyStore.GetAsync(_options.IpPolicyPrefix); + } + + [HttpGet("checkIp/{ip}")] + public async Task CheckIp(string ip) + { + var res = await _ipRateLimitPolicyService.Queryable() + .Where(it => it.Ip == ip) + .AnyAsync(); + return SUCCESS(res); + } +} \ No newline at end of file diff --git a/ZR.Admin.WebApi/Controllers/System/monitor/MonitorController.cs b/ZR.Admin.WebApi/Controllers/System/monitor/MonitorController.cs index e350369..d0c72bb 100644 --- a/ZR.Admin.WebApi/Controllers/System/monitor/MonitorController.cs +++ b/ZR.Admin.WebApi/Controllers/System/monitor/MonitorController.cs @@ -2,6 +2,8 @@ using Infrastructure.Extensions; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; using System.Diagnostics; +using System.Net; +using System.Net.Sockets; using System.Runtime.InteropServices; namespace ZR.Admin.WebApi.Controllers.monitor @@ -48,8 +50,12 @@ namespace ZR.Admin.WebApi.Controllers.monitor string appRAM = ((double)Process.GetCurrentProcess().WorkingSet64 / 1048576).ToString("N2") + " MB"; string startTime = Process.GetCurrentProcess().StartTime.ToString("yyyy-MM-dd HH:mm:ss"); string sysRunTime = ComputerHelper.GetRunTime(); - string serverIP = Request.HttpContext.Connection.LocalIpAddress.MapToIPv4().ToString() + ":" + Request.HttpContext.Connection.LocalPort;//获取服务器IP - + // string serverIP = Request.HttpContext.Connection.LocalIpAddress.MapToIPv4().ToString() + ":" + Request.HttpContext.Connection.LocalPort;//获取服务器IP + var host = Dns.GetHostEntry(Dns.GetHostName()); + var ip = host.AddressList + .FirstOrDefault(it => it.AddressFamily == AddressFamily.InterNetwork); + var serverIP = ip + ":" + Request.HttpContext.Connection.LocalPort;//获取服务器IP + var programStartTime = Process.GetCurrentProcess().StartTime; string programRunTime = DateTimeHelper.FormatTime((DateTime.Now - programStartTime).TotalMilliseconds.ToString().Split('.')[0].ParseToLong()); var data = new diff --git a/ZR.Admin.WebApi/NLog.config b/ZR.Admin.WebApi/NLog.config index 02a7cc2..fc07a6f 100644 --- a/ZR.Admin.WebApi/NLog.config +++ b/ZR.Admin.WebApi/NLog.config @@ -69,6 +69,11 @@ + + + + @@ -82,7 +87,7 @@ - + diff --git a/ZR.Admin.WebApi/Program.cs b/ZR.Admin.WebApi/Program.cs index a685668..aa708a6 100644 --- a/ZR.Admin.WebApi/Program.cs +++ b/ZR.Admin.WebApi/Program.cs @@ -3,12 +3,13 @@ using Infrastructure.Converter; using Microsoft.AspNetCore.DataProtection; using NLog.Web; using System.Text.Json; -using System.Text.Json.Serialization; using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.Caching.Redis; +using Microsoft.Extensions.Options; using ZR.Admin.WebApi.Extensions; using ZR.Common.Cache; using ZR.Infrastructure.WebExtensions; +using ZR.ServiceCore.Services.IService; using ZR.ServiceCore.Signalr; using ZR.ServiceCore.SqlSugar; @@ -31,7 +32,7 @@ builder.Services.AddSingleton(); // 跨域配置 builder.Services.AddCors(builder.Configuration); // 显示logo -builder.Services.AddLogo(); +// builder.Services.AddLogo(); //消除Error unprotecting the session cookie警告 builder.Services.AddDataProtection() .PersistKeysToFileSystem(new DirectoryInfo(Directory.GetCurrentDirectory() + Path.DirectorySeparatorChar + "DataProtection")); @@ -131,7 +132,8 @@ if (builder.Environment.IsProduction()) //使用swagger app.UseSwagger(); //启用客户端IP限制速率 -app.UseIpRateLimiting(); +// app.UseIpRateLimiting(); +app.UseMiddleware(); app.UseRateLimiter(); //设置socket连接 app.MapHub("/msgHub"); @@ -141,4 +143,26 @@ app.MapControllerRoute( pattern: "{controller=Home}/{action=Index}/{id?}"); app.MapControllers(); + +using (var serviceScope = app.Services.CreateScope()) +{ + var services = serviceScope.ServiceProvider; + + // get the IpPolicyStore instance + var ipPolicyStore = services.GetRequiredService(); + + // seed IP data from appsettings + ipPolicyStore.SeedAsync().GetAwaiter().GetResult(); + + var optionsAccessor = services.GetRequiredService>(); + var ipRateLimitPolicyService = services.GetRequiredService(); + var ipRateLimitPolicies = ipRateLimitPolicyService.Queryable() + .Includes(it => it.Rules.Where(r => r.Flag == '1').ToList()) + .Where(it => it.Flag == '1') + .ToListAsync().GetAwaiter().GetResult(); + var pol = await ipPolicyStore.GetAsync(optionsAccessor.Value.IpPolicyPrefix); + pol.IpRules.AddRange(ipRateLimitPolicies.Adapt>()); + await ipPolicyStore.SetAsync(optionsAccessor.Value.IpPolicyPrefix, pol); +} + app.Run(); \ No newline at end of file diff --git a/ZR.Repository/BaseRepository.cs b/ZR.Repository/BaseRepository.cs index 01fa179..ec24a2c 100644 --- a/ZR.Repository/BaseRepository.cs +++ b/ZR.Repository/BaseRepository.cs @@ -58,14 +58,38 @@ namespace ZR.Repository { return Context.Insertable(t); } + public IInsertable Insertable(List t) + { + return Context.Insertable(t); + } + public IInsertable Insertable(T[] t) + { + return Context.Insertable(t); + } + public InsertNavTaskInit InsertNav(T t) + { + return Context.InsertNav(t); + } + public InsertNavTaskInit InsertNav(List t) + { + return Context.InsertNav(t); + } #endregion add #region update - //public IUpdateable Updateable(T entity) - //{ - // return Context.Updateable(entity); - //} - + public IUpdateable Updateable(T entity) + { + return Context.Updateable(entity); + } + public IUpdateable Updateable(List t) + { + return Context.Updateable(t); + } + public IUpdateable Updateable(T[] t) + { + return Context.Updateable(t); + } + /// /// 实体根据主键更新 /// @@ -114,7 +138,25 @@ namespace ZR.Repository { return Context.Updateable().SetColumns(columns).Where(where).RemoveDataCache().ExecuteCommand(); } + + public UpdateNavTaskInit UpdateNav(T t) + { + return Context.UpdateNav(t); + } + + public UpdateNavTaskInit UpdateNav(List t) + { + return Context.UpdateNav(t); + } #endregion update + public IStorageable Storageable(T t) + { + return Context.Storageable(t); + } + public IStorageable Storageable(List t) + { + return Context.Storageable(t); + } public DbResult UseTran(Action action) { @@ -190,6 +232,10 @@ namespace ZR.Repository { return Context.DbMaintenance.TruncateTable(); } + public DeleteNavTaskInit DeleteNav(Expression> whereExpression) + { + return Context.DeleteNav(whereExpression); + } #endregion delete #region query diff --git a/ZR.Repository/IBaseRepository.cs b/ZR.Repository/IBaseRepository.cs index 2e44a8e..e86bed7 100644 --- a/ZR.Repository/IBaseRepository.cs +++ b/ZR.Repository/IBaseRepository.cs @@ -16,9 +16,17 @@ namespace ZR.Repository int Insert(T parm, Expression> iClumns = null, bool ignoreNull = true); IInsertable Insertable(T t); + IInsertable Insertable(List t); + IInsertable Insertable(T[] t); + InsertNavTaskInit InsertNav(T t); + InsertNavTaskInit InsertNav(List t); #endregion add #region update + IUpdateable Updateable(T entity); + IUpdateable Updateable(List t); + IUpdateable Updateable(T[] t); + int Update(T entity, bool ignoreNullColumns = false, object data = null); /// @@ -32,8 +40,14 @@ namespace ZR.Repository int Update(T entity, Expression> expression, Expression> where); int Update(Expression> where, Expression> columns); + + UpdateNavTaskInit UpdateNav(T t); + UpdateNavTaskInit UpdateNav(List t); #endregion update + IStorageable Storageable(T t); + IStorageable Storageable(List t); + DbResult UseTran(Action action); DbResult UseTran(SqlSugarClient client, Action action); @@ -46,7 +60,7 @@ namespace ZR.Repository int Delete(object id, string title = ""); int DeleteTable(); bool Truncate(); - + DeleteNavTaskInit DeleteNav(Expression> whereExpression); #endregion delete #region query diff --git a/ZR.ServiceCore/Middleware/CustomIpRateLimitMiddleware.cs b/ZR.ServiceCore/Middleware/CustomIpRateLimitMiddleware.cs new file mode 100644 index 0000000..fcce487 --- /dev/null +++ b/ZR.ServiceCore/Middleware/CustomIpRateLimitMiddleware.cs @@ -0,0 +1,47 @@ +using AspNetCoreRateLimit; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using NLog; +using ZR.ServiceCore.Model; +using ZR.ServiceCore.Services.IService; +using RateLimitRule = AspNetCoreRateLimit.RateLimitRule; + +namespace ZR.ServiceCore.Middleware; + +public class CustomIpRateLimitMiddleware : IpRateLimitMiddleware +{ + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + + private readonly IIpRateLimitLogService _ipRateLimitLogService; + + public CustomIpRateLimitMiddleware(RequestDelegate next, IProcessingStrategy processingStrategy, + IOptions options, IIpPolicyStore policyStore, IRateLimitConfiguration config, + ILogger logger, IIpRateLimitLogService ipRateLimitLogService) : base(next, processingStrategy, options, policyStore, config, logger) + { + _ipRateLimitLogService = ipRateLimitLogService; + } + + protected override void LogBlockedRequest(HttpContext httpContext, ClientRequestIdentity identity, + RateLimitCounter counter, RateLimitRule rule) + { + // base.LogBlockedRequest(httpContext, identity, counter, rule); + var nowDate = DateTime.Now; + var ipRateLimitLog = new IpRateLimitLog + { + HttpVerb = identity.HttpVerb, + Path = identity.Path, + ClientIp = identity.ClientIp, + Limit = rule.Limit, + Period = rule.Period, + Exceeded = counter.Count - rule.Limit, + Endpoint = rule.Endpoint, + CreateTime = nowDate + }; + var logStr = $"请求 {ipRateLimitLog.HttpVerb}:{ipRateLimitLog.Path} 来自 IP {ipRateLimitLog.ClientIp} 已被阻止, " + + $"配额 {ipRateLimitLog.Limit}/{ipRateLimitLog.Period} 超出次数 {ipRateLimitLog.Exceeded}. " + + $"被规则 {ipRateLimitLog.Endpoint} 阻止. 时间: {ipRateLimitLog.CreateTime}"; + Logger.Info(logStr); + _ipRateLimitLogService.InsertIpRateLimitLogAsync(ipRateLimitLog); + } +} \ No newline at end of file diff --git a/ZR.ServiceCore/Model/IpRateLimitLog.cs b/ZR.ServiceCore/Model/IpRateLimitLog.cs new file mode 100644 index 0000000..6c0063f --- /dev/null +++ b/ZR.ServiceCore/Model/IpRateLimitLog.cs @@ -0,0 +1,26 @@ +namespace ZR.ServiceCore.Model; + +[SugarTable("ip_rate_limit_log")] +[Tenant("0")] +public class IpRateLimitLog +{ + [SugarColumn(IsPrimaryKey = true)] + [JsonConverter(typeof(ValueToStringConverter))] + public long Id { get; set;} + + public string HttpVerb { get; set; } + + public string Path { get; set; } + + public string ClientIp { get; set; } + + public double Limit { get; set; } + + public string Period { get; set; } + + public double Exceeded { get; set; } + + public string Endpoint { get; set; } + + public DateTime CreateTime { get; set; } +} \ No newline at end of file diff --git a/ZR.ServiceCore/Model/IpRateLimitPolicy.cs b/ZR.ServiceCore/Model/IpRateLimitPolicy.cs new file mode 100644 index 0000000..58eb696 --- /dev/null +++ b/ZR.ServiceCore/Model/IpRateLimitPolicy.cs @@ -0,0 +1,17 @@ +namespace ZR.ServiceCore.Model; + +[SugarTable("ip_rate_limit_policy")] +[Tenant("0")] +public class IpRateLimitPolicy +{ + [SugarColumn(IsPrimaryKey = true)] + [JsonConverter(typeof(ValueToStringConverter))] + public long Id { get; set; } + + public string Ip { get; set; } + + [Navigate(NavigateType.OneToMany, nameof(RateLimitRule.IpRateLimitPolicyId))] + public List Rules { get; set; } + + public char Flag { get; set; } = '0'; +} \ No newline at end of file diff --git a/ZR.ServiceCore/Model/RateLimitRule.cs b/ZR.ServiceCore/Model/RateLimitRule.cs new file mode 100644 index 0000000..3d20faf --- /dev/null +++ b/ZR.ServiceCore/Model/RateLimitRule.cs @@ -0,0 +1,21 @@ +namespace ZR.ServiceCore.Model; + +[SugarTable("rate_limit_rule")] +[Tenant("0")] +public class RateLimitRule +{ + [SugarColumn(IsPrimaryKey = true)] + [JsonConverter(typeof(ValueToStringConverter))] + public long Id { get; set; } + + [JsonConverter(typeof(ValueToStringConverter))] + public long IpRateLimitPolicyId { get; set; } + + public string Endpoint { get; set; } + + public string Period { get; set; } + + public double Limit { get; set; } + + public char Flag { get; set; } = '0'; +} \ No newline at end of file diff --git a/ZR.ServiceCore/Model/SysLoginLimit.cs b/ZR.ServiceCore/Model/SysLoginLimit.cs new file mode 100644 index 0000000..ad33548 --- /dev/null +++ b/ZR.ServiceCore/Model/SysLoginLimit.cs @@ -0,0 +1,20 @@ +namespace ZR.ServiceCore.Model; + +[SugarTable("sys_login_limit")] +[Tenant("0")] +public class SysLoginLimit +{ + [SugarColumn(IsPrimaryKey = true)] + [JsonConverter(typeof(ValueToStringConverter))] + public long Id { get; set; } + + public string UserName { get; set; } + + public int? ErrorCount { get; set; } + + public DateTime? ErrorTime { get; set; } + + public string Ip { get; set; } + + public char Flag { get; set; } +} \ No newline at end of file diff --git a/ZR.ServiceCore/Services/IService/IIpRateLimitLogService.cs b/ZR.ServiceCore/Services/IService/IIpRateLimitLogService.cs new file mode 100644 index 0000000..94cf6eb --- /dev/null +++ b/ZR.ServiceCore/Services/IService/IIpRateLimitLogService.cs @@ -0,0 +1,12 @@ +using ZR.Model; +using ZR.Service; +using ZR.ServiceCore.Model; + +namespace ZR.ServiceCore.Services.IService; + +public interface IIpRateLimitLogService : IBaseService +{ + void InsertIpRateLimitLogAsync(IpRateLimitLog ipRateLimitLog); + + Task> SelectIpRateLimitLogPageAsync(IpRateLimitLog ipRateLimitLog, PagerInfo pager); +} \ No newline at end of file diff --git a/ZR.ServiceCore/Services/IService/IIpRateLimitPolicyService.cs b/ZR.ServiceCore/Services/IService/IIpRateLimitPolicyService.cs new file mode 100644 index 0000000..02024b4 --- /dev/null +++ b/ZR.ServiceCore/Services/IService/IIpRateLimitPolicyService.cs @@ -0,0 +1,12 @@ +using ZR.Model; +using ZR.Service; +using ZR.ServiceCore.Model; + +namespace ZR.ServiceCore.Services.IService; + +public interface IIpRateLimitPolicyService : IBaseService +{ + Task> SelectIpRateLimitPolicyPageAsync(IpRateLimitPolicy ipRateLimitPolicy, + PagerInfo pager); + +} \ No newline at end of file diff --git a/ZR.ServiceCore/Services/IService/IRateLimitRuleService.cs b/ZR.ServiceCore/Services/IService/IRateLimitRuleService.cs new file mode 100644 index 0000000..d1d69bc --- /dev/null +++ b/ZR.ServiceCore/Services/IService/IRateLimitRuleService.cs @@ -0,0 +1,11 @@ +using ZR.Service; +using ZR.ServiceCore.Model; + +namespace ZR.ServiceCore.Services.IService; + +public interface IRateLimitRuleService : IBaseService +{ + Task> DeleteRateLimitRuleAsync(long id); + + Task ChangeRateLimitRuleFlagAsync(long id); +} \ No newline at end of file diff --git a/ZR.ServiceCore/Services/IService/ISysFileService.cs b/ZR.ServiceCore/Services/IService/ISysFileService.cs index 2482bc7..e76ffd3 100644 --- a/ZR.ServiceCore/Services/IService/ISysFileService.cs +++ b/ZR.ServiceCore/Services/IService/ISysFileService.cs @@ -19,7 +19,7 @@ namespace ZR.Service.System.IService /// /// /// 文件对象 - Task SaveFileToLocal(string rootPath, string fileName, string fileDir, string userName, IFormFile formFile); + Task SaveFileToLocal(string rootPath, string fileName, string fileDir, string userName, IFormFile formFile, string uploadUrl); Task SaveFileToAliyun(SysFile file, IFormFile formFile); /// diff --git a/ZR.ServiceCore/Services/IService/ISysLoginLimitService.cs b/ZR.ServiceCore/Services/IService/ISysLoginLimitService.cs new file mode 100644 index 0000000..c36e93b --- /dev/null +++ b/ZR.ServiceCore/Services/IService/ISysLoginLimitService.cs @@ -0,0 +1,11 @@ +using ZR.Service; +using ZR.ServiceCore.Model; + +namespace ZR.ServiceCore.Services.IService; + +public interface ISysLoginLimitService : IBaseService +{ + Task AddSysLoginLimitAsync(SysLoginLimit sysLoginLimit); + + Task RemoveSysLoginLimitAsync(SysLoginLimit sysLoginLimit); +} \ No newline at end of file diff --git a/ZR.ServiceCore/Services/IpRateLimitLogService.cs b/ZR.ServiceCore/Services/IpRateLimitLogService.cs new file mode 100644 index 0000000..b7b90b3 --- /dev/null +++ b/ZR.ServiceCore/Services/IpRateLimitLogService.cs @@ -0,0 +1,31 @@ +using Infrastructure.Attribute; +using ZR.Model; +using ZR.Service; +using ZR.ServiceCore.Model; +using ZR.ServiceCore.Services.IService; + +namespace ZR.ServiceCore.Services; + +[AppService(ServiceType = typeof(IIpRateLimitLogService), ServiceLifetime = LifeTime.Transient)] +public class IpRateLimitLogService : BaseService, IIpRateLimitLogService +{ + public void InsertIpRateLimitLogAsync(IpRateLimitLog ipRateLimitLog) + { + Insertable(ipRateLimitLog).ExecuteReturnSnowflakeId(); + } + + public async Task> SelectIpRateLimitLogPageAsync(IpRateLimitLog ipRateLimitLog, PagerInfo pager) + { + RefAsync total = 0; + var res = await Queryable() + .ToPageListAsync(pager.PageNum, pager.PageSize, total); + var page = new PagedInfo + { + PageSize = pager.PageSize, + PageIndex = pager.PageNum, + Result = res, + TotalNum = total + }; + return page; + } +} \ No newline at end of file diff --git a/ZR.ServiceCore/Services/IpRateLimitPolicyService.cs b/ZR.ServiceCore/Services/IpRateLimitPolicyService.cs new file mode 100644 index 0000000..a26ee2d --- /dev/null +++ b/ZR.ServiceCore/Services/IpRateLimitPolicyService.cs @@ -0,0 +1,27 @@ +using Infrastructure.Attribute; +using ZR.Model; +using ZR.Service; +using ZR.ServiceCore.Services.IService; +using IpRateLimitPolicy = ZR.ServiceCore.Model.IpRateLimitPolicy; + +namespace ZR.ServiceCore.Services; + +[AppService(ServiceType = typeof(IIpRateLimitPolicyService), ServiceLifetime = LifeTime.Transient)] +public class IpRateLimitPolicyService : BaseService, IIpRateLimitPolicyService +{ + public async Task> SelectIpRateLimitPolicyPageAsync(IpRateLimitPolicy ipRateLimitPolicy, PagerInfo pager) + { + RefAsync total = 0; + var res = await Queryable() + .Includes(it => it.Rules) + .ToPageListAsync(pager.PageNum, pager.PageSize, total); + var page = new PagedInfo + { + PageSize = pager.PageSize, + PageIndex = pager.PageNum, + Result = res, + TotalNum = total + }; + return page; + } +} \ No newline at end of file diff --git a/ZR.ServiceCore/Services/RateLimitRuleService.cs b/ZR.ServiceCore/Services/RateLimitRuleService.cs new file mode 100644 index 0000000..756129e --- /dev/null +++ b/ZR.ServiceCore/Services/RateLimitRuleService.cs @@ -0,0 +1,51 @@ +using Infrastructure; +using Infrastructure.Attribute; +using ZR.Service; +using ZR.ServiceCore.Model; +using ZR.ServiceCore.Services.IService; + +namespace ZR.ServiceCore.Services; + +[AppService(ServiceType = typeof(IRateLimitRuleService), ServiceLifetime = LifeTime.Transient)] +public class RateLimitRuleService : BaseService, IRateLimitRuleService +{ + public async Task> DeleteRateLimitRuleAsync(long id) + { + var isExist = await Queryable() + .Where(it => it.Id == id) + .SingleAsync(); + if (isExist == null) + { + throw new CustomException("数据不存在,无法删除"); + } + var del = await Deleteable() + .Where(it => it.Id == id) + .ExecuteCommandAsync(); + if (del <= 0) throw new CustomException("删除失败"); + { + var res = await Queryable() + .Where(it => it.IpRateLimitPolicyId == isExist.IpRateLimitPolicyId) + .ToListAsync(); + return res; + } + } + + public async Task ChangeRateLimitRuleFlagAsync(long id) + { + var isExist = await Queryable() + .Where(it => it.Id == id) + .SingleAsync(); + if (isExist == null) throw new CustomException("数据不存在,无法更改"); + var upd = await Updateable(new RateLimitRule + { + Id = id, + Flag = isExist.Flag == '1' ? '0' : '1' + }).UpdateColumns(it => it.Flag) + .ExecuteCommandAsync(); + if (upd == 0) throw new CustomException("更改失败"); + { + var res = isExist.Flag == '1' ? '0' : '1'; + return res; + } + } +} diff --git a/ZR.ServiceCore/Services/SysFileService.cs b/ZR.ServiceCore/Services/SysFileService.cs index d905800..e37eb24 100644 --- a/ZR.ServiceCore/Services/SysFileService.cs +++ b/ZR.ServiceCore/Services/SysFileService.cs @@ -37,7 +37,7 @@ namespace ZR.Service.System /// 上传的文件流 /// /// - public async Task SaveFileToLocal(string rootPath, string fileName, string fileDir, string userName, IFormFile formFile) + public async Task SaveFileToLocal(string rootPath, string fileName, string fileDir, string userName, IFormFile formFile, string uploadUrl) { string fileExt = Path.GetExtension(formFile.FileName); fileName = (fileName.IsEmpty() ? HashFileName() : fileName) + fileExt; @@ -55,7 +55,7 @@ namespace ZR.Service.System { await formFile.CopyToAsync(stream); } - string uploadUrl = OptionsSetting.Upload.UploadUrl; + // string uploadUrl = OptionsSetting.Upload.UploadUrl; string accessPath = string.Concat(uploadUrl, "/", filePath.Replace("\\", "/"), "/", fileName); SysFile file = new(formFile.FileName, fileName, fileExt, fileSize + "kb", filePath, userName) { diff --git a/ZR.ServiceCore/Services/SysLoginLimitService.cs b/ZR.ServiceCore/Services/SysLoginLimitService.cs new file mode 100644 index 0000000..847af7f --- /dev/null +++ b/ZR.ServiceCore/Services/SysLoginLimitService.cs @@ -0,0 +1,194 @@ +using AspNetCoreRateLimit; +using Infrastructure; +using Infrastructure.Attribute; +using Mapster; +using Microsoft.Extensions.Options; +using ZR.Service; +using ZR.ServiceCore.Model; +using ZR.ServiceCore.Services.IService; +using IpRateLimitPolicy = ZR.ServiceCore.Model.IpRateLimitPolicy; +using RateLimitRule = ZR.ServiceCore.Model.RateLimitRule; + +namespace ZR.ServiceCore.Services; +[AppService(ServiceType = typeof(ISysLoginLimitService), ServiceLifetime = LifeTime.Transient)] +public class SysLoginLimitService : BaseService, ISysLoginLimitService +{ + private readonly IpRateLimitOptions _options; + + private readonly IIpPolicyStore _ipPolicyStore; + + private readonly IRateLimitRuleService _rateLimitRuleService; + + private readonly IIpRateLimitPolicyService _ipRateLimitPolicyService; + + public SysLoginLimitService(IIpPolicyStore ipPolicyStore, IOptions optionsAccessor, + IIpRateLimitPolicyService ipRateLimitPolicyService, IRateLimitRuleService rateLimitRuleService) + { + _ipPolicyStore = ipPolicyStore; + _ipRateLimitPolicyService = ipRateLimitPolicyService; + _rateLimitRuleService = rateLimitRuleService; + _options = optionsAccessor.Value; + } + + public async Task AddSysLoginLimitAsync(SysLoginLimit sysLoginLimit) + { + try + { + await Context.Ado.BeginTranAsync(); + var rateLimitRule = new RateLimitRule + { + Endpoint = "*", + Period = "1s", + Limit = 0, + Flag = '1' + }; + var ipRateLimitPolicy = new IpRateLimitPolicy + { + Ip = sysLoginLimit.Ip, + Flag = '1', + Rules = new List + { + rateLimitRule + } + }; + var existIpRateLimitPolicy = await _ipRateLimitPolicyService.Queryable() + .Where(it => it.Ip == sysLoginLimit.Ip) + .FirstAsync(); + if (existIpRateLimitPolicy != null) + { + rateLimitRule.IpRateLimitPolicyId = existIpRateLimitPolicy.Id; + var rateLimitRuleStore = await _rateLimitRuleService + .Storageable(rateLimitRule) + .WhereColumns(it => new + { + it.IpRateLimitPolicyId, + it.Endpoint, + it.Period, + it.Limit + }) + .ToStorageAsync(); + await rateLimitRuleStore.AsInsertable.ExecuteReturnSnowflakeIdAsync(); + await rateLimitRuleStore.AsUpdateable + .IgnoreColumns(it => it.Id) + .ExecuteCommandAsync(); + if (existIpRateLimitPolicy.Flag != '1') + { + await _ipRateLimitPolicyService.Updateable(new IpRateLimitPolicy + { + Id = existIpRateLimitPolicy.Id, + Flag = '1' + }).UpdateColumns(it => it.Flag) + .ExecuteCommandAsync(); + } + } + else + { + await _ipRateLimitPolicyService.InsertNav(ipRateLimitPolicy) + .Include(it => it.Rules) + .ExecuteCommandAsync(); + } + + var res = await _ipRateLimitPolicyService.Queryable() + .Includes(it => it.Rules.Where(r => r.Flag == '1').ToList()) + .Where(it => it.Flag == '1') + .ToListAsync(); + await _ipPolicyStore.RemoveAsync(_options.IpPolicyPrefix); + await _ipPolicyStore.SeedAsync(); + var pol = await _ipPolicyStore.GetAsync(_options.IpPolicyPrefix); + pol.IpRules.AddRange(res.Adapt>()); + await _ipPolicyStore.SetAsync(_options.IpPolicyPrefix, pol); + await Updateable(new SysLoginLimit + { + Id = sysLoginLimit.Id, + Flag = '1' + }).UpdateColumns(it => it.Flag) + .ExecuteCommandAsync(); + await Context.Ado.CommitTranAsync(); + return true; + } + catch (Exception e) + { + await Context.Ado.RollbackTranAsync(); + var res = await _ipRateLimitPolicyService.Queryable() + .Includes(it => it.Rules.Where(r => r.Flag == '1').ToList()) + .Where(it => it.Flag == '1') + .ToListAsync(); + await _ipPolicyStore.RemoveAsync(_options.IpPolicyPrefix); + await _ipPolicyStore.SeedAsync(); + var pol = await _ipPolicyStore.GetAsync(_options.IpPolicyPrefix); + pol.IpRules.AddRange(res.Adapt>()); + await _ipPolicyStore.SetAsync(_options.IpPolicyPrefix, pol); + throw; + } + } + + public async Task RemoveSysLoginLimitAsync(SysLoginLimit sysLoginLimit) + { + try + { + await Context.Ado.BeginTranAsync(); + // 查询是否存在此IP的规则 + var existIpRateLimitPolicy = await _ipRateLimitPolicyService.Queryable() + .Where(it => it.Ip == sysLoginLimit.Ip) + .FirstAsync(); + if (existIpRateLimitPolicy != null) + { + var rateLimitRule = new RateLimitRule + { + Endpoint = "*", + Period = "1s", + Limit = 0, + Flag = '0', + IpRateLimitPolicyId = existIpRateLimitPolicy.Id + }; + var rateLimitRuleStore = await _rateLimitRuleService + .Storageable(rateLimitRule) + .WhereColumns(it => new + { + it.IpRateLimitPolicyId, + it.Endpoint, + it.Period, + it.Limit + }) + .ToStorageAsync(); + await rateLimitRuleStore.AsInsertable.ExecuteReturnSnowflakeIdAsync(); + await rateLimitRuleStore.AsUpdateable + .IgnoreColumns(it => it.Id) + .ExecuteCommandAsync(); + var res = await _ipRateLimitPolicyService.Queryable() + .Includes(it => it.Rules.Where(r => r.Flag == '1').ToList()) + .Where(it => it.Flag == '1') + .ToListAsync(); + await _ipPolicyStore.RemoveAsync(_options.IpPolicyPrefix); + await _ipPolicyStore.SeedAsync(); + var pol = await _ipPolicyStore.GetAsync(_options.IpPolicyPrefix); + pol.IpRules.AddRange(res.Adapt>()); + await _ipPolicyStore.SetAsync(_options.IpPolicyPrefix, pol); + await Updateable(new SysLoginLimit + { + Id = sysLoginLimit.Id, + Flag = '0', + ErrorCount = 0 + }).UpdateColumns(it => new { it.Flag, it.ErrorCount }) + .ExecuteCommandAsync(); + await Context.Ado.CommitTranAsync(); + return true; + } + throw new CustomException("不存在此IP的规则"); + } + catch (Exception e) + { + await Context.Ado.RollbackTranAsync(); + var res = await _ipRateLimitPolicyService.Queryable() + .Includes(it => it.Rules.Where(r => r.Flag == '1').ToList()) + .Where(it => it.Flag == '1') + .ToListAsync(); + await _ipPolicyStore.RemoveAsync(_options.IpPolicyPrefix); + await _ipPolicyStore.SeedAsync(); + var pol = await _ipPolicyStore.GetAsync(_options.IpPolicyPrefix); + pol.IpRules.AddRange(res.Adapt>()); + await _ipPolicyStore.SetAsync(_options.IpPolicyPrefix, pol); + throw; + } + } +} \ No newline at end of file