fix: 追加原始SKU字段兼容迁移 (#259)

This commit is contained in:
chengma
2026-08-18 15:37:12 +08:00
parent a989e0dc12
commit b685d2ce62
3 changed files with 63 additions and 2 deletions
+38 -1
View File
@@ -20,7 +20,7 @@ import (
"cmautobuy/admin/spec"
)
const mysqlSchemaVersion = 28
const mysqlSchemaVersion = 29
// OpenMySQL 打开生产 MySQL 8 数据库。错误信息绝不包含完整 DSN 或密码。
func OpenMySQL(cfg config.DatabaseConfig) (*sql.DB, error) {
@@ -759,9 +759,39 @@ func MigrateMySQL(db *sql.DB) error {
return fmt.Errorf("记录 MySQL schema v28 失败: %w", err)
}
}
if current < 29 {
if err := migrateMySQLV29(db); err != nil {
return fmt.Errorf("执行 MySQL schema v29 失败: %w", err)
}
if err := checkMySQLV29Shape(db); err != nil {
return fmt.Errorf("MySQL schema v29 自检失败,未记录版本: %w", err)
}
if _, err := db.Exec(`INSERT INTO schema_migrations (version, applied_at) VALUES (?, ?)`, 29, time.Now().UTC().Format(time.RFC3339Nano)); err != nil {
return fmt.Errorf("记录 MySQL schema v29 失败: %w", err)
}
}
return CheckMySQLSchema(db)
}
// migrateMySQLV29 修复曾由 v28 中间版本按表默认排序规则创建的原始 SKU 字段。
// 已经是最终结构时直接返回,兼容空库、v27 升级和已记录 v28 的数据库。
func migrateMySQLV29(db *sql.DB) error {
var dataType, nullable, collation string
var length sql.NullInt64
if err := db.QueryRow(`SELECT data_type,character_maximum_length,is_nullable,collation_name
FROM information_schema.columns WHERE table_schema=DATABASE()
AND table_name='syb_inner_code_records' AND column_name='source_sku_raw'`).
Scan(&dataType, &length, &nullable, &collation); err != nil {
return fmt.Errorf("读取档口入库码原始 SKU 字段结构失败: %w", err)
}
if dataType == "varchar" && length.Valid && length.Int64 == 500 && nullable == "YES" && collation == "utf8mb4_bin" {
return nil
}
_, err := db.Exec(`ALTER TABLE syb_inner_code_records
MODIFY COLUMN source_sku_raw VARCHAR(500) COLLATE utf8mb4_bin NULL`)
return err
}
// migrateMySQLV28 保留 Excel 原始 SKU,作为确定性匹配的可审计证据。
func migrateMySQLV28(db *sql.DB) error {
exists, err := mysqlColumnExists(db, "syb_inner_code_records", "source_sku_raw")
@@ -2376,6 +2406,13 @@ func CheckMySQLSchema(db *sql.DB) error {
if err := checkMySQLV27Shape(db); err != nil {
return err
}
if err := checkMySQLV28Shape(db); err != nil {
return err
}
return checkMySQLV29Shape(db)
}
func checkMySQLV29Shape(db *sql.DB) error {
return checkMySQLV28Shape(db)
}
@@ -1260,6 +1260,28 @@ func TestMySQLMigrate_V25升级V26且规格解析审计幂等(t *testing.T) {
}
}
func TestMySQLMigrate_V28升级V29修复原始SKU排序规则(t *testing.T) {
db := openMySQLMigrationTestDB(t)
defer db.Close()
cleanMySQLTestSchema(t, db)
defer cleanMySQLTestSchema(t, db)
if err := MigrateMySQL(db); err != nil {
t.Fatal(err)
}
mustExec(t, db, `ALTER TABLE syb_inner_code_records
MODIFY COLUMN source_sku_raw VARCHAR(500) COLLATE utf8mb4_0900_ai_ci NULL`)
mustExec(t, db, `DELETE FROM schema_migrations WHERE version=29`)
if err := MigrateMySQL(db); err != nil {
t.Fatalf("v28 升级 v29 失败: %v", err)
}
if err := MigrateMySQL(db); err != nil {
t.Fatalf("v29 重复迁移失败: %v", err)
}
if err := checkMySQLV29Shape(db); err != nil {
t.Fatal(err)
}
}
func TestUpsertInnerCodeImportRow_软删除记录按状态安全恢复(t *testing.T) {
db := openMySQLMigrationTestDB(t)
defer db.Close()
+3 -1
View File
@@ -133,6 +133,8 @@ v27(#250)为 `syb_inner_code_records` 增加可空 JSON 字段 `remote_items
多件商品逐个入库码对应的远端明细、来源和动作状态;仍不拆分新的业务表。
v28(#259)为该表增加可空 `source_sku_raw`,保留 Excel 原始 SKU,供匹配阶段与
顺运宝 `sku/variationSku` 做边界去空白后的精确比较。
v29(#259)兼容曾由 v28 中间版本按表默认排序规则创建并记录版本的数据库,把
`source_sku_raw` 统一规范为 `utf8mb4_bin`;字段已经正确时迁移不改数据。
**v3 为什么丢弃旧 `sku_mappings` 数据(见 #20):** 新主键需要 `pdd_option_key`,
这是 Go 的 `service.OptionKey()` 用 `json.Marshal` 算出来的规范化键,SQL 语句
@@ -1029,7 +1031,7 @@ CREATE UNIQUE INDEX idx_client_assignment_current
`ai_spec_match_decisions`。Admin 重启把未完成明细改为 `interrupted` 并完成批次计数,已经
成功写入的 `spec_mappings` 不回滚。
## 17. `syb_inner_code_records` 档口入库码记录(MySQL v23,软删除 v24,后台回写 v25,逐件检查点 v27,原始 SKU v28)
## 17. `syb_inner_code_records` 档口入库码记录(MySQL v23,软删除 v24,后台回写 v25,逐件检查点 v27,原始 SKU v28/v29)
档口入库码使用一张业务表完成导入、匹配、回写和异常恢复。Excel 只是导入载体,系统
不保存原文件、文件哈希,不再拆批次表或尝试记录表。