package main import ( "crypto/aes" "crypto/cipher" "encoding/base64" "encoding/json" "fmt" "log" "os" "os/exec" "path/filepath" "strconv" "strings" "syscall" "time" "github.com/gookit/color" "gopkg.in/ini.v1" ) const ( SECRET_KEY = ")VQbB(vpy=U(wcp)" CLEAN_KEY = "MergePet2024!" ) // GOOS=linux GOARCH=amd64 go build -o /data/devops/MergePet/tool/tool main.go var help = ` Usage: app.ini [options] start start the server stop stop the server restart restart the server reload reload the config status get the server status install install the server uninstall uninstall the server kill kill the server backup backup the database clean clean the database ` var cfg *ini.File var FuncMap map[string]func() error var dirPath string var app_path string var ( infoColor = color.New(color.FgGreen) warn = color.New(color.FgYellow) err = color.New(color.FgRed) ) func info(format string, a ...interface{}) { time := time.Now().Format("2006-01-02 15:04:05") format = fmt.Sprintf("[%s] %s\n", time, format) infoColor.Printf(format, a...) } func main() { defer func() { if err := recover(); err != nil { info("%s", help) } }() FuncMap = make(map[string]func() error) // 检查是否提供了命令行参数 if len(os.Args) < 2 { log.Fatal(help) } var err error // 获取当前文件的绝对路径 execPath, err := os.Executable() if err != nil { log.Fatal(err) } absPath, err := filepath.Abs(execPath) if err != nil { log.Fatal(err) } // 获取当前文件的绝对路径的文件夹路径 dirPath = filepath.Dir(absPath) // 加载 app.ini 文件 cfg, err = ini.Load(dirPath + "/app.ini") if err != nil { log.Fatal(err) } app_path = cfg.Section("app").Key("app_path").String() register("start", start) register("stop", stop) register("install", install) register("status", status) register("restart", restart) register("uninstall", uninstall) register("reload", reload) register("kill", kill) register("backup", backup) register("clean", clean) funcName := os.Args[1] if f, ok := FuncMap[funcName]; ok { e := f() if e != nil { log.Fatal(e) } } else { log.Fatal(help) } } func register(name string, f func() error) { FuncMap[name] = f } func kill() error { if len(os.Args) < 4 { log.Fatal("请输入要停止的服务类型和区号 kill [center|node] 区号") } NodeType := os.Args[2] Zone := os.Args[3] // 示例命令 app_name := cfg.Section("app").Key("app_name").String() processName := fmt.Sprintf("%s/zone/%s_%s_%s/server.json", app_path, app_name, NodeType, Zone) // 示例进程名称 // 获取进程号 pid, err := getPidByArgs(processName) if err != nil { log.Fatal(err) } // 使用命令行命令 kill -9 杀死进程 cmd := exec.Command("kill", "-9", strconv.Itoa(pid)) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr err = cmd.Run() if err != nil { log.Fatal(err) } info("server %s_%s 已被 kill -9 杀死, 进程号:%d", NodeType, Zone, pid) return nil } func install() error { // log.Println("install") if len(os.Args) < 4 { err.Println("请输入要安装的服务类型和区号 install [center|node] 区号") } serverType := os.Args[2] zone := os.Args[3] if serverType != "center" && serverType != "node" { err.Println("请输入正确的服务类型 center|node") } AppName := cfg.Section("app").Key("app_name").String() // 生成服务名 serviceName := fmt.Sprintf("%s_%s_%s", AppName, serverType, zone) // 判断文件夹是否存在 folderPath := fmt.Sprintf("%s/zone/%s", app_path, serviceName) if _, err := os.Stat(folderPath); os.IsNotExist(err) { err := os.MkdirAll(folderPath, os.ModePerm) if err != nil { log.Fatal(err) } info("文件夹 %s 创建成功\n", folderPath) } else { info("文件夹 %s 已存在\n", folderPath) } // 生成配置文件 configPath := fmt.Sprintf("%s/server.json", folderPath) file, err := os.Create(configPath) if err != nil { log.Fatal(err) } defer file.Close() zoneId, _ := strconv.Atoi(zone) cf, confs := createConfigFile(zoneId, serverType) file.WriteString(confs) // 读取 SQL 文件内容并替换 %database% 字符串 sqlFilePath := fmt.Sprintf("%s/tool/Merge_Pet.sql", app_path) sqlContent, err := os.ReadFile(sqlFilePath) if err != nil { log.Fatal(err) } dbName := cf["DbName"].(string) modifiedSQL := strings.ReplaceAll(string(sqlContent), "%database%", dbName) // 将修改后的内容写入临时文件 tempSQLFile, err := os.CreateTemp("", "modified_*.sql") if err != nil { log.Fatal(err) } _, err = tempSQLFile.WriteString(modifiedSQL) if err != nil { log.Fatal(err) } tempSQLFile.Close() // 创建数据库 dbUser := cfg.Section("mysql").Key("mysql_user").String() dbPassword, _ := Decrypt(cfg.Section("mysql").Key("mysql_password").String()) dbHost := cfg.Section("mysql").Key("mysql_host").String() dbPort := cfg.Section("mysql").Key("mysql_port").String() // log.Println("mysql", "-u"+dbUser, "-p"+dbPassword, "-h"+dbHost, "-P"+dbPort, "-e", fmt.Sprintf("CREATE DATABASE IF NOT EXISTS %s", dbName)) createDBCmd := exec.Command("mysql", "-u"+dbUser, "-p"+dbPassword, "-h"+dbHost, "-P"+dbPort, "-e", fmt.Sprintf("CREATE DATABASE IF NOT EXISTS %s", dbName)) createDBCmd.Stdout = os.Stdout createDBCmd.Stderr = os.Stderr err = createDBCmd.Run() if err != nil { log.Fatal(err) } // log.Println("mysql", "-u"+dbUser, "-p"+dbPassword, "-h"+dbHost, "-P"+dbPort, dbName, "-e", fmt.Sprintf("source %s", tempSQLFile.Name())) // 执行修改后的 SQL 文件 cmd := exec.Command("mysql", "-u"+dbUser, "-p"+dbPassword, "-h"+dbHost, "-P"+dbPort, dbName, "-e", fmt.Sprintf("source %s", tempSQLFile.Name())) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr err = cmd.Run() if err != nil { log.Fatal(err) } cmd = exec.Command("mkdir", cf["LogPath"].(string)) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr err = cmd.Run() if err != nil { log.Fatal(err) } info("SQL 文件 %s 执行成功\n", sqlFilePath) info("配置文件 %s 创建成功\n", configPath) return nil } func uninstall() error { stop() if len(os.Args) < 4 { log.Fatal("请输入要卸载的服务类型和区号 uninstall [center|node] 区号") } serverType := os.Args[2] zone := os.Args[3] if serverType != "center" && serverType != "node" { log.Fatal("请输入正确的服务类型 center|node") } AppName := cfg.Section("app").Key("app_name").String() // 生成服务名 serviceName := fmt.Sprintf("%s_%s_%s", AppName, serverType, zone) // 判断文件夹是否存在 folderPath := fmt.Sprintf("%s/zone/%s", app_path, serviceName) if _, err := os.Stat(folderPath); os.IsNotExist(err) { info("文件夹 %s 不存在\n", folderPath) return nil } else { info("文件夹 %s 存在\n", folderPath) } // 删除文件夹 err := os.RemoveAll(folderPath) if err != nil { log.Fatal(err) } info("文件夹 %s 删除成功\n", folderPath) return nil } func createConfigFile(Id int, Type string) (map[string]interface{}, string) { conf := make(map[string]interface{}) conf["LogLevel"] = cfg.Section("log").Key("log_level").String() TcpStartAddr, _ := cfg.Section("server").Key("tcp_addr").Int() conf["TcpAddr"] = fmt.Sprintf(":%d", TcpStartAddr+Id) WsStartAddr, _ := cfg.Section("server").Key("ws_addr").Int() conf["WsAddr"] = fmt.Sprintf(":%d", WsStartAddr+Id) ListenAddr, _ := cfg.Section("server").Key("listen_addr").Int() conf["MySqlAddr"] = cfg.Section("mysql").Key("mysql_host").String() conf["MySqlUsr"] = cfg.Section("mysql").Key("mysql_user").String() conf["MySqlPwd"] = cfg.Section("mysql").Key("mysql_password").String() conf["MySqlPort"] = cfg.Section("mysql").Key("mysql_port").String() MaxConnNum, _ := cfg.Section("server").Key("max_conn").Int() conf["MaxConnNum"] = MaxConnNum app_name := cfg.Section("app").Key("app_name").String() conf["LogPath"] = fmt.Sprintf("%s/zone/%s_%s_%d/log", app_path, app_name, Type, Id) conf["DbName"] = fmt.Sprintf("%s_%d", app_name, Id) conf["TELOGDIR"] = cfg.Section("log").Key("te_log_path").String() conf["GameName"] = app_name AppId, _ := cfg.Section("app").Key("app_id").Int() conf["AppID"] = AppId conf["AppPath"] = app_path conf["ServerType"] = Type conf["ServerID"] = Id conf["KafkaHost"] = cfg.Section("kafka").Key("kafka_host").String() conf["KafkaPort"] = cfg.Section("kafka").Key("kafka_port").String conf["ServerOpenTime"] = "2028-01-01 00:00:00" conf["ServerName"] = fmt.Sprintf("%s_%d", app_name, Id) conf["ServerStatus"] = 1 CenterId, _ := cfg.Section("app").Key("center_id").Int() conf["ServerCenter"] = CenterId conf["CountryCode"] = cfg.Section("app").Key("country_code").String() conf["RedisAddr"] = cfg.Section("redis").Key("redis_host").String() conf["RedisPort"] = cfg.Section("redis").Key("redis_port").String() conf["GameConfPath"] = app_path + "/config/" conf["RemoteAddr"] = fmt.Sprintf("%s:%d", cfg.Section("server").Key("remote").String(), ListenAddr+Id) // 服务器地址 conf["ListenAddr"] = fmt.Sprintf(":%d", ListenAddr+Id) conf["CenterAddr"] = fmt.Sprintf("%s:%s", cfg.Section("cluster").Key("center_host").String(), cfg.Section("cluster").Key("center_port").String()) // 服务器地址 b, _ := json.MarshalIndent(conf, "", " ") return conf, string(b) } func backup() error { if len(os.Args) < 4 { log.Fatal("请输入要启动的服务类型和区号 start [center|node] 区号") } err := status() if err == nil { log.Fatal("节点启动中,请先关闭节点") } info("开始备份:server %s_%s ...", os.Args[2], os.Args[3]) NodeType := os.Args[2] Zone := os.Args[3] // 示例命令 app_name := cfg.Section("app").Key("app_name").String() configName := fmt.Sprintf("%s/zone/%s_%s_%s/server.json", app_path, app_name, NodeType, Zone) jsonData, err := os.ReadFile(configName) if err != nil { log.Fatal(err) } var config map[string]interface{} if err := json.Unmarshal(jsonData, &config); err != nil { log.Fatal(err) } info("读取配置文件 %s 成功", configName) dbUser := cfg.Section("mysql").Key("mysql_user").String() dbPassword, _ := Decrypt(cfg.Section("mysql").Key("mysql_password").String()) dbHost := cfg.Section("mysql").Key("mysql_host").String() dbPort := cfg.Section("mysql").Key("mysql_port").String() dbName := config["DbName"].(string) // 生成备份文件名 backupFile := fmt.Sprintf("%s/zone/%s_%s_%s/%s_backup_%s.sql", app_path, app_name, NodeType, Zone, dbName, time.Now().Format("20060102_150405")) // 执行 mysqldump 命令 cmd := exec.Command("mysqldump", "-u"+dbUser, "-p"+dbPassword, "-h"+dbHost, "-P"+dbPort, dbName, ) outFile, err := os.Create(backupFile) if err != nil { log.Fatal(err) } defer outFile.Close() cmd.Stdout = outFile cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil { log.Fatal(err) } info("数据库 %s 备份成功,文件:%s", dbName, backupFile) return nil } func clean() error { if len(os.Args) < 4 { log.Fatal("请输入要清空的服务类型和区号 clean [center|node] 区号") } err := status() if err == nil { log.Fatal("节点启动中,请先关闭节点") } fmt.Print("请输入操作密钥: ") var inputKey string fmt.Scanln(&inputKey) if inputKey != CLEAN_KEY { log.Fatal("密钥错误,操作终止") } info("开始清空数据库:server %s_%s ...", os.Args[2], os.Args[3]) NodeType := os.Args[2] Zone := os.Args[3] app_name := cfg.Section("app").Key("app_name").String() configName := fmt.Sprintf("%s/zone/%s_%s_%s/server.json", app_path, app_name, NodeType, Zone) jsonData, err := os.ReadFile(configName) if err != nil { log.Fatal(err) } var config map[string]interface{} if err := json.Unmarshal(jsonData, &config); err != nil { log.Fatal(err) } dbUser := cfg.Section("mysql").Key("mysql_user").String() dbPassword, _ := Decrypt(cfg.Section("mysql").Key("mysql_password").String()) dbHost := cfg.Section("mysql").Key("mysql_host").String() dbPort := cfg.Section("mysql").Key("mysql_port").String() dbName := config["DbName"].(string) // 获取所有表名 showTablesCmd := exec.Command("mysql", "-u"+dbUser, "-p"+dbPassword, "-h"+dbHost, "-P"+dbPort, "-N", "-e", "SHOW TABLES IN "+dbName) output, err := showTablesCmd.Output() if err != nil { log.Fatal(err) } tables := strings.Fields(string(output)) if len(tables) == 0 { info("数据库 %s 无表,无需清空", dbName) return nil } // 拼接 TRUNCATE 语句 var stmts []string for _, t := range tables { stmts = append(stmts, fmt.Sprintf("TRUNCATE TABLE `%s`;", t)) } truncateSQL := strings.Join(stmts, " ") // 执行清空 truncateCmd := exec.Command("mysql", "-u"+dbUser, "-p"+dbPassword, "-h"+dbHost, "-P"+dbPort, dbName, "-e", truncateSQL) truncateCmd.Stdout = os.Stdout truncateCmd.Stderr = os.Stderr if err := truncateCmd.Run(); err != nil { log.Fatal(err) } info("数据库 %s 所有表已清空", dbName) redisHost := cfg.Section("redis").Key("redis_host").String() redisPort := cfg.Section("redis").Key("redis_port").String() cmd := exec.Command("redis-cli", "-h", redisHost, "-p", redisPort, "FLUSHALL") cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil { log.Fatal("清空 Redis 失败: ", err) } info("Redis 所有数据已清空") return nil } func start() error { // log.Println("start") if len(os.Args) < 4 { log.Fatal("请输入要启动的服务类型和区号 start [center|node] 区号") } err := status() if err == nil { log.Fatal("节点启动中") } info("正在启动服务:server %s_%s ...", os.Args[2], os.Args[3]) NodeType := os.Args[2] Zone := os.Args[3] // 示例命令 app_name := cfg.Section("app").Key("app_name").String() cmdName := app_path + "/main" cmdArgs := []string{fmt.Sprintf("%s/zone/%s_%s_%s/server.json", app_path, app_name, NodeType, Zone)} // 创建命令 cmd := exec.Command(cmdName, cmdArgs...) cmd.SysProcAttr = &syscall.SysProcAttr{} // 创建管道 stdin, err := cmd.StdinPipe() if err != nil { log.Fatal(err) } cmd.Env = os.Environ() // 打开输出文件 outfile, err := os.OpenFile(fmt.Sprintf("%s/zone/%s_%s_%s/output.log", app_path, app_name, NodeType, Zone), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) if err != nil { log.Fatal(err) } defer outfile.Close() // 重定向标准输出和标准错误到文件 cmd.Stdout = outfile cmd.Stderr = outfile err = cmd.Start() if err != nil { log.Fatal(err) } // 打印进程号 info("server %s_%s已启动, 进程号:%d", os.Args[2], os.Args[3], cmd.Process.Pid) // 发送命令到子进程 go func() { time.Sleep(2 * time.Second) // 等待子进程启动 _, err := stdin.Write([]byte("your_command\n")) if err != nil { log.Fatal(err) } stdin.Close() }() // 释放与子进程相关的资源 err = cmd.Process.Release() if err != nil { log.Fatal(err) } return nil } func reload() error { if len(os.Args) < 4 { log.Fatal("请输入要重启的服务类型和区号 reload [center|node] 区号") } app_name := cfg.Section("app").Key("app_name").String() NodeType := os.Args[2] Zone := os.Args[3] args := fmt.Sprintf("%s_%s_%s", app_name, NodeType, Zone) pid, err := getPidByArgs(args) if err != nil { log.Fatal(err) } // 向进程发送SIGTERM信号 process, err := os.FindProcess(pid) if err != nil { log.Fatal(err) } err = process.Signal(syscall.SIGINT) if err != nil { log.Fatal(err) } fmt.Printf("server %s_%s 已发送SIGINT信号, 进程号: %d\n", NodeType, Zone, pid) return nil } func stop() error { if len(os.Args) < 4 { log.Fatal("请输入要停止的服务类型和区号 stop [center|node] 区号") } err := statusInfo() if err != nil { return err } info("正在关闭服务:server %s_%s ...", os.Args[2], os.Args[3]) NodeType := os.Args[2] Zone := os.Args[3] // 示例命令 app_name := cfg.Section("app").Key("app_name").String() processName := fmt.Sprintf("%s/zone/%s_%s_%s/server.json", app_path, app_name, NodeType, Zone) // 示例进程名称 // 获取进程号 pid, err := getPidByArgs(processName) if err != nil { log.Fatal(err) } // 查找进程 process, err := os.FindProcess(pid) if err != nil { log.Fatal(err) } // 关闭进程 err = process.Signal(syscall.SIGTERM) if err != nil { log.Fatal(err) } info("server %s_%s 已关闭,进程号:%d", NodeType, Zone, pid) return nil } func getPidByArgs(args string) (int, error) { cmd := exec.Command("pgrep", "-f", args) output, err := cmd.Output() if err != nil { return 0, err } // 解析输出,获取第一个匹配的进程号 outputStr := strings.TrimSpace(string(output)) pidStr := strings.Split(outputStr, "\n")[0] pid, err := strconv.Atoi(pidStr) if err != nil { return 0, err } return pid, nil } func status() error { if len(os.Args) < 4 { log.Fatal("请输入要查询的服务类型和区号 status [center|node] 区号") } NodeType := os.Args[2] Zone := os.Args[3] // 示例命令 app_name := cfg.Section("app").Key("app_name").String() processName := fmt.Sprintf("%s/zone/%s_%s_%s/server.json", app_path, app_name, NodeType, Zone) // 示例进程名称 // 获取进程号 pid, err := getPidByArgs(processName) if err != nil { return fmt.Errorf("进程 %s_%s 未启动", NodeType, Zone) } // 查找进程 _, err = os.FindProcess(pid) if err != nil { return fmt.Errorf("进程 %s_%s 未启动", NodeType, Zone) } info("节点 %s_%s 启动中, 进程号 %d\n", NodeType, Zone, pid) return nil } func statusInfo() error { NodeType := os.Args[2] Zone := os.Args[3] // 示例命令 app_name := cfg.Section("app").Key("app_name").String() processName := fmt.Sprintf("%s/zone/%s_%s_%s/server.json", app_path, app_name, NodeType, Zone) // 示例进程名称 // 获取进程号 pid, err := getPidByArgs(processName) if err != nil { return fmt.Errorf("进程 %s_%s 未启动", NodeType, Zone) } // 查找进程 _, err = os.FindProcess(pid) if err != nil { return fmt.Errorf("进程 %s_%s 未启动", NodeType, Zone) } // info.Println("节点 %s_%s 启动中, 进程号 %d\n", NodeType, Zone, pid) return nil } func restart() error { if len(os.Args) < 4 { log.Fatal("请输入要重启的服务类型和区号 restart [center|node] 区号") } err := stop() if err != nil { log.Println(err) } for statusInfo() == nil { time.Sleep(1 * time.Second) } err = start() if err != nil { return err } return nil } // 解密字符串 func Decrypt(cipherText string) (string, error) { cipherTextBytes, err := base64.URLEncoding.DecodeString(cipherText) if err != nil { return "", err } block, err := aes.NewCipher([]byte(SECRET_KEY)) if err != nil { return "", err } if len(cipherTextBytes) < aes.BlockSize { return "", fmt.Errorf("cipherText too short") } iv := cipherTextBytes[:aes.BlockSize] cipherTextBytes = cipherTextBytes[aes.BlockSize:] stream := cipher.NewCFBDecrypter(block, iv) stream.XORKeyStream(cipherTextBytes, cipherTextBytes) return string(cipherTextBytes), nil }