├── gserver ├── clienconnect │ ├── client_mail.go │ ├── client_item.go │ ├── client_rolebase .go │ ├── client_login.go │ └── client.go ├── commonstruct │ ├── mail.go │ ├── account.go │ ├── roleBaseInfo.go │ ├── serverCfg.go │ ├── roleItem.go │ └── roleData.go ├── cfg │ ├── errorCodeCfg.go │ ├── GameCfg.go │ ├── itemCfg.go │ ├── GameCfg_test.go │ ├── expCfg.go │ └── configInit.go ├── genServer │ ├── gateGenHanderInterface.go │ ├── gameGenServer.go │ ├── dbGenServer.go │ ├── cmdGenServer.go │ └── gateGenServer.go ├── timedtasks │ ├── timedtasks_test.go │ └── timedtasks.go ├── nodeManange │ ├── databaseSup.go │ ├── gateSup.go │ ├── nodes.go │ └── gameServerSup.go └── gserver.go ├── .vscode ├── settings.json └── launch.json ├── db ├── gameDB.go ├── storage.go ├── redis_test.go ├── mongo_test.go ├── mongo.go └── redis.go ├── tools ├── tools_test.go ├── pidfile.go ├── taptap.go └── tools.go ├── proto ├── common.proto ├── protocol_base.proto ├── item.proto ├── role.proto ├── account.proto ├── common │ └── common.pb.go └── protocol_base │ └── protocol_base.pb.go ├── .gitignore ├── cmd ├── test.go ├── system_shell.go ├── start.go ├── clean.go ├── version.go ├── state.go ├── stop.go ├── reloadCfg.go ├── excel.go ├── debug.go ├── root.go ├── completion.go ├── protobuf.go ├── ergoPing.go └── clientconn.go ├── main.go ├── config └── errorCode.json ├── cfg.yaml ├── logger ├── writeHook.go ├── logger.go └── contextHook.go ├── dockerfile ├── network ├── tcp.go ├── kcp.go └── network.go ├── web ├── ws_hub.go ├── wsChat.html ├── gin_web.go └── ws_client.go ├── README.md └── go.mod /gserver/clienconnect/client_mail.go: -------------------------------------------------------------------------------- 1 | package clienconnect 2 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "go.useLanguageServer": true 3 | } -------------------------------------------------------------------------------- /db/gameDB.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | //游戏mongodb 表定义 4 | var ( 5 | //账号信息 6 | AccountTable = "account_info" 7 | // 用户表 8 | RoleBaseTable = "role_base" 9 | RoleItemsTable = "role_item" 10 | ) 11 | -------------------------------------------------------------------------------- /tools/tools_test.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestString(t *testing.T) { 9 | str := "这里是 www\n.runoob\n.com" 10 | fmt.Println(StringReplace(str)) 11 | } 12 | -------------------------------------------------------------------------------- /proto/common.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package common; 3 | option go_package = "proto/common"; 4 | 5 | 6 | 7 | 8 | 9 | message NetworkMsg{ 10 | int32 Module =1; 11 | int32 Method =2; 12 | bytes MsgBytes =3; 13 | } 14 | 15 | 16 | -------------------------------------------------------------------------------- /gserver/clienconnect/client_item.go: -------------------------------------------------------------------------------- 1 | package clienconnect 2 | 3 | import pbitem "server/proto/item" 4 | 5 | func (c *Client) getBackpackInfo(msg *pbitem.C2S_GetBackpackInfo) { 6 | 7 | c.SendToClient(int32(pbitem.MSG_ITEM_Module), 8 | int32(pbitem.MSG_ITEM_GetBackpackInfo), 9 | &pbitem.S2C_GetBackpackInfo{}) 10 | } 11 | -------------------------------------------------------------------------------- /gserver/commonstruct/mail.go: -------------------------------------------------------------------------------- 1 | package commonstruct 2 | 3 | type RoleMail struct { 4 | RoleID int32 5 | MailList []*MailInfo 6 | } 7 | 8 | type MailInfo struct { 9 | UUID string 10 | Title string 11 | Body string 12 | Annex []string 13 | Logotype bool //是否已读 14 | Timestamp int64 //发邮件时间戳 15 | } 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | *.test 8 | *.pb 9 | *.log 10 | main 11 | server 12 | 13 | # Test binary, built with `go test -c` 14 | *.test 15 | 16 | # Output of the go coverage tool, specifically when used with LiteIDE 17 | *.out 18 | 19 | # Dependency directories (remove the comment below to include it) 20 | # vendor/ 21 | .vscode/* -------------------------------------------------------------------------------- /gserver/cfg/errorCodeCfg.go: -------------------------------------------------------------------------------- 1 | package cfg 2 | 3 | //ErrorCodeCfg 错误码 4 | type ErrorCodeCfg struct { 5 | Key string `json:"key"` 6 | Name string `json:"Name"` 7 | Code int32 `json:"Code"` 8 | } 9 | 10 | //GetErrorCodeNumber 错误提示码 11 | func GetErrorCodeNumber(code string) int32 { 12 | cfg := GetGameCfg() 13 | for _, v := range cfg.ErrorCode.CfgList { 14 | if code == v.Key { 15 | return v.Code // strconv.Itoa(v.Code) 16 | } 17 | } 18 | return 0 19 | } 20 | -------------------------------------------------------------------------------- /gserver/genServer/gateGenHanderInterface.go: -------------------------------------------------------------------------------- 1 | package genServer 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/ergo-services/ergo/etf" 7 | "github.com/ergo-services/ergo/gen" 8 | ) 9 | 10 | type GateGenHanderInterface interface { 11 | InitHander(process *gen.ServerProcess, sendChan chan []byte) 12 | LoopHander() (nextloop time.Duration) 13 | MsgHander(module, method int32, buf []byte) 14 | HandleCall(message etf.Term) 15 | HandleInfo(message etf.Term) 16 | Terminate(reason string) 17 | GenServerStatus() gen.ServerStatus 18 | } 19 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // 使用 IntelliSense 了解相关属性。 3 | // 悬停以查看现有属性的描述。 4 | // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "server", 9 | "type": "go", 10 | "request": "launch", 11 | "mode": "auto", 12 | "program": "${workspaceRoot}/main.go", 13 | "env": {}, 14 | "showLog": true 15 | 16 | //"args": ["start"] 17 | } 18 | ] 19 | 20 | } -------------------------------------------------------------------------------- /gserver/cfg/GameCfg.go: -------------------------------------------------------------------------------- 1 | package cfg 2 | 3 | import ( 4 | "go.uber.org/atomic" 5 | ) 6 | 7 | var ( 8 | //gameCfg 全局配置 9 | gameCfg atomic.Value 10 | ) 11 | 12 | func GetGameCfg() *cfgCollection { 13 | cfg := gameCfg.Load().(*cfgCollection) 14 | return cfg 15 | } 16 | 17 | func saveCfg(cfg *cfgCollection) { 18 | gameCfg.Store(cfg) 19 | } 20 | 21 | type cfgCollection struct { 22 | 23 | //错误提示码 24 | ErrorCode struct { 25 | CfgList []*ErrorCodeCfg 26 | } 27 | 28 | //exp 29 | ExpXiufaInfo []*ExpLvInfoCfg 30 | //道具 31 | ItemInfo []*ItemInfoCfg 32 | } 33 | -------------------------------------------------------------------------------- /cmd/test.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | // testCmd represents the test command 10 | var testCmd = &cobra.Command{ 11 | Use: "test", 12 | Short: "test demo", 13 | Long: `test demo`, 14 | Run: exectest, 15 | } 16 | 17 | func init() { 18 | rootCmd.AddCommand(testCmd) 19 | } 20 | 21 | type TestObject struct { 22 | Field1 int 23 | Field2 int 24 | } 25 | 26 | var ( 27 | mp = map[string]TestObject{} 28 | lock = sync.RWMutex{} 29 | ) 30 | 31 | func exectest(cmd *cobra.Command, args []string) { 32 | 33 | } 34 | -------------------------------------------------------------------------------- /proto/protocol_base.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package protocol_base; 3 | option go_package = "proto/protocol_base"; 4 | 5 | 6 | 7 | //消息号 8 | enum MSG_BASE { 9 | PLACEHOLDER_BASE =0; //占位 10 | 11 | //模块id 12 | Module=100; //基础模块 13 | 14 | //消息method id 15 | HeartBeat =101; //心跳 16 | 17 | NoticeMsg = 103; //错误提示 18 | } 19 | 20 | //心跳 1 21 | message c2s_HeartBeat { 22 | } 23 | 24 | message s2c_HeartBeat { 25 | sint32 retcode = 1; 26 | int64 Timestamp =2;//服务器时间 27 | } 28 | 29 | //错误提示消息 30 | message s2c_NoticeMsg_S { 31 | sint32 retcode = 1; 32 | string NoticeMsg =2; 33 | } -------------------------------------------------------------------------------- /db/storage.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "go.mongodb.org/mongo-driver/bson" 5 | "go.mongodb.org/mongo-driver/bson/primitive" 6 | ) 7 | 8 | //GetStorageInfo 获取数据 9 | func GetStorageInfo(tabname string, field string, value interface{}, document interface{}) error { 10 | // //读取缓存 11 | // if err := GetStruct(fmt.Sprintf("%v_%v", tabname, value), document); err != nil && document != nil { 12 | // return nil 13 | // } 14 | 15 | filter := bson.D{primitive.E{Key: field, Value: value}} 16 | return FindOneBson(tabname, document, filter) 17 | } 18 | 19 | //SaveStorageInfo save 20 | func SaveStorageInfo(tabname string, key interface{}, document interface{}) { 21 | //RedisSetStruct(fmt.Sprintf("%v_%v", tabname, key), document) 22 | } 23 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2019 NAME HERE 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package main 17 | 18 | import ( 19 | "server/cmd" 20 | ) 21 | 22 | func main() { 23 | cmd.Execute() 24 | } 25 | -------------------------------------------------------------------------------- /gserver/timedtasks/timedtasks_test.go: -------------------------------------------------------------------------------- 1 | package timedtasks 2 | 3 | import ( 4 | "server/tools" 5 | "testing" 6 | "time" 7 | 8 | "github.com/sirupsen/logrus" 9 | ) 10 | 11 | func init() { 12 | logrus.Info("start task:", tools.GoID()) 13 | StartCronTasks() 14 | } 15 | func TestInit(t *testing.T) { 16 | AddTasks("st", "* * * * * ?", func() { 17 | logrus.Info("st loop") 18 | }) 19 | 20 | time.Sleep(5000000000) 21 | logrus.Info("RemoveTasks st") 22 | RemoveTasks("st") 23 | time.Sleep(5000000000) 24 | } 25 | 26 | // //go test -bench=Chan -run=XXX -benchtime=10s 27 | // func BenchmarkChan(b *testing.B) { 28 | // tasks.Range(func(key, value interface{}) bool { 29 | // value.(chan interface{}) <- key 30 | // return true 31 | // }) 32 | 33 | // } 34 | -------------------------------------------------------------------------------- /cmd/system_shell.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "bytes" 5 | "os/exec" 6 | "runtime" 7 | ) 8 | 9 | //runtime.GOARCH 返回当前的系统架构;runtime.GOOS 返回当前的操作系统。 10 | //https://github.com/golang/go/wiki/SliceTricks#push-frontunshift 切片示例 11 | 12 | // Shellout system shell 13 | func Shellout(command ...string) (string, string, error) { 14 | var stdout bytes.Buffer 15 | var stderr bytes.Buffer 16 | var cmdtype, cmdarg string 17 | if runtime.GOOS == "windows" { 18 | cmdtype = "cmd" 19 | cmdarg = "/C" 20 | } else { 21 | cmdtype = "bash" 22 | cmdarg = "-c" 23 | } 24 | 25 | command = append([]string{cmdarg}, command...) 26 | 27 | cmd := exec.Command(cmdtype, command...) 28 | cmd.Stdout = &stdout 29 | cmd.Stderr = &stderr 30 | err := cmd.Run() 31 | return stdout.String(), stderr.String(), err 32 | 33 | } 34 | -------------------------------------------------------------------------------- /cmd/start.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "server/gserver" 5 | 6 | "github.com/spf13/cobra" 7 | //msg "server/proto" 8 | ) 9 | 10 | // startCmd represents the start command 11 | var startCmd = &cobra.Command{ 12 | Use: "start", 13 | Short: "启动服务", 14 | Long: `long`, 15 | Run: func(cmd *cobra.Command, args []string) { 16 | gserver.StartGServer() 17 | }, 18 | } 19 | 20 | func init() { 21 | rootCmd.AddCommand(startCmd) 22 | 23 | // Here you will define your flags and configuration settings. 24 | 25 | // Cobra supports Persistent Flags which will work for this command 26 | // and all subcommands, e.g.: 27 | // startCmd.PersistentFlags().String("foo", "", "A help for foo") 28 | 29 | // Cobra supports local flags which will only run when this command 30 | // is called directly, e.g.: 31 | // startCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 32 | } 33 | -------------------------------------------------------------------------------- /gserver/cfg/itemCfg.go: -------------------------------------------------------------------------------- 1 | package cfg 2 | 3 | type ItemInfoCfg struct { 4 | ID uint32 `json:"id"` 5 | Type uint32 `json:"type"` 6 | Level uint32 `json:"level"` 7 | Quality uint32 `json:"quality"` 8 | Name string `json:"name"` 9 | BagType uint32 `json:"bagType"` 10 | OverlayLimit uint32 `json:"overlayLimit"` //堆叠上限 11 | SellItemID uint32 `json:"sell_item_id"` //售卖物品ID 12 | SellItemNum uint32 `json:"sell_item_num"` //售卖货币数量 13 | Usetype uint32 `json:"usetype"` 14 | Data1 uint32 `json:"data1"` 15 | Data2 uint32 `json:"data2"` 16 | IsUse uint32 `json:"isUse"` //使用后消失 17 | AttrCreat []uint32 `json:"attrCreat"` 18 | } 19 | 20 | func GetItemCfg(id uint32) *ItemInfoCfg { 21 | itemlist := GetGameCfg().ItemInfo 22 | for _, iic := range itemlist { 23 | if iic.ID == id { 24 | return iic 25 | } 26 | } 27 | return nil 28 | } 29 | -------------------------------------------------------------------------------- /gserver/timedtasks/timedtasks.go: -------------------------------------------------------------------------------- 1 | package timedtasks 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | 7 | "github.com/robfig/cron/v3" 8 | "github.com/sirupsen/logrus" 9 | ) 10 | 11 | var ( 12 | crontask *cron.Cron 13 | taskmap sync.Map 14 | ) 15 | 16 | //StartCronTasks spec := "* * * * * ?" 17 | func StartCronTasks() { 18 | logrus.Infof("Start Cron") 19 | crontask = cron.New(cron.WithSeconds()) 20 | crontask.Start() 21 | } 22 | 23 | //AddTasks add 24 | func AddTasks(key string, spec string, cmd func()) error { 25 | entryid, err := crontask.AddFunc(spec, cmd) 26 | 27 | if _, ok := taskmap.Load(key); ok { 28 | return fmt.Errorf("[%v] 已注册", key) 29 | } 30 | 31 | if err == nil { 32 | taskmap.Store(key, entryid) 33 | } 34 | return err 35 | } 36 | 37 | //RemoveTasks Remove Tasks 38 | func RemoveTasks(key string) { 39 | entryid, ok := taskmap.Load(key) 40 | if ok { 41 | crontask.Remove(entryid.(cron.EntryID)) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /gserver/cfg/GameCfg_test.go: -------------------------------------------------------------------------------- 1 | package cfg 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/sirupsen/logrus" 7 | ) 8 | 9 | func init() { 10 | InitViperConfig("../../config", "json") 11 | logrus.Info("ErrorCode:", GetGameCfg().ErrorCode.CfgList) 12 | logrus.Info("ExpXiufaInfo len :", len(GetGameCfg().ExpXiufaInfo)) 13 | logrus.Info("item len :", len(GetGameCfg().ItemInfo)) 14 | } 15 | 16 | func TestMapCfg(t *testing.T) { 17 | 18 | //10000 50982 132201 19 | // level, exp := AddRoleExp(2, 60, 10000+50982+132201+50) 20 | // assert.Equal(t, int(level), 5) 21 | // assert.Equal(t, int(exp), 110) 22 | 23 | // assert.Equal(t, GetErrorCodeNumber("PARAMETER_EMPTY"), "参数不能为空") 24 | // assert.Equal(t, GetTroopsCfg(1).Name, "骑兵") 25 | 26 | // assert.Equal(t, CheckBigMapConfig(), true) 27 | //assert.Equal(t, AreasIsBeside(1319, 1316), false) 28 | 29 | // assert.Equal(t, AreasIsBeside(2427, 2725), true) 30 | // assert.Equal(t, AreasIsBeside(2427, 3966), false) 31 | } 32 | -------------------------------------------------------------------------------- /proto/item.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package item; 3 | option go_package = "proto/item"; 4 | 5 | 6 | //策划配置表属性 7 | enum Item_Type 8 | { 9 | UnKnown = 0;//未知 10 | Money = 1;//货币 11 | Equip = 2;//装备 12 | Prop = 3;//道具 13 | Drug = 4;//丹药 14 | Trump = 5;//法宝 15 | Rune = 6;//符文 16 | Material = 7;//材料 17 | Exp = 8;//经验 18 | Pokemon = 9;//宠物 19 | TrumpMaterial = 10;//法宝材料 20 | }; 21 | 22 | 23 | message pbItem 24 | { 25 | string uuid = 1; //道具id 26 | uint32 item_id = 2; 27 | int64 item_number = 3; //拥有数量 28 | uint32 item_lock = 4;//是否锁定0没锁 29 | uint32 use_location = 5;//是否装备,0否,1~6装备位置 30 | map base_attr =6;//基本属性 31 | string AttributeJson =7;//扩展属性 32 | string name=11; 33 | string desc=12; 34 | } 35 | 36 | 37 | //消息号 38 | enum MSG_ITEM { 39 | PLACEHOLDER =0; //占位 40 | 41 | //账号模块 42 | Module = 3000; 43 | 44 | GetBackpackInfo = 3001;//获取背包信息 45 | } 46 | 47 | message c2s_GetBackpackInfo { 48 | } 49 | 50 | 51 | message s2c_GetBackpackInfo { 52 | 53 | } 54 | -------------------------------------------------------------------------------- /config/errorCode.json: -------------------------------------------------------------------------------- 1 | { 2 | "CfgList": [ 3 | { 4 | "key": "PARAMETER_EMPTY", 5 | "Name": "参数不能为空1", 6 | "Code": 100001 7 | }, 8 | { 9 | "key": "PASSWORD_ERROR", 10 | "Name": "密码错误", 11 | "Code": 100002 12 | }, 13 | { 14 | "key": "NOT_LOGIN", 15 | "Name": "用户未登陆", 16 | "Code": 100003 17 | }, 18 | { 19 | "key": "AccountNull", 20 | "Name": "未找到账号", 21 | "Code": 100004 22 | }, 23 | { 24 | "key": "RoleNull", 25 | "Name": "角色为空", 26 | "Code": 100005 27 | }, 28 | { 29 | "key": "AccountExists", 30 | "Name": "账号已存在", 31 | "Code": 100006 32 | }, 33 | { 34 | "key": "RoleNameExists", 35 | "Name": "角色名已存在", 36 | "Code": 100007 37 | }, 38 | { 39 | "key": "LOGIN", 40 | "Name": "已登陆", 41 | "Code": 100008 42 | }, 43 | { 44 | "key": "CDK", 45 | "Name": "cdk已注册", 46 | "Code": 100009 47 | } 48 | 49 | 50 | ] 51 | } 52 | -------------------------------------------------------------------------------- /proto/role.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package role; 3 | option go_package = "proto/role"; 4 | 5 | 6 | 7 | 8 | //用户游戏信息 9 | message Pb_RoleInfo { 10 | int32 roleID = 1; 11 | string roleName =2; 12 | int64 exp = 3; 13 | int32 level =4; 14 | uint32 sex =5; 15 | map AttributeValue =6;//属性值 16 | map BodyList = 7;//体质列表 17 | int64 CE =8;//战斗力 18 | } 19 | 20 | 21 | 22 | //消息号 23 | enum MSG_ROLE { 24 | PLACEHOLDER =0; //占位 25 | 26 | //角色模块 27 | Module = 2000; 28 | AddExp = 2001; 29 | 30 | 31 | Upgrade =2002; 32 | 33 | AttributeChange = 2004; 34 | } 35 | 36 | //加经验 37 | message s2c_AddExp_S { 38 | int64 addexp = 1; 39 | int64 exp =2; 40 | } 41 | 42 | //升级突破-雷劫 43 | message c2s_Upgrade { 44 | } 45 | 46 | message s2c_Upgrade { 47 | sint32 retcode = 1; 48 | int32 level = 2; 49 | int64 exp =3; 50 | } 51 | 52 | //属性变化通知 53 | message s2c_AttributeChange_S { 54 | map AttributeList = 1; //属性列表 55 | int64 CE = 2;//战斗力combat effectiveness 56 | } -------------------------------------------------------------------------------- /cfg.yaml: -------------------------------------------------------------------------------- 1 | ServerName: "server" 2 | ServerID: 1 3 | #Version: "1.0.1" 4 | 5 | 6 | ##web htpp port 7 | OpenHTTP: true 8 | HTTPPort: 8080 9 | OpenWS: true 10 | SetMode: "debug" #debug release, test 11 | 12 | #pyroscope 13 | OpenPyroscope: false 14 | PyroscopeHost: "http://192.168.1.68:4040" 15 | 16 | #NetType : tcp/kcp 17 | NetType: tcp 18 | Port: 3344 19 | Packet : 2 20 | Readtimeout: 60 #超时(秒) 21 | MaxConnectNum: 10000 22 | 23 | # MsgTime 多少秒后 24 | # MsgNum 多少条消息后 25 | MsgTime: 300 26 | MsgNum : 500 27 | 28 | #protobuf path 29 | ProtoPath: "./proto" 30 | GoOut: "./proto" 31 | 32 | #mongodb 33 | MongoConnStr: "mongodb://admin:123456@localhost:27017" 34 | Mongodb: "gamedemo" 35 | 36 | #redis 37 | RedisConnStr: "localhost:6379" 38 | RedisDB: 0 39 | 40 | 41 | #game confg 42 | CfgPath: "./config" 43 | CfgType: "json" 44 | WatchConfig: true 45 | 46 | #log 47 | LogWrite: false 48 | Loglevel: "debug" #panic fatal error warn info debug trace 49 | LogPath: "./log" 50 | LogName: "log" 51 | 52 | #OTP 53 | ListenBegin: 15151 54 | ListenEnd: 25151 55 | Cookie: "123" 56 | 57 | 58 | 59 | #gateway db server 60 | StartList : ["gateway","db","server"] 61 | CentralServerNode: ["serverNode@127.0.0.1"] -------------------------------------------------------------------------------- /logger/writeHook.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import ( 4 | "server/tools" 5 | "time" 6 | 7 | rotatelogs "github.com/lestrrat-go/file-rotatelogs" 8 | "github.com/rifflock/lfshook" 9 | "github.com/sirupsen/logrus" 10 | ) 11 | 12 | func fileHook(path string) *lfshook.LfsHook { 13 | writer, _ := rotatelogs.New( 14 | path, 15 | rotatelogs.WithLinkName(path), 16 | 17 | // WithMaxAge和WithRotationCount二者只能设置一个, 18 | // WithMaxAge设置文件清理前的最长保存时间, 19 | // WithRotationCount设置文件清理前最多保存的个数。 20 | rotatelogs.WithMaxAge(time.Duration(24)*time.Hour), 21 | //rotatelogs.WithRotationCount(maxRemainCnt), 22 | 23 | // WithRotationTime设置日志分割的时间,这里设置为一小时分割一次 24 | rotatelogs.WithRotationTime(time.Hour), 25 | rotatelogs.WithRotationSize(200*1024*1024), 26 | ) 27 | //panic fatal error warn info debug trace 28 | return lfshook.NewHook( 29 | lfshook.WriterMap{ 30 | logrus.PanicLevel: writer, 31 | logrus.FatalLevel: writer, 32 | logrus.ErrorLevel: writer, 33 | logrus.WarnLevel: writer, 34 | logrus.InfoLevel: writer, 35 | logrus.DebugLevel: writer, 36 | logrus.TraceLevel: writer, 37 | }, 38 | &logrus.TextFormatter{ 39 | ForceColors: true, 40 | TimestampFormat: tools.DateTimeFormat, 41 | }, 42 | ) 43 | } 44 | -------------------------------------------------------------------------------- /gserver/cfg/expCfg.go: -------------------------------------------------------------------------------- 1 | package cfg 2 | 3 | type ExpLvInfoCfg struct { 4 | Level int `json:"level"` 5 | NeedExp int64 `json:"needExp"` 6 | FailExp int64 `json:"failExp"` 7 | MaxExp int64 `json:"maxExp"` 8 | BigLevel string `json:"bigLevel"` 9 | HeadSize int `json:"headSize"` 10 | CycleEXP int64 `json:"cycleEXP"` 11 | ExpWeight []int `json:"expWeight"` 12 | ExpWeightValue [][]int `json:"expWeightValue"` 13 | ExpUp int `json:"expUp"` 14 | PropertiesID []int `json:"propertiesId"` 15 | AttributeValues []int `json:"attributeValues"` 16 | Times int `json:"times"` 17 | Properties []int `json:"properties"` 18 | PropertiesWeight []int `json:"propertiesWeight"` 19 | CycleProperties []int `json:"cycleProperties"` 20 | PropertiesMax []int `json:"propertiesMax"` 21 | TribulationID int `json:"tribulationId"` 22 | ShowNum []int `json:"showNum"` 23 | IDGroup int `json:"idGroup"` 24 | MailID int `json:"mailId"` 25 | } 26 | 27 | func GetLvExpInfo(lv int32) *ExpLvInfoCfg { 28 | for _, v := range GetGameCfg().ExpXiufaInfo { 29 | if v.Level == int(lv) { 30 | return v 31 | } 32 | } 33 | return nil 34 | } 35 | -------------------------------------------------------------------------------- /gserver/clienconnect/client_rolebase .go: -------------------------------------------------------------------------------- 1 | package clienconnect 2 | 3 | import ( 4 | "server/gserver/cfg" 5 | pbrole "server/proto/role" 6 | 7 | "go.mongodb.org/mongo-driver/bson/primitive" 8 | ) 9 | 10 | //练功修为同步 11 | 12 | //练功突破 CMD_Role_Insight_CS 13 | 14 | //雷劫 15 | func (c *Client) upgrade(msg *pbrole.C2S_Upgrade) { 16 | retmsg := &pbrole.S2C_Upgrade{} 17 | rolebase := &c.roleData.RoleBase 18 | lvcfg := cfg.GetLvExpInfo(rolebase.Level) 19 | if rolebase.Exp < int64(lvcfg.NeedExp) { 20 | retmsg.Retcode = cfg.GetErrorCodeNumber("EXP_NOT_ENOUGH") 21 | c.SendToClient(int32(pbrole.MSG_ROLE_Module), int32(pbrole.MSG_ROLE_Upgrade), retmsg) 22 | return 23 | } 24 | 25 | rolebase.Exp -= int64(lvcfg.NeedExp) 26 | rolebase.Level++ 27 | rolebase.SetDirtyData(primitive.E{Key: "exp", Value: rolebase.Exp}, 28 | primitive.E{Key: "level", Value: rolebase.Level}) 29 | rolebase.CalculationProperties() 30 | 31 | retmsg.Exp = rolebase.Exp 32 | retmsg.Level = rolebase.Level 33 | c.SendToClient(int32(pbrole.MSG_ROLE_Module), int32(pbrole.MSG_ROLE_Upgrade), retmsg) 34 | c.SendToClient(int32(pbrole.MSG_ROLE_Module), 35 | int32(pbrole.MSG_ROLE_AttributeChange), 36 | &pbrole.S2C_AttributeChange_S{ 37 | AttributeList: rolebase.AttributeValue, 38 | CE: rolebase.CE, 39 | }) 40 | 41 | } 42 | 43 | //背包 44 | 45 | //阵法 46 | -------------------------------------------------------------------------------- /cmd/clean.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "context" 5 | "server/db" 6 | "server/gserver/commonstruct" 7 | 8 | "github.com/spf13/cobra" 9 | ) 10 | 11 | // cleanCmd represents the clean command 12 | var cleanCmd = &cobra.Command{ 13 | Use: "clean", 14 | Short: "清理数据", 15 | Long: `清理 redis 缓存数据 [areasSMap] [troopsSMap]`, 16 | Run: func(cmd *cobra.Command, args []string) { 17 | clean() 18 | }, 19 | } 20 | 21 | func init() { 22 | rootCmd.AddCommand(cleanCmd) 23 | 24 | // Here you will define your flags and configuration settings. 25 | 26 | // Cobra supports Persistent Flags which will work for this command 27 | // and all subcommands, e.g.: 28 | // cleanCmd.PersistentFlags().String("foo", "", "A help for foo") 29 | 30 | // Cobra supports local flags which will only run when this command 31 | // is called directly, e.g.: 32 | // cleanCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 33 | } 34 | 35 | func clean() { 36 | db.StartMongodb(commonstruct.ServerCfg.Mongodb, commonstruct.ServerCfg.MongoConnStr) 37 | _, database := db.GetDatabase() 38 | database.Drop(context.Background()) 39 | 40 | db.StartRedis(commonstruct.ServerCfg.RedisConnStr, commonstruct.ServerCfg.RedisDB) 41 | //db.RedisExec("del", "ConnectNumber") 42 | db.RedisExec("FLUSHDB", "") 43 | } 44 | -------------------------------------------------------------------------------- /gserver/genServer/gameGenServer.go: -------------------------------------------------------------------------------- 1 | package genServer 2 | 3 | import ( 4 | "github.com/ergo-services/ergo/etf" 5 | "github.com/ergo-services/ergo/gen" 6 | "github.com/sirupsen/logrus" 7 | ) 8 | 9 | //中心服务 10 | type GameGenServer struct { 11 | gen.Server 12 | } 13 | 14 | func (dgs *GameGenServer) Init(process *gen.ServerProcess, args ...etf.Term) error { 15 | logrus.Infof("Init (%v): args %v ", process.Name(), args) 16 | return nil 17 | } 18 | 19 | func (dgs *GameGenServer) HandleCast(process *gen.ServerProcess, message etf.Term) gen.ServerStatus { 20 | logrus.Infof("HandleCast (%v): %v", process.Name(), message) 21 | 22 | return gen.ServerStatusOK 23 | } 24 | 25 | func (dgs *GameGenServer) HandleCall(process *gen.ServerProcess, from gen.ServerFrom, message etf.Term) (etf.Term, gen.ServerStatus) { 26 | logrus.Infof("HandleCall (%v): %v, From: %v", process.Name(), message, from) 27 | 28 | reply := etf.Term(etf.Tuple{etf.Atom("error"), etf.Atom("unknown_request")}) 29 | 30 | return reply, gen.ServerStatusOK 31 | } 32 | 33 | func (dgs *GameGenServer) HandleInfo(process *gen.ServerProcess, message etf.Term) gen.ServerStatus { 34 | logrus.Debugf("HandleInfo (%v): %v", process.Name(), message) 35 | 36 | return gen.ServerStatusOK 37 | } 38 | 39 | func (dgs *GameGenServer) Terminate(process *gen.ServerProcess, reason string) { 40 | logrus.Infof("Terminate (%v): %v", process.Name(), reason) 41 | } 42 | -------------------------------------------------------------------------------- /proto/account.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package account; 3 | option go_package = "proto/account"; 4 | import "proto/role.proto"; 5 | 6 | 7 | //消息号 8 | enum MSG_ACCOUNT { 9 | PLACEHOLDER =0; //占位 10 | 11 | //账号模块 12 | Module = 1000; 13 | 14 | //method 15 | Login = 1001;//用户登陆 16 | Register= 1002;//注册账号 17 | CreateRole= 1003;//创建角色 18 | 19 | } 20 | 21 | 22 | //用户账号信息 23 | message P_Account { 24 | string account=1; 25 | string password =2; 26 | string equipment =3; //设备信息 27 | string registrationSource =4; //注册来源 28 | string registrationTime =5; //注册时间 29 | } 30 | 31 | 32 | 33 | 34 | 35 | //用户登陆 36 | message c2s_Login { 37 | string account =1; 38 | string password =2; 39 | } 40 | 41 | message s2c_Login { 42 | sint32 retcode = 1; 43 | role.Pb_RoleInfo RoleInfo =2; 44 | map Settings =3; //游戏内设置 45 | map ItemList = 7;//背包物品列表 46 | 47 | } 48 | 49 | message c2s_Register { 50 | string account =1; 51 | string password =2; 52 | string CDK = 3;//注册码 53 | string Source =4;//注册来源 54 | string Equipment =5;//设备信息 55 | } 56 | 57 | message s2c_Register { 58 | sint32 retcode = 1; 59 | } 60 | 61 | // 创建角色 62 | message c2s_CreateRole { 63 | string RoleName =1; 64 | uint32 sex =2; 65 | uint32 headID =3; 66 | 67 | } 68 | 69 | message s2c_CreateRole { 70 | sint32 retcode = 1; 71 | role.Pb_RoleInfo RoleInfo =2; 72 | } 73 | 74 | -------------------------------------------------------------------------------- /dockerfile: -------------------------------------------------------------------------------- 1 | #基于的镜像不是golang(733M),而是alpine(4.14M) #scratch 空镜像 2 | FROM alpine:latest 3 | 4 | #ENV 设置环境变量 5 | #ENV PATH /usr/local/nginx/sbin:$PATH 6 | 7 | #[WORKDIR 进入docker内文件夹] 相当于cd $GOPATH/.. 8 | WORKDIR /home/server 9 | 10 | #[ADD 本地文件 docker文件地址] 文件放在当前目录下,拷过去会自动解压 11 | ADD server /home/server 12 | #ADD cfg.yaml /home/server/cfg.yaml 13 | #ADD config /home/server/config 14 | 15 | #RUN 执行以下命令 16 | #RUN mkdir /lib64 && ln -s /lib/libc.musl-x86_64.so.1 /lib64/ld-linux-x86-64.so.2 17 | #RUN go build 18 | 19 | #EXPOSE 指定容器要打开的端口 格式为 EXPOSE [...] 20 | EXPOSE 8080 8081 3344 21 | 22 | #CMD 运行以下命令 23 | #CMD ["nginx"] 24 | 25 | #ENTRYPOINT ["command", "param1", "param2"] 26 | ENTRYPOINT ["./server","start","--config=/home/server/config/cfg.yaml"] 27 | 28 | #================================================================================================================================================= 29 | #构建镜像 [ -t name:tag ] 30 | #sudo docker build -t slgdocker . 31 | 32 | #运行容器 -v[本地配置地址 :docker内读取配置固定地址"/home/server/config"] 33 | #sudo docker run -t -i -d -v /mnt/e/dockerconfig:/home/server/config -p 3344:3344 -p 8080:8080 -p 8081:8081 --name gamedemo slgdocker:latest 34 | 35 | #进入容器 36 | #sudo docker exec -it gamedemo /bin/sh 37 | 38 | #保存 加载 镜像 39 | #sudo docker save slgdocker:latest -o /home/wq/slgdocker:latest.tar 40 | #sudo docker load -i slgdocker:latest.tar 41 | #================================================================================================================================================= -------------------------------------------------------------------------------- /logger/logger.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "server/tools" 7 | 8 | "github.com/sirupsen/logrus" 9 | ) 10 | 11 | //Init add logrus hook 12 | func Init(loglevel logrus.Level, writefile bool, LogName string, path string) { 13 | logrus.SetLevel(loglevel) 14 | 15 | // if loglevel == logrus.DebugLevel || loglevel == logrus.TraceLevel { 16 | // logrus.SetFormatter(new(MyFormatter)) 17 | // } 18 | logrus.SetFormatter(&logrus.TextFormatter{ 19 | //DisableTimestamp: false, 20 | FullTimestamp: true, 21 | // 定义时间戳格式 22 | TimestampFormat: tools.DateTimeFormat, 23 | DisableSorting: true, 24 | }) 25 | logrus.AddHook(NewContextHook(logrus.ErrorLevel, logrus.WarnLevel, logrus.DebugLevel, logrus.TraceLevel, logrus.FatalLevel)) 26 | if writefile { 27 | logrus.Infof("log path: [%v]", path) 28 | logrus.AddHook(fileHook(fmt.Sprintf("%v/%v_%v.log", path, LogName, "%Y%m%d%H%M"))) 29 | } 30 | 31 | } 32 | 33 | // MyFormatter 自定义 formatter 34 | type MyFormatter struct { 35 | Prefix string 36 | Suffix string 37 | } 38 | 39 | // Format implement the Formatter interface 40 | func (mf *MyFormatter) Format(entry *logrus.Entry) ([]byte, error) { 41 | var b *bytes.Buffer 42 | if entry.Buffer != nil { 43 | b = entry.Buffer 44 | } else { 45 | b = &bytes.Buffer{} 46 | } 47 | // entry.Message 就是需要打印的日志 48 | b.WriteString(fmt.Sprintf("[%s][%v][%s]: %s\n", 49 | entry.Level, 50 | tools.GoID(), 51 | entry.Time.Format(tools.DateTimeFormat), 52 | entry.Message)) 53 | return b.Bytes(), nil 54 | } 55 | -------------------------------------------------------------------------------- /network/tcp.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "sync/atomic" 7 | 8 | "github.com/sirupsen/logrus" 9 | ) 10 | 11 | //TCPNetwork tcp/ip 12 | type TCPNetwork struct { 13 | // network string 14 | // address string 15 | } 16 | 17 | // type innerBuffer []byte 18 | 19 | // func (in *innerBuffer) readN(n int) (buf []byte, err error) { 20 | // if n <= 0 { 21 | // return nil, errors.New("zero or negative length is invalid") 22 | // } else if n > len(*in) { 23 | // return nil, errors.New("exceeding buffer length") 24 | // } 25 | // buf = (*in)[:n] 26 | // *in = (*in)[n:] 27 | // return 28 | // } 29 | 30 | //Start start 31 | func (c *TCPNetwork) Start(nw *NetWorkx) { 32 | logrus.Info(fmt.Sprintf("tcp run on localhost: [%v]", nw.Port)) 33 | 34 | listener, err := net.Listen("tcp", fmt.Sprintf(":%v", nw.Port)) 35 | defer listener.Close() 36 | checkError(err) 37 | if nw.StartHook != nil { 38 | nw.StartHook() 39 | } 40 | 41 | for { 42 | conn, err := listener.Accept() 43 | if err != nil { 44 | logrus.Error(err.Error()) 45 | continue 46 | } 47 | logrus.Infof("sockert connect RemoteAddr:[%v]", conn.RemoteAddr().String()) 48 | 49 | num := atomic.LoadInt32(&nw.ConnectCount) 50 | if !nw.OpenConn || num >= nw.MaxConnectNum { 51 | logrus.Warnf("sockert connect open:[%v] max count:[%v]", nw.OpenConn, nw.MaxConnectNum) 52 | conn.Close() 53 | continue 54 | } 55 | 56 | go nw.HandleClient(conn) 57 | } 58 | //listener.Close() 59 | } 60 | 61 | //Close 关闭 62 | func (c *TCPNetwork) Close() { 63 | 64 | } 65 | -------------------------------------------------------------------------------- /web/ws_hub.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package web 6 | 7 | // Hub maintains the set of active clients and broadcasts messages to the 8 | // clients. 9 | type Hub struct { 10 | // Registered clients. 11 | clients map[*wsClient]bool 12 | 13 | // Inbound messages from the clients. 14 | broadcast chan []byte 15 | 16 | // Register requests from the clients. 17 | register chan *wsClient 18 | 19 | // Unregister requests from clients. 20 | unregister chan *wsClient 21 | } 22 | 23 | var WSHub = &Hub{ 24 | broadcast: make(chan []byte), 25 | register: make(chan *wsClient), 26 | unregister: make(chan *wsClient), 27 | clients: make(map[*wsClient]bool), 28 | } 29 | 30 | // func newHub() *Hub { 31 | // return &Hub{ 32 | // broadcast: make(chan []byte), 33 | // register: make(chan *Client), 34 | // unregister: make(chan *Client), 35 | // clients: make(map[*Client]bool), 36 | // } 37 | // } 38 | 39 | func (h *Hub) run() { 40 | for { 41 | select { 42 | case client := <-h.register: 43 | h.clients[client] = true 44 | case client := <-h.unregister: 45 | if _, ok := h.clients[client]; ok { 46 | delete(h.clients, client) 47 | close(client.testsend) 48 | } 49 | case message := <-h.broadcast: 50 | for client := range h.clients { 51 | select { 52 | case client.testsend <- message: 53 | default: 54 | close(client.testsend) 55 | delete(h.clients, client) 56 | } 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /cmd/version.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2021 NAME HERE 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package cmd 17 | 18 | import ( 19 | "fmt" 20 | "server/gserver/commonstruct" 21 | 22 | "github.com/spf13/cobra" 23 | ) 24 | 25 | // versionCmd represents the version command 26 | var versionCmd = &cobra.Command{ 27 | Use: "version", 28 | Short: "version number", 29 | Long: ``, 30 | Run: func(cmd *cobra.Command, args []string) { 31 | fmt.Printf("version V%v", commonstruct.ServerCfg.Version) 32 | }, 33 | } 34 | 35 | func init() { 36 | rootCmd.AddCommand(versionCmd) 37 | 38 | // Here you will define your flags and configuration settings. 39 | 40 | // Cobra supports Persistent Flags which will work for this command 41 | // and all subcommands, e.g.: 42 | // versionCmd.PersistentFlags().String("foo", "", "A help for foo") 43 | 44 | // Cobra supports local flags which will only run when this command 45 | // is called directly, e.g.: 46 | // versionCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 47 | } 48 | -------------------------------------------------------------------------------- /gserver/commonstruct/account.go: -------------------------------------------------------------------------------- 1 | package commonstruct 2 | 3 | import ( 4 | "server/db" 5 | 6 | "go.mongodb.org/mongo-driver/bson" 7 | "go.mongodb.org/mongo-driver/bson/primitive" 8 | ) 9 | 10 | //AccountInfo 账号信息 11 | type AccountInfo struct { 12 | Account string //账号 13 | Password string //密码 14 | Equipment string //设备信息 15 | RegistrationSource string //注册来源(平台) 16 | RegistrationTime int64 17 | RoleID int32 //角色id 18 | RoleUUID string //角色uuid 19 | CDK string 20 | Settings map[uint32]string //设置 21 | } 22 | 23 | func (accountinfo *AccountInfo) GetAccountinfo() bool { 24 | filter := bson.D{ 25 | primitive.E{Key: "account", Value: accountinfo.Account}, 26 | } 27 | 28 | if accountinfo.Password != "" { 29 | filter = append(filter, primitive.E{Key: "password", Value: accountinfo.Password}) 30 | } 31 | 32 | if err := db.FindOneBson(db.AccountTable, accountinfo, filter); err != nil { 33 | return false 34 | } 35 | 36 | return true 37 | } 38 | 39 | func GetMaxRoleID(value int32) int32 { 40 | var obj AccountInfo 41 | //区号 42 | db.FindFieldMax(db.AccountTable, "roleid", &obj, bson.D{{}}) 43 | return obj.RoleID 44 | } 45 | 46 | func GetNewRoleID() int32 { 47 | return db.RedisINCRBY("MaxRoleID", 1) 48 | } 49 | 50 | func GetCDKinfo(cdk string) bool { 51 | filter := bson.D{ 52 | primitive.E{Key: "cdk", Value: cdk}, 53 | } 54 | 55 | accountinfo := &AccountInfo{} 56 | if err := db.FindOneBson(db.AccountTable, accountinfo, filter); err != nil { 57 | return false 58 | } 59 | return true 60 | } 61 | -------------------------------------------------------------------------------- /gserver/commonstruct/roleBaseInfo.go: -------------------------------------------------------------------------------- 1 | package commonstruct 2 | 3 | import ( 4 | "server/gserver/cfg" 5 | pbrole "server/proto/role" 6 | ) 7 | 8 | //角色基础数据 9 | type RoleBaseInfo struct { 10 | ZoneID int32 //所属区号 11 | RoleID int32 //角色id 12 | Name string //角色名 13 | HeadID uint32 //头像id 14 | Sex uint32 //性别 15 | Level int32 //等级 16 | Exp int64 //经验 17 | MaxExp int64 //储存最大经验 18 | PracticeTimestamp int64 //练功时间戳 19 | 20 | AttributeValue map[uint32]int64 //属性值 21 | BodyList map[uint32]*RoleBodyInfo //体质信息 22 | CE int64 //战斗力 combat effectiveness 23 | 24 | ItemList map[string]*ItemInfo //道具 25 | 26 | OfflineTimestamp int64 //离线时间戳 27 | Online bool //是否在线 28 | State uint32 //状态 29 | 30 | //好友 31 | //宗门 32 | //任务 33 | //成就记录 34 | 35 | // DirtyData bool 36 | // DirtyDataList map[string]primitive.E 37 | DirtyDataRecord 38 | } 39 | 40 | //体质 41 | type RoleBodyInfo struct { 42 | BodyID uint32 //体质类型 43 | BodyLevel uint32 //体质等级 44 | PropertiesId uint32 //属性id 45 | AttributeValue int64 //属性值 46 | } 47 | 48 | func (r *RoleBaseInfo) ToPB() *pbrole.Pb_RoleInfo { 49 | return &pbrole.Pb_RoleInfo{ 50 | RoleID: r.RoleID, 51 | RoleName: r.Name, 52 | Level: r.Level, 53 | Exp: r.Exp, 54 | Sex: r.Sex, 55 | AttributeValue: r.AttributeValue, 56 | CE: r.CE, 57 | //BodyList: r.BodyList, 58 | } 59 | } 60 | 61 | func (r *RoleBaseInfo) CalculationProperties() { 62 | lvcfg := cfg.GetLvExpInfo(r.Level) 63 | r.MaxExp = lvcfg.MaxExp 64 | for i, v := range lvcfg.Properties { 65 | r.AttributeValue[uint32(v)] = int64(lvcfg.AttributeValues[i]) 66 | } 67 | r.CE = 0 68 | } 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # game server 2 | 3 | 💡❌💙 💔💜 💚💬⭐️⚠️💃🏻📄📚🛠 😎 🔧 🐭🐮🐯🐇🐉🐍🐎🐑🐒🐔🐶🐷 4 | 5 | ### 基于 [ergo-services/ergo](https://github.com/ergo-services/ergo) 以erlang otp 模型方式组织创建的游戏服务器解决方案 6 | 7 | ##### 服务启动时会创建3个节点 8 | 9 | - gatewayNode 用户连接后创建网关连接 10 | - serverNode 创建游戏公共服务 [cmdGenserver] 11 | - dbNode 用做数据落地 12 | 13 | serverNode 节点启动会创建一个 cmdGenserver 用于接收外部发送过来的命令,以 14 | 便于从内部 获取信息、更新配置、关闭服务 15 | 16 | server运行时 执行 cmd [state|stop|debug|reloadcfg] 命令 17 | 18 | 会在创建一个 debugNode 节点去接连服务器内部 serverNode 节点下的 cmdGenserver 发送命令消息 19 | 20 | 21 | 22 | 23 | 24 | 25 | ## 🔨 command 26 | Available Commands: 27 | - clean    清理数据 28 | - completion  生成补全脚本 29 | - debug    控制台 30 | - pb [int] [obj]  生成protobuf 31 | - reloadcfg    重新加载配置 32 | - start    启动服务 33 | - state    获取服务器运行状态 34 | - stop    关闭服务器 35 | ##### 使用 [spf13/cobra](https://github.com/spf13/cobra) 创建的服务器命令 36 | 37 | 38 | 39 | ### ✅ 安全退出 40 | ctrl + | 41 | 42 | 43 | ### 📄 协议 44 | | 2Byte (包长) | 2Byte | 2Byte | message| 45 | | ---- | ---- |---- |---- | 46 | | 4Byte+ len(消息体) | 模块ID | 方法ID | 消息体| 47 | 48 | 49 | 50 | ## 🛠 构建镜像 51 | ``` 52 | sudo docker build -t gamedocker . 53 | ``` 54 | 55 | ## 🏃 运行容器 56 | ``` 57 | sudo docker run -t -i -d -v /mnt/e/dockerconfig:/home/server/config -p 3344:3344 -p 8080:8080 -p 8081:8081 --name gameserver gamedocker:latest 58 | ``` 59 | 60 | ## 📝 进入容器 61 | ``` 62 | sudo docker exec -it gameserver /bin/sh 63 | ``` 64 | 65 | ## 📥 保存镜像 66 | ``` 67 | sudo docker save gamedocker:latest -o /home/wq/gamedocker:latest.tar 68 | ``` 69 | ## 💡 加载镜像 70 | ``` 71 | sudo docker load -i gamedocker:latest.tar 72 | ``` 73 | -------------------------------------------------------------------------------- /gserver/genServer/dbGenServer.go: -------------------------------------------------------------------------------- 1 | package genServer 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/ergo-services/ergo/etf" 7 | "github.com/ergo-services/ergo/gen" 8 | "github.com/sirupsen/logrus" 9 | ) 10 | 11 | //数据落地服务 12 | 13 | type DbGenServer struct { 14 | gen.Server 15 | } 16 | 17 | func (dgs *DbGenServer) Init(process *gen.ServerProcess, args ...etf.Term) error { 18 | logrus.Infof("Init (%v): args %v ", process.Name(), args) 19 | process.SendAfter(process.Self(), etf.Atom("loop"), time.Minute*10) 20 | return nil 21 | } 22 | 23 | func (dgs *DbGenServer) HandleCast(process *gen.ServerProcess, message etf.Term) gen.ServerStatus { 24 | logrus.Infof("HandleCast (%v): %v", process.Name(), message) 25 | 26 | return gen.ServerStatusOK 27 | } 28 | 29 | func (dgs *DbGenServer) HandleCall(process *gen.ServerProcess, from gen.ServerFrom, message etf.Term) (etf.Term, gen.ServerStatus) { 30 | logrus.Infof("HandleCall (%v): %v, From: %v", process.Name(), message, from) 31 | 32 | reply := etf.Term(etf.Tuple{etf.Atom("error"), etf.Atom("unknown_request")}) 33 | 34 | return reply, gen.ServerStatusOK 35 | } 36 | 37 | func (dgs *DbGenServer) HandleInfo(process *gen.ServerProcess, message etf.Term) gen.ServerStatus { 38 | defer func() { 39 | if err := recover(); err != nil { 40 | logrus.Error("err:", err) 41 | } 42 | }() 43 | 44 | switch info := message.(type) { 45 | case etf.Atom: 46 | switch info { 47 | case "loop": 48 | process.SendAfter(process.Self(), etf.Atom("loop"), time.Minute*10) 49 | loop() 50 | } 51 | } 52 | 53 | return gen.ServerStatusOK 54 | } 55 | 56 | func (dgs *DbGenServer) Terminate(process *gen.ServerProcess, reason string) { 57 | logrus.Infof("Terminate (%v): %v", process.Name(), reason) 58 | } 59 | 60 | func loop() { 61 | //commonstruct.RangeAllData(commonstruct.SaveRoleData) 62 | } 63 | -------------------------------------------------------------------------------- /cmd/state.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2021 NAME HERE 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package cmd 17 | 18 | import ( 19 | "fmt" 20 | "server/gserver/commonstruct" 21 | "strconv" 22 | 23 | "github.com/spf13/cobra" 24 | ) 25 | 26 | // stateCmd represents the state command 27 | var stateCmd = &cobra.Command{ 28 | Use: "state", 29 | Short: "获取服务器运行状态", 30 | Long: ``, 31 | Run: func(cmd *cobra.Command, args []string) { 32 | 33 | var serverid, ip string 34 | if len(args) == 2 { 35 | serverid = args[0] 36 | ip = args[1] 37 | } else { 38 | serverid = strconv.Itoa(int(commonstruct.ServerCfg.ServerID)) 39 | ip = "127.0.0.1" 40 | } 41 | 42 | startDebugGen(serverid, ip) 43 | if info, err := call("state"); err == nil { 44 | fmt.Printf(" %v \n", info) 45 | } else { 46 | fmt.Println(err.Error()) 47 | } 48 | }, 49 | } 50 | 51 | func init() { 52 | rootCmd.AddCommand(stateCmd) 53 | 54 | // Here you will define your flags and configuration settings. 55 | 56 | // Cobra supports Persistent Flags which will work for this command 57 | // and all subcommands, e.g.: 58 | // stateCmd.PersistentFlags().String("foo", "", "A help for foo") 59 | 60 | // Cobra supports local flags which will only run when this command 61 | // is called directly, e.g.: 62 | // stateCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 63 | } 64 | -------------------------------------------------------------------------------- /cmd/stop.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2021 NAME HERE 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package cmd 17 | 18 | import ( 19 | "fmt" 20 | "server/gserver/commonstruct" 21 | "strconv" 22 | 23 | "github.com/spf13/cobra" 24 | ) 25 | 26 | // stopCmd represents the stop command 27 | var stopCmd = &cobra.Command{ 28 | Use: "stop", 29 | Short: "关闭服务器", 30 | Long: `shut down game server`, 31 | Run: func(cmd *cobra.Command, args []string) { 32 | var serverid, ip string 33 | if len(args) == 2 { 34 | serverid = args[0] 35 | ip = args[1] 36 | } else { 37 | serverid = strconv.Itoa(int(commonstruct.ServerCfg.ServerID)) 38 | ip = "127.0.0.1" 39 | } 40 | startDebugGen(serverid, ip) 41 | 42 | if info, err := call("shutdown"); err == nil { 43 | fmt.Printf("[%v] shutdown \n", info) 44 | } else { 45 | fmt.Println("not running ") 46 | fmt.Println("err:", err) 47 | } 48 | 49 | }, 50 | } 51 | 52 | func init() { 53 | rootCmd.AddCommand(stopCmd) 54 | 55 | // Here you will define your flags and configuration settings. 56 | 57 | // Cobra supports Persistent Flags which will work for this command 58 | // and all subcommands, e.g.: 59 | // stopCmd.PersistentFlags().String("foo", "", "A help for foo") 60 | 61 | // Cobra supports local flags which will only run when this command 62 | // is called directly, e.g.: 63 | // stopCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 64 | } 65 | -------------------------------------------------------------------------------- /cmd/reloadCfg.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2021 NAME HERE 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package cmd 17 | 18 | import ( 19 | "fmt" 20 | "server/gserver/commonstruct" 21 | "strconv" 22 | 23 | "github.com/spf13/cobra" 24 | ) 25 | 26 | // upcfgCmd represents the upcfg command 27 | var upcfgCmd = &cobra.Command{ 28 | Use: "reloadcfg", 29 | Short: "重新加载配置", 30 | Long: ``, 31 | Run: func(cmd *cobra.Command, args []string) { 32 | var serverid, ip string 33 | if len(args) == 2 { 34 | serverid = args[0] 35 | ip = args[1] 36 | } else { 37 | serverid = strconv.Itoa(int(commonstruct.ServerCfg.ServerID)) 38 | ip = "127.0.0.1" 39 | } 40 | 41 | startDebugGen(serverid, ip) 42 | 43 | if info, err := call("ReloadCfg"); err == nil { 44 | fmt.Printf("[%v] ReloadCfg \n", info) 45 | } else { 46 | fmt.Println("not running ") 47 | fmt.Println("err:", err) 48 | } 49 | 50 | }, 51 | } 52 | 53 | func init() { 54 | rootCmd.AddCommand(upcfgCmd) 55 | 56 | // Here you will define your flags and configuration settings. 57 | 58 | // Cobra supports Persistent Flags which will work for this command 59 | // and all subcommands, e.g.: 60 | // upcfgCmd.PersistentFlags().String("foo", "", "A help for foo") 61 | 62 | // Cobra supports local flags which will only run when this command 63 | // is called directly, e.g.: 64 | // upcfgCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 65 | } 66 | -------------------------------------------------------------------------------- /logger/contextHook.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | "strings" 7 | 8 | "github.com/sirupsen/logrus" 9 | ) 10 | 11 | // ContextHook for log the call context 12 | type contextHook struct { 13 | Field string 14 | Skip int 15 | levels []logrus.Level 16 | } 17 | 18 | // NewContextHook use to make an hook 19 | // 根据上面的推断, 我们递归深度可以设置到5即可. 20 | func NewContextHook(levels ...logrus.Level) logrus.Hook { 21 | hook := contextHook{ 22 | Field: "line", 23 | Skip: 5, 24 | levels: levels, 25 | } 26 | if len(hook.levels) == 0 { 27 | hook.levels = logrus.AllLevels 28 | } 29 | return &hook 30 | } 31 | 32 | // Levels implement levels 33 | func (hook contextHook) Levels() []logrus.Level { 34 | return hook.levels 35 | } 36 | 37 | // Fire implement fire 38 | func (hook contextHook) Fire(entry *logrus.Entry) error { 39 | entry.Data[hook.Field] = findCaller(hook.Skip) 40 | return nil 41 | } 42 | 43 | // 对caller进行递归查询, 直到找到非logrus包产生的第一个调用. 44 | // 因为filename我获取到了上层目录名, 因此所有logrus包的调用的文件名都是 logrus/... 45 | // 因此通过排除logrus开头的文件名, 就可以排除所有logrus包的自己的函数调用 46 | func findCaller(skip int) string { 47 | file := "" 48 | line := 0 49 | for i := 0; i < 10; i++ { 50 | file, line = getCaller(skip + i) 51 | if !strings.HasPrefix(file, "logrus") { 52 | break 53 | } 54 | } 55 | return fmt.Sprintf("%s:%d", file, line) 56 | } 57 | 58 | // 这里其实可以获取函数名称的: fnName := runtime.FuncForPC(pc).Name() 59 | // 但是我觉得有 文件名和行号就够定位问题, 因此忽略了caller返回的第一个值:pc 60 | // 在标准库log里面我们可以选择记录文件的全路径或者文件名, 但是在使用过程成并发最合适的, 61 | // 因为文件的全路径往往很长, 而文件名在多个包中往往有重复, 因此这里选择多取一层, 取到文件所在的上层目录那层. 62 | func getCaller(skip int) (string, int) { 63 | _, file, line, ok := runtime.Caller(skip) 64 | //fmt.Println(file) 65 | //fmt.Println(line) 66 | if !ok { 67 | return "", 0 68 | } 69 | n := 0 70 | for i := len(file) - 1; i > 0; i-- { 71 | if file[i] == '/' { 72 | n++ 73 | if n >= 2 { 74 | file = file[i+1:] 75 | break 76 | } 77 | } 78 | } 79 | return file, line 80 | } 81 | -------------------------------------------------------------------------------- /tools/pidfile.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "io/ioutil" 8 | "os" 9 | "path/filepath" 10 | "strconv" 11 | 12 | "github.com/facebookgo/atomicfile" 13 | ) 14 | 15 | var ( 16 | errNotConfigured = errors.New("pidfile not configured") 17 | pidfile = "" 18 | ) 19 | 20 | // IsNotConfigured returns true if the error indicates the pidfile location has 21 | // not been configured. 22 | func IsNotConfigured(err error) bool { 23 | return err == errNotConfigured 24 | } 25 | 26 | // GetPidfilePath returns the configured pidfile path. 27 | func GetPidfilePath() string { 28 | return pidfile 29 | } 30 | 31 | // SetPidfilePath sets the pidfile path. 32 | func SetPidfilePath(p string) { 33 | pidfile = p 34 | } 35 | 36 | // Write the pidfile based on the flag. It is an error if the pidfile hasn't 37 | // been configured. 38 | func Write() error { 39 | if pidfile == "" { 40 | return errNotConfigured 41 | } 42 | 43 | if err := os.MkdirAll(filepath.Dir(pidfile), os.FileMode(0755)); err != nil { 44 | return err 45 | } 46 | 47 | file, err := atomicfile.New(pidfile, os.FileMode(0644)) 48 | if err != nil { 49 | return fmt.Errorf("error opening pidfile %s: %s", pidfile, err) 50 | } 51 | defer file.Close() // in case we fail before the explicit close 52 | 53 | _, err = fmt.Fprintf(file, "%d", os.Getpid()) 54 | if err != nil { 55 | return err 56 | } 57 | 58 | err = file.Close() 59 | if err != nil { 60 | return err 61 | } 62 | 63 | return nil 64 | } 65 | 66 | // Read the pid from the configured file. It is an error if the pidfile hasn't 67 | // been configured. 68 | func Read() (int, error) { 69 | if pidfile == "" { 70 | return 0, errNotConfigured 71 | } 72 | 73 | d, err := ioutil.ReadFile(pidfile) 74 | if err != nil { 75 | return 0, err 76 | } 77 | 78 | pid, err := strconv.Atoi(string(bytes.TrimSpace(d))) 79 | if err != nil { 80 | return 0, fmt.Errorf("error parsing pid from %s: %s", pidfile, err) 81 | } 82 | 83 | return pid, nil 84 | } 85 | -------------------------------------------------------------------------------- /cmd/excel.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2021 NAME HERE 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package cmd 17 | 18 | import ( 19 | "fmt" 20 | "strconv" 21 | "strings" 22 | 23 | "github.com/go-basic/uuid" 24 | "github.com/spf13/cobra" 25 | "github.com/xuri/excelize/v2" 26 | ) 27 | 28 | // excelCmd represents the excel command 29 | var excelCmd = &cobra.Command{ 30 | Use: "excel", 31 | Short: "excel [数量] [长度] 生成字符串cdk", 32 | Long: `生成excel 字符串cdk`, 33 | Args: cobra.ExactValidArgs(2), 34 | Run: func(cmd *cobra.Command, args []string) { 35 | num, _ := strconv.Atoi(args[0]) 36 | lennum, _ := strconv.Atoi(args[1]) 37 | excel(num, lennum) 38 | }, 39 | } 40 | 41 | func init() { 42 | rootCmd.AddCommand(excelCmd) 43 | } 44 | 45 | func excel(num, lennum int) { 46 | fmt.Println("生成excel") 47 | f := excelize.NewFile() 48 | // Create a new sheet. 49 | index := f.NewSheet("CDKList") 50 | 51 | f.DeleteSheet("Sheet1") 52 | 53 | tmpmap := map[string]interface{}{} 54 | 55 | i := 0 56 | for { 57 | uuid := uuid.New() 58 | strNum := strings.Replace(uuid, "-", "", -1)[0:lennum] 59 | 60 | if _, ok := tmpmap[strNum]; ok { 61 | continue 62 | } 63 | 64 | i++ 65 | f.SetCellValue("CDKList", fmt.Sprintf("A%v", i), strNum) 66 | f.SetCellValue("CDKList", fmt.Sprintf("B%v", i), uuid) 67 | if i >= num { 68 | break 69 | } 70 | } 71 | 72 | f.SetActiveSheet(index) 73 | if err := f.SaveAs(fmt.Sprintf("%v个随机长度为%v的cdk.xlsx", num, lennum)); err != nil { 74 | fmt.Println(err) 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /gserver/nodeManange/databaseSup.go: -------------------------------------------------------------------------------- 1 | package nodeManange 2 | 3 | import ( 4 | "server/gserver/commonstruct" 5 | "server/gserver/genServer" 6 | 7 | "github.com/ergo-services/ergo" 8 | "github.com/ergo-services/ergo/etf" 9 | "github.com/ergo-services/ergo/gen" 10 | "github.com/ergo-services/ergo/node" 11 | ) 12 | 13 | type DataBaseSup struct { 14 | gen.Supervisor 15 | } 16 | 17 | func (ds *DataBaseSup) Init(args ...etf.Term) (gen.SupervisorSpec, error) { 18 | return gen.SupervisorSpec{ 19 | Name: "DataBaseSup", 20 | Children: []gen.SupervisorChildSpec{ 21 | { 22 | Name: string(DBGenServer), //"dbServer", 23 | Child: &genServer.DbGenServer{}, 24 | //Args: []interface{}{}, 25 | }, 26 | }, 27 | Strategy: gen.SupervisorStrategy{ 28 | Type: gen.SupervisorStrategyOneForOne, 29 | //Type: ergo.SupervisorStrategyOneForAll, 30 | //Type: ergo.SupervisorStrategyRestForOne, 31 | 32 | //重启策略 33 | // one_for_one : 把子进程当成各自独立的,一个进程出现问题其它进程不会受到崩溃的进程的影响.该子进程死掉,只有这个进程会被重启 34 | // one_for_all : 如果子进程终止,所有其它子进程也都会被终止,然后所有进程都会被重启. 35 | // rest_for_one:如果一个子进程终止,在这个进程启动之后启动的进程都会被终止掉.然后终止掉的进程和连带关闭的进程都会被重启. 36 | // simple_one_for_one 是one_for_one的简化版 ,所有子进程都动态添加同一种进程的实例 37 | Intensity: 3, //次数 38 | Period: 5, //时间 1 -0 代表不重启 39 | 40 | Restart: gen.SupervisorStrategyRestartTemporary, 41 | //Restart: gen.SupervisorStrategyRestartTemporary, 42 | //Restart: gen.SupervisorStrategyRestartTransient, 43 | //Restart: gen.SupervisorStrategyRestartPermanent, 44 | 45 | // temporary:进程永远都不会被重启 46 | // transient: 只有进程异常终止的时候会被重启 47 | // permanent:遇到任何错误导致进程终止就会重启 48 | }, 49 | }, nil 50 | } 51 | 52 | func StartDataBaseSupSupNode(nodeName string) (node.Node, gen.Process, error) { 53 | opts := node.Options{ 54 | ListenBegin: uint16(commonstruct.ServerCfg.ListenBegin), 55 | ListenEnd: uint16(commonstruct.ServerCfg.ListenEnd), 56 | } 57 | 58 | node, err := ergo.StartNode(nodeName, commonstruct.ServerCfg.Cookie, opts) 59 | if err != nil { 60 | return nil, nil, err 61 | } 62 | // Spawn supervisor process 63 | process, err := node.Spawn("database_sup", gen.ProcessOptions{}, &DataBaseSup{}) 64 | return node, process, err 65 | } 66 | -------------------------------------------------------------------------------- /tools/taptap.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import ( 4 | "crypto/hmac" 5 | "crypto/sha1" 6 | "encoding/base64" 7 | "encoding/json" 8 | "fmt" 9 | "io/ioutil" 10 | "math/rand" 11 | "net/http" 12 | "time" 13 | ) 14 | 15 | type TapTapJson struct { 16 | Data struct { 17 | Name string `json:"name"` 18 | Avatar string `json:"avatar"` 19 | Gender string `json:"gender"` 20 | Openid string `json:"openid"` 21 | Unionid string `json:"unionid"` 22 | } `json:"data"` 23 | Success bool `json:"success"` 24 | } 25 | 26 | func HmacSHA1(key string, data string) string { 27 | mac := hmac.New(sha1.New, []byte(key)) 28 | mac.Write([]byte(data)) 29 | return base64.StdEncoding.EncodeToString(mac.Sum(nil)) 30 | 31 | } 32 | 33 | //客户端 ID SDK 获取的 mac_key 34 | func TapTapLogin(client_id, access_token, mac_key string) *TapTapJson { 35 | taptapjson := &TapTapJson{} 36 | 37 | url := fmt.Sprintf("https://openapi.taptap.com/account/profile/v1?client_id=%v", client_id) 38 | 39 | //当前时间戳 40 | ts := time.Now().Unix() 41 | // 随机数,正式上线请替换 42 | nonce := RandString(5) 43 | 44 | // # 请求方法 45 | METHOD := "GET" 46 | // # 请求地址 (带 query string) 47 | REQUEST_URI := fmt.Sprintf("/account/profile/v1?client_id=%v", client_id) 48 | // # 请求域名 49 | REQUEST_HOST := "openapi.taptap.com" 50 | mac := HmacSHA1(mac_key, fmt.Sprintf("%v\n%v\n%v\n%v\n%v\n443\n\n", ts, nonce, METHOD, REQUEST_URI, REQUEST_HOST)) 51 | 52 | reqest, err := http.NewRequest("GET", url, nil) 53 | if err != nil { 54 | fmt.Println("http :", err) 55 | } 56 | reqest.Header.Add("AUTHORIZATION", fmt.Sprintf("MAC id=\"%v\",ts=\"%v\",nonce=\"%v\",mac=\"%v\"", access_token, ts, nonce, mac)) 57 | 58 | client := &http.Client{} 59 | response, err := client.Do(reqest) 60 | if err != nil { 61 | fmt.Println("http get:", err) 62 | } 63 | defer response.Body.Close() 64 | 65 | respbyte, _ := ioutil.ReadAll(response.Body) 66 | json.Unmarshal(respbyte, taptapjson) 67 | return taptapjson 68 | } 69 | 70 | func RandString(len int) string { 71 | r := rand.New(rand.NewSource(time.Now().Unix())) 72 | bytes := make([]byte, len) 73 | for i := 0; i < len; i++ { 74 | b := r.Intn(26) + 65 75 | bytes[i] = byte(b) 76 | } 77 | return string(bytes) 78 | } 79 | -------------------------------------------------------------------------------- /gserver/nodeManange/gateSup.go: -------------------------------------------------------------------------------- 1 | package nodeManange 2 | 3 | import ( 4 | "server/gserver/commonstruct" 5 | 6 | "github.com/ergo-services/ergo" 7 | "github.com/ergo-services/ergo/etf" 8 | "github.com/ergo-services/ergo/gen" 9 | "github.com/ergo-services/ergo/node" 10 | ) 11 | 12 | type GateWaySup struct { 13 | gen.Supervisor 14 | } 15 | 16 | func (ds *GateWaySup) Init(args ...etf.Term) (gen.SupervisorSpec, error) { 17 | return gen.SupervisorSpec{ 18 | Name: "GateWaySup", 19 | Children: []gen.SupervisorChildSpec{ 20 | // { 21 | // Name: "gateServer", 22 | // Child: &genServer.GateGenServer{}, 23 | // //Args: []interface{}{}, 24 | // }, 25 | }, 26 | Strategy: gen.SupervisorStrategy{ 27 | //Type: gen.SupervisorStrategyOneForAll, 28 | //Type: gen.SupervisorStrategyRestForOne, 29 | //Type: gen.SupervisorStrategyOneForOne, 30 | Type: gen.SupervisorStrategySimpleOneForOne, 31 | 32 | //重启策略 33 | // one_for_one : 把子进程当成各自独立的,一个进程出现问题其它进程不会受到崩溃的进程的影响.该子进程死掉,只有这个进程会被重启 34 | // one_for_all : 如果子进程终止,所有其它子进程也都会被终止,然后所有进程都会被重启. 35 | // rest_for_one:如果一个子进程终止,在这个进程启动之后启动的进程都会被终止掉.然后终止掉的进程和连带关闭的进程都会被重启. 36 | // simple_one_for_one 是one_for_one的简化版 ,所有子进程都动态添加同一种进程的实例 37 | Intensity: 3, //次数 38 | Period: 5, //时间 1 -0 代表不重启 39 | 40 | Restart: gen.SupervisorStrategyRestartTemporary, 41 | //Restart: gen.SupervisorStrategyRestartTemporary, 42 | //Restart: gen.SupervisorStrategyRestartTransient, 43 | //Restart: gen.SupervisorStrategyRestartPermanent, 44 | 45 | // temporary:进程永远都不会被重启 46 | // transient: 只有进程异常终止的时候会被重启 47 | // permanent:遇到任何错误导致进程终止就会重启 48 | }, 49 | }, nil 50 | } 51 | 52 | func StartGateSupNode(nodeName string) (node.Node, gen.Process, error) { 53 | opts := node.Options{ 54 | ListenBegin: uint16(commonstruct.ServerCfg.ListenBegin), 55 | ListenEnd: uint16(commonstruct.ServerCfg.ListenEnd), 56 | } 57 | node, err := ergo.StartNode(nodeName, commonstruct.ServerCfg.Cookie, opts) 58 | if err != nil { 59 | return nil, nil, err 60 | } 61 | //node := ergo.StartNode(nodeName, serverCfg.Cookie, opts) 62 | // Spawn supervisor process 63 | process, err := node.Spawn("gateway_sup", gen.ProcessOptions{}, &GateWaySup{}) 64 | return node, process, err 65 | } 66 | -------------------------------------------------------------------------------- /cmd/debug.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2021 NAME HERE 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package cmd 17 | 18 | import ( 19 | "log" 20 | "server/gserver/commonstruct" 21 | "strconv" 22 | "strings" 23 | 24 | "github.com/c-bata/go-prompt" 25 | "github.com/spf13/cobra" 26 | ) 27 | 28 | // debugCmd represents the debug command 29 | var debugCmd = &cobra.Command{ 30 | Use: "debug", 31 | Short: "控制台", 32 | Long: `gen sever ping GameServer `, 33 | Run: func(cmd *cobra.Command, args []string) { 34 | if len(args) == 2 { 35 | debug(args[0], args[1]) 36 | } else { 37 | debug(strconv.Itoa(int(commonstruct.ServerCfg.ServerID)), "127.0.0.1") 38 | } 39 | }, 40 | } 41 | 42 | func init() { 43 | rootCmd.AddCommand(debugCmd) 44 | } 45 | 46 | func debug(serverid, ip string) { 47 | ok, servername := ping(serverid, ip) 48 | if !ok { 49 | return 50 | } 51 | 52 | for { 53 | command := strings.TrimSpace(prompt.Input("["+servername+"] > ", completer)) 54 | if command == "quit" { 55 | return 56 | } 57 | if command == "" { 58 | break 59 | } 60 | term, err := call(strings.Split(command, " ")...) 61 | log.Printf("info: %v [%v]\n", term, command) 62 | 63 | if command == "shutdown" && err == nil { 64 | return 65 | } 66 | } 67 | 68 | } 69 | 70 | //commands = []string{"ping", "reloadCfg", "state", "shutdown"} 71 | func completer(d prompt.Document) []prompt.Suggest { 72 | s := []prompt.Suggest{ 73 | {Text: "quit", Description: "退出连接模式"}, 74 | {Text: "state", Description: "查看服务器状态"}, 75 | {Text: "reloadCfg", Description: "重新加载配置文件"}, 76 | {Text: "shutdown", Description: "关闭服务器!!!"}, 77 | } 78 | return prompt.FilterHasPrefix(s, d.GetWordBeforeCursor(), true) 79 | } 80 | -------------------------------------------------------------------------------- /gserver/commonstruct/serverCfg.go: -------------------------------------------------------------------------------- 1 | package commonstruct 2 | 3 | // ServerConfig server cfg 4 | type ServerConfig struct { 5 | ServerName string 6 | ServerID int32 7 | Version string 8 | 9 | Daemon bool 10 | RestartNum int 11 | 12 | OpenHTTP bool 13 | HTTPPort int32 14 | OpenWS bool 15 | SetMode string 16 | 17 | OpenPyroscope bool 18 | PyroscopeHost string 19 | 20 | NetType string 21 | Port int32 22 | Packet int32 23 | Readtimeout int32 //读超时时间 24 | MaxConnectNum int32 //最大连接数 25 | 26 | MsgTime int32 27 | MsgNum int32 28 | 29 | ProtoPath string 30 | GoOut string 31 | 32 | MongoConnStr string 33 | Mongodb string 34 | 35 | RedisConnStr string 36 | RedisDB int 37 | 38 | CfgPath string 39 | CfgType string 40 | WatchConfig bool 41 | 42 | LogWrite bool 43 | Loglevel string 44 | LogPath string 45 | LogName string 46 | 47 | ListenBegin int 48 | ListenEnd int 49 | Cookie string 50 | 51 | StartList []string 52 | CentralServerNode []string 53 | } 54 | 55 | // ServerCfg Program overall configuration 56 | var ServerCfg = ServerConfig{ 57 | ServerName: "server", 58 | ServerID: 1, 59 | Version: "1.0.0", 60 | 61 | Daemon: false, 62 | RestartNum: 2, 63 | 64 | // http 65 | OpenHTTP: false, 66 | HTTPPort: 8080, 67 | OpenWS: true, 68 | SetMode: "debug", 69 | 70 | OpenPyroscope: false, 71 | PyroscopeHost: "http://localhost:4040", 72 | 73 | // #network : tcp/udp 74 | NetType: "tcp", 75 | Port: 3344, 76 | Packet: 2, 77 | Readtimeout: 0, 78 | MaxConnectNum: 2000, 79 | 80 | MsgTime: 300, 81 | MsgNum: 500, 82 | 83 | // #protobuf path 84 | ProtoPath: "./proto", 85 | GoOut: "./msgproto", 86 | 87 | MongoConnStr: "mongodb://localhost:27017", 88 | Mongodb: "mygame", 89 | 90 | RedisConnStr: "localhost:6379", 91 | RedisDB: 0, 92 | 93 | CfgPath: "./config", 94 | CfgType: "json", 95 | WatchConfig: false, 96 | 97 | Loglevel: "info", 98 | LogPath: "./log", 99 | LogName: "log", 100 | LogWrite: false, 101 | 102 | ListenBegin: 15151, 103 | ListenEnd: 25151, 104 | Cookie: "123", 105 | StartList: []string{"db", "gateway", "server"}, 106 | CentralServerNode: []string{"CentralServerNode@127.0.0.1"}, 107 | } 108 | -------------------------------------------------------------------------------- /gserver/nodeManange/nodes.go: -------------------------------------------------------------------------------- 1 | package nodeManange 2 | 3 | import ( 4 | "fmt" 5 | "server/gserver/commonstruct" 6 | "sync" 7 | 8 | "github.com/ergo-services/ergo/gen" 9 | "github.com/ergo-services/ergo/node" 10 | ) 11 | 12 | //ergo.Node 节点管理 13 | var ( 14 | nodesMap sync.Map 15 | //remoteMap sync.Map //远程连接节点 16 | ) 17 | 18 | type GenNodeName string 19 | 20 | const ( 21 | GateNode GenNodeName = "gate" 22 | ServerNode GenNodeName = "server" 23 | DBNode GenNodeName = "db" 24 | ) 25 | 26 | //系统初始启动的几个 genserver 27 | type GenServerName string 28 | 29 | const ( 30 | DBGenServer GenServerName = "dbServer" 31 | GameGenServer GenServerName = "gameServer" 32 | CMDGenServer GenServerName = "cmdServer" 33 | ) 34 | 35 | func Start(command chan string) { 36 | for _, v := range commonstruct.ServerCfg.StartList { 37 | switch v { 38 | case "gateway": 39 | gateNode, _, gerr := StartGateSupNode(getNodeName(GateNode)) 40 | if gerr != nil { 41 | panic(gerr) 42 | } 43 | nodesMap.Store(gateNode.Name(), gateNode) 44 | case "server": 45 | serverNode, _, serr := StartGameServerSupNode(getNodeName(ServerNode), command) 46 | if serr != nil { 47 | panic(serr) 48 | } 49 | nodesMap.Store(serverNode.Name(), serverNode) 50 | case "db": 51 | dbNode, _, derr := StartDataBaseSupSupNode(getNodeName(DBNode)) 52 | if derr != nil { 53 | panic(derr) 54 | } 55 | //dbNode.Spawn("", gen.ProcessOptions{}, nil) 56 | nodesMap.Store(dbNode.Name(), dbNode) 57 | } 58 | } 59 | } 60 | 61 | func getNodeName(node GenNodeName) string { 62 | switch node { 63 | case GateNode: 64 | return fmt.Sprintf("gatewayNode_%v@127.0.0.1", commonstruct.ServerCfg.ServerID) 65 | case ServerNode: 66 | return fmt.Sprintf("serverNode_%v@127.0.0.1", commonstruct.ServerCfg.ServerID) 67 | case DBNode: 68 | return fmt.Sprintf("dbNode_%v@127.0.0.1", commonstruct.ServerCfg.ServerID) 69 | } 70 | return "" 71 | } 72 | 73 | func GetNode(nodename GenNodeName) node.Node { 74 | if v, ok := nodesMap.Load(getNodeName(nodename)); ok { 75 | return v.(node.Node) 76 | } 77 | return nil 78 | } 79 | 80 | func GetGenServer(genserver GenServerName) gen.Process { 81 | dbnode := GetNode(DBNode) 82 | return dbnode.ProcessByName(string(genserver)) 83 | } 84 | 85 | func GetNodes() map[string]node.Node { 86 | nodemap := map[string]node.Node{} 87 | nodesMap.Range(func(key, value interface{}) bool { 88 | nodemap[key.(string)] = value.(node.Node) 89 | return true 90 | }) 91 | return nodemap 92 | } 93 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "server/gserver/commonstruct" 7 | 8 | "github.com/sirupsen/logrus" 9 | "github.com/spf13/cobra" 10 | "github.com/spf13/viper" 11 | //"github.com/joho/godotenv" 12 | ) 13 | 14 | // rootCmd represents the base command when called without any subcommands 15 | var rootCmd = &cobra.Command{ 16 | Use: "root demo", 17 | Short: "root Short", 18 | Long: `服务器`, 19 | // Uncomment the following line if your bare application 20 | // has an action associated with it: 21 | // Run: func(cmd *cobra.Command, args []string) { }, 22 | } 23 | 24 | var cfgfile string 25 | 26 | // Execute adds all child commands to the root command and sets flags appropriately. 27 | // This is called by main.main(). It only needs to happen once to the rootCmd. 28 | func Execute() { 29 | if err := rootCmd.Execute(); err != nil { 30 | fmt.Println(err) 31 | os.Exit(1) 32 | } 33 | } 34 | 35 | func init() { 36 | //initConfig() 37 | cobra.OnInitialize(initConfig) 38 | 39 | // Here you will define your flags and configuration settings. 40 | // Cobra supports persistent flags, which, if defined here, 41 | // will be global for your application. 42 | 43 | rootCmd.PersistentFlags().StringVar(&cfgfile, "config", "", "config file (default is $HOME/cfg.yaml)") 44 | 45 | // Cobra also supports local flags, which will only run 46 | // when this action is called directly. 47 | //rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 48 | 49 | } 50 | 51 | // initConfig reads in config file and ENV variables if set. 52 | func initConfig() { 53 | if cfgfile != "" { 54 | fmt.Println("initConfig config :", cfgfile) 55 | // Use config file from the flag. 56 | viper.SetConfigFile(cfgfile) 57 | } else { 58 | // Find home directory. 59 | // home, err := homedir.Dir() 60 | // if err != nil { 61 | // fmt.Println(err) 62 | // os.Exit(1) 63 | // } 64 | 65 | // fmt.Println("initConfig config home:", home) 66 | // // Search config in home directory with name ".demo" (without extension). 67 | // viper.AddConfigPath(home) 68 | 69 | // dir, _ := os.Getwd() 70 | // viper.AddConfigPath(dir) 71 | // fmt.Println("initConfig config dir:", dir) 72 | viper.AddConfigPath(".") 73 | viper.SetConfigName("cfg") 74 | } 75 | 76 | //viper.AutomaticEnv() // read in environment variables that match 77 | // If a config file is found, read it in. 78 | if err := viper.ReadInConfig(); err == nil { 79 | viper.Unmarshal(&commonstruct.ServerCfg) 80 | logrus.Info("Using config file:", viper.ConfigFileUsed()) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /web/wsChat.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Chat Example 5 | 53 | 90 | 91 | 92 |
93 |
94 | 95 | 96 |
97 | 98 | 99 | -------------------------------------------------------------------------------- /gserver/nodeManange/gameServerSup.go: -------------------------------------------------------------------------------- 1 | package nodeManange 2 | 3 | import ( 4 | "fmt" 5 | "server/gserver/commonstruct" 6 | "server/gserver/genServer" 7 | "server/tools" 8 | 9 | "github.com/ergo-services/ergo" 10 | "github.com/ergo-services/ergo/etf" 11 | "github.com/ergo-services/ergo/gen" 12 | "github.com/ergo-services/ergo/node" 13 | ) 14 | 15 | //游戏内公共服务 16 | //地图、组队、公会、世界 等等需要同步的独立进程 17 | 18 | type GameServerSup struct { 19 | gen.Supervisor 20 | ServerCmdChan chan string 21 | } 22 | 23 | func (ds *GameServerSup) Init(args ...etf.Term) (gen.SupervisorSpec, error) { 24 | return gen.SupervisorSpec{ 25 | Name: "GameServerSup", 26 | Children: []gen.SupervisorChildSpec{ 27 | { 28 | Name: string(GameGenServer), // "gameServer", 29 | Child: &genServer.GameGenServer{}, 30 | //Args: []interface{}{}, 31 | }, 32 | { 33 | Name: string(CMDGenServer), //"cmdServer", 34 | Child: &genServer.CmdGenServer{}, 35 | Args: []etf.Term{ 36 | tools.AbsPathify(commonstruct.ServerCfg.CfgPath), 37 | commonstruct.ServerCfg.CfgType, 38 | ds.ServerCmdChan, 39 | fmt.Sprintf("%v_%v", commonstruct.ServerCfg.ServerName, commonstruct.ServerCfg.ServerID), 40 | }, 41 | // Restart: ergo.SupervisorChildRestartTransient, 42 | }, 43 | }, 44 | Strategy: gen.SupervisorStrategy{ 45 | //Type: ergo.SupervisorStrategyOneForAll, 46 | // Type: ergo.SupervisorStrategyRestForOne, 47 | Type: gen.SupervisorStrategyOneForOne, 48 | //重启策略 49 | // one_for_one : 把子进程当成各自独立的,一个进程出现问题其它进程不会受到崩溃的进程的影响.该子进程死掉,只有这个进程会被重启 50 | // one_for_all : 如果子进程终止,所有其它子进程也都会被终止,然后所有进程都会被重启. 51 | // rest_for_one:如果一个子进程终止,在这个进程启动之后启动的进程都会被终止掉.然后终止掉的进程和连带关闭的进程都会被重启. 52 | // simple_one_for_one 是one_for_one的简化版 ,所有子进程都动态添加同一种进程的实例 53 | Intensity: 3, //次数 54 | Period: 5, //时间 1 -0 代表不重启 55 | 56 | Restart: gen.SupervisorStrategyRestartTemporary, 57 | //Restart: gen.SupervisorStrategyRestartTemporary, 58 | //Restart: gen.SupervisorStrategyRestartTransient, 59 | //Restart: gen.SupervisorStrategyRestartPermanent, 60 | 61 | // temporary:进程永远都不会被重启 62 | // transient: 只有进程异常终止的时候会被重启 63 | // permanent:遇到任何错误导致进程终止就会重启 64 | }, 65 | }, nil 66 | } 67 | 68 | func StartGameServerSupNode(nodeName string, cmd chan string) (node.Node, gen.Process, error) { 69 | opts := node.Options{ 70 | ListenBegin: uint16(commonstruct.ServerCfg.ListenBegin), 71 | ListenEnd: uint16(commonstruct.ServerCfg.ListenEnd), 72 | } 73 | node, err := ergo.StartNode(nodeName, commonstruct.ServerCfg.Cookie, opts) 74 | if err != nil { 75 | return nil, nil, err 76 | } 77 | 78 | // Spawn supervisor process 79 | process, err := node.Spawn("gameServer_sup", gen.ProcessOptions{}, &GameServerSup{ServerCmdChan: cmd}) 80 | 81 | return node, process, err 82 | 83 | } 84 | -------------------------------------------------------------------------------- /gserver/genServer/cmdGenServer.go: -------------------------------------------------------------------------------- 1 | package genServer 2 | 3 | import ( 4 | "fmt" 5 | "server/gserver/cfg" 6 | 7 | "github.com/ergo-services/ergo/etf" 8 | "github.com/ergo-services/ergo/gen" 9 | "github.com/facebookgo/pidfile" 10 | "github.com/sirupsen/logrus" 11 | ) 12 | 13 | //命令服务 用于接收 外部发过来的服务命令 14 | 15 | // GenServer implementation structure 16 | type CmdGenServer struct { 17 | gen.Server 18 | CfgPath string 19 | CfgType string 20 | ServerCmdChan chan string 21 | ServerNmae string 22 | } 23 | 24 | func (dgs *CmdGenServer) Init(process *gen.ServerProcess, args ...etf.Term) error { 25 | logrus.Infof("Init (%v): args %v ", process.Name(), args) 26 | dgs.CfgPath = args[0].(string) 27 | dgs.CfgType = args[1].(string) 28 | dgs.ServerCmdChan = args[2].(chan string) 29 | dgs.ServerNmae = args[3].(string) 30 | 31 | return nil 32 | } 33 | 34 | func (dgs *CmdGenServer) HandleCast(process *gen.ServerProcess, message etf.Term) gen.ServerStatus { 35 | logrus.Infof("HandleCast (%v): %v", process.Name(), message) 36 | // switch message { 37 | // case etf.Atom("stop"): 38 | // return gen.ServerStatusStopWithReason("stop normal") 39 | // } 40 | return gen.ServerStatusOK 41 | } 42 | 43 | func (gd *CmdGenServer) HandleDirect(process *gen.ServerProcess, message interface{}) (interface{}, error) { 44 | 45 | return nil, nil 46 | } 47 | 48 | func (dgs *CmdGenServer) HandleCall(process *gen.ServerProcess, from gen.ServerFrom, message etf.Term) (etf.Term, gen.ServerStatus) { 49 | logrus.Infof("HandleCall (%v): %v ", process.Name(), message) 50 | reply := etf.Term(etf.Tuple{etf.Atom("error"), etf.Atom("unknown_request")}) 51 | 52 | switch message { 53 | case etf.Atom("ping"): 54 | reply = etf.Atom(dgs.ServerNmae) 55 | case etf.Atom("reloadCfg"): 56 | cfg.InitViperConfig(dgs.CfgPath, dgs.CfgType) 57 | reply = etf.Atom("ReloadCfg ok") 58 | case etf.Atom("shutdown"): 59 | dgs.ServerCmdChan <- "shutdown" 60 | reply = etf.Atom(dgs.ServerNmae + " shutdown") 61 | case etf.Atom("OpenConn"): 62 | dgs.ServerCmdChan <- "OpenConn" 63 | reply = etf.Atom("ok") 64 | case etf.Atom("CloseConn"): 65 | dgs.ServerCmdChan <- "CloseConn" 66 | reply = etf.Atom("ok") 67 | case etf.Atom("state"): 68 | i, _ := pidfile.Read() 69 | reply = etf.Atom(fmt.Sprintf(" [%v] pid:[%v]", dgs.ServerNmae, i)) 70 | default: 71 | logrus.Debug("info:", message) 72 | } 73 | return reply, gen.ServerStatusOK 74 | } 75 | 76 | func (dgs *CmdGenServer) HandleInfo(process *gen.ServerProcess, message etf.Term) gen.ServerStatus { 77 | logrus.Infof("HandleInfo (%v): %v", process.Name(), message) 78 | 79 | return gen.ServerStatusOK 80 | } 81 | 82 | func (dgs *CmdGenServer) Terminate(process *gen.ServerProcess, reason string) { 83 | logrus.Infof("Terminate (%v): %v", process.Name(), reason) 84 | 85 | } 86 | -------------------------------------------------------------------------------- /web/gin_web.go: -------------------------------------------------------------------------------- 1 | package web 2 | 3 | import ( 4 | "fmt" 5 | "server/gserver/commonstruct" 6 | "server/network" 7 | "sync/atomic" 8 | "time" 9 | 10 | "github.com/gin-gonic/gin" 11 | "github.com/sirupsen/logrus" 12 | log "github.com/sirupsen/logrus" 13 | ) 14 | 15 | // Start gin web interface 16 | func Start(Port int32, setmode string, nw *network.NetWorkx) { 17 | log.Info("Start [Web Http]") 18 | //禁用控制台颜色,在将日志写入文件时不需要控制台颜色 19 | //gin.DisableConsoleColor() 20 | 21 | //如果需要控制台输出带有颜色的字体,请使用下面代码 22 | gin.ForceConsoleColor() 23 | 24 | //如果需要将日志写入文件,请使用以下代码 25 | //创建日志文件 26 | //f, _ := os.Create("gin.log") 27 | //gin.DefaultWriter = io.MultiWriter(f) 28 | 29 | //如果需要将日志输出到控制台,请使用以下代码 30 | //gin.DefaultWriter = io.MultiWriter(os.Stdout) 31 | 32 | //如果需要同时将日志写入文件和控制台,请使用以下代码 33 | //gin.DefaultWriter = io.MultiWriter(f, os.Stdout) 34 | 35 | gin.SetMode(setmode) 36 | router := gin.New() 37 | router.Use(logger(), gin.Recovery()) 38 | 39 | router.GET("/refreshCfg", refreshCfg) 40 | 41 | router.GET("/ping", func(context *gin.Context) { 42 | context.JSON(200, gin.H{ 43 | "message": "pong", 44 | }) 45 | }) 46 | 47 | if commonstruct.ServerCfg.OpenWS { 48 | log.Info("Start [Web Socket]") 49 | go WSHub.run() 50 | 51 | //ws 测试 52 | router.GET("/", func(context *gin.Context) { 53 | context.File("web/wsChat.html") 54 | }) 55 | 56 | router.GET("/ws", func(context *gin.Context) { 57 | num := atomic.LoadInt32(&nw.ConnectCount) 58 | if !nw.OpenConn || num >= nw.MaxConnectNum { 59 | logrus.Warnf("sockert connect open:[%v] max count:[%v]", nw.OpenConn, nw.MaxConnectNum) 60 | context.JSON(500, gin.H{ 61 | "message": "", 62 | }) 63 | return 64 | } 65 | 66 | WsClient(WSHub, context, nw) 67 | }) 68 | } 69 | 70 | // automatically add routers for net/http/pprof 71 | // e.g. /debug/pprof, /debug/pprof/heap, etc. 72 | //ginpprof.Wrap(router) 73 | 74 | // ginpprof also plays well with *gin.RouterGroup 75 | // group := router.Group("/debug/pprof") 76 | // ginpprof.WrapGroup(group) 77 | //http://localhost:8080/debug/pprof/ 78 | 79 | router.Run(fmt.Sprintf(":%v", Port)) 80 | 81 | } 82 | 83 | //刷新配置 84 | func refreshCfg(c *gin.Context) { 85 | 86 | c.JSON(200, gin.H{ 87 | "message": "ok", 88 | }) 89 | } 90 | 91 | // 日志中间件 92 | func logger() gin.HandlerFunc { 93 | return func(c *gin.Context) { 94 | startTime := time.Now() 95 | c.Next() 96 | endTime := time.Now() 97 | latencyTime := endTime.Sub(startTime) 98 | reqMethod := c.Request.Method 99 | reqURI := c.Request.RequestURI 100 | statusCode := c.Writer.Status() 101 | clientIP := c.Request.Host 102 | log.Infof("| %3d | %13v | %15s | %s | %s |", statusCode, latencyTime, clientIP, reqMethod, reqURI) 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /db/redis_test.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/sirupsen/logrus" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func init() { 12 | StartRedis("127.0.0.1:6379", 0) 13 | } 14 | 15 | // func TestGetAutoID(t *testing.T) { 16 | // key := GetAutoID("log") 17 | // assert.Equal(t, key, int32(1)) 18 | // key = GetAutoID("log") 19 | // assert.Equal(t, key, int32(2)) 20 | // key = GetAutoID("log") 21 | // assert.Equal(t, key, int32(3)) 22 | // v, e := RedisExec("del", "log") 23 | // assert.Equal(t, v, int64(1)) 24 | // assert.Equal(t, e, nil) 25 | // } 26 | 27 | //go test -bench=SaveStruct -run=XXX -benchtime=10s 28 | // func BenchmarkSaveStruct(t *testing.B) { 29 | 30 | // data := Testdata{Name: "wq", Age: 18} 31 | // SetStruct("t1", data) 32 | // readdata := &Testdata{} 33 | // GetStruct("t1", readdata) 34 | 35 | // assert.Equal(t, readdata.Age, int32(18)) 36 | // assert.Equal(t, readdata.Name, "wq") 37 | // RedisExec("del", "t1") 38 | // } 39 | 40 | func TestHMGET(t *testing.T) { 41 | HMSET("field", "name", "天王盖地", 123, 18, "show", "23434") 42 | 43 | data, _ := HMGET("field", "name") 44 | assert.Equal(t, data["name"], "天王盖地") 45 | 46 | data, _ = HMGET("field", 123, "show", "s") 47 | assert.Equal(t, data[123], "18") 48 | assert.Equal(t, data["show"], "23434") 49 | assert.Equal(t, data["s"], "") 50 | 51 | logrus.Info(data) 52 | RedisExec("del", "field") 53 | 54 | logrus.Info("RedisGetInt:", RedisGetInt("test11")) 55 | logrus.Info("INCRBY:", RedisINCRBY("test11", 1)) 56 | logrus.Info("RedisGetInt:", RedisGetInt("test11")) 57 | logrus.Info("INCRBY:", RedisINCRBY("test11", 1)) 58 | logrus.Info("RedisGetInt:", RedisGetInt("test11")) 59 | logrus.Info("INCRBY:", RedisINCRBY("test11", -1)) 60 | logrus.Info("RedisGetInt:", RedisGetInt("test11")) 61 | RedisExec("del", "test11") 62 | 63 | RedisSetStruct("test", &Test{Name: "test", Age: 18}) 64 | info, e := GetStruct[Test]("test") 65 | fmt.Println(e, info) 66 | 67 | RedisExec("del", "test") 68 | } 69 | 70 | type Test struct { 71 | Name string 72 | Age int 73 | } 74 | 75 | // func TestSyncMap(t *testing.T) { 76 | // var smp sync.Map 77 | // for i := 123; i < 130; i++ { 78 | // areas := bigmapmanage.AreasInfo{AreasIndex: int32(i)} 79 | // smp.Store(areas.AreasIndex, areas) 80 | // } 81 | 82 | // smp.Range(func(key, value interface{}) bool { 83 | // areas := value.(bigmapmanage.AreasInfo) 84 | // b, err := json.Marshal(areas) 85 | // if err != nil { 86 | // return true 87 | // } 88 | // HMSET("areasSMap", areas.AreasIndex, b) 89 | // return true 90 | // }) 91 | 92 | // value, _ := HVALS("areasSMap") 93 | // for _, v := range value { 94 | // areas := &bigmapmanage.AreasInfo{} 95 | // json.Unmarshal(v, areas) 96 | // logrus.Infof(" %v", areas) 97 | // } 98 | 99 | // } 100 | -------------------------------------------------------------------------------- /cmd/completion.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2021 NAME HERE 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package cmd 17 | 18 | import ( 19 | "os" 20 | 21 | "github.com/spf13/cobra" 22 | ) 23 | 24 | // completionCmd represents the completion command 25 | var completionCmd = &cobra.Command{ 26 | Use: "completion [bash|zsh|fish|powershell]", 27 | Short: "[bash|zsh|fish|powershell] 生成补全脚本", 28 | Long: `To load completions: 29 | 30 | Bash: 31 | 32 | $ source <(yourprogram completion bash) 33 | 34 | # To load completions for each session, execute once: 35 | Linux: 36 | $ yourprogram completion bash > /etc/bash_completion.d/yourprogram 37 | MacOS: 38 | $ yourprogram completion bash > /usr/local/etc/bash_completion.d/yourprogram 39 | 40 | Zsh: 41 | 42 | # If shell completion is not already enabled in your environment you will need 43 | # to enable it. You can execute the following once: 44 | 45 | $ echo "autoload -U compinit; compinit" >> ~/.zshrc 46 | 47 | # To load completions for each session, execute once: 48 | $ yourprogram completion zsh > "${fpath[1]}/_yourprogram" 49 | 50 | # You will need to start a new shell for this setup to take effect. 51 | 52 | Fish: 53 | 54 | $ yourprogram completion fish | source 55 | 56 | # To load completions for each session, execute once: 57 | $ yourprogram completion fish > ~/.config/fish/completions/yourprogram.fish 58 | `, 59 | DisableFlagsInUseLine: true, 60 | ValidArgs: []string{"bash", "zsh", "fish", "powershell"}, 61 | Args: cobra.ExactValidArgs(1), 62 | Run: func(cmd *cobra.Command, args []string) { 63 | switch args[0] { 64 | case "bash": 65 | cmd.Root().GenBashCompletion(os.Stdout) 66 | case "zsh": 67 | cmd.Root().GenZshCompletion(os.Stdout) 68 | case "fish": 69 | cmd.Root().GenFishCompletion(os.Stdout, true) 70 | case "powershell": 71 | cmd.Root().GenPowerShellCompletion(os.Stdout) 72 | } 73 | }, 74 | } 75 | 76 | func init() { 77 | rootCmd.AddCommand(completionCmd) 78 | 79 | // Here you will define your flags and configuration settings. 80 | 81 | // Cobra supports Persistent Flags which will work for this command 82 | // and all subcommands, e.g.: 83 | // completionCmd.PersistentFlags().String("foo", "", "A help for foo") 84 | 85 | // Cobra supports local flags which will only run when this command 86 | // is called directly, e.g.: 87 | // completionCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 88 | } 89 | -------------------------------------------------------------------------------- /cmd/protobuf.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "os" 7 | "path" 8 | "server/gserver/commonstruct" 9 | "server/tools" 10 | "strconv" 11 | "time" 12 | 13 | "github.com/sirupsen/logrus" 14 | "github.com/spf13/cobra" 15 | ) 16 | 17 | // protobufCmd build protobuf 18 | var protobufCmd = &cobra.Command{ 19 | Use: "pb", 20 | Short: "pb [int] [obj] ", 21 | Long: `protobuf [只生成 int 分钟内修改文件] [生成客户端pb]`, 22 | Run: func(cmd *cobra.Command, args []string) { 23 | var filetime int64 = 0 24 | 25 | if len(args) > 0 { 26 | i, err := strconv.ParseInt(args[0], 10, 64) 27 | if err == nil { 28 | filetime = i 29 | } 30 | } 31 | 32 | isclientpb := len(args) == 2 33 | 34 | pbpath := commonstruct.ServerCfg.ProtoPath 35 | outpath := commonstruct.ServerCfg.GoOut 36 | timeformat := tools.DateTimeFormat 37 | 38 | if !PathExists(pbpath) || !PathExists(outpath) { 39 | fmt.Println("文件夹不存在:", pbpath, outpath) 40 | return 41 | } 42 | 43 | //输出地址 protoc --go_out=. proto/*.proto 44 | execstr := "protoc --go_out=. proto/*.proto" 45 | if isclientpb { 46 | execstr = "protoc -o %s/%s.pb --proto_path=%s --go_out=../ %s" 47 | } else { 48 | execstr = "protoc --proto_path=%s --go_out=../ %s" 49 | } 50 | 51 | files, _ := ioutil.ReadDir(pbpath) 52 | for _, onefile := range files { 53 | filename := onefile.Name() 54 | filebase := filename[0 : len(filename)-len(path.Ext(filename))] 55 | if !onefile.IsDir() && path.Ext(filename) == ".proto" { 56 | 57 | diff := getHourDiffer(onefile.ModTime().Format(timeformat), time.Now().Format(timeformat)) 58 | if filetime == 0 || diff < 60*filetime { 59 | 60 | execstrpro := "" 61 | if isclientpb { 62 | execstrpro = fmt.Sprintf(execstr, outpath, filebase, pbpath, filename) 63 | } else { 64 | execstrpro = fmt.Sprintf(execstr, pbpath, filename) 65 | } 66 | 67 | _, errout, err := Shellout(execstrpro) 68 | if err != nil { 69 | logrus.Errorf("protoc [%s] ==>: %v errout:%v [%v]", filename, err, errout, execstrpro) 70 | } else { 71 | logrus.Infof("protoc [%s] ==> success", filename) 72 | } 73 | } 74 | 75 | } 76 | } 77 | }, 78 | } 79 | 80 | func init() { 81 | rootCmd.AddCommand(protobufCmd) 82 | } 83 | 84 | // PathExists 判断文件夹是否存在 85 | func PathExists(path string) bool { 86 | _, err := os.Stat(path) 87 | return err == nil || os.IsExist(err) 88 | //return err == nil || !os.IsNotExist(err) 89 | //return !os.IsNotExist(err) 90 | } 91 | 92 | //获取相差时间 93 | func getHourDiffer(startTime, endTime string) int64 { 94 | var hour int64 95 | t1, err := time.ParseInLocation(tools.DateTimeFormat, startTime, time.Local) 96 | t2, err2 := time.ParseInLocation(tools.DateTimeFormat, endTime, time.Local) 97 | 98 | if err == nil && err2 == nil && t1.Before(t2) { 99 | diff := t2.Unix() - t1.Unix() // 100 | hour = diff 101 | return hour 102 | } 103 | return hour 104 | } 105 | -------------------------------------------------------------------------------- /network/kcp.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | import ( 4 | "crypto/sha1" 5 | "fmt" 6 | "sync/atomic" 7 | 8 | "github.com/sirupsen/logrus" 9 | "github.com/xtaci/kcp-go" 10 | "golang.org/x/crypto/pbkdf2" 11 | ) 12 | 13 | //KCPNetwork kcp 14 | type KCPNetwork struct { 15 | } 16 | 17 | //Start start 18 | func (c *KCPNetwork) Start(nw *NetWorkx) { 19 | key := pbkdf2.Key([]byte("demo pass"), []byte("demo salt"), 1024, 32, sha1.New) 20 | block, _ := kcp.NewAESBlockCrypt(key) 21 | 22 | connstr := fmt.Sprintf("127.0.0.1:%v", nw.Port) 23 | if listener, err := kcp.ListenWithOptions(connstr, block, 10, 3); err == nil { 24 | if nw.StartHook != nil { 25 | nw.StartHook() 26 | } 27 | 28 | for { 29 | conn, err := listener.AcceptKCP() 30 | if err != nil { 31 | logrus.Fatal(err) 32 | } 33 | logrus.Infof("kcp connect RemoteAddr:[%v]", conn.RemoteAddr().String()) 34 | 35 | num := atomic.LoadInt32(&nw.ConnectCount) 36 | if !nw.OpenConn || num >= nw.MaxConnectNum { 37 | logrus.Warnf("kcp connect open:[%v] max count:[%v]", nw.OpenConn, nw.MaxConnectNum) 38 | conn.Close() 39 | continue 40 | } 41 | 42 | go nw.HandleClient(conn) 43 | } 44 | 45 | } else { 46 | logrus.Fatal(err) 47 | } 48 | } 49 | 50 | //Close 关闭 51 | func (c *KCPNetwork) Close() { 52 | 53 | } 54 | 55 | //demo : 56 | // func startDemo() { 57 | // key := pbkdf2.Key([]byte("demo pass"), []byte("demo salt"), 1024, 32, sha1.New) 58 | // block, _ := kcp.NewAESBlockCrypt(key) 59 | // if listener, err := kcp.ListenWithOptions("127.0.0.1:12345", block, 10, 3); err == nil { 60 | // // spin-up the client 61 | // go client() 62 | // for { 63 | // s, err := listener.AcceptKCP() 64 | // if err != nil { 65 | // logrus.Fatal(err) 66 | // } 67 | // go handleEcho(s) 68 | // } 69 | // } else { 70 | // logrus.Fatal(err) 71 | // } 72 | // } 73 | 74 | // // handleEcho send back everything it received 75 | // func handleEcho(conn *kcp.UDPSession) { 76 | // buf := make([]byte, 4096) 77 | // for { 78 | // n, err := conn.Read(buf) 79 | // if err != nil { 80 | // logrus.Println(err) 81 | // return 82 | // } 83 | 84 | // n, err = conn.Write(buf[:n]) 85 | // if err != nil { 86 | // logrus.Println(err) 87 | // return 88 | // } 89 | // } 90 | // } 91 | 92 | // func client() { 93 | // key := pbkdf2.Key([]byte("demo pass"), []byte("demo salt"), 1024, 32, sha1.New) 94 | // block, _ := kcp.NewAESBlockCrypt(key) 95 | 96 | // // wait for server to become ready 97 | // time.Sleep(time.Second) 98 | 99 | // // dial to the echo server 100 | // if sess, err := kcp.DialWithOptions("127.0.0.1:12345", block, 10, 3); err == nil { 101 | // for { 102 | // data := time.Now().String() 103 | // buf := make([]byte, len(data)) 104 | // logrus.Println("sent:", data) 105 | // if _, err := sess.Write([]byte(data)); err == nil { 106 | // // read back the data 107 | // if _, err := io.ReadFull(sess, buf); err == nil { 108 | // logrus.Println("recv:", string(buf)) 109 | // } else { 110 | // logrus.Fatal(err) 111 | // } 112 | // } else { 113 | // logrus.Fatal(err) 114 | // } 115 | // time.Sleep(time.Second) 116 | // } 117 | // } else { 118 | // logrus.Fatal(err) 119 | // } 120 | // } 121 | -------------------------------------------------------------------------------- /cmd/ergoPing.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "server/gserver/commonstruct" 6 | 7 | "github.com/ergo-services/ergo" 8 | "github.com/ergo-services/ergo/etf" 9 | "github.com/ergo-services/ergo/gen" 10 | "github.com/ergo-services/ergo/node" 11 | ) 12 | 13 | var ( 14 | genServerName string = "cmdServer" 15 | gateNodeName string 16 | debugGenServer *DebugGenServer 17 | ) 18 | 19 | func call(cmd ...string) (etf.Term, error) { 20 | if len(cmd) == 1 { 21 | return debugGenServer.process.Call(gen.ProcessID{Name: genServerName, Node: gateNodeName}, etf.Atom(cmd[0])) 22 | } else { 23 | return debugGenServer.process.Call(gen.ProcessID{Name: genServerName, Node: gateNodeName}, cmd) 24 | } 25 | } 26 | 27 | func send(cmd ...string) error { 28 | if len(cmd) == 1 { 29 | return debugGenServer.process.Send(gen.ProcessID{Name: genServerName, Node: gateNodeName}, etf.Atom(cmd[0])) 30 | } else { 31 | return debugGenServer.process.Send(gen.ProcessID{Name: genServerName, Node: gateNodeName}, cmd) 32 | } 33 | } 34 | 35 | func ping(serverid, ip string) (bool, string) { 36 | startDebugGen(serverid, ip) 37 | serverName, err := call("ping") 38 | if err != nil { 39 | fmt.Println(err) 40 | return false, "" 41 | } 42 | return true, fmt.Sprint(serverName) 43 | 44 | } 45 | 46 | func startDebugGen(serverid, ip string) (node.Node, gen.Process) { 47 | gateNodeName = fmt.Sprintf("serverNode_%v@%v", serverid, ip) 48 | 49 | opts := node.Options{ 50 | ListenBegin: uint16(commonstruct.ServerCfg.ListenBegin), 51 | ListenEnd: uint16(commonstruct.ServerCfg.ListenEnd), 52 | } 53 | node, _ := ergo.StartNode("debug_server@127.0.0.1", commonstruct.ServerCfg.Cookie, opts) 54 | debugGenServer = &DebugGenServer{} 55 | // Spawn supervisor process 56 | process, _ := node.Spawn("deubg_gen", gen.ProcessOptions{}, debugGenServer) 57 | 58 | return node, process 59 | } 60 | 61 | // GenServer implementation structure 62 | type DebugGenServer struct { 63 | gen.Server 64 | process *gen.ServerProcess 65 | } 66 | 67 | // Init initializes process state using arbitrary arguments 68 | // Init(...) -> state 69 | func (dgs *DebugGenServer) Init(process *gen.ServerProcess, args ...etf.Term) error { 70 | dgs.process = process 71 | return nil 72 | } 73 | 74 | // HandleCast serves incoming messages sending via gen_server:cast 75 | // HandleCast -> ("noreply", state) - noreply 76 | // ("stop", reason) - stop with reason 77 | func (dgs *DebugGenServer) HandleCast(process *gen.ServerProcess, message etf.Term) gen.ServerStatus { 78 | return gen.ServerStatusOK 79 | } 80 | 81 | // HandleCall serves incoming messages sending via gen_server:call 82 | // HandleCall -> ("reply", message, state) - reply 83 | // ("noreply", _, state) - noreply 84 | // ("stop", reason, _) - normal stop 85 | func (dgs *DebugGenServer) HandleCall(process *gen.ServerProcess, from gen.ServerFrom, message etf.Term) (etf.Term, gen.ServerStatus) { 86 | return etf.Term(""), gen.ServerStatusOK 87 | } 88 | 89 | // HandleInfo serves all another incoming messages (Pid ! message) 90 | // HandleInfo -> ("noreply", state) - noreply 91 | // ("stop", reason) - normal stop 92 | func (dgs *DebugGenServer) HandleInfo(process *gen.ServerProcess, message etf.Term) gen.ServerStatus { 93 | return gen.ServerStatusOK 94 | } 95 | 96 | // Terminate called when process died 97 | func (dgs *DebugGenServer) Terminate(process *gen.ServerProcess, reason string) { 98 | } 99 | -------------------------------------------------------------------------------- /gserver/commonstruct/roleItem.go: -------------------------------------------------------------------------------- 1 | package commonstruct 2 | 3 | import ( 4 | "server/gserver/cfg" 5 | 6 | "github.com/google/uuid" 7 | "go.mongodb.org/mongo-driver/bson/primitive" 8 | ) 9 | 10 | type RoleItemlist struct { 11 | RoleID int32 12 | ItemList map[string]*ItemInfo 13 | 14 | DirtyDataRecord 15 | } 16 | 17 | func (r *RoleItemlist) SetDirtyData(fieldNames ...primitive.E) { 18 | if len(fieldNames) == 0 { 19 | r.DirtyData = true 20 | } else { 21 | for _, v := range fieldNames { 22 | r.DirtyDataList[v.Key] = v 23 | } 24 | } 25 | } 26 | 27 | //道具 28 | type ItemInfo struct { 29 | ItemUUID string //uuid 30 | ID uint32 31 | Name string //名称 32 | Num uint32 //数量 33 | Level uint32 //等级 34 | Star uint32 //星级 35 | Type uint32 //类型 36 | ChildType uint32 //子类型 37 | 38 | UseLocation uint32 //装备位置 39 | Attribute map[uint32]int64 //属性 40 | Lock bool //是否锁定 41 | } 42 | 43 | func CreateItem(id, num uint32) *ItemInfo { 44 | itemcfg := cfg.GetItemCfg(id) 45 | uid, _ := uuid.NewRandom() 46 | return &ItemInfo{ 47 | ItemUUID: uid.String(), 48 | ID: itemcfg.ID, 49 | Num: num, 50 | Name: itemcfg.Name, 51 | Level: itemcfg.Level, 52 | Star: 0, 53 | Type: itemcfg.Type, 54 | ChildType: 0, 55 | UseLocation: 0, 56 | Attribute: map[uint32]int64{}, 57 | Lock: false, 58 | } 59 | } 60 | 61 | //新增道具 62 | func (r *RoleItemlist) AddItem(id, num uint32) { 63 | 64 | } 65 | 66 | //是否存在N个道具 67 | func (r *RoleItemlist) CheckItem(id, num uint32) bool { 68 | itemnum := 0 69 | for _, ii := range r.ItemList { 70 | if ii.ID == id { 71 | itemnum += int(ii.Num) 72 | if itemnum >= int(num) { 73 | return true 74 | } 75 | } 76 | } 77 | return false 78 | } 79 | 80 | func (r *RoleItemlist) CheckItemList(ids, nums []uint32) bool { 81 | for i, v := range ids { 82 | if !r.CheckItem(v, nums[i]) { 83 | return false 84 | } 85 | } 86 | return true 87 | } 88 | 89 | func (r *RoleItemlist) CheckItemUUID(uuid string, num uint32) bool { 90 | if info, ok := r.ItemList[uuid]; ok { 91 | return info.Num >= num 92 | } 93 | return false 94 | } 95 | 96 | func (r *RoleItemlist) CheckItemListUUID(uuids []string, nums []uint32) bool { 97 | for i, uuid := range uuids { 98 | if !r.CheckItemUUID(uuid, nums[i]) { 99 | return false 100 | } 101 | } 102 | return true 103 | } 104 | 105 | //扣除道具 106 | func (r *RoleItemlist) DeleteItem(id, num uint32) bool { 107 | for k, ii := range r.ItemList { 108 | if ii.ID == id { 109 | if ii.Num >= num { 110 | ii.Num -= num 111 | if ii.Num == 0 { 112 | delete(r.ItemList, k) 113 | } 114 | return true 115 | } else { 116 | num -= ii.Num 117 | delete(r.ItemList, k) 118 | } 119 | 120 | if num == 0 { 121 | return true 122 | } 123 | } 124 | } 125 | return false 126 | } 127 | 128 | func (r *RoleItemlist) DeleteItemList(ids, nums []uint32) bool { 129 | if !r.CheckItemList(ids, nums) { 130 | return false 131 | } 132 | 133 | for i, v := range ids { 134 | r.DeleteItem(v, nums[i]) 135 | } 136 | return true 137 | } 138 | 139 | func (r *RoleItemlist) DeleteItemUUID(uuid string, num uint32) bool { 140 | if info, ok := r.ItemList[uuid]; ok { 141 | if info.Num >= num { 142 | info.Num -= num 143 | if info.Num == 0 { 144 | delete(r.ItemList, uuid) 145 | } 146 | return true 147 | } 148 | } 149 | return false 150 | } 151 | 152 | func (r *RoleItemlist) DeleteItemListUUID(uuids []string, nums []uint32) bool { 153 | if !r.CheckItemListUUID(uuids, nums) { 154 | return false 155 | } 156 | 157 | for i, v := range uuids { 158 | r.DeleteItemUUID(v, nums[i]) 159 | } 160 | return true 161 | } 162 | -------------------------------------------------------------------------------- /gserver/cfg/configInit.go: -------------------------------------------------------------------------------- 1 | package cfg 2 | 3 | import ( 4 | 5 | // "github.com/google/wire" 6 | 7 | "encoding/json" 8 | "io/ioutil" 9 | "os" 10 | "reflect" 11 | "sync" 12 | 13 | "github.com/fsnotify/fsnotify" 14 | "github.com/sirupsen/logrus" 15 | "github.com/spf13/viper" 16 | ) 17 | 18 | // InitViperConfig 初始化viper 19 | func InitViperConfig(cfgPath string, cfgType string) *viper.Viper { 20 | logrus.Infof("loanding config [%s] [%s]", cfgPath, cfgType) 21 | 22 | v := viper.New() 23 | v.AddConfigPath(cfgPath) 24 | v.SetConfigType(cfgType) 25 | 26 | cfg := &cfgCollection{} 27 | reflectField(cfg, cfgPath, cfgType, v) 28 | saveCfg(cfg) 29 | return v 30 | } 31 | 32 | func reflectField(structName interface{}, cfgPath, cfgType string, v *viper.Viper) { 33 | t := reflect.ValueOf(structName) 34 | 35 | if t.Kind() == reflect.Ptr { 36 | t = t.Elem() 37 | } 38 | if t.Kind() != reflect.Struct { 39 | logrus.Fatal("Check type error not Struct") 40 | return 41 | } 42 | 43 | fieldNum := t.NumField() 44 | 45 | for i := 0; i < fieldNum; i++ { 46 | fieldname := t.Type().Field(i).Name 47 | typename := t.Field(i).Type().Name() 48 | field := t.Field(i).Interface() 49 | 50 | logrus.Info("load init config =>:", fieldname) 51 | v.SetConfigName(fieldname) 52 | 53 | if err := v.ReadInConfig(); err != nil { 54 | //viper 库无法加载 "[{}]" 格式json 55 | if cfgType == "json" { 56 | jsonFile, e1 := os.Open(cfgPath + "/" + fieldname + "." + cfgType) 57 | defer jsonFile.Close() 58 | if e1 != nil { 59 | logrus.Fatalf("fiel: [%v] err:[%v]", jsonFile, e1) 60 | } 61 | jsda, err := ioutil.ReadAll(jsonFile) 62 | if err != nil { 63 | logrus.Fatalf("ReadAll: [%v] [%v][%v]", err, typename, field) 64 | } 65 | 66 | newdata := reflect.New(reflect.TypeOf(field)).Interface() 67 | if err := json.Unmarshal(jsda, newdata); err != nil { 68 | logrus.Fatalf("unmarshal: [%v] [%v][%v]", err, typename, field) 69 | } 70 | 71 | t.FieldByName(fieldname).Set(reflect.ValueOf(newdata).Elem()) 72 | continue 73 | } 74 | logrus.Fatalf("err: [%v] ", err) 75 | } 76 | 77 | if err := v.UnmarshalExact(&field); err != nil { 78 | logrus.Fatalf("err: [%v] [%v] ", err, field) 79 | } 80 | 81 | t.FieldByName(fieldname).Set(reflect.ValueOf(field)) 82 | } 83 | } 84 | 85 | func WatchConfig(configDir string, run func(in fsnotify.Event)) { 86 | initWG := sync.WaitGroup{} 87 | initWG.Add(1) 88 | go func() { 89 | watcher, err := fsnotify.NewWatcher() 90 | if err != nil { 91 | logrus.Fatal(err) 92 | } 93 | defer watcher.Close() 94 | // we have to watch the entire directory to pick up renames/atomic saves in a cross-platform way 95 | 96 | if err != nil { 97 | logrus.Printf("error: %v\n", err) 98 | initWG.Done() 99 | return 100 | } 101 | 102 | eventsWG := sync.WaitGroup{} 103 | eventsWG.Add(1) 104 | go func() { 105 | for { 106 | select { 107 | case event, ok := <-watcher.Events: 108 | if !ok { // 'Events' channel is closed 109 | eventsWG.Done() 110 | return 111 | } 112 | // we only care about the config file with the following cases: 113 | // 1 - if the config file was modified or created 114 | // 2 - if the real path to the config file changed (eg: k8s ConfigMap replacement) 115 | const writeOrCreateMask = fsnotify.Write | fsnotify.Create 116 | if event.Op&writeOrCreateMask != 0 { 117 | if run != nil { 118 | run(event) 119 | } 120 | } 121 | 122 | case err, ok := <-watcher.Errors: 123 | if ok { // 'Errors' channel is not closed 124 | logrus.Printf("watcher error: %v\n", err) 125 | } 126 | eventsWG.Done() 127 | return 128 | } 129 | } 130 | }() 131 | watcher.Add(configDir) 132 | initWG.Done() // done initializing the watch in this go routine, so the parent routine can move on... 133 | eventsWG.Wait() // now, wait for event loop to end in this go-routine... 134 | }() 135 | initWG.Wait() // make sure that the go routine above fully ended before returning 136 | } 137 | -------------------------------------------------------------------------------- /gserver/genServer/gateGenServer.go: -------------------------------------------------------------------------------- 1 | package genServer 2 | 3 | import ( 4 | "runtime" 5 | "time" 6 | 7 | "github.com/ergo-services/ergo/etf" 8 | "github.com/ergo-services/ergo/gen" 9 | "github.com/sirupsen/logrus" 10 | ) 11 | 12 | //接收处理socket 发送过来的信息 13 | // 处理玩家独立无交互的游戏逻辑 14 | // 在socket中断后 此进程会保留一段时间以便于重新建立连接 15 | 16 | type GateGenServer struct { 17 | gen.Server 18 | sendChan chan []byte 19 | clientHander GateGenHanderInterface 20 | } 21 | 22 | func (gateGS *GateGenServer) Init(process *gen.ServerProcess, args ...etf.Term) error { 23 | logrus.Infof("Init (%v): args %v ", process.Name(), args) 24 | gateGS.sendChan = args[0].(chan []byte) 25 | gateGS.clientHander = args[1].(GateGenHanderInterface) 26 | gateGS.clientHander.InitHander(process, gateGS.sendChan) 27 | process.SendAfter(process.Self(), etf.Atom("loop"), time.Second) 28 | return nil 29 | } 30 | 31 | func (gateGS *GateGenServer) HandleCast(process *gen.ServerProcess, message etf.Term) gen.ServerStatus { 32 | //logrus.Infof("gateGen HandleCast (%v): %v", process.Name(), message) 33 | defer func() { 34 | if err := recover(); err != nil { 35 | pc, fn, line, _ := runtime.Caller(5) 36 | logrus.Errorf("process:[%v] funcname:[%v] fn:[%v] line:[%v]", process.Name(), runtime.FuncForPC(pc).Name(), fn, line) 37 | } 38 | }() 39 | 40 | switch info := message.(type) { 41 | case etf.Atom: 42 | switch info { 43 | case "SocketStop": 44 | return gen.ServerStatusStopWithReason("stop normal") 45 | case "timeloop": 46 | logrus.Debug("time loop") 47 | } 48 | case etf.Tuple: 49 | module := info[0].(int32) 50 | method := info[1].(int32) 51 | buf := info[2].([]byte) 52 | gateGS.clientHander.MsgHander(module, method, buf) 53 | //gateGS.sendChan <- []byte("send msg test") 54 | case []byte: 55 | logrus.Debug("[]byte:", info) 56 | } 57 | return gateGS.clientHander.GenServerStatus() 58 | } 59 | 60 | func (gateGS *GateGenServer) HandleCall(process *gen.ServerProcess, from gen.ServerFrom, message etf.Term) (etf.Term, gen.ServerStatus) { 61 | logrus.Infof("HandleCall (%v): %v, From: %v", process.Name(), message, from) 62 | 63 | //gateGS.clientHander.HandleCall(message) 64 | reply := etf.Atom("ignore") 65 | return reply, gateGS.clientHander.GenServerStatus() 66 | } 67 | 68 | func (gateGS *GateGenServer) HandleInfo(process *gen.ServerProcess, message etf.Term) gen.ServerStatus { 69 | switch info := message.(type) { 70 | case etf.Atom: 71 | switch info { 72 | case "Extrusionline": //挤下线 73 | gateGS.clientHander.Terminate("Extrusionline") 74 | case "loop": 75 | after := gateGS.clientHander.LoopHander() 76 | if after < time.Millisecond { 77 | after = time.Second 78 | } 79 | process.SendAfter(process.Self(), etf.Atom("loop"), after) 80 | return gateGS.clientHander.GenServerStatus() 81 | case "stop": 82 | return gen.ServerStatusStop 83 | } 84 | default: 85 | gateGS.clientHander.HandleInfo(message) 86 | } 87 | 88 | return gateGS.clientHander.GenServerStatus() 89 | } 90 | 91 | // Terminate called when process died 92 | func (gateGS *GateGenServer) Terminate(process *gen.ServerProcess, reason string) { 93 | logrus.Infof("Terminate (%v): %v", process.Name(), reason) 94 | gateGS.clientHander.Terminate(reason) 95 | } 96 | 97 | // // //Send 发送消息 98 | // func (gateGS *GateGenServer) Send(module int32, method int32, pb proto.Message) { 99 | // //logrus.Debugf("client send msg [%v] [%v] [%v]", module, method, pb) 100 | // data, err := proto.Marshal(pb) 101 | // if err != nil { 102 | // logrus.Errorf("proto encode error[%v] [%v][%v] [%v]", err.Error(), module, method, pb) 103 | // return 104 | // } 105 | // // msginfo := &common.NetworkMsg{} 106 | // // msginfo.Module = module 107 | // // msginfo.Method = method 108 | // // msginfo.MsgBytes = data 109 | // // msgdata, err := proto.Marshal(msginfo) 110 | // // if err != nil { 111 | // // logrus.Errorf("msg encode error[%s]\n", err.Error()) 112 | // // } 113 | // // gateGS.sendChan <- msgdata 114 | 115 | // mldulebuf := tools.IntToBytes(module, 2) 116 | // methodbuf := tools.IntToBytes(method, 2) 117 | // gateGS.sendChan <- tools.BytesCombine(mldulebuf, methodbuf, data) 118 | 119 | // } 120 | -------------------------------------------------------------------------------- /gserver/commonstruct/roleData.go: -------------------------------------------------------------------------------- 1 | package commonstruct 2 | 3 | import ( 4 | "server/db" 5 | "sync" 6 | 7 | "github.com/sirupsen/logrus" 8 | "go.mongodb.org/mongo-driver/bson" 9 | "go.mongodb.org/mongo-driver/bson/primitive" 10 | ) 11 | 12 | var ( 13 | roleDataMap sync.Map 14 | ) 15 | 16 | type RoleData struct { 17 | Acconut *AccountInfo //账号信息 18 | RoleBase RoleBaseInfo //角色基础数据 19 | RoleItems RoleItemlist //角色道具 20 | //宗门 21 | //好友 22 | } 23 | 24 | //数据更新标注 25 | type DirtyDataRecord struct { 26 | TableName string 27 | DirtyData bool 28 | DirtyDataList map[string]primitive.E 29 | } 30 | 31 | func (r *DirtyDataRecord) SetDirtyData(fieldNames ...primitive.E) { 32 | if len(fieldNames) == 0 { 33 | r.DirtyData = true 34 | } else { 35 | for _, v := range fieldNames { 36 | r.DirtyDataList[v.Key] = v 37 | } 38 | } 39 | } 40 | 41 | func GetRoleAllData(roleid int32) *RoleData { 42 | data, ok := roleDataMap.Load(roleid) 43 | roledata := &RoleData{} 44 | if ok { 45 | info := data.(RoleData) 46 | roledata = &info 47 | } 48 | 49 | if roledata.RoleBase.RoleID == 0 { 50 | rolebase := &RoleBaseInfo{} 51 | if err := db.FindOneBson(db.RoleBaseTable, rolebase, bson.D{primitive.E{Key: "roleid", Value: roleid}}); err == nil { 52 | roledata.RoleBase = *rolebase 53 | } 54 | } 55 | 56 | if roledata.RoleItems.RoleID == 0 { 57 | roleitem := &RoleItemlist{} 58 | if err := db.FindOneBson(db.RoleItemsTable, roleitem, bson.D{primitive.E{Key: "roleid", Value: roleid}}); err == nil { 59 | roledata.RoleItems = *roleitem 60 | } 61 | } 62 | 63 | return roledata 64 | } 65 | 66 | //缓存数据 67 | func StoreRoleData(roledata *RoleData) { 68 | if roledata.RoleBase.RoleID != 0 { 69 | roleDataMap.Store(roledata.RoleBase.RoleID, *roledata) 70 | } 71 | } 72 | 73 | func RangeAllData(fc func(*RoleData) bool) { 74 | roleDataMap.Range(func(key, value interface{}) bool { 75 | data := value.(RoleData) 76 | if fc(&data) { 77 | logrus.Debug("db 节点定时更新数据: 角色id:[%v]", data.RoleBase.RoleID) 78 | roleDataMap.Store(key, data) 79 | } 80 | return true 81 | }) 82 | } 83 | 84 | func saveDirtyData(RoleID int32, data *DirtyDataRecord, replacement interface{}) (updateDB bool) { 85 | findfield := bson.D{primitive.E{Key: "roleid", Value: RoleID}} 86 | if len(data.DirtyDataList) != 0 { 87 | Upfield := bson.D{} 88 | for _, primitiveE := range data.DirtyDataList { 89 | Upfield = append(Upfield, primitiveE) 90 | } 91 | db.Update(data.TableName, findfield, bson.D{primitive.E{Key: "$set", Value: Upfield}}) 92 | updateDB = true 93 | data.DirtyDataList = make(map[string]primitive.E) 94 | } else if data.DirtyData { 95 | if _, err := db.ReplaceOne(data.TableName, findfield, replacement); err == nil { 96 | data.DirtyData = false 97 | updateDB = true 98 | data.DirtyDataList = make(map[string]primitive.E) 99 | } 100 | } 101 | return false 102 | } 103 | 104 | //保存更新到mongo 105 | func SaveRoleData(rd *RoleData) (updateDB bool) { 106 | if ok := saveDirtyData(rd.RoleBase.RoleID, &rd.RoleBase.DirtyDataRecord, &rd.RoleBase); ok { 107 | updateDB = ok 108 | } 109 | 110 | if ok := saveDirtyData(rd.RoleBase.RoleID, &rd.RoleItems.DirtyDataRecord, &rd.RoleItems); ok { 111 | updateDB = ok 112 | } 113 | //findfield := bson.D{primitive.E{Key: "roleid", Value: rd.RoleBase.RoleID}} 114 | // if len(rd.RoleBase.DirtyDataList) != 0 { 115 | // Upfield := bson.D{} 116 | // for _, primitiveE := range rd.RoleBase.DirtyDataList { 117 | // Upfield = append(Upfield, primitiveE) 118 | // } 119 | // db.Update(db.RoleBaseTable, findfield, bson.D{primitive.E{Key: "$set", Value: Upfield}}) 120 | // updateDB = true 121 | // } else if rd.RoleBase.DirtyData { 122 | // if _, err := db.ReplaceOne(db.RoleBaseTable, findfield, rd.RoleBase); err == nil { 123 | // rd.RoleBase.DirtyData = false 124 | // updateDB = true 125 | // } 126 | // } 127 | // rd.RoleBase.DirtyDataList = make(map[string]primitive.E) 128 | 129 | // if len(rd.RoleItems.DirtyDataList) != 0 { 130 | // Upfield := bson.D{} 131 | // for _, primitiveE := range rd.RoleItems.DirtyDataList { 132 | // Upfield = append(Upfield, primitiveE) 133 | // } 134 | // db.Update(db.RoleItemsTable, findfield, bson.D{primitive.E{Key: "$set", Value: Upfield}}) 135 | // updateDB = true 136 | // } else if rd.RoleItems.DirtyData { 137 | // if _, err := db.ReplaceOne(db.RoleItemsTable, findfield, rd.RoleItems); err == nil { 138 | // rd.RoleItems.DirtyData = false 139 | // updateDB = true 140 | // } 141 | // } 142 | // rd.RoleItems.DirtyDataList = make(map[string]primitive.E) 143 | 144 | return updateDB 145 | } 146 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module server 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/c-bata/go-prompt v0.2.6 7 | github.com/ergo-services/ergo v1.999.210 8 | github.com/facebookgo/atomicfile v0.0.0-20151019160806-2de1f203e7d5 9 | github.com/facebookgo/pidfile v0.0.0-20150612191647-f242e2999868 10 | github.com/fsnotify/fsnotify v1.5.4 11 | github.com/gin-gonic/gin v1.7.7 12 | github.com/go-basic/uuid v1.0.0 13 | github.com/gomodule/redigo v1.8.8 14 | github.com/google/uuid v1.3.0 15 | github.com/gorilla/websocket v1.5.0 16 | github.com/jolestar/go-commons-pool/v2 v2.1.2 17 | github.com/json-iterator/go v1.1.12 18 | github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible 19 | github.com/pyroscope-io/pyroscope v0.15.4 20 | github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5 21 | github.com/robfig/cron/v3 v3.0.1 22 | github.com/sirupsen/logrus v1.8.1 23 | github.com/spf13/cobra v1.4.0 24 | github.com/spf13/viper v1.11.0 25 | github.com/stretchr/testify v1.7.1 26 | github.com/xtaci/kcp-go v5.4.20+incompatible 27 | github.com/xuri/excelize/v2 v2.6.0 28 | go.mongodb.org/mongo-driver v1.9.1 29 | go.uber.org/atomic v1.9.0 30 | golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f 31 | google.golang.org/protobuf v1.28.0 32 | ) 33 | 34 | require ( 35 | github.com/Microsoft/go-winio v0.5.2 // indirect 36 | github.com/cespare/xxhash/v2 v2.1.2 // indirect 37 | github.com/davecgh/go-spew v1.1.1 // indirect 38 | github.com/gin-contrib/sse v0.1.0 // indirect 39 | github.com/go-ole/go-ole v1.2.6 // indirect 40 | github.com/go-playground/locales v0.14.0 // indirect 41 | github.com/go-playground/universal-translator v0.18.0 // indirect 42 | github.com/go-playground/validator/v10 v10.11.0 // indirect 43 | github.com/go-stack/stack v1.8.1 // indirect 44 | github.com/golang/protobuf v1.5.2 // indirect 45 | github.com/golang/snappy v0.0.4 // indirect 46 | github.com/hashicorp/errwrap v1.1.0 // indirect 47 | github.com/hashicorp/go-multierror v1.1.1 // indirect 48 | github.com/hashicorp/hcl v1.0.0 // indirect 49 | github.com/inconshreveable/mousetrap v1.0.0 // indirect 50 | github.com/jonboulle/clockwork v0.3.0 // indirect 51 | github.com/klauspost/compress v1.15.2 // indirect 52 | github.com/klauspost/cpuid/v2 v2.0.12 // indirect 53 | github.com/klauspost/reedsolomon v1.9.16 // indirect 54 | github.com/leodido/go-urn v1.2.1 // indirect 55 | github.com/lestrrat-go/strftime v1.0.6 // indirect 56 | github.com/magiconair/properties v1.8.6 // indirect 57 | github.com/mattn/go-colorable v0.1.12 // indirect 58 | github.com/mattn/go-isatty v0.0.14 // indirect 59 | github.com/mattn/go-runewidth v0.0.13 // indirect 60 | github.com/mattn/go-tty v0.0.4 // indirect 61 | github.com/mitchellh/go-ps v1.0.0 // indirect 62 | github.com/mitchellh/mapstructure v1.5.0 // indirect 63 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 64 | github.com/modern-go/reflect2 v1.0.2 // indirect 65 | github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect 66 | github.com/pelletier/go-toml v1.9.5 // indirect 67 | github.com/pelletier/go-toml/v2 v2.0.0 // indirect 68 | github.com/pkg/errors v0.9.1 // indirect 69 | github.com/pkg/term v1.2.0-beta.2 // indirect 70 | github.com/pmezard/go-difflib v1.0.0 // indirect 71 | github.com/pyroscope-io/dotnetdiag v1.2.1 // indirect 72 | github.com/richardlehane/mscfb v1.0.4 // indirect 73 | github.com/richardlehane/msoleps v1.0.3 // indirect 74 | github.com/rivo/uniseg v0.2.0 // indirect 75 | github.com/shirou/gopsutil v3.21.11+incompatible // indirect 76 | github.com/spf13/afero v1.8.2 // indirect 77 | github.com/spf13/cast v1.4.1 // indirect 78 | github.com/spf13/jwalterweatherman v1.1.0 // indirect 79 | github.com/spf13/pflag v1.0.5 // indirect 80 | github.com/subosito/gotenv v1.2.0 // indirect 81 | github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 // indirect 82 | github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b // indirect 83 | github.com/tjfoc/gmsm v1.4.1 // indirect 84 | github.com/tklauser/go-sysconf v0.3.10 // indirect 85 | github.com/tklauser/numcpus v0.4.0 // indirect 86 | github.com/ugorji/go/codec v1.2.7 // indirect 87 | github.com/valyala/bytebufferpool v1.0.0 // indirect 88 | github.com/xdg-go/pbkdf2 v1.0.0 // indirect 89 | github.com/xdg-go/scram v1.1.1 // indirect 90 | github.com/xdg-go/stringprep v1.0.3 // indirect 91 | github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37 // indirect 92 | github.com/xuri/efp v0.0.0-20220407160117-ad0f7a785be8 // indirect 93 | github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22 // indirect 94 | github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a // indirect 95 | github.com/yusufpapurcu/wmi v1.2.2 // indirect 96 | golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 // indirect 97 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect 98 | golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 // indirect 99 | golang.org/x/text v0.3.7 // indirect 100 | gopkg.in/ini.v1 v1.66.4 // indirect 101 | gopkg.in/yaml.v2 v2.4.0 // indirect 102 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect 103 | ) 104 | -------------------------------------------------------------------------------- /proto/common/common.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // versions: 3 | // protoc-gen-go v1.27.1 4 | // protoc v3.6.1 5 | // source: proto/common.proto 6 | 7 | package common 8 | 9 | import ( 10 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 11 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 12 | reflect "reflect" 13 | sync "sync" 14 | ) 15 | 16 | const ( 17 | // Verify that this generated code is sufficiently up-to-date. 18 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 19 | // Verify that runtime/protoimpl is sufficiently up-to-date. 20 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 21 | ) 22 | 23 | type NetworkMsg struct { 24 | state protoimpl.MessageState 25 | sizeCache protoimpl.SizeCache 26 | unknownFields protoimpl.UnknownFields 27 | 28 | Module int32 `protobuf:"varint,1,opt,name=Module,proto3" json:"Module,omitempty"` 29 | Method int32 `protobuf:"varint,2,opt,name=Method,proto3" json:"Method,omitempty"` 30 | MsgBytes []byte `protobuf:"bytes,3,opt,name=MsgBytes,proto3" json:"MsgBytes,omitempty"` 31 | } 32 | 33 | func (x *NetworkMsg) Reset() { 34 | *x = NetworkMsg{} 35 | if protoimpl.UnsafeEnabled { 36 | mi := &file_proto_common_proto_msgTypes[0] 37 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 38 | ms.StoreMessageInfo(mi) 39 | } 40 | } 41 | 42 | func (x *NetworkMsg) String() string { 43 | return protoimpl.X.MessageStringOf(x) 44 | } 45 | 46 | func (*NetworkMsg) ProtoMessage() {} 47 | 48 | func (x *NetworkMsg) ProtoReflect() protoreflect.Message { 49 | mi := &file_proto_common_proto_msgTypes[0] 50 | if protoimpl.UnsafeEnabled && x != nil { 51 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 52 | if ms.LoadMessageInfo() == nil { 53 | ms.StoreMessageInfo(mi) 54 | } 55 | return ms 56 | } 57 | return mi.MessageOf(x) 58 | } 59 | 60 | // Deprecated: Use NetworkMsg.ProtoReflect.Descriptor instead. 61 | func (*NetworkMsg) Descriptor() ([]byte, []int) { 62 | return file_proto_common_proto_rawDescGZIP(), []int{0} 63 | } 64 | 65 | func (x *NetworkMsg) GetModule() int32 { 66 | if x != nil { 67 | return x.Module 68 | } 69 | return 0 70 | } 71 | 72 | func (x *NetworkMsg) GetMethod() int32 { 73 | if x != nil { 74 | return x.Method 75 | } 76 | return 0 77 | } 78 | 79 | func (x *NetworkMsg) GetMsgBytes() []byte { 80 | if x != nil { 81 | return x.MsgBytes 82 | } 83 | return nil 84 | } 85 | 86 | var File_proto_common_proto protoreflect.FileDescriptor 87 | 88 | var file_proto_common_proto_rawDesc = []byte{ 89 | 0x0a, 0x12, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 90 | 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x22, 0x58, 0x0a, 0x0a, 91 | 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x4d, 0x73, 0x67, 0x12, 0x16, 0x0a, 0x06, 0x4d, 0x6f, 92 | 0x64, 0x75, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x4d, 0x6f, 0x64, 0x75, 93 | 0x6c, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x02, 0x20, 0x01, 94 | 0x28, 0x05, 0x52, 0x06, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x4d, 0x73, 95 | 0x67, 0x42, 0x79, 0x74, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x4d, 0x73, 96 | 0x67, 0x42, 0x79, 0x74, 0x65, 0x73, 0x42, 0x0e, 0x5a, 0x0c, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 97 | 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 98 | } 99 | 100 | var ( 101 | file_proto_common_proto_rawDescOnce sync.Once 102 | file_proto_common_proto_rawDescData = file_proto_common_proto_rawDesc 103 | ) 104 | 105 | func file_proto_common_proto_rawDescGZIP() []byte { 106 | file_proto_common_proto_rawDescOnce.Do(func() { 107 | file_proto_common_proto_rawDescData = protoimpl.X.CompressGZIP(file_proto_common_proto_rawDescData) 108 | }) 109 | return file_proto_common_proto_rawDescData 110 | } 111 | 112 | var file_proto_common_proto_msgTypes = make([]protoimpl.MessageInfo, 1) 113 | var file_proto_common_proto_goTypes = []interface{}{ 114 | (*NetworkMsg)(nil), // 0: common.NetworkMsg 115 | } 116 | var file_proto_common_proto_depIdxs = []int32{ 117 | 0, // [0:0] is the sub-list for method output_type 118 | 0, // [0:0] is the sub-list for method input_type 119 | 0, // [0:0] is the sub-list for extension type_name 120 | 0, // [0:0] is the sub-list for extension extendee 121 | 0, // [0:0] is the sub-list for field type_name 122 | } 123 | 124 | func init() { file_proto_common_proto_init() } 125 | func file_proto_common_proto_init() { 126 | if File_proto_common_proto != nil { 127 | return 128 | } 129 | if !protoimpl.UnsafeEnabled { 130 | file_proto_common_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { 131 | switch v := v.(*NetworkMsg); i { 132 | case 0: 133 | return &v.state 134 | case 1: 135 | return &v.sizeCache 136 | case 2: 137 | return &v.unknownFields 138 | default: 139 | return nil 140 | } 141 | } 142 | } 143 | type x struct{} 144 | out := protoimpl.TypeBuilder{ 145 | File: protoimpl.DescBuilder{ 146 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 147 | RawDescriptor: file_proto_common_proto_rawDesc, 148 | NumEnums: 0, 149 | NumMessages: 1, 150 | NumExtensions: 0, 151 | NumServices: 0, 152 | }, 153 | GoTypes: file_proto_common_proto_goTypes, 154 | DependencyIndexes: file_proto_common_proto_depIdxs, 155 | MessageInfos: file_proto_common_proto_msgTypes, 156 | }.Build() 157 | File_proto_common_proto = out.File 158 | file_proto_common_proto_rawDesc = nil 159 | file_proto_common_proto_goTypes = nil 160 | file_proto_common_proto_depIdxs = nil 161 | } 162 | -------------------------------------------------------------------------------- /tools/tools.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import ( 4 | "bytes" 5 | crand "crypto/rand" //加密安全的随机库 6 | "encoding/binary" 7 | "fmt" 8 | "math" 9 | "math/big" 10 | "math/rand" //伪随机库 11 | "os" 12 | "path/filepath" 13 | "runtime" 14 | "strconv" 15 | "strings" 16 | "time" 17 | "unsafe" 18 | 19 | "github.com/robfig/cron/v3" 20 | ) 21 | 22 | const ( 23 | //DateTimeFormat 日期时间格式化 24 | DateTimeFormat = "2006-01-02 15:04:05" 25 | //DateFormat 日期式化 26 | DateFormat = "2006-01-02" 27 | //TimeFormat 时间格式化 28 | TimeFormat = "15:04:05" 29 | ) 30 | 31 | //IsLittleEndian 判断大小端 32 | func IsLittleEndian() bool { 33 | var i int32 = 0x01020304 34 | u := unsafe.Pointer(&i) 35 | pb := (*byte)(u) 36 | b := *pb 37 | return (b == 0x04) 38 | } 39 | 40 | //GoID go 协程id 41 | func GoID() uint64 { 42 | b := make([]byte, 64) 43 | b = b[:runtime.Stack(b, false)] 44 | b = bytes.TrimPrefix(b, []byte("goroutine ")) 45 | b = b[:bytes.IndexByte(b, ' ')] 46 | n, _ := strconv.ParseUint(string(b), 10, 64) 47 | return n 48 | } 49 | 50 | //BytesToInt []byte to int 51 | func BytesToInt(bys []byte) int { 52 | bytebuff := bytes.NewBuffer(bys) 53 | var data int64 54 | binary.Read(bytebuff, binary.LittleEndian, &data) 55 | return int(data) 56 | } 57 | 58 | // string转成int: 59 | // int, err := strconv.Atoi(string) 60 | // string转成int64: 61 | // int64, err := strconv.ParseInt(string, 10, 64) 62 | // int转成string: 63 | // string := strconv.Itoa(int) 64 | // int64转成string: 65 | // string := strconv.FormatInt(int64,10) 66 | 67 | //StringReplace 去除空格和换行 68 | func StringReplace(str string) string { 69 | // 去除空格 70 | str = strings.Replace(str, " ", "", -1) 71 | // 去除换行符 72 | str = strings.Replace(str, "\n", "", -1) 73 | return str 74 | } 75 | 76 | //Round 四舍五入 77 | func Round(x float64) int { 78 | return int(math.Floor(x + 0/5)) 79 | } 80 | 81 | //DelList 删除 82 | func DelList(list []int32, key int32) []int32 { 83 | for index, v := range list { 84 | if v == key { 85 | return append(list[:index], list[index+1:]...) 86 | } 87 | } 88 | return list 89 | } 90 | 91 | //Random 100 随机 92 | func Random(randkey float64) bool { 93 | rand.Seed(time.Now().Unix()) 94 | num := rand.Intn(100) 95 | return num < int(randkey*100) 96 | } 97 | 98 | func RandInt64ForRange(min, max int64) int64 { 99 | if min >= max { 100 | return min 101 | } 102 | maxBigInt := big.NewInt(max + 1 - min) 103 | i, err := crand.Int(crand.Reader, maxBigInt) 104 | if err != nil { 105 | return min 106 | } 107 | i64 := i.Int64() 108 | return i64 + min 109 | } 110 | 111 | //权重随机 112 | func RandWeight(values []int64) int64 { 113 | var total int64 114 | for _, v := range values { 115 | total += v 116 | } 117 | ranNum := RandInt64ForRange(0, total) 118 | for i, v := range values { 119 | ranNum -= v 120 | if ranNum <= 0 { 121 | return int64(i) 122 | } 123 | } 124 | return 0 125 | } 126 | 127 | //对比时间范围 startStr< difftime < endStr 128 | func DiffCronStrNowTime(difftime time.Time, startStr, endStr string) bool { 129 | parser := cron.NewParser(cron.Second | cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow | cron.Descriptor) 130 | startTime, serr := parser.Parse(startStr) 131 | endTime, eerr := parser.Parse(endStr) 132 | if serr != nil || eerr != nil { 133 | fmt.Println("不合法的 cron 格式 ", startStr, endStr) 134 | return false 135 | } 136 | //logrus.Debug("活动开放时间:", startTime.Next(time.Now()), endTime.Next(time.Now())) 137 | 138 | return startTime.Next(difftime).Unix() > endTime.Next(difftime).Unix() 139 | } 140 | 141 | //每日凌晨时间 142 | func GetToDayStartUnix() int64 { 143 | timeStr := time.Now().Format(DateFormat) 144 | t2, _ := time.ParseInLocation(DateFormat, timeStr, time.Local) 145 | return t2.Unix() 146 | } 147 | 148 | //这个时间是否是今天 149 | func IsDay(old_time int64) bool { 150 | key := GetToDayStartUnix() 151 | return key+60*60*24 > old_time && old_time > key 152 | } 153 | 154 | //相对路径 转换 155 | func AbsPathify(inPath string) string { 156 | if inPath == "$HOME" || strings.HasPrefix(inPath, "$HOME"+string(os.PathSeparator)) { 157 | inPath = userHomeDir() + inPath[5:] 158 | } 159 | 160 | if strings.HasPrefix(inPath, "$") { 161 | end := strings.Index(inPath, string(os.PathSeparator)) 162 | 163 | var value, suffix string 164 | if end == -1 { 165 | value = os.Getenv(inPath[1:]) 166 | } else { 167 | value = os.Getenv(inPath[1:end]) 168 | suffix = inPath[end:] 169 | } 170 | 171 | inPath = value + suffix 172 | } 173 | 174 | if filepath.IsAbs(inPath) { 175 | return filepath.Clean(inPath) 176 | } 177 | 178 | p, err := filepath.Abs(inPath) 179 | if err == nil { 180 | return filepath.Clean(p) 181 | } 182 | return "" 183 | } 184 | 185 | func userHomeDir() string { 186 | if runtime.GOOS == "windows" { 187 | home := os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH") 188 | if home == "" { 189 | home = os.Getenv("USERPROFILE") 190 | } 191 | return home 192 | } 193 | return os.Getenv("HOME") 194 | } 195 | 196 | //IntToBytes int 转换为[]byte 197 | func IntToBytes(i int32, packet int32) []byte { 198 | var buf = make([]byte, 2) 199 | if packet == 2 { 200 | binary.BigEndian.PutUint16(buf, uint16(i)) 201 | } else { 202 | binary.BigEndian.PutUint32(buf, uint32(i)) 203 | } 204 | return buf 205 | } 206 | 207 | //BytesCombine 多个[]byte数组合并成一个[]byte 208 | func BytesCombine(pBytes ...[]byte) []byte { 209 | len := len(pBytes) 210 | s := make([][]byte, len) 211 | for index := 0; index < len; index++ { 212 | s[index] = pBytes[index] 213 | } 214 | sep := []byte("") 215 | return bytes.Join(s, sep) 216 | } 217 | -------------------------------------------------------------------------------- /db/mongo_test.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "testing" 7 | 8 | "github.com/sirupsen/logrus" 9 | "github.com/stretchr/testify/assert" 10 | 11 | "go.mongodb.org/mongo-driver/bson" 12 | ) 13 | 14 | func TestConnectPool(t *testing.T) { 15 | StartMongodb("gamedemo", "mongodb://admin:123456@localhost:27017") 16 | b, e := MongodbPing() 17 | fmt.Println(b, e) 18 | } 19 | 20 | func TestInsertOne(t *testing.T) { 21 | InsertOne("cron_log", Testdata{ 22 | Name: "Ash", 23 | Age: 17, 24 | }) 25 | 26 | data := &Testdata{Name: "wq", Age: 18} 27 | InsertOne("cron_log", &data) 28 | logrus.Info("TestInsertOne") 29 | } 30 | 31 | func TestFindFieldMax(t *testing.T) { 32 | var obj Testdata 33 | FindFieldMax("cron_log", "age", &obj, bson.D{{}}) 34 | logrus.Info("TestFindFieldMax:", obj.Age) 35 | assert.Equal(t, obj.Age, int32(18)) 36 | } 37 | 38 | func TestFindBson(t *testing.T) { 39 | var obj Testdata 40 | filter := bson.D{{"name", "wq"}, {"age", 18}} 41 | FindOneBson("cron_log", &obj, filter) 42 | logrus.Info("TestFindObject", obj) 43 | 44 | } 45 | 46 | func TestUpdate(t *testing.T) { 47 | filter := bson.D{{"name", "Ash"}} 48 | // $inc 加减 49 | updatefilter := bson.D{{"$set", bson.D{{"age", 18}}}} 50 | Update("cron_log", filter, updatefilter) 51 | 52 | } 53 | 54 | func TestFindOne(t *testing.T) { 55 | var obj Testdata 56 | list := make(map[string]interface{}) 57 | list["name"] = "Ash" 58 | //list["age"] = 18 59 | FindOneBson("cron_log", &obj, list) 60 | logrus.Info("TestFindObject", obj) 61 | } 62 | 63 | func TestFind(t *testing.T) { 64 | filter := bson.D{{"age", 18}} 65 | 66 | var results []*Testdata 67 | cur, err := FindBson("cron_log", filter) 68 | if err != nil { 69 | logrus.Fatal(err) 70 | } 71 | for cur.Next(context.TODO()) { 72 | var elem Testdata 73 | err := cur.Decode(&elem) 74 | if err != nil { 75 | logrus.Fatal(err) 76 | } 77 | results = append(results, &elem) 78 | } 79 | if err := cur.Err(); err != nil { 80 | logrus.Fatal(err) 81 | } 82 | // Close the cursor once finished 83 | cur.Close(context.TODO()) 84 | 85 | logrus.Info(results) 86 | } 87 | 88 | func TestDelete(t *testing.T) { 89 | num := Delete("cron_log", "name", "Ash") 90 | logrus.Info("TestDelete num:", num) 91 | num = Delete("cron_log", "name", "wq") 92 | logrus.Info("TestDelete num:", num) 93 | } 94 | 95 | type Testdata struct { 96 | Name string 97 | Age int32 98 | } 99 | 100 | // func mongodb() { 101 | // type trainer struct { 102 | // Name string 103 | // Age int 104 | // City string 105 | // } 106 | 107 | // var ( 108 | // client *mongo.Client 109 | // err error 110 | // ) 111 | 112 | // // 建立mongodb连接 113 | // clientOptions := options.Client().ApplyURI("mongodb://localhost:27017") 114 | // if client, err = mongo.Connect(context.TODO(), clientOptions); err != nil { 115 | // logrus.Error(err) 116 | // return 117 | // } 118 | 119 | // // 检查连接 120 | // err = client.Ping(context.TODO(), nil) 121 | // if err != nil { 122 | // logrus.Fatal(err) 123 | // } 124 | // logrus.Info("Connected to MongoDB!") 125 | 126 | // // 2, 选择数据库my_db 127 | // database := client.Database("gamedemo") 128 | 129 | // // 3, 选择表my_collection 130 | // collection := database.Collection("cron_log") 131 | // // 4, 插入记录(bson) 132 | // ash := trainer{"Ash", 10, "Pallet Town"} 133 | // misty := trainer{"Misty", 10, "Cerulean City"} 134 | // brock := trainer{"Brock", 15, "Pewter City"} 135 | // insertResult, err := collection.InsertOne(context.TODO(), ash) 136 | // if err != nil { 137 | // logrus.Fatal(err) 138 | // } 139 | // logrus.Info("Inserted a single document: ", insertResult) 140 | 141 | // //插入列表数据 142 | // trainers := []interface{}{misty, brock} 143 | // insertManyResult, err := collection.InsertMany(context.TODO(), trainers) 144 | // if err != nil { 145 | // logrus.Fatal(err) 146 | // } 147 | // logrus.Info("Inserted multiple documents: ", insertManyResult.InsertedIDs) 148 | 149 | // // 更新 150 | // filter := bson.D{primitive.E{Key: "name", Value: "Ash"}} 151 | // update := bson.D{primitive.E{Key: "$inc", Value: bson.D{primitive.E{Key: "age", Value: 1}}}} 152 | // updateResult, err := collection.UpdateOne(context.TODO(), filter, update) 153 | // if err != nil { 154 | // logrus.Fatal(err) 155 | // } 156 | // logrus.Infof("Matched %v documents and updated %v documents.\n", updateResult.MatchedCount, updateResult.ModifiedCount) 157 | 158 | // //查找 159 | // filter2 := bson.D{primitive.E{Key: "name", Value: "Ash"}} 160 | // var result trainer 161 | // err = collection.FindOne(context.TODO(), filter2).Decode(&result) 162 | // if err != nil { 163 | // logrus.Fatal(err) 164 | // } 165 | // logrus.Infof("Found a single document: %+v\n", result) 166 | 167 | // //删除所有 168 | // deleteResult, err := collection.DeleteMany(context.TODO(), bson.D{{}}) 169 | // if err != nil { 170 | // logrus.Fatal(err) 171 | // } 172 | // logrus.Infof("Deleted %v documents in the trainers collection\n", deleteResult.DeletedCount) 173 | // } 174 | 175 | // func objectPool() { 176 | // factory := pool.NewPooledObjectFactorySimple( 177 | // func(context.Context) (interface{}, error) { 178 | // clientOptions := options.Client().ApplyURI("mongodb://localhost:27017") 179 | // client, err := mongo.Connect(context.TODO(), clientOptions) 180 | // if err != nil { 181 | // logrus.Error(err) 182 | // } 183 | // return client, nil 184 | // }) 185 | 186 | // ctx := context.Background() 187 | // p := pool.NewObjectPoolWithDefaultConfig(ctx, factory) 188 | 189 | // obj, err := p.BorrowObject(ctx) 190 | // if err != nil { 191 | // logrus.Error(err) 192 | // } 193 | 194 | // client := obj.(*mongo.Client) 195 | // err = client.Ping(context.TODO(), nil) 196 | // fmt.Println(err) 197 | 198 | // err = p.ReturnObject(ctx, obj) 199 | // if err != nil { 200 | // logrus.Error(err) 201 | // } 202 | // } 203 | -------------------------------------------------------------------------------- /cmd/clientconn.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2022 NAME HERE 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package cmd 17 | 18 | import ( 19 | "context" 20 | "encoding/binary" 21 | "fmt" 22 | "net" 23 | "server/network" 24 | "server/proto/account" 25 | "server/proto/protocol_base" 26 | "server/tools" 27 | "strconv" 28 | "sync" 29 | 30 | "github.com/sirupsen/logrus" 31 | "github.com/spf13/cobra" 32 | "google.golang.org/protobuf/proto" 33 | ) 34 | 35 | var wg sync.WaitGroup 36 | 37 | // clientconnCmd represents the clientconn command 38 | var clientconnCmd = &cobra.Command{ 39 | Use: "clientconn", 40 | Short: "模拟客户端连接", 41 | Long: `模拟客户端连接 args: 连接数量`, 42 | Run: func(cmd *cobra.Command, args []string) { 43 | num := 2 44 | 45 | wg = sync.WaitGroup{} 46 | 47 | if len(args) == 1 { 48 | num, _ = strconv.Atoi(args[0]) 49 | num++ 50 | } 51 | wg.Add(num - 1) 52 | fmt.Println(num) 53 | for i := 1; i < num; i++ { 54 | go conn(i) 55 | } 56 | wg.Wait() 57 | }, 58 | } 59 | 60 | func init() { 61 | rootCmd.AddCommand(clientconnCmd) 62 | 63 | // Here you will define your flags and configuration settings. 64 | 65 | // Cobra supports Persistent Flags which will work for this command 66 | // and all subcommands, e.g.: 67 | // clientconnCmd.PersistentFlags().String("foo", "", "A help for foo") 68 | 69 | // Cobra supports local flags which will only run when this command 70 | // is called directly, e.g.: 71 | // clientconnCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 72 | } 73 | 74 | func conn(key int) { 75 | defer wg.Done() 76 | 77 | tcpaddr, _ := net.ResolveTCPAddr("tcp", "127.0.0.1:3344") 78 | conn, err := net.DialTCP("tcp", nil, tcpaddr) 79 | if err != nil { 80 | fmt.Println("Dial failed:", err) 81 | return 82 | } 83 | defer conn.Close() 84 | 85 | sendctx, sendcancelFunc := context.WithCancel(context.Background()) 86 | defer sendcancelFunc() 87 | sendchan := make(chan []byte, 1) 88 | 89 | accountname := fmt.Sprintf("%v_%v", "test", key) 90 | password := "123456" 91 | 92 | go func() { 93 | defer sendcancelFunc() 94 | for { 95 | _, buf, err := network.UnpackToBlockFromReader(conn, 2) 96 | if err != nil { 97 | fmt.Println("read failed:", err) 98 | return 99 | } 100 | //module := int32(binary.BigEndian.Uint16(buf[2:4])) 101 | method := int32(binary.BigEndian.Uint16(buf[4:6])) 102 | msgbuf := buf[6:] 103 | 104 | switch method { 105 | case int32(account.MSG_ACCOUNT_Login): 106 | msg := &account.S2C_Login{} 107 | proto.Unmarshal(msgbuf, msg) 108 | fmt.Printf("S2C_Login: [%v]\n", msg) 109 | 110 | if msg.Retcode == 100005 { 111 | //角色为空, 创建角色 112 | SendToClient(int32(account.MSG_ACCOUNT_Module), 113 | int32(account.MSG_ACCOUNT_CreateRole), 114 | &account.C2S_CreateRole{ 115 | RoleName: accountname, 116 | Sex: 1, 117 | HeadID: 23, 118 | }, sendchan) 119 | } else if msg.Retcode == 100004 { 120 | //注册账号 121 | SendToClient(int32(account.MSG_ACCOUNT_Module), 122 | int32(account.MSG_ACCOUNT_Register), 123 | &account.C2S_Register{ 124 | Account: accountname, 125 | Password: password, 126 | Source: accountname, 127 | Equipment: "pc", 128 | CDK: "", 129 | }, sendchan) 130 | } else if msg.Retcode == 100002 { 131 | fmt.Println("密码错误:", msg.Retcode) 132 | return 133 | } else if msg.Retcode == 0 { 134 | fmt.Println("登陆成功:", msg.Retcode) 135 | // time.Sleep(time.Second * 1) 136 | // SendToClient(int32(account.MSG_ACCOUNT_Module), 137 | // int32(account.MSG_ACCOUNT_Ping), 138 | // &account.C2S_Ping{}, sendchan) 139 | } 140 | // case int32(account.MSG_ACCOUNT_Ping): 141 | // msg := &account.S2C_Ping{} 142 | // proto.Unmarshal(msgbuf, msg) 143 | // fmt.Println("ping:", msg.Timestamp) 144 | // time.Sleep(time.Second * 1) 145 | // SendToClient(int32(account.MSG_ACCOUNT_Module), 146 | // int32(account.MSG_ACCOUNT_Ping), 147 | // &account.C2S_Ping{}, sendchan) 148 | case int32(protocol_base.MSG_BASE_NoticeMsg): 149 | fmt.Println("被挤下线") 150 | return 151 | case int32(account.MSG_ACCOUNT_Register): 152 | msg := &account.S2C_Register{} 153 | proto.Unmarshal(msgbuf, msg) 154 | fmt.Printf("S2C_Register: [%v]\n", msg) 155 | 156 | //成功注册创建角色 157 | SendToClient(int32(account.MSG_ACCOUNT_Module), 158 | int32(account.MSG_ACCOUNT_CreateRole), 159 | &account.C2S_CreateRole{ 160 | RoleName: accountname, 161 | Sex: 1, 162 | HeadID: 23, 163 | }, sendchan) 164 | 165 | case int32(account.MSG_ACCOUNT_CreateRole): 166 | msg := &account.S2C_CreateRole{} 167 | proto.Unmarshal(msgbuf, msg) 168 | fmt.Printf("S2C_CreateRole: [%v]\n", msg) 169 | SendToClient(int32(account.MSG_ACCOUNT_Module), 170 | int32(protocol_base.MSG_BASE_HeartBeat), 171 | &protocol_base.C2S_HeartBeat{}, sendchan) 172 | } 173 | } 174 | }() 175 | 176 | msg := &account.C2S_Login{ 177 | Account: accountname, 178 | Password: password, 179 | } 180 | SendToClient(int32(account.MSG_ACCOUNT_Module), int32(account.MSG_ACCOUNT_Login), msg, sendchan) 181 | 182 | for { 183 | select { 184 | case buf := <-sendchan: 185 | le := tools.IntToBytes(int32(len(buf)), 2) 186 | conn.Write(tools.BytesCombine(le, buf)) 187 | case <-sendctx.Done(): 188 | return 189 | } 190 | } 191 | 192 | } 193 | 194 | // //SendToClient 发送消息至客户端 195 | func SendToClient(module int32, method int32, pb proto.Message, sendchan chan []byte) { 196 | //logrus.Debugf("client send msg [%v] [%v] [%v]", module, method, pb) 197 | data, err := proto.Marshal(pb) 198 | if err != nil { 199 | logrus.Errorf("proto encode error[%v] [%v][%v] [%v]", err.Error(), module, method, pb) 200 | return 201 | } 202 | mldulebuf := tools.IntToBytes(module, 2) 203 | methodbuf := tools.IntToBytes(method, 2) 204 | sendchan <- tools.BytesCombine(mldulebuf, methodbuf, data) 205 | } 206 | -------------------------------------------------------------------------------- /web/ws_client.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package web 6 | 7 | import ( 8 | "bytes" 9 | "encoding/binary" 10 | "errors" 11 | "fmt" 12 | "net/http" 13 | "server/network" 14 | "server/tools" 15 | "sync/atomic" 16 | "time" 17 | 18 | "github.com/ergo-services/ergo/etf" 19 | "github.com/ergo-services/ergo/gen" 20 | "github.com/gin-gonic/gin" 21 | "github.com/gorilla/websocket" 22 | "github.com/sirupsen/logrus" 23 | ) 24 | 25 | var ( 26 | // Time allowed to write a message to the peer. 27 | writeWait = 30 * time.Second 28 | 29 | // Time allowed to read the next pong message from the peer. 30 | pongWait = 60 * time.Second 31 | 32 | // Send pings to peer with this period. Must be less than pongWait. 33 | pingPeriod = (pongWait * 9) / 10 34 | 35 | // Maximum message size allowed from peer. 36 | maxMessageSize = 1024 37 | ) 38 | 39 | var ( 40 | newline = []byte{'\n'} 41 | space = []byte{' '} 42 | ) 43 | 44 | // 客户端连接 45 | func WsClient(hub *Hub, context *gin.Context, nw *network.NetWorkx) { 46 | upGrande := websocket.Upgrader{ 47 | //设置允许跨域 48 | CheckOrigin: func(r *http.Request) bool { 49 | return true 50 | }, 51 | //设置请求协议 52 | Subprotocols: []string{context.GetHeader("Sec-WebSocket-Protocol")}, 53 | // ReadBufferSize: 1024, 54 | // WriteBufferSize: 1024, 55 | } 56 | //创建连接 57 | conn, err := upGrande.Upgrade(context.Writer, context.Request, nil) 58 | if err != nil { 59 | context.JSON(51001, gin.H{ 60 | "websocket connect error": context.Param("channel"), 61 | }) 62 | return 63 | } 64 | 65 | process, clientHander, sendchan, err := nw.CreateProcess() 66 | defer nw.UserPool.Put(clientHander) 67 | 68 | wsclient := &wsClient{hub: hub, conn: conn, send: sendchan, testsend: make(chan []byte, 1)} 69 | wsclient.hub.register <- wsclient 70 | 71 | pongWait = time.Second * time.Duration(nw.Readtimeout) 72 | 73 | atomic.AddInt32(&nw.ConnectCount, 1) 74 | defer atomic.AddInt32(&nw.ConnectCount, -1) 75 | 76 | // Allow collection of memory referenced by the caller by doing all work in 77 | // new goroutines. 78 | go wsclient.writePump(nw.Packet) 79 | wsclient.readPump(process, nw.Packet) 80 | } 81 | 82 | // wsClient is a middleman between the websocket connection and the hub. 83 | type wsClient struct { 84 | hub *Hub 85 | // The websocket connection. 86 | conn *websocket.Conn 87 | // Buffered channel of outbound messages. 88 | send chan []byte 89 | 90 | testsend chan []byte 91 | } 92 | 93 | func (c *wsClient) readPump(process gen.Process, packet int32) { 94 | defer func() { 95 | c.hub.unregister <- c 96 | c.conn.Close() 97 | }() 98 | c.conn.SetReadLimit(int64(maxMessageSize)) 99 | c.conn.SetReadDeadline(time.Now().Add(pongWait)) 100 | c.conn.SetPongHandler(func(string) error { c.conn.SetReadDeadline(time.Now().Add(pongWait)); return nil }) 101 | 102 | defer process.Send(process.Self(), etf.Term(etf.Tuple{etf.Atom("$gen_cast"), etf.Atom("SocketStop")})) 103 | 104 | for { 105 | messageType, messagebuf, err := unpackToBlockFromReader(c.conn, packet, []byte{}) 106 | if err != nil { 107 | if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) { 108 | logrus.Errorf("WebSocket closed error: %v", err.Error()) 109 | } 110 | return 111 | } 112 | 113 | if messageType == 1 { 114 | messagebuf = bytes.TrimSpace(bytes.Replace(messagebuf, newline, space, -1)) 115 | c.hub.broadcast <- messagebuf 116 | } else { 117 | if len(messagebuf) < 4 { 118 | logrus.Debug("buf len:", len(messagebuf)) 119 | return 120 | } 121 | module := int32(binary.BigEndian.Uint16(messagebuf[packet : packet+2])) 122 | method := int32(binary.BigEndian.Uint16(messagebuf[packet+2 : packet+4])) 123 | process.Send(process.Self(), etf.Term(etf.Tuple{etf.Atom("$gen_cast"), etf.Tuple{module, method, messagebuf[packet+4:]}})) 124 | } 125 | } 126 | } 127 | 128 | func (c *wsClient) writePump(packet int32) { 129 | ticker := time.NewTicker(pingPeriod) 130 | defer func() { 131 | ticker.Stop() 132 | c.conn.Close() 133 | }() 134 | for { 135 | select { 136 | case message, ok := <-c.send: 137 | c.conn.SetWriteDeadline(time.Now().Add(writeWait)) 138 | if !ok { 139 | // The hub closed the channel. 140 | c.conn.WriteMessage(websocket.CloseMessage, []byte{}) 141 | return 142 | } 143 | w, err := c.conn.NextWriter(websocket.BinaryMessage) 144 | if err != nil { 145 | return 146 | } 147 | 148 | le := tools.IntToBytes(int32(len(message)), packet) 149 | buf := tools.BytesCombine(le, message) 150 | w.Write(buf) 151 | 152 | if err := w.Close(); err != nil { 153 | return 154 | } 155 | case message, ok := <-c.testsend: 156 | c.conn.SetWriteDeadline(time.Now().Add(writeWait)) 157 | if !ok { 158 | // The hub closed the channel. 159 | c.conn.WriteMessage(websocket.CloseMessage, []byte{}) 160 | return 161 | } 162 | 163 | w, err := c.conn.NextWriter(websocket.TextMessage) 164 | if err != nil { 165 | return 166 | } 167 | w.Write(message) 168 | 169 | // Add queued chat messages to the current websocket message. 170 | n := len(c.testsend) 171 | for i := 0; i < n; i++ { 172 | w.Write(newline) 173 | w.Write(<-c.testsend) 174 | } 175 | 176 | if err := w.Close(); err != nil { 177 | return 178 | } 179 | case <-ticker.C: 180 | c.conn.SetWriteDeadline(time.Now().Add(writeWait)) 181 | if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil { 182 | return 183 | } 184 | } 185 | } 186 | } 187 | 188 | func unpackToBlockFromReader(conn *websocket.Conn, packet int32, lastbyte []byte) (int, []byte, error) { 189 | messageType, buf, err := conn.ReadMessage() 190 | buf = tools.BytesCombine(lastbyte, buf) 191 | if err != nil { 192 | return 0, nil, err 193 | } 194 | switch messageType { 195 | case 1: 196 | return messageType, buf, nil 197 | case 2: 198 | if len(buf) <= int(packet) { 199 | return messageType, nil, errors.New("packet size error") 200 | } 201 | lenght, err := LengthOf(buf, packet) 202 | if err != nil { 203 | return messageType, nil, err 204 | } 205 | 206 | if int(lenght) < len(buf[packet:]) { 207 | return unpackToBlockFromReader(conn, packet, buf[lenght:]) 208 | } else { 209 | return messageType, buf, nil 210 | } 211 | } 212 | 213 | return 0, nil, nil 214 | } 215 | 216 | func LengthOf(stream []byte, packet int32) (int32, error) { 217 | if len(stream) < int(packet) { 218 | return 0, errors.New(fmt.Sprint("stream lenth should be bigger than ", packet)) 219 | } 220 | 221 | switch packet { 222 | case 2: 223 | return int32(binary.BigEndian.Uint16(stream[0:2])), nil 224 | case 4: 225 | return int32(binary.BigEndian.Uint32(stream[0:4])), nil 226 | default: 227 | errstr := fmt.Sprintf("stream lenth seting error [packet: %v]", packet) 228 | return 0, errors.New(errstr) 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /gserver/gserver.go: -------------------------------------------------------------------------------- 1 | package gserver 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "os/signal" 7 | "runtime" 8 | "server/db" 9 | "server/gserver/cfg" 10 | "server/gserver/clienconnect" 11 | "server/gserver/commonstruct" 12 | "server/gserver/genServer" 13 | "server/gserver/nodeManange" 14 | "server/logger" 15 | "server/network" 16 | "server/web" 17 | "sync/atomic" 18 | "syscall" 19 | 20 | "github.com/fsnotify/fsnotify" 21 | "github.com/pyroscope-io/pyroscope/pkg/agent/profiler" 22 | "github.com/sirupsen/logrus" 23 | 24 | //msg "server/proto" 25 | 26 | "github.com/facebookgo/pidfile" 27 | ) 28 | 29 | //GameServerInfo game info 30 | var GameServerInfo *gameServer 31 | 32 | type gameServer struct { 33 | nw *network.NetWorkx 34 | command chan string 35 | } 36 | 37 | func (g *gameServer) OpenConn() { 38 | g.nw.OpenConn = true 39 | } 40 | 41 | func (g *gameServer) CloseConn() { 42 | g.nw.OpenConn = false 43 | } 44 | 45 | func (g *gameServer) Start() { 46 | nodeManange.Start(g.command) 47 | 48 | //启动网络 49 | gateNode := nodeManange.GetNode(nodeManange.GateNode) 50 | 51 | if gateNode != nil { 52 | g.nw.Start(gateNode) 53 | } 54 | } 55 | 56 | func (g *gameServer) Close() { 57 | g.nw.Close() 58 | for _, node := range nodeManange.GetNodes() { 59 | for _, process := range node.ProcessList() { 60 | process.Exit("server stop") 61 | process.Wait() 62 | } 63 | node.Stop() 64 | node.Wait() 65 | } 66 | } 67 | 68 | //StartGServer 启动game server 69 | //go run main.go start --config=E:/worke/server/cfg.yaml 70 | func StartGServer() { 71 | logrus.Infof("============================= Begin Start [%v][%v] version:[%v] ===============================", commonstruct.ServerCfg.ServerName, commonstruct.ServerCfg.ServerID, commonstruct.ServerCfg.Version) 72 | if level, err := logrus.ParseLevel(commonstruct.ServerCfg.Loglevel); err == nil { 73 | logger.Init(level, commonstruct.ServerCfg.LogWrite, commonstruct.ServerCfg.LogName, commonstruct.ServerCfg.LogPath) 74 | } else { 75 | logger.Init(logrus.InfoLevel, commonstruct.ServerCfg.LogWrite, commonstruct.ServerCfg.LogName, commonstruct.ServerCfg.LogPath) 76 | } 77 | 78 | //set pid file 79 | //file, _ := ioutil.TempFile("", fmt.Sprintf("pid_%v_%v_", commonstruct.ServerCfg.ServerName, commonstruct.ServerCfg.ServerID)) 80 | filename := fmt.Sprintf("/tmp/pid_%v_%v", commonstruct.ServerCfg.ServerName, commonstruct.ServerCfg.ServerID) 81 | pidfile.SetPidfilePath(filename) 82 | if i, _ := pidfile.Read(); i != 0 { 83 | logrus.Warnf("服务已启动请检查或清除 进程id [%v] pidfile: [%v] ", i, filename) 84 | return 85 | } 86 | 87 | cfg.InitViperConfig(commonstruct.ServerCfg.CfgPath, commonstruct.ServerCfg.CfgType) 88 | if commonstruct.ServerCfg.WatchConfig { 89 | cfg.WatchConfig(commonstruct.ServerCfg.CfgPath, func(in fsnotify.Event) { 90 | logrus.Debug("Config file changed: [%v] ", in.Name) 91 | cfg.InitViperConfig(commonstruct.ServerCfg.CfgPath, commonstruct.ServerCfg.CfgType) 92 | }) 93 | } 94 | 95 | //启动定时器 96 | //timedtasks.StartCronTasks() 97 | // //定时器 98 | // timedtasks.AddTasks("loop", "* * * * * ?", func() { 99 | // logrus.Info("server time:", time.Now()) 100 | // }) 101 | //defer timedtasks.RemoveTasks("loop") 102 | 103 | db.StartMongodb(commonstruct.ServerCfg.Mongodb, commonstruct.ServerCfg.MongoConnStr) 104 | if ok, err := db.MongodbPing(); ok { 105 | logrus.Info("mongodb conn success") 106 | } else { 107 | panic(err) 108 | } 109 | 110 | db.StartRedis(commonstruct.ServerCfg.RedisConnStr, commonstruct.ServerCfg.RedisDB) 111 | if ok, err := db.RedisConn(); ok { 112 | logrus.Info("redis conn success") 113 | } else { 114 | panic(err) 115 | } 116 | 117 | GameServerInfo = &gameServer{ 118 | nw: network.NewNetWorkX( 119 | func() genServer.GateGenHanderInterface { 120 | return clienconnect.NewClient() 121 | }, 122 | commonstruct.ServerCfg.Port, 123 | commonstruct.ServerCfg.Packet, 124 | commonstruct.ServerCfg.Readtimeout, 125 | commonstruct.ServerCfg.NetType, 126 | commonstruct.ServerCfg.MaxConnectNum, 127 | commonstruct.ServerCfg.MsgTime, 128 | commonstruct.ServerCfg.MsgNum, 129 | func() { SendGameServerMsg("StartSuccess") }, 130 | func() { 131 | db.RedisExec("del", "MaxRoleID") 132 | }, 133 | func() { 134 | //logrus.Info("connect number: ", db.RedisINCRBY("ConnectNumber", 1)) 135 | }, 136 | func() { 137 | //logrus.Info("connect number: ", db.RedisINCRBY("ConnectNumber", -1)) 138 | }, 139 | ), 140 | 141 | command: make(chan string), 142 | } 143 | 144 | GameServerInfo.Start() 145 | defer ClonseServer() 146 | defer GameServerInfo.Close() 147 | 148 | if commonstruct.ServerCfg.OpenHTTP { 149 | go web.Start( 150 | commonstruct.ServerCfg.HTTPPort, 151 | commonstruct.ServerCfg.SetMode, 152 | GameServerInfo.nw) 153 | } 154 | 155 | if commonstruct.ServerCfg.OpenPyroscope { 156 | profiler.Start(profiler.Config{ 157 | ApplicationName: fmt.Sprintf("%v_%v", commonstruct.ServerCfg.ServerName, commonstruct.ServerCfg.ServerID), 158 | ServerAddress: commonstruct.ServerCfg.PyroscopeHost, 159 | }) 160 | } 161 | 162 | //退出消息监控 163 | var exitChan = make(chan os.Signal) 164 | 165 | if runtime.GOOS == "linux" { 166 | //signal.Notify(exitChan, os.Interrupt, os.Kill, syscall.SIGTERM) 167 | signal.Notify(exitChan, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGUSR1, syscall.SIGUSR2, syscall.SIGTSTP) 168 | } else { 169 | signal.Notify(exitChan, os.Interrupt) 170 | } 171 | 172 | for { 173 | select { 174 | case command := <-GameServerInfo.command: 175 | switch command { 176 | case "StartSuccess": 177 | pid := StartSuccess() 178 | logrus.Infof("====================== Start Game Server pid:[%v] Success =========================", pid) 179 | case "shutdown": 180 | GameServerInfo.CloseConn() 181 | logrus.Warn("Shut down the game server") 182 | return 183 | case "OpenConn": 184 | GameServerInfo.OpenConn() 185 | case "CloseConn": 186 | GameServerInfo.CloseConn() 187 | default: 188 | logrus.Warn("command:", command) 189 | } 190 | case s := <-exitChan: 191 | num := atomic.LoadInt32(&GameServerInfo.nw.ConnectCount) 192 | logrus.Infof("收到信号: %v ConnectCount:%v", s, num) 193 | if runtime.GOOS == "linux" && s.String() == "quit" || s.String() == "terminated" { 194 | return 195 | } else if runtime.GOOS == "windows" && s.String() == "interrupt" { 196 | return 197 | } 198 | // case <-time.After(1 * time.Second): 199 | // logrus.Infof("time: [%v] online:[%v] [%v]", time.Now().Format(tools.DateTimeFormat), db.RedisGetInt("ConnectNumber"), GameServerInfo.nw.ConnectCount) 200 | } 201 | } 202 | } 203 | 204 | //SendGameServerMsg game system msg 205 | func SendGameServerMsg(msg string) { 206 | GameServerInfo.command <- msg 207 | } 208 | 209 | //成功启动 210 | func StartSuccess() int { 211 | pidfile.Write() 212 | logrus.Infof("pidfile :%v", pidfile.GetPidfilePath()) 213 | i, _ := pidfile.Read() 214 | 215 | db.RedisExec("set", "MaxRoleID", commonstruct.GetMaxRoleID(commonstruct.ServerCfg.ServerID)) 216 | GameServerInfo.OpenConn() 217 | return i 218 | } 219 | 220 | //关闭服务 221 | func ClonseServer() { 222 | logrus.Info("delete pidfile: ", pidfile.GetPidfilePath()) 223 | os.Remove(pidfile.GetPidfilePath()) 224 | } 225 | -------------------------------------------------------------------------------- /db/mongo.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "context" 5 | 6 | pool "github.com/jolestar/go-commons-pool/v2" 7 | "github.com/sirupsen/logrus" 8 | "go.mongodb.org/mongo-driver/bson" 9 | "go.mongodb.org/mongo-driver/bson/primitive" 10 | "go.mongodb.org/mongo-driver/mongo" 11 | "go.mongodb.org/mongo-driver/mongo/options" 12 | ) 13 | 14 | var ( 15 | database string 16 | urlstr string 17 | clientPool *pool.ObjectPool 18 | ) 19 | 20 | //StartMongodb mongodb init 21 | func StartMongodb(dbname string, url string) { 22 | logrus.Infof("StartMongodb create sync.pool:[%v] dbname:[%v]", url, dbname) 23 | database = dbname 24 | urlstr = url 25 | 26 | factory := pool.NewPooledObjectFactorySimple( 27 | func(context.Context) (interface{}, error) { 28 | clientOptions := options.Client().ApplyURI(url) 29 | client, err := mongo.Connect(context.TODO(), clientOptions) 30 | if err != nil { 31 | logrus.Error(err) 32 | } 33 | return client, nil 34 | }) 35 | ctx := context.Background() 36 | clientPool = pool.NewObjectPoolWithDefaultConfig(ctx, factory) 37 | } 38 | 39 | func MongodbPing() (bool, error) { 40 | ctx := context.Background() 41 | obj, err := clientPool.BorrowObject(ctx) 42 | if err != nil { 43 | logrus.Error(err) 44 | } 45 | client := obj.(*mongo.Client) 46 | if err := client.Ping(context.TODO(), nil); err != nil { 47 | return false, err 48 | } 49 | return true, nil 50 | } 51 | 52 | //GetDatabase connectPool mongodb database 53 | func GetDatabase() (*mongo.Client, *mongo.Database) { 54 | 55 | ctx := context.Background() 56 | obj, err := clientPool.BorrowObject(ctx) 57 | if err != nil { 58 | logrus.Error(err) 59 | } 60 | client := obj.(*mongo.Client) 61 | 62 | // 检查连接 63 | if err := client.Ping(context.TODO(), nil); err != nil { 64 | logrus.Warn(err) 65 | clientOptions := options.Client().ApplyURI(urlstr) 66 | if client, err = mongo.Connect(context.TODO(), clientOptions); err != nil { 67 | logrus.Error(err) 68 | } 69 | } 70 | 71 | database := client.Database(database) 72 | return client, database 73 | } 74 | 75 | //getCollection connectPool mongodb collection 76 | func getCollection(collectionname string) (*mongo.Client, *mongo.Collection) { 77 | client, database := GetDatabase() 78 | collection := database.Collection(collectionname) 79 | return client, collection 80 | } 81 | 82 | //InsertOne 添加数据 83 | func InsertOne(tbname string, document interface{}) error { 84 | client, collection := getCollection(tbname) 85 | 86 | _, err := collection.InsertOne(context.TODO(), document) 87 | 88 | if err != nil { 89 | logrus.Error(err) 90 | } 91 | 92 | clientPool.ReturnObject(context.Background(), client) 93 | 94 | return err 95 | } 96 | 97 | //FindOneBson 查询数据 98 | //filter := bson.D{{field, value}} 99 | //filter := bson.D{primitive.E{Key:field, value}} 100 | func FindOneBson(tbname string, document interface{}, filter interface{}) error { 101 | client, collection := getCollection(tbname) 102 | defer clientPool.ReturnObject(context.Background(), client) 103 | return collection.FindOne(context.TODO(), filter).Decode(document) 104 | } 105 | 106 | //事务执行命令 107 | func SessionFunc(tbname string, execfun func(collection *mongo.Collection) error) error { 108 | client, collection := getCollection(tbname) 109 | defer clientPool.ReturnObject(context.Background(), client) 110 | session, err := client.StartSession() 111 | if err != nil { 112 | logrus.Error(err) 113 | return err 114 | } 115 | defer session.EndSession(context.TODO()) 116 | return execfun(collection) 117 | } 118 | 119 | //FindBson 查找数据 120 | func FindBson(tbname string, filter interface{}) (*mongo.Cursor, error) { 121 | client, collection := getCollection(tbname) 122 | defer clientPool.ReturnObject(context.Background(), client) 123 | 124 | findOptions := options.Find() 125 | 126 | return collection.Find(context.TODO(), filter, findOptions) 127 | // cur, err := collection.Find(context.TODO(), filter, findOptions) 128 | // if err != nil { 129 | // return 0, err 130 | // } 131 | 132 | // // Finding multiple documents returns a cursor 133 | // // Iterating through the cursor allows us to decode documents one at a time 134 | // for cur.Next(context.TODO()) { 135 | 136 | // // create a value into which the single document can be decoded 137 | // var elem Trainer 138 | // err := cur.Decode(&elem) 139 | // if err != nil { 140 | // logrus.Fatal(err) 141 | // } 142 | 143 | // results = append(results, &elem) 144 | // } 145 | 146 | // if err := cur.Err(); err != nil { 147 | // logrus.Fatal(err) 148 | // } 149 | 150 | // // Close the cursor once finished 151 | // cur.Close(context.TODO()) 152 | 153 | //return len(cur.Current), nil 154 | } 155 | 156 | //Update 更新数据 157 | // Findfield := bson.D{{"name", "Ash"}} 158 | // Upfield := bson.D{{"$set", bson.D{{"age", 1}}}} 159 | func Update(tbname string, Findfield interface{}, Upfield interface{}) (int64, error) { 160 | client, collection := getCollection(tbname) 161 | defer clientPool.ReturnObject(context.Background(), client) 162 | 163 | updateResult, err := collection.UpdateOne(context.TODO(), Findfield, Upfield) 164 | if err != nil { 165 | return 0, err 166 | } 167 | //logrus.Debug("Matched %v documents and updated %v documents.\n", updateResult.MatchedCount, updateResult.ModifiedCount) 168 | return updateResult.ModifiedCount, nil 169 | } 170 | 171 | //Update 更新数据 172 | // Findfield := bson.D{primitive.E{Key: field, Value: value}} 173 | // replacement 替换所选文档的文档 174 | func ReplaceOne(tbname string, Findfield interface{}, replacement interface{}) (int64, error) { 175 | client, collection := getCollection(tbname) 176 | defer clientPool.ReturnObject(context.Background(), client) 177 | 178 | updateResult, err := collection.ReplaceOne(context.TODO(), Findfield, replacement) 179 | //updateResult, err := collection.UpdateOne(context.TODO(), Findfield, Upfield) 180 | if err != nil { 181 | return 0, err 182 | } 183 | //logrus.Debug("Matched %v documents and updated %v documents.\n", updateResult.MatchedCount, updateResult.ModifiedCount) 184 | return updateResult.ModifiedCount, nil 185 | } 186 | 187 | //Delete 删除 188 | func Delete(tbname string, field string, value interface{}) int64 { 189 | client, collection := getCollection(tbname) 190 | defer clientPool.ReturnObject(context.Background(), client) 191 | filter := bson.D{primitive.E{Key: field, Value: value}} 192 | //删除所有 193 | deleteResult, err := collection.DeleteMany(context.TODO(), filter) 194 | if err != nil { 195 | logrus.Error(err) 196 | } 197 | //logrus.Debugf("Deleted %v documents in the trainers collection\n", deleteResult.DeletedCount) 198 | 199 | return deleteResult.DeletedCount 200 | } 201 | 202 | //FindFieldMax 查询最大值 203 | func FindFieldMax(tbname string, fieldkey string, document interface{}, filter interface{}) error { 204 | client, collection := getCollection(tbname) 205 | defer clientPool.ReturnObject(context.Background(), client) 206 | 207 | //filter := bson.D{{}} 208 | findOptions := options.FindOne().SetSort(bson.D{primitive.E{Key: fieldkey, Value: -1}}).SetSkip(0) 209 | return collection.FindOne(context.TODO(), filter, findOptions).Decode(document) 210 | } 211 | 212 | // //FindFieldMaxFilter 查询最大值 213 | // func FindFieldMaxFilter(tbname string, fieldkey string, filter interface{}, document interface{}) error { 214 | // client, collection := getCollection(tbname) 215 | // defer clientPool.ReturnObject(context.Background(), client) 216 | 217 | // findOptions := options.FindOne().SetSort(bson.D{primitive.E{Key: fieldkey, Value: -1}}).SetSkip(0) 218 | // return collection.FindOne(context.TODO(), filter, findOptions).Decode(document) 219 | // } 220 | -------------------------------------------------------------------------------- /db/redis.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | //"encoding/json" 5 | "fmt" 6 | "time" 7 | 8 | jsoniter "github.com/json-iterator/go" 9 | "github.com/sirupsen/logrus" 10 | 11 | red "github.com/gomodule/redigo/redis" 12 | ) 13 | 14 | var json = jsoniter.ConfigCompatibleWithStandardLibrary 15 | 16 | //Redis redis 17 | type Redis struct { 18 | pool *red.Pool 19 | } 20 | 21 | var redis *Redis 22 | 23 | //StartRedis 初始化 24 | func StartRedis(address string, selectdb int) { 25 | logrus.Infof("StartRedis create redis.pool: [%v]", address) 26 | redis = new(Redis) 27 | redis.pool = &red.Pool{ 28 | MaxIdle: 256, 29 | MaxActive: 0, 30 | IdleTimeout: time.Duration(120), 31 | Dial: func() (red.Conn, error) { 32 | return red.Dial( 33 | "tcp", 34 | address, 35 | red.DialReadTimeout(time.Duration(1000)*time.Millisecond), 36 | red.DialWriteTimeout(time.Duration(1000)*time.Millisecond), 37 | red.DialConnectTimeout(time.Duration(1000)*time.Millisecond), 38 | red.DialDatabase(selectdb), 39 | //red.DialPassword(""), 40 | ) 41 | }, 42 | } 43 | } 44 | 45 | func RedisConn() (bool, error) { 46 | con := redis.pool.Get() 47 | if err := con.Err(); err != nil { 48 | return false, err 49 | } 50 | return true, nil 51 | } 52 | 53 | //RedisExec 命令 54 | func RedisExec(cmd string, key interface{}, args ...interface{}) (interface{}, error) { 55 | con := redis.pool.Get() 56 | if err := con.Err(); err != nil { 57 | return nil, err 58 | } 59 | defer con.Close() 60 | parmas := make([]interface{}, 0) 61 | parmas = append(parmas, key) 62 | 63 | if len(args) > 0 { 64 | parmas = append(parmas, args...) 65 | } 66 | return con.Do(cmd, parmas...) 67 | } 68 | 69 | //GetAutoID 获取自增id 70 | func GetAutoID(tabname string) int32 { 71 | autoid, err := red.Int(RedisExec("incr", tabname)) 72 | if err != nil { 73 | logrus.Error(err) 74 | } 75 | return int32(autoid) 76 | } 77 | 78 | //RedisINCRBY RedisINCRBY num 79 | func RedisINCRBY(tabname string, num int32) int32 { 80 | autoid, err := red.Int(RedisExec("INCRBY", tabname, num)) 81 | if err != nil { 82 | logrus.Error(err) 83 | } 84 | return int32(autoid) 85 | } 86 | 87 | func RedisDel(key string) { 88 | _, err := RedisExec("del", key) 89 | if err != nil { 90 | logrus.Error(err) 91 | } 92 | } 93 | 94 | //RedisGetInt get redis int 95 | func RedisGetInt(tabname string) int { 96 | if data, err := red.Int(RedisExec("get", tabname)); err == nil { 97 | return data 98 | } 99 | return 0 100 | } 101 | 102 | //RedisSetStruct save struct 103 | func RedisSetStruct(key string, v interface{}) (interface{}, error) { 104 | conn := redis.pool.Get() 105 | defer conn.Close() 106 | b, err := json.Marshal(v) 107 | if err != nil { 108 | return nil, err 109 | } 110 | return conn.Do("SET", key, string(b)) 111 | } 112 | 113 | //GetStruct get 114 | // func GetStruct(key string, obj interface{}) error { 115 | // conn := redis.pool.Get() 116 | // defer conn.Close() 117 | // objStr, err := red.String(conn.Do("GET", key)) 118 | // if err != nil { 119 | // return err 120 | // } 121 | // b := []byte(objStr) 122 | // err = json.Unmarshal(b, obj) 123 | // return err 124 | // } 125 | 126 | //GetStruct get 127 | func GetStruct[T any](key string) (*T, error) { 128 | conn := redis.pool.Get() 129 | defer conn.Close() 130 | objStr, err := red.String(conn.Do("GET", key)) 131 | 132 | obj := new(T) 133 | if err != nil { 134 | return obj, err 135 | } 136 | b := []byte(objStr) 137 | 138 | err = json.Unmarshal(b, obj) 139 | return obj, err 140 | } 141 | 142 | //HMSET HMSET redsi 143 | func HMSET(field string, args ...interface{}) { 144 | RedisExec("HMSET", field, args...) 145 | } 146 | 147 | //HMGET HMGET redsi 148 | func HMGET(field string, keys ...interface{}) (map[interface{}]string, error) { 149 | value, err := red.Values(RedisExec("HMGET", field, keys...)) 150 | if err != nil { 151 | return nil, err 152 | } 153 | 154 | var returnmap = make(map[interface{}]string) 155 | for k, v := range value { 156 | if v == nil { 157 | break 158 | } 159 | v := v.([]byte) 160 | returnmap[keys[k]] = string(v) 161 | } 162 | return returnmap, err 163 | } 164 | 165 | //HVALS 获取所有值 166 | func HVALS(field string) (map[interface{}][]byte, error) { 167 | value, err := red.Values(RedisExec("HVALS", field)) 168 | if err != nil { 169 | return nil, err 170 | } 171 | 172 | var returnmap = make(map[interface{}][]byte) 173 | for k, v := range value { 174 | 175 | if v == nil { 176 | break 177 | } 178 | v := v.([]byte) 179 | 180 | returnmap[k] = v 181 | } 182 | 183 | return returnmap, err 184 | } 185 | 186 | //==========================有序集合================================================== 187 | //保存数据 key score:分值 member 数据 188 | func RedisZADD(key string, score int64, member interface{}) (reply interface{}, e error) { 189 | 190 | switch member.(type) { 191 | case string: 192 | return RedisExec("ZADD", key, score, member) 193 | default: 194 | b, _ := json.Marshal(member) 195 | return RedisExec("ZADD", key, score, string(b)) 196 | } 197 | 198 | } 199 | 200 | //返回有序集中成员的排名。其中有序集成员按分数值递减(从大到小)排序。 201 | func RedisZrevrank(key string, member interface{}) (int, error) { 202 | switch member.(type) { 203 | case string: 204 | return red.Int(RedisExec("ZREVRANK", key, member)) 205 | default: 206 | b, _ := json.Marshal(member) 207 | fmt.Println("数据:", string(b)) 208 | return red.Int(RedisExec("ZREVRANK", key, string(b))) 209 | } 210 | 211 | } 212 | 213 | //有序集合成员数 214 | func RedisZCARD(key string) (interface{}, error) { 215 | return red.Int(RedisExec("ZCARD", key)) 216 | } 217 | 218 | //返回有序集中,指定区间内的成员。 分数由低到高 219 | //key strart 起始 stop 结束 withscores:是否附带分值 220 | func RedisZrange(key string, start, stop int32, withscores bool) ([]string, error) { 221 | if withscores { 222 | return red.Strings(RedisExec("ZRANGE", key, start, stop, "WITHSCORES")) 223 | } 224 | return red.Strings(RedisExec("ZRANGE", key, start, stop)) 225 | } 226 | 227 | //返回有序集中,指定区间内的成员。 分数由高到低 228 | //key strart 起始 stop 结束 withscores:是否附带分值 229 | func RedisZrevRange(key string, start, stop int32, withscores bool) ([]string, error) { 230 | if withscores { 231 | return red.Strings(RedisExec("zrevrange", key, start, stop, "WITHSCORES")) 232 | } 233 | return red.Strings(RedisExec("zrevrange", key, start, stop)) 234 | } 235 | 236 | //按分值 返回有序集合中指定分数区间的成员列表。有序集成员按分数值递增(从小到大)次序排列。 237 | // min < score <= max 238 | // withscores :是否附带分值 239 | func RedisZrangeByScore(key string, min, max int32, withscores bool) ([]string, error) { 240 | if withscores { 241 | return red.Strings(RedisExec("ZRANGEBYSCORE", key, fmt.Sprintf("(%v", min), max, "WITHSCORES")) 242 | } 243 | return red.Strings(RedisExec("ZRANGEBYSCORE", key, fmt.Sprintf("(%v", min), max)) 244 | } 245 | 246 | //按分值 返回有序集合中指定分数区间的成员列表。有序集成员按分数值递增(从大到小)次序排列。 247 | // min < score <= max 248 | // withscores :是否附带分值 249 | func RedisZrevrangebyscore(key string, max, min int32, withscores bool) ([]string, error) { 250 | if withscores { 251 | return red.Strings(RedisExec("Zrevrangebyscore", key, max, min, "WITHSCORES")) 252 | } 253 | return red.Strings(RedisExec("Zrevrangebyscore", key, max, min)) 254 | } 255 | 256 | //用于移除有序集中,指定排名(rank)区间内的所有成员。 排名rank的分值 由低到高计算 257 | func RedisZremeangeByRank(key string, start, stop int32) (int, error) { 258 | return red.Int(RedisExec("ZREMRANGEBYRANK", key, start, stop)) 259 | } 260 | 261 | //移除有序集中,指定分数(score)区间内的所有成员。 262 | func RedisZremrangebyScore(key string, min, max int32) (int, error) { 263 | return red.Int(RedisExec("ZREMRANGEBYSCORE", key, min, max)) 264 | } 265 | 266 | //==================================================================================== 267 | -------------------------------------------------------------------------------- /gserver/clienconnect/client_login.go: -------------------------------------------------------------------------------- 1 | package clienconnect 2 | 3 | import ( 4 | "fmt" 5 | "server/db" 6 | "server/gserver/cfg" 7 | "server/gserver/commonstruct" 8 | "server/gserver/nodeManange" 9 | pbaccount "server/proto/account" 10 | "server/proto/protocol_base" 11 | pbrole "server/proto/role" 12 | "time" 13 | 14 | "github.com/ergo-services/ergo/etf" 15 | "github.com/sirupsen/logrus" 16 | "go.mongodb.org/mongo-driver/bson/primitive" 17 | ) 18 | 19 | func (c *Client) accountLogin(msg *pbaccount.C2S_Login) { 20 | retmsg := &pbaccount.S2C_Login{ 21 | Retcode: 0, 22 | RoleInfo: &pbrole.Pb_RoleInfo{}, 23 | } 24 | 25 | //已登陆 26 | if c.connectState != StatusSockert { 27 | retmsg.Retcode = cfg.GetErrorCodeNumber("LOGIN") 28 | c.SendToClient(int32(pbaccount.MSG_ACCOUNT_Module), int32(pbaccount.MSG_ACCOUNT_Login), retmsg) 29 | } 30 | 31 | accountinfo := &commonstruct.AccountInfo{ 32 | Account: msg.Account, 33 | } 34 | ok := accountinfo.GetAccountinfo() 35 | c.roleID = accountinfo.RoleID 36 | //未找到账号 37 | if !ok { 38 | retmsg.Retcode = cfg.GetErrorCodeNumber("AccountNull") 39 | c.SendToClient(int32(pbaccount.MSG_ACCOUNT_Module), int32(pbaccount.MSG_ACCOUNT_Login), retmsg) 40 | return 41 | } 42 | if accountinfo.Password != msg.Password { 43 | retmsg.Retcode = cfg.GetErrorCodeNumber("PASSWORD_ERROR") 44 | c.SendToClient(int32(pbaccount.MSG_ACCOUNT_Module), int32(pbaccount.MSG_ACCOUNT_Login), retmsg) 45 | return 46 | } 47 | 48 | c.connectState = StatusLogin 49 | roledata := commonstruct.GetRoleAllData(accountinfo.RoleID) 50 | //未创建账号 角色为空 51 | if roledata.RoleBase.Name == "" { 52 | retmsg.Retcode = cfg.GetErrorCodeNumber("RoleNull") 53 | c.SendToClient(int32(pbaccount.MSG_ACCOUNT_Module), int32(pbaccount.MSG_ACCOUNT_Login), retmsg) 54 | return 55 | } 56 | 57 | //账号已登陆 58 | c.registerName = fmt.Sprintf("role_%v", accountinfo.RoleID) 59 | node := nodeManange.GetNode(nodeManange.GateNode) 60 | if registerProcess := node.ProcessByName(c.registerName); registerProcess != nil { 61 | //if c.process.Self() == registerProcess.Self() 62 | node.UnregisterName(c.registerName) 63 | c.process.Send(registerProcess.Self(), etf.Atom("Extrusionline")) 64 | } 65 | 66 | retmsg.RoleInfo = roledata.RoleBase.ToPB() 67 | 68 | //绑定genserver name 69 | if error := c.process.RegisterName(c.registerName); error != nil { 70 | logrus.Errorf("绑定genserver name 失败: [%v] [%v] [%v]", error, c.registerName, accountinfo) 71 | } 72 | 73 | c.connectState = StatusGame 74 | roledata.RoleBase.Online = true 75 | commonstruct.StoreRoleData(roledata) 76 | c.roleData = roledata 77 | 78 | c.SendToClient(int32(pbaccount.MSG_ACCOUNT_Module), 79 | int32(pbaccount.MSG_ACCOUNT_Login), 80 | retmsg) 81 | } 82 | 83 | func (c *Client) registerAccount(msg *pbaccount.C2S_Register) { 84 | retmsg := &pbaccount.S2C_Register{ 85 | Retcode: 0, 86 | } 87 | //已登陆 88 | if c.connectState != StatusSockert { 89 | retmsg.Retcode = cfg.GetErrorCodeNumber("LOGIN") 90 | c.SendToClient(int32(pbaccount.MSG_ACCOUNT_Module), int32(pbaccount.MSG_ACCOUNT_Register), retmsg) 91 | } 92 | 93 | accountinfo := &commonstruct.AccountInfo{ 94 | Account: msg.Account, 95 | } 96 | //已注册 97 | if ok := accountinfo.GetAccountinfo(); ok { 98 | retmsg.Retcode = cfg.GetErrorCodeNumber("AccountExists") 99 | c.SendToClient(int32(pbaccount.MSG_ACCOUNT_Module), int32(pbaccount.MSG_ACCOUNT_Register), retmsg) 100 | return 101 | } 102 | 103 | //cdk已注册 104 | if msg.CDK != "" { 105 | if ok := commonstruct.GetCDKinfo(msg.CDK); ok { 106 | retmsg.Retcode = cfg.GetErrorCodeNumber("CDK") 107 | c.SendToClient(int32(pbaccount.MSG_ACCOUNT_Module), int32(pbaccount.MSG_ACCOUNT_Register), retmsg) 108 | return 109 | } 110 | } 111 | 112 | c.roleID = commonstruct.GetNewRoleID() 113 | if c.roleID == 0 { 114 | logrus.Error("系统错误 获取角色ID失败") 115 | retmsg.Retcode = cfg.GetErrorCodeNumber("SystemError") 116 | c.SendToClient(int32(pbaccount.MSG_ACCOUNT_Module), int32(pbaccount.MSG_ACCOUNT_Register), retmsg) 117 | return 118 | } 119 | 120 | accountinfo = &commonstruct.AccountInfo{ 121 | Account: msg.Account, 122 | Password: msg.Password, 123 | RegistrationSource: msg.Source, 124 | Equipment: msg.Equipment, 125 | CDK: msg.CDK, 126 | RoleUUID: c.process.Name(), 127 | RoleID: c.roleID, 128 | RegistrationTime: time.Now().Unix(), 129 | Settings: make(map[uint32]string), 130 | } 131 | 132 | db.InsertOne(db.AccountTable, accountinfo) 133 | 134 | //角色信息 135 | roleBase := &commonstruct.RoleBaseInfo{ 136 | ZoneID: commonstruct.ServerCfg.ServerID, 137 | RoleID: c.roleID, 138 | Name: "", 139 | HeadID: 0, 140 | Sex: 0, 141 | Level: 1, 142 | Exp: 0, 143 | PracticeTimestamp: time.Now().Unix(), 144 | AttributeValue: map[uint32]int64{}, 145 | BodyList: map[uint32]*commonstruct.RoleBodyInfo{}, 146 | CE: 0, 147 | ItemList: map[string]*commonstruct.ItemInfo{}, 148 | Online: true, 149 | State: 0, 150 | 151 | DirtyDataRecord: commonstruct.DirtyDataRecord{TableName: db.RoleBaseTable, DirtyDataList: map[string]primitive.E{}}, 152 | // DirtyDataList: map[string]primitive.E{}, 153 | } 154 | roleBase.CalculationProperties() 155 | db.InsertOne(db.RoleBaseTable, roleBase) 156 | 157 | //初始道具 158 | roleItems := &commonstruct.RoleItemlist{ 159 | RoleID: c.roleID, 160 | ItemList: map[string]*commonstruct.ItemInfo{}, 161 | DirtyDataRecord: commonstruct.DirtyDataRecord{TableName: db.RoleItemsTable, DirtyDataList: map[string]primitive.E{}}, 162 | } 163 | db.InsertOne(db.RoleItemsTable, roleItems) 164 | 165 | roledata := &commonstruct.RoleData{ 166 | Acconut: accountinfo, 167 | RoleBase: *roleBase, 168 | RoleItems: *roleItems, 169 | } 170 | c.connectState = StatusLogin 171 | commonstruct.StoreRoleData(roledata) 172 | 173 | c.SendToClient(int32(pbaccount.MSG_ACCOUNT_Module), 174 | int32(pbaccount.MSG_ACCOUNT_Register), retmsg) 175 | } 176 | 177 | func (c *Client) accountCreateRole(msg *pbaccount.C2S_CreateRole) { 178 | retmsg := &pbaccount.S2C_Login{ 179 | Retcode: 0, 180 | RoleInfo: &pbrole.Pb_RoleInfo{}, 181 | } 182 | //未登陆 183 | if c.connectState == StatusSockert { 184 | retmsg.Retcode = cfg.GetErrorCodeNumber("NOT_LOGIN") 185 | c.SendToClient(int32(pbaccount.MSG_ACCOUNT_Module), int32(pbaccount.MSG_ACCOUNT_CreateRole), retmsg) 186 | return 187 | } 188 | 189 | roledata := commonstruct.GetRoleAllData(c.roleID) 190 | //已创建账号 191 | if roledata.RoleBase.Name != "" { 192 | retmsg.Retcode = cfg.GetErrorCodeNumber("AccountExists") 193 | c.SendToClient(int32(pbaccount.MSG_ACCOUNT_Module), int32(pbaccount.MSG_ACCOUNT_CreateRole), retmsg) 194 | return 195 | } 196 | 197 | roledata.RoleBase.Name = msg.RoleName 198 | roledata.RoleBase.HeadID = msg.HeadID 199 | roledata.RoleBase.Sex = msg.Sex 200 | roledata.RoleBase.Exp = 0 201 | roledata.RoleBase.PracticeTimestamp = time.Now().Unix() 202 | roledata.RoleBase.Online = true 203 | roledata.RoleBase.SetDirtyData(primitive.E{Key: "name", Value: msg.RoleName}) 204 | roledata.RoleBase.SetDirtyData(primitive.E{Key: "headid", Value: msg.HeadID}) 205 | roledata.RoleBase.SetDirtyData(primitive.E{Key: "sex", Value: msg.Sex}) 206 | roledata.RoleBase.SetDirtyData(primitive.E{Key: "exp", Value: 0}) 207 | roledata.RoleBase.SetDirtyData(primitive.E{Key: "practicetimestamp", Value: time.Now().Unix()}) 208 | commonstruct.StoreRoleData(roledata) 209 | commonstruct.SaveRoleData(roledata) 210 | c.connectState = StatusGame 211 | c.roleData = roledata 212 | 213 | c.SendToClient(int32(pbaccount.MSG_ACCOUNT_Module), 214 | int32(pbaccount.MSG_ACCOUNT_CreateRole), 215 | &pbaccount.S2C_CreateRole{ 216 | Retcode: 0, 217 | RoleInfo: roledata.RoleBase.ToPB(), 218 | }) 219 | } 220 | 221 | //心跳 222 | func (c *Client) heartBeat(msg *protocol_base.C2S_HeartBeat) { 223 | c.SendToClient(int32(protocol_base.MSG_BASE_Module), 224 | int32(protocol_base.MSG_BASE_HeartBeat), 225 | &protocol_base.S2C_HeartBeat{ 226 | Timestamp: time.Now().Unix(), 227 | }) 228 | } 229 | -------------------------------------------------------------------------------- /gserver/clienconnect/client.go: -------------------------------------------------------------------------------- 1 | package clienconnect 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | "server/gserver/cfg" 7 | "server/gserver/commonstruct" 8 | "server/gserver/nodeManange" 9 | pbaccount "server/proto/account" 10 | pbitem "server/proto/item" 11 | "server/proto/protocol_base" 12 | pbrole "server/proto/role" 13 | "server/tools" 14 | "time" 15 | 16 | "github.com/ergo-services/ergo/etf" 17 | "github.com/ergo-services/ergo/gen" 18 | "github.com/sirupsen/logrus" 19 | "go.mongodb.org/mongo-driver/bson/primitive" 20 | "google.golang.org/protobuf/proto" 21 | "google.golang.org/protobuf/reflect/protoreflect" 22 | ) 23 | 24 | //Client 客户端连接 25 | type Client struct { 26 | process *gen.ServerProcess 27 | registerName string 28 | sendChan chan []byte 29 | infofunc map[int32]func(buf []byte) 30 | genServerStatus gen.ServerStatus 31 | 32 | roleID int32 //角色ID 33 | roleData *commonstruct.RoleData 34 | connectState userStatus //连接状态 35 | } 36 | 37 | type userStatus int32 38 | 39 | const ( 40 | //StatusSockert socker 连接状态 41 | StatusSockert userStatus = 0 42 | //StatusLogin 已登陆成功 43 | StatusLogin userStatus = 1 44 | //正常游戏中 45 | StatusGame userStatus = 2 46 | 47 | //StatusSqueezeOut 重复登陆 挤下线 48 | //StatusSqueezeOut userStatus = 2 49 | ) 50 | 51 | //===========GateGenHanderInterface 接口实现=============== 52 | func NewClient() *Client { 53 | client := &Client{} 54 | client.initMsgRoute() 55 | return client 56 | } 57 | 58 | func (c *Client) initMsgRoute() { 59 | //消息注册 60 | c.infofunc = make(map[int32]func(buf []byte)) 61 | //心跳 62 | c.infofunc[int32(protocol_base.MSG_BASE_HeartBeat)] = createRegisterFunc(c.heartBeat) 63 | 64 | //账号 65 | c.infofunc[int32(pbaccount.MSG_ACCOUNT_Login)] = createRegisterFunc(c.accountLogin) 66 | c.infofunc[int32(pbaccount.MSG_ACCOUNT_Register)] = createRegisterFunc(c.registerAccount) 67 | c.infofunc[int32(pbaccount.MSG_ACCOUNT_CreateRole)] = createRegisterFunc(c.accountCreateRole) 68 | //角色 69 | c.infofunc[int32(pbrole.MSG_ROLE_Upgrade)] = createRegisterFunc(c.upgrade) 70 | //道具 71 | c.infofunc[int32(pbitem.MSG_ITEM_GetBackpackInfo)] = createRegisterFunc(c.getBackpackInfo) 72 | 73 | c.genServerStatus = gen.ServerStatusOK 74 | } 75 | 76 | func (c *Client) InitHander(process *gen.ServerProcess, sendChan chan []byte) { 77 | c.process = process 78 | c.sendChan = sendChan 79 | 80 | } 81 | 82 | func (c *Client) MsgHander(module, method int32, buf []byte) { 83 | defer func() { 84 | if err := recover(); err != nil { 85 | var err string 86 | for i := 0; i < 10; i++ { 87 | pc, fn, line, _ := runtime.Caller(i) 88 | if line == 0 { 89 | break 90 | } 91 | err += fmt.Sprintf("funcname:[%v] fn:[%v] line:[%v] \n", runtime.FuncForPC(pc).Name(), fn, line) 92 | } 93 | logrus.Error("err: \n", err) 94 | } 95 | }() 96 | 97 | //禁用模块 98 | //next... 99 | 100 | if msgfunc := c.infofunc[method]; msgfunc != nil { 101 | if c.connectState == StatusGame || module == int32(pbaccount.MSG_ACCOUNT_Module) { 102 | msgfunc(buf) 103 | } else { 104 | logrus.Errorf("未登陆 [%v] [%v] [%v]", module, method, buf) 105 | } 106 | } else { 107 | logrus.Warnln("未注册的消息", module, method) 108 | } 109 | } 110 | 111 | func (c *Client) LoopHander() time.Duration { 112 | defer func() { 113 | if err := recover(); err != nil { 114 | var err string 115 | for i := 0; i < 10; i++ { 116 | pc, fn, line, _ := runtime.Caller(i) 117 | if line == 0 { 118 | break 119 | } 120 | err += fmt.Sprintf("funcname:[%v] fn:[%v] line:[%v] \n", runtime.FuncForPC(pc).Name(), fn, line) 121 | } 122 | logrus.Error("err: \n", err) 123 | } 124 | }() 125 | 126 | if c.connectState == StatusGame { 127 | rolebase := &c.roleData.RoleBase 128 | //logrus.Debugf("roledata.RoleBase.Exp: %v %v", c.roleID, roledata.RoleBase.Exp) 129 | nowtime := time.Now().Unix() 130 | num := (nowtime - rolebase.PracticeTimestamp) / 5 131 | if num > 0 && rolebase.Exp < rolebase.MaxExp { 132 | rolebase.PracticeTimestamp = nowtime 133 | if expcfg := cfg.GetLvExpInfo(rolebase.Level); expcfg != nil { 134 | expcfg := cfg.GetLvExpInfo(rolebase.Level) 135 | addexp := int64(expcfg.CycleEXP) * num 136 | rolebase.Exp += addexp 137 | if rolebase.Exp >= rolebase.MaxExp { 138 | rolebase.Exp = rolebase.MaxExp 139 | } 140 | 141 | rolebase.SetDirtyData(primitive.E{Key: "exp", Value: rolebase.Exp}) 142 | //commonstruct.SaveRoleData(roledata) 143 | //commonstruct.StoreRoleData(roledata) 144 | 145 | //加经验通知 146 | c.SendToClient(int32(pbrole.MSG_ROLE_Module), 147 | int32(pbrole.MSG_ROLE_AddExp), 148 | &pbrole.S2C_AddExp_S{Exp: rolebase.Exp, Addexp: addexp}) 149 | 150 | } else { 151 | logrus.Error("expcfg is nil:", rolebase.Level, rolebase) 152 | } 153 | } 154 | 155 | //logrus.Debug("loop role exp:", roledata.RoleBase.Exp, num) 156 | } 157 | 158 | return time.Second * 5 159 | } 160 | 161 | func (c *Client) HandleCall(message etf.Term) { 162 | 163 | } 164 | 165 | func (c *Client) HandleInfo(message etf.Term) { 166 | // switch info := message.(type) { 167 | // case etf.Tuple: 168 | // switch info[0].(string) { 169 | // case "BroadcastMsg": 170 | // module := info[1].(int32) 171 | // method := info[2].(int32) 172 | // buf := info[3].(proto.Message) 173 | // c.SendToClient(module, method, buf) 174 | // } 175 | // } 176 | } 177 | 178 | func (c *Client) GenServerStatus() gen.ServerStatus { 179 | return c.genServerStatus 180 | } 181 | 182 | func (c *Client) Terminate(reason string) { 183 | logrus.Debugf("client 关闭 [%v] roleid:[%v] [%v]", reason, c.roleID, c.process.Name()) 184 | switch reason { 185 | case "Extrusionline": //挤下线 不中断socket连接 ,只将状态设置成为登陆状态 186 | c.SendToClient(int32(protocol_base.MSG_BASE_Module), 187 | int32(protocol_base.MSG_BASE_NoticeMsg), 188 | &protocol_base.S2C_NoticeMsg_S{ 189 | Retcode: cfg.GetErrorCodeNumber(reason), 190 | NoticeMsg: reason, 191 | }) 192 | c.connectState = StatusSockert 193 | return 194 | } 195 | 196 | if c.connectState == StatusGame { 197 | 198 | c.roleData.RoleBase.Online = false 199 | c.roleData.RoleBase.OfflineTimestamp = time.Now().Unix() 200 | c.roleData.RoleBase.SetDirtyData(primitive.E{Key: "offlinetimestamp", Value: time.Now().Unix()}, 201 | primitive.E{Key: "online", Value: false}) 202 | //下线时保存数据 203 | commonstruct.StoreRoleData(c.roleData) 204 | commonstruct.SaveRoleData(c.roleData) 205 | 206 | //注销网关注册 207 | node := nodeManange.GetNode(nodeManange.GateNode) 208 | node.UnregisterName(c.registerName) 209 | c.registerName = "" 210 | c.process = nil 211 | c.sendChan = nil 212 | c.roleID = 0 213 | c.connectState = StatusSockert 214 | } 215 | } 216 | 217 | //========================== 218 | 219 | // //SendToClient 发送消息至客户端 220 | func (c *Client) SendToClient(module int32, method int32, pb proto.Message) { 221 | //logrus.Debugf("client send msg [%v] [%v] [%v]", module, method, pb) 222 | data, err := proto.Marshal(pb) 223 | if err != nil { 224 | logrus.Errorf("proto encode error[%v] [%v][%v] [%v]", err.Error(), module, method, pb) 225 | return 226 | } 227 | // msginfo := &common.NetworkMsg{} 228 | // msginfo.Module = module 229 | // msginfo.Method = method 230 | // msginfo.MsgBytes = data 231 | // msgdata, err := proto.Marshal(msginfo) 232 | // if err != nil { 233 | // logrus.Errorf("msg encode error[%s]\n", err.Error()) 234 | // } 235 | // gateGS.sendChan <- msgdata 236 | 237 | mldulebuf := tools.IntToBytes(module, 2) 238 | methodbuf := tools.IntToBytes(method, 2) 239 | c.sendChan <- tools.BytesCombine(mldulebuf, methodbuf, data) 240 | } 241 | 242 | //==========msg register ======= 243 | //消息注册 244 | func createRegisterFunc[T any](execfunc func(*T)) func(buf []byte) { 245 | return func(buf []byte) { 246 | info := new(T) 247 | err := decodeProto(info, buf) 248 | if err != nil { 249 | logrus.Errorf("decode error[%v]", err.Error()) 250 | } else { 251 | //logrus.Debugf("client msg:[%v] [%v]", info, tools.GoID()) 252 | execfunc(info) 253 | } 254 | } 255 | } 256 | 257 | //protobuf 解码 258 | func decodeProto(info interface{}, buf []byte) error { 259 | if data, ok := info.(protoreflect.ProtoMessage); ok { 260 | return proto.Unmarshal(buf, data) 261 | } 262 | return nil 263 | } 264 | -------------------------------------------------------------------------------- /network/network.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | import ( 4 | "context" 5 | "encoding/binary" 6 | "errors" 7 | "fmt" 8 | "io" 9 | "net" 10 | "os" 11 | "server/gserver/genServer" 12 | "server/tools" 13 | "sync" 14 | "sync/atomic" 15 | "time" 16 | 17 | "github.com/ergo-services/ergo/etf" 18 | "github.com/ergo-services/ergo/gen" 19 | "github.com/ergo-services/ergo/node" 20 | "github.com/google/uuid" 21 | "github.com/sirupsen/logrus" 22 | ) 23 | 24 | //ClientInterface client hander 25 | // type ClientInterface interface { 26 | // //OnConnect(sendchan chan []byte, packet int32, msgchan chan commonstruct.ProcessMsg, addr net.Addr) 27 | // OnClose() 28 | // OnMessage(module int32, method int32, buf []byte) 29 | // Send(module int32, method int32, pb proto.Message) 30 | // ProcessMessage(msg []byte) bool 31 | // } 32 | 33 | //NetInterface network 34 | type NetInterface interface { 35 | Start(n *NetWorkx) 36 | Close() 37 | } 38 | 39 | //NetWorkx 网络管理 40 | type NetWorkx struct { 41 | //tcp/udp/kcp 42 | src NetInterface 43 | 44 | //包长度0 2 4 45 | Packet int32 46 | //读取超时时间(秒) 47 | Readtimeout int32 48 | 49 | MsgTime int32 50 | MsgNum int32 51 | 52 | //tcp kcp 53 | NetType string 54 | //监听端口. 55 | Port int32 56 | //用户对象池 //nw.UserPool.Get().(*client).OnConnect() 57 | UserPool sync.Pool 58 | //CreateGenServerObj func() genServer.GateGenHanderInterface 59 | 60 | //启动成功后回调 61 | StartHook func() 62 | 63 | //新连接回调 64 | connectHook func() 65 | //连接关闭回调 66 | closedConnectHook func() 67 | //socket 关闭回调 68 | closeHook func() 69 | 70 | ConnectCount int32 //连接数 71 | MaxConnectNum int32 //最大连接数 72 | OpenConn bool //是否开启连接 73 | 74 | gateNode node.Node 75 | } 76 | 77 | //NewNetWorkX instance 78 | func NewNetWorkX(createObj func() genServer.GateGenHanderInterface, port, packet, readtimeout int32, nettype string, maxConnectNum, msgtime, msgnum int32, 79 | startHook, closeHook, connectHook, closedConnectHook func()) *NetWorkx { 80 | 81 | netWorkx := &NetWorkx{ 82 | Packet: packet, 83 | NetType: nettype, 84 | Port: port, 85 | UserPool: sync.Pool{ 86 | New: func() interface{} { 87 | return createObj() 88 | }}, 89 | //CreateGenServerObj: createObj, 90 | Readtimeout: readtimeout, 91 | MsgTime: msgtime, 92 | MsgNum: msgnum, 93 | StartHook: startHook, 94 | closeHook: closeHook, 95 | connectHook: connectHook, 96 | closedConnectHook: closedConnectHook, 97 | MaxConnectNum: maxConnectNum, 98 | OpenConn: false, 99 | } 100 | atomic.StoreInt32(&netWorkx.ConnectCount, 0) 101 | 102 | return netWorkx 103 | } 104 | 105 | //Start 启动网络服务 106 | func (n *NetWorkx) Start(gateNode node.Node) { 107 | n.gateNode = gateNode 108 | switch n.NetType { 109 | case "kcp": 110 | logrus.Info("NetWorkx [kcp] port:", n.Port) 111 | n.src = &KCPNetwork{} 112 | case "tcp": 113 | logrus.Info("NetWorkx [tcp] port:", n.Port) 114 | n.src = &TCPNetwork{} 115 | default: 116 | logrus.Info("NetWorkx default [tcp] port:", n.Port) 117 | n.src = new(TCPNetwork) 118 | } 119 | 120 | //start socket 121 | go n.src.Start(n) 122 | 123 | } 124 | 125 | func (n *NetWorkx) CreateProcess() (gen.Process, genServer.GateGenHanderInterface, chan []byte, error) { 126 | clientHander := n.UserPool.Get().(genServer.GateGenHanderInterface) 127 | 128 | uid, err := uuid.NewRandom() 129 | if err != nil { 130 | return nil, nil, nil, err 131 | } 132 | 133 | sendchan := make(chan []byte, 1) 134 | option := gen.ProcessOptions{ 135 | MailboxSize: 0, 136 | //Fallback: gen.ProcessFallback{}, 137 | } 138 | 139 | process, err := n.gateNode.Spawn(uid.String(), option, &genServer.GateGenServer{}, sendchan, clientHander) 140 | 141 | if err != nil { 142 | return nil, nil, nil, err 143 | } 144 | 145 | return process, clientHander, sendchan, nil 146 | } 147 | 148 | //HandleClient 消息处理 149 | func (n *NetWorkx) HandleClient(conn net.Conn) { 150 | process, clientHander, sendchan, err := n.CreateProcess() 151 | if err != nil { 152 | logrus.Error("createProcess err: [%v]", err) 153 | return 154 | } 155 | 156 | n.onConnect() 157 | atomic.AddInt32(&n.ConnectCount, 1) 158 | defer atomic.AddInt32(&n.ConnectCount, -1) 159 | 160 | defer n.UserPool.Put(clientHander) 161 | defer n.onClosedConnect() 162 | defer conn.Close() 163 | 164 | defer process.Send(process.Self(), etf.Term(etf.Tuple{etf.Atom("$gen_cast"), etf.Atom("SocketStop")})) 165 | //defer process.Send(process.Self(), etf.Atom("SocketStop")) 166 | 167 | // sendc := make(chan []byte, 1) 168 | //c.OnConnect(conn.RemoteAddr(), sendc) 169 | // go func(conn net.Conn) { 170 | // for { 171 | // select { 172 | // case buf := <-sendc: 173 | // le := IntToBytes(len(buf), n.Packet) 174 | // conn.Write(BytesCombine(le, buf)) 175 | // case <-ctx.Done(): 176 | // logrus.Debug("exit role sendGO") 177 | // return 178 | // } 179 | // } 180 | // }(conn) 181 | 182 | rootContext := context.Background() 183 | sendctx, sendcancelFunc := context.WithCancel(rootContext) 184 | defer sendcancelFunc() 185 | 186 | //readchan := make(chan []byte, 1) 187 | //sendchan := make(chan []byte, 1) 188 | // gamechan := make(chan commonstruct.ProcessMsg) 189 | // c.OnConnect(sendchan, n.Packet, gamechan, conn.RemoteAddr()) 190 | 191 | // for { 192 | // _, buf, e := UnpackToBlockFromReader(conn, n.Packet) 193 | // if e != nil { 194 | // logrus.Error("socket error:", e.Error()) 195 | // return 196 | // } 197 | // module := int32(binary.BigEndian.Uint16(buf[n.Packet : n.Packet+2])) 198 | // method := int32(binary.BigEndian.Uint16(buf[n.Packet+2 : n.Packet+4])) 199 | // c.OnMessage(module, method, buf[n.Packet+4:]) 200 | // //pb 消息拆包 201 | // // Decode protobuf -> buf[n.Packet:] 202 | // msginfo := &common.NetworkMsg{} 203 | // e = proto.Unmarshal(buf[n.Packet:], msginfo) 204 | // if e != nil { 205 | // logrus.Errorf("msg decode error[%s]", e.Error()) 206 | // msgdata, _ := proto.Marshal(&common.NetworkMsg{ 207 | // Module: 0, 208 | // Method: 1, 209 | // }) 210 | // conn.Write(msgdata) 211 | // } else { 212 | // c.OnMessage(msginfo.Module, msginfo.Method, msginfo.MsgBytes) 213 | // } 214 | // } 215 | 216 | go func(conn net.Conn) { 217 | for { 218 | select { 219 | case buf := <-sendchan: 220 | le := tools.IntToBytes(int32(len(buf)), n.Packet) 221 | conn.Write(tools.BytesCombine(le, buf)) 222 | case <-sendctx.Done(): 223 | //logrus.Debug("exit role sendGO") 224 | return 225 | } 226 | } 227 | }(conn) 228 | 229 | //go func(conn net.Conn, sendcancelFunc context.CancelFunc) { 230 | unix := time.Now().Unix() 231 | msgNum := 0 232 | for { 233 | //超时 234 | if n.Readtimeout != 0 { 235 | readtimeout := time.Second * time.Duration(n.Readtimeout) 236 | conn.SetReadDeadline(time.Now().Add(readtimeout)) 237 | } 238 | 239 | _, buf, e := UnpackToBlockFromReader(conn, n.Packet) 240 | if e != nil { 241 | switch e { 242 | case io.EOF: 243 | //logrus.Debug("socket closed:", e.Error()) 244 | default: 245 | logrus.Warn("socket closed:", e.Error()) 246 | } 247 | return 248 | } 249 | //readchan <- buf 250 | 251 | if len(buf) < int(n.Packet+4) { 252 | logrus.Debug("buf len:", len(buf), n.Packet+4, len(buf[n.Packet:])) 253 | return 254 | } 255 | 256 | module := int32(binary.BigEndian.Uint16(buf[n.Packet : n.Packet+2])) 257 | method := int32(binary.BigEndian.Uint16(buf[n.Packet+2 : n.Packet+4])) 258 | //process.Send(process.Self(), etf.Tuple{module, method, buf[n.Packet+4:]}) 259 | logrus.Debug("客户端发消息: ", process.Name()) 260 | err := process.Send(process.Self(), etf.Term(etf.Tuple{etf.Atom("$gen_cast"), etf.Tuple{module, method, buf[n.Packet+4:]}})) 261 | if err != nil { 262 | logrus.Warnf("send error:[%v] [%v] [%v]", method, err.Error(), n.ConnectCount) 263 | return 264 | } 265 | 266 | //间隔时间大于 N 分钟后 或者 接收到500条消息后 给连接送条信息 267 | now := time.Now().Unix() 268 | msgNum++ 269 | 270 | if now > unix+int64(n.MsgTime) || msgNum >= int(n.MsgNum) { 271 | //logrus.Infof("time:=======>[%v] [%v]", time.Now().Format("15:04:05"), msgNum) 272 | 273 | process.Send(process.Self(), etf.Term(etf.Tuple{etf.Atom("$gen_cast"), "timeloop"})) 274 | //process.Send(process.Self(), "timeloop") 275 | 276 | //gamechan <- commonstruct.ProcessMsg{MsgType: commonstruct.ProcessMsgTimeInterval, Data: msgNum} 277 | unix = now 278 | msgNum = 0 279 | } 280 | } 281 | 282 | // for { 283 | // select { 284 | // case buf := <-readchan: 285 | // module := int32(binary.BigEndian.Uint16(buf[n.Packet : n.Packet+2])) 286 | // method := int32(binary.BigEndian.Uint16(buf[n.Packet+2 : n.Packet+4])) 287 | // c.OnMessage(module, method, buf[n.Packet+4:]) 288 | // // case msg := <-gamechan: 289 | // // if !c.ProcessMessage(msg) { 290 | // // return 291 | // // } 292 | // case <-ctx.Done(): 293 | // //logrus.Debug("exit role goroutine") 294 | // return 295 | // } 296 | // } 297 | 298 | } 299 | 300 | func (n *NetWorkx) onConnect() { 301 | if n.connectHook != nil { 302 | n.connectHook() 303 | } 304 | } 305 | 306 | func (n *NetWorkx) onClosedConnect() { 307 | if n.closedConnectHook != nil { 308 | n.closedConnectHook() 309 | } 310 | } 311 | 312 | //Close 关闭 313 | func (n *NetWorkx) Close() { 314 | if n.closeHook != nil { 315 | n.closeHook() 316 | } 317 | n.src.Close() 318 | } 319 | 320 | func checkError(err error) { 321 | if err != nil { 322 | logrus.Errorf("Fatal error: %s", err.Error()) 323 | os.Exit(1) 324 | } 325 | } 326 | 327 | // UnpackToBlockFromReader -> unpack the first block from the reader. 328 | func UnpackToBlockFromReader(reader io.Reader, packet int32) (int32, []byte, error) { 329 | if reader == nil { 330 | return 0, nil, errors.New("reader is nil") 331 | } 332 | var info = make([]byte, packet) 333 | if e := readUntil(reader, info); e != nil { 334 | if e == io.EOF { 335 | return 0, nil, e 336 | } 337 | return 0, nil, e //errorx.Wrap(e) 338 | } 339 | 340 | length, e := LengthOf(info, packet) 341 | if e != nil { 342 | return 0, nil, e 343 | } 344 | var content = make([]byte, length) 345 | if e := readUntil(reader, content); e != nil { 346 | if e == io.EOF { 347 | return 0, nil, e 348 | } 349 | return 0, nil, e //errorx.Wrap(e) 350 | } 351 | 352 | return length, append(info, content...), nil 353 | } 354 | 355 | //LengthOf Length of the stream starting validly. 356 | //Length doesn't include length flag itself, it refers to a valid message length after it. 357 | func LengthOf(stream []byte, packet int32) (int32, error) { 358 | if len(stream) < int(packet) { 359 | return 0, errors.New(fmt.Sprint("stream lenth should be bigger than ", packet)) 360 | } 361 | 362 | switch packet { 363 | case 2: 364 | return int32(binary.BigEndian.Uint16(stream[0:2])), nil 365 | case 4: 366 | return int32(binary.BigEndian.Uint32(stream[0:4])), nil 367 | default: 368 | errstr := fmt.Sprintf("stream lenth seting error [packet: %v]", packet) 369 | return 0, errors.New(errstr) 370 | } 371 | 372 | } 373 | 374 | func readUntil(reader io.Reader, buf []byte) error { 375 | if len(buf) == 0 { 376 | return nil 377 | } 378 | var offset int 379 | for { 380 | n, e := reader.Read(buf[offset:]) 381 | if e != nil { 382 | if e == io.EOF { 383 | return e 384 | } 385 | return e //errorx.Wrap(e) 386 | } 387 | //logrus.Debugf("offset:[%s] buf[%s]", offset, len(buf)) 388 | offset += n 389 | if offset >= len(buf) { 390 | break 391 | } 392 | } 393 | return nil 394 | } 395 | -------------------------------------------------------------------------------- /proto/protocol_base/protocol_base.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // versions: 3 | // protoc-gen-go v1.27.1 4 | // protoc v3.6.1 5 | // source: proto/protocol_base.proto 6 | 7 | package protocol_base 8 | 9 | import ( 10 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 11 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 12 | reflect "reflect" 13 | sync "sync" 14 | ) 15 | 16 | const ( 17 | // Verify that this generated code is sufficiently up-to-date. 18 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 19 | // Verify that runtime/protoimpl is sufficiently up-to-date. 20 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 21 | ) 22 | 23 | //消息号 24 | type MSG_BASE int32 25 | 26 | const ( 27 | MSG_BASE_PLACEHOLDER_BASE MSG_BASE = 0 //占位 28 | //模块id 29 | MSG_BASE_Module MSG_BASE = 100 //基础模块 30 | //消息method id 31 | MSG_BASE_HeartBeat MSG_BASE = 101 //心跳 32 | MSG_BASE_NoticeMsg MSG_BASE = 103 //错误提示 33 | ) 34 | 35 | // Enum value maps for MSG_BASE. 36 | var ( 37 | MSG_BASE_name = map[int32]string{ 38 | 0: "PLACEHOLDER_BASE", 39 | 100: "Module", 40 | 101: "HeartBeat", 41 | 103: "NoticeMsg", 42 | } 43 | MSG_BASE_value = map[string]int32{ 44 | "PLACEHOLDER_BASE": 0, 45 | "Module": 100, 46 | "HeartBeat": 101, 47 | "NoticeMsg": 103, 48 | } 49 | ) 50 | 51 | func (x MSG_BASE) Enum() *MSG_BASE { 52 | p := new(MSG_BASE) 53 | *p = x 54 | return p 55 | } 56 | 57 | func (x MSG_BASE) String() string { 58 | return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) 59 | } 60 | 61 | func (MSG_BASE) Descriptor() protoreflect.EnumDescriptor { 62 | return file_proto_protocol_base_proto_enumTypes[0].Descriptor() 63 | } 64 | 65 | func (MSG_BASE) Type() protoreflect.EnumType { 66 | return &file_proto_protocol_base_proto_enumTypes[0] 67 | } 68 | 69 | func (x MSG_BASE) Number() protoreflect.EnumNumber { 70 | return protoreflect.EnumNumber(x) 71 | } 72 | 73 | // Deprecated: Use MSG_BASE.Descriptor instead. 74 | func (MSG_BASE) EnumDescriptor() ([]byte, []int) { 75 | return file_proto_protocol_base_proto_rawDescGZIP(), []int{0} 76 | } 77 | 78 | //心跳 1 79 | type C2S_HeartBeat struct { 80 | state protoimpl.MessageState 81 | sizeCache protoimpl.SizeCache 82 | unknownFields protoimpl.UnknownFields 83 | } 84 | 85 | func (x *C2S_HeartBeat) Reset() { 86 | *x = C2S_HeartBeat{} 87 | if protoimpl.UnsafeEnabled { 88 | mi := &file_proto_protocol_base_proto_msgTypes[0] 89 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 90 | ms.StoreMessageInfo(mi) 91 | } 92 | } 93 | 94 | func (x *C2S_HeartBeat) String() string { 95 | return protoimpl.X.MessageStringOf(x) 96 | } 97 | 98 | func (*C2S_HeartBeat) ProtoMessage() {} 99 | 100 | func (x *C2S_HeartBeat) ProtoReflect() protoreflect.Message { 101 | mi := &file_proto_protocol_base_proto_msgTypes[0] 102 | if protoimpl.UnsafeEnabled && x != nil { 103 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 104 | if ms.LoadMessageInfo() == nil { 105 | ms.StoreMessageInfo(mi) 106 | } 107 | return ms 108 | } 109 | return mi.MessageOf(x) 110 | } 111 | 112 | // Deprecated: Use C2S_HeartBeat.ProtoReflect.Descriptor instead. 113 | func (*C2S_HeartBeat) Descriptor() ([]byte, []int) { 114 | return file_proto_protocol_base_proto_rawDescGZIP(), []int{0} 115 | } 116 | 117 | type S2C_HeartBeat struct { 118 | state protoimpl.MessageState 119 | sizeCache protoimpl.SizeCache 120 | unknownFields protoimpl.UnknownFields 121 | 122 | Retcode int32 `protobuf:"zigzag32,1,opt,name=retcode,proto3" json:"retcode,omitempty"` 123 | Timestamp int64 `protobuf:"varint,2,opt,name=Timestamp,proto3" json:"Timestamp,omitempty"` //服务器时间 124 | } 125 | 126 | func (x *S2C_HeartBeat) Reset() { 127 | *x = S2C_HeartBeat{} 128 | if protoimpl.UnsafeEnabled { 129 | mi := &file_proto_protocol_base_proto_msgTypes[1] 130 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 131 | ms.StoreMessageInfo(mi) 132 | } 133 | } 134 | 135 | func (x *S2C_HeartBeat) String() string { 136 | return protoimpl.X.MessageStringOf(x) 137 | } 138 | 139 | func (*S2C_HeartBeat) ProtoMessage() {} 140 | 141 | func (x *S2C_HeartBeat) ProtoReflect() protoreflect.Message { 142 | mi := &file_proto_protocol_base_proto_msgTypes[1] 143 | if protoimpl.UnsafeEnabled && x != nil { 144 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 145 | if ms.LoadMessageInfo() == nil { 146 | ms.StoreMessageInfo(mi) 147 | } 148 | return ms 149 | } 150 | return mi.MessageOf(x) 151 | } 152 | 153 | // Deprecated: Use S2C_HeartBeat.ProtoReflect.Descriptor instead. 154 | func (*S2C_HeartBeat) Descriptor() ([]byte, []int) { 155 | return file_proto_protocol_base_proto_rawDescGZIP(), []int{1} 156 | } 157 | 158 | func (x *S2C_HeartBeat) GetRetcode() int32 { 159 | if x != nil { 160 | return x.Retcode 161 | } 162 | return 0 163 | } 164 | 165 | func (x *S2C_HeartBeat) GetTimestamp() int64 { 166 | if x != nil { 167 | return x.Timestamp 168 | } 169 | return 0 170 | } 171 | 172 | //错误提示消息 173 | type S2C_NoticeMsg_S struct { 174 | state protoimpl.MessageState 175 | sizeCache protoimpl.SizeCache 176 | unknownFields protoimpl.UnknownFields 177 | 178 | Retcode int32 `protobuf:"zigzag32,1,opt,name=retcode,proto3" json:"retcode,omitempty"` 179 | NoticeMsg string `protobuf:"bytes,2,opt,name=NoticeMsg,proto3" json:"NoticeMsg,omitempty"` 180 | } 181 | 182 | func (x *S2C_NoticeMsg_S) Reset() { 183 | *x = S2C_NoticeMsg_S{} 184 | if protoimpl.UnsafeEnabled { 185 | mi := &file_proto_protocol_base_proto_msgTypes[2] 186 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 187 | ms.StoreMessageInfo(mi) 188 | } 189 | } 190 | 191 | func (x *S2C_NoticeMsg_S) String() string { 192 | return protoimpl.X.MessageStringOf(x) 193 | } 194 | 195 | func (*S2C_NoticeMsg_S) ProtoMessage() {} 196 | 197 | func (x *S2C_NoticeMsg_S) ProtoReflect() protoreflect.Message { 198 | mi := &file_proto_protocol_base_proto_msgTypes[2] 199 | if protoimpl.UnsafeEnabled && x != nil { 200 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 201 | if ms.LoadMessageInfo() == nil { 202 | ms.StoreMessageInfo(mi) 203 | } 204 | return ms 205 | } 206 | return mi.MessageOf(x) 207 | } 208 | 209 | // Deprecated: Use S2C_NoticeMsg_S.ProtoReflect.Descriptor instead. 210 | func (*S2C_NoticeMsg_S) Descriptor() ([]byte, []int) { 211 | return file_proto_protocol_base_proto_rawDescGZIP(), []int{2} 212 | } 213 | 214 | func (x *S2C_NoticeMsg_S) GetRetcode() int32 { 215 | if x != nil { 216 | return x.Retcode 217 | } 218 | return 0 219 | } 220 | 221 | func (x *S2C_NoticeMsg_S) GetNoticeMsg() string { 222 | if x != nil { 223 | return x.NoticeMsg 224 | } 225 | return "" 226 | } 227 | 228 | var File_proto_protocol_base_proto protoreflect.FileDescriptor 229 | 230 | var file_proto_protocol_base_proto_rawDesc = []byte{ 231 | 0x0a, 0x19, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 232 | 0x5f, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0d, 0x70, 0x72, 0x6f, 233 | 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x5f, 0x62, 0x61, 0x73, 0x65, 0x22, 0x0f, 0x0a, 0x0d, 0x63, 0x32, 234 | 0x73, 0x5f, 0x48, 0x65, 0x61, 0x72, 0x74, 0x42, 0x65, 0x61, 0x74, 0x22, 0x47, 0x0a, 0x0d, 0x73, 235 | 0x32, 0x63, 0x5f, 0x48, 0x65, 0x61, 0x72, 0x74, 0x42, 0x65, 0x61, 0x74, 0x12, 0x18, 0x0a, 0x07, 236 | 0x72, 0x65, 0x74, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x11, 0x52, 0x07, 0x72, 237 | 0x65, 0x74, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 238 | 0x61, 0x6d, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x54, 0x69, 0x6d, 0x65, 0x73, 239 | 0x74, 0x61, 0x6d, 0x70, 0x22, 0x49, 0x0a, 0x0f, 0x73, 0x32, 0x63, 0x5f, 0x4e, 0x6f, 0x74, 0x69, 240 | 0x63, 0x65, 0x4d, 0x73, 0x67, 0x5f, 0x53, 0x12, 0x18, 0x0a, 0x07, 0x72, 0x65, 0x74, 0x63, 0x6f, 241 | 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x11, 0x52, 0x07, 0x72, 0x65, 0x74, 0x63, 0x6f, 0x64, 242 | 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x4e, 0x6f, 0x74, 0x69, 0x63, 0x65, 0x4d, 0x73, 0x67, 0x18, 0x02, 243 | 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x4e, 0x6f, 0x74, 0x69, 0x63, 0x65, 0x4d, 0x73, 0x67, 0x2a, 244 | 0x4a, 0x0a, 0x08, 0x4d, 0x53, 0x47, 0x5f, 0x42, 0x41, 0x53, 0x45, 0x12, 0x14, 0x0a, 0x10, 0x50, 245 | 0x4c, 0x41, 0x43, 0x45, 0x48, 0x4f, 0x4c, 0x44, 0x45, 0x52, 0x5f, 0x42, 0x41, 0x53, 0x45, 0x10, 246 | 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x10, 0x64, 0x12, 0x0d, 0x0a, 247 | 0x09, 0x48, 0x65, 0x61, 0x72, 0x74, 0x42, 0x65, 0x61, 0x74, 0x10, 0x65, 0x12, 0x0d, 0x0a, 0x09, 248 | 0x4e, 0x6f, 0x74, 0x69, 0x63, 0x65, 0x4d, 0x73, 0x67, 0x10, 0x67, 0x42, 0x15, 0x5a, 0x13, 0x70, 249 | 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x5f, 0x62, 0x61, 250 | 0x73, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 251 | } 252 | 253 | var ( 254 | file_proto_protocol_base_proto_rawDescOnce sync.Once 255 | file_proto_protocol_base_proto_rawDescData = file_proto_protocol_base_proto_rawDesc 256 | ) 257 | 258 | func file_proto_protocol_base_proto_rawDescGZIP() []byte { 259 | file_proto_protocol_base_proto_rawDescOnce.Do(func() { 260 | file_proto_protocol_base_proto_rawDescData = protoimpl.X.CompressGZIP(file_proto_protocol_base_proto_rawDescData) 261 | }) 262 | return file_proto_protocol_base_proto_rawDescData 263 | } 264 | 265 | var file_proto_protocol_base_proto_enumTypes = make([]protoimpl.EnumInfo, 1) 266 | var file_proto_protocol_base_proto_msgTypes = make([]protoimpl.MessageInfo, 3) 267 | var file_proto_protocol_base_proto_goTypes = []interface{}{ 268 | (MSG_BASE)(0), // 0: protocol_base.MSG_BASE 269 | (*C2S_HeartBeat)(nil), // 1: protocol_base.c2s_HeartBeat 270 | (*S2C_HeartBeat)(nil), // 2: protocol_base.s2c_HeartBeat 271 | (*S2C_NoticeMsg_S)(nil), // 3: protocol_base.s2c_NoticeMsg_S 272 | } 273 | var file_proto_protocol_base_proto_depIdxs = []int32{ 274 | 0, // [0:0] is the sub-list for method output_type 275 | 0, // [0:0] is the sub-list for method input_type 276 | 0, // [0:0] is the sub-list for extension type_name 277 | 0, // [0:0] is the sub-list for extension extendee 278 | 0, // [0:0] is the sub-list for field type_name 279 | } 280 | 281 | func init() { file_proto_protocol_base_proto_init() } 282 | func file_proto_protocol_base_proto_init() { 283 | if File_proto_protocol_base_proto != nil { 284 | return 285 | } 286 | if !protoimpl.UnsafeEnabled { 287 | file_proto_protocol_base_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { 288 | switch v := v.(*C2S_HeartBeat); i { 289 | case 0: 290 | return &v.state 291 | case 1: 292 | return &v.sizeCache 293 | case 2: 294 | return &v.unknownFields 295 | default: 296 | return nil 297 | } 298 | } 299 | file_proto_protocol_base_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { 300 | switch v := v.(*S2C_HeartBeat); i { 301 | case 0: 302 | return &v.state 303 | case 1: 304 | return &v.sizeCache 305 | case 2: 306 | return &v.unknownFields 307 | default: 308 | return nil 309 | } 310 | } 311 | file_proto_protocol_base_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { 312 | switch v := v.(*S2C_NoticeMsg_S); i { 313 | case 0: 314 | return &v.state 315 | case 1: 316 | return &v.sizeCache 317 | case 2: 318 | return &v.unknownFields 319 | default: 320 | return nil 321 | } 322 | } 323 | } 324 | type x struct{} 325 | out := protoimpl.TypeBuilder{ 326 | File: protoimpl.DescBuilder{ 327 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 328 | RawDescriptor: file_proto_protocol_base_proto_rawDesc, 329 | NumEnums: 1, 330 | NumMessages: 3, 331 | NumExtensions: 0, 332 | NumServices: 0, 333 | }, 334 | GoTypes: file_proto_protocol_base_proto_goTypes, 335 | DependencyIndexes: file_proto_protocol_base_proto_depIdxs, 336 | EnumInfos: file_proto_protocol_base_proto_enumTypes, 337 | MessageInfos: file_proto_protocol_base_proto_msgTypes, 338 | }.Build() 339 | File_proto_protocol_base_proto = out.File 340 | file_proto_protocol_base_proto_rawDesc = nil 341 | file_proto_protocol_base_proto_goTypes = nil 342 | file_proto_protocol_base_proto_depIdxs = nil 343 | } 344 | --------------------------------------------------------------------------------