using MyHomePage.Api.Models.Entities; using SqlSugar; namespace MyHomePage.Api.Infrastructure.Database; /// /// 数据库初始化器:CodeFirst 建表 + 种子数据。 /// 应用启动时调用一次。 /// public class DatabaseInitializer { private readonly SqlSugarContext _ctx; private readonly ILogger _logger; public DatabaseInitializer(SqlSugarContext ctx, ILogger logger) { _ctx = ctx; _logger = logger; } /// 建表 + 种子数据 public async Task InitializeAsync() { try { _logger.LogInformation("开始 CodeFirst 建表..."); // ===== 兼容老库:表已存在则跳过 CodeFirst InitTables(Sqlite 不支持 alter column primary key,触发表结构不一致会抛)===== // 后续靠 MigrateSettingColumns / MigrateBookmarkColumns 给老库补列。 const string settingsTable = "settings"; if (_ctx.Db.DbMaintenance.IsAnyTable(settingsTable)) { _logger.LogInformation("检测到 settings 表已存在,跳过 CodeFirst(已通过轻量迁移补齐列)"); } else { _ctx.Db.CodeFirst.InitTables( typeof(Category), typeof(Bookmark), typeof(SearchEngine), typeof(Setting), typeof(SyncLog) ); } // 对已存在数据库做轻量迁移:给 settings 表补上新增列(CodeFirst InitTables 不会自动 ALTER 老库) MigrateSettingColumns(); // 给 bookmarks 表补充 ColorBg 列(P28 链接 logo 背景色) MigrateBookmarkColumns(); // 给 search_engines 表补充 IconType / IconUrl / ColorBg 列(P37 引擎图标逻辑对齐链接) MigrateSearchEngineColumns(); // 给 settings 表补充 OpenSearchInNewTab 列(P46 搜索框行为开关) MigrateSettingColumnsV2(); await SeedAsync(); _logger.LogInformation("数据库初始化完成"); } catch (Exception ex) { _logger.LogError(ex, "数据库初始化失败"); throw; } } /// 为 settings 表补充新列(已存在则跳过)。 private void MigrateSettingColumns() { const string tableName = "settings"; // P26.2 if (!_ctx.Db.DbMaintenance.IsAnyColumn(tableName, "OpenLinksInNewTab")) { _ctx.Db.DbMaintenance.AddColumn(tableName, new DbColumnInfo { DbColumnName = "OpenLinksInNewTab", DataType = "INTEGER", IsNullable = false, DefaultValue = "1" }); _logger.LogInformation("已为 settings 表补充列 {Column}", "OpenLinksInNewTab"); } // P34 360 壁纸模式 if (!_ctx.Db.DbMaintenance.IsAnyColumn(tableName, "WallpaperEnabled")) { _ctx.Db.DbMaintenance.AddColumn(tableName, new DbColumnInfo { DbColumnName = "WallpaperEnabled", DataType = "INTEGER", IsNullable = false, DefaultValue = "0" }); _logger.LogInformation("已为 settings 表补充列 {Column}", "WallpaperEnabled"); } if (!_ctx.Db.DbMaintenance.IsAnyColumn(tableName, "WallpaperCategoryId")) { _ctx.Db.DbMaintenance.AddColumn(tableName, new DbColumnInfo { DbColumnName = "WallpaperCategoryId", DataType = "varchar(32)", IsNullable = true, DefaultValue = "" }); _logger.LogInformation("已为 settings 表补充列 {Column}", "WallpaperCategoryId"); } if (!_ctx.Db.DbMaintenance.IsAnyColumn(tableName, "WallpaperInterval")) { _ctx.Db.DbMaintenance.AddColumn(tableName, new DbColumnInfo { DbColumnName = "WallpaperInterval", DataType = "INTEGER", IsNullable = false, DefaultValue = "30" }); _logger.LogInformation("已为 settings 表补充列 {Column}", "WallpaperInterval"); } } /// 为 bookmarks 表补充 ColorBg 列(已存在则跳过)。 private void MigrateBookmarkColumns() { const string tableName = "bookmarks"; const string newColumn = "ColorBg"; if (!_ctx.Db.DbMaintenance.IsAnyColumn(tableName, newColumn)) { _ctx.Db.DbMaintenance.AddColumn(tableName, new DbColumnInfo { DbColumnName = newColumn, DataType = "varchar(32)", IsNullable = true }); _logger.LogInformation("已为 bookmarks 表补充列 {Column}", newColumn); } } /// 为 search_engines 表补充 IconType / IconUrl / ColorBg 列(已存在则跳过,P37 引擎图标逻辑对齐链接)。 private void MigrateSearchEngineColumns() { const string tableName = "search_engines"; AddColumnIfMissing(tableName, "IconType", "varchar(16)", isNullable: false, defaultValue: "lucide"); AddColumnIfMissing(tableName, "IconUrl", "varchar(512)", isNullable: true); AddColumnIfMissing(tableName, "ColorBg", "varchar(32)", isNullable: true); } /// P46:给 settings 表补 OpenSearchInNewTab 列(int default 1)—— 复刻 P37/P42 的「轻量迁移」模式 private void MigrateSettingColumnsV2() { const string tableName = "settings"; // 注意:int 类型的 column 在 SqlSugar + SQLite 下要显式声明 DataType if (!_ctx.Db.DbMaintenance.IsAnyColumn(tableName, "OpenSearchInNewTab")) { _ctx.Db.DbMaintenance.AddColumn(tableName, new DbColumnInfo { DbColumnName = "OpenSearchInNewTab", DataType = "int", IsNullable = false, DefaultValue = "1" }); } } private void AddColumnIfMissing(string tableName, string columnName, string dataType, bool isNullable, string? defaultValue = null) { if (_ctx.Db.DbMaintenance.IsAnyColumn(tableName, columnName)) return; _ctx.Db.DbMaintenance.AddColumn(tableName, new DbColumnInfo { DbColumnName = columnName, DataType = dataType, IsNullable = isNullable, DefaultValue = defaultValue }); _logger.LogInformation("已为 {Table} 表补充列 {Column}", tableName, columnName); } /// 写入种子数据(仅当表为空时执行) private async Task SeedAsync() { var db = _ctx.Db; // 搜索引擎种子 if (!db.Queryable().Any()) { var engines = new List { new() { Name = "百度", UrlTemplate = "https://www.baidu.com/s?wd={q}", Icon = "search", Sort = 0, IsDefault = true, CreatedAt = DateTime.UtcNow, UpdatedAt = DateTime.UtcNow }, new() { Name = "Google", UrlTemplate = "https://www.google.com/search?q={q}", Icon = "search", Sort = 1, IsDefault = false, CreatedAt = DateTime.UtcNow, UpdatedAt = DateTime.UtcNow }, new() { Name = "Bing", UrlTemplate = "https://www.bing.com/search?q={q}", Icon = "search", Sort = 2, IsDefault = false, CreatedAt = DateTime.UtcNow, UpdatedAt = DateTime.UtcNow } }; await db.Insertable(engines).ExecuteCommandAsync(); _logger.LogInformation("已写入搜索引擎种子数据 ({Count} 条)", engines.Count); } // 设置种子(单行) if (!db.Queryable().Any()) { var setting = new Setting { Id = 1, ThemeMode = "dark", AccentColor = "#6c5ce7", BackgroundImage = "wp1", BackgroundType = "preset", OpenLinksInNewTab = 1, UpdatedAt = DateTime.UtcNow }; await db.Insertable(setting).ExecuteCommandAsync(); _logger.LogInformation("已写入默认设置"); } // 分类 + 链接种子 if (!db.Queryable().Any()) { var now = DateTime.UtcNow; // 一级:常用工具 var catTools = new Category { ParentId = 0, Name = "常用工具", Icon = "wrench", Sort = 0, CreatedAt = now, UpdatedAt = now }; catTools.Id = await db.Insertable(catTools).ExecuteReturnIdentityAsync(); var catToolsId = catTools.Id; // 二级分类(单独插入回填 Id,方便后续链接绑定) var subAi = new Category { ParentId = catToolsId, Name = "AI 工具", Icon = "bot", Sort = 1, CreatedAt = now, UpdatedAt = now }; var subDev = new Category { ParentId = catToolsId, Name = "开发工具", Icon = "code-2", Sort = 2, CreatedAt = now, UpdatedAt = now }; subAi.Id = await db.Insertable(subAi).ExecuteReturnIdentityAsync(); subDev.Id = await db.Insertable(subDev).ExecuteReturnIdentityAsync(); // 链接示例 var bookmarks = new List { new() { CategoryId = subAi.Id, Title = "ChatGPT", Url = "https://chat.openai.com", Description = "AI 对话助手,智能问答", Icon = "bot", IconType = "lucide", Sort = 0, CreatedAt = now, UpdatedAt = now }, new() { CategoryId = subAi.Id, Title = "Claude", Url = "https://claude.ai", Description = "Anthropic 推出的 AI 助手", Icon = "bot", IconType = "lucide", Sort = 1, CreatedAt = now, UpdatedAt = now }, new() { CategoryId = subDev.Id, Title = "GitHub", Url = "https://github.com", Description = "代码托管与协作平台", Icon = "github", IconType = "lucide", Sort = 0, CreatedAt = now, UpdatedAt = now }, new() { CategoryId = subDev.Id, Title = "MDN", Url = "https://developer.mozilla.org", Description = "Web 技术文档参考", Icon = "book", IconType = "lucide", Sort = 1, CreatedAt = now, UpdatedAt = now }, new() { CategoryId = subDev.Id, Title = "Stack Overflow", Url = "https://stackoverflow.com", Description = "开发者问答社区", Icon = "message-circle", IconType = "lucide", Sort = 2, CreatedAt = now, UpdatedAt = now }, new() { CategoryId = subDev.Id, Title = "VS Code", Url = "https://code.visualstudio.com", Description = "轻量级代码编辑器", Icon = "code-2", IconType = "lucide", Sort = 3, CreatedAt = now, UpdatedAt = now } }; await db.Insertable(bookmarks).ExecuteCommandAsync(); _logger.LogInformation("已写入分类 / 链接种子数据"); } } }