mirror of
https://github.com/truewhile/MeBox.git
synced 2026-10-12 00:06:37 +08:00
优化
This commit is contained in:
@@ -13,9 +13,19 @@ func AutoMigrate(db *gorm.DB) error {
|
||||
if err := dedupePlaybackHistories(db); err != nil {
|
||||
return err
|
||||
}
|
||||
// 书源从「全局唯一」改成「按用户独立」:先删掉 source_url 上的旧唯一索引。
|
||||
// AutoMigrate 只会按索引名创建新索引,不会删除已改名的旧唯一索引,留着它
|
||||
// 第二个用户导入同一书源就会撞唯一约束。
|
||||
if err := dropLegacyReaderSourceUniqueIndex(db); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := db.AutoMigrate(model.AllModels()...); err != nil {
|
||||
return err
|
||||
}
|
||||
// 回填书源归属:老库的书源没有 user_id,按「谁书架上有这本书」分配或复制副本。
|
||||
if err := backfillReaderSourceOwnership(db); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ensurePostgresColumnCompatibility(db); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -42,6 +52,105 @@ func ensureSQLiteQueryOptimizer(db *gorm.DB) error {
|
||||
return db.Exec("ANALYZE").Error
|
||||
}
|
||||
|
||||
// dropLegacyReaderSourceUniqueIndex 删除书源表上旧版 source_url 唯一索引。
|
||||
//
|
||||
// 书源改为按用户独立后唯一键是 (user_id, source_url);SQLite 的 RenameTable 与
|
||||
// AutoMigrate 都不会删除按旧名创建的索引(实测旧唯一索引在改名后依然生效),
|
||||
// 必须显式删除,否则第二个用户导入同一书源会撞唯一约束。
|
||||
func dropLegacyReaderSourceUniqueIndex(db *gorm.DB) error {
|
||||
if !db.Migrator().HasTable(&model.ReaderBookSource{}) {
|
||||
return nil
|
||||
}
|
||||
for _, name := range []string{"idx_reader_book_sources_source_url", "reader_book_sources_source_url_key"} {
|
||||
if db.Migrator().HasIndex(&model.ReaderBookSource{}, name) {
|
||||
if err := db.Migrator().DropIndex(&model.ReaderBookSource{}, name); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
// 旧版 DeleteSource 是软删:这些历史行会继续占用 (user_id, source_url) 唯一键,
|
||||
// 让「删掉再导入同一书源」失败。书源是可重新导入的数据,这里一次性清掉。
|
||||
return db.Unscoped().Where("deleted_at IS NOT NULL").Delete(&model.ReaderBookSource{}).Error
|
||||
}
|
||||
|
||||
// backfillReaderSourceOwnership 给老库的书源补上归属用户。
|
||||
//
|
||||
// 书源原先全局唯一,现在按用户独立:按「谁的书架上有这本书」把书源分配给该用户;
|
||||
// 同一书源被多个用户引用时,给每个用户复制一份副本(各自启停/排序/改规则),
|
||||
// 这样谁都不会因为别人删源而失去书源。
|
||||
//
|
||||
// 没有 user_id 且无人引用的书源保持原样(user_id 为空)——它不再出现在任何人的
|
||||
// 列表里,但数据仍在,需要时可以人工认领。
|
||||
func backfillReaderSourceOwnership(db *gorm.DB) error {
|
||||
if !db.Migrator().HasTable(&model.ReaderBookSource{}) {
|
||||
return nil
|
||||
}
|
||||
var pending []model.ReaderBookSource
|
||||
if err := db.Where("user_id = '' OR user_id IS NULL").Find(&pending).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
for i := range pending {
|
||||
src := pending[i]
|
||||
if err := assignOrCloneReaderSource(db, &src); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func assignOrCloneReaderSource(db *gorm.DB, src *model.ReaderBookSource) error {
|
||||
if src == nil || src.SourceURL == "" {
|
||||
return nil
|
||||
}
|
||||
// 引用该书源的书籍分布(去重到用户)。
|
||||
type ownerCount struct {
|
||||
UserID string
|
||||
N int64
|
||||
}
|
||||
var owners []ownerCount
|
||||
if err := db.Model(&model.ReaderBook{}).
|
||||
Select("user_id, COUNT(*) AS n").
|
||||
Where("origin = ?", src.SourceURL).
|
||||
Group("user_id").Order("n DESC").Scan(&owners).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if len(owners) == 0 {
|
||||
return nil // 无人引用:保持无归属
|
||||
}
|
||||
// 第一个用户(引用最多)直接接手这一行。
|
||||
owner := owners[0].UserID
|
||||
if owner == "" {
|
||||
return nil
|
||||
}
|
||||
if err := db.Model(&model.ReaderBookSource{}).
|
||||
Where("id = ?", src.ID).
|
||||
Updates(map[string]any{"user_id": owner}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
// 其余用户各复制一份副本(新行 ID,其余字段全量拷贝)。
|
||||
for _, o := range owners[1:] {
|
||||
if o.UserID == "" || o.UserID == owner {
|
||||
continue
|
||||
}
|
||||
var exists int64
|
||||
if err := db.Model(&model.ReaderBookSource{}).
|
||||
Where("user_id = ? AND source_url = ?", o.UserID, src.SourceURL).
|
||||
Count(&exists).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if exists > 0 {
|
||||
continue
|
||||
}
|
||||
clone := *src
|
||||
clone.ID = ""
|
||||
clone.UserID = o.UserID
|
||||
if err := db.Create(&clone).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// dedupePlaybackHistories removes duplicate (user_id, media_id) rows left by
|
||||
// the former read-then-write upsert, so the uniq_user_history composite unique
|
||||
// index can be created on existing databases. Keeps the most recent row per
|
||||
|
||||
Reference in New Issue
Block a user