Compare commits
3 Commits
b6b0a7c0ca
...
5f831c7005
Author | SHA1 | Date | |
---|---|---|---|
5f831c7005 | |||
c6e6430808 | |||
8b29ccee17 |
18
gcache/cache.go
Normal file
18
gcache/cache.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
//
|
||||||
|
// cache.go
|
||||||
|
// Copyright (C) 2023 tiglog <me@tiglog.com>
|
||||||
|
//
|
||||||
|
// Distributed under terms of the MIT license.
|
||||||
|
//
|
||||||
|
|
||||||
|
package gcache
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type ICache interface {
|
||||||
|
Set(key string, data []byte, expires time.Duration) error
|
||||||
|
Get(key string) ([]byte, error)
|
||||||
|
Delete(key string) error
|
||||||
|
Flush() error
|
||||||
|
Has(key string) bool
|
||||||
|
}
|
51
gcache/cache_test.go
Normal file
51
gcache/cache_test.go
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
//
|
||||||
|
// cache_test.go
|
||||||
|
// Copyright (C) 2023 tiglog <me@tiglog.com>
|
||||||
|
//
|
||||||
|
// Distributed under terms of the MIT license.
|
||||||
|
//
|
||||||
|
|
||||||
|
package gcache_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.hexq.cn/tiglog/golib/gcache"
|
||||||
|
"git.hexq.cn/tiglog/golib/gtest"
|
||||||
|
"github.com/redis/go-redis/v9"
|
||||||
|
)
|
||||||
|
|
||||||
|
var sample_name = "golib gcache"
|
||||||
|
|
||||||
|
func TestRedisCache(t *testing.T) {
|
||||||
|
|
||||||
|
REDIS_URL := os.Getenv("REDIS_URL")
|
||||||
|
// fmt.Println(REDIS_URL)
|
||||||
|
redisOpt, err := redis.ParseURL(REDIS_URL)
|
||||||
|
gtest.Nil(t, err)
|
||||||
|
var store gcache.ICache = gcache.NewRedisStore(gcache.RCClientOptions(redisOpt))
|
||||||
|
|
||||||
|
var sampleData = []byte(sample_name)
|
||||||
|
err = store.Set("name", sampleData, time.Minute*1)
|
||||||
|
gtest.Nil(t, err)
|
||||||
|
|
||||||
|
r1, err := store.Get("name")
|
||||||
|
gtest.Nil(t, err)
|
||||||
|
gtest.Equal(t, string(r1), sample_name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMemoryCache(t *testing.T) {
|
||||||
|
store := gcache.NewMemoryStore()
|
||||||
|
|
||||||
|
var err error
|
||||||
|
var sampleData = []byte(sample_name)
|
||||||
|
err = store.Set("name", sampleData, time.Minute*1)
|
||||||
|
gtest.Nil(t, err)
|
||||||
|
|
||||||
|
r1, err := store.Get("name")
|
||||||
|
gtest.Nil(t, err)
|
||||||
|
gtest.Equal(t, string(r1), sample_name)
|
||||||
|
|
||||||
|
}
|
@ -1,21 +0,0 @@
|
|||||||
//
|
|
||||||
// config.go
|
|
||||||
// Copyright (C) 2023 tiglog <me@tiglog.com>
|
|
||||||
//
|
|
||||||
// Distributed under terms of the MIT license.
|
|
||||||
//
|
|
||||||
|
|
||||||
package gcache
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
shards int
|
|
||||||
expiredCallback ExpiredCallback
|
|
||||||
hash IHash
|
|
||||||
clearInterval time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewConfig() *Config {
|
|
||||||
return &Config{shards: 1024, hash: newDefaultHash(), clearInterval: 1 * time.Second}
|
|
||||||
}
|
|
214
gcache/fs_adapter.go
Normal file
214
gcache/fs_adapter.go
Normal file
@ -0,0 +1,214 @@
|
|||||||
|
//
|
||||||
|
// fs_adapter.go
|
||||||
|
// Copyright (C) 2023 tiglog <me@tiglog.com>
|
||||||
|
//
|
||||||
|
// Distributed under terms of the MIT license.
|
||||||
|
//
|
||||||
|
|
||||||
|
package gcache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/md5"
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Option is an optional type
|
||||||
|
type FCOption func(fs *FSStore)
|
||||||
|
|
||||||
|
func BaseDirectory(base string) FCOption {
|
||||||
|
return func(fs *FSStore) {
|
||||||
|
fs.baseDir = base
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func FCSerializer(serializer Serializer) FCOption {
|
||||||
|
return func(fs *FSStore) {
|
||||||
|
fs.b = serializer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func FCCacheKeyGenerator(fn KeyFunc) FCOption {
|
||||||
|
return func(fs *FSStore) {
|
||||||
|
fs.keyFn = fn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultFilePerm os.FileMode = 0644
|
||||||
|
defaultDirectoryFilePerm = 0755
|
||||||
|
)
|
||||||
|
|
||||||
|
func FilePathKeyFunc(s string) string {
|
||||||
|
|
||||||
|
hashSum := md5.Sum([]byte(s))
|
||||||
|
hashSumAsString := hex.EncodeToString(hashSum[:])
|
||||||
|
|
||||||
|
return filepath.Join(string(hashSumAsString[0:2]),
|
||||||
|
string(hashSumAsString[2:4]),
|
||||||
|
string(hashSumAsString[4:6]), hashSumAsString)
|
||||||
|
}
|
||||||
|
|
||||||
|
func createDirectory(dir string) error {
|
||||||
|
return os.MkdirAll(dir, defaultDirectoryFilePerm)
|
||||||
|
}
|
||||||
|
|
||||||
|
type FSStore struct {
|
||||||
|
baseDir string
|
||||||
|
b Serializer
|
||||||
|
keyFn KeyFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFSStore(baseDir string, opts ...FCOption) (*FSStore, error) {
|
||||||
|
|
||||||
|
_, err := os.Stat(baseDir)
|
||||||
|
|
||||||
|
if err != nil { //Directory does not exist..Let's create it
|
||||||
|
if err := createDirectory(baseDir); err != nil {
|
||||||
|
panic(fmt.Errorf("Base directory could not be created : %s", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
opts = append(opts, BaseDirectory(baseDir))
|
||||||
|
|
||||||
|
store := &FSStore{}
|
||||||
|
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(store)
|
||||||
|
}
|
||||||
|
|
||||||
|
if store.b == nil {
|
||||||
|
store.b = NewCacheSerializer()
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(strings.TrimSpace(store.baseDir)) == 0 {
|
||||||
|
return nil, errors.New("onecache : base directory not provided")
|
||||||
|
}
|
||||||
|
|
||||||
|
if store.keyFn == nil {
|
||||||
|
store.keyFn = FilePathKeyFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
return store, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *FSStore) Set(key string, data []byte, expiresAt time.Duration) error {
|
||||||
|
|
||||||
|
path := fs.filePathFor(key)
|
||||||
|
|
||||||
|
if err := createDirectory(filepath.Dir(path)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
i := &Item{ExpiresAt: time.Now().Add(expiresAt), Data: data}
|
||||||
|
|
||||||
|
b, err := fs.b.Serialize(i)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return writeFile(path, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *FSStore) Get(key string) ([]byte, error) {
|
||||||
|
|
||||||
|
var b = new(bytes.Buffer)
|
||||||
|
|
||||||
|
f, err := os.OpenFile(fs.filePathFor(key), os.O_RDONLY, 0644)
|
||||||
|
if err != nil {
|
||||||
|
pe, ok := err.(*os.PathError)
|
||||||
|
if !ok {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if pe.Err == syscall.ENOENT && pe.Op == "open" {
|
||||||
|
return nil, ErrCacheMiss
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := io.Copy(b, f); err != nil {
|
||||||
|
f.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
f.Close()
|
||||||
|
|
||||||
|
i := new(Item)
|
||||||
|
|
||||||
|
if err := fs.b.DeSerialize(b.Bytes(), i); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.IsExpired() {
|
||||||
|
fs.Delete(key)
|
||||||
|
return nil, ErrCacheMiss
|
||||||
|
}
|
||||||
|
|
||||||
|
return i.Data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *FSStore) Delete(key string) error {
|
||||||
|
return os.RemoveAll(fs.filePathFor(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *FSStore) Flush() error {
|
||||||
|
return os.RemoveAll(fs.baseDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *FSStore) GC() {
|
||||||
|
|
||||||
|
filepath.Walk(
|
||||||
|
fs.baseDir,
|
||||||
|
func(path string, finfo os.FileInfo, err error) error {
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if finfo.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
currentItem := new(Item)
|
||||||
|
|
||||||
|
byt, err := os.ReadFile(path)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = fs.b.DeSerialize(byt, currentItem); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if currentItem.IsExpired() {
|
||||||
|
if err := os.Remove(path); !os.IsExist(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *FSStore) Has(key string) bool {
|
||||||
|
_, err := os.Stat(fs.filePathFor(key))
|
||||||
|
return !os.IsNotExist(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *FSStore) filePathFor(key string) string {
|
||||||
|
return filepath.Join(fs.baseDir, fs.keyFn(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeFile(path string, b []byte) error {
|
||||||
|
return os.WriteFile(path, b, defaultFilePerm)
|
||||||
|
}
|
@ -1,42 +0,0 @@
|
|||||||
//
|
|
||||||
// hash.go
|
|
||||||
// Copyright (C) 2023 tiglog <me@tiglog.com>
|
|
||||||
//
|
|
||||||
// Distributed under terms of the MIT license.
|
|
||||||
//
|
|
||||||
|
|
||||||
package gcache
|
|
||||||
|
|
||||||
// IHash is responsible for generating unsigned, 64-bit hash of provided string. IHash should minimize collisions
|
|
||||||
// (generating same hash for different strings) and while performance is also important fast functions are preferable (i.e.
|
|
||||||
// you can use FarmHash family).
|
|
||||||
type IHash interface {
|
|
||||||
Sum64(string) uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
// newDefaultHash returns a new 64-bit FNV-1a IHash which makes no memory allocations.
|
|
||||||
// Its Sum64 method will lay the value out in big-endian byte order.
|
|
||||||
// See https://en.wikipedia.org/wiki/Fowler–Noll–Vo_hash_function
|
|
||||||
func newDefaultHash() IHash {
|
|
||||||
return fnv64a{}
|
|
||||||
}
|
|
||||||
|
|
||||||
type fnv64a struct{}
|
|
||||||
|
|
||||||
const (
|
|
||||||
// offset64 FNVa offset basis. See https://en.wikipedia.org/wiki/Fowler–Noll–Vo_hash_function#FNV-1a_hash
|
|
||||||
offset64 = 14695981039346656037
|
|
||||||
// prime64 FNVa prime value. See https://en.wikipedia.org/wiki/Fowler–Noll–Vo_hash_function#FNV-1a_hash
|
|
||||||
prime64 = 1099511628211
|
|
||||||
)
|
|
||||||
|
|
||||||
// Sum64 gets the string and returns its uint64 hash value.
|
|
||||||
func (f fnv64a) Sum64(key string) uint64 {
|
|
||||||
var hash uint64 = offset64
|
|
||||||
for i := 0; i < len(key); i++ {
|
|
||||||
hash ^= uint64(key[i])
|
|
||||||
hash *= prime64
|
|
||||||
}
|
|
||||||
|
|
||||||
return hash
|
|
||||||
}
|
|
122
gcache/icache.go
122
gcache/icache.go
@ -1,122 +0,0 @@
|
|||||||
//
|
|
||||||
// icache.go
|
|
||||||
// Copyright (C) 2023 tiglog <me@tiglog.com>
|
|
||||||
//
|
|
||||||
// Distributed under terms of the MIT license.
|
|
||||||
//
|
|
||||||
|
|
||||||
package gcache
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
// ExpiredCallback Callback the function when the key-value pair expires
|
|
||||||
// Note that it is executed after expiration
|
|
||||||
type ExpiredCallback func(k string, v interface{}) error
|
|
||||||
|
|
||||||
type ICache interface {
|
|
||||||
//Set key to hold the string value. If key already holds a value, it is overwritten, regardless of its type.
|
|
||||||
//Any previous time to live associated with the key is discarded on successful SET operation.
|
|
||||||
//Example:
|
|
||||||
//c.Set("demo", 1)
|
|
||||||
//c.Set("demo", 1, WithEx(10*time.Second))
|
|
||||||
//c.Set("demo", 1, WithEx(10*time.Second), WithNx())
|
|
||||||
Set(k string, v interface{}, opts ...SetIOption) bool
|
|
||||||
//Get the value of key.
|
|
||||||
//If the key does not exist the special value nil,false is returned.
|
|
||||||
//Example:
|
|
||||||
//c.Get("demo") //nil, false
|
|
||||||
//c.Set("demo", "value")
|
|
||||||
//c.Get("demo") //"value", true
|
|
||||||
Get(k string) (interface{}, bool)
|
|
||||||
//GetSet Atomically sets key to value and returns the old value stored at key.
|
|
||||||
//Returns nil,false when key not exists.
|
|
||||||
//Example:
|
|
||||||
//c.GetSet("demo", 1) //nil,false
|
|
||||||
//c.GetSet("demo", 2) //1,true
|
|
||||||
GetSet(k string, v interface{}, opts ...SetIOption) (interface{}, bool)
|
|
||||||
//GetDel Get the value of key and delete the key.
|
|
||||||
//This command is similar to GET, except for the fact that it also deletes the key on success.
|
|
||||||
//Example:
|
|
||||||
//c.Set("demo", "value")
|
|
||||||
//c.GetDel("demo") //"value", true
|
|
||||||
//c.GetDel("demo") //nil, false
|
|
||||||
GetDel(k string) (interface{}, bool)
|
|
||||||
//Del Removes the specified keys. A key is ignored if it does not exist.
|
|
||||||
//Return the number of keys that were removed.
|
|
||||||
//Example:
|
|
||||||
//c.Set("demo1", "1")
|
|
||||||
//c.Set("demo2", "1")
|
|
||||||
//c.Del("demo1", "demo2", "demo3") //2
|
|
||||||
Del(keys ...string) int
|
|
||||||
//DelExpired Only delete when key expires
|
|
||||||
//Example:
|
|
||||||
//c.Set("demo1", "1")
|
|
||||||
//c.Set("demo2", "1", WithEx(1*time.Second))
|
|
||||||
//time.Sleep(1*time.Second)
|
|
||||||
//c.DelExpired("demo1", "demo2") //true
|
|
||||||
DelExpired(k string) bool
|
|
||||||
//Exists Returns if key exists.
|
|
||||||
//Return the number of exists keys.
|
|
||||||
//Example:
|
|
||||||
//c.Set("demo1", "1")
|
|
||||||
//c.Set("demo2", "1")
|
|
||||||
//c.Exists("demo1", "demo2", "demo3") //2
|
|
||||||
Exists(keys ...string) bool
|
|
||||||
//Expire Set a timeout on key.
|
|
||||||
//After the timeout has expired, the key will automatically be deleted.
|
|
||||||
//Return false if the key not exist.
|
|
||||||
//Example:
|
|
||||||
//c.Expire("demo", 1*time.Second) // false
|
|
||||||
//c.Set("demo", "1")
|
|
||||||
//c.Expire("demo", 1*time.Second) // true
|
|
||||||
Expire(k string, d time.Duration) bool
|
|
||||||
//ExpireAt has the same effect and semantic as Expire, but instead of specifying the number of seconds representing the TTL (time to live),
|
|
||||||
//it takes an absolute Unix Time (seconds since January 1, 1970). A Time in the past will delete the key immediately.
|
|
||||||
//Return false if the key not exist.
|
|
||||||
//Example:
|
|
||||||
//c.ExpireAt("demo", time.Now().Add(10*time.Second)) // false
|
|
||||||
//c.Set("demo", "1")
|
|
||||||
//c.ExpireAt("demo", time.Now().Add(10*time.Second)) // true
|
|
||||||
ExpireAt(k string, t time.Time) bool
|
|
||||||
//Persist Remove the existing timeout on key.
|
|
||||||
//Return false if the key not exist.
|
|
||||||
//Example:
|
|
||||||
//c.Persist("demo") // false
|
|
||||||
//c.Set("demo", "1")
|
|
||||||
//c.Persist("demo") // true
|
|
||||||
Persist(k string) bool
|
|
||||||
//Ttl Returns the remaining time to live of a key that has a timeout.
|
|
||||||
//Returns 0,false if the key does not exist or if the key exist but has no associated expire.
|
|
||||||
//Example:
|
|
||||||
//c.Set("demo", "1")
|
|
||||||
//c.Ttl("demo") // 0,false
|
|
||||||
//c.Set("demo", "1", WithEx(10*time.Second))
|
|
||||||
//c.Ttl("demo") // 10*time.Second,true
|
|
||||||
Ttl(k string) (time.Duration, bool)
|
|
||||||
}
|
|
||||||
|
|
||||||
type IItem interface {
|
|
||||||
Expired() bool
|
|
||||||
CanExpire() bool
|
|
||||||
SetExpireAt(t time.Time)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Item struct {
|
|
||||||
v interface{}
|
|
||||||
expire time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Item) Expired() bool {
|
|
||||||
if !i.CanExpire() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return time.Now().After(i.expire)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Item) CanExpire() bool {
|
|
||||||
return !i.expire.IsZero()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Item) SetExpireAt(t time.Time) {
|
|
||||||
i.expire = t
|
|
||||||
}
|
|
255
gcache/memory.go
255
gcache/memory.go
@ -1,255 +0,0 @@
|
|||||||
//
|
|
||||||
// memory.go
|
|
||||||
// Copyright (C) 2023 tiglog <me@tiglog.com>
|
|
||||||
//
|
|
||||||
// Distributed under terms of the MIT license.
|
|
||||||
//
|
|
||||||
|
|
||||||
package gcache
|
|
||||||
|
|
||||||
import (
|
|
||||||
"runtime"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewMemCache(opts ...ICacheOption) ICache {
|
|
||||||
conf := NewConfig()
|
|
||||||
for _, opt := range opts {
|
|
||||||
opt(conf)
|
|
||||||
}
|
|
||||||
|
|
||||||
c := &memCache{
|
|
||||||
shards: make([]*memCacheShard, conf.shards),
|
|
||||||
closed: make(chan struct{}),
|
|
||||||
shardMask: uint64(conf.shards - 1),
|
|
||||||
config: conf,
|
|
||||||
hash: conf.hash,
|
|
||||||
}
|
|
||||||
for i := 0; i < len(c.shards); i++ {
|
|
||||||
c.shards[i] = newMemCacheShard(conf)
|
|
||||||
}
|
|
||||||
if conf.clearInterval > 0 {
|
|
||||||
go func() {
|
|
||||||
ticker := time.NewTicker(conf.clearInterval)
|
|
||||||
defer ticker.Stop()
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ticker.C:
|
|
||||||
for _, shard := range c.shards {
|
|
||||||
shard.checkExpire()
|
|
||||||
}
|
|
||||||
case <-c.closed:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
cache := &MemCache{c}
|
|
||||||
// Associated finalizer function with obj.
|
|
||||||
// When the obj is unreachable, close the obj.
|
|
||||||
runtime.SetFinalizer(cache, func(cache *MemCache) { close(cache.closed) })
|
|
||||||
return cache
|
|
||||||
}
|
|
||||||
|
|
||||||
type MemCache struct {
|
|
||||||
*memCache
|
|
||||||
}
|
|
||||||
|
|
||||||
type memCache struct {
|
|
||||||
shards []*memCacheShard
|
|
||||||
hash IHash
|
|
||||||
shardMask uint64
|
|
||||||
config *Config
|
|
||||||
closed chan struct{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *memCache) Set(k string, v interface{}, opts ...SetIOption) bool {
|
|
||||||
item := Item{v: v}
|
|
||||||
for _, opt := range opts {
|
|
||||||
if pass := opt(c, k, &item); !pass {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
hashedKey := c.hash.Sum64(k)
|
|
||||||
shard := c.getShard(hashedKey)
|
|
||||||
shard.set(k, &item)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *memCache) Get(k string) (interface{}, bool) {
|
|
||||||
hashedKey := c.hash.Sum64(k)
|
|
||||||
shard := c.getShard(hashedKey)
|
|
||||||
return shard.get(k)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *memCache) GetSet(k string, v interface{}, opts ...SetIOption) (interface{}, bool) {
|
|
||||||
defer c.Set(k, v, opts...)
|
|
||||||
return c.Get(k)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *memCache) GetDel(k string) (interface{}, bool) {
|
|
||||||
defer c.Del(k)
|
|
||||||
return c.Get(k)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *memCache) Del(ks ...string) int {
|
|
||||||
var count int
|
|
||||||
for _, k := range ks {
|
|
||||||
hashedKey := c.hash.Sum64(k)
|
|
||||||
shard := c.getShard(hashedKey)
|
|
||||||
count += shard.del(k)
|
|
||||||
}
|
|
||||||
return count
|
|
||||||
}
|
|
||||||
|
|
||||||
// DelExpired Only delete when key expires
|
|
||||||
func (c *memCache) DelExpired(k string) bool {
|
|
||||||
hashedKey := c.hash.Sum64(k)
|
|
||||||
shard := c.getShard(hashedKey)
|
|
||||||
return shard.delExpired(k)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *memCache) Exists(ks ...string) bool {
|
|
||||||
for _, k := range ks {
|
|
||||||
if _, found := c.Get(k); !found {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *memCache) Expire(k string, d time.Duration) bool {
|
|
||||||
v, found := c.Get(k)
|
|
||||||
if !found {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return c.Set(k, v, WithEx(d))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *memCache) ExpireAt(k string, t time.Time) bool {
|
|
||||||
v, found := c.Get(k)
|
|
||||||
if !found {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return c.Set(k, v, WithExAt(t))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *memCache) Persist(k string) bool {
|
|
||||||
v, found := c.Get(k)
|
|
||||||
if !found {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return c.Set(k, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *memCache) Ttl(k string) (time.Duration, bool) {
|
|
||||||
hashedKey := c.hash.Sum64(k)
|
|
||||||
shard := c.getShard(hashedKey)
|
|
||||||
return shard.ttl(k)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *memCache) getShard(hashedKey uint64) (shard *memCacheShard) {
|
|
||||||
return c.shards[hashedKey&c.shardMask]
|
|
||||||
}
|
|
||||||
|
|
||||||
//////////////
|
|
||||||
// shard //
|
|
||||||
//////////////
|
|
||||||
|
|
||||||
type memCacheShard struct {
|
|
||||||
hashmap map[string]Item
|
|
||||||
lock sync.RWMutex
|
|
||||||
expiredCallback ExpiredCallback
|
|
||||||
}
|
|
||||||
|
|
||||||
func newMemCacheShard(conf *Config) *memCacheShard {
|
|
||||||
return &memCacheShard{expiredCallback: conf.expiredCallback, hashmap: map[string]Item{}}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *memCacheShard) set(k string, item *Item) {
|
|
||||||
c.lock.Lock()
|
|
||||||
c.hashmap[k] = *item
|
|
||||||
c.lock.Unlock()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *memCacheShard) get(k string) (interface{}, bool) {
|
|
||||||
c.lock.RLock()
|
|
||||||
item, exist := c.hashmap[k]
|
|
||||||
c.lock.RUnlock()
|
|
||||||
if !exist {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
if !item.Expired() {
|
|
||||||
return item.v, true
|
|
||||||
}
|
|
||||||
if c.delExpired(k) {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
return c.get(k)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *memCacheShard) getSet(k string, item *Item) (interface{}, bool) {
|
|
||||||
defer c.set(k, item)
|
|
||||||
return c.get(k)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *memCacheShard) getDel(k string) (interface{}, bool) {
|
|
||||||
defer c.del(k)
|
|
||||||
return c.get(k)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *memCacheShard) del(k string) int {
|
|
||||||
var count int
|
|
||||||
c.lock.Lock()
|
|
||||||
v, found := c.hashmap[k]
|
|
||||||
if found {
|
|
||||||
delete(c.hashmap, k)
|
|
||||||
if !v.Expired() {
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
c.lock.Unlock()
|
|
||||||
return count
|
|
||||||
}
|
|
||||||
|
|
||||||
// delExpired Only delete when key expires
|
|
||||||
func (c *memCacheShard) delExpired(k string) bool {
|
|
||||||
c.lock.Lock()
|
|
||||||
item, found := c.hashmap[k]
|
|
||||||
if !found || !item.Expired() {
|
|
||||||
c.lock.Unlock()
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
delete(c.hashmap, k)
|
|
||||||
c.lock.Unlock()
|
|
||||||
if c.expiredCallback != nil {
|
|
||||||
_ = c.expiredCallback(k, item.v)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *memCacheShard) ttl(k string) (time.Duration, bool) {
|
|
||||||
c.lock.RLock()
|
|
||||||
v, found := c.hashmap[k]
|
|
||||||
c.lock.RUnlock()
|
|
||||||
if !found || !v.CanExpire() || v.Expired() {
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
return v.expire.Sub(time.Now()), true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *memCacheShard) checkExpire() {
|
|
||||||
var expiredKeys []string
|
|
||||||
c.lock.RLock()
|
|
||||||
for k, item := range c.hashmap {
|
|
||||||
if item.Expired() {
|
|
||||||
expiredKeys = append(expiredKeys, k)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
c.lock.RUnlock()
|
|
||||||
for _, k := range expiredKeys {
|
|
||||||
c.delExpired(k)
|
|
||||||
}
|
|
||||||
}
|
|
155
gcache/memory_adapter.go
Normal file
155
gcache/memory_adapter.go
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
//
|
||||||
|
// memory_adapter.go
|
||||||
|
// Copyright (C) 2023 tiglog <me@tiglog.com>
|
||||||
|
//
|
||||||
|
// Distributed under terms of the MIT license.
|
||||||
|
//
|
||||||
|
|
||||||
|
package gcache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Option defines options for creating a memory store
|
||||||
|
type MCOption func(i *InMemoryStore)
|
||||||
|
|
||||||
|
// BufferSize configures the store to allow a maximum of n
|
||||||
|
func BufferSize(n int) MCOption {
|
||||||
|
return func(i *InMemoryStore) {
|
||||||
|
i.bufferSize = n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// KeyFunc allows for dynamic generation of cache keys
|
||||||
|
func MCCacheKeyGenerator(fn KeyFunc) MCOption {
|
||||||
|
return func(i *InMemoryStore) {
|
||||||
|
i.keyfn = fn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a configured in memory store
|
||||||
|
func NewMemoryStore(opts ...MCOption) *InMemoryStore {
|
||||||
|
i := &InMemoryStore{}
|
||||||
|
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.keyfn == nil {
|
||||||
|
i.keyfn = DefaultKeyFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.data == nil {
|
||||||
|
if i.bufferSize == 0 {
|
||||||
|
i.bufferSize = 100
|
||||||
|
}
|
||||||
|
|
||||||
|
i.data = make(map[string]*Item, i.bufferSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
// Represents an in-memory store
|
||||||
|
type InMemoryStore struct {
|
||||||
|
lock sync.RWMutex
|
||||||
|
data map[string]*Item
|
||||||
|
|
||||||
|
bufferSize int
|
||||||
|
keyfn KeyFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *InMemoryStore) Set(key string, data []byte, expires time.Duration) error {
|
||||||
|
i.lock.Lock()
|
||||||
|
|
||||||
|
i.data[i.keyfn(key)] = &Item{
|
||||||
|
ExpiresAt: time.Now().Add(expires),
|
||||||
|
Data: copyData(data),
|
||||||
|
}
|
||||||
|
|
||||||
|
i.lock.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *InMemoryStore) Get(key string) ([]byte, error) {
|
||||||
|
i.lock.RLock()
|
||||||
|
|
||||||
|
item := i.data[i.keyfn(key)]
|
||||||
|
if item == nil {
|
||||||
|
i.lock.RUnlock()
|
||||||
|
return nil, ErrCacheMiss
|
||||||
|
}
|
||||||
|
|
||||||
|
if item.IsExpired() {
|
||||||
|
i.lock.RUnlock()
|
||||||
|
i.Delete(key)
|
||||||
|
return nil, ErrCacheMiss
|
||||||
|
}
|
||||||
|
|
||||||
|
i.lock.RUnlock()
|
||||||
|
return copyData(item.Data), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *InMemoryStore) Delete(key string) error {
|
||||||
|
i.lock.RLock()
|
||||||
|
|
||||||
|
_, ok := i.data[i.keyfn(key)]
|
||||||
|
if !ok {
|
||||||
|
i.lock.RUnlock()
|
||||||
|
return ErrCacheMiss
|
||||||
|
}
|
||||||
|
|
||||||
|
i.lock.RUnlock()
|
||||||
|
|
||||||
|
i.lock.Lock()
|
||||||
|
delete(i.data, i.keyfn(key))
|
||||||
|
i.lock.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *InMemoryStore) Flush() error {
|
||||||
|
i.lock.Lock()
|
||||||
|
|
||||||
|
i.data = make(map[string]*Item, i.bufferSize)
|
||||||
|
i.lock.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *InMemoryStore) Has(key string) bool {
|
||||||
|
i.lock.RLock()
|
||||||
|
|
||||||
|
_, ok := i.data[i.keyfn(key)]
|
||||||
|
i.lock.RUnlock()
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *InMemoryStore) GC() {
|
||||||
|
i.lock.Lock()
|
||||||
|
|
||||||
|
for k, item := range i.data {
|
||||||
|
if item.IsExpired() {
|
||||||
|
//No need to spawn a new goroutine since we
|
||||||
|
//still have the lock here
|
||||||
|
delete(i.data, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
i.lock.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *InMemoryStore) count() int {
|
||||||
|
i.lock.Lock()
|
||||||
|
n := len(i.data)
|
||||||
|
i.lock.Unlock()
|
||||||
|
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyData(data []byte) []byte {
|
||||||
|
result := make([]byte, len(data))
|
||||||
|
copy(result, data)
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
@ -1,31 +0,0 @@
|
|||||||
//
|
|
||||||
// memory_test.go
|
|
||||||
// Copyright (C) 2023 tiglog <me@tiglog.com>
|
|
||||||
//
|
|
||||||
// Distributed under terms of the MIT license.
|
|
||||||
//
|
|
||||||
|
|
||||||
package gcache_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"git.hexq.cn/tiglog/golib/gcache"
|
|
||||||
"git.hexq.cn/tiglog/golib/gtest"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestUsage(t *testing.T) {
|
|
||||||
|
|
||||||
c := gcache.NewMemCache()
|
|
||||||
c.Set("a", 1)
|
|
||||||
c.Set("b", 1, gcache.WithEx(1*time.Second))
|
|
||||||
time.Sleep(1 * time.Second)
|
|
||||||
r1, ok := c.Get("a") // 1, true
|
|
||||||
gtest.True(t, ok)
|
|
||||||
gtest.Equal(t, 1, r1)
|
|
||||||
|
|
||||||
r2, ok := c.Get("b")
|
|
||||||
gtest.False(t, ok)
|
|
||||||
gtest.Nil(t, r2)
|
|
||||||
}
|
|
@ -1,68 +0,0 @@
|
|||||||
//
|
|
||||||
// option.go
|
|
||||||
// Copyright (C) 2023 tiglog <me@tiglog.com>
|
|
||||||
//
|
|
||||||
// Distributed under terms of the MIT license.
|
|
||||||
//
|
|
||||||
|
|
||||||
package gcache
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
// SetIOption The option used to cache set
|
|
||||||
type SetIOption func(ICache, string, IItem) bool
|
|
||||||
|
|
||||||
// WithEx Set the specified expire time, in time.Duration.
|
|
||||||
func WithEx(d time.Duration) SetIOption {
|
|
||||||
return func(c ICache, k string, v IItem) bool {
|
|
||||||
v.SetExpireAt(time.Now().Add(d))
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithExAt Set the specified expire deadline, in time.Time.
|
|
||||||
func WithExAt(t time.Time) SetIOption {
|
|
||||||
return func(c ICache, k string, v IItem) bool {
|
|
||||||
v.SetExpireAt(t)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ICacheOption The option used to create the cache object
|
|
||||||
type ICacheOption func(conf *Config)
|
|
||||||
|
|
||||||
// WithShards set custom size of sharding. Default is 1024
|
|
||||||
// The larger the size, the smaller the lock force, the higher the concurrency performance,
|
|
||||||
// and the higher the memory footprint, so try to choose a size that fits your business scenario
|
|
||||||
func WithShards(shards int) ICacheOption {
|
|
||||||
if shards <= 0 {
|
|
||||||
panic("Invalid shards")
|
|
||||||
}
|
|
||||||
return func(conf *Config) {
|
|
||||||
conf.shards = shards
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithExpiredCallback set custom expired callback function
|
|
||||||
// This callback function is called when the key-value pair expires
|
|
||||||
func WithExpiredCallback(ec ExpiredCallback) ICacheOption {
|
|
||||||
return func(conf *Config) {
|
|
||||||
conf.expiredCallback = ec
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithHash set custom hash key function
|
|
||||||
func WithHash(hash IHash) ICacheOption {
|
|
||||||
return func(conf *Config) {
|
|
||||||
conf.hash = hash
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithClearInterval set custom clear interval.
|
|
||||||
// Interval for clearing expired key-value pairs. The default value is 1 second
|
|
||||||
// If the d is 0, the periodic clearing function is disabled
|
|
||||||
func WithClearInterval(d time.Duration) ICacheOption {
|
|
||||||
return func(conf *Config) {
|
|
||||||
conf.clearInterval = d
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
//
|
|
||||||
// redis.go
|
|
||||||
// Copyright (C) 2023 tiglog <me@tiglog.com>
|
|
||||||
//
|
|
||||||
// Distributed under terms of the MIT license.
|
|
||||||
//
|
|
||||||
|
|
||||||
package gcache
|
|
||||||
|
|
||||||
// TODO 暂时不需要,后面再实现
|
|
87
gcache/redis_adapter.go
Normal file
87
gcache/redis_adapter.go
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
//
|
||||||
|
// redis_adapter.go
|
||||||
|
// Copyright (C) 2023 tiglog <me@tiglog.com>
|
||||||
|
//
|
||||||
|
// Distributed under terms of the MIT license.
|
||||||
|
//
|
||||||
|
|
||||||
|
package gcache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/redis/go-redis/v9"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Option is a redis option type
|
||||||
|
type RCOption func(r *RedisStore)
|
||||||
|
|
||||||
|
// ClientOptions is an Option type that allows configuring a redis client
|
||||||
|
func RCClientOptions(opts *redis.Options) RCOption {
|
||||||
|
return func(r *RedisStore) {
|
||||||
|
r.client = redis.NewClient(opts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CacheKeyGenerator allows configuring the cache key generation process
|
||||||
|
func RCCacheKeyGenerator(fn KeyFunc) RCOption {
|
||||||
|
return func(r *RedisStore) {
|
||||||
|
r.keyFn = fn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type RedisStore struct {
|
||||||
|
client *redis.Client
|
||||||
|
|
||||||
|
keyFn KeyFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a new RedisStore by applying all options passed into it
|
||||||
|
// It also sets sensible defaults too
|
||||||
|
func NewRedisStore(opts ...RCOption) *RedisStore {
|
||||||
|
r := &RedisStore{}
|
||||||
|
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.client == nil {
|
||||||
|
panic("需要指定 redis 的连接参数")
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.keyFn == nil {
|
||||||
|
r.keyFn = DefaultKeyFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RedisStore) Set(k string, data []byte, expires time.Duration) error {
|
||||||
|
cmd := r.client.Set(context.Background(), r.key(k), data, expires)
|
||||||
|
return cmd.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RedisStore) Get(key string) ([]byte, error) {
|
||||||
|
return r.client.Get(context.Background(), r.key(key)).Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RedisStore) Delete(key string) error {
|
||||||
|
return r.client.Del(context.Background(), r.key(key)).Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RedisStore) Flush() error {
|
||||||
|
return r.client.FlushDB(context.Background()).Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RedisStore) Has(key string) bool {
|
||||||
|
if _, err := r.Get(key); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RedisStore) key(k string) string {
|
||||||
|
return r.keyFn(k)
|
||||||
|
}
|
59
gcache/types.go
Normal file
59
gcache/types.go
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
//
|
||||||
|
// tyeps.go
|
||||||
|
// Copyright (C) 2023 tiglog <me@tiglog.com>
|
||||||
|
//
|
||||||
|
// Distributed under terms of the MIT license.
|
||||||
|
//
|
||||||
|
|
||||||
|
package gcache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
EXPIRES_DEFAULT = time.Duration(0)
|
||||||
|
EXPIRES_FOREVER = time.Duration(-1)
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrCacheMiss = errors.New("Key not found")
|
||||||
|
ErrCacheNotStored = errors.New("Data not stored")
|
||||||
|
ErrCacheNotSupported = errors.New("Operation not supported")
|
||||||
|
ErrCacheDataCannotBeIncreasedOrDecreased = errors.New(`
|
||||||
|
Data isn't an integer/string type. Hence, it cannot be increased or decreased`)
|
||||||
|
)
|
||||||
|
|
||||||
|
// DefaultKeyFunc is the default implementation of cache keys
|
||||||
|
// All it does is to preprend "onecache:" to the key sent in by client code
|
||||||
|
func DefaultKeyFunc(s string) string {
|
||||||
|
return "gcache:" + s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Item identifes a cached piece of data
|
||||||
|
type Item struct {
|
||||||
|
ExpiresAt time.Time
|
||||||
|
Data []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interface for all onecache store implementations
|
||||||
|
type Store interface {
|
||||||
|
Set(key string, data []byte, expires time.Duration) error
|
||||||
|
Get(key string) ([]byte, error)
|
||||||
|
Delete(key string) error
|
||||||
|
Flush() error
|
||||||
|
Has(key string) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Some stores like redis and memcache automatically clear out the cache
|
||||||
|
// But for the filesystem and in memory, this cannot be said.
|
||||||
|
// Stores that have to manually clear out the cached data should implement this method.
|
||||||
|
// It's implementation should re run this function everytime the interval is reached
|
||||||
|
// Say every 5 minutes.
|
||||||
|
type GarbageCollector interface {
|
||||||
|
GC()
|
||||||
|
}
|
||||||
|
|
||||||
|
// KeyFunc defines a transformer for cache keys
|
||||||
|
type KeyFunc func(s string) string
|
120
gcache/utils.go
Normal file
120
gcache/utils.go
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
//
|
||||||
|
// utils.go
|
||||||
|
// Copyright (C) 2023 tiglog <me@tiglog.com>
|
||||||
|
//
|
||||||
|
// Distributed under terms of the MIT license.
|
||||||
|
//
|
||||||
|
|
||||||
|
package gcache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/gob"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Helper method to check if an item is expired.
|
||||||
|
// Current usecase for this is for garbage collection
|
||||||
|
func (i *Item) IsExpired() bool {
|
||||||
|
return time.Now().After(i.ExpiresAt)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Serializer interface {
|
||||||
|
Serialize(i interface{}) ([]byte, error)
|
||||||
|
DeSerialize(data []byte, i interface{}) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCacheSerializer() *CacheSerializer {
|
||||||
|
return &CacheSerializer{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to serialize and deserialize types
|
||||||
|
type CacheSerializer struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert a given type into a byte array
|
||||||
|
// Caveat -> Types you create might have to be registered with the encoding/gob package
|
||||||
|
func (b *CacheSerializer) Serialize(i interface{}) ([]byte, error) {
|
||||||
|
|
||||||
|
if b, ok := i.([]byte); ok {
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
enc := gob.NewEncoder(&buf)
|
||||||
|
|
||||||
|
if err := enc.Encode(i); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Writes a byte array into a type.
|
||||||
|
func (b *CacheSerializer) DeSerialize(data []byte, i interface{}) error {
|
||||||
|
|
||||||
|
if b, ok := i.(*[]byte); ok {
|
||||||
|
*b = data
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return gob.NewDecoder(bytes.NewBuffer(data)).Decode(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Increment increases the value of an item by the specified number of steps
|
||||||
|
func Increment(val interface{}, steps int) (interface{}, error) {
|
||||||
|
|
||||||
|
var ret interface{}
|
||||||
|
|
||||||
|
switch val.(type) {
|
||||||
|
|
||||||
|
case int:
|
||||||
|
ret = val.(int) + steps
|
||||||
|
|
||||||
|
case int32:
|
||||||
|
ret = val.(int32) + int32(steps)
|
||||||
|
|
||||||
|
case int64:
|
||||||
|
ret = val.(int64) + int64(steps)
|
||||||
|
|
||||||
|
case uint:
|
||||||
|
ret = val.(uint) + uint(steps)
|
||||||
|
|
||||||
|
case uint8:
|
||||||
|
ret = val.(uint8) + uint8(steps)
|
||||||
|
|
||||||
|
case uint16:
|
||||||
|
ret = val.(uint16) + uint16(steps)
|
||||||
|
|
||||||
|
case uint32:
|
||||||
|
ret = val.(uint32) + uint32(steps)
|
||||||
|
|
||||||
|
case uint64:
|
||||||
|
ret = val.(uint64) + uint64(steps)
|
||||||
|
|
||||||
|
case string:
|
||||||
|
|
||||||
|
num, err := strconv.Atoi(val.(string))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return -0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
num += steps
|
||||||
|
|
||||||
|
ret = strconv.Itoa(num)
|
||||||
|
|
||||||
|
default:
|
||||||
|
return -0, ErrCacheDataCannotBeIncreasedOrDecreased
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decrement decreases the value of an item by the specified number of steps
|
||||||
|
func Decrement(val interface{}, steps int) (interface{}, error) {
|
||||||
|
return Increment(val, steps*-1)
|
||||||
|
}
|
@ -16,7 +16,7 @@ import (
|
|||||||
"github.com/casbin/casbin/v2/model"
|
"github.com/casbin/casbin/v2/model"
|
||||||
"github.com/casbin/casbin/v2/persist"
|
"github.com/casbin/casbin/v2/persist"
|
||||||
"github.com/casbin/casbin/v2/util"
|
"github.com/casbin/casbin/v2/util"
|
||||||
"github.com/go-redis/redis/v8"
|
"github.com/redis/go-redis/v9"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -12,10 +12,10 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/casbin/casbin/v2"
|
|
||||||
"github.com/go-redis/redis/v8"
|
|
||||||
"git.hexq.cn/tiglog/golib/gcasbin"
|
"git.hexq.cn/tiglog/golib/gcasbin"
|
||||||
"git.hexq.cn/tiglog/golib/gtest"
|
"git.hexq.cn/tiglog/golib/gtest"
|
||||||
|
"github.com/casbin/casbin/v2"
|
||||||
|
"github.com/redis/go-redis/v9"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getRedis() *redis.Client {
|
func getRedis() *redis.Client {
|
||||||
|
@ -9,7 +9,7 @@ package gconfig
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"io/ioutil"
|
"os"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"git.hexq.cn/tiglog/golib/gfile"
|
"git.hexq.cn/tiglog/golib/gfile"
|
||||||
@ -50,7 +50,7 @@ func (c *BaseConfig) GetData(fname string, must bool) *bytes.Buffer {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dat, err := ioutil.ReadFile(fp)
|
dat, err := os.ReadFile(fp)
|
||||||
helper.CheckErr(err)
|
helper.CheckErr(err)
|
||||||
tpl, err := template.New("config").Parse(string(dat))
|
tpl, err := template.New("config").Parse(string(dat))
|
||||||
helper.CheckErr(err)
|
helper.CheckErr(err)
|
||||||
|
8
go.mod
8
go.mod
@ -3,18 +3,14 @@ module git.hexq.cn/tiglog/golib
|
|||||||
go 1.20
|
go 1.20
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/DATA-DOG/go-sqlmock v1.5.0
|
|
||||||
github.com/casbin/casbin/v2 v2.70.0
|
github.com/casbin/casbin/v2 v2.70.0
|
||||||
github.com/gin-gonic/gin v1.9.1
|
github.com/gin-gonic/gin v1.9.1
|
||||||
|
github.com/go-redis/redis v6.15.9+incompatible
|
||||||
github.com/go-redis/redis/v8 v8.11.5
|
github.com/go-redis/redis/v8 v8.11.5
|
||||||
github.com/go-sql-driver/mysql v1.7.1
|
|
||||||
github.com/hibiken/asynq v0.24.1
|
github.com/hibiken/asynq v0.24.1
|
||||||
github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible
|
github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible
|
||||||
github.com/lib/pq v1.10.9
|
|
||||||
github.com/mattn/go-runewidth v0.0.14
|
github.com/mattn/go-runewidth v0.0.14
|
||||||
github.com/mattn/go-sqlite3 v1.14.6
|
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/poy/onpar v0.3.2
|
|
||||||
github.com/rs/xid v1.5.0
|
github.com/rs/xid v1.5.0
|
||||||
github.com/stretchr/testify v1.8.3
|
github.com/stretchr/testify v1.8.3
|
||||||
go.mongodb.org/mongo-driver v1.11.7
|
go.mongodb.org/mongo-driver v1.11.7
|
||||||
@ -53,7 +49,7 @@ require (
|
|||||||
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect
|
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
|
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/redis/go-redis/v9 v9.0.3 // indirect
|
github.com/redis/go-redis/v9 v9.2.1 // indirect
|
||||||
github.com/rivo/uniseg v0.2.0 // indirect
|
github.com/rivo/uniseg v0.2.0 // indirect
|
||||||
github.com/robfig/cron/v3 v3.0.1 // indirect
|
github.com/robfig/cron/v3 v3.0.1 // indirect
|
||||||
github.com/spf13/cast v1.3.1 // indirect
|
github.com/spf13/cast v1.3.1 // indirect
|
||||||
|
17
go.sum
17
go.sum
@ -1,13 +1,12 @@
|
|||||||
git.sr.ht/~nelsam/hel v0.4.3 h1:9W0zz8zv8CZhFsp8r9Wq6c8gFemBdtMurjZU/JKfvfM=
|
|
||||||
github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
|
|
||||||
github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
|
|
||||||
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible h1:1G1pk05UrOh0NlF1oeaaix1x8XzrfjIDK47TY0Zehcw=
|
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible h1:1G1pk05UrOh0NlF1oeaaix1x8XzrfjIDK47TY0Zehcw=
|
||||||
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
|
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
|
||||||
github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A=
|
github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A=
|
||||||
github.com/bsm/ginkgo/v2 v2.7.0 h1:ItPMPH90RbmZJt5GtkcNvIRuGEdwlBItdNVoyzaNQao=
|
github.com/bsm/ginkgo/v2 v2.7.0 h1:ItPMPH90RbmZJt5GtkcNvIRuGEdwlBItdNVoyzaNQao=
|
||||||
github.com/bsm/ginkgo/v2 v2.7.0/go.mod h1:AiKlXPm7ItEHNc/2+OkrNG4E0ITzojb9/xWzvQ9XZ9w=
|
github.com/bsm/ginkgo/v2 v2.7.0/go.mod h1:AiKlXPm7ItEHNc/2+OkrNG4E0ITzojb9/xWzvQ9XZ9w=
|
||||||
|
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||||
github.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y=
|
github.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y=
|
||||||
github.com/bsm/gomega v1.26.0/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
github.com/bsm/gomega v1.26.0/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||||
|
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||||
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
||||||
github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
|
github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
|
||||||
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
||||||
@ -37,10 +36,10 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
|
|||||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
|
github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
|
||||||
github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
||||||
|
github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg=
|
||||||
|
github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
|
||||||
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
|
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
|
||||||
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
|
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
|
||||||
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
|
|
||||||
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
|
||||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||||
github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc=
|
github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc=
|
||||||
@ -81,14 +80,10 @@ github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible h1:Y6sqxHMyB1D2YSzWkL
|
|||||||
github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible/go.mod h1:ZQnN8lSECaebrkQytbHj4xNgtg8CR7RYXnPok8e0EHA=
|
github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible/go.mod h1:ZQnN8lSECaebrkQytbHj4xNgtg8CR7RYXnPok8e0EHA=
|
||||||
github.com/lestrrat-go/strftime v1.0.6 h1:CFGsDEt1pOpFNU+TJB0nhz9jl+K0hZSLE205AhTIGQQ=
|
github.com/lestrrat-go/strftime v1.0.6 h1:CFGsDEt1pOpFNU+TJB0nhz9jl+K0hZSLE205AhTIGQQ=
|
||||||
github.com/lestrrat-go/strftime v1.0.6/go.mod h1:f7jQKgV5nnJpYgdEasS+/y7EsTb8ykN2z68n3TtcTaw=
|
github.com/lestrrat-go/strftime v1.0.6/go.mod h1:f7jQKgV5nnJpYgdEasS+/y7EsTb8ykN2z68n3TtcTaw=
|
||||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
|
||||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
|
||||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
|
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
|
||||||
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg=
|
|
||||||
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
@ -105,10 +100,10 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
|||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/poy/onpar v0.3.2 h1:yo8ZRqU3C4RlvkXPWUWfonQiTodAgpKQZ1g8VTNU9xU=
|
|
||||||
github.com/poy/onpar v0.3.2/go.mod h1:6XDWG8DJ1HsFX6/Btn0pHl3Jz5d1SEEGNZ5N1gtYo+I=
|
|
||||||
github.com/redis/go-redis/v9 v9.0.3 h1:+7mmR26M0IvyLxGZUHxu4GiBkJkVDid0Un+j4ScYu4k=
|
github.com/redis/go-redis/v9 v9.0.3 h1:+7mmR26M0IvyLxGZUHxu4GiBkJkVDid0Un+j4ScYu4k=
|
||||||
github.com/redis/go-redis/v9 v9.0.3/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk=
|
github.com/redis/go-redis/v9 v9.0.3/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk=
|
||||||
|
github.com/redis/go-redis/v9 v9.2.1 h1:WlYJg71ODF0dVspZZCpYmoF1+U1Jjk9Rwd7pq6QmlCg=
|
||||||
|
github.com/redis/go-redis/v9 v9.2.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
|
||||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||||
|
@ -17,16 +17,16 @@ import (
|
|||||||
|
|
||||||
func TestClient(t *testing.T) {
|
func TestClient(t *testing.T) {
|
||||||
gqueue.Init(os.Getenv("REDIS_ADDR"), os.Getenv("REDIS_USERNAME"), os.Getenv("REDIS_PASSWORD"), 0)
|
gqueue.Init(os.Getenv("REDIS_ADDR"), os.Getenv("REDIS_USERNAME"), os.Getenv("REDIS_PASSWORD"), 0)
|
||||||
c1 := gqueue.Client()
|
c1 := gqueue.GetClient()
|
||||||
c2 := gqueue.Client()
|
c2 := gqueue.GetClient()
|
||||||
gtest.NotNil(t, c1)
|
gtest.NotNil(t, c1)
|
||||||
gtest.Equal(t, c1, c2)
|
gtest.Equal(t, c1, c2)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestServer(t *testing.T) {
|
func TestServer(t *testing.T) {
|
||||||
gqueue.Init(os.Getenv("REDIS_ADDR"), os.Getenv("REDIS_USERNAME"), os.Getenv("REDIS_PASSWORD"), 0)
|
gqueue.Init(os.Getenv("REDIS_ADDR"), os.Getenv("REDIS_USERNAME"), os.Getenv("REDIS_PASSWORD"), 0)
|
||||||
s1 := gqueue.Server()
|
s1 := gqueue.GetServer()
|
||||||
s2 := gqueue.Server()
|
s2 := gqueue.GetServer()
|
||||||
gtest.NotNil(t, s1)
|
gtest.NotNil(t, s1)
|
||||||
gtest.Equal(t, s1, s2)
|
gtest.Equal(t, s1, s2)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user