Skip to content

Commit

Permalink
- 使用snowflake id替代uuid做session id的种子值,提升性能
Browse files Browse the repository at this point in the history
- 修复在同一会话中新建session id同时写入session数据时,session数据的生命周期失效的问题
  • Loading branch information
dxvgef committed Jul 10, 2020
1 parent 98de83d commit 1b3b3cd
Show file tree
Hide file tree
Showing 2 changed files with 66 additions and 48 deletions.
78 changes: 37 additions & 41 deletions engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,19 @@ package sessions
import (
"encoding/hex"
"errors"
"math/rand"
"net/http"
"strconv"
"time"

"github.com/bwmarrin/snowflake"
"github.com/go-redis/redis/v7"
"github.com/google/uuid"
)

// Engine session管理引擎
type Engine struct {
config *Config // 配置
config *Config // 配置
seedIDNode *snowflake.Node // 种子ID节点
}

// RedisError Redis错误
Expand Down Expand Up @@ -73,6 +76,13 @@ func NewEngine(config *Config) (*Engine, error) {
// 将redis连接对象传入session管理器
engine.config = config

// 创建种子ID节点的实例
seedIDNode, err := newSeedIDNode()
if err != nil {
return nil, err
}
engine.seedIDNode = seedIDNode

return &engine, nil
}

Expand All @@ -81,8 +91,6 @@ func (this *Engine) Use(req *http.Request, resp http.ResponseWriter) (*Session,
var (
sess Session
cookieValid = true
sidValue string
sid string
)

// 从cookie中获得sessionID
Expand All @@ -93,26 +101,28 @@ func (this *Engine) Use(req *http.Request, resp http.ResponseWriter) (*Session,

// 如果cookie中的sessionID有效
if cookieValid {
// 将cookie中的值解码
sid, err = decodeSID(cookieObj.Value, this.config.Key)
// 将cookie中的cid解码成sid
sid, err := decodeSID(cookieObj.Value, this.config.Key)
if err != nil {
return nil, err
}
// 将uuid作为sessionID赋值给session对象
sess.ID = sid
sess.CookieID = cookieObj.Value
sess.StorageID = sid
} else {
var err error
// 生成一个uuid并赋值给session对象
sess.ID = uuid.New().String()
// 将uuid结合key加密成sid
if sidValue, err = encodeByBytes(strToByte(this.config.Key), strToByte(sess.ID)); err != nil {
// 如果cookies中的sessionID无效
// 生成种子id
seedID := this.seedIDNode.Generate().String() + strconv.FormatUint(uint64(rand.New(rand.NewSource(rand.Int63n(time.Now().UnixNano()))).Uint32()), 10)
// 用种子ID编码成cid
cid, err := encodeByBytes(strToByte(this.config.Key), strToByte(seedID))
if err != nil {
return nil, err
}

sess.CookieID = cid
sess.StorageID = this.config.RedisKeyPrefix + ":" + seedID
// 创建一个cookie对象并赋值后写入到客户端
http.SetCookie(resp, &http.Cookie{
Name: this.config.CookieName,
Value: sidValue,
Value: cid,
Domain: this.config.Domain,
Path: this.config.Path,
Expires: time.Now().Add(this.config.IdleTime),
Expand All @@ -122,34 +132,18 @@ func (this *Engine) Use(req *http.Request, resp http.ResponseWriter) (*Session,
})
}

sess.ID = this.config.RedisKeyPrefix + ":" + sess.ID
sess.req = req
sess.resp = resp

// 自动更新空闲时间
if !this.config.DisableAutoUpdateIdleTime {
if err := this.UpdateIdleTime(req, resp); err != nil {
return nil, err
}
}
sess.engine = this

return &sess, nil
}

// 更新session的空闲时间
func (this *Engine) UpdateIdleTime(req *http.Request, resp http.ResponseWriter) error {
// 从cookie中获得sessionID
cookieObj, err := req.Cookie(this.config.CookieName)
if err != nil || cookieObj == nil {
return nil
} else if cookieObj.Value == "" {
return nil
}

func (this *Engine) UpdateIdleTime(cid, sid string, resp http.ResponseWriter) error {
// 更新cookie的超时时间
http.SetCookie(resp, &http.Cookie{
Name: this.config.CookieName,
Value: cookieObj.Value,
Value: cid,
Domain: this.config.Domain,
Path: this.config.Path,
Expires: time.Now().Add(this.config.IdleTime),
Expand All @@ -158,13 +152,7 @@ func (this *Engine) UpdateIdleTime(req *http.Request, resp http.ResponseWriter)
HttpOnly: this.config.HttpOnly,
})

// 将cookie中的值解码
sid, err := decodeSID(cookieObj.Value, this.config.Key)
if err != nil {
return err
}
// 更新redis的超时时间
return redisClient.ExpireAt(this.config.RedisKeyPrefix+":"+sid, time.Now().Add(this.config.IdleTime)).Err()
return redisClient.ExpireAt(sid, time.Now().Add(this.config.IdleTime)).Err()
}

// 解码得到sessionID
Expand Down Expand Up @@ -297,3 +285,11 @@ func (engine *Engine) DeleteByID(id, key string) error {
}
return redisClient.HDel(sid, key).Err()
}

// 设置种子ID的实例
func newSeedIDNode() (*snowflake.Node, error) {
snowflake.Epoch = time.Now().Unix()
rand.Seed(rand.Int63n(time.Now().UnixNano()))
node := 0 + rand.Int63n(1023-0)
return snowflake.NewNode(node)
}
36 changes: 29 additions & 7 deletions session.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ import (

// session对象
type Session struct {
ID string
req *http.Request
resp http.ResponseWriter
StorageID string // 存储器中的ID
CookieID string // cookies中的ID
resp http.ResponseWriter
engine *Engine
}

// 键不存在时的错误类型
Expand All @@ -29,18 +30,39 @@ type Value struct {
func (obj *Session) Get(key string) *Value {
var result Value
result.Key = key
value, err := redisClient.HGet(obj.ID, key).Result()

value, err := redisClient.HGet(obj.StorageID, key).Result()
if err != nil {
result.Error = err
return &result
}
result.Value = value

// 自动更新空闲时间
if !obj.engine.config.DisableAutoUpdateIdleTime {
if err := obj.engine.UpdateIdleTime(obj.CookieID, obj.StorageID, obj.resp); err != nil {
result.Error = err
return &result
}
}

return &result
}

// 设置一个键值,如果键名存在则覆盖
func (obj *Session) Set(key string, value interface{}) error {
return redisClient.HSet(obj.ID, key, value).Err()
err := redisClient.HSet(obj.StorageID, key, value).Err()
if err != nil {
return err
}
// 自动更新空闲时间
if !obj.engine.config.DisableAutoUpdateIdleTime {
if err := obj.engine.UpdateIdleTime(obj.CookieID, obj.StorageID, obj.resp); err != nil {
return err
}
}

return nil
}

// String 将值转为string类型
Expand Down Expand Up @@ -205,10 +227,10 @@ func (v *Value) Bool(def ...bool) (bool, error) {

// Delete 删除一个键值,如果键名不存在则忽略,不会报错
func (this *Session) Delete(key string) error {
return redisClient.HDel(this.ID, key).Err()
return redisClient.HDel(this.StorageID, key).Err()
}

// ClearData 清除所有redis中的session数据,但不删除cookie中的sessionID
func (this *Session) ClearData() error {
return redisClient.Del(this.ID).Err()
return redisClient.Del(this.StorageID).Err()
}

0 comments on commit 1b3b3cd

Please sign in to comment.