├── l_test.go ├── tests ├── main.go └── etc │ └── config.yaml ├── utils ├── string.go ├── file.go ├── option.go └── funs.go ├── app_option.go ├── .vscode └── settings.json ├── db ├── dbx │ ├── option.go │ ├── log.go │ ├── gen.go │ ├── gorm.go │ └── query.go ├── mongox │ ├── option.go │ ├── collection.go │ └── mongo.go └── redisx │ ├── redis_option.go │ └── redis.go ├── log ├── log_option.go └── log.go ├── resources ├── casbinx │ ├── option.go │ ├── casbin.go │ └── redis_adapter.go └── jwt │ ├── option.go │ └── jwt.go ├── httpx ├── option.go ├── errcode.go ├── controler.go └── gin.go ├── config ├── defined.go ├── read.go └── config.go ├── typesx ├── gorm.go └── decimal.go ├── monitoring └── redis.go ├── README-CN.md ├── README.md ├── go.mod ├── app.go └── go.sum /l_test.go: -------------------------------------------------------------------------------- 1 | package tool 2 | 3 | import ( 4 | "testing" 5 | 6 | _ "github.com/smartystreets/goconvey/convey" 7 | ) 8 | 9 | func TestCmd(t *testing.T) { 10 | NewApp() 11 | } 12 | -------------------------------------------------------------------------------- /tests/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "github.com/wjoj/tool/v2" 4 | 5 | func main() { 6 | tool.NewApp(). 7 | Redis(). 8 | Gorm(). 9 | HttpServer(). 10 | Run() 11 | } 12 | -------------------------------------------------------------------------------- /utils/string.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import "time" 4 | 5 | type Duration string 6 | 7 | func (d Duration) ParseDuration() (time.Duration, error) { 8 | return time.ParseDuration(string(d)) 9 | } 10 | -------------------------------------------------------------------------------- /utils/file.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import "os" 4 | 5 | func FileOpenAppend(name string) (*os.File, error) { 6 | return os.OpenFile(name, os.O_RDWR|os.O_APPEND, os.ModePerm) 7 | } 8 | 9 | func FileRead(path string) ([]byte, error) { 10 | return os.ReadFile(path) 11 | } 12 | -------------------------------------------------------------------------------- /app_option.go: -------------------------------------------------------------------------------- 1 | package tool 2 | 3 | type Options struct { 4 | quit bool 5 | } 6 | 7 | type Option func(c *Options) 8 | 9 | func WithQuitEnableOption() Option { 10 | return func(c *Options) { 11 | c.quit = true 12 | } 13 | } 14 | 15 | func applyOptions(options ...Option) Options { 16 | opts := Options{ 17 | quit: false, 18 | } 19 | for _, option := range options { 20 | if option == nil { 21 | continue 22 | } 23 | option(&opts) 24 | } 25 | return opts 26 | } 27 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "go.useLanguageServer": true, 3 | "go.inferGopath": false, 4 | "go.gopath": "/Users/joj/Documents/GO/qb", // 5 | "gopls": { 6 | "usePlaceholders": true, 7 | "completeUnimported": true, 8 | "completionDocumentation": true, 9 | "hoverKind": "SynopsisDocumentation", 10 | }, 11 | "files.eol": "\n", 12 | "commentTranslate.targetLanguage": "zh-CN", 13 | "terminal.integrated.env.osx": { 14 | "GOPATH": "/Users/joj/Documents/GO/qb" 15 | } 16 | } -------------------------------------------------------------------------------- /utils/option.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | var ( 4 | DefaultKey = &DefaultKeys{ 5 | DefaultKey: "def", 6 | Keys: []string{}, 7 | } 8 | ) 9 | 10 | type DefaultKeys struct { 11 | DefaultKey string 12 | Keys []string 13 | } 14 | 15 | type DefaultOption func(c *DefaultKeys) 16 | 17 | // 设置默认key 18 | func WithDefaultKeyOption(key string) DefaultOption { 19 | return func(c *DefaultKeys) { 20 | c.DefaultKey = key 21 | } 22 | } 23 | 24 | // 设置日志要使用配置文件的key 25 | func WithLogConfigKeysOption(keys ...string) DefaultOption { 26 | return func(c *DefaultKeys) { 27 | c.Keys = keys 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /db/dbx/option.go: -------------------------------------------------------------------------------- 1 | package dbx 2 | 3 | import "github.com/wjoj/tool/v2/utils" 4 | 5 | type Options struct { 6 | defKey *utils.DefaultKeys 7 | } 8 | 9 | type Option func(c *Options) 10 | 11 | // 设置默认key 12 | func WithDefaultKeyOption(key string) Option { 13 | return func(c *Options) { 14 | c.defKey.DefaultKey = key 15 | } 16 | } 17 | 18 | // 设置要使用配置文件的key 19 | func WithLogConfigKeysOption(keys ...string) Option { 20 | return func(c *Options) { 21 | c.defKey.Keys = keys 22 | } 23 | } 24 | 25 | func applyOptions(options ...Option) Options { 26 | opts := Options{ 27 | defKey: utils.DefaultKey, 28 | } 29 | for _, option := range options { 30 | if option == nil { 31 | continue 32 | } 33 | option(&opts) 34 | } 35 | return opts 36 | } 37 | -------------------------------------------------------------------------------- /db/mongox/option.go: -------------------------------------------------------------------------------- 1 | package mongox 2 | 3 | import "github.com/wjoj/tool/v2/utils" 4 | 5 | type Options struct { 6 | defKey *utils.DefaultKeys 7 | } 8 | 9 | type Option func(c *Options) 10 | 11 | // 设置默认key 12 | func WithDefaultKeyOption(key string) Option { 13 | return func(c *Options) { 14 | c.defKey.DefaultKey = key 15 | } 16 | } 17 | 18 | // 设置要使用配置文件的key 19 | func WithLogConfigKeysOption(keys ...string) Option { 20 | return func(c *Options) { 21 | c.defKey.Keys = keys 22 | } 23 | } 24 | 25 | func applyGenGormOptions(options ...Option) Options { 26 | opts := Options{ 27 | defKey: utils.DefaultKey, 28 | } 29 | for _, option := range options { 30 | if option == nil { 31 | continue 32 | } 33 | option(&opts) 34 | } 35 | return opts 36 | } 37 | -------------------------------------------------------------------------------- /log/log_option.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import "github.com/wjoj/tool/v2/utils" 4 | 5 | type Options struct { 6 | defKey *utils.DefaultKeys 7 | } 8 | 9 | type Option func(c *Options) 10 | 11 | // 设置默认key 12 | func WithDefaultKeyOption(key string) Option { 13 | return func(c *Options) { 14 | c.defKey.DefaultKey = key 15 | } 16 | } 17 | 18 | // 设置日志要使用配置文件的key 19 | func WithLogConfigKeysOption(keys ...string) Option { 20 | return func(c *Options) { 21 | c.defKey.Keys = keys 22 | } 23 | } 24 | 25 | func applyGenGormOptions(options ...Option) Options { 26 | opts := Options{ 27 | defKey: utils.DefaultKey, 28 | } 29 | for _, option := range options { 30 | if option == nil { 31 | continue 32 | } 33 | option(&opts) 34 | } 35 | return opts 36 | } 37 | -------------------------------------------------------------------------------- /resources/casbinx/option.go: -------------------------------------------------------------------------------- 1 | package casbinx 2 | 3 | import "github.com/wjoj/tool/v2/utils" 4 | 5 | type Options struct { 6 | defKey *utils.DefaultKeys 7 | } 8 | 9 | type Option func(c *Options) 10 | 11 | // 设置默认key 12 | func WithDefaultKeyOption(key string) Option { 13 | return func(c *Options) { 14 | c.defKey.DefaultKey = key 15 | } 16 | } 17 | 18 | // 设置日志要使用配置文件的key 19 | func WithLogConfigKeysOption(keys ...string) Option { 20 | return func(c *Options) { 21 | c.defKey.Keys = keys 22 | } 23 | } 24 | 25 | func applyOptions(options ...Option) Options { 26 | opts := Options{ 27 | defKey: utils.DefaultKey, 28 | } 29 | for _, option := range options { 30 | if option == nil { 31 | continue 32 | } 33 | option(&opts) 34 | } 35 | return opts 36 | } 37 | -------------------------------------------------------------------------------- /resources/jwt/option.go: -------------------------------------------------------------------------------- 1 | package jwt 2 | 3 | import "github.com/wjoj/tool/v2/utils" 4 | 5 | type Options struct { 6 | defKey *utils.DefaultKeys 7 | } 8 | 9 | type Option func(c *Options) 10 | 11 | // 设置默认key 12 | func WithDefaultKeyOption(key string) Option { 13 | return func(c *Options) { 14 | c.defKey.DefaultKey = key 15 | } 16 | } 17 | 18 | // 设置日志要使用配置文件的key 19 | func WithLogConfigKeysOption(keys ...string) Option { 20 | return func(c *Options) { 21 | c.defKey.Keys = keys 22 | } 23 | } 24 | 25 | func applyGenGormOptions(options ...Option) Options { 26 | opts := Options{ 27 | defKey: utils.DefaultKey, 28 | } 29 | for _, option := range options { 30 | if option == nil { 31 | continue 32 | } 33 | option(&opts) 34 | } 35 | return opts 36 | } 37 | -------------------------------------------------------------------------------- /db/redisx/redis_option.go: -------------------------------------------------------------------------------- 1 | package redisx 2 | 3 | import "github.com/wjoj/tool/v2/utils" 4 | 5 | type Options struct { 6 | defKey *utils.DefaultKeys 7 | } 8 | 9 | type Option func(c *Options) 10 | 11 | // 设置默认key 12 | func WithDefaultKeyOption(key string) Option { 13 | return func(c *Options) { 14 | c.defKey.DefaultKey = key 15 | } 16 | } 17 | 18 | // 设置日志要使用配置文件的key 19 | func WithLogConfigKeysOption(keys ...string) Option { 20 | return func(c *Options) { 21 | c.defKey.Keys = keys 22 | } 23 | } 24 | 25 | func applyGenGormOptions(options ...Option) Options { 26 | opts := Options{ 27 | defKey: utils.DefaultKey, 28 | } 29 | for _, option := range options { 30 | if option == nil { 31 | continue 32 | } 33 | option(&opts) 34 | } 35 | return opts 36 | } 37 | -------------------------------------------------------------------------------- /httpx/option.go: -------------------------------------------------------------------------------- 1 | package httpx 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/wjoj/tool/v2/utils" 6 | ) 7 | 8 | type Options struct { 9 | defKey *utils.DefaultKeys 10 | engFuncs map[string][]func(eng *gin.Engine) 11 | } 12 | 13 | type Option func(c *Options) 14 | 15 | // 设置默认key 16 | func WithDefaultKeyOption(key string) Option { 17 | return func(c *Options) { 18 | c.defKey.DefaultKey = key 19 | } 20 | } 21 | 22 | // 设置要使用配置文件的key 23 | func WithLogConfigKeysOption(keys ...string) Option { 24 | return func(c *Options) { 25 | c.defKey.Keys = keys 26 | } 27 | } 28 | 29 | // WithGinEngineFuncOption 创建一个Option函数,用于向Options中添加Gin引擎的处理函数 30 | // 参数: 31 | // 32 | // key: 用于标识这组处理函数的键 33 | // fs: 一个或多个处理Gin引擎的函数 34 | // 35 | // 返回值: 36 | // 37 | // 返回一个Option类型的函数,该函数会将处理函数添加到Options的engFuncs映射中 38 | func WithGinEngineFuncOption(key string, fs ...func(eng *gin.Engine)) Option { 39 | return func(c *Options) { 40 | c.engFuncs[key] = fs 41 | } 42 | } 43 | 44 | func applyGenGormOptions(options ...Option) Options { 45 | opts := Options{ 46 | defKey: utils.DefaultKey, 47 | engFuncs: make(map[string][]func(eng *gin.Engine)), 48 | } 49 | for _, option := range options { 50 | if option == nil { 51 | continue 52 | } 53 | option(&opts) 54 | } 55 | return opts 56 | } 57 | -------------------------------------------------------------------------------- /tests/etc/config.yaml: -------------------------------------------------------------------------------- 1 | env: staging 2 | namespace: "test" 3 | 4 | http: 5 | def: 6 | port: 8080 7 | shutdownCloseMaxWait: 10s 8 | log: true 9 | logName: 10 | debug: false 11 | ping: true 12 | swagger: false 13 | routePrefix: api 14 | cors: false 15 | 16 | logs: 17 | def: 18 | level: debug 19 | levelColor: true #是否启用颜色 20 | out: stdout #输出类型 21 | outFormat: console #输出格式 console 22 | path: log #日志文件路径 23 | maxSize: 1024 #日志文件最大大小以MB为单位) 24 | maxBackups: 10 #保留的最大旧日志文件数 25 | maxAge: 7 #保留的最大旧日志文件天数 26 | compress: false #是否压缩旧日志文件 27 | 28 | dbs: 29 | def: #默认配置 30 | driver: mysql 31 | host: localhost 32 | port: 3308 33 | user: root 34 | password: root 35 | dbname: test 36 | debug: true 37 | prefix: 38 | charset: utf8mb4 39 | maxIdleConns: 100 40 | maxOpenConns: 100 41 | connMaxLifetime: 10m 42 | connMaxIdleTime: 10m 43 | timeout: 10 44 | logLevel: info 45 | log: 46 | 47 | rediss: 48 | def: 49 | addrs: 50 | - localhost:6379 51 | isCluster: false 52 | username: 53 | password: 54 | readTimeout: 55 | writeTimeout: 56 | poolSize: 57 | minIdleConns: 58 | maxConnAge: 59 | maxIdleConns: 60 | maxActiveConns: 61 | poolTimeout: 62 | idleTimeout: 63 | connMaxIdleTime: 64 | connMaxLifetime: -------------------------------------------------------------------------------- /config/defined.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "github.com/wjoj/tool/v2/db/dbx" 5 | "github.com/wjoj/tool/v2/db/mongox" 6 | "github.com/wjoj/tool/v2/db/redisx" 7 | "github.com/wjoj/tool/v2/httpx" 8 | "github.com/wjoj/tool/v2/log" 9 | "github.com/wjoj/tool/v2/resources/casbinx" 10 | "github.com/wjoj/tool/v2/resources/jwt" 11 | ) 12 | 13 | var defaultKey = "def" 14 | 15 | type EnvType string 16 | 17 | const ( 18 | EnvDevelopment EnvType = "dev" // 开发环境 19 | EnvTesting EnvType = "test" // 测试环境 20 | EnvStaging EnvType = "staging" // 预发布环境 21 | EnvProduction EnvType = "prod" // 生产环境 22 | ) 23 | 24 | type App struct { 25 | Env EnvType `yaml:"env" json:"env"` //开发环境 26 | Namespace string `yaml:"namespace" json:"namespace"` //命名空间 27 | Logs map[string]log.Config `yaml:"logs" json:"logs"` //日志配置 28 | Rediss map[string]redisx.Config `yaml:"rediss" json:"rediss"` //redis配置 29 | Dbs map[string]dbx.Config `yaml:"dbs" json:"dbs"` //db配置 30 | Mongos map[string]mongox.Config `yaml:"mongos" json:"mongos"` //mongo配置 31 | Http map[string]httpx.Config `yaml:"http" json:"http"` //http配置 32 | Casbins map[string]casbinx.Config `yaml:"casbins" json:"casbins"` //casbin配置 33 | Jwts map[string]jwt.Config `yaml:"jwts" json:"jwts"` 34 | } 35 | -------------------------------------------------------------------------------- /httpx/errcode.go: -------------------------------------------------------------------------------- 1 | package httpx 2 | 3 | type ErrMsgData struct { 4 | Code ErrCodeType `json:"code"` 5 | Msg string `json:"msg"` 6 | Data any `json:"data"` 7 | } 8 | 9 | // 实现Error接口,返回ErrMsgData结构体的Msg字段 10 | func (e ErrMsgData) Error() string { 11 | return e.Msg 12 | } 13 | 14 | func (e ErrMsgData) GetCode() int { 15 | return int(e.Code) 16 | } 17 | 18 | func (e ErrMsgData) GetData() any { 19 | return e.Data 20 | } 21 | 22 | type ErrCodeType int 23 | 24 | const ( 25 | ErrCodeTypeSuccess ErrCodeType = 0 26 | ErrCodeTypeFail ErrCodeType = 10000 + iota 27 | ) 28 | 29 | func (e ErrCodeType) Error() string { 30 | if e == ErrCodeTypeSuccess { 31 | return "success" 32 | } 33 | return "fail" 34 | } 35 | 36 | func (e ErrCodeType) String() string { 37 | if e == ErrCodeTypeSuccess { 38 | return "success" 39 | } 40 | return "fail" 41 | } 42 | 43 | func (e ErrCodeType) GetCode() int { 44 | return int(e) 45 | } 46 | 47 | func (e ErrCodeType) SetError(msg error) ErrMsgData { 48 | return ErrMsgData{ 49 | Code: e, 50 | Msg: msg.Error(), 51 | Data: nil, 52 | } 53 | } 54 | func (e ErrCodeType) SetData(data any) ErrMsgData { 55 | return ErrMsgData{ 56 | Code: e, 57 | Msg: "", 58 | Data: data, 59 | } 60 | } 61 | 62 | func (e ErrCodeType) SetMsg(msg string) ErrMsgData { 63 | return ErrMsgData{ 64 | Code: e, 65 | Msg: msg, 66 | Data: nil, 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /utils/funs.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func Get[T any](fname, def string, fMap func(string) (T, bool), key ...string) (T, error) { 8 | if len(key) == 0 { 9 | cli, is := fMap(def) 10 | if is { 11 | return cli, nil 12 | } 13 | return cli, fmt.Errorf("[get] %s %s not found", fname, def) 14 | } 15 | cli, is := fMap(key[0]) 16 | if is { 17 | return cli, nil 18 | } 19 | return cli, fmt.Errorf("[get] %s %s not found", fname, key[0]) 20 | } 21 | 22 | func Init[T any, C any](fname, def string, keys []string, cfgs map[string]C, fNew func(cfg C) (T, error), fDef func(T)) (map[string]T, error) { 23 | dMap := make(map[string]T) 24 | if len(keys) != 0 { 25 | keys = append(keys, def) 26 | for _, key := range keys { 27 | _, is := dMap[key] 28 | if is { 29 | continue 30 | } 31 | cfg, is := cfgs[key] 32 | if !is { 33 | return nil, fmt.Errorf("%s %s not found", fname, key) 34 | } 35 | cli, err := fNew(cfg) 36 | if err != nil { 37 | return nil, fmt.Errorf("init %s %s init error: %v", fname, key, err) 38 | } 39 | dMap[key] = cli 40 | if key == def { 41 | fDef(cli) 42 | } 43 | } 44 | return dMap, nil 45 | } 46 | for name, cfg := range cfgs { 47 | cli, err := fNew(cfg) 48 | if err != nil { 49 | return nil, fmt.Errorf("init %s %s init error: %v", fname, name, err) 50 | } 51 | dMap[name] = cli 52 | if name == def { 53 | fDef(cli) 54 | } 55 | } 56 | return dMap, nil 57 | } 58 | -------------------------------------------------------------------------------- /config/read.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "os" 7 | "path/filepath" 8 | "strings" 9 | 10 | "github.com/fsnotify/fsnotify" 11 | "github.com/go-viper/mapstructure/v2" 12 | "github.com/spf13/viper" 13 | ) 14 | 15 | func Read(cfgRoot, cfgFile string) error { 16 | cfgpath := filepath.Join(cfgRoot, cfgFile) 17 | ext := strings.ToLower(strings.Replace(filepath.Ext(cfgFile), ".", "", 1)) 18 | if ext == "yml" { 19 | ext = "yaml" 20 | } 21 | viper.SetConfigFile(cfgpath) 22 | viper.SetConfigType(ext) 23 | if err := viper.ReadInConfig(); err != nil { 24 | return errors.New("read config file failed: " + err.Error()) 25 | } 26 | tagName := ext 27 | if ext == "yml" { 28 | tagName = "yaml" 29 | } 30 | var cfg *App 31 | if err := viper.Unmarshal(&cfg, decoderTagName(tagName)); err != nil { 32 | return errors.New("unmarshal config failed: " + err.Error()) 33 | } 34 | if len(cfg.Env) == 0 { 35 | cfg.Env = "dev" 36 | } 37 | envstr := os.Getenv("ENV") 38 | if len(envstr) != 0 { 39 | cfg.Env = EnvType(envstr) 40 | } 41 | 42 | SetEnv(cfg.Env) 43 | SetConfigRoot(cfgRoot) 44 | SetConfigFile(cfgFile) 45 | SetNamespace(cfg.Namespace) 46 | SetLog(cfg.Logs) 47 | SetRediss(cfg.Rediss) 48 | SetDbs(cfg.Dbs) 49 | SetMongos(cfg.Mongos) 50 | SetHttp(cfg.Http) 51 | SetCasbins(cfg.Casbins) 52 | SetJwts(cfg.Jwts) 53 | viper.OnConfigChange(func(e fsnotify.Event) { // 监听配置文件修改 54 | fmt.Printf("config file changed:%+v\n", e) 55 | }) 56 | viper.WatchConfig() 57 | return nil 58 | } 59 | 60 | func decoderTagName(tag string) viper.DecoderConfigOption { 61 | return func(dc *mapstructure.DecoderConfig) { 62 | dc.TagName = tag 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /db/dbx/log.go: -------------------------------------------------------------------------------- 1 | package dbx 2 | 3 | import ( 4 | // ... existing imports ... 5 | "context" 6 | "time" 7 | 8 | "go.uber.org/zap" 9 | "go.uber.org/zap/zapcore" 10 | "gorm.io/gorm/logger" 11 | ) 12 | 13 | type zapLogger struct { 14 | *zap.SugaredLogger 15 | } 16 | 17 | func (l *zapLogger) LogMode(level logger.LogLevel) logger.Interface { 18 | if level == logger.Silent { 19 | l.SugaredLogger = l.WithOptions(zap.IncreaseLevel(zapcore.Level(zapcore.InvalidLevel))) 20 | } else if level == logger.Info { 21 | l.SugaredLogger = l.WithOptions(zap.IncreaseLevel(zapcore.Level(zapcore.InfoLevel))) 22 | } else if level == logger.Warn { 23 | l.SugaredLogger = l.WithOptions(zap.IncreaseLevel(zapcore.Level(zapcore.WarnLevel))) 24 | } else if level == logger.Error { 25 | l.SugaredLogger = l.WithOptions(zap.IncreaseLevel(zapcore.Level(zapcore.ErrorLevel))) 26 | } 27 | return l 28 | } 29 | 30 | func (l *zapLogger) Info(ctx context.Context, msg string, data ...any) { 31 | l.SugaredLogger.Infof(msg, data...) 32 | } 33 | 34 | func (l *zapLogger) Warn(ctx context.Context, msg string, data ...any) { 35 | l.SugaredLogger.Warnf(msg, data...) 36 | } 37 | 38 | func (l *zapLogger) Error(ctx context.Context, msg string, data ...any) { 39 | l.SugaredLogger.Errorf(msg, data...) 40 | } 41 | 42 | func (l *zapLogger) Trace(ctx context.Context, begin time.Time, fc func() (string, int64), err error) { 43 | sql, rows := fc() 44 | l.Desugar().Debug("gorm trace", 45 | zap.String("sql", sql), 46 | zap.Int64("rows", rows), 47 | zap.Error(err), 48 | zap.Duration("duration", time.Since(begin)), 49 | ) 50 | } 51 | 52 | func (l *zapLogger) Printf(f string, msg ...interface{}) { 53 | l.SugaredLogger.Logf(zap.InfoLevel, f, msg...) 54 | } 55 | 56 | func WithLogger(logger *zap.SugaredLogger) logger.Interface { 57 | return &zapLogger{logger} 58 | } 59 | -------------------------------------------------------------------------------- /typesx/gorm.go: -------------------------------------------------------------------------------- 1 | package typesx 2 | 3 | import ( 4 | "database/sql/driver" 5 | "errors" 6 | "fmt" 7 | "strconv" 8 | "time" 9 | ) 10 | 11 | type Time struct { 12 | time.Time 13 | } 14 | 15 | func (j *Time) Scan(value any) error { 16 | v, ok := value.(time.Time) 17 | if !ok { 18 | return fmt.Errorf("time conversion error: %T", value) 19 | } 20 | 21 | *j = Time{ 22 | Time: v, 23 | } 24 | return nil 25 | } 26 | 27 | func (j Time) Value() (driver.Value, error) { 28 | if j.IsZero() { 29 | return nil, nil 30 | } 31 | return j.Local().Format(time.DateTime), nil 32 | } 33 | 34 | func (s Time) MarshalJSON() ([]byte, error) { 35 | if s.IsZero() { 36 | return []byte(`0`), nil 37 | } 38 | return []byte(strconv.FormatInt(s.Local().Unix(), 10)), nil 39 | } 40 | 41 | func (s *Time) UnmarshalJSON(data []byte) error { 42 | if s == nil { 43 | return errors.New("unmarshaling JSON value is empty(time)") 44 | } 45 | t, err := time.Parse("2006-01-02 15:04:05", string(data)) 46 | if err != nil { 47 | return err 48 | } 49 | *s = Time{Time: t} 50 | return nil 51 | } 52 | 53 | type Date struct { 54 | time.Time 55 | } 56 | 57 | func NewDataString(str string) (Date, error) { 58 | t, err := time.Parse(time.DateOnly, str) 59 | return Date{ 60 | Time: t, 61 | }, err 62 | } 63 | 64 | func (j *Date) Scan(value any) error { 65 | v, ok := value.(time.Time) 66 | if !ok { 67 | return fmt.Errorf("date to time conversion error: %T", value) 68 | } 69 | 70 | *j = Date{ 71 | Time: v, 72 | } 73 | return nil 74 | } 75 | 76 | func (j Date) Value() (driver.Value, error) { 77 | if j.IsZero() { 78 | return nil, nil 79 | } 80 | return j.Local().Format(time.DateOnly), nil 81 | } 82 | 83 | func (s Date) MarshalJSON() ([]byte, error) { 84 | if s.IsZero() { 85 | return []byte(`""`), nil 86 | } 87 | return []byte(`"` + s.Local().Format("2006-01-02") + `"`), nil 88 | } 89 | 90 | func (s *Date) UnmarshalJSON(data []byte) error { 91 | if s == nil { 92 | return errors.New("null point exception") 93 | } 94 | t, err := time.Parse("2006-01-02", string(data)) 95 | if err != nil { 96 | return err 97 | } 98 | *s = Date{Time: t} 99 | return nil 100 | } 101 | -------------------------------------------------------------------------------- /monitoring/redis.go: -------------------------------------------------------------------------------- 1 | package monitoring 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "github.com/prometheus/client_golang/prometheus" 8 | "github.com/prometheus/client_golang/prometheus/promauto" 9 | "github.com/redis/go-redis/v9" 10 | ) 11 | 12 | // 添加 Prometheus 指标定义 13 | var ( 14 | redisConnectionsActive = promauto.NewGauge(prometheus.GaugeOpts{ 15 | Name: "redis_connections_active", 16 | Help: "Current number of active connections in the pool", 17 | }) 18 | 19 | redisConnectionsIdle = promauto.NewGauge(prometheus.GaugeOpts{ 20 | Name: "redis_connections_idle", 21 | Help: "Current number of idle connections in the pool", 22 | }) 23 | 24 | redisCommandsTotal = promauto.NewCounterVec(prometheus.CounterOpts{ 25 | Name: "redis_commands_total", 26 | Help: "Total number of Redis commands executed", 27 | }, []string{"command"}) 28 | 29 | redisCommandDuration = promauto.NewHistogramVec(prometheus.HistogramOpts{ 30 | Name: "redis_command_duration_seconds", 31 | Help: "Duration of Redis commands execution", 32 | Buckets: []float64{.001, .005, .01, .025, .05, .1, .25, .5, 1}, 33 | }, []string{"command"}) 34 | redisHealthMetric = promauto.NewGauge(prometheus.GaugeOpts{ 35 | Name: "redis_health_status", 36 | Help: "Redis health status (1 = healthy, 0 = unhealthy)", 37 | }) 38 | redisLatencyMetric = promauto.NewHistogram(prometheus.HistogramOpts{ 39 | Name: "redis_latency_seconds", 40 | Help: "Redis command latency in seconds", 41 | Buckets: prometheus.DefBuckets, 42 | }) 43 | ) 44 | 45 | // 添加指标收集方法 46 | func collectMetrics(stats *redis.PoolStats) { 47 | if stats != nil { 48 | redisConnectionsActive.Set(float64(stats.TotalConns - stats.IdleConns)) 49 | redisConnectionsIdle.Set(float64(stats.IdleConns)) 50 | } 51 | } 52 | 53 | // 包装命令执行以收集指标 54 | func instrumentedCommand(ctx context.Context, cmd string, fn func() error) error { 55 | start := time.Now() 56 | defer func() { 57 | redisCommandDuration.WithLabelValues(cmd).Observe(time.Since(start).Seconds()) 58 | redisCommandsTotal.WithLabelValues(cmd).Inc() 59 | }() 60 | return fn() 61 | } 62 | 63 | // 添加健康检查方法 64 | func CheckHealth(rd redis.Cmdable, ctx context.Context) bool { 65 | start := time.Now() 66 | err := rd.Ping(ctx).Err() 67 | duration := time.Since(start).Seconds() 68 | 69 | redisLatencyMetric.Observe(duration) 70 | 71 | if err != nil { 72 | redisHealthMetric.Set(0) 73 | return false 74 | } 75 | 76 | redisHealthMetric.Set(1) 77 | return true 78 | } 79 | 80 | // 添加 Prometheus 指标收集器 81 | func RegisterMetrics(registry prometheus.Registerer) { 82 | registry.MustRegister(redisHealthMetric) 83 | registry.MustRegister(redisLatencyMetric) 84 | } 85 | -------------------------------------------------------------------------------- /db/mongox/collection.go: -------------------------------------------------------------------------------- 1 | package mongox 2 | 3 | import ( 4 | "context" 5 | "reflect" 6 | "strings" 7 | 8 | "go.mongodb.org/mongo-driver/v2/bson" 9 | "go.mongodb.org/mongo-driver/v2/mongo" 10 | "go.mongodb.org/mongo-driver/v2/mongo/options" 11 | ) 12 | 13 | type Collection struct { 14 | col *mongo.Collection 15 | dbName, colName string 16 | } 17 | 18 | func (c *Collection) SwitchCollection(name string) *Collection { 19 | return &Collection{ 20 | colName: name, 21 | col: c.col.Database().Collection(name), 22 | } 23 | } 24 | 25 | func (c *Collection) Collection() *mongo.Collection { 26 | return c.col 27 | } 28 | 29 | func (c *Collection) Insert(d ...any) ([]any, error) { 30 | if len(d) == 1 { 31 | res, err := c.col.InsertOne(context.Background(), d[0]) 32 | if err != nil { 33 | return nil, err 34 | } 35 | return []any{res.InsertedID}, nil 36 | } else { 37 | res, err := c.col.InsertMany(context.Background(), d) 38 | if err != nil { 39 | return nil, err 40 | } 41 | return res.InsertedIDs, nil 42 | } 43 | } 44 | 45 | func (c *Collection) Update(filter bson.M, d ...any) (int64, error) { 46 | if len(d) == 1 { 47 | res, err := c.col.UpdateOne(context.Background(), filter, d[0]) 48 | if err != nil { 49 | return 0, err 50 | } 51 | if res.ModifiedCount == res.UpsertedCount { 52 | return res.UpsertedCount, nil 53 | } 54 | return res.ModifiedCount - res.UpsertedCount, nil 55 | } else { 56 | res, err := c.col.UpdateMany(context.Background(), filter, d) 57 | if err != nil { 58 | return 0, err 59 | } 60 | if res.ModifiedCount == res.UpsertedCount { 61 | return res.UpsertedCount, nil 62 | } 63 | return res.ModifiedCount - res.UpsertedCount, nil 64 | } 65 | } 66 | 67 | func (c *Collection) Delete(filter bson.M, isMany bool) error { 68 | if isMany { 69 | _, err := c.col.DeleteMany(context.Background(), filter) 70 | if err != nil { 71 | return err 72 | } 73 | } 74 | _, err := c.col.DeleteOne(context.Background(), filter) 75 | return err 76 | } 77 | 78 | func (c *Collection) Find(sel string, filter bson.M, offset, limit int64, out any) error { 79 | sels := strings.Split(sel, ",") 80 | sls := make(bson.D, len(sels)) 81 | for _, s := range sels { 82 | sls = append(sls, bson.E{Key: s, Value: 1}) 83 | } 84 | switch reflect.TypeOf(out).Kind() { 85 | case reflect.Array, reflect.Slice: 86 | opts := []options.Lister[options.FindOptions]{} 87 | if len(sls) != 0 { 88 | opts = append(opts, options.Find().SetProjection(sls)) 89 | } 90 | if limit != 0 { 91 | opts = append(opts, options.Find().SetLimit(limit)) 92 | } 93 | if offset != 0 { 94 | opts = append(opts, options.Find().SetSkip(offset)) 95 | } 96 | cur, err := c.col.Find(context.Background(), filter, opts...) 97 | if err != nil { 98 | return err 99 | } 100 | err = cur.All(context.Background(), out) 101 | if err != nil { 102 | return err 103 | } 104 | return cur.Close(context.Background()) 105 | } 106 | opts := []options.Lister[options.FindOneOptions]{} 107 | if len(sls) != 0 { 108 | opts = append(opts, options.FindOne().SetProjection(sls)) 109 | } 110 | return c.col.FindOne(context.Background(), filter, opts...).Decode(out) 111 | } 112 | 113 | func (c *Collection) Aggregate(pipeline bson.D, v any) error { 114 | cur, err := c.col.Aggregate(context.Background(), pipeline) 115 | if err != nil { 116 | return err 117 | } 118 | err = cur.All(context.Background(), v) 119 | if err != nil { 120 | return err 121 | } 122 | return cur.Close(context.Background()) 123 | } 124 | 125 | func (c *Collection) Count(filter bson.D) (int64, error) { 126 | return c.col.CountDocuments(context.Background(), filter) 127 | } 128 | -------------------------------------------------------------------------------- /resources/casbinx/casbin.go: -------------------------------------------------------------------------------- 1 | package casbinx 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/casbin/casbin/v2" 7 | "github.com/casbin/casbin/v2/model" 8 | "github.com/casbin/casbin/v2/persist" 9 | gormadapter "github.com/casbin/gorm-adapter/v3" 10 | "github.com/gin-contrib/authz" 11 | "github.com/gin-gonic/gin" 12 | "github.com/wjoj/tool/v2/db/dbx" 13 | "github.com/wjoj/tool/v2/db/redisx" 14 | "github.com/wjoj/tool/v2/log" 15 | "github.com/wjoj/tool/v2/utils" 16 | ) 17 | 18 | type DbType string 19 | 20 | const ( 21 | DbTypeGorm DbType = "gorm" 22 | DbTypeRedis DbType = "redis" 23 | ) 24 | 25 | type Config struct { 26 | DBType DbType `yaml:"dbType"` 27 | Key string `yaml:"key"` 28 | Prefix string `yaml:"prefix"` 29 | Name string `yaml:"name"` 30 | } 31 | 32 | const rbac_model = ` 33 | [request_definition] 34 | r = sub, obj, act 35 | 36 | [policy_definition] 37 | p = sub, obj, act 38 | 39 | [role_definition] 40 | g = _, _ 41 | 42 | [policy_effect] 43 | e = some(where (p.eft == allow)) 44 | 45 | [matchers] 46 | m = g(r.sub, p.sub) && keyMatch(r.obj, p.obj) && (r.act == p.act || p.act == "*")` 47 | 48 | const rabc_model2 = ` 49 | [request_definition] 50 | r = sub, obj, act 51 | 52 | [policy_definition] 53 | p = sub, obj, act 54 | 55 | [role_definition] 56 | g = _, _ 57 | 58 | [policy_effect] 59 | e = some(where (p.eft == allow)) 60 | 61 | [matchers] 62 | m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act` 63 | 64 | type Casbin struct { 65 | *casbin.Enforcer 66 | cfg *Config 67 | } 68 | 69 | func New(cfg *Config) (*Casbin, error) { 70 | if len(cfg.Key) == 0 { 71 | cfg.Key = utils.DefaultKey.DefaultKey 72 | } 73 | if len(cfg.Name) == 0 { 74 | cfg.Name = "casbin_rule" 75 | } 76 | var adapter persist.Adapter 77 | var err error 78 | switch cfg.DBType { 79 | case DbTypeGorm: 80 | adapter, err = gormadapter.NewAdapterByDBUseTableName(dbx.Get(cfg.Key), cfg.Prefix, cfg.Name) 81 | if err != nil { 82 | return nil, fmt.Errorf("failed to initialize casbin adapter: %v", err) 83 | } 84 | case DbTypeRedis: 85 | PolicyKey = cfg.Prefix + cfg.Name 86 | adapter = NewFromRedixClient(redisx.GetClient()) 87 | } 88 | 89 | m, err := model.NewModelFromString(rbac_model) 90 | if err != nil { 91 | return nil, fmt.Errorf("failed to create casbin model: %v", err) 92 | } 93 | enforcer, err := casbin.NewEnforcer(m, adapter) 94 | if err != nil { 95 | return nil, fmt.Errorf("failed to create casbin enforcer: %v", err) 96 | } 97 | if err = enforcer.LoadPolicy(); err != nil { 98 | return nil, fmt.Errorf("failed to load casbin policy: %v", err) 99 | } 100 | return &Casbin{ 101 | Enforcer: enforcer, 102 | cfg: cfg, 103 | }, nil 104 | } 105 | 106 | var cb *Casbin 107 | var cbMap map[string]*Casbin 108 | var defaultKey = utils.DefaultKey.DefaultKey 109 | 110 | func Init(cfgs map[string]Config, options ...Option) error { 111 | log.Info("init casbin") 112 | opt := applyOptions(options...) 113 | defaultKey = opt.defKey.DefaultKey 114 | var err error 115 | cbMap, err = utils.Init("casbin", defaultKey, opt.defKey.Keys, cfgs, func(cfg Config) (*Casbin, error) { 116 | return New(&cfg) 117 | }, func(c *Casbin) { 118 | cb = c 119 | }) 120 | if err != nil { 121 | log.Errorf("%v", err) 122 | return err 123 | } 124 | log.Info("init casbin success") 125 | return nil 126 | } 127 | 128 | func InitGlobal(cfg *Config) error { 129 | var err error 130 | cb, err = New(cfg) 131 | if err != nil { 132 | return err 133 | } 134 | return nil 135 | } 136 | 137 | func Get(key ...string) *Casbin { 138 | c, err := utils.Get("jwt", defaultKey, func(s string) (*Casbin, bool) { 139 | cli, is := cbMap[s] 140 | return cli, is 141 | }, key...) 142 | if err != nil { 143 | panic(err) 144 | } 145 | return c 146 | } 147 | 148 | func BasicAuthorizer(key ...string) func(*gin.Context) { 149 | return authz.NewAuthorizer(Get(key...).Enforcer) 150 | } 151 | -------------------------------------------------------------------------------- /README-CN.md: -------------------------------------------------------------------------------- 1 | # TOOL 2 | 3 | [English](README.md) | 简体中文 4 | 5 | ## 概述 6 | 7 | * 构建HTTP、GPRC服务 8 | * 快速生成SQL条件语句 9 | * LRU缓存、本地缓存 10 | * 过滤器有布隆、布谷 11 | * 唯一ID生成方式: GUID, NanoID, Snowflake, or UUID 12 | * Websocket、socekt 13 | * 参数验证器 14 | * 类型转换 15 | * 字符验证、AES、DES、RSA 16 | * Prometheus监控、链路跟踪 17 | * 分布式锁 18 | * 日志等级 19 | * 限流器 20 | 21 | ## SQL条件语句 22 | 23 | * 快速生成条件语句 24 | 25 | ```go 26 | import ( 27 | "github.com/wjoj/tool/v2" 28 | ) 29 | 30 | type Account struct{ 31 | Name `json:"name" gorm:"column:name" ifs:"="` 32 | } 33 | 34 | whs := NewWhereStructureFuncIfs(&Account{ 35 | Name: "wjoj", 36 | } , "gorm column", func(key string) string { 37 | if key == "name" { 38 | return "=" 39 | } 40 | return "=" 41 | }) 42 | 43 | wh := new(tool.Where) 44 | wh.AndIf("phone","like", "%28%") 45 | wh.AndWhereStructure(whs, "or") 46 | ``` 47 | 48 | ## Socket 49 | 50 | ```go 51 | type Message struct { 52 | Lng uint32 53 | I uint16 54 | Msg string 55 | } 56 | ``` 57 | 58 | 服务端 59 | 60 | ```go 61 | SocketListen(899, func(s *SocketConn) error { 62 | readMsg := new(Message) 63 | err := s.ReadBody().Numerical(&readMsg.Lng) 64 | if err != nil { 65 | t.Errorf("\nReadBody error lng:%+v err:%v", readMsg.Lng, err) 66 | return err 67 | } 68 | err = s.ReadBody().Numerical(&readMsg.I) 69 | if err != nil { 70 | t.Errorf("\nReadBody error ri:%v err:%v", readMsg.I, err) 71 | return err 72 | } 73 | body, err := s.ReadBody().Body(int64(readMsg.Lng)) 74 | if err != nil { 75 | t.Errorf("\nBody error ri:%v err:%v", len(body), err) 76 | return err 77 | } 78 | readMsg.Msg = string(body) 79 | t.Logf("\nread body succ msg:%+v", readMsg) 80 | msgb := fmt.Sprintf("server:%+v kkkkkkk", readMsg.I) 81 | writeMsg := &Message{ 82 | Msg: msgb, 83 | Lng: uint32(len(msgb)), 84 | I: readMsg.I, 85 | } 86 | 87 | msg := NewBodyWrite() 88 | msg.Numerical(&writeMsg.Lng) 89 | msg.Numerical(&writeMsg.I) 90 | msg.Write([]byte(writeMsg.Msg)) 91 | _, err = s.WriteBody(msg) 92 | if err != nil { 93 | t.Errorf("\nWriteBody error i:%+v err:%v", writeMsg.I, err) 94 | return err 95 | } 96 | t.Logf("\nwrite body succ:%+v", writeMsg.I) 97 | // time.Sleep(time.Second * 2) 98 | return nil 99 | }) 100 | ``` 101 | 102 | 客户端 103 | 104 | ```go 105 | for i := uint16(0); i < uint16(t.N); i++ { 106 | // fmt.Printf("\ni:%+v", i) 107 | j := i 108 | SocketClient("127.0.0.1:899", func(s *SocketConn) error { 109 | msgb := fmt.Sprintf("client:%+v", i) 110 | writeMsg := &Message{ 111 | Msg: msgb, 112 | Lng: uint32(len(msgb)), 113 | I: j, 114 | } 115 | msg := NewBodyWrite() 116 | err := msg.Numerical(writeMsg.Lng) 117 | if err != nil { 118 | // t.Fatalf("WriteBody error lng i:%+v err:%v", j, err) 119 | return err 120 | } 121 | err = msg.Numerical(&writeMsg.I) 122 | if err != nil { 123 | // t.Fatalf("\nWriteBody error i i:%+v err:%v", j, err) 124 | return err 125 | } 126 | _, err = msg.Write([]byte(writeMsg.Msg)) 127 | if err != nil { 128 | // t.Fatalf("\nWriteBody error body i:%+v err:%v", j, err) 129 | return err 130 | } 131 | _, err = s.WriteBody(msg) 132 | if err != nil { 133 | // t.Fatalf("\nWriteBody error i:%+v err:%v", j, err) 134 | return err 135 | } 136 | // t.Logf("\nWriteBody succ i:%+v ", j) 137 | readMsg := new(Message) 138 | err = s.ReadBody().Numerical(&readMsg.Lng) 139 | if err != nil { 140 | // t.Fatalf("\nReadBody error i:%v lng:%+v err:%v", j, readMsg.Lng, err) 141 | return err 142 | } 143 | err = s.ReadBody().Numerical(&readMsg.I) 144 | if err != nil { 145 | // t.Fatalf("\nReadBody error i:%+v ri:%v err:%v", j, readMsg.I, err) 146 | return err 147 | } 148 | body, err := s.ReadBody().Body(int64(readMsg.Lng)) 149 | if err != nil { 150 | // t.Fatalf("\nBody error i:%+v ri:%v err:%v", j, len(body), err) 151 | return err 152 | } 153 | readMsg.Msg = string(body) 154 | // t.Logf("\nread body i:%v msg:%+v", j, readMsg) 155 | time.Sleep(time.Second * 2) 156 | return nil 157 | }) 158 | } 159 | ``` -------------------------------------------------------------------------------- /config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "github.com/wjoj/tool/v2/db/dbx" 5 | "github.com/wjoj/tool/v2/db/mongox" 6 | "github.com/wjoj/tool/v2/db/redisx" 7 | "github.com/wjoj/tool/v2/httpx" 8 | "github.com/wjoj/tool/v2/log" 9 | "github.com/wjoj/tool/v2/resources/casbinx" 10 | "github.com/wjoj/tool/v2/resources/jwt" 11 | "github.com/wjoj/tool/v2/utils" 12 | ) 13 | 14 | var ( 15 | env EnvType 16 | namespace string 17 | configFile string 18 | configRoot string 19 | logs map[string]log.Config 20 | rediss map[string]redisx.Config 21 | dbs map[string]dbx.Config 22 | mongos map[string]mongox.Config 23 | http map[string]httpx.Config 24 | casbins map[string]casbinx.Config 25 | jwts map[string]jwt.Config 26 | ) 27 | 28 | func SetDefaultKey(key string) { 29 | defaultKey = key 30 | } 31 | 32 | func GetDefaultKey() string { 33 | return defaultKey 34 | } 35 | 36 | // GetEnv 获取环境 37 | func GetEnv() EnvType { 38 | return env 39 | } 40 | func SetEnv(e EnvType) { 41 | env = e 42 | } 43 | func GetNamespace() string { 44 | return namespace 45 | } 46 | 47 | func SetNamespace(n string) { 48 | namespace = n 49 | } 50 | func GetConfigFile() string { 51 | return configFile 52 | } 53 | func SetConfigFile(f string) { 54 | configFile = f 55 | } 56 | 57 | func GetConfigRoot() string { 58 | return configRoot 59 | } 60 | func SetConfigRoot(r string) { 61 | configRoot = r 62 | } 63 | 64 | // SetLog 设置日志 65 | func SetLog(lgs map[string]log.Config) { 66 | logs = lgs 67 | } 68 | 69 | func GetLogs() map[string]log.Config { 70 | return logs 71 | } 72 | 73 | // GetLog 获取日志 74 | func GetLog(key ...string) (logc log.Config) { 75 | logc, err := utils.Get("config log", GetDefaultKey(), func(k string) (log.Config, bool) { 76 | m, is := logs[k] 77 | return m, is 78 | }, key...) 79 | if err != nil { 80 | panic(err) 81 | } 82 | return 83 | } 84 | 85 | func SetRediss(r map[string]redisx.Config) { 86 | rediss = r 87 | } 88 | 89 | // SetRedis 设置redis 90 | func GetRediss() map[string]redisx.Config { 91 | return rediss 92 | } 93 | 94 | func GetRedis(key ...string) (redis redisx.Config) { 95 | redis, err := utils.Get("config redis", GetDefaultKey(), func(k string) (redisx.Config, bool) { 96 | m, is := rediss[k] 97 | return m, is 98 | }, key...) 99 | if err != nil { 100 | panic(err) 101 | } 102 | return 103 | } 104 | 105 | func SetDbs(d map[string]dbx.Config) { 106 | dbs = d 107 | } 108 | 109 | // SetDb 设置db 110 | func GetDbs() map[string]dbx.Config { 111 | return dbs 112 | } 113 | 114 | func GetDb(key ...string) (db dbx.Config) { 115 | db, err := utils.Get("config db", GetDefaultKey(), func(k string) (dbx.Config, bool) { 116 | m, is := dbs[k] 117 | return m, is 118 | }, key...) 119 | if err != nil { 120 | panic(err) 121 | } 122 | return 123 | } 124 | 125 | func SetMongos(m map[string]mongox.Config) { 126 | mongos = m 127 | } 128 | 129 | func GetMongos() map[string]mongox.Config { 130 | return mongos 131 | } 132 | 133 | func GetMongo(key ...string) (mgo mongox.Config) { 134 | mgo, err := utils.Get("config mongo", GetDefaultKey(), func(k string) (mongox.Config, bool) { 135 | m, is := mongos[k] 136 | return m, is 137 | }, key...) 138 | if err != nil { 139 | panic(err) 140 | } 141 | return 142 | } 143 | 144 | func SetHttp(h map[string]httpx.Config) { 145 | http = h 146 | } 147 | 148 | func GetHttp() map[string]httpx.Config { 149 | return http 150 | } 151 | 152 | func GetHttpServer(key ...string) (cfg httpx.Config) { 153 | cfg, err := utils.Get("config http", GetDefaultKey(), func(k string) (httpx.Config, bool) { 154 | m, is := http[k] 155 | return m, is 156 | }, key...) 157 | if err != nil { 158 | panic(err) 159 | } 160 | return 161 | } 162 | 163 | func SetCasbins(c map[string]casbinx.Config) { 164 | casbins = c 165 | } 166 | 167 | func GetCasbins() map[string]casbinx.Config { 168 | return casbins 169 | } 170 | 171 | func GetCasbin(key ...string) (casbin casbinx.Config) { 172 | casbin, err := utils.Get("config casbin", GetDefaultKey(), func(k string) (casbinx.Config, bool) { 173 | m, is := casbins[k] 174 | return m, is 175 | }, key...) 176 | if err != nil { 177 | panic(err) 178 | } 179 | return 180 | } 181 | 182 | func SetJwts(j map[string]jwt.Config) { 183 | jwts = j 184 | } 185 | 186 | func GetJwts() map[string]jwt.Config { 187 | return jwts 188 | } 189 | 190 | func GetJwt(key ...string) (jt jwt.Config) { 191 | jt, err := utils.Get("config jwt", GetDefaultKey(), func(k string) (jwt.Config, bool) { 192 | m, is := jwts[k] 193 | return m, is 194 | }, key...) 195 | if err != nil { 196 | panic(err) 197 | } 198 | return 199 | } 200 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TOOL 2 | 3 | English | [简体中文](README-CN.md) 4 | 5 | ## Overview 6 | 7 | * Build HTTP and GRPC services 8 | * Quickly build SQL conditional statements 9 | * The LRU cache、local cache 10 | * Filters include Bloom and Cuckoo 11 | * The unique ID can be GUID, NanoID, Snowflake, or UUID 12 | * The Websocket、socket 13 | * Parameter validator 14 | * Type conversion 15 | * Verify the characters、AES、DES、RSA 16 | * Prometheus monitoring and link tracing 17 | * A distributed lock 18 | * The log level 19 | * Current limiter 20 | 21 | ## SQL Where 22 | * conditional statements are built quickly 23 | 24 | ```go 25 | import ( 26 | "github.com/wjoj/tool/v2" 27 | ) 28 | 29 | type Account struct{ 30 | Name `json:"name" gorm:"column:name" ifs:"="` 31 | } 32 | 33 | whs := NewWhereStructureFuncIfs(&Account{ 34 | Name: "wjoj", 35 | } , "gorm column", func(key string) string { 36 | if key == "name" { 37 | return "=" 38 | } 39 | return "=" 40 | }) 41 | 42 | wh := new(tool.Where) 43 | wh.AndIf("phone","like", "%28%") 44 | wh.AndWhereStructure(whs, "or") 45 | ``` 46 | 47 | ## Socket 48 | 49 | ```go 50 | type Message struct { 51 | Lng uint32 52 | I uint16 53 | Msg string 54 | } 55 | ``` 56 | 57 | Server 58 | 59 | ```go 60 | SocketListen(899, func(s *SocketConn) error { 61 | readMsg := new(Message) 62 | err := s.ReadBody().Numerical(&readMsg.Lng) 63 | if err != nil { 64 | t.Errorf("\nReadBody error lng:%+v err:%v", readMsg.Lng, err) 65 | return err 66 | } 67 | err = s.ReadBody().Numerical(&readMsg.I) 68 | if err != nil { 69 | t.Errorf("\nReadBody error ri:%v err:%v", readMsg.I, err) 70 | return err 71 | } 72 | body, err := s.ReadBody().Body(int64(readMsg.Lng)) 73 | if err != nil { 74 | t.Errorf("\nBody error ri:%v err:%v", len(body), err) 75 | return err 76 | } 77 | readMsg.Msg = string(body) 78 | t.Logf("\nread body succ msg:%+v", readMsg) 79 | msgb := fmt.Sprintf("server:%+v kkkkkkk", readMsg.I) 80 | writeMsg := &Message{ 81 | Msg: msgb, 82 | Lng: uint32(len(msgb)), 83 | I: readMsg.I, 84 | } 85 | 86 | msg := NewBodyWrite() 87 | msg.Numerical(&writeMsg.Lng) 88 | msg.Numerical(&writeMsg.I) 89 | msg.Write([]byte(writeMsg.Msg)) 90 | _, err = s.WriteBody(msg) 91 | if err != nil { 92 | t.Errorf("\nWriteBody error i:%+v err:%v", writeMsg.I, err) 93 | return err 94 | } 95 | t.Logf("\nwrite body succ:%+v", writeMsg.I) 96 | // time.Sleep(time.Second * 2) 97 | return nil 98 | }) 99 | ``` 100 | 101 | Client 102 | 103 | ```go 104 | for i := uint16(0); i < uint16(t.N); i++ { 105 | // fmt.Printf("\ni:%+v", i) 106 | j := i 107 | SocketClient("127.0.0.1:899", func(s *SocketConn) error { 108 | msgb := fmt.Sprintf("client:%+v", i) 109 | writeMsg := &Message{ 110 | Msg: msgb, 111 | Lng: uint32(len(msgb)), 112 | I: j, 113 | } 114 | msg := NewBodyWrite() 115 | err := msg.Numerical(writeMsg.Lng) 116 | if err != nil { 117 | // t.Fatalf("WriteBody error lng i:%+v err:%v", j, err) 118 | return err 119 | } 120 | err = msg.Numerical(&writeMsg.I) 121 | if err != nil { 122 | // t.Fatalf("\nWriteBody error i i:%+v err:%v", j, err) 123 | return err 124 | } 125 | _, err = msg.Write([]byte(writeMsg.Msg)) 126 | if err != nil { 127 | // t.Fatalf("\nWriteBody error body i:%+v err:%v", j, err) 128 | return err 129 | } 130 | _, err = s.WriteBody(msg) 131 | if err != nil { 132 | // t.Fatalf("\nWriteBody error i:%+v err:%v", j, err) 133 | return err 134 | } 135 | // t.Logf("\nWriteBody succ i:%+v ", j) 136 | readMsg := new(Message) 137 | err = s.ReadBody().Numerical(&readMsg.Lng) 138 | if err != nil { 139 | // t.Fatalf("\nReadBody error i:%v lng:%+v err:%v", j, readMsg.Lng, err) 140 | return err 141 | } 142 | err = s.ReadBody().Numerical(&readMsg.I) 143 | if err != nil { 144 | // t.Fatalf("\nReadBody error i:%+v ri:%v err:%v", j, readMsg.I, err) 145 | return err 146 | } 147 | body, err := s.ReadBody().Body(int64(readMsg.Lng)) 148 | if err != nil { 149 | // t.Fatalf("\nBody error i:%+v ri:%v err:%v", j, len(body), err) 150 | return err 151 | } 152 | readMsg.Msg = string(body) 153 | // t.Logf("\nread body i:%v msg:%+v", j, readMsg) 154 | time.Sleep(time.Second * 2) 155 | return nil 156 | }) 157 | } 158 | ``` -------------------------------------------------------------------------------- /typesx/decimal.go: -------------------------------------------------------------------------------- 1 | package typesx 2 | 3 | import ( 4 | "database/sql/driver" 5 | "errors" 6 | "fmt" 7 | 8 | "github.com/shopspring/decimal" 9 | "github.com/wjoj/tool/v2/log" 10 | ) 11 | 12 | type DecimalCalculateType int 13 | 14 | const ( 15 | DecimalCalculateTypeAdd DecimalCalculateType = 1 // * 16 | DecimalCalculateTypeSub DecimalCalculateType = 2 // - 17 | DecimalCalculateTypeMul DecimalCalculateType = 3 // * 18 | DecimalCalculateTypeDiv DecimalCalculateType = 4 // / 19 | ) 20 | 21 | type Decimal struct { 22 | decimal.Decimal 23 | } 24 | 25 | func NewDecimal(value any) Decimal { 26 | dec := Decimal{} 27 | if err := dec.setvalue(value); err != nil { 28 | log.Warnf("decimal type conversion error %v", err) 29 | } 30 | return dec 31 | } 32 | func (d *Decimal) setvalue(value any) error { 33 | switch val := value.(type) { 34 | case int: 35 | d.Decimal = decimal.NewFromInt(int64(val)) 36 | case int8: 37 | d.Decimal = decimal.NewFromInt(int64(val)) 38 | case int16: 39 | d.Decimal = decimal.NewFromInt(int64(val)) 40 | case uint: 41 | d.Decimal = decimal.NewFromUint64(uint64(val)) 42 | case uint8: 43 | d.Decimal = decimal.NewFromUint64(uint64(val)) 44 | case uint16: 45 | d.Decimal = decimal.NewFromUint64(uint64(val)) 46 | case uint32: 47 | d.Decimal = decimal.NewFromUint64(uint64(val)) 48 | case uint64: 49 | d.Decimal = decimal.NewFromUint64(val) 50 | case string: 51 | dec, err := decimal.NewFromString(val) 52 | if err != nil { 53 | return err 54 | } 55 | d.Decimal = dec 56 | case float64: 57 | d.Decimal = decimal.NewFromFloat(val) 58 | case float32: 59 | d.Decimal = decimal.NewFromFloat32(val) 60 | case int64: 61 | d.Decimal = decimal.NewFromInt(val) 62 | case int32: 63 | d.Decimal = decimal.NewFromInt32(val) 64 | case []byte: 65 | dec, err := decimal.NewFromString(string(val)) 66 | if err != nil { 67 | return err 68 | } 69 | d.Decimal = dec 70 | case decimal.Decimal: 71 | d.Decimal = val 72 | case *decimal.Decimal: 73 | d.Decimal = *val 74 | case Decimal: 75 | d.Decimal = val.Decimal 76 | case *Decimal: 77 | d.Decimal = val.Decimal 78 | default: 79 | return fmt.Errorf("unsupported type %T", value) 80 | } 81 | return nil 82 | } 83 | 84 | // Add 在d值 + v 85 | func (d *Decimal) Add(v any) { 86 | d.Decimal = d.Decimal.Add(NewDecimal(v).Decimal) 87 | } 88 | 89 | // NewAdd returns a new Add d + v 90 | func (d Decimal) NewAdd(v any) Decimal { 91 | return Decimal{Decimal: d.Decimal.Add(NewDecimal(v).Decimal)} 92 | } 93 | 94 | // Sub 在d值 - v 95 | func (d *Decimal) Sub(v any) { 96 | d.Decimal = d.Decimal.Sub(NewDecimal(v).Decimal) 97 | } 98 | 99 | // NewSub returns a new Sub d - v 100 | func (d Decimal) NewSub(v any) Decimal { 101 | return Decimal{Decimal: d.Decimal.Sub(NewDecimal(v).Decimal)} 102 | } 103 | 104 | // Mul 在d值 * v 105 | func (d *Decimal) Mul(v any) { 106 | d.Decimal = d.Decimal.Mul(NewDecimal(v).Decimal) 107 | } 108 | 109 | // NewMul returns a new Mul d * v 110 | func (d Decimal) NewMul(v any) Decimal { 111 | return Decimal{Decimal: d.Decimal.Mul(NewDecimal(v).Decimal)} 112 | } 113 | 114 | // Div 在d值 / v 115 | func (d *Decimal) Div(v any) { 116 | d.Decimal = d.Decimal.Div(NewDecimal(v).Decimal) 117 | } 118 | 119 | // NewDiv returns a new Div d / v 120 | func (d Decimal) NewDiv(v any) Decimal { 121 | return Decimal{Decimal: d.Decimal.Div(NewDecimal(v).Decimal)} 122 | } 123 | 124 | // DivRound 在d值 / v 125 | func (d *Decimal) DivRound(v any, precision int32) { 126 | d.Decimal = d.Decimal.DivRound(NewDecimal(v).Decimal, precision) 127 | } 128 | 129 | // NewDivRound returns a new Div d / v 130 | func (d Decimal) NewDivRound(v any, precision int32) Decimal { 131 | return Decimal{Decimal: d.Decimal.DivRound(NewDecimal(v).Decimal, precision)} 132 | } 133 | 134 | // Calculator 数学计算 135 | func (d *Decimal) Calculator(cty DecimalCalculateType, v any) error { 136 | if cty == DecimalCalculateTypeAdd { 137 | d.Add(v) 138 | } else if cty == DecimalCalculateTypeSub { 139 | d.Sub(v) 140 | } else if cty == DecimalCalculateTypeMul { 141 | d.Mul(v) 142 | } else if cty == DecimalCalculateTypeDiv { 143 | d.Div(v) 144 | } else { 145 | return errors.New("undefined calculation type") 146 | } 147 | return nil 148 | } 149 | 150 | // Calculator 数学计算 返回新值 151 | func (d Decimal) NewCalculator(cty DecimalCalculateType, v any) Decimal { 152 | if cty == DecimalCalculateTypeAdd { 153 | return d.NewAdd(v) 154 | } else if cty == DecimalCalculateTypeSub { 155 | return d.NewSub(v) 156 | } else if cty == DecimalCalculateTypeMul { 157 | return d.NewMul(v) 158 | } else if cty == DecimalCalculateTypeDiv { 159 | return d.NewDiv(v) 160 | } 161 | return Decimal{} 162 | } 163 | 164 | func (d *Decimal) Scan(value any) error { 165 | return d.setvalue(value) 166 | } 167 | 168 | func (d Decimal) Value() (driver.Value, error) { 169 | return d.Decimal.String(), nil 170 | } 171 | 172 | func (d Decimal) MarshalJSON() ([]byte, error) { 173 | return []byte(d.Decimal.String()), nil 174 | } 175 | 176 | func (d *Decimal) UnmarshalJSON(data []byte) error { 177 | return d.setvalue(string(data)) 178 | } 179 | -------------------------------------------------------------------------------- /db/dbx/gen.go: -------------------------------------------------------------------------------- 1 | package dbx 2 | 3 | import ( 4 | "github.com/wjoj/tool/v2/utils" 5 | "gorm.io/gen" 6 | "gorm.io/gorm" 7 | ) 8 | 9 | type TableModelOpt struct { 10 | Table string 11 | ImportPkgPaths []string 12 | ModelOpts []gen.ModelOpt 13 | } 14 | type GenDBInfo struct { 15 | OutPath string 16 | ModelPkgPath string 17 | Module string 18 | PkgName string 19 | FieldNullable bool 20 | ModelTypePkgPaths []string 21 | TableModelOpts []*TableModelOpt 22 | } 23 | 24 | type GenOptions struct { 25 | defKey *utils.DefaultKeys 26 | module string 27 | infos map[string]*GenDBInfo 28 | } 29 | 30 | type GenOption func(c *GenOptions) 31 | 32 | // 设置默认key 33 | func WithDefaultKeyGenOption(key string) GenOption { 34 | return func(c *GenOptions) { 35 | c.defKey.DefaultKey = key 36 | } 37 | } 38 | 39 | // 设置要使用配置文件的key 40 | func WithLogConfigKeysGenOption(keys ...string) GenOption { 41 | return func(c *GenOptions) { 42 | c.defKey.Keys = keys 43 | } 44 | } 45 | 46 | func WithGenModuleGenOption(module string) GenOption { 47 | return func(c *GenOptions) { 48 | c.module = module 49 | } 50 | } 51 | 52 | // 设置要使用配置文件的key 53 | func WithGenDBInfoGenOption(key string, info *GenDBInfo) GenOption { 54 | return func(c *GenOptions) { 55 | c.infos[key] = info 56 | } 57 | } 58 | 59 | func applyGenOptions(options ...GenOption) GenOptions { 60 | opts := GenOptions{ 61 | defKey: utils.DefaultKey, 62 | infos: map[string]*GenDBInfo{}, 63 | } 64 | for _, option := range options { 65 | if option == nil { 66 | continue 67 | } 68 | option(&opts) 69 | } 70 | return opts 71 | } 72 | 73 | func GenByGorm(options ...GenOption) { 74 | opt := applyGenOptions(options...) 75 | 76 | keys := opt.defKey.Keys 77 | if len(keys) == 0 { 78 | for key := range dbs { 79 | keys = append(keys, key) 80 | } 81 | } 82 | for _, key := range keys { 83 | info, is := opt.infos[key] 84 | if !is { 85 | info = &GenDBInfo{ 86 | FieldNullable: false, 87 | } 88 | } 89 | if len(info.PkgName) == 0 { 90 | info.PkgName = key 91 | } 92 | module := opt.module 93 | if len(module) == 0 { 94 | module = "models/" + info.PkgName 95 | } else { 96 | module = module + "/" + info.PkgName 97 | } 98 | if len(info.OutPath) == 0 { 99 | info.OutPath = "./models/" + info.PkgName + "/dao" 100 | } 101 | if len(info.ModelPkgPath) == 0 { 102 | info.ModelPkgPath = "./models/" + info.PkgName 103 | } 104 | genb := gen.NewGenerator(gen.Config{ 105 | OutPath: info.OutPath, 106 | ModelPkgPath: info.ModelPkgPath, 107 | Mode: gen.WithDefaultQuery | gen.WithQueryInterface, 108 | FieldNullable: info.FieldNullable, 109 | FieldCoverable: false, 110 | FieldSignable: false, 111 | FieldWithIndexTag: false, 112 | FieldWithTypeTag: true, 113 | }) 114 | 115 | genb.WithDataTypeMap(map[string]func(detailType gorm.ColumnType) (dataType string){ 116 | "tinyint": func(detailType gorm.ColumnType) (dataType string) { return "int" }, 117 | "smallint": func(detailType gorm.ColumnType) (dataType string) { return "int" }, 118 | "mediumint": func(detailType gorm.ColumnType) (dataType string) { return "int64" }, 119 | "bigint": func(detailType gorm.ColumnType) (dataType string) { return "int64" }, 120 | "int": func(detailType gorm.ColumnType) (dataType string) { return "int" }, 121 | "float": func(detailType gorm.ColumnType) (dataType string) { return "float64" }, 122 | "json": func(detailType gorm.ColumnType) (dataType string) { return "datatypes.JSON" }, // 自定义时间 123 | "timestamp": func(detailType gorm.ColumnType) (dataType string) { return "typesx.Time" }, // 自定义时间 124 | "datetime": func(detailType gorm.ColumnType) (dataType string) { return "typesx.Time" }, // 自定义时间 125 | "date": func(detailType gorm.ColumnType) (dataType string) { return "typesx.Date" }, // 自定义时间 126 | "decimal": func(detailType gorm.ColumnType) (dataType string) { return "typesx.Decimal" }, // 金额类型全部转换为第三方库,github.com/shopspring/decimal 127 | }) 128 | genb.WithImportPkgPath(append([]string{ 129 | "github.com/wjoj/tool/v2/typesx", 130 | "github.com/wjoj/tool/v2/utils", 131 | "github.com/shopspring/decimal", 132 | "gorm.io/datatypes", 133 | module, 134 | }, info.ModelTypePkgPaths...)...) 135 | 136 | genb.UseDB(Get(key)) 137 | genb.ApplyBasic(genb.GenerateAllTable(setModelOpts()...)...) 138 | for _, mopt := range info.TableModelOpts { 139 | gm := genb.GenerateModel(mopt.Table, 140 | append(setModelOpts(), 141 | mopt.ModelOpts..., 142 | )..., 143 | ) 144 | if len(mopt.ImportPkgPaths) != 0 { 145 | gm.ImportPkgPaths = append(gm.ImportPkgPaths, mopt.ImportPkgPaths...) 146 | } 147 | } 148 | genb.Execute() 149 | } 150 | } 151 | 152 | func setModelOpts() []gen.ModelOpt { 153 | jsonField := gen.FieldJSONTagWithNS(func(columnName string) (tagContent string) { 154 | if columnName == "deleted_at" { 155 | return "-" 156 | } else if columnName == "password" { 157 | return "-" 158 | } 159 | return columnName 160 | }) 161 | return []gen.ModelOpt{ 162 | jsonField, 163 | gen.FieldType("deleted_at", "gorm.DeletedAt"), 164 | gen.FieldJSONTag("deleted_at", "-"), 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /resources/jwt/jwt.go: -------------------------------------------------------------------------------- 1 | package jwt 2 | 3 | import ( 4 | "errors" 5 | "strings" 6 | "time" 7 | 8 | "github.com/gin-gonic/gin" 9 | "github.com/golang-jwt/jwt/v5" 10 | "github.com/wjoj/tool/v2/log" 11 | "github.com/wjoj/tool/v2/utils" 12 | ) 13 | 14 | const ( 15 | Contextlaims = "claims" 16 | ) 17 | 18 | type Config struct { 19 | Method string `json:"method" yaml:"method"` 20 | Secret string `json:"secret" yaml:"secret"` //密钥 21 | Public string `json:"public" yaml:"public"` //公钥 22 | Private string `json:"private" yaml:"private"` //私钥 23 | Expire time.Duration `json:"expire" yaml:"expire"` //过期时间 24 | Number int64 `json:"number" yaml:"number"` //token数量 超过删除前面token 默认0:不限制并可控制token -1:不限制 25 | } 26 | 27 | type JwtToken struct { 28 | Token string `json:"token"` 29 | Expire int64 `json:"expire"` //过期时间 30 | } 31 | 32 | type Claims[T any] struct { 33 | jwt.RegisteredClaims 34 | Data T `json:"data"` 35 | } 36 | 37 | type Jwt struct { 38 | cfg *Config 39 | pub any 40 | pri any 41 | } 42 | 43 | func New(cfg *Config) (*Jwt, error) { 44 | if len(cfg.Method) == 0 { 45 | cfg.Method = jwt.SigningMethodHS256.Alg() 46 | } else { 47 | cfg.Method = strings.ToUpper(string(cfg.Method)) 48 | } 49 | if strings.HasPrefix(string(cfg.Method), "ED") { 50 | cfg.Method = jwt.SigningMethodEdDSA.Alg() 51 | } 52 | jt := &Jwt{ 53 | cfg: cfg, 54 | } 55 | if strings.HasPrefix(string(cfg.Method), "RS") { 56 | pubBy, priBy, err := readPubPri(cfg.Public, cfg.Private) 57 | if err != nil { 58 | return nil, err 59 | } 60 | jt.pub, err = jwt.ParseRSAPublicKeyFromPEM(pubBy) 61 | if err != nil { 62 | return nil, err 63 | } 64 | jt.pri, err = jwt.ParseRSAPrivateKeyFromPEM(priBy) 65 | if err != nil { 66 | return nil, err 67 | } 68 | } else if strings.HasPrefix(string(cfg.Method), "Ed") { 69 | pubBy, priBy, err := readPubPri(cfg.Public, cfg.Private) 70 | if err != nil { 71 | return nil, err 72 | } 73 | jt.pub, err = jwt.ParseEdPublicKeyFromPEM(pubBy) 74 | if err != nil { 75 | return nil, err 76 | } 77 | jt.pri, err = jwt.ParseEdPrivateKeyFromPEM(priBy) 78 | if err != nil { 79 | return nil, err 80 | } 81 | } else if strings.HasPrefix(string(cfg.Method), "ES") { 82 | pubBy, priBy, err := readPubPri(cfg.Public, cfg.Private) 83 | if err != nil { 84 | return nil, err 85 | } 86 | jt.pub, err = jwt.ParseECPublicKeyFromPEM(pubBy) 87 | if err != nil { 88 | return nil, err 89 | } 90 | jt.pri, err = jwt.ParseECPrivateKeyFromPEM(priBy) 91 | if err != nil { 92 | return nil, err 93 | } 94 | } else { 95 | if len(cfg.Secret) == 0 { 96 | return nil, errors.New("secret is empty") 97 | } 98 | jt.pub = []byte(cfg.Secret) 99 | jt.pri = []byte(cfg.Secret) 100 | } 101 | return jt, nil 102 | } 103 | 104 | func readPubPri(p, pi string) (pub, pri []byte, err error) { 105 | pub, err = utils.FileRead(p) 106 | if err != nil { 107 | return 108 | } 109 | pri, err = utils.FileRead(pi) 110 | if err != nil { 111 | return 112 | } 113 | return 114 | } 115 | 116 | var cb *Jwt 117 | var cbMap map[string]*Jwt 118 | var defaultKey = utils.DefaultKey.DefaultKey 119 | 120 | func Init(cfgs map[string]Config, options ...Option) error { 121 | log.Info("init jwt") 122 | opt := applyGenGormOptions(options...) 123 | defaultKey = opt.defKey.DefaultKey 124 | var err error 125 | cbMap, err = utils.Init("jwt", defaultKey, opt.defKey.Keys, cfgs, func(cfg Config) (*Jwt, error) { 126 | return New(&cfg) 127 | }, func(c *Jwt) { 128 | cb = c 129 | }) 130 | if err != nil { 131 | log.Errorf("%v", err) 132 | return err 133 | } 134 | log.Info("init jwt success") 135 | return nil 136 | } 137 | 138 | func InitGlobal(cfg *Config) error { 139 | var err error 140 | cb, err = New(cfg) 141 | if err != nil { 142 | return err 143 | } 144 | return nil 145 | } 146 | 147 | func Get(key ...string) *Jwt { 148 | jt, err := utils.Get("jwt", defaultKey, func(s string) (*Jwt, bool) { 149 | cli, is := cbMap[s] 150 | return cli, is 151 | }, key...) 152 | if err != nil { 153 | panic(err) 154 | } 155 | return jt 156 | } 157 | 158 | func AuthMiddleware[T any](key ...string) gin.HandlerFunc { 159 | return func(ctx *gin.Context) { 160 | token := ctx.GetHeader("Authorization") 161 | if len(token) == 0 { 162 | ctx.AbortWithStatusJSON(401, gin.H{"error": "Authorization header required"}) 163 | return 164 | } 165 | token = strings.TrimPrefix(token, "Bearer ") 166 | claims, err := ValidateToken[T](token, key...) 167 | if err != nil { 168 | ctx.AbortWithStatusJSON(401, gin.H{"error": err.Error()}) 169 | return 170 | } 171 | ctx.Set(Contextlaims, claims) 172 | ctx.Next() 173 | } 174 | } 175 | 176 | func GenerateToken[T any](uid string, data T, key ...string) (token *JwtToken, err error) { 177 | j := Get(key...) 178 | cl := Claims[T]{ 179 | RegisteredClaims: jwt.RegisteredClaims{ 180 | ExpiresAt: jwt.NewNumericDate(time.Now().Add(j.cfg.Expire)), 181 | }, 182 | Data: data, 183 | } 184 | tk := jwt.NewWithClaims(jwt.GetSigningMethod(string(j.cfg.Method)), cl) 185 | s, err := tk.SignedString(j.pri) 186 | if err != nil { 187 | return nil, err 188 | } 189 | return &JwtToken{ 190 | Token: s, 191 | Expire: cl.ExpiresAt.Unix(), 192 | }, nil 193 | } 194 | 195 | func ValidateToken[T any](token string, key ...string) (claims *Claims[T], err error) { 196 | j := Get(key...) 197 | tokenc, err := jwt.ParseWithClaims(token, &Claims[T]{}, func(token *jwt.Token) (interface{}, error) { 198 | return j.pub, nil 199 | }) 200 | if err != nil { 201 | return nil, err 202 | } 203 | return tokenc.Claims.(*Claims[T]), nil 204 | } 205 | -------------------------------------------------------------------------------- /db/mongox/mongo.go: -------------------------------------------------------------------------------- 1 | package mongox 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "strings" 7 | "time" 8 | 9 | "github.com/wjoj/tool/v2/log" 10 | "github.com/wjoj/tool/v2/utils" 11 | "go.mongodb.org/mongo-driver/v2/bson" 12 | "go.mongodb.org/mongo-driver/v2/mongo" 13 | "go.mongodb.org/mongo-driver/v2/mongo/options" 14 | ) 15 | 16 | type Config struct { 17 | Url string `yaml:"url" json:"url"` 18 | Dbname string `yaml:"dbname" json:"dbname"` 19 | MaxConnIdleTime time.Duration `yaml:"maxConnIdleTime" json:"maxConnIdleTime"` 20 | MaxPoolSize uint64 `yaml:"maxPoolSize" json:"maxPoolSize"` 21 | MinPoolSize uint64 `yaml:"minPoolSize" json:"minPoolSize"` 22 | MaxConnecting uint64 `yaml:"maxConnecting" json:"maxConnecting"` 23 | } 24 | 25 | type Mongo struct { 26 | cfg *Config 27 | cli *mongo.Client 28 | db *mongo.Database 29 | } 30 | 31 | func New(cfg *Config) (*Mongo, error) { 32 | if len(cfg.Url) == 0 { 33 | return nil, fmt.Errorf("mongo url is empty") 34 | } 35 | url := "" 36 | if strings.HasPrefix(cfg.Url, ":") { 37 | url = "mongodb" + cfg.Url 38 | } else if strings.HasPrefix(cfg.Url, "//") { 39 | url = "mongodb:" + cfg.Url 40 | } else if strings.HasPrefix(cfg.Url, "mongodb://") { 41 | url = cfg.Url 42 | } else { 43 | url = "mongodb://" + cfg.Url 44 | } 45 | mgo := options.Client().ApplyURI(url) 46 | mgo.SetMaxConnIdleTime(cfg.MaxConnIdleTime). 47 | SetMaxConnecting(cfg.MaxConnecting). 48 | SetMaxPoolSize(cfg.MaxPoolSize). 49 | SetMinPoolSize(cfg.MinPoolSize) 50 | client, err := mongo.Connect(mgo) 51 | if err != nil { 52 | return nil, err 53 | } 54 | if err := client.Ping(context.Background(), nil); err != nil { 55 | return nil, err 56 | } 57 | if len(cfg.Dbname) != 0 { 58 | return &Mongo{ 59 | cfg: cfg, 60 | cli: client, 61 | db: client.Database(cfg.Dbname), 62 | }, nil 63 | } 64 | return &Mongo{ 65 | cfg: cfg, 66 | cli: client, 67 | }, nil 68 | } 69 | 70 | func (m *Mongo) Client() *mongo.Client { 71 | return m.cli 72 | } 73 | func (m *Mongo) Database() *mongo.Database { 74 | return m.db 75 | } 76 | func (m *Mongo) Ping() error { 77 | return m.cli.Ping(context.Background(), nil) 78 | } 79 | func (m *Mongo) AllDBs() ([]mongo.DatabaseSpecification, int64, error) { 80 | data, err := m.cli.ListDatabases(context.Background(), bson.D{}) 81 | if err != nil { 82 | return nil, 0, err 83 | } 84 | return data.Databases, data.TotalSize, nil 85 | } 86 | func (m *Mongo) DbAllDCollections(dbname string) ([]mongo.CollectionSpecification, error) { 87 | colls, err := m.DB(dbname).ListCollectionSpecifications(context.Background(), bson.D{}) 88 | if err != nil { 89 | return nil, err 90 | } 91 | return colls, nil 92 | } 93 | 94 | func (m *Mongo) DB(name string) *mongo.Database { 95 | return m.cli.Database(name) 96 | } 97 | 98 | func (m *Mongo) DbCollection(dbname, colname string) *Collection { 99 | return &Collection{ 100 | dbName: dbname, 101 | colName: colname, 102 | col: m.cli.Database(dbname).Collection(colname), 103 | } 104 | } 105 | func (m *Mongo) Collection(colname string) *Collection { 106 | if len(m.cfg.Dbname) != 0 { 107 | log.Warnf("use default dbname is empty") 108 | return nil 109 | } 110 | return &Collection{ 111 | dbName: m.cfg.Dbname, 112 | colName: colname, 113 | col: m.db.Collection(colname), 114 | } 115 | } 116 | 117 | // Close函数用于关闭Mongo连接 118 | func (m *Mongo) Close() error { 119 | return m.cli.Disconnect(context.Background()) 120 | } 121 | 122 | var dbs = make(map[string]*Mongo) 123 | var db *Mongo 124 | var defaultKey = utils.DefaultKey.DefaultKey 125 | 126 | func Init(cfgs map[string]Config, options ...Option) error { 127 | opt := applyGenGormOptions(options...) 128 | defaultKey = opt.defKey.DefaultKey 129 | dbs = make(map[string]*Mongo) 130 | if len(opt.defKey.Keys) != 0 { 131 | opt.defKey.Keys = append(opt.defKey.Keys, opt.defKey.DefaultKey) 132 | for _, key := range opt.defKey.Keys { 133 | _, is := dbs[key] 134 | if is { 135 | continue 136 | } 137 | cfg, is := cfgs[key] 138 | if !is { 139 | return fmt.Errorf("mongo client %s not found", key) 140 | } 141 | cli, err := New(&cfg) 142 | if err != nil { 143 | return err 144 | } 145 | dbs[key] = cli 146 | if key == defaultKey { 147 | db = cli 148 | } 149 | } 150 | return nil 151 | } 152 | for name, cfg := range cfgs { 153 | cli, err := New(&cfg) 154 | if err != nil { 155 | return err 156 | } 157 | dbs[name] = cli 158 | if name == defaultKey { 159 | db = cli 160 | } 161 | } 162 | return nil 163 | } 164 | func InitGlobal(cfg *Config) error { 165 | var err error 166 | db, err = New(cfg) 167 | if err != nil { 168 | return err 169 | } 170 | return nil 171 | } 172 | 173 | func GetClient(name ...string) *Mongo { 174 | if len(name) == 0 { 175 | cli, is := dbs[defaultKey] 176 | if !is { 177 | panic(fmt.Errorf("mongo client %s not found", utils.DefaultKey.DefaultKey)) 178 | } 179 | return cli 180 | } 181 | cli, is := dbs[name[0]] 182 | if !is { 183 | panic(fmt.Errorf("mongo client %s not found", name[0])) 184 | } 185 | return cli 186 | } 187 | 188 | // Client 重新一遍所有方法 189 | func Client() *Mongo { 190 | return db 191 | } 192 | func Close() error { 193 | return db.Close() 194 | } 195 | 196 | func CloseAll() error { 197 | for _, cli := range dbs { 198 | err := cli.Close() 199 | if err != nil { 200 | continue 201 | } 202 | } 203 | return nil 204 | } 205 | 206 | func Insert(d ...any) { 207 | 208 | } 209 | 210 | func Update(filter bson.M, d ...any) (int64, error) { 211 | return 0, nil 212 | } 213 | 214 | func Delete(colname string, filter bson.M, isMany bool) error { 215 | return db.Collection(colname).Delete(filter, isMany) 216 | } 217 | -------------------------------------------------------------------------------- /resources/casbinx/redis_adapter.go: -------------------------------------------------------------------------------- 1 | package casbinx 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "strings" 8 | 9 | "github.com/casbin/casbin/v2/util" 10 | "github.com/wjoj/tool/v2/db/redisx" 11 | 12 | "github.com/casbin/casbin/v2/persist" 13 | 14 | "github.com/casbin/casbin/v2/model" 15 | "github.com/redis/go-redis/v9" 16 | ) 17 | 18 | var ( 19 | // The key under which the policies are stored in redis 20 | PolicyKey = "casbin:policy" 21 | ) 22 | 23 | // Adapter is an adapter for policy storage based on Redis 24 | type Adapter struct { 25 | redisCli *redisx.Clientx 26 | } 27 | 28 | // NewFromDSN returns a new Adapter by using the given DSN. 29 | // Format: redis://:{password}@{host}:{port}/{database} 30 | // Example: redis://:123@localhost:6379/0 31 | func NewFromRedisURL(url string) (adapter *Adapter, err error) { 32 | opt, err := redis.ParseURL(url) 33 | if err != nil { 34 | return nil, err 35 | } 36 | 37 | redisCli := redis.NewClient(opt) 38 | if err = redisCli.Ping(context.Background()).Err(); err != nil { 39 | return nil, fmt.Errorf("failed to ping redis: %v", err) 40 | } 41 | 42 | return NewFromRedixClient(&redisx.Clientx{ClientInf: redisCli}), nil 43 | } 44 | 45 | // NewFromClient returns a new instance of Adapter from an already existing go-redis client. 46 | func NewFromRedixClient(redisCli *redisx.Clientx) (adapter *Adapter) { 47 | return &Adapter{redisCli: redisCli} 48 | } 49 | 50 | // LoadPolicy loads all policy rules from the storage. 51 | func (a *Adapter) LoadPolicy(model model.Model) (err error) { 52 | ctx := context.Background() 53 | 54 | // Using the LoadPolicyLine handler from the Casbin repo for building rules 55 | return a.loadPolicy(ctx, model, persist.LoadPolicyArray) 56 | } 57 | 58 | func (a *Adapter) loadPolicy(ctx context.Context, model model.Model, handler func([]string, model.Model) error) (err error) { 59 | // 0, -1 fetches all entries from the list 60 | rules, err := a.redisCli.LRange(ctx, PolicyKey, 0, -1).Result() 61 | if err != nil { 62 | return err 63 | } 64 | 65 | // Parse the rules from Redis 66 | for _, rule := range rules { 67 | handler(strings.Split(rule, ", "), model) 68 | } 69 | 70 | return 71 | } 72 | 73 | // SavePolicy saves all policy rules to the storage. 74 | func (a *Adapter) SavePolicy(model model.Model) (err error) { 75 | ctx := context.Background() 76 | var rules []string 77 | 78 | // Serialize the policies into a string slice 79 | for ptype, assertion := range model["p"] { 80 | for _, rule := range assertion.Policy { 81 | rules = append(rules, buildRuleStr(ptype, rule)) 82 | } 83 | } 84 | 85 | // Append the group policies to the slice 86 | for ptype, assertion := range model["g"] { 87 | for _, rule := range assertion.Policy { 88 | rules = append(rules, buildRuleStr(ptype, rule)) 89 | } 90 | } 91 | 92 | // If an empty ruleset is saved, the policy is completely deleted from Redis. 93 | if len(rules) > 0 { 94 | return a.savePolicy(ctx, rules) 95 | } 96 | return a.delPolicy(ctx) 97 | } 98 | 99 | func (a *Adapter) savePolicy(ctx context.Context, rules []string) (err error) { 100 | // Use a transaction for deleting the key & creating a new one. 101 | // This only uses one round trip to Redis and also makes sure nothing bad happens. 102 | cmd, err := a.redisCli.TxPipelined(ctx, func(tx redis.Pipeliner) error { 103 | tx.Del(ctx, PolicyKey) 104 | tx.RPush(ctx, PolicyKey, strToInterfaceSlice(rules)...) 105 | 106 | return nil 107 | }) 108 | if err != nil { 109 | return err 110 | } 111 | if err = cmd[0].Err(); err != nil { 112 | return fmt.Errorf("failed to delete policy key: %v", err) 113 | } 114 | if err = cmd[1].Err(); err != nil { 115 | return fmt.Errorf("failed to save policy: %v", err) 116 | } 117 | 118 | return 119 | } 120 | 121 | func (a *Adapter) delPolicy(ctx context.Context) (err error) { 122 | if err = a.redisCli.Del(ctx, PolicyKey).Err(); err != nil { 123 | return err 124 | } 125 | return 126 | } 127 | 128 | // AddPolicy adds a policy rule to the storage. 129 | func (a *Adapter) AddPolicy(_ string, ptype string, rule []string) (err error) { 130 | ctx := context.Background() 131 | return a.addPolicy(ctx, buildRuleStr(ptype, rule)) 132 | } 133 | 134 | func (a *Adapter) AddPolicies(_ string, ptype string, rules [][]string) (err error) { 135 | ctx := context.Background() 136 | for _, rule := range rules { 137 | if err = a.addPolicy(ctx, buildRuleStr(ptype, rule)); err != nil { 138 | return err 139 | } 140 | } 141 | return nil 142 | } 143 | 144 | func (a *Adapter) addPolicy(ctx context.Context, rule string) (err error) { 145 | if err = a.redisCli.RPush(ctx, PolicyKey, rule).Err(); err != nil { 146 | return err 147 | } 148 | return 149 | } 150 | 151 | // RemovePolicy removes a policy rule from the storage. 152 | func (a *Adapter) RemovePolicy(_ string, ptype string, rule []string) (err error) { 153 | ctx := context.Background() 154 | 155 | return a.removePolicy(ctx, buildRuleStr(ptype, rule)) 156 | } 157 | 158 | func (a *Adapter) RemovePolicies(_ string, ptype string, rules [][]string) (err error) { 159 | ctx := context.Background() 160 | for _, rule := range rules { 161 | if err = a.removePolicy(ctx, buildRuleStr(ptype, rule)); err != nil { 162 | return err 163 | } 164 | } 165 | return nil 166 | } 167 | 168 | func (a *Adapter) removePolicy(ctx context.Context, rule string) (err error) { 169 | if err = a.redisCli.LRem(ctx, PolicyKey, 1, rule).Err(); err != nil { 170 | return err 171 | } 172 | return 173 | } 174 | 175 | // RemoveFilteredPolicy removes policy rules that match the filter from the storage. 176 | func (a *Adapter) RemoveFilteredPolicy(sec string, ptype string, fieldIndex int, fieldValues ...string) error { 177 | return errors.New("not implemented") 178 | } 179 | 180 | // Converts a string slice to an interface{} slice. 181 | // Needed for pushing elements to a redis list. 182 | func strToInterfaceSlice(ss []string) (is []interface{}) { 183 | for _, s := range ss { 184 | is = append(is, s) 185 | } 186 | return 187 | } 188 | 189 | func buildRuleStr(ptype string, rule []string) string { 190 | return ptype + ", " + util.ArrayToString(rule) 191 | } 192 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/wjoj/tool/v2 2 | 3 | go 1.24.0 4 | 5 | require ( 6 | github.com/casbin/casbin/v2 v2.105.0 7 | github.com/casbin/gorm-adapter/v3 v3.32.0 8 | github.com/fsnotify/fsnotify v1.9.0 9 | github.com/gin-contrib/authz v1.0.5 10 | github.com/gin-contrib/cors v1.7.5 11 | github.com/gin-contrib/zap v1.1.5 12 | github.com/gin-gonic/gin v1.10.1 13 | github.com/go-playground/validator/v10 v10.26.0 14 | github.com/go-viper/mapstructure/v2 v2.2.1 15 | github.com/golang-jwt/jwt/v5 v5.2.0 16 | github.com/iancoleman/strcase v0.3.0 17 | github.com/natefinch/lumberjack v2.0.0+incompatible 18 | github.com/prometheus/client_golang v1.22.0 19 | github.com/redis/go-redis/v9 v9.8.0 20 | github.com/smartystreets/goconvey v1.8.1 21 | github.com/spf13/cobra v1.9.1 22 | github.com/spf13/pflag v1.0.6 23 | github.com/spf13/viper v1.20.1 24 | github.com/swaggo/files v1.0.1 25 | github.com/swaggo/gin-swagger v1.6.0 26 | go.mongodb.org/mongo-driver/v2 v2.2.1 27 | go.uber.org/zap v1.27.0 28 | gorm.io/driver/clickhouse v0.7.0 29 | gorm.io/driver/mysql v1.5.7 30 | gorm.io/driver/postgres v1.5.11 31 | gorm.io/driver/sqlite v1.5.7 32 | gorm.io/driver/sqlserver v1.6.0 33 | gorm.io/gen v0.3.27 34 | gorm.io/gorm v1.30.0 35 | ) 36 | 37 | require ( 38 | filippo.io/edwards25519 v1.1.0 // indirect 39 | github.com/BurntSushi/toml v1.5.0 // indirect 40 | github.com/ClickHouse/ch-go v0.61.5 // indirect 41 | github.com/ClickHouse/clickhouse-go/v2 v2.30.0 // indirect 42 | github.com/KyleBanks/depth v1.2.1 // indirect 43 | github.com/andybalholm/brotli v1.1.1 // indirect 44 | github.com/beorn7/perks v1.0.1 // indirect 45 | github.com/bmatcuk/doublestar/v4 v4.8.1 // indirect 46 | github.com/bytedance/sonic v1.13.2 // indirect 47 | github.com/bytedance/sonic/loader v0.2.4 // indirect 48 | github.com/casbin/govaluate v1.7.0 // indirect 49 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 50 | github.com/cloudwego/base64x v0.1.5 // indirect 51 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect 52 | github.com/dustin/go-humanize v1.0.1 // indirect 53 | github.com/gabriel-vasile/mimetype v1.4.9 // indirect 54 | github.com/gin-contrib/sse v1.1.0 // indirect 55 | github.com/glebarez/go-sqlite v1.20.3 // indirect 56 | github.com/glebarez/sqlite v1.7.0 // indirect 57 | github.com/go-faster/city v1.0.1 // indirect 58 | github.com/go-faster/errors v0.7.1 // indirect 59 | github.com/go-openapi/jsonpointer v0.19.5 // indirect 60 | github.com/go-openapi/jsonreference v0.20.0 // indirect 61 | github.com/go-openapi/spec v0.20.4 // indirect 62 | github.com/go-openapi/swag v0.19.15 // indirect 63 | github.com/go-playground/locales v0.14.1 // indirect 64 | github.com/go-playground/universal-translator v0.18.1 // indirect 65 | github.com/go-sql-driver/mysql v1.8.1 // indirect 66 | github.com/goccy/go-json v0.10.5 // indirect 67 | github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect 68 | github.com/golang-sql/sqlexp v0.1.0 // indirect 69 | github.com/golang/snappy v1.0.0 // indirect 70 | github.com/google/uuid v1.6.0 // indirect 71 | github.com/gopherjs/gopherjs v1.17.2 // indirect 72 | github.com/hashicorp/go-version v1.6.0 // indirect 73 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 74 | github.com/jackc/pgpassfile v1.0.0 // indirect 75 | github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 // indirect 76 | github.com/jackc/pgx/v5 v5.5.5 // indirect 77 | github.com/jackc/puddle/v2 v2.2.1 // indirect 78 | github.com/jinzhu/inflection v1.0.0 // indirect 79 | github.com/jinzhu/now v1.1.5 // indirect 80 | github.com/josharian/intern v1.0.0 // indirect 81 | github.com/json-iterator/go v1.1.12 // indirect 82 | github.com/jtolds/gls v4.20.0+incompatible // indirect 83 | github.com/klauspost/compress v1.18.0 // indirect 84 | github.com/klauspost/cpuid/v2 v2.2.10 // indirect 85 | github.com/leodido/go-urn v1.4.0 // indirect 86 | github.com/mailru/easyjson v0.7.6 // indirect 87 | github.com/mattn/go-isatty v0.0.20 // indirect 88 | github.com/mattn/go-sqlite3 v1.14.22 // indirect 89 | github.com/microsoft/go-mssqldb v1.7.2 // indirect 90 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 91 | github.com/modern-go/reflect2 v1.0.2 // indirect 92 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 93 | github.com/paulmach/orb v0.11.1 // indirect 94 | github.com/pelletier/go-toml/v2 v2.2.4 // indirect 95 | github.com/pierrec/lz4/v4 v4.1.21 // indirect 96 | github.com/pkg/errors v0.9.1 // indirect 97 | github.com/prometheus/client_model v0.6.1 // indirect 98 | github.com/prometheus/common v0.62.0 // indirect 99 | github.com/prometheus/procfs v0.15.1 // indirect 100 | github.com/remyoudompheng/bigfft v0.0.0-20230126093431-47fa9a501578 // indirect 101 | github.com/sagikazarmark/locafero v0.7.0 // indirect 102 | github.com/segmentio/asm v1.2.0 // indirect 103 | github.com/shopspring/decimal v1.4.0 // indirect 104 | github.com/smarty/assertions v1.15.0 // indirect 105 | github.com/sourcegraph/conc v0.3.0 // indirect 106 | github.com/spf13/afero v1.12.0 // indirect 107 | github.com/spf13/cast v1.7.1 // indirect 108 | github.com/subosito/gotenv v1.6.0 // indirect 109 | github.com/swaggo/swag v1.8.12 // indirect 110 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 111 | github.com/ugorji/go/codec v1.2.12 // indirect 112 | github.com/xdg-go/pbkdf2 v1.0.0 // indirect 113 | github.com/xdg-go/scram v1.1.2 // indirect 114 | github.com/xdg-go/stringprep v1.0.4 // indirect 115 | github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect 116 | go.opentelemetry.io/otel v1.29.0 // indirect 117 | go.opentelemetry.io/otel/trace v1.29.0 // indirect 118 | go.uber.org/multierr v1.11.0 // indirect 119 | golang.org/x/arch v0.17.0 // indirect 120 | golang.org/x/crypto v0.38.0 // indirect 121 | golang.org/x/mod v0.17.0 // indirect 122 | golang.org/x/net v0.40.0 // indirect 123 | golang.org/x/sync v0.14.0 // indirect 124 | golang.org/x/sys v0.33.0 // indirect 125 | golang.org/x/text v0.25.0 // indirect 126 | golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect 127 | google.golang.org/protobuf v1.36.6 // indirect 128 | gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect 129 | gopkg.in/yaml.v2 v2.4.0 // indirect 130 | gopkg.in/yaml.v3 v3.0.1 // indirect 131 | gorm.io/datatypes v1.2.4 // indirect 132 | gorm.io/hints v1.1.0 // indirect 133 | gorm.io/plugin/dbresolver v1.5.3 // indirect 134 | modernc.org/libc v1.22.2 // indirect 135 | modernc.org/mathutil v1.5.0 // indirect 136 | modernc.org/memory v1.5.0 // indirect 137 | modernc.org/sqlite v1.20.3 // indirect 138 | ) 139 | -------------------------------------------------------------------------------- /app.go: -------------------------------------------------------------------------------- 1 | package tool 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "os" 7 | "os/signal" 8 | "syscall" 9 | 10 | "github.com/wjoj/tool/v2/config" 11 | "github.com/wjoj/tool/v2/db/dbx" 12 | "github.com/wjoj/tool/v2/db/mongox" 13 | "github.com/wjoj/tool/v2/db/redisx" 14 | "github.com/wjoj/tool/v2/httpx" 15 | "github.com/wjoj/tool/v2/log" 16 | "github.com/wjoj/tool/v2/resources/casbinx" 17 | "github.com/wjoj/tool/v2/resources/jwt" 18 | "github.com/wjoj/tool/v2/utils" 19 | 20 | "github.com/spf13/cobra" 21 | "github.com/spf13/pflag" 22 | ) 23 | 24 | type fnNameType string 25 | 26 | const ( 27 | fnNameConfig fnNameType = "config" 28 | fnNameGorm fnNameType = "gorm" 29 | fnNameGenGorm fnNameType = "gengorm" 30 | fnNameLog fnNameType = "log" 31 | fnNameRedis fnNameType = "redis" 32 | fnNameMongo fnNameType = "mongo" 33 | fnNameHttp fnNameType = "http" 34 | fnNameCasbin fnNameType = "casbin" 35 | fnNameJwt fnNameType = "jwt" 36 | ) 37 | 38 | type funcErr struct { 39 | Fn func() error 40 | RekeaseFn func() error 41 | Name fnNameType 42 | } 43 | 44 | type cmdarg struct { 45 | config *string 46 | configroot *string 47 | } 48 | 49 | type App struct { 50 | isConfig bool 51 | fnMap map[fnNameType]funcErr 52 | cmdarg *cmdarg 53 | opt Options 54 | rootCmd *cobra.Command 55 | cmds []*cobra.Command 56 | } 57 | 58 | func NewApp(opts ...Option) *App { 59 | return &App{ 60 | isConfig: false, 61 | opt: applyOptions(opts...), 62 | fnMap: map[fnNameType]funcErr{}, 63 | cmdarg: &cmdarg{}, 64 | rootCmd: &cobra.Command{ 65 | Use: "echo [string to echo]", 66 | Aliases: []string{"say"}, 67 | Short: "Echo anything to the screen", 68 | Long: "an utterly useless command for testing", 69 | Example: "Just run cobra-test echo", 70 | }, 71 | } 72 | } 73 | 74 | func (a *App) setIsConfig() { 75 | a.isConfig = true 76 | } 77 | 78 | func (a *App) Config() *App { 79 | a.setIsConfig() 80 | a.cmdarg.config = a.rootCmd.PersistentFlags().StringP("config", "c", "config.yaml", "configuration file") 81 | a.fnMap[fnNameConfig] = funcErr{ 82 | Fn: func() error { 83 | return config.Read(*a.cmdarg.configroot, *a.cmdarg.config) 84 | }, 85 | RekeaseFn: nil, 86 | Name: fnNameConfig, 87 | } 88 | return a 89 | } 90 | 91 | func (a *App) Log(options ...log.Option) *App { 92 | a.setIsConfig() 93 | a.fnMap[fnNameLog] = funcErr{ 94 | Fn: func() error { 95 | return log.Load(config.GetLogs(), options...) 96 | }, 97 | RekeaseFn: func() error { 98 | log.CloseAll() 99 | return nil 100 | }, 101 | Name: fnNameLog, 102 | } 103 | return a 104 | } 105 | 106 | func (a *App) Redis(options ...redisx.Option) *App { 107 | a.setIsConfig() 108 | a.fnMap[fnNameRedis] = funcErr{ 109 | Fn: func() error { 110 | return redisx.Init(config.GetRediss(), options...) 111 | }, 112 | RekeaseFn: func() error { 113 | redisx.CloseAll() 114 | return nil 115 | }, 116 | Name: fnNameRedis, 117 | } 118 | return a 119 | } 120 | 121 | func (a *App) Gorm(options ...dbx.Option) *App { 122 | a.setIsConfig() 123 | a.fnMap[fnNameGorm] = funcErr{ 124 | Fn: func() error { 125 | return dbx.Init(config.GetDbs(), options...) 126 | }, 127 | RekeaseFn: dbx.CloseAll, 128 | Name: fnNameGorm, 129 | } 130 | return a 131 | } 132 | 133 | func (a *App) GenGorm(options ...dbx.GenOption) *App { 134 | a.setIsConfig() 135 | a.fnMap[fnNameGenGorm] = funcErr{ 136 | Fn: func() error { 137 | dbx.GenByGorm(options...) 138 | return nil 139 | }, 140 | RekeaseFn: nil, 141 | Name: fnNameGenGorm, 142 | } 143 | return a 144 | } 145 | 146 | func (a *App) Mongo(options ...mongox.Option) *App { 147 | a.setIsConfig() 148 | a.fnMap[fnNameMongo] = funcErr{ 149 | Fn: func() error { 150 | return mongox.Init(config.GetMongos(), options...) 151 | }, 152 | RekeaseFn: mongox.CloseAll, 153 | Name: fnNameMongo, 154 | } 155 | return a 156 | } 157 | 158 | func (a *App) Casbin(options ...casbinx.Option) *App { 159 | a.setIsConfig() 160 | a.fnMap[fnNameCasbin] = funcErr{ 161 | Fn: func() error { 162 | return casbinx.Init(config.GetCasbins(), options...) 163 | }, 164 | RekeaseFn: nil, 165 | Name: fnNameCasbin, 166 | } 167 | return a 168 | } 169 | func (a *App) Jwt(options ...jwt.Option) *App { 170 | a.setIsConfig() 171 | a.fnMap[fnNameJwt] = funcErr{ 172 | Fn: func() error { 173 | return jwt.Init(config.GetJwts(), options...) 174 | }, 175 | RekeaseFn: nil, 176 | Name: fnNameJwt, 177 | } 178 | return a 179 | } 180 | func (a *App) HttpServer(options ...httpx.Option) *App { 181 | a.setIsConfig() 182 | a.fnMap[fnNameHttp] = funcErr{ 183 | Fn: func() error { 184 | return httpx.Init(config.GetHttp(), options...) 185 | }, 186 | RekeaseFn: httpx.ShutdownAll, 187 | Name: fnNameHttp, 188 | } 189 | return a 190 | } 191 | 192 | func (a *App) With(is bool, fn func(a *App) error) *App { 193 | return a 194 | } 195 | 196 | func (a *App) WithFunc(is bool, fn func() error) *App { 197 | return a 198 | } 199 | 200 | func (a *App) WithRekease(is bool, fn func(a *App) error) *App { 201 | return a 202 | } 203 | 204 | func (a *App) WithRekeaseFunc(is bool, fn func() error) *App { 205 | return a 206 | } 207 | 208 | func (a *App) Flags(f func(flag *pflag.FlagSet)) *App { 209 | f(a.rootCmd.Flags()) 210 | return a 211 | } 212 | 213 | func (a *App) AddCommand(cmd *cobra.Command) *App { 214 | a.cmds = append(a.cmds, cmd) 215 | return a 216 | } 217 | 218 | func (a *App) AddServer(srvName string) *App { 219 | return a 220 | } 221 | 222 | func (a *App) run(fs []funcErr) error { 223 | for i := range fs { 224 | f := fs[i] 225 | if f.Fn == nil { 226 | continue 227 | } 228 | 229 | if err := f.Fn(); err != nil { 230 | fmt.Printf("%+v err:%+v\n", f.Name, err) 231 | return err 232 | } 233 | } 234 | return nil 235 | } 236 | 237 | func (a *App) rekease(fs []funcErr) error { 238 | rel := func() error { 239 | for _, f := range fs { 240 | if f.RekeaseFn == nil { 241 | continue 242 | } 243 | if err := f.RekeaseFn(); err != nil { 244 | fmt.Printf("%+v err:%+v\n", f.Name, err) 245 | return err 246 | } 247 | } 248 | return nil 249 | } 250 | if a.opt.quit { 251 | return rel() 252 | } 253 | quit := make(chan os.Signal, 1) 254 | signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) 255 | <-quit 256 | return rel() 257 | } 258 | 259 | func (a *App) Run() error { 260 | a.cmdarg.configroot = a.rootCmd.PersistentFlags().StringP("configroot", "r", "etc", "configure the root directory") 261 | fs := []funcErr{} 262 | if a.isConfig { //需要配置 263 | fnConfig, is := a.fnMap[fnNameConfig] 264 | if !is { 265 | a.Config() 266 | fnConfig, is = a.fnMap[fnNameConfig] 267 | if !is { 268 | return errors.New("config not found") 269 | } 270 | } 271 | fs = append(fs, fnConfig) 272 | } 273 | fnLog, is := a.fnMap[fnNameLog] 274 | if is { 275 | fs = append(fs, fnLog) 276 | } else { 277 | fs = append(fs, funcErr{ 278 | Fn: func() error { 279 | return log.Load(map[string]log.Config{ 280 | utils.DefaultKey.DefaultKey: { 281 | Level: "debug", 282 | LevelColor: true, 283 | Out: log.OutStdout, 284 | OutFormat: log.OutFormatConsole, 285 | }, 286 | }) 287 | }, 288 | }) 289 | } 290 | fnames := []fnNameType{ 291 | fnNameRedis, fnNameGorm, fnNameMongo, 292 | fnNameJwt, fnNameCasbin, fnNameHttp, 293 | fnNameGenGorm, 294 | } 295 | for _, fname := range fnames { 296 | fn, is := a.fnMap[fname] 297 | if is { 298 | fs = append(fs, fn) 299 | } 300 | } 301 | a.rootCmd.Run = func(cmd *cobra.Command, args []string) { 302 | if err := a.run(fs); err != nil { 303 | return 304 | } 305 | if err := a.rekease(fs); err != nil { 306 | return 307 | } 308 | } 309 | a.rootCmd.AddCommand(a.cmds...) 310 | err := a.rootCmd.Execute() 311 | if err != nil { 312 | fmt.Println("xxxxx") 313 | return err 314 | } 315 | 316 | return nil 317 | } 318 | -------------------------------------------------------------------------------- /log/log.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "errors" 5 | "io" 6 | "os" 7 | "strings" 8 | 9 | "github.com/natefinch/lumberjack" 10 | "github.com/wjoj/tool/v2/utils" 11 | "go.uber.org/zap" 12 | "go.uber.org/zap/zapcore" 13 | ) 14 | 15 | type LevelType string 16 | 17 | func (t LevelType) ZapLevel() zapcore.Level { 18 | t = LevelType(strings.ToLower(string(t))) 19 | if t == "debug" { 20 | return zapcore.DebugLevel 21 | } else if t == "info" { 22 | return zapcore.InfoLevel 23 | } else if t == "warn" { 24 | return zapcore.WarnLevel 25 | } else if t == "error" { 26 | return zapcore.ErrorLevel 27 | } else if t == "dpanic" { 28 | return zapcore.DPanicLevel 29 | } else if t == "panic" { 30 | return zapcore.PanicLevel 31 | } else if t == "fatal" { 32 | return zapcore.FatalLevel 33 | } 34 | return zapcore.DebugLevel 35 | } 36 | 37 | // OutType 输出类型 38 | type OutType string 39 | 40 | const ( 41 | OutFile OutType = "file" 42 | OutStdout OutType = "stdout" 43 | OutFileStdout OutType = "fileStdout" // 系统日志 44 | ) 45 | 46 | // OutFormatType 输出格式 47 | type OutFormatType string 48 | 49 | const ( 50 | OutFormatJson OutFormatType = "json" //json 51 | OutFormatConsole OutFormatType = "console" 52 | ) 53 | 54 | type Config struct { 55 | Level LevelType `json:"level" yaml:"level"` //等级 56 | LevelColor bool `json:"levelColor" yaml:"levelColor"` //是否开启等级颜色 57 | Out OutType `json:"out" yaml:"out"` //输出类型 58 | OutFormat OutFormatType `json:"outFormat" yaml:"outFormat"` //输出格式 59 | Path string `json:"path" yaml:"path"` //日志路径 60 | MaxSize int `json:"maxSize" yaml:"maxSize"` // 在进行切割之前,日志文件的最大大小(以MB为单位) 61 | MaxBackups int `json:"maxBackups" yaml:"maxBackups"` // 保留旧文件的最大个数 62 | MaxAge int `json:"maxAge" yaml:"maxAge"` // 保留旧文件的最大天数 63 | Compress bool `json:"compress" yaml:"compress"` // 是否压缩/归档旧文件 64 | } 65 | 66 | func New(cfg *Config) (*zap.Logger, error) { 67 | if cfg.MaxAge == 0 { 68 | cfg.MaxAge = 7 69 | } 70 | if cfg.MaxBackups == 0 { 71 | cfg.MaxBackups = 10 72 | } 73 | if cfg.MaxSize == 0 { 74 | cfg.MaxSize = 512 75 | } 76 | if len(cfg.Level) == 0 { 77 | cfg.Level = LevelType("debug") 78 | } 79 | if len(cfg.Out) == 0 { 80 | cfg.Out = OutStdout 81 | } 82 | if len(cfg.OutFormat) == 0 { 83 | cfg.OutFormat = OutFormatConsole 84 | } 85 | if (cfg.Out == OutFile || cfg.Out == OutFileStdout) && len(cfg.Path) == 0 { 86 | return nil, errors.New("path is empty") 87 | } 88 | enccfg := zap.NewProductionEncoderConfig() 89 | enccfg.EncodeTime = zapcore.TimeEncoderOfLayout("2006-01-02 15:04:05") //zapcore.ISO8601TimeEncoder 90 | if cfg.LevelColor && 91 | cfg.OutFormat == OutFormatConsole && 92 | cfg.Out == OutStdout { 93 | enccfg.EncodeLevel = zapcore.CapitalColorLevelEncoder 94 | } else { 95 | enccfg.EncodeLevel = zapcore.CapitalLevelEncoder 96 | } 97 | 98 | var enc zapcore.Encoder 99 | var file io.Writer 100 | 101 | if cfg.Out == OutFile || cfg.Out == OutFileStdout { 102 | lumberJackLogger := &lumberjack.Logger{ 103 | Filename: cfg.Path, 104 | MaxSize: cfg.MaxSize, //在进行切割之前,日志文件的最大大小(以MB为单位) 105 | MaxBackups: cfg.MaxBackups, //保留旧文件的最大个数 106 | MaxAge: cfg.MaxAge, //保留旧文件的最大天数 107 | Compress: cfg.Compress, //是否压缩/归档旧文件 108 | } 109 | file = lumberJackLogger 110 | } else { 111 | file = os.Stdout 112 | } 113 | if cfg.OutFormat == OutFormatConsole { 114 | enc = zapcore.NewConsoleEncoder(enccfg) 115 | } else { 116 | enc = zapcore.NewJSONEncoder(enccfg) 117 | } 118 | core := zapcore.NewCore(enc, 119 | zapcore.AddSync(file), cfg.Level.ZapLevel()) 120 | ler := zap.New(core, zap.AddCaller(), zap.Development(), zap.AddCallerSkip(1)) 121 | logsugared = ler.Sugar() 122 | return ler, nil 123 | } 124 | 125 | var logsugared *zap.SugaredLogger 126 | var logg *zap.Logger 127 | var logsugaredMap map[string]*zap.SugaredLogger 128 | var defaultKey = utils.DefaultKey.DefaultKey 129 | 130 | func Load(logs map[string]Config, options ...Option) error { 131 | opt := applyGenGormOptions(options...) 132 | defaultKey = opt.defKey.DefaultKey 133 | logsugaredMap = make(map[string]*zap.SugaredLogger) 134 | if len(opt.defKey.Keys) != 0 { 135 | opt.defKey.Keys = append(opt.defKey.Keys, opt.defKey.DefaultKey) 136 | for _, key := range opt.defKey.Keys { 137 | _, is := logsugaredMap[key] 138 | if is { 139 | continue 140 | } 141 | cfg, is := logs[key] 142 | if !is { 143 | return errors.New(key + " log key not found") 144 | } 145 | zaplog, err := New(&cfg) 146 | if err != nil { 147 | return err 148 | } 149 | logsugaredMap[key] = zaplog.Sugar() 150 | if key == opt.defKey.DefaultKey { 151 | logg = zaplog 152 | logsugared = zaplog.Sugar() 153 | } 154 | } 155 | return nil 156 | } 157 | for name := range logs { 158 | cfg := logs[name] 159 | zaplog, err := New(&cfg) 160 | if err != nil { 161 | return err 162 | } 163 | logsugaredMap[name] = zaplog.Sugar() 164 | if name == opt.defKey.DefaultKey { 165 | logg = zaplog 166 | logsugared = zaplog.Sugar() 167 | } 168 | } 169 | return nil 170 | } 171 | 172 | func GetLogger(key ...string) *zap.SugaredLogger { 173 | k := defaultKey 174 | if len(key) != 0 { 175 | k = key[0] 176 | } 177 | log, is := logsugaredMap[k] 178 | if is { 179 | return log 180 | } 181 | panic(k + "log key not found") 182 | } 183 | 184 | func NewGlobal(cfg Config) error { 185 | log, err := New(&cfg) 186 | if err != nil { 187 | return err 188 | } 189 | logsugared = log.Sugar() 190 | return nil 191 | } 192 | 193 | func FieldDebugf(msg string, a ...zap.Field) { 194 | logg.Info(msg, a...) 195 | } 196 | func FieldDebug(msg string, a ...zap.Field) { 197 | logg.Debug(msg, a...) 198 | } 199 | func FieldInfo(msg string, a ...zap.Field) { 200 | logg.Info(msg, a...) 201 | } 202 | 203 | func FieldWarn(msg string, a ...zap.Field) { 204 | logg.Warn(msg, a...) 205 | } 206 | 207 | func FieldError(msg string, a ...zap.Field) { 208 | logg.Error(msg, a...) 209 | } 210 | 211 | func FieldPanic(msg string, a ...zap.Field) { 212 | logg.Panic(msg, a...) 213 | } 214 | func FieldFatal(msg string, a ...zap.Field) { 215 | logg.Fatal(msg, a...) 216 | } 217 | func FieldDPanic(msg string, a ...zap.Field) { 218 | logg.DPanic(msg, a...) 219 | } 220 | 221 | func Debug(a ...any) { 222 | logsugared.Debug(a...) 223 | } 224 | 225 | func Debugf(format string, a ...any) { 226 | logsugared.Debugf(format, a...) 227 | } 228 | 229 | func Debugw(msg string, keysAndValues ...any) { 230 | logsugared.Debugw(msg, keysAndValues...) 231 | } 232 | 233 | func Info(a ...any) { 234 | logsugared.Info(a...) 235 | } 236 | 237 | func Infof(format string, a ...any) { 238 | logsugared.Infof(format, a...) 239 | } 240 | func Infow(msg string, keysAndValues ...any) { 241 | logsugared.Infow(msg, keysAndValues...) 242 | } 243 | 244 | func Warn(a ...any) { 245 | logsugared.Warn(a...) 246 | } 247 | func Warnf(format string, a ...any) { 248 | logsugared.Warnf(format, a...) 249 | } 250 | func Warnw(msg string, keysAndValues ...any) { 251 | logsugared.Warnw(msg, keysAndValues...) 252 | } 253 | 254 | func Error(a ...any) { 255 | logsugared.Error(a...) 256 | } 257 | func Errorf(format string, keysAndValues ...any) { 258 | logsugared.Errorf(format, keysAndValues...) 259 | } 260 | func Errorw(msg string, keysAndValues ...any) { 261 | logsugared.Errorw(msg, keysAndValues...) 262 | } 263 | 264 | func DPanic(a ...any) { 265 | logsugared.DPanic(a...) 266 | } 267 | func Panicf(format string, a ...any) { 268 | logsugared.Panicf(format, a...) 269 | } 270 | func Panicw(msg string, keysAndValues ...any) { 271 | logsugared.Panicw(msg, keysAndValues...) 272 | } 273 | 274 | func Close() { 275 | logsugared.Desugar().Sync() 276 | } 277 | 278 | func CloseAll() { 279 | for _, log := range logsugaredMap { 280 | log.Desugar().Sync() 281 | } 282 | } 283 | 284 | func LoggerSugared() *zap.SugaredLogger { 285 | return logsugared 286 | } 287 | -------------------------------------------------------------------------------- /httpx/controler.go: -------------------------------------------------------------------------------- 1 | package httpx 2 | 3 | import ( 4 | "errors" 5 | "net/http" 6 | "strconv" 7 | "time" 8 | 9 | "github.com/gin-gonic/gin" 10 | "github.com/gin-gonic/gin/binding" 11 | "github.com/go-playground/validator/v10" 12 | ) 13 | 14 | type ResponseData struct { 15 | Code int `json:"code"` 16 | Msg string `json:"msg"` 17 | UUID string `json:"uuid"` 18 | Data any `json:"data"` 19 | } 20 | 21 | type Controller struct { 22 | } 23 | 24 | func (b Controller) Parameter(g *gin.Context, req any, binds ...binding.Binding) error { 25 | for i := range binds { 26 | if err := g.ShouldBindWith(req, binds[i]); err != nil { 27 | Fail(g, err) 28 | return err 29 | } 30 | } 31 | return nil 32 | } 33 | 34 | func (b Controller) Handle(c *gin.Context, f func() (data any, err error)) { 35 | data, err := f() 36 | if err != nil { 37 | Fail(c, err) 38 | return 39 | } 40 | Success(c, data) 41 | } 42 | 43 | func (b Controller) HandleParameter(c *gin.Context, req any, binds []binding.Binding, f func() (data any, err error)) { 44 | if err := b.Parameter(c, req, binds...); err != nil { 45 | return 46 | } 47 | b.Handle(c, f) 48 | } 49 | 50 | type AuthInf interface { 51 | Resolver(g *gin.Context) error 52 | } 53 | 54 | func (b Controller) Auth(g *gin.Context, auth AuthInf) error { 55 | if err := auth.Resolver(g); err != nil { 56 | Fail(g, err) 57 | return err 58 | } 59 | return nil 60 | } 61 | func (b Controller) HandleAuthParameter(c *gin.Context, auth AuthInf, req any, binds []binding.Binding, f func() (data any, err error)) { 62 | err := b.Auth(c, auth) 63 | if err != nil { 64 | return 65 | } 66 | b.HandleParameter(c, req, binds, f) 67 | } 68 | func Json(g *gin.Context, status int, data any) { 69 | g.JSON(status, data) 70 | } 71 | 72 | func Success(g *gin.Context, data any) { 73 | Json(g, 200, ResponseData{ 74 | Code: ErrCodeTypeSuccess.GetCode(), 75 | Msg: ErrCodeTypeSuccess.Error(), 76 | Data: data, 77 | UUID: uuid(), 78 | }) 79 | } 80 | 81 | func Fail(g *gin.Context, err error) { 82 | if err == nil { 83 | err = errors.New("fail") 84 | } 85 | 86 | switch er := err.(type) { 87 | case validator.ValidationErrors: 88 | Json(g, http.StatusOK, ResponseData{ 89 | Code: ErrCodeTypeFail.GetCode(), 90 | UUID: uuid(), 91 | // Msg: er[0].Translate(TransZh), 92 | Data: nil, 93 | }) 94 | case ErrCodeType: 95 | Json(g, http.StatusOK, ResponseData{ 96 | Code: er.GetCode(), 97 | Msg: er.Error(), 98 | Data: nil, 99 | UUID: uuid(), 100 | }) 101 | case *ErrCodeType: 102 | Json(g, http.StatusOK, ResponseData{ 103 | Code: er.GetCode(), 104 | Msg: er.Error(), 105 | Data: nil, 106 | UUID: uuid(), 107 | }) 108 | case ErrMsgData: 109 | Json(g, http.StatusOK, ResponseData{ 110 | Code: er.GetCode(), 111 | Msg: er.Error(), 112 | Data: er.GetData(), 113 | UUID: uuid(), 114 | }) 115 | case *ErrMsgData: 116 | Json(g, http.StatusOK, ResponseData{ 117 | Code: er.GetCode(), 118 | Msg: er.Error(), 119 | Data: er.GetData(), 120 | UUID: uuid(), 121 | }) 122 | default: 123 | Json(g, http.StatusOK, ResponseData{ 124 | Code: ErrCodeTypeFail.GetCode(), 125 | Msg: er.Error(), 126 | Data: nil, 127 | UUID: uuid(), 128 | }) 129 | } 130 | } 131 | 132 | func uuid() string { 133 | return strconv.FormatInt(time.Now().UnixNano(), 10) 134 | } 135 | 136 | func Handle(f func(*gin.Context) (data any, err error)) func(g *gin.Context) { 137 | return func(g *gin.Context) { 138 | data, err := f(g) 139 | if err != nil { 140 | Fail(g, err) 141 | return 142 | } 143 | Success(g, data) 144 | } 145 | } 146 | 147 | func HandleParameter[P any](f func(g *gin.Context, parameter P) (data any, err error), binds ...binding.Binding) func(g *gin.Context) { 148 | return func(g *gin.Context) { 149 | var req P 150 | for _, bind := range binds { 151 | if err := g.ShouldBindWith(&req, bind); err != nil { 152 | Fail(g, err) 153 | return 154 | } 155 | } 156 | data, err := f(g, req) 157 | if err != nil { 158 | Fail(g, err) 159 | return 160 | } 161 | Success(g, data) 162 | } 163 | } 164 | 165 | type AuthInfG[A any] interface { 166 | *A 167 | AuthInf 168 | } 169 | 170 | type HandleType[A any] func(g *gin.Context, auth A) (data any, err error) 171 | type HandleResType[A any] func(g *gin.Context, auth A) func(res ...any) 172 | 173 | type HandleT[A any] interface { 174 | HandleType[A] | HandleResType[A] 175 | } 176 | 177 | func HandleAuthT[A any, F HandleT[A], Ag AuthInfG[A]](f F, binds ...binding.Binding) func(g *gin.Context) { 178 | return func(g *gin.Context) { 179 | auth := new(A) 180 | var a Ag = auth 181 | if err := a.Resolver(g); err != nil { 182 | Fail(g, err) 183 | return 184 | } 185 | var req any 186 | for _, bind := range binds { 187 | if err := g.ShouldBindWith(&req, bind); err != nil { 188 | Fail(g, err) 189 | return 190 | } 191 | } 192 | var fn any = f 193 | switch fnc := fn.(type) { 194 | case HandleType[A]: 195 | data, err := fnc(g, *auth) 196 | if err != nil { 197 | Fail(g, err) 198 | return 199 | } 200 | Success(g, data) 201 | case HandleResType[A]: 202 | fnc(g, *auth)(resf(g)) 203 | } 204 | } 205 | } 206 | 207 | func HandleAuth[A any, Ag AuthInfG[A]](f func(g *gin.Context, auth A) (data any, err error)) func(g *gin.Context) { 208 | return func(g *gin.Context) { 209 | auth := new(A) 210 | var a Ag = auth 211 | if err := a.Resolver(g); err != nil { 212 | Fail(g, err) 213 | return 214 | } 215 | data, err := f(g, *auth) 216 | if err != nil { 217 | Fail(g, err) 218 | return 219 | } 220 | Success(g, data) 221 | } 222 | } 223 | 224 | func HandleAuthRes[A any, Ag AuthInfG[A]](f func(g *gin.Context, auth A) func(res ...any)) func(g *gin.Context) { 225 | return func(g *gin.Context) { 226 | auth := new(A) 227 | var a Ag = auth 228 | if err := a.Resolver(g); err != nil { 229 | Fail(g, err) 230 | return 231 | } 232 | f(g, *auth)(resf(g)) 233 | } 234 | } 235 | 236 | type Res struct { 237 | Data any 238 | HttpStatus HttpStatus 239 | Code ErrCodeType 240 | Msg string 241 | } 242 | 243 | type HttpStatus int 244 | type MsgType string 245 | type FailType string 246 | 247 | func resf(ctx *gin.Context) func(res ...any) { 248 | return func(res ...any) { 249 | re := Res{ 250 | HttpStatus: http.StatusOK, 251 | Code: ErrCodeTypeSuccess, 252 | Msg: ErrCodeTypeSuccess.Error(), 253 | } 254 | for i := range res { 255 | resdata := res[i] 256 | switch data := resdata.(type) { 257 | case HttpStatus: 258 | re.HttpStatus = data 259 | case ErrCodeType: 260 | re.Code = data 261 | re.Msg = data.Error() 262 | case int: 263 | re.Code = ErrCodeType(data) 264 | case MsgType: 265 | re.Msg = string(data) 266 | case validator.ValidationErrors: 267 | re.Code = ErrCodeTypeFail 268 | // re.Msg = data[0].Translate(TransZh) 269 | case *ErrCodeType: 270 | re.Code = *data 271 | re.Msg = data.Error() 272 | case ErrMsgData: 273 | re.Code = ErrCodeType(data.GetCode()) 274 | re.Msg = data.Error() 275 | case *ErrMsgData: 276 | re.Code = ErrCodeType(data.GetCode()) 277 | re.Msg = data.Error() 278 | case FailType: 279 | re.Code = ErrCodeTypeFail 280 | re.Msg = string(data) 281 | default: 282 | re.Data = data 283 | } 284 | } 285 | Json(ctx, int(re.HttpStatus), ResponseData{ 286 | Code: int(re.Code), 287 | Msg: re.Msg, 288 | Data: re.Data, 289 | UUID: uuid(), 290 | }) 291 | } 292 | } 293 | 294 | func HandleAuthParameter[A any, AInf AuthInfG[A], P any](f func(g *gin.Context, auth A, parameter P) (data any, err error), binds ...binding.Binding) func(g *gin.Context) { 295 | return func(g *gin.Context) { 296 | auth := new(A) 297 | var a AInf = auth 298 | if err := a.Resolver(g); err != nil { 299 | Fail(g, err) 300 | return 301 | } 302 | var req P 303 | for _, bind := range binds { 304 | if err := g.ShouldBindWith(&req, bind); err != nil { 305 | Fail(g, err) 306 | return 307 | } 308 | } 309 | data, err := f(g, *auth, req) 310 | if err != nil { 311 | Fail(g, err) 312 | return 313 | } 314 | Success(g, data) 315 | } 316 | } 317 | func HandleAuthParameterRes[A any, AInf AuthInfG[A], P any](f func(g *gin.Context, auth A, parameter P) func(res ...any), binds ...binding.Binding) func(g *gin.Context) { 318 | return func(g *gin.Context) { 319 | auth := new(A) 320 | var a AInf = auth 321 | if err := a.Resolver(g); err != nil { 322 | Fail(g, err) 323 | return 324 | } 325 | var req P 326 | for _, bind := range binds { 327 | if err := g.ShouldBindWith(&req, bind); err != nil { 328 | Fail(g, err) 329 | return 330 | } 331 | } 332 | f(g, *auth, req)(resf(g)) 333 | } 334 | } 335 | 336 | type AuthParameter[A any, P any] struct { 337 | Context *gin.Context 338 | Auth A 339 | Parameter P 340 | } 341 | 342 | func HandleAuthParameter2[A any, AInf AuthInfG[A], P any](f func(p AuthParameter[A, P]) (data any, err error), binds ...binding.Binding) func(g *gin.Context) { 343 | return func(g *gin.Context) { 344 | auth := new(A) 345 | var a AInf = auth 346 | if err := a.Resolver(g); err != nil { 347 | Fail(g, err) 348 | return 349 | } 350 | var req P 351 | for _, bind := range binds { 352 | if err := g.ShouldBindWith(&req, bind); err != nil { 353 | Fail(g, err) 354 | return 355 | } 356 | } 357 | data, err := f(AuthParameter[A, P]{ 358 | Context: g, 359 | Auth: *auth, 360 | Parameter: req, 361 | }) 362 | if err != nil { 363 | Fail(g, err) 364 | return 365 | } 366 | Success(g, data) 367 | } 368 | } 369 | -------------------------------------------------------------------------------- /httpx/gin.go: -------------------------------------------------------------------------------- 1 | package httpx 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/http" 7 | "slices" 8 | "strconv" 9 | "time" 10 | 11 | "github.com/gin-contrib/cors" 12 | 13 | ginzap "github.com/gin-contrib/zap" 14 | "github.com/gin-gonic/gin" 15 | swaggerFiles "github.com/swaggo/files" 16 | ginSwagger "github.com/swaggo/gin-swagger" 17 | "github.com/wjoj/tool/v2/log" 18 | "github.com/wjoj/tool/v2/utils" 19 | "go.uber.org/zap" 20 | ) 21 | 22 | type Config struct { 23 | Debug bool `yaml:"debug" json:"debug"` 24 | Log bool `yaml:"log" json:"log"` 25 | LogName string `yaml:"logName" json:"logName"` //指定log的名称 26 | Port int `yaml:"port" json:"port"` 27 | ShutdownCloseMaxWait time.Duration `yaml:"shutdownCloseMaxWait" json:"shutdownCloseMaxWait"` // 28 | Ping bool `yaml:"ping" json:"ping"` 29 | Swagger bool `yaml:"swagger" json:"swagger"` // 是否开启docs 30 | RoutePrefix string `yaml:"routePrefix" json:"routePrefix"` // 路由前缀 31 | Cors bool `yaml:"cors" json:"cors"` // 是否启用cors 32 | CorsCfg CorsConfig `yaml:"corsCfg" json:"corsCfg"` 33 | } 34 | 35 | type CorsConfig struct { 36 | AllowOrigins []string `yaml:"allowOrigins" json:"allowOrigins"` 37 | AllowMethods []string `yaml:"allowMethods" json:"allowMethods"` 38 | AllowHeaders []string `yaml:"allowHeaders" json:"allowHeaders"` 39 | ExposeHeaders []string `yaml:"exposeHeaders" json:"exposeHeaders"` 40 | AllowCredentials bool `yaml:"allowCredentials" json:"allowCredentials"` 41 | MaxAge utils.Duration `yaml:"maxAge" json:"maxAge"` 42 | } 43 | 44 | type Http struct { 45 | cfg *Config 46 | *gin.Engine 47 | srv *http.Server 48 | done chan struct{} 49 | } 50 | 51 | func New(cfg *Config) (*Http, error) { 52 | var err error 53 | if cfg.Port == 0 { 54 | cfg.Port = 8080 55 | } 56 | if cfg.ShutdownCloseMaxWait == 0 { 57 | cfg.ShutdownCloseMaxWait = 5 * time.Minute 58 | } 59 | if len(cfg.LogName) == 0 { 60 | cfg.LogName = utils.DefaultKey.DefaultKey 61 | } 62 | if len(cfg.CorsCfg.MaxAge) == 0 { 63 | cfg.CorsCfg.MaxAge = "12h" 64 | } 65 | if cfg.Debug { 66 | gin.SetMode(gin.DebugMode) 67 | } else { 68 | gin.SetMode(gin.ReleaseMode) 69 | } 70 | var g *gin.Engine 71 | if cfg.Debug { 72 | g = gin.Default() 73 | } else { 74 | g = gin.New() 75 | if cfg.Log && cfg.LogName == "--" { 76 | g.Use(gin.Recovery()) 77 | g.Use(gin.Logger()) 78 | } 79 | } 80 | g.RouterGroup = *g.Group(cfg.RoutePrefix) 81 | if cfg.Log && cfg.LogName != "--" { 82 | // g.Use(zapLogger(log.GetLogger(cfg.LogName).Desugar())) 83 | logc := log.GetLogger(cfg.LogName).Desugar() 84 | g.Use(ginzap.Ginzap(logc, time.RFC3339, true)) 85 | g.Use(ginzap.RecoveryWithZap(logc, true)) 86 | } 87 | if cfg.Cors { 88 | corsConfig := cors.DefaultConfig() 89 | if len(cfg.CorsCfg.AllowOrigins) > 0 { 90 | corsConfig.AllowAllOrigins = false 91 | corsConfig.AllowOrigins = cfg.CorsCfg.AllowOrigins 92 | } else { 93 | corsConfig.AllowAllOrigins = true // 允许所有来源 94 | corsConfig.AllowOriginWithContextFunc = func(c *gin.Context, origin string) bool { 95 | if len(cfg.CorsCfg.AllowOrigins) == 0 || len(origin) == 0 { 96 | return true 97 | } 98 | return slices.Contains(cfg.CorsCfg.AllowOrigins, origin) 99 | } 100 | } 101 | if len(cfg.CorsCfg.AllowMethods) > 0 { 102 | corsConfig.AllowMethods = cfg.CorsCfg.AllowMethods 103 | } else { 104 | corsConfig.AllowMethods = []string{"GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"} 105 | } 106 | if len(cfg.CorsCfg.AllowHeaders) > 0 { 107 | corsConfig.AllowHeaders = cfg.CorsCfg.AllowHeaders 108 | } else { 109 | corsConfig.AllowHeaders = []string{"Origin", "Content-Length", "Content-Type", "Authorization"} 110 | } 111 | corsConfig.AllowCredentials = cfg.CorsCfg.AllowCredentials 112 | corsConfig.ExposeHeaders = cfg.CorsCfg.ExposeHeaders 113 | corsConfig.MaxAge, err = cfg.CorsCfg.MaxAge.ParseDuration() 114 | if err != nil { 115 | return nil, fmt.Errorf("parse corsCfg.maxAge error: %v", err) 116 | } 117 | g.Use(cors.New(corsConfig)) 118 | } 119 | 120 | if len(cfg.RoutePrefix) == 0 { 121 | g.RouterGroup = *g.Group(cfg.RoutePrefix) 122 | } 123 | if cfg.Ping { 124 | g.GET("/ping", func(c *gin.Context) { 125 | c.JSON(200, gin.H{ 126 | "message": "pong", 127 | }) 128 | }) 129 | } 130 | if cfg.Swagger { 131 | g.RouterGroup.GET("docs/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) 132 | } 133 | return &Http{ 134 | cfg: cfg, 135 | Engine: g, 136 | }, nil 137 | } 138 | 139 | func (h *Http) Run(f func(eng *gin.Engine), fc func()) error { 140 | if f != nil { 141 | f(h.Engine) 142 | } 143 | h.srv = &http.Server{ 144 | Addr: ":" + strconv.Itoa(h.cfg.Port), 145 | Handler: h.Engine, 146 | } 147 | // 创建优雅关闭的channel 148 | h.done = make(chan struct{}) 149 | go func() { 150 | // 启动服务 151 | fc() 152 | if err := h.srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { 153 | if h.cfg.Log && h.cfg.LogName != "--" { 154 | log.GetLogger(h.cfg.LogName).Errorf("listen: %s", err) 155 | } 156 | } 157 | close(h.done) 158 | }() 159 | 160 | // // 监听系统信号 161 | // quit := make(chan os.Signal, 1) 162 | // signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) 163 | // <-quit 164 | // if h.cfg.Log && h.cfg.LogName != "--" { 165 | // log.GetLogger(h.cfg.LogName).Info("Shutting down server...") 166 | // } 167 | // // 创建超时上下文 168 | // ctx, cancel := context.WithTimeout(context.Background(), h.cfg.ShutdownCloseMaxWait) 169 | // defer cancel() 170 | // // 关闭服务器 171 | // if err := h.srv.Shutdown(ctx); err != nil { 172 | // if h.cfg.Log && h.cfg.LogName != "--" { 173 | // log.GetLogger(h.cfg.LogName).Errorf("Server forced to shutdown: %v", err) 174 | // } 175 | // return err 176 | // } 177 | // // 等待所有请求完成 178 | // select { 179 | // case <-h.done: 180 | // if h.cfg.Log && h.cfg.LogName != "--" { 181 | // log.GetLogger(h.cfg.LogName).Info("Server gracefully stopped") 182 | // } 183 | // case <-ctx.Done(): 184 | // if h.cfg.Log && h.cfg.LogName != "--" { 185 | // log.GetLogger(h.cfg.LogName).Warn("Timeout while waiting for requests to complete") 186 | // } 187 | // } 188 | return nil 189 | } 190 | 191 | func (h *Http) Shutdown() error { 192 | ctx, cancel := context.WithTimeout(context.Background(), h.cfg.ShutdownCloseMaxWait) 193 | defer cancel() 194 | if err := h.srv.Shutdown(ctx); err != nil { 195 | if h.cfg.Log && h.cfg.LogName != "--" { 196 | log.GetLogger(h.cfg.LogName).Errorf("server forced to shutdown: %v", err) 197 | } 198 | return err 199 | } 200 | // 等待所有请求完成 201 | select { 202 | case <-h.done: 203 | if h.cfg.Log && h.cfg.LogName != "--" { 204 | log.GetLogger(h.cfg.LogName).Info("server gracefully stopped") 205 | } 206 | case <-ctx.Done(): 207 | if h.cfg.Log && h.cfg.LogName != "--" { 208 | log.GetLogger(h.cfg.LogName).Warn("timeout while waiting for requests to complete") 209 | } 210 | } 211 | return nil 212 | } 213 | 214 | var https = make(map[string]*Http) 215 | 216 | func Init(cfgs map[string]Config, options ...Option) error { 217 | log.Info("init http server...") 218 | if len(cfgs) == 0 { 219 | log.Warn("init http server cfgs is empty") 220 | return fmt.Errorf("init http server cfgs is empty") 221 | } 222 | opt := applyGenGormOptions(options...) 223 | if len(opt.defKey.Keys) == 0 { 224 | for key := range cfgs { 225 | opt.defKey.Keys = append(opt.defKey.Keys, key) 226 | } 227 | } else { 228 | opt.defKey.Keys = append(opt.defKey.Keys, opt.defKey.DefaultKey) 229 | } 230 | 231 | for _, key := range opt.defKey.Keys { 232 | _, is := https[key] 233 | if is { 234 | continue 235 | } 236 | cfg, is := cfgs[key] 237 | if !is { 238 | log.Errorf("init http server %s not found", key) 239 | return fmt.Errorf("init http server %s not found", key) 240 | } 241 | cli, err := New(&cfg) 242 | if err != nil { 243 | log.Errorf("init http server %s error: %v", key, err) 244 | return err 245 | } 246 | funs, is := opt.engFuncs[key] 247 | if !is { 248 | log.Warnf("init http server %s not found engineFunc", key) 249 | } 250 | https[key] = cli 251 | go func() { 252 | if err := cli.Run(func(eng *gin.Engine) { 253 | for i := range funs { 254 | funs[i](eng) 255 | } 256 | }, func() { 257 | log.Infof("http server port: %d", cli.cfg.Port) 258 | }); err != nil { 259 | log.Errorf("init http server %s run error: %v", key, err) 260 | return 261 | } 262 | }() 263 | } 264 | 265 | return nil 266 | } 267 | 268 | func ShutdownAll() error { 269 | for key, cli := range https { 270 | if err := cli.Shutdown(); err != nil { 271 | log.Warnf("http server %s shutdown error: %v", key, err) 272 | continue 273 | } 274 | } 275 | return nil 276 | } 277 | 278 | func zapLogger(logger *zap.Logger) gin.HandlerFunc { 279 | return func(c *gin.Context) { 280 | start := time.Now() 281 | path := c.Request.URL.Path 282 | query := c.Request.URL.RawQuery 283 | 284 | c.Next() 285 | 286 | end := time.Now() 287 | latency := end.Sub(start) 288 | 289 | logger.Info(path, 290 | zap.Int("status", c.Writer.Status()), 291 | zap.String("method", c.Request.Method), 292 | zap.String("path", path), 293 | zap.String("query", query), 294 | // zap.Any("keys", c.Keys), 295 | zap.String("ip", c.ClientIP()), 296 | zap.String("user-agent", c.Request.UserAgent()), 297 | zap.Duration("latency", latency), 298 | zap.Int("size", c.Writer.Size()), 299 | zap.String("error", c.Errors.ByType(gin.ErrorTypePrivate).String()), 300 | ) 301 | } 302 | } 303 | -------------------------------------------------------------------------------- /db/dbx/gorm.go: -------------------------------------------------------------------------------- 1 | package dbx 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | "fmt" 7 | logs "log" 8 | "os" 9 | "time" 10 | 11 | "github.com/wjoj/tool/v2/log" 12 | "github.com/wjoj/tool/v2/utils" 13 | "gorm.io/driver/clickhouse" 14 | "gorm.io/driver/mysql" 15 | "gorm.io/driver/postgres" 16 | "gorm.io/driver/sqlite" 17 | "gorm.io/driver/sqlserver" 18 | "gorm.io/gorm" 19 | "gorm.io/gorm/clause" 20 | "gorm.io/gorm/logger" 21 | "gorm.io/gorm/schema" 22 | ) 23 | 24 | type DB = gorm.DB 25 | type LogLevelType string 26 | 27 | const ( 28 | LogLevelError LogLevelType = "error" 29 | LogLevelWarn LogLevelType = "warn" 30 | LogLevelInfo LogLevelType = "info" 31 | LogLevelSilent LogLevelType = "silent" 32 | ) 33 | 34 | func (l LogLevelType) GormLoggerLevel() logger.LogLevel { 35 | if l == LogLevelError { 36 | return logger.Error 37 | } else if l == LogLevelWarn { 38 | return logger.Warn 39 | } else if l == LogLevelInfo { 40 | return logger.Info 41 | } else if l == LogLevelSilent { 42 | return logger.Silent 43 | } 44 | return logger.Info 45 | } 46 | 47 | type DriverType string 48 | 49 | const ( 50 | DriverMySQL DriverType = "mysql" 51 | DriverSQLite DriverType = "sqlite3" 52 | DriverPostGres DriverType = "postgres" 53 | DriverMsSQL DriverType = "mssql" 54 | DriverSQLServer DriverType = "sqlserver" 55 | DriverClickHouse DriverType = "clickhouse" 56 | ) 57 | 58 | type Config struct { 59 | Driver DriverType `yaml:"driver" json:"driver"` 60 | Host string `yaml:"host" json:"host"` 61 | Port int `yaml:"port" json:"port"` 62 | User string `yaml:"user" json:"user"` 63 | Pass string `yaml:"password" json:"password"` 64 | DbName string `yaml:"dbname" json:"dbname"` 65 | Debug bool `yaml:"debug" json:"debug"` 66 | Prefix string `yaml:"prefix" json:"prefix"` 67 | Charset string `yaml:"charset" json:"charset"` 68 | MaxIdleConns int `yaml:"maxIdleConns" json:"maxIdleConns"` 69 | MaxOpenConns int `yaml:"maxOpenConns" json:"maxOpenConns"` 70 | ConnMaxLifetime time.Duration `yaml:"connMaxLifetime" json:"connMaxLifetime"` 71 | ConnMaxIdleTime time.Duration `yaml:"connMaxIdleTime" json:"connMaxIdleTime"` 72 | TimeOut int `json:"timeout" yaml:"timeout"` 73 | LogLevel LogLevelType `yaml:"logLevel" json:"logLevel"` 74 | LogName string `yaml:"logName" json:"logName"` 75 | } 76 | 77 | func New(cfg *Config) (*gorm.DB, error) { 78 | if len(cfg.LogLevel) == 0 { 79 | cfg.LogLevel = LogLevelInfo 80 | } 81 | if len(cfg.LogName) == 0 { 82 | cfg.LogName = utils.DefaultKey.DefaultKey 83 | } 84 | var dbDSN gorm.Dialector 85 | switch cfg.Driver { 86 | case DriverMySQL: 87 | if len(cfg.User) == 0 || len(cfg.Pass) == 0 { 88 | return nil, fmt.Errorf("数据库链接错误: 数据库链接的账号或密码不能为空") 89 | } 90 | if len(cfg.DbName) == 0 { 91 | return nil, fmt.Errorf("数据库链接错误: 数据库链接的数据库名不能为空") 92 | } 93 | if len(cfg.Charset) == 0 { 94 | cfg.Charset = "utf8mb4" 95 | } 96 | if len(cfg.Host) == 0 { 97 | cfg.Host = "127.0.0.1" 98 | } 99 | if cfg.Port == 0 { 100 | cfg.Port = 3306 101 | } 102 | dbDSN = mysql.New(mysql.Config{ 103 | DSN: fmt.Sprintf("%v:%v@tcp(%v:%v)/%v?charset=%s&parseTime=True&loc=Local&allowNativePasswords=true", 104 | cfg.User, 105 | cfg.Pass, 106 | cfg.Host, 107 | cfg.Port, 108 | cfg.DbName, 109 | cfg.Charset, 110 | ), 111 | }) 112 | case DriverSQLite: 113 | dbDSN = sqlite.Open(fmt.Sprintf("%v.db", cfg.DbName)) 114 | case DriverPostGres: 115 | if len(cfg.User) == 0 || len(cfg.Pass) == 0 { 116 | return nil, fmt.Errorf("数据库链接错误: 数据库链接的账号或密码不能为空") 117 | } 118 | if len(cfg.DbName) == 0 { 119 | return nil, fmt.Errorf("数据库链接错误: 数据库链接的数据库名不能为空") 120 | } 121 | if len(cfg.Host) == 0 { 122 | cfg.Host = "127.0.0.1" 123 | } 124 | if cfg.Port == 0 { 125 | cfg.Port = 5432 126 | } 127 | dbDSN = postgres.Open(fmt.Sprintf("host=%s user=%s dbname=%s sslmode=disable password=%s port=%d", 128 | cfg.Host, cfg.User, cfg.DbName, cfg.Pass, cfg.Port)) 129 | case DriverMsSQL: 130 | case DriverSQLServer: 131 | if len(cfg.User) == 0 || len(cfg.Pass) == 0 { 132 | return nil, fmt.Errorf("数据库链接错误: 数据库链接的账号或密码不能为空") 133 | } 134 | if len(cfg.DbName) == 0 { 135 | return nil, fmt.Errorf("数据库链接错误: 数据库链接的数据库名不能为空") 136 | } 137 | if len(cfg.Host) == 0 { 138 | cfg.Host = "127.0.0.1" 139 | } 140 | if cfg.Port == 0 { 141 | cfg.Port = 1433 142 | } 143 | dbDSN = sqlserver.Open(fmt.Sprintf("sqlserver://%v:%v@%v:%v?database=%v", 144 | cfg.User, cfg.Pass, cfg.Host, cfg.Port, cfg.DbName)) 145 | case DriverClickHouse: 146 | if len(cfg.User) == 0 || len(cfg.Pass) == 0 { 147 | return nil, fmt.Errorf("数据库链接错误: 数据库链接的账号或密码不能为空") 148 | } 149 | if len(cfg.DbName) == 0 { 150 | return nil, fmt.Errorf("数据库链接错误: 数据库链接的数据库名不能为空") 151 | } 152 | if len(cfg.Host) == 0 { 153 | cfg.Host = "127.0.0.1" 154 | } 155 | if cfg.Port == 0 { 156 | cfg.Port = 9000 157 | } 158 | dbDSN = clickhouse.Open(fmt.Sprintf("clickhouse://%s:%s@%s:%d/%s?read_timeout=%d", 159 | cfg.User, cfg.Pass, cfg.Host, cfg.Port, cfg.DbName, cfg.TimeOut)) 160 | default: 161 | if len(cfg.DbName) == 0 { 162 | return nil, fmt.Errorf("数据库链接错误: 数据库链接的数据库名不能为空") 163 | } 164 | dbDSN = sqlite.Open(fmt.Sprintf("%v.db", cfg.DbName)) 165 | } 166 | sch := schema.NamingStrategy{ 167 | SingularTable: true, 168 | } 169 | if len(cfg.Prefix) != 0 { 170 | sch.TablePrefix = cfg.Prefix 171 | } 172 | dbConfig := &gorm.Config{ 173 | NamingStrategy: sch, 174 | } 175 | if len(cfg.LogName) != 0 { 176 | var out logger.Writer 177 | if cfg.LogName == "--" { 178 | out = logs.New(os.Stdout, "\r\n", logs.LstdFlags) 179 | dbConfig.Logger = logger.New( 180 | out, // io writer 181 | logger.Config{ 182 | SlowThreshold: time.Second, // Slow SQL threshold 183 | LogLevel: cfg.LogLevel.GormLoggerLevel(), // Log level 184 | IgnoreRecordNotFoundError: true, // Ignore ErrRecordNotFound error for logger 185 | Colorful: true, // Disable color 186 | }, 187 | ) 188 | } else { 189 | dbConfig.Logger = &zapLogger{log.GetLogger(cfg.LogName)} 190 | } 191 | 192 | } 193 | db, err := gorm.Open(dbDSN, dbConfig) 194 | if err != nil { 195 | return nil, fmt.Errorf("数据库链接错误: %v", err) 196 | } 197 | if cfg.Debug { 198 | db = db.Debug() 199 | } 200 | dc, err := db.DB() 201 | if err != nil { 202 | return nil, err 203 | } 204 | if cfg.MaxIdleConns != 0 { 205 | dc.SetMaxIdleConns(cfg.MaxIdleConns) 206 | } 207 | if cfg.MaxOpenConns != 0 { 208 | dc.SetMaxOpenConns(cfg.MaxOpenConns) 209 | } 210 | if cfg.ConnMaxLifetime != 0 { 211 | dc.SetConnMaxLifetime(cfg.ConnMaxLifetime) 212 | } 213 | if cfg.ConnMaxIdleTime != 0 { 214 | dc.SetConnMaxIdleTime(cfg.ConnMaxIdleTime) 215 | } 216 | if err := dc.Ping(); err != nil { 217 | return nil, err 218 | } 219 | return db, nil 220 | } 221 | 222 | var dbs = map[string]*DB{} //全局 223 | var db *DB 224 | var defaultKey = utils.DefaultKey.DefaultKey 225 | 226 | func Init(cfgs map[string]Config, options ...Option) error { 227 | log.Info("init db") 228 | opt := applyOptions(options...) 229 | defaultKey = opt.defKey.DefaultKey 230 | dbs = make(map[string]*DB) 231 | if len(opt.defKey.Keys) != 0 { 232 | opt.defKey.Keys = append(opt.defKey.Keys, opt.defKey.DefaultKey) 233 | for _, key := range opt.defKey.Keys { 234 | _, is := dbs[key] 235 | if is { 236 | continue 237 | } 238 | cfg, is := cfgs[key] 239 | if !is { 240 | log.Errorf("init db client %s not found", key) 241 | return fmt.Errorf("init db client %s not found", key) 242 | } 243 | cli, err := New(&cfg) 244 | if err != nil { 245 | log.Errorf("init db client %s error: %v", key, err) 246 | return err 247 | } 248 | dbs[key] = cli 249 | if key == defaultKey { 250 | db = cli 251 | } 252 | } 253 | log.Info("init db success") 254 | return nil 255 | } 256 | for name, cfg := range cfgs { 257 | cli, err := New(&cfg) 258 | if err != nil { 259 | log.Errorf("init db client %s error: %v", name, err) 260 | return err 261 | } 262 | dbs[name] = cli 263 | if name == defaultKey { 264 | db = cli 265 | } 266 | } 267 | log.Info("init db success") 268 | return nil 269 | } 270 | func InitGlobal(cfg *Config) error { 271 | var err error 272 | db, err = New(cfg) 273 | if err != nil { 274 | return err 275 | } 276 | return nil 277 | } 278 | 279 | func Get(key ...string) *DB { 280 | dbc, err := utils.Get("db", defaultKey, func(s string) (*DB, bool) { 281 | cli, is := dbs[s] 282 | return cli, is 283 | }, key...) 284 | if err != nil { 285 | panic(err) 286 | } 287 | return dbc 288 | } 289 | 290 | // Client 291 | func Client() *DB { 292 | return db 293 | } 294 | func Close() error { 295 | dc, err := db.DB() 296 | if err != nil { 297 | return err 298 | } 299 | return dc.Close() 300 | } 301 | 302 | func CloseAll() error { 303 | for _, cli := range dbs { 304 | dc, err := cli.DB() 305 | if err != nil { 306 | continue 307 | } 308 | dc.Close() 309 | } 310 | return nil 311 | } 312 | 313 | func Model(value any) *gorm.DB { 314 | return db.Model(value) 315 | } 316 | 317 | func Table(name string, args ...any) (tx *gorm.DB) { 318 | return db.Table(name, args...) 319 | } 320 | 321 | func Distinct(args ...any) (tx *DB) { 322 | return db.Distinct(args...) 323 | } 324 | 325 | func MapColumns(m map[string]string) (tx *DB) { 326 | return db.MapColumns(m) 327 | } 328 | 329 | // Create 创建记录 330 | func Create(value any) *gorm.DB { 331 | return db.Create(value) 332 | } 333 | func CreateInBatches(value any, batchSize int) (tx *DB) { 334 | return db.CreateInBatches(value, batchSize) 335 | } 336 | 337 | // Save 保存记录 338 | func Save(value any) *gorm.DB { 339 | return db.Save(value) 340 | } 341 | 342 | // First 获取第一条记录 343 | func First(dest any, conds ...any) *gorm.DB { 344 | return db.First(dest, conds...) 345 | } 346 | 347 | // Take 获取一条记录 348 | func Take(dest any, conds ...any) *gorm.DB { 349 | return db.Take(dest, conds...) 350 | } 351 | 352 | // Last 获取最后一条记录 353 | func Last(dest any, conds ...any) *gorm.DB { 354 | return db.Last(dest, conds...) 355 | } 356 | 357 | // Find 查询多条记录 358 | func Find(dest any, conds ...any) *gorm.DB { 359 | return db.Find(dest, conds...) 360 | } 361 | 362 | func FindInBatches(dest any, batchSize int, fc func(tx *DB, batch int) error) *DB { 363 | return db.FindInBatches(dest, batchSize, fc) 364 | } 365 | func FirstOrInit(dest any, conds ...any) (tx *DB) { 366 | return db.FirstOrInit(dest, conds...) 367 | } 368 | func FirstOrCreate(dest any, conds ...any) (tx *DB) { 369 | return db.FirstOrCreate(dest, conds...) 370 | } 371 | 372 | // Where 条件查询 373 | func Where(query any, args ...any) *gorm.DB { 374 | return db.Where(query, args...) 375 | } 376 | 377 | func Not(query any, args ...any) (tx *DB) { 378 | return db.Not(query, args...) 379 | } 380 | 381 | func Or(query any, args ...any) (tx *DB) { 382 | return db.Or(query, args...) 383 | } 384 | 385 | // Order 排序 386 | func Order(value any) *gorm.DB { 387 | return db.Order(value) 388 | } 389 | 390 | // Limit 限制数量 391 | func Limit(limit int) *gorm.DB { 392 | return db.Limit(limit) 393 | } 394 | 395 | // Offset 偏移量 396 | func Offset(offset int) *gorm.DB { 397 | return db.Offset(offset) 398 | } 399 | 400 | // Select 选择字段 401 | func Select(query any, args ...any) *gorm.DB { 402 | return db.Select(query, args...) 403 | } 404 | 405 | // Omit 忽略字段 406 | func Omit(columns ...string) *gorm.DB { 407 | return db.Omit(columns...) 408 | } 409 | 410 | // Group 分组 411 | func Group(name string) *gorm.DB { 412 | return db.Group(name) 413 | } 414 | 415 | // Having Having条件 416 | func Having(query any, args ...any) *gorm.DB { 417 | return db.Having(query, args...) 418 | } 419 | 420 | // Joins 关联查询 421 | func Joins(query string, args ...any) *gorm.DB { 422 | return db.Joins(query, args...) 423 | } 424 | func InnerJoins(query string, args ...any) (tx *DB) { 425 | return db.InnerJoins(query, args...) 426 | } 427 | 428 | // Scopes 作用域 429 | func Scopes(funcs ...func(*gorm.DB) *gorm.DB) *gorm.DB { 430 | return db.Scopes(funcs...) 431 | } 432 | 433 | // Preload 预加载 434 | func Preload(query string, args ...any) *gorm.DB { 435 | return db.Preload(query, args...) 436 | } 437 | 438 | // Raw 原生SQL 439 | func Raw(sql string, values ...any) *gorm.DB { 440 | return db.Raw(sql, values...) 441 | } 442 | 443 | // Exec 执行SQL 444 | func Exec(sql string, values ...any) *gorm.DB { 445 | return db.Exec(sql, values...) 446 | } 447 | 448 | // Delete 删除记录 449 | func Delete(value any, conds ...any) *gorm.DB { 450 | return db.Delete(value, conds...) 451 | } 452 | 453 | // Update 更新记录 454 | func Update(column string, value any) *gorm.DB { 455 | return db.Update(column, value) 456 | } 457 | 458 | // Updates 更新多个字段 459 | func Updates(values any) *gorm.DB { 460 | return db.Updates(values) 461 | } 462 | 463 | // Count 计数 464 | func Count(count *int64) *gorm.DB { 465 | return db.Count(count) 466 | } 467 | 468 | // Pluck 查询单个列 469 | func Pluck(column string, dest any) *gorm.DB { 470 | return db.Pluck(column, dest) 471 | } 472 | 473 | // Transaction 事务 474 | func Transaction(fc func(tx *gorm.DB) error, opts ...*sql.TxOptions) error { 475 | return db.Transaction(fc, opts...) 476 | } 477 | 478 | // Begin 开始事务 479 | func Begin(opts ...*sql.TxOptions) *gorm.DB { 480 | return db.Begin(opts...) 481 | } 482 | 483 | // Commit 提交事务 484 | func Commit() *gorm.DB { 485 | return db.Commit() 486 | } 487 | 488 | // Rollback 回滚事务 489 | func Rollback() *gorm.DB { 490 | return db.Rollback() 491 | } 492 | 493 | func WithContext(ctx context.Context) *DB { 494 | return db.WithContext(ctx) 495 | } 496 | 497 | func Scan(dest any) (tx *DB) { 498 | return db.Scan(dest) 499 | } 500 | func Clauses(conds ...clause.Expression) (tx *DB) { 501 | return db.Clauses(conds...) 502 | } 503 | func Attrs(attrs ...any) (tx *DB) { 504 | return db.Attrs(attrs...) 505 | } 506 | 507 | func Assign(attrs ...any) (tx *DB) { 508 | return db.Assign(attrs...) 509 | } 510 | func Unscoped() (tx *DB) { 511 | return db.Unscoped() 512 | } 513 | 514 | func AutoMigrate(models ...any) (err error) { 515 | return db.AutoMigrate(models...) 516 | } 517 | -------------------------------------------------------------------------------- /db/dbx/query.go: -------------------------------------------------------------------------------- 1 | package dbx 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "regexp" 7 | "runtime" 8 | "slices" 9 | "strings" 10 | 11 | "github.com/iancoleman/strcase" 12 | "github.com/wjoj/tool/v2/log" 13 | "gorm.io/gorm" 14 | ) 15 | 16 | var regfmt, _ = regexp.Compile(`(%[vsfdx0-9.]{1,}){1,}`) 17 | 18 | type funcName struct { 19 | funcNameMaps map[string]struct{} 20 | fieldFuncNames map[string]funcName 21 | } 22 | 23 | func (f funcName) IsFuncName(fname string) bool { 24 | if len(f.funcNameMaps) == 0 { 25 | return true 26 | } 27 | if _, is := f.funcNameMaps[fname]; is { 28 | return true 29 | } 30 | return false 31 | } 32 | func (f funcName) IsNotFuncName(fname string) bool { 33 | if len(f.funcNameMaps) == 0 { 34 | return false 35 | } 36 | if _, is := f.funcNameMaps[fname]; is { 37 | return true 38 | } 39 | return false 40 | } 41 | func (f funcName) GetFuncName(field string) funcName { 42 | if len(f.fieldFuncNames) == 0 { 43 | return funcName{ 44 | funcNameMaps: make(map[string]struct{}), 45 | fieldFuncNames: map[string]funcName{}, 46 | } 47 | } 48 | fName, is := f.fieldFuncNames[field] 49 | if !is { 50 | fName = funcName{ 51 | funcNameMaps: make(map[string]struct{}), 52 | fieldFuncNames: map[string]funcName{}, 53 | } 54 | } 55 | return fName 56 | } 57 | 58 | type KeyClauseType string 59 | 60 | const ( 61 | KeyClauseTypeWhere KeyClauseType = "where" 62 | KeyClauseTypeOrderBy KeyClauseType = "orderBy" 63 | KeyClauseTypeGroup KeyClauseType = "group" 64 | KeyClauseTypeHaving KeyClauseType = "having" 65 | KeyClauseTypeJoins KeyClauseType = "joins" 66 | ) 67 | 68 | // QueryConditionOptions Query condition configuration 69 | type QueryConditionOptions struct { 70 | tag string //重置tag 71 | funcNames funcName //加入条件的函数(使用名称) 72 | funcs []func(*gorm.DB) *gorm.DB //加入条件的函数 73 | notFuncNames funcName //不加入条件的函数 74 | keyClause map[KeyClauseType]struct{} //加入条件的关键子句 75 | } 76 | 77 | type QueryConditionOption func(c *QueryConditionOptions) 78 | 79 | // WithQueryConditionOptionTag Reset tag 80 | func WithQueryConditionOptionTag(tag string) QueryConditionOption { 81 | return func(c *QueryConditionOptions) { 82 | c.tag = tag 83 | } 84 | } 85 | 86 | // WithQueryConditionOptionKeyClause Reset keyClause 87 | func WithQueryConditionOptionKeyClause(keyClause map[KeyClauseType]struct{}) QueryConditionOption { 88 | return func(c *QueryConditionOptions) { 89 | c.keyClause = keyClause 90 | } 91 | } 92 | 93 | // WithQueryConditionOptionKeyClauseOrderBy Only use OrderBy 94 | func WithQueryConditionOptionKeyClauseOrderBy() QueryConditionOption { 95 | return func(c *QueryConditionOptions) { 96 | c.keyClause[KeyClauseTypeOrderBy] = struct{}{} 97 | } 98 | } 99 | 100 | // WithQueryConditionOptionKeyClauseNotOrderBy Do not use OrderBy 101 | func WithQueryConditionOptionKeyClauseNotOrderBy() QueryConditionOption { 102 | return func(c *QueryConditionOptions) { 103 | c.keyClause[KeyClauseTypeWhere] = struct{}{} 104 | c.keyClause[KeyClauseTypeGroup] = struct{}{} 105 | c.keyClause[KeyClauseTypeHaving] = struct{}{} 106 | c.keyClause[KeyClauseTypeJoins] = struct{}{} 107 | } 108 | } 109 | 110 | // funcNames: Use the name, such as:func1,Feild.func2,Feild2.Feild.func(字段名:Feild* 方法名:func*) 111 | // funcNames: The usage method structure is as followsfunc(db *gorm.DB) *gorm.DB, 如: OrderByC(bool) func(db *gorm.DB) *gorm.DB 或 OrderByC(db *gorm.DB) *gorm.DB 112 | // WithQueryConditionOptionFuncNames Reset the method name (the name of the method to be added for the query condition) 113 | func WithQueryConditionOptionFuncNames(funcNames ...any) QueryConditionOption { 114 | funcNamesc := &funcName{ 115 | funcNameMaps: map[string]struct{}{}, 116 | fieldFuncNames: map[string]funcName{}, 117 | } 118 | funcs := []func(*gorm.DB) *gorm.DB{} 119 | for _, fnames := range funcNames { 120 | switch fname := fnames.(type) { 121 | case func(*gorm.DB) *gorm.DB: 122 | funcs = append(funcs, fname) 123 | case string: 124 | analysisQueryConditionFuncNames(strings.Split(strings.TrimSpace(fname), "."), funcNamesc) 125 | default: 126 | log.Warnf("unsupported function:%+v", reflect.TypeOf(fnames)) 127 | } 128 | } 129 | return func(c *QueryConditionOptions) { 130 | c.funcNames = *funcNamesc 131 | c.funcs = funcs 132 | } 133 | } 134 | 135 | // notFuncNames: as:func1,Feild.func2,Feild2.Feild.func(字段名:Feild* 方法:func*) 136 | // WithQueryConditionOptionNotFuncNames Reset the method names that are not included (the method names of the queries that are not included) 137 | func WithQueryConditionOptionNotFuncNames(notFuncNames ...any) QueryConditionOption { 138 | funcNamesc := &funcName{ 139 | funcNameMaps: map[string]struct{}{}, 140 | fieldFuncNames: map[string]funcName{}, 141 | } 142 | for _, fname := range notFuncNames { 143 | kind := reflect.TypeOf(fname).Kind() 144 | switch kind { 145 | case reflect.String: 146 | analysisQueryConditionFuncNames(strings.Split(strings.TrimSpace(fname.(string)), "."), funcNamesc) 147 | case reflect.Func: 148 | name := runtime.FuncForPC(reflect.ValueOf(fname).Pointer()).Name() 149 | names := strings.Split(name, ".") 150 | var cpnames []string 151 | if strings.HasSuffix(names[len(names)-1], "-fm") { 152 | cpnames = []string{strings.ReplaceAll(names[len(names)-1], "-fm", "")} 153 | } else if strings.HasPrefix(names[len(names)-1], "func") { 154 | cpnames = []string{names[len(names)-2]} 155 | } 156 | analysisQueryConditionFuncNames(cpnames, funcNamesc) 157 | default: 158 | log.Warnf("(not func)unsupported type:%+v", kind) 159 | } 160 | } 161 | return func(c *QueryConditionOptions) { 162 | c.notFuncNames = *funcNamesc 163 | } 164 | } 165 | 166 | // WithQueryConditionOption opt Reset the method name for addition 167 | func WithQueryConditionOptionFuncName(opt funcName) QueryConditionOption { 168 | return func(c *QueryConditionOptions) { 169 | c.funcNames = opt 170 | } 171 | } 172 | 173 | // WithQueryConditionOptionNotFuncName opt Reset the method name without adding it. 174 | func WithQueryConditionOptionNotFuncName(opt funcName) QueryConditionOption { 175 | return func(c *QueryConditionOptions) { 176 | c.notFuncNames = opt 177 | } 178 | } 179 | 180 | // WithQueryConditionOption opt 181 | func WithQueryConditionOption(opt QueryConditionOptions) QueryConditionOption { 182 | return func(c *QueryConditionOptions) { 183 | *c = opt 184 | } 185 | } 186 | 187 | func applyQueryConditionOptions(options ...QueryConditionOption) QueryConditionOptions { 188 | opts := QueryConditionOptions{ 189 | tag: "query", //默认 190 | funcNames: funcName{ 191 | funcNameMaps: map[string]struct{}{}, 192 | fieldFuncNames: map[string]funcName{}, 193 | }, 194 | notFuncNames: funcName{ 195 | funcNameMaps: map[string]struct{}{}, 196 | fieldFuncNames: map[string]funcName{}, 197 | }, 198 | keyClause: map[KeyClauseType]struct{}{}, 199 | } 200 | for _, option := range options { 201 | if option == nil { 202 | continue 203 | } 204 | option(&opts) 205 | } 206 | if len(opts.keyClause) == 0 { 207 | opts.keyClause = map[KeyClauseType]struct{}{ 208 | KeyClauseTypeWhere: {}, 209 | KeyClauseTypeOrderBy: {}, 210 | KeyClauseTypeGroup: {}, 211 | KeyClauseTypeHaving: {}, 212 | KeyClauseTypeJoins: {}, 213 | } 214 | } 215 | return opts 216 | } 217 | func analysisQueryConditionFuncNames(fs []string, fc *funcName) { 218 | if len(fs) == 0 { 219 | return 220 | } 221 | if len(fs) == 1 { 222 | fc.funcNameMaps[fs[0]] = struct{}{} 223 | return 224 | } 225 | fieldFs, is := fc.fieldFuncNames[fs[0]] 226 | if !is { 227 | fieldFs = funcName{ 228 | funcNameMaps: make(map[string]struct{}), 229 | fieldFuncNames: make(map[string]funcName), 230 | } 231 | fc.fieldFuncNames[fs[0]] = fieldFs 232 | } 233 | if len(fs) == 2 { 234 | fieldFs.funcNameMaps[fs[1]] = struct{}{} 235 | return 236 | } 237 | fieldFs2, is := fieldFs.fieldFuncNames[fs[1]] 238 | if !is { 239 | fieldFs2 = funcName{ 240 | funcNameMaps: make(map[string]struct{}), 241 | fieldFuncNames: make(map[string]funcName), 242 | } 243 | fieldFs.fieldFuncNames[fs[1]] = fieldFs2 244 | } 245 | analysisQueryConditionFuncNames(fs[2:], &fieldFs2) 246 | } 247 | 248 | // item: tag例子 249 | // 250 | // 例子1 field type `query` 默认sql语句 field = ? 251 | // 例子1 field type `query:"or"` 默认sql语句 or field = ? 252 | // 例子2 field type `query:"field like '%%%v%%'"` 253 | // 例子2 field type `query:"field like '%%%v%%';or"` 254 | // 例子3 field type `query:"field like '%%%v%%';or;order"` 255 | // 例子4 field type `query:"field like '%%%v%%';or;order desc;group"` 256 | // 例子5 field type `query:"field=?;or;order field desc;group field"` 257 | // 例子6 field type `query:"field;or;order field desc;group field"` 258 | // 例子7 field type `query:"order field desc;joins:left join table1 on table1.xx=?"` 259 | // item: 对象 字段的tag=query(默认)加入查询条件 值为0或空字符串不加入查询(要使用0或空字符串使用指针) 260 | // item: 对象定义的方法 结构为func(*gorm.DB) *gorm.DB,方法名任意 加入查询条件(注意方法定义推荐不使用指针) 261 | // item: 对象的字段对应的类型,存在Value方法(必须有一个返回值), 这个字段的值为Value方法返回的值 262 | // item: 对象转换方法UseMap ,返回值 map[string]any(字段名对应的值) 263 | // opts: 重设tag标签名 或 指定加入搜索的方法名(结构为func(*gorm.DB) *gorm.DB),未指定方法名会组合所有结构为func(*gorm.DB) *gorm.DB的方法 264 | // QueryConditions 拼接查询条件 265 | func QueryConditions(item any, opts ...QueryConditionOption) func(db *gorm.DB) *gorm.DB { 266 | opt := applyQueryConditionOptions(opts...) 267 | ty := reflect.TypeOf(item) 268 | val := reflect.ValueOf(item) 269 | ty2 := ty 270 | val2 := val 271 | if ty.Kind() == reflect.Ptr { 272 | ty = ty.Elem() 273 | } 274 | if val.Kind() == reflect.Ptr { 275 | val = val.Elem() 276 | } 277 | if !val.IsValid() { 278 | return func(db *gorm.DB) *gorm.DB { 279 | return db 280 | } 281 | } 282 | var useMap map[string]any 283 | var is bool 284 | valuef := val2.MethodByName("UseMap") 285 | if valuef.IsValid() { 286 | results := valuef.Call([]reflect.Value{}) 287 | if len(results) != 1 { 288 | log.Warn("the UseMap method must have a map[string]any") 289 | } else { 290 | useMap, is = results[0].Interface().(map[string]any) 291 | if !is { 292 | log.Warn("the UseMap method must have a map[string]any") 293 | useMap = make(map[string]any) 294 | } 295 | } 296 | } 297 | return func(db *gorm.DB) *gorm.DB { 298 | for i := range opt.funcs { 299 | db = opt.funcs[i](db) 300 | } 301 | for i := range ty2.NumMethod() { 302 | fcname := ty2.Method(i).Name 303 | if !opt.funcNames.IsFuncName(fcname) { 304 | continue 305 | } 306 | if opt.notFuncNames.IsNotFuncName(fcname) { 307 | continue 308 | } 309 | fnc := val2.MethodByName(fcname) 310 | switch fnc.Type().String() { 311 | case "func(*gorm.DB) *gorm.DB": 312 | results := fnc.Call([]reflect.Value{reflect.ValueOf(db)}) 313 | if len(results) == 0 { 314 | log.Warnf("the return value of the %s function must be either *gorm.DB or func(*gorm.DB) *gorm.DB", fcname) 315 | continue 316 | } 317 | switch results[0].Type().String() { 318 | case "*gorm.DB": 319 | db = results[0].Interface().(*gorm.DB) 320 | default: 321 | log.Warnf("the return value of the %s function must be either *gorm.DB or func(*gorm.DB) *gorm.DB", fcname) 322 | continue 323 | } 324 | } 325 | } 326 | for i := range ty.NumField() { 327 | field := ty.Field(i) 328 | fieldty := field.Type 329 | if fieldty.Kind() == reflect.Ptr { 330 | fieldty = fieldty.Elem() 331 | } 332 | tagVal, is := field.Tag.Lookup(opt.tag) 333 | if !is { 334 | continue 335 | } 336 | if tagVal == "-" { 337 | continue 338 | } 339 | switch fieldty.Kind() { 340 | case reflect.Struct: 341 | val := val.FieldByName(field.Name) 342 | if !val.IsValid() || val.IsZero() { 343 | continue 344 | } 345 | db = QueryConditions( 346 | val.Interface(), 347 | WithQueryConditionOptionTag(opt.tag), 348 | WithQueryConditionOptionFuncName(opt.funcNames.GetFuncName(field.Name)), 349 | WithQueryConditionOptionNotFuncName(opt.notFuncNames.GetFuncName(field.Name)), 350 | WithQueryConditionOptionKeyClause(opt.keyClause), 351 | )(db) 352 | continue 353 | } 354 | tagCon := analyzeQueryConditionsTagVal(tagVal, field.Name, opt.keyClause) 355 | db = dbSpliceSqlOrderGroup(db, tagCon) 356 | val := val.FieldByName(field.Name) 357 | if !val.IsValid() || val.IsZero() { 358 | continue 359 | } 360 | valuef := val.MethodByName("Value") 361 | if valuef.IsValid() { 362 | results := valuef.Call([]reflect.Value{}) 363 | if len(results) != 1 { 364 | log.Warnf("the value method of %s does not return a value", field.Name) 365 | } else { 366 | val = results[0] 367 | } 368 | } else if vl, is := useMap[field.Name]; is { 369 | val = reflect.ValueOf(vl) 370 | } 371 | if !val.IsValid() || val.IsZero() { 372 | continue 373 | } 374 | db = dbSpliceSqlVal(db, tagCon, val.Interface()) 375 | } 376 | return db 377 | } 378 | } 379 | 380 | type tagQueryConditions struct { 381 | Where string 382 | Ifc string 383 | Order string 384 | Group string 385 | Having string 386 | Joins string 387 | FieldName string 388 | } 389 | 390 | func analyzeQueryConditionsTagVal(tagVal string, fieldName string, keyClause map[KeyClauseType]struct{}) (tag tagQueryConditions) { 391 | tag = tagQueryConditions{} 392 | tagVals := strings.Split(tagVal, ";") 393 | tag.FieldName = strcase.ToSnake(fieldName) 394 | for i := range tagVals { 395 | val := tagVals[i] 396 | vall := strings.TrimSpace(strings.ToLower(val)) 397 | if _, is := keyClause[KeyClauseTypeWhere]; is && slices.Contains([]string{"or", "not", "and"}, vall) { 398 | tag.Ifc = vall 399 | } else if _, is := keyClause[KeyClauseTypeOrderBy]; is && strings.HasPrefix(vall, "order") { 400 | tag.Order = strings.Replace(strings.Replace(vall, "order", "", 1), " ", "", -1) 401 | if tag.Order == "desc" { 402 | tag.Order = tag.FieldName + " desc" 403 | } else if tag.Order == "asc" { 404 | tag.Order = tag.FieldName + " asc" 405 | } 406 | if len(tag.Order) == 0 { 407 | tag.Order = tag.FieldName 408 | } 409 | } else if _, is := keyClause[KeyClauseTypeGroup]; is && strings.HasPrefix(vall, "group") { 410 | tag.Group = strings.Replace(vall, "group", "", 1) 411 | if len(tag.Group) == 0 { 412 | tag.Group = tag.FieldName 413 | } 414 | } else if _, is := keyClause[KeyClauseTypeHaving]; is && strings.HasPrefix(vall, "having") { 415 | tag.Having = strings.Replace(vall, "having", "", 1) 416 | if len(tag.Having) == 0 { 417 | tag.Having = tag.FieldName 418 | } 419 | } else if _, is := keyClause[KeyClauseTypeJoins]; is && strings.HasPrefix(vall, "joins") { 420 | tag.Joins = strings.Replace(vall, "joins", "", 1) 421 | if len(tag.Joins) == 0 { 422 | log.Warn("joins statements cannot be empty") 423 | } 424 | } else if _, is := keyClause[KeyClauseTypeWhere]; is { 425 | tag.Where = val 426 | } 427 | } 428 | if len(tag.Where) == 0 && ((len(tag.Having) == 0 && len(tag.Group) == 0 && 429 | len(tag.Joins) == 0 && len(tag.Order) == 0) || len(tag.Ifc) > 0) { 430 | tag.Where = tag.FieldName 431 | } 432 | return 433 | } 434 | 435 | func dbSpliceSqlOrderGroup(db *gorm.DB, tag tagQueryConditions) *gorm.DB { 436 | if len(tag.Order) > 0 { 437 | db = db.Order(tag.Order) 438 | } 439 | if len(tag.Group) > 0 { 440 | db = db.Group(tag.Group) 441 | } 442 | return db 443 | } 444 | 445 | // dbSpliceSql 拼接sql 446 | func dbSpliceSqlVal(db *gorm.DB, tag tagQueryConditions, val any) *gorm.DB { 447 | if len(tag.Joins) > 0 { 448 | if num := strings.Count(tag.Joins, "?"); num > 0 { 449 | db = db.Having(tag.Joins, slices.Repeat([]any{val}, num)...) 450 | } else if nums := regfmt.FindAllStringIndex(tag.Joins, -1); len(nums) > 0 { 451 | db = db.Having(fmt.Sprintf(tag.Joins, slices.Repeat([]any{val}, len(nums))...)) 452 | } else { 453 | db = db.Having(tag.Joins+" = ?", val) 454 | } 455 | } 456 | if len(tag.Where) > 0 { 457 | var wheref func(query any, args ...any) (tx *DB) 458 | if tag.Ifc == "or" { 459 | wheref = db.Or 460 | } else if tag.Ifc == "not" { 461 | wheref = db.Not 462 | } else { 463 | wheref = db.Where 464 | } 465 | if num := strings.Count(tag.Where, "?"); num > 0 { 466 | db = wheref(tag.Where, slices.Repeat([]any{val}, num)...) 467 | } else if nums := regfmt.FindAllStringIndex(tag.Where, -1); len(nums) > 0 { 468 | db = wheref(fmt.Sprintf(tag.Where, slices.Repeat([]any{val}, len(nums))...)) 469 | } else { 470 | db = wheref(tag.Where+" = ?", val) 471 | } 472 | } 473 | 474 | if len(tag.Having) > 0 { 475 | if num := strings.Count(tag.Having, "?"); num > 0 { 476 | db = db.Having(tag.Having, slices.Repeat([]any{val}, num)...) 477 | } else if nums := regfmt.FindAllStringIndex(tag.Having, -1); len(nums) > 0 { 478 | db = db.Having(fmt.Sprintf(tag.Having, slices.Repeat([]any{val}, len(nums))...)) 479 | } else { 480 | db = db.Having(tag.Having+" = ?", val) 481 | } 482 | } 483 | return db 484 | } 485 | -------------------------------------------------------------------------------- /db/redisx/redis.go: -------------------------------------------------------------------------------- 1 | package redisx 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "time" 7 | 8 | "github.com/redis/go-redis/v9" 9 | "github.com/wjoj/tool/v2/log" 10 | "github.com/wjoj/tool/v2/utils" 11 | ) 12 | 13 | type Config struct { 14 | Addrs []string `json:"addrs" yaml:"addrs"` 15 | IsCluster bool `json:"isCluster" yaml:"isCluster"` 16 | Username string `json:"username" yaml:"username"` 17 | Password string `json:"password" yaml:"password"` 18 | ReadTimeout time.Duration `yaml:"readTimeout"` 19 | WriteTimeout time.Duration `yaml:"writeTimeout"` 20 | PoolSize int `json:"poolSize" yaml:"poolSize"` 21 | MinIdleConns int `json:"minIdleConns" yaml:"minIdleConns"` 22 | MaxConnAge time.Duration `json:"maxConnAge" yaml:"maxConnAge"` 23 | MaxIdleConns int `yaml:"maxIdleConns"` //最大空闲连接数。默认0 24 | MaxActiveConns int `yaml:"maxActiveConns"` // 25 | PoolTimeout time.Duration `json:"poolTimeout" yaml:"poolTimeout"` 26 | IdleTimeout time.Duration `json:"idleTimeout" yaml:"idleTimeout"` 27 | ConnMaxIdleTime time.Duration `yaml:"connMaxIdleTime"` 28 | ConnMaxLifetime time.Duration `yaml:"connMaxLifetime"` 29 | } 30 | 31 | type ClientInf interface { 32 | redis.Cmdable 33 | Close() error 34 | } 35 | 36 | type Clientx struct { 37 | ClientInf 38 | cfg *Config 39 | } 40 | 41 | func New(cfg *Config) (*Clientx, error) { 42 | if len(cfg.Addrs) == 0 { 43 | return nil, fmt.Errorf("redis adds can't be empty") 44 | } 45 | if cfg.ReadTimeout > 0 { 46 | cfg.ReadTimeout *= time.Second 47 | } 48 | if cfg.WriteTimeout > 0 { 49 | cfg.WriteTimeout *= time.Second 50 | } 51 | var cli ClientInf 52 | if cfg.IsCluster { 53 | cli = redis.NewClusterClient(&redis.ClusterOptions{ 54 | Addrs: cfg.Addrs, 55 | Password: cfg.Password, 56 | ReadTimeout: cfg.ReadTimeout, 57 | WriteTimeout: cfg.WriteTimeout, 58 | PoolSize: cfg.PoolSize, 59 | PoolTimeout: cfg.PoolTimeout * time.Second, 60 | MinIdleConns: cfg.MinIdleConns, 61 | MaxIdleConns: cfg.MaxIdleConns, 62 | MaxActiveConns: cfg.MaxActiveConns, 63 | ConnMaxIdleTime: cfg.ConnMaxIdleTime * time.Second, 64 | ConnMaxLifetime: cfg.ConnMaxIdleTime * time.Second, 65 | }) 66 | } else { 67 | cli = redis.NewClient(&redis.Options{ 68 | Addr: cfg.Addrs[0], 69 | Password: cfg.Password, 70 | ReadTimeout: cfg.ReadTimeout, 71 | WriteTimeout: cfg.WriteTimeout, 72 | PoolSize: cfg.PoolSize, 73 | PoolTimeout: cfg.PoolTimeout * time.Second, 74 | MinIdleConns: cfg.MinIdleConns, 75 | MaxIdleConns: cfg.MaxIdleConns, 76 | MaxActiveConns: cfg.MaxActiveConns, 77 | ConnMaxIdleTime: cfg.ConnMaxIdleTime * time.Second, 78 | ConnMaxLifetime: cfg.ConnMaxIdleTime * time.Second, 79 | }) 80 | } 81 | if _, err := cli.Ping(context.Background()).Result(); err != nil { 82 | return nil, err 83 | } 84 | return &Clientx{ 85 | ClientInf: cli, 86 | cfg: cfg, 87 | }, nil 88 | } 89 | 90 | var rd *Clientx 91 | var rdMap map[string]*Clientx 92 | var defaultKey = utils.DefaultKey.DefaultKey 93 | 94 | func Init(cfgs map[string]Config, options ...Option) error { 95 | log.Info("init redis") 96 | opt := applyGenGormOptions(options...) 97 | defaultKey = opt.defKey.DefaultKey 98 | rdMap = make(map[string]*Clientx) 99 | if len(opt.defKey.Keys) != 0 { 100 | opt.defKey.Keys = append(opt.defKey.Keys, opt.defKey.DefaultKey) 101 | for _, key := range opt.defKey.Keys { 102 | _, is := rdMap[key] 103 | if is { 104 | continue 105 | } 106 | cfg, is := cfgs[key] 107 | if !is { 108 | log.Errorf("init redis client %s not found", key) 109 | return fmt.Errorf("redis client %s not found", key) 110 | } 111 | cli, err := New(&cfg) 112 | if err != nil { 113 | log.Errorf("init redis client %s init error: %v", key, err) 114 | return err 115 | } 116 | rdMap[key] = cli 117 | if key == defaultKey { 118 | rd = cli 119 | } 120 | } 121 | log.Info("init redis success") 122 | return nil 123 | } 124 | for name, cfg := range cfgs { 125 | cli, err := New(&cfg) 126 | if err != nil { 127 | log.Errorf("init redis client %s init error: %v", name, err) 128 | return err 129 | } 130 | rdMap[name] = cli 131 | if name == defaultKey { 132 | rd = cli 133 | } 134 | } 135 | log.Info("init redis success") 136 | return nil 137 | } 138 | 139 | func InitGlobal(cfg *Config) error { 140 | var err error 141 | rd, err = New(cfg) 142 | if err != nil { 143 | return err 144 | } 145 | return nil 146 | } 147 | 148 | func GetClient(name ...string) *Clientx { 149 | if len(name) == 0 { 150 | cli, is := rdMap[defaultKey] 151 | if !is { 152 | panic(fmt.Errorf("redis client %s not found", utils.DefaultKey.DefaultKey)) 153 | } 154 | return cli 155 | } 156 | cli, is := rdMap[name[0]] 157 | if !is { 158 | panic(fmt.Errorf("redis client %s not found", name[0])) 159 | } 160 | return cli 161 | } 162 | 163 | // Client 重新一遍所有方法 164 | func Client() *Clientx { 165 | return rd 166 | } 167 | 168 | func GetConfig() Config { 169 | return *rd.cfg 170 | } 171 | 172 | func Close() error { 173 | return rd.Close() 174 | } 175 | 176 | func CloseAll() error { 177 | for _, cli := range rdMap { 178 | cli.Close() 179 | } 180 | return nil 181 | } 182 | 183 | // Key 相关操作 184 | func Del(ctx context.Context, keys ...string) *redis.IntCmd { 185 | return rd.Del(ctx, keys...) 186 | } 187 | 188 | func Exists(ctx context.Context, keys ...string) *redis.IntCmd { 189 | return rd.Exists(ctx, keys...) 190 | } 191 | 192 | func Expire(ctx context.Context, key string, expiration time.Duration) *redis.BoolCmd { 193 | return rd.Expire(ctx, key, expiration) 194 | } 195 | 196 | func ExpireAt(ctx context.Context, key string, tm time.Time) *redis.BoolCmd { 197 | return rd.ExpireAt(ctx, key, tm) 198 | } 199 | 200 | func Keys(ctx context.Context, pattern string) *redis.StringSliceCmd { 201 | return rd.Keys(ctx, pattern) 202 | } 203 | 204 | func Persist(ctx context.Context, key string) *redis.BoolCmd { 205 | return rd.Persist(ctx, key) 206 | } 207 | 208 | func PExpire(ctx context.Context, key string, expiration time.Duration) *redis.BoolCmd { 209 | return rd.PExpire(ctx, key, expiration) 210 | } 211 | 212 | func PExpireAt(ctx context.Context, key string, tm time.Time) *redis.BoolCmd { 213 | return rd.PExpireAt(ctx, key, tm) 214 | } 215 | 216 | func PTTL(ctx context.Context, key string) *redis.DurationCmd { 217 | return rd.PTTL(ctx, key) 218 | } 219 | 220 | func Rename(ctx context.Context, key, newkey string) *redis.StatusCmd { 221 | return rd.Rename(ctx, key, newkey) 222 | } 223 | 224 | func RenameNX(ctx context.Context, key, newkey string) *redis.BoolCmd { 225 | return rd.RenameNX(ctx, key, newkey) 226 | } 227 | 228 | func TTL(ctx context.Context, key string) *redis.DurationCmd { 229 | return rd.TTL(ctx, key) 230 | } 231 | 232 | func Type(ctx context.Context, key string) *redis.StatusCmd { 233 | return rd.Type(ctx, key) 234 | } 235 | 236 | // String 相关操作 237 | func Get(ctx context.Context, key string) *redis.StringCmd { 238 | return rd.Get(ctx, key) 239 | } 240 | 241 | func GetRange(ctx context.Context, key string, start, end int64) *redis.StringCmd { 242 | return rd.GetRange(ctx, key, start, end) 243 | } 244 | 245 | func GetSet(ctx context.Context, key string, value interface{}) *redis.StringCmd { 246 | return rd.GetSet(ctx, key, value) 247 | } 248 | 249 | func Incr(ctx context.Context, key string) *redis.IntCmd { 250 | return rd.Incr(ctx, key) 251 | } 252 | 253 | func IncrBy(ctx context.Context, key string, value int64) *redis.IntCmd { 254 | return rd.IncrBy(ctx, key, value) 255 | } 256 | 257 | func IncrByFloat(ctx context.Context, key string, value float64) *redis.FloatCmd { 258 | return rd.IncrByFloat(ctx, key, value) 259 | } 260 | 261 | func MGet(ctx context.Context, keys ...string) *redis.SliceCmd { 262 | return rd.MGet(ctx, keys...) 263 | } 264 | 265 | func MSet(ctx context.Context, values ...interface{}) *redis.StatusCmd { 266 | return rd.MSet(ctx, values...) 267 | } 268 | 269 | func MSetNX(ctx context.Context, values ...interface{}) *redis.BoolCmd { 270 | return rd.MSetNX(ctx, values...) 271 | } 272 | 273 | func Set(ctx context.Context, key string, value interface{}, expiration time.Duration) *redis.StatusCmd { 274 | return rd.Set(ctx, key, value, expiration) 275 | } 276 | 277 | func SetEx(ctx context.Context, key string, value interface{}, expiration time.Duration) *redis.StatusCmd { 278 | return rd.SetEx(ctx, key, value, expiration) 279 | } 280 | 281 | // List 相关操作 282 | func LIndex(ctx context.Context, key string, index int64) *redis.StringCmd { 283 | return rd.LIndex(ctx, key, index) 284 | } 285 | 286 | func LInsert(ctx context.Context, key, op string, pivot, value interface{}) *redis.IntCmd { 287 | return rd.LInsert(ctx, key, op, pivot, value) 288 | } 289 | 290 | func LInsertBefore(ctx context.Context, key string, pivot, value interface{}) *redis.IntCmd { 291 | return rd.LInsertBefore(ctx, key, pivot, value) 292 | } 293 | 294 | func LInsertAfter(ctx context.Context, key string, pivot, value interface{}) *redis.IntCmd { 295 | return rd.LInsertAfter(ctx, key, pivot, value) 296 | } 297 | 298 | func LLen(ctx context.Context, key string) *redis.IntCmd { 299 | return rd.LLen(ctx, key) 300 | } 301 | 302 | func LPop(ctx context.Context, key string) *redis.StringCmd { 303 | return rd.LPop(ctx, key) 304 | } 305 | 306 | func LPopCount(ctx context.Context, key string, count int) *redis.StringSliceCmd { 307 | return rd.LPopCount(ctx, key, count) 308 | } 309 | 310 | func LPos(ctx context.Context, key string, value string, args redis.LPosArgs) *redis.IntCmd { 311 | return rd.LPos(ctx, key, value, args) 312 | } 313 | 314 | func LPosCount(ctx context.Context, key string, value string, count int64, args redis.LPosArgs) *redis.IntSliceCmd { 315 | return rd.LPosCount(ctx, key, value, count, args) 316 | } 317 | 318 | func LPush(ctx context.Context, key string, values ...interface{}) *redis.IntCmd { 319 | return rd.LPush(ctx, key, values...) 320 | } 321 | 322 | func LPushX(ctx context.Context, key string, values ...interface{}) *redis.IntCmd { 323 | return rd.LPushX(ctx, key, values...) 324 | } 325 | 326 | func LRange(ctx context.Context, key string, start, stop int64) *redis.StringSliceCmd { 327 | return rd.LRange(ctx, key, start, stop) 328 | } 329 | 330 | func LRem(ctx context.Context, key string, count int64, value interface{}) *redis.IntCmd { 331 | return rd.LRem(ctx, key, count, value) 332 | } 333 | 334 | func LSet(ctx context.Context, key string, index int64, value interface{}) *redis.StatusCmd { 335 | return rd.LSet(ctx, key, index, value) 336 | } 337 | 338 | func LTrim(ctx context.Context, key string, start, stop int64) *redis.StatusCmd { 339 | return rd.LTrim(ctx, key, start, stop) 340 | } 341 | 342 | func RPop(ctx context.Context, key string) *redis.StringCmd { 343 | return rd.RPop(ctx, key) 344 | } 345 | 346 | func RPopCount(ctx context.Context, key string, count int) *redis.StringSliceCmd { 347 | return rd.RPopCount(ctx, key, count) 348 | } 349 | 350 | func RPopLPush(ctx context.Context, source, destination string) *redis.StringCmd { 351 | return rd.RPopLPush(ctx, source, destination) 352 | } 353 | 354 | func RPush(ctx context.Context, key string, values ...interface{}) *redis.IntCmd { 355 | return rd.RPush(ctx, key, values...) 356 | } 357 | 358 | func RPushX(ctx context.Context, key string, values ...interface{}) *redis.IntCmd { 359 | return rd.RPushX(ctx, key, values...) 360 | } 361 | 362 | // Set 相关操作 363 | func SAdd(ctx context.Context, key string, members ...interface{}) *redis.IntCmd { 364 | return rd.SAdd(ctx, key, members...) 365 | } 366 | 367 | func SCard(ctx context.Context, key string) *redis.IntCmd { 368 | return rd.SCard(ctx, key) 369 | } 370 | 371 | func SDiff(ctx context.Context, keys ...string) *redis.StringSliceCmd { 372 | return rd.SDiff(ctx, keys...) 373 | } 374 | 375 | func SDiffStore(ctx context.Context, destination string, keys ...string) *redis.IntCmd { 376 | return rd.SDiffStore(ctx, destination, keys...) 377 | } 378 | 379 | func SInter(ctx context.Context, keys ...string) *redis.StringSliceCmd { 380 | return rd.SInter(ctx, keys...) 381 | } 382 | 383 | func SInterStore(ctx context.Context, destination string, keys ...string) *redis.IntCmd { 384 | return rd.SInterStore(ctx, destination, keys...) 385 | } 386 | 387 | func SIsMember(ctx context.Context, key string, member interface{}) *redis.BoolCmd { 388 | return rd.SIsMember(ctx, key, member) 389 | } 390 | 391 | func SMembers(ctx context.Context, key string) *redis.StringSliceCmd { 392 | return rd.SMembers(ctx, key) 393 | } 394 | 395 | func SMembersMap(ctx context.Context, key string) *redis.StringStructMapCmd { 396 | return rd.SMembersMap(ctx, key) 397 | } 398 | 399 | func SMove(ctx context.Context, source, destination string, member interface{}) *redis.BoolCmd { 400 | return rd.SMove(ctx, source, destination, member) 401 | } 402 | 403 | func SPop(ctx context.Context, key string) *redis.StringCmd { 404 | return rd.SPop(ctx, key) 405 | } 406 | 407 | func SPopN(ctx context.Context, key string, count int64) *redis.StringSliceCmd { 408 | return rd.SPopN(ctx, key, count) 409 | } 410 | 411 | func SRandMember(ctx context.Context, key string) *redis.StringCmd { 412 | return rd.SRandMember(ctx, key) 413 | } 414 | 415 | func SRandMemberN(ctx context.Context, key string, count int64) *redis.StringSliceCmd { 416 | return rd.SRandMemberN(ctx, key, count) 417 | } 418 | 419 | func SRem(ctx context.Context, key string, members ...interface{}) *redis.IntCmd { 420 | return rd.SRem(ctx, key, members...) 421 | } 422 | 423 | func SUnion(ctx context.Context, keys ...string) *redis.StringSliceCmd { 424 | return rd.SUnion(ctx, keys...) 425 | } 426 | 427 | func SUnionStore(ctx context.Context, destination string, keys ...string) *redis.IntCmd { 428 | return rd.SUnionStore(ctx, destination, keys...) 429 | } 430 | func SetNX(ctx context.Context, key string, value interface{}, expiration time.Duration) *redis.BoolCmd { 431 | return rd.SetNX(ctx, key, value, expiration) 432 | } 433 | 434 | func SetRange(ctx context.Context, key string, offset int64, value string) *redis.IntCmd { 435 | return rd.SetRange(ctx, key, offset, value) 436 | } 437 | 438 | func StrLen(ctx context.Context, key string) *redis.IntCmd { 439 | return rd.StrLen(ctx, key) 440 | } 441 | 442 | // SortedSet 相关操作 443 | func ZAdd(ctx context.Context, key string, members ...redis.Z) *redis.IntCmd { 444 | return rd.ZAdd(ctx, key, members...) 445 | } 446 | 447 | func ZAddNX(ctx context.Context, key string, members ...redis.Z) *redis.IntCmd { 448 | return rd.ZAddNX(ctx, key, members...) 449 | } 450 | 451 | func ZAddXX(ctx context.Context, key string, members ...redis.Z) *redis.IntCmd { 452 | return rd.ZAddXX(ctx, key, members...) 453 | } 454 | 455 | func ZCard(ctx context.Context, key string) *redis.IntCmd { 456 | return rd.ZCard(ctx, key) 457 | } 458 | 459 | func ZCount(ctx context.Context, key, min, max string) *redis.IntCmd { 460 | return rd.ZCount(ctx, key, min, max) 461 | } 462 | 463 | func ZIncrBy(ctx context.Context, key string, increment float64, member string) *redis.FloatCmd { 464 | return rd.ZIncrBy(ctx, key, increment, member) 465 | } 466 | 467 | func ZInterStore(ctx context.Context, destination string, store *redis.ZStore) *redis.IntCmd { 468 | return rd.ZInterStore(ctx, destination, store) 469 | } 470 | 471 | func ZLexCount(ctx context.Context, key, min, max string) *redis.IntCmd { 472 | return rd.ZLexCount(ctx, key, min, max) 473 | } 474 | 475 | func ZPopMax(ctx context.Context, key string, count ...int64) *redis.ZSliceCmd { 476 | return rd.ZPopMax(ctx, key, count...) 477 | } 478 | 479 | func ZPopMin(ctx context.Context, key string, count ...int64) *redis.ZSliceCmd { 480 | return rd.ZPopMin(ctx, key, count...) 481 | } 482 | 483 | func ZRange(ctx context.Context, key string, start, stop int64) *redis.StringSliceCmd { 484 | return rd.ZRange(ctx, key, start, stop) 485 | } 486 | 487 | func ZRangeByLex(ctx context.Context, key string, opt *redis.ZRangeBy) *redis.StringSliceCmd { 488 | return rd.ZRangeByLex(ctx, key, opt) 489 | } 490 | 491 | func ZRangeByScore(ctx context.Context, key string, opt *redis.ZRangeBy) *redis.StringSliceCmd { 492 | return rd.ZRangeByScore(ctx, key, opt) 493 | } 494 | 495 | func ZRank(ctx context.Context, key, member string) *redis.IntCmd { 496 | return rd.ZRank(ctx, key, member) 497 | } 498 | 499 | func ZRem(ctx context.Context, key string, members ...interface{}) *redis.IntCmd { 500 | return rd.ZRem(ctx, key, members...) 501 | } 502 | 503 | func ZRemRangeByLex(ctx context.Context, key, min, max string) *redis.IntCmd { 504 | return rd.ZRemRangeByLex(ctx, key, min, max) 505 | } 506 | 507 | func ZRemRangeByRank(ctx context.Context, key string, start, stop int64) *redis.IntCmd { 508 | return rd.ZRemRangeByRank(ctx, key, start, stop) 509 | } 510 | 511 | func ZRemRangeByScore(ctx context.Context, key, min, max string) *redis.IntCmd { 512 | return rd.ZRemRangeByScore(ctx, key, min, max) 513 | } 514 | 515 | func ZRevRange(ctx context.Context, key string, start, stop int64) *redis.StringSliceCmd { 516 | return rd.ZRevRange(ctx, key, start, stop) 517 | } 518 | 519 | func ZRevRangeByLex(ctx context.Context, key string, opt *redis.ZRangeBy) *redis.StringSliceCmd { 520 | return rd.ZRevRangeByLex(ctx, key, opt) 521 | } 522 | 523 | func ZRevRangeByScore(ctx context.Context, key string, opt *redis.ZRangeBy) *redis.StringSliceCmd { 524 | return rd.ZRevRangeByScore(ctx, key, opt) 525 | } 526 | 527 | func ZRevRank(ctx context.Context, key, member string) *redis.IntCmd { 528 | return rd.ZRevRank(ctx, key, member) 529 | } 530 | 531 | func ZScore(ctx context.Context, key, member string) *redis.FloatCmd { 532 | return rd.ZScore(ctx, key, member) 533 | } 534 | 535 | func ZUnionStore(ctx context.Context, dest string, store *redis.ZStore) *redis.IntCmd { 536 | return rd.ZUnionStore(ctx, dest, store) 537 | } 538 | 539 | // Stream 相关操作 540 | func XAdd(ctx context.Context, a *redis.XAddArgs) *redis.StringCmd { 541 | return rd.XAdd(ctx, a) 542 | } 543 | 544 | func XDel(ctx context.Context, stream string, ids ...string) *redis.IntCmd { 545 | return rd.XDel(ctx, stream, ids...) 546 | } 547 | 548 | func XLen(ctx context.Context, stream string) *redis.IntCmd { 549 | return rd.XLen(ctx, stream) 550 | } 551 | 552 | func XRange(ctx context.Context, stream, start, stop string) *redis.XMessageSliceCmd { 553 | return rd.XRange(ctx, stream, start, stop) 554 | } 555 | 556 | func XRangeN(ctx context.Context, stream, start, stop string, count int64) *redis.XMessageSliceCmd { 557 | return rd.XRangeN(ctx, stream, start, stop, count) 558 | } 559 | 560 | func XRevRange(ctx context.Context, stream, start, stop string) *redis.XMessageSliceCmd { 561 | return rd.XRevRange(ctx, stream, start, stop) 562 | } 563 | 564 | func XRevRangeN(ctx context.Context, stream, start, stop string, count int64) *redis.XMessageSliceCmd { 565 | return rd.XRevRangeN(ctx, stream, start, stop, count) 566 | } 567 | 568 | func XRead(ctx context.Context, a *redis.XReadArgs) *redis.XStreamSliceCmd { 569 | return rd.XRead(ctx, a) 570 | } 571 | 572 | func XReadStreams(ctx context.Context, streams ...string) *redis.XStreamSliceCmd { 573 | return rd.XReadStreams(ctx, streams...) 574 | } 575 | 576 | func XGroupCreate(ctx context.Context, stream, group, start string) *redis.StatusCmd { 577 | return rd.XGroupCreate(ctx, stream, group, start) 578 | } 579 | 580 | func XGroupCreateMkStream(ctx context.Context, stream, group, start string) *redis.StatusCmd { 581 | return rd.XGroupCreateMkStream(ctx, stream, group, start) 582 | } 583 | 584 | func XGroupSetID(ctx context.Context, stream, group, start string) *redis.StatusCmd { 585 | return rd.XGroupSetID(ctx, stream, group, start) 586 | } 587 | 588 | func XGroupDestroy(ctx context.Context, stream, group string) *redis.IntCmd { 589 | return rd.XGroupDestroy(ctx, stream, group) 590 | } 591 | 592 | func XGroupCreateConsumer(ctx context.Context, stream, group, consumer string) *redis.IntCmd { 593 | return rd.XGroupCreateConsumer(ctx, stream, group, consumer) 594 | } 595 | 596 | func XGroupDelConsumer(ctx context.Context, stream, group, consumer string) *redis.IntCmd { 597 | return rd.XGroupDelConsumer(ctx, stream, group, consumer) 598 | } 599 | 600 | func XReadGroup(ctx context.Context, a *redis.XReadGroupArgs) *redis.XStreamSliceCmd { 601 | return rd.XReadGroup(ctx, a) 602 | } 603 | 604 | func XAck(ctx context.Context, stream, group string, ids ...string) *redis.IntCmd { 605 | return rd.XAck(ctx, stream, group, ids...) 606 | } 607 | 608 | func XPending(ctx context.Context, stream, group string) *redis.XPendingCmd { 609 | return rd.XPending(ctx, stream, group) 610 | } 611 | 612 | func XPendingExt(ctx context.Context, a *redis.XPendingExtArgs) *redis.XPendingExtCmd { 613 | return rd.XPendingExt(ctx, a) 614 | } 615 | 616 | func XClaim(ctx context.Context, a *redis.XClaimArgs) *redis.XMessageSliceCmd { 617 | return rd.XClaim(ctx, a) 618 | } 619 | 620 | func XClaimJustID(ctx context.Context, a *redis.XClaimArgs) *redis.StringSliceCmd { 621 | return rd.XClaimJustID(ctx, a) 622 | } 623 | 624 | func XAutoClaim(ctx context.Context, a *redis.XAutoClaimArgs) *redis.XAutoClaimCmd { 625 | return rd.XAutoClaim(ctx, a) 626 | } 627 | 628 | func XAutoClaimJustID(ctx context.Context, a *redis.XAutoClaimArgs) *redis.XAutoClaimJustIDCmd { 629 | return rd.XAutoClaimJustID(ctx, a) 630 | } 631 | 632 | func XTrimMaxLen(ctx context.Context, key string, maxLen int64) *redis.IntCmd { 633 | return rd.XTrimMaxLen(ctx, key, maxLen) 634 | } 635 | 636 | func XTrimMaxLenApprox(ctx context.Context, key string, maxLen, limit int64) *redis.IntCmd { 637 | return rd.XTrimMaxLenApprox(ctx, key, maxLen, limit) 638 | } 639 | 640 | func XTrimMinID(ctx context.Context, key string, minID string) *redis.IntCmd { 641 | return rd.XTrimMinID(ctx, key, minID) 642 | } 643 | 644 | func XTrimMinIDApprox(ctx context.Context, key string, minID string, limit int64) *redis.IntCmd { 645 | return rd.XTrimMinIDApprox(ctx, key, minID, limit) 646 | } 647 | 648 | func XInfoGroups(ctx context.Context, key string) *redis.XInfoGroupsCmd { 649 | return rd.XInfoGroups(ctx, key) 650 | } 651 | 652 | func XInfoStream(ctx context.Context, key string) *redis.XInfoStreamCmd { 653 | return rd.XInfoStream(ctx, key) 654 | } 655 | 656 | func XInfoStreamFull(ctx context.Context, key string, count int) *redis.XInfoStreamFullCmd { 657 | return rd.XInfoStreamFull(ctx, key, count) 658 | } 659 | 660 | func XInfoConsumers(ctx context.Context, key, group string) *redis.XInfoConsumersCmd { 661 | return rd.XInfoConsumers(ctx, key, group) 662 | } 663 | 664 | // 脚本相关操作 665 | func Eval(ctx context.Context, script string, keys []string, args ...interface{}) *redis.Cmd { 666 | return rd.Eval(ctx, script, keys, args...) 667 | } 668 | 669 | func EvalSha(ctx context.Context, sha1 string, keys []string, args ...interface{}) *redis.Cmd { 670 | return rd.EvalSha(ctx, sha1, keys, args...) 671 | } 672 | 673 | func ScriptExists(ctx context.Context, scripts ...string) *redis.BoolSliceCmd { 674 | return rd.ScriptExists(ctx, scripts...) 675 | } 676 | 677 | func ScriptLoad(ctx context.Context, script string) *redis.StringCmd { 678 | return rd.ScriptLoad(ctx, script) 679 | } 680 | 681 | func ScriptFlush(ctx context.Context) *redis.StatusCmd { 682 | return rd.ScriptFlush(ctx) 683 | } 684 | 685 | func ScriptKill(ctx context.Context) *redis.StatusCmd { 686 | return rd.ScriptKill(ctx) 687 | } 688 | 689 | // 发布订阅相关操作 690 | func Publish(ctx context.Context, channel string, message interface{}) *redis.IntCmd { 691 | return rd.Publish(ctx, channel, message) 692 | } 693 | func SPublish(ctx context.Context, channel string, message interface{}) *redis.IntCmd { 694 | return rd.SPublish(ctx, channel, message) 695 | } 696 | 697 | func PubSubChannels(ctx context.Context, pattern string) *redis.StringSliceCmd { 698 | return rd.PubSubChannels(ctx, pattern) 699 | } 700 | 701 | func PubSubNumSub(ctx context.Context, channels ...string) *redis.MapStringIntCmd { 702 | return rd.PubSubNumSub(ctx, channels...) 703 | } 704 | 705 | func PubSubNumPat(ctx context.Context) *redis.IntCmd { 706 | return rd.PubSubNumPat(ctx) 707 | } 708 | 709 | // 连接管理相关操作 710 | func Ping(ctx context.Context) *redis.StatusCmd { 711 | return rd.Ping(ctx) 712 | } 713 | 714 | func ClientID(ctx context.Context) *redis.IntCmd { 715 | return rd.ClientID(ctx) 716 | } 717 | 718 | func ClientInfo(ctx context.Context) *redis.ClientInfoCmd { 719 | return rd.ClientInfo(ctx) 720 | } 721 | 722 | func ClientList(ctx context.Context) *redis.StringCmd { 723 | return rd.ClientList(ctx) 724 | } 725 | 726 | func ClientKill(ctx context.Context, ipPort string) *redis.StatusCmd { 727 | return rd.ClientKill(ctx, ipPort) 728 | } 729 | 730 | func ClientKillByFilter(ctx context.Context, keys ...string) *redis.IntCmd { 731 | return rd.ClientKillByFilter(ctx, keys...) 732 | } 733 | 734 | func ClientPause(ctx context.Context, dur time.Duration) *redis.BoolCmd { 735 | return rd.ClientPause(ctx, dur) 736 | } 737 | 738 | func ClientUnblock(ctx context.Context, id int64) *redis.IntCmd { 739 | return rd.ClientUnblock(ctx, id) 740 | } 741 | 742 | func ClientUnblockWithError(ctx context.Context, id int64) *redis.IntCmd { 743 | return rd.ClientUnblockWithError(ctx, id) 744 | } 745 | 746 | // 服务器相关操作 747 | func Time(ctx context.Context) *redis.TimeCmd { 748 | return rd.Time(ctx) 749 | } 750 | 751 | func DBSize(ctx context.Context) *redis.IntCmd { 752 | return rd.DBSize(ctx) 753 | } 754 | 755 | func FlushAll(ctx context.Context) *redis.StatusCmd { 756 | return rd.FlushAll(ctx) 757 | } 758 | 759 | func FlushAllAsync(ctx context.Context) *redis.StatusCmd { 760 | return rd.FlushAllAsync(ctx) 761 | } 762 | 763 | func FlushDB(ctx context.Context) *redis.StatusCmd { 764 | return rd.FlushDB(ctx) 765 | } 766 | 767 | func FlushDBAsync(ctx context.Context) *redis.StatusCmd { 768 | return rd.FlushDBAsync(ctx) 769 | } 770 | 771 | func Info(ctx context.Context, section ...string) *redis.StringCmd { 772 | return rd.Info(ctx, section...) 773 | } 774 | 775 | func Save(ctx context.Context) *redis.StatusCmd { 776 | return rd.Save(ctx) 777 | } 778 | 779 | func Shutdown(ctx context.Context) *redis.StatusCmd { 780 | return rd.Shutdown(ctx) 781 | } 782 | 783 | func ShutdownSave(ctx context.Context) *redis.StatusCmd { 784 | return rd.ShutdownSave(ctx) 785 | } 786 | 787 | func ShutdownNoSave(ctx context.Context) *redis.StatusCmd { 788 | return rd.ShutdownNoSave(ctx) 789 | } 790 | 791 | func SlaveOf(ctx context.Context, host, port string) *redis.StatusCmd { 792 | return rd.SlaveOf(ctx, host, port) 793 | } 794 | 795 | func SlowLogGet(ctx context.Context, num int64) *redis.SlowLogCmd { 796 | return rd.SlowLogGet(ctx, num) 797 | } 798 | 799 | func DebugObject(ctx context.Context, key string) *redis.StringCmd { 800 | return rd.DebugObject(ctx, key) 801 | } 802 | 803 | func MemoryUsage(ctx context.Context, key string, samples ...int) *redis.IntCmd { 804 | return rd.MemoryUsage(ctx, key, samples...) 805 | } 806 | 807 | // 位图(Bitmap)相关操作 808 | func BitCount(ctx context.Context, key string, bitCount *redis.BitCount) *redis.IntCmd { 809 | return rd.BitCount(ctx, key, bitCount) 810 | } 811 | 812 | func BitOpAnd(ctx context.Context, destKey string, keys ...string) *redis.IntCmd { 813 | return rd.BitOpAnd(ctx, destKey, keys...) 814 | } 815 | 816 | func BitOpOr(ctx context.Context, destKey string, keys ...string) *redis.IntCmd { 817 | return rd.BitOpOr(ctx, destKey, keys...) 818 | } 819 | 820 | func BitOpXor(ctx context.Context, destKey string, keys ...string) *redis.IntCmd { 821 | return rd.BitOpXor(ctx, destKey, keys...) 822 | } 823 | 824 | func BitOpNot(ctx context.Context, destKey string, key string) *redis.IntCmd { 825 | return rd.BitOpNot(ctx, destKey, key) 826 | } 827 | 828 | func BitPos(ctx context.Context, key string, bit int64, pos ...int64) *redis.IntCmd { 829 | return rd.BitPos(ctx, key, bit, pos...) 830 | } 831 | 832 | func BitField(ctx context.Context, key string, args ...interface{}) *redis.IntSliceCmd { 833 | return rd.BitField(ctx, key, args...) 834 | } 835 | 836 | func GetBit(ctx context.Context, key string, offset int64) *redis.IntCmd { 837 | return rd.GetBit(ctx, key, offset) 838 | } 839 | 840 | func SetBit(ctx context.Context, key string, offset int64, value int) *redis.IntCmd { 841 | return rd.SetBit(ctx, key, offset, value) 842 | } 843 | 844 | func HDel(ctx context.Context, key string, fields ...string) *redis.IntCmd { 845 | return rd.HDel(ctx, key, fields...) 846 | } 847 | func HExists(ctx context.Context, key, field string) *redis.BoolCmd { 848 | return rd.HExists(ctx, key, field) 849 | } 850 | func HGet(ctx context.Context, key, field string) *redis.StringCmd { 851 | return rd.HGet(ctx, key, field) 852 | } 853 | func HGetAll(ctx context.Context, key string) *redis.MapStringStringCmd { 854 | return rd.HGetAll(ctx, key) 855 | } 856 | 857 | func HIncrBy(ctx context.Context, key, field string, incr int64) *redis.IntCmd { 858 | return rd.HIncrBy(ctx, key, field, incr) 859 | } 860 | func HIncrByFloat(ctx context.Context, key, field string, incr float64) *redis.FloatCmd { 861 | return rd.HIncrByFloat(ctx, key, field, incr) 862 | } 863 | 864 | func HKeys(ctx context.Context, key string) *redis.StringSliceCmd { 865 | return rd.HKeys(ctx, key) 866 | } 867 | func HLen(ctx context.Context, key string) *redis.IntCmd { 868 | return rd.HLen(ctx, key) 869 | } 870 | func HMGet(ctx context.Context, key string, fields ...string) *redis.SliceCmd { 871 | return rd.HMGet(ctx, key, fields...) 872 | } 873 | func HSet(ctx context.Context, key string, values ...interface{}) *redis.IntCmd { 874 | return rd.HSet(ctx, key, values...) 875 | } 876 | 877 | func HSetNX(ctx context.Context, key, field string, value interface{}) *redis.BoolCmd { 878 | return rd.HSetNX(ctx, key, field, value) 879 | } 880 | func HScan(ctx context.Context, key string, cursor uint64, match string, count int64) *redis.ScanCmd { 881 | return rd.HScan(ctx, key, cursor, match, count) 882 | } 883 | func HScanNoValues(ctx context.Context, key string, cursor uint64, match string, count int64) *redis.ScanCmd { 884 | return rd.HScanNoValues(ctx, key, cursor, match, count) 885 | } 886 | func HVals(ctx context.Context, key string) *redis.StringSliceCmd { 887 | return rd.HVals(ctx, key) 888 | } 889 | 890 | func HRandField(ctx context.Context, key string, count int) *redis.StringSliceCmd { 891 | return rd.HRandField(ctx, key, count) 892 | } 893 | func HRandFieldWithValues(ctx context.Context, key string, count int) *redis.KeyValueSliceCmd { 894 | return rd.HRandFieldWithValues(ctx, key, count) 895 | } 896 | func HExpire(ctx context.Context, key string, expiration time.Duration, fields ...string) *redis.IntSliceCmd { 897 | return rd.HExpire(ctx, key, expiration, fields...) 898 | } 899 | func HExpireWithArgs(ctx context.Context, key string, expiration time.Duration, expirationArgs redis.HExpireArgs, fields ...string) *redis.IntSliceCmd { 900 | return rd.HExpireWithArgs(ctx, key, expiration, expirationArgs, fields...) 901 | } 902 | func HPExpire(ctx context.Context, key string, expiration time.Duration, fields ...string) *redis.IntSliceCmd { 903 | return rd.HPExpire(ctx, key, expiration, fields...) 904 | } 905 | func HPExpireWithArgs(ctx context.Context, key string, expiration time.Duration, expirationArgs redis.HExpireArgs, fields ...string) *redis.IntSliceCmd { 906 | return rd.HPExpireWithArgs(ctx, key, expiration, expirationArgs, fields...) 907 | } 908 | func HExpireAt(ctx context.Context, key string, tm time.Time, fields ...string) *redis.IntSliceCmd { 909 | return rd.HExpireAt(ctx, key, tm, fields...) 910 | } 911 | func HExpireAtWithArgs(ctx context.Context, key string, tm time.Time, expirationArgs redis.HExpireArgs, fields ...string) *redis.IntSliceCmd { 912 | return rd.HExpireAtWithArgs(ctx, key, tm, expirationArgs, fields...) 913 | } 914 | func HPExpireAt(ctx context.Context, key string, tm time.Time, fields ...string) *redis.IntSliceCmd { 915 | return rd.HPExpireAt(ctx, key, tm, fields...) 916 | } 917 | func HPExpireAtWithArgs(ctx context.Context, key string, tm time.Time, expirationArgs redis.HExpireArgs, fields ...string) *redis.IntSliceCmd { 918 | return rd.HPExpireAtWithArgs(ctx, key, tm, expirationArgs, fields...) 919 | } 920 | func HPersist(ctx context.Context, key string, fields ...string) *redis.IntSliceCmd { 921 | return rd.HPersist(ctx, key, fields...) 922 | } 923 | func HExpireTime(ctx context.Context, key string, fields ...string) *redis.IntSliceCmd { 924 | return rd.HExpireTime(ctx, key, fields...) 925 | } 926 | func HPExpireTime(ctx context.Context, key string, fields ...string) *redis.IntSliceCmd { 927 | return rd.HPExpireTime(ctx, key, fields...) 928 | } 929 | func HTTL(ctx context.Context, key string, fields ...string) *redis.IntSliceCmd { 930 | return rd.HTTL(ctx, key, fields...) 931 | } 932 | func HPTTL(ctx context.Context, key string, fields ...string) *redis.IntSliceCmd { 933 | return rd.HPTTL(ctx, key, fields...) 934 | } 935 | func PFAdd(ctx context.Context, key string, els ...interface{}) *redis.IntCmd { 936 | return rd.PFAdd(ctx, key, els...) 937 | } 938 | func PFCount(ctx context.Context, keys ...string) *redis.IntCmd { 939 | return rd.PFCount(ctx, keys...) 940 | } 941 | func PFMerge(ctx context.Context, dest string, keys ...string) *redis.StatusCmd { 942 | return rd.PFMerge(ctx, dest, keys...) 943 | } 944 | 945 | func GeoAdd(ctx context.Context, key string, geoLocation ...*redis.GeoLocation) *redis.IntCmd { 946 | return rd.GeoAdd(ctx, key, geoLocation...) 947 | } 948 | func GeoPos(ctx context.Context, key string, members ...string) *redis.GeoPosCmd { 949 | return rd.GeoPos(ctx, key, members...) 950 | } 951 | func GeoRadius(ctx context.Context, key string, longitude, latitude float64, query *redis.GeoRadiusQuery) *redis.GeoLocationCmd { 952 | return rd.GeoRadius(ctx, key, longitude, latitude, query) 953 | } 954 | func GeoRadiusStore(ctx context.Context, key string, longitude, latitude float64, query *redis.GeoRadiusQuery) *redis.IntCmd { 955 | return rd.GeoRadiusStore(ctx, key, longitude, latitude, query) 956 | } 957 | func GeoRadiusByMember(ctx context.Context, key, member string, query *redis.GeoRadiusQuery) *redis.GeoLocationCmd { 958 | return rd.GeoRadiusByMember(ctx, key, member, query) 959 | } 960 | func GeoRadiusByMemberStore(ctx context.Context, key, member string, query *redis.GeoRadiusQuery) *redis.IntCmd { 961 | return rd.GeoRadiusByMemberStore(ctx, key, member, query) 962 | } 963 | func GeoSearch(ctx context.Context, key string, q *redis.GeoSearchQuery) *redis.StringSliceCmd { 964 | return rd.GeoSearch(ctx, key, q) 965 | } 966 | func GeoSearchLocation(ctx context.Context, key string, q *redis.GeoSearchLocationQuery) *redis.GeoSearchLocationCmd { 967 | return rd.GeoSearchLocation(ctx, key, q) 968 | } 969 | func GeoSearchStore(ctx context.Context, key, store string, q *redis.GeoSearchStoreQuery) *redis.IntCmd { 970 | return rd.GeoSearchStore(ctx, key, store, q) 971 | } 972 | func GeoDist(ctx context.Context, key string, member1, member2, unit string) *redis.FloatCmd { 973 | return rd.GeoDist(ctx, key, member1, member2, unit) 974 | } 975 | func GeoHash(ctx context.Context, key string, members ...string) *redis.StringSliceCmd { 976 | return rd.GeoHash(ctx, key, members...) 977 | } 978 | 979 | // 添加连接池统计信息获取 980 | func PoolStats() *redis.PoolStats { 981 | if cluster, ok := rd.ClientInf.(*redis.ClusterClient); ok { 982 | return cluster.PoolStats() 983 | } 984 | if client, ok := rd.ClientInf.(*redis.Client); ok { 985 | return client.PoolStats() 986 | } 987 | return nil 988 | } 989 | 990 | // 添加健康检查 991 | func PingWithTimeout(ctx context.Context, timeout time.Duration) error { 992 | ctx, cancel := context.WithTimeout(ctx, timeout) 993 | defer cancel() 994 | return rd.Ping(ctx).Err() 995 | } 996 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= 2 | filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= 3 | github.com/Azure/azure-sdk-for-go/sdk/azcore v1.0.0/go.mod h1:uGG2W01BaETf0Ozp+QxxKJdMBNRWPdstHG0Fmdwn1/U= 4 | github.com/Azure/azure-sdk-for-go/sdk/azcore v1.1.2/go.mod h1:uGG2W01BaETf0Ozp+QxxKJdMBNRWPdstHG0Fmdwn1/U= 5 | github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.1 h1:lGlwhPtrX6EVml1hO0ivjkUxsSyl4dsiw9qcA1k/3IQ= 6 | github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.1/go.mod h1:RKUqNu35KJYcVG/fqTRqmuXJZYNhYkBrnC/hX7yGbTA= 7 | github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.1.0/go.mod h1:bhXu1AjYL+wutSL/kpSq6s7733q2Rb0yuot9Zgfqa/0= 8 | github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1 h1:sO0/P7g68FrryJzljemN+6GTssUXdANk6aJ7T1ZxnsQ= 9 | github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1/go.mod h1:h8hyGFDsU5HMivxiS2iYFZsgDbU9OnnJ163x5UGVKYo= 10 | github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0/go.mod h1:eWRD7oawr1Mu1sLCawqVc0CUiF43ia3qQMxLscsKQ9w= 11 | github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.1 h1:6oNBlSdi1QqM1PNW7FPA6xOGA5UNsXnkaYZz9vdPGhA= 12 | github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.1/go.mod h1:s4kgfzA0covAXNicZHDMN58jExvcng2mC/DepXiF1EI= 13 | github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.1 h1:MyVTgWR8qd/Jw1Le0NZebGBUCLbtak3bJ3z1OlqZBpw= 14 | github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.1/go.mod h1:GpPjLhVR9dnUoJMyHWSPy71xY9/lcmpzIPZXmF0FCVY= 15 | github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0 h1:D3occbWoio4EBLkbkevetNMAVX197GkzbUMtqjGWn80= 16 | github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0/go.mod h1:bTSOgj05NGRuHHhQwAdPnYr9TOdNmKlZTgGLL6nyAdI= 17 | github.com/AzureAD/microsoft-authentication-library-for-go v0.5.1/go.mod h1:Vt9sXTKwMyGcOxSmLDMnGPgqsUg7m8pe215qMLrDXw4= 18 | github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1 h1:DzHpqpoJVaCgOUdVHxE8QB52S6NiVdDQvGlny1qvPqA= 19 | github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= 20 | github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= 21 | github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= 22 | github.com/ClickHouse/ch-go v0.61.5 h1:zwR8QbYI0tsMiEcze/uIMK+Tz1D3XZXLdNrlaOpeEI4= 23 | github.com/ClickHouse/ch-go v0.61.5/go.mod h1:s1LJW/F/LcFs5HJnuogFMta50kKDO0lf9zzfrbl0RQg= 24 | github.com/ClickHouse/clickhouse-go/v2 v2.30.0 h1:AG4D/hW39qa58+JHQIFOSnxyL46H6h2lrmGGk17dhFo= 25 | github.com/ClickHouse/clickhouse-go/v2 v2.30.0/go.mod h1:i9ZQAojcayW3RsdCb3YR+n+wC2h65eJsZCscZ1Z1wyo= 26 | github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= 27 | github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= 28 | github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= 29 | github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= 30 | github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= 31 | github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= 32 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 33 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 34 | github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= 35 | github.com/bmatcuk/doublestar/v4 v4.8.1 h1:54Bopc5c2cAvhLRAzqOGCYHYyhcDHsFF4wWIR5wKP38= 36 | github.com/bmatcuk/doublestar/v4 v4.8.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= 37 | github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= 38 | github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= 39 | github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= 40 | github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= 41 | github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ= 42 | github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4= 43 | github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= 44 | github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY= 45 | github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= 46 | github.com/casbin/casbin/v2 v2.105.0 h1:dLj5P6pLApBRat9SADGiLxLZjiDPvA1bsPkyV4PGx6I= 47 | github.com/casbin/casbin/v2 v2.105.0/go.mod h1:Ee33aqGrmES+GNL17L0h9X28wXuo829wnNUnS0edAco= 48 | github.com/casbin/gorm-adapter/v3 v3.32.0 h1:Au+IOILBIE9clox5BJhI2nA3p9t7Ep1ePlupdGbGfus= 49 | github.com/casbin/gorm-adapter/v3 v3.32.0/go.mod h1:Zre/H8p17mpv5U3EaWgPoxLILLdXO3gHW5aoQQpUDZI= 50 | github.com/casbin/govaluate v1.3.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A= 51 | github.com/casbin/govaluate v1.7.0 h1:Es2j2K2jv7br+QHJhxKcdoOa4vND0g0TqsO6rJeqJbA= 52 | github.com/casbin/govaluate v1.7.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A= 53 | github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= 54 | github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 55 | github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4= 56 | github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= 57 | github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= 58 | github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= 59 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 60 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 61 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 62 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 63 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= 64 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= 65 | github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko= 66 | github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= 67 | github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= 68 | github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= 69 | github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= 70 | github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= 71 | github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= 72 | github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= 73 | github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY= 74 | github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok= 75 | github.com/gin-contrib/authz v1.0.5 h1:zN6CdnO4NAVQj9LoTBMaVZGOnUicPyjB5RTVMvQ42iE= 76 | github.com/gin-contrib/authz v1.0.5/go.mod h1:T85j2+UevHJSFvCAj5w6nQFQZ3M4xB4sXz/nVwvIjfo= 77 | github.com/gin-contrib/cors v1.7.5 h1:cXC9SmofOrRg0w9PigwGlHG3ztswH6bqq4vJVXnvYMk= 78 | github.com/gin-contrib/cors v1.7.5/go.mod h1:4q3yi7xBEDDWKapjT2o1V7mScKDDr8k+jZ0fSquGoy0= 79 | github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4= 80 | github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk= 81 | github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= 82 | github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= 83 | github.com/gin-contrib/zap v1.1.5 h1:qKwhWb4DQgPriCl1AHLLob6hav/KUIctKXIjTmWIN3I= 84 | github.com/gin-contrib/zap v1.1.5/go.mod h1:lAchUtGz9M2K6xDr1rwtczyDrThmSx6c9F384T45iOE= 85 | github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ= 86 | github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= 87 | github.com/glebarez/go-sqlite v1.20.3 h1:89BkqGOXR9oRmG58ZrzgoY/Fhy5x0M+/WV48U5zVrZ4= 88 | github.com/glebarez/go-sqlite v1.20.3/go.mod h1:u3N6D/wftiAzIOJtZl6BmedqxmmkDfH3q+ihjqxC9u0= 89 | github.com/glebarez/sqlite v1.7.0 h1:A7Xj/KN2Lvie4Z4rrgQHY8MsbebX3NyWsL3n2i82MVI= 90 | github.com/glebarez/sqlite v1.7.0/go.mod h1:PkeevrRlF/1BhQBCnzcMWzgrIk7IOop+qS2jUYLfHhk= 91 | github.com/go-faster/city v1.0.1 h1:4WAxSZ3V2Ws4QRDrscLEDcibJY8uf41H6AhXDrNDcGw= 92 | github.com/go-faster/city v1.0.1/go.mod h1:jKcUJId49qdW3L1qKHH/3wPeUstCVpVSXTM6vO3VcTw= 93 | github.com/go-faster/errors v0.7.1 h1:MkJTnDoEdi9pDabt1dpWf7AA8/BaSYZqibYyhZ20AYg= 94 | github.com/go-faster/errors v0.7.1/go.mod h1:5ySTjWFiphBs07IKuiL69nxdfd5+fzh1u7FPGZP2quo= 95 | github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= 96 | github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= 97 | github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= 98 | github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= 99 | github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA= 100 | github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= 101 | github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M= 102 | github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I= 103 | github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= 104 | github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM= 105 | github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= 106 | github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= 107 | github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 108 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= 109 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= 110 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= 111 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= 112 | github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k= 113 | github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= 114 | github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= 115 | github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= 116 | github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= 117 | github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= 118 | github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= 119 | github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= 120 | github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= 121 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 122 | github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= 123 | github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= 124 | github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= 125 | github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw= 126 | github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= 127 | github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= 128 | github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA= 129 | github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= 130 | github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A= 131 | github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= 132 | github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc= 133 | github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= 134 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 135 | github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 136 | github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs= 137 | github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 138 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 139 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 140 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 141 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 142 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 143 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 144 | github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= 145 | github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= 146 | github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 147 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 148 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 149 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 150 | github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= 151 | github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= 152 | github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= 153 | github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= 154 | github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 155 | github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= 156 | github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= 157 | github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI= 158 | github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= 159 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 160 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 161 | github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= 162 | github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= 163 | github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA= 164 | github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= 165 | github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw= 166 | github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= 167 | github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= 168 | github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= 169 | github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= 170 | github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM= 171 | github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o= 172 | github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg= 173 | github.com/jcmturner/gokrb5/v8 v8.4.2/go.mod h1:sb+Xq/fTY5yktf/VxLsE3wlfPqQjp0aWNYyvBVK62bc= 174 | github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= 175 | github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= 176 | github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= 177 | github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= 178 | github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= 179 | github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= 180 | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= 181 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 182 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 183 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 184 | github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= 185 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 186 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 187 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 188 | github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= 189 | github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= 190 | github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= 191 | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= 192 | github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= 193 | github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= 194 | github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= 195 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 196 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 197 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 198 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 199 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 200 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 201 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 202 | github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= 203 | github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= 204 | github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= 205 | github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= 206 | github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= 207 | github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= 208 | github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 209 | github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 210 | github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= 211 | github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 212 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 213 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 214 | github.com/mattn/go-sqlite3 v1.14.8/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= 215 | github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= 216 | github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= 217 | github.com/microsoft/go-mssqldb v0.19.0/go.mod h1:ukJCBnnzLzpVF0qYRT+eg1e+eSwjeQ7IvenUv8QPook= 218 | github.com/microsoft/go-mssqldb v1.7.2 h1:CHkFJiObW7ItKTJfHo1QX7QBBD1iV+mn1eOyRP3b/PA= 219 | github.com/microsoft/go-mssqldb v1.7.2/go.mod h1:kOvZKUdrhhFQmxLZqbwUV0rHkNkZpthMITIb2Ko1IoA= 220 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 221 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 222 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 223 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 224 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 225 | github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= 226 | github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= 227 | github.com/montanaflynn/stats v0.6.6/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= 228 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= 229 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 230 | github.com/natefinch/lumberjack v2.0.0+incompatible h1:4QJd3OLAMgj7ph+yZTuX13Ld4UpgHp07nNdFX7mqFfM= 231 | github.com/natefinch/lumberjack v2.0.0+incompatible/go.mod h1:Wi9p2TTF5DG5oU+6YfsmYQpsTIOm0B1VNzQg9Mw6nPk= 232 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= 233 | github.com/paulmach/orb v0.11.1 h1:3koVegMC4X/WeiXYz9iswopaTwMem53NzTJuTF20JzU= 234 | github.com/paulmach/orb v0.11.1/go.mod h1:5mULz1xQfs3bmQm63QEJA6lNGujuRafwA5S/EnuLaLU= 235 | github.com/paulmach/protoscan v0.2.1/go.mod h1:SpcSwydNLrxUGSDvXvO0P7g7AuhJ7lcKfDlhJCDw2gY= 236 | github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= 237 | github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= 238 | github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= 239 | github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= 240 | github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4/go.mod h1:N6UoU20jOqggOuDwUaBQpluzLNDqif3kq9z2wpdYEfQ= 241 | github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= 242 | github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= 243 | github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= 244 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 245 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 246 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 247 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 248 | github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= 249 | github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= 250 | github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= 251 | github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= 252 | github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= 253 | github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= 254 | github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= 255 | github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= 256 | github.com/redis/go-redis/v9 v9.8.0 h1:q3nRvjrlge/6UD7eTu/DSg2uYiU2mCL0G/uzBWqhicI= 257 | github.com/redis/go-redis/v9 v9.8.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= 258 | github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= 259 | github.com/remyoudompheng/bigfft v0.0.0-20230126093431-47fa9a501578 h1:VstopitMQi3hZP0fzvnsLmzXZdQGc4bEcgu24cp+d4M= 260 | github.com/remyoudompheng/bigfft v0.0.0-20230126093431-47fa9a501578/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= 261 | github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= 262 | github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= 263 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 264 | github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo= 265 | github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k= 266 | github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= 267 | github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= 268 | github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= 269 | github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= 270 | github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY= 271 | github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec= 272 | github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY= 273 | github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60= 274 | github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= 275 | github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= 276 | github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs= 277 | github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4= 278 | github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= 279 | github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= 280 | github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= 281 | github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= 282 | github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= 283 | github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 284 | github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4= 285 | github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4= 286 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 287 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 288 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 289 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 290 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 291 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 292 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 293 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 294 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 295 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 296 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 297 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 298 | github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= 299 | github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= 300 | github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE= 301 | github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg= 302 | github.com/swaggo/gin-swagger v1.6.0 h1:y8sxvQ3E20/RCyrXeFfg60r6H0Z+SwpTjMYsMm+zy8M= 303 | github.com/swaggo/gin-swagger v1.6.0/go.mod h1:BG00cCEy294xtVpyIAHG6+e2Qzj/xKlRdOqDkvq0uzo= 304 | github.com/swaggo/swag v1.8.12 h1:pctzkNPu0AlQP2royqX3apjKCQonAnf7KGoxeO4y64w= 305 | github.com/swaggo/swag v1.8.12/go.mod h1:lNfm6Gg+oAq3zRJQNEMBE66LIJKM44mxFqhEEgy2its= 306 | github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= 307 | github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= 308 | github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= 309 | github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= 310 | github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= 311 | github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= 312 | github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= 313 | github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g= 314 | github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY= 315 | github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= 316 | github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8= 317 | github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8= 318 | github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= 319 | github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= 320 | github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= 321 | github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= 322 | github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= 323 | github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= 324 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 325 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 326 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 327 | go.mongodb.org/mongo-driver v1.11.4/go.mod h1:PTSz5yu21bkT/wXpkS7WR5f0ddqw5quethTUn9WM+2g= 328 | go.mongodb.org/mongo-driver/v2 v2.2.1 h1:w5xra3yyu/sGrziMzK1D0cRRaH/b7lWCSsoN6+WV6AM= 329 | go.mongodb.org/mongo-driver/v2 v2.2.1/go.mod h1:qQkDMhCGWl3FN509DfdPd4GRBLU/41zqF/k8eTRceps= 330 | go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= 331 | go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= 332 | go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= 333 | go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= 334 | go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= 335 | go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 336 | go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= 337 | go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= 338 | go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= 339 | go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= 340 | golang.org/x/arch v0.17.0 h1:4O3dfLzd+lQewptAHqjewQZQDyEdejz3VwgeYwkZneU= 341 | golang.org/x/arch v0.17.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= 342 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 343 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 344 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 345 | golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 346 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 347 | golang.org/x/crypto v0.0.0-20220511200225-c6db032c6c88/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 348 | golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 349 | golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= 350 | golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= 351 | golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= 352 | golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= 353 | golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= 354 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 355 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 356 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 357 | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 358 | golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 359 | golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 360 | golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= 361 | golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 362 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 363 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 364 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 365 | golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 366 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 367 | golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 368 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 369 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 370 | golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= 371 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 372 | golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= 373 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 374 | golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 375 | golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 376 | golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= 377 | golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= 378 | golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= 379 | golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= 380 | golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= 381 | golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= 382 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 383 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 384 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 385 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 386 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 387 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 388 | golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= 389 | golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 390 | golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 391 | golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 392 | golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= 393 | golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 394 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 395 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 396 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 397 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 398 | golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 399 | golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 400 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 401 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 402 | golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 403 | golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 404 | golang.org/x/sys v0.0.0-20220224120231-95c6836cb0e7/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 405 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 406 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 407 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 408 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 409 | golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 410 | golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 411 | golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 412 | golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 413 | golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= 414 | golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 415 | golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= 416 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 417 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 418 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 419 | golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= 420 | golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= 421 | golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= 422 | golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= 423 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 424 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 425 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 426 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 427 | golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= 428 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 429 | golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 430 | golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 431 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 432 | golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 433 | golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= 434 | golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= 435 | golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= 436 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 437 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 438 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 439 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 440 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 441 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 442 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 443 | golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= 444 | golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= 445 | golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= 446 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 447 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 448 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 449 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 450 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 451 | google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 452 | google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= 453 | google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= 454 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 455 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 456 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 457 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 458 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 459 | gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= 460 | gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= 461 | gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= 462 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 463 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 464 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 465 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 466 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 467 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 468 | gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 469 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 470 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 471 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 472 | gorm.io/datatypes v1.2.4 h1:uZmGAcK/QZ0uyfCuVg0VQY1ZmV9h1fuG0tMwKByO1z4= 473 | gorm.io/datatypes v1.2.4/go.mod h1:f4BsLcFAX67szSv8svwLRjklArSHAvHLeE3pXAS5DZI= 474 | gorm.io/driver/clickhouse v0.7.0 h1:BCrqvgONayvZRgtuA6hdya+eAW5P2QVagV3OlEp1vtA= 475 | gorm.io/driver/clickhouse v0.7.0/go.mod h1:TmNo0wcVTsD4BBObiRnCahUgHJHjBIwuRejHwYt3JRs= 476 | gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo= 477 | gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM= 478 | gorm.io/driver/postgres v1.5.11 h1:ubBVAfbKEUld/twyKZ0IYn9rSQh448EdelLYk9Mv314= 479 | gorm.io/driver/postgres v1.5.11/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI= 480 | gorm.io/driver/sqlite v1.1.6/go.mod h1:W8LmC/6UvVbHKah0+QOC7Ja66EaZXHwUTjgXY8YNWX8= 481 | gorm.io/driver/sqlite v1.5.7 h1:8NvsrhP0ifM7LX9G4zPB97NwovUakUxc+2V2uuf3Z1I= 482 | gorm.io/driver/sqlite v1.5.7/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4= 483 | gorm.io/driver/sqlserver v1.6.0 h1:VZOBQVsVhkHU/NzNhRJKoANt5pZGQAS1Bwc6m6dgfnc= 484 | gorm.io/driver/sqlserver v1.6.0/go.mod h1:WQzt4IJo/WHKnckU9jXBLMJIVNMVeTu25dnOzehntWw= 485 | gorm.io/gen v0.3.27 h1:ziocAFLpE7e0g4Rum69pGfB9S6DweTxK8gAun7cU8as= 486 | gorm.io/gen v0.3.27/go.mod h1:9zquz2xD1f3Eb/eHq4oLn2z6vDVvQlCY5S3uMBLv4EA= 487 | gorm.io/gorm v1.21.15/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0= 488 | gorm.io/gorm v1.22.2/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0= 489 | gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= 490 | gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= 491 | gorm.io/gorm v1.30.0 h1:qbT5aPv1UH8gI99OsRlvDToLxW5zR7FzS9acZDOZcgs= 492 | gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE= 493 | gorm.io/hints v1.1.0 h1:Lp4z3rxREufSdxn4qmkK3TLDltrM10FLTHiuqwDPvXw= 494 | gorm.io/hints v1.1.0/go.mod h1:lKQ0JjySsPBj3uslFzY3JhYDtqEwzm+G1hv8rWujB6Y= 495 | gorm.io/plugin/dbresolver v1.5.3 h1:wFwINGZZmttuu9h7XpvbDHd8Lf9bb8GNzp/NpAMV2wU= 496 | gorm.io/plugin/dbresolver v1.5.3/go.mod h1:TSrVhaUg2DZAWP3PrHlDlITEJmNOkL0tFTjvTEsQ4XE= 497 | modernc.org/libc v1.22.2 h1:4U7v51GyhlWqQmwCHj28Rdq2Yzwk55ovjFrdPjs8Hb0= 498 | modernc.org/libc v1.22.2/go.mod h1:uvQavJ1pZ0hIoC/jfqNoMLURIMhKzINIWypNM17puug= 499 | modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ= 500 | modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= 501 | modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds= 502 | modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= 503 | modernc.org/sqlite v1.20.3 h1:SqGJMMxjj1PHusLxdYxeQSodg7Jxn9WWkaAQjKrntZs= 504 | modernc.org/sqlite v1.20.3/go.mod h1:zKcGyrICaxNTMEHSr1HQ2GUraP0j+845GYw37+EyT6A= 505 | nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= 506 | --------------------------------------------------------------------------------