Golang web 开发实战之 session 缓存:如何使用 redigo 将一个结构体数据保存到 redis?

自定义 session 结构体:
type Session struct {
	SessionID  string        `json:"sessionId" bson:"sessionId"`
	User       *User         `json:"-" bson:"user"`
	UserType   string        `json:"userType" bson:"userType"`
	NickName   string        `json:"nickName" bson:"nickName"`
	CreateTime time.Time     `json:"-" bson:"createTime"`
	UpdateTime time.Time     `json:"-" bson:"updateTime"`
	Expires    time.Time     `json:"-" bson:"expires"`
	Locale     string        `json:"-" bson:"locale"` // default is zh_CN
	Menus      []wmodel.Menu `json:"menus" bson:"menus"`
}

1. session 的保存

使用 json.Marshal 将结构体 json 化之后保存到 redis:

/*
	【增】
	描述:插入一个 session 对象
	session 顶级 key,顶级 key 可以设置过期时间
	<[session]: 要插入的 session 对象
	>[error]:插入失败相关信息
*/
func (s *sessionService) SetSession(session *model.Session) error {
	// 从池里获取连接
	conn := pool.Get()
	if conn == nil {
		log.Errorf("redis connnection is nil")
		return errors.New("redis connnection is nil")
	}
	// 用完后将连接放回连接池
	defer conn.Close()
	// 将session转换成json数据,注意:转换后的value是一个byte数组
	value, err := json.Marshal(session)
	if err != nil {
		log.Errorf("json marshal err,%s", err)
		return err
	}
	log.Infof("send data[%s]", session.SessionID, value)
	_, err = conn.Do("SET", session.SessionID, value, "EX", sessionTimeOutInSeconds)
	if err != nil {
		return err
	}
	return nil
}

Golang 测试验证:

func TestSetSession(t *testing.T) {
	s := &model.Session{
		SessionID: "20150421120000",
		UserType:  "admin",
		NickName:  "df",
	}
	err := SessionService.SetSession(s)
	if err != nil {
		t.Errorf("fail to add one session(%+v): %s", s, err)
		t.FailNow()
	}
}

redis 客户端查看该 session 保存情况:

redis保存情况.png


2. session 的删除

直接通过 redigo 驱动远程调用 DEL 命令:

/*
	【删】
	描述: 删除一个 session 对象
	session 顶级 key,一般情况下 session 会在用户无操作 30 分钟后自行过期删除
	但用户登出操作可以提前对 session 进行删除,这就是本方法被调用的地方
	<[sessionID]: 要删除的 session 对象的 id
	>[error]:删除失败相关信息
*/
func (s *sessionService) DelSession(sessionID string) (err error) {
	// 从池里获取连接
	conn := pool.Get()
	if conn == nil {
		log.Errorf("redis connnection is nil")
		return errors.New("redis connnection is nil")
	}
	// 用完后将连接放回连接池
	defer conn.Close()
	log.Infof("move data[%s]", sessionID)
	_, err = conn.Do("DEL", sessionID)
	if err != nil {
		return err
	}
	return nil
}
Golang 测试验证:

func TestDelSession(t *testing.T) {
	err := SessionService.DelSession("20150421120000")
	if err != nil {
		t.Errorf("fail to delete one session(%s): %s", "20150421120000", err)
		t.FailNow()
	}
}

3. session 的获取

使用 json.Unmarshal 将序列化之后的结构体还原:

/*
	【查】
	描述: 查看并返回一个 session 实体
	session 顶级 key
	<[sessionID]: 要查看的 session 对象的 id
	>[error]:查看失败相关信息
*/
func (s *sessionService) GetSession(sessionID string) (session *model.Session, err error) {
	// 从池里获取连接
	conn := pool.Get()
	if conn == nil {
		log.Errorf("redis connnection is nil")
		return nil, errors.New("redis connnection is nil")
	}
	// 用完后将连接放回连接池
	defer conn.Close()
	log.Infof("exists data[%s]", sessionID)
	// 先查看该session是否存在
	var ifExists bool
	ifExists, err = SessionService.ExistsSession(sessionID)
	if err != nil {
		log.Errorf("fail to exists one session(%s): %s", sessionID, err)
		return nil, errors.New("session not exists, sessionID: " + sessionID)
	}
	if ifExists {
		// json数据在go中是[]byte类型,所以此处用redis.Bytes转换
		valueBytes, err2 := redis.Bytes(conn.Do("GET", sessionID))
		if err2 != nil {
			return nil, err2
		}
		//log.Infof("receive data[%s]:%s", sessionID, string(valueBytes))
		session = &model.Session{}
		err = json.Unmarshal(valueBytes, session)
		if err != nil {
			return nil, err
		}
		return session, nil
	} else {
		return nil, errors.New("session not exists, sessionID: " + sessionID)
	}

}
Golang 测试验证:

func TestGetSession(t *testing.T) {
	s, err := SessionService.GetSession("20150421120000")
	if err != nil {
		t.Errorf("fail to exists one session(%s): %s", "20150421120000", err)
		t.FailNow()
	}
	log.Debug("session exists, session nickname is: %s", s.NickName)
}


后记

redis 的作者为了保持简单的架构只允许我们对 top level 的 key 设置超时时间,次级 key(比如 Hash 里边的每个子 key)是不能设置超时时间的,所以我们单独使用了一个 index 为 3 的 redis 库专门存放 session,就是方便管理。另外,我们将每个 session 的 key(即 top level 的 key)有效期设置为半小时,有效期断定及处理托管 redis,避开了程序里对 session 超时机制管理的复杂性——特别是分布式环境。

另外,本文只提供了 session 的增、删、查操作,对于 session 的修改以及有效期推延操作笔者建议可以先删除再增加。

参考资料

GO: How to save and retrieve a struct to redis using redigo
posted @ 2016-07-21 17:11  Defonds  阅读(115)  评论(0编辑  收藏  举报