- server: Go 单二进制骨架,embed 内嵌前端,配置/迁移/HTTP 路由 - store: system.db + event.db 两套迁移,业务表 + 种子数据 - domain: 积分引擎(并列三口径/计分通道/流水/排名/破纪录)+ 68 个测试 - web: Vite + Vue 三入口骨架 + shared 层(类型/API/WebSocket 客户端) - console: Electron 宿主骨架(选屏/防熄屏/进程守护/托盘)
106 lines
2.6 KiB
Go
106 lines
2.6 KiB
Go
package store
|
|
|
|
import (
|
|
"database/sql"
|
|
"fmt"
|
|
"io/fs"
|
|
"log/slog"
|
|
"sort"
|
|
|
|
"git.yeij.top/AskaEth/Scoreboard/server/migrations"
|
|
)
|
|
|
|
const createMigrationsTable = `CREATE TABLE IF NOT EXISTS schema_migrations (
|
|
name TEXT PRIMARY KEY,
|
|
applied_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
)`
|
|
|
|
// Migrate 按文件名顺序迁移系统库(data/system.db)。
|
|
func (s *Store) Migrate() error {
|
|
fsys, err := fs.Sub(migrations.SystemFS, "system")
|
|
if err != nil {
|
|
return fmt.Errorf("定位系统库迁移目录: %w", err)
|
|
}
|
|
return migrate(s.db, fsys, "system")
|
|
}
|
|
|
|
// MigrateEvent 迁移赛事库(event.db)。赛事库由调用方自行打开,
|
|
// 本函数只负责执行 event/ 下的迁移,不持有连接。
|
|
func MigrateEvent(db *sql.DB) error {
|
|
fsys, err := fs.Sub(migrations.EventFS, "event")
|
|
if err != nil {
|
|
return fmt.Errorf("定位赛事库迁移目录: %w", err)
|
|
}
|
|
return migrate(db, fsys, "event")
|
|
}
|
|
|
|
// migrate 执行 fsys 中尚未应用的迁移,每个迁移在独立事务中完成。
|
|
func migrate(db *sql.DB, fsys fs.FS, label string) error {
|
|
if _, err := db.Exec(createMigrationsTable); err != nil {
|
|
return fmt.Errorf("创建 schema_migrations: %w", err)
|
|
}
|
|
|
|
applied, err := appliedMigrations(db)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
names, err := fs.Glob(fsys, "*.sql")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
sort.Strings(names)
|
|
|
|
for _, name := range names {
|
|
if applied[name] {
|
|
continue
|
|
}
|
|
body, err := fs.ReadFile(fsys, name)
|
|
if err != nil {
|
|
return fmt.Errorf("读取迁移 %s: %w", name, err)
|
|
}
|
|
if err := applyMigration(db, name, string(body)); err != nil {
|
|
return err
|
|
}
|
|
slog.Info("已应用迁移", "db", label, "name", name)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func appliedMigrations(db *sql.DB) (map[string]bool, error) {
|
|
rows, err := db.Query(`SELECT name FROM schema_migrations`)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("查询迁移记录: %w", err)
|
|
}
|
|
defer func() { _ = rows.Close() }()
|
|
|
|
applied := make(map[string]bool)
|
|
for rows.Next() {
|
|
var name string
|
|
if err := rows.Scan(&name); err != nil {
|
|
return nil, err
|
|
}
|
|
applied[name] = true
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return applied, nil
|
|
}
|
|
|
|
func applyMigration(db *sql.DB, name, body string) error {
|
|
tx, err := db.Begin()
|
|
if err != nil {
|
|
return fmt.Errorf("开启事务: %w", err)
|
|
}
|
|
defer func() { _ = tx.Rollback() }()
|
|
|
|
if _, err := tx.Exec(body); err != nil {
|
|
return fmt.Errorf("执行迁移 %s: %w", name, err)
|
|
}
|
|
if _, err := tx.Exec(`INSERT INTO schema_migrations(name) VALUES (?)`, name); err != nil {
|
|
return fmt.Errorf("记录迁移 %s: %w", name, err)
|
|
}
|
|
return tx.Commit()
|
|
}
|