feat: 书库导入导出/图片变体/书籍搜索/小组件运行时等

新增:
- 书库导入导出(library_import/library_export)及测试
- 图片变体生成(image_variants)与响应式图片(responsiveImage)
- 书籍搜索(bookSearch)+ BookSearch/LibrarySearchGrid 组件
- 小组件运行时(widgetRuntime)与静态检查(widgetLint)
- 上传缓存中间件(upload_cache)与字体 CSS 提取脚本

其它:
- 后端 handlers/services 全量调整
- 前端页面、组件、库函数与配置更新
This commit is contained in:
2026-10-01 03:06:05 +08:00
parent 7fbd6ba699
commit ff2ab286fb
141 changed files with 7527 additions and 1407 deletions

View File

@@ -78,6 +78,9 @@ func InitDB(dsn string) error {
// 每次启动执行,幂等自愈,保证与 CreditTx 增量口径一致(均排除 bounty_refund)
backfillUserTotalPoints(db)
// 书库:历史条目 creator_id=0 时介绍页无创建者可展示,回填为站长
backfillLibraryCreator(db)
// login_logs.success 早期 default=true 与 GORM 零值省略叠加,
// 会把失败登录错存为成功;AutoMigrate 不会改列默认值,这里幂等修正
if err := db.Exec(`ALTER TABLE login_logs ALTER COLUMN success SET DEFAULT false`).Error; err != nil {
@@ -146,6 +149,7 @@ func dropStaleChatFKConstraints(db *gorm.DB) error {
pairs := []struct{ table, constraint string }{
{"chat_rooms", "fk_chat_rooms_last_message"},
{"notifications", "fk_notifications_room"},
{"chat_messages", "fk_chat_messages_sender"},
}
for _, p := range pairs {
db.Exec(fmt.Sprintf(`ALTER TABLE IF EXISTS %s DROP CONSTRAINT IF EXISTS %s`, p.table, p.constraint))
@@ -368,6 +372,29 @@ func backfillUserTotalPoints(db *gorm.DB) {
}
}
// backfillLibraryCreator 为历史书库条目(creator_id=0,特性上线前创建或早期导入)
// 回填站长为创建者,保证介绍页「创建者」可展示。幂等:仅影响 creator_id=0 的行;
// 站长不存在(空库)时跳过,等下次启动自愈。
func backfillLibraryCreator(db *gorm.DB) {
var owner User
err := db.Select("id").Where("role = ?", RoleOwner).Order("id ASC").First(&owner).Error
if errors.Is(err, gorm.ErrRecordNotFound) {
return
}
if err != nil {
log.Printf("[model] 回填书库创建者前置查询失败: %v", err)
return
}
res := db.Exec(`UPDATE library_docs SET creator_id = ? WHERE creator_id = 0`, owner.ID)
if res.Error != nil {
log.Printf("[model] 回填书库创建者失败: %v", res.Error)
return
}
if res.RowsAffected > 0 {
log.Printf("[model] 已回填书库条目创建者为站长 %d 行", res.RowsAffected)
}
}
// backfillBoardDefaults 为升级前已存在的板块补齐新字段默认值。
// 以 post_policy 为空作为「迁移前旧行」的判定标志:仅对这些行一次性回填
// post_policy=all、visible=true;后续已被显式设置过的行(含站长隐藏的板块)不会被覆盖。