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("已写入分类 / 链接种子数据");
}
}
}