2026-04-28 10:23:58 +08:00
|
|
|
|
using Dm.util;
|
|
|
|
|
|
using Mapster;
|
|
|
|
|
|
using Microsoft.Data.Sqlite;
|
|
|
|
|
|
using Microsoft.Extensions.Configuration;
|
|
|
|
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
|
|
|
|
using NewLife;
|
|
|
|
|
|
using Prism.Ioc;
|
|
|
|
|
|
using System;
|
|
|
|
|
|
using System.Collections;
|
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
using System.Data;
|
|
|
|
|
|
using System.Diagnostics;
|
2026-05-15 17:30:30 +08:00
|
|
|
|
using System.IO;
|
2026-04-28 10:23:58 +08:00
|
|
|
|
using System.Linq;
|
|
|
|
|
|
using System.Linq.Dynamic.Core;
|
|
|
|
|
|
using System.Reflection;
|
|
|
|
|
|
using System.Text;
|
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
|
using Yitter.IdGenerator;
|
|
|
|
|
|
using YY.Admin.Core;
|
|
|
|
|
|
using YY.Admin.Core.Extension;
|
|
|
|
|
|
using YY.Admin.Core.Option;
|
2026-05-15 17:30:30 +08:00
|
|
|
|
using YY.Admin.Core.SeedData;
|
2026-04-28 10:23:58 +08:00
|
|
|
|
using YY.Admin.Core.Session;
|
2026-05-15 17:30:30 +08:00
|
|
|
|
using YY.Admin.Core.Util;
|
2026-04-28 10:23:58 +08:00
|
|
|
|
using DbType = SqlSugar.DbType;
|
|
|
|
|
|
|
|
|
|
|
|
namespace YY.Admin.Core.SqlSugar
|
|
|
|
|
|
{
|
|
|
|
|
|
public static class SqlSugarSetup
|
|
|
|
|
|
{
|
|
|
|
|
|
// 多租户实例
|
|
|
|
|
|
public static ITenant ITenant { get; set; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 是否正在处理种子数据
|
|
|
|
|
|
private static bool _isHandlingSeedData = false;
|
|
|
|
|
|
public static void AddSqlSugar(this IContainerRegistry containerRegistry,IConfiguration configuration)
|
|
|
|
|
|
{
|
|
|
|
|
|
//var _logger = Container.Resolve<ILoggerService>();
|
|
|
|
|
|
// 直接读取配置
|
|
|
|
|
|
var dbOptions = configuration.GetSection("DbConnection").Get<DbConnectionOptions>();
|
|
|
|
|
|
// 注册雪花Id
|
|
|
|
|
|
var snowIdOpt = new IdGeneratorOptions
|
|
|
|
|
|
{
|
|
|
|
|
|
// 扩展 WorkerId 位数至 12 位 (0-4095)
|
|
|
|
|
|
WorkerIdBitLength = 12,
|
|
|
|
|
|
|
|
|
|
|
|
// 减少序列号位数保持 64 位总长
|
|
|
|
|
|
SeqBitLength = 8,
|
|
|
|
|
|
|
|
|
|
|
|
// 调整时间戳位数 (仍可支持 68 年)
|
|
|
|
|
|
BaseTime = DateTime.Now.AddYears(-30)
|
|
|
|
|
|
};
|
|
|
|
|
|
YitIdHelper.SetIdGenerator(snowIdOpt);
|
|
|
|
|
|
// 自定义 SqlSugar 雪花ID算法
|
|
|
|
|
|
SnowFlakeSingle.WorkId = snowIdOpt.WorkerId;
|
|
|
|
|
|
StaticConfig.CustomSnowFlakeFunc = () =>
|
|
|
|
|
|
{
|
|
|
|
|
|
return YitIdHelper.NextId();
|
|
|
|
|
|
};
|
|
|
|
|
|
// 动态表达式 SqlFunc 支持,https://www.donet5.com/Home/Doc?typeId=2569
|
|
|
|
|
|
StaticConfig.DynamicExpressionParserType = typeof(DynamicExpressionParser);
|
|
|
|
|
|
StaticConfig.DynamicExpressionParsingConfig = new ParsingConfig
|
|
|
|
|
|
{
|
|
|
|
|
|
CustomTypeProvider = new SqlSugarTypeProvider()
|
|
|
|
|
|
};
|
|
|
|
|
|
dbOptions!.ConnectionConfigs = SetDbConfig(dbOptions.ConnectionConfigs);
|
|
|
|
|
|
|
|
|
|
|
|
SqlSugarScope sqlSugar = new(dbOptions.ConnectionConfigs.Cast<ConnectionConfig>().ToList(), db =>
|
|
|
|
|
|
{
|
|
|
|
|
|
foreach(var config in dbOptions.ConnectionConfigs)
|
|
|
|
|
|
{
|
|
|
|
|
|
var dbProvider = db.GetConnectionScope(config.ConfigId);
|
|
|
|
|
|
SetDbAop(dbProvider, config, dbOptions.EnableConsoleSql, dbOptions.SuperAdminIgnoreIDeletedFilter);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
ITenant = sqlSugar;
|
|
|
|
|
|
// 注册为单例服务(Prism 方式)
|
|
|
|
|
|
containerRegistry.RegisterInstance<ISqlSugarClient>(sqlSugar);
|
|
|
|
|
|
foreach (var config in dbOptions.ConnectionConfigs)
|
|
|
|
|
|
{
|
|
|
|
|
|
InitDatabase(sqlSugar, config);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 配置Aop
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="db"></param>
|
|
|
|
|
|
/// <param name="enableConsoleSql"></param>
|
|
|
|
|
|
/// <param name="superAdminIgnoreIDeletedFilter"></param>
|
|
|
|
|
|
public static void SetDbAop(SqlSugarScopeProvider db, DbConnectionConfig config, bool enableConsoleSql, bool superAdminIgnoreIDeletedFilter)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 设置超时时间
|
|
|
|
|
|
db.Ado.CommandTimeOut = 30;
|
|
|
|
|
|
|
|
|
|
|
|
// 打印SQL语句
|
|
|
|
|
|
if (enableConsoleSql)
|
|
|
|
|
|
{
|
|
|
|
|
|
db.Aop.OnLogExecuting = (sql, pars) =>
|
|
|
|
|
|
{
|
|
|
|
|
|
//var log = $"【{DateTime.Now}——执行SQL】\r\n{UtilMethods.GetNativeSql(sql, pars)}\r\n";
|
|
|
|
|
|
var log = $"【{DateTime.Now}——执行SQL】\r\n{UtilMethods.GetSqlString(dbType: config.DbType, sql, pars)}\r\n";
|
|
|
|
|
|
var originColor = Console.ForegroundColor;
|
|
|
|
|
|
if (sql.StartsWith("SELECT", StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
|
Console.ForegroundColor = ConsoleColor.Green;
|
|
|
|
|
|
if (sql.StartsWith("UPDATE", StringComparison.OrdinalIgnoreCase) || sql.StartsWith("INSERT", StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
|
Console.ForegroundColor = ConsoleColor.Yellow;
|
|
|
|
|
|
if (sql.StartsWith("DELETE", StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
|
Console.ForegroundColor = ConsoleColor.Red;
|
|
|
|
|
|
Debug.WriteLine(log);
|
|
|
|
|
|
Console.ForegroundColor = originColor;
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
db.Aop.OnError = ex =>
|
|
|
|
|
|
{
|
|
|
|
|
|
if (ex.Parametres == null) return;
|
|
|
|
|
|
var log = $"【{DateTime.Now}——错误SQL】\r\n{UtilMethods.GetSqlString(config.DbType, ex.Sql, (SugarParameter[])ex.Parametres)}\r\n";
|
|
|
|
|
|
Debug.WriteLine(log);
|
|
|
|
|
|
// logger.Error(log, ex);
|
|
|
|
|
|
};
|
|
|
|
|
|
db.Aop.OnLogExecuted = (sql, pars) =>
|
|
|
|
|
|
{
|
|
|
|
|
|
// 执行时间超过5秒时
|
|
|
|
|
|
if (!(db.Ado.SqlExecutionTime.TotalSeconds > 5)) return;
|
|
|
|
|
|
|
|
|
|
|
|
var fileName = db.Ado.SqlStackTrace.FirstFileName; // 文件名
|
|
|
|
|
|
var fileLine = db.Ado.SqlStackTrace.FirstLine; // 行号
|
|
|
|
|
|
var firstMethodName = db.Ado.SqlStackTrace.FirstMethodName; // 方法名
|
|
|
|
|
|
var log = $"【{DateTime.Now}——超时SQL】\r\n【所在文件名】:{fileName}\r\n【代码行数】:{fileLine}\r\n【方法名】:{firstMethodName}\r\n" + $"【SQL语句】:{UtilMethods.GetNativeSql(sql, pars)}";
|
|
|
|
|
|
Debug.WriteLine(log);
|
|
|
|
|
|
// logger.Warning(log);
|
|
|
|
|
|
};
|
|
|
|
|
|
var currentUser = AppSession.CurrentUser;
|
|
|
|
|
|
|
|
|
|
|
|
var isSuperAdmin = currentUser?.AccountType == AccountTypeEnum.SuperAdmin;
|
|
|
|
|
|
|
|
|
|
|
|
// 配置假删除过滤器,如果当前用户是超级管理员并且允许忽略软删除过滤器则不会应用
|
|
|
|
|
|
if (!isSuperAdmin || !superAdminIgnoreIDeletedFilter)
|
|
|
|
|
|
db.QueryFilter.AddTableFilter<IDeletedFilter>(u => u.IsDelete == false);
|
|
|
|
|
|
|
|
|
|
|
|
// 超级管理员排除其他过滤器
|
|
|
|
|
|
if (isSuperAdmin) return;
|
|
|
|
|
|
|
|
|
|
|
|
// 配置租户过滤器
|
|
|
|
|
|
var tenantId = currentUser?.TenantId ?? 0;
|
|
|
|
|
|
if (tenantId > 0)
|
|
|
|
|
|
db.QueryFilter.AddTableFilter<ITenantIdFilter>(u => u.TenantId == tenantId);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 配置连接属性
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="config"></param>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
public static List<DbConnectionConfig> SetDbConfig(List<DbConnectionConfig> configs)
|
|
|
|
|
|
{
|
|
|
|
|
|
foreach (var config in configs)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (config.DbSettings.EnableConnStringEncrypt)
|
|
|
|
|
|
{
|
|
|
|
|
|
config.ConnectionString = CryptogramUtil.Decrypt(config.ConnectionString);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-18 17:31:18 +08:00
|
|
|
|
// SQLite 相对路径会解析到 LocalApplicationData\Data(见 ResolveSqliteConnectionToAbsolutePath)
|
2026-05-15 17:30:30 +08:00
|
|
|
|
if (config.DbType == DbType.Sqlite && !string.IsNullOrWhiteSpace(config.ConnectionString))
|
|
|
|
|
|
{
|
2026-05-18 17:31:18 +08:00
|
|
|
|
// 安装包随带的 Admin.NET.db / Slave.db 在安装目录;运行时代码只读写可写目录。首启若无用户库则从安装目录复制模板。
|
|
|
|
|
|
TrySeedSqliteFromInstallDirectory(config.ConnectionString);
|
2026-05-15 17:30:30 +08:00
|
|
|
|
config.ConnectionString = ResolveSqliteConnectionToAbsolutePath(config.ConnectionString);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-28 10:23:58 +08:00
|
|
|
|
var configureExternalServices = new ConfigureExternalServices
|
|
|
|
|
|
{
|
|
|
|
|
|
EntityNameService = (type, entity) => // 处理表
|
|
|
|
|
|
{
|
|
|
|
|
|
entity.IsDisabledDelete = true; // 禁止删除非 sqlsugar 创建的列
|
|
|
|
|
|
// 只处理贴了特性[SugarTable]表
|
|
|
|
|
|
if (!type.GetCustomAttributes<SugarTable>().Any())
|
|
|
|
|
|
return;
|
|
|
|
|
|
if (config.DbSettings.EnableUnderLine && !entity.DbTableName.Contains('_'))
|
|
|
|
|
|
entity.DbTableName = UtilMethods.ToUnderLine(entity.DbTableName); // 驼峰转下划线
|
|
|
|
|
|
},
|
|
|
|
|
|
EntityService = (type, column) => // 处理列
|
|
|
|
|
|
{
|
|
|
|
|
|
// 只处理贴了特性[SugarColumn]列
|
|
|
|
|
|
if (!type.GetCustomAttributes<SugarColumn>().Any())
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (new NullabilityInfoContext().Create(type).WriteState is NullabilityState.Nullable)
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
column.IsNullable = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (config.DbSettings.EnableUnderLine && !column.IsIgnore && !column.DbColumnName.Contains('_'))
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
column.DbColumnName = UtilMethods.ToUnderLine(column.DbColumnName); // 驼峰转下划线
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
};
|
|
|
|
|
|
config.ConfigureExternalServices = configureExternalServices;
|
|
|
|
|
|
config.InitKeyType = InitKeyType.Attribute;
|
|
|
|
|
|
config.IsAutoCloseConnection = true;
|
|
|
|
|
|
config.MoreSettings = new ConnMoreSettings
|
|
|
|
|
|
{
|
|
|
|
|
|
IsAutoRemoveDataCache = true, // 启用自动删除缓存,所有增删改会自动调用.RemoveDataCache()
|
|
|
|
|
|
IsAutoDeleteQueryFilter = true, // 启用删除查询过滤器
|
|
|
|
|
|
IsAutoUpdateQueryFilter = true, // 启用更新查询过滤器
|
|
|
|
|
|
SqlServerCodeFirstNvarchar = true // 采用Nvarchar
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 若库类型是人大金仓则默认设置PG模式
|
|
|
|
|
|
if (config.DbType == DbType.Kdbndp)
|
|
|
|
|
|
config.MoreSettings.DatabaseModel = DbType.PostgreSQL; // 配置PG模式主要是兼容系统表差异
|
|
|
|
|
|
|
|
|
|
|
|
// 若库类型是Oracle则默认主键名字和参数名字最大长度
|
|
|
|
|
|
if (config.DbType == DbType.Oracle)
|
|
|
|
|
|
config.MoreSettings.MaxParameterNameLength = 30;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return configs;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 初始化数据库
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="db">SqlSugarScope 实例</param>
|
|
|
|
|
|
/// <param name="config">数据库连接配置</param>
|
|
|
|
|
|
private static void InitDatabase(SqlSugarScope db, DbConnectionConfig config)
|
|
|
|
|
|
{
|
|
|
|
|
|
var dbProvider = db.GetConnectionScope(config.ConfigId);
|
|
|
|
|
|
|
|
|
|
|
|
// 等待数据库连接就绪
|
|
|
|
|
|
WaitForDatabaseReady(dbProvider);
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化数据库
|
|
|
|
|
|
if (config.DbSettings.EnableInitDb)
|
|
|
|
|
|
{
|
|
|
|
|
|
//Log.Information($"初始化数据库 {config.DbType} - {config.ConfigId} - {config.ConnectionString}");
|
|
|
|
|
|
if (config.DbType != DbType.Oracle) dbProvider.DbMaintenance.CreateDatabase();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化表结构
|
|
|
|
|
|
if (config.TableSettings.EnableInitTable)
|
|
|
|
|
|
{
|
|
|
|
|
|
//Log.Information($"初始化表结构 {config.DbType} - {config.ConfigId}");
|
|
|
|
|
|
var entityTypes = GetEntityTypesForInit(config);
|
|
|
|
|
|
InitializeTables(dbProvider, entityTypes, config);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 兼容旧库:曾关闭表初始化或升级前库无 Jeecg 盐列时补齐,避免脱网按 Jeecg 规则验密时读列失败
|
|
|
|
|
|
EnsureSysUserJeecgPasswordSaltColumn(dbProvider, config);
|
2026-05-18 15:55:11 +08:00
|
|
|
|
// 兼容旧库:菜单表增加「桌面默认首页」标记列
|
|
|
|
|
|
EnsureSysMenuDefaultDesktopHomeColumn(dbProvider, config);
|
2026-04-28 10:23:58 +08:00
|
|
|
|
// 兜底:确保 Jeecg 用户同构表存在(登录页“同步 Jeecg 用户”写入此表)
|
|
|
|
|
|
EnsureJeecgSysUserMirrorTable(dbProvider, config);
|
|
|
|
|
|
// 兜底:确保 Jeecg 数据字典同构表存在(数据字典页面“同步数据字典”写入此表)
|
|
|
|
|
|
EnsureJeecgSysDictItemMirrorTable(dbProvider, config);
|
|
|
|
|
|
//// 初始化视图
|
|
|
|
|
|
//if (config.DbSettings.EnableInitView) InitView(dbProvider);
|
|
|
|
|
|
// 初始化种子数据
|
|
|
|
|
|
if (config.SeedSettings.EnableInitSeed) InitSeedData(db, config);
|
2026-05-15 17:30:30 +08:00
|
|
|
|
// 关闭全量种子时首启可能无菜单数据;补一份基准菜单,避免打包版本左侧空白
|
|
|
|
|
|
EnsureBaselineSysMenuSeed(db, config);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2026-05-18 17:31:18 +08:00
|
|
|
|
/// 首次启动时:若用户数据目录中尚无该 SQLite 文件,且安装目录(exe 旁)存在同名库,则复制一份作为初始模板。
|
|
|
|
|
|
/// 否则安装包内随带的菜单/配置永远不会被用到(实际始终读写 %LocalAppData%\YY.Admin\Data\)。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private static void TrySeedSqliteFromInstallDirectory(string connectionString)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
var builder = new SqliteConnectionStringBuilder(connectionString);
|
|
|
|
|
|
var ds = builder.DataSource;
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(ds) || Path.IsPathFullyQualified(ds))
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
var fileName = Path.GetFileName(ds.TrimStart('.', '/', '\\'));
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(fileName))
|
|
|
|
|
|
fileName = "Admin.NET.db";
|
|
|
|
|
|
|
|
|
|
|
|
var dataDir = AppWritablePaths.EnsureDirectoryExists(AppWritablePaths.DataDirectory);
|
|
|
|
|
|
var targetPath = Path.Combine(dataDir, fileName);
|
|
|
|
|
|
if (File.Exists(targetPath))
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
var bundledPath = Path.Combine(AppContext.BaseDirectory, fileName);
|
|
|
|
|
|
if (!File.Exists(bundledPath))
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
File.Copy(bundledPath, targetPath, overwrite: false);
|
|
|
|
|
|
}
|
|
|
|
|
|
catch
|
|
|
|
|
|
{
|
|
|
|
|
|
// 忽略:后续仍可按空库走建表/基准种子
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 将 SQLite 连接串中的相对 DataSource 解析为 %LocalAppData%\YY.Admin\Data\ 下绝对路径(适配 Program Files 只读安装)。
|
2026-05-15 17:30:30 +08:00
|
|
|
|
/// </summary>
|
|
|
|
|
|
private static string ResolveSqliteConnectionToAbsolutePath(string connectionString)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
var builder = new SqliteConnectionStringBuilder(connectionString);
|
|
|
|
|
|
var ds = builder.DataSource;
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(ds))
|
|
|
|
|
|
{
|
|
|
|
|
|
return connectionString;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (Path.IsPathFullyQualified(ds))
|
|
|
|
|
|
{
|
|
|
|
|
|
return connectionString;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-18 17:31:18 +08:00
|
|
|
|
// Program Files 等安装目录对普通用户只读;运行期 SQLite 放到 LocalApplicationData(首启可由 TrySeedSqliteFromInstallDirectory 从安装目录拷贝模板)
|
2026-05-15 17:30:30 +08:00
|
|
|
|
var dataDir = AppWritablePaths.EnsureDirectoryExists(AppWritablePaths.DataDirectory);
|
|
|
|
|
|
var fileName = Path.GetFileName(ds.TrimStart('.', '/', '\\'));
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(fileName))
|
|
|
|
|
|
{
|
|
|
|
|
|
fileName = "Admin.NET.db";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
builder.DataSource = Path.Combine(dataDir, fileName);
|
|
|
|
|
|
return builder.ConnectionString;
|
|
|
|
|
|
}
|
|
|
|
|
|
catch
|
|
|
|
|
|
{
|
|
|
|
|
|
return connectionString;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 未启用 EnableInitSeed 且 sys_menu 为空时,写入 SysMenu 种子,避免发布后界面无功能菜单。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private static void EnsureBaselineSysMenuSeed(SqlSugarScope db, DbConnectionConfig config)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
if (config.SeedSettings.EnableInitSeed)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!string.Equals(config.ConfigId.ToString(), SqlSugarConst.MainConfigId, StringComparison.Ordinal))
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (config.DbType != DbType.Sqlite)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var dbProvider = db.GetConnectionScope(config.ConfigId);
|
|
|
|
|
|
var menuEntityInfo = dbProvider.EntityMaintenance.GetEntityInfo(typeof(SysMenu));
|
|
|
|
|
|
if (!dbProvider.DbMaintenance.IsAnyTable(menuEntityInfo.DbTableName, false))
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var cnt = dbProvider.Queryable<SysMenu>().ClearFilter().Count();
|
|
|
|
|
|
if (cnt > 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var seedType = typeof(SysMenuSeedData);
|
|
|
|
|
|
var seedData = GetSeedData(seedType)?.ToList();
|
|
|
|
|
|
if (seedData == null || seedData.Count == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
AdjustSeedDataIds(seedData, config);
|
|
|
|
|
|
var progress = 0;
|
|
|
|
|
|
InsertOrUpdateSeedData(dbProvider, seedType, typeof(SysMenu), seedData, config, ref progress, 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
catch
|
|
|
|
|
|
{
|
|
|
|
|
|
// 启动阶段不因兜底种子失败而阻断
|
|
|
|
|
|
}
|
2026-04-28 10:23:58 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2026-05-18 15:55:11 +08:00
|
|
|
|
/// 若 sys_menu 缺少桌面默认首页标记列,则 ALTER 补齐(与实体 SysMenu.IsDefaultDesktopHome 一致)
|
2026-04-28 10:23:58 +08:00
|
|
|
|
/// </summary>
|
2026-05-18 15:55:11 +08:00
|
|
|
|
private static void EnsureSysMenuDefaultDesktopHomeColumn(SqlSugarScopeProvider dbProvider, DbConnectionConfig config)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
var entityInfo = dbProvider.EntityMaintenance.GetEntityInfo(typeof(SysMenu));
|
|
|
|
|
|
var tableName = entityInfo.DbTableName;
|
|
|
|
|
|
if (!dbProvider.DbMaintenance.IsAnyTable(tableName, false))
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var col = entityInfo.Columns.FirstOrDefault(c => c.PropertyName == nameof(SysMenu.IsDefaultDesktopHome));
|
|
|
|
|
|
if (col == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var columns = dbProvider.DbMaintenance.GetColumnInfosByTableName(tableName, false);
|
|
|
|
|
|
if (columns != null &&
|
|
|
|
|
|
columns.Any(c => string.Equals(c.DbColumnName, col.DbColumnName, StringComparison.OrdinalIgnoreCase)))
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var cName = col.DbColumnName;
|
|
|
|
|
|
var sql = config.DbType switch
|
|
|
|
|
|
{
|
|
|
|
|
|
DbType.Sqlite => $"ALTER TABLE {tableName} ADD COLUMN {cName} INTEGER NOT NULL DEFAULT 0;",
|
|
|
|
|
|
DbType.MySql => $"ALTER TABLE `{tableName}` ADD COLUMN `{cName}` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '桌面端默认首页';",
|
|
|
|
|
|
DbType.PostgreSQL => $"ALTER TABLE \"{tableName}\" ADD COLUMN IF NOT EXISTS {cName} BOOLEAN NOT NULL DEFAULT FALSE;",
|
|
|
|
|
|
DbType.SqlServer => $"ALTER TABLE [{tableName}] ADD [{cName}] BIT NOT NULL DEFAULT 0;",
|
|
|
|
|
|
DbType.Kdbndp => $"ALTER TABLE \"{tableName}\" ADD COLUMN IF NOT EXISTS {cName} BOOLEAN NOT NULL DEFAULT FALSE;",
|
|
|
|
|
|
DbType.Dm => $"ALTER TABLE {tableName} ADD {cName} NUMBER(1) DEFAULT 0 NOT NULL;",
|
|
|
|
|
|
_ => (string?)null
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
if (string.IsNullOrEmpty(sql))
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Retry(() => dbProvider.Ado.ExecuteCommand(sql), maxRetry: 3, retryIntervalMs: 1000);
|
|
|
|
|
|
}
|
|
|
|
|
|
catch
|
|
|
|
|
|
{
|
|
|
|
|
|
// 无权限、从库只读等场景不阻断启动
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-28 10:23:58 +08:00
|
|
|
|
private static void EnsureSysUserJeecgPasswordSaltColumn(SqlSugarScopeProvider dbProvider, DbConnectionConfig config)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
var tableName = dbProvider.EntityMaintenance.GetEntityInfo(typeof(SysUser)).DbTableName;
|
|
|
|
|
|
if (!dbProvider.DbMaintenance.IsAnyTable(tableName, false))
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var columns = dbProvider.DbMaintenance.GetColumnInfosByTableName(tableName);
|
|
|
|
|
|
if (columns != null && columns.Any(c => string.Equals(c.DbColumnName, "jeecg_password_salt", StringComparison.OrdinalIgnoreCase)))
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var sql = config.DbType switch
|
|
|
|
|
|
{
|
|
|
|
|
|
DbType.Sqlite => $"ALTER TABLE {tableName} ADD COLUMN jeecg_password_salt TEXT NULL;",
|
|
|
|
|
|
DbType.MySql => $"ALTER TABLE `{tableName}` ADD COLUMN `jeecg_password_salt` VARCHAR(64) NULL COMMENT 'Jeecg密码盐';",
|
|
|
|
|
|
DbType.PostgreSQL => $"ALTER TABLE \"{tableName}\" ADD COLUMN IF NOT EXISTS jeecg_password_salt VARCHAR(64) NULL;",
|
|
|
|
|
|
DbType.SqlServer => $"ALTER TABLE [{tableName}] ADD [jeecg_password_salt] NVARCHAR(64) NULL;",
|
|
|
|
|
|
DbType.Kdbndp => $"ALTER TABLE \"{tableName}\" ADD COLUMN IF NOT EXISTS jeecg_password_salt VARCHAR(64) NULL;",
|
|
|
|
|
|
DbType.Dm => $"ALTER TABLE {tableName} ADD jeecg_password_salt VARCHAR(64) NULL;",
|
|
|
|
|
|
_ => (string?)null
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
if (string.IsNullOrEmpty(sql))
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Retry(() => dbProvider.Ado.ExecuteCommand(sql), maxRetry: 3, retryIntervalMs: 1000);
|
|
|
|
|
|
}
|
|
|
|
|
|
catch
|
|
|
|
|
|
{
|
|
|
|
|
|
// 无权限、从库只读等场景不阻断启动
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 若缺少 Jeecg 同构用户表,则启动时强制创建。
|
|
|
|
|
|
/// 说明:用于规避某些环境 AppDomain 扫描不到新实体导致 CodeFirst 漏建的问题。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private static void EnsureJeecgSysUserMirrorTable(SqlSugarScopeProvider dbProvider, DbConnectionConfig config)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
var tableName = dbProvider.EntityMaintenance.GetEntityInfo(typeof(JeecgSysUser)).DbTableName;
|
|
|
|
|
|
if (dbProvider.DbMaintenance.IsAnyTable(tableName, false))
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Retry(() =>
|
|
|
|
|
|
{
|
|
|
|
|
|
dbProvider.CodeFirst.InitTables(typeof(JeecgSysUser));
|
|
|
|
|
|
}, maxRetry: 3, retryIntervalMs: 1000);
|
|
|
|
|
|
|
|
|
|
|
|
// CodeFirst 仍未创建时,针对 SQLite 做 SQL 级兜底,确保表可见
|
|
|
|
|
|
if (!dbProvider.DbMaintenance.IsAnyTable(tableName, false) && config.DbType == DbType.Sqlite)
|
|
|
|
|
|
{
|
|
|
|
|
|
const string createSql = @"
|
|
|
|
|
|
CREATE TABLE IF NOT EXISTS jeecg_sys_user (
|
|
|
|
|
|
id TEXT PRIMARY KEY NOT NULL,
|
|
|
|
|
|
username TEXT NULL,
|
|
|
|
|
|
realname TEXT NULL,
|
|
|
|
|
|
password TEXT NULL,
|
|
|
|
|
|
salt TEXT NULL,
|
|
|
|
|
|
avatar TEXT NULL,
|
|
|
|
|
|
birthday TEXT NULL,
|
|
|
|
|
|
sex INTEGER NULL,
|
|
|
|
|
|
email TEXT NULL,
|
|
|
|
|
|
phone TEXT NULL,
|
|
|
|
|
|
org_code TEXT NULL,
|
|
|
|
|
|
login_tenant_id INTEGER NULL,
|
|
|
|
|
|
status INTEGER NULL,
|
|
|
|
|
|
del_flag INTEGER NULL,
|
|
|
|
|
|
work_no TEXT NULL,
|
|
|
|
|
|
telephone TEXT NULL,
|
|
|
|
|
|
create_by TEXT NULL,
|
|
|
|
|
|
create_time TEXT NULL,
|
|
|
|
|
|
update_by TEXT NULL,
|
|
|
|
|
|
update_time TEXT NULL,
|
|
|
|
|
|
activiti_sync INTEGER NULL,
|
|
|
|
|
|
user_identity INTEGER NULL,
|
|
|
|
|
|
depart_ids TEXT NULL,
|
|
|
|
|
|
client_id TEXT NULL,
|
|
|
|
|
|
bpm_status TEXT NULL,
|
|
|
|
|
|
sign TEXT NULL,
|
|
|
|
|
|
sign_enable INTEGER NULL,
|
|
|
|
|
|
main_dep_post_id TEXT NULL,
|
|
|
|
|
|
position_type TEXT NULL,
|
|
|
|
|
|
last_pwd_update_time TEXT NULL,
|
|
|
|
|
|
sort INTEGER NULL,
|
|
|
|
|
|
iz_hide_contact TEXT NULL
|
|
|
|
|
|
);";
|
|
|
|
|
|
Retry(() => dbProvider.Ado.ExecuteCommand(createSql), maxRetry: 3, retryIntervalMs: 1000);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch
|
|
|
|
|
|
{
|
|
|
|
|
|
// 兜底逻辑不阻断启动
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 若缺少 Jeecg 同构数据字典项表,则启动时强制创建。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private static void EnsureJeecgSysDictItemMirrorTable(SqlSugarScopeProvider dbProvider, DbConnectionConfig config)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
var tableName = dbProvider.EntityMaintenance.GetEntityInfo(typeof(JeecgSysDictItem)).DbTableName;
|
|
|
|
|
|
if (dbProvider.DbMaintenance.IsAnyTable(tableName, false))
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Retry(() =>
|
|
|
|
|
|
{
|
|
|
|
|
|
dbProvider.CodeFirst.InitTables(typeof(JeecgSysDictItem));
|
|
|
|
|
|
}, maxRetry: 3, retryIntervalMs: 1000);
|
|
|
|
|
|
|
|
|
|
|
|
if (!dbProvider.DbMaintenance.IsAnyTable(tableName, false) && config.DbType == DbType.Sqlite)
|
|
|
|
|
|
{
|
|
|
|
|
|
const string createSql = @"
|
|
|
|
|
|
CREATE TABLE IF NOT EXISTS jeecg_sys_dict_item (
|
|
|
|
|
|
id TEXT PRIMARY KEY NOT NULL,
|
|
|
|
|
|
dict_id TEXT NULL,
|
|
|
|
|
|
dict_name TEXT NULL,
|
|
|
|
|
|
dict_code TEXT NULL,
|
|
|
|
|
|
dict_type INTEGER NULL,
|
|
|
|
|
|
dict_description TEXT NULL,
|
|
|
|
|
|
item_text TEXT NULL,
|
|
|
|
|
|
item_value TEXT NULL,
|
|
|
|
|
|
item_description TEXT NULL,
|
|
|
|
|
|
sort_order INTEGER NULL,
|
|
|
|
|
|
status INTEGER NULL,
|
|
|
|
|
|
item_color TEXT NULL,
|
|
|
|
|
|
create_by TEXT NULL,
|
|
|
|
|
|
create_time TEXT NULL,
|
|
|
|
|
|
update_by TEXT NULL,
|
|
|
|
|
|
update_time TEXT NULL
|
|
|
|
|
|
);";
|
|
|
|
|
|
Retry(() => dbProvider.Ado.ExecuteCommand(createSql), maxRetry: 3, retryIntervalMs: 1000);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch
|
|
|
|
|
|
{
|
|
|
|
|
|
// 兜底逻辑不阻断启动
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 初始化种子数据
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="db">SqlSugarScope 实例</param>
|
|
|
|
|
|
/// <param name="config">数据库连接配置</param>
|
|
|
|
|
|
private static void InitSeedData(SqlSugarScope db, DbConnectionConfig config)
|
|
|
|
|
|
{
|
|
|
|
|
|
var dbProvider = db.GetConnectionScope(config.ConfigId);
|
|
|
|
|
|
_isHandlingSeedData = true;
|
|
|
|
|
|
|
|
|
|
|
|
// Log.Information($"初始化种子数据 {config.DbType} - {config.ConfigId}");
|
|
|
|
|
|
var seedDataTypes = GetSeedDataTypes(config);
|
|
|
|
|
|
|
|
|
|
|
|
int count = 0, sum = seedDataTypes.Count;
|
|
|
|
|
|
var tasks = seedDataTypes.Select(seedType => Task.Run(() =>
|
|
|
|
|
|
{
|
|
|
|
|
|
var entityType = seedType.GetInterfaces().First().GetGenericArguments().First();
|
|
|
|
|
|
if (!IsEntityForConfig(entityType, config)) return;
|
|
|
|
|
|
|
|
|
|
|
|
var seedData = GetSeedData(seedType);
|
|
|
|
|
|
if (seedData == null) return;
|
|
|
|
|
|
|
|
|
|
|
|
AdjustSeedDataIds(seedData, config);
|
|
|
|
|
|
InsertOrUpdateSeedData(dbProvider, seedType, entityType, seedData, config, ref count, sum);
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
Task.WhenAll(tasks).GetAwaiter().GetResult();
|
|
|
|
|
|
_isHandlingSeedData = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 获取需要初始化的实体类型
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="config">数据库连接配置</param>
|
|
|
|
|
|
/// <returns>实体类型列表</returns>
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 获取需要初始化的实体类型
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="config">数据库连接配置</param>
|
|
|
|
|
|
/// <returns>实体类型列表</returns>
|
|
|
|
|
|
private static List<Type> GetEntityTypesForInit(DbConnectionConfig config)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 获取当前应用程序域中的所有程序集
|
|
|
|
|
|
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
|
|
|
|
|
|
|
|
|
|
|
|
// 收集所有符合条件的类型
|
|
|
|
|
|
var allTypes = new List<Type>();
|
|
|
|
|
|
foreach (var assembly in assemblies)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
// 跳过动态程序集(通常不包含用户定义的实体类)
|
|
|
|
|
|
if (assembly.IsDynamic) continue;
|
|
|
|
|
|
|
|
|
|
|
|
// 获取程序集中定义的所有类型
|
|
|
|
|
|
var types = assembly.GetTypes();
|
|
|
|
|
|
allTypes.AddRange(types);
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (ReflectionTypeLoadException ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 处理部分类型加载失败的情况
|
|
|
|
|
|
var loadedTypes = ex.Types.Where(t => t != null);
|
|
|
|
|
|
allTypes.AddRange(loadedTypes!);
|
|
|
|
|
|
}
|
|
|
|
|
|
catch
|
|
|
|
|
|
{
|
|
|
|
|
|
// 忽略无法加载的程序集
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 过滤类型
|
|
|
|
|
|
return allTypes
|
|
|
|
|
|
.Where(u => u != null)
|
|
|
|
|
|
.Where(u => u.IsClass && !u.IsAbstract && !u.IsInterface)
|
|
|
|
|
|
.Where(u => u.IsDefined(typeof(SugarTable), false))
|
|
|
|
|
|
.Where(u => !u.IsDefined(typeof(IgnoreTableAttribute), false))
|
|
|
|
|
|
.WhereIF(config.TableSettings.EnableIncreTable, u => u.IsDefined(typeof(IncreTableAttribute), false))
|
|
|
|
|
|
.Where(u => IsEntityForConfig(u, config))
|
|
|
|
|
|
.ToList();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 获取种子数据类型
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="config">数据库连接配置</param>
|
|
|
|
|
|
/// <returns>种子数据类型列表</returns>
|
|
|
|
|
|
private static List<Type> GetSeedDataTypes(DbConnectionConfig config)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 获取所有程序集及其类型
|
|
|
|
|
|
var allTypes = AppDomain.CurrentDomain.GetAssemblies()
|
|
|
|
|
|
.Where(a => !a.IsDynamic) // 跳过动态程序集
|
|
|
|
|
|
.SelectMany(assembly =>
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
return assembly.GetTypes();
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (ReflectionTypeLoadException ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 处理部分类型加载失败的情况
|
|
|
|
|
|
return ex.Types?.Where(t => t != null) ?? Enumerable.Empty<Type>();
|
|
|
|
|
|
}
|
|
|
|
|
|
catch
|
|
|
|
|
|
{
|
|
|
|
|
|
// 忽略无法加载的程序集
|
|
|
|
|
|
return Enumerable.Empty<Type>();
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
.Where(t => t != null)
|
|
|
|
|
|
.ToList();
|
|
|
|
|
|
|
|
|
|
|
|
// 过滤种子数据类型
|
|
|
|
|
|
var seedTypes = allTypes
|
|
|
|
|
|
.Where(u => u!.IsClass && !u.IsAbstract && !u.IsInterface)
|
|
|
|
|
|
.Where(u => u!.HasImplementedRawGeneric(typeof(ISqlSugarEntitySeedData<>)))
|
|
|
|
|
|
.WhereIF(config.SeedSettings.EnableIncreSeed,
|
|
|
|
|
|
u => u!.IsDefined(typeof(IncreSeedAttribute), false))
|
|
|
|
|
|
.OrderBy(u =>
|
|
|
|
|
|
{
|
|
|
|
|
|
var seedAttrs = u!.GetCustomAttributes(typeof(SeedDataAttribute), false);
|
|
|
|
|
|
return seedAttrs.Length > 0 ? ((SeedDataAttribute)seedAttrs[0]).Order : 0;
|
|
|
|
|
|
})
|
|
|
|
|
|
.ToList();
|
|
|
|
|
|
|
|
|
|
|
|
return seedTypes!;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 判断实体是否属于当前配置
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="entityType">实体类型</param>
|
|
|
|
|
|
/// <param name="config">数据库连接配置</param>
|
|
|
|
|
|
/// <returns>是否属于当前配置</returns>
|
|
|
|
|
|
private static bool IsEntityForConfig(Type entityType, DbConnectionConfig config)
|
|
|
|
|
|
{
|
|
|
|
|
|
switch (config.ConfigId.ToString())
|
|
|
|
|
|
{
|
|
|
|
|
|
case SqlSugarConst.MainConfigId:
|
|
|
|
|
|
return entityType.GetCustomAttributes<SysTableAttribute>().Any() ||
|
|
|
|
|
|
(!entityType.GetCustomAttributes<LogTableAttribute>().Any() &&
|
|
|
|
|
|
!entityType.GetCustomAttributes<TenantAttribute>().Any());
|
|
|
|
|
|
|
|
|
|
|
|
case SqlSugarConst.LogConfigId:
|
|
|
|
|
|
return entityType.GetCustomAttributes<LogTableAttribute>().Any();
|
|
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
|
{
|
|
|
|
|
|
var tenantAttribute = entityType.GetCustomAttribute<TenantAttribute>();
|
|
|
|
|
|
return tenantAttribute != null && tenantAttribute.configId.ToString() == config.ConfigId.ToString();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 初始化表结构
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="dbProvider">SqlSugarScopeProvider 实例</param>
|
|
|
|
|
|
/// <param name="entityTypes">实体类型列表</param>
|
|
|
|
|
|
/// <param name="config">数据库连接配置</param>
|
|
|
|
|
|
private static void InitializeTables(SqlSugarScopeProvider dbProvider, List<Type> entityTypes, DbConnectionConfig config)
|
|
|
|
|
|
{
|
|
|
|
|
|
int count = 0, sum = entityTypes.Count;
|
|
|
|
|
|
var tasks = entityTypes.Select(entityType => Task.Run(() =>
|
|
|
|
|
|
{
|
|
|
|
|
|
Console.WriteLine($"初始化表结构 {entityType.FullName,-64} ({config.ConfigId} - {Interlocked.Increment(ref count):D003}/{sum:D003})");
|
|
|
|
|
|
UpdateNullableColumns(dbProvider, entityType);
|
|
|
|
|
|
InitializeTable(dbProvider, entityType);
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
Task.WhenAll(tasks).GetAwaiter().GetResult();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 更新表中不存在于实体的字段为可空
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="dbProvider">SqlSugarScopeProvider 实例</param>
|
|
|
|
|
|
/// <param name="entityType">实体类型</param>
|
|
|
|
|
|
private static void UpdateNullableColumns(SqlSugarScopeProvider dbProvider, Type entityType)
|
|
|
|
|
|
{
|
|
|
|
|
|
var entityInfo = dbProvider.EntityMaintenance.GetEntityInfo(entityType);
|
|
|
|
|
|
var dbColumns = dbProvider.DbMaintenance.GetColumnInfosByTableName(entityInfo.DbTableName) ?? new List<DbColumnInfo>();
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var dbColumn in dbColumns.Where(c => !c.IsPrimarykey && entityInfo.Columns.All(u => u.DbColumnName != c.DbColumnName)))
|
|
|
|
|
|
{
|
|
|
|
|
|
dbColumn.IsNullable = true;
|
|
|
|
|
|
Retry(() =>
|
|
|
|
|
|
{
|
|
|
|
|
|
dbProvider.DbMaintenance.UpdateColumn(entityInfo.DbTableName, dbColumn);
|
|
|
|
|
|
}, maxRetry: 3, retryIntervalMs: 1000);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 等待数据库就绪
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="dbProvider"></param>
|
|
|
|
|
|
private static void WaitForDatabaseReady(SqlSugarScopeProvider db)
|
|
|
|
|
|
{
|
|
|
|
|
|
do
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
if (db.Ado.Connection.State != ConnectionState.Open)
|
|
|
|
|
|
db.Ado.Connection.Open();
|
|
|
|
|
|
|
|
|
|
|
|
// 如果连接成功,直接返回
|
|
|
|
|
|
//logger.Information("数据库连接成功。");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
// logger.Warning($"数据库尚未就绪,等待中... 错误:{ex.Message}");
|
|
|
|
|
|
Thread.Sleep(1000);
|
|
|
|
|
|
}
|
|
|
|
|
|
} while (true);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 简单的重试机制
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="action"></param>
|
|
|
|
|
|
/// <param name="maxRetry"></param>
|
|
|
|
|
|
/// <param name="retryIntervalMs"></param>
|
|
|
|
|
|
private static void Retry(Action action, int maxRetry, int retryIntervalMs)
|
|
|
|
|
|
{
|
|
|
|
|
|
int attempt = 0;
|
|
|
|
|
|
while (true)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
action();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (SqliteException ex) when (ex.SqliteErrorCode == 5) // SQLITE_BUSY
|
|
|
|
|
|
{
|
|
|
|
|
|
if (++attempt >= maxRetry)
|
|
|
|
|
|
{
|
|
|
|
|
|
// logger.Error($"简单的重试机制:{ex.Message}"); throw;
|
|
|
|
|
|
}
|
|
|
|
|
|
//logger.Information($"数据库忙,正在重试... (尝试 {attempt}/{maxRetry})");
|
|
|
|
|
|
Thread.Sleep(retryIntervalMs);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 获取种子数据
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="seedType">种子数据类型</param>
|
|
|
|
|
|
/// <returns>种子数据列表</returns>
|
|
|
|
|
|
private static IEnumerable<object> GetSeedData(Type seedType)
|
|
|
|
|
|
{
|
|
|
|
|
|
var instance = Activator.CreateInstance(seedType);
|
|
|
|
|
|
var hasDataMethod = seedType.GetMethod("HasData");
|
|
|
|
|
|
return ((IEnumerable)hasDataMethod?.Invoke(instance, null)!)?.Cast<object>()!;
|
|
|
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 插入或更新种子数据
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="dbProvider">SqlSugarScopeProvider 实例</param>
|
|
|
|
|
|
/// <param name="seedType">种子数据类型</param>
|
|
|
|
|
|
/// <param name="entityType">实体类型</param>
|
|
|
|
|
|
/// <param name="seedData">种子数据列表</param>
|
|
|
|
|
|
/// <param name="config">数据库连接配置</param>
|
|
|
|
|
|
/// <param name="count">当前处理的数量</param>
|
|
|
|
|
|
/// <param name="sum">总数量</param>
|
|
|
|
|
|
private static void InsertOrUpdateSeedData(SqlSugarScopeProvider dbProvider, Type seedType, Type entityType, IEnumerable<object> seedData, DbConnectionConfig config, ref int count, int sum)
|
|
|
|
|
|
{
|
|
|
|
|
|
var entityInfo = dbProvider.EntityMaintenance.GetEntityInfo(entityType);
|
|
|
|
|
|
var dataList = seedData.ToList();
|
|
|
|
|
|
|
|
|
|
|
|
if (entityType.GetCustomAttribute<SplitTableAttribute>(true) != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
var initMethod = seedType.GetMethod("Init");
|
|
|
|
|
|
initMethod?.Invoke(Activator.CreateInstance(seedType), new object[] { dbProvider });
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
int updateCount = 0, insertCount = 0;
|
|
|
|
|
|
if (entityInfo.Columns.Any(u => u.IsPrimarykey))
|
|
|
|
|
|
{
|
|
|
|
|
|
var storage = dbProvider.StorageableByObject(dataList).ToStorage();
|
|
|
|
|
|
if (seedType.GetCustomAttribute<IgnoreUpdateSeedAttribute>() == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
updateCount = storage.AsUpdateable
|
|
|
|
|
|
.IgnoreColumns(entityInfo.Columns
|
|
|
|
|
|
.Where(u => u.PropertyInfo.GetCustomAttribute<IgnoreUpdateSeedColumnAttribute>() != null)
|
|
|
|
|
|
.Select(u => u.PropertyName).ToArray())
|
|
|
|
|
|
.ExecuteCommand();
|
|
|
|
|
|
}
|
|
|
|
|
|
insertCount = storage.AsInsertable.ExecuteCommand();
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!dbProvider.Queryable(entityInfo.DbTableName, entityInfo.DbTableName).Any())
|
|
|
|
|
|
{
|
|
|
|
|
|
insertCount = dataList.Count;
|
|
|
|
|
|
dbProvider.InsertableByObject(dataList).ExecuteCommand();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
Console.WriteLine($"添加数据 {entityInfo.DbTableName,-32} ({config.ConfigId} - {Interlocked.Increment(ref count):D003}/{sum:D003},数据量:{dataList.Count:D003},插入 {insertCount:D003} 条记录,修改 {updateCount:D003} 条记录)");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 调整种子数据的 ID
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="seedData">种子数据列表</param>
|
|
|
|
|
|
/// <param name="config">数据库连接配置</param>
|
|
|
|
|
|
private static void AdjustSeedDataIds(IEnumerable<object> seedData, DbConnectionConfig config)
|
|
|
|
|
|
{
|
|
|
|
|
|
var seedId = config.ConfigId.ToLong();
|
|
|
|
|
|
foreach (var data in seedData)
|
|
|
|
|
|
{
|
|
|
|
|
|
var idProperty = data.GetType().GetProperty(nameof(EntityBaseId.Id));
|
|
|
|
|
|
if (idProperty == null || idProperty.PropertyType != typeof(Int64)) continue;
|
|
|
|
|
|
|
|
|
|
|
|
var idValue = idProperty.GetValue(data);
|
|
|
|
|
|
if (idValue == null || idValue.ToString() == "0" || string.IsNullOrWhiteSpace(idValue.ToString()))
|
|
|
|
|
|
{
|
|
|
|
|
|
idProperty.SetValue(data, ++seedId);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 初始化表
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="dbProvider">SqlSugarScopeProvider 实例</param>
|
|
|
|
|
|
/// <param name="entityType">实体类型</param>
|
|
|
|
|
|
private static void InitializeTable(SqlSugarScopeProvider dbProvider, Type entityType)
|
|
|
|
|
|
{
|
|
|
|
|
|
Retry(() =>
|
|
|
|
|
|
{
|
|
|
|
|
|
if (entityType.GetCustomAttribute<SplitTableAttribute>() == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
dbProvider.CodeFirst.InitTables(entityType);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
dbProvider.CodeFirst.SplitTables().InitTables(entityType);
|
|
|
|
|
|
}
|
|
|
|
|
|
}, maxRetry: 3, retryIntervalMs: 1000);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|