From e2daef423e4c5db67c0bc63df78986122c66b502 Mon Sep 17 00:00:00 2001 From: coolcalf <28551@qq.com> Date: Wed, 4 Jun 2025 20:49:47 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=EF=BC=9A=E5=BC=80=E5=8F=91?= =?UTF-8?q?=E5=B7=A5=E5=85=B7->=E6=95=B0=E6=8D=AE=E9=87=8D=E7=BD=AE(?= =?UTF-8?q?=E6=89=8B=E5=8A=A8=E6=93=8D=E4=BD=9C=E7=94=9F=E6=88=90=E8=A1=A8?= =?UTF-8?q?=E5=92=8C=E7=A7=8D=E5=AD=90=E6=95=B0=E6=8D=AE)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SeedData/SysMenuSeedData.cs | 9 +- .../Service/DataInit/Dto/DataInitInput.cs | 14 + .../Service/DataInit/Dto/DataInitOutput.cs | 16 + .../Service/DataInit/SysDataInitService.cs | 103 +++++++ .../Admin.NET.Core/SqlSugar/SqlSugarSetup.cs | 92 ++++++ Web/src/api/system/dataInit.ts | 37 +++ .../system/dataInit/component/entityList.vue | 284 ++++++++++++++++++ .../dataInit/component/seedDataList.vue | 284 ++++++++++++++++++ Web/src/views/system/dataInit/index.vue | 62 ++++ 9 files changed, 897 insertions(+), 4 deletions(-) create mode 100644 Admin.NET/Admin.NET.Core/Service/DataInit/Dto/DataInitInput.cs create mode 100644 Admin.NET/Admin.NET.Core/Service/DataInit/Dto/DataInitOutput.cs create mode 100644 Admin.NET/Admin.NET.Core/Service/DataInit/SysDataInitService.cs create mode 100644 Web/src/api/system/dataInit.ts create mode 100644 Web/src/views/system/dataInit/component/entityList.vue create mode 100644 Web/src/views/system/dataInit/component/seedDataList.vue create mode 100644 Web/src/views/system/dataInit/index.vue diff --git a/Admin.NET/Admin.NET.Core/SeedData/SysMenuSeedData.cs b/Admin.NET/Admin.NET.Core/SeedData/SysMenuSeedData.cs index e8f030c3..7388082c 100644 --- a/Admin.NET/Admin.NET.Core/SeedData/SysMenuSeedData.cs +++ b/Admin.NET/Admin.NET.Core/SeedData/SysMenuSeedData.cs @@ -205,10 +205,11 @@ public class SysMenuSeedData : ISqlSugarEntitySeedData new SysMenu{ Id=1310000000601, Pid=0, Title="开发工具", Path="/develop", Name="develop", Component="Layout", Icon="ele-Cpu", Type=MenuTypeEnum.Dir, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=13000 }, new SysMenu{ Id=1310000000611, Pid=1310000000601, Title="库表管理", Path="/develop/database", Name="sysDatabase", Component="/system/database/index",Icon="ele-Coin", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, - new SysMenu{ Id=1310000000621, Pid=1310000000601, Title="代码生成", Path="/develop/codeGen", Name="sysCodeGen", Component="/system/codeGen/index", Icon="ele-Crop", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=110 }, - new SysMenu{ Id=1310000000631, Pid=1310000000601, Title="表单设计", Path="/develop/formDes", Name="sysFormDes", Component="/system/formDes/index", Icon="ele-Edit", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=120 }, - new SysMenu{ Id=1310000000641, Pid=1310000000601, Title="系统接口", Path="/develop/api", Name="sysApi", Component="layout/routerView/iframe", IsIframe=true, OutLink="http://localhost:5005", Icon="ele-Help", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=130 }, - new SysMenu{ Id=1310000000651, Pid=1310000000601, Title="接口压测", Path="/develop/stressTest", Name="sysStressTest", Component="/system/stressTest/index", Icon="ele-Compass", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2024-12-28 00:00:00"), OrderNo=140 }, + new SysMenu{ Id=1310000000621, Pid=1310000000601, Title="数据重置", Path="/develop/dataInit", Name="sysDataInit", Component="/system/dataInit/index",Icon="ele-TakeawayBox", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000631, Pid=1310000000601, Title="代码生成", Path="/develop/codeGen", Name="sysCodeGen", Component="/system/codeGen/index", Icon="ele-Crop", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=110 }, + new SysMenu{ Id=1310000000641, Pid=1310000000601, Title="表单设计", Path="/develop/formDes", Name="sysFormDes", Component="/system/formDes/index", Icon="ele-Edit", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=120 }, + new SysMenu{ Id=1310000000651, Pid=1310000000601, Title="系统接口", Path="/develop/api", Name="sysApi", Component="layout/routerView/iframe", IsIframe=true, OutLink="http://localhost:5005", Icon="ele-Help", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=130 }, + new SysMenu{ Id=1310000000661, Pid=1310000000601, Title="接口压测", Path="/develop/stressTest", Name="sysStressTest", Component="/system/stressTest/index", Icon="ele-Compass", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2024-12-28 00:00:00"), OrderNo=140 }, new SysMenu{ Id=1310000000701, Pid=0, Title="帮助文档", Path="/doc", Name="doc", Component="Layout", Icon="ele-Notebook", Type=MenuTypeEnum.Dir, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=14000 }, new SysMenu{ Id=1310000000711, Pid=1310000000701, Title="框架教程", Path="/doc/admin", Name="sysAdmin", Component="layout/routerView/link", IsIframe=false, IsKeepAlive=false, OutLink="https://adminnet.top/", Icon="ele-Sunny", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, diff --git a/Admin.NET/Admin.NET.Core/Service/DataInit/Dto/DataInitInput.cs b/Admin.NET/Admin.NET.Core/Service/DataInit/Dto/DataInitInput.cs new file mode 100644 index 00000000..a0dccfe1 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/DataInit/Dto/DataInitInput.cs @@ -0,0 +1,14 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +public class DataInitInput +{ + public string ConfigId { get; set; } + + public List EntityNames { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/DataInit/Dto/DataInitOutput.cs b/Admin.NET/Admin.NET.Core/Service/DataInit/Dto/DataInitOutput.cs new file mode 100644 index 00000000..34ab4631 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/DataInit/Dto/DataInitOutput.cs @@ -0,0 +1,16 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +public class DataInitItemOutput +{ + public string Name { get; set; } + + public string AssemblyName { get; set; } + + public string Description { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/DataInit/SysDataInitService.cs b/Admin.NET/Admin.NET.Core/Service/DataInit/SysDataInitService.cs new file mode 100644 index 00000000..ca3d5abe --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/DataInit/SysDataInitService.cs @@ -0,0 +1,103 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using DocumentFormat.OpenXml.Office2016.Drawing.ChartDrawing; + +namespace Admin.NET.Core.Service; + +/// +/// 系统表和种子数据初始化服务 🧩 +/// +[ApiDescriptionSettings(Order = 250, Description = "系统表和种子数据初始化服务")] +public class SysDataInitService : IDynamicApiController, ITransient +{ + private readonly UserManager userManager; + + public SysDataInitService(UserManager userManager) + { + this.userManager = userManager; + } + + /// + /// 获取实体类列表 🔖 + /// + /// + [DisplayName("获取实体类列表")] + public List GetClassList(string configId) + { + var totalWatch = Stopwatch.StartNew(); // 开始总计时 + Log.Information($"获取实体类列表"); + var entityTypes = App.EffectiveTypes.Where(u => !u.IsInterface && !u.IsAbstract && u.IsClass && u.IsDefined(typeof(SugarTable), false)) + .Where(u => !u.GetCustomAttributes().Any()) + //.WhereIF(config.TableSettings.EnableIncreTable, u => u.IsDefined(typeof(IncreTableAttribute), false)) + .ToList(); + + if (configId == SqlSugarConst.MainConfigId) // 默认库(有系统表特性、没有日志表和租户表特性) + entityTypes = entityTypes.Where(u => u.GetCustomAttributes().Any() || (!u.GetCustomAttributes().Any() && !u.GetCustomAttributes().Any())).ToList(); + else if (configId == SqlSugarConst.LogConfigId) // 日志库 + entityTypes = entityTypes.Where(u => u.GetCustomAttributes().Any()).ToList(); + else + entityTypes = entityTypes.Where(u => u.GetCustomAttribute()?.configId.ToString() == configId).ToList(); // 自定义的库 + + List outputList = new List(); + foreach (var entityType in entityTypes) + { + outputList.Add(new DataInitItemOutput() { Name = entityType.Name, AssemblyName = entityType.Assembly.ManifestModule.Name, Description = entityType.GetCustomAttribute().TableDescription }); + } + return outputList; + } + + /// + /// 获取种子数据列表 🔖 + /// + /// + [DisplayName("获取种子数据列表")] + public List GetSeedDataList(string configId) + { + bool enableIncreSeed = false; // 是否启用增量种子数据TODO + Log.Information($"初始化种子数据"); + var seedDataTypes = App.EffectiveTypes.Where(u => !u.IsInterface && !u.IsAbstract && u.IsClass && u.GetInterfaces().Any(i => i.HasImplementedRawGeneric(typeof(ISqlSugarEntitySeedData<>)))) + .Where(u => !u.IsDefined(typeof(TenantSeedAttribute), false)) + .WhereIF(enableIncreSeed, u => u.IsDefined(typeof(IncreSeedAttribute), false)) + .OrderBy(u => u.GetCustomAttributes(typeof(SeedDataAttribute), false).Length > 0 ? ((SeedDataAttribute)u.GetCustomAttributes(typeof(SeedDataAttribute), false)[0]).Order : 0).ToList(); + + List outputList = new List(); + foreach (var seedDataType in seedDataTypes) + { + var entityType = seedDataType.GetInterfaces().First().GetGenericArguments().First(); + outputList.Add(new DataInitItemOutput() { Name = seedDataType.Name, AssemblyName = seedDataType.Assembly.ManifestModule.Name, Description = entityType.GetCustomAttribute().TableDescription + "种子数据" }); + } + return outputList; + } + + /// + /// 初始化表结构 🔖 + /// + /// + [ApiDescriptionSettings(Name = "InitTable"), HttpPost] + [DisplayName("初始化表结构")] + public void InitializeTable(DataInitInput input) + { + if (!userManager.SuperAdmin) + throw Oops.Oh("无权限操作!"); + + SqlSugarSetup.InitTables(input.ConfigId, input.EntityNames); + } + + /// + /// 初始化种子数据 🔖 + /// + /// + [ApiDescriptionSettings(Name = "InitSeedData"), HttpPost] + [DisplayName("初始化种子数据")] + public void InitializeSeedData(DataInitInput input) + { + if (!userManager.SuperAdmin) + throw Oops.Oh("无权限操作!"); + + SqlSugarSetup.InitSeedData(input.ConfigId, input.EntityNames); + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/SqlSugar/SqlSugarSetup.cs b/Admin.NET/Admin.NET.Core/SqlSugar/SqlSugarSetup.cs index 42cd5243..184c5ace 100644 --- a/Admin.NET/Admin.NET.Core/SqlSugar/SqlSugarSetup.cs +++ b/Admin.NET/Admin.NET.Core/SqlSugar/SqlSugarSetup.cs @@ -703,4 +703,96 @@ public static class SqlSugarSetup } } } + + /// + /// 初始化表结构 + /// + /// 实体名称列表 + public static void InitTables(string configId, List entityNames) + { + var dbOptions = App.GetConfig("DbConnection", true); + var config = dbOptions.ConnectionConfigs.FirstOrDefault(u => u.ConfigId.ToString() == configId); + var dbProvider = ITenant.GetConnectionScope(config.ConfigId); + + // 初始化表结构之前——系统版本号 + var (startups, oldVerion, currentVersion) = BeforeInitTable(dbProvider); + + // 初始化表结构 + var totalWatch = Stopwatch.StartNew(); // 开始总计时 + Log.Information($"初始化表结构 {config.DbType} - {config.ConfigId}"); + var entityTypes = App.EffectiveTypes.Where(u => !u.IsInterface && !u.IsAbstract && u.IsClass && u.IsDefined(typeof(SugarTable), false)) + .Where(u => !u.GetCustomAttributes().Any()) + .WhereIF(config.TableSettings.EnableIncreTable, u => u.IsDefined(typeof(IncreTableAttribute), false)).ToList(); + + entityTypes = entityTypes.Where(u => entityNames.Contains(u.Name)).ToList(); + + // 删除视图再初始化表结构,防止因为视图导致无法同步表结构 + var viewTypeList = App.EffectiveTypes.Where(u => !u.IsInterface && !u.IsAbstract && u.IsClass && u.GetInterfaces().Any(i => i.HasImplementedRawGeneric(typeof(ISqlSugarView)))).ToList(); + foreach (var viewType in viewTypeList) + { + var entityInfo = dbProvider.EntityMaintenance.GetEntityInfo(viewType) ?? throw new Exception("获取视图实体配置有误"); + if (dbProvider.DbMaintenance.GetViewInfoList(false).Any(it => it.Name.EqualIgnoreCase(entityInfo.DbTableName))) + dbProvider.DbMaintenance.DropView(entityInfo.DbTableName); + } + + int taskIndex = 0, size = entityTypes.Count; + var taskList = entityTypes.Select(entityType => Task.Run(() => + { + var stopWatch = Stopwatch.StartNew(); // 开始计时 + + dbProvider.InitTable(entityType); // 初始化表结构 + + stopWatch.Stop(); // 停止计时 + + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine($"初始化表 {entityType,-64} ({config.ConfigId} - {Interlocked.Increment(ref taskIndex):D003}/{size:D003}) 耗时:{stopWatch.ElapsedMilliseconds} ms"); + })); + Task.WaitAll(taskList.ToArray()); + + totalWatch.Stop(); // 停止总计时 + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine($"初始化表结构 {config.DbType} - {config.ConfigId} 总耗时:{totalWatch.ElapsedMilliseconds} ms"); + + + // 初始化种子数据之后——系统版本号 + AfterInitSeed(dbProvider, startups, oldVerion, currentVersion); + } + + /// + /// 初始化种子数据 + /// + /// 实体名称列表 + public static void InitSeedData(string configId, List entityNames) + { + var dbOptions = App.GetConfig("DbConnection", true); + var config = dbOptions.ConnectionConfigs.FirstOrDefault(u => u.ConfigId.ToString() == configId); + var dbProvider = ITenant.GetConnectionScope(config.ConfigId); + + var totalWatch = Stopwatch.StartNew(); // 开始总计时 + Log.Information($"初始化种子数据 {dbProvider.CurrentConnectionConfig.DbType} - {dbProvider.CurrentConnectionConfig.ConfigId}"); + var seedDataTypes = App.EffectiveTypes.Where(u => !u.IsInterface && !u.IsAbstract && u.IsClass && u.GetInterfaces().Any(i => i.HasImplementedRawGeneric(typeof(ISqlSugarEntitySeedData<>)))) + .Where(u => !u.IsDefined(typeof(TenantSeedAttribute), false)) + .OrderBy(u => u.GetCustomAttributes(typeof(SeedDataAttribute), false).Length > 0 ? ((SeedDataAttribute)u.GetCustomAttributes(typeof(SeedDataAttribute), false)[0]).Order : 0).ToList(); + + seedDataTypes = seedDataTypes.Where(u => entityNames.Contains(u.Name)).ToList(); + + int taskIndex = 0, size = seedDataTypes.Count; + foreach (var seedType in seedDataTypes) + { + var stopWatch = Stopwatch.StartNew(); // 开始计时 + + // 初始化种子数据 + var tuple = dbProvider.InitTableSeedData(seedType); + if (tuple == null) continue; + + stopWatch.Stop(); // 停止计时 + + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine($"初始化种子数据 {seedType.FullName,-58} ({dbProvider.CurrentConnectionConfig.ConfigId} - {Interlocked.Increment(ref taskIndex):D003}/{size:D003},数据量:{tuple.Value.Item1:D003},新增 {tuple.Value.Item2:D003} 条记录,更新 {tuple.Value.Item3:D003} 条记录,耗时:{stopWatch.ElapsedMilliseconds:N0} ms)"); + } + + totalWatch.Stop(); // 停止总计时 + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine($"初始化种子数据 {dbProvider.CurrentConnectionConfig.DbType} - {dbProvider.CurrentConnectionConfig.ConfigId} 总耗时:{totalWatch.ElapsedMilliseconds:N0} ms"); + } } \ No newline at end of file diff --git a/Web/src/api/system/dataInit.ts b/Web/src/api/system/dataInit.ts new file mode 100644 index 00000000..2ce366c1 --- /dev/null +++ b/Web/src/api/system/dataInit.ts @@ -0,0 +1,37 @@ +import request from '/@/utils/request'; +enum Api { + EntityClassList = '/api/sysDataInit/classList', + SeedDataList = '/api/sysDataInit/seedDataList', + InitTable = '/api/sysDataInit/initTable', + InitSeedData = '/api/sysDataInit/initSeedData', +} + +// 获取所有实体类 +export const getEntityClassList = (params?: any) => + request({ + url: `${Api.EntityClassList}/${params}`, + method: 'get', + }); + +// 获取所有种子数据 +export const getSeedDataList = (params?: any) => + request({ + url: `${Api.SeedDataList}/${params}`, + method: 'get', + }); + +// 初始化表 +export const initTable = (params?: any) => + request({ + url: `${Api.InitTable}`, + method: 'post', + data: params, + }); + +// 初始化种子数据 +export const initSeedData = (params?: any) => + request({ + url: `${Api.InitSeedData}`, + method: 'post', + data: params, + }); diff --git a/Web/src/views/system/dataInit/component/entityList.vue b/Web/src/views/system/dataInit/component/entityList.vue new file mode 100644 index 00000000..d525a779 --- /dev/null +++ b/Web/src/views/system/dataInit/component/entityList.vue @@ -0,0 +1,284 @@ + + + + + diff --git a/Web/src/views/system/dataInit/component/seedDataList.vue b/Web/src/views/system/dataInit/component/seedDataList.vue new file mode 100644 index 00000000..5aae2a58 --- /dev/null +++ b/Web/src/views/system/dataInit/component/seedDataList.vue @@ -0,0 +1,284 @@ + + + + + diff --git a/Web/src/views/system/dataInit/index.vue b/Web/src/views/system/dataInit/index.vue new file mode 100644 index 00000000..6e21b154 --- /dev/null +++ b/Web/src/views/system/dataInit/index.vue @@ -0,0 +1,62 @@ + + + + +