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/persist"
|
||||
"github.com/casbin/casbin/v2/util"
|
||||
"github.com/go-redis/redis/v8"
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -12,10 +12,10 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/casbin/casbin/v2"
|
||||
"github.com/go-redis/redis/v8"
|
||||
"git.hexq.cn/tiglog/golib/gcasbin"
|
||||
"git.hexq.cn/tiglog/golib/gtest"
|
||||
"github.com/casbin/casbin/v2"
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
func getRedis() *redis.Client {
|
||||
|
@ -9,7 +9,7 @@ package gconfig
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"text/template"
|
||||
|
||||
"git.hexq.cn/tiglog/golib/gfile"
|
||||
@ -50,7 +50,7 @@ func (c *BaseConfig) GetData(fname string, must bool) *bytes.Buffer {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
dat, err := ioutil.ReadFile(fp)
|
||||
dat, err := os.ReadFile(fp)
|
||||
helper.CheckErr(err)
|
||||
tpl, err := template.New("config").Parse(string(dat))
|
||||
helper.CheckErr(err)
|
||||
|
8
go.mod
8
go.mod
@ -3,18 +3,14 @@ module git.hexq.cn/tiglog/golib
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.0
|
||||
github.com/casbin/casbin/v2 v2.70.0
|
||||
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-sql-driver/mysql v1.7.1
|
||||
github.com/hibiken/asynq v0.24.1
|
||||
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-sqlite3 v1.14.6
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/poy/onpar v0.3.2
|
||||
github.com/rs/xid v1.5.0
|
||||
github.com/stretchr/testify v1.8.3
|
||||
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/pelletier/go-toml/v2 v2.0.8 // 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/robfig/cron/v3 v3.0.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/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
|
||||
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/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/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.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
|
||||
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/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-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/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/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
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/strftime v1.0.6 h1:CFGsDEt1pOpFNU+TJB0nhz9jl+K0hZSLE205AhTIGQQ=
|
||||
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/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/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-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
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/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/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||
|
@ -17,16 +17,16 @@ import (
|
||||
|
||||
func TestClient(t *testing.T) {
|
||||
gqueue.Init(os.Getenv("REDIS_ADDR"), os.Getenv("REDIS_USERNAME"), os.Getenv("REDIS_PASSWORD"), 0)
|
||||
c1 := gqueue.Client()
|
||||
c2 := gqueue.Client()
|
||||
c1 := gqueue.GetClient()
|
||||
c2 := gqueue.GetClient()
|
||||
gtest.NotNil(t, c1)
|
||||
gtest.Equal(t, c1, c2)
|
||||
}
|
||||
|
||||
func TestServer(t *testing.T) {
|
||||
gqueue.Init(os.Getenv("REDIS_ADDR"), os.Getenv("REDIS_USERNAME"), os.Getenv("REDIS_PASSWORD"), 0)
|
||||
s1 := gqueue.Server()
|
||||
s2 := gqueue.Server()
|
||||
s1 := gqueue.GetServer()
|
||||
s2 := gqueue.GetServer()
|
||||
gtest.NotNil(t, s1)
|
||||
gtest.Equal(t, s1, s2)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user