From 1b0aa27e53270812590c4d07b60b3c643836bded Mon Sep 17 00:00:00 2001 From: zuohuaijun Date: Wed, 18 Dec 2024 12:31:28 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=98=8E=E6=B8=85=E7=90=86=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Service/CodeGen/SysCodeGenService.cs | 7 +- .../Service/DataBase/SysDatabaseService.cs | 1213 ++++++++--------- 2 files changed, 610 insertions(+), 610 deletions(-) diff --git a/Admin.NET/Admin.NET.Core/Service/CodeGen/SysCodeGenService.cs b/Admin.NET/Admin.NET.Core/Service/CodeGen/SysCodeGenService.cs index 8591fe40..5c7b0839 100644 --- a/Admin.NET/Admin.NET.Core/Service/CodeGen/SysCodeGenService.cs +++ b/Admin.NET/Admin.NET.Core/Service/CodeGen/SysCodeGenService.cs @@ -319,8 +319,9 @@ public class SysCodeGenService : IDynamicApiController, ITransient /// /// 获取库表信息 /// + /// /// - private async Task> GetEntityInfos(bool filtSysTable = false) + private async Task> GetEntityInfos(bool containSysTable = false) { var types = new List(); if (_codeGenOptions.EntityAssemblyNames != null) @@ -355,8 +356,8 @@ public class SysCodeGenService : IDynamicApiController, ITransient var entityInfos = new List(); foreach (var ct in cosType) { - //// 若实体贴[SysTable]特性,则禁止显示系统自带的 - if (filtSysTable && ct.IsDefined(typeof(SysTableAttribute), false)) + // 若实体贴[SysTable]特性,则禁止显示系统自带的 + if (containSysTable && ct.IsDefined(typeof(SysTableAttribute), false)) continue; var des = ct.GetCustomAttributes(typeof(DescriptionAttribute), false); diff --git a/Admin.NET/Admin.NET.Core/Service/DataBase/SysDatabaseService.cs b/Admin.NET/Admin.NET.Core/Service/DataBase/SysDatabaseService.cs index b947d49f..478bb8cb 100644 --- a/Admin.NET/Admin.NET.Core/Service/DataBase/SysDatabaseService.cs +++ b/Admin.NET/Admin.NET.Core/Service/DataBase/SysDatabaseService.cs @@ -1,414 +1,413 @@ -// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 -// -// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 -// -// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! - -using NewLife.Reflection; -using Newtonsoft.Json.Converters; -using Newtonsoft.Json; -using Npgsql; - -namespace Admin.NET.Core.Service; - -/// -/// 系统数据库管理服务 🧩 -/// -[ApiDescriptionSettings(Order = 250, Description = "数据库管理")] -public class SysDatabaseService : IDynamicApiController, ITransient -{ - private readonly ISqlSugarClient _db; - private readonly IViewEngine _viewEngine; - private readonly CodeGenOptions _codeGenOptions; - - public SysDatabaseService(ISqlSugarClient db, - IViewEngine viewEngine, - IOptions codeGenOptions) - { - _db = db; - _viewEngine = viewEngine; - _codeGenOptions = codeGenOptions.Value; - } - - /// - /// 获取库列表 🔖 - /// - /// - [DisplayName("获取库列表")] - public List GetList() - { - return App.GetOptions().ConnectionConfigs.Select(u => u.ConfigId.ToString()).ToList(); - } - - /// - /// 获取可视化库表结构 🔖 - /// - /// - [DisplayName("获取可视化库表结构")] - public VisualDbTable GetVisualDbTable() - { - var visualTableList = new List(); - var visualColumnList = new List(); - var columnRelationList = new List(); - var dbOptions = App.GetOptions().ConnectionConfigs.First(u => u.ConfigId.ToString() == SqlSugarConst.MainConfigId); - - // 遍历所有实体获取所有库表结构 - var random = new Random(); - var entityTypes = App.EffectiveTypes.Where(u => !u.IsInterface && !u.IsAbstract && u.IsClass && u.IsDefined(typeof(SugarTable), false)).ToList(); - foreach (var entityType in entityTypes) - { - var entityInfo = _db.EntityMaintenance.GetEntityInfoNoCache(entityType); - - var visualTable = new VisualTable - { - TableName = entityInfo.DbTableName, - TableComents = entityInfo.TableDescription + entityInfo.DbTableName, - X = random.Next(5000), - Y = random.Next(5000) - }; - visualTableList.Add(visualTable); - - foreach (EntityColumnInfo columnInfo in entityInfo.Columns) - { - var visualColumn = new VisualColumn - { - TableName = columnInfo.DbTableName, - ColumnName = dbOptions.DbSettings.EnableUnderLine ? UtilMethods.ToUnderLine(columnInfo.DbColumnName) : columnInfo.DbColumnName, - DataType = columnInfo.PropertyInfo.PropertyType.Name, - DataLength = columnInfo.Length.ToString(), - ColumnDescription = columnInfo.ColumnDescription, - }; - visualColumnList.Add(visualColumn); - - // 根据导航配置获取表之间关联关系 - if (columnInfo.Navigat != null) - { - var name1 = columnInfo.Navigat.GetName(); - var name2 = columnInfo.Navigat.GetName2(); - var targetColumnName = string.IsNullOrEmpty(name2) ? "Id" : name2; - var relation = new ColumnRelation - { - SourceTableName = columnInfo.DbTableName, - SourceColumnName = dbOptions.DbSettings.EnableUnderLine ? UtilMethods.ToUnderLine(name1) : name1, - Type = columnInfo.Navigat.GetNavigateType() == NavigateType.OneToOne ? "ONE_TO_ONE" : "ONE_TO_MANY", - TargetTableName = dbOptions.DbSettings.EnableUnderLine ? UtilMethods.ToUnderLine(columnInfo.DbColumnName) : columnInfo.DbColumnName, - TargetColumnName = dbOptions.DbSettings.EnableUnderLine ? UtilMethods.ToUnderLine(targetColumnName) : targetColumnName - }; - columnRelationList.Add(relation); - } - } - } - - return new VisualDbTable { VisualTableList = visualTableList, VisualColumnList = visualColumnList, ColumnRelationList = columnRelationList }; - } - - /// - /// 获取字段列表 🔖 - /// - /// 表名 - /// ConfigId - /// - [DisplayName("获取字段列表")] - public List GetColumnList(string tableName, string configId = SqlSugarConst.MainConfigId) - { - var db = _db.AsTenant().GetConnectionScope(configId); - if (string.IsNullOrWhiteSpace(tableName)) - return new List(); - - return db.DbMaintenance.GetColumnInfosByTableName(tableName, false).Adapt>(); - } - - /// - /// 获取数据库数据类型列表 🔖 - /// - /// - /// - [DisplayName("获取数据库数据类型列表")] - public List GetDbTypeList(string configId = SqlSugarConst.MainConfigId) - { - var db = _db.AsTenant().GetConnectionScope(configId); - return db.DbMaintenance.GetDbTypes().OrderBy(u => u).ToList(); - } - - /// - /// 增加列 🔖 - /// - /// - [ApiDescriptionSettings(Name = "AddColumn"), HttpPost] - [DisplayName("增加列")] - public void AddColumn(DbColumnInput input) - { - var column = new DbColumnInfo - { - ColumnDescription = input.ColumnDescription, - DbColumnName = input.DbColumnName, - IsIdentity = input.IsIdentity == 1, - IsNullable = input.IsNullable == 1, - IsPrimarykey = input.IsPrimarykey == 1, - Length = input.Length, - DecimalDigits = input.DecimalDigits, - DataType = input.DataType - }; - var db = _db.AsTenant().GetConnectionScope(input.ConfigId); - db.DbMaintenance.AddColumn(input.TableName, column); - db.DbMaintenance.AddColumnRemark(input.DbColumnName, input.TableName, input.ColumnDescription); - if (column.IsPrimarykey) - db.DbMaintenance.AddPrimaryKey(input.TableName, input.DbColumnName); - } - - /// - /// 删除列 🔖 - /// - /// - [ApiDescriptionSettings(Name = "DeleteColumn"), HttpPost] - [DisplayName("删除列")] - public void DeleteColumn(DeleteDbColumnInput input) - { - var db = _db.AsTenant().GetConnectionScope(input.ConfigId); - db.DbMaintenance.DropColumn(input.TableName, input.DbColumnName); - } - - /// - /// 编辑列 🔖 - /// - /// - [ApiDescriptionSettings(Name = "UpdateColumn"), HttpPost] - [DisplayName("编辑列")] - public void UpdateColumn(UpdateDbColumnInput input) - { - var db = _db.AsTenant().GetConnectionScope(input.ConfigId); - db.DbMaintenance.RenameColumn(input.TableName, input.OldColumnName, input.ColumnName); - if (db.DbMaintenance.IsAnyColumnRemark(input.ColumnName, input.TableName)) - db.DbMaintenance.DeleteColumnRemark(input.ColumnName, input.TableName); - db.DbMaintenance.AddColumnRemark(input.ColumnName, input.TableName, string.IsNullOrWhiteSpace(input.Description) ? input.ColumnName : input.Description); - } - - /// - /// 获取表列表 🔖 - /// - /// ConfigId - /// - [DisplayName("获取表列表")] - public List GetTableList(string configId = SqlSugarConst.MainConfigId) - { - var db = _db.AsTenant().GetConnectionScope(configId); - return db.DbMaintenance.GetTableInfoList(false); - } - - /// - /// 增加表 🔖 - /// - /// - [ApiDescriptionSettings(Name = "AddTable"), HttpPost] - [DisplayName("增加表")] - public void AddTable(DbTableInput input) - { - if (input.DbColumnInfoList == null || !input.DbColumnInfoList.Any()) - throw Oops.Oh(ErrorCodeEnum.db1000); - - if (input.DbColumnInfoList.GroupBy(u => u.DbColumnName).Any(u => u.Count() > 1)) - throw Oops.Oh(ErrorCodeEnum.db1002); - - var config = App.GetOptions().ConnectionConfigs.FirstOrDefault(u => u.ConfigId.ToString() == input.ConfigId); - var db = _db.AsTenant().GetConnectionScope(input.ConfigId); - var typeBuilder = db.DynamicBuilder().CreateClass(input.TableName, new SugarTable() { TableName = input.TableName, TableDescription = input.Description }); - input.DbColumnInfoList.ForEach(u => - { - var dbColumnName = config!.DbSettings.EnableUnderLine ? UtilMethods.ToUnderLine(u.DbColumnName.Trim()) : u.DbColumnName.Trim(); - // 虚拟类都默认string类型,具体以列数据类型为准 - typeBuilder.CreateProperty(dbColumnName, typeof(string), new SugarColumn() - { - IsPrimaryKey = u.IsPrimarykey == 1, - IsIdentity = u.IsIdentity == 1, - ColumnDataType = u.DataType, - Length = u.Length, - IsNullable = u.IsNullable == 1, - DecimalDigits = u.DecimalDigits, - ColumnDescription = u.ColumnDescription, - }); - }); - db.CodeFirst.InitTables(typeBuilder.BuilderType()); - } - - /// - /// 删除表 🔖 - /// - /// - [ApiDescriptionSettings(Name = "DeleteTable"), HttpPost] - [DisplayName("删除表")] - public void DeleteTable(DeleteDbTableInput input) - { - var db = _db.AsTenant().GetConnectionScope(input.ConfigId); - db.DbMaintenance.DropTable(input.TableName); - } - - /// - /// 编辑表 🔖 - /// - /// - [ApiDescriptionSettings(Name = "UpdateTable"), HttpPost] - [DisplayName("编辑表")] - public void UpdateTable(UpdateDbTableInput input) - { - var db = _db.AsTenant().GetConnectionScope(input.ConfigId); - db.DbMaintenance.RenameTable(input.OldTableName, input.TableName); - try - { - if (db.DbMaintenance.IsAnyTableRemark(input.TableName)) - db.DbMaintenance.DeleteTableRemark(input.TableName); - - if (!string.IsNullOrWhiteSpace(input.Description)) - db.DbMaintenance.AddTableRemark(input.TableName, input.Description); - } - catch (NotSupportedException ex) - { - throw Oops.Oh(ex.ToString()); - } - } - - /// - /// 创建实体 🔖 - /// - /// - [ApiDescriptionSettings(Name = "CreateEntity"), HttpPost] - [DisplayName("创建实体")] - public void CreateEntity(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 ? CodeGenUtil.CamelColumnName(input.TableName, null) : input.TableName) : input.EntityName; - string[] dbColumnNames = Array.Empty(); - // 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 => - { - u.PropertyName = config.DbSettings.EnableUnderLine ? CodeGenUtil.CamelColumnName(u.DbColumnName, dbColumnNames) : u.DbColumnName; // 转下划线后的列名需要再转回来 - u.DataType = CodeGenUtil.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", - input.TableName, - input.EntityName, - BaseClassName = string.IsNullOrWhiteSpace(input.BaseClassName) ? "" : $": {input.BaseClassName}", - input.ConfigId, - Description = string.IsNullOrWhiteSpace(dbTableInfo.Description) ? input.EntityName + "业务表" : dbTableInfo.Description, - TableField = dbColumnInfos - }); - var targetPath = GetEntityTargetPath(input); - File.WriteAllText(targetPath, tResult, Encoding.UTF8); - } - - /// - /// 创建种子数据 🔖 - /// - /// - [ApiDescriptionSettings(Name = "CreateSeedData"), HttpPost] - [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); - // 优先用创建时间排序 - DbColumnInfo orderField = dbColumnInfos.FirstOrDefault(u => u.DbColumnName.ToLower() == "create_time" || u.DbColumnName.ToLower() == "createtime"); - if (orderField != null) query = query.OrderBy(orderField.DbColumnName); +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using Npgsql; + +namespace Admin.NET.Core.Service; + +/// +/// 系统数据库管理服务 🧩 +/// +[ApiDescriptionSettings(Order = 250, Description = "数据库管理")] +public class SysDatabaseService : IDynamicApiController, ITransient +{ + private readonly ISqlSugarClient _db; + private readonly IViewEngine _viewEngine; + private readonly CodeGenOptions _codeGenOptions; + + public SysDatabaseService(ISqlSugarClient db, + IViewEngine viewEngine, + IOptions codeGenOptions) + { + _db = db; + _viewEngine = viewEngine; + _codeGenOptions = codeGenOptions.Value; + } + + /// + /// 获取库列表 🔖 + /// + /// + [DisplayName("获取库列表")] + public List GetList() + { + return App.GetOptions().ConnectionConfigs.Select(u => u.ConfigId.ToString()).ToList(); + } + + /// + /// 获取可视化库表结构 🔖 + /// + /// + [DisplayName("获取可视化库表结构")] + public VisualDbTable GetVisualDbTable() + { + var visualTableList = new List(); + var visualColumnList = new List(); + var columnRelationList = new List(); + var dbOptions = App.GetOptions().ConnectionConfigs.First(u => u.ConfigId.ToString() == SqlSugarConst.MainConfigId); + + // 遍历所有实体获取所有库表结构 + var random = new Random(); + var entityTypes = App.EffectiveTypes.Where(u => !u.IsInterface && !u.IsAbstract && u.IsClass && u.IsDefined(typeof(SugarTable), false)).ToList(); + foreach (var entityType in entityTypes) + { + var entityInfo = _db.EntityMaintenance.GetEntityInfoNoCache(entityType); + + var visualTable = new VisualTable + { + TableName = entityInfo.DbTableName, + TableComents = entityInfo.TableDescription + entityInfo.DbTableName, + X = random.Next(5000), + Y = random.Next(5000) + }; + visualTableList.Add(visualTable); + + foreach (EntityColumnInfo columnInfo in entityInfo.Columns) + { + var visualColumn = new VisualColumn + { + TableName = columnInfo.DbTableName, + ColumnName = dbOptions.DbSettings.EnableUnderLine ? UtilMethods.ToUnderLine(columnInfo.DbColumnName) : columnInfo.DbColumnName, + DataType = columnInfo.PropertyInfo.PropertyType.Name, + DataLength = columnInfo.Length.ToString(), + ColumnDescription = columnInfo.ColumnDescription, + }; + visualColumnList.Add(visualColumn); + + // 根据导航配置获取表之间关联关系 + if (columnInfo.Navigat != null) + { + var name1 = columnInfo.Navigat.GetName(); + var name2 = columnInfo.Navigat.GetName2(); + var targetColumnName = string.IsNullOrEmpty(name2) ? "Id" : name2; + var relation = new ColumnRelation + { + SourceTableName = columnInfo.DbTableName, + SourceColumnName = dbOptions.DbSettings.EnableUnderLine ? UtilMethods.ToUnderLine(name1) : name1, + Type = columnInfo.Navigat.GetNavigateType() == NavigateType.OneToOne ? "ONE_TO_ONE" : "ONE_TO_MANY", + TargetTableName = dbOptions.DbSettings.EnableUnderLine ? UtilMethods.ToUnderLine(columnInfo.DbColumnName) : columnInfo.DbColumnName, + TargetColumnName = dbOptions.DbSettings.EnableUnderLine ? UtilMethods.ToUnderLine(targetColumnName) : targetColumnName + }; + columnRelationList.Add(relation); + } + } + } + + return new VisualDbTable { VisualTableList = visualTableList, VisualColumnList = visualColumnList, ColumnRelationList = columnRelationList }; + } + + /// + /// 获取字段列表 🔖 + /// + /// 表名 + /// ConfigId + /// + [DisplayName("获取字段列表")] + public List GetColumnList(string tableName, string configId = SqlSugarConst.MainConfigId) + { + var db = _db.AsTenant().GetConnectionScope(configId); + if (string.IsNullOrWhiteSpace(tableName)) + return new List(); + + return db.DbMaintenance.GetColumnInfosByTableName(tableName, false).Adapt>(); + } + + /// + /// 获取数据库数据类型列表 🔖 + /// + /// + /// + [DisplayName("获取数据库数据类型列表")] + public List GetDbTypeList(string configId = SqlSugarConst.MainConfigId) + { + var db = _db.AsTenant().GetConnectionScope(configId); + return db.DbMaintenance.GetDbTypes().OrderBy(u => u).ToList(); + } + + /// + /// 增加列 🔖 + /// + /// + [ApiDescriptionSettings(Name = "AddColumn"), HttpPost] + [DisplayName("增加列")] + public void AddColumn(DbColumnInput input) + { + var column = new DbColumnInfo + { + ColumnDescription = input.ColumnDescription, + DbColumnName = input.DbColumnName, + IsIdentity = input.IsIdentity == 1, + IsNullable = input.IsNullable == 1, + IsPrimarykey = input.IsPrimarykey == 1, + Length = input.Length, + DecimalDigits = input.DecimalDigits, + DataType = input.DataType + }; + var db = _db.AsTenant().GetConnectionScope(input.ConfigId); + db.DbMaintenance.AddColumn(input.TableName, column); + db.DbMaintenance.AddColumnRemark(input.DbColumnName, input.TableName, input.ColumnDescription); + if (column.IsPrimarykey) + db.DbMaintenance.AddPrimaryKey(input.TableName, input.DbColumnName); + } + + /// + /// 删除列 🔖 + /// + /// + [ApiDescriptionSettings(Name = "DeleteColumn"), HttpPost] + [DisplayName("删除列")] + public void DeleteColumn(DeleteDbColumnInput input) + { + var db = _db.AsTenant().GetConnectionScope(input.ConfigId); + db.DbMaintenance.DropColumn(input.TableName, input.DbColumnName); + } + + /// + /// 编辑列 🔖 + /// + /// + [ApiDescriptionSettings(Name = "UpdateColumn"), HttpPost] + [DisplayName("编辑列")] + public void UpdateColumn(UpdateDbColumnInput input) + { + var db = _db.AsTenant().GetConnectionScope(input.ConfigId); + db.DbMaintenance.RenameColumn(input.TableName, input.OldColumnName, input.ColumnName); + if (db.DbMaintenance.IsAnyColumnRemark(input.ColumnName, input.TableName)) + db.DbMaintenance.DeleteColumnRemark(input.ColumnName, input.TableName); + db.DbMaintenance.AddColumnRemark(input.ColumnName, input.TableName, string.IsNullOrWhiteSpace(input.Description) ? input.ColumnName : input.Description); + } + + /// + /// 获取表列表 🔖 + /// + /// ConfigId + /// + [DisplayName("获取表列表")] + public List GetTableList(string configId = SqlSugarConst.MainConfigId) + { + var db = _db.AsTenant().GetConnectionScope(configId); + return db.DbMaintenance.GetTableInfoList(false); + } + + /// + /// 增加表 🔖 + /// + /// + [ApiDescriptionSettings(Name = "AddTable"), HttpPost] + [DisplayName("增加表")] + public void AddTable(DbTableInput input) + { + if (input.DbColumnInfoList == null || !input.DbColumnInfoList.Any()) + throw Oops.Oh(ErrorCodeEnum.db1000); + + if (input.DbColumnInfoList.GroupBy(u => u.DbColumnName).Any(u => u.Count() > 1)) + throw Oops.Oh(ErrorCodeEnum.db1002); + + var config = App.GetOptions().ConnectionConfigs.FirstOrDefault(u => u.ConfigId.ToString() == input.ConfigId); + var db = _db.AsTenant().GetConnectionScope(input.ConfigId); + var typeBuilder = db.DynamicBuilder().CreateClass(input.TableName, new SugarTable() { TableName = input.TableName, TableDescription = input.Description }); + input.DbColumnInfoList.ForEach(u => + { + var dbColumnName = config!.DbSettings.EnableUnderLine ? UtilMethods.ToUnderLine(u.DbColumnName.Trim()) : u.DbColumnName.Trim(); + // 虚拟类都默认string类型,具体以列数据类型为准 + typeBuilder.CreateProperty(dbColumnName, typeof(string), new SugarColumn() + { + IsPrimaryKey = u.IsPrimarykey == 1, + IsIdentity = u.IsIdentity == 1, + ColumnDataType = u.DataType, + Length = u.Length, + IsNullable = u.IsNullable == 1, + DecimalDigits = u.DecimalDigits, + ColumnDescription = u.ColumnDescription, + }); + }); + db.CodeFirst.InitTables(typeBuilder.BuilderType()); + } + + /// + /// 删除表 🔖 + /// + /// + [ApiDescriptionSettings(Name = "DeleteTable"), HttpPost] + [DisplayName("删除表")] + public void DeleteTable(DeleteDbTableInput input) + { + var db = _db.AsTenant().GetConnectionScope(input.ConfigId); + db.DbMaintenance.DropTable(input.TableName); + } + + /// + /// 编辑表 🔖 + /// + /// + [ApiDescriptionSettings(Name = "UpdateTable"), HttpPost] + [DisplayName("编辑表")] + public void UpdateTable(UpdateDbTableInput input) + { + var db = _db.AsTenant().GetConnectionScope(input.ConfigId); + db.DbMaintenance.RenameTable(input.OldTableName, input.TableName); + try + { + if (db.DbMaintenance.IsAnyTableRemark(input.TableName)) + db.DbMaintenance.DeleteTableRemark(input.TableName); + + if (!string.IsNullOrWhiteSpace(input.Description)) + db.DbMaintenance.AddTableRemark(input.TableName, input.Description); + } + catch (NotSupportedException ex) + { + throw Oops.Oh(ex.ToString()); + } + } + + /// + /// 创建实体 🔖 + /// + /// + [ApiDescriptionSettings(Name = "CreateEntity"), HttpPost] + [DisplayName("创建实体")] + public void CreateEntity(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 ? CodeGenUtil.CamelColumnName(input.TableName, null) : input.TableName) : input.EntityName; + string[] dbColumnNames = Array.Empty(); + // 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 => + { + u.PropertyName = config.DbSettings.EnableUnderLine ? CodeGenUtil.CamelColumnName(u.DbColumnName, dbColumnNames) : u.DbColumnName; // 转下划线后的列名需要再转回来 + u.DataType = CodeGenUtil.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", + input.TableName, + input.EntityName, + BaseClassName = string.IsNullOrWhiteSpace(input.BaseClassName) ? "" : $": {input.BaseClassName}", + input.ConfigId, + Description = string.IsNullOrWhiteSpace(dbTableInfo.Description) ? input.EntityName + "业务表" : dbTableInfo.Description, + TableField = dbColumnInfos + }); + var targetPath = GetEntityTargetPath(input); + File.WriteAllText(targetPath, tResult, Encoding.UTF8); + } + + /// + /// 创建种子数据 🔖 + /// + /// + [ApiDescriptionSettings(Name = "CreateSeedData"), HttpPost] + [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); + // 优先用创建时间排序 + 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.Any()) - { - // 获取实体类型-所有种数据数据类型 - 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 = App.EffectiveTypes - .Where(u => !u.IsInterface && !u.IsAbstract && u.IsClass && u.GetInterfaces().Any( - i => i.HasImplementedRawGeneric(typeof(ISqlSugarEntitySeedData<>)) && i.GenericTypeArguments[0] == entityTypes[0] - ) - ) - .ToList(); - // 可能会重名的种子数据不作为过滤项 - 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 = new(); - 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); - } - } - } - } + 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.Any()) + { + // 获取实体类型-所有种数据数据类型 + 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 = App.EffectiveTypes + .Where(u => !u.IsInterface && !u.IsAbstract && u.IsClass && u.GetInterfaces().Any( + i => i.HasImplementedRawGeneric(typeof(ISqlSugarEntitySeedData<>)) && i.GenericTypeArguments[0] == entityTypes[0] + ) + ) + .ToList(); + // 可能会重名的种子数据不作为过滤项 + 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 = new(); + 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 || + var jsonIgnoreProperties = entityType.GetProperties().Where(p => (p.GetAttribute() != null || p.GetAttribute() != null) && p.GetAttribute() != null).ToList(); var jsonIgnoreInfo = new List>(); if (jsonIgnoreProperties.Count > 0) @@ -434,206 +433,206 @@ public class SysDatabaseService : IDynamicApiController, ITransient 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 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 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.AddUsing("System.Collections.Generic"); - builder.AddUsing("System.Linq"); - }); - - var targetPath = GetSeedDataTargetPath(input); - await File.WriteAllTextAsync(targetPath, tResult, Encoding.UTF8); - } - - /// - /// 获取库表信息 - /// - /// - private async Task> GetEntityInfos() - { - var entityInfos = new List(); - - var type = typeof(SugarTable); - var types = new List(); - if (_codeGenOptions.EntityAssemblyNames != null) - { - foreach (var asm in _codeGenOptions.EntityAssemblyNames.Select(Assembly.Load)) - { - types.AddRange(asm.GetExportedTypes().ToList()); - } - } - - Type[] cosType = types.Where(u => IsMyAttribute(Attribute.GetCustomAttributes(u, true))).ToArray(); - foreach (var c in cosType) - { - var sugarAttribute = c.GetCustomAttributes(type, true)?.FirstOrDefault(); - - var des = c.GetCustomAttributes(typeof(DescriptionAttribute), true); - var description = ""; - if (des.Length > 0) - { - description = ((DescriptionAttribute)des[0]).Description; - } - entityInfos.Add(new EntityInfo() - { - EntityName = c.Name, - DbTableName = sugarAttribute == null ? c.Name : ((SugarTable)sugarAttribute).TableName, - TableDescription = description, - Type = c - }); - } - return await Task.FromResult(entityInfos); - - bool IsMyAttribute(Attribute[] o) - { - return o.Any(a => a.GetType() == type); - } - } - - /// - /// 获取实体模板文件路径 - /// - /// - private static string GetEntityTemplatePath() - { - var templatePath = Path.Combine(App.WebHostEnvironment.WebRootPath, "template"); - return Path.Combine(templatePath, "Entity.cs.vm"); - } - - /// - /// 获取种子数据模板文件路径 - /// - /// - private static string GetSeedDataTemplatePath() - { - var templatePath = Path.Combine(App.WebHostEnvironment.WebRootPath, "template"); - return Path.Combine(templatePath, "SeedData.cs.vm"); - } - - /// - /// 设置生成实体文件路径 - /// - /// - /// - private static string GetEntityTargetPath(CreateEntityInput input) - { - var backendPath = Path.Combine(new DirectoryInfo(App.WebHostEnvironment.ContentRootPath).Parent.FullName, input.Position, "Entity"); - if (!Directory.Exists(backendPath)) - Directory.CreateDirectory(backendPath); - return Path.Combine(backendPath, input.EntityName + ".cs"); - } - - /// - /// 设置生成种子数据文件路径 - /// - /// - /// - private static string GetSeedDataTargetPath(CreateSeedDataInput input) - { - var backendPath = Path.Combine(new DirectoryInfo(App.WebHostEnvironment.ContentRootPath).Parent.FullName, input.Position, "SeedData"); - if (!Directory.Exists(backendPath)) - Directory.CreateDirectory(backendPath); - return Path.Combine(backendPath, input.SeedDataName + ".cs"); - } - - /// - /// 备份数据库(PostgreSQL)🔖 - /// - /// - [HttpPost, NonUnify] - [DisplayName("备份数据库(PostgreSQL)")] - public async Task BackupDatabase() - { - if (_db.CurrentConnectionConfig.DbType != SqlSugar.DbType.PostgreSQL) - throw Oops.Oh("只支持 PostgreSQL 数据库 😁"); - - var npgsqlConn = new NpgsqlConnectionStringBuilder(_db.CurrentConnectionConfig.ConnectionString); - if (npgsqlConn == null || string.IsNullOrWhiteSpace(npgsqlConn.Host) || string.IsNullOrWhiteSpace(npgsqlConn.Username) || string.IsNullOrWhiteSpace(npgsqlConn.Password) || string.IsNullOrWhiteSpace(npgsqlConn.Database)) - throw Oops.Oh("PostgreSQL 数据库配置错误"); - - // 确保备份目录存在 - var backupDirectory = Path.Combine(Directory.GetCurrentDirectory(), "backups"); - Directory.CreateDirectory(backupDirectory); - - // 构建备份文件名 - string backupFileName = $"backup_{DateTime.Now:yyyyMMddHHmmss}.sql"; - string backupFilePath = Path.Combine(backupDirectory, backupFileName); - - // 启动pg_dump进程进行备份 - // 设置密码:export PGPASSWORD='xxxxxx' - var bash = $"-U {npgsqlConn.Username} -h {npgsqlConn.Host} -p {npgsqlConn.Port} -E UTF8 -F c -b -v -f {backupFilePath} {npgsqlConn.Database}"; - var startInfo = new ProcessStartInfo - { - FileName = "pg_dump", - Arguments = bash, - UseShellExecute = false, - RedirectStandardOutput = true, - RedirectStandardError = true, - CreateNoWindow = true, - EnvironmentVariables = - { - ["PGPASSWORD"] = npgsqlConn.Password - } - }; - - //_logger.LogInformation("备份数据库:pg_dump " + bash); - - //try - //{ - using (var backupProcess = Process.Start(startInfo)) - { - await backupProcess.WaitForExitAsync(); - - //var output = await backupProcess.StandardOutput.ReadToEndAsync(); - //var error = await backupProcess.StandardError.ReadToEndAsync(); - - // 检查备份是否成功 - if (backupProcess.ExitCode != 0) - { - throw Oops.Oh($"备份失败:ExitCode({backupProcess.ExitCode})"); - } - } - - // _logger.LogInformation($"备份成功:{backupFilePath}"); - //} - //catch (Exception ex) - //{ - // _logger.LogError(ex, $"备份失败:"); - // throw; - //} - - // 若备份成功则提供下载链接 - return new FileStreamResult(new FileStream(backupFilePath, FileMode.Open), "application/octet-stream") - { - FileDownloadName = backupFileName - }; - } + var recordList = JsonConvert.SerializeObject(records, Formatting.Indented, timeConverter); + + 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.AddUsing("System.Collections.Generic"); + builder.AddUsing("System.Linq"); + }); + + var targetPath = GetSeedDataTargetPath(input); + await File.WriteAllTextAsync(targetPath, tResult, Encoding.UTF8); + } + + /// + /// 获取库表信息 + /// + /// + private async Task> GetEntityInfos() + { + var entityInfos = new List(); + + var type = typeof(SugarTable); + var types = new List(); + if (_codeGenOptions.EntityAssemblyNames != null) + { + foreach (var asm in _codeGenOptions.EntityAssemblyNames.Select(Assembly.Load)) + { + types.AddRange(asm.GetExportedTypes().ToList()); + } + } + + Type[] cosType = types.Where(u => IsMyAttribute(Attribute.GetCustomAttributes(u, true))).ToArray(); + foreach (var c in cosType) + { + var sugarAttribute = c.GetCustomAttributes(type, true)?.FirstOrDefault(); + + var des = c.GetCustomAttributes(typeof(DescriptionAttribute), true); + var description = ""; + if (des.Length > 0) + { + description = ((DescriptionAttribute)des[0]).Description; + } + entityInfos.Add(new EntityInfo() + { + EntityName = c.Name, + DbTableName = sugarAttribute == null ? c.Name : ((SugarTable)sugarAttribute).TableName, + TableDescription = description, + Type = c + }); + } + return await Task.FromResult(entityInfos); + + bool IsMyAttribute(Attribute[] o) + { + return o.Any(a => a.GetType() == type); + } + } + + /// + /// 获取实体模板文件路径 + /// + /// + private static string GetEntityTemplatePath() + { + var templatePath = Path.Combine(App.WebHostEnvironment.WebRootPath, "template"); + return Path.Combine(templatePath, "Entity.cs.vm"); + } + + /// + /// 获取种子数据模板文件路径 + /// + /// + private static string GetSeedDataTemplatePath() + { + var templatePath = Path.Combine(App.WebHostEnvironment.WebRootPath, "template"); + return Path.Combine(templatePath, "SeedData.cs.vm"); + } + + /// + /// 设置生成实体文件路径 + /// + /// + /// + private static string GetEntityTargetPath(CreateEntityInput input) + { + var backendPath = Path.Combine(new DirectoryInfo(App.WebHostEnvironment.ContentRootPath).Parent.FullName, input.Position, "Entity"); + if (!Directory.Exists(backendPath)) + Directory.CreateDirectory(backendPath); + return Path.Combine(backendPath, input.EntityName + ".cs"); + } + + /// + /// 设置生成种子数据文件路径 + /// + /// + /// + private static string GetSeedDataTargetPath(CreateSeedDataInput input) + { + var backendPath = Path.Combine(new DirectoryInfo(App.WebHostEnvironment.ContentRootPath).Parent.FullName, input.Position, "SeedData"); + if (!Directory.Exists(backendPath)) + Directory.CreateDirectory(backendPath); + return Path.Combine(backendPath, input.SeedDataName + ".cs"); + } + + /// + /// 备份数据库(PostgreSQL)🔖 + /// + /// + [HttpPost, NonUnify] + [DisplayName("备份数据库(PostgreSQL)")] + public async Task BackupDatabase() + { + if (_db.CurrentConnectionConfig.DbType != SqlSugar.DbType.PostgreSQL) + throw Oops.Oh("只支持 PostgreSQL 数据库 😁"); + + var npgsqlConn = new NpgsqlConnectionStringBuilder(_db.CurrentConnectionConfig.ConnectionString); + if (npgsqlConn == null || string.IsNullOrWhiteSpace(npgsqlConn.Host) || string.IsNullOrWhiteSpace(npgsqlConn.Username) || string.IsNullOrWhiteSpace(npgsqlConn.Password) || string.IsNullOrWhiteSpace(npgsqlConn.Database)) + throw Oops.Oh("PostgreSQL 数据库配置错误"); + + // 确保备份目录存在 + var backupDirectory = Path.Combine(Directory.GetCurrentDirectory(), "backups"); + Directory.CreateDirectory(backupDirectory); + + // 构建备份文件名 + string backupFileName = $"backup_{DateTime.Now:yyyyMMddHHmmss}.sql"; + string backupFilePath = Path.Combine(backupDirectory, backupFileName); + + // 启动pg_dump进程进行备份 + // 设置密码:export PGPASSWORD='xxxxxx' + var bash = $"-U {npgsqlConn.Username} -h {npgsqlConn.Host} -p {npgsqlConn.Port} -E UTF8 -F c -b -v -f {backupFilePath} {npgsqlConn.Database}"; + var startInfo = new ProcessStartInfo + { + FileName = "pg_dump", + Arguments = bash, + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true, + EnvironmentVariables = + { + ["PGPASSWORD"] = npgsqlConn.Password + } + }; + + //_logger.LogInformation("备份数据库:pg_dump " + bash); + + //try + //{ + using (var backupProcess = Process.Start(startInfo)) + { + await backupProcess.WaitForExitAsync(); + + //var output = await backupProcess.StandardOutput.ReadToEndAsync(); + //var error = await backupProcess.StandardError.ReadToEndAsync(); + + // 检查备份是否成功 + if (backupProcess.ExitCode != 0) + { + throw Oops.Oh($"备份失败:ExitCode({backupProcess.ExitCode})"); + } + } + + // _logger.LogInformation($"备份成功:{backupFilePath}"); + //} + //catch (Exception ex) + //{ + // _logger.LogError(ex, $"备份失败:"); + // throw; + //} + + // 若备份成功则提供下载链接 + return new FileStreamResult(new FileStream(backupFilePath, FileMode.Open), "application/octet-stream") + { + FileDownloadName = backupFileName + }; + } } \ No newline at end of file