// Command i18n_migrate 落地方案 A 的 i18n 列(加列 + 回填)。 // // 项目已移除 AutoMigrate,表结构由外部 SQL 托管(见 scripts/sql/002_i18n_columns.sql, // 那是给人 review 的规范底稿)。本程序是「没有 mysql 客户端时」的执行器: // - ALTER 走 ensureColumn:先查 information_schema,缺才加,可安全重跑(幂等)。 // - 回填 UPDATE 内联且幂等,重跑无害。 // // 不写任何业务逻辑。 package main import ( "fmt" "log" "gorm.io/driver/mysql" "gorm.io/gorm" ) const dsn = "root:root@tcp(127.0.0.1:3306)/db?charset=utf8mb4&parseTime=True&loc=Local" const han = "[一-鿿]" func main() { db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) if err != nil { log.Fatal("open:", err) } // 1) 幂等加列(与 002_i18n_columns.sql 对齐) ensureColumn(db, "brand", "name_en", "VARCHAR(255) NOT NULL DEFAULT '' AFTER name") ensureColumn(db, "brand", "name_cn", "VARCHAR(255) NOT NULL DEFAULT '' AFTER name_en") ensureColumn(db, "brand_runway", "title_en", "VARCHAR(255) NOT NULL DEFAULT '' AFTER title") ensureColumn(db, "brand_runway", "title_cn", "VARCHAR(255) NOT NULL DEFAULT '' AFTER title_en") ensureColumn(db, "brand_runway", "description_en", "VARCHAR(1024) NOT NULL DEFAULT '' AFTER description") ensureColumn(db, "brand_runway", "description_cn", "VARCHAR(1024) NOT NULL DEFAULT '' AFTER description_en") // 2) 回填品牌名:从 name / show_name 拆拉丁与中文(幂等) db.Exec(fmt.Sprintf(` UPDATE brand SET name_en = CASE WHEN TRIM(REGEXP_REPLACE(name, '%s', '')) <> '' THEN TRIM(REGEXP_REPLACE(name, '%s', '')) WHEN show_name LIKE '%%/%%' THEN TRIM(SUBSTRING_INDEX(show_name, '/', 1)) ELSE '' END, name_cn = CASE WHEN show_name LIKE '%%/%%' AND SUBSTRING_INDEX(show_name, '/', -1) REGEXP '%s' THEN TRIM(SUBSTRING_INDEX(show_name, '/', -1)) WHEN show_name REGEXP '%s' THEN TRIM(show_name) WHEN name REGEXP '%s' THEN TRIM(name) ELSE '' END`, han, han, han, han, han)) // 3) 回填走秀:title / description 当前全英文,直接进 *_en(幂等) db.Exec(`UPDATE brand_runway SET title_en = title, description_en = description, title_cn = '', description_cn = ''`) // 4) 校验 printCounts(db) fmt.Println("migration done.") } func ensureColumn(db *gorm.DB, table, col, def string) { var n int64 db.Raw("SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='db' AND TABLE_NAME=? AND COLUMN_NAME=?", table, col).Scan(&n) if n > 0 { fmt.Printf(" skip %s.%s (exists)\n", table, col) return } stmt := fmt.Sprintf("ALTER TABLE %s ADD COLUMN %s %s", table, col, def) fmt.Println(" +", stmt) if r := db.Exec(stmt); r.Error != nil { log.Fatalf("alter %s.%s failed: %v", table, col, r.Error) } } func printCounts(db *gorm.DB) { type c struct { Tot, En, Cn, Both, Dcn int64 } var b c db.Raw(`SELECT COUNT(*) tot, SUM(CASE WHEN name_en<>'' THEN 1 ELSE 0 END) en, SUM(CASE WHEN name_cn<>'' THEN 1 ELSE 0 END) cn, SUM(CASE WHEN name_en<>'' AND name_cn<>'' THEN 1 ELSE 0 END) both_cnt FROM brand`).Scan(&b) fmt.Printf(" brand: total=%d name_en=%d name_cn=%d both=%d\n", b.Tot, b.En, b.Cn, b.Both) var r c db.Raw(`SELECT COUNT(*) tot, SUM(CASE WHEN title_en<>'' THEN 1 ELSE 0 END) en, SUM(CASE WHEN title_cn<>'' THEN 1 ELSE 0 END) cn, SUM(CASE WHEN description_cn<>'' THEN 1 ELSE 0 END) dcn FROM brand_runway WHERE is_deleted=0`).Scan(&r) fmt.Printf(" runway: total=%d title_en=%d title_cn=%d desc_cn=%d\n", r.Tot, r.En, r.Cn, r.Dcn) type s struct { Name string ShowName string NameEn string NameCn string } var samples []s db.Raw("SELECT name, show_name, name_en, name_cn FROM brand WHERE name REGEXP ? OR show_name REGEXP ? ORDER BY id LIMIT 8", han, han).Scan(&samples) fmt.Println(" brand samples:") for _, x := range samples { fmt.Printf(" name=%-18q show=%-26q -> en=%-18q cn=%-18q\n", x.Name, x.ShowName, x.NameEn, x.NameCn) } }