Browse Source

session manage

master
tangmingyou 3 years ago
parent
commit
2876dd313c
  1. 23
      internal/dao/user_dao.go
  2. 2
      internal/model/entity/user.go
  3. 43
      internal/service/auth.go
  4. 37
      internal/service/online.go
  5. 86
      internal/service/store/store.go
  6. 27
      internal/service/table.go
  7. 1
      internal/session/subject.go

23
internal/dao/user_dao.go

@ -1,6 +1,7 @@
package dao package dao
import ( import (
"errors"
"fmt" "fmt"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"gorm.io/gorm" "gorm.io/gorm"
@ -46,3 +47,25 @@ func (u *User) FindUserByName(username string) *entity.User {
} }
return user return user
} }
func (u *User) IncrementTokenVersion(userId int64) (int32, error) {
u1 := &entity.User{}
tx := u.DB.Select("token_version").First(u1, userId)
// fmt.Printf("current user: %v, %d\n", u1, tx.RowsAffected)
if tx.Error != nil {
return 0, tx.Error
}
newTokenVersion := u1.TokenVersion + 1
tx.Model(&entity.User{}).
Where("id", userId).
Where("token_version", u1.TokenVersion).
Update("token_version", newTokenVersion)
// fmt.Printf("RowsAffected: %d\n\n", tx.RowsAffected)
if tx.Error != nil {
return 0, tx.Error
}
if tx.RowsAffected != 1 {
return 0, errors.New("登录异常")
}
return newTokenVersion, nil
}

2
internal/model/entity/user.go

@ -8,7 +8,7 @@ type User struct {
Balance int32 `gorm:"column:balance;default:0" json:"balance"` // comment:余额 Balance int32 `gorm:"column:balance;default:0" json:"balance"` // comment:余额
Avatar string `gorm:"column:avatar" json:"avatar"` // comment:头像 Avatar string `gorm:"column:avatar" json:"avatar"` // comment:头像
Version int32 `gorm:"column:version;default:0" json:"version"` // comment:更新版本锁 Version int32 `gorm:"column:version;default:0" json:"version"` // comment:更新版本锁
TokenVersion int32 `gorm:"column:token_version;default:0" json:"tokenVersion"` // comment:token版本,重新登录后之前的token失效,登录丢到缓存中和token过期时间相同 TokenVersion int32 `gorm:"column:token_version;default:1" json:"tokenVersion"` // comment:token版本,重新登录后之前的token失效,登录丢到缓存中和token过期时间相同
} }
func (u *User) TableName() string { func (u *User) TableName() string {

43
internal/service/auth.go

@ -76,7 +76,15 @@ func Authorize(ctx *gin.Context) {
Name: user.Username, Name: user.Username,
Time: time.Now().UnixMilli(), Time: time.Now().UnixMilli(),
Avatar: user.Avatar, Avatar: user.Avatar,
TokenVersion: user.TokenVersion + 1,
} }
// dao.UserDao.UpdateTokenVersion(user.Id, sub.TokenVersion, user.TokenVersion)
tVersion, err := dao.UserDao.IncrementTokenVersion(user.Id)
if err != nil {
ctx.JSON(http.StatusUnauthorized, gin.H{"msg": err.Error()})
return
}
store.TokenVersions.SetDefault(user.Id, tVersion)
token, err := EncodeSubject(sub) token, err := EncodeSubject(sub)
if err != nil { if err != nil {
@ -103,6 +111,7 @@ func registerUser(username string, password string) (*entity.User, error) {
// 生成头像 // 生成头像
avatar := fmt.Sprintf("%d%d%s", time.Now().UnixMilli(), rand.Intn(90)+10, ".jpg") avatar := fmt.Sprintf("%d%d%s", time.Now().UnixMilli(), rand.Intn(90)+10, ".jpg")
err := govatar.GenerateFile(govatar.MALE, conf.Conf.Game.AvatarPath+avatar) err := govatar.GenerateFile(govatar.MALE, conf.Conf.Game.AvatarPath+avatar)
// 保存用户信息到db
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -112,9 +121,12 @@ func registerUser(username string, password string) (*entity.User, error) {
Balance: conf.Conf.Game.GiftChip, Balance: conf.Conf.Game.GiftChip,
Avatar: avatar, Avatar: avatar,
Version: 0, Version: 0,
TokenVersion: 0,
}
tx := dao.UserDao.DB.Save(user)
if tx.Error != nil {
return nil, tx.Error
} }
// TODO error
dao.UserDao.DB.Save(user)
return user, nil return user, nil
} }
@ -152,10 +164,15 @@ func SubjectAuthFilter(ctx *gin.Context) {
if err != nil { if err != nil {
ctx.JSON(http.StatusUnauthorized, gin.H{"msg": "认证失败,请重新登录"}) ctx.JSON(http.StatusUnauthorized, gin.H{"msg": "认证失败,请重新登录"})
ctx.Abort() ctx.Abort()
} else { return
}
if subject.TokenVersion != store.TokenVersions.Get(subject.Id) {
ctx.JSON(http.StatusUnauthorized, gin.H{"msg": "登录已过期"})
ctx.Abort()
return
}
// 设置token用户到请求上下文 // 设置token用户到请求上下文
ctx.Set(SubjectKey, subject) ctx.Set(SubjectKey, subject)
}
} }
// EncodeSubject 对subject对象aesCBC加密并返回base64Std编码的 token // EncodeSubject 对subject对象aesCBC加密并返回base64Std编码的 token
@ -206,6 +223,9 @@ func HandleReqIdentity(client *session.NetClient, msg *api.ReqIdentity) (proto.M
// client.Close("authorize failed!") // client.Close("authorize failed!")
return &api.ResFail{Code: 401, Msg: err.Error()}, nil return &api.ResFail{Code: 401, Msg: err.Error()}, nil
} }
if subject.TokenVersion != store.TokenVersions.Get(subject.Id) {
return &api.ResFail{Code: 401, Msg: "登录已过期"}, nil
}
account := &session.NetAccount{ account := &session.NetAccount{
Id: subject.Id, Id: subject.Id,
@ -216,8 +236,19 @@ func HandleReqIdentity(client *session.NetClient, msg *api.ReqIdentity) (proto.M
BalanceLock: &sync.RWMutex{}, BalanceLock: &sync.RWMutex{},
Lock: &sync.Mutex{}, Lock: &sync.Mutex{},
} }
// TODO 查询 DB 账户余额 // TODO 查询 DB 账户余额,处理断线、重连问题
account = store.SaveAndTryRecoverNetAccounts(account) current := store.NetAccounts.Get(account.Id)
if current == nil {
store.NetAccounts.SetDefault(account.Id, account)
} else {
// 掉线重连的用户, TODO 重复登录的用户
current.Client.Write(&api.ResFail{Code: 403, Msg: "该账号在其他地方登录,您已下线"})
current.Client = account.Client
if current.Player != nil {
current.Player.ProtoWriter = account.Client
}
account = current
}
client.Account = account client.Account = account
// response // response

37
internal/service/online.go

@ -0,0 +1,37 @@
package service
import (
"fmt"
"texas-poker-bk/internal/service/store"
"texas-poker-bk/internal/session"
"texas-poker-bk/tool/collect"
"time"
)
func init() {
OfflineQueue := collect.NewDelayQueue(HandleUserOffline)
OfflineQueue.Add(1, nil)
}
func HandleUserOffline(account *session.NetAccount, t time.Time) {
fmt.Printf("account %d, %s handle offline.\n", account.Id, account.UserName)
if account == nil || store.NetAccounts.Get(account.Id) == nil {
return
}
if account.Player == nil {
// 不在游戏中,直接下线
removeOnlineUser(account)
return
}
if collect.In(account.Player.GameTable.Stage, 1, 9) {
// TODO account.Player.GameTable.RemovePlayer()
}
}
func removeOnlineUser(account *session.NetAccount) {
store.NetAccounts.Delete(account.Id)
store.TokenVersions.Delete(account.Id)
// TODO 持久化账户金额
}

86
internal/service/store/store.go

@ -1,13 +1,9 @@
package store package store
import ( import (
"errors"
"fmt"
"github.com/patrickmn/go-cache" "github.com/patrickmn/go-cache"
"strconv" "strconv"
"sync"
"sync/atomic" "sync/atomic"
"texas-poker-bk/api"
"texas-poker-bk/internal/game" "texas-poker-bk/internal/game"
"texas-poker-bk/internal/session" "texas-poker-bk/internal/session"
"time" "time"
@ -15,22 +11,13 @@ import (
var ( var (
TableNo *atomic.Int32 TableNo *atomic.Int32
LobbyTables map[int32]*game.Table
lobbyLock *sync.Mutex LobbyTables *store[int32, *game.Table]
// NetAccounts 存储一下在线用户, TODO 连接终端,未在牌局或牌局未开始中回收账号,机器人代打后回收账号 // NetAccounts 存储一下在线用户, TODO 连接终端,未在牌局或牌局未开始中回收账号,机器人代打后回收账号
NetAccounts map[int64]*session.NetAccount NetAccounts *store[int64, *session.NetAccount]
accountLock *sync.Mutex
TokenVersions *store[int64, int32]
// 存储登录 token version
//TokenVersionStore map[int64]int32
//tokenVersionLock *sync.Mutex
// 后面使用多个缓存实体,减少不同类型锁时间
storeCache *cache.Cache
// a. cache in one -> type conv
// b. cache in many -> space/code more
TokenVStore *store[int64, int32]
) )
const ( const (
@ -57,55 +44,46 @@ func (s *store[K, V]) Get(k K) V {
if !found { if !found {
return s.zeroValue return s.zeroValue
} }
return v return v.(V)
} }
func (s *store[K, V]) Delete(k K) { func (s *store[K, V]) Delete(k K) {
s.c.Delete(s.k2str(k)) s.c.Delete(s.k2str(k))
} }
func (s *store[K, V]) ForEach(f func(k string, v V)) {
for k, v := range s.c.Items() {
f(k, v.Object.(V))
}
}
func (s *store[K, V]) Count() int {
return s.c.ItemCount()
}
func init() { func init() {
TokenVStore = &store[int64, int32]{ LobbyTables = &store[int32, *game.Table]{
c: cache.New(cache.NoExpiration, cache.NoExpiration),
zeroValue: nil,
k2str: func(k int32) string {
return strconv.Itoa(int(k))
},
}
NetAccounts = &store[int64, *session.NetAccount]{
c: cache.New(cache.NoExpiration, cache.NoExpiration), c: cache.New(cache.NoExpiration, cache.NoExpiration),
zeroValue: 0, zeroValue: nil,
k2str: func(k int64) string { k2str: func(k int64) string {
return strconv.FormatInt(k, 10) return strconv.FormatInt(k, 10)
}, },
} }
TableNo = &atomic.Int32{} // 牌桌编号计数器 TokenVersions = &store[int64, int32]{
c: cache.New(cache.NoExpiration, cache.NoExpiration),
LobbyTables = make(map[int32]*game.Table, 16) zeroValue: -1,
lobbyLock = &sync.Mutex{} k2str: func(k int64) string {
return strconv.FormatInt(k, 10)
NetAccounts = make(map[int64]*session.NetAccount) },
accountLock = &sync.Mutex{}
}
func SaveNewTable(table *game.Table) error {
lobbyLock.Lock()
defer lobbyLock.Unlock()
if LobbyTables[table.TableNo] != nil {
return errors.New(fmt.Sprintf("table no %d exists!", table.TableNo))
} }
LobbyTables[table.TableNo] = table
return nil
}
// SaveAndTryRecoverNetAccounts 保存一下在线账户映射关系 TODO 处理断线、重连问题 TableNo = &atomic.Int32{} // 牌桌编号计数器
func SaveAndTryRecoverNetAccounts(account *session.NetAccount) *session.NetAccount {
accountLock.Lock()
defer accountLock.Unlock()
current := NetAccounts[account.Id]
if current == nil {
NetAccounts[account.Id] = account
return account
}
// 掉线重连的用户, TODO 重复登录的用户
current.Client.Write(&api.ResFail{Code: 403, Msg: "该账号在其他地方登录,您已下线"})
current.Client = account.Client
if current.Player != nil {
current.Player.ProtoWriter = account.Client
}
return current
} }

27
internal/service/table.go

@ -80,20 +80,21 @@ func HandleReqCreateTable(account *session.NetAccount, msg *api.ReqCreateTable)
for i := 0; i < int(robotNum); i++ { for i := 0; i < int(robotNum); i++ {
table.Robots[i] = &game.Robot{} table.Robots[i] = &game.Robot{}
} }
err := store.SaveNewTable(table) store.LobbyTables.SetDefault(table.TableNo, table)
if err != nil { //err := store.SaveNewTable(table)
return &api.ResFail{Msg: err.Error()}, nil //if err != nil {
} // return &api.ResFail{Msg: err.Error()}, nil
//}
return &api.ResCreateTable{TableNo: table.TableNo}, nil return &api.ResCreateTable{TableNo: table.TableNo}, nil
} }
// HandleReqLobbyView 返回当前所有桌面和玩家数量 // HandleReqLobbyView 返回当前所有桌面和玩家数量
func HandleReqLobbyView(account *session.NetAccount, msg *api.ReqLobbyView) (proto.Message, error) { func HandleReqLobbyView(account *session.NetAccount, msg *api.ReqLobbyView) (proto.Message, error) {
res := &api.ResLobbyView{} res := &api.ResLobbyView{}
if store.LobbyTables == nil || len(store.LobbyTables) == 0 { if store.LobbyTables.Count() == 0 {
return res, nil return res, nil
} }
tables := make([]*api.LobbyTable, len(store.LobbyTables)) tables := make([]*api.LobbyTable, store.LobbyTables.Count())
if account.Player != nil { if account.Player != nil {
res.CurTableNo = account.Player.GameTable.TableNo res.CurTableNo = account.Player.GameTable.TableNo
} }
@ -101,7 +102,7 @@ func HandleReqLobbyView(account *session.NetAccount, msg *api.ReqLobbyView) (pro
// 遍历 store tables 转换为视图层结构体 // 遍历 store tables 转换为视图层结构体
idx := 0 idx := 0
for _, t := range store.LobbyTables { store.LobbyTables.ForEach(func(k string, t *game.Table) {
table := &api.LobbyTable{TableNo: t.TableNo, PlayerNum: t.PlayerNum, RobotNum: t.RobotNum} table := &api.LobbyTable{TableNo: t.TableNo, PlayerNum: t.PlayerNum, RobotNum: t.RobotNum}
table.Players = make([]*api.LobbyPlayer, t.PlayerNum+t.RobotNum) table.Players = make([]*api.LobbyPlayer, t.PlayerNum+t.RobotNum)
@ -126,7 +127,7 @@ func HandleReqLobbyView(account *session.NetAccount, msg *api.ReqLobbyView) (pro
tables[idx] = table tables[idx] = table
idx++ idx++
} })
// 牌桌号降序 // 牌桌号降序
sort.Slice(tables, func(i, j int) bool { sort.Slice(tables, func(i, j int) bool {
return tables[i].TableNo > tables[j].TableNo return tables[i].TableNo > tables[j].TableNo
@ -142,7 +143,7 @@ func HandleReqJoinTable(account *session.NetAccount, msg *api.ReqJoinTable) (pro
if account.Player != nil { if account.Player != nil {
return &api.ResFail{Msg: fmt.Sprintf("当前已加入#%d牌桌", account.Player.GameTable.TableNo)}, nil return &api.ResFail{Msg: fmt.Sprintf("当前已加入#%d牌桌", account.Player.GameTable.TableNo)}, nil
} }
table := store.LobbyTables[msg.TableNo] table := store.LobbyTables.Get(msg.TableNo)
if table == nil { if table == nil {
return &api.ResFail{Msg: "牌桌不存在"}, nil return &api.ResFail{Msg: "牌桌不存在"}, nil
} }
@ -209,7 +210,7 @@ func HandleReqKickOutTable(player *game.Player, msg *api.ReqKickOutTable) (proto
// 从牌桌移除该玩家 // 从牌桌移除该玩家
player.GameTable.Players[i] = nil player.GameTable.Players[i] = nil
// 被对踢出人发送消息 // 被对踢出人发送消息
account := store.NetAccounts[p.Id] account := store.NetAccounts.Get(p.Id)
account.Player = nil // 解除账户绑定 account.Player = nil // 解除账户绑定
account.IncrementBalance(player.Chip) // 筹码返还账户 account.IncrementBalance(player.Chip) // 筹码返还账户
p.ProtoWriter.Write(&api.ResKickOutTable{}) // 被踢玩家消息 p.ProtoWriter.Write(&api.ResKickOutTable{}) // 被踢玩家消息
@ -242,7 +243,7 @@ func HandleReqLeaveTable(player *game.Player, msg *api.ReqLeaveTable) (proto.Mes
break break
} }
} }
account := store.NetAccounts[player.Id] account := store.NetAccounts.Get(player.Id)
account.Player = nil // 解除账户绑定 account.Player = nil // 解除账户绑定
account.IncrementBalance(player.Chip) // 筹码返还账户 account.IncrementBalance(player.Chip) // 筹码返还账户
@ -318,13 +319,13 @@ func HandleReqDismissGameTable(player *game.Player, msg *api.ReqDismissGameTable
return &api.ResFail{Msg: fmt.Sprintf("#%d,牌局进行中", player.GameTable.Stage)}, nil return &api.ResFail{Msg: fmt.Sprintf("#%d,牌局进行中", player.GameTable.Stage)}, nil
} }
player.GameTable.Stage = 9 player.GameTable.Stage = 9
delete(store.LobbyTables, player.GameTable.TableNo) store.LobbyTables.Delete(player.GameTable.TableNo)
// 通知所有玩家,解除账号绑定,结算玩家金额 // 通知所有玩家,解除账号绑定,结算玩家金额
for i, p := range player.GameTable.Players { for i, p := range player.GameTable.Players {
if p == nil { if p == nil {
continue continue
} }
account := store.NetAccounts[p.Id] account := store.NetAccounts.Get(p.Id)
account.IncrementBalance(p.Chip) account.IncrementBalance(p.Chip)
account.Player = nil account.Player = nil
player.GameTable.Players[i] = nil player.GameTable.Players[i] = nil

1
internal/session/subject.go

@ -6,4 +6,5 @@ type Subject struct {
Name string `json:"name,omitempty"` // 用户名 Name string `json:"name,omitempty"` // 用户名
Avatar string `json:"avatar,omitempty"` // 头像 TODO 缓存,不加到token Avatar string `json:"avatar,omitempty"` // 头像 TODO 缓存,不加到token
Time int64 `json:"time,omitempty"` // 生成时间戳 Time int64 `json:"time,omitempty"` // 生成时间戳
TokenVersion int32 `json:"tv,omitempty"`
} }

Loading…
Cancel
Save