diff --git a/cache/cache.go b/cache/cache.go index ac30b3c..ad1696c 100644 --- a/cache/cache.go +++ b/cache/cache.go @@ -1,9 +1,15 @@ package cache +import "errors" + type Cache interface { Init(opts ...Option) error Options() Options Get(key string, resultPtr interface{}, opts ...ReadOption) error + BatchGet(keys []string, resultsPtr interface{}, opts ...ReadOption) error Set(key string, value interface{}, opts ...WriteOption) error Delete(key string, opts ...DeleteOption) error -} \ No newline at end of file + BatchDelete(keys []string, opts ...DeleteOption) error +} + +var ErrNil = errors.New("nil returned") diff --git a/cache/options.go b/cache/options.go index 34dd34d..ed94efa 100644 --- a/cache/options.go +++ b/cache/options.go @@ -7,7 +7,7 @@ import ( type Options struct { Context context.Context - Prefix string + Prefix string } type Option func(o *Options) @@ -29,9 +29,7 @@ func WriteExpiry(t time.Duration) WriteOption { } } - type DeleteOptions struct { - } -type DeleteOption func(o *DeleteOptions) \ No newline at end of file +type DeleteOption func(o *DeleteOptions) diff --git a/cache/redis/redis.go b/cache/redis/redis.go index 63646d2..f8cee7b 100644 --- a/cache/redis/redis.go +++ b/cache/redis/redis.go @@ -1,29 +1,28 @@ package redis -import( +import ( "context" + "encoding/json" "fmt" - "time" - "strings" - "github.com/xxxmicro/base/cache" "github.com/garyburd/redigo/redis" - "encoding/json" + "github.com/xxxmicro/base/cache" + "reflect" + "strings" + "time" ) type RedisCache struct { options cache.Options - r *redis.Pool + r *redis.Pool } func NewCache(opts ...cache.Option) cache.Cache { options := cache.Options{ Context: context.Background(), } - for _, o := range opts { o(&options) } - return &RedisCache{ options: options, } @@ -33,7 +32,6 @@ func (m *RedisCache) Init(opts ...cache.Option) error { for _, o := range opts { o(&m.options) } - r, err := m.connect() if err != nil { return err @@ -65,7 +63,7 @@ func (m *RedisCache) connect() (*redis.Pool, error) { if password != "" { if _, err := c.Do("AUTH", password); err != nil { - c.Close() + _ = c.Close() return nil, err } } @@ -76,12 +74,15 @@ func (m *RedisCache) connect() (*redis.Pool, error) { return err }, } - return pool, nil } func (m *RedisCache) prefix(key string) string { - return fmt.Sprintf("%s:%s", m.options.Prefix, key) + if len(m.options.Prefix) > 0 { + return fmt.Sprintf("%s:%s", m.options.Prefix, key) + } else { + return key + } } func (m *RedisCache) String() string { @@ -98,14 +99,58 @@ func (m *RedisCache) Get(key string, resultPtr interface{}, opts ...cache.ReadOp c := m.r.Get() defer c.Close() + data, err := redis.Bytes(c.Do("GET", key)) - if err != nil { + if err == redis.ErrNil { + return cache.ErrNil + } else if err != nil { return err } - return json.Unmarshal(data, resultPtr) } +func (m *RedisCache) BatchGet(keys []string, resultsPtr interface{}, opts ...cache.ReadOption) error { + readOpts := cache.ReadOptions{} + for _, o := range opts { + o(&readOpts) + } + + realKeys := make([]interface{}, len(keys)) + for i, key := range keys { + realKeys[i] = m.prefix(key) + } + + c := m.r.Get() + defer c.Close() + + var replies interface{} + var err error + if replies, err = c.Do("MGET", realKeys...); err != nil { + return err + } + + sliceType := reflect.TypeOf(resultsPtr).Elem() + valuePtrType := sliceType.Elem() + valueType := valuePtrType.Elem() + slice := reflect.MakeSlice(sliceType, 0, 0) + for _, v := range replies.([]interface{}) { + var data []byte + if data, err = redis.Bytes(v, nil); err == redis.ErrNil { + slice = reflect.Append(slice, reflect.Zero(valuePtrType)) + continue + } else if err != nil { + return err + } + valuePtr := reflect.New(valueType) + if err = json.Unmarshal(data, valuePtr.Interface()); err != nil { + return err + } + slice = reflect.Append(slice, valuePtr) + } + reflect.ValueOf(resultsPtr).Elem().Set(slice) + return nil +} + func (m *RedisCache) Set(key string, value interface{}, opts ...cache.WriteOption) error { writeOpts := cache.WriteOptions{} for _, o := range opts { @@ -130,10 +175,6 @@ func (m *RedisCache) Set(key string, value interface{}, opts ...cache.WriteOptio return err } -func (m *RedisCache) Close() error { - return m.r.Close() -} - func (m *RedisCache) Delete(key string, opts ...cache.DeleteOption) error { deleteOptions := cache.DeleteOptions{} for _, o := range opts { @@ -147,4 +188,26 @@ func (m *RedisCache) Delete(key string, opts ...cache.DeleteOption) error { _, err := c.Do("DEL", key) return err -} \ No newline at end of file +} + +func (m *RedisCache) BatchDelete(keys []string, opts ...cache.DeleteOption) error { + deleteOptions := cache.DeleteOptions{} + for _, o := range opts { + o(&deleteOptions) + } + + realKeys := make([]interface{}, len(keys)) + for i, key := range keys { + realKeys[i] = m.prefix(key) + } + + c := m.r.Get() + defer c.Close() + + _, err := c.Do("DEL", realKeys...) + return err +} + +func (m *RedisCache) Close() error { + return m.r.Close() +} diff --git a/cache/redis/redis_cache_test.go b/cache/redis/redis_cache_test.go index d62aef5..1ac3d56 100644 --- a/cache/redis/redis_cache_test.go +++ b/cache/redis/redis_cache_test.go @@ -1,16 +1,16 @@ package redis_test -import( - "testing" - "time" - "log" +import ( + "github.com/stretchr/testify/assert" "github.com/xxxmicro/base/cache" "github.com/xxxmicro/base/cache/redis" - "github.com/stretchr/testify/assert" + "log" + "testing" + "time" ) type User struct { - Name string `json:"name"` + Name string `json:"name"` CreatedAt time.Time `json:"created_at"` } @@ -19,9 +19,9 @@ func TestBasic(t *testing.T) { s.Init() key := "hello" - value := &User{Name:"李小龙", CreatedAt: time.Now()} + value := &User{Name: "李小龙", CreatedAt: time.Now()} - err := s.Set(key, value, cache.WriteExpiry(time.Millisecond * 1000)) + err := s.Set(key, value, cache.WriteExpiry(time.Millisecond*1000)) assert.NoError(t, err) time.Sleep(time.Millisecond * 500) @@ -35,5 +35,45 @@ func TestBasic(t *testing.T) { value2 := &User{} err = s.Get(key, value2) - assert.Error(t, err, "Expected no records in redis store") -} \ No newline at end of file + assert.Equal(t, cache.ErrNil, err) +} + +func TestBatch(t *testing.T) { + s := redis.NewCache(redis.WithAddrs(":6379")) + s.Init() + + var err error + + key1 := "bruceli" + value1 := &User{Name: "李小龙", CreatedAt: time.Now()} + + key2 := "jackychen" + value2 := &User{Name: "成龙", CreatedAt: time.Now()} + + err = s.Set(key1, value1) + assert.NoError(t, err) + + err = s.Set(key2, value2) + assert.NoError(t, err) + + users := make([]*User, 0) + err = s.BatchGet([]string{key1, "fatchow", key2}, &users) + assert.NoError(t, err) + assert.Equal(t, 3, len(users)) + assert.Equal(t, value1.Name, users[0].Name) + assert.Nil(t, users[1]) + assert.Equal(t, value2.Name, users[2].Name) + t.Log(users[0]) + t.Log(users[1]) + t.Log(users[2]) + + err = s.BatchDelete([]string{key1, "fatchow", key2}) + assert.NoError(t, err) + + gotValue := &User{} + err = s.Get(key1, gotValue) + assert.Equal(t, cache.ErrNil, err) + + err = s.Get(key2, gotValue) + assert.Equal(t, cache.ErrNil, err) +}