using Newtonsoft.Json; using Newtonsoft.Json.Converters; using Npgsql; namespace Admin.NET.Core.Service; /// /// 系统数据库管理服务 /// [ApiDescriptionSettings(Order = 250)] 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(); } /// /// 获取字段列表 /// /// 表名 /// ConfigId /// [AllowAnonymous] [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); else db.DbMaintenance.AddTableRemark(input.TableName, input.Description); } catch (NotSupportedException) { //Ignore 不支持该方法则不处理 } } /// /// 创建实体 /// /// [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 templatePath = GetEntityTemplatePath(); var targetPath = GetEntityTargetPath(input); var db = _db.AsTenant().GetConnectionScope(input.ConfigId); DbTableInfo dbTableInfo = db.DbMaintenance.GetTableInfoList(false).FirstOrDefault(u => u.Name == input.TableName || u.Name == input.TableName.ToLower()) ?? throw Oops.Oh(ErrorCodeEnum.db1001); List 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 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, dbTableInfo.Description, TableField = dbColumnInfos }); File.WriteAllText(targetPath, tResult, Encoding.UTF8); } /// /// 创建种子数据 /// /// [ApiDescriptionSettings(Name = "CreateSeedData"), HttpPost] [DisplayName("创建种子数据")] public async void 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; } input.EntityName = entityType.Name; input.SeedDataName = entityType.Name + "SeedData"; if (!string.IsNullOrWhiteSpace(input.Suffix)) input.SeedDataName += input.Suffix; var targetPath = GetSeedDataTargetPath(input); // 查询所有数据 var query = db.QueryableByObject(entityType); DbColumnInfo orderField = null; // 排序字段 // 优先用创建时间排序 orderField = dbColumnInfos.Where(u => u.DbColumnName.ToLower() == "create_time" || u.DbColumnName.ToLower() == "createtime").FirstOrDefault(); if (orderField != null) query.OrderBy(orderField.DbColumnName); // 其次用Id排序 orderField = dbColumnInfos.Where(u => u.DbColumnName.ToLower() == "id").FirstOrDefault(); if (orderField != null) query.OrderBy(orderField.DbColumnName); object records = query.ToList(); var timeConverter = new IsoDateTimeConverter { DateTimeFormat = "yyyy-MM-dd HH:mm:ss" }; var recordsJSON = JsonConvert.SerializeObject(records, Formatting.Indented, timeConverter); // 检查有没有 System.Text.Json.Serialization.JsonIgnore 的属性 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 = new(); 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 tContent = File.ReadAllText(templatePath); var data = new { NameSpace = $"{input.Position}.SeedData", EntityNameSpace = entityType.Namespace, input.TableName, input.EntityName, input.SeedDataName, input.ConfigId, tableInfo.Description, JsonIgnoreInfo = jsonIgnoreInfo, RecordsJSON = recordsJSON }; var tResult = _viewEngine.RunCompile(tContent, data, builderAction: builder => { builder.AddAssemblyReferenceByName("System.Linq"); builder.AddAssemblyReferenceByName("System.Collections"); builder.AddUsing("System.Collections.Generic"); builder.AddUsing("System.Linq"); }); File.WriteAllText(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 assemblyName in _codeGenOptions.EntityAssemblyNames) { Assembly asm = Assembly.Load(assemblyName); types.AddRange(asm.GetExportedTypes().ToList()); } } bool IsMyAttribute(Attribute[] o) { foreach (Attribute a in o) { if (a.GetType() == type) return true; } return false; } Type[] cosType = types.Where(o => { return IsMyAttribute(Attribute.GetCustomAttributes(o, 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); } /// /// 获取实体模板文件路径 /// /// 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] 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 }; } }