feat 排队
This commit is contained in:
@@ -25,6 +25,7 @@ func (p *Program) Init(_ svc.Environment) error {
|
|||||||
p.moduleList = append(p.moduleList, &ModuleWebServer{})
|
p.moduleList = append(p.moduleList, &ModuleWebServer{})
|
||||||
p.moduleList = append(p.moduleList, &ModuleWebsocketServer{})
|
p.moduleList = append(p.moduleList, &ModuleWebsocketServer{})
|
||||||
p.moduleList = append(p.moduleList, &ModuleGrpcServer{})
|
p.moduleList = append(p.moduleList, &ModuleGrpcServer{})
|
||||||
|
p.moduleList = append(p.moduleList, &ModuleLoginQueue{})
|
||||||
|
|
||||||
for _, module := range p.moduleList {
|
for _, module := range p.moduleList {
|
||||||
if err := module.init(); err != nil {
|
if err := module.init(); err != nil {
|
||||||
|
|||||||
29
app/login_queue.go
Normal file
29
app/login_queue.go
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gateway/internal/global"
|
||||||
|
"gateway/internal/handler/ws_handler/login"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ModuleLoginQueue 登录队列模块
|
||||||
|
type ModuleLoginQueue struct {
|
||||||
|
login *login.Login
|
||||||
|
queueUp *login.QueueUp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ModuleLoginQueue) init() error {
|
||||||
|
m.login = login.NewLoginQueue(global.MaxQueueUpSize)
|
||||||
|
m.queueUp = login.NewQueueUp(global.MaxQueueUpSize)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ModuleLoginQueue) start() error {
|
||||||
|
m.login.Start(runtime.NumCPU())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ModuleLoginQueue) stop() error {
|
||||||
|
m.login.Stop()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -2,16 +2,7 @@ package config
|
|||||||
|
|
||||||
import "common/config"
|
import "common/config"
|
||||||
|
|
||||||
const (
|
const path = "./config"
|
||||||
path = "./config"
|
|
||||||
KeyUserAccessToken = "user:access:%v"
|
|
||||||
KeyUserRefreshToken = "user:refresh:%v"
|
|
||||||
)
|
|
||||||
|
|
||||||
// PublicPaths 不需要鉴权的接口,硬编码注册
|
|
||||||
var PublicPaths = []string{
|
|
||||||
"/user/info",
|
|
||||||
}
|
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
App *config.AppConfig `yaml:"app"`
|
App *config.AppConfig `yaml:"app"`
|
||||||
|
|||||||
2
go.mod
2
go.mod
@@ -13,6 +13,7 @@ require (
|
|||||||
github.com/judwhite/go-svc v1.2.1
|
github.com/judwhite/go-svc v1.2.1
|
||||||
github.com/panjf2000/gnet/v2 v2.9.7
|
github.com/panjf2000/gnet/v2 v2.9.7
|
||||||
github.com/prometheus/client_golang v1.20.5
|
github.com/prometheus/client_golang v1.20.5
|
||||||
|
github.com/redis/go-redis/v9 v9.10.0
|
||||||
github.com/stretchr/testify v1.11.1
|
github.com/stretchr/testify v1.11.1
|
||||||
go.uber.org/zap v1.27.0
|
go.uber.org/zap v1.27.0
|
||||||
google.golang.org/grpc v1.71.1
|
google.golang.org/grpc v1.71.1
|
||||||
@@ -66,7 +67,6 @@ require (
|
|||||||
github.com/prometheus/procfs v0.15.1 // indirect
|
github.com/prometheus/procfs v0.15.1 // indirect
|
||||||
github.com/quic-go/qpack v0.5.1 // indirect
|
github.com/quic-go/qpack v0.5.1 // indirect
|
||||||
github.com/quic-go/quic-go v0.54.0 // indirect
|
github.com/quic-go/quic-go v0.54.0 // indirect
|
||||||
github.com/redis/go-redis/v9 v9.10.0 // indirect
|
|
||||||
github.com/sagikazarmark/locafero v0.11.0 // indirect
|
github.com/sagikazarmark/locafero v0.11.0 // indirect
|
||||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
|
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
|
||||||
github.com/spf13/afero v1.15.0 // indirect
|
github.com/spf13/afero v1.15.0 // indirect
|
||||||
|
|||||||
19
internal/global/global.go
Normal file
19
internal/global/global.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package global
|
||||||
|
|
||||||
|
const (
|
||||||
|
KeyGatewayAccessToken = "gateway:token:access:%v"
|
||||||
|
KeyGatewayRefreshToken = "gateway:token:refresh:%v"
|
||||||
|
|
||||||
|
KeyGatewayInfo = "gateway:info:%v"
|
||||||
|
HFieldInfoGatewaySID = "gateway_sid"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
MaxOnlineSize = 100 // 最大在线人数
|
||||||
|
MaxQueueUpSize = 100 // 最大排队人数
|
||||||
|
)
|
||||||
|
|
||||||
|
// PublicPaths 不需要鉴权的接口,硬编码注册
|
||||||
|
var PublicPaths = []string{
|
||||||
|
"/user/info",
|
||||||
|
}
|
||||||
@@ -4,7 +4,8 @@ import (
|
|||||||
"common/log"
|
"common/log"
|
||||||
"common/proto/sc/sc_pb"
|
"common/proto/sc/sc_pb"
|
||||||
"common/proto/ss/grpc_pb"
|
"common/proto/ss/grpc_pb"
|
||||||
"gateway/internal/handler/ws_handler"
|
"context"
|
||||||
|
"gateway/internal/handler/ws_handler/client"
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
@@ -38,16 +39,16 @@ func (s *Server) ToClient(server grpc_pb.Gateway_ToClientServer) error {
|
|||||||
log.Errorf("ToClient proto.Marshal error: %v", err)
|
log.Errorf("ToClient proto.Marshal error: %v", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
for _, client := range ws_handler.UserMgr.GetAll() {
|
for _, cli := range client.UserMgr.GetAll() {
|
||||||
client.WriteBytesPreMarshal(data)
|
cli.WriteBytesPreMarshal(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
//for _, client := range ws_handler.UserMgr.GetAll() {
|
//for _, client := range ws_handler.UserMgr.GetAll() {
|
||||||
// client.WriteBytes(sc_pb.MessageID(args.MessageID), args.Payload)
|
// client.WriteBytes(sc_pb.MessageID(args.MessageID), args.Payload)
|
||||||
//}
|
//}
|
||||||
} else {
|
} else {
|
||||||
if client := ws_handler.UserMgr.GetByUSN(args.USN); client != nil {
|
if cli := client.UserMgr.GetByUSN(args.USN); cli != nil {
|
||||||
client.WriteBytes(sc_pb.MessageID(args.MessageID), args.Payload)
|
cli.WriteBytes(sc_pb.MessageID(args.MessageID), args.Payload)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -56,3 +57,7 @@ func (s *Server) ToClient(server grpc_pb.Gateway_ToClientServer) error {
|
|||||||
wg.Wait()
|
wg.Wait()
|
||||||
return server.SendAndClose(&grpc_pb.ToClientResp{})
|
return server.SendAndClose(&grpc_pb.ToClientResp{})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) KickUser(ctx context.Context, req *grpc_pb.KickUserReq) (*grpc_pb.KickUserResp, error) {
|
||||||
|
return &grpc_pb.KickUserResp{}, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"common/discover/common"
|
"common/discover/common"
|
||||||
"common/net/grpc/service"
|
"common/net/grpc/service"
|
||||||
"common/proto/ss/grpc_pb"
|
"common/proto/ss/grpc_pb"
|
||||||
"gateway/internal/handler/ws_handler"
|
"gateway/internal/handler/ws_handler/client"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -26,7 +26,7 @@ func NewServer(ttl int64) *Server {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) OnInit(serve *grpc.Server) {
|
func (s *Server) OnInit(serve *grpc.Server) {
|
||||||
ws_handler.GatewaySID = s.SID
|
client.GatewaySID = s.SID
|
||||||
grpc_pb.RegisterGatewayServer(serve, s)
|
grpc_pb.RegisterGatewayServer(serve, s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,60 +6,79 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
var sceneServerM map[int64]map[SceneFun]grpc.ClientStream // map[sid]map[方法名]流连接
|
|
||||||
|
|
||||||
type SceneFun int
|
type SceneFun int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
FunAction SceneFun = iota
|
FunAction SceneFun = iota
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
var sceneServer sync.Map // map[string]*sceneStream
|
||||||
sceneServerM = make(map[int64]map[SceneFun]grpc.ClientStream)
|
|
||||||
|
type sceneStream struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
stream grpc.ClientStream
|
||||||
}
|
}
|
||||||
|
|
||||||
func findSceneBySID(sid int64, fun SceneFun) (grpc.ClientStream, error) {
|
func findSceneBySID(sid int64, fun SceneFun) (*sceneStream, error) {
|
||||||
g := sceneServerM[sid]
|
key := sceneKey(sid, fun)
|
||||||
if g == nil {
|
|
||||||
g = make(map[SceneFun]grpc.ClientStream)
|
if v, ok := sceneServer.Load(key); ok {
|
||||||
sceneServerM[sid] = g
|
return v.(*sceneStream), nil
|
||||||
}
|
}
|
||||||
sceneLink := g[fun]
|
|
||||||
if sceneLink == nil {
|
client, err := service.SceneNewClient(sid)
|
||||||
sceneClient, err := service.SceneNewClient(sid)
|
if err != nil {
|
||||||
if err != nil {
|
log.Errorf("findSceneBySID cannot find client: %v", err)
|
||||||
log.Errorf("cannot find sceneClient: %v", err)
|
return nil, err
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var link grpc.ClientStream
|
|
||||||
switch fun {
|
|
||||||
case FunAction:
|
|
||||||
link, err = sceneClient.Action(context.Background())
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("findSceneBySID %v err: %v, sid: %v", fun, err, sid)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
g[fun] = link
|
|
||||||
sceneLink = link
|
|
||||||
}
|
}
|
||||||
return sceneLink, nil
|
var stream grpc.ClientStream
|
||||||
|
switch fun {
|
||||||
|
case FunAction:
|
||||||
|
stream, err = client.Action(context.Background())
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("findSceneBySID %v err: %v, sid: %v", fun, err, sid)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ss := &sceneStream{stream: stream}
|
||||||
|
if actual, loaded := sceneServer.LoadOrStore(key, ss); loaded {
|
||||||
|
go func() { _ = stream.CloseSend() }()
|
||||||
|
return actual.(*sceneStream), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return ss, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func SendMessageToScene(sid int64, fun SceneFun, msg proto.Message, re ...bool) error {
|
func SendMessageToScene(sid int64, fun SceneFun, msg proto.Message, re ...bool) error {
|
||||||
stream, err := findSceneBySID(sid, fun)
|
ss, err := findSceneBySID(sid, fun)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err = stream.SendMsg(msg); err != nil {
|
|
||||||
|
ss.mu.Lock()
|
||||||
|
err = ss.stream.SendMsg(msg)
|
||||||
|
ss.mu.Unlock()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
key := sceneKey(sid, fun)
|
||||||
|
if v, ok := sceneServer.Load(key); ok && v == ss {
|
||||||
|
sceneServer.Delete(key)
|
||||||
|
_ = ss.stream.CloseSend()
|
||||||
|
}
|
||||||
|
// 如果没有标识本次是重试的,就重试一次(默认重试)
|
||||||
if re == nil || !re[0] {
|
if re == nil || !re[0] {
|
||||||
_ = stream.CloseSend()
|
|
||||||
delete(sceneServerM[sid], fun)
|
|
||||||
return SendMessageToScene(sid, fun, msg, true)
|
return SendMessageToScene(sid, fun, msg, true)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func sceneKey(sid int64, fun SceneFun) string {
|
||||||
|
return strconv.FormatInt(sid, 10) + "-" + strconv.Itoa(int(fun))
|
||||||
|
}
|
||||||
|
|||||||
20
internal/grpc_server/stream_client/scene_test.go
Normal file
20
internal/grpc_server/stream_client/scene_test.go
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package stream_client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gateway/internal/testutil"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SceneTestSuite struct {
|
||||||
|
testutil.TestSuite
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts *SceneTestSuite) TestSceneKey() {
|
||||||
|
r := sceneKey(1122, FunAction)
|
||||||
|
ts.Assert().Equal("1122-0", r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoginTestSuite(t *testing.T) {
|
||||||
|
suite.Run(t, &SceneTestSuite{})
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"gateway/config"
|
"gateway/config"
|
||||||
|
"gateway/internal/global"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -79,7 +80,7 @@ func RefreshToken(c *gin.Context) {
|
|||||||
http_resp.JsonOK(c, http_resp.Error(http_resp.TokenInvalid))
|
http_resp.JsonOK(c, http_resp.Error(http_resp.TokenInvalid))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if redis.GetClient().Get(c, fmt.Sprintf(config.KeyUserRefreshToken, claims.USN)).Val() != req.RefreshToken {
|
if redis.GetClient().Get(c, fmt.Sprintf(global.KeyGatewayRefreshToken, claims.USN)).Val() != req.RefreshToken {
|
||||||
http_resp.JsonOK(c, http_resp.Error(http_resp.TokenInvalid))
|
http_resp.JsonOK(c, http_resp.Error(http_resp.TokenInvalid))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -97,11 +98,11 @@ func RefreshToken(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func genToken(ctx context.Context, usn int64) (string, string, error) {
|
func genToken(ctx context.Context, usn int64) (string, string, error) {
|
||||||
at, err := genTokenOne(ctx, config.KeyUserAccessToken, usn, 2*time.Hour)
|
at, err := genTokenOne(ctx, global.KeyGatewayAccessToken, usn, 2*time.Hour)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
rt, err := genTokenOne(ctx, config.KeyUserRefreshToken, usn, 3*24*time.Hour)
|
rt, err := genTokenOne(ctx, global.KeyGatewayRefreshToken, usn, 3*24*time.Hour)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
"common/utils"
|
"common/utils"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"gateway/config"
|
"gateway/internal/global"
|
||||||
"gateway/internal/testutil"
|
"gateway/internal/testutil"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/golang/mock/gomock"
|
"github.com/golang/mock/gomock"
|
||||||
@@ -30,9 +30,9 @@ func (ts *LoginTestSuite) TestGenToken() {
|
|||||||
at, rt, err := genToken(context.Background(), int64(usn))
|
at, rt, err := genToken(context.Background(), int64(usn))
|
||||||
ts.Assert().NoError(err)
|
ts.Assert().NoError(err)
|
||||||
|
|
||||||
redisAt := redis.GetClient().Get(context.Background(), fmt.Sprintf(config.KeyUserAccessToken, usn)).Val()
|
redisAt := redis.GetClient().Get(context.Background(), fmt.Sprintf(global.KeyGatewayAccessToken, usn)).Val()
|
||||||
ts.Assert().Equal(at, redisAt)
|
ts.Assert().Equal(at, redisAt)
|
||||||
redisRt := redis.GetClient().Get(context.Background(), fmt.Sprintf(config.KeyUserRefreshToken, usn)).Val()
|
redisRt := redis.GetClient().Get(context.Background(), fmt.Sprintf(global.KeyGatewayRefreshToken, usn)).Val()
|
||||||
ts.Assert().Equal(rt, redisRt)
|
ts.Assert().Equal(rt, redisRt)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,7 +138,7 @@ func (ts *LoginTestSuite) TestRefreshToken() {
|
|||||||
})
|
})
|
||||||
defer monkey.Unpatch(utils.ParseToken)
|
defer monkey.Unpatch(utils.ParseToken)
|
||||||
|
|
||||||
redis.GetClient().Set(context.Background(), fmt.Sprintf(config.KeyUserRefreshToken, 1), "ab", redis2.KeepTTL)
|
redis.GetClient().Set(context.Background(), fmt.Sprintf(global.KeyGatewayRefreshToken, 1), "ab", redis2.KeepTTL)
|
||||||
|
|
||||||
w, c := utils.CreateTestContext("POST", "/", &RefreshTokenReq{
|
w, c := utils.CreateTestContext("POST", "/", &RefreshTokenReq{
|
||||||
RefreshToken: "abc",
|
RefreshToken: "abc",
|
||||||
@@ -156,7 +156,7 @@ func (ts *LoginTestSuite) TestRefreshToken() {
|
|||||||
})
|
})
|
||||||
defer monkey.Unpatch(utils.ParseToken)
|
defer monkey.Unpatch(utils.ParseToken)
|
||||||
|
|
||||||
redis.GetClient().Set(context.Background(), fmt.Sprintf(config.KeyUserRefreshToken, 1), "abc", redis2.KeepTTL)
|
redis.GetClient().Set(context.Background(), fmt.Sprintf(global.KeyGatewayRefreshToken, 1), "abc", redis2.KeepTTL)
|
||||||
|
|
||||||
w, c := utils.CreateTestContext("POST", "/", &RefreshTokenReq{
|
w, c := utils.CreateTestContext("POST", "/", &RefreshTokenReq{
|
||||||
RefreshToken: "abc",
|
RefreshToken: "abc",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package ws_handler
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"common/log"
|
"common/log"
|
||||||
@@ -22,6 +22,7 @@ type Client struct {
|
|||||||
cancel context.CancelFunc // 取消上下文
|
cancel context.CancelFunc // 取消上下文
|
||||||
heartBeat time.Time // 最后一次心跳
|
heartBeat time.Time // 最后一次心跳
|
||||||
|
|
||||||
|
Status int32 // 状态:0 登陆中 1 正常 2 离线
|
||||||
USN int64 // 用户ID
|
USN int64 // 用户ID
|
||||||
SceneSID int64 // 场景服ID
|
SceneSID int64 // 场景服ID
|
||||||
InstanceID int32 // 副本ID,副本类型
|
InstanceID int32 // 副本ID,副本类型
|
||||||
@@ -100,6 +101,7 @@ func (c *Client) onClose() {
|
|||||||
close(c.mailChan)
|
close(c.mailChan)
|
||||||
c.mailChan = nil
|
c.mailChan = nil
|
||||||
}
|
}
|
||||||
|
c.Status = 2
|
||||||
UserMgr.Delete(c.USN)
|
UserMgr.Delete(c.USN)
|
||||||
c.onLeave()
|
c.onLeave()
|
||||||
c.Done()
|
c.Done()
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package ws_handler
|
package client
|
||||||
|
|
||||||
type Event interface {
|
type Event interface {
|
||||||
}
|
}
|
||||||
@@ -11,3 +11,7 @@ type ClientEvent struct {
|
|||||||
type PongEvent struct {
|
type PongEvent struct {
|
||||||
Event
|
Event
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SystemLoginSuccessEvent struct {
|
||||||
|
Event
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package ws_handler
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"common/net/grpc/service"
|
"common/net/grpc/service"
|
||||||
@@ -38,6 +38,14 @@ func (c *Client) handle(event Event) {
|
|||||||
}
|
}
|
||||||
case *PongEvent:
|
case *PongEvent:
|
||||||
c.heartBeat = time.Now()
|
c.heartBeat = time.Now()
|
||||||
|
case *SystemLoginSuccessEvent:
|
||||||
|
if c.Status == 0 {
|
||||||
|
c.Status = 1
|
||||||
|
UserMgr.Add(c.USN, c)
|
||||||
|
c.WriteMessage(sc_pb.MessageID_MESSAGE_ID_LOGIN_SUCCESS, &sc_pb.S2C_LoginSuccess{
|
||||||
|
InstanceID: 1,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,16 +71,17 @@ func (c *Client) onEnter(msg *sc_pb.C2S_EnterInstance) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) onLeave() {
|
func (c *Client) onLeave() {
|
||||||
|
if c.SceneSID == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
client, err := service.SceneNewClient(c.SceneSID)
|
client, err := service.SceneNewClient(c.SceneSID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.logger.Errorf("SceneNewClient err: %v", err)
|
c.logger.Errorf("SceneNewClient err: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
_, err = client.Leave(c.ctx, &grpc_pb.LeaveReq{
|
_, err = client.Leave(c.ctx, &grpc_pb.LeaveReq{
|
||||||
USN: c.USN,
|
USN: c.USN,
|
||||||
GatewaySID: GatewaySID,
|
UniqueNo: c.UniqueNo,
|
||||||
InstanceID: c.InstanceID,
|
|
||||||
UniqueNo: c.UniqueNo,
|
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.logger.Errorf("leave err: %v", err)
|
c.logger.Errorf("leave err: %v", err)
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package ws_handler
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"common/proto/sc/sc_pb"
|
"common/proto/sc/sc_pb"
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package ws_handler
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"sync"
|
"sync"
|
||||||
@@ -56,3 +56,9 @@ func (m *userManager) GetByUSN(usn int64) *Client {
|
|||||||
defer m.RUnlock()
|
defer m.RUnlock()
|
||||||
return m.userMap[usn]
|
return m.userMap[usn]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *userManager) GetSize() int32 {
|
||||||
|
m.RLock()
|
||||||
|
defer m.RUnlock()
|
||||||
|
return int32(len(m.userMap))
|
||||||
|
}
|
||||||
181
internal/handler/ws_handler/login/login.go
Normal file
181
internal/handler/ws_handler/login/login.go
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
package login
|
||||||
|
|
||||||
|
import (
|
||||||
|
"common/db/redis"
|
||||||
|
"common/log"
|
||||||
|
"common/net/grpc/service"
|
||||||
|
"common/proto/sc/sc_pb"
|
||||||
|
"common/proto/ss/grpc_pb"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"gateway/internal/global"
|
||||||
|
"gateway/internal/handler/ws_handler/client"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var loginQueue *Login
|
||||||
|
|
||||||
|
// Login 登录队列结构
|
||||||
|
type Login struct {
|
||||||
|
queue chan *User // 用户队列
|
||||||
|
ctx context.Context
|
||||||
|
cancel context.CancelFunc
|
||||||
|
wg sync.WaitGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
Cli *client.Client
|
||||||
|
Token string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLoginQueue(maxSize int) *Login {
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
loginQueue = &Login{
|
||||||
|
queue: make(chan *User, maxSize),
|
||||||
|
ctx: ctx,
|
||||||
|
cancel: cancel,
|
||||||
|
}
|
||||||
|
return loginQueue
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetLoginQueue() *Login {
|
||||||
|
return loginQueue
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddToLoginQueue 添加到登录队列
|
||||||
|
func (l *Login) AddToLoginQueue(user *User) bool {
|
||||||
|
select {
|
||||||
|
case l.queue <- user:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Login) Start(num int) {
|
||||||
|
for i := 0; i < num; i++ {
|
||||||
|
l.wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer l.wg.Done()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-l.ctx.Done():
|
||||||
|
return
|
||||||
|
case user, ok := <-l.queue:
|
||||||
|
if ok {
|
||||||
|
l.StartLogin(user)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
// 定时从排队的队列中取用户
|
||||||
|
l.wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer l.wg.Done()
|
||||||
|
tick := time.NewTicker(1 * time.Second)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-tick.C:
|
||||||
|
for {
|
||||||
|
if client.UserMgr.GetSize() < global.MaxOnlineSize {
|
||||||
|
cli, err := GetQueueUp().Dequeue()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("dequeue err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if cli == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
l.LoginSuccess(cli)
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case <-l.ctx.Done():
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Login) Stop() {
|
||||||
|
l.cancel()
|
||||||
|
l.wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartLogin 开始登录流程
|
||||||
|
func (l *Login) StartLogin(user *User) {
|
||||||
|
if !l.CheckToken(user) {
|
||||||
|
user.Cli.WriteMessage(sc_pb.MessageID_MESSAGE_ID_KICK_OUT, &sc_pb.S2C_KickOut{
|
||||||
|
ID: sc_pb.KickOutID_KICK_OUT_ID_TOKEN_INVALID,
|
||||||
|
})
|
||||||
|
user.Cli.CloseClient()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if gatewaySID := l.CheckOnline(user); len(gatewaySID) > 0 {
|
||||||
|
// 如果在线就要踢,如果踢失败了就返回服务器繁忙,一般不应该走到这里
|
||||||
|
if !l.KickUser(user.Cli.SceneSID, user.Cli.USN) {
|
||||||
|
user.Cli.WriteMessage(sc_pb.MessageID_MESSAGE_ID_KICK_OUT, &sc_pb.S2C_KickOut{
|
||||||
|
ID: sc_pb.KickOutID_KICK_OUT_ID_SERVER_BUSY,
|
||||||
|
})
|
||||||
|
user.Cli.CloseClient()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if client.UserMgr.GetSize() >= global.MaxOnlineSize {
|
||||||
|
// 如果人数满了就排队
|
||||||
|
if err := GetQueueUp().Enqueue(user.Cli); err != nil {
|
||||||
|
user.Cli.WriteMessage(sc_pb.MessageID_MESSAGE_ID_KICK_OUT, &sc_pb.S2C_KickOut{
|
||||||
|
ID: sc_pb.KickOutID_KICK_OUT_ID_QUEUE_UP_FULL,
|
||||||
|
})
|
||||||
|
user.Cli.CloseClient()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 告诉客户端正在排队
|
||||||
|
position, ok := GetQueueUp().GetPosition(user.Cli.USN)
|
||||||
|
if !ok {
|
||||||
|
user.Cli.WriteMessage(sc_pb.MessageID_MESSAGE_ID_KICK_OUT, &sc_pb.S2C_KickOut{
|
||||||
|
ID: sc_pb.KickOutID_KICK_OUT_ID_SERVER_BUSY,
|
||||||
|
})
|
||||||
|
user.Cli.CloseClient()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
user.Cli.WriteMessage(sc_pb.MessageID_MESSAGE_ID_QUEUE_UP, &sc_pb.S2C_QueueUp{
|
||||||
|
QueueUpCount: int32(position),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
l.LoginSuccess(user.Cli)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckToken 校验Token是否有效
|
||||||
|
func (l *Login) CheckToken(user *User) bool {
|
||||||
|
return redis.GetClient().Get(l.ctx, fmt.Sprintf(global.KeyGatewayAccessToken, user.Cli.USN)).Val() == user.Token
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckOnline 校验是否在线
|
||||||
|
func (l *Login) CheckOnline(user *User) string {
|
||||||
|
return redis.GetClient().HGet(l.ctx, fmt.Sprintf(global.KeyGatewayInfo, user.Cli.USN), global.HFieldInfoGatewaySID).Val()
|
||||||
|
}
|
||||||
|
|
||||||
|
// KickUser 把玩家踢下线
|
||||||
|
func (l *Login) KickUser(gatewaySID int64, usn int64) bool {
|
||||||
|
gc, err := service.GatewayNewClient(gatewaySID)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("KickUser cannot find gateway client: %v, sid: %v", err, gatewaySID)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
_, err = gc.KickUser(l.ctx, &grpc_pb.KickUserReq{USN: usn})
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("KickUser err: %v, sid: %v, usn: %v", err, gatewaySID, usn)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoginSuccess 登录成功
|
||||||
|
func (l *Login) LoginSuccess(user *client.Client) {
|
||||||
|
user.OnEvent(&client.SystemLoginSuccessEvent{})
|
||||||
|
}
|
||||||
104
internal/handler/ws_handler/login/queue_up.go
Normal file
104
internal/handler/ws_handler/login/queue_up.go
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
package login
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"gateway/internal/handler/ws_handler/client"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
)
|
||||||
|
|
||||||
|
var queueUp *QueueUp
|
||||||
|
|
||||||
|
// QueueUp 排队队列结构
|
||||||
|
type QueueUp struct {
|
||||||
|
queue chan *QueueUser // 用户队列
|
||||||
|
waiting sync.Map // map[usn]*QueueUser
|
||||||
|
minTicket atomic.Int64 // 最小的Ticket
|
||||||
|
maxTicket atomic.Int64 // 最大的Ticket
|
||||||
|
ctx context.Context
|
||||||
|
cancel context.CancelFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
type QueueUser struct {
|
||||||
|
Cli *client.Client
|
||||||
|
Ticket int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewQueueUp(maxSize int) *QueueUp {
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
queueUp = &QueueUp{
|
||||||
|
queue: make(chan *QueueUser, maxSize),
|
||||||
|
ctx: ctx,
|
||||||
|
cancel: cancel,
|
||||||
|
}
|
||||||
|
return queueUp
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetQueueUp() *QueueUp {
|
||||||
|
return queueUp
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enqueue 将用户加入排队队列
|
||||||
|
func (q *QueueUp) Enqueue(cli *client.Client) error {
|
||||||
|
select {
|
||||||
|
case <-q.ctx.Done():
|
||||||
|
return errors.New("queue stopped")
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
ticket := q.maxTicket.Add(1)
|
||||||
|
item := &QueueUser{Cli: cli, Ticket: ticket}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case q.queue <- item:
|
||||||
|
q.waiting.Store(cli.USN, item)
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return errors.New("queue is full")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dequeue 从排队队列中取出下一个有效用户
|
||||||
|
func (q *QueueUp) Dequeue() (*client.Client, error) {
|
||||||
|
select {
|
||||||
|
case item, ok := <-q.queue:
|
||||||
|
if ok {
|
||||||
|
q.minTicket.Store(item.Ticket)
|
||||||
|
if _, loaded := q.waiting.LoadAndDelete(item.Cli.USN); loaded {
|
||||||
|
return item.Cli, nil
|
||||||
|
}
|
||||||
|
return q.Dequeue()
|
||||||
|
}
|
||||||
|
case <-q.ctx.Done():
|
||||||
|
return nil, q.ctx.Err()
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPosition 返回用户前面还有多少人在排队
|
||||||
|
func (q *QueueUp) GetPosition(usn int64) (int64, bool) {
|
||||||
|
val, ok := q.waiting.Load(usn)
|
||||||
|
if !ok {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
user := val.(*QueueUser)
|
||||||
|
return user.Ticket - q.minTicket.Load() - 1, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveUser 安全移除用户(标记为取消)
|
||||||
|
func (q *QueueUp) RemoveUser(usn int64) bool {
|
||||||
|
_, loaded := q.waiting.LoadAndDelete(usn)
|
||||||
|
return loaded
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetQueueSize 获取当前排队人数
|
||||||
|
func (q *QueueUp) GetQueueSize() int {
|
||||||
|
return len(q.queue)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop 停止整个队列服务
|
||||||
|
func (q *QueueUp) Stop() {
|
||||||
|
q.cancel()
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"common/utils"
|
"common/utils"
|
||||||
"fmt"
|
"fmt"
|
||||||
"gateway/config"
|
"gateway/config"
|
||||||
|
"gateway/internal/global"
|
||||||
"github.com/gin-contrib/cors"
|
"github.com/gin-contrib/cors"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
@@ -45,7 +46,7 @@ func authJwt() gin.HandlerFunc {
|
|||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
// 如果是Public接口,有Token就读,没有就算了
|
// 如果是Public接口,有Token就读,没有就算了
|
||||||
public := false
|
public := false
|
||||||
for _, path := range config.PublicPaths {
|
for _, path := range global.PublicPaths {
|
||||||
if strings.HasPrefix(c.Request.URL.Path, path) {
|
if strings.HasPrefix(c.Request.URL.Path, path) {
|
||||||
public = true
|
public = true
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -5,9 +5,10 @@ import (
|
|||||||
"common/net/socket"
|
"common/net/socket"
|
||||||
"common/utils"
|
"common/utils"
|
||||||
"fmt"
|
"fmt"
|
||||||
"gateway/internal/handler/ws_handler"
|
"gateway/config"
|
||||||
|
"gateway/internal/handler/ws_handler/client"
|
||||||
|
"gateway/internal/handler/ws_handler/login"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"strconv"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -20,49 +21,42 @@ func (g *GatewayWsServer) OnOpen(conn socket.ISocketConn) ([]byte, socket.Action
|
|||||||
return nil, socket.None
|
return nil, socket.None
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *GatewayWsServer) OnHandShake(conn socket.ISocketConn, bytes []byte, callback func(conn socket.ISocketConn, bytes []byte)) socket.Action {
|
func (g *GatewayWsServer) OnHandShake(conn socket.ISocketConn) socket.Action {
|
||||||
token, ok := conn.GetParam("token").(string)
|
token, ok := conn.GetParam("token").(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
g.logger.Warnf("token is not string")
|
g.logger.Warnf("token is invalid")
|
||||||
return socket.Close
|
return socket.Close
|
||||||
}
|
}
|
||||||
//claims, err := utils.ParseToken(token, config.Get().Auth.Secret)
|
claims, err := utils.ParseToken(token, config.Get().Auth.Secret)
|
||||||
//if err != nil {
|
if err != nil {
|
||||||
// g.logger.Warnf("token is invalid")
|
g.logger.Warnf("token is invalid")
|
||||||
// return socket.Close
|
return socket.Close
|
||||||
//}
|
}
|
||||||
|
|
||||||
t, _ := strconv.Atoi(token)
|
cli := client.NewClient(claims.USN, conn)
|
||||||
claims := utils.Claims{
|
conn.SetParam("client", cli)
|
||||||
USN: int64(t),
|
if !login.GetLoginQueue().AddToLoginQueue(&login.User{Cli: cli, Token: token}) {
|
||||||
|
g.logger.Warnf("AddToLoginQueue err, login queue full, usn: %v", claims.USN)
|
||||||
|
return socket.Close
|
||||||
}
|
}
|
||||||
go func(shResp []byte) {
|
|
||||||
if oldClient := ws_handler.UserMgr.GetByUSN(claims.USN); oldClient != nil {
|
|
||||||
oldClient.CloseClient()
|
|
||||||
}
|
|
||||||
client := ws_handler.NewClient(claims.USN, conn)
|
|
||||||
ws_handler.UserMgr.Add(claims.USN, client)
|
|
||||||
conn.SetParam("client", client)
|
|
||||||
callback(conn, shResp)
|
|
||||||
}(bytes)
|
|
||||||
return socket.None
|
return socket.None
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *GatewayWsServer) OnMessage(conn socket.ISocketConn, bytes []byte) socket.Action {
|
func (g *GatewayWsServer) OnMessage(conn socket.ISocketConn, bytes []byte) socket.Action {
|
||||||
client, ok := conn.GetParam("client").(*ws_handler.Client)
|
cli, ok := conn.GetParam("client").(*client.Client)
|
||||||
if !ok || client.USN == 0 {
|
if !ok || cli.USN == 0 || cli.Status != 1 {
|
||||||
return socket.Close
|
return socket.Close
|
||||||
}
|
}
|
||||||
client.OnEvent(&ws_handler.ClientEvent{Msg: bytes})
|
cli.OnEvent(&client.ClientEvent{Msg: bytes})
|
||||||
return socket.None
|
return socket.None
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *GatewayWsServer) OnPong(conn socket.ISocketConn) {
|
func (g *GatewayWsServer) OnPong(conn socket.ISocketConn) {
|
||||||
client, ok := conn.GetParam("client").(*ws_handler.Client)
|
cli, ok := conn.GetParam("client").(*client.Client)
|
||||||
if !ok || client.USN == 0 {
|
if !ok || cli.USN == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
client.OnEvent(&ws_handler.PongEvent{})
|
cli.OnEvent(&client.PongEvent{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *GatewayWsServer) OnClose(_ socket.ISocketConn, _ error) socket.Action {
|
func (g *GatewayWsServer) OnClose(_ socket.ISocketConn, _ error) socket.Action {
|
||||||
|
|||||||
Reference in New Issue
Block a user