From 9d4f3c20bd1341cfb39889a6e726f9cec714704d Mon Sep 17 00:00:00 2001 From: zuohuaijun Date: Fri, 19 Sep 2025 02:46:24 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=98=8E1=E3=80=81=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E5=89=8D=E7=AB=AFform=E5=AD=97=E6=AE=B5=E9=AA=8C=E8=AF=81=20?= =?UTF-8?q?=202=E3=80=81=E4=BC=98=E5=8C=96=E6=8E=A5=E5=8F=A3=E9=87=8D?= =?UTF-8?q?=E5=A4=8D=E8=AF=B7=E6=B1=82=20=203=E3=80=81=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E5=89=8D=E7=AB=AF=E5=AD=97=E5=85=B8=E3=80=81=E4=B8=8B=E6=8B=89?= =?UTF-8?q?=E6=A1=86=E7=BB=84=E4=BB=B6=20=204=E3=80=81=E8=B0=83=E6=95=B4?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E7=94=9F=E6=88=90=E5=8F=8A=E5=85=B6=E5=91=BD?= =?UTF-8?q?=E5=90=8D=E7=A9=BA=E9=97=B4=20=205=E3=80=81=E5=85=B6=E5=AE=83?= =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Attribute/IdempotentAttribute.cs | 98 +-- .../CodeGen/CodeGenStrategyFactory.cs | 2 +- .../CodeGen/Engines/CustomTemplateEngine.cs | 2 +- .../CodeGen/Engines/TableEntityEngine.cs | 2 +- .../CodeGen/Engines/TableSeedDataEngine.cs | 2 +- .../CodeGen/Models/CodeGenColumnConfig.cs | 2 +- .../CodeGen/Models/TemplateContextOutput.cs | 2 +- .../Base/CodeGenEntityStrategyBase.cs | 2 +- .../Strategies/Base/CodeGenStrategy.cs | 2 +- .../Base/CodeGenTableStrategyBase.cs | 2 +- .../Entity/TableEntityCodeGenStrategy.cs | 2 +- .../Entity/TableSeedDataCodeGenStrategy.cs | 2 +- .../MasterSlaveTablesCodeGenStrategy.cs | 2 +- .../Strategies/SingleTableCodeGenStrategy.cs | 2 +- .../TableRelationshipCodeGenStrategy.cs | 2 +- .../TreeMasterSlaveTablesCodeGenStrategy.cs | 2 +- .../TreeSingleTableCodeGenTableStrategy.cs | 2 +- .../TreeTableRelationshipCodeGenStrategy.cs | 2 +- Admin.NET/Admin.NET.Core/Const/CacheConst.cs | 4 +- .../CodeGen/SysCodeGenColumnService.cs | 1 - .../Service/CodeGen/SysCodeGenService.cs | 1 - .../Service/DataBase/SysDatabaseService.cs | 271 +------ .../Attribute/IdempotentTest.cs | 56 ++ Web/src/components/badgeTabs/index.vue | 12 +- .../components/cameraDialog/cameraDialog.vue | 361 +++++++++ .../components/selector/pulldownSelecter.vue | 88 +- Web/src/components/sysDict/sysDict.vue | 755 +++++++++--------- Web/src/layout/navBars/tagsView/tagsView.vue | 1 + Web/src/theme/element.scss | 58 +- Web/src/utils/formRule.ts | 402 ++++++++++ Web/src/utils/formValidator.ts | 436 ++++++++++ 31 files changed, 1773 insertions(+), 805 deletions(-) create mode 100644 Admin.NET/Admin.NET.Test/Attribute/IdempotentTest.cs create mode 100644 Web/src/components/cameraDialog/cameraDialog.vue create mode 100644 Web/src/utils/formRule.ts create mode 100644 Web/src/utils/formValidator.ts diff --git a/Admin.NET/Admin.NET.Core/Attribute/IdempotentAttribute.cs b/Admin.NET/Admin.NET.Core/Attribute/IdempotentAttribute.cs index 1c8cfe9c..59ea9463 100644 --- a/Admin.NET/Admin.NET.Core/Attribute/IdempotentAttribute.cs +++ b/Admin.NET/Admin.NET.Core/Attribute/IdempotentAttribute.cs @@ -14,99 +14,41 @@ namespace Admin.NET.Core; /// [SuppressSniffer] [AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = true)] -public class IdempotentAttribute : Attribute, IAsyncActionFilter +public class IdempotentAttribute(int intervalTime = 5, bool throwBah = true, string message = "您的操作过于频繁,请稍后再试!") : Attribute, IAsyncActionFilter { - /// - /// 请求间隔时间/秒 - /// - public int IntervalTime { get; set; } = 5; - - /// - /// 错误提示内容 - /// - public string Message { get; set; } = "你操作频率过快,请稍后重试!"; - - /// - /// 缓存前缀: Key+请求路由+用户Id+请求参数 - /// - public string CacheKey { get; set; } = CacheConst.KeyIdempotent; - - /// - /// 是否直接抛出异常:Ture是,False返回上次请求结果 - /// - public bool ThrowBah { get; set; } - - /// - /// 锁前缀 - /// - public string LockPrefix { get; set; } = "lock_"; - - public IdempotentAttribute() - { - } + private static readonly Lazy SysCacheService = new(() => App.GetService()); public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { var httpContext = context.HttpContext; - var path = httpContext.Request.Path.Value.ToString(); - var userId = httpContext.User?.FindFirstValue(ClaimConst.UserId); - var cacheExpireTime = TimeSpan.FromSeconds(IntervalTime); - + var path = httpContext.Request.Path.Value; + var userId = httpContext.User.FindFirstValue(ClaimConst.UserId); var parameters = JsonConvert.SerializeObject(context.ActionArguments, Formatting.None, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Include, DefaultValueHandling = DefaultValueHandling.Include }); - var cacheKey = CacheKey + MD5Encryption.Encrypt($"{path}{userId}{parameters}"); - var sysCacheService = httpContext.RequestServices.GetService(); - try + // 分布式锁 + var md5Key = MD5Encryption.Encrypt($"{path}{userId}{parameters}"); + using var distributedLock = SysCacheService.Value.BeginCacheLock(CacheConst.KeyIdempotent + md5Key); + if (distributedLock == null) { - // 分布式锁 - using var distributedLock = sysCacheService.BeginCacheLock($"{LockPrefix}{cacheKey}") ?? throw Oops.Oh(Message); - - var cacheValue = sysCacheService.Get(cacheKey); - if (cacheValue != null) - { - if (ThrowBah) throw Oops.Oh(Message); - context.Result = new ObjectResult(cacheValue.Value); - return; - } - else - { - var resultContext = await next(); - // 缓存请求结果 null 值不缓存 - if (resultContext.Result is ObjectResult { Value: { } } objectResult) - { - var typeName = objectResult.Value.GetType().Name; - var responseData = new ResponseData - { - Type = typeName, - Value = objectResult.Value - }; - sysCacheService.Set(cacheKey, responseData, cacheExpireTime); - } - } + if (throwBah) throw Oops.Oh(message); + return; } - catch (Exception ex) + + // 判断是否存在重复请求 + var cacheKey = CacheConst.KeyIdempotent + "cache:" + md5Key; + var isExist = SysCacheService.Value.ExistKey(cacheKey); + if (isExist) { - throw Oops.Oh($"{Message}-{ex}"); + if (throwBah) throw Oops.Oh(message); + return; } - } - /// - /// 请求结果数据 - /// - private class ResponseData - { - /// - /// 结果类型 - /// - public string Type { get; set; } - - /// - /// 请求结果 - /// - public dynamic Value { get; set; } + // 标记请求 + SysCacheService.Value.Set(cacheKey, 1, TimeSpan.FromSeconds(intervalTime)); + await next(); } } \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/CodeGen/CodeGenStrategyFactory.cs b/Admin.NET/Admin.NET.Core/CodeGen/CodeGenStrategyFactory.cs index 65ed8fb7..ace47a07 100644 --- a/Admin.NET/Admin.NET.Core/CodeGen/CodeGenStrategyFactory.cs +++ b/Admin.NET/Admin.NET.Core/CodeGen/CodeGenStrategyFactory.cs @@ -4,7 +4,7 @@ // // 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! -namespace Admin.NET.Core.CodeGen; +namespace Admin.NET.Core; /// /// 代码生成策略工厂 diff --git a/Admin.NET/Admin.NET.Core/CodeGen/Engines/CustomTemplateEngine.cs b/Admin.NET/Admin.NET.Core/CodeGen/Engines/CustomTemplateEngine.cs index 48db5539..d651dd5e 100644 --- a/Admin.NET/Admin.NET.Core/CodeGen/Engines/CustomTemplateEngine.cs +++ b/Admin.NET/Admin.NET.Core/CodeGen/Engines/CustomTemplateEngine.cs @@ -4,7 +4,7 @@ // // 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! -namespace Admin.NET.Core.CodeGen; +namespace Admin.NET.Core; /// /// 自定义模板引擎 diff --git a/Admin.NET/Admin.NET.Core/CodeGen/Engines/TableEntityEngine.cs b/Admin.NET/Admin.NET.Core/CodeGen/Engines/TableEntityEngine.cs index 49f3aad3..d9beb81b 100644 --- a/Admin.NET/Admin.NET.Core/CodeGen/Engines/TableEntityEngine.cs +++ b/Admin.NET/Admin.NET.Core/CodeGen/Engines/TableEntityEngine.cs @@ -4,7 +4,7 @@ // // 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! -namespace Admin.NET.Core.CodeGen; +namespace Admin.NET.Core; public class TableEntityEngine : ViewEngineModel { diff --git a/Admin.NET/Admin.NET.Core/CodeGen/Engines/TableSeedDataEngine.cs b/Admin.NET/Admin.NET.Core/CodeGen/Engines/TableSeedDataEngine.cs index 9bb8b43d..14c60d3b 100644 --- a/Admin.NET/Admin.NET.Core/CodeGen/Engines/TableSeedDataEngine.cs +++ b/Admin.NET/Admin.NET.Core/CodeGen/Engines/TableSeedDataEngine.cs @@ -4,7 +4,7 @@ // // 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! -namespace Admin.NET.Core.CodeGen; +namespace Admin.NET.Core; public class TableSeedDataEngine : ViewEngineModel { diff --git a/Admin.NET/Admin.NET.Core/CodeGen/Models/CodeGenColumnConfig.cs b/Admin.NET/Admin.NET.Core/CodeGen/Models/CodeGenColumnConfig.cs index 23e23016..e7d94253 100644 --- a/Admin.NET/Admin.NET.Core/CodeGen/Models/CodeGenColumnConfig.cs +++ b/Admin.NET/Admin.NET.Core/CodeGen/Models/CodeGenColumnConfig.cs @@ -4,7 +4,7 @@ // // 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! -namespace Admin.NET.Core.CodeGen; +namespace Admin.NET.Core; /// /// 代码生成详细配置参数 diff --git a/Admin.NET/Admin.NET.Core/CodeGen/Models/TemplateContextOutput.cs b/Admin.NET/Admin.NET.Core/CodeGen/Models/TemplateContextOutput.cs index 2f5dd52a..1ec9e74f 100644 --- a/Admin.NET/Admin.NET.Core/CodeGen/Models/TemplateContextOutput.cs +++ b/Admin.NET/Admin.NET.Core/CodeGen/Models/TemplateContextOutput.cs @@ -4,7 +4,7 @@ // // 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! -namespace Admin.NET.Core.CodeGen; +namespace Admin.NET.Core; /// /// 模板输出上下文 diff --git a/Admin.NET/Admin.NET.Core/CodeGen/Strategies/Base/CodeGenEntityStrategyBase.cs b/Admin.NET/Admin.NET.Core/CodeGen/Strategies/Base/CodeGenEntityStrategyBase.cs index 6859fd81..80f11bfe 100644 --- a/Admin.NET/Admin.NET.Core/CodeGen/Strategies/Base/CodeGenEntityStrategyBase.cs +++ b/Admin.NET/Admin.NET.Core/CodeGen/Strategies/Base/CodeGenEntityStrategyBase.cs @@ -4,7 +4,7 @@ // // 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! -namespace Admin.NET.Core.CodeGen; +namespace Admin.NET.Core; /// /// 种子数据策略基类(处理TableSeedDataEngine类型) diff --git a/Admin.NET/Admin.NET.Core/CodeGen/Strategies/Base/CodeGenStrategy.cs b/Admin.NET/Admin.NET.Core/CodeGen/Strategies/Base/CodeGenStrategy.cs index fc159d04..9b13efda 100644 --- a/Admin.NET/Admin.NET.Core/CodeGen/Strategies/Base/CodeGenStrategy.cs +++ b/Admin.NET/Admin.NET.Core/CodeGen/Strategies/Base/CodeGenStrategy.cs @@ -4,7 +4,7 @@ // // 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! -namespace Admin.NET.Core.CodeGen; +namespace Admin.NET.Core; /// /// 基础策略接口 diff --git a/Admin.NET/Admin.NET.Core/CodeGen/Strategies/Base/CodeGenTableStrategyBase.cs b/Admin.NET/Admin.NET.Core/CodeGen/Strategies/Base/CodeGenTableStrategyBase.cs index 699e2a67..c5465124 100644 --- a/Admin.NET/Admin.NET.Core/CodeGen/Strategies/Base/CodeGenTableStrategyBase.cs +++ b/Admin.NET/Admin.NET.Core/CodeGen/Strategies/Base/CodeGenTableStrategyBase.cs @@ -4,7 +4,7 @@ // // 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! -namespace Admin.NET.Core.CodeGen; +namespace Admin.NET.Core; /// /// 表策略基类(处理SysCodeGen类型) diff --git a/Admin.NET/Admin.NET.Core/CodeGen/Strategies/Entity/TableEntityCodeGenStrategy.cs b/Admin.NET/Admin.NET.Core/CodeGen/Strategies/Entity/TableEntityCodeGenStrategy.cs index 9eb7fd79..6894e7de 100644 --- a/Admin.NET/Admin.NET.Core/CodeGen/Strategies/Entity/TableEntityCodeGenStrategy.cs +++ b/Admin.NET/Admin.NET.Core/CodeGen/Strategies/Entity/TableEntityCodeGenStrategy.cs @@ -4,7 +4,7 @@ // // 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! -namespace Admin.NET.Core.CodeGen; +namespace Admin.NET.Core; /// /// 表实体代码生成策略类 diff --git a/Admin.NET/Admin.NET.Core/CodeGen/Strategies/Entity/TableSeedDataCodeGenStrategy.cs b/Admin.NET/Admin.NET.Core/CodeGen/Strategies/Entity/TableSeedDataCodeGenStrategy.cs index 776f91f7..dba38ccc 100644 --- a/Admin.NET/Admin.NET.Core/CodeGen/Strategies/Entity/TableSeedDataCodeGenStrategy.cs +++ b/Admin.NET/Admin.NET.Core/CodeGen/Strategies/Entity/TableSeedDataCodeGenStrategy.cs @@ -4,7 +4,7 @@ // // 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! -namespace Admin.NET.Core.CodeGen; +namespace Admin.NET.Core; /// /// 表种子数据代码生成策略类 diff --git a/Admin.NET/Admin.NET.Core/CodeGen/Strategies/MasterSlaveTablesCodeGenStrategy.cs b/Admin.NET/Admin.NET.Core/CodeGen/Strategies/MasterSlaveTablesCodeGenStrategy.cs index b4462957..c71dd5d0 100644 --- a/Admin.NET/Admin.NET.Core/CodeGen/Strategies/MasterSlaveTablesCodeGenStrategy.cs +++ b/Admin.NET/Admin.NET.Core/CodeGen/Strategies/MasterSlaveTablesCodeGenStrategy.cs @@ -4,7 +4,7 @@ // // 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! -namespace Admin.NET.Core.CodeGen; +namespace Admin.NET.Core; /// /// 主从表代码生成策略类 diff --git a/Admin.NET/Admin.NET.Core/CodeGen/Strategies/SingleTableCodeGenStrategy.cs b/Admin.NET/Admin.NET.Core/CodeGen/Strategies/SingleTableCodeGenStrategy.cs index 7ca7947a..e6050fae 100644 --- a/Admin.NET/Admin.NET.Core/CodeGen/Strategies/SingleTableCodeGenStrategy.cs +++ b/Admin.NET/Admin.NET.Core/CodeGen/Strategies/SingleTableCodeGenStrategy.cs @@ -4,7 +4,7 @@ // // 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! -namespace Admin.NET.Core.CodeGen; +namespace Admin.NET.Core; /// /// 单表代码生成策略类 diff --git a/Admin.NET/Admin.NET.Core/CodeGen/Strategies/TableRelationshipCodeGenStrategy.cs b/Admin.NET/Admin.NET.Core/CodeGen/Strategies/TableRelationshipCodeGenStrategy.cs index b115a8ee..0f0556dc 100644 --- a/Admin.NET/Admin.NET.Core/CodeGen/Strategies/TableRelationshipCodeGenStrategy.cs +++ b/Admin.NET/Admin.NET.Core/CodeGen/Strategies/TableRelationshipCodeGenStrategy.cs @@ -4,7 +4,7 @@ // // 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! -namespace Admin.NET.Core.CodeGen; +namespace Admin.NET.Core; /// /// 关系对照代码生成策略类 diff --git a/Admin.NET/Admin.NET.Core/CodeGen/Strategies/TreeMasterSlaveTablesCodeGenStrategy.cs b/Admin.NET/Admin.NET.Core/CodeGen/Strategies/TreeMasterSlaveTablesCodeGenStrategy.cs index 28e02f26..5a87d517 100644 --- a/Admin.NET/Admin.NET.Core/CodeGen/Strategies/TreeMasterSlaveTablesCodeGenStrategy.cs +++ b/Admin.NET/Admin.NET.Core/CodeGen/Strategies/TreeMasterSlaveTablesCodeGenStrategy.cs @@ -4,7 +4,7 @@ // // 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! -namespace Admin.NET.Core.CodeGen; +namespace Admin.NET.Core; /// /// 主从明细带树组件代码生成策略类 diff --git a/Admin.NET/Admin.NET.Core/CodeGen/Strategies/TreeSingleTableCodeGenTableStrategy.cs b/Admin.NET/Admin.NET.Core/CodeGen/Strategies/TreeSingleTableCodeGenTableStrategy.cs index 812f06e8..96045ef6 100644 --- a/Admin.NET/Admin.NET.Core/CodeGen/Strategies/TreeSingleTableCodeGenTableStrategy.cs +++ b/Admin.NET/Admin.NET.Core/CodeGen/Strategies/TreeSingleTableCodeGenTableStrategy.cs @@ -4,7 +4,7 @@ // // 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! -namespace Admin.NET.Core.CodeGen; +namespace Admin.NET.Core; /// /// 单表带树组件代码生成策略类 diff --git a/Admin.NET/Admin.NET.Core/CodeGen/Strategies/TreeTableRelationshipCodeGenStrategy.cs b/Admin.NET/Admin.NET.Core/CodeGen/Strategies/TreeTableRelationshipCodeGenStrategy.cs index 562ffd70..b4441507 100644 --- a/Admin.NET/Admin.NET.Core/CodeGen/Strategies/TreeTableRelationshipCodeGenStrategy.cs +++ b/Admin.NET/Admin.NET.Core/CodeGen/Strategies/TreeTableRelationshipCodeGenStrategy.cs @@ -4,7 +4,7 @@ // // 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! -namespace Admin.NET.Core.CodeGen; +namespace Admin.NET.Core; /// /// 关系对照带树组件代码生成策略类 diff --git a/Admin.NET/Admin.NET.Core/Const/CacheConst.cs b/Admin.NET/Admin.NET.Core/Const/CacheConst.cs index 673ce30e..cb41bc70 100644 --- a/Admin.NET/Admin.NET.Core/Const/CacheConst.cs +++ b/Admin.NET/Admin.NET.Core/Const/CacheConst.cs @@ -112,9 +112,9 @@ public class CacheConst public const string KeyDict = "sys_dict:"; /// - /// 重复请求(幂等)字典缓存 + /// 重复请求(幂等)缓存 /// - public const string KeyIdempotent = "sys_idempotent:"; + public const string KeyIdempotent = "sys_idempotent_lock:"; /// /// Excel临时文件缓存 diff --git a/Admin.NET/Admin.NET.Core/Service/CodeGen/SysCodeGenColumnService.cs b/Admin.NET/Admin.NET.Core/Service/CodeGen/SysCodeGenColumnService.cs index 41825976..2d019837 100644 --- a/Admin.NET/Admin.NET.Core/Service/CodeGen/SysCodeGenColumnService.cs +++ b/Admin.NET/Admin.NET.Core/Service/CodeGen/SysCodeGenColumnService.cs @@ -4,7 +4,6 @@ // // 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! -using Admin.NET.Core.CodeGen; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; diff --git a/Admin.NET/Admin.NET.Core/Service/CodeGen/SysCodeGenService.cs b/Admin.NET/Admin.NET.Core/Service/CodeGen/SysCodeGenService.cs index 34a5c020..9b8f9366 100644 --- a/Admin.NET/Admin.NET.Core/Service/CodeGen/SysCodeGenService.cs +++ b/Admin.NET/Admin.NET.Core/Service/CodeGen/SysCodeGenService.cs @@ -4,7 +4,6 @@ // // 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! -using Admin.NET.Core.CodeGen; using System.IO.Compression; namespace Admin.NET.Core; diff --git a/Admin.NET/Admin.NET.Core/Service/DataBase/SysDatabaseService.cs b/Admin.NET/Admin.NET.Core/Service/DataBase/SysDatabaseService.cs index 0f1027bb..fb41d18c 100644 --- a/Admin.NET/Admin.NET.Core/Service/DataBase/SysDatabaseService.cs +++ b/Admin.NET/Admin.NET.Core/Service/DataBase/SysDatabaseService.cs @@ -18,16 +18,19 @@ public class SysDatabaseService : IDynamicApiController, ITransient private readonly ISqlSugarClient _db; private readonly IViewEngine _viewEngine; private readonly CodeGenOptions _codeGenOptions; + private readonly CodeGenStrategyFactory _codeGenStrategyFactory; public SysDatabaseService(UserManager userManager, ISqlSugarClient db, IViewEngine viewEngine, - IOptions codeGenOptions) + IOptions codeGenOptions, + CodeGenStrategyFactory codeGenStrategyFactory) { _userManager = userManager; _db = db; _viewEngine = viewEngine; _codeGenOptions = codeGenOptions.Value; + _codeGenStrategyFactory = codeGenStrategyFactory; } /// @@ -363,33 +366,10 @@ public class SysDatabaseService : IDynamicApiController, ITransient /// [ApiDescriptionSettings(Name = "CreateEntity"), HttpPost] [DisplayName("创建实体")] - public void CreateEntity(CreateEntityInput input) + public async Task CreateEntity(CreateEntityInput input) { - var tResult = GenerateEntity(input); - var targetPath = GetEntityTargetPath(input); - File.WriteAllText(targetPath, tResult, Encoding.UTF8); - } - - /// - /// 创建实体文件内容 - /// - /// - /// - /// - /// - /// - [DisplayName("创建实体文件内容")] - public string GenerateEntity(string ConfigId, string TableName, string Position, string BaseClassName) - { - var input = new CreateEntityInput - { - TableName = TableName, - EntityName = TableName.ToFirstLetterUpperCase(), - ConfigId = ConfigId, - Position = string.IsNullOrWhiteSpace(Position) ? "Admin.NET.Application" : Position, - BaseClassName = string.IsNullOrWhiteSpace(BaseClassName) ? "EntityBaseId" : BaseClassName - }; - return GenerateEntity(input); + var template = await GenerateEntity(input); + await File.WriteAllTextAsync(template.OutPath, template.Context, Encoding.UTF8); } /// @@ -397,53 +377,12 @@ public class SysDatabaseService : IDynamicApiController, ITransient /// /// /// + [ApiDescriptionSettings(Name = "GenerateEntity"), HttpPost] [DisplayName("创建实体文件内容")] - public string GenerateEntity(CreateEntityInput input) + public async Task GenerateEntity(CreateEntityInput input) { - var config = App.GetOptions().ConnectionConfigs.FirstOrDefault(u => u.ConfigId.ToString() == input.ConfigId); - input.Position = string.IsNullOrWhiteSpace(input.Position) ? "Admin.NET.Application" : input.Position; - input.EntityName = string.IsNullOrWhiteSpace(input.EntityName) - ? (config.DbSettings.EnableUnderLine ? CodeGenHelper.CamelColumnName(input.TableName, null) : input.TableName) - : input.EntityName; - string[] dbColumnNames = []; - // Entity.cs.vm中是允许创建没有基类的实体的,所以这里也要做出相同的判断 - if (!string.IsNullOrWhiteSpace(input.BaseClassName)) - { - _codeGenOptions.EntityBaseColumn.TryGetValue(input.BaseClassName, out dbColumnNames); - if (dbColumnNames is null || dbColumnNames is { Length: 0 }) - throw Oops.Oh("基类配置文件不存在此类型"); - } - - var db = _db.AsTenant().GetConnectionScope(input.ConfigId); - var dbColumnInfos = db.DbMaintenance.GetColumnInfosByTableName(input.TableName, false); - dbColumnInfos.ForEach(u => - { - // 禁止字段全是大写的 - if (u.DbColumnName.ToUpper() == u.DbColumnName) - throw new Exception($"字段命名规范错误:{u.DbColumnName} 字段全是大写字母,请用大驼峰式命名规范!"); - - u.PropertyName = config.DbSettings.EnableUnderLine ? CodeGenHelper.CamelColumnName(u.DbColumnName, dbColumnNames) : u.DbColumnName; // 转下划线后的列名需要再转回来 - u.DataType = CodeGenHelper.ConvertDataType(u, config.DbType); - }); - if (_codeGenOptions.BaseEntityNames.Contains(input.BaseClassName, StringComparer.OrdinalIgnoreCase)) - dbColumnInfos = dbColumnInfos.Where(u => !dbColumnNames.Contains(u.PropertyName, StringComparer.OrdinalIgnoreCase)).ToList(); - - var dbTableInfo = db.DbMaintenance.GetTableInfoList(false).FirstOrDefault(u => u.Name == input.TableName || u.Name == input.TableName.ToLower()) ?? throw Oops.Oh(ErrorCodeEnum.db1001); - var templatePath = GetEntityTemplatePath(); - var tContent = File.ReadAllText(templatePath); - var tResult = _viewEngine.RunCompileFromCached(tContent, new - { - NameSpace = $"{input.Position}.Entity", - TableName = input.TableName, - EntityName = input.EntityName, - BaseClassName = string.IsNullOrWhiteSpace(input.BaseClassName) ? "" : $": {input.BaseClassName}", - ConfigId = input.ConfigId, - Description = string.IsNullOrWhiteSpace(dbTableInfo.Description) ? input.EntityName + "业务表" : dbTableInfo.Description, - TableFields = dbColumnInfos, - AuthorName = "Admin.NET", - Email = "Admin.NET@qq.com" - }); - return tResult; + var strategy = _codeGenStrategyFactory.GetStrategy(CodeGenSceneEnum.TableEntity); + return (await strategy.GenerateCode(input))?.FirstOrDefault(); } /// @@ -454,191 +393,9 @@ public class SysDatabaseService : IDynamicApiController, ITransient [DisplayName("创建种子数据")] public async Task CreateSeedData(CreateSeedDataInput input) { - var config = App.GetOptions().ConnectionConfigs.FirstOrDefault(u => u.ConfigId.ToString() == input.ConfigId); - input.Position = string.IsNullOrWhiteSpace(input.Position) ? "Admin.NET.Core" : input.Position; - - var templatePath = GetSeedDataTemplatePath(); - var db = _db.AsTenant().GetConnectionScope(input.ConfigId); - var tableInfo = db.DbMaintenance.GetTableInfoList(false).First(u => u.Name == input.TableName); // 表名 - List dbColumnInfos = db.DbMaintenance.GetColumnInfosByTableName(input.TableName, false); // 所有字段 - IEnumerable entityInfos = await GetEntityInfos(); - Type entityType = null; - foreach (var item in entityInfos) - { - if (tableInfo.Name.ToLower() != (config.DbSettings.EnableUnderLine ? UtilMethods.ToUnderLine(item.DbTableName) : item.DbTableName).ToLower()) continue; - entityType = item.Type; - break; - } - - if (entityType == null) throw Oops.Oh(ErrorCodeEnum.db1003); - - input.EntityName = entityType.Name; - input.SeedDataName = entityType.Name + "SeedData"; - if (!string.IsNullOrWhiteSpace(input.Suffix)) input.SeedDataName += input.Suffix; - - // 查询所有数据 - var query = db.QueryableByObject(entityType); - // 如果 entityType.Name=="SysDictType"或"SysDictData"或"SysDictDataTenant" 加入查询条件 u.IsEnum!=1 - if (entityType.Name == "SysDictType" || entityType.Name == "SysDictData" || entityType.Name == "SysDictDataTenant") - { - if (config.DbSettings.EnableUnderLine) - query = query.Where("is_enum != 1"); - else - query = query.Where("IsEnum != 1"); - } - - // 优先用创建时间排序 - DbColumnInfo orderField = dbColumnInfos.FirstOrDefault(u => u.DbColumnName.ToLower() == "create_time" || u.DbColumnName.ToLower() == "createtime"); - if (orderField != null) query = query.OrderBy(orderField.DbColumnName); - // 优先用创建时间排序,再使用第一个主键排序 - if (dbColumnInfos.Any(u => u.IsPrimarykey)) - query = query.OrderBy(dbColumnInfos.First(u => u.IsPrimarykey).DbColumnName); - var records = ((IEnumerable)await query.ToListAsync()).ToDynamicList(); - - // 过滤已存在的数据 - if (input.FilterExistingData && records.Count != 0) - { - // 获取实体类型-所有种数据数据类型 - var entityTypes = App.EffectiveTypes.Where(u => !u.IsInterface && !u.IsAbstract && u.IsClass && u.IsDefined(typeof(SugarTable), false) && u.FullName.EndsWith("." + input.EntityName)) - .Where(u => !u.GetCustomAttributes().Any()).ToList(); - if (entityTypes.Count == 1) // 只有一个实体匹配才能过滤 - { - // 获取实体的主键对应的属性名称 - var pkInfo = entityTypes[0].GetProperties().FirstOrDefault(u => u.GetCustomAttribute()?.IsPrimaryKey == true); - if (pkInfo != null) - { - var seedDataTypes = SeedDataHelper.GetSeedDataTypeList(entityTypes[0]); - // 可能会重名的种子数据不作为过滤项 - string doNotFilterFullName1 = $"{input.Position}.SeedData.{input.SeedDataName}"; - string doNotFilterFullName2 = $"{input.Position}.{input.SeedDataName}"; // Core中的命名空间没有SeedData - - PropertyInfo idPropertySeedData = records[0].GetType().GetProperty("Id"); - - for (int i = seedDataTypes.Count - 1; i >= 0; i--) - { - string fullName = seedDataTypes[i].FullName; - if ((fullName == doNotFilterFullName1) || (fullName == doNotFilterFullName2)) continue; - - // 删除重复数据 - var instance = Activator.CreateInstance(seedDataTypes[i]); - var hasDataMethod = seedDataTypes[i].GetMethod("HasData"); - var seedData = ((IEnumerable)hasDataMethod?.Invoke(instance, null))?.Cast(); - if (seedData == null) continue; - - List recordsToRemove = []; - foreach (var record in records) - { - object recordId = pkInfo.GetValue(record); - if (seedData.Select(d1 => idPropertySeedData.GetValue(d1)).Any(dataId => recordId != null && dataId != null && recordId.Equals(dataId))) - { - recordsToRemove.Add(record); - } - } - - foreach (var itemToRemove in recordsToRemove) - { - records.Remove(itemToRemove); - } - } - } - } - } - - // 检查有没有 System.Text.Json.Serialization.JsonIgnore 或 Newtonsoft.Json.JsonIgnore 的属性 - // 如果 JsonIgnore 和 SugarColumn 都存在,那么后成序更化时就生成这了这些字段,就需要在这里另外补充(以处理用户表SysUser中的Password为例) - var jsonIgnoreProperties = entityType.GetProperties().Where(p => (p.GetAttribute() != null || - p.GetAttribute() != null) && p.GetAttribute() != null).ToList(); - var jsonIgnoreInfo = new List>(); - if (jsonIgnoreProperties.Count > 0) - { - int recordIndex = 0; - foreach (var r in (IEnumerable)records) - { - List record = []; - foreach (var item in jsonIgnoreProperties) - { - object v = item.GetValue(r); - string strValue = "null"; - if (v != null) - { - strValue = v.ToString(); - if (v.GetType() == typeof(string)) - strValue = "\"" + strValue + "\""; - else if (v.GetType() == typeof(DateTime)) - strValue = "DateTime.Parse(\"" + ((DateTime)v).ToString("yyyy-MM-dd HH:mm:ss") + "\")"; - } - - record.Add(new JsonIgnoredPropertyData { RecordIndex = recordIndex, Name = item.Name, Value = strValue }); - } - - recordIndex++; - jsonIgnoreInfo.Add(record); - } - } - - // 获取所有字段信息 - var propertyList = entityType.GetProperties().Where(x => false == (x.GetCustomAttribute()?.IsIgnore ?? false)).ToList(); - for (var i = 0; i < propertyList.Count; i++) - { - if (propertyList[i].Name != nameof(EntityBaseId.Id) || !(propertyList[i].GetCustomAttribute()?.IsPrimaryKey ?? true)) continue; - var temp = propertyList[i]; - for (var j = i; j > 0; j--) propertyList[j] = propertyList[j - 1]; - propertyList[0] = temp; - } - // var timeConverter = new IsoDateTimeConverter { DateTimeFormat = "yyyy-MM-dd HH:mm:ss" }; - // var recordList = JsonConvert.SerializeObject(records, Formatting.Indented, timeConverter); - - // 拼接种子数据 - var recordList = records.Select(obj => string.Join(", ", propertyList.Select(prop => - { - var propType = Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType; - object value = prop.GetValue(obj); - if (value == null) value = "null"; - else if (propType == typeof(string)) - { - value = $"@\"{value.ToString().Replace("\"", "\"\"")}\""; - } - else if (propType.IsEnum) - { - value = $"{propType.Name}.{value}"; - } - else if (propType == typeof(bool)) - { - value = (bool)value ? "true" : "false"; - } - else if (propType == typeof(DateTime)) - { - value = $"DateTime.Parse(\"{((DateTime)value):yyyy-MM-dd HH:mm:ss.fff}\")"; - } - - return $"{prop.Name}={value}"; - }))).ToList(); - - var tContent = await File.ReadAllTextAsync(templatePath); - var data = new - { - NameSpace = $"{input.Position}.SeedData", - EntityNameSpace = entityType.Namespace, - input.TableName, - input.EntityName, - input.SeedDataName, - input.ConfigId, - tableInfo.Description, - JsonIgnoreInfo = jsonIgnoreInfo, - RecordList = recordList - }; - var tResult = await _viewEngine.RunCompileAsync(tContent, data, builderAction: builder => - { - builder.AddAssemblyReferenceByName("System.Linq"); - builder.AddAssemblyReferenceByName("System.Collections"); - builder.AddAssemblyReferenceByName("System.Text.RegularExpressions"); - builder.AddUsing("System.Text.RegularExpressions"); - builder.AddUsing("System.Collections.Generic"); - builder.AddUsing("System.Linq"); - }); - - var targetPath = GetSeedDataTargetPath(input); - await File.WriteAllTextAsync(targetPath, tResult, Encoding.UTF8); + var strategy = _codeGenStrategyFactory.GetStrategy(CodeGenSceneEnum.TableSeedData); + var result = (await strategy.GenerateCode(input))?.FirstOrDefault(); + await File.WriteAllTextAsync(result!.OutPath, result!.Context, Encoding.UTF8); } /// diff --git a/Admin.NET/Admin.NET.Test/Attribute/IdempotentTest.cs b/Admin.NET/Admin.NET.Test/Attribute/IdempotentTest.cs new file mode 100644 index 00000000..beb8fd51 --- /dev/null +++ b/Admin.NET/Admin.NET.Test/Attribute/IdempotentTest.cs @@ -0,0 +1,56 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using Xunit.Abstractions; + +namespace Admin.NET.Test.Attribute; + +/// +/// 防止重复请求测试 +/// +public class IdempotentTest +{ + private readonly ITestOutputHelper _testOutputHelper; + private const string BaseAddress = "http://localhost:5005"; + private readonly HttpClient _client; + + public IdempotentTest(ITestOutputHelper testOutputHelper) + { + _testOutputHelper = testOutputHelper; + _client = new HttpClient(); + _client.BaseAddress = new Uri(BaseAddress); + } + + private async Task PostAsync(string action) + { + var result = await _client.PostAsync(action, null); + return await result.Content.ReadAsStringAsync(); + } + + [Fact] + public async Task IdempotentTest1() + { + // 模拟重复请求50次 + var tasks = Task.WhenAll(Enumerable.Range(0, 50).Select(u => PostAsync("/api/test/idempotentTest1?input=1"))); + + var resultList = await tasks; + _testOutputHelper.WriteLine(string.Join('\n', resultList)); + Assert.Equal(1, resultList.Count(u => u == "请求成功")); + Assert.Equal(resultList.Length - 1, resultList.Count(u => u == "\"您的操作过于频繁,请稍后再试!\"")); + } + + [Fact] + public async Task IdempotentTest2() + { + // 模拟重复请求50次 + var tasks = Task.WhenAll(Enumerable.Range(0, 50).Select(u => PostAsync("/api/test/idempotentTest2?input=1"))); + + var resultList = await tasks; + _testOutputHelper.WriteLine(string.Join('\n', resultList)); + Assert.Equal(1, resultList.Count(u => u == "请求成功")); + Assert.Equal(resultList.Length - 1, resultList.Count(string.IsNullOrWhiteSpace)); + } +} \ No newline at end of file diff --git a/Web/src/components/badgeTabs/index.vue b/Web/src/components/badgeTabs/index.vue index 9ed64c52..543141fd 100644 --- a/Web/src/components/badgeTabs/index.vue +++ b/Web/src/components/badgeTabs/index.vue @@ -1,5 +1,6 @@