├── .gitignore ├── Dockerfile ├── README.md ├── api └── v1 │ ├── upload.go │ └── user.go ├── build.sh ├── cmd └── main.go ├── conf ├── config.docker.yaml └── config.yaml ├── consts ├── common.go └── user.go ├── deploy ├── filebeat │ └── conf │ │ └── filebeat.yml ├── gencode │ └── gencode.sh ├── go-stash │ └── etc │ │ └── config.yaml ├── kubernetes │ └── server │ │ ├── server-configMap.yaml │ │ ├── server-deployment.yaml │ │ └── server-service.yaml ├── nginx │ └── conf.d │ │ └── go_builder-gateway.conf └── sql │ └── sys_user.sql ├── doc └── elk-flow.png ├── docker-compose-env.yml ├── docker-compose.yml ├── go.mod ├── go.sum ├── logger └── logger.go ├── middlewares ├── cors.go ├── jwt.go └── track.go ├── repository ├── cache │ ├── key.go │ └── redis.go ├── db │ ├── dao │ │ ├── migrate.go │ │ ├── mysql.go │ │ └── user.go │ └── model │ │ └── user.go ├── es │ └── es.go ├── kafka │ ├── consumer.go │ ├── kafka.go │ └── producer.go └── track │ └── init.go ├── router └── route.go ├── service ├── svc │ └── user.go └── user.go ├── setting ├── server │ ├── server.go │ └── signal_error.go └── setting.go ├── static └── dictionary │ └── 其他词库.txt ├── types ├── common.go └── user.go └── utils ├── app ├── app.go ├── claims.go └── code.go ├── duration.go ├── event └── event.go ├── jwt └── jwt.go ├── md5.go ├── sensitiveWords └── check.go ├── snowflake └── snowflake.go ├── upload ├── local.go ├── qiniu.go ├── tool.go └── upload.go ├── urls └── url.go └── validator.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | .idea/ 8 | log 9 | swag.exe 10 | resource/excel/* 11 | .air.conf 12 | *.log 13 | # Test binary, built with `go test -c` 14 | *.test 15 | 16 | # Output of the go coverage tool, specifically when used with LiteIDE 17 | *.out 18 | 19 | conf/config.yaml 20 | 21 | # Dependency directories (remove the comment below to include it) 22 | # vendor/ 23 | .DS_Store 24 | latest_log 25 | main -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # 注意:golang:1.19-alpine 这个版本号和自己的go.mod 对应,如果不一致可能会出现镜像构建失败,go和gomod有版本差 2 | 3 | FROM golang:1.19-alpine as builder 4 | WORKDIR /app/src/go_builder 5 | COPY . . 6 | 7 | RUN go env -w GO111MODULE=on \ 8 | && go env -w GOPROXY=https://goproxy.cn,direct \ 9 | && go env -w CGO_ENABLED=0 \ 10 | && go env \ 11 | && go mod tidy \ 12 | && go build -o main . 13 | 14 | FROM alpine:latest 15 | 16 | LABEL MAINTAINER="cherryqcsj@gmail.com" 17 | 18 | WORKDIR /app/src/go_builder 19 | 20 | COPY --from=0 /app/src/go_builder/main ./ 21 | COPY --from=0 /app/src/go_builder/conf/config.docker.yaml ./ 22 | 23 | EXPOSE 8888 24 | ENTRYPOINT ./main -c conf/config.docker.yaml 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 一、go_builder脚手架介绍 2 | 3 | ### 1、项目介绍 4 | 5 | 本脚手架是基于gin框架实现 6 | 7 | - 内置zap日志库 8 | - viper读取配置文件 9 | - jwt & cors & track的中间件 10 | - 缓存使用redis,orm框架使用gorm 11 | - 七牛云oss文件上传 12 | - 使用依赖倒置原则抽象dao层 13 | - dockerfile制作镜像,docker-compose编排项目 14 | - 使用shell脚本自动生成dao、cache、service、svc模板代码 15 | - elk日志收集系统 16 | - kubernetes集群搭建 17 | 18 | 准备实现 19 | 20 | - jaeger的链路追踪收集到jaeger-ui 21 | - 添加apollo配置注册中心 22 | 23 | ### 2、项目目的 24 | 25 | 在使用Gin框架的时候,因为比较轻量级,使用gin.new(),并且run起来就运行起来了,但是我们项目中往往需要配置许多依赖库,我这里就使用cld的架构搭建里一套关于gin框架的脚手架。 26 | 27 | ### 3、项目介绍 28 | 29 | 脚手架架构分为CLD分层,controller为api层、service为逻辑层,dao层为数据库层,上手简单,目录结构清晰,一些常用小工具后续会慢慢加上~~~ 30 | 31 | ## 二、go_builder 脚手架目录结构 32 | 33 | ```shell 34 | ├── conf 35 | ├── api 36 | ├── cmd 37 | ├── deploy 38 | ├── logger 39 | ├── middlewares 40 | ├── repository 41 | ├── cache 42 | ├── db 43 | ├── dao 44 | ├── model 45 | ├── es 46 | ├── rabbitmq 47 | ├── track 48 | ├── router 49 | ├── service 50 | ├── svc 51 | ├── setting 52 | └── utils 53 | ├── app 54 | └── snowflake 55 | ├── jwt 56 | ├── upload 57 | ``` 58 | 59 | 60 | 61 | | 文件夹 | 说明 | 描述 | 62 | | :------------ | -------------- | --------------------------------------------------- | 63 | | `conf` | 配置包 | 放置配置文件,例:config.yaml | 64 | | `api` | api层 | 程序入口层 | 65 | | `cmd` | 服务程序包 | 包含程序运行main函数以及运行脚本 | 66 | | `deploy` | 外来配置工具包 | 配置nginx.conf、sql建表、script脚本、组件yaml配置等 | 67 | | `logger` | 日志包 | 初始化日志文件 | 68 | | `middlewares` | 中间件 | 自定义关于gin的中间件,例如jwt、cors等 | 69 | | `repository` | 组件仓库 | 包含缓存、db、es、mq等 | 70 | | `--cache` | 缓存组件 | 使用redis缓存层 | 71 | | `--db` | sql组件 | 存放mysql的model & dao | 72 | | `router` | 路由层 | 用于放入全局路由 | 73 | | `service` | 逻辑层 | 用于放入业务逻辑 | 74 | | `--svc` | 逻辑上下文层 | 用于初始化service层的context,拿到dao层接口数据 | 75 | | `setting` | 配置项 | yaml配置映射为结构体 | 76 | | `utils` | 工具包 | 自定义工具使用 | 77 | | `--app` | 全局响应 | 返回json数据的封装,success & failed | 78 | | `--snowflake` | 雪花算法工具包 | 生成int64的id | 79 | 80 | ### 三、关于gencode自动生成代码 81 | 使用的是shell脚本实现 82 | 83 | gencode脚本在deploy/gencode/gencode.sh中,首先进入gencode文件目录,如果我要生成关于user的代码,命令:**./gencode.sh user**,会生成对应的svc、service、dao、cache模板代码。后续考虑单独抽出来,放入gopath/bin目录下 84 | 85 | ### 四、关于ELK日志系统分析 86 | 87 | 使用的是filebeat收集业务数据发送到 -> kafka消息队列 <- 使用go-stash消费kafka当中的日志数据 -> 发送到es里 <- 最后收集进kibana展示 88 | 89 | 1、配置的filebeat.yml在deploy下,挂在到docker-compose 90 | 91 | 2、配置的go-stash.yaml也在deploy下,挂在所需要的配置文件进docker 92 | 93 | ![Image text](doc/elk-flow.png) 94 | 95 | 最终运行`docker-compose -f docker-compose-env.yml up -d`运行起来elk环境,最终访问kibana即可 96 | 97 | ### 五、docker的使用 98 | 99 | #### 1、使用dockerfile制作镜像 100 | 101 | `docker build -t go_builder .` 构建镜像 102 | 103 | #### 2、使用docker-compose一键编排项目 104 | 105 | `docekr-compose up` 一键启动项目 106 | 107 | `docker-compose stop` 一键关闭项目 108 | #### 3、关于docker-compose-env 109 | 这个是中间件的环境包含mysql、redis、kafka等 110 | 111 | ### 六、kubernetes的使用 112 | #### 1、启动deployment服务 113 | `kubectl apply -f server-deployment.yaml` 114 | #### 2、 启动service服务 115 | `kubectl apply -f server-service.yaml` 116 | #### 3、 创建configMap对象 117 | `kubectl apply -f server-configMap.yaml` 118 | 119 | ### 七、关于CICD流水线构建流程 120 | #### 1、使用jenkins+镜像仓库+github/gitlab 121 | - 配置jenkins和github,当main分支有变动jenkins就可以触发然后拉去最新代码 122 | - 执行**build.sh**去当可以构建流水线,执行build.sh脚本构建镜像 123 | - 构建完成之后就可以在docker-compose或者k8s更改版本号执行即可 124 | 125 | 126 | ## 欢迎大家提issue!!! 127 | -------------------------------------------------------------------------------- /api/v1/upload.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "io/ioutil" 5 | "project/consts" 6 | "project/utils/app" 7 | "project/utils/upload" 8 | "strconv" 9 | 10 | "github.com/gin-gonic/gin" 11 | "go.uber.org/zap" 12 | ) 13 | 14 | func UploadFileHandler(c *gin.Context) { 15 | file, header, err := c.Request.FormFile("image") 16 | 17 | if err != nil { 18 | zap.L().Error("formfile failed", zap.Error(err)) 19 | app.ResponseErrorWithMsg(c, err) 20 | return 21 | } 22 | 23 | defer file.Close() 24 | if header.Size > consts.UploadMaxBytes { 25 | app.ResponseErrorWithMsg(c, "图片不能超过"+strconv.Itoa(consts.UploadMaxM)+"M") 26 | return 27 | } 28 | contentType := header.Header.Get("Content-Type") 29 | 30 | fileBytes, err := ioutil.ReadAll(file) 31 | 32 | if err != nil { 33 | zap.L().Error("ReadAll failed", zap.Error(err)) 34 | app.ResponseErrorWithMsg(c, err) 35 | return 36 | } 37 | zap.L().Info("上传文件:", zap.String("filename: ", header.Filename), zap.Int64("size:", header.Size)) 38 | if err != nil { 39 | app.ResponseErrorWithMsg(c, err) 40 | return 41 | } 42 | url, err := upload.PutImage(fileBytes, contentType) 43 | if err != nil { 44 | app.ResponseErrorWithMsg(c, err) 45 | return 46 | } 47 | 48 | app.ResponseSuccess(c, gin.H{"urls": url}) 49 | } 50 | -------------------------------------------------------------------------------- /api/v1/user.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "project/service" 5 | "project/service/svc" 6 | "project/types" 7 | "project/utils/app" 8 | 9 | "github.com/gin-gonic/gin" 10 | "go.uber.org/zap" 11 | ) 12 | 13 | type UserApi struct{} 14 | 15 | func UserRegisterHandler(c *gin.Context) { 16 | var u types.UserRegisterReq 17 | if err := c.ShouldBind(&u); err != nil { 18 | zap.L().Error("UserRegisterHandler param with invalid", zap.Error(err)) 19 | app.ResponseErrorWithMsg(c, err.Error()) 20 | return 21 | } 22 | us := service.NewUserService(c.Request.Context(), svc.NewUserServiceContext()) 23 | if err := us.UserRegisterSrv(&u); err != nil { 24 | zap.L().Error("UserRegisterSrv failed,err:", zap.Error(err)) 25 | app.ResponseErrorWithMsg(c, err.Error()) 26 | return 27 | } 28 | 29 | app.ResponseSuccess(c, nil) 30 | } 31 | 32 | func UserLoginHandler(c *gin.Context) { 33 | var req types.UserRegisterReq 34 | if err := c.ShouldBind(&req); err != nil { 35 | zap.L().Error("UserLoginHandler param with invalid", zap.Error(err)) 36 | app.ResponseErrorWithMsg(c, err.Error()) 37 | return 38 | } 39 | 40 | us := service.NewUserService(c.Request.Context(), svc.NewUserServiceContext()) 41 | resp, err := us.UserLoginSrv(&req) 42 | if err != nil { 43 | zap.L().Error("UserLoginHandler failed,err:", zap.Error(err)) 44 | app.ResponseErrorWithMsg(c, err.Error()) 45 | return 46 | } 47 | 48 | app.ResponseSuccess(c, resp) 49 | } 50 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | VERSION="1.0.0" 4 | PNAME="server" 5 | 6 | ## 如果有Dockerfile模板,则需要以下代码来初始化 7 | #sed 's/__VERSION__/'${VERSION}'/' Dockerfile-tpl > Dockerfile-tmp 8 | #sed 's/__PNAME__/'${PNAME}'/' Dockerfile-tmp > Dockerfile 9 | #rm -f Dockerfile-tmp 10 | 11 | # 开始编译Docker镜像,如果有一些初始化操作亦可以放到这里 12 | 13 | #检查版本号是否存在 14 | #function check_version() { 15 | # existVersion=$(docker images | awk '{if ($1 == "'$PNAME'") print $0}' | awk '{print $2}' | grep $VERSION) 16 | # if [ x"$existVersion" == x"$VERSION" ];then 17 | # echo "bad version of build $PNAME-$VERSION" 18 | # exit -1 19 | # fi 20 | #} 21 | 22 | #if [ x"$1" != "test" ]; then 23 | # check_version 24 | #fi 25 | 26 | ################################################################ 27 | # 以下代码一般无需更改 # 28 | ################################################################ 29 | 30 | 31 | 32 | echo "docker build..." 33 | docker build -t $PNAME . 34 | 35 | if [ $? != 0 ];then 36 | echo "failed to build $PNAME-${PNAME} image" 37 | exit -1 38 | fi 39 | 40 | docker tag $PNAME:latest $PNAME:${VERSION} 41 | 42 | 43 | # 清理本地的镜像,避免无用镜像过多 44 | function clean() { 45 | DOCKER_REPO_TMP=$1 46 | if [ x$1 == x"" ]; then 47 | echo "not found docker repo argument." 48 | return -1 49 | fi 50 | docker rmi ${DOCKER_REPO_TMP}/${PNAME}:latest 51 | docker rmi ${DOCKER_REPO_TMP}/${PNAME}:${VERSION} 52 | } 53 | 54 | # 推送前打tag 55 | function tag() { 56 | DOCKER_REPO_TMP=$1 57 | if [ x$1 == x"" ]; then 58 | echo "not found docker repo argument." 59 | return -1 60 | fi 61 | echo "docker tag ${DOCKER_REPO_TMP} latest..." 62 | docker tag ${PNAME}:latest ${DOCKER_REPO_TMP}/${PNAME}:latest 63 | echo "docker tag ${DOCKER_REPO_TMP} ${VERSION}..." 64 | docker tag ${PNAME}:latest ${DOCKER_REPO_TMP}/${PNAME}:${VERSION} 65 | } 66 | # 推送镜像,有三次重试 67 | function push() { 68 | DOCKER_REPO_TMP=$1 69 | if [ x$1 == x"" ]; then 70 | echo "not found docker repo argument." 71 | return -1 72 | fi 73 | 74 | TAG_TMP=$2 75 | if [ x$2 == x"" ]; then 76 | echo "not found docker tag argument." 77 | return -1 78 | fi 79 | 80 | echo "starting to push ${PNAME}:${TAG_TMP} to ${DOCKER_REPO_TMP}/${PNAME}:${TAG_TMP}..." 81 | succ=0 82 | for i in `seq 1 3`; do 83 | docker push ${DOCKER_REPO_TMP}/${PNAME}:${TAG_TMP} 84 | if [ $? != 0 ];then 85 | echo "[$i times] docker push failed..." 86 | echo "trying to push ${PNAME}:${TAG_TMP} to ${DOCKER_REPO_TMP}/${PNAME}:${TAG_TMP}..." 87 | else 88 | echo "[$i times] docker push is successful..." 89 | succ=1 90 | break 91 | fi 92 | done 93 | 94 | 95 | if ( $succ == 0 ); then 96 | echo "failed to push docker images ${DOCKER_REPO_TMP}/${PNAME}:${TAG_TMP} at last..., exit now." 97 | exit -1 98 | fi 99 | } 100 | 101 | REGION="hangzhou" 102 | 103 | DOCKER_REPO="registry.cn-$REGION.aliyuncs.com/q_cloud_ll/qcsh-rep" 104 | 105 | echo "runing as region in $REGION.." 106 | tag $DOCKER_REPO 107 | if [ $? != 0 ];then 108 | echo "docker tag failed.." 109 | exit -1 110 | fi 111 | push $DOCKER_REPO latest 112 | push $DOCKER_REPO ${VERSION} 113 | clean $DOCKER_REPO 114 | 115 | 116 | 117 | echo "cleaning none tag images" 118 | 119 | #docker images|grep none|awk '{print $3}'|xargs docker rmi >/dev/null 2>&1 120 | 121 | docker images |grep $PNAME 122 | 123 | echo "build complete" 124 | -------------------------------------------------------------------------------- /cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "project/logger" 6 | "project/repository/cache" 7 | "project/repository/db/dao" 8 | "project/repository/track" 9 | "project/router" 10 | "project/setting" 11 | "project/setting/server" 12 | "project/utils/snowflake" 13 | ) 14 | 15 | // @title go_builder 16 | // @version 1.0 17 | // @description 基于Go Web 简易脚手架 18 | 19 | // @contact.name camellia 20 | // @contact.urls https://github.com/q-cloud-ll 21 | 22 | // @host 127.0.0.1:8888 23 | // @BasePath /api/v1 24 | 25 | func main() { 26 | loadingConfig() 27 | // 初始化注册路由 28 | r := router.SetupRouter() 29 | server.RunWindowServer(r) 30 | fmt.Println("Starting configuration success...") 31 | _ = r.Run(fmt.Sprintf(":%d", setting.Conf.Port)) 32 | } 33 | 34 | func loadingConfig() { 35 | setting.Init() 36 | logger.Init() 37 | dao.InitMysql() 38 | cache.InitRedis() 39 | //es.InitEs() 40 | //kafka.InitKafka() 41 | //rabbitmq.InitRabbitMQ() 42 | track.InitJaeger() 43 | snowflake.InitSnowflake() 44 | fmt.Println("Loading configuration success...") 45 | go scriptStarting() 46 | } 47 | 48 | func scriptStarting() { 49 | // start script 50 | } 51 | -------------------------------------------------------------------------------- /conf/config.docker.yaml: -------------------------------------------------------------------------------- 1 | name: "go_builder" 2 | mode: "dev" 3 | port: 8888 4 | db-type: "mysql" 5 | version: "v0.0.1" 6 | 7 | # jwt configuration 8 | jwt: 9 | signing-key: q-cloud-ll 10 | expires-time: 7d 11 | buffer-time: 1d 12 | issuer: q-cloud-ll 13 | 14 | cors: 15 | mode: strict-whitelist 16 | whitelist: 17 | - allow-origin: example1.com 18 | allow-headers: Content-Type,AccessToken,X-CSRF-Token, Authorization, Token,X-Token,X-User-Id 19 | allow-methods: POST, GET 20 | expose-headers: Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type 21 | 22 | allow-credentials: true # 布尔值 23 | - allow-origin: example2.com 24 | allow-headers: content-type 25 | allow-methods: GET, POST 26 | expose-headers: Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type 27 | allow-credentials: true # 布尔值 28 | 29 | snowflake: 30 | start_time: "2020-07-01" 31 | machine_id: 1 32 | 33 | log: 34 | level: "debug" 35 | filename: "go_builder.log" 36 | max_size: 200 37 | max_age: 30 38 | max_backups: 7 39 | mysql: 40 | host: 41 | port: 42 | user: 43 | password: 44 | dbname: 45 | max_open_conns: 46 | max_idle_conns: 47 | redis: 48 | host: 49 | port: 50 | password: "" 51 | db: 0 52 | pool_size: 100 -------------------------------------------------------------------------------- /conf/config.yaml: -------------------------------------------------------------------------------- 1 | name: "go_builder" 2 | mode: "dev" 3 | port: 8888 4 | db-type: "mysql" 5 | version: "v1.0.1" 6 | 7 | # jwt configuration 8 | jwt: 9 | signing-key: q-cloud-ll 10 | access-expire: 1d 11 | refresh-expire: 7d 12 | issuer: q-cloud-ll 13 | 14 | cors: 15 | mode: strict-whitelist 16 | whitelist: 17 | - allow-origin: example1.com 18 | allow-headers: Content-Type,AccessToken,X-CSRF-Token, Authorization, Token,X-Token,X-User-Id 19 | allow-methods: POST, GET 20 | expose-headers: Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type 21 | 22 | allow-credentials: true # 布尔值 23 | - allow-origin: example2.com 24 | allow-headers: content-type 25 | allow-methods: GET, POST 26 | expose-headers: Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type 27 | allow-credentials: true # 布尔值 28 | 29 | snowflake: 30 | start_time: "2020-07-01" 31 | machine_id: 1 32 | 33 | log: 34 | level: "debug" 35 | filename: "go_builder.log" 36 | max_size: 200 37 | max_age: 30 38 | max_backups: 7 39 | 40 | es: 41 | es_host: 127.0.0.1 42 | es_port: 9200 43 | es_index: mylog 44 | 45 | jaeger: 46 | addr: 127.0.0.1:6831 47 | type: const 48 | param: 1 49 | log-spans: true 50 | 51 | mysql: 52 | host: 127.0.0.1 53 | port: 3306 54 | user: root 55 | password: 11223344 56 | dbname: go_builder 57 | config: charset=utf8mb4&parseTime=true&loc=Local 58 | log-mode: debug 59 | log-zap: false 60 | max_idle_conns: 10 61 | max_open_conns: 100 62 | 63 | redis: 64 | addr: 127.0.0.1:6379 65 | password: 11223344 66 | db: 0 67 | pool_size: 100 68 | 69 | qiniu-oss: 70 | enable: oss 71 | zone: ZoneHuaDong 72 | bucket: moyuban-oss 73 | use-https: false 74 | access-key: 75 | secret-key: 76 | img-path: 77 | use-cdn-domains: false 78 | 79 | # 本地文件上传 80 | local: 81 | host: 82 | path: 83 | 84 | kafka: 85 | disableConsumer: 86 | debug: true 87 | address: 127.0.0.1:9092 88 | required-ack: -1 # 发送完数据后是否需要拿多少个副本确认 -1 需要全部 89 | read-timeout: 30 # 默认30s 90 | write-timeout: 30 # 默认30s 91 | max-open-requests: 5 # 在发送阻塞之前,允许有多少个未完成的请求,默认为5 92 | partition: 2 # 分区生成方案 0根据topic进行hash、1随机、2轮询 -------------------------------------------------------------------------------- /consts/common.go: -------------------------------------------------------------------------------- 1 | package consts 2 | 3 | import ( 4 | "errors" 5 | 6 | "golang.org/x/sync/singleflight" 7 | ) 8 | 9 | const ( 10 | ConfigEnv = "GB_CONFIG" 11 | ConfigDefaultFile = "conf/config.yaml" 12 | ConfigTestFile = "conf/config.test.yaml" 13 | ConfigReleaseFile = "conf/config.release.yaml" 14 | ) 15 | 16 | var ( 17 | TokenExpired = errors.New("token is expired") 18 | TokenMalformed = errors.New("that's not even a token") 19 | TokenInvalid = errors.New("couldn't handle this token") 20 | TokenNotValidYet = errors.New("token not active yet") 21 | SingleFlight = &singleflight.Group{} 22 | ) 23 | 24 | const ( 25 | AccessTokenHeader = "access_token" 26 | RefreshTokenHeader = "refresh_token" 27 | HeaderForwardedProto = "X-Forwarded-Proto" 28 | MaxAge = 3600 * 24 29 | ) 30 | 31 | const ( 32 | UploadMaxM = 10 33 | UploadMaxBytes int64 = 1024 * 1024 * 1024 * UploadMaxM 34 | ) 35 | 36 | const SpanCTX = "span-ctx" 37 | 38 | var ( 39 | GetTrackIdErr = errors.New("获取 track id 错误") 40 | ) 41 | 42 | const ( 43 | OtherSen = "./static/dictionary/其他词库.txt" 44 | ) 45 | -------------------------------------------------------------------------------- /consts/user.go: -------------------------------------------------------------------------------- 1 | package consts 2 | 3 | import "errors" 4 | 5 | var ( 6 | UserExistErr = errors.New("用户已存在") 7 | UserNotExistErr = errors.New("用户不存在") 8 | UserInvalidPasswordErr = errors.New("用户名或密码错误") 9 | UserInvalidIDErr = errors.New("无效的ID") 10 | UserInfoErr = errors.New("获取用户信息错误") 11 | UserCreateErr = errors.New("用户创建失败") 12 | ) 13 | -------------------------------------------------------------------------------- /deploy/filebeat/conf/filebeat.yml: -------------------------------------------------------------------------------- 1 | filebeat.inputs: 2 | - type: log 3 | enabled: true 4 | paths: 5 | - /var/lib/docker/containers/*/*-json.log 6 | - type: log 7 | enabled: true 8 | paths: 9 | - /usr/share/filebeat/*.log 10 | 11 | filebeat.config: 12 | modules: 13 | path: ${path.config}/modules.d/*.yml 14 | reload.enabled: false 15 | 16 | processors: 17 | - add_cloud_metadata: ~ 18 | - add_docker_metadata: ~ 19 | 20 | output.kafka: 21 | enabled: true 22 | hosts: ["kafka:9092"] 23 | #要提前创建topic 24 | topic: "go_builder-log" 25 | partition.hash: 26 | reachable_only: true 27 | compression: gzip 28 | max_message_bytes: 1000000 29 | required_acks: 1 30 | -------------------------------------------------------------------------------- /deploy/gencode/gencode.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 输入命令,such as ./gencode.sh comment 4 | template_name=$1 5 | 6 | # 定义目录路径数组 7 | dir_paths=( 8 | "../../repository/db/dao/" 9 | "../../repository/cache/" 10 | "../../service/svc/" 11 | "../../service/" 12 | ) 13 | 14 | # 循环遍历目录路径数组 15 | for dir_path in "${dir_paths[@]}"; do 16 | # 检查目录是否存在,如果不存在则创建目录 17 | if [ ! -d "$dir_path" ]; then 18 | mkdir -p "$dir_path" 19 | fi 20 | done 21 | 22 | 23 | # 将输入的comment更改为Comment 24 | template_name_upper="$(tr '[:lower:]' '[:upper:]' <<< "${template_name:0:1}")${template_name:1}" 25 | 26 | # dao包下模板 27 | template_dao="package dao 28 | 29 | import \"gorm.io/gorm\" 30 | 31 | var _ ${template_name_upper}Model = (*custom${template_name_upper}Model)(nil) 32 | 33 | type ( 34 | // ${template_name_upper}Model is an interface to be customized, add more methods here, 35 | // and implement the added methods in custom${template_name_upper}Model. 36 | ${template_name_upper}Model interface { 37 | } 38 | 39 | custom${template_name_upper}Model struct { 40 | *gorm.DB 41 | } 42 | ) 43 | 44 | func New${template_name_upper}Model() ${template_name_upper}Model { 45 | return &custom${template_name_upper}Model{ 46 | DB: NewDBClient(), 47 | } 48 | } 49 | " 50 | 51 | echo "$template_dao" > "${dir_paths[0]}/${template_name}.go" 52 | 53 | # cache包下模板 54 | template_cache="package cache 55 | 56 | import ( 57 | \"context\" 58 | \"github.com/go-redis/redis/v8\" 59 | ) 60 | 61 | var _ ${template_name_upper}Cache = (*custom${template_name_upper}Cache)(nil) 62 | 63 | type ( 64 | // ${template_name_upper}Cache is an interface to be customized, add more methods here, 65 | // and implement the added methods in custom${template_name_upper}Cache. 66 | ${template_name_upper}Cache interface { 67 | } 68 | custom${template_name_upper}Cache struct { 69 | *redis.Client 70 | } 71 | ) 72 | 73 | func New${template_name_upper}Cache() ${template_name_upper}Cache { 74 | return &custom${template_name_upper}Cache{ 75 | Client: RedisClient, 76 | } 77 | } 78 | " 79 | echo "$template_cache" > "${dir_paths[1]}/${template_name}.go" 80 | 81 | 82 | # svc包下模板 83 | template_svc="package svc 84 | 85 | import ( 86 | \"project/repository/cache\" 87 | \"project/repository/db/dao\" 88 | ) 89 | 90 | type ${template_name_upper}ServiceContext struct { 91 | ${template_name_upper}Model dao.${template_name_upper}Model 92 | ${template_name_upper}Cache cache.${template_name_upper}Cache 93 | } 94 | 95 | func New${template_name_upper}ServiceContext() *${template_name_upper}ServiceContext { 96 | return &${template_name_upper}ServiceContext{ 97 | ${template_name_upper}Model: dao.New${template_name_upper}Model(), 98 | ${template_name_upper}Cache: cache.New${template_name_upper}Cache(), 99 | } 100 | } 101 | " 102 | echo "$template_svc" > "${dir_paths[2]}/${template_name}.go" 103 | 104 | # service包下模板 105 | template_service="package service 106 | 107 | import ( 108 | \"context\" 109 | \"go.uber.org/zap\" 110 | \"project/logger\" 111 | \"project/service/svc\" 112 | ) 113 | 114 | 115 | type ${template_name_upper}Srv struct { 116 | ctx context.Context 117 | svcCtx *svc.${template_name_upper}ServiceContext 118 | log *zap.Logger 119 | } 120 | 121 | func New${template_name_upper}Service(ctx context.Context, svcCtx *svc.${template_name_upper}ServiceContext) *${template_name_upper}Srv { 122 | return &${template_name_upper}Srv{ 123 | ctx: ctx, 124 | svcCtx: svcCtx, 125 | log: logger.Lg, 126 | } 127 | }" 128 | 129 | # 生成模板代码 130 | echo "$template_service" > "${dir_paths[3]}/${template_name}.go" 131 | -------------------------------------------------------------------------------- /deploy/go-stash/etc/config.yaml: -------------------------------------------------------------------------------- 1 | Clusters: 2 | - Input: 3 | Kafka: 4 | Name: go-stash 5 | Brokers: 6 | - "kafka:9092" 7 | Topics: 8 | - go_builder-log 9 | Group: pro 10 | Consumers: 16 11 | Filters: 12 | - Action: drop 13 | Conditions: 14 | - Key: level 15 | Value: info 16 | Type: match 17 | Op: and 18 | - Action: remove_field 19 | Fields: 20 | # - message 21 | - _source 22 | - _type 23 | - _score 24 | - _id 25 | - "@version" 26 | - topic 27 | - index 28 | - beat 29 | - docker_container 30 | - offset 31 | - prospector 32 | - source 33 | - stream 34 | - "@metadata" 35 | - Action: transfer 36 | Field: message 37 | Target: data 38 | Output: 39 | ElasticSearch: 40 | Hosts: 41 | - "http://elasticsearch:9200" 42 | Index: "go_builder-{{yyyy-MM-dd}}" 43 | -------------------------------------------------------------------------------- /deploy/kubernetes/server/server-configMap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: config.yaml 5 | labels: 6 | app: go-builder-server 7 | data: 8 | config.yaml: | 9 | appid: go-builder-server 10 | cluster: default 11 | interval: 30s 12 | dns: "http://localhost:" 13 | secret: 14 | backup: "/data/apollo/go-builder-server/.agollo" 15 | streamenable: true 16 | close: false 17 | 18 | 19 | -------------------------------------------------------------------------------- /deploy/kubernetes/server/server-deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: go-builder-server 5 | spec: 6 | replicas: 100 7 | selector: 8 | matchLabels: 9 | env: prod 10 | app: go-builder-server 11 | template: 12 | metadata: 13 | labels: 14 | app: go-builder-server 15 | env: prod 16 | spec: 17 | terminationGracePeriodSeconds: 30 18 | containers: 19 | - name: go-builder-server 20 | image: registry.cn-hangzhou.aliyuncs.com/go-builder/server:latest 21 | livenessProbe: 22 | failureThreshold: 1 23 | periodSeconds: 5 24 | successThreshold: 1 25 | tcpSocket: 26 | port: 8888 27 | timeoutSeconds: 1 28 | readinessProbe: 29 | failureThreshold: 3 30 | initialDelaySeconds: 30 31 | periodSeconds: 5 32 | successThreshold: 1 33 | tcpSocket: 34 | port: 8888 35 | timeoutSeconds: 1 36 | ports: 37 | - name: http 38 | containerPort: 8888 39 | volumeMounts: 40 | - mountPath: /app/src/go_builder/conf 41 | name: config 42 | subPath: config.yaml 43 | resources: 44 | requests: 45 | memory: 4000Mi 46 | cpu: 4000m 47 | limits: 48 | memory: 4000Mi 49 | cpu: 4000m 50 | volumes: 51 | - name: config 52 | configMap: 53 | name: config.yaml 54 | 55 | minReadySeconds: 10 56 | strategy: 57 | type: RollingUpdate 58 | rollingUpdate: 59 | maxSurge: 5% 60 | maxUnavailable: 1% -------------------------------------------------------------------------------- /deploy/kubernetes/server/server-service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: go-builder-server 5 | labels: 6 | app: go-builder-server 7 | spec: 8 | selector: 9 | app: go-builder-server 10 | ports: 11 | - port: 8888 12 | name: http 13 | targetPort: 8888 14 | 15 | type: ClusterIP 16 | -------------------------------------------------------------------------------- /deploy/nginx/conf.d/go_builder-gateway.conf: -------------------------------------------------------------------------------- 1 | upstream go_builder { 2 | server 172.20.0.12:8888; # 流量转发的地址,这个是docker-compose内部的子网 3 | } 4 | 5 | server{ 6 | listen 8081; 7 | client_max_body_size 20m; 8 | server_name 127.0.0.1; # 你的服务器地址 9 | access_log /var/log/nginx/go_builder.com_access.log; 10 | error_log /var/log/nginx/go_builder.com_error.log; 11 | 12 | 13 | location ~ /api { 14 | proxy_set_header Host $http_host; 15 | proxy_set_header X-Real-IP $remote_addr; 16 | proxy_set_header REMOTE-HOST $remote_addr; 17 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 18 | proxy_pass http://go_builder; 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /deploy/sql/sys_user.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Navicat MySQL Data Transfer 3 | 4 | Source Server : seatManagementSys 5 | Source Server Type : MySQL 6 | Source Server Version : 80028 7 | Source Host : 127.0.0.1:3306 8 | Source Schema : sys_user 9 | 10 | Target Server Type : MySQL 11 | Target Server Version : 80028 12 | File Encoding : 65001 13 | 14 | Date: 06/10/2023 14:54:50 15 | */ 16 | 17 | SET NAMES utf8mb4; 18 | SET FOREIGN_KEY_CHECKS = 0; 19 | 20 | DROP TABLE IF EXISTS `sys_user`; 21 | CREATE TABLE `sys_user` ( 22 | `id` bigint NOT NULL AUTO_INCREMENT, 23 | `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, 24 | `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 25 | `delete_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, 26 | `del_state` tinyint NOT NULL DEFAULT '0', 27 | `user_id` bigint NOT NULL DEFAULT '0' COMMENT '用户id', 28 | `user_name` char(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户名', 29 | `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '密码', 30 | PRIMARY KEY (`id`), 31 | UNIQUE KEY `idx_user` (`user_id`) 32 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='用户表'; 33 | 34 | SET FOREIGN_KEY_CHECKS = 1; -------------------------------------------------------------------------------- /doc/elk-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/q-cloud-ll/go_builder/fc10a66173801aec5f1908016d8084dcbf0cae0b/doc/elk-flow.png -------------------------------------------------------------------------------- /docker-compose-env.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | networks: 4 | go_builder_net: 5 | driver: bridge 6 | ipam: 7 | config: 8 | - subnet: 172.12.0.0/16 9 | 10 | 11 | services: 12 | #zookeeper是kafka的依赖 - Zookeeper is the dependencies of Kafka 13 | zookeeper: 14 | image: wurstmeister/zookeeper 15 | container_name: zookeeper 16 | environment: 17 | # 时区上海 - Time zone Shanghai (Change if needed) 18 | TZ: Asia/Shanghai 19 | restart: always 20 | ports: 21 | - 2181:2181 22 | networks: 23 | - go_builder_net 24 | 25 | #消息队列 26 | kafka: 27 | image: wurstmeister/kafka 28 | container_name: kafka 29 | ports: 30 | - 9092:9092 31 | environment: 32 | KAFKA_ADVERTISED_HOST_NAME: kafka 33 | KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 34 | TZ: Asia/Shanghai 35 | restart: always 36 | volumes: 37 | - /var/run/docker.sock:/var/run/docker.sock 38 | networks: 39 | - go_builder_net 40 | depends_on: 41 | - zookeeper 42 | 43 | # #搜集kafka业务日志 - Kafka for collecting business logs 44 | elasticsearch: 45 | image: docker.elastic.co/elasticsearch/elasticsearch:7.13.4 46 | container_name: elasticsearch 47 | user: root 48 | environment: 49 | - discovery.type=single-node 50 | - "ES_JAVA_OPTS=-Xms512m -Xmx512m" 51 | - TZ=Asia/Shanghai 52 | volumes: 53 | - ./data/elasticsearch/data:/usr/share/elasticsearch/data 54 | restart: always 55 | ports: 56 | - 9200:9200 57 | - 9300:9300 58 | networks: 59 | - go_builder_net 60 | 61 | #收集业务数据 - Collect business data 62 | filebeat: 63 | image: elastic/filebeat:7.13.4 64 | container_name: filebeat 65 | environment: 66 | # 时区上海 - Time zone Shanghai (Change if needed) 67 | TZ: Asia/Shanghai 68 | user: root 69 | restart: always 70 | entrypoint: "filebeat -e -strict.perms=false" #解决配置文件权限问题 - Solving the configuration file permissions 71 | volumes: 72 | - ./deploy/filebeat/conf/filebeat.yml:/usr/share/filebeat/filebeat.yml 73 | # 此处需指定docker的containers目录,取决于你docker的配置 - The containers directory of docker needs to be specified here, depending on your docker configuration 74 | # 如snap安装的docker,则为/var/snap/docker/common/var-lib-docker/containers - Example if docker is installed by Snap /var/snap/docker/common/var-lib-docker/containers 75 | # - /var/snap/docker/common/var-lib-docker/containers:/var/lib/docker/containers 76 | - /var/lib/docker/containers:/var/lib/docker/containers 77 | - /Users/mac/Desktop/go_builder/go_builder.log:/usr/share/filebeat/go_builder.log 78 | networks: 79 | - go_builder_net 80 | depends_on: 81 | - kafka 82 | 83 | #消费kafka中filebeat收集的数据输出到es - The data output collected by FileBeat in Kafka is output to ES 84 | go-stash: 85 | image: kevinwan/go-stash:1.0 # if you "macOs intel" or "linux amd" 86 | # image: kevinwan/go-stash:1.0-arm64 # if you "macOs m1" or "linux arm" 87 | container_name: go-stash 88 | environment: 89 | # 时区上海 - Time zone Shanghai (Change if needed) 90 | TZ: Asia/Shanghai 91 | user: root 92 | restart: always 93 | volumes: 94 | - ./deploy/go-stash/etc:/app/etc 95 | networks: 96 | - go_builder_net 97 | depends_on: 98 | - elasticsearch 99 | - kafka 100 | 101 | #查看elasticsearch数据 - Kibana to view Elasticsearch data 102 | kibana: 103 | image: docker.elastic.co/kibana/kibana:7.13.4 104 | container_name: kibana 105 | environment: 106 | - elasticsearch.hosts=http://elasticsearch:9200 107 | - TZ=Asia/Shanghai 108 | restart: always 109 | networks: 110 | - go_builder_net 111 | ports: 112 | - "5601:5601" 113 | depends_on: 114 | - elasticsearch 115 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | # 声明一个名为forum_net的networks,subnet为forum_net的子网地址,默认网关是172.20.0.0 4 | networks: 5 | network: 6 | driver: bridge 7 | ipam: 8 | config: 9 | - subnet: 172.20.0.0/16 10 | 11 | # !!!!!!!!!!!!!!! <===注意事项===> !!!!!!!!!!!!!!! 12 | # 1、mysql:填写自己的database、password和 注意数据挂载改为自己的目录 13 | # 2、redis:数据挂载到自己的目录 ---- 注意command中的命令,里面是输入redis密码和开启aof,如果不需要可删除这行! 14 | # 3、最终实际项目访问端口是nginx代理出去的<-:9000->端口 15 | 16 | services: 17 | server: 18 | build: 19 | context: ./ 20 | dockerfile: ./Dockerfile 21 | container_name: go_builder_server 22 | restart: always 23 | depends_on: 24 | - mysql 25 | - redis 26 | links: 27 | - mysql 28 | - redis 29 | networks: 30 | network: 31 | ipv4_address: 172.20.0.12 32 | mysql: 33 | image: mysql:8.0.21 34 | container_name: go_builder_mysql 35 | environment: 36 | # 时区上海 - Time zone Shanghai (Change if needed) 37 | TZ: Asia/Shanghai 38 | MYSQL_DATABASE: '' 39 | # root 密码 - root password 40 | MYSQL_ROOT_PASSWORD: '' 41 | ports: 42 | - "13306:3306" 43 | volumes: 44 | # 数据挂载 - Data mounting 45 | - /Users/mac/docker/go_builder_mysql/data:/var/lib/mysql 46 | - /Users/mac/docker/go_builder_mysql/conf.d:/etc/mysql/conf.d 47 | # 日志 48 | command: 49 | # 将mysql8.0默认密码策略 修改为 原先 策略 (mysql8.0对其默认策略做了更改 会导致密码无法匹配) 50 | # Modify the Mysql 8.0 default password strategy to the original strategy (MySQL8.0 to change its default strategy will cause the password to be unable to match) 51 | --default-authentication-plugin=mysql_native_password 52 | --character-set-server=utf8mb4 53 | --collation-server=utf8mb4_general_ci 54 | --explicit_defaults_for_timestamp=true 55 | --lower_case_table_names=1 56 | privileged: true 57 | restart: always 58 | networks: 59 | network: 60 | ipv4_address: 172.20.0.13 61 | 62 | redis: 63 | image: redis:6.2.5 64 | container_name: go_builder_redis 65 | ports: 66 | - "16379:6379" 67 | environment: 68 | # 时区上海 - Time zone Shanghai (Change if needed) 69 | TZ: Asia/Shanghai 70 | volumes: 71 | - /Users/mac/docker/go_builder_redis/data:/data 72 | - /Users/mac/docker/go_builder_redis/redis.conf:/etc/redis/redis.conf 73 | command: "redis-server --requirepass your_password --appendonly yes" 74 | privileged: true 75 | restart: always 76 | networks: 77 | network: 78 | ipv4_address: 172.20.0.14 79 | 80 | nginx-gateway: 81 | image: nginx:1.21.5 82 | container_name: go_builder_nginx_gateway 83 | restart: always 84 | privileged: true 85 | environment: 86 | - TZ=Asia/Shanghai 87 | ports: 88 | - "9000:8081" 89 | volumes: 90 | - ./deploy/nginx/conf.d:/etc/nginx/conf.d 91 | - ./deploy/nginx/log:/var/log/nginx 92 | networks: 93 | network: 94 | ipv4_address: 172.20.0.15 95 | links: 96 | - server 97 | depends_on: 98 | - server -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module project 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/IBM/sarama v1.40.1 7 | github.com/bwmarrin/snowflake v0.3.0 8 | github.com/dgrijalva/jwt-go v3.2.0+incompatible 9 | github.com/elastic/go-elasticsearch v0.0.0 10 | github.com/fsnotify/fsnotify v1.6.0 11 | github.com/fvbock/endless v0.0.0-20170109170031-447134032cb6 12 | github.com/gin-contrib/sessions v0.0.5 13 | github.com/gin-gonic/gin v1.9.1 14 | github.com/go-playground/locales v0.14.1 15 | github.com/go-playground/universal-translator v0.18.1 16 | github.com/go-playground/validator/v10 v10.14.0 17 | github.com/go-redis/redis/v8 v8.11.5 18 | github.com/go-resty/resty/v2 v2.7.0 19 | github.com/go-sql-driver/mysql v1.7.0 20 | github.com/mlogclub/simple v1.2.17 21 | github.com/natefinch/lumberjack v2.0.0+incompatible 22 | github.com/olivere/elastic/v7 v7.0.32 23 | github.com/opentracing/opentracing-go v1.2.0 24 | github.com/qiniu/api.v7/v7 v7.8.2 25 | github.com/spf13/viper v1.16.0 26 | github.com/uber/jaeger-client-go v2.30.0+incompatible 27 | go.uber.org/zap v1.21.0 28 | golang.org/x/crypto v0.11.0 29 | golang.org/x/sync v0.3.0 30 | gorm.io/driver/mysql v1.5.1 31 | gorm.io/gorm v1.25.2 32 | gorm.io/plugin/dbresolver v1.4.1 33 | 34 | ) 35 | 36 | require ( 37 | github.com/HdrHistogram/hdrhistogram-go v1.1.2 // indirect 38 | github.com/bytedance/sonic v1.9.1 // indirect 39 | github.com/cespare/xxhash/v2 v2.1.2 // indirect 40 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect 41 | github.com/davecgh/go-spew v1.1.1 // indirect 42 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect 43 | github.com/eapache/go-resiliency v1.3.0 // indirect 44 | github.com/eapache/go-xerial-snappy v0.0.0-20230111030713-bf00bc1b83b6 // indirect 45 | github.com/eapache/queue v1.1.0 // indirect 46 | github.com/gabriel-vasile/mimetype v1.4.2 // indirect 47 | github.com/gin-contrib/sse v0.1.0 // indirect 48 | github.com/goccy/go-json v0.10.2 // indirect 49 | github.com/golang/snappy v0.0.4 // indirect 50 | github.com/gookit/color v1.3.6 // indirect 51 | github.com/gorilla/context v1.1.1 // indirect 52 | github.com/gorilla/securecookie v1.1.1 // indirect 53 | github.com/gorilla/sessions v1.2.1 // indirect 54 | github.com/hashicorp/errwrap v1.0.0 // indirect 55 | github.com/hashicorp/go-multierror v1.1.1 // indirect 56 | github.com/hashicorp/go-uuid v1.0.3 // indirect 57 | github.com/hashicorp/hcl v1.0.0 // indirect 58 | github.com/iris-contrib/go.uuid v2.0.0+incompatible // indirect 59 | github.com/jcmturner/aescts/v2 v2.0.0 // indirect 60 | github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect 61 | github.com/jcmturner/gofork v1.7.6 // indirect 62 | github.com/jcmturner/gokrb5/v8 v8.4.3 // indirect 63 | github.com/jcmturner/rpc/v2 v2.0.3 // indirect 64 | github.com/jinzhu/inflection v1.0.0 // indirect 65 | github.com/jinzhu/now v1.1.5 // indirect 66 | github.com/josharian/intern v1.0.0 // indirect 67 | github.com/json-iterator/go v1.1.12 // indirect 68 | github.com/klauspost/compress v1.16.6 // indirect 69 | github.com/klauspost/cpuid/v2 v2.2.4 // indirect 70 | github.com/leodido/go-urn v1.2.4 // indirect 71 | github.com/magiconair/properties v1.8.7 // indirect 72 | github.com/mailru/easyjson v0.7.7 // indirect 73 | github.com/mattn/go-isatty v0.0.19 // indirect 74 | github.com/mitchellh/mapstructure v1.5.0 // indirect 75 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 76 | github.com/modern-go/reflect2 v1.0.2 // indirect 77 | github.com/pelletier/go-toml/v2 v2.0.8 // indirect 78 | github.com/pierrec/lz4/v4 v4.1.17 // indirect 79 | github.com/pkg/errors v0.9.1 // indirect 80 | github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect 81 | github.com/spf13/afero v1.9.5 // indirect 82 | github.com/spf13/cast v1.5.1 // indirect 83 | github.com/spf13/jwalterweatherman v1.1.0 // indirect 84 | github.com/spf13/pflag v1.0.5 // indirect 85 | github.com/subosito/gotenv v1.4.2 // indirect 86 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 87 | github.com/uber/jaeger-lib v2.4.1+incompatible // indirect 88 | github.com/ugorji/go/codec v1.2.11 // indirect 89 | go.uber.org/atomic v1.9.0 // indirect 90 | go.uber.org/goleak v1.2.1 // indirect 91 | go.uber.org/multierr v1.8.0 // indirect 92 | golang.org/x/arch v0.3.0 // indirect 93 | golang.org/x/net v0.12.0 // indirect 94 | golang.org/x/sys v0.10.0 // indirect 95 | golang.org/x/text v0.11.0 // indirect 96 | google.golang.org/protobuf v1.30.0 // indirect 97 | gopkg.in/ini.v1 v1.67.0 // indirect 98 | gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect 99 | gopkg.in/yaml.v3 v3.0.1 // indirect 100 | ) 101 | -------------------------------------------------------------------------------- /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/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= 41 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 42 | github.com/HdrHistogram/hdrhistogram-go v1.1.2 h1:5IcZpTvzydCQeHzK4Ef/D5rrSqwxob0t8PQPMybUNFM= 43 | github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= 44 | github.com/IBM/sarama v1.40.1 h1:lL01NNg/iBeigUbT+wpPysuTYW6roHo6kc1QrffRf0k= 45 | github.com/IBM/sarama v1.40.1/go.mod h1:+5OFwA5Du9I6QrznhaMHsuwWdWZNMjaBSIxEWEgKOYE= 46 | github.com/Shopify/toxiproxy/v2 v2.5.0 h1:i4LPT+qrSlKNtQf5QliVjdP08GyAH8+BUIc9gT0eahc= 47 | github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= 48 | github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= 49 | github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= 50 | github.com/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0= 51 | github.com/bwmarrin/snowflake v0.3.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/E9WsDpxqwE= 52 | github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= 53 | github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= 54 | github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= 55 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 56 | github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= 57 | github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 58 | github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= 59 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= 60 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= 61 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 62 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 63 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 64 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 65 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 66 | github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= 67 | github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= 68 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 69 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 70 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 71 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 72 | github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= 73 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 74 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= 75 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= 76 | github.com/eapache/go-resiliency v1.3.0 h1:RRL0nge+cWGlxXbUzJ7yMcq6w2XBEr19dCN6HECGaT0= 77 | github.com/eapache/go-resiliency v1.3.0/go.mod h1:5yPzW0MIvSe0JDsv0v+DvcjEv2FyD6iZYSs1ZI+iQho= 78 | github.com/eapache/go-xerial-snappy v0.0.0-20230111030713-bf00bc1b83b6 h1:8yY/I9ndfrgrXUbOGObLHKBR4Fl3nZXwM2c7OYTT8hM= 79 | github.com/eapache/go-xerial-snappy v0.0.0-20230111030713-bf00bc1b83b6/go.mod h1:YvSRo5mw33fLEx1+DlK6L2VV43tJt5Eyel9n9XBcR+0= 80 | github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= 81 | github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= 82 | github.com/elastic/go-elasticsearch v0.0.0 h1:Pd5fqOuBxKxv83b0+xOAJDAkziWYwFinWnBO0y+TZaA= 83 | github.com/elastic/go-elasticsearch v0.0.0/go.mod h1:TkBSJBuTyFdBnrNqoPc54FN0vKf5c04IdM4zuStJ7xg= 84 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 85 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 86 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 87 | github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= 88 | github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= 89 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 90 | github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= 91 | github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= 92 | github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= 93 | github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= 94 | github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= 95 | github.com/fvbock/endless v0.0.0-20170109170031-447134032cb6 h1:6VSn3hB5U5GeA6kQw4TwWIWbOhtvR2hmbBJnTOtqTWc= 96 | github.com/fvbock/endless v0.0.0-20170109170031-447134032cb6/go.mod h1:YxOVT5+yHzKvwhsiSIWmbAYM3Dr9AEEbER2dVayfBkg= 97 | github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= 98 | github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= 99 | github.com/gin-contrib/sessions v0.0.5 h1:CATtfHmLMQrMNpJRgzjWXD7worTh7g7ritsQfmF+0jE= 100 | github.com/gin-contrib/sessions v0.0.5/go.mod h1:vYAuaUPqie3WUSsft6HUlCjlwwoJQs97miaG2+7neKY= 101 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= 102 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 103 | github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= 104 | github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= 105 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= 106 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 107 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 108 | github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= 109 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= 110 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= 111 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= 112 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= 113 | github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= 114 | github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= 115 | github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= 116 | github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= 117 | github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY= 118 | github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I= 119 | github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= 120 | github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= 121 | github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= 122 | github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= 123 | github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= 124 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= 125 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 126 | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 127 | github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 128 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 129 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 130 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 131 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= 132 | github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 133 | github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 134 | github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 135 | github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= 136 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 137 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 138 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 139 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 140 | github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 141 | github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= 142 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 143 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 144 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 145 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 146 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 147 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 148 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 149 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 150 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 151 | github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= 152 | github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 153 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 154 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 155 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 156 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 157 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 158 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 159 | github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 160 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 161 | github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 162 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 163 | github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 164 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 165 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 166 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 167 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= 168 | github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= 169 | github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= 170 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 171 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 172 | github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 173 | github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 174 | github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 175 | github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 176 | github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 177 | github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 178 | github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 179 | github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 180 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 181 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 182 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= 183 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= 184 | github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= 185 | github.com/gookit/color v1.3.6 h1:Rgbazd4JO5AgSTVGS3o0nvaSdwdrS8bzvIXwtK6OiMk= 186 | github.com/gookit/color v1.3.6/go.mod h1:R3ogXq2B9rTbXoSHJ1HyUVAZ3poOJHpd9nQmyGZsfvQ= 187 | github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= 188 | github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= 189 | github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= 190 | github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= 191 | github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI= 192 | github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= 193 | github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= 194 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 195 | github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= 196 | github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= 197 | github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 198 | github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= 199 | github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 200 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 201 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 202 | github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= 203 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 204 | github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 205 | github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 206 | github.com/iris-contrib/go.uuid v2.0.0+incompatible h1:XZubAYg61/JwnJNbZilGjf3b3pB80+OQg2qf6c8BfWE= 207 | github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/+fafWORmlnuysV2EMP8MW+qe0= 208 | github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= 209 | github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= 210 | github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo= 211 | github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM= 212 | github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg= 213 | github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo= 214 | github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o= 215 | github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg= 216 | github.com/jcmturner/gokrb5/v8 v8.4.3 h1:iTonLeSJOn7MVUtyMT+arAn5AKAPrkilzhGw8wE/Tq8= 217 | github.com/jcmturner/gokrb5/v8 v8.4.3/go.mod h1:dqRwJGXznQrzw6cWmyo6kH+E7jksEQG/CyVWsJEsJO0= 218 | github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY= 219 | github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= 220 | github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= 221 | github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= 222 | github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= 223 | github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= 224 | github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= 225 | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= 226 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 227 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 228 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 229 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 230 | github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= 231 | github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= 232 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 233 | github.com/klauspost/compress v1.16.6 h1:91SKEy4K37vkp255cJ8QesJhjyRO0hn9i9G0GoUwLsk= 234 | github.com/klauspost/compress v1.16.6/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= 235 | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= 236 | github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= 237 | github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= 238 | github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= 239 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 240 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 241 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 242 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 243 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 244 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 245 | github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= 246 | github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= 247 | github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= 248 | github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= 249 | github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= 250 | github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 251 | github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= 252 | github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 253 | github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= 254 | github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= 255 | github.com/mlogclub/simple v1.2.17 h1:Gq3dOxn7ISiwnv7PbO01Os6PotQMVoEqEoJ0nIJez0Y= 256 | github.com/mlogclub/simple v1.2.17/go.mod h1:s2BcovqQz2Ez2F1swZcG2A3Gr1+fSNs3mZoc9hvNOzU= 257 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 258 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 259 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 260 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 261 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 262 | github.com/natefinch/lumberjack v2.0.0+incompatible h1:4QJd3OLAMgj7ph+yZTuX13Ld4UpgHp07nNdFX7mqFfM= 263 | github.com/natefinch/lumberjack v2.0.0+incompatible/go.mod h1:Wi9p2TTF5DG5oU+6YfsmYQpsTIOm0B1VNzQg9Mw6nPk= 264 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= 265 | github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= 266 | github.com/olivere/elastic/v7 v7.0.32 h1:R7CXvbu8Eq+WlsLgxmKVKPox0oOwAE/2T9Si5BnvK6E= 267 | github.com/olivere/elastic/v7 v7.0.32/go.mod h1:c7PVmLe3Fxq77PIfY/bZmxY/TAamBhCzZ8xDOE09a9k= 268 | github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= 269 | github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= 270 | github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= 271 | github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= 272 | github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= 273 | github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= 274 | github.com/pierrec/lz4/v4 v4.1.17 h1:kV4Ip+/hUBC+8T6+2EgburRtkE9ef4nbY3f4dFhGjMc= 275 | github.com/pierrec/lz4/v4 v4.1.17/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= 276 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 277 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 278 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 279 | github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= 280 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 281 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 282 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 283 | github.com/qiniu/api.v7/v7 v7.8.2 h1:f08kI0MmsJNzK4sUS8bG3HDH67ktwd/ji23Gkiy2ra4= 284 | github.com/qiniu/api.v7/v7 v7.8.2/go.mod h1:FPsIqxh1Ym3X01sANE5ZwXfLZSWoCUp5+jNI8cLo3l0= 285 | github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= 286 | github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= 287 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 288 | github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= 289 | github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM= 290 | github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= 291 | github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= 292 | github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= 293 | github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= 294 | github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= 295 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 296 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 297 | github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc= 298 | github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg= 299 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 300 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 301 | github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= 302 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 303 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 304 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 305 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 306 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 307 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 308 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 309 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 310 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 311 | github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 312 | github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 313 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= 314 | github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= 315 | github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= 316 | github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= 317 | github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= 318 | github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o= 319 | github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= 320 | github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg= 321 | github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= 322 | github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= 323 | github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= 324 | github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 325 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 326 | github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 327 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 328 | github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 329 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= 330 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= 331 | go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 332 | go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 333 | go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 334 | go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= 335 | go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 336 | go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= 337 | go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 338 | go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= 339 | go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= 340 | go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= 341 | go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= 342 | go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= 343 | go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= 344 | go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8= 345 | go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= 346 | golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 347 | golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= 348 | golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 349 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 350 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 351 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 352 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 353 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 354 | golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= 355 | golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 356 | golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= 357 | golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= 358 | golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 359 | golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 360 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 361 | golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 362 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 363 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 364 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= 365 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= 366 | golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 367 | golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 368 | golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 369 | golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= 370 | golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= 371 | golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= 372 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 373 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 374 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 375 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 376 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 377 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 378 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 379 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 380 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 381 | golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= 382 | golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 383 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 384 | golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 385 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= 386 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= 387 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 388 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= 389 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 390 | golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 391 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 392 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 393 | golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 394 | golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 395 | golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 396 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 397 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 398 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 399 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 400 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 401 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 402 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 403 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 404 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 405 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 406 | golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 407 | golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 408 | golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 409 | golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 410 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 411 | golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 412 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 413 | golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 414 | golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 415 | golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 416 | golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 417 | golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 418 | golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 419 | golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 420 | golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 421 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 422 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 423 | golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 424 | golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 425 | golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 426 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 427 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= 428 | golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 429 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 430 | golang.org/x/net v0.0.0-20220725212005-46097bf591d3/go.mod h1:AaygXjzTFtRAg2ttMY5RMuhpJ3cNnI0XpyFJD1iQRSM= 431 | golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= 432 | golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= 433 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 434 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 435 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 436 | golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 437 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 438 | golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 439 | golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 440 | golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 441 | golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 442 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 443 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 444 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 445 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 446 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 447 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 448 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 449 | golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 450 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 451 | golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 452 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 453 | golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= 454 | golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= 455 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 456 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 457 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 458 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 459 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 460 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 461 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 462 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 463 | golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 464 | golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 465 | golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 466 | golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 467 | golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 468 | golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 469 | golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 470 | golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 471 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 472 | golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 473 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 474 | golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 475 | golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 476 | golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 477 | golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 478 | golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 479 | golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 480 | golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 481 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 482 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 483 | golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 484 | golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 485 | golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 486 | golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 487 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 488 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 489 | golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 490 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 491 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 492 | golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 493 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 494 | golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 495 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 496 | golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= 497 | golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 498 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 499 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 500 | golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 501 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 502 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 503 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 504 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 505 | golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 506 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 507 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 508 | golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= 509 | golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 510 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 511 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 512 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 513 | golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 514 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 515 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 516 | golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 517 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 518 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 519 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 520 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 521 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 522 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 523 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 524 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 525 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 526 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 527 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 528 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 529 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 530 | golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 531 | golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 532 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 533 | golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 534 | golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 535 | golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 536 | golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 537 | golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 538 | golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 539 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 540 | golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 541 | golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 542 | golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 543 | golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 544 | golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 545 | golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= 546 | golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= 547 | golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= 548 | golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 549 | golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 550 | golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 551 | golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 552 | golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 553 | golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 554 | golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 555 | golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= 556 | golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 557 | golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 558 | golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 559 | golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 560 | golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 561 | golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= 562 | golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 563 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 564 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 565 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 566 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 567 | gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= 568 | gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= 569 | gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= 570 | gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= 571 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= 572 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= 573 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 574 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 575 | google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 576 | google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 577 | google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 578 | google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 579 | google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 580 | google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 581 | google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 582 | google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 583 | google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= 584 | google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= 585 | google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= 586 | google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= 587 | google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= 588 | google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= 589 | google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= 590 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 591 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 592 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 593 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= 594 | google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 595 | google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 596 | google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 597 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 598 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 599 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 600 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 601 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 602 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 603 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 604 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= 605 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 606 | google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 607 | google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 608 | google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 609 | google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 610 | google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 611 | google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= 612 | google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 613 | google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 614 | google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 615 | google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 616 | google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 617 | google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 618 | google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 619 | google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 620 | google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= 621 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 622 | google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= 623 | google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 624 | google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 625 | google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 626 | google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 627 | google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 628 | google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 629 | google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 630 | google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 631 | google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 632 | google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 633 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 634 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= 635 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 636 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 637 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 638 | google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 639 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 640 | google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 641 | google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= 642 | google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= 643 | google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 644 | google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 645 | google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 646 | google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= 647 | google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= 648 | google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= 649 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 650 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 651 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 652 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 653 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 654 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 655 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 656 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 657 | google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= 658 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 659 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 660 | google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= 661 | google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 662 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 663 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 664 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 665 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 666 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 667 | gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= 668 | gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 669 | gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= 670 | gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= 671 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 672 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 673 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 674 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 675 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 676 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 677 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 678 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 679 | gorm.io/driver/mysql v1.4.3/go.mod h1:sSIebwZAVPiT+27jK9HIwvsqOGKx3YMPmrA3mBJR10c= 680 | gorm.io/driver/mysql v1.5.1 h1:WUEH5VF9obL/lTtzjmML/5e6VfFR/788coz2uaVCAZw= 681 | gorm.io/driver/mysql v1.5.1/go.mod h1:Jo3Xu7mMhCyj8dlrb3WoCaRd1FhsVh+yMXb1jUInf5o= 682 | gorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= 683 | gorm.io/gorm v1.24.3/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA= 684 | gorm.io/gorm v1.25.1/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= 685 | gorm.io/gorm v1.25.2 h1:gs1o6Vsa+oVKG/a9ElL3XgyGfghFfkKA2SInQaCyMho= 686 | gorm.io/gorm v1.25.2/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= 687 | gorm.io/plugin/dbresolver v1.4.1 h1:Ug4LcoPhrvqq71UhxtF346f+skTYoCa/nEsdjvHwEzk= 688 | gorm.io/plugin/dbresolver v1.4.1/go.mod h1:CTbCtMWhsjXSiJqiW2R8POvJ2cq18RVOl4WGyT5nhNc= 689 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 690 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 691 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 692 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 693 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 694 | honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 695 | honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 696 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= 697 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= 698 | rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= 699 | rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= 700 | -------------------------------------------------------------------------------- /logger/logger.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import ( 4 | "net" 5 | "net/http" 6 | "net/http/httputil" 7 | "os" 8 | "project/repository/es" 9 | "project/setting" 10 | "runtime/debug" 11 | "strings" 12 | "time" 13 | 14 | "github.com/gin-gonic/gin" 15 | "github.com/natefinch/lumberjack" 16 | "go.uber.org/zap" 17 | "go.uber.org/zap/zapcore" 18 | ) 19 | 20 | var Lg *zap.Logger 21 | 22 | // Init 初始化lg 23 | func Init() (err error) { 24 | lConfig := setting.Conf.LogConfig 25 | mode := setting.Conf.Mode 26 | 27 | writeSyncer := getLogWriter(lConfig.Filename, lConfig.MaxSize, lConfig.MaxBackups, lConfig.MaxAge) 28 | encoder := getEncoder() 29 | var l = new(zapcore.Level) 30 | err = l.UnmarshalText([]byte(lConfig.Level)) 31 | if err != nil { 32 | return 33 | } 34 | var core zapcore.Core 35 | if mode == "dev" { 36 | // 进入开发模式,日志输出到终端 37 | consoleEncoder := zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig()) 38 | core = zapcore.NewTee( 39 | zapcore.NewCore(encoder, writeSyncer, l), 40 | zapcore.NewCore(consoleEncoder, zapcore.Lock(os.Stdout), zapcore.DebugLevel), 41 | ) 42 | } else { 43 | hook := es.EsHookLog() 44 | core = zapcore.NewTee( 45 | hook, 46 | zapcore.NewCore(encoder, writeSyncer, l), 47 | ) 48 | } 49 | 50 | Lg = zap.New(core, zap.AddCaller()) 51 | zap.ReplaceGlobals(Lg) 52 | zap.L().Info("init logger success") 53 | 54 | return 55 | } 56 | 57 | func getEncoder() zapcore.Encoder { 58 | encoderConfig := zap.NewProductionEncoderConfig() 59 | encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder 60 | encoderConfig.TimeKey = "time" 61 | encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder 62 | encoderConfig.EncodeDuration = zapcore.SecondsDurationEncoder 63 | encoderConfig.EncodeCaller = zapcore.ShortCallerEncoder 64 | return zapcore.NewJSONEncoder(encoderConfig) 65 | } 66 | 67 | func getLogWriter(filename string, maxSize, maxBackup, maxAge int) zapcore.WriteSyncer { 68 | lumberJackLogger := &lumberjack.Logger{ 69 | Filename: filename, 70 | MaxSize: maxSize, 71 | MaxBackups: maxBackup, 72 | MaxAge: maxAge, 73 | } 74 | return zapcore.AddSync(lumberJackLogger) 75 | } 76 | 77 | // GinLogger 接收gin框架默认的日志 78 | func GinLogger() gin.HandlerFunc { 79 | return func(c *gin.Context) { 80 | start := time.Now() 81 | path := c.Request.URL.Path 82 | query := c.Request.URL.RawQuery 83 | c.Next() 84 | 85 | cost := time.Since(start) 86 | Lg.Info(path, 87 | zap.Int("status", c.Writer.Status()), 88 | zap.String("method", c.Request.Method), 89 | zap.String("path", path), 90 | zap.String("query", query), 91 | zap.String("ip", c.ClientIP()), 92 | zap.String("user-agent", c.Request.UserAgent()), 93 | zap.String("errors", c.Errors.ByType(gin.ErrorTypePrivate).String()), 94 | zap.Duration("cost", cost), 95 | ) 96 | } 97 | } 98 | 99 | // GinRecovery recover掉项目可能出现的panic,并使用zap记录相关日志 100 | func GinRecovery(stack bool) gin.HandlerFunc { 101 | return func(c *gin.Context) { 102 | defer func() { 103 | if err := recover(); err != nil { 104 | // Check for a broken connection, as it is not really a 105 | // condition that warrants a panic stack trace. 106 | var brokenPipe bool 107 | if ne, ok := err.(*net.OpError); ok { 108 | if se, ok := ne.Err.(*os.SyscallError); ok { 109 | if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") { 110 | brokenPipe = true 111 | } 112 | } 113 | } 114 | 115 | httpRequest, _ := httputil.DumpRequest(c.Request, false) 116 | if brokenPipe { 117 | Lg.Error(c.Request.URL.Path, 118 | zap.Any("error", err), 119 | zap.String("request", string(httpRequest)), 120 | ) 121 | // If the connection is dead, we can't write a status to it. 122 | c.Error(err.(error)) // nolint: errcheck 123 | c.Abort() 124 | return 125 | } 126 | 127 | if stack { 128 | Lg.Error("[Recovery from panic]", 129 | zap.Any("error", err), 130 | zap.String("request", string(httpRequest)), 131 | zap.String("stack", string(debug.Stack())), 132 | ) 133 | } else { 134 | Lg.Error("[Recovery from panic]", 135 | zap.Any("error", err), 136 | zap.String("request", string(httpRequest)), 137 | ) 138 | } 139 | c.AbortWithStatus(http.StatusInternalServerError) 140 | } 141 | }() 142 | c.Next() 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /middlewares/cors.go: -------------------------------------------------------------------------------- 1 | package middlewares 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | // Cors 放行全部请求的中间件, 如果需要走白名单可去yaml文件配置,写后续中间件 10 | func Cors() gin.HandlerFunc { 11 | return func(c *gin.Context) { 12 | method := c.Request.Method 13 | origin := c.Request.Header.Get("Origin") 14 | c.Header("Access-Control-Allow-Origin", origin) 15 | c.Header("Access-Control-Allow-Headers", "Content-Type,AccessToken,X-CSRF-Token, Authorization, Token,X-Token,X-User-Id") 16 | c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PUT") 17 | c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type, New-Token, New-Expires-At") 18 | c.Header("Access-Control-Allow-Credentials", "true") 19 | 20 | // 放行所有OPTIONS方法 21 | if method == "OPTIONS" { 22 | c.AbortWithStatus(http.StatusNoContent) 23 | } 24 | // 处理请求 25 | c.Next() 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /middlewares/jwt.go: -------------------------------------------------------------------------------- 1 | package middlewares 2 | 3 | import ( 4 | "project/consts" 5 | "project/utils/app" 6 | "project/utils/jwt" 7 | 8 | "github.com/gin-gonic/gin" 9 | ) 10 | 11 | func JWTAuth() gin.HandlerFunc { 12 | return func(c *gin.Context) { 13 | accessToken := c.GetHeader("access_token") 14 | refreshToken := c.GetHeader("refresh_token") 15 | if accessToken == "" { 16 | app.ResponseErrorWithCodeMsg(c, app.CodeNoLogin, "未登陆") 17 | c.Abort() 18 | return 19 | } 20 | j := jwt.NewJWT() 21 | newAccessToken, newRefreshToken, err := j.ParseRefreshToken(accessToken, refreshToken) 22 | if err != nil { 23 | app.ResponseErrorWithCodeMsg(c, app.CodeLoginExpire, err.Error()) 24 | c.Abort() 25 | return 26 | } 27 | claims, err := j.ParseToken(newAccessToken) 28 | if err != nil { 29 | app.ResponseErrorWithCodeMsg(c, app.CodeErrorAuthCheckTokenFail, err.Error()) 30 | c.Abort() 31 | return 32 | } 33 | SetToken(c, newAccessToken, newRefreshToken) 34 | c.Request = c.Request.WithContext(app.NewContext(c.Request.Context(), &app.UserInfo{Username: claims.Username, ID: claims.ID, UID: claims.UID})) 35 | 36 | c.Next() 37 | } 38 | } 39 | 40 | func SetToken(c *gin.Context, accessToken, refreshToken string) { 41 | secure := IsHttps(c) 42 | c.Header(consts.AccessTokenHeader, accessToken) 43 | c.Header(consts.RefreshTokenHeader, refreshToken) 44 | c.SetCookie(consts.AccessTokenHeader, accessToken, consts.MaxAge, "/", "", secure, true) 45 | c.SetCookie(consts.RefreshTokenHeader, refreshToken, consts.MaxAge, "/", "", secure, true) 46 | } 47 | 48 | func IsHttps(c *gin.Context) bool { 49 | if c.GetHeader(consts.HeaderForwardedProto) == "https" || c.Request.TLS != nil { 50 | return true 51 | } 52 | return false 53 | } 54 | -------------------------------------------------------------------------------- /middlewares/track.go: -------------------------------------------------------------------------------- 1 | package middlewares 2 | 3 | import ( 4 | "project/consts" 5 | "project/repository/track" 6 | 7 | "github.com/gin-gonic/gin" 8 | "github.com/opentracing/opentracing-go" 9 | ) 10 | 11 | func Jaeger() gin.HandlerFunc { 12 | return func(c *gin.Context) { 13 | traceId := c.GetHeader("uber-trace-id") 14 | var span opentracing.Span 15 | if traceId != "" { 16 | var err error 17 | span, err = track.GetParentSpan(c.FullPath(), traceId, c.Request.Header) 18 | if err != nil { 19 | return 20 | } 21 | } else { 22 | span = track.StartSpan(opentracing.GlobalTracer(), c.FullPath()) 23 | } 24 | defer span.Finish() 25 | 26 | c.Set(consts.SpanCTX, opentracing.ContextWithSpan(c, span)) 27 | c.Next() 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /repository/cache/key.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | // PreFix 项目redis统一前缀 4 | const PreFix = "go_builder:" 5 | 6 | // getRedisKey 给key加上前缀 7 | func getRedisKey(key string) string { 8 | return PreFix + key 9 | } 10 | -------------------------------------------------------------------------------- /repository/cache/redis.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "context" 5 | "os" 6 | "project/setting" 7 | "sync" 8 | 9 | "github.com/go-redis/redis/v8" 10 | "go.uber.org/zap" 11 | ) 12 | 13 | var ( 14 | once sync.Once 15 | RedisClient *redis.Client 16 | RedisContext = context.Background() 17 | ) 18 | 19 | func InitRedis() { 20 | rConfig := setting.Conf.RedisConfig 21 | once.Do(func() { 22 | RedisClient = redis.NewClient(&redis.Options{ 23 | Addr: rConfig.Addr, 24 | Password: rConfig.Password, 25 | DB: rConfig.DB, 26 | }) 27 | pong, err := RedisClient.Ping(RedisContext).Result() 28 | if err != nil { 29 | zap.L().Error("redis connect ping failed, err:", zap.Error(err)) 30 | os.Exit(0) 31 | return 32 | } else { 33 | zap.L().Info("redis connect ping response:", zap.String("pong", pong)) 34 | } 35 | }) 36 | } 37 | -------------------------------------------------------------------------------- /repository/db/dao/migrate.go: -------------------------------------------------------------------------------- 1 | package dao 2 | 3 | import "project/repository/db/model" 4 | 5 | func migrate() (err error) { 6 | err = _db.Set("gorm:table_options", "charset=utf8mb4"). 7 | AutoMigrate( 8 | model.User{}, 9 | ) 10 | 11 | return 12 | } 13 | -------------------------------------------------------------------------------- /repository/db/dao/mysql.go: -------------------------------------------------------------------------------- 1 | package dao 2 | 3 | import ( 4 | "project/setting" 5 | "strings" 6 | "time" 7 | 8 | "github.com/gin-gonic/gin" 9 | "gorm.io/driver/mysql" 10 | "gorm.io/gorm/logger" 11 | "gorm.io/gorm/schema" 12 | "gorm.io/plugin/dbresolver" 13 | 14 | "gorm.io/gorm" 15 | ) 16 | 17 | var _db *gorm.DB 18 | 19 | func InitMysql() { 20 | pathRead := strings.Join([]string{setting.Conf.MySQLConfig.User, ":", setting.Conf.MySQLConfig.Password, "@tcp(", setting.Conf.MySQLConfig.Host, ":", setting.Conf.MySQLConfig.Port, ")/", setting.Conf.MySQLConfig.DbName, "?", setting.Conf.MySQLConfig.Config}, "") 21 | pathWrite := strings.Join([]string{setting.Conf.MySQLConfig.User, ":", setting.Conf.MySQLConfig.Password, "@tcp(", setting.Conf.MySQLConfig.Host, ":", setting.Conf.MySQLConfig.Port, ")/", setting.Conf.MySQLConfig.DbName, "?", setting.Conf.MySQLConfig.Config}, "") 22 | 23 | var ormLogger logger.Interface 24 | if gin.Mode() == "debug" { 25 | ormLogger = logger.Default.LogMode(logger.Info) 26 | } else { 27 | ormLogger = logger.Default 28 | } 29 | 30 | mysqlCfg := mysql.Config{ 31 | DSN: pathRead, // DSN data source name 32 | DefaultStringSize: 191, // string 类型字段的默认长度 33 | DisableDatetimePrecision: true, // 禁用 datetime 精度,MySQL 5.6 之前的数据库不支持 34 | DontSupportRenameIndex: true, // 重命名索引时采用删除并新建的方式,MySQL 5.7 之前的数据库和 MariaDB 不支持重命名索引 35 | DontSupportRenameColumn: true, // 用 `change` 重命名列,MySQL 8 之前的数据库和 MariaDB 不支持重命名列 36 | SkipInitializeWithVersion: false, // 根据当前 MySQL 版本自动配置 37 | } 38 | db, err := gorm.Open(mysql.New(mysqlCfg), &gorm.Config{ 39 | Logger: ormLogger, 40 | NamingStrategy: schema.NamingStrategy{ 41 | SingularTable: true, 42 | }, 43 | }) 44 | if err != nil { 45 | panic(err) 46 | } 47 | sqlDB, _ := db.DB() 48 | sqlDB.SetMaxIdleConns(setting.Conf.MaxIdleConns) 49 | sqlDB.SetMaxOpenConns(setting.Conf.MaxOpenConns) 50 | sqlDB.SetConnMaxLifetime(time.Second * 30) 51 | _db = db 52 | _ = _db.Use(dbresolver.Register( 53 | dbresolver.Config{ 54 | Sources: []gorm.Dialector{mysql.Open(pathRead)}, 55 | Replicas: []gorm.Dialector{mysql.Open(pathWrite), mysql.Open(pathWrite)}, 56 | Policy: dbresolver.RandomPolicy{}, 57 | })) 58 | _db = _db.Set("gorm:table_options", "charset=utf8mb4") 59 | err = migrate() 60 | if err != nil { 61 | panic(err) 62 | } 63 | } 64 | 65 | //func NewDBClient(ctx context.Context) *gorm.DB { 66 | // db := _db 67 | // return db.WithContext(ctx) 68 | //} 69 | 70 | func NewDBClient() *gorm.DB { 71 | return _db 72 | } 73 | -------------------------------------------------------------------------------- /repository/db/dao/user.go: -------------------------------------------------------------------------------- 1 | package dao 2 | 3 | import ( 4 | "context" 5 | "project/repository/db/model" 6 | 7 | "gorm.io/gorm" 8 | ) 9 | 10 | var _ UserModel = (*customUserModel)(nil) 11 | 12 | type ( 13 | // UserModel is an interface to be customized, add more methods here, 14 | // and implement the added methods in customUserModel. 15 | UserModel interface { 16 | CreateUser(ctx context.Context, user *model.User) error 17 | ExistOrNotByUserName(ctx context.Context, userName string) (user *model.User, exist bool, err error) 18 | } 19 | 20 | customUserModel struct { 21 | *gorm.DB 22 | } 23 | ) 24 | 25 | func NewUserModel() UserModel { 26 | return &customUserModel{ 27 | DB: NewDBClient(), 28 | } 29 | } 30 | 31 | func (m *customUserModel) CreateUser(ctx context.Context, user *model.User) error { 32 | return m.DB.WithContext(ctx).Model(&model.User{}).Create(&user).Error 33 | } 34 | 35 | // ExistOrNotByUserName 根据username判断是否存在该名字 36 | func (m *customUserModel) ExistOrNotByUserName(ctx context.Context, userName string) (user *model.User, exist bool, err error) { 37 | var count int64 38 | err = m.DB.WithContext(ctx).Model(&model.User{}).Where("user_name = ?", userName).Count(&count).Error 39 | if count == 0 { 40 | return user, false, err 41 | } 42 | err = m.DB.Model(&model.User{}).Where("user_name = ?", userName).First(&user).Error 43 | if err != nil { 44 | return user, false, err 45 | } 46 | return user, true, nil 47 | } 48 | -------------------------------------------------------------------------------- /repository/db/model/user.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "golang.org/x/crypto/bcrypt" 5 | "gorm.io/gorm" 6 | ) 7 | 8 | // User 用户模型 9 | type User struct { 10 | gorm.Model 11 | UserId int64 `gorm:"not null"` 12 | UserName string `gorm:"unique"` 13 | Email string 14 | PasswordDigest string 15 | NickName string 16 | Status string 17 | Avatar string `gorm:"size:1000"` 18 | Relations []User `gorm:"many2many:relation;"` 19 | } 20 | 21 | const ( 22 | PassWordCost = 12 // 密码加密难度 23 | Active string = "active" // 激活用户 24 | ) 25 | 26 | // SetPassword 设置密码 27 | func (u *User) SetPassword(password string) error { 28 | bytes, err := bcrypt.GenerateFromPassword([]byte(password), PassWordCost) 29 | if err != nil { 30 | return err 31 | } 32 | u.PasswordDigest = string(bytes) 33 | return nil 34 | } 35 | 36 | // CheckPassword 校验密码 37 | func (u *User) CheckPassword(password string) bool { 38 | err := bcrypt.CompareHashAndPassword([]byte(u.PasswordDigest), []byte(password)) 39 | return err == nil 40 | } 41 | -------------------------------------------------------------------------------- /repository/es/es.go: -------------------------------------------------------------------------------- 1 | package es 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "project/setting" 8 | "time" 9 | 10 | "go.uber.org/zap" 11 | 12 | "go.uber.org/zap/zapcore" 13 | 14 | elastic "github.com/elastic/go-elasticsearch" 15 | elastic7 "github.com/olivere/elastic/v7" 16 | ) 17 | 18 | var ( 19 | EsClient *elastic.Client 20 | ) 21 | 22 | func InitEs() { 23 | esConn := fmt.Sprintf("http://%s:%s", setting.Conf.EsConfig.EsHost, setting.Conf.EsConfig.EsPort) 24 | cfg := elastic.Config{ 25 | Addresses: []string{esConn}, 26 | } 27 | 28 | client, err := elastic.NewClient(cfg) 29 | if err != nil { 30 | log.Panic(err) 31 | } 32 | 33 | EsClient = client 34 | } 35 | 36 | //func EsHookLog() *elogrus.ElasticHook { 37 | // eConfig := setting.Conf.EsConfig 38 | // hook, err := elogrus.NewElasticHook(EsClient, eConfig.EsHost, zap.InfoLevel, eConfig.EsIndex) 39 | // if err != nil { 40 | // log.Panic(err) 41 | // } 42 | // return hook 43 | //} 44 | 45 | func EsHookLog() zapcore.Core { 46 | eConfig := setting.Conf.EsConfig 47 | client, err := elastic7.NewClient(elastic7.SetURL(eConfig.EsHost)) 48 | if err != nil { 49 | log.Panic(err) 50 | } 51 | esEncoder := zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()) 52 | esWriteSyncer := NewElasticsearchWriteSyncer(client, eConfig.EsIndex) 53 | 54 | return zapcore.NewCore(esEncoder, esWriteSyncer, zap.InfoLevel) 55 | } 56 | 57 | func NewElasticsearchWriteSyncer(client *elastic7.Client, index string) zapcore.WriteSyncer { 58 | return &ElasticsearchWriteSyncer{ 59 | client: client, 60 | index: index, 61 | } 62 | } 63 | 64 | type ElasticsearchWriteSyncer struct { 65 | client *elastic7.Client 66 | index string 67 | } 68 | 69 | func (w ElasticsearchWriteSyncer) Write(p []byte) (n int, err error) { 70 | //TODO implement me 71 | ctx := context.Background() 72 | indexName := w.index + "-" + time.Now().Format("2006.01.02") 73 | _, err = w.client.Index(). 74 | Index(indexName). 75 | BodyString(string(p)). 76 | Do(ctx) 77 | if err != nil { 78 | return 0, err 79 | } 80 | 81 | return len(p), nil 82 | } 83 | 84 | func (e ElasticsearchWriteSyncer) Sync() error { 85 | //TODO implement me 86 | panic("implement me") 87 | } 88 | -------------------------------------------------------------------------------- /repository/kafka/consumer.go: -------------------------------------------------------------------------------- 1 | package kafka 2 | 3 | import ( 4 | "context" 5 | "strings" 6 | 7 | "github.com/IBM/sarama" 8 | "go.uber.org/zap" 9 | ) 10 | 11 | // Consumer 消费者函数 12 | func Consumer(ctx context.Context, key, topic string, fn func(message *sarama.ConsumerMessage) error) (err error) { 13 | kafka, err := GetClient(key) 14 | if err != nil { 15 | return 16 | } 17 | partitions, err := kafka.Consumer.Partitions(topic) 18 | if err != nil { 19 | return 20 | } 21 | for _, partition := range partitions { 22 | // 针对每个分区创建一个对应的分区消费者 23 | offset, errx := kafka.Client.GetOffset(topic, partition, sarama.OffsetNewest) 24 | if errx != nil { 25 | zap.L().Info("获取Offset失败:", zap.Error(errx)) 26 | continue 27 | } 28 | pc, errx := kafka.Consumer.ConsumePartition(topic, partition, offset-1) 29 | if errx != nil { 30 | zap.L().Info("获取Offset失败:", zap.Error(errx)) 31 | return nil 32 | } 33 | // 从每个分区都消费消息 34 | go func(consume sarama.PartitionConsumer) { 35 | defer func() { 36 | if err := recover(); err != nil { 37 | zap.L().Error("消费kafka信息发生panic,err:%s", zap.Any("err:", err)) 38 | } 39 | }() 40 | 41 | defer func() { 42 | err := pc.Close() 43 | if err != nil { 44 | zap.L().Error("消费kafka信息发生panic,err:%s", zap.Any("err:", err)) 45 | } 46 | }() 47 | 48 | for { 49 | select { 50 | case msg := <-pc.Messages(): 51 | err := MiddlewareConsumerHandler(fn)(msg) 52 | if err != nil { 53 | return 54 | } 55 | case <-ctx.Done(): 56 | return 57 | } 58 | } 59 | 60 | }(pc) 61 | } 62 | return nil 63 | } 64 | 65 | // ConsumerGroup 消费者组消费消息 66 | func ConsumerGroup(ctx context.Context, key, groupId, topics string, msgHandler ConsumerGroupHandler) (err error) { 67 | kafka, err := GetClient(key) 68 | if err != nil { 69 | return 70 | } 71 | 72 | if isConsumerDisabled(kafka) { 73 | return 74 | } 75 | 76 | consumerGroup, err := sarama.NewConsumerGroupFromClient(groupId, kafka.Client) 77 | if err != nil { 78 | return 79 | } 80 | 81 | go func() { 82 | defer func() { 83 | if err := recover(); err != nil { 84 | zap.L().Error("消费kafka发生panic", zap.Any("panic", err)) 85 | } 86 | }() 87 | 88 | defer func() { 89 | err := consumerGroup.Close() 90 | if err != nil { 91 | zap.L().Error("close err", zap.Any("panic", err)) 92 | } 93 | }() 94 | 95 | for { 96 | select { 97 | case <-ctx.Done(): 98 | return 99 | default: 100 | err := consumerGroup.Consume(ctx, strings.Split(topics, ","), ConsumerGroupHandler(func(msg *sarama.ConsumerMessage) error { 101 | return MiddlewareConsumerHandler(msgHandler)(msg) 102 | })) 103 | if err != nil { 104 | zap.L().Error("消费kafka失败 err", zap.Any("panic", err)) 105 | 106 | } 107 | } 108 | } 109 | }() 110 | 111 | return 112 | } 113 | 114 | func isConsumerDisabled(in *Kafka) bool { 115 | if in.DisableConsumer { 116 | zap.L().Info("kafka consumer disabled,key:%s", zap.String("key", in.Key)) 117 | } 118 | 119 | return in.DisableConsumer 120 | } 121 | 122 | func MiddlewareConsumerHandler(fn func(message *sarama.ConsumerMessage) error) func(message *sarama.ConsumerMessage) error { 123 | return func(msg *sarama.ConsumerMessage) error { 124 | return fn(msg) 125 | } 126 | } 127 | 128 | type ConsumerGroupHandler func(message *sarama.ConsumerMessage) error 129 | 130 | func (ConsumerGroupHandler) Setup(_ sarama.ConsumerGroupSession) error { 131 | return nil 132 | } 133 | 134 | func (ConsumerGroupHandler) Cleanup(_ sarama.ConsumerGroupSession) error { 135 | return nil 136 | } 137 | 138 | func (h ConsumerGroupHandler) ConsumeClaim(sess sarama.ConsumerGroupSession, claim sarama.ConsumerGroupClaim) error { 139 | for msg := range claim.Messages() { 140 | if err := h(msg); err != nil { 141 | zap.L().Info("消息处理失败", 142 | zap.String("topic", msg.Topic), 143 | zap.String("value", string(msg.Value))) 144 | continue 145 | } 146 | sess.MarkMessage(msg, "") 147 | } 148 | 149 | return nil 150 | } 151 | -------------------------------------------------------------------------------- /repository/kafka/kafka.go: -------------------------------------------------------------------------------- 1 | package kafka 2 | 3 | import ( 4 | "fmt" 5 | "project/setting" 6 | "strings" 7 | "sync" 8 | "time" 9 | 10 | "go.uber.org/zap" 11 | 12 | "github.com/IBM/sarama" 13 | ) 14 | 15 | type Kafka struct { 16 | Key string 17 | DisableConsumer bool 18 | Debug bool 19 | Producer sarama.SyncProducer 20 | Consumer sarama.Consumer 21 | Client sarama.Client 22 | } 23 | 24 | var kafkaClient = new(sync.Map) 25 | 26 | func InitKafka() { 27 | for k, v := range setting.Conf.KafKa { 28 | key := k 29 | val := v 30 | scfg := buildConfig(val) 31 | kafka, err := newKafkaClient(key, val, scfg) 32 | if err != nil { 33 | zap.L().Error("newKafkaClient(key, val, scfg) failed:", zap.Error(err)) 34 | return 35 | } 36 | kafkaClient.Store(key, kafka) 37 | } 38 | } 39 | 40 | func GetClient(key string) (*Kafka, error) { 41 | val, ok := kafkaClient.Load(key) 42 | if !ok { 43 | return nil, fmt.Errorf("获取kafka client失败,key:%s", key) 44 | } 45 | 46 | return val.(*Kafka), nil 47 | } 48 | 49 | func buildConfig(v *setting.KafkaConfig) *sarama.Config { 50 | cfg := sarama.NewConfig() 51 | cfg.Producer.RequiredAcks = sarama.RequiredAcks(v.RequiredAck) 52 | cfg.Producer.Return.Successes = true 53 | 54 | if v.Partition == 1 { 55 | cfg.Producer.Partitioner = sarama.NewRandomPartitioner 56 | } 57 | 58 | if v.Partition == 2 { 59 | cfg.Producer.Partitioner = sarama.NewRoundRobinPartitioner 60 | } 61 | 62 | if v.ReadTimeout != 0 { 63 | cfg.Net.ReadTimeout = time.Duration(v.ReadTimeout) * time.Second 64 | } 65 | 66 | if v.WriteTimeout != 0 { 67 | cfg.Net.WriteTimeout = time.Duration(v.WriteTimeout) * time.Second 68 | } 69 | 70 | if v.MaxOpenRequests != 0 { 71 | cfg.Net.MaxOpenRequests = v.MaxOpenRequests 72 | } 73 | 74 | return cfg 75 | } 76 | 77 | func newKafkaClient(key string, cfg *setting.KafkaConfig, scfg *sarama.Config) (*Kafka, error) { 78 | client, err := sarama.NewClient(strings.Split(cfg.Address, ","), scfg) 79 | if err != nil { 80 | return nil, err 81 | } 82 | 83 | syncProducer, err := sarama.NewSyncProducer(strings.Split(cfg.Address, ","), scfg) 84 | if err != nil { 85 | return nil, err 86 | } 87 | 88 | consumer, err := sarama.NewConsumer(strings.Split(cfg.Address, ","), scfg) 89 | if err != nil { 90 | return nil, err 91 | } 92 | 93 | return &Kafka{ 94 | Key: key, 95 | DisableConsumer: cfg.DisableConsumer, 96 | Debug: cfg.Debug, 97 | Producer: syncProducer, 98 | Consumer: consumer, 99 | Client: client, 100 | }, nil 101 | 102 | } 103 | -------------------------------------------------------------------------------- /repository/kafka/producer.go: -------------------------------------------------------------------------------- 1 | package kafka 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/IBM/sarama" 7 | "go.uber.org/zap" 8 | ) 9 | 10 | // SendMessage 发送消息,默认分区 11 | func SendMessage(key, topic, value string) error { 12 | return SendMessagePartitionPar(key, topic, value, "") 13 | } 14 | 15 | // SendMessagePartitionPar 发送消息,指定分区 16 | func SendMessagePartitionPar(key, topic, value, partitionKey string) error { 17 | kafka, err := GetClient(key) 18 | if err != nil { 19 | return err 20 | } 21 | 22 | msg := &sarama.ProducerMessage{ 23 | Topic: topic, 24 | Value: sarama.StringEncoder(value), 25 | Timestamp: time.Now(), 26 | } 27 | 28 | if partitionKey != "" { 29 | msg.Key = sarama.StringEncoder(partitionKey) 30 | } 31 | partition, offset, err := kafka.Producer.SendMessage(msg) 32 | if err != nil { 33 | return err 34 | } 35 | if kafka.Debug { 36 | zap.L().Info("发送kafka消息成功", 37 | zap.Int32("partition", partition), 38 | zap.Int64("offset", offset)) 39 | } 40 | 41 | return err 42 | } 43 | -------------------------------------------------------------------------------- /repository/track/init.go: -------------------------------------------------------------------------------- 1 | package track 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "project/setting" 9 | 10 | "github.com/opentracing/opentracing-go/ext" 11 | 12 | "github.com/opentracing/opentracing-go" 13 | "github.com/uber/jaeger-client-go" 14 | "github.com/uber/jaeger-client-go/config" 15 | ) 16 | 17 | func GetDefaultConfig() *config.Configuration { 18 | jConfig := setting.Conf.JaegerConfig 19 | cfg := &config.Configuration{ 20 | Sampler: &config.SamplerConfig{ 21 | Type: jConfig.Type, 22 | Param: jConfig.Param, 23 | }, 24 | Reporter: &config.ReporterConfig{ 25 | LogSpans: jConfig.LogSpans, 26 | LocalAgentHostPort: jConfig.Addr, 27 | }, 28 | } 29 | 30 | return cfg 31 | } 32 | 33 | func InitJaeger() (opentracing.Tracer, io.Closer) { 34 | cfg := GetDefaultConfig() 35 | cfg.ServiceName = setting.Conf.Name 36 | tracer, closer, err := cfg.NewTracer(config.Logger(jaeger.StdLogger)) 37 | if err != nil { 38 | panic(fmt.Sprintf("Error: connot init Jaeger: %v\n", err)) 39 | } 40 | 41 | opentracing.SetGlobalTracer(tracer) 42 | return tracer, closer 43 | } 44 | 45 | func GetParentSpan(spanName string, traceId string, header http.Header) (opentracing.Span, error) { 46 | carrier := opentracing.HTTPHeadersCarrier{} 47 | carrier.Set("uber-trace-id", traceId) 48 | 49 | tracer := opentracing.GlobalTracer() 50 | wireContext, err := tracer.Extract( 51 | opentracing.HTTPHeaders, 52 | opentracing.HTTPHeadersCarrier(header), 53 | ) 54 | 55 | parentSpan := opentracing.StartSpan( 56 | spanName, 57 | ext.RPCServerOption(wireContext), 58 | ) 59 | 60 | if err != nil { 61 | return nil, err 62 | } 63 | return parentSpan, err 64 | } 65 | 66 | func StartSpan(tracer opentracing.Tracer, name string) opentracing.Span { 67 | // 设置顶级span 68 | span := tracer.StartSpan(name) 69 | return span 70 | } 71 | 72 | func WithSpan(ctx context.Context, name string) (opentracing.Span, context.Context) { 73 | span, ctx := opentracing.StartSpanFromContext(ctx, name) 74 | return span, ctx 75 | } 76 | 77 | func GetCarrier(span opentracing.Span) (opentracing.HTTPHeadersCarrier, error) { 78 | carrier := opentracing.HTTPHeadersCarrier{} 79 | err := span.Tracer().Inject(span.Context(), opentracing.HTTPHeaders, carrier) 80 | if err != nil { 81 | return nil, err 82 | } 83 | return carrier, nil 84 | } 85 | -------------------------------------------------------------------------------- /router/route.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "net/http" 5 | "project/middlewares" 6 | "project/setting" 7 | 8 | api "project/api/v1" 9 | "project/logger" 10 | 11 | "github.com/gin-contrib/sessions" 12 | "github.com/gin-contrib/sessions/cookie" 13 | "github.com/gin-gonic/gin" 14 | ) 15 | 16 | // SetupRouter 路由 17 | func SetupRouter() *gin.Engine { 18 | mode := setting.Conf.Mode 19 | if mode == gin.ReleaseMode { 20 | gin.SetMode(gin.ReleaseMode) // gin设置成发布模式 21 | } 22 | r := gin.New() 23 | //r.Use(logger.GinLogger(), logger.GinRecovery(true), middlewares.RateLimitMiddleware(2*time.Second, 1)) 24 | r.Use(logger.GinLogger(), logger.GinRecovery(true)) 25 | 26 | r.LoadHTMLFiles("./templates/index.html") 27 | r.Static("/static", "./static") 28 | 29 | r.GET("/", func(c *gin.Context) { 30 | c.HTML(http.StatusOK, "index.html", nil) 31 | }) 32 | 33 | // 如果有跨域问题,请打开下一行 34 | store := cookie.NewStore([]byte("something-very-secret")) 35 | r.Use(middlewares.Cors(), middlewares.Jaeger()) 36 | r.Use(sessions.Sessions("mysession", store)) 37 | 38 | // 自定义路由组 39 | v1 := r.Group("/api/v1") 40 | 41 | // ---------------- 不使用jwt鉴权接口路由 --------------- 42 | { 43 | v1.POST("signup", api.UserRegisterHandler) 44 | v1.POST("signin", api.UserLoginHandler) 45 | } 46 | 47 | // ---------------- 使用jwt鉴权接口路由 --------------- 48 | v1.Use(middlewares.JWTAuth()) 49 | { 50 | v1.POST("/upload", api.UploadFileHandler) 51 | } 52 | //pprof.Register(r) // 注册pprof相关路由 53 | 54 | r.NoRoute(func(c *gin.Context) { 55 | c.JSON(http.StatusOK, gin.H{ 56 | "msg": "404", 57 | }) 58 | }) 59 | 60 | return r 61 | } 62 | -------------------------------------------------------------------------------- /service/svc/user.go: -------------------------------------------------------------------------------- 1 | package svc 2 | 3 | import ( 4 | "project/repository/cache" 5 | "project/repository/db/dao" 6 | 7 | "github.com/go-redis/redis/v8" 8 | ) 9 | 10 | type UserServiceContext struct { 11 | RedisClient *redis.Client 12 | 13 | UserModel dao.UserModel 14 | } 15 | 16 | func NewUserServiceContext() *UserServiceContext { 17 | return &UserServiceContext{ 18 | RedisClient: cache.RedisClient, 19 | UserModel: dao.NewUserModel(), 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /service/user.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | "project/consts" 6 | "project/logger" 7 | "project/repository/db/model" 8 | "project/service/svc" 9 | "project/types" 10 | "project/utils/jwt" 11 | "project/utils/snowflake" 12 | 13 | "go.uber.org/zap" 14 | ) 15 | 16 | type UserSrv struct { 17 | ctx context.Context 18 | svcCtx *svc.UserServiceContext 19 | log *zap.Logger 20 | } 21 | 22 | func NewUserService(ctx context.Context, svcCtx *svc.UserServiceContext) *UserSrv { 23 | return &UserSrv{ 24 | ctx: ctx, 25 | svcCtx: svcCtx, 26 | log: logger.Lg, 27 | } 28 | } 29 | 30 | func (l *UserSrv) UserRegisterSrv(req *types.UserRegisterReq) (err error) { 31 | _, exist, err := l.svcCtx.UserModel.ExistOrNotByUserName(l.ctx, req.UserName) 32 | if err != nil { 33 | return err 34 | } 35 | if exist { 36 | err = consts.UserExistErr 37 | } 38 | user := &model.User{ 39 | NickName: req.NickName, 40 | UserName: req.UserName, 41 | UserId: snowflake.GenID(), 42 | Status: model.Active, 43 | } 44 | // 加密密码 45 | if err = user.SetPassword(req.Password); err != nil { 46 | return err 47 | } 48 | 49 | // 创建用户 50 | err = l.svcCtx.UserModel.CreateUser(l.ctx, user) 51 | if err != nil { 52 | return consts.UserCreateErr 53 | } 54 | 55 | return 56 | } 57 | 58 | func (l *UserSrv) UserLoginSrv(req *types.UserRegisterReq) (resp interface{}, err error) { 59 | var user *model.User 60 | user, exist, err := l.svcCtx.UserModel.ExistOrNotByUserName(l.ctx, req.UserName) 61 | 62 | if !exist { 63 | return nil, consts.UserNotExistErr 64 | } 65 | 66 | if !user.CheckPassword(req.Password) { 67 | return nil, consts.UserInvalidPasswordErr 68 | } 69 | b := jwt.BaseClaims{ 70 | UID: user.UserId, 71 | ID: user.ID, 72 | Username: user.UserName, 73 | } 74 | accessToken, refreshToken, err := jwt.NewJWT().GenerateToken(b) 75 | if err != nil { 76 | return nil, err 77 | } 78 | userResp := &types.UserInfoResp{ 79 | ID: user.ID, 80 | UserName: user.UserName, 81 | NickName: user.NickName, 82 | Email: user.Email, 83 | Status: user.Status, 84 | CreateAt: user.CreatedAt.Unix(), 85 | } 86 | 87 | resp = &types.UserTokenData{ 88 | User: userResp, 89 | AccessToken: accessToken, 90 | RefreshToken: refreshToken, 91 | } 92 | 93 | return 94 | } 95 | -------------------------------------------------------------------------------- /setting/server/server.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "fmt" 5 | "project/setting" 6 | "project/utils" 7 | 8 | "go.uber.org/zap" 9 | 10 | "github.com/gin-gonic/gin" 11 | 12 | "time" 13 | ) 14 | 15 | type server interface { 16 | ListenAndServe() error 17 | } 18 | 19 | func RunWindowServer(router *gin.Engine) { 20 | if err := utils.InitTrans("zh"); err != nil { 21 | panic(fmt.Errorf("failed to initialize translator: %v", err)) 22 | } 23 | address := fmt.Sprintf(":%d", setting.Conf.Port) 24 | s := initServer(address, router) 25 | // 保证文本顺序输出 26 | // In order to ensure that the text order output can be deleted 27 | time.Sleep(10 * time.Microsecond) 28 | zap.L().Info("server run success on ", zap.String("address", address)) 29 | csdn := "https://blog.csdn.net/weixin_51991615" 30 | fmt.Printf(` 31 | 欢迎使用 go_builder 32 | 当前版本:v0.0.1 33 | 简介:主要为了快速搭建小型项目的脚手架 34 | Up主博客地址:%s 35 | `, csdn) 36 | zap.L().Error(s.ListenAndServe().Error()) 37 | } 38 | -------------------------------------------------------------------------------- /setting/server/signal_error.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | // +build !windows 3 | 4 | package server 5 | 6 | import ( 7 | "time" 8 | 9 | "github.com/fvbock/endless" 10 | "github.com/gin-gonic/gin" 11 | ) 12 | 13 | func initServer(address string, router *gin.Engine) server { 14 | s := endless.NewServer(address, router) 15 | s.ReadHeaderTimeout = 20 * time.Second 16 | s.WriteTimeout = 20 * time.Second 17 | s.MaxHeaderBytes = 1 << 20 18 | 19 | return s 20 | } 21 | -------------------------------------------------------------------------------- /setting/setting.go: -------------------------------------------------------------------------------- 1 | package setting 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | "project/consts" 8 | 9 | "github.com/go-sql-driver/mysql" 10 | 11 | "github.com/gin-gonic/gin" 12 | 13 | "github.com/fsnotify/fsnotify" 14 | "github.com/spf13/viper" 15 | ) 16 | 17 | var Conf = new(AppConfig) 18 | 19 | type AppConfig struct { 20 | Name string `mapstructure:"name"` 21 | Mode string `mapstructure:"mode"` 22 | Version string `mapstructure:"version"` 23 | DbType string `mapstructure:"db-type"` 24 | Port int `mapstructure:"port"` 25 | KafKa map[string]*KafkaConfig `mapstructure:"kafka"` 26 | *CORS `mapstructure:"cors"` 27 | *JWT `mapstructure:"jwt"` 28 | *SnowflakeConfig `mapstructure:"snowflake"` 29 | *QiNiuOssConfig `mapstructure:"qiniu-oss"` 30 | *LocalConfig `mapstructure:"local"` 31 | *LogConfig `mapstructure:"log"` 32 | *EsConfig `mapstructure:"es"` 33 | *JaegerConfig `mapstructure:"jaeger"` 34 | *MySQLConfig `mapstructure:"mysql"` 35 | *RedisConfig `mapstructure:"redis"` 36 | } 37 | 38 | type KafkaConfig struct { 39 | DisableConsumer bool `mapstructure:"disableConsumer"` 40 | Debug bool `mapstructure:"debug"` 41 | Address string `mapstructure:"address"` 42 | ReadTimeout int64 `mapstructure:"read-timeout"` 43 | WriteTimeout int64 `mapstructure:"write-timeout"` 44 | RequiredAck int `mapstructure:"required-ack"` 45 | MaxOpenRequests int `mapstructure:"max-open-requests"` 46 | Partition int `mapstructure:"partition"` 47 | } 48 | 49 | type LocalConfig struct { 50 | Host string `mapstructure:"host"` 51 | Path string `mapstructure:"path"` 52 | } 53 | 54 | type QiNiuOssConfig struct { 55 | Enable string `mapstructure:"enable"` 56 | Zone string `mapstructure:"zone"` 57 | Bucket string `mapstructure:"bucket"` 58 | AccessKey string `mapstructure:"access-key"` 59 | SecretKey string `mapstructure:"secret-key"` 60 | ImgPath string `mapstructure:"img-path"` 61 | UseHttps bool `mapstructure:"use-https"` 62 | UseCdnDomains bool `mapstructure:"use-cdn-domains"` // 63 | } 64 | 65 | type JaegerConfig struct { 66 | Addr string `mapstructure:"addr"` 67 | Type string `mapstructure:"type"` 68 | Param float64 `mapstructure:"param"` 69 | LogSpans bool `mapstructure:"log-spans"` 70 | } 71 | 72 | type EsConfig struct { 73 | EsHost string `mapstructure:"es_host"` 74 | EsPort string `mapstructure:"es_port"` 75 | EsIndex string `mapstructure:"es_index"` 76 | } 77 | 78 | type JWT struct { 79 | SigningKey string `mapstructure:"signing-key" json:"signing-key" yaml:"signing-key"` // jwt签名 80 | AccessExpire string `mapstructure:"access-expire" json:"access-expire" yaml:"access-expire"` // 缓冲时间 81 | RefreshExpire string `mapstructure:"refresh-expire" json:"refresh-expire" yaml:"refresh-expire"` // 过期时间 82 | Issuer string `mapstructure:"issuer" json:"issuer" yaml:"issuer"` // 签发者 83 | } 84 | 85 | type SnowflakeConfig struct { 86 | StartTime string `mapstructure:"start_time"` 87 | MachineID int64 `mapstructure:"machine_id"` 88 | } 89 | 90 | type CORS struct { 91 | Mode string `mapstructure:"mode"` 92 | Whitelist []CORSWhitelist `mapstructure:"whitelist"` 93 | } 94 | 95 | type CORSWhitelist struct { 96 | AllowOrigin string `mapstructure:"allow-origin"` 97 | AllowMethods string `mapstructure:"allow-methods"` 98 | AllowHeaders string `mapstructure:"allow-headers"` 99 | ExposeHeaders string `mapstructure:"expose-headers"` 100 | AllowCredentials bool `mapstructure:"allow-credentials"` 101 | } 102 | 103 | type MySQLConfig struct { 104 | Host string `mapstructure:"host"` 105 | User string `mapstructure:"user"` 106 | Password string `mapstructure:"password"` 107 | DbName string `mapstructure:"dbname"` 108 | Config string `mapstructure:"config"` 109 | Port string `mapstructure:"port"` 110 | Prefix string `mapstructure:"prefix"` //全局表前缀,单独定义TableName则不生效 111 | Engine string `mapstructure:"engine" default:"InnoDB"` 112 | LogMode string `mapstructure:"log_mode"` 113 | LogZap bool `mapstructure:"log_zap"` 114 | Singular bool `mapstructure:"singular"` //是否开启全局禁用复数,true表示开启 115 | MaxOpenConns int `mapstructure:"max_open_conns"` 116 | MaxIdleConns int `mapstructure:"max_idle_conns"` 117 | } 118 | 119 | func (m *MySQLConfig) Dsn() string { 120 | dsn := m.User + ":" + m.Password + "@tcp(" + m.Host + ":" + m.Port + ")/" + m.DbName + "?" + m.Config 121 | cfg, _ := mysql.ParseDSN(dsn) 122 | 123 | return cfg.FormatDSN() 124 | } 125 | 126 | type RedisConfig struct { 127 | Addr string `mapstructure:"addr"` 128 | Password string `mapstructure:"password"` 129 | DB int `mapstructure:"db"` 130 | PoolSize int `mapstructure:"pool_size"` 131 | MinIdleConns int `mapstructure:"min_idle_conns"` 132 | } 133 | 134 | type LogConfig struct { 135 | Level string `mapstructure:"level"` 136 | Filename string `mapstructure:"filename"` 137 | MaxSize int `mapstructure:"max_size"` 138 | MaxAge int `mapstructure:"max_age"` 139 | MaxBackups int `mapstructure:"max_backups"` 140 | } 141 | 142 | func Init(path ...string) (err error) { 143 | var config string 144 | 145 | if len(path) == 0 { 146 | flag.StringVar(&config, "c", "", "choose config file.") 147 | flag.Parse() 148 | if config == "" { // 判断命令行参数是否为空 149 | if configEnv := os.Getenv(consts.ConfigEnv); configEnv == "" { 150 | switch gin.Mode() { 151 | case gin.DebugMode: 152 | config = consts.ConfigDefaultFile 153 | fmt.Printf("您正在使用gin模式的%s环境名称,config的路径为%s\n", gin.EnvGinMode, consts.ConfigDefaultFile) 154 | case gin.ReleaseMode: 155 | config = consts.ConfigReleaseFile 156 | fmt.Printf("您正在使用gin模式的%s环境名称,config的路径为%s\n", gin.EnvGinMode, consts.ConfigReleaseFile) 157 | case gin.TestMode: 158 | config = consts.ConfigTestFile 159 | fmt.Printf("您正在使用gin模式的%s环境名称,config的路径为%s\n", gin.EnvGinMode, consts.ConfigTestFile) 160 | } 161 | } else { // internal.ConfigEnv 常量存储的环境变量不为空 将值赋值于config 162 | config = configEnv 163 | fmt.Printf("您正在使用%s环境变量,config的路径为%s\n", consts.ConfigEnv, config) 164 | } 165 | } else { // 命令行参数不为空 将值赋值于config 166 | fmt.Printf("您正在使用命令行的-c参数传递的值,config的路径为%s\n", config) 167 | } 168 | } else { // 函数传递的可变参数的第一个值赋值于config 169 | config = path[0] 170 | fmt.Printf("您正在使用func Viper()传递的值,config的路径为%s\n", config) 171 | } 172 | 173 | viper.SetConfigFile(config) 174 | 175 | err = viper.ReadInConfig() // 读取配置信息 176 | if err != nil { 177 | // 读取配置信息失败 178 | fmt.Printf("viper.ReadInConfig failed, err:%v\n", err) 179 | return 180 | } 181 | 182 | // 把读取到的配置信息反序列化到 Conf 变量中 183 | if err := viper.Unmarshal(Conf); err != nil { 184 | fmt.Printf("viper.Unmarshal failed, err:%v\n", err) 185 | } 186 | 187 | viper.WatchConfig() 188 | viper.OnConfigChange(func(in fsnotify.Event) { 189 | fmt.Println("天啦噜,配置文件被修改了...") 190 | if err := viper.Unmarshal(Conf); err != nil { 191 | fmt.Printf("viper.Unmarshal failed, err:%v\n", err) 192 | } 193 | }) 194 | 195 | return 196 | } 197 | -------------------------------------------------------------------------------- /static/dictionary/其他词库.txt: -------------------------------------------------------------------------------- 1 | fuck 2 | fucking -------------------------------------------------------------------------------- /types/common.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | type SensitiveWord struct { 4 | Word string `json:"word"` 5 | Indexes []int `json:"indexes"` 6 | Length int `json:"length"` 7 | } 8 | -------------------------------------------------------------------------------- /types/user.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | type UserRegisterReq struct { 4 | NickName string `form:"nick_name" json:"nick_name"` 5 | UserName string `form:"user_name" json:"user_name"` 6 | Password string `form:"password" json:"password"` 7 | Key string `form:"key" json:"key"` // 前端进行判断 8 | } 9 | 10 | type UserInfoResp struct { 11 | ID uint `json:"id"` 12 | UserName string `json:"user_name"` 13 | NickName string `json:"nickname"` 14 | Type int `json:"type"` 15 | Email string `json:"email"` 16 | Status string `json:"status"` 17 | Avatar string `json:"avatar"` 18 | CreateAt int64 `json:"create_at"` 19 | } 20 | 21 | type UserTokenData struct { 22 | User interface{} `json:"user"` 23 | AccessToken string `json:"access_token"` 24 | RefreshToken string `json:"refresh_token"` 25 | } 26 | -------------------------------------------------------------------------------- /utils/app/app.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "project/consts" 7 | "regexp" 8 | 9 | "github.com/gin-gonic/gin" 10 | ) 11 | 12 | // ResponseData 返回数据结构体 13 | type ResponseData struct { 14 | Code ResCode `json:"code"` 15 | Msg interface{} `json:"message"` 16 | Data interface{} `json:"data,omitempty"` 17 | } 18 | 19 | type TrackedErrorResponse struct { 20 | ResponseData 21 | TrackId string `json:"track_id"` 22 | } 23 | 24 | // ResponseError 返回错误响应 25 | func ResponseError(c *gin.Context, code ResCode) { 26 | trackId, _ := getTrackIdFromCtx(c) 27 | r := &TrackedErrorResponse{ 28 | ResponseData: ResponseData{ 29 | Code: code, 30 | Msg: code.Msg(), 31 | Data: nil, 32 | }, 33 | TrackId: trackId, 34 | } 35 | c.JSON(http.StatusOK, r) 36 | } 37 | 38 | // ResponseErrorWithCodeMsg 返回错误响应和信息 39 | func ResponseErrorWithCodeMsg(c *gin.Context, code ResCode, msg interface{}) { 40 | trackId, _ := getTrackIdFromCtx(c) 41 | r := &TrackedErrorResponse{ 42 | ResponseData: ResponseData{ 43 | Code: code, 44 | Msg: msg, 45 | Data: nil, 46 | }, 47 | TrackId: trackId, 48 | } 49 | c.JSON(http.StatusOK, r) 50 | } 51 | 52 | // ResponseErrorWithMsg 返回错误信息 53 | func ResponseErrorWithMsg(c *gin.Context, msg interface{}) { 54 | trackId, _ := getTrackIdFromCtx(c) 55 | r := &TrackedErrorResponse{ 56 | ResponseData: ResponseData{ 57 | Msg: msg, 58 | Data: nil, 59 | }, 60 | TrackId: trackId, 61 | } 62 | c.JSON(http.StatusOK, r) 63 | } 64 | 65 | // ResponseSuccess 返回成功响应 66 | func ResponseSuccess(c *gin.Context, data interface{}) { 67 | c.JSON(http.StatusOK, &ResponseData{ 68 | Code: CodeSuccess, 69 | Msg: CodeSuccess.Msg(), 70 | Data: data, 71 | }) 72 | } 73 | 74 | // ResponseSuccessWithMsg 返回成功,自带文字描述 75 | func ResponseSuccessWithMsg(c *gin.Context, data interface{}, msg interface{}) { 76 | c.JSON(http.StatusOK, &ResponseData{ 77 | Code: CodeSuccess, 78 | Msg: msg, 79 | Data: data, 80 | }) 81 | } 82 | 83 | func getTrackIdFromCtx(ctx *gin.Context) (trackId string, err error) { 84 | spanCtxInterface, _ := ctx.Get(consts.SpanCTX) 85 | str := fmt.Sprintf("%v", spanCtxInterface) 86 | re := regexp.MustCompile(`([0-9a-fA-F]{16})`) 87 | 88 | match := re.FindStringSubmatch(str) 89 | if len(match) > 0 { 90 | return match[1], nil 91 | } 92 | return "", consts.GetTrackIdErr 93 | } 94 | -------------------------------------------------------------------------------- /utils/app/claims.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "context" 5 | "project/consts" 6 | ) 7 | 8 | type key int 9 | 10 | var userKey key 11 | 12 | type UserInfo struct { 13 | UID int64 `json:"uid"` 14 | ID uint `json:"id"` 15 | Username string `json:"username"` 16 | } 17 | 18 | func GetUserInfo(ctx context.Context) (*UserInfo, error) { 19 | user, ok := FromContext(ctx) 20 | if !ok { 21 | return nil, consts.UserInfoErr 22 | } 23 | 24 | return user, nil 25 | } 26 | 27 | func NewContext(ctx context.Context, u *UserInfo) context.Context { 28 | return context.WithValue(ctx, userKey, u) 29 | } 30 | 31 | func FromContext(ctx context.Context) (*UserInfo, bool) { 32 | u, ok := ctx.Value(userKey).(*UserInfo) 33 | return u, ok 34 | } 35 | -------------------------------------------------------------------------------- /utils/app/code.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | type ResCode int64 4 | 5 | const ( 6 | // CodeSuccess 成功(默认返回状态码) 7 | CodeSuccess ResCode = 0 8 | // CodeSeverError 全局未知异常 9 | CodeSeverError ResCode = 500 10 | // CodeBadRequest 请求失败(一般前端处理,不常用) 11 | CodeBadRequest ResCode = 400 12 | // CodeDataNotFount 请求资源不存在(静态资源不存在,不常用) 13 | CodeDataNotFount ResCode = 404 14 | // CodeLoginExpire 登录认证异常 15 | CodeLoginExpire ResCode = 401 16 | ) 17 | 18 | /* 19 | 通用业务 20 | */ 21 | const ( 22 | /* 23 | 1001-1010 通用操作相关 24 | */ 25 | // CodeOperationFail 操作失败 26 | CodeOperationFail ResCode = 1001 + iota 27 | // CodeSelectOperationFail 查询操作失败 28 | CodeSelectOperationFail 29 | // CodeUpdateOperationFail 更新操作失败 30 | CodeUpdateOperationFail 31 | // CodeDeleteOperationFail 删除操作失败 32 | CodeDeleteOperationFail 33 | // CodeInsertOperationFail 新增操作失败 34 | CodeInsertOperationFail 35 | // CodeInvalidParam 参数错误 36 | CodeInvalidParam 37 | 38 | /* 39 | 1011-1050 例如登录注册相关 40 | */ 41 | CodeNoLogin ResCode = 1011 + iota 42 | CodeErrorAuthCheckTokenFail 43 | CodeUserNameOrPasswordFail 44 | ) 45 | 46 | /* 47 | -----------go_api 业务相关(2xxx)------------ 48 | */ 49 | 50 | var codeMsgMap = map[ResCode]string{ 51 | CodeSuccess: "success", 52 | CodeSeverError: "服务器繁忙请重试", 53 | CodeBadRequest: "请求失败", 54 | CodeDataNotFount: "未找到资源", 55 | CodeLoginExpire: "请登录后重试", 56 | CodeOperationFail: "操作失败", 57 | CodeSelectOperationFail: "查询操作失败!", 58 | CodeUpdateOperationFail: "更新操作失败!", 59 | CodeDeleteOperationFail: "删除操作失败!", 60 | CodeInsertOperationFail: "新增操作失败!", 61 | CodeInvalidParam: "请求参数错误", 62 | 63 | CodeNoLogin: "未登陆", 64 | CodeErrorAuthCheckTokenFail: "token 错误", 65 | CodeUserNameOrPasswordFail: "用户名或密码错误", 66 | } 67 | 68 | func (c ResCode) Msg() string { 69 | msg, ok := codeMsgMap[c] 70 | if !ok { 71 | msg = codeMsgMap[CodeSeverError] 72 | } 73 | return msg 74 | } 75 | -------------------------------------------------------------------------------- /utils/duration.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "strconv" 5 | "strings" 6 | "time" 7 | ) 8 | 9 | // ParseDuration 解析时间 10 | func ParseDuration(d string) (time.Duration, error) { 11 | d = strings.TrimSpace(d) 12 | dr, err := time.ParseDuration(d) 13 | if err == nil { 14 | return dr, nil 15 | } 16 | if strings.Contains(d, "d") { 17 | index := strings.Index(d, "d") 18 | 19 | hour, _ := strconv.Atoi(d[:index]) 20 | dr = time.Hour * 24 * time.Duration(hour) 21 | ndr, err := time.ParseDuration(d[index+1:]) 22 | if err != nil { 23 | return dr, nil 24 | } 25 | return dr + ndr, nil 26 | } 27 | 28 | dv, err := strconv.ParseInt(d, 10, 64) 29 | return time.Duration(dv), err 30 | } 31 | -------------------------------------------------------------------------------- /utils/event/event.go: -------------------------------------------------------------------------------- 1 | package event 2 | 3 | import ( 4 | "project/repository/db/model" 5 | "time" 6 | ) 7 | 8 | type Observer interface { 9 | Update(event Event) 10 | } 11 | 12 | type Event struct { 13 | User model.User 14 | Time time.Time 15 | // 其他相关字段 16 | } 17 | 18 | type EmailNotifier struct { 19 | // Email通知器的具体实现 20 | } 21 | 22 | func (en *EmailNotifier) Update(event Event) { 23 | // 处理邮件通知的逻辑 24 | } 25 | 26 | type SMSNotifier struct { 27 | // 短信通知器的具体实现 28 | } 29 | 30 | func (sn *SMSNotifier) Update(event Event) { 31 | // 处理短信通知的逻辑 32 | } 33 | -------------------------------------------------------------------------------- /utils/jwt/jwt.go: -------------------------------------------------------------------------------- 1 | package jwt 2 | 3 | import ( 4 | "project/consts" 5 | "project/setting" 6 | "project/utils" 7 | "time" 8 | 9 | "github.com/dgrijalva/jwt-go" 10 | ) 11 | 12 | type CustomClaims struct { 13 | BaseClaims 14 | jwt.StandardClaims 15 | } 16 | 17 | // BaseClaims 自定义字段封装进jwt,后续从token想取出什么字段就在这里加 18 | type BaseClaims struct { 19 | UID int64 20 | ID uint 21 | Username string 22 | } 23 | 24 | type JWT struct { 25 | SigningKey []byte 26 | } 27 | 28 | func NewJWT() *JWT { 29 | return &JWT{ 30 | []byte(setting.Conf.JWT.SigningKey), 31 | } 32 | } 33 | 34 | func (j *JWT) GenerateToken(baseClaims BaseClaims) (accessToken, refreshToken string, err error) { 35 | jConfig := setting.Conf.JWT 36 | nowTime := time.Now() 37 | ae, _ := utils.ParseDuration(jConfig.AccessExpire) 38 | re, _ := utils.ParseDuration(jConfig.RefreshExpire) 39 | accessExpireTime := nowTime.Add(ae) 40 | refreshExpireTime := nowTime.Add(re) 41 | claims := CustomClaims{ 42 | BaseClaims: baseClaims, 43 | StandardClaims: jwt.StandardClaims{ 44 | ExpiresAt: accessExpireTime.Unix(), 45 | Issuer: jConfig.Issuer, 46 | }, 47 | } 48 | accessToken, err = jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString(j.SigningKey) 49 | if err != nil { 50 | return "", "", err 51 | } 52 | 53 | refreshToken, err = jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.StandardClaims{ 54 | ExpiresAt: refreshExpireTime.Unix(), 55 | Issuer: jConfig.Issuer, 56 | }).SignedString(j.SigningKey) 57 | if err != nil { 58 | return "", "", err 59 | } 60 | 61 | return accessToken, refreshToken, err 62 | } 63 | 64 | // ParseToken 解析token 65 | func (j *JWT) ParseToken(token string) (*CustomClaims, error) { 66 | tokenClaims, err := jwt.ParseWithClaims(token, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) { 67 | return j.SigningKey, nil 68 | }) 69 | if tokenClaims != nil { 70 | if claims, ok := tokenClaims.Claims.(*CustomClaims); ok { 71 | return claims, nil 72 | } 73 | } 74 | 75 | return nil, err 76 | } 77 | 78 | // ParseRefreshToken 以旧换新 79 | func (j *JWT) ParseRefreshToken(aToken, rToken string) (newAToken, newRToken string, err error) { 80 | accessClaims, err := j.ParseToken(aToken) 81 | if err != nil { 82 | return 83 | } 84 | 85 | refreshClaim, err := j.ParseToken(rToken) 86 | if err != nil { 87 | return 88 | } 89 | aClaim := BaseClaims{ 90 | UID: accessClaims.UID, 91 | ID: accessClaims.ID, 92 | Username: accessClaims.Username, 93 | } 94 | if accessClaims.ExpiresAt > time.Now().Unix() { 95 | 96 | return j.GenerateToken(aClaim) 97 | } 98 | 99 | if refreshClaim.ExpiresAt > time.Now().Unix() { 100 | 101 | return j.GenerateToken(aClaim) 102 | } 103 | 104 | return "", "", consts.TokenExpired 105 | } 106 | -------------------------------------------------------------------------------- /utils/md5.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "crypto/md5" 5 | "encoding/hex" 6 | ) 7 | 8 | //@author: [piexlmax](https://github.com/piexlmax) 9 | //@function: MD5V 10 | //@description: md5加密 11 | //@param: str []byte 12 | //@return: string 13 | 14 | func MD5V(str []byte, b ...byte) string { 15 | h := md5.New() 16 | h.Write(str) 17 | return hex.EncodeToString(h.Sum(b)) 18 | } 19 | -------------------------------------------------------------------------------- /utils/sensitiveWords/check.go: -------------------------------------------------------------------------------- 1 | package sensitiveWord 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | "project/consts" 7 | "project/types" 8 | "strings" 9 | 10 | "go.uber.org/zap" 11 | ) 12 | 13 | // SensitiveMap 使用前缀树实现敏感词过滤 14 | type SensitiveMap struct { 15 | sensitiveNode map[string]interface{} 16 | isEnd bool 17 | } 18 | 19 | type Target struct { 20 | Indexes []int 21 | Len int 22 | } 23 | 24 | var s *SensitiveMap 25 | 26 | // getMap 将自己的词库放入/static/dictionary下,放入下列切片中!!!! 27 | func getMap() *SensitiveMap { 28 | if s == nil { 29 | var Sen []string 30 | Sen = append(Sen, consts.OtherSen) 31 | s = InitDictionary(s, Sen) 32 | } 33 | 34 | return s 35 | } 36 | 37 | // CheckSensitiveWord 判断是否有敏感词 38 | func CheckSensitiveWord(content string) []*types.SensitiveWord { 39 | var res []*types.SensitiveWord 40 | sensitiveMap := getMap() 41 | target := sensitiveMap.FindAllSensitive(content) 42 | for k, v := range target { 43 | t := &types.SensitiveWord{ 44 | Word: k, 45 | Indexes: v.Indexes, 46 | Length: v.Len, 47 | } 48 | res = append(res, t) 49 | } 50 | 51 | return res 52 | } 53 | 54 | // FindAllSensitive 查找所有的敏感词 55 | func (s *SensitiveMap) FindAllSensitive(text string) map[string]*Target { 56 | content := []rune(text) 57 | contentLength := len(content) 58 | result := false 59 | ta := make(map[string]*Target) 60 | 61 | for index := range content { 62 | sMapTmp := s 63 | target := "" 64 | in := index 65 | result = false 66 | 67 | for { 68 | wo := string(content[in]) 69 | target += wo 70 | if _, ok := sMapTmp.sensitiveNode[wo]; ok { 71 | if sMapTmp.sensitiveNode[wo].(*SensitiveMap).isEnd { 72 | result = true 73 | break 74 | } 75 | if in == contentLength-1 { 76 | break 77 | } 78 | sMapTmp = sMapTmp.sensitiveNode[wo].(*SensitiveMap) 79 | in++ 80 | } else { 81 | break 82 | } 83 | } 84 | if result { 85 | if _, targetInTa := ta[target]; targetInTa { 86 | ta[target].Indexes = append(ta[target].Indexes, index) 87 | } else { 88 | ta[target] = &Target{ 89 | Indexes: []int{index}, 90 | Len: len([]rune(target)), 91 | } 92 | } 93 | } 94 | } 95 | 96 | return ta 97 | } 98 | 99 | // InitDictionary 初始化字典,构造前缀树 100 | func InitDictionary(s *SensitiveMap, dictionary []string) *SensitiveMap { 101 | // 初始化字典树 102 | s = initSensitiveMap() 103 | var dictionaryContent []string 104 | for i := 0; i < len(dictionary); i++ { 105 | dictionaryContentTmp := ReadDictionary(dictionary[i]) 106 | // TODO:将所有词拿到 107 | dictionaryContent = append(dictionaryContent, dictionaryContentTmp...) 108 | } 109 | for _, words := range dictionaryContent { 110 | sMapTmp := s 111 | // 将每一个词转换为一个rune数组,不光英文、中文 112 | w := []rune(words) 113 | wordsLen := len(w) 114 | for i := 0; i < wordsLen; i++ { 115 | t := string(w[i]) 116 | isEnd := false 117 | if i == (wordsLen - 1) { 118 | isEnd = true 119 | } 120 | func(tx string) { 121 | if _, ok := sMapTmp.sensitiveNode[tx]; !ok { 122 | sMapTemp := new(SensitiveMap) 123 | sMapTemp.sensitiveNode = make(map[string]interface{}) 124 | sMapTemp.isEnd = isEnd 125 | sMapTmp.sensitiveNode[tx] = sMapTemp 126 | } 127 | sMapTmp = sMapTmp.sensitiveNode[tx].(*SensitiveMap) 128 | sMapTmp.isEnd = isEnd 129 | }(t) 130 | } 131 | } 132 | 133 | return s 134 | } 135 | 136 | // initSensitiveMap 初始化map 137 | func initSensitiveMap() *SensitiveMap { 138 | return &SensitiveMap{ 139 | sensitiveNode: make(map[string]interface{}), 140 | isEnd: false, 141 | } 142 | } 143 | 144 | // ReadDictionary 将词库读取出来 145 | func ReadDictionary(path string) []string { 146 | file, err := os.Open(path) 147 | if err != nil { 148 | zap.L().Error("read dictionary file failed:", zap.Error(err)) 149 | return nil 150 | } 151 | 152 | defer file.Close() 153 | 154 | str, err := ioutil.ReadAll(file) 155 | dictionary := strings.Fields(string(str)) 156 | 157 | return dictionary 158 | } 159 | -------------------------------------------------------------------------------- /utils/snowflake/snowflake.go: -------------------------------------------------------------------------------- 1 | package snowflake 2 | 3 | import ( 4 | "project/setting" 5 | "time" 6 | 7 | sf "github.com/bwmarrin/snowflake" 8 | ) 9 | 10 | var node *sf.Node 11 | 12 | func Init(startTime string, machineID int64) (err error) { 13 | var st time.Time 14 | st, err = time.Parse("2006-01-02", startTime) 15 | if err != nil { 16 | return 17 | } 18 | sf.Epoch = st.UnixNano() / 1000000 19 | node, err = sf.NewNode(machineID) 20 | return 21 | } 22 | 23 | func GenID() int64 { 24 | return node.Generate().Int64() 25 | } 26 | 27 | func InitSnowflake() (err error) { 28 | sConfig := setting.Conf.SnowflakeConfig 29 | var st time.Time 30 | st, err = time.Parse("2006-01-02", sConfig.StartTime) 31 | if err != nil { 32 | return 33 | } 34 | sf.Epoch = st.UnixNano() / 1000000 35 | node, err = sf.NewNode(sConfig.MachineID) 36 | 37 | return 38 | } 39 | -------------------------------------------------------------------------------- /utils/upload/local.go: -------------------------------------------------------------------------------- 1 | package upload 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | "path/filepath" 7 | "project/setting" 8 | "project/utils/urls" 9 | ) 10 | 11 | type localUpload struct{} 12 | 13 | func (local *localUpload) PutImage(data []byte, contentType string) (string, error) { 14 | key := generateImageKey(data, contentType) 15 | return local.PutObject(key, data, contentType) 16 | } 17 | 18 | func (local *localUpload) PutObject(key string, data []byte, contentType string) (string, error) { 19 | if err := os.MkdirAll("/", os.ModeDir); err != nil { 20 | return "", err 21 | } 22 | c := setting.Conf.LocalConfig 23 | filename := filepath.Join(c.Path, key) 24 | if err := os.MkdirAll(filepath.Dir(filename), os.ModePerm); err != nil { 25 | return "", err 26 | } 27 | if err := ioutil.WriteFile(filename, data, os.ModePerm); err != nil { 28 | return "", err 29 | } 30 | return urls.UrlJoin(c.Host, key), nil 31 | } 32 | 33 | func (local *localUpload) CopyImage(originUrl string) (string, error) { 34 | data, contentType, err := download(originUrl) 35 | if err != nil { 36 | return "", err 37 | } 38 | return local.PutImage(data, contentType) 39 | } 40 | -------------------------------------------------------------------------------- /utils/upload/qiniu.go: -------------------------------------------------------------------------------- 1 | package upload 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "project/setting" 7 | "sync" 8 | 9 | "github.com/mlogclub/simple/common/strs" 10 | "github.com/qiniu/api.v7/v7/auth/qbox" 11 | "github.com/qiniu/api.v7/v7/storage" 12 | ) 13 | 14 | type qiniuOssUpload struct { 15 | once sync.Once 16 | bucket *storage.BucketManager 17 | auth *qbox.Mac 18 | domain string 19 | } 20 | 21 | func (q *qiniuOssUpload) PutImage(data []byte, contentType string) (string, error) { 22 | if strs.IsBlank(contentType) { 23 | contentType = "image/jpeg" 24 | } 25 | key := generateImageKey(data, contentType) 26 | 27 | return q.PutObject(key, data, contentType) 28 | } 29 | 30 | func (q *qiniuOssUpload) PutObject(key string, data []byte, contentType string) (string, error) { 31 | bucket := q.getBucket() 32 | putPolicy := storage.PutPolicy{ 33 | Scope: bucket + ":" + key, 34 | } 35 | 36 | upToken := putPolicy.UploadToken(q.auth) 37 | cfg := storage.Config{ 38 | Zone: &storage.ZoneHuadong, 39 | } 40 | formUploader := storage.NewFormUploader(&cfg) 41 | var ret storage.PutRet 42 | var extra = &storage.PutExtra{} 43 | if strs.IsNotBlank(contentType) { 44 | extra.MimeType = contentType 45 | } 46 | if err := formUploader.Put(context.Background(), &ret, upToken, key, bytes.NewReader(data), int64(len(data)), extra); err != nil { 47 | return "", err 48 | } 49 | return q.domain + "/" + key, nil 50 | } 51 | 52 | func (q *qiniuOssUpload) CopyImage(originUrl string) (string, error) { 53 | data, contentType, err := download(originUrl) 54 | if err != nil { 55 | return "", err 56 | } 57 | return q.PutImage(data, contentType) 58 | } 59 | 60 | func (q *qiniuOssUpload) getBucket() string { 61 | q.once.Do(func() { 62 | qConfig := setting.Conf.QiNiuOssConfig 63 | q.auth = qbox.NewMac(qConfig.AccessKey, qConfig.SecretKey) 64 | sc := qiniuConfig() 65 | q.domain = qConfig.ImgPath 66 | q.bucket = storage.NewBucketManager(q.auth, sc) 67 | }) 68 | 69 | return setting.Conf.QiNiuOssConfig.Bucket 70 | } 71 | 72 | func qiniuConfig() *storage.Config { 73 | qConfig := setting.Conf.QiNiuOssConfig 74 | cfg := storage.Config{ 75 | UseHTTPS: qConfig.UseHttps, 76 | UseCdnDomains: qConfig.UseCdnDomains, 77 | } 78 | 79 | switch qConfig.Zone { 80 | case "ZoneHuadong": 81 | cfg.Zone = &storage.ZoneHuadong 82 | case "ZoneHuabei": 83 | cfg.Zone = &storage.ZoneHuabei 84 | case "ZoneHuanan": 85 | cfg.Zone = &storage.ZoneHuanan 86 | case "ZoneBeimei": 87 | cfg.Zone = &storage.ZoneBeimei 88 | case "ZoneXinjiapo": 89 | cfg.Zone = &storage.ZoneXinjiapo 90 | } 91 | 92 | return &cfg 93 | } 94 | -------------------------------------------------------------------------------- /utils/upload/tool.go: -------------------------------------------------------------------------------- 1 | package upload 2 | 3 | import ( 4 | "mime" 5 | "project/setting" 6 | "time" 7 | 8 | "github.com/go-resty/resty/v2" 9 | "github.com/mlogclub/simple/common/dates" 10 | "github.com/mlogclub/simple/common/digests" 11 | "github.com/mlogclub/simple/common/strs" 12 | ) 13 | 14 | func generateImageKey(data []byte, contentType string) string { 15 | md5 := digests.MD5Bytes(data) 16 | ext := "" 17 | if strs.IsNotBlank(contentType) { 18 | exts, err := mime.ExtensionsByType(contentType) 19 | if err == nil || len(exts) > 0 { 20 | ext = exts[0] 21 | } 22 | } 23 | if setting.Conf.Mode == "dev" { 24 | return "test/images/" + dates.Format(time.Now(), "2006/01/02/") + md5 + ext 25 | } else { 26 | return "images/" + dates.Format(time.Now(), "2006/01/02/") + md5 + ext 27 | } 28 | } 29 | 30 | func download(url string) ([]byte, string, error) { 31 | rsp, err := resty.New().R().Get(url) 32 | if err != nil { 33 | return nil, "", err 34 | } 35 | 36 | return rsp.Body(), rsp.Header().Get("Content-Type"), nil 37 | } 38 | -------------------------------------------------------------------------------- /utils/upload/upload.go: -------------------------------------------------------------------------------- 1 | package upload 2 | 3 | import ( 4 | "project/setting" 5 | "strings" 6 | "sync" 7 | 8 | "github.com/mlogclub/simple/common/strs" 9 | "github.com/mlogclub/simple/common/urls" 10 | ) 11 | 12 | type uploader interface { 13 | PutImage(data []byte, contentType string) (string, error) 14 | PutObject(key string, data []byte, contentType string) (string, error) 15 | CopyImage(originUrl string) (string, error) 16 | } 17 | 18 | var ( 19 | qiniu = &qiniuOssUpload{ 20 | once: sync.Once{}, 21 | bucket: nil, 22 | auth: nil, 23 | domain: "", 24 | } 25 | local = &localUpload{} 26 | ) 27 | 28 | func PutImage(data []byte, contentType string) (string, error) { 29 | return getUploader().PutImage(data, contentType) 30 | } 31 | 32 | func PutObject(key string, data []byte, contentType string) (string, error) { 33 | return getUploader().PutObject(key, data, contentType) 34 | } 35 | 36 | func CopyImage(url string) (string, error) { 37 | u1 := urls.ParseUrl(url).GetURL() 38 | u2 := urls.ParseUrl(setting.Conf.LocalConfig.Host).GetURL() 39 | // 本站host,不下载 40 | if u1.Host == u2.Host { 41 | return url, nil 42 | } 43 | return getUploader().CopyImage(url) 44 | } 45 | 46 | func getUploader() uploader { 47 | if IsEnabledOss() { 48 | return qiniu 49 | } 50 | 51 | return local 52 | } 53 | 54 | // IsEnabledOss 是否启用七牛云oss 55 | func IsEnabledOss() bool { 56 | enable := setting.Conf.QiNiuOssConfig.Enable 57 | return strs.EqualsIgnoreCase(enable, "qiniuyun") || strs.EqualsIgnoreCase(enable, "oss") || 58 | strs.EqualsIgnoreCase(enable, "qiniuyunOss") 59 | } 60 | 61 | // IsOssImageUrl 是否是存放在七牛云oss中的图片 62 | func IsOssImageUrl(url string) bool { 63 | host := urls.ParseUrl(setting.Conf.QiNiuOssConfig.ImgPath).GetURL().Host 64 | return strings.Contains(url, host) 65 | } 66 | -------------------------------------------------------------------------------- /utils/urls/url.go: -------------------------------------------------------------------------------- 1 | package urls 2 | 3 | import "strings" 4 | 5 | func UrlJoin(parts ...string) string { 6 | sep := "/" 7 | var ss []string 8 | for i, part := range parts { 9 | part = strings.TrimSpace(part) 10 | var ( 11 | from = 0 12 | to = len(part) 13 | ) 14 | if strings.Index(part, sep) == 0 { 15 | from = 1 16 | } 17 | if strings.LastIndex(part, sep) == len(part)-1 { 18 | to = len(part) - 1 19 | } 20 | part = part[from:to] 21 | 22 | ss = append(ss, part) 23 | if i != len(parts)-1 { 24 | ss = append(ss, sep) 25 | } 26 | } 27 | return strings.Join(ss, "") 28 | } 29 | -------------------------------------------------------------------------------- /utils/validator.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "strings" 7 | 8 | "github.com/gin-gonic/gin/binding" 9 | "github.com/go-playground/locales/en" 10 | "github.com/go-playground/locales/zh" 11 | ut "github.com/go-playground/universal-translator" 12 | "github.com/go-playground/validator/v10" 13 | enTranslations "github.com/go-playground/validator/v10/translations/en" 14 | zhTranslations "github.com/go-playground/validator/v10/translations/zh" 15 | ) 16 | 17 | // 定义一个全局翻译器T 18 | var trans ut.Translator 19 | 20 | // InitTrans 初始化翻译器 21 | func InitTrans(locale string) (err error) { 22 | // 修改gin框架中的Validator引擎属性,实现自定制 23 | if v, ok := binding.Validator.Engine().(*validator.Validate); ok { 24 | 25 | // 注册一个获取json tag的自定义方法 26 | v.RegisterTagNameFunc(func(fld reflect.StructField) string { 27 | name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0] 28 | if name == "-" { 29 | return "" 30 | } 31 | return name 32 | }) 33 | 34 | // 为SignUpParam注册自定义校验方法 35 | //v.RegisterStructValidation(SignUpParamStructLevelValidation, model.User{}) 36 | 37 | zhT := zh.New() // 中文翻译器 38 | enT := en.New() // 英文翻译器 39 | 40 | // 第一个参数是备用(fallback)的语言环境 41 | // 后面的参数是应该支持的语言环境(支持多个) 42 | // uni := ut.New(zhT, zhT) 也是可以的 43 | uni := ut.New(enT, zhT, enT) 44 | 45 | // locale 通常取决于 http 请求头的 'Accept-Language' 46 | var ok bool 47 | // 也可以使用 uni.FindTranslator(...) 传入多个locale进行查找 48 | trans, ok = uni.GetTranslator(locale) 49 | if !ok { 50 | return fmt.Errorf("uni.GetTranslator(%s) failed", locale) 51 | } 52 | 53 | // 注册翻译器 54 | switch locale { 55 | case "en": 56 | err = enTranslations.RegisterDefaultTranslations(v, trans) 57 | case "zh": 58 | err = zhTranslations.RegisterDefaultTranslations(v, trans) 59 | default: 60 | err = enTranslations.RegisterDefaultTranslations(v, trans) 61 | } 62 | return 63 | } 64 | return 65 | } 66 | 67 | // removeTopStruct 去除提示信息中的结构体名称 68 | func removeTopStruct(fields map[string]string) map[string]string { 69 | res := map[string]string{} 70 | for field, err := range fields { 71 | res[field[strings.Index(field, ".")+1:]] = err 72 | } 73 | return res 74 | } 75 | 76 | // SignUpParamStructLevelValidation 自定义SignUpParam结构体校验函数 77 | func SignUpParamStructLevelValidation(sl validator.StructLevel) { 78 | //su := sl.Current().Interface().(model.User) 79 | // 80 | //if su.Password != su.RePassword { 81 | // // 输出错误提示信息,最后一个参数就是传递的param 82 | // sl.ReportError(su.RePassword, "re_password", "RePassword", "eqfield", "password") 83 | //} 84 | } 85 | --------------------------------------------------------------------------------