feat: 按允许店铺筛选顺运宝同步 (#196)

This commit is contained in:
chengma
2026-08-12 18:48:31 +08:00
parent 4089033453
commit 8a8fff8480
17 changed files with 720 additions and 30 deletions
+98 -2
View File
@@ -20,7 +20,7 @@ import (
"cmautobuy/admin/spec"
)
const mysqlSchemaVersion = 13
const mysqlSchemaVersion = 14
// OpenMySQL 打开生产 MySQL 8 数据库。错误信息绝不包含完整 DSN 或密码。
func OpenMySQL(cfg config.DatabaseConfig) (*sql.DB, error) {
@@ -580,10 +580,61 @@ func MigrateMySQL(db *sql.DB) error {
if _, err := db.Exec(`INSERT INTO schema_migrations (version, applied_at) VALUES (?, ?)`, 13, time.Now().UTC().Format(time.RFC3339Nano)); err != nil {
return fmt.Errorf("记录 MySQL schema v13 失败: %w", err)
}
current = 13
}
if current < 14 {
if err := migrateMySQLV14(db); err != nil {
return fmt.Errorf("执行 MySQL schema v14 失败: %w", err)
}
if err := checkMySQLV14Shape(db); err != nil {
return fmt.Errorf("MySQL schema v14 自检失败,未记录版本: %w", err)
}
if _, err := db.Exec(`INSERT INTO schema_migrations (version, applied_at) VALUES (?, ?)`, 14, time.Now().UTC().Format(time.RFC3339Nano)); err != nil {
return fmt.Errorf("记录 MySQL schema v14 失败: %w", err)
}
}
return CheckMySQLSchema(db)
}
// migrateMySQLV14 增加顺运宝同步店铺准入表和审计统计。
func migrateMySQLV14(db *sql.DB) error {
if _, err := db.Exec(`CREATE TABLE IF NOT EXISTS syb_allowed_shops (
shop_id VARCHAR(191) COLLATE utf8mb4_bin PRIMARY KEY,
shop_name VARCHAR(500) NOT NULL,
normalized_name VARCHAR(500) COLLATE utf8mb4_bin NOT NULL,
enabled TINYINT NOT NULL DEFAULT 1,
created_by_user_id VARCHAR(191) COLLATE utf8mb4_bin NOT NULL,
created_at VARCHAR(35) NOT NULL,
updated_at VARCHAR(35) NOT NULL,
UNIQUE KEY uq_syb_allowed_shops_name (normalized_name),
KEY idx_syb_allowed_shops_enabled (enabled, normalized_name),
CONSTRAINT fk_syb_allowed_shops_user FOREIGN KEY (created_by_user_id) REFERENCES users(user_id),
CONSTRAINT chk_syb_allowed_shops_enabled CHECK (enabled IN (0,1))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci`); err != nil {
return fmt.Errorf("建立顺运宝允许店铺表失败: %w", err)
}
columns := []struct{ name, ddl string }{
{"accepted_stock_count", `ALTER TABLE syb_sync_runs ADD COLUMN accepted_stock_count BIGINT NOT NULL DEFAULT 0 AFTER stock_count`},
{"shop_skipped_count", `ALTER TABLE syb_sync_runs ADD COLUMN shop_skipped_count BIGINT NOT NULL DEFAULT 0 AFTER accepted_stock_count`},
{"shop_filter_hash", `ALTER TABLE syb_sync_runs ADD COLUMN shop_filter_hash VARCHAR(64) COLLATE utf8mb4_bin NULL AFTER shop_skipped_count`},
}
for _, column := range columns {
exists, err := mysqlColumnExists(db, "syb_sync_runs", column.name)
if err != nil {
return err
}
if !exists {
if _, err := db.Exec(column.ddl); err != nil {
return fmt.Errorf("增加 syb_sync_runs.%s 失败: %w", column.name, err)
}
}
}
if _, err := db.Exec(`UPDATE syb_sync_runs SET accepted_stock_count=stock_count, shop_skipped_count=0 WHERE shop_filter_hash IS NULL`); err != nil {
return fmt.Errorf("回填顺运宝同步店铺统计失败: %w", err)
}
return nil
}
// migrateMySQLV13 把任务真实主键迁为采集 cjN、采购 cgN 两套独立业务单号。
//
// 外键 DDL 可重放;主键和关联数据在单一事务中同时切换。若进程在记录版本前退出,
@@ -1490,6 +1541,7 @@ func CheckMySQLSchema(db *sql.DB) error {
"catalog_import_runs",
"task_syb_sources",
"task_sequences",
"syb_allowed_shops",
}
if err := checkMySQLSchema(db, mysqlRequiredTables); err != nil {
return err
@@ -1524,7 +1576,51 @@ func CheckMySQLSchema(db *sql.DB) error {
if err := checkMySQLV12Shape(db); err != nil {
return err
}
return checkMySQLV13Shape(db)
if err := checkMySQLV13Shape(db); err != nil {
return err
}
return checkMySQLV14Shape(db)
}
func checkMySQLV14Shape(db *sql.DB) error {
if err := checkMySQLSchema(db, []string{"syb_allowed_shops"}); err != nil {
return err
}
for _, column := range []struct {
table, name string
length int64
nullable bool
collation, defVal string
}{
{"syb_allowed_shops", "shop_id", 191, false, "utf8mb4_bin", ""},
{"syb_allowed_shops", "shop_name", 500, false, "utf8mb4_0900_ai_ci", ""},
{"syb_allowed_shops", "normalized_name", 500, false, "utf8mb4_bin", ""},
{"syb_allowed_shops", "created_by_user_id", 191, false, "utf8mb4_bin", ""},
{"syb_sync_runs", "shop_filter_hash", 64, true, "utf8mb4_bin", ""},
} {
if err := checkMySQLVarcharColumn(db, column.table, column.name, column.length, column.nullable, column.collation, column.defVal); err != nil {
return err
}
}
for _, name := range []string{"accepted_stock_count", "shop_skipped_count"} {
exists, err := mysqlColumnExists(db, "syb_sync_runs", name)
if err != nil || !exists {
return fmt.Errorf("顺运宝同步记录字段 %s 缺失", name)
}
}
for _, name := range []string{"uq_syb_allowed_shops_name", "idx_syb_allowed_shops_enabled"} {
exists, err := mysqlIndexExists(db, "syb_allowed_shops", name)
if err != nil || !exists {
return fmt.Errorf("顺运宝允许店铺索引 %s 缺失", name)
}
}
for _, name := range []string{"fk_syb_allowed_shops_user", "chk_syb_allowed_shops_enabled"} {
exists, err := mysqlConstraintExists(db, "syb_allowed_shops", name)
if err != nil || !exists {
return fmt.Errorf("顺运宝允许店铺约束 %s 缺失", name)
}
}
return nil
}
func checkMySQLV9Shape(db *sql.DB) error {