43.session存储与操作具体实现(基于内存实现)(三)

本文详细介绍了如何在Golang中自定义内存存储的Session管理器,包括Provider与SessionStore的实现,以及如何使用这个自定义session。同时,文章还提供了使用gorilla/sessions包的示例,供读者对比学习。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

代码地址:https://gitee.com/lymgoforIT/golang-trick/tree/master/25-session

一、Provider与Session的具体实现

上一节我们介绍了Session管理器的实现原理,定义了存储session的接口,这小节我们将示例一个基于内存的session存储接口的实现,其他的存储方式,读者可以自行参考示例来实现,内存的实现请看下面的例子代码,逻辑并不复杂,直接看代码中注释即可

Provider与Session的具体实现均在如下代码中

package memory

import (
	"container/list"
	"golang-trick/25-session/session"

	"sync"
	"time"
)

var pder = &Provider{list: list.New()}

func init() {
	pder.sessions = make(map[string]*list.Element, 0)
	session.Register("memory", pder)
}

// 实现了session包中的Session接口,具备增删改查等操作
type SessionStore struct {
	sid          string                      //session id唯一标识
	timeAccessed time.Time                   //最后访问时间,用于GC时判断是否距离当前时间超过了manager.maxlifetime
	value        map[interface{}]interface{} //session里面存储的数据
}

func (st *SessionStore) Set(key, value interface{}) error {
	st.value[key] = value
	// 更新当前sid对应的session的最后访问时间,并移动到链表头部,GC时,从尾部开始判断每个session是否过期
	pder.SessionUpdate(st.sid)
	return nil
}

func (st *SessionStore) Get(key interface{}) interface{} {
	pder.SessionUpdate(st.sid)
	if v, ok := st.value[key]; ok {
		return v
	} else {
		return nil
	}
	return nil
}

func (st *SessionStore) Delete(key interface{}) error {
	delete(st.value, key)
	pder.SessionUpdate(st.sid)
	return nil
}

func (st *SessionStore) SessionID() string {
	return st.sid
}

// 实现了session包中的Provider接口
type Provider struct {
	lock     sync.Mutex               //用来锁
	sessions map[string]*list.Element //用来存储在内存,如果是存储在redis,则这里可能是redisClient
	list     *list.List               //用来做gc,如果是存储在redis,则这个可以不要,而是通过redis自带的过期时间使得key过期即可
}

func (pder *Provider) SessionInit(sid string) (session.Session, error) {
	pder.lock.Lock()
	defer pder.lock.Unlock()
	v := make(map[interface{}]interface{}, 0)
	// 实际使用时,v中应该放入相关数据,如用户名,用户id等,这些则应该由SessionInit方法的参数传入,而不能只有一个sid作为参数
	newsess := &SessionStore{sid: sid, timeAccessed: time.Now(), value: v}
	element := pder.list.PushBack(newsess)
	pder.sessions[sid] = element
	return newsess, nil
}

func (pder *Provider) SessionRead(sid string) (session.Session, error) {
	if element, ok := pder.sessions[sid]; ok {
		return element.Value.(*SessionStore), nil
	} else {
		sess, err := pder.SessionInit(sid)
		return sess, err
	}
	return nil, nil
}

// 重置服务端sid的session,就是从provider中删除相应数据
// 重置客户端sid的session,就是响应请求时,设置cookie头的内容为空
func (pder *Provider) SessionDestroy(sid string) error {
	if element, ok := pder.sessions[sid]; ok {
		delete(pder.sessions, sid)
		pder.list.Remove(element)
		return nil
	}
	return nil
}

// 从尾部开始判断每个session是否过期
func (pder *Provider) SessionGC(maxlifetime int64) {
	pder.lock.Lock()
	defer pder.lock.Unlock()

	for {
		element := pder.list.Back()
		if element == nil {
			break
		}
		if (element.Value.(*SessionStore).timeAccessed.Unix() + maxlifetime) < time.Now().Unix() {
			pder.list.Remove(element)
			delete(pder.sessions, element.Value.(*SessionStore).sid)
		} else {
			break
		}
	}
}

// 更新当前sid对应的session的最后访问时间,并移动到链表头部,GC时,从尾部开始判断每个session是否过期
func (pder *Provider) SessionUpdate(sid string) error {
	pder.lock.Lock()
	defer pder.lock.Unlock()
	if element, ok := pder.sessions[sid]; ok {
		element.Value.(*SessionStore).timeAccessed = time.Now()
		pder.list.MoveToFront(element)
		return nil
	}
	return nil
}

