用go設計開發一個自己的輕量級登錄庫/框架吧(拓展篇)

来源:https://www.cnblogs.com/weloe/archive/2023/05/21/17418722.html
-Advertisement-
Play Games

用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了,為什麼我這裡不往下寫了?陽了好累當然是因為還在開發中~~


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 網上的教學視頻大部分全是以centos為教材底子——沒辦法更換系統了,這樣方便麻! 我參考的文章: https://blog.csdn.net/shengjie87/article/details/106805105#:~:text=%E7%94%A8%E6%8C%82%E8%BD%BD%E5%85 ...
  • Wayland 配置起來確實相對麻煩很多,需要註意很多細節,如果不註意就會出現問題,在這裡說一下可能的現象與解決方法。 根據觀察,這些現象在 GNOME 與 KDE 桌面環境鐘均會出現。 ## 現象 ### App 打開慢 現象為當首次打開一個圖形化的 App 時,需要等待2-3秒鐘才會打開,但是如 ...
  • 上一講我們安裝 etcd 服務端,這一講我們來一起學學如何使用 etcd 客戶端常見的命令。文章內容來源於參考資料,如若侵權,請聯繫刪除,謝謝。 > etcd可通過客戶端命令行工具 etcdctl 對etcd進行請求操作 ```sh # 幫助命令,會列出所有的命令和選項,在記不太清命令的時候,可以使 ...
  • > 本文首發於公眾號:Hunter後端 > 原文鏈接:[es筆記四之中文分詞插件安裝與使用](https://mp.weixin.qq.com/s/aQuwrUzLZDKLv_K8dKeVzw) 前面我們介紹的操作及演示都是基於英語單詞的分詞,但我們大部分使用的肯定都是中文,所以如果需要使用分詞的操 ...
  • 在上一篇博客中,我們介紹了用Python對來實現一個Scheme求值器。然而,我們跳過了部分特殊形式(special forms)和基本過程(primitive procedures)實現的介紹,如特殊形式中的delay、cons-stream,基本過程中的force、streawn-car、str... ...
  • ## 1.自定義starter的作用 在我們的日常開發工作中,經常會有一些獨立於業務之外的配置模塊,比如阿裡雲oss存儲的時候,我們需要一個工具類進行文件上傳。我們經常將其放到一個特定的包下,然後如果另一個工程需要復用這塊功能的時候,需要將代碼硬拷貝到另一個工程,重新集成一遍,這樣會非常麻煩。如果我 ...
  • # 使用 Async Rust 構建簡單的 P2P 節點 ### P2P 簡介 - P2P:peer-to-peer - P2P 是一種網路技術,可以在不同的電腦之間共用各種計算資源,如 CPU、網路帶寬和存儲。 - P2P 是當今用戶線上共用文件(如音樂、圖像和其他數字媒體)的一種非常常用的方法 ...
  • ## 1.1 為什麼要學 Qt Qt是一個跨平臺的 C++ 圖形用戶界面應用程式框架 Qt 為應用程式開發者提供建立藝術級圖形界面所需的所有功能 Qt 是完全面向對象的,很容易擴展,並且允許真正的組件編程 (1)Qt 發展史 在講解學習 Qt 的必要性之前, 先來瞭解下 Qt 的發展歷史: 1991 ...
一周排行
    -Advertisement-
    Play Games
  • 前言 在我們開發過程中基本上不可或缺的用到一些敏感機密數據,比如SQL伺服器的連接串或者是OAuth2的Secret等,這些敏感數據在代碼中是不太安全的,我們不應該在源代碼中存儲密碼和其他的敏感數據,一種推薦的方式是通過Asp.Net Core的機密管理器。 機密管理器 在 ASP.NET Core ...
  • 新改進提供的Taurus Rpc 功能,可以簡化微服務間的調用,同時可以不用再手動輸出模塊名稱,或調用路徑,包括負載均衡,這一切,由框架實現並提供了。新的Taurus Rpc 功能,將使得服務間的調用,更加輕鬆、簡約、高效。 ...
  • 順序棧的介面程式 目錄順序棧的介面程式頭文件創建順序棧入棧出棧利用棧將10進位轉16進位數驗證 頭文件 #include <stdio.h> #include <stdbool.h> #include <stdlib.h> 創建順序棧 // 指的是順序棧中的元素的數據類型,用戶可以根據需要進行修改 ...
  • 前言 整理這個官方翻譯的系列,原因是網上大部分的 tomcat 版本比較舊,此版本為 v11 最新的版本。 開源項目 從零手寫實現 tomcat minicat 別稱【嗅虎】心有猛虎,輕嗅薔薇。 系列文章 web server apache tomcat11-01-官方文檔入門介紹 web serv ...
  • C總結與剖析:關鍵字篇 -- <<C語言深度解剖>> 目錄C總結與剖析:關鍵字篇 -- <<C語言深度解剖>>程式的本質:二進位文件變數1.變數:記憶體上的某個位置開闢的空間2.變數的初始化3.為什麼要有變數4.局部變數與全局變數5.變數的大小由類型決定6.任何一個變數,記憶體賦值都是從低地址開始往高地 ...
  • 如果讓你來做一個有狀態流式應用的故障恢復,你會如何來做呢? 單機和多機會遇到什麼不同的問題? Flink Checkpoint 是做什麼用的?原理是什麼? ...
  • C++ 多級繼承 多級繼承是一種面向對象編程(OOP)特性,允許一個類從多個基類繼承屬性和方法。它使代碼更易於組織和維護,並促進代碼重用。 多級繼承的語法 在 C++ 中,使用 : 符號來指定繼承關係。多級繼承的語法如下: class DerivedClass : public BaseClass1 ...
  • 前言 什麼是SpringCloud? Spring Cloud 是一系列框架的有序集合,它利用 Spring Boot 的開發便利性簡化了分散式系統的開發,比如服務註冊、服務發現、網關、路由、鏈路追蹤等。Spring Cloud 並不是重覆造輪子,而是將市面上開發得比較好的模塊集成進去,進行封裝,從 ...
  • class_template 類模板和函數模板的定義和使用類似,我們已經進行了介紹。有時,有兩個或多個類,其功能是相同的,僅僅是數據類型不同。類模板用於實現類所需數據的類型參數化 template<class NameType, class AgeType> class Person { publi ...
  • 目錄system v IPC簡介共用記憶體需要用到的函數介面shmget函數--獲取對象IDshmat函數--獲得映射空間shmctl函數--釋放資源共用記憶體實現思路註意 system v IPC簡介 消息隊列、共用記憶體和信號量統稱為system v IPC(進程間通信機制),V是羅馬數字5,是UNI ...