mirror of
https://github.com/FlourishingWorld/hk4e.git
synced 2026-02-23 14:32:27 +08:00
玩家管理模块接入redis,已完整实现好友、多人世界的跨服交互功能
This commit is contained in:
@@ -6,56 +6,36 @@ import (
|
||||
"hk4e/gs/dao"
|
||||
"hk4e/gs/model"
|
||||
"hk4e/pkg/logger"
|
||||
"hk4e/pkg/object"
|
||||
"hk4e/protocol/proto"
|
||||
)
|
||||
|
||||
type SaveUserData struct {
|
||||
insertPlayerList []*model.Player
|
||||
updatePlayerList []*model.Player
|
||||
}
|
||||
// 玩家管理器
|
||||
|
||||
// 玩家注册 从db查询对应uid是否存在并异步回调返回结果
|
||||
// 玩家登录 从db查询出来然后写入redis并异步回调返回玩家对象
|
||||
// 玩家离线 写入db和redis
|
||||
// 玩家定时保存 写入db和redis
|
||||
|
||||
type UserManager struct {
|
||||
dao *dao.Dao
|
||||
playerMap map[uint32]*model.Player
|
||||
saveUserChan chan *SaveUserData
|
||||
remotePlayerMap map[uint32]string // 远程玩家 key:userId value:玩家所在gs的appid
|
||||
dao *dao.Dao // db对象
|
||||
playerMap map[uint32]*model.Player // 内存玩家数据
|
||||
saveUserChan chan *SaveUserData // 用于主协程发送玩家数据给定时保存协程
|
||||
remotePlayerMap map[uint32]string // 远程玩家 key:userId value:玩家所在gs的appid
|
||||
}
|
||||
|
||||
func NewUserManager(dao *dao.Dao) (r *UserManager) {
|
||||
r = new(UserManager)
|
||||
r.dao = dao
|
||||
r.playerMap = make(map[uint32]*model.Player)
|
||||
r.saveUserChan = make(chan *SaveUserData)
|
||||
r.saveUserChan = make(chan *SaveUserData) // 无缓冲区chan 避免主协程在写入时被迫加锁
|
||||
r.remotePlayerMap = make(map[uint32]string)
|
||||
return r
|
||||
}
|
||||
|
||||
func (u *UserManager) GetRemoteUserOnlineState(userId uint32) bool {
|
||||
_, exist := u.remotePlayerMap[userId]
|
||||
if !exist {
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func (u *UserManager) GetRemoteUserGsAppId(userId uint32) string {
|
||||
appId, exist := u.remotePlayerMap[userId]
|
||||
if !exist {
|
||||
return ""
|
||||
} else {
|
||||
return appId
|
||||
}
|
||||
}
|
||||
|
||||
func (u *UserManager) SetRemoteUserOnlineState(userId uint32, isOnline bool, appId string) {
|
||||
if isOnline {
|
||||
u.remotePlayerMap[userId] = appId
|
||||
} else {
|
||||
delete(u.remotePlayerMap, userId)
|
||||
}
|
||||
}
|
||||
// 在线玩家相关操作
|
||||
|
||||
// GetUserOnlineState 获取玩家在线状态
|
||||
func (u *UserManager) GetUserOnlineState(userId uint32) bool {
|
||||
player, exist := u.playerMap[userId]
|
||||
if !exist {
|
||||
@@ -65,6 +45,7 @@ func (u *UserManager) GetUserOnlineState(userId uint32) bool {
|
||||
}
|
||||
}
|
||||
|
||||
// GetOnlineUser 获取在线玩家对象
|
||||
func (u *UserManager) GetOnlineUser(userId uint32) *model.Player {
|
||||
player, exist := u.playerMap[userId]
|
||||
if !exist {
|
||||
@@ -78,6 +59,7 @@ func (u *UserManager) GetOnlineUser(userId uint32) *model.Player {
|
||||
}
|
||||
}
|
||||
|
||||
// GetAllOnlineUserList 获取全部在线玩家
|
||||
func (u *UserManager) GetAllOnlineUserList() map[uint32]*model.Player {
|
||||
onlinePlayerMap := make(map[uint32]*model.Player)
|
||||
for userId, player := range u.playerMap {
|
||||
@@ -97,13 +79,14 @@ type PlayerRegInfo struct {
|
||||
GateAppId string
|
||||
}
|
||||
|
||||
// CheckUserExistOnReg 玩家注册检查是否已存在
|
||||
func (u *UserManager) CheckUserExistOnReg(userId uint32, req *proto.SetPlayerBornDataReq, clientSeq uint32, gateAppId string) (exist bool, asyncWait bool) {
|
||||
_, exist = u.playerMap[userId]
|
||||
if exist {
|
||||
return true, false
|
||||
} else {
|
||||
go func() {
|
||||
player := u.loadUserFromDb(userId)
|
||||
player := u.LoadUserFromDbSync(userId)
|
||||
exist = false
|
||||
if player != nil {
|
||||
exist = true
|
||||
@@ -123,38 +106,7 @@ func (u *UserManager) CheckUserExistOnReg(userId uint32, req *proto.SetPlayerBor
|
||||
}
|
||||
}
|
||||
|
||||
func (u *UserManager) LoadTempOfflineUserSync(userId uint32) *model.Player {
|
||||
player, exist := u.playerMap[userId]
|
||||
if exist {
|
||||
return player
|
||||
} else {
|
||||
player = u.loadUserFromDb(userId)
|
||||
if player == nil {
|
||||
return nil
|
||||
}
|
||||
u.ChangeUserDbState(player, model.DbDelete)
|
||||
u.playerMap[player.PlayerID] = player
|
||||
return player
|
||||
}
|
||||
}
|
||||
|
||||
func (u *UserManager) loadUserFromDb(userId uint32) *model.Player {
|
||||
player, err := u.dao.QueryPlayerByID(userId)
|
||||
if err != nil {
|
||||
logger.Error("query player error: %v", err)
|
||||
return nil
|
||||
}
|
||||
return player
|
||||
}
|
||||
|
||||
func (u *UserManager) saveUserToDb(player *model.Player) {
|
||||
err := u.dao.UpdatePlayer(player)
|
||||
if err != nil {
|
||||
logger.Error("update player error: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// AddUser 向内存玩家数据里添加一个玩家
|
||||
func (u *UserManager) AddUser(player *model.Player) {
|
||||
if player == nil {
|
||||
return
|
||||
@@ -163,12 +115,9 @@ func (u *UserManager) AddUser(player *model.Player) {
|
||||
u.playerMap[player.PlayerID] = player
|
||||
}
|
||||
|
||||
func (u *UserManager) DeleteUser(player *model.Player) {
|
||||
if player == nil {
|
||||
return
|
||||
}
|
||||
u.ChangeUserDbState(player, model.DbDelete)
|
||||
u.playerMap[player.PlayerID] = player
|
||||
// DeleteUser 从内存玩家数据里删除一个玩家
|
||||
func (u *UserManager) DeleteUser(userId uint32) {
|
||||
delete(u.playerMap, userId)
|
||||
}
|
||||
|
||||
type PlayerLoginInfo struct {
|
||||
@@ -178,6 +127,7 @@ type PlayerLoginInfo struct {
|
||||
GateAppId string
|
||||
}
|
||||
|
||||
// OnlineUser 玩家上线
|
||||
func (u *UserManager) OnlineUser(userId uint32, clientSeq uint32, gateAppId string) (*model.Player, bool) {
|
||||
player, exist := u.playerMap[userId]
|
||||
if exist {
|
||||
@@ -185,8 +135,9 @@ func (u *UserManager) OnlineUser(userId uint32, clientSeq uint32, gateAppId stri
|
||||
return player, false
|
||||
} else {
|
||||
go func() {
|
||||
player = u.loadUserFromDb(userId)
|
||||
player = u.LoadUserFromDbSync(userId)
|
||||
if player != nil {
|
||||
u.SaveUserToRedisSync(player)
|
||||
u.ChangeUserDbState(player, model.DbNormal)
|
||||
} else {
|
||||
logger.Error("can not find user from db, uid: %v", userId)
|
||||
@@ -205,6 +156,38 @@ func (u *UserManager) OnlineUser(userId uint32, clientSeq uint32, gateAppId stri
|
||||
}
|
||||
}
|
||||
|
||||
type ChangeGsInfo struct {
|
||||
IsChangeGs bool
|
||||
JoinHostUserId uint32
|
||||
}
|
||||
|
||||
type PlayerOfflineInfo struct {
|
||||
Player *model.Player
|
||||
ChangeGsInfo *ChangeGsInfo
|
||||
}
|
||||
|
||||
// OfflineUser 玩家离线
|
||||
func (u *UserManager) OfflineUser(player *model.Player, changeGsInfo *ChangeGsInfo) {
|
||||
playerCopy := new(model.Player)
|
||||
err := object.FastDeepCopy(playerCopy, player)
|
||||
if err != nil {
|
||||
logger.Error("deep copy player error: %v", err)
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
u.SaveUserToDbSync(playerCopy)
|
||||
u.SaveUserToRedisSync(playerCopy)
|
||||
LOCAL_EVENT_MANAGER.localEventChan <- &LocalEvent{
|
||||
EventId: UserOfflineSaveToDbFinish,
|
||||
Msg: &PlayerOfflineInfo{
|
||||
Player: player,
|
||||
ChangeGsInfo: changeGsInfo,
|
||||
},
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// ChangeUserDbState 玩家存档状态机 主要用于玩家定时保存时进行分类处理
|
||||
func (u *UserManager) ChangeUserDbState(player *model.Player, state int) {
|
||||
if player == nil {
|
||||
return
|
||||
@@ -238,8 +221,140 @@ func (u *UserManager) ChangeUserDbState(player *model.Player, state int) {
|
||||
}
|
||||
}
|
||||
|
||||
// 用户数据库定时同步
|
||||
// 远程玩家相关操作
|
||||
|
||||
func (u *UserManager) GetRemoteUserOnlineState(userId uint32) bool {
|
||||
_, exist := u.remotePlayerMap[userId]
|
||||
if !exist {
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func (u *UserManager) GetRemoteUserGsAppId(userId uint32) string {
|
||||
appId, exist := u.remotePlayerMap[userId]
|
||||
if !exist {
|
||||
return ""
|
||||
} else {
|
||||
return appId
|
||||
}
|
||||
}
|
||||
|
||||
func (u *UserManager) SetRemoteUserOnlineState(userId uint32, isOnline bool, appId string) {
|
||||
if isOnline {
|
||||
u.remotePlayerMap[userId] = appId
|
||||
} else {
|
||||
delete(u.remotePlayerMap, userId)
|
||||
u.DeleteUser(userId)
|
||||
}
|
||||
}
|
||||
|
||||
// GetRemoteOnlineUserList 获取指定数量的远程在线玩家
|
||||
func (u *UserManager) GetRemoteOnlineUserList(total int) map[uint32]*model.Player {
|
||||
if total > 50 {
|
||||
return nil
|
||||
}
|
||||
onlinePlayerMap := make(map[uint32]*model.Player)
|
||||
count := 0
|
||||
for userId := range u.remotePlayerMap {
|
||||
player := u.LoadTempOfflineUser(userId)
|
||||
if player == nil {
|
||||
continue
|
||||
}
|
||||
onlinePlayerMap[player.PlayerID] = player
|
||||
count++
|
||||
if count >= total {
|
||||
break
|
||||
}
|
||||
}
|
||||
return onlinePlayerMap
|
||||
}
|
||||
|
||||
// LoadGlobalPlayer 加载并返回一个全服玩家及其在线状态
|
||||
// 参见LoadTempOfflineUser说明
|
||||
func (u *UserManager) LoadGlobalPlayer(userId uint32) (player *model.Player, online bool, remote bool) {
|
||||
online = u.GetUserOnlineState(userId)
|
||||
remote = false
|
||||
if !online {
|
||||
// 本地不在线就看看远程在不在线
|
||||
online = u.GetRemoteUserOnlineState(userId)
|
||||
if online {
|
||||
remote = true
|
||||
}
|
||||
}
|
||||
if online {
|
||||
if remote {
|
||||
// 远程在线玩家 为了简化实现流程 直接加载数据库临时档
|
||||
player = u.LoadTempOfflineUser(userId)
|
||||
} else {
|
||||
// 本地在线玩家
|
||||
player = u.GetOnlineUser(userId)
|
||||
}
|
||||
} else {
|
||||
// 全服离线玩家
|
||||
player = u.LoadTempOfflineUser(userId)
|
||||
}
|
||||
return player, online, remote
|
||||
}
|
||||
|
||||
// 离线玩家相关操作
|
||||
|
||||
// LoadTempOfflineUser 加载临时离线玩家
|
||||
// 正常情况速度较快可以同步阻塞调用
|
||||
func (u *UserManager) LoadTempOfflineUser(userId uint32) *model.Player {
|
||||
player := u.GetOnlineUser(userId)
|
||||
if player != nil && player.Online {
|
||||
logger.Error("not allow get a online player as offline player, uid: %v", userId)
|
||||
return nil
|
||||
}
|
||||
player = u.LoadUserFromRedisSync(userId)
|
||||
if player == nil {
|
||||
// 玩家可能不存在于redis 尝试从db查询出来然后写入redis
|
||||
// 大多数情况下活跃玩家都在redis 所以不会走到下面
|
||||
// TODO 布隆过滤器防止恶意攻击造成redis缓存穿透
|
||||
if userId < 100000000 || userId > 200000000 {
|
||||
logger.Error("try to load a not exist uid, uid: %v", userId)
|
||||
return nil
|
||||
}
|
||||
player = u.LoadUserFromDbSync(userId)
|
||||
if player == nil {
|
||||
// 玩家根本就不存在
|
||||
logger.Error("try to load a not exist player from db, uid: %v", userId)
|
||||
return nil
|
||||
}
|
||||
u.SaveUserToRedisSync(player)
|
||||
}
|
||||
u.ChangeUserDbState(player, model.DbDelete)
|
||||
u.playerMap[player.PlayerID] = player
|
||||
return player
|
||||
}
|
||||
|
||||
// SaveTempOfflineUser 保存临时离线玩家
|
||||
// 如果在调用LoadTempOfflineUser后修改了离线玩家数据 则必须立即调用此函数回写
|
||||
func (u *UserManager) SaveTempOfflineUser(player *model.Player) {
|
||||
// 主协程同步写入redis
|
||||
u.SaveUserToRedisSync(player)
|
||||
// 另一个协程异步的写回db
|
||||
playerCopy := new(model.Player)
|
||||
err := object.FastDeepCopy(playerCopy, player)
|
||||
if err != nil {
|
||||
logger.Error("deep copy player error: %v", err)
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
u.SaveUserToDbSync(playerCopy)
|
||||
}()
|
||||
}
|
||||
|
||||
// db和redis相关操作
|
||||
|
||||
type SaveUserData struct {
|
||||
insertPlayerList []*model.Player
|
||||
updatePlayerList []*model.Player
|
||||
}
|
||||
|
||||
// StartAutoSaveUser 玩家定时保存
|
||||
func (u *UserManager) StartAutoSaveUser() {
|
||||
go func() {
|
||||
ticker := time.NewTicker(time.Minute * 5)
|
||||
@@ -253,12 +368,30 @@ func (u *UserManager) StartAutoSaveUser() {
|
||||
go func() {
|
||||
for {
|
||||
saveUserData := <-u.saveUserChan
|
||||
u.SaveUser(saveUserData)
|
||||
u.SaveUserListToDbSync(saveUserData)
|
||||
u.SaveUserListToRedisSync(saveUserData)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (u *UserManager) SaveUser(saveUserData *SaveUserData) {
|
||||
func (u *UserManager) LoadUserFromDbSync(userId uint32) *model.Player {
|
||||
player, err := u.dao.QueryPlayerByID(userId)
|
||||
if err != nil {
|
||||
logger.Error("query player error: %v", err)
|
||||
return nil
|
||||
}
|
||||
return player
|
||||
}
|
||||
|
||||
func (u *UserManager) SaveUserToDbSync(player *model.Player) {
|
||||
err := u.dao.UpdatePlayer(player)
|
||||
if err != nil {
|
||||
logger.Error("update player error: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (u *UserManager) SaveUserListToDbSync(saveUserData *SaveUserData) {
|
||||
err := u.dao.InsertPlayerList(saveUserData.insertPlayerList)
|
||||
if err != nil {
|
||||
logger.Error("insert player list error: %v", err)
|
||||
@@ -271,3 +404,23 @@ func (u *UserManager) SaveUser(saveUserData *SaveUserData) {
|
||||
}
|
||||
logger.Info("save user finish, insert user count: %v, update user count: %v", len(saveUserData.insertPlayerList), len(saveUserData.updatePlayerList))
|
||||
}
|
||||
|
||||
func (u *UserManager) LoadUserFromRedisSync(userId uint32) *model.Player {
|
||||
player := u.dao.GetRedisPlayer(userId)
|
||||
return player
|
||||
}
|
||||
|
||||
func (u *UserManager) SaveUserToRedisSync(player *model.Player) {
|
||||
u.dao.SetRedisPlayer(player)
|
||||
}
|
||||
|
||||
func (u *UserManager) SaveUserListToRedisSync(saveUserData *SaveUserData) {
|
||||
setPlayerList := make([]*model.Player, 0, len(saveUserData.insertPlayerList)+len(saveUserData.updatePlayerList))
|
||||
for _, player := range saveUserData.insertPlayerList {
|
||||
setPlayerList = append(setPlayerList, player)
|
||||
}
|
||||
for _, player := range saveUserData.updatePlayerList {
|
||||
setPlayerList = append(setPlayerList, player)
|
||||
}
|
||||
u.dao.SetRedisPlayerList(setPlayerList)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user