使用gin封装一个web脚手架(七):实现session(下)

741 阅读3分钟

接下来就是实现session的几个函数了,分别是set、get、remove

先实现set,在context.go添加

package context

//context/context.go

import (
	"context"
	"encoding/json"
	"errors"
	"github.com/gin-gonic/gin"
	"myGin/redis"
	"strings"
	"time"
)

type Context struct {
	*gin.Context
}

type HandlerFunc func(*Context)

func (c *Context) Domain() string {

	return c.Request.Host[:strings.Index(c.Request.Host, ":")]
}

// Session 返回一个session实例
func (c *Context) Session() *Session {

	var session Session

	cookie, ok := c.Get("_session")

	if !ok {

		return nil
	}

	session = cookie.(Session)

	return &session

}

type Session struct {
	Cookie      string                 `json:"cookie"`
	ExpireTime  int64                  `json:"expire_time"`
	SessionList map[string]interface{} `json:"session_list"`
}

func (s *Session) Set(key string, value interface{}) error {

	sessionString, err := redis.Client().Get(context.TODO(), s.Cookie).Result()

	if err != nil {

		return err
	}

	var session Session

	err = json.Unmarshal([]byte(sessionString), &session)

	if err != nil {

		return err
	}

	//设置
	session.SessionList[key] = value

	sessionStringNew, err := json.Marshal(session)

	//计算新的过期时间
	e := s.ExpireTime - time.Now().Unix()

	if e < 0 {

		return errors.New("the session has expired")
	}

	redis.Client().Set(context.TODO(), s.Cookie, sessionStringNew, time.Duration(e)*time.Second)

	return nil
}

在index控制器中调用

package controller

//controller/Index.go

import (
	"myGin/context"
	"myGin/response"
)

func Index(context *context.Context) *response.Response {

	context.Session().Set("msg", "php是世界上最好的语言!")

	return response.Resp().String(context.Domain())
}

在redis中查看

image.png

设置成功

接下来是get和remove

func (s *Session) Get(key string) (interface{}, error) {

	sessionString, err := redis.Client().Get(context.TODO(), s.Cookie).Result()

	if err != nil {

		return nil, err
	}

	var session Session

	err = json.Unmarshal([]byte(sessionString), &session)

	if err != nil {

		return nil, err
	}

	value, ok := session.SessionList[key]

	if ok {

		return value, nil
	}

	return nil, errors.New("not found key :" + key)

}

func (s *Session) Remove(key string) error {

	sessionString, err := redis.Client().Get(context.TODO(), s.Cookie).Result()

	if err != nil {

		return err
	}

	var session Session

	err = json.Unmarshal([]byte(sessionString), &session)

	if err != nil {

		return err
	}

	delete(session.SessionList, key)

	sessionStringNew, err := json.Marshal(session)

	if err != nil {

		return err
	}

	//计算新的过期时间
	e := s.ExpireTime - time.Now().Unix()

	if e < 0 {

		return errors.New("the session has expired")
	}

	redis.Client().Set(context.TODO(), s.Cookie, sessionStringNew, time.Duration(e)*time.Second)

	return nil

}

调用

package controller

//controller/Index.go

import (
	"myGin/context"
	"myGin/response"
)

func Index(context *context.Context) *response.Response {

	context.Session().Set("msg", "php是世界上最好的语言!")

	return response.Resp().String(context.Domain())
}

func Index2(context *context.Context) *response.Response {

	msg, _ := context.Session().Get("msg")

	return response.Resp().String(msg.(string))
}

func Index3(context *context.Context) *response.Response {

	context.Session().Remove("msg")

	return response.Resp().String("")
}

到这里,session的功能基本都实现了,下面看一个有意思的问题。

func Index4(context *context.Context) *response.Response {

	session := context.Session()

	for i := 0; i < 100; i++ {

		go func(index int) {

			session.Set("msg"+strconv.Itoa(index), index)

		}(i)
	}

	return response.Resp().String("")
}

访问这个请求,redis中的结果是这样的

image.png

上面的循环执行了100个协程,按理说redis中的数据是应该有100个的,这里却只有2个。

原因在于每个session设置都是并行的,读取的结果并不是最终结果,所以session的设置还需要加一个锁。

完整代码如下

package context

//context/context.go

import (
	"context"
	"encoding/json"
	"errors"
	"github.com/gin-gonic/gin"
	"myGin/redis"
	"strings"
	"sync"
	"time"
)

type Context struct {
	*gin.Context
}

type HandlerFunc func(*Context)

func (c *Context) Domain() string {

	return c.Request.Host[:strings.Index(c.Request.Host, ":")]
}

// Session 返回一个session实例
func (c *Context) Session() *Session {

	var session Session

	cookie, ok := c.Get("_session")

	if !ok {

		return nil
	}

	session = cookie.(Session)

	session.Lock = &sync.Mutex{}

	return &session

}

type Session struct {
	Cookie      string                 `json:"cookie"`
	ExpireTime  int64                  `json:"expire_time"`
	SessionList map[string]interface{} `json:"session_list"`
	Lock        *sync.Mutex
}

func (s *Session) Set(key string, value interface{}) error {

	//加锁,防止读取并行
	s.Lock.Lock()

	defer s.Lock.Unlock()

	sessionString, err := redis.Client().Get(context.TODO(), s.Cookie).Result()

	if err != nil {

		return err
	}

	var session Session

	err = json.Unmarshal([]byte(sessionString), &session)

	if err != nil {

		return err
	}

	//设置
	session.SessionList[key] = value

	sessionStringNew, err := json.Marshal(session)

	//计算新的过期时间
	e := s.ExpireTime - time.Now().Unix()

	if e < 0 {

		return errors.New("the session has expired")
	}

	redis.Client().Set(context.TODO(), s.Cookie, sessionStringNew, time.Duration(e)*time.Second)

	return nil
}

func (s *Session) Get(key string) (interface{}, error) {

	sessionString, err := redis.Client().Get(context.TODO(), s.Cookie).Result()

	if err != nil {

		return nil, err
	}

	var session Session

	err = json.Unmarshal([]byte(sessionString), &session)

	if err != nil {

		return nil, err
	}

	value, ok := session.SessionList[key]

	if ok {

		return value, nil
	}

	return nil, errors.New("not found key :" + key)

}

func (s *Session) Remove(key string) error {

	s.Lock.Lock()

	defer s.Lock.Unlock()

	sessionString, err := redis.Client().Get(context.TODO(), s.Cookie).Result()

	if err != nil {

		return err
	}

	var session Session

	err = json.Unmarshal([]byte(sessionString), &session)

	if err != nil {

		return err
	}

	delete(session.SessionList, key)

	sessionStringNew, err := json.Marshal(session)

	if err != nil {

		return err
	}

	//计算新的过期时间
	e := s.ExpireTime - time.Now().Unix()

	if e < 0 {

		return errors.New("the session has expired")
	}

	redis.Client().Set(context.TODO(), s.Cookie, sessionStringNew, time.Duration(e)*time.Second)

	return nil

}

源代码:github.com/PeterYangs/…