初始化缓存管理器
This commit is contained in:
@@ -0,0 +1,444 @@
|
||||
package manager
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
"wallet-pay-api/common/global"
|
||||
"wallet-pay-api/common/model"
|
||||
"wallet-pay-api/pkg/loggerx"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
func newTestRedisCache() *CacheRedis {
|
||||
// 支持通过环境变量覆盖 redis 地址
|
||||
addr := os.Getenv("TEST_REDIS_ADDR")
|
||||
if addr == "" {
|
||||
addr = "10.40.92.54:30379"
|
||||
}
|
||||
client := redis.NewClient(&redis.Options{
|
||||
Addr: addr,
|
||||
Password: "123000",
|
||||
DB: 0,
|
||||
})
|
||||
return NewCacheRedis(context.Background(), client, "test")
|
||||
}
|
||||
func TestBatchGetRedis(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
cache := newTestRedisCache()
|
||||
keys := []string{"k1", "k2", "k3"}
|
||||
resp, err := BatchGetRedis[model.YuebaoUserAsset](cache, ctx, keys)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Logf("%+v", resp)
|
||||
|
||||
}
|
||||
|
||||
func TestCacheRedis_Types(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
cache := newTestRedisCache()
|
||||
// int
|
||||
err := cache.Set(ctx, "int", 123, time.Second*10, []int64{123})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var i int
|
||||
err = cache.Get(ctx, "int", &i)
|
||||
if err != nil || i != 123 {
|
||||
t.Errorf("int get failed: %v, %d", err, i)
|
||||
}
|
||||
// string
|
||||
err = cache.Set(ctx, "str", "abc", time.Second*10, []int64{124})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var s string
|
||||
err = cache.Get(ctx, "str", &s)
|
||||
if err != nil || s != "abc" {
|
||||
t.Errorf("string get failed: %v, %s", err, s)
|
||||
}
|
||||
// struct
|
||||
st := TestStruct{Name: "foo"}
|
||||
err = cache.Set(ctx, "struct", st, time.Second*10, []int64{125})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var st2 TestStruct
|
||||
err = cache.Get(ctx, "struct", &st2)
|
||||
if err != nil || st2.Name != "foo" {
|
||||
t.Errorf("struct get failed: %v, %+v", err, st2)
|
||||
}
|
||||
// map
|
||||
m := map[string]int{"a": 1}
|
||||
err = cache.Set(ctx, "map", m, time.Second*10, []int64{126})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var m2 map[string]int
|
||||
err = cache.Get(ctx, "map", &m2)
|
||||
if err != nil || m2["a"] != 1 {
|
||||
t.Errorf("map get failed: %v, %+v", err, m2)
|
||||
}
|
||||
// slice
|
||||
sl := []string{"x", "y"}
|
||||
err = cache.Set(ctx, "slice", sl, time.Second*10, []int64{127})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var sl2 []string
|
||||
err = cache.Get(ctx, "slice", &sl2)
|
||||
if err != nil || len(sl2) != 2 || sl2[0] != "x" {
|
||||
t.Errorf("slice get failed: %v, %+v", err, sl2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCacheRedis_Expire(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
cache := newTestRedisCache()
|
||||
err := cache.Set(ctx, "expire", "v", time.Millisecond*200, []int64{128})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var s string
|
||||
err = cache.Get(ctx, "expire", &s)
|
||||
if err != nil || s != "v" {
|
||||
t.Errorf("expire get failed: %v, %s", err, s)
|
||||
}
|
||||
time.Sleep(time.Millisecond * 250)
|
||||
err = cache.Get(ctx, "expire", &s)
|
||||
if err == nil {
|
||||
t.Errorf("should expire, got: %v", s)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCacheRedis_Del(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
cache := newTestRedisCache()
|
||||
err := cache.Set(ctx, "del", 1, time.Second*10, []int64{129})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = cache.Del(ctx, "del")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var i int
|
||||
err = cache.Get(ctx, "del", &i)
|
||||
if err == nil {
|
||||
t.Errorf("should not get deleted key")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCacheRedis_TypeMismatch(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
cache := newTestRedisCache()
|
||||
err := cache.Set(ctx, "mismatch", 123, time.Second*10, []int64{123})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var s string
|
||||
err = cache.Get(ctx, "mismatch", &s)
|
||||
if err == nil {
|
||||
t.Errorf("should type mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCacheRedis_BatchGet(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
cache := newTestRedisCache()
|
||||
cache.Set(ctx, "ba", 1, time.Second*10, []int64{1})
|
||||
cache.Set(ctx, "bb", 2, time.Second*10, []int64{2})
|
||||
cache.Set(ctx, "bc", 3, time.Second*10, []int64{3})
|
||||
keys := []string{"bj", "ba", "bb", "bx"}
|
||||
m, err := BatchGetRedis[int](cache, ctx, keys)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
b, _ := json.Marshal(m)
|
||||
fmt.Println(string(b))
|
||||
if len(m) != 2 || *m["ba"] != 1 || *m["bb"] != 2 {
|
||||
t.Errorf("batch get failed: %+v", m)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCacheManager_Redis_Generic(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
cache := newTestRedisCache()
|
||||
m := NewCacheManager[TestStruct](cache, "pref", func(t *TestStruct) int64 { return 0 })
|
||||
data := TestStruct{Name: "bar"}
|
||||
err := m.Set(ctx, "k1", &data, time.Second*10, []int64{0})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got, err := m.Get(ctx, "k1")
|
||||
if err != nil || got.Name != "bar" {
|
||||
t.Errorf("manager redis get failed: %v, %+v", err, got)
|
||||
}
|
||||
err = m.Del(ctx, "k1")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got, err = m.Get(ctx, "k1")
|
||||
if err == nil {
|
||||
t.Errorf("manager redis del failed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestICacheInterface(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
var cache ICache = NewCacheLocal(time.Second * 10)
|
||||
err := cache.Set(ctx, "iface", "val", time.Second*10, []int64{130})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var s string
|
||||
err = cache.Get(ctx, "iface", &s)
|
||||
if err != nil || s != "val" {
|
||||
t.Errorf("iface get failed: %v, %s", err, s)
|
||||
}
|
||||
err = cache.Del(ctx, "iface")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = cache.Get(ctx, "iface", &s)
|
||||
if err == nil {
|
||||
t.Errorf("iface del failed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCacheManager_Generic(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
cache := NewCacheLocal(time.Second * 10)
|
||||
m := NewCacheManager[TestStruct](cache, "pref", func(t *TestStruct) int64 { return 0 })
|
||||
data := TestStruct{Name: "bar"}
|
||||
err := m.Set(ctx, "k1", &data, time.Second*10, []int64{0})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got, err := m.Get(ctx, "k1")
|
||||
if err != nil || got.Name != "bar" {
|
||||
t.Errorf("manager get failed: %v, %+v", err, got)
|
||||
}
|
||||
err = m.Del(ctx, "k1")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got, err = m.Get(ctx, "k1")
|
||||
if err == nil {
|
||||
t.Errorf("manager del failed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCacheLocal_Boundary(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
cache := NewCacheLocal(time.Second * 10)
|
||||
|
||||
// 空 key
|
||||
err := cache.Set(ctx, "", "v", time.Second*10, []int64{0})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var s string
|
||||
err = cache.Get(ctx, "", &s)
|
||||
if err != nil || s != "v" {
|
||||
t.Errorf("empty key failed: %v, %s", err, s)
|
||||
}
|
||||
// nil value
|
||||
err = cache.Set(ctx, "nil", nil, time.Second*10, []int64{0})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var x any
|
||||
err = cache.Get(ctx, "nil", &x)
|
||||
if err != nil || x != nil {
|
||||
t.Errorf("nil value failed: %v, %v", err, x)
|
||||
}
|
||||
// 极短 ttl
|
||||
err = cache.Set(ctx, "short", "v", time.Millisecond*1, []int64{0})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
time.Sleep(time.Millisecond * 5)
|
||||
err = cache.Get(ctx, "short", &s)
|
||||
if err == nil {
|
||||
t.Errorf("short ttl should expire")
|
||||
}
|
||||
// 重复 set
|
||||
err = cache.Set(ctx, "dup", "v1", time.Second*10, []int64{0})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = cache.Set(ctx, "dup", "v2", time.Second*10, []int64{0})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = cache.Get(ctx, "dup", &s)
|
||||
if err != nil || s != "v2" {
|
||||
t.Errorf("dup set failed: %v, %s", err, s)
|
||||
}
|
||||
// Stop 后行为
|
||||
cache.Stop()
|
||||
// Stop 后再次 Stop 不应 panic
|
||||
cache.Stop()
|
||||
}
|
||||
|
||||
func TestCacheLocal_Concurrent(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
cache := NewCacheLocal(time.Second * 10)
|
||||
|
||||
n := 100
|
||||
done := make(chan struct{}, n*2)
|
||||
for i := 0; i < n; i++ {
|
||||
go func(i int) {
|
||||
key := "k" + string(rune(i))
|
||||
cache.Set(ctx, key, i, time.Second*10, []int64{int64(i)})
|
||||
done <- struct{}{}
|
||||
}(i)
|
||||
go func(i int) {
|
||||
key := "k" + string(rune(i))
|
||||
var v int
|
||||
cache.Get(ctx, key, &v)
|
||||
done <- struct{}{}
|
||||
}(i)
|
||||
}
|
||||
for i := 0; i < n*2; i++ {
|
||||
<-done
|
||||
}
|
||||
}
|
||||
|
||||
func TestCacheLocal_Types(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
cache := NewCacheLocal(time.Second * 10)
|
||||
// int
|
||||
err := cache.Set(ctx, "int", 123, time.Second*10, []int64{0})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var i int
|
||||
err = cache.Get(ctx, "int", &i)
|
||||
if err != nil || i != 123 {
|
||||
t.Errorf("int get failed: %v, %d", err, i)
|
||||
}
|
||||
// string
|
||||
err = cache.Set(ctx, "str", "abc", time.Second*10, []int64{0})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var s string
|
||||
err = cache.Get(ctx, "str", &s)
|
||||
if err != nil || s != "abc" {
|
||||
t.Errorf("string get failed: %v, %s", err, s)
|
||||
}
|
||||
// struct
|
||||
st := TestStruct{Name: "foo"}
|
||||
err = cache.Set(ctx, "struct", st, time.Second*10, []int64{0})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var st2 TestStruct
|
||||
err = cache.Get(ctx, "struct", &st2)
|
||||
if err != nil || st2.Name != "foo" {
|
||||
t.Errorf("struct get failed: %v, %+v", err, st2)
|
||||
}
|
||||
// map
|
||||
m := map[string]int{"a": 1}
|
||||
err = cache.Set(ctx, "map", m, time.Second*10, []int64{0})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var m2 map[string]int
|
||||
err = cache.Get(ctx, "map", &m2)
|
||||
if err != nil || m2["a"] != 1 {
|
||||
t.Errorf("map get failed: %v, %+v", err, m2)
|
||||
}
|
||||
// slice
|
||||
sl := []string{"x", "y"}
|
||||
err = cache.Set(ctx, "slice", sl, time.Second*10, []int64{0})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var sl2 []string
|
||||
err = cache.Get(ctx, "slice", &sl2)
|
||||
if err != nil || len(sl2) != 2 || sl2[0] != "x" {
|
||||
t.Errorf("slice get failed: %v, %+v", err, sl2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCacheLocal_Expire(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
cache := NewCacheLocal(time.Millisecond * 50)
|
||||
|
||||
err := cache.Set(ctx, "expire", "v", time.Millisecond*100, []int64{0})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var s string
|
||||
err = cache.Get(ctx, "expire", &s)
|
||||
if err != nil || s != "v" {
|
||||
t.Errorf("expire get failed: %v, %s", err, s)
|
||||
}
|
||||
time.Sleep(time.Millisecond * 120)
|
||||
err = cache.Get(ctx, "expire", &s)
|
||||
if err == nil {
|
||||
t.Errorf("should expire, got: %v", s)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCacheLocal_Del(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
cache := NewCacheLocal(time.Second * 10)
|
||||
err := cache.Set(ctx, "del", 1, time.Second*10, []int64{0})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = cache.Del(ctx, "del")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var i int
|
||||
err = cache.Get(ctx, "del", &i)
|
||||
if err == nil {
|
||||
t.Errorf("should not get deleted key")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCacheLocal_TypeMismatch(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
cache := NewCacheLocal(time.Second * 10)
|
||||
err := cache.Set(ctx, "mismatch", 123, time.Second*10, []int64{0})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var s string
|
||||
err = cache.Get(ctx, "mismatch", &s)
|
||||
if err == nil {
|
||||
t.Errorf("should type mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCacheLocal_BatchGet(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
cache := NewCacheLocal(time.Second * 10)
|
||||
|
||||
cache.Set(ctx, "a", 1, time.Second*10, []int64{0})
|
||||
cache.Set(ctx, "b", 2, time.Second*10, []int64{0})
|
||||
cache.Set(ctx, "c", 3, time.Second*10, []int64{0})
|
||||
keys := []string{"a", "b", "x"}
|
||||
m, err := BatchGetLocal[int](cache, ctx, keys)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(m) != 2 || *m["a"] != 1 || *m["b"] != 2 {
|
||||
t.Errorf("batch get failed: %+v", m)
|
||||
}
|
||||
}
|
||||
|
||||
type TestStruct struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
Reference in New Issue
Block a user