Compare commits
No commits in common. "ec5d33743d96ffd78b578e0757756542431b9966" and "929fab208a156419ed58192ef030669c62322b22" have entirely different histories.
ec5d33743d
...
929fab208a
@ -1,30 +0,0 @@
|
|||||||
// Package abstract ...
|
|
||||||
//
|
|
||||||
// Description : abstract ...
|
|
||||||
//
|
|
||||||
// Author : go_developer@163.com<白茶清欢>
|
|
||||||
//
|
|
||||||
// Date : 2024-06-18 15:09
|
|
||||||
package abstract
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"git.zhangdeman.cn/zhangdeman/redis/define"
|
|
||||||
"github.com/redis/go-redis/v9"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
)
|
|
||||||
|
|
||||||
// IRedisClient redis客户端定义
|
|
||||||
//
|
|
||||||
// Author : go_developer@163.com<白茶清欢>
|
|
||||||
//
|
|
||||||
// Date : 15:09 2024/6/18
|
|
||||||
type IRedisClient interface {
|
|
||||||
GetRealClient(instanceFlag string) *redis.ClusterClient // 获取客户端连接
|
|
||||||
GetRealClientWithError(instanceFlag string) (*redis.ClusterClient, error) // 获取带error的客户端连接
|
|
||||||
AddClient(instanceFlag string, instanceConfig *redis.ClusterOptions) error // 添加新的客户端连接
|
|
||||||
RemoveClient(instanceFlag string) // 移除一个客户端连接
|
|
||||||
SetLogger(loggerInstance *zap.Logger) // 设置日志实例, 全局生效, 而非针对某一个实例
|
|
||||||
Exec(ctx context.Context, instanceFlag string, command string, args ...any) *define.RedisResult // 执行任意命令
|
|
||||||
SetCommandWhiteList(command []string) // 设置命令的白名单,全局生效, 而非单独针对某一个实例
|
|
||||||
}
|
|
546
client.go
546
client.go
@ -8,152 +8,432 @@
|
|||||||
package redis
|
package redis
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"encoding/json"
|
||||||
"git.zhangdeman.cn/zhangdeman/redis/abstract"
|
"fmt"
|
||||||
"git.zhangdeman.cn/zhangdeman/redis/define"
|
"git.zhangdeman.cn/zhangdeman/consts"
|
||||||
"git.zhangdeman.cn/zhangdeman/wrapper"
|
"git.zhangdeman.cn/zhangdeman/serialize"
|
||||||
"github.com/pkg/errors"
|
"path/filepath"
|
||||||
redisClient "github.com/redis/go-redis/v9"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"git.zhangdeman.cn/zhangdeman/logger"
|
||||||
|
redisInstance "github.com/go-redis/redis/v8"
|
||||||
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
var Client abstract.IRedisClient
|
var (
|
||||||
|
// Client 连接实例
|
||||||
|
Client ClientInterface
|
||||||
|
)
|
||||||
|
|
||||||
func init() {
|
// InitWithCfgFile 使用配置文件初始化
|
||||||
Client = &OwnClient{
|
//
|
||||||
|
// Author : go_developer@163.com<白茶清欢>
|
||||||
|
//
|
||||||
|
// Date : 21:24 2022/6/22
|
||||||
|
func InitWithCfgFile(cfgFilePath string) error {
|
||||||
|
c := &OwnClient{
|
||||||
lock: &sync.RWMutex{},
|
lock: &sync.RWMutex{},
|
||||||
instanceTable: make(map[string]*redisClient.ClusterClient),
|
instanceTable: make(map[string]*RealClient),
|
||||||
whiteCommandTable: make(map[string]bool),
|
confTable: make(map[string]*FullConfig),
|
||||||
|
parseErrorFunc: defaultParseError,
|
||||||
}
|
}
|
||||||
}
|
if err := c.loadConfig(cfgFilePath); nil != err {
|
||||||
|
return err
|
||||||
type OwnClient struct {
|
|
||||||
lock *sync.RWMutex
|
|
||||||
instanceTable map[string]*redisClient.ClusterClient
|
|
||||||
whiteCommandTable map[string]bool
|
|
||||||
logger *zap.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *OwnClient) isAllowCommand(command string) bool {
|
|
||||||
if len(o.whiteCommandTable) == 0 {
|
|
||||||
// 未配置, 视为全部允许执行
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
if o.whiteCommandTable["*"] {
|
if err := c.init(); nil != err {
|
||||||
// 配置了 * 视为全部允许执行
|
return err
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
command = strings.ToLower(strings.TrimSpace(command))
|
Client = c
|
||||||
o.lock.RLock()
|
|
||||||
defer o.lock.RUnlock()
|
|
||||||
return o.whiteCommandTable[command]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exec 执行命令
|
|
||||||
//
|
|
||||||
// Author : go_developer@163.com<白茶清欢>
|
|
||||||
//
|
|
||||||
// Date : 11:05 2024/6/19
|
|
||||||
func (o *OwnClient) Exec(ctx context.Context, instanceFlag string, command string, args ...any) *define.RedisResult {
|
|
||||||
var (
|
|
||||||
instance *redisClient.ClusterClient
|
|
||||||
)
|
|
||||||
|
|
||||||
cmdParamList := []any{
|
|
||||||
command,
|
|
||||||
}
|
|
||||||
argStrList := make([]string, 0)
|
|
||||||
for _, itemArg := range args {
|
|
||||||
argStrList = append(argStrList, wrapper.AnyDataType(itemArg).ToString().Value())
|
|
||||||
cmdParamList = append(cmdParamList, itemArg)
|
|
||||||
}
|
|
||||||
res := &define.RedisResult{
|
|
||||||
StartTime: time.Now().UnixMilli(),
|
|
||||||
FinishTime: 0,
|
|
||||||
UsedTime: 0,
|
|
||||||
Result: "",
|
|
||||||
Command: command,
|
|
||||||
ArgList: argStrList,
|
|
||||||
Err: nil,
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
res.FinishTime = time.Now().UnixMilli()
|
|
||||||
res.UsedTime = res.FinishTime - res.StartTime
|
|
||||||
if nil == o.logger {
|
|
||||||
// 未注入日志实例
|
|
||||||
return
|
|
||||||
}
|
|
||||||
o.logger.Info(
|
|
||||||
"Redis命令执行记录",
|
|
||||||
zap.Int64("start_time", res.StartTime),
|
|
||||||
zap.Int64("finish_time", res.FinishTime),
|
|
||||||
zap.Int64("used_time", res.UsedTime),
|
|
||||||
zap.String("command", res.Command),
|
|
||||||
zap.String("arg_list", strings.Join(res.ArgList, " ")),
|
|
||||||
zap.String("execute_result", res.Result),
|
|
||||||
zap.Error(res.Err),
|
|
||||||
)
|
|
||||||
}()
|
|
||||||
if instance, res.Err = o.GetRealClientWithError(instanceFlag); nil != res.Err {
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
if nil == ctx {
|
|
||||||
ctx = context.Background()
|
|
||||||
}
|
|
||||||
cmdRes := instance.Do(ctx, cmdParamList...)
|
|
||||||
if res.Err = cmdRes.Err(); nil != res.Err {
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
res.Result = cmdRes.String()
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetCommandWhiteList 设置命令白名单, 空 或者 包含 * 则认为所有命令均允许执行
|
|
||||||
//
|
|
||||||
// Author : go_developer@163.com<白茶清欢>
|
|
||||||
//
|
|
||||||
// Date : 11:02 2024/6/19
|
|
||||||
func (o *OwnClient) SetCommandWhiteList(commandList []string) {
|
|
||||||
o.lock.Lock()
|
|
||||||
defer o.lock.Unlock()
|
|
||||||
for _, itemCommand := range commandList {
|
|
||||||
o.whiteCommandTable[strings.ToLower(strings.TrimSpace(itemCommand))] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *OwnClient) GetRealClient(instanceFlag string) *redisClient.ClusterClient {
|
|
||||||
o.lock.RLock()
|
|
||||||
defer o.lock.RUnlock()
|
|
||||||
return o.instanceTable[instanceFlag]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *OwnClient) GetRealClientWithError(instanceFlag string) (*redisClient.ClusterClient, error) {
|
|
||||||
o.lock.RLock()
|
|
||||||
defer o.lock.RUnlock()
|
|
||||||
instance, exist := o.instanceTable[instanceFlag]
|
|
||||||
if !exist {
|
|
||||||
return nil, errors.New(instanceFlag + " : redis instance is not found")
|
|
||||||
}
|
|
||||||
return instance, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *OwnClient) AddClient(instanceFlag string, instanceConfig *redisClient.ClusterOptions) error {
|
|
||||||
instance := redisClient.NewClusterClient(instanceConfig)
|
|
||||||
o.lock.Lock()
|
|
||||||
defer o.lock.Unlock()
|
|
||||||
o.instanceTable[instanceFlag] = instance
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *OwnClient) RemoveClient(instanceFlag string) {
|
// InitWithCfgDir 使用配置目录初始化
|
||||||
o.lock.Lock()
|
//
|
||||||
defer o.lock.Unlock()
|
// Author : go_developer@163.com<白茶清欢>
|
||||||
delete(o.instanceTable, instanceFlag)
|
//
|
||||||
|
// Date : 21:31 2022/6/22
|
||||||
|
func InitWithCfgDir(cfgDir string) error {
|
||||||
|
c := &OwnClient{
|
||||||
|
lock: &sync.RWMutex{},
|
||||||
|
instanceTable: make(map[string]*RealClient),
|
||||||
|
confTable: make(map[string]*FullConfig),
|
||||||
|
parseErrorFunc: defaultParseError,
|
||||||
|
}
|
||||||
|
if err := c.batchLoadConfig(cfgDir); nil != err {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := c.init(); nil != err {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
Client = c
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *OwnClient) SetLogger(loggerInstance *zap.Logger) {
|
// InitWithCfgFileList 使用批量的配置文件初始化
|
||||||
o.logger = loggerInstance
|
//
|
||||||
|
// Author : go_developer@163.com<白茶清欢>
|
||||||
|
//
|
||||||
|
// Date : 21:33 2022/6/22
|
||||||
|
func InitWithCfgFileList(cfgFileList []string) error {
|
||||||
|
c := &OwnClient{
|
||||||
|
lock: &sync.RWMutex{},
|
||||||
|
instanceTable: make(map[string]*RealClient),
|
||||||
|
confTable: make(map[string]*FullConfig),
|
||||||
|
parseErrorFunc: defaultParseError,
|
||||||
|
}
|
||||||
|
for _, cfgFilePath := range cfgFileList {
|
||||||
|
if err := c.loadConfig(cfgFilePath); nil != err {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := c.init(); nil != err {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
Client = c
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// defaultParseError ...
|
||||||
|
//
|
||||||
|
// Author : go_developer@163.com<白茶清欢>
|
||||||
|
//
|
||||||
|
// Date : 10:59 下午 2021/2/27
|
||||||
|
func defaultParseError(err error) error {
|
||||||
|
if nil == err {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
errMsg := err.Error()
|
||||||
|
if errMsg == "nil" || errMsg == "<nil>" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
strArr := strings.Split(errMsg, ":")
|
||||||
|
if len(strArr) != 2 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
msg := strings.ToLower(strings.TrimSpace(strArr[1]))
|
||||||
|
if msg == "nil" || msg == "<nil>" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// RealClient 包装好的 redis client
|
||||||
|
type RealClient struct {
|
||||||
|
Flag string // redis 标识
|
||||||
|
Master *redisInstance.Client // redis 实例
|
||||||
|
Slave *redisInstance.Client // redis 实例
|
||||||
|
Logger *zap.Logger // 日志实例
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClient 获取redis client实例
|
||||||
|
//
|
||||||
|
// Author : go_developer@163.com<白茶清欢>
|
||||||
|
//
|
||||||
|
// Date : 5:05 下午 2021/2/27
|
||||||
|
func NewClient(config map[string]*FullConfig, parseErrorFunc func(err error) error) (ClientInterface, error) {
|
||||||
|
c := &OwnClient{
|
||||||
|
lock: &sync.RWMutex{},
|
||||||
|
instanceTable: make(map[string]*RealClient),
|
||||||
|
loggerTable: make(map[string]*zap.Logger),
|
||||||
|
confTable: config,
|
||||||
|
parseErrorFunc: parseErrorFunc,
|
||||||
|
}
|
||||||
|
if nil == c.parseErrorFunc {
|
||||||
|
c.parseErrorFunc = defaultParseError
|
||||||
|
}
|
||||||
|
return c, c.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
// OwnClient 包装的redis client
|
||||||
|
//
|
||||||
|
// Author : go_developer@163.com<白茶清欢>
|
||||||
|
//
|
||||||
|
// Date : 4:52 下午 2021/2/27
|
||||||
|
type OwnClient struct {
|
||||||
|
lock *sync.RWMutex // 锁
|
||||||
|
loggerTable map[string]*zap.Logger // 日志实例
|
||||||
|
instanceTable map[string]*RealClient // redis 实例
|
||||||
|
confTable map[string]*FullConfig // redis 配置
|
||||||
|
parseErrorFunc func(err error) error // 解析err的function,解析执行结果是否为失败,有的场景,执行成功,返回 redis:nil / redis:<nil>
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddClientWithCfgFile 使用具体配置文件初始化
|
||||||
|
//
|
||||||
|
// Author : go_developer@163.com<白茶清欢>
|
||||||
|
//
|
||||||
|
// Date : 20:57 2022/6/15
|
||||||
|
func (c *OwnClient) AddClientWithCfgFile(cfgPath string) error {
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if err = c.loadConfig(cfgPath); nil != err {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return c.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddClientWithCfgDir 使用配置目录进行初始化
|
||||||
|
//
|
||||||
|
// Author : go_developer@163.com<白茶清欢>
|
||||||
|
//
|
||||||
|
// Date : 20:58 2022/6/15
|
||||||
|
func (c *OwnClient) AddClientWithCfgDir(cfgDir string) error {
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if err = c.batchLoadConfig(cfgDir); nil != err {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return c.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddClientWithCfg 通过配置增加实例
|
||||||
|
//
|
||||||
|
// Author : go_developer@163.com<白茶清欢>
|
||||||
|
//
|
||||||
|
// Date : 11:45 2023/8/16
|
||||||
|
func (c *OwnClient) AddClientWithCfg(flag string, cfg *FullConfig) error {
|
||||||
|
c.lock.Lock()
|
||||||
|
c.confTable[flag] = cfg
|
||||||
|
c.lock.Unlock()
|
||||||
|
return c.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadConfig 载入配置文件
|
||||||
|
//
|
||||||
|
// Author : go_developer@163.com<白茶清欢>
|
||||||
|
//
|
||||||
|
// Date : 14:31 2022/6/15
|
||||||
|
func (c *OwnClient) loadConfig(cfgPath string) error {
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
cfg FullConfig
|
||||||
|
)
|
||||||
|
|
||||||
|
filePathArr := strings.Split(cfgPath, string(filepath.Separator))
|
||||||
|
if len(filePathArr) == 0 {
|
||||||
|
return CfgFilePathError()
|
||||||
|
}
|
||||||
|
fileName := strings.ToLower(filePathArr[len(filePathArr)-1])
|
||||||
|
fileArr := strings.Split(fileName, ".")
|
||||||
|
if len(filePathArr) < 2 {
|
||||||
|
return CfgFileFormatErr("未知")
|
||||||
|
}
|
||||||
|
flag := strings.Trim(
|
||||||
|
strings.Trim(
|
||||||
|
strings.Trim(fileName, ".json"),
|
||||||
|
".yaml"), ".yml")
|
||||||
|
switch strings.ToLower(fileArr[len(fileArr)-1]) {
|
||||||
|
case consts.FileTypeJson:
|
||||||
|
if err = serialize.File.ReadJSONContent(cfgPath, &cfg); nil != err {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
flag = strings.Join(fileArr[0:len(fileArr)-1], ".")
|
||||||
|
case consts.FileTypeYml:
|
||||||
|
fallthrough
|
||||||
|
case consts.FileTypeYaml:
|
||||||
|
if err = serialize.File.ReadYmlContent(cfgPath, &cfg); nil != err {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
flag = strings.Join(fileArr[0:len(fileArr)-1], ".")
|
||||||
|
default:
|
||||||
|
// 不支持的格式,跳过
|
||||||
|
}
|
||||||
|
|
||||||
|
c.lock.Lock()
|
||||||
|
c.confTable[flag] = &cfg
|
||||||
|
c.lock.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// batchLoadConfig 批量载入配置
|
||||||
|
//
|
||||||
|
// Author : go_developer@163.com<白茶清欢>
|
||||||
|
//
|
||||||
|
// Date : 16:44 2022/6/15
|
||||||
|
func (c *OwnClient) batchLoadConfig(cfgDir string) error {
|
||||||
|
filepathNames, _ := filepath.Glob(filepath.Join(cfgDir, "*"))
|
||||||
|
for i := range filepathNames {
|
||||||
|
if err := c.loadConfig(filepathNames[i]); nil != err {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// init 初始化redis连接
|
||||||
|
//
|
||||||
|
// Author : go_developer@163.com<白茶清欢>
|
||||||
|
//
|
||||||
|
// Date : 5:31 下午 2021/2/27
|
||||||
|
func (c *OwnClient) init() error {
|
||||||
|
var err error
|
||||||
|
for flag, conf := range c.confTable {
|
||||||
|
if _, exist := c.instanceTable[flag]; exist {
|
||||||
|
// 实例已经初始化
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
c.instanceTable[flag] = &RealClient{
|
||||||
|
Flag: flag,
|
||||||
|
Master: redisInstance.NewClient(Config2Options(conf.Master)),
|
||||||
|
Slave: redisInstance.NewClient(Config2Options(conf.Slave)),
|
||||||
|
}
|
||||||
|
if c.instanceTable[flag].Logger, err = logger.GetLogInstanceFromInputConfig(conf.Logger); nil != err {
|
||||||
|
return InitLoggerErr(flag, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// initLogger 初始化日志
|
||||||
|
//
|
||||||
|
// Author : go_developer@163.com<白茶清欢>
|
||||||
|
//
|
||||||
|
// Date : 7:07 下午 2021/2/27
|
||||||
|
func (c *OwnClient) initLogger(flag string, conf *logger.InputLogConfig) error {
|
||||||
|
var err error
|
||||||
|
c.loggerTable[flag], err = logger.GetLogInstanceFromInputConfig(conf)
|
||||||
|
return LoggerInitFail(flag, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRedisClientWithError 获取redis实例
|
||||||
|
//
|
||||||
|
// Author : go_developer@163.com<白茶清欢>
|
||||||
|
//
|
||||||
|
// Date : 5:16 下午 2021/2/27
|
||||||
|
func (c *OwnClient) GetRedisClientWithError(flag string) (*RealClient, error) {
|
||||||
|
c.lock.RLock()
|
||||||
|
defer c.lock.RUnlock()
|
||||||
|
redisClient, exist := c.instanceTable[flag]
|
||||||
|
if !exist {
|
||||||
|
return nil, FlagNotFound(flag)
|
||||||
|
}
|
||||||
|
return redisClient, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRedisClient ...
|
||||||
|
//
|
||||||
|
// Author : go_developer@163.com<白茶清欢>
|
||||||
|
//
|
||||||
|
// Date : 11:52 2023/8/16
|
||||||
|
func (c *OwnClient) GetRedisClient(flag string) *RealClient {
|
||||||
|
redisClient, _ := c.GetRedisClientWithError(flag)
|
||||||
|
return redisClient
|
||||||
|
}
|
||||||
|
|
||||||
|
// log 记录redis请求日志
|
||||||
|
//
|
||||||
|
// Author : go_developer@163.com<白茶清欢>
|
||||||
|
//
|
||||||
|
// Date : 8:52 下午 2021/2/27
|
||||||
|
func (c *OwnClient) log(ctx *Context, realClient *RealClient, isMaster bool, cmdResult redisInstance.Cmder, startTime int64, finishTime int64) {
|
||||||
|
if nil == realClient || nil == realClient.Logger {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
realClient.Logger.Info(
|
||||||
|
"执行redis命令日志记录",
|
||||||
|
zap.Any(ctx.RequestIDField, ctx.RequestID), // 上下文串联的requestID
|
||||||
|
zap.String("instance_flag", ctx.Flag), // redis 实例的标识
|
||||||
|
zap.Any("exec_command_name", cmdResult.Name()), // 执行的命令
|
||||||
|
zap.Any("exec_command_arg", cmdResult.Args()), // 执行的命令参数
|
||||||
|
zap.Bool("use_master", isMaster), // 是否使用主节点
|
||||||
|
zap.Float64("exec_used_time", float64(finishTime-startTime)/1e6), // 耗时,单位: ms
|
||||||
|
zap.Error(cmdResult.Err()), // 异常信息
|
||||||
|
zap.String("command_result", cmdResult.String()), // 命令执行后的结果
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommandProxy 执行命令的代理
|
||||||
|
//
|
||||||
|
// Author : go_developer@163.com<白茶清欢>
|
||||||
|
//
|
||||||
|
// Date : 9:41 下午 2021/2/27
|
||||||
|
func (c *OwnClient) CommandProxy(ctx *Context, flag string, cmd string, param ...interface{}) (string, error) {
|
||||||
|
var (
|
||||||
|
realClient *RealClient
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if len(cmd) == 0 {
|
||||||
|
return "", EmptyCmd()
|
||||||
|
}
|
||||||
|
|
||||||
|
if nil == ctx {
|
||||||
|
ctx = NewContext(flag)
|
||||||
|
}
|
||||||
|
if realClient, err = c.GetRedisClientWithError(ctx.Flag); nil != err {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
flagArr := strings.Split(flag, "#")
|
||||||
|
if len(flagArr) == 1 {
|
||||||
|
flagArr = append(flagArr, "r")
|
||||||
|
}
|
||||||
|
isMater := flagArr[1] == "w"
|
||||||
|
redisClient := realClient.Slave
|
||||||
|
if isMater {
|
||||||
|
redisClient = realClient.Master
|
||||||
|
}
|
||||||
|
redisCmd := append([]interface{}{cmd}, param...)
|
||||||
|
startTime := time.Now().Unix()
|
||||||
|
cmdResult := redisClient.Do(ctx.Ctx, redisCmd...)
|
||||||
|
go c.log(ctx, realClient, isMater, cmdResult, startTime, time.Now().UnixNano())
|
||||||
|
return fmt.Sprintf("%v", cmdResult.Val()), c.parseErrorFunc(cmdResult.Err())
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommandProxyWithReceiver 执行命令,并解析结果
|
||||||
|
//
|
||||||
|
// Author : go_developer@163.com<白茶清欢>
|
||||||
|
//
|
||||||
|
// Date : 10:00 下午 2021/2/27
|
||||||
|
func (c *OwnClient) CommandProxyWithReceiver(ctx *Context, flag string, receiver interface{}, cmd string, param ...interface{}) error {
|
||||||
|
if nil == receiver {
|
||||||
|
return ReceiverISNIL()
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
result string
|
||||||
|
)
|
||||||
|
|
||||||
|
if result, err = c.CommandProxy(ctx, flag, cmd, param...); nil != err {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResultConvertFail(json.Unmarshal([]byte(result), receiver))
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveClient 移除client
|
||||||
|
//
|
||||||
|
// Author : go_developer@163.com<白茶清欢>
|
||||||
|
//
|
||||||
|
// Date : 11:49 2023/8/16
|
||||||
|
func (c *OwnClient) RemoveClient(flag string) {
|
||||||
|
c.lock.Lock()
|
||||||
|
defer c.lock.Unlock()
|
||||||
|
delete(c.confTable, flag)
|
||||||
|
if _, exist := c.instanceTable[flag]; exist {
|
||||||
|
if nil != c.instanceTable[flag].Master {
|
||||||
|
_ = c.instanceTable[flag].Master.Close()
|
||||||
|
}
|
||||||
|
if nil != c.instanceTable[flag].Slave {
|
||||||
|
_ = c.instanceTable[flag].Slave.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete(c.instanceTable, flag)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClientInterface 定义redis client的接口实现,方便单元测试数据mock
|
||||||
|
//
|
||||||
|
// Author : go_developer@163.com<白茶清欢>
|
||||||
|
//
|
||||||
|
// Date : 10:49 下午 2021/2/27
|
||||||
|
type ClientInterface interface {
|
||||||
|
GetRedisClient(flag string) *RealClient
|
||||||
|
GetRedisClientWithError(flag string) (*RealClient, error)
|
||||||
|
CommandProxy(ctx *Context, flag string, cmd string, param ...interface{}) (string, error)
|
||||||
|
CommandProxyWithReceiver(ctx *Context, flag string, receiver interface{}, cmd string, param ...interface{}) error
|
||||||
|
AddClientWithCfgFile(cfgPath string) error
|
||||||
|
AddClientWithCfgDir(cfgDir string) error
|
||||||
|
AddClientWithCfg(flag string, cfg *FullConfig) error
|
||||||
|
RemoveClient(flag string)
|
||||||
}
|
}
|
||||||
|
@ -1,53 +0,0 @@
|
|||||||
// Package define ...
|
|
||||||
//
|
|
||||||
// Description : define ...
|
|
||||||
//
|
|
||||||
// Author : go_developer@163.com<白茶清欢>
|
|
||||||
//
|
|
||||||
// Date : 2024-06-18 16:12
|
|
||||||
package define
|
|
||||||
|
|
||||||
type ClusterOptions struct {
|
|
||||||
// A seed list of host:port addresses of cluster nodes.
|
|
||||||
Addrs []string `json:"addrs" yaml:"addrs" ini:"addrs" toml:"addrs"` // 集群IP列表
|
|
||||||
// ClientName will execute the `CLIENT SETNAME ClientName` command for each conn.
|
|
||||||
ClientName string `json:"client_name" yaml:"client_name" ini:"client_name" toml:"client_name"` // 集群名称
|
|
||||||
// The maximum number of retries before giving up. Command is retried
|
|
||||||
// on network errors and MOVED/ASK redirects.
|
|
||||||
// Default is 3 retries.
|
|
||||||
MaxRedirects int `json:"max_redirects" yaml:"max_redirects" ini:"max_redirects" toml:"max_redirects"` // 最大重试次数
|
|
||||||
// Enables read-only commands on slave nodes.
|
|
||||||
ReadOnly bool `json:"read_only" yaml:"read_only" ini:"read_only" toml:"read_only"` // 只读
|
|
||||||
// Allows routing read-only commands to the closest master or slave node.
|
|
||||||
// It automatically enables ReadOnly.
|
|
||||||
RouteByLatency bool `json:"route_by_latency" yaml:"route_by_latency" ini:"route_by_latency" toml:"route_by_latency"`
|
|
||||||
// Allows routing read-only commands to the random master or slave node.
|
|
||||||
// It automatically enables ReadOnly.
|
|
||||||
RouteRandomly bool `json:"route_randomly" yaml:"route_randomly" ini:"route_randomly" toml:"route_randomly"`
|
|
||||||
|
|
||||||
Protocol int
|
|
||||||
Username string `json:"username" yaml:"username" ini:"username" toml:"username"`
|
|
||||||
Password string `json:"password" yaml:"password" ini:"password" toml:"password"`
|
|
||||||
|
|
||||||
MaxRetries int `json:"max_retries" yaml:"max_retries" ini:"max_retries" toml:"max_retries"`
|
|
||||||
MinRetryBackoff int `json:"min_retry_backoff" yaml:"min_retry_backoff" ini:"min_retry_backoff" toml:"min_retry_backoff"` // 最小重试间隔,单位 : ms
|
|
||||||
MaxRetryBackoff int `json:"max_retry_backoff" yaml:"max_retry_backoff" ini:"max_retry_backoff" toml:"max_retry_backoff"` // 最大重试时间间隔, 单位 : ms
|
|
||||||
|
|
||||||
DialTimeout int `json:"dial_timeout" yaml:"dial_timeout" ini:"dial_timeout" toml:"dial_timeout"` // 连接超时时间
|
|
||||||
ReadTimeout int `json:"read_timeout" yaml:"read_timeout" ini:"read_timeout" toml:"read_timeout"` // 读取超时时间
|
|
||||||
WriteTimeout int `json:"write_timeout" yaml:"write_timeout" ini:"write_timeout" toml:"write_timeout"` // 写入超时时间
|
|
||||||
ContextTimeoutEnabled bool `json:"context_timeout_enabled" yaml:"context_timeout_enabled" ini:"context_timeout_enabled" toml:"context_timeout_enabled"`
|
|
||||||
|
|
||||||
PoolFIFO bool `json:"pool_fifo" yaml:"pool_fifo" ini:"pool_fifo" toml:"pool_fifo"`
|
|
||||||
PoolSize int `json:"pool_size" yaml:"pool_size" ini:"pool_size" toml:"pool_size"` // applies per cluster node and not for the whole cluster
|
|
||||||
PoolTimeout int `json:"pool_timeout" yaml:"pool_timeout" ini:"pool_timeout" toml:"pool_timeout"` // 单位 : ms
|
|
||||||
MinIdleConn int `json:"min_idle_conn" yaml:"min_idle_conn" ini:"min_idle_conn" toml:"min_idle_conn"` // 最小空闲连接数
|
|
||||||
MaxIdleConn int `json:"max_idle_conn" yaml:"max_idle_conn" ini:"max_idle_conn" toml:"max_idle_conn"` // 最大空闲连接数
|
|
||||||
MaxActiveConn int `json:"max_active_conn" yaml:"max_active_conn" ini:"max_active_conn" toml:"max_active_conn"` // applies per cluster node and not for the whole cluster
|
|
||||||
ConnMaxIdleTime int `json:"conn_max_idle_time" yaml:"conn_max_idle_time" ini:"conn_max_idle_time" toml:"conn_max_idle_time"` // 连接最大空闲时长, 单位 : s
|
|
||||||
ConnMaxLifetime int `json:"conn_max_lifetime" yaml:"conn_max_lifetime" ini:"conn_max_lifetime" toml:"conn_max_lifetime"` // 连接最大存活时长, 单位ms
|
|
||||||
|
|
||||||
DisableIdentity bool `json:"disable_identity" yaml:"disable_identity" ini:"disable_identity" toml:"disable_identity"` // Disable set-lib on connect. Default is false.
|
|
||||||
|
|
||||||
IdentitySuffix string `json:"identity_suffix" yaml:"identity_suffix" ini:"identity_suffix" toml:"identity_suffix"` // Add suffix to client name. Default is empty.
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
// Package define ...
|
|
||||||
//
|
|
||||||
// Description : define ...
|
|
||||||
//
|
|
||||||
// Author : go_developer@163.com<白茶清欢>
|
|
||||||
//
|
|
||||||
// Date : 2024-06-19 10:49
|
|
||||||
package define
|
|
||||||
|
|
||||||
// RedisResult redis名玲玲执行结果
|
|
||||||
//
|
|
||||||
// Author : go_developer@163.com<白茶清欢>
|
|
||||||
//
|
|
||||||
// Date : 10:50 2024/6/19
|
|
||||||
type RedisResult struct {
|
|
||||||
InstanceFlag string `json:"instance_flag"` // 实例标识
|
|
||||||
StartTime int64 `json:"start_time"` // 开始执行时间, 单位 : ms
|
|
||||||
FinishTime int64 `json:"finish_time"` // 完成执行时间, 单位 : ms
|
|
||||||
UsedTime int64 `json:"used_time"` // 执行耗时, 单位 : ms
|
|
||||||
Result string `json:"result"` // 执行结果
|
|
||||||
Command string `json:"command"` // 执行的命令
|
|
||||||
ArgList []string `json:"arg_list"` // 参数列表
|
|
||||||
Err error `json:"-"` // 失败信息
|
|
||||||
}
|
|
1
go.mod
1
go.mod
@ -46,7 +46,6 @@ require (
|
|||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/mozillazg/go-pinyin v0.20.0 // indirect
|
github.com/mozillazg/go-pinyin v0.20.0 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||||
github.com/redis/go-redis/v9 v9.5.3 // indirect
|
|
||||||
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||||
|
2
go.sum
2
go.sum
@ -161,8 +161,6 @@ 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/redis/go-redis/v9 v9.5.3 h1:fOAp1/uJG+ZtcITgZOfYFmTKPE7n4Vclj1wZFgRciUU=
|
|
||||||
github.com/redis/go-redis/v9 v9.5.3/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
|
|
||||||
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
|
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
|
||||||
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
39
logger.go
Normal file
39
logger.go
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
// Package redis...
|
||||||
|
//
|
||||||
|
// Description : redis...
|
||||||
|
//
|
||||||
|
// Author : go_developer@163.com<白茶清欢>
|
||||||
|
//
|
||||||
|
// Date : 2021-02-27 5:26 下午
|
||||||
|
package redis
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.zhangdeman.cn/zhangdeman/logger"
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LoggerConfig 日志配置
|
||||||
|
//
|
||||||
|
// Author : go_developer@163.com<白茶清欢>
|
||||||
|
//
|
||||||
|
// Date : 5:26 下午 2021/2/27
|
||||||
|
type LoggerConfig struct {
|
||||||
|
LoggerPath string
|
||||||
|
LoggerFile string
|
||||||
|
LoggerLevel zapcore.Level
|
||||||
|
ConsoleOutput bool
|
||||||
|
Encoder zapcore.Encoder
|
||||||
|
SplitConfig *logger.RotateLogConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogFieldConfig 日志字段配置
|
||||||
|
//
|
||||||
|
// Author : go_developer@163.com<白茶清欢>
|
||||||
|
//
|
||||||
|
// Date : 9:20 下午 2021/2/27
|
||||||
|
type LogFieldConfig struct {
|
||||||
|
Message string
|
||||||
|
UsedTimeField string
|
||||||
|
CommandField string
|
||||||
|
FlagField string
|
||||||
|
}
|
125
wrapper.go
125
wrapper.go
@ -1,125 +0,0 @@
|
|||||||
// Package redis ...
|
|
||||||
//
|
|
||||||
// Description : redis ...
|
|
||||||
//
|
|
||||||
// Author : go_developer@163.com<白茶清欢>
|
|
||||||
//
|
|
||||||
// Date : 2024-06-19 16:15
|
|
||||||
package redis
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"git.zhangdeman.cn/zhangdeman/redis/define"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
Wrapper = &wrapper{}
|
|
||||||
)
|
|
||||||
|
|
||||||
// wrapper 常用命令的包装
|
|
||||||
//
|
|
||||||
// Author : go_developer@163.com<白茶清欢>
|
|
||||||
//
|
|
||||||
// Date : 16:16 2024/6/19
|
|
||||||
type wrapper struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get Get命令
|
|
||||||
//
|
|
||||||
// Author : go_developer@163.com<白茶清欢>
|
|
||||||
//
|
|
||||||
// Date : 16:17 2024/6/19
|
|
||||||
func (w *wrapper) Get(ctx context.Context, instanceFlag string, key string) *define.RedisResult {
|
|
||||||
return Client.Exec(ctx, instanceFlag, "GET", key)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Del 删除命令
|
|
||||||
//
|
|
||||||
// Author : go_developer@163.com<白茶清欢>
|
|
||||||
//
|
|
||||||
// Date : 16:19 2024/6/19
|
|
||||||
func (w *wrapper) Del(ctx context.Context, instanceFlag string, keyList ...string) *define.RedisResult {
|
|
||||||
return Client.Exec(ctx, instanceFlag, "DEL", strings.Join(keyList, " "))
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetEx 设置数据并且带有效期, 有效期单位 : s
|
|
||||||
//
|
|
||||||
// Author : go_developer@163.com<白茶清欢>
|
|
||||||
//
|
|
||||||
// Date : 16:22 2024/6/19
|
|
||||||
func (w *wrapper) SetEx(ctx context.Context, instanceFlag string, key string, value string, ttl int64, withLock bool) *define.RedisResult {
|
|
||||||
now := time.Now().Unix()
|
|
||||||
if ttl > now {
|
|
||||||
// 传入过期时间大于当前时间, 说明是指定具体时间过期, 做一下处理
|
|
||||||
ttl = ttl - now
|
|
||||||
}
|
|
||||||
if withLock {
|
|
||||||
return Client.Exec(ctx, instanceFlag, "SET", key, value, "EX", ttl, "NX")
|
|
||||||
}
|
|
||||||
return Client.Exec(ctx, instanceFlag, "SET", key, value, "EX", ttl)
|
|
||||||
}
|
|
||||||
|
|
||||||
// LPop ...
|
|
||||||
//
|
|
||||||
// Author : go_developer@163.com<白茶清欢>
|
|
||||||
//
|
|
||||||
// Date : 16:29 2024/6/19
|
|
||||||
func (w *wrapper) LPop(ctx context.Context, instanceFlag string, key string) *define.RedisResult {
|
|
||||||
return Client.Exec(ctx, instanceFlag, "LPOP", key)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RPop ...
|
|
||||||
//
|
|
||||||
// Author : go_developer@163.com<白茶清欢>
|
|
||||||
//
|
|
||||||
// Date : 16:30 2024/6/19
|
|
||||||
func (w *wrapper) RPop(ctx context.Context, instanceFlag string, key string) *define.RedisResult {
|
|
||||||
return Client.Exec(ctx, instanceFlag, "RPOP", key)
|
|
||||||
}
|
|
||||||
|
|
||||||
// LPush ...
|
|
||||||
//
|
|
||||||
// Author : go_developer@163.com<白茶清欢>
|
|
||||||
//
|
|
||||||
// Date : 16:31 2024/6/19
|
|
||||||
func (w *wrapper) LPush(ctx context.Context, instanceFlag string, key string, value string) *define.RedisResult {
|
|
||||||
return Client.Exec(ctx, instanceFlag, "LPUSH", key, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RPush ...
|
|
||||||
//
|
|
||||||
// Author : go_developer@163.com<白茶清欢>
|
|
||||||
//
|
|
||||||
// Date : 16:31 2024/6/19
|
|
||||||
func (w *wrapper) RPush(ctx context.Context, instanceFlag string, key string, value string) *define.RedisResult {
|
|
||||||
return Client.Exec(ctx, instanceFlag, "RPUSH", key, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HGet ...
|
|
||||||
//
|
|
||||||
// Author : go_developer@163.com<白茶清欢>
|
|
||||||
//
|
|
||||||
// Date : 16:33 2024/6/19
|
|
||||||
func (w *wrapper) HGet(ctx context.Context, instanceFlag string, key string, field string) *define.RedisResult {
|
|
||||||
return Client.Exec(ctx, instanceFlag, "HGET", key, field)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HSet ...
|
|
||||||
//
|
|
||||||
// Author : go_developer@163.com<白茶清欢>
|
|
||||||
//
|
|
||||||
// Date : 16:33 2024/6/19
|
|
||||||
func (w *wrapper) HSet(ctx context.Context, instanceFlag string, key string, field string, value string) *define.RedisResult {
|
|
||||||
return Client.Exec(ctx, instanceFlag, "HSET", key, field, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HDel ...
|
|
||||||
//
|
|
||||||
// Author : go_developer@163.com<白茶清欢>
|
|
||||||
//
|
|
||||||
// Date : 16:37 2024/6/19
|
|
||||||
func (w *wrapper) HDel(ctx context.Context, instanceFlag string, key string, fieldList ...string) *define.RedisResult {
|
|
||||||
return Client.Exec(ctx, instanceFlag, "HSET", key, strings.Join(fieldList, " "))
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user