Merge pull request #4493 from flycash/session-cache

error code for cache module
This commit is contained in:
Ming Deng 2021-02-16 20:14:54 +08:00 committed by GitHub
commit 1ae542255c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 603 additions and 269 deletions

View File

@ -1,4 +1,5 @@
# developing
- Error codes definition of cache module. [4493](https://github.com/beego/beego/pull/4493)
- Remove generateCommentRoute http hook. Using `bee generate routers` commands instead.[4486](https://github.com/beego/beego/pull/4486) [bee PR 762](https://github.com/beego/bee/pull/762)
- Fix: /abc.html/aaa match /abc/aaa. [4459](https://github.com/beego/beego/pull/4459)
- ORM mock. [4407](https://github.com/beego/beego/pull/4407)

View File

@ -1 +1,5 @@
# Error Module
## Module code
- httplib 1
- cache 2

View File

@ -33,8 +33,9 @@ package cache
import (
"context"
"fmt"
"time"
"github.com/beego/beego/v2/core/berror"
)
// Cache interface contains all behaviors for cache adapter.
@ -55,12 +56,14 @@ type Cache interface {
// Set a cached value with key and expire time.
Put(ctx context.Context, key string, val interface{}, timeout time.Duration) error
// Delete cached value by key.
// Should not return error if key not found
Delete(ctx context.Context, key string) error
// Increment a cached int value by key, as a counter.
Incr(ctx context.Context, key string) error
// Decrement a cached int value by key, as a counter.
Decr(ctx context.Context, key string) error
// Check if a cached value exists or not.
// if key is expired, return (false, nil)
IsExist(ctx context.Context, key string) (bool, error)
// Clear all cache.
ClearAll(ctx context.Context) error
@ -78,7 +81,7 @@ var adapters = make(map[string]Instance)
// it panics.
func Register(name string, adapter Instance) {
if adapter == nil {
panic("cache: Register adapter is nil")
panic(berror.Error(NilCacheAdapter, "cache: Register adapter is nil").Error())
}
if _, ok := adapters[name]; ok {
panic("cache: Register called twice for adapter " + name)
@ -92,7 +95,7 @@ func Register(name string, adapter Instance) {
func NewCache(adapterName, config string) (adapter Cache, err error) {
instanceFunc, ok := adapters[adapterName]
if !ok {
err = fmt.Errorf("cache: unknown adapter name %q (forgot to import?)", adapterName)
err = berror.Errorf(UnknownAdapter, "cache: unknown adapter name %s (forgot to import?)", adapterName)
return
}
adapter = instanceFunc()

View File

@ -18,6 +18,7 @@ import (
"context"
"math"
"os"
"strings"
"sync"
"testing"
"time"
@ -108,7 +109,7 @@ func TestCache(t *testing.T) {
assert.Equal(t, "author1", vv[1])
assert.NotNil(t, err)
assert.Equal(t, "key [astaxie0] error: key not exist", err.Error())
assert.True(t, strings.Contains(err.Error(), "key isn't exist"))
}
func TestFileCache(t *testing.T) {

View File

@ -1,46 +1,55 @@
package cache
import (
"fmt"
"math"
"github.com/beego/beego/v2/core/berror"
)
var (
ErrIncrementOverflow = berror.Error(IncrementOverflow, "this incr invocation will overflow.")
ErrDecrementOverflow = berror.Error(DecrementOverflow, "this decr invocation will overflow.")
ErrNotIntegerType = berror.Error(NotIntegerType, "item val is not (u)int (u)int32 (u)int64")
)
func incr(originVal interface{}) (interface{}, error) {
switch val := originVal.(type) {
case int:
tmp := val + 1
if val > 0 && tmp < 0 {
return nil, fmt.Errorf("increment would overflow")
return nil, ErrIncrementOverflow
}
return tmp, nil
case int32:
if val == math.MaxInt32 {
return nil, fmt.Errorf("increment would overflow")
return nil, ErrIncrementOverflow
}
return val + 1, nil
case int64:
if val == math.MaxInt64 {
return nil, fmt.Errorf("increment would overflow")
return nil, ErrIncrementOverflow
}
return val + 1, nil
case uint:
tmp := val + 1
if tmp < val {
return nil, fmt.Errorf("increment would overflow")
return nil, ErrIncrementOverflow
}
return tmp, nil
case uint32:
if val == math.MaxUint32 {
return nil, fmt.Errorf("increment would overflow")
return nil, ErrIncrementOverflow
}
return val + 1, nil
case uint64:
if val == math.MaxUint64 {
return nil, fmt.Errorf("increment would overflow")
return nil, ErrIncrementOverflow
}
return val + 1, nil
default:
return nil, fmt.Errorf("item val is not (u)int (u)int32 (u)int64")
return nil, ErrNotIntegerType
}
}
@ -49,35 +58,35 @@ func decr(originVal interface{}) (interface{}, error) {
case int:
tmp := val - 1
if val < 0 && tmp > 0 {
return nil, fmt.Errorf("decrement would overflow")
return nil, ErrDecrementOverflow
}
return tmp, nil
case int32:
if val == math.MinInt32 {
return nil, fmt.Errorf("decrement would overflow")
return nil, ErrDecrementOverflow
}
return val - 1, nil
case int64:
if val == math.MinInt64 {
return nil, fmt.Errorf("decrement would overflow")
return nil, ErrDecrementOverflow
}
return val - 1, nil
case uint:
if val == 0 {
return nil, fmt.Errorf("decrement would overflow")
return nil, ErrDecrementOverflow
}
return val - 1, nil
case uint32:
if val == 0 {
return nil, fmt.Errorf("increment would overflow")
return nil, ErrDecrementOverflow
}
return val - 1, nil
case uint64:
if val == 0 {
return nil, fmt.Errorf("increment would overflow")
return nil, ErrDecrementOverflow
}
return val - 1, nil
default:
return nil, fmt.Errorf("item val is not (u)int (u)int32 (u)int64")
return nil, ErrNotIntegerType
}
}

View File

@ -14,61 +14,64 @@ func TestIncr(t *testing.T) {
var updateVal interface{} = int(2)
val, err := incr(originVal)
assert.Nil(t, err)
assert.Equal(t, updateVal, val)
assert.Equal(t, val, updateVal)
_, err = incr(int(1<<(strconv.IntSize-1) - 1))
assert.NotNil(t, err)
assert.Equal(t, ErrIncrementOverflow, err)
// int32
originVal = int32(1)
updateVal = int32(2)
val, err = incr(originVal)
assert.Nil(t, err)
assert.Equal(t, updateVal, val)
assert.Equal(t, val, updateVal)
_, err = incr(int32(math.MaxInt32))
assert.NotNil(t, err)
assert.Equal(t, ErrIncrementOverflow, err)
// int64
originVal = int64(1)
updateVal = int64(2)
val, err = incr(originVal)
assert.Nil(t, err)
assert.Equal(t, updateVal, val)
assert.Equal(t, val, updateVal)
_, err = incr(int64(math.MaxInt64))
assert.NotNil(t, err)
assert.Equal(t, ErrIncrementOverflow, err)
// uint
originVal = uint(1)
updateVal = uint(2)
val, err = incr(originVal)
assert.Nil(t, err)
assert.Equal(t, updateVal, val)
assert.Nil(t, err)
assert.Equal(t, val, updateVal)
_, err = incr(uint(1<<(strconv.IntSize) - 1))
assert.NotNil(t, err)
assert.Equal(t, ErrIncrementOverflow, err)
// uint32
originVal = uint32(1)
updateVal = uint32(2)
val, err = incr(originVal)
assert.Nil(t, err)
assert.Equal(t, updateVal, val)
assert.Equal(t, val, updateVal)
_, err = incr(uint32(math.MaxUint32))
assert.NotNil(t, err)
assert.Equal(t, ErrIncrementOverflow, err)
// uint64
originVal = uint64(1)
updateVal = uint64(2)
val, err = incr(originVal)
assert.Nil(t, err)
assert.Equal(t, updateVal, val)
_, err = incr(uint64(math.MaxUint64))
assert.NotNil(t, err)
assert.Equal(t, val, updateVal)
_, err = incr(uint64(math.MaxUint64))
assert.Equal(t, ErrIncrementOverflow, err)
// other type
_, err = incr("string")
assert.NotNil(t, err)
assert.Equal(t, ErrNotIntegerType, err)
}
func TestDecr(t *testing.T) {
@ -77,57 +80,61 @@ func TestDecr(t *testing.T) {
var updateVal interface{} = int(1)
val, err := decr(originVal)
assert.Nil(t, err)
assert.Equal(t, updateVal, val)
assert.Equal(t, val, updateVal)
_, err = decr(int(-1 << (strconv.IntSize - 1)))
assert.NotNil(t, err)
assert.Equal(t, ErrDecrementOverflow, err)
// int32
originVal = int32(2)
updateVal = int32(1)
val, err = decr(originVal)
assert.Nil(t, err)
assert.Equal(t, updateVal, val)
assert.Equal(t, val, updateVal)
_, err = decr(int32(math.MinInt32))
assert.NotNil(t, err)
assert.Equal(t, ErrDecrementOverflow, err)
// int64
originVal = int64(2)
updateVal = int64(1)
val, err = decr(originVal)
assert.Nil(t, err)
assert.Equal(t, updateVal, val)
assert.Equal(t, val, updateVal)
_, err = decr(int64(math.MinInt64))
assert.NotNil(t, err)
assert.Equal(t, ErrDecrementOverflow, err)
// uint
originVal = uint(2)
updateVal = uint(1)
val, err = decr(originVal)
assert.Nil(t, err)
assert.Equal(t, updateVal, val)
assert.Equal(t, val, updateVal)
_, err = decr(uint(0))
assert.NotNil(t, err)
assert.Equal(t, ErrDecrementOverflow, err)
// uint32
originVal = uint32(2)
updateVal = uint32(1)
val, err = decr(originVal)
assert.Nil(t, err)
assert.Equal(t, updateVal, val)
assert.Equal(t, val, updateVal)
_, err = decr(uint32(0))
assert.NotNil(t, err)
assert.Equal(t, ErrDecrementOverflow, err)
// uint64
originVal = uint64(2)
updateVal = uint64(1)
val, err = decr(originVal)
assert.Nil(t, err)
assert.Equal(t, updateVal, val)
assert.Equal(t, val, updateVal)
_, err = decr(uint64(0))
assert.NotNil(t, err)
assert.Equal(t, ErrDecrementOverflow, err)
// other type
_, err = decr("string")
assert.NotNil(t, err)
assert.Equal(t, ErrNotIntegerType, err)
}

182
client/cache/error_code.go vendored Normal file
View File

@ -0,0 +1,182 @@
// Copyright 2021 beego
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cache
import (
"github.com/beego/beego/v2/core/berror"
)
var NilCacheAdapter = berror.DefineCode(4002001, moduleName, "NilCacheAdapter", `
It means that you register cache adapter by pass nil.
A cache adapter is an instance of Cache interface.
`)
var DuplicateAdapter = berror.DefineCode(4002002, moduleName, "DuplicateAdapter", `
You register two adapter with same name. In beego cache module, one name one adapter.
Once you got this error, please check the error stack, search adapter
`)
var UnknownAdapter = berror.DefineCode(4002003, moduleName, "UnknownAdapter", `
Unknown adapter, do you forget to register the adapter?
You must register adapter before use it. For example, if you want to use redis implementation,
you must import the cache/redis package.
`)
var IncrementOverflow = berror.DefineCode(4002004, moduleName, "IncrementOverflow", `
The increment operation will overflow.
`)
var DecrementOverflow = berror.DefineCode(4002005, moduleName, "DecrementOverflow", `
The decrement operation will overflow.
`)
var NotIntegerType = berror.DefineCode(4002006, moduleName, "NotIntegerType", `
The type of value is not (u)int (u)int32 (u)int64.
When you want to call Incr or Decr function of Cache API, you must confirm that the value's type is one of (u)int (u)int32 (u)int64.
`)
var InvalidFileCacheDirectoryLevelCfg = berror.DefineCode(4002007, moduleName, "InvalidFileCacheDirectoryLevelCfg", `
You pass invalid DirectoryLevel parameter when you try to StartAndGC file cache instance.
This parameter must be a integer, and please check your input.
`)
var InvalidFileCacheEmbedExpiryCfg = berror.DefineCode(4002008, moduleName, "InvalidFileCacheEmbedExpiryCfg", `
You pass invalid EmbedExpiry parameter when you try to StartAndGC file cache instance.
This parameter must be a integer, and please check your input.
`)
var CreateFileCacheDirFailed = berror.DefineCode(4002009, moduleName, "CreateFileCacheDirFailed", `
Beego failed to create file cache directory. There are two cases:
1. You pass invalid CachePath parameter. Please check your input.
2. Beego doesn't have the permission to create this directory. Please check your file mode.
`)
var InvalidFileCachePath = berror.DefineCode(4002010, moduleName, "InvalidFilePath", `
The file path of FileCache is invalid. Please correct the config.
`)
var ReadFileCacheContentFailed = berror.DefineCode(4002011, moduleName, "ReadFileCacheContentFailed", `
Usually you won't got this error. It means that Beego cannot read the data from the file.
You need to check whether the file exist. Sometimes it may be deleted by other processes.
If the file exists, please check the permission that Beego is able to read data from the file.
`)
var InvalidGobEncodedData = berror.DefineCode(4002012, moduleName, "InvalidEncodedData", `
The data is invalid. When you try to decode the invalid data, you got this error.
Please confirm that the data is encoded by GOB correctly.
`)
var GobEncodeDataFailed = berror.DefineCode(4002013, moduleName, "GobEncodeDataFailed", `
Beego could not encode the data to GOB byte array. In general, the data type is invalid.
For example, GOB doesn't support function type.
Basic types, string, structure, structure pointer are supported.
`)
var KeyExpired = berror.DefineCode(4002014, moduleName, "KeyExpired", `
Cache key is expired.
You should notice that, a key is expired and then it may be deleted by GC goroutine.
So when you query a key which may be expired, you may got this code, or KeyNotExist.
`)
var KeyNotExist = berror.DefineCode(4002015, moduleName, "KeyNotExist", `
Key not found.
`)
var MultiGetFailed = berror.DefineCode(4002016, moduleName, "MultiGetFailed", `
Get multiple keys failed. Please check the detail msg to find out the root cause.
`)
var InvalidMemoryCacheCfg = berror.DefineCode(4002017, moduleName, "InvalidMemoryCacheCfg", `
The config is invalid. Please check your input. It must be a json string.
`)
var InvalidMemCacheCfg = berror.DefineCode(4002018, moduleName, "InvalidMemCacheCfg", `
The config is invalid. Please check your input, it must be json string and contains "conn" field.
`)
var InvalidMemCacheValue = berror.DefineCode(4002019, moduleName, "InvalidMemCacheValue", `
The value must be string or byte[], please check your input.
`)
var InvalidRedisCacheCfg = berror.DefineCode(4002020, moduleName, "InvalidRedisCacheCfg", `
The config must be json string, and has "conn" field.
`)
var InvalidSsdbCacheCfg = berror.DefineCode(4002021, moduleName, "InvalidSsdbCacheCfg", `
The config must be json string, and has "conn" field. The value of "conn" field should be "host:port".
"port" must be a valid integer.
`)
var InvalidSsdbCacheValue = berror.DefineCode(4002022, moduleName, "InvalidSsdbCacheValue", `
SSDB cache only accept string value. Please check your input.
`)
var DeleteFileCacheItemFailed = berror.DefineCode(5002001, moduleName, "DeleteFileCacheItemFailed", `
Beego try to delete file cache item failed.
Please check whether Beego generated file correctly.
And then confirm whether this file is already deleted by other processes or other people.
`)
var MemCacheCurdFailed = berror.DefineCode(5002002, moduleName, "MemCacheError", `
When you want to get, put, delete key-value from remote memcache servers, you may get error:
1. You pass invalid servers address, so Beego could not connect to remote server;
2. The servers address is correct, but there is some net issue. Typically there is some firewalls between application and memcache server;
3. Key is invalid. The key's length should be less than 250 and must not contains special characters;
4. The response from memcache server is invalid;
`)
var RedisCacheCurdFailed = berror.DefineCode(5002003, moduleName, "RedisCacheCurdFailed", `
When Beego uses client to send request to redis server, it failed.
1. The server addresses is invalid;
2. Network issue, firewall issue or network is unstable;
3. Client failed to manage connection. In extreme cases, Beego's redis client didn't maintain connections correctly, for example, Beego try to send request via closed connection;
4. The request are huge and redis server spent too much time to process it, and client is timeout;
In general, if you always got this error whatever you do, in most cases, it was caused by network issue.
You could check your network state, and confirm that firewall rules are correct.
`)
var InvalidConnection = berror.DefineCode(5002004, moduleName, "InvalidConnection", `
The connection is invalid. Please check your connection info, network, firewall.
You could simply uses ping, telnet or write some simple tests to test network.
`)
var DialFailed = berror.DefineCode(5002005, moduleName, "DialFailed", `
When Beego try to dial to remote servers, it failed. Please check your connection info and network state, server state.
`)
var SsdbCacheCurdFailed = berror.DefineCode(5002006, moduleName, "SsdbCacheCurdFailed", `
When you try to use SSDB cache, it failed. There are many cases:
1. servers unavailable;
2. network issue, including network unstable, firewall;
3. connection issue;
4. request are huge and servers spent too much time to process it, got timeout;
`)
var SsdbBadResponse = berror.DefineCode(5002007, moduleName, "SsdbBadResponse", `
The reponse from SSDB server is invalid.
Usually it indicates something wrong on server side.
`)
var ErrKeyExpired = berror.Error(KeyExpired, "the key is expired")
var ErrKeyNotExist = berror.Error(KeyNotExist, "the key isn't exist")

143
client/cache/file.go vendored
View File

@ -30,7 +30,7 @@ import (
"strings"
"time"
"github.com/pkg/errors"
"github.com/beego/beego/v2/core/berror"
)
// FileCacheItem is basic unit of file cache adapter which
@ -73,38 +73,60 @@ func (fc *FileCache) StartAndGC(config string) error {
if err != nil {
return err
}
if _, ok := cfg["CachePath"]; !ok {
cfg["CachePath"] = FileCachePath
}
if _, ok := cfg["FileSuffix"]; !ok {
cfg["FileSuffix"] = FileCacheFileSuffix
}
if _, ok := cfg["DirectoryLevel"]; !ok {
cfg["DirectoryLevel"] = strconv.Itoa(FileCacheDirectoryLevel)
}
if _, ok := cfg["EmbedExpiry"]; !ok {
cfg["EmbedExpiry"] = strconv.FormatInt(int64(FileCacheEmbedExpiry.Seconds()), 10)
}
fc.CachePath = cfg["CachePath"]
fc.FileSuffix = cfg["FileSuffix"]
fc.DirectoryLevel, _ = strconv.Atoi(cfg["DirectoryLevel"])
fc.EmbedExpiry, _ = strconv.Atoi(cfg["EmbedExpiry"])
fc.Init()
return nil
const cpKey = "CachePath"
const fsKey = "FileSuffix"
const dlKey = "DirectoryLevel"
const eeKey = "EmbedExpiry"
if _, ok := cfg[cpKey]; !ok {
cfg[cpKey] = FileCachePath
}
if _, ok := cfg[fsKey]; !ok {
cfg[fsKey] = FileCacheFileSuffix
}
if _, ok := cfg[dlKey]; !ok {
cfg[dlKey] = strconv.Itoa(FileCacheDirectoryLevel)
}
if _, ok := cfg[eeKey]; !ok {
cfg[eeKey] = strconv.FormatInt(int64(FileCacheEmbedExpiry.Seconds()), 10)
}
fc.CachePath = cfg[cpKey]
fc.FileSuffix = cfg[fsKey]
fc.DirectoryLevel, err = strconv.Atoi(cfg[dlKey])
if err != nil {
return berror.Wrapf(err, InvalidFileCacheDirectoryLevelCfg,
"invalid directory level config, please check your input, it must be integer: %s", cfg[dlKey])
}
fc.EmbedExpiry, err = strconv.Atoi(cfg[eeKey])
if err != nil {
return berror.Wrapf(err, InvalidFileCacheEmbedExpiryCfg,
"invalid embed expiry config, please check your input, it must be integer: %s", cfg[eeKey])
}
return fc.Init()
}
// Init makes new a dir for file cache if it does not already exist
func (fc *FileCache) Init() {
if ok, _ := exists(fc.CachePath); !ok { // todo : error handle
_ = os.MkdirAll(fc.CachePath, os.ModePerm) // todo : error handle
func (fc *FileCache) Init() error {
ok, err := exists(fc.CachePath)
if err != nil || ok {
return err
}
err = os.MkdirAll(fc.CachePath, os.ModePerm)
if err != nil {
return berror.Wrapf(err, CreateFileCacheDirFailed,
"could not create directory, please check the config [%s] and file mode.", fc.CachePath)
}
return nil
}
// getCachedFilename returns an md5 encoded file name.
func (fc *FileCache) getCacheFileName(key string) string {
func (fc *FileCache) getCacheFileName(key string) (string, error) {
m := md5.New()
io.WriteString(m, key)
_, _ = io.WriteString(m, key)
keyMd5 := hex.EncodeToString(m.Sum(nil))
cachePath := fc.CachePath
switch fc.DirectoryLevel {
@ -113,18 +135,29 @@ func (fc *FileCache) getCacheFileName(key string) string {
case 1:
cachePath = filepath.Join(cachePath, keyMd5[0:2])
}
if ok, _ := exists(cachePath); !ok { // todo : error handle
_ = os.MkdirAll(cachePath, os.ModePerm) // todo : error handle
ok, err := exists(cachePath)
if err != nil {
return "", err
}
if !ok {
err = os.MkdirAll(cachePath, os.ModePerm)
if err != nil {
return "", berror.Wrapf(err, CreateFileCacheDirFailed,
"could not create the directory: %s", cachePath)
}
}
return filepath.Join(cachePath, fmt.Sprintf("%s%s", keyMd5, fc.FileSuffix))
return filepath.Join(cachePath, fmt.Sprintf("%s%s", keyMd5, fc.FileSuffix)), nil
}
// Get value from file cache.
// if nonexistent or expired return an empty string.
func (fc *FileCache) Get(ctx context.Context, key string) (interface{}, error) {
fileData, err := FileGetContents(fc.getCacheFileName(key))
fn, err := fc.getCacheFileName(key)
if err != nil {
return nil, err
}
fileData, err := FileGetContents(fn)
if err != nil {
return nil, err
}
@ -136,7 +169,7 @@ func (fc *FileCache) Get(ctx context.Context, key string) (interface{}, error) {
}
if to.Expired.Before(time.Now()) {
return nil, errors.New("The key is expired")
return nil, ErrKeyExpired
}
return to.Data, nil
}
@ -159,7 +192,7 @@ func (fc *FileCache) GetMulti(ctx context.Context, keys []string) ([]interface{}
if len(keysErr) == 0 {
return rc, nil
}
return rc, errors.New(strings.Join(keysErr, "; "))
return rc, berror.Error(MultiGetFailed, strings.Join(keysErr, "; "))
}
// Put value into file cache.
@ -179,14 +212,26 @@ func (fc *FileCache) Put(ctx context.Context, key string, val interface{}, timeo
if err != nil {
return err
}
return FilePutContents(fc.getCacheFileName(key), data)
fn, err := fc.getCacheFileName(key)
if err != nil {
return err
}
return FilePutContents(fn, data)
}
// Delete file cache value.
func (fc *FileCache) Delete(ctx context.Context, key string) error {
filename := fc.getCacheFileName(key)
filename, err := fc.getCacheFileName(key)
if err != nil {
return err
}
if ok, _ := exists(filename); ok {
return os.Remove(filename)
err = os.Remove(filename)
if err != nil {
return berror.Wrapf(err, DeleteFileCacheItemFailed,
"can not delete this file cache key-value, key is %s and file name is %s", key, filename)
}
}
return nil
}
@ -224,8 +269,11 @@ func (fc *FileCache) Decr(ctx context.Context, key string) error {
// IsExist checks if value exists.
func (fc *FileCache) IsExist(ctx context.Context, key string) (bool, error) {
ret, _ := exists(fc.getCacheFileName(key))
return ret, nil
fn, err := fc.getCacheFileName(key)
if err != nil {
return false, err
}
return exists(fn)
}
// ClearAll cleans cached files (not implemented)
@ -242,13 +290,19 @@ func exists(path string) (bool, error) {
if os.IsNotExist(err) {
return false, nil
}
return false, err
return false, berror.Wrapf(err, InvalidFileCachePath, "file cache path is invalid: %s", path)
}
// FileGetContents Reads bytes from a file.
// if non-existent, create this file.
func FileGetContents(filename string) (data []byte, e error) {
return ioutil.ReadFile(filename)
func FileGetContents(filename string) ([]byte, error) {
data, err := ioutil.ReadFile(filename)
if err != nil {
return nil, berror.Wrapf(err, ReadFileCacheContentFailed,
"could not read the data from the file: %s, " +
"please confirm that file exist and Beego has the permission to read the content.", filename)
}
return data, nil
}
// FilePutContents puts bytes into a file.
@ -263,16 +317,21 @@ func GobEncode(data interface{}) ([]byte, error) {
enc := gob.NewEncoder(buf)
err := enc.Encode(data)
if err != nil {
return nil, err
return nil, berror.Wrap(err, GobEncodeDataFailed, "could not encode this data")
}
return buf.Bytes(), err
return buf.Bytes(), nil
}
// GobDecode Gob decodes a file cache item.
func GobDecode(data []byte, to *FileCacheItem) error {
buf := bytes.NewBuffer(data)
dec := gob.NewDecoder(buf)
return dec.Decode(&to)
err := dec.Decode(&to)
if err != nil {
return berror.Wrap(err, InvalidGobEncodedData,
"could not decode this data to FileCacheItem. Make sure that the data is encoded by GOB.")
}
return nil
}
func init() {

108
client/cache/file_test.go vendored Normal file
View File

@ -0,0 +1,108 @@
// Copyright 2021 beego
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cache
import (
"context"
"fmt"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
)
func TestFileCacheStartAndGC(t *testing.T) {
fc := NewFileCache().(*FileCache)
err := fc.StartAndGC(`{`)
assert.NotNil(t, err)
err = fc.StartAndGC(`{}`)
assert.Nil(t, err)
assert.Equal(t, fc.CachePath, FileCachePath)
assert.Equal(t, fc.DirectoryLevel, FileCacheDirectoryLevel)
assert.Equal(t, fc.EmbedExpiry, int(FileCacheEmbedExpiry))
assert.Equal(t, fc.FileSuffix, FileCacheFileSuffix)
err = fc.StartAndGC(`{"CachePath":"/cache","FileSuffix":".bin","DirectoryLevel":"2","EmbedExpiry":"0"}`)
// could not create dir
assert.NotNil(t, err)
str := getTestCacheFilePath()
err = fc.StartAndGC(fmt.Sprintf(`{"CachePath":"%s","FileSuffix":".bin","DirectoryLevel":"2","EmbedExpiry":"0"}`, str))
assert.Nil(t, err)
assert.Equal(t, fc.CachePath, str)
assert.Equal(t, fc.DirectoryLevel, 2)
assert.Equal(t, fc.EmbedExpiry, 0)
assert.Equal(t, fc.FileSuffix, ".bin")
err = fc.StartAndGC(fmt.Sprintf(`{"CachePath":"%s","FileSuffix":".bin","DirectoryLevel":"aaa","EmbedExpiry":"0"}`, str))
assert.NotNil(t, err)
err = fc.StartAndGC(fmt.Sprintf(`{"CachePath":"%s","FileSuffix":".bin","DirectoryLevel":"2","EmbedExpiry":"aaa"}`, str))
assert.NotNil(t, err)
}
func TestFileCacheInit(t *testing.T) {
fc := NewFileCache().(*FileCache)
fc.CachePath = "////aaa"
err := fc.Init()
assert.NotNil(t, err)
fc.CachePath = getTestCacheFilePath()
err = fc.Init()
assert.Nil(t, err)
}
func TestFileGetContents(t *testing.T) {
_, err := FileGetContents("/bin/aaa")
assert.NotNil(t, err)
fn := filepath.Join(os.TempDir(), "fileCache.txt")
f, err := os.Create(fn)
assert.Nil(t, err)
_, err = f.WriteString("text")
assert.Nil(t, err)
data, err := FileGetContents(fn)
assert.Nil(t, err)
assert.Equal(t, "text", string(data))
}
func TestGobEncodeDecode(t *testing.T) {
_, err := GobEncode(func() {
fmt.Print("test func")
})
assert.NotNil(t, err)
data, err := GobEncode(&FileCacheItem{
Data: "hello",
})
assert.Nil(t, err)
err = GobDecode([]byte("wrong data"), &FileCacheItem{})
assert.NotNil(t, err)
dci := &FileCacheItem{}
err = GobDecode(data, dci)
assert.Nil(t, err)
assert.Equal(t, "hello", dci.Data)
}
func TestFileCacheDelete(t *testing.T) {
fc := NewFileCache()
err := fc.StartAndGC(`{}`)
assert.Nil(t, err)
err = fc.Delete(context.Background(), "my-key")
assert.Nil(t, err)
}
func getTestCacheFilePath() string {
return filepath.Join(os.TempDir(), "test", "file.txt")
}

View File

@ -32,7 +32,6 @@ package memcache
import (
"context"
"encoding/json"
"errors"
"fmt"
"strings"
"time"
@ -40,6 +39,7 @@ import (
"github.com/bradfitz/gomemcache/memcache"
"github.com/beego/beego/v2/client/cache"
"github.com/beego/beego/v2/core/berror"
)
// Cache Memcache adapter.
@ -55,30 +55,25 @@ func NewMemCache() cache.Cache {
// Get get value from memcache.
func (rc *Cache) Get(ctx context.Context, key string) (interface{}, error) {
if rc.conn == nil {
if err := rc.connectInit(); err != nil {
return nil, err
}
}
if item, err := rc.conn.Get(key); err == nil {
return item.Value, nil
} else {
return nil, err
return nil, berror.Wrapf(err, cache.MemCacheCurdFailed,
"could not read data from memcache, please check your key, network and connection. Root cause: %s",
err.Error())
}
}
// GetMulti gets a value from a key in memcache.
func (rc *Cache) GetMulti(ctx context.Context, keys []string) ([]interface{}, error) {
rv := make([]interface{}, len(keys))
if rc.conn == nil {
if err := rc.connectInit(); err != nil {
return rv, err
}
}
mv, err := rc.conn.GetMulti(keys)
if err != nil {
return rv, err
return rv, berror.Wrapf(err, cache.MemCacheCurdFailed,
"could not read multiple key-values from memcache, " +
"please check your keys, network and connection. Root cause: %s",
err.Error())
}
keysErr := make([]string, 0)
@ -93,78 +88,54 @@ func (rc *Cache) GetMulti(ctx context.Context, keys []string) ([]interface{}, er
if len(keysErr) == 0 {
return rv, nil
}
return rv, fmt.Errorf(strings.Join(keysErr, "; "))
return rv, berror.Error(cache.MultiGetFailed, strings.Join(keysErr, "; "))
}
// Put puts a value into memcache.
func (rc *Cache) Put(ctx context.Context, key string, val interface{}, timeout time.Duration) error {
if rc.conn == nil {
if err := rc.connectInit(); err != nil {
return err
}
}
item := memcache.Item{Key: key, Expiration: int32(timeout / time.Second)}
if v, ok := val.([]byte); ok {
item.Value = v
} else if str, ok := val.(string); ok {
item.Value = []byte(str)
} else {
return errors.New("val only support string and []byte")
return berror.Errorf(cache.InvalidMemCacheValue,
"the value must be string or byte[]. key: %s, value:%v", key, val)
}
return rc.conn.Set(&item)
return berror.Wrapf(rc.conn.Set(&item), cache.MemCacheCurdFailed,
"could not put key-value to memcache, key: %s", key)
}
// Delete deletes a value in memcache.
func (rc *Cache) Delete(ctx context.Context, key string) error {
if rc.conn == nil {
if err := rc.connectInit(); err != nil {
return err
}
}
return rc.conn.Delete(key)
return berror.Wrapf(rc.conn.Delete(key), cache.MemCacheCurdFailed,
"could not delete key-value from memcache, key: %s", key)
}
// Incr increases counter.
func (rc *Cache) Incr(ctx context.Context, key string) error {
if rc.conn == nil {
if err := rc.connectInit(); err != nil {
return err
}
}
_, err := rc.conn.Increment(key, 1)
return err
return berror.Wrapf(err, cache.MemCacheCurdFailed,
"could not increase value for key: %s", key)
}
// Decr decreases counter.
func (rc *Cache) Decr(ctx context.Context, key string) error {
if rc.conn == nil {
if err := rc.connectInit(); err != nil {
return err
}
}
_, err := rc.conn.Decrement(key, 1)
return err
return berror.Wrapf(err, cache.MemCacheCurdFailed,
"could not decrease value for key: %s", key)
}
// IsExist checks if a value exists in memcache.
func (rc *Cache) IsExist(ctx context.Context, key string) (bool, error) {
if rc.conn == nil {
if err := rc.connectInit(); err != nil {
return false, err
}
}
_, err := rc.conn.Get(key)
_, err := rc.Get(ctx, key)
return err == nil, err
}
// ClearAll clears all cache in memcache.
func (rc *Cache) ClearAll(context.Context) error {
if rc.conn == nil {
if err := rc.connectInit(); err != nil {
return err
}
}
return rc.conn.FlushAll()
return berror.Wrap(rc.conn.FlushAll(), cache.MemCacheCurdFailed,
"try to clear all key-value pairs failed")
}
// StartAndGC starts the memcache adapter.
@ -172,21 +143,15 @@ func (rc *Cache) ClearAll(context.Context) error {
// If an error occurs during connecting, an error is returned
func (rc *Cache) StartAndGC(config string) error {
var cf map[string]string
json.Unmarshal([]byte(config), &cf)
if err := json.Unmarshal([]byte(config), &cf); err != nil {
return berror.Wrapf(err, cache.InvalidMemCacheCfg,
"could not unmarshal this config, it must be valid json stringP: %s", config)
}
if _, ok := cf["conn"]; !ok {
return errors.New("config has no conn key")
return berror.Errorf(cache.InvalidMemCacheCfg, `config must contains "conn" field: %s`, config)
}
rc.conninfo = strings.Split(cf["conn"], ";")
if rc.conn == nil {
if err := rc.connectInit(); err != nil {
return err
}
}
return nil
}
// connect to memcache and keep the connection.
func (rc *Cache) connectInit() error {
rc.conn = memcache.New(rc.conninfo...)
return nil
}

View File

@ -19,6 +19,7 @@ import (
"fmt"
"os"
"strconv"
"strings"
"testing"
"time"
@ -106,7 +107,7 @@ func TestMemcacheCache(t *testing.T) {
assert.Equal(t, "author1", string(vv[1].([]byte)))
assert.NotNil(t, err)
assert.Equal(t, "key [astaxie0] error: key not exist", err.Error())
assert.True(t, strings.Contains(err.Error(), "key not exist"))
assert.Nil(t, bm.ClearAll(context.Background()))
// test clear all

View File

@ -17,11 +17,12 @@ package cache
import (
"context"
"encoding/json"
"errors"
"fmt"
"strings"
"sync"
"time"
"github.com/beego/beego/v2/core/berror"
)
var (
@ -29,8 +30,6 @@ var (
DefaultEvery = 60 // 1 minute
)
const keyNotExistMsg = "key not exist"
// MemoryItem stores memory cache item.
type MemoryItem struct {
val interface{}
@ -66,13 +65,14 @@ func NewMemoryCache() Cache {
func (bc *MemoryCache) Get(ctx context.Context, key string) (interface{}, error) {
bc.RLock()
defer bc.RUnlock()
if itm, ok := bc.items[key]; ok {
if itm, ok :=
bc.items[key]; ok {
if itm.isExpire() {
return nil, errors.New("the key is expired")
return nil, ErrKeyExpired
}
return itm.val, nil
}
return nil, errors.New(keyNotExistMsg)
return nil, ErrKeyNotExist
}
// GetMulti gets caches from memory.
@ -93,7 +93,7 @@ func (bc *MemoryCache) GetMulti(ctx context.Context, keys []string) ([]interface
if len(keysErr) == 0 {
return rc, nil
}
return rc, errors.New(strings.Join(keysErr, "; "))
return rc, berror.Error(MultiGetFailed, strings.Join(keysErr, "; "))
}
// Put puts cache into memory.
@ -110,17 +110,11 @@ func (bc *MemoryCache) Put(ctx context.Context, key string, val interface{}, tim
}
// Delete cache in memory.
// If the key is not found, it will not return error
func (bc *MemoryCache) Delete(ctx context.Context, key string) error {
bc.Lock()
defer bc.Unlock()
if _, ok := bc.items[key]; !ok {
return errors.New(keyNotExistMsg)
}
delete(bc.items, key)
if _, ok := bc.items[key]; ok {
return errors.New("delete key error")
}
return nil
}
@ -131,7 +125,7 @@ func (bc *MemoryCache) Incr(ctx context.Context, key string) error {
defer bc.Unlock()
itm, ok := bc.items[key]
if !ok {
return errors.New(keyNotExistMsg)
return ErrKeyNotExist
}
val, err := incr(itm.val)
@ -148,7 +142,7 @@ func (bc *MemoryCache) Decr(ctx context.Context, key string) error {
defer bc.Unlock()
itm, ok := bc.items[key]
if !ok {
return errors.New(keyNotExistMsg)
return ErrKeyNotExist
}
val, err := decr(itm.val)
@ -180,7 +174,9 @@ func (bc *MemoryCache) ClearAll(context.Context) error {
// StartAndGC starts memory cache. Checks expiration in every clock time.
func (bc *MemoryCache) StartAndGC(config string) error {
var cf map[string]int
json.Unmarshal([]byte(config), &cf)
if err := json.Unmarshal([]byte(config), &cf); err != nil {
return berror.Wrapf(err, InvalidMemoryCacheCfg, "invalid config, please check your input: %s", config)
}
if _, ok := cf["interval"]; !ok {
cf = make(map[string]int)
cf["interval"] = DefaultEvery

17
client/cache/module.go vendored Normal file
View File

@ -0,0 +1,17 @@
// Copyright 2021 beego
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cache
const moduleName = "cache"

View File

@ -32,7 +32,6 @@ package redis
import (
"context"
"encoding/json"
"errors"
"fmt"
"strconv"
"strings"
@ -41,6 +40,7 @@ import (
"github.com/gomodule/redigo/redis"
"github.com/beego/beego/v2/client/cache"
"github.com/beego/beego/v2/core/berror"
)
var (
@ -67,15 +67,20 @@ func NewRedisCache() cache.Cache {
}
// Execute the redis commands. args[0] must be the key name
func (rc *Cache) do(commandName string, args ...interface{}) (reply interface{}, err error) {
if len(args) < 1 {
return nil, errors.New("missing required arguments")
}
func (rc *Cache) do(commandName string, args ...interface{}) (interface{}, error) {
args[0] = rc.associate(args[0])
c := rc.p.Get()
defer c.Close()
defer func() {
_ = c.Close()
}()
return c.Do(commandName, args...)
reply, err := c.Do(commandName, args...)
if err != nil {
return nil, berror.Wrapf(err, cache.RedisCacheCurdFailed,
"could not execute this command: %s", commandName)
}
return reply, nil
}
// associate with config key.
@ -95,7 +100,9 @@ func (rc *Cache) Get(ctx context.Context, key string) (interface{}, error) {
// GetMulti gets cache from redis.
func (rc *Cache) GetMulti(ctx context.Context, keys []string) ([]interface{}, error) {
c := rc.p.Get()
defer c.Close()
defer func() {
_ = c.Close()
}()
var args []interface{}
for _, key := range keys {
args = append(args, rc.associate(key))
@ -137,13 +144,16 @@ func (rc *Cache) Decr(ctx context.Context, key string) error {
}
// ClearAll deletes all cache in the redis collection
// Be careful about this method, because it scans all keys and the delete them one by one
func (rc *Cache) ClearAll(context.Context) error {
cachedKeys, err := rc.Scan(rc.key + ":*")
if err != nil {
return err
}
c := rc.p.Get()
defer c.Close()
defer func() {
_ = c.Close()
}()
for _, str := range cachedKeys {
if _, err = c.Do("DEL", str); err != nil {
return err
@ -155,7 +165,9 @@ func (rc *Cache) ClearAll(context.Context) error {
// Scan scans all keys matching a given pattern.
func (rc *Cache) Scan(pattern string) (keys []string, err error) {
c := rc.p.Get()
defer c.Close()
defer func() {
_ = c.Close()
}()
var (
cursor uint64 = 0 // start
result []interface{}
@ -186,13 +198,16 @@ func (rc *Cache) Scan(pattern string) (keys []string, err error) {
// Cached items in redis are stored forever, no garbage collection happens
func (rc *Cache) StartAndGC(config string) error {
var cf map[string]string
json.Unmarshal([]byte(config), &cf)
err := json.Unmarshal([]byte(config), &cf)
if err != nil {
return berror.Wrapf(err, cache.InvalidRedisCacheCfg, "could not unmarshal the config: %s", config)
}
if _, ok := cf["key"]; !ok {
cf["key"] = DefaultKey
}
if _, ok := cf["conn"]; !ok {
return errors.New("config has no conn key")
return berror.Wrapf(err, cache.InvalidRedisCacheCfg, "config missing conn field: %s", config)
}
// Format redis://<password>@<host>:<port>
@ -229,9 +244,16 @@ func (rc *Cache) StartAndGC(config string) error {
rc.connectInit()
c := rc.p.Get()
defer c.Close()
defer func() {
_ = c.Close()
}()
return c.Err()
// test connection
if err = c.Err(); err != nil {
return berror.Wrapf(err, cache.InvalidConnection,
"can not connect to remote redis server, please check the connection info and network state: %s", config)
}
return nil
}
// connect to redis.
@ -239,19 +261,20 @@ func (rc *Cache) connectInit() {
dialFunc := func() (c redis.Conn, err error) {
c, err = redis.Dial("tcp", rc.conninfo)
if err != nil {
return nil, err
return nil, berror.Wrapf(err, cache.DialFailed,
"could not dial to remote server: %s ", rc.conninfo)
}
if rc.password != "" {
if _, err := c.Do("AUTH", rc.password); err != nil {
c.Close()
if _, err = c.Do("AUTH", rc.password); err != nil {
_ = c.Close()
return nil, err
}
}
_, selecterr := c.Do("SELECT", rc.dbNum)
if selecterr != nil {
c.Close()
_ = c.Close()
return nil, selecterr
}
return

View File

@ -3,7 +3,6 @@ package ssdb
import (
"context"
"encoding/json"
"errors"
"fmt"
"strconv"
"strings"
@ -12,6 +11,7 @@ import (
"github.com/ssdb/gossdb/ssdb"
"github.com/beego/beego/v2/client/cache"
"github.com/beego/beego/v2/core/berror"
)
// Cache SSDB adapter
@ -27,31 +27,21 @@ func NewSsdbCache() cache.Cache {
// Get gets a key's value from memcache.
func (rc *Cache) Get(ctx context.Context, key string) (interface{}, error) {
if rc.conn == nil {
if err := rc.connectInit(); err != nil {
return nil, err
}
}
value, err := rc.conn.Get(key)
if err == nil {
return value, nil
}
return nil, err
return nil, berror.Wrapf(err, cache.SsdbCacheCurdFailed, "could not get value, key: %s", key)
}
// GetMulti gets one or keys values from ssdb.
func (rc *Cache) GetMulti(ctx context.Context, keys []string) ([]interface{}, error) {
size := len(keys)
values := make([]interface{}, size)
if rc.conn == nil {
if err := rc.connectInit(); err != nil {
return values, err
}
}
res, err := rc.conn.Do("multi_get", keys)
if err != nil {
return values, err
return values, berror.Wrapf(err, cache.SsdbCacheCurdFailed, "multi_get failed, key: %v", keys)
}
resSize := len(res)
@ -70,7 +60,7 @@ func (rc *Cache) GetMulti(ctx context.Context, keys []string) ([]interface{}, er
}
if len(keysErr) != 0 {
return values, fmt.Errorf(strings.Join(keysErr, "; "))
return values, berror.Error(cache.MultiGetFailed, strings.Join(keysErr, "; "))
}
return values, nil
@ -78,26 +68,16 @@ func (rc *Cache) GetMulti(ctx context.Context, keys []string) ([]interface{}, er
// DelMulti deletes one or more keys from memcache
func (rc *Cache) DelMulti(keys []string) error {
if rc.conn == nil {
if err := rc.connectInit(); err != nil {
return err
}
}
_, err := rc.conn.Do("multi_del", keys)
return err
return berror.Wrapf(err, cache.SsdbCacheCurdFailed, "multi_del failed: %v", keys)
}
// Put puts value into memcache.
// value: must be of type string
func (rc *Cache) Put(ctx context.Context, key string, val interface{}, timeout time.Duration) error {
if rc.conn == nil {
if err := rc.connectInit(); err != nil {
return err
}
}
v, ok := val.(string)
if !ok {
return errors.New("value must string")
return berror.Errorf(cache.InvalidSsdbCacheValue, "value must be string: %v", val)
}
var resp []string
var err error
@ -108,57 +88,37 @@ func (rc *Cache) Put(ctx context.Context, key string, val interface{}, timeout t
resp, err = rc.conn.Do("setx", key, v, ttl)
}
if err != nil {
return err
return berror.Wrapf(err, cache.SsdbCacheCurdFailed, "set or setx failed, key: %s", key)
}
if len(resp) == 2 && resp[0] == "ok" {
return nil
}
return errors.New("bad response")
return berror.Errorf(cache.SsdbBadResponse, "the response from SSDB server is invalid: %v", resp)
}
// Delete deletes a value in memcache.
func (rc *Cache) Delete(ctx context.Context, key string) error {
if rc.conn == nil {
if err := rc.connectInit(); err != nil {
return err
}
}
_, err := rc.conn.Del(key)
return err
return berror.Wrapf(err, cache.SsdbCacheCurdFailed, "del failed: %s", key)
}
// Incr increases a key's counter.
func (rc *Cache) Incr(ctx context.Context, key string) error {
if rc.conn == nil {
if err := rc.connectInit(); err != nil {
return err
}
}
_, err := rc.conn.Do("incr", key, 1)
return err
return berror.Wrapf(err, cache.SsdbCacheCurdFailed, "increase failed: %s", key)
}
// Decr decrements a key's counter.
func (rc *Cache) Decr(ctx context.Context, key string) error {
if rc.conn == nil {
if err := rc.connectInit(); err != nil {
return err
}
}
_, err := rc.conn.Do("incr", key, -1)
return err
return berror.Wrapf(err, cache.SsdbCacheCurdFailed, "decrease failed: %s", key)
}
// IsExist checks if a key exists in memcache.
func (rc *Cache) IsExist(ctx context.Context, key string) (bool, error) {
if rc.conn == nil {
if err := rc.connectInit(); err != nil {
return false, err
}
}
resp, err := rc.conn.Do("exists", key)
if err != nil {
return false, err
return false, berror.Wrapf(err, cache.SsdbCacheCurdFailed, "exists failed: %s", key)
}
if len(resp) == 2 && resp[1] == "1" {
return true, nil
@ -167,13 +127,9 @@ func (rc *Cache) IsExist(ctx context.Context, key string) (bool, error) {
}
// ClearAll clears all cached items in memcache.
// ClearAll clears all cached items in ssdb.
// If there are many keys, this method may spent much time.
func (rc *Cache) ClearAll(context.Context) error {
if rc.conn == nil {
if err := rc.connectInit(); err != nil {
return err
}
}
keyStart, keyEnd, limit := "", "", 50
resp, err := rc.Scan(keyStart, keyEnd, limit)
for err == nil {
@ -187,21 +143,16 @@ func (rc *Cache) ClearAll(context.Context) error {
}
_, e := rc.conn.Do("multi_del", keys)
if e != nil {
return e
return berror.Wrapf(e, cache.SsdbCacheCurdFailed, "multi_del failed: %v", keys)
}
keyStart = resp[size-2]
resp, err = rc.Scan(keyStart, keyEnd, limit)
}
return err
return berror.Wrap(err, cache.SsdbCacheCurdFailed, "scan failed")
}
// Scan key all cached in ssdb.
func (rc *Cache) Scan(keyStart string, keyEnd string, limit int) ([]string, error) {
if rc.conn == nil {
if err := rc.connectInit(); err != nil {
return nil, err
}
}
resp, err := rc.conn.Do("scan", keyStart, keyEnd, limit)
if err != nil {
return nil, err
@ -214,30 +165,36 @@ func (rc *Cache) Scan(keyStart string, keyEnd string, limit int) ([]string, erro
// If an error occurs during connection, an error is returned
func (rc *Cache) StartAndGC(config string) error {
var cf map[string]string
json.Unmarshal([]byte(config), &cf)
err := json.Unmarshal([]byte(config), &cf)
if err != nil {
return berror.Wrapf(err, cache.InvalidSsdbCacheCfg,
"unmarshal this config failed, it must be a valid json string: %s", config)
}
if _, ok := cf["conn"]; !ok {
return errors.New("config has no conn key")
return berror.Wrapf(err, cache.InvalidSsdbCacheCfg,
"Missing conn field: %s", config)
}
rc.conninfo = strings.Split(cf["conn"], ";")
if rc.conn == nil {
if err := rc.connectInit(); err != nil {
return err
}
}
return nil
return rc.connectInit()
}
// connect to memcache and keep the connection.
func (rc *Cache) connectInit() error {
conninfoArray := strings.Split(rc.conninfo[0], ":")
if len(conninfoArray) < 2 {
return berror.Errorf(cache.InvalidSsdbCacheCfg, "The value of conn should be host:port: %s", rc.conninfo[0])
}
host := conninfoArray[0]
port, e := strconv.Atoi(conninfoArray[1])
if e != nil {
return e
return berror.Errorf(cache.InvalidSsdbCacheCfg, "Port is invalid. It must be integer, %s", rc.conninfo[0])
}
var err error
rc.conn, err = ssdb.Connect(host, port)
return err
if rc.conn, err = ssdb.Connect(host, port); err != nil {
return berror.Wrapf(err, cache.InvalidConnection,
"could not connect to SSDB, please check your connection info, network and firewall: %s", rc.conninfo[0])
}
return nil
}
func init() {

View File

@ -5,6 +5,7 @@ import (
"fmt"
"os"
"strconv"
"strings"
"testing"
"time"
@ -89,7 +90,7 @@ func TestSsdbcacheCache(t *testing.T) {
assert.Nil(t, vv[1])
assert.NotNil(t, err)
assert.Equal(t, "key [ssdb11] error: key not exist", err.Error())
assert.True(t, strings.Contains(err.Error(), "key not exist"))
// test clear all done
assert.Nil(t, ssdb.ClearAll(context.Background()))