提交 54ae8fdb authored 作者: mooncake's avatar mooncake

update

上级 9b11dbc1
...@@ -4,14 +4,49 @@ import ( ...@@ -4,14 +4,49 @@ import (
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
"strings"
"sync"
"gitlab.wanzhuangkj.com/tush/opkg/pkg/oss"
"gitlab.wanzhuangkj.com/tush/opkg/xutils/fileutils" "gitlab.wanzhuangkj.com/tush/opkg/xutils/fileutils"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
) )
type Config struct { type Config struct {
ConfFilePath string `yaml:"confFilePath"` l *sync.RWMutex
ConfFilePath string `yaml:"confFilePath"`
App App `yaml:"app"`
Oss oss.AliOssConfig `yaml:"oss"`
}
func Read(f func(c *Config)) {
if Cfg == nil {
panic("config is nil, please call config.Init() first")
}
Cfg.l.RLock()
f(Cfg)
Cfg.l.RUnlock()
}
func Write(f func(c *Config)) {
if Cfg == nil {
panic("config is nil, please call config.Init() first")
}
Cfg.l.Lock()
defer Cfg.l.Unlock()
f(Cfg)
}
func IsNotProd() bool {
return !IsProd()
}
func IsProd() bool {
env := "prod"
Read(func(c *Config) {
env = c.App.Env
})
return strings.ToLower(env) == "prod"
} }
func (g *Config) Reload() { func (g *Config) Reload() {
...@@ -32,9 +67,22 @@ func (g *Config) Reload() { ...@@ -32,9 +67,22 @@ func (g *Config) Reload() {
fmt.Println("config file reload success") fmt.Println("config file reload success")
} }
var Conf = &Config{} var Cfg = &Config{
l: &sync.RWMutex{},
}
func Init() { func Init() {
Conf.ConfFilePath = filepath.Join(fileutils.CWD(), "conf/conf.yaml") Cfg.ConfFilePath = filepath.Join(fileutils.CWD(), "conf/conf.yaml")
Conf.Reload() Cfg.Reload()
}
type App struct {
ContextPath string `yaml:"contextPath" json:"contextPath" mapstructure:"contextPath"`
Env string `yaml:"env" json:"env" mapstructure:"env"`
Host string `yaml:"host" json:"host" mapstructure:"host"`
Name string `yaml:"name" json:"name" mapstructure:"name"`
Version string `yaml:"version" json:"version" mapstructure:"version"`
HideSensitiveFields string `yaml:"hideSensitiveFields" json:"hideSensitiveFields" mapstructure:"hideSensitiveFields"`
} }
package request
import (
"math"
"gitlab.wanzhuangkj.com/tush/opkg/pkg/errcode"
"gitlab.wanzhuangkj.com/tush/opkg/pkg/sf"
"gitlab.wanzhuangkj.com/tush/opkg/pkg/xerrors/xerror"
)
const (
LastIDLimit = 1000
)
var (
LastIDMax = sf.ParseInt64(math.MaxInt64)
)
type Params struct {
PageIndex int `json:"page" comment:"page" encomment:"page" form:"page" validate:"required" example:"1"` // page number, starting from page 1
PageSize int `json:"pageSize" comment:"pageSize" encomment:"pageSize" form:"pageSize" validate:"required" example:"10"` // lines per page
Sort string `json:"sort,omitempty" form:"sort,omitempty" validate:"required" example:"id"` // sorted fields, multi-column sorting separated by commas
Columns []Column `json:"columns,omitempty" form:"columns,omitempty"` // query conditions
}
type Column struct {
Name string `json:"name" comment:"name" encomment:"name" form:"name" validate:"required" example:"id"` // column name
Exp string `json:"exp" comment:"exp" encomment:"exp" form:"exp" validate:"required" example:"="` // expressions, which default to = when the value is null, have =, !=, >, >=, <, <=, like
Value interface{} `json:"value" comment:"value" encomment:"value" form:"value" validate:"required" example:"102"` // column value
Logic string `json:"logic" comment:"logic" encomment:"logic" form:"logic" example:"and"` // logical type, default value is "and", support &, and, ||, or
}
type Conditions struct {
Columns []Column `json:"columns" comment:"columns" encomment:"columns" form:"columns"` // columns info
}
type BaseCreateReply struct {
ID sf.ID `json:"id" comment:"id" encomment:"id" form:"id" example:"1924346047086211072"` // id
}
type BaseIDReq struct {
ID sf.ID `json:"id" comment:"id" encomment:"id" form:"id" swaggertype:"string" validate:"required" example:"1"` // id
}
func (x *BaseIDReq) Valid() error {
if x.ID <= 0 {
return xerror.NewC(errcode.InvalidParams.Code(), "参数[id]不合法")
}
return nil
}
type BaseIDsReq struct {
IDs []sf.ID `json:"ids" comment:"ids" encomment:"ids" swaggertype:"array,string" form:"ids" validate:"required" example:"1" binding:"min=1"` // id list
}
func (x BaseIDsReq) Valid() error {
if len(x.IDs) == 0 {
return xerror.NewC(errcode.InvalidParams.Code(), "参数[ids]不能为空")
}
return nil
}
type GetByLastIDReq struct {
LastID sf.ID `json:"lastID" form:"lastID" validate:"required" example:"10086"` // 最后一个id
Limit int `json:"limit" form:"limit" validate:"required" example:"1000"` // 限制数量
Sort string `json:"sort" form:"sort" example:"id asc"` // 排序
}
func (x *GetByLastIDReq) Valid() error {
return nil
}
func (r *GetByLastIDReq) GetLastID() sf.ID {
if r.LastID <= 0 {
r.LastID = LastIDMax
}
return r.LastID
}
func (r *GetByLastIDReq) GetLimit() int {
if r.Limit <= 0 {
r.Limit = LastIDLimit
}
return r.Limit
}
func (r *GetByLastIDReq) GetSort() string {
if r.Sort == "" {
r.Sort = "id asc"
}
return r.Sort
}
...@@ -3,14 +3,20 @@ module gitlab.wanzhuangkj.com/tush/opkg ...@@ -3,14 +3,20 @@ module gitlab.wanzhuangkj.com/tush/opkg
go 1.24.3 go 1.24.3
require ( require (
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible
github.com/bwmarrin/snowflake v0.3.0 github.com/bwmarrin/snowflake v0.3.0
github.com/duke-git/lancet/v2 v2.3.8
github.com/gin-gonic/gin v1.11.0 github.com/gin-gonic/gin v1.11.0
github.com/go-redsync/redsync/v4 v4.14.0
github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1
github.com/huandu/xstrings v1.5.0 github.com/huandu/xstrings v1.5.0
github.com/jinzhu/copier v0.4.0
github.com/mojocn/base64Captcha v1.3.8
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/redis/go-redis/v9 v9.16.0
github.com/stretchr/testify v1.11.1 github.com/stretchr/testify v1.11.1
github.com/uptrace/opentelemetry-go-extra/otelgorm v0.3.2 github.com/uptrace/opentelemetry-go-extra/otelgorm v0.3.2
go.uber.org/zap v1.27.0 go.uber.org/zap v1.27.0
golang.org/x/sync v0.17.0
google.golang.org/grpc v1.76.0 google.golang.org/grpc v1.76.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1 gopkg.in/natefinch/lumberjack.v2 v2.2.1
gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v2 v2.4.0
...@@ -26,8 +32,10 @@ require ( ...@@ -26,8 +32,10 @@ require (
filippo.io/edwards25519 v1.1.0 // indirect filippo.io/edwards25519 v1.1.0 // indirect
github.com/bytedance/sonic v1.14.0 // indirect github.com/bytedance/sonic v1.14.0 // indirect
github.com/bytedance/sonic/loader v0.3.0 // indirect github.com/bytedance/sonic/loader v0.3.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cloudwego/base64x v0.1.6 // indirect github.com/cloudwego/base64x v0.1.6 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/gabriel-vasile/mimetype v1.4.8 // indirect github.com/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/gin-contrib/sse v1.1.0 // indirect github.com/gin-contrib/sse v1.1.0 // indirect
github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/logr v1.4.3 // indirect
...@@ -38,6 +46,9 @@ require ( ...@@ -38,6 +46,9 @@ require (
github.com/go-sql-driver/mysql v1.8.1 // indirect github.com/go-sql-driver/mysql v1.8.1 // indirect
github.com/goccy/go-json v0.10.2 // indirect github.com/goccy/go-json v0.10.2 // indirect
github.com/goccy/go-yaml v1.18.0 // indirect github.com/goccy/go-yaml v1.18.0 // indirect
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/pgx/v5 v5.6.0 // indirect github.com/jackc/pgx/v5 v5.6.0 // indirect
...@@ -66,12 +77,13 @@ require ( ...@@ -66,12 +77,13 @@ require (
go.uber.org/multierr v1.10.0 // indirect go.uber.org/multierr v1.10.0 // indirect
golang.org/x/arch v0.20.0 // indirect golang.org/x/arch v0.20.0 // indirect
golang.org/x/crypto v0.40.0 // indirect golang.org/x/crypto v0.40.0 // indirect
golang.org/x/exp v0.0.0-20221208152030-732eee02a75a // indirect golang.org/x/image v0.23.0 // indirect
golang.org/x/mod v0.25.0 // indirect golang.org/x/mod v0.25.0 // indirect
golang.org/x/net v0.42.0 // indirect golang.org/x/net v0.42.0 // indirect
golang.org/x/sync v0.16.0 // indirect
golang.org/x/sys v0.35.0 // indirect golang.org/x/sys v0.35.0 // indirect
golang.org/x/text v0.27.0 // indirect golang.org/x/text v0.27.0 // indirect
golang.org/x/time v0.14.0 // indirect
golang.org/x/tools v0.34.0 // indirect golang.org/x/tools v0.34.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b // indirect
google.golang.org/protobuf v1.36.9 // indirect google.golang.org/protobuf v1.36.9 // indirect
) )
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible h1:8psS8a+wKfiLt1iVDX79F7Y6wUM49Lcha2FMXt4UM8g=
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0= github.com/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0=
github.com/bwmarrin/snowflake v0.3.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/E9WsDpxqwE= github.com/bwmarrin/snowflake v0.3.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/E9WsDpxqwE=
github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ= github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ=
github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA= github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA=
github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA= github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=
github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/duke-git/lancet/v2 v2.3.8 h1:dlkqn6Nj2LRWFuObNxttkMHxrFeaV6T26JR8jbEVbPg= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/duke-git/lancet/v2 v2.3.8/go.mod h1:zGa2R4xswg6EG9I6WnyubDbFO/+A/RROxIbXcwryTsc= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
...@@ -32,15 +40,37 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn ...@@ -32,15 +40,37 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4= github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg=
github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
github.com/go-redis/redis/v7 v7.4.1 h1:PASvf36gyUpr2zdOUS/9Zqc80GbM+9BDyiJSJDDOrTI=
github.com/go-redis/redis/v7 v7.4.1/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg=
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
github.com/go-redsync/redsync/v4 v4.14.0 h1:zyxzFJsmQHIPBl8iBT7KFKohWsjsghgGLiP8TnFMLNc=
github.com/go-redsync/redsync/v4 v4.14.0/go.mod h1:twMlVd19upZ/juvJyJGlQOSQxor1oeHtjs62l4pRFzo=
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1 h1:FWNFq4fM1wPfcK40yHE5UO3RUdSNPaBC+j3PokzA6OQ=
github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1/go.mod h1:5YoVOkjYAQumqlV356Hj3xeYh4BdZuLE0/nRkf2NKkI=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/gomodule/redigo v1.9.2 h1:HrutZBLhSIU8abiSfW8pj8mPhOyMYjZT/wcA4/L9L9s=
github.com/gomodule/redigo v1.9.2/go.mod h1:KsU3hiK/Ay8U42qpaJk+kuNa3C+spxapWpM+ywhcgtw=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
...@@ -51,6 +81,8 @@ github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY= ...@@ -51,6 +81,8 @@ github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY=
github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw= github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw=
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
...@@ -73,6 +105,8 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OH ...@@ -73,6 +105,8 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OH
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mojocn/base64Captcha v1.3.8 h1:rrN9BhCwXKS8ht1e21kvR3iTaMgf4qPC9sRoV52bqEg=
github.com/mojocn/base64Captcha v1.3.8/go.mod h1:QFZy927L8HVP3+VV5z2b1EAEiv1KxVJKZbAucVgLUy4=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
...@@ -83,6 +117,12 @@ github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= ...@@ -83,6 +117,12 @@ github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg= github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg=
github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY= github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY=
github.com/redis/go-redis/v9 v9.16.0 h1:OotgqgLSRCmzfqChbQyG1PHC3tLNR89DG4jdOERSEP4=
github.com/redis/go-redis/v9 v9.16.0/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370=
github.com/redis/rueidis v1.0.64 h1:XqgbueDuNV3qFdVdQwAHJl1uNt90zUuAJuzqjH4cw6Y=
github.com/redis/rueidis v1.0.64/go.mod h1:Lkhr2QTgcoYBhxARU7kJRO8SyVlgUuEkcJO1Y8MCluA=
github.com/redis/rueidis/rueidiscompat v1.0.64 h1:M8JbLP4LyHQhBLBRsUQIzui8/LyTtdESNIMVveqm4RY=
github.com/redis/rueidis/rueidiscompat v1.0.64/go.mod h1:8pJVPhEjpw0izZFSxYwDziUiEYEkEklTSw/nZzga61M=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
...@@ -95,6 +135,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO ...@@ -95,6 +135,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/stvp/tempredis v0.0.0-20181119212430-b82af8480203 h1:QVqDTf3h2WHt08YuiTGPZLls0Wq99X9bWd0Q5ZSBesM=
github.com/stvp/tempredis v0.0.0-20181119212430-b82af8480203/go.mod h1:oqN97ltKNihBbwlX8dLpwxCl3+HnXKV/R0e+sRLd9C8=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA= github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
...@@ -103,6 +145,7 @@ github.com/uptrace/opentelemetry-go-extra/otelgorm v0.3.2 h1:Jjn3zoRz13f8b1bR6Lr ...@@ -103,6 +145,7 @@ github.com/uptrace/opentelemetry-go-extra/otelgorm v0.3.2 h1:Jjn3zoRz13f8b1bR6Lr
github.com/uptrace/opentelemetry-go-extra/otelgorm v0.3.2/go.mod h1:wocb5pNrj/sjhWB9J5jctnC0K2eisSdz/nJJBNFHo+A= github.com/uptrace/opentelemetry-go-extra/otelgorm v0.3.2/go.mod h1:wocb5pNrj/sjhWB9J5jctnC0K2eisSdz/nJJBNFHo+A=
github.com/uptrace/opentelemetry-go-extra/otelsql v0.3.2 h1:ZjUj9BLYf9PEqBn8W/OapxhPjVRdC6CsXTdULHsyk5c= github.com/uptrace/opentelemetry-go-extra/otelsql v0.3.2 h1:ZjUj9BLYf9PEqBn8W/OapxhPjVRdC6CsXTdULHsyk5c=
github.com/uptrace/opentelemetry-go-extra/otelsql v0.3.2/go.mod h1:O8bHQfyinKwTXKkiKNGmLQS7vRsqRxIQTFZpYpHK3IQ= github.com/uptrace/opentelemetry-go-extra/otelsql v0.3.2/go.mod h1:O8bHQfyinKwTXKkiKNGmLQS7vRsqRxIQTFZpYpHK3IQ=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
...@@ -121,23 +164,86 @@ go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= ...@@ -121,23 +164,86 @@ go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c= golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c=
golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
golang.org/x/exp v0.0.0-20221208152030-732eee02a75a h1:4iLhBPcpqFmylhnkbY3W0ONLUYYkDAW9xMFLfxgsvCw= golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68=
golang.org/x/exp v0.0.0-20221208152030-732eee02a75a/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w= golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo= golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b h1:zPKJod4w6F1+nRGDI9ubnXYhU9NSWoFAijkHkUXeTK8=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A= google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A=
google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c= google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c=
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
......
package captcha
import (
"context"
"strings"
"time"
"github.com/mojocn/base64Captcha"
"github.com/redis/go-redis/v9"
"gitlab.wanzhuangkj.com/tush/opkg/pkg/xerrors/xerror"
)
const (
NotFoundPlaceholder = "*"
)
type Captcha struct {
captcha *base64Captcha.Captcha
}
func (r *Captcha) Verify(id, answer string, clear bool) bool {
return r.captcha.Store.Verify(id, answer, clear)
}
func (r *Captcha) Generate(ctx context.Context) (id, b64s, answer string, err error) {
return r.captcha.Generate()
}
type RedisStore struct {
client *redis.Client // 支持单节点和集群模式
prefix string // Key前缀隔离
expiration time.Duration // 验证码有效期
}
func NewRedisStore(redisClient *redis.Client, prefix string) *RedisStore {
return &RedisStore{
client: redisClient,
prefix: prefix,
expiration: 10 * time.Minute,
}
}
func (rs *RedisStore) Set(id string, value string) error {
ctx := context.Background()
fullValue := value + "|" + time.Now().Format(time.RFC3339)
return rs.client.Set(ctx, rs.prefix+id, fullValue, rs.expiration).Err()
}
func (rs *RedisStore) Get(id string, clear bool) string {
ctx := context.Background()
key := rs.prefix + id
val, err := rs.client.Get(ctx, key).Result()
if err != nil {
if xerror.Is(err, redis.Nil) {
return ""
}
panic(xerror.New(err.Error()))
}
if clear {
go rs.client.Del(ctx, key)
}
parts := strings.Split(val, "|")
if len(parts) != 2 {
return ""
}
if parts[0] == NotFoundPlaceholder { // 这块逻辑应该用不到
return ""
}
return parts[0]
}
func (r *RedisStore) Verify(id, answer string, clear bool) bool {
stored := r.Get(id, clear)
return strings.EqualFold(stored, answer)
}
func NewCaptcha(redisClient *redis.Client) *Captcha {
store := NewRedisStore(redisClient, "captcha:")
driver := &base64Captcha.DriverDigit{
Height: 80,
Width: 240,
Length: 5,
MaxSkew: 0.7,
DotCount: 80,
}
return &Captcha{
captcha: base64Captcha.NewCaptcha(driver, store),
}
}
package csvexporter
import (
"bytes"
"context"
"fmt"
"io"
"path/filepath"
"github.com/gin-gonic/gin"
"github.com/gocarina/gocsv"
"github.com/jinzhu/copier"
"gitlab.wanzhuangkj.com/tush/opkg/config"
"gitlab.wanzhuangkj.com/tush/opkg/pkg/oss"
ctxutils "gitlab.wanzhuangkj.com/tush/opkg/xutils/ctxutils"
)
type CSVExporter[T any] struct {
pageSize int
page PageFn[T]
total TotalFn
filename FilenameFn
err error
content []byte
fileURL string
}
type PageFn[T any] func(offset int) ([]*T, error)
type TotalFn func() (int64, error)
type FilenameFn func() string
type IPageReq interface {
Query
}
type Query interface {
TableName() string
}
func New[T any](pageSize int, total TotalFn, page PageFn[T], filename FilenameFn) *CSVExporter[T] {
return &CSVExporter[T]{
pageSize: pageSize,
total: total,
page: page,
filename: filename,
}
}
func (x *CSVExporter[T]) Export() *CSVExporter[T] {
if x.err != nil {
return x
}
total, err := x.total()
if err != nil {
x.err = err
return x
}
var rs []*T
if total > 0 {
rs, err = x.nestedLoop(total)
if err != nil {
x.err = err
return x
}
}
content, err := gocsv.MarshalBytes(&rs)
if err != nil {
x.err = err
return x
}
x.content = content
return x
}
func (x *CSVExporter[T]) nestedLoop(total int64) ([]*T, error) {
ret := make([]*T, 0, total)
pageIndex := 1
offset := 0
for {
offset = (pageIndex - 1) * x.pageSize
rs, err := x.page(offset)
if err != nil {
return nil, err
}
if len(rs) == 0 {
break
}
ret = append(ret, rs...)
pageIndex++
}
return ret, nil
}
func (x *CSVExporter[T]) Upload() *CSVExporter[T] {
if x.err != nil {
return x
}
ossCfg := oss.AliOssConfig{}
config.Read(func(c *config.Config) {
copier.Copy(&ossCfg, c.Oss)
})
if fileUrl, _, err := oss.OSS.UploadFile(bytes.NewBuffer(x.content), x.filename(), &ossCfg); err != nil {
x.err = err
return x
} else {
x.fileURL = fileUrl
}
return x
}
func (x *CSVExporter[T]) writeToGin(c *gin.Context) *CSVExporter[T] {
if x.err != nil {
return x
}
filename := filepath.Base(x.filename())
c.Header("Content-Type", "application/octet-stream")
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", filename))
if _, err := io.Copy(c.Writer, bytes.NewBuffer(x.content)); err != nil {
x.err = err
return x
}
c.Writer.Flush()
return x
}
func (x *CSVExporter[T]) WriteToReply(ctx context.Context) *CSVExporter[T] {
if x.err != nil {
return x
}
c := ctxutils.Gin(ctx)
return x.writeToGin(c)
}
func (x *CSVExporter[T]) Err() error {
return x.err
}
func (x *CSVExporter[T]) URI() (string, error) {
return x.fileURL, x.err
}
// Package errcode is used for http and grpc error codes, include system-level error codes and business-level error codes
package errcode
import (
"fmt"
"net/http"
"strconv"
"strings"
)
// ToHTTPCodeLabel need to convert to standard http code label
const ToHTTPCodeLabel = "[standard http code]"
var errCodes = map[int]*Error{}
var httpErrCodes = map[int]string{}
// Error error
type Error struct {
code int
msg string
details []string
// if true, need to convert to standard http code
// use ErrToHTTP and ParseError will set this to true
needHTTPCode bool
}
// NewError create a new error message
func NewError(code int, msg string, details ...string) *Error {
if v, ok := errCodes[code]; ok {
panic(fmt.Sprintf(`http error code = %d already exists, please define a new error code,
msg1 = %s
msg2 = %s
`, code, v.Msg(), msg))
}
httpErrCodes[code] = msg
e := &Error{code: code, msg: msg, details: details}
errCodes[code] = e
return e
}
// Err convert to standard error,
// if there is a parameter 'msg', it will replace the original message.
func (e *Error) Err(msg ...string) error {
message := e.msg
if len(msg) > 0 {
message = strings.Join(msg, ", ")
}
if len(e.details) == 0 {
return fmt.Errorf("code = %d, msg = %s", e.code, message)
}
return fmt.Errorf("code = %d, msg = %s, details = %v", e.code, message, e.details)
}
// ErrToHTTP convert to standard error add ToHTTPCodeLabel to error message,
// use it if you need to convert to standard HTTP status code,
// if there is a parameter 'msg', it will replace the original message.
// Tips: you can call the GetErrorCode function to get the standard HTTP status code.
func (e *Error) ErrToHTTP(msg ...string) error {
message := e.msg
if len(msg) > 0 {
message = strings.Join(msg, ", ")
}
if len(e.details) == 0 {
return fmt.Errorf("code = %d, msg = %s%s", e.code, message, ToHTTPCodeLabel)
}
return fmt.Errorf("code = %d, msg = %s, details = %v%s", e.code, message, strings.Join(e.details, ", "), ToHTTPCodeLabel)
}
// Code get error code
func (e *Error) Code() int {
return e.code
}
// Msg get error code message
func (e *Error) Msg() string {
return e.msg
}
func (e *Error) Error() string {
return e.msg
}
func (e *Error) ToXerror() (int, string) {
return e.code, e.msg
}
// NeedHTTPCode need to convert to standard http code
func (e *Error) NeedHTTPCode() bool {
return e.needHTTPCode
}
// Details get error code details
func (e *Error) Details() []string {
return e.details
}
// WithDetails add error details
func (e *Error) WithDetails(details ...string) *Error {
newError := &Error{code: e.code, msg: e.msg}
newError.msg += ", " + strings.Join(details, ", ")
return newError
}
// RewriteMsg rewrite error message
func (e *Error) RewriteMsg(msg string) *Error {
return &Error{code: e.code, msg: msg}
}
// WithOutMsg out error message
// Deprecated: use RewriteMsg instead
func (e *Error) WithOutMsg(msg string) *Error {
return &Error{code: e.code, msg: msg}
}
// WithOutMsgI18n out error message i18n
// langMsg example:
//
// map[int]map[string]string{
// 20010: {
// "en-US": "login failed",
// "zh-CN": "登录失败",
// },
// }
//
// lang BCP 47 code https://learn.microsoft.com/en-us/openspecs/office_standards/ms-oe376/6c085406-a698-4e12-9d4d-c3b0ee3dbc4a
func (e *Error) WithOutMsgI18n(langMsg map[int]map[string]string, lang string) *Error {
if i18nMsg, ok := langMsg[e.Code()]; ok {
if msg, ok2 := i18nMsg[lang]; ok2 {
return &Error{code: e.code, msg: msg}
}
}
return &Error{code: e.code, msg: e.msg}
}
// ToHTTPCode convert to http error code
func (e *Error) ToHTTPCode() int {
switch e.Code() {
case Success.Code():
return http.StatusOK
case InternalServerError.Code():
return http.StatusInternalServerError
case InvalidParams.Code():
return http.StatusBadRequest
case Unauthorized.Code(), PermissionDenied.Code():
return http.StatusUnauthorized
case TooManyRequests.Code(), LimitExceed.Code():
return http.StatusTooManyRequests
case Forbidden.Code(), AccessDenied.Code():
return http.StatusForbidden
case NotFound.Code():
return http.StatusNotFound
case Conflict.Code(), AlreadyExists.Code():
return http.StatusConflict
case TooEarly.Code():
return http.StatusTooEarly
case Timeout.Code(), DeadlineExceeded.Code():
return http.StatusRequestTimeout
case MethodNotAllowed.Code():
return http.StatusMethodNotAllowed
case ServiceUnavailable.Code():
return http.StatusServiceUnavailable
case Unimplemented.Code():
return http.StatusNotImplemented
case StatusBadGateway.Code():
return http.StatusBadGateway
}
return http.StatusInternalServerError
}
// ParseError parsing out error codes from error messages
func ParseError(err error) *Error {
if err == nil {
return Success
}
outError := &Error{
code: -1,
msg: "unknown error",
}
splits := strings.Split(err.Error(), ", msg = ")
codeStr := strings.ReplaceAll(splits[0], "code = ", "")
code, er := strconv.Atoi(codeStr)
if er != nil {
return outError
}
if e, ok := errCodes[code]; ok {
if len(splits) > 1 {
outError.code = code
outError.msg = splits[1]
outError.needHTTPCode = strings.Contains(err.Error(), ToHTTPCodeLabel)
return outError
}
return e
}
return outError
}
// GetErrorCode get Error code from error returned by http invoke
func GetErrorCode(err error) int {
e := ParseError(err)
if e.needHTTPCode {
return e.ToHTTPCode()
}
return e.Code()
}
// ListHTTPErrCodes list http error codes
func ListHTTPErrCodes() []ErrInfo {
return getErrorInfo(httpErrCodes)
}
func IsSysDefinedError(code int) bool {
_, ok := errCodes[code]
return ok
}
package errcode
// HCode Generate an error code between 200000 and 300000 according to the number
//
// http service level error code, Err prefix, example.
//
// var (
// ErrUserCreate = NewError(HCode(1)+1, "failed to create user") // 200101
// ErrUserDelete = NewError(HCode(1)+2, "failed to delete user") // 200102
// ErrUserUpdate = NewError(HCode(1)+3, "failed to update user") // 200103
// ErrUserGet = NewError(HCode(1)+4, "failed to get user details") // 200104
// )
func HCode(num int) int {
if num > 999 || num < 1 {
panic("num range must be between 0 to 1000")
}
return 200000 + num*100
}
package errcode
// http system level error code, error code range 10000~20000
var (
Success = NewError(1, "OK")
InvalidParams = NewError(100001, "Invalid Parameter")
Unauthorized = NewError(100002, "Unauthorized")
InternalServerError = NewError(100003, "Internal Server Error")
NotFound = NewError(100004, "Not Found")
AlreadyExists = NewError(100005, "Already Exists")
Timeout = NewError(100006, "Request Timeout")
TooManyRequests = NewError(100007, "Too Many Requests")
Forbidden = NewError(100008, "Forbidden")
LimitExceed = NewError(100009, "Limit Exceed")
DeadlineExceeded = NewError(100010, "Deadline Exceeded")
AccessDenied = NewError(100011, "Access Denied")
MethodNotAllowed = NewError(100012, "Method Not Allowed")
ServiceUnavailable = NewError(100013, "Service Unavailable")
Canceled = NewError(100014, "Canceled")
Unknown = NewError(100015, "Unknown")
PermissionDenied = NewError(100016, "Permission Denied")
ResourceExhausted = NewError(100017, "Resource Exhausted")
FailedPrecondition = NewError(100018, "Failed Precondition")
Aborted = NewError(100019, "Aborted")
OutOfRange = NewError(100020, "Out Of Range")
Unimplemented = NewError(100021, "Unimplemented")
DataLoss = NewError(100022, "Data Loss")
StatusBadGateway = NewError(100023, "Bad Gateway")
DBError = NewError(100024, "DB Error")
NetError = NewError(100025, "Net Error")
FileError = NewError(100026, "File Error")
BizLogicError = NewError(100027, "Business Logic Error")
// Deprecated: use Conflict instead
Conflict = NewError(100409, "Conflict")
TooEarly = NewError(100425, "Too Early")
)
package errcode
import (
"errors"
"net/http"
"strconv"
"strings"
"github.com/gin-gonic/gin"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
// SkipResponse skip response
var SkipResponse = errors.New("skip response") //nolint
// Responser response interface
type Responser interface {
Success(ctx *gin.Context, data interface{})
ParamError(ctx *gin.Context, err error)
Error(ctx *gin.Context, err error) bool
}
// NewResponser creates a new responser, if isFromRPC=true, it means return from rpc, otherwise default return from http
func NewResponser(isFromRPC bool, httpErrors []*Error, rpcStatus []*RPCStatus) Responser {
httpErrorsMap := make(map[int]*Error)
rpcStatusMap := make(map[int]*RPCStatus)
for _, httpError := range httpErrors {
if httpError == nil {
continue
}
httpErrorsMap[httpError.Code()] = httpError
}
for _, statusError := range rpcStatus {
if statusError == nil || statusError.status == nil {
continue
}
rpcStatusMap[int(statusError.ToRPCCode())] = statusError
rpcStatusMap[int(statusError.status.Code())] = statusError
}
return &defaultResponse{
isFromRPC: isFromRPC,
httpErrors: httpErrorsMap,
rpcStatus: rpcStatusMap,
}
}
type defaultResponse struct {
isFromRPC bool // error comes from grpc, if not, default is from http
httpErrors map[int]*Error
rpcStatus map[int]*RPCStatus
}
func (resp *defaultResponse) response(c *gin.Context, respStatus, code int, msg string, data interface{}) {
c.JSON(respStatus, map[string]interface{}{
"code": code,
"msg": msg,
"data": data,
})
}
// Success response success information
func (resp *defaultResponse) Success(c *gin.Context, data interface{}) {
resp.response(c, http.StatusOK, 0, "ok", data)
}
// ParamError response parameter error information, does not return an error message
func (resp *defaultResponse) ParamError(c *gin.Context, _ error) {
resp.response(c, http.StatusOK, InvalidParams.Code(), InvalidParams.Msg(), struct{}{})
}
// Error response error information, if return true, means that the error code is converted to a standard http code,
// otherwise the return http code is always 200
func (resp *defaultResponse) Error(c *gin.Context, err error) bool {
if resp.isFromRPC {
// error from rpc and response the corresponding http code
return resp.handleRPCError(c, err)
}
// error from http and response http code
return resp.handleHTTPError(c, err)
}
// error from grpc
func (resp *defaultResponse) handleRPCError(c *gin.Context, err error) bool {
st, _ := status.FromError(err)
// user defined err, response 200
if st.Code() == codes.Unknown {
code, msg := parseCodeAndMsg(st.String())
if code == -1 {
// non-conforming err
resp.response(c, http.StatusOK, -1, "unknown error", struct{}{})
} else {
// err created using NewRPCStatus
resp.response(c, http.StatusOK, code, msg, struct{}{})
}
return false
}
// default error code to http
switch st.Code() {
case codes.Internal, StatusInternalServerError.status.Code():
resp.response(c, http.StatusInternalServerError, http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError), struct{}{})
return true
case codes.Unavailable, StatusServiceUnavailable.status.Code():
resp.response(c, http.StatusServiceUnavailable, http.StatusServiceUnavailable, http.StatusText(http.StatusServiceUnavailable), struct{}{})
return true
}
// check if you need to return the standard http code
if strings.Contains(st.Message(), ToHTTPCodeLabel) {
code := convertToHTTPCode(st.Code())
msg := strings.ReplaceAll(st.Message(), ToHTTPCodeLabel, "")
resp.response(c, code, int(st.Code()), msg, struct{}{})
return true
}
// user defined error code to http
if resp.isUserDefinedRPCErrorCode(c, int(st.Code())) {
return true
}
// response 200
resp.response(c, http.StatusOK, int(st.Code()), st.Message(), struct{}{})
return false
}
// error from http
func (resp *defaultResponse) handleHTTPError(c *gin.Context, err error) bool {
e := ParseError(err)
// default error code to http
switch e.Code() {
case InternalServerError.Code(), http.StatusInternalServerError:
resp.response(c, http.StatusInternalServerError, http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError), struct{}{})
return true
case ServiceUnavailable.Code(), http.StatusServiceUnavailable:
resp.response(c, http.StatusServiceUnavailable, http.StatusServiceUnavailable, http.StatusText(http.StatusServiceUnavailable), struct{}{})
return true
}
// user requests to return standard HTTP code, if e.ToHTTPCode() not match, will return of 500
if e.needHTTPCode {
msg := strings.ReplaceAll(e.msg, ToHTTPCodeLabel, "")
resp.response(c, e.ToHTTPCode(), e.code, msg, struct{}{})
return true
}
// user defined error code to http
if resp.isUserDefinedHTTPErrorCode(c, e.Code()) {
return true
}
// response 200
resp.response(c, http.StatusOK, e.code, e.msg, struct{}{})
return false
}
func (resp *defaultResponse) isUserDefinedRPCErrorCode(c *gin.Context, errCode int) bool {
if v, ok := resp.rpcStatus[errCode]; ok {
httpCode := ToHTTPErr(v.status).ToHTTPCode()
msg := http.StatusText(httpCode)
if msg == "" {
msg = "unknown error"
}
resp.response(c, httpCode, httpCode, msg, struct{}{})
return true
}
return false
}
func (resp *defaultResponse) isUserDefinedHTTPErrorCode(c *gin.Context, errCode int) bool {
if v, ok := resp.httpErrors[errCode]; ok {
httpCode := v.ToHTTPCode()
msg := http.StatusText(httpCode)
if msg == "" {
msg = "unknown error"
}
resp.response(c, httpCode, httpCode, msg, struct{}{})
return true
}
return false
}
// ToHTTPErr converted to http error
func ToHTTPErr(st *status.Status) *Error { //nolint
switch st.Code() {
case StatusSuccess.status.Code(), codes.OK:
return Success
case StatusInvalidParams.status.Code(), codes.InvalidArgument:
return InvalidParams
case StatusInternalServerError.status.Code(), codes.Internal:
return InternalServerError
case StatusUnimplemented.status.Code(), codes.Unimplemented:
return Unimplemented
case StatusPermissionDenied.status.Code(), codes.PermissionDenied:
return PermissionDenied
}
switch st.Code() {
case StatusCanceled.status.Code(), codes.Canceled:
return Canceled
case StatusUnknown.status.Code(), codes.Unknown:
return Unknown
case StatusDeadlineExceeded.status.Code(), codes.DeadlineExceeded:
return DeadlineExceeded
case StatusNotFound.status.Code(), codes.NotFound:
return NotFound
case StatusAlreadyExists.status.Code(), codes.AlreadyExists, StatusConflict.status.Code():
return Conflict
case StatusResourceExhausted.status.Code(), codes.ResourceExhausted:
return ResourceExhausted
case StatusFailedPrecondition.status.Code(), codes.FailedPrecondition:
return FailedPrecondition
case StatusAborted.status.Code(), codes.Aborted:
return Aborted
case StatusOutOfRange.status.Code(), codes.OutOfRange:
return OutOfRange
case StatusServiceUnavailable.status.Code(), codes.Unavailable:
return ServiceUnavailable
case StatusDataLoss.status.Code(), codes.DataLoss:
return DataLoss
case StatusUnauthorized.status.Code(), codes.Unauthenticated:
return Unauthorized
case StatusAccessDenied.status.Code():
return AccessDenied
case StatusLimitExceed.status.Code():
return LimitExceed
case StatusMethodNotAllowed.status.Code():
return MethodNotAllowed
}
return &Error{
code: int(st.Code()),
msg: st.Message(),
}
}
func parseCodeAndMsg(errStr string) (int, string) {
if errStr != "" {
ss := strings.Split(errStr, "desc = ")
cm := strings.Split(ss[len(ss)-1], "msg = ")
if len(cm) == 2 {
codeStr := strings.ReplaceAll(cm[0], "code = ", "")
codeStr = strings.ReplaceAll(codeStr, ", ", "")
code, _ := strconv.Atoi(codeStr)
msg := cm[1]
return code, msg
}
}
return -1, errStr
}
package errcode
import (
"encoding/json"
"fmt"
"net/http"
"sort"
"strings"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
var grpcErrCodes = map[int]string{}
// RPCStatus rpc status
type RPCStatus struct {
status *status.Status
}
var statusCodes = map[codes.Code]string{}
// NewRPCStatus create a new rpc status
func NewRPCStatus(code codes.Code, msg string) *RPCStatus {
if v, ok := statusCodes[code]; ok {
panic(fmt.Sprintf(`grpc status code = %d already exists, please define a new error code,
msg1 = %s
msg2 = %s
`, code, v, msg))
}
grpcErrCodes[int(code)] = msg
statusCodes[code] = msg
return &RPCStatus{
status: status.New(code, msg),
}
}
// Detail error details
type Detail struct {
key string
val interface{}
}
// String detail key-value
func (d *Detail) String() string {
return fmt.Sprintf("%s: %v", d.key, d.val)
}
// Any type key value
func Any(key string, val interface{}) Detail {
return Detail{
key: key,
val: val,
}
}
// Code get code
func (s *RPCStatus) Code() codes.Code {
return s.status.Code()
}
// Msg get message
func (s *RPCStatus) Msg() string {
return s.status.Message()
}
// Err return error
// if there is a parameter 'desc', it will replace the original message
func (s *RPCStatus) Err(desc ...string) error {
if len(desc) > 0 {
return status.Errorf(s.status.Code(), "%s", strings.Join(desc, ", "))
}
return status.Errorf(s.status.Code(), "%s", s.status.Message())
}
// ErrToHTTP convert to standard error add ToHTTPCodeLabel to error message,
// usually used when HTTP calls the GRPC API,
// if there is a parameter 'desc', it will replace the original message.
func (s *RPCStatus) ErrToHTTP(desc ...string) error {
message := s.status.Message()
if len(desc) > 0 {
message = strings.Join(desc, ", ")
}
return status.Errorf(s.status.Code(), "%s%s", message, ToHTTPCodeLabel)
}
// ToRPCErr converted to standard RPC error,
// use it if you need to convert to standard RPC errors,
// if there is a parameter 'desc', it will replace the original message.
func (s *RPCStatus) ToRPCErr(desc ...string) error {
switch s.status.Code() {
case StatusInvalidParams.status.Code():
return toRPCErr(codes.InvalidArgument, desc...)
case StatusInternalServerError.status.Code():
return toRPCErr(codes.Internal, desc...)
}
switch s.status.Code() {
case StatusCanceled.status.Code():
return toRPCErr(codes.Canceled, desc...)
case StatusUnknown.status.Code():
return toRPCErr(codes.Unknown, desc...)
case StatusDeadlineExceeded.status.Code():
return toRPCErr(codes.DeadlineExceeded, desc...)
case StatusNotFound.status.Code():
return toRPCErr(codes.NotFound, desc...)
case StatusAlreadyExists.status.Code(), StatusConflict.status.Code():
return toRPCErr(codes.AlreadyExists, desc...)
case StatusPermissionDenied.status.Code():
return toRPCErr(codes.PermissionDenied, desc...)
case StatusResourceExhausted.status.Code():
return toRPCErr(codes.ResourceExhausted, desc...)
case StatusFailedPrecondition.status.Code():
return toRPCErr(codes.FailedPrecondition, desc...)
case StatusAborted.status.Code():
return toRPCErr(codes.Aborted, desc...)
case StatusOutOfRange.status.Code():
return toRPCErr(codes.OutOfRange, desc...)
case StatusUnimplemented.status.Code():
return toRPCErr(codes.Unimplemented, desc...)
case StatusServiceUnavailable.status.Code():
return toRPCErr(codes.Unavailable, desc...)
case StatusDataLoss.status.Code():
return toRPCErr(codes.DataLoss, desc...)
case StatusUnauthorized.status.Code():
return toRPCErr(codes.Unauthenticated, desc...)
case StatusAccessDenied.status.Code():
return toRPCErr(codes.PermissionDenied, desc...)
case StatusLimitExceed.status.Code():
return toRPCErr(codes.ResourceExhausted, desc...)
case StatusMethodNotAllowed.status.Code():
return toRPCErr(codes.Unimplemented, desc...)
}
return s.status.Err()
}
func toRPCErr(code codes.Code, descs ...string) error {
var desc string
if len(descs) > 0 {
desc = strings.Join(descs, ", ")
} else {
desc = code.String()
}
return status.New(code, desc).Err()
}
// ToRPCCode converted to standard RPC error code
func (s *RPCStatus) ToRPCCode() codes.Code {
switch s.status.Code() {
case StatusInvalidParams.status.Code():
return codes.InvalidArgument
case StatusInternalServerError.status.Code():
return codes.Internal
case StatusUnimplemented.status.Code():
return codes.Unimplemented
}
switch s.status.Code() {
case StatusPermissionDenied.status.Code():
return codes.PermissionDenied
case StatusCanceled.status.Code():
return codes.Canceled
case StatusUnknown.status.Code():
return codes.Unknown
case StatusDeadlineExceeded.status.Code():
return codes.DeadlineExceeded
case StatusNotFound.status.Code():
return codes.NotFound
case StatusAlreadyExists.status.Code(), StatusConflict.status.Code():
return codes.AlreadyExists
case StatusResourceExhausted.status.Code():
return codes.ResourceExhausted
case StatusFailedPrecondition.status.Code():
return codes.FailedPrecondition
case StatusAborted.status.Code():
return codes.Aborted
case StatusOutOfRange.status.Code():
return codes.OutOfRange
case StatusServiceUnavailable.status.Code():
return codes.Unavailable
case StatusDataLoss.status.Code():
return codes.DataLoss
case StatusUnauthorized.status.Code():
return codes.Unauthenticated
case StatusAccessDenied.status.Code():
return codes.PermissionDenied
case StatusLimitExceed.status.Code():
return codes.ResourceExhausted
case StatusMethodNotAllowed.status.Code():
return codes.Unimplemented
}
return s.status.Code()
}
// converted grpc code to http code
func convertToHTTPCode(code codes.Code) int {
switch code {
case StatusSuccess.status.Code():
return http.StatusOK
case codes.InvalidArgument, StatusInvalidParams.status.Code():
return http.StatusBadRequest
case codes.Internal, StatusInternalServerError.status.Code():
return http.StatusInternalServerError
case codes.Unimplemented, StatusUnimplemented.status.Code():
return http.StatusNotImplemented
case codes.NotFound, StatusNotFound.status.Code():
return http.StatusNotFound
case StatusForbidden.status.Code(), StatusAccessDenied.status.Code():
return http.StatusForbidden
}
switch code {
case StatusTimeout.status.Code():
return http.StatusRequestTimeout
case StatusTooManyRequests.status.Code(), StatusLimitExceed.status.Code():
return http.StatusTooManyRequests
case codes.FailedPrecondition, StatusFailedPrecondition.status.Code():
return http.StatusPreconditionFailed
case codes.Unavailable, StatusServiceUnavailable.status.Code():
return http.StatusServiceUnavailable
case codes.Unauthenticated, StatusUnauthorized.status.Code():
return http.StatusUnauthorized
case codes.PermissionDenied, StatusPermissionDenied.status.Code():
return http.StatusUnauthorized
case StatusLimitExceed.status.Code():
return http.StatusTooManyRequests
case StatusMethodNotAllowed.status.Code():
return http.StatusMethodNotAllowed
case StatusConflict.status.Code():
return http.StatusConflict
}
return http.StatusInternalServerError
}
// GetStatusCode get status code from error returned by RPC invoke
func GetStatusCode(err error) codes.Code {
st, _ := status.FromError(err)
return st.Code()
}
// ErrInfo error info
type ErrInfo struct {
Code int `json:"code"`
Msg string `json:"msg"`
}
func getErrorInfo(codeInfo map[int]string) []ErrInfo {
var keys []int
for key := range codeInfo {
keys = append(keys, key)
}
sort.Ints(keys)
eis := []ErrInfo{}
for _, key := range keys {
eis = append(eis, ErrInfo{
Code: key,
Msg: codeInfo[key],
})
}
return eis
}
// ListGRPCErrCodes list grpc error codes, http handle func
func ListGRPCErrCodes(w http.ResponseWriter, _ *http.Request) {
eis := getErrorInfo(grpcErrCodes)
jsonData, err := json.Marshal(&eis)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
_, err = w.Write(jsonData)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
// ShowConfig show config info
// @Summary show config info
// @Description show config info
// @Tags system
// @Accept json
// @Produce json
// @Router /config [get]
func ShowConfig(jsonData []byte) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
//w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
_, err := w.Write(jsonData)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
}
package errcode
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"google.golang.org/grpc/status"
)
var rpcStatus = []*RPCStatus{
StatusSuccess,
StatusCanceled,
StatusUnknown,
StatusInvalidParams,
StatusDeadlineExceeded,
StatusNotFound,
StatusAlreadyExists,
StatusPermissionDenied,
StatusResourceExhausted,
StatusFailedPrecondition,
StatusAborted,
StatusOutOfRange,
StatusUnimplemented,
StatusInternalServerError,
StatusServiceUnavailable,
StatusDataLoss,
StatusUnauthorized,
StatusTimeout,
StatusTooManyRequests,
StatusForbidden,
StatusLimitExceed,
StatusMethodNotAllowed,
StatusAccessDenied,
StatusConflict,
}
func TestRPCStatus(t *testing.T) {
st := NewRPCStatus(401101, "something is wrong")
err := st.Err()
assert.Error(t, err)
err = st.Err("another thing is wrong")
s, ok := status.FromError(err)
assert.True(t, ok)
assert.Equal(t, s.Code(), st.Code())
assert.Equal(t, s.Message(), "another thing is wrong")
code := st.Code()
assert.Equal(t, int(code), 401101)
msg := st.Msg()
assert.Equal(t, msg, "something is wrong")
defer func() {
if e := recover(); e != nil {
t.Log(e)
}
}()
NewRPCStatus(401101, "something is wrong 2")
}
func TestToRPCCode(t *testing.T) {
var codes []string
for _, s := range rpcStatus {
codes = append(codes, s.ToRPCCode().String())
}
t.Log(codes)
var errors []error
for i, s := range rpcStatus {
if i%2 == 0 {
errors = append(errors, s.ToRPCErr())
continue
}
errors = append(errors, s.ToRPCErr(s.status.Message()))
}
t.Log(errors)
codeInt := []int{}
for _, s := range rpcStatus {
codeInt = append(codeInt, ToHTTPErr(s.status).code)
}
t.Log(codeInt)
}
func TestConvertToHTTPCode(t *testing.T) {
var codes []int
for _, s := range rpcStatus {
codes = append(codes, convertToHTTPCode(s.Code()))
}
t.Log(codes)
}
func TestGetStatusCode(t *testing.T) {
t.Log(GetStatusCode(fmt.Errorf("reason for error")))
for _, s := range rpcStatus {
t.Log(s.Code(), "|",
GetStatusCode(s.Err()),
GetStatusCode(s.Err("reason for error")), "|",
GetStatusCode(s.ToRPCErr()),
GetStatusCode(s.ToRPCErr("reason for error")), "|",
GetStatusCode(s.ErrToHTTP()),
GetStatusCode(s.ErrToHTTP("reason for error")),
)
}
}
func TestRCode(t *testing.T) {
code := RCode(1)
t.Log("error code is", int(code))
defer func() {
recover()
}()
code = RCode(1001)
t.Log("error code is", int(code))
}
package errcode
import "google.golang.org/grpc/codes"
// RCode Generate an error code between 400000 and 500000 according to the number
//
// rpc service level error code, status prefix, example.
//
// var (
// StatusUserCreate = NewRPCStatus(RCode(1)+1, "failed to create user") // 400101
// StatusUserDelete = NewRPCStatus(RCode(1)+2, "failed to delete user") // 400102
// StatusUserUpdate = NewRPCStatus(RCode(1)+3, "failed to update user") // 400103
// StatusUserGet = NewRPCStatus(RCode(1)+4, "failed to get user details") // 400104
// )
func RCode(num int) codes.Code {
if num > 999 || num < 1 {
panic("NO range must be between 0 to 1000")
}
return codes.Code(400000 + num*100)
}
package errcode
// rpc system level error code with status prefix, error code range 30000~40000
var (
StatusSuccess = NewRPCStatus(0, "OK")
StatusCanceled = NewRPCStatus(300001, "Canceled")
StatusUnknown = NewRPCStatus(300002, "Unknown")
StatusInvalidParams = NewRPCStatus(300003, "Invalid Parameter")
StatusDeadlineExceeded = NewRPCStatus(300004, "Deadline Exceeded")
StatusNotFound = NewRPCStatus(300005, "Not Found")
StatusAlreadyExists = NewRPCStatus(300006, "Already Exists")
StatusPermissionDenied = NewRPCStatus(300007, "Permission Denied")
StatusResourceExhausted = NewRPCStatus(300008, "Resource Exhausted")
StatusFailedPrecondition = NewRPCStatus(300009, "Failed Precondition")
StatusAborted = NewRPCStatus(300010, "Aborted")
StatusOutOfRange = NewRPCStatus(300011, "Out Of Range")
StatusUnimplemented = NewRPCStatus(300012, "Unimplemented")
StatusInternalServerError = NewRPCStatus(300013, "Internal Server Error")
StatusServiceUnavailable = NewRPCStatus(300014, "Service Unavailable")
StatusDataLoss = NewRPCStatus(300015, "Data Loss")
StatusUnauthorized = NewRPCStatus(300016, "Unauthorized")
StatusTimeout = NewRPCStatus(300017, "Request Timeout")
StatusTooManyRequests = NewRPCStatus(300018, "Too Many Requests")
StatusForbidden = NewRPCStatus(300019, "Forbidden")
StatusLimitExceed = NewRPCStatus(300020, "Limit Exceed")
StatusMethodNotAllowed = NewRPCStatus(300021, "Method Not Allowed")
StatusAccessDenied = NewRPCStatus(300022, "Access Denied")
StatusConflict = NewRPCStatus(300023, "Conflict")
)
package eventbus
import (
"context"
"fmt"
"reflect"
"sync"
)
type BusSubscriber interface {
Subscribe(topic string, fn interface{}) error
SubscribeAsync(topic string, fn interface{}, transactional bool) error
SubscribeOnce(topic string, fn interface{}) error
SubscribeOnceAsync(topic string, fn interface{}) error
Unsubscribe(topic string, handler interface{}) error
}
type BusPublisher interface {
Publish(ctx context.Context, topic string, args ...interface{})
}
type BusController interface {
HasCallback(topic string) bool
WaitAsync()
}
type Bus interface {
BusController
BusSubscriber
BusPublisher
}
type EventBus struct {
handlers map[string][]*eventHandler
lock sync.Mutex
wg sync.WaitGroup
}
type eventHandler struct {
callBack reflect.Value
flagOnce bool
async bool
transactional bool
sync.Mutex
}
func New() Bus {
b := &EventBus{
make(map[string][]*eventHandler),
sync.Mutex{},
sync.WaitGroup{},
}
return Bus(b)
}
func (bus *EventBus) doSubscribe(topic string, fn interface{}, handler *eventHandler) error {
bus.lock.Lock()
defer bus.lock.Unlock()
if !(reflect.TypeOf(fn).Kind() == reflect.Func) {
return fmt.Errorf("%s is not of type reflect.Func", reflect.TypeOf(fn).Kind())
}
bus.handlers[topic] = append(bus.handlers[topic], handler)
return nil
}
func (bus *EventBus) Subscribe(topic string, fn interface{}) error {
return bus.doSubscribe(topic, fn, &eventHandler{
reflect.ValueOf(fn), false, false, false, sync.Mutex{},
})
}
func (bus *EventBus) SubscribeAsync(topic string, fn interface{}, transactional bool) error {
return bus.doSubscribe(topic, fn, &eventHandler{
reflect.ValueOf(fn), false, true, transactional, sync.Mutex{},
})
}
func (bus *EventBus) SubscribeOnce(topic string, fn interface{}) error {
return bus.doSubscribe(topic, fn, &eventHandler{
reflect.ValueOf(fn), true, false, false, sync.Mutex{},
})
}
func (bus *EventBus) SubscribeOnceAsync(topic string, fn interface{}) error {
return bus.doSubscribe(topic, fn, &eventHandler{
reflect.ValueOf(fn), true, true, false, sync.Mutex{},
})
}
func (bus *EventBus) HasCallback(topic string) bool {
bus.lock.Lock()
defer bus.lock.Unlock()
_, ok := bus.handlers[topic]
if ok {
return len(bus.handlers[topic]) > 0
}
return false
}
func (bus *EventBus) Unsubscribe(topic string, handler interface{}) error {
bus.lock.Lock()
defer bus.lock.Unlock()
if _, ok := bus.handlers[topic]; ok && len(bus.handlers[topic]) > 0 {
bus.removeHandler(topic, bus.findHandlerIdx(topic, reflect.ValueOf(handler)))
return nil
}
return fmt.Errorf("topic %s doesn't exist", topic)
}
func (bus *EventBus) Publish(ctx context.Context, topic string, args ...interface{}) {
bus.lock.Lock()
defer bus.lock.Unlock()
if handlers, ok := bus.handlers[topic]; ok && 0 < len(handlers) {
copyHandlers := make([]*eventHandler, len(handlers))
copy(copyHandlers, handlers)
for i, handler := range copyHandlers {
if handler.flagOnce {
bus.removeHandler(topic, i)
}
if !handler.async {
bus.doPublish(ctx, handler, args...)
} else {
bus.wg.Add(1)
if handler.transactional {
bus.lock.Unlock()
handler.Lock()
bus.lock.Lock()
}
go func() {
bus.doPublishAsync(ctx, handler, args...)
}()
}
}
}
}
func (bus *EventBus) doPublish(ctx context.Context, handler *eventHandler, args ...interface{}) {
allArgs := make([]any, 0, len(args)+1)
allArgs = append(allArgs, ctx)
allArgs = append(allArgs, args...)
passedArguments := bus.setUpPublish(handler, allArgs...)
handler.callBack.Call(passedArguments)
}
func (bus *EventBus) doPublishAsync(ctx context.Context, handler *eventHandler, args ...interface{}) {
defer bus.wg.Done()
if handler.transactional {
defer handler.Unlock()
}
bus.doPublish(ctx, handler, args...)
}
func (bus *EventBus) removeHandler(topic string, idx int) {
if _, ok := bus.handlers[topic]; !ok {
return
}
l := len(bus.handlers[topic])
if !(0 <= idx && idx < l) {
return
}
copy(bus.handlers[topic][idx:], bus.handlers[topic][idx+1:])
bus.handlers[topic][l-1] = nil
bus.handlers[topic] = bus.handlers[topic][:l-1]
}
func (bus *EventBus) findHandlerIdx(topic string, callback reflect.Value) int {
if _, ok := bus.handlers[topic]; ok {
for idx, handler := range bus.handlers[topic] {
if handler.callBack.Type() == callback.Type() &&
handler.callBack.Pointer() == callback.Pointer() {
return idx
}
}
}
return -1
}
func (bus *EventBus) setUpPublish(callback *eventHandler, args ...interface{}) []reflect.Value {
funcType := callback.callBack.Type()
passedArguments := make([]reflect.Value, len(args))
for i, v := range args {
if v == nil {
passedArguments[i] = reflect.New(funcType.In(i)).Elem()
} else {
passedArguments[i] = reflect.ValueOf(v)
}
}
return passedArguments
}
func (bus *EventBus) WaitAsync() {
bus.wg.Wait()
}
package eventbus
var Eb = New()
const (
TopicParseConf string = "event:application:conf:init" //
TopicParseConfFinish string = "event:application:conf:init:finish" //
TopicConfChange string = "event:application:conf:change" //
TopicLoggerInit string = "event:application:logger:init" //
TopicLoggerInitFinish string = "event:application:logger:init:finish" //
TopicDBInit string = "event:application:database:init" //
TopicDBInitFinish string = "event:application:database:init:finish" //
TopicCacheInit string = "event:application:cache:init" //
TopicCacheInitFinish string = "event:application:cache:init:finish" //
TopicMiddlewareInit string = "event:application:middleware:init" //
TopicMiddlewareInitFinish string = "event:application:middleware:init:finish" //
TopicCronInit string = "event:application:cron:init" //
TopicCronInitFinish string = "event:application:cron:init:finish" //
TopicRdInit string = "event:application:rd:init" //
TopicRdInitFinish string = "event:application:rd:init:finish" //
TopicGinEngineCreated string = "event:application:gin:engine:created" //
TopicPrintSwagger string = "event:application:swagger:print" //
TopicApplicationClose string = "event:application:close" //
)
...@@ -296,7 +296,7 @@ func (x *Request) send(ctx context.Context) { ...@@ -296,7 +296,7 @@ func (x *Request) send(ctx context.Context) {
return return
} }
if x.reqBody != nil { if x.reqBody != nil {
if c := ctxutils.CtxGin(ctx); c != nil { if c := ctxutils.Gin(ctx); c != nil {
entity.SetCopyApiReq(c, bodyBuf) entity.SetCopyApiReq(c, bodyBuf)
} }
} }
...@@ -408,7 +408,7 @@ func (x *Request) pushDo(ctx context.Context) { ...@@ -408,7 +408,7 @@ func (x *Request) pushDo(ctx context.Context) {
return return
} }
if len(x.response.body) > 0 { if len(x.response.body) > 0 {
if c := ctxutils.CtxGin(ctx); c != nil { if c := ctxutils.Gin(ctx); c != nil {
entity.SetCopyApiRsp(c, x.response.BodyBuf()) entity.SetCopyApiRsp(c, x.response.BodyBuf())
} }
} }
......
package ips
import (
"encoding/json"
"fmt"
"io"
"net"
"net/http"
"strings"
"github.com/gin-gonic/gin"
)
// get ip of client from context
func GetClientIP(c *gin.Context) string {
ip := c.Request.Header.Get("X-Forwarded-For")
if strings.Contains(ip, "127.0.0.1") || ip == "" {
ip = c.Request.Header.Get("X-real-ip")
}
if ip == "" {
ip = "127.0.0.1"
}
RemoteIP := c.RemoteIP()
if RemoteIP != "127.0.0.1" {
ip = RemoteIP
}
ClientIP := c.ClientIP()
if ClientIP != "127.0.0.1" {
ip = ClientIP
}
return ip
}
type IPLocation struct {
Code int `json:"code"` //返回码 200成功
Msg string `json:"msg"` //返回消息
Data IPLocationData `json:"data"`
}
type IPLocationData struct {
AreaCode string `json:"area_code"` //: "320311",
Province string `json:"province"` //省: "江苏",
City string `json:"city"` //: "徐州",
District string `json:"district"` //: "丰县",
CityCode string `json:"city_code"` //: "0516",
Continent string `json:"continent"` //: "亚洲",
Country string `json:"country"` //: "中国",
CountryCode string `json:"country_code"` //: "CN",
CountryEnglish string `json:"country_english"` //: "",
Elevation string `json:"elevation"` //: "40",
Ip string `json:"ip"` //: "114.234.76.140",
Isp string `json:"isp"` //: "电信",
Latitude string `json:"latitude"` //: "34.227883",
LocalTime string `json:"local_time"` //: "2023-08-02 14:36",
Longitude string `json:"longitude"` //: "117.213995",
MultiStreet []Street `json:"multi_street"`
Street string `json:"street"` //: "解放路168号",
Version string `json:"version"` //: "V4",
WeatherStation string `json:"weather_station"` //: "CHXX0437",
ZipCode string `json:"zip_code"` //: "221006"
}
type Street struct {
Lng string `json:"lng"` //经度: "116.60833",
Lat string `json:"lat"` //纬度: "34.701533",
Province string `json:"province"` //省: "江苏",
City string `json:"city"` //: "徐州",
District string `json:"district"` //: "丰县",
Street string `json:"street"` //: "解放路168号",
StreetNumber string `json:"street_number"` //: "解放路168号"
}
// func GetLocationByIp(secretKey, ip string, location *IPLocationData) error {
// url := "https://api.ipdatacloud.com/v2/query?ip=" + ip + "&key=" + secretKey
// client := https.HTTPClient{}
// data, err := client.Get(url)
// if err != nil {
// return err
// }
// var ipd IPLocation
// if err := json.Unmarshal(data, &ipd); err != nil {
// return err
// }
// if ipd.Code != 200 {
// return errors.New(fmt.Sprintf("获取出错 code:{%d} msg{%s}", ipd.Code, ipd.Msg))
// }
// *location = ipd.Data
// return nil
// }
// GetLocation 获取外网ip地址
func GetLocation(ip, key string) string {
if ip == "127.0.0.1" || ip == "localhost" {
return "内部IP"
}
url := "https://restapi.amap.com/v5/ip?ip=" + ip + "&type=4&key=" + key
fmt.Println("url", url)
resp, err := http.Get(url)
if err != nil {
fmt.Println("restapi.amap.com failed:", err)
return "未知位置"
}
defer resp.Body.Close()
s, err := io.ReadAll(resp.Body)
fmt.Println(string(s))
m := make(map[string]string)
err = json.Unmarshal(s, &m)
if err != nil {
fmt.Println("Umarshal failed:", err)
}
//if m["province"] == "" {
// return "未知位置"
//}
return m["country"] + "-" + m["province"] + "-" + m["city"] + "-" + m["district"] + "-" + m["isp"]
}
// GetLocalHost 获取局域网ip地址
func GetLocalHost() string {
netInterfaces, err := net.Interfaces()
if err != nil {
fmt.Println("net.Interfaces failed, err:", err.Error())
}
for i := 0; i < len(netInterfaces); i++ {
if (netInterfaces[i].Flags & net.FlagUp) != 0 {
addrs, _ := netInterfaces[i].Addrs()
for _, address := range addrs {
if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
if ipnet.IP.To4() != nil {
return ipnet.IP.String()
}
}
}
}
}
return ""
}
package oss
import (
"io"
"mime/multipart"
"gitlab.wanzhuangkj.com/tush/opkg/pkg/logger"
"gitlab.wanzhuangkj.com/tush/opkg/pkg/xerrors/xerror"
"github.com/aliyun/aliyun-oss-go-sdk/oss"
)
type AliyunOSS struct{}
var OSS = &AliyunOSS{}
type AliOssConfig struct {
Endpoint string `mapstructure:"endpoint" json:"endpoint" yaml:"endpoint"`
AccessKeyId string `mapstructure:"access-key-id" json:"access-key-id" yaml:"access-key-id"`
AccessKeySecret string `mapstructure:"access-key-secret" json:"access-key-secret" yaml:"access-key-secret"`
BucketName string `mapstructure:"bucket-name" json:"bucket-name" yaml:"bucket-name"`
BasePath string `mapstructure:"base-path" json:"base-path" yaml:"base-path"`
BucketUrl string `mapstructure:"bucket-url" json:"bucket-url" yaml:"bucket-url"`
Region string `mapstructure:"region" json:"region" yaml:"region"`
}
func NewClient(cfg *AliOssConfig) (bucket *oss.Bucket, err error) {
client, err := oss.New(cfg.Endpoint, cfg.AccessKeyId, cfg.AccessKeySecret)
if err != nil {
return nil, xerror.New(err.Error())
}
logger.Info("bucker name" + cfg.BucketName)
bucket, err = client.Bucket(cfg.BucketName)
return
}
func NewBucket(cfg *AliOssConfig) (bucket *oss.Bucket, err error) {
client, err := oss.New(cfg.Endpoint, cfg.AccessKeyId, cfg.AccessKeySecret)
if err != nil {
return
}
bucket, err = client.Bucket(cfg.BucketName)
return
}
func (*AliyunOSS) UploadFile(reader io.Reader, name string, cfg *AliOssConfig) (string, string, error) {
bucket, err := NewClient(cfg)
if err != nil {
return "", "", err
}
fileTmpPath := cfg.BasePath + "/" + name
err = bucket.PutObject(fileTmpPath, reader)
if err != nil {
return "", "", xerror.New(err.Error())
}
return cfg.BucketUrl + "/" + fileTmpPath, fileTmpPath, nil
}
type Attachment struct {
File io.Reader
Name string
AbsUrl string
RelativeUrl string
}
func (x Attachment) NewFromMultipartFileHeaders(files []*multipart.FileHeader) ([]*Attachment, error) {
if len(files) == 0 {
return nil, nil
}
attachments := make([]*Attachment, len(files))
for i := range files {
reader, err := files[i].Open()
if err != nil {
return nil, xerror.New(err.Error())
}
attachment := &Attachment{
File: reader,
Name: files[i].Filename,
}
attachments = append(attachments, attachment)
}
return attachments, nil
}
func (x *AliyunOSS) UploadMultipartFileHeaders(files []*multipart.FileHeader, cfg *AliOssConfig) ([]*Attachment, error) {
if len(files) == 0 {
return nil, nil
}
attachments, err := Attachment{}.NewFromMultipartFileHeaders(files)
if err != nil {
return nil, err
}
err = x.UploadFiles(&attachments, cfg)
if err != nil {
return nil, err
}
return attachments, nil
}
func (*AliyunOSS) UploadFiles(files *[]*Attachment, cfg *AliOssConfig) error {
if len(*files) == 0 {
return nil
}
bucket, err := NewClient(cfg)
if err != nil {
return xerror.New(err.Error())
}
for i, file := range *files {
relativeUri := cfg.BasePath + "/" + file.Name
err = bucket.PutObject(relativeUri, file.File)
if err != nil {
return xerror.New(err.Error())
}
(*files)[i].AbsUrl = cfg.BucketUrl + "/" + relativeUri
(*files)[i].RelativeUrl = relativeUri
}
return nil
}
package redlock
import (
"context"
"fmt"
"github.com/go-redsync/redsync/v4"
"github.com/go-redsync/redsync/v4/redis/goredis/v9"
"github.com/redis/go-redis/v9"
"gitlab.wanzhuangkj.com/tush/opkg/pkg/logger"
"gitlab.wanzhuangkj.com/tush/opkg/pkg/xerrors/xerror"
ctxutils "gitlab.wanzhuangkj.com/tush/opkg/xutils/ctxutils"
)
var (
redSync *redsync.Redsync
)
func Init(redisCli *redis.Client) {
pool := goredis.NewPool(redisCli)
redSync = redsync.New(pool)
}
func Sync(ctx context.Context, key string, fn func(), options ...redsync.Option) error {
if key == "" {
return xerror.New("[redSync]key is empty, please check")
}
if fn == nil {
return xerror.New("[redSync]fn is nil, please check")
}
if redSync == nil {
return xerror.New("[redSync]redSync is nil, please init first")
}
mutex := redSync.NewMutex(key, options...)
if err := mutex.LockContext(ctx); err != nil {
logger.Error("[redSync]try get lock fail", logger.Any("err", err), ctxutils.CtxTraceIDField(ctx))
return xerror.Wrap(err, fmt.Sprintf("[redSync][%s]get lock fail", key))
}
defer func() {
if _, err := mutex.UnlockContext(ctx); err != nil {
logger.Error("[redSync]release lock fail", logger.Any("err", err), ctxutils.CtxTraceIDField(ctx))
}
}()
fn()
return nil
}
...@@ -5,7 +5,7 @@ import ( ...@@ -5,7 +5,7 @@ import (
"strconv" "strconv"
"github.com/bwmarrin/snowflake" "github.com/bwmarrin/snowflake"
"github.com/duke-git/lancet/v2/xerror" "gitlab.wanzhuangkj.com/tush/opkg/pkg/xerrors/xerror"
) )
var ( var (
......
...@@ -6,7 +6,7 @@ import ( ...@@ -6,7 +6,7 @@ import (
"strings" "strings"
"gitlab.wanzhuangkj.com/tush/opkg/pkg/xerrors/xerror" "gitlab.wanzhuangkj.com/tush/opkg/pkg/xerrors/xerror"
"gitlab.wanzhuangkj.com/tush/opkg/xutils/xtime" "gitlab.wanzhuangkj.com/tush/opkg/pkg/xtime"
) )
const ( const (
......
package text
import (
"fmt"
)
// 前景 背景 颜色
// ---------------------------------------
// 30 40 黑色
// 31 41 红色
// 32 42 绿色
// 33 43 黄色
// 34 44 蓝色
// 35 45 紫红色
// 36 46 青蓝色
// 37 47 白色
//
// 代码 意义
// -------------------------
// 0 终端默认设置
// 1 高亮显示
// 4 使用下划线
// 5 闪烁
// 7 反白显示
// 8 不可见
const (
TextBlack = iota + 30
TextRed
TextGreen
TextYellow
TextBlue
TextMagenta
TextCyan
TextWhite
)
func Black(msg string) string {
return SetColor(msg, 0, 0, TextBlack)
}
func Red(msg string) string {
return SetColor(msg, 0, 0, TextRed)
}
func Green(msg string) string {
return SetColor(msg, 0, 0, TextGreen)
}
func Yellow(msg string) string {
return SetColor(msg, 0, 0, TextYellow)
}
func Blue(msg string) string {
return SetColor(msg, 0, 0, TextBlue)
}
func Magenta(msg string) string {
return SetColor(msg, 0, 0, TextMagenta)
}
func Cyan(msg string) string {
return SetColor(msg, 0, 0, TextCyan)
}
func White(msg string) string {
return SetColor(msg, 0, 0, TextWhite)
}
func SetColor(msg string, conf, bg, text int) string {
return fmt.Sprintf("%c[%d;%d;%dm%s%c[0m", 0x1B, conf, bg, text, msg, 0x1B)
}
package xset
type Key interface {
~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~float32 | ~float64 | ~string
}
type Set[T Key] struct {
m map[any]bool
}
func NewSet[T Key]() *Set[T] {
return &Set[T]{m: make(map[any]bool)}
}
func (s *Set[T]) Add(item any) {
if !s.Exists(item) {
s.m[item] = true
}
}
func (s *Set[T]) Remove(item any) {
delete(s.m, item)
}
func (s *Set[T]) Clear() {
s.m = make(map[any]bool)
}
func (s *Set[T]) Exists(item any) bool {
_, exists := s.m[item]
return exists
}
func (s *Set[T]) Size() int {
return len(s.m)
}
func (s *Set[T]) Slice() []T {
var list []T
for k := range s.m {
if v, ok := k.(T); ok {
list = append(list, v)
}
}
return list
}
...@@ -105,7 +105,7 @@ func WrapCtx(c *gin.Context) context.Context { ...@@ -105,7 +105,7 @@ func WrapCtx(c *gin.Context) context.Context {
return ctx return ctx
} }
func CtxGin(c context.Context) *gin.Context { func Gin(c context.Context) *gin.Context {
if str, ok := c.Value(GinContextKey).(*gin.Context); ok { if str, ok := c.Value(GinContextKey).(*gin.Context); ok {
return str return str
} }
...@@ -118,7 +118,7 @@ type IPagination interface { ...@@ -118,7 +118,7 @@ type IPagination interface {
} }
func SetPage(ctx context.Context, in IPagination) { func SetPage(ctx context.Context, in IPagination) {
if c := CtxGin(ctx); c != nil { if c := Gin(ctx); c != nil {
c.Set(KeyApiType, "page") c.Set(KeyApiType, "page")
pagination := query.Pagination{ pagination := query.Pagination{
PageIndex: in.GetPageIndex(), PageIndex: in.GetPageIndex(),
......
package goutils
import (
"context"
"runtime/debug"
"time"
"gitlab.wanzhuangkj.com/tush/opkg/pkg/logger"
"gitlab.wanzhuangkj.com/tush/opkg/pkg/xerrors/xerror"
"gitlab.wanzhuangkj.com/tush/opkg/xutils/ctxutils"
"golang.org/x/sync/errgroup"
)
func Recover() {
if r := recover(); r != nil {
logger.Fatalf("Recovered panic: %v\nStack trace:\n%s", r, string(debug.Stack()))
}
}
func RecoverCtx(ctx context.Context) {
if r := recover(); r != nil {
logger.Fatalf("Recovered panic: %v\nStack trace:\n%s", r, string(debug.Stack()), ctxutils.CtxTraceIDField(ctx))
}
}
func Go(ctx context.Context, f func()) {
go func() {
defer RecoverCtx(ctx)
f()
}()
}
type FnCtx func(ctx context.Context) error
func Do(ctx context.Context, fns ...FnCtx) error {
if len(fns) == 0 {
return nil
}
ctx, cancel := context.WithCancel(ctx)
defer cancel()
eg, ctx := errgroup.WithContext(ctx)
for _, fn := range fns {
eg.Go(func() error {
return fn(ctx)
})
}
if err := eg.Wait(); err != nil {
return xerror.New(err.Error())
}
return nil
}
func DoWithTimeout(timeout time.Duration, fns ...FnCtx) error {
if len(fns) == 0 {
return nil
}
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
eg, ctx := errgroup.WithContext(ctx)
for _, fn := range fns {
eg.Go(func() error {
return fn(ctx)
})
}
if err := eg.Wait(); err != nil {
return xerror.New(err.Error())
}
return nil
}
...@@ -9,7 +9,7 @@ import ( ...@@ -9,7 +9,7 @@ import (
"gitlab.wanzhuangkj.com/tush/opkg/pkg/httpcli" "gitlab.wanzhuangkj.com/tush/opkg/pkg/httpcli"
"gitlab.wanzhuangkj.com/tush/opkg/pkg/logger" "gitlab.wanzhuangkj.com/tush/opkg/pkg/logger"
"gitlab.wanzhuangkj.com/tush/opkg/xutils/sf" "gitlab.wanzhuangkj.com/tush/opkg/pkg/sf"
) )
type IDGeneration struct { type IDGeneration struct {
......
package jsonutils
import (
"bytes"
"encoding/json"
"gitlab.wanzhuangkj.com/tush/opkg/pkg/xerrors/xerror"
)
func Unmarshal(data []byte, v any) error {
err := json.Unmarshal(data, v)
if err != nil {
return xerror.New(err.Error())
}
return nil
}
func MarshalEscapeHTML(data interface{}) ([]byte, error) {
bf := bytes.NewBuffer([]byte{})
jsonEncoder := json.NewEncoder(bf)
jsonEncoder.SetEscapeHTML(false)
if err := jsonEncoder.Encode(data); err != nil {
return nil, xerror.Wrap(err, "json marshalEscapeHTML")
}
return bf.Bytes(), nil
}
func MarshalEscapeHTMLSilent(data interface{}) []byte {
d, _ := MarshalEscapeHTML(data)
return d
}
func Marshal(data any) ([]byte, error) {
if data == nil {
return nil, nil
}
d, err := json.Marshal(data)
if err != nil {
return nil, xerror.Wrap(err, "json marshal")
}
return d, nil
}
func MarshalSilent(data any) []byte {
d, _ := Marshal(data)
return d
}
func MarshalIndentString(data any) string {
d, _ := json.MarshalIndent(data, "", " ")
return string(d)
}
func ToJSONBytes(v any) []byte {
if v == nil {
return []byte{}
}
bytes, _ := json.Marshal(v)
return bytes
}
func ToJSONString(v interface{}) string {
return string(ToJSONBytes(v))
}
func ToJSONStringPtr(d interface{}) *string {
v := ToJSONString(d)
return &v
}
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论