feat: 收敛店铺管理和蝦皮分类搜索 (#212 #213)

This commit is contained in:
chengma
2026-08-13 11:47:56 +08:00
parent 1abed648a5
commit 313e9f9151
22 changed files with 389 additions and 464 deletions
+78 -2
View File
@@ -20,7 +20,7 @@ import (
"cmautobuy/admin/spec"
)
const mysqlSchemaVersion = 18
const mysqlSchemaVersion = 19
// OpenMySQL 打开生产 MySQL 8 数据库。错误信息绝不包含完整 DSN 或密码。
func OpenMySQL(cfg config.DatabaseConfig) (*sql.DB, error) {
@@ -640,6 +640,18 @@ func MigrateMySQL(db *sql.DB) error {
if _, err := db.Exec(`INSERT INTO schema_migrations (version, applied_at) VALUES (?, ?)`, 18, time.Now().UTC().Format(time.RFC3339Nano)); err != nil {
return fmt.Errorf("记录 MySQL schema v18 失败: %w", err)
}
current = 18
}
if current < 19 {
if err := migrateMySQLV19(db); err != nil {
return fmt.Errorf("执行 MySQL schema v19 失败: %w", err)
}
if err := checkMySQLV19Shape(db); err != nil {
return fmt.Errorf("MySQL schema v19 自检失败,未记录版本: %w", err)
}
if _, err := db.Exec(`INSERT INTO schema_migrations (version, applied_at) VALUES (?, ?)`, 19, time.Now().UTC().Format(time.RFC3339Nano)); err != nil {
return fmt.Errorf("记录 MySQL schema v19 失败: %w", err)
}
}
return CheckMySQLSchema(db)
}
@@ -795,6 +807,51 @@ func migrateMySQLV18(db *sql.DB) error {
return nil
}
// migrateMySQLV19 把 v18 的渠道别名收敛成唯一店铺名称。
// shop_channel_aliases 暂时作为兼容表保留,其内容完全由 shops 派生,不再允许独立配置。
func migrateMySQLV19(db *sql.DB) error {
tx, err := db.Begin()
if err != nil {
return err
}
defer tx.Rollback()
// v18 同时存在全局启停和 SYB 启停。收敛时任一侧已停用都保持停用,
// 避免升级后意外把原本禁止同步的店铺重新放入同步范围。
if _, err := tx.Exec(`UPDATE shops s JOIN (
SELECT shop_id,MIN(enabled) AS enabled FROM shop_channel_aliases
WHERE channel='syb' GROUP BY shop_id
) a ON a.shop_id=s.shop_id
SET s.enabled=IF(s.enabled=1 AND a.enabled=1,1,0)`); err != nil {
return fmt.Errorf("收敛店铺启停状态失败: %w", err)
}
if _, err := tx.Exec(`DELETE FROM shop_channel_aliases`); err != nil {
return fmt.Errorf("清理旧渠道店铺名称失败: %w", err)
}
for _, channel := range []string{"syb", "shopee"} {
if _, err := tx.Exec(`INSERT INTO shop_channel_aliases
(alias_id,shop_id,channel,alias_name,normalized_alias,enabled,created_at,updated_at)
SELECT CONCAT('v19-',?,'-',SHA2(shop_id,256)),shop_id,?,display_name,normalized_name,enabled,created_at,updated_at
FROM shops`, channel, channel); err != nil {
return fmt.Errorf("生成 %s 店铺兼容数据失败: %w", channel, err)
}
}
if _, err := tx.Exec(`UPDATE syb_orders SET shop_id=NULL`); err != nil {
return fmt.Errorf("清理顺运宝旧店铺关联失败: %w", err)
}
if _, err := tx.Exec(`UPDATE syb_orders so JOIN shops s
ON s.normalized_name=TRIM(so.shop_name) COLLATE utf8mb4_bin SET so.shop_id=s.shop_id`); err != nil {
return fmt.Errorf("按唯一店铺名称重建顺运宝关联失败: %w", err)
}
if _, err := tx.Exec(`UPDATE shopee_products SET shop_id=NULL`); err != nil {
return fmt.Errorf("清理蝦皮旧店铺关联失败: %w", err)
}
if _, err := tx.Exec(`UPDATE shopee_products sp JOIN shops s
ON s.normalized_name=TRIM(sp.shopee_shop_name) COLLATE utf8mb4_bin SET sp.shop_id=s.shop_id`); err != nil {
return fmt.Errorf("按唯一店铺名称重建蝦皮关联失败: %w", err)
}
return tx.Commit()
}
// migrateMySQLV14 增加顺运宝同步店铺准入表和审计统计。
func migrateMySQLV14(db *sql.DB) error {
if _, err := db.Exec(`CREATE TABLE IF NOT EXISTS syb_allowed_shops (
@@ -1790,7 +1847,10 @@ func CheckMySQLSchema(db *sql.DB) error {
if err := checkMySQLV17Shape(db); err != nil {
return err
}
return checkMySQLV18Shape(db)
if err := checkMySQLV18Shape(db); err != nil {
return err
}
return checkMySQLV19Shape(db)
}
func checkMySQLV15Shape(db *sql.DB) error {
@@ -1869,6 +1929,22 @@ func checkMySQLV18Shape(db *sql.DB) error {
return nil
}
func checkMySQLV19Shape(db *sql.DB) error {
var invalid int
err := db.QueryRow(`SELECT COUNT(*) FROM shops s WHERE
(SELECT COUNT(*) FROM shop_channel_aliases a WHERE a.shop_id=s.shop_id AND a.channel='syb')<>1 OR
(SELECT COUNT(*) FROM shop_channel_aliases a WHERE a.shop_id=s.shop_id AND a.channel='shopee')<>1 OR
EXISTS (SELECT 1 FROM shop_channel_aliases a WHERE a.shop_id=s.shop_id AND
(a.alias_name<>s.display_name OR a.normalized_alias<>s.normalized_name OR a.enabled<>s.enabled))`).Scan(&invalid)
if err != nil {
return fmt.Errorf("检查单一店铺名称兼容数据失败: %w", err)
}
if invalid != 0 {
return fmt.Errorf("有 %d 个店铺的渠道兼容数据与唯一店铺名称不一致", invalid)
}
return nil
}
func checkMySQLV14Shape(db *sql.DB) error {
if err := checkMySQLSchema(db, []string{"syb_allowed_shops"}); err != nil {
return err