P0-1 (limits.go): Allow()方法改为全程使用写锁保护counters map读写,避免RLock写入时的data race P0-2 (ticket_workflow.go+ticket_handler.go): Assign/Resolve/Close操作先查询ticket存在性和状态,返回明确的CS_TICKET_4001/CS_TKT_4002/CS_TICKET_4092/CS_TICKET_4093错误码,handler根据错误前缀路由HTTP状态码 P1-1 (ticket_store.go): 移除GetStats中3处手动rows.Close(),只保留defer Close()
65 lines
1.5 KiB
Go
65 lines
1.5 KiB
Go
package postgres
|
|
|
|
import (
|
|
"database/sql"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
)
|
|
|
|
func RunMigrations(db *sql.DB, dir string) error {
|
|
if db == nil {
|
|
return fmt.Errorf("db is nil")
|
|
}
|
|
if dir == "" {
|
|
return fmt.Errorf("migration dir is required")
|
|
}
|
|
entries, err := os.ReadDir(dir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
files := make([]string, 0, len(entries))
|
|
for _, entry := range entries {
|
|
if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".up.sql") {
|
|
continue
|
|
}
|
|
files = append(files, entry.Name())
|
|
}
|
|
sort.Strings(files)
|
|
if _, err := db.Exec(`CREATE TABLE IF NOT EXISTS cs_schema_migrations (version VARCHAR(255) PRIMARY KEY, applied_at TIMESTAMPTZ NOT NULL DEFAULT NOW())`); err != nil {
|
|
return err
|
|
}
|
|
for _, name := range files {
|
|
version := strings.TrimSuffix(name, ".up.sql")
|
|
var exists bool
|
|
if err := db.QueryRow(`SELECT EXISTS (SELECT 1 FROM cs_schema_migrations WHERE version = $1)`, version).Scan(&exists); err != nil {
|
|
return err
|
|
}
|
|
if exists {
|
|
continue
|
|
}
|
|
content, err := os.ReadFile(filepath.Join(dir, name))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
tx, err := db.Begin()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if _, err := tx.Exec(string(content)); err != nil {
|
|
_ = tx.Rollback()
|
|
return fmt.Errorf("apply migration %s: %w", name, err)
|
|
}
|
|
if _, err := tx.Exec(`INSERT INTO cs_schema_migrations(version) VALUES ($1)`, version); err != nil {
|
|
_ = tx.Rollback()
|
|
return err
|
|
}
|
|
if err := tx.Commit(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|