From 973c3281b2421dc543500e8bd4076b545f861c5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=8D=E5=81=9A=E7=A0=81=E5=86=9C?= <599854767@qq.com> Date: Sat, 9 Apr 2022 15:06:45 +0800 Subject: [PATCH] Squashed commit of the following: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 1daa137d98a3a814e3617606b14c737dbb611f4f Author: 不做码农 <599854767@qq.com> Date: Sat Apr 9 14:51:29 2022 +0800 IPRatelimit添加白名单接口 commit d05690654b889ae3ab8f4add1b76a5859e1ab89a Author: 不做码农 <599854767@qq.com> Date: Fri Apr 8 20:20:11 2022 +0800 添加页签openPage支持传递参数 commit 861710079a26294c64d70eeadb7630bf83e3bfc4 Author: 不做码农 <599854767@qq.com> Date: Fri Apr 8 12:44:28 2022 +0800 update FileHelper.cs commit a0cf47c099533fabaf5bd389cf498f08eead2ef4 Author: 不做码农 <599854767@qq.com> Date: Thu Apr 7 13:30:47 2022 +0800 优化代码生成模板 commit 5b376614d02c2d27b6e90252c2a3169adfb0e612 Author: 不做码农 <599854767@qq.com> Date: Thu Apr 7 13:30:13 2022 +0800 IP限制增加百名单接口 commit 939ec56d1db4cad52f7b64555e16d52b5b061360 Author: 不做码农 <599854767@qq.com> Date: Mon Apr 4 21:48:27 2022 +0800 fix 基础sql脚本bug commit 19c738b974e14b62929dc69c2c6ac1e8540aad98 Author: 不做码农 <599854767@qq.com> Date: Mon Apr 4 18:53:02 2022 +0800 新增加IPRateLimit限制 commit 6b0e6b11b3fdbb5ab53606cf4d0910ae30886365 Author: 不做码农 <599854767@qq.com> Date: Mon Apr 4 12:09:39 2022 +0800 格式化代码 commit 1024471c64beef683b257b38d4904ab7cd509c82 Author: 不做码农 <599854767@qq.com> Date: Mon Apr 4 12:02:32 2022 +0800 自定义异常新增获取LogAttribute属性 --- .../Controllers/CommonController.cs | 5 +- ZR.Admin.WebApi/Extensions/IPRateExtension.cs | 27 +++ .../Middleware/GlobalExceptionMiddleware.cs | 40 ++++- ZR.Admin.WebApi/Startup.cs | 168 ++++++++++++++++++ ZR.Admin.WebApi/ZR.Admin.WebApi.csproj | 8 +- ZR.Admin.WebApi/appsettings.json | 69 +++++++ .../wwwroot/CodeGenTemplate/CurdForm.txt | 109 ++++++++++++ .../wwwroot/CodeGenTemplate/TplTreeVue.txt | 3 +- .../wwwroot/CodeGenTemplate/TplVue.txt | 2 +- .../wwwroot/CodeGenTemplate/v3/Vue.txt | 1 - ZR.CodeGenerator/CodeGenerateTemplate.cs | 34 +--- ZR.CodeGenerator/CodeGeneratorTool.cs | 65 +++---- ZR.CodeGenerator/FileHelper.cs | 4 +- ZR.CodeGenerator/Model/ReplaceDto.cs | 4 - ZR.Vue/src/plugins/tab.js | 4 +- ZR.Vue/src/utils/request.js | 8 +- ZR.Vue/src/views/index_v1.vue | 72 ++++---- document/admin-mysql.sql | 45 +++-- document/admin-sqlserver.sql | 21 +-- 19 files changed, 515 insertions(+), 174 deletions(-) create mode 100644 ZR.Admin.WebApi/Extensions/IPRateExtension.cs create mode 100644 ZR.Admin.WebApi/Startup.cs create mode 100644 ZR.Admin.WebApi/wwwroot/CodeGenTemplate/CurdForm.txt diff --git a/ZR.Admin.WebApi/Controllers/CommonController.cs b/ZR.Admin.WebApi/Controllers/CommonController.cs index c9e44a6..04ded2d 100644 --- a/ZR.Admin.WebApi/Controllers/CommonController.cs +++ b/ZR.Admin.WebApi/Controllers/CommonController.cs @@ -1,16 +1,13 @@ using Infrastructure; using Infrastructure.Attribute; using Infrastructure.Enums; -using Infrastructure.Extensions; using Infrastructure.Model; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; using Newtonsoft.Json; -using Snowflake.Core; using System; -using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; @@ -171,7 +168,7 @@ namespace ZR.Admin.WebApi.Controllers } SysFile file = new(formFile.FileName, fileName, fileExt, fileSize + "kb", fileDir, HttpContext.GetName()) { - StoreType = (int)Infrastructure.Enums.StoreType.ALIYUN, + StoreType = (int)StoreType.ALIYUN, FileType = formFile.ContentType }; file = await SysFileService.SaveFileToAliyun(file, formFile); diff --git a/ZR.Admin.WebApi/Extensions/IPRateExtension.cs b/ZR.Admin.WebApi/Extensions/IPRateExtension.cs new file mode 100644 index 0000000..ea0da14 --- /dev/null +++ b/ZR.Admin.WebApi/Extensions/IPRateExtension.cs @@ -0,0 +1,27 @@ +using AspNetCoreRateLimit; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using System; + +namespace ZR.Admin.WebApi.Extensions +{ + public static class IPRateExtension + { + public static void AddIPRate(this IServiceCollection services, IConfiguration configuration) + { + if (services == null) throw new ArgumentNullException(nameof(services)); + + //从appsettings.json中加载常规配置,IpRateLimiting与配置文件中节点对应 + services.Configure(configuration.GetSection("IpRateLimiting")); + + //从appsettings.json中加载Ip规则 + services.Configure(configuration.GetSection("IpRateLimitPolicies")); + //注入计数器和规则存储 + services.AddSingleton(); + services.AddSingleton(); + //配置(解析器、计数器密钥生成器) + services.AddSingleton(); + services.AddSingleton(); + } + } +} diff --git a/ZR.Admin.WebApi/Middleware/GlobalExceptionMiddleware.cs b/ZR.Admin.WebApi/Middleware/GlobalExceptionMiddleware.cs index 1e91d9e..9044a22 100644 --- a/ZR.Admin.WebApi/Middleware/GlobalExceptionMiddleware.cs +++ b/ZR.Admin.WebApi/Middleware/GlobalExceptionMiddleware.cs @@ -1,10 +1,13 @@ using Infrastructure; +using Infrastructure.Attribute; using Infrastructure.Model; using IPTools.Core; using Microsoft.AspNetCore.Http; -using Newtonsoft.Json; +using Microsoft.AspNetCore.Http.Features; using NLog; using System; +using System.Text.Encodings.Web; +using System.Text.Json; using System.Threading.Tasks; using ZR.Admin.WebApi.Extensions; using ZR.Model.System; @@ -66,8 +69,15 @@ namespace ZR.Admin.WebApi.Middleware logLevel = NLog.LogLevel.Error; context.Response.StatusCode = 500; } + var options = new JsonSerializerOptions + { + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + WriteIndented = true + }; - string responseResult = System.Text.Json.JsonSerializer.Serialize(new ApiResult(code, msg)).ToLower(); + ApiResult apiResult = new(code, msg); + string responseResult = JsonSerializer.Serialize(apiResult, options).ToLower(); string ip = HttpContextExtension.GetClientUserIp(context); var ip_info = IpTool.Search(ip); @@ -83,6 +93,18 @@ namespace ZR.Admin.WebApi.Middleware operLocation = ip_info.Province + " " + ip_info.City, operTime = DateTime.Now }; + var endpoint = GetEndpoint(context); + if (endpoint != null) + { + var logAttribute = endpoint.Metadata.GetMetadata(); + if (logAttribute != null) + { + sysOperLog.businessType = (int)logAttribute?.BusinessType; + sysOperLog.title = logAttribute?.Title; + sysOperLog.operParam = logAttribute.IsSaveRequestData ? sysOperLog.operParam : ""; + sysOperLog.jsonResult = logAttribute.IsSaveResponseData ? sysOperLog.jsonResult : ""; + } + } HttpContextExtension.GetRequestValue(context, sysOperLog); LogEventInfo ei = new(logLevel, "GlobalExceptionMiddleware", error); @@ -91,12 +113,22 @@ namespace ZR.Admin.WebApi.Middleware ei.Properties["status"] = 1;//走正常返回都是通过走GlobalExceptionFilter不通过 ei.Properties["jsonResult"] = responseResult; ei.Properties["requestParam"] = sysOperLog.operParam; - ei.Properties["user"] = context.User.Identity?.Name; + ei.Properties["user"] = HttpContextExtension.GetName(context); Logger.Log(ei); - await context.Response.WriteAsync(responseResult); + await context.Response.WriteAsync(responseResult, System.Text.Encoding.UTF8); SysOperLogService.InsertOperlog(sysOperLog); } + + public static Endpoint? GetEndpoint(HttpContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + return context.Features.Get()?.Endpoint; + } } } diff --git a/ZR.Admin.WebApi/Startup.cs b/ZR.Admin.WebApi/Startup.cs new file mode 100644 index 0000000..e60e549 --- /dev/null +++ b/ZR.Admin.WebApi/Startup.cs @@ -0,0 +1,168 @@ +using AspNetCoreRateLimit; +using Hei.Captcha; +using Infrastructure; +using Infrastructure.Extensions; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.DataProtection; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using System; +using System.IO; +using System.Threading.Tasks; +using ZR.Admin.WebApi.Extensions; +using ZR.Admin.WebApi.Filters; +using ZR.Admin.WebApi.Framework; +using ZR.Admin.WebApi.Hubs; +using ZR.Admin.WebApi.Middleware; + +namespace ZR.Admin.WebApi +{ + public class Startup + { + public Startup(IConfiguration configuration) + { + Configuration = configuration; + } + private NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger(); + public IConfiguration Configuration { get; } + public void ConfigureServices(IServiceCollection services) + { + string corsUrls = Configuration["corsUrls"]; + + //ÿ + services.AddCors(c => + { + c.AddPolicy("Policy", policy => + { + policy.WithOrigins(corsUrls.Split(',', StringSplitOptions.RemoveEmptyEntries)) + .AllowAnyHeader()//ͷ + .AllowCredentials()//cookie + .AllowAnyMethod();//ⷽ + }); + }); + //עSignalRʵʱͨѶĬjson + services.AddSignalR(options => + { + //ͻ˷󵽷Ĭ30룬ij4ӣҳconnection.keepAliveIntervalInMilliseconds = 12e4;2 + //options.ClientTimeoutInterval = TimeSpan.FromMinutes(4); + //˷󵽿ͻ˼Ĭ15룬ij2ӣҳconnection.serverTimeoutInMilliseconds = 24e4;4 + //options.KeepAliveInterval = TimeSpan.FromMinutes(2); + }); + //Error unprotecting the session cookie + services.AddDataProtection() + .PersistKeysToFileSystem(new DirectoryInfo(Directory.GetCurrentDirectory() + Path.DirectorySeparatorChar + "DataProtection")); + //֤ͨ + services.AddHeiCaptcha(); + services.AddIPRate(Configuration); + services.AddSession(); + services.AddMemoryCache(); + services.AddHttpContextAccessor(); + + //Model + services.Configure(Configuration); + + //jwt ֤ + services.AddAuthentication(options => + { + options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; + }).AddCookie() + .AddJwtBearer(o => + { + o.TokenValidationParameters = JwtUtil.ValidParameters(); + }); + + InjectServices(services, Configuration); + + services.AddMvc(options => + { + options.Filters.Add(typeof(GlobalActionMonitor));//ȫע + }) + .AddJsonOptions(options => + { + options.JsonSerializerOptions.Converters.Add(new JsonConverterUtil.DateTimeConverter()); + options.JsonSerializerOptions.Converters.Add(new JsonConverterUtil.DateTimeNullConverter()); + }); + + services.AddSwaggerConfig(); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + app.UseSwagger(); + //ʹԶζȥbody + app.Use((context, next) => + { + context.Request.EnableBuffering(); + if (context.Request.Query.TryGetValue("access_token", out var token)) + { + context.Request.Headers.Add("Authorization", $"Bearer {token}"); + } + return next(); + }); + //ʾ̬ļ/wwwrootĿ¼ļҪUseRoutingǰ + app.UseStaticFiles(); + //·ɷ + app.UseRouting(); + app.UseCors("Policy");//Ҫapp.UseEndpointsǰ + + //app.UseAuthenticationAuthenticationммݵǰHttpеCookieϢHttpContext.Userԣõ + //ֻapp.UseAuthentication֮עмܹHttpContext.Userжȡֵ + //ҲΪʲôǿapp.UseAuthenticationһҪapp.UseMvcǰ棬ΪֻASP.NET CoreMVCмвܶȡHttpContext.Userֵ + //1.ȿ֤ + app.UseAuthentication(); + //2.ٿȨ + app.UseAuthorization(); + //session + app.UseSession(); + // + app.UseResponseCaching(); + //ָ/ + app.UseAddTaskSchedulers(); + //ʹȫ쳣м + app.UseMiddleware(); + //ÿͻIP + app.UseIpRateLimiting(); + + app.UseEndpoints(endpoints => + { + //socket + endpoints.MapHub("/msgHub"); + + endpoints.MapControllerRoute( + name: "default", + pattern: "{controller=Home}/{action=Index}/{id?}"); + }); + } + + /// + /// עServices + /// + /// + /// + private void InjectServices(IServiceCollection services, IConfiguration configuration) + { + services.AddAppService(); + services.AddSingleton(new AppSettings(configuration)); + //ƻ + services.AddTaskSchedulers(); + //ʼdb + DbExtension.AddDb(configuration); + + //עREDIS + Task.Run(() => + { + //RedisServer.Initalize(); + }); + } + } +} diff --git a/ZR.Admin.WebApi/ZR.Admin.WebApi.csproj b/ZR.Admin.WebApi/ZR.Admin.WebApi.csproj index 803610d..1cd61ea 100644 --- a/ZR.Admin.WebApi/ZR.Admin.WebApi.csproj +++ b/ZR.Admin.WebApi/ZR.Admin.WebApi.csproj @@ -21,6 +21,7 @@ + @@ -37,11 +38,4 @@ - - - - PreserveNewest - - - diff --git a/ZR.Admin.WebApi/appsettings.json b/ZR.Admin.WebApi/appsettings.json index f4ddf7b..bbead78 100644 --- a/ZR.Admin.WebApi/appsettings.json +++ b/ZR.Admin.WebApi/appsettings.json @@ -54,5 +54,74 @@ "RedisServer": { "Cache": "127.0.0.1:6379,defaultDatabase=0,poolsize=50,ssl=false,writeBuffer=10240,prefix=cache:", "Session": "127.0.0.1:6379,defaultDatabase=0,poolsize=50,ssl=false,writeBuffer=10240,prefix=session:" + }, + //ӿ + "IpRateLimiting": { + //5ÿӷFalseʱĿÿӿڶĸӿڣֻҪһۼƹ5Σֹʡ + //Trueһ5GetDataӿڣýӿڽʱڽֹʣǻԷPostData()5,ܵ˵ÿӿڶ5һӣš + "EnableEndpointRateLimiting": true, + //falseܾAPIòӵô; ͻÿ뷢3ÿһõƣÿӻÿƽ¼һãɹAPIáϣܾAPIüʱʾӣСʱȣ + //StackBlockedRequestsΪtrue + "StackBlockedRequests": false, + "RealIpHeader": "X-Real-IP", + //ȡĿͻID˱ͷдڿͻIDClientWhitelistֵָƥ䣬Ӧơ + "ClientIdHeader": "X-ClientId", + "HttpStatusCode": 429, + //˵ + "EndpointWhitelist": [ "post:/system/dict/data/types", "*:/msghub/negotiate", "*:/LogOut" ], + //ͻ˰ + //"ClientWhitelist": [ "dev-id-1", "dev-id-2" ], + "QuotaExceededResponse": { + "Content": "{{\"code\":429,\"msg\":\"ʹƵԺ\"}}", + "ContentType": "application/json", + "StatusCode": 429 + }, + //ͨùapi,βһҪ* + "GeneralRules": [ + { + "Endpoint": "*:/captchaImage", + //ʱΣʽ{}{λ}ʹõλs, m, h, d + "Period": "3s", + "Limit": 5 + }, + { + "Endpoint": "post:*", + //ʱΣʽ{}{λ}ʹõλs, m, h, d + "Period": "3s", + "Limit": 1 + }, + { + "Endpoint": "put:*", + //ʱΣʽ{}{λ}ʹõλs, m, h, d + "Period": "3s", + "Limit": 1 + } + //{ + // "Endpoint": "*", + // //ʱΣʽ{}{λ}ʹõλs, m, h, d + // "Period": "1s", + // "Limit": 2 + //} + //{ + // "Endpoint": "*", + // "Period": "15m", + // "Limit": 100 + //}, + //{ + // "Endpoint": "*", + // "Period": "12h", + // "Limit": 1000 + //}, + //{ + // "Endpoint": "*", + // "Period": "7d", + // "Limit": 10000 + //} + ], + "IpRateLimitPolicies": { + //ip + "IpRules": [ + ] + } } } diff --git a/ZR.Admin.WebApi/wwwroot/CodeGenTemplate/CurdForm.txt b/ZR.Admin.WebApi/wwwroot/CodeGenTemplate/CurdForm.txt new file mode 100644 index 0000000..2c37aa2 --- /dev/null +++ b/ZR.Admin.WebApi/wwwroot/CodeGenTemplate/CurdForm.txt @@ -0,0 +1,109 @@ +$foreach(column in genTable.Columns) +$set(labelName = "") +$set(labelDisabled = "") +$set(columnName = column.CsharpFieldFl) +$set(value = "item.dictValue") + +$if(column.ColumnComment != "") +$set(labelName = column.ColumnComment) +$else +$set(labelName = column.CsharpFieldFl) +$end +$if(column.IsPk == true) +$set(labelDisabled = ":disabled=true") +$end +$if(column.CsharpType == "int" || column.CsharpType == "long") + $set(value = "parseInt(item.dictValue)") +$end + +$if(tool.CheckInputDtoNoField(column.CsharpField)) +$elseif(column.IsInsert == false && column.IsEdit == false) + + {{form.${columnName}}} + +$elseif(tool.CheckTree(genTable ,column.CsharpField)) + + + + + +$elseif(column.IsPK || column.IsIncrement) + + +$if(column.IsIncrement == false) + +$else + +$end + + +$else +$if(column.HtmlType == "inputNumber") + + + + + +$elseif(column.HtmlType == "datetime") + + + + + +$elseif(column.HtmlType == "imageUpload") + + + + + +$elseif(column.HtmlType == "fileUpload") + + + + + +$elseif(column.HtmlType == "radio") + + + + {{item.dictLabel}} + + + +$elseif(column.HtmlType == "textarea") + + + + + +$elseif(column.HtmlType == "editor") + + + + + +$elseif(column.HtmlType == "select") + + + + + + + +$elseif(column.HtmlType == "checkbox") + + + + {{item.dictLabel}} + + + +$else + + + + + +$end +$end +$end \ No newline at end of file diff --git a/ZR.Admin.WebApi/wwwroot/CodeGenTemplate/TplTreeVue.txt b/ZR.Admin.WebApi/wwwroot/CodeGenTemplate/TplTreeVue.txt index 3b3edef..1528c3e 100644 --- a/ZR.Admin.WebApi/wwwroot/CodeGenTemplate/TplTreeVue.txt +++ b/ZR.Admin.WebApi/wwwroot/CodeGenTemplate/TplTreeVue.txt @@ -48,7 +48,7 @@ ${VueViewListContent} -${VueViewFormContent} + ${VueViewFormContent}