diff --git a/Makefile b/Makefile index 540a718..be4b6d1 100644 --- a/Makefile +++ b/Makefile @@ -7,12 +7,12 @@ gen: protoc-go-inject-tag -input=./pb/*.pb.go test: - go run test/client.go + go run cmd/test/client.go http: - go run -race cmd/http.go + go run -race cmd/httpserver/*.go game: - go run -race cmd/game.go + go run -race cmd/gameserver/*.go build: go build -race -o bin/account cmd/http.go go build -race -o bin/game cmd/game.go diff --git a/cmd/game.go b/cmd/game.go deleted file mode 100644 index 06bd2d3..0000000 --- a/cmd/game.go +++ /dev/null @@ -1,41 +0,0 @@ -package main - -import ( - _ "net/http/pprof" - "os" - "os/signal" - "pro2d/conf" - "pro2d/src/components/logger" - "pro2d/src/components/net" - _ "pro2d/src/plugin" - "syscall" -) - -func main() { - err := make(chan error) - stopChan := make(chan os.Signal) - signal.Notify(stopChan, syscall.SIGTERM, syscall.SIGINT, syscall.SIGKILL) - - userChan := make(chan os.Signal) - signal.Notify(userChan, syscall.SIGUSR1, syscall.SIGUSR2) - - s := net.NewServer(conf.GlobalConf.GameConf) - go func() { - err <- s.Start() - }() - - for { - select { - case e := <- err: - logger.Error("game server error: %v", e) - return - case <-stopChan: - s.Stop() - logger.Debug("game stop...") - return - case u := <-userChan: - logger.Debug("userChan .. %v",u.String()) - s.LoadPlugin() - } - } -} diff --git a/cmd/gameserver/agent.go b/cmd/gameserver/agent.go new file mode 100644 index 0000000..f64a38b --- /dev/null +++ b/cmd/gameserver/agent.go @@ -0,0 +1,132 @@ +package main + +import ( + "fmt" + "github.com/golang/protobuf/proto" + "math" + "pro2d/common" + "pro2d/common/components" + "pro2d/models" + "pro2d/pb" + "pro2d/utils" + "pro2d/utils/logger" + "sync/atomic" +) + +type Agent struct { + components.IConnection + Server components.IServer + + Role *models.RoleModel + + readFunc chan func() + timerFunc chan func() + Quit chan *Agent + + nextCheckTime int64 //下一次检查的时间 + lastHeartCheckTime int64 + heartTimeoutCount int //超时次数 +} + +func NewAgent(s components.IServer) *Agent { + return &Agent{ + Server: s, + readFunc: make(chan func(), 10), + timerFunc: make(chan func(), 10), + Quit: make(chan *Agent), + + nextCheckTime: 0, + lastHeartCheckTime: utils.Timex(), + heartTimeoutCount: 0, + } +} + +func (c *Agent) listen() { + defer c.Close() + for { + select { + case timerFunc := <- c.timerFunc: + timerFunc() + case readFunc := <- c.readFunc: + readFunc() + case <- c.Quit: + return + } + } +} + +func (c *Agent) OnConnection(conn components.IConnection) { + c.IConnection = conn + go c.listen() +} + +func (c *Agent) OnMessage(msg components.IMessage) { + f := func() { + atomic.StoreInt64(&c.lastHeartCheckTime, utils.Timex()) + if md, ok := components.ActionMap[pb.ProtoCode(msg.GetHeader().GetMsgID())]; ok { + logger.Debug("protocode handler: %d", msg.GetHeader().GetMsgID()) + errCode, protomsg := md(msg) + rsp, err := proto.Marshal(protomsg) + fmt.Printf("errCode: %d, protomsg:%v\n", errCode, protomsg) + if err != nil { + conn := c.Server.GetIConnection(msg.GetSessId()) + if conn != nil { + conn.Send(-100, msg.GetHeader().GetMsgID(), nil) + } + return + } + conn := c.Server.GetIConnection(msg.GetSessId()) + if conn != nil { + conn.Send(errCode, msg.GetHeader().GetMsgID(), rsp) + } + return + } + logger.Error("protocode not handler: %d", msg.GetHeader().GetMsgID()) + } + c.readFunc <- f +} + +func (c *Agent) OnClose() { + c.Quit <- c +} + +func (c *Agent) Close() { + if c.Role == nil { + return + } + + c.Role.OnOfflineEvent() +} + +func (c *Agent) checkHeartBeat(now int64) { + lastHeartCheckTime := atomic.LoadInt64(&c.lastHeartCheckTime) + logger.Debug("checkHeartBeat ID: %d, last: %d, now: %d", c.GetID(), lastHeartCheckTime, now) + if math.Abs(float64(lastHeartCheckTime - now)) > common.HeartTimerInterval { + c.heartTimeoutCount++ + if c.heartTimeoutCount >= common.HeartTimeoutCountMax { + c.Stop() + return + } + logger.Debug("timeout count: %d", c.heartTimeoutCount) + }else { + c.heartTimeoutCount = 0 + } +} + +func (c *Agent) update() { + nextCheckTime := atomic.LoadInt64(&c.nextCheckTime) + now := utils.Timex() + if now >= nextCheckTime { + //检查心跳 + c.checkHeartBeat(now) + nextCheckTime = now + common.HeartTimerInterval + atomic.StoreInt64(&c.nextCheckTime, nextCheckTime) + } + + c.timerFunc <- func() { + if c.Role != nil { + //role 恢复数据 + c.Role.OnRecoverTimer(now) + } + } +} diff --git a/cmd/gameserver/game.go b/cmd/gameserver/game.go new file mode 100644 index 0000000..1c93c73 --- /dev/null +++ b/cmd/gameserver/game.go @@ -0,0 +1,125 @@ +package main + +import ( + "context" + "fmt" + _ "net/http/pprof" + "os" + "os/signal" + "pro2d/common/components" + "pro2d/conf" + "pro2d/models" + _ "pro2d/plugin" + "pro2d/utils/db" + "pro2d/utils/etcd" + "pro2d/utils/logger" + "sync" + "syscall" + "time" +) + +type GameServer struct { + components.IServer + EtcdClient *etcd.EtcdClient + + Agents *sync.Map +} + +func NewGameServer(sconf *conf.SConf) (*GameServer, error) { + s := &GameServer{ + IServer: components.NewServer(sconf.Port, conf.GlobalConf.GameConf.PluginPath, components.NewPBSplitter()), + Agents: new(sync.Map), + } + s.SetConnectionCallback(s.OnConnection) + s.SetMessageCallback(s.OnMessage) + s.SetCloseCallback(s.OnClose) + + //mongo 初始化 + db.MongoDatabase = db.MongoClient.Database(sconf.DBName) + models.InitGameServerModels() + + //Etcd 初始化 + var err error + s.EtcdClient, err = etcd.NewEtcdClient(conf.GlobalConf.Etcd) + if err != nil { + return nil, err + } + s.EtcdClient.PutWithLeasePrefix(conf.GlobalConf.GameConf.Name, conf.GlobalConf.GameConf.ID, fmt.Sprintf("%s:%d", conf.GlobalConf.GameConf.IP, conf.GlobalConf.GameConf.Port), 5) + + go s.handleTimeOut() + return s, nil +} + +func (s *GameServer) OnConnection(conn components.IConnection) { + agent := NewAgent(s) + agent.OnConnection(conn) + s.Agents.Store(conn.GetID(),agent) +} + +func (s *GameServer) OnMessage(msg components.IMessage) { + agent, ok := s.Agents.Load(msg.GetSessId()) + if !ok { + return + } + agent.(*Agent).OnMessage(msg) +} + +func (s *GameServer) OnClose(conn components.IConnection) { + agent, ok := s.Agents.Load(conn.GetID()) + if !ok { + return + } + agent.(*Agent).OnClose() + s.Agents.Delete(conn.GetID()) +} + +func (s *GameServer) Stop() { + s.IServer.Stop() + + db.MongoClient.Disconnect(context.TODO()) +} + + +func (s *GameServer) handleTimeOut() { + s.Agents.Range(func(key, value interface{}) bool { + agent := value.(*Agent) + agent.update() + return true + }) + + components.TimeOut(1*time.Second, s.handleTimeOut) +} + +func main() { + err := make(chan error) + stopChan := make(chan os.Signal) + signal.Notify(stopChan, syscall.SIGTERM, syscall.SIGINT, syscall.SIGKILL) + + userChan := make(chan os.Signal) + signal.Notify(userChan, syscall.SIGUSR1, syscall.SIGUSR2) + + s,err1 := NewGameServer(conf.GlobalConf.GameConf) + if err1 != nil { + fmt.Errorf(err1.Error()) + return + } + + go func() { + err <- s.Start() + }() + + for { + select { + case e := <- err: + logger.Error("game server error: %v", e) + return + case <-stopChan: + s.Stop() + logger.Debug("game stop...") + return + case u := <-userChan: + logger.Debug("userChan .. %v",u.String()) + //s.LoadPlugin() + } + } +} diff --git a/cmd/http.go b/cmd/http.go deleted file mode 100644 index f7bf928..0000000 --- a/cmd/http.go +++ /dev/null @@ -1,30 +0,0 @@ -package main - -import ( - "os" - "os/signal" - _ "pro2d/conf" - "pro2d/src/actions" - "pro2d/src/components/logger" - "pro2d/src/components/net" - "syscall" -) - -func main() { - err := make(chan error) - stopChan := make(chan os.Signal) - signal.Notify(stopChan, syscall.SIGTERM, syscall.SIGINT, syscall.SIGKILL) - - web := net.NewHttpServer("v1") - web.BindHandler(&actions.AccountAction{HttpServer: web}) - go func() { - err <- web.Start() - }() - - select { - case e := <- err: - logger.Error("game server error: %v", e) - case <-stopChan: - logger.Debug("game stop") - } -} \ No newline at end of file diff --git a/cmd/httpserver/AccountAction.go b/cmd/httpserver/AccountAction.go new file mode 100644 index 0000000..27ae06d --- /dev/null +++ b/cmd/httpserver/AccountAction.go @@ -0,0 +1,62 @@ +package main + +import ( + "github.com/gin-gonic/gin" + "pro2d/conf" + "pro2d/models" + "pro2d/pb" + "pro2d/utils" +) + +type AccountAction struct { + HttpServer *AccountServer +} + +func (h *AccountAction) Register(c *gin.Context) (int, interface{}){ + var register pb.Register + if err := c.ShouldBindJSON(®ister); err != nil { + return -1, err.Error() + } + + account := models.NewAccount(register.Phone) + if err := account.Load(); err == nil { + return -2 , "account exists: " + register.Phone + } + + account.Uid = conf.SnowFlack.NextValStr() + account.Password = utils.Md5V(register.Password) + if err := account.Create(); err != nil{ + return -3, "account register err: " + err.Error() + } + account.Password = register.Password + return 0, account.Account +} + +func (h *AccountAction) Login(c *gin.Context) (int,interface{}) { + var login pb.Account + if err := c.ShouldBindJSON(&login); err != nil { + return -1, err.Error() + } + account := models.NewAccount(login.Phone) + if err := account.Load(); err != nil { + return -2, err.Error() + } + + if utils.Md5V(login.Password) != account.Password { + return -3, "password error" + } + + var gs []*pb.ServiceInfo + for k, v := range h.HttpServer.EtcdClient.GetByPrefix(conf.GlobalConf.GameConf.Name) { + gs = append(gs, &pb.ServiceInfo{ + Id: k, + Name: conf.GlobalConf.GameConf.Name, + Address: v, + }) + } + rsp := &pb.LoginRsp{ + Uid: account.Uid, + GameService: gs, + } + return 0, rsp +} diff --git a/cmd/httpserver/http.go b/cmd/httpserver/http.go new file mode 100644 index 0000000..b4df3cf --- /dev/null +++ b/cmd/httpserver/http.go @@ -0,0 +1,59 @@ +package main + +import ( + "fmt" + "os" + "os/signal" + "pro2d/common/components" + "pro2d/conf" + _ "pro2d/conf" + "pro2d/models" + "pro2d/utils/db" + "pro2d/utils/etcd" + "pro2d/utils/logger" + "syscall" +) + +type AccountServer struct { + components.IHttp + EtcdClient *etcd.EtcdClient +} + +func NewAccountServer(version string, port ...string) *AccountServer { + return &AccountServer{IHttp: components.NewHttpServer(version, port...)} +} + +func (s *AccountServer) Start() error { + //mongo 初始化 + db.MongoDatabase = db.MongoClient.Database(conf.GlobalConf.AccountConf.DBName) + models.InitAccountServerModels() + + //Etcd 初始化 + var err error + s.EtcdClient, err = etcd.NewEtcdClient(conf.GlobalConf.Etcd) + if err != nil { + return err + } + s.EtcdClient.PutWithLeasePrefix(conf.GlobalConf.AccountConf.Name, conf.GlobalConf.AccountConf.ID, fmt.Sprintf("%s:%d", conf.GlobalConf.AccountConf.IP, conf.GlobalConf.AccountConf.Port), 5) + return nil +} + +func main() { + err := make(chan error) + stopChan := make(chan os.Signal) + signal.Notify(stopChan, syscall.SIGTERM, syscall.SIGINT, syscall.SIGKILL) + + web := NewAccountServer("v1") + web.BindHandler(&AccountAction{HttpServer: web}) + go func() { + err <- web.Start() + }() + + select { + case e := <- err: + logger.Error("game server error: %v", e) + case <-stopChan: + logger.Debug("game stop") + web.Stop() + } +} \ No newline at end of file diff --git a/cmd/httpserver/http_test.go b/cmd/httpserver/http_test.go new file mode 100644 index 0000000..a1d2e3f --- /dev/null +++ b/cmd/httpserver/http_test.go @@ -0,0 +1,23 @@ +package main + +import ( + "github.com/gin-gonic/gin" + "pro2d/common/components" + "testing" +) + +type HttpAction struct { +} + +func (h *HttpAction) PrintA(c *gin.Context) (int, interface{}) { + return 0, "I'm A" +} +func (h *HttpAction) PrintB(c *gin.Context) (int, interface{}) { + return 0, "I'm B" +} + +func TestHttpServer_Start(t *testing.T) { + web := components.NewHttpServer("v1") + web.BindHandler(&HttpAction{}) + web.Start() +} \ No newline at end of file diff --git a/cmd/test/client.go b/cmd/test/client.go new file mode 100644 index 0000000..3cf0ded --- /dev/null +++ b/cmd/test/client.go @@ -0,0 +1,75 @@ +package main + +import ( + "bufio" + "bytes" + "encoding/binary" + "github.com/golang/protobuf/proto" + "net" + "pro2d/common/components" + "pro2d/pb" + "pro2d/utils/logger" + "time" +) + +func main() { + + head := &components.PBHead{ + Length: 0, + Cmd: uint32(pb.ProtoCode_LoginReq), + ErrCode: 0, + PreField: 0, + } + + loginReq := &pb.LoginReq{ + Uid: "141815055745814528", + Device: "123123", + } + l, _ :=proto.Marshal(loginReq) + + b := components.PBMessage{ + Head: head, + Body: l, + } + head.Length = uint32(16 + len(b.Body)) + buf := &bytes.Buffer{} + err := binary.Write(buf, binary.BigEndian, head) + if err != nil { + logger.Error("err: %v, head: %v", err, head) + return + } + logger.Debug("head: %v", head) + + err = binary.Write(buf, binary.BigEndian, b.Body) + if err != nil { + logger.Error("err: %v, msg: %v", err, b.Body) + return + } + + client, err := net.Dial("tcp", "localhost:8850") + if err != nil { + logger.Error(err) + return + } + + rd := bufio.NewReadWriter(bufio.NewReader(client), bufio.NewWriter(client)) + for { + b1 := make([]byte, 1024) + n, err := rd.Write(buf.Bytes()) + if err != nil { + logger.Error(err) + return + } + rd.Flush() + logger.Debug("write:n: %d, msg: %s", n, buf.Bytes()) + + n, err = rd.Read(b1) + if err != nil { + logger.Error(err) + return + } + logger.Debug("recv: %s, n: %d\n", b1, n) + time.Sleep(1*time.Second) + } + +} \ No newline at end of file diff --git a/cmd/test/client_test.go b/cmd/test/client_test.go new file mode 100644 index 0000000..e72e7ab --- /dev/null +++ b/cmd/test/client_test.go @@ -0,0 +1,32 @@ +package main + +import ( + "testing" +) + +type Error struct { + errCode uint8 +} +func (e *Error) Error() string { + switch e.errCode { + case 1: + return "file not found" + case 2: + return "time out" + case 3: + return "permission denied" + default: + return "unknown error" + } +} + +func checkError(err error) { + if err != nil { + panic(err) + } +} + +func TestStart( t *testing.T) { + var e *Error + checkError(e) +} \ No newline at end of file diff --git a/common/components/conn.go b/common/components/conn.go new file mode 100644 index 0000000..c636289 --- /dev/null +++ b/common/components/conn.go @@ -0,0 +1,122 @@ +package components + +import ( + "bufio" + "errors" + "net" + "pro2d/common" + "pro2d/utils/logger" + "time" +) + + +type Connection struct { + IConnection + net.Conn + Server IServer + Id int + + scanner *bufio.Scanner + writer *bufio.Writer + WBuffer chan []byte + Quit chan *Connection + + messageCallback MessageCallback + connectionCallback ConnectionCallback + closeCallback CloseCallback + timerCallback TimerCallback +} + +func NewConn(id int, conn net.Conn, s IServer) *Connection { + return &Connection{ + Id: id, + Conn: conn, + Server: s, + + scanner: bufio.NewScanner(conn), + writer: bufio.NewWriter(conn), + WBuffer: make(chan []byte, common.MaxMsgChan), + Quit: make(chan *Connection), + } +} + +func (c *Connection) GetID() int { + return c.Id +} + +func (c *Connection) SetConnectionCallback(cb ConnectionCallback) { + c.connectionCallback = cb +} + +func (c *Connection) SetMessageCallback(cb MessageCallback) { + c.messageCallback = cb +} + +func (c *Connection) SetCloseCallback(cb CloseCallback) { + c.closeCallback = cb +} + +func (c *Connection) write() { + defer c.Stop() + + for msg := range c.WBuffer { + n, err := c.writer.Write(msg) + if err != nil{ + logger.Error("write fail err: " + err.Error(), "n: ", n) + return + } + if err := c.writer.Flush(); err != nil { + logger.Error("write Flush fail err: " + err.Error()) + return + } + } +} + +func (c *Connection) read() { + defer c.Stop() + c.scanner.Split(c.Server.GetSplitter().ParseMsg) + + for c.scanner.Scan() { + req, err := c.Server.GetSplitter().UnPack(c.scanner.Bytes()) + if err != nil { + return + } + + req.SetSessId(c.Id) + c.messageCallback(req) + } + + if err := c.scanner.Err(); err != nil { + logger.Error("scanner.err: %s", err.Error()) + return + } +} + +func (c *Connection) Start() { + c.connectionCallback(c) + go c.write() + go c.read() +} + +func (c *Connection) Stop() { + logger.Debug("ID: %d close", c.Id) + c.Conn.Close() + c.closeCallback(c) +} + +func (c *Connection) Send(errCode int32, cmd uint32, data []byte) error{ + buf, err := c.Server.GetSplitter().Pack(cmd, data, errCode, 0) + if err != nil { + return err + } + + sendTimeout := time.NewTimer(5 * time.Millisecond) + defer sendTimeout.Stop() + // 发送超时 + select { + case <-sendTimeout.C: + return errors.New("send buff msg timeout") + case c.WBuffer <- buf: + return nil + } +} diff --git a/common/components/http.go b/common/components/http.go new file mode 100644 index 0000000..9fe119a --- /dev/null +++ b/common/components/http.go @@ -0,0 +1,62 @@ +package components + +import ( + "github.com/gin-gonic/gin" + "net/http" + "reflect" + "strings" +) + +type HttpServer struct { + IHttp + version string + port []string + Handler interface{} +} + +func Pong (c *gin.Context) { + c.JSON(200, gin.H{ + "message": "pong", + }) +} + +func NewHttpServer(version string, port ...string) *HttpServer { + return &HttpServer{version: version, port: port} +} + +func GetRoutePath(objName, objFunc string) string { + return strings.ToLower(objName + "/" + objFunc) +} + +func (h *HttpServer)HandlerFuncObj(tvl, obj reflect.Value) gin.HandlerFunc { + return func(c *gin.Context) { + v := tvl.Call([]reflect.Value{obj, reflect.ValueOf(c)}) + if len(v) != 2 { + c.JSON(http.StatusNotFound, gin.H{"code": -100, "data": ""}) + return + } + c.JSON(http.StatusOK, gin.H{"code": v[0].Interface(), "data": v[1].Interface()}) + } +} + +func (h *HttpServer) BindHandler(handler interface{}) { + h.Handler = handler +} + +func (h *HttpServer) Start() error { + //gin初始化 + r := gin.Default() + r.GET("/ping", Pong) + typ := reflect.TypeOf(h.Handler) + val := reflect.ValueOf(h.Handler) + //t := reflect.Indirect(val).Type() + //objectName := t.Name() + + numOfMethod := val.NumMethod() + for i := 0; i < numOfMethod; i++ { + method := typ.Method(i) + r.GET(GetRoutePath(h.version, method.Name), h.HandlerFuncObj(method.Func, val)) + r.POST(GetRoutePath(h.version, method.Name), h.HandlerFuncObj(method.Func, val)) + } + return r.Run(h.port...) // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080") +} \ No newline at end of file diff --git a/common/components/icompontents.go b/common/components/icompontents.go new file mode 100644 index 0000000..c66dcc3 --- /dev/null +++ b/common/components/icompontents.go @@ -0,0 +1,68 @@ +package components + +//网络包头 +type IHead interface { + GetDataLen() uint32 //获取消息数据段长度 + GetMsgID() uint32 //获取消息ID + GetErrCode() int32 //获取消息错误码 + GetPreserve() uint32 //获取预留数据 +} + +//网络包 +type IMessage interface { + IHead + GetHeader() IHead //获取消息头 + SetHeader(header IHead) //设置消息头 + + GetData() []byte //获取消息内容 + SetData([]byte) //设置消息内容 + + SetSessId(int) //设置连接id + GetSessId() int //获取连接id +} + +//网络拆包解包器 +type ISplitter interface { + UnPack([]byte) (IMessage, error) + Pack(cmd uint32, data []byte, errcode int32, preserve uint32) ([]byte, error) + ParseMsg (data []byte, atEOF bool) (advance int, token []byte, err error) + GetHeadLen() uint32 +} + +//链接 +type IConnection interface { + GetID() int + Start() + Stop() + Send(code int32, cmd uint32, b []byte) error + + SetConnectionCallback(ConnectionCallback) + SetMessageCallback(MessageCallback) + SetCloseCallback(CloseCallback) +} + +type ConnectionCallback func(IConnection) +type CloseCallback func(IConnection) +type MessageCallback func(IMessage) +type TimerCallback func(IConnection) + +//server +type IServer interface { + Start() error + Stop() + + GetSplitter() ISplitter + GetIConnection(id int) IConnection + + SetConnectionCallback(ConnectionCallback) + SetMessageCallback(MessageCallback) + SetCloseCallback(CloseCallback) + SetTimerCallback(TimerCallback) +} + +//httpserver +type IHttp interface { + Start() error + Stop() + BindHandler(interface{}) +} \ No newline at end of file diff --git a/common/components/pbsplitter.go b/common/components/pbsplitter.go new file mode 100644 index 0000000..50d316d --- /dev/null +++ b/common/components/pbsplitter.go @@ -0,0 +1,134 @@ +package components + +import ( + "bytes" + "encoding/binary" + "fmt" + "pro2d/common" +) + +type PBHead struct { + Length uint32 + Cmd uint32 + ErrCode int32 + PreField uint32 +} + +func (h *PBHead) GetDataLen() uint32 { + return h.Length +} + +func (h *PBHead) GetMsgID() uint32 { + return h.Cmd +} + +func (h *PBHead) GetErrCode() int32 { + return h.ErrCode +} + +func (h *PBHead) GetPreserve() uint32 { + return h.PreField +} + +type PBMessage struct { + IMessage + Head IHead + Body []byte + + SessionID int +} + + +func (m *PBMessage) GetHeader() IHead { + return m.Head +} + +func (m *PBMessage) SetHeader(header IHead) { + m.Head = header +} +func (m *PBMessage) GetData() []byte { + return m.Body +} + +func (m *PBMessage) SetData(b []byte) { + m.Body = b +} + +func (m *PBMessage) SetSessId(id int) { + m.SessionID = id +} + +func (m *PBMessage) GetSessId() int { + return m.SessionID +} + + +type PBSplitter struct {} + +func NewPBSplitter() *PBSplitter { + return &PBSplitter{} +} + +func (m *PBSplitter) GetHeadLen() uint32 { + return uint32(binary.Size(PBHead{})) +} + +func (m *PBSplitter) UnPack(data []byte) (IMessage,error) { + h := &PBHead{} + err := binary.Read(bytes.NewReader(data), binary.BigEndian, h) + if err != nil { + return nil, err + } + + return &PBMessage{ + Head: h, + Body: data[m.GetHeadLen():], + },nil +} + +func (m *PBSplitter) ParseMsg (data []byte, atEOF bool) (advance int, token []byte, err error) { + // 表示我们已经扫描到结尾了 + if atEOF && len(data) == 0 { + return 0, nil, nil + } + if !atEOF && len(data) >= int(m.GetHeadLen()) { //4字节数据包长度 4字节指令 + length := int32(0) + binary.Read(bytes.NewReader(data[0:4]), binary.BigEndian, &length) + if length <= 0 { + return 0, nil, fmt.Errorf("length is 0") + } + + if length > common.MaxPacketLength { + return 0, nil, fmt.Errorf("length exceeds maximum length") + } + if int(length) <= len(data) { + return int(length) , data[:int(length)], nil + } + return 0 , nil, nil + } + if atEOF { + return len(data), data, nil + } + return 0, nil, nil +} + +func (m *PBSplitter) Pack(cmd uint32, data []byte, errcode int32, preserve uint32) ([]byte, error) { + buf := &bytes.Buffer{} + h := &PBHead{ + Length: m.GetHeadLen()+ uint32(len(data)), + Cmd: cmd, + ErrCode: errcode, + PreField: preserve, + } + err := binary.Write(buf, binary.BigEndian, h) + if err != nil { + return nil, err + } + + err = binary.Write(buf, binary.BigEndian, data) + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} \ No newline at end of file diff --git a/common/components/server.go b/common/components/server.go new file mode 100644 index 0000000..8cdb3d5 --- /dev/null +++ b/common/components/server.go @@ -0,0 +1,126 @@ +package components + +import ( + "fmt" + "github.com/golang/protobuf/proto" + "net" + "plugin" + "pro2d/pb" + "pro2d/utils/logger" + "sync" +) + +type ActionHandler func (msg IMessage) (int32, proto.Message) +var ActionMap map[pb.ProtoCode]ActionHandler + +type Server struct { + IServer + + connectionCallback ConnectionCallback + messageCallback MessageCallback + closeCallback CloseCallback + + splitter ISplitter + + port int + PluginPath string + Clients *sync.Map +} + +func NewServer(port int, pluginPath string, splitter ISplitter) *Server { + s := &Server{ + splitter: splitter, + port: port, + PluginPath: pluginPath, + Clients: new(sync.Map), + } + return s +} + +func (s *Server) GetSplitter() ISplitter { + return s.splitter +} + +func (s *Server) GetIConnection(id int) IConnection { + c, ok := s.Clients.Load(id) + if !ok { + return nil + } + return c.(IConnection) +} + +func (s *Server) SetConnectionCallback(cb ConnectionCallback) { + s.connectionCallback = cb +} + +func (s *Server) SetMessageCallback(cb MessageCallback) { + s.messageCallback = cb +} + +func (s *Server) SetCloseCallback(cb CloseCallback) { + s.closeCallback = cb +} + +func (s *Server) SetTimerCallback(cb TimerCallback) { +} + +func (s *Server) newConnection(conn IConnection) { + s.Clients.Store(conn.GetID(), conn) + + conn.SetConnectionCallback(s.connectionCallback) + conn.SetCloseCallback(s.removeConnection) + conn.SetMessageCallback(s.messageCallback) + + go conn.Start() +} + +func (s *Server) removeConnection(conn IConnection) { + s.closeCallback(conn) + s.Clients.Delete(conn.GetID()) +} + +func (s *Server) LoadPlugin() { + //重新加载 + _, err:=plugin.Open(s.PluginPath) + if err != nil { + logger.Error("load plugin err: %v, %s", err, s.PluginPath) + return + } + logger.Debug("load plugin success") +} + + +func (s *Server) Start() error { + //初始化plugin + //_, err = plugin.Open(conf.GlobalConf.GameConf.PluginPath) + //if err != nil { + // return err + //} + port := fmt.Sprintf(":%d", s.port) + l, err := net.Listen("tcp", port) + if err != nil { + return err + } + //监听端口 + logger.Debug("listen on %s\n", port) + id := 0 + for { + conn, err := l.Accept() + if err != nil { + return err + } + + id++ + client := NewConn(id, conn, s) + s.newConnection(client) + } +} + +func (s *Server)Stop() { + StopTimer() + s.Clients.Range(func(key, value interface{}) bool { + client := value.(IConnection) + client.Stop() + return true + }) +} diff --git a/common/components/timerwheel.go b/common/components/timerwheel.go new file mode 100644 index 0000000..6286d23 --- /dev/null +++ b/common/components/timerwheel.go @@ -0,0 +1,203 @@ +package components + +import ( + "container/list" + "pro2d/common" + "sync" + "sync/atomic" + "time" +) + +//skynet的时间轮 + 协程池 +const ( + TimeNearShift = 8 + TimeNear = 1 << TimeNearShift + TimeLevelShift = 6 + TimeLevel = 1 << TimeLevelShift + TimeNearMask = TimeNear - 1 + TimeLevelMask = TimeLevel - 1 +) + +type bucket struct { + expiration int32 + timers *list.List + + mu sync.Mutex +} + +func newBucket() *bucket { + return &bucket{ + expiration: -1, + timers: list.New(), + mu: sync.Mutex{}, + } +} + +func (b*bucket) Add(t *timer) { + b.mu.Lock() + defer b.mu.Unlock() + + b.timers.PushBack(t) +} + +func (b*bucket) Flush(reinsert func(t *timer)) { + b.mu.Lock() + defer b.mu.Unlock() + + for e := b.timers.Front(); e != nil; { + next := e.Next() + reinsert(e.Value.(*timer)) + + b.timers.Remove(e) + e = next + } +} + +type timer struct { + expiration uint32 + f func() +} + +var TimingWheel *TimeWheel + +func init() { + TimingWheel = NewTimeWheel() + TimingWheel.Start() +} + +type TimeWheel struct { + tick time.Duration + ticker *time.Ticker + near [TimeNear]*bucket + t [4][TimeLevel]*bucket + time uint32 + + WorkPool *WorkPool + exit chan struct{} +} + +func NewTimeWheel() *TimeWheel { + tw := &TimeWheel{ + tick: 10*time.Millisecond, + time: 0, + WorkPool: NewWorkPool(common.WorkerPoolSize, common.MaxTaskPerWorker), + exit: make(chan struct{}), + } + for i :=0; i < TimeNear; i++ { + tw.near[i] = newBucket() + } + + for i :=0; i < 4; i++ { + for j :=0; j < TimeLevel; j++ { + tw.t[i][j] = newBucket() + } + } + return tw +} + +func (tw *TimeWheel) add(t *timer) bool { + time := t.expiration + currentTime := atomic.LoadUint32(&tw.time) + if time <= currentTime { + return false + } + + if (time | TimeNearMask) == (currentTime | TimeNearMask) { + tw.near[time&TimeNearMask].Add(t) + }else { + i := 0 + mask := TimeNear << TimeNearShift + for i=0; i < 3; i ++ { + if (time | uint32(mask - 1)) == (currentTime | uint32(mask - 1)) { + break + } + mask <<= TimeLevelShift + } + + tw.t[i][((time>>(TimeNearShift + i*TimeLevelShift)) & TimeLevelMask)].Add(t) + } + return true +} + +func (tw *TimeWheel) addOrRun(t *timer) { + if !tw.add(t) { + workerID := int64(t.expiration) % tw.WorkPool.WorkerPoolSize + //将请求消息发送给任务队列 + tw.WorkPool.TaskQueue[workerID] <- t.f + } +} + +func (tw *TimeWheel) moveList(level, idx int) { + current := tw.t[level][idx] + current.Flush(tw.addOrRun) +} + +func (tw *TimeWheel) shift() { + mask := TimeNear + ct := atomic.AddUint32(&tw.time, 1) + if ct == 0 { + tw.moveList(3, 0) + }else { + time := ct >> TimeNearShift + + i := 0 + for (ct & uint32(mask-1)) == 0{ + idx := time & TimeLevelMask + if idx != 0 { + tw.moveList(i, int(idx)) + break + } + + mask <<= TimeLevelShift + time >>= TimeLevelShift + i++ + } + } +} + +func (tw *TimeWheel) execute() { + idx := tw.time & TimeNearMask + tw.near[idx].Flush(tw.addOrRun) +} + +func (tw *TimeWheel) update() { + tw.execute() + tw.shift() + tw.execute() +} + +func (tw *TimeWheel) Start() { + tw.ticker = time.NewTicker(tw.tick) + tw.WorkPool.StartWorkerPool() + + go func() { + for { + select { + case <- tw.ticker.C: + tw.update() + case <- tw.exit: + return + } + } + }() +} + +func (tw *TimeWheel) Stop() { + close(tw.exit) +} + +func (tw *TimeWheel) afterFunc(expiration time.Duration, f func()) { + time := atomic.LoadUint32(&tw.time) + tw.addOrRun(&timer{ + expiration: uint32(expiration / tw.tick) + time, + f: f, + }) +} + +func TimeOut(expire time.Duration, f func()) { + TimingWheel.afterFunc(expire, f) +} + +func StopTimer() { + TimingWheel.Stop() +} \ No newline at end of file diff --git a/common/components/timewheel_test.go b/common/components/timewheel_test.go new file mode 100644 index 0000000..aafe050 --- /dev/null +++ b/common/components/timewheel_test.go @@ -0,0 +1,18 @@ +package components + +import ( + "fmt" + "testing" + "time" +) + +func PRINT() { + fmt.Println("12312312312") +} + +func TestTimeWheel_Start(t *testing.T) { + TimeOut(1 * time.Second, func() { + fmt.Println("12312313123") + }) + select{} +} diff --git a/common/components/workpool.go b/common/components/workpool.go new file mode 100644 index 0000000..1bd8acf --- /dev/null +++ b/common/components/workpool.go @@ -0,0 +1,41 @@ +package components + +type Job func() + +type WorkPool struct { + WorkerPoolSize int64 + MaxTaskPerWorker int64 + TaskQueue []chan Job +} + +func NewWorkPool(poolSize, maxTaskSize int64) *WorkPool { + return &WorkPool{ + WorkerPoolSize: poolSize, + MaxTaskPerWorker: maxTaskSize, + TaskQueue: make([]chan Job, poolSize), + } +} + +//StartOneWorker 启动一个Worker工作流程 +func (wp *WorkPool) StartOneWorker(workerID int, taskQueue chan Job) { + //不断的等待队列中的消息 + for { + select { + //有消息则取出队列的Request,并执行绑定的业务方法 + case job := <-taskQueue: + _ = workerID + job() + } + } +} + +func (wp *WorkPool) StartWorkerPool() { + //遍历需要启动worker的数量,依此启动 + for i := 0; i < int(wp.WorkerPoolSize); i++ { + //一个worker被启动 + //给当前worker对应的任务队列开辟空间 + wp.TaskQueue[i] = make(chan Job, wp.MaxTaskPerWorker) + //启动当前Worker,阻塞的等待对应的任务队列是否有消息传递进来 + go wp.StartOneWorker(i, wp.TaskQueue[i]) + } +} \ No newline at end of file diff --git a/common/const.go b/common/const.go new file mode 100644 index 0000000..1d9a08f --- /dev/null +++ b/common/const.go @@ -0,0 +1,25 @@ +package common + +const ( + //协程池 大小 + WorkerPoolSize = 10 + MaxTaskPerWorker = 100 + + //最大包大 + MaxPacketLength = 10 * 1024 * 1024 + MaxMsgChan = 100 + + //jwt + Pro2DTokenSignedString = "Pro2DSecret" + + //定时器 + TickMS = 10 + WheelSize = 3600 + + //心跳 + HeartTimerInterval = 5 //s + HeartTimeoutCountMax = 20 //最大超时次数 + + //保存数据时间 + SaveDataInterval = 5 //s +) diff --git a/conf/conf.go b/conf/conf.go index c3bbfea..badc322 100644 --- a/conf/conf.go +++ b/conf/conf.go @@ -5,9 +5,9 @@ import ( "fmt" "gopkg.in/yaml.v3" "io/ioutil" - "pro2d/src/components/db" - "pro2d/src/components/logger" - "pro2d/src/utils" + "pro2d/utils" + "pro2d/utils/db" + "pro2d/utils/logger" "strings" ) diff --git a/conf/conf.yaml b/conf/conf.yaml index 9e3562d..5f55514 100644 --- a/conf/conf.yaml +++ b/conf/conf.yaml @@ -28,7 +28,7 @@ server_game: id: "1" name: "game" ip: "192.168.0.206" - port: 8849 + port: 8850 dbname: "game" pool_size: 1 plugin_path: "./bin/plugin.so" diff --git a/models/account.go b/models/account.go new file mode 100644 index 0000000..2498b5e --- /dev/null +++ b/models/account.go @@ -0,0 +1,31 @@ +package models + +import ( + "pro2d/pb" + "pro2d/utils/db" +) + +type AccountModel struct { + *db.Schema + *pb.Account +} + +func AccountExistByPhone(phone string) (bool, *AccountModel){ + m := NewAccount(phone) + if err := m.Load(); err != nil { + return false, m + } + return true, m +} + +func NewAccount(phone string) *AccountModel { + ac := &pb.Account{ + Phone: phone, + } + account := &AccountModel{ + Schema: db.NewSchema(phone, ac), + Account: ac, + } + + return account +} \ No newline at end of file diff --git a/models/equip.go b/models/equip.go new file mode 100644 index 0000000..bab4782 --- /dev/null +++ b/models/equip.go @@ -0,0 +1,23 @@ +package models + +import ( + "pro2d/pb" + "pro2d/utils/db" +) + +type EquipModels struct { + *db.Schema + Equip *pb.Equipment +} + +func NewEquip(id string) *EquipModels { + data := &pb.Equipment{ + Id: id, + } + m := &EquipModels{ + Schema: db.NewSchema(id, data), + Equip: data, + } + + return m +} \ No newline at end of file diff --git a/models/hero.go b/models/hero.go new file mode 100644 index 0000000..8c341c7 --- /dev/null +++ b/models/hero.go @@ -0,0 +1,31 @@ +package models + +import ( + "pro2d/pb" + "pro2d/utils/db" +) + +type HeroModel struct { + *db.Schema + Hero *pb.Hero +} +type HeroMap map[string]*HeroModel + +func GetHeros(hm HeroMap) map[string]*pb.Hero { + h := make(map[string]*pb.Hero) + for k, v := range hm { + h[k] = v.Hero + } + return h +} + +func NewHero(id string) *HeroModel { + h := &pb.Hero{ + Id: id, + } + m := &HeroModel{ + Schema: db.NewSchema(id, h), + Hero: h, + } + return m +} diff --git a/models/init.go b/models/init.go new file mode 100644 index 0000000..271e22c --- /dev/null +++ b/models/init.go @@ -0,0 +1,40 @@ +package models + +import ( + "pro2d/pb" + "pro2d/utils" + "pro2d/utils/db" + "pro2d/utils/logger" +) + +func InitDoc(schema ...interface{}) { + for _, s := range schema { + coll, keys := utils.FindIndex(s) + for _, index := range keys { + db.CreateCollection(coll) + + logger.Debug("InitDoc collect: %v, createIndex: %s", coll, index) + res, err := db.SetUnique(coll, index) + if err != nil { + logger.Error("InitDoc unique: %s, err: %v", res, err) + continue + } + } + } +} + +func InitAccountServerModels() { + var schema []interface{} = []interface{}{ + pb.Account{}, + } + InitDoc(schema...) +} + +func InitGameServerModels() { + var schema []interface{} = []interface{}{ + pb.Hero{}, + pb.Role{}, + pb.Team{}, + } + InitDoc(schema...) +} \ No newline at end of file diff --git a/models/init_test.go b/models/init_test.go new file mode 100644 index 0000000..ed02802 --- /dev/null +++ b/models/init_test.go @@ -0,0 +1,20 @@ +package models + +import ( + "context" + _ "pro2d/conf" + "pro2d/utils/db" + "testing" +) + + +func TestInitModels(t *testing.T) { + + db.MongoDatabase = db.MongoClient.Database("account") + InitAccountServerModels() + db.MongoClient.Disconnect(context.TODO()) + + db.MongoDatabase = db.MongoClient.Database("game") + InitGameServerModels() + db.MongoClient.Disconnect(context.TODO()) +} \ No newline at end of file diff --git a/models/prop.go b/models/prop.go new file mode 100644 index 0000000..4ffb68d --- /dev/null +++ b/models/prop.go @@ -0,0 +1,23 @@ +package models + +import ( + "pro2d/pb" + "pro2d/utils/db" +) + +type PropModels struct { + *db.Schema + Prop *pb.Prop +} + +func NewProp(id string) *PropModels { + data := &pb.Prop{ + Id: id, + } + m := &PropModels{ + Schema: db.NewSchema(id, data), + Prop: data, + } + + return m +} \ No newline at end of file diff --git a/models/role.go b/models/role.go new file mode 100644 index 0000000..6fe5f4f --- /dev/null +++ b/models/role.go @@ -0,0 +1,129 @@ +package models + +import ( + "fmt" + "pro2d/common" + "pro2d/pb" + "pro2d/utils" + "pro2d/utils/db" + "pro2d/utils/logger" + "sync/atomic" +) + +type RoleModel struct { + *db.Schema + Role *pb.Role + Heros HeroMap + Teams *TeamModel + Equip *EquipModels + Prop *PropModels + + lastSaveTs int64 +} + +func RoleExistByUid(uid string) *RoleModel { + data := &pb.Role{Uid: uid} + + if err := db.FindOne(db.GetBsonM("uid", uid), data); err != nil { + logger.Error("Role exist err: %v", err) + return nil + } + + + r := &RoleModel{ + Schema: db.NewSchema(data.Id, data), + Role: data, + Heros: make(HeroMap), + Teams: new(TeamModel), + Equip: new(EquipModels), + Prop: new(PropModels), + } + r.LoadAll() + return r +} + +func NewRole(id string) *RoleModel { + data := &pb.Role{Id: id} + m := &RoleModel{ + Schema: db.NewSchema(id, data), + Role: data, + Heros: make(HeroMap), + Teams: new(TeamModel), + Equip: new(EquipModels), + Prop: new(PropModels), + } + return m +} + +func (m *RoleModel) LoadHero() { + m.Heros["test"] = NewHero("") + m.Heros["test"].Hero = &pb.Hero{ + Id: "1", + RoleId: m.Role.Id, + Type: 1, + Level: 1, + ReinCount: 0, + ReinPoint: 0, + Equipments: "123123", + } +} + +func (m *RoleModel) LoadTeams() { + m.Teams = NewTeam("0") + m.Teams.Team = &pb.Team{ + Id: "1", + HeroIds: "1", + } +} + +func (m *RoleModel) LoadEquips() { + m.Equip = NewEquip("0") + m.Equip.Equip = &pb.Equipment{ + Id: "0", + RoleId: m.Role.Id, + Type: 0, + Equip: false, + EnhanceLevel: false, + } +} + +func (m *RoleModel) LoadAll() { + m.LoadHero() + m.LoadTeams() + m.LoadEquips() +} + +func (m *RoleModel) updateProperty(property map[string]interface{}) { +} + +func (m *RoleModel) AddHero(hero *pb.Hero) { + h := NewHero(hero.Id) + h.Hero = hero + h.Create() + m.Heros[fmt.Sprintf("%s%s", m.Role.Id, h.Hero.Id)] = h +} + +func (m *RoleModel) GetAllHero() []*pb.Hero { + var h []*pb.Hero + for _, hero := range m.Heros { + h = append(h, hero.Hero) + } + return h +} + +func (m *RoleModel) OnRecoverTimer(now int64) { + m.saveRoleData(now) +} + +func (m *RoleModel) OnOfflineEvent() { + // 设置最新的登录时间 + m.saveRoleData(utils.Timex()) +} + +func (m *RoleModel) saveRoleData(now int64) { + if now - m.lastSaveTs < common.SaveDataInterval { + return + } + atomic.StoreInt64(&m.lastSaveTs, now) + m.Update() +} \ No newline at end of file diff --git a/models/role_test.go b/models/role_test.go new file mode 100644 index 0000000..c00e43a --- /dev/null +++ b/models/role_test.go @@ -0,0 +1,48 @@ +package models + +import ( + "fmt" + _ "pro2d/conf" + "pro2d/pb" + "pro2d/utils" + "pro2d/utils/db" + "pro2d/utils/logger" + "testing" +) + +func TestNewRole(t *testing.T) { + db.MongoDatabase = db.MongoClient.Database("game") + + var uid = "141815055745814528" + role := RoleExistByUid(uid) + if role != nil { + //uid存在 , 更新角色 + //role.AddHero(&pb.Hero{ + // Id: 1, + // RoleId: role.Role.Id, + // Type: 0, + // Level: 0, + // ReinCount: 0, + // ReinPoint: 0, + // Equipments: "", + //}) + role.SetProperty("Device", "1111") + //role.Save() + }else { + //uid不存在,创建角色 + role = NewRole("1") + role.Role.Uid = uid + role.Role.Device = "111111" + role.Role.Level = 0 + err := role.Create() + fmt.Println(err) + } + print(role) +} + +func TestRoleIndex(t *testing.T) { + coll, keys := utils.FindIndex(pb.Role{}) + for _, index := range keys { + logger.Debug("coll: %s, key: %s", coll, index) + } +} \ No newline at end of file diff --git a/models/team.go b/models/team.go new file mode 100644 index 0000000..230ec98 --- /dev/null +++ b/models/team.go @@ -0,0 +1,23 @@ +package models + +import ( + "pro2d/pb" + "pro2d/utils/db" +) + +type TeamModel struct { + *db.Schema + Team *pb.Team +} + +func NewTeam(id string) *TeamModel { + data := &pb.Team{ + Id: id, + } + m := &TeamModel{ + Schema: db.NewSchema(id, data), + Team: data, + } + + return m +} \ No newline at end of file diff --git a/plugin/RolePlugin.go b/plugin/RolePlugin.go new file mode 100644 index 0000000..3b5004e --- /dev/null +++ b/plugin/RolePlugin.go @@ -0,0 +1,58 @@ +package plugin + +import ( + "github.com/golang/protobuf/proto" + "pro2d/common/components" + "pro2d/conf" + "pro2d/models" + "pro2d/pb" + "pro2d/utils/logger" +) + +func HeartRpc(msg components.IMessage) (int32, proto.Message) { + //msg.Conn.SetLastHeartCheckTime() + return 0, nil +} + +func CreateRpc(msg components.IMessage) (int32, proto.Message) { + req := pb.CreateReq{} + if err := proto.Unmarshal(msg.GetData(), &req); err != nil { + logger.Error("CreateRpc err: %v", err) + return 1, nil + } + role := models.RoleExistByUid(req.Uid) + if role != nil { + return 2, nil + } + + roleId := conf.SnowFlack.NextValStr() + role = models.NewRole(roleId) + if err := role.Create(); err != nil { + logger.Error("CreateRpc role create err: %v", err) + return 3, nil + } + return 0, nil +} + +func LoginRpc(msg components.IMessage) (int32, proto.Message) { + //logger.Debug("cmd: %v, msg: %s", msg.PBHead.Cmd, msg.Body) + req := pb.LoginReq{} + if err := proto.Unmarshal(msg.GetData(), &req); err != nil { + logger.Error("loginRpc err: %v", err) + return 1, nil + } + + role := models.RoleExistByUid(req.Uid) + if role == nil { + return 2, nil + } + role.SetProperty("Device", req.Device) + + + return 0, &pb.RoleRsp{ + Role: role.Role, + Hero: role.GetAllHero(), + Team: role.Teams.Team, + Equips: []*pb.Equipment{role.Equip.Equip}, + } +} \ No newline at end of file diff --git a/plugin/protocode.go b/plugin/protocode.go new file mode 100644 index 0000000..dc1410b --- /dev/null +++ b/plugin/protocode.go @@ -0,0 +1,17 @@ +package plugin + +import ( + "pro2d/common/components" + "pro2d/pb" + "pro2d/utils/logger" +) + +func init() { + logger.Debug("init protocode...") + components.ActionMap = make(map[pb.ProtoCode]components.ActionHandler) + + components.ActionMap[pb.ProtoCode_HeartReq] = HeartRpc + components.ActionMap[pb.ProtoCode_LoginReq] = LoginRpc + components.ActionMap[pb.ProtoCode_CreateReq] = CreateRpc + +} diff --git a/src/actions/AccountAction.go b/src/actions/AccountAction.go deleted file mode 100644 index 482d659..0000000 --- a/src/actions/AccountAction.go +++ /dev/null @@ -1,63 +0,0 @@ -package actions - -import ( - "github.com/gin-gonic/gin" - "pro2d/conf" - "pro2d/pb" - "pro2d/src/components/net" - "pro2d/src/models" - "pro2d/src/utils" -) - -type AccountAction struct { - HttpServer *net.HttpServer -} - -func (h *AccountAction) Register(c *gin.Context) (int, interface{}){ - var register pb.Register - if err := c.ShouldBindJSON(®ister); err != nil { - return -1, err.Error() - } - - account := models.NewAccount(register.Phone) - if err := account.Load(); err == nil { - return -2 , "account exists: " + register.Phone - } - - account.Uid = conf.SnowFlack.NextValStr() - account.Password = utils.Md5V(register.Password) - if err := account.Create(); err != nil{ - return -3, "account register err: " + err.Error() - } - account.Password = register.Password - return 0, account.Account -} - -func (h *AccountAction) Login(c *gin.Context) (int,interface{}) { - var login pb.Account - if err := c.ShouldBindJSON(&login); err != nil { - return -1, err.Error() - } - account := models.NewAccount(login.Phone) - if err := account.Load(); err != nil { - return -2, err.Error() - } - - if utils.Md5V(login.Password) != account.Password { - return -3, "password error" - } - - var gs []*pb.ServiceInfo - for k, v := range h.HttpServer.EtcdClient.GetByPrefix(conf.GlobalConf.GameConf.Name) { - gs = append(gs, &pb.ServiceInfo{ - Id: k, - Name: conf.GlobalConf.GameConf.Name, - Address: v, - }) - } - rsp := &pb.LoginRsp{ - Uid: account.Uid, - GameService: gs, - } - return 0, rsp -} diff --git a/src/common/common.go b/src/common/common.go deleted file mode 100644 index 33ca56e..0000000 --- a/src/common/common.go +++ /dev/null @@ -1,24 +0,0 @@ -package common - -const ( - //协程池 大小 - WorkerPoolSize = 10 - MaxTaskPerWorker = 100 - - //包头 - HEADLEN = 16 - - //jwt - Pro2DTokenSignedString = "Pro2DSecret" - - //定时器 - TickMS = 10 - WheelSize = 3600 - - //心跳 - HeartTimerInterval = 5 //s - HeartTimeoutCountMax = 20 //最大超时次数 - - //保存数据时间剑客 - SaveDataInterval = 5 //s -) diff --git a/src/components/db/mongo.go b/src/components/db/mongo.go deleted file mode 100644 index cae34e7..0000000 --- a/src/components/db/mongo.go +++ /dev/null @@ -1,224 +0,0 @@ -package db - -import ( - "context" - "fmt" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" - "go.mongodb.org/mongo-driver/mongo/readpref" - "go.mongodb.org/mongo-driver/x/bsonx" - "pro2d/src/utils" - "sort" - "strconv" - "strings" - "time" -) - -var ( - MongoClient *mongo.Client - MongoDatabase *mongo.Database -) - -//初始化 -func Connect(user, password, host string,port int, MaxNum int, timeOut int) error { - var uri string - if user!= "" { - //uri = fmt.Sprintf("mongodb://%s:%s@%s:%d/%s?w=majority", conf.User, conf.Password, conf.Host, conf.Port, conf.DBName) - uri = fmt.Sprintf("mongodb://%s:%s@%s:%d/?w=majority", user, password, host, port) - }else { - //uri = fmt.Sprintf("mongodb://%s:%d/%s?w=majority", conf.Host, conf.Port, conf.DBName) - uri = fmt.Sprintf("mongodb://%s:%d/?w=majority", host, port) - } - // 设置连接超时时间 - ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeOut)) - defer cancel() - // 通过传进来的uri连接相关的配置 - o := options.Client().ApplyURI(uri) - // 设置最大连接数 - 默认是100 ,不设置就是最大 max 64 - o.SetMaxPoolSize(uint64(MaxNum)) - // 发起链接 - var err error - MongoClient, err = mongo.Connect(ctx, o) - if err != nil { - return err - } - // 判断服务是不是可用 - if err = MongoClient.Ping(context.Background(), readpref.Primary()); err != nil { - return err - } - - //MongoDatabase = MongoClient.Database(dbname) - return nil -} - -func CreateCollection(collection string) error { - colls, _ := MongoDatabase.ListCollectionNames(context.TODO(), bson.D{}) - pos := sort.SearchStrings(colls, collection) - if pos != len(colls) { - if collection == colls[pos] { - return MongoDatabase.CreateCollection(context.TODO(), collection) - } - } - return MongoDatabase.CreateCollection(context.TODO(), collection) -} - -func SetUnique(collection string, key string) (string, error) { - return MongoDatabase.Collection(collection).Indexes().CreateOne( - context.TODO(), - mongo.IndexModel{ - Keys : bsonx.Doc{{key, bsonx.Int32(1)}}, - Options: options.Index().SetUnique(true), - }, - ) -} - -type MgoColl struct { - collection *mongo.Collection - - schema *Schema -} - -func GetBsonD(key string, value interface{}) interface{} { - return bson.D{ {key, value}} -} -func GetBsonM(key string, value interface{}) interface{} { - return bson.M{key: value} -} - -func NewMongoColl(schema *Schema) *MgoColl { - return &MgoColl{ - collection: MongoDatabase.Collection(schema.GetCollName()), - schema: schema, - } -} - -func FindOne(pri interface{}, schema interface{}) error { - r := MongoDatabase.Collection(utils.GetCollName(schema)).FindOne(context.TODO(), pri) - return r.Decode(schema) -} - -// 查询单个 -func (m *MgoColl) FindOneKV(key string, value interface{}) *mongo.SingleResult { - //collection. - filter := bson.D{ {key, value}} - singleResult := m.collection.FindOne(context.TODO(), filter) - return singleResult -} - -//查询集合里有多少数据 -func (m *MgoColl) CollectionCount() (string, int64) { - size, _ := m.collection.EstimatedDocumentCount(context.TODO()) - return m.collection.Name(), size -} - -//按选项查询集合 Skip 跳过 Limit 读取数量 sort 1 ,-1 . 1 为最初时间读取 , -1 为最新时间读取 -func (m *MgoColl) CollectionDocuments(Skip, Limit int64, sort int) *mongo.Cursor { - SORT := bson.D{ - {"_id", sort}} //filter := bson.D{ {key,value}} - filter := bson.D{ - {}} - findOptions := options.Find().SetSort(SORT).SetLimit(Limit).SetSkip(Skip) - //findOptions.SetLimit(i) - temp, _ := m.collection.Find(context.Background(), filter, findOptions) - return temp -} - -//获取集合创建时间和编号 -func (m *MgoColl) ParsingId(result string) (time.Time, uint64) { - temp1 := result[:8] - timestamp, _ := strconv.ParseInt(temp1, 16, 64) - dateTime := time.Unix(timestamp, 0) //这是截获情报时间 时间格式 2019-04-24 09:23:39 +0800 CST - temp2 := result[18:] - count, _ := strconv.ParseUint(temp2, 16, 64) //截获情报的编号 - return dateTime, count -} - -//删除文章和查询文章 -func (m *MgoColl) DeleteAndFind(key string, value interface{}) (int64, *mongo.SingleResult) { - filter := bson.D{ - {key, value}} - singleResult := m.collection.FindOne(context.TODO(), filter) - DeleteResult, err := m.collection.DeleteOne(context.TODO(), filter, nil) - if err != nil { - fmt.Println("删除时出现错误,你删不掉的~") - } - return DeleteResult.DeletedCount, singleResult -} - -//删除文章 -func (m *MgoColl) Delete(key string, value interface{}) int64 { - filter := bson.D{ {key, value}} - count, err := m.collection.DeleteOne(context.TODO(), filter, nil) - if err != nil { - fmt.Println(err) - } - return count.DeletedCount - -} - -//删除多个 -func (m *MgoColl) DeleteMany(key string, value interface{}) int64 { - filter := bson.D{ {key, value}} - - count, err := m.collection.DeleteMany(context.TODO(), filter) - if err != nil { - fmt.Println(err) - } - return count.DeletedCount -} - -//索引 -func (m *MgoColl) SetUnique(key string){ - m.collection.Indexes().CreateOne( - context.Background(), - mongo.IndexModel{ - Keys : bsonx.Doc{{key, bsonx.Int32(1)}}, - Options: options.Index().SetUnique(true), - }, - ) -} - -//更新&保存 -func (m *MgoColl) FindOneAndUpdate(filter interface{}, update interface{})*mongo.SingleResult { - //filter := bson.M{"name": "x", "array.name": "b"} - //update := bson.M{"array.$[item].detail": "test"} - - res := m.collection.FindOneAndUpdate(context.Background(), - filter, - bson.M{"$set": update}) - if res.Err() != nil { - return nil - } - return res -} - -func (m *MgoColl) UpdateOne(filter interface{}, update interface{})*mongo.UpdateResult { - res, err := m.collection.UpdateOne(context.TODO(), filter, bson.D{{"$set", update}}) - if err != nil { - return nil - } - - return res -} - -func (m *MgoColl) Load() error{ - r := m.collection.FindOne(context.TODO(), m.schema.GetPri()) - err := r.Decode(m.schema.GetSchema()) - if err != nil { - return err - } - return nil -} - -func (m *MgoColl) Create() (*mongo.InsertOneResult, error){ - return m.collection.InsertOne(context.TODO(), m.schema.GetSchema()) -} - -func (m *MgoColl) UpdateProperty(key string, val interface{}) { - m.UpdateOne(m.schema.GetPri(), bson.M{strings.ToLower(key): val}) -} - -func (m *MgoColl) UpdateProperties(properties map[string]interface{}) { - m.UpdateOne(m.schema.GetPri(), properties) -} diff --git a/src/components/db/redis.go b/src/components/db/redis.go deleted file mode 100644 index 76de78a..0000000 --- a/src/components/db/redis.go +++ /dev/null @@ -1,43 +0,0 @@ -package db - -import ( - "github.com/garyburd/redigo/redis" - "time" -) -var RedisPool *redis.Pool - -//conf *conf.ServerConf -func ConnectRedis(db int, auth, address string) error { - RedisPool = &redis.Pool{ - //最大活跃连接数,0代表无限 - MaxActive: 888, - MaxIdle: 20, - //闲置连接的超时时间 - IdleTimeout: time.Second * 100, - //定义拨号获得连接的函数 - Dial: func() (redis.Conn, error) { - option := []redis.DialOption{redis.DialDatabase(db)} - if auth != "" { - option = append(option, redis.DialPassword(auth)) - } - return redis.Dial("tcp",address, option...) - }, - } - return nil -} - -func CloseRedis() { - RedisPool.Close() -} - -func HKEYS(args ...interface{}) (reply interface{}, err error) { - conn := RedisPool.Get() - defer conn.Close() - return conn.Do("HKEYS", args) -} - -func HMSET(args ...interface{}) (reply interface{}, err error) { - conn := RedisPool.Get() - defer conn.Close() - return conn.Do("HMSET", args) -} \ No newline at end of file diff --git a/src/components/db/schema.go b/src/components/db/schema.go deleted file mode 100644 index 2598094..0000000 --- a/src/components/db/schema.go +++ /dev/null @@ -1,88 +0,0 @@ -package db - -import ( - "reflect" - "strings" -) - -type Schema struct { - mgo *MgoColl - reflectValue *reflect.Value - reflectType reflect.Type - - cacheFields map[string]interface{} - - pri interface{} - schema interface{} -} - -func NewSchema(key string, schema interface{}) *Schema { - s := reflect.ValueOf(schema) - if s.Kind() == reflect.Ptr { - s = reflect.ValueOf(schema).Elem() - } - sch := &Schema{ - reflectValue: &s, - reflectType: s.Type(), - cacheFields: make(map[string]interface{}), - schema: schema, - } - sch.mgo = NewMongoColl(sch) - sch.pri = GetBsonD(sch.getPriTag(), key) - return sch -} - -func (s *Schema) GetSchemaType() reflect.Type { - return s.reflectType -} - -func (s *Schema) GetCollName() string { - return strings.ToLower(s.GetSchemaType().Name()) -} - -func (s *Schema) getPriTag() string { - var pri string - for i := 0; i < s.reflectType.NumField(); i++ { - if s.reflectType.Field(i).Tag.Get("pri") == "1" { - pri = strings.ToLower(s.reflectType.Field(i).Name) - break - } - } - return pri -} - -func (s *Schema) GetPri() interface{} { - return s.pri -} - -func (s *Schema) GetSchema() interface{} { - return s.schema -} - -func (s *Schema) Load() error { - return s.mgo.Load() -} - -func (s *Schema) Create() error { - _, err := s.mgo.Create() - return err -} - -func (s *Schema) Update() { - if s.cacheFields != nil { - s.mgo.UpdateProperties(s.cacheFields) - s.cacheFields = make(map[string]interface{}) - } -} - -func (s *Schema) SetProperty(key string, val interface{}) { - s.reflectValue.FieldByName(key).Set(reflect.ValueOf(val)) - s.cacheFields[strings.ToLower(key)] = val -} - -func (s *Schema) SetProperties(properties map[string]interface{}) { - for key, val := range properties { - s.reflectValue.FieldByName(key).Set(reflect.ValueOf(val)) - s.cacheFields[strings.ToLower(key)] = val - } -} \ No newline at end of file diff --git a/src/components/etcd/etcd.go b/src/components/etcd/etcd.go deleted file mode 100644 index 3c38570..0000000 --- a/src/components/etcd/etcd.go +++ /dev/null @@ -1,106 +0,0 @@ -package etcd - -import ( - "context" - "fmt" - clientv3 "go.etcd.io/etcd/client/v3" - "pro2d/conf" - "pro2d/src/components/logger" - "time" -) - -type EtcdClient struct { - etcd *clientv3.Client -} - -func NewEtcdClient(conf *conf.Etcd) (*EtcdClient, error) { - cli, err := clientv3.New(clientv3.Config{ - Endpoints: conf.Endpoints, - DialTimeout: time.Duration(conf.DialTimeout) * time.Second, - }) - if err != nil { - logger.Error("etcd init err: %v", err) - return nil, err - } - return &EtcdClient{ - etcd: cli, - }, nil -} - -func (e *EtcdClient)PutWithPrefix(prefix, key, val string) { - _, err := e.etcd.Put(context.TODO(), fmt.Sprintf("/%s/%s/", prefix, key), val) - if err != nil { - logger.Error("PutWithPrefix err: %v", err) - return - } -} - -func (e *EtcdClient)PutWithLeasePrefix(prefix, key, val string, ttl int64) error { - lease := clientv3.NewLease(e.etcd) - leaseResp, err := lease.Grant(context.TODO(), ttl) - if err != nil { - logger.Error("PutWithLeasePrefix 设置租约时间失败:%v\n", err) - return err - } - - _, err = e.etcd.Put(context.TODO(), fmt.Sprintf("/%s/%s/", prefix, key), val, clientv3.WithLease(leaseResp.ID)) - if err != nil { - logger.Error("PutWithLeasePrefix err: %v", err) - return err - } - - keepRespChan, err := lease.KeepAlive(context.TODO(), leaseResp.ID) - if err != nil { - logger.Error("keepalive err: %v", err) - return err - } - go func() { - for { - select { - case _ = <-keepRespChan: - if keepRespChan == nil { - fmt.Println("租约已经失效") - goto END - } else { //每秒会续租一次,所以就会受到一次应答 - //fmt.Println("收到自动续租应答:", keepResp.ID) - } - } - } - END: - }() - return nil -} - -func (e *EtcdClient)Get(key string) map[string]string { - ctx, cancel := context.WithTimeout(context.Background(), time.Second) - resp, err := e.etcd.Get(ctx, fmt.Sprintf("/%s/", key)) - cancel() - if err != nil { - logger.Error("etcd get key: %s, err: %v", key, err) - return nil - } - m := make(map[string]string) - for _, v := range resp.Kvs { - m[string(v.Key)] = string(v.Value) - } - return m -} - -func (e *EtcdClient)GetByPrefix(prefix string) map[string]string { - ctx, cancel := context.WithTimeout(context.Background(), time.Second) - resp, err := e.etcd.Get(ctx, fmt.Sprintf("/%s/", prefix), clientv3.WithPrefix()) - cancel() - if err != nil { - logger.Error("etcd get prefix: %s, err: %v", prefix, err) - return nil - } - m := make(map[string]string) - for _, v := range resp.Kvs { - m[string(v.Key)] = string(v.Value) - } - return m -} - -func (e *EtcdClient)Close() { - e.etcd.Close() -} \ No newline at end of file diff --git a/src/components/etcd/etcd_test.go b/src/components/etcd/etcd_test.go deleted file mode 100644 index a3c94cc..0000000 --- a/src/components/etcd/etcd_test.go +++ /dev/null @@ -1,50 +0,0 @@ -package etcd - -import ( - "context" - "fmt" - "go.etcd.io/etcd/api/v3/mvccpb" - clientv3 "go.etcd.io/etcd/client/v3" - "pro2d/conf" - "pro2d/src/components/logger" - "testing" -) - -func TestEtcdClient_GetByPrefix(t *testing.T) { - etcd, err := NewEtcdClient(conf.GlobalConf.Etcd) - if err != nil { - logger.Error(err) - return - } - gameInfo := etcd.GetByPrefix(conf.GlobalConf.AccountConf.Name) - for k, v := range gameInfo { - logger.Debug("game info key: %v val: %v", k, v) - } - - rch := etcd.etcd.Watch(context.Background(), fmt.Sprintf("/%s/", conf.GlobalConf.AccountConf.Name), clientv3.WithPrefix()) - go func() { - for wresp := range rch { - for _, ev := range wresp.Events { - switch ev.Type { - case mvccpb.PUT: //修改或者新增 - logger.Debug("account put key: %s val: %s", ev.Kv.Key, ev.Kv.Value) - case mvccpb.DELETE: //删除 - logger.Debug("account delete key: %s val: %s", ev.Kv.Key, ev.Kv.Value) - } - } - } - }() - - game := etcd.etcd.Watch(context.Background(), fmt.Sprintf("/%s/", conf.GlobalConf.GameConf.Name), clientv3.WithPrefix()) - for wresp := range game { - for _, ev := range wresp.Events { - switch ev.Type { - case mvccpb.PUT: //修改或者新增 - logger.Debug("game put key: %s val: %s", ev.Kv.Key, ev.Kv.Value) - case mvccpb.DELETE: //删除 - logger.Debug("game delete key: %s val: %s", ev.Kv.Key, ev.Kv.Value) - } - } - } - -} diff --git a/src/components/logger/README.md b/src/components/logger/README.md deleted file mode 100644 index 43d62bd..0000000 --- a/src/components/logger/README.md +++ /dev/null @@ -1,143 +0,0 @@ -# logger -convenient log package - -# 1. 使用说明 -```go - import "github.com/wonderivan/logger" - - // 配置logger,如果不配置时默认为控制台输出,等级为DEBG - logger.SetLogger(`{"Console": {"level": "DEBG"}`) - // 配置说明见下文 - - // 设置完成后,即可在控制台和日志文件app.log中看到如下输出 - logger.Trace("this is Trace") - logger.Debug("this is Debug") - logger.Info("this is Info") - logger.Warn("this is Warn") - logger.Error("this is Error") - logger.Crit("this is Critical") - logger.Alert("this is Alert") - logger.Emer("this is Emergency") -``` -输出结果: - -![](images/output1.png) - -# 2. 日志等级 - -当前日志输出等级共8种,从0-7对应的等级由高到底,当配置为某个输出等级时,只有大于等于该等级的日志才会输出。不同的输出适配器支持不同的日志等级配置: - -| 等级 | 配置 | 释义 | 控制台颜色 | -| ---- | ---- | ------------------------------------------------ | ---------- | -| 0 | EMER | 系统级紧急,比如磁盘出错,内存异常,网络不可用等 | 红色底 | -| 1 | ALRT | 系统级警告,比如数据库访问异常,配置文件出错等 | 紫色 | -| 2 | CRIT | 系统级危险,比如权限出错,访问异常等 | 蓝色 | -| 3 | EROR | 用户级错误 | 红色 | -| 4 | WARN | 用户级警告 | 黄色 | -| 5 | INFO | 用户级重要 | 天蓝色 | -| 6 | DEBG | 用户级调试 | 绿色 | -| 7 | TRAC | 用户级基本输出 | 绿色 | - - -# 3. 配置说明 -logger当前支持控制台、文件、网络3种方式适配器输出,可以通过各自的参数进行设置,该logger支持多个方式同时输出,如果未配置某项适配器时,则不初始化也不会输出到该适配器。 - -通过调用logger.SetLogger(config string)方法设置参数,config支持json配置,也支持指定内容为json配置的文件路径,例如: -```go - // 通过配置参数直接配置 - logger.SetLogger(`{"Console": {"level": "DEBG"}}`) - // 通过配置文件配置 - logger.SetLogger("/home/log.json") - -``` - -```json -{ - "TimeFormat":"2006-01-02 15:04:05", // 输出日志开头时间格式 - "Console": { // 控制台日志配置 - "level": "TRAC", // 控制台日志输出等级 - "color": true // 控制台日志颜色开关 - }, - "File": { // 文件日志配置 - "filename": "app.log", // 初始日志文件名 - "level": "TRAC", // 日志文件日志输出等级 - "daily": true, // 跨天后是否创建新日志文件,当append=true时有效 - "maxlines": 1000000, // 日志文件最大行数,当append=true时有效 - "maxsize": 1, // 日志文件最大大小,当append=true时有效 - "maxdays": -1, // 日志文件有效期 - "append": true, // 是否支持日志追加 - "permit": "0660" // 新创建的日志文件权限属性 - }, - "Conn": { // 网络日志配置 - "net":"tcp", // 日志传输模式 - "addr":"10.1.55.10:1024", // 日志接收服务器 - "level": "Warn", // 网络日志输出等级 - "reconnect":true, // 网络断开后是否重连 - "reconnectOnMsg":false, // 发送完每条消息后是否断开网络 - } -} -``` - -- 时间格式 - -| 时间类型 | 时间格式 | -| ------------ | ----------------------------------------- | -| ANSIC | "Mon Jan _2 15:04:05 2006" | -| UnixDate | "Mon Jan _2 15:04:05 MST 2006" | -| RubyDate | "Mon Jan 02 15:04:05 -0700 2006" | -| RFC822 | "02 Jan 06 15:04 MST" | -| RFC822Z | "02 Jan 06 15:04 -0700" | -| RFC850 | "Monday, 02-Jan-06 15:04:05 MST" | -| RFC1123 | "Mon, 02 Jan 2006 15:04:05 MST" | -| RFC1123Z | "Mon, 02 Jan 2006 15:04:05 -0700" | -| RFC3339 | "2006-01-02T15:04:05Z07:00" | -| RFC3339Nano | "2006-01-02T15:04:05.999999999Z07:00" | -| Kitchen | "3:04PM" | -| Stamp | "Jan _2 15:04:05" | -| StampMilli | "Jan _2 15:04:05.000" | -| StampMicro | "Jan _2 15:04:05.000000" | -| StampNano | "Jan _2 15:04:05.000000000" | -| RFC3339Nano1 | "2006-01-02 15:04:05.999999999 -0700 MST" | -| DEFAULT | "2006-01-02 15:04:05" | - -- 时间格式打印: -``` -========RFC1123Z time format======== -Thu, 02 Aug 2018 18:48:04 +0800 [DEBG] [github.com/wonderivan/logger/log_test.go:115] Debug RFC1123Z -========Stamp time format======== -Aug 2 18:48:04 [DEBG] [github.com/wonderivan/logger/log_test.go:115] Debug Stamp -========StampMilli time format======== -Aug 2 18:48:04.489 [DEBG] [github.com/wonderivan/logger/log_test.go:115] Debug StampMilli -========StampNano time format======== -Aug 2 18:48:04.490002155 [DEBG] [github.com/wonderivan/logger/log_test.go:115] Debug StampNano -========RubyDate time format======== -Thu Aug 02 18:48:04 +0800 2018 [DEBG] [github.com/wonderivan/logger/log_test.go:115] Debug RubyDate -========RFC822 time format======== -02 Aug 18 18:48 CST [DEBG] [github.com/wonderivan/logger/log_test.go:115] Debug RFC822 -========RFC822Z time format======== -02 Aug 18 18:48 +0800 [DEBG] [github.com/wonderivan/logger/log_test.go:115] Debug RFC822Z -========RFC1123 time format======== -Thu, 02 Aug 2018 18:48:04 CST [DEBG] [github.com/wonderivan/logger/log_test.go:115] Debug RFC1123 -========RFC3339 time format======== -2018-08-02T18:48:04+08:00 [DEBG] [github.com/wonderivan/logger/log_test.go:115] Debug RFC3339 -========RFC3339Nano time format======== -2018-08-02T18:48:04.490377325+08:00 [DEBG] [github.com/wonderivan/logger/log_test.go:115] Debug RFC3339Nano -========ANSIC time format======== -Thu Aug 2 18:48:04 2018 [DEBG] [github.com/wonderivan/logger/log_test.go:115] Debug ANSIC -========UnixDate time format======== -Thu Aug 2 18:48:04 CST 2018 [DEBG] [github.com/wonderivan/logger/log_test.go:115] Debug UnixDate -========RFC850 time format======== -Thursday, 02-Aug-18 18:48:04 CST [DEBG] [github.com/wonderivan/logger/log_test.go:115] Debug RFC850 -========Kitchen time format======== -6:48PM [DEBG] [github.com/wonderivan/logger/log_test.go:115] Debug Kitchen -========StampMicro time format======== -Aug 2 18:48:04.490662 [DEBG] [github.com/wonderivan/logger/log_test.go:115] Debug StampMicro -``` - -# 4. 其他 - -1. logger默认是控制台输出,输出等级为DEBG,默认是支持颜色区分的。 -2. 日志文件append为true时,当写入的日志文件发生跨天(daily为true)或超过最大限制时,会创建一个新文件,原有文件格式被重命名为: ****.xxxx-xx-xx.xxx.xxx 格式,例如:当向app.log写入日志时,触发了创建新文件操作,则将app.log重命名为 app.2018-01-01.001.log, 如果此时app.2018-01-01.001.log已经存在,则将刚才的app.log重命名为 app.2018-01-01.002.log,以此类推。 -3. logger package默认初始化了全局的defaultLogger,直接调用logger包的Debug方法时,会默认调用defaultLogger.Debug,所以普通调用时,仅需要import logger即可使用。 -4. 网络配置中的reconnectOnMsg为每条消息都重连一次网络日志中心,适用于写日志频率极低的情况下的服务调用,避免长时间连接,占用资源。但强烈不建议普通使用时设置为true,这将会导致调用方反复的网络重连,极大增加资源消耗和延迟。 -5. conn网络输出适配器经过ELK集成环境的测试验证,通过该方式发送的日志,能够正常通过Elecsearch和Kibana检索和分析 \ No newline at end of file diff --git a/src/components/logger/conn.go b/src/components/logger/conn.go deleted file mode 100644 index 0c26efa..0000000 --- a/src/components/logger/conn.go +++ /dev/null @@ -1,115 +0,0 @@ -package logger - -import ( - "encoding/json" - "fmt" - "io" - "net" - "strings" - "sync" - "time" -) - -type connLogger struct { - sync.Mutex - innerWriter io.WriteCloser - Net string `json:"net"` - Addr string `json:"addr"` - Level string `json:"level"` - LogLevel int - illNetFlag bool //网络异常标记 -} - -func (c *connLogger) Init(jsonConfig string, appName string) error { - if len(jsonConfig) == 0 { - return nil - } - //fmt.Printf("consoleWriter Init:%s\n", jsonConfig) - err := json.Unmarshal([]byte(jsonConfig), c) - if err != nil { - return err - } - if l, ok := LevelMap[c.Level]; ok { - c.LogLevel = l - } - if c.innerWriter != nil { - c.innerWriter.Close() - c.innerWriter = nil - } - - go func() { - for { - c.connect() - time.Sleep(10*time.Millisecond) - } - }() - - return nil -} - -func (c *connLogger) LogWrite(when time.Time, msgText interface{}, level int) (err error) { - if level > c.LogLevel { - return nil - } - - msg, ok := msgText.(*loginfo) - if !ok { - return - } - - if c.innerWriter != nil { - err = c.println(when, msg) - //网络异常,通知处理网络的go程自动重连 - if err != nil { - c.innerWriter.Close() - c.innerWriter = nil - } - } - - return -} - -func (c *connLogger) Destroy() { - if c.innerWriter != nil { - c.innerWriter.Close() - } -} - -func (c *connLogger) connect() error { - if c.innerWriter != nil { - return nil - } - addrs := strings.Split(c.Addr, ";") - for _, addr := range addrs { - conn, err := net.DialTimeout(c.Net, addr, 1 * time.Second) - if err != nil { - fmt.Printf("net.Dial error:%v\n", err) - //continue - return err - } - - if tcpConn, ok := conn.(*net.TCPConn); ok { - tcpConn.SetKeepAlive(true) - } - c.innerWriter = conn - return nil - } - return fmt.Errorf("hava no valid logs service addr:%v", c.Addr) -} - -func (c *connLogger) println(when time.Time, msg *loginfo) error { - c.Lock() - defer c.Unlock() - ss, err := json.Marshal(msg) - if err != nil { - return err - } - _, err = c.innerWriter.Write(append(ss, '\n')) - - //返回err,解决日志系统网络异常后的自动重连 - return err -} - -func init() { - Register(AdapterConn, &connLogger{LogLevel: LevelTrace}) -} diff --git a/src/components/logger/console.go b/src/components/logger/console.go deleted file mode 100644 index 691d308..0000000 --- a/src/components/logger/console.go +++ /dev/null @@ -1,92 +0,0 @@ -package logger - -import ( - "encoding/json" - //"fmt" - "os" - "runtime" - "sync" - "time" -) - -type brush func(string) string - -func newBrush(color string) brush { - pre := "\033[" - reset := "\033[0m" - return func(text string) string { - return pre + color + "m" + text + reset - } -} - -//鉴于终端的通常使用习惯,一般白色和黑色字体是不可行的,所以30,37不可用, -var colors = []brush{ - newBrush("1;41"), // Emergency 红色底 - newBrush("1;35"), // Alert 紫色 - newBrush("1;34"), // Critical 蓝色 - newBrush("1;31"), // Error 红色 - newBrush("1;33"), // Warn 黄色 - newBrush("1;36"), // Informational 天蓝色 - newBrush("1;32"), // Debug 绿色 - newBrush("1;32"), // Trace 绿色 -} - -type consoleLogger struct { - sync.Mutex - Level string `json:"level"` - Colorful bool `json:"color"` - LogLevel int -} - -func (c *consoleLogger) Init(jsonConfig string, appName string) error { - if len(jsonConfig) == 0 { - return nil - } - if jsonConfig != "{}" { - //fmt.Fprintf(os.Stdout, "consoleLogger Init:%s\n", jsonConfig) - } - - err := json.Unmarshal([]byte(jsonConfig), c) - if runtime.GOOS == "windows" { - c.Colorful = false - } - - if l, ok := LevelMap[c.Level]; ok { - c.LogLevel = l - return nil - } - - return err -} - -func (c *consoleLogger) LogWrite(when time.Time, msgText interface{}, level int) error { - if level > c.LogLevel { - return nil - } - msg, ok := msgText.(string) - if !ok { - return nil - } - if c.Colorful { - msg = colors[level](msg) - } - c.printlnConsole(when, msg) - return nil -} - -func (c *consoleLogger) Destroy() { - -} - -func (c *consoleLogger) printlnConsole(when time.Time, msg string) { - c.Lock() - defer c.Unlock() - os.Stdout.Write(append([]byte(msg), '\n')) -} - -func init() { - Register(AdapterConsole, &consoleLogger{ - LogLevel: LevelDebug, - Colorful: runtime.GOOS != "windows", - }) -} diff --git a/src/components/logger/file.go b/src/components/logger/file.go deleted file mode 100644 index e2a94e9..0000000 --- a/src/components/logger/file.go +++ /dev/null @@ -1,288 +0,0 @@ -package logger - -import ( - "bytes" - "encoding/json" - "errors" - "fmt" - "io" - "os" - "path/filepath" - "strconv" - "strings" - "sync" - "time" -) - -type fileLogger struct { - sync.RWMutex - fileWriter *os.File - - Filename string //`json:"filename"` - Append bool `json:"append"` - MaxLines int `json:"maxlines"` - MaxSize int `json:"maxsize"` - Daily bool `json:"daily"` - MaxDays int64 `json:"maxdays"` - Level string `json:"level"` - PermitMask string `json:"permit"` - - LogLevel int - maxSizeCurSize int - maxLinesCurLines int - dailyOpenDate int - dailyOpenTime time.Time - fileNameOnly, suffix string -} - -// Init file logger with json config. -// jsonConfig like: -// { -// "filename":"log/app.log", -// "maxlines":10000, -// "maxsize":1024, -// "daily":true, -// "maxdays":15, -// "rotate":true, -// "permit":"0600" -// } -func (f *fileLogger) Init(jsonConfig string, appName string) error { - //fmt.Printf("fileLogger Init:%s\n", jsonConfig) - if len(jsonConfig) == 0 { - return nil - } - err := json.Unmarshal([]byte(jsonConfig), f) - if err != nil { - return err - } - f.Filename = appName - if len(f.Filename) == 0 { - return errors.New("jsonconfig must have filename") - } - f.suffix = filepath.Ext(f.Filename) - f.fileNameOnly = strings.TrimSuffix(f.Filename, f.suffix) - f.MaxSize *= 1024 * 1024 // 将单位转换成MB - if f.suffix == "" { - f.suffix = ".log" - f.Filename += f.suffix - } - if l, ok := LevelMap[f.Level]; ok { - f.LogLevel = l - } - err = f.newFile() - return err -} - -func (f *fileLogger) needCreateFresh(size int, day int) bool { - return (f.MaxLines > 0 && f.maxLinesCurLines >= f.MaxLines) || - (f.MaxSize > 0 && f.maxSizeCurSize+size >= f.MaxSize) || - (f.Daily && day != f.dailyOpenDate) - -} - -// WriteMsg write logger message into file. -func (f *fileLogger) LogWrite(when time.Time, msgText interface{}, level int) error { - msg, ok := msgText.(string) - if !ok { - return nil - } - if level > f.LogLevel { - return nil - } - - day := when.Day() - msg += "\n" - if f.Append { - f.RLock() - if f.needCreateFresh(len(msg), day) { - f.RUnlock() - f.Lock() - if f.needCreateFresh(len(msg), day) { - if err := f.createFreshFile(when); err != nil { - fmt.Fprintf(os.Stdout, "createFreshFile(%q): %s\n", f.Filename, err) - } - } - f.Unlock() - } else { - f.RUnlock() - } - } - - f.Lock() - _, err := f.fileWriter.Write([]byte(msg)) - if err == nil { - f.maxLinesCurLines++ - f.maxSizeCurSize += len(msg) - } - f.Unlock() - return err -} - -func (f *fileLogger) createLogFile() (*os.File, error) { - // Open the log file - perm, err := strconv.ParseInt(f.PermitMask, 8, 64) - if err != nil { - return nil, err - } - fd, err := os.OpenFile(f.Filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, os.FileMode(perm)) - if err == nil { - // Make sure file perm is user set perm cause of `os.OpenFile` will obey umask - os.Chmod(f.Filename, os.FileMode(perm)) - } - return fd, err -} - -func (f *fileLogger) newFile() error { - file, err := f.createLogFile() - if err != nil { - return err - } - if f.fileWriter != nil { - f.fileWriter.Close() - } - f.fileWriter = file - - fInfo, err := file.Stat() - if err != nil { - return fmt.Errorf("get stat err: %s", err) - } - f.maxSizeCurSize = int(fInfo.Size()) - f.dailyOpenTime = time.Now() - f.dailyOpenDate = f.dailyOpenTime.Day() - f.maxLinesCurLines = 0 - if f.maxSizeCurSize > 0 { - count, err := f.lines() - if err != nil { - return err - } - f.maxLinesCurLines = count - } - return nil -} - -func (f *fileLogger) lines() (int, error) { - fd, err := os.Open(f.Filename) - if err != nil { - return 0, err - } - defer fd.Close() - - buf := make([]byte, 32768) // 32k - count := 0 - lineSep := []byte{'\n'} - - for { - c, err := fd.Read(buf) - if err != nil && err != io.EOF { - return count, err - } - - count += bytes.Count(buf[:c], lineSep) - - if err == io.EOF { - break - } - } - - return count, nil -} - -// new file name like xx.2013-01-01.001.log -func (f *fileLogger) createFreshFile(logTime time.Time) error { - // file exists - // Find the next available number - num := 1 - fName := "" - rotatePerm, err := strconv.ParseInt(f.PermitMask, 8, 64) - if err != nil { - return err - } - - _, err = os.Lstat(f.Filename) - if err != nil { - // 初始日志文件不存在,无需创建新文件 - goto RESTART_LOGGER - } - // 日期变了, 说明跨天,重命名时需要保存为昨天的日期 - if f.dailyOpenDate != logTime.Day() { - for ; err == nil && num <= 999; num++ { - fName = f.fileNameOnly + fmt.Sprintf(".%s.%03d%s", f.dailyOpenTime.Format("2006-01-02"), num, f.suffix) - _, err = os.Lstat(fName) - } - } else { //如果仅仅是文件大小或行数达到了限制,仅仅变更后缀序号即可 - for ; err == nil && num <= 999; num++ { - fName = f.fileNameOnly + fmt.Sprintf(".%s.%03d%s", logTime.Format("2006-01-02"), num, f.suffix) - _, err = os.Lstat(fName) - } - } - - if err == nil { - return fmt.Errorf("Cannot find free log number to rename %s", f.Filename) - } - f.fileWriter.Close() - - // 当创建新文件标记为true时 - // 当日志文件超过最大限制行 - // 当日志文件超过最大限制字节 - // 当日志文件隔天更新标记为true时 - // 将旧文件重命名,然后创建新文件 - err = os.Rename(f.Filename, fName) - if err != nil { - fmt.Fprintf(os.Stdout, "os.Rename %s to %s err:%s\n", f.Filename, fName, err.Error()) - goto RESTART_LOGGER - } - - err = os.Chmod(fName, os.FileMode(rotatePerm)) - -RESTART_LOGGER: - - startLoggerErr := f.newFile() - go f.deleteOldLog() - - if startLoggerErr != nil { - return fmt.Errorf("Rotate StartLogger: %s", startLoggerErr) - } - if err != nil { - return fmt.Errorf("Rotate: %s", err) - } - return nil -} - -func (f *fileLogger) deleteOldLog() { - dir := filepath.Dir(f.Filename) - filepath.Walk(dir, func(path string, info os.FileInfo, err error) (returnErr error) { - defer func() { - if r := recover(); r != nil { - fmt.Fprintf(os.Stdout, "Unable to delete old log '%s', error: %v\n", path, r) - } - }() - - if info == nil { - return - } - - if f.MaxDays != -1 && !info.IsDir() && info.ModTime().Add(24*time.Hour*time.Duration(f.MaxDays)).Before(time.Now()) { - if strings.HasPrefix(filepath.Base(path), filepath.Base(f.fileNameOnly)) && - strings.HasSuffix(filepath.Base(path), f.suffix) { - os.Remove(path) - } - } - return - }) -} - -func (f *fileLogger) Destroy() { - f.fileWriter.Close() -} - -func init() { - Register(AdapterFile, &fileLogger{ - Daily: true, - MaxDays: 7, - Append: true, - LogLevel: LevelDebug, - PermitMask: "0777", - MaxLines: 10, - MaxSize: 10 * 1024 * 1024, - }) -} diff --git a/src/components/logger/log.go b/src/components/logger/log.go deleted file mode 100644 index 79f1b15..0000000 --- a/src/components/logger/log.go +++ /dev/null @@ -1,472 +0,0 @@ -package logger - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "os" - "runtime" - "strings" - "sync" - "time" -) - -// 默认日志输出 -var defaultLogger *LocalLogger -var appName = "app" - -// 日志等级,从0-7,日优先级由高到低 -const ( - LevelEmergency = iota // 系统级紧急,比如磁盘出错,内存异常,网络不可用等 - LevelAlert // 系统级警告,比如数据库访问异常,配置文件出错等 - LevelCritical // 系统级危险,比如权限出错,访问异常等 - LevelError // 用户级错误 - LevelWarning // 用户级警告 - LevelInformational // 用户级信息 - LevelDebug // 用户级调试 - LevelTrace // 用户级基本输出 -) - -// 日志等级和描述映射关系 -var LevelMap = map[string]int{ - "EMER": LevelEmergency, - "ALRT": LevelAlert, - "CRIT": LevelCritical, - "EROR": LevelError, - "WARN": LevelWarning, - "INFO": LevelInformational, - "DEBG": LevelDebug, - "TRAC": LevelTrace, -} - -// 注册实现的适配器, 当前支持控制台,文件和网络输出 -var adapters = make(map[string]Logger) - -// 日志记录等级字段 -var levelPrefix = [LevelTrace + 1]string{ - "EMER", - "ALRT", - "CRIT", - "EROR", - "WARN", - "INFO", - "DEBG", - "TRAC", -} - -const ( - logTimeDefaultFormat = "2006-01-02 15:04:05" // 日志输出默认格式 - AdapterConsole = "console" // 控制台输出配置项 - AdapterFile = "file" // 文件输出配置项 - AdapterConn = "conn" // 网络输出配置项 -) - -// log provider interface -type Logger interface { - Init(config string, appName string) error - LogWrite(when time.Time, msg interface{}, level int) error - Destroy() -} - -// 日志输出适配器注册,log需要实现Init,LogWrite,Destroy方法 -func Register(name string, log Logger) { - if log == nil { - panic("logs: Register provide is nil") - } - if _, ok := adapters[name]; ok { - panic("logs: Register called twice for provider " + name) - } - adapters[name] = log -} - -type loginfo struct { - Time string - Level string - Path string - Name string - Content string -} - -type nameLogger struct { - Logger - name string - config string -} - -type LocalLogger struct { - lock sync.RWMutex - init bool - outputs []*nameLogger - appName string - callDepth int - timeFormat string - usePath string -} - -func NewLogger(depth ...int) *LocalLogger { - dep := append(depth, 2)[0] - l := new(LocalLogger) - // appName用于记录网络传输时标记的程序发送方, - // 通过环境变量APPSN进行设置,默认为NONE,此时无法通过网络日志检索区分不同服务发送方 - appSn := os.Getenv("APPSN") - if appSn == "" { - appSn = "NONE" - } - l.appName = "[" + appSn + "]" - l.callDepth = dep - l.SetLogger(AdapterConsole) - l.timeFormat = logTimeDefaultFormat - return l -} - -//配置文件 -type logConfig struct { - TimeFormat string `json:"TimeFormat"` - Console *consoleLogger `json:"Console,omitempty"` - File *fileLogger `json:"File,omitempty"` - Conn *connLogger `json:"Conn,omitempty"` -} - -func init() { - defaultLogger = NewLogger(3) -} - -func (this *LocalLogger) SetLogger(adapterName string, configs ...string) error { - this.lock.Lock() - defer this.lock.Unlock() - - if !this.init { - this.outputs = []*nameLogger{} - this.init = true - } - - config := append(configs, "{}")[0] - var num int = -1 - var i int - var l *nameLogger - for i, l = range this.outputs { - if l.name == adapterName { - if l.config == config { - //配置没有变动,不重新设置 - return fmt.Errorf("you have set same config for this adaptername %s", adapterName) - } - l.Logger.Destroy() - num = i - break - } - } - logger, ok := adapters[adapterName] - if !ok { - return fmt.Errorf("unknown adaptername %s (forgotten Register?)", adapterName) - } - - err := logger.Init(config, appName) - if err != nil { - fmt.Fprintf(os.Stdout, "logger Init <%s> err:%v, %s output ignore!\n", - adapterName, err, adapterName) - return err - } - if num >= 0 { - this.outputs[i] = &nameLogger{name: adapterName, Logger: logger, config: config} - return nil - } - this.outputs = append(this.outputs, &nameLogger{name: adapterName, Logger: logger, config: config}) - return nil -} - -func (this *LocalLogger) DelLogger(adapterName string) error { - this.lock.Lock() - defer this.lock.Unlock() - outputs := []*nameLogger{} - for _, lg := range this.outputs { - if lg.name == adapterName { - lg.Destroy() - } else { - outputs = append(outputs, lg) - } - } - if len(outputs) == len(this.outputs) { - return fmt.Errorf("logs: unknown adaptername %s (forgotten Register?)", adapterName) - } - this.outputs = outputs - return nil -} - -// 设置日志起始路径 -func (this *LocalLogger) SetLogPathTrim(trimPath string) { - this.usePath = trimPath -} - -func (this *LocalLogger) writeToLoggers(when time.Time, msg *loginfo, level int) { - this.lock.RLock() - defer this.lock.RUnlock() - for _, l := range this.outputs { - if l.name == AdapterConn { - //网络日志,使用json格式发送,此处使用结构体,用于类似ElasticSearch功能检索 - err := l.LogWrite(when, msg, level) - if err != nil { - fmt.Fprintf(os.Stdout, "unable to WriteMsg to adapter:%v,error:%v\n", l.name, err) - } - continue - } - - msgStr := when.Format(this.timeFormat) + " [" + msg.Level + "] " + "[" + msg.Path + "] " + msg.Content - err := l.LogWrite(when, msgStr, level) - if err != nil { - fmt.Fprintf(os.Stdout, "unable to WriteMsg to adapter:%v,error:%v\n", l.name, err) - } - } -} - -func (this *LocalLogger) writeMsg(logLevel int, msg string, v ...interface{}) error { - if !this.init { - this.SetLogger(AdapterConsole) - } - msgSt := new(loginfo) - src := "" - if len(v) > 0 { - msg = fmt.Sprintf(msg, v...) - } - when := time.Now() - _, file, lineno, ok := runtime.Caller(this.callDepth) - var strim string = "src/" - if this.usePath != "" { - strim = this.usePath - } - if ok { - - src = strings.Replace( - fmt.Sprintf("%s:%d", stringTrim(file, strim), lineno), "%2e", ".", -1) - } - - msgSt.Level = levelPrefix[logLevel] - msgSt.Path = src - msgSt.Content = msg - msgSt.Name = this.appName - msgSt.Time = when.Format(this.timeFormat) - this.writeToLoggers(when, msgSt, logLevel) - - return nil -} - -func (this *LocalLogger) Fatal(format string, args ...interface{}) { - this.Emer("###Exec Panic:"+format, args...) - os.Exit(1) -} - -func (this *LocalLogger) Panic(format string, args ...interface{}) { - this.Emer("###Exec Panic:"+format, args...) - panic(fmt.Sprintf(format, args...)) -} - -// Emer Log EMERGENCY level message. -func (this *LocalLogger) Emer(format string, v ...interface{}) { - this.writeMsg(LevelEmergency, format, v...) -} - -// Alert Log ALERT level message. -func (this *LocalLogger) Alert(format string, v ...interface{}) { - this.writeMsg(LevelAlert, format, v...) -} - -// Crit Log CRITICAL level message. -func (this *LocalLogger) Crit(format string, v ...interface{}) { - this.writeMsg(LevelCritical, format, v...) -} - -// Error Log ERROR level message. -func (this *LocalLogger) Error(format string, v ...interface{}) { - this.writeMsg(LevelError, format, v...) -} - -// Warn Log WARNING level message. -func (this *LocalLogger) Warn(format string, v ...interface{}) { - this.writeMsg(LevelWarning, format, v...) -} - -// Info Log INFO level message. -func (this *LocalLogger) Info(format string, v ...interface{}) { - this.writeMsg(LevelInformational, format, v...) -} - -// Debug Log DEBUG level message. -func (this *LocalLogger) Debug(format string, v ...interface{}) { - this.writeMsg(LevelDebug, format, v...) -} - -// Trace Log TRAC level message. -func (this *LocalLogger) Trace(format string, v ...interface{}) { - this.writeMsg(LevelTrace, format, v...) -} - -func (this *LocalLogger) Close() { - - for _, l := range this.outputs { - l.Destroy() - } - this.outputs = nil - -} - -func (this *LocalLogger) Reset() { - this.lock.Lock() - defer this.lock.Unlock() - for _, l := range this.outputs { - l.Destroy() - } - this.outputs = nil -} - -func (this *LocalLogger) SetCallDepth(depth int) { - this.callDepth = depth -} - -// GetlocalLogger returns the defaultLogger -func GetlocalLogger() *LocalLogger { - return defaultLogger -} - -// Reset will remove all the adapter -func Reset() { - defaultLogger.Reset() -} - -func SetLogPathTrim(trimPath string) { - defaultLogger.SetLogPathTrim(trimPath) -} - -// param 可以是log配置文件名,也可以是log配置内容,默认DEBUG输出到控制台 -func SetLogger(param ...string) error { - if 0 == len(param) { - //默认只输出到控制台 - defaultLogger.SetLogger(AdapterConsole) - return nil - } - - c := param[0] - if len(param) > 1 { - appName = param[1] - } - conf := new(logConfig) - err := json.Unmarshal([]byte(c), conf) - if err != nil { //不是json,就认为是配置文件,如果都不是,打印日志,然后退出 - // Open the configuration file - fd, err := os.Open(c) - if err != nil { - fmt.Fprintf(os.Stdout, "Could not open %s for configure: %s\n", c, err) - os.Exit(1) - return err - } - - contents, err := ioutil.ReadAll(fd) - if err != nil { - fmt.Fprintf(os.Stdout, "Could not read %s: %s\n", c, err) - os.Exit(1) - return err - } - err = json.Unmarshal(contents, conf) - if err != nil { - fmt.Fprintf(os.Stdout, "Could not Unmarshal %s: %s\n", contents, err) - os.Exit(1) - return err - } - } - if conf.TimeFormat != "" { - defaultLogger.timeFormat = conf.TimeFormat - } - if conf.Console != nil { - console, _ := json.Marshal(conf.Console) - defaultLogger.SetLogger(AdapterConsole, string(console)) - } - if conf.File != nil { - file, _ := json.Marshal(conf.File) - defaultLogger.SetLogger(AdapterFile, string(file)) - } - if conf.Conn != nil { - conn, _ := json.Marshal(conf.Conn) - defaultLogger.SetLogger(AdapterConn, string(conn)) - } - return nil -} - -// Painc logs a message at emergency level and panic. -func Painc(f interface{}, v ...interface{}) { - defaultLogger.Panic(formatLog(f, v...)) -} - -// Fatal logs a message at emergency level and exit. -func Fatal(f interface{}, v ...interface{}) { - defaultLogger.Fatal(formatLog(f, v...)) -} - -// Emer logs a message at emergency level. -func Emer(f interface{}, v ...interface{}) { - defaultLogger.Emer(formatLog(f, v...)) -} - -// Alert logs a message at alert level. -func Alert(f interface{}, v ...interface{}) { - defaultLogger.Alert(formatLog(f, v...)) -} - -// Crit logs a message at critical level. -func Crit(f interface{}, v ...interface{}) { - defaultLogger.Crit(formatLog(f, v...)) -} - -// Error logs a message at error level. -func Error(f interface{}, v ...interface{}) { - defaultLogger.Error(formatLog(f, v...)) -} - -// Warn logs a message at warning level. -func Warn(f interface{}, v ...interface{}) { - defaultLogger.Warn(formatLog(f, v...)) -} - -// Info logs a message at info level. -func Info(f interface{}, v ...interface{}) { - defaultLogger.Info(formatLog(f, v...)) -} - -// Notice logs a message at debug level. -func Debug(f interface{}, v ...interface{}) { - defaultLogger.Debug(formatLog(f, v...)) -} - -// Trace logs a message at trace level. -func Trace(f interface{}, v ...interface{}) { - defaultLogger.Trace(formatLog(f, v...)) -} - -func formatLog(f interface{}, v ...interface{}) string { - var msg string - switch f.(type) { - case string: - msg = f.(string) - if len(v) == 0 { - return msg - } - if strings.Contains(msg, "%") && !strings.Contains(msg, "%%") { - //format string - } else { - //do not contain format char - msg += strings.Repeat(" %v", len(v)) - } - default: - msg = fmt.Sprint(f) - if len(v) == 0 { - return msg - } - msg += strings.Repeat(" %v", len(v)) - } - return fmt.Sprintf(msg, v...) -} - -func stringTrim(s string, cut string) string { - ss := strings.SplitN(s, cut, 2) - if 1 == len(ss) { - return ss[0] - } - return ss[1] -} diff --git a/src/components/net/conn.go b/src/components/net/conn.go deleted file mode 100644 index c3bc8f7..0000000 --- a/src/components/net/conn.go +++ /dev/null @@ -1,195 +0,0 @@ -package net - -import ( - "bufio" - "fmt" - "math" - "net" - "pro2d/src/common" - "pro2d/src/components/logger" - "pro2d/src/models" - "pro2d/src/utils" - "sync/atomic" -) - -type Head struct { - Length int32 - Cmd int32 - ErrCode int32 - PreField int32 -} - - -type Connection struct { - net.Conn - Id int - Server *Server - - scanner *bufio.Scanner - writer *bufio.Writer - - WBuffer chan []byte - - updateFunc chan func() - readFunc chan func() - - Quit chan *Connection - - Role *models.RoleModel - - nextCheckTime int64 //下一次检查的时间 - lastHeartCheckTime int64 //最后收消息时间 - heartTimeoutCount int //超时次数 -} - -type MsgPkg struct { - Head *Head - Body []byte - Conn *Connection -} - -func NewConn(id int, conn net.Conn, s *Server) *Connection { - return &Connection{ - Id: id, - Conn: conn, - Server: s, - - scanner: bufio.NewScanner(conn), - writer: bufio.NewWriter(conn), - WBuffer: make(chan []byte), - updateFunc: make(chan func()), - readFunc: make(chan func()), - Quit: make(chan *Connection), - lastHeartCheckTime: utils.Timex(), - heartTimeoutCount: 0, - } -} - -func (c *Connection) write() { - defer c.Quiting() - - for msg := range c.WBuffer { - n, err := c.writer.Write(msg) - if err != nil{ - logger.Error("write fail err: " + err.Error(), "n: ", n) - return - } - if err := c.writer.Flush(); err != nil { - logger.Error("write Flush fail err: " + err.Error()) - return - } - } -} - -func (c *Connection) read() { - defer c.Quiting() - c.scanner.Split(ParseMsg) - - for c.scanner.Scan() { - req, err := DecodeMsg(c.scanner.Bytes()) - if err != nil { - return - } - - req.Conn = c - c.readFunc <- func() { - c.Server.DoMsgHandler(req) - } - - atomic.StoreInt64(&c.lastHeartCheckTime, utils.Timex()) - - //备注,可以在当前协程处理当条请求(如下, 实现很简单,已经删除),也可以丢到协程池里处理任务(如上),还未对比效果。 - //c.Server.OnRecv(req) - } - - if err := c.scanner.Err(); err != nil { - fmt.Printf("scanner.err: %s\n", err.Error()) - return - } -} - -func (c *Connection) checkHeartBeat(now int64) { - lastHeartCheckTime := atomic.LoadInt64(&c.lastHeartCheckTime) - logger.Debug("checkHeartBeat ID: %d, last: %d, now: %d", c.Id, lastHeartCheckTime, now) - if math.Abs(float64(lastHeartCheckTime - now)) > common.HeartTimerInterval { - c.heartTimeoutCount++ - if c.heartTimeoutCount >= common.HeartTimeoutCountMax { - c.Quiting() - return - } - logger.Debug("timeout count: %d", c.heartTimeoutCount) - }else { - c.heartTimeoutCount = 0 - } -} - -func (c *Connection) update() { - nextCheckTime := atomic.LoadInt64(&c.nextCheckTime) - now := utils.Timex() - if now >= nextCheckTime { - //检查心跳 - c.checkHeartBeat(now) - nextCheckTime = now + common.HeartTimerInterval - atomic.StoreInt64(&c.nextCheckTime, nextCheckTime) - } - - c.updateFunc <- func() { - if c.Role != nil { - //role 恢复数据 - c.Role.OnRecoverTimer(now) - } - } -} - -func (c *Connection) flush() { - defer c.Stop() - for { - select { - case rf := <- c.readFunc: - rf() - case uf := <- c.updateFunc: - uf() - case <- c.Quit: - return - } - } -} - -func (c *Connection) Start() { - go c.write() - go c.read() - c.flush() -} - -func (c *Connection) Stop() { - logger.Debug("ID: %d close", c.Id) - c.Conn.Close() - if c.Role != nil { - c.Role.OnOfflineEvent() - } - - c.Server.OnClose(c) -} - -func (c *Connection) Quiting() { - c.Quit <- c -} - -func (c *Connection) SendMsgByCode(errCode int32, cmd int32, data []byte){ - h := &Head{ - Length: int32(common.HEADLEN + len(data)), - Cmd: cmd, - ErrCode: errCode, - PreField: 0, - } - pkg := &MsgPkg{ - Head: h, - Body: data, - } - buf, err := EncodeMsg(pkg) - if err != nil { - logger.Error("SendMsg error: %v", err) - return - } - c.WBuffer <- buf -} \ No newline at end of file diff --git a/src/components/net/http.go b/src/components/net/http.go deleted file mode 100644 index 79d2966..0000000 --- a/src/components/net/http.go +++ /dev/null @@ -1,80 +0,0 @@ -package net - -import ( - "fmt" - "github.com/gin-gonic/gin" - "net/http" - "pro2d/conf" - "pro2d/src/components/db" - "pro2d/src/components/etcd" - "pro2d/src/models" - "reflect" - "strings" -) - -type HttpServer struct { - version string - port []string - EtcdClient *etcd.EtcdClient - - Handler interface{} -} - -func Pong (c *gin.Context) { - c.JSON(200, gin.H{ - "message": "pong", - }) -} - -func NewHttpServer(version string, port ...string) *HttpServer { - return &HttpServer{version: version, port: port} -} - -func (h *HttpServer)HandlerFuncObj(tvl, obj reflect.Value) gin.HandlerFunc { - return func(c *gin.Context) { - v := tvl.Call([]reflect.Value{obj, reflect.ValueOf(c)}) - if len(v) != 2 { - c.JSON(http.StatusNotFound, gin.H{"code": -100, "data": ""}) - return - } - c.JSON(http.StatusOK, gin.H{"code": v[0].Interface(), "data": v[1].Interface()}) - } -} - -func GetRoutePath(objName, objFunc string) string { - return strings.ToLower(objName + "/" + objFunc) -} - -func (h *HttpServer) BindHandler(handler interface{}) { - h.Handler = handler -} - -func (h *HttpServer) Start() error { - //mongo 初始化 - db.MongoDatabase = db.MongoClient.Database(conf.GlobalConf.AccountConf.DBName) - models.InitAccountServerModels() - - //Etcd 初始化 - var err error - h.EtcdClient, err = etcd.NewEtcdClient(conf.GlobalConf.Etcd) - if err != nil { - return err - } - h.EtcdClient.PutWithLeasePrefix(conf.GlobalConf.AccountConf.Name, conf.GlobalConf.AccountConf.ID, fmt.Sprintf("%s:%d", conf.GlobalConf.AccountConf.IP, conf.GlobalConf.AccountConf.Port), 5) - - //gin初始化 - r := gin.Default() - r.GET("/ping", Pong) - typ := reflect.TypeOf(h.Handler) - val := reflect.ValueOf(h.Handler) - //t := reflect.Indirect(val).Type() - //objectName := t.Name() - - numOfMethod := val.NumMethod() - for i := 0; i < numOfMethod; i++ { - method := typ.Method(i) - r.GET(GetRoutePath(h.version, method.Name), h.HandlerFuncObj(method.Func, val)) - r.POST(GetRoutePath(h.version, method.Name), h.HandlerFuncObj(method.Func, val)) - } - return r.Run(h.port...) // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080") -} \ No newline at end of file diff --git a/src/components/net/http_test.go b/src/components/net/http_test.go deleted file mode 100644 index a2d05c9..0000000 --- a/src/components/net/http_test.go +++ /dev/null @@ -1,22 +0,0 @@ -package net - -import ( - "github.com/gin-gonic/gin" - "testing" -) - -type HttpAction struct { -} - -func (h *HttpAction) PrintA(c *gin.Context) (int, interface{}) { - return 0, "I'm A" -} -func (h *HttpAction) PrintB(c *gin.Context) (int, interface{}) { - return 0, "I'm B" -} - -func TestHttpServer_Start(t *testing.T) { - web := NewHttpServer("v1") - web.BindHandler(&HttpAction{}) - web.Start() -} \ No newline at end of file diff --git a/src/components/net/msg.go b/src/components/net/msg.go deleted file mode 100644 index beb331e..0000000 --- a/src/components/net/msg.go +++ /dev/null @@ -1,57 +0,0 @@ -package net - -import ( - "bytes" - "encoding/binary" - "fmt" - "pro2d/src/common" -) - -func ParseMsg (data []byte, atEOF bool) (advance int, token []byte, err error) { - // 表示我们已经扫描到结尾了 - if atEOF && len(data) == 0 { - return 0, nil, nil - } - if !atEOF && len(data) >= common.HEADLEN { //4字节数据包长度 4字节指令 - length := int32(0) - binary.Read(bytes.NewReader(data[0:4]), binary.BigEndian, &length) - if length <= 0 { - return 0, nil, fmt.Errorf("length is 0") - } - if int(length) <= len(data) { - return int(length) , data[:int(length)], nil - } - return 0 , nil, nil - } - if atEOF { - return len(data), data, nil - } - return 0, nil, nil -} - - -func DecodeMsg(data []byte) (*MsgPkg, error) { - h := &Head{} - err := binary.Read(bytes.NewReader(data), binary.BigEndian, h) - if err != nil { - return nil, err - } - return &MsgPkg{ - Head: h, - Body: data[common.HEADLEN:], - },nil -} - -func EncodeMsg(pkg *MsgPkg) ([]byte, error) { - buf := &bytes.Buffer{} - err := binary.Write(buf, binary.BigEndian, pkg.Head) - if err != nil { - return nil, err - } - - err = binary.Write(buf, binary.BigEndian, pkg.Body) - if err != nil { - return nil, err - } - return buf.Bytes(), nil -} \ No newline at end of file diff --git a/src/components/net/server.go b/src/components/net/server.go deleted file mode 100644 index d4ad0fc..0000000 --- a/src/components/net/server.go +++ /dev/null @@ -1,131 +0,0 @@ -package net - -import ( - "context" - "fmt" - "github.com/golang/protobuf/proto" - "net" - "plugin" - "pro2d/conf" - "pro2d/pb" - "pro2d/src/components/db" - "pro2d/src/components/etcd" - "pro2d/src/components/logger" - "pro2d/src/components/timewheel" - "pro2d/src/models" - "sync" - "time" -) - -type ActionHandler func (msg *MsgPkg) (int32, proto.Message) -var ActionMap map[pb.ProtoCode]ActionHandler - -type Server struct { - SConf *conf.SConf - Clients *sync.Map - EtcdClient *etcd.EtcdClient -} - -func NewServer(sConf *conf.SConf) *Server { - return &Server{ - SConf: sConf, - Clients: new(sync.Map), - EtcdClient: new(etcd.EtcdClient), - } -} - -func (s *Server) DoMsgHandler(msg *MsgPkg) { - if md, ok := ActionMap[pb.ProtoCode(msg.Head.Cmd)]; ok { - logger.Debug("protocode handler: %d", msg.Head.Cmd) - errCode, protomsg := md(msg) - rsp, err := proto.Marshal(protomsg) - fmt.Printf("errCode: %d, protomsg:%v\n", errCode, protomsg) - if err != nil { - msg.Conn.SendMsgByCode(-100, msg.Head.Cmd, nil) - return - } - msg.Conn.SendMsgByCode(errCode, msg.Head.Cmd, rsp) - return - } - logger.Error("protocode not handler: %d", msg.Head.Cmd) -} - -func (s *Server) OnClose(conn *Connection) { - s.Clients.Delete(conn.Id) -} - -func (s *Server) LoadPlugin() { - //重新加载 - _, err:=plugin.Open(conf.GlobalConf.GameConf.PluginPath) - if err != nil { - logger.Error("load plugin err: %v, %s", err, conf.GlobalConf.GameConf.PluginPath) - return - } - logger.Debug("load plugin success") -} - -func (s *Server) handleTimeOut() { - s.Clients.Range(func(key, value interface{}) bool { - client := value.(*Connection) - client.update() - return true - }) - - timewheel.TimeOut(1*time.Second, s.handleTimeOut) -} - -func (s *Server)Start() error { - //mongo 初始化 - db.MongoDatabase = db.MongoClient.Database(conf.GlobalConf.GameConf.DBName) - models.InitGameServerModels() - - //Etcd 初始化 - var err error - s.EtcdClient, err = etcd.NewEtcdClient(conf.GlobalConf.Etcd) - if err != nil { - return err - } - s.EtcdClient.PutWithLeasePrefix(conf.GlobalConf.GameConf.Name, conf.GlobalConf.GameConf.ID, fmt.Sprintf("%s:%d", conf.GlobalConf.GameConf.IP, conf.GlobalConf.GameConf.Port), 5) - - //初始化plugin - //_, err = plugin.Open(conf.GlobalConf.GameConf.PluginPath) - //if err != nil { - // return err - //} - - port := fmt.Sprintf(":%d", s.SConf.Port) - l, err := net.Listen("tcp", port) - if err != nil { - return err - } - - //启动定时器 - s.handleTimeOut() - - //监听端口 - logger.Debug("listen on %s\n", port) - id := 0 - for { - conn, err := l.Accept() - if err != nil { - return err - } - - id++ - client := NewConn(id, conn, s) - s.Clients.Store(id, client) - go client.Start() - } -} - -func (s *Server)Stop() { - timewheel.StopTimer() - - s.Clients.Range(func(key, value interface{}) bool { - client := value.(*Connection) - client.Quiting() - return true - }) - - db.MongoClient.Disconnect(context.TODO()) -} \ No newline at end of file diff --git a/src/components/timewheel/timerwheel.go b/src/components/timewheel/timerwheel.go deleted file mode 100644 index 948b2f2..0000000 --- a/src/components/timewheel/timerwheel.go +++ /dev/null @@ -1,204 +0,0 @@ -package timewheel - -import ( - "container/list" - "pro2d/src/common" - "pro2d/src/components/workpool" - "sync" - "sync/atomic" - "time" -) - -//skynet的时间轮 + 协程池 -const ( - TimeNearShift = 8 - TimeNear = 1 << TimeNearShift - TimeLevelShift = 6 - TimeLevel = 1 << TimeLevelShift - TimeNearMask = TimeNear - 1 - TimeLevelMask = TimeLevel - 1 -) - -type bucket struct { - expiration int32 - timers *list.List - - mu sync.Mutex -} - -func newBucket() *bucket { - return &bucket{ - expiration: -1, - timers: list.New(), - mu: sync.Mutex{}, - } -} - -func (b*bucket) Add(t *timer) { - b.mu.Lock() - defer b.mu.Unlock() - - b.timers.PushBack(t) -} - -func (b*bucket) Flush(reinsert func(t *timer)) { - b.mu.Lock() - defer b.mu.Unlock() - - for e := b.timers.Front(); e != nil; { - next := e.Next() - reinsert(e.Value.(*timer)) - - b.timers.Remove(e) - e = next - } -} - -type timer struct { - expiration uint32 - f func() -} - -var TimingWheel *TimeWheel - -func init() { - TimingWheel = NewTimeWheel() - TimingWheel.Start() -} - -type TimeWheel struct { - tick time.Duration - ticker *time.Ticker - near [TimeNear]*bucket - t [4][TimeLevel]*bucket - time uint32 - - WorkPool *workpool.WorkPool - exit chan struct{} -} - -func NewTimeWheel() *TimeWheel { - tw := &TimeWheel{ - tick: 10*time.Millisecond, - time: 0, - WorkPool: workpool.NewWorkPool(common.WorkerPoolSize, common.MaxTaskPerWorker), - exit: make(chan struct{}), - } - for i :=0; i < TimeNear; i++ { - tw.near[i] = newBucket() - } - - for i :=0; i < 4; i++ { - for j :=0; j < TimeLevel; j++ { - tw.t[i][j] = newBucket() - } - } - return tw -} - -func (tw *TimeWheel) add(t *timer) bool { - time := t.expiration - currentTime := atomic.LoadUint32(&tw.time) - if time <= currentTime { - return false - } - - if (time | TimeNearMask) == (currentTime | TimeNearMask) { - tw.near[time&TimeNearMask].Add(t) - }else { - i := 0 - mask := TimeNear << TimeNearShift - for i=0; i < 3; i ++ { - if (time | uint32(mask - 1)) == (currentTime | uint32(mask - 1)) { - break - } - mask <<= TimeLevelShift - } - - tw.t[i][((time>>(TimeNearShift + i*TimeLevelShift)) & TimeLevelMask)].Add(t) - } - return true -} - -func (tw *TimeWheel) addOrRun(t *timer) { - if !tw.add(t) { - workerID := int64(t.expiration) % tw.WorkPool.WorkerPoolSize - //将请求消息发送给任务队列 - tw.WorkPool.TaskQueue[workerID] <- t.f - } -} - -func (tw *TimeWheel) moveList(level, idx int) { - current := tw.t[level][idx] - current.Flush(tw.addOrRun) -} - -func (tw *TimeWheel) shift() { - mask := TimeNear - ct := atomic.AddUint32(&tw.time, 1) - if ct == 0 { - tw.moveList(3, 0) - }else { - time := ct >> TimeNearShift - - i := 0 - for (ct & uint32(mask-1)) == 0{ - idx := time & TimeLevelMask - if idx != 0 { - tw.moveList(i, int(idx)) - break - } - - mask <<= TimeLevelShift - time >>= TimeLevelShift - i++ - } - } -} - -func (tw *TimeWheel) execute() { - idx := tw.time & TimeNearMask - tw.near[idx].Flush(tw.addOrRun) -} - -func (tw *TimeWheel) update() { - tw.execute() - tw.shift() - tw.execute() -} - -func (tw *TimeWheel) Start() { - tw.ticker = time.NewTicker(tw.tick) - tw.WorkPool.StartWorkerPool() - - go func() { - for { - select { - case <- tw.ticker.C: - tw.update() - case <- tw.exit: - return - } - } - }() -} - -func (tw *TimeWheel) Stop() { - close(tw.exit) -} - -func (tw *TimeWheel) afterFunc(expiration time.Duration, f func()) { - time := atomic.LoadUint32(&tw.time) - tw.addOrRun(&timer{ - expiration: uint32(expiration / tw.tick) + time, - f: f, - }) -} - -func TimeOut(expire time.Duration, f func()) { - TimingWheel.afterFunc(expire, f) -} - -func StopTimer() { - TimingWheel.Stop() -} \ No newline at end of file diff --git a/src/components/timewheel/timewheel_test.go b/src/components/timewheel/timewheel_test.go deleted file mode 100644 index b19776a..0000000 --- a/src/components/timewheel/timewheel_test.go +++ /dev/null @@ -1,18 +0,0 @@ -package timewheel - -import ( - "fmt" - "testing" - "time" -) - -func PRINT() { - fmt.Println("12312312312") -} - -func TestTimeWheel_Start(t *testing.T) { - TimeOut(1 * time.Second, func() { - fmt.Println("12312313123") - }) - select{} -} diff --git a/src/components/workpool/workpool.go b/src/components/workpool/workpool.go deleted file mode 100644 index e325231..0000000 --- a/src/components/workpool/workpool.go +++ /dev/null @@ -1,41 +0,0 @@ -package workpool - -type Job func() - -type WorkPool struct { - WorkerPoolSize int64 - MaxTaskPerWorker int64 - TaskQueue []chan Job -} - -func NewWorkPool(poolSize, maxTaskSize int64) *WorkPool { - return &WorkPool{ - WorkerPoolSize: poolSize, - MaxTaskPerWorker: maxTaskSize, - TaskQueue: make([]chan Job, poolSize), - } -} - -//StartOneWorker 启动一个Worker工作流程 -func (wp *WorkPool) StartOneWorker(workerID int, taskQueue chan Job) { - //不断的等待队列中的消息 - for { - select { - //有消息则取出队列的Request,并执行绑定的业务方法 - case job := <-taskQueue: - _ = workerID - job() - } - } -} - -func (wp *WorkPool) StartWorkerPool() { - //遍历需要启动worker的数量,依此启动 - for i := 0; i < int(wp.WorkerPoolSize); i++ { - //一个worker被启动 - //给当前worker对应的任务队列开辟空间 - wp.TaskQueue[i] = make(chan Job, wp.MaxTaskPerWorker) - //启动当前Worker,阻塞的等待对应的任务队列是否有消息传递进来 - go wp.StartOneWorker(i, wp.TaskQueue[i]) - } -} \ No newline at end of file diff --git a/src/models/account.go b/src/models/account.go deleted file mode 100644 index 9e95486..0000000 --- a/src/models/account.go +++ /dev/null @@ -1,31 +0,0 @@ -package models - -import ( - "pro2d/pb" - "pro2d/src/components/db" -) - -type AccountModel struct { - *db.Schema - *pb.Account -} - -func AccountExistByPhone(phone string) (bool, *AccountModel){ - m := NewAccount(phone) - if err := m.Load(); err != nil { - return false, m - } - return true, m -} - -func NewAccount(phone string) *AccountModel { - ac := &pb.Account{ - Phone: phone, - } - account := &AccountModel{ - Schema: db.NewSchema(phone, ac), - Account: ac, - } - - return account -} \ No newline at end of file diff --git a/src/models/equip.go b/src/models/equip.go deleted file mode 100644 index d249e3e..0000000 --- a/src/models/equip.go +++ /dev/null @@ -1,23 +0,0 @@ -package models - -import ( - "pro2d/pb" - "pro2d/src/components/db" -) - -type EquipModels struct { - *db.Schema - Equip *pb.Equipment -} - -func NewEquip(id string) *EquipModels { - data := &pb.Equipment{ - Id: id, - } - m := &EquipModels{ - Schema: db.NewSchema(id, data), - Equip: data, - } - - return m -} \ No newline at end of file diff --git a/src/models/hero.go b/src/models/hero.go deleted file mode 100644 index 69876c2..0000000 --- a/src/models/hero.go +++ /dev/null @@ -1,31 +0,0 @@ -package models - -import ( - "pro2d/pb" - "pro2d/src/components/db" -) - -type HeroModel struct { - *db.Schema - Hero *pb.Hero -} -type HeroMap map[string]*HeroModel - -func GetHeros(hm HeroMap) map[string]*pb.Hero { - h := make(map[string]*pb.Hero) - for k, v := range hm { - h[k] = v.Hero - } - return h -} - -func NewHero(id string) *HeroModel { - h := &pb.Hero{ - Id: id, - } - m := &HeroModel{ - Schema: db.NewSchema(id, h), - Hero: h, - } - return m -} diff --git a/src/models/init.go b/src/models/init.go deleted file mode 100644 index 2dc6771..0000000 --- a/src/models/init.go +++ /dev/null @@ -1,40 +0,0 @@ -package models - -import ( - "pro2d/pb" - "pro2d/src/components/db" - "pro2d/src/components/logger" - "pro2d/src/utils" -) - -func InitDoc(schema ...interface{}) { - for _, s := range schema { - coll, keys := utils.FindIndex(s) - for _, index := range keys { - db.CreateCollection(coll) - - logger.Debug("InitDoc collect: %v, createIndex: %s", coll, index) - res, err := db.SetUnique(coll, index) - if err != nil { - logger.Error("InitDoc unique: %s, err: %v", res, err) - continue - } - } - } -} - -func InitAccountServerModels() { - var schema []interface{} = []interface{}{ - pb.Account{}, - } - InitDoc(schema...) -} - -func InitGameServerModels() { - var schema []interface{} = []interface{}{ - pb.Hero{}, - pb.Role{}, - pb.Team{}, - } - InitDoc(schema...) -} \ No newline at end of file diff --git a/src/models/init_test.go b/src/models/init_test.go deleted file mode 100644 index f50fa63..0000000 --- a/src/models/init_test.go +++ /dev/null @@ -1,20 +0,0 @@ -package models - -import ( - "context" - _ "pro2d/conf" - "pro2d/src/components/db" - "testing" -) - - -func TestInitModels(t *testing.T) { - - db.MongoDatabase = db.MongoClient.Database("account") - InitAccountServerModels() - db.MongoClient.Disconnect(context.TODO()) - - db.MongoDatabase = db.MongoClient.Database("game") - InitGameServerModels() - db.MongoClient.Disconnect(context.TODO()) -} \ No newline at end of file diff --git a/src/models/prop.go b/src/models/prop.go deleted file mode 100644 index 76632c8..0000000 --- a/src/models/prop.go +++ /dev/null @@ -1,23 +0,0 @@ -package models - -import ( - "pro2d/pb" - "pro2d/src/components/db" -) - -type PropModels struct { - *db.Schema - Prop *pb.Prop -} - -func NewProp(id string) *PropModels { - data := &pb.Prop{ - Id: id, - } - m := &PropModels{ - Schema: db.NewSchema(id, data), - Prop: data, - } - - return m -} \ No newline at end of file diff --git a/src/models/role.go b/src/models/role.go deleted file mode 100644 index 2dec556..0000000 --- a/src/models/role.go +++ /dev/null @@ -1,129 +0,0 @@ -package models - -import ( - "fmt" - "pro2d/pb" - "pro2d/src/common" - "pro2d/src/components/db" - "pro2d/src/components/logger" - "pro2d/src/utils" - "sync/atomic" -) - -type RoleModel struct { - *db.Schema - Role *pb.Role - Heros HeroMap - Teams *TeamModel - Equip *EquipModels - Prop *PropModels - - lastSaveTs int64 -} - -func RoleExistByUid(uid string) *RoleModel { - data := &pb.Role{Uid: uid} - - if err := db.FindOne(db.GetBsonM("uid", uid), data); err != nil { - logger.Error("Role exist err: %v", err) - return nil - } - - - r := &RoleModel{ - Schema: db.NewSchema(data.Id, data), - Role: data, - Heros: make(HeroMap), - Teams: new(TeamModel), - Equip: new(EquipModels), - Prop: new(PropModels), - } - r.LoadAll() - return r -} - -func NewRole(id string) *RoleModel { - data := &pb.Role{Id: id} - m := &RoleModel{ - Schema: db.NewSchema(id, data), - Role: data, - Heros: make(HeroMap), - Teams: new(TeamModel), - Equip: new(EquipModels), - Prop: new(PropModels), - } - return m -} - -func (m *RoleModel) LoadHero() { - m.Heros["test"] = NewHero("") - m.Heros["test"].Hero = &pb.Hero{ - Id: "1", - RoleId: m.Role.Id, - Type: 1, - Level: 1, - ReinCount: 0, - ReinPoint: 0, - Equipments: "123123", - } -} - -func (m *RoleModel) LoadTeams() { - m.Teams = NewTeam("0") - m.Teams.Team = &pb.Team{ - Id: "1", - HeroIds: "1", - } -} - -func (m *RoleModel) LoadEquips() { - m.Equip = NewEquip("0") - m.Equip.Equip = &pb.Equipment{ - Id: "0", - RoleId: m.Role.Id, - Type: 0, - Equip: false, - EnhanceLevel: false, - } -} - -func (m *RoleModel) LoadAll() { - m.LoadHero() - m.LoadTeams() - m.LoadEquips() -} - -func (m *RoleModel) updateProperty(property map[string]interface{}) { -} - -func (m *RoleModel) AddHero(hero *pb.Hero) { - h := NewHero(hero.Id) - h.Hero = hero - h.Create() - m.Heros[fmt.Sprintf("%s%s", m.Role.Id, h.Hero.Id)] = h -} - -func (m *RoleModel) GetAllHero() []*pb.Hero { - var h []*pb.Hero - for _, hero := range m.Heros { - h = append(h, hero.Hero) - } - return h -} - -func (m *RoleModel) OnRecoverTimer(now int64) { - m.saveRoleData(now) -} - -func (m *RoleModel) OnOfflineEvent() { - // 设置最新的登录时间 - m.saveRoleData(utils.Timex()) -} - -func (m *RoleModel) saveRoleData(now int64) { - if now - m.lastSaveTs < common.SaveDataInterval { - return - } - atomic.StoreInt64(&m.lastSaveTs, now) - m.Update() -} \ No newline at end of file diff --git a/src/models/role_test.go b/src/models/role_test.go deleted file mode 100644 index f88cc70..0000000 --- a/src/models/role_test.go +++ /dev/null @@ -1,48 +0,0 @@ -package models - -import ( - "fmt" - _ "pro2d/conf" - "pro2d/pb" - "pro2d/src/components/db" - "pro2d/src/components/logger" - "pro2d/src/utils" - "testing" -) - -func TestNewRole(t *testing.T) { - db.MongoDatabase = db.MongoClient.Database("game") - - var uid = "141815055745814528" - role := RoleExistByUid(uid) - if role != nil { - //uid存在 , 更新角色 - //role.AddHero(&pb.Hero{ - // Id: 1, - // RoleId: role.Role.Id, - // Type: 0, - // Level: 0, - // ReinCount: 0, - // ReinPoint: 0, - // Equipments: "", - //}) - role.SetProperty("Device", "1111") - //role.Save() - }else { - //uid不存在,创建角色 - role = NewRole("1") - role.Role.Uid = uid - role.Role.Device = "111111" - role.Role.Level = 0 - err := role.Create() - fmt.Println(err) - } - print(role) -} - -func TestRoleIndex(t *testing.T) { - coll, keys := utils.FindIndex(pb.Role{}) - for _, index := range keys { - logger.Debug("coll: %s, key: %s", coll, index) - } -} \ No newline at end of file diff --git a/src/models/team.go b/src/models/team.go deleted file mode 100644 index 67da3a7..0000000 --- a/src/models/team.go +++ /dev/null @@ -1,23 +0,0 @@ -package models - -import ( - "pro2d/pb" - "pro2d/src/components/db" -) - -type TeamModel struct { - *db.Schema - Team *pb.Team -} - -func NewTeam(id string) *TeamModel { - data := &pb.Team{ - Id: id, - } - m := &TeamModel{ - Schema: db.NewSchema(id, data), - Team: data, - } - - return m -} \ No newline at end of file diff --git a/src/plugin/RolePlugin.go b/src/plugin/RolePlugin.go deleted file mode 100644 index d3852a7..0000000 --- a/src/plugin/RolePlugin.go +++ /dev/null @@ -1,59 +0,0 @@ -package plugin - -import ( - "github.com/golang/protobuf/proto" - "pro2d/conf" - "pro2d/pb" - "pro2d/src/components/logger" - "pro2d/src/components/net" - "pro2d/src/models" -) - -func HeartRpc(msg *net.MsgPkg) (int32, proto.Message) { - //msg.Conn.SetLastHeartCheckTime() - return 0, nil -} - -func CreateRpc(msg *net.MsgPkg) (int32, proto.Message) { - req := pb.CreateReq{} - if err := proto.Unmarshal(msg.Body, &req); err != nil { - logger.Error("CreateRpc err: %v", err) - return 1, nil - } - role := models.RoleExistByUid(req.Uid) - if role != nil { - return 2, nil - } - - roleId := conf.SnowFlack.NextValStr() - role = models.NewRole(roleId) - if err := role.Create(); err != nil { - logger.Error("CreateRpc role create err: %v", err) - return 3, nil - } - return 0, nil -} - -func LoginRpc(msg *net.MsgPkg) (int32, proto.Message) { - //logger.Debug("cmd: %v, msg: %s", msg.Head.Cmd, msg.Body) - req := pb.LoginReq{} - if err := proto.Unmarshal(msg.Body, &req); err != nil { - logger.Error("loginRpc err: %v", err) - return 1, nil - } - - role := models.RoleExistByUid(req.Uid) - if role == nil { - return 2, nil - } - role.SetProperty("Device", req.Device) - - - msg.Conn.Role = role - return 0, &pb.RoleRsp{ - Role: role.Role, - Hero: role.GetAllHero(), - Team: role.Teams.Team, - Equips: []*pb.Equipment{role.Equip.Equip}, - } -} \ No newline at end of file diff --git a/src/plugin/protocode.go b/src/plugin/protocode.go deleted file mode 100644 index bba508a..0000000 --- a/src/plugin/protocode.go +++ /dev/null @@ -1,17 +0,0 @@ -package plugin - -import ( - "pro2d/pb" - "pro2d/src/components/logger" - "pro2d/src/components/net" -) - -func init() { - logger.Debug("init protocode...") - net.ActionMap = make(map[pb.ProtoCode]net.ActionHandler) - - net.ActionMap[pb.ProtoCode_HeartReq] = HeartRpc - net.ActionMap[pb.ProtoCode_LoginReq] = LoginRpc - net.ActionMap[pb.ProtoCode_CreateReq] = CreateRpc - -} diff --git a/src/utils/snowflake.go b/src/utils/snowflake.go deleted file mode 100644 index 3b94ba5..0000000 --- a/src/utils/snowflake.go +++ /dev/null @@ -1,76 +0,0 @@ -package utils - -import ( - "fmt" - "github.com/golang/glog" - "sync" - "time" -) - -type Snowflake struct { - *sync.Mutex // 锁 - timestamp int64 // 时间戳 ,毫秒 - workerid int64 // 工作节点 - datacenterid int64 // 数据中心机房id - sequence int64 // 序列号 -} -const ( - epoch = int64(1577808000000) // 设置起始时间(时间戳/毫秒):2020-01-01 00:00:00,有效期69年 - timestampBits = uint(41) // 时间戳占用位数 - datacenteridBits = uint(2) // 数据中心id所占位数 - workeridBits = uint(7) // 机器id所占位数 - sequenceBits = uint(12) // 序列所占的位数 - timestampMax = int64(-1 ^ (-1 << timestampBits)) // 时间戳最大值 - datacenteridMax = int64(-1 ^ (-1 << datacenteridBits)) // 支持的最大数据中心id数量 - workeridMax = int64(-1 ^ (-1 << workeridBits)) // 支持的最大机器id数量 - sequenceMask = int64(-1 ^ (-1 << sequenceBits)) // 支持的最大序列id数量 - workeridShift = sequenceBits // 机器id左移位数 - datacenteridShift = sequenceBits + workeridBits // 数据中心id左移位数 - timestampShift = sequenceBits + workeridBits + datacenteridBits // 时间戳左移位数 -) - - - -func NewSnowflake(workerid, datacenterid int64) *Snowflake { - return &Snowflake{ - Mutex: new(sync.Mutex), - timestamp: time.Now().UnixNano() / 1000000, - workerid: workerid, - datacenterid: datacenterid, - sequence: 0, - } -} - -func (s *Snowflake) NextValStr() string { - return fmt.Sprintf("%d", s.NextVal()) -} - - -func (s *Snowflake) NextVal() int64 { - s.Lock() - now := time.Now().UnixNano() / 1000000 // 转毫秒 - if s.timestamp == now { - // 当同一时间戳(精度:毫秒)下多次生成id会增加序列号 - s.sequence = (s.sequence + 1) & sequenceMask - if s.sequence == 0 { - // 如果当前序列超出12bit长度,则需要等待下一毫秒 - // 下一毫秒将使用sequence:0 - for now <= s.timestamp { - now = time.Now().UnixNano() / 1000000 - } - } - } else { - // 不同时间戳(精度:毫秒)下直接使用序列号:0 - s.sequence = 0 - } - t := now - epoch - if t > timestampMax { - s.Unlock() - glog.Errorf("epoch must be between 0 and %d", timestampMax-1) - return 0 - } - s.timestamp = now - r := int64((t)< c.LogLevel { + return nil + } + + msg, ok := msgText.(*loginfo) + if !ok { + return + } + + if c.innerWriter != nil { + err = c.println(when, msg) + //网络异常,通知处理网络的go程自动重连 + if err != nil { + c.innerWriter.Close() + c.innerWriter = nil + } + } + + return +} + +func (c *connLogger) Destroy() { + if c.innerWriter != nil { + c.innerWriter.Close() + } +} + +func (c *connLogger) connect() error { + if c.innerWriter != nil { + return nil + } + addrs := strings.Split(c.Addr, ";") + for _, addr := range addrs { + conn, err := net.DialTimeout(c.Net, addr, 1 * time.Second) + if err != nil { + fmt.Printf("net.Dial error:%v\n", err) + //continue + return err + } + + if tcpConn, ok := conn.(*net.TCPConn); ok { + tcpConn.SetKeepAlive(true) + } + c.innerWriter = conn + return nil + } + return fmt.Errorf("hava no valid logs service addr:%v", c.Addr) +} + +func (c *connLogger) println(when time.Time, msg *loginfo) error { + c.Lock() + defer c.Unlock() + ss, err := json.Marshal(msg) + if err != nil { + return err + } + _, err = c.innerWriter.Write(append(ss, '\n')) + + //返回err,解决日志系统网络异常后的自动重连 + return err +} + +func init() { + Register(AdapterConn, &connLogger{LogLevel: LevelTrace}) +} diff --git a/utils/logger/console.go b/utils/logger/console.go new file mode 100644 index 0000000..691d308 --- /dev/null +++ b/utils/logger/console.go @@ -0,0 +1,92 @@ +package logger + +import ( + "encoding/json" + //"fmt" + "os" + "runtime" + "sync" + "time" +) + +type brush func(string) string + +func newBrush(color string) brush { + pre := "\033[" + reset := "\033[0m" + return func(text string) string { + return pre + color + "m" + text + reset + } +} + +//鉴于终端的通常使用习惯,一般白色和黑色字体是不可行的,所以30,37不可用, +var colors = []brush{ + newBrush("1;41"), // Emergency 红色底 + newBrush("1;35"), // Alert 紫色 + newBrush("1;34"), // Critical 蓝色 + newBrush("1;31"), // Error 红色 + newBrush("1;33"), // Warn 黄色 + newBrush("1;36"), // Informational 天蓝色 + newBrush("1;32"), // Debug 绿色 + newBrush("1;32"), // Trace 绿色 +} + +type consoleLogger struct { + sync.Mutex + Level string `json:"level"` + Colorful bool `json:"color"` + LogLevel int +} + +func (c *consoleLogger) Init(jsonConfig string, appName string) error { + if len(jsonConfig) == 0 { + return nil + } + if jsonConfig != "{}" { + //fmt.Fprintf(os.Stdout, "consoleLogger Init:%s\n", jsonConfig) + } + + err := json.Unmarshal([]byte(jsonConfig), c) + if runtime.GOOS == "windows" { + c.Colorful = false + } + + if l, ok := LevelMap[c.Level]; ok { + c.LogLevel = l + return nil + } + + return err +} + +func (c *consoleLogger) LogWrite(when time.Time, msgText interface{}, level int) error { + if level > c.LogLevel { + return nil + } + msg, ok := msgText.(string) + if !ok { + return nil + } + if c.Colorful { + msg = colors[level](msg) + } + c.printlnConsole(when, msg) + return nil +} + +func (c *consoleLogger) Destroy() { + +} + +func (c *consoleLogger) printlnConsole(when time.Time, msg string) { + c.Lock() + defer c.Unlock() + os.Stdout.Write(append([]byte(msg), '\n')) +} + +func init() { + Register(AdapterConsole, &consoleLogger{ + LogLevel: LevelDebug, + Colorful: runtime.GOOS != "windows", + }) +} diff --git a/utils/logger/file.go b/utils/logger/file.go new file mode 100644 index 0000000..e2a94e9 --- /dev/null +++ b/utils/logger/file.go @@ -0,0 +1,288 @@ +package logger + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "os" + "path/filepath" + "strconv" + "strings" + "sync" + "time" +) + +type fileLogger struct { + sync.RWMutex + fileWriter *os.File + + Filename string //`json:"filename"` + Append bool `json:"append"` + MaxLines int `json:"maxlines"` + MaxSize int `json:"maxsize"` + Daily bool `json:"daily"` + MaxDays int64 `json:"maxdays"` + Level string `json:"level"` + PermitMask string `json:"permit"` + + LogLevel int + maxSizeCurSize int + maxLinesCurLines int + dailyOpenDate int + dailyOpenTime time.Time + fileNameOnly, suffix string +} + +// Init file logger with json config. +// jsonConfig like: +// { +// "filename":"log/app.log", +// "maxlines":10000, +// "maxsize":1024, +// "daily":true, +// "maxdays":15, +// "rotate":true, +// "permit":"0600" +// } +func (f *fileLogger) Init(jsonConfig string, appName string) error { + //fmt.Printf("fileLogger Init:%s\n", jsonConfig) + if len(jsonConfig) == 0 { + return nil + } + err := json.Unmarshal([]byte(jsonConfig), f) + if err != nil { + return err + } + f.Filename = appName + if len(f.Filename) == 0 { + return errors.New("jsonconfig must have filename") + } + f.suffix = filepath.Ext(f.Filename) + f.fileNameOnly = strings.TrimSuffix(f.Filename, f.suffix) + f.MaxSize *= 1024 * 1024 // 将单位转换成MB + if f.suffix == "" { + f.suffix = ".log" + f.Filename += f.suffix + } + if l, ok := LevelMap[f.Level]; ok { + f.LogLevel = l + } + err = f.newFile() + return err +} + +func (f *fileLogger) needCreateFresh(size int, day int) bool { + return (f.MaxLines > 0 && f.maxLinesCurLines >= f.MaxLines) || + (f.MaxSize > 0 && f.maxSizeCurSize+size >= f.MaxSize) || + (f.Daily && day != f.dailyOpenDate) + +} + +// WriteMsg write logger message into file. +func (f *fileLogger) LogWrite(when time.Time, msgText interface{}, level int) error { + msg, ok := msgText.(string) + if !ok { + return nil + } + if level > f.LogLevel { + return nil + } + + day := when.Day() + msg += "\n" + if f.Append { + f.RLock() + if f.needCreateFresh(len(msg), day) { + f.RUnlock() + f.Lock() + if f.needCreateFresh(len(msg), day) { + if err := f.createFreshFile(when); err != nil { + fmt.Fprintf(os.Stdout, "createFreshFile(%q): %s\n", f.Filename, err) + } + } + f.Unlock() + } else { + f.RUnlock() + } + } + + f.Lock() + _, err := f.fileWriter.Write([]byte(msg)) + if err == nil { + f.maxLinesCurLines++ + f.maxSizeCurSize += len(msg) + } + f.Unlock() + return err +} + +func (f *fileLogger) createLogFile() (*os.File, error) { + // Open the log file + perm, err := strconv.ParseInt(f.PermitMask, 8, 64) + if err != nil { + return nil, err + } + fd, err := os.OpenFile(f.Filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, os.FileMode(perm)) + if err == nil { + // Make sure file perm is user set perm cause of `os.OpenFile` will obey umask + os.Chmod(f.Filename, os.FileMode(perm)) + } + return fd, err +} + +func (f *fileLogger) newFile() error { + file, err := f.createLogFile() + if err != nil { + return err + } + if f.fileWriter != nil { + f.fileWriter.Close() + } + f.fileWriter = file + + fInfo, err := file.Stat() + if err != nil { + return fmt.Errorf("get stat err: %s", err) + } + f.maxSizeCurSize = int(fInfo.Size()) + f.dailyOpenTime = time.Now() + f.dailyOpenDate = f.dailyOpenTime.Day() + f.maxLinesCurLines = 0 + if f.maxSizeCurSize > 0 { + count, err := f.lines() + if err != nil { + return err + } + f.maxLinesCurLines = count + } + return nil +} + +func (f *fileLogger) lines() (int, error) { + fd, err := os.Open(f.Filename) + if err != nil { + return 0, err + } + defer fd.Close() + + buf := make([]byte, 32768) // 32k + count := 0 + lineSep := []byte{'\n'} + + for { + c, err := fd.Read(buf) + if err != nil && err != io.EOF { + return count, err + } + + count += bytes.Count(buf[:c], lineSep) + + if err == io.EOF { + break + } + } + + return count, nil +} + +// new file name like xx.2013-01-01.001.log +func (f *fileLogger) createFreshFile(logTime time.Time) error { + // file exists + // Find the next available number + num := 1 + fName := "" + rotatePerm, err := strconv.ParseInt(f.PermitMask, 8, 64) + if err != nil { + return err + } + + _, err = os.Lstat(f.Filename) + if err != nil { + // 初始日志文件不存在,无需创建新文件 + goto RESTART_LOGGER + } + // 日期变了, 说明跨天,重命名时需要保存为昨天的日期 + if f.dailyOpenDate != logTime.Day() { + for ; err == nil && num <= 999; num++ { + fName = f.fileNameOnly + fmt.Sprintf(".%s.%03d%s", f.dailyOpenTime.Format("2006-01-02"), num, f.suffix) + _, err = os.Lstat(fName) + } + } else { //如果仅仅是文件大小或行数达到了限制,仅仅变更后缀序号即可 + for ; err == nil && num <= 999; num++ { + fName = f.fileNameOnly + fmt.Sprintf(".%s.%03d%s", logTime.Format("2006-01-02"), num, f.suffix) + _, err = os.Lstat(fName) + } + } + + if err == nil { + return fmt.Errorf("Cannot find free log number to rename %s", f.Filename) + } + f.fileWriter.Close() + + // 当创建新文件标记为true时 + // 当日志文件超过最大限制行 + // 当日志文件超过最大限制字节 + // 当日志文件隔天更新标记为true时 + // 将旧文件重命名,然后创建新文件 + err = os.Rename(f.Filename, fName) + if err != nil { + fmt.Fprintf(os.Stdout, "os.Rename %s to %s err:%s\n", f.Filename, fName, err.Error()) + goto RESTART_LOGGER + } + + err = os.Chmod(fName, os.FileMode(rotatePerm)) + +RESTART_LOGGER: + + startLoggerErr := f.newFile() + go f.deleteOldLog() + + if startLoggerErr != nil { + return fmt.Errorf("Rotate StartLogger: %s", startLoggerErr) + } + if err != nil { + return fmt.Errorf("Rotate: %s", err) + } + return nil +} + +func (f *fileLogger) deleteOldLog() { + dir := filepath.Dir(f.Filename) + filepath.Walk(dir, func(path string, info os.FileInfo, err error) (returnErr error) { + defer func() { + if r := recover(); r != nil { + fmt.Fprintf(os.Stdout, "Unable to delete old log '%s', error: %v\n", path, r) + } + }() + + if info == nil { + return + } + + if f.MaxDays != -1 && !info.IsDir() && info.ModTime().Add(24*time.Hour*time.Duration(f.MaxDays)).Before(time.Now()) { + if strings.HasPrefix(filepath.Base(path), filepath.Base(f.fileNameOnly)) && + strings.HasSuffix(filepath.Base(path), f.suffix) { + os.Remove(path) + } + } + return + }) +} + +func (f *fileLogger) Destroy() { + f.fileWriter.Close() +} + +func init() { + Register(AdapterFile, &fileLogger{ + Daily: true, + MaxDays: 7, + Append: true, + LogLevel: LevelDebug, + PermitMask: "0777", + MaxLines: 10, + MaxSize: 10 * 1024 * 1024, + }) +} diff --git a/utils/logger/log.go b/utils/logger/log.go new file mode 100644 index 0000000..79f1b15 --- /dev/null +++ b/utils/logger/log.go @@ -0,0 +1,472 @@ +package logger + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "os" + "runtime" + "strings" + "sync" + "time" +) + +// 默认日志输出 +var defaultLogger *LocalLogger +var appName = "app" + +// 日志等级,从0-7,日优先级由高到低 +const ( + LevelEmergency = iota // 系统级紧急,比如磁盘出错,内存异常,网络不可用等 + LevelAlert // 系统级警告,比如数据库访问异常,配置文件出错等 + LevelCritical // 系统级危险,比如权限出错,访问异常等 + LevelError // 用户级错误 + LevelWarning // 用户级警告 + LevelInformational // 用户级信息 + LevelDebug // 用户级调试 + LevelTrace // 用户级基本输出 +) + +// 日志等级和描述映射关系 +var LevelMap = map[string]int{ + "EMER": LevelEmergency, + "ALRT": LevelAlert, + "CRIT": LevelCritical, + "EROR": LevelError, + "WARN": LevelWarning, + "INFO": LevelInformational, + "DEBG": LevelDebug, + "TRAC": LevelTrace, +} + +// 注册实现的适配器, 当前支持控制台,文件和网络输出 +var adapters = make(map[string]Logger) + +// 日志记录等级字段 +var levelPrefix = [LevelTrace + 1]string{ + "EMER", + "ALRT", + "CRIT", + "EROR", + "WARN", + "INFO", + "DEBG", + "TRAC", +} + +const ( + logTimeDefaultFormat = "2006-01-02 15:04:05" // 日志输出默认格式 + AdapterConsole = "console" // 控制台输出配置项 + AdapterFile = "file" // 文件输出配置项 + AdapterConn = "conn" // 网络输出配置项 +) + +// log provider interface +type Logger interface { + Init(config string, appName string) error + LogWrite(when time.Time, msg interface{}, level int) error + Destroy() +} + +// 日志输出适配器注册,log需要实现Init,LogWrite,Destroy方法 +func Register(name string, log Logger) { + if log == nil { + panic("logs: Register provide is nil") + } + if _, ok := adapters[name]; ok { + panic("logs: Register called twice for provider " + name) + } + adapters[name] = log +} + +type loginfo struct { + Time string + Level string + Path string + Name string + Content string +} + +type nameLogger struct { + Logger + name string + config string +} + +type LocalLogger struct { + lock sync.RWMutex + init bool + outputs []*nameLogger + appName string + callDepth int + timeFormat string + usePath string +} + +func NewLogger(depth ...int) *LocalLogger { + dep := append(depth, 2)[0] + l := new(LocalLogger) + // appName用于记录网络传输时标记的程序发送方, + // 通过环境变量APPSN进行设置,默认为NONE,此时无法通过网络日志检索区分不同服务发送方 + appSn := os.Getenv("APPSN") + if appSn == "" { + appSn = "NONE" + } + l.appName = "[" + appSn + "]" + l.callDepth = dep + l.SetLogger(AdapterConsole) + l.timeFormat = logTimeDefaultFormat + return l +} + +//配置文件 +type logConfig struct { + TimeFormat string `json:"TimeFormat"` + Console *consoleLogger `json:"Console,omitempty"` + File *fileLogger `json:"File,omitempty"` + Conn *connLogger `json:"Conn,omitempty"` +} + +func init() { + defaultLogger = NewLogger(3) +} + +func (this *LocalLogger) SetLogger(adapterName string, configs ...string) error { + this.lock.Lock() + defer this.lock.Unlock() + + if !this.init { + this.outputs = []*nameLogger{} + this.init = true + } + + config := append(configs, "{}")[0] + var num int = -1 + var i int + var l *nameLogger + for i, l = range this.outputs { + if l.name == adapterName { + if l.config == config { + //配置没有变动,不重新设置 + return fmt.Errorf("you have set same config for this adaptername %s", adapterName) + } + l.Logger.Destroy() + num = i + break + } + } + logger, ok := adapters[adapterName] + if !ok { + return fmt.Errorf("unknown adaptername %s (forgotten Register?)", adapterName) + } + + err := logger.Init(config, appName) + if err != nil { + fmt.Fprintf(os.Stdout, "logger Init <%s> err:%v, %s output ignore!\n", + adapterName, err, adapterName) + return err + } + if num >= 0 { + this.outputs[i] = &nameLogger{name: adapterName, Logger: logger, config: config} + return nil + } + this.outputs = append(this.outputs, &nameLogger{name: adapterName, Logger: logger, config: config}) + return nil +} + +func (this *LocalLogger) DelLogger(adapterName string) error { + this.lock.Lock() + defer this.lock.Unlock() + outputs := []*nameLogger{} + for _, lg := range this.outputs { + if lg.name == adapterName { + lg.Destroy() + } else { + outputs = append(outputs, lg) + } + } + if len(outputs) == len(this.outputs) { + return fmt.Errorf("logs: unknown adaptername %s (forgotten Register?)", adapterName) + } + this.outputs = outputs + return nil +} + +// 设置日志起始路径 +func (this *LocalLogger) SetLogPathTrim(trimPath string) { + this.usePath = trimPath +} + +func (this *LocalLogger) writeToLoggers(when time.Time, msg *loginfo, level int) { + this.lock.RLock() + defer this.lock.RUnlock() + for _, l := range this.outputs { + if l.name == AdapterConn { + //网络日志,使用json格式发送,此处使用结构体,用于类似ElasticSearch功能检索 + err := l.LogWrite(when, msg, level) + if err != nil { + fmt.Fprintf(os.Stdout, "unable to WriteMsg to adapter:%v,error:%v\n", l.name, err) + } + continue + } + + msgStr := when.Format(this.timeFormat) + " [" + msg.Level + "] " + "[" + msg.Path + "] " + msg.Content + err := l.LogWrite(when, msgStr, level) + if err != nil { + fmt.Fprintf(os.Stdout, "unable to WriteMsg to adapter:%v,error:%v\n", l.name, err) + } + } +} + +func (this *LocalLogger) writeMsg(logLevel int, msg string, v ...interface{}) error { + if !this.init { + this.SetLogger(AdapterConsole) + } + msgSt := new(loginfo) + src := "" + if len(v) > 0 { + msg = fmt.Sprintf(msg, v...) + } + when := time.Now() + _, file, lineno, ok := runtime.Caller(this.callDepth) + var strim string = "src/" + if this.usePath != "" { + strim = this.usePath + } + if ok { + + src = strings.Replace( + fmt.Sprintf("%s:%d", stringTrim(file, strim), lineno), "%2e", ".", -1) + } + + msgSt.Level = levelPrefix[logLevel] + msgSt.Path = src + msgSt.Content = msg + msgSt.Name = this.appName + msgSt.Time = when.Format(this.timeFormat) + this.writeToLoggers(when, msgSt, logLevel) + + return nil +} + +func (this *LocalLogger) Fatal(format string, args ...interface{}) { + this.Emer("###Exec Panic:"+format, args...) + os.Exit(1) +} + +func (this *LocalLogger) Panic(format string, args ...interface{}) { + this.Emer("###Exec Panic:"+format, args...) + panic(fmt.Sprintf(format, args...)) +} + +// Emer Log EMERGENCY level message. +func (this *LocalLogger) Emer(format string, v ...interface{}) { + this.writeMsg(LevelEmergency, format, v...) +} + +// Alert Log ALERT level message. +func (this *LocalLogger) Alert(format string, v ...interface{}) { + this.writeMsg(LevelAlert, format, v...) +} + +// Crit Log CRITICAL level message. +func (this *LocalLogger) Crit(format string, v ...interface{}) { + this.writeMsg(LevelCritical, format, v...) +} + +// Error Log ERROR level message. +func (this *LocalLogger) Error(format string, v ...interface{}) { + this.writeMsg(LevelError, format, v...) +} + +// Warn Log WARNING level message. +func (this *LocalLogger) Warn(format string, v ...interface{}) { + this.writeMsg(LevelWarning, format, v...) +} + +// Info Log INFO level message. +func (this *LocalLogger) Info(format string, v ...interface{}) { + this.writeMsg(LevelInformational, format, v...) +} + +// Debug Log DEBUG level message. +func (this *LocalLogger) Debug(format string, v ...interface{}) { + this.writeMsg(LevelDebug, format, v...) +} + +// Trace Log TRAC level message. +func (this *LocalLogger) Trace(format string, v ...interface{}) { + this.writeMsg(LevelTrace, format, v...) +} + +func (this *LocalLogger) Close() { + + for _, l := range this.outputs { + l.Destroy() + } + this.outputs = nil + +} + +func (this *LocalLogger) Reset() { + this.lock.Lock() + defer this.lock.Unlock() + for _, l := range this.outputs { + l.Destroy() + } + this.outputs = nil +} + +func (this *LocalLogger) SetCallDepth(depth int) { + this.callDepth = depth +} + +// GetlocalLogger returns the defaultLogger +func GetlocalLogger() *LocalLogger { + return defaultLogger +} + +// Reset will remove all the adapter +func Reset() { + defaultLogger.Reset() +} + +func SetLogPathTrim(trimPath string) { + defaultLogger.SetLogPathTrim(trimPath) +} + +// param 可以是log配置文件名,也可以是log配置内容,默认DEBUG输出到控制台 +func SetLogger(param ...string) error { + if 0 == len(param) { + //默认只输出到控制台 + defaultLogger.SetLogger(AdapterConsole) + return nil + } + + c := param[0] + if len(param) > 1 { + appName = param[1] + } + conf := new(logConfig) + err := json.Unmarshal([]byte(c), conf) + if err != nil { //不是json,就认为是配置文件,如果都不是,打印日志,然后退出 + // Open the configuration file + fd, err := os.Open(c) + if err != nil { + fmt.Fprintf(os.Stdout, "Could not open %s for configure: %s\n", c, err) + os.Exit(1) + return err + } + + contents, err := ioutil.ReadAll(fd) + if err != nil { + fmt.Fprintf(os.Stdout, "Could not read %s: %s\n", c, err) + os.Exit(1) + return err + } + err = json.Unmarshal(contents, conf) + if err != nil { + fmt.Fprintf(os.Stdout, "Could not Unmarshal %s: %s\n", contents, err) + os.Exit(1) + return err + } + } + if conf.TimeFormat != "" { + defaultLogger.timeFormat = conf.TimeFormat + } + if conf.Console != nil { + console, _ := json.Marshal(conf.Console) + defaultLogger.SetLogger(AdapterConsole, string(console)) + } + if conf.File != nil { + file, _ := json.Marshal(conf.File) + defaultLogger.SetLogger(AdapterFile, string(file)) + } + if conf.Conn != nil { + conn, _ := json.Marshal(conf.Conn) + defaultLogger.SetLogger(AdapterConn, string(conn)) + } + return nil +} + +// Painc logs a message at emergency level and panic. +func Painc(f interface{}, v ...interface{}) { + defaultLogger.Panic(formatLog(f, v...)) +} + +// Fatal logs a message at emergency level and exit. +func Fatal(f interface{}, v ...interface{}) { + defaultLogger.Fatal(formatLog(f, v...)) +} + +// Emer logs a message at emergency level. +func Emer(f interface{}, v ...interface{}) { + defaultLogger.Emer(formatLog(f, v...)) +} + +// Alert logs a message at alert level. +func Alert(f interface{}, v ...interface{}) { + defaultLogger.Alert(formatLog(f, v...)) +} + +// Crit logs a message at critical level. +func Crit(f interface{}, v ...interface{}) { + defaultLogger.Crit(formatLog(f, v...)) +} + +// Error logs a message at error level. +func Error(f interface{}, v ...interface{}) { + defaultLogger.Error(formatLog(f, v...)) +} + +// Warn logs a message at warning level. +func Warn(f interface{}, v ...interface{}) { + defaultLogger.Warn(formatLog(f, v...)) +} + +// Info logs a message at info level. +func Info(f interface{}, v ...interface{}) { + defaultLogger.Info(formatLog(f, v...)) +} + +// Notice logs a message at debug level. +func Debug(f interface{}, v ...interface{}) { + defaultLogger.Debug(formatLog(f, v...)) +} + +// Trace logs a message at trace level. +func Trace(f interface{}, v ...interface{}) { + defaultLogger.Trace(formatLog(f, v...)) +} + +func formatLog(f interface{}, v ...interface{}) string { + var msg string + switch f.(type) { + case string: + msg = f.(string) + if len(v) == 0 { + return msg + } + if strings.Contains(msg, "%") && !strings.Contains(msg, "%%") { + //format string + } else { + //do not contain format char + msg += strings.Repeat(" %v", len(v)) + } + default: + msg = fmt.Sprint(f) + if len(v) == 0 { + return msg + } + msg += strings.Repeat(" %v", len(v)) + } + return fmt.Sprintf(msg, v...) +} + +func stringTrim(s string, cut string) string { + ss := strings.SplitN(s, cut, 2) + if 1 == len(ss) { + return ss[0] + } + return ss[1] +} diff --git a/utils/snowflake.go b/utils/snowflake.go new file mode 100644 index 0000000..3b94ba5 --- /dev/null +++ b/utils/snowflake.go @@ -0,0 +1,76 @@ +package utils + +import ( + "fmt" + "github.com/golang/glog" + "sync" + "time" +) + +type Snowflake struct { + *sync.Mutex // 锁 + timestamp int64 // 时间戳 ,毫秒 + workerid int64 // 工作节点 + datacenterid int64 // 数据中心机房id + sequence int64 // 序列号 +} +const ( + epoch = int64(1577808000000) // 设置起始时间(时间戳/毫秒):2020-01-01 00:00:00,有效期69年 + timestampBits = uint(41) // 时间戳占用位数 + datacenteridBits = uint(2) // 数据中心id所占位数 + workeridBits = uint(7) // 机器id所占位数 + sequenceBits = uint(12) // 序列所占的位数 + timestampMax = int64(-1 ^ (-1 << timestampBits)) // 时间戳最大值 + datacenteridMax = int64(-1 ^ (-1 << datacenteridBits)) // 支持的最大数据中心id数量 + workeridMax = int64(-1 ^ (-1 << workeridBits)) // 支持的最大机器id数量 + sequenceMask = int64(-1 ^ (-1 << sequenceBits)) // 支持的最大序列id数量 + workeridShift = sequenceBits // 机器id左移位数 + datacenteridShift = sequenceBits + workeridBits // 数据中心id左移位数 + timestampShift = sequenceBits + workeridBits + datacenteridBits // 时间戳左移位数 +) + + + +func NewSnowflake(workerid, datacenterid int64) *Snowflake { + return &Snowflake{ + Mutex: new(sync.Mutex), + timestamp: time.Now().UnixNano() / 1000000, + workerid: workerid, + datacenterid: datacenterid, + sequence: 0, + } +} + +func (s *Snowflake) NextValStr() string { + return fmt.Sprintf("%d", s.NextVal()) +} + + +func (s *Snowflake) NextVal() int64 { + s.Lock() + now := time.Now().UnixNano() / 1000000 // 转毫秒 + if s.timestamp == now { + // 当同一时间戳(精度:毫秒)下多次生成id会增加序列号 + s.sequence = (s.sequence + 1) & sequenceMask + if s.sequence == 0 { + // 如果当前序列超出12bit长度,则需要等待下一毫秒 + // 下一毫秒将使用sequence:0 + for now <= s.timestamp { + now = time.Now().UnixNano() / 1000000 + } + } + } else { + // 不同时间戳(精度:毫秒)下直接使用序列号:0 + s.sequence = 0 + } + t := now - epoch + if t > timestampMax { + s.Unlock() + glog.Errorf("epoch must be between 0 and %d", timestampMax-1) + return 0 + } + s.timestamp = now + r := int64((t)<