commit 8a3b3100f82327ef50fc065b0511735e4cb2e6eb Author: 李光春 Date: Thu May 26 17:38:26 2022 +0800 - init diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 0000000..c56c479 --- /dev/null +++ b/.drone.yml @@ -0,0 +1,17 @@ +kind: pipeline +type: docker +name: clone + +steps: + - name: Test + image: golang:1.18 + commands: + - go env -w GO111MODULE=on + - go env -w GOPROXY=https://goproxy.cn,direct + - go test -v ./... + - name: Benchmark + image: golang:1.18 + commands: + - go env -w GO111MODULE=on + - go env -w GOPROXY=https://goproxy.cn,direct + - go test -bench=. -benchmem \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4794692 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +.env +.git +.svn +.idea +.vscode +*.log +goinit.sh +gomod.sh +/vendor/ \ No newline at end of file diff --git a/Iterator.go b/Iterator.go new file mode 100644 index 0000000..6c4957b --- /dev/null +++ b/Iterator.go @@ -0,0 +1,26 @@ +package goredis + +type Iterator struct { + data []interface{} + index int +} + +// NewIterator 构造函数 +func NewIterator(data []interface{}) *Iterator { + return &Iterator{data: data} +} + +// HasNext 是否有下一个 +func (i *Iterator) HasNext() bool { + if i.data == nil || len(i.data) == 0 { + return false + } + return i.index < len(i.data) +} + +// Next 循环下一个 +func (i *Iterator) Next() (Ret interface{}) { + Ret = i.data[i.index] + i.index = i.index + 1 + return +} diff --git a/app.go b/app.go new file mode 100644 index 0000000..3863016 --- /dev/null +++ b/app.go @@ -0,0 +1,45 @@ +package goredis + +import ( + "context" + "errors" + "fmt" + "github.com/go-redis/redis/v8" + "log" + "time" +) + +// App 实例 +type App struct { + Db *redis.Client + Addr string // 地址 + Password string // 密码 + DB int // 数据库 + PoolSize int // 连接池大小 +} + +// InitClient 初始化连接 +func (app *App) InitClient() { + + log.Printf("redis config:%+v\n", app) + + if app.PoolSize == 0 { + app.PoolSize = 100 + } + + app.Db = redis.NewClient(&redis.Options{ + Addr: app.Addr, // 地址 + Password: app.Password, // 密码 + DB: app.DB, // 数据库 + PoolSize: app.PoolSize, // 连接池大小 + }) + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + _, err := app.Db.Ping(ctx).Result() + if err != nil { + panic(errors.New(fmt.Sprintf("数据库【redis】连接失败:%v", err))) + } + return +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..6969919 --- /dev/null +++ b/go.mod @@ -0,0 +1,13 @@ +module go.dtapp.net/goredis + +go 1.18 + +require ( + github.com/go-redis/redis/v8 v8.11.5 + go.dtapp.net/gotime v1.0.2 +) + +require ( + github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..e72fa46 --- /dev/null +++ b/go.sum @@ -0,0 +1,17 @@ +github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +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/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= +go.dtapp.net/gotime v1.0.2 h1:CFIJHQXC/4t9bsJhk2cLhjHd6rpdPcJXr8BcHKHDuQo= +go.dtapp.net/gotime v1.0.2/go.mod h1:Gq7eNLr2iMLP18UNWONRq4V3Uhf/ADp4bIrS+Tc6ktY= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= diff --git a/hash_operation.go b/hash_operation.go new file mode 100644 index 0000000..fa0c1b4 --- /dev/null +++ b/hash_operation.go @@ -0,0 +1,26 @@ +package goredis + +import ( + "context" + "github.com/go-redis/redis/v8" +) + +type HashOperation struct { + db *redis.Client + ctx context.Context +} + +// NewHashOperation hash类型数据操作 https://www.tizi365.com/archives/296.html +func NewHashOperation(db *redis.Client, ctx context.Context) *HashOperation { + return &HashOperation{db: db, ctx: ctx} +} + +// Set 根据key和field字段设置,field字段的值 +func (cl *HashOperation) Set(key string, value interface{}) *redis.IntCmd { + return cl.db.HSet(cl.ctx, key, value) +} + +// Get 根据key和field字段设置,field字段的值 +func (cl *HashOperation) Get(key, field string) *redis.StringCmd { + return cl.db.HGet(cl.ctx, key, field) +} diff --git a/list_operation.go b/list_operation.go new file mode 100644 index 0000000..5c2a412 --- /dev/null +++ b/list_operation.go @@ -0,0 +1,76 @@ +package goredis + +import ( + "context" + "github.com/go-redis/redis/v8" +) + +type ListOperation struct { + db *redis.Client + ctx context.Context +} + +// NewListOperation 列表(list)类型数据操作 https://www.tizi365.com/archives/299.html +func (app *App) NewListOperation() *ListOperation { + return &ListOperation{db: app.Db, ctx: context.Background()} +} + +// LPush 从列表左边插入数据 +func (cl *ListOperation) LPush(key string, value interface{}) *redis.IntCmd { + return cl.db.LPush(cl.ctx, key, value) +} + +// LPushX 跟LPush的区别是,仅当列表存在的时候才插入数据 +func (cl *ListOperation) LPushX(key string, value interface{}) *redis.IntCmd { + return cl.db.LPushX(cl.ctx, key, value) +} + +// RPop 从列表的右边删除第一个数据,并返回删除的数据 +func (cl *ListOperation) RPop(key string) *redis.StringCmd { + return cl.db.RPop(cl.ctx, key) +} + +// RPush 从列表右边插入数据 +func (cl *ListOperation) RPush(key string, value interface{}) *redis.IntCmd { + return cl.db.RPush(cl.ctx, key, value) +} + +// RPushX 跟RPush的区别是,仅当列表存在的时候才插入数据 +func (cl *ListOperation) RPushX(key string, value interface{}) *redis.IntCmd { + return cl.db.RPushX(cl.ctx, key, value) +} + +// LPop 从列表左边删除第一个数据,并返回删除的数据 +func (cl *ListOperation) LPop(key string) *redis.StringCmd { + return cl.db.LPop(cl.ctx, key) +} + +// Len 返回列表的大小 +func (cl *ListOperation) Len(key string) *redis.IntCmd { + return cl.db.LLen(cl.ctx, key) +} + +// Range 返回列表的一个范围内的数据,也可以返回全部数据 +func (cl *ListOperation) Range(key string, start, stop int64) *redis.StringSliceCmd { + return cl.db.LRange(cl.ctx, key, start, stop) +} + +// RangeAli 返回key全部数据 +func (cl *ListOperation) RangeAli(key string) *redis.StringSliceCmd { + return cl.db.LRange(cl.ctx, key, 0, -1) +} + +// Rem 删除key中的数据 +func (cl *ListOperation) Rem(key string, count int64, value interface{}) *redis.IntCmd { + return cl.db.LRem(cl.ctx, key, count, value) +} + +// Index 根据索引坐标,查询key中的数据 +func (cl *ListOperation) Index(key string, index int64) *redis.StringCmd { + return cl.db.LIndex(cl.ctx, key, index) +} + +// Insert 在指定位置插入数据 +func (cl *ListOperation) Insert(key, op string, pivot, value interface{}) *redis.IntCmd { + return cl.db.LInsert(cl.ctx, key, op, pivot, value) +} diff --git a/operation_attr.go b/operation_attr.go new file mode 100644 index 0000000..e61fc53 --- /dev/null +++ b/operation_attr.go @@ -0,0 +1,35 @@ +package goredis + +import "time" + +type empty struct{} + +const ( + AttrExpr = "expr" // 过期时间 + AttrNx = "nx" // 设置Nx +) + +type OperationAttr struct { + Name string + Value interface{} +} + +type OperationAttrs []*OperationAttr + +func (a OperationAttrs) Find(name string) interface{} { + for _, attr := range a { + if attr.Name == name { + return attr.Value + } + } + return nil +} + +// WithExpire 过期时间 +func WithExpire(t time.Duration) *OperationAttr { + return &OperationAttr{Name: AttrExpr, Value: t} +} + +func WithNX() *OperationAttr { + return &OperationAttr{Name: AttrNx, Value: empty{}} +} diff --git a/simple_cache.go b/simple_cache.go new file mode 100644 index 0000000..abcbd0a --- /dev/null +++ b/simple_cache.go @@ -0,0 +1,61 @@ +package goredis + +import ( + "encoding/json" + "time" +) + +const ( + SerializerJson = "json" + SerializerString = "string" +) + +type JsonGttFunc func() interface{} + +type DBGttFunc func() string + +// SimpleCache 缓存 +type SimpleCache struct { + Operation *StringOperation // 操作类 + Expire time.Duration // 过去时间 + DBGetter DBGttFunc // 缓存不存在的操作 DB + JsonGetter JsonGttFunc // 缓存不存在的操作 JSON + Serializer string // 序列化方式 +} + +// NewSimpleCache 构造函数 +func (app *App) NewSimpleCache(operation *StringOperation, expire time.Duration, serializer string) *SimpleCache { + return &SimpleCache{ + Operation: operation, // 操作类 + Expire: expire, // 过去时间 + Serializer: serializer, // 缓存不存在的操作 DB + } +} + +// SetCache 设置缓存 +func (c *SimpleCache) SetCache(key string, value interface{}) { + c.Operation.Set(key, value, WithExpire(c.Expire)).Unwrap() +} + +// GetCache 获取缓存 +func (c *SimpleCache) GetCache(key string) (ret interface{}) { + if c.Serializer == SerializerJson { + f := func() string { + obj := c.JsonGetter() + b, err := json.Marshal(obj) + if err != nil { + return "" + } + return string(b) + } + ret = c.Operation.Get(key).UnwrapOrElse(f) + c.SetCache(key, ret) + } else if c.Serializer == SerializerString { + f := func() string { + return c.DBGetter() + } + ret = c.Operation.Get(key).UnwrapOrElse(f) + c.SetCache(key, ret) + } + return +} diff --git a/simple_interface_cache.go b/simple_interface_cache.go new file mode 100644 index 0000000..c80d246 --- /dev/null +++ b/simple_interface_cache.go @@ -0,0 +1,39 @@ +package goredis + +import ( + "log" + "time" +) + +type DBGttInterfaceFunc func() interface{} + +// SimpleInterfaceCache 缓存 +type SimpleInterfaceCache struct { + Operation *SimpleOperation // 操作类 + Expire time.Duration // 过期时间 + DBGetter DBGttInterfaceFunc // 缓存不存在的操作 DB +} + +// NewSimpleInterfaceCache 构造函数 +func (app *App) NewSimpleInterfaceCache(operation *SimpleOperation, expire time.Duration) *SimpleInterfaceCache { + return &SimpleInterfaceCache{ + Operation: operation, // 操作类 + Expire: expire, // 过期时间 + } +} + +// SetCache 设置缓存 +func (c *SimpleInterfaceCache) SetCache(key string, value interface{}) { + c.Operation.Set(key, value, WithExpire(c.Expire)).Unwrap() +} + +// GetCache 获取缓存 +func (c *SimpleInterfaceCache) GetCache(key string) (ret interface{}) { + f := func() interface{} { + return c.DBGetter() + } + ret = c.Operation.Get(key).UnwrapOrElse(f) + c.SetCache(key, ret) + log.Println(ret) + return ret +} diff --git a/simple_json_cache.go b/simple_json_cache.go new file mode 100644 index 0000000..a180925 --- /dev/null +++ b/simple_json_cache.go @@ -0,0 +1,43 @@ +package goredis + +import ( + "encoding/json" + "time" +) + +type DBGttJsonFunc func() interface{} + +// SimpleJsonCache 缓存 +type SimpleJsonCache struct { + Operation *StringOperation // 操作类 + Expire time.Duration // 过期时间 + DBGetter DBGttJsonFunc // 缓存不存在的操作 DB +} + +// NewSimpleJsonCache 构造函数 +func (app *App) NewSimpleJsonCache(operation *StringOperation, expire time.Duration) *SimpleJsonCache { + return &SimpleJsonCache{ + Operation: operation, // 操作类 + Expire: expire, // 过期时间 + } +} + +// SetCache 设置缓存 +func (c *SimpleJsonCache) SetCache(key string, value interface{}) { + c.Operation.Set(key, value, WithExpire(c.Expire)).Unwrap() +} + +// GetCache 获取缓存 +func (c *SimpleJsonCache) GetCache(key string) (ret interface{}) { + f := func() string { + obj := c.DBGetter() + b, err := json.Marshal(obj) + if err != nil { + return "" + } + return string(b) + } + ret = c.Operation.Get(key).UnwrapOrElse(f) + c.SetCache(key, ret) + return +} diff --git a/simple_operation.go b/simple_operation.go new file mode 100644 index 0000000..d479f9f --- /dev/null +++ b/simple_operation.go @@ -0,0 +1,38 @@ +package goredis + +import ( + "context" + "github.com/go-redis/redis/v8" + "time" +) + +type SimpleOperation struct { + db *redis.Client + ctx context.Context +} + +func (app *App) NewSimpleOperation() *SimpleOperation { + return &SimpleOperation{ + db: app.Db, + ctx: context.Background(), + } +} + +// Set 设置 +func (o *SimpleOperation) Set(key string, value interface{}, attrs ...*OperationAttr) *SimpleResult { + exp := OperationAttrs(attrs).Find(AttrExpr) + if exp == nil { + exp = time.Second * 0 + } + return NewSimpleResult(o.db.Set(o.ctx, key, value, exp.(time.Duration)).Result()) +} + +// Get 获取单个 +func (o *SimpleOperation) Get(key string) *SimpleResult { + return NewSimpleResult(o.db.Get(o.ctx, key).Result()) +} + +// Del 删除key操作,支持批量删除 +func (o *SimpleOperation) Del(keys ...string) *redis.IntCmd { + return o.db.Del(o.ctx, keys...) +} diff --git a/simple_result.go b/simple_result.go new file mode 100644 index 0000000..25fbe0d --- /dev/null +++ b/simple_result.go @@ -0,0 +1,35 @@ +package goredis + +type SimpleResult struct { + Result interface{} + Err error +} + +// NewSimpleResult 构造函数 +func NewSimpleResult(result interface{}, err error) *SimpleResult { + return &SimpleResult{Result: result, Err: err} +} + +// Unwrap 空值情况下返回错误 +func (r *SimpleResult) Unwrap() interface{} { + if r.Err != nil { + panic(r.Err) + } + return r.Result +} + +// UnwrapOr 空值情况下设置返回默认值 +func (r *SimpleResult) UnwrapOr(defaults interface{}) interface{} { + if r.Err != nil { + return defaults + } + return r.Result +} + +// UnwrapOrElse 空值情况下设置返回其他 +func (r *SimpleResult) UnwrapOrElse(f func() interface{}) interface{} { + if r.Err != nil { + return f() + } + return r.Result +} diff --git a/simple_sring_cache.go b/simple_sring_cache.go new file mode 100644 index 0000000..9d49c86 --- /dev/null +++ b/simple_sring_cache.go @@ -0,0 +1,37 @@ +package goredis + +import ( + "time" +) + +type DBGttStringFunc func() string + +// SimpleStringCache 缓存 +type SimpleStringCache struct { + Operation *StringOperation // 操作类 + Expire time.Duration // 过期时间 + DBGetter DBGttStringFunc // 缓存不存在的操作 DB +} + +// NewSimpleStringCache 构造函数 +func (app *App) NewSimpleStringCache(operation *StringOperation, expire time.Duration) *SimpleStringCache { + return &SimpleStringCache{ + Operation: operation, // 操作类 + Expire: expire, // 过期时间 + } +} + +// SetCache 设置缓存 +func (c *SimpleStringCache) SetCache(key string, value string) { + c.Operation.Set(key, value, WithExpire(c.Expire)).Unwrap() +} + +// GetCache 获取缓存 +func (c *SimpleStringCache) GetCache(key string) (ret string) { + f := func() string { + return c.DBGetter() + } + ret = c.Operation.Get(key).UnwrapOrElse(f) + c.SetCache(key, ret) + return +} diff --git a/slice_result.go b/slice_result.go new file mode 100644 index 0000000..d04737e --- /dev/null +++ b/slice_result.go @@ -0,0 +1,31 @@ +package goredis + +type SliceResult struct { + Result []interface{} + Err error +} + +// NewSliceResult 构造函数 +func NewSliceResult(result []interface{}, err error) *SliceResult { + return &SliceResult{Result: result, Err: err} +} + +// Unwrap 空值情况下返回错误 +func (r *SliceResult) Unwrap() []interface{} { + if r.Err != nil { + panic(r.Err) + } + return r.Result +} + +// UnwrapOr 空值情况下设置返回默认值 +func (r *SliceResult) UnwrapOr(defaults []interface{}) []interface{} { + if r.Err != nil { + return defaults + } + return r.Result +} + +func (r *SliceResult) Iter() *Iterator { + return NewIterator(r.Result) +} diff --git a/string_operation.go b/string_operation.go new file mode 100644 index 0000000..e9183ae --- /dev/null +++ b/string_operation.go @@ -0,0 +1,43 @@ +package goredis + +import ( + "context" + "github.com/go-redis/redis/v8" + "time" +) + +type StringOperation struct { + db *redis.Client + ctx context.Context +} + +func (app *App) NewStringOperation() *StringOperation { + return &StringOperation{ + db: app.Db, + ctx: context.Background(), + } +} + +// Set 设置 +func (o *StringOperation) Set(key string, value interface{}, attrs ...*OperationAttr) *StringResult { + exp := OperationAttrs(attrs).Find(AttrExpr) + if exp == nil { + exp = time.Second * 0 + } + return NewStringResult(o.db.Set(o.ctx, key, value, exp.(time.Duration)).Result()) +} + +// Get 获取单个 +func (o *StringOperation) Get(key string) *StringResult { + return NewStringResult(o.db.Get(o.ctx, key).Result()) +} + +// MGet 获取多个 +func (o *StringOperation) MGet(keys ...string) *SliceResult { + return NewSliceResult(o.db.MGet(o.ctx, keys...).Result()) +} + +// Del 删除key操作,支持批量删除 +func (o *StringOperation) Del(keys ...string) *redis.IntCmd { + return o.db.Del(o.ctx, keys...) +} diff --git a/string_result.go b/string_result.go new file mode 100644 index 0000000..d9c2c81 --- /dev/null +++ b/string_result.go @@ -0,0 +1,38 @@ +package goredis + +type StringResult struct { + Result string // 结果 + Err error // 错误 +} + +// NewStringResult 构造函数 +func NewStringResult(result string, err error) *StringResult { + return &StringResult{ + Result: result, + Err: err, + } +} + +// Unwrap 空值情况下返回错误 +func (r *StringResult) Unwrap() string { + if r.Err != nil { + panic(r.Err) + } + return r.Result +} + +// UnwrapOr 空值情况下设置返回默认值 +func (r *StringResult) UnwrapOr(defaults string) string { + if r.Err != nil { + return defaults + } + return r.Result +} + +// UnwrapOrElse 空值情况下设置返回其他 +func (r *StringResult) UnwrapOrElse(f func() string) string { + if r.Err != nil { + return f() + } + return r.Result +} diff --git a/version.go b/version.go new file mode 100644 index 0000000..ce28751 --- /dev/null +++ b/version.go @@ -0,0 +1,3 @@ +package goredis + +const Version = "1.0.0" diff --git a/version_test.go b/version_test.go new file mode 100644 index 0000000..6f3faab --- /dev/null +++ b/version_test.go @@ -0,0 +1,7 @@ +package goredis + +import "testing" + +func TestVersion(t *testing.T) { + t.Log(Version) +}