├── .gitignore ├── doc ├── img.png ├── graphql-page.png └── graphql-run.png ├── example ├── graphql │ ├── README.md │ ├── img.png │ ├── img_1.png │ ├── sdl │ │ ├── type.graphql │ │ └── root.graphql │ ├── graphql.go │ ├── graphtype.go │ └── root.go ├── gin-default │ └── gin-default.go ├── gin_handler │ └── gin_handler.go ├── logic_handler │ └── logic_handler.go ├── default │ └── cmd.go ├── go.mod ├── openapi.yaml └── go.sum ├── swagger ├── dist │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── index.css │ ├── swagger-initializer.js │ ├── index.html │ └── oauth2-redirect.html └── generate.go ├── engine_test.go ├── middleware_test.go ├── logger_test.go ├── logger.go ├── request.go ├── gen_route_test.go ├── schema.go ├── engine.go ├── response.go ├── handle.go ├── handle_test.go ├── ref.go ├── openapi_test.go ├── go.mod ├── util_test.go ├── tracer.go ├── openapi.go ├── new_test.go ├── README.md ├── util.go ├── middleware.go ├── ref_test.go └── new.go /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | openapi*.yaml -------------------------------------------------------------------------------- /doc/img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aide-family/gin-plus/HEAD/doc/img.png -------------------------------------------------------------------------------- /example/graphql/README.md: -------------------------------------------------------------------------------- 1 | # 运行结果 2 | 3 | ![img.png](img.png) 4 | 5 | ![img_1.png](img_1.png) -------------------------------------------------------------------------------- /doc/graphql-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aide-family/gin-plus/HEAD/doc/graphql-page.png -------------------------------------------------------------------------------- /doc/graphql-run.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aide-family/gin-plus/HEAD/doc/graphql-run.png -------------------------------------------------------------------------------- /example/graphql/img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aide-family/gin-plus/HEAD/example/graphql/img.png -------------------------------------------------------------------------------- /example/graphql/img_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aide-family/gin-plus/HEAD/example/graphql/img_1.png -------------------------------------------------------------------------------- /swagger/dist/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aide-family/gin-plus/HEAD/swagger/dist/favicon-16x16.png -------------------------------------------------------------------------------- /swagger/dist/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aide-family/gin-plus/HEAD/swagger/dist/favicon-32x32.png -------------------------------------------------------------------------------- /example/graphql/sdl/type.graphql: -------------------------------------------------------------------------------- 1 | """ 2 | This module contains the definition of the UUID scalar. 3 | """ 4 | scalar UUID -------------------------------------------------------------------------------- /swagger/generate.go: -------------------------------------------------------------------------------- 1 | package swagger 2 | 3 | import ( 4 | "embed" 5 | _ "embed" 6 | ) 7 | 8 | //go:embed dist/* 9 | var Dist embed.FS 10 | -------------------------------------------------------------------------------- /engine_test.go: -------------------------------------------------------------------------------- 1 | package ginplus 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "testing" 6 | ) 7 | 8 | func TestNewCtrlC(t *testing.T) { 9 | ctrlC := NewCtrlC(New(gin.Default())) 10 | ctrlC.Start() 11 | } 12 | -------------------------------------------------------------------------------- /middleware_test.go: -------------------------------------------------------------------------------- 1 | package ginplus 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestNewMiddleware(t *testing.T) { 8 | NewMiddleware(WithResponse(NewResponse()), WithServerName("gin-plus"), WithID("id"), WithEnv("default")) 9 | } 10 | -------------------------------------------------------------------------------- /swagger/dist/index.css: -------------------------------------------------------------------------------- 1 | html { 2 | box-sizing: border-box; 3 | overflow: -moz-scrollbars-vertical; 4 | overflow-y: scroll; 5 | } 6 | 7 | *, 8 | *:before, 9 | *:after { 10 | box-sizing: inherit; 11 | } 12 | 13 | body { 14 | margin: 0; 15 | background: #fafafa; 16 | } 17 | -------------------------------------------------------------------------------- /logger_test.go: -------------------------------------------------------------------------------- 1 | package ginplus 2 | 3 | import ( 4 | "testing" 5 | 6 | "go.uber.org/zap" 7 | ) 8 | 9 | func TestLogger(t *testing.T) { 10 | Logger().Info("hello world") 11 | } 12 | 13 | func TestSetLogger(t *testing.T) { 14 | SetLogger(zap.NewExample()) 15 | Logger().Info("hello world") 16 | } 17 | -------------------------------------------------------------------------------- /logger.go: -------------------------------------------------------------------------------- 1 | package ginplus 2 | 3 | import ( 4 | "sync" 5 | 6 | "go.uber.org/zap" 7 | ) 8 | 9 | var ( 10 | logger = zap.NewExample() 11 | loggerOnce = sync.Once{} 12 | ) 13 | 14 | // SetLogger 设置日志记录器 15 | func SetLogger(l *zap.Logger) { 16 | loggerOnce.Do(func() { 17 | logger = l 18 | }) 19 | } 20 | 21 | // Logger 返回日志记录器 22 | func Logger() *zap.Logger { 23 | return logger 24 | } 25 | -------------------------------------------------------------------------------- /example/gin-default/gin-default.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | ginplus "github.com/aide-cloud/gin-plus" 5 | "github.com/gin-gonic/gin" 6 | ) 7 | 8 | func main() { 9 | instance := ginplus.New(gin.Default()) 10 | 11 | instance.GET("/hello", func(ctx *gin.Context) { 12 | ctx.JSON(200, gin.H{ 13 | "message": "hello world", 14 | }) 15 | }) 16 | 17 | ginplus.NewCtrlC(instance).Start() 18 | } 19 | -------------------------------------------------------------------------------- /example/graphql/graphql.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | 6 | ginplus "github.com/aide-cloud/gin-plus" 7 | ) 8 | 9 | func main() { 10 | instance := ginplus.New(gin.Default(), 11 | ginplus.WithAddr(":8080"), 12 | ginplus.WithGenApiEnable(false), 13 | ginplus.WithGraphqlConfig(ginplus.GraphqlConfig{ 14 | Enable: true, 15 | HandlePath: "/graphql", 16 | ViewPath: "/graphql", 17 | Root: &Root{}, 18 | Content: content, 19 | })) 20 | 21 | ginplus.NewCtrlC(instance).Start() 22 | } 23 | -------------------------------------------------------------------------------- /request.go: -------------------------------------------------------------------------------- 1 | package ginplus 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/gin-gonic/gin/binding" 6 | ) 7 | 8 | func Bind(c *gin.Context, params interface{}) error { 9 | b := binding.Default(c.Request.Method, c.ContentType()) 10 | if err := c.ShouldBindWith(params, b); err != nil { 11 | return err 12 | } 13 | 14 | if err := binding.Form.Bind(c.Request, params); err != nil { 15 | return err 16 | } 17 | 18 | if err := c.ShouldBindUri(params); err != nil { 19 | return err 20 | } 21 | 22 | if err := c.ShouldBindHeader(params); err != nil { 23 | return err 24 | } 25 | 26 | return nil 27 | } 28 | -------------------------------------------------------------------------------- /swagger/dist/swagger-initializer.js: -------------------------------------------------------------------------------- 1 | window.onload = function() { 2 | // 3 | 4 | // the following lines will be replaced by docker/configurator, when it runs in a docker-container 5 | window.ui = SwaggerUIBundle({ 6 | url: "/openapi/doc/swagger", 7 | dom_id: '#swagger-ui', 8 | deepLinking: true, 9 | presets: [ 10 | SwaggerUIBundle.presets.apis, 11 | SwaggerUIStandalonePreset 12 | ], 13 | plugins: [ 14 | SwaggerUIBundle.plugins.DownloadUrl 15 | ], 16 | layout: "StandaloneLayout" 17 | }); 18 | 19 | // 20 | }; 21 | -------------------------------------------------------------------------------- /swagger/dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Swagger UI 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /example/graphql/graphtype.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/google/uuid" 6 | "github.com/graph-gophers/graphql-go/decode" 7 | ) 8 | 9 | type UUID string 10 | 11 | // 自定义类型实现graphql类型接口 12 | var _ decode.Unmarshaler = (*UUID)(nil) 13 | var _ fmt.Stringer = (*UUID)(nil) 14 | 15 | func (*UUID) ImplementsGraphQLType(name string) bool { 16 | return name == "UUID" 17 | } 18 | 19 | func (U *UUID) UnmarshalGraphQL(input interface{}) error { 20 | val, ok := input.(string) 21 | if !ok { 22 | return fmt.Errorf("[UUID] wrong type, input: %T", input) 23 | } 24 | 25 | // 验证是否是uuid 26 | if _, err := uuid.Parse(val); err != nil { 27 | return fmt.Errorf("[UUID] wrong format, input: %s", val) 28 | } 29 | 30 | *U = UUID(val) 31 | return nil 32 | } 33 | 34 | func (U *UUID) String() string { 35 | return string(*U) 36 | } 37 | -------------------------------------------------------------------------------- /example/graphql/sdl/root.graphql: -------------------------------------------------------------------------------- 1 | schema { 2 | query: RootQuery 3 | mutation: RootMutation 4 | } 5 | 6 | type RootQuery { 7 | ping: String! 8 | extend: ExtendQuery 9 | } 10 | 11 | type ExtendQuery { 12 | info: String! 13 | } 14 | 15 | type RootMutation { 16 | ping: String! 17 | add(in: AddInput!): Int! 18 | sum(in: SumInput!): Int! 19 | sub(in: SubInput!): Int! 20 | mul(in: MulInput!): Int! 21 | div(in: DivInput!): Int! 22 | checkUUID(in: UUID!): String! 23 | } 24 | 25 | input AddInput { 26 | a: Int! 27 | b: Int! 28 | } 29 | 30 | input SumInput { 31 | nums: [Int!]! 32 | } 33 | 34 | input SubInput { 35 | a: Int! 36 | b: Int! 37 | } 38 | 39 | # a * b 40 | input MulInput { 41 | a: Int! 42 | b: Int! 43 | } 44 | 45 | # a / b 46 | input DivInput { 47 | a: Int! 48 | b: Int! 49 | } -------------------------------------------------------------------------------- /example/gin_handler/gin_handler.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | 6 | ginplus "github.com/aide-cloud/gin-plus" 7 | "github.com/gin-gonic/gin" 8 | ) 9 | 10 | var _ ginplus.Controller = (*Api)(nil) 11 | var _ ginplus.Middlewarer = (*Api)(nil) 12 | 13 | type Api struct { 14 | } 15 | 16 | func (a *Api) Middlewares() []gin.HandlerFunc { 17 | return []gin.HandlerFunc{ 18 | func(ctx *gin.Context) { 19 | log.Println("middleware 1") 20 | }, 21 | } 22 | } 23 | 24 | func (a *Api) BasePath() string { 25 | return "/api/v1" 26 | } 27 | 28 | func (a *Api) GetA() gin.HandlerFunc { 29 | return func(ctx *gin.Context) { 30 | ctx.String(200, "[Get] hello world") 31 | } 32 | } 33 | 34 | func NewApi() *Api { 35 | return &Api{} 36 | } 37 | 38 | func main() { 39 | instance := ginplus.New(gin.Default(), ginplus.WithControllers(NewApi())) 40 | 41 | ginplus.NewCtrlC(instance).Start() 42 | } 43 | -------------------------------------------------------------------------------- /gen_route_test.go: -------------------------------------------------------------------------------- 1 | package ginplus 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/gin-gonic/gin" 8 | ) 9 | 10 | type Student struct { 11 | A *AApi 12 | B *BApi 13 | } 14 | 15 | type AApi struct { 16 | } 17 | 18 | func (a *AApi) Get(ctx context.Context, req *StudentReq) (*StudentResp, error) { 19 | return &StudentResp{}, nil 20 | } 21 | 22 | type BApi struct { 23 | } 24 | 25 | func (b *BApi) Get(ctx context.Context, req *StudentReq) (*StudentResp, error) { 26 | return &StudentResp{}, nil 27 | } 28 | 29 | type StudentReq struct { 30 | } 31 | 32 | type StudentResp struct { 33 | } 34 | 35 | func (s *Student) Get(ctx context.Context, req *StudentReq) (*StudentResp, error) { 36 | return &StudentResp{}, nil 37 | } 38 | 39 | func TestGenRoute(t *testing.T) { 40 | r := gin.Default() 41 | // var p *Student 42 | p := &Student{ 43 | A: &AApi{}, 44 | B: &BApi{}, 45 | } 46 | pr := New(r, WithControllers(p)) 47 | NewCtrlC(pr) 48 | } 49 | -------------------------------------------------------------------------------- /example/logic_handler/logic_handler.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | 6 | ginplus "github.com/aide-cloud/gin-plus" 7 | "github.com/gin-gonic/gin" 8 | ) 9 | 10 | type ( 11 | Api struct { 12 | V1 *V1 13 | V2 *V2 14 | } 15 | 16 | V1 struct{} 17 | 18 | V2 struct{} 19 | 20 | Req struct { 21 | Id uint `uri:"id"` 22 | Name string `form:"name"` 23 | Data []any `json:"data"` 24 | } 25 | Resp struct { 26 | Id uint `json:"id"` 27 | Name string `json:"name"` 28 | } 29 | ) 30 | 31 | func (a *Api) GetInfo(ctx context.Context, req *Req) (*Resp, error) { 32 | return &Resp{ 33 | Id: req.Id, 34 | Name: "gin-plus" + req.Name, 35 | }, nil 36 | } 37 | 38 | func (l *V1) PostInfo(ctx context.Context, req *Req) (*Resp, error) { 39 | return &Resp{ 40 | Id: req.Id, 41 | Name: "gin-plus" + req.Name, 42 | }, nil 43 | } 44 | 45 | func (l *V2) PutInfo(ctx context.Context, req *Req) (*Resp, error) { 46 | return &Resp{ 47 | Id: req.Id, 48 | Name: "gin-plus" + req.Name, 49 | }, nil 50 | } 51 | 52 | func main() { 53 | instance := ginplus.New(gin.Default(), ginplus.WithControllers(&Api{ 54 | V1: &V1{}, 55 | V2: &V2{}, 56 | })) 57 | 58 | // http://localhost:8080/api/info/12?name=xx 59 | ginplus.NewCtrlC(instance).Start() 60 | } 61 | -------------------------------------------------------------------------------- /schema.go: -------------------------------------------------------------------------------- 1 | package ginplus 2 | 3 | import ( 4 | "bytes" 5 | "embed" 6 | "fmt" 7 | "io/fs" 8 | "strings" 9 | ) 10 | 11 | // String reads the .graphql schema files from the embed.FS, concatenating the 12 | // files together into one string. 13 | // 14 | // If this method complains about not finding functions AssetNames() or MustAsset(), 15 | // run `go generate` against this package to generate the functions. 16 | func String(content embed.FS) (string, error) { 17 | var buf bytes.Buffer 18 | 19 | fn := func(path string, d fs.DirEntry, err error) error { 20 | if err != nil { 21 | return fmt.Errorf("walking dir: %w", err) 22 | } 23 | 24 | // Only add files with the .graphql extension. 25 | if !strings.HasSuffix(path, ".graphql") { 26 | return nil 27 | } 28 | 29 | b, err := content.ReadFile(path) 30 | if err != nil { 31 | return fmt.Errorf("reading file %q: %w", path, err) 32 | } 33 | 34 | // Add a newline to separate each file. 35 | b = append(b, []byte("\n")...) 36 | 37 | if _, err := buf.Write(b); err != nil { 38 | return fmt.Errorf("writing %q bytes to buffer: %w", path, err) 39 | } 40 | 41 | return nil 42 | } 43 | 44 | // Recursively walk this directory and append all the file contents together. 45 | if err := fs.WalkDir(content, ".", fn); err != nil { 46 | return buf.String(), fmt.Errorf("walking content directory: %w", err) 47 | } 48 | 49 | return buf.String(), nil 50 | } 51 | -------------------------------------------------------------------------------- /engine.go: -------------------------------------------------------------------------------- 1 | package ginplus 2 | 3 | import ( 4 | "os" 5 | "os/signal" 6 | "syscall" 7 | ) 8 | 9 | type ( 10 | // Starter 开始方法的接口 11 | Starter interface { 12 | Start() error 13 | } 14 | 15 | // Stopper 结束方法的接口 16 | Stopper interface { 17 | Stop() 18 | } 19 | 20 | Server interface { 21 | // Starter 启动服务 22 | Starter 23 | // Stopper 停止服务 24 | Stopper 25 | } 26 | 27 | // CtrlC 捕获ctrl-c的控制器 28 | CtrlC struct { 29 | servers []Server 30 | // 信号通道 31 | signalChan chan os.Signal 32 | } 33 | ) 34 | 35 | // NewCtrlC 初始化生成CtrlC 36 | func NewCtrlC(services ...Server) *CtrlC { 37 | return &CtrlC{ 38 | servers: services, 39 | signalChan: make(chan os.Signal, 1), 40 | } 41 | } 42 | 43 | // 等待键盘信号 44 | func (c *CtrlC) waitSignals(signals ...os.Signal) { 45 | signal.Notify(c.signalChan, signals...) 46 | <-c.signalChan 47 | } 48 | 49 | // 接收到kill信号 50 | func (c *CtrlC) waitKill() { 51 | c.waitSignals(os.Interrupt, syscall.SIGTERM, syscall.SIGHUP) 52 | } 53 | 54 | func (c *CtrlC) recover() { 55 | if err := recover(); err != nil { 56 | Logger().Sugar().Panicf("panic: %v", err) 57 | c.signalChan <- os.Kill 58 | } 59 | } 60 | 61 | // Start 开始运行程序,遇到os.Interrupt停止 62 | func (c *CtrlC) Start() { 63 | go func() { 64 | defer c.recover() 65 | // 启动程序内部的服务列表 66 | c.startMulServices() 67 | }() 68 | 69 | c.waitKill() 70 | c.stopMulServices() 71 | } 72 | 73 | // 启动应用子服务 74 | func (c *CtrlC) startMulServices() { 75 | servicesSlice := c.servers 76 | for _, service := range servicesSlice { 77 | go func(s Starter) { 78 | defer c.recover() 79 | if err := s.Start(); err != nil { 80 | panic(err) 81 | } 82 | }(service) 83 | } 84 | } 85 | 86 | // 停止应用子服务 87 | func (c *CtrlC) stopMulServices() { 88 | servicesSlice := c.servers 89 | for _, service := range servicesSlice { 90 | service.Stop() 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /example/graphql/root.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "embed" 6 | "errors" 7 | ) 8 | 9 | //go:embed sdl 10 | var content embed.FS 11 | 12 | type Root struct{} 13 | 14 | func (r *Root) Ping() string { 15 | return "pong" 16 | } 17 | 18 | type ( 19 | AddArgs struct { 20 | A int32 21 | B int32 22 | } 23 | 24 | SumArgs struct { 25 | Nums []int32 26 | } 27 | 28 | SubArgs struct { 29 | A int32 30 | B int32 31 | } 32 | 33 | MultiArgs struct { 34 | A int32 35 | B int32 36 | } 37 | 38 | DivArgs struct { 39 | A int32 40 | B int32 41 | } 42 | 43 | Extend struct { 44 | } 45 | ) 46 | 47 | func (r *Root) Add(ctx context.Context, args struct { 48 | In AddArgs 49 | }) (int32, error) { 50 | return args.In.A + args.In.B, nil 51 | } 52 | 53 | func (r *Root) Sum(ctx context.Context, args struct { 54 | In SumArgs 55 | }) (int32, error) { 56 | var sum int32 57 | for _, num := range args.In.Nums { 58 | sum += num 59 | } 60 | return sum, nil 61 | } 62 | 63 | func (r *Root) Sub(ctx context.Context, args struct { 64 | In SubArgs 65 | }) (int32, error) { 66 | return args.In.A - args.In.B, nil 67 | } 68 | 69 | func (r *Root) Mul(ctx context.Context, args struct { 70 | In MultiArgs 71 | }) (int32, error) { 72 | return args.In.A * args.In.B, nil 73 | } 74 | 75 | func (r *Root) Div(ctx context.Context, args struct { 76 | In DivArgs 77 | }) (int32, error) { 78 | if args.In.B == 0 { 79 | return 0, errors.New("div by zero") 80 | } 81 | return args.In.A / args.In.B, nil 82 | } 83 | 84 | func (r *Root) CheckUUID(ctx context.Context, args struct { 85 | In UUID 86 | }) (string, error) { 87 | return args.In.String(), nil 88 | } 89 | 90 | func (r *Root) Extend(ctx context.Context) *Extend { 91 | return &Extend{} 92 | } 93 | 94 | // Info ... 95 | // request: 96 | // 97 | // { 98 | // extend { 99 | // info 100 | // } 101 | // } 102 | // 103 | // response: 104 | // 105 | // { 106 | // "data": { 107 | // "extend": { 108 | // "info": "extend info" 109 | // } 110 | // } 111 | // } 112 | func (r *Extend) Info(ctx context.Context) string { 113 | return "extend info" 114 | } 115 | -------------------------------------------------------------------------------- /response.go: -------------------------------------------------------------------------------- 1 | package ginplus 2 | 3 | import ( 4 | "errors" 5 | "reflect" 6 | 7 | "github.com/gin-gonic/gin" 8 | "go.uber.org/zap" 9 | ) 10 | 11 | type IResponse interface { 12 | Response(ctx *gin.Context, resp any, err error) 13 | } 14 | 15 | type IValidator interface { 16 | Validate() error 17 | } 18 | 19 | type response struct { 20 | Error error `json:"error"` 21 | Data any `json:"data"` 22 | } 23 | 24 | var _ IResponse = (*response)(nil) 25 | 26 | func NewResponse() IResponse { 27 | return &response{} 28 | } 29 | 30 | func (l *response) Response(ctx *gin.Context, resp any, err error) { 31 | defer ctx.Abort() 32 | ctx.JSON(200, &response{ 33 | Error: err, 34 | Data: resp, 35 | }) 36 | } 37 | 38 | func (l *GinEngine) newDefaultHandler(controller any, t reflect.Method, req reflect.Type) gin.HandlerFunc { 39 | // 缓存反射数据, 避免在请求中再处理导致性能问题 40 | reqTmp := req 41 | for reqTmp.Kind() == reflect.Ptr { 42 | reqTmp = reqTmp.Elem() 43 | } 44 | 45 | handleFunc := t.Func 46 | controllerVal := reflect.ValueOf(controller) 47 | return func(ctx *gin.Context) { 48 | // new一个req的实例 49 | reqVal := reflect.New(reqTmp) 50 | // 绑定请求参数 51 | if err := l.defaultBind(ctx, reqVal.Interface()); err != nil { 52 | Logger().Info("defaultBind req err", zap.Error(err)) 53 | l.defaultResponse.Response(ctx, nil, err) 54 | return 55 | } 56 | 57 | // Validate 58 | if validate, ok := reqVal.Interface().(IValidator); ok { 59 | if err := validate.Validate(); err != nil { 60 | Logger().Info("Validate req err", zap.Error(err)) 61 | l.defaultResponse.Response(ctx, nil, err) 62 | return 63 | } 64 | } 65 | 66 | // 调用方法 67 | respVal := handleFunc.Call([]reflect.Value{controllerVal, reflect.ValueOf(ctx), reqVal}) 68 | if !respVal[1].IsNil() { 69 | err, ok := respVal[1].Interface().(error) 70 | if ok { 71 | Logger().Info("handleFunc Call err", zap.Error(err)) 72 | l.defaultResponse.Response(ctx, nil, err) 73 | return 74 | } 75 | Logger().Info("handleFunc Call abnormal err", zap.Error(err)) 76 | l.defaultResponse.Response(ctx, nil, errors.New("response error")) 77 | return 78 | } 79 | 80 | // 返回结果 81 | l.defaultResponse.Response(ctx, respVal[0].Interface(), nil) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /handle.go: -------------------------------------------------------------------------------- 1 | package ginplus 2 | 3 | import ( 4 | "embed" 5 | "fmt" 6 | "net/http" 7 | 8 | "github.com/graph-gophers/graphql-go" 9 | "github.com/graph-gophers/graphql-go/relay" 10 | ) 11 | 12 | const ( 13 | DefaultHandlePath = "/graphql" 14 | DefaultViewPath = "/view" 15 | ) 16 | 17 | func View(handlePath string) http.HandlerFunc { 18 | var page = []byte(` 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |
Loading...
30 | 52 | 53 | 54 | `) 55 | 56 | return func(w http.ResponseWriter, r *http.Request) { 57 | _, err := w.Write(page) 58 | if err != nil { 59 | panic(fmt.Sprintf("write page error: %s\n", err)) 60 | } 61 | } 62 | } 63 | 64 | func Handler(root any, content embed.FS) *relay.Handler { 65 | s, err := String(content) 66 | if err != nil { 67 | panic(fmt.Sprintf("reading embedded schema contents: %v", err)) 68 | } 69 | 70 | return &relay.Handler{Schema: graphql.MustParseSchema(s, root, graphql.UseFieldResolvers())} 71 | } 72 | -------------------------------------------------------------------------------- /example/default/cmd.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "log" 6 | 7 | ginplush "github.com/aide-cloud/gin-plus" 8 | "github.com/gin-gonic/gin" 9 | ) 10 | 11 | type ( 12 | Api struct { 13 | } 14 | 15 | ApiDetailReq struct { 16 | Id uint `uri:"id"` 17 | } 18 | 19 | ApiDetailResp struct { 20 | Id uint `json:"id"` 21 | Name string `json:"name"` 22 | Remark string `json:"remark"` 23 | } 24 | 25 | ApiListReq struct { 26 | Current int `form:"current"` 27 | Size int `form:"size"` 28 | Keryworld string `form:"keyworld"` 29 | } 30 | ApiListResp struct { 31 | Total int64 `json:"total"` 32 | List []*ApiInfoItem `json:"list"` 33 | } 34 | 35 | ApiInfoItem struct { 36 | Name string `json:"name"` 37 | Id uint `json:"id"` 38 | Remark string `json:"remark"` 39 | } 40 | 41 | ApiUpdateReq struct { 42 | Id uint `uri:"id"` 43 | Name string `json:"name"` 44 | Remark string `json:"remark"` 45 | } 46 | ApiUpdateResp struct { 47 | Id uint `json:"id"` 48 | } 49 | 50 | DelApiReq struct { 51 | Id uint `uri:"id"` 52 | } 53 | 54 | DelApiResp struct { 55 | Id uint `json:"id"` 56 | } 57 | ) 58 | 59 | func (l *Api) GetDetail(ctx context.Context, req *ApiDetailReq) (*ApiDetailResp, error) { 60 | log.Println("Api.GetDetail") 61 | return &ApiDetailResp{ 62 | Id: req.Id, 63 | Name: "demo", 64 | Remark: "hello world", 65 | }, nil 66 | } 67 | 68 | func (l *Api) GetList(ctx context.Context, req *ApiListReq) (*ApiListResp, error) { 69 | log.Println("Api.GetList", req) 70 | return &ApiListResp{ 71 | Total: 100, 72 | List: []*ApiInfoItem{ 73 | { 74 | Id: 10, 75 | Name: "demo", 76 | Remark: "hello world", 77 | }, 78 | }, 79 | }, nil 80 | } 81 | 82 | func (l *Api) UpdateInfo(ctx context.Context, req *ApiUpdateReq) (*ApiUpdateResp, error) { 83 | log.Println("Api.UpdateInfo") 84 | return &ApiUpdateResp{Id: req.Id}, nil 85 | } 86 | 87 | func (l *Api) DeleteInfo(ctx context.Context, req *DelApiReq) (*DelApiResp, error) { 88 | log.Println("Api.DeleteInfo") 89 | return &DelApiResp{Id: req.Id}, nil 90 | } 91 | 92 | func main() { 93 | r := gin.Default() 94 | ginInstance := ginplush.New(r, ginplush.WithControllers(&Api{})) 95 | ginInstance.Run(":8080") 96 | } 97 | -------------------------------------------------------------------------------- /handle_test.go: -------------------------------------------------------------------------------- 1 | package ginplus 2 | 3 | import ( 4 | "database/sql" 5 | "fmt" 6 | "testing" 7 | 8 | "github.com/gin-gonic/gin" 9 | oteltrace "go.opentelemetry.io/otel/trace" 10 | "gorm.io/driver/mysql" 11 | "gorm.io/gorm" 12 | ) 13 | 14 | type HandleApi struct { 15 | } 16 | 17 | func (l *HandleApi) Get() gin.HandlerFunc { 18 | sqlDB, err := sql.Open("mysql", "root:12345678@tcp(localhost:3060)/prom?charset=utf8mb4&parseTime=True&loc=Local") 19 | if err != nil { 20 | panic(err) 21 | } 22 | db, err := gorm.Open(mysql.New(mysql.Config{ 23 | Conn: sqlDB, 24 | })) 25 | if err != nil { 26 | panic(err) 27 | } 28 | 29 | db = db.Debug() 30 | 31 | if err := db.Use(&OpentracingPlugin{}); err != nil { 32 | panic(err) 33 | } 34 | return func(c *gin.Context) { 35 | _span := c.Value("span") 36 | fmt.Printf("span: %T\n", _span) 37 | ctx := c.Request.Context() 38 | span, ok := _span.(oteltrace.Span) 39 | if ok { 40 | fmt.Println("--------------ok------------------") 41 | ctx, span = span.TracerProvider().Tracer("api").Start(ctx, "HandleApi.Get") 42 | defer span.End() 43 | } 44 | type Dict struct { 45 | Id uint `gorm:"column:id;primaryKey;autoIncrement;comment:主键ID"` 46 | Name string `gorm:"column:name;type:varchar(255);not null;comment:字典名称"` 47 | } 48 | list := make([]*Dict, 0) 49 | err = db.Table("prom_dict").WithContext(ctx).Find(&list).Error 50 | if err != nil { 51 | c.String(200, err.Error()) 52 | return 53 | } 54 | 55 | var first Dict 56 | if err := db.Table("prom_dict").WithContext(ctx).First(&first).Error; err != nil { 57 | c.String(200, err.Error()) 58 | return 59 | } 60 | 61 | if err := db.Table("prom_dict").WithContext(ctx).Where("id = 0").Updates(&first).Error; err != nil { 62 | c.String(200, err.Error()) 63 | return 64 | } 65 | 66 | if err := db.Table("prom_dict").WithContext(ctx).Where("id = 0").Delete(&first).Error; err != nil { 67 | c.String(200, err.Error()) 68 | return 69 | } 70 | 71 | c.JSON(200, list) 72 | } 73 | } 74 | 75 | func NewHandleApiApi() *HandleApi { 76 | return &HandleApi{} 77 | } 78 | 79 | func TestApiHandleFunc(t *testing.T) { 80 | ginR := gin.New() 81 | 82 | midd := NewMiddleware() 83 | 84 | ginR.Use( 85 | midd.Tracing("http://localhost:14268/api/traces"), 86 | midd.Logger(), 87 | midd.IpLimit(100, 5), 88 | midd.Interceptor(), 89 | midd.Cors(), 90 | ) 91 | 92 | r := New(ginR, WithControllers(NewHandleApiApi())) 93 | 94 | NewCtrlC(r).Start() 95 | } 96 | -------------------------------------------------------------------------------- /ref.go: -------------------------------------------------------------------------------- 1 | package ginplus 2 | 3 | import ( 4 | "reflect" 5 | "strings" 6 | ) 7 | 8 | type ( 9 | Field struct { 10 | Name string 11 | Info []FieldInfo 12 | } 13 | 14 | FieldInfo struct { 15 | Type string 16 | Name string 17 | Tags Tag 18 | ChildType string 19 | Info []FieldInfo 20 | } 21 | 22 | Tag struct { 23 | FormKey string 24 | UriKey string 25 | Skip string 26 | JsonKey string 27 | Title string 28 | Format string 29 | Desc string 30 | } 31 | ) 32 | 33 | var tags = []string{"form", "uri", "json", "title", "format", "desc", "skip"} 34 | 35 | // var 临时存储已经处理过的结构体 36 | var tmpStruct = make(map[string]struct{}) 37 | 38 | // 获取结构体tag 39 | func getTag(t reflect.Type) []FieldInfo { 40 | if _, ok := tmpStruct[t.String()]; ok { 41 | return nil 42 | } 43 | tmpStruct[t.String()] = struct{}{} 44 | tmp := t 45 | for tmp.Kind() == reflect.Ptr { 46 | tmp = tmp.Elem() 47 | } 48 | 49 | if tmp.Kind() == reflect.Slice { 50 | tmp = tmp.Elem() 51 | for tmp.Kind() == reflect.Ptr { 52 | tmp = tmp.Elem() 53 | } 54 | } 55 | 56 | if tmp.Kind() != reflect.Struct { 57 | return nil 58 | } 59 | 60 | fieldList := make([]FieldInfo, 0, tmp.NumField()) 61 | for i := 0; i < tmp.NumField(); i++ { 62 | field := tmp.Field(i) 63 | fieldName := field.Name 64 | fieldType := field.Type.String() 65 | tagInfo := Tag{ 66 | Title: fieldName, 67 | } 68 | for _, tagKey := range tags { 69 | tagVal, ok := field.Tag.Lookup(tagKey) 70 | if !ok { 71 | continue 72 | } 73 | 74 | switch tagKey { 75 | case "form": 76 | tagInfo.FormKey = tagVal 77 | case "uri": 78 | tagInfo.UriKey = tagVal 79 | case "skip": 80 | tagInfo.Skip = tagVal 81 | case "title": 82 | tagInfo.Title = tagVal 83 | case "format": 84 | tagInfo.Format = tagVal 85 | case "desc": 86 | tagInfo.Desc = tagVal 87 | default: 88 | valList := strings.Split(tagVal, ",") 89 | tagInfo.JsonKey = valList[0] 90 | } 91 | } 92 | 93 | childType := field.Type 94 | if childType.Kind() == reflect.Slice { 95 | childType = childType.Elem() 96 | for childType.Kind() == reflect.Ptr { 97 | childType = childType.Elem() 98 | } 99 | } 100 | 101 | fieldList = append(fieldList, FieldInfo{ 102 | Type: fieldType, 103 | Name: fieldName, 104 | Tags: tagInfo, 105 | ChildType: childType.String(), 106 | Info: getTag(childType), 107 | }) 108 | } 109 | 110 | return fieldList 111 | } 112 | -------------------------------------------------------------------------------- /example/go.mod: -------------------------------------------------------------------------------- 1 | module gin-plus-demo 2 | 3 | go 1.21.0 4 | 5 | require ( 6 | github.com/aide-cloud/gin-plus v0.1.7 7 | github.com/gin-gonic/gin v1.9.1 8 | github.com/google/uuid v1.1.2 9 | github.com/graph-gophers/graphql-go v1.5.0 10 | ) 11 | 12 | require ( 13 | github.com/beorn7/perks v1.0.1 // indirect 14 | github.com/bytedance/sonic v1.10.0 // indirect 15 | github.com/cespare/xxhash/v2 v2.2.0 // indirect 16 | github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect 17 | github.com/chenzhuoyu/iasm v0.9.0 // indirect 18 | github.com/fsnotify/fsnotify v1.6.0 // indirect 19 | github.com/gabriel-vasile/mimetype v1.4.2 // indirect 20 | github.com/gin-contrib/sse v0.1.0 // indirect 21 | github.com/go-playground/locales v0.14.1 // indirect 22 | github.com/go-playground/universal-translator v0.18.1 // indirect 23 | github.com/go-playground/validator/v10 v10.15.3 // indirect 24 | github.com/goccy/go-json v0.10.2 // indirect 25 | github.com/golang/protobuf v1.5.3 // indirect 26 | github.com/hashicorp/hcl v1.0.0 // indirect 27 | github.com/json-iterator/go v1.1.12 // indirect 28 | github.com/klauspost/cpuid/v2 v2.2.5 // indirect 29 | github.com/leodido/go-urn v1.2.4 // indirect 30 | github.com/magiconair/properties v1.8.7 // indirect 31 | github.com/mattn/go-isatty v0.0.19 // indirect 32 | github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect 33 | github.com/mitchellh/mapstructure v1.5.0 // indirect 34 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 35 | github.com/modern-go/reflect2 v1.0.2 // indirect 36 | github.com/pelletier/go-toml/v2 v2.1.0 // indirect 37 | github.com/prometheus/client_golang v1.16.0 // indirect 38 | github.com/prometheus/client_model v0.4.0 // indirect 39 | github.com/prometheus/common v0.44.0 // indirect 40 | github.com/prometheus/procfs v0.11.1 // indirect 41 | github.com/spf13/afero v1.9.5 // indirect 42 | github.com/spf13/cast v1.5.1 // indirect 43 | github.com/spf13/jwalterweatherman v1.1.0 // indirect 44 | github.com/spf13/pflag v1.0.5 // indirect 45 | github.com/spf13/viper v1.16.0 // indirect 46 | github.com/subosito/gotenv v1.4.2 // indirect 47 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 48 | github.com/ugorji/go/codec v1.2.11 // indirect 49 | golang.org/x/arch v0.4.0 // indirect 50 | golang.org/x/crypto v0.12.0 // indirect 51 | golang.org/x/net v0.14.0 // indirect 52 | golang.org/x/sys v0.12.0 // indirect 53 | golang.org/x/text v0.12.0 // indirect 54 | google.golang.org/protobuf v1.31.0 // indirect 55 | gopkg.in/ini.v1 v1.67.0 // indirect 56 | gopkg.in/yaml.v3 v3.0.1 // indirect 57 | ) 58 | -------------------------------------------------------------------------------- /openapi_test.go: -------------------------------------------------------------------------------- 1 | package ginplus 2 | 3 | import ( 4 | "context" 5 | "log" 6 | "testing" 7 | 8 | "github.com/gin-gonic/gin" 9 | ) 10 | 11 | type ( 12 | Api struct { 13 | } 14 | 15 | ApiDetailReq struct { 16 | Id uint `uri:"id" desc:"数据自增唯一ID"` 17 | } 18 | 19 | ApiDetailResp struct { 20 | Id uint `json:"id"` 21 | Name string `json:"name"` 22 | Remark string `json:"remark"` 23 | } 24 | 25 | ApiListReq struct { 26 | Current int `form:"current"` 27 | Size int `form:"size"` 28 | Keyword string `form:"keyword"` 29 | } 30 | ApiListResp struct { 31 | Total int64 `json:"total"` 32 | List []*ApiInfoItem `json:"list"` 33 | } 34 | 35 | Extent struct { 36 | A `json:"a"` 37 | B `json:"b"` 38 | } 39 | 40 | ApiInfoItem struct { 41 | Name string `json:"name"` 42 | Id uint `json:"id"` 43 | Remark string `json:"remark"` 44 | Extent Extent `json:"extent"` 45 | } 46 | 47 | ApiUpdateReq struct { 48 | Id uint `uri:"id"` 49 | Name string `json:"name"` 50 | Remark string `json:"remark"` 51 | } 52 | ApiUpdateResp struct { 53 | Id uint `json:"id"` 54 | } 55 | 56 | DelApiReq struct { 57 | Id uint `uri:"id"` 58 | } 59 | 60 | DelApiResp struct { 61 | Id uint `json:"id"` 62 | } 63 | ) 64 | 65 | func (l *Api) GetDetail(_ context.Context, req *ApiDetailReq) (*ApiDetailResp, error) { 66 | log.Println("Api.GetDetail") 67 | return &ApiDetailResp{ 68 | Id: req.Id, 69 | Name: "demo", 70 | Remark: "hello world", 71 | }, nil 72 | } 73 | 74 | func (l *Api) GetList(_ context.Context, _ *ApiListReq) (*ApiListResp, error) { 75 | log.Println("Api.GetList") 76 | return &ApiListResp{ 77 | Total: 100, 78 | List: []*ApiInfoItem{ 79 | { 80 | Id: 10, 81 | Name: "demo", 82 | Remark: "hello world", 83 | }, 84 | }, 85 | }, nil 86 | } 87 | 88 | func (l *Api) UpdateInfo(_ context.Context, req *ApiUpdateReq) (*ApiUpdateResp, error) { 89 | log.Println("Api.UpdateInfo") 90 | return &ApiUpdateResp{Id: req.Id}, nil 91 | } 92 | 93 | func (l *Api) DeleteInfo(_ context.Context, req *DelApiReq) (*DelApiResp, error) { 94 | log.Println("Api.DeleteInfo") 95 | return &DelApiResp{Id: req.Id}, nil 96 | } 97 | 98 | func TestGinEngine_execute(t *testing.T) { 99 | New(gin.Default(), 100 | WithControllers(&Api{}), 101 | WithOpenApiYaml("./", "openapi.yaml"), 102 | AppendHttpMethodPrefixes( 103 | HttpMethod{ 104 | Prefix: "Update", 105 | Method: Put, 106 | }, HttpMethod{ 107 | Prefix: "Del", 108 | Method: Delete, 109 | }, 110 | ), 111 | ) 112 | } 113 | -------------------------------------------------------------------------------- /swagger/dist/oauth2-redirect.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Swagger UI: OAuth2 Redirect 5 | 6 | 7 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/aide-cloud/gin-plus 2 | 3 | go 1.21 4 | 5 | require ( 6 | github.com/gin-gonic/gin v1.9.1 7 | github.com/graph-gophers/graphql-go v1.5.0 8 | github.com/prometheus/client_golang v1.16.0 9 | github.com/spf13/viper v1.16.0 10 | go.opentelemetry.io/otel v1.18.0 11 | go.opentelemetry.io/otel/exporters/jaeger v1.17.0 12 | go.opentelemetry.io/otel/sdk v1.18.0 13 | go.opentelemetry.io/otel/trace v1.18.0 14 | go.uber.org/zap v1.21.0 15 | gorm.io/driver/mysql v1.5.1 16 | gorm.io/gorm v1.25.4 17 | ) 18 | 19 | require ( 20 | github.com/beorn7/perks v1.0.1 // indirect 21 | github.com/bytedance/sonic v1.9.1 // indirect 22 | github.com/cespare/xxhash/v2 v2.2.0 // indirect 23 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect 24 | github.com/fsnotify/fsnotify v1.6.0 // indirect 25 | github.com/gabriel-vasile/mimetype v1.4.2 // indirect 26 | github.com/gin-contrib/sse v0.1.0 // indirect 27 | github.com/go-logr/logr v1.2.4 // indirect 28 | github.com/go-logr/stdr v1.2.2 // indirect 29 | github.com/go-playground/locales v0.14.1 // indirect 30 | github.com/go-playground/universal-translator v0.18.1 // indirect 31 | github.com/go-playground/validator/v10 v10.14.0 // indirect 32 | github.com/go-sql-driver/mysql v1.7.0 // indirect 33 | github.com/goccy/go-json v0.10.2 // indirect 34 | github.com/golang/protobuf v1.5.3 // indirect 35 | github.com/hashicorp/hcl v1.0.0 // indirect 36 | github.com/jinzhu/inflection v1.0.0 // indirect 37 | github.com/jinzhu/now v1.1.5 // indirect 38 | github.com/json-iterator/go v1.1.12 // indirect 39 | github.com/klauspost/cpuid/v2 v2.2.4 // indirect 40 | github.com/leodido/go-urn v1.2.4 // indirect 41 | github.com/magiconair/properties v1.8.7 // indirect 42 | github.com/mattn/go-isatty v0.0.19 // indirect 43 | github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect 44 | github.com/mitchellh/mapstructure v1.5.0 // indirect 45 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 46 | github.com/modern-go/reflect2 v1.0.2 // indirect 47 | github.com/pelletier/go-toml/v2 v2.0.8 // indirect 48 | github.com/prometheus/client_model v0.4.0 // indirect 49 | github.com/prometheus/common v0.44.0 // indirect 50 | github.com/prometheus/procfs v0.11.1 // indirect 51 | github.com/spf13/afero v1.9.5 // indirect 52 | github.com/spf13/cast v1.5.1 // indirect 53 | github.com/spf13/jwalterweatherman v1.1.0 // indirect 54 | github.com/spf13/pflag v1.0.5 // indirect 55 | github.com/subosito/gotenv v1.4.2 // indirect 56 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 57 | github.com/ugorji/go/codec v1.2.11 // indirect 58 | go.opentelemetry.io/otel/metric v1.18.0 // indirect 59 | go.uber.org/atomic v1.11.0 // indirect 60 | go.uber.org/multierr v1.8.0 // indirect 61 | golang.org/x/arch v0.3.0 // indirect 62 | golang.org/x/crypto v0.9.0 // indirect 63 | golang.org/x/net v0.10.0 // indirect 64 | golang.org/x/sys v0.12.0 // indirect 65 | golang.org/x/text v0.9.0 // indirect 66 | google.golang.org/protobuf v1.31.0 // indirect 67 | gopkg.in/ini.v1 v1.67.0 // indirect 68 | gopkg.in/yaml.v3 v3.0.1 // indirect 69 | ) 70 | -------------------------------------------------------------------------------- /util_test.go: -------------------------------------------------------------------------------- 1 | package ginplus 2 | 3 | import ( 4 | "context" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/gin-gonic/gin" 9 | ) 10 | 11 | type A struct { 12 | } 13 | 14 | type B struct { 15 | } 16 | 17 | func (a *A) Middlewares() []gin.HandlerFunc { 18 | return nil 19 | } 20 | 21 | func Test_isController(t *testing.T) { 22 | type args struct { 23 | c any 24 | } 25 | 26 | tests := []struct { 27 | name string 28 | args args 29 | want bool 30 | }{ 31 | { 32 | name: "test", 33 | args: args{ 34 | c: &A{}, 35 | }, 36 | want: true, 37 | }, 38 | { 39 | name: "test", 40 | args: args{ 41 | c: &B{}, 42 | }, 43 | want: false, 44 | }, 45 | } 46 | for _, tt := range tests { 47 | t.Run(tt.name, func(t *testing.T) { 48 | _, got := isMiddleware(tt.args.c) 49 | if got != tt.want { 50 | t.Errorf("isController() = %v, want %v", got, tt.want) 51 | } 52 | }) 53 | } 54 | } 55 | 56 | type ( 57 | Call struct { 58 | } 59 | Req struct { 60 | Name string `json:"name"` 61 | } 62 | Resp struct { 63 | Code int `json:"code"` 64 | Msg string `json:"msg"` 65 | Data any `json:"data"` 66 | } 67 | ) 68 | 69 | func (c *Call) CallBack(_ context.Context, _ Req) (Resp, error) { 70 | return Resp{}, nil 71 | } 72 | 73 | func Test_isCallBack(t *testing.T) { 74 | ty := reflect.TypeOf(&Call{}) 75 | m, ok := ty.MethodByName("CallBack") 76 | if !ok { 77 | t.Error("not found method") 78 | } 79 | 80 | req, resp, ok := isCallBack(m.Type) 81 | if ok { 82 | t.Error("not found method CallBack: ", m.Type.String()) 83 | } 84 | 85 | t.Log(req, resp) 86 | } 87 | 88 | func TestGinEngine_parseRoute(t *testing.T) { 89 | instance := New(gin.Default()) 90 | t.Log(instance.parseRoute("Delete")) 91 | } 92 | 93 | func TestGinEngine_isPublic(t *testing.T) { 94 | if !isPublic("Abc") { 95 | t.Log("Abc is public") 96 | } 97 | 98 | if isPublic("abc") { 99 | t.Log("abc is private") 100 | } 101 | } 102 | 103 | type Pub struct { 104 | v1 *V1 105 | V1x *V1 106 | *V1 107 | } 108 | 109 | type V1 struct { 110 | } 111 | 112 | type PubReq struct { 113 | EID string `uri:"eid"` 114 | Id int `uri:"id"` 115 | Name string `json:"name"` 116 | } 117 | 118 | type PubResp struct { 119 | Code int `json:"code"` 120 | Msg string `json:"msg"` 121 | Data any `json:"data"` 122 | } 123 | 124 | func (p *Pub) GetPingA(_ context.Context, _ *PubReq) (*PubResp, error) { 125 | return nil, nil 126 | } 127 | 128 | func (p *Pub) getPingB(_ context.Context, _ *PubReq) (*PubResp, error) { 129 | return nil, nil 130 | } 131 | 132 | func (p *V1) GetList(_ context.Context, _ *PubReq) (*PubResp, error) { 133 | return nil, nil 134 | } 135 | 136 | func TestPrivateMethod(t *testing.T) { 137 | New(gin.Default(), WithControllers(&Pub{ 138 | v1: &V1{}, 139 | }), AppendHttpMethodPrefixes( 140 | HttpMethod{ 141 | Prefix: "get", 142 | Method: httpMethod{get}, 143 | }, 144 | )) 145 | } 146 | 147 | func TestGinEngine_genStructRoute(t *testing.T) { 148 | New(gin.Default(), WithControllers(&Pub{ 149 | v1: &V1{}, 150 | }), AppendHttpMethodPrefixes( 151 | HttpMethod{ 152 | Prefix: "Get", 153 | Method: httpMethod{get}, 154 | }, 155 | )) 156 | } 157 | 158 | type GenRouteV1 struct{} 159 | type GenRouteV2 struct{} 160 | type GenRouteApi struct { 161 | ApiV1 *GenRouteV1 162 | ApiV2 *GenRouteV2 163 | } 164 | 165 | func (g *GenRouteV1) GetPing(_ context.Context, _ *PubReq) (*PubResp, error) { 166 | return nil, nil 167 | } 168 | func (g *GenRouteV2) PostPing(_ context.Context, _ *PubReq) (*PubResp, error) { 169 | return nil, nil 170 | } 171 | func (g *GenRouteApi) PutPing(_ context.Context, _ *PubReq) (*PubResp, error) { 172 | return nil, nil 173 | } 174 | func (g *GenRouteApi) DeletePing(_ context.Context, _ *PubReq) (*PubResp, error) { 175 | return nil, nil 176 | } 177 | 178 | func TestGinEngine_genRoute(t *testing.T) { 179 | New(gin.Default(), WithControllers(&GenRouteApi{ 180 | ApiV1: &GenRouteV1{}, 181 | ApiV2: &GenRouteV2{}, 182 | })) 183 | } 184 | 185 | type LogicApi struct{} 186 | 187 | type LogicApiReq struct { 188 | EID string `uri:"eid" skip:"true"` 189 | PID uint `uri:"pid" skip:"true"` 190 | Id int `uri:"id"` 191 | } 192 | type LogicApiResp struct { 193 | Code int `json:"code"` 194 | Msg string `json:"msg"` 195 | Data any `json:"data"` 196 | } 197 | 198 | func (l *LogicApi) GetPing(_ context.Context, req *LogicApiReq) (*LogicApiResp, error) { 199 | return &LogicApiResp{ 200 | Code: 0, 201 | Msg: "ok", 202 | Data: req, 203 | }, nil 204 | } 205 | 206 | func TestGinEngine_GenRoute(t *testing.T) { 207 | i := New(gin.Default()) 208 | group := i.Group("/enterprise/:eid/project/:pid") 209 | i.GenRoute(group, &LogicApi{}).RegisterSwaggerUI() 210 | NewCtrlC(i).Start() 211 | } 212 | -------------------------------------------------------------------------------- /example/openapi.yaml: -------------------------------------------------------------------------------- 1 | info: 2 | title: github.com/aide-cloud/gin-plus 3 | version: v0.1.6 4 | openapi: 3.0.3 5 | paths: 6 | /info/:id: 7 | get: 8 | operationId: GetInfo 9 | responses: 10 | 200: 11 | description: "" 12 | content: 13 | application/json: 14 | schema: 15 | type: object 16 | properties: 17 | id: 18 | type: integer 19 | title: Id 20 | name: 21 | type: string 22 | title: Name 23 | parameters: 24 | - name: id 25 | in: path 26 | required: true 27 | schema: 28 | type: integer 29 | title: Id 30 | - name: name 31 | in: query 32 | schema: 33 | type: string 34 | title: Name 35 | requestBody: 36 | content: 37 | application/json: 38 | schema: 39 | type: object 40 | properties: 41 | data: 42 | type: array 43 | title: Data 44 | items: 45 | type: object 46 | post: 47 | operationId: PostInfo 48 | responses: 49 | 200: 50 | description: "" 51 | content: 52 | application/json: 53 | schema: 54 | type: object 55 | properties: 56 | id: 57 | type: integer 58 | title: Id 59 | name: 60 | type: string 61 | title: Name 62 | parameters: 63 | - name: id 64 | in: path 65 | required: true 66 | schema: 67 | type: integer 68 | title: Id 69 | - name: name 70 | in: query 71 | schema: 72 | type: string 73 | title: Name 74 | requestBody: 75 | content: 76 | application/json: 77 | schema: 78 | type: object 79 | properties: 80 | data: 81 | type: array 82 | title: Data 83 | items: 84 | type: object 85 | put: 86 | operationId: PutInfo 87 | responses: 88 | 200: 89 | description: "" 90 | content: 91 | application/json: 92 | schema: 93 | type: object 94 | properties: 95 | id: 96 | type: integer 97 | title: Id 98 | name: 99 | type: string 100 | title: Name 101 | parameters: 102 | - name: id 103 | in: path 104 | required: true 105 | schema: 106 | type: integer 107 | title: Id 108 | - name: name 109 | in: query 110 | schema: 111 | type: string 112 | title: Name 113 | requestBody: 114 | content: 115 | application/json: 116 | schema: 117 | type: object 118 | properties: 119 | data: 120 | type: array 121 | title: Data 122 | items: 123 | type: object 124 | -------------------------------------------------------------------------------- /tracer.go: -------------------------------------------------------------------------------- 1 | package ginplus 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/gin-gonic/gin" 7 | "go.opentelemetry.io/otel" 8 | "go.opentelemetry.io/otel/attribute" 9 | "go.opentelemetry.io/otel/exporters/jaeger" 10 | "go.opentelemetry.io/otel/sdk/resource" 11 | tracesdk "go.opentelemetry.io/otel/sdk/trace" 12 | semconv "go.opentelemetry.io/otel/semconv/v1.21.0" 13 | oteltrace "go.opentelemetry.io/otel/trace" 14 | "gorm.io/gorm" 15 | ) 16 | 17 | // 告诉编译器这个结构体实现了gorm.Plugin接口 18 | var _ gorm.Plugin = (*OpentracingPlugin)(nil) 19 | 20 | type tracingConfig struct { 21 | // URL 上报地址 22 | URL string 23 | KeyValue func(c *gin.Context) []attribute.KeyValue 24 | } 25 | 26 | type TracingOption func(*tracingConfig) 27 | 28 | // WithTracingURL 设置上报地址 29 | func WithTracingURL(url string) TracingOption { 30 | return func(c *tracingConfig) { 31 | c.URL = url 32 | } 33 | } 34 | 35 | // WithTracingKeyValueFunc 设置KeyValueFunc 36 | func WithTracingKeyValueFunc(keyValue func(c *gin.Context) []attribute.KeyValue) TracingOption { 37 | return func(c *tracingConfig) { 38 | c.KeyValue = keyValue 39 | } 40 | } 41 | 42 | func defaultKeyValueFunc(c *gin.Context) []attribute.KeyValue { 43 | return []attribute.KeyValue{ 44 | attribute.String("http.method", c.Request.Method), 45 | attribute.String("http.host", c.Request.Host), 46 | attribute.String("http.user_agent", c.Request.UserAgent()), 47 | attribute.String("http.client_ip", c.ClientIP()), 48 | } 49 | } 50 | 51 | func tracerProvider(url, serviceName, environment, id string) *tracesdk.TracerProvider { 52 | // Create the Jaeger exporter 53 | exp, err := jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint(url))) 54 | if err != nil { 55 | panic(err) 56 | } 57 | tp := tracesdk.NewTracerProvider( 58 | // Always be sure to batch in production. 59 | tracesdk.WithBatcher(exp), 60 | // Record information about this application in a Resource. 61 | tracesdk.WithResource(resource.NewWithAttributes( 62 | semconv.SchemaURL, 63 | semconv.ServiceNameKey.String(serviceName), 64 | attribute.String("environment", environment), 65 | attribute.String("ID", id), 66 | )), 67 | ) 68 | otel.SetTracerProvider(tp) 69 | return tp 70 | } 71 | 72 | const gormSpanKey = "__gorm_span" 73 | const gormTime = "__gorm_time" 74 | 75 | func before(db *gorm.DB) { 76 | // 先从父级spans生成子span ---> 这里命名为gorm,但实际上可以自定义 77 | // 自己喜欢的operationName 78 | ctx, span := otel.Tracer("gorm").Start(db.Statement.Context, "gorm") 79 | // 利用db实例去传递span 80 | db.InstanceSet(gormSpanKey, span) 81 | db.InstanceSet(gormTime, time.Now()) 82 | // 设置context 83 | db.WithContext(ctx) 84 | } 85 | 86 | func after(db *gorm.DB) { 87 | // 从GORM的DB实例中取出span 88 | _span, isExist := db.InstanceGet(gormSpanKey) 89 | if !isExist { 90 | // 不存在就直接抛弃掉 91 | return 92 | } 93 | 94 | // 断言进行类型转换 95 | span, ok := _span.(oteltrace.Span) 96 | if !ok { 97 | return 98 | } 99 | defer span.End() 100 | 101 | // Error 102 | if db.Error != nil { 103 | span.SetAttributes(attribute.String("error", db.Error.Error())) 104 | } 105 | // sql --> 写法来源GORM V2的日志 106 | span.SetAttributes(attribute.String("sql", db.Dialector.Explain(db.Statement.SQL.String(), db.Statement.Vars...))) 107 | // rows 108 | span.SetAttributes(attribute.Int64("rows", db.RowsAffected)) 109 | // elapsed 110 | _time, isExist := db.InstanceGet(gormTime) 111 | if !isExist { 112 | return 113 | } 114 | startTime, ok := _time.(time.Time) 115 | if ok { 116 | span.SetAttributes(attribute.String("elapsed", time.Since(startTime).String())) 117 | } 118 | } 119 | 120 | const ( 121 | callBackBeforeName = "opentracing:before" 122 | callBackAfterName = "opentracing:after" 123 | ) 124 | 125 | type OpentracingPlugin struct{} 126 | 127 | // NewOpentracingPlugin 创建一个opentracing插件 128 | func NewOpentracingPlugin() *OpentracingPlugin { 129 | return &OpentracingPlugin{} 130 | } 131 | 132 | func (op *OpentracingPlugin) Name() string { 133 | return "opentracingPlugin" 134 | } 135 | 136 | func (op *OpentracingPlugin) Initialize(db *gorm.DB) (err error) { 137 | // 开始前 - 并不是都用相同的方法,可以自己自定义 138 | if err = db.Callback().Create().Before("gorm:before_create").Register(callBackBeforeName, before); err != nil { 139 | return err 140 | } 141 | if err = db.Callback().Query().Before("gorm:query").Register(callBackBeforeName, before); err != nil { 142 | return err 143 | } 144 | if err = db.Callback().Delete().Before("gorm:before_delete").Register(callBackBeforeName, before); err != nil { 145 | return err 146 | } 147 | if err = db.Callback().Update().Before("gorm:setup_reflect_value").Register(callBackBeforeName, before); err != nil { 148 | return err 149 | } 150 | if err = db.Callback().Row().Before("gorm:row").Register(callBackBeforeName, before); err != nil { 151 | return err 152 | } 153 | if err = db.Callback().Raw().Before("gorm:raw").Register(callBackBeforeName, before); err != nil { 154 | return err 155 | } 156 | 157 | // 结束后 - 并不是都用相同的方法,可以自己自定义 158 | if err = db.Callback().Create().After("gorm:after_create").Register(callBackAfterName, after); err != nil { 159 | return err 160 | } 161 | if err = db.Callback().Query().After("gorm:after_query").Register(callBackAfterName, after); err != nil { 162 | return err 163 | } 164 | if err = db.Callback().Delete().After("gorm:after_delete").Register(callBackAfterName, after); err != nil { 165 | return err 166 | } 167 | if err = db.Callback().Update().After("gorm:after_update").Register(callBackAfterName, after); err != nil { 168 | return err 169 | } 170 | if err = db.Callback().Row().After("gorm:row").Register(callBackAfterName, after); err != nil { 171 | return err 172 | } 173 | if err = db.Callback().Raw().After("gorm:raw").Register(callBackAfterName, after); err != nil { 174 | return err 175 | } 176 | return 177 | } 178 | -------------------------------------------------------------------------------- /openapi.go: -------------------------------------------------------------------------------- 1 | package ginplus 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/spf13/viper" 7 | ) 8 | 9 | const defaultOpenApiYaml = "openapi.yaml" 10 | 11 | type ( 12 | Info struct { 13 | Title string `yaml:"title,omitempty"` 14 | Version string `yaml:"version,omitempty"` 15 | } 16 | 17 | Properties struct { 18 | Properties map[string]SchemaInfo `yaml:"properties,omitempty"` 19 | Type string `yaml:"type,omitempty"` 20 | } 21 | 22 | SchemaInfo struct { 23 | Type string `yaml:"type,omitempty"` 24 | Title string `yaml:"title,omitempty"` 25 | Format string `yaml:"format,omitempty"` 26 | Description string `yaml:"description,omitempty"` 27 | Properties map[string]SchemaInfo `yaml:"properties,omitempty"` 28 | Items Properties `yaml:"items,omitempty"` 29 | } 30 | 31 | Schema struct { 32 | Schema SchemaInfo `yaml:"schema,omitempty"` 33 | } 34 | 35 | ApiContent map[string]Schema 36 | 37 | ApiResponse struct { 38 | Description string `yaml:"description"` 39 | Content ApiContent `yaml:"content,omitempty"` 40 | } 41 | 42 | ApiRequest struct { 43 | Content ApiContent `yaml:"content,omitempty"` 44 | } 45 | 46 | Parameter struct { 47 | Name string `yaml:"name,omitempty"` 48 | In string `yaml:"in,omitempty"` 49 | Required bool `yaml:"required,omitempty"` 50 | Schema SchemaInfo `yaml:"schema,omitempty"` 51 | } 52 | 53 | ApiHttpMethod struct { 54 | OperationId string `yaml:"operationId,omitempty"` 55 | Tags []string `yaml:"tags,omitempty"` 56 | Responses map[int]ApiResponse `yaml:"responses,omitempty"` 57 | Parameters []Parameter `yaml:"parameters,omitempty"` 58 | RequestBody ApiRequest `yaml:"requestBody,omitempty"` 59 | } 60 | 61 | Path map[string]map[string]ApiHttpMethod 62 | 63 | ApiTemplate struct { 64 | Openapi string `yaml:"openapi,omitempty"` 65 | Info Info `yaml:"info,omitempty"` 66 | Paths map[string]Path `yaml:"paths,omitempty"` 67 | } 68 | ) 69 | 70 | func (l *GinEngine) genOpenApiYaml() { 71 | apiYaml := defaultOpenApiYaml 72 | if l.defaultOpenApiYaml != "" && strings.HasSuffix(l.defaultOpenApiYaml, ".yaml") { 73 | apiYaml = l.defaultOpenApiYaml 74 | } 75 | 76 | // 写入之前先清空viper 77 | viper.Reset() 78 | 79 | viper.SetConfigFile(apiYaml) 80 | viper.SetConfigPermissions(0644) 81 | viper.SetConfigType("yaml") 82 | viper.Set("openapi", "3.1.3") 83 | viper.Set("info", Info{ 84 | Title: l.apiConfig.Title, 85 | Version: l.apiConfig.Version, 86 | }) 87 | viper.Set("paths", l.apiToYamlModel()) 88 | 89 | if err := viper.WriteConfig(); err != nil { 90 | panic(err) 91 | } 92 | } 93 | 94 | func isUri(uriStr string) bool { 95 | return uriStr != "" && uriStr != "-" 96 | } 97 | 98 | func (l *GinEngine) apiToYamlModel() Path { 99 | apiRoutes := l.apiRoutes 100 | 101 | apiPath := make(Path) 102 | for url, info := range apiRoutes { 103 | if _, ok := apiPath[url]; !ok { 104 | apiPath[url] = make(map[string]ApiHttpMethod) 105 | } 106 | 107 | methodRoute := make(map[string]ApiHttpMethod) 108 | for _, route := range info { 109 | methodRoute[route.HttpMethod] = ApiHttpMethod{ 110 | OperationId: route.MethodName, 111 | Tags: nil, 112 | Responses: map[int]ApiResponse{ 113 | 200: { 114 | Content: map[string]Schema{ 115 | "application/json": { 116 | Schema: SchemaInfo{ 117 | Type: "object", 118 | Title: route.RespParams.Name, 119 | Properties: genProperties(route.RespParams.Info), 120 | }, 121 | }, 122 | }, 123 | }, 124 | }, 125 | Parameters: func() []Parameter { 126 | infos := route.ReqParams.Info 127 | res := make([]Parameter, 0, len(infos)) 128 | for _, fieldInfo := range infos { 129 | if fieldInfo.Tags.FormKey == "" && fieldInfo.Tags.UriKey == "" { 130 | continue 131 | } 132 | 133 | name := fieldInfo.Tags.FormKey 134 | in := "query" 135 | isUriParam := isUri(fieldInfo.Tags.UriKey) 136 | if isUriParam { 137 | name = fieldInfo.Tags.UriKey 138 | in = "path" 139 | } 140 | 141 | res = append(res, Parameter{ 142 | Name: name, 143 | In: in, 144 | Required: isUriParam, 145 | Schema: SchemaInfo{ 146 | Type: getTypeMap(fieldInfo.Type), 147 | Title: fieldInfo.Tags.Title, 148 | Format: fieldInfo.Tags.Format, 149 | Description: fieldInfo.Tags.Desc, 150 | }, 151 | }) 152 | } 153 | 154 | return res 155 | }(), 156 | RequestBody: ApiRequest{ 157 | Content: map[string]Schema{ 158 | "application/json": { 159 | Schema: SchemaInfo{ 160 | Type: "object", 161 | Title: route.ReqParams.Name, 162 | Properties: genProperties(route.ReqParams.Info), 163 | }, 164 | }, 165 | }, 166 | }, 167 | } 168 | } 169 | apiPath[url] = methodRoute 170 | } 171 | return apiPath 172 | } 173 | 174 | func genProperties(fieldList []FieldInfo) map[string]SchemaInfo { 175 | if len(fieldList) == 0 { 176 | return nil 177 | } 178 | resp := make(map[string]SchemaInfo) 179 | for _, info := range fieldList { 180 | jsonKey := info.Tags.JsonKey 181 | if jsonKey == "-" || jsonKey == "" { 182 | continue 183 | } 184 | fieldType := getTypeMap(info.Type) 185 | 186 | schema := SchemaInfo{ 187 | Type: fieldType, 188 | Title: info.Tags.Title, 189 | Format: info.Tags.Format, 190 | Description: info.Tags.Desc, 191 | } 192 | 193 | switch fieldType { 194 | case "object": 195 | schema.Properties = genProperties(info.Info) 196 | case "array": 197 | schema.Items.Properties = genProperties(info.Info) 198 | schema.Items.Type = getTypeMap(info.ChildType) 199 | } 200 | 201 | resp[info.Tags.JsonKey] = schema 202 | } 203 | 204 | return resp 205 | } 206 | 207 | // "array", "boolean", "integer", "null", "number", "object", "string" 208 | func getTypeMap(typeStr string) string { 209 | switch typeStr { 210 | case "int", "int8", "int16", "int32", "int64", "uint", "uint8", "uint16", "uint32", "uint64", "uintptr": 211 | return "integer" 212 | case "float32", "float64", "complex64", "complex128": 213 | return "number" 214 | case "boolean", "string", "array", "bool": 215 | return typeStr 216 | default: 217 | if strings.HasPrefix(typeStr, "[]") { 218 | return "array" 219 | } 220 | return "object" 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /new_test.go: -------------------------------------------------------------------------------- 1 | package ginplus 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "log" 7 | "testing" 8 | 9 | "github.com/gin-gonic/gin" 10 | ) 11 | 12 | type People struct { 13 | *Img 14 | } 15 | 16 | type Img struct { 17 | File *File 18 | } 19 | 20 | type File struct { 21 | } 22 | 23 | var _ IMiddleware = (*People)(nil) 24 | var _ Controller = (*People)(nil) 25 | var _ MethodeMiddleware = (*People)(nil) 26 | 27 | func (l *Img) GetImgInfo() gin.HandlerFunc { 28 | return func(ctx *gin.Context) { 29 | ctx.String(200, "GetInfo") 30 | } 31 | } 32 | 33 | func (l *File) GetFiles() gin.HandlerFunc { 34 | return func(ctx *gin.Context) { 35 | ctx.String(200, "GetFiles") 36 | } 37 | } 38 | 39 | func (p *People) GetInfo() gin.HandlerFunc { 40 | return func(ctx *gin.Context) { 41 | ctx.String(200, "GetInfo") 42 | } 43 | } 44 | 45 | func (p *People) List() gin.HandlerFunc { 46 | return func(ctx *gin.Context) { 47 | ctx.String(200, "List") 48 | } 49 | } 50 | 51 | func (p *People) PostCreateInfo(_ context.Context, _ struct{ Name string }) (string, error) { 52 | return "PostCreateInfo", errors.New("custom error") 53 | } 54 | 55 | func (p *People) PutUpdateInfo(_ context.Context, req struct{ Name string }) (struct{ Name string }, error) { 56 | return struct{ Name string }{Name: req.Name}, nil 57 | } 58 | 59 | func (p *People) Middlewares() []gin.HandlerFunc { 60 | return []gin.HandlerFunc{ 61 | func(context *gin.Context) { 62 | log.Println("middleware1") 63 | }, 64 | func(context *gin.Context) { 65 | log.Println("middleware2") 66 | }, 67 | } 68 | } 69 | 70 | func (p *People) BasePath() string { 71 | return "/people/v1" 72 | } 73 | 74 | func (p *People) MethodeMiddlewares() map[string][]gin.HandlerFunc { 75 | return map[string][]gin.HandlerFunc{ 76 | "GetInfo": { 77 | func(ctx *gin.Context) { 78 | log.Println("GetInfo middleware1") 79 | }, 80 | }, 81 | } 82 | } 83 | 84 | type Slice []string 85 | 86 | var _ IMiddleware = (*Slice)(nil) 87 | 88 | func (l *Slice) Middlewares() []gin.HandlerFunc { 89 | return nil 90 | } 91 | 92 | func (l *Slice) GetInfo() gin.HandlerFunc { 93 | return func(ctx *gin.Context) { 94 | ctx.String(200, "GetInfo") 95 | } 96 | } 97 | 98 | func TestNew(t *testing.T) { 99 | r := gin.Default() 100 | opts := []OptionFun{ 101 | WithMiddlewares(func(ctx *gin.Context) { 102 | log.Println("main middleware") 103 | }), 104 | WithBasePath("aide-cloud"), 105 | WithHttpMethodPrefixes(HttpMethod{ 106 | Prefix: "Get", 107 | Method: Get, 108 | }, HttpMethod{ 109 | Prefix: "Post", 110 | Method: Post, 111 | }, HttpMethod{ 112 | Prefix: "Put", 113 | Method: Put, 114 | }, HttpMethod{ 115 | Prefix: "Delete", 116 | Method: Delete, 117 | }), 118 | WithControllers(&People{ 119 | Img: &Img{ 120 | File: &File{}, 121 | }, 122 | }, &Slice{}), 123 | WithRouteNamingRuleFunc(func(methodName string) string { 124 | return routeToCamel(methodName) 125 | }), 126 | WithApiConfig(ApiConfig{ 127 | Title: "aide-cloud-api", 128 | Version: "v1", 129 | }), 130 | } 131 | ginInstance := New(r, opts...) 132 | _ = ginInstance.Run(":8080") 133 | } 134 | 135 | type ( 136 | MyController struct { 137 | } 138 | 139 | MyControllerReq struct { 140 | Name string `form:"name"` 141 | Id uint `uri:"id"` 142 | Keyword string `form:"keyword"` 143 | } 144 | 145 | MyControllerResp struct { 146 | Name string `json:"name"` 147 | Id uint `json:"id"` 148 | Age int `json:"-"` 149 | Data any `json:"data"` 150 | } 151 | ) 152 | 153 | func (l *MyController) GetInfo(_ context.Context, req MyControllerReq) (*MyControllerResp, error) { 154 | log.Println(req) 155 | return nil, nil 156 | } 157 | 158 | func TestGenApi(t *testing.T) { 159 | r := gin.Default() 160 | opts := []OptionFun{ 161 | WithBasePath("aide-cloud"), 162 | WithControllers(&MyController{}), 163 | } 164 | ginInstance := New(r, opts...) 165 | ginInstance.genOpenApiYaml() 166 | } 167 | 168 | func TestGenApiRun(t *testing.T) { 169 | r := gin.Default() 170 | opts := []OptionFun{ 171 | WithBasePath("aide-cloud"), 172 | WithControllers(&MyController{}), 173 | } 174 | ginInstance := New(r, opts...) 175 | _ = ginInstance.Run() 176 | } 177 | 178 | type ( 179 | MidController struct { 180 | ChildMidController *ChildMidController 181 | } 182 | 183 | ChildMidController struct { 184 | } 185 | ) 186 | 187 | func (l *ChildMidController) Middlewares() []gin.HandlerFunc { 188 | return []gin.HandlerFunc{ 189 | func(ctx *gin.Context) { 190 | log.Println("ChildMidController") 191 | }, 192 | } 193 | } 194 | 195 | func (l *ChildMidController) Info() gin.HandlerFunc { 196 | return func(ctx *gin.Context) { 197 | log.Println("Info action") 198 | } 199 | } 200 | 201 | type DetailReq struct { 202 | Id uint `uri:"id"` 203 | } 204 | 205 | type DetailResp struct { 206 | Name string `json:"name"` 207 | Id uint `json:"id"` 208 | } 209 | 210 | func (l *ChildMidController) Detail(_ context.Context, req *DetailReq) (*DetailResp, error) { 211 | log.Println("Detail") 212 | return &DetailResp{Name: "aide-cloud", Id: req.Id}, nil 213 | } 214 | 215 | func (l *MidController) Middlewares() []gin.HandlerFunc { 216 | return []gin.HandlerFunc{ 217 | func(ctx *gin.Context) { 218 | log.Println("MidController") 219 | }, 220 | } 221 | } 222 | 223 | func (l *MidController) GetParent() gin.HandlerFunc { 224 | return func(ctx *gin.Context) { 225 | log.Println("Parent action") 226 | } 227 | } 228 | 229 | var _ IMiddleware = (*MidController)(nil) 230 | var _ IMiddleware = (*ChildMidController)(nil) 231 | 232 | func TestGenApiRunMid(t *testing.T) { 233 | r := gin.Default() 234 | opts := []OptionFun{ 235 | WithBasePath("aide-cloud"), 236 | WithControllers(&MidController{ 237 | ChildMidController: &ChildMidController{}, 238 | }), 239 | } 240 | ginInstance := New(r, opts...) 241 | _ = ginInstance.Run() 242 | } 243 | 244 | type ( 245 | Path1 struct { 246 | Path2 *Path2 247 | } 248 | Path2 struct { 249 | } 250 | ) 251 | 252 | func (p *Path2) MethodeMiddlewares() map[string][]gin.HandlerFunc { 253 | return map[string][]gin.HandlerFunc{ 254 | "GetInfoByID": { 255 | func(ctx *gin.Context) { 256 | log.Println("Path2 GetInfoByID middleware1") 257 | }, 258 | func(ctx *gin.Context) { 259 | log.Println("Path2 GetInfoByID middleware2") 260 | }, 261 | }, 262 | } 263 | } 264 | 265 | func (p *Path2) Middlewares() []gin.HandlerFunc { 266 | return []gin.HandlerFunc{ 267 | func(ctx *gin.Context) { 268 | log.Println("Path2 Middlewares 1") 269 | }, 270 | func(ctx *gin.Context) { 271 | log.Println("Path2 Middlewares 2") 272 | }, 273 | } 274 | } 275 | 276 | func (p *Path1) Middlewares() []gin.HandlerFunc { 277 | return []gin.HandlerFunc{ 278 | func(ctx *gin.Context) { 279 | log.Println("Path1 Middlewares 1") 280 | }, 281 | func(ctx *gin.Context) { 282 | log.Println("Path1 Middlewares 2") 283 | }, 284 | } 285 | } 286 | 287 | var _ IMiddleware = (*Path1)(nil) 288 | var _ IMiddleware = (*Path2)(nil) 289 | var _ MethodeMiddleware = (*Path2)(nil) 290 | 291 | func (p *Path1) GetInfo() gin.HandlerFunc { 292 | return func(ctx *gin.Context) { 293 | log.Println("Path1 GetInfo") 294 | } 295 | } 296 | 297 | func (p *Path1) GetInfoByID(_ context.Context, req *struct { 298 | Id uint `uri:"id"` 299 | }) (*struct { 300 | Id uint `json:"id"` 301 | }, error) { 302 | log.Println("Path1 GetInfoByID") 303 | return (*struct { 304 | Id uint `json:"id"` 305 | })(&struct{ Id uint }{Id: req.Id}), nil 306 | } 307 | 308 | func (p *Path2) GetInfo() gin.HandlerFunc { 309 | return func(ctx *gin.Context) { 310 | log.Println("Path2 GetInfo") 311 | } 312 | } 313 | 314 | func (p *Path2) GetInfoByID(_ context.Context, req *struct { 315 | Id uint `uri:"id"` 316 | }) (*struct { 317 | Id uint `json:"id"` 318 | }, error) { 319 | log.Println("Path2 GetInfoByID") 320 | return (*struct { 321 | Id uint `json:"id"` 322 | })(&struct{ Id uint }{Id: req.Id}), nil 323 | } 324 | 325 | func TestRouteGroup(t *testing.T) { 326 | New(gin.Default(), WithControllers(&Path1{Path2: &Path2{}})) 327 | } 328 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gin plus 2 | 3 | > 用于对gin框架增强, 实现根据结构体+结构体方法名实现路由注册、文档生成等功能 4 | 5 | [使用示例](https://github.com/aide-cloud/gin-plus-app) 6 | 7 | ## 安装 8 | 9 | ```shell 10 | go get -u github.com/aide-cloud/gin-plus 11 | ``` 12 | 13 | ## 使用 14 | 15 | ```go 16 | package main 17 | 18 | import ( 19 | "log" 20 | 21 | ginplush "github.com/aide-cloud/gin-plus" 22 | 23 | "github.com/gin-gonic/gin" 24 | ) 25 | 26 | type People struct { 27 | } 28 | 29 | func (p *People) GetInfo() gin.HandlerFunc { 30 | return func(ctx *gin.Context) { 31 | ctx.String(200, "GetInfo") 32 | } 33 | } 34 | 35 | func (p *People) Middlewares() []gin.HandlerFunc { 36 | return []gin.HandlerFunc{ 37 | func(context *gin.Context) { 38 | log.Println("middleware1") 39 | }, 40 | func(context *gin.Context) { 41 | log.Println("middleware2") 42 | }, 43 | } 44 | } 45 | 46 | func main() { 47 | r := gin.Default() 48 | ginInstance := ginplush.New(r, ginplush.WithControllers(&People{})) 49 | ginInstance.Run(":8080") 50 | } 51 | 52 | ``` 53 | 54 | ## 自定义 55 | 56 | ```go 57 | package main 58 | 59 | import ( 60 | "log" 61 | 62 | ginplush "github.com/aide-cloud/gin-plus" 63 | 64 | "github.com/gin-gonic/gin" 65 | ) 66 | 67 | // People controller, 会根据该controller的方法名注册路由 68 | type People struct { 69 | // 内联controller, 方法注册到父级 70 | *Img 71 | } 72 | 73 | // Img controller, 会根据该controller的方法名注册路由 74 | type Img struct { 75 | File *File 76 | } 77 | 78 | // File controller, 会根据该controller的方法名注册路由 79 | type File struct { 80 | } 81 | 82 | // GetImgInfo Get /imgInfo 83 | func (l *Img) GetImgInfo() gin.HandlerFunc { 84 | return func(ctx *gin.Context) { 85 | ctx.String(200, "GetInfo") 86 | } 87 | } 88 | 89 | // GetFiles Get /files 90 | func (l *File) GetFiles() gin.HandlerFunc { 91 | return func(ctx *gin.Context) { 92 | ctx.String(200, "GetFiles") 93 | } 94 | } 95 | 96 | // GetInfo Get /info 97 | func (p *People) GetInfo() gin.HandlerFunc { 98 | return func(ctx *gin.Context) { 99 | ctx.String(200, "GetInfo") 100 | } 101 | } 102 | 103 | // List Get /list 104 | func (p *People) List() gin.HandlerFunc { 105 | return func(ctx *gin.Context) { 106 | ctx.String(200, "List") 107 | } 108 | } 109 | 110 | // Middlewares 模块下公共中间件 111 | func (p *People) Middlewares() []gin.HandlerFunc { 112 | return []gin.HandlerFunc{ 113 | func(context *gin.Context) { 114 | log.Println("middleware1") 115 | }, 116 | func(context *gin.Context) { 117 | log.Println("middleware2") 118 | }, 119 | } 120 | } 121 | 122 | // BasePath People模块下公共路由前缀 123 | func (p *People) BasePath() string { 124 | return "/people/v1" 125 | } 126 | 127 | // MethoderMiddlewares People各个方法的中间件 128 | func (p *People) MethoderMiddlewares() map[string][]gin.HandlerFunc { 129 | return map[string][]gin.HandlerFunc{ 130 | "GetInfo": { 131 | func(ctx *gin.Context) { 132 | log.Println("GetInfo middleware1") 133 | }, 134 | }, 135 | } 136 | } 137 | 138 | type Slice []string 139 | 140 | func (l *Slice) Middlewares() []gin.HandlerFunc { 141 | return nil 142 | } 143 | 144 | func (l *Slice) GetInfo() gin.HandlerFunc { 145 | return func(ctx *gin.Context) { 146 | ctx.String(200, "GetInfo") 147 | } 148 | } 149 | 150 | func main() { 151 | r := gin.Default() 152 | opts := []ginplush.Option{ 153 | // 注册全局路由 154 | ginplush.WithMiddlewares(func(ctx *gin.Context) { 155 | log.Println("main middleware") 156 | }), 157 | // 设置基础的路由前缀, 会在每个路由前面加上该前缀 158 | ginplush.WithBasePath("aide-cloud"), 159 | // 设置路由前缀, 只识别这个列表的函数, 其他函数统一以defaultHttpMethod为http方法注册 160 | ginplush.WithHttpMethodPrefixes(ginplush.Get, ginplush.Post), 161 | // 无前缀的函数默认使用的http方法 162 | ginplush.WithDefaultHttpMethod(ginplush.Post), 163 | // 路由controller, 会根据该controller的方法名注册路由 164 | ginplush.WithControllers(&People{ 165 | Img: &Img{ 166 | File: &File{}, 167 | }, 168 | }, &Slice{}), 169 | } 170 | ginInstance := ginplush.New(r, opts...) 171 | ginInstance.Run(":8080") 172 | } 173 | ``` 174 | 175 | * 运行截图 176 | 177 | ![img.png](doc/img.png) 178 | 179 | ## v0.0.6+之后 180 | 181 | * 增加新的方法格式注入 182 | 183 | 除了上述的直接实现`gin.HandlerFunc`类型的方法可以注入之外, 从v0.0.6版本开始, 新增了`func(ctx context.Context, req *ApiReq) (*ApiResp, error)`格式的方法也能注入, 这里的req和resp可以是结构体, 也可以是结构体指针类型, 入参第一参数必须为context.Context, 出参只允许两个返回值, 且第二返回值必须为error类型 184 | 185 | 如下所示: 186 | 187 | 我们定义了一个Api的注入对象, 并增加了CRUD的四个方法, 这四个方法都为上面提到的函数格式, 完成这些后, 通过`WithControllers`方法把该对象注入到ginplus中, 从而实现`gin`路由的注册和`api`文档生成, 启动时候, 会在当前目录下生成一个`openapi.yaml`文件, 该文件就是你的注入对象所生成api文档 188 | 189 | 当然, 我们也提供了关闭生成api文档功能的开关, `ApiConfig`的`GenApiEnable`属性为`false`时候, 会关闭文档生成和文档预览功能, 通过`WithApiConfig`完成控制 190 | 191 | ```go 192 | package main 193 | 194 | import ( 195 | "context" 196 | "log" 197 | 198 | ginplush "github.com/aide-cloud/gin-plus" 199 | 200 | "github.com/gin-gonic/gin" 201 | ) 202 | 203 | type ( 204 | Api struct { 205 | } 206 | 207 | ApiDetailReq struct { 208 | Id uint `uri:"id"` 209 | } 210 | 211 | ApiDetailResp struct { 212 | Id uint `json:"id"` 213 | Name string `json:"name"` 214 | Remark string `json:"remark"` 215 | } 216 | 217 | ApiListReq struct { 218 | Current int `form:"current"` 219 | Size int `form:"size"` 220 | Keryworld string `form:"keyworld"` 221 | } 222 | ApiListResp struct { 223 | Total int64 `json:"total"` 224 | Current int `json:"current"` 225 | Size int `json:"size"` 226 | List []*ApiInfoItem `json:"list"` 227 | } 228 | 229 | ApiInfoItem struct { 230 | Name string `json:"name"` 231 | Id uint `json:"id"` 232 | Remark string `json:"remark"` 233 | } 234 | 235 | ApiUpdateReq struct { 236 | Id uint `uri:"id"` 237 | Name string `json:"name"` 238 | Remark string `json:"remark"` 239 | } 240 | ApiUpdateResp struct { 241 | Id uint `json:"id"` 242 | } 243 | 244 | DelApiReq struct { 245 | Id uint `uri:"id"` 246 | } 247 | 248 | DelApiResp struct { 249 | Id uint `json:"id"` 250 | } 251 | ) 252 | 253 | func (l *Api) GetDetail(ctx context.Context, req *ApiDetailReq) (*ApiDetailResp, error) { 254 | log.Println("Api.GetDetail") 255 | return &ApiDetailResp{ 256 | Id: req.Id, 257 | Name: "demo", 258 | Remark: "hello world", 259 | }, nil 260 | } 261 | 262 | func (l *Api) GetList(ctx context.Context, req *ApiListReq) (*ApiListResp, error) { 263 | log.Println("Api.GetList", req) 264 | return &ApiListResp{ 265 | Total: 100, 266 | Current: req.Current, 267 | Size: req.Size, 268 | List: []*ApiInfoItem{ 269 | { 270 | Id: 10, 271 | Name: "demo", 272 | Remark: "hello world", 273 | }, 274 | }, 275 | }, nil 276 | } 277 | 278 | func (l *Api) UpdateInfo(ctx context.Context, req *ApiUpdateReq) (*ApiUpdateResp, error) { 279 | log.Println("Api.UpdateInfo") 280 | return &ApiUpdateResp{Id: req.Id}, nil 281 | } 282 | 283 | func (l *Api) DeleteInfo(ctx context.Context, req *DelApiReq) (*DelApiResp, error) { 284 | log.Println("Api.DeleteInfo") 285 | return &DelApiResp{Id: req.Id}, nil 286 | } 287 | 288 | func main() { 289 | r := gin.Default() 290 | opts := []ginplush.Option{ 291 | // 路由controller, 会根据该controller的方法名注册路由 292 | ginplush.WithControllers(&Api{}), 293 | } 294 | ginInstance := ginplush.New(r, opts...) 295 | ginInstance.Run(":8080") 296 | } 297 | ``` 298 | 299 | ## graphql 300 | 301 | ```go 302 | package ginplus 303 | 304 | import ( 305 | "embed" 306 | "testing" 307 | 308 | "github.com/gin-gonic/gin" 309 | ) 310 | 311 | // Content holds all the SDL file content. 312 | // 313 | //go:embed sdl 314 | var content embed.FS 315 | 316 | type Root struct{} 317 | 318 | func (r *Root) Ping() string { 319 | return "pong" 320 | } 321 | 322 | func TestGraphql(t *testing.T) { 323 | instance := New(gin.Default(), WithGraphqlConfig(GraphqlConfig{ 324 | Enable: true, 325 | HandlePath: "/graphql", 326 | ViewPath: "/graphql", 327 | Root: &Root{}, 328 | Content: content, 329 | })) 330 | 331 | instance.Run(":8080") 332 | } 333 | ``` 334 | 335 | ![img.png](./doc/graphql-run.png) 336 | 337 | ![img.png](doc/graphql-page.png) 338 | 339 | -------------------------------------------------------------------------------- /util.go: -------------------------------------------------------------------------------- 1 | package ginplus 2 | 3 | import ( 4 | "fmt" 5 | "path" 6 | "reflect" 7 | "strings" 8 | 9 | "github.com/gin-gonic/gin" 10 | ) 11 | 12 | const ( 13 | GinHandleFunc = "gin.HandlerFunc" 14 | ) 15 | 16 | // 判断该方法返回值是否为 gin.HandlerFunc类型 17 | func isHandlerFunc(t reflect.Type) bool { 18 | if t.Kind() != reflect.Func { 19 | return false 20 | } 21 | if t.NumOut() != 1 { 22 | return false 23 | } 24 | return t.Out(0).String() == GinHandleFunc 25 | } 26 | 27 | // isCallBack 判断是否为CallBack类型 28 | func isCallBack(t reflect.Type) (reflect.Type, reflect.Type, bool) { 29 | // 通过反射获取方法的返回值类型 30 | if t.Kind() != reflect.Func { 31 | return nil, nil, false 32 | } 33 | 34 | if t.NumIn() != 3 || t.NumOut() != 2 { 35 | return nil, nil, false 36 | } 37 | 38 | if t.Out(1).String() != "error" { 39 | return nil, nil, false 40 | } 41 | 42 | if t.In(1).String() != "context.Context" { 43 | return nil, nil, false 44 | } 45 | 46 | // new一个out 0的实例和in 2的实例 47 | req := t.In(2) 48 | resp := t.Out(0) 49 | 50 | return req, resp, true 51 | } 52 | 53 | func isNil(value interface{}) bool { 54 | // 使用反射获取值的类型和值 55 | val := reflect.ValueOf(value) 56 | 57 | // 检查值的类型 58 | switch val.Kind() { 59 | case reflect.Ptr, reflect.Interface, reflect.Slice, reflect.Map, reflect.Chan, reflect.Func: 60 | // 对于指针、接口、切片、映射、通道和函数类型,使用IsNil()方法来检查是否为nil 61 | return val.IsNil() 62 | default: 63 | // 对于其他类型,无法直接检查是否为nil 64 | return false 65 | } 66 | } 67 | 68 | func (l *GinEngine) GenRoute(parentGroup *gin.RouterGroup, controller any) *GinEngine { 69 | if isNil(controller) { 70 | logger.Warn("controller is nil") 71 | return l 72 | } 73 | l.genRoute(parentGroup, controller, false) 74 | return l 75 | } 76 | 77 | func (l *GinEngine) genRoute(parentGroup *gin.RouterGroup, controller any, skipAnonymous bool) { 78 | if isNil(controller) { 79 | return 80 | } 81 | t := reflect.TypeOf(controller) 82 | 83 | tmp := t 84 | for tmp.Kind() == reflect.Ptr { 85 | if tmp == nil { 86 | return 87 | } 88 | tmp = tmp.Elem() 89 | } 90 | 91 | if !isPublic(tmp.Name()) { 92 | return 93 | } 94 | 95 | var middlewares []gin.HandlerFunc 96 | mid, isMid := isMiddleware(controller) 97 | if isMid { 98 | //Middlewares方法返回的是gin.HandlerFunc类型的切片, 中间件 99 | middlewares = mid.Middlewares() 100 | } 101 | 102 | basePath := l.routeNamingRuleFunc(tmp.Name()) 103 | ctrl, isCtrl := isController(controller) 104 | if isCtrl { 105 | basePath = ctrl.BasePath() 106 | } 107 | parentRouteGroup := parentGroup 108 | if parentRouteGroup == nil { 109 | parentRouteGroup = l.Group("/") 110 | } 111 | routeGroup := parentRouteGroup.Group(path.Join(basePath), middlewares...) 112 | 113 | methodMiddlewaresMap := make(map[string][]gin.HandlerFunc) 114 | methodMid, privateMidOk := isMethodMiddleware(controller) 115 | if privateMidOk { 116 | methodMiddlewaresMap = methodMid.MethodeMiddlewares() 117 | } 118 | 119 | if !skipAnonymous { 120 | for i := 0; i < t.NumMethod(); i++ { 121 | methodName := t.Method(i).Name 122 | if !isPublic(methodName) { 123 | continue 124 | } 125 | 126 | route := l.parseRoute(methodName) 127 | if route == nil { 128 | continue 129 | } 130 | 131 | privateMid := methodMiddlewaresMap[methodName] 132 | 133 | // 接口私有中间件 134 | route.Handles = append(route.Handles, privateMid...) 135 | 136 | if isHandlerFunc(t.Method(i).Type) { 137 | // 具体的action 138 | handleFunc := t.Method(i).Func.Call([]reflect.Value{reflect.ValueOf(controller)})[0].Interface().(gin.HandlerFunc) 139 | route.Handles = append(route.Handles, handleFunc) 140 | routeGroup.Handle(strings.ToUpper(route.HttpMethod), route.Path, route.Handles...) 141 | continue 142 | } 143 | 144 | // 判断是否为CallBack类型 145 | req, resp, isCb := isCallBack(t.Method(i).Type) 146 | if isCb { 147 | // 生成路由openAPI数据 148 | l.genOpenAPI(routeGroup, req, resp, route, methodName) 149 | // 注册路由回调函数 150 | handleFunc := l.defaultHandler(controller, t.Method(i), req) 151 | l.registerCallHandler(route, routeGroup, handleFunc) 152 | continue 153 | } 154 | } 155 | } 156 | 157 | l.genStructRoute(routeGroup, controller) 158 | } 159 | 160 | // 生成openAPI数据 161 | func (l *GinEngine) genOpenAPI(group *gin.RouterGroup, req, resp reflect.Type, route *Route, methodName string) { 162 | reqName := req.Name() 163 | respName := resp.Name() 164 | reqTagInfo := getTag(req) 165 | apiRoute := ApiRoute{ 166 | Path: route.Path, 167 | HttpMethod: strings.ToLower(route.HttpMethod), 168 | MethodName: methodName, 169 | ReqParams: Field{ 170 | Name: reqName, 171 | Info: reqTagInfo, 172 | }, 173 | RespParams: Field{ 174 | Name: respName, 175 | Info: getTag(resp), 176 | }, 177 | } 178 | 179 | // 处理Uri参数 180 | for _, tagInfo := range reqTagInfo { 181 | uriKey := tagInfo.Tags.UriKey 182 | skip := tagInfo.Tags.Skip 183 | if uriKey != "" && uriKey != "-" && skip != "true" { 184 | route.Path = path.Join(route.Path, fmt.Sprintf(":%s", uriKey)) 185 | } 186 | } 187 | 188 | apiPath := path.Join(group.BasePath(), route.Path) 189 | apiRoute.Path = apiPath 190 | 191 | if _, ok := l.apiRoutes[apiPath]; !ok { 192 | l.apiRoutes[apiPath] = make([]ApiRoute, 0, 1) 193 | } 194 | l.apiRoutes[apiPath] = append(l.apiRoutes[apiPath], apiRoute) 195 | } 196 | 197 | // registerCallHandler 注册回调函数 198 | func (l *GinEngine) registerCallHandler(route *Route, routeGroup *gin.RouterGroup, handleFunc gin.HandlerFunc) { 199 | // 具体的action 200 | route.Handles = append(route.Handles, handleFunc) 201 | routeGroup.Handle(strings.ToUpper(route.HttpMethod), route.Path, route.Handles...) 202 | } 203 | 204 | // genStructRoute 递归注册结构体路由 205 | func (l *GinEngine) genStructRoute(parentGroup *gin.RouterGroup, controller any) { 206 | if isNil(controller) { 207 | return 208 | } 209 | tmp := reflect.TypeOf(controller) 210 | for tmp.Kind() == reflect.Ptr { 211 | if tmp == nil { 212 | return 213 | } 214 | tmp = tmp.Elem() 215 | } 216 | if isStruct(tmp) { 217 | // 递归获取内部的controller 218 | for i := 0; i < tmp.NumField(); i++ { 219 | field := tmp.Field(i) 220 | for field.Type.Kind() == reflect.Ptr { 221 | field.Type = field.Type.Elem() 222 | } 223 | if !isStruct(field.Type) { 224 | continue 225 | } 226 | 227 | if !isPublic(field.Name) { 228 | continue 229 | } 230 | 231 | newController := reflect.ValueOf(controller).Elem().Field(i).Interface() 232 | // 判断field值是否为nil 233 | if isNil(newController) { 234 | continue 235 | } 236 | 237 | l.genRoute(parentGroup, newController, field.Anonymous) 238 | } 239 | } 240 | } 241 | 242 | // isPublic 判断是否为公共方法 243 | func isPublic(name string) bool { 244 | if len(name) == 0 { 245 | return false 246 | } 247 | 248 | first := name[0] 249 | if first < 'A' || first > 'Z' { 250 | return false 251 | } 252 | 253 | return true 254 | } 255 | 256 | // parseRoute 从方法名称中解析出路由和请求方式 257 | func (l *GinEngine) parseRoute(methodName string) *Route { 258 | method := "" 259 | routePath := "" 260 | 261 | for prefix, httpMethodKey := range l.httpMethodPrefixes { 262 | if strings.HasPrefix(methodName, prefix) { 263 | method = strings.ToLower(httpMethodKey.key) 264 | routePath = strings.TrimPrefix(methodName, prefix) 265 | if routePath == "" { 266 | routePath = strings.ToLower(methodName) 267 | } 268 | break 269 | } 270 | } 271 | 272 | if method == "" || routePath == "" { 273 | return nil 274 | } 275 | 276 | return &Route{ 277 | Path: path.Join("/", l.routeNamingRuleFunc(routePath)), 278 | HttpMethod: method, 279 | } 280 | } 281 | 282 | // routeToCamel 将路由转换为驼峰命名 283 | func routeToCamel(route string) string { 284 | if route == "" { 285 | return "" 286 | } 287 | 288 | // 首字母小写 289 | if route[0] >= 'A' && route[0] <= 'Z' { 290 | route = string(route[0]+32) + route[1:] 291 | } 292 | 293 | return route 294 | } 295 | 296 | // isMiddleware 判断是否为Controller类型 297 | func isMiddleware(c any) (IMiddleware, bool) { 298 | mid, ok := c.(IMiddleware) 299 | return mid, ok 300 | } 301 | 302 | // isController 判断是否为Controller类型 303 | func isController(c any) (Controller, bool) { 304 | ctrl, ok := c.(Controller) 305 | return ctrl, ok 306 | } 307 | 308 | // isMethodMiddleware 判断是否为MethodMiddleware类型 309 | func isMethodMiddleware(c any) (MethodeMiddleware, bool) { 310 | mid, ok := c.(MethodeMiddleware) 311 | return mid, ok 312 | } 313 | 314 | // isStruct 判断是否为struct类型 315 | func isStruct(t reflect.Type) bool { 316 | tmp := t 317 | for tmp.Kind() == reflect.Ptr { 318 | if tmp == nil { 319 | return false 320 | } 321 | tmp = tmp.Elem() 322 | } 323 | return tmp.Kind() == reflect.Struct 324 | } 325 | -------------------------------------------------------------------------------- /middleware.go: -------------------------------------------------------------------------------- 1 | package ginplus 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "sync" 7 | "time" 8 | 9 | "github.com/gin-gonic/gin" 10 | "go.opentelemetry.io/otel" 11 | "go.opentelemetry.io/otel/attribute" 12 | semconv "go.opentelemetry.io/otel/semconv/v1.7.0" 13 | oteltrace "go.opentelemetry.io/otel/trace" 14 | "go.uber.org/zap" 15 | ) 16 | 17 | type Middleware struct { 18 | resp IResponse 19 | tracing oteltrace.TracerProvider 20 | serverName string 21 | id string 22 | env string 23 | } 24 | 25 | type MiddlewareOption func(*Middleware) 26 | 27 | // NewMiddleware 创建中间件 28 | func NewMiddleware(opts ...MiddlewareOption) *Middleware { 29 | id, _ := os.Hostname() 30 | mid := &Middleware{ 31 | resp: NewResponse(), 32 | serverName: "gin-plus", 33 | id: id, 34 | env: "default", 35 | } 36 | 37 | for _, opt := range opts { 38 | opt(mid) 39 | } 40 | 41 | return mid 42 | } 43 | 44 | // WithResponse 设置响应 45 | func WithResponse(resp IResponse) MiddlewareOption { 46 | return func(m *Middleware) { 47 | m.resp = resp 48 | } 49 | } 50 | 51 | // WithServerName 设置服务名称 52 | func WithServerName(serverName string) MiddlewareOption { 53 | return func(m *Middleware) { 54 | m.serverName = serverName 55 | } 56 | } 57 | 58 | // WithID 设置服务ID 59 | func WithID(id string) MiddlewareOption { 60 | return func(m *Middleware) { 61 | m.id = id 62 | } 63 | } 64 | 65 | // WithEnv 设置环境 66 | func WithEnv(env string) MiddlewareOption { 67 | return func(m *Middleware) { 68 | m.env = env 69 | } 70 | } 71 | 72 | // Cors 直接放行所有跨域请求并放行所有 OPTIONS 方法 73 | func (l *Middleware) Cors(headers ...map[string]string) gin.HandlerFunc { 74 | return func(c *gin.Context) { 75 | ctx := c.Request.Context() 76 | tracerSpan, _ := c.Get("span") 77 | span, ok := tracerSpan.(oteltrace.Span) 78 | if ok { 79 | ctx, span = span.TracerProvider().Tracer("IMiddleware.Cors").Start(ctx, "IMiddleware.Cors") 80 | defer span.End() 81 | } 82 | method := c.Request.Method 83 | origin := c.Request.Header.Get("Origin") 84 | if origin == "null" || origin == "" { 85 | origin = "*" 86 | } 87 | c.Writer.Header().Set("Access-Control-Allow-Origin", origin) 88 | c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type,AccessToken,X-CSRF-Token, Authorization, Token,X-Token,X-User-Id") 89 | c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS,DELETE,PUT") 90 | c.Writer.Header().Set("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type, New-Token, New-Expires-At") 91 | c.Writer.Header().Set("Access-Control-Allow-Credentials", "true") 92 | 93 | for _, header := range headers { 94 | for k, v := range header { 95 | c.Writer.Header().Set(k, v) 96 | } 97 | } 98 | 99 | // 放行所有OPTIONS方法 100 | if method == "OPTIONS" { 101 | c.Writer.Header().Set("Access-Control-Max-Age", "3600") 102 | l.resp.Response(c, nil, nil) 103 | return 104 | } 105 | // 处理请求 106 | c.Next() 107 | } 108 | } 109 | 110 | type InterceptorConfig struct { 111 | // IP 如果IP不为空, 则不允许该IP访问, 否则所有IP都不允许访问 112 | IPList []string 113 | Method string 114 | Path string 115 | Msg any 116 | } 117 | 118 | // Interceptor 拦截器, 拦截指定API, 用于控制API当下不允许访问 119 | func (l *Middleware) Interceptor(configs ...InterceptorConfig) gin.HandlerFunc { 120 | return func(c *gin.Context) { 121 | ctx := c.Request.Context() 122 | tracerSpan, _ := c.Get("span") 123 | span, ok := tracerSpan.(oteltrace.Span) 124 | if ok { 125 | ctx, span = span.TracerProvider().Tracer("IMiddleware.Interceptor").Start(ctx, "IMiddleware.Interceptor") 126 | defer span.End() 127 | } 128 | for _, config := range configs { 129 | if config.Method == c.Request.Method && config.Path == c.Request.URL.Path { 130 | if len(config.IPList) != 0 { 131 | clientIP := c.ClientIP() 132 | for _, ip := range config.IPList { 133 | if ip == clientIP { 134 | l.resp.Response(c, config.Msg, nil) 135 | return 136 | } 137 | } 138 | } else { 139 | l.resp.Response(c, config.Msg, nil) 140 | return 141 | } 142 | } 143 | } 144 | c.Next() 145 | } 146 | } 147 | 148 | type TokenBucket struct { 149 | capacity int64 // 桶的容量 150 | rate float64 // 令牌放入速率 151 | tokens float64 // 当前令牌数量 152 | lastToken time.Time // 上一次放令牌的时间 153 | mtx sync.Mutex // 互斥锁 154 | } 155 | 156 | // Allow 判断是否允许请求 157 | func (tb *TokenBucket) Allow() bool { 158 | tb.mtx.Lock() 159 | defer tb.mtx.Unlock() 160 | now := time.Now() 161 | // 计算需要放的令牌数量 162 | tb.tokens = tb.tokens + tb.rate*now.Sub(tb.lastToken).Seconds() 163 | if tb.tokens > float64(tb.capacity) { 164 | tb.tokens = float64(tb.capacity) 165 | } 166 | // 判断是否允许请求 167 | if tb.tokens >= 1 { 168 | tb.tokens-- 169 | tb.lastToken = now 170 | return true 171 | } 172 | 173 | return false 174 | } 175 | 176 | // IpLimit IP限制, 用于控制API的访问频率 177 | func (l *Middleware) IpLimit(capacity int64, rate float64) gin.HandlerFunc { 178 | syncTokenMap := sync.Map{} 179 | return func(c *gin.Context) { 180 | ctx := c.Request.Context() 181 | tracerSpan, _ := c.Get("span") 182 | span, ok := tracerSpan.(oteltrace.Span) 183 | if ok { 184 | ctx, span = span.TracerProvider().Tracer("IMiddleware.IpLimit").Start(ctx, "IMiddleware.IpLimit") 185 | defer span.End() 186 | } 187 | clientIP := c.ClientIP() 188 | if _, ok := syncTokenMap.Load(clientIP); !ok { 189 | clientTb := TokenBucket{ 190 | capacity: capacity, 191 | rate: rate, 192 | tokens: float64(capacity) * rate, 193 | lastToken: time.Now(), 194 | } 195 | syncTokenMap.Store(clientIP, &clientTb) 196 | } 197 | 198 | tb, ok := syncTokenMap.Load(clientIP) 199 | if !ok { 200 | logger.Error("ip limit error, not found token bucket") 201 | l.resp.Response(c, nil, nil) 202 | return 203 | } 204 | 205 | if !tb.(*TokenBucket).Allow() { 206 | l.resp.Response(c, nil, nil) 207 | return 208 | } 209 | c.Next() 210 | } 211 | } 212 | 213 | // Tracing 链路追踪 214 | func (l *Middleware) Tracing(url string, opts ...TracingOption) gin.HandlerFunc { 215 | cfg := &tracingConfig{ 216 | KeyValue: defaultKeyValueFunc, 217 | URL: url, 218 | } 219 | for _, opt := range opts { 220 | opt(cfg) 221 | } 222 | 223 | l.tracing = tracerProvider(cfg.URL, l.serverName, l.env, l.id) 224 | 225 | return func(c *gin.Context) { 226 | spanOpts := []oteltrace.SpanStartOption{ 227 | oteltrace.WithAttributes(semconv.HTTPServerAttributesFromHTTPRequest(l.serverName, c.FullPath(), c.Request)...), 228 | oteltrace.WithSpanKind(oteltrace.SpanKindServer), 229 | } 230 | spanName := c.FullPath() 231 | if spanName == "" { 232 | spanName = fmt.Sprintf("HTTP %s route not found", c.Request.Method) 233 | } 234 | 235 | ctx, span := otel.Tracer("IMiddleware.Tracing").Start(c, spanName, spanOpts...) 236 | defer span.End() 237 | c.Set("span", span) 238 | 239 | c.Request = c.Request.WithContext(ctx) 240 | sc := span.SpanContext() 241 | if sc.HasTraceID() { 242 | c.Header("trace_id", sc.TraceID().String()) 243 | } else { 244 | c.Header("trace_id", "not-trace") 245 | } 246 | 247 | c.Request.Header.Set("trace_id", span.SpanContext().TraceID().String()) 248 | c.Request.Header.Set("span_id", span.SpanContext().SpanID().String()) 249 | c.Next() 250 | kvFunc := cfg.KeyValue 251 | if kvFunc == nil { 252 | kvFunc = func(c *gin.Context) []attribute.KeyValue { 253 | return nil 254 | } 255 | } 256 | kvs := kvFunc(c) 257 | span.SetAttributes(kvs...) 258 | 259 | // 上报错误 260 | if len(c.Errors) > 0 { 261 | span.SetAttributes(attribute.String("gin.errors", c.Errors.String())) 262 | } 263 | 264 | // 上报状态码 265 | status := c.Writer.Status() 266 | attrs := semconv.HTTPAttributesFromHTTPStatusCode(status) 267 | spanStatus, spanMessage := semconv.SpanStatusFromHTTPStatusCode(status) 268 | span.SetAttributes(attrs...) 269 | span.SetStatus(spanStatus, spanMessage) 270 | } 271 | } 272 | 273 | // Logger 日志 274 | func (l *Middleware) Logger(timeLayout ...string) gin.HandlerFunc { 275 | layout := time.RFC3339 276 | if len(timeLayout) > 0 { 277 | layout = timeLayout[0] 278 | } 279 | return func(c *gin.Context) { 280 | startTime := time.Now() 281 | c.Next() 282 | endTime := time.Now() 283 | latencyTime := endTime.Sub(startTime) 284 | 285 | reqMethod := c.Request.Method 286 | reqUri := c.Request.RequestURI 287 | statusCode := c.Writer.Status() 288 | clientIP := c.ClientIP() 289 | kv := []zap.Field{ 290 | zap.String("timestamp", time.Now().Format(layout)), 291 | zap.String("start_time", startTime.Format(layout)), 292 | zap.String("end_time", endTime.Format(layout)), 293 | zap.String("client_ip", clientIP), 294 | zap.Int("status_code", statusCode), 295 | zap.String("req_method", reqMethod), 296 | zap.String("req_uri", reqUri), 297 | zap.String("latency_time", latencyTime.String()), 298 | } 299 | 300 | ctx := c.Request.Context() 301 | 302 | if l.tracing != nil { 303 | tracerSpan, _ := c.Get("span") 304 | span, ok := tracerSpan.(oteltrace.Span) 305 | if ok { 306 | _, span := span.TracerProvider().Tracer("IMiddleware.Logger").Start(ctx, "IMiddleware.Logger") 307 | defer span.End() 308 | span.SetAttributes(attribute.String("latency_time", latencyTime.String())) 309 | spanCtx := span.SpanContext() 310 | traceID := "" 311 | if spanCtx.HasTraceID() { 312 | traceID = spanCtx.TraceID().String() 313 | } 314 | 315 | spanID := "" 316 | if spanCtx.HasSpanID() { 317 | spanID = spanCtx.SpanID().String() 318 | } 319 | kv = append(kv, zap.String("trace_id", traceID), zap.String("span_id", spanID)) 320 | } 321 | } 322 | 323 | logger.Info(l.serverName, kv...) 324 | } 325 | } 326 | -------------------------------------------------------------------------------- /ref_test.go: -------------------------------------------------------------------------------- 1 | package ginplus 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | "time" 7 | 8 | "gorm.io/gorm" 9 | ) 10 | 11 | func Test_getTag(t *testing.T) { 12 | type My struct { 13 | Name string `json:"name"` 14 | Id uint `uri:"id"` 15 | Keyword string `form:"keyword"` 16 | } 17 | 18 | fieldList := getTag(reflect.TypeOf(&My{})) 19 | 20 | t.Log(fieldList) 21 | } 22 | 23 | func Test_getTag2(t *testing.T) { 24 | var b bool 25 | t.Logf("%T", b) 26 | } 27 | 28 | type PromStrategy struct { 29 | ID int32 `gorm:"column:id;type:int unsigned;primaryKey;autoIncrement:true" json:"id"` 30 | GroupID int32 `gorm:"column:group_id;type:int unsigned;not null;comment:所属规则组ID" json:"group_id"` // 所属规则组ID 31 | Alert string `gorm:"column:alert;type:varchar(64);not null;comment:规则名称" json:"alert"` // 规则名称 32 | Expr string `gorm:"column:expr;type:text;not null;comment:prom ql" json:"expr"` // prom ql 33 | For string `gorm:"column:for;type:varchar(64);not null;default:10s;comment:持续时间" json:"for"` // 持续时间 34 | Labels string `gorm:"column:labels;type:json;not null;comment:标签" json:"labels"` // 标签 35 | Annotations string `gorm:"column:annotations;type:json;not null;comment:告警文案" json:"annotations"` // 告警文案 36 | AlertLevelID int32 `gorm:"column:alert_level_id;type:int;not null;index:idx__alert_level_id,priority:1;comment:告警等级dict ID" json:"alert_level_id"` // 告警等级dict ID 37 | Status int32 `gorm:"column:status;type:tinyint;not null;default:1;comment:启用状态: 1启用;2禁用" json:"status"` // 启用状态: 1启用;2禁用 38 | CreatedAt time.Time `gorm:"column:created_at;type:timestamp;not null;default:CURRENT_TIMESTAMP;comment:创建时间" json:"created_at"` // 创建时间 39 | UpdatedAt time.Time `gorm:"column:updated_at;type:timestamp;not null;default:CURRENT_TIMESTAMP;comment:更新时间" json:"updated_at"` // 更新时间 40 | DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;type:timestamp;comment:删除时间" json:"deleted_at"` // 删除时间 41 | AlarmPages []*PromAlarmPage `gorm:"References:ID;foreignKey:ID;joinForeignKey:PromStrategyID;joinReferences:AlarmPageID;many2many:prom_strategy_alarm_pages" json:"alarm_pages"` 42 | Categories []*PromDict `gorm:"References:ID;foreignKey:ID;joinForeignKey:PromStrategyID;joinReferences:DictID;many2many:prom_strategy_categories" json:"categories"` 43 | AlertLevel *PromDict `gorm:"foreignKey:AlertLevelID" json:"alert_level"` 44 | GroupInfo *PromGroup `gorm:"foreignKey:GroupID" json:"group_info"` 45 | } 46 | 47 | type PromGroup struct { 48 | ID int32 `gorm:"column:id;type:int unsigned;primaryKey;autoIncrement:true" json:"id"` 49 | Name string `gorm:"column:name;type:varchar(64);not null;comment:规则组名称" json:"name"` // 规则组名称 50 | StrategyCount int64 `gorm:"column:strategy_count;type:bigint;not null;comment:规则数量" json:"strategy_count"` // 规则数量 51 | Status int32 `gorm:"column:status;type:tinyint;not null;default:1;comment:启用状态1:启用;2禁用" json:"status"` // 启用状态1:启用;2禁用 52 | Remark string `gorm:"column:remark;type:varchar(255);not null;comment:描述信息" json:"remark"` // 描述信息 53 | CreatedAt time.Time `gorm:"column:created_at;type:timestamp;not null;default:CURRENT_TIMESTAMP;comment:创建时间" json:"created_at"` // 创建时间 54 | UpdatedAt time.Time `gorm:"column:updated_at;type:timestamp;not null;default:CURRENT_TIMESTAMP;comment:更新时间" json:"updated_at"` // 更新时间 55 | DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;type:timestamp;comment:删除时间" json:"deleted_at"` // 删除时间 56 | PromStrategies []*PromStrategy `gorm:"foreignKey:GroupID" json:"prom_strategies"` 57 | Categories []*PromDict `gorm:"References:ID;foreignKey:ID;joinForeignKey:PromGroupID;joinReferences:DictID;many2many:prom_group_categories" json:"categories"` 58 | } 59 | 60 | type PromDict struct { 61 | ID int32 `gorm:"column:id;type:int unsigned;primaryKey;autoIncrement:true" json:"id"` 62 | Name string `gorm:"column:name;type:varchar(64);not null;uniqueIndex:idx__name__category,priority:1;comment:字典名称" json:"name"` // 字典名称 63 | Category int32 `gorm:"column:category;type:tinyint;not null;uniqueIndex:idx__name__category,priority:2;index:idx__category,priority:1;comment:字典类型" json:"category"` // 字典类型 64 | Color string `gorm:"column:color;type:varchar(32);not null;default:#165DFF;comment:字典tag颜色" json:"color"` // 字典tag颜色 65 | Status int32 `gorm:"column:status;type:tinyint;not null;default:1;comment:状态" json:"status"` // 状态 66 | Remark string `gorm:"column:remark;type:varchar(255);not null;comment:字典备注" json:"remark"` // 字典备注 67 | CreatedAt time.Time `gorm:"column:created_at;type:timestamp;not null;default:CURRENT_TIMESTAMP;comment:创建时间" json:"created_at"` // 创建时间 68 | UpdatedAt time.Time `gorm:"column:updated_at;type:timestamp;not null;default:CURRENT_TIMESTAMP;comment:更新时间" json:"updated_at"` // 更新时间 69 | DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;type:timestamp;comment:删除时间" json:"deleted_at"` // 删除时间 70 | } 71 | 72 | type PromAlarmPage struct { 73 | ID int32 `gorm:"column:id;type:int unsigned;primaryKey;autoIncrement:true" json:"id"` 74 | Name string `gorm:"column:name;type:varchar(64);not null;uniqueIndex:idx__name,priority:1;comment:报警页面名称" json:"name"` // 报警页面名称 75 | Remark string `gorm:"column:remark;type:varchar(255);not null;comment:描述信息" json:"remark"` // 描述信息 76 | Icon string `gorm:"column:icon;type:varchar(1024);not null;comment:图表" json:"icon"` // 图表 77 | Color string `gorm:"column:color;type:varchar(64);not null;comment:tab颜色" json:"color"` // tab颜色 78 | Status int32 `gorm:"column:status;type:tinyint;not null;default:1;comment:启用状态,1启用;2禁用" json:"status"` // 启用状态,1启用;2禁用 79 | CreatedAt time.Time `gorm:"column:created_at;type:timestamp;not null;default:CURRENT_TIMESTAMP;comment:创建时间" json:"created_at"` // 创建时间 80 | UpdatedAt time.Time `gorm:"column:updated_at;type:timestamp;not null;default:CURRENT_TIMESTAMP;comment:更新时间" json:"updated_at"` // 更新时间 81 | DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;type:timestamp;comment:删除时间" json:"deleted_at"` // 删除时间 82 | PromStrategies []*PromStrategy `gorm:"References:ID;foreignKey:ID;joinForeignKey:AlarmPageID;joinReferences:PromStrategyID;many2many:prom_strategy_alarm_pages" json:"prom_strategies"` 83 | Histories []*PromAlarmHistory `gorm:"References:ID;foreignKey:ID;joinForeignKey:AlarmPageID;joinReferences:HistoryID;many2many:prom_alarm_page_histories" json:"histories"` 84 | } 85 | 86 | type PromAlarmHistory struct { 87 | ID int32 `gorm:"column:id;type:int unsigned;primaryKey;autoIncrement:true" json:"id"` 88 | Node string `gorm:"column:node;type:varchar(64);not null;comment:node名称" json:"node"` // node名称 89 | Status string `gorm:"column:status;type:varchar(16);not null;comment:告警消息状态, 报警和恢复" json:"status"` // 告警消息状态, 报警和恢复 90 | Info string `gorm:"column:info;type:json;not null;comment:原始告警消息" json:"info"` // 原始告警消息 91 | CreatedAt time.Time `gorm:"column:created_at;type:timestamp;not null;default:CURRENT_TIMESTAMP;comment:创建时间" json:"created_at"` // 创建时间 92 | StartAt int64 `gorm:"column:start_at;type:bigint unsigned;not null;comment:报警开始时间" json:"start_at"` // 报警开始时间 93 | EndAt int64 `gorm:"column:end_at;type:bigint unsigned;not null;comment:报警恢复时间" json:"end_at"` // 报警恢复时间 94 | Duration int64 `gorm:"column:duration;type:bigint unsigned;not null;comment:持续时间时间戳, 没有恢复, 时间戳是0" json:"duration"` // 持续时间时间戳, 没有恢复, 时间戳是0 95 | StrategyID int32 `gorm:"column:strategy_id;type:int unsigned;not null;index:idx__strategy_id,priority:1;comment:规则ID, 用于查询时候" json:"strategy_id"` // 规则ID, 用于查询时候 96 | LevelID int32 `gorm:"column:level_id;type:int unsigned;not null;index:idx__level_id,priority:1;comment:报警等级ID" json:"level_id"` // 报警等级ID 97 | Md5 string `gorm:"column:md5;type:char(32);not null;comment:md5" json:"md5"` // md5 98 | Pages []*PromAlarmPage `gorm:"References:ID;foreignKey:ID;joinForeignKey:AlarmPageID;joinReferences:PageID;many2many:prom_prom_alarm_page_histories" json:"pages"` 99 | } 100 | 101 | type Page struct { 102 | Curr int `json:"curr"` 103 | Size int `json:"size"` 104 | Total int64 `json:"total"` 105 | } 106 | 107 | func Test_getTag1(t *testing.T) { 108 | // ListStrategyResp ... 109 | type ListStrategyResp struct { 110 | List []*PromStrategy `json:"list"` 111 | Page Page `json:"page"` 112 | } 113 | 114 | fieldList := getTag(reflect.TypeOf(&ListStrategyResp{})) 115 | t.Log(fieldList) 116 | } 117 | -------------------------------------------------------------------------------- /new.go: -------------------------------------------------------------------------------- 1 | package ginplus 2 | 3 | import ( 4 | "context" 5 | "embed" 6 | "errors" 7 | "io/fs" 8 | "net/http" 9 | "os" 10 | "path" 11 | "reflect" 12 | "strings" 13 | "time" 14 | 15 | "github.com/aide-cloud/gin-plus/swagger" 16 | "github.com/gin-gonic/gin" 17 | "github.com/prometheus/client_golang/prometheus/promhttp" 18 | ) 19 | 20 | var _ Server = (*GinEngine)(nil) 21 | 22 | type HandlerFunc func(controller any, t reflect.Method, req reflect.Type) gin.HandlerFunc 23 | 24 | type ( 25 | GinEngine struct { 26 | *gin.Engine 27 | 28 | // 中间件 29 | middlewares []gin.HandlerFunc 30 | // 控制器 31 | controllers []any 32 | // 绑定前缀和http请求方法的映射 33 | httpMethodPrefixes map[string]httpMethod 34 | // 路由组基础路径 35 | basePath string 36 | // 自定义路由命名规则函数 37 | routeNamingRuleFunc func(methodName string) string 38 | // 自定义handler函数 39 | defaultHandler HandlerFunc 40 | // 自定义Response接口实现 41 | defaultResponse IResponse 42 | // 默认Bind函数 43 | defaultBind func(c *gin.Context, params any) error 44 | 45 | // 文档配置 46 | apiConfig ApiConfig 47 | defaultOpenApiYaml string 48 | apiRoutes map[string][]ApiRoute 49 | 50 | // 生成API路由开关, 默认为true 51 | genApiEnable bool 52 | 53 | // 内置启动地址 54 | // 默认为: :8080 55 | addr string 56 | server *http.Server 57 | 58 | // graphql配置 59 | graphqlConfig GraphqlConfig 60 | 61 | // prom metrics 62 | metrics *Metrics 63 | // ping 64 | ping *Ping 65 | } 66 | 67 | // Metrics prometheus metrics配置 68 | Metrics struct { 69 | Path string 70 | } 71 | 72 | // Ping ping配置 73 | Ping struct { 74 | HandlerFunc gin.HandlerFunc 75 | } 76 | 77 | // ApiConfig 文档配置, 78 | ApiConfig Info 79 | 80 | // RouteNamingRuleFunc 自定义路由命名函数 81 | RouteNamingRuleFunc func(methodName string) string 82 | 83 | // IMiddleware 中间件接口, 实现该接口的结构体将会被把中间件添加到该路由组的公共中间件中 84 | IMiddleware interface { 85 | Middlewares() []gin.HandlerFunc 86 | } 87 | 88 | // MethodeMiddleware 中间件接口, 为每个方法添加中间件 89 | MethodeMiddleware interface { 90 | MethodeMiddlewares() map[string][]gin.HandlerFunc 91 | } 92 | 93 | // Controller 控制器接口, 实现该接口的对象可以自定义模块的路由 94 | Controller interface { 95 | BasePath() string 96 | } 97 | 98 | // Route 路由参数结构 99 | Route struct { 100 | Path string 101 | HttpMethod string 102 | Handles []gin.HandlerFunc 103 | } 104 | 105 | // ApiRoute api路由参数结构, 用于生成文档 106 | ApiRoute struct { 107 | Path string 108 | HttpMethod string 109 | MethodName string 110 | ReqParams Field 111 | RespParams Field 112 | } 113 | 114 | // OptionFun GinEngine配置函数 115 | OptionFun func(*GinEngine) 116 | 117 | // httpMethod http请求方法 118 | httpMethod struct { 119 | key string 120 | } 121 | // HttpMethod http请求方法, 绑定前缀和http请求方法的映射 122 | HttpMethod struct { 123 | Prefix string 124 | Method httpMethod 125 | } 126 | 127 | // GraphqlConfig graphql配置 128 | GraphqlConfig struct { 129 | // Enable 是否启用 130 | Enable bool 131 | // HandlePath graphql请求路径 132 | HandlePath string 133 | // SchemaPath graphql schema文件路径 134 | ViewPath string 135 | // Root graphql 服务根节点 136 | Root any 137 | // Content graphql schema文件内容 138 | Content embed.FS 139 | } 140 | ) 141 | 142 | const ( 143 | get = "Get" 144 | post = "Post" 145 | put = "Put" 146 | del = "Delete" 147 | patch = "Patch" 148 | head = "Head" 149 | option = "Option" 150 | ) 151 | 152 | const ( 153 | defaultTitle = "github.com/aide-cloud/gin-plus" 154 | defaultVersion = "v0.5.0" 155 | defaultMetricsPath = "/metrics" 156 | defaultPingPath = "/ping" 157 | ) 158 | 159 | var ( 160 | Get = httpMethod{key: get} 161 | Post = httpMethod{key: post} 162 | Put = httpMethod{key: put} 163 | Delete = httpMethod{key: del} 164 | Patch = httpMethod{key: patch} 165 | Head = httpMethod{key: head} 166 | Option = httpMethod{key: option} 167 | ) 168 | 169 | // defaultPrefixes is the default prefixes. 170 | var defaultPrefixes = map[string]httpMethod{ 171 | get: Get, 172 | post: Post, 173 | put: Put, 174 | del: Delete, 175 | patch: Patch, 176 | head: Head, 177 | option: Option, 178 | } 179 | 180 | // New 返回一个GinEngine实例 181 | func New(r *gin.Engine, opts ...OptionFun) *GinEngine { 182 | instance := &GinEngine{ 183 | Engine: r, 184 | httpMethodPrefixes: defaultPrefixes, 185 | defaultOpenApiYaml: defaultOpenApiYaml, 186 | defaultResponse: NewResponse(), 187 | defaultBind: Bind, 188 | routeNamingRuleFunc: routeToCamel, 189 | apiRoutes: make(map[string][]ApiRoute), 190 | genApiEnable: true, 191 | apiConfig: ApiConfig{ 192 | Title: defaultTitle, 193 | Version: defaultVersion, 194 | }, 195 | ping: &Ping{HandlerFunc: func(ctx *gin.Context) { 196 | ctx.Status(http.StatusOK) 197 | }}, 198 | metrics: &Metrics{Path: defaultMetricsPath}, 199 | addr: ":8080", 200 | } 201 | for _, opt := range opts { 202 | opt(instance) 203 | } 204 | 205 | if instance.defaultHandler == nil { 206 | instance.defaultHandler = instance.newDefaultHandler 207 | } 208 | 209 | instance.Use(instance.middlewares...) 210 | 211 | for _, c := range instance.controllers { 212 | instance.genRoute(nil, c, false) 213 | } 214 | 215 | return instance 216 | } 217 | 218 | // Start 启动HTTP服务器 219 | func (l *GinEngine) Start() error { 220 | if l.server == nil { 221 | //创建HTTP服务器 222 | server := &http.Server{ 223 | Addr: l.addr, 224 | Handler: l.Engine, 225 | } 226 | l.server = server 227 | } 228 | 229 | //启动HTTP服务器 230 | go func() { 231 | if err := l.server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { 232 | Logger().Sugar().Infof("[GIN-PLUS] [INFO] Server listen: %s\n", err) 233 | } 234 | }() 235 | 236 | Logger().Sugar().Infof("[GIN-PLUS] [INFO] Server is running at %s", l.addr) 237 | 238 | return nil 239 | } 240 | 241 | // Stop 停止HTTP服务器 242 | func (l *GinEngine) Stop() { 243 | //创建超时上下文,Shutdown可以让未处理的连接在这个时间内关闭 244 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 245 | defer cancel() 246 | 247 | //停止HTTP服务器 248 | if err := l.server.Shutdown(ctx); err != nil { 249 | Logger().Sugar().Errorf("[GIN-PLUS] [INFO] Server Shutdown: %v", err) 250 | } 251 | 252 | Logger().Sugar().Infof("[GIN-PLUS] [INFO] Server stopped") 253 | } 254 | 255 | func registerPing(instance *GinEngine, ping *Ping) { 256 | if ping != nil { 257 | instance.GET(defaultPingPath, ping.HandlerFunc) 258 | return 259 | } 260 | } 261 | 262 | func registerMetrics(instance *GinEngine, metrics *Metrics) { 263 | if metrics == nil { 264 | return 265 | } 266 | if metrics.Path == "" { 267 | metrics.Path = defaultMetricsPath 268 | } 269 | instance.GET(metrics.Path, gin.WrapH(promhttp.Handler())) 270 | } 271 | 272 | func registerSwaggerUI(instance *GinEngine, enable bool) { 273 | if !enable { 274 | return 275 | } 276 | instance.genOpenApiYaml() 277 | fp, _ := fs.Sub(swagger.Dist, "dist") 278 | instance.StaticFS("/swagger-ui", http.FS(fp)) 279 | instance.GET("/openapi/doc/swagger", func(ctx *gin.Context) { 280 | file, _ := os.ReadFile(instance.defaultOpenApiYaml) 281 | ctx.Writer.Header().Set("Content-Type", "text/yaml; charset=utf-8") 282 | _, _ = ctx.Writer.Write(file) 283 | }) 284 | } 285 | 286 | func registerGraphql(instance *GinEngine, config GraphqlConfig) { 287 | if !config.Enable || config.Root == nil { 288 | return 289 | } 290 | if config.HandlePath == "" { 291 | config.HandlePath = DefaultHandlePath 292 | } 293 | if config.ViewPath == "" { 294 | config.ViewPath = DefaultViewPath 295 | } 296 | instance.POST(config.HandlePath, gin.WrapH(Handler(config.Root, config.Content))) 297 | instance.GET(config.ViewPath, gin.WrapF(View(config.HandlePath))) 298 | } 299 | 300 | func (l *GinEngine) RegisterPing(ping ...*Ping) *GinEngine { 301 | if len(ping) > 0 { 302 | l.ping = ping[0] 303 | } 304 | registerPing(l, l.ping) 305 | return l 306 | } 307 | 308 | // RegisterMetrics 注册prometheus metrics 309 | func (l *GinEngine) RegisterMetrics(metrics ...*Metrics) *GinEngine { 310 | if len(metrics) > 0 { 311 | l.metrics = metrics[0] 312 | } 313 | registerMetrics(l, l.metrics) 314 | return l 315 | } 316 | 317 | // RegisterSwaggerUI 注册swagger ui 318 | func (l *GinEngine) RegisterSwaggerUI() *GinEngine { 319 | registerSwaggerUI(l, l.genApiEnable) 320 | return l 321 | } 322 | 323 | // RegisterGraphql 注册graphql 324 | func (l *GinEngine) RegisterGraphql(config ...*GraphqlConfig) *GinEngine { 325 | if len(config) > 0 { 326 | l.graphqlConfig = *config[0] 327 | } 328 | registerGraphql(l, l.graphqlConfig) 329 | return l 330 | } 331 | 332 | // WithControllers sets the controllers. 333 | func WithControllers(controllers ...any) OptionFun { 334 | return func(g *GinEngine) { 335 | g.controllers = controllers 336 | } 337 | } 338 | 339 | // WithMiddlewares sets the middlewares. 340 | func WithMiddlewares(middlewares ...gin.HandlerFunc) OptionFun { 341 | return func(g *GinEngine) { 342 | g.middlewares = middlewares 343 | } 344 | } 345 | 346 | // WithHttpMethodPrefixes sets the prefixes. 347 | func WithHttpMethodPrefixes(prefixes ...HttpMethod) OptionFun { 348 | return func(g *GinEngine) { 349 | prefixHttpMethodMap := make(map[string]httpMethod) 350 | for _, prefix := range prefixes { 351 | if prefix.Prefix == "" || prefix.Method.key == "" { 352 | continue 353 | } 354 | prefixHttpMethodMap[prefix.Prefix] = prefix.Method 355 | } 356 | g.httpMethodPrefixes = prefixHttpMethodMap 357 | } 358 | } 359 | 360 | // AppendHttpMethodPrefixes append the prefixes. 361 | func AppendHttpMethodPrefixes(prefixes ...HttpMethod) OptionFun { 362 | return func(g *GinEngine) { 363 | prefixHttpMethodMap := g.httpMethodPrefixes 364 | if prefixHttpMethodMap == nil { 365 | prefixHttpMethodMap = make(map[string]httpMethod) 366 | } 367 | for _, prefix := range prefixes { 368 | if prefix.Prefix == "" || prefix.Method.key == "" { 369 | continue 370 | } 371 | prefixHttpMethodMap[prefix.Prefix] = prefix.Method 372 | } 373 | g.httpMethodPrefixes = prefixHttpMethodMap 374 | } 375 | } 376 | 377 | // WithBasePath sets the base path. 378 | func WithBasePath(basePath string) OptionFun { 379 | return func(g *GinEngine) { 380 | g.basePath = path.Join("/", basePath) 381 | } 382 | } 383 | 384 | // WithRouteNamingRuleFunc 自定义路由命名函数 385 | func WithRouteNamingRuleFunc(ruleFunc RouteNamingRuleFunc) OptionFun { 386 | return func(g *GinEngine) { 387 | g.routeNamingRuleFunc = ruleFunc 388 | } 389 | } 390 | 391 | // WithApiConfig sets the title. 392 | func WithApiConfig(c ApiConfig) OptionFun { 393 | return func(g *GinEngine) { 394 | g.apiConfig = c 395 | } 396 | } 397 | 398 | // WithOpenApiYaml 自定义api文件存储位置和文件名称 399 | func WithOpenApiYaml(dir, filename string) OptionFun { 400 | return func(g *GinEngine) { 401 | if !strings.HasSuffix(filename, ".yaml") { 402 | Logger().Sugar().Infof("[GIN-PLUS] [WARNING] filename has no (.yaml) suffix, so the default (%s) is used as the filename.\n", defaultOpenApiYaml) 403 | } 404 | g.defaultOpenApiYaml = path.Join(dir, filename) 405 | } 406 | } 407 | 408 | // WithGenApiEnable 设置是否生成API路由 409 | func WithGenApiEnable(enable bool) OptionFun { 410 | return func(g *GinEngine) { 411 | g.genApiEnable = enable 412 | } 413 | } 414 | 415 | // WithAddr 设置启动地址 416 | func WithAddr(addr string) OptionFun { 417 | return func(g *GinEngine) { 418 | g.addr = addr 419 | } 420 | } 421 | 422 | // WithHttpServer 设置启动地址 423 | func WithHttpServer(server *http.Server) OptionFun { 424 | return func(g *GinEngine) { 425 | server.Handler = g.Engine 426 | if server.Addr == "" { 427 | server.Addr = g.addr 428 | } 429 | g.server = server 430 | } 431 | } 432 | 433 | // WithGraphqlConfig 设置graphql配置 434 | func WithGraphqlConfig(config GraphqlConfig) OptionFun { 435 | return func(g *GinEngine) { 436 | g.graphqlConfig = config 437 | } 438 | } 439 | 440 | // WithDefaultHandler 自定义handler函数 441 | func WithDefaultHandler(handler HandlerFunc) OptionFun { 442 | return func(g *GinEngine) { 443 | g.defaultHandler = handler 444 | } 445 | } 446 | 447 | // WithDefaultResponse 自定义Response接口实现 448 | func WithDefaultResponse(response IResponse) OptionFun { 449 | return func(g *GinEngine) { 450 | g.defaultResponse = response 451 | } 452 | } 453 | 454 | // WithMetrics 自定义Metrics 455 | func WithMetrics(metrics *Metrics) OptionFun { 456 | return func(g *GinEngine) { 457 | g.metrics = metrics 458 | } 459 | } 460 | 461 | // WithPing 自定义Ping 462 | func WithPing(ping *Ping) OptionFun { 463 | return func(g *GinEngine) { 464 | g.ping = ping 465 | } 466 | } 467 | 468 | // WithBind 自定义Bind 469 | func WithBind(bind func(c *gin.Context, params any) error) OptionFun { 470 | return func(g *GinEngine) { 471 | g.defaultBind = bind 472 | } 473 | } 474 | -------------------------------------------------------------------------------- /example/go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= 4 | cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= 5 | cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= 6 | cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= 7 | cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= 8 | cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= 9 | cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= 10 | cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= 11 | cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= 12 | cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= 13 | cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= 14 | cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= 15 | cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= 16 | cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= 17 | cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= 18 | cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= 19 | cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= 20 | cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= 21 | cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= 22 | cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= 23 | cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= 24 | cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= 25 | cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= 26 | cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= 27 | cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= 28 | cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= 29 | cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= 30 | cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= 31 | cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= 32 | cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= 33 | cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= 34 | cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= 35 | cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= 36 | cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= 37 | cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= 38 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= 39 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 40 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 41 | github.com/aide-cloud/gin-plus v0.1.7 h1:LzXAQBcM8pDPz+4vpFjiZaxPtgQ/GEGG21iFf7QWhZg= 42 | github.com/aide-cloud/gin-plus v0.1.7/go.mod h1:6gpcyVxTZZrMve7BSxZAoSiy5keINVGbFBAhZdUZIEU= 43 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 44 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 45 | github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= 46 | github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM= 47 | github.com/bytedance/sonic v1.10.0 h1:qtNZduETEIWJVIyDl01BeNxur2rW9OwTQ/yBqFRkKEk= 48 | github.com/bytedance/sonic v1.10.0/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= 49 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 50 | github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= 51 | github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 52 | github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= 53 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= 54 | github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0= 55 | github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA= 56 | github.com/chenzhuoyu/iasm v0.9.0 h1:9fhXjVzq5hUy2gkhhgHl95zG2cEAhw9OSGs8toWWAwo= 57 | github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= 58 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 59 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 60 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 61 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 62 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 63 | github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= 64 | github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= 65 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 66 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 67 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 68 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 69 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 70 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 71 | github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= 72 | github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= 73 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 74 | github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= 75 | github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= 76 | github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= 77 | github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= 78 | github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= 79 | github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= 80 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= 81 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 82 | github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= 83 | github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= 84 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= 85 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 86 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 87 | github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 88 | github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 89 | github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= 90 | github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= 91 | github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 92 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= 93 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= 94 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= 95 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= 96 | github.com/go-playground/validator/v10 v10.15.3 h1:S+sSpunYjNPDuXkWbK+x+bA7iXiW296KG4dL3X7xUZo= 97 | github.com/go-playground/validator/v10 v10.15.3/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= 98 | github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= 99 | github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= 100 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 101 | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 102 | github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 103 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 104 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 105 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 106 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= 107 | github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 108 | github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 109 | github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 110 | github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= 111 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 112 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 113 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 114 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 115 | github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 116 | github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= 117 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 118 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 119 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 120 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 121 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 122 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 123 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 124 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 125 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 126 | github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= 127 | github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 128 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 129 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 130 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 131 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 132 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 133 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 134 | github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 135 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 136 | github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 137 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 138 | github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 139 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 140 | github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= 141 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 142 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 143 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 144 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= 145 | github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= 146 | github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= 147 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 148 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 149 | github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 150 | github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 151 | github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 152 | github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 153 | github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 154 | github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 155 | github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 156 | github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 157 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 158 | github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= 159 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 160 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= 161 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= 162 | github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= 163 | github.com/graph-gophers/graphql-go v1.5.0 h1:fDqblo50TEpD0LY7RXk/LFVYEVqo3+tXMNMPSVXA1yc= 164 | github.com/graph-gophers/graphql-go v1.5.0/go.mod h1:YtmJZDLbF1YYNrlNAuiO5zAStUWc3XZT07iGsVqe1Os= 165 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 166 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 167 | github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= 168 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 169 | github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 170 | github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 171 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 172 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 173 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 174 | github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= 175 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 176 | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= 177 | github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= 178 | github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= 179 | github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= 180 | github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= 181 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 182 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 183 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 184 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 185 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 186 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 187 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 188 | github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= 189 | github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= 190 | github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= 191 | github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= 192 | github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= 193 | github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 194 | github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= 195 | github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= 196 | github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= 197 | github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= 198 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 199 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 200 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 201 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 202 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 203 | github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= 204 | github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= 205 | github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= 206 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 207 | github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= 208 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 209 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 210 | github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8= 211 | github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc= 212 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 213 | github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY= 214 | github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= 215 | github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY= 216 | github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= 217 | github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwaUuI= 218 | github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY= 219 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 220 | github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= 221 | github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= 222 | github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM= 223 | github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= 224 | github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= 225 | github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= 226 | github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= 227 | github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= 228 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 229 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 230 | github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc= 231 | github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg= 232 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 233 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 234 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 235 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 236 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 237 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 238 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 239 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 240 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 241 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 242 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 243 | github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 244 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= 245 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 246 | github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= 247 | github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= 248 | github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= 249 | github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= 250 | github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= 251 | github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= 252 | github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 253 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 254 | github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 255 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 256 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= 257 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= 258 | go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 259 | go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 260 | go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 261 | go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= 262 | go.opentelemetry.io/otel v1.6.3/go.mod h1:7BgNga5fNlF/iZjG06hM3yofffp0ofKCDwSXx1GC4dI= 263 | go.opentelemetry.io/otel/trace v1.6.3/go.mod h1:GNJQusJlUgZl9/TQBPKU/Y/ty+0iVB5fjhKeJGZPGFs= 264 | golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 265 | golang.org/x/arch v0.4.0 h1:A8WCeEWhLwPBKNbFi5Wv5UTCBx5zzubnXDlMOFAzFMc= 266 | golang.org/x/arch v0.4.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 267 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 268 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 269 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 270 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 271 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 272 | golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= 273 | golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 274 | golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= 275 | golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= 276 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 277 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 278 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 279 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= 280 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= 281 | golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 282 | golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 283 | golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 284 | golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= 285 | golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= 286 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 287 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 288 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 289 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 290 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 291 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 292 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 293 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 294 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 295 | golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= 296 | golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 297 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 298 | golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 299 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= 300 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= 301 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 302 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= 303 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 304 | golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 305 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 306 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 307 | golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 308 | golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 309 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 310 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 311 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 312 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 313 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 314 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 315 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 316 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 317 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 318 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 319 | golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 320 | golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 321 | golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 322 | golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 323 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 324 | golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 325 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 326 | golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 327 | golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 328 | golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 329 | golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 330 | golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 331 | golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 332 | golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 333 | golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 334 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 335 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 336 | golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 337 | golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 338 | golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 339 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 340 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 341 | golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= 342 | golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= 343 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 344 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 345 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 346 | golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 347 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 348 | golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 349 | golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 350 | golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 351 | golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 352 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 353 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 354 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 355 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 356 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 357 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 358 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 359 | golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 360 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 361 | golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 362 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 363 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 364 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 365 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 366 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 367 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 368 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 369 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 370 | golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 371 | golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 372 | golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 373 | golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 374 | golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 375 | golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 376 | golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 377 | golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 378 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 379 | golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 380 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 381 | golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 382 | golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 383 | golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 384 | golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 385 | golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 386 | golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 387 | golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 388 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 389 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 390 | golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 391 | golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 392 | golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 393 | golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 394 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 395 | golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 396 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 397 | golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 398 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 399 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 400 | golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= 401 | golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 402 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 403 | golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 404 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 405 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 406 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 407 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 408 | golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 409 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 410 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 411 | golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= 412 | golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 413 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 414 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 415 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 416 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 417 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 418 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 419 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 420 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 421 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 422 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 423 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 424 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 425 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 426 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 427 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 428 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 429 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 430 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 431 | golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 432 | golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 433 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 434 | golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 435 | golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 436 | golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 437 | golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 438 | golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 439 | golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 440 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 441 | golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 442 | golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 443 | golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 444 | golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 445 | golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 446 | golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= 447 | golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= 448 | golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= 449 | golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 450 | golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 451 | golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 452 | golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 453 | golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 454 | golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 455 | golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 456 | golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= 457 | golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 458 | golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 459 | golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 460 | golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 461 | golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 462 | golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= 463 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 464 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 465 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 466 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 467 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= 468 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= 469 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 470 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 471 | google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 472 | google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 473 | google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 474 | google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 475 | google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 476 | google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 477 | google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 478 | google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 479 | google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= 480 | google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= 481 | google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= 482 | google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= 483 | google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= 484 | google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= 485 | google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= 486 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 487 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 488 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 489 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= 490 | google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 491 | google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 492 | google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 493 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 494 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 495 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 496 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 497 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 498 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 499 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 500 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= 501 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 502 | google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 503 | google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 504 | google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 505 | google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 506 | google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 507 | google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= 508 | google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 509 | google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 510 | google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 511 | google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 512 | google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 513 | google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 514 | google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 515 | google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 516 | google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= 517 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 518 | google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= 519 | google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 520 | google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 521 | google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 522 | google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 523 | google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 524 | google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 525 | google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 526 | google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 527 | google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 528 | google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 529 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 530 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= 531 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 532 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 533 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 534 | google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 535 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 536 | google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 537 | google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= 538 | google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= 539 | google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 540 | google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 541 | google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 542 | google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= 543 | google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= 544 | google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= 545 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 546 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 547 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 548 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 549 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 550 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 551 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 552 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 553 | google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= 554 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 555 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 556 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 557 | google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= 558 | google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 559 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 560 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 561 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 562 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 563 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 564 | gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= 565 | gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 566 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 567 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 568 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 569 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 570 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 571 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 572 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 573 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 574 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 575 | honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 576 | honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 577 | nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= 578 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= 579 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= 580 | rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= 581 | rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= 582 | --------------------------------------------------------------------------------