feat: 迁移 Admin Repository 到 MySQL 8 (#79)

This commit is contained in:
chengma
2026-08-10 02:21:31 +08:00
parent 05793e48b3
commit 13cfced80d
29 changed files with 390 additions and 240 deletions
+36 -3
View File
@@ -13,7 +13,7 @@ import (
"cmautobuy/admin/config"
)
const mysqlSchemaVersion = 1
const mysqlSchemaVersion = 2
// OpenMySQL 打开生产 MySQL 8 数据库。错误信息绝不包含完整 DSN 或密码。
func OpenMySQL(cfg config.DatabaseConfig) (*sql.DB, error) {
@@ -29,6 +29,8 @@ func OpenMySQL(cfg config.DatabaseConfig) (*sql.DB, error) {
driverConfig.ReadTimeout = 30 * time.Second
driverConfig.WriteTimeout = 30 * time.Second
driverConfig.RejectReadOnly = true
// 业务层用 RowsAffected 判断目标行是否存在;重复写入相同值也应算匹配到。
driverConfig.ClientFoundRows = true
driverConfig.Params = map[string]string{
"time_zone": "'+00:00'",
"sql_mode": "'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION'",
@@ -298,6 +300,17 @@ var mysqlSchemaV1 = []string{
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci`,
}
// mysqlSchemaV2 增加首位管理员初始化的并发哨兵。MySQL DDL 会隐式提交,
// 因此每条语句都必须可重放,并在全部成功后才记录版本。
var mysqlSchemaV2 = []string{
`CREATE TABLE IF NOT EXISTS admin_initialization_lock (
id TINYINT PRIMARY KEY,
CHECK (id = 1)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci`,
`INSERT INTO admin_initialization_lock (id) VALUES (1)
ON DUPLICATE KEY UPDATE id = VALUES(id)`,
}
// MigrateMySQL 建立或升级 MySQL schema。生产迁移只能在这里追加新版本。
func MigrateMySQL(db *sql.DB) error {
if _, err := db.Exec(`CREATE TABLE IF NOT EXISTS schema_migrations (
@@ -319,19 +332,39 @@ func MigrateMySQL(db *sql.DB) error {
return fmt.Errorf("执行 MySQL schema v1 第 %d 条失败: %w", i+1, err)
}
}
if err := CheckMySQLSchema(db); err != nil {
if err := checkMySQLSchema(db, requiredTables); err != nil {
return fmt.Errorf("MySQL schema v1 自检失败,未记录版本: %w", err)
}
if _, err := db.Exec(`INSERT INTO schema_migrations (version, applied_at) VALUES (?, ?)`,
1, time.Now().UTC().Format(time.RFC3339Nano)); err != nil {
return fmt.Errorf("记录 MySQL schema v1 失败: %w", err)
}
current = 1
}
if current < 2 {
for i, statement := range mysqlSchemaV2 {
if _, err := db.Exec(statement); err != nil {
return fmt.Errorf("执行 MySQL schema v2 第 %d 条失败: %w", i+1, err)
}
}
if err := CheckMySQLSchema(db); err != nil {
return fmt.Errorf("MySQL schema v2 自检失败,未记录版本: %w", err)
}
if _, err := db.Exec(`INSERT INTO schema_migrations (version, applied_at) VALUES (?, ?)`,
2, time.Now().UTC().Format(time.RFC3339Nano)); err != nil {
return fmt.Errorf("记录 MySQL schema v2 失败: %w", err)
}
}
return CheckMySQLSchema(db)
}
// CheckMySQLSchema 确认所有业务表和关键追加列存在。
func CheckMySQLSchema(db *sql.DB) error {
mysqlRequiredTables := append(append([]string{}, requiredTables...), "admin_initialization_lock")
return checkMySQLSchema(db, mysqlRequiredTables)
}
func checkMySQLSchema(db *sql.DB, tables []string) error {
rows, err := db.Query(`SELECT table_name FROM information_schema.tables
WHERE table_schema = DATABASE() AND table_type = 'BASE TABLE'`)
if err != nil {
@@ -350,7 +383,7 @@ func CheckMySQLSchema(db *sql.DB) error {
return err
}
var missing []string
for _, table := range requiredTables {
for _, table := range tables {
if !existing[table] {
missing = append(missing, table)
}