mirror of
https://github.com/bufanyun/hotgo.git
synced 2025-10-13 13:33:44 +08:00
This commit is contained in:
@@ -3,7 +3,6 @@ package tcp
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/v2/encoding/gjson"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/net/gtcp"
|
||||
@@ -36,6 +35,7 @@ type Client struct {
|
||||
IsLogin bool // 是否已登录
|
||||
addr string
|
||||
auth *AuthMeta
|
||||
rpc *Rpc
|
||||
timeout time.Duration
|
||||
connectInterval time.Duration
|
||||
maxConnectCount uint
|
||||
@@ -103,6 +103,7 @@ func NewClient(config *ClientConfig) (client *Client, err error) {
|
||||
client.timeout = config.Timeout
|
||||
}
|
||||
|
||||
client.rpc = NewRpc(client.Ctx)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -248,7 +249,31 @@ func (client *Client) read() {
|
||||
client.Logger.Debugf(client.Ctx, "client RecvPkg invalid message: %+v", msg)
|
||||
continue
|
||||
}
|
||||
f(msg.Data, client.conn)
|
||||
|
||||
switch msg.Router {
|
||||
case "ResponseServerLogin", "ResponseServerHeartbeat": // 服务登录、心跳无需验证签名
|
||||
ctx, cancel := initCtx(gctx.New(), &Context{})
|
||||
doHandleRouterMsg(f, ctx, cancel, msg.Data)
|
||||
default: // 通用路由消息处理
|
||||
in, err := VerifySign(msg.Data, client.auth.AppId, client.auth.SecretKey)
|
||||
if err != nil {
|
||||
client.Logger.Warningf(client.Ctx, "client read VerifySign err:%+v message: %+v", err, msg)
|
||||
continue
|
||||
}
|
||||
|
||||
ctx, cancel := initCtx(gctx.New(), &Context{
|
||||
Conn: client.conn,
|
||||
Auth: client.auth,
|
||||
TraceID: in.TraceID,
|
||||
})
|
||||
|
||||
// 响应rpc消息
|
||||
if client.rpc.HandleMsg(ctx, cancel, msg.Data) {
|
||||
return
|
||||
}
|
||||
|
||||
doHandleRouterMsg(f, ctx, cancel, msg.Data)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -307,16 +332,45 @@ func (client *Client) Write(data interface{}) error {
|
||||
return gerror.New("client Write message is nil")
|
||||
}
|
||||
|
||||
// 签名
|
||||
SetSign(data, gctx.CtxId(client.Ctx), client.auth.AppId, client.auth.SecretKey)
|
||||
|
||||
msgType := reflect.TypeOf(data)
|
||||
if msgType == nil || msgType.Kind() != reflect.Ptr {
|
||||
return gerror.Newf("client json message pointer required: %+v", data)
|
||||
}
|
||||
msg := &Message{Router: msgType.Elem().Name(), Data: data}
|
||||
|
||||
client.Logger.Debugf(client.Ctx, "client Write Router:%v, data:%+v", msg.Router, gjson.New(data).String())
|
||||
|
||||
return SendPkg(client.conn, msg)
|
||||
}
|
||||
|
||||
// Send 发送消息
|
||||
func (client *Client) Send(ctx context.Context, data interface{}) error {
|
||||
MsgPkg(data, client.auth, gctx.CtxId(ctx))
|
||||
return client.Write(data)
|
||||
}
|
||||
|
||||
// Reply 回复消息
|
||||
func (client *Client) Reply(ctx context.Context, data interface{}) (err error) {
|
||||
user := GetCtx(ctx)
|
||||
if user == nil {
|
||||
err = gerror.New("获取回复用户信息失败")
|
||||
return
|
||||
}
|
||||
MsgPkg(data, client.auth, user.TraceID)
|
||||
return client.Write(data)
|
||||
}
|
||||
|
||||
// RpcRequest 发送消息并等待响应结果
|
||||
func (client *Client) RpcRequest(ctx context.Context, data interface{}) (res interface{}, err error) {
|
||||
var (
|
||||
traceID = MsgPkg(data, client.auth, gctx.CtxId(ctx))
|
||||
key = client.rpc.GetCallId(client.conn, traceID)
|
||||
)
|
||||
|
||||
if traceID == "" {
|
||||
err = gerror.New("traceID is required")
|
||||
return
|
||||
}
|
||||
|
||||
return client.rpc.Request(key, func() {
|
||||
client.Write(data)
|
||||
})
|
||||
}
|
||||
|
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/v2/os/gcron"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"hotgo/internal/consts"
|
||||
)
|
||||
|
||||
func (client *Client) getCronKey(s string) string {
|
||||
@@ -19,19 +20,19 @@ func (client *Client) stopCron() {
|
||||
|
||||
func (client *Client) startCron() {
|
||||
// 心跳超时检查
|
||||
if gcron.Search(client.getCronKey(cronHeartbeatVerify)) == nil {
|
||||
if gcron.Search(client.getCronKey(consts.TCPCronHeartbeatVerify)) == nil {
|
||||
gcron.AddSingleton(client.Ctx, "@every 600s", func(ctx context.Context) {
|
||||
if client.heartbeat < gtime.Timestamp()-600 {
|
||||
client.Logger.Debugf(client.Ctx, "client heartbeat timeout, about to reconnect..")
|
||||
client.Logger.Debugf(client.Ctx, "client heartbeat timeout, about to reconnect..")
|
||||
client.Destroy()
|
||||
}
|
||||
}, client.getCronKey(cronHeartbeatVerify))
|
||||
}, client.getCronKey(consts.TCPCronHeartbeatVerify))
|
||||
}
|
||||
|
||||
// 心跳
|
||||
if gcron.Search(client.getCronKey(cronHeartbeat)) == nil {
|
||||
if gcron.Search(client.getCronKey(consts.TCPCronHeartbeat)) == nil {
|
||||
gcron.AddSingleton(client.Ctx, "@every 120s", func(ctx context.Context) {
|
||||
client.serverHeartbeat()
|
||||
}, client.getCronKey(cronHeartbeat))
|
||||
}, client.getCronKey(consts.TCPCronHeartbeat))
|
||||
}
|
||||
}
|
||||
|
@@ -1,16 +1,19 @@
|
||||
package tcp
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"context"
|
||||
"github.com/gogf/gf/v2/os/gctx"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"hotgo/internal/consts"
|
||||
"hotgo/internal/model/input/msgin"
|
||||
)
|
||||
|
||||
// serverLogin 心跳
|
||||
func (client *Client) serverHeartbeat() {
|
||||
if err := client.Write(&msgin.ServerHeartbeat{}); err != nil {
|
||||
client.Logger.Debugf(client.Ctx, "client WriteMsg ServerHeartbeat err:%+v", err)
|
||||
ctx := gctx.New()
|
||||
if err := client.Send(ctx, &msgin.ServerHeartbeat{}); err != nil {
|
||||
client.Logger.Debugf(ctx, "client WriteMsg ServerHeartbeat err:%+v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -22,40 +25,45 @@ func (client *Client) serverLogin() {
|
||||
Name: client.auth.Name,
|
||||
}
|
||||
|
||||
if err := client.Write(data); err != nil {
|
||||
client.Logger.Debugf(client.Ctx, "client WriteMsg ServerLogin err:%+v", err)
|
||||
ctx := gctx.New()
|
||||
if err := client.Send(ctx, data); err != nil {
|
||||
client.Logger.Debugf(ctx, "client WriteMsg ServerLogin err:%+v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (client *Client) onResponseServerLogin(ctx context.Context, args ...interface{}) {
|
||||
var in *msgin.ResponseServerLogin
|
||||
if err := gconv.Scan(args[0], &in); err != nil {
|
||||
client.Logger.Infof(ctx, "onResponseServerLogin message Scan failed:%+v, args:%+v", err, args[0])
|
||||
return
|
||||
}
|
||||
|
||||
if in.Code != consts.TCPMsgCodeSuccess {
|
||||
client.IsLogin = false
|
||||
client.Logger.Warningf(ctx, "onResponseServerLogin quit err:%v", in.Message)
|
||||
client.Destroy()
|
||||
return
|
||||
}
|
||||
|
||||
client.IsLogin = true
|
||||
|
||||
if client.loginEvent != nil {
|
||||
client.loginEvent()
|
||||
}
|
||||
}
|
||||
|
||||
func (client *Client) onResponseServerLogin(args ...interface{}) {
|
||||
var in *msgin.ResponseServerLogin
|
||||
if err := gconv.Scan(args[0], &in); err != nil {
|
||||
client.Logger.Infof(client.Ctx, "onResponseServerLogin message Scan failed:%+v, args:%+v", err, args[0])
|
||||
return
|
||||
}
|
||||
client.Logger.Infof(client.Ctx, "onResponseServerLogin in:%+v", *in)
|
||||
|
||||
if in.Code != gcode.CodeOK.Code() {
|
||||
client.IsLogin = false
|
||||
client.Logger.Warningf(client.Ctx, "onResponseServerLogin quit err:%v", in.Message)
|
||||
client.Destroy()
|
||||
return
|
||||
}
|
||||
client.IsLogin = true
|
||||
}
|
||||
|
||||
func (client *Client) onResponseServerHeartbeat(args ...interface{}) {
|
||||
func (client *Client) onResponseServerHeartbeat(ctx context.Context, args ...interface{}) {
|
||||
var in *msgin.ResponseServerHeartbeat
|
||||
if err := gconv.Scan(args[0], &in); err != nil {
|
||||
client.Logger.Infof(client.Ctx, "onResponseServerHeartbeat message Scan failed:%+v, args:%+v", err, args)
|
||||
client.Logger.Infof(ctx, "onResponseServerHeartbeat message Scan failed:%+v, args:%+v", err, args)
|
||||
return
|
||||
}
|
||||
|
||||
if in.Code != consts.TCPMsgCodeSuccess {
|
||||
client.Logger.Warningf(ctx, "onResponseServerHeartbeat err:%v", in.Message)
|
||||
return
|
||||
}
|
||||
|
||||
client.heartbeat = gtime.Timestamp()
|
||||
client.Logger.Infof(client.Ctx, "onResponseServerHeartbeat in:%+v", *in)
|
||||
}
|
||||
|
57
server/internal/library/network/tcp/context.go
Normal file
57
server/internal/library/network/tcp/context.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package tcp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/gogf/gf/v2/net/gtcp"
|
||||
"github.com/gogf/gf/v2/net/gtrace"
|
||||
"hotgo/internal/consts"
|
||||
)
|
||||
|
||||
// initCtx 初始化上下文对象指针到上下文对象中,以便后续的请求流程中可以修改
|
||||
func initCtx(ctx context.Context, model *Context) (newCtx context.Context, cancel context.CancelFunc) {
|
||||
if model.TraceID != "" {
|
||||
newCtx, _ = gtrace.WithTraceID(ctx, model.TraceID)
|
||||
} else {
|
||||
newCtx = ctx
|
||||
}
|
||||
newCtx = context.WithValue(newCtx, consts.ContextTCPKey, model)
|
||||
newCtx, cancel = context.WithCancel(newCtx)
|
||||
return
|
||||
}
|
||||
|
||||
// SetCtx 设置上下文变量
|
||||
func SetCtx(ctx context.Context, model *Context) {
|
||||
context.WithValue(ctx, consts.ContextTCPKey, model)
|
||||
}
|
||||
|
||||
// GetCtx 获得上下文变量,如果没有设置,那么返回nil
|
||||
func GetCtx(ctx context.Context) *Context {
|
||||
value := ctx.Value(consts.ContextTCPKey)
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
if localCtx, ok := value.(*Context); ok {
|
||||
return localCtx
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetCtxConn .
|
||||
func GetCtxConn(ctx context.Context) *gtcp.Conn {
|
||||
c := GetCtx(ctx)
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return c.Conn
|
||||
}
|
||||
|
||||
// GetCtxAuth 认证元数据
|
||||
func GetCtxAuth(ctx context.Context) *AuthMeta {
|
||||
c := GetCtx(ctx)
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return c.Auth
|
||||
}
|
@@ -1,19 +1,8 @@
|
||||
package tcp
|
||||
|
||||
import "github.com/gogf/gf/v2/os/gtime"
|
||||
|
||||
// 定时任务
|
||||
const (
|
||||
cronHeartbeatVerify = "tcpHeartbeatVerify"
|
||||
cronHeartbeat = "tcpHeartbeat"
|
||||
cronAuthVerify = "tcpAuthVerify"
|
||||
)
|
||||
|
||||
// 认证分组
|
||||
const (
|
||||
ClientGroupCron = "cron" // 定时任务
|
||||
ClientGroupQueue = "queue" // 消息队列
|
||||
ClientGroupAuth = "auth" // 服务授权
|
||||
import (
|
||||
"github.com/gogf/gf/v2/net/gtcp"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
)
|
||||
|
||||
// AuthMeta 认证元数据
|
||||
@@ -25,5 +14,11 @@ type AuthMeta struct {
|
||||
EndAt *gtime.Time `json:"-"`
|
||||
}
|
||||
|
||||
type Context struct {
|
||||
Conn *gtcp.Conn `json:"conn"`
|
||||
Auth *AuthMeta `json:"auth"` // 认证元数据
|
||||
TraceID string `json:"traceID"` // 链路ID
|
||||
}
|
||||
|
||||
// CallbackEvent 回调事件
|
||||
type CallbackEvent func()
|
||||
|
22
server/internal/library/network/tcp/response.go
Normal file
22
server/internal/library/network/tcp/response.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package tcp
|
||||
|
||||
type Response interface {
|
||||
PkgResponse()
|
||||
GetError() (err error)
|
||||
}
|
||||
|
||||
// PkgResponse 打包响应消息
|
||||
func PkgResponse(data interface{}) {
|
||||
if c, ok := data.(Response); ok {
|
||||
c.PkgResponse()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// GetResponseError 解析响应消息中的错误
|
||||
func GetResponseError(data interface{}) (err error) {
|
||||
if c, ok := data.(Response); ok {
|
||||
return c.GetError()
|
||||
}
|
||||
return
|
||||
}
|
@@ -1,13 +1,17 @@
|
||||
package tcp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/net/gtcp"
|
||||
"github.com/gogf/gf/v2/os/grpool"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
type RouterHandler func(args ...interface{})
|
||||
var GoPool = grpool.New(100)
|
||||
|
||||
type RouterHandler func(ctx context.Context, args ...interface{})
|
||||
|
||||
// Message 路由消息
|
||||
type Message struct {
|
||||
@@ -37,3 +41,26 @@ func RecvPkg(conn *gtcp.Conn) (*Message, error) {
|
||||
return msg, err
|
||||
}
|
||||
}
|
||||
|
||||
// MsgPkg 打包消息
|
||||
func MsgPkg(data interface{}, auth *AuthMeta, traceID string) string {
|
||||
// 打包签名
|
||||
msg := PkgSign(data, auth.AppId, auth.SecretKey, traceID)
|
||||
|
||||
// 打包响应消息
|
||||
PkgResponse(data)
|
||||
|
||||
if msg == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return msg.TraceID
|
||||
}
|
||||
|
||||
// doHandleRouterMsg 处理路由消息
|
||||
func doHandleRouterMsg(fun RouterHandler, ctx context.Context, cancel context.CancelFunc, args ...interface{}) {
|
||||
GoPool.Add(ctx, func(ctx context.Context) {
|
||||
fun(ctx, args...)
|
||||
cancel()
|
||||
})
|
||||
}
|
||||
|
103
server/internal/library/network/tcp/rpc.go
Normal file
103
server/internal/library/network/tcp/rpc.go
Normal file
@@ -0,0 +1,103 @@
|
||||
package tcp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/net/gtcp"
|
||||
"hotgo/internal/consts"
|
||||
"hotgo/utility/simple"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Rpc struct {
|
||||
ctx context.Context
|
||||
mutex sync.Mutex
|
||||
callbacks map[string]RpcRespFunc
|
||||
}
|
||||
|
||||
type RpcResp struct {
|
||||
res interface{}
|
||||
err error
|
||||
}
|
||||
|
||||
type RpcRespFunc func(resp interface{}, err error)
|
||||
|
||||
func NewRpc(ctx context.Context) *Rpc {
|
||||
return &Rpc{
|
||||
ctx: ctx,
|
||||
callbacks: make(map[string]RpcRespFunc),
|
||||
}
|
||||
}
|
||||
|
||||
// GetCallId 获取回调id
|
||||
func (r *Rpc) GetCallId(client *gtcp.Conn, traceID string) string {
|
||||
return fmt.Sprintf("%v.%v", client.LocalAddr().String(), traceID)
|
||||
}
|
||||
|
||||
// HandleMsg 处理rpc消息
|
||||
func (r *Rpc) HandleMsg(ctx context.Context, cancel context.CancelFunc, data interface{}) bool {
|
||||
user := GetCtx(ctx)
|
||||
callId := r.GetCallId(user.Conn, user.TraceID)
|
||||
|
||||
if call, ok := r.callbacks[callId]; ok {
|
||||
r.mutex.Lock()
|
||||
delete(r.callbacks, callId)
|
||||
r.mutex.Unlock()
|
||||
|
||||
simple.SafeGo(ctx, func(ctx context.Context) {
|
||||
call(data, nil)
|
||||
cancel()
|
||||
})
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Request 发起rpc请求
|
||||
func (r *Rpc) Request(callId string, send func()) (res interface{}, err error) {
|
||||
var (
|
||||
waitCh = make(chan struct{})
|
||||
resCh = make(chan RpcResp, 1)
|
||||
isClose = false
|
||||
)
|
||||
|
||||
defer func() {
|
||||
isClose = true
|
||||
close(resCh)
|
||||
|
||||
// 移除消息
|
||||
if _, ok := r.callbacks[callId]; ok {
|
||||
r.mutex.Lock()
|
||||
delete(r.callbacks, callId)
|
||||
r.mutex.Unlock()
|
||||
}
|
||||
}()
|
||||
|
||||
simple.SafeGo(r.ctx, func(ctx context.Context) {
|
||||
close(waitCh)
|
||||
|
||||
// 加入回调
|
||||
r.mutex.Lock()
|
||||
r.callbacks[callId] = func(res interface{}, err error) {
|
||||
if !isClose {
|
||||
resCh <- RpcResp{res: res, err: err}
|
||||
}
|
||||
}
|
||||
r.mutex.Unlock()
|
||||
|
||||
// 发送消息
|
||||
send()
|
||||
})
|
||||
|
||||
<-waitCh
|
||||
select {
|
||||
case <-time.After(consts.TCPRpcTimeout):
|
||||
err = gerror.New("rpc response timeout")
|
||||
return
|
||||
case got := <-resCh:
|
||||
return got.res, got.err
|
||||
}
|
||||
}
|
@@ -3,12 +3,12 @@ package tcp
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/v2/encoding/gjson"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/net/gtcp"
|
||||
"github.com/gogf/gf/v2/os/gctx"
|
||||
"github.com/gogf/gf/v2/os/glog"
|
||||
"hotgo/internal/consts"
|
||||
"reflect"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -31,6 +31,7 @@ type Server struct {
|
||||
Logger *glog.Logger
|
||||
addr string
|
||||
name string
|
||||
rpc *Rpc
|
||||
ln *gtcp.Server
|
||||
wgLn sync.WaitGroup
|
||||
mutex sync.Mutex
|
||||
@@ -72,6 +73,7 @@ func NewServer(config *ServerConfig) (server *Server, err error) {
|
||||
return
|
||||
}
|
||||
server.Logger = logger
|
||||
server.rpc = NewRpc(server.Ctx)
|
||||
|
||||
server.startCron()
|
||||
|
||||
@@ -100,13 +102,19 @@ func (server *Server) accept(conn *gtcp.Conn) {
|
||||
|
||||
switch msg.Router {
|
||||
case "ServerLogin": // 服务登录
|
||||
server.onServerLogin(msg.Data, conn)
|
||||
// 初始化上下文
|
||||
ctx, cancel := initCtx(gctx.New(), &Context{
|
||||
Conn: conn,
|
||||
})
|
||||
doHandleRouterMsg(server.onServerLogin, ctx, cancel, msg.Data)
|
||||
case "ServerHeartbeat": // 心跳
|
||||
if client == nil {
|
||||
server.Logger.Infof(server.Ctx, "conn not connected, ignore the heartbeat, msg:%+v", msg)
|
||||
continue
|
||||
}
|
||||
server.onServerHeartbeat(msg, client)
|
||||
// 初始化上下文
|
||||
ctx, cancel := initCtx(gctx.New(), &Context{})
|
||||
doHandleRouterMsg(server.onServerHeartbeat, ctx, cancel, msg.Data, client)
|
||||
default: // 通用路由消息处理
|
||||
if client == nil {
|
||||
server.Logger.Warningf(server.Ctx, "conn is not logged in but sends a routing message. actively conn disconnect, msg:%+v", msg)
|
||||
@@ -121,14 +129,25 @@ func (server *Server) accept(conn *gtcp.Conn) {
|
||||
|
||||
// handleRouterMsg 处理路由消息
|
||||
func (server *Server) handleRouterMsg(msg *Message, client *ClientConn) {
|
||||
|
||||
// 验证签名
|
||||
err := VerifySign(msg.Data, client.Auth.AppId, client.Auth.SecretKey)
|
||||
in, err := VerifySign(msg.Data, client.Auth.AppId, client.Auth.SecretKey)
|
||||
if err != nil {
|
||||
server.Logger.Warningf(server.Ctx, "handleRouterMsg VerifySign err:%+v message: %+v", err, msg)
|
||||
return
|
||||
}
|
||||
|
||||
// 初始化上下文
|
||||
ctx, cancel := initCtx(gctx.New(), &Context{
|
||||
Conn: client.Conn,
|
||||
Auth: client.Auth,
|
||||
TraceID: in.TraceID,
|
||||
})
|
||||
|
||||
// 响应rpc消息
|
||||
if server.rpc.HandleMsg(ctx, cancel, msg.Data) {
|
||||
return
|
||||
}
|
||||
|
||||
handle := func(routers map[string]RouterHandler, group string) {
|
||||
if routers == nil {
|
||||
server.Logger.Debugf(server.Ctx, "handleRouterMsg route is not initialized %v message: %+v", group, msg)
|
||||
@@ -139,15 +158,16 @@ func (server *Server) handleRouterMsg(msg *Message, client *ClientConn) {
|
||||
server.Logger.Debugf(server.Ctx, "handleRouterMsg invalid %v message: %+v", group, msg)
|
||||
return
|
||||
}
|
||||
f(msg.Data, client)
|
||||
|
||||
doHandleRouterMsg(f, ctx, cancel, msg.Data)
|
||||
}
|
||||
|
||||
switch client.Auth.Group {
|
||||
case ClientGroupCron:
|
||||
case consts.TCPClientGroupCron:
|
||||
handle(server.cronRouters, client.Auth.Group)
|
||||
case ClientGroupQueue:
|
||||
case consts.TCPClientGroupQueue:
|
||||
handle(server.queueRouters, client.Auth.Group)
|
||||
case ClientGroupAuth:
|
||||
case consts.TCPClientGroupAuth:
|
||||
handle(server.authRouters, client.Auth.Group)
|
||||
default:
|
||||
server.Logger.Warningf(server.Ctx, "group is not registered: %+v", client.Auth.Group)
|
||||
@@ -173,6 +193,16 @@ func (server *Server) getAppIdClients(appid string) (list []*ClientConn) {
|
||||
return
|
||||
}
|
||||
|
||||
// GetGroupClients 获取指定分组的所有连接
|
||||
func (server *Server) GetGroupClients(group string) (list []*ClientConn) {
|
||||
for _, v := range server.clients {
|
||||
if v.Auth.Group == group {
|
||||
list = append(list, v)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// RegisterAuthRouter 注册授权路由
|
||||
func (server *Server) RegisterAuthRouter(routers map[string]RouterHandler) {
|
||||
server.mutex.Lock()
|
||||
@@ -272,7 +302,39 @@ func (server *Server) Write(conn *gtcp.Conn, data interface{}) (err error) {
|
||||
|
||||
msg := &Message{Router: msgType.Elem().Name(), Data: data}
|
||||
|
||||
server.Logger.Debugf(server.Ctx, "server Write Router:%v, data:%+v", msg.Router, gjson.New(data).String())
|
||||
|
||||
return SendPkg(conn, msg)
|
||||
}
|
||||
|
||||
// Send 发送消息
|
||||
func (server *Server) Send(ctx context.Context, client *ClientConn, data interface{}) (err error) {
|
||||
MsgPkg(data, client.Auth, gctx.CtxId(ctx))
|
||||
return server.Write(client.Conn, data)
|
||||
}
|
||||
|
||||
// Reply 回复消息
|
||||
func (server *Server) Reply(ctx context.Context, data interface{}) (err error) {
|
||||
user := GetCtx(ctx)
|
||||
if user == nil {
|
||||
err = gerror.New("获取回复用户信息失败")
|
||||
return
|
||||
}
|
||||
MsgPkg(data, user.Auth, user.TraceID)
|
||||
return server.Write(user.Conn, data)
|
||||
}
|
||||
|
||||
// RpcRequest 向指定客户端发送消息并等待响应结果
|
||||
func (server *Server) RpcRequest(ctx context.Context, client *ClientConn, data interface{}) (res interface{}, err error) {
|
||||
var (
|
||||
traceID = MsgPkg(data, client.Auth, gctx.CtxId(ctx))
|
||||
key = server.rpc.GetCallId(client.Conn, traceID)
|
||||
)
|
||||
|
||||
if traceID == "" {
|
||||
err = gerror.New("traceID is required")
|
||||
return
|
||||
}
|
||||
|
||||
return server.rpc.Request(key, func() {
|
||||
server.Write(client.Conn, data)
|
||||
})
|
||||
}
|
||||
|
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/v2/os/gcron"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"hotgo/internal/consts"
|
||||
)
|
||||
|
||||
func (server *Server) getCronKey(s string) string {
|
||||
@@ -19,7 +20,7 @@ func (server *Server) stopCron() {
|
||||
|
||||
func (server *Server) startCron() {
|
||||
// 心跳超时检查
|
||||
if gcron.Search(server.getCronKey(cronHeartbeatVerify)) == nil {
|
||||
if gcron.Search(server.getCronKey(consts.TCPCronHeartbeatVerify)) == nil {
|
||||
gcron.AddSingleton(server.Ctx, "@every 300s", func(ctx context.Context) {
|
||||
if server.clients == nil {
|
||||
return
|
||||
@@ -30,11 +31,11 @@ func (server *Server) startCron() {
|
||||
server.Logger.Debugf(server.Ctx, "client heartbeat timeout, close conn. auth:%+v", client.Auth)
|
||||
}
|
||||
}
|
||||
}, server.getCronKey(cronHeartbeatVerify))
|
||||
}, server.getCronKey(consts.TCPCronHeartbeatVerify))
|
||||
}
|
||||
|
||||
// 认证检查
|
||||
if gcron.Search(server.getCronKey(cronAuthVerify)) == nil {
|
||||
if gcron.Search(server.getCronKey(consts.TCPCronAuthVerify)) == nil {
|
||||
gcron.AddSingleton(server.Ctx, "@every 300s", func(ctx context.Context) {
|
||||
if server.clients == nil {
|
||||
return
|
||||
@@ -45,6 +46,6 @@ func (server *Server) startCron() {
|
||||
server.Logger.Debugf(server.Ctx, "client auth expired, close conn. auth:%+v", client.Auth)
|
||||
}
|
||||
}
|
||||
}, server.getCronKey(cronAuthVerify))
|
||||
}, server.getCronKey(consts.TCPCronAuthVerify))
|
||||
}
|
||||
}
|
||||
|
@@ -1,8 +1,8 @@
|
||||
package tcp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/net/gtcp"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
@@ -12,75 +12,73 @@ import (
|
||||
"hotgo/utility/convert"
|
||||
)
|
||||
|
||||
func (server *Server) onServerLogin(args ...interface{}) {
|
||||
func (server *Server) onServerLogin(ctx context.Context, args ...interface{}) {
|
||||
var (
|
||||
in = new(msgin.ServerLogin)
|
||||
conn = args[1].(*gtcp.Conn)
|
||||
user = GetCtx(ctx)
|
||||
res = new(msgin.ResponseServerLogin)
|
||||
models *entity.SysServeLicense
|
||||
)
|
||||
|
||||
if err := gconv.Scan(args[0], &in); err != nil {
|
||||
server.Logger.Infof(server.Ctx, "onServerLogin message Scan failed:%+v, args:%+v", err, args)
|
||||
server.Logger.Warningf(ctx, "onServerLogin message Scan failed:%+v, args:%+v", err, args)
|
||||
return
|
||||
}
|
||||
server.Logger.Infof(server.Ctx, "onServerLogin in:%+v", *in)
|
||||
|
||||
err := g.Model("sys_serve_license").
|
||||
Ctx(server.Ctx).
|
||||
err := g.Model("sys_serve_license").Ctx(ctx).
|
||||
Where("appid = ?", in.AppId).
|
||||
Scan(&models)
|
||||
|
||||
if err != nil {
|
||||
res.Code = 1
|
||||
res.Message = err.Error()
|
||||
server.Write(conn, res)
|
||||
server.Write(user.Conn, res)
|
||||
return
|
||||
}
|
||||
|
||||
if models == nil {
|
||||
res.Code = 2
|
||||
res.Message = "授权信息不存在"
|
||||
server.Write(conn, res)
|
||||
server.Write(user.Conn, res)
|
||||
return
|
||||
}
|
||||
|
||||
// 验证签名
|
||||
if err = VerifySign(in, models.Appid, models.SecretKey); err != nil {
|
||||
if _, err = VerifySign(in, models.Appid, models.SecretKey); err != nil {
|
||||
res.Code = 3
|
||||
res.Message = "签名错误,请联系管理员"
|
||||
server.Write(conn, res)
|
||||
server.Write(user.Conn, res)
|
||||
return
|
||||
}
|
||||
|
||||
if models.Status != consts.StatusEnabled {
|
||||
res.Code = 4
|
||||
res.Message = "授权已禁用,请联系管理员"
|
||||
server.Write(conn, res)
|
||||
server.Write(user.Conn, res)
|
||||
return
|
||||
}
|
||||
|
||||
if models.Group != in.Group {
|
||||
res.Code = 5
|
||||
res.Message = "你登录的授权分组未得到授权,请联系管理员"
|
||||
server.Write(conn, res)
|
||||
server.Write(user.Conn, res)
|
||||
return
|
||||
}
|
||||
|
||||
if models.EndAt.Before(gtime.Now()) {
|
||||
res.Code = 6
|
||||
res.Message = "授权已过期,请联系管理员"
|
||||
server.Write(conn, res)
|
||||
server.Write(user.Conn, res)
|
||||
return
|
||||
}
|
||||
|
||||
allowedIps := convert.IpFilterStrategy(models.AllowedIps)
|
||||
if _, ok := allowedIps["*"]; !ok {
|
||||
ip := gstr.StrTillEx(conn.RemoteAddr().String(), ":")
|
||||
ip := gstr.StrTillEx(user.Conn.RemoteAddr().String(), ":")
|
||||
if _, ok2 := allowedIps[ip]; !ok2 {
|
||||
res.Code = 7
|
||||
res.Message = "IP(" + ip + ")未授权,请联系管理员"
|
||||
server.Write(conn, res)
|
||||
server.Write(user.Conn, res)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -99,14 +97,14 @@ func (server *Server) onServerLogin(args ...interface{}) {
|
||||
}
|
||||
|
||||
// 当前连接也踢掉
|
||||
server.Write(conn, res2)
|
||||
conn.Close()
|
||||
server.Write(user.Conn, res2)
|
||||
user.Conn.Close()
|
||||
return
|
||||
}
|
||||
|
||||
server.mutexConns.Lock()
|
||||
server.clients[conn.RemoteAddr().String()] = &ClientConn{
|
||||
Conn: conn,
|
||||
server.clients[user.Conn.RemoteAddr().String()] = &ClientConn{
|
||||
Conn: user.Conn,
|
||||
Auth: &AuthMeta{
|
||||
Group: in.Group,
|
||||
Name: in.Name,
|
||||
@@ -118,39 +116,46 @@ func (server *Server) onServerLogin(args ...interface{}) {
|
||||
}
|
||||
server.mutexConns.Unlock()
|
||||
|
||||
server.Write(conn, res)
|
||||
|
||||
_, err = g.Model("sys_serve_license").
|
||||
Ctx(server.Ctx).
|
||||
_, err = g.Model("sys_serve_license").Ctx(ctx).
|
||||
Where("id = ?", models.Id).Data(g.Map{
|
||||
"online": online,
|
||||
"login_times": models.LoginTimes + 1,
|
||||
"last_login_at": gtime.Now(),
|
||||
"last_active_at": gtime.Now(),
|
||||
"remote_addr": conn.RemoteAddr().String(),
|
||||
"remote_addr": user.Conn.RemoteAddr().String(),
|
||||
}).Update()
|
||||
if err != nil {
|
||||
server.Logger.Warningf(server.Ctx, "onServerLogin Update err:%+v", err)
|
||||
server.Logger.Warningf(ctx, "onServerLogin Update err:%+v", err)
|
||||
}
|
||||
|
||||
res.AppId = in.AppId
|
||||
res.Code = consts.TCPMsgCodeSuccess
|
||||
server.Write(user.Conn, res)
|
||||
}
|
||||
|
||||
func (server *Server) onServerHeartbeat(args ...interface{}) {
|
||||
var in *msgin.ServerHeartbeat
|
||||
if err := gconv.Scan(args, &in); err != nil {
|
||||
server.Logger.Infof(server.Ctx, "onServerHeartbeat message Scan failed:%+v, args:%+v", err, args)
|
||||
func (server *Server) onServerHeartbeat(ctx context.Context, args ...interface{}) {
|
||||
var (
|
||||
in *msgin.ServerHeartbeat
|
||||
res = new(msgin.ResponseServerHeartbeat)
|
||||
)
|
||||
|
||||
if err := gconv.Scan(args[0], &in); err != nil {
|
||||
server.Logger.Warningf(ctx, "onServerHeartbeat message Scan failed:%+v, args:%+v", err, args)
|
||||
return
|
||||
}
|
||||
|
||||
client := args[1].(*ClientConn)
|
||||
client.heartbeat = gtime.Timestamp()
|
||||
|
||||
server.Write(client.Conn, &msgin.ResponseServerHeartbeat{})
|
||||
|
||||
_, err := g.Model("sys_serve_license").
|
||||
Ctx(server.Ctx).
|
||||
_, err := g.Model("sys_serve_license").Ctx(ctx).
|
||||
Where("appid = ?", client.Auth.AppId).Data(g.Map{
|
||||
"last_active_at": gtime.Now(),
|
||||
}).Update()
|
||||
if err != nil {
|
||||
server.Logger.Warningf(server.Ctx, "onServerHeartbeat Update err:%+v", err)
|
||||
server.Logger.Warningf(ctx, "onServerHeartbeat Update err:%+v", err)
|
||||
}
|
||||
|
||||
res.Code = consts.TCPMsgCodeSuccess
|
||||
server.Write(client.Conn, res)
|
||||
|
||||
}
|
||||
|
@@ -7,35 +7,38 @@ import (
|
||||
)
|
||||
|
||||
type Sign interface {
|
||||
SetSign(traceID, appId, secretKey string)
|
||||
SetSign(appId, secretKey string) *msgin.RpcMsg
|
||||
SetTraceID(traceID string)
|
||||
}
|
||||
|
||||
// SetSign 设置签名
|
||||
func SetSign(data interface{}, traceID, appId, secretKey string) {
|
||||
// PkgSign 打包签名
|
||||
func PkgSign(data interface{}, appId, secretKey, traceID string) *msgin.RpcMsg {
|
||||
if c, ok := data.(Sign); ok {
|
||||
c.SetSign(traceID, appId, secretKey)
|
||||
return
|
||||
c.SetTraceID(traceID)
|
||||
return c.SetSign(appId, secretKey)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// VerifySign 验证签名
|
||||
func VerifySign(data interface{}, appId, secretKey string) (err error) {
|
||||
func VerifySign(data interface{}, appId, secretKey string) (in *msgin.RpcMsg, err error) {
|
||||
// 无密钥,无需签名
|
||||
if secretKey == "" {
|
||||
return
|
||||
}
|
||||
|
||||
var in *msgin.Request
|
||||
if err = gconv.Scan(data, &in); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if appId != in.AppId {
|
||||
return gerror.New("appId invalid")
|
||||
err = gerror.New("appId invalid")
|
||||
return
|
||||
}
|
||||
|
||||
if in.Sign != in.GetSign(secretKey) {
|
||||
return gerror.New("sign invalid")
|
||||
err = gerror.New("sign invalid")
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
Reference in New Issue
Block a user