feat: 建立商品颜色映射数据模型 (#293)
This commit is contained in:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user