feat: 建立商品颜色映射数据模型 (#293)

This commit is contained in:
chengma
2026-08-23 23:55:33 +08:00
parent 4ffa4e070a
commit f06ded8a0a
7 changed files with 776 additions and 2 deletions
+100 -2
View File
@@ -20,7 +20,7 @@ import (
"cmautobuy/admin/spec"
)
const mysqlSchemaVersion = 29
const mysqlSchemaVersion = 30
// OpenMySQL 打开生产 MySQL 8 数据库。错误信息绝不包含完整 DSN 或密码。
func OpenMySQL(cfg config.DatabaseConfig) (*sql.DB, error) {
@@ -770,9 +770,64 @@ func MigrateMySQL(db *sql.DB) error {
return fmt.Errorf("记录 MySQL schema v29 失败: %w", err)
}
}
if current < 30 {
if err := migrateMySQLV30(db); err != nil {
return fmt.Errorf("执行 MySQL schema v30 失败: %w", err)
}
if err := checkMySQLV30Shape(db); err != nil {
return fmt.Errorf("MySQL schema v30 自检失败,未记录版本: %w", err)
}
if _, err := db.Exec(`INSERT INTO schema_migrations (version, applied_at) VALUES (?, ?)`, 30, time.Now().UTC().Format(time.RFC3339Nano)); err != nil {
return fmt.Errorf("记录 MySQL schema v30 失败: %w", err)
}
}
return CheckMySQLSchema(db)
}
// migrateMySQLV30 建立商品级颜色映射当前态和追加式审计。
// PDD 商品 ID 属于映射身份的一部分,换商品后旧映射仍保留但不会被当前链路读取。
func migrateMySQLV30(db *sql.DB) error {
statements := []string{
`CREATE TABLE IF NOT EXISTS product_color_mappings (
shopee_goods_id VARCHAR(191) COLLATE utf8mb4_bin NOT NULL,
shopee_color_key VARCHAR(191) COLLATE utf8mb4_bin NOT NULL,
shopee_color_raw VARCHAR(500) COLLATE utf8mb4_bin NOT NULL,
pdd_goods_id VARCHAR(191) COLLATE utf8mb4_bin NOT NULL,
pdd_dimension_key VARCHAR(191) COLLATE utf8mb4_bin NOT NULL,
pdd_color_value VARCHAR(500) COLLATE utf8mb4_bin NOT NULL,
context_version CHAR(64) COLLATE ascii_bin NOT NULL,
mapped_by VARCHAR(191) COLLATE utf8mb4_bin NOT NULL,
mapped_at VARCHAR(35) NOT NULL,
PRIMARY KEY (shopee_goods_id,shopee_color_key,pdd_goods_id),
KEY idx_product_color_mappings_pdd (pdd_goods_id,pdd_dimension_key,pdd_color_value)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci`,
`CREATE TABLE IF NOT EXISTS product_color_mapping_audits (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
shopee_goods_id VARCHAR(191) COLLATE utf8mb4_bin NOT NULL,
shopee_color_key VARCHAR(191) COLLATE utf8mb4_bin NOT NULL,
shopee_color_raw VARCHAR(500) COLLATE utf8mb4_bin NOT NULL,
pdd_goods_id VARCHAR(191) COLLATE utf8mb4_bin NOT NULL,
action VARCHAR(16) COLLATE ascii_bin NOT NULL,
old_dimension_key VARCHAR(191) COLLATE utf8mb4_bin NULL,
old_color_value VARCHAR(500) COLLATE utf8mb4_bin NULL,
new_dimension_key VARCHAR(191) COLLATE utf8mb4_bin NULL,
new_color_value VARCHAR(500) COLLATE utf8mb4_bin NULL,
context_version CHAR(64) COLLATE ascii_bin NOT NULL,
actor_user_id VARCHAR(191) COLLATE utf8mb4_bin NOT NULL,
created_at VARCHAR(35) NOT NULL,
CONSTRAINT chk_product_color_mapping_audit_action CHECK (action IN ('upsert','clear')),
KEY idx_product_color_mapping_audits_identity
(shopee_goods_id,shopee_color_key,pdd_goods_id,created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci`,
}
for i, statement := range statements {
if _, err := db.Exec(statement); err != nil {
return fmt.Errorf("执行颜色映射建表语句 %d 失败: %w", i+1, err)
}
}
return nil
}
// migrateMySQLV29 修复曾由 v28 中间版本按表默认排序规则创建的原始 SKU 字段。
// 已经是最终结构时直接返回,兼容空库、v27 升级和已记录 v28 的数据库。
func migrateMySQLV29(db *sql.DB) error {
@@ -2327,6 +2382,7 @@ func CheckMySQLSchema(db *sql.DB) error {
"ai_spec_match_decisions",
"ai_match_batches", "ai_match_batch_items",
"purchase_spec_resolutions",
"product_color_mappings", "product_color_mapping_audits",
}
if err := checkMySQLSchema(db, mysqlRequiredTables); err != nil {
return err
@@ -2409,7 +2465,49 @@ func CheckMySQLSchema(db *sql.DB) error {
if err := checkMySQLV28Shape(db); err != nil {
return err
}
return checkMySQLV29Shape(db)
if err := checkMySQLV29Shape(db); err != nil {
return err
}
return checkMySQLV30Shape(db)
}
func checkMySQLV30Shape(db *sql.DB) error {
if err := checkMySQLSchema(db, []string{"product_color_mappings", "product_color_mapping_audits"}); err != nil {
return err
}
for table, columns := range map[string][]string{
"product_color_mappings": {
"shopee_goods_id", "shopee_color_key", "shopee_color_raw", "pdd_goods_id",
"pdd_dimension_key", "pdd_color_value", "context_version", "mapped_by", "mapped_at",
},
"product_color_mapping_audits": {
"id", "shopee_goods_id", "shopee_color_key", "shopee_color_raw", "pdd_goods_id",
"action", "old_dimension_key", "old_color_value", "new_dimension_key", "new_color_value",
"context_version", "actor_user_id", "created_at",
},
} {
for _, column := range columns {
exists, err := mysqlColumnExists(db, table, column)
if err != nil || !exists {
return fmt.Errorf("商品颜色映射表 %s 字段 %s 缺失: %v", table, column, err)
}
}
}
for _, item := range []struct{ table, name string }{
{"product_color_mappings", "PRIMARY"},
{"product_color_mappings", "idx_product_color_mappings_pdd"},
{"product_color_mapping_audits", "idx_product_color_mapping_audits_identity"},
} {
exists, err := mysqlIndexExists(db, item.table, item.name)
if err != nil || !exists {
return fmt.Errorf("商品颜色映射索引 %s.%s 缺失: %v", item.table, item.name, err)
}
}
exists, err := mysqlConstraintExists(db, "product_color_mapping_audits", "chk_product_color_mapping_audit_action")
if err != nil || !exists {
return fmt.Errorf("商品颜色映射审计动作约束缺失: %v", err)
}
return nil
}
func checkMySQLV29Shape(db *sql.DB) error {