// dbtool 数据库初始化工具:读取 sql/init.sql 并逐条执行 // 用法(在 nl-game-api-gin 目录下):go run ./cmd/dbtool -f ../sql/init.sql // 连接参数复用 config.yaml 中的 mysql 配置(不指定库名,脚本自带 CREATE DATABASE) package main import ( "database/sql" "flag" "fmt" "log" "os" "strings" _ "github.com/go-sql-driver/mysql" "nl-game-api-gin/internal/config" ) func main() { file := flag.String("f", "../sql/init.sql", "SQL 脚本路径") conf := flag.String("c", "config.yaml", "配置文件路径") flag.Parse() if err := config.Load(*conf); err != nil { log.Fatalln("配置加载失败:", err) } raw, err := os.ReadFile(*file) if err != nil { log.Fatalln("读取 SQL 文件失败:", err) } // 服务器级 DSN(不带库名),multiStatements 关闭、逐条执行便于定位错误 dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/?charset=utf8mb4&parseTime=false", config.C.MySQL.User, config.C.MySQL.Password, config.C.MySQL.Host, config.C.MySQL.Port) db, err := sql.Open("mysql", dsn) if err != nil { log.Fatalln("连接 MySQL 失败:", err) } defer db.Close() // 限制单连接:保证 USE xiaoyouxi 对后续语句生效 db.SetMaxOpenConns(1) if err := db.Ping(); err != nil { log.Fatalln("MySQL 无法连通,请确认服务已启动且账号密码正确:", err) } stmts := splitSQL(string(raw)) log.Printf("共解析出 %d 条语句,开始执行…\n", len(stmts)) for i, s := range stmts { if _, err := db.Exec(s); err != nil { head := s if len(head) > 80 { head = head[:80] + "…" } log.Fatalf("第 %d 条语句执行失败: %v\n语句开头: %s", i+1, err, head) } } log.Println("✅ 数据库初始化完成(库名 xiaoyouxi,含种子数据与超管 liqi)") } // splitSQL 把脚本按分号拆成独立语句:跟踪引号与注释状态,避免误切字符串内的分号 func splitSQL(text string) []string { var stmts []string var sb strings.Builder inSingle, inDouble, inBacktick, inLineComment := false, false, false, false runes := []rune(text) for i := 0; i < len(runes); i++ { ch := runes[i] // 行注释:遇到换行结束(注释内容不写入语句) if inLineComment { if ch == '\n' { inLineComment = false } continue } // 检测行注释起始(不在任何引号内) if !inSingle && !inDouble && !inBacktick && ch == '-' && i+1 < len(runes) && runes[i+1] == '-' { inLineComment = true i++ continue } // 引号状态翻转(MySQL 字符串内以 \' 转义,需要跳过转义符) switch { case inSingle: if ch == '\\' && i+1 < len(runes) { sb.WriteRune(ch) i++ sb.WriteRune(runes[i]) continue } if ch == '\'' { inSingle = false } case inDouble: if ch == '\\' && i+1 < len(runes) { sb.WriteRune(ch) i++ sb.WriteRune(runes[i]) continue } if ch == '"' { inDouble = false } case inBacktick: if ch == '`' { inBacktick = false } default: if ch == '\'' { inSingle = true } else if ch == '"' { inDouble = true } else if ch == '`' { inBacktick = true } else if ch == ';' { // 语句结束 stmt := strings.TrimSpace(sb.String()) if stmt != "" { stmts = append(stmts, stmt) } sb.Reset() continue } } sb.WriteRune(ch) } // 收尾:最后一条可能没有分号 if tail := strings.TrimSpace(sb.String()); tail != "" { stmts = append(stmts, tail) } return stmts }