feat: 按创建人隔离采集采购任务 (#127)
This commit is contained in:
@@ -19,7 +19,7 @@ import (
|
||||
"cmautobuy/admin/spec"
|
||||
)
|
||||
|
||||
const mysqlSchemaVersion = 5
|
||||
const mysqlSchemaVersion = 6
|
||||
|
||||
// OpenMySQL 打开生产 MySQL 8 数据库。错误信息绝不包含完整 DSN 或密码。
|
||||
func OpenMySQL(cfg config.DatabaseConfig) (*sql.DB, error) {
|
||||
@@ -483,10 +483,55 @@ func MigrateMySQL(db *sql.DB) error {
|
||||
if _, err := db.Exec(`INSERT INTO schema_migrations (version, applied_at) VALUES (?, ?)`, 5, time.Now().UTC().Format(time.RFC3339Nano)); err != nil {
|
||||
return fmt.Errorf("记录 MySQL schema v5 失败: %w", err)
|
||||
}
|
||||
current = 5
|
||||
}
|
||||
if current < 6 {
|
||||
if err := migrateMySQLV6(db); err != nil {
|
||||
return fmt.Errorf("执行 MySQL schema v6 失败: %w", err)
|
||||
}
|
||||
if err := checkMySQLV6Shape(db); err != nil {
|
||||
return fmt.Errorf("MySQL schema v6 自检失败,未记录版本: %w", err)
|
||||
}
|
||||
if _, err := db.Exec(`INSERT INTO schema_migrations (version, applied_at) VALUES (?, ?)`, 6, time.Now().UTC().Format(time.RFC3339Nano)); err != nil {
|
||||
return fmt.Errorf("记录 MySQL schema v6 失败: %w", err)
|
||||
}
|
||||
}
|
||||
return CheckMySQLSchema(db)
|
||||
}
|
||||
|
||||
// migrateMySQLV6 给任务补充创建人。存量任务保持 NULL,明确标记为历史任务;
|
||||
// 新代码创建任务时写入当前网页登录用户。DDL 逐项检查,支持中断后重放。
|
||||
func migrateMySQLV6(db *sql.DB) error {
|
||||
exists, err := mysqlColumnExists(db, "tasks", "created_by_user_id")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !exists {
|
||||
if _, err := db.Exec(`ALTER TABLE tasks ADD COLUMN created_by_user_id VARCHAR(191) COLLATE utf8mb4_bin NULL AFTER live_confirmed_at`); err != nil {
|
||||
return fmt.Errorf("增加 tasks.created_by_user_id 失败: %w", err)
|
||||
}
|
||||
}
|
||||
constraintExists, err := mysqlConstraintExists(db, "tasks", "fk_tasks_created_by")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !constraintExists {
|
||||
if _, err := db.Exec(`ALTER TABLE tasks ADD CONSTRAINT fk_tasks_created_by FOREIGN KEY (created_by_user_id) REFERENCES users(user_id)`); err != nil {
|
||||
return fmt.Errorf("增加任务创建人外键失败: %w", err)
|
||||
}
|
||||
}
|
||||
indexExists, err := mysqlIndexExists(db, "tasks", "idx_tasks_creator_list")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !indexExists {
|
||||
if _, err := db.Exec(`ALTER TABLE tasks ADD INDEX idx_tasks_creator_list (created_by_user_id, updated_at DESC, task_id DESC)`); err != nil {
|
||||
return fmt.Errorf("增加任务创建人列表索引失败: %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// migrateMySQLV5 给任务增加不可变执行模式和真实下单创建审计。
|
||||
// 每条 DDL 都先检查存在性,MySQL 在任意一步隐式提交后都可以安全重放。
|
||||
func migrateMySQLV5(db *sql.DB) error {
|
||||
@@ -808,7 +853,28 @@ func CheckMySQLSchema(db *sql.DB) error {
|
||||
if err := checkMySQLV4Shape(db); err != nil {
|
||||
return err
|
||||
}
|
||||
return checkMySQLV5Shape(db)
|
||||
if err := checkMySQLV5Shape(db); err != nil {
|
||||
return err
|
||||
}
|
||||
return checkMySQLV6Shape(db)
|
||||
}
|
||||
|
||||
func checkMySQLV6Shape(db *sql.DB) error {
|
||||
if err := checkMySQLVarcharColumn(db, "tasks", "created_by_user_id", 191, true, "utf8mb4_bin", ""); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := checkMySQLNullDefault(db, "tasks", "created_by_user_id"); err != nil {
|
||||
return err
|
||||
}
|
||||
var referencedTable, referencedColumn string
|
||||
if err := db.QueryRow(`SELECT referenced_table_name, referenced_column_name FROM information_schema.key_column_usage WHERE constraint_schema=DATABASE() AND table_name='tasks' AND constraint_name='fk_tasks_created_by' AND column_name='created_by_user_id'`).Scan(&referencedTable, &referencedColumn); err != nil || referencedTable != "users" || referencedColumn != "user_id" {
|
||||
return fmt.Errorf("任务创建人外键不正确")
|
||||
}
|
||||
var cols string
|
||||
if err := db.QueryRow(`SELECT GROUP_CONCAT(CONCAT(column_name,':',collation) ORDER BY seq_in_index) FROM information_schema.statistics WHERE table_schema=DATABASE() AND table_name='tasks' AND index_name='idx_tasks_creator_list'`).Scan(&cols); err != nil || cols != "created_by_user_id:A,updated_at:D,task_id:D" {
|
||||
return fmt.Errorf("任务创建人列表索引不正确")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkMySQLV5Shape(db *sql.DB) error {
|
||||
|
||||
Reference in New Issue
Block a user