129 lines
3.4 KiB
Go
129 lines
3.4 KiB
Go
|
|
// 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
|
|||
|
|
}
|