diff --git a/go.mod b/go.mod index 3d49e1a..4508c4c 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index 742bcd2..03b6e61 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/pkg/cache/cache.go b/pkg/cache/cache.go index c90207b..54506e3 100644 --- a/pkg/cache/cache.go +++ b/pkg/cache/cache.go @@ -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. diff --git a/pkg/cache/memory/cache.go b/pkg/cache/memory/cache.go index d189c0b..84d6250 100644 --- a/pkg/cache/memory/cache.go +++ b/pkg/cache/memory/cache.go @@ -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, + } +} diff --git a/pkg/cache/memory/item.go b/pkg/cache/memory/item.go index 3e339f8..f3402d1 100644 --- a/pkg/cache/memory/item.go +++ b/pkg/cache/memory/item.go @@ -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, - } -} diff --git a/pkg/cache/redis/cache.go b/pkg/cache/redis/cache.go new file mode 100644 index 0000000..d7a0309 --- /dev/null +++ b/pkg/cache/redis/cache.go @@ -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...), + } +} diff --git a/pkg/cache/redis/types.go b/pkg/cache/redis/types.go new file mode 100644 index 0000000..32312e3 --- /dev/null +++ b/pkg/cache/redis/types.go @@ -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 +}