用go設計開發一個自己的輕量級登錄庫/框架吧(拓展篇),給自己的庫/框架拓展一下吧,主庫:https://github.com/weloe/token-go ...
給自己的庫/框架拓展一下吧(拓展篇)
主庫:weloe/token-go: a light login library.
擴展庫:weloe/token-go-extensions (github.com)
本篇給主庫擴展一個Adapter提供簡單的外部數據存儲。
思路
一個庫/框架往往不能完成所有事情,需要其他庫/框架的支持才能達到更加完善的效果。本篇會對token-go框架的Adapter進行簡單的拓展。
首先我們應該想想Adapter是用來乾什麼的?
從第一篇我們就明確其職責,就是存儲數據。我們在token-go里提供了一個內置的adapter:default_adapter,用於框架底層的數據存儲,但是這種記憶體的數據存儲有著很多的缺陷,並且沒有經過實際的生產測試使用。也因此,我們應該提供更成熟的存儲方案來提供給使用者去替代它。
這就是本篇要實現的redis_adapter了
實現
這裡還有一個點要註意,將數據存儲到外部需要確定數據的序列化和反序列化方法。因此,我們加了一個SerializerAdapter介面,要求新的Adapter選擇實現。
token-go/serializer_adapter.go at master · weloe/token-go · GitHub
package persist
import "github.com/weloe/token-go/model"
type SerializerAdapter interface {
Adapter
Serialize(*model.Session) ([]byte, error)
UnSerialize([]byte) (*model.Session, error)
}
具體的調用則是在enforcer對session進行存儲或者取出數據的時候進行調用。
func (e *Enforcer) GetSession(id string) *model.Session {
if v := e.adapter.Get(e.spliceSessionKey(id)); v != nil {
if s := e.sessionUnSerialize(v); s != nil {
return s
} else {
session, ok := v.(*model.Session)
if !ok {
return nil
}
return session
}
}
return nil
}
這裡的sessionUnSerialize()
實際上就是嘗試調用了adapter實現的反序列化方法。同理SetSession()
也是一樣的。
最後就是RedisAdapter了
token-go-extensions/adapter.go at master · weloe/token-go-extensions · GitHub
並不難,只要實現我們之前的Adapter和SerializerAdapter兩個介面就行了。
序列化方法使用json,方便查看
package redis_adapter
import (
"context"
"encoding/json"
"github.com/go-redis/redis/v8"
"github.com/weloe/token-go/model"
"github.com/weloe/token-go/persist"
"time"
)
var _ persist.Adapter = (*RedisAdapter)(nil)
var _ persist.SerializerAdapter = (*RedisAdapter)(nil)
type RedisAdapter struct {
client *redis.Client
}
func (r *RedisAdapter) Serialize(session *model.Session) ([]byte, error) {
return json.Marshal(session)
}
func (r *RedisAdapter) UnSerialize(bytes []byte) (*model.Session, error) {
s := &model.Session{}
err := json.Unmarshal(bytes, s)
if err != nil {
return nil, err
}
return s, nil
}
func (r *RedisAdapter) GetStr(key string) string {
res, err := r.client.Get(context.Background(), key).Result()
if err != nil {
return ""
}
return res
}
func (r *RedisAdapter) SetStr(key string, value string, timeout int64) error {
err := r.client.Set(context.Background(), key, value, time.Duration(timeout)*time.Second).Err()
if err != nil {
return err
}
return nil
}
func (r *RedisAdapter) UpdateStr(key string, value string) error {
err := r.client.Set(context.Background(), key, value, 0).Err()
if err != nil {
return err
}
return nil
}
func (r *RedisAdapter) DeleteStr(key string) error {
err := r.client.Del(context.Background(), key).Err()
if err != nil {
return err
}
return nil
}
func (r *RedisAdapter) GetStrTimeout(key string) int64 {
duration, err := r.client.TTL(context.Background(), key).Result()
if err != nil {
return -1
}
return int64(duration.Seconds())
}
func (r *RedisAdapter) UpdateStrTimeout(key string, timeout int64) error {
var duration time.Duration
if timeout < 0 {
duration = -1
} else {
duration = time.Duration(timeout) * time.Second
}
err := r.client.Expire(context.Background(), key, duration).Err()
if err != nil {
return err
}
return nil
}
func (r *RedisAdapter) Get(key string) interface{} {
res, err := r.client.Get(context.Background(), key).Result()
if err != nil {
return nil
}
s := &model.Session{}
err = json.Unmarshal([]byte(res), s)
if err != nil {
return nil
}
return s
}
func (r *RedisAdapter) Set(key string, value interface{}, timeout int64) error {
err := r.client.Set(context.Background(), key, value, time.Duration(timeout)*time.Second).Err()
if err != nil {
return err
}
return nil
}
func (r *RedisAdapter) Update(key string, value interface{}) error {
err := r.client.Set(context.Background(), key, value, 0).Err()
if err != nil {
return err
}
return nil
}
func (r *RedisAdapter) Delete(key string) error {
err := r.client.Del(context.Background(), key).Err()
if err != nil {
return err
}
return nil
}
func (r *RedisAdapter) GetTimeout(key string) int64 {
duration, err := r.client.TTL(context.Background(), key).Result()
if err != nil {
return -1
}
return int64(duration.Seconds())
}
func (r *RedisAdapter) UpdateTimeout(key string, timeout int64) error {
var duration time.Duration
if timeout < 0 {
duration = -1
} else {
duration = time.Duration(timeout) * time.Second
}
err := r.client.Expire(context.Background(), key, duration).Err()
if err != nil {
return err
}
return nil
}
func (r *RedisAdapter) DeleteBatchFilteredKey(filterKeyPrefix string) error {
var cursor uint64 = 0
for {
keys, cursor, err := r.client.Scan(context.Background(), cursor, filterKeyPrefix+"*", 100).Result()
if err != nil {
return err
}
if len(keys) == 0 && cursor == 0 {
break
}
// use pip delete batch
pipe := r.client.Pipeline()
for _, key := range keys {
pipe.Del(context.Background(), key)
}
_, err = pipe.Exec(context.Background())
if err != nil {
return err
}
}
return nil
}
實現完介面,再寫幾個初始化方法
func NewAdapter(addr string, username string, password string, db int) (*RedisAdapter, error) {
return NewAdapterByOptions(&redis.Options{
Addr: addr,
Username: username,
Password: password,
DB: db,
})
}
func NewAdapterByOptions(options *redis.Options) (*RedisAdapter, error) {
client := redis.NewClient(options)
_, err := client.Ping(context.Background()).Result()
if err != nil {
return nil, err
}
return &RedisAdapter{client: client}, nil
}
測試
就不貼測試代碼了,就放個鏈接~
token-go-extensions/adapter_test.go at master · weloe/token-go-extensions · GitHub
最後
這樣RedisAdapter就開發完了嗎?不不不,並沒有。
用戶量的增大,對容錯,一致性等等的要求提高,可能需要用到多個redis,這就需要我們繼續適配開發一個ClusterAdapter了,為什麼我這裡不往下寫了?陽了好累當然是因為還在開發中~~