From d3d214a6b233782655e1fbbafd304e2097e103bd Mon Sep 17 00:00:00 2001 From: Lex Lim Date: Thu, 15 Dec 2022 15:42:38 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=E6=9C=8D=E5=8A=A1=E5=99=A8?= =?UTF-8?q?=E8=BF=9E=E6=8E=A5=E5=92=8C=E7=BB=91=E5=AE=9A=E6=92=AD=E6=94=BE?= =?UTF-8?q?=E5=99=A8=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config-example.yaml | 3 + controller/ClientSocketController.go | 171 ++++++++++++------ controller/ServerSocketController.go | 107 ++++++++++- events/BindPlayerEvent.go | 7 - .../{PlayerPause.go => PlayerPauseEvent.go} | 0 ...erStartPlay.go => PlayerStartPlayEvent.go} | 0 .../{PlayerStop.go => PlayerStopPlayEvent.go} | 0 main.go | 5 +- public/server-test.js | 4 + store/cache.go | 12 +- store/model.go | 25 +-- utils/event.go | 8 +- utils/utils.go | 12 ++ 13 files changed, 259 insertions(+), 95 deletions(-) create mode 100644 config-example.yaml delete mode 100644 events/BindPlayerEvent.go rename events/{PlayerPause.go => PlayerPauseEvent.go} (100%) rename events/{PlayerStartPlay.go => PlayerStartPlayEvent.go} (100%) rename events/{PlayerStop.go => PlayerStopPlayEvent.go} (100%) create mode 100644 utils/utils.go diff --git a/config-example.yaml b/config-example.yaml new file mode 100644 index 0000000..a7b983a --- /dev/null +++ b/config-example.yaml @@ -0,0 +1,3 @@ +applications: + test_app: + token: "12345678" \ No newline at end of file diff --git a/controller/ClientSocketController.go b/controller/ClientSocketController.go index 6aa8b4e..8a36fea 100644 --- a/controller/ClientSocketController.go +++ b/controller/ClientSocketController.go @@ -25,6 +25,9 @@ type ClientSocketController struct { var clientSocketControllerInstance *ClientSocketController +var ErrPlayerNotExists = errors.New("code not exists") +var ErrParamsInvalid = errors.New("params invalid") + func NewClientSocketController(basePath string, ctx *context.ServerContext) (*ClientSocketController, error) { instance := &ClientSocketController{ SocketController{ @@ -51,6 +54,7 @@ func (c *ClientSocketController) Initialize() error { server.OnConnect(c.basePath, func(s socketio.Conn) error { s.SetContext(&ClientSocketContext{}) + s.Join("clients") return nil }) @@ -69,6 +73,12 @@ func (c *ClientSocketController) Initialize() error { log.Println("meet error:", e) }) + c.events.AddListener("bindUser", c.OnBindUser) + c.events.AddListener("playMusic", c.OnPlayMusic) + c.events.AddListener("playSoundEffect", c.OnPlaySoundEffect) + c.events.AddListener("playVoice", c.OnPlayVoice) + c.events.AddListener("stopPlay", c.OnStopPlay) + return nil } @@ -89,49 +99,21 @@ func (c *ClientSocketController) SwitchRoom(socket socketio.Conn, prefix string, func (c *ClientSocketController) OnDisconnect(socket socketio.Conn, reason string) { ctx := GetContext[ClientSocketContext](socket) // 删除存储中的code - if ctx.PairCode != "" { - c.storeModel.RemoveCodeInfo(ctx.PairCode) - - if ctx.Token != "" { - playerInfo := c.storeModel.GetPlayerInfo(ctx.Token) - if playerInfo != nil { - playerInfo.PairCode = "" - c.storeModel.SetPlayerInfo(ctx.Token, playerInfo) - } + if ctx.Token != "" { + if ctx.PairCode != "" { + c.storeModel.RemoveCodeInfo(ctx.PairCode) } - } -} - -// RefreshToken 刷新token -func (c *ClientSocketController) RefreshToken(socket socketio.Conn) { - ctx := GetContext[ClientSocketContext](socket) - - oldToken := ctx.Token - var playerInfo *store.PlayerInfo - if oldToken != "" { - playerInfo = c.storeModel.GetPlayerInfo(oldToken) - } - - newToken := randstr.RandomAlphanumeric(32) - ctx.Token = newToken - - if playerInfo == nil { - playerInfo = new(store.PlayerInfo) - } else { - c.storeModel.RemovePlayerInfo(oldToken) - } - if playerInfo.PairCode != "" { // 删除存储中的code - c.storeModel.RemoveCodeInfo(playerInfo.PairCode) - playerInfo.PairCode = "" + playerInfo := c.storeModel.GetPlayerInfo(ctx.Token) + if playerInfo != nil { + playerInfo.PairCode = "" + playerInfo.IsOnline = false + c.storeModel.SetPlayerInfo(ctx.Token, playerInfo) + } } - - c.storeModel.SetPlayerInfo(newToken, playerInfo) - - socket.Emit("token:update", newToken) } -// SetToken 设置token +// SetToken 设置token (客户端登录) func (c *ClientSocketController) SetToken(socket socketio.Conn, token string) { ctx := GetContext[ClientSocketContext](socket) @@ -153,7 +135,7 @@ func (c *ClientSocketController) SetToken(socket socketio.Conn, token string) { } // 通知客户端 - socket.Emit("user:update", userName) + socket.Emit("user:update", ctx.User, userName) // 加入房间 c.SwitchRoom(socket, "user", playerInfo.BindingUser) } @@ -164,10 +146,42 @@ func (c *ClientSocketController) SetToken(socket socketio.Conn, token string) { c.storeModel.SetPlayerInfo(ctx.Token, playerInfo) } + playerInfo.IsOnline = true + c.storeModel.SetPlayerInfo(ctx.Token, playerInfo) + socket.Emit("token:update", token) } } +// RefreshToken 刷新token +func (c *ClientSocketController) RefreshToken(socket socketio.Conn) { + ctx := GetContext[ClientSocketContext](socket) + + oldToken := ctx.Token + var playerInfo *store.PlayerInfo + if oldToken != "" { + playerInfo = c.storeModel.GetPlayerInfo(oldToken) + } + + newToken := randstr.RandomAlphanumeric(32) + ctx.Token = newToken + + if playerInfo == nil { + playerInfo = new(store.PlayerInfo) + } else { + c.storeModel.RemovePlayerInfo(oldToken) + } + + if playerInfo.PairCode != "" { // 删除存储中的code + c.storeModel.RemoveCodeInfo(playerInfo.PairCode) + playerInfo.PairCode = "" + } + + c.storeModel.SetPlayerInfo(newToken, playerInfo) + + socket.Emit("token:update", newToken) +} + // GetCode 获取连接码 func (c *ClientSocketController) GetCode(socket socketio.Conn) { ctx := GetContext[ClientSocketContext](socket) @@ -223,43 +237,75 @@ func (c *ClientSocketController) PingbackStop(socket socketio.Conn, musicId stri } // BindPlayer 用户触发绑定播放器 -func (c *ClientSocketController) BindPlayer(user string, code string) (*store.PlayerInfo, error) { +func (c *ClientSocketController) BindPlayer(user string, code string) (string, error) { // code为空则不绑定 if code == "" { - return nil, errors.New("code invalid") + return "", ErrParamsInvalid } - c.sockets.ForEach("/", "", func(socket socketio.Conn) { + token := "" + c.sockets.ForEach("/", "clients", func(socket socketio.Conn) { ctx := GetContext[ClientSocketContext](socket) - if ctx.PairCode == code { - // 找到需要绑定的客户端 + if ctx.PairCode == code { // 找到需要绑定的客户端 + token = ctx.Token c.events.EmitEvent("bindUser", &events.BindUserEvent{ - Token: ctx.Token, + Token: token, User: user, }) + } + }) - userName := user - userInfo := c.storeModel.GetUserInfo(user) - if userInfo != nil { - userName = userInfo.DisplayName - } + if token != "" { + return token, nil + } else { + return "", ErrPlayerNotExists + } +} - // 通知客户端 - socket.Emit("user:update", userName) +func (c *ClientSocketController) OnBindUser(eventObj ...interface{}) { + event, err := utils.GetEvent[events.BindUserEvent](eventObj) + if err != nil { + log.Panicln("Cannot get event in OnBindUser:", err.Error()) + } + + userName := event.User + userInfo := c.storeModel.GetUserInfo(event.User) + if userInfo == nil { + log.Println("Cannot find user info for user name '" + event.User + "' while bind player") + return + } + userName = userInfo.DisplayName + + playerInfo := c.storeModel.GetPlayerInfo(event.Token) + if playerInfo == nil { + log.Println("Cannot find player info for token '" + event.Token + "' while bind player") + return + } + + // 存储信息 + userInfo.BoundPlayer = append(userInfo.BoundPlayer, event.Token) + playerInfo.BindingUser = event.User + + c.storeModel.SetUserInfo(event.User, userInfo) + c.storeModel.SetPlayerInfo(event.Token, playerInfo) + + // 通知客户端 + c.sockets.ForEach("/", "", func(socket socketio.Conn) { + ctx := GetContext[ClientSocketContext](socket) + if ctx.Token == event.Token { + socket.Emit("user:update", event.User, userName) // 加入房间 - c.SwitchRoom(socket, "user", user) - return + c.SwitchRoom(socket, "user", event.User) } }) - return nil, errors.New("code not exists") } // OnPlayMusic 用户触发播放音乐 func (c *ClientSocketController) OnPlayMusic(eventObj ...interface{}) { event, err := utils.GetEvent[events.PlayMusicEvent](eventObj) if err != nil { - log.Panicln("Cannot get event in OnPlayMusic: ", err.Error()) + log.Panicln("Cannot get event in OnPlayMusic:", err.Error()) } type PlayMusicResponse struct { @@ -283,6 +329,17 @@ func (c *ClientSocketController) OnPlayMusic(eventObj ...interface{}) { c.sockets.BroadcastToRoom("/", "user"+event.User, "music:play", res) } -func (c *ClientSocketController) OnPlaySE(eventObj interface{}) { +// OnPlaySoundEffect 用户触发播放音效 +func (c *ClientSocketController) OnPlaySoundEffect(eventObj ...interface{}) { + +} + +// OnPlayVoice 用户触发播放语音 +func (c *ClientSocketController) OnPlayVoice(eventObj ...interface{}) { + +} + +// OnStopPlay 用户触发停止播放 +func (c *ClientSocketController) OnStopPlay(eventObj ...interface{}) { } diff --git a/controller/ServerSocketController.go b/controller/ServerSocketController.go index cc187f8..0b31be6 100644 --- a/controller/ServerSocketController.go +++ b/controller/ServerSocketController.go @@ -1,12 +1,13 @@ package controller import ( + "errors" socketio "github.com/googollee/go-socket.io" "github.com/hyperzlib/isekai-remote-playback/context" + "github.com/hyperzlib/isekai-remote-playback/events" "github.com/hyperzlib/isekai-remote-playback/store" "github.com/hyperzlib/isekai-remote-playback/utils" "log" - "time" ) type ServerSocketContext struct { @@ -51,7 +52,7 @@ func (c *ServerSocketController) Initialize() error { log.Println("Server connected: " + s.RemoteAddr().String()) - time.AfterFunc(5*time.Second, func() { // 关闭5秒内为登录成功的链接 + /*time.AfterFunc(5*time.Second, func() { // 关闭5秒内为登录成功的链接 ctx := GetContext[ServerSocketContext](s) if !ctx.IsAuth { log.Println("Server auth timeout: " + s.RemoteAddr().String()) @@ -60,21 +61,35 @@ func (c *ServerSocketController) Initialize() error { s.Close() }) } - }) + })*/ return nil }) server.OnEvent(c.basePath, "auth:login", c.AppLogin) - server.OnEvent(c.basePath, "user:player:bind", c.AppLogin) + server.OnEvent(c.basePath, "user:update", c.UpdateUser) + + server.OnEvent(c.basePath, "user:player:bind", c.UserBindPlayer) server.OnError(c.basePath, func(s socketio.Conn, e error) { - log.Println("meet error:", e) + log.Println("Socket error:", e) }) + c.events.AddListener("bindUser", c.OnBindUser) + return nil } +// CheckAuth 检测登录 +func (c *ServerSocketController) CheckAuth(s socketio.Conn) bool { + ctx := GetContext[ServerSocketContext](s) + if !ctx.IsAuth { + s.Emit("error", "ERR::NEED_AUTH") + return false + } + return true +} + func (c *ServerSocketController) AppLogin(s socketio.Conn, appId string, token string) { if appId == "" || token == "" { s.Emit("error", "ERR::AUTH_PARAM_INVALID") @@ -102,10 +117,88 @@ func (c *ServerSocketController) AppLogin(s socketio.Conn, appId string, token s log.Println("Server auth success: " + s.RemoteAddr().String()) } +func (c *ServerSocketController) UpdateUser(s socketio.Conn, user string, displayName string) { + if !c.CheckAuth(s) { + return + } + + if user == "" { + s.Emit("error:user:update", "ERR::PARAMS_INVALID", user) + } + userInfo := c.storeModel.GetUserInfo(user) + if userInfo == nil { + userInfo = new(store.UserInfo) + } + + if displayName != "" { + userInfo.DisplayName = displayName + } else { + userInfo.DisplayName = user + } + + type PlayerInfoResponse struct { + Name string `json:"name"` + Token string `json:"token"` + IsOnline bool `json:"online"` + } + + newBindingPlayers := make([]string, 0) + boundPlayers := make([]*PlayerInfoResponse, 0) + for _, token := range userInfo.BoundPlayer { + playerInfo := c.storeModel.GetPlayerInfo(token) + if playerInfo != nil { + newBindingPlayers = append(newBindingPlayers, token) + resPlayerInfo := new(PlayerInfoResponse) + resPlayerInfo.Name = playerInfo.Name + resPlayerInfo.Token = utils.AddAsterisks(token, 10, 10) + resPlayerInfo.IsOnline = playerInfo.IsOnline + boundPlayers = append(boundPlayers, resPlayerInfo) + } + } + + userInfo.BoundPlayer = newBindingPlayers + + // 保存用户信息 + c.storeModel.SetUserInfo(user, userInfo) + + s.Emit("user:updated", user, boundPlayers) +} + func (c *ServerSocketController) UserBindPlayer(s socketio.Conn, user string, code string) { - ctx := GetContext[ServerSocketContext](s) - if !ctx.IsAuth { + if !c.CheckAuth(s) { + return + } + + if user == "" || code == "" { + s.Emit("error:user:player:bind", "ERR::PARAMS_INVALID", user, code) + } + instance := clientSocketControllerInstance + _, err := instance.BindPlayer(user, code) + if err != nil { + if errors.Is(err, ErrParamsInvalid) { + s.Emit("error:user:player:bind", "ERR::PARAMS_INVALID", user, code) + } else if errors.Is(err, ErrPlayerNotExists) { + s.Emit("error:user:player:bind", "ERR::PLAYER_NOT_EXISTS", user, code) + } else { + log.Println("Cannot bind user: ", err.Error()) + s.Emit("error:user:player:bind", "ERR::UNKNOWN", user, code) + } + } +} + +// OnBindUser 播放器绑定用户 +func (c *ServerSocketController) OnBindUser(eventObj ...interface{}) { + event, err := utils.GetEvent[events.BindUserEvent](eventObj) + if err != nil { + log.Panicln("Cannot get event in OnBindUser: ", err.Error()) + } + + playerInfo := c.storeModel.GetPlayerInfo(event.Token) + if playerInfo == nil { + log.Println("Cannot find player info for token '" + event.Token + "' while bind player") return } + // 广播客户端连接的信息 + c.sockets.BroadcastToNamespace(c.basePath, "user:player:bound", event.Token, playerInfo.Name) } diff --git a/events/BindPlayerEvent.go b/events/BindPlayerEvent.go deleted file mode 100644 index aab67cc..0000000 --- a/events/BindPlayerEvent.go +++ /dev/null @@ -1,7 +0,0 @@ -package events - -type BindPlayerEvent struct { - QueryId string - Code string - User string -} diff --git a/events/PlayerPause.go b/events/PlayerPauseEvent.go similarity index 100% rename from events/PlayerPause.go rename to events/PlayerPauseEvent.go diff --git a/events/PlayerStartPlay.go b/events/PlayerStartPlayEvent.go similarity index 100% rename from events/PlayerStartPlay.go rename to events/PlayerStartPlayEvent.go diff --git a/events/PlayerStop.go b/events/PlayerStopPlayEvent.go similarity index 100% rename from events/PlayerStop.go rename to events/PlayerStopPlayEvent.go diff --git a/main.go b/main.go index 5f3003e..3d4465c 100644 --- a/main.go +++ b/main.go @@ -26,10 +26,7 @@ func initConfig() { viper.SetDefault("http.port", 8913) viper.SetDefault("http.origins", []string{}) - viper.SetDefault("redis.prefix", "socket.io") - - viper.SetDefault("db.type", "sqlite3") - viper.SetDefault("db.url", "./data.db") + viper.SetDefault("redis.prefix", "irp") err := viper.ReadInConfig() if err != nil { diff --git a/public/server-test.js b/public/server-test.js index 3167252..65f9a0d 100644 --- a/public/server-test.js +++ b/public/server-test.js @@ -2,4 +2,8 @@ let socket = io("/server"); socket.on('connect', () => { socket.emit("auth:login", "test_app", "12345678") +}) + +socket.on('auth:update', () => { + socket.emit("user:update", "test", "测试用户") }) \ No newline at end of file diff --git a/store/cache.go b/store/cache.go index fe0d1d1..7a0cd97 100644 --- a/store/cache.go +++ b/store/cache.go @@ -1,6 +1,7 @@ package store import ( + "strings" "time" "github.com/allegro/bigcache" @@ -14,6 +15,8 @@ import ( var CacheStore store.StoreInterface var CachePrefix string +var PersistTTL = 14 * 24 * time.Hour +var CacheTTL = 12 * time.Hour func InitStore() { if viper.GetString("redis.addr") != "" { @@ -24,9 +27,9 @@ func InitStore() { })) CacheStore = redisStore - CachePrefix = viper.GetString("redis.prefix") + CachePrefix = strings.TrimRight(viper.GetString("redis.prefix"), ":") } else { - bigcacheClient, _ := bigcache.NewBigCache(bigcache.DefaultConfig(14 * 24 * time.Hour)) + bigcacheClient, _ := bigcache.NewBigCache(bigcache.DefaultConfig(PersistTTL)) bigcacheStore := bigcache_store.NewBigcache(bigcacheClient) CacheStore = bigcacheStore @@ -37,6 +40,7 @@ func NewManager[T any]() *cache.Cache[T] { return cache.New[T](CacheStore) } -func MakeKey(key string) string { - return CachePrefix + key +func MakeKey(keys ...string) string { + keys = append([]string{CachePrefix}, keys...) + return strings.Join(keys, ":") } diff --git a/store/model.go b/store/model.go index 31f97a7..1617813 100644 --- a/store/model.go +++ b/store/model.go @@ -15,14 +15,15 @@ type StoreModel struct { } type PlayerInfo struct { + IsOnline bool `json:"online"` BindingUser string `json:"binding_user"` PairCode string `json:"pair_code"` Name string `json:"name"` } type UserInfo struct { - DisplayName string - BindingPlayer []string `json:"binding_player"` + DisplayName string `json:"display_name"` + BoundPlayer []string `json:"bound_player"` } func NewStoreModel() *StoreModel { @@ -34,8 +35,8 @@ func NewStoreModel() *StoreModel { } func (m *StoreModel) GetPlayerInfo(token string) *PlayerInfo { - dataStr, err := m.Store.Get(m.Ctx, "player:"+token) - if err != nil && err.Error() != store.NOT_FOUND_ERR { + dataStr, err := m.Store.Get(m.Ctx, MakeKey("player", token)) + if err != nil && err.Error() != store.NOT_FOUND_ERR && err.Error() != "Entry not found" { log.Panicln("Cannot get player info for token ["+token+"]: ", err) } @@ -58,15 +59,15 @@ func (m *StoreModel) SetPlayerInfo(token string, data *PlayerInfo) { log.Panicln("Cannot convert player info to json: ", err) } - err = m.Store.Set(m.Ctx, "player:"+token, string(dataStr)) + err = m.Store.Set(m.Ctx, MakeKey("player", token), string(dataStr), store.WithExpiration(PersistTTL)) if err != nil { log.Panicln("Cannot set player info for token ["+token+"]: ", err) } } func (m *StoreModel) GetUserInfo(userName string) *UserInfo { - dataStr, err := m.Store.Get(m.Ctx, "user:"+userName) - if err != nil && err.Error() != store.NOT_FOUND_ERR { + dataStr, err := m.Store.Get(m.Ctx, MakeKey("user", userName)) + if err != nil && err.Error() != store.NOT_FOUND_ERR && err.Error() != "Entry not found" { log.Panicln("Cannot get user info from userName ["+userName+"]: ", err) } @@ -89,7 +90,7 @@ func (m *StoreModel) SetUserInfo(userName string, data *UserInfo) { log.Panicln("Cannot convert info to json: ", err) } - err = m.Store.Set(m.Ctx, "user:"+userName, string(dataStr)) + err = m.Store.Set(m.Ctx, MakeKey("user", userName), string(dataStr), store.WithExpiration(PersistTTL)) if err != nil { log.Panicln("Cannot set player info for userName ["+userName+"]: ", err) } @@ -100,20 +101,20 @@ func (m *StoreModel) RemovePlayerInfo(token string) { } func (m *StoreModel) GetCodeInfo(code string) string { - token, err := m.Store.Get(m.Ctx, "code:"+code) - if err != nil && err.Error() != store.NOT_FOUND_ERR { + token, err := m.Store.Get(m.Ctx, MakeKey("code", code)) + if err != nil && err.Error() != store.NOT_FOUND_ERR && err.Error() != "Entry not found" { log.Panicln("Cannot get code info from code ["+code+"]: ", err) } return token } func (m *StoreModel) SetCodeInfo(code string, token string) { - err := m.Store.Set(m.Ctx, "code:"+code, token) + err := m.Store.Set(m.Ctx, MakeKey("code", code), token, store.WithExpiration(CacheTTL)) if err != nil { log.Panicln("Cannot set code info: ", err) } } func (m *StoreModel) RemoveCodeInfo(code string) { - m.Store.Delete(m.Ctx, "code:"+code) + m.Store.Delete(m.Ctx, MakeKey("code", code)) } diff --git a/utils/event.go b/utils/event.go index 911de29..8d202bb 100644 --- a/utils/event.go +++ b/utils/event.go @@ -4,11 +4,11 @@ import "errors" func GetEvent[T interface{}](eventObj []interface{}) (*T, error) { if len(eventObj) == 0 || eventObj[0] == nil { - return nil, errors.New("Cannot get event from eventObj") + return nil, errors.New("cannot get event from eventObj") } - if event, ok := eventObj[0].(T); ok { - return &event, nil + if event, ok := eventObj[0].(*T); ok { + return event, nil } else { - return nil, errors.New("Cannot get event from eventObj") + return nil, errors.New("cannot get event from eventObj") } } diff --git a/utils/utils.go b/utils/utils.go new file mode 100644 index 0000000..f11aef3 --- /dev/null +++ b/utils/utils.go @@ -0,0 +1,12 @@ +package utils + +import "strings" + +func AddAsterisks(text string, prefixLen int, suffixLen int) string { + strLen := len(text) + padLen := strLen - prefixLen - suffixLen + if padLen <= 0 { + return text + } + return text[0:prefixLen] + strings.Repeat("*", padLen) + text[strLen-suffixLen:strLen] +}