• 用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了,为什么我这里不往下写了?阳了好累当然是因为还在开发中~~

  • 相关阅读:
    CAN总线学习笔记 | CAN基础知识介绍
    MyBatis一对多、多对一映射
    笔试题知识点
    递增递减运算符 ++ -- 前置后置的区别
    RocketMQ消息消费(Consumer)源码解析
    Vivado_AXI Quad SPI_IP核
    飞书事件订阅的应用
    Cholesterol-PEG-Amine CLS-PEG-NH2 胆固醇-聚乙二醇-氨基科研用
    【基于HTML5的网页设计及应用】——float实现页面布局
    Nginx-keepalived双机热备集群
  • 原文地址:https://www.cnblogs.com/weloe/p/17418722.html