完成服务器连接和绑定播放器功能

main
落雨楓 2 years ago
parent ef2e7b027e
commit d3d214a6b2

@ -0,0 +1,3 @@
applications:
test_app:
token: "12345678"

@ -25,6 +25,9 @@ type ClientSocketController struct {
var clientSocketControllerInstance *ClientSocketController 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) { func NewClientSocketController(basePath string, ctx *context.ServerContext) (*ClientSocketController, error) {
instance := &ClientSocketController{ instance := &ClientSocketController{
SocketController{ SocketController{
@ -51,6 +54,7 @@ func (c *ClientSocketController) Initialize() error {
server.OnConnect(c.basePath, func(s socketio.Conn) error { server.OnConnect(c.basePath, func(s socketio.Conn) error {
s.SetContext(&ClientSocketContext{}) s.SetContext(&ClientSocketContext{})
s.Join("clients")
return nil return nil
}) })
@ -69,6 +73,12 @@ func (c *ClientSocketController) Initialize() error {
log.Println("meet error:", e) 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 return nil
} }
@ -89,49 +99,21 @@ func (c *ClientSocketController) SwitchRoom(socket socketio.Conn, prefix string,
func (c *ClientSocketController) OnDisconnect(socket socketio.Conn, reason string) { func (c *ClientSocketController) OnDisconnect(socket socketio.Conn, reason string) {
ctx := GetContext[ClientSocketContext](socket) ctx := GetContext[ClientSocketContext](socket)
// 删除存储中的code // 删除存储中的code
if ctx.PairCode != "" { if ctx.Token != "" {
c.storeModel.RemoveCodeInfo(ctx.PairCode) 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)
}
} }
}
}
// 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 playerInfo := c.storeModel.GetPlayerInfo(ctx.Token)
c.storeModel.RemoveCodeInfo(playerInfo.PairCode) if playerInfo != nil {
playerInfo.PairCode = "" 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) { func (c *ClientSocketController) SetToken(socket socketio.Conn, token string) {
ctx := GetContext[ClientSocketContext](socket) 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) 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) c.storeModel.SetPlayerInfo(ctx.Token, playerInfo)
} }
playerInfo.IsOnline = true
c.storeModel.SetPlayerInfo(ctx.Token, playerInfo)
socket.Emit("token:update", token) 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 获取连接码 // GetCode 获取连接码
func (c *ClientSocketController) GetCode(socket socketio.Conn) { func (c *ClientSocketController) GetCode(socket socketio.Conn) {
ctx := GetContext[ClientSocketContext](socket) ctx := GetContext[ClientSocketContext](socket)
@ -223,43 +237,75 @@ func (c *ClientSocketController) PingbackStop(socket socketio.Conn, musicId stri
} }
// BindPlayer 用户触发绑定播放器 // BindPlayer 用户触发绑定播放器
func (c *ClientSocketController) BindPlayer(user string, code string) (*store.PlayerInfo, error) { func (c *ClientSocketController) BindPlayer(user string, code string) (string, error) {
// code为空则不绑定 // code为空则不绑定
if 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) ctx := GetContext[ClientSocketContext](socket)
if ctx.PairCode == code { if ctx.PairCode == code { // 找到需要绑定的客户端
// 找到需要绑定的客户端 token = ctx.Token
c.events.EmitEvent("bindUser", &events.BindUserEvent{ c.events.EmitEvent("bindUser", &events.BindUserEvent{
Token: ctx.Token, Token: token,
User: user, User: user,
}) })
}
})
userName := user if token != "" {
userInfo := c.storeModel.GetUserInfo(user) return token, nil
if userInfo != nil { } else {
userName = userInfo.DisplayName return "", ErrPlayerNotExists
} }
}
// 通知客户端 func (c *ClientSocketController) OnBindUser(eventObj ...interface{}) {
socket.Emit("user:update", userName) 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) c.SwitchRoom(socket, "user", event.User)
return
} }
}) })
return nil, errors.New("code not exists")
} }
// OnPlayMusic 用户触发播放音乐 // OnPlayMusic 用户触发播放音乐
func (c *ClientSocketController) OnPlayMusic(eventObj ...interface{}) { func (c *ClientSocketController) OnPlayMusic(eventObj ...interface{}) {
event, err := utils.GetEvent[events.PlayMusicEvent](eventObj) event, err := utils.GetEvent[events.PlayMusicEvent](eventObj)
if err != nil { if err != nil {
log.Panicln("Cannot get event in OnPlayMusic: ", err.Error()) log.Panicln("Cannot get event in OnPlayMusic:", err.Error())
} }
type PlayMusicResponse struct { type PlayMusicResponse struct {
@ -283,6 +329,17 @@ func (c *ClientSocketController) OnPlayMusic(eventObj ...interface{}) {
c.sockets.BroadcastToRoom("/", "user"+event.User, "music:play", res) 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{}) {
} }

@ -1,12 +1,13 @@
package controller package controller
import ( import (
"errors"
socketio "github.com/googollee/go-socket.io" socketio "github.com/googollee/go-socket.io"
"github.com/hyperzlib/isekai-remote-playback/context" "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/store"
"github.com/hyperzlib/isekai-remote-playback/utils" "github.com/hyperzlib/isekai-remote-playback/utils"
"log" "log"
"time"
) )
type ServerSocketContext struct { type ServerSocketContext struct {
@ -51,7 +52,7 @@ func (c *ServerSocketController) Initialize() error {
log.Println("Server connected: " + s.RemoteAddr().String()) 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) ctx := GetContext[ServerSocketContext](s)
if !ctx.IsAuth { if !ctx.IsAuth {
log.Println("Server auth timeout: " + s.RemoteAddr().String()) log.Println("Server auth timeout: " + s.RemoteAddr().String())
@ -60,21 +61,35 @@ func (c *ServerSocketController) Initialize() error {
s.Close() s.Close()
}) })
} }
}) })*/
return nil return nil
}) })
server.OnEvent(c.basePath, "auth:login", c.AppLogin) 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) { 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 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) { func (c *ServerSocketController) AppLogin(s socketio.Conn, appId string, token string) {
if appId == "" || token == "" { if appId == "" || token == "" {
s.Emit("error", "ERR::AUTH_PARAM_INVALID") 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()) 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) { func (c *ServerSocketController) UserBindPlayer(s socketio.Conn, user string, code string) {
ctx := GetContext[ServerSocketContext](s) if !c.CheckAuth(s) {
if !ctx.IsAuth { 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 return
} }
// 广播客户端连接的信息
c.sockets.BroadcastToNamespace(c.basePath, "user:player:bound", event.Token, playerInfo.Name)
} }

@ -1,7 +0,0 @@
package events
type BindPlayerEvent struct {
QueryId string
Code string
User string
}

@ -26,10 +26,7 @@ func initConfig() {
viper.SetDefault("http.port", 8913) viper.SetDefault("http.port", 8913)
viper.SetDefault("http.origins", []string{}) viper.SetDefault("http.origins", []string{})
viper.SetDefault("redis.prefix", "socket.io") viper.SetDefault("redis.prefix", "irp")
viper.SetDefault("db.type", "sqlite3")
viper.SetDefault("db.url", "./data.db")
err := viper.ReadInConfig() err := viper.ReadInConfig()
if err != nil { if err != nil {

@ -2,4 +2,8 @@ let socket = io("/server");
socket.on('connect', () => { socket.on('connect', () => {
socket.emit("auth:login", "test_app", "12345678") socket.emit("auth:login", "test_app", "12345678")
})
socket.on('auth:update', () => {
socket.emit("user:update", "test", "测试用户")
}) })

@ -1,6 +1,7 @@
package store package store
import ( import (
"strings"
"time" "time"
"github.com/allegro/bigcache" "github.com/allegro/bigcache"
@ -14,6 +15,8 @@ import (
var CacheStore store.StoreInterface var CacheStore store.StoreInterface
var CachePrefix string var CachePrefix string
var PersistTTL = 14 * 24 * time.Hour
var CacheTTL = 12 * time.Hour
func InitStore() { func InitStore() {
if viper.GetString("redis.addr") != "" { if viper.GetString("redis.addr") != "" {
@ -24,9 +27,9 @@ func InitStore() {
})) }))
CacheStore = redisStore CacheStore = redisStore
CachePrefix = viper.GetString("redis.prefix") CachePrefix = strings.TrimRight(viper.GetString("redis.prefix"), ":")
} else { } else {
bigcacheClient, _ := bigcache.NewBigCache(bigcache.DefaultConfig(14 * 24 * time.Hour)) bigcacheClient, _ := bigcache.NewBigCache(bigcache.DefaultConfig(PersistTTL))
bigcacheStore := bigcache_store.NewBigcache(bigcacheClient) bigcacheStore := bigcache_store.NewBigcache(bigcacheClient)
CacheStore = bigcacheStore CacheStore = bigcacheStore
@ -37,6 +40,7 @@ func NewManager[T any]() *cache.Cache[T] {
return cache.New[T](CacheStore) return cache.New[T](CacheStore)
} }
func MakeKey(key string) string { func MakeKey(keys ...string) string {
return CachePrefix + key keys = append([]string{CachePrefix}, keys...)
return strings.Join(keys, ":")
} }

@ -15,14 +15,15 @@ type StoreModel struct {
} }
type PlayerInfo struct { type PlayerInfo struct {
IsOnline bool `json:"online"`
BindingUser string `json:"binding_user"` BindingUser string `json:"binding_user"`
PairCode string `json:"pair_code"` PairCode string `json:"pair_code"`
Name string `json:"name"` Name string `json:"name"`
} }
type UserInfo struct { type UserInfo struct {
DisplayName string DisplayName string `json:"display_name"`
BindingPlayer []string `json:"binding_player"` BoundPlayer []string `json:"bound_player"`
} }
func NewStoreModel() *StoreModel { func NewStoreModel() *StoreModel {
@ -34,8 +35,8 @@ func NewStoreModel() *StoreModel {
} }
func (m *StoreModel) GetPlayerInfo(token string) *PlayerInfo { func (m *StoreModel) GetPlayerInfo(token string) *PlayerInfo {
dataStr, err := m.Store.Get(m.Ctx, "player:"+token) dataStr, err := m.Store.Get(m.Ctx, MakeKey("player", token))
if err != nil && err.Error() != store.NOT_FOUND_ERR { if err != nil && err.Error() != store.NOT_FOUND_ERR && err.Error() != "Entry not found" {
log.Panicln("Cannot get player info for token ["+token+"]: ", err) 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) 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 { if err != nil {
log.Panicln("Cannot set player info for token ["+token+"]: ", err) log.Panicln("Cannot set player info for token ["+token+"]: ", err)
} }
} }
func (m *StoreModel) GetUserInfo(userName string) *UserInfo { func (m *StoreModel) GetUserInfo(userName string) *UserInfo {
dataStr, err := m.Store.Get(m.Ctx, "user:"+userName) dataStr, err := m.Store.Get(m.Ctx, MakeKey("user", userName))
if err != nil && err.Error() != store.NOT_FOUND_ERR { if err != nil && err.Error() != store.NOT_FOUND_ERR && err.Error() != "Entry not found" {
log.Panicln("Cannot get user info from userName ["+userName+"]: ", err) 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) 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 { if err != nil {
log.Panicln("Cannot set player info for userName ["+userName+"]: ", err) 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 { func (m *StoreModel) GetCodeInfo(code string) string {
token, err := m.Store.Get(m.Ctx, "code:"+code) token, err := m.Store.Get(m.Ctx, MakeKey("code", code))
if err != nil && err.Error() != store.NOT_FOUND_ERR { if err != nil && err.Error() != store.NOT_FOUND_ERR && err.Error() != "Entry not found" {
log.Panicln("Cannot get code info from code ["+code+"]: ", err) log.Panicln("Cannot get code info from code ["+code+"]: ", err)
} }
return token return token
} }
func (m *StoreModel) SetCodeInfo(code string, token string) { 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 { if err != nil {
log.Panicln("Cannot set code info: ", err) log.Panicln("Cannot set code info: ", err)
} }
} }
func (m *StoreModel) RemoveCodeInfo(code string) { func (m *StoreModel) RemoveCodeInfo(code string) {
m.Store.Delete(m.Ctx, "code:"+code) m.Store.Delete(m.Ctx, MakeKey("code", code))
} }

@ -4,11 +4,11 @@ import "errors"
func GetEvent[T interface{}](eventObj []interface{}) (*T, error) { func GetEvent[T interface{}](eventObj []interface{}) (*T, error) {
if len(eventObj) == 0 || eventObj[0] == nil { 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 { if event, ok := eventObj[0].(*T); ok {
return &event, nil return event, nil
} else { } else {
return nil, errors.New("Cannot get event from eventObj") return nil, errors.New("cannot get event from eventObj")
} }
} }

@ -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]
}
Loading…
Cancel
Save