add redis cache

This commit is contained in:
Yavolte 2025-06-18 17:47:04 +08:00
parent 4dc5e64dde
commit 673d22fcff
7 changed files with 177 additions and 30 deletions

3
go.mod
View File

@ -11,6 +11,7 @@ require (
github.com/google/uuid v1.6.0
github.com/mattn/go-runewidth v0.0.16
github.com/peterh/liner v1.2.2
github.com/redis/go-redis/v9 v9.10.0
github.com/spf13/cobra v1.9.1
go.etcd.io/etcd/api/v3 v3.6.0
go.etcd.io/etcd/client/v3 v3.6.0
@ -24,10 +25,12 @@ require (
require (
github.com/bytedance/sonic v1.11.6 // indirect
github.com/bytedance/sonic/loader v0.1.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/coreos/go-semver v0.3.1 // indirect
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect

10
go.sum
View File

@ -1,7 +1,13 @@
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
@ -14,6 +20,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6N
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8=
github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
@ -86,6 +94,8 @@ github.com/peterh/liner v1.2.2 h1:aJ4AOodmL+JxOZZEL2u9iJf8omNRpqHc/EbrK+3mAXw=
github.com/peterh/liner v1.2.2/go.mod h1:xFwJyiKIXJZUKItq5dGHZSTBRAuG/CpeNpWLyiNRNwI=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/redis/go-redis/v9 v9.10.0 h1:FxwK3eV8p/CQa0Ch276C7u2d0eNC9kCmAYQ7mCXCzVs=
github.com/redis/go-redis/v9 v9.10.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=

14
pkg/cache/cache.go vendored
View File

@ -13,9 +13,11 @@ var (
type Cache interface {
// Get gets a cached value by key.
Get(ctx context.Context, key string) (any, error)
Load(ctx context.Context, key string, val any) error
// Get gets a cached value by key.
Exists(ctx context.Context, key string) (bool, error)
// Put stores a key-value pair into cache.
Put(ctx context.Context, key string, val any, d time.Duration) error
Store(ctx context.Context, key string, val any, d time.Duration) error
// Delete removes a key from cache.
Delete(ctx context.Context, key string) error
// String returns the name of the implementation.
@ -27,13 +29,13 @@ func Default() Cache {
}
// Get gets a cached value by key.
func Get(ctx context.Context, key string) (any, error) {
return std.Get(ctx, key)
func Load(ctx context.Context, key string, val any) error {
return std.Load(ctx, key, val)
}
// Put stores a key-value pair into cache.
func Put(ctx context.Context, key string, val any, d time.Duration) error {
return std.Put(ctx, key, val, d)
func Store(ctx context.Context, key string, val any, d time.Duration) error {
return std.Store(ctx, key, val, d)
}
// String returns the name of the implementation.

View File

@ -2,10 +2,18 @@ package memory
import (
"context"
"fmt"
"reflect"
"sync"
"time"
"git.nobla.cn/golang/aeus/pkg/errors"
"errors"
)
var (
ErrWongType = errors.New("val must be a pointer")
ErrNotExists = errors.New("not exists")
ErrAaddressable = errors.New("cannot set value: val is not addressable")
)
type memCache struct {
@ -15,22 +23,47 @@ type memCache struct {
sync.RWMutex
}
func (c *memCache) Get(ctx context.Context, key string) (any, error) {
func (c *memCache) Load(ctx context.Context, key string, val any) error {
c.RWMutex.RLock()
defer c.RWMutex.RUnlock()
item, found := c.items[key]
if !found {
return nil, errors.ErrNotFound
return ErrNotExists
}
if item.Expired() {
return nil, errors.ErrExpired
return ErrNotExists
}
return item.Value, nil
refValue := reflect.ValueOf(val)
if refValue.Type().Kind() != reflect.Ptr {
return ErrWongType
}
refElem := refValue.Elem()
if !refElem.CanSet() {
return ErrAaddressable
}
targetValue := reflect.Indirect(reflect.ValueOf(item.Value))
if targetValue.Type() != refElem.Type() {
return fmt.Errorf("type mismatch: expected %v, got %v", refElem.Type(), targetValue.Type())
}
refElem.Set(targetValue)
return nil
}
func (c *memCache) Put(ctx context.Context, key string, val any, d time.Duration) error {
func (c *memCache) Exists(ctx context.Context, key string) (bool, error) {
c.RWMutex.RLock()
defer c.RWMutex.RUnlock()
item, found := c.items[key]
if !found {
return false, nil
}
if item.Expired() {
return false, nil
}
return true, nil
}
func (c *memCache) Store(ctx context.Context, key string, val any, d time.Duration) error {
var e int64
if d == DefaultExpiration {
d = c.opts.Expiration
@ -56,9 +89,8 @@ func (c *memCache) Delete(ctx context.Context, key string) error {
_, found := c.items[key]
if !found {
return errors.ErrNotFound
return ErrNotExists
}
delete(c.items, key)
return nil
}
@ -66,3 +98,18 @@ func (c *memCache) Delete(ctx context.Context, key string) error {
func (m *memCache) String() string {
return "memory"
}
// NewCache returns a new cache.
func NewCache(opts ...Option) *memCache {
options := NewOptions(opts...)
items := make(map[string]Item)
if len(options.Items) > 0 {
items = options.Items
}
return &memCache{
opts: options,
items: items,
}
}

View File

@ -16,18 +16,3 @@ func (i *Item) Expired() bool {
return time.Now().UnixNano() > i.Expiration
}
// NewCache returns a new cache.
func NewCache(opts ...Option) *memCache {
options := NewOptions(opts...)
items := make(map[string]Item)
if len(options.Items) > 0 {
items = options.Items
}
return &memCache{
opts: options,
items: items,
}
}

63
pkg/cache/redis/cache.go vendored 100644
View File

@ -0,0 +1,63 @@
package redis
import (
"context"
"encoding/json"
"time"
)
type redisCache struct {
opts *options
}
func (c *redisCache) buildKey(key string) string {
return c.opts.prefix + key
}
func (c *redisCache) Load(ctx context.Context, key string, val any) error {
var (
err error
buf []byte
)
if buf, err = c.opts.client.Get(ctx, c.buildKey(key)).Bytes(); err == nil {
err = json.Unmarshal(buf, val)
}
return err
}
func (c *redisCache) Exists(ctx context.Context, key string) (ok bool, err error) {
var n int64
n, err = c.opts.client.Exists(ctx, c.buildKey(key)).Result()
if n > 0 {
ok = true
}
return
}
// Put stores a key-value pair into cache.
func (c *redisCache) Store(ctx context.Context, key string, val any, d time.Duration) error {
var (
err error
buf []byte
)
if buf, err = json.Marshal(val); err == nil {
err = c.opts.client.Set(ctx, c.buildKey(key), buf, d).Err()
}
return err
}
// Delete removes a key from cache.
func (c *redisCache) Delete(ctx context.Context, key string) error {
return c.opts.client.Del(ctx, c.buildKey(key)).Err()
}
// String returns the name of the implementation.
func (c *redisCache) String() string {
return "redis"
}
func NewCache(opts ...Option) *redisCache {
return &redisCache{
opts: newOptions(opts...),
}
}

37
pkg/cache/redis/types.go vendored 100644
View File

@ -0,0 +1,37 @@
package redis
import (
"github.com/redis/go-redis/v9"
)
type (
options struct {
client *redis.Client
prefix string
}
Option func(*options)
)
func WithClient(client *redis.Client) Option {
return func(o *options) {
o.client = client
}
}
func WithPrefix(prefix string) Option {
return func(o *options) {
o.prefix = prefix
}
}
// NewOptions returns a new options struct.
func newOptions(opts ...Option) *options {
options := &options{
prefix: "cache:",
}
for _, o := range opts {
o(options)
}
return options
}