diff --git a/.github/workflows/build-check.yml b/.github/workflows/build-check.yml new file mode 100644 index 0000000..d400bbb --- /dev/null +++ b/.github/workflows/build-check.yml @@ -0,0 +1,27 @@ +name: DockerBuildCheck + +on: + pull_request + +jobs: + build-base: + name: Build Base + runs-on: ubuntu-latest + steps: + - name: checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Build + uses: docker/build-push-action@v4 + with: + context: . + file: Dockerfile + push: false diff --git a/.github/workflows/depbot-auto-merge.yaml b/.github/workflows/depbot-auto-merge.yaml new file mode 100644 index 0000000..9351bfa --- /dev/null +++ b/.github/workflows/depbot-auto-merge.yaml @@ -0,0 +1,23 @@ +name: Dependabot auto-merge +on: pull_request + +permissions: + contents: write + pull-requests: write + +jobs: + dependabot: + runs-on: ubuntu-latest + if: ${{ github.actor == 'dependabot[bot]' }} + steps: + - name: Dependabot metadata + id: metadata + uses: dependabot/fetch-metadata@v1.1.1 + with: + github-token: "${{ secrets.GITHUB_TOKEN }}" + - name: Enable auto-merge for Dependabot PRs + if: ${{steps.metadata.outputs.update-type == 'version-update:semver-patch' || steps.metadata.outputs.update-type == 'version-update:semver-minor'}} + run: gh pr merge --auto --merge "$PR_URL" + env: + PR_URL: ${{github.event.pull_request.html_url}} + GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} \ No newline at end of file diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..11e1199 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,46 @@ +name: Release +on: + push: + branches: + - main + tags-ignore: + - '**' +# pull_request: +# branches: +# - '**' + workflow_dispatch: + +jobs: +# test: +# runs-on: ubuntu-22.04 +# strategy: +# matrix: +# go: ['1.17', '1.18', '1.19'] +# name: Go ${{ matrix.go }} test +# steps: +# - uses: actions/checkout@v3 +# - name: Setup go +# uses: actions/setup-go@v3 +# with: +# go-version: ${{ matrix.go }} +# - run: go test -race -v -coverprofile=profile.cov ./pkg/... +# - uses: codecov/codecov-action@v3.1.1 +# with: +# file: ./profile.cov +# name: codecov-go + release: + runs-on: ubuntu-22.04 +# needs: [test] +# if: ${{ github.event_name == 'workflow_dispatch' || github.ref == 'refs/heads/main' }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - name: Source checkout + uses: actions/checkout@v3 + - name: Semantic Release + uses: cycjimmy/semantic-release-action@v3 + with: + dry_run: false + semantic_version: 18.0.1 + extra_plugins: | + @semantic-release/exec@6.0.3 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 6f89a38..4b3330b 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,5 @@ *.log .idea *test.go -config/config.yaml +*config.yaml scripts/k8s/config/* \ No newline at end of file diff --git a/.releaserc.js b/.releaserc.js new file mode 100644 index 0000000..7fd503d --- /dev/null +++ b/.releaserc.js @@ -0,0 +1,8 @@ +module.exports = { + branches: ["main", {name: 'alpha', prerelease: true}, {name: 'beta', prerelease: true}], + plugins: [ + "@semantic-release/commit-analyzer", + "@semantic-release/release-notes-generator", + "@semantic-release/github" + ] +}; \ No newline at end of file diff --git a/cmd/config/config.go b/cmd/config/config.go index 8c12f70..77eed2d 100644 --- a/cmd/config/config.go +++ b/cmd/config/config.go @@ -1,23 +1,24 @@ package config import ( + "fmt" "github.com/spf13/cobra" - "supreme-flamego/config" "os" + "supreme-flamego/conf" ) var ( - configYml string - forceGen bool - StartCmd = &cobra.Command{ + configPath string + forceGen bool + StartCmd = &cobra.Command{ Use: "config", Short: "Generate config file", - Example: "app config -p config/config.yaml -f", + Example: "mod config -p ./config.yaml -f", Run: func(cmd *cobra.Command, args []string) { - println("Generate config...") + fmt.Println("Generating config...") err := load() if err != nil { - println(err.Error()) + fmt.Println(err.Error()) os.Exit(1) } }, @@ -25,10 +26,10 @@ var ( ) func init() { - StartCmd.PersistentFlags().StringVarP(&configYml, "path", "p", "config/config.yaml", "Generate config in provided path") + StartCmd.PersistentFlags().StringVarP(&configPath, "path", "p", "./config.yaml", "Generate config in provided path") StartCmd.PersistentFlags().BoolVarP(&forceGen, "force", "f", false, "Force generate config in provided path") } func load() error { - return config.GenConfig(configYml, forceGen) + return conf.GenYamlConfig(configPath, forceGen) } diff --git a/cmd/create/createApp.go b/cmd/create/createApp.go deleted file mode 100644 index 5b8af07..0000000 --- a/cmd/create/createApp.go +++ /dev/null @@ -1,142 +0,0 @@ -package create - -import ( - "bytes" - "github.com/pkg/errors" - "github.com/spf13/cobra" - "os" - "path" - "strings" - "supreme-flamego/pkg/colorful" - "supreme-flamego/pkg/fs" - "text/template" -) - -var ( - appName string - dir string - force bool - StartCmd = &cobra.Command{ - Use: "create", - Short: "create a new app", - Example: "app create -n users", - Run: func(cmd *cobra.Command, args []string) { - err := load() - if err != nil { - println(colorful.Red(err.Error())) - os.Exit(1) - } - println(colorful.Green("App " + appName + " generate success under " + dir)) - }, - } -) - -func init() { - StartCmd.PersistentFlags().StringVarP(&appName, "name", "n", "", "create a new app with provided name") - StartCmd.PersistentFlags().StringVarP(&dir, "path", "p", "internal/app", "new file will generate under provided path") - StartCmd.PersistentFlags().BoolVarP(&force, "force", "f", false, "Force generate the app") -} - -func load() error { - if appName == "" { - return errors.New("app name should not be empty, use -n") - } - - router := path.Join(dir, appName, "router") - handlerMain := path.Join(dir, appName, "handler", "v1") - handlerType := path.Join(dir, appName, "handler", "v1") - dto := path.Join(dir, appName, "dto") - e := path.Join(dir, appName, "e") - service := path.Join(dir, appName, "service") - model := path.Join(dir, appName, "model") - trigger := path.Join(dir, "routerInitialize") - - _ = fs.IsNotExistMkDir(router) - _ = fs.IsNotExistMkDir(handlerType) - _ = fs.IsNotExistMkDir(handlerType) - _ = fs.IsNotExistMkDir(e) - _ = fs.IsNotExistMkDir(dto) - _ = fs.IsNotExistMkDir(service) - _ = fs.IsNotExistMkDir(model) - _ = fs.IsNotExistMkDir(trigger) - - m := map[string]string{} - m["appNameExport"] = strings.ToUpper(appName[:1]) + appName[1:] - m["appName"] = strings.ToLower(appName[:1]) + appName[1:] - - router += "/" + m["appName"] + ".go" - service += "/" + m["appName"] + ".go" - model += "/" + m["appName"] + ".go" - handlerMain += "/" + m["appName"] + ".go" - handlerType += "/" + "type.go" - dto += "/" + m["appName"] + ".go" - trigger += "/" + m["appName"] + ".go" - e += "/" + m["appName"] + ".go" - - if !force && (fs.FileExist(router) || fs.FileExist(handlerMain) || fs.FileExist(handlerType) || - fs.FileExist(dto) || fs.FileExist(trigger)) || fs.FileExist(service) || fs.FileExist(model) { - return errors.New("target file already exist, use -f flag to cover") - } - - if rt, err := template.ParseFiles("template/router.template"); err != nil { - return err - } else { - var b bytes.Buffer - err = rt.Execute(&b, m) - fs.FileCreate(b, router) - } - - if rt, err := template.ParseFiles("template/handler.template"); err != nil { - return err - } else { - var b bytes.Buffer - err = rt.Execute(&b, m) - fs.FileCreate(b, handlerMain) - } - if rt, err := template.ParseFiles("template/type.template"); err != nil { - return err - } else { - var b bytes.Buffer - err = rt.Execute(&b, m) - fs.FileCreate(b, handlerType) - } - - if rt, err := template.ParseFiles("template/dto.template"); err != nil { - return err - } else { - var b bytes.Buffer - err = rt.Execute(&b, m) - fs.FileCreate(b, dto) - } - if rt, err := template.ParseFiles("template/e.template"); err != nil { - return err - } else { - var b bytes.Buffer - err = rt.Execute(&b, m) - fs.FileCreate(b, e) - } - if rt, err := template.ParseFiles("template/service.template"); err != nil { - return err - } else { - var b bytes.Buffer - err = rt.Execute(&b, m) - fs.FileCreate(b, service) - } - if rt, err := template.ParseFiles("template/model.template"); err != nil { - return err - } else { - var b bytes.Buffer - err = rt.Execute(&b, m) - fs.FileCreate(b, model) - } - - if rt, err := template.ParseFiles("template/trigger.template"); err != nil { - return err - } else { - var b bytes.Buffer - err = rt.Execute(&b, m) - fs.FileCreate(b, trigger) - } - - return nil -} diff --git a/cmd/create/createMod.go b/cmd/create/createMod.go new file mode 100644 index 0000000..7d84fa4 --- /dev/null +++ b/cmd/create/createMod.go @@ -0,0 +1,103 @@ +package create + +import ( + "bytes" + "fmt" + "github.com/pkg/errors" + "github.com/spf13/cobra" + "io/fs" + "log" + "os" + "path" + "supreme-flamego/internal/mod/example" + "supreme-flamego/pkg/colorful" +) + +var ( + appName string + dir string + force bool + StartCmd = &cobra.Command{ + Use: "create", + Short: "create a new mod", + Example: "mod create -n users", + Run: func(cmd *cobra.Command, args []string) { + err := load() + if err != nil { + fmt.Println(colorful.Red(err.Error())) + os.Exit(1) + } + fmt.Println(colorful.Green("Module " + appName + " generate success under " + dir)) + }, + } +) + +func init() { + StartCmd.PersistentFlags().StringVarP(&appName, "name", "n", "", "create a new mod with provided name") + StartCmd.PersistentFlags().StringVarP(&dir, "path", "p", "internal/mod", "new file will generate under provided path") + StartCmd.PersistentFlags().BoolVarP(&force, "force", "f", false, "Force generate the mod") +} + +func load() error { + if appName == "" { + return errors.New("mod name should not be empty, use -n") + } + + dirEntries, err := example.FS.ReadDir(".") + if err != nil { + return err + } + + err = cloneDir("", dirEntries) + if err != nil { + return err + } + + return nil +} + +// 克隆目录以及目录下文件 +func cloneDir(subPath string, entry []fs.DirEntry) error { + for _, dirEntry := range entry { + if dirEntry.IsDir() { + if _, err := os.Stat(path.Join(dir, appName, subPath, dirEntry.Name())); os.IsNotExist(err) { + if err = os.MkdirAll(path.Join(dir, appName, subPath, dirEntry.Name()), os.ModePerm); err != nil { + return err + } + } else if err != nil { + return err + } + subDir, _ := example.FS.ReadDir(path.Join(subPath, dirEntry.Name())) + if err := cloneDir(path.Join(subPath, dirEntry.Name()), subDir); err != nil { + return err + } + continue + } + if dirEntry.Name() == "embed.go" { + continue + } + file, err := example.FS.ReadFile(path.Join(subPath, dirEntry.Name())) + if err != nil { + return err + } + file = bytes.ReplaceAll(file, []byte("example"), []byte(appName)) + + // check if file exist + fp := path.Join(dir, appName, subPath, dirEntry.Name()) + _, err = os.Stat(fp) + if err == nil && !force { + return errors.New("file " + fp + " is existed, use -f to force generate") + } + + f, err := os.Create(fp) + if err != nil { + log.Println(err) + } + _, err = f.WriteString(bytes.NewBuffer(file).String()) + if err != nil { + log.Println(err) + } + _ = f.Close() + } + return nil +} diff --git a/cmd/init.go b/cmd/init.go index f7a10cc..3546fdc 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -2,17 +2,17 @@ package cmd import ( "github.com/spf13/cobra" + "os" "supreme-flamego/cmd/config" "supreme-flamego/cmd/create" "supreme-flamego/cmd/server" - "os" ) var rootCmd = &cobra.Command{ - Use: "app", - Short: "app", + Use: "mod", + Short: "mod", SilenceUsage: true, - Long: `app`, + Long: `mod`, } func init() { diff --git a/cmd/server/server.go b/cmd/server/server.go index 6aa2bf6..2d292de 100644 --- a/cmd/server/server.go +++ b/cmd/server/server.go @@ -1,112 +1,99 @@ package server import ( - "context" - "errors" "fmt" - sentryflame "github.com/asjdf/flamego-sentry" - "github.com/flamego/flamego" + "github.com/soheilhy/cmux" "github.com/spf13/cobra" "go.uber.org/zap/zapcore" - "net/http" + "net" "os" "os/signal" - "supreme-flamego/config" - "supreme-flamego/internal/app/routerInitialize" - "supreme-flamego/internal/cache" - "supreme-flamego/internal/database" - "supreme-flamego/internal/sentry" + "supreme-flamego/conf" + "supreme-flamego/core/kernel" + "supreme-flamego/core/logx" + "supreme-flamego/internal/mod/dbLite" + "supreme-flamego/internal/mod/example" + "supreme-flamego/internal/mod/flame" "supreme-flamego/pkg/colorful" "supreme-flamego/pkg/ip" - "supreme-flamego/pkg/logger" + "supreme-flamego/pkg/sentry" "syscall" - "time" ) +var log = logx.NameSpace("cmd.server") + var ( configYml string - e *flamego.Flame StartCmd = &cobra.Command{ Use: "server", Short: "Set Application config info", - Example: "main server -c config/settings.yml", - PreRun: func(cmd *cobra.Command, args []string) { - println("loading config...") - setUp() - println("loading config complete") - println("loading api...") - load() - println("loading api complete") - }, + Example: "main server -c ./config.yaml", Run: func(cmd *cobra.Command, args []string) { - println("starting Server...") - run() + log.Info("loading config...") + conf.LoadConfig(configYml) + log.Info("loading config complete") + + log.Info("init dep...") + if conf.GetConfig().SentryDsn != "" { + sentry.Init() + } + if conf.GetConfig().MODE == "" || conf.GetConfig().MODE == "debug" { + logx.Init(zapcore.DebugLevel) + } else { + logx.Init(zapcore.InfoLevel) + } + log.Info("init dep complete") + + log.Info("init kernel...") + conn, err := net.Listen("tcp", fmt.Sprintf(":%s", conf.GetConfig().Port)) + if err != nil { + log.Fatalw("failed to listen", "error", err) + } + tcpMux := cmux.New(conn) + log.Infow("start listening", "port", conf.GetConfig().Port) + k := kernel.New(kernel.Config{ + Listener: conn}) + k.Map(&tcpMux) + k.RegMod( + &example.Mod{}, + &flame.Mod{}, + &dbLite.Mod{}, + ) + k.Init() + log.Info("init kernel complete") + + log.Info("init module...") + err = k.StartModule() + if err != nil { + panic(err) + } + log.Info("init module complete") + + log.Info("starting Server...") + k.Serve() + go func() { + _ = tcpMux.Serve() + }() + + fmt.Println(colorful.Green("Server run at:")) + fmt.Println(fmt.Sprintf("- Local: http://localhost:%s", conf.GetConfig().Port)) + for _, host := range ip.GetLocalHost() { + fmt.Println(fmt.Sprintf("- Network: http://%s:%s", host, conf.GetConfig().Port)) + } + + quit := make(chan os.Signal, 1) + signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) + <-quit + fmt.Println(colorful.Blue("Shutting down server...")) + + err = k.Stop() + if err != nil { + panic(err) + } }, } ) func init() { - StartCmd.PersistentFlags().StringVarP(&configYml, "config", "c", "config/config.yaml", "Start server with provided configuration file") -} - -func setUp() { - // 顺序不能变 logger依赖config logger后面的同时依赖logger和config 否则crash - config.LoadConfig(configYml) - - if config.GetConfig().SentryDsn != "" { - sentry.Init() - } - - if config.GetConfig().MODE == "" || config.GetConfig().MODE == "debug" { - logger.Init(zapcore.DebugLevel) - } else { - logger.Init(zapcore.InfoLevel) - } - - database.InitDB() - cache.InitCache() -} - -func load() { - flamego.SetEnv(flamego.EnvType(config.GetConfig().MODE)) - e = flamego.New() - e.Use(flamego.Recovery()) - if config.GetConfig().SentryDsn != "" { - e.Use(sentryflame.New(sentryflame.Options{Repanic: true})) - } - - routerInitialize.ApiInit(e) -} - -func run() { - srv := &http.Server{ - Addr: config.GetConfig().Listen + ":" + config.GetConfig().Port, - Handler: e, - } - - go func() { - if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { - println(colorful.Red("Got Server Err: " + err.Error())) - } - }() - - println(colorful.Green("Server run at:")) - println(fmt.Sprintf("- Local: http://localhost:%s", config.GetConfig().Port)) - for _, host := range ip.GetLocalHost() { - println(fmt.Sprintf("- Network: http://%s:%s", host, config.GetConfig().Port)) - } - - quit := make(chan os.Signal, 1) - signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) - <-quit - println(colorful.Blue("Shutting down server...")) - - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - - if err := srv.Shutdown(ctx); err != nil { - println(colorful.Yellow("Server forced to shutdown: " + err.Error())) - } - - println(colorful.Green("Server exiting Correctly")) + //StartCmd.PersistentFlags().StringVarP(&configYml, "config", "c", "config/config.yaml", "Start server with provided configuration file") } diff --git a/conf/config.go b/conf/config.go new file mode 100644 index 0000000..4e179b4 --- /dev/null +++ b/conf/config.go @@ -0,0 +1,66 @@ +package conf + +import ( + "fmt" + "github.com/fsnotify/fsnotify" + "github.com/pkg/errors" + "github.com/spf13/viper" + "gopkg.in/yaml.v3" + "os" + "supreme-flamego/pkg/colorful" + "supreme-flamego/pkg/fsx" +) + +var SysVersion = "dev" + +var serveConfig *GlobalConfig + +func LoadConfig(configPath ...string) { + if len(configPath) == 0 || configPath[0] == "" { + viper.SetConfigName("config") + viper.AddConfigPath(".") + viper.AddConfigPath("./config") + } else { + viper.SetConfigFile(configPath[0]) + } + + serveConfig = new(GlobalConfig) + err := viper.ReadInConfig() + if err != nil { + fmt.Println("Config Read failed: " + err.Error()) + os.Exit(1) + } + err = viper.Unmarshal(serveConfig) + if err != nil { + fmt.Println("Config Unmarshal failed: " + err.Error()) + os.Exit(1) + } + viper.OnConfigChange(func(e fsnotify.Event) { + fmt.Println("Config fileHandle changed: ", e.Name) + _ = viper.ReadInConfig() + err = viper.Unmarshal(serveConfig) + if err != nil { + fmt.Println("New Config fileHandle Parse Failed: ", e.Name) + return + } + }) + viper.WatchConfig() +} + +func GenYamlConfig(path string, force bool) error { + if !fsx.FileExist(path) || force { + data, _ := yaml.Marshal(&GlobalConfig{MODE: "debug"}) + err := os.WriteFile(path, data, 0644) + if err != nil { + return errors.New(colorful.Red("Generate file with error: " + err.Error())) + } + fmt.Println(colorful.Green("Config file `config.yaml` generate success in " + path)) + } else { + return errors.New(colorful.Red(path + " already exist, use -f to Force coverage")) + } + return nil +} + +func GetConfig() *GlobalConfig { + return serveConfig +} diff --git a/config/vars.go b/conf/vars.go similarity index 56% rename from config/vars.go rename to conf/vars.go index 1b93666..2507a1e 100644 --- a/config/vars.go +++ b/conf/vars.go @@ -1,17 +1,13 @@ -package config +package conf type GlobalConfig struct { - MODE string `yaml:"Mode"` - ProgramName string `yaml:"ProgramName"` - BaseURL string `yaml:"BaseURL"` - AUTHOR string `yaml:"Author"` - Listen string `yaml:"Listen"` - Port string `yaml:"Port"` - LogPath string `yaml:"LogPath"` - Databases []Datasource `yaml:"Databases"` - Caches []Cache `yaml:"Caches"` - SentryDsn string `yaml:"SentryDsn"` - Auth struct { + MODE string `yaml:"Mode"` + Port string `yaml:"Port"` // grpc和http服务监听端口 + LogPath string `yaml:"LogPath"` + Databases []Datasource `yaml:"Databases"` + Caches []Cache `yaml:"Caches"` + SentryDsn string `yaml:"SentryDsn"` + Auth struct { Secret string `yaml:"Secret"` Issuer string `yaml:"Issuer"` } `yaml:"Auth"` diff --git a/config/config.go b/config/config.go deleted file mode 100644 index 433cac7..0000000 --- a/config/config.go +++ /dev/null @@ -1,62 +0,0 @@ -package config - -import ( - "github.com/fsnotify/fsnotify" - "github.com/pkg/errors" - "github.com/spf13/viper" - "gopkg.in/yaml.v3" - "supreme-flamego/pkg/colorful" - "supreme-flamego/pkg/fs" - "os" -) - -var SysVersion = "dev" - -var serveConfig *GlobalConfig - -func LoadConfig(configYml string) { - if !fs.FileExist(configYml) { - println("cannot find config file") - os.Exit(1) - } - serveConfig = new(GlobalConfig) - viper.SetConfigFile(configYml) - err := viper.ReadInConfig() - if err != nil { - println("Config Read failed: " + err.Error()) - os.Exit(1) - } - err = viper.Unmarshal(serveConfig) - if err != nil { - println("Config Unmarshal failed: " + err.Error()) - os.Exit(1) - } - viper.OnConfigChange(func(e fsnotify.Event) { - println("Config fileHandle changed: ", e.Name) - _ = viper.ReadInConfig() - err = viper.Unmarshal(serveConfig) - if err != nil { - println("New Config fileHandle Parse Failed: ", e.Name) - return - } - }) - viper.WatchConfig() -} - -func GenConfig(configYml string, force bool) error { - if !fs.FileExist(configYml) || force { - data, _ := yaml.Marshal(&GlobalConfig{MODE: "debug"}) - err := os.WriteFile(configYml, data, 0644) - if err != nil { - return errors.New(colorful.Red("Generate file with error: " + err.Error())) - } - println(colorful.Green("config file `config.yaml` generate success in " + configYml)) - } else { - return errors.New(colorful.Red(configYml + " already exist, use -f to Force coverage")) - } - return nil -} - -func GetConfig() *GlobalConfig { - return serveConfig -} diff --git a/core/color/color.go b/core/color/color.go new file mode 100644 index 0000000..6e892d2 --- /dev/null +++ b/core/color/color.go @@ -0,0 +1,73 @@ +package color + +import "github.com/fatih/color" + +const ( + // NoColor is no color for both foreground and background. + NoColor Color = iota + // FgBlack is the foreground color black. + FgBlack + // FgRed is the foreground color red. + FgRed + // FgGreen is the foreground color green. + FgGreen + // FgYellow is the foreground color yellow. + FgYellow + // FgBlue is the foreground color blue. + FgBlue + // FgMagenta is the foreground color magenta. + FgMagenta + // FgCyan is the foreground color cyan. + FgCyan + // FgWhite is the foreground color white. + FgWhite + + // BgBlack is the background color black. + BgBlack + // BgRed is the background color red. + BgRed + // BgGreen is the background color green. + BgGreen + // BgYellow is the background color yellow. + BgYellow + // BgBlue is the background color blue. + BgBlue + // BgMagenta is the background color magenta. + BgMagenta + // BgCyan is the background color cyan. + BgCyan + // BgWhite is the background color white. + BgWhite +) + +var colors = map[Color][]color.Attribute{ + FgBlack: {color.FgBlack, color.Bold}, + FgRed: {color.FgRed, color.Bold}, + FgGreen: {color.FgGreen, color.Bold}, + FgYellow: {color.FgYellow, color.Bold}, + FgBlue: {color.FgBlue, color.Bold}, + FgMagenta: {color.FgMagenta, color.Bold}, + FgCyan: {color.FgCyan, color.Bold}, + FgWhite: {color.FgWhite, color.Bold}, + BgBlack: {color.BgBlack, color.FgHiWhite, color.Bold}, + BgRed: {color.BgRed, color.FgHiWhite, color.Bold}, + BgGreen: {color.BgGreen, color.FgHiWhite, color.Bold}, + BgYellow: {color.BgHiYellow, color.FgHiBlack, color.Bold}, + BgBlue: {color.BgBlue, color.FgHiWhite, color.Bold}, + BgMagenta: {color.BgMagenta, color.FgHiWhite, color.Bold}, + BgCyan: {color.BgCyan, color.FgHiWhite, color.Bold}, + BgWhite: {color.BgHiWhite, color.FgHiBlack, color.Bold}, +} + +type Color uint32 + +// WithColor returns a string with the given color applied. +func WithColor(text string, colour Color) string { + c := color.New(colors[colour]...) + return c.Sprint(text) +} + +// WithColorPadding returns a string with the given color applied with leading and trailing spaces. +func WithColorPadding(text string, colour Color) string { + return WithColor(" "+text+" ", colour) +} diff --git a/core/contextx/unmarshaler.go b/core/contextx/unmarshaler.go new file mode 100644 index 0000000..788c71e --- /dev/null +++ b/core/contextx/unmarshaler.go @@ -0,0 +1,26 @@ +package contextx + +import ( + "context" + "supreme-flamego/core/mappingx" +) + +const contextTagKey = "ctx" + +var unmarshaler = mappingx.NewUnmarshaler(contextTagKey) + +type contextValuer struct { + context.Context +} + +func (cv contextValuer) Value(key string) (any, bool) { + v := cv.Context.Value(key) + return v, v != nil +} + +// For unmarshals ctx into v. +func For(ctx context.Context, v any) error { + return unmarshaler.UnmarshalValuer(contextValuer{ + Context: ctx, + }, v) +} diff --git a/core/contextx/valueonlycontext.go b/core/contextx/valueonlycontext.go new file mode 100644 index 0000000..40170b2 --- /dev/null +++ b/core/contextx/valueonlycontext.go @@ -0,0 +1,29 @@ +package contextx + +import ( + "context" + "time" +) + +type valueOnlyContext struct { + context.Context +} + +func (valueOnlyContext) Deadline() (deadline time.Time, ok bool) { + return +} + +func (valueOnlyContext) Done() <-chan struct{} { + return nil +} + +func (valueOnlyContext) Err() error { + return nil +} + +// ValueOnlyFrom takes all values from the given ctx, without deadline and error control. +func ValueOnlyFrom(ctx context.Context) context.Context { + return valueOnlyContext{ + Context: ctx, + } +} diff --git a/core/discov/accountregistry.go b/core/discov/accountregistry.go new file mode 100644 index 0000000..acd4c50 --- /dev/null +++ b/core/discov/accountregistry.go @@ -0,0 +1,14 @@ +package discov + +import "supreme-flamego/core/discov/internal" + +// RegisterAccount registers the username/password to the given etcd cluster. +func RegisterAccount(endpoints []string, user, pass string) { + internal.AddAccount(endpoints, user, pass) +} + +// RegisterTLS registers the CertFile/CertKeyFile/CACertFile to the given etcd. +func RegisterTLS(endpoints []string, certFile, certKeyFile, caFile string, + insecureSkipVerify bool) error { + return internal.AddTLS(endpoints, certFile, certKeyFile, caFile, insecureSkipVerify) +} diff --git a/core/discov/clients.go b/core/discov/clients.go new file mode 100644 index 0000000..8c47d2d --- /dev/null +++ b/core/discov/clients.go @@ -0,0 +1,40 @@ +package discov + +import ( + "fmt" + "strings" + "supreme-flamego/core/discov/internal" +) + +const ( + _ = iota + indexOfId +) + +const timeToLive int64 = 10 + +// TimeToLive is seconds to live in etcd. +var TimeToLive = timeToLive + +func extract(etcdKey string, index int) (string, bool) { + if index < 0 { + return "", false + } + + fields := strings.FieldsFunc(etcdKey, func(ch rune) bool { + return ch == internal.Delimiter + }) + if index >= len(fields) { + return "", false + } + + return fields[index], true +} + +func extractId(etcdKey string) (string, bool) { + return extract(etcdKey, indexOfId) +} + +func makeEtcdKey(key string, id int64) string { + return fmt.Sprintf("%s%c%d", key, internal.Delimiter, id) +} diff --git a/core/discov/conf.go b/core/discov/conf.go new file mode 100644 index 0000000..7ac2ba1 --- /dev/null +++ b/core/discov/conf.go @@ -0,0 +1,43 @@ +package discov + +import "errors" + +var ( + // errEmptyEtcdHosts indicates that etcd hosts are empty. + errEmptyEtcdHosts = errors.New("empty etcd hosts") + // errEmptyEtcdKey indicates that etcd key is empty. + errEmptyEtcdKey = errors.New("empty etcd key") +) + +// EtcdConf is the config item with the given key on etcd. +type EtcdConf struct { + Hosts []string `yaml:"Hosts"` + Key string `yaml:"Key"` + User string `json:",optional" yaml:"User"` + Pass string `json:",optional" yaml:"Pass"` + CertFile string `json:",optional" yaml:"CertFile"` + CertKeyFile string `json:",optional=CertFile" yaml:"CertKeyFile"` + CACertFile string `json:",optional=CertFile" yaml:"CACertFile"` + InsecureSkipVerify bool `json:",optional" yaml:"InsecureSkipVerify"` +} + +// HasAccount returns if account provided. +func (c EtcdConf) HasAccount() bool { + return len(c.User) > 0 && len(c.Pass) > 0 +} + +// HasTLS returns if TLS CertFile/CertKeyFile/CACertFile are provided. +func (c EtcdConf) HasTLS() bool { + return len(c.CertFile) > 0 && len(c.CertKeyFile) > 0 && len(c.CACertFile) > 0 +} + +// Validate validates c. +func (c EtcdConf) Validate() error { + if len(c.Hosts) == 0 { + return errEmptyEtcdHosts + } else if len(c.Key) == 0 { + return errEmptyEtcdKey + } else { + return nil + } +} diff --git a/core/discov/internal/accountmanager.go b/core/discov/internal/accountmanager.go new file mode 100644 index 0000000..55bd913 --- /dev/null +++ b/core/discov/internal/accountmanager.go @@ -0,0 +1,75 @@ +package internal + +import ( + "crypto/tls" + "crypto/x509" + "os" + "sync" +) + +var ( + accounts = make(map[string]Account) + tlsConfigs = make(map[string]*tls.Config) + lock sync.RWMutex +) + +// Account holds the username/password for an etcd cluster. +type Account struct { + User string + Pass string +} + +// AddAccount adds the username/password for the given etcd cluster. +func AddAccount(endpoints []string, user, pass string) { + lock.Lock() + defer lock.Unlock() + + accounts[getClusterKey(endpoints)] = Account{ + User: user, + Pass: pass, + } +} + +// AddTLS adds the tls cert files for the given etcd cluster. +func AddTLS(endpoints []string, certFile, certKeyFile, caFile string, insecureSkipVerify bool) error { + cert, err := tls.LoadX509KeyPair(certFile, certKeyFile) + if err != nil { + return err + } + + caData, err := os.ReadFile(caFile) + if err != nil { + return err + } + + pool := x509.NewCertPool() + pool.AppendCertsFromPEM(caData) + + lock.Lock() + defer lock.Unlock() + tlsConfigs[getClusterKey(endpoints)] = &tls.Config{ + Certificates: []tls.Certificate{cert}, + RootCAs: pool, + InsecureSkipVerify: insecureSkipVerify, + } + + return nil +} + +// GetAccount gets the username/password for the given etcd cluster. +func GetAccount(endpoints []string) (Account, bool) { + lock.RLock() + defer lock.RUnlock() + + account, ok := accounts[getClusterKey(endpoints)] + return account, ok +} + +// GetTLS gets the tls config for the given etcd cluster. +func GetTLS(endpoints []string) (*tls.Config, bool) { + lock.RLock() + defer lock.RUnlock() + + cfg, ok := tlsConfigs[getClusterKey(endpoints)] + return cfg, ok +} diff --git a/core/discov/internal/etcdclient.go b/core/discov/internal/etcdclient.go new file mode 100644 index 0000000..8e0c149 --- /dev/null +++ b/core/discov/internal/etcdclient.go @@ -0,0 +1,21 @@ +package internal + +import ( + "context" + + clientv3 "go.etcd.io/etcd/client/v3" + "google.golang.org/grpc" +) + +// EtcdClient interface represents an etcd client. +type EtcdClient interface { + ActiveConnection() *grpc.ClientConn + Close() error + Ctx() context.Context + Get(ctx context.Context, key string, opts ...clientv3.OpOption) (*clientv3.GetResponse, error) + Grant(ctx context.Context, ttl int64) (*clientv3.LeaseGrantResponse, error) + KeepAlive(ctx context.Context, id clientv3.LeaseID) (<-chan *clientv3.LeaseKeepAliveResponse, error) + Put(ctx context.Context, key, val string, opts ...clientv3.OpOption) (*clientv3.PutResponse, error) + Revoke(ctx context.Context, id clientv3.LeaseID) (*clientv3.LeaseRevokeResponse, error) + Watch(ctx context.Context, key string, opts ...clientv3.OpOption) clientv3.WatchChan +} diff --git a/core/discov/internal/listener.go b/core/discov/internal/listener.go new file mode 100644 index 0000000..3893ea3 --- /dev/null +++ b/core/discov/internal/listener.go @@ -0,0 +1,6 @@ +package internal + +// Listener interface wraps the OnUpdate method. +type Listener interface { + OnUpdate(keys, values []string, newKey string) +} diff --git a/core/discov/internal/register.go b/core/discov/internal/register.go new file mode 100644 index 0000000..8cdf6e8 --- /dev/null +++ b/core/discov/internal/register.go @@ -0,0 +1,367 @@ +package internal + +import ( + "context" + "fmt" + "io" + "sort" + "strings" + "supreme-flamego/core/contextx" + "supreme-flamego/core/langx" + "supreme-flamego/core/syncx" + "supreme-flamego/core/threadx" + "sync" + "time" + + clientv3 "go.etcd.io/etcd/client/v3" +) + +var ( + registry = Registry{ + clusters: make(map[string]*cluster), + } + connManager = syncx.NewResourceManager() +) + +// A Registry is a registry that manages the etcd client connections. +type Registry struct { + clusters map[string]*cluster + lock sync.Mutex +} + +// GetRegistry returns a global Registry. +func GetRegistry() *Registry { + return ®istry +} + +// GetConn returns an etcd client connection associated with given endpoints. +func (r *Registry) GetConn(endpoints []string) (EtcdClient, error) { + c, _ := r.getCluster(endpoints) + return c.getClient() +} + +// Monitor monitors the key on given etcd endpoints, notify with the given UpdateListener. +func (r *Registry) Monitor(endpoints []string, key string, l UpdateListener) error { + c, exists := r.getCluster(endpoints) + // if exists, the existing values should be updated to the listener. + if exists { + kvs := c.getCurrent(key) + for _, kv := range kvs { + l.OnAdd(kv) + } + } + + return c.monitor(key, l) +} + +func (r *Registry) getCluster(endpoints []string) (c *cluster, exists bool) { + clusterKey := getClusterKey(endpoints) + r.lock.Lock() + defer r.lock.Unlock() + c, exists = r.clusters[clusterKey] + if !exists { + c = newCluster(endpoints) + r.clusters[clusterKey] = c + } + + return +} + +type cluster struct { + endpoints []string + key string + values map[string]map[string]string + listeners map[string][]UpdateListener + watchGroup *threadx.RoutineGroup + done chan langx.PlaceholderType + lock sync.Mutex +} + +func newCluster(endpoints []string) *cluster { + return &cluster{ + endpoints: endpoints, + key: getClusterKey(endpoints), + values: make(map[string]map[string]string), + listeners: make(map[string][]UpdateListener), + watchGroup: threadx.NewRoutineGroup(), + done: make(chan langx.PlaceholderType), + } +} + +func (c *cluster) context(cli EtcdClient) context.Context { + return contextx.ValueOnlyFrom(cli.Ctx()) +} + +func (c *cluster) getClient() (EtcdClient, error) { + val, err := connManager.GetResource(c.key, func() (io.Closer, error) { + return c.newClient() + }) + if err != nil { + return nil, err + } + + return val.(EtcdClient), nil +} + +func (c *cluster) getCurrent(key string) []KV { + c.lock.Lock() + defer c.lock.Unlock() + + var kvs []KV + for k, v := range c.values[key] { + kvs = append(kvs, KV{ + Key: k, + Val: v, + }) + } + + return kvs +} + +func (c *cluster) handleChanges(key string, kvs []KV) { + var add []KV + var remove []KV + c.lock.Lock() + listeners := append([]UpdateListener(nil), c.listeners[key]...) + vals, ok := c.values[key] + if !ok { + add = kvs + vals = make(map[string]string) + for _, kv := range kvs { + vals[kv.Key] = kv.Val + } + c.values[key] = vals + } else { + m := make(map[string]string) + for _, kv := range kvs { + m[kv.Key] = kv.Val + } + for k, v := range vals { + if val, ok := m[k]; !ok || v != val { + remove = append(remove, KV{ + Key: k, + Val: v, + }) + } + } + for k, v := range m { + if val, ok := vals[k]; !ok || v != val { + add = append(add, KV{ + Key: k, + Val: v, + }) + } + } + c.values[key] = m + } + c.lock.Unlock() + + for _, kv := range add { + for _, l := range listeners { + l.OnAdd(kv) + } + } + for _, kv := range remove { + for _, l := range listeners { + l.OnDelete(kv) + } + } +} + +func (c *cluster) handleWatchEvents(key string, events []*clientv3.Event) { + c.lock.Lock() + listeners := append([]UpdateListener(nil), c.listeners[key]...) + c.lock.Unlock() + + for _, ev := range events { + switch ev.Type { + case clientv3.EventTypePut: + c.lock.Lock() + + if vals, ok := c.values[key]; ok { + vals[string(ev.Kv.Key)] = string(ev.Kv.Value) + } else { + c.values[key] = map[string]string{string(ev.Kv.Key): string(ev.Kv.Value)} + } + c.lock.Unlock() + for _, l := range listeners { + l.OnAdd(KV{ + Key: string(ev.Kv.Key), + Val: string(ev.Kv.Value), + }) + } + case clientv3.EventTypeDelete: + c.lock.Lock() + + if vals, ok := c.values[key]; ok { + delete(vals, string(ev.Kv.Key)) + } + c.lock.Unlock() + for _, l := range listeners { + l.OnDelete(KV{ + Key: string(ev.Kv.Key), + Val: string(ev.Kv.Value), + }) + } + default: + print("Unknown event type: %v", ev.Type) + } + } +} + +func (c *cluster) load(cli EtcdClient, key string) int64 { + var resp *clientv3.GetResponse + for { + var err error + ctx, cancel := context.WithTimeout(c.context(cli), RequestTimeout) + resp, err = cli.Get(ctx, makeKeyPrefix(key), clientv3.WithPrefix()) + cancel() + if err == nil { + break + } + + print(err) + time.Sleep(coolDownInterval) + } + + var kvs []KV + for _, ev := range resp.Kvs { + kvs = append(kvs, KV{ + Key: string(ev.Key), + Val: string(ev.Value), + }) + } + + c.handleChanges(key, kvs) + + return resp.Header.Revision +} + +func (c *cluster) monitor(key string, l UpdateListener) error { + c.lock.Lock() + c.listeners[key] = append(c.listeners[key], l) + c.lock.Unlock() + + cli, err := c.getClient() + if err != nil { + return err + } + + rev := c.load(cli, key) + c.watchGroup.Run(func() { + c.watch(cli, key, rev) + }) + + return nil +} + +func (c *cluster) newClient() (EtcdClient, error) { + cli, err := NewClient(c.endpoints) + if err != nil { + return nil, err + } + + go c.watchConnState(cli) + + return cli, nil +} + +func (c *cluster) reload(cli EtcdClient) { + c.lock.Lock() + close(c.done) + c.watchGroup.Wait() + c.done = make(chan langx.PlaceholderType) + c.watchGroup = threadx.NewRoutineGroup() + var keys []string + for k := range c.listeners { + keys = append(keys, k) + } + c.lock.Unlock() + + for _, key := range keys { + k := key + c.watchGroup.Run(func() { + rev := c.load(cli, k) + c.watch(cli, k, rev) + }) + } +} + +func (c *cluster) watch(cli EtcdClient, key string, rev int64) { + for { + if c.watchStream(cli, key, rev) { + return + } + } +} + +func (c *cluster) watchStream(cli EtcdClient, key string, rev int64) bool { + var rch clientv3.WatchChan + if rev != 0 { + rch = cli.Watch(clientv3.WithRequireLeader(c.context(cli)), makeKeyPrefix(key), clientv3.WithPrefix(), + clientv3.WithRev(rev+1)) + } else { + rch = cli.Watch(clientv3.WithRequireLeader(c.context(cli)), makeKeyPrefix(key), clientv3.WithPrefix()) + } + + for { + select { + case wresp, ok := <-rch: + if !ok { + print("etcd monitor chan has been closed") + return false + } + if wresp.Canceled { + print("etcd monitor chan has been canceled, error: %v", wresp.Err()) + return false + } + if wresp.Err() != nil { + print(fmt.Sprintf("etcd monitor chan error: %v", wresp.Err())) + return false + } + + c.handleWatchEvents(key, wresp.Events) + case <-c.done: + return true + } + } +} + +func (c *cluster) watchConnState(cli EtcdClient) { + watcher := newStateWatcher() + watcher.addListener(func() { + go c.reload(cli) + }) + watcher.watch(cli.ActiveConnection()) +} + +// DialClient dials an etcd cluster with given endpoints. +func DialClient(endpoints []string) (EtcdClient, error) { + cfg := clientv3.Config{ + Endpoints: endpoints, + AutoSyncInterval: autoSyncInterval, + DialTimeout: DialTimeout, + DialKeepAliveTime: dialKeepAliveTime, + DialKeepAliveTimeout: DialTimeout, + RejectOldCluster: true, + PermitWithoutStream: true, + } + if account, ok := GetAccount(endpoints); ok { + cfg.Username = account.User + cfg.Password = account.Pass + } + if tlsCfg, ok := GetTLS(endpoints); ok { + cfg.TLS = tlsCfg + } + + return clientv3.New(cfg) +} + +func getClusterKey(endpoints []string) string { + sort.Strings(endpoints) + return strings.Join(endpoints, endpointsSeparator) +} + +func makeKeyPrefix(key string) string { + return fmt.Sprintf("%s%c", key, Delimiter) +} diff --git a/core/discov/internal/statewatcher.go b/core/discov/internal/statewatcher.go new file mode 100644 index 0000000..749c734 --- /dev/null +++ b/core/discov/internal/statewatcher.go @@ -0,0 +1,65 @@ +package internal + +import ( + "context" + "sync" + + "google.golang.org/grpc/connectivity" +) + +type ( + etcdConn interface { + GetState() connectivity.State + WaitForStateChange(ctx context.Context, sourceState connectivity.State) bool + } + + stateWatcher struct { + disconnected bool + currentState connectivity.State + listeners []func() + // lock only guards listeners, because only listens can be accessed by other goroutines. + lock sync.Mutex + } +) + +func newStateWatcher() *stateWatcher { + return new(stateWatcher) +} + +func (sw *stateWatcher) addListener(l func()) { + sw.lock.Lock() + sw.listeners = append(sw.listeners, l) + sw.lock.Unlock() +} + +func (sw *stateWatcher) notifyListeners() { + sw.lock.Lock() + defer sw.lock.Unlock() + + for _, l := range sw.listeners { + l() + } +} + +func (sw *stateWatcher) updateState(conn etcdConn) { + sw.currentState = conn.GetState() + + switch sw.currentState { + case connectivity.TransientFailure, connectivity.Shutdown: + sw.disconnected = true + case connectivity.Ready: + if sw.disconnected { + sw.disconnected = false + sw.notifyListeners() + } + } +} + +func (sw *stateWatcher) watch(conn etcdConn) { + sw.currentState = conn.GetState() + for { + if conn.WaitForStateChange(context.Background(), sw.currentState) { + sw.updateState(conn) + } + } +} diff --git a/core/discov/internal/updatelistener.go b/core/discov/internal/updatelistener.go new file mode 100644 index 0000000..417b948 --- /dev/null +++ b/core/discov/internal/updatelistener.go @@ -0,0 +1,15 @@ +package internal + +type ( + // A KV is used to store an etcd entry with key and value. + KV struct { + Key string + Val string + } + + // UpdateListener wraps the OnAdd and OnDelete methods. + UpdateListener interface { + OnAdd(kv KV) + OnDelete(kv KV) + } +) diff --git a/core/discov/internal/vars.go b/core/discov/internal/vars.go new file mode 100644 index 0000000..77360b4 --- /dev/null +++ b/core/discov/internal/vars.go @@ -0,0 +1,24 @@ +package internal + +import "time" + +const ( + // Delimiter is a separator that separates the etcd path. + Delimiter = '/' + + autoSyncInterval = time.Minute + coolDownInterval = time.Second + dialTimeout = 5 * time.Second + dialKeepAliveTime = 5 * time.Second + requestTimeout = 3 * time.Second + endpointsSeparator = "," +) + +var ( + // DialTimeout is the dial timeout. + DialTimeout = dialTimeout + // RequestTimeout is the request timeout. + RequestTimeout = requestTimeout + // NewClient is used to create etcd clients. + NewClient = DialClient +) diff --git a/core/discov/publisher.go b/core/discov/publisher.go new file mode 100644 index 0000000..0d1c6ee --- /dev/null +++ b/core/discov/publisher.go @@ -0,0 +1,206 @@ +package discov + +import ( + "fmt" + clientv3 "go.etcd.io/etcd/client/v3" + "go.uber.org/zap" + "os" + "supreme-flamego/core/discov/internal" + "supreme-flamego/core/langx" + "supreme-flamego/core/syncx" + "supreme-flamego/core/systemx" + "supreme-flamego/core/threadx" + "time" +) + +type ( + // PubOption defines the method to customize a Publisher. + PubOption func(client *Publisher) + + // A Publisher can be used to publish the value to an etcd cluster on the given key. + Publisher struct { + endpoints []string + key string + fullKey string + id int64 + value string + lease clientv3.LeaseID + quit *syncx.DoneChan + pauseChan chan langx.PlaceholderType + resumeChan chan langx.PlaceholderType + } +) + +// NewPublisher returns a Publisher. +// endpoints is the hosts of the etcd cluster. +// key:value are a pair to be published. +// opts are used to customize the Publisher. +func NewPublisher(endpoints []string, key, value string, opts ...PubOption) *Publisher { + publisher := &Publisher{ + endpoints: endpoints, + key: key, + value: value, + quit: syncx.NewDoneChan(), + pauseChan: make(chan langx.PlaceholderType), + resumeChan: make(chan langx.PlaceholderType), + } + + for _, opt := range opts { + opt(publisher) + } + + return publisher +} + +// KeepAlive keeps key:value alive. +func (p *Publisher) KeepAlive() error { + cli, err := p.doRegister() + if err != nil { + return err + } + + systemx.AddWrapUpListener(func() { + p.Stop() + }) + + return p.keepAliveAsync(cli) +} + +// Pause pauses the renewing of key:value. +func (p *Publisher) Pause() { + p.pauseChan <- langx.Placeholder +} + +// Resume resumes the renewing of key:value. +func (p *Publisher) Resume() { + p.resumeChan <- langx.Placeholder +} + +// Stop stops the renewing and revokes the registration. +func (p *Publisher) Stop() { + p.quit.Close() +} + +func (p *Publisher) doKeepAlive() error { + ticker := time.NewTicker(time.Second) + defer ticker.Stop() + + for range ticker.C { + select { + case <-p.quit.Done(): + return nil + default: + cli, err := p.doRegister() + if err != nil { + zap.S().Error("etcd publisher doRegister: ", err.Error()) + break + } + + if err := p.keepAliveAsync(cli); err != nil { + zap.S().Error("etcd publisher keepAliveAsync: ", err.Error()) + break + } + + return nil + } + } + + return nil +} + +func (p *Publisher) doRegister() (internal.EtcdClient, error) { + cli, err := internal.GetRegistry().GetConn(p.endpoints) + if err != nil { + return nil, err + } + + p.lease, err = p.register(cli) + return cli, err +} + +func (p *Publisher) keepAliveAsync(cli internal.EtcdClient) error { + ch, err := cli.KeepAlive(cli.Ctx(), p.lease) + if err != nil { + return err + } + + threadx.GoSafe(func() { + for { + select { + case _, ok := <-ch: + if !ok { + p.revoke(cli) + if err := p.doKeepAlive(); err != nil { + zap.S().Error("etcd publisher KeepAlive: ", err.Error()) + } + return + } + case <-p.pauseChan: + zap.S().Info(fmt.Sprintf("paused etcd renew, key: %s, value: %s", p.key, p.value)) + p.revoke(cli) + select { + case <-p.resumeChan: + if err := p.doKeepAlive(); err != nil { + zap.S().Error("etcd publisher KeepAlive: ", err.Error()) + } + return + case <-p.quit.Done(): + return + } + case <-p.quit.Done(): + p.revoke(cli) + return + } + } + }) + + return nil +} + +func (p *Publisher) register(client internal.EtcdClient) (clientv3.LeaseID, error) { + resp, err := client.Grant(client.Ctx(), TimeToLive) + if err != nil { + return clientv3.NoLease, err + } + + lease := resp.ID + if p.id > 0 { + p.fullKey = makeEtcdKey(p.key, p.id) + } else { + p.fullKey = makeEtcdKey(p.key, int64(lease)) + } + _, err = client.Put(client.Ctx(), p.fullKey, p.value, clientv3.WithLease(lease)) + + return lease, err +} + +func (p *Publisher) revoke(cli internal.EtcdClient) { + if _, err := cli.Revoke(cli.Ctx(), p.lease); err != nil { + zap.S().Error("etcd publisher revoke: ", err.Error()) + } +} + +// WithId customizes a Publisher with the id. +func WithId(id int64) PubOption { + return func(publisher *Publisher) { + publisher.id = id + } +} + +// WithPubEtcdAccount provides the etcd username/password. +func WithPubEtcdAccount(user, pass string) PubOption { + return func(pub *Publisher) { + RegisterAccount(pub.endpoints, user, pass) + } +} + +// WithPubEtcdTLS provides the etcd CertFile/CertKeyFile/CACertFile. +func WithPubEtcdTLS(certFile, certKeyFile, caFile string, insecureSkipVerify bool) PubOption { + return func(pub *Publisher) { + err := RegisterTLS(pub.endpoints, certFile, certKeyFile, caFile, insecureSkipVerify) + if err != nil { + zap.S().Error(err) + os.Exit(1) + } + } +} diff --git a/core/discov/subscriber.go b/core/discov/subscriber.go new file mode 100644 index 0000000..3bde33d --- /dev/null +++ b/core/discov/subscriber.go @@ -0,0 +1,199 @@ +package discov + +import ( + "fmt" + "github.com/charmbracelet/log" + "os" + "supreme-flamego/core/discov/internal" + "supreme-flamego/core/syncx" + "sync" + "sync/atomic" +) + +type ( + // SubOption defines the method to customize a Subscriber. + SubOption func(sub *Subscriber) + + // A Subscriber is used to subscribe the given key on an etcd cluster. + Subscriber struct { + endpoints []string + exclusive bool + items *container + } +) + +// NewSubscriber returns a Subscriber. +// endpoints is the hosts of the etcd cluster. +// key is the key to subscribe. +// opts are used to customize the Subscriber. +func NewSubscriber(endpoints []string, key string, opts ...SubOption) (*Subscriber, error) { + sub := &Subscriber{ + endpoints: endpoints, + } + for _, opt := range opts { + opt(sub) + } + sub.items = newContainer(sub.exclusive) + + if err := internal.GetRegistry().Monitor(endpoints, key, sub.items); err != nil { + return nil, err + } + + return sub, nil +} + +// AddListener adds listener to s. +func (s *Subscriber) AddListener(listener func()) { + s.items.addListener(listener) +} + +// Values returns all the subscription values. +func (s *Subscriber) Values() []string { + return s.items.getValues() +} + +// Exclusive means that key value can only be 1:1, +// which means later added value will remove the keys associated with the same value previously. +func Exclusive() SubOption { + return func(sub *Subscriber) { + sub.exclusive = true + } +} + +// WithSubEtcdAccount provides the etcd username/password. +func WithSubEtcdAccount(user, pass string) SubOption { + return func(sub *Subscriber) { + RegisterAccount(sub.endpoints, user, pass) + } +} + +// WithSubEtcdTLS provides the etcd CertFile/CertKeyFile/CACertFile. +func WithSubEtcdTLS(certFile, certKeyFile, caFile string, insecureSkipVerify bool) SubOption { + return func(sub *Subscriber) { + err := RegisterTLS(sub.endpoints, certFile, certKeyFile, caFile, insecureSkipVerify) + if err != nil { + log.Error(err) + os.Exit(1) + } + } +} + +type container struct { + exclusive bool + values map[string][]string + mapping map[string]string + snapshot atomic.Value + dirty *syncx.AtomicBool + listeners []func() + lock sync.Mutex +} + +func newContainer(exclusive bool) *container { + return &container{ + exclusive: exclusive, + values: make(map[string][]string), + mapping: make(map[string]string), + dirty: syncx.ForAtomicBool(true), + } +} + +func (c *container) OnAdd(kv internal.KV) { + c.addKv(kv.Key, kv.Val) + c.notifyChange() +} + +func (c *container) OnDelete(kv internal.KV) { + c.removeKey(kv.Key) + c.notifyChange() +} + +// addKv adds the kv, returns if there are already other keys associate with the value +func (c *container) addKv(key, value string) ([]string, bool) { + c.lock.Lock() + defer c.lock.Unlock() + + c.dirty.Set(true) + keys := c.values[value] + previous := append([]string(nil), keys...) + early := len(keys) > 0 + if c.exclusive && early { + for _, each := range keys { + c.doRemoveKey(each) + } + } + c.values[value] = append(c.values[value], key) + c.mapping[key] = value + + if early { + return previous, true + } + + return nil, false +} + +func (c *container) addListener(listener func()) { + c.lock.Lock() + c.listeners = append(c.listeners, listener) + c.lock.Unlock() +} + +func (c *container) doRemoveKey(key string) { + server, ok := c.mapping[key] + if !ok { + return + } + + delete(c.mapping, key) + keys := c.values[server] + remain := keys[:0] + + for _, k := range keys { + if k != key { + remain = append(remain, k) + } + } + + if len(remain) > 0 { + c.values[server] = remain + } else { + delete(c.values, server) + } +} + +func (c *container) getValues() []string { + if !c.dirty.True() { + return c.snapshot.Load().([]string) + } + + c.lock.Lock() + defer c.lock.Unlock() + fmt.Println(c.values, c.mapping) + + var vals []string + for each := range c.values { + vals = append(vals, each) + } + c.snapshot.Store(vals) + c.dirty.Set(false) + + return vals +} + +func (c *container) notifyChange() { + c.lock.Lock() + listeners := append(([]func())(nil), c.listeners...) + c.lock.Unlock() + + for _, listener := range listeners { + listener() + } +} + +// removeKey removes the kv, returns true if there are still other keys associate with the value +func (c *container) removeKey(key string) { + c.lock.Lock() + defer c.lock.Unlock() + + c.dirty.Set(true) + c.doRemoveKey(key) +} diff --git a/core/errorx/atomicerror.go b/core/errorx/atomicerror.go new file mode 100644 index 0000000..2a3db80 --- /dev/null +++ b/core/errorx/atomicerror.go @@ -0,0 +1,23 @@ +package errorx + +import "sync/atomic" + +// AtomicError defines an atomic error. +type AtomicError struct { + err atomic.Value // error +} + +// Set sets the error. +func (ae *AtomicError) Set(err error) { + if err != nil { + ae.err.Store(err) + } +} + +// Load returns the error. +func (ae *AtomicError) Load() error { + if v := ae.err.Load(); v != nil { + return v.(error) + } + return nil +} diff --git a/core/errorx/batcherror.go b/core/errorx/batcherror.go new file mode 100644 index 0000000..92ae644 --- /dev/null +++ b/core/errorx/batcherror.go @@ -0,0 +1,52 @@ +package errorx + +import "bytes" + +type ( + // A BatchError is an error that can hold multiple errors. + BatchError struct { + errs errorArray + } + + errorArray []error +) + +// Add adds errs to be, nil errors are ignored. +func (be *BatchError) Add(errs ...error) { + for _, err := range errs { + if err != nil { + be.errs = append(be.errs, err) + } + } +} + +// Err returns an error that represents all errors. +func (be *BatchError) Err() error { + switch len(be.errs) { + case 0: + return nil + case 1: + return be.errs[0] + default: + return be.errs + } +} + +// NotNil checks if any error inside. +func (be *BatchError) NotNil() bool { + return len(be.errs) > 0 +} + +// Error returns a string that represents inside errors. +func (ea errorArray) Error() string { + var buf bytes.Buffer + + for i := range ea { + if i > 0 { + buf.WriteByte('\n') + } + buf.WriteString(ea[i].Error()) + } + + return buf.String() +} diff --git a/core/errorx/callchain.go b/core/errorx/callchain.go new file mode 100644 index 0000000..dbdab05 --- /dev/null +++ b/core/errorx/callchain.go @@ -0,0 +1,12 @@ +package errorx + +// Chain runs funs one by one until an error occurred. +func Chain(fns ...func() error) error { + for _, fn := range fns { + if err := fn(); err != nil { + return err + } + } + + return nil +} diff --git a/core/errorx/eCode.go b/core/errorx/eCode.go new file mode 100644 index 0000000..4c63633 --- /dev/null +++ b/core/errorx/eCode.go @@ -0,0 +1,12 @@ +package errorx + +const OK uint32 = 200 + +const SERVER_COMMON_ERROR uint32 = 500000 +const REUQEST_PARAM_ERROR uint32 = 400000 +const TOKEN_EXPIRE_ERROR uint32 = 403000 + +var ( + UnAuthorizedError = NewErrCodeMsg(TOKEN_EXPIRE_ERROR, "登录过期失效,请重新登陆") + InternalError = NewErrCodeMsg(SERVER_COMMON_ERROR, "服务拥挤,请稍后再试") +) diff --git a/core/errorx/eMsg.go b/core/errorx/eMsg.go new file mode 100644 index 0000000..ea53df9 --- /dev/null +++ b/core/errorx/eMsg.go @@ -0,0 +1,27 @@ +package errorx + +var message map[uint32]string + +func init() { + message = make(map[uint32]string) + message[OK] = "SUCCESS" + message[SERVER_COMMON_ERROR] = "服务器开小差啦,稍后再来试一试" + message[REUQEST_PARAM_ERROR] = "参数错误" + message[TOKEN_EXPIRE_ERROR] = "登录过期失效,请重新登陆" +} + +func MapErrMsg(errcode uint32) string { + if msg, ok := message[errcode]; ok { + return msg + } else { + return "服务器开小差啦,稍后再来试一试" + } +} + +func IsCodeErr(errcode uint32) bool { + if _, ok := message[errcode]; ok { + return true + } else { + return false + } +} diff --git a/core/errorx/eRpc.go b/core/errorx/eRpc.go new file mode 100644 index 0000000..e34db52 --- /dev/null +++ b/core/errorx/eRpc.go @@ -0,0 +1,33 @@ +package errorx + +import "fmt" + +type CodeError struct { + errCode uint32 + errMsg string +} + +// GetErrCode 返回给前端的错误码 +func (e *CodeError) GetErrCode() uint32 { + return e.errCode +} + +// GetErrMsg 返回给前端显示端错误信息 +func (e *CodeError) GetErrMsg() string { + return e.errMsg +} + +func (e *CodeError) Error() string { + return fmt.Sprintf("ErrCode:%d,ErrMsg:%s", e.errCode, e.errMsg) +} + +func NewErrCodeMsg(errCode uint32, errMsg string) *CodeError { + return &CodeError{errCode: errCode, errMsg: errMsg} +} +func NewErrCode(errCode uint32) *CodeError { + return &CodeError{errCode: errCode, errMsg: MapErrMsg(errCode)} +} + +func NewErrMsg(errMsg string) *CodeError { + return &CodeError{errCode: SERVER_COMMON_ERROR, errMsg: errMsg} +} diff --git a/core/errorx/wrap.go b/core/errorx/wrap.go new file mode 100644 index 0000000..67e8617 --- /dev/null +++ b/core/errorx/wrap.go @@ -0,0 +1,21 @@ +package errorx + +import "fmt" + +// Wrap returns an error that wraps err with given message. +func Wrap(err error, message string) error { + if err == nil { + return nil + } + + return fmt.Errorf("%s: %w", message, err) +} + +// Wrapf returns an error that wraps err with given format and args. +func Wrapf(err error, format string, args ...any) error { + if err == nil { + return nil + } + + return fmt.Errorf("%s: %w", fmt.Sprintf(format, args...), err) +} diff --git a/core/fsx/files.go b/core/fsx/files.go new file mode 100644 index 0000000..6d87897 --- /dev/null +++ b/core/fsx/files.go @@ -0,0 +1,16 @@ +//go:build linux || darwin +// +build linux darwin + +package fsx + +import ( + "os" + "syscall" +) + +// CloseOnExec makes sure closing the file on process forking. +func CloseOnExec(file *os.File) { + if file != nil { + syscall.CloseOnExec(int(file.Fd())) + } +} diff --git a/core/fsx/files_win.go b/core/fsx/files_win.go new file mode 100644 index 0000000..8f938c1 --- /dev/null +++ b/core/fsx/files_win.go @@ -0,0 +1,9 @@ +//go:build windows +// +build windows + +package fsx + +import "os" + +func CloseOnExec(*os.File) { +} diff --git a/core/fsx/temps.go b/core/fsx/temps.go new file mode 100644 index 0000000..e27f1a1 --- /dev/null +++ b/core/fsx/temps.go @@ -0,0 +1,40 @@ +package fsx + +import ( + "os" + "supreme-flamego/core/hash" +) + +// TempFileWithText creates the temporary file with the given content, +// and returns the opened *os.File instance. +// The file is kept as open, the caller should close the file handle, +// and remove the file by name. +func TempFileWithText(text string) (*os.File, error) { + tmpfile, err := os.CreateTemp(os.TempDir(), hash.Md5Hex([]byte(text))) + if err != nil { + return nil, err + } + + if err := os.WriteFile(tmpfile.Name(), []byte(text), os.ModeTemporary); err != nil { + return nil, err + } + + return tmpfile, nil +} + +// TempFilenameWithText creates the file with the given content, +// and returns the filename (full path). +// The caller should remove the file after use. +func TempFilenameWithText(text string) (string, error) { + tmpfile, err := TempFileWithText(text) + if err != nil { + return "", err + } + + filename := tmpfile.Name() + if err = tmpfile.Close(); err != nil { + return "", err + } + + return filename, nil +} diff --git a/core/hash/consistenthash.go b/core/hash/consistenthash.go new file mode 100644 index 0000000..cfea409 --- /dev/null +++ b/core/hash/consistenthash.go @@ -0,0 +1,185 @@ +package hash + +import ( + "fmt" + "sort" + "strconv" + "supreme-flamego/core/langx" + "sync" +) + +const ( + // TopWeight is the top weight that one entry might set. + TopWeight = 100 + + minReplicas = 100 + prime = 16777619 +) + +type ( + // Func defines the hash method. + Func func(data []byte) uint64 + + // A ConsistentHash is a ring hash implementation. + ConsistentHash struct { + hashFunc Func + replicas int + keys []uint64 + ring map[uint64][]any + nodes map[string]langx.PlaceholderType + lock sync.RWMutex + } +) + +// NewConsistentHash returns a ConsistentHash. +func NewConsistentHash() *ConsistentHash { + return NewCustomConsistentHash(minReplicas, Hash) +} + +// NewCustomConsistentHash returns a ConsistentHash with given replicas and hash func. +func NewCustomConsistentHash(replicas int, fn Func) *ConsistentHash { + if replicas < minReplicas { + replicas = minReplicas + } + + if fn == nil { + fn = Hash + } + + return &ConsistentHash{ + hashFunc: fn, + replicas: replicas, + ring: make(map[uint64][]any), + nodes: make(map[string]langx.PlaceholderType), + } +} + +// Add adds the node with the number of h.replicas, +// the later call will overwrite the replicas of the former calls. +func (h *ConsistentHash) Add(node any) { + h.AddWithReplicas(node, h.replicas) +} + +// AddWithReplicas adds the node with the number of replicas, +// replicas will be truncated to h.replicas if it's larger than h.replicas, +// the later call will overwrite the replicas of the former calls. +func (h *ConsistentHash) AddWithReplicas(node any, replicas int) { + h.Remove(node) + + if replicas > h.replicas { + replicas = h.replicas + } + + nodeRepr := repr(node) + h.lock.Lock() + defer h.lock.Unlock() + h.addNode(nodeRepr) + + for i := 0; i < replicas; i++ { + hash := h.hashFunc([]byte(nodeRepr + strconv.Itoa(i))) + h.keys = append(h.keys, hash) + h.ring[hash] = append(h.ring[hash], node) + } + + sort.Slice(h.keys, func(i, j int) bool { + return h.keys[i] < h.keys[j] + }) +} + +// AddWithWeight adds the node with weight, the weight can be 1 to 100, indicates the percent, +// the later call will overwrite the replicas of the former calls. +func (h *ConsistentHash) AddWithWeight(node any, weight int) { + // don't need to make sure weight not larger than TopWeight, + // because AddWithReplicas makes sure replicas cannot be larger than h.replicas + replicas := h.replicas * weight / TopWeight + h.AddWithReplicas(node, replicas) +} + +// Get returns the corresponding node from h base on the given v. +func (h *ConsistentHash) Get(v any) (any, bool) { + h.lock.RLock() + defer h.lock.RUnlock() + + if len(h.ring) == 0 { + return nil, false + } + + hash := h.hashFunc([]byte(repr(v))) + index := sort.Search(len(h.keys), func(i int) bool { + return h.keys[i] >= hash + }) % len(h.keys) + + nodes := h.ring[h.keys[index]] + switch len(nodes) { + case 0: + return nil, false + case 1: + return nodes[0], true + default: + innerIndex := h.hashFunc([]byte(innerRepr(v))) + pos := int(innerIndex % uint64(len(nodes))) + return nodes[pos], true + } +} + +// Remove removes the given node from h. +func (h *ConsistentHash) Remove(node any) { + nodeRepr := repr(node) + + h.lock.Lock() + defer h.lock.Unlock() + + if !h.containsNode(nodeRepr) { + return + } + + for i := 0; i < h.replicas; i++ { + hash := h.hashFunc([]byte(nodeRepr + strconv.Itoa(i))) + index := sort.Search(len(h.keys), func(i int) bool { + return h.keys[i] >= hash + }) + if index < len(h.keys) && h.keys[index] == hash { + h.keys = append(h.keys[:index], h.keys[index+1:]...) + } + h.removeRingNode(hash, nodeRepr) + } + + h.removeNode(nodeRepr) +} + +func (h *ConsistentHash) removeRingNode(hash uint64, nodeRepr string) { + if nodes, ok := h.ring[hash]; ok { + newNodes := nodes[:0] + for _, x := range nodes { + if repr(x) != nodeRepr { + newNodes = append(newNodes, x) + } + } + if len(newNodes) > 0 { + h.ring[hash] = newNodes + } else { + delete(h.ring, hash) + } + } +} + +func (h *ConsistentHash) addNode(nodeRepr string) { + h.nodes[nodeRepr] = langx.Placeholder +} + +func (h *ConsistentHash) containsNode(nodeRepr string) bool { + _, ok := h.nodes[nodeRepr] + return ok +} + +func (h *ConsistentHash) removeNode(nodeRepr string) { + delete(h.nodes, nodeRepr) +} + +func innerRepr(node any) string { + return fmt.Sprintf("%d:%v", prime, node) +} + +func repr(node any) string { + return langx.Repr(node) +} diff --git a/core/hash/hash.go b/core/hash/hash.go new file mode 100644 index 0000000..b23739a --- /dev/null +++ b/core/hash/hash.go @@ -0,0 +1,24 @@ +package hash + +import ( + "crypto/md5" + "fmt" + "github.com/spaolacci/murmur3" +) + +// Hash returns the hash value of data. +func Hash(data []byte) uint64 { + return murmur3.Sum64(data) +} + +// Md5 returns the md5 bytes of data. +func Md5(data []byte) []byte { + digest := md5.New() + digest.Write(data) + return digest.Sum(nil) +} + +// Md5Hex returns the md5 hex string of data. +func Md5Hex(data []byte) string { + return fmt.Sprintf("%x", Md5(data)) +} diff --git a/core/jsonx/json.go b/core/jsonx/json.go new file mode 100644 index 0000000..1b522e5 --- /dev/null +++ b/core/jsonx/json.go @@ -0,0 +1,65 @@ +package jsonx + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "strings" +) + +// Marshal marshals v into json bytes. +func Marshal(v any) ([]byte, error) { + return json.Marshal(v) +} + +// MarshalToString marshals v into a string. +func MarshalToString(v any) (string, error) { + data, err := Marshal(v) + if err != nil { + return "", err + } + + return string(data), nil +} + +// Unmarshal unmarshals data bytes into v. +func Unmarshal(data []byte, v any) error { + decoder := json.NewDecoder(bytes.NewReader(data)) + if err := unmarshalUseNumber(decoder, v); err != nil { + return formatError(string(data), err) + } + + return nil +} + +// UnmarshalFromString unmarshals v from str. +func UnmarshalFromString(str string, v any) error { + decoder := json.NewDecoder(strings.NewReader(str)) + if err := unmarshalUseNumber(decoder, v); err != nil { + return formatError(str, err) + } + + return nil +} + +// UnmarshalFromReader unmarshals v from reader. +func UnmarshalFromReader(reader io.Reader, v any) error { + var buf strings.Builder + teeReader := io.TeeReader(reader, &buf) + decoder := json.NewDecoder(teeReader) + if err := unmarshalUseNumber(decoder, v); err != nil { + return formatError(buf.String(), err) + } + + return nil +} + +func unmarshalUseNumber(decoder *json.Decoder, v any) error { + decoder.UseNumber() + return decoder.Decode(v) +} + +func formatError(v string, err error) error { + return fmt.Errorf("string: `%s`, error: `%w`", v, err) +} diff --git a/core/kernel/kernel.go b/core/kernel/kernel.go new file mode 100644 index 0000000..f2da52b --- /dev/null +++ b/core/kernel/kernel.go @@ -0,0 +1,118 @@ +package kernel + +import ( + "context" + "github.com/juanjiTech/inject" + "net" + "supreme-flamego/conf" + "supreme-flamego/core/logx" + "sync" +) + +type Engine struct { + config Config + // 下面这些将会从模组以依赖注入的形式加入内核 + //Mysql *gorm.DB + //Cache *redis.Client + //http *flamego.Flame + //httpSrv *http.Server + //grpc *grpc.Server + //Conn *grpc.ClientConn + //Mux *runtime.ServeMux + //HttpServer *http.Server + + //EtcdPublisher *discov.Publisher + + Ctx context.Context + Cancel context.CancelFunc + + ConfigListener []func(*conf.GlobalConfig) + + listener net.Listener + + inject.Injector + modules map[string]Module + modulesMu sync.Mutex +} + +type Config struct { + Listener net.Listener + EnableSentry bool +} + +func New(config ...Config) *Engine { + if len(config) == 0 { + panic("config can't be empty") + } + return &Engine{ + config: config[0], + listener: config[0].Listener, + Injector: inject.New(), + modules: make(map[string]Module), + } +} + +func (e *Engine) Init() { + e.Ctx, e.Cancel = context.WithCancel(context.Background()) +} + +func (e *Engine) StartModule() error { + hub := Hub{ + Injector: e.Injector, + } + for _, m := range e.modules { + h4m := hub + h4m.Logger = logx.NameSpace("module." + m.Name()) + if err := m.PreInit(&h4m); err != nil { + return err + } + } + for _, m := range e.modules { + h4m := hub + h4m.Logger = logx.NameSpace("module." + m.Name()) + if err := m.Init(&h4m); err != nil { + return err + } + } + for _, m := range e.modules { + h4m := hub + h4m.Logger = logx.NameSpace("module." + m.Name()) + if err := m.PostInit(&h4m); err != nil { + return err + } + } + for _, m := range e.modules { + h4m := hub + h4m.Logger = logx.NameSpace("module." + m.Name()) + if err := m.Load(&h4m); err != nil { + return err + } + } + for _, m := range e.modules { + go func(m Module) { + h4m := hub + h4m.Logger = logx.NameSpace("module." + m.Name()) + if err := m.Start(&h4m); err != nil { + panic(err) + } + }(m) + } + return nil +} + +func (e *Engine) Serve() { +} + +func (e *Engine) Stop() error { + wg := sync.WaitGroup{} + wg.Add(len(e.modules)) + for _, m := range e.modules { + err := m.Stop(&wg, e.Ctx) + if err != nil { + return err + } + } + wg.Wait() + + return nil +} diff --git a/core/kernel/module.go b/core/kernel/module.go new file mode 100644 index 0000000..d667884 --- /dev/null +++ b/core/kernel/module.go @@ -0,0 +1,75 @@ +package kernel + +import ( + "context" + "github.com/juanjiTech/inject" + "go.uber.org/zap" + "sync" +) + +type Hub struct { + inject.Injector + Logger *zap.SugaredLogger +} + +type Module interface { + Name() string + PreInit(*Hub) error + Init(*Hub) error + PostInit(*Hub) error + Load(*Hub) error + Start(*Hub) error + Stop(wg *sync.WaitGroup, ctx context.Context) error + mustEmbedUnimplementedModule() +} + +func (e *Engine) RegMod(mods ...Module) { + e.modulesMu.Lock() + defer e.modulesMu.Unlock() + for _, mod := range mods { + if mod.Name() == "" { + panic("name of module can't be empty") + } + if _, ok := e.modules[mod.Name()]; ok { + panic("module " + mod.Name() + " already exists") + } + e.modules[mod.Name()] = mod + } +} + +var _ Module = (*UnimplementedModule)(nil) + +// UnimplementedModule 由于Module接口中的方法除Name外都是可选的,所以这里提供一个默认实现,方便开发者只实现需要的方法 +type UnimplementedModule struct { +} + +func (u *UnimplementedModule) Name() string { + panic("name of module should be defined") +} + +func (u *UnimplementedModule) PreInit(*Hub) error { + return nil +} + +func (u *UnimplementedModule) Init(*Hub) error { + return nil +} + +func (u *UnimplementedModule) PostInit(*Hub) error { + return nil +} + +func (u *UnimplementedModule) Load(*Hub) error { + return nil +} + +func (u *UnimplementedModule) Start(*Hub) error { + return nil +} + +func (u *UnimplementedModule) Stop(wg *sync.WaitGroup, _ context.Context) error { + defer wg.Done() + return nil +} + +func (u *UnimplementedModule) mustEmbedUnimplementedModule() {} diff --git a/core/langx/lang.go b/core/langx/lang.go new file mode 100644 index 0000000..1c49da0 --- /dev/null +++ b/core/langx/lang.go @@ -0,0 +1,78 @@ +package langx + +import ( + "fmt" + "reflect" + "strconv" +) + +// Placeholder is a placeholder object that can be used globally. +var Placeholder PlaceholderType + +type ( + // AnyType can be used to hold any type. + AnyType = any + // PlaceholderType represents a placeholder type. + PlaceholderType = struct{} +) + +// Repr returns the string representation of v. +func Repr(v any) string { + if v == nil { + return "" + } + + // if func (v *Type) String() string, we can't use Elem() + switch vt := v.(type) { + case fmt.Stringer: + return vt.String() + } + + val := reflect.ValueOf(v) + for val.Kind() == reflect.Ptr && !val.IsNil() { + val = val.Elem() + } + + return reprOfValue(val) +} + +func reprOfValue(val reflect.Value) string { + switch vt := val.Interface().(type) { + case bool: + return strconv.FormatBool(vt) + case error: + return vt.Error() + case float32: + return strconv.FormatFloat(float64(vt), 'f', -1, 32) + case float64: + return strconv.FormatFloat(vt, 'f', -1, 64) + case fmt.Stringer: + return vt.String() + case int: + return strconv.Itoa(vt) + case int8: + return strconv.Itoa(int(vt)) + case int16: + return strconv.Itoa(int(vt)) + case int32: + return strconv.Itoa(int(vt)) + case int64: + return strconv.FormatInt(vt, 10) + case string: + return vt + case uint: + return strconv.FormatUint(uint64(vt), 10) + case uint8: + return strconv.FormatUint(uint64(vt), 10) + case uint16: + return strconv.FormatUint(uint64(vt), 10) + case uint32: + return strconv.FormatUint(uint64(vt), 10) + case uint64: + return strconv.FormatUint(vt, 10) + case []byte: + return string(vt) + default: + return fmt.Sprint(val.Interface()) + } +} diff --git a/pkg/logger/logger.go b/core/logx/logger.go similarity index 89% rename from pkg/logger/logger.go rename to core/logx/logger.go index 0c1d302..d6b82b3 100644 --- a/pkg/logger/logger.go +++ b/core/logx/logger.go @@ -1,12 +1,12 @@ -package logger +package logx import ( "go.uber.org/zap" "go.uber.org/zap/zapcore" "gopkg.in/natefinch/lumberjack.v2" - "supreme-flamego/config" "log" "os" + "supreme-flamego/conf" ) // NameSpace - 提供带有模块命名空间的logger @@ -15,11 +15,11 @@ func NameSpace(name string) *zap.SugaredLogger { } func getLogWriter() zapcore.WriteSyncer { - if config.GetConfig().LogPath == "" { + if conf.GetConfig().LogPath == "" { log.Fatalln("LogPath 未设置") } lj := &lumberjack.Logger{ - Filename: config.GetConfig().LogPath, + Filename: conf.GetConfig().LogPath, MaxSize: 5, MaxBackups: 5, MaxAge: 30, diff --git a/core/mappingx/fieldoptions.go b/core/mappingx/fieldoptions.go new file mode 100644 index 0000000..fd33544 --- /dev/null +++ b/core/mappingx/fieldoptions.go @@ -0,0 +1,112 @@ +package mappingx + +import "fmt" + +const notSymbol = '!' + +type ( + // use context and OptionalDep option to determine the value of Optional + // nothing to do with context.Context + fieldOptionsWithContext struct { + Inherit bool + FromString bool + Optional bool + Options []string + Default string + EnvVar string + Range *numberRange + } + + fieldOptions struct { + fieldOptionsWithContext + OptionalDep string + } + + numberRange struct { + left float64 + leftInclude bool + right float64 + rightInclude bool + } +) + +func (o *fieldOptionsWithContext) fromString() bool { + return o != nil && o.FromString +} + +func (o *fieldOptionsWithContext) getDefault() (string, bool) { + if o == nil { + return "", false + } + + return o.Default, len(o.Default) > 0 +} + +func (o *fieldOptionsWithContext) inherit() bool { + return o != nil && o.Inherit +} + +func (o *fieldOptionsWithContext) optional() bool { + return o != nil && o.Optional +} + +func (o *fieldOptionsWithContext) options() []string { + if o == nil { + return nil + } + + return o.Options +} + +func (o *fieldOptions) optionalDep() string { + if o == nil { + return "" + } + + return o.OptionalDep +} + +func (o *fieldOptions) toOptionsWithContext(key string, m Valuer, fullName string) ( + *fieldOptionsWithContext, error) { + var optional bool + if o.optional() { + dep := o.optionalDep() + if len(dep) == 0 { + optional = true + } else if dep[0] == notSymbol { + dep = dep[1:] + if len(dep) == 0 { + return nil, fmt.Errorf("wrong optional value for %q in %q", key, fullName) + } + + _, baseOn := m.Value(dep) + _, selfOn := m.Value(key) + if baseOn == selfOn { + return nil, fmt.Errorf("set value for either %q or %q in %q", dep, key, fullName) + } + + optional = baseOn + } else { + _, baseOn := m.Value(dep) + _, selfOn := m.Value(key) + if baseOn != selfOn { + return nil, fmt.Errorf("values for %q and %q should be both provided or both not in %q", + dep, key, fullName) + } + + optional = !baseOn + } + } + + if o.fieldOptionsWithContext.Optional == optional { + return &o.fieldOptionsWithContext, nil + } + + return &fieldOptionsWithContext{ + FromString: o.FromString, + Optional: optional, + Options: o.Options, + Default: o.Default, + EnvVar: o.EnvVar, + }, nil +} diff --git a/core/mappingx/unmarshaler.go b/core/mappingx/unmarshaler.go new file mode 100644 index 0000000..93edd13 --- /dev/null +++ b/core/mappingx/unmarshaler.go @@ -0,0 +1,1038 @@ +package mappingx + +import ( + "encoding" + "encoding/json" + "errors" + "fmt" + "reflect" + "strconv" + "strings" + "supreme-flamego/core/jsonx" + "supreme-flamego/core/langx" + "supreme-flamego/core/stringx" + "supreme-flamego/core/systemx" + "sync" + "time" +) + +const ( + defaultKeyName = "key" + delimiter = '.' +) + +var ( + errTypeMismatch = errors.New("type mismatch") + errValueNotSettable = errors.New("value is not settable") + errValueNotStruct = errors.New("value type is not struct") + keyUnmarshaler = NewUnmarshaler(defaultKeyName) + durationType = reflect.TypeOf(time.Duration(0)) + cacheKeys = make(map[string][]string) + cacheKeysLock sync.Mutex + defaultCache = make(map[string]any) + defaultCacheLock sync.Mutex + emptyMap = map[string]any{} + emptyValue = reflect.ValueOf(langx.Placeholder) +) + +type ( + // Unmarshaler is used to unmarshal with given tag key. + Unmarshaler struct { + key string + opts unmarshalOptions + } + + // UnmarshalOption defines the method to customize an Unmarshaler. + UnmarshalOption func(*unmarshalOptions) + + unmarshalOptions struct { + fromString bool + canonicalKey func(key string) string + } +) + +// NewUnmarshaler returns an Unmarshaler. +func NewUnmarshaler(key string, opts ...UnmarshalOption) *Unmarshaler { + unmarshaler := Unmarshaler{ + key: key, + } + + for _, opt := range opts { + opt(&unmarshaler.opts) + } + + return &unmarshaler +} + +// UnmarshalKey unmarshals m into v with tag key. +func UnmarshalKey(m map[string]any, v any) error { + return keyUnmarshaler.Unmarshal(m, v) +} + +// Unmarshal unmarshals m into v. +func (u *Unmarshaler) Unmarshal(i any, v any) error { + valueType := reflect.TypeOf(v) + if valueType.Kind() != reflect.Ptr { + return errValueNotSettable + } + + elemType := Deref(valueType) + switch iv := i.(type) { + case map[string]any: + if elemType.Kind() != reflect.Struct { + return errTypeMismatch + } + + return u.UnmarshalValuer(mapValuer(iv), v) + case []any: + if elemType.Kind() != reflect.Slice { + return errTypeMismatch + } + + return u.fillSlice(elemType, reflect.ValueOf(v).Elem(), iv) + default: + return errUnsupportedType + } +} + +// UnmarshalValuer unmarshals m into v. +func (u *Unmarshaler) UnmarshalValuer(m Valuer, v any) error { + return u.unmarshalWithFullName(simpleValuer{current: m}, v, "") +} + +func (u *Unmarshaler) fillMap(fieldType reflect.Type, value reflect.Value, mapValue any) error { + if !value.CanSet() { + return errValueNotSettable + } + + fieldKeyType := fieldType.Key() + fieldElemType := fieldType.Elem() + targetValue, err := u.generateMap(fieldKeyType, fieldElemType, mapValue) + if err != nil { + return err + } + + if !targetValue.Type().AssignableTo(value.Type()) { + return errTypeMismatch + } + + value.Set(targetValue) + return nil +} + +func (u *Unmarshaler) fillMapFromString(value reflect.Value, mapValue any) error { + if !value.CanSet() { + return errValueNotSettable + } + + switch v := mapValue.(type) { + case fmt.Stringer: + if err := jsonx.UnmarshalFromString(v.String(), value.Addr().Interface()); err != nil { + return err + } + case string: + if err := jsonx.UnmarshalFromString(v, value.Addr().Interface()); err != nil { + return err + } + default: + return errUnsupportedType + } + + return nil +} + +func (u *Unmarshaler) fillSlice(fieldType reflect.Type, value reflect.Value, mapValue any) error { + if !value.CanSet() { + return errValueNotSettable + } + + baseType := fieldType.Elem() + dereffedBaseType := Deref(baseType) + dereffedBaseKind := dereffedBaseType.Kind() + refValue := reflect.ValueOf(mapValue) + if refValue.IsNil() { + return nil + } + + conv := reflect.MakeSlice(reflect.SliceOf(baseType), refValue.Len(), refValue.Cap()) + if refValue.Len() == 0 { + value.Set(conv) + return nil + } + + var valid bool + for i := 0; i < refValue.Len(); i++ { + ithValue := refValue.Index(i).Interface() + if ithValue == nil { + continue + } + + valid = true + switch dereffedBaseKind { + case reflect.Struct: + target := reflect.New(dereffedBaseType) + if err := u.Unmarshal(ithValue.(map[string]any), target.Interface()); err != nil { + return err + } + + SetValue(fieldType.Elem(), conv.Index(i), target.Elem()) + case reflect.Slice: + if err := u.fillSlice(dereffedBaseType, conv.Index(i), ithValue); err != nil { + return err + } + default: + if err := u.fillSliceValue(conv, i, dereffedBaseKind, ithValue); err != nil { + return err + } + } + } + + if valid { + value.Set(conv) + } + + return nil +} + +func (u *Unmarshaler) fillSliceFromString(fieldType reflect.Type, value reflect.Value, + mapValue any) error { + var slice []any + switch v := mapValue.(type) { + case fmt.Stringer: + if err := jsonx.UnmarshalFromString(v.String(), &slice); err != nil { + return err + } + case string: + if err := jsonx.UnmarshalFromString(v, &slice); err != nil { + return err + } + default: + return errUnsupportedType + } + + baseFieldType := Deref(fieldType.Elem()) + baseFieldKind := baseFieldType.Kind() + conv := reflect.MakeSlice(reflect.SliceOf(baseFieldType), len(slice), cap(slice)) + + for i := 0; i < len(slice); i++ { + if err := u.fillSliceValue(conv, i, baseFieldKind, slice[i]); err != nil { + return err + } + } + + value.Set(conv) + return nil +} + +func (u *Unmarshaler) fillSliceValue(slice reflect.Value, index int, + baseKind reflect.Kind, value any) error { + ithVal := slice.Index(index) + switch v := value.(type) { + case fmt.Stringer: + return setValueFromString(baseKind, ithVal, v.String()) + case string: + return setValueFromString(baseKind, ithVal, v) + case map[string]any: + return u.fillMap(ithVal.Type(), ithVal, value) + default: + // don't need to consider the difference between int, int8, int16, int32, int64, + // uint, uint8, uint16, uint32, uint64, because they're handled as json.Number. + if ithVal.Kind() == reflect.Ptr { + baseType := Deref(ithVal.Type()) + if !reflect.TypeOf(value).AssignableTo(baseType) { + return errTypeMismatch + } + + target := reflect.New(baseType).Elem() + target.Set(reflect.ValueOf(value)) + SetValue(ithVal.Type(), ithVal, target) + return nil + } + + if !reflect.TypeOf(value).AssignableTo(ithVal.Type()) { + return errTypeMismatch + } + + ithVal.Set(reflect.ValueOf(value)) + return nil + } +} + +func (u *Unmarshaler) fillSliceWithDefault(derefedType reflect.Type, value reflect.Value, + defaultValue string) error { + baseFieldType := Deref(derefedType.Elem()) + baseFieldKind := baseFieldType.Kind() + defaultCacheLock.Lock() + slice, ok := defaultCache[defaultValue] + defaultCacheLock.Unlock() + if !ok { + if baseFieldKind == reflect.String { + slice = parseGroupedSegments(defaultValue) + } else if err := jsonx.UnmarshalFromString(defaultValue, &slice); err != nil { + return err + } + + defaultCacheLock.Lock() + defaultCache[defaultValue] = slice + defaultCacheLock.Unlock() + } + + return u.fillSlice(derefedType, value, slice) +} + +func (u *Unmarshaler) generateMap(keyType, elemType reflect.Type, mapValue any) (reflect.Value, error) { + mapType := reflect.MapOf(keyType, elemType) + valueType := reflect.TypeOf(mapValue) + if mapType == valueType { + return reflect.ValueOf(mapValue), nil + } + + refValue := reflect.ValueOf(mapValue) + targetValue := reflect.MakeMapWithSize(mapType, refValue.Len()) + dereffedElemType := Deref(elemType) + dereffedElemKind := dereffedElemType.Kind() + + for _, key := range refValue.MapKeys() { + keythValue := refValue.MapIndex(key) + keythData := keythValue.Interface() + + switch dereffedElemKind { + case reflect.Slice: + target := reflect.New(dereffedElemType) + if err := u.fillSlice(elemType, target.Elem(), keythData); err != nil { + return emptyValue, err + } + + targetValue.SetMapIndex(key, target.Elem()) + case reflect.Struct: + keythMap, ok := keythData.(map[string]any) + if !ok { + return emptyValue, errTypeMismatch + } + + target := reflect.New(dereffedElemType) + if err := u.Unmarshal(keythMap, target.Interface()); err != nil { + return emptyValue, err + } + + SetMapIndexValue(elemType, targetValue, key, target.Elem()) + case reflect.Map: + keythMap, ok := keythData.(map[string]any) + if !ok { + return emptyValue, errTypeMismatch + } + + innerValue, err := u.generateMap(elemType.Key(), elemType.Elem(), keythMap) + if err != nil { + return emptyValue, err + } + + targetValue.SetMapIndex(key, innerValue) + default: + switch v := keythData.(type) { + case bool: + if dereffedElemKind != reflect.Bool { + return emptyValue, errTypeMismatch + } + + targetValue.SetMapIndex(key, reflect.ValueOf(v)) + case string: + if dereffedElemKind != reflect.String { + return emptyValue, errTypeMismatch + } + + targetValue.SetMapIndex(key, reflect.ValueOf(v)) + case json.Number: + target := reflect.New(dereffedElemType) + if err := setValueFromString(dereffedElemKind, target.Elem(), v.String()); err != nil { + return emptyValue, err + } + + targetValue.SetMapIndex(key, target.Elem()) + default: + if dereffedElemKind != keythValue.Kind() { + return emptyValue, errTypeMismatch + } + + targetValue.SetMapIndex(key, keythValue) + } + } + } + + return targetValue, nil +} + +func (u *Unmarshaler) parseOptionsWithContext(field reflect.StructField, m Valuer, fullName string) ( + string, *fieldOptionsWithContext, error) { + key, options, err := parseKeyAndOptions(u.key, field) + if err != nil { + return "", nil, err + } else if options == nil { + return key, nil, nil + } + + if u.opts.canonicalKey != nil { + key = u.opts.canonicalKey(key) + + if len(options.OptionalDep) > 0 { + // need to create a new fieldOption, because the original one is shared through cache. + options = &fieldOptions{ + fieldOptionsWithContext: fieldOptionsWithContext{ + Inherit: options.Inherit, + FromString: options.FromString, + Optional: options.Optional, + Options: options.Options, + Default: options.Default, + EnvVar: options.EnvVar, + Range: options.Range, + }, + OptionalDep: u.opts.canonicalKey(options.OptionalDep), + } + } + } + + optsWithContext, err := options.toOptionsWithContext(key, m, fullName) + if err != nil { + return "", nil, err + } + + return key, optsWithContext, nil +} + +func (u *Unmarshaler) processAnonymousField(field reflect.StructField, value reflect.Value, + m valuerWithParent, fullName string) error { + key, options, err := u.parseOptionsWithContext(field, m, fullName) + if err != nil { + return err + } + + if options.optional() { + return u.processAnonymousFieldOptional(field, value, key, m, fullName) + } + + return u.processAnonymousFieldRequired(field, value, m, fullName) +} + +func (u *Unmarshaler) processAnonymousFieldOptional(field reflect.StructField, value reflect.Value, + key string, m valuerWithParent, fullName string) error { + derefedFieldType := Deref(field.Type) + + switch derefedFieldType.Kind() { + case reflect.Struct: + return u.processAnonymousStructFieldOptional(field.Type, value, key, m, fullName) + default: + return u.processNamedField(field, value, m, fullName) + } +} + +func (u *Unmarshaler) processAnonymousFieldRequired(field reflect.StructField, value reflect.Value, + m valuerWithParent, fullName string) error { + fieldType := field.Type + maybeNewValue(fieldType, value) + derefedFieldType := Deref(fieldType) + indirectValue := reflect.Indirect(value) + + switch derefedFieldType.Kind() { + case reflect.Struct: + for i := 0; i < derefedFieldType.NumField(); i++ { + if err := u.processField(derefedFieldType.Field(i), indirectValue.Field(i), + m, fullName); err != nil { + return err + } + } + default: + if err := u.processNamedField(field, indirectValue, m, fullName); err != nil { + return err + } + } + + return nil +} + +func (u *Unmarshaler) processAnonymousStructFieldOptional(fieldType reflect.Type, + value reflect.Value, key string, m valuerWithParent, fullName string) error { + var filled bool + var required int + var requiredFilled int + var indirectValue reflect.Value + derefedFieldType := Deref(fieldType) + + for i := 0; i < derefedFieldType.NumField(); i++ { + subField := derefedFieldType.Field(i) + fieldKey, fieldOpts, err := u.parseOptionsWithContext(subField, m, fullName) + if err != nil { + return err + } + + _, hasValue := getValue(m, fieldKey) + if hasValue { + if !filled { + filled = true + maybeNewValue(fieldType, value) + indirectValue = reflect.Indirect(value) + } + if err = u.processField(subField, indirectValue.Field(i), m, fullName); err != nil { + return err + } + } + if !fieldOpts.optional() { + required++ + if hasValue { + requiredFilled++ + } + } + } + + if filled && required != requiredFilled { + return fmt.Errorf("%s is not fully set", key) + } + + return nil +} + +func (u *Unmarshaler) processField(field reflect.StructField, value reflect.Value, + m valuerWithParent, fullName string) error { + if usingDifferentKeys(u.key, field) { + return nil + } + + if field.Anonymous { + return u.processAnonymousField(field, value, m, fullName) + } + + return u.processNamedField(field, value, m, fullName) +} + +func (u *Unmarshaler) processFieldNotFromString(fieldType reflect.Type, value reflect.Value, + vp valueWithParent, opts *fieldOptionsWithContext, fullName string) error { + derefedFieldType := Deref(fieldType) + typeKind := derefedFieldType.Kind() + valueKind := reflect.TypeOf(vp.value).Kind() + mapValue := vp.value + + switch { + case valueKind == reflect.Map && typeKind == reflect.Struct: + mv, ok := mapValue.(map[string]any) + if !ok { + return errTypeMismatch + } + + return u.processFieldStruct(fieldType, value, &simpleValuer{ + current: mapValuer(mv), + parent: vp.parent, + }, fullName) + case valueKind == reflect.Map && typeKind == reflect.Map: + return u.fillMap(fieldType, value, mapValue) + case valueKind == reflect.String && typeKind == reflect.Map: + return u.fillMapFromString(value, mapValue) + case valueKind == reflect.String && typeKind == reflect.Slice: + return u.fillSliceFromString(fieldType, value, mapValue) + case valueKind == reflect.String && derefedFieldType == durationType: + return fillDurationValue(fieldType, value, mapValue.(string)) + default: + return u.processFieldPrimitive(fieldType, value, mapValue, opts, fullName) + } +} + +func (u *Unmarshaler) processFieldPrimitive(fieldType reflect.Type, value reflect.Value, + mapValue any, opts *fieldOptionsWithContext, fullName string) error { + typeKind := Deref(fieldType).Kind() + valueKind := reflect.TypeOf(mapValue).Kind() + + switch { + case typeKind == reflect.Slice && valueKind == reflect.Slice: + return u.fillSlice(fieldType, value, mapValue) + case typeKind == reflect.Map && valueKind == reflect.Map: + return u.fillMap(fieldType, value, mapValue) + default: + switch v := mapValue.(type) { + case json.Number: + return u.processFieldPrimitiveWithJSONNumber(fieldType, value, v, opts, fullName) + default: + if typeKind == valueKind { + if err := validateValueInOptions(mapValue, opts.options()); err != nil { + return err + } + + return fillWithSameType(fieldType, value, mapValue, opts) + } + } + } + + return newTypeMismatchError(fullName) +} + +func (u *Unmarshaler) processFieldPrimitiveWithJSONNumber(fieldType reflect.Type, value reflect.Value, + v json.Number, opts *fieldOptionsWithContext, fullName string) error { + baseType := Deref(fieldType) + typeKind := baseType.Kind() + + if err := validateJsonNumberRange(v, opts); err != nil { + return err + } + + if err := validateValueInOptions(v, opts.options()); err != nil { + return err + } + + target := reflect.New(Deref(fieldType)).Elem() + + switch typeKind { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + iValue, err := v.Int64() + if err != nil { + return err + } + + target.SetInt(iValue) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + iValue, err := v.Int64() + if err != nil { + return err + } + + if iValue < 0 { + return fmt.Errorf("unmarshal %q with bad value %q", fullName, v.String()) + } + + target.SetUint(uint64(iValue)) + case reflect.Float32, reflect.Float64: + fValue, err := v.Float64() + if err != nil { + return err + } + + target.SetFloat(fValue) + default: + return newTypeMismatchError(fullName) + } + + SetValue(fieldType, value, target) + + return nil +} + +func (u *Unmarshaler) processFieldStruct(fieldType reflect.Type, value reflect.Value, + m valuerWithParent, fullName string) error { + if fieldType.Kind() == reflect.Ptr { + baseType := Deref(fieldType) + target := reflect.New(baseType).Elem() + if err := u.unmarshalWithFullName(m, target.Addr().Interface(), fullName); err != nil { + return err + } + + SetValue(fieldType, value, target) + } else if err := u.unmarshalWithFullName(m, value.Addr().Interface(), fullName); err != nil { + return err + } + + return nil +} + +func (u *Unmarshaler) processFieldTextUnmarshaler(fieldType reflect.Type, value reflect.Value, + mapValue any) (bool, error) { + var tval encoding.TextUnmarshaler + var ok bool + + if fieldType.Kind() == reflect.Ptr { + if value.Elem().Kind() == reflect.Ptr { + target := reflect.New(Deref(fieldType)) + SetValue(fieldType.Elem(), value, target) + tval, ok = target.Interface().(encoding.TextUnmarshaler) + } else { + tval, ok = value.Interface().(encoding.TextUnmarshaler) + } + } else { + tval, ok = value.Addr().Interface().(encoding.TextUnmarshaler) + } + if ok { + switch mv := mapValue.(type) { + case string: + return true, tval.UnmarshalText([]byte(mv)) + case []byte: + return true, tval.UnmarshalText(mv) + } + } + + return false, nil +} + +func (u *Unmarshaler) processFieldWithEnvValue(fieldType reflect.Type, value reflect.Value, + envVal string, opts *fieldOptionsWithContext, fullName string) error { + if err := validateValueInOptions(envVal, opts.options()); err != nil { + return err + } + + fieldKind := fieldType.Kind() + switch fieldKind { + case reflect.Bool: + val, err := strconv.ParseBool(envVal) + if err != nil { + return fmt.Errorf("unmarshal field %q with environment variable, %w", fullName, err) + } + + value.SetBool(val) + return nil + case durationType.Kind(): + if err := fillDurationValue(fieldType, value, envVal); err != nil { + return fmt.Errorf("unmarshal field %q with environment variable, %w", fullName, err) + } + + return nil + case reflect.String: + value.SetString(envVal) + return nil + default: + return u.processFieldPrimitiveWithJSONNumber(fieldType, value, json.Number(envVal), opts, fullName) + } +} + +func (u *Unmarshaler) processNamedField(field reflect.StructField, value reflect.Value, + m valuerWithParent, fullName string) error { + key, opts, err := u.parseOptionsWithContext(field, m, fullName) + if err != nil { + return err + } + + fullName = join(fullName, key) + if opts != nil && len(opts.EnvVar) > 0 { + envVal := systemx.Env(opts.EnvVar) + if len(envVal) > 0 { + return u.processFieldWithEnvValue(field.Type, value, envVal, opts, fullName) + } + } + + canonicalKey := key + if u.opts.canonicalKey != nil { + canonicalKey = u.opts.canonicalKey(key) + } + + valuer := createValuer(m, opts) + mapValue, hasValue := getValue(valuer, canonicalKey) + if !hasValue { + return u.processNamedFieldWithoutValue(field.Type, value, opts, fullName) + } + + return u.processNamedFieldWithValue(field.Type, value, valueWithParent{ + value: mapValue, + parent: valuer, + }, key, opts, fullName) +} + +func (u *Unmarshaler) processNamedFieldWithValue(fieldType reflect.Type, value reflect.Value, + vp valueWithParent, key string, opts *fieldOptionsWithContext, fullName string) error { + mapValue := vp.value + if mapValue == nil { + if opts.optional() { + return nil + } + + return fmt.Errorf("field %s mustn't be nil", key) + } + + if !value.CanSet() { + return fmt.Errorf("field %s is not settable", key) + } + + maybeNewValue(fieldType, value) + + if yes, err := u.processFieldTextUnmarshaler(fieldType, value, mapValue); yes { + return err + } + + fieldKind := Deref(fieldType).Kind() + switch fieldKind { + case reflect.Array, reflect.Map, reflect.Slice, reflect.Struct: + return u.processFieldNotFromString(fieldType, value, vp, opts, fullName) + default: + if u.opts.fromString || opts.fromString() { + return u.processNamedFieldWithValueFromString(fieldType, value, mapValue, + key, opts, fullName) + } + + return u.processFieldNotFromString(fieldType, value, vp, opts, fullName) + } +} + +func (u *Unmarshaler) processNamedFieldWithValueFromString(fieldType reflect.Type, value reflect.Value, + mapValue any, key string, opts *fieldOptionsWithContext, fullName string) error { + valueKind := reflect.TypeOf(mapValue).Kind() + if valueKind != reflect.String { + return fmt.Errorf("the value in map is not string, but %s", valueKind) + } + + options := opts.options() + if len(options) > 0 { + var checkValue string + switch mt := mapValue.(type) { + case string: + checkValue = mt + case fmt.Stringer: + checkValue = mt.String() + default: + return fmt.Errorf("the value in map is not string or json.Number, but %s", + valueKind.String()) + } + + if !stringx.Contains(options, checkValue) { + return fmt.Errorf(`value "%s" for field "%s" is not defined in options "%v"`, + mapValue, key, options) + } + } + + return fillPrimitive(fieldType, value, mapValue, opts, fullName) +} + +func (u *Unmarshaler) processNamedFieldWithoutValue(fieldType reflect.Type, value reflect.Value, + opts *fieldOptionsWithContext, fullName string) error { + derefedType := Deref(fieldType) + fieldKind := derefedType.Kind() + if defaultValue, ok := opts.getDefault(); ok { + if derefedType == durationType { + return fillDurationValue(fieldType, value, defaultValue) + } + + switch fieldKind { + case reflect.Array, reflect.Slice: + return u.fillSliceWithDefault(derefedType, value, defaultValue) + default: + return setValueFromString(fieldKind, value, defaultValue) + } + } + + switch fieldKind { + case reflect.Array, reflect.Map, reflect.Slice: + if !opts.optional() { + return u.processFieldNotFromString(fieldType, value, valueWithParent{ + value: emptyMap, + }, opts, fullName) + } + case reflect.Struct: + if !opts.optional() { + required, err := structValueRequired(u.key, derefedType) + if err != nil { + return err + } + + if required { + return fmt.Errorf("%q is not set", fullName) + } + + return u.processFieldNotFromString(fieldType, value, valueWithParent{ + value: emptyMap, + }, opts, fullName) + } + default: + if !opts.optional() { + return newInitError(fullName) + } + } + + return nil +} + +func (u *Unmarshaler) unmarshalWithFullName(m valuerWithParent, v any, fullName string) error { + rv := reflect.ValueOf(v) + if err := ValidatePtr(&rv); err != nil { + return err + } + + valueType := reflect.TypeOf(v) + baseType := Deref(valueType) + if baseType.Kind() != reflect.Struct { + return errValueNotStruct + } + + valElem := rv.Elem() + if valElem.Kind() == reflect.Ptr { + target := reflect.New(baseType).Elem() + SetValue(valueType.Elem(), valElem, target) + valElem = target + } + + numFields := baseType.NumField() + for i := 0; i < numFields; i++ { + field := baseType.Field(i) + if !field.IsExported() { + continue + } + + if err := u.processField(field, valElem.Field(i), m, fullName); err != nil { + return err + } + } + + return nil +} + +// WithStringValues customizes an Unmarshaler with number values from strings. +func WithStringValues() UnmarshalOption { + return func(opt *unmarshalOptions) { + opt.fromString = true + } +} + +// WithCanonicalKeyFunc customizes an Unmarshaler with Canonical Key func +func WithCanonicalKeyFunc(f func(string) string) UnmarshalOption { + return func(opt *unmarshalOptions) { + opt.canonicalKey = f + } +} + +func createValuer(v valuerWithParent, opts *fieldOptionsWithContext) valuerWithParent { + if opts.inherit() { + return recursiveValuer{ + current: v, + parent: v.Parent(), + } + } + + return simpleValuer{ + current: v, + parent: v.Parent(), + } +} + +func fillDurationValue(fieldType reflect.Type, value reflect.Value, dur string) error { + d, err := time.ParseDuration(dur) + if err != nil { + return err + } + + SetValue(fieldType, value, reflect.ValueOf(d)) + + return nil +} + +func fillPrimitive(fieldType reflect.Type, value reflect.Value, mapValue any, + opts *fieldOptionsWithContext, fullName string) error { + if !value.CanSet() { + return errValueNotSettable + } + + baseType := Deref(fieldType) + if fieldType.Kind() == reflect.Ptr { + target := reflect.New(baseType).Elem() + switch mapValue.(type) { + case string, json.Number: + SetValue(fieldType, value, target) + value = target + } + } + + switch v := mapValue.(type) { + case string: + return validateAndSetValue(baseType.Kind(), value, v, opts) + case json.Number: + if err := validateJsonNumberRange(v, opts); err != nil { + return err + } + return setValueFromString(baseType.Kind(), value, v.String()) + default: + return newTypeMismatchError(fullName) + } +} + +func fillWithSameType(fieldType reflect.Type, value reflect.Value, mapValue any, + opts *fieldOptionsWithContext) error { + if !value.CanSet() { + return errValueNotSettable + } + + if err := validateValueRange(mapValue, opts); err != nil { + return err + } + + if fieldType.Kind() == reflect.Ptr { + baseType := Deref(fieldType) + target := reflect.New(baseType).Elem() + setSameKindValue(baseType, target, mapValue) + SetValue(fieldType, value, target) + } else { + setSameKindValue(fieldType, value, mapValue) + } + + return nil +} + +// getValue gets the value for the specific key, the key can be in the format of parentKey.childKey +func getValue(m valuerWithParent, key string) (any, bool) { + keys := readKeys(key) + return getValueWithChainedKeys(m, keys) +} + +func getValueWithChainedKeys(m valuerWithParent, keys []string) (any, bool) { + switch len(keys) { + case 0: + return nil, false + case 1: + v, ok := m.Value(keys[0]) + return v, ok + default: + if v, ok := m.Value(keys[0]); ok { + if nextm, ok := v.(map[string]any); ok { + return getValueWithChainedKeys(recursiveValuer{ + current: mapValuer(nextm), + parent: m, + }, keys[1:]) + } + } + + return nil, false + } +} + +func join(elem ...string) string { + var builder strings.Builder + + var fillSep bool + for _, e := range elem { + if len(e) == 0 { + continue + } + + if fillSep { + builder.WriteByte(delimiter) + } else { + fillSep = true + } + + builder.WriteString(e) + } + + return builder.String() +} + +func newInitError(name string) error { + return fmt.Errorf("field %s is not set", name) +} + +func newTypeMismatchError(name string) error { + return fmt.Errorf("type mismatch for field %s", name) +} + +func readKeys(key string) []string { + cacheKeysLock.Lock() + keys, ok := cacheKeys[key] + cacheKeysLock.Unlock() + if ok { + return keys + } + + keys = strings.FieldsFunc(key, func(c rune) bool { + return c == delimiter + }) + cacheKeysLock.Lock() + cacheKeys[key] = keys + cacheKeysLock.Unlock() + + return keys +} + +func setSameKindValue(targetType reflect.Type, target reflect.Value, value any) { + if reflect.ValueOf(value).Type().AssignableTo(targetType) { + target.Set(reflect.ValueOf(value)) + } else { + target.Set(reflect.ValueOf(value).Convert(targetType)) + } +} diff --git a/core/mappingx/utils.go b/core/mappingx/utils.go new file mode 100644 index 0000000..ad18e42 --- /dev/null +++ b/core/mappingx/utils.go @@ -0,0 +1,653 @@ +package mappingx + +import ( + "encoding/json" + "errors" + "fmt" + "math" + "reflect" + "strconv" + "strings" + "supreme-flamego/core/langx" + "supreme-flamego/core/stringx" + "sync" +) + +const ( + defaultOption = "default" + envOption = "env" + inheritOption = "inherit" + stringOption = "string" + optionalOption = "optional" + optionsOption = "options" + rangeOption = "range" + optionSeparator = "|" + equalToken = "=" + escapeChar = '\\' + leftBracket = '(' + rightBracket = ')' + leftSquareBracket = '[' + rightSquareBracket = ']' + segmentSeparator = ',' +) + +var ( + errUnsupportedType = errors.New("unsupported type on setting field value") + errNumberRange = errors.New("wrong number range setting") + optionsCache = make(map[string]optionsCacheValue) + cacheLock sync.RWMutex + structRequiredCache = make(map[reflect.Type]requiredCacheValue) + structCacheLock sync.RWMutex +) + +type ( + optionsCacheValue struct { + key string + options *fieldOptions + err error + } + + requiredCacheValue struct { + required bool + err error + } +) + +// Deref dereferences a type, if pointer type, returns its element type. +func Deref(t reflect.Type) reflect.Type { + for t.Kind() == reflect.Ptr { + t = t.Elem() + } + + return t +} + +// Repr returns the string representation of v. +func Repr(v any) string { + return langx.Repr(v) +} + +// SetValue sets target to value, pointers are processed automatically. +func SetValue(tp reflect.Type, value, target reflect.Value) { + value.Set(convertTypeOfPtr(tp, target)) +} + +// SetMapIndexValue sets target to value at key position, pointers are processed automatically. +func SetMapIndexValue(tp reflect.Type, value, key, target reflect.Value) { + value.SetMapIndex(key, convertTypeOfPtr(tp, target)) +} + +// ValidatePtr validates v if it's a valid pointer. +func ValidatePtr(v *reflect.Value) error { + // sequence is very important, IsNil must be called after checking Kind() with reflect.Ptr, + // panic otherwise + if !v.IsValid() || v.Kind() != reflect.Ptr || v.IsNil() { + return fmt.Errorf("not a valid pointer: %v", v) + } + + return nil +} + +func convertTypeFromString(kind reflect.Kind, str string) (any, error) { + switch kind { + case reflect.Bool: + switch strings.ToLower(str) { + case "1", "true": + return true, nil + case "0", "false": + return false, nil + default: + return false, errTypeMismatch + } + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + intValue, err := strconv.ParseInt(str, 10, 64) + if err != nil { + return 0, fmt.Errorf("the value %q cannot parsed as int", str) + } + + return intValue, nil + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + uintValue, err := strconv.ParseUint(str, 10, 64) + if err != nil { + return 0, fmt.Errorf("the value %q cannot parsed as uint", str) + } + + return uintValue, nil + case reflect.Float32, reflect.Float64: + floatValue, err := strconv.ParseFloat(str, 64) + if err != nil { + return 0, fmt.Errorf("the value %q cannot parsed as float", str) + } + + return floatValue, nil + case reflect.String: + return str, nil + default: + return nil, errUnsupportedType + } +} + +func convertTypeOfPtr(tp reflect.Type, target reflect.Value) reflect.Value { + // keep the original value is a pointer + if tp.Kind() == reflect.Ptr && target.CanAddr() { + tp = tp.Elem() + target = target.Addr() + } + + for tp.Kind() == reflect.Ptr { + p := reflect.New(target.Type()) + p.Elem().Set(target) + target = p + tp = tp.Elem() + } + + return target +} + +func doParseKeyAndOptions(field reflect.StructField, value string) (string, *fieldOptions, error) { + segments := parseSegments(value) + key := strings.TrimSpace(segments[0]) + options := segments[1:] + + if len(options) == 0 { + return key, nil, nil + } + + var fieldOpts fieldOptions + for _, segment := range options { + option := strings.TrimSpace(segment) + if err := parseOption(&fieldOpts, field.Name, option); err != nil { + return "", nil, err + } + } + + return key, &fieldOpts, nil +} + +// ensureValue ensures nested members not to be nil. +// If pointer value is nil, set to a new value. +func ensureValue(v reflect.Value) reflect.Value { + for { + if v.Kind() != reflect.Ptr { + break + } + + if v.IsNil() { + v.Set(reflect.New(v.Type().Elem())) + } + v = v.Elem() + } + + return v +} + +func implicitValueRequiredStruct(tag string, tp reflect.Type) (bool, error) { + numFields := tp.NumField() + for i := 0; i < numFields; i++ { + childField := tp.Field(i) + if usingDifferentKeys(tag, childField) { + return true, nil + } + + _, opts, err := parseKeyAndOptions(tag, childField) + if err != nil { + return false, err + } + + if opts == nil { + if childField.Type.Kind() != reflect.Struct { + return true, nil + } + + if required, err := implicitValueRequiredStruct(tag, childField.Type); err != nil { + return false, err + } else if required { + return true, nil + } + } else if !opts.Optional && len(opts.Default) == 0 { + return true, nil + } else if len(opts.OptionalDep) > 0 && opts.OptionalDep[0] == notSymbol { + return true, nil + } + } + + return false, nil +} + +func isLeftInclude(b byte) (bool, error) { + switch b { + case '[': + return true, nil + case '(': + return false, nil + default: + return false, errNumberRange + } +} + +func isRightInclude(b byte) (bool, error) { + switch b { + case ']': + return true, nil + case ')': + return false, nil + default: + return false, errNumberRange + } +} + +func maybeNewValue(fieldType reflect.Type, value reflect.Value) { + if fieldType.Kind() == reflect.Ptr && value.IsNil() { + value.Set(reflect.New(value.Type().Elem())) + } +} + +func parseGroupedSegments(val string) []string { + val = strings.TrimLeftFunc(val, func(r rune) bool { + return r == leftBracket || r == leftSquareBracket + }) + val = strings.TrimRightFunc(val, func(r rune) bool { + return r == rightBracket || r == rightSquareBracket + }) + return parseSegments(val) +} + +// don't modify returned fieldOptions, it's cached and shared among different calls. +func parseKeyAndOptions(tagName string, field reflect.StructField) (string, *fieldOptions, error) { + value := field.Tag.Get(tagName) + if len(value) == 0 { + return field.Name, nil, nil + } + + cacheLock.RLock() + cache, ok := optionsCache[value] + cacheLock.RUnlock() + if ok { + return stringx.TakeOne(cache.key, field.Name), cache.options, cache.err + } + + key, options, err := doParseKeyAndOptions(field, value) + cacheLock.Lock() + optionsCache[value] = optionsCacheValue{ + key: key, + options: options, + err: err, + } + cacheLock.Unlock() + + return stringx.TakeOne(key, field.Name), options, err +} + +// support below notations: +// [:5] (:5] [:5) (:5) +// [1:] [1:) (1:] (1:) +// [1:5] [1:5) (1:5] (1:5) +func parseNumberRange(str string) (*numberRange, error) { + if len(str) == 0 { + return nil, errNumberRange + } + + leftInclude, err := isLeftInclude(str[0]) + if err != nil { + return nil, err + } + + str = str[1:] + if len(str) == 0 { + return nil, errNumberRange + } + + rightInclude, err := isRightInclude(str[len(str)-1]) + if err != nil { + return nil, err + } + + str = str[:len(str)-1] + fields := strings.Split(str, ":") + if len(fields) != 2 { + return nil, errNumberRange + } + + if len(fields[0]) == 0 && len(fields[1]) == 0 { + return nil, errNumberRange + } + + var left float64 + if len(fields[0]) > 0 { + var err error + if left, err = strconv.ParseFloat(fields[0], 64); err != nil { + return nil, err + } + } else { + left = -math.MaxFloat64 + } + + var right float64 + if len(fields[1]) > 0 { + var err error + if right, err = strconv.ParseFloat(fields[1], 64); err != nil { + return nil, err + } + } else { + right = math.MaxFloat64 + } + + if left > right { + return nil, errNumberRange + } + + // [2:2] valid + // [2:2) invalid + // (2:2] invalid + // (2:2) invalid + if left == right { + if !leftInclude || !rightInclude { + return nil, errNumberRange + } + } + + return &numberRange{ + left: left, + leftInclude: leftInclude, + right: right, + rightInclude: rightInclude, + }, nil +} + +func parseOption(fieldOpts *fieldOptions, fieldName, option string) error { + switch { + case option == inheritOption: + fieldOpts.Inherit = true + case option == stringOption: + fieldOpts.FromString = true + case strings.HasPrefix(option, optionalOption): + segs := strings.Split(option, equalToken) + switch len(segs) { + case 1: + fieldOpts.Optional = true + case 2: + fieldOpts.Optional = true + fieldOpts.OptionalDep = segs[1] + default: + return fmt.Errorf("field %s has wrong optional", fieldName) + } + case option == optionalOption: + fieldOpts.Optional = true + case strings.HasPrefix(option, optionsOption): + val, err := parseProperty(fieldName, optionsOption, option) + if err != nil { + return err + } + + fieldOpts.Options = parseOptions(val) + case strings.HasPrefix(option, defaultOption): + val, err := parseProperty(fieldName, defaultOption, option) + if err != nil { + return err + } + + fieldOpts.Default = val + case strings.HasPrefix(option, envOption): + val, err := parseProperty(fieldName, envOption, option) + if err != nil { + return err + } + + fieldOpts.EnvVar = val + case strings.HasPrefix(option, rangeOption): + val, err := parseProperty(fieldName, rangeOption, option) + if err != nil { + return err + } + + nr, err := parseNumberRange(val) + if err != nil { + return err + } + + fieldOpts.Range = nr + } + + return nil +} + +// parseOptions parses the given options in tag. +// for example: `json:"name,options=foo|bar"` or `json:"name,options=[foo,bar]"` +func parseOptions(val string) []string { + if len(val) == 0 { + return nil + } + + if val[0] == leftSquareBracket { + return parseGroupedSegments(val) + } + + return strings.Split(val, optionSeparator) +} + +func parseProperty(field, tag, val string) (string, error) { + segs := strings.Split(val, equalToken) + if len(segs) != 2 { + return "", fmt.Errorf("field %s has wrong %s", field, tag) + } + + return strings.TrimSpace(segs[1]), nil +} + +func parseSegments(val string) []string { + var segments []string + var escaped, grouped bool + var buf strings.Builder + + for _, ch := range val { + if escaped { + buf.WriteRune(ch) + escaped = false + continue + } + + switch ch { + case segmentSeparator: + if grouped { + buf.WriteRune(ch) + } else { + // need to trim spaces, but we cannot ignore empty string, + // because the first segment stands for the key might be empty. + // if ignored, the later tag will be used as the key. + segments = append(segments, strings.TrimSpace(buf.String())) + buf.Reset() + } + case escapeChar: + if grouped { + buf.WriteRune(ch) + } else { + escaped = true + } + case leftBracket, leftSquareBracket: + buf.WriteRune(ch) + grouped = true + case rightBracket, rightSquareBracket: + buf.WriteRune(ch) + grouped = false + default: + buf.WriteRune(ch) + } + } + + last := strings.TrimSpace(buf.String()) + // ignore last empty string + if len(last) > 0 { + segments = append(segments, last) + } + + return segments +} + +func setMatchedPrimitiveValue(kind reflect.Kind, value reflect.Value, v any) error { + switch kind { + case reflect.Bool: + value.SetBool(v.(bool)) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + value.SetInt(v.(int64)) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + value.SetUint(v.(uint64)) + case reflect.Float32, reflect.Float64: + value.SetFloat(v.(float64)) + case reflect.String: + value.SetString(v.(string)) + default: + return errUnsupportedType + } + + return nil +} + +func setValueFromString(kind reflect.Kind, value reflect.Value, str string) error { + if !value.CanSet() { + return errValueNotSettable + } + + value = ensureValue(value) + v, err := convertTypeFromString(kind, str) + if err != nil { + return err + } + + return setMatchedPrimitiveValue(kind, value, v) +} + +func structValueRequired(tag string, tp reflect.Type) (bool, error) { + structCacheLock.RLock() + val, ok := structRequiredCache[tp] + structCacheLock.RUnlock() + if ok { + return val.required, val.err + } + + required, err := implicitValueRequiredStruct(tag, tp) + structCacheLock.Lock() + structRequiredCache[tp] = requiredCacheValue{ + required: required, + err: err, + } + structCacheLock.Unlock() + + return required, err +} + +func toFloat64(v any) (float64, bool) { + switch val := v.(type) { + case int: + return float64(val), true + case int8: + return float64(val), true + case int16: + return float64(val), true + case int32: + return float64(val), true + case int64: + return float64(val), true + case uint: + return float64(val), true + case uint8: + return float64(val), true + case uint16: + return float64(val), true + case uint32: + return float64(val), true + case uint64: + return float64(val), true + case float32: + return float64(val), true + case float64: + return val, true + default: + return 0, false + } +} + +func usingDifferentKeys(key string, field reflect.StructField) bool { + if len(field.Tag) > 0 { + if _, ok := field.Tag.Lookup(key); !ok { + return true + } + } + + return false +} + +func validateAndSetValue(kind reflect.Kind, value reflect.Value, str string, opts *fieldOptionsWithContext) error { + if !value.CanSet() { + return errValueNotSettable + } + + v, err := convertTypeFromString(kind, str) + if err != nil { + return err + } + + if err := validateValueRange(v, opts); err != nil { + return err + } + + return setMatchedPrimitiveValue(kind, value, v) +} + +func validateJsonNumberRange(v json.Number, opts *fieldOptionsWithContext) error { + if opts == nil || opts.Range == nil { + return nil + } + + fv, err := v.Float64() + if err != nil { + return err + } + + return validateNumberRange(fv, opts.Range) +} + +func validateNumberRange(fv float64, nr *numberRange) error { + if nr == nil { + return nil + } + + if (nr.leftInclude && fv < nr.left) || (!nr.leftInclude && fv <= nr.left) { + return errNumberRange + } + + if (nr.rightInclude && fv > nr.right) || (!nr.rightInclude && fv >= nr.right) { + return errNumberRange + } + + return nil +} + +func validateValueInOptions(val any, options []string) error { + if len(options) > 0 { + switch v := val.(type) { + case string: + if !stringx.Contains(options, v) { + return fmt.Errorf(`error: value "%s" is not defined in options "%v"`, v, options) + } + default: + if !stringx.Contains(options, Repr(v)) { + return fmt.Errorf(`error: value "%v" is not defined in options "%v"`, val, options) + } + } + } + + return nil +} + +func validateValueRange(mapValue any, opts *fieldOptionsWithContext) error { + if opts == nil || opts.Range == nil { + return nil + } + + fv, ok := toFloat64(mapValue) + if !ok { + return errNumberRange + } + + return validateNumberRange(fv, opts.Range) +} diff --git a/core/mappingx/valuer.go b/core/mappingx/valuer.go new file mode 100644 index 0000000..ef3dc83 --- /dev/null +++ b/core/mappingx/valuer.go @@ -0,0 +1,112 @@ +package mappingx + +type ( + // A Valuer interface defines the way to get values from the underlying object with keys. + Valuer interface { + // Value gets the value associated with the given key. + Value(key string) (any, bool) + } + + // A valuerWithParent defines a node that has a parent node. + valuerWithParent interface { + Valuer + // Parent get the parent valuer for current node. + Parent() valuerWithParent + } + + // A node is a map that can use Value method to get values with given keys. + node struct { + current Valuer + parent valuerWithParent + } + + // A valueWithParent is used to wrap the value with its parent. + valueWithParent struct { + value any + parent valuerWithParent + } + + // mapValuer is a type for map to meet the Valuer interface. + mapValuer map[string]any + // simpleValuer is a type to get value from current node. + simpleValuer node + // recursiveValuer is a type to get the value recursively from current and parent nodes. + recursiveValuer node +) + +// Value gets the value assciated with the given key from mv. +func (mv mapValuer) Value(key string) (any, bool) { + v, ok := mv[key] + return v, ok +} + +// Value gets the value associated with the given key from sv. +func (sv simpleValuer) Value(key string) (any, bool) { + v, ok := sv.current.Value(key) + return v, ok +} + +// Parent get the parent valuer from sv. +func (sv simpleValuer) Parent() valuerWithParent { + if sv.parent == nil { + return nil + } + + return recursiveValuer{ + current: sv.parent, + parent: sv.parent.Parent(), + } +} + +// Value gets the value associated with the given key from rv, +// and it will inherit the value from parent nodes. +func (rv recursiveValuer) Value(key string) (any, bool) { + val, ok := rv.current.Value(key) + if !ok { + if parent := rv.Parent(); parent != nil { + return parent.Value(key) + } + + return nil, false + } + + vm, ok := val.(map[string]any) + if !ok { + return val, true + } + + parent := rv.Parent() + if parent == nil { + return val, true + } + + pv, ok := parent.Value(key) + if !ok { + return val, true + } + + pm, ok := pv.(map[string]any) + if !ok { + return val, true + } + + for k, v := range pm { + if _, ok := vm[k]; !ok { + vm[k] = v + } + } + + return vm, true +} + +// Parent get the parent valuer from rv. +func (rv recursiveValuer) Parent() valuerWithParent { + if rv.parent == nil { + return nil + } + + return recursiveValuer{ + current: rv.parent, + parent: rv.parent.Parent(), + } +} diff --git a/core/rescue/recover.go b/core/rescue/recover.go new file mode 100644 index 0000000..e39b19b --- /dev/null +++ b/core/rescue/recover.go @@ -0,0 +1,15 @@ +package rescue + +// Recover is used with defer to do cleanup on panics. +// Use it like: +// +// defer Recover(func() {}) +func Recover(cleanups ...func()) { + for _, cleanup := range cleanups { + cleanup() + } + + if p := recover(); p != nil { + print(p) + } +} diff --git a/core/stringx/node.go b/core/stringx/node.go new file mode 100644 index 0000000..c6fdb9a --- /dev/null +++ b/core/stringx/node.go @@ -0,0 +1,98 @@ +package stringx + +type node struct { + children map[rune]*node + fail *node + depth int + end bool +} + +func (n *node) add(word string) { + chars := []rune(word) + if len(chars) == 0 { + return + } + + nd := n + for i, char := range chars { + if nd.children == nil { + child := new(node) + child.depth = i + 1 + nd.children = map[rune]*node{char: child} + nd = child + } else if child, ok := nd.children[char]; ok { + nd = child + } else { + child := new(node) + child.depth = i + 1 + nd.children[char] = child + nd = child + } + } + + nd.end = true +} + +func (n *node) build() { + var nodes []*node + for _, child := range n.children { + child.fail = n + nodes = append(nodes, child) + } + for len(nodes) > 0 { + nd := nodes[0] + nodes = nodes[1:] + for key, child := range nd.children { + nodes = append(nodes, child) + cur := nd + for cur != nil { + if cur.fail == nil { + child.fail = n + break + } + if fail, ok := cur.fail.children[key]; ok { + child.fail = fail + break + } + cur = cur.fail + } + } + } +} + +func (n *node) find(chars []rune) []scope { + var scopes []scope + size := len(chars) + cur := n + + for i := 0; i < size; i++ { + child, ok := cur.children[chars[i]] + if ok { + cur = child + } else { + for cur != n { + cur = cur.fail + if child, ok = cur.children[chars[i]]; ok { + cur = child + break + } + } + + if child == nil { + continue + } + } + + for child != n { + if child.end { + scopes = append(scopes, scope{ + start: i + 1 - child.depth, + stop: i + 1, + }) + } + child = child.fail + } + } + + return scopes +} diff --git a/core/stringx/random.go b/core/stringx/random.go new file mode 100644 index 0000000..d479b73 --- /dev/null +++ b/core/stringx/random.go @@ -0,0 +1,96 @@ +package stringx + +import ( + crand "crypto/rand" + "fmt" + "math/rand" + "strings" + "sync" + "time" +) + +const ( + letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + letterIdxBits = 6 // 6 bits to represent a letter index + idLen = 8 + defaultRandLen = 8 + letterIdxMask = 1<= 0; { + if remain == 0 { + cache, remain = src.Int63(), letterIdxMax + } + if idx := int(cache & letterIdxMask); idx < len(letterBytes) { + b[i] = letterBytes[idx] + i-- + } + cache >>= letterIdxBits + remain-- + } + + return string(b) +} + +// Seed sets the seed to seed. +func Seed(seed int64) { + src.Seed(seed) +} + +func GenValidateCode(width int) string { + numeric := [10]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} + r := len(numeric) + rand.Seed(time.Now().UnixNano()) + + var sb strings.Builder + for i := 0; i < width; i++ { + _, _ = fmt.Fprintf(&sb, "%d", numeric[rand.Intn(r)]) + } + return sb.String() +} diff --git a/core/stringx/replacer.go b/core/stringx/replacer.go new file mode 100644 index 0000000..efb2c70 --- /dev/null +++ b/core/stringx/replacer.go @@ -0,0 +1,84 @@ +package stringx + +import ( + "sort" + "strings" +) + +// replace more than once to avoid overlapped keywords after replace. +// only try 2 times to avoid too many or infinite loops. +const replaceTimes = 2 + +type ( + // Replacer interface wraps the Replace method. + Replacer interface { + Replace(text string) string + } + + replacer struct { + *node + mapping map[string]string + } +) + +// NewReplacer returns a Replacer. +func NewReplacer(mapping map[string]string) Replacer { + rep := &replacer{ + node: new(node), + mapping: mapping, + } + for k := range mapping { + rep.add(k) + } + rep.build() + + return rep +} + +// Replace replaces text with given substitutes. +func (r *replacer) Replace(text string) string { + for i := 0; i < replaceTimes; i++ { + var replaced bool + if text, replaced = r.doReplace(text); !replaced { + return text + } + } + + return text +} + +func (r *replacer) doReplace(text string) (string, bool) { + chars := []rune(text) + scopes := r.find(chars) + if len(scopes) == 0 { + return text, false + } + + sort.Slice(scopes, func(i, j int) bool { + if scopes[i].start < scopes[j].start { + return true + } + if scopes[i].start == scopes[j].start { + return scopes[i].stop > scopes[j].stop + } + return false + }) + + var buf strings.Builder + var index int + for i := 0; i < len(scopes); i++ { + scp := &scopes[i] + if scp.start < index { + continue + } + + buf.WriteString(string(chars[index:scp.start])) + buf.WriteString(r.mapping[string(chars[scp.start:scp.stop])]) + index = scp.stop + } + if index < len(chars) { + buf.WriteString(string(chars[index:])) + } + + return buf.String(), true +} diff --git a/core/stringx/strings.go b/core/stringx/strings.go new file mode 100644 index 0000000..09f64b3 --- /dev/null +++ b/core/stringx/strings.go @@ -0,0 +1,197 @@ +package stringx + +import ( + "errors" + "supreme-flamego/core/langx" + "unicode" +) + +var ( + // ErrInvalidStartPosition is an error that indicates the start position is invalid. + ErrInvalidStartPosition = errors.New("start position is invalid") + // ErrInvalidStopPosition is an error that indicates the stop position is invalid. + ErrInvalidStopPosition = errors.New("stop position is invalid") +) + +// Contains checks if str is in list. +func Contains(list []string, str string) bool { + for _, each := range list { + if each == str { + return true + } + } + + return false +} + +// Filter filters chars from s with given filter function. +func Filter(s string, filter func(r rune) bool) string { + var n int + chars := []rune(s) + for i, x := range chars { + if n < i { + chars[n] = x + } + if !filter(x) { + n++ + } + } + + return string(chars[:n]) +} + +// FirstN returns first n runes from s. +func FirstN(s string, n int, ellipsis ...string) string { + var i int + + for j := range s { + if i == n { + ret := s[:j] + for _, each := range ellipsis { + ret += each + } + return ret + } + i++ + } + + return s +} + +// HasEmpty checks if there are empty strings in args. +func HasEmpty(args ...string) bool { + for _, arg := range args { + if len(arg) == 0 { + return true + } + } + + return false +} + +// Join joins any number of elements into a single string, separating them with given sep. +// Empty elements are ignored. However, if the argument list is empty or all its elements are empty, +// Join returns an empty string. +func Join(sep byte, elem ...string) string { + var size int + for _, e := range elem { + size += len(e) + } + if size == 0 { + return "" + } + + buf := make([]byte, 0, size+len(elem)-1) + for _, e := range elem { + if len(e) == 0 { + continue + } + + if len(buf) > 0 { + buf = append(buf, sep) + } + buf = append(buf, e...) + } + + return string(buf) +} + +// NotEmpty checks if all strings are not empty in args. +func NotEmpty(args ...string) bool { + return !HasEmpty(args...) +} + +// Remove removes given strs from strings. +func Remove(strings []string, strs ...string) []string { + out := append([]string(nil), strings...) + + for _, str := range strs { + var n int + for _, v := range out { + if v != str { + out[n] = v + n++ + } + } + out = out[:n] + } + + return out +} + +// Reverse reverses s. +func Reverse(s string) string { + runes := []rune(s) + + for from, to := 0, len(runes)-1; from < to; from, to = from+1, to-1 { + runes[from], runes[to] = runes[to], runes[from] + } + + return string(runes) +} + +// Substr returns runes between start and stop [start, stop) +// regardless of the chars are ascii or utf8. +func Substr(str string, start, stop int) (string, error) { + rs := []rune(str) + length := len(rs) + + if start < 0 || start > length { + return "", ErrInvalidStartPosition + } + + if stop < 0 || stop > length { + return "", ErrInvalidStopPosition + } + + return string(rs[start:stop]), nil +} + +// TakeOne returns valid string if not empty or later one. +func TakeOne(valid, or string) string { + if len(valid) > 0 { + return valid + } + + return or +} + +// TakeWithPriority returns the first not empty result from fns. +func TakeWithPriority(fns ...func() string) string { + for _, fn := range fns { + val := fn() + if len(val) > 0 { + return val + } + } + + return "" +} + +// ToCamelCase returns the string that converts the first letter to lowercase. +func ToCamelCase(s string) string { + for i, v := range s { + return string(unicode.ToLower(v)) + s[i+1:] + } + + return "" +} + +// Union merges the strings in first and second. +func Union(first, second []string) []string { + set := make(map[string]langx.PlaceholderType) + + for _, each := range first { + set[each] = langx.Placeholder + } + for _, each := range second { + set[each] = langx.Placeholder + } + + merged := make([]string, 0, len(set)) + for k := range set { + merged = append(merged, k) + } + + return merged +} diff --git a/core/stringx/trie.go b/core/stringx/trie.go new file mode 100644 index 0000000..85ffb3c --- /dev/null +++ b/core/stringx/trie.go @@ -0,0 +1,101 @@ +package stringx + +import "supreme-flamego/core/langx" + +const defaultMask = '*' + +type ( + // TrieOption defines the method to customize a Trie. + TrieOption func(trie *trieNode) + + // A Trie is a tree implementation that used to find elements rapidly. + Trie interface { + Filter(text string) (string, []string, bool) + FindKeywords(text string) []string + } + + trieNode struct { + node + mask rune + } + + scope struct { + start int + stop int + } +) + +// NewTrie returns a Trie. +func NewTrie(words []string, opts ...TrieOption) Trie { + n := new(trieNode) + + for _, opt := range opts { + opt(n) + } + if n.mask == 0 { + n.mask = defaultMask + } + for _, word := range words { + n.add(word) + } + + n.build() + + return n +} + +func (n *trieNode) Filter(text string) (sentence string, keywords []string, found bool) { + chars := []rune(text) + if len(chars) == 0 { + return text, nil, false + } + + scopes := n.find(chars) + keywords = n.collectKeywords(chars, scopes) + + for _, match := range scopes { + // we don't care about overlaps, not bringing a performance improvement + n.replaceWithAsterisk(chars, match.start, match.stop) + } + + return string(chars), keywords, len(keywords) > 0 +} + +func (n *trieNode) FindKeywords(text string) []string { + chars := []rune(text) + if len(chars) == 0 { + return nil + } + + scopes := n.find(chars) + return n.collectKeywords(chars, scopes) +} + +func (n *trieNode) collectKeywords(chars []rune, scopes []scope) []string { + set := make(map[string]langx.PlaceholderType) + for _, v := range scopes { + set[string(chars[v.start:v.stop])] = langx.Placeholder + } + + var i int + keywords := make([]string, len(set)) + for k := range set { + keywords[i] = k + i++ + } + + return keywords +} + +func (n *trieNode) replaceWithAsterisk(chars []rune, start, stop int) { + for i := start; i < stop; i++ { + chars[i] = n.mask + } +} + +// WithMask customizes a Trie with keywords masked as given mask char. +func WithMask(mask rune) TrieOption { + return func(n *trieNode) { + n.mask = mask + } +} diff --git a/core/stringx/wrap.go b/core/stringx/wrap.go new file mode 100644 index 0000000..50ae58c --- /dev/null +++ b/core/stringx/wrap.go @@ -0,0 +1,72 @@ +package stringx + +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) +} diff --git a/core/syncx/atomicbool.go b/core/syncx/atomicbool.go new file mode 100644 index 0000000..7e489d9 --- /dev/null +++ b/core/syncx/atomicbool.go @@ -0,0 +1,44 @@ +package syncx + +import "sync/atomic" + +// An AtomicBool is an atomic implementation for boolean values. +type AtomicBool uint32 + +// NewAtomicBool returns an AtomicBool. +func NewAtomicBool() *AtomicBool { + return new(AtomicBool) +} + +// ForAtomicBool returns an AtomicBool with given val. +func ForAtomicBool(val bool) *AtomicBool { + b := NewAtomicBool() + b.Set(val) + return b +} + +// CompareAndSwap compares current value with given old, if equals, set to given val. +func (b *AtomicBool) CompareAndSwap(old, val bool) bool { + var ov, nv uint32 + if old { + ov = 1 + } + if val { + nv = 1 + } + return atomic.CompareAndSwapUint32((*uint32)(b), ov, nv) +} + +// Set sets the value to v. +func (b *AtomicBool) Set(v bool) { + if v { + atomic.StoreUint32((*uint32)(b), 1) + } else { + atomic.StoreUint32((*uint32)(b), 0) + } +} + +// True returns true if current value is true. +func (b *AtomicBool) True() bool { + return atomic.LoadUint32((*uint32)(b)) == 1 +} diff --git a/core/syncx/atomicduration.go b/core/syncx/atomicduration.go new file mode 100644 index 0000000..3ae12c6 --- /dev/null +++ b/core/syncx/atomicduration.go @@ -0,0 +1,36 @@ +package syncx + +import ( + "sync/atomic" + "time" +) + +// An AtomicDuration is an implementation of atomic duration. +type AtomicDuration int64 + +// NewAtomicDuration returns an AtomicDuration. +func NewAtomicDuration() *AtomicDuration { + return new(AtomicDuration) +} + +// ForAtomicDuration returns an AtomicDuration with given value. +func ForAtomicDuration(val time.Duration) *AtomicDuration { + d := NewAtomicDuration() + d.Set(val) + return d +} + +// CompareAndSwap compares current value with old, if equals, set the value to val. +func (d *AtomicDuration) CompareAndSwap(old, val time.Duration) bool { + return atomic.CompareAndSwapInt64((*int64)(d), int64(old), int64(val)) +} + +// Load loads the current duration. +func (d *AtomicDuration) Load() time.Duration { + return time.Duration(atomic.LoadInt64((*int64)(d))) +} + +// Set sets the value to val. +func (d *AtomicDuration) Set(val time.Duration) { + atomic.StoreInt64((*int64)(d), int64(val)) +} diff --git a/core/syncx/donechan.go b/core/syncx/donechan.go new file mode 100644 index 0000000..15b37b2 --- /dev/null +++ b/core/syncx/donechan.go @@ -0,0 +1,31 @@ +package syncx + +import ( + "supreme-flamego/core/langx" + "sync" +) + +// A DoneChan is used as a channel that can be closed multiple times and wait for done. +type DoneChan struct { + done chan langx.PlaceholderType + once sync.Once +} + +// NewDoneChan returns a DoneChan. +func NewDoneChan() *DoneChan { + return &DoneChan{ + done: make(chan langx.PlaceholderType), + } +} + +// Close closes dc, it's safe to close more than once. +func (dc *DoneChan) Close() { + dc.once.Do(func() { + close(dc.done) + }) +} + +// Done returns a channel that can be notified on dc closed. +func (dc *DoneChan) Done() chan langx.PlaceholderType { + return dc.done +} diff --git a/core/syncx/resourcemanager.go b/core/syncx/resourcemanager.go new file mode 100644 index 0000000..585eb0c --- /dev/null +++ b/core/syncx/resourcemanager.go @@ -0,0 +1,76 @@ +package syncx + +import ( + "io" + "supreme-flamego/core/errorx" + "sync" +) + +// A ResourceManager is a manager that used to manage resources. +type ResourceManager struct { + resources map[string]io.Closer + singleFlight SingleFlight + lock sync.RWMutex +} + +// NewResourceManager returns a ResourceManager. +func NewResourceManager() *ResourceManager { + return &ResourceManager{ + resources: make(map[string]io.Closer), + singleFlight: NewSingleFlight(), + } +} + +// Close closes the manager. +// Don't use the ResourceManager after Close() called. +func (manager *ResourceManager) Close() error { + manager.lock.Lock() + defer manager.lock.Unlock() + + var be errorx.BatchError + for _, resource := range manager.resources { + if err := resource.Close(); err != nil { + be.Add(err) + } + } + + // release resources to avoid using it later + manager.resources = nil + + return be.Err() +} + +// GetResource returns the resource associated with given key. +func (manager *ResourceManager) GetResource(key string, create func() (io.Closer, error)) (io.Closer, error) { + val, err := manager.singleFlight.Do(key, func() (any, error) { + manager.lock.RLock() + resource, ok := manager.resources[key] + manager.lock.RUnlock() + if ok { + return resource, nil + } + + resource, err := create() + if err != nil { + return nil, err + } + + manager.lock.Lock() + defer manager.lock.Unlock() + manager.resources[key] = resource + + return resource, nil + }) + if err != nil { + return nil, err + } + + return val.(io.Closer), nil +} + +// Inject injects the resource associated with given key. +func (manager *ResourceManager) Inject(key string, resource io.Closer) { + manager.lock.Lock() + manager.resources[key] = resource + manager.lock.Unlock() +} diff --git a/core/syncx/singleflight.go b/core/syncx/singleflight.go new file mode 100644 index 0000000..cbe62c6 --- /dev/null +++ b/core/syncx/singleflight.go @@ -0,0 +1,81 @@ +package syncx + +import "sync" + +type ( + // SingleFlight lets the concurrent calls with the same key to share the call result. + // For example, A called F, before it's done, B called F. Then B would not execute F, + // and shared the result returned by F which called by A. + // The calls with the same key are dependent, concurrent calls share the returned values. + // A ------->calls F with key<------------------->returns val + // B --------------------->calls F with key------>returns val + SingleFlight interface { + Do(key string, fn func() (any, error)) (any, error) + DoEx(key string, fn func() (any, error)) (any, bool, error) + } + + call struct { + wg sync.WaitGroup + val any + err error + } + + flightGroup struct { + calls map[string]*call + lock sync.Mutex + } +) + +// NewSingleFlight returns a SingleFlight. +func NewSingleFlight() SingleFlight { + return &flightGroup{ + calls: make(map[string]*call), + } +} + +func (g *flightGroup) Do(key string, fn func() (any, error)) (any, error) { + c, done := g.createCall(key) + if done { + return c.val, c.err + } + + g.makeCall(c, key, fn) + return c.val, c.err +} + +func (g *flightGroup) DoEx(key string, fn func() (any, error)) (val any, fresh bool, err error) { + c, done := g.createCall(key) + if done { + return c.val, false, c.err + } + + g.makeCall(c, key, fn) + return c.val, true, c.err +} + +func (g *flightGroup) createCall(key string) (c *call, done bool) { + g.lock.Lock() + if c, ok := g.calls[key]; ok { + g.lock.Unlock() + c.wg.Wait() + return c, true + } + + c = new(call) + c.wg.Add(1) + g.calls[key] = c + g.lock.Unlock() + + return c, false +} + +func (g *flightGroup) makeCall(c *call, key string, fn func() (any, error)) { + defer func() { + g.lock.Lock() + delete(g.calls, key) + g.lock.Unlock() + c.wg.Done() + }() + + c.val, c.err = fn() +} diff --git a/core/systemx/env.go b/core/systemx/env.go new file mode 100644 index 0000000..1a68d1d --- /dev/null +++ b/core/systemx/env.go @@ -0,0 +1,45 @@ +package systemx + +import ( + "os" + "strconv" + "sync" +) + +var ( + envs = make(map[string]string) + envLock sync.RWMutex +) + +// Env returns the value of the given environment variable. +func Env(name string) string { + envLock.RLock() + val, ok := envs[name] + envLock.RUnlock() + + if ok { + return val + } + + val = os.Getenv(name) + envLock.Lock() + envs[name] = val + envLock.Unlock() + + return val +} + +// EnvInt returns an int value of the given environment variable. +func EnvInt(name string) (int, bool) { + val := Env(name) + if len(val) == 0 { + return 0, false + } + + n, err := strconv.Atoi(val) + if err != nil { + return 0, false + } + + return n, true +} diff --git a/core/systemx/hostname.go b/core/systemx/hostname.go new file mode 100644 index 0000000..5568b78 --- /dev/null +++ b/core/systemx/hostname.go @@ -0,0 +1,21 @@ +package systemx + +import ( + "os" + "supreme-flamego/core/stringx" +) + +var hostname string + +func init() { + var err error + hostname, err = os.Hostname() + if err != nil { + hostname = stringx.RandId() + } +} + +// Hostname returns the name of the host, if no hostname, a random id is returned. +func Hostname() string { + return hostname +} diff --git a/core/systemx/shutdown_linux.go b/core/systemx/shutdown_linux.go new file mode 100644 index 0000000..29cfec3 --- /dev/null +++ b/core/systemx/shutdown_linux.go @@ -0,0 +1,100 @@ +//go:build linux || darwin +// +build linux darwin + +package systemx + +import ( + "fmt" + "github.com/charmbracelet/log" + "os" + "os/signal" + "supreme-flamego/core/threadx" + "sync" + "syscall" + "time" +) + +const ( + wrapUpTime = time.Second + // why we use 5500 milliseconds is because most of our queue are blocking mode with 5 seconds + waitTime = 5500 * time.Millisecond +) + +var ( + wrapUpListeners = new(listenerManager) + shutdownListeners = new(listenerManager) + delayTimeBeforeForceQuit = waitTime +) + +// AddShutdownListener adds fn as a shutdown listener. +// The returned func can be used to wait for fn getting called. +func AddShutdownListener(fn func()) (waitForCalled func()) { + return shutdownListeners.addListener(fn) +} + +// AddWrapUpListener adds fn as a wrap up listener. +// The returned func can be used to wait for fn getting called. +func AddWrapUpListener(fn func()) (waitForCalled func()) { + return wrapUpListeners.addListener(fn) +} + +// SetTimeToForceQuit sets the waiting time before force quitting. +func SetTimeToForceQuit(duration time.Duration) { + delayTimeBeforeForceQuit = duration +} + +// Shutdown calls the registered shutdown listeners, only for test purpose. +func Shutdown() { + shutdownListeners.notifyListeners() +} + +// WrapUp wraps up the process, only for test purpose. +func WrapUp() { + wrapUpListeners.notifyListeners() +} + +func gracefulStop(signals chan os.Signal) { + signal.Stop(signals) + + log.Info("Got signal SIGTERM, shutting down...") + go wrapUpListeners.notifyListeners() + + time.Sleep(wrapUpTime) + go shutdownListeners.notifyListeners() + + time.Sleep(delayTimeBeforeForceQuit - wrapUpTime) + log.Info(fmt.Sprintf("Still alive after %v, going to force kill the process...", delayTimeBeforeForceQuit)) + syscall.Kill(syscall.Getpid(), syscall.SIGTERM) +} + +type listenerManager struct { + lock sync.Mutex + waitGroup sync.WaitGroup + listeners []func() +} + +func (lm *listenerManager) addListener(fn func()) (waitForCalled func()) { + lm.waitGroup.Add(1) + + lm.lock.Lock() + lm.listeners = append(lm.listeners, func() { + defer lm.waitGroup.Done() + fn() + }) + lm.lock.Unlock() + + return func() { + lm.waitGroup.Wait() + } +} + +func (lm *listenerManager) notifyListeners() { + lm.lock.Lock() + defer lm.lock.Unlock() + + group := threadx.NewRoutineGroup() + for _, listener := range lm.listeners { + group.RunSafe(listener) + } + group.Wait() +} diff --git a/core/systemx/shutdown_win.go b/core/systemx/shutdown_win.go new file mode 100644 index 0000000..cc857ad --- /dev/null +++ b/core/systemx/shutdown_win.go @@ -0,0 +1,102 @@ +//go:build windows + +package systemx + +import ( + "fmt" + "github.com/charmbracelet/log" + "os" + "os/signal" + "supreme-flamego/core/threadx" + "sync" + "time" +) + +const ( + wrapUpTime = time.Second + // why we use 5500 milliseconds is because most of our queue are blocking mode with 5 seconds + waitTime = 5500 * time.Millisecond +) + +var ( + wrapUpListeners = new(listenerManager) + shutdownListeners = new(listenerManager) + delayTimeBeforeForceQuit = waitTime +) + +// AddShutdownListener adds fn as a shutdown listener. +// The returned func can be used to wait for fn getting called. +func AddShutdownListener(fn func()) (waitForCalled func()) { + return shutdownListeners.addListener(fn) +} + +// AddWrapUpListener adds fn as a wrap up listener. +// The returned func can be used to wait for fn getting called. +func AddWrapUpListener(fn func()) (waitForCalled func()) { + return wrapUpListeners.addListener(fn) +} + +// SetTimeToForceQuit sets the waiting time before force quitting. +func SetTimeToForceQuit(duration time.Duration) { + delayTimeBeforeForceQuit = duration +} + +// Shutdown calls the registered shutdown listeners, only for test purpose. +func Shutdown() { + shutdownListeners.notifyListeners() +} + +// WrapUp wraps up the process, only for test purpose. +func WrapUp() { + wrapUpListeners.notifyListeners() +} + +func gracefulStop(signals chan os.Signal) { + signal.Stop(signals) + + log.Info("Got signal SIGTERM, shutting down...") + go wrapUpListeners.notifyListeners() + + time.Sleep(wrapUpTime) + go shutdownListeners.notifyListeners() + + time.Sleep(delayTimeBeforeForceQuit - wrapUpTime) + log.Info(fmt.Sprintf("Still alive after %v, going to force kill the process...", delayTimeBeforeForceQuit)) + // 替代syscall.Kill(syscall.Getpid(), syscall.SIGTERM)的代码 + p, err := os.FindProcess(os.Getpid()) + if err == nil { + p.Signal(os.Interrupt) + } +} + +type listenerManager struct { + lock sync.Mutex + waitGroup sync.WaitGroup + listeners []func() +} + +func (lm *listenerManager) addListener(fn func()) (waitForCalled func()) { + lm.waitGroup.Add(1) + + lm.lock.Lock() + lm.listeners = append(lm.listeners, func() { + defer lm.waitGroup.Done() + fn() + }) + lm.lock.Unlock() + + return func() { + lm.waitGroup.Wait() + } +} + +func (lm *listenerManager) notifyListeners() { + lm.lock.Lock() + defer lm.lock.Unlock() + + group := threadx.NewRoutineGroup() + for _, listener := range lm.listeners { + group.RunSafe(listener) + } + group.Wait() +} diff --git a/core/threadx/routine.go b/core/threadx/routine.go new file mode 100644 index 0000000..1a455f2 --- /dev/null +++ b/core/threadx/routine.go @@ -0,0 +1,32 @@ +package threadx + +import ( + "bytes" + "runtime" + "strconv" + "supreme-flamego/core/rescue" +) + +// GoSafe runs the given fn using another goroutine, recovers if fn panics. +func GoSafe(fn func()) { + go RunSafe(fn) +} + +// RoutineId is only for debug, never use it in production. +func RoutineId() uint64 { + b := make([]byte, 64) + b = b[:runtime.Stack(b, false)] + b = bytes.TrimPrefix(b, []byte("goroutine ")) + b = b[:bytes.IndexByte(b, ' ')] + // if error, just return 0 + n, _ := strconv.ParseUint(string(b), 10, 64) + + return n +} + +// RunSafe runs the given fn, recovers if fn panics. +func RunSafe(fn func()) { + defer rescue.Recover() + + fn() +} diff --git a/core/threadx/routinegroup.go b/core/threadx/routinegroup.go new file mode 100644 index 0000000..7da5a74 --- /dev/null +++ b/core/threadx/routinegroup.go @@ -0,0 +1,42 @@ +package threadx + +import "sync" + +// A RoutineGroup is used to group goroutines together and all wait all goroutines to be done. +type RoutineGroup struct { + waitGroup sync.WaitGroup +} + +// NewRoutineGroup returns a RoutineGroup. +func NewRoutineGroup() *RoutineGroup { + return new(RoutineGroup) +} + +// Run runs the given fn in RoutineGroup. +// Don't reference the variables from outside, +// because outside variables can be changed by other goroutines +func (g *RoutineGroup) Run(fn func()) { + g.waitGroup.Add(1) + + go func() { + defer g.waitGroup.Done() + fn() + }() +} + +// RunSafe runs the given fn in RoutineGroup, and avoid panics. +// Don't reference the variables from outside, +// because outside variables can be changed by other goroutines +func (g *RoutineGroup) RunSafe(fn func()) { + g.waitGroup.Add(1) + + GoSafe(func() { + defer g.waitGroup.Done() + fn() + }) +} + +// Wait waits all running functions to be done. +func (g *RoutineGroup) Wait() { + g.waitGroup.Wait() +} diff --git a/core/timex/relativetime.go b/core/timex/relativetime.go new file mode 100644 index 0000000..23375a0 --- /dev/null +++ b/core/timex/relativetime.go @@ -0,0 +1,17 @@ +package timex + +import "time" + +// Use the long enough past time as start time, in case timex.Now() - lastTime equals 0. +var initTime = time.Now().AddDate(-1, -1, -1) + +// Now returns a relative time duration since initTime, which is not important. +// The caller only needs to care about the relative value. +func Now() time.Duration { + return time.Since(initTime) +} + +// Since returns a diff since given d. +func Since(d time.Duration) time.Duration { + return time.Since(initTime) - d +} diff --git a/core/timex/repr.go b/core/timex/repr.go new file mode 100644 index 0000000..856eb96 --- /dev/null +++ b/core/timex/repr.go @@ -0,0 +1,11 @@ +package timex + +import ( + "fmt" + "time" +) + +// ReprOfDuration returns the string representation of given duration in ms. +func ReprOfDuration(duration time.Duration) string { + return fmt.Sprintf("%.1fms", float32(duration)/float32(time.Millisecond)) +} diff --git a/core/timex/ticker.go b/core/timex/ticker.go new file mode 100644 index 0000000..6210449 --- /dev/null +++ b/core/timex/ticker.go @@ -0,0 +1,79 @@ +package timex + +import ( + "errors" + "supreme-flamego/core/langx" + "time" +) + +// errTimeout indicates a timeout. +var errTimeout = errors.New("timeout") + +type ( + // Ticker interface wraps the Chan and Stop methods. + Ticker interface { + Chan() <-chan time.Time + Stop() + } + + // FakeTicker interface is used for unit testing. + FakeTicker interface { + Ticker + Done() + Tick() + Wait(d time.Duration) error + } + + fakeTicker struct { + c chan time.Time + done chan langx.PlaceholderType + } + + realTicker struct { + *time.Ticker + } +) + +// NewTicker returns a Ticker. +func NewTicker(d time.Duration) Ticker { + return &realTicker{ + Ticker: time.NewTicker(d), + } +} + +func (rt *realTicker) Chan() <-chan time.Time { + return rt.C +} + +// NewFakeTicker returns a FakeTicker. +func NewFakeTicker() FakeTicker { + return &fakeTicker{ + c: make(chan time.Time, 1), + done: make(chan langx.PlaceholderType, 1), + } +} + +func (ft *fakeTicker) Chan() <-chan time.Time { + return ft.c +} + +func (ft *fakeTicker) Done() { + ft.done <- langx.Placeholder +} + +func (ft *fakeTicker) Stop() { + close(ft.c) +} + +func (ft *fakeTicker) Tick() { + ft.c <- time.Now() +} + +func (ft *fakeTicker) Wait(d time.Duration) error { + select { + case <-time.After(d): + return errTimeout + case <-ft.done: + return nil + } +} diff --git a/go.mod b/go.mod index 29acda5..8ee6b1e 100644 --- a/go.mod +++ b/go.mod @@ -4,80 +4,92 @@ go 1.19 require ( github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d - github.com/asjdf/flamego-sentry v0.0.0-20220809062217-41848e0b8de9 + github.com/asjdf/flamego-sentry v0.0.0-20221203170410-58bea485de8e github.com/bwmarrin/snowflake v0.3.0 - github.com/flamego/binding v1.2.0 - github.com/flamego/cors v1.0.2 - github.com/flamego/csrf v1.1.0 - github.com/flamego/flamego v1.7.0 - github.com/flamego/session v1.2.1 - github.com/flamego/template v1.1.0 - github.com/fsnotify/fsnotify v1.5.4 - github.com/getsentry/sentry-go v0.13.0 + github.com/charmbracelet/log v0.2.1 + github.com/fatih/color v1.15.0 + github.com/flamego/flamego v1.9.1 + github.com/fsnotify/fsnotify v1.6.0 + github.com/getsentry/sentry-go v0.19.0 github.com/go-redis/redis v6.15.9+incompatible - github.com/google/uuid v1.1.2 + github.com/go-redis/redis/v8 v8.11.5 github.com/gorilla/websocket v1.5.0 - github.com/guonaihong/gout v0.3.1 - github.com/patrickmn/go-cache v2.1.0+incompatible + github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.2 + github.com/juanjiTech/inject v1.1.0 github.com/pkg/errors v0.9.1 - github.com/robfig/cron v1.2.0 + github.com/soheilhy/cmux v0.1.5 github.com/sony/sonyflake v1.0.0 - github.com/spf13/cobra v1.5.0 - github.com/spf13/viper v1.13.0 - github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.545 - github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/sms v1.0.545 - github.com/tidwall/gjson v1.14.3 - go.uber.org/zap v1.17.0 - golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 - golang.org/x/tools v0.3.0 - gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df - gopkg.in/natefinch/lumberjack.v2 v2.0.0 + github.com/spaolacci/murmur3 v1.1.0 + github.com/spf13/cobra v1.6.1 + github.com/spf13/viper v1.15.0 + github.com/stretchr/testify v1.8.2 + go.etcd.io/etcd/client/v3 v3.5.6 + go.uber.org/zap v1.24.0 + golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be + golang.org/x/tools v0.7.0 + google.golang.org/grpc v1.54.0 + gopkg.in/natefinch/lumberjack.v2 v2.2.1 gopkg.in/yaml.v3 v3.0.1 - gorm.io/datatypes v1.0.7 - gorm.io/driver/mysql v1.3.2 - gorm.io/gorm v1.23.8 - gorm.io/plugin/optimisticlock v1.1.0 + gorm.io/driver/mysql v1.4.7 + gorm.io/gorm v1.24.6 ) require ( - github.com/alecthomas/participle/v2 v2.0.0-beta.5 // indirect - github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/alecthomas/participle/v2 v2.0.0 // indirect + github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/charmbracelet/lipgloss v0.7.1 // indirect + github.com/coreos/go-semver v0.3.0 // indirect + github.com/coreos/go-systemd/v22 v22.3.2 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect - github.com/fatih/color v1.13.0 // indirect - github.com/flamego/validator v1.0.0 // indirect - github.com/go-playground/locales v0.14.0 // indirect - github.com/go-playground/universal-translator v0.18.0 // indirect - github.com/go-playground/validator/v10 v10.11.0 // indirect - github.com/go-redis/redis/v8 v8.11.5 // indirect - github.com/go-sql-driver/mysql v1.6.0 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/glebarez/go-sqlite v1.20.3 // indirect + github.com/glebarez/sqlite v1.7.0 // indirect + github.com/go-logfmt/logfmt v0.6.0 // indirect + github.com/go-sql-driver/mysql v1.7.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/google/uuid v1.3.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect - github.com/inconshreveable/mousetrap v1.0.1 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect - github.com/leodido/go-urn v1.2.1 // indirect - github.com/magiconair/properties v1.8.6 // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect + github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.16 // indirect + github.com/mattn/go-isatty v0.0.18 // indirect + github.com/mattn/go-runewidth v0.0.14 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/muesli/reflow v0.3.0 // indirect + github.com/muesli/termenv v0.15.1 // indirect github.com/onsi/gomega v1.20.2 // indirect github.com/pelletier/go-toml v1.9.5 // indirect - github.com/pelletier/go-toml/v2 v2.0.5 // indirect + github.com/pelletier/go-toml/v2 v2.0.7 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230126093431-47fa9a501578 // indirect + github.com/rivo/uniseg v0.4.4 // indirect github.com/rogpeppe/go-internal v1.9.0 // indirect - github.com/spf13/afero v1.8.2 // indirect + github.com/spf13/afero v1.9.5 // indirect github.com/spf13/cast v1.5.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/subosito/gotenv v1.4.1 // indirect - github.com/tidwall/match v1.1.1 // indirect - github.com/tidwall/pretty v1.2.0 // indirect - go.uber.org/atomic v1.7.0 // indirect - go.uber.org/multierr v1.6.0 // indirect - golang.org/x/mod v0.7.0 // indirect - golang.org/x/net v0.2.0 // indirect - golang.org/x/sys v0.2.0 // indirect - golang.org/x/text v0.4.0 // indirect - google.golang.org/protobuf v1.28.0 // indirect - gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect + github.com/subosito/gotenv v1.4.2 // indirect + go.etcd.io/etcd/api/v3 v3.5.6 // indirect + go.etcd.io/etcd/client/pkg/v3 v3.5.6 // indirect + go.uber.org/atomic v1.10.0 // indirect + go.uber.org/multierr v1.10.0 // indirect + golang.org/x/mod v0.9.0 // indirect + golang.org/x/net v0.8.0 // indirect + golang.org/x/sys v0.6.0 // indirect + golang.org/x/text v0.8.0 // indirect + google.golang.org/genproto v0.0.0-20230323212658-478b75c54725 // indirect + google.golang.org/protobuf v1.30.0 // indirect + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect + modernc.org/libc v1.22.2 // indirect + modernc.org/mathutil v1.5.0 // indirect + modernc.org/memory v1.5.0 // indirect + modernc.org/sqlite v1.20.3 // indirect ) diff --git a/go.sum b/go.sum index ef22b0a..e5cdf57 100644 --- a/go.sum +++ b/go.sum @@ -36,28 +36,44 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/Azure/azure-sdk-for-go/sdk/azcore v0.19.0/go.mod h1:h6H6c8enJmmocHUbLiiGY6sx7f9i+X3m1CHdd5c6Rdw= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.11.0/go.mod h1:HcM1YX14R7CJcghJGOYCgdezslRSVzqwLf/q+4Y2r/0= -github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0/go.mod h1:yqy467j36fJxcRV2TzfVZ1pCb5vxm4BtZPUdYWe/Xo8= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/alecthomas/assert/v2 v2.0.3 h1:WKqJODfOiQG0nEJKFKzDIG3E29CN2/4zR9XGJzKIkbg= -github.com/alecthomas/participle/v2 v2.0.0-alpha9/go.mod h1:NumScqsC42o9x+dGj8/YqsIfhrIQjFEOFovxotbBirA= +github.com/alecthomas/assert/v2 v2.2.2 h1:Z/iVC0xZfWTaFNE6bA3z07T86hd45Xe2eLt6WVy2bbk= github.com/alecthomas/participle/v2 v2.0.0-beta.5 h1:y6dsSYVb1G5eK6mgmy+BgI3Mw35a3WghArZ/Hbebrjo= github.com/alecthomas/participle/v2 v2.0.0-beta.5/go.mod h1:RC764t6n4L8D8ITAJv0qdokritYSNR3wV5cVwmIEaMM= -github.com/alecthomas/repr v0.0.0-20181024024818-d37bc2a10ba1/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ= +github.com/alecthomas/participle/v2 v2.0.0 h1:Fgrq+MbuSsJwIkw3fEj9h75vDP0Er5JzepJ0/HNHv0g= +github.com/alecthomas/participle/v2 v2.0.0/go.mod h1:rAKZdJldHu8084ojcWevWAL8KmEU+AT+Olodb+WoN2Y= github.com/alecthomas/repr v0.1.0 h1:ENn2e1+J3k09gyj2shc0dHr/yjaWSHRlrJ4DPMevDqE= +github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/asjdf/flamego-sentry v0.0.0-20220809062217-41848e0b8de9 h1:/Fmuk9vIU7fIio6QHUmkPZC4+bb0NyVosLT6DgKoK1k= github.com/asjdf/flamego-sentry v0.0.0-20220809062217-41848e0b8de9/go.mod h1:Fcldu9fkkmIwdm9DEmZCYFcxRs1MLnZLXqY147MeBkk= +github.com/asjdf/flamego-sentry v0.0.0-20221203170410-58bea485de8e h1:ij8m/FtqEIB+ODm8YdojIZCAQ5VVDB+6dOzddK6dZRg= +github.com/asjdf/flamego-sentry v0.0.0-20221203170410-58bea485de8e/go.mod h1:Fcldu9fkkmIwdm9DEmZCYFcxRs1MLnZLXqY147MeBkk= +github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= +github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0= github.com/bwmarrin/snowflake v0.3.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/E9WsDpxqwE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= -github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/charmbracelet/lipgloss v0.7.1 h1:17WMwi7N1b1rVWOjMT+rCh7sQkvDU75B2hbZpc5Kc1E= +github.com/charmbracelet/lipgloss v0.7.1/go.mod h1:yG0k3giv8Qj8edTCbbg6AlQ5e8KNWpFujkNawKNhE2c= +github.com/charmbracelet/log v0.2.1 h1:1z7jpkk4yKyjwlmKmKMM5qnEDSpV32E7XtWhuv0mTZE= +github.com/charmbracelet/log v0.2.1/go.mod h1:GwFfjewhcVDWLrpAbY5A0Hin9YOlEn40eWT4PNaxFT4= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -65,80 +81,79 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= -github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/deckarep/golang-set v1.7.1 h1:SCQV0S6gTtp6itiFrTqI+pfmJ4LN85S1YzhDf9rTHJQ= github.com/deckarep/golang-set v1.7.1/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ= -github.com/denisenkom/go-mssqldb v0.12.0 h1:VtrkII767ttSPNRfFekePK3sctr+joXgO58stqQbtUA= -github.com/denisenkom/go-mssqldb v0.12.0/go.mod h1:iiK0YP1ZeepvmBQk/QpLEhhTNJgfzrpArPY/aFvc9yU= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= -github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= -github.com/flamego/binding v1.2.0 h1:rSEurfmhoFHjUooXcG4NJHZAGm918Pq52VjG1Ft6lp4= -github.com/flamego/binding v1.2.0/go.mod h1:hRtlg8mVujsuYWxnd0xAYLSET4MxoqXzg/KWP7OtSz0= -github.com/flamego/cors v1.0.2 h1:QygZelnxrN7hIikhm98EAU6c8yBUWy65iKWmGkgWe/w= -github.com/flamego/cors v1.0.2/go.mod h1:YcTw5oYqHprVcA+LLe6GTsWy8zOK2e981otgpglOPyg= -github.com/flamego/csrf v1.1.0 h1:JtZ2QDA2miYtdEzIySrRBsXtd81ex1SNe6lWZT2AbbY= -github.com/flamego/csrf v1.1.0/go.mod h1:+Qd6Kh26EbMf+VzpB84rqxBSwmkymv9WbZX3YKoycNs= -github.com/flamego/flamego v1.5.0/go.mod h1:/jaVrXeoApZnsJyK2KXV2ugSvn605R7Y5hh9eDhuKpk= +github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= +github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= github.com/flamego/flamego v1.7.0 h1:c1Lu16PBAZKkpsjHw42vwotdoQnMMpUi60ITP41W12w= github.com/flamego/flamego v1.7.0/go.mod h1:dnVMBJyHKaxjcqRVN93taSK+YB/9p+Op1GdLIuA1hFQ= -github.com/flamego/session v1.2.1 h1:tk4695rdBkkRhT6a4LdxzH/qz+ToO1XYCsmVcypZZXM= -github.com/flamego/session v1.2.1/go.mod h1:wV3JdoW1hG9y8QsKKQw885nHuVzHjxU2jLkNqVhTmb8= -github.com/flamego/template v1.1.0 h1:iYtCzY3TeYpsoQiGApFXw2qycKdMzimz2gkO/SlcksM= -github.com/flamego/template v1.1.0/go.mod h1:bgnmEXNumarhQIUzFgn18CDG6u8cM6X09c7UOTwZcxM= -github.com/flamego/validator v1.0.0 h1:ixuWHVgiVGp4pVGtUn/0d6HBjZJbbXfJHDNkxW+rZoY= -github.com/flamego/validator v1.0.0/go.mod h1:POYn0/5iW4sdamdPAYPrzqN6DFC4YaczY0gYY+Pyx5E= +github.com/flamego/flamego v1.9.1 h1:JplX2eFB/CM8VHuZG6m6sHQhcBdOMhGnZFbPCxtN9ao= +github.com/flamego/flamego v1.9.1/go.mod h1:WjaZO8GM/EGvIIGXlOiwp3oPuyy1fAdjWqEgEJWovJo= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/getsentry/sentry-go v0.13.0 h1:20dgTiUSfxRB/EhMPtxcL9ZEbM1ZdR+W/7f7NWD+xWo= github.com/getsentry/sentry-go v0.13.0/go.mod h1:EOsfu5ZdvKPfeHYV6pTVQnsjfp30+XA7//UooKNumH0= -github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= -github.com/gin-gonic/gin v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs= +github.com/getsentry/sentry-go v0.19.0 h1:BcCH3CN5tXt5aML+gwmbFwVptLLQA+eT866fCO9wVOM= +github.com/getsentry/sentry-go v0.19.0/go.mod h1:y3+lGEFEFexZtpbG1GUE2WD/f9zGyKYwpEqryTOC/nE= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/glebarez/go-sqlite v1.20.3 h1:89BkqGOXR9oRmG58ZrzgoY/Fhy5x0M+/WV48U5zVrZ4= +github.com/glebarez/go-sqlite v1.20.3/go.mod h1:u3N6D/wftiAzIOJtZl6BmedqxmmkDfH3q+ihjqxC9u0= +github.com/glebarez/sqlite v1.7.0 h1:A7Xj/KN2Lvie4Z4rrgQHY8MsbebX3NyWsL3n2i82MVI= +github.com/glebarez/sqlite v1.7.0/go.mod h1:PkeevrRlF/1BhQBCnzcMWzgrIk7IOop+qS2jUYLfHhk= github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= -github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= -github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= -github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= -github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= -github.com/go-playground/validator/v10 v10.11.0 h1:0W+xRM511GY47Yy3bZUbJVitCNg2BOGlCyvTqsp/xIw= -github.com/go-playground/validator/v10 v10.11.0/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= +github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= +github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg= github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= +github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= -github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= -github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA= -github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= -github.com/golang-sql/sqlexp v0.0.0-20170517235910-f1bb20e5a188 h1:+eHOFJl1BaXrQxKX+T06f78590z4qA2ZzBTqahsKSE4= -github.com/golang-sql/sqlexp v0.0.0-20170517235910-f1bb20e5a188/go.mod h1:vXjM/+wXQnTPR4KqTKDgJukSZ6amVRtWMPEjE6sQoK8= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -165,6 +180,9 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -177,7 +195,8 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -192,15 +211,18 @@ github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/guonaihong/gout v0.3.1 h1:pj/44Jw0TTmcHF2RjMaCWhKPwCH98YuQejbN15Hts/o= -github.com/guonaihong/gout v0.3.1/go.mod h1:lhje0jRkh/gcIogrG22ENPITo9tylQa3kwD9eVxcDrk= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.2 h1:gDLXvp5S9izjldquuoAhDzccbskOL6tDC5jMSyx3zxE= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.2/go.mod h1:7pdNwVWBBHGiCxa9lAszqCJMbfTISJ7oMftp8+UGV08= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= @@ -211,225 +233,206 @@ github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1: github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0= -github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= -github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= -github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= -github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= -github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= -github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= -github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= -github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= -github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= -github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= -github.com/jackc/pgconn v1.11.0/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= -github.com/jackc/pgconn v1.13.0 h1:3L1XMNV2Zvca/8BYhzcRFS70Lr0WlDg16Di6SFGAbys= -github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= -github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= -github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= -github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= -github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= -github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= -github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= -github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A= -github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= -github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= -github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= -github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= -github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= -github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgproto3/v2 v2.2.0/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgproto3/v2 v2.3.1 h1:nwj7qwf0S+Q7ISFfBndqeLwSwxs+4DPsbRFjECT1Y4Y= -github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg= -github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= -github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= -github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= -github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= -github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= -github.com/jackc/pgtype v1.10.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= -github.com/jackc/pgtype v1.12.0 h1:Dlq8Qvcch7kiehm8wPGIW0W3KsCCHJnRacKW0UM8n5w= -github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= -github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= -github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= -github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= -github.com/jackc/pgx/v4 v4.15.0/go.mod h1:D/zyOyXiaM1TmVWnOM18p0xdDtdakRBa0RsVGI3U3bw= -github.com/jackc/pgx/v4 v4.17.2 h1:0Ut0rpeKwvIVbMQ1KbMBU4h6wxehBI535LK6Flheh8E= -github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v1.2.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 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/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= -github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/juanjiTech/inject v1.0.0 h1:uZyhb0OGupUuXhMdL7Sd7oduJtT6MtHcMwO/xIg0a5c= +github.com/juanjiTech/inject v1.0.0/go.mod h1:/3jwrfe/F+GzaoQTjbYyQk3p8IjD9gpbkIF5Rzf1RBE= +github.com/juanjiTech/inject v1.1.0 h1:mlSaprJWJi6jByXYXHaTfxbfhPb6W1UrmdrpUGT9YY8= +github.com/juanjiTech/inject v1.1.0/go.mod h1:/3jwrfe/F+GzaoQTjbYyQk3p8IjD9gpbkIF5Rzf1RBE= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= -github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= -github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= -github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= -github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= -github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= -github.com/mattn/go-sqlite3 v1.14.12 h1:TJ1bhYJPV44phC+IMu1u2K/i5RriLTPe+yc68XDJ1Z0= -github.com/mattn/go-sqlite3 v1.14.12/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= +github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= +github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= -github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= +github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= +github.com/muesli/termenv v0.15.1 h1:UzuTb/+hhlBugQz28rpzey4ZuKcZ03MeKsoG7IJZIxs= +github.com/muesli/termenv v0.15.1/go.mod h1:HeAQPTzpfs016yGtA4g00CsdYnVLJvxsS4ANqrZs2sQ= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/gomega v1.20.2 h1:8uQq0zMgLEfa0vRrrBgaJF2gyW9Da9BmfGV+OyUzfkY= github.com/onsi/gomega v1.20.2/go.mod h1:iYAIXgPSaDHak0LCMA+AWBpIKBr8WZicMxnE8luStNc= -github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= -github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg= github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas= +github.com/pelletier/go-toml/v2 v2.0.7 h1:muncTPStnKRos5dpVKULv2FVd4bMOhNePj9CjgDb8Us= +github.com/pelletier/go-toml/v2 v2.0.7/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= -github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ= -github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/remyoudompheng/bigfft v0.0.0-20230126093431-47fa9a501578 h1:VstopitMQi3hZP0fzvnsLmzXZdQGc4bEcgu24cp+d4M= +github.com/remyoudompheng/bigfft v0.0.0-20230126093431-47fa9a501578/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= +github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= -github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= -github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= -github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= -github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= +github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= github.com/sony/sonyflake v1.0.0 h1:MpU6Ro7tfXwgn2l5eluf9xQvQJDROTBImNCfRXn/YeM= github.com/sony/sonyflake v1.0.0/go.mod h1:Jv3cfhf/UFtolOTTRd3q4Nl6ENqM+KfyZ5PseKfZGF4= +github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= +github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo= github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo= +github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM= +github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU= github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= +github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= +github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.13.0 h1:BWSJ/M+f+3nmdz9bxB+bWX28kkALN2ok11D0rSo8EJU= github.com/spf13/viper v1.13.0/go.mod h1:Icm2xNL3/8uyh/wFuB1jI7TiTNKp8632Nwegu+zgdYw= +github.com/spf13/viper v1.15.0 h1:js3yy885G8xwJa6iOISGFwd+qlUo5AvyXb7CiihdtiU= +github.com/spf13/viper v1.15.0/go.mod h1:fFcTBJxvhhzSJiZy8n+PeW6t8l+KeT/uTARa0jHOQLA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.545 h1:wlyGRRGsglx7YFQZY03D8v5TFLCRGCS7VFaIFgo3538= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.545/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/sms v1.0.545 h1:YAlU+pvDYE6qFxt/eu1xW33heaXfvNrVXVaOqWyYxws= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/sms v1.0.545/go.mod h1:peEDVrASDMNFCo3ViEHxHJj+eo9jEApIAudVXXfU/O4= -github.com/tidwall/gjson v1.14.3 h1:9jvXn7olKEHU1S9vwoMGliaT8jq1vJ7IH/n9zD9Dnlw= -github.com/tidwall/gjson v1.14.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= -github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= -github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= -github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= -github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= -github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= -github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= +github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= +github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.etcd.io/etcd/api/v3 v3.5.4 h1:OHVyt3TopwtUQ2GKdd5wu3PmmipR4FTwCqoEjSyRdIc= +go.etcd.io/etcd/api/v3 v3.5.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A= +go.etcd.io/etcd/api/v3 v3.5.6 h1:Cy2qx3npLcYqTKqGJzMypnMv2tiRyifZJ17BlWIWA7A= +go.etcd.io/etcd/api/v3 v3.5.6/go.mod h1:KFtNaxGDw4Yx/BA4iPPwevUTAuqcsPxzyX8PHydchN8= +go.etcd.io/etcd/client/pkg/v3 v3.5.4 h1:lrneYvz923dvC14R54XcA7FXoZ3mlGZAgmwhfm7HqOg= +go.etcd.io/etcd/client/pkg/v3 v3.5.4/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/pkg/v3 v3.5.6 h1:TXQWYceBKqLp4sa87rcPs11SXxUA/mHwH975v+BDvLU= +go.etcd.io/etcd/client/pkg/v3 v3.5.6/go.mod h1:ggrwbk069qxpKPq8/FKkQ3Xq9y39kbFR4LnKszpRXeQ= +go.etcd.io/etcd/client/v3 v3.5.4 h1:p83BUL3tAYS0OT/r0qglgc3M1JjhM0diV8DSWAhVXv4= +go.etcd.io/etcd/client/v3 v3.5.4/go.mod h1:ZaRkVgBZC+L+dLCjTcF1hRXpgZXQPOvnA/Ak/gq3kiY= +go.etcd.io/etcd/client/v3 v3.5.6 h1:coLs69PWCXE9G4FKquzNaSHrRyMCAXwF+IX1tAPVO8E= +go.etcd.io/etcd/client/v3 v3.5.6/go.mod h1:f6GRinRMCsFVv9Ht42EyY7nfsVGwrNO0WEoS2pRKzQk= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= -go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= +go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= -go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= +go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.17.0 h1:MTjgFu6ZLKvY6Pvaqk97GlxNBuMpV4Hy/3P6tRGlI2U= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= +go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= +go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM= golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -453,6 +456,7 @@ golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRu golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= @@ -463,10 +467,14 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA= golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs= +golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -474,10 +482,10 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -494,13 +502,16 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU= -golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -520,13 +531,13 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -534,11 +545,10 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -552,6 +562,8 @@ golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -559,17 +571,21 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A= -golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -577,10 +593,13 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -591,18 +610,14 @@ golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -610,7 +625,6 @@ golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= @@ -626,6 +640,7 @@ golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= @@ -634,12 +649,14 @@ golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.3.0 h1:SrNbZl6ECOS1qFzgTdQfWXZM9XBkiA6tkFrH9YSTPHM= golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= -golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= +golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -693,6 +710,7 @@ google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfG google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= @@ -706,6 +724,11 @@ google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20230223222841-637eb2293923 h1:znp6mq/drrY+6khTAlJUDNFFcDGV2ENLYKpMq8SyCds= +google.golang.org/genproto v0.0.0-20230223222841-637eb2293923/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw= +google.golang.org/genproto v0.0.0-20230323212658-478b75c54725 h1:VmCWItVXcKboEMCwZaWge+1JLiTCQSngZeINF+wzO+g= +google.golang.org/genproto v0.0.0-20230323212658-478b75c54725/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -719,9 +742,17 @@ google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3Iji google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k= +google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc= +google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= +google.golang.org/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag= +google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -733,47 +764,47 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= -google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= -gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE= -gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw= -gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= +gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= +gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gorm.io/datatypes v1.0.7 h1:8NhJN4+annFjwV1WufDhFiPjdUvV1lSGUdg1UCjQIWY= -gorm.io/datatypes v1.0.7/go.mod h1:l9qkCuy0CdzDEop9HKUdcnC9gHC2sRlaFtHkTzsZRqg= gorm.io/driver/mysql v1.3.2 h1:QJryWiqQ91EvZ0jZL48NOpdlPdMjdip1hQ8bTgo4H7I= gorm.io/driver/mysql v1.3.2/go.mod h1:ChK6AHbHgDCFZyJp0F+BmVGb06PSIoh9uVYKAlRbb2U= -gorm.io/driver/postgres v1.3.4 h1:evZ7plF+Bp+Lr1mO5NdPvd6M/N98XtwHixGB+y7fdEQ= -gorm.io/driver/postgres v1.3.4/go.mod h1:y0vEuInFKJtijuSGu9e5bs5hzzSzPK+LancpKpvbRBw= -gorm.io/driver/sqlite v1.3.1 h1:bwfE+zTEWklBYoEodIOIBwuWHpnx52Z9zJFW5F33WLk= -gorm.io/driver/sqlite v1.3.1/go.mod h1:wJx0hJspfycZ6myN38x1O/AqLtNS6c5o9TndewFbELg= -gorm.io/driver/sqlserver v1.3.1 h1:F5t6ScMzOgy1zukRTIZgLZwKahgt3q1woAILVolKpOI= -gorm.io/driver/sqlserver v1.3.1/go.mod h1:w25Vrx2BG+CJNUu/xKbFhaKlGxT/nzRkhWCCoptX8tQ= +gorm.io/driver/mysql v1.4.7 h1:rY46lkCspzGHn7+IYsNpSfEv9tA+SU4SkkB+GFX125Y= +gorm.io/driver/mysql v1.4.7/go.mod h1:SxzItlnT1cb6e1e4ZRpgJN2VYtcqJgqnHxWr4wsP8oc= gorm.io/gorm v1.23.1/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= -gorm.io/gorm v1.23.6/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= gorm.io/gorm v1.23.8 h1:h8sGJ+biDgBA1AD1Ha9gFCx7h8npU7AsLdlkX0n2TpE= gorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= -gorm.io/plugin/optimisticlock v1.1.0 h1:BOnkG80xsXxSHSvsX6bb5bOC8/M+cel+6nLJyhOAv1A= -gorm.io/plugin/optimisticlock v1.1.0/go.mod h1:YwUkSV3Oit0L80RxNVvA/D2gfCqaTjAZe8o57vKb4tk= +gorm.io/gorm v1.24.6 h1:wy98aq9oFEetsc4CAbKD2SoBCdMzsbSIvSUUFJuHi5s= +gorm.io/gorm v1.24.6/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -781,6 +812,15 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +modernc.org/libc v1.22.2 h1:4U7v51GyhlWqQmwCHj28Rdq2Yzwk55ovjFrdPjs8Hb0= +modernc.org/libc v1.22.2/go.mod h1:uvQavJ1pZ0hIoC/jfqNoMLURIMhKzINIWypNM17puug= +modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ= +modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds= +modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= +modernc.org/sqlite v1.20.3 h1:SqGJMMxjj1PHusLxdYxeQSodg7Jxn9WWkaAQjKrntZs= +modernc.org/sqlite v1.20.3/go.mod h1:zKcGyrICaxNTMEHSr1HQ2GUraP0j+845GYw37+EyT6A= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= diff --git a/internal/app/example/dto/service.go b/internal/app/example/dto/service.go deleted file mode 100644 index e4a04e2..0000000 --- a/internal/app/example/dto/service.go +++ /dev/null @@ -1,69 +0,0 @@ -package dto - -import ( - "errors" - "gorm.io/gorm" - "supreme-flamego/internal/app/example/model" - "supreme-flamego/internal/database" -) - -func init() { - // 就近原则 那里要用 哪里就马上初始化 - _ = database.AutoMigrate("*", &model.Example{}) -} - -var Example *services - -type services struct{} - -func (*services) Count() (count int64) { - database.GetDB("*").Model(&model.Example{}).Count(&count) - return -} - -func (*services) Save(data *model.Example, tx ...*gorm.DB) error { - if len(tx) != 0 { - return tx[0].Save(data).Error - } - return database.GetDB("*").Save(data).Error -} - -func (*services) Find(where *model.Example) ([]*model.Example, error) { - list := make([]*model.Example, 0, 10) - err := database.GetDB("*").Where(where).Find(&list).Error - return list, err -} - -func (*services) Update(data *model.Example, tx ...*gorm.DB) error { - var result *gorm.DB - if len(tx) != 0 { - result = tx[0].Model(data).Updates(data) - } else { - result = database.GetDB("*").Model(data).Updates(data) - } - if result.Error != nil { - return result.Error - } else if result.RowsAffected != 1 { - return errors.New("update locked by optimistic lock") - } - return nil -} - -func (*services) Del(data *model.Example, tx ...*gorm.DB) error { - if len(tx) != 0 { - return tx[0].Where(data).Delete(&model.Example{}).Error - } - return database.GetDB("*").Where(data).Delete(&model.Example{}).Error -} - -func (*services) GetByID(sid uint) (s *model.Example, err error) { - s = &model.Example{} - err = database.GetDB("*").Where("id = ?", sid).First(s).Error - return -} - -func (*services) Gets(offset, limit int) ([]*model.Example, error) { - var users []*model.Example - err := database.GetDB("*").Model(&model.Example{}).Offset(offset).Limit(limit).Find(&users).Error - return users, err -} diff --git a/internal/app/routerInitialize/init.go b/internal/app/routerInitialize/init.go deleted file mode 100644 index 4f99e1f..0000000 --- a/internal/app/routerInitialize/init.go +++ /dev/null @@ -1,13 +0,0 @@ -package routerInitialize - -import "github.com/flamego/flamego" - -var ( - routers = make([]func(e *flamego.Flame), 0) -) - -func ApiInit(r *flamego.Flame) { - for _, router := range routers { - router(r) - } -} diff --git a/internal/cache/creatorMap.go b/internal/cache/creatorMap.go deleted file mode 100644 index dc293ff..0000000 --- a/internal/cache/creatorMap.go +++ /dev/null @@ -1,21 +0,0 @@ -package cache - -import ( - "supreme-flamego/config" - "supreme-flamego/internal/cache/driver" - "supreme-flamego/internal/cache/types" -) - -type Creator interface { - Create(conf config.Cache) (types.Cache, error) -} - -func init() { - typeMap["redis"] = driver.RedisCreator{} -} - -var typeMap = make(map[string]Creator) - -func getCreatorByType(cacheType string) Creator { - return typeMap[cacheType] -} diff --git a/internal/cache/driver/redis.go b/internal/cache/driver/redis.go deleted file mode 100644 index ebb8503..0000000 --- a/internal/cache/driver/redis.go +++ /dev/null @@ -1,112 +0,0 @@ -package driver - -import ( - "fmt" - "github.com/go-redis/redis" - "supreme-flamego/config" - "supreme-flamego/internal/cache/types" - "supreme-flamego/pkg/logger" - "time" -) - -type RedisCreator struct{} - -func (c RedisCreator) Create(conf config.Cache) (types.Cache, error) { - var r RedisCache - r.client = redis.NewClient(&redis.Options{ - Addr: fmt.Sprintf("%s:%s", conf.IP, conf.PORT), - Password: conf.PASSWORD, - DB: conf.DB, - }) - _, err := r.client.Ping().Result() - if err != nil { - logger.NameSpace("redis").Fatal(err) - } - return r, nil -} - -type RedisCache struct { - client *redis.Client -} - -func (r RedisCache) GetInt(key string) (int, bool) { - value, err := r.client.Get(key).Int() - if err == nil { - return value, true - } - if err != redis.Nil { - logger.NameSpace("redis").Error(err) - } - return 0, false -} - -func (r RedisCache) GetInt64(key string) (int64, bool) { - value, err := r.client.Get(key).Int64() - if err == nil { - return value, true - } - if err != redis.Nil { - logger.NameSpace("redis").Error(err) - } - return 0, false -} - -func (r RedisCache) GetFloat32(key string) (float32, bool) { - value, err := r.client.Get(key).Float32() - if err == nil { - return value, true - } - if err != redis.Nil { - logger.NameSpace("redis").Error(err) - } - return 0, false -} - -func (r RedisCache) GetFloat64(key string) (float64, bool) { - value, err := r.client.Get(key).Float64() - if err == nil { - return value, true - } - if err != redis.Nil { - logger.NameSpace("redis").Error(err) - } - return 0, false -} - -func (r RedisCache) GetString(key string) (string, bool) { - value, err := r.client.Get(key).Result() - if err == nil { - return value, true - } - if err != redis.Nil { - logger.NameSpace("redis").Error(err) - } - return "", false -} - -func (r RedisCache) GetBool(key string) (bool, bool) { - value, err := r.client.Get(key).Result() - if err != redis.Nil { - logger.NameSpace("redis").Error(err) - } - if value == "1" { - return true, true - } else if value == "0" { - return false, true - } - return false, false -} - -func (r RedisCache) Set(Key string, value any, expireDuration time.Duration) error { - return r.client.Set(Key, value, expireDuration).Err() -} - -func (r RedisCache) Del(key string) bool { - err := r.client.Del(key).Err() - if err == redis.Nil { - return false - } else if err != nil { - logger.NameSpace("redis").Error(err) - } - return true -} diff --git a/internal/cache/init.go b/internal/cache/init.go deleted file mode 100644 index 294fe11..0000000 --- a/internal/cache/init.go +++ /dev/null @@ -1,59 +0,0 @@ -package cache - -import ( - "supreme-flamego/config" - "supreme-flamego/internal/cache/types" - "supreme-flamego/pkg/logger" - "sync" -) - -var ( - dbs = make(map[string]types.Cache) - mux sync.RWMutex -) - -func InitCache() { - sources := config.GetConfig().Caches - for _, source := range sources { - setCacheByKey(source.Key, mustCreateCache(source)) - if source.Key == "" { - source.Key = "*" - } - logger.NameSpace("cache").Infof("create cache %s => %s:%s", source.Key, source.IP, source.PORT) - } -} - -func GetCache(key ...string) types.Cache { - mux.Lock() - defer mux.Unlock() - if len(key) == 0 { - return dbs["*"] - } - return dbs[key[0]] -} - -func setCacheByKey(key string, cache types.Cache) { - if key == "" { - key = "*" - } - if GetCache(key) != nil { - logger.NameSpace("cache").Error("duplicate db key: ", key) - } - mux.Lock() - defer mux.Unlock() - dbs[key] = cache -} - -func mustCreateCache(conf config.Cache) types.Cache { - var creator = getCreatorByType(conf.Type) - if creator == nil { - logger.NameSpace("cache").Fatal("fail to find creator for cache types:%s", conf.Type) - return nil - } - cache, err := creator.Create(conf) - if err != nil { - logger.NameSpace("cache").Fatal(err) - return nil - } - return cache -} diff --git a/internal/cache/types/type.go b/internal/cache/types/type.go deleted file mode 100644 index d34af2e..0000000 --- a/internal/cache/types/type.go +++ /dev/null @@ -1,14 +0,0 @@ -package types - -import "time" - -type Cache interface { - GetInt(key string) (int, bool) - GetInt64(key string) (int64, bool) - GetFloat32(key string) (float32, bool) - GetFloat64(key string) (float64, bool) - GetString(key string) (string, bool) - GetBool(key string) (bool, bool) - Set(Key string, value any, expireDuration time.Duration) error - Del(key string) bool -} diff --git a/internal/corn/corn.go b/internal/corn/corn.go deleted file mode 100644 index 7009345..0000000 --- a/internal/corn/corn.go +++ /dev/null @@ -1,16 +0,0 @@ -package corn - -import ( - "github.com/robfig/cron" - "supreme-flamego/pkg/logger" -) - -func init() { - c := cron.New() - err := c.AddFunc("0 0/10 * * * *", func() {}) - if err != nil { - logger.NameSpace("cron").Fatal(err) - } - c.Start() - logger.NameSpace("cron").Info("corn routerInitialize SUCCESS ") -} diff --git a/internal/database/creatorMap.go b/internal/database/creatorMap.go deleted file mode 100644 index e45f848..0000000 --- a/internal/database/creatorMap.go +++ /dev/null @@ -1,24 +0,0 @@ -package database - -import ( - "gorm.io/gorm" - "supreme-flamego/internal/database/driver" -) - -type Creator interface { - Create(ip string, port string, userName string, password string, dbName string) (*gorm.DB, error) -} - -type DbModel interface { - DbKey() string -} - -func init() { - typeMap["mysql"] = driver.MySQLCreator{} -} - -var typeMap = make(map[string]Creator) - -func getCreatorByType(dbType string) Creator { - return typeMap[dbType] -} diff --git a/internal/database/driver/mysql.go b/internal/database/driver/mysql.go deleted file mode 100644 index 6b314fb..0000000 --- a/internal/database/driver/mysql.go +++ /dev/null @@ -1,31 +0,0 @@ -package driver - -import ( - "fmt" - "gorm.io/driver/mysql" - "gorm.io/gorm" - "gorm.io/gorm/logger" - "log" - "os" - "time" -) - -type MySQLCreator struct{} - -func (m MySQLCreator) Create(ip string, port string, userName string, password string, dbName string) (*gorm.DB, error) { - newLogger := logger.New( - log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer - logger.Config{ - SlowThreshold: time.Second, // 慢 SQL 阈值 - LogLevel: logger.Silent, // 日志级别 - IgnoreRecordNotFoundError: true, // 忽略ErrRecordNotFound(记录未找到)错误 - Colorful: false, // 禁用彩色打印 - }, - ) - var dsn = fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local", - userName, password, ip, port, dbName) - db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{ - Logger: newLogger, - }) - return db, err -} diff --git a/internal/database/init.go b/internal/database/init.go deleted file mode 100644 index 119a1c7..0000000 --- a/internal/database/init.go +++ /dev/null @@ -1,77 +0,0 @@ -package database - -import ( - "gorm.io/gorm" - "supreme-flamego/config" - "supreme-flamego/pkg/logger" - "sync" -) - -var ( - dbs = make(map[string]*gorm.DB) - mux sync.RWMutex - migrateList = make(map[string][]interface{}) -) - -func InitDB() { - sources := config.GetConfig().Databases - for _, source := range sources { - setDbByKey(source.Key, mustCreateGorm(source)) - if source.Key == "" { - source.Key = "*" - } - logger.NameSpace("database").Info("create datasource %s => %s:%s", source.Key, source.IP, source.PORT) - } - for key, models := range migrateList { - db := GetDB(key) - if db == nil { - logger.NameSpace("database").Fatal("fail to find db for key:%s", key) - return - } - err := db.AutoMigrate(models...) - if err != nil { - logger.NameSpace("database").Fatal(err) - return - } - logger.NameSpace("database").Info("migrate datasource %s success", key) - } -} - -func GetDB(key string) *gorm.DB { - mux.Lock() - defer mux.Unlock() - return dbs[key] -} - -func setDbByKey(key string, db *gorm.DB) { - if key == "" { - key = "*" - } - if GetDB(key) != nil { - logger.NameSpace("database").Error("duplicate db key: " + key) - } - mux.Lock() - defer mux.Unlock() - dbs[key] = db -} - -func mustCreateGorm(database config.Datasource) *gorm.DB { - var creator = getCreatorByType(database.Type) - if creator == nil { - logger.NameSpace("database").Fatalf("fail to find creator for types:%s", database.Type) - return nil - } - db, err := creator.Create(database.IP, database.PORT, database.USER, database.PASSWORD, database.DATABASE) - if err != nil { - logger.NameSpace("database").Fatal(err) - return nil - } - - return db -} - -// AutoMigrate 暂时注册一下数据库模型 将在InitDB的时候自动使用 -func AutoMigrate(dbKey string, dst ...interface{}) error { - migrateList[dbKey] = append(migrateList[dbKey], dst...) - return nil -} diff --git a/internal/middleware/authorization.go b/internal/middleware/authorization.go deleted file mode 100644 index 1e0f52b..0000000 --- a/internal/middleware/authorization.go +++ /dev/null @@ -1,20 +0,0 @@ -package middleware - -import ( - "github.com/flamego/flamego" - "supreme-flamego/pkg/jwt" -) - -func Authorization(c flamego.Context, r flamego.Render) { - token := c.Request().Header.Get("Authorization") - if token == "" { - UnAuthorization(r) - return - } - entry, err := jwt.ParseToken(token) - if err != nil { - UnAuthorization(r) - return - } - c.Map(entry.Info) -} diff --git a/internal/mod/dbLite/mod.go b/internal/mod/dbLite/mod.go new file mode 100644 index 0000000..580d5b4 --- /dev/null +++ b/internal/mod/dbLite/mod.go @@ -0,0 +1,26 @@ +package dbLite + +import ( + "github.com/glebarez/sqlite" + "gorm.io/gorm" + "supreme-flamego/core/kernel" +) + +var _ kernel.Module = (*Mod)(nil) + +type Mod struct { + kernel.UnimplementedModule // 请为所有Module引入UnimplementedModule +} + +func (m *Mod) Name() string { + return "dbLite" +} + +func (m *Mod) Init(h *kernel.Hub) error { + open, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) + if err != nil { + return err + } + h.Map(&DB{open}) + return nil +} diff --git a/internal/mod/dbLite/model.go b/internal/mod/dbLite/model.go new file mode 100644 index 0000000..1575fbf --- /dev/null +++ b/internal/mod/dbLite/model.go @@ -0,0 +1,7 @@ +package dbLite + +import "gorm.io/gorm" + +type DB struct { + *gorm.DB +} diff --git a/internal/mod/example/dao/dao.go b/internal/mod/example/dao/dao.go new file mode 100644 index 0000000..07a0cc0 --- /dev/null +++ b/internal/mod/example/dao/dao.go @@ -0,0 +1 @@ +package dao diff --git a/internal/app/example/e/e.go b/internal/mod/example/e/e.go similarity index 54% rename from internal/app/example/e/e.go rename to internal/mod/example/e/e.go index fcdd392..2515e13 100644 --- a/internal/app/example/e/e.go +++ b/internal/mod/example/e/e.go @@ -1,3 +1,4 @@ +// Package e 主要用于定义app特有的错误码 package e import "supreme-flamego/pkg/ecode" @@ -11,5 +12,5 @@ var ECode = map[ecode.Code]string{ } func init() { - ecode.Register(ECode) + ecode.Register(ECode) // 使用错误码注册机制保证软件全局不存在错误码冲突 } diff --git a/internal/mod/example/embed.go b/internal/mod/example/embed.go new file mode 100644 index 0000000..0af41bc --- /dev/null +++ b/internal/mod/example/embed.go @@ -0,0 +1,10 @@ +package example + +import "embed" + +// FS contains all files in this module, +// and since embed does not support import relative dir, +// we move embed from "modGenerator" to "example" +// +//go:embed * +var FS embed.FS diff --git a/internal/app/example/handler/v1/example.go b/internal/mod/example/handler/v1/example.go similarity index 100% rename from internal/app/example/handler/v1/example.go rename to internal/mod/example/handler/v1/example.go diff --git a/internal/app/example/handler/v1/type.go b/internal/mod/example/handler/v1/type.go similarity index 100% rename from internal/app/example/handler/v1/type.go rename to internal/mod/example/handler/v1/type.go diff --git a/internal/mod/example/mod.go b/internal/mod/example/mod.go new file mode 100644 index 0000000..8d934de --- /dev/null +++ b/internal/mod/example/mod.go @@ -0,0 +1,61 @@ +package example + +import ( + "context" + "errors" + "fmt" + "github.com/flamego/flamego" + "reflect" + "supreme-flamego/core/kernel" + "sync" +) + +var _ kernel.Module = (*Mod)(nil) + +type Mod struct { + kernel.UnimplementedModule // 请为所有Module引入UnimplementedModule +} + +func (m *Mod) Name() string { + return "example" +} + +// 下面的方法皆为可选实现 + +func (m *Mod) PreInit(h *kernel.Hub) error { + return nil +} + +func (m *Mod) Init(h *kernel.Hub) error { + h.Map("hello world") // 在内核注册这个依赖 + return nil +} + +func (m *Mod) PostInit(h *kernel.Hub) error { + return nil +} + +func (m *Mod) Load(h *kernel.Hub) error { + str := h.Value(reflect.TypeOf("string")).String() // 从内核获取上面注册的依赖 + fmt.Println(str) + _, _ = h.Invoke(func(s string) { fmt.Println(s) }) // 也可以这样从内核获取上面注册的依赖 + var str2 string + _ = h.Load(&str2) // 也可以这样从内核获取上面注册的依赖 + + var http flamego.Flame + err := h.Load(&http) + if err != nil { + return errors.New("can't load flame from kernel") + } + http.Get("/ping", func() string { return "pong" }) + return nil +} + +func (m *Mod) Start(h *kernel.Hub) error { + return nil +} + +func (m *Mod) Stop(wg *sync.WaitGroup, ctx context.Context) error { + defer wg.Done() + return nil +} diff --git a/internal/app/example/model/example.go b/internal/mod/example/model/example.go similarity index 100% rename from internal/app/example/model/example.go rename to internal/mod/example/model/example.go diff --git a/internal/app/example/router/router.go b/internal/mod/example/router/router.go similarity index 100% rename from internal/app/example/router/router.go rename to internal/mod/example/router/router.go diff --git a/internal/app/example/service/example.go b/internal/mod/example/service/example.go similarity index 100% rename from internal/app/example/service/example.go rename to internal/mod/example/service/example.go diff --git a/internal/mod/flame/mod.go b/internal/mod/flame/mod.go new file mode 100644 index 0000000..85471c3 --- /dev/null +++ b/internal/mod/flame/mod.go @@ -0,0 +1,106 @@ +package flame + +import ( + "context" + "errors" + "fmt" + sentryflame "github.com/asjdf/flamego-sentry" + "github.com/charmbracelet/log" + "github.com/flamego/flamego" + "github.com/juanjiTech/inject" + "github.com/soheilhy/cmux" + "net" + "net/http" + "supreme-flamego/conf" + "supreme-flamego/core/kernel" + "supreme-flamego/pkg/colorful" + "sync" + "time" +) + +var _ kernel.Module = (*Mod)(nil) + +type Mod struct { + kernel.UnimplementedModule // 请为所有Module引入UnimplementedModule + + listener net.Listener + flame *flamego.Flame + httpSrv *http.Server +} + +func (m *Mod) Name() string { + return "flame" +} + +// 下面的方法皆为可选实现 + +func (m *Mod) PreInit(h *kernel.Hub) error { + return nil +} + +func (m *Mod) Init(h *kernel.Hub) error { + m.flame = flamego.New() + m.flame.Use(flamego.LoggerInvoker(func(c flamego.Context, log *log.Logger) { + defer func() { + if err := recover(); err != nil { + log.Printf("PANIC: %s", err) + + // Lookup the current ResponseWriter + val := c.Value(inject.InterfaceOf((*http.ResponseWriter)(nil))) + w := val.Interface().(http.ResponseWriter) + w.WriteHeader(http.StatusInternalServerError) + } + }() + + c.Next() + })) + if conf.GetConfig().SentryDsn != "" { + m.flame.Use(sentryflame.New(sentryflame.Options{Repanic: true})) + } + h.Map(m.flame) // 在内核注册这个依赖 + return nil +} + +func (m *Mod) PostInit(h *kernel.Hub) error { + return nil +} + +func (m *Mod) Load(h *kernel.Hub) error { + var flame flamego.Flame + err := h.Load(&flame) + if err != nil { + return errors.New("can't load flame from kernel") + } + return nil +} + +func (m *Mod) Start(h *kernel.Hub) error { + var tcpMux cmux.CMux + err := h.Load(&tcpMux) + if err != nil { + return errors.New("can't load tcpMux from kernel") + } + + httpL := tcpMux.Match(cmux.HTTP1Fast()) + m.listener = httpL + m.httpSrv = &http.Server{ + Handler: m.flame, + } + + if err := m.httpSrv.Serve(httpL); err != nil && err != http.ErrServerClosed { + h.Logger.Infow("failed to start to listen and serve", "error", err) + } + return nil +} + +func (m *Mod) Stop(wg *sync.WaitGroup, ctx context.Context) error { + defer wg.Done() + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + if err := m.httpSrv.Shutdown(ctx); err != nil { + fmt.Println(colorful.Yellow("Server forced to shutdown: " + err.Error())) + return err + } + return nil +} diff --git a/internal/models/jwt.go b/internal/models/jwt.go deleted file mode 100644 index 263d614..0000000 --- a/internal/models/jwt.go +++ /dev/null @@ -1,5 +0,0 @@ -package models - -type UserInfo struct { - Uid uint -} diff --git a/pkg/fs/fs.go b/pkg/fsx/fs.go similarity index 99% rename from pkg/fs/fs.go rename to pkg/fsx/fs.go index 4cf8559..8983d79 100644 --- a/pkg/fs/fs.go +++ b/pkg/fsx/fs.go @@ -1,4 +1,4 @@ -package fs +package fsx import ( "bytes" diff --git a/pkg/ip/ip.go b/pkg/ip/ip.go index ae5e404..7e37b56 100644 --- a/pkg/ip/ip.go +++ b/pkg/ip/ip.go @@ -1,14 +1,15 @@ package ip import ( - "supreme-flamego/pkg/colorful" + "fmt" "net" + "supreme-flamego/pkg/colorful" ) func GetLocalHost() (res []string) { netInterfaces, err := net.Interfaces() if err != nil { - println(colorful.Red("net.Interfaces failed, err: " + err.Error())) + fmt.Println(colorful.Red("net.Interfaces failed, err: " + err.Error())) } for i := 0; i < len(netInterfaces); i++ { diff --git a/pkg/jwt/jwt.go b/pkg/jwt/jwt.go deleted file mode 100644 index fb49a68..0000000 --- a/pkg/jwt/jwt.go +++ /dev/null @@ -1,43 +0,0 @@ -package jwt - -import ( - "errors" - "github.com/golang-jwt/jwt" - "supreme-flamego/config" - "supreme-flamego/internal/models" - "time" -) - -type JWTClaims struct { - Info models.UserInfo - jwt.StandardClaims -} - -const TokenExpireDuration = time.Hour * 12 - -// GenToken 生成JWT -func GenToken(info models.UserInfo) (string, error) { - c := JWTClaims{ - Info: info, - StandardClaims: jwt.StandardClaims{ - ExpiresAt: time.Now().Add(TokenExpireDuration).Unix(), - Issuer: config.GetConfig().Auth.Issuer, - }, - } - token := jwt.NewWithClaims(jwt.SigningMethodHS256, c) - return token.SignedString([]byte(config.GetConfig().Auth.Secret)) -} - -// ParseToken 解析JWT -func ParseToken(tokenString string) (*JWTClaims, error) { - token, err := jwt.ParseWithClaims(tokenString, &JWTClaims{}, func(token *jwt.Token) (i interface{}, err error) { - return []byte(config.GetConfig().Auth.Secret), nil - }) - if err != nil { - return nil, err - } - if claims, ok := token.Claims.(*JWTClaims); ok && token.Valid { // 校验token - return claims, nil - } - return nil, errors.New("invalid token") -} diff --git a/pkg/logx/logger.go b/pkg/logx/logger.go deleted file mode 100644 index a6a0d2e..0000000 --- a/pkg/logx/logger.go +++ /dev/null @@ -1,50 +0,0 @@ -package logx - -import ( - "io" - "supreme-flamego/config" - "supreme-flamego/pkg/colorful" - "log" - "os" -) - -type debugDefault struct { - Debug *log.Logger -} - -func (d *debugDefault) Println(v ...interface{}) { - if config.GetConfig().MODE == "debug" { - d.Debug.Println(v...) - } -} - -var ( - Info *log.Logger - Warning *log.Logger - Error *log.Logger - Debug *debugDefault -) - -func InitLogger() { - if config.GetConfig().LogPath == "" { - log.Fatalln("LogPath 未设置") - } - if _, err := os.Stat(config.GetConfig().LogPath); os.IsNotExist(err) { - log.Print("LogPath 不存在") - if _, err := os.Create(config.GetConfig().LogPath); err != nil { - log.Fatalln("新建 LogPath 失败 ", err) - } - log.Print("新建 LogPath 成功") - } - errFile, err := os.OpenFile(config.GetConfig().LogPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) - if err != nil { - log.Fatalln("打开 LogPath 失败 ", err) - } - - Info = log.New(os.Stdout, "[Info] ", log.Ldate|log.Ltime|log.Lshortfile) - Warning = log.New(os.Stdout, colorful.Yellow("[Warning] "), log.Ldate|log.Ltime|log.Lshortfile) - Error = log.New(io.MultiWriter(os.Stderr, errFile), colorful.Red("[Error] "), log.Ldate|log.Ltime|log.Lshortfile) - Debug = &debugDefault{ - Debug: log.New(os.Stdout, colorful.Blue("[Debug] "), log.Ldate|log.Ltime|log.Lshortfile), - } -} diff --git a/internal/middleware/inject.go b/pkg/middleware/inject.go similarity index 98% rename from internal/middleware/inject.go rename to pkg/middleware/inject.go index b010953..9e1c484 100644 --- a/internal/middleware/inject.go +++ b/pkg/middleware/inject.go @@ -7,9 +7,9 @@ import ( "reflect" "supreme-flamego/internal/cache" "supreme-flamego/internal/database" - "supreme-flamego/internal/websocket" "supreme-flamego/pkg/format" "supreme-flamego/pkg/utils/page" + "supreme-flamego/pkg/websocket" ) func InjectDB(key ...string) flamego.Handler { diff --git a/internal/middleware/log.go b/pkg/middleware/log.go similarity index 89% rename from internal/middleware/log.go rename to pkg/middleware/log.go index 9161dce..38b5237 100644 --- a/internal/middleware/log.go +++ b/pkg/middleware/log.go @@ -2,7 +2,7 @@ package middleware import ( "github.com/flamego/flamego" - "supreme-flamego/pkg/logger" + "supreme-flamego/core/logx" "supreme-flamego/pkg/utils/gen/snowflake" "time" ) @@ -11,7 +11,7 @@ import ( func RequestLog() flamego.Handler { return func(c flamego.Context) { traceId := snowflake.Node.Generate().String() - log := logger.NameSpace("access") + log := logx.NameSpace("access") c.Map(*log) // 开始时间 startTime := time.Now() diff --git a/internal/middleware/response.go b/pkg/middleware/response.go similarity index 100% rename from internal/middleware/response.go rename to pkg/middleware/response.go diff --git a/internal/sentry/sentry.go b/pkg/sentry/sentry.go similarity index 76% rename from internal/sentry/sentry.go rename to pkg/sentry/sentry.go index 6261d9b..b1d5d77 100644 --- a/internal/sentry/sentry.go +++ b/pkg/sentry/sentry.go @@ -3,12 +3,12 @@ package sentry import ( "fmt" "github.com/getsentry/sentry-go" - "supreme-flamego/config" + "supreme-flamego/conf" ) func Init() { if err := sentry.Init(sentry.ClientOptions{ - Dsn: config.GetConfig().SentryDsn, + Dsn: conf.GetConfig().SentryDsn, }); err != nil { fmt.Printf("Sentry initialization failed: %v\n", err) } diff --git a/pkg/utils/gen/sonyflake/sonyFlakeId.go b/pkg/utils/gen/sonyflake/sonyFlakeId.go index 1a73181..3b84937 100644 --- a/pkg/utils/gen/sonyflake/sonyFlakeId.go +++ b/pkg/utils/gen/sonyflake/sonyFlakeId.go @@ -2,7 +2,7 @@ package sonyflake import ( "github.com/sony/sonyflake" - "supreme-flamego/pkg/logger" + "supreme-flamego/core/logx" ) var flake *sonyflake.Sonyflake @@ -14,7 +14,7 @@ func init() { func GenSonyFlakeId() (int64, error) { id, err := flake.NextID() if err != nil { - logger.NameSpace("sonyFlakeId").Warn("flake NextID failed: ", err) + logx.NameSpace("sonyFlakeId").Warn("flake NextID failed: ", err) return 0, err } return int64(id), nil diff --git a/internal/websocket/manager.go b/pkg/websocket/manager.go similarity index 90% rename from internal/websocket/manager.go rename to pkg/websocket/manager.go index 3fa381f..5392f2b 100644 --- a/internal/websocket/manager.go +++ b/pkg/websocket/manager.go @@ -1,8 +1,8 @@ package websocket import ( - "supreme-flamego/pkg/logger" "net/http" + "supreme-flamego/core/logx" "time" "github.com/gorilla/websocket" @@ -89,13 +89,13 @@ func (c *socketClient) readPump() { c.manager.unregister <- c err := c.conn.Close() if err != nil { - logger.NameSpace("manager").Error(err) + logx.NameSpace("manager").Error(err) } }() for { _, message, err := c.conn.ReadMessage() if err != nil { - logger.NameSpace("manager").Errorf("error: %v", err) + logx.NameSpace("manager").Errorf("error: %v", err) break } c.manager.receive <- map[*socketClient][]byte{ @@ -111,7 +111,7 @@ func (c *socketClient) writePump() { defer func() { err := c.conn.Close() if err != nil { - logger.NameSpace("manager").Error(err) + logx.NameSpace("manager").Error(err) } }() for { @@ -155,7 +155,7 @@ func (m *SocketManager) SendAllSocket(message string) { func (m *SocketManager) ServeSocket(w http.ResponseWriter, r *http.Request, n string) { conn, err := upGrader.Upgrade(w, r, nil) if err != nil { - logger.NameSpace("manager").Error(err) + logx.NameSpace("manager").Error(err) return } client := &socketClient{name: n, manager: m, conn: conn, send: make(chan []byte, 256)} @@ -169,7 +169,7 @@ func InitSocketManager(key string) { key = "*" } if _, ok := managers[key]; ok { - logger.NameSpace("manager").Error("socket manager key duplication") + logx.NameSpace("manager").Error("socket manager key duplication") } m := newManager() go m.run() @@ -180,6 +180,6 @@ func GetSocketManager(key string) *SocketManager { if m, ok := managers[key]; ok { return m } - logger.NameSpace("manager").Error("socket client ", key, " not found") + logx.NameSpace("manager").Error("socket client ", key, " not found") return nil } diff --git a/template/dto.template b/template/dto.template deleted file mode 100644 index 490149d..0000000 --- a/template/dto.template +++ /dev/null @@ -1,69 +0,0 @@ -package dto - -import ( - "errors" - "gorm.io/gorm" - "supreme-flamego/internal/app/{{.appName}}/model" - "supreme-flamego/internal/database" -) - -func init() { - // 就近原则 那里要用 哪里就马上初始化 - _ = database.AutoMigrate("*", &model.{{.appNameExport}}{}) -} - -var {{.appNameExport}} *{{.appName}} - -type {{.appName}} struct{} - -func (*{{.appName}}) Count() (count int64) { - database.GetDB("*").Model(&model.{{.appNameExport}}{}).Count(&count) - return -} - -func (*{{.appName}}) Save(data *model.{{.appNameExport}}, tx ...*gorm.DB) error { - if len(tx) != 0 { - return tx[0].Save(data).Error - } - return database.GetDB("*").Save(data).Error -} - -func (*{{.appName}}) Find(where *model.{{.appNameExport}}) ([]*model.{{.appNameExport}}, error) { - list := make([]*model.{{.appNameExport}}, 0, 10) - err := database.GetDB("*").Where(where).Find(&list).Error - return list, err -} - -func (*{{.appName}}) Update(data *model.{{.appNameExport}}, tx ...*gorm.DB) error { - var result *gorm.DB - if len(tx) != 0 { - result = tx[0].Model(data).Updates(data) - } else { - result = database.GetDB("*").Model(data).Updates(data) - } - if result.Error != nil { - return result.Error - } else if result.RowsAffected != 1 { - return errors.New("update locked by optimistic lock") - } - return nil -} - -func (*{{.appName}}) Del(data *model.{{.appNameExport}}, tx ...*gorm.DB) error { - if len(tx) != 0 { - return tx[0].Where(data).Delete(&model.{{.appNameExport}}{}).Error - } - return database.GetDB("*").Where(data).Delete(&model.{{.appNameExport}}{}).Error -} - -func (*{{.appName}}) GetByID(sid uint) (s *model.{{.appNameExport}}, err error) { - s = &model.{{.appNameExport}}{} - err = database.GetDB("*").Where("id = ?", sid).First(s).Error - return -} - -func (*{{.appName}}) Gets(offset, limit int) ([]*model.{{.appNameExport}}, error) { - var entities []*model.{{.appNameExport}} - err := database.GetDB("*").Model(&model.{{.appNameExport}}{}).Offset(offset).Limit(limit).Find(&entities).Error - return users, err -} diff --git a/template/e.template b/template/e.template deleted file mode 100644 index fcdd392..0000000 --- a/template/e.template +++ /dev/null @@ -1,15 +0,0 @@ -package e - -import "supreme-flamego/pkg/ecode" - -var ( - ErrNotExist = ecode.New(40400) -) - -var ECode = map[ecode.Code]string{ - ErrNotExist: "资源不存在", -} - -func init() { - ecode.Register(ECode) -} diff --git a/template/handler.template b/template/handler.template deleted file mode 100644 index b7b1f99..0000000 --- a/template/handler.template +++ /dev/null @@ -1 +0,0 @@ -package v1 diff --git a/template/model.template b/template/model.template deleted file mode 100644 index 9cc1bb6..0000000 --- a/template/model.template +++ /dev/null @@ -1,9 +0,0 @@ -package model - -import "gorm.io/gorm" - -// 数据库模型 - -type {{.appNameExport}} struct { - gorm.Model -} diff --git a/template/router.template b/template/router.template deleted file mode 100644 index 0c85fc7..0000000 --- a/template/router.template +++ /dev/null @@ -1,13 +0,0 @@ -package router - -import ( - "github.com/flamego/flamego" -) - -func App{{.appNameExport}}Init(e *flamego.Flame) { - e.Group("/api/v1/{{.appName}}", func() { - {{.appNameExport}}Group(e) - }) -} - -func {{.appNameExport}}Group(e *flamego.Flame) {} diff --git a/template/service.template b/template/service.template deleted file mode 100644 index c784fc5..0000000 --- a/template/service.template +++ /dev/null @@ -1,3 +0,0 @@ -package service - -// 数据处理可以在这哦 diff --git a/template/trigger.template b/template/trigger.template deleted file mode 100644 index a380e36..0000000 --- a/template/trigger.template +++ /dev/null @@ -1,7 +0,0 @@ -package routerInitialize - -import {{.appName}} "supreme-flamego/internal/app/{{.appName}}/router" - -func init() { - routers = append(routers, {{.appName}}.App{{.appNameExport}}Init) -} diff --git a/template/type.template b/template/type.template deleted file mode 100644 index 3e7157f..0000000 --- a/template/type.template +++ /dev/null @@ -1,3 +0,0 @@ -package v1 - -// Data transfer object