├── api └── proto │ ├── protoc.exe │ ├── build.bat │ ├── list.proto │ ├── logger.proto │ ├── property.proto │ ├── room.proto │ ├── table.proto │ ├── gameddz.proto │ ├── config.proto │ ├── types.proto │ ├── center.proto │ ├── lobby.proto │ └── gateway.proto ├── scripts ├── windows │ ├── CreateNewService.bat │ ├── Cleanup.bat │ └── Startup.bat ├── template │ ├── main.txt │ └── business.txt ├── linux │ ├── CreateNewService.sh │ ├── Shutdown.sh │ ├── Startup.sh │ └── Cleanup.sh └── database │ ├── mango_property.sql │ └── mango_logic.sql ├── configs └── config │ ├── room-2000.json │ ├── table-1000.json │ ├── daemon-300.json │ ├── list.json │ ├── property.json │ ├── robot-3000.json │ ├── gateway-100.json │ ├── lobby.json │ ├── config.json │ └── center.json ├── cmd ├── list │ ├── main.go │ └── business │ │ └── business.go ├── room │ ├── main.go │ └── business │ │ ├── player │ │ └── player.go │ │ └── table │ │ └── table.go ├── center │ ├── main.go │ └── business │ │ └── http_handler.go ├── daemon │ └── main.go ├── lobby │ ├── main.go │ └── business │ │ └── business.go ├── logger │ ├── main.go │ └── business │ │ └── business.go ├── robot │ ├── main.go │ └── business │ │ ├── game │ │ ├── game.go │ │ └── ddz │ │ │ └── ddz.go │ │ ├── business.go │ │ └── player │ │ └── agent.go ├── table │ ├── main.go │ └── business │ │ ├── player │ │ └── player.go │ │ ├── table │ │ ├── ddz │ │ │ ├── logic.go │ │ │ └── table_sink.go │ │ └── table.go │ │ └── business.go ├── gateway │ └── main.go ├── property │ ├── main.go │ └── business │ │ └── business.go └── config │ ├── main.go │ ├── conf │ └── json.go │ └── business │ └── apolloDefaultLogger.go ├── .dockerignore ├── pkg ├── util │ ├── semaphore.go │ ├── timehelper │ │ ├── timeAfter.go │ │ ├── timeFormat.go │ │ └── timeTicker.go │ ├── errorhelper │ │ └── recover.go │ ├── file.go │ ├── debugInfo.go │ ├── gorsa │ │ ├── rsa_generate.go │ │ ├── rsa_test.go │ │ └── rsa.go │ ├── colorprint │ │ ├── color_darwin.go │ │ ├── color_linux.go │ │ └── color_windows.go │ ├── string.go │ ├── stringtransform.go │ ├── goaes │ │ ├── aes_test.go │ │ └── aes.go │ ├── example_test.go │ ├── rand.go │ ├── map.go │ ├── deepcopy.go │ ├── common.go │ └── dingding │ │ └── dingding.go ├── network │ ├── conn.go │ ├── processor.go │ ├── agent.go │ ├── grpc_server.go │ ├── tcp_conn.go │ ├── ws_conn.go │ ├── tcp_client.go │ ├── ws_client.go │ ├── protobuf │ │ └── protobuf.go │ ├── http_server.go │ ├── ws_server.go │ ├── tcp_server.go │ ├── tcp_msg.go │ └── json │ │ └── json.go ├── log │ └── example_test.go ├── go │ ├── example_test.go │ └── go.go ├── database │ ├── mchelper │ │ └── mchelper.go │ ├── mgohelper │ │ └── mgohelper.go │ ├── database.go │ └── sqlhelper │ │ └── dataresult.go ├── timer │ ├── example_test.go │ └── timer.go ├── console │ ├── console.go │ └── command.go ├── chanrpc │ └── example_test.go ├── amqp │ └── rabbitmq.go ├── module │ └── skeleton.go ├── conf │ └── conf.go └── gate │ └── clients.go ├── .gitignore ├── go.mod └── README.md /api/proto/protoc.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoldBaby5511/mango/HEAD/api/proto/protoc.exe -------------------------------------------------------------------------------- /scripts/windows/CreateNewService.bat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoldBaby5511/mango/HEAD/scripts/windows/CreateNewService.bat -------------------------------------------------------------------------------- /configs/config/room-2000.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": { 3 | "日志服务器地址": "127.0.0.1:10001", 4 | "桌子服务AppID": "1000" 5 | } 6 | } -------------------------------------------------------------------------------- /configs/config/table-1000.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": { 3 | "日志服务器地址": "127.0.0.1:10001", 4 | "游戏类型": "666", 5 | "桌子数量": "2000" 6 | } 7 | } -------------------------------------------------------------------------------- /scripts/windows/Cleanup.bat: -------------------------------------------------------------------------------- 1 | cd ../../cmd 2 | for /R %%i in (.) do (cd %%i 3 | del /s *.exe 4 | del /s *.json 5 | rd /s /q log 6 | rd /s /q configs 7 | cd ..) -------------------------------------------------------------------------------- /configs/config/daemon-300.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": { 3 | "LogScreenPrint": "0", 4 | "日志服务器地址": "127.0.0.1:15005", 5 | "服务路径": "/home//ServerBin/" 6 | } 7 | } -------------------------------------------------------------------------------- /cmd/list/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | _ "mango/cmd/list/business" 5 | "mango/pkg/gate" 6 | ) 7 | 8 | func main() { 9 | gate.Start("list") 10 | } 11 | -------------------------------------------------------------------------------- /cmd/room/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | _ "mango/cmd/room/business" 5 | "mango/pkg/gate" 6 | ) 7 | 8 | func main() { 9 | gate.Start("room") 10 | } 11 | -------------------------------------------------------------------------------- /configs/config/list.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": { 3 | "LogScreenPrint": "1", 4 | "title": "列表服务", 5 | "日志服务器地址": "127.0.0.1:10001" 6 | } 7 | } -------------------------------------------------------------------------------- /configs/config/property.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": { 3 | "LogScreenPrint": "1", 4 | "title": "财富服务", 5 | "日志服务器地址": "127.0.0.1:10001" 6 | } 7 | } -------------------------------------------------------------------------------- /cmd/center/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | _ "mango/cmd/center/business" 5 | "mango/pkg/gate" 6 | ) 7 | 8 | func main() { 9 | gate.Start("center") 10 | } 11 | -------------------------------------------------------------------------------- /cmd/daemon/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | _ "mango/cmd/daemon/business" 5 | "mango/pkg/gate" 6 | ) 7 | 8 | func main() { 9 | gate.Start("daemon") 10 | } 11 | -------------------------------------------------------------------------------- /cmd/lobby/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | _ "mango/cmd/lobby/business" 5 | "mango/pkg/gate" 6 | ) 7 | 8 | func main() { 9 | gate.Start("login") 10 | } 11 | -------------------------------------------------------------------------------- /cmd/logger/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | _ "mango/cmd/logger/business" 5 | "mango/pkg/gate" 6 | ) 7 | 8 | func main() { 9 | gate.Start("logger") 10 | } 11 | -------------------------------------------------------------------------------- /cmd/robot/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | _ "mango/cmd/robot/business" 5 | "mango/pkg/gate" 6 | ) 7 | 8 | func main() { 9 | gate.Start("robot") 10 | } 11 | -------------------------------------------------------------------------------- /cmd/table/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | _ "mango/cmd/table/business" 5 | "mango/pkg/gate" 6 | ) 7 | 8 | func main() { 9 | gate.Start("table") 10 | } 11 | -------------------------------------------------------------------------------- /cmd/gateway/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | _ "mango/cmd/gateway/business" 5 | "mango/pkg/gate" 6 | ) 7 | 8 | func main() { 9 | gate.Start("gateway") 10 | } 11 | -------------------------------------------------------------------------------- /cmd/property/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | _ "mango/cmd/property/business" 5 | "mango/pkg/gate" 6 | ) 7 | 8 | func main() { 9 | gate.Start("property") 10 | } 11 | -------------------------------------------------------------------------------- /scripts/template/main.txt: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | _ "mango/cmd/template/business" 5 | "mango/pkg/gate" 6 | ) 7 | 8 | func main() { 9 | gate.Start("template") 10 | } 11 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | .idea 3 | *.exe 4 | .gitignore 5 | .bat 6 | LICENSE 7 | README.md 8 | examples/ 9 | cmd/gateway/log/ 10 | cmd/logger/log/ 11 | cmd/login/log/ 12 | cmd/router/log/ 13 | -------------------------------------------------------------------------------- /cmd/config/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | _ "mango/cmd/config/business" 5 | _ "mango/cmd/config/conf" 6 | "mango/pkg/gate" 7 | ) 8 | 9 | func main() { 10 | gate.Start("config") 11 | } 12 | -------------------------------------------------------------------------------- /configs/config/robot-3000.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": { 3 | "LogScreenPrint": "1", 4 | "title": "机器人服务", 5 | "日志服务器地址": "127.0.0.1:10001", 6 | "工作模式": "1", 7 | "机器人数量": "1000" 8 | } 9 | } -------------------------------------------------------------------------------- /pkg/util/semaphore.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | type Semaphore chan struct{} 4 | 5 | func MakeSemaphore(n int) Semaphore { 6 | return make(Semaphore, n) 7 | } 8 | 9 | func (s Semaphore) Acquire() { 10 | s <- struct{}{} 11 | } 12 | 13 | func (s Semaphore) Release() { 14 | <-s 15 | } 16 | -------------------------------------------------------------------------------- /pkg/network/conn.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | import ( 4 | "net" 5 | ) 6 | 7 | type Conn interface { 8 | ReadMsg() (BaseMessage, []byte, error) 9 | WriteMsg(appType, cmdId uint16, msgData, otherData []byte) error 10 | LocalAddr() net.Addr 11 | RemoteAddr() net.Addr 12 | Close() 13 | Destroy() 14 | } 15 | -------------------------------------------------------------------------------- /pkg/util/timehelper/timeAfter.go: -------------------------------------------------------------------------------- 1 | package timehelper 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | func AfterTimeFunc(d time.Time, fn func()) *time.Timer { 8 | return time.AfterFunc(time.Duration(d.Sub(time.Now()).Nanoseconds())*time.Nanosecond, func() { 9 | if fn != nil { 10 | fn() 11 | } 12 | }) 13 | } 14 | -------------------------------------------------------------------------------- /scripts/template/business.txt: -------------------------------------------------------------------------------- 1 | package business 2 | 3 | import ( 4 | g "mango/pkg/gate" 5 | ) 6 | 7 | func init() { 8 | g.EventRegister(g.ConnectSuccess, connectSuccess) 9 | g.EventRegister(g.Disconnect, disconnect) 10 | } 11 | 12 | func connectSuccess(args []interface{}) { 13 | } 14 | 15 | func disconnect(args []interface{}) { 16 | } -------------------------------------------------------------------------------- /api/proto/build.bat: -------------------------------------------------------------------------------- 1 | echo "go proto" 2 | @.\protoc.exe --go_out=.. lobby.proto 3 | @.\protoc.exe --go_out=.. types.proto 4 | @.\protoc.exe --go_out=.. room.proto 5 | @.\protoc.exe --go_out=.. table.proto 6 | @.\protoc.exe --go_out=.. gameddz.proto 7 | @.\protoc.exe --go_out=.. list.proto 8 | @.\protoc.exe --go_out=.. property.proto 9 | 10 | pause 11 | -------------------------------------------------------------------------------- /pkg/network/processor.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | type Processor interface { 4 | // must goroutine safe 5 | Route(args ...interface{}) error 6 | // must goroutine safe 7 | Unmarshal(appType uint16, cmdId uint16, data []byte) (interface{}, interface{}, error) 8 | // must goroutine safe 9 | Marshal(msg interface{}) ([][]byte, error) 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.exe 3 | *.DS_Store 4 | cmd/center/center 5 | cmd/config/config 6 | cmd/gateway/gateway 7 | cmd/list/list 8 | cmd/logger/logger 9 | cmd/login/login 10 | cmd/property/property 11 | cmd/robot/robot 12 | cmd/room/room 13 | cmd/table/table 14 | examples/ 15 | cmd/gateway/log/ 16 | cmd/logger/log/ 17 | cmd/login/log/ 18 | cmd/config/log/ 19 | -------------------------------------------------------------------------------- /scripts/linux/CreateNewService.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo -n "请输入新服务名,appName=" 4 | read appName 5 | 6 | cd ../.. 7 | cp -r scripts/template cmd 8 | mv cmd/template cmd/$appName 9 | cd cmd/$appName 10 | mkdir business 11 | mv business.txt business/business.go 12 | mv main.txt main.go 13 | sed -i "s/template/$appName/g" `grep "template" -rl ./` 14 | echo "新服务 $appName 创建完成" -------------------------------------------------------------------------------- /scripts/linux/Shutdown.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | KillApp(){ 4 | echo "查找 $1 " 5 | pid=$(ps -ef|grep ./$1|grep Type=|grep -v grep|awk '{print $2}') 6 | echo $pid 7 | kill -9 $pid 8 | } 9 | 10 | KillApp logger 11 | KillApp center 12 | KillApp config 13 | KillApp gateway 14 | KillApp lobby 15 | KillApp list 16 | KillApp property 17 | KillApp table 18 | KillApp room 19 | KillApp robot 20 | -------------------------------------------------------------------------------- /pkg/util/errorhelper/recover.go: -------------------------------------------------------------------------------- 1 | package errorhelper 2 | 3 | import ( 4 | "mango/pkg/log" 5 | "runtime/debug" 6 | ) 7 | 8 | func Recover() { 9 | if err := recover(); err != nil { 10 | log.Error("", "Recover err=%v,stack=%v\r\n", err, string(debug.Stack())) 11 | } 12 | } 13 | 14 | func RecoverWarn() { 15 | if err := recover(); err != nil { 16 | log.Debug("", "Recover Warn:err=%v", err) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /configs/config/gateway-100.json: -------------------------------------------------------------------------------- 1 | { 2 | "appId": "", 3 | "cluster": "", 4 | "namespaceName": "", 5 | "releaseKey": "", 6 | "configurations": { 7 | "LogScreenPrint": "1", 8 | "appid": "100", 9 | "router地址": "", 10 | "title": "网关0", 11 | "内存限制": "0", 12 | "开始监控连接数量": "5000", 13 | "心跳间隔": "600", 14 | "日志服务器地址": "127.0.0.1:10001", 15 | "监听地址": "", 16 | "监控间隔": "1000" 17 | } 18 | } -------------------------------------------------------------------------------- /cmd/table/business/player/player.go: -------------------------------------------------------------------------------- 1 | package player 2 | 3 | const ( 4 | NilState uint32 = 0 5 | HandsUpState uint32 = 1 6 | PlayingState uint32 = 2 7 | SitdownState uint32 = 3 8 | ) 9 | 10 | type Player struct { 11 | UserId uint64 12 | State uint32 13 | TableId uint64 14 | SeatId uint32 15 | GateConnId uint64 16 | SrcAppId uint32 17 | } 18 | 19 | func NewPlayer() *Player { 20 | p := new(Player) 21 | p.UserId = 0 22 | p.State = NilState 23 | return p 24 | } 25 | -------------------------------------------------------------------------------- /cmd/room/business/player/player.go: -------------------------------------------------------------------------------- 1 | package player 2 | 3 | const ( 4 | NilState uint32 = 0 5 | StandingInRoom uint32 = 1 6 | HandsUpState uint32 = 2 7 | PlayingState uint32 = 3 8 | ) 9 | 10 | type Player struct { 11 | UserID uint64 12 | State uint32 13 | TableServiceId uint32 14 | TableId uint64 15 | SeatId uint32 16 | GateConnId uint64 17 | } 18 | 19 | func NewPlayer(userID uint64) *Player { 20 | p := new(Player) 21 | p.UserID = userID 22 | p.State = NilState 23 | return p 24 | } 25 | -------------------------------------------------------------------------------- /pkg/util/timehelper/timeFormat.go: -------------------------------------------------------------------------------- 1 | package timehelper 2 | 3 | import "time" 4 | 5 | const ( 6 | //默认时间格式 7 | Default = "2006-01-02 15:04:05" 8 | //只精确到小时 9 | Hour = "2006-01-02 15" 10 | //只精确到天 11 | Day = "2006-01-02" 12 | //短日期格式 13 | ShortDay = "20060102" 14 | //短时间格式 15 | ShortDateTime = "2006-01-02 15:04" 16 | ) 17 | 18 | func GetZeroTime(d time.Time) time.Time { 19 | return time.Date(d.Year(), d.Month(), d.Day(), 0, 0, 0, 0, d.Location()) 20 | } 21 | 22 | func GetNextZeroTime() time.Time { 23 | return GetZeroTime(time.Now().Add(24 * time.Hour)) 24 | } 25 | -------------------------------------------------------------------------------- /scripts/linux/Startup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | cd ../.. 3 | cp -r configs cmd/config 4 | 5 | build(){ 6 | echo "开始编译 $1 " 7 | cd cmd/$1/ 8 | go build 9 | echo "编译完成,启动 $1 " 10 | nohup ./$1 $2 $3 1>log.log 2>err.log & 11 | cd ../.. 12 | } 13 | 14 | build logger -Type=1 -Id=1 15 | build center -Type=2 -Id=50 16 | build config -Type=3 -Id=60 17 | build gateway -Type=4 -Id=100 18 | build lobby -Type=5 -Id=70 19 | build list -Type=6 -Id=80 20 | build property -Type=7 -Id=90 21 | build table -Type=8 -Id=1000 22 | build room -Type=9 -Id=2000 23 | build robot -Type=10 -Id=3000 24 | -------------------------------------------------------------------------------- /pkg/util/timehelper/timeTicker.go: -------------------------------------------------------------------------------- 1 | package timehelper 2 | 3 | import ( 4 | "mango/pkg/util/errorhelper" 5 | "time" 6 | ) 7 | 8 | func NewTickerSecond(second time.Duration, fn func()) { 9 | newTicker(second, time.Second, fn) 10 | } 11 | 12 | func NewTicker(millisecond time.Duration, fn func()) { 13 | newTicker(millisecond, time.Millisecond, fn) 14 | } 15 | 16 | func newTicker(t time.Duration, unit time.Duration, fn func()) { 17 | defer errorhelper.Recover() 18 | 19 | timer := time.NewTicker(t * unit) 20 | for { 21 | select { 22 | case <-timer.C: 23 | fn() 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /pkg/util/file.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "errors" 5 | "os" 6 | "os/exec" 7 | "path/filepath" 8 | "strings" 9 | ) 10 | 11 | func GetCurrentPath() (string, error) { 12 | file, err := exec.LookPath(os.Args[0]) 13 | if err != nil { 14 | return "", err 15 | } 16 | path, err := filepath.Abs(file) 17 | if err != nil { 18 | return "", err 19 | } 20 | i := strings.LastIndex(path, "/") 21 | if i < 0 { 22 | i = strings.LastIndex(path, "\\") 23 | } 24 | if i < 0 { 25 | return "", errors.New(`error: Can't find "/" or "\".`) 26 | } 27 | return string(path[0 : i+1]), nil 28 | } 29 | -------------------------------------------------------------------------------- /scripts/linux/Cleanup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | find ../../cmd -name *.log -type f -print -exec rm -rf {} \; 3 | find ../../cmd -name *.bat -type f -print -exec rm -rf {} \; 4 | find ../../cmd -name log -type d -print -exec rm -rf {} \; 5 | rm -rf ../../cmd/center/center 6 | rm -rf ../../cmd/config/config 7 | rm -rf ../../cmd/config/configs 8 | rm -rf ../../cmd/gateway/gateway 9 | rm -rf ../../cmd/list/list 10 | rm -rf ../../cmd/logger/logger 11 | rm -rf ../../cmd/login/login 12 | rm -rf ../../cmd/property/property 13 | rm -rf ../../cmd/robot/robot 14 | rm -rf ../../cmd/room/room 15 | rm -rf ../../cmd/table/table -------------------------------------------------------------------------------- /pkg/log/example_test.go: -------------------------------------------------------------------------------- 1 | package log_test 2 | 3 | import ( 4 | "mango/pkg/log" 5 | ) 6 | 7 | func Example() { 8 | name := "Leaf" 9 | 10 | log.Debug("log", "My name is %v", name) 11 | log.Info("log", "My name is %v", name) 12 | log.Error("log", "My name is %v", name) 13 | // log.Fatal("My name is %v", name) 14 | 15 | logger, err := log.New("") 16 | if err != nil { 17 | return 18 | } 19 | defer logger.Close() 20 | 21 | //logger.Debug("will not print") 22 | //logger.Release("My name is %v", name) 23 | 24 | log.Export(logger) 25 | 26 | log.Debug("log", "will not print") 27 | log.Info("log", "My name is %v", name) 28 | } 29 | -------------------------------------------------------------------------------- /api/proto/list.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | import "types.proto"; 3 | package bs.list; 4 | option go_package ="/list"; 5 | 6 | enum CMDList { 7 | IDRoomRegisterReq = 1; //房间注册 8 | IDRoomRegisterRsp = 2; //房间注册 9 | IDRoomListReq = 3; //列表请求 10 | IDRoomListRsp = 4; //列表请求 11 | } 12 | 13 | message RoomRegisterReq{ 14 | optional types.RoomInfo info = 1; 15 | } 16 | 17 | message RoomRegisterRsp{ 18 | optional types.ErrorInfo err_info = 99; 19 | } 20 | 21 | //房间列表 22 | message RoomListReq{ 23 | optional uint32 list_id = 1; 24 | } 25 | 26 | //房间列表 27 | message RoomListRsp{ 28 | repeated types.RoomInfo rooms = 1; 29 | } -------------------------------------------------------------------------------- /pkg/util/debugInfo.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path" 7 | "runtime/debug" 8 | "time" 9 | ) 10 | 11 | func TryE(pathName string) { 12 | errs := recover() 13 | if errs == nil { 14 | return 15 | } 16 | 17 | filename := fmt.Sprintf("%s_pid%d_dump.log", 18 | time.Now().Format("2006-01-02_15-04-05"), 19 | os.Getpid()) 20 | f, err := os.Create(path.Join(pathName, filename)) 21 | if err != nil { 22 | return 23 | } 24 | 25 | defer f.Close() 26 | 27 | f.WriteString(fmt.Sprintf("%v\r\n", errs)) //输出panic信息 28 | f.WriteString("========\r\n") 29 | f.WriteString(string(debug.Stack())) //输出堆栈信息 30 | } 31 | -------------------------------------------------------------------------------- /cmd/table/business/table/ddz/logic.go: -------------------------------------------------------------------------------- 1 | package ddz 2 | 3 | var cardData = []byte{ 4 | 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, //方块 A - K 5 | 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, //梅花 A - K 6 | 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, //红桃 A - K 7 | 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, //黑桃 A - K 8 | 0x4E, 0x4F} 9 | 10 | func RemoveCard(handCards, rmCards []uint8) { 11 | if len(rmCards) >= len(handCards) { 12 | handCards = append([]uint8{}) 13 | } 14 | 15 | handCards = handCards[:len(handCards)-len(rmCards)] 16 | } 17 | -------------------------------------------------------------------------------- /cmd/robot/business/game/game.go: -------------------------------------------------------------------------------- 1 | package game 2 | 3 | import ( 4 | n "mango/pkg/network" 5 | "time" 6 | ) 7 | 8 | const ( 9 | Begin uint32 = 1 10 | Over uint32 = 2 11 | ) 12 | 13 | type ( 14 | UserInfo struct { 15 | Account string 16 | PassWord string 17 | UserId uint64 18 | State uint32 19 | RoomID uint32 20 | TableServiceId uint32 21 | TableId uint64 22 | SeatId uint32 23 | Scene uint32 24 | } 25 | 26 | Sink interface { 27 | GameMessage(seatId, cmdId uint32, data []byte) 28 | } 29 | 30 | Frame interface { 31 | AfterFunc(d time.Duration, cb func()) 32 | SendGameMessage(bm n.BaseMessage) 33 | GetMyInfo() *UserInfo 34 | GameOver() 35 | } 36 | ) 37 | -------------------------------------------------------------------------------- /pkg/network/agent.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | import ( 4 | "github.com/golang/protobuf/proto" 5 | "net" 6 | ) 7 | 8 | const ( 9 | NormalUser uint32 = 0 10 | CommonServer uint32 = 1 11 | ) 12 | 13 | type ( 14 | BaseAgentInfo struct { 15 | AgentType uint32 16 | AppName string 17 | AppType uint32 18 | AppId uint32 19 | ListenOnAddr string 20 | } 21 | 22 | Agent interface { 23 | Run() 24 | OnClose() 25 | SendData(appType, cmdId uint32, m proto.Message) 26 | Close() 27 | Destroy() 28 | } 29 | 30 | AgentClient interface { 31 | Agent 32 | AgentInfo() *BaseAgentInfo 33 | LocalAddr() net.Addr 34 | RemoteAddr() net.Addr 35 | } 36 | 37 | AgentServer interface { 38 | Agent 39 | SendMessage(bm BaseMessage) 40 | } 41 | ) 42 | -------------------------------------------------------------------------------- /configs/config/lobby.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": { 3 | "LogScreenPrint": "1", 4 | "title": "登录服务", 5 | "日志服务器地址": "127.0.0.1:10001", 6 | "数据库配置": { 7 | "master": { 8 | "driver": "mysql", 9 | "server": "", 10 | "database": "TODO", 11 | "port": "TODO", 12 | "userid": "TODO", 13 | "password": "TODO", 14 | "maxOpenConnects": 150, 15 | "maxIdleConnects": 5, 16 | "connMaxLifeTime": 30000 17 | }, 18 | "nosql": { 19 | "redis": { 20 | "server": "", 21 | "password": "TODO" 22 | } 23 | } 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /cmd/robot/business/business.go: -------------------------------------------------------------------------------- 1 | package business 2 | 3 | import ( 4 | "fmt" 5 | "mango/pkg/conf/apollo" 6 | g "mango/pkg/gate" 7 | "mango/pkg/log" 8 | "mango/cmd/robot/business/player" 9 | "strconv" 10 | ) 11 | 12 | var ( 13 | userList = make([]*player.Player, 0) 14 | ) 15 | 16 | func init() { 17 | g.CallBackRegister(g.CbConfigChangeNotify, configChangeNotify) 18 | } 19 | 20 | func configChangeNotify(args []interface{}) { 21 | key := args[apollo.KeyIndex].(apollo.ConfKey) 22 | value := args[apollo.ValueIndex].(apollo.ConfValue) 23 | 24 | switch key.Key { 25 | case "机器人数量": 26 | robotCount, _ := strconv.Atoi(value.Value) 27 | log.Debug("", "开始创建,robotCount=%v", robotCount) 28 | for i := 0; i < int(robotCount); i++ { 29 | pl := player.NewPlayer(fmt.Sprintf("robot%05d", i), "", 666) 30 | if pl != nil { 31 | userList = append(userList, pl) 32 | } 33 | } 34 | default: 35 | break 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /pkg/util/gorsa/rsa_generate.go: -------------------------------------------------------------------------------- 1 | package gorsa 2 | 3 | import ( 4 | "crypto/rand" 5 | "crypto/rsa" 6 | "crypto/x509" 7 | "encoding/pem" 8 | ) 9 | 10 | func GenerateRSAKey(bits int, keyType PriKeyType) (private, public []byte, err error) { 11 | // 生成私钥文件 12 | privateKey, err := rsa.GenerateKey(rand.Reader, bits) 13 | if err != nil { 14 | panic(err) 15 | } 16 | var derStream []byte 17 | if keyType == PKCS8 { 18 | derStream, err = x509.MarshalPKCS8PrivateKey(privateKey) 19 | if err != nil { 20 | panic(err) 21 | } 22 | } else { 23 | derStream = x509.MarshalPKCS1PrivateKey(privateKey) 24 | } 25 | block := &pem.Block{ 26 | Type: "RSA PRIVATE KEY", 27 | Bytes: derStream, 28 | } 29 | private = pem.EncodeToMemory(block) 30 | publicKey := &privateKey.PublicKey 31 | derPkix, err := x509.MarshalPKIXPublicKey(publicKey) 32 | if err != nil { 33 | panic(err) 34 | } 35 | block = &pem.Block{ 36 | Type: "PUBLIC KEY", 37 | Bytes: derPkix, 38 | } 39 | public = pem.EncodeToMemory(block) 40 | return 41 | } 42 | -------------------------------------------------------------------------------- /pkg/util/colorprint/color_darwin.go: -------------------------------------------------------------------------------- 1 | // +build darwin 2 | package colorprint 3 | 4 | import "fmt" 5 | 6 | var ( 7 | FontColor Color = Color{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15} 8 | ) 9 | 10 | type Color struct { 11 | Black int // 黑色 12 | Blue int // 蓝色 13 | Green int // 绿色 14 | Cyan int // 青色 15 | Red int // 红色 16 | Purple int // 紫色 17 | Yellow int // 黄色 18 | LightGray int // 淡灰色 19 | Gray int // 灰色 20 | LightBlue int // 亮蓝色 21 | LightGreen int // 亮绿色 22 | LightCyan int // 亮青色 23 | LightRed int // 亮红色 24 | LightPurple int // 亮紫色 25 | LightYellow int // 亮黄色 26 | White int // 白色 27 | } 28 | 29 | func ColorPrint(s string, i int) { 30 | switch i { 31 | case FontColor.Yellow: 32 | fmt.Printf("%c[0;40;33m%s%c[0m", 0x1B, s, 0x1B) 33 | case FontColor.Red: 34 | fmt.Printf("%c[0;40;31m%s%c[0m", 0x1B, s, 0x1B) 35 | case FontColor.LightRed: 36 | fmt.Printf("%c[1;40;31m%s%c[0m", 0x1B, s, 0x1B) 37 | default: 38 | fmt.Print(s) 39 | } 40 | } -------------------------------------------------------------------------------- /cmd/config/conf/json.go: -------------------------------------------------------------------------------- 1 | package conf 2 | 3 | import ( 4 | "encoding/json" 5 | aConfig "github.com/apolloconfig/agollo/v4/env/config" 6 | "io/ioutil" 7 | "mango/pkg/log" 8 | ) 9 | 10 | const ( 11 | DefaultConfigFile string = "configs/config/config.json" 12 | ) 13 | 14 | var Server struct { 15 | UseApollo bool `default:"false" json:"UseApollo"` 16 | LoggerAddr string 17 | Config aConfig.AppConfig 18 | CommonServers []ApolloConfig 19 | } 20 | 21 | type ApolloConfig struct { 22 | Appid string `json:"appID"` 23 | Cluster string `json:"cluster"` 24 | Ns string `json:"namespaceName"` 25 | Ip string `json:"ip"` 26 | ServerType uint32 `json:"serverType"` 27 | ServerId uint32 `json:"serverID"` 28 | } 29 | 30 | func init() { 31 | data, err := ioutil.ReadFile(DefaultConfigFile) 32 | if err != nil { 33 | log.Fatal("", "%v", err) 34 | } 35 | err = json.Unmarshal(data, &Server) 36 | if err != nil { 37 | log.Fatal("", "%v", err) 38 | } 39 | 40 | log.Info("", "配置文件载入成功%v", Server) 41 | } 42 | -------------------------------------------------------------------------------- /pkg/util/colorprint/color_linux.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package colorprint 4 | 5 | import "fmt" 6 | 7 | var ( 8 | FontColor Color = Color{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15} 9 | ) 10 | 11 | type Color struct { 12 | Black int // 黑色 13 | Blue int // 蓝色 14 | Green int // 绿色 15 | Cyan int // 青色 16 | Red int // 红色 17 | Purple int // 紫色 18 | Yellow int // 黄色 19 | LightGray int // 淡灰色 20 | Gray int // 灰色 21 | LightBlue int // 亮蓝色 22 | LightGreen int // 亮绿色 23 | LightCyan int // 亮青色 24 | LightRed int // 亮红色 25 | LightPurple int // 亮紫色 26 | LightYellow int // 亮黄色 27 | White int // 白色 28 | } 29 | 30 | func ColorPrint(s string, i int) { 31 | switch i { 32 | case FontColor.Yellow: 33 | fmt.Printf("%c[0;40;33m%s%c[0m", 0x1B, s, 0x1B) 34 | case FontColor.Red: 35 | fmt.Printf("%c[0;40;31m%s%c[0m", 0x1B, s, 0x1B) 36 | case FontColor.LightRed: 37 | fmt.Printf("%c[1;40;31m%s%c[0m", 0x1B, s, 0x1B) 38 | default: 39 | fmt.Print(s) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /api/proto/logger.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | package bs.logger; 3 | option go_package ="/logger"; 4 | 5 | enum CMDLogger{ 6 | IDLogClientInit = 1; 7 | IDLogReq = 2; 8 | IDLogFlush = 3; // 立即写日志到文件中 9 | } 10 | 11 | message LogReq{ 12 | optional string file_name=1; // FILE_NAME 13 | optional uint32 line_no=2; // LINE_NO 14 | optional uint32 thread_id=3; // 线程 15 | optional bytes class_name=4; // 日志分类名称 16 | optional uint32 log_level=5; // 日志级别 17 | optional bytes content=6; // 内容 18 | optional uint32 server_id=7; // 来自于哪个服务器 19 | optional int64 time_ns=8; // 发出时间 20 | optional bool show_list=9; // 是否在界面的日志框中输出 21 | optional uint32 src_apptype=10; // 源AppType 22 | optional uint32 src_appid=11; // 源AppID 23 | optional uint64 time_ms_recv=12; // 接收时间 24 | optional string src_appname=13; // 源App名(一般为进程名) 25 | } 26 | 27 | message LogFlush { 28 | optional uint32 server_id=1; // 来自于哪个服务器 29 | optional uint32 src_apptype=2; // 源AppType 30 | optional uint32 src_appid=3; // 源AppID 31 | optional string src_appname=4; // 源App名(一般为进程名) 32 | } -------------------------------------------------------------------------------- /api/proto/property.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | import "types.proto"; 3 | package bs.property; 4 | option go_package ="/property"; 5 | 6 | enum CMDProperty { 7 | IDQueryPropertyReq = 1; //查询财富 8 | IDQueryPropertyRsp = 2; //查询财富 9 | IDModifyPropertyReq = 3; //修改财富 10 | IDModifyPropertyRsp = 4; //修改财富 11 | }; 12 | 13 | message QueryPropertyReq{ 14 | optional uint64 user_id = 1; //用户ID 15 | } 16 | 17 | message QueryPropertyRsp{ 18 | optional uint64 user_id = 1; //用户ID 19 | repeated types.PropItem user_props = 2; //用户道具 20 | optional types.ErrorInfo err_info = 99; 21 | } 22 | 23 | message ModifyPropertyReq{ 24 | optional uint64 user_id = 1; //用户ID 25 | optional int32 op_type = 2; //操作类型 26 | repeated types.PropItem user_props = 3; //用户道具 27 | } 28 | 29 | message ModifyPropertyRsp{ 30 | optional uint64 user_id = 1; //用户ID 31 | optional int32 op_type = 2; //操作类型 32 | repeated types.PropItem user_props = 3; //用户道具 33 | optional types.ErrorInfo err_info = 99; 34 | } -------------------------------------------------------------------------------- /pkg/util/colorprint/color_windows.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | 3 | package colorprint 4 | 5 | import "syscall" 6 | 7 | var ( 8 | kernel32 *syscall.LazyDLL = syscall.NewLazyDLL(`kernel32.dll`) 9 | proc *syscall.LazyProc = kernel32.NewProc(`SetConsoleTextAttribute`) 10 | CloseHandle *syscall.LazyProc = kernel32.NewProc(`CloseHandle`) 11 | 12 | // 给字体颜色对象赋值 13 | FontColor Color = Color{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15} 14 | ) 15 | 16 | type Color struct { 17 | Black int // 黑色 18 | Blue int // 蓝色 19 | Green int // 绿色 20 | Cyan int // 青色 21 | Red int // 红色 22 | Purple int // 紫色 23 | Yellow int // 黄色 24 | LightGray int // 淡灰色(系统默认值) 25 | Gray int // 灰色 26 | LightBlue int // 亮蓝色 27 | LightGreen int // 亮绿色 28 | LightCyan int // 亮青色 29 | LightRed int // 亮红色 30 | LightPurple int // 亮紫色 31 | LightYellow int // 亮黄色 32 | White int // 白色 33 | } 34 | 35 | // 输出有颜色的字体 36 | func ColorPrint(s string, i int) { 37 | handle, _, _ := proc.Call(uintptr(syscall.Stdout), uintptr(i)) 38 | print(s) 39 | CloseHandle.Call(handle) 40 | } 41 | -------------------------------------------------------------------------------- /cmd/config/business/apolloDefaultLogger.go: -------------------------------------------------------------------------------- 1 | package business 2 | 3 | import ( 4 | "fmt" 5 | "mango/pkg/log" 6 | ) 7 | 8 | type DefaultLogger struct { 9 | } 10 | 11 | func (d *DefaultLogger) Debugf(format string, params ...interface{}) { 12 | //log.Debug("agollo", format, params...) 13 | } 14 | 15 | func (d *DefaultLogger) Infof(format string, params ...interface{}) { 16 | log.Info("agollo", format, params...) 17 | } 18 | 19 | func (d *DefaultLogger) Warnf(format string, params ...interface{}) { 20 | log.Warning("agollo", format, params...) 21 | } 22 | 23 | func (d *DefaultLogger) Errorf(format string, params ...interface{}) { 24 | log.Error("agollo", format, params...) 25 | } 26 | 27 | func (d *DefaultLogger) Debug(v ...interface{}) { 28 | //log.Debug("agollo", "%v", fmt.Sprint(v...)) 29 | } 30 | func (d *DefaultLogger) Info(v ...interface{}) { 31 | log.Info("agollo", "%v", fmt.Sprint(v...)) 32 | } 33 | 34 | func (d *DefaultLogger) Warn(v ...interface{}) { 35 | log.Warning("agollo", "%v", fmt.Sprint(v...)) 36 | } 37 | 38 | func (d *DefaultLogger) Error(v ...interface{}) { 39 | log.Error("agollo", "%v", fmt.Sprint(v...)) 40 | } 41 | -------------------------------------------------------------------------------- /pkg/go/example_test.go: -------------------------------------------------------------------------------- 1 | package g_test 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | g "mango/pkg/go" 8 | ) 9 | 10 | func Example() { 11 | d := g.New(10) 12 | 13 | // go 1 14 | var res int 15 | d.Go(func() { 16 | fmt.Println("1 + 1 = ?") 17 | res = 1 + 1 18 | }, func() { 19 | fmt.Println(res) 20 | }) 21 | 22 | d.Cb(<-d.ChanCb) 23 | 24 | // go 2 25 | d.Go(func() { 26 | fmt.Print("My name is ") 27 | }, func() { 28 | fmt.Println("Leaf") 29 | }) 30 | 31 | d.Close() 32 | 33 | // Output: 34 | // 1 + 1 = ? 35 | // 2 36 | // My name is Leaf 37 | } 38 | 39 | func ExampleLinearContext() { 40 | d := g.New(10) 41 | 42 | // parallel 43 | d.Go(func() { 44 | time.Sleep(time.Second / 2) 45 | fmt.Println("1") 46 | }, nil) 47 | d.Go(func() { 48 | fmt.Println("2") 49 | }, nil) 50 | 51 | d.Cb(<-d.ChanCb) 52 | d.Cb(<-d.ChanCb) 53 | 54 | // linear 55 | c := d.NewLinearContext() 56 | c.Go(func() { 57 | time.Sleep(time.Second / 2) 58 | fmt.Println("1") 59 | }, nil) 60 | c.Go(func() { 61 | fmt.Println("2") 62 | }, nil) 63 | 64 | d.Close() 65 | 66 | // Output: 67 | // 2 68 | // 1 69 | // 1 70 | // 2 71 | } 72 | -------------------------------------------------------------------------------- /pkg/database/mchelper/mchelper.go: -------------------------------------------------------------------------------- 1 | package mchelper 2 | 3 | import ( 4 | "errors" 5 | "github.com/bradfitz/gomemcache/memcache" 6 | ) 7 | 8 | type MCHelper struct { 9 | client *memcache.Client 10 | } 11 | 12 | func (mch *MCHelper) Init(server ...string) { 13 | mch.client = memcache.New(server...) 14 | } 15 | 16 | func (mch *MCHelper) Set(Key string, Value string) error { 17 | return mch.SetWithExp(Key, Value, 0) 18 | } 19 | 20 | func (mch *MCHelper) SetWithExp(Key string, Value string, Exp int32) error { 21 | if mch.client == nil { 22 | return errors.New("mch.client is null") 23 | } 24 | return mch.client.Set(&memcache.Item{Key: Key, Value: []byte(Value), Expiration: Exp}) 25 | } 26 | 27 | func (mch *MCHelper) SetWithExpB(Key string, Value []byte, Exp int32) error { 28 | if mch.client == nil { 29 | return errors.New("mch.client is null") 30 | } 31 | return mch.client.Set(&memcache.Item{Key: Key, Value: Value, Expiration: Exp}) 32 | } 33 | 34 | func (mch *MCHelper) Get(Key string) *memcache.Item { 35 | if mch.client == nil { 36 | return nil 37 | } 38 | item, err := mch.client.Get(Key) 39 | if err != nil { 40 | return nil 41 | } 42 | return item 43 | } 44 | -------------------------------------------------------------------------------- /pkg/database/mgohelper/mgohelper.go: -------------------------------------------------------------------------------- 1 | package mgohelper 2 | 3 | import ( 4 | "mango/pkg/log" 5 | "gopkg.in/mgo.v2" 6 | ) 7 | 8 | type MgoHelper struct { 9 | server *mgo.Session 10 | db *mgo.Database 11 | } 12 | 13 | func (mh *MgoHelper) Init(server, database, username, pwd string) error { 14 | var err error 15 | mh.server, err = mgo.Dial(server) 16 | if err != nil { 17 | log.Error("", "MgoHelper Init:err=%v", err) 18 | return err 19 | } 20 | mh.server.SetMode(mgo.Monotonic, true) 21 | mh.db = mh.server.DB(database) 22 | if len(username) > 0 && len(pwd) > 0 { 23 | err := mh.db.Login(username, pwd) 24 | if err != nil { 25 | log.Error("", "MgoHelper InitDB:err=%v", err) 26 | return err 27 | } 28 | } 29 | 30 | log.Info("MgoHelper", "mongodb连接完成,server=%v,database=%v, username=%v, pwd=%v", server, database, username, pwd) 31 | 32 | return nil 33 | } 34 | 35 | func (mh *MgoHelper) GetDB() *mgo.Database { 36 | return mh.db 37 | } 38 | 39 | func (mh *MgoHelper) Ping() error { 40 | return mh.server.Ping() 41 | } 42 | 43 | func (mh *MgoHelper) Close(server string) { 44 | if mh.server == nil { 45 | return 46 | } 47 | mh.server.Close() 48 | } 49 | -------------------------------------------------------------------------------- /pkg/util/string.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "strconv" 5 | "strings" 6 | ) 7 | 8 | // 字符串转数组 - 默认|分隔 9 | func SplitToInt(val string, psep ...string) []int { 10 | sep := "|" 11 | if len(psep) == 1 { 12 | sep = psep[0] 13 | } 14 | if len(val) == 0 { 15 | return []int{} 16 | } 17 | vals := strings.Split(val, sep) 18 | ret := make([]int, len(vals)) 19 | for i, v := range vals { 20 | vint, _ := strconv.Atoi(v) 21 | ret[i] = vint 22 | } 23 | return ret 24 | } 25 | 26 | // 字符串转数组 - 默认|分隔 27 | func SplitToInt32(val string, psep ...string) []int32 { 28 | sep := "|" 29 | if len(psep) == 1 { 30 | sep = psep[0] 31 | } 32 | if len(val) == 0 { 33 | return []int32{} 34 | } 35 | vals := strings.Split(val, sep) 36 | ret := make([]int32, len(vals)) 37 | for i, v := range vals { 38 | vint, _ := strconv.Atoi(v) 39 | ret[i] = int32(vint) 40 | } 41 | return ret 42 | } 43 | 44 | func Uint64Slice2String(val []uint64, psep string) string { 45 | if len(val) == 1 { 46 | return strconv.Itoa(int(val[0])) 47 | } 48 | str := make([]string, 0) 49 | for _, v := range val { 50 | str = append(str, strconv.Itoa(int(v))) 51 | } 52 | return strings.Join(str, psep) 53 | } 54 | -------------------------------------------------------------------------------- /api/proto/room.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | import "types.proto"; 3 | package bs.room; 4 | option go_package ="/room"; 5 | 6 | enum CMDRoom { 7 | IDJoinReq = 1; //进入房间 8 | IDJoinRsp = 2; //进入房间 9 | IDUserActionReq = 3; //用户动作 10 | IDUserActionRsp = 4; //用户动作 11 | IDExitReq = 5; //离开房间 12 | IDExitRsp = 6; //离开房间 13 | IDUserStateChange = 7; //状态变化 14 | } 15 | 16 | message JoinReq{ 17 | } 18 | 19 | message JoinRsp{ 20 | optional uint32 app_id = 1; 21 | optional types.ErrorInfo err_info = 99; 22 | } 23 | 24 | enum ActionType { 25 | Ready=1; 26 | Cancel=2; 27 | } 28 | 29 | message UserActionReq{ 30 | optional ActionType action = 1; 31 | } 32 | 33 | message UserActionRsp{ 34 | optional ActionType action = 1; 35 | optional types.ErrorInfo err_info = 99; 36 | } 37 | 38 | message ExitReq{ 39 | } 40 | 41 | message ExitRsp{ 42 | optional types.ErrorInfo err_info = 99; 43 | } 44 | 45 | message UserStateChange{ 46 | optional uint64 user_id = 1; //用户ID 47 | optional uint32 user_state = 2; //用户状态 48 | optional uint32 table_service_id = 3; // 49 | optional uint64 table_id = 4; // 50 | optional uint32 seat_id = 5; // 51 | } -------------------------------------------------------------------------------- /pkg/timer/example_test.go: -------------------------------------------------------------------------------- 1 | package timer_test 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | "mango/pkg/timer" 7 | ) 8 | 9 | func ExampleTimer() { 10 | d := timer.NewDispatcher(10) 11 | 12 | // timer 1 13 | d.AfterFunc(1, func() { 14 | fmt.Println("My name is Leaf") 15 | }) 16 | 17 | // timer 2 18 | t := d.AfterFunc(1, func() { 19 | fmt.Println("will not print") 20 | }) 21 | t.Stop() 22 | 23 | // dispatch 24 | (<-d.ChanTimer).Cb() 25 | 26 | // Output: 27 | // My name is Leaf 28 | } 29 | 30 | func ExampleCronExpr() { 31 | cronExpr, err := timer.NewCronExpr("0 * * * *") 32 | if err != nil { 33 | return 34 | } 35 | 36 | fmt.Println(cronExpr.Next(time.Date( 37 | 2000, 1, 1, 38 | 20, 10, 5, 39 | 0, time.UTC, 40 | ))) 41 | 42 | // Output: 43 | // 2000-01-01 21:00:00 +0000 UTC 44 | } 45 | 46 | func ExampleCron() { 47 | d := timer.NewDispatcher(10) 48 | 49 | // cron expr 50 | cronExpr, err := timer.NewCronExpr("* * * * * *") 51 | if err != nil { 52 | return 53 | } 54 | 55 | // cron 56 | var c *timer.Cron 57 | c = d.CronFunc(cronExpr, func() { 58 | fmt.Println("My name is Leaf") 59 | c.Stop() 60 | }) 61 | 62 | // dispatch 63 | (<-d.ChanTimer).Cb() 64 | 65 | // Output: 66 | // My name is Leaf 67 | } 68 | -------------------------------------------------------------------------------- /pkg/util/stringtransform.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "bytes" 5 | "io/ioutil" 6 | 7 | "golang.org/x/text/encoding/simplifiedchinese" 8 | "golang.org/x/text/transform" 9 | ) 10 | 11 | // transform GBK bytes to UTF-8 bytes 12 | func GbkToUtf8(str []byte) (b []byte, err error) { 13 | r := transform.NewReader(bytes.NewReader(str), simplifiedchinese.GBK.NewDecoder()) 14 | b, err = ioutil.ReadAll(r) 15 | if err != nil { 16 | return 17 | } 18 | return 19 | } 20 | 21 | // transform UTF-8 bytes to GBK bytes 22 | func Utf8ToGbk(str []byte) (b []byte, err error) { 23 | r := transform.NewReader(bytes.NewReader(str), simplifiedchinese.GBK.NewEncoder()) 24 | b, err = ioutil.ReadAll(r) 25 | if err != nil { 26 | return 27 | } 28 | return 29 | } 30 | 31 | // transform GBK string to UTF-8 string and replace it, if transformed success, returned nil error, or died by error message 32 | func StrToUtf8(str *string) error { 33 | b, err := GbkToUtf8([]byte(*str)) 34 | if err != nil { 35 | return err 36 | } 37 | *str = string(b) 38 | return nil 39 | } 40 | 41 | // transform UTF-8 string to GBK string and replace it, if transformed success, returned nil error, or died by error message 42 | func StrToGBK(str string) string { 43 | b, err := Utf8ToGbk([]byte(str)) 44 | if err != nil { 45 | return "err" 46 | } 47 | return string(b) 48 | } 49 | -------------------------------------------------------------------------------- /pkg/network/grpc_server.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | import ( 4 | "mango/pkg/log" 5 | "mango/pkg/util/errorhelper" 6 | "fmt" 7 | "google.golang.org/grpc" 8 | "net" 9 | ) 10 | 11 | type RpcServer struct { 12 | Port int 13 | isStart bool 14 | services []func(gs *grpc.Server) 15 | } 16 | 17 | func NewRpcServer() *RpcServer { 18 | rs := new(RpcServer) 19 | rs.isStart = false 20 | return rs 21 | } 22 | 23 | func (rs *RpcServer) AddService(service func(gs *grpc.Server)) { 24 | rs.services = append(rs.services, service) 25 | } 26 | 27 | func (rs *RpcServer) IsStart() bool { 28 | return rs.isStart 29 | } 30 | 31 | func (rs *RpcServer) Start() { 32 | if rs.Port == 0 || rs.IsStart() { 33 | return 34 | } 35 | go rs.run() 36 | } 37 | 38 | func (rs *RpcServer) run() { 39 | defer errorhelper.Recover() 40 | 41 | //建立连接 42 | ln, err := net.Listen("tcp", fmt.Sprintf(":%d", rs.Port)) 43 | if err != nil { 44 | log.Error("", "异常,rpc服务启动失败,port=%v,err=%v", rs.Port, err) 45 | return 46 | } 47 | 48 | //创建服务 49 | s := grpc.NewServer() 50 | for _, f := range rs.services { 51 | f(s) 52 | } 53 | 54 | rs.isStart = true 55 | 56 | log.Info("", fmt.Sprintf("listening rpc on %d,len=%v", rs.Port, len(rs.services))) 57 | 58 | // 运行服务 59 | if err := s.Serve(ln); err != nil { 60 | log.Error("", "failed to serve: err=%v", err) 61 | return 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /pkg/util/goaes/aes_test.go: -------------------------------------------------------------------------------- 1 | package goaes 2 | 3 | import ( 4 | "encoding/base64" 5 | "testing" 6 | ) 7 | 8 | func TestEncrypt(t *testing.T) { 9 | raw := []byte("password123") 10 | key := []byte("asdf1234qwer7894") 11 | str, err := Encrypt(raw, key, nil, CBC, false) 12 | if err == nil { 13 | t.Log("suc", str) 14 | } else { 15 | t.Fatal("fail", err) 16 | } 17 | } 18 | 19 | func TestDncrypt(t *testing.T) { 20 | raw := []byte("pqjPM0GJUjlgryzMaslqBAzIknumcdgey1MN+ylWHqY=") 21 | key := []byte("asdf1234qwer7894") 22 | str, err := Decrypt(raw, key, nil, CBC, false) 23 | if err == nil { 24 | t.Log("suc", str) 25 | } else { 26 | t.Fatal("fail", err) 27 | } 28 | } 29 | 30 | func TestEcbEncrypt(t *testing.T) { 31 | raw := []byte("password1234") 32 | key := []byte("asdf1234qwer7894") 33 | 34 | str := EcbEncrypt(raw, key) 35 | res := base64.StdEncoding.EncodeToString(str) 36 | if res == "eIfgwnbjlf1OymWjfJUFZw==" { 37 | t.Log("suc ", res) 38 | } else { 39 | t.Fatal("fail ", res) 40 | } 41 | } 42 | 43 | func TestEcbDecrypt(t *testing.T) { 44 | raw := "eIfgwnbjlf1OymWjfJUFZw==" 45 | key := []byte("asdf1234qwer7894") 46 | 47 | res, err := base64.StdEncoding.DecodeString(raw) 48 | if err != nil { 49 | t.Fatal(err) 50 | } 51 | str := EcbDecrypt(res, key) 52 | if err == nil && string(str) == "password1234" { 53 | t.Log("suc ", string(str)) 54 | } else { 55 | t.Fatal("fail ", err, ", ", string(str)) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /api/proto/table.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | //import "types.proto"; 3 | package bs.table; 4 | option go_package ="/table"; 5 | 6 | enum CMDTable { 7 | IDApplyReq = 1; // 8 | IDApplyRsp = 2; // 9 | IDReleaseReq = 3; // 10 | IDReleaseRsp = 4; // 11 | IDSetPlayerToTableReq = 5; // 12 | IDSetPlayerToTableRsp = 6; // 13 | IDMatchTableReq = 7; // 14 | IDMatchTableRsp = 8; // 15 | IDGameMessage = 9; // 16 | IDWriteGameScore = 10; // 17 | IDGameOver = 11; // 18 | } 19 | 20 | message ApplyReq{ 21 | optional uint32 apply_count = 1; 22 | } 23 | 24 | message ApplyRsp{ 25 | optional uint32 apply_count = 1; 26 | repeated uint64 table_ids = 2; 27 | } 28 | 29 | message ReleaseReq{ 30 | optional uint32 release_count = 1; 31 | repeated uint64 table_ids = 2; 32 | } 33 | 34 | message ReleaseRsp{ 35 | 36 | } 37 | 38 | message SetPlayerToTableReq{ 39 | optional uint64 table_id = 1; 40 | optional uint64 user_id = 2; 41 | optional uint32 seat_id = 3; 42 | optional uint64 gateconnid = 4; 43 | } 44 | 45 | message SetPlayerToTableRsp{ 46 | } 47 | 48 | message MatchTableReq{ 49 | optional uint64 table_id = 1; 50 | repeated uint64 players = 2; 51 | } 52 | 53 | message MatchTableRsp{ 54 | 55 | } 56 | 57 | message GameMessage{ 58 | optional uint32 sub_cmdid =1; 59 | optional bytes data = 2; 60 | } 61 | 62 | message WriteGameScore{ 63 | optional uint64 table_id = 1; 64 | } 65 | 66 | message GameOver{ 67 | optional uint64 table_id = 1; 68 | } -------------------------------------------------------------------------------- /pkg/util/example_test.go: -------------------------------------------------------------------------------- 1 | package util_test 2 | 3 | import ( 4 | "fmt" 5 | "mango/pkg/util" 6 | ) 7 | 8 | func ExampleMap() { 9 | m := new(util.Map) 10 | 11 | fmt.Println(m.Get("key")) 12 | m.Set("key", "value") 13 | fmt.Println(m.Get("key")) 14 | m.Del("key") 15 | fmt.Println(m.Get("key")) 16 | 17 | m.Set(1, "1") 18 | m.Set(2, 2) 19 | m.Set("3", 3) 20 | 21 | fmt.Println(m.Len()) 22 | 23 | // Output: 24 | // 25 | // value 26 | // 27 | // 3 28 | } 29 | 30 | func ExampleRandGroup() { 31 | i := util.RandGroup(0, 0, 50, 50) 32 | switch i { 33 | case 2, 3: 34 | fmt.Println("ok") 35 | } 36 | 37 | // Output: 38 | // ok 39 | } 40 | 41 | func ExampleRandInterval() { 42 | v := util.RandInterval(-1, 1) 43 | switch v { 44 | case -1, 0, 1: 45 | fmt.Println("ok") 46 | } 47 | 48 | // Output: 49 | // ok 50 | } 51 | 52 | func ExampleRandIntervalN() { 53 | r := util.RandIntervalN(-1, 0, 2) 54 | if r[0] == -1 && r[1] == 0 || 55 | r[0] == 0 && r[1] == -1 { 56 | fmt.Println("ok") 57 | } 58 | 59 | // Output: 60 | // ok 61 | } 62 | 63 | func ExampleDeepCopy() { 64 | src := []int{1, 2, 3} 65 | 66 | var dst []int 67 | util.DeepCopy(&dst, &src) 68 | 69 | for _, v := range dst { 70 | fmt.Println(v) 71 | } 72 | 73 | // Output: 74 | // 1 75 | // 2 76 | // 3 77 | } 78 | 79 | func ExampleDeepClone() { 80 | src := []int{1, 2, 3} 81 | 82 | dst := util.DeepClone(src).([]int) 83 | 84 | for _, v := range dst { 85 | fmt.Println(v) 86 | } 87 | 88 | // Output: 89 | // 1 90 | // 2 91 | // 3 92 | } 93 | -------------------------------------------------------------------------------- /api/proto/gameddz.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | package bs.gameddz; 3 | option go_package ="/gameddz"; 4 | 5 | enum CMDGameddz { 6 | IDGameStart = 1; // 7 | IDReSendCard = 2; // 8 | IDCallLandReq = 3; // 9 | IDCallLandRsp = 4; // 10 | IDConfirmLand = 5; // 11 | IDOutCardReq = 6; // 12 | IDOutCardRsp = 7; // 13 | IDGameDataReq = 8; // 14 | IDGameDataRsp = 9; // 15 | IDGameOver = 10; // 16 | } 17 | 18 | message GameStart{ 19 | optional uint32 current_seat = 1; 20 | repeated bytes hand_card = 2; 21 | repeated bytes user_state = 3; 22 | } 23 | 24 | message ReSendCard{ 25 | optional uint32 current_seat = 1; 26 | repeated bytes hand_card = 2; 27 | } 28 | 29 | message CallLandReq{ 30 | optional uint32 call_score = 1; 31 | } 32 | 33 | message CallLandRsp{ 34 | optional uint32 current_seat = 1; 35 | optional uint32 call_seat = 2; 36 | optional uint32 call_score = 3; 37 | repeated uint32 cur_times = 4; 38 | } 39 | 40 | message ConfirmLand{ 41 | optional uint32 current_seat = 1; 42 | optional uint32 land_seat = 2; 43 | optional bytes bottom_card = 3; 44 | repeated uint32 cur_times = 4; 45 | } 46 | 47 | message OutCardReq{ 48 | optional bytes out_card = 1; 49 | } 50 | 51 | message OutCardRsp{ 52 | optional uint32 current_seat = 1; 53 | optional uint32 outcard_seat = 2; 54 | optional bytes out_card = 3; 55 | } 56 | 57 | message UserState{ 58 | } 59 | 60 | message GameDataReq{ 61 | } 62 | 63 | message GameDataRsp{ 64 | } 65 | 66 | message GameOver{ 67 | optional uint32 win_seat = 1; 68 | repeated int64 score = 2; 69 | } -------------------------------------------------------------------------------- /api/proto/config.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | package bs.config; 3 | option go_package ="/config"; 4 | 5 | enum CMDConfig { 6 | IDConfigReq = 1; // 请求配置 7 | IDConfigRsp = 2; // 配置响应 8 | IDItemRspState = 3; // 配置响应状态 9 | } 10 | 11 | //配置中心消息 12 | message ConfigReq { 13 | enum EnumSubscribe { 14 | NOT_SUBSCRIBE = 0; // 不订阅 15 | SUBSCRIBE = 1; // 订阅,成功就下发配置 16 | UNSUBSCRIBE = 2; // 取消订阅 17 | NEED_RSP = 4; // 要求回复,下发配置 18 | NO_RSP = 8; // 不要回复,不下发配置 19 | } 20 | optional string name_space = 1; // 要读取的配置的命名空间 21 | optional string key = 2; // 键名 空表示命名空间下的所有键 22 | optional uint32 subscribe = 3; // 是否订阅配置的更新 见EnumSubscribe 23 | optional uint32 app_type = 4; // AppType 24 | optional uint32 app_id = 5; // AppId 25 | optional uint32 sub_app_type = 6; // AppType 26 | optional uint32 sub_app_id = 7; // AppId 27 | } 28 | 29 | //配置条目 30 | message ConfigItem{ 31 | optional string key = 1; // 32 | optional string value = 2; // 33 | } 34 | 35 | //配置响应 36 | message ConfigRsp { 37 | optional string name_space = 1; // 要读取的配置的命名空间 38 | optional string reg_key = 2; // 订阅的key 39 | repeated ConfigItem item = 3; // 键值 40 | optional uint32 sub_app_type = 4; // AppType 41 | optional uint32 sub_app_id = 5; // AppId 42 | } 43 | 44 | //配置响应完成 45 | message ItemRspState { 46 | optional string name_space = 1; // 要读取的配置的命名空间 47 | optional string key = 2; // 键名 48 | optional uint32 sub_app_type = 3; // AppType 49 | optional uint32 sub_app_id = 4; // 50 | optional uint32 state = 5; // 发送状态 0完成、1开始发送、2发送中 51 | } -------------------------------------------------------------------------------- /scripts/windows/Startup.bat: -------------------------------------------------------------------------------- 1 | @echo build and run 2 | @cd ..\.. 3 | @if not exist .\cmd\config\configs mkdir .\cmd\config\configs\config 4 | @copy .\configs\config\center.json .\cmd\config\configs\config 5 | @copy .\configs\config\config.json .\cmd\config\configs\config 6 | @copy .\configs\config\gateway-100.json .\cmd\config\configs\config 7 | @copy .\configs\config\list.json .\cmd\config\configs\config 8 | @copy .\configs\config\lobby.json .\cmd\config\configs\config 9 | @copy .\configs\config\property.json .\cmd\config\configs\config 10 | @copy .\configs\config\robot-3000.json .\cmd\config\configs\config 11 | @copy .\configs\config\room-2000.json .\cmd\config\configs\config 12 | @copy .\configs\config\table-1000.json .\cmd\config\configs\config 13 | 14 | @echo call build 15 | @call:build logger -Type=1 -Id=1 -CenterAddr="127.0.0.1:10050" 16 | @call:build center -Type=2 -Id=50 -CenterAddr="127.0.0.1:10050" 17 | @call:build config -Type=3 -Id=60 -CenterAddr="127.0.0.1:10050" 18 | @call:build gateway -Type=4 -Id=100 -CenterAddr="127.0.0.1:10050" 19 | @call:build lobby -Type=5 -Id=70 -CenterAddr="127.0.0.1:10050" 20 | @call:build property -Type=6 -Id=80 -CenterAddr="127.0.0.1:10050" 21 | @call:build robot -Type=9 -Id=3000 -CenterAddr="127.0.0.1:10050" 22 | @call:build list -Type=10 -Id=90 -CenterAddr="127.0.0.1:10050" 23 | @call:build table -Type=11 -Id=1000 -CenterAddr="127.0.0.1:10050" 24 | @call:build room -Type=12 -Id=2000 -CenterAddr="127.0.0.1:10050" 25 | @goto:eof 26 | 27 | :build 28 | @set appName=%~1 29 | @cd .\cmd\%appName%\ 30 | @go build 31 | @start .\%appName%.exe %~2=%~3 %~4=%~5 %~6=%~7 32 | @cd ../.. 33 | @goto:eof 34 | -------------------------------------------------------------------------------- /pkg/util/rand.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "math/rand" 5 | "time" 6 | ) 7 | 8 | func init() { 9 | rand.Seed(time.Now().UnixNano()) 10 | } 11 | 12 | func RandGroup(p ...uint32) int { 13 | if p == nil { 14 | panic("args not found") 15 | } 16 | 17 | r := make([]uint32, len(p)) 18 | for i := 0; i < len(p); i++ { 19 | if i == 0 { 20 | r[0] = p[0] 21 | } else { 22 | r[i] = r[i-1] + p[i] 23 | } 24 | } 25 | 26 | rl := r[len(r)-1] 27 | if rl == 0 { 28 | return 0 29 | } 30 | 31 | rn := uint32(rand.Int63n(int64(rl))) 32 | for i := 0; i < len(r); i++ { 33 | if rn < r[i] { 34 | return i 35 | } 36 | } 37 | 38 | panic("bug") 39 | } 40 | 41 | func RandInterval(b1, b2 int32) int32 { 42 | if b1 == b2 { 43 | return b1 44 | } 45 | 46 | min, max := int64(b1), int64(b2) 47 | if min > max { 48 | min, max = max, min 49 | } 50 | return int32(rand.Int63n(max-min+1) + min) 51 | } 52 | 53 | func RandIntervalN(b1, b2 int32, n uint32) []int32 { 54 | if b1 == b2 { 55 | return []int32{b1} 56 | } 57 | 58 | min, max := int64(b1), int64(b2) 59 | if min > max { 60 | min, max = max, min 61 | } 62 | l := max - min + 1 63 | if int64(n) > l { 64 | n = uint32(l) 65 | } 66 | 67 | r := make([]int32, n) 68 | m := make(map[int32]int32) 69 | for i := uint32(0); i < n; i++ { 70 | v := int32(rand.Int63n(l) + min) 71 | 72 | if mv, ok := m[v]; ok { 73 | r[i] = mv 74 | } else { 75 | r[i] = v 76 | } 77 | 78 | lv := int32(l - 1 + min) 79 | if v != lv { 80 | if mv, ok := m[lv]; ok { 81 | m[v] = mv 82 | } else { 83 | m[v] = lv 84 | } 85 | } 86 | 87 | l-- 88 | } 89 | 90 | return r 91 | } 92 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module mango 2 | 3 | go 1.21.0 4 | 5 | require ( 6 | github.com/GoldBaby5511/go-simplejson v0.5.0 7 | github.com/apolloconfig/agollo/v4 v4.2.0 8 | github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d 9 | github.com/fsnotify/fsnotify v1.5.1 10 | github.com/go-sql-driver/mysql v1.6.0 11 | github.com/golang/protobuf v1.5.2 12 | github.com/gomodule/redigo v1.8.8 13 | github.com/google/uuid v1.1.2 14 | github.com/gorilla/websocket v1.4.2 15 | github.com/sirupsen/logrus v1.9.0 16 | github.com/streadway/amqp v1.0.0 17 | golang.org/x/text v0.3.7 18 | google.golang.org/grpc v1.40.0 19 | google.golang.org/protobuf v1.27.1 20 | gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 21 | gopkg.in/natefinch/lumberjack.v2 v2.2.1 22 | ) 23 | 24 | require ( 25 | github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect 26 | github.com/hashicorp/hcl v1.0.0 // indirect 27 | github.com/magiconair/properties v1.8.5 // indirect 28 | github.com/mitchellh/mapstructure v1.4.2 // indirect 29 | github.com/pelletier/go-toml v1.9.4 // indirect 30 | github.com/spf13/afero v1.6.0 // indirect 31 | github.com/spf13/cast v1.4.1 // indirect 32 | github.com/spf13/jwalterweatherman v1.1.0 // indirect 33 | github.com/spf13/pflag v1.0.5 // indirect 34 | github.com/spf13/viper v1.9.0 // indirect 35 | github.com/subosito/gotenv v1.2.0 // indirect 36 | golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420 // indirect 37 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect 38 | google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71 // indirect 39 | gopkg.in/ini.v1 v1.63.2 // indirect 40 | gopkg.in/yaml.v2 v2.4.0 // indirect 41 | ) 42 | -------------------------------------------------------------------------------- /cmd/list/business/business.go: -------------------------------------------------------------------------------- 1 | package business 2 | 3 | import ( 4 | "mango/api/gateway" 5 | "mango/api/list" 6 | "mango/api/types" 7 | g "mango/pkg/gate" 8 | "mango/pkg/log" 9 | n "mango/pkg/network" 10 | "mango/pkg/util" 11 | ) 12 | 13 | var ( 14 | roomList = make(map[uint64]*types.RoomInfo) 15 | ) 16 | 17 | func init() { 18 | g.MsgRegister(&list.RoomRegisterReq{}, n.AppList, uint16(list.CMDList_IDRoomRegisterReq), handleRoomRegisterReq) 19 | g.MsgRegister(&list.RoomListReq{}, n.AppList, uint16(list.CMDList_IDRoomListReq), handleRoomListReq) 20 | } 21 | 22 | func handleRoomRegisterReq(args []interface{}) { 23 | b := args[n.DataIndex].(n.BaseMessage) 24 | m := (b.MyMessage).(*list.RoomRegisterReq) 25 | srcApp := args[n.OtherIndex].(n.BaseAgentInfo) 26 | 27 | regKey := util.MakeUint64FromUint32(m.GetInfo().GetAppInfo().GetType(), m.GetInfo().GetAppInfo().GetId()) 28 | roomList[regKey] = m.GetInfo() 29 | log.Debug("", "收到注册,AttAppid=%d,len=%d", srcApp.AppId, m.GetInfo().GetAppInfo().GetId()) 30 | } 31 | 32 | func handleRoomListReq(args []interface{}) { 33 | b := args[n.DataIndex].(n.BaseMessage) 34 | m := (b.MyMessage).(*list.RoomListReq) 35 | srcData := args[n.OtherIndex].(*gateway.TransferDataReq) 36 | 37 | log.Debug("", "收到列表请求,listID=%d", m.GetListId()) 38 | 39 | var rsp list.RoomListRsp 40 | for _, r := range roomList { 41 | room := new(types.RoomInfo) 42 | room = r 43 | rsp.Rooms = append(rsp.Rooms, room) 44 | } 45 | rspBm := n.BaseMessage{MyMessage: &rsp, TraceId: ""} 46 | rspBm.Cmd = n.TCPCommand{AppType: uint16(n.AppList), CmdId: uint16(list.CMDList_IDRoomListRsp)} 47 | g.SendMessage2Client(rspBm, srcData.GetGateconnid(), 0) 48 | } 49 | -------------------------------------------------------------------------------- /configs/config/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "Name": "config", 3 | "Type": 3, 4 | "Id": 60, 5 | "CenterAddr": "127.0.0.1:10050", 6 | "ListenOnAddr": "", 7 | "LoggerAddr": "127.0.0.1:10001", 8 | "UseApollo": false, 9 | "Config": { 10 | "appID": "mango-apollo", 11 | "cluster": "dev", 12 | "ip": "http://192.168.22.47:8080", 13 | "namespaceName": "application", 14 | "isBackupConfig": true, 15 | "secret": "" 16 | }, 17 | "CommonServers": [ 18 | { 19 | "namespaceName":"configs/config/center.json", 20 | "serverType":2, 21 | "serverID":0 22 | }, 23 | { 24 | "namespaceName":"configs/config/gateway-100.json", 25 | "serverType":4, 26 | "serverID":100 27 | }, 28 | { 29 | "namespaceName":"configs/config/lobby.json", 30 | "serverType":5, 31 | "serverID":0 32 | }, 33 | { 34 | "namespaceName":"configs/config/property.json", 35 | "serverType":6, 36 | "serverID":0 37 | }, 38 | { 39 | "namespaceName":"configs/config/list.json", 40 | "serverType":10, 41 | "serverID":0 42 | }, 43 | { 44 | "namespaceName":"configs/config/daemon-300.json", 45 | "serverType":100, 46 | "serverID":0 47 | }, 48 | { 49 | "namespaceName":"configs/config/table-1000.json", 50 | "serverType":11, 51 | "serverID":1000 52 | }, 53 | { 54 | "namespaceName":"configs/config/room-2000.json", 55 | "serverType":12, 56 | "serverID":2000 57 | }, 58 | { 59 | "namespaceName":"configs/config/robot-3000.json", 60 | "serverType":9, 61 | "serverID":3000 62 | } 63 | ] 64 | } -------------------------------------------------------------------------------- /pkg/util/map.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | type Map struct { 8 | sync.RWMutex 9 | m map[interface{}]interface{} 10 | } 11 | 12 | func (m *Map) init() { 13 | if m.m == nil { 14 | m.m = make(map[interface{}]interface{}) 15 | } 16 | } 17 | 18 | func (m *Map) UnsafeGet(key interface{}) interface{} { 19 | if m.m == nil { 20 | return nil 21 | } else { 22 | return m.m[key] 23 | } 24 | } 25 | 26 | func (m *Map) Get(key interface{}) interface{} { 27 | m.RLock() 28 | defer m.RUnlock() 29 | return m.UnsafeGet(key) 30 | } 31 | 32 | func (m *Map) UnsafeSet(key interface{}, value interface{}) { 33 | m.init() 34 | m.m[key] = value 35 | } 36 | 37 | func (m *Map) Set(key interface{}, value interface{}) { 38 | m.Lock() 39 | defer m.Unlock() 40 | m.UnsafeSet(key, value) 41 | } 42 | 43 | func (m *Map) TestAndSet(key interface{}, value interface{}) interface{} { 44 | m.Lock() 45 | defer m.Unlock() 46 | 47 | m.init() 48 | 49 | if v, ok := m.m[key]; ok { 50 | return v 51 | } else { 52 | m.m[key] = value 53 | return nil 54 | } 55 | } 56 | 57 | func (m *Map) UnsafeDel(key interface{}) { 58 | m.init() 59 | delete(m.m, key) 60 | } 61 | 62 | func (m *Map) Del(key interface{}) { 63 | m.Lock() 64 | defer m.Unlock() 65 | m.UnsafeDel(key) 66 | } 67 | 68 | func (m *Map) UnsafeLen() int { 69 | if m.m == nil { 70 | return 0 71 | } else { 72 | return len(m.m) 73 | } 74 | } 75 | 76 | func (m *Map) Len() int { 77 | m.RLock() 78 | defer m.RUnlock() 79 | return m.UnsafeLen() 80 | } 81 | 82 | func (m *Map) UnsafeRange(f func(interface{}, interface{})) { 83 | if m.m == nil { 84 | return 85 | } 86 | for k, v := range m.m { 87 | f(k, v) 88 | } 89 | } 90 | 91 | func (m *Map) RLockRange(f func(interface{}, interface{})) { 92 | m.RLock() 93 | defer m.RUnlock() 94 | m.UnsafeRange(f) 95 | } 96 | 97 | func (m *Map) LockRange(f func(interface{}, interface{})) { 98 | m.Lock() 99 | defer m.Unlock() 100 | m.UnsafeRange(f) 101 | } 102 | -------------------------------------------------------------------------------- /pkg/util/deepcopy.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | // reference: https://github.com/mohae/deepcopy 4 | import ( 5 | "reflect" 6 | ) 7 | 8 | func deepCopy(dst, src reflect.Value) { 9 | switch src.Kind() { 10 | case reflect.Interface: 11 | value := src.Elem() 12 | if !value.IsValid() { 13 | return 14 | } 15 | newValue := reflect.New(value.Type()).Elem() 16 | deepCopy(newValue, value) 17 | dst.Set(newValue) 18 | case reflect.Ptr: 19 | value := src.Elem() 20 | if !value.IsValid() { 21 | return 22 | } 23 | dst.Set(reflect.New(value.Type())) 24 | deepCopy(dst.Elem(), value) 25 | case reflect.Map: 26 | dst.Set(reflect.MakeMap(src.Type())) 27 | keys := src.MapKeys() 28 | for _, key := range keys { 29 | value := src.MapIndex(key) 30 | newValue := reflect.New(value.Type()).Elem() 31 | deepCopy(newValue, value) 32 | dst.SetMapIndex(key, newValue) 33 | } 34 | case reflect.Slice: 35 | dst.Set(reflect.MakeSlice(src.Type(), src.Len(), src.Cap())) 36 | for i := 0; i < src.Len(); i++ { 37 | deepCopy(dst.Index(i), src.Index(i)) 38 | } 39 | case reflect.Struct: 40 | typeSrc := src.Type() 41 | for i := 0; i < src.NumField(); i++ { 42 | value := src.Field(i) 43 | tag := typeSrc.Field(i).Tag 44 | if value.CanSet() && tag.Get("deepcopy") != "-" { 45 | deepCopy(dst.Field(i), value) 46 | } 47 | } 48 | default: 49 | dst.Set(src) 50 | } 51 | } 52 | 53 | func DeepCopy(dst, src interface{}) { 54 | typeDst := reflect.TypeOf(dst) 55 | typeSrc := reflect.TypeOf(src) 56 | if typeDst != typeSrc { 57 | panic("DeepCopy: " + typeDst.String() + " != " + typeSrc.String()) 58 | } 59 | if typeSrc.Kind() != reflect.Ptr { 60 | panic("DeepCopy: pass arguments by address") 61 | } 62 | 63 | valueDst := reflect.ValueOf(dst).Elem() 64 | valueSrc := reflect.ValueOf(src).Elem() 65 | if !valueDst.IsValid() || !valueSrc.IsValid() { 66 | panic("DeepCopy: invalid arguments") 67 | } 68 | 69 | deepCopy(valueDst, valueSrc) 70 | } 71 | 72 | func DeepClone(v interface{}) interface{} { 73 | dst := reflect.New(reflect.TypeOf(v)).Elem() 74 | deepCopy(dst, reflect.ValueOf(v)) 75 | return dst.Interface() 76 | } 77 | -------------------------------------------------------------------------------- /api/proto/types.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | package bs.types; 3 | option go_package ="mango/api/types"; 4 | //option go_package ="/types"; 5 | 6 | message ErrorInfo{ 7 | optional int32 code = 1; 8 | optional string info = 2; 9 | } 10 | 11 | enum PropType{ 12 | Score = 1; 13 | } 14 | 15 | message PropItem{ 16 | optional PropType prop_id = 1; 17 | optional int64 prop_count = 2; 18 | } 19 | 20 | message BaseAppInfo{ 21 | optional string name = 1; 22 | optional uint32 type = 2; 23 | optional uint32 id = 3; 24 | optional uint32 version = 4; 25 | } 26 | 27 | message BaseUserInfo { 28 | enum UserType{ 29 | UNKNOW = 0; //未知 30 | Normal = 1; //正常类型 31 | Robot = 10; //机器人 32 | } 33 | optional string account = 1; //用户账号 34 | optional uint64 user_id = 2; //用户ID 35 | optional uint64 game_id = 3; //数字ID 36 | optional uint32 gender = 4; //性别 37 | optional uint32 face_id = 5; //头像id 38 | optional string custom_face = 6; //自定义的图像地址 39 | optional string nick_name = 7; //昵称 40 | optional UserType user_type = 8; //用户类别 41 | repeated PropItem user_props = 9; //用户道具 42 | optional uint32 market_id = 10; //登录主渠道 43 | optional uint32 site_id = 11; //登录子渠道 44 | optional uint32 reg_market_id = 12; //注册主渠道 45 | optional uint32 reg_site_id = 13; //注册子渠道 46 | optional string register_data = 14; //注册时间 47 | optional uint64 gate_connid = 15; //关联的gate连接id 48 | } 49 | 50 | message UserRoomInfo{ 51 | optional BaseUserInfo base_info = 1; //基础信息 52 | optional uint64 table_id = 2; //所有桌子 53 | optional uint32 seat_id = 3; //所在位置 54 | optional uint32 user_state = 4; //用户状态 55 | optional uint32 lost_count = 5; //玩家总输局 56 | optional uint32 draw_count = 6; //玩家总平局 57 | optional uint32 win_count = 7; //玩家总胜局 58 | } 59 | 60 | message RoomInfo{ 61 | enum RoomType{ 62 | Gold = 0x0001; //金币 63 | Private = 0x0010; //私有 64 | RedPack = 0x0020; //红包 65 | } 66 | optional BaseAppInfo app_info = 1; 67 | optional uint32 kind = 2; 68 | optional RoomType type = 3; 69 | optional uint32 level = 4; 70 | optional string name = 5; 71 | optional int64 base_score = 6; 72 | optional int64 join_min = 7; 73 | optional int64 join_max = 8; 74 | optional int64 out_score = 9; 75 | optional int64 win_limit = 10; 76 | } -------------------------------------------------------------------------------- /pkg/console/console.go: -------------------------------------------------------------------------------- 1 | package console 2 | 3 | import ( 4 | "bufio" 5 | "mango/pkg/conf" 6 | "mango/pkg/log" 7 | "mango/pkg/network" 8 | "github.com/golang/protobuf/proto" 9 | "math" 10 | "net" 11 | "strconv" 12 | "strings" 13 | ) 14 | 15 | var server *network.TCPServer 16 | 17 | func Init() { 18 | if conf.AppInfo.Type != network.AppCenter { 19 | return 20 | } 21 | 22 | server = new(network.TCPServer) 23 | server.Addr = "localhost:" + strconv.Itoa(int(conf.DefaultBasePort+conf.AppInfo.Id+1)) 24 | server.MaxConnNum = math.MaxInt32 25 | server.PendingWriteNum = 100 26 | server.NewAgent = newAgent 27 | 28 | log.Debug("", "start,addr=%v", server.Addr) 29 | 30 | server.Start() 31 | } 32 | 33 | func Destroy() { 34 | if server != nil { 35 | server.Close() 36 | } 37 | } 38 | 39 | type Agent struct { 40 | conn *network.TCPConn 41 | reader *bufio.Reader 42 | } 43 | 44 | func newAgent(conn *network.TCPConn) network.AgentClient { 45 | a := new(Agent) 46 | a.conn = conn 47 | a.reader = bufio.NewReader(conn) 48 | return a 49 | } 50 | 51 | func (a *Agent) Run() { 52 | for { 53 | 54 | a.conn.Write([]byte("\r\nankots$")) 55 | 56 | line, err := a.reader.ReadString('\n') 57 | if err != nil { 58 | break 59 | } 60 | line = strings.TrimSuffix(line[:len(line)-1], "\r") 61 | 62 | args := strings.Fields(line) 63 | if len(args) == 0 { 64 | continue 65 | } 66 | if args[0] == "quit" { 67 | break 68 | } 69 | var c Command 70 | for _, _c := range commands { 71 | if _c.name() == args[0] { 72 | c = _c 73 | break 74 | } 75 | } 76 | if c == nil { 77 | a.conn.Write([]byte("command not found, try `help` for help\r\n")) 78 | continue 79 | } 80 | output := c.run(args[1:]) 81 | if output != "" { 82 | a.conn.Write([]byte("\r\n" + output + "\r\n")) 83 | } 84 | } 85 | } 86 | 87 | func (a *Agent) OnClose() {} 88 | 89 | func (a *Agent) SendData(appType, cmdId uint32, m proto.Message) {} 90 | func (a *Agent) Close() {} 91 | func (a *Agent) Destroy() {} 92 | func (a *Agent) AgentInfo() *network.BaseAgentInfo { 93 | return nil 94 | } 95 | func (a *Agent) LocalAddr() net.Addr { 96 | return nil 97 | } 98 | func (a *Agent) RemoteAddr() net.Addr { 99 | return nil 100 | } 101 | -------------------------------------------------------------------------------- /cmd/room/business/table/table.go: -------------------------------------------------------------------------------- 1 | package table 2 | 3 | import ( 4 | "mango/pkg/conf/apollo" 5 | g "mango/pkg/gate" 6 | "mango/pkg/log" 7 | n "mango/pkg/network" 8 | "github.com/golang/protobuf/proto" 9 | tCMD "mango/api/table" 10 | "mango/cmd/room/business/player" 11 | ) 12 | 13 | const ( 14 | All uint32 = 0 15 | Free uint32 = 1 16 | InUse uint32 = 2 17 | ) 18 | 19 | var ( 20 | gameTables = make(map[uint64]*Table) 21 | ) 22 | 23 | type Table struct { 24 | Id uint64 25 | Players map[uint32]*player.Player 26 | } 27 | 28 | func NewTable(id uint64) { 29 | if _, ok := gameTables[id]; ok { 30 | log.Warning("", "已存在,id=%v", id) 31 | return 32 | } 33 | t := new(Table) 34 | t.Id = id 35 | t.Players = make(map[uint32]*player.Player) 36 | gameTables[id] = t 37 | } 38 | 39 | func CheckApplyTable() { 40 | maxCount := int(apollo.GetConfigAsInt64("最大桌子数量", 3000)) 41 | tableAppID := apollo.GetConfigAsInt64("桌子服务AppID", 0) 42 | if tableAppID == 0 || GetTableCount(All) > maxCount { 43 | return 44 | } 45 | 46 | if GetTableCount(Free) == 0 && GetTableCount(All) <= maxCount { 47 | var req tCMD.ApplyReq 48 | req.ApplyCount = proto.Uint32(uint32(apollo.GetConfigAsInt64("申请桌子数", 1000))) 49 | g.SendData2App(n.AppTable, uint32(tableAppID), n.AppTable, uint32(tCMD.CMDTable_IDApplyReq), &req) 50 | } 51 | } 52 | 53 | func GetAFreeTable() *Table { 54 | if GetTableCount(Free) == 0 { 55 | return nil 56 | } 57 | for _, v := range gameTables { 58 | if len(v.Players) == 0 { 59 | return v 60 | } 61 | } 62 | return nil 63 | } 64 | 65 | func GetTableCount(tableType uint32) int { 66 | switch tableType { 67 | case All: 68 | return len(gameTables) 69 | case Free: 70 | count := 0 71 | for _, v := range gameTables { 72 | if len(v.Players) == 0 { 73 | count++ 74 | } 75 | } 76 | return count 77 | case InUse: 78 | count := 0 79 | for _, v := range gameTables { 80 | if len(v.Players) != 0 { 81 | count++ 82 | } 83 | } 84 | return count 85 | default: 86 | break 87 | } 88 | return 0 89 | } 90 | func GameOver(id uint64) { 91 | if _, ok := gameTables[id]; !ok { 92 | log.Warning("", "结束,不存在,id=%v", id) 93 | return 94 | } 95 | 96 | gameTables[id].Players = make(map[uint32]*player.Player) 97 | 98 | log.Debug("", "桌子信息,all=%v,free=%v,inuse=%v", 99 | GetTableCount(All), GetTableCount(Free), GetTableCount(InUse)) 100 | } 101 | -------------------------------------------------------------------------------- /pkg/network/tcp_conn.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | import ( 4 | "net" 5 | "sync" 6 | ) 7 | 8 | type ConnSet map[net.Conn]struct{} 9 | 10 | type TCPConn struct { 11 | sync.Mutex 12 | conn net.Conn 13 | writeChan chan []byte 14 | closeFlag bool 15 | msgParser *MsgParser 16 | } 17 | 18 | func newTCPConn(conn net.Conn, pendingWriteNum int, msgParser *MsgParser) *TCPConn { 19 | tcpConn := new(TCPConn) 20 | tcpConn.conn = conn 21 | tcpConn.writeChan = make(chan []byte, pendingWriteNum) 22 | tcpConn.msgParser = msgParser 23 | 24 | go func() { 25 | for b := range tcpConn.writeChan { 26 | if b == nil { 27 | break 28 | } 29 | 30 | _, err := conn.Write(b) 31 | if err != nil { 32 | break 33 | } 34 | } 35 | 36 | conn.Close() 37 | tcpConn.Lock() 38 | tcpConn.closeFlag = true 39 | tcpConn.Unlock() 40 | }() 41 | 42 | return tcpConn 43 | } 44 | 45 | func (tcpConn *TCPConn) doDestroy() { 46 | tcpConn.conn.(*net.TCPConn).SetLinger(0) 47 | tcpConn.conn.Close() 48 | 49 | if !tcpConn.closeFlag { 50 | close(tcpConn.writeChan) 51 | tcpConn.closeFlag = true 52 | } 53 | } 54 | 55 | func (tcpConn *TCPConn) Destroy() { 56 | tcpConn.Lock() 57 | defer tcpConn.Unlock() 58 | 59 | tcpConn.doDestroy() 60 | } 61 | 62 | func (tcpConn *TCPConn) Close() { 63 | tcpConn.Lock() 64 | defer tcpConn.Unlock() 65 | if tcpConn.closeFlag { 66 | return 67 | } 68 | 69 | tcpConn.doWrite(nil) 70 | tcpConn.closeFlag = true 71 | } 72 | 73 | func (tcpConn *TCPConn) doWrite(b []byte) { 74 | tcpConn.writeChan <- b 75 | } 76 | 77 | // b must not be modified by the others goroutines 78 | func (tcpConn *TCPConn) Write(b []byte) { 79 | tcpConn.Lock() 80 | defer tcpConn.Unlock() 81 | if tcpConn.closeFlag || b == nil { 82 | return 83 | } 84 | 85 | tcpConn.doWrite(b) 86 | } 87 | 88 | func (tcpConn *TCPConn) Read(b []byte) (int, error) { 89 | return tcpConn.conn.Read(b) 90 | } 91 | 92 | func (tcpConn *TCPConn) LocalAddr() net.Addr { 93 | return tcpConn.conn.LocalAddr() 94 | } 95 | 96 | func (tcpConn *TCPConn) RemoteAddr() net.Addr { 97 | return tcpConn.conn.RemoteAddr() 98 | } 99 | 100 | func (tcpConn *TCPConn) ReadMsg() (BaseMessage, []byte, error) { 101 | return tcpConn.msgParser.Read(tcpConn) 102 | } 103 | 104 | func (tcpConn *TCPConn) WriteMsg(appType, cmdId uint16, msgData, otherData []byte) error { 105 | return tcpConn.msgParser.Write(appType, cmdId, tcpConn, msgData, otherData) 106 | } 107 | -------------------------------------------------------------------------------- /pkg/util/gorsa/rsa_test.go: -------------------------------------------------------------------------------- 1 | package gorsa 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | var Publickey = `-----BEGIN RSA PRIVATE KEY----- 8 | MIGdMA0GCSqGSIb3DQEBAQUAA4GLADCBhwKBgQCgNCfas9omwx2CuSa3VPjDEdC9crw+kdSFSCkAOnF1cIzR6UCuzbLEgGmXqdRKKuHnJIMpZHPWg3bW+fIkv2nP6jY+HZVy7LgEPDYTSYgo5lMMYdfgHJB5iURA89x/h7tnOO3i4lQLqqAEL6IOk+iDtaj7eUE/NgBuor98gW+yBwIBAw== 9 | -----END RSA PRIVATE KEY----- 10 | ` 11 | 12 | var str = `UJ24diX/vlJOPgycQujgtUWRsxAGqGdRYDjFUwClCBbUSOOYdxhT1vJJakgQjefUBYbPV9RIsNfjQe68NsRd9YKBC7g7VkDxYxK06KhAgUBQHRxLxkvhx3spJe6+DvCsTXFhKWvfPfMh1n6oXbWkG/COmQtp9DxuMGNBV6tCJcU=` 13 | 14 | var Pirvatekey = `-----BEGIN RSA PRIVATE KEY----- 15 | MIICdAIBADANBgkqhkiG9w0BAQEFAASCAl4wggJaAgEAAoGBAKNxgr/o1Wp2jpCPU3U/A9AmQOy7yOwzkRW67ThBUV+aEiIjV+6N2ZePC9qblV1in0U9GICKXIdVl5cSZfrJnwDJdZ2FhEaRiZvi2Zuf1OekpiAWvXcOlcCm3PAZHKOregYmB/pQfr+wQc9KQ9n7dnibFetEGf7YN2EzCtWG8VSVAgEDAoGAGz2VyqbOPGkXwsKN6N/V+AZgJ3ShfLNC2PR83rWNj+8DBbCOp8JO7pfXTxnuOjsai4ouwBcPa+OZQ9hmVHbv1TEajOWbXpknCC4L4drn4l6ZrJ1ds1eiqF4kfZTVvHebP7YSACK0dWyT+U4tA4eD6TMCdcdx+cQ4uNAMy0zW5m0CQQD+TZ/jwPRx9qOWbluZwMZbpx+DAzvH1uYeUTRimwt3r+U0esBKmD06A8XQCZquwYH3OLSMnoy6FFAzdZsmV3ujAkEApIiwQB8aiKjHOCP05KTTEWT044gHOO7oU7DKOX8tZiairSE5NavB6sYxpSwqH51/cc50Cs+XhM68H0h2k5ByZwJBAKmJFUKAovakbQ70PRErLufEv6ys0oU57r7gzZcSB6Uf7iL8gDG603wCg+AGZx8rq/olzbMUXdFi4CJOZ27k/RcCQG2wdYAUvFsbL3rCo0MYjLZDTe0FWiX0muJ13CZUyO7EbHNre3kdK/HZdm4dcWpo/6E0TVyKZQM0fWowTw0K9u8CQH5GSr9mziPBRHdX8xf0zlEUEwxvO584qsXDsjPBA1eX6KS2ndNdYEdLAHsEhJbQVhqa/KOR2AsMzUTdx3VsD7Y= 16 | -----END RSA PRIVATE KEY----- 17 | ` 18 | 19 | func TestEncrypt(t *testing.T) { 20 | var tests = []string{ 21 | "abasdf中222国", 22 | "12345678", 23 | "sjgfjvbj", 24 | } 25 | for _, test := range tests { 26 | enc, _ := Encrypt([]byte(test), []byte(Publickey)) 27 | 28 | t.Log(string(enc)) 29 | got, _ := Decrypt(enc, []byte(Pirvatekey), PKCS8) 30 | if string(got) != test { 31 | t.Errorf("Failed (%q) = %v", test, string(got)) 32 | } 33 | } 34 | } 35 | 36 | func TestSign(t *testing.T) { 37 | var tests = []string{ 38 | "abasdf中222国", 39 | "12345678", 40 | "sjgfjvbj", 41 | } 42 | for _, test := range tests { 43 | sign, _ := Sign([]byte(test), []byte(Pirvatekey), PKCS1) 44 | err := SignVer([]byte(test), sign, []byte(Publickey)) 45 | if err != nil { 46 | t.Errorf("Failed %s", test) 47 | } 48 | } 49 | } 50 | 51 | func TestDecrypt(t *testing.T) { 52 | data, err := Decrypt([]byte(str), []byte(Pirvatekey), PKCS8) 53 | if err != nil { 54 | t.Errorf("Failed %s", err) 55 | } 56 | t.Logf("%s", string(data)) 57 | } 58 | -------------------------------------------------------------------------------- /pkg/chanrpc/example_test.go: -------------------------------------------------------------------------------- 1 | package chanrpc_test 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "mango/pkg/chanrpc" 7 | ) 8 | 9 | func Example() { 10 | s := chanrpc.NewServer(10) 11 | 12 | var wg sync.WaitGroup 13 | wg.Add(1) 14 | 15 | // goroutine 1 16 | go func() { 17 | s.Register("f0", func(args []interface{}) { 18 | 19 | }) 20 | 21 | s.Register("f1", func(args []interface{}) interface{} { 22 | return 1 23 | }) 24 | 25 | s.Register("fn", func(args []interface{}) []interface{} { 26 | return []interface{}{1, 2, 3} 27 | }) 28 | 29 | s.Register("add", func(args []interface{}) interface{} { 30 | n1 := args[0].(int) 31 | n2 := args[1].(int) 32 | return n1 + n2 33 | }) 34 | 35 | wg.Done() 36 | 37 | for { 38 | s.Exec(<-s.ChanCall) 39 | } 40 | }() 41 | 42 | wg.Wait() 43 | wg.Add(1) 44 | 45 | // goroutine 2 46 | go func() { 47 | c := s.Open(10) 48 | 49 | // sync 50 | err := c.Call0("f0") 51 | if err != nil { 52 | fmt.Println(err) 53 | } 54 | 55 | r1, err := c.Call1("f1") 56 | if err != nil { 57 | fmt.Println(err) 58 | } else { 59 | fmt.Println(r1) 60 | } 61 | 62 | rn, err := c.CallN("fn") 63 | if err != nil { 64 | fmt.Println(err) 65 | } else { 66 | fmt.Println(rn[0], rn[1], rn[2]) 67 | } 68 | 69 | ra, err := c.Call1("add", 1, 2) 70 | if err != nil { 71 | fmt.Println(err) 72 | } else { 73 | fmt.Println(ra) 74 | } 75 | 76 | // asyn 77 | c.AsynCall("f0", func(err error) { 78 | if err != nil { 79 | fmt.Println(err) 80 | } 81 | }) 82 | 83 | c.AsynCall("f1", func(ret interface{}, err error) { 84 | if err != nil { 85 | fmt.Println(err) 86 | } else { 87 | fmt.Println(ret) 88 | } 89 | }) 90 | 91 | c.AsynCall("fn", func(ret []interface{}, err error) { 92 | if err != nil { 93 | fmt.Println(err) 94 | } else { 95 | fmt.Println(ret[0], ret[1], ret[2]) 96 | } 97 | }) 98 | 99 | c.AsynCall("add", 1, 2, func(ret interface{}, err error) { 100 | if err != nil { 101 | fmt.Println(err) 102 | } else { 103 | fmt.Println(ret) 104 | } 105 | }) 106 | 107 | c.Cb(<-c.ChanAsynRet) 108 | c.Cb(<-c.ChanAsynRet) 109 | c.Cb(<-c.ChanAsynRet) 110 | c.Cb(<-c.ChanAsynRet) 111 | 112 | // go 113 | s.Go("f0") 114 | 115 | wg.Done() 116 | }() 117 | 118 | wg.Wait() 119 | 120 | // Output: 121 | // 1 122 | // 1 2 3 123 | // 3 124 | // 1 125 | // 1 2 3 126 | // 3 127 | } 128 | -------------------------------------------------------------------------------- /pkg/amqp/rabbitmq.go: -------------------------------------------------------------------------------- 1 | package amqp 2 | 3 | import ( 4 | "encoding/json" 5 | "mango/pkg/chanrpc" 6 | "mango/pkg/log" 7 | "github.com/GoldBaby5511/go-simplejson" 8 | "github.com/streadway/amqp" 9 | ) 10 | 11 | const ( 12 | RabbitMqMessageNotifyId string = "RabbitMqMessageNotifyId" 13 | ) 14 | 15 | type RabbitMQMessage struct { 16 | Time int64 `json:"timestamp"` 17 | Body string `json:"body"` 18 | } 19 | 20 | var ( 21 | MsgRouter *chanrpc.Server = nil 22 | conn *amqp.Connection = nil 23 | ch *amqp.Channel = nil 24 | ) 25 | 26 | func NewConsumer(c string) { 27 | if c == "" { 28 | return 29 | } 30 | var err error 31 | var jsonConfig *simplejson.Json 32 | jsonConfig, err = simplejson.NewJson([]byte(c)) 33 | if err != nil { 34 | log.Warning("database", "RabbitMq配置异常,MqConfig=%v,err=%v", c, err) 35 | return 36 | } 37 | url := jsonConfig.Get("url").MustString("") 38 | conn, err = amqp.Dial(url) 39 | if err != nil { 40 | log.Error("", "异常,MQ连接失败,url=%v,err=%v", url, err) 41 | return 42 | } 43 | ch, err = conn.Channel() 44 | if err != nil { 45 | log.Error("", "异常,连接channel失败,err=%v", err) 46 | return 47 | } 48 | queueName := jsonConfig.Get("queueName").MustString("") 49 | q, err := ch.QueueDeclare( 50 | queueName, // name 51 | false, // durable 52 | false, // delete when unused 53 | false, // exclusive 54 | false, // no-wait 55 | nil, // arguments 56 | ) 57 | if err != nil { 58 | log.Error("", "异常,创建队列失败,name=%v,err=%v", queueName, err) 59 | return 60 | } 61 | msg, err := ch.Consume( 62 | q.Name, // queue 63 | "", // consumer 64 | true, // auto-ack 65 | false, // exclusive 66 | false, // no-local 67 | false, // no-wait 68 | nil, // args 69 | ) 70 | if err != nil { 71 | log.Error("", "异常,注册消费者失败,name=%v,err=%v", q.Name, err) 72 | return 73 | } 74 | go func() { 75 | for d := range msg { 76 | if MsgRouter != nil { 77 | log.Debug("", "Received a message: %s", d.Body) 78 | var m RabbitMQMessage 79 | if err := json.Unmarshal(d.Body, &m); err == nil { 80 | MsgRouter.Go(RabbitMqMessageNotifyId, m) 81 | } else { 82 | log.Warning("", "消息序列化失败?,body=%v", d.Body) 83 | } 84 | } else { 85 | log.Warning("", "没有消息路由?,body=%v", d.Body) 86 | } 87 | } 88 | }() 89 | 90 | log.Info("", "MQ连接成功,url=%v,queue=%v", url, queueName) 91 | } 92 | 93 | func Close() { 94 | if conn != nil { 95 | conn.Close() 96 | } 97 | if ch != nil { 98 | ch.Close() 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /cmd/property/business/business.go: -------------------------------------------------------------------------------- 1 | package business 2 | 3 | import ( 4 | "github.com/golang/protobuf/proto" 5 | "mango/api/property" 6 | "mango/api/types" 7 | g "mango/pkg/gate" 8 | "mango/pkg/log" 9 | n "mango/pkg/network" 10 | ) 11 | 12 | var ( 13 | userList = make(map[uint64]int64) 14 | ) 15 | 16 | func init() { 17 | g.MsgRegister(&property.QueryPropertyReq{}, n.AppProperty, uint16(property.CMDProperty_IDQueryPropertyReq), handleQueryPropertyReq) 18 | g.MsgRegister(&property.ModifyPropertyReq{}, n.AppProperty, uint16(property.CMDProperty_IDModifyPropertyReq), handleModifyPropertyReq) 19 | g.EventRegister(g.ConnectSuccess, connectSuccess) 20 | g.EventRegister(g.Disconnect, disconnect) 21 | } 22 | 23 | func connectSuccess(args []interface{}) { 24 | } 25 | 26 | func disconnect(args []interface{}) { 27 | } 28 | 29 | func handleQueryPropertyReq(args []interface{}) { 30 | b := args[n.DataIndex].(n.BaseMessage) 31 | m := (b.MyMessage).(*property.QueryPropertyReq) 32 | srcApp := b.AgentInfo 33 | 34 | if _, ok := userList[m.GetUserId()]; !ok { 35 | userList[m.GetUserId()] = 1000000000 36 | } 37 | 38 | log.Debug("", "收到查询,appId=%d,userId=%d", srcApp.AppId, m.GetUserId()) 39 | 40 | var rsp property.QueryPropertyRsp 41 | rsp.UserId = proto.Uint64(m.GetUserId()) 42 | p := new(types.PropItem) 43 | p.PropId = (*types.PropType)(proto.Int32(int32(types.PropType_Score))) 44 | p.PropCount = proto.Int64(userList[m.GetUserId()]) 45 | rsp.UserProps = append(rsp.UserProps, p) 46 | cmd := n.TCPCommand{AppType: uint16(n.AppProperty), CmdId: uint16(property.CMDProperty_IDQueryPropertyRsp)} 47 | bm := n.BaseMessage{MyMessage: &rsp, Cmd: cmd} 48 | g.SendData(srcApp, bm) 49 | } 50 | 51 | func handleModifyPropertyReq(args []interface{}) { 52 | b := args[n.DataIndex].(n.BaseMessage) 53 | m := (b.MyMessage).(*property.ModifyPropertyReq) 54 | 55 | if _, ok := userList[m.GetUserId()]; !ok { 56 | userList[m.GetUserId()] = 1000000000 57 | } 58 | 59 | log.Debug("", "收到修改,appId=%d,userId=%d,opType=%v", b.AgentInfo.AppId, m.GetUserId(), m.GetOpType()) 60 | 61 | if m.GetOpType() == 0 { 62 | userList[m.GetUserId()] += 100 63 | } else { 64 | userList[m.GetUserId()] -= 100 65 | } 66 | 67 | var rsp property.ModifyPropertyRsp 68 | rsp.UserId = proto.Uint64(m.GetUserId()) 69 | rsp.OpType = proto.Int32(m.GetOpType()) 70 | p := new(types.PropItem) 71 | p.PropId = (*types.PropType)(proto.Int32(int32(types.PropType_Score))) 72 | p.PropCount = proto.Int64(100) 73 | rsp.UserProps = append(rsp.UserProps, p) 74 | cmd := n.TCPCommand{AppType: uint16(n.AppProperty), CmdId: uint16(property.CMDProperty_IDModifyPropertyRsp)} 75 | bm := n.BaseMessage{MyMessage: &rsp, Cmd: cmd} 76 | g.SendData(b.AgentInfo, bm) 77 | } 78 | -------------------------------------------------------------------------------- /cmd/robot/business/game/ddz/ddz.go: -------------------------------------------------------------------------------- 1 | package ddz 2 | 3 | import ( 4 | "github.com/golang/protobuf/proto" 5 | "mango/api/gameddz" 6 | "mango/cmd/robot/business/game" 7 | "mango/pkg/log" 8 | n "mango/pkg/network" 9 | "math/rand" 10 | "time" 11 | ) 12 | 13 | type Ddz struct { 14 | f game.Frame 15 | } 16 | 17 | func NewDdz(f game.Frame) game.Sink { 18 | d := new(Ddz) 19 | d.f = f 20 | return d 21 | } 22 | 23 | func (d *Ddz) GameMessage(seatId, cmdId uint32, data []byte) { 24 | switch cmdId { 25 | case uint32(gameddz.CMDGameddz_IDGameStart): 26 | d.GameStart(seatId, data) 27 | case uint32(gameddz.CMDGameddz_IDOutCardRsp): 28 | d.OutCardRsp(seatId, data) 29 | case uint32(gameddz.CMDGameddz_IDGameOver): 30 | d.GameOver(seatId, data) 31 | default: 32 | log.Warning("", "未定义消息,seatId=%d,cmdId=%d", seatId, cmdId) 33 | } 34 | } 35 | 36 | func (d *Ddz) GameStart(seatId uint32, data []byte) { 37 | var m gameddz.GameStart 38 | _ = proto.Unmarshal(data, &m) 39 | 40 | d.f.GetMyInfo().Scene = game.Begin 41 | log.Debug("", "游戏开始,UserId=%v,a=%v,TableId=%v,CurrentSeat=%v,p.SeatId=%v", 42 | d.f.GetMyInfo().UserId, d.f.GetMyInfo().Account, d.f.GetMyInfo().TableId, m.GetCurrentSeat(), d.f.GetMyInfo().SeatId) 43 | 44 | if d.f.GetMyInfo().SeatId == m.GetCurrentSeat() { 45 | d.f.AfterFunc(time.Duration(rand.Intn(3)+1)*time.Second, d.outCards) 46 | } 47 | } 48 | 49 | func (d *Ddz) OutCardRsp(seatId uint32, data []byte) { 50 | var m gameddz.OutCardRsp 51 | _ = proto.Unmarshal(data, &m) 52 | 53 | log.Debug("", "收到出牌消息,UserId=%v,a=%v,CurrentSeat=%v,SeatId=%v", 54 | d.f.GetMyInfo().UserId, d.f.GetMyInfo().Account, m.GetCurrentSeat(), d.f.GetMyInfo().SeatId) 55 | 56 | if d.f.GetMyInfo().SeatId == m.GetCurrentSeat() { 57 | d.f.AfterFunc(time.Duration(rand.Intn(3)+1)*time.Second, d.outCards) 58 | } 59 | } 60 | 61 | func (d *Ddz) GameOver(seatId uint32, data []byte) { 62 | var m gameddz.GameOver 63 | _ = proto.Unmarshal(data, &m) 64 | 65 | log.Debug("", "游戏结束消息,UserId=%v,a=%v", d.f.GetMyInfo().UserId, d.f.GetMyInfo().Account) 66 | 67 | d.f.GetMyInfo().Scene = game.Over 68 | d.f.GameOver() 69 | } 70 | 71 | func (d *Ddz) outCards() { 72 | if d.f.GetMyInfo().Scene == game.Over { 73 | return 74 | } 75 | 76 | log.Debug("", "出牌,UserId=%v,a=%v,SeatId=%v,Scene=%v", 77 | d.f.GetMyInfo().UserId, d.f.GetMyInfo().Account, d.f.GetMyInfo().SeatId, d.f.GetMyInfo().Scene) 78 | 79 | var req gameddz.OutCardReq 80 | for i := 0; i < rand.Intn(3)+1; i++ { 81 | req.OutCard = append(req.OutCard, byte(rand.Intn(3)+1)) 82 | } 83 | cmd := n.TCPCommand{AppType: uint16(n.AppTable), CmdId: uint16(gameddz.CMDGameddz_IDOutCardReq)} 84 | bm := n.BaseMessage{MyMessage: &req, Cmd: cmd} 85 | d.f.SendGameMessage(bm) 86 | } 87 | -------------------------------------------------------------------------------- /pkg/go/go.go: -------------------------------------------------------------------------------- 1 | package g 2 | 3 | import ( 4 | "container/list" 5 | "runtime" 6 | "sync" 7 | "mango/pkg/conf" 8 | "mango/pkg/log" 9 | ) 10 | 11 | // one Go per goroutine (goroutine not safe) 12 | type Go struct { 13 | ChanCb chan func() 14 | pendingGo int 15 | } 16 | 17 | type LinearGo struct { 18 | f func() 19 | cb func() 20 | } 21 | 22 | type LinearContext struct { 23 | g *Go 24 | linearGo *list.List 25 | mutexLinearGo sync.Mutex 26 | mutexExecution sync.Mutex 27 | } 28 | 29 | func New(l int) *Go { 30 | g := new(Go) 31 | g.ChanCb = make(chan func(), l) 32 | return g 33 | } 34 | 35 | func (g *Go) Go(f func(), cb func()) { 36 | g.pendingGo++ 37 | 38 | go func() { 39 | defer func() { 40 | g.ChanCb <- cb 41 | if r := recover(); r != nil { 42 | if conf.LenStackBuf > 0 { 43 | buf := make([]byte, conf.LenStackBuf) 44 | l := runtime.Stack(buf, false) 45 | log.Error("Go", "%v: %s", r, buf[:l]) 46 | } else { 47 | log.Error("Go", "%v", r) 48 | } 49 | } 50 | }() 51 | 52 | f() 53 | }() 54 | } 55 | 56 | func (g *Go) Cb(cb func()) { 57 | defer func() { 58 | g.pendingGo-- 59 | if r := recover(); r != nil { 60 | if conf.LenStackBuf > 0 { 61 | buf := make([]byte, conf.LenStackBuf) 62 | l := runtime.Stack(buf, false) 63 | log.Error("Go", "%v: %s", r, buf[:l]) 64 | } else { 65 | log.Error("Go", "%v", r) 66 | } 67 | } 68 | }() 69 | 70 | if cb != nil { 71 | cb() 72 | } 73 | } 74 | 75 | func (g *Go) Close() { 76 | for g.pendingGo > 0 { 77 | g.Cb(<-g.ChanCb) 78 | } 79 | } 80 | 81 | func (g *Go) Idle() bool { 82 | return g.pendingGo == 0 83 | } 84 | 85 | func (g *Go) NewLinearContext() *LinearContext { 86 | c := new(LinearContext) 87 | c.g = g 88 | c.linearGo = list.New() 89 | return c 90 | } 91 | 92 | func (c *LinearContext) Go(f func(), cb func()) { 93 | c.g.pendingGo++ 94 | 95 | c.mutexLinearGo.Lock() 96 | c.linearGo.PushBack(&LinearGo{f: f, cb: cb}) 97 | c.mutexLinearGo.Unlock() 98 | 99 | go func() { 100 | c.mutexExecution.Lock() 101 | defer c.mutexExecution.Unlock() 102 | 103 | c.mutexLinearGo.Lock() 104 | e := c.linearGo.Remove(c.linearGo.Front()).(*LinearGo) 105 | c.mutexLinearGo.Unlock() 106 | 107 | defer func() { 108 | c.g.ChanCb <- e.cb 109 | if r := recover(); r != nil { 110 | if conf.LenStackBuf > 0 { 111 | buf := make([]byte, conf.LenStackBuf) 112 | l := runtime.Stack(buf, false) 113 | log.Error("Go", "%v: %s", r, buf[:l]) 114 | } else { 115 | log.Error("Go", "%v", r) 116 | } 117 | } 118 | }() 119 | 120 | e.f() 121 | }() 122 | } 123 | -------------------------------------------------------------------------------- /pkg/timer/timer.go: -------------------------------------------------------------------------------- 1 | package timer 2 | 3 | import ( 4 | "runtime" 5 | "time" 6 | "mango/pkg/conf" 7 | "mango/pkg/log" 8 | ) 9 | 10 | const ( 11 | Invalid = -1 12 | LoopForever = 0 13 | ) 14 | 15 | // one dispatcher per goroutine (goroutine not safe) 16 | type Dispatcher struct { 17 | ChanTimer chan *Timer 18 | } 19 | 20 | func NewDispatcher(l int) *Dispatcher { 21 | disp := new(Dispatcher) 22 | disp.ChanTimer = make(chan *Timer, l) 23 | return disp 24 | } 25 | 26 | // Timer 27 | type Timer struct { 28 | t *time.Timer 29 | loopCount int 30 | cb func() 31 | } 32 | 33 | func (t *Timer) Stop() { 34 | t.t.Stop() 35 | t.cb = nil 36 | } 37 | 38 | func (t *Timer) Cb() { 39 | defer func() { 40 | if t.loopCount == Invalid { 41 | t.cb = nil 42 | } 43 | if r := recover(); r != nil { 44 | if conf.LenStackBuf > 0 { 45 | buf := make([]byte, conf.LenStackBuf) 46 | l := runtime.Stack(buf, false) 47 | log.Error("Timer", "%v: %s", r, buf[:l]) 48 | } else { 49 | log.Error("Timer", "%v", r) 50 | } 51 | } 52 | }() 53 | 54 | if t.cb != nil { 55 | t.cb() 56 | } 57 | } 58 | 59 | func (disp *Dispatcher) AfterFunc(d time.Duration, cb func()) *Timer { 60 | t := new(Timer) 61 | t.cb = cb 62 | t.loopCount = Invalid 63 | t.t = time.AfterFunc(d, func() { 64 | disp.ChanTimer <- t 65 | }) 66 | return t 67 | } 68 | 69 | // Cron 70 | type Cron struct { 71 | t *Timer 72 | } 73 | 74 | func (c *Cron) Stop() { 75 | if c.t != nil { 76 | c.t.Stop() 77 | } 78 | } 79 | 80 | func (disp *Dispatcher) CronFunc(cronExpr *CronExpr, _cb func()) *Cron { 81 | c := new(Cron) 82 | 83 | now := time.Now() 84 | nextTime := cronExpr.Next(now) 85 | if nextTime.IsZero() { 86 | return c 87 | } 88 | 89 | // callback 90 | var cb func() 91 | cb = func() { 92 | defer _cb() 93 | 94 | now := time.Now() 95 | nextTime := cronExpr.Next(now) 96 | if nextTime.IsZero() { 97 | return 98 | } 99 | c.t = disp.AfterFunc(nextTime.Sub(now), cb) 100 | } 101 | 102 | c.t = disp.AfterFunc(nextTime.Sub(now), cb) 103 | return c 104 | } 105 | 106 | func (disp *Dispatcher) LoopFunc(d time.Duration, cb func(), loopCount int) *Timer { 107 | if loopCount < LoopForever { 108 | return nil 109 | } 110 | 111 | t := new(Timer) 112 | t.loopCount = loopCount 113 | t.cb = cb 114 | t.t = time.NewTimer(d) 115 | go func() { 116 | for { 117 | <-t.t.C 118 | disp.ChanTimer <- t 119 | if t.loopCount != LoopForever { 120 | t.loopCount-- 121 | if t.loopCount == 0 { 122 | t.loopCount = Invalid 123 | break 124 | } 125 | } 126 | t.t.Reset(d) 127 | } 128 | }() 129 | return t 130 | } 131 | -------------------------------------------------------------------------------- /cmd/table/business/table/table.go: -------------------------------------------------------------------------------- 1 | package table 2 | 3 | import ( 4 | "github.com/golang/protobuf/proto" 5 | tCMD "mango/api/table" 6 | "mango/cmd/table/business/player" 7 | g "mango/pkg/gate" 8 | "mango/pkg/log" 9 | n "mango/pkg/network" 10 | ) 11 | 12 | const ( 13 | InvalidSeadID = 0xFFFF 14 | 15 | DdzKind int64 = 666 16 | ) 17 | 18 | type ( 19 | Frame interface { 20 | SendTableData(seatId uint32, bm n.BaseMessage) 21 | WriteGameScore() 22 | GameOver() 23 | } 24 | 25 | FrameSink interface { 26 | StartGame(f Frame) 27 | GameMessage(seatId, cmdId uint32, data []byte) 28 | } 29 | ) 30 | 31 | type Table struct { 32 | Id uint64 33 | HostAppId uint32 34 | gameSink FrameSink 35 | Players map[uint32]*player.Player 36 | } 37 | 38 | func NewTable(id uint64, sink FrameSink) *Table { 39 | t := new(Table) 40 | t.Id = id 41 | t.HostAppId = 0 42 | t.gameSink = sink 43 | t.Players = make(map[uint32]*player.Player) 44 | return t 45 | } 46 | 47 | func (t *Table) SendTableData(seatId uint32, bm n.BaseMessage) { 48 | var gameMessage tCMD.GameMessage 49 | gameMessage.SubCmdid = proto.Uint32(uint32(bm.Cmd.CmdId)) 50 | gameMessage.Data, _ = proto.Marshal(bm.MyMessage.(proto.Message)) 51 | bm.Cmd.CmdId = uint16(tCMD.CMDTable_IDGameMessage) 52 | bm.MyMessage = &gameMessage 53 | 54 | if seatId == InvalidSeadID { 55 | for _, pl := range t.Players { 56 | g.SendMessage2Client(bm, pl.GateConnId, 0) 57 | } 58 | } else { 59 | pl, ok := t.Players[seatId] 60 | if !ok { 61 | log.Warning("", "没找到,seatId=%d,id=%v,hostId=%v", seatId, t.Id, t.HostAppId) 62 | return 63 | } 64 | g.SendMessage2Client(bm, pl.GateConnId, 0) 65 | } 66 | } 67 | 68 | func (t *Table) WriteGameScore() { 69 | var writeScore tCMD.WriteGameScore 70 | g.SendData2App(n.AppRoom, t.HostAppId, n.AppTable, uint32(tCMD.CMDTable_IDWriteGameScore), &writeScore) 71 | } 72 | 73 | func (t *Table) GameOver() { 74 | t.Players = make(map[uint32]*player.Player) 75 | var over tCMD.GameOver 76 | over.TableId = proto.Uint64(t.Id) 77 | g.SendData2App(n.AppRoom, t.HostAppId, n.AppTable, uint32(tCMD.CMDTable_IDGameOver), &over) 78 | } 79 | 80 | func (t *Table) Reset() { 81 | t.HostAppId = 0 82 | t.Players = make(map[uint32]*player.Player) 83 | } 84 | 85 | func (t *Table) SetPlayer(pl *player.Player) { 86 | if _, ok := t.Players[pl.SeatId]; ok { 87 | log.Warning("", "有人了,id=%v,userId=%v,seatId=%v", t.Id, pl.UserId, pl.SeatId) 88 | return 89 | } 90 | t.Players[pl.SeatId] = pl 91 | } 92 | 93 | func (t *Table) Start() { 94 | t.gameSink.StartGame(t) 95 | } 96 | 97 | func (t *Table) GameMessage(seatId, cmdId uint32, data []byte) { 98 | t.gameSink.GameMessage(seatId, cmdId, data) 99 | } 100 | -------------------------------------------------------------------------------- /api/proto/center.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | package bs.center; 3 | option go_package ="/center"; 4 | 5 | enum CMDCenter { 6 | IDAppRegReq =1; //服务注册 7 | IDAppRegRsp =2; //服务注册 8 | IDAppState =3; //服务状态 9 | IDHeartBeatReq =4; //服务心跳 10 | IDHeartBeatRsp =5; //服务心跳 11 | IDAppControlReq =6; //控制消息 12 | IDAppControlRsp =7; //控制消息 13 | } 14 | 15 | //服务注册 16 | message RegisterAppReq { 17 | required string auth_key =1; 18 | optional string att_data =2; 19 | optional string my_address=3; 20 | optional uint32 app_type=4; 21 | optional uint32 app_id =5; 22 | optional string rereg_token=6; //如果中间网络断开了,可以使用rereg_token强行再次注册 23 | optional string app_name = 7; //app的名称(一般为进程名) 24 | } 25 | 26 | //服务注册 27 | message RegisterAppRsp { 28 | required uint32 reg_result = 1; //0表示成功,其它为错误码(rereg_token为出错内容) 29 | optional string rereg_token = 2; //如果中间网络断开了,可以使用rereg_token强行再次注册 30 | optional uint32 center_id = 3; 31 | optional uint32 app_type=4; //Router 或其他 32 | optional uint32 app_id =5; 33 | optional string app_name = 6; //app的名称(一般为进程名) 34 | optional string app_address = 7; //监听地址 35 | } 36 | 37 | //服务状态 38 | message AppStateNotify{ 39 | required int32 app_state=1; 40 | optional uint32 center_id=2; 41 | optional uint32 app_type=4; 42 | optional uint32 app_id =5; 43 | } 44 | 45 | //服务心跳 46 | message HeartBeatReq{ 47 | optional int64 beat_id = 1; 48 | optional int64 pulse_time = 2; 49 | optional int32 service_state = 3; 50 | optional string state_description = 4; 51 | optional string http_address=5; 52 | optional string rpc_address=6; 53 | } 54 | 55 | //服务心跳 56 | message HeartBeatRsp{ 57 | optional int64 pulse_time = 1; 58 | } 59 | 60 | enum CtlId{ 61 | Maintenance=1; //开始维护 62 | MaintenanceFinish=2; //维护完成 63 | ShowServerList=3; //显示列表 64 | StartService=4; //启动服务 65 | StopService=5; //停止服务 66 | UpdateService=6; //更新服务 67 | } 68 | 69 | message controlItem{ 70 | optional string name = 1; 71 | optional uint32 type = 2; 72 | optional uint32 id = 3; 73 | optional string command=4; //命令 74 | repeated string args=5; //参数 75 | } 76 | 77 | message AppControlReq { 78 | optional int32 ctl_id = 1; // 命令编号 79 | optional uint32 app_type=2; 80 | optional uint32 app_id =3; 81 | repeated controlItem ctl_servers=4; //操作服务 82 | repeated string args=5; //参数 83 | } 84 | 85 | message AppControlRsp { 86 | optional int32 ctl_id = 1; // 命令编号 87 | optional uint32 app_type=2; 88 | optional uint32 app_id =3; 89 | optional int32 code = 4; 90 | optional string info = 5; 91 | } 92 | 93 | service AppControl{ 94 | rpc ControlReq(AppControlReq) returns(AppControlRsp); 95 | } -------------------------------------------------------------------------------- /api/proto/lobby.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | import "types.proto"; 3 | package bs.lobby; 4 | option go_package ="/lobby"; 5 | 6 | enum CMDLobby { 7 | IDLoginReq = 1; //登录请求 8 | IDLoginRsp = 2; //登录回复 9 | IDLogoutReq = 3; //注销登录 10 | IDLogoutRsp = 4; //注销登录 11 | IDLogicRegReq = 5; //逻辑注册 12 | IDLogicRegRsp = 6; //逻辑注册 13 | IDUserState = 7; //用户状态 14 | } 15 | 16 | //登录请求 17 | message LoginReq{ 18 | enum LoginType { 19 | Account = 0; //账号 20 | Token = 1; // 21 | UnionId = 2; //唯一标识(游客登录等) 22 | } 23 | 24 | optional uint32 game_kind = 1; //游戏种类 25 | optional LoginType login_type = 2; //登录类型 26 | optional string account = 3; //用户账号(根据 LoginAction填不通内容) 27 | optional string password = 4; //用户密码 28 | optional string version = 5; //客户端版本号 29 | optional string IP = 6; //客户端IP 30 | optional string system_version = 7; //操作系统版本号 31 | optional uint32 channel_id = 8; //主渠道id 32 | optional uint32 site_id = 9; //子渠道id 33 | optional string device_id = 10; //设备唯一码 34 | optional int32 user_type = 11; //用户类型(客户端禁止使用) 35 | } 36 | 37 | // 登录回复 38 | message LoginRsp{ 39 | enum Result { 40 | SUCCESS = 0; //成功 41 | NOTEXIST = 1; //账号不存在 42 | FROZEN = 2; //账号被冻结 43 | FALSEPW = 3; //密码错误 44 | NETERROR = 4; //网络异常 45 | APPISBUSY = 5; //服务器忙,人数爆满 46 | GUESTFORBID = 6; //禁止游客登录 47 | CONNECTERROR = 7; //连接异常 48 | VERSIONOLD = 8; //版本过低 49 | NOMOREGUEST = 9; //游客分配失败 50 | FREQUENTLY = 10; //所在ip登录过多 51 | APPINITING = 11; //系统初始化,请稍后再试 52 | SERVERERROR = 0xFF; //服务端出错 53 | UNKOWN = 1000; //未知错误 54 | TOKEN_FAILED = 1001; //Token出错 55 | TOKEN_EXPIRED = 1002; //token过期了 56 | TOKEN_NOTMATCH = 1003; //token与appid不匹配 57 | } 58 | optional Result result = 1; //登录结果 59 | optional types.BaseUserInfo base_info = 2; //基本信息 60 | optional types.ErrorInfo err_info = 99; 61 | } 62 | 63 | 64 | // 注销登录 65 | message LogoutReq{ 66 | required uint64 user_id = 1; 67 | optional uint64 GateConnId = 2; //客户端可不填 68 | } 69 | 70 | // 注销登录 71 | message LogoutRsp{ 72 | enum LogoutReason { 73 | Normal = 0; 74 | AnotherLogin = 1; //被顶号 75 | } 76 | 77 | optional LogoutReason reason = 1; 78 | optional types.ErrorInfo err_info = 99; 79 | } 80 | 81 | // 82 | message LogicRegReq{ 83 | } 84 | 85 | // 86 | message LogicRegRsp{ 87 | optional types.ErrorInfo err_info = 99; 88 | } 89 | 90 | //用户状态 91 | message UserOnlineState{ 92 | enum OnlineStat { 93 | Online = 1; 94 | Disconnection = 2; 95 | Reconnect = 3; 96 | Offline = 4; 97 | } 98 | optional types.BaseUserInfo user_info = 1; //基本信息 99 | optional OnlineStat online_state = 2; 100 | } -------------------------------------------------------------------------------- /pkg/util/gorsa/rsa.go: -------------------------------------------------------------------------------- 1 | package gorsa 2 | 3 | import ( 4 | "crypto" 5 | "crypto/rand" 6 | "crypto/rsa" 7 | "crypto/sha256" 8 | "crypto/x509" 9 | "encoding/pem" 10 | "errors" 11 | ) 12 | 13 | type PriKeyType uint 14 | 15 | const ( 16 | PKCS1 PriKeyType = iota 17 | PKCS8 18 | ) 19 | 20 | //私钥签名 21 | func Sign(data, privateKey []byte, keyType PriKeyType) ([]byte, error) { 22 | h := sha256.New() 23 | h.Write(data) 24 | hashed := h.Sum(nil) 25 | priv, err := getPriKey(privateKey, keyType) 26 | if err != nil { 27 | return nil, err 28 | } 29 | return rsa.SignPKCS1v15(rand.Reader, priv, crypto.SHA256, hashed) 30 | } 31 | 32 | //公钥验证 33 | func SignVer(data, signature, publicKey []byte) error { 34 | hashed := sha256.Sum256(data) 35 | //获取公钥 36 | pub, err := getPubKey(publicKey) 37 | if err != nil { 38 | return err 39 | } 40 | //验证签名 41 | return rsa.VerifyPKCS1v15(pub, crypto.SHA256, hashed[:], signature) 42 | } 43 | 44 | // 公钥加密 45 | func Encrypt(data, publicKey []byte) ([]byte, error) { 46 | //获取公钥 47 | pub, err := getPubKey(publicKey) 48 | if err != nil { 49 | return nil, err 50 | } 51 | //加密 52 | return rsa.EncryptPKCS1v15(rand.Reader, pub, data) 53 | } 54 | 55 | // 私钥解密,privateKey为pem文件里的字符 56 | func Decrypt(encData, privateKey []byte, keyType PriKeyType) ([]byte, error) { 57 | //解析PKCS1a或者PKCS8格式的私钥 58 | priv, err := getPriKey(privateKey, keyType) 59 | if err != nil { 60 | return nil, err 61 | } 62 | // 解密 63 | return rsa.DecryptPKCS1v15(rand.Reader, priv, encData) 64 | } 65 | 66 | func getPubKey(publicKey []byte) (*rsa.PublicKey, error) { 67 | //解密pem格式的公钥 68 | block, _ := pem.Decode(publicKey) 69 | if block == nil { 70 | return nil, errors.New("public key error") 71 | } 72 | // 解析公钥 73 | pubInterface, err := x509.ParsePKIXPublicKey(block.Bytes) 74 | if err != nil { 75 | return nil, err 76 | } 77 | // 类型断言 78 | if pub, ok := pubInterface.(*rsa.PublicKey); ok { 79 | return pub, nil 80 | } else { 81 | return nil, errors.New("public key error") 82 | } 83 | } 84 | 85 | func getPriKey(privateKey []byte, keyType PriKeyType) (*rsa.PrivateKey, error) { 86 | //获取私钥 87 | block, _ := pem.Decode(privateKey) 88 | if block == nil { 89 | return nil, errors.New("private key error!") 90 | } 91 | var priKey *rsa.PrivateKey 92 | var err error 93 | switch keyType { 94 | case PKCS1: 95 | { 96 | priKey, err = x509.ParsePKCS1PrivateKey(block.Bytes) 97 | if err != nil { 98 | return nil, err 99 | } 100 | } 101 | case PKCS8: 102 | { 103 | prkI, err := x509.ParsePKCS8PrivateKey(block.Bytes) 104 | if err != nil { 105 | return nil, err 106 | } 107 | priKey = prkI.(*rsa.PrivateKey) 108 | } 109 | default: 110 | { 111 | return nil, errors.New("unsupport private key type") 112 | } 113 | } 114 | return priKey, nil 115 | } 116 | -------------------------------------------------------------------------------- /pkg/network/ws_conn.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | import ( 4 | "errors" 5 | "mango/pkg/log" 6 | "net" 7 | "sync" 8 | 9 | "github.com/gorilla/websocket" 10 | ) 11 | 12 | type WebsocketConnSet map[*websocket.Conn]struct{} 13 | 14 | type WSConn struct { 15 | sync.Mutex 16 | conn *websocket.Conn 17 | writeChan chan []byte 18 | maxMsgLen uint32 19 | closeFlag bool 20 | } 21 | 22 | func newWSConn(conn *websocket.Conn, pendingWriteNum int, maxMsgLen uint32) *WSConn { 23 | wsConn := new(WSConn) 24 | wsConn.conn = conn 25 | wsConn.writeChan = make(chan []byte, pendingWriteNum) 26 | wsConn.maxMsgLen = maxMsgLen 27 | 28 | go func() { 29 | for b := range wsConn.writeChan { 30 | if b == nil { 31 | break 32 | } 33 | 34 | err := conn.WriteMessage(websocket.BinaryMessage, b) 35 | if err != nil { 36 | break 37 | } 38 | } 39 | 40 | conn.Close() 41 | wsConn.Lock() 42 | wsConn.closeFlag = true 43 | wsConn.Unlock() 44 | }() 45 | 46 | return wsConn 47 | } 48 | 49 | func (wsConn *WSConn) doDestroy() { 50 | wsConn.conn.UnderlyingConn().(*net.TCPConn).SetLinger(0) 51 | wsConn.conn.Close() 52 | 53 | if !wsConn.closeFlag { 54 | close(wsConn.writeChan) 55 | wsConn.closeFlag = true 56 | } 57 | } 58 | 59 | func (wsConn *WSConn) Destroy() { 60 | wsConn.Lock() 61 | defer wsConn.Unlock() 62 | 63 | wsConn.doDestroy() 64 | } 65 | 66 | func (wsConn *WSConn) Close() { 67 | wsConn.Lock() 68 | defer wsConn.Unlock() 69 | if wsConn.closeFlag { 70 | return 71 | } 72 | 73 | wsConn.doWrite(nil) 74 | wsConn.closeFlag = true 75 | } 76 | 77 | func (wsConn *WSConn) doWrite(b []byte) { 78 | if len(wsConn.writeChan) == cap(wsConn.writeChan) { 79 | log.Debug("ws_conn", "close conn: channel full") 80 | wsConn.doDestroy() 81 | return 82 | } 83 | 84 | wsConn.writeChan <- b 85 | } 86 | 87 | func (wsConn *WSConn) LocalAddr() net.Addr { 88 | return wsConn.conn.LocalAddr() 89 | } 90 | 91 | func (wsConn *WSConn) RemoteAddr() net.Addr { 92 | return wsConn.conn.RemoteAddr() 93 | } 94 | 95 | // goroutine not safe 96 | func (wsConn *WSConn) ReadMsg() (BaseMessage, []byte, error) { 97 | _, b, err := wsConn.conn.ReadMessage() 98 | return BaseMessage{}, b, err 99 | } 100 | 101 | func (wsConn *WSConn) WriteCMD(appType, cmdId uint16) { 102 | 103 | } 104 | 105 | // args must not be modified by the others goroutines 106 | func (wsConn *WSConn) WriteMsg(appType, cmdId uint16, msgData, otherData []byte) error { 107 | wsConn.Lock() 108 | defer wsConn.Unlock() 109 | if wsConn.closeFlag { 110 | return nil 111 | } 112 | 113 | // get len 114 | var msgLen uint32 = uint32(len(msgData)) 115 | 116 | // check len 117 | if msgLen > wsConn.maxMsgLen { 118 | return errors.New("message too long") 119 | } else if msgLen < 1 { 120 | return errors.New("message too short") 121 | } 122 | 123 | wsConn.doWrite(msgData) 124 | 125 | return nil 126 | } 127 | -------------------------------------------------------------------------------- /cmd/robot/business/player/agent.go: -------------------------------------------------------------------------------- 1 | package player 2 | 3 | import ( 4 | "github.com/golang/protobuf/proto" 5 | "mango/api/gateway" 6 | "mango/pkg/conf" 7 | "mango/pkg/log" 8 | n "mango/pkg/network" 9 | "reflect" 10 | ) 11 | 12 | type agentPlayer struct { 13 | tcpClient *n.TCPClient 14 | p *Player 15 | conn n.Conn 16 | info n.BaseAgentInfo 17 | } 18 | 19 | func (a *agentPlayer) Run() { 20 | for { 21 | bm, msgData, err := a.conn.ReadMsg() 22 | if err != nil { 23 | log.Warning("agentPlayer", "异常,网关读取消息失败,info=%v,err=%v", a.info, err) 24 | break 25 | } 26 | 27 | if a.p.processor == nil { 28 | log.Warning("", "processor==nil,cmd=%v", bm.Cmd) 29 | break 30 | } 31 | 32 | unmarshalCmd := bm.Cmd 33 | var cmd, msg, dataReq interface{} 34 | if bm.Cmd.AppType == uint16(n.AppGate) && bm.Cmd.CmdId == uint16(gateway.CMDGateway_IDTransferDataReq) && conf.AppInfo.Type != n.AppGate { 35 | var m gateway.TransferDataReq 36 | _ = proto.Unmarshal(msgData, &m) 37 | unmarshalCmd = n.TCPCommand{AppType: uint16(m.GetDataApptype()), CmdId: uint16(m.GetDataCmdid())} 38 | msgData = m.GetData() 39 | dataReq = &m 40 | } else { 41 | dataReq = a.info 42 | } 43 | 44 | cmd, msg, err = a.p.processor.Unmarshal(unmarshalCmd.AppType, unmarshalCmd.CmdId, msgData) 45 | if err != nil { 46 | log.Error("agentClient", "unmarshal message,headCmd=%v,error: %v", bm.Cmd, err) 47 | continue 48 | } 49 | err = a.p.processor.Route(n.BaseMessage{MyMessage: msg, TraceId: bm.TraceId}, a, cmd, dataReq) 50 | if err != nil { 51 | log.Error("agentClient", "client agentClient route message error: %v,cmd=%v", err, cmd) 52 | continue 53 | } 54 | } 55 | } 56 | 57 | func (a *agentPlayer) OnClose() { 58 | log.Debug("", "服务间连接断开了,info=%v", a.info) 59 | } 60 | 61 | func (a *agentPlayer) SendMessage(bm n.BaseMessage) { 62 | m := bm.MyMessage.(proto.Message) 63 | data, err := proto.Marshal(m) 64 | if err != nil { 65 | log.Error("agentPlayer", "异常,proto.Marshal %v error: %v", reflect.TypeOf(m), err) 66 | return 67 | } 68 | //追加TraceId 69 | otherData := make([]byte, 0, n.TraceIdLen+1) 70 | if bm.TraceId != "" { 71 | otherData = append(otherData, n.FlagOtherTraceId) 72 | otherData = append(otherData, []byte(bm.TraceId)...) 73 | } 74 | err = a.conn.WriteMsg(bm.Cmd.AppType, bm.Cmd.CmdId, data, otherData) 75 | if err != nil { 76 | log.Error("agentPlayer", "写信息失败 %v error: %v", reflect.TypeOf(m), err) 77 | } 78 | } 79 | 80 | func (a *agentPlayer) SendData(appType, cmdId uint32, m proto.Message) { 81 | data, err := proto.Marshal(m) 82 | if err != nil { 83 | log.Error("agentPlayer", "异常,proto.Marshal %v error: %v", reflect.TypeOf(m), err) 84 | return 85 | } 86 | err = a.conn.WriteMsg(uint16(appType), uint16(cmdId), data, nil) 87 | if err != nil { 88 | log.Error("agentPlayer", "write message %v error: %v", reflect.TypeOf(m), err) 89 | } 90 | } 91 | 92 | func (a *agentPlayer) Close() { 93 | a.conn.Close() 94 | } 95 | func (a *agentPlayer) Destroy() { 96 | a.conn.Destroy() 97 | } 98 | -------------------------------------------------------------------------------- /pkg/util/common.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "bytes" 5 | "math/rand" 6 | "net" 7 | "runtime" 8 | "strconv" 9 | "strings" 10 | "time" 11 | 12 | "github.com/google/uuid" 13 | ) 14 | 15 | func CurMemory() int64 { 16 | var rtm runtime.MemStats 17 | runtime.ReadMemStats(&rtm) 18 | return int64(rtm.Alloc / 1024) 19 | } 20 | 21 | func ParseArgsUint32(name string, args []string) (uint32, bool) { 22 | for i := 0; i < len(args); i++ { 23 | a := strings.Split(args[i], "=") 24 | if len(a) != 2 { 25 | continue 26 | } 27 | if a[0] == name { 28 | v, err := strconv.Atoi(a[1]) 29 | if err == nil { 30 | return uint32(v), true 31 | } 32 | } 33 | } 34 | return 0, false 35 | } 36 | 37 | func ParseArgsString(name string, args []string) (string, bool) { 38 | for i := 0; i < len(args); i++ { 39 | a := strings.Split(args[i], "=") 40 | if len(a) != 2 { 41 | continue 42 | } 43 | if a[0] == name { 44 | return a[1], true 45 | } 46 | } 47 | return "", false 48 | } 49 | 50 | func MakeUint64FromUint32(high, low uint32) uint64 { 51 | return uint64(high)<<32 | uint64(low) 52 | } 53 | 54 | func Get2Uint32FromUint64(v uint64) (uint32, uint32) { 55 | return GetHUint32FromUint64(v), GetLUint32FromUint64(v) 56 | } 57 | 58 | func GetHUint32FromUint64(v uint64) uint32 { 59 | return uint32(v >> 32) 60 | } 61 | 62 | func GetLUint32FromUint64(v uint64) uint32 { 63 | return uint32(v & 0xFFFFFFFF) 64 | } 65 | 66 | func GetIPFromIPAddress(addr string) string { 67 | a := strings.Split(addr, ":") 68 | if len(a) != 2 { 69 | return "" 70 | } 71 | return a[0] 72 | } 73 | 74 | func GetPortFromIPAddress(addr string) int { 75 | a := strings.Split(addr, ":") 76 | if len(a) != 2 { 77 | return 0 78 | } 79 | p, _ := strconv.Atoi(a[1]) 80 | return p 81 | } 82 | 83 | func PortInUse(portNumber int) bool { 84 | p := strconv.Itoa(portNumber) 85 | addr := net.JoinHostPort("127.0.0.1", p) 86 | conn, err := net.DialTimeout("tcp", addr, 3*time.Second) 87 | if err != nil { 88 | return false 89 | } 90 | defer conn.Close() 91 | 92 | return true 93 | } 94 | 95 | func StrconvAsInt64(str string, defaultValue int64) int64 { 96 | v, err := strconv.ParseInt(str, 10, 64) 97 | if err != nil { 98 | return defaultValue 99 | } 100 | return v 101 | } 102 | 103 | func GetUUID() string { 104 | return uuid.New().String() 105 | } 106 | 107 | func RandByte(length int) []byte { 108 | var chars = []byte{'.', '/', '?', '%', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0'} 109 | buffer := bytes.Buffer{} 110 | clength := len(chars) 111 | rand.Seed(time.Now().UnixNano()) //重新播种,否则值不会变 112 | for i := 0; i < length; i++ { 113 | buffer.WriteByte(chars[rand.Intn(clength)]) 114 | 115 | } 116 | return buffer.Bytes() 117 | } 118 | -------------------------------------------------------------------------------- /api/proto/gateway.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | package bs.gateway; 3 | option go_package ="/gateway"; 4 | 5 | enum CMDGateway{ 6 | IDPulseReq = 1; //测速请求 7 | IDPulseRsp = 2; //测速回复 8 | IDTransferDataReq = 3; //数据转发请求 9 | IDTransferDataRsp = 4; //数据转发回复 10 | IDAuthInfo = 5; //认证信息 11 | IDHelloReq = 6; //握手请求 12 | IDHelloRsp = 7; //握手回复 13 | } 14 | 15 | //测速请求 16 | message PulseReq{ 17 | optional uint32 my_speed=1; 18 | optional uint32 speed_data=2; 19 | optional bytes attached_data=3; 20 | } 21 | 22 | //测速请求 23 | message PulseRsp{ 24 | optional uint32 speed_data=1; 25 | optional bytes attachd_data=2; 26 | optional int64 timestamp = 3; 27 | } 28 | 29 | //数据转发请求 30 | message TransferDataReq{ 31 | optional uint32 dest_apptype=1; //目标或源apptype 32 | optional uint32 dest_appid=2; //目标或源appid 33 | optional uint32 data_apptype=3; 34 | optional uint32 data_cmdid=4; 35 | optional bytes data = 5; 36 | optional uint32 req_id =6; 37 | optional uint32 client_ip_v4=7; //客户端的ip 38 | optional uint64 att_sessionid=8; //联联的session id ,目前只由gate->client 39 | optional uint64 gateconnid=9; //关联的gate连接id 40 | optional uint32 gateid = 10; //关联的gate_id 41 | optional uint64 user_id = 11; //用户ID 42 | }; 43 | 44 | //数据转发请求 45 | message TransferDataRsp{ 46 | optional uint32 result=1; 47 | optional uint32 req_id =6; 48 | }; 49 | 50 | //连接认证信息 51 | message AuthInfo{ 52 | enum OpType { 53 | Bind = 0;//userId与connId绑定 54 | Disconnect = 1;//断开当前connId连接 55 | } 56 | optional uint64 user_id =1; //用户id 57 | optional uint64 gateconnid=2; //关联的gate连接id 58 | optional uint32 result=3; //结果 0成功 59 | optional string info=4; //描述信息 60 | optional OpType op_type=5; //操作类型 61 | }; 62 | 63 | //握手请求 64 | message HelloReq{ 65 | optional uint32 ad_id=2; 66 | optional string others=3; 67 | optional uint32 builder_no =4; 68 | optional uint32 game_kind = 5; 69 | optional string client_version=6; 70 | optional uint32 client_type = 7; 71 | optional string public_key = 8; 72 | optional string encrypt_key = 9; //加密key 73 | optional string guid = 10; 74 | } 75 | 76 | //握手回复 77 | message HelloRsp{ 78 | enum RspFlag{ 79 | UNKNOWN=0; //未知 80 | EncryptInfo=1; //加密信息 encrypt_key 这是存在的 81 | AdviceNewGate=2; //建议去新的gate,这时gate_address 必须有内容 82 | LoginToken=4; //登录令牌 83 | } 84 | 85 | optional uint32 rsp_flag= 1; //通知的消息内容 86 | repeated string gate_address=2; //当前最新的gate地址 87 | optional uint32 login_token=4; //登录令牌 88 | optional string public_key = 8; 89 | optional string encrypt_key = 3; //加密key 90 | 91 | /* 92 | =0 表示是最新版本 93 | =1 表示有新版本,但当前版本还可以用 94 | =2 表示老版本必须更新了,当前连接会被断开的 95 | */ 96 | optional uint32 version_result = 5; 97 | 98 | /* 99 | 如果有新的版本,下载地址(一般用于手机) 100 | */ 101 | optional string down_url = 6; 102 | 103 | //如果Req的guid为空,则这里为其创建一个guid 104 | optional string guid = 7; 105 | } 106 | -------------------------------------------------------------------------------- /pkg/network/tcp_client.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | import ( 4 | "mango/pkg/log" 5 | "net" 6 | "strings" 7 | "sync" 8 | "time" 9 | ) 10 | 11 | //TCPClient 客户端连接 12 | type TCPClient struct { 13 | sync.Mutex 14 | Addr string 15 | ConnectInterval time.Duration 16 | PendingWriteNum int 17 | AutoReconnect bool 18 | NewAgent func(*TCPConn) AgentServer 19 | conns ConnSet 20 | wg sync.WaitGroup 21 | closeFlag bool 22 | 23 | // msg parser 24 | MinMsgLen uint32 25 | MaxMsgLen uint32 26 | LittleEndian bool 27 | msgParser *MsgParser 28 | } 29 | 30 | func (client *TCPClient) Start() { 31 | client.init() 32 | 33 | go func() { 34 | reconnect: 35 | conn := client.dial() 36 | if conn == nil { 37 | return 38 | } 39 | 40 | client.Lock() 41 | if client.closeFlag { 42 | client.Unlock() 43 | conn.Close() 44 | return 45 | } 46 | client.conns[conn] = struct{}{} 47 | client.Unlock() 48 | 49 | tcpConn := newTCPConn(conn, client.PendingWriteNum, client.msgParser) 50 | agent := client.NewAgent(tcpConn) 51 | agent.Run() 52 | 53 | // cleanup 54 | tcpConn.Close() 55 | client.Lock() 56 | delete(client.conns, conn) 57 | client.Unlock() 58 | agent.OnClose() 59 | 60 | if client.AutoReconnect { 61 | time.Sleep(client.ConnectInterval) 62 | goto reconnect 63 | } 64 | }() 65 | } 66 | 67 | func (client *TCPClient) init() { 68 | client.Lock() 69 | defer client.Unlock() 70 | 71 | if client.ConnectInterval <= 0 { 72 | client.ConnectInterval = 3 * time.Second 73 | } 74 | if client.PendingWriteNum <= 0 { 75 | client.PendingWriteNum = 1000 76 | } 77 | if client.conns != nil { 78 | log.Fatal("tcpclient", "client is running") 79 | } 80 | if client.NewAgent == nil { 81 | log.Fatal("tcpclient", "NewAgent is nil") 82 | } 83 | if client.Addr == "" { 84 | log.Fatal("tcpclient", "client.Addr为空") 85 | } 86 | 87 | client.conns = make(ConnSet) 88 | client.closeFlag = false 89 | 90 | // msg parser 91 | msgParser := NewMsgParser() 92 | msgParser.SetMsgLen(client.MinMsgLen, client.MaxMsgLen) 93 | client.msgParser = msgParser 94 | } 95 | 96 | func (client *TCPClient) dial() net.Conn { 97 | addr := strings.Split(client.Addr, "|") 98 | index := 0 99 | for { 100 | curConnAddr := addr[index%len(addr)] 101 | conn, err := net.Dial("tcp", curConnAddr) 102 | if err == nil || client.closeFlag { 103 | return conn 104 | } 105 | 106 | log.Warning("TCPClient", "err=%v,index=%v", err, index) 107 | index++ 108 | if index >= len(addr) { 109 | if client.AutoReconnect { 110 | index = 0 111 | } else { 112 | break 113 | } 114 | } 115 | time.Sleep(client.ConnectInterval) 116 | continue 117 | } 118 | 119 | return nil 120 | } 121 | 122 | func (client *TCPClient) Close() { 123 | client.Lock() 124 | client.closeFlag = true 125 | for conn := range client.conns { 126 | conn.Close() 127 | } 128 | client.conns = nil 129 | client.Unlock() 130 | 131 | client.wg.Wait() 132 | } 133 | 134 | func (client *TCPClient) IsRunning() bool { 135 | if client.conns != nil { 136 | return true 137 | } 138 | 139 | return false 140 | } 141 | -------------------------------------------------------------------------------- /pkg/util/dingding/dingding.go: -------------------------------------------------------------------------------- 1 | package dingding 2 | 3 | import ( 4 | "bytes" 5 | "crypto/hmac" 6 | "crypto/sha256" 7 | "encoding/base64" 8 | "encoding/json" 9 | "errors" 10 | "fmt" 11 | "io/ioutil" 12 | "net/http" 13 | "time" 14 | ) 15 | 16 | type Webhook struct { 17 | AccessToken string 18 | Secret string 19 | EnableAt bool 20 | AtAll bool 21 | } 22 | 23 | // SendMessage Function to send message 24 | //goland:noinspection GoUnhandledErrorResult 25 | func (t *Webhook) SendTextMessage(s string, at ...string) error { 26 | msg := map[string]interface{}{ 27 | "msgtype": "text", 28 | "text": map[string]string{ 29 | "content": s, 30 | }, 31 | } 32 | if t.EnableAt { 33 | if t.AtAll { 34 | if len(at) > 0 { 35 | return errors.New("the parameter \"AtAll\" is \"true\", but the \"at\" parameter of SendMessage is not empty") 36 | } 37 | msg["at"] = map[string]interface{}{ 38 | "isAtAll": t.AtAll, 39 | } 40 | } else { 41 | msg["at"] = map[string]interface{}{ 42 | "atMobiles": at, 43 | "isAtAll": t.AtAll, 44 | } 45 | } 46 | } else { 47 | if len(at) > 0 { 48 | return errors.New("the parameter \"EnableAt\" is \"false\", but the \"at\" parameter of SendMessage is not empty") 49 | } 50 | } 51 | return t.sendMessage(msg) 52 | } 53 | 54 | func (t *Webhook) SendMarkdownMessage(s string, at ...string) error { 55 | msg := map[string]interface{}{ 56 | "msgtype": "markdown", 57 | "markdown": map[string]string{ 58 | "title": "告警消息", 59 | "text": s, 60 | }, 61 | } 62 | if t.EnableAt { 63 | if t.AtAll { 64 | if len(at) > 0 { 65 | return errors.New("the parameter \"AtAll\" is \"true\", but the \"at\" parameter of SendMessage is not empty") 66 | } 67 | msg["at"] = map[string]interface{}{ 68 | "isAtAll": t.AtAll, 69 | } 70 | } else { 71 | msg["at"] = map[string]interface{}{ 72 | "atMobiles": at, 73 | "isAtAll": t.AtAll, 74 | } 75 | } 76 | } else { 77 | if len(at) > 0 { 78 | return errors.New("the parameter \"EnableAt\" is \"false\", but the \"at\" parameter of SendMessage is not empty") 79 | } 80 | } 81 | return t.sendMessage(msg) 82 | } 83 | 84 | func (t *Webhook) sendMessage(msg map[string]interface{}) error { 85 | b, err := json.Marshal(msg) 86 | if err != nil { 87 | return err 88 | } 89 | resp, err := http.Post(t.getURL(), "application/json", bytes.NewBuffer(b)) 90 | if err != nil { 91 | return err 92 | } 93 | defer resp.Body.Close() 94 | _, err = ioutil.ReadAll(resp.Body) 95 | if err != nil { 96 | return err 97 | } 98 | return nil 99 | } 100 | 101 | func (t *Webhook) hmacSha256(stringToSign string, secret string) string { 102 | h := hmac.New(sha256.New, []byte(secret)) 103 | h.Write([]byte(stringToSign)) 104 | return base64.StdEncoding.EncodeToString(h.Sum(nil)) 105 | } 106 | 107 | func (t *Webhook) getURL() string { 108 | wh := "https://oapi.dingtalk.com/robot/send?access_token=" + t.AccessToken 109 | timestamp := time.Now().UnixNano() / 1e6 110 | stringToSign := fmt.Sprintf("%d\n%s", timestamp, t.Secret) 111 | sign := t.hmacSha256(stringToSign, t.Secret) 112 | url := fmt.Sprintf("%s×tamp=%d&sign=%s", wh, timestamp, sign) 113 | return url 114 | } 115 | -------------------------------------------------------------------------------- /pkg/database/database.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "mango/pkg/database/mchelper" 5 | "mango/pkg/database/mgohelper" 6 | "mango/pkg/database/redishelper" 7 | "mango/pkg/database/sqlhelper" 8 | "mango/pkg/log" 9 | "github.com/GoldBaby5511/go-simplejson" 10 | "strings" 11 | ) 12 | 13 | type DBCollection struct { 14 | SqlHelperMap map[string]*sqlhelper.SqlHelper 15 | MCHelper *mchelper.MCHelper 16 | MongoHelper *mgohelper.MgoHelper 17 | RedisHelper *redishelper.RedisHelper 18 | } 19 | 20 | var ( 21 | DBC *DBCollection = nil 22 | ) 23 | 24 | func InitDBHelper(dbConfig string) { 25 | if DBC != nil || dbConfig == "" { 26 | log.Warning("database", "已初始化或配置为空,dbConfig=%v", dbConfig) 27 | return 28 | } 29 | 30 | dataBase, err := simplejson.NewJson([]byte(dbConfig)) 31 | if err != nil { 32 | log.Warning("database", "数据库配置异常,dbConfig=%v,err=%v", dbConfig, err) 33 | return 34 | } 35 | 36 | DBC = &DBCollection{} 37 | noSql, ok := dataBase.CheckGet("nosql") 38 | if ok { 39 | memcached, err := noSql.Get("memcached").String() 40 | if err == nil && memcached != "" { 41 | DBC.MCHelper = &mchelper.MCHelper{} 42 | DBC.MCHelper.Init(strings.Split(memcached, ",")...) 43 | } 44 | 45 | mongodb, ok := noSql.CheckGet("mongodb") 46 | if ok { 47 | mongoHost, err := mongodb.Get("server").String() 48 | if err == nil && mongoHost != "" { 49 | mongoDatabase, _ := mongodb.Get("database").String() 50 | mongoUserid := mongodb.Get("userid").MustString("") 51 | mongoPassword := mongodb.Get("password").MustString("") 52 | DBC.MongoHelper = &mgohelper.MgoHelper{} 53 | DBC.MongoHelper.Init(mongoHost, mongoDatabase, mongoUserid, mongoPassword) 54 | } 55 | } 56 | 57 | redis, ok := noSql.CheckGet("redis") 58 | if ok { 59 | redisServer, err := redis.Get("server").String() 60 | if err == nil && redisServer != "" { 61 | redisPassword := redis.Get("password").MustString("") 62 | DBC.RedisHelper = &redishelper.RedisHelper{} 63 | DBC.RedisHelper.Init(redisServer, redisPassword) 64 | } 65 | } 66 | } 67 | 68 | DBC.SqlHelperMap = make(map[string]*sqlhelper.SqlHelper) 69 | for key, _ := range dataBase.MustMap() { 70 | if key == "nosql" || key == "" { 71 | continue 72 | } 73 | if dataBase.Get(key).Get("server").MustString("") == "" { 74 | continue 75 | } 76 | if _, ok := DBC.SqlHelperMap[key]; ok { 77 | log.Warning("database", "数据库配置重复,key=%v,dbConfig=%v", key, dbConfig) 78 | continue 79 | } 80 | helper := &sqlhelper.SqlHelper{} 81 | helper.Init(dataBase.Get(key)) 82 | DBC.SqlHelperMap[key] = helper 83 | } 84 | 85 | log.Info("database", "数据库初始化完成") 86 | } 87 | 88 | func GetMasterSqlDB() *sqlhelper.SqlHelper { 89 | return GetSqlDB("master") 90 | } 91 | 92 | func GetSqlDB(name string) *sqlhelper.SqlHelper { 93 | if DBC == nil { 94 | return nil 95 | } 96 | return DBC.SqlHelperMap[name] 97 | } 98 | 99 | func GetMCHelper() *mchelper.MCHelper { 100 | if DBC == nil { 101 | return nil 102 | } 103 | return DBC.MCHelper 104 | } 105 | 106 | func GetRedisHelper() *redishelper.RedisHelper { 107 | if DBC == nil { 108 | return nil 109 | } 110 | return DBC.RedisHelper 111 | } 112 | 113 | func GetMongoHelper() *mgohelper.MgoHelper { 114 | if DBC == nil { 115 | return nil 116 | } 117 | return DBC.MongoHelper 118 | } 119 | -------------------------------------------------------------------------------- /pkg/network/ws_client.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | "mango/pkg/log" 7 | 8 | "github.com/gorilla/websocket" 9 | ) 10 | 11 | type WSClient struct { 12 | sync.Mutex 13 | Addr string 14 | ConnNum int 15 | ConnectInterval time.Duration 16 | PendingWriteNum int 17 | MaxMsgLen uint32 18 | HandshakeTimeout time.Duration 19 | AutoReconnect bool 20 | NewAgent func(*WSConn) AgentClient 21 | dialer websocket.Dialer 22 | conns WebsocketConnSet 23 | wg sync.WaitGroup 24 | closeFlag bool 25 | } 26 | 27 | func (client *WSClient) Start() { 28 | client.init() 29 | 30 | for i := 0; i < client.ConnNum; i++ { 31 | client.wg.Add(1) 32 | go client.connect() 33 | } 34 | } 35 | 36 | func (client *WSClient) init() { 37 | client.Lock() 38 | defer client.Unlock() 39 | 40 | if client.ConnNum <= 0 { 41 | client.ConnNum = 1 42 | log.Info("WSClient", "invalid ConnNum, reset to %v", client.ConnNum) 43 | } 44 | if client.ConnectInterval <= 0 { 45 | client.ConnectInterval = 3 * time.Second 46 | log.Info("WSClient", "invalid ConnectInterval, reset to %v", client.ConnectInterval) 47 | } 48 | if client.PendingWriteNum <= 0 { 49 | client.PendingWriteNum = 100 50 | log.Info("WSClient", "invalid PendingWriteNum, reset to %v", client.PendingWriteNum) 51 | } 52 | if client.MaxMsgLen <= 0 { 53 | client.MaxMsgLen = 4096 54 | log.Info("WSClient", "invalid MaxMsgLen, reset to %v", client.MaxMsgLen) 55 | } 56 | if client.HandshakeTimeout <= 0 { 57 | client.HandshakeTimeout = 10 * time.Second 58 | log.Info("WSClient", "invalid HandshakeTimeout, reset to %v", client.HandshakeTimeout) 59 | } 60 | if client.NewAgent == nil { 61 | log.Fatal("wsclient", "NewAgent must not be nil") 62 | } 63 | if client.conns != nil { 64 | log.Fatal("wsclient", "client is running") 65 | } 66 | 67 | client.conns = make(WebsocketConnSet) 68 | client.closeFlag = false 69 | client.dialer = websocket.Dialer{ 70 | HandshakeTimeout: client.HandshakeTimeout, 71 | } 72 | } 73 | 74 | func (client *WSClient) dial() *websocket.Conn { 75 | for { 76 | conn, _, err := client.dialer.Dial(client.Addr, nil) 77 | if err == nil || client.closeFlag { 78 | return conn 79 | } 80 | 81 | log.Info("connect to %v error: %v", client.Addr, err) 82 | time.Sleep(client.ConnectInterval) 83 | continue 84 | } 85 | } 86 | 87 | func (client *WSClient) connect() { 88 | defer client.wg.Done() 89 | 90 | reconnect: 91 | conn := client.dial() 92 | if conn == nil { 93 | return 94 | } 95 | conn.SetReadLimit(int64(client.MaxMsgLen)) 96 | 97 | client.Lock() 98 | if client.closeFlag { 99 | client.Unlock() 100 | conn.Close() 101 | return 102 | } 103 | client.conns[conn] = struct{}{} 104 | client.Unlock() 105 | 106 | wsConn := newWSConn(conn, client.PendingWriteNum, client.MaxMsgLen) 107 | agent := client.NewAgent(wsConn) 108 | agent.Run() 109 | 110 | // cleanup 111 | wsConn.Close() 112 | client.Lock() 113 | delete(client.conns, conn) 114 | client.Unlock() 115 | agent.OnClose() 116 | 117 | if client.AutoReconnect { 118 | time.Sleep(client.ConnectInterval) 119 | goto reconnect 120 | } 121 | } 122 | 123 | func (client *WSClient) Close() { 124 | client.Lock() 125 | client.closeFlag = true 126 | for conn := range client.conns { 127 | conn.Close() 128 | } 129 | client.conns = nil 130 | client.Unlock() 131 | 132 | client.wg.Wait() 133 | } 134 | -------------------------------------------------------------------------------- /cmd/lobby/business/business.go: -------------------------------------------------------------------------------- 1 | package business 2 | 3 | import ( 4 | "github.com/golang/protobuf/proto" 5 | "mango/api/gateway" 6 | "mango/api/lobby" 7 | "mango/api/property" 8 | "mango/api/types" 9 | g "mango/pkg/gate" 10 | "mango/pkg/log" 11 | n "mango/pkg/network" 12 | "mango/pkg/util" 13 | ) 14 | 15 | var ( 16 | userList = make(map[uint64]*types.BaseUserInfo) 17 | ) 18 | 19 | func init() { 20 | g.MsgRegister(&lobby.LoginReq{}, n.AppLobby, uint16(lobby.CMDLobby_IDLoginReq), handleLoginReq) 21 | g.MsgRegister(&lobby.LogoutReq{}, n.AppLobby, uint16(lobby.CMDLobby_IDLogoutReq), handleLogoutReq) 22 | g.MsgRegister(&property.QueryPropertyRsp{}, n.AppProperty, uint16(property.CMDProperty_IDQueryPropertyRsp), handleQueryPropertyRsp) 23 | g.CallBackRegister(g.CbAppControlNotify, appControlNotify) 24 | } 25 | 26 | func appControlNotify(args []interface{}) { 27 | 28 | } 29 | 30 | func handleLoginReq(args []interface{}) { 31 | b := args[n.DataIndex].(n.BaseMessage) 32 | m := (b.MyMessage).(*lobby.LoginReq) 33 | srcData := b.AgentInfo 34 | gateConnId := util.MakeUint64FromUint32(srcData.AppType, srcData.AppId) 35 | 36 | log.Debug("登录", "收到登录,AppType=%v,AppID=%v,Account=%v,gateConnId=%d,子渠道=%d", 37 | b.AgentInfo.AppType, b.AgentInfo.AppId, m.GetAccount(), gateConnId, m.GetSiteId()) 38 | 39 | var userId uint64 = 0 40 | for _, v := range userList { 41 | if v.GetAccount() == m.GetAccount() { 42 | userId = v.GetUserId() 43 | v.GateConnid = proto.Uint64(gateConnId) 44 | } 45 | } 46 | if userId == 0 { 47 | userId = uint64(10000 + len(userList)) 48 | userList[userId] = new(types.BaseUserInfo) 49 | userList[userId].Account = proto.String(m.GetAccount()) 50 | userList[userId].UserId = proto.Uint64(userId) 51 | userList[userId].GameId = proto.Uint64(userId) 52 | userList[userId].GateConnid = proto.Uint64(gateConnId) 53 | } 54 | var req property.QueryPropertyReq 55 | req.UserId = proto.Uint64(userId) 56 | g.SendData2App(n.AppProperty, n.Send2AnyOne, n.AppProperty, uint32(property.CMDProperty_IDQueryPropertyReq), &req) 57 | } 58 | 59 | func handleLogoutReq(args []interface{}) { 60 | b := args[n.DataIndex].(n.BaseMessage) 61 | m := (b.MyMessage).(*lobby.LogoutReq) 62 | log.Debug("注销", "注销请求,userId=%v", m.GetUserId()) 63 | } 64 | 65 | func handleQueryPropertyRsp(args []interface{}) { 66 | b := args[n.DataIndex].(n.BaseMessage) 67 | m := (b.MyMessage).(*property.QueryPropertyRsp) 68 | 69 | if _, ok := userList[m.GetUserId()]; !ok { 70 | return 71 | } 72 | userList[m.GetUserId()].UserProps = append(userList[m.GetUserId()].UserProps, m.GetUserProps()...) 73 | 74 | log.Debug("", "财富查询,userId=%v,len=%v,gateConnId=%d", m.GetUserId(), len(m.GetUserProps()), userList[m.GetUserId()].GetGateConnid()) 75 | 76 | var authRsp gateway.AuthInfo 77 | authRsp.UserId = proto.Uint64(m.GetUserId()) 78 | authRsp.Gateconnid = proto.Uint64(userList[m.GetUserId()].GetGateConnid()) 79 | authRsp.Result = proto.Uint32(uint32(lobby.LoginRsp_SUCCESS)) 80 | g.SendData2App(n.AppGate, util.GetLUint32FromUint64(userList[m.GetUserId()].GetGateConnid()), n.AppGate, uint32(gateway.CMDGateway_IDAuthInfo), &authRsp) 81 | 82 | var rsp lobby.LoginRsp 83 | rsp.ErrInfo = new(types.ErrorInfo) 84 | rsp.ErrInfo.Info = proto.String("成功") 85 | rsp.ErrInfo.Code = proto.Int32(int32(lobby.LoginRsp_SUCCESS)) 86 | rsp.BaseInfo = new(types.BaseUserInfo) 87 | rsp.BaseInfo = userList[m.GetUserId()] 88 | rspBm := n.BaseMessage{MyMessage: &rsp, TraceId: ""} 89 | rspBm.Cmd = n.TCPCommand{AppType: uint16(n.AppLobby), CmdId: uint16(lobby.CMDLobby_IDLoginRsp)} 90 | g.SendMessage2Client(rspBm, userList[m.GetUserId()].GetGateConnid(), 0) 91 | } 92 | -------------------------------------------------------------------------------- /pkg/module/skeleton.go: -------------------------------------------------------------------------------- 1 | package module 2 | 3 | import ( 4 | "mango/pkg/chanrpc" 5 | g "mango/pkg/go" 6 | "mango/pkg/timer" 7 | "time" 8 | ) 9 | 10 | type Skeleton struct { 11 | GoLen int 12 | TimerDispatcherLen int 13 | AsynCallLen int 14 | ChanRPCServer *chanrpc.Server 15 | g *g.Go 16 | dispatcher *timer.Dispatcher 17 | client *chanrpc.Client 18 | server *chanrpc.Server 19 | commandServer *chanrpc.Server 20 | closeSig chan bool 21 | } 22 | 23 | func NewSkeleton(GoLen, TimerDispatcherLen, AsynCallLen, ChanRPCLen int) *Skeleton { 24 | skeleton := &Skeleton{ 25 | GoLen: GoLen, 26 | TimerDispatcherLen: TimerDispatcherLen, 27 | AsynCallLen: AsynCallLen, 28 | ChanRPCServer: chanrpc.NewServer(ChanRPCLen), 29 | } 30 | skeleton.Init() 31 | return skeleton 32 | } 33 | 34 | func (s *Skeleton) Init() { 35 | if s.GoLen <= 0 { 36 | s.GoLen = 0 37 | } 38 | if s.TimerDispatcherLen <= 0 { 39 | s.TimerDispatcherLen = 0 40 | } 41 | if s.AsynCallLen <= 0 { 42 | s.AsynCallLen = 0 43 | } 44 | 45 | s.g = g.New(s.GoLen) 46 | s.dispatcher = timer.NewDispatcher(s.TimerDispatcherLen) 47 | s.client = chanrpc.NewClient(s.AsynCallLen) 48 | s.server = s.ChanRPCServer 49 | 50 | if s.server == nil { 51 | s.server = chanrpc.NewServer(0) 52 | } 53 | s.commandServer = chanrpc.NewServer(0) 54 | s.closeSig = make(chan bool, 1) 55 | } 56 | 57 | func (s *Skeleton) Run() { 58 | for { 59 | select { 60 | case <-s.closeSig: 61 | s.commandServer.Close() 62 | s.server.Close() 63 | for !s.g.Idle() || !s.client.Idle() { 64 | s.g.Close() 65 | s.client.Close() 66 | } 67 | return 68 | case ri := <-s.client.ChanAsynRet: 69 | s.client.Cb(ri) 70 | case ci := <-s.server.ChanCall: 71 | s.server.Exec(ci) 72 | case ci := <-s.commandServer.ChanCall: 73 | s.commandServer.Exec(ci) 74 | case cb := <-s.g.ChanCb: 75 | s.g.Cb(cb) 76 | case t := <-s.dispatcher.ChanTimer: 77 | t.Cb() 78 | } 79 | } 80 | } 81 | 82 | func (s *Skeleton) Close() { 83 | s.closeSig <- true 84 | } 85 | 86 | func (s *Skeleton) AfterFunc(d time.Duration, cb func()) *timer.Timer { 87 | if s.TimerDispatcherLen == 0 { 88 | panic("invalid TimerDispatcherLen") 89 | } 90 | 91 | return s.dispatcher.AfterFunc(d, cb) 92 | } 93 | 94 | func (s *Skeleton) CronFunc(cronExpr *timer.CronExpr, cb func()) *timer.Cron { 95 | if s.TimerDispatcherLen == 0 { 96 | panic("invalid TimerDispatcherLen") 97 | } 98 | 99 | return s.dispatcher.CronFunc(cronExpr, cb) 100 | } 101 | 102 | func (s *Skeleton) LoopFunc(d time.Duration, cb func(), loopCount int) *timer.Timer { 103 | if s.TimerDispatcherLen == 0 { 104 | panic("invalid TimerDispatcherLen") 105 | } 106 | 107 | return s.dispatcher.LoopFunc(d, cb, loopCount) 108 | } 109 | 110 | func (s *Skeleton) Go(f func(), cb func()) { 111 | if s.GoLen == 0 { 112 | panic("invalid GoLen") 113 | } 114 | 115 | s.g.Go(f, cb) 116 | } 117 | 118 | func (s *Skeleton) NewLinearContext() *g.LinearContext { 119 | if s.GoLen == 0 { 120 | panic("invalid GoLen") 121 | } 122 | 123 | return s.g.NewLinearContext() 124 | } 125 | 126 | func (s *Skeleton) AsynCall(server *chanrpc.Server, id interface{}, args ...interface{}) { 127 | if s.AsynCallLen == 0 { 128 | panic("invalid AsynCallLen") 129 | } 130 | 131 | s.client.Attach(server) 132 | s.client.AsynCall(id, args...) 133 | } 134 | 135 | func (s *Skeleton) RegisterChanRPC(id interface{}, f interface{}) { 136 | if s.ChanRPCServer == nil { 137 | panic("invalid ChanRPCServer") 138 | } 139 | 140 | s.server.Register(id, f) 141 | } 142 | -------------------------------------------------------------------------------- /pkg/network/protobuf/protobuf.go: -------------------------------------------------------------------------------- 1 | package protobuf 2 | 3 | import ( 4 | "fmt" 5 | "github.com/golang/protobuf/proto" 6 | "mango/pkg/chanrpc" 7 | "mango/pkg/log" 8 | "mango/pkg/network" 9 | "math" 10 | "reflect" 11 | ) 12 | 13 | // ------------------------- 14 | // | id | protobuf message | 15 | // ------------------------- 16 | type Processor struct { 17 | littleEndian bool 18 | msgInfo map[network.TCPCommand]*MsgInfo 19 | msgID map[reflect.Type]network.TCPCommand 20 | } 21 | 22 | type MsgInfo struct { 23 | msgType reflect.Type 24 | msgRouter *chanrpc.Server 25 | } 26 | 27 | func NewProcessor() *Processor { 28 | p := new(Processor) 29 | p.littleEndian = false 30 | p.msgInfo = make(map[network.TCPCommand]*MsgInfo) 31 | p.msgID = make(map[reflect.Type]network.TCPCommand) 32 | 33 | return p 34 | } 35 | 36 | // It's dangerous to call the method on routing or marshaling (unmarshaling) 37 | func (p *Processor) SetByteOrder(littleEndian bool) { 38 | p.littleEndian = littleEndian 39 | } 40 | 41 | //异步rpc 42 | func (p *Processor) Register(msg proto.Message, appType uint32, cmdId uint16, msgRouter *chanrpc.Server) { 43 | msgType := reflect.TypeOf(msg) 44 | if msgType == nil || msgType.Kind() != reflect.Ptr { 45 | log.Fatal("proto", "protobuf message pointer required") 46 | } 47 | if len(p.msgInfo) >= math.MaxUint16 { 48 | log.Fatal("proto", "too many protobuf messages (max = %v)", math.MaxUint16) 49 | } 50 | 51 | //协议命令 52 | command := network.TCPCommand{AppType: uint16(appType), CmdId: cmdId} 53 | if _, ok := p.msgInfo[command]; ok { 54 | log.Fatal("proto", "message %s,cmd=%v is already registered", msgType, command) 55 | } 56 | 57 | i := new(MsgInfo) 58 | i.msgType = msgType 59 | i.msgRouter = msgRouter 60 | p.msgInfo[command] = i 61 | } 62 | 63 | // It's dangerous to call the method on routing or marshaling (unmarshaling) 64 | func (p *Processor) SetRouter(id network.TCPCommand, msgRouter *chanrpc.Server) { 65 | _, ok := p.msgInfo[id] 66 | if !ok { 67 | log.Fatal("proto", "message %v not registered", id) 68 | } 69 | 70 | p.msgInfo[id].msgRouter = msgRouter 71 | } 72 | 73 | // goroutine safe 74 | func (p *Processor) Route(args ...interface{}) error { 75 | if len(args) < network.MinRouteArgsCount { 76 | return fmt.Errorf("路由消息参数过少,MinRouteArgsCount=%v,len(args)=%v", len(args), network.MinRouteArgsCount) 77 | } 78 | //注册处理 79 | id := *args[network.CMDIndex].(*network.TCPCommand) 80 | i, ok := p.msgInfo[id] 81 | if ok && i.msgRouter != nil { 82 | i.msgRouter.Go(i.msgType, args...) 83 | return nil 84 | } 85 | return fmt.Errorf("异常,protobuf.go Route nil,ok=%v,id=%v", ok, id) 86 | } 87 | 88 | // goroutine safe 89 | func (p *Processor) Unmarshal(appType, cmdId uint16, data []byte) (interface{}, interface{}, error) { 90 | id := network.TCPCommand{AppType: appType, CmdId: cmdId} 91 | if _, ok := p.msgInfo[id]; !ok { 92 | return &id, nil, fmt.Errorf("protobuf Unmarshal木有找到ID=%v", id) 93 | } 94 | 95 | // msg 96 | i := p.msgInfo[id] 97 | msg := reflect.New(i.msgType.Elem()).Interface() 98 | return &id, msg, proto.UnmarshalMerge(data, msg.(proto.Message)) 99 | } 100 | 101 | // goroutine safe 102 | func (p *Processor) Marshal(msg interface{}) ([][]byte, error) { 103 | msgType := reflect.TypeOf(msg) 104 | 105 | // id 106 | _, ok := p.msgID[msgType] 107 | if !ok { 108 | err := fmt.Errorf("message %s not registered", msgType) 109 | return nil, err 110 | } 111 | 112 | // data 113 | data, err := proto.Marshal(msg.(proto.Message)) 114 | return [][]byte{data}, err 115 | } 116 | 117 | // goroutine safe 118 | func (p *Processor) Range(f func(id network.TCPCommand, t reflect.Type)) { 119 | for id, i := range p.msgInfo { 120 | f(network.TCPCommand(id), i.msgType) 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /cmd/table/business/table/ddz/table_sink.go: -------------------------------------------------------------------------------- 1 | package ddz 2 | 3 | import ( 4 | "github.com/golang/protobuf/proto" 5 | "mango/api/gameddz" 6 | "mango/cmd/table/business/table" 7 | "mango/pkg/log" 8 | n "mango/pkg/network" 9 | "math/rand" 10 | "time" 11 | ) 12 | 13 | const ( 14 | playerCount = 3 15 | ) 16 | 17 | type TableSink struct { 18 | frame table.Frame 19 | userHandCards [playerCount][]uint8 20 | bottomCards []uint8 21 | } 22 | 23 | func (s *TableSink) StartGame(f table.Frame) { 24 | s.frame = f 25 | 26 | c := cardData 27 | rand.Seed(time.Now().UnixNano()) 28 | rand.Shuffle(len(c), func(i, j int) { 29 | c[i], c[j] = c[j], c[i] 30 | }) 31 | 32 | s.userHandCards[0] = c[17*0 : 17*1] 33 | s.userHandCards[1] = c[17*1 : 17*2] 34 | s.userHandCards[2] = c[17*2 : 17*3] 35 | s.bottomCards = c[17*3:] 36 | 37 | log.Debug("", "游戏开始") 38 | 39 | var start gameddz.GameStart 40 | start.CurrentSeat = proto.Uint32(0) 41 | for i := 0; i < playerCount; i++ { 42 | start.HandCard = make([][]byte, playerCount) 43 | start.HandCard[i] = s.userHandCards[0] 44 | bm := n.BaseMessage{MyMessage: &start, TraceId: ""} 45 | bm.Cmd = n.TCPCommand{AppType: uint16(n.AppTable), CmdId: uint16(gameddz.CMDGameddz_IDGameStart)} 46 | s.frame.SendTableData(uint32(i), bm) 47 | } 48 | } 49 | 50 | func (s *TableSink) EndGame() { 51 | 52 | } 53 | 54 | func (s *TableSink) GameMessage(seatId, cmdId uint32, data []byte) { 55 | switch cmdId { 56 | case uint32(gameddz.CMDGameddz_IDCallLandReq): 57 | s.CallLandReq(seatId, data) 58 | case uint32(gameddz.CMDGameddz_IDOutCardReq): 59 | s.OutCardReq(seatId, data) 60 | case uint32(gameddz.CMDGameddz_IDGameDataReq): 61 | s.GameDataReq(seatId, data) 62 | default: 63 | log.Warning("", "未定义消息,seatId=%d,cmdId=%d", seatId, cmdId) 64 | } 65 | } 66 | 67 | func (s *TableSink) CallLandReq(seatId uint32, data []byte) { 68 | var m gameddz.CallLandReq 69 | _ = proto.Unmarshal(data, &m) 70 | 71 | var rsp gameddz.CallLandRsp 72 | bm := n.BaseMessage{MyMessage: &rsp, TraceId: ""} 73 | bm.Cmd = n.TCPCommand{AppType: uint16(n.AppTable), CmdId: uint16(gameddz.CMDGameddz_IDCallLandRsp)} 74 | s.frame.SendTableData(table.InvalidSeadID, bm) 75 | 76 | log.Debug("", "叫地主消息,seatId=%d", seatId) 77 | } 78 | 79 | func (s *TableSink) OutCardReq(seatId uint32, data []byte) { 80 | var m gameddz.OutCardReq 81 | _ = proto.Unmarshal(data, &m) 82 | 83 | if len(m.GetOutCard()) >= len(s.userHandCards[seatId]) { 84 | s.userHandCards[seatId] = append([]uint8{}) 85 | } else { 86 | s.userHandCards[seatId] = s.userHandCards[seatId][:len(s.userHandCards[seatId])-len(m.GetOutCard())] 87 | } 88 | 89 | var rsp gameddz.OutCardRsp 90 | bm := n.BaseMessage{MyMessage: &rsp, TraceId: ""} 91 | bm.Cmd = n.TCPCommand{AppType: uint16(n.AppTable), CmdId: uint16(gameddz.CMDGameddz_IDOutCardRsp)} 92 | s.frame.SendTableData(seatId, bm) 93 | 94 | log.Debug("", "出牌消息,seatId=%d,len=%v", seatId, len(s.userHandCards[seatId])) 95 | 96 | if len(s.userHandCards[seatId]) == 0 { 97 | log.Debug("", "本局结束") 98 | 99 | var over gameddz.GameOver 100 | bm := n.BaseMessage{MyMessage: &over, TraceId: ""} 101 | bm.Cmd = n.TCPCommand{AppType: uint16(n.AppTable), CmdId: uint16(gameddz.CMDGameddz_IDGameOver)} 102 | s.frame.SendTableData(table.InvalidSeadID, bm) 103 | 104 | s.frame.WriteGameScore() 105 | s.frame.GameOver() 106 | } 107 | } 108 | 109 | func (s *TableSink) GameDataReq(seatId uint32, data []byte) { 110 | var m gameddz.GameDataReq 111 | _ = proto.Unmarshal(data, &m) 112 | 113 | var rsp gameddz.GameDataRsp 114 | bm := n.BaseMessage{MyMessage: &rsp, TraceId: ""} 115 | bm.Cmd = n.TCPCommand{AppType: uint16(n.AppTable), CmdId: uint16(gameddz.CMDGameddz_IDGameDataRsp)} 116 | s.frame.SendTableData(table.InvalidSeadID, bm) 117 | 118 | log.Debug("", "数据消息,seatId=%d", seatId) 119 | } 120 | -------------------------------------------------------------------------------- /scripts/database/mango_property.sql: -------------------------------------------------------------------------------- 1 | -- 创建逻辑库 2 | DROP DATABASE if EXISTS mango_property; 3 | CREATE DATABASE mango_property DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; 4 | 5 | /*----------------------------------------------------------------------------------*/ 6 | -- 财富信息 7 | DROP TABLE IF EXISTS `mango_property`.`wealth`; 8 | CREATE TABLE `mango_property`.`wealth` ( 9 | `sys_id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增id', 10 | `user_id` BIGINT NOT NULL DEFAULT '0' COMMENT 'user_id', 11 | `ingot` BIGINT NOT NULL DEFAULT '0' COMMENT 'ingot', 12 | `coin` BIGINT NOT NULL DEFAULT '0' COMMENT 'coin', 13 | `last_change_id` SMALLINT UNSIGNED NOT NULL DEFAULT '0' COMMENT '最后变化财富id<=100', 14 | `last_change_count` BIGINT NOT NULL DEFAULT '0' COMMENT '最后变化数量', 15 | `last_change_reason` INT NOT NULL DEFAULT '0' COMMENT '最后变化原因', 16 | `last_change_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后变化时间', 17 | PRIMARY KEY (`sys_id`,`user_id`) USING BTREE, 18 | KEY `idx_user_id` (`user_id`) 19 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='财富信息'; 20 | 21 | 22 | /*----------------------------------------------------------------------------------*/ 23 | -- 财富变化日志 24 | DROP TABLE IF EXISTS `mango_property`.`wealth_change_log`; 25 | CREATE TABLE `mango_property`.`wealth_change_log` ( 26 | `sys_id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增id', 27 | `user_id` BIGINT NOT NULL DEFAULT '0' COMMENT 'user_id', 28 | `reason` INT NOT NULL DEFAULT '0' COMMENT '原因', 29 | `source_app_type` INT NOT NULL DEFAULT '0' COMMENT '来源类型', 30 | `source_app_id` INT NOT NULL DEFAULT '0' COMMENT '来源id', 31 | `change_id` SMALLINT UNSIGNED NOT NULL DEFAULT '0' COMMENT '变化财富id<=100', 32 | `change_count` BIGINT NOT NULL DEFAULT '0' COMMENT '变化数量', 33 | `change_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '变化时间', 34 | PRIMARY KEY (`sys_id`,`user_id`) USING BTREE, 35 | KEY `idx_user_id` (`user_id`) 36 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='财富变化日志'; 37 | 38 | /*----------------------------------------------------------------------------------*/ 39 | -- 道具信息 40 | DROP TABLE IF EXISTS `mango_property`.`prop`; 41 | CREATE TABLE `mango_property`.`prop` ( 42 | `sys_id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增id', 43 | `user_id` BIGINT NOT NULL DEFAULT '0' COMMENT 'user_id', 44 | `id` BIGINT UNSIGNED NOT NULL DEFAULT '0' COMMENT '道具id', 45 | `count` BIGINT NOT NULL DEFAULT '0' COMMENT '道具数量', 46 | `last_change_count` BIGINT NOT NULL DEFAULT '0' COMMENT '最后变化数量', 47 | `last_change_reason` INT NOT NULL DEFAULT '0' COMMENT '最后变化原因', 48 | `last_change_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后变化时间', 49 | PRIMARY KEY (`sys_id`,`user_id`) USING BTREE, 50 | KEY `idx_user_id` (`user_id`), 51 | KEY `idx_id` (`id`) 52 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='道具信息'; 53 | 54 | /*----------------------------------------------------------------------------------*/ 55 | -- 道具变化日志 56 | DROP TABLE IF EXISTS `mango_property`.`prop_change_log`; 57 | CREATE TABLE `mango_property`.`prop_change_log` ( 58 | `sys_id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增id', 59 | `user_id` BIGINT NOT NULL DEFAULT '0' COMMENT 'user_id', 60 | `reason` INT NOT NULL DEFAULT '0' COMMENT '原因', 61 | `source_app_type` INT NOT NULL DEFAULT '0' COMMENT '来源类型', 62 | `source_app_id` INT NOT NULL DEFAULT '0' COMMENT '来源id', 63 | `change_id` BIGINT UNSIGNED NOT NULL DEFAULT '0' COMMENT '变化道具', 64 | `change_count` BIGINT NOT NULL DEFAULT '0' COMMENT '变化数量', 65 | `change_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '变化时间', 66 | PRIMARY KEY (`sys_id`,`user_id`) USING BTREE, 67 | KEY `idx_user_id` (`user_id`) 68 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='道具变化日志'; -------------------------------------------------------------------------------- /pkg/conf/conf.go: -------------------------------------------------------------------------------- 1 | package conf 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "mango/pkg/log" 8 | n "mango/pkg/network" 9 | "mango/pkg/util" 10 | "io/ioutil" 11 | "os" 12 | "strconv" 13 | ) 14 | 15 | const ( 16 | ArgAppName string = "-Name" 17 | ArgAppType string = "-Type" 18 | ArgAppId string = "-Id" 19 | ArgCenterAddr string = "-CenterAddr" 20 | ArgListenOnAddr string = "-ListenOnAddr" 21 | ArgDockerRun string = "-DockerRun" 22 | ArgDefaultBasePort string = "-DefaultBasePort" 23 | 24 | //服务状态 25 | AppStateNone = 0 26 | AppStateStarting = 1 27 | AppStateRunning = 2 28 | AppStateMaintenance = 4 29 | AppStateMaintenanceFinish = 8 30 | AppStateOffline = 16 31 | ) 32 | 33 | var ( 34 | LenStackBuf = 4096 35 | GoLen = 10000 36 | TimerDispatcherLen = 10000 37 | AsynCallLen = 10000 38 | ChanRPCLen = 10000 39 | DefaultBasePort uint32 = 10000 40 | AppInfo BaseInfo 41 | ApplogDir string 42 | ) 43 | 44 | type BaseInfo struct { 45 | Name string 46 | Type uint32 47 | Id uint32 48 | ListenOnAddr string 49 | CenterAddr string 50 | } 51 | 52 | func LoadBaseConfig(name string) { 53 | AppInfo.Name = name 54 | if AppInfo.Name != "" { 55 | data, err := ioutil.ReadFile(fmt.Sprintf("configs/%s/%s.json", AppInfo.Name, AppInfo.Name)) 56 | if err == nil { 57 | err = json.Unmarshal(data, &AppInfo) 58 | } 59 | } 60 | args := os.Args 61 | if v, ok := util.ParseArgsString(ArgAppName, args); ok { 62 | AppInfo.Name = v 63 | } 64 | if v, ok := util.ParseArgsUint32(ArgAppType, args); ok { 65 | AppInfo.Type = v 66 | } 67 | if v, ok := util.ParseArgsUint32(ArgAppId, args); ok { 68 | AppInfo.Id = v 69 | } 70 | if v, ok := util.ParseArgsString(ArgCenterAddr, args); ok { 71 | AppInfo.CenterAddr = v 72 | } 73 | if v, ok := util.ParseArgsString(ArgListenOnAddr, args); ok { 74 | AppInfo.ListenOnAddr = v 75 | } 76 | if v, ok := util.ParseArgsUint32(ArgDefaultBasePort, args); ok { 77 | DefaultBasePort = v 78 | } 79 | if AppInfo.ListenOnAddr == "" { 80 | AppInfo.ListenOnAddr = fmt.Sprintf("0.0.0.0:%d", DefaultBasePort+AppInfo.Id) 81 | } 82 | if AppInfo.CenterAddr == "" && AppInfo.Type != n.AppCenter && AppInfo.Type != n.AppLogger { 83 | AppInfo.CenterAddr = fmt.Sprintf("127.0.0.1:%v", DefaultBasePort+50) 84 | log.Debug("", "使用默认地址,CenterAddr=%v", AppInfo.CenterAddr) 85 | } 86 | if RunInLocalDocker() { 87 | AppInfo.CenterAddr = "center:" + strconv.Itoa(util.GetPortFromIPAddress(AppInfo.CenterAddr)) 88 | } 89 | 90 | if util.PortInUse(util.GetPortFromIPAddress(AppInfo.ListenOnAddr)) { 91 | log.Fatal("初始化", "端口[%v]已被占用,请检查运行环境", util.GetPortFromIPAddress(AppInfo.ListenOnAddr)) 92 | return 93 | } 94 | 95 | if AppInfo.Name == "" || AppInfo.Type == 0 || AppInfo.Id == 0 || AppInfo.ListenOnAddr == "" || 96 | (AppInfo.CenterAddr == "" && AppInfo.Type != n.AppCenter && AppInfo.Type != n.AppLogger) { 97 | log.Fatal("初始化", "初始参数异常,请检查,AppInfo=%v", AppInfo) 98 | return 99 | } 100 | 101 | //创建日志目录 102 | if err := makeAppLogDir(); err != nil { 103 | log.Fatal("初始化", "创建日志目录失败,err=%v", err) 104 | return 105 | } 106 | 107 | log.Debug("", "基础属性,%v,log目录=%v", AppInfo, ApplogDir) 108 | } 109 | 110 | func RunInLocalDocker() bool { 111 | args := os.Args 112 | if v, ok := util.ParseArgsUint32(ArgDockerRun, args); ok && v == 1 { 113 | return true 114 | } 115 | return false 116 | } 117 | 118 | func makeAppLogDir() error { 119 | curPath, err := util.GetCurrentPath() 120 | if err != nil { 121 | return errors.New("获取当前路径失败") 122 | } 123 | pathName := fmt.Sprintf("%slog/%s%d/", curPath, AppInfo.Name, AppInfo.Id) 124 | err = os.MkdirAll(pathName, os.ModePerm) 125 | if err != nil { 126 | return errors.New("文件路径创建失败") 127 | } 128 | ApplogDir = pathName 129 | return nil 130 | } 131 | -------------------------------------------------------------------------------- /scripts/database/mango_logic.sql: -------------------------------------------------------------------------------- 1 | -- 创建逻辑库 2 | DROP DATABASE if EXISTS mango_logic; 3 | CREATE DATABASE mango_logic DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; 4 | 5 | /*----------------------------------------------------------------------------------*/ 6 | /*创建表*/ 7 | /*----------------------------------------------------------------------------------*/ 8 | -- 账号信息 9 | DROP TABLE IF EXISTS `mango_logic`.`account`; 10 | CREATE TABLE `mango_logic`.`account` ( 11 | `sys_id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增id', 12 | `user_id` BIGINT UNSIGNED NOT NULL DEFAULT '0' COMMENT 'user_id', 13 | `game_id` BIGINT NOT NULL DEFAULT '0' COMMENT 'game_id', 14 | `account` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '账号', 15 | `pass_word` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '密码', 16 | `nick_name` VARCHAR(30) NOT NULL DEFAULT '' COMMENT '昵称', 17 | `avatar` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '头像', 18 | `sex` INT NOT NULL DEFAULT '0' COMMENT '性别 1、男 0、女', 19 | `active` INT NOT NULL DEFAULT '0' COMMENT '用户累计活跃天数', 20 | `age` INT NOT NULL DEFAULT '0', 21 | `login_type` INT NOT NULL DEFAULT '0' COMMENT '类型 0=账号 1=Token 2=唯一标识登录', 22 | `device_id` VARCHAR(256) NOT NULL DEFAULT '' COMMENT '设备唯一ID', 23 | `reg_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '注册时间', 24 | `reg_ip` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '注册IP地址', 25 | `os` INT NOT NULL DEFAULT '0' COMMENT '0、未知 1、IOS 2、Android 3、h5', 26 | `frozen` INT NOT NULL DEFAULT '0' COMMENT '冻结状态 1、冻结 0、正常', 27 | `token` VARCHAR(255) NOT NULL DEFAULT '' COMMENT 'token', 28 | `market_id` INT NOT NULL DEFAULT '0' COMMENT '登录主渠道', 29 | `site_id` INT NOT NULL DEFAULT '0' COMMENT '登录子渠道', 30 | `reg_market_id` INT NOT NULL DEFAULT '0' COMMENT '注册主渠道', 31 | `reg_site_id` INT NOT NULL DEFAULT '0' COMMENT '注册子渠道', 32 | `user_type` TINYINT NOT NULL DEFAULT '0' COMMENT '用户类型', 33 | `last_login_time` TIMESTAMP NULL DEFAULT NULL COMMENT '最后登录时间', 34 | `last_login_type` INT NOT NULL DEFAULT '0' COMMENT '最后登录类型', 35 | `last_login_ip` VARCHAR(128) NOT NULL DEFAULT '', 36 | `last_login_addr` VARCHAR(128) NOT NULL DEFAULT '', 37 | `last_login_area` VARCHAR(128) NOT NULL DEFAULT '', 38 | PRIMARY KEY (`sys_id`,`user_id`) USING BTREE, 39 | UNIQUE KEY `user_id` (`user_id`), 40 | UNIQUE KEY `game_id` (`game_id`), 41 | KEY `nick_name` (`nick_name`), 42 | KEY `device_id` (`device_id`), 43 | KEY `login_type` (`login_type`), 44 | KEY `user_type` (`user_type`), 45 | KEY `ix_lastlogintime` (`last_login_time`) 46 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='账号信息'; 47 | 48 | /*----------------------------------------------------------------------------------*/ 49 | -- 登录日志 50 | DROP TABLE IF EXISTS `mango_logic`.`login_log`; 51 | CREATE TABLE `mango_logic`.`login_log` ( 52 | `sys_id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增id', 53 | `user_id` BIGINT UNSIGNED NOT NULL DEFAULT '0' COMMENT 'user_id', 54 | `game_id` BIGINT NOT NULL DEFAULT '0' COMMENT 'game_id', 55 | `account` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '账号', 56 | `reason` INT NOT NULL DEFAULT '0' COMMENT '原因', 57 | `device_id` VARCHAR(256) NOT NULL DEFAULT '' COMMENT '设备唯一ID', 58 | `login_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '登录时间', 59 | `logout_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '退出时间', 60 | PRIMARY KEY (`sys_id`,`user_id`) USING BTREE 61 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='登录日志'; 62 | 63 | /*----------------------------------------------------------------------------------*/ 64 | 65 | -- 新手引导 66 | DROP TABLE IF EXISTS `mango_logic`.`novice_guide`; 67 | CREATE TABLE `mango_logic`.`novice_guide` ( 68 | `sys_id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增id', 69 | `user_id` BIGINT UNSIGNED NOT NULL DEFAULT '0' COMMENT '玩家id', 70 | `step_number` INT UNSIGNED NOT NULL DEFAULT '0' COMMENT '进度值', 71 | `create_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', 72 | `change_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '变化时间', 73 | PRIMARY KEY (`sys_id`) USING BTREE, 74 | UNIQUE `idx_user_id` (`user_id`), 75 | KEY `idx_step_number` (`step_number`) 76 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='新手引导'; -------------------------------------------------------------------------------- /pkg/network/http_server.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "mango/pkg/log" 7 | "mango/pkg/util" 8 | "mango/pkg/util/errorhelper" 9 | "net/http" 10 | "net/http/pprof" 11 | "sync" 12 | ) 13 | 14 | type HttpServer struct { 15 | Port int 16 | mux *http.ServeMux 17 | sync.Mutex 18 | isStart bool 19 | route map[string]func(http.ResponseWriter, *http.Request) 20 | } 21 | 22 | func NewHttpServer() *HttpServer { 23 | hs := new(HttpServer) 24 | hs.isStart = false 25 | hs.route = make(map[string]func(http.ResponseWriter, *http.Request)) 26 | return hs 27 | } 28 | 29 | func (hs *HttpServer) IsStart() bool { 30 | return hs.isStart 31 | } 32 | 33 | func (hs *HttpServer) Start() { 34 | if hs.Port == 0 || hs.IsStart() { 35 | return 36 | } 37 | go hs.run() 38 | } 39 | 40 | func (hs *HttpServer) run() { 41 | defer errorhelper.Recover() 42 | 43 | hs.Lock() 44 | if hs.mux == nil { 45 | hs.mux = http.NewServeMux() 46 | } 47 | hs.mux.HandleFunc("/debug/pprof/", pprof.Index) 48 | for r, h := range hs.route { 49 | hs.mux.HandleFunc(r, h) 50 | } 51 | hs.Unlock() 52 | 53 | hs.isStart = true 54 | 55 | log.Info("", fmt.Sprintf("http服务器启动,Port=%d,route=%v", hs.Port, len(hs.route))) 56 | 57 | if err := http.ListenAndServe(":"+fmt.Sprintf("%d", hs.Port), hs.mux); err != nil { 58 | log.Fatal("", "异常,http服务启动失败,port=%v,err=%v", hs.Port, err) 59 | } 60 | } 61 | 62 | func (hs *HttpServer) AddRoute(route string, handler func(http.ResponseWriter, *http.Request)) { 63 | if hs.IsStart() { 64 | return 65 | } 66 | if _, exist := hs.route[route]; exist { 67 | log.Warning("", "route已存在,r=%v", route) 68 | return 69 | } 70 | 71 | hs.route[route] = handler 72 | } 73 | 74 | type Responses interface { 75 | SetCode(int32) 76 | SetTraceID(string) 77 | SetMsg(string) 78 | SetData(interface{}) 79 | SetSuccess(bool) 80 | Clone() Responses 81 | } 82 | 83 | type Response struct { 84 | // 数据集 85 | RequestId string `json:"requestId,omitempty"` 86 | Code int32 `json:"code,omitempty"` 87 | Msg string `json:"msg,omitempty"` 88 | Status string `json:"status,omitempty"` 89 | } 90 | 91 | type response struct { 92 | Response 93 | Data interface{} `json:"data"` 94 | } 95 | 96 | type Page struct { 97 | Count int `json:"count"` 98 | PageIndex int `json:"pageIndex"` 99 | PageSize int `json:"pageSize"` 100 | } 101 | 102 | type page struct { 103 | Page 104 | List interface{} `json:"list"` 105 | } 106 | 107 | func (e *response) SetData(data interface{}) { 108 | e.Data = data 109 | } 110 | 111 | func (e response) Clone() Responses { 112 | return &e 113 | } 114 | 115 | func (e *response) SetTraceID(id string) { 116 | e.RequestId = id 117 | } 118 | 119 | func (e *response) SetMsg(s string) { 120 | e.Msg = s 121 | } 122 | 123 | func (e *response) SetCode(code int32) { 124 | e.Code = code 125 | } 126 | 127 | func (e *response) SetSuccess(success bool) { 128 | if !success { 129 | e.Status = "error" 130 | } 131 | } 132 | 133 | // SetupCORS 忽略跨域问题 134 | func SetupCORS(rsp *http.ResponseWriter) { 135 | (*rsp).Header().Set("Access-Control-Allow-Origin", "*") 136 | (*rsp).Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE") 137 | (*rsp).Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization") 138 | } 139 | 140 | func HttpSuccess(rsp http.ResponseWriter, data interface{}, msg string) { 141 | r := &response{} 142 | r.SetData(data) 143 | r.SetSuccess(true) 144 | if msg != "" { 145 | r.SetMsg(msg) 146 | } 147 | r.SetTraceID(util.GetUUID()) 148 | r.SetCode(http.StatusOK) 149 | if buff, err := json.Marshal(r); err == nil { 150 | log.Debug("", "成功了,buff=%v", string(buff)) 151 | rsp.Write(buff) 152 | } else { 153 | log.Warning("", "失败了") 154 | } 155 | } 156 | 157 | func HttpFail(rsp http.ResponseWriter, code uint32, err error, msg string) { 158 | r := &response{} 159 | 160 | if err != nil { 161 | r.SetMsg(err.Error()) 162 | } 163 | if msg != "" { 164 | r.SetMsg(msg) 165 | } 166 | r.SetTraceID(util.GetUUID()) 167 | r.SetCode(int32(code)) 168 | r.SetSuccess(false) 169 | if buff, err := json.Marshal(r); err == nil { 170 | log.Debug("", "失败了,buff=%v", string(buff)) 171 | rsp.Write(buff) 172 | } else { 173 | log.Warning("", "失败了") 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /pkg/gate/clients.go: -------------------------------------------------------------------------------- 1 | package gate 2 | 3 | import ( 4 | "net" 5 | "reflect" 6 | 7 | "mango/api/center" 8 | "mango/api/gateway" 9 | "mango/pkg/conf" 10 | "mango/pkg/log" 11 | n "mango/pkg/network" 12 | "mango/pkg/util" 13 | 14 | "github.com/golang/protobuf/proto" 15 | ) 16 | 17 | type agentClient struct { 18 | conn n.Conn 19 | info n.BaseAgentInfo 20 | } 21 | 22 | func (a *agentClient) Run() { 23 | for { 24 | bm, msgData, err := a.conn.ReadMsg() 25 | if err != nil { 26 | //服务间连接 27 | if a.info.AppType == n.CommonServer { 28 | log.Error("agentClient", "异常,网关读取消息失败,info=%v,err=%v", a.info, err) 29 | continue 30 | } else { 31 | log.Warning("agentClient", "异常,网关读取消息失败,info=%v,err=%v", a.info, err) 32 | break 33 | } 34 | } 35 | if processor == nil { 36 | log.Error("agentClient", "异常,解析器为nil断开连接,cmd=%v", &bm.Cmd) 37 | break 38 | } 39 | 40 | if conf.AppInfo.Type != n.AppCenter && bm.Cmd.AppType == uint16(n.AppCenter) { 41 | if bm.Cmd.CmdId == uint16(center.CMDCenter_IDAppRegReq) { 42 | var m center.RegisterAppReq 43 | _ = proto.Unmarshal(msgData, &m) 44 | a.info = n.BaseAgentInfo{AgentType: n.CommonServer, AppName: m.GetAppName(), AppType: m.GetAppType(), AppId: m.GetAppId()} 45 | if agentChanRPC != nil { 46 | agentChanRPC.Call0(CommonServerReg, a, a.info) 47 | } 48 | log.Debug("", "相互注册,%v", a.info) 49 | mxClients.Lock() 50 | clients[util.MakeUint64FromUint32(a.info.AppType, a.info.AppId)] = a 51 | mxClients.Unlock() 52 | continue 53 | } else if bm.Cmd.CmdId == uint16(center.CMDCenter_IDHeartBeatReq) { 54 | //TODO 其他服务传来的心跳暂不处理 55 | continue 56 | } 57 | } 58 | 59 | unmarshalCmd := bm.Cmd 60 | var cmd, msg, dataReq interface{} 61 | if bm.Cmd.AppType == uint16(n.AppGate) && bm.Cmd.CmdId == uint16(gateway.CMDGateway_IDTransferDataReq) && conf.AppInfo.Type != n.AppGate { 62 | var m gateway.TransferDataReq 63 | _ = proto.Unmarshal(msgData, &m) 64 | unmarshalCmd = n.TCPCommand{AppType: uint16(m.GetDataApptype()), CmdId: uint16(m.GetDataCmdid())} 65 | msgData = m.GetData() 66 | dataReq = &m 67 | bm.AgentInfo = n.BaseAgentInfo{AgentType: n.NormalUser, AppName: "NormalUser", AppType: util.GetHUint32FromUint64(m.GetGateconnid()), AppId: util.GetLUint32FromUint64(m.GetGateconnid())} 68 | } else { 69 | bm.AgentInfo = a.info 70 | dataReq = a.info 71 | } 72 | 73 | cmd, msg, err = processor.Unmarshal(unmarshalCmd.AppType, unmarshalCmd.CmdId, msgData) 74 | if err != nil { 75 | log.Warning("agentClient", "异常,agentClient反序列化,headCmd=%v,error: %v", bm.Cmd, err) 76 | continue 77 | } 78 | err = processor.Route(n.BaseMessage{MyMessage: msg, AgentInfo: bm.AgentInfo, TraceId: bm.TraceId}, a, cmd, dataReq) 79 | if err != nil { 80 | log.Warning("agentClient", "client agentClient route message error: %v,cmd=%v", err, cmd) 81 | continue 82 | } 83 | } 84 | } 85 | 86 | func (a *agentClient) OnClose() { 87 | if agentChanRPC != nil { 88 | err := agentChanRPC.Call0(Disconnect, a) 89 | if err != nil { 90 | log.Warning("agentClient", "agentClient OnClose err=%v", err) 91 | } 92 | } 93 | 94 | mxClients.Lock() 95 | delete(clients, util.MakeUint64FromUint32(a.info.AppType, a.info.AppId)) 96 | mxClients.Unlock() 97 | } 98 | 99 | func (a *agentClient) LocalAddr() net.Addr { 100 | return a.conn.LocalAddr() 101 | } 102 | 103 | func (a *agentClient) RemoteAddr() net.Addr { 104 | return a.conn.RemoteAddr() 105 | } 106 | 107 | func (a *agentClient) Close() { 108 | a.conn.Close() 109 | } 110 | 111 | func (a *agentClient) Destroy() { 112 | a.conn.Destroy() 113 | } 114 | 115 | func (a *agentClient) SendData(appType, cmdId uint32, m proto.Message) { 116 | data, err := proto.Marshal(m) 117 | if err != nil { 118 | log.Error("agentClient", "异常,proto.Marshal %v error: %v", reflect.TypeOf(m), err) 119 | return 120 | } 121 | 122 | //超长判断 123 | if len(data) > int(MaxMsgLen-1024) { 124 | log.Error("agentClient", "异常,消息体超长,type=%v,appType=%v,cmdId=%v,len=%v,max=%v", reflect.TypeOf(m), appType, cmdId, len(data), int(MaxMsgLen-1024)) 125 | return 126 | } 127 | 128 | err = a.conn.WriteMsg(uint16(appType), uint16(cmdId), data, nil) 129 | if err != nil { 130 | log.Error("agentClient", "write message %v error: %v", reflect.TypeOf(m), err) 131 | } 132 | } 133 | 134 | func (a *agentClient) AgentInfo() *n.BaseAgentInfo { 135 | return &a.info 136 | } 137 | -------------------------------------------------------------------------------- /pkg/network/ws_server.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | import ( 4 | "crypto/tls" 5 | "net" 6 | "net/http" 7 | "sync" 8 | "time" 9 | "mango/pkg/log" 10 | 11 | "github.com/gorilla/websocket" 12 | ) 13 | 14 | type WSServer struct { 15 | Addr string 16 | MaxConnNum int 17 | PendingWriteNum int 18 | MaxMsgLen uint32 19 | HTTPTimeout time.Duration 20 | CertFile string 21 | KeyFile string 22 | NewAgent func(*WSConn) AgentClient 23 | ln net.Listener 24 | handler *WSHandler 25 | } 26 | 27 | type WSHandler struct { 28 | maxConnNum int 29 | pendingWriteNum int 30 | maxMsgLen uint32 31 | newAgent func(*WSConn) AgentClient 32 | upgrader websocket.Upgrader 33 | conns WebsocketConnSet 34 | mutexConns sync.Mutex 35 | wg sync.WaitGroup 36 | } 37 | 38 | func (handler *WSHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 39 | if r.Method != "GET" { 40 | http.Error(w, "Method not allowed", 405) 41 | return 42 | } 43 | conn, err := handler.upgrader.Upgrade(w, r, nil) 44 | if err != nil { 45 | log.Debug("ws_conn", "upgrade error: %v", err) 46 | return 47 | } 48 | conn.SetReadLimit(int64(handler.maxMsgLen)) 49 | 50 | handler.wg.Add(1) 51 | defer handler.wg.Done() 52 | 53 | handler.mutexConns.Lock() 54 | if handler.conns == nil { 55 | handler.mutexConns.Unlock() 56 | conn.Close() 57 | return 58 | } 59 | if len(handler.conns) >= handler.maxConnNum { 60 | handler.mutexConns.Unlock() 61 | conn.Close() 62 | log.Debug("ws_conn", "too many connections") 63 | return 64 | } 65 | handler.conns[conn] = struct{}{} 66 | handler.mutexConns.Unlock() 67 | 68 | wsConn := newWSConn(conn, handler.pendingWriteNum, handler.maxMsgLen) 69 | agent := handler.newAgent(wsConn) 70 | agent.Run() 71 | 72 | // cleanup 73 | wsConn.Close() 74 | handler.mutexConns.Lock() 75 | delete(handler.conns, conn) 76 | handler.mutexConns.Unlock() 77 | agent.OnClose() 78 | } 79 | 80 | func (server *WSServer) Start() { 81 | ln, err := net.Listen("tcp", server.Addr) 82 | if err != nil { 83 | log.Fatal("wsserver", "%v", err) 84 | } 85 | 86 | if server.MaxConnNum <= 0 { 87 | server.MaxConnNum = 100 88 | log.Info("WSServer", "invalid MaxConnNum, reset to %v", server.MaxConnNum) 89 | } 90 | if server.PendingWriteNum <= 0 { 91 | server.PendingWriteNum = 100 92 | log.Info("WSServer", "invalid PendingWriteNum, reset to %v", server.PendingWriteNum) 93 | } 94 | if server.MaxMsgLen <= 0 { 95 | server.MaxMsgLen = 4096 96 | log.Info("WSServer", "invalid MaxMsgLen, reset to %v", server.MaxMsgLen) 97 | } 98 | if server.HTTPTimeout <= 0 { 99 | server.HTTPTimeout = 10 * time.Second 100 | log.Info("WSServer", "invalid HTTPTimeout, reset to %v", server.HTTPTimeout) 101 | } 102 | if server.NewAgent == nil { 103 | log.Fatal("wsserver", "NewAgent must not be nil") 104 | } 105 | 106 | if server.CertFile != "" || server.KeyFile != "" { 107 | config := &tls.Config{} 108 | config.NextProtos = []string{"http/1.1"} 109 | 110 | var err error 111 | config.Certificates = make([]tls.Certificate, 1) 112 | config.Certificates[0], err = tls.LoadX509KeyPair(server.CertFile, server.KeyFile) 113 | if err != nil { 114 | log.Fatal("wsserver", "%v", err) 115 | } 116 | 117 | ln = tls.NewListener(ln, config) 118 | } 119 | 120 | server.ln = ln 121 | server.handler = &WSHandler{ 122 | maxConnNum: server.MaxConnNum, 123 | pendingWriteNum: server.PendingWriteNum, 124 | maxMsgLen: server.MaxMsgLen, 125 | newAgent: server.NewAgent, 126 | conns: make(WebsocketConnSet), 127 | upgrader: websocket.Upgrader{ 128 | HandshakeTimeout: server.HTTPTimeout, 129 | CheckOrigin: func(_ *http.Request) bool { return true }, 130 | }, 131 | } 132 | 133 | httpServer := &http.Server{ 134 | Addr: server.Addr, 135 | Handler: server.handler, 136 | ReadTimeout: server.HTTPTimeout, 137 | WriteTimeout: server.HTTPTimeout, 138 | MaxHeaderBytes: 1024, 139 | } 140 | 141 | go httpServer.Serve(ln) 142 | } 143 | 144 | func (server *WSServer) Close() { 145 | server.ln.Close() 146 | 147 | server.handler.mutexConns.Lock() 148 | for conn := range server.handler.conns { 149 | conn.Close() 150 | } 151 | server.handler.conns = nil 152 | server.handler.mutexConns.Unlock() 153 | 154 | server.handler.wg.Wait() 155 | } 156 | -------------------------------------------------------------------------------- /cmd/center/business/http_handler.go: -------------------------------------------------------------------------------- 1 | package business 2 | 3 | import ( 4 | "encoding/json" 5 | "io/ioutil" 6 | g "mango/pkg/gate" 7 | "mango/pkg/log" 8 | n "mango/pkg/network" 9 | "mango/pkg/util" 10 | "mango/pkg/util/errorhelper" 11 | "net/http" 12 | "sort" 13 | ) 14 | 15 | const ( 16 | //code成功 17 | CODE_SUCCESS = 200 18 | //未知错误 19 | CODE_UNKNOWN = 10001 20 | //参数错误 21 | CODE_PARAMERROR = 10002 22 | //token验证失败 23 | CODE_TOKENERROR = 10003 24 | ) 25 | 26 | func init() { 27 | if g.HttpServer == nil { 28 | log.Warning("", "警告,HttpServer == nil") 29 | return 30 | } 31 | 32 | if g.HttpServer.IsStart() { 33 | log.Warning("", "警告,http服务已启动") 34 | return 35 | } 36 | 37 | //注册路由 38 | g.HttpRouteRegister("/server_list", httpHandleServerList) 39 | g.HttpRouteRegister("/server_control", httpHandleServerControl) 40 | g.HttpRouteRegister("/server_start", httpHandleServerStart) 41 | } 42 | 43 | func verify(rsp http.ResponseWriter, req *http.Request) bool { 44 | n.SetupCORS(&rsp) 45 | if req.Method == "OPTIONS" { 46 | return false 47 | } 48 | 49 | req.ParseForm() 50 | return true 51 | } 52 | 53 | //获取服务器列表 54 | func httpHandleServerList(rsp http.ResponseWriter, req *http.Request) { 55 | defer errorhelper.Recover() 56 | 57 | if !verify(rsp, req) { 58 | return 59 | } 60 | log.Debug("", "收到列表请求") 61 | responseServerList(rsp) 62 | } 63 | 64 | // 65 | func httpHandleServerControl(rsp http.ResponseWriter, req *http.Request) { 66 | defer errorhelper.Recover() 67 | 68 | if !verify(rsp, req) { 69 | return 70 | } 71 | 72 | type serverItem struct { 73 | AppType uint32 `json:"appType"` 74 | AppId uint32 `json:"appId"` 75 | } 76 | 77 | params := &struct { 78 | CtlId int32 `json:"ctlId"` 79 | Token string `json:"token"` 80 | ServerList []serverItem `json:"serverList"` 81 | }{} 82 | 83 | if err := json.NewDecoder(req.Body).Decode(params); err != nil { 84 | n.HttpFail(rsp, CODE_PARAMERROR, err, "") 85 | return 86 | } 87 | 88 | //TODO:验证token 89 | 90 | log.Debug("", "控制命令,params=%v", params) 91 | errInfo := "" 92 | for _, item := range params.ServerList { 93 | if err := controlServer(item.AppType, item.AppId, params.CtlId); err != nil { 94 | errInfo += err.Error() 95 | } 96 | } 97 | 98 | if errInfo != "" { 99 | n.HttpFail(rsp, CODE_PARAMERROR, nil, errInfo) 100 | } else { 101 | n.HttpSuccess(rsp, nil, "执行成功") 102 | } 103 | } 104 | 105 | //开启服务 106 | func httpHandleServerStart(rsp http.ResponseWriter, req *http.Request) { 107 | defer errorhelper.Recover() 108 | 109 | if !verify(rsp, req) { 110 | return 111 | } 112 | 113 | buff, _ := ioutil.ReadAll(req.Body) 114 | log.Debug("", "收到启动请求,buff=%v", string(buff)) 115 | } 116 | 117 | //获取服务器列表 118 | func responseServerList(rsp http.ResponseWriter) { 119 | type ServerList struct { 120 | Id int `json:"id" ` 121 | AppName string `json:"appName"` 122 | AppType uint32 `json:"appType"` 123 | AppId uint32 `json:"appId"` 124 | AppState int `json:"appState" ` 125 | Address string `json:"address" ` 126 | Description string `json:"description"` 127 | } 128 | 129 | idKey := make([]float64, 0) 130 | for k, _ := range appRegData { 131 | idKey = append(idKey, float64(k)) 132 | } 133 | sort.Float64s(idKey) 134 | sList := make([]ServerList, 0) 135 | for _, k := range idKey { 136 | v := appRegData[uint64(k)] 137 | if v.appInfo.Type == n.AppDaemon { 138 | continue 139 | } 140 | l := ServerList{ 141 | Id: len(sList) + 1, 142 | AppName: v.appInfo.Name, 143 | AppType: v.appInfo.Type, 144 | AppId: v.appInfo.Id, 145 | Address: v.appInfo.ListenOnAddr, 146 | } 147 | l.AppState = v.appState 148 | l.Description = v.stateDescription 149 | if s := getBaseInfoFromConfigList(v.appInfo.Type, v.appInfo.Id); s != nil { 150 | l.AppName = s.Alias 151 | } 152 | sList = append(sList, l) 153 | } 154 | 155 | //添加配置但未启动注册服务 156 | cs := getConfigServerList() 157 | for _, s := range cs { 158 | if s.Type == n.AppDaemon { 159 | continue 160 | } 161 | key := util.MakeUint64FromUint32(s.Type, s.Id) 162 | if _, ok := appRegData[key]; !ok { 163 | l := ServerList{ 164 | Id: len(sList) + 1, 165 | AppName: s.Alias, 166 | AppType: s.Type, 167 | AppId: s.Id, 168 | Address: s.ListenOnAddr, 169 | } 170 | sList = append(sList, l) 171 | } 172 | } 173 | 174 | r := &struct { 175 | List []ServerList `json:"list"` 176 | Count int `json:"count"` 177 | }{ 178 | List: sList, Count: len(sList), 179 | } 180 | 181 | n.HttpSuccess(rsp, r, "成功了啊") 182 | } 183 | -------------------------------------------------------------------------------- /pkg/database/sqlhelper/dataresult.go: -------------------------------------------------------------------------------- 1 | package sqlhelper 2 | 3 | import ( 4 | "mango/pkg/util/timehelper" 5 | "strconv" 6 | "time" 7 | ) 8 | 9 | func ValueToInt(val interface{}, base int, bitSize int) int64 { 10 | if val == nil { 11 | return 0 12 | } 13 | switch val.(type) { 14 | case []byte: 15 | str := ByteToString(val.([]byte)) 16 | u, _ := strconv.ParseInt(str, base, bitSize) 17 | return u 18 | default: 19 | return val.(int64) 20 | } 21 | } 22 | 23 | func ValueToUint(val interface{}, base int, bitSize int) uint64 { 24 | if val == nil { 25 | return 0 26 | } 27 | switch val.(type) { 28 | case []byte: 29 | str := ByteToString(val.([]byte)) 30 | u, _ := strconv.ParseUint(str, base, bitSize) 31 | return u 32 | default: 33 | return uint64(val.(int64)) 34 | } 35 | } 36 | 37 | func ValueToFloat(val interface{}, bitSize int) float64 { 38 | if val == nil { 39 | return 0 40 | } 41 | switch val.(type) { 42 | case []byte: 43 | f, _ := strconv.ParseFloat(ByteToString(val.([]byte)), bitSize) 44 | return f 45 | case int64: 46 | return float64(val.(int64)) 47 | default: 48 | return val.(float64) 49 | } 50 | } 51 | 52 | type DataResult struct { 53 | RowCount int 54 | Rows []interface{} 55 | } 56 | 57 | func (result *DataResult) GetRow(row int) []interface{} { 58 | return result.Rows[row].([]interface{}) 59 | } 60 | 61 | func (result *DataResult) GetValue(row int, column int) interface{} { 62 | resultRow := result.GetRow(row) 63 | value := resultRow[column].(*interface{}) 64 | return *value 65 | } 66 | 67 | func (result *DataResult) GetIntValue(row int, column int) int { 68 | val := result.GetValue(row, column) 69 | return int(ValueToInt(val, 10, 64)) 70 | } 71 | 72 | func (result *DataResult) GetInt32Value(row int, column int) int32 { 73 | val := result.GetValue(row, column) 74 | return int32(ValueToInt(val, 10, 32)) 75 | } 76 | 77 | func (result *DataResult) GetInt64Value(row int, column int) int64 { 78 | val := result.GetValue(row, column) 79 | return int64(ValueToInt(val, 10, 64)) 80 | } 81 | 82 | func (result *DataResult) GetUIntValue(row int, column int) uint { 83 | val := result.GetValue(row, column) 84 | 85 | return uint(ValueToUint(val, 10, 64)) 86 | } 87 | 88 | func (result *DataResult) GetUInt64Value(row int, column int) uint64 { 89 | val := result.GetValue(row, column) 90 | return ValueToUint(val, 10, 64) 91 | } 92 | 93 | func (result *DataResult) GetUInt32Value(row int, column int) uint32 { 94 | val := result.GetValue(row, column) 95 | 96 | return uint32(ValueToUint(val, 10, 32)) 97 | } 98 | 99 | func (result *DataResult) GetUInt8Value(row int, column int) uint8 { 100 | val := result.GetValue(row, column) 101 | 102 | return uint8(ValueToUint(val, 10, 8)) 103 | } 104 | 105 | func (result *DataResult) GetBoolValue(row int, column int) bool { 106 | val := result.GetValue(row, column) 107 | if val == nil { 108 | return false 109 | } 110 | switch val.(type) { 111 | case bool: 112 | return val.(bool) 113 | default: 114 | val = val.(int64) 115 | if val == int64(1) { 116 | return true 117 | } 118 | } 119 | return false 120 | } 121 | 122 | func (result *DataResult) GetFloat32Value(row int, column int) float32 { 123 | val := result.GetValue(row, column) 124 | return float32(ValueToFloat(val, 32)) 125 | } 126 | 127 | func (result *DataResult) GetFloat64Value(row int, column int) float64 { 128 | val := result.GetValue(row, column) 129 | return ValueToFloat(val, 64) 130 | } 131 | 132 | func (result *DataResult) GetStringValue(row int, column int) string { 133 | val := result.GetValue(row, column) 134 | if val == nil { 135 | return "" 136 | } 137 | switch val.(type) { 138 | case []byte: 139 | return ByteToString(val.([]byte)) 140 | default: 141 | return val.(string) 142 | } 143 | } 144 | 145 | func (result *DataResult) GetByteArrayValue(row int, column int) []byte { 146 | val := result.GetValue(row, column) 147 | if val == nil { 148 | return nil 149 | } 150 | 151 | switch val.(type) { 152 | case []byte: 153 | return val.([]byte) 154 | default: 155 | return []byte(val.(string)) 156 | } 157 | } 158 | 159 | func (result *DataResult) GetTimeValue(row int, column int) *time.Time { 160 | val := result.GetValue(row, column) 161 | if val == nil { 162 | return nil 163 | } 164 | switch val.(type) { 165 | case []byte: 166 | timeStr := ByteToString(val.([]byte)) 167 | t, _ := time.ParseInLocation(timehelper.Default, timeStr, time.Local) //string转time 168 | return &t 169 | default: 170 | return val.(*time.Time) 171 | } 172 | } 173 | 174 | func ByteToString(bs []byte) string { 175 | return string(bs) 176 | } 177 | -------------------------------------------------------------------------------- /pkg/util/goaes/aes.go: -------------------------------------------------------------------------------- 1 | package goaes 2 | 3 | import ( 4 | "bytes" 5 | "crypto/aes" 6 | "crypto/cipher" 7 | "crypto/rand" 8 | "encoding/base64" 9 | "io" 10 | 11 | "mango/pkg/log" 12 | ) 13 | 14 | type AESMODE int 15 | 16 | const ( 17 | ECB AESMODE = iota 18 | CBC 19 | CFB 20 | OFB 21 | CTR 22 | GCM 23 | ) 24 | 25 | // 使用PKCS7进行填充 26 | func PKCS7Padding(ciphertext []byte, blockSize int) []byte { 27 | padding := blockSize - len(ciphertext)%blockSize 28 | padtext := bytes.Repeat([]byte{byte(padding)}, padding) 29 | return append(ciphertext, padtext...) 30 | } 31 | func PKCS7UnPadding(ciphertext []byte) []byte { 32 | length := len(ciphertext) 33 | //去掉最后一次的padding 34 | unpadding := int(ciphertext[length-1]) 35 | return ciphertext[:(length - unpadding)] 36 | } 37 | 38 | func ZeroPadding(ciphertext []byte, blockSize int) []byte { 39 | padding := blockSize - len(ciphertext)%blockSize 40 | padtext := bytes.Repeat([]byte{0}, padding) 41 | return append(ciphertext, padtext...) 42 | } 43 | 44 | func ZeroUnPadding(origData []byte) []byte { 45 | return bytes.TrimRightFunc(origData, func(r rune) bool { 46 | return r == rune(0) 47 | }) 48 | } 49 | 50 | func AesCBCEncrypt(plaintext, key, iv []byte) (ciphertext []byte, err error) { 51 | block, err := aes.NewCipher(key) 52 | if err != nil { 53 | log.Debug("", "错误 - %v", err.Error()) 54 | return nil, err 55 | } 56 | //填充内容,如果不足16位字符 57 | blockSize := block.BlockSize() 58 | originData := PKCS7Padding(plaintext, blockSize) 59 | //加密方式 60 | blockMode := cipher.NewCBCEncrypter(block, iv) 61 | //加密,输出到[]byte数组 62 | crypted := make([]byte, len(originData)) 63 | blockMode.CryptBlocks(crypted, originData) 64 | return crypted, nil 65 | } 66 | 67 | func AesCBCDecrypt(ciphertext, key, iv []byte) (plaintext []byte, err error) { 68 | //生成密码数据块cipher.Block 69 | block, _ := aes.NewCipher(key) 70 | //解密模式 71 | blockMode := cipher.NewCBCDecrypter(block, iv) 72 | //输出到[]byte数组 73 | originData := make([]byte, len(ciphertext)) 74 | blockMode.CryptBlocks(originData, ciphertext) 75 | //去除填充,并返回 76 | return PKCS7UnPadding(originData), nil 77 | } 78 | 79 | func AesCFBEncrypt(plaintext, key, iv []byte) (ciphertext []byte, err error) { 80 | block, err := aes.NewCipher(key) 81 | if err != nil { 82 | panic(err) 83 | } 84 | ciphertext = make([]byte, aes.BlockSize+len(plaintext)) 85 | if iv == nil { 86 | iv = ciphertext[:aes.BlockSize] 87 | } 88 | if _, err := io.ReadFull(rand.Reader, iv); err != nil { 89 | panic(err) 90 | } 91 | stream := cipher.NewCFBEncrypter(block, iv) 92 | stream.XORKeyStream(ciphertext[aes.BlockSize:], plaintext) 93 | return ciphertext, nil 94 | } 95 | 96 | func AesCFBDecrypt(ciphertext, key, iv []byte) (plaintext []byte, err error) { 97 | block, _ := aes.NewCipher(key) 98 | if len(ciphertext) < aes.BlockSize { 99 | panic("ciphertext too short") 100 | } 101 | if iv == nil { 102 | iv = ciphertext[:aes.BlockSize] 103 | } 104 | ciphertext = ciphertext[aes.BlockSize:] 105 | 106 | stream := cipher.NewCFBDecrypter(block, iv) 107 | stream.XORKeyStream(ciphertext, ciphertext) 108 | return ciphertext, nil 109 | } 110 | 111 | func Encrypt(rawData, key, iv []byte, mode AESMODE, base64Code bool) ([]byte, error) { 112 | var encrypted []byte 113 | switch mode { 114 | case CBC: 115 | encrypted, _ = AesCBCEncrypt(rawData, key, iv) 116 | case CFB: 117 | encrypted, _ = AesCFBEncrypt(rawData, key, iv) 118 | default: 119 | return nil, nil 120 | } 121 | if base64Code { 122 | encrypted = []byte(base64.StdEncoding.EncodeToString(encrypted)) 123 | } 124 | return encrypted, nil 125 | } 126 | 127 | func Decrypt(encrypted, key, iv []byte, mode AESMODE, base64Code bool) ([]byte, error) { 128 | if base64Code { 129 | encrypted, _ = base64.StdEncoding.DecodeString(string(encrypted)) 130 | } 131 | switch mode { 132 | case CBC: 133 | return AesCBCDecrypt(encrypted, key, iv) 134 | case CFB: 135 | return AesCFBDecrypt(encrypted, key, iv) 136 | default: 137 | return nil, nil 138 | } 139 | } 140 | 141 | func EcbDecrypt(data, key []byte) []byte { 142 | block, _ := aes.NewCipher(key) 143 | decrypted := make([]byte, len(data)) 144 | size := block.BlockSize() 145 | 146 | for bs, be := 0, size; bs < len(data); bs, be = bs+size, be+size { 147 | block.Decrypt(decrypted[bs:be], data[bs:be]) 148 | } 149 | 150 | return PKCS7UnPadding(decrypted) 151 | } 152 | 153 | func EcbEncrypt(data, key []byte) []byte { 154 | block, _ := aes.NewCipher(key) 155 | data = PKCS7Padding(data, block.BlockSize()) 156 | decrypted := make([]byte, len(data)) 157 | size := block.BlockSize() 158 | 159 | for bs, be := 0, size; bs < len(data); bs, be = bs+size, be+size { 160 | block.Encrypt(decrypted[bs:be], data[bs:be]) 161 | } 162 | 163 | return decrypted 164 | } 165 | -------------------------------------------------------------------------------- /configs/config/center.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": { 3 | "LogScreenPrint": "1", 4 | "title": "中心服务", 5 | "日志服务器地址": "127.0.0.1:10001", 6 | "http监听地址": "127.0.0.1:10052", 7 | "服务列表": [ 8 | { 9 | "DaemonId": 300, 10 | "Alias": "配置中心", 11 | "Name": "config", 12 | "Type": 3, 13 | "Id": 55, 14 | "ListenOnAddr": "127.0.0.1:10055", 15 | "CenterAddr": "127.0.0.1:10050" 16 | }, 17 | { 18 | "DaemonId": 300, 19 | "Alias": "网关0", 20 | "Name": "gateway", 21 | "Type": 4, 22 | "Id": 100, 23 | "ListenOnAddr": "127.0.0.1:10100", 24 | "CenterAddr": "127.0.0.1:10050" 25 | }, 26 | { 27 | "DaemonId": 300, 28 | "Alias": "网关1", 29 | "Name": "gateway", 30 | "Type": 4, 31 | "Id": 110, 32 | "ListenOnAddr": "127.0.0.1:10110", 33 | "CenterAddr": "127.0.0.1:10050" 34 | }, 35 | { 36 | "DaemonId": 300, 37 | "Alias": "大厅服", 38 | "Name": "lobby", 39 | "Type": 5, 40 | "Id": 60, 41 | "ListenOnAddr": "127.0.0.1:10060", 42 | "CenterAddr": "127.0.0.1:10050" 43 | }, 44 | { 45 | "DaemonId": 300, 46 | "Alias": "列表服", 47 | "Name": "list", 48 | "Type": 6, 49 | "Id": 65, 50 | "ListenOnAddr": "127.0.0.1:10060", 51 | "CenterAddr": "127.0.0.1:10050" 52 | }, 53 | { 54 | "DaemonId": 300, 55 | "Alias": "桌子服0", 56 | "Name": "table", 57 | "Type": 11, 58 | "Id": 1000, 59 | "ListenOnAddr": "127.0.0.1:11000", 60 | "CenterAddr": "127.0.0.1:10050" 61 | }, 62 | { 63 | "DaemonId": 300, 64 | "Alias": "房间服0", 65 | "Name": "room", 66 | "Type": 12, 67 | "Id": 2000, 68 | "ListenOnAddr": "127.0.0.1:12000", 69 | "CenterAddr": "127.0.0.1:10050" 70 | }, 71 | { 72 | "DaemonId": 300, 73 | "Alias": "机器人0", 74 | "Name": "robot", 75 | "Type": 9, 76 | "Id": 3000, 77 | "ListenOnAddr": "127.0.0.1:13000", 78 | "CenterAddr": "127.0.0.1:10050" 79 | }, 80 | { 81 | "DaemonId": 300, 82 | "Alias": "桌子服1", 83 | "Name": "table", 84 | "Type": 11, 85 | "Id": 1010, 86 | "ListenOnAddr": "127.0.0.1:11010", 87 | "CenterAddr": "127.0.0.1:10050" 88 | }, 89 | { 90 | "DaemonId": 300, 91 | "Alias": "房间服1", 92 | "Name": "room", 93 | "Type": 12, 94 | "Id": 2010, 95 | "ListenOnAddr": "127.0.0.1:12010", 96 | "CenterAddr": "127.0.0.1:10050" 97 | }, 98 | { 99 | "DaemonId": 300, 100 | "Alias": "机器人1", 101 | "Name": "robot", 102 | "Type": 9, 103 | "Id": 3010, 104 | "ListenOnAddr": "127.0.0.1:13010", 105 | "CenterAddr": "127.0.0.1:10050" 106 | }, 107 | { 108 | "DaemonId": 300, 109 | "Alias": "桌子服2", 110 | "Name": "table", 111 | "Type": 11, 112 | "Id": 1020, 113 | "ListenOnAddr": "127.0.0.1:11020", 114 | "CenterAddr": "127.0.0.1:10050" 115 | }, 116 | { 117 | "DaemonId": 300, 118 | "Alias": "房间服2", 119 | "Name": "room", 120 | "Type": 12, 121 | "Id": 2020, 122 | "ListenOnAddr": "127.0.0.1:12020", 123 | "CenterAddr": "127.0.0.1:10050" 124 | }, 125 | { 126 | "DaemonId": 300, 127 | "Alias": "机器人2", 128 | "Name": "robot", 129 | "Type": 9, 130 | "Id": 3020, 131 | "ListenOnAddr": "127.0.0.1:13020", 132 | "CenterAddr": "127.0.0.1:10050" 133 | } 134 | ] 135 | } 136 | } -------------------------------------------------------------------------------- /pkg/network/tcp_server.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | import ( 4 | "mango/pkg/log" 5 | "mango/pkg/util" 6 | "net" 7 | "runtime" 8 | "sync" 9 | "time" 10 | ) 11 | 12 | type TCPServer struct { 13 | Addr string 14 | MaxConnNum int 15 | PendingWriteNum int 16 | NewAgent func(*TCPConn) AgentClient 17 | GetConfig func(key string, defaultValue int64) int64 18 | Ln net.Listener 19 | conns ConnSet 20 | mutexConns sync.Mutex 21 | wgLn sync.WaitGroup 22 | wgConns sync.WaitGroup 23 | memOverLimit bool 24 | rwMemLimit sync.RWMutex 25 | 26 | // msg parser 27 | MinMsgLen uint32 28 | MaxMsgLen uint32 29 | msgParser *MsgParser 30 | } 31 | 32 | func (server *TCPServer) Start() { 33 | server.init() 34 | go server.run() 35 | } 36 | 37 | func (server *TCPServer) init() { 38 | ln, err := net.Listen("tcp", server.Addr) 39 | if err != nil { 40 | log.Fatal("tcpserver", "%v", err) 41 | } 42 | 43 | if server.MaxConnNum <= 0 { 44 | server.MaxConnNum = 10000 45 | } 46 | if server.PendingWriteNum <= 0 { 47 | server.PendingWriteNum = 10000 48 | } 49 | if server.NewAgent == nil { 50 | log.Fatal("tcpserver", "NewAgent or GetConfig must not be nil") 51 | } 52 | 53 | server.Ln = ln 54 | server.conns = make(ConnSet) 55 | server.memOverLimit = false 56 | 57 | // msg parser 58 | msgParser := NewMsgParser() 59 | msgParser.SetMsgLen(server.MinMsgLen, server.MaxMsgLen) 60 | server.msgParser = msgParser 61 | } 62 | 63 | func (server *TCPServer) run() { 64 | server.wgLn.Add(1) 65 | defer server.wgLn.Done() 66 | 67 | var tempDelay time.Duration 68 | for { 69 | conn, err := server.Ln.Accept() 70 | if err != nil { 71 | if ne, ok := err.(net.Error); ok && ne.Temporary() { 72 | if tempDelay == 0 { 73 | tempDelay = 5 * time.Millisecond 74 | } else { 75 | tempDelay *= 2 76 | } 77 | if max := 1 * time.Second; tempDelay > max { 78 | tempDelay = max 79 | } 80 | log.Error("TCPServer", "accept error: %v; retrying in %v", err, tempDelay) 81 | time.Sleep(tempDelay) 82 | continue 83 | } 84 | return 85 | } 86 | tempDelay = 0 87 | 88 | server.mutexConns.Lock() 89 | alloc, curMemory := server.checkAllocMemory() 90 | if len(server.conns) >= server.MaxConnNum || !alloc { 91 | server.mutexConns.Unlock() 92 | conn.Close() 93 | log.Warning("TCPServer", "超出连接上限,MaxConnNum=%d,alloc=%v,curMemory=%d", server.MaxConnNum, alloc, curMemory) 94 | continue 95 | } 96 | server.conns[conn] = struct{}{} 97 | server.mutexConns.Unlock() 98 | 99 | server.wgConns.Add(1) 100 | 101 | tcpConn := newTCPConn(conn, server.PendingWriteNum, server.msgParser) 102 | agent := server.NewAgent(tcpConn) 103 | go func() { 104 | agent.Run() 105 | // cleanup 106 | tcpConn.Close() 107 | server.mutexConns.Lock() 108 | delete(server.conns, conn) 109 | server.mutexConns.Unlock() 110 | agent.OnClose() 111 | 112 | server.wgConns.Done() 113 | }() 114 | } 115 | } 116 | 117 | func (server *TCPServer) checkAllocMemory() (bool, int64) { 118 | if server.GetConfig == nil { 119 | return true, 0 120 | } 121 | maxMemory := server.GetConfig("内存限制", 0) 122 | checkCount := server.GetConfig("开始监控连接数量", 5000) 123 | if maxMemory <= 0 || len(server.conns) < int(checkCount) { 124 | return true, 0 125 | } 126 | 127 | server.rwMemLimit.RLock() 128 | if server.memOverLimit { 129 | server.rwMemLimit.RUnlock() 130 | return false, util.CurMemory() 131 | } 132 | server.rwMemLimit.RUnlock() 133 | 134 | watchInterval := server.GetConfig("监控间隔", 1000) 135 | if (len(server.conns) % int(watchInterval)) == 0 { 136 | if util.CurMemory() > maxMemory { 137 | server.rwMemLimit.Lock() 138 | server.memOverLimit = true 139 | server.rwMemLimit.Unlock() 140 | 141 | timeInterval := 1 * time.Second 142 | timer := time.NewTimer(timeInterval) 143 | go func(t *time.Timer) { 144 | for { 145 | <-t.C 146 | log.Warning("TCPServer", "超标,开始GC,mem=%v", util.CurMemory()) 147 | runtime.GC() 148 | if util.CurMemory() < (maxMemory * 9 / 10) { 149 | server.rwMemLimit.Lock() 150 | server.memOverLimit = false 151 | server.rwMemLimit.Unlock() 152 | timer.Stop() 153 | log.Warning("TCPServer", "恢复,当前,mem=%v", util.CurMemory()) 154 | break 155 | } 156 | t.Reset(timeInterval) 157 | } 158 | }(timer) 159 | 160 | return false, util.CurMemory() 161 | } 162 | } 163 | return true, 0 164 | } 165 | 166 | func (server *TCPServer) Close() { 167 | server.Ln.Close() 168 | server.wgLn.Wait() 169 | 170 | server.mutexConns.Lock() 171 | for conn := range server.conns { 172 | conn.Close() 173 | } 174 | server.conns = nil 175 | server.mutexConns.Unlock() 176 | server.wgConns.Wait() 177 | } 178 | -------------------------------------------------------------------------------- /pkg/console/command.go: -------------------------------------------------------------------------------- 1 | package console 2 | 3 | import ( 4 | "fmt" 5 | "mango/pkg/chanrpc" 6 | "mango/pkg/log" 7 | "os" 8 | "path" 9 | "runtime/pprof" 10 | "time" 11 | ) 12 | 13 | var commands = []Command{ 14 | new(CommandHelp), 15 | new(CommandCPUProf), 16 | new(CommandProf), 17 | } 18 | 19 | type Command interface { 20 | // must goroutine safe 21 | name() string 22 | // must goroutine safe 23 | help() string 24 | // must goroutine safe 25 | run(args []string) string 26 | } 27 | 28 | type ExternalCommand struct { 29 | _name string 30 | _help string 31 | server *chanrpc.Server 32 | } 33 | 34 | func (c *ExternalCommand) name() string { 35 | return c._name 36 | } 37 | 38 | func (c *ExternalCommand) help() string { 39 | return c._help 40 | } 41 | 42 | func (c *ExternalCommand) run(_args []string) string { 43 | args := make([]interface{}, len(_args)) 44 | for i, v := range _args { 45 | args[i] = v 46 | } 47 | 48 | ret, err := c.server.Call1(c._name, args...) 49 | if err != nil { 50 | return err.Error() 51 | } 52 | output, ok := ret.(string) 53 | if !ok { 54 | return "invalid output type" 55 | } 56 | 57 | return output 58 | } 59 | 60 | // you must call the function before calling console.Init 61 | // goroutine not safe 62 | func Register(name string, help string, f interface{}, server *chanrpc.Server) { 63 | for _, c := range commands { 64 | if c.name() == name { 65 | log.Fatal("", "command %v is already registered", name) 66 | return 67 | } 68 | } 69 | 70 | server.Register(name, f) 71 | 72 | c := new(ExternalCommand) 73 | c._name = name 74 | c._help = help 75 | c.server = server 76 | commands = append(commands, c) 77 | } 78 | 79 | // help 80 | type CommandHelp struct{} 81 | 82 | func (c *CommandHelp) name() string { 83 | return "help" 84 | } 85 | 86 | func (c *CommandHelp) help() string { 87 | return "this help text" 88 | } 89 | 90 | func (c *CommandHelp) run([]string) string { 91 | output := "Commands:\r\n" 92 | for _, c := range commands { 93 | output += c.name() + " - " + c.help() + "\r\n" 94 | } 95 | output += "quit - exit console" 96 | 97 | return output 98 | } 99 | 100 | // cpuprof 101 | type CommandCPUProf struct{} 102 | 103 | func (c *CommandCPUProf) name() string { 104 | return "cpuprof" 105 | } 106 | 107 | func (c *CommandCPUProf) help() string { 108 | return "CPU profiling for the current process" 109 | } 110 | 111 | func (c *CommandCPUProf) usage() string { 112 | return "cpuprof writes runtime profiling data in the format expected by \r\n" + 113 | "the pprof visualization tool\r\n\r\n" + 114 | "Usage: cpuprof start|stop\r\n" + 115 | " start - enables CPU profiling\r\n" + 116 | " stop - stops the current CPU profile" 117 | } 118 | 119 | func (c *CommandCPUProf) run(args []string) string { 120 | if len(args) == 0 { 121 | return c.usage() 122 | } 123 | 124 | switch args[0] { 125 | case "start": 126 | fn := profileName() + ".cpuprof" 127 | f, err := os.Create(fn) 128 | if err != nil { 129 | return err.Error() 130 | } 131 | err = pprof.StartCPUProfile(f) 132 | if err != nil { 133 | f.Close() 134 | return err.Error() 135 | } 136 | return fn 137 | case "stop": 138 | pprof.StopCPUProfile() 139 | return "" 140 | default: 141 | return c.usage() 142 | } 143 | } 144 | 145 | func profileName() string { 146 | now := time.Now() 147 | ProfilePath := "" 148 | return path.Join(ProfilePath, 149 | fmt.Sprintf("%d%02d%02d_%02d_%02d_%02d", 150 | now.Year(), 151 | now.Month(), 152 | now.Day(), 153 | now.Hour(), 154 | now.Minute(), 155 | now.Second())) 156 | } 157 | 158 | // prof 159 | type CommandProf struct{} 160 | 161 | func (c *CommandProf) name() string { 162 | return "prof" 163 | } 164 | 165 | func (c *CommandProf) help() string { 166 | return "writes a pprof-formatted snapshot" 167 | } 168 | 169 | func (c *CommandProf) usage() string { 170 | return "prof writes runtime profiling data in the format expected by \r\n" + 171 | "the pprof visualization tool\r\n\r\n" + 172 | "Usage: prof goroutine|heap|thread|block\r\n" + 173 | " goroutine - stack traces of all current goroutines\r\n" + 174 | " heap - a sampling of all heap allocations\r\n" + 175 | " thread - stack traces that led to the creation of new OS threads\r\n" + 176 | " block - stack traces that led to blocking on synchronization primitives" 177 | } 178 | 179 | func (c *CommandProf) run(args []string) string { 180 | if len(args) == 0 { 181 | return c.usage() 182 | } 183 | 184 | var ( 185 | p *pprof.Profile 186 | fn string 187 | ) 188 | switch args[0] { 189 | case "goroutine": 190 | p = pprof.Lookup("goroutine") 191 | fn = profileName() + ".gprof" 192 | case "heap": 193 | p = pprof.Lookup("heap") 194 | fn = profileName() + ".hprof" 195 | case "thread": 196 | p = pprof.Lookup("threadcreate") 197 | fn = profileName() + ".tprof" 198 | case "block": 199 | p = pprof.Lookup("block") 200 | fn = profileName() + ".bprof" 201 | default: 202 | return c.usage() 203 | } 204 | 205 | f, err := os.Create(fn) 206 | if err != nil { 207 | return err.Error() 208 | } 209 | defer f.Close() 210 | err = p.WriteTo(f, 0) 211 | if err != nil { 212 | return err.Error() 213 | } 214 | 215 | return fn 216 | } 217 | -------------------------------------------------------------------------------- /cmd/logger/business/business.go: -------------------------------------------------------------------------------- 1 | package business 2 | 3 | import ( 4 | "fmt" 5 | "mango/api/logger" 6 | g "mango/pkg/gate" 7 | "mango/pkg/log" 8 | n "mango/pkg/network" 9 | "mango/pkg/util" 10 | "math/rand" 11 | "os" 12 | "path" 13 | "time" 14 | ) 15 | 16 | var ( 17 | appConnData = make(map[n.AgentClient]*connectionData) 18 | ) 19 | 20 | const ( 21 | connected int = 1 22 | registered int = 2 23 | ) 24 | 25 | //连接数据 26 | type appRegInfo struct { 27 | appType uint32 28 | appId uint32 29 | regToken string 30 | appName string 31 | curStep int 32 | } 33 | 34 | type connectionData struct { 35 | a n.AgentClient 36 | regInfo appRegInfo 37 | lastHeartbeat int64 38 | baseFile *os.File 39 | pathname string 40 | } 41 | 42 | func init() { 43 | g.MsgRegister(&logger.LogReq{}, n.AppLogger, uint16(logger.CMDLogger_IDLogReq), handleLogReq) 44 | g.MsgRegister(&logger.LogFlush{}, n.AppLogger, uint16(logger.CMDLogger_IDLogFlush), handleLogFlush) 45 | g.EventRegister(g.ConnectSuccess, connectSuccess) 46 | g.EventRegister(g.Disconnect, disconnect) 47 | } 48 | 49 | func connectSuccess(args []interface{}) { 50 | log.Info("连接", "来了老弟,当前连接数=%d", len(appConnData)) 51 | a := args[g.AgentIndex].(n.AgentClient) 52 | if v, ok := appConnData[a]; ok { 53 | log.Error("连接", "异常,重复连接?,%d,%d", v.regInfo.appType, v.regInfo.appId) 54 | a.Close() 55 | return 56 | } 57 | appConnData[a] = &connectionData{a: a, regInfo: appRegInfo{curStep: connected}} 58 | } 59 | 60 | func disconnect(args []interface{}) { 61 | log.Info("连接", "告辞中,当前连接数=%d", len(appConnData)) 62 | a := args[g.AgentIndex].(n.AgentClient) 63 | if v, ok := appConnData[a]; ok { 64 | regKey := makeRegKey(v.regInfo.appType, v.regInfo.appId) 65 | log.Info("连接", "再见,appName=%v,appType=%d,appId=%d,regKey=%d", 66 | v.regInfo.appName, v.regInfo.appType, v.regInfo.appId, regKey) 67 | 68 | //关闭文件 69 | if v.baseFile != nil { 70 | v.baseFile.Close() 71 | } 72 | delete(appConnData, a) 73 | } else { 74 | log.Error("连接", "异常,没有注册的连接?") 75 | } 76 | } 77 | 78 | func handleLogReq(args []interface{}) { 79 | b := args[n.DataIndex].(n.BaseMessage) 80 | m := (b.MyMessage).(*logger.LogReq) 81 | //m := args[n.DataIndex].(*logger.LogReq) 82 | a := args[n.AgentIndex].(n.AgentClient) 83 | 84 | //连接存在判断 85 | if _, ok := appConnData[a]; !ok { 86 | log.Error("连接", "异常,没有注册的连接?") 87 | a.Close() 88 | return 89 | } 90 | 91 | log.Debug("写日志", "收到,Level=%v,Appname=%v,Content=%s", m.GetLogLevel(), m.GetSrcAppname(), string(m.GetContent())) 92 | 93 | if appConnData[a].baseFile == nil { 94 | pathname := "" 95 | curPath, err := util.GetCurrentPath() 96 | if err == nil { 97 | pathname = curPath + "log/" + m.GetSrcAppname() + "/" 98 | _, err := os.Stat(pathname) 99 | if err != nil && os.IsNotExist(err) { 100 | err = os.MkdirAll(pathname, os.ModePerm) 101 | if err != nil { 102 | pathname = "" 103 | } 104 | } 105 | } 106 | if pathname == "" { 107 | return 108 | } 109 | 110 | file, err := createNewLogFile(pathname, m.GetSrcAppname(), m.GetSrcApptype(), m.GetSrcAppid()) 111 | if err != nil { 112 | return 113 | } 114 | 115 | appConnData[a].baseFile = file 116 | appConnData[a].pathname = pathname 117 | 118 | token := fmt.Sprintf("gb%x%x%x", rand.Int(), time.Now().UnixNano(), rand.Int()) 119 | appConnData[a].regInfo = appRegInfo{m.GetSrcApptype(), m.GetSrcAppid(), token, m.GetSrcAppname(), registered} 120 | 121 | } else { 122 | //60M分割文件 1024*1024*60 123 | fi, err := appConnData[a].baseFile.Stat() 124 | if err == nil && fi.Size() >= 1024*1024*60 { 125 | file, err := createNewLogFile(appConnData[a].pathname, m.GetSrcAppname(), m.GetSrcApptype(), m.GetSrcAppid()) 126 | if err == nil { 127 | appConnData[a].baseFile.Close() 128 | appConnData[a].baseFile = file 129 | } 130 | } 131 | } 132 | 133 | //再次判断 134 | if appConnData[a].baseFile == nil { 135 | return 136 | } 137 | 138 | //构造内容 139 | now := time.Now() 140 | timeStr := fmt.Sprintf("[local:%v-%02d-%02d %02d:%02d:%02d.%09d]", 141 | now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second(), now.Nanosecond()) 142 | now = time.Unix(0, m.GetTimeNs()) 143 | timeStr = timeStr + fmt.Sprintf("[remote:%v-%02d-%02d %02d:%02d:%02d.%09d]\t", 144 | now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second(), now.Nanosecond()) 145 | className := fmt.Sprintf("[%s]", string(m.GetClassName())) 146 | fileInfo := fmt.Sprintf("\t[%s][lineNO:%d]", m.GetFileName(), m.GetLineNo()) 147 | logStr := timeStr + log.GetLogLevelStr(int(m.GetLogLevel())) + className + string(m.GetContent()) + fileInfo 148 | 149 | appConnData[a].baseFile.WriteString(logStr + "\n") 150 | } 151 | 152 | func handleLogFlush(args []interface{}) { 153 | 154 | } 155 | 156 | func makeRegKey(appType, appId uint32) uint64 { 157 | return uint64(appType)<<32 | uint64(appId) 158 | } 159 | 160 | func createNewLogFile(pathName, appName string, appType, appId uint32) (*os.File, error) { 161 | now := time.Now() 162 | filename := fmt.Sprintf("%s_%d_%d_%d%02d%02d_%02d_%02d_%02d.log", 163 | appName, 164 | appType, 165 | appId, 166 | now.Year(), 167 | now.Month(), 168 | now.Day(), 169 | now.Hour(), 170 | now.Minute(), 171 | now.Second()) 172 | file, err := os.Create(path.Join(pathName, filename)) 173 | if err != nil { 174 | return nil, err 175 | } 176 | return file, nil 177 | } 178 | -------------------------------------------------------------------------------- /pkg/network/tcp_msg.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "fmt" 7 | "mango/pkg/util/errorhelper" 8 | "io" 9 | ) 10 | 11 | const ( 12 | AppLogger uint32 = 1 13 | AppCenter uint32 = 2 14 | AppConfig uint32 = 3 15 | AppGate uint32 = 4 16 | AppLobby uint32 = 5 17 | AppProperty uint32 = 6 18 | AppBattle uint32 = 7 19 | AppLogic uint32 = 8 20 | AppRobot uint32 = 9 21 | AppList uint32 = 10 22 | AppTable uint32 = 11 23 | AppRoom uint32 = 12 24 | AppDaemon uint32 = 100 25 | 26 | Send2All uint32 = 1 27 | Send2AnyOne uint32 = 2 28 | 29 | DataIndex = 0 //数据 30 | AgentIndex = 1 //网络代理 31 | CMDIndex = 2 //TCPCommon 32 | OtherIndex = 3 //其他 33 | 34 | MinRouteArgsCount = 3 35 | 36 | // FlagOtherTraceId 消息头内other字段常量 37 | FlagOtherTraceId = 1 38 | TraceIdLen = 16 39 | 40 | msgSizeLen = 4 41 | headerSizeLen = 2 42 | msgHeaderLen = 6 43 | ) 44 | 45 | type ( 46 | //网络命令 47 | TCPCommand struct { 48 | AppType uint16 49 | CmdId uint16 50 | } 51 | 52 | MessageHeader struct { 53 | version uint8 54 | encrypt uint8 55 | TCPCommand //命令 56 | Other []byte // 0xFF字节以内 57 | } 58 | 59 | // BaseMessage 基础消息结构 60 | BaseMessage struct { 61 | MyMessage interface{} //消息体 62 | AgentInfo BaseAgentInfo 63 | Cmd TCPCommand //命令 64 | TraceId string //traceId 65 | } 66 | 67 | MsgParser struct { 68 | minMsgLen uint32 69 | maxMsgLen uint32 70 | } 71 | 72 | //丢失消息 73 | MissingMessage struct { 74 | DestAppType uint32 `bson:"destAppType"` 75 | DestAppId uint32 `bson:"destAppId"` 76 | AgentInfo BaseAgentInfo `bson:"agentInfo"` 77 | Cmd TCPCommand `bson:"cmd"` 78 | TraceId string `bson:"traceId"` 79 | Data []byte `bson:"data"` 80 | Time int64 `bson:"time"` 81 | } 82 | ) 83 | 84 | func NewMsgParser() *MsgParser { 85 | p := new(MsgParser) 86 | p.minMsgLen = headerSizeLen + msgHeaderLen 87 | p.maxMsgLen = 16 * 1024 88 | 89 | return p 90 | } 91 | 92 | // SetMsgLen It's dangerous to call the method on reading or writing 93 | func (p *MsgParser) SetMsgLen(minMsgLen uint32, maxMsgLen uint32) { 94 | if minMsgLen != 0 { 95 | p.minMsgLen = minMsgLen 96 | } 97 | if maxMsgLen != 0 { 98 | p.maxMsgLen = maxMsgLen 99 | } 100 | } 101 | 102 | // | msgSize | headSize | header | msgData 103 | // |4bit(msgSize)| 2bit(headSize) | 1bit(version) + 1bit(encrypt) + 2bit(AppType) + 2bit(CmdId) + Xbit(other) | msgData 104 | func (p *MsgParser) Read(conn *TCPConn) (BaseMessage, []byte, error) { 105 | defer errorhelper.Recover() 106 | msgSizeBuf := make([]byte, msgSizeLen) 107 | if _, err := io.ReadFull(conn, msgSizeBuf); err != nil { 108 | return BaseMessage{}, nil, fmt.Errorf("消息头读取失败,%v", err) 109 | } 110 | 111 | var msgSize uint32 = 0 112 | if err := binary.Read(bytes.NewBuffer(msgSizeBuf), binary.BigEndian, &msgSize); err != nil { 113 | return BaseMessage{}, nil, fmt.Errorf("消息体长度读取失败,%v", err) 114 | } 115 | 116 | if msgSize < p.minMsgLen || msgSize > p.maxMsgLen { 117 | return BaseMessage{}, nil, fmt.Errorf("消息长度有问题,msgSize=%v,minMsgLen=%d,maxMsgLen=%d", msgSize, p.minMsgLen, p.maxMsgLen) 118 | } 119 | 120 | // data 121 | allData := make([]byte, msgSize) 122 | if _, err := io.ReadFull(conn, allData); err != nil { 123 | return BaseMessage{}, nil, fmt.Errorf("消息体内容读取失败,%v", err) 124 | } 125 | 126 | var headSize uint16 = 0 127 | _ = binary.Read(bytes.NewBuffer(allData[0:headerSizeLen]), binary.BigEndian, &headSize) 128 | if headSize > (1 + 1 + 2 + 2 + 0xFF) { 129 | return BaseMessage{}, nil, fmt.Errorf("消息头长度异常,headSize=%v", headSize) 130 | } 131 | 132 | header := &MessageHeader{} 133 | dataBuf := bytes.NewBuffer(allData[headerSizeLen:]) 134 | _ = binary.Read(dataBuf, binary.BigEndian, &header.version) 135 | _ = binary.Read(dataBuf, binary.BigEndian, &header.encrypt) 136 | _ = binary.Read(dataBuf, binary.BigEndian, &header.AppType) 137 | _ = binary.Read(dataBuf, binary.BigEndian, &header.CmdId) 138 | 139 | //获取traceId,不做通用按字节去读,前8个字节是固定的,第9位如果等于1则紧跟在后边的16位就是traceId 140 | traceId := "" 141 | if len(allData) >= 8+1+TraceIdLen { 142 | //获取traceId == 1为具体标识 143 | var traceIdFlag uint8 = 0 144 | _ = binary.Read(bytes.NewBuffer(allData[8:8+1]), binary.BigEndian, &traceIdFlag) 145 | if traceIdFlag == FlagOtherTraceId { 146 | traceId = string(allData[8+1 : 8+1+TraceIdLen]) 147 | } 148 | } 149 | 150 | //构造参数 151 | headCmd := &TCPCommand{AppType: header.AppType, CmdId: header.CmdId} 152 | msgData := allData[headSize+headerSizeLen:] 153 | bm := BaseMessage{Cmd: *headCmd, TraceId: traceId} 154 | return bm, msgData, nil 155 | } 156 | 157 | // | msgSize | headSize | header | msgData 158 | // |4bit(msgSize)| 2bit(headSize) | 1bit(version) + 1bit(encrypt) + 2bit(AppType) + 2bit(CmdId) + Xbit(other) | msgData 159 | func (p *MsgParser) Write(appType, cmdId uint16, conn *TCPConn, msgData, otherData []byte) error { 160 | defer errorhelper.Recover() 161 | var headSize uint16 = 1 + 1 + 2 + 2 + uint16(len(otherData)) 162 | var msgSize uint32 = headerSizeLen + uint32(headSize) + uint32(len(msgData)) 163 | 164 | header := MessageHeader{0, 0, TCPCommand{appType, cmdId}, otherData} 165 | buf := new(bytes.Buffer) 166 | _ = binary.Write(buf, binary.BigEndian, msgSize) 167 | _ = binary.Write(buf, binary.BigEndian, headSize) 168 | _ = binary.Write(buf, binary.BigEndian, header.version) 169 | _ = binary.Write(buf, binary.BigEndian, header.encrypt) 170 | _ = binary.Write(buf, binary.BigEndian, header.AppType) 171 | _ = binary.Write(buf, binary.BigEndian, header.CmdId) 172 | if len(otherData) > 0 { 173 | _ = binary.Write(buf, binary.BigEndian, otherData) 174 | } 175 | _ = binary.Write(buf, binary.BigEndian, msgData) 176 | 177 | conn.Write(buf.Bytes()) 178 | 179 | return nil 180 | } 181 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mango 2 | 3 | --- 4 | 5 | ## 概述 6 | 7 | 站在巨人肩膀,核心部分基于[leaf](https://github.com/name5566/leaf),一个经典开源游戏服务。但leaf为单进程多模块模型,无法进行分布式部署。本项目即在此基础上做了扩展,实现了多进程分布式部署,业务模型为本人之前做过的一个项目。定义为分布式框架,适用所有分区、分房间类游戏。 8 | 9 | --- 10 | 11 | ## 相关组成 12 | 13 | * mango:https://github.com/GoldBaby5511/mango.git 14 | * mango-admin:https://github.com/GoldBaby5511/mango-admin.git 15 | * mango-admin-ui:https://github.com/GoldBaby5511/mango-admin-ui.git 16 | 17 | ---- 18 | 19 | ## 架构 20 | 21 | 采用网状拓扑结构,中心center服务负责服务治理,除了日志服之外,其他所有服务两两互联且向center服务注册管理。 22 | 23 | * gateway:网关服务(可水平扩展) 24 | * center:中心服务,服务注册、治理 25 | * logger:日志服,日志上报、预警 26 | * config:配置中心,支持携程apollo配置中心或本地json、csv、excel文本配置文件,配置更新实时通知相应服务 27 | * lobby:大厅服务,登录、在线管理等 28 | * room:房间服务,用户匹配等(可水平扩展) 29 | * table:桌子服务,游戏具体逻辑(可水平扩展) 30 | * list:房间列表服务,房间负载均衡、列表查询等 31 | * property:财富服务,用户道具增删改查 32 | * robot:机器人服务,模拟客户端行为、陪玩、压测(可水平扩展) 33 | * daemon:守护服务,执行管理端命令,开启/守护相关服务 34 | 35 | --- 36 | 37 | ## 使用 38 | 39 | ### 脚本 40 | 41 | * windows 42 | 1. 启动:右键scripts\windows目录下Startup.bat已管理员身份运行,若无权限问题会依次编译并运行各个服务 43 | 44 | 2. 创建:右键scripts\windows目录下CreateNewService.bat已管理员身份运行,输入新服务名,将会在cmd目录下创建对应新服务目录及模板源文件 45 | 46 | 3. 清理:右键scripts\windows目录下Cleanup.bat已管理员身份运行,将删除cmd内各服务内生成的中间及log文件 47 | * linux 48 | 1. 执行权限检查,转到scripts\linux目录,查看三个脚本是否有执行权限若没有则执行以下命令赋权 49 | 50 | ```bash 51 | chmod +x Cleanup.sh 52 | chmod +x CreateNewService.sh 53 | chmod +x Shutdown.sh 54 | chmod +x Startup.sh 55 | ``` 56 | 57 | 2. 启动:转到scripts\linux目录下执行./Startup.sh ,依次编译并后台运行各个服务 58 | 59 | ```bash 60 | #执行命令,验证服务是否启动成功 61 | ps -aux 62 | 63 | #成功的话会有十个进程,类似如下,若有未启动成功进程,可转入cmd对应服务目录下查看日志 64 | sanfeng 12248 15.3 0.7 1015440 29876 pts/1 Sl 11:41 0:02 ./logger -Type=1 -Id=1 65 | sanfeng 12333 0.1 0.3 906492 11916 pts/1 Sl 11:41 0:00 ./center -Type=2 -Id=50 66 | sanfeng 12417 1.1 0.6 1467784 23432 pts/1 Sl 11:41 0:00 ./config -Type=3 -Id=60 67 | sanfeng 12507 18.4 7.4 1417264 286952 pts/1 Sl 11:41 0:02 ./gateway -Type=4 -Id=100 68 | sanfeng 12593 4.9 0.5 1080816 20128 pts/1 Sl 11:41 0:00 ./lobby -Type=5 -Id=70 69 | sanfeng 12673 2.7 0.4 990388 17028 pts/1 Sl 11:41 0:00 ./list -Type=6 -Id=80 70 | sanfeng 12764 2.5 0.4 1055672 15468 pts/1 Sl 11:41 0:00 ./property -Type=7 -Id=90 71 | sanfeng 12851 1.9 0.5 941548 21116 pts/1 Sl 11:41 0:00 ./table -Type=8 -Id=1000 72 | sanfeng 12942 5.0 0.6 1146420 25668 pts/1 Sl 11:41 0:00 ./room -Type=9 -Id=2000 73 | sanfeng 13025 20.3 10.9 1627632 421024 pts/1 Sl 11:41 0:02 ./robot -Type=10 -Id=3000 74 | ``` 75 | 76 | 3. 关闭:转到scripts\linux目录下执行./Shutdown.sh 77 | 4. 创建:转到scripts\linux目录下执行./CreateNewService.sh,输入名称,会在cmd目录下生成对应服务 78 | 5. 清理:转到scripts\linux目录下执行./Cleanup.sh 79 | 80 | ### 手动编译 81 | 82 | windows下可能存在权限问题,导致脚本运行失败,若出现该类情况则可手动编译运行。 83 | 84 | 1. 启动命令行或shell分别进入cmd下各个服务内,执行 go build 85 | 2. 拷贝configs目录至cmd\config目录下,因为config(配置中心服)需要加载各个服务配置 86 | 3. 命令行或shell进入cmd下各个服务内,执行以下命令启动服务 87 | 88 | ```bash 89 | .\logger -Type=1 -Id=1 90 | .\center -Type=2 -Id=50 91 | .\config -Type=3 -Id=55 92 | .\gateway -Type=4 -Id=100 93 | .\lobby -Type=5 -Id=60 94 | .\property -Type=6 -Id=65 95 | .\list -Type=10 -Id=70 96 | .\table -Type=11 -Id=1000 97 | .\room -Type=12 -Id=2000 98 | .\robot -Type=9 -Id=3000 99 | .\daemon -Type=100 -Id=300 100 | ``` 101 | 102 | 服务启动完成后,robot会默认创建1000用户模拟客户端行为,连接网关-->登录-->报名-->举手-->游戏。起始用户数量可配,robot-3000.json 文件 "机器人数量"字段 103 | 104 | --- 105 | 106 | ## Docker部署 107 | 108 | * 本机部署好Docker环境,命令行执行以下命令,生成镜像 109 | 110 | ```bash 111 | docker build --file ./build/package/Dockerfile.center --tag mango/center . 112 | docker build --file ./build/package/Dockerfile.config --tag mango/config . 113 | docker build --file ./build/package/Dockerfile.gateway --tag mango/gateway . 114 | docker build --file ./build/package/Dockerfile.logger --tag mango/logger . 115 | docker build --file ./build/package/Dockerfile.lobby --tag mango/lobby . 116 | docker build --file ./build/package/Dockerfile.list --tag mango/list . 117 | docker build --file ./build/package/Dockerfile.property --tag mango/property . 118 | docker build --file ./build/package/Dockerfile.table --tag mango/table . 119 | docker build --file ./build/package/Dockerfile.room --tag mango/room . 120 | docker build --file ./build/package/Dockerfile.robot --tag mango/robot . 121 | ``` 122 | 123 | * 创建网桥 124 | 125 | ```bash 126 | docker network create mango 127 | ``` 128 | 129 | * 执行以下命令,运行本地容器 130 | 131 | ```bash 132 | docker run -d --name="logger" --network mango mango/logger 133 | docker run -d --name="center" --network mango mango/center 134 | docker run -d --name="config" --network mango mango/config 135 | docker run -d --name="lobby" --network mango mango/lobby 136 | docker run -d --name="list" --network mango mango/list 137 | docker run -d --name="property" --network mango mango/property 138 | docker run -d --name="table" --network mango mango/table 139 | docker run -d --name="room" --network mango mango/room 140 | docker run -d --name="robot" --network mango mango/robot 141 | docker run -d -p 10100:10100 --name="gateway" --network mango mango/gateway 142 | ``` 143 | 144 | --- 145 | 146 | ## 将来 147 | 148 | 1. 日志服对分片文本文件自动压缩;具备kafka上报,方便接入ELK、信息统计、消息预警等 149 | 2. 服务治理,对除网关之外的服务实现热插拔式切换更新 150 | 3. 管理工具,服务启动、监控守护、更新、切换等 151 | 152 | 最终目的不仅是一套完整的服务框架,同时可以将是某些特定业务直接的解决方案。 153 | 154 | --- 155 | 156 | ## 相关博客 157 | 158 | mango(一):杂谈项目由来:https://blog.csdn.net/weixin_42780662/article/details/122006434 159 | 160 | mango(二):架构:https://blog.csdn.net/weixin_42780662/article/details/122172058 161 | 162 | --- 163 | 164 | ## 参考引用 165 | 166 | * leaf:https://github.com/name5566/leaf.git 167 | * agollo:https://github.com/apolloconfig/agollo.git 168 | * fsnotify:https://github.com/fsnotify/fsnotify.git 169 | * proto:https://github.com/protocolbuffers/protobuf.git 170 | * project-layout:https://github.com/golang-standards/project-layout.git 171 | 172 | --- 173 | 174 | ## 交流群 175 | 176 | * QQ交流群:781335145 177 | -------------------------------------------------------------------------------- /cmd/table/business/business.go: -------------------------------------------------------------------------------- 1 | package business 2 | 3 | import ( 4 | "github.com/golang/protobuf/proto" 5 | "mango/api/gateway" 6 | tCMD "mango/api/table" 7 | "mango/cmd/table/business/player" 8 | "mango/cmd/table/business/table" 9 | "mango/cmd/table/business/table/ddz" 10 | "mango/pkg/conf/apollo" 11 | g "mango/pkg/gate" 12 | "mango/pkg/log" 13 | n "mango/pkg/network" 14 | ) 15 | 16 | var ( 17 | freeTables = make(map[uint64]*table.Table) 18 | usedTables = make(map[uint64]*table.Table) 19 | ) 20 | 21 | func init() { 22 | g.MsgRegister(&tCMD.ApplyReq{}, n.AppTable, uint16(tCMD.CMDTable_IDApplyReq), handleApplyReq) 23 | g.MsgRegister(&tCMD.ReleaseReq{}, n.AppTable, uint16(tCMD.CMDTable_IDReleaseReq), handleReleaseReq) 24 | g.MsgRegister(&tCMD.SetPlayerToTableReq{}, n.AppTable, uint16(tCMD.CMDTable_IDSetPlayerToTableReq), handleSetPlayerToTableReq) 25 | g.MsgRegister(&tCMD.MatchTableReq{}, n.AppTable, uint16(tCMD.CMDTable_IDMatchTableReq), handleMatchTableReq) 26 | g.MsgRegister(&tCMD.GameMessage{}, n.AppTable, uint16(tCMD.CMDTable_IDGameMessage), handleGameMessage) 27 | g.CallBackRegister(g.CbConfigChangeNotify, configChangeNotify) 28 | } 29 | 30 | func configChangeNotify(args []interface{}) { 31 | tableCount := apollo.GetConfigAsInt64("桌子数量", 5000) 32 | gameKind := apollo.GetConfigAsInt64("游戏类型", 0) 33 | if len(freeTables) == 0 && len(usedTables) == 0 && tableCount != 0 && gameKind != 0 { 34 | log.Info("配置", "初始化桌子,tableCount=%d,gameKind=%v", tableCount, gameKind) 35 | for i := 0; i < int(tableCount); i++ { 36 | 37 | switch gameKind { 38 | case table.DdzKind: 39 | freeTables[uint64(i)] = table.NewTable(uint64(i), new(ddz.TableSink)) 40 | default: 41 | log.Warning("", "异常,游戏类型不存在,gameKind=%v", gameKind) 42 | } 43 | } 44 | } 45 | } 46 | 47 | func handleApplyReq(args []interface{}) { 48 | b := args[n.DataIndex].(n.BaseMessage) 49 | m := (b.MyMessage).(*tCMD.ApplyReq) 50 | srcApp := args[n.OtherIndex].(n.BaseAgentInfo) 51 | 52 | if len(freeTables) < int(m.GetApplyCount()) { 53 | log.Warning("", "空闲桌子不够了,ApplyCount=%d,len=%d", m.GetApplyCount(), len(freeTables)) 54 | return 55 | } 56 | log.Debug("", "收到申请,ApplyCount=%d,len=%d", m.GetApplyCount(), len(freeTables)) 57 | 58 | var rsp tCMD.ApplyRsp 59 | rsp.ApplyCount = proto.Uint32(m.GetApplyCount()) 60 | for k, v := range freeTables { 61 | rsp.TableIds = append(rsp.TableIds, v.Id) 62 | v.HostAppId = srcApp.AppId 63 | delete(freeTables, k) 64 | usedTables[k] = v 65 | if len(rsp.GetTableIds()) == int(m.GetApplyCount()) { 66 | break 67 | } 68 | } 69 | 70 | g.SendData2App(srcApp.AppType, srcApp.AppId, n.AppTable, uint32(tCMD.CMDTable_IDApplyRsp), &rsp) 71 | } 72 | 73 | func handleReleaseReq(args []interface{}) { 74 | m := args[n.DataIndex].(n.BaseMessage).MyMessage.(*tCMD.ReleaseReq) 75 | srcApp := args[n.OtherIndex].(n.BaseAgentInfo) 76 | 77 | log.Debug("", "收到释放,ApplyCount=%d,len=%d,srcID=%d", m.GetReleaseCount(), len(freeTables), srcApp.AppId) 78 | for _, tId := range m.GetTableIds() { 79 | t := getTable(srcApp.AppId, tId) 80 | if t == nil { 81 | continue 82 | } 83 | t.Reset() 84 | delete(usedTables, tId) 85 | freeTables[tId] = t 86 | } 87 | } 88 | 89 | func handleSetPlayerToTableReq(args []interface{}) { 90 | m := args[n.DataIndex].(n.BaseMessage).MyMessage.(*tCMD.SetPlayerToTableReq) 91 | srcApp := args[n.OtherIndex].(n.BaseAgentInfo) 92 | if _, ok := usedTables[m.GetTableId()]; !ok { 93 | log.Warning("", "没找到桌子啊,tableId=%v", m.GetTableId()) 94 | return 95 | } 96 | t := getTable(srcApp.AppId, m.GetTableId()) 97 | if t == nil { 98 | log.Warning("", "这桌子不是你的啊,tableId=%v,host=%v,srcId=%v", m.GetTableId(), usedTables[m.GetTableId()].HostAppId, srcApp.AppId) 99 | return 100 | } 101 | 102 | pl := getPlayer(m.GetUserId()) 103 | if pl != nil { 104 | log.Warning("", "已经存在了啊,userId=%v,tableId=%v,host=%v,srcId=%v", pl.UserId, m.GetTableId(), usedTables[m.GetTableId()].HostAppId, srcApp.AppId) 105 | return 106 | } 107 | 108 | log.Debug("", "收到入座,UserId=%v,SeatId=%v,TableId=%d,len=%d,srcID=%d", m.GetUserId(), m.GetSeatId(), m.GetTableId(), len(freeTables), srcApp.AppId) 109 | 110 | pl = player.NewPlayer() 111 | pl.UserId = m.GetUserId() 112 | pl.TableId = t.Id 113 | pl.SrcAppId = srcApp.AppId 114 | pl.SeatId = m.GetSeatId() 115 | pl.GateConnId = m.GetGateconnid() 116 | pl.State = player.SitdownState 117 | t.SetPlayer(pl) 118 | } 119 | 120 | func handleMatchTableReq(args []interface{}) { 121 | m := args[n.DataIndex].(n.BaseMessage).MyMessage.(*tCMD.MatchTableReq) 122 | srcApp := args[n.OtherIndex].(n.BaseAgentInfo) 123 | 124 | t := getTable(srcApp.AppId, m.GetTableId()) 125 | if t == nil { 126 | return 127 | } 128 | 129 | log.Debug("", "收到配桌,TableId=%d,len=%d,srcID=%d", m.GetTableId(), len(freeTables), srcApp.AppId) 130 | t.Start() 131 | } 132 | 133 | func handleGameMessage(args []interface{}) { 134 | m := args[n.DataIndex].(n.BaseMessage).MyMessage.(*tCMD.GameMessage) 135 | srcData := args[n.OtherIndex].(*gateway.TransferDataReq) 136 | 137 | userID := srcData.GetUserId() 138 | pl := getPlayer(userID) 139 | if pl == nil { 140 | log.Warning("", "游戏消息,没找到用户啊,userID=%v", userID) 141 | return 142 | } 143 | 144 | t := getTable(pl.SrcAppId, pl.TableId) 145 | if t == nil { 146 | log.Warning("", "游戏消息,没找到桌子啊,userID=%v,SrcAppId=%v,TableId=%v", userID, pl.SrcAppId, pl.TableId) 147 | return 148 | } 149 | t.GameMessage(pl.SeatId, m.GetSubCmdid(), m.GetData()) 150 | } 151 | 152 | func getTable(srcAppId uint32, tableID uint64) *table.Table { 153 | for _, t := range usedTables { 154 | if t.Id == tableID && t.HostAppId == srcAppId { 155 | return t 156 | } 157 | } 158 | return nil 159 | } 160 | 161 | func getPlayer(userID uint64) *player.Player { 162 | for _, t := range usedTables { 163 | for _, pl := range t.Players { 164 | if pl.UserId == userID { 165 | return pl 166 | } 167 | } 168 | } 169 | return nil 170 | } 171 | -------------------------------------------------------------------------------- /pkg/network/json/json.go: -------------------------------------------------------------------------------- 1 | package json 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "mango/pkg/chanrpc" 8 | "mango/pkg/log" 9 | "reflect" 10 | ) 11 | 12 | type Processor struct { 13 | msgInfo map[string]*MsgInfo 14 | } 15 | 16 | type MsgInfo struct { 17 | msgType reflect.Type 18 | msgRouter *chanrpc.Server 19 | msgHandler MsgHandler 20 | msgRawHandler MsgHandler 21 | } 22 | 23 | type MsgHandler func([]interface{}) 24 | 25 | type MsgRaw struct { 26 | msgID string 27 | msgRawData json.RawMessage 28 | } 29 | 30 | func NewProcessor() *Processor { 31 | p := new(Processor) 32 | p.msgInfo = make(map[string]*MsgInfo) 33 | return p 34 | } 35 | 36 | // It's dangerous to call the method on routing or marshaling (unmarshaling) 37 | func (p *Processor) Register(msg interface{}) string { 38 | msgType := reflect.TypeOf(msg) 39 | if msgType == nil || msgType.Kind() != reflect.Ptr { 40 | log.Fatal("json", "json message pointer required") 41 | } 42 | msgID := msgType.Elem().Name() 43 | if msgID == "" { 44 | log.Fatal("json", "unnamed json message") 45 | } 46 | if _, ok := p.msgInfo[msgID]; ok { 47 | log.Fatal("json", "message %v is already registered", msgID) 48 | } 49 | 50 | i := new(MsgInfo) 51 | i.msgType = msgType 52 | p.msgInfo[msgID] = i 53 | return msgID 54 | } 55 | 56 | // It's dangerous to call the method on routing or marshaling (unmarshaling) 57 | func (p *Processor) SetRouter(msg interface{}, msgRouter *chanrpc.Server) { 58 | msgType := reflect.TypeOf(msg) 59 | if msgType == nil || msgType.Kind() != reflect.Ptr { 60 | log.Fatal("json", "json message pointer required") 61 | } 62 | msgID := msgType.Elem().Name() 63 | i, ok := p.msgInfo[msgID] 64 | if !ok { 65 | log.Fatal("json", "message %v not registered", msgID) 66 | } 67 | 68 | i.msgRouter = msgRouter 69 | } 70 | 71 | // It's dangerous to call the method on routing or marshaling (unmarshaling) 72 | func (p *Processor) SetHandler(msg interface{}, msgHandler MsgHandler) { 73 | msgType := reflect.TypeOf(msg) 74 | if msgType == nil || msgType.Kind() != reflect.Ptr { 75 | log.Fatal("json", "json message pointer required") 76 | } 77 | msgID := msgType.Elem().Name() 78 | i, ok := p.msgInfo[msgID] 79 | if !ok { 80 | log.Fatal("json", "message %v not registered", msgID) 81 | } 82 | 83 | i.msgHandler = msgHandler 84 | } 85 | 86 | // It's dangerous to call the method on routing or marshaling (unmarshaling) 87 | func (p *Processor) SetRawHandler(msgID string, msgRawHandler MsgHandler) { 88 | i, ok := p.msgInfo[msgID] 89 | if !ok { 90 | log.Fatal("json", "message %v not registered", msgID) 91 | } 92 | 93 | i.msgRawHandler = msgRawHandler 94 | } 95 | 96 | // goroutine safe 97 | func (p *Processor) Route(userData interface{}, cmd interface{}, msg interface{}) error { 98 | // raw 99 | if msgRaw, ok := msg.(MsgRaw); ok { 100 | i, ok := p.msgInfo[msgRaw.msgID] 101 | if !ok { 102 | return fmt.Errorf("message %v not registered", msgRaw.msgID) 103 | } 104 | if i.msgRawHandler != nil { 105 | i.msgRawHandler([]interface{}{msgRaw.msgID, msgRaw.msgRawData, userData}) 106 | } 107 | return nil 108 | } 109 | 110 | // json 111 | msgType := reflect.TypeOf(msg) 112 | if msgType == nil || msgType.Kind() != reflect.Ptr { 113 | return errors.New("json message pointer required") 114 | } 115 | msgID := msgType.Elem().Name() 116 | i, ok := p.msgInfo[msgID] 117 | if !ok { 118 | return fmt.Errorf("message %v not registered", msgID) 119 | } 120 | if i.msgHandler != nil { 121 | i.msgHandler([]interface{}{msg, userData}) 122 | } 123 | if i.msgRouter != nil { 124 | i.msgRouter.Go(msgType, msg, userData) 125 | } 126 | return nil 127 | } 128 | 129 | // goroutine safe 130 | func (p *Processor) Unmarshal(data []byte) (interface{}, interface{}, error) { 131 | //去除末尾0 132 | //data = data[0 : len(data)-1] 133 | //手动解析 134 | head := make([]byte, 8) 135 | copy(head, data[0:8]) 136 | 137 | //buf := bytes.NewBuffer(head) 138 | 139 | //tcpHead := &network.MessageHeader{} 140 | //binary.Read(buf, binary.LittleEndian, &tcpHead.DataKind) 141 | //binary.Read(buf, binary.LittleEndian, &tcpHead.CheckCode) 142 | //binary.Read(buf, binary.LittleEndian, &tcpHead.PacketSize) 143 | //binary.Read(buf, binary.LittleEndian, &tcpHead.AppType) 144 | //binary.Read(buf, binary.LittleEndian, &tcpHead.CmdId) 145 | // 146 | ////log.Debug("JSON 解析数据% d\n", data) 147 | // 148 | //if tcpHead.PacketSize == 8 { 149 | // tcpData, err := p.Marshal(&tcpHead) 150 | // if err != nil { 151 | // log.Error("json", "marshal message %v error: %v", reflect.TypeOf(&network.TCPHead{}), err) 152 | // return nil, nil, errors.New("数据包头序列化失败") 153 | // } 154 | // l := 0 155 | // for i := 0; i < len(tcpData); i++ { 156 | // copy(data[l:], tcpData[i]) 157 | // l += len(tcpData[i]) 158 | // } 159 | //} else { 160 | // data = data[8:] 161 | //} 162 | 163 | //log.Debug("JSON 解析数据% d\n", data) 164 | 165 | //JSON反序列化 166 | var m map[string]json.RawMessage 167 | err := json.Unmarshal(data, &m) 168 | if err != nil { 169 | log.Error("json", "JSON反序列化失败") 170 | return nil, nil, err 171 | } 172 | if len(m) != 1 { 173 | return nil, nil, errors.New("invalid json data") 174 | } 175 | 176 | for msgID, data := range m { 177 | i, ok := p.msgInfo[msgID] 178 | if !ok { 179 | return nil, nil, fmt.Errorf("message %v not registered", msgID) 180 | } 181 | 182 | // msg 183 | if i.msgRawHandler != nil { 184 | return nil, MsgRaw{msgID, data}, nil 185 | } else { 186 | msg := reflect.New(i.msgType.Elem()).Interface() 187 | return nil, msg, json.Unmarshal(data, msg) 188 | } 189 | } 190 | 191 | panic("bug") 192 | } 193 | 194 | // goroutine safe 195 | func (p *Processor) Marshal(msg interface{}) ([][]byte, error) { 196 | msgType := reflect.TypeOf(msg) 197 | if msgType == nil || msgType.Kind() != reflect.Ptr { 198 | return nil, errors.New("json message pointer required") 199 | } 200 | msgID := msgType.Elem().Name() 201 | if _, ok := p.msgInfo[msgID]; !ok { 202 | return nil, fmt.Errorf("message %v not registered", msgID) 203 | } 204 | 205 | // data 206 | m := map[string]interface{}{msgID: msg} 207 | data, err := json.Marshal(m) 208 | return [][]byte{data}, err 209 | } 210 | --------------------------------------------------------------------------------