二、如何使用我们自己写的session

上面这个代码实现了一个内存存储的session机制。通过init函数注册到session管理器中。这样就可以方便的调用了。我们如何来调用该引擎呢?请看下面的代码

import (
    _ "golang-trick/25-session/memory"  // memory 包的引入方式用下划线,只需执行 memory的init方法即可。
	"golang-trick/25-session/session"
)

import的时候已经执行了memory函数里面的init函数,这样就已经注册到session管理器中,我们就可以使用了,通过如下方式就可以初始化一个session管理器,,后续直接使用globalSessions 即可,我们将原来放在manager.go文件中的globalSessions 相关代码放到main.go中:

package main

import (
	"fmt"
	_ "golang-trick/25-session/memory"  // memory 包的引入方式用下划线,只需执行 memory的init方法即可。
	"golang-trick/25-session/session"
	"log"
	"net/http"
)

var globalSessions *session.Manager

func init() {
	var err error
	globalSessions, err = session.NewManager("memory", "goSessionid", 3600)
	if err != nil {
		fmt.Println(err)
		return
	}
	go globalSessions.GC()
	fmt.Println("fd")
}

func sayHelloHandler(w http.ResponseWriter, r *http.Request) {

	cookie, err := r.Cookie("goSessionid")
	if err == nil {
		fmt.Println(cookie.Value)
	}
}

func login(w http.ResponseWriter, r *http.Request) {
	sess := globalSessions.SessionStart(w, r)
	val := sess.Get("username")
	if val != nil {
		fmt.Println(val)
	} else {
		sess.Set("username", "jerry")
		fmt.Println("set session")
	}
}

func loginOut(w http.ResponseWriter, r *http.Request) {
	//销毁
	globalSessions.SessionDestroy(w, r)
	fmt.Println("session destroy")
}

func main() {
	http.HandleFunc("/", sayHelloHandler) //	设置访问路由
	http.HandleFunc("/login", login)
	http.HandleFunc("/logout", loginOut) //销毁
	log.Fatal(http.ListenAndServe(":8080", nil))
}

注:memory 包的引入方式用下划线,只需执行 memory的init方法即可。

三、启动服务测试

在浏览器中输入http://localhost:8080/login,可以看到控制台输出

在这里插入图片描述
也可以从浏览器中看到cookie详情
在这里插入图片描述

访问http://localhost:8080/ ,控制台输出
在这里插入图片描述

在浏览器中输入http://localhost:8080/logout,可以看到控制台输出

在这里插入图片描述

浏览器再次查看cookie,可以看到namegoSessionidcookie被删除了

在这里插入图片描述

附:直接使用已有的github.com/gorilla/sessions

package main

import (
	"fmt"
	"net/http"

	"github.com/gorilla/sessions"
)

var store = sessions.NewCookieStore([]byte("secret-key"))

func main() {
	http.HandleFunc("/", homeHandler)
	http.HandleFunc("/login", loginHandler)
	http.HandleFunc("/logout", logoutHandler)

	fmt.Println("Server started on http://localhost:8080")
	http.ListenAndServe(":8080", nil)
}

func homeHandler(w http.ResponseWriter, r *http.Request) {
	session, _ := store.Get(r, "session-name")

	// Check if the user is authenticated
	if auth, ok := session.Values["authenticated"].(bool); !ok || !auth {
		http.Redirect(w, r, "/login", http.StatusSeeOther)
		return
	}

	// Display the user's name
	name := session.Values["name"].(string)
	fmt.Fprintf(w, "Welcome, %s!", name)
}

func loginHandler(w http.ResponseWriter, r *http.Request) {
	session, _ := store.Get(r, "session-name")

	// Set user authentication status and name in the session
	session.Values["authenticated"] = true
	session.Values["name"] = "John Doe"

	// Save the session
	session.Save(r, w)

	http.Redirect(w, r, "/", http.StatusSeeOther)
}

func logoutHandler(w http.ResponseWriter, r *http.Request) {
	session, _ := store.Get(r, "session-name")

	// Revoke user authentication status and clear session data
	session.Values["authenticated"] = false
	session.Values["name"] = ""

	// Save the session
	session.Save(r, w)

	http.Redirect(w, r, "/login", http.StatusSeeOther)
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值