package main import ( "encoding/json" "flag" "fmt" "go_dreamfactory/comm" "go_dreamfactory/lego" "go_dreamfactory/lego/core" "go_dreamfactory/lego/sys/log" "io/ioutil" "net" "os" "os/exec" "path/filepath" "strconv" "strings" "time" "github.com/spf13/cobra" "golang.org/x/crypto/ssh" "gopkg.in/yaml.v3" ) /* 服务类型:区服启动程序 服务描述:通过读取游戏json配置,启动服务程序 */ var ( gmpath string //服务列表下标 crosspath string //服务列表下标 sid string //服务列表下标 openlog bool //日志路径 ) var confCmd = &cobra.Command{ Use: "conf", Short: "生成配置", Run: func(cmd *cobra.Command, args []string) { lego.Recover("conf") conf() }, } var startCmd = &cobra.Command{ Use: "start", Short: "启动程序", Run: func(cmd *cobra.Command, args []string) { lego.Recover("start") start() }, } var stopCmd = &cobra.Command{ Use: "stop", Short: "关闭程序", Run: func(cmd *cobra.Command, args []string) { lego.Recover("stop") stop() }, } var restart = &cobra.Command{ Use: "restart", Short: "重启服务", Run: func(cmd *cobra.Command, args []string) { lego.Recover("restart") stop() start() }, } var sync = &cobra.Command{ Use: "sync", Short: "同步服务器", Run: func(cmd *cobra.Command, args []string) { lego.Recover("sync") syncServer() }, } func emptyRun(*cobra.Command, []string) {} var RootCmd = &cobra.Command{ Use: "dreamfactory", Short: "命令行", Long: "命令行工具", Run: emptyRun, } // 初始化自定义cmd func init() { RootCmd.PersistentFlags().StringVarP(&gmpath, "gm", "g", "./gm.json", "游戏区服配置") RootCmd.PersistentFlags().StringVarP(&crosspath, "cross", "c", "./cross.json", "游戏跨服配置") RootCmd.PersistentFlags().StringVarP(&sid, "sid", "i", "", "区服id") RootCmd.PersistentFlags().BoolVarP(&openlog, "log", "l", false, "输出日志") RootCmd.AddCommand(confCmd, startCmd, stopCmd, restart, sync) } func main() { flag.Parse() if err := log.OnInit(nil, log.SetFileName("./s.log"), log.SetLoglevel(log.DebugLevel), log.SetUniqueLog(true), log.SetIsDebug(true)); err != nil { panic(fmt.Sprintf("Sys log Init err:%v", err)) } else { log.Infof("Sys log Init success !") } Execute() } // 执行命令 func Execute() { if err := RootCmd.Execute(); err != nil { log.Errorln(err) os.Exit(1) } } // 生成配置 func conf() { if config, err := readergmconf(gmpath); err != nil { log.Error("读取区服配置失败!", log.Field{Key: "err", Value: err.Error()}) return } else { if ss, err := rederServiceSttings(config); err != nil { log.Error("转换服务配置异常!", log.Field{Key: "err", Value: err.Error()}) return } else { for _, v := range ss { if sid == "" || fmt.Sprintf("%s_%s", v.Tag, sid) == v.Id { if err = writeServiceConfig(fmt.Sprintf("./conf/%s.yaml", v.Id), v); err != nil { log.Error("写入配置文件失败!", log.Field{Key: "err", Value: err.Error()}) return } } } } } log.Debugf("conf succ!") } // 启动程序 func start() { if config, err := readergmconf(gmpath); err != nil { log.Error("读取区服配置失败!", log.Field{Key: "err", Value: err.Error()}) } else { var ( maintes *core.ServiceSttings workers []*core.ServiceSttings = make([]*core.ServiceSttings, 0) gateways []*core.ServiceSttings = make([]*core.ServiceSttings, 0) ) if ss, err := rederServiceSttings(config); err != nil { log.Error("转换服务配置异常!", log.Field{Key: "err", Value: err.Error()}) } else { for _, v := range ss { if sid == "" || fmt.Sprintf("%s_%s", v.Tag, sid) == v.Id { if err = writeServiceConfig(fmt.Sprintf("./conf/%s.yaml", v.Id), v); err != nil { log.Error("写入配置文件失败!", log.Field{Key: "err", Value: err.Error()}) return } switch v.Type { case comm.Service_Gateway: //网关服务 gateways = append(gateways, v) break case comm.Service_Worker: //业务服务 workers = append(workers, v) break case comm.Service_Mainte: //维护服务 maintes = v break default: err = fmt.Errorf("服务类型异常 stype:%s", v.Type) return } } } } //优先启动 维护服 if maintes != nil { if err = startService(maintes); err != nil { log.Error("启动服务失败!", log.Field{Key: "id", Value: maintes.Id}, log.Field{Key: "err", Value: err.Error()}, ) return } } time.Sleep(time.Second * 3) // 业务服 for _, v := range workers { if err = startService(v); err != nil { log.Error("启动服务失败!", log.Field{Key: "id", Value: v.Id}, log.Field{Key: "err", Value: err.Error()}, ) return } } time.Sleep(time.Second * 3) // 网关服 for _, v := range gateways { if err = startService(v); err != nil { log.Error("启动服务失败!", log.Field{Key: "id", Value: v.Id}, log.Field{Key: "err", Value: err.Error()}, ) return } } } log.Errorf("start succ!") } // 关闭程序 func stop() { if config, err := readergmconf(gmpath); err != nil { log.Error("读取区服配置失败!", log.Field{Key: "err", Value: err.Error()}) } else { if ss, err := rederServiceSttings(config); err != nil { log.Error("转换服务配置异常!", log.Field{Key: "err", Value: err.Error()}) } else { for _, v := range ss { if sid == "" || fmt.Sprintf("%s_%s", v.Tag, sid) == v.Id { stopService(v) } } } } log.Infof("stop succ!") } //同步服务器 func syncServer() { if sid != "" { switch sid { case "qa": exesshcomd("10.0.0.9", ` curl -XPOST -s -L 'https://oapi.dingtalk.com/robot/send?access_token=c6d2066cd4b36882b5dc3033e359a1c1b259eb4fd6cb69f397a65f544dbce86f' -H 'Content-Type: application/json' -H "charset:utf-8" -d '{"msgtype": "text","text": {"content": "* 服务准备同步--QA测试服"}}'; cd /home/liwei/dreamworks; svn revert -R . ; svn update sudo cp -f /home/liwei/go_dreamfactory/bin/cmd /home/liwei/dreamworks/cmd; sudo cp -f /home/liwei/go_dreamfactory/bin/gateway /home/liwei/dreamworks/gateway; sudo cp -f /home/liwei/go_dreamfactory/bin/mainte /home/liwei/dreamworks/mainte; sudo cp -f /home/liwei/go_dreamfactory/bin/worker /home/liwei/dreamworks/worker; sudo cp -f /home/liwei/go_dreamfactory/bin/json/* /home/liwei/dreamworks/json/; sudo cp -f /home/liwei/go_dreamfactory/bin/wordfilter.txt /home/liwei/dreamworks/wordfilter.txt; cd /home/liwei/dreamworks; svn add . --no-ignore --force ; svn commit -m "同步服务器" *; curl -XPOST -s -L 'https://oapi.dingtalk.com/robot/send?access_token=c6d2066cd4b36882b5dc3033e359a1c1b259eb4fd6cb69f397a65f544dbce86f' -H 'Content-Type: application/json' -H "charset:utf-8" -d '{"msgtype": "text","text": {"content": "* 服务停止--QA测试服"}}'; `) exesshcomd("101.35.121.71", ` cd /data/dreamworksserver/s80; python stopserver.py; python install.py; python start.py; cd /data/dreamworksserver/s100; python stopserver.py; python install.py; python start.py; `) exesshcomd("101.35.125.220", ` cd /data/dreamworksserver/s90; python stopserver.py; python install.py; python start.py; cd /data/dreamworksserver/s110; python stopserver.py; python install.py; python start.py; curl -XPOST -s -L 'https://oapi.dingtalk.com/robot/send?access_token=c6d2066cd4b36882b5dc3033e359a1c1b259eb4fd6cb69f397a65f544dbce86f' -H 'Content-Type: application/json' -H "charset:utf-8" -d '{"msgtype": "text","text": {"content": "* 服务启动--QA测试服"}}'; `) case "dw": exesshcomd("10.0.0.9", ` curl -XPOST -s -L 'https://oapi.dingtalk.com/robot/send?access_token=c6d2066cd4b36882b5dc3033e359a1c1b259eb4fd6cb69f397a65f544dbce86f' -H 'Content-Type: application/json' -H "charset:utf-8" -d '{"msgtype": "text","text": {"content": "* 服务准备同步--DW测试服"}}'; cd /home/liwei/dreamworks; svn revert -R . ; svn update sudo cp -f /home/liwei/go_dreamfactory/bin/cmd /home/liwei/dreamworks/cmd; sudo cp -f /home/liwei/go_dreamfactory/bin/gateway /home/liwei/dreamworks/gateway; sudo cp -f /home/liwei/go_dreamfactory/bin/mainte /home/liwei/dreamworks/mainte; sudo cp -f /home/liwei/go_dreamfactory/bin/worker /home/liwei/dreamworks/worker; sudo cp -f /home/liwei/go_dreamfactory/bin/json/* /home/liwei/dreamworks/json/; sudo cp -f /home/liwei/go_dreamfactory/bin/wordfilter.txt /home/liwei/dreamworks/wordfilter.txt; cd /home/liwei/dreamworks; svn add . --no-ignore --force ; svn commit -m "同步服务器" *; curl -XPOST -s -L 'https://oapi.dingtalk.com/robot/send?access_token=c6d2066cd4b36882b5dc3033e359a1c1b259eb4fd6cb69f397a65f544dbce86f' -H 'Content-Type: application/json' -H "charset:utf-8" -d '{"msgtype": "text","text": {"content": "* 服务停止--DW测试服"}}'; `) exesshcomd("101.35.121.71", ` cd /data/dreamworksserver/s40; python stopserver.py; python install.py; python start.py; `) exesshcomd("101.35.125.220", ` cd /data/dreamworksserver/s50; python stopserver.py; python install.py; python start.py; curl -XPOST -s -L 'https://oapi.dingtalk.com/robot/send?access_token=c6d2066cd4b36882b5dc3033e359a1c1b259eb4fd6cb69f397a65f544dbce86f' -H 'Content-Type: application/json' -H "charset:utf-8" -d '{"msgtype": "text","text": {"content": "* 服务启动--DW测试服"}}'; `) case "battle": exesshcomd("10.0.0.9", ` cd /home/liwei/dfbattle/output; ./stop.sh; cd /home/liwei/fightdll; svn update; sudo cp -f /home/liwei/fightdll/FightRunner.dll /home/liwei/dfbattle/lib/FightRunner.dll; sudo cp -f /home/liwei/fightdll/GameFight.dll /home/liwei/dfbattle/lib/GameFight.dll; sudo cp -f /home/liwei/fightdll/GameProto.dll /home/liwei/dfbattle/lib/GameProto.dll; sudo cp -r -f /home/liwei/fightdll/GameConfig/* /home/liwei/dfbattle/GameConfig/; cd /home/liwei/dfbattle; dotnet clean; dotnet build -o output; sudo cp -r -f /home/liwei/dfbattle/GameConfig/* /home/liwei/dfbattle/output/GameConfig/; cd /home/liwei/dfbattle/output; ./start.sh ; cd /home/liwei/dfbattle; git add ./lib/* ./GameConfig/*; git commit -m 同步战斗服; git push; `) } } } func exesshcomd(addr, cmd string) (err error) { var ( config *ssh.ClientConfig sshClient *ssh.Client session *ssh.Session combo []byte ) //创建sshp登陆配置 config = &ssh.ClientConfig{ Timeout: time.Second, //ssh 连接time out 时间一秒钟, 如果ssh验证错误 会在一秒内返回 User: "root", HostKeyCallback: ssh.InsecureIgnoreHostKey(), //这个可以, 但是不够安全 } config.Auth = []ssh.AuthMethod{ssh.Password("Legu.cc()123")} //dial 获取ssh client sshClient, err = ssh.Dial("tcp", fmt.Sprintf("%s:22", addr), config) if err != nil { log.Fatal("创建ssh client 失败", log.Field{Key: "err", Value: err.Error()}) } defer sshClient.Close() //创建ssh-session session, err = sshClient.NewSession() if err != nil { log.Fatal("创建ssh session 失败", log.Field{Key: "err", Value: err.Error()}) } defer session.Close() //执行远程命令 combo, err = session.CombinedOutput(cmd) if err != nil { log.Fatal("远程执行cmd 失败", log.Field{Key: "err", Value: err.Error()}) } log.Println("命令输出:", string(combo)) return } // /转换区服配置到服务配置 func rederServiceSttings(config *comm.GameConfig) (ss []*core.ServiceSttings, err error) { ss = make([]*core.ServiceSttings, 0) var ( ip string port int sseting *core.ServiceSttings ) if config.Mainte != "" { if ip, port, err = parseaddr(config.Mainte); err != nil { return } else { if sseting, err = convertServiceSttings(config, 0, comm.Service_Mainte, ip, config.MaintePort, port, config.OpenServiceTime); err != nil { return } ss = append(ss, sseting) } } for i, v := range config.Workers { if ip, port, err = parseaddr(v); err != nil { return } else { if sseting, err = convertServiceSttings(config, i, comm.Service_Worker, ip, port, 0, config.OpenServiceTime); err != nil { return } ss = append(ss, sseting) } } for i, v := range config.Gateways { if ip, port, err = parseaddr(v); err != nil { return } else { if sseting, err = convertServiceSttings(config, i, comm.Service_Gateway, ip, config.GatewayPorts[i], port, config.OpenServiceTime); err != nil { return } ss = append(ss, sseting) } } return } // 读取游戏配置文件 func readergmconf(path string) (config *comm.GameConfig, err error) { config = &comm.GameConfig{} var ( jsonFile *os.File byteValue []byte ) if jsonFile, err = os.Open(path); err != nil { return } else { defer jsonFile.Close() if byteValue, err = ioutil.ReadAll(jsonFile); err != nil { return } err = json.Unmarshal(byteValue, config) } return } // 转换游戏服务配置 func convertServiceSttings(config *comm.GameConfig, id int, stype string, ip string, rport int, lport int, opentime string) (sseting *core.ServiceSttings, err error) { sseting = &core.ServiceSttings{} sseting.Tag = config.AreaId sseting.Ip = ip sseting.Port = rport sseting.Opentime = opentime sseting.Modules = make(map[string]map[string]interface{}) sseting.Sys = make(map[string]map[string]interface{}) sseting.Sys["rpcx"] = map[string]interface{}{ "ConsulServers": config.ConsulAddr, } switch stype { case comm.Service_Gateway: //网关服务 sseting.Id = fmt.Sprintf("%s_%s%d", config.AreaId, comm.Service_Gateway, id) sseting.Type = comm.Service_Gateway sseting.Sys["rpcx"]["RpcxStartType"] = 1 sseting.Modules["gateway"] = map[string]interface{}{ "ListenPort": lport, } break //业务服务 case comm.Service_Worker: sseting.Id = fmt.Sprintf("%s_%s%d", config.AreaId, comm.Service_Worker, id) sseting.Type = comm.Service_Worker sseting.Sys["rpcx"]["RpcxStartType"] = 2 if config.BattleAddr != "" { sseting.Modules["battle"] = map[string]interface{}{ "OpenCheck": config.BattleOpen, "BattleServerAddr": config.BattleAddr, } } else { sseting.Modules["battle"] = map[string]interface{}{ "OpenCheck": false, } } break case comm.Service_Mainte: //维护服务 sseting.Id = fmt.Sprintf("%s_%s", config.AreaId, comm.Service_Mainte) sseting.Type = comm.Service_Mainte sseting.Sys["rpcx"]["RpcxStartType"] = 2 sseting.Modules["web"] = map[string]interface{}{ "WebDir": "./dist", "Port": lport, "Key": "@234%67g12q4*67m12#4l67!", "Debug": true, } break default: err = fmt.Errorf("服务类型异常 stype:%s", sseting.Type) return } if config.OpenGM { sseting.Modules["chat"] = map[string]interface{}{ "GM": true, } } if openlog { sseting.Sys["rpcx"]["Debug"] = true sseting.Sys["log"] = map[string]interface{}{ "FileName": fmt.Sprintf("./log/%s.log", sseting.Id), "IsDebug": true, "Loglevel": log.DebugLevel, "MaxAgeTime": 7, } } else { sseting.Sys["log"] = map[string]interface{}{ "Alias": sseting.Id, "FileName": fmt.Sprintf("./log/%s.log", sseting.Id), "IsDebug": false, "Loglevel": log.InfoLevel, "MaxAgeTime": 7, } } sseting.Sys["configure"] = map[string]interface{}{ "ConfigurePath": "./json", "TimestampFile": "./timestamp.text", } sseting.Sys["wordfilter"] = map[string]interface{}{ "WorldFile": []string{"./wordfilter.json", "./wordfilter.txt"}, } sseting.Sys["db"] = map[string]interface{}{ "IsCross": config.IsCross, "CrossChannel": config.BelongCrossServerId, "RedisIsCluster": config.LoaclDB.RedisIsCluster, "RedisAddr": config.LoaclDB.RedisAddr, "RedisPassword": config.LoaclDB.RedisPassword, "RedisDB": config.LoaclDB.RedisDB, "MongodbUrl": config.LoaclDB.MongodbUrl, "MongodbDatabase": config.LoaclDB.MongodbDatabase, "CrossConfig": crosspath, } return } // 启动服务程序 func startService(sseting *core.ServiceSttings) (err error) { var ( cmd *exec.Cmd command string confpath string = fmt.Sprintf("./conf/%s.yaml", sseting.Id) output []byte ) switch sseting.Type { case comm.Service_Gateway: //网关服务 command = fmt.Sprintf("./stup.sh start %s gateway %s", sseting.Id, confpath) break case comm.Service_Worker: //业务服务 command = fmt.Sprintf("./stup.sh start %s worker %s", sseting.Id, confpath) break case comm.Service_Mainte: //维护服务 command = fmt.Sprintf("./stup.sh start %s mainte %s", sseting.Id, confpath) break default: err = fmt.Errorf("服务类型异常 stype:%s", sseting.Type) return } log.Debug("启动外部命令", log.Field{Key: "cmd", Value: command}) cmd = exec.Command("/bin/bash", "-c", command) if output, err = cmd.CombinedOutput(); err != nil { return } log.Debugln(string(output)) return } // 启动服务程序 func stopService(sseting *core.ServiceSttings) (err error) { var ( cmd *exec.Cmd command string output []byte ) switch sseting.Type { case comm.Service_Gateway: //网关服务 command = fmt.Sprintf("./stup.sh stop %s ", sseting.Id) break case comm.Service_Worker: //业务服务 command = fmt.Sprintf("./stup.sh stop %s", sseting.Id) break case comm.Service_Mainte: //维护服务 command = fmt.Sprintf("./stup.sh stop %s ", sseting.Id) break default: err = fmt.Errorf("服务类型异常 stype:%s", sseting.Type) return } log.Debug("启动外部命令", log.Field{Key: "cmd", Value: command}) cmd = exec.Command("/bin/bash", "-c", command) if output, err = cmd.CombinedOutput(); err != nil { return } log.Debugln(string(output)) return } // 写入服务配置文件 func writeServiceConfig(filename string, sseting *core.ServiceSttings) (err error) { var data []byte if err = os.MkdirAll(filepath.Dir(filename), 0755); err != nil { return } if data, err = yaml.Marshal(sseting); err != nil { return } else { err = ioutil.WriteFile(filename, data, 0777) } return } func parseaddr(addr string) (ip string, port int, err error) { ss := strings.Split(addr, ":") if len(ss) != 2 { err = fmt.Errorf("addr:%s解析异常", addr) return } address := net.ParseIP(ss[0]) if address == nil { err = fmt.Errorf("addr:%s解析异常 ip:%s 校验失败", addr, ss[0]) return } ip = ss[0] if port, err = strconv.Atoi(ss[1]); err != nil { err = fmt.Errorf("addr:%s解析异常 port:%s 校验失败", addr, ss[1]) return } return }