-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
3cffc30
commit 57312fd
Showing
10 changed files
with
1,286 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,3 +19,4 @@ | |
|
||
# Go workspace file | ||
go.work | ||
.idea |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,73 @@ | ||
# cc-go-cache | ||
go cache | ||
|
||
## Installation | ||
|
||
```shell | ||
go get github.com/crosect/cc-go-cache | ||
``` | ||
|
||
## Configuration | ||
```yaml | ||
app.cache: | ||
driver: "memory" #support memory, redis | ||
# If use memory | ||
memory: | ||
defaultExpiration: "30s" # 30s, 30m, 30h | ||
cleanupInterval: "1s" # 1s, 1m, 1h | ||
# If use redis | ||
redis: | ||
host: localhost | ||
port: 6379 | ||
database: 0 | ||
user: username | ||
password: secret | ||
enableTLS: true #default: false | ||
``` | ||
|
||
## Usage | ||
|
||
Register to fx container | ||
|
||
```go | ||
package bootstrap | ||
|
||
import ( | ||
"github.com/crosect/cc-go-cache" | ||
"go.uber.org/fx" | ||
) | ||
|
||
func Register() fx.Option { | ||
return cccache.EnableCache() | ||
} | ||
``` | ||
|
||
Remember function will get value in the cache if exists, if not exists, it will set to cache | ||
|
||
```go | ||
package app | ||
|
||
import ( | ||
"github.com/crosect/cc-go-cache" | ||
"time" | ||
) | ||
|
||
type NeedCache struct { | ||
cache *cccache.Cache | ||
} | ||
|
||
func (nc *NeedCache) UseRemember() { | ||
// String | ||
str, err := nc.cache.Remember("key", 30*time.Second, func() (interface{}, error) { | ||
return "value", nil | ||
}) | ||
// Number | ||
num, err := nc.cache.Remember("key", 30*time.Second, func() (interface{}, error) { | ||
return 100, nil | ||
}) | ||
// Struct | ||
value, err := nc.cache.Remember("key", 30*time.Second, func() (interface{}, error) { | ||
return &Example{Data: "data"}, nil | ||
}) | ||
data := value.(*Example).Data | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
package cccache | ||
|
||
import ( | ||
"context" | ||
"github.com/crosect/cc-go/log" | ||
"github.com/eko/gocache/v2/cache" | ||
"github.com/eko/gocache/v2/store" | ||
"time" | ||
) | ||
|
||
type Cache struct { | ||
properties *CacheProperties | ||
cache *cache.Cache | ||
} | ||
|
||
func NewCache(properties *CacheProperties) (*Cache, error) { | ||
cacheStore, err := NewStore(properties) | ||
if err != nil { | ||
return nil, err | ||
} | ||
c := cache.New(cacheStore) | ||
return &Cache{ | ||
properties: properties, | ||
cache: c, | ||
}, nil | ||
} | ||
|
||
func (c *Cache) Exist(key string) bool { | ||
_, err := c.Get(key) | ||
return err == nil | ||
} | ||
|
||
func (c *Cache) Remember(key string, duration time.Duration, fn func() (interface{}, error)) (interface{}, error) { | ||
value, err := c.Get(key) | ||
if err == nil { | ||
return value, nil | ||
} | ||
v, err := fn() | ||
if err == nil { | ||
c.AsyncSet(key, v, duration) | ||
} | ||
return v, err | ||
} | ||
|
||
func (c *Cache) AsyncSet(key string, value interface{}, duration time.Duration) { | ||
go func() { | ||
err := c.cache.Set(context.Background(), key, value, &store.Options{ | ||
Expiration: duration, | ||
}) | ||
if err != nil { | ||
log.Warnf("cache: async set: %v", err) | ||
} | ||
}() | ||
} | ||
|
||
func (c *Cache) Get(key string) (interface{}, error) { | ||
ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond) | ||
defer cancel() | ||
value, err := c.cache.Get(ctx, key) | ||
if err != nil { | ||
log.Debugf("cache: get by key: %v: %v", key, err) | ||
return nil, err | ||
} | ||
return value, nil | ||
} | ||
|
||
func (c *Cache) Set(key string, value interface{}, duration time.Duration) error { | ||
return c.cache.Set(context.Background(), key, value, &store.Options{ | ||
Expiration: duration, | ||
}) | ||
} | ||
|
||
func (c *Cache) Delete(key string) error { | ||
return c.cache.Delete(context.Background(), key) | ||
} | ||
|
||
func (c *Cache) Clear() error { | ||
return c.cache.Clear(context.Background()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
package cccache | ||
|
||
import ( | ||
"github.com/crosect/cc-go/config" | ||
"time" | ||
) | ||
|
||
// CacheProperties represents ... | ||
type CacheProperties struct { | ||
Driver string | ||
Memory MemoryCacheProperties | ||
Redis RedisCacheProperties | ||
} | ||
|
||
// MemoryCacheProperties represent memory cache properties | ||
type MemoryCacheProperties struct { | ||
DefaultExpiration time.Duration `default:"30s"` | ||
CleanupInterval time.Duration `default:"30s"` | ||
} | ||
|
||
// RedisCacheProperties represents redis cache properties | ||
type RedisCacheProperties struct { | ||
Host string | ||
Port int | ||
Database int | ||
User string | ||
Password string | ||
EnableTLS bool | ||
} | ||
|
||
// NewCacheProperties return a new CacheProperties instance | ||
func NewCacheProperties(loader config.Loader) (*CacheProperties, error) { | ||
props := CacheProperties{} | ||
err := loader.Bind(&props) | ||
return &props, err | ||
} | ||
|
||
// Prefix return config prefix | ||
func (t *CacheProperties) Prefix() string { | ||
return "app.cache" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
package cccache | ||
|
||
import ( | ||
"crypto/tls" | ||
"fmt" | ||
"github.com/eko/gocache/v2/store" | ||
"github.com/go-redis/redis/v8" | ||
gc "github.com/patrickmn/go-cache" | ||
) | ||
|
||
func NewStore(properties *CacheProperties) (store.StoreInterface, error) { | ||
switch properties.Driver { | ||
case "memory": | ||
return NewMemoryStore(properties.Memory) | ||
case "redis": | ||
return NewRedisStore(properties.Redis) | ||
default: | ||
return nil, fmt.Errorf("cache driver: %s is not supported", properties.Driver) | ||
} | ||
} | ||
|
||
func NewMemoryStore(properties MemoryCacheProperties) (store.StoreInterface, error) { | ||
client := gc.New(properties.DefaultExpiration, properties.CleanupInterval) | ||
return store.NewGoCache(client, nil), nil | ||
} | ||
|
||
func NewRedisStore(properties RedisCacheProperties) (store.StoreInterface, error) { | ||
options := &redis.Options{ | ||
Addr: fmt.Sprintf("%s:%d", properties.Host, properties.Port), | ||
Username: properties.User, | ||
Password: properties.Password, | ||
DB: properties.Database, | ||
} | ||
if properties.EnableTLS { | ||
options.TLSConfig = &tls.Config{ | ||
MinVersion: tls.VersionTLS12, | ||
} | ||
} | ||
return store.NewRedis(redis.NewClient(options), nil), nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
package cccache | ||
|
||
import ( | ||
"context" | ||
"github.com/eko/gocache/v2/store" | ||
"github.com/stretchr/testify/assert" | ||
"testing" | ||
"time" | ||
) | ||
|
||
type Data struct { | ||
Value string | ||
} | ||
|
||
func TestCache_Exist(t *testing.T) { | ||
cache, err := NewCache(&CacheProperties{ | ||
Driver: "memory", | ||
Memory: MemoryCacheProperties{ | ||
DefaultExpiration: time.Minute, | ||
CleanupInterval: 30 * time.Second, | ||
}, | ||
}) | ||
assert.Nil(t, err) | ||
t.Run("Not Exist", func(t *testing.T) { | ||
exist := cache.Exist("not_exist_key") | ||
assert.False(t, exist) | ||
}) | ||
t.Run("Exist", func(t *testing.T) { | ||
err := cache.cache.Set(context.Background(), "exist_key", "value", &store.Options{Expiration: time.Minute}) | ||
assert.Nil(t, err) | ||
exist := cache.Exist("exist_key") | ||
assert.True(t, exist) | ||
}) | ||
} | ||
|
||
func TestCache_Remember(t *testing.T) { | ||
cache, err := NewCache(&CacheProperties{ | ||
Driver: "memory", | ||
Memory: MemoryCacheProperties{ | ||
DefaultExpiration: time.Minute, | ||
CleanupInterval: 30 * time.Second, | ||
}, | ||
}) | ||
assert.Nil(t, err) | ||
t.Run("Not Exist With Integer", func(t *testing.T) { | ||
err := cache.cache.Delete(context.Background(), "not_exist_key") | ||
assert.Nil(t, err) | ||
value, err := cache.Remember("not_exist_key", time.Minute, func() (interface{}, error) { | ||
return 10, nil | ||
}) | ||
assert.Nil(t, err) | ||
assert.Equal(t, 10, value) | ||
time.Sleep(200 * time.Millisecond) | ||
v, err := cache.cache.Get(context.Background(), "not_exist_key") | ||
assert.Nil(t, err) | ||
assert.Equal(t, 10, v) | ||
}) | ||
t.Run("Not Exist With String", func(t *testing.T) { | ||
err := cache.cache.Delete(context.Background(), "not_exist_key") | ||
assert.Nil(t, err) | ||
value, err := cache.Remember("not_exist_key", time.Minute, func() (interface{}, error) { | ||
return "string", nil | ||
}) | ||
assert.Nil(t, err) | ||
assert.Equal(t, "string", value) | ||
time.Sleep(200 * time.Millisecond) | ||
v, err := cache.cache.Get(context.Background(), "not_exist_key") | ||
assert.Nil(t, err) | ||
assert.Equal(t, "string", v) | ||
}) | ||
t.Run("Not Exist With Struct", func(t *testing.T) { | ||
err := cache.cache.Delete(context.Background(), "not_exist_key") | ||
assert.Nil(t, err) | ||
value, err := cache.Remember("not_exist_key", time.Minute, func() (interface{}, error) { | ||
return &Data{Value: "value"}, nil | ||
}) | ||
assert.Nil(t, err) | ||
assert.Equal(t, "value", value.(*Data).Value) | ||
time.Sleep(200 * time.Millisecond) | ||
v, err := cache.cache.Get(context.Background(), "not_exist_key") | ||
assert.Nil(t, err) | ||
assert.Equal(t, "value", v.(*Data).Value) | ||
}) | ||
t.Run("Exist With Integer", func(t *testing.T) { | ||
err := cache.cache.Set(context.Background(), "exist_key", 10, &store.Options{Expiration: time.Minute}) | ||
assert.Nil(t, err) | ||
value, err := cache.Remember("exist_key", time.Minute, func() (interface{}, error) { | ||
return 10, nil | ||
}) | ||
assert.Nil(t, err) | ||
assert.Equal(t, 10, value) | ||
}) | ||
t.Run("Exist With String", func(t *testing.T) { | ||
err := cache.cache.Set(context.Background(), "exist_key", "string", &store.Options{Expiration: time.Minute}) | ||
assert.Nil(t, err) | ||
value, err := cache.Remember("exist_key", time.Minute, func() (interface{}, error) { | ||
return "string", nil | ||
}) | ||
assert.Nil(t, err) | ||
assert.Equal(t, "string", value) | ||
}) | ||
t.Run("Exist With Struct", func(t *testing.T) { | ||
err := cache.cache.Set(context.Background(), "exist_key", &Data{Value: "value"}, &store.Options{Expiration: time.Minute}) | ||
assert.Nil(t, err) | ||
value, err := cache.Remember("exist_key", time.Minute, func() (interface{}, error) { | ||
return &Data{Value: "value"}, nil | ||
}) | ||
assert.Nil(t, err) | ||
assert.Equal(t, "value", value.(*Data).Value) | ||
}) | ||
} | ||
|
||
func TestCache_Delete(t *testing.T) { | ||
cache, err := NewCache(&CacheProperties{ | ||
Driver: "memory", | ||
Memory: MemoryCacheProperties{ | ||
DefaultExpiration: time.Minute, | ||
CleanupInterval: 30 * time.Second, | ||
}, | ||
}) | ||
assert.Nil(t, err) | ||
t.Run("Delete", func(t *testing.T) { | ||
setErr := cache.cache.Set(context.Background(), "delete_key", "value", &store.Options{Expiration: time.Minute}) | ||
assert.Nil(t, setErr) | ||
beforeDelete := cache.Exist("delete_key") | ||
assert.True(t, beforeDelete) | ||
err := cache.Delete("delete_key") | ||
assert.Nil(t, err) | ||
afterDelete := cache.Exist("delete_key") | ||
assert.False(t, afterDelete) | ||
}) | ||
} | ||
|
||
func TestCache_Clear(t *testing.T) { | ||
cache, err := NewCache(&CacheProperties{ | ||
Driver: "memory", | ||
Memory: MemoryCacheProperties{ | ||
DefaultExpiration: time.Minute, | ||
CleanupInterval: 30 * time.Second, | ||
}, | ||
}) | ||
assert.Nil(t, err) | ||
t.Run("Clear", func(t *testing.T) { | ||
setErr := cache.cache.Set(context.Background(), "first_key", "value", &store.Options{Expiration: time.Minute}) | ||
assert.Nil(t, setErr) | ||
setErr = cache.cache.Set(context.Background(), "second_key", "value", &store.Options{Expiration: time.Minute}) | ||
assert.Nil(t, setErr) | ||
assert.True(t, cache.Exist("first_key")) | ||
assert.True(t, cache.Exist("second_key")) | ||
err := cache.Clear() | ||
assert.Nil(t, err) | ||
assert.False(t, cache.Exist("first_key")) | ||
assert.False(t, cache.Exist("second_key")) | ||
}) | ||
} |
Oops, something went wrong.