fix(goauto): create syb_product_filter on existing databases #269
Three defects that only a real MySQL run could show. The table had no version-local migration. Adding the model to migrations.Migrate covers databases built from scratch, but every existing one already has the older versions recorded in sys_migration, so that list never runs again there. cmd/migrate's post-migration table check caught it: "迁移后仍缺少表:syb_product_filter". The new version file creates the table and seeds the same eight rules with FirstOrCreate on the normalized key, so running both paths against one database cannot duplicate them. Keyword and NormalizedKeyword were typed text, which MySQL refuses to put in a key specification without a prefix length (Error 1170), so the unique index could not be built at all. They are sized 200 now, matching how SYBShop sizes its own matching key. SQLite accepts text in an index, which is why the whole test suite passed while the real migration failed. The 只看启用 filter is removed rather than kept: hiding a disabled structural rule would conceal the one state on this page that matters most, since a disabled "#" means roughly 65% of 档口 rows start entering the system again. Keyword search stays. Verified against the local MySQL database: migration applies, eight rules present with traditional Chinese preserved (印花大學T normalizes to 印花大學t), menu row created under 采采管理. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F
This commit is contained in:
@@ -605,10 +605,14 @@ func (SYBShop) TableName() string { return "syb_shop" }
|
||||
// rows are operator-maintained. DeletedFlag keeps the composite unique key
|
||||
// reusable after a keyword is soft-deleted.
|
||||
type SYBProductFilter struct {
|
||||
ID uint64 `json:"id" gorm:"primaryKey;autoIncrement"`
|
||||
Kind string `json:"kind" gorm:"size:16;not null;index;uniqueIndex:ux_syb_product_filter_key,priority:1;check:ck_syb_product_filter_kind,kind IN ('char','keyword')"`
|
||||
Keyword string `json:"keyword" gorm:"type:text;not null"`
|
||||
NormalizedKeyword string `json:"normalizedKeyword" gorm:"type:text;not null;uniqueIndex:ux_syb_product_filter_key,priority:2"`
|
||||
ID uint64 `json:"id" gorm:"primaryKey;autoIncrement"`
|
||||
Kind string `json:"kind" gorm:"size:16;not null;index;uniqueIndex:ux_syb_product_filter_key,priority:1;check:ck_syb_product_filter_kind,kind IN ('char','keyword')"`
|
||||
// Keyword and NormalizedKeyword are sized, not text: MySQL refuses a
|
||||
// BLOB/TEXT column in a key specification without a prefix length, so the
|
||||
// unique index below cannot be built on a text column (Error 1170). SQLite
|
||||
// accepts either, which is why only a real MySQL migration surfaces this.
|
||||
Keyword string `json:"keyword" gorm:"size:200;not null"`
|
||||
NormalizedKeyword string `json:"normalizedKeyword" gorm:"size:200;not null;uniqueIndex:ux_syb_product_filter_key,priority:2"`
|
||||
Enabled bool `json:"enabled" gorm:"not null;default:true"`
|
||||
LastHitAt *time.Time `json:"lastHitAt"`
|
||||
LastHitCount *int `json:"lastHitCount"`
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
package version_local
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
|
||||
"go-admin/app/goauto/models"
|
||||
"go-admin/app/goauto/sybshop"
|
||||
"go-admin/cmd/migrate/migration"
|
||||
common "go-admin/common/models"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func init() {
|
||||
_, file, _, _ := runtime.Caller(0)
|
||||
migration.Migrate.SetVersion(migration.GetFilename(file), migrateSYBProductFilterTable)
|
||||
}
|
||||
|
||||
// migrateSYBProductFilterTable creates syb_product_filter and seeds its rules
|
||||
// for #269.
|
||||
//
|
||||
// `[必须]` Adding the model to migrations.Migrate only covers databases created
|
||||
// from scratch. Every existing database already has the older version numbers
|
||||
// recorded in sys_migration, so that list never runs again there — a new model
|
||||
// needs its own version file, which is what the post-migration table check in
|
||||
// cmd/migrate enforces.
|
||||
//
|
||||
// The seed is deliberately the same set the fresh-database path writes, created
|
||||
// with FirstOrCreate on the normalized key so running both paths against one
|
||||
// database cannot produce duplicates.
|
||||
func migrateSYBProductFilterTable(db *gorm.DB, version string) error {
|
||||
if err := db.AutoMigrate(&models.SYBProductFilter{}); err != nil {
|
||||
return err
|
||||
}
|
||||
return db.Transaction(func(tx *gorm.DB) error {
|
||||
// 结构判据 is what defines a 档口 product; the keywords are the forward
|
||||
// safety net for the day those codes stop carrying "#" (#269).
|
||||
seed := []struct{ kind, keyword string }{
|
||||
{"char", "-"},
|
||||
{"char", "#"},
|
||||
{"keyword", "CVC短袖220斤"},
|
||||
{"keyword", "180克純棉圓領短袖"},
|
||||
{"keyword", "拼色純棉短袖"},
|
||||
{"keyword", "300斤牛奶絲圓領"},
|
||||
{"keyword", "印花大學T"},
|
||||
{"keyword", "印花帽T"},
|
||||
}
|
||||
for _, item := range seed {
|
||||
norm := sybshop.Normalize(item.keyword)
|
||||
row := models.SYBProductFilter{
|
||||
Kind: item.kind,
|
||||
Keyword: item.keyword,
|
||||
NormalizedKeyword: norm,
|
||||
Enabled: true,
|
||||
}
|
||||
if err := tx.Where("kind = ? AND normalized_keyword = ?", item.kind, norm).
|
||||
FirstOrCreate(&row).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return tx.Create(&common.Migration{Version: version}).Error
|
||||
})
|
||||
}
|
||||
@@ -6,9 +6,6 @@
|
||||
<el-form-item label="搜索">
|
||||
<el-input v-model="query.keyword" clearable placeholder="字符或关键词" @keyup.enter="load" />
|
||||
</el-form-item>
|
||||
<el-form-item label="只看启用">
|
||||
<el-switch v-model="query.enabledOnly" @change="load" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="reset">重置</el-button>
|
||||
</el-form-item>
|
||||
@@ -163,7 +160,7 @@ export default {
|
||||
loading: false,
|
||||
chars: [],
|
||||
keywords: [],
|
||||
query: { keyword: '', enabledOnly: false },
|
||||
query: { keyword: '' },
|
||||
editor: { open: false, saving: false, keyword: '', note: '' }
|
||||
}
|
||||
},
|
||||
@@ -182,8 +179,7 @@ export default {
|
||||
const res = await listSybProductFilters({
|
||||
page: 1,
|
||||
pageSize: 200,
|
||||
keyword: this.query.keyword || undefined,
|
||||
enabledOnly: this.query.enabledOnly || undefined
|
||||
keyword: this.query.keyword || undefined
|
||||
})
|
||||
const rows = (res.data.items || []).map(row => ({ ...row, updating: false }))
|
||||
this.chars = rows.filter(row => row.kind === 'char')
|
||||
@@ -193,7 +189,7 @@ export default {
|
||||
}
|
||||
},
|
||||
reset() {
|
||||
this.query = { keyword: '', enabledOnly: false }
|
||||
this.query = { keyword: '' }
|
||||
this.load()
|
||||
},
|
||||
formatTime(value) {
|
||||
|
||||
Reference in New Issue
Block a user