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
import (
"errors"
"fmt"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
@ -46,3 +47,25 @@ func (u *User) FindUserByName(username string) *entity.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:余额
Avatar string `gorm:"column:avatar" json:"avatar"` // 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 {

43
internal/service/auth.go

@ -76,7 +76,15 @@ func Authorize(ctx *gin.Context) {
Name: user.Username,
Time: time.Now().UnixMilli(),
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)
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")
err := govatar.GenerateFile(govatar.MALE, conf.Conf.Game.AvatarPath+avatar)
// 保存用户信息到db
if err != nil {
return nil, err
}
@ -112,9 +121,12 @@ func registerUser(username string, password string) (*entity.User, error) {
Balance: conf.Conf.Game.GiftChip,
Avatar: avatar,
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
}
@ -152,11 +164,16 @@ func SubjectAuthFilter(ctx *gin.Context) {
if err != nil {
ctx.JSON(http.StatusUnauthorized, gin.H{"msg": "认证失败,请重新登录"})
ctx.Abort()
} else {
return
}
if subject.TokenVersion != store.TokenVersions.Get(subject.Id) {
ctx.JSON(http.StatusUnauthorized, gin.H{"msg": "登录已过期"})
ctx.Abort()
return
}
// 设置token用户到请求上下文
ctx.Set(SubjectKey, subject)
}
}
// EncodeSubject 对subject对象aesCBC加密并返回base64Std编码的 token
// session 客户端对象
@ -206,6 +223,9 @@ func HandleReqIdentity(client *session.NetClient, msg *api.ReqIdentity) (proto.M
// client.Close("authorize failed!")
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{
Id: subject.Id,
@ -216,8 +236,19 @@ func HandleReqIdentity(client *session.NetClient, msg *api.ReqIdentity) (proto.M
BalanceLock: &sync.RWMutex{},
Lock: &sync.Mutex{},
}
// TODO 查询 DB 账户余额
account = store.SaveAndTryRecoverNetAccounts(account)
// TODO 查询 DB 账户余额,处理断线、重连问题
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
// 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
import (
"errors"
"fmt"
"github.com/patrickmn/go-cache"
"strconv"
"sync"
"sync/atomic"
"texas-poker-bk/api"
"texas-poker-bk/internal/game"
"texas-poker-bk/internal/session"
"time"
@ -15,22 +11,13 @@ import (
var (
TableNo *atomic.Int32
LobbyTables map[int32]*game.Table
lobbyLock *sync.Mutex
LobbyTables *store[int32, *game.Table]
// NetAccounts 存储一下在线用户, TODO 连接终端,未在牌局或牌局未开始中回收账号,机器人代打后回收账号
NetAccounts map[int64]*session.NetAccount
accountLock *sync.Mutex
// 存储登录 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]
NetAccounts *store[int64, *session.NetAccount]
TokenVersions *store[int64, int32]
)
const (
@ -57,55 +44,46 @@ func (s *store[K, V]) Get(k K) V {
if !found {
return s.zeroValue
}
return v
return v.(V)
}
func (s *store[K, V]) Delete(k 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() {
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),
zeroValue: 0,
zeroValue: nil,
k2str: func(k int64) string {
return strconv.FormatInt(k, 10)
},
}
TableNo = &atomic.Int32{} // 牌桌编号计数器
LobbyTables = make(map[int32]*game.Table, 16)
lobbyLock = &sync.Mutex{}
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
TokenVersions = &store[int64, int32]{
c: cache.New(cache.NoExpiration, cache.NoExpiration),
zeroValue: -1,
k2str: func(k int64) string {
return strconv.FormatInt(k, 10)
},
}
// SaveAndTryRecoverNetAccounts 保存一下在线账户映射关系 TODO 处理断线、重连问题
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
TableNo = &atomic.Int32{} // 牌桌编号计数器
}

27
internal/service/table.go

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

1
internal/session/subject.go

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

Loading…
Cancel
Save