Repository: Q1mi/goweb_pro Branch: master Commit: 5831d6ef43a5 Files: 445 Total size: 147.4 MB Directory structure: gitextract_l4ew9fl9/ ├── .gitignore ├── README.md ├── bluebell/ │ ├── .air.conf │ ├── Makefile │ ├── conf/ │ │ └── config.yaml │ ├── controller/ │ │ ├── code.go │ │ ├── request.go │ │ ├── response.go │ │ ├── user.go │ │ └── validator.go │ ├── dao/ │ │ ├── mysql/ │ │ │ ├── mysql.go │ │ │ └── user.go │ │ └── redis/ │ │ └── redis.go │ ├── go.mod │ ├── go.sum │ ├── logger/ │ │ └── logger.go │ ├── logic/ │ │ └── user.go │ ├── main.go │ ├── middlewares/ │ │ └── auth.go │ ├── models/ │ │ ├── create_table.sql │ │ ├── params.go │ │ └── user.go │ ├── pkg/ │ │ ├── jwt/ │ │ │ └── jwt.go │ │ └── snowflake/ │ │ └── snowflake.go │ ├── router/ │ │ └── route.go │ └── setting/ │ └── setting.go ├── docker_demo/ │ ├── Dockerfile │ ├── Dockerfile.back │ ├── go.mod │ └── main.go ├── flag_demo/ │ ├── args_demo.go │ ├── flag_demo │ ├── flag_demo.go │ └── go.mod ├── gin_demo/ │ ├── go.mod │ ├── go.sum │ └── main.go ├── json_demo/ │ ├── go.mod │ ├── go.sum │ ├── index.html │ ├── json_demo.go │ └── main.go ├── lesson23/ │ └── bluebell/ │ ├── .air.conf │ ├── Makefile │ ├── conf/ │ │ └── config.yaml │ ├── controller/ │ │ ├── code.go │ │ ├── community.go │ │ ├── post.go │ │ ├── request.go │ │ ├── response.go │ │ ├── user.go │ │ └── validator.go │ ├── dao/ │ │ ├── mysql/ │ │ │ ├── community.go │ │ │ ├── error_code.go │ │ │ ├── mysql.go │ │ │ └── user.go │ │ └── redis/ │ │ └── redis.go │ ├── go.mod │ ├── go.sum │ ├── logger/ │ │ └── logger.go │ ├── logic/ │ │ ├── community.go │ │ └── user.go │ ├── main.go │ ├── middlewares/ │ │ └── auth.go │ ├── models/ │ │ ├── community.go │ │ ├── create_table.sql │ │ ├── params.go │ │ ├── post.go │ │ ├── struct_test.go │ │ └── user.go │ ├── pkg/ │ │ ├── jwt/ │ │ │ └── jwt.go │ │ └── snowflake/ │ │ └── snowflake.go │ ├── router/ │ │ └── route.go │ └── setting/ │ └── setting.go ├── lesson28/ │ └── bluebell/ │ ├── .air.conf │ ├── Makefile │ ├── conf/ │ │ └── config.yaml │ ├── controller/ │ │ ├── code.go │ │ ├── community.go │ │ ├── post.go │ │ ├── request.go │ │ ├── response.go │ │ ├── user.go │ │ └── validator.go │ ├── dao/ │ │ ├── mysql/ │ │ │ ├── community.go │ │ │ ├── error_code.go │ │ │ ├── mysql.go │ │ │ ├── post.go │ │ │ └── user.go │ │ └── redis/ │ │ └── redis.go │ ├── go.mod │ ├── go.sum │ ├── logger/ │ │ └── logger.go │ ├── logic/ │ │ ├── community.go │ │ ├── post.go │ │ └── user.go │ ├── main.go │ ├── middlewares/ │ │ └── auth.go │ ├── models/ │ │ ├── community.go │ │ ├── create_table.sql │ │ ├── params.go │ │ ├── post.go │ │ ├── struct_test.go │ │ └── user.go │ ├── pkg/ │ │ ├── jwt/ │ │ │ └── jwt.go │ │ └── snowflake/ │ │ └── snowflake.go │ ├── router/ │ │ └── route.go │ └── setting/ │ └── setting.go ├── lesson35/ │ └── bluebell/ │ ├── .air.conf │ ├── Makefile │ ├── bluebell │ ├── conf/ │ │ └── config.yaml │ ├── controller/ │ │ ├── code.go │ │ ├── community.go │ │ ├── post.go │ │ ├── request.go │ │ ├── response.go │ │ ├── user.go │ │ ├── validator.go │ │ └── vote.go │ ├── dao/ │ │ ├── mysql/ │ │ │ ├── community.go │ │ │ ├── error_code.go │ │ │ ├── mysql.go │ │ │ ├── post.go │ │ │ └── user.go │ │ └── redis/ │ │ ├── keys.go │ │ ├── redis.go │ │ └── vote.go │ ├── go.mod │ ├── go.sum │ ├── logger/ │ │ └── logger.go │ ├── logic/ │ │ ├── community.go │ │ ├── post.go │ │ ├── user.go │ │ └── vote.go │ ├── main.go │ ├── middlewares/ │ │ └── auth.go │ ├── models/ │ │ ├── community.go │ │ ├── create_table.sql │ │ ├── params.go │ │ ├── post.go │ │ ├── struct_test.go │ │ └── user.go │ ├── pkg/ │ │ ├── jwt/ │ │ │ └── jwt.go │ │ └── snowflake/ │ │ └── snowflake.go │ ├── router/ │ │ └── route.go │ └── setting/ │ └── setting.go ├── lesson68/ │ └── bluebell/ │ ├── .air.conf │ ├── Makefile │ ├── bluebell │ ├── conf/ │ │ └── config.yaml │ ├── controller/ │ │ ├── code.go │ │ ├── community.go │ │ ├── post.go │ │ ├── request.go │ │ ├── response.go │ │ ├── user.go │ │ ├── validator.go │ │ └── vote.go │ ├── dao/ │ │ ├── mysql/ │ │ │ ├── community.go │ │ │ ├── error_code.go │ │ │ ├── mysql.go │ │ │ ├── post.go │ │ │ └── user.go │ │ └── redis/ │ │ ├── keys.go │ │ ├── post.go │ │ ├── redis.go │ │ └── vote.go │ ├── go.mod │ ├── go.sum │ ├── logger/ │ │ └── logger.go │ ├── logic/ │ │ ├── community.go │ │ ├── post.go │ │ ├── user.go │ │ └── vote.go │ ├── main.go │ ├── middlewares/ │ │ └── auth.go │ ├── models/ │ │ ├── community.go │ │ ├── create_table.sql │ │ ├── params.go │ │ ├── post.go │ │ ├── struct_test.go │ │ └── user.go │ ├── pkg/ │ │ ├── jwt/ │ │ │ └── jwt.go │ │ └── snowflake/ │ │ └── snowflake.go │ ├── router/ │ │ └── route.go │ └── setting/ │ └── setting.go ├── lesson69/ │ └── bluebell/ │ ├── .air.conf │ ├── Makefile │ ├── conf/ │ │ └── config.yaml │ ├── controller/ │ │ ├── code.go │ │ ├── community.go │ │ ├── doc_response_models.go │ │ ├── post.go │ │ ├── request.go │ │ ├── response.go │ │ ├── user.go │ │ ├── validator.go │ │ └── vote.go │ ├── dao/ │ │ ├── mysql/ │ │ │ ├── community.go │ │ │ ├── error_code.go │ │ │ ├── mysql.go │ │ │ ├── post.go │ │ │ └── user.go │ │ └── redis/ │ │ ├── keys.go │ │ ├── post.go │ │ ├── redis.go │ │ └── vote.go │ ├── docs/ │ │ ├── docs.go │ │ ├── swagger.json │ │ └── swagger.yaml │ ├── go.mod │ ├── go.sum │ ├── logger/ │ │ └── logger.go │ ├── logic/ │ │ ├── community.go │ │ ├── post.go │ │ ├── user.go │ │ └── vote.go │ ├── main.go │ ├── middlewares/ │ │ └── auth.go │ ├── models/ │ │ ├── community.go │ │ ├── create_table.sql │ │ ├── params.go │ │ ├── post.go │ │ ├── struct_test.go │ │ └── user.go │ ├── pkg/ │ │ ├── jwt/ │ │ │ └── jwt.go │ │ └── snowflake/ │ │ └── snowflake.go │ ├── router/ │ │ └── route.go │ └── setting/ │ └── setting.go ├── lesson70/ │ └── bluebell/ │ ├── .air.conf │ ├── Makefile │ ├── conf/ │ │ └── config.yaml │ ├── controller/ │ │ ├── code.go │ │ ├── community.go │ │ ├── doc_response_models.go │ │ ├── post.go │ │ ├── post_test.go │ │ ├── request.go │ │ ├── response.go │ │ ├── user.go │ │ ├── validator.go │ │ └── vote.go │ ├── dao/ │ │ ├── mysql/ │ │ │ ├── community.go │ │ │ ├── error_code.go │ │ │ ├── mysql.go │ │ │ ├── post.go │ │ │ ├── post_test.go │ │ │ └── user.go │ │ └── redis/ │ │ ├── keys.go │ │ ├── post.go │ │ ├── redis.go │ │ └── vote.go │ ├── docs/ │ │ ├── docs.go │ │ ├── swagger.json │ │ └── swagger.yaml │ ├── go.mod │ ├── go.sum │ ├── logger/ │ │ └── logger.go │ ├── logic/ │ │ ├── community.go │ │ ├── post.go │ │ ├── user.go │ │ └── vote.go │ ├── main.go │ ├── middlewares/ │ │ └── auth.go │ ├── models/ │ │ ├── community.go │ │ ├── create_table.sql │ │ ├── params.go │ │ ├── post.go │ │ ├── struct_test.go │ │ └── user.go │ ├── pkg/ │ │ ├── jwt/ │ │ │ └── jwt.go │ │ └── snowflake/ │ │ └── snowflake.go │ ├── router/ │ │ └── route.go │ └── setting/ │ └── setting.go ├── lesson77/ │ └── bluebell/ │ ├── .air.conf │ ├── Dockerfile │ ├── Dockerfile.back │ ├── Makefile │ ├── bluebell │ ├── conf/ │ │ └── config.yaml │ ├── controller/ │ │ ├── code.go │ │ ├── community.go │ │ ├── doc_response_models.go │ │ ├── post.go │ │ ├── post_test.go │ │ ├── request.go │ │ ├── response.go │ │ ├── user.go │ │ ├── validator.go │ │ └── vote.go │ ├── dao/ │ │ ├── mysql/ │ │ │ ├── community.go │ │ │ ├── error_code.go │ │ │ ├── mysql.go │ │ │ ├── post.go │ │ │ ├── post_test.go │ │ │ └── user.go │ │ └── redis/ │ │ ├── keys.go │ │ ├── post.go │ │ ├── redis.go │ │ └── vote.go │ ├── docker-compose.yml │ ├── docs/ │ │ ├── docs.go │ │ ├── swagger.json │ │ └── swagger.yaml │ ├── go.mod │ ├── go.sum │ ├── init.sql │ ├── logger/ │ │ └── logger.go │ ├── logic/ │ │ ├── community.go │ │ ├── post.go │ │ ├── user.go │ │ └── vote.go │ ├── main.go │ ├── middlewares/ │ │ ├── auth.go │ │ └── ratelimit.go │ ├── models/ │ │ ├── community.go │ │ ├── create_table.sql │ │ ├── params.go │ │ ├── post.go │ │ ├── struct_test.go │ │ └── user.go │ ├── pkg/ │ │ ├── jwt/ │ │ │ └── jwt.go │ │ └── snowflake/ │ │ └── snowflake.go │ ├── router/ │ │ └── route.go │ ├── setting/ │ │ └── setting.go │ ├── static/ │ │ ├── css/ │ │ │ └── app.6310b918.css │ │ └── js/ │ │ ├── app.ea0d453e.js │ │ └── chunk-vendors.57f9e9d6.js │ ├── templates/ │ │ └── index.html │ └── wait-for.sh ├── lesson80/ │ └── bluebell/ │ ├── .air.conf │ ├── .gitignore │ ├── Dockerfile │ ├── Dockerfile.back │ ├── Makefile │ ├── README.md │ ├── bin/ │ │ └── .gitkeep │ ├── conf/ │ │ └── config.yaml │ ├── controller/ │ │ ├── code.go │ │ ├── community.go │ │ ├── doc_response_models.go │ │ ├── post.go │ │ ├── post_test.go │ │ ├── request.go │ │ ├── response.go │ │ ├── user.go │ │ ├── validator.go │ │ └── vote.go │ ├── dao/ │ │ ├── mysql/ │ │ │ ├── community.go │ │ │ ├── error_code.go │ │ │ ├── mysql.go │ │ │ ├── post.go │ │ │ ├── post_test.go │ │ │ └── user.go │ │ └── redis/ │ │ ├── keys.go │ │ ├── post.go │ │ ├── redis.go │ │ └── vote.go │ ├── docker-compose.yml │ ├── docs/ │ │ ├── docs.go │ │ ├── swagger.json │ │ └── swagger.yaml │ ├── go.mod │ ├── go.sum │ ├── logger/ │ │ └── logger.go │ ├── logic/ │ │ ├── community.go │ │ ├── post.go │ │ ├── user.go │ │ └── vote.go │ ├── main.go │ ├── middlewares/ │ │ ├── auth.go │ │ └── ratelimit.go │ ├── models/ │ │ ├── community.go │ │ ├── create_table.sql │ │ ├── params.go │ │ ├── post.go │ │ ├── struct_test.go │ │ └── user.go │ ├── nginx.conf │ ├── nginx2.conf │ ├── pkg/ │ │ ├── jwt/ │ │ │ └── jwt.go │ │ └── snowflake/ │ │ └── snowflake.go │ ├── router/ │ │ └── route.go │ ├── setting/ │ │ └── setting.go │ ├── sql/ │ │ ├── bluebell_community.sql │ │ ├── bluebell_post.sql │ │ ├── bluebell_user.sql │ │ └── init.sql │ ├── static/ │ │ ├── css/ │ │ │ └── app.0afe9dae.css │ │ └── js/ │ │ ├── app.9f3efa6d.js │ │ └── chunk-vendors.57f9e9d6.js │ ├── templates/ │ │ └── index.html │ └── wait-for.sh ├── mysql_demo/ │ ├── go.mod │ ├── go.sum │ └── main.go ├── ratelimit_demo/ │ ├── go.mod │ ├── go.sum │ └── main.go ├── redis_demo/ │ ├── go.mod │ ├── go.sum │ ├── main.go │ └── new.go ├── shutdown_demo/ │ ├── go.mod │ ├── go.sum │ └── main.go ├── sqlx_demo/ │ ├── go.mod │ ├── go.sum │ └── main.go ├── swagger_demo/ │ ├── docs/ │ │ ├── docs.go │ │ ├── swagger.json │ │ └── swagger.yaml │ ├── go.mod │ ├── go.sum │ ├── main.go │ └── swagger_demo ├── viper_demo/ │ ├── config.yaml │ ├── go.mod │ ├── go.sum │ └── main.go ├── web_app/ │ ├── config.yaml │ ├── dao/ │ │ ├── mysql/ │ │ │ └── mysql.go │ │ └── redis/ │ │ └── redis.go │ ├── go.mod │ ├── go.sum │ ├── logger/ │ │ └── logger.go │ ├── main.go │ ├── routes/ │ │ └── routes.go │ ├── settings/ │ │ └── settings.go │ └── web_app ├── web_app2/ │ ├── config.json │ ├── config.yaml │ ├── dao/ │ │ ├── mysql/ │ │ │ └── mysql.go │ │ └── redis/ │ │ └── redis.go │ ├── go.mod │ ├── go.sum │ ├── logger/ │ │ └── logger.go │ ├── main.go │ ├── routes/ │ │ └── routes.go │ ├── settings/ │ │ └── settings.go │ └── web_app └── zap_demo/ ├── go.mod ├── go.sum └── main.go ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # Binaries for programs and plugins *.exe *.exe~ *.dll *.so *.dylib # Test binary, built with `go test -c` *.test # Output of the go coverage tool, specifically when used with LiteIDE *.out # Dependency directories (remove the comment below to include it) # vendor/ *.log .idea/ ================================================ FILE: README.md ================================================ # Go Web开发进阶实战课程 ## 课程源代码地址 [Github仓库地址](https://github.com/Q1mi/goweb_pro) ## 视频课程地址 - [网易云课堂课程连接](https://study.163.com/course/courseMain.htm?courseId=1210171207&share=2&shareId=480000002229610) - [51CTO课程链接](https://edu.51cto.com/sd/68c23) ================================================ FILE: bluebell/.air.conf ================================================ # [Air](https://github.com/cosmtrek/air) TOML 格式的配置文件 # 工作目录 # 使用 . 或绝对路径,请注意 `tmp_dir` 目录必须在 `root` 目录下 root = "." tmp_dir = "tmp" [build] # 只需要写你平常编译使用的shell命令。你也可以使用 `make` cmd = "go build -o ./tmp/main" # 由`cmd`命令得到的二进制文件名 bin = "tmp/main" # 自定义的二进制,可以添加额外的编译标识例如添加 GIN_MODE=release full_bin = "./tmp/main ./conf/config.yaml" # 监听以下文件扩展名的文件. include_ext = ["go", "tpl", "tmpl", "html", "yaml"] # 忽略这些文件扩展名或目录 exclude_dir = ["assets", "tmp", "vendor", "frontend/node_modules"] # 监听以下指定目录的文件 include_dir = [] # 排除以下文件 exclude_file = [] # 如果文件更改过于频繁,则没有必要在每次更改时都触发构建。可以设置触发构建的延迟时间 delay = 1000 # ms # 发生构建错误时,停止运行旧的二进制文件。 stop_on_error = true # air的日志文件名,该日志文件放置在你的`tmp_dir`中 log = "air_errors.log" [log] # 显示日志时间 time = true [color] # 自定义每个部分显示的颜色。如果找不到颜色,使用原始的应用程序日志。 main = "magenta" watcher = "cyan" build = "yellow" runner = "green" [misc] # 退出时删除tmp目录 clean_on_exit = true ================================================ FILE: bluebell/Makefile ================================================ .PHONY: all build run gotool clean help BINARY="xx" all: gotool build build: CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o ${BINARY} run: @go run ./main.go conf/config.yaml gotool: go fmt ./ go vet ./ clean: @if [ -f ${BINARY} ] ; then rm ${BINARY} ; fi help: @echo "make - 格式化 Go 代码, 并编译生成二进制文件" @echo "make build - 编译 Go 代码, 生成二进制文件" @echo "make run - 直接运行 Go 代码" @echo "make clean - 移除二进制文件和 vim swap files" @echo "make gotool - 运行 Go 工具 'fmt' and 'vet'" ================================================ FILE: bluebell/conf/config.yaml ================================================ name: "bluebell" mode: "dev" port: 8081 version: "v0.0.1" start_time: "2020-07-01" machine_id: 1 log: level: "debug" filename: "web_app.log" max_size: 200 max_age: 30 max_backups: 7 mysql: host: "127.0.0.1" port: 13306 user: "root" password: "root1234" dbname: "bluebell" max_open_conns: 200 max_idle_conns: 50 redis: host: "127.0.0.1" port: 16379 password: "" db: 0 pool_size: 100 ================================================ FILE: bluebell/controller/code.go ================================================ package controller type ResCode int64 const ( CodeSuccess ResCode = 1000 + iota CodeInvalidParam CodeUserExist CodeUserNotExist CodeInvalidPassword CodeServerBusy CodeNeedLogin CodeInvalidToken ) var codeMsgMap = map[ResCode]string{ CodeSuccess: "success", CodeInvalidParam: "请求参数错误", CodeUserExist: "用户名已存在", CodeUserNotExist: "用户名不存在", CodeInvalidPassword: "用户名或密码错误", CodeServerBusy: "服务繁忙", CodeNeedLogin: "需要登录", CodeInvalidToken: "无效的token", } func (c ResCode) Msg() string { msg, ok := codeMsgMap[c] if !ok { msg = codeMsgMap[CodeServerBusy] } return msg } ================================================ FILE: bluebell/controller/request.go ================================================ package controller import ( "errors" "github.com/gin-gonic/gin" ) const CtxUserIDKey = "userID" var ErrorUserNotLogin = errors.New("用户未登录") // getCurrentUser 获取当前登录的用户ID func getCurrentUser(c *gin.Context) (userID int64, err error) { uid, ok := c.Get(CtxUserIDKey) if !ok { err = ErrorUserNotLogin return } userID, ok = uid.(int64) if !ok { err = ErrorUserNotLogin return } return } ================================================ FILE: bluebell/controller/response.go ================================================ package controller import ( "net/http" "github.com/gin-gonic/gin" ) /* { "code": 10000, // 程序中的错误码 "msg": xx, // 提示信息 "data": {}, // 数据 } */ type ResponseData struct { Code ResCode `json:"code"` Msg interface{} `json:"msg"` Data interface{} `json:"data"` } func ResponseError(c *gin.Context, code ResCode) { c.JSON(http.StatusOK, &ResponseData{ Code: code, Msg: code.Msg(), Data: nil, }) } func ResponseErrorWithMsg(c *gin.Context, code ResCode, msg interface{}) { c.JSON(http.StatusOK, &ResponseData{ Code: code, Msg: msg, Data: nil, }) } func ResponseSuccess(c *gin.Context, data interface{}) { c.JSON(http.StatusOK, &ResponseData{ Code: CodeSuccess, Msg: CodeSuccess.Msg(), Data: data, }) } ================================================ FILE: bluebell/controller/user.go ================================================ package controller import ( "bluebell/dao/mysql" "bluebell/logic" "bluebell/models" "errors" "github.com/go-playground/validator/v10" "go.uber.org/zap" "github.com/gin-gonic/gin" ) // SignUpHandler 处理注册请求的函数 func SignUpHandler(c *gin.Context) { // 1. 获取参数和参数校验 p := new(models.ParamSignUp) if err := c.ShouldBindJSON(p); err != nil { // 请求参数有误,直接返回响应 zap.L().Error("SignUp with invalid param", zap.Error(err)) // 判断err是不是validator.ValidationErrors 类型 errs, ok := err.(validator.ValidationErrors) if !ok { ResponseError(c, CodeInvalidParam) return } ResponseErrorWithMsg(c, CodeInvalidParam, removeTopStruct(errs.Translate(trans))) return } // 2. 业务处理 if err := logic.SignUp(p); err != nil { zap.L().Error("logic.SignUp failed", zap.Error(err)) if errors.Is(err, mysql.ErrorUserExist) { ResponseError(c, CodeUserExist) return } ResponseError(c, CodeServerBusy) return } // 3. 返回响应 ResponseSuccess(c, nil) } func LoginHandler(c *gin.Context) { // 1.获取请求参数及参数校验 p := new(models.ParamLogin) if err := c.ShouldBindJSON(p); err != nil { // 请求参数有误,直接返回响应 zap.L().Error("Login with invalid param", zap.Error(err)) // 判断err是不是validator.ValidationErrors 类型 errs, ok := err.(validator.ValidationErrors) if !ok { ResponseError(c, CodeInvalidParam) return } ResponseErrorWithMsg(c, CodeInvalidParam, removeTopStruct(errs.Translate(trans))) return } // 2.业务逻辑处理 token, err := logic.Login(p) if err != nil { zap.L().Error("logic.Login failed", zap.String("username", p.Username), zap.Error(err)) if errors.Is(err, mysql.ErrorUserNotExist) { ResponseError(c, CodeUserNotExist) return } ResponseError(c, CodeInvalidPassword) return } // 3.返回响应 ResponseSuccess(c, token) } ================================================ FILE: bluebell/controller/validator.go ================================================ package controller import ( "bluebell/models" "fmt" "reflect" "strings" "github.com/gin-gonic/gin/binding" "github.com/go-playground/locales/en" "github.com/go-playground/locales/zh" ut "github.com/go-playground/universal-translator" "github.com/go-playground/validator/v10" enTranslations "github.com/go-playground/validator/v10/translations/en" zhTranslations "github.com/go-playground/validator/v10/translations/zh" ) // 定义一个全局翻译器T var trans ut.Translator // InitTrans 初始化翻译器 func InitTrans(locale string) (err error) { // 修改gin框架中的Validator引擎属性,实现自定制 if v, ok := binding.Validator.Engine().(*validator.Validate); ok { // 注册一个获取json tag的自定义方法 v.RegisterTagNameFunc(func(fld reflect.StructField) string { name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0] if name == "-" { return "" } return name }) // 为SignUpParam注册自定义校验方法 v.RegisterStructValidation(SignUpParamStructLevelValidation, models.ParamSignUp{}) zhT := zh.New() // 中文翻译器 enT := en.New() // 英文翻译器 // 第一个参数是备用(fallback)的语言环境 // 后面的参数是应该支持的语言环境(支持多个) // uni := ut.New(zhT, zhT) 也是可以的 uni := ut.New(enT, zhT, enT) // locale 通常取决于 http 请求头的 'Accept-Language' var ok bool // 也可以使用 uni.FindTranslator(...) 传入多个locale进行查找 trans, ok = uni.GetTranslator(locale) if !ok { return fmt.Errorf("uni.GetTranslator(%s) failed", locale) } // 注册翻译器 switch locale { case "en": err = enTranslations.RegisterDefaultTranslations(v, trans) case "zh": err = zhTranslations.RegisterDefaultTranslations(v, trans) default: err = enTranslations.RegisterDefaultTranslations(v, trans) } return } return } // removeTopStruct 去除提示信息中的结构体名称 func removeTopStruct(fields map[string]string) map[string]string { res := map[string]string{} for field, err := range fields { res[field[strings.Index(field, ".")+1:]] = err } return res } // SignUpParamStructLevelValidation 自定义SignUpParam结构体校验函数 func SignUpParamStructLevelValidation(sl validator.StructLevel) { su := sl.Current().Interface().(models.ParamSignUp) if su.Password != su.RePassword { // 输出错误提示信息,最后一个参数就是传递的param sl.ReportError(su.RePassword, "re_password", "RePassword", "eqfield", "password") } } ================================================ FILE: bluebell/dao/mysql/mysql.go ================================================ package mysql import ( "bluebell/setting" "fmt" _ "github.com/go-sql-driver/mysql" "github.com/jmoiron/sqlx" ) var db *sqlx.DB // Init 初始化MySQL连接 func Init(cfg *setting.MySQLConfig) (err error) { // "user:password@tcp(host:port)/dbname" dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?parseTime=true&loc=Local", cfg.User, cfg.Password, cfg.Host, cfg.Port, cfg.DB) db, err = sqlx.Connect("mysql", dsn) if err != nil { return } db.SetMaxOpenConns(cfg.MaxOpenConns) db.SetMaxIdleConns(cfg.MaxIdleConns) return } // Close 关闭MySQL连接 func Close() { _ = db.Close() } ================================================ FILE: bluebell/dao/mysql/user.go ================================================ package mysql import ( "bluebell/models" "crypto/md5" "database/sql" "encoding/hex" "errors" ) // 把每一步数据库操作封装成函数 // 待logic层根据业务需求调用 const secret = "liwenzhou.com" var ( ErrorUserExist = errors.New("用户已存在") ErrorUserNotExist = errors.New("用户不存在") ErrorInvalidPassword = errors.New("用户名或密码错误") ) // CheckUserExist 检查指定用户名的用户是否存在 func CheckUserExist(username string) (err error) { sqlStr := `select count(user_id) from user where username = ?` var count int64 if err := db.Get(&count, sqlStr, username); err != nil { return err } if count > 0 { return ErrorUserExist } return } // InsertUser 想数据库中插入一条新的用户记录 func InsertUser(user *models.User) (err error) { // 对密码进行加密 user.Password = encryptPassword(user.Password) // 执行SQL语句入库 sqlStr := `insert into user(user_id, username, password) values(?,?,?)` _, err = db.Exec(sqlStr, user.UserID, user.Username, user.Password) return } // encryptPassword 密码加密 func encryptPassword(oPassword string) string { h := md5.New() h.Write([]byte(secret)) return hex.EncodeToString(h.Sum([]byte(oPassword))) } func Login(user *models.User) (err error) { oPassword := user.Password // 用户登录的密码 sqlStr := `select user_id, username, password from user where username=?` err = db.Get(user, sqlStr, user.Username) if err == sql.ErrNoRows { return ErrorUserNotExist } if err != nil { // 查询数据库失败 return err } // 判断密码是否正确 password := encryptPassword(oPassword) if password != user.Password { return ErrorInvalidPassword } return } ================================================ FILE: bluebell/dao/redis/redis.go ================================================ package redis import ( "bluebell/setting" "fmt" "github.com/go-redis/redis" ) var ( client *redis.Client Nil = redis.Nil ) // Init 初始化连接 func Init(cfg *setting.RedisConfig) (err error) { client = redis.NewClient(&redis.Options{ Addr: fmt.Sprintf("%s:%d", cfg.Host, cfg.Port), Password: cfg.Password, // no password set DB: cfg.DB, // use default DB PoolSize: cfg.PoolSize, MinIdleConns: cfg.MinIdleConns, }) _, err = client.Ping().Result() if err != nil { return err } return nil } func Close() { _ = client.Close() } ================================================ FILE: bluebell/go.mod ================================================ module bluebell go 1.14 require ( github.com/bwmarrin/snowflake v0.3.0 github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/fsnotify/fsnotify v1.4.9 github.com/gin-gonic/gin v1.6.3 github.com/go-playground/locales v0.13.0 github.com/go-playground/universal-translator v0.17.0 github.com/go-playground/validator/v10 v10.2.0 github.com/go-redis/redis v6.15.8+incompatible github.com/go-sql-driver/mysql v1.5.0 github.com/jmoiron/sqlx v1.2.0 github.com/natefinch/lumberjack v2.0.0+incompatible github.com/onsi/ginkgo v1.14.0 // indirect github.com/sony/sonyflake v1.0.0 // indirect github.com/spf13/viper v1.7.0 go.uber.org/zap v1.15.0 gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect ) ================================================ FILE: bluebell/go.sum ================================================ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0= github.com/bwmarrin/snowflake v0.3.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/E9WsDpxqwE= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/deckarep/golang-set v1.7.1/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14= github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY= github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= github.com/go-redis/redis v6.15.8+incompatible h1:BKZuG6mCnRj5AOaWJXoCgf6rqTYnYJLe4en2hxT7r9o= github.com/go-redis/redis v6.15.8+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA= github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-sqlite3 v1.9.0 h1:pDRiWfl+++eC2FEFRy6jXmQlvp4Yh3z1MJKg4UeYM/4= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/natefinch/lumberjack v2.0.0+incompatible h1:4QJd3OLAMgj7ph+yZTuX13Ld4UpgHp07nNdFX7mqFfM= github.com/natefinch/lumberjack v2.0.0+incompatible/go.mod h1:Wi9p2TTF5DG5oU+6YfsmYQpsTIOm0B1VNzQg9Mw6nPk= github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/sony/sonyflake v1.0.0 h1:MpU6Ro7tfXwgn2l5eluf9xQvQJDROTBImNCfRXn/YeM= github.com/sony/sonyflake v1.0.0/go.mod h1:Jv3cfhf/UFtolOTTRd3q4Nl6ENqM+KfyZ5PseKfZGF4= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.15.0 h1:ZZCA22JRF2gQE5FoNmhmrf7jeJJ2uhqDUNRYKm8dvmM= go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 h1:AeiKBIuRw3UomYXSbLy0Mc2dDLfdtbT/IVn4keq83P0= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 h1:DYfZAGf2WMFjMxbgTjaC+2HC7NkNAQs+6Q8b9WEB/F4= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc h1:NCy3Ohtk6Iny5V/reW2Ktypo4zIpWBdRJ1uFMjBxdg8= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= ================================================ FILE: bluebell/logger/logger.go ================================================ package logger import ( "bluebell/setting" "net" "net/http" "net/http/httputil" "os" "runtime/debug" "strings" "time" "github.com/gin-gonic/gin" "github.com/natefinch/lumberjack" "go.uber.org/zap" "go.uber.org/zap/zapcore" ) var lg *zap.Logger // Init 初始化lg func Init(cfg *setting.LogConfig, mode string) (err error) { writeSyncer := getLogWriter(cfg.Filename, cfg.MaxSize, cfg.MaxBackups, cfg.MaxAge) encoder := getEncoder() var l = new(zapcore.Level) err = l.UnmarshalText([]byte(cfg.Level)) if err != nil { return } var core zapcore.Core if mode == "dev" { // 进入开发模式,日志输出到终端 consoleEncoder := zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig()) core = zapcore.NewTee( zapcore.NewCore(encoder, writeSyncer, l), zapcore.NewCore(consoleEncoder, zapcore.Lock(os.Stdout), zapcore.DebugLevel), ) } else { core = zapcore.NewCore(encoder, writeSyncer, l) } lg = zap.New(core, zap.AddCaller()) zap.ReplaceGlobals(lg) zap.L().Info("init logger success") return } func getEncoder() zapcore.Encoder { encoderConfig := zap.NewProductionEncoderConfig() encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder encoderConfig.TimeKey = "time" encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder encoderConfig.EncodeDuration = zapcore.SecondsDurationEncoder encoderConfig.EncodeCaller = zapcore.ShortCallerEncoder return zapcore.NewJSONEncoder(encoderConfig) } func getLogWriter(filename string, maxSize, maxBackup, maxAge int) zapcore.WriteSyncer { lumberJackLogger := &lumberjack.Logger{ Filename: filename, MaxSize: maxSize, MaxBackups: maxBackup, MaxAge: maxAge, } return zapcore.AddSync(lumberJackLogger) } // GinLogger 接收gin框架默认的日志 func GinLogger() gin.HandlerFunc { return func(c *gin.Context) { start := time.Now() path := c.Request.URL.Path query := c.Request.URL.RawQuery c.Next() cost := time.Since(start) lg.Info(path, zap.Int("status", c.Writer.Status()), zap.String("method", c.Request.Method), zap.String("path", path), zap.String("query", query), zap.String("ip", c.ClientIP()), zap.String("user-agent", c.Request.UserAgent()), zap.String("errors", c.Errors.ByType(gin.ErrorTypePrivate).String()), zap.Duration("cost", cost), ) } } // GinRecovery recover掉项目可能出现的panic,并使用zap记录相关日志 func GinRecovery(stack bool) gin.HandlerFunc { return func(c *gin.Context) { defer func() { if err := recover(); err != nil { // Check for a broken connection, as it is not really a // condition that warrants a panic stack trace. var brokenPipe bool if ne, ok := err.(*net.OpError); ok { if se, ok := ne.Err.(*os.SyscallError); ok { if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") { brokenPipe = true } } } httpRequest, _ := httputil.DumpRequest(c.Request, false) if brokenPipe { lg.Error(c.Request.URL.Path, zap.Any("error", err), zap.String("request", string(httpRequest)), ) // If the connection is dead, we can't write a status to it. c.Error(err.(error)) // nolint: errcheck c.Abort() return } if stack { lg.Error("[Recovery from panic]", zap.Any("error", err), zap.String("request", string(httpRequest)), zap.String("stack", string(debug.Stack())), ) } else { lg.Error("[Recovery from panic]", zap.Any("error", err), zap.String("request", string(httpRequest)), ) } c.AbortWithStatus(http.StatusInternalServerError) } }() c.Next() } } ================================================ FILE: bluebell/logic/user.go ================================================ package logic import ( "bluebell/dao/mysql" "bluebell/models" "bluebell/pkg/jwt" "bluebell/pkg/snowflake" ) // 存放业务逻辑的代码 func SignUp(p *models.ParamSignUp) (err error) { // 1.判断用户存不存在 if err := mysql.CheckUserExist(p.Username); err != nil { return err } // 2.生成UID userID := snowflake.GenID() // 构造一个User实例 user := &models.User{ UserID: userID, Username: p.Username, Password: p.Password, } // 3.保存进数据库 return mysql.InsertUser(user) } func Login(p *models.ParamLogin) (token string, err error) { user := &models.User{ Username: p.Username, Password: p.Password, } // 传递的是指针,就能拿到user.UserID if err := mysql.Login(user); err != nil { return "", err } // 生成JWT return jwt.GenToken(user.UserID, user.Username) } ================================================ FILE: bluebell/main.go ================================================ package main import ( "bluebell/controller" "bluebell/dao/mysql" "bluebell/dao/redis" "bluebell/logger" "bluebell/pkg/snowflake" "bluebell/router" "bluebell/setting" "fmt" "os" ) func main() { if len(os.Args) < 2 { fmt.Println("need config file.eg: bluebell config.yaml") return } // 加载配置 if err := setting.Init(os.Args[1]); err != nil { fmt.Printf("load config failed, err:%v\n", err) return } if err := logger.Init(setting.Conf.LogConfig, setting.Conf.Mode); err != nil { fmt.Printf("init logger failed, err:%v\n", err) return } if err := mysql.Init(setting.Conf.MySQLConfig); err != nil { fmt.Printf("init mysql failed, err:%v\n", err) return } defer mysql.Close() // 程序退出关闭数据库连接 if err := redis.Init(setting.Conf.RedisConfig); err != nil { fmt.Printf("init redis failed, err:%v\n", err) return } defer redis.Close() if err := snowflake.Init(setting.Conf.StartTime, setting.Conf.MachineID); err != nil { fmt.Printf("init snowflake failed, err:%v\n", err) return } // 初始化gin框架内置的校验器使用的翻译器 if err := controller.InitTrans("zh"); err != nil { fmt.Printf("init validator trans failed, err:%v\n", err) return } // 注册路由 r := router.SetupRouter(setting.Conf.Mode) err := r.Run(fmt.Sprintf(":%d", setting.Conf.Port)) if err != nil { fmt.Printf("run server failed, err:%v\n", err) return } } ================================================ FILE: bluebell/middlewares/auth.go ================================================ package middlewares import ( "bluebell/controller" "bluebell/pkg/jwt" "strings" "github.com/gin-gonic/gin" ) // JWTAuthMiddleware 基于JWT的认证中间件 func JWTAuthMiddleware() func(c *gin.Context) { return func(c *gin.Context) { // 客户端携带Token有三种方式 1.放在请求头 2.放在请求体 3.放在URI // 这里假设Token放在Header的Authorization中,并使用Bearer开头 // Authorization: Bearer xxxxxxx.xxx.xxx / X-TOKEN: xxx.xxx.xx // 这里的具体实现方式要依据你的实际业务情况决定 authHeader := c.Request.Header.Get("Authorization") if authHeader == "" { controller.ResponseError(c, controller.CodeNeedLogin) c.Abort() return } // 按空格分割 parts := strings.SplitN(authHeader, " ", 2) if !(len(parts) == 2 && parts[0] == "Bearer") { controller.ResponseError(c, controller.CodeInvalidToken) c.Abort() return } // parts[1]是获取到的tokenString,我们使用之前定义好的解析JWT的函数来解析它 mc, err := jwt.ParseToken(parts[1]) if err != nil { controller.ResponseError(c, controller.CodeInvalidToken) c.Abort() return } // 将当前请求的userID信息保存到请求的上下文c上 c.Set(controller.CtxUserIDKey, mc.UserID) c.Next() // 后续的处理请求的函数中 可以用过c.Get(CtxUserIDKey) 来获取当前请求的用户信息 } } ================================================ FILE: bluebell/models/create_table.sql ================================================ CREATE TABLE `user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `user_id` bigint(20) NOT NULL, `username` varchar(64) COLLATE utf8mb4_general_ci NOT NULL, `password` varchar(64) COLLATE utf8mb4_general_ci NOT NULL, `email` varchar(64) COLLATE utf8mb4_general_ci, `gender` tinyint(4) NOT NULL DEFAULT '0', `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP, `update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `idx_username` (`username`) USING BTREE, UNIQUE KEY `idx_user_id` (`user_id`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; ================================================ FILE: bluebell/models/params.go ================================================ package models // 定义请求的参数结构体 // ParamSignUp 注册请求参数 type ParamSignUp struct { Username string `json:"username" binding:"required"` Password string `json:"password" binding:"required"` RePassword string `json:"re_password" binding:"required,eqfield=Password"` } // ParamLogin 登录请求参数 type ParamLogin struct { Username string `json:"username" binding:"required"` Password string `json:"password" binding:"required"` } ================================================ FILE: bluebell/models/user.go ================================================ package models type User struct { UserID int64 `db:"user_id"` Username string `db:"username"` Password string `db:"password"` } ================================================ FILE: bluebell/pkg/jwt/jwt.go ================================================ package jwt import ( "errors" "time" "github.com/dgrijalva/jwt-go" ) const TokenExpireDuration = time.Hour * 2 var mySecret = []byte("夏天夏天悄悄过去") // MyClaims 自定义声明结构体并内嵌jwt.StandardClaims // jwt包自带的jwt.StandardClaims只包含了官方字段 // 我们这里需要额外记录一个username字段,所以要自定义结构体 // 如果想要保存更多信息,都可以添加到这个结构体中 type MyClaims struct { UserID int64 `json:"user_id"` Username string `json:"username"` jwt.StandardClaims } // GenToken 生成JWT func GenToken(userID int64, username string) (string, error) { // 创建一个我们自己的声明的数据 c := MyClaims{ userID, "username", // 自定义字段 jwt.StandardClaims{ ExpiresAt: time.Now().Add(TokenExpireDuration).Unix(), // 过期时间 Issuer: "bluebell", // 签发人 }, } // 使用指定的签名方法创建签名对象 token := jwt.NewWithClaims(jwt.SigningMethodHS256, c) // 使用指定的secret签名并获得完整的编码后的字符串token return token.SignedString(mySecret) } // ParseToken 解析JWT func ParseToken(tokenString string) (*MyClaims, error) { // 解析token var mc = new(MyClaims) token, err := jwt.ParseWithClaims(tokenString, mc, func(token *jwt.Token) (i interface{}, err error) { return mySecret, nil }) if err != nil { return nil, err } if token.Valid { // 校验token return mc, nil } return nil, errors.New("invalid token") } ================================================ FILE: bluebell/pkg/snowflake/snowflake.go ================================================ package snowflake import ( "time" sf "github.com/bwmarrin/snowflake" ) var node *sf.Node func Init(startTime string, machineID int64) (err error) { var st time.Time st, err = time.Parse("2006-01-02", startTime) if err != nil { return } sf.Epoch = st.UnixNano() / 1000000 node, err = sf.NewNode(machineID) return } func GenID() int64 { return node.Generate().Int64() } ================================================ FILE: bluebell/router/route.go ================================================ package router import ( "bluebell/controller" "bluebell/logger" "bluebell/middlewares" "net/http" "github.com/gin-gonic/gin" ) func SetupRouter(mode string) *gin.Engine { if mode == gin.ReleaseMode { gin.SetMode(gin.ReleaseMode) // gin设置成发布模式 } r := gin.New() r.Use(logger.GinLogger(), logger.GinRecovery(true)) // 注册 r.POST("/signup", controller.SignUpHandler) // 登录 r.POST("/login", controller.LoginHandler) r.GET("/ping", middlewares.JWTAuthMiddleware(), func(c *gin.Context) { // 如果是登录的用户,判断请求头中是否有 有效的JWT ? c.JSON(http.StatusOK, gin.H{ "msg": "ok", }) }) r.NoRoute(func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "msg": "404", }) }) return r } ================================================ FILE: bluebell/setting/setting.go ================================================ package setting import ( "fmt" "github.com/fsnotify/fsnotify" "github.com/spf13/viper" ) var Conf = new(AppConfig) type AppConfig struct { Name string `mapstructure:"name"` Mode string `mapstructure:"mode"` Version string `mapstructure:"version"` StartTime string `mapstructure:"start_time"` MachineID int64 `mapstructure:"machine_id"` Port int `mapstructure:"port"` *LogConfig `mapstructure:"log"` *MySQLConfig `mapstructure:"mysql"` *RedisConfig `mapstructure:"redis"` } type MySQLConfig struct { Host string `mapstructure:"host"` User string `mapstructure:"user"` Password string `mapstructure:"password"` DB string `mapstructure:"dbname"` Port int `mapstructure:"port"` MaxOpenConns int `mapstructure:"max_open_conns"` MaxIdleConns int `mapstructure:"max_idle_conns"` } type RedisConfig struct { Host string `mapstructure:"host"` Password string `mapstructure:"password"` Port int `mapstructure:"port"` DB int `mapstructure:"db"` PoolSize int `mapstructure:"pool_size"` MinIdleConns int `mapstructure:"min_idle_conns"` } type LogConfig struct { Level string `mapstructure:"level"` Filename string `mapstructure:"filename"` MaxSize int `mapstructure:"max_size"` MaxAge int `mapstructure:"max_age"` MaxBackups int `mapstructure:"max_backups"` } func Init(filePath string) (err error) { // 方式1:直接指定配置文件路径(相对路径或者绝对路径) // 相对路径:相对执行的可执行文件的相对路径 //viper.SetConfigFile("./conf/config.yaml") // 绝对路径:系统中实际的文件路径 //viper.SetConfigFile("/Users/liwenzhou/Desktop/bluebell/conf/config.yaml") // 方式2:指定配置文件名和配置文件的位置,viper自行查找可用的配置文件 // 配置文件名不需要带后缀 // 配置文件位置可配置多个 //viper.SetConfigName("config") // 指定配置文件名(不带后缀) //viper.AddConfigPath(".") // 指定查找配置文件的路径(这里使用相对路径) //viper.AddConfigPath("./conf") // 指定查找配置文件的路径(这里使用相对路径) // 基本上是配合远程配置中心使用的,告诉viper当前的数据使用什么格式去解析 //viper.SetConfigType("json") viper.SetConfigFile(filePath) err = viper.ReadInConfig() // 读取配置信息 if err != nil { // 读取配置信息失败 fmt.Printf("viper.ReadInConfig failed, err:%v\n", err) return } // 把读取到的配置信息反序列化到 Conf 变量中 if err := viper.Unmarshal(Conf); err != nil { fmt.Printf("viper.Unmarshal failed, err:%v\n", err) } viper.WatchConfig() viper.OnConfigChange(func(in fsnotify.Event) { fmt.Println("配置文件修改了...") if err := viper.Unmarshal(Conf); err != nil { fmt.Printf("viper.Unmarshal failed, err:%v\n", err) } }) return } ================================================ FILE: docker_demo/Dockerfile ================================================ FROM golang:alpine AS builder # 为我们的镜像设置必要的环境变量 ENV GO111MODULE=on \ CGO_ENABLED=0 \ GOOS=linux \ GOARCH=amd64 # 移动到工作目录:/build WORKDIR /build # 将代码复制到容器中 COPY . . # 将我们的代码编译成二进制可执行文件 app RUN go build -o app . ################### # 接下来创建一个小镜像 ################### FROM scratch # 从builder镜像中把/dist/app 拷贝到当前目录 COPY --from=builder /build/app / # 需要运行的命令 ENTRYPOINT ["/app"] ================================================ FILE: docker_demo/Dockerfile.back ================================================ FROM golang:alpine # 为我们的镜像设置必要的环境变量 ENV GO111MODULE=on \ CGO_ENABLED=0 \ GOOS=linux \ GOARCH=amd64 # 移动到工作目录:/build WORKDIR /build # 将代码复制到容器中 COPY . . # 将我们的代码编译成二进制可执行文件app RUN go build -o app . # 声明服务端口 EXPOSE 8888 # 启动容器时运行的命令 CMD ["/build/app"] ================================================ FILE: docker_demo/go.mod ================================================ module "docker_demo" ================================================ FILE: docker_demo/main.go ================================================ package main import ( "fmt" "net/http" ) func main() { http.HandleFunc("/", hello) server := &http.Server{ Addr: ":8888", } fmt.Println("server startup...") if err := server.ListenAndServe(); err != nil { fmt.Printf("server startup failed, err:%v\n", err) } } func hello(w http.ResponseWriter, _ *http.Request) { w.Write([]byte("hello liwenzhou.com!")) } ================================================ FILE: flag_demo/args_demo.go ================================================ package main import ( "fmt" "os" ) //os.Args demo func main() { // os.Args是一个[]string // os.Args[0] 是当前执行的程序 fmt.Println(os.Args) if len(os.Args) > 0 { for index, arg := range os.Args { fmt.Printf("args[%d]=%v\n", index, arg) } } } ================================================ FILE: flag_demo/flag_demo.go ================================================ package main import ( "flag" "fmt" "time" ) func main() { //定义命令行参数方式1 var name string var age int var married bool var delay time.Duration flag.StringVar(&name, "name", "张三", "姓名") flag.IntVar(&age, "age", 18, "年龄") flag.BoolVar(&married, "married", false, "婚否") flag.DurationVar(&delay, "d", 0, "延迟的时间间隔") //解析命令行参数 flag.Parse() fmt.Println(name, age, married, delay) //返回命令行参数后的其他参数 fmt.Println(flag.Args()) //返回命令行参数后的其他参数个数 fmt.Println(flag.NArg()) //返回使用的命令行参数个数 fmt.Println(flag.NFlag()) } ================================================ FILE: flag_demo/go.mod ================================================ module flag_demo go 1.14 ================================================ FILE: gin_demo/go.mod ================================================ module gin_demo go 1.14 require github.com/gin-gonic/gin v1.6.3 ================================================ FILE: gin_demo/go.sum ================================================ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14= github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY= github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= ================================================ FILE: gin_demo/main.go ================================================ package main import ( "fmt" "github.com/gin-gonic/gin" "net/http" ) func func1(c *gin.Context){ fmt.Println("func1") } func func2(c *gin.Context){ fmt.Println("func2 before") c.Next() fmt.Println("func2 after") } func func3(c *gin.Context){ fmt.Println("func3") //c.Abort() } func func4(c *gin.Context){ fmt.Println("func4") c.Set("name", "q1mi") } func func5(c *gin.Context){ fmt.Println("func5") v, ok := c.Get("name") if ok{ vStr := v.(string) // 类型转换 fmt.Println(vStr) } } func main() { r := gin.Default() r.GET("/hello", func(c *gin.Context) { c.String(http.StatusOK, "ok") }) shopGroup := r.Group("/shop", func1, func2) shopGroup.Use(func3) { shopGroup.GET("/index", func4, func5) } r.Run() } ================================================ FILE: json_demo/go.mod ================================================ module json_demo go 1.14 require github.com/gin-gonic/gin v1.6.3 ================================================ FILE: json_demo/go.sum ================================================ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14= github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY= github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= ================================================ FILE: json_demo/index.html ================================================ json demo

json demo

================================================ FILE: json_demo/json_demo.go ================================================ package main import ( "encoding/json" "fmt" "math" ) type MyData struct { ID int64 `json:"id,string"` Name string `json:"name"` } //func (d *MyData) Unmarshal() { // //} // //func (d *MyData) Marshal() { // //} // 第一层: // 第二层: // 第五层: // 第九层: func main() { // 序列化: 后端的数据->JSON格式的数据 d1 := MyData{ ID: math.MaxInt64, Name: "七米", } // json序列化 b, err := json.Marshal(d1) if err != nil { fmt.Println(err) return } fmt.Println(string(b)) // 反序列化:JSON格式的数据 -> Go语言中的数据 s := `{"id":"9223372036854775807","name":"七米"}` var d2 MyData if err := json.Unmarshal([]byte(s), &d2); err != nil { fmt.Println(err) return } fmt.Printf("%#v type:%T\n", d2, d2.ID) } ================================================ FILE: json_demo/main.go ================================================ package main import ( "net/http" "github.com/gin-gonic/gin" ) // ??? // 在后端使用的时候还是用int64 // 进行json序列化与反序列化的时候就使用字符串 type Data struct { ID int64 `json:"id"` } func main() { r := gin.Default() r.LoadHTMLFiles("index.html") r.GET("/index", func(c *gin.Context) { c.HTML(http.StatusOK, "index.html", nil) }) r.GET("/data", func(c *gin.Context) { // int64 // math.MaxInt64 --> 1<<63 -1 // math.MinInt64 --> -1 << 63 // 1234567891234568000 c.JSON(http.StatusOK, Data{1234567891234567889}) }) r.Run(":8990") } ================================================ FILE: lesson23/bluebell/.air.conf ================================================ # [Air](https://github.com/cosmtrek/air) TOML 格式的配置文件 # 工作目录 # 使用 . 或绝对路径,请注意 `tmp_dir` 目录必须在 `root` 目录下 root = "." tmp_dir = "tmp" [build] # 只需要写你平常编译使用的shell命令。你也可以使用 `make` cmd = "go build -o ./tmp/main" # 由`cmd`命令得到的二进制文件名 bin = "tmp/main" # 自定义的二进制,可以添加额外的编译标识例如添加 GIN_MODE=release full_bin = "./tmp/main ./conf/config.yaml" # 监听以下文件扩展名的文件. include_ext = ["go", "tpl", "tmpl", "html", "yaml"] # 忽略这些文件扩展名或目录 exclude_dir = ["assets", "tmp", "vendor", "frontend/node_modules"] # 监听以下指定目录的文件 include_dir = [] # 排除以下文件 exclude_file = [] # 如果文件更改过于频繁,则没有必要在每次更改时都触发构建。可以设置触发构建的延迟时间 delay = 1000 # ms # 发生构建错误时,停止运行旧的二进制文件。 stop_on_error = true # air的日志文件名,该日志文件放置在你的`tmp_dir`中 log = "air_errors.log" [log] # 显示日志时间 time = true [color] # 自定义每个部分显示的颜色。如果找不到颜色,使用原始的应用程序日志。 main = "magenta" watcher = "cyan" build = "yellow" runner = "green" [misc] # 退出时删除tmp目录 clean_on_exit = true ================================================ FILE: lesson23/bluebell/Makefile ================================================ .PHONY: all build run gotool clean help BINARY="xx" all: gotool build build: CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o ${BINARY} run: @go run ./main.go conf/config.yaml gotool: go fmt ./ go vet ./ clean: @if [ -f ${BINARY} ] ; then rm ${BINARY} ; fi help: @echo "make - 格式化 Go 代码, 并编译生成二进制文件" @echo "make build - 编译 Go 代码, 生成二进制文件" @echo "make run - 直接运行 Go 代码" @echo "make clean - 移除二进制文件和 vim swap files" @echo "make gotool - 运行 Go 工具 'fmt' and 'vet'" ================================================ FILE: lesson23/bluebell/conf/config.yaml ================================================ name: "bluebell" mode: "dev" port: 8084 version: "v0.0.1" start_time: "2020-07-01" machine_id: 1 auth: jwt_expire: 8760 log: level: "debug" filename: "web_app.log" max_size: 200 max_age: 30 max_backups: 7 mysql: host: "127.0.0.1" port: 13306 user: "root" password: "root1234" dbname: "bluebell" max_open_conns: 200 max_idle_conns: 50 redis: host: "127.0.0.1" port: 16379 password: "" db: 0 pool_size: 100 ================================================ FILE: lesson23/bluebell/controller/code.go ================================================ package controller type ResCode int64 const ( CodeSuccess ResCode = 1000 + iota CodeInvalidParam CodeUserExist CodeUserNotExist CodeInvalidPassword CodeServerBusy CodeNeedLogin CodeInvalidToken ) var codeMsgMap = map[ResCode]string{ CodeSuccess: "success", CodeInvalidParam: "请求参数错误", CodeUserExist: "用户名已存在", CodeUserNotExist: "用户名不存在", CodeInvalidPassword: "用户名或密码错误", CodeServerBusy: "服务繁忙", CodeNeedLogin: "需要登录", CodeInvalidToken: "无效的token", } func (c ResCode) Msg() string { msg, ok := codeMsgMap[c] if !ok { msg = codeMsgMap[CodeServerBusy] } return msg } ================================================ FILE: lesson23/bluebell/controller/community.go ================================================ package controller import ( "bluebell/logic" "strconv" "github.com/gin-gonic/gin" "go.uber.org/zap" ) // ---- 跟社区相关的 ---- func CommunityHandler(c *gin.Context) { // 查询到所有的社区(community_id, community_name) 以列表的形式返回 data, err := logic.GetCommunityList() if err != nil { zap.L().Error("logic.GetCommunityList() failed", zap.Error(err)) ResponseError(c, CodeServerBusy) // 不轻易把服务端报错暴露给外面 return } ResponseSuccess(c, data) } // CommunityDetailHandler 社区分类详情 func CommunityDetailHandler(c *gin.Context) { // 1. 获取社区id idStr := c.Param("id") // 获取URL参数 id, err := strconv.ParseInt(idStr, 10, 64) if err != nil { ResponseError(c, CodeInvalidParam) return } // 2. 根据id获取社区详情 data, err := logic.GetCommunityDetail(id) if err != nil { zap.L().Error("logic.GetCommunityList() failed", zap.Error(err)) ResponseError(c, CodeServerBusy) // 不轻易把服务端报错暴露给外面 return } ResponseSuccess(c, data) } ================================================ FILE: lesson23/bluebell/controller/post.go ================================================ package controller import "github.com/gin-gonic/gin" func CreatePostHandler(c *gin.Context) { // 1. 获取参数及参数的校验 //c.ShouldBindJSON() // 2. 创建帖子 // 3. 返回响应 } ================================================ FILE: lesson23/bluebell/controller/request.go ================================================ package controller import ( "errors" "github.com/gin-gonic/gin" ) const CtxUserIDKey = "userID" var ErrorUserNotLogin = errors.New("用户未登录") // getCurrentUser 获取当前登录的用户ID func getCurrentUser(c *gin.Context) (userID int64, err error) { uid, ok := c.Get(CtxUserIDKey) if !ok { err = ErrorUserNotLogin return } userID, ok = uid.(int64) if !ok { err = ErrorUserNotLogin return } return } ================================================ FILE: lesson23/bluebell/controller/response.go ================================================ package controller import ( "net/http" "github.com/gin-gonic/gin" ) /* { "code": 10000, // 程序中的错误码 "msg": xx, // 提示信息 "data": {}, // 数据 } */ type ResponseData struct { Code ResCode `json:"code"` Msg interface{} `json:"msg"` Data interface{} `json:"data"` } func ResponseError(c *gin.Context, code ResCode) { c.JSON(http.StatusOK, &ResponseData{ Code: code, Msg: code.Msg(), Data: nil, }) } func ResponseErrorWithMsg(c *gin.Context, code ResCode, msg interface{}) { c.JSON(http.StatusOK, &ResponseData{ Code: code, Msg: msg, Data: nil, }) } func ResponseSuccess(c *gin.Context, data interface{}) { c.JSON(http.StatusOK, &ResponseData{ Code: CodeSuccess, Msg: CodeSuccess.Msg(), Data: data, }) } ================================================ FILE: lesson23/bluebell/controller/user.go ================================================ package controller import ( "bluebell/dao/mysql" "bluebell/logic" "bluebell/models" "errors" "github.com/go-playground/validator/v10" "go.uber.org/zap" "github.com/gin-gonic/gin" ) // SignUpHandler 处理注册请求的函数 func SignUpHandler(c *gin.Context) { // 1. 获取参数和参数校验 p := new(models.ParamSignUp) if err := c.ShouldBindJSON(p); err != nil { // 请求参数有误,直接返回响应 zap.L().Error("SignUp with invalid param", zap.Error(err)) // 判断err是不是validator.ValidationErrors 类型 errs, ok := err.(validator.ValidationErrors) if !ok { ResponseError(c, CodeInvalidParam) return } ResponseErrorWithMsg(c, CodeInvalidParam, removeTopStruct(errs.Translate(trans))) return } // 2. 业务处理 if err := logic.SignUp(p); err != nil { zap.L().Error("logic.SignUp failed", zap.Error(err)) if errors.Is(err, mysql.ErrorUserExist) { ResponseError(c, CodeUserExist) return } ResponseError(c, CodeServerBusy) return } // 3. 返回响应 ResponseSuccess(c, nil) } func LoginHandler(c *gin.Context) { // 1.获取请求参数及参数校验 p := new(models.ParamLogin) if err := c.ShouldBindJSON(p); err != nil { // 请求参数有误,直接返回响应 zap.L().Error("Login with invalid param", zap.Error(err)) // 判断err是不是validator.ValidationErrors 类型 errs, ok := err.(validator.ValidationErrors) if !ok { ResponseError(c, CodeInvalidParam) return } ResponseErrorWithMsg(c, CodeInvalidParam, removeTopStruct(errs.Translate(trans))) return } // 2.业务逻辑处理 token, err := logic.Login(p) if err != nil { zap.L().Error("logic.Login failed", zap.String("username", p.Username), zap.Error(err)) if errors.Is(err, mysql.ErrorUserNotExist) { ResponseError(c, CodeUserNotExist) return } ResponseError(c, CodeInvalidPassword) return } // 3.返回响应 ResponseSuccess(c, token) } ================================================ FILE: lesson23/bluebell/controller/validator.go ================================================ package controller import ( "bluebell/models" "fmt" "reflect" "strings" "github.com/gin-gonic/gin/binding" "github.com/go-playground/locales/en" "github.com/go-playground/locales/zh" ut "github.com/go-playground/universal-translator" "github.com/go-playground/validator/v10" enTranslations "github.com/go-playground/validator/v10/translations/en" zhTranslations "github.com/go-playground/validator/v10/translations/zh" ) // 定义一个全局翻译器T var trans ut.Translator // InitTrans 初始化翻译器 func InitTrans(locale string) (err error) { // 修改gin框架中的Validator引擎属性,实现自定制 if v, ok := binding.Validator.Engine().(*validator.Validate); ok { // 注册一个获取json tag的自定义方法 v.RegisterTagNameFunc(func(fld reflect.StructField) string { name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0] if name == "-" { return "" } return name }) // 为SignUpParam注册自定义校验方法 v.RegisterStructValidation(SignUpParamStructLevelValidation, models.ParamSignUp{}) zhT := zh.New() // 中文翻译器 enT := en.New() // 英文翻译器 // 第一个参数是备用(fallback)的语言环境 // 后面的参数是应该支持的语言环境(支持多个) // uni := ut.New(zhT, zhT) 也是可以的 uni := ut.New(enT, zhT, enT) // locale 通常取决于 http 请求头的 'Accept-Language' var ok bool // 也可以使用 uni.FindTranslator(...) 传入多个locale进行查找 trans, ok = uni.GetTranslator(locale) if !ok { return fmt.Errorf("uni.GetTranslator(%s) failed", locale) } // 注册翻译器 switch locale { case "en": err = enTranslations.RegisterDefaultTranslations(v, trans) case "zh": err = zhTranslations.RegisterDefaultTranslations(v, trans) default: err = enTranslations.RegisterDefaultTranslations(v, trans) } return } return } // removeTopStruct 去除提示信息中的结构体名称 func removeTopStruct(fields map[string]string) map[string]string { res := map[string]string{} for field, err := range fields { res[field[strings.Index(field, ".")+1:]] = err } return res } // SignUpParamStructLevelValidation 自定义SignUpParam结构体校验函数 func SignUpParamStructLevelValidation(sl validator.StructLevel) { su := sl.Current().Interface().(models.ParamSignUp) if su.Password != su.RePassword { // 输出错误提示信息,最后一个参数就是传递的param sl.ReportError(su.RePassword, "re_password", "RePassword", "eqfield", "password") } } ================================================ FILE: lesson23/bluebell/dao/mysql/community.go ================================================ package mysql import ( "bluebell/models" "database/sql" "go.uber.org/zap" ) func GetCommunityList() (communityList []*models.Community, err error) { sqlStr := "select community_id, community_name from community" if err := db.Select(&communityList, sqlStr); err != nil { if err == sql.ErrNoRows { zap.L().Warn("there is no community in db") err = nil } } return } // GetCommunityDetailByID 根据ID查询社区详情 func GetCommunityDetailByID(id int64) (community *models.CommunityDetail, err error) { community = new(models.CommunityDetail) sqlStr := `select community_id, community_name, introduction, create_time from community where community_id = ? ` if err := db.Get(community, sqlStr, id); err != nil { if err == sql.ErrNoRows { err = ErrorInvalidID } } return community, err } ================================================ FILE: lesson23/bluebell/dao/mysql/error_code.go ================================================ package mysql import "errors" var ( ErrorUserExist = errors.New("用户已存在") ErrorUserNotExist = errors.New("用户不存在") ErrorInvalidPassword = errors.New("用户名或密码错误") ErrorInvalidID = errors.New("无效的ID") ) ================================================ FILE: lesson23/bluebell/dao/mysql/mysql.go ================================================ package mysql import ( "bluebell/setting" "fmt" _ "github.com/go-sql-driver/mysql" "github.com/jmoiron/sqlx" ) var db *sqlx.DB // Init 初始化MySQL连接 func Init(cfg *setting.MySQLConfig) (err error) { // "user:password@tcp(host:port)/dbname" dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?parseTime=true&loc=Local", cfg.User, cfg.Password, cfg.Host, cfg.Port, cfg.DB) db, err = sqlx.Connect("mysql", dsn) if err != nil { return } db.SetMaxOpenConns(cfg.MaxOpenConns) db.SetMaxIdleConns(cfg.MaxIdleConns) return } // Close 关闭MySQL连接 func Close() { _ = db.Close() } ================================================ FILE: lesson23/bluebell/dao/mysql/user.go ================================================ package mysql import ( "bluebell/models" "crypto/md5" "database/sql" "encoding/hex" ) // 把每一步数据库操作封装成函数 // 待logic层根据业务需求调用 const secret = "liwenzhou.com" // CheckUserExist 检查指定用户名的用户是否存在 func CheckUserExist(username string) (err error) { sqlStr := `select count(user_id) from user where username = ?` var count int64 if err := db.Get(&count, sqlStr, username); err != nil { return err } if count > 0 { return ErrorUserExist } return } // InsertUser 想数据库中插入一条新的用户记录 func InsertUser(user *models.User) (err error) { // 对密码进行加密 user.Password = encryptPassword(user.Password) // 执行SQL语句入库 sqlStr := `insert into user(user_id, username, password) values(?,?,?)` _, err = db.Exec(sqlStr, user.UserID, user.Username, user.Password) return } // encryptPassword 密码加密 func encryptPassword(oPassword string) string { h := md5.New() h.Write([]byte(secret)) return hex.EncodeToString(h.Sum([]byte(oPassword))) } func Login(user *models.User) (err error) { oPassword := user.Password // 用户登录的密码 sqlStr := `select user_id, username, password from user where username=?` err = db.Get(user, sqlStr, user.Username) if err == sql.ErrNoRows { return ErrorUserNotExist } if err != nil { // 查询数据库失败 return err } // 判断密码是否正确 password := encryptPassword(oPassword) if password != user.Password { return ErrorInvalidPassword } return } ================================================ FILE: lesson23/bluebell/dao/redis/redis.go ================================================ package redis import ( "bluebell/setting" "fmt" "github.com/go-redis/redis" ) var ( client *redis.Client Nil = redis.Nil ) // Init 初始化连接 func Init(cfg *setting.RedisConfig) (err error) { client = redis.NewClient(&redis.Options{ Addr: fmt.Sprintf("%s:%d", cfg.Host, cfg.Port), Password: cfg.Password, // no password set DB: cfg.DB, // use default DB PoolSize: cfg.PoolSize, MinIdleConns: cfg.MinIdleConns, }) _, err = client.Ping().Result() if err != nil { return err } return nil } func Close() { _ = client.Close() } ================================================ FILE: lesson23/bluebell/go.mod ================================================ module bluebell go 1.14 require ( github.com/bwmarrin/snowflake v0.3.0 github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/fsnotify/fsnotify v1.4.9 github.com/gin-gonic/gin v1.6.3 github.com/go-playground/locales v0.13.0 github.com/go-playground/universal-translator v0.17.0 github.com/go-playground/validator/v10 v10.2.0 github.com/go-redis/redis v6.15.8+incompatible github.com/go-sql-driver/mysql v1.5.0 github.com/jmoiron/sqlx v1.2.0 github.com/natefinch/lumberjack v2.0.0+incompatible github.com/onsi/ginkgo v1.14.0 // indirect github.com/sony/sonyflake v1.0.0 // indirect github.com/spf13/viper v1.7.0 go.uber.org/zap v1.15.0 gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect ) ================================================ FILE: lesson23/bluebell/go.sum ================================================ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0= github.com/bwmarrin/snowflake v0.3.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/E9WsDpxqwE= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/deckarep/golang-set v1.7.1/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14= github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY= github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= github.com/go-redis/redis v6.15.8+incompatible h1:BKZuG6mCnRj5AOaWJXoCgf6rqTYnYJLe4en2hxT7r9o= github.com/go-redis/redis v6.15.8+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA= github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-sqlite3 v1.9.0 h1:pDRiWfl+++eC2FEFRy6jXmQlvp4Yh3z1MJKg4UeYM/4= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/natefinch/lumberjack v2.0.0+incompatible h1:4QJd3OLAMgj7ph+yZTuX13Ld4UpgHp07nNdFX7mqFfM= github.com/natefinch/lumberjack v2.0.0+incompatible/go.mod h1:Wi9p2TTF5DG5oU+6YfsmYQpsTIOm0B1VNzQg9Mw6nPk= github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/sony/sonyflake v1.0.0 h1:MpU6Ro7tfXwgn2l5eluf9xQvQJDROTBImNCfRXn/YeM= github.com/sony/sonyflake v1.0.0/go.mod h1:Jv3cfhf/UFtolOTTRd3q4Nl6ENqM+KfyZ5PseKfZGF4= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.15.0 h1:ZZCA22JRF2gQE5FoNmhmrf7jeJJ2uhqDUNRYKm8dvmM= go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 h1:AeiKBIuRw3UomYXSbLy0Mc2dDLfdtbT/IVn4keq83P0= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 h1:DYfZAGf2WMFjMxbgTjaC+2HC7NkNAQs+6Q8b9WEB/F4= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc h1:NCy3Ohtk6Iny5V/reW2Ktypo4zIpWBdRJ1uFMjBxdg8= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= ================================================ FILE: lesson23/bluebell/logger/logger.go ================================================ package logger import ( "bluebell/setting" "net" "net/http" "net/http/httputil" "os" "runtime/debug" "strings" "time" "github.com/gin-gonic/gin" "github.com/natefinch/lumberjack" "go.uber.org/zap" "go.uber.org/zap/zapcore" ) var lg *zap.Logger // Init 初始化lg func Init(cfg *setting.LogConfig, mode string) (err error) { writeSyncer := getLogWriter(cfg.Filename, cfg.MaxSize, cfg.MaxBackups, cfg.MaxAge) encoder := getEncoder() var l = new(zapcore.Level) err = l.UnmarshalText([]byte(cfg.Level)) if err != nil { return } var core zapcore.Core if mode == "dev" { // 进入开发模式,日志输出到终端 consoleEncoder := zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig()) core = zapcore.NewTee( zapcore.NewCore(encoder, writeSyncer, l), zapcore.NewCore(consoleEncoder, zapcore.Lock(os.Stdout), zapcore.DebugLevel), ) } else { core = zapcore.NewCore(encoder, writeSyncer, l) } lg = zap.New(core, zap.AddCaller()) zap.ReplaceGlobals(lg) zap.L().Info("init logger success") return } func getEncoder() zapcore.Encoder { encoderConfig := zap.NewProductionEncoderConfig() encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder encoderConfig.TimeKey = "time" encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder encoderConfig.EncodeDuration = zapcore.SecondsDurationEncoder encoderConfig.EncodeCaller = zapcore.ShortCallerEncoder return zapcore.NewJSONEncoder(encoderConfig) } func getLogWriter(filename string, maxSize, maxBackup, maxAge int) zapcore.WriteSyncer { lumberJackLogger := &lumberjack.Logger{ Filename: filename, MaxSize: maxSize, MaxBackups: maxBackup, MaxAge: maxAge, } return zapcore.AddSync(lumberJackLogger) } // GinLogger 接收gin框架默认的日志 func GinLogger() gin.HandlerFunc { return func(c *gin.Context) { start := time.Now() path := c.Request.URL.Path query := c.Request.URL.RawQuery c.Next() cost := time.Since(start) lg.Info(path, zap.Int("status", c.Writer.Status()), zap.String("method", c.Request.Method), zap.String("path", path), zap.String("query", query), zap.String("ip", c.ClientIP()), zap.String("user-agent", c.Request.UserAgent()), zap.String("errors", c.Errors.ByType(gin.ErrorTypePrivate).String()), zap.Duration("cost", cost), ) } } // GinRecovery recover掉项目可能出现的panic,并使用zap记录相关日志 func GinRecovery(stack bool) gin.HandlerFunc { return func(c *gin.Context) { defer func() { if err := recover(); err != nil { // Check for a broken connection, as it is not really a // condition that warrants a panic stack trace. var brokenPipe bool if ne, ok := err.(*net.OpError); ok { if se, ok := ne.Err.(*os.SyscallError); ok { if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") { brokenPipe = true } } } httpRequest, _ := httputil.DumpRequest(c.Request, false) if brokenPipe { lg.Error(c.Request.URL.Path, zap.Any("error", err), zap.String("request", string(httpRequest)), ) // If the connection is dead, we can't write a status to it. c.Error(err.(error)) // nolint: errcheck c.Abort() return } if stack { lg.Error("[Recovery from panic]", zap.Any("error", err), zap.String("request", string(httpRequest)), zap.String("stack", string(debug.Stack())), ) } else { lg.Error("[Recovery from panic]", zap.Any("error", err), zap.String("request", string(httpRequest)), ) } c.AbortWithStatus(http.StatusInternalServerError) } }() c.Next() } } ================================================ FILE: lesson23/bluebell/logic/community.go ================================================ package logic import ( "bluebell/dao/mysql" "bluebell/models" ) func GetCommunityList() ([]*models.Community, error) { // 查数据库 查找到所有的community 并返回 return mysql.GetCommunityList() } func GetCommunityDetail(id int64) (*models.CommunityDetail, error) { return mysql.GetCommunityDetailByID(id) } ================================================ FILE: lesson23/bluebell/logic/user.go ================================================ package logic import ( "bluebell/dao/mysql" "bluebell/models" "bluebell/pkg/jwt" "bluebell/pkg/snowflake" ) // 存放业务逻辑的代码 func SignUp(p *models.ParamSignUp) (err error) { // 1.判断用户存不存在 if err := mysql.CheckUserExist(p.Username); err != nil { return err } // 2.生成UID userID := snowflake.GenID() // 构造一个User实例 user := &models.User{ UserID: userID, Username: p.Username, Password: p.Password, } // 3.保存进数据库 return mysql.InsertUser(user) } func Login(p *models.ParamLogin) (token string, err error) { user := &models.User{ Username: p.Username, Password: p.Password, } // 传递的是指针,就能拿到user.UserID if err := mysql.Login(user); err != nil { return "", err } // 生成JWT return jwt.GenToken(user.UserID, user.Username) } ================================================ FILE: lesson23/bluebell/main.go ================================================ package main import ( "bluebell/controller" "bluebell/dao/mysql" "bluebell/dao/redis" "bluebell/logger" "bluebell/pkg/snowflake" "bluebell/router" "bluebell/setting" "fmt" "os" ) func main() { if len(os.Args) < 2 { fmt.Println("need config file.eg: bluebell config.yaml") return } // 加载配置 if err := setting.Init(os.Args[1]); err != nil { fmt.Printf("load config failed, err:%v\n", err) return } if err := logger.Init(setting.Conf.LogConfig, setting.Conf.Mode); err != nil { fmt.Printf("init logger failed, err:%v\n", err) return } if err := mysql.Init(setting.Conf.MySQLConfig); err != nil { fmt.Printf("init mysql failed, err:%v\n", err) return } defer mysql.Close() // 程序退出关闭数据库连接 if err := redis.Init(setting.Conf.RedisConfig); err != nil { fmt.Printf("init redis failed, err:%v\n", err) return } defer redis.Close() if err := snowflake.Init(setting.Conf.StartTime, setting.Conf.MachineID); err != nil { fmt.Printf("init snowflake failed, err:%v\n", err) return } // 初始化gin框架内置的校验器使用的翻译器 if err := controller.InitTrans("zh"); err != nil { fmt.Printf("init validator trans failed, err:%v\n", err) return } // 注册路由 r := router.SetupRouter(setting.Conf.Mode) err := r.Run(fmt.Sprintf(":%d", setting.Conf.Port)) if err != nil { fmt.Printf("run server failed, err:%v\n", err) return } } ================================================ FILE: lesson23/bluebell/middlewares/auth.go ================================================ package middlewares import ( "bluebell/controller" "bluebell/pkg/jwt" "strings" "github.com/gin-gonic/gin" ) // JWTAuthMiddleware 基于JWT的认证中间件 func JWTAuthMiddleware() func(c *gin.Context) { return func(c *gin.Context) { // 客户端携带Token有三种方式 1.放在请求头 2.放在请求体 3.放在URI // 这里假设Token放在Header的Authorization中,并使用Bearer开头 // Authorization: Bearer xxxxxxx.xxx.xxx / X-TOKEN: xxx.xxx.xx // 这里的具体实现方式要依据你的实际业务情况决定 authHeader := c.Request.Header.Get("Authorization") if authHeader == "" { controller.ResponseError(c, controller.CodeNeedLogin) c.Abort() return } // 按空格分割 parts := strings.SplitN(authHeader, " ", 2) if !(len(parts) == 2 && parts[0] == "Bearer") { controller.ResponseError(c, controller.CodeInvalidToken) c.Abort() return } // parts[1]是获取到的tokenString,我们使用之前定义好的解析JWT的函数来解析它 mc, err := jwt.ParseToken(parts[1]) if err != nil { controller.ResponseError(c, controller.CodeInvalidToken) c.Abort() return } // 将当前请求的userID信息保存到请求的上下文c上 c.Set(controller.CtxUserIDKey, mc.UserID) c.Next() // 后续的处理请求的函数中 可以用过c.Get(CtxUserIDKey) 来获取当前请求的用户信息 } } ================================================ FILE: lesson23/bluebell/models/community.go ================================================ package models import "time" type Community struct { ID int64 `json:"id" db:"community_id"` Name string `json:"name" db:"community_name"` } type CommunityDetail struct { ID int64 `json:"id" db:"community_id"` Name string `json:"name" db:"community_name"` Introduction string `json:"introduction,omitempty" db:"introduction"` CreateTime time.Time `json:"create_time" db:"create_time"` } ================================================ FILE: lesson23/bluebell/models/create_table.sql ================================================ -- CREATE TABLE `user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `user_id` bigint(20) NOT NULL, `username` varchar(64) COLLATE utf8mb4_general_ci NOT NULL, `password` varchar(64) COLLATE utf8mb4_general_ci NOT NULL, `email` varchar(64) COLLATE utf8mb4_general_ci, `gender` tinyint(4) NOT NULL DEFAULT '0', `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP, `update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `idx_username` (`username`) USING BTREE, UNIQUE KEY `idx_user_id` (`user_id`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; DROP TABLE IF EXISTS `community`; CREATE TABLE `community` ( `id` int(11) NOT NULL AUTO_INCREMENT, `community_id` int(10) unsigned NOT NULL, `community_name` varchar(128) COLLATE utf8mb4_general_ci NOT NULL, `introduction` varchar(256) COLLATE utf8mb4_general_ci NOT NULL, `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `idx_community_id` (`community_id`), UNIQUE KEY `idx_community_name` (`community_name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; INSERT INTO `community` VALUES ('1', '1', 'Go', 'Golang', '2016-11-01 08:10:10', '2016-11-01 08:10:10'); INSERT INTO `community` VALUES ('2', '2', 'leetcode', '刷题刷题刷题', '2020-01-01 08:00:00', '2020-01-01 08:00:00'); INSERT INTO `community` VALUES ('3', '3', 'CS:GO', 'Rush B。。。', '2018-08-07 08:30:00', '2018-08-07 08:30:00'); INSERT INTO `community` VALUES ('4', '4', 'LOL', '欢迎来到英雄联盟!', '2016-01-01 08:00:00', '2016-01-01 08:00:00'); DROP TABLE IF EXISTS `post`; CREATE TABLE `post` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `post_id` bigint(20) NOT NULL COMMENT '帖子id', `title` varchar(128) COLLATE utf8mb4_general_ci NOT NULL COMMENT '标题', `content` varchar(8192) COLLATE utf8mb4_general_ci NOT NULL COMMENT '内容', `author_id` bigint(20) NOT NULL COMMENT '作者的用户id', `community_id` bigint(20) NOT NULL COMMENT '所属社区', `status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '帖子状态', `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), UNIQUE KEY `idx_post_id` (`post_id`), KEY `idx_author_id` (`author_id`), KEY `idx_community_id` (`community_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; ================================================ FILE: lesson23/bluebell/models/params.go ================================================ package models // 定义请求的参数结构体 // ParamSignUp 注册请求参数 type ParamSignUp struct { Username string `json:"username" binding:"required"` Password string `json:"password" binding:"required"` RePassword string `json:"re_password" binding:"required,eqfield=Password"` } // ParamLogin 登录请求参数 type ParamLogin struct { Username string `json:"username" binding:"required"` Password string `json:"password" binding:"required"` } ================================================ FILE: lesson23/bluebell/models/post.go ================================================ package models import "time" // 内存对齐概念 type Post struct { ID int64 `json:"id" db:"post_id"` AuthorID int64 `json:"author_id" db:"author_id"` CommunityID int64 `json:"community_id" db:"community_id"` Status int32 `json:"status" db:"status"` Title string `json:"title" db:"title"` Content string `json:"content" db:"content"` CreateTime time.Time `json:"create_time" db:"create_time"` } ================================================ FILE: lesson23/bluebell/models/struct_test.go ================================================ package models import ( "fmt" "testing" "unsafe" ) // Go 内存对齐 type s1 struct { a int8 b string c int8 } type s2 struct { a int8 c int8 b string } func TestStruct(t *testing.T) { v1 := s1{ a: 1, b: "七米", c: 2, } v2 := s2{ a: 1, b: "七米", c: 2, } fmt.Println(unsafe.Sizeof(v1), unsafe.Sizeof(v2)) } ================================================ FILE: lesson23/bluebell/models/user.go ================================================ package models type User struct { UserID int64 `db:"user_id"` Username string `db:"username"` Password string `db:"password"` } ================================================ FILE: lesson23/bluebell/pkg/jwt/jwt.go ================================================ package jwt import ( "errors" "time" "github.com/spf13/viper" "github.com/dgrijalva/jwt-go" ) var mySecret = []byte("夏天夏天悄悄过去") // MyClaims 自定义声明结构体并内嵌jwt.StandardClaims // jwt包自带的jwt.StandardClaims只包含了官方字段 // 我们这里需要额外记录一个username字段,所以要自定义结构体 // 如果想要保存更多信息,都可以添加到这个结构体中 type MyClaims struct { UserID int64 `json:"user_id"` Username string `json:"username"` jwt.StandardClaims } // GenToken 生成JWT func GenToken(userID int64, username string) (string, error) { // 创建一个我们自己的声明的数据 c := MyClaims{ userID, "username", // 自定义字段 jwt.StandardClaims{ ExpiresAt: time.Now().Add( time.Duration(viper.GetInt("auth.jwt_expire")) * time.Hour).Unix(), // 过期时间 Issuer: "bluebell", // 签发人 }, } // 使用指定的签名方法创建签名对象 token := jwt.NewWithClaims(jwt.SigningMethodHS256, c) // 使用指定的secret签名并获得完整的编码后的字符串token return token.SignedString(mySecret) } // ParseToken 解析JWT func ParseToken(tokenString string) (*MyClaims, error) { // 解析token var mc = new(MyClaims) token, err := jwt.ParseWithClaims(tokenString, mc, func(token *jwt.Token) (i interface{}, err error) { return mySecret, nil }) if err != nil { return nil, err } if token.Valid { // 校验token return mc, nil } return nil, errors.New("invalid token") } ================================================ FILE: lesson23/bluebell/pkg/snowflake/snowflake.go ================================================ package snowflake import ( "time" sf "github.com/bwmarrin/snowflake" ) var node *sf.Node func Init(startTime string, machineID int64) (err error) { var st time.Time st, err = time.Parse("2006-01-02", startTime) if err != nil { return } sf.Epoch = st.UnixNano() / 1000000 node, err = sf.NewNode(machineID) return } func GenID() int64 { return node.Generate().Int64() } ================================================ FILE: lesson23/bluebell/router/route.go ================================================ package router import ( "bluebell/controller" "bluebell/logger" "bluebell/middlewares" "net/http" "github.com/gin-gonic/gin" ) func SetupRouter(mode string) *gin.Engine { if mode == gin.ReleaseMode { gin.SetMode(gin.ReleaseMode) // gin设置成发布模式 } r := gin.New() r.Use(logger.GinLogger(), logger.GinRecovery(true)) v1 := r.Group("/api/v1") // 注册 v1.POST("/signup", controller.SignUpHandler) // 登录 v1.POST("/login", controller.LoginHandler) v1.Use(middlewares.JWTAuthMiddleware()) // 应用JWT认证中间件 { v1.GET("/community", controller.CommunityHandler) v1.GET("/community/:id", controller.CommunityDetailHandler) v1.POST("/post", controller.CreatePostHandler) } r.NoRoute(func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "msg": "404", }) }) return r } ================================================ FILE: lesson23/bluebell/setting/setting.go ================================================ package setting import ( "fmt" "github.com/fsnotify/fsnotify" "github.com/spf13/viper" ) var Conf = new(AppConfig) type AppConfig struct { Name string `mapstructure:"name"` Mode string `mapstructure:"mode"` Version string `mapstructure:"version"` StartTime string `mapstructure:"start_time"` MachineID int64 `mapstructure:"machine_id"` Port int `mapstructure:"port"` *LogConfig `mapstructure:"log"` *MySQLConfig `mapstructure:"mysql"` *RedisConfig `mapstructure:"redis"` } type MySQLConfig struct { Host string `mapstructure:"host"` User string `mapstructure:"user"` Password string `mapstructure:"password"` DB string `mapstructure:"dbname"` Port int `mapstructure:"port"` MaxOpenConns int `mapstructure:"max_open_conns"` MaxIdleConns int `mapstructure:"max_idle_conns"` } type RedisConfig struct { Host string `mapstructure:"host"` Password string `mapstructure:"password"` Port int `mapstructure:"port"` DB int `mapstructure:"db"` PoolSize int `mapstructure:"pool_size"` MinIdleConns int `mapstructure:"min_idle_conns"` } type LogConfig struct { Level string `mapstructure:"level"` Filename string `mapstructure:"filename"` MaxSize int `mapstructure:"max_size"` MaxAge int `mapstructure:"max_age"` MaxBackups int `mapstructure:"max_backups"` } func Init(filePath string) (err error) { // 方式1:直接指定配置文件路径(相对路径或者绝对路径) // 相对路径:相对执行的可执行文件的相对路径 //viper.SetConfigFile("./conf/config.yaml") // 绝对路径:系统中实际的文件路径 //viper.SetConfigFile("/Users/liwenzhou/Desktop/bluebell/conf/config.yaml") // 方式2:指定配置文件名和配置文件的位置,viper自行查找可用的配置文件 // 配置文件名不需要带后缀 // 配置文件位置可配置多个 //viper.SetConfigName("config") // 指定配置文件名(不带后缀) //viper.AddConfigPath(".") // 指定查找配置文件的路径(这里使用相对路径) //viper.AddConfigPath("./conf") // 指定查找配置文件的路径(这里使用相对路径) // 基本上是配合远程配置中心使用的,告诉viper当前的数据使用什么格式去解析 //viper.SetConfigType("json") viper.SetConfigFile(filePath) err = viper.ReadInConfig() // 读取配置信息 if err != nil { // 读取配置信息失败 fmt.Printf("viper.ReadInConfig failed, err:%v\n", err) return } // 把读取到的配置信息反序列化到 Conf 变量中 if err := viper.Unmarshal(Conf); err != nil { fmt.Printf("viper.Unmarshal failed, err:%v\n", err) } viper.WatchConfig() viper.OnConfigChange(func(in fsnotify.Event) { fmt.Println("配置文件修改了...") if err := viper.Unmarshal(Conf); err != nil { fmt.Printf("viper.Unmarshal failed, err:%v\n", err) } }) return } ================================================ FILE: lesson28/bluebell/.air.conf ================================================ # [Air](https://github.com/cosmtrek/air) TOML 格式的配置文件 # 工作目录 # 使用 . 或绝对路径,请注意 `tmp_dir` 目录必须在 `root` 目录下 root = "." tmp_dir = "tmp" [build] # 只需要写你平常编译使用的shell命令。你也可以使用 `make` cmd = "go build -o ./tmp/main" # 由`cmd`命令得到的二进制文件名 bin = "tmp/main" # 自定义的二进制,可以添加额外的编译标识例如添加 GIN_MODE=release full_bin = "./tmp/main ./conf/config.yaml" # 监听以下文件扩展名的文件. include_ext = ["go", "tpl", "tmpl", "html", "yaml"] # 忽略这些文件扩展名或目录 exclude_dir = ["assets", "tmp", "vendor", "frontend/node_modules"] # 监听以下指定目录的文件 include_dir = [] # 排除以下文件 exclude_file = [] # 如果文件更改过于频繁,则没有必要在每次更改时都触发构建。可以设置触发构建的延迟时间 delay = 1000 # ms # 发生构建错误时,停止运行旧的二进制文件。 stop_on_error = true # air的日志文件名,该日志文件放置在你的`tmp_dir`中 log = "air_errors.log" [log] # 显示日志时间 time = true [color] # 自定义每个部分显示的颜色。如果找不到颜色,使用原始的应用程序日志。 main = "magenta" watcher = "cyan" build = "yellow" runner = "green" [misc] # 退出时删除tmp目录 clean_on_exit = true ================================================ FILE: lesson28/bluebell/Makefile ================================================ .PHONY: all build run gotool clean help BINARY="xx" all: gotool build build: CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o ${BINARY} run: @go run ./main.go conf/config.yaml gotool: go fmt ./ go vet ./ clean: @if [ -f ${BINARY} ] ; then rm ${BINARY} ; fi help: @echo "make - 格式化 Go 代码, 并编译生成二进制文件" @echo "make build - 编译 Go 代码, 生成二进制文件" @echo "make run - 直接运行 Go 代码" @echo "make clean - 移除二进制文件和 vim swap files" @echo "make gotool - 运行 Go 工具 'fmt' and 'vet'" ================================================ FILE: lesson28/bluebell/conf/config.yaml ================================================ name: "bluebell" mode: "dev" port: 8084 version: "v0.0.1" start_time: "2020-07-01" machine_id: 1 auth: jwt_expire: 8760 log: level: "debug" filename: "web_app.log" max_size: 200 max_age: 30 max_backups: 7 mysql: host: "127.0.0.1" port: 13306 user: "root" password: "root1234" dbname: "bluebell" max_open_conns: 200 max_idle_conns: 50 redis: host: "127.0.0.1" port: 16379 password: "" db: 0 pool_size: 100 ================================================ FILE: lesson28/bluebell/controller/code.go ================================================ package controller type ResCode int64 const ( CodeSuccess ResCode = 1000 + iota CodeInvalidParam CodeUserExist CodeUserNotExist CodeInvalidPassword CodeServerBusy CodeNeedLogin CodeInvalidToken ) var codeMsgMap = map[ResCode]string{ CodeSuccess: "success", CodeInvalidParam: "请求参数错误", CodeUserExist: "用户名已存在", CodeUserNotExist: "用户名不存在", CodeInvalidPassword: "用户名或密码错误", CodeServerBusy: "服务繁忙", CodeNeedLogin: "需要登录", CodeInvalidToken: "无效的token", } func (c ResCode) Msg() string { msg, ok := codeMsgMap[c] if !ok { msg = codeMsgMap[CodeServerBusy] } return msg } ================================================ FILE: lesson28/bluebell/controller/community.go ================================================ package controller import ( "bluebell/logic" "strconv" "github.com/gin-gonic/gin" "go.uber.org/zap" ) // ---- 跟社区相关的 ---- func CommunityHandler(c *gin.Context) { // 查询到所有的社区(community_id, community_name) 以列表的形式返回 data, err := logic.GetCommunityList() if err != nil { zap.L().Error("logic.GetCommunityList() failed", zap.Error(err)) ResponseError(c, CodeServerBusy) // 不轻易把服务端报错暴露给外面 return } ResponseSuccess(c, data) } // CommunityDetailHandler 社区分类详情 func CommunityDetailHandler(c *gin.Context) { // 1. 获取社区id idStr := c.Param("id") // 获取URL参数 id, err := strconv.ParseInt(idStr, 10, 64) if err != nil { ResponseError(c, CodeInvalidParam) return } // 2. 根据id获取社区详情 data, err := logic.GetCommunityDetail(id) if err != nil { zap.L().Error("logic.GetCommunityList() failed", zap.Error(err)) ResponseError(c, CodeServerBusy) // 不轻易把服务端报错暴露给外面 return } ResponseSuccess(c, data) } ================================================ FILE: lesson28/bluebell/controller/post.go ================================================ package controller import ( "bluebell/logic" "bluebell/models" "strconv" "go.uber.org/zap" "github.com/gin-gonic/gin" ) // CreatePostHandler 创建帖子的处理函数 func CreatePostHandler(c *gin.Context) { // 1. 获取参数及参数的校验 //c.ShouldBindJSON() // validator --> binding tag p := new(models.Post) if err := c.ShouldBindJSON(p); err != nil { zap.L().Debug("c.ShouldBindJSON(p) error", zap.Any("err", err)) zap.L().Error("create post with invalid param") ResponseError(c, CodeInvalidParam) return } // 从 c 取到当前发请求的用户的ID userID, err := getCurrentUserID(c) if err != nil { ResponseError(c, CodeNeedLogin) return } p.AuthorID = userID // 2. 创建帖子 if err := logic.CreatePost(p); err != nil { zap.L().Error("logic.CreatePost(p) failed", zap.Error(err)) ResponseError(c, CodeServerBusy) return } // 3. 返回响应 ResponseSuccess(c, nil) } // GetPostDetailHandler 获取帖子详情的处理函数 func GetPostDetailHandler(c *gin.Context) { // 1. 获取参数(从URL中获取帖子的id) pidStr := c.Param("id") pid, err := strconv.ParseInt(pidStr, 10, 64) if err != nil { zap.L().Error("get post detail with invalid param", zap.Error(err)) ResponseError(c, CodeInvalidParam) return } // 2. 根据id取出帖子数据(查数据库) data, err := logic.GetPostById(pid) if err != nil { zap.L().Error("logic.GetPostById(pid) failed", zap.Error(err)) ResponseError(c, CodeServerBusy) return } // 3. 返回响应 ResponseSuccess(c, data) } // GetPostListHandler 获取帖子列表的处理函数 func GetPostListHandler(c *gin.Context) { // 获取分页参数 page, size := getPageInfo(c) // 获取数据 data, err := logic.GetPostList(page, size) if err != nil { zap.L().Error("logic.GetPostList() failed", zap.Error(err)) ResponseError(c, CodeServerBusy) return } ResponseSuccess(c, data) // 返回响应 } ================================================ FILE: lesson28/bluebell/controller/request.go ================================================ package controller import ( "errors" "strconv" "github.com/gin-gonic/gin" ) const CtxUserIDKey = "userID" var ErrorUserNotLogin = errors.New("用户未登录") // getCurrentUserID 获取当前登录的用户ID func getCurrentUserID(c *gin.Context) (userID int64, err error) { uid, ok := c.Get(CtxUserIDKey) if !ok { err = ErrorUserNotLogin return } userID, ok = uid.(int64) if !ok { err = ErrorUserNotLogin return } return } func getPageInfo(c *gin.Context) (int64, int64) { pageStr := c.Query("page") sizeStr := c.Query("size") var ( page int64 size int64 err error ) page, err = strconv.ParseInt(pageStr, 10, 64) if err != nil { page = 1 } size, err = strconv.ParseInt(sizeStr, 10, 64) if err != nil { size = 10 } return page, size } ================================================ FILE: lesson28/bluebell/controller/response.go ================================================ package controller import ( "net/http" "github.com/gin-gonic/gin" ) /* { "code": 10000, // 程序中的错误码 "msg": xx, // 提示信息 "data": {}, // 数据 } */ type ResponseData struct { Code ResCode `json:"code"` Msg interface{} `json:"msg"` Data interface{} `json:"data"` } func ResponseError(c *gin.Context, code ResCode) { c.JSON(http.StatusOK, &ResponseData{ Code: code, Msg: code.Msg(), Data: nil, }) } func ResponseErrorWithMsg(c *gin.Context, code ResCode, msg interface{}) { c.JSON(http.StatusOK, &ResponseData{ Code: code, Msg: msg, Data: nil, }) } func ResponseSuccess(c *gin.Context, data interface{}) { c.JSON(http.StatusOK, &ResponseData{ Code: CodeSuccess, Msg: CodeSuccess.Msg(), Data: data, }) } ================================================ FILE: lesson28/bluebell/controller/user.go ================================================ package controller import ( "bluebell/dao/mysql" "bluebell/logic" "bluebell/models" "errors" "github.com/go-playground/validator/v10" "go.uber.org/zap" "github.com/gin-gonic/gin" ) // SignUpHandler 处理注册请求的函数 func SignUpHandler(c *gin.Context) { // 1. 获取参数和参数校验 p := new(models.ParamSignUp) if err := c.ShouldBindJSON(p); err != nil { // 请求参数有误,直接返回响应 zap.L().Error("SignUp with invalid param", zap.Error(err)) // 判断err是不是validator.ValidationErrors 类型 errs, ok := err.(validator.ValidationErrors) if !ok { ResponseError(c, CodeInvalidParam) return } ResponseErrorWithMsg(c, CodeInvalidParam, removeTopStruct(errs.Translate(trans))) return } // 2. 业务处理 if err := logic.SignUp(p); err != nil { zap.L().Error("logic.SignUp failed", zap.Error(err)) if errors.Is(err, mysql.ErrorUserExist) { ResponseError(c, CodeUserExist) return } ResponseError(c, CodeServerBusy) return } // 3. 返回响应 ResponseSuccess(c, nil) } func LoginHandler(c *gin.Context) { // 1.获取请求参数及参数校验 p := new(models.ParamLogin) if err := c.ShouldBindJSON(p); err != nil { // 请求参数有误,直接返回响应 zap.L().Error("Login with invalid param", zap.Error(err)) // 判断err是不是validator.ValidationErrors 类型 errs, ok := err.(validator.ValidationErrors) if !ok { ResponseError(c, CodeInvalidParam) return } ResponseErrorWithMsg(c, CodeInvalidParam, removeTopStruct(errs.Translate(trans))) return } // 2.业务逻辑处理 token, err := logic.Login(p) if err != nil { zap.L().Error("logic.Login failed", zap.String("username", p.Username), zap.Error(err)) if errors.Is(err, mysql.ErrorUserNotExist) { ResponseError(c, CodeUserNotExist) return } ResponseError(c, CodeInvalidPassword) return } // 3.返回响应 ResponseSuccess(c, token) } ================================================ FILE: lesson28/bluebell/controller/validator.go ================================================ package controller import ( "bluebell/models" "fmt" "reflect" "strings" "github.com/gin-gonic/gin/binding" "github.com/go-playground/locales/en" "github.com/go-playground/locales/zh" ut "github.com/go-playground/universal-translator" "github.com/go-playground/validator/v10" enTranslations "github.com/go-playground/validator/v10/translations/en" zhTranslations "github.com/go-playground/validator/v10/translations/zh" ) // 定义一个全局翻译器T var trans ut.Translator // InitTrans 初始化翻译器 func InitTrans(locale string) (err error) { // 修改gin框架中的Validator引擎属性,实现自定制 if v, ok := binding.Validator.Engine().(*validator.Validate); ok { // 注册一个获取json tag的自定义方法 v.RegisterTagNameFunc(func(fld reflect.StructField) string { name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0] if name == "-" { return "" } return name }) // 为SignUpParam注册自定义校验方法 v.RegisterStructValidation(SignUpParamStructLevelValidation, models.ParamSignUp{}) zhT := zh.New() // 中文翻译器 enT := en.New() // 英文翻译器 // 第一个参数是备用(fallback)的语言环境 // 后面的参数是应该支持的语言环境(支持多个) // uni := ut.New(zhT, zhT) 也是可以的 uni := ut.New(enT, zhT, enT) // locale 通常取决于 http 请求头的 'Accept-Language' var ok bool // 也可以使用 uni.FindTranslator(...) 传入多个locale进行查找 trans, ok = uni.GetTranslator(locale) if !ok { return fmt.Errorf("uni.GetTranslator(%s) failed", locale) } // 注册翻译器 switch locale { case "en": err = enTranslations.RegisterDefaultTranslations(v, trans) case "zh": err = zhTranslations.RegisterDefaultTranslations(v, trans) default: err = enTranslations.RegisterDefaultTranslations(v, trans) } return } return } // removeTopStruct 去除提示信息中的结构体名称 func removeTopStruct(fields map[string]string) map[string]string { res := map[string]string{} for field, err := range fields { res[field[strings.Index(field, ".")+1:]] = err } return res } // SignUpParamStructLevelValidation 自定义SignUpParam结构体校验函数 func SignUpParamStructLevelValidation(sl validator.StructLevel) { su := sl.Current().Interface().(models.ParamSignUp) if su.Password != su.RePassword { // 输出错误提示信息,最后一个参数就是传递的param sl.ReportError(su.RePassword, "re_password", "RePassword", "eqfield", "password") } } ================================================ FILE: lesson28/bluebell/dao/mysql/community.go ================================================ package mysql import ( "bluebell/models" "database/sql" "go.uber.org/zap" ) func GetCommunityList() (communityList []*models.Community, err error) { sqlStr := "select community_id, community_name from community" if err := db.Select(&communityList, sqlStr); err != nil { if err == sql.ErrNoRows { zap.L().Warn("there is no community in db") err = nil } } return } // GetCommunityDetailByID 根据ID查询社区详情 func GetCommunityDetailByID(id int64) (community *models.CommunityDetail, err error) { community = new(models.CommunityDetail) sqlStr := `select community_id, community_name, introduction, create_time from community where community_id = ? ` if err := db.Get(community, sqlStr, id); err != nil { if err == sql.ErrNoRows { err = ErrorInvalidID } } return community, err } ================================================ FILE: lesson28/bluebell/dao/mysql/error_code.go ================================================ package mysql import "errors" var ( ErrorUserExist = errors.New("用户已存在") ErrorUserNotExist = errors.New("用户不存在") ErrorInvalidPassword = errors.New("用户名或密码错误") ErrorInvalidID = errors.New("无效的ID") ) ================================================ FILE: lesson28/bluebell/dao/mysql/mysql.go ================================================ package mysql import ( "bluebell/setting" "fmt" _ "github.com/go-sql-driver/mysql" "github.com/jmoiron/sqlx" ) var db *sqlx.DB // Init 初始化MySQL连接 func Init(cfg *setting.MySQLConfig) (err error) { // "user:password@tcp(host:port)/dbname" dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?parseTime=true&loc=Local", cfg.User, cfg.Password, cfg.Host, cfg.Port, cfg.DB) db, err = sqlx.Connect("mysql", dsn) if err != nil { return } db.SetMaxOpenConns(cfg.MaxOpenConns) db.SetMaxIdleConns(cfg.MaxIdleConns) return } // Close 关闭MySQL连接 func Close() { _ = db.Close() } ================================================ FILE: lesson28/bluebell/dao/mysql/post.go ================================================ package mysql import "bluebell/models" func CreatePost(p *models.Post) (err error) { sqlStr := `insert into post( post_id, title, content, author_id, community_id) values (?, ?, ?, ?, ?) ` _, err = db.Exec(sqlStr, p.ID, p.Title, p.Content, p.AuthorID, p.CommunityID) return } func GetPostById(pid int64) (post *models.Post, err error) { post = new(models.Post) sqlStr := `select post_id, title, content, author_id, community_id, create_time from post where post_id = ? ` err = db.Get(post, sqlStr, pid) return } func GetPostList(page, size int64) (posts []*models.Post, err error) { sqlStr := `select post_id, title, content, author_id, community_id, create_time from post limit ?,? ` posts = make([]*models.Post, 0, 2) // 不要写成make([]*models.Post, 2) err = db.Select(&posts, sqlStr, (page-1)*size, size) return } ================================================ FILE: lesson28/bluebell/dao/mysql/user.go ================================================ package mysql import ( "bluebell/models" "crypto/md5" "database/sql" "encoding/hex" ) // 把每一步数据库操作封装成函数 // 待logic层根据业务需求调用 const secret = "liwenzhou.com" // CheckUserExist 检查指定用户名的用户是否存在 func CheckUserExist(username string) (err error) { sqlStr := `select count(user_id) from user where username = ?` var count int64 if err := db.Get(&count, sqlStr, username); err != nil { return err } if count > 0 { return ErrorUserExist } return } // InsertUser 想数据库中插入一条新的用户记录 func InsertUser(user *models.User) (err error) { // 对密码进行加密 user.Password = encryptPassword(user.Password) // 执行SQL语句入库 sqlStr := `insert into user(user_id, username, password) values(?,?,?)` _, err = db.Exec(sqlStr, user.UserID, user.Username, user.Password) return } // encryptPassword 密码加密 func encryptPassword(oPassword string) string { h := md5.New() h.Write([]byte(secret)) return hex.EncodeToString(h.Sum([]byte(oPassword))) } func Login(user *models.User) (err error) { oPassword := user.Password // 用户登录的密码 sqlStr := `select user_id, username, password from user where username=?` err = db.Get(user, sqlStr, user.Username) if err == sql.ErrNoRows { return ErrorUserNotExist } if err != nil { // 查询数据库失败 return err } // 判断密码是否正确 password := encryptPassword(oPassword) if password != user.Password { return ErrorInvalidPassword } return } // GetUserById 根据id获取用户信息 func GetUserById(uid int64) (user *models.User, err error) { user = new(models.User) sqlStr := `select user_id, username from user where user_id = ?` err = db.Get(user, sqlStr, uid) return } ================================================ FILE: lesson28/bluebell/dao/redis/redis.go ================================================ package redis import ( "bluebell/setting" "fmt" "github.com/go-redis/redis" ) var ( client *redis.Client Nil = redis.Nil ) // Init 初始化连接 func Init(cfg *setting.RedisConfig) (err error) { client = redis.NewClient(&redis.Options{ Addr: fmt.Sprintf("%s:%d", cfg.Host, cfg.Port), Password: cfg.Password, // no password set DB: cfg.DB, // use default DB PoolSize: cfg.PoolSize, MinIdleConns: cfg.MinIdleConns, }) _, err = client.Ping().Result() if err != nil { return err } return nil } func Close() { _ = client.Close() } ================================================ FILE: lesson28/bluebell/go.mod ================================================ module bluebell go 1.14 require ( github.com/bwmarrin/snowflake v0.3.0 github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/fsnotify/fsnotify v1.4.9 github.com/gin-gonic/gin v1.6.3 github.com/go-playground/locales v0.13.0 github.com/go-playground/universal-translator v0.17.0 github.com/go-playground/validator/v10 v10.2.0 github.com/go-redis/redis v6.15.8+incompatible github.com/go-sql-driver/mysql v1.5.0 github.com/jmoiron/sqlx v1.2.0 github.com/natefinch/lumberjack v2.0.0+incompatible github.com/onsi/ginkgo v1.14.0 // indirect github.com/sony/sonyflake v1.0.0 // indirect github.com/spf13/viper v1.7.0 go.uber.org/zap v1.15.0 gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect ) ================================================ FILE: lesson28/bluebell/go.sum ================================================ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0= github.com/bwmarrin/snowflake v0.3.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/E9WsDpxqwE= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/deckarep/golang-set v1.7.1/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14= github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY= github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= github.com/go-redis/redis v6.15.8+incompatible h1:BKZuG6mCnRj5AOaWJXoCgf6rqTYnYJLe4en2hxT7r9o= github.com/go-redis/redis v6.15.8+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA= github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-sqlite3 v1.9.0 h1:pDRiWfl+++eC2FEFRy6jXmQlvp4Yh3z1MJKg4UeYM/4= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/natefinch/lumberjack v2.0.0+incompatible h1:4QJd3OLAMgj7ph+yZTuX13Ld4UpgHp07nNdFX7mqFfM= github.com/natefinch/lumberjack v2.0.0+incompatible/go.mod h1:Wi9p2TTF5DG5oU+6YfsmYQpsTIOm0B1VNzQg9Mw6nPk= github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/sony/sonyflake v1.0.0 h1:MpU6Ro7tfXwgn2l5eluf9xQvQJDROTBImNCfRXn/YeM= github.com/sony/sonyflake v1.0.0/go.mod h1:Jv3cfhf/UFtolOTTRd3q4Nl6ENqM+KfyZ5PseKfZGF4= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.15.0 h1:ZZCA22JRF2gQE5FoNmhmrf7jeJJ2uhqDUNRYKm8dvmM= go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 h1:AeiKBIuRw3UomYXSbLy0Mc2dDLfdtbT/IVn4keq83P0= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 h1:DYfZAGf2WMFjMxbgTjaC+2HC7NkNAQs+6Q8b9WEB/F4= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc h1:NCy3Ohtk6Iny5V/reW2Ktypo4zIpWBdRJ1uFMjBxdg8= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= ================================================ FILE: lesson28/bluebell/logger/logger.go ================================================ package logger import ( "bluebell/setting" "net" "net/http" "net/http/httputil" "os" "runtime/debug" "strings" "time" "github.com/gin-gonic/gin" "github.com/natefinch/lumberjack" "go.uber.org/zap" "go.uber.org/zap/zapcore" ) var lg *zap.Logger // Init 初始化lg func Init(cfg *setting.LogConfig, mode string) (err error) { writeSyncer := getLogWriter(cfg.Filename, cfg.MaxSize, cfg.MaxBackups, cfg.MaxAge) encoder := getEncoder() var l = new(zapcore.Level) err = l.UnmarshalText([]byte(cfg.Level)) if err != nil { return } var core zapcore.Core if mode == "dev" { // 进入开发模式,日志输出到终端 consoleEncoder := zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig()) core = zapcore.NewTee( zapcore.NewCore(encoder, writeSyncer, l), zapcore.NewCore(consoleEncoder, zapcore.Lock(os.Stdout), zapcore.DebugLevel), ) } else { core = zapcore.NewCore(encoder, writeSyncer, l) } lg = zap.New(core, zap.AddCaller()) zap.ReplaceGlobals(lg) zap.L().Info("init logger success") return } func getEncoder() zapcore.Encoder { encoderConfig := zap.NewProductionEncoderConfig() encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder encoderConfig.TimeKey = "time" encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder encoderConfig.EncodeDuration = zapcore.SecondsDurationEncoder encoderConfig.EncodeCaller = zapcore.ShortCallerEncoder return zapcore.NewJSONEncoder(encoderConfig) } func getLogWriter(filename string, maxSize, maxBackup, maxAge int) zapcore.WriteSyncer { lumberJackLogger := &lumberjack.Logger{ Filename: filename, MaxSize: maxSize, MaxBackups: maxBackup, MaxAge: maxAge, } return zapcore.AddSync(lumberJackLogger) } // GinLogger 接收gin框架默认的日志 func GinLogger() gin.HandlerFunc { return func(c *gin.Context) { start := time.Now() path := c.Request.URL.Path query := c.Request.URL.RawQuery c.Next() cost := time.Since(start) lg.Info(path, zap.Int("status", c.Writer.Status()), zap.String("method", c.Request.Method), zap.String("path", path), zap.String("query", query), zap.String("ip", c.ClientIP()), zap.String("user-agent", c.Request.UserAgent()), zap.String("errors", c.Errors.ByType(gin.ErrorTypePrivate).String()), zap.Duration("cost", cost), ) } } // GinRecovery recover掉项目可能出现的panic,并使用zap记录相关日志 func GinRecovery(stack bool) gin.HandlerFunc { return func(c *gin.Context) { defer func() { if err := recover(); err != nil { // Check for a broken connection, as it is not really a // condition that warrants a panic stack trace. var brokenPipe bool if ne, ok := err.(*net.OpError); ok { if se, ok := ne.Err.(*os.SyscallError); ok { if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") { brokenPipe = true } } } httpRequest, _ := httputil.DumpRequest(c.Request, false) if brokenPipe { lg.Error(c.Request.URL.Path, zap.Any("error", err), zap.String("request", string(httpRequest)), ) // If the connection is dead, we can't write a status to it. c.Error(err.(error)) // nolint: errcheck c.Abort() return } if stack { lg.Error("[Recovery from panic]", zap.Any("error", err), zap.String("request", string(httpRequest)), zap.String("stack", string(debug.Stack())), ) } else { lg.Error("[Recovery from panic]", zap.Any("error", err), zap.String("request", string(httpRequest)), ) } c.AbortWithStatus(http.StatusInternalServerError) } }() c.Next() } } ================================================ FILE: lesson28/bluebell/logic/community.go ================================================ package logic import ( "bluebell/dao/mysql" "bluebell/models" ) func GetCommunityList() ([]*models.Community, error) { // 查数据库 查找到所有的community 并返回 return mysql.GetCommunityList() } func GetCommunityDetail(id int64) (*models.CommunityDetail, error) { return mysql.GetCommunityDetailByID(id) } ================================================ FILE: lesson28/bluebell/logic/post.go ================================================ package logic import ( "bluebell/dao/mysql" "bluebell/models" "bluebell/pkg/snowflake" "go.uber.org/zap" ) func CreatePost(p *models.Post) (err error) { // 1. 生成post id p.ID = snowflake.GenID() // 2. 保存到数据库 return mysql.CreatePost(p) // 3. 返回 } // GetPostById 根据帖子id查询帖子详情数据 func GetPostById(pid int64) (data *models.ApiPostDetail, err error) { // 查询并组合我们接口想用的数据 post, err := mysql.GetPostById(pid) if err != nil { zap.L().Error("mysql.GetPostById(pid) failed", zap.Int64("pid", pid), zap.Error(err)) return } // 根据作者id查询作者信息 user, err := mysql.GetUserById(post.AuthorID) if err != nil { zap.L().Error("mysql.GetUserById(post.AuthorID) failed", zap.Int64("author_id", post.AuthorID), zap.Error(err)) return } // 根据社区id查询社区详细信息 community, err := mysql.GetCommunityDetailByID(post.CommunityID) if err != nil { zap.L().Error("mysql.GetUserById(post.AuthorID) failed", zap.Int64("community_id", post.CommunityID), zap.Error(err)) return } // 接口数据拼接 data = &models.ApiPostDetail{ AuthorName: user.Username, Post: post, CommunityDetail: community, } return } // GetPostList 获取帖子列表 func GetPostList(page, size int64) (data []*models.ApiPostDetail, err error) { posts, err := mysql.GetPostList(page, size) if err != nil { return nil, err } data = make([]*models.ApiPostDetail, 0, len(posts)) for _, post := range posts { // 根据作者id查询作者信息 user, err := mysql.GetUserById(post.AuthorID) if err != nil { zap.L().Error("mysql.GetUserById(post.AuthorID) failed", zap.Int64("author_id", post.AuthorID), zap.Error(err)) continue } // 根据社区id查询社区详细信息 community, err := mysql.GetCommunityDetailByID(post.CommunityID) if err != nil { zap.L().Error("mysql.GetUserById(post.AuthorID) failed", zap.Int64("community_id", post.CommunityID), zap.Error(err)) continue } postDetail := &models.ApiPostDetail{ AuthorName: user.Username, Post: post, CommunityDetail: community, } data = append(data, postDetail) } return } ================================================ FILE: lesson28/bluebell/logic/user.go ================================================ package logic import ( "bluebell/dao/mysql" "bluebell/models" "bluebell/pkg/jwt" "bluebell/pkg/snowflake" ) // 存放业务逻辑的代码 func SignUp(p *models.ParamSignUp) (err error) { // 1.判断用户存不存在 if err := mysql.CheckUserExist(p.Username); err != nil { return err } // 2.生成UID userID := snowflake.GenID() // 构造一个User实例 user := &models.User{ UserID: userID, Username: p.Username, Password: p.Password, } // 3.保存进数据库 return mysql.InsertUser(user) } func Login(p *models.ParamLogin) (token string, err error) { user := &models.User{ Username: p.Username, Password: p.Password, } // 传递的是指针,就能拿到user.UserID if err := mysql.Login(user); err != nil { return "", err } // 生成JWT return jwt.GenToken(user.UserID, user.Username) } ================================================ FILE: lesson28/bluebell/main.go ================================================ package main import ( "bluebell/controller" "bluebell/dao/mysql" "bluebell/dao/redis" "bluebell/logger" "bluebell/pkg/snowflake" "bluebell/router" "bluebell/setting" "fmt" "os" ) func main() { if len(os.Args) < 2 { fmt.Println("need config file.eg: bluebell config.yaml") return } // 加载配置 if err := setting.Init(os.Args[1]); err != nil { fmt.Printf("load config failed, err:%v\n", err) return } if err := logger.Init(setting.Conf.LogConfig, setting.Conf.Mode); err != nil { fmt.Printf("init logger failed, err:%v\n", err) return } if err := mysql.Init(setting.Conf.MySQLConfig); err != nil { fmt.Printf("init mysql failed, err:%v\n", err) return } defer mysql.Close() // 程序退出关闭数据库连接 if err := redis.Init(setting.Conf.RedisConfig); err != nil { fmt.Printf("init redis failed, err:%v\n", err) return } defer redis.Close() if err := snowflake.Init(setting.Conf.StartTime, setting.Conf.MachineID); err != nil { fmt.Printf("init snowflake failed, err:%v\n", err) return } // 初始化gin框架内置的校验器使用的翻译器 if err := controller.InitTrans("zh"); err != nil { fmt.Printf("init validator trans failed, err:%v\n", err) return } // 注册路由 r := router.SetupRouter(setting.Conf.Mode) err := r.Run(fmt.Sprintf(":%d", setting.Conf.Port)) if err != nil { fmt.Printf("run server failed, err:%v\n", err) return } } ================================================ FILE: lesson28/bluebell/middlewares/auth.go ================================================ package middlewares import ( "bluebell/controller" "bluebell/pkg/jwt" "strings" "github.com/gin-gonic/gin" ) // JWTAuthMiddleware 基于JWT的认证中间件 func JWTAuthMiddleware() func(c *gin.Context) { return func(c *gin.Context) { // 客户端携带Token有三种方式 1.放在请求头 2.放在请求体 3.放在URI // 这里假设Token放在Header的Authorization中,并使用Bearer开头 // Authorization: Bearer xxxxxxx.xxx.xxx / X-TOKEN: xxx.xxx.xx // 这里的具体实现方式要依据你的实际业务情况决定 authHeader := c.Request.Header.Get("Authorization") if authHeader == "" { controller.ResponseError(c, controller.CodeNeedLogin) c.Abort() return } // 按空格分割 parts := strings.SplitN(authHeader, " ", 2) if !(len(parts) == 2 && parts[0] == "Bearer") { controller.ResponseError(c, controller.CodeInvalidToken) c.Abort() return } // parts[1]是获取到的tokenString,我们使用之前定义好的解析JWT的函数来解析它 mc, err := jwt.ParseToken(parts[1]) if err != nil { controller.ResponseError(c, controller.CodeInvalidToken) c.Abort() return } // 将当前请求的userID信息保存到请求的上下文c上 c.Set(controller.CtxUserIDKey, mc.UserID) c.Next() // 后续的处理请求的函数中 可以用过c.Get(CtxUserIDKey) 来获取当前请求的用户信息 } } ================================================ FILE: lesson28/bluebell/models/community.go ================================================ package models import "time" type Community struct { ID int64 `json:"id" db:"community_id"` Name string `json:"name" db:"community_name"` } type CommunityDetail struct { ID int64 `json:"id" db:"community_id"` Name string `json:"name" db:"community_name"` Introduction string `json:"introduction,omitempty" db:"introduction"` CreateTime time.Time `json:"create_time" db:"create_time"` } ================================================ FILE: lesson28/bluebell/models/create_table.sql ================================================ -- CREATE TABLE `user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `user_id` bigint(20) NOT NULL, `username` varchar(64) COLLATE utf8mb4_general_ci NOT NULL, `password` varchar(64) COLLATE utf8mb4_general_ci NOT NULL, `email` varchar(64) COLLATE utf8mb4_general_ci, `gender` tinyint(4) NOT NULL DEFAULT '0', `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP, `update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `idx_username` (`username`) USING BTREE, UNIQUE KEY `idx_user_id` (`user_id`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; DROP TABLE IF EXISTS `community`; CREATE TABLE `community` ( `id` int(11) NOT NULL AUTO_INCREMENT, `community_id` int(10) unsigned NOT NULL, `community_name` varchar(128) COLLATE utf8mb4_general_ci NOT NULL, `introduction` varchar(256) COLLATE utf8mb4_general_ci NOT NULL, `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `idx_community_id` (`community_id`), UNIQUE KEY `idx_community_name` (`community_name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; INSERT INTO `community` VALUES ('1', '1', 'Go', 'Golang', '2016-11-01 08:10:10', '2016-11-01 08:10:10'); INSERT INTO `community` VALUES ('2', '2', 'leetcode', '刷题刷题刷题', '2020-01-01 08:00:00', '2020-01-01 08:00:00'); INSERT INTO `community` VALUES ('3', '3', 'CS:GO', 'Rush B。。。', '2018-08-07 08:30:00', '2018-08-07 08:30:00'); INSERT INTO `community` VALUES ('4', '4', 'LOL', '欢迎来到英雄联盟!', '2016-01-01 08:00:00', '2016-01-01 08:00:00'); DROP TABLE IF EXISTS `post`; CREATE TABLE `post` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `post_id` bigint(20) NOT NULL COMMENT '帖子id', `title` varchar(128) COLLATE utf8mb4_general_ci NOT NULL COMMENT '标题', `content` varchar(8192) COLLATE utf8mb4_general_ci NOT NULL COMMENT '内容', `author_id` bigint(20) NOT NULL COMMENT '作者的用户id', `community_id` bigint(20) NOT NULL COMMENT '所属社区', `status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '帖子状态', `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), UNIQUE KEY `idx_post_id` (`post_id`), KEY `idx_author_id` (`author_id`), KEY `idx_community_id` (`community_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; ================================================ FILE: lesson28/bluebell/models/params.go ================================================ package models // 定义请求的参数结构体 // ParamSignUp 注册请求参数 type ParamSignUp struct { Username string `json:"username" binding:"required"` Password string `json:"password" binding:"required"` RePassword string `json:"re_password" binding:"required,eqfield=Password"` } // ParamLogin 登录请求参数 type ParamLogin struct { Username string `json:"username" binding:"required"` Password string `json:"password" binding:"required"` } ================================================ FILE: lesson28/bluebell/models/post.go ================================================ package models import "time" // 内存对齐概念 type Post struct { ID int64 `json:"id" db:"post_id"` AuthorID int64 `json:"author_id" db:"author_id"` CommunityID int64 `json:"community_id" db:"community_id" binding:"required"` Status int32 `json:"status" db:"status"` Title string `json:"title" db:"title" binding:"required"` Content string `json:"content" db:"content" binding:"required"` CreateTime time.Time `json:"create_time" db:"create_time"` } // ApiPostDetail 帖子详情接口的结构体 type ApiPostDetail struct { AuthorName string `json:"author_name"` *Post // 嵌入帖子结构体 *CommunityDetail `json:"community"` // 嵌入社区信息 } ================================================ FILE: lesson28/bluebell/models/struct_test.go ================================================ package models import ( "fmt" "testing" "unsafe" ) // Go 内存对齐 type s1 struct { a int8 // 1 b string // 3 c int8 // 1 } type s2 struct { a int8 c int8 // 1 b string // 2 } func TestStruct(t *testing.T) { v1 := s1{ a: 1, b: "七米", c: 2, } v2 := s2{ a: 1, b: "七米", c: 2, } fmt.Println(unsafe.Sizeof(v1), unsafe.Sizeof(v2)) } ================================================ FILE: lesson28/bluebell/models/user.go ================================================ package models type User struct { UserID int64 `db:"user_id"` Username string `db:"username"` Password string `db:"password"` } ================================================ FILE: lesson28/bluebell/pkg/jwt/jwt.go ================================================ package jwt import ( "errors" "time" "github.com/spf13/viper" "github.com/dgrijalva/jwt-go" ) var mySecret = []byte("夏天夏天悄悄过去") // MyClaims 自定义声明结构体并内嵌jwt.StandardClaims // jwt包自带的jwt.StandardClaims只包含了官方字段 // 我们这里需要额外记录一个username字段,所以要自定义结构体 // 如果想要保存更多信息,都可以添加到这个结构体中 type MyClaims struct { UserID int64 `json:"user_id"` Username string `json:"username"` jwt.StandardClaims } // GenToken 生成JWT func GenToken(userID int64, username string) (string, error) { // 创建一个我们自己的声明的数据 c := MyClaims{ userID, "username", // 自定义字段 jwt.StandardClaims{ ExpiresAt: time.Now().Add( time.Duration(viper.GetInt("auth.jwt_expire")) * time.Hour).Unix(), // 过期时间 Issuer: "bluebell", // 签发人 }, } // 使用指定的签名方法创建签名对象 token := jwt.NewWithClaims(jwt.SigningMethodHS256, c) // 使用指定的secret签名并获得完整的编码后的字符串token return token.SignedString(mySecret) } // ParseToken 解析JWT func ParseToken(tokenString string) (*MyClaims, error) { // 解析token var mc = new(MyClaims) token, err := jwt.ParseWithClaims(tokenString, mc, func(token *jwt.Token) (i interface{}, err error) { return mySecret, nil }) if err != nil { return nil, err } if token.Valid { // 校验token return mc, nil } return nil, errors.New("invalid token") } ================================================ FILE: lesson28/bluebell/pkg/snowflake/snowflake.go ================================================ package snowflake import ( "time" sf "github.com/bwmarrin/snowflake" ) var node *sf.Node func Init(startTime string, machineID int64) (err error) { var st time.Time st, err = time.Parse("2006-01-02", startTime) if err != nil { return } sf.Epoch = st.UnixNano() / 1000000 node, err = sf.NewNode(machineID) return } func GenID() int64 { return node.Generate().Int64() } ================================================ FILE: lesson28/bluebell/router/route.go ================================================ package router import ( "bluebell/controller" "bluebell/logger" "bluebell/middlewares" "net/http" "github.com/gin-gonic/gin" ) func SetupRouter(mode string) *gin.Engine { if mode == gin.ReleaseMode { gin.SetMode(gin.ReleaseMode) // gin设置成发布模式 } r := gin.New() r.Use(logger.GinLogger(), logger.GinRecovery(true)) v1 := r.Group("/api/v1") // 注册 v1.POST("/signup", controller.SignUpHandler) // 登录 v1.POST("/login", controller.LoginHandler) v1.Use(middlewares.JWTAuthMiddleware()) // 应用JWT认证中间件 { v1.GET("/community", controller.CommunityHandler) v1.GET("/community/:id", controller.CommunityDetailHandler) v1.POST("/post", controller.CreatePostHandler) v1.GET("/post/:id", controller.GetPostDetailHandler) v1.GET("/posts/", controller.GetPostListHandler) } r.NoRoute(func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "msg": "404", }) }) return r } ================================================ FILE: lesson28/bluebell/setting/setting.go ================================================ package setting import ( "fmt" "github.com/fsnotify/fsnotify" "github.com/spf13/viper" ) var Conf = new(AppConfig) type AppConfig struct { Name string `mapstructure:"name"` Mode string `mapstructure:"mode"` Version string `mapstructure:"version"` StartTime string `mapstructure:"start_time"` MachineID int64 `mapstructure:"machine_id"` Port int `mapstructure:"port"` *LogConfig `mapstructure:"log"` *MySQLConfig `mapstructure:"mysql"` *RedisConfig `mapstructure:"redis"` } type MySQLConfig struct { Host string `mapstructure:"host"` User string `mapstructure:"user"` Password string `mapstructure:"password"` DB string `mapstructure:"dbname"` Port int `mapstructure:"port"` MaxOpenConns int `mapstructure:"max_open_conns"` MaxIdleConns int `mapstructure:"max_idle_conns"` } type RedisConfig struct { Host string `mapstructure:"host"` Password string `mapstructure:"password"` Port int `mapstructure:"port"` DB int `mapstructure:"db"` PoolSize int `mapstructure:"pool_size"` MinIdleConns int `mapstructure:"min_idle_conns"` } type LogConfig struct { Level string `mapstructure:"level"` Filename string `mapstructure:"filename"` MaxSize int `mapstructure:"max_size"` MaxAge int `mapstructure:"max_age"` MaxBackups int `mapstructure:"max_backups"` } func Init(filePath string) (err error) { // 方式1:直接指定配置文件路径(相对路径或者绝对路径) // 相对路径:相对执行的可执行文件的相对路径 //viper.SetConfigFile("./conf/config.yaml") // 绝对路径:系统中实际的文件路径 //viper.SetConfigFile("/Users/liwenzhou/Desktop/bluebell/conf/config.yaml") // 方式2:指定配置文件名和配置文件的位置,viper自行查找可用的配置文件 // 配置文件名不需要带后缀 // 配置文件位置可配置多个 //viper.SetConfigName("config") // 指定配置文件名(不带后缀) //viper.AddConfigPath(".") // 指定查找配置文件的路径(这里使用相对路径) //viper.AddConfigPath("./conf") // 指定查找配置文件的路径(这里使用相对路径) // 基本上是配合远程配置中心使用的,告诉viper当前的数据使用什么格式去解析 //viper.SetConfigType("json") viper.SetConfigFile(filePath) err = viper.ReadInConfig() // 读取配置信息 if err != nil { // 读取配置信息失败 fmt.Printf("viper.ReadInConfig failed, err:%v\n", err) return } // 把读取到的配置信息反序列化到 Conf 变量中 if err := viper.Unmarshal(Conf); err != nil { fmt.Printf("viper.Unmarshal failed, err:%v\n", err) } viper.WatchConfig() viper.OnConfigChange(func(in fsnotify.Event) { fmt.Println("配置文件修改了...") if err := viper.Unmarshal(Conf); err != nil { fmt.Printf("viper.Unmarshal failed, err:%v\n", err) } }) return } ================================================ FILE: lesson35/bluebell/.air.conf ================================================ # [Air](https://github.com/cosmtrek/air) TOML 格式的配置文件 # 工作目录 # 使用 . 或绝对路径,请注意 `tmp_dir` 目录必须在 `root` 目录下 root = "." tmp_dir = "tmp" [build] # 只需要写你平常编译使用的shell命令。你也可以使用 `make` cmd = "go build -o ./tmp/main" # 由`cmd`命令得到的二进制文件名 bin = "tmp/main" # 自定义的二进制,可以添加额外的编译标识例如添加 GIN_MODE=release full_bin = "./tmp/main ./conf/config.yaml" # 监听以下文件扩展名的文件. include_ext = ["go", "tpl", "tmpl", "html", "yaml"] # 忽略这些文件扩展名或目录 exclude_dir = ["assets", "tmp", "vendor", "frontend/node_modules"] # 监听以下指定目录的文件 include_dir = [] # 排除以下文件 exclude_file = [] # 如果文件更改过于频繁,则没有必要在每次更改时都触发构建。可以设置触发构建的延迟时间 delay = 1000 # ms # 发生构建错误时,停止运行旧的二进制文件。 stop_on_error = true # air的日志文件名,该日志文件放置在你的`tmp_dir`中 log = "air_errors.log" [log] # 显示日志时间 time = true [color] # 自定义每个部分显示的颜色。如果找不到颜色,使用原始的应用程序日志。 main = "magenta" watcher = "cyan" build = "yellow" runner = "green" [misc] # 退出时删除tmp目录 clean_on_exit = true ================================================ FILE: lesson35/bluebell/Makefile ================================================ .PHONY: all build run gotool clean help BINARY="xx" all: gotool build build: CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o ${BINARY} run: @go run ./main.go conf/config.yaml gotool: go fmt ./ go vet ./ clean: @if [ -f ${BINARY} ] ; then rm ${BINARY} ; fi help: @echo "make - 格式化 Go 代码, 并编译生成二进制文件" @echo "make build - 编译 Go 代码, 生成二进制文件" @echo "make run - 直接运行 Go 代码" @echo "make clean - 移除二进制文件和 vim swap files" @echo "make gotool - 运行 Go 工具 'fmt' and 'vet'" ================================================ FILE: lesson35/bluebell/bluebell ================================================ [File too large to display: 20.6 MB] ================================================ FILE: lesson35/bluebell/conf/config.yaml ================================================ name: "bluebell" mode: "dev" port: 8084 version: "v0.0.1" start_time: "2020-07-01" machine_id: 1 auth: jwt_expire: 8760 log: level: "debug" filename: "web_app.log" max_size: 200 max_age: 30 max_backups: 7 mysql: host: "127.0.0.1" port: 13306 user: "root" password: "root1234" dbname: "bluebell" max_open_conns: 200 max_idle_conns: 50 redis: host: "127.0.0.1" port: 16379 password: "" db: 0 pool_size: 100 ================================================ FILE: lesson35/bluebell/controller/code.go ================================================ package controller type ResCode int64 const ( CodeSuccess ResCode = 1000 + iota CodeInvalidParam CodeUserExist CodeUserNotExist CodeInvalidPassword CodeServerBusy CodeNeedLogin CodeInvalidToken ) var codeMsgMap = map[ResCode]string{ CodeSuccess: "success", CodeInvalidParam: "请求参数错误", CodeUserExist: "用户名已存在", CodeUserNotExist: "用户名不存在", CodeInvalidPassword: "用户名或密码错误", CodeServerBusy: "服务繁忙", CodeNeedLogin: "需要登录", CodeInvalidToken: "无效的token", } func (c ResCode) Msg() string { msg, ok := codeMsgMap[c] if !ok { msg = codeMsgMap[CodeServerBusy] } return msg } ================================================ FILE: lesson35/bluebell/controller/community.go ================================================ package controller import ( "bluebell/logic" "strconv" "github.com/gin-gonic/gin" "go.uber.org/zap" ) // ---- 跟社区相关的 ---- func CommunityHandler(c *gin.Context) { // 查询到所有的社区(community_id, community_name) 以列表的形式返回 data, err := logic.GetCommunityList() if err != nil { zap.L().Error("logic.GetCommunityList() failed", zap.Error(err)) ResponseError(c, CodeServerBusy) // 不轻易把服务端报错暴露给外面 return } ResponseSuccess(c, data) } // CommunityDetailHandler 社区分类详情 func CommunityDetailHandler(c *gin.Context) { // 1. 获取社区id idStr := c.Param("id") // 获取URL参数 id, err := strconv.ParseInt(idStr, 10, 64) if err != nil { ResponseError(c, CodeInvalidParam) return } // 2. 根据id获取社区详情 data, err := logic.GetCommunityDetail(id) if err != nil { zap.L().Error("logic.GetCommunityList() failed", zap.Error(err)) ResponseError(c, CodeServerBusy) // 不轻易把服务端报错暴露给外面 return } ResponseSuccess(c, data) } ================================================ FILE: lesson35/bluebell/controller/post.go ================================================ package controller import ( "bluebell/logic" "bluebell/models" "strconv" "github.com/gin-gonic/gin" "go.uber.org/zap" ) // CreatePostHandler 创建帖子的处理函数 func CreatePostHandler(c *gin.Context) { // 1. 获取参数及参数的校验 //c.ShouldBindJSON() // validator --> binding tag p := new(models.Post) if err := c.ShouldBindJSON(p); err != nil { zap.L().Debug("c.ShouldBindJSON(p) error", zap.Any("err", err)) zap.L().Error("create post with invalid param") ResponseError(c, CodeInvalidParam) return } // 从 c 取到当前发请求的用户的ID userID, err := getCurrentUserID(c) if err != nil { ResponseError(c, CodeNeedLogin) return } p.AuthorID = userID // 2. 创建帖子 if err := logic.CreatePost(p); err != nil { zap.L().Error("logic.CreatePost(p) failed", zap.Error(err)) ResponseError(c, CodeServerBusy) return } // 3. 返回响应 ResponseSuccess(c, nil) } // GetPostDetailHandler 获取帖子详情的处理函数 func GetPostDetailHandler(c *gin.Context) { // 1. 获取参数(从URL中获取帖子的id) pidStr := c.Param("id") pid, err := strconv.ParseInt(pidStr, 10, 64) if err != nil { zap.L().Error("get post detail with invalid param", zap.Error(err)) ResponseError(c, CodeInvalidParam) return } // 2. 根据id取出帖子数据(查数据库) data, err := logic.GetPostById(pid) if err != nil { zap.L().Error("logic.GetPostById(pid) failed", zap.Error(err)) ResponseError(c, CodeServerBusy) return } // 3. 返回响应 ResponseSuccess(c, data) } // GetPostListHandler 获取帖子列表的处理函数 func GetPostListHandler(c *gin.Context) { // 获取分页参数 page, size := getPageInfo(c) // 获取数据 data, err := logic.GetPostList(page, size) if err != nil { zap.L().Error("logic.GetPostList() failed", zap.Error(err)) ResponseError(c, CodeServerBusy) return } ResponseSuccess(c, data) // 返回响应 } ================================================ FILE: lesson35/bluebell/controller/request.go ================================================ package controller import ( "errors" "strconv" "github.com/gin-gonic/gin" ) const CtxUserIDKey = "userID" var ErrorUserNotLogin = errors.New("用户未登录") // getCurrentUserID 获取当前登录的用户ID func getCurrentUserID(c *gin.Context) (userID int64, err error) { uid, ok := c.Get(CtxUserIDKey) if !ok { err = ErrorUserNotLogin return } userID, ok = uid.(int64) if !ok { err = ErrorUserNotLogin return } return } func getPageInfo(c *gin.Context) (int64, int64) { pageStr := c.Query("page") sizeStr := c.Query("size") var ( page int64 size int64 err error ) page, err = strconv.ParseInt(pageStr, 10, 64) if err != nil { page = 1 } size, err = strconv.ParseInt(sizeStr, 10, 64) if err != nil { size = 10 } return page, size } ================================================ FILE: lesson35/bluebell/controller/response.go ================================================ package controller import ( "net/http" "github.com/gin-gonic/gin" ) /* { "code": 10000, // 程序中的错误码 "msg": xx, // 提示信息 "data": {}, // 数据 } */ type ResponseData struct { Code ResCode `json:"code"` Msg interface{} `json:"msg"` Data interface{} `json:"data,omitempty"` } func ResponseError(c *gin.Context, code ResCode) { c.JSON(http.StatusOK, &ResponseData{ Code: code, Msg: code.Msg(), Data: nil, }) } func ResponseErrorWithMsg(c *gin.Context, code ResCode, msg interface{}) { c.JSON(http.StatusOK, &ResponseData{ Code: code, Msg: msg, Data: nil, }) } func ResponseSuccess(c *gin.Context, data interface{}) { c.JSON(http.StatusOK, &ResponseData{ Code: CodeSuccess, Msg: CodeSuccess.Msg(), Data: data, }) } ================================================ FILE: lesson35/bluebell/controller/user.go ================================================ package controller import ( "bluebell/dao/mysql" "bluebell/logic" "bluebell/models" "errors" "fmt" "github.com/go-playground/validator/v10" "go.uber.org/zap" "github.com/gin-gonic/gin" ) // SignUpHandler 处理注册请求的函数 func SignUpHandler(c *gin.Context) { // 1. 获取参数和参数校验 p := new(models.ParamSignUp) if err := c.ShouldBindJSON(p); err != nil { // 请求参数有误,直接返回响应 zap.L().Error("SignUp with invalid param", zap.Error(err)) // 判断err是不是validator.ValidationErrors 类型 errs, ok := err.(validator.ValidationErrors) if !ok { ResponseError(c, CodeInvalidParam) return } ResponseErrorWithMsg(c, CodeInvalidParam, removeTopStruct(errs.Translate(trans))) return } // 2. 业务处理 if err := logic.SignUp(p); err != nil { zap.L().Error("logic.SignUp failed", zap.Error(err)) if errors.Is(err, mysql.ErrorUserExist) { ResponseError(c, CodeUserExist) return } ResponseError(c, CodeServerBusy) return } // 3. 返回响应 ResponseSuccess(c, nil) } func LoginHandler(c *gin.Context) { // 1.获取请求参数及参数校验 p := new(models.ParamLogin) if err := c.ShouldBindJSON(p); err != nil { // 请求参数有误,直接返回响应 zap.L().Error("Login with invalid param", zap.Error(err)) // 判断err是不是validator.ValidationErrors 类型 errs, ok := err.(validator.ValidationErrors) if !ok { ResponseError(c, CodeInvalidParam) return } ResponseErrorWithMsg(c, CodeInvalidParam, removeTopStruct(errs.Translate(trans))) return } // 2.业务逻辑处理 user, err := logic.Login(p) if err != nil { zap.L().Error("logic.Login failed", zap.String("username", p.Username), zap.Error(err)) if errors.Is(err, mysql.ErrorUserNotExist) { ResponseError(c, CodeUserNotExist) return } ResponseError(c, CodeInvalidPassword) return } // 3.返回响应 ResponseSuccess(c, gin.H{ "user_id": fmt.Sprintf("%d", user.UserID), // id值大于1<<53-1 int64类型的最大值是1<<63-1 "user_name": user.Username, "token": user.Token, }) } ================================================ FILE: lesson35/bluebell/controller/validator.go ================================================ package controller import ( "bluebell/models" "fmt" "reflect" "strings" "github.com/gin-gonic/gin/binding" "github.com/go-playground/locales/en" "github.com/go-playground/locales/zh" ut "github.com/go-playground/universal-translator" "github.com/go-playground/validator/v10" enTranslations "github.com/go-playground/validator/v10/translations/en" zhTranslations "github.com/go-playground/validator/v10/translations/zh" ) // 定义一个全局翻译器T var trans ut.Translator // InitTrans 初始化翻译器 func InitTrans(locale string) (err error) { // 修改gin框架中的Validator引擎属性,实现自定制 if v, ok := binding.Validator.Engine().(*validator.Validate); ok { // 注册一个获取json tag的自定义方法 v.RegisterTagNameFunc(func(fld reflect.StructField) string { name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0] if name == "-" { return "" } return name }) // 为SignUpParam注册自定义校验方法 v.RegisterStructValidation(SignUpParamStructLevelValidation, models.ParamSignUp{}) zhT := zh.New() // 中文翻译器 enT := en.New() // 英文翻译器 // 第一个参数是备用(fallback)的语言环境 // 后面的参数是应该支持的语言环境(支持多个) // uni := ut.New(zhT, zhT) 也是可以的 uni := ut.New(enT, zhT, enT) // locale 通常取决于 http 请求头的 'Accept-Language' var ok bool // 也可以使用 uni.FindTranslator(...) 传入多个locale进行查找 trans, ok = uni.GetTranslator(locale) if !ok { return fmt.Errorf("uni.GetTranslator(%s) failed", locale) } // 注册翻译器 switch locale { case "en": err = enTranslations.RegisterDefaultTranslations(v, trans) case "zh": err = zhTranslations.RegisterDefaultTranslations(v, trans) default: err = enTranslations.RegisterDefaultTranslations(v, trans) } return } return } // removeTopStruct 去除提示信息中的结构体名称 func removeTopStruct(fields map[string]string) map[string]string { res := map[string]string{} for field, err := range fields { res[field[strings.Index(field, ".")+1:]] = err } return res } // SignUpParamStructLevelValidation 自定义SignUpParam结构体校验函数 func SignUpParamStructLevelValidation(sl validator.StructLevel) { su := sl.Current().Interface().(models.ParamSignUp) if su.Password != su.RePassword { // 输出错误提示信息,最后一个参数就是传递的param sl.ReportError(su.RePassword, "re_password", "RePassword", "eqfield", "password") } } ================================================ FILE: lesson35/bluebell/controller/vote.go ================================================ package controller import ( "bluebell/logic" "bluebell/models" "go.uber.org/zap" "github.com/go-playground/validator/v10" "github.com/gin-gonic/gin" ) // 投票 //type VoteData struct { // // UserID 从请求中获取当前的用户 // PostID int64 `json:"post_id,string"` // 贴子id // Direction int `json:"direction,string"` // 赞成票(1)还是反对票(-1) //} func PostVoteController(c *gin.Context) { // 参数校验 p := new(models.ParamVoteData) if err := c.ShouldBindJSON(p); err != nil { errs, ok := err.(validator.ValidationErrors) // 类型断言 if !ok { ResponseError(c, CodeInvalidParam) return } errData := removeTopStruct(errs.Translate(trans)) // 翻译并去除掉错误提示中的结构体标识 ResponseErrorWithMsg(c, CodeInvalidParam, errData) return } // 获取当前请求的用户的id userID, err := getCurrentUserID(c) if err != nil { ResponseError(c, CodeNeedLogin) return } // 具体投票的业务逻辑 if err := logic.VoteForPost(userID, p); err != nil { zap.L().Error("logic.VoteForPost() failed", zap.Error(err)) ResponseError(c, CodeServerBusy) return } ResponseSuccess(c, nil) } ================================================ FILE: lesson35/bluebell/dao/mysql/community.go ================================================ package mysql import ( "bluebell/models" "database/sql" "go.uber.org/zap" ) func GetCommunityList() (communityList []*models.Community, err error) { sqlStr := "select community_id, community_name from community" if err := db.Select(&communityList, sqlStr); err != nil { if err == sql.ErrNoRows { zap.L().Warn("there is no community in db") err = nil } } return } // GetCommunityDetailByID 根据ID查询社区详情 func GetCommunityDetailByID(id int64) (community *models.CommunityDetail, err error) { community = new(models.CommunityDetail) sqlStr := `select community_id, community_name, introduction, create_time from community where community_id = ? ` if err := db.Get(community, sqlStr, id); err != nil { if err == sql.ErrNoRows { err = ErrorInvalidID } } return community, err } ================================================ FILE: lesson35/bluebell/dao/mysql/error_code.go ================================================ package mysql import "errors" var ( ErrorUserExist = errors.New("用户已存在") ErrorUserNotExist = errors.New("用户不存在") ErrorInvalidPassword = errors.New("用户名或密码错误") ErrorInvalidID = errors.New("无效的ID") ) ================================================ FILE: lesson35/bluebell/dao/mysql/mysql.go ================================================ package mysql import ( "bluebell/setting" "fmt" _ "github.com/go-sql-driver/mysql" "github.com/jmoiron/sqlx" ) var db *sqlx.DB // Init 初始化MySQL连接 func Init(cfg *setting.MySQLConfig) (err error) { // "user:password@tcp(host:port)/dbname" dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?parseTime=true&loc=Local", cfg.User, cfg.Password, cfg.Host, cfg.Port, cfg.DB) db, err = sqlx.Connect("mysql", dsn) if err != nil { return } db.SetMaxOpenConns(cfg.MaxOpenConns) db.SetMaxIdleConns(cfg.MaxIdleConns) return } // Close 关闭MySQL连接 func Close() { _ = db.Close() } ================================================ FILE: lesson35/bluebell/dao/mysql/post.go ================================================ package mysql import "bluebell/models" func CreatePost(p *models.Post) (err error) { sqlStr := `insert into post( post_id, title, content, author_id, community_id) values (?, ?, ?, ?, ?) ` _, err = db.Exec(sqlStr, p.ID, p.Title, p.Content, p.AuthorID, p.CommunityID) return } func GetPostById(pid int64) (post *models.Post, err error) { post = new(models.Post) sqlStr := `select post_id, title, content, author_id, community_id, create_time from post where post_id = ? ` err = db.Get(post, sqlStr, pid) return } func GetPostList(page, size int64) (posts []*models.Post, err error) { sqlStr := `select post_id, title, content, author_id, community_id, create_time from post limit ?,? ` posts = make([]*models.Post, 0, 2) // 不要写成make([]*models.Post, 2) err = db.Select(&posts, sqlStr, (page-1)*size, size) return } ================================================ FILE: lesson35/bluebell/dao/mysql/user.go ================================================ package mysql import ( "bluebell/models" "crypto/md5" "database/sql" "encoding/hex" ) // 把每一步数据库操作封装成函数 // 待logic层根据业务需求调用 const secret = "liwenzhou.com" // CheckUserExist 检查指定用户名的用户是否存在 func CheckUserExist(username string) (err error) { sqlStr := `select count(user_id) from user where username = ?` var count int64 if err := db.Get(&count, sqlStr, username); err != nil { return err } if count > 0 { return ErrorUserExist } return } // InsertUser 想数据库中插入一条新的用户记录 func InsertUser(user *models.User) (err error) { // 对密码进行加密 user.Password = encryptPassword(user.Password) // 执行SQL语句入库 sqlStr := `insert into user(user_id, username, password) values(?,?,?)` _, err = db.Exec(sqlStr, user.UserID, user.Username, user.Password) return } // encryptPassword 密码加密 func encryptPassword(oPassword string) string { h := md5.New() h.Write([]byte(secret)) return hex.EncodeToString(h.Sum([]byte(oPassword))) } func Login(user *models.User) (err error) { oPassword := user.Password // 用户登录的密码 sqlStr := `select user_id, username, password from user where username=?` err = db.Get(user, sqlStr, user.Username) if err == sql.ErrNoRows { return ErrorUserNotExist } if err != nil { // 查询数据库失败 return err } // 判断密码是否正确 password := encryptPassword(oPassword) if password != user.Password { return ErrorInvalidPassword } return } // GetUserById 根据id获取用户信息 func GetUserById(uid int64) (user *models.User, err error) { user = new(models.User) sqlStr := `select user_id, username from user where user_id = ?` err = db.Get(user, sqlStr, uid) return } ================================================ FILE: lesson35/bluebell/dao/redis/keys.go ================================================ package redis // redis key // redis key注意使用命名空间的方式,方便查询和拆分 const ( Prefix = "bluebell:" // 项目key前缀 KeyPostTimeZSet = "post:time" // zset;贴子及发帖时间 KeyPostScoreZSet = "post:score" // zset;贴子及投票的分数 KeyPostVotedZSetPF = "post:voted:" // zset;记录用户及投票类型;参数是post id ) // 给redis key加上前缀 func getRedisKey(key string) string { return Prefix + key } ================================================ FILE: lesson35/bluebell/dao/redis/redis.go ================================================ package redis import ( "bluebell/setting" "fmt" "github.com/go-redis/redis" ) var ( client *redis.Client Nil = redis.Nil ) // Init 初始化连接 func Init(cfg *setting.RedisConfig) (err error) { client = redis.NewClient(&redis.Options{ Addr: fmt.Sprintf("%s:%d", cfg.Host, cfg.Port), Password: cfg.Password, // no password set DB: cfg.DB, // use default DB PoolSize: cfg.PoolSize, MinIdleConns: cfg.MinIdleConns, }) _, err = client.Ping().Result() if err != nil { return err } return nil } func Close() { _ = client.Close() } ================================================ FILE: lesson35/bluebell/dao/redis/vote.go ================================================ package redis import ( "errors" "math" "time" "github.com/go-redis/redis" ) // 推荐阅读 // 基于用户投票的相关算法:http://www.ruanyifeng.com/blog/algorithm/ // 本项目使用简化版的投票分数 // 投一票就加432分 86400/200 --> 200张赞成票可以给你的帖子续一天 /* 投票的几种情况: direction=1时,有两种情况: 1. 之前没有投过票,现在投赞成票 --> 更新分数和投票记录 差值的绝对值:1 +432 2. 之前投反对票,现在改投赞成票 --> 更新分数和投票记录 差值的绝对值:2 +432*2 direction=0时,有两种情况: 1. 之前投过反对票,现在要取消投票 --> 更新分数和投票记录 差值的绝对值:1 +432 2. 之前投过赞成票,现在要取消投票 --> 更新分数和投票记录 差值的绝对值:1 -432 direction=-1时,有两种情况: 1. 之前没有投过票,现在投反对票 --> 更新分数和投票记录 差值的绝对值:1 -432 2. 之前投赞成票,现在改投反对票 --> 更新分数和投票记录 差值的绝对值:2 -432*2 投票的限制: 每个贴子自发表之日起一个星期之内允许用户投票,超过一个星期就不允许再投票了。 1. 到期之后将redis中保存的赞成票数及反对票数存储到mysql表中 2. 到期之后删除那个 KeyPostVotedZSetPF */ const ( oneWeekInSeconds = 7 * 24 * 3600 scorePerVote = 432 // 每一票值多少分 ) var ( ErrVoteTimeExpire = errors.New("投票时间已过") ) func CreatePost(postID int64) error { pipeline := client.TxPipeline() // 帖子时间 pipeline.ZAdd(getRedisKey(KeyPostTimeZSet), redis.Z{ Score: float64(time.Now().Unix()), Member: postID, }) // 帖子分数 pipeline.ZAdd(getRedisKey(KeyPostScoreZSet), redis.Z{ Score: float64(time.Now().Unix()), Member: postID, }) _, err := pipeline.Exec() return err } func VoteForPost(userID, postID string, value float64) error { // 1. 判断投票限制 // 去redis取帖子发布时间 postTime := client.ZScore(getRedisKey(KeyPostTimeZSet), postID).Val() if float64(time.Now().Unix())-postTime > oneWeekInSeconds { return ErrVoteTimeExpire } // 2和3需要放到一个pipeline事务中操作 // 2. 更新贴子的分数 // 先查当前用户给当前帖子的投票记录 ov := client.ZScore(getRedisKey(KeyPostVotedZSetPF+postID), userID).Val() var op float64 if value > ov { op = 1 } else { op = -1 } diff := math.Abs(ov - value) // 计算两次投票的差值 pipeline := client.TxPipeline() pipeline.ZIncrBy(getRedisKey(KeyPostScoreZSet), op*diff*scorePerVote, postID) // 3. 记录用户为该贴子投票的数据 if value == 0 { pipeline.ZRem(getRedisKey(KeyPostVotedZSetPF+postID), postID) } else { pipeline.ZAdd(getRedisKey(KeyPostVotedZSetPF+postID), redis.Z{ Score: value, // 赞成票还是反对票 Member: userID, }) } _, err := pipeline.Exec() return err } ================================================ FILE: lesson35/bluebell/go.mod ================================================ module bluebell go 1.14 require ( github.com/bwmarrin/snowflake v0.3.0 github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/fsnotify/fsnotify v1.4.9 github.com/gin-gonic/gin v1.6.3 github.com/go-playground/locales v0.13.0 github.com/go-playground/universal-translator v0.17.0 github.com/go-playground/validator/v10 v10.2.0 github.com/go-redis/redis v6.15.8+incompatible github.com/go-sql-driver/mysql v1.5.0 github.com/jmoiron/sqlx v1.2.0 github.com/natefinch/lumberjack v2.0.0+incompatible github.com/onsi/ginkgo v1.14.0 // indirect github.com/sony/sonyflake v1.0.0 // indirect github.com/spf13/viper v1.7.0 go.uber.org/zap v1.15.0 gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect ) ================================================ FILE: lesson35/bluebell/go.sum ================================================ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0= github.com/bwmarrin/snowflake v0.3.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/E9WsDpxqwE= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/deckarep/golang-set v1.7.1/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14= github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY= github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= github.com/go-redis/redis v6.15.8+incompatible h1:BKZuG6mCnRj5AOaWJXoCgf6rqTYnYJLe4en2hxT7r9o= github.com/go-redis/redis v6.15.8+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA= github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-sqlite3 v1.9.0 h1:pDRiWfl+++eC2FEFRy6jXmQlvp4Yh3z1MJKg4UeYM/4= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/natefinch/lumberjack v2.0.0+incompatible h1:4QJd3OLAMgj7ph+yZTuX13Ld4UpgHp07nNdFX7mqFfM= github.com/natefinch/lumberjack v2.0.0+incompatible/go.mod h1:Wi9p2TTF5DG5oU+6YfsmYQpsTIOm0B1VNzQg9Mw6nPk= github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/sony/sonyflake v1.0.0 h1:MpU6Ro7tfXwgn2l5eluf9xQvQJDROTBImNCfRXn/YeM= github.com/sony/sonyflake v1.0.0/go.mod h1:Jv3cfhf/UFtolOTTRd3q4Nl6ENqM+KfyZ5PseKfZGF4= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.15.0 h1:ZZCA22JRF2gQE5FoNmhmrf7jeJJ2uhqDUNRYKm8dvmM= go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 h1:AeiKBIuRw3UomYXSbLy0Mc2dDLfdtbT/IVn4keq83P0= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 h1:DYfZAGf2WMFjMxbgTjaC+2HC7NkNAQs+6Q8b9WEB/F4= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc h1:NCy3Ohtk6Iny5V/reW2Ktypo4zIpWBdRJ1uFMjBxdg8= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= ================================================ FILE: lesson35/bluebell/logger/logger.go ================================================ package logger import ( "bluebell/setting" "net" "net/http" "net/http/httputil" "os" "runtime/debug" "strings" "time" "github.com/gin-gonic/gin" "github.com/natefinch/lumberjack" "go.uber.org/zap" "go.uber.org/zap/zapcore" ) var lg *zap.Logger // Init 初始化lg func Init(cfg *setting.LogConfig, mode string) (err error) { writeSyncer := getLogWriter(cfg.Filename, cfg.MaxSize, cfg.MaxBackups, cfg.MaxAge) encoder := getEncoder() var l = new(zapcore.Level) err = l.UnmarshalText([]byte(cfg.Level)) if err != nil { return } var core zapcore.Core if mode == "dev" { // 进入开发模式,日志输出到终端 consoleEncoder := zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig()) core = zapcore.NewTee( zapcore.NewCore(encoder, writeSyncer, l), zapcore.NewCore(consoleEncoder, zapcore.Lock(os.Stdout), zapcore.DebugLevel), ) } else { core = zapcore.NewCore(encoder, writeSyncer, l) } lg = zap.New(core, zap.AddCaller()) zap.ReplaceGlobals(lg) zap.L().Info("init logger success") return } func getEncoder() zapcore.Encoder { encoderConfig := zap.NewProductionEncoderConfig() encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder encoderConfig.TimeKey = "time" encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder encoderConfig.EncodeDuration = zapcore.SecondsDurationEncoder encoderConfig.EncodeCaller = zapcore.ShortCallerEncoder return zapcore.NewJSONEncoder(encoderConfig) } func getLogWriter(filename string, maxSize, maxBackup, maxAge int) zapcore.WriteSyncer { lumberJackLogger := &lumberjack.Logger{ Filename: filename, MaxSize: maxSize, MaxBackups: maxBackup, MaxAge: maxAge, } return zapcore.AddSync(lumberJackLogger) } // GinLogger 接收gin框架默认的日志 func GinLogger() gin.HandlerFunc { return func(c *gin.Context) { start := time.Now() path := c.Request.URL.Path query := c.Request.URL.RawQuery c.Next() cost := time.Since(start) lg.Info(path, zap.Int("status", c.Writer.Status()), zap.String("method", c.Request.Method), zap.String("path", path), zap.String("query", query), zap.String("ip", c.ClientIP()), zap.String("user-agent", c.Request.UserAgent()), zap.String("errors", c.Errors.ByType(gin.ErrorTypePrivate).String()), zap.Duration("cost", cost), ) } } // GinRecovery recover掉项目可能出现的panic,并使用zap记录相关日志 func GinRecovery(stack bool) gin.HandlerFunc { return func(c *gin.Context) { defer func() { if err := recover(); err != nil { // Check for a broken connection, as it is not really a // condition that warrants a panic stack trace. var brokenPipe bool if ne, ok := err.(*net.OpError); ok { if se, ok := ne.Err.(*os.SyscallError); ok { if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") { brokenPipe = true } } } httpRequest, _ := httputil.DumpRequest(c.Request, false) if brokenPipe { lg.Error(c.Request.URL.Path, zap.Any("error", err), zap.String("request", string(httpRequest)), ) // If the connection is dead, we can't write a status to it. c.Error(err.(error)) // nolint: errcheck c.Abort() return } if stack { lg.Error("[Recovery from panic]", zap.Any("error", err), zap.String("request", string(httpRequest)), zap.String("stack", string(debug.Stack())), ) } else { lg.Error("[Recovery from panic]", zap.Any("error", err), zap.String("request", string(httpRequest)), ) } c.AbortWithStatus(http.StatusInternalServerError) } }() c.Next() } } ================================================ FILE: lesson35/bluebell/logic/community.go ================================================ package logic import ( "bluebell/dao/mysql" "bluebell/models" ) func GetCommunityList() ([]*models.Community, error) { // 查数据库 查找到所有的community 并返回 return mysql.GetCommunityList() } func GetCommunityDetail(id int64) (*models.CommunityDetail, error) { return mysql.GetCommunityDetailByID(id) } ================================================ FILE: lesson35/bluebell/logic/post.go ================================================ package logic import ( "bluebell/dao/mysql" "bluebell/dao/redis" "bluebell/models" "bluebell/pkg/snowflake" "go.uber.org/zap" ) func CreatePost(p *models.Post) (err error) { // 1. 生成post id p.ID = snowflake.GenID() // 2. 保存到数据库 err = mysql.CreatePost(p) if err != nil { return err } err = redis.CreatePost(p.ID) return // 3. 返回 } // GetPostById 根据帖子id查询帖子详情数据 func GetPostById(pid int64) (data *models.ApiPostDetail, err error) { // 查询并组合我们接口想用的数据 post, err := mysql.GetPostById(pid) if err != nil { zap.L().Error("mysql.GetPostById(pid) failed", zap.Int64("pid", pid), zap.Error(err)) return } // 根据作者id查询作者信息 user, err := mysql.GetUserById(post.AuthorID) if err != nil { zap.L().Error("mysql.GetUserById(post.AuthorID) failed", zap.Int64("author_id", post.AuthorID), zap.Error(err)) return } // 根据社区id查询社区详细信息 community, err := mysql.GetCommunityDetailByID(post.CommunityID) if err != nil { zap.L().Error("mysql.GetUserById(post.AuthorID) failed", zap.Int64("community_id", post.CommunityID), zap.Error(err)) return } // 接口数据拼接 data = &models.ApiPostDetail{ AuthorName: user.Username, Post: post, CommunityDetail: community, } return } // GetPostList 获取帖子列表 func GetPostList(page, size int64) (data []*models.ApiPostDetail, err error) { posts, err := mysql.GetPostList(page, size) if err != nil { return nil, err } data = make([]*models.ApiPostDetail, 0, len(posts)) for _, post := range posts { // 根据作者id查询作者信息 user, err := mysql.GetUserById(post.AuthorID) if err != nil { zap.L().Error("mysql.GetUserById(post.AuthorID) failed", zap.Int64("author_id", post.AuthorID), zap.Error(err)) continue } // 根据社区id查询社区详细信息 community, err := mysql.GetCommunityDetailByID(post.CommunityID) if err != nil { zap.L().Error("mysql.GetUserById(post.AuthorID) failed", zap.Int64("community_id", post.CommunityID), zap.Error(err)) continue } postDetail := &models.ApiPostDetail{ AuthorName: user.Username, Post: post, CommunityDetail: community, } data = append(data, postDetail) } return } ================================================ FILE: lesson35/bluebell/logic/user.go ================================================ package logic import ( "bluebell/dao/mysql" "bluebell/models" "bluebell/pkg/jwt" "bluebell/pkg/snowflake" ) // 存放业务逻辑的代码 func SignUp(p *models.ParamSignUp) (err error) { // 1.判断用户存不存在 if err := mysql.CheckUserExist(p.Username); err != nil { return err } // 2.生成UID userID := snowflake.GenID() // 构造一个User实例 user := &models.User{ UserID: userID, Username: p.Username, Password: p.Password, } // 3.保存进数据库 return mysql.InsertUser(user) } func Login(p *models.ParamLogin) (user *models.User, err error) { user = &models.User{ Username: p.Username, Password: p.Password, } // 传递的是指针,就能拿到user.UserID if err := mysql.Login(user); err != nil { return nil, err } // 生成JWT token, err := jwt.GenToken(user.UserID, user.Username) if err != nil { return } user.Token = token return } ================================================ FILE: lesson35/bluebell/logic/vote.go ================================================ package logic import ( "bluebell/dao/redis" "bluebell/models" "strconv" "go.uber.org/zap" ) // 推荐阅读 // 基于用户投票的相关算法:http://www.ruanyifeng.com/blog/algorithm/ // 本项目使用简化版的投票分数 // 投一票就加432分 86400/200 --> 200张赞成票可以给你的帖子续一天 /* 投票的几种情况: direction=1时,有两种情况: 1. 之前没有投过票,现在投赞成票 --> 更新分数和投票记录 2. 之前投反对票,现在改投赞成票 --> 更新分数和投票记录 direction=0时,有两种情况: 1. 之前投过赞成票,现在要取消投票 --> 更新分数和投票记录 2. 之前投过反对票,现在要取消投票 --> 更新分数和投票记录 direction=-1时,有两种情况: 1. 之前没有投过票,现在投反对票 --> 更新分数和投票记录 2. 之前投赞成票,现在改投反对票 --> 更新分数和投票记录 投票的限制: 每个贴子自发表之日起一个星期之内允许用户投票,超过一个星期就不允许再投票了。 1. 到期之后将redis中保存的赞成票数及反对票数存储到mysql表中 2. 到期之后删除那个 KeyPostVotedZSetPF */ // VoteForPost 为帖子投票的函数 func VoteForPost(userID int64, p *models.ParamVoteData) error { zap.L().Debug("VoteForPost", zap.Int64("userID", userID), zap.String("postID", p.PostID), zap.Int8("direction", p.Direction)) return redis.VoteForPost(strconv.Itoa(int(userID)), p.PostID, float64(p.Direction)) } ================================================ FILE: lesson35/bluebell/main.go ================================================ package main import ( "bluebell/controller" "bluebell/dao/mysql" "bluebell/dao/redis" "bluebell/logger" "bluebell/pkg/snowflake" "bluebell/router" "bluebell/setting" "fmt" "os" ) func main() { if len(os.Args) < 2 { fmt.Println("need config file.eg: bluebell config.yaml") return } // 加载配置 if err := setting.Init(os.Args[1]); err != nil { fmt.Printf("load config failed, err:%v\n", err) return } if err := logger.Init(setting.Conf.LogConfig, setting.Conf.Mode); err != nil { fmt.Printf("init logger failed, err:%v\n", err) return } if err := mysql.Init(setting.Conf.MySQLConfig); err != nil { fmt.Printf("init mysql failed, err:%v\n", err) return } defer mysql.Close() // 程序退出关闭数据库连接 if err := redis.Init(setting.Conf.RedisConfig); err != nil { fmt.Printf("init redis failed, err:%v\n", err) return } defer redis.Close() if err := snowflake.Init(setting.Conf.StartTime, setting.Conf.MachineID); err != nil { fmt.Printf("init snowflake failed, err:%v\n", err) return } // 初始化gin框架内置的校验器使用的翻译器 if err := controller.InitTrans("zh"); err != nil { fmt.Printf("init validator trans failed, err:%v\n", err) return } // 注册路由 r := router.SetupRouter(setting.Conf.Mode) err := r.Run(fmt.Sprintf(":%d", setting.Conf.Port)) if err != nil { fmt.Printf("run server failed, err:%v\n", err) return } } ================================================ FILE: lesson35/bluebell/middlewares/auth.go ================================================ package middlewares import ( "bluebell/controller" "bluebell/pkg/jwt" "strings" "github.com/gin-gonic/gin" ) // JWTAuthMiddleware 基于JWT的认证中间件 func JWTAuthMiddleware() func(c *gin.Context) { return func(c *gin.Context) { // 客户端携带Token有三种方式 1.放在请求头 2.放在请求体 3.放在URI // 这里假设Token放在Header的Authorization中,并使用Bearer开头 // Authorization: Bearer xxxxxxx.xxx.xxx / X-TOKEN: xxx.xxx.xx // 这里的具体实现方式要依据你的实际业务情况决定 authHeader := c.Request.Header.Get("Authorization") if authHeader == "" { controller.ResponseError(c, controller.CodeNeedLogin) c.Abort() return } // 按空格分割 parts := strings.SplitN(authHeader, " ", 2) if !(len(parts) == 2 && parts[0] == "Bearer") { controller.ResponseError(c, controller.CodeInvalidToken) c.Abort() return } // parts[1]是获取到的tokenString,我们使用之前定义好的解析JWT的函数来解析它 mc, err := jwt.ParseToken(parts[1]) if err != nil { controller.ResponseError(c, controller.CodeInvalidToken) c.Abort() return } // 将当前请求的userID信息保存到请求的上下文c上 c.Set(controller.CtxUserIDKey, mc.UserID) c.Next() // 后续的处理请求的函数中 可以用过c.Get(CtxUserIDKey) 来获取当前请求的用户信息 } } ================================================ FILE: lesson35/bluebell/models/community.go ================================================ package models import "time" type Community struct { ID int64 `json:"id" db:"community_id"` Name string `json:"name" db:"community_name"` } type CommunityDetail struct { ID int64 `json:"id" db:"community_id"` Name string `json:"name" db:"community_name"` Introduction string `json:"introduction,omitempty" db:"introduction"` CreateTime time.Time `json:"create_time" db:"create_time"` } ================================================ FILE: lesson35/bluebell/models/create_table.sql ================================================ -- CREATE TABLE `user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `user_id` bigint(20) NOT NULL, `username` varchar(64) COLLATE utf8mb4_general_ci NOT NULL, `password` varchar(64) COLLATE utf8mb4_general_ci NOT NULL, `email` varchar(64) COLLATE utf8mb4_general_ci, `gender` tinyint(4) NOT NULL DEFAULT '0', `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP, `update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `idx_username` (`username`) USING BTREE, UNIQUE KEY `idx_user_id` (`user_id`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; DROP TABLE IF EXISTS `community`; CREATE TABLE `community` ( `id` int(11) NOT NULL AUTO_INCREMENT, `community_id` int(10) unsigned NOT NULL, `community_name` varchar(128) COLLATE utf8mb4_general_ci NOT NULL, `introduction` varchar(256) COLLATE utf8mb4_general_ci NOT NULL, `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `idx_community_id` (`community_id`), UNIQUE KEY `idx_community_name` (`community_name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; INSERT INTO `community` VALUES ('1', '1', 'Go', 'Golang', '2016-11-01 08:10:10', '2016-11-01 08:10:10'); INSERT INTO `community` VALUES ('2', '2', 'leetcode', '刷题刷题刷题', '2020-01-01 08:00:00', '2020-01-01 08:00:00'); INSERT INTO `community` VALUES ('3', '3', 'CS:GO', 'Rush B。。。', '2018-08-07 08:30:00', '2018-08-07 08:30:00'); INSERT INTO `community` VALUES ('4', '4', 'LOL', '欢迎来到英雄联盟!', '2016-01-01 08:00:00', '2016-01-01 08:00:00'); DROP TABLE IF EXISTS `post`; CREATE TABLE `post` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `post_id` bigint(20) NOT NULL COMMENT '帖子id', `title` varchar(128) COLLATE utf8mb4_general_ci NOT NULL COMMENT '标题', `content` varchar(8192) COLLATE utf8mb4_general_ci NOT NULL COMMENT '内容', `author_id` bigint(20) NOT NULL COMMENT '作者的用户id', `community_id` bigint(20) NOT NULL COMMENT '所属社区', `status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '帖子状态', `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), UNIQUE KEY `idx_post_id` (`post_id`), KEY `idx_author_id` (`author_id`), KEY `idx_community_id` (`community_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; ================================================ FILE: lesson35/bluebell/models/params.go ================================================ package models // 定义请求的参数结构体 // ParamSignUp 注册请求参数 type ParamSignUp struct { Username string `json:"username" binding:"required"` Password string `json:"password" binding:"required"` RePassword string `json:"re_password" binding:"required,eqfield=Password"` } // ParamLogin 登录请求参数 type ParamLogin struct { Username string `json:"username" binding:"required"` Password string `json:"password" binding:"required"` } // ParamVoteData 投票数据 type ParamVoteData struct { // UserID 从请求中获取当前的用户 PostID string `json:"post_id" binding:"required"` // 贴子id Direction int8 `json:"direction,string" binding:"oneof=1 0 -1" ` // 赞成票(1)还是反对票(-1)取消投票(0) } ================================================ FILE: lesson35/bluebell/models/post.go ================================================ package models import "time" // 内存对齐概念 type Post struct { ID int64 `json:"id,string" db:"post_id"` AuthorID int64 `json:"author_id" db:"author_id"` CommunityID int64 `json:"community_id" db:"community_id" binding:"required"` Status int32 `json:"status" db:"status"` Title string `json:"title" db:"title" binding:"required"` Content string `json:"content" db:"content" binding:"required"` CreateTime time.Time `json:"create_time" db:"create_time"` } // ApiPostDetail 帖子详情接口的结构体 type ApiPostDetail struct { AuthorName string `json:"author_name"` *Post // 嵌入帖子结构体 *CommunityDetail `json:"community"` // 嵌入社区信息 } ================================================ FILE: lesson35/bluebell/models/struct_test.go ================================================ package models import ( "fmt" "testing" "unsafe" ) // Go 内存对齐 type s1 struct { a int8 // 1 b string // 3 c int8 // 1 } type s2 struct { a int8 c int8 // 1 b string // 2 } func TestStruct(t *testing.T) { v1 := s1{ a: 1, b: "七米", c: 2, } v2 := s2{ a: 1, b: "七米", c: 2, } fmt.Println(unsafe.Sizeof(v1), unsafe.Sizeof(v2)) } ================================================ FILE: lesson35/bluebell/models/user.go ================================================ package models type User struct { UserID int64 `db:"user_id"` Username string `db:"username"` Password string `db:"password"` Token string } ================================================ FILE: lesson35/bluebell/pkg/jwt/jwt.go ================================================ package jwt import ( "errors" "time" "github.com/spf13/viper" "github.com/dgrijalva/jwt-go" ) var mySecret = []byte("夏天夏天悄悄过去") // MyClaims 自定义声明结构体并内嵌jwt.StandardClaims // jwt包自带的jwt.StandardClaims只包含了官方字段 // 我们这里需要额外记录一个username字段,所以要自定义结构体 // 如果想要保存更多信息,都可以添加到这个结构体中 type MyClaims struct { UserID int64 `json:"user_id"` Username string `json:"username"` jwt.StandardClaims } // GenToken 生成JWT func GenToken(userID int64, username string) (string, error) { // 创建一个我们自己的声明的数据 c := MyClaims{ userID, "username", // 自定义字段 jwt.StandardClaims{ ExpiresAt: time.Now().Add( time.Duration(viper.GetInt("auth.jwt_expire")) * time.Hour).Unix(), // 过期时间 Issuer: "bluebell", // 签发人 }, } // 使用指定的签名方法创建签名对象 token := jwt.NewWithClaims(jwt.SigningMethodHS256, c) // 使用指定的secret签名并获得完整的编码后的字符串token return token.SignedString(mySecret) } // ParseToken 解析JWT func ParseToken(tokenString string) (*MyClaims, error) { // 解析token var mc = new(MyClaims) token, err := jwt.ParseWithClaims(tokenString, mc, func(token *jwt.Token) (i interface{}, err error) { return mySecret, nil }) if err != nil { return nil, err } if token.Valid { // 校验token return mc, nil } return nil, errors.New("invalid token") } ================================================ FILE: lesson35/bluebell/pkg/snowflake/snowflake.go ================================================ package snowflake import ( "time" sf "github.com/bwmarrin/snowflake" ) var node *sf.Node func Init(startTime string, machineID int64) (err error) { var st time.Time st, err = time.Parse("2006-01-02", startTime) if err != nil { return } sf.Epoch = st.UnixNano() / 1000000 node, err = sf.NewNode(machineID) return } func GenID() int64 { return node.Generate().Int64() } ================================================ FILE: lesson35/bluebell/router/route.go ================================================ package router import ( "bluebell/controller" "bluebell/logger" "bluebell/middlewares" "net/http" "github.com/gin-gonic/gin" ) func SetupRouter(mode string) *gin.Engine { if mode == gin.ReleaseMode { gin.SetMode(gin.ReleaseMode) // gin设置成发布模式 } r := gin.New() r.Use(logger.GinLogger(), logger.GinRecovery(true)) v1 := r.Group("/api/v1") // 注册 v1.POST("/signup", controller.SignUpHandler) // 登录 v1.POST("/login", controller.LoginHandler) v1.Use(middlewares.JWTAuthMiddleware()) // 应用JWT认证中间件 { v1.GET("/community", controller.CommunityHandler) v1.GET("/community/:id", controller.CommunityDetailHandler) v1.POST("/post", controller.CreatePostHandler) v1.GET("/post/:id", controller.GetPostDetailHandler) v1.GET("/posts/", controller.GetPostListHandler) // 投票 v1.POST("/vote", controller.PostVoteController) } r.NoRoute(func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "msg": "404", }) }) return r } ================================================ FILE: lesson35/bluebell/setting/setting.go ================================================ package setting import ( "fmt" "github.com/fsnotify/fsnotify" "github.com/spf13/viper" ) var Conf = new(AppConfig) type AppConfig struct { Name string `mapstructure:"name"` Mode string `mapstructure:"mode"` Version string `mapstructure:"version"` StartTime string `mapstructure:"start_time"` MachineID int64 `mapstructure:"machine_id"` Port int `mapstructure:"port"` *LogConfig `mapstructure:"log"` *MySQLConfig `mapstructure:"mysql"` *RedisConfig `mapstructure:"redis"` } type MySQLConfig struct { Host string `mapstructure:"host"` User string `mapstructure:"user"` Password string `mapstructure:"password"` DB string `mapstructure:"dbname"` Port int `mapstructure:"port"` MaxOpenConns int `mapstructure:"max_open_conns"` MaxIdleConns int `mapstructure:"max_idle_conns"` } type RedisConfig struct { Host string `mapstructure:"host"` Password string `mapstructure:"password"` Port int `mapstructure:"port"` DB int `mapstructure:"db"` PoolSize int `mapstructure:"pool_size"` MinIdleConns int `mapstructure:"min_idle_conns"` } type LogConfig struct { Level string `mapstructure:"level"` Filename string `mapstructure:"filename"` MaxSize int `mapstructure:"max_size"` MaxAge int `mapstructure:"max_age"` MaxBackups int `mapstructure:"max_backups"` } func Init(filePath string) (err error) { // 方式1:直接指定配置文件路径(相对路径或者绝对路径) // 相对路径:相对执行的可执行文件的相对路径 //viper.SetConfigFile("./conf/config.yaml") // 绝对路径:系统中实际的文件路径 //viper.SetConfigFile("/Users/liwenzhou/Desktop/bluebell/conf/config.yaml") // 方式2:指定配置文件名和配置文件的位置,viper自行查找可用的配置文件 // 配置文件名不需要带后缀 // 配置文件位置可配置多个 //viper.SetConfigName("config") // 指定配置文件名(不带后缀) //viper.AddConfigPath(".") // 指定查找配置文件的路径(这里使用相对路径) //viper.AddConfigPath("./conf") // 指定查找配置文件的路径(这里使用相对路径) // 基本上是配合远程配置中心使用的,告诉viper当前的数据使用什么格式去解析 //viper.SetConfigType("json") viper.SetConfigFile(filePath) err = viper.ReadInConfig() // 读取配置信息 if err != nil { // 读取配置信息失败 fmt.Printf("viper.ReadInConfig failed, err:%v\n", err) return } // 把读取到的配置信息反序列化到 Conf 变量中 if err := viper.Unmarshal(Conf); err != nil { fmt.Printf("viper.Unmarshal failed, err:%v\n", err) } viper.WatchConfig() viper.OnConfigChange(func(in fsnotify.Event) { fmt.Println("配置文件修改了...") if err := viper.Unmarshal(Conf); err != nil { fmt.Printf("viper.Unmarshal failed, err:%v\n", err) } }) return } ================================================ FILE: lesson68/bluebell/.air.conf ================================================ # [Air](https://github.com/cosmtrek/air) TOML 格式的配置文件 # 工作目录 # 使用 . 或绝对路径,请注意 `tmp_dir` 目录必须在 `root` 目录下 root = "." tmp_dir = "tmp" [build] # 只需要写你平常编译使用的shell命令。你也可以使用 `make` cmd = "go build -o ./tmp/main" # 由`cmd`命令得到的二进制文件名 bin = "tmp/main" # 自定义的二进制,可以添加额外的编译标识例如添加 GIN_MODE=release full_bin = "./tmp/main ./conf/config.yaml" # 监听以下文件扩展名的文件. include_ext = ["go", "tpl", "tmpl", "html", "yaml"] # 忽略这些文件扩展名或目录 exclude_dir = ["assets", "tmp", "vendor", "frontend/node_modules"] # 监听以下指定目录的文件 include_dir = [] # 排除以下文件 exclude_file = [] # 如果文件更改过于频繁,则没有必要在每次更改时都触发构建。可以设置触发构建的延迟时间 delay = 1000 # ms # 发生构建错误时,停止运行旧的二进制文件。 stop_on_error = true # air的日志文件名,该日志文件放置在你的`tmp_dir`中 log = "air_errors.log" [log] # 显示日志时间 time = true [color] # 自定义每个部分显示的颜色。如果找不到颜色,使用原始的应用程序日志。 main = "magenta" watcher = "cyan" build = "yellow" runner = "green" [misc] # 退出时删除tmp目录 clean_on_exit = true ================================================ FILE: lesson68/bluebell/Makefile ================================================ .PHONY: all build run gotool clean help BINARY="xx" all: gotool build build: CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o ${BINARY} run: @go run ./main.go conf/config.yaml gotool: go fmt ./ go vet ./ clean: @if [ -f ${BINARY} ] ; then rm ${BINARY} ; fi help: @echo "make - 格式化 Go 代码, 并编译生成二进制文件" @echo "make build - 编译 Go 代码, 生成二进制文件" @echo "make run - 直接运行 Go 代码" @echo "make clean - 移除二进制文件和 vim swap files" @echo "make gotool - 运行 Go 工具 'fmt' and 'vet'" ================================================ FILE: lesson68/bluebell/bluebell ================================================ [File too large to display: 20.6 MB] ================================================ FILE: lesson68/bluebell/conf/config.yaml ================================================ name: "bluebell" mode: "dev" port: 8084 version: "v0.0.1" start_time: "2020-07-01" machine_id: 1 auth: jwt_expire: 8760 log: level: "debug" filename: "web_app.log" max_size: 200 max_age: 30 max_backups: 7 mysql: host: "127.0.0.1" port: 13306 user: "root" password: "root1234" dbname: "bluebell" max_open_conns: 200 max_idle_conns: 50 redis: host: "127.0.0.1" port: 16379 password: "" db: 0 pool_size: 100 ================================================ FILE: lesson68/bluebell/controller/code.go ================================================ package controller type ResCode int64 const ( CodeSuccess ResCode = 1000 + iota CodeInvalidParam CodeUserExist CodeUserNotExist CodeInvalidPassword CodeServerBusy CodeNeedLogin CodeInvalidToken ) var codeMsgMap = map[ResCode]string{ CodeSuccess: "success", CodeInvalidParam: "请求参数错误", CodeUserExist: "用户名已存在", CodeUserNotExist: "用户名不存在", CodeInvalidPassword: "用户名或密码错误", CodeServerBusy: "服务繁忙", CodeNeedLogin: "需要登录", CodeInvalidToken: "无效的token", } func (c ResCode) Msg() string { msg, ok := codeMsgMap[c] if !ok { msg = codeMsgMap[CodeServerBusy] } return msg } ================================================ FILE: lesson68/bluebell/controller/community.go ================================================ package controller import ( "bluebell/logic" "strconv" "github.com/gin-gonic/gin" "go.uber.org/zap" ) // ---- 跟社区相关的 ---- func CommunityHandler(c *gin.Context) { // 查询到所有的社区(community_id, community_name) 以列表的形式返回 data, err := logic.GetCommunityList() if err != nil { zap.L().Error("logic.GetCommunityList() failed", zap.Error(err)) ResponseError(c, CodeServerBusy) // 不轻易把服务端报错暴露给外面 return } ResponseSuccess(c, data) } // CommunityDetailHandler 社区分类详情 func CommunityDetailHandler(c *gin.Context) { // 1. 获取社区id idStr := c.Param("id") // 获取URL参数 id, err := strconv.ParseInt(idStr, 10, 64) if err != nil { ResponseError(c, CodeInvalidParam) return } // 2. 根据id获取社区详情 data, err := logic.GetCommunityDetail(id) if err != nil { zap.L().Error("logic.GetCommunityList() failed", zap.Error(err)) ResponseError(c, CodeServerBusy) // 不轻易把服务端报错暴露给外面 return } ResponseSuccess(c, data) } ================================================ FILE: lesson68/bluebell/controller/post.go ================================================ package controller import ( "bluebell/logic" "bluebell/models" "strconv" "github.com/gin-gonic/gin" "go.uber.org/zap" ) // CreatePostHandler 创建帖子的处理函数 func CreatePostHandler(c *gin.Context) { // 1. 获取参数及参数的校验 //c.ShouldBindJSON() // validator --> binding tag p := new(models.Post) if err := c.ShouldBindJSON(p); err != nil { zap.L().Debug("c.ShouldBindJSON(p) error", zap.Any("err", err)) zap.L().Error("create post with invalid param") ResponseError(c, CodeInvalidParam) return } // 从 c 取到当前发请求的用户的ID userID, err := getCurrentUserID(c) if err != nil { ResponseError(c, CodeNeedLogin) return } p.AuthorID = userID // 2. 创建帖子 if err := logic.CreatePost(p); err != nil { zap.L().Error("logic.CreatePost(p) failed", zap.Error(err)) ResponseError(c, CodeServerBusy) return } // 3. 返回响应 ResponseSuccess(c, nil) } // GetPostDetailHandler 获取帖子详情的处理函数 func GetPostDetailHandler(c *gin.Context) { // 1. 获取参数(从URL中获取帖子的id) pidStr := c.Param("id") pid, err := strconv.ParseInt(pidStr, 10, 64) if err != nil { zap.L().Error("get post detail with invalid param", zap.Error(err)) ResponseError(c, CodeInvalidParam) return } // 2. 根据id取出帖子数据(查数据库) data, err := logic.GetPostById(pid) if err != nil { zap.L().Error("logic.GetPostById(pid) failed", zap.Error(err)) ResponseError(c, CodeServerBusy) return } // 3. 返回响应 ResponseSuccess(c, data) } // GetPostListHandler 获取帖子列表的处理函数 func GetPostListHandler(c *gin.Context) { // 获取分页参数 page, size := getPageInfo(c) // 获取数据 data, err := logic.GetPostList(page, size) if err != nil { zap.L().Error("logic.GetPostList() failed", zap.Error(err)) ResponseError(c, CodeServerBusy) return } ResponseSuccess(c, data) // 返回响应 } // GetPostListHandler2 升级版帖子列表接口 // 根据前端传来的参数动态的获取帖子列表 // 按创建时间排序 或者 按照 分数排序 // 1. 获取请求的query string参数 // 2. 去redis查询id列表 // 3. 根据id去数据库查询帖子详细信息 func GetPostListHandler2(c *gin.Context) { // GET请求参数(query string):/api/v1/posts2?page=1&size=10&order=time // 初始化结构体时指定初始参数 p := &models.ParamPostList{ Page: 1, Size: 10, Order: models.OrderTime, // magic string } //c.ShouldBind() 根据请求的数据类型选择相应的方法去获取数据 //c.ShouldBindJSON() 如果请求中携带的是json格式的数据,才能用这个方法获取到数据 if err := c.ShouldBindQuery(p); err != nil { zap.L().Error("GetPostListHandler2 with invalid params", zap.Error(err)) ResponseError(c, CodeInvalidParam) return } data, err := logic.GetPostListNew(p) // 更新:合二为一 // 获取数据 if err != nil { zap.L().Error("logic.GetPostList() failed", zap.Error(err)) ResponseError(c, CodeServerBusy) return } ResponseSuccess(c, data) // 返回响应 } // 根据社区去查询帖子列表 //func GetCommunityPostListHandler(c *gin.Context) { // // 初始化结构体时指定初始参数 // p := &models.ParamCommunityPostList{ // ParamPostList: &models.ParamPostList{ // Page: 1, // Size: 10, // Order: models.OrderTime, // }, // } // //c.ShouldBind() 根据请求的数据类型选择相应的方法去获取数据 // //c.ShouldBindJSON() 如果请求中携带的是json格式的数据,才能用这个方法获取到数据 // if err := c.ShouldBindQuery(p); err != nil { // zap.L().Error("GetCommunityPostListHandler with invalid params", zap.Error(err)) // ResponseError(c, CodeInvalidParam) // return // } // // // 获取数据 // data, err := logic.GetCommunityPostList(p) // if err != nil { // zap.L().Error("logic.GetPostList() failed", zap.Error(err)) // ResponseError(c, CodeServerBusy) // return // } // ResponseSuccess(c, data) //} ================================================ FILE: lesson68/bluebell/controller/request.go ================================================ package controller import ( "errors" "strconv" "github.com/gin-gonic/gin" ) const CtxUserIDKey = "userID" var ErrorUserNotLogin = errors.New("用户未登录") // getCurrentUserID 获取当前登录的用户ID func getCurrentUserID(c *gin.Context) (userID int64, err error) { uid, ok := c.Get(CtxUserIDKey) if !ok { err = ErrorUserNotLogin return } userID, ok = uid.(int64) if !ok { err = ErrorUserNotLogin return } return } func getPageInfo(c *gin.Context) (int64, int64) { pageStr := c.Query("page") sizeStr := c.Query("size") var ( page int64 size int64 err error ) page, err = strconv.ParseInt(pageStr, 10, 64) if err != nil { page = 1 } size, err = strconv.ParseInt(sizeStr, 10, 64) if err != nil { size = 10 } return page, size } ================================================ FILE: lesson68/bluebell/controller/response.go ================================================ package controller import ( "net/http" "github.com/gin-gonic/gin" ) /* { "code": 10000, // 程序中的错误码 "msg": xx, // 提示信息 "data": {}, // 数据 } */ type ResponseData struct { Code ResCode `json:"code"` Msg interface{} `json:"msg"` Data interface{} `json:"data,omitempty"` } func ResponseError(c *gin.Context, code ResCode) { c.JSON(http.StatusOK, &ResponseData{ Code: code, Msg: code.Msg(), Data: nil, }) } func ResponseErrorWithMsg(c *gin.Context, code ResCode, msg interface{}) { c.JSON(http.StatusOK, &ResponseData{ Code: code, Msg: msg, Data: nil, }) } func ResponseSuccess(c *gin.Context, data interface{}) { c.JSON(http.StatusOK, &ResponseData{ Code: CodeSuccess, Msg: CodeSuccess.Msg(), Data: data, }) } ================================================ FILE: lesson68/bluebell/controller/user.go ================================================ package controller import ( "bluebell/dao/mysql" "bluebell/logic" "bluebell/models" "errors" "fmt" "github.com/go-playground/validator/v10" "go.uber.org/zap" "github.com/gin-gonic/gin" ) // SignUpHandler 处理注册请求的函数 func SignUpHandler(c *gin.Context) { // 1. 获取参数和参数校验 p := new(models.ParamSignUp) if err := c.ShouldBindJSON(p); err != nil { // 请求参数有误,直接返回响应 zap.L().Error("SignUp with invalid param", zap.Error(err)) // 判断err是不是validator.ValidationErrors 类型 errs, ok := err.(validator.ValidationErrors) if !ok { ResponseError(c, CodeInvalidParam) return } ResponseErrorWithMsg(c, CodeInvalidParam, removeTopStruct(errs.Translate(trans))) return } // 2. 业务处理 if err := logic.SignUp(p); err != nil { zap.L().Error("logic.SignUp failed", zap.Error(err)) if errors.Is(err, mysql.ErrorUserExist) { ResponseError(c, CodeUserExist) return } ResponseError(c, CodeServerBusy) return } // 3. 返回响应 ResponseSuccess(c, nil) } func LoginHandler(c *gin.Context) { // 1.获取请求参数及参数校验 p := new(models.ParamLogin) if err := c.ShouldBindJSON(p); err != nil { // 请求参数有误,直接返回响应 zap.L().Error("Login with invalid param", zap.Error(err)) // 判断err是不是validator.ValidationErrors 类型 errs, ok := err.(validator.ValidationErrors) if !ok { ResponseError(c, CodeInvalidParam) return } ResponseErrorWithMsg(c, CodeInvalidParam, removeTopStruct(errs.Translate(trans))) return } // 2.业务逻辑处理 user, err := logic.Login(p) if err != nil { zap.L().Error("logic.Login failed", zap.String("username", p.Username), zap.Error(err)) if errors.Is(err, mysql.ErrorUserNotExist) { ResponseError(c, CodeUserNotExist) return } ResponseError(c, CodeInvalidPassword) return } // 3.返回响应 ResponseSuccess(c, gin.H{ "user_id": fmt.Sprintf("%d", user.UserID), // id值大于1<<53-1 int64类型的最大值是1<<63-1 "user_name": user.Username, "token": user.Token, }) } ================================================ FILE: lesson68/bluebell/controller/validator.go ================================================ package controller import ( "bluebell/models" "fmt" "reflect" "strings" "github.com/gin-gonic/gin/binding" "github.com/go-playground/locales/en" "github.com/go-playground/locales/zh" ut "github.com/go-playground/universal-translator" "github.com/go-playground/validator/v10" enTranslations "github.com/go-playground/validator/v10/translations/en" zhTranslations "github.com/go-playground/validator/v10/translations/zh" ) // 定义一个全局翻译器T var trans ut.Translator // InitTrans 初始化翻译器 func InitTrans(locale string) (err error) { // 修改gin框架中的Validator引擎属性,实现自定制 if v, ok := binding.Validator.Engine().(*validator.Validate); ok { // 注册一个获取json tag的自定义方法 v.RegisterTagNameFunc(func(fld reflect.StructField) string { name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0] if name == "-" { return "" } return name }) // 为SignUpParam注册自定义校验方法 v.RegisterStructValidation(SignUpParamStructLevelValidation, models.ParamSignUp{}) zhT := zh.New() // 中文翻译器 enT := en.New() // 英文翻译器 // 第一个参数是备用(fallback)的语言环境 // 后面的参数是应该支持的语言环境(支持多个) // uni := ut.New(zhT, zhT) 也是可以的 uni := ut.New(enT, zhT, enT) // locale 通常取决于 http 请求头的 'Accept-Language' var ok bool // 也可以使用 uni.FindTranslator(...) 传入多个locale进行查找 trans, ok = uni.GetTranslator(locale) if !ok { return fmt.Errorf("uni.GetTranslator(%s) failed", locale) } // 注册翻译器 switch locale { case "en": err = enTranslations.RegisterDefaultTranslations(v, trans) case "zh": err = zhTranslations.RegisterDefaultTranslations(v, trans) default: err = enTranslations.RegisterDefaultTranslations(v, trans) } return } return } // removeTopStruct 去除提示信息中的结构体名称 func removeTopStruct(fields map[string]string) map[string]string { res := map[string]string{} for field, err := range fields { res[field[strings.Index(field, ".")+1:]] = err } return res } // SignUpParamStructLevelValidation 自定义SignUpParam结构体校验函数 func SignUpParamStructLevelValidation(sl validator.StructLevel) { su := sl.Current().Interface().(models.ParamSignUp) if su.Password != su.RePassword { // 输出错误提示信息,最后一个参数就是传递的param sl.ReportError(su.RePassword, "re_password", "RePassword", "eqfield", "password") } } ================================================ FILE: lesson68/bluebell/controller/vote.go ================================================ package controller import ( "bluebell/logic" "bluebell/models" "go.uber.org/zap" "github.com/go-playground/validator/v10" "github.com/gin-gonic/gin" ) // 投票 //type VoteData struct { // // UserID 从请求中获取当前的用户 // PostID int64 `json:"post_id,string"` // 贴子id // Direction int `json:"direction,string"` // 赞成票(1)还是反对票(-1) //} func PostVoteController(c *gin.Context) { // 参数校验 p := new(models.ParamVoteData) if err := c.ShouldBindJSON(p); err != nil { errs, ok := err.(validator.ValidationErrors) // 类型断言 if !ok { ResponseError(c, CodeInvalidParam) return } errData := removeTopStruct(errs.Translate(trans)) // 翻译并去除掉错误提示中的结构体标识 ResponseErrorWithMsg(c, CodeInvalidParam, errData) return } // 获取当前请求的用户的id userID, err := getCurrentUserID(c) if err != nil { ResponseError(c, CodeNeedLogin) return } // 具体投票的业务逻辑 if err := logic.VoteForPost(userID, p); err != nil { zap.L().Error("logic.VoteForPost() failed", zap.Error(err)) ResponseError(c, CodeServerBusy) return } ResponseSuccess(c, nil) } ================================================ FILE: lesson68/bluebell/dao/mysql/community.go ================================================ package mysql import ( "bluebell/models" "database/sql" "go.uber.org/zap" ) func GetCommunityList() (communityList []*models.Community, err error) { sqlStr := "select community_id, community_name from community" if err := db.Select(&communityList, sqlStr); err != nil { if err == sql.ErrNoRows { zap.L().Warn("there is no community in db") err = nil } } return } // GetCommunityDetailByID 根据ID查询社区详情 func GetCommunityDetailByID(id int64) (community *models.CommunityDetail, err error) { community = new(models.CommunityDetail) sqlStr := `select community_id, community_name, introduction, create_time from community where community_id = ? ` if err := db.Get(community, sqlStr, id); err != nil { if err == sql.ErrNoRows { err = ErrorInvalidID } } return community, err } ================================================ FILE: lesson68/bluebell/dao/mysql/error_code.go ================================================ package mysql import "errors" var ( ErrorUserExist = errors.New("用户已存在") ErrorUserNotExist = errors.New("用户不存在") ErrorInvalidPassword = errors.New("用户名或密码错误") ErrorInvalidID = errors.New("无效的ID") ) ================================================ FILE: lesson68/bluebell/dao/mysql/mysql.go ================================================ package mysql import ( "bluebell/setting" "fmt" _ "github.com/go-sql-driver/mysql" "github.com/jmoiron/sqlx" ) var db *sqlx.DB // Init 初始化MySQL连接 func Init(cfg *setting.MySQLConfig) (err error) { // "user:password@tcp(host:port)/dbname" dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?parseTime=true&loc=Local", cfg.User, cfg.Password, cfg.Host, cfg.Port, cfg.DB) db, err = sqlx.Connect("mysql", dsn) if err != nil { return } db.SetMaxOpenConns(cfg.MaxOpenConns) db.SetMaxIdleConns(cfg.MaxIdleConns) return } // Close 关闭MySQL连接 func Close() { _ = db.Close() } ================================================ FILE: lesson68/bluebell/dao/mysql/post.go ================================================ package mysql import ( "bluebell/models" "strings" "github.com/jmoiron/sqlx" ) // CreatePost 创建帖子 func CreatePost(p *models.Post) (err error) { sqlStr := `insert into post( post_id, title, content, author_id, community_id) values (?, ?, ?, ?, ?) ` _, err = db.Exec(sqlStr, p.ID, p.Title, p.Content, p.AuthorID, p.CommunityID) return } // GetPostById 根据id查询单个贴子数据 func GetPostById(pid int64) (post *models.Post, err error) { post = new(models.Post) sqlStr := `select post_id, title, content, author_id, community_id, create_time from post where post_id = ? ` err = db.Get(post, sqlStr, pid) return } // GetPostList 查询帖子列表函数 func GetPostList(page, size int64) (posts []*models.Post, err error) { sqlStr := `select post_id, title, content, author_id, community_id, create_time from post ORDER BY create_time DESC limit ?,? ` posts = make([]*models.Post, 0, 2) // 不要写成make([]*models.Post, 2) err = db.Select(&posts, sqlStr, (page-1)*size, size) return } // GetPostListByIDs 根据给定的id列表查询帖子数据 func GetPostListByIDs(ids []string) (postList []*models.Post, err error) { sqlStr := `select post_id, title, content, author_id, community_id, create_time from post where post_id in (?) order by FIND_IN_SET(post_id, ?) ` // https: //www.liwenzhou.com/posts/Go/sqlx/ query, args, err := sqlx.In(sqlStr, ids, strings.Join(ids, ",")) if err != nil { return nil, err } query = db.Rebind(query) err = db.Select(&postList, query, args...) // !!!!!! return } ================================================ FILE: lesson68/bluebell/dao/mysql/user.go ================================================ package mysql import ( "bluebell/models" "crypto/md5" "database/sql" "encoding/hex" ) // 把每一步数据库操作封装成函数 // 待logic层根据业务需求调用 const secret = "liwenzhou.com" // CheckUserExist 检查指定用户名的用户是否存在 func CheckUserExist(username string) (err error) { sqlStr := `select count(user_id) from user where username = ?` var count int64 if err := db.Get(&count, sqlStr, username); err != nil { return err } if count > 0 { return ErrorUserExist } return } // InsertUser 想数据库中插入一条新的用户记录 func InsertUser(user *models.User) (err error) { // 对密码进行加密 user.Password = encryptPassword(user.Password) // 执行SQL语句入库 sqlStr := `insert into user(user_id, username, password) values(?,?,?)` _, err = db.Exec(sqlStr, user.UserID, user.Username, user.Password) return } // encryptPassword 密码加密 func encryptPassword(oPassword string) string { h := md5.New() h.Write([]byte(secret)) return hex.EncodeToString(h.Sum([]byte(oPassword))) } func Login(user *models.User) (err error) { oPassword := user.Password // 用户登录的密码 sqlStr := `select user_id, username, password from user where username=?` err = db.Get(user, sqlStr, user.Username) if err == sql.ErrNoRows { return ErrorUserNotExist } if err != nil { // 查询数据库失败 return err } // 判断密码是否正确 password := encryptPassword(oPassword) if password != user.Password { return ErrorInvalidPassword } return } // GetUserById 根据id获取用户信息 func GetUserById(uid int64) (user *models.User, err error) { user = new(models.User) sqlStr := `select user_id, username from user where user_id = ?` err = db.Get(user, sqlStr, uid) return } ================================================ FILE: lesson68/bluebell/dao/redis/keys.go ================================================ package redis // redis key // redis key注意使用命名空间的方式,方便查询和拆分 const ( Prefix = "bluebell:" // 项目key前缀 KeyPostTimeZSet = "post:time" // zset;贴子及发帖时间 KeyPostScoreZSet = "post:score" // zset;贴子及投票的分数 KeyPostVotedZSetPF = "post:voted:" // zset;记录用户及投票类型;参数是post id KeyCommunitySetPF = "community:" // set;保存每个分区下帖子的id ) // 给redis key加上前缀 func getRedisKey(key string) string { return Prefix + key } ================================================ FILE: lesson68/bluebell/dao/redis/post.go ================================================ package redis import ( "bluebell/models" "strconv" "time" "github.com/go-redis/redis" ) func getIDsFormKey(key string, page, size int64) ([]string, error) { start := (page - 1) * size end := start + size - 1 // 3. ZREVRANGE 按分数从大到小的顺序查询指定数量的元素 return client.ZRevRange(key, start, end).Result() } func GetPostIDsInOrder(p *models.ParamPostList) ([]string, error) { // 从redis获取id // 1. 根据用户请求中携带的order参数确定要查询的redis key key := getRedisKey(KeyPostTimeZSet) if p.Order == models.OrderScore { key = getRedisKey(KeyPostScoreZSet) } // 2. 确定查询的索引起始点 return getIDsFormKey(key, p.Page, p.Size) } // GetPostVoteData 根据ids查询每篇帖子的投赞成票的数据 func GetPostVoteData(ids []string) (data []int64, err error) { //data = make([]int64, 0, len(ids)) //for _, id := range ids { // key := getRedisKey(KeyPostVotedZSetPF + id) // // 查找key中分数是1的元素的数量->统计每篇帖子的赞成票的数量 // v := client.ZCount(key, "1", "1").Val() // data = append(data, v) //} // 使用pipeline一次发送多条命令,减少RTT pipeline := client.Pipeline() for _, id := range ids { key := getRedisKey(KeyPostVotedZSetPF + id) pipeline.ZCount(key, "1", "1") } cmders, err := pipeline.Exec() if err != nil { return nil, err } data = make([]int64, 0, len(cmders)) for _, cmder := range cmders { v := cmder.(*redis.IntCmd).Val() data = append(data, v) } return } // GetCommunityPostIDsInOrder 按社区查询ids func GetCommunityPostIDsInOrder(p *models.ParamCommunityPostList) ([]string, error) { orderKey := getRedisKey(KeyPostTimeZSet) if p.Order == models.OrderScore { orderKey = getRedisKey(KeyPostScoreZSet) } // 使用 zinterstore 把分区的帖子set与帖子分数的 zset 生成一个新的zset // 针对新的zset 按之前的逻辑取数据 // 社区的key cKey := getRedisKey(KeyCommunitySetPF + strconv.Itoa(int(p.CommunityID))) // 利用缓存key减少zinterstore执行的次数 key := orderKey + strconv.Itoa(int(p.CommunityID)) if client.Exists(key).Val() < 1 { // 不存在,需要计算 pipeline := client.Pipeline() pipeline.ZInterStore(key, redis.ZStore{ Aggregate: "MAX", }, cKey, orderKey) // zinterstore 计算 pipeline.Expire(key, 60*time.Second) // 设置超时时间 _, err := pipeline.Exec() if err != nil { return nil, err } } // 存在的话就直接根据key查询ids return getIDsFormKey(key, p.Page, p.Size) } ================================================ FILE: lesson68/bluebell/dao/redis/redis.go ================================================ package redis import ( "bluebell/setting" "fmt" "github.com/go-redis/redis" ) var ( client *redis.Client Nil = redis.Nil ) // Init 初始化连接 func Init(cfg *setting.RedisConfig) (err error) { client = redis.NewClient(&redis.Options{ Addr: fmt.Sprintf("%s:%d", cfg.Host, cfg.Port), Password: cfg.Password, // no password set DB: cfg.DB, // use default DB PoolSize: cfg.PoolSize, MinIdleConns: cfg.MinIdleConns, }) _, err = client.Ping().Result() if err != nil { return err } return nil } func Close() { _ = client.Close() } ================================================ FILE: lesson68/bluebell/dao/redis/vote.go ================================================ package redis import ( "errors" "math" "strconv" "time" "github.com/go-redis/redis" ) // 推荐阅读 // 基于用户投票的相关算法:http://www.ruanyifeng.com/blog/algorithm/ // 本项目使用简化版的投票分数 // 投一票就加432分 86400/200 --> 200张赞成票可以给你的帖子续一天 /* 投票的几种情况: direction=1时,有两种情况: 1. 之前没有投过票,现在投赞成票 --> 更新分数和投票记录 差值的绝对值:1 +432 2. 之前投反对票,现在改投赞成票 --> 更新分数和投票记录 差值的绝对值:2 +432*2 direction=0时,有两种情况: 1. 之前投过反对票,现在要取消投票 --> 更新分数和投票记录 差值的绝对值:1 +432 2. 之前投过赞成票,现在要取消投票 --> 更新分数和投票记录 差值的绝对值:1 -432 direction=-1时,有两种情况: 1. 之前没有投过票,现在投反对票 --> 更新分数和投票记录 差值的绝对值:1 -432 2. 之前投赞成票,现在改投反对票 --> 更新分数和投票记录 差值的绝对值:2 -432*2 投票的限制: 每个贴子自发表之日起一个星期之内允许用户投票,超过一个星期就不允许再投票了。 1. 到期之后将redis中保存的赞成票数及反对票数存储到mysql表中 2. 到期之后删除那个 KeyPostVotedZSetPF */ const ( oneWeekInSeconds = 7 * 24 * 3600 scorePerVote = 432 // 每一票值多少分 ) var ( ErrVoteTimeExpire = errors.New("投票时间已过") ErrVoteRepeated = errors.New("不允许重复投票") ) func CreatePost(postID, communityID int64) error { pipeline := client.TxPipeline() // 帖子时间 pipeline.ZAdd(getRedisKey(KeyPostTimeZSet), redis.Z{ Score: float64(time.Now().Unix()), Member: postID, }) // 帖子分数 pipeline.ZAdd(getRedisKey(KeyPostScoreZSet), redis.Z{ Score: float64(time.Now().Unix()), Member: postID, }) // 更新:把帖子id加到社区的set cKey := getRedisKey(KeyCommunitySetPF + strconv.Itoa(int(communityID))) pipeline.SAdd(cKey, postID) _, err := pipeline.Exec() return err } func VoteForPost(userID, postID string, value float64) error { // 1. 判断投票限制 // 去redis取帖子发布时间 postTime := client.ZScore(getRedisKey(KeyPostTimeZSet), postID).Val() if float64(time.Now().Unix())-postTime > oneWeekInSeconds { return ErrVoteTimeExpire } // 2和3需要放到一个pipeline事务中操作 // 2. 更新贴子的分数 // 先查当前用户给当前帖子的投票记录 ov := client.ZScore(getRedisKey(KeyPostVotedZSetPF+postID), userID).Val() // 更新:如果这一次投票的值和之前保存的值一致,就提示不允许重复投票 if value == ov { return ErrVoteRepeated } var op float64 if value > ov { op = 1 } else { op = -1 } diff := math.Abs(ov - value) // 计算两次投票的差值 pipeline := client.TxPipeline() pipeline.ZIncrBy(getRedisKey(KeyPostScoreZSet), op*diff*scorePerVote, postID) // 3. 记录用户为该贴子投票的数据 if value == 0 { pipeline.ZRem(getRedisKey(KeyPostVotedZSetPF+postID), userID) } else { pipeline.ZAdd(getRedisKey(KeyPostVotedZSetPF+postID), redis.Z{ Score: value, // 赞成票还是反对票 Member: userID, }) } _, err := pipeline.Exec() return err } ================================================ FILE: lesson68/bluebell/go.mod ================================================ module bluebell go 1.14 require ( github.com/bwmarrin/snowflake v0.3.0 github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/fsnotify/fsnotify v1.4.9 github.com/gin-gonic/gin v1.6.3 github.com/go-playground/locales v0.13.0 github.com/go-playground/universal-translator v0.17.0 github.com/go-playground/validator/v10 v10.2.0 github.com/go-redis/redis v6.15.8+incompatible github.com/go-sql-driver/mysql v1.5.0 github.com/jmoiron/sqlx v1.2.0 github.com/natefinch/lumberjack v2.0.0+incompatible github.com/onsi/ginkgo v1.14.0 // indirect github.com/sony/sonyflake v1.0.0 // indirect github.com/spf13/viper v1.7.0 go.uber.org/zap v1.15.0 gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect ) ================================================ FILE: lesson68/bluebell/go.sum ================================================ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0= github.com/bwmarrin/snowflake v0.3.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/E9WsDpxqwE= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/deckarep/golang-set v1.7.1/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14= github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY= github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= github.com/go-redis/redis v6.15.8+incompatible h1:BKZuG6mCnRj5AOaWJXoCgf6rqTYnYJLe4en2hxT7r9o= github.com/go-redis/redis v6.15.8+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA= github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-sqlite3 v1.9.0 h1:pDRiWfl+++eC2FEFRy6jXmQlvp4Yh3z1MJKg4UeYM/4= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/natefinch/lumberjack v2.0.0+incompatible h1:4QJd3OLAMgj7ph+yZTuX13Ld4UpgHp07nNdFX7mqFfM= github.com/natefinch/lumberjack v2.0.0+incompatible/go.mod h1:Wi9p2TTF5DG5oU+6YfsmYQpsTIOm0B1VNzQg9Mw6nPk= github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/sony/sonyflake v1.0.0 h1:MpU6Ro7tfXwgn2l5eluf9xQvQJDROTBImNCfRXn/YeM= github.com/sony/sonyflake v1.0.0/go.mod h1:Jv3cfhf/UFtolOTTRd3q4Nl6ENqM+KfyZ5PseKfZGF4= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.15.0 h1:ZZCA22JRF2gQE5FoNmhmrf7jeJJ2uhqDUNRYKm8dvmM= go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 h1:AeiKBIuRw3UomYXSbLy0Mc2dDLfdtbT/IVn4keq83P0= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 h1:DYfZAGf2WMFjMxbgTjaC+2HC7NkNAQs+6Q8b9WEB/F4= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc h1:NCy3Ohtk6Iny5V/reW2Ktypo4zIpWBdRJ1uFMjBxdg8= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= ================================================ FILE: lesson68/bluebell/logger/logger.go ================================================ package logger import ( "bluebell/setting" "net" "net/http" "net/http/httputil" "os" "runtime/debug" "strings" "time" "github.com/gin-gonic/gin" "github.com/natefinch/lumberjack" "go.uber.org/zap" "go.uber.org/zap/zapcore" ) var lg *zap.Logger // Init 初始化lg func Init(cfg *setting.LogConfig, mode string) (err error) { writeSyncer := getLogWriter(cfg.Filename, cfg.MaxSize, cfg.MaxBackups, cfg.MaxAge) encoder := getEncoder() var l = new(zapcore.Level) err = l.UnmarshalText([]byte(cfg.Level)) if err != nil { return } var core zapcore.Core if mode == "dev" { // 进入开发模式,日志输出到终端 consoleEncoder := zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig()) core = zapcore.NewTee( zapcore.NewCore(encoder, writeSyncer, l), zapcore.NewCore(consoleEncoder, zapcore.Lock(os.Stdout), zapcore.DebugLevel), ) } else { core = zapcore.NewCore(encoder, writeSyncer, l) } lg = zap.New(core, zap.AddCaller()) zap.ReplaceGlobals(lg) zap.L().Info("init logger success") return } func getEncoder() zapcore.Encoder { encoderConfig := zap.NewProductionEncoderConfig() encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder encoderConfig.TimeKey = "time" encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder encoderConfig.EncodeDuration = zapcore.SecondsDurationEncoder encoderConfig.EncodeCaller = zapcore.ShortCallerEncoder return zapcore.NewJSONEncoder(encoderConfig) } func getLogWriter(filename string, maxSize, maxBackup, maxAge int) zapcore.WriteSyncer { lumberJackLogger := &lumberjack.Logger{ Filename: filename, MaxSize: maxSize, MaxBackups: maxBackup, MaxAge: maxAge, } return zapcore.AddSync(lumberJackLogger) } // GinLogger 接收gin框架默认的日志 func GinLogger() gin.HandlerFunc { return func(c *gin.Context) { start := time.Now() path := c.Request.URL.Path query := c.Request.URL.RawQuery c.Next() cost := time.Since(start) lg.Info(path, zap.Int("status", c.Writer.Status()), zap.String("method", c.Request.Method), zap.String("path", path), zap.String("query", query), zap.String("ip", c.ClientIP()), zap.String("user-agent", c.Request.UserAgent()), zap.String("errors", c.Errors.ByType(gin.ErrorTypePrivate).String()), zap.Duration("cost", cost), ) } } // GinRecovery recover掉项目可能出现的panic,并使用zap记录相关日志 func GinRecovery(stack bool) gin.HandlerFunc { return func(c *gin.Context) { defer func() { if err := recover(); err != nil { // Check for a broken connection, as it is not really a // condition that warrants a panic stack trace. var brokenPipe bool if ne, ok := err.(*net.OpError); ok { if se, ok := ne.Err.(*os.SyscallError); ok { if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") { brokenPipe = true } } } httpRequest, _ := httputil.DumpRequest(c.Request, false) if brokenPipe { lg.Error(c.Request.URL.Path, zap.Any("error", err), zap.String("request", string(httpRequest)), ) // If the connection is dead, we can't write a status to it. c.Error(err.(error)) // nolint: errcheck c.Abort() return } if stack { lg.Error("[Recovery from panic]", zap.Any("error", err), zap.String("request", string(httpRequest)), zap.String("stack", string(debug.Stack())), ) } else { lg.Error("[Recovery from panic]", zap.Any("error", err), zap.String("request", string(httpRequest)), ) } c.AbortWithStatus(http.StatusInternalServerError) } }() c.Next() } } ================================================ FILE: lesson68/bluebell/logic/community.go ================================================ package logic import ( "bluebell/dao/mysql" "bluebell/models" ) func GetCommunityList() ([]*models.Community, error) { // 查数据库 查找到所有的community 并返回 return mysql.GetCommunityList() } func GetCommunityDetail(id int64) (*models.CommunityDetail, error) { return mysql.GetCommunityDetailByID(id) } ================================================ FILE: lesson68/bluebell/logic/post.go ================================================ package logic import ( "bluebell/dao/mysql" "bluebell/dao/redis" "bluebell/models" "bluebell/pkg/snowflake" "go.uber.org/zap" ) func CreatePost(p *models.Post) (err error) { // 1. 生成post id p.ID = snowflake.GenID() // 2. 保存到数据库 err = mysql.CreatePost(p) if err != nil { return err } err = redis.CreatePost(p.ID, p.CommunityID) return // 3. 返回 } // GetPostById 根据帖子id查询帖子详情数据 func GetPostById(pid int64) (data *models.ApiPostDetail, err error) { // 查询并组合我们接口想用的数据 post, err := mysql.GetPostById(pid) if err != nil { zap.L().Error("mysql.GetPostById(pid) failed", zap.Int64("pid", pid), zap.Error(err)) return } // 根据作者id查询作者信息 user, err := mysql.GetUserById(post.AuthorID) if err != nil { zap.L().Error("mysql.GetUserById(post.AuthorID) failed", zap.Int64("author_id", post.AuthorID), zap.Error(err)) return } // 根据社区id查询社区详细信息 community, err := mysql.GetCommunityDetailByID(post.CommunityID) if err != nil { zap.L().Error("mysql.GetUserById(post.AuthorID) failed", zap.Int64("community_id", post.CommunityID), zap.Error(err)) return } // 接口数据拼接 data = &models.ApiPostDetail{ AuthorName: user.Username, Post: post, CommunityDetail: community, } return } // GetPostList 获取帖子列表 func GetPostList(page, size int64) (data []*models.ApiPostDetail, err error) { posts, err := mysql.GetPostList(page, size) if err != nil { return nil, err } data = make([]*models.ApiPostDetail, 0, len(posts)) for _, post := range posts { // 根据作者id查询作者信息 user, err := mysql.GetUserById(post.AuthorID) if err != nil { zap.L().Error("mysql.GetUserById(post.AuthorID) failed", zap.Int64("author_id", post.AuthorID), zap.Error(err)) continue } // 根据社区id查询社区详细信息 community, err := mysql.GetCommunityDetailByID(post.CommunityID) if err != nil { zap.L().Error("mysql.GetUserById(post.AuthorID) failed", zap.Int64("community_id", post.CommunityID), zap.Error(err)) continue } postDetail := &models.ApiPostDetail{ AuthorName: user.Username, Post: post, CommunityDetail: community, } data = append(data, postDetail) } return } func GetPostList2(p *models.ParamPostList) (data []*models.ApiPostDetail, err error) { // 2. 去redis查询id列表 ids, err := redis.GetPostIDsInOrder(p) if err != nil { return } if len(ids) == 0 { zap.L().Warn("redis.GetPostIDsInOrder(p) return 0 data") return } zap.L().Debug("GetPostList2", zap.Any("ids", ids)) // 3. 根据id去MySQL数据库查询帖子详细信息 // 返回的数据还要按照我给定的id的顺序返回 posts, err := mysql.GetPostListByIDs(ids) if err != nil { return } zap.L().Debug("GetPostList2", zap.Any("posts", posts)) // 提前查询好每篇帖子的投票数 voteData, err := redis.GetPostVoteData(ids) if err != nil { return } // 将帖子的作者及分区信息查询出来填充到帖子中 for idx, post := range posts { // 根据作者id查询作者信息 user, err := mysql.GetUserById(post.AuthorID) if err != nil { zap.L().Error("mysql.GetUserById(post.AuthorID) failed", zap.Int64("author_id", post.AuthorID), zap.Error(err)) continue } // 根据社区id查询社区详细信息 community, err := mysql.GetCommunityDetailByID(post.CommunityID) if err != nil { zap.L().Error("mysql.GetUserById(post.AuthorID) failed", zap.Int64("community_id", post.CommunityID), zap.Error(err)) continue } postDetail := &models.ApiPostDetail{ AuthorName: user.Username, VoteNum: voteData[idx], Post: post, CommunityDetail: community, } data = append(data, postDetail) } return } func GetCommunityPostList(p *models.ParamPostList) (data []*models.ApiPostDetail, err error) { // 2. 去redis查询id列表 ids, err := redis.GetCommunityPostIDsInOrder(p) if err != nil { return } if len(ids) == 0 { zap.L().Warn("redis.GetPostIDsInOrder(p) return 0 data") return } zap.L().Debug("GetCommunityPostIDsInOrder", zap.Any("ids", ids)) // 3. 根据id去MySQL数据库查询帖子详细信息 // 返回的数据还要按照我给定的id的顺序返回 posts, err := mysql.GetPostListByIDs(ids) if err != nil { return } zap.L().Debug("GetPostList2", zap.Any("posts", posts)) // 提前查询好每篇帖子的投票数 voteData, err := redis.GetPostVoteData(ids) if err != nil { return } // 将帖子的作者及分区信息查询出来填充到帖子中 for idx, post := range posts { // 根据作者id查询作者信息 user, err := mysql.GetUserById(post.AuthorID) if err != nil { zap.L().Error("mysql.GetUserById(post.AuthorID) failed", zap.Int64("author_id", post.AuthorID), zap.Error(err)) continue } // 根据社区id查询社区详细信息 community, err := mysql.GetCommunityDetailByID(post.CommunityID) if err != nil { zap.L().Error("mysql.GetUserById(post.AuthorID) failed", zap.Int64("community_id", post.CommunityID), zap.Error(err)) continue } postDetail := &models.ApiPostDetail{ AuthorName: user.Username, VoteNum: voteData[idx], Post: post, CommunityDetail: community, } data = append(data, postDetail) } return } // GetPostListNew 将两个查询帖子列表逻辑合二为一的函数 func GetPostListNew(p *models.ParamPostList) (data []*models.ApiPostDetail, err error) { // 根据请求参数的不同,执行不同的逻辑。 if p.CommunityID == 0 { // 查所有 data, err = GetPostList2(p) } else { // 根据社区id查询 data, err = GetCommunityPostList(p) } if err != nil { zap.L().Error("GetPostListNew failed", zap.Error(err)) return nil, err } return } ================================================ FILE: lesson68/bluebell/logic/user.go ================================================ package logic import ( "bluebell/dao/mysql" "bluebell/models" "bluebell/pkg/jwt" "bluebell/pkg/snowflake" ) // 存放业务逻辑的代码 func SignUp(p *models.ParamSignUp) (err error) { // 1.判断用户存不存在 if err := mysql.CheckUserExist(p.Username); err != nil { return err } // 2.生成UID userID := snowflake.GenID() // 构造一个User实例 user := &models.User{ UserID: userID, Username: p.Username, Password: p.Password, } // 3.保存进数据库 return mysql.InsertUser(user) } func Login(p *models.ParamLogin) (user *models.User, err error) { user = &models.User{ Username: p.Username, Password: p.Password, } // 传递的是指针,就能拿到user.UserID if err := mysql.Login(user); err != nil { return nil, err } // 生成JWT token, err := jwt.GenToken(user.UserID, user.Username) if err != nil { return } user.Token = token return } ================================================ FILE: lesson68/bluebell/logic/vote.go ================================================ package logic import ( "bluebell/dao/redis" "bluebell/models" "strconv" "go.uber.org/zap" ) // 推荐阅读 // 基于用户投票的相关算法:http://www.ruanyifeng.com/blog/algorithm/ // 本项目使用简化版的投票分数 // 投一票就加432分 86400/200 --> 200张赞成票可以给你的帖子续一天 /* 投票的几种情况: direction=1时,有两种情况: 1. 之前没有投过票,现在投赞成票 --> 更新分数和投票记录 2. 之前投反对票,现在改投赞成票 --> 更新分数和投票记录 direction=0时,有两种情况: 1. 之前投过赞成票,现在要取消投票 --> 更新分数和投票记录 2. 之前投过反对票,现在要取消投票 --> 更新分数和投票记录 direction=-1时,有两种情况: 1. 之前没有投过票,现在投反对票 --> 更新分数和投票记录 2. 之前投赞成票,现在改投反对票 --> 更新分数和投票记录 投票的限制: 每个贴子自发表之日起一个星期之内允许用户投票,超过一个星期就不允许再投票了。 1. 到期之后将redis中保存的赞成票数及反对票数存储到mysql表中 2. 到期之后删除那个 KeyPostVotedZSetPF */ // VoteForPost 为帖子投票的函数 func VoteForPost(userID int64, p *models.ParamVoteData) error { zap.L().Debug("VoteForPost", zap.Int64("userID", userID), zap.String("postID", p.PostID), zap.Int8("direction", p.Direction)) return redis.VoteForPost(strconv.Itoa(int(userID)), p.PostID, float64(p.Direction)) } ================================================ FILE: lesson68/bluebell/main.go ================================================ package main import ( "bluebell/controller" "bluebell/dao/mysql" "bluebell/dao/redis" "bluebell/logger" "bluebell/pkg/snowflake" "bluebell/router" "bluebell/setting" "fmt" "os" ) func main() { if len(os.Args) < 2 { fmt.Println("need config file.eg: bluebell config.yaml") return } // 加载配置 if err := setting.Init(os.Args[1]); err != nil { fmt.Printf("load config failed, err:%v\n", err) return } if err := logger.Init(setting.Conf.LogConfig, setting.Conf.Mode); err != nil { fmt.Printf("init logger failed, err:%v\n", err) return } if err := mysql.Init(setting.Conf.MySQLConfig); err != nil { fmt.Printf("init mysql failed, err:%v\n", err) return } defer mysql.Close() // 程序退出关闭数据库连接 if err := redis.Init(setting.Conf.RedisConfig); err != nil { fmt.Printf("init redis failed, err:%v\n", err) return } defer redis.Close() if err := snowflake.Init(setting.Conf.StartTime, setting.Conf.MachineID); err != nil { fmt.Printf("init snowflake failed, err:%v\n", err) return } // 初始化gin框架内置的校验器使用的翻译器 if err := controller.InitTrans("zh"); err != nil { fmt.Printf("init validator trans failed, err:%v\n", err) return } // 注册路由 r := router.SetupRouter(setting.Conf.Mode) err := r.Run(fmt.Sprintf(":%d", setting.Conf.Port)) if err != nil { fmt.Printf("run server failed, err:%v\n", err) return } } ================================================ FILE: lesson68/bluebell/middlewares/auth.go ================================================ package middlewares import ( "bluebell/controller" "bluebell/pkg/jwt" "strings" "github.com/gin-gonic/gin" ) // JWTAuthMiddleware 基于JWT的认证中间件 func JWTAuthMiddleware() func(c *gin.Context) { return func(c *gin.Context) { // 客户端携带Token有三种方式 1.放在请求头 2.放在请求体 3.放在URI // 这里假设Token放在Header的Authorization中,并使用Bearer开头 // Authorization: Bearer xxxxxxx.xxx.xxx / X-TOKEN: xxx.xxx.xx // 这里的具体实现方式要依据你的实际业务情况决定 authHeader := c.Request.Header.Get("Authorization") if authHeader == "" { controller.ResponseError(c, controller.CodeNeedLogin) c.Abort() return } // 按空格分割 parts := strings.SplitN(authHeader, " ", 2) if !(len(parts) == 2 && parts[0] == "Bearer") { controller.ResponseError(c, controller.CodeInvalidToken) c.Abort() return } // parts[1]是获取到的tokenString,我们使用之前定义好的解析JWT的函数来解析它 mc, err := jwt.ParseToken(parts[1]) if err != nil { controller.ResponseError(c, controller.CodeInvalidToken) c.Abort() return } // 将当前请求的userID信息保存到请求的上下文c上 c.Set(controller.CtxUserIDKey, mc.UserID) c.Next() // 后续的处理请求的函数中 可以用过c.Get(CtxUserIDKey) 来获取当前请求的用户信息 } } ================================================ FILE: lesson68/bluebell/models/community.go ================================================ package models import "time" type Community struct { ID int64 `json:"id" db:"community_id"` Name string `json:"name" db:"community_name"` } type CommunityDetail struct { ID int64 `json:"id" db:"community_id"` Name string `json:"name" db:"community_name"` Introduction string `json:"introduction,omitempty" db:"introduction"` CreateTime time.Time `json:"create_time" db:"create_time"` } ================================================ FILE: lesson68/bluebell/models/create_table.sql ================================================ -- CREATE TABLE `user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `user_id` bigint(20) NOT NULL, `username` varchar(64) COLLATE utf8mb4_general_ci NOT NULL, `password` varchar(64) COLLATE utf8mb4_general_ci NOT NULL, `email` varchar(64) COLLATE utf8mb4_general_ci, `gender` tinyint(4) NOT NULL DEFAULT '0', `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP, `update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `idx_username` (`username`) USING BTREE, UNIQUE KEY `idx_user_id` (`user_id`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; DROP TABLE IF EXISTS `community`; CREATE TABLE `community` ( `id` int(11) NOT NULL AUTO_INCREMENT, `community_id` int(10) unsigned NOT NULL, `community_name` varchar(128) COLLATE utf8mb4_general_ci NOT NULL, `introduction` varchar(256) COLLATE utf8mb4_general_ci NOT NULL, `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `idx_community_id` (`community_id`), UNIQUE KEY `idx_community_name` (`community_name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; INSERT INTO `community` VALUES ('1', '1', 'Go', 'Golang', '2016-11-01 08:10:10', '2016-11-01 08:10:10'); INSERT INTO `community` VALUES ('2', '2', 'leetcode', '刷题刷题刷题', '2020-01-01 08:00:00', '2020-01-01 08:00:00'); INSERT INTO `community` VALUES ('3', '3', 'CS:GO', 'Rush B。。。', '2018-08-07 08:30:00', '2018-08-07 08:30:00'); INSERT INTO `community` VALUES ('4', '4', 'LOL', '欢迎来到英雄联盟!', '2016-01-01 08:00:00', '2016-01-01 08:00:00'); DROP TABLE IF EXISTS `post`; CREATE TABLE `post` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `post_id` bigint(20) NOT NULL COMMENT '帖子id', `title` varchar(128) COLLATE utf8mb4_general_ci NOT NULL COMMENT '标题', `content` varchar(8192) COLLATE utf8mb4_general_ci NOT NULL COMMENT '内容', `author_id` bigint(20) NOT NULL COMMENT '作者的用户id', `community_id` bigint(20) NOT NULL COMMENT '所属社区', `status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '帖子状态', `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), UNIQUE KEY `idx_post_id` (`post_id`), KEY `idx_author_id` (`author_id`), KEY `idx_community_id` (`community_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; ================================================ FILE: lesson68/bluebell/models/params.go ================================================ package models // 定义请求的参数结构体 const ( OrderTime = "time" OrderScore = "score" ) // ParamSignUp 注册请求参数 type ParamSignUp struct { Username string `json:"username" binding:"required"` Password string `json:"password" binding:"required"` RePassword string `json:"re_password" binding:"required,eqfield=Password"` } // ParamLogin 登录请求参数 type ParamLogin struct { Username string `json:"username" binding:"required"` Password string `json:"password" binding:"required"` } // ParamVoteData 投票数据 type ParamVoteData struct { // UserID 从请求中获取当前的用户 PostID string `json:"post_id" binding:"required"` // 贴子id Direction int8 `json:"direction,string" binding:"oneof=1 0 -1" ` // 赞成票(1)还是反对票(-1)取消投票(0) } // ParamPostList 获取帖子列表query string参数 type ParamPostList struct { CommunityID int64 `json:"community_id" form:"community_id"` // 可以为空 Page int64 `json:"page" form:"page"` Size int64 `json:"size" form:"size"` Order string `json:"order" form:"order"` } ================================================ FILE: lesson68/bluebell/models/post.go ================================================ package models import "time" // 内存对齐概念 type Post struct { ID int64 `json:"id,string" db:"post_id"` AuthorID int64 `json:"author_id" db:"author_id"` CommunityID int64 `json:"community_id" db:"community_id" binding:"required"` Status int32 `json:"status" db:"status"` Title string `json:"title" db:"title" binding:"required"` Content string `json:"content" db:"content" binding:"required"` CreateTime time.Time `json:"create_time" db:"create_time"` } // ApiPostDetail 帖子详情接口的结构体 type ApiPostDetail struct { AuthorName string `json:"author_name"` VoteNum int64 `json:"vote_num"` *Post // 嵌入帖子结构体 *CommunityDetail `json:"community"` // 嵌入社区信息 } ================================================ FILE: lesson68/bluebell/models/struct_test.go ================================================ package models import ( "fmt" "testing" "unsafe" ) // Go 内存对齐 type s1 struct { a int8 // 1 b string // 3 c int8 // 1 } type s2 struct { a int8 c int8 // 1 b string // 2 } func TestStruct(t *testing.T) { v1 := s1{ a: 1, b: "七米", c: 2, } v2 := s2{ a: 1, b: "七米", c: 2, } fmt.Println(unsafe.Sizeof(v1), unsafe.Sizeof(v2)) } ================================================ FILE: lesson68/bluebell/models/user.go ================================================ package models type User struct { UserID int64 `db:"user_id"` Username string `db:"username"` Password string `db:"password"` Token string } ================================================ FILE: lesson68/bluebell/pkg/jwt/jwt.go ================================================ package jwt import ( "errors" "time" "github.com/spf13/viper" "github.com/dgrijalva/jwt-go" ) var mySecret = []byte("夏天夏天悄悄过去") // MyClaims 自定义声明结构体并内嵌jwt.StandardClaims // jwt包自带的jwt.StandardClaims只包含了官方字段 // 我们这里需要额外记录一个username字段,所以要自定义结构体 // 如果想要保存更多信息,都可以添加到这个结构体中 type MyClaims struct { UserID int64 `json:"user_id"` Username string `json:"username"` jwt.StandardClaims } // GenToken 生成JWT func GenToken(userID int64, username string) (string, error) { // 创建一个我们自己的声明的数据 c := MyClaims{ userID, "username", // 自定义字段 jwt.StandardClaims{ ExpiresAt: time.Now().Add( time.Duration(viper.GetInt("auth.jwt_expire")) * time.Hour).Unix(), // 过期时间 Issuer: "bluebell", // 签发人 }, } // 使用指定的签名方法创建签名对象 token := jwt.NewWithClaims(jwt.SigningMethodHS256, c) // 使用指定的secret签名并获得完整的编码后的字符串token return token.SignedString(mySecret) } // ParseToken 解析JWT func ParseToken(tokenString string) (*MyClaims, error) { // 解析token var mc = new(MyClaims) token, err := jwt.ParseWithClaims(tokenString, mc, func(token *jwt.Token) (i interface{}, err error) { return mySecret, nil }) if err != nil { return nil, err } if token.Valid { // 校验token return mc, nil } return nil, errors.New("invalid token") } ================================================ FILE: lesson68/bluebell/pkg/snowflake/snowflake.go ================================================ package snowflake import ( "time" sf "github.com/bwmarrin/snowflake" ) var node *sf.Node func Init(startTime string, machineID int64) (err error) { var st time.Time st, err = time.Parse("2006-01-02", startTime) if err != nil { return } sf.Epoch = st.UnixNano() / 1000000 node, err = sf.NewNode(machineID) return } func GenID() int64 { return node.Generate().Int64() } ================================================ FILE: lesson68/bluebell/router/route.go ================================================ package router import ( "bluebell/controller" "bluebell/logger" "bluebell/middlewares" "net/http" "github.com/gin-gonic/gin" ) func SetupRouter(mode string) *gin.Engine { if mode == gin.ReleaseMode { gin.SetMode(gin.ReleaseMode) // gin设置成发布模式 } r := gin.New() r.Use(logger.GinLogger(), logger.GinRecovery(true)) v1 := r.Group("/api/v1") // 注册 v1.POST("/signup", controller.SignUpHandler) // 登录 v1.POST("/login", controller.LoginHandler) v1.Use(middlewares.JWTAuthMiddleware()) // 应用JWT认证中间件 { v1.GET("/community", controller.CommunityHandler) v1.GET("/community/:id", controller.CommunityDetailHandler) v1.POST("/post", controller.CreatePostHandler) v1.GET("/post/:id", controller.GetPostDetailHandler) v1.GET("/posts", controller.GetPostListHandler) // 根据时间或分数获取帖子列表 v1.GET("/posts2", controller.GetPostListHandler2) // 投票 v1.POST("/vote", controller.PostVoteController) } r.NoRoute(func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "msg": "404", }) }) return r } ================================================ FILE: lesson68/bluebell/setting/setting.go ================================================ package setting import ( "fmt" "github.com/fsnotify/fsnotify" "github.com/spf13/viper" ) var Conf = new(AppConfig) type AppConfig struct { Name string `mapstructure:"name"` Mode string `mapstructure:"mode"` Version string `mapstructure:"version"` StartTime string `mapstructure:"start_time"` MachineID int64 `mapstructure:"machine_id"` Port int `mapstructure:"port"` *LogConfig `mapstructure:"log"` *MySQLConfig `mapstructure:"mysql"` *RedisConfig `mapstructure:"redis"` } type MySQLConfig struct { Host string `mapstructure:"host"` User string `mapstructure:"user"` Password string `mapstructure:"password"` DB string `mapstructure:"dbname"` Port int `mapstructure:"port"` MaxOpenConns int `mapstructure:"max_open_conns"` MaxIdleConns int `mapstructure:"max_idle_conns"` } type RedisConfig struct { Host string `mapstructure:"host"` Password string `mapstructure:"password"` Port int `mapstructure:"port"` DB int `mapstructure:"db"` PoolSize int `mapstructure:"pool_size"` MinIdleConns int `mapstructure:"min_idle_conns"` } type LogConfig struct { Level string `mapstructure:"level"` Filename string `mapstructure:"filename"` MaxSize int `mapstructure:"max_size"` MaxAge int `mapstructure:"max_age"` MaxBackups int `mapstructure:"max_backups"` } func Init(filePath string) (err error) { // 方式1:直接指定配置文件路径(相对路径或者绝对路径) // 相对路径:相对执行的可执行文件的相对路径 //viper.SetConfigFile("./conf/config.yaml") // 绝对路径:系统中实际的文件路径 //viper.SetConfigFile("/Users/liwenzhou/Desktop/bluebell/conf/config.yaml") // 方式2:指定配置文件名和配置文件的位置,viper自行查找可用的配置文件 // 配置文件名不需要带后缀 // 配置文件位置可配置多个 //viper.SetConfigName("config") // 指定配置文件名(不带后缀) //viper.AddConfigPath(".") // 指定查找配置文件的路径(这里使用相对路径) //viper.AddConfigPath("./conf") // 指定查找配置文件的路径(这里使用相对路径) // 基本上是配合远程配置中心使用的,告诉viper当前的数据使用什么格式去解析 //viper.SetConfigType("json") viper.SetConfigFile(filePath) err = viper.ReadInConfig() // 读取配置信息 if err != nil { // 读取配置信息失败 fmt.Printf("viper.ReadInConfig failed, err:%v\n", err) return } // 把读取到的配置信息反序列化到 Conf 变量中 if err := viper.Unmarshal(Conf); err != nil { fmt.Printf("viper.Unmarshal failed, err:%v\n", err) } viper.WatchConfig() viper.OnConfigChange(func(in fsnotify.Event) { fmt.Println("配置文件修改了...") if err := viper.Unmarshal(Conf); err != nil { fmt.Printf("viper.Unmarshal failed, err:%v\n", err) } }) return } ================================================ FILE: lesson69/bluebell/.air.conf ================================================ # [Air](https://github.com/cosmtrek/air) TOML 格式的配置文件 # 工作目录 # 使用 . 或绝对路径,请注意 `tmp_dir` 目录必须在 `root` 目录下 root = "." tmp_dir = "tmp" [build] # 只需要写你平常编译使用的shell命令。你也可以使用 `make` cmd = "swag init && go build -o ./tmp/main" # 由`cmd`命令得到的二进制文件名 bin = "tmp/main" # 自定义的二进制,可以添加额外的编译标识例如添加 GIN_MODE=release full_bin = "./tmp/main ./conf/config.yaml" # 监听以下文件扩展名的文件. include_ext = ["go", "tpl", "tmpl", "html", "yaml"] # 忽略这些文件扩展名或目录 exclude_dir = ["assets", "tmp", "vendor", "frontend/node_modules"] # 监听以下指定目录的文件 include_dir = [] # 排除以下文件 exclude_file = [] # 如果文件更改过于频繁,则没有必要在每次更改时都触发构建。可以设置触发构建的延迟时间 delay = 1000 # ms # 发生构建错误时,停止运行旧的二进制文件。 stop_on_error = true # air的日志文件名,该日志文件放置在你的`tmp_dir`中 log = "air_errors.log" [log] # 显示日志时间 time = true [color] # 自定义每个部分显示的颜色。如果找不到颜色,使用原始的应用程序日志。 main = "magenta" watcher = "cyan" build = "yellow" runner = "green" [misc] # 退出时删除tmp目录 clean_on_exit = true ================================================ FILE: lesson69/bluebell/Makefile ================================================ .PHONY: all build run gotool clean help BINARY="xx" all: gotool build build: CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o ${BINARY} run: @go run ./main.go conf/config.yaml gotool: go fmt ./ go vet ./ clean: @if [ -f ${BINARY} ] ; then rm ${BINARY} ; fi help: @echo "make - 格式化 Go 代码, 并编译生成二进制文件" @echo "make build - 编译 Go 代码, 生成二进制文件" @echo "make run - 直接运行 Go 代码" @echo "make clean - 移除二进制文件和 vim swap files" @echo "make gotool - 运行 Go 工具 'fmt' and 'vet'" ================================================ FILE: lesson69/bluebell/conf/config.yaml ================================================ name: "bluebell" mode: "dev" port: 8084 version: "v0.0.1" start_time: "2020-07-01" machine_id: 1 auth: jwt_expire: 8760 log: level: "debug" filename: "web_app.log" max_size: 200 max_age: 30 max_backups: 7 mysql: host: "127.0.0.1" port: 13306 user: "root" password: "root1234" dbname: "bluebell" max_open_conns: 200 max_idle_conns: 50 redis: host: "127.0.0.1" port: 16379 password: "" db: 0 pool_size: 100 ================================================ FILE: lesson69/bluebell/controller/code.go ================================================ package controller type ResCode int64 const ( CodeSuccess ResCode = 1000 + iota CodeInvalidParam CodeUserExist CodeUserNotExist CodeInvalidPassword CodeServerBusy CodeNeedLogin CodeInvalidToken ) var codeMsgMap = map[ResCode]string{ CodeSuccess: "success", CodeInvalidParam: "请求参数错误", CodeUserExist: "用户名已存在", CodeUserNotExist: "用户名不存在", CodeInvalidPassword: "用户名或密码错误", CodeServerBusy: "服务繁忙", CodeNeedLogin: "需要登录", CodeInvalidToken: "无效的token", } func (c ResCode) Msg() string { msg, ok := codeMsgMap[c] if !ok { msg = codeMsgMap[CodeServerBusy] } return msg } ================================================ FILE: lesson69/bluebell/controller/community.go ================================================ package controller import ( "bluebell/logic" "strconv" "github.com/gin-gonic/gin" "go.uber.org/zap" ) // ---- 跟社区相关的 ---- func CommunityHandler(c *gin.Context) { // 查询到所有的社区(community_id, community_name) 以列表的形式返回 data, err := logic.GetCommunityList() if err != nil { zap.L().Error("logic.GetCommunityList() failed", zap.Error(err)) ResponseError(c, CodeServerBusy) // 不轻易把服务端报错暴露给外面 return } ResponseSuccess(c, data) } // CommunityDetailHandler 社区分类详情 func CommunityDetailHandler(c *gin.Context) { // 1. 获取社区id idStr := c.Param("id") // 获取URL参数 id, err := strconv.ParseInt(idStr, 10, 64) if err != nil { ResponseError(c, CodeInvalidParam) return } // 2. 根据id获取社区详情 data, err := logic.GetCommunityDetail(id) if err != nil { zap.L().Error("logic.GetCommunityList() failed", zap.Error(err)) ResponseError(c, CodeServerBusy) // 不轻易把服务端报错暴露给外面 return } ResponseSuccess(c, data) } ================================================ FILE: lesson69/bluebell/controller/doc_response_models.go ================================================ package controller import "bluebell/models" // 专门用来放接口文档用到的model // 因为我们的接口文档返回的数据格式是一致的,但是具体的data类型不一致 type _ResponsePostList struct { Code ResCode `json:"code"` // 业务响应状态码 Message string `json:"message"` // 提示信息 Data []*models.ApiPostDetail `json:"data"` // 数据 } ================================================ FILE: lesson69/bluebell/controller/post.go ================================================ package controller import ( "bluebell/logic" "bluebell/models" "strconv" "github.com/gin-gonic/gin" "go.uber.org/zap" // swagger 嵌入文件 ) // CreatePostHandler 创建帖子的处理函数 func CreatePostHandler(c *gin.Context) { // 1. 获取参数及参数的校验 //c.ShouldBindJSON() // validator --> binding tag p := new(models.Post) if err := c.ShouldBindJSON(p); err != nil { zap.L().Debug("c.ShouldBindJSON(p) error", zap.Any("err", err)) zap.L().Error("create post with invalid param") ResponseError(c, CodeInvalidParam) return } // 从 c 取到当前发请求的用户的ID userID, err := getCurrentUserID(c) if err != nil { ResponseError(c, CodeNeedLogin) return } p.AuthorID = userID // 2. 创建帖子 if err := logic.CreatePost(p); err != nil { zap.L().Error("logic.CreatePost(p) failed", zap.Error(err)) ResponseError(c, CodeServerBusy) return } // 3. 返回响应 ResponseSuccess(c, nil) } // GetPostDetailHandler 获取帖子详情的处理函数 func GetPostDetailHandler(c *gin.Context) { // 1. 获取参数(从URL中获取帖子的id) pidStr := c.Param("id") pid, err := strconv.ParseInt(pidStr, 10, 64) if err != nil { zap.L().Error("get post detail with invalid param", zap.Error(err)) ResponseError(c, CodeInvalidParam) return } // 2. 根据id取出帖子数据(查数据库) data, err := logic.GetPostById(pid) if err != nil { zap.L().Error("logic.GetPostById(pid) failed", zap.Error(err)) ResponseError(c, CodeServerBusy) return } // 3. 返回响应 ResponseSuccess(c, data) } // GetPostListHandler 获取帖子列表的处理函数 func GetPostListHandler(c *gin.Context) { // 获取分页参数 page, size := getPageInfo(c) // 获取数据 data, err := logic.GetPostList(page, size) if err != nil { zap.L().Error("logic.GetPostList() failed", zap.Error(err)) ResponseError(c, CodeServerBusy) return } ResponseSuccess(c, data) // 返回响应 } // GetPostListHandler2 升级版帖子列表接口 // @Summary 升级版帖子列表接口 // @Description 可按社区按时间或分数排序查询帖子列表接口 // @Tags 帖子相关接口(api分组展示使用的) // @Accept application/json // @Produce application/json // @Param Authorization header string true "Bearer JWT" // @Param object query models.ParamPostList false "查询参数" // @Security ApiKeyAuth // @Success 200 {object} _ResponsePostList // @Router /posts2 [get] func GetPostListHandler2(c *gin.Context) { // GET请求参数(query string):/api/v1/posts2?page=1&size=10&order=time // 初始化结构体时指定初始参数 p := &models.ParamPostList{ Page: 1, Size: 10, Order: models.OrderTime, // magic string } //c.ShouldBind() 根据请求的数据类型选择相应的方法去获取数据 //c.ShouldBindJSON() 如果请求中携带的是json格式的数据,才能用这个方法获取到数据 if err := c.ShouldBindQuery(p); err != nil { zap.L().Error("GetPostListHandler2 with invalid params", zap.Error(err)) ResponseError(c, CodeInvalidParam) return } data, err := logic.GetPostListNew(p) // 更新:合二为一 // 获取数据 if err != nil { zap.L().Error("logic.GetPostList() failed", zap.Error(err)) ResponseError(c, CodeServerBusy) return } ResponseSuccess(c, data) // 返回响应 } // 根据社区去查询帖子列表 //func GetCommunityPostListHandler(c *gin.Context) { // // 初始化结构体时指定初始参数 // p := &models.ParamCommunityPostList{ // ParamPostList: &models.ParamPostList{ // Page: 1, // Size: 10, // Order: models.OrderTime, // }, // } // //c.ShouldBind() 根据请求的数据类型选择相应的方法去获取数据 // //c.ShouldBindJSON() 如果请求中携带的是json格式的数据,才能用这个方法获取到数据 // if err := c.ShouldBindQuery(p); err != nil { // zap.L().Error("GetCommunityPostListHandler with invalid params", zap.Error(err)) // ResponseError(c, CodeInvalidParam) // return // } // // // 获取数据 // data, err := logic.GetCommunityPostList(p) // if err != nil { // zap.L().Error("logic.GetPostList() failed", zap.Error(err)) // ResponseError(c, CodeServerBusy) // return // } // ResponseSuccess(c, data) //} ================================================ FILE: lesson69/bluebell/controller/request.go ================================================ package controller import ( "errors" "strconv" "github.com/gin-gonic/gin" ) const CtxUserIDKey = "userID" var ErrorUserNotLogin = errors.New("用户未登录") // getCurrentUserID 获取当前登录的用户ID func getCurrentUserID(c *gin.Context) (userID int64, err error) { uid, ok := c.Get(CtxUserIDKey) if !ok { err = ErrorUserNotLogin return } userID, ok = uid.(int64) if !ok { err = ErrorUserNotLogin return } return } func getPageInfo(c *gin.Context) (int64, int64) { pageStr := c.Query("page") sizeStr := c.Query("size") var ( page int64 size int64 err error ) page, err = strconv.ParseInt(pageStr, 10, 64) if err != nil { page = 1 } size, err = strconv.ParseInt(sizeStr, 10, 64) if err != nil { size = 10 } return page, size } ================================================ FILE: lesson69/bluebell/controller/response.go ================================================ package controller import ( "net/http" "github.com/gin-gonic/gin" ) /* { "code": 10000, // 程序中的错误码 "msg": xx, // 提示信息 "data": {}, // 数据 } */ type ResponseData struct { Code ResCode `json:"code"` Msg interface{} `json:"msg"` Data interface{} `json:"data,omitempty"` } func ResponseError(c *gin.Context, code ResCode) { c.JSON(http.StatusOK, &ResponseData{ Code: code, Msg: code.Msg(), Data: nil, }) } func ResponseErrorWithMsg(c *gin.Context, code ResCode, msg interface{}) { c.JSON(http.StatusOK, &ResponseData{ Code: code, Msg: msg, Data: nil, }) } func ResponseSuccess(c *gin.Context, data interface{}) { c.JSON(http.StatusOK, &ResponseData{ Code: CodeSuccess, Msg: CodeSuccess.Msg(), Data: data, }) } ================================================ FILE: lesson69/bluebell/controller/user.go ================================================ package controller import ( "bluebell/dao/mysql" "bluebell/logic" "bluebell/models" "errors" "fmt" "github.com/go-playground/validator/v10" "go.uber.org/zap" "github.com/gin-gonic/gin" ) // SignUpHandler 处理注册请求的函数 func SignUpHandler(c *gin.Context) { // 1. 获取参数和参数校验 p := new(models.ParamSignUp) if err := c.ShouldBindJSON(p); err != nil { // 请求参数有误,直接返回响应 zap.L().Error("SignUp with invalid param", zap.Error(err)) // 判断err是不是validator.ValidationErrors 类型 errs, ok := err.(validator.ValidationErrors) if !ok { ResponseError(c, CodeInvalidParam) return } ResponseErrorWithMsg(c, CodeInvalidParam, removeTopStruct(errs.Translate(trans))) return } // 2. 业务处理 if err := logic.SignUp(p); err != nil { zap.L().Error("logic.SignUp failed", zap.Error(err)) if errors.Is(err, mysql.ErrorUserExist) { ResponseError(c, CodeUserExist) return } ResponseError(c, CodeServerBusy) return } // 3. 返回响应 ResponseSuccess(c, nil) } func LoginHandler(c *gin.Context) { // 1.获取请求参数及参数校验 p := new(models.ParamLogin) if err := c.ShouldBindJSON(p); err != nil { // 请求参数有误,直接返回响应 zap.L().Error("Login with invalid param", zap.Error(err)) // 判断err是不是validator.ValidationErrors 类型 errs, ok := err.(validator.ValidationErrors) if !ok { ResponseError(c, CodeInvalidParam) return } ResponseErrorWithMsg(c, CodeInvalidParam, removeTopStruct(errs.Translate(trans))) return } // 2.业务逻辑处理 user, err := logic.Login(p) if err != nil { zap.L().Error("logic.Login failed", zap.String("username", p.Username), zap.Error(err)) if errors.Is(err, mysql.ErrorUserNotExist) { ResponseError(c, CodeUserNotExist) return } ResponseError(c, CodeInvalidPassword) return } // 3.返回响应 ResponseSuccess(c, gin.H{ "user_id": fmt.Sprintf("%d", user.UserID), // id值大于1<<53-1 int64类型的最大值是1<<63-1 "user_name": user.Username, "token": user.Token, }) } ================================================ FILE: lesson69/bluebell/controller/validator.go ================================================ package controller import ( "bluebell/models" "fmt" "reflect" "strings" "github.com/gin-gonic/gin/binding" "github.com/go-playground/locales/en" "github.com/go-playground/locales/zh" ut "github.com/go-playground/universal-translator" "github.com/go-playground/validator/v10" enTranslations "github.com/go-playground/validator/v10/translations/en" zhTranslations "github.com/go-playground/validator/v10/translations/zh" ) // 定义一个全局翻译器T var trans ut.Translator // InitTrans 初始化翻译器 func InitTrans(locale string) (err error) { // 修改gin框架中的Validator引擎属性,实现自定制 if v, ok := binding.Validator.Engine().(*validator.Validate); ok { // 注册一个获取json tag的自定义方法 v.RegisterTagNameFunc(func(fld reflect.StructField) string { name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0] if name == "-" { return "" } return name }) // 为SignUpParam注册自定义校验方法 v.RegisterStructValidation(SignUpParamStructLevelValidation, models.ParamSignUp{}) zhT := zh.New() // 中文翻译器 enT := en.New() // 英文翻译器 // 第一个参数是备用(fallback)的语言环境 // 后面的参数是应该支持的语言环境(支持多个) // uni := ut.New(zhT, zhT) 也是可以的 uni := ut.New(enT, zhT, enT) // locale 通常取决于 http 请求头的 'Accept-Language' var ok bool // 也可以使用 uni.FindTranslator(...) 传入多个locale进行查找 trans, ok = uni.GetTranslator(locale) if !ok { return fmt.Errorf("uni.GetTranslator(%s) failed", locale) } // 注册翻译器 switch locale { case "en": err = enTranslations.RegisterDefaultTranslations(v, trans) case "zh": err = zhTranslations.RegisterDefaultTranslations(v, trans) default: err = enTranslations.RegisterDefaultTranslations(v, trans) } return } return } // removeTopStruct 去除提示信息中的结构体名称 func removeTopStruct(fields map[string]string) map[string]string { res := map[string]string{} for field, err := range fields { res[field[strings.Index(field, ".")+1:]] = err } return res } // SignUpParamStructLevelValidation 自定义SignUpParam结构体校验函数 func SignUpParamStructLevelValidation(sl validator.StructLevel) { su := sl.Current().Interface().(models.ParamSignUp) if su.Password != su.RePassword { // 输出错误提示信息,最后一个参数就是传递的param sl.ReportError(su.RePassword, "re_password", "RePassword", "eqfield", "password") } } ================================================ FILE: lesson69/bluebell/controller/vote.go ================================================ package controller import ( "bluebell/logic" "bluebell/models" "go.uber.org/zap" "github.com/go-playground/validator/v10" "github.com/gin-gonic/gin" ) // 投票 //type VoteData struct { // // UserID 从请求中获取当前的用户 // PostID int64 `json:"post_id,string"` // 贴子id // Direction int `json:"direction,string"` // 赞成票(1)还是反对票(-1) //} func PostVoteController(c *gin.Context) { // 参数校验 p := new(models.ParamVoteData) if err := c.ShouldBindJSON(p); err != nil { errs, ok := err.(validator.ValidationErrors) // 类型断言 if !ok { ResponseError(c, CodeInvalidParam) return } errData := removeTopStruct(errs.Translate(trans)) // 翻译并去除掉错误提示中的结构体标识 ResponseErrorWithMsg(c, CodeInvalidParam, errData) return } // 获取当前请求的用户的id userID, err := getCurrentUserID(c) if err != nil { ResponseError(c, CodeNeedLogin) return } // 具体投票的业务逻辑 if err := logic.VoteForPost(userID, p); err != nil { zap.L().Error("logic.VoteForPost() failed", zap.Error(err)) ResponseError(c, CodeServerBusy) return } ResponseSuccess(c, nil) } ================================================ FILE: lesson69/bluebell/dao/mysql/community.go ================================================ package mysql import ( "bluebell/models" "database/sql" "go.uber.org/zap" ) func GetCommunityList() (communityList []*models.Community, err error) { sqlStr := "select community_id, community_name from community" if err := db.Select(&communityList, sqlStr); err != nil { if err == sql.ErrNoRows { zap.L().Warn("there is no community in db") err = nil } } return } // GetCommunityDetailByID 根据ID查询社区详情 func GetCommunityDetailByID(id int64) (community *models.CommunityDetail, err error) { community = new(models.CommunityDetail) sqlStr := `select community_id, community_name, introduction, create_time from community where community_id = ? ` if err := db.Get(community, sqlStr, id); err != nil { if err == sql.ErrNoRows { err = ErrorInvalidID } } return community, err } ================================================ FILE: lesson69/bluebell/dao/mysql/error_code.go ================================================ package mysql import "errors" var ( ErrorUserExist = errors.New("用户已存在") ErrorUserNotExist = errors.New("用户不存在") ErrorInvalidPassword = errors.New("用户名或密码错误") ErrorInvalidID = errors.New("无效的ID") ) ================================================ FILE: lesson69/bluebell/dao/mysql/mysql.go ================================================ package mysql import ( "bluebell/setting" "fmt" _ "github.com/go-sql-driver/mysql" "github.com/jmoiron/sqlx" ) var db *sqlx.DB // Init 初始化MySQL连接 func Init(cfg *setting.MySQLConfig) (err error) { // "user:password@tcp(host:port)/dbname" dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?parseTime=true&loc=Local", cfg.User, cfg.Password, cfg.Host, cfg.Port, cfg.DB) db, err = sqlx.Connect("mysql", dsn) if err != nil { return } db.SetMaxOpenConns(cfg.MaxOpenConns) db.SetMaxIdleConns(cfg.MaxIdleConns) return } // Close 关闭MySQL连接 func Close() { _ = db.Close() } ================================================ FILE: lesson69/bluebell/dao/mysql/post.go ================================================ package mysql import ( "bluebell/models" "strings" "github.com/jmoiron/sqlx" ) // CreatePost 创建帖子 func CreatePost(p *models.Post) (err error) { sqlStr := `insert into post( post_id, title, content, author_id, community_id) values (?, ?, ?, ?, ?) ` _, err = db.Exec(sqlStr, p.ID, p.Title, p.Content, p.AuthorID, p.CommunityID) return } // GetPostById 根据id查询单个贴子数据 func GetPostById(pid int64) (post *models.Post, err error) { post = new(models.Post) sqlStr := `select post_id, title, content, author_id, community_id, create_time from post where post_id = ? ` err = db.Get(post, sqlStr, pid) return } // GetPostList 查询帖子列表函数 func GetPostList(page, size int64) (posts []*models.Post, err error) { sqlStr := `select post_id, title, content, author_id, community_id, create_time from post ORDER BY create_time DESC limit ?,? ` posts = make([]*models.Post, 0, 2) // 不要写成make([]*models.Post, 2) err = db.Select(&posts, sqlStr, (page-1)*size, size) return } // GetPostListByIDs 根据给定的id列表查询帖子数据 func GetPostListByIDs(ids []string) (postList []*models.Post, err error) { sqlStr := `select post_id, title, content, author_id, community_id, create_time from post where post_id in (?) order by FIND_IN_SET(post_id, ?) ` // https: //www.liwenzhou.com/posts/Go/sqlx/ query, args, err := sqlx.In(sqlStr, ids, strings.Join(ids, ",")) if err != nil { return nil, err } query = db.Rebind(query) err = db.Select(&postList, query, args...) // !!!!!! return } ================================================ FILE: lesson69/bluebell/dao/mysql/user.go ================================================ package mysql import ( "bluebell/models" "crypto/md5" "database/sql" "encoding/hex" ) // 把每一步数据库操作封装成函数 // 待logic层根据业务需求调用 const secret = "liwenzhou.com" // CheckUserExist 检查指定用户名的用户是否存在 func CheckUserExist(username string) (err error) { sqlStr := `select count(user_id) from user where username = ?` var count int64 if err := db.Get(&count, sqlStr, username); err != nil { return err } if count > 0 { return ErrorUserExist } return } // InsertUser 想数据库中插入一条新的用户记录 func InsertUser(user *models.User) (err error) { // 对密码进行加密 user.Password = encryptPassword(user.Password) // 执行SQL语句入库 sqlStr := `insert into user(user_id, username, password) values(?,?,?)` _, err = db.Exec(sqlStr, user.UserID, user.Username, user.Password) return } // encryptPassword 密码加密 func encryptPassword(oPassword string) string { h := md5.New() h.Write([]byte(secret)) return hex.EncodeToString(h.Sum([]byte(oPassword))) } func Login(user *models.User) (err error) { oPassword := user.Password // 用户登录的密码 sqlStr := `select user_id, username, password from user where username=?` err = db.Get(user, sqlStr, user.Username) if err == sql.ErrNoRows { return ErrorUserNotExist } if err != nil { // 查询数据库失败 return err } // 判断密码是否正确 password := encryptPassword(oPassword) if password != user.Password { return ErrorInvalidPassword } return } // GetUserById 根据id获取用户信息 func GetUserById(uid int64) (user *models.User, err error) { user = new(models.User) sqlStr := `select user_id, username from user where user_id = ?` err = db.Get(user, sqlStr, uid) return } ================================================ FILE: lesson69/bluebell/dao/redis/keys.go ================================================ package redis // redis key // redis key注意使用命名空间的方式,方便查询和拆分 const ( Prefix = "bluebell:" // 项目key前缀 KeyPostTimeZSet = "post:time" // zset;贴子及发帖时间 KeyPostScoreZSet = "post:score" // zset;贴子及投票的分数 KeyPostVotedZSetPF = "post:voted:" // zset;记录用户及投票类型;参数是post id KeyCommunitySetPF = "community:" // set;保存每个分区下帖子的id ) // 给redis key加上前缀 func getRedisKey(key string) string { return Prefix + key } ================================================ FILE: lesson69/bluebell/dao/redis/post.go ================================================ package redis import ( "bluebell/models" "strconv" "time" "github.com/go-redis/redis" ) func getIDsFormKey(key string, page, size int64) ([]string, error) { start := (page - 1) * size end := start + size - 1 // 3. ZREVRANGE 按分数从大到小的顺序查询指定数量的元素 return client.ZRevRange(key, start, end).Result() } func GetPostIDsInOrder(p *models.ParamPostList) ([]string, error) { // 从redis获取id // 1. 根据用户请求中携带的order参数确定要查询的redis key key := getRedisKey(KeyPostTimeZSet) if p.Order == models.OrderScore { key = getRedisKey(KeyPostScoreZSet) } // 2. 确定查询的索引起始点 return getIDsFormKey(key, p.Page, p.Size) } // GetPostVoteData 根据ids查询每篇帖子的投赞成票的数据 func GetPostVoteData(ids []string) (data []int64, err error) { //data = make([]int64, 0, len(ids)) //for _, id := range ids { // key := getRedisKey(KeyPostVotedZSetPF + id) // // 查找key中分数是1的元素的数量->统计每篇帖子的赞成票的数量 // v := client.ZCount(key, "1", "1").Val() // data = append(data, v) //} // 使用pipeline一次发送多条命令,减少RTT pipeline := client.Pipeline() for _, id := range ids { key := getRedisKey(KeyPostVotedZSetPF + id) pipeline.ZCount(key, "1", "1") } cmders, err := pipeline.Exec() if err != nil { return nil, err } data = make([]int64, 0, len(cmders)) for _, cmder := range cmders { v := cmder.(*redis.IntCmd).Val() data = append(data, v) } return } // GetCommunityPostIDsInOrder 按社区查询ids func GetCommunityPostIDsInOrder(p *models.ParamPostList) ([]string, error) { orderKey := getRedisKey(KeyPostTimeZSet) if p.Order == models.OrderScore { orderKey = getRedisKey(KeyPostScoreZSet) } // 使用 zinterstore 把分区的帖子set与帖子分数的 zset 生成一个新的zset // 针对新的zset 按之前的逻辑取数据 // 社区的key cKey := getRedisKey(KeyCommunitySetPF + strconv.Itoa(int(p.CommunityID))) // 利用缓存key减少zinterstore执行的次数 key := orderKey + strconv.Itoa(int(p.CommunityID)) if client.Exists(key).Val() < 1 { // 不存在,需要计算 pipeline := client.Pipeline() pipeline.ZInterStore(key, redis.ZStore{ Aggregate: "MAX", }, cKey, orderKey) // zinterstore 计算 pipeline.Expire(key, 60*time.Second) // 设置超时时间 _, err := pipeline.Exec() if err != nil { return nil, err } } // 存在的话就直接根据key查询ids return getIDsFormKey(key, p.Page, p.Size) } ================================================ FILE: lesson69/bluebell/dao/redis/redis.go ================================================ package redis import ( "bluebell/setting" "fmt" "github.com/go-redis/redis" ) var ( client *redis.Client Nil = redis.Nil ) // Init 初始化连接 func Init(cfg *setting.RedisConfig) (err error) { client = redis.NewClient(&redis.Options{ Addr: fmt.Sprintf("%s:%d", cfg.Host, cfg.Port), Password: cfg.Password, // no password set DB: cfg.DB, // use default DB PoolSize: cfg.PoolSize, MinIdleConns: cfg.MinIdleConns, }) _, err = client.Ping().Result() if err != nil { return err } return nil } func Close() { _ = client.Close() } ================================================ FILE: lesson69/bluebell/dao/redis/vote.go ================================================ package redis import ( "errors" "math" "strconv" "time" "github.com/go-redis/redis" ) // 推荐阅读 // 基于用户投票的相关算法:http://www.ruanyifeng.com/blog/algorithm/ // 本项目使用简化版的投票分数 // 投一票就加432分 86400/200 --> 200张赞成票可以给你的帖子续一天 /* 投票的几种情况: direction=1时,有两种情况: 1. 之前没有投过票,现在投赞成票 --> 更新分数和投票记录 差值的绝对值:1 +432 2. 之前投反对票,现在改投赞成票 --> 更新分数和投票记录 差值的绝对值:2 +432*2 direction=0时,有两种情况: 1. 之前投过反对票,现在要取消投票 --> 更新分数和投票记录 差值的绝对值:1 +432 2. 之前投过赞成票,现在要取消投票 --> 更新分数和投票记录 差值的绝对值:1 -432 direction=-1时,有两种情况: 1. 之前没有投过票,现在投反对票 --> 更新分数和投票记录 差值的绝对值:1 -432 2. 之前投赞成票,现在改投反对票 --> 更新分数和投票记录 差值的绝对值:2 -432*2 投票的限制: 每个贴子自发表之日起一个星期之内允许用户投票,超过一个星期就不允许再投票了。 1. 到期之后将redis中保存的赞成票数及反对票数存储到mysql表中 2. 到期之后删除那个 KeyPostVotedZSetPF */ const ( oneWeekInSeconds = 7 * 24 * 3600 scorePerVote = 432 // 每一票值多少分 ) var ( ErrVoteTimeExpire = errors.New("投票时间已过") ErrVoteRepeated = errors.New("不允许重复投票") ) func CreatePost(postID, communityID int64) error { pipeline := client.TxPipeline() // 帖子时间 pipeline.ZAdd(getRedisKey(KeyPostTimeZSet), redis.Z{ Score: float64(time.Now().Unix()), Member: postID, }) // 帖子分数 pipeline.ZAdd(getRedisKey(KeyPostScoreZSet), redis.Z{ Score: float64(time.Now().Unix()), Member: postID, }) // 更新:把帖子id加到社区的set cKey := getRedisKey(KeyCommunitySetPF + strconv.Itoa(int(communityID))) pipeline.SAdd(cKey, postID) _, err := pipeline.Exec() return err } func VoteForPost(userID, postID string, value float64) error { // 1. 判断投票限制 // 去redis取帖子发布时间 postTime := client.ZScore(getRedisKey(KeyPostTimeZSet), postID).Val() if float64(time.Now().Unix())-postTime > oneWeekInSeconds { return ErrVoteTimeExpire } // 2和3需要放到一个pipeline事务中操作 // 2. 更新贴子的分数 // 先查当前用户给当前帖子的投票记录 ov := client.ZScore(getRedisKey(KeyPostVotedZSetPF+postID), userID).Val() // 更新:如果这一次投票的值和之前保存的值一致,就提示不允许重复投票 if value == ov { return ErrVoteRepeated } var op float64 if value > ov { op = 1 } else { op = -1 } diff := math.Abs(ov - value) // 计算两次投票的差值 pipeline := client.TxPipeline() pipeline.ZIncrBy(getRedisKey(KeyPostScoreZSet), op*diff*scorePerVote, postID) // 3. 记录用户为该贴子投票的数据 if value == 0 { pipeline.ZRem(getRedisKey(KeyPostVotedZSetPF+postID), userID) } else { pipeline.ZAdd(getRedisKey(KeyPostVotedZSetPF+postID), redis.Z{ Score: value, // 赞成票还是反对票 Member: userID, }) } _, err := pipeline.Exec() return err } ================================================ FILE: lesson69/bluebell/docs/docs.go ================================================ // GENERATED BY THE COMMAND ABOVE; DO NOT EDIT // This file was generated by swaggo/swag package docs import ( "bytes" "encoding/json" "strings" "github.com/alecthomas/template" "github.com/swaggo/swag" ) var doc = `{ "schemes": {{ marshal .Schemes }}, "swagger": "2.0", "info": { "description": "{{.Description}}", "title": "{{.Title}}", "contact": { "name": "liwenzhou", "url": "http://www.liwenzhou.com" }, "license": {}, "version": "{{.Version}}" }, "host": "{{.Host}}", "basePath": "{{.BasePath}}", "paths": { "/posts2": { "get": { "security": [ { "ApiKeyAuth": [] } ], "description": "可按社区按时间或分数排序查询帖子列表接口", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "帖子相关接口(api分组展示使用的)" ], "summary": "升级版帖子列表接口", "parameters": [ { "type": "string", "description": "Bearer JWT", "name": "Authorization", "in": "header", "required": true }, { "type": "integer", "description": "可以为空", "name": "community_id", "in": "query" }, { "type": "string", "example": "score", "description": "排序依据", "name": "order", "in": "query" }, { "type": "integer", "description": "页码", "name": "page", "in": "query" }, { "type": "integer", "description": "每页数据量", "name": "size", "in": "query" } ], "responses": { "200": { "description": "OK", "schema": { "$ref": "#/definitions/controller._ResponsePostList" } } } } } }, "definitions": { "controller._ResponsePostList": { "type": "object", "properties": { "code": { "description": "业务响应状态码", "type": "integer" }, "data": { "description": "数据", "type": "array", "items": { "$ref": "#/definitions/models.ApiPostDetail" } }, "message": { "description": "提示信息", "type": "string" } } }, "models.ApiPostDetail": { "type": "object", "required": [ "community_id", "content", "title" ], "properties": { "author_id": { "description": "作者id", "type": "integer" }, "author_name": { "description": "作者", "type": "string" }, "community_id": { "description": "社区id", "type": "integer" }, "content": { "description": "帖子内容", "type": "string" }, "create_time": { "type": "string" }, "id": { "type": "integer" }, "introduction": { "type": "string" }, "name": { "type": "string" }, "status": { "description": "帖子状态", "type": "integer" }, "title": { "description": "帖子标题", "type": "string" }, "vote_num": { "description": "投票数", "type": "integer" } } }, "models.ParamPostList": { "type": "object", "properties": { "community_id": { "description": "可以为空", "type": "integer" }, "order": { "description": "排序依据", "type": "string", "example": "score" }, "page": { "description": "页码", "type": "integer" }, "size": { "description": "每页数据量", "type": "integer" } } } } }` type swaggerInfo struct { Version string Host string BasePath string Schemes []string Title string Description string } // SwaggerInfo holds exported Swagger Info so clients can modify it var SwaggerInfo = swaggerInfo{ Version: "1.0", Host: "127.0.0.1:8084", BasePath: "/api/v1", Schemes: []string{}, Title: "bluebell项目接口文档", Description: "Go web开发进阶项目实战课程bluebell", } type s struct{} func (s *s) ReadDoc() string { sInfo := SwaggerInfo sInfo.Description = strings.Replace(sInfo.Description, "\n", "\\n", -1) t, err := template.New("swagger_info").Funcs(template.FuncMap{ "marshal": func(v interface{}) string { a, _ := json.Marshal(v) return string(a) }, }).Parse(doc) if err != nil { return doc } var tpl bytes.Buffer if err := t.Execute(&tpl, sInfo); err != nil { return doc } return tpl.String() } func init() { swag.Register(swag.Name, &s{}) } ================================================ FILE: lesson69/bluebell/docs/swagger.json ================================================ { "swagger": "2.0", "info": { "description": "Go web开发进阶项目实战课程bluebell", "title": "bluebell项目接口文档", "contact": { "name": "liwenzhou", "url": "http://www.liwenzhou.com" }, "license": {}, "version": "1.0" }, "host": "127.0.0.1:8084", "basePath": "/api/v1", "paths": { "/posts2": { "get": { "security": [ { "ApiKeyAuth": [] } ], "description": "可按社区按时间或分数排序查询帖子列表接口", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "帖子相关接口(api分组展示使用的)" ], "summary": "升级版帖子列表接口", "parameters": [ { "type": "string", "description": "Bearer JWT", "name": "Authorization", "in": "header", "required": true }, { "type": "integer", "description": "可以为空", "name": "community_id", "in": "query" }, { "type": "string", "example": "score", "description": "排序依据", "name": "order", "in": "query" }, { "type": "integer", "description": "页码", "name": "page", "in": "query" }, { "type": "integer", "description": "每页数据量", "name": "size", "in": "query" } ], "responses": { "200": { "description": "OK", "schema": { "$ref": "#/definitions/controller._ResponsePostList" } } } } } }, "definitions": { "controller._ResponsePostList": { "type": "object", "properties": { "code": { "description": "业务响应状态码", "type": "integer" }, "data": { "description": "数据", "type": "array", "items": { "$ref": "#/definitions/models.ApiPostDetail" } }, "message": { "description": "提示信息", "type": "string" } } }, "models.ApiPostDetail": { "type": "object", "required": [ "community_id", "content", "title" ], "properties": { "author_id": { "description": "作者id", "type": "integer" }, "author_name": { "description": "作者", "type": "string" }, "community_id": { "description": "社区id", "type": "integer" }, "content": { "description": "帖子内容", "type": "string" }, "create_time": { "type": "string" }, "id": { "type": "integer" }, "introduction": { "type": "string" }, "name": { "type": "string" }, "status": { "description": "帖子状态", "type": "integer" }, "title": { "description": "帖子标题", "type": "string" }, "vote_num": { "description": "投票数", "type": "integer" } } }, "models.ParamPostList": { "type": "object", "properties": { "community_id": { "description": "可以为空", "type": "integer" }, "order": { "description": "排序依据", "type": "string", "example": "score" }, "page": { "description": "页码", "type": "integer" }, "size": { "description": "每页数据量", "type": "integer" } } } } } ================================================ FILE: lesson69/bluebell/docs/swagger.yaml ================================================ basePath: /api/v1 definitions: controller._ResponsePostList: properties: code: description: 业务响应状态码 type: integer data: description: 数据 items: $ref: '#/definitions/models.ApiPostDetail' type: array message: description: 提示信息 type: string type: object models.ApiPostDetail: properties: author_id: description: 作者id type: integer author_name: description: 作者 type: string community_id: description: 社区id type: integer content: description: 帖子内容 type: string create_time: type: string id: type: integer introduction: type: string name: type: string status: description: 帖子状态 type: integer title: description: 帖子标题 type: string vote_num: description: 投票数 type: integer required: - community_id - content - title type: object models.ParamPostList: properties: community_id: description: 可以为空 type: integer order: description: 排序依据 example: score type: string page: description: 页码 type: integer size: description: 每页数据量 type: integer type: object host: 127.0.0.1:8084 info: contact: name: liwenzhou url: http://www.liwenzhou.com description: Go web开发进阶项目实战课程bluebell license: {} title: bluebell项目接口文档 version: "1.0" paths: /posts2: get: consumes: - application/json description: 可按社区按时间或分数排序查询帖子列表接口 parameters: - description: Bearer JWT in: header name: Authorization required: true type: string - description: 可以为空 in: query name: community_id type: integer - description: 排序依据 example: score in: query name: order type: string - description: 页码 in: query name: page type: integer - description: 每页数据量 in: query name: size type: integer produces: - application/json responses: "200": description: OK schema: $ref: '#/definitions/controller._ResponsePostList' security: - ApiKeyAuth: [] summary: 升级版帖子列表接口 tags: - 帖子相关接口(api分组展示使用的) swagger: "2.0" ================================================ FILE: lesson69/bluebell/go.mod ================================================ module bluebell go 1.14 require ( github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 github.com/bwmarrin/snowflake v0.3.0 github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/fsnotify/fsnotify v1.4.9 github.com/gin-gonic/gin v1.6.3 github.com/go-openapi/spec v0.19.9 // indirect github.com/go-openapi/swag v0.19.9 // indirect github.com/go-playground/locales v0.13.0 github.com/go-playground/universal-translator v0.17.0 github.com/go-playground/validator/v10 v10.3.0 github.com/go-redis/redis v6.15.8+incompatible github.com/go-sql-driver/mysql v1.5.0 github.com/jmoiron/sqlx v1.2.0 github.com/json-iterator/go v1.1.10 // indirect github.com/mailru/easyjson v0.7.6 // indirect github.com/natefinch/lumberjack v2.0.0+incompatible github.com/onsi/ginkgo v1.14.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/sony/sonyflake v1.0.0 // indirect github.com/spf13/viper v1.7.0 github.com/swaggo/gin-swagger v1.2.0 github.com/swaggo/swag v1.6.7 github.com/urfave/cli/v2 v2.2.0 // indirect go.uber.org/zap v1.15.0 golang.org/x/net v0.0.0-20200904194848-62affa334b73 // indirect golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f // indirect golang.org/x/tools v0.0.0-20200904185747-39188db58858 // indirect google.golang.org/protobuf v1.25.0 // indirect gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect ) ================================================ FILE: lesson69/bluebell/go.sum ================================================ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/PuerkitoBio/purell v1.1.0 h1:rmGxhojJlM0tuKtfdvliR84CFHljx9ag64t2xmVkjK4= github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0= github.com/bwmarrin/snowflake v0.3.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/E9WsDpxqwE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/deckarep/golang-set v1.7.1/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/gzip v0.0.1/go.mod h1:fGBJBCdt6qCZuCAOwWuFhBB4OOq9EFqlo5dEaFhhu5w= github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.3.0/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y= github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14= github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-openapi/jsonpointer v0.17.0 h1:nH6xp8XdXHx8dqveo0ZuJBluCO2qGrPbDNZ0dwoRHP0= github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= github.com/go-openapi/jsonreference v0.19.0 h1:BqWKpV1dFd+AuiKlgtddwVIFQsuMpxfBDBHGfM2yNpk= github.com/go-openapi/jsonreference v0.19.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= github.com/go-openapi/jsonreference v0.19.4 h1:3Vw+rh13uq2JFNxgnMTGE1rnoieU9FmyE1gvnyylsYg= github.com/go-openapi/jsonreference v0.19.4/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= github.com/go-openapi/spec v0.19.0 h1:A4SZ6IWh3lnjH0rG0Z5lkxazMGBECtrZcbyYQi+64k4= github.com/go-openapi/spec v0.19.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= github.com/go-openapi/spec v0.19.4/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= github.com/go-openapi/spec v0.19.9 h1:9z9cbFuZJ7AcvOHKIY+f6Aevb4vObNDkTEyoMfO7rAc= github.com/go-openapi/spec v0.19.9/go.mod h1:vqK/dIdLGCosfvYsQV3WfC7N3TiZSnGY2RZKoFK7X28= github.com/go-openapi/swag v0.17.0 h1:iqrgMg7Q7SvtbWLlltPrkMs0UBJI6oTSs79JFRUi880= github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.9 h1:1IxuqvBUU3S2Bi4YC7tlP9SJF1gVpCvqN0T2Qof4azE= github.com/go-openapi/swag v0.19.9/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY= github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= github.com/go-playground/validator/v10 v10.3.0 h1:nZU+7q+yJoFmwvNgv/LnPUkwPal62+b2xXj0AU1Es7o= github.com/go-playground/validator/v10 v10.3.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= github.com/go-redis/redis v6.15.8+incompatible h1:BKZuG6mCnRj5AOaWJXoCgf6rqTYnYJLe4en2hxT7r9o= github.com/go-redis/redis v6.15.8+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA= github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329 h1:2gxZ0XQIU/5z3Z3bUBu+FXuk2pFbkN6tcwi/pjyaDic= github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-sqlite3 v1.9.0 h1:pDRiWfl+++eC2FEFRy6jXmQlvp4Yh3z1MJKg4UeYM/4= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/natefinch/lumberjack v2.0.0+incompatible h1:4QJd3OLAMgj7ph+yZTuX13Ld4UpgHp07nNdFX7mqFfM= github.com/natefinch/lumberjack v2.0.0+incompatible/go.mod h1:Wi9p2TTF5DG5oU+6YfsmYQpsTIOm0B1VNzQg9Mw6nPk= github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/sony/sonyflake v1.0.0 h1:MpU6Ro7tfXwgn2l5eluf9xQvQJDROTBImNCfRXn/YeM= github.com/sony/sonyflake v1.0.0/go.mod h1:Jv3cfhf/UFtolOTTRd3q4Nl6ENqM+KfyZ5PseKfZGF4= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14 h1:PyYN9JH5jY9j6av01SpfRMb+1DWg/i3MbGOKPxJ2wjM= github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14/go.mod h1:gxQT6pBGRuIGunNf/+tSOB5OHvguWi8Tbt82WOkf35E= github.com/swaggo/gin-swagger v1.2.0 h1:YskZXEiv51fjOMTsXrOetAjrMDfFaXD79PEoQBOe2W0= github.com/swaggo/gin-swagger v1.2.0/go.mod h1:qlH2+W7zXGZkczuL+r2nEBR2JTT+/lX05Nn6vPhc7OI= github.com/swaggo/swag v1.5.1 h1:2Agm8I4K5qb00620mHq0VJ05/KT4FtmALPIcQR9lEZM= github.com/swaggo/swag v1.5.1/go.mod h1:1Bl9F/ZBpVWh22nY0zmYyASPO1lI/zIwRDrpZU+tv8Y= github.com/swaggo/swag v1.6.7 h1:e8GC2xDllJZr3omJkm9YfmK0Y56+rMO3cg0JBKNz09s= github.com/swaggo/swag v1.6.7/go.mod h1:xDhTyuFIujYiN3DKWC/H/83xcfHp+UE/IzWWampG7Zc= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go v1.1.5-pre/go.mod h1:FwP/aQVg39TXzItUBMwnWp9T9gPQnXw4Poh4/oBQZ/0= github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v0.0.0-20181022190402-e5e69e061d4f/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/ugorji/go/codec v1.1.5-pre/go.mod h1:tULtS6Gy1AE1yCENaw4Vb//HLH5njI2tfCQDUqRd8fI= github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli/v2 v2.1.1 h1:Qt8FeAtxE/vfdrLmR3rxR6JRE0RoVmbXu8+6kZtYU4k= github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= github.com/urfave/cli/v2 v2.2.0 h1:JTTnM6wKzdA0Jqodd966MVj4vWbbquZykeX1sKbe2C4= github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.15.0 h1:ZZCA22JRF2gQE5FoNmhmrf7jeJJ2uhqDUNRYKm8dvmM= go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190611141213-3f473d35a33a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 h1:AeiKBIuRw3UomYXSbLy0Mc2dDLfdtbT/IVn4keq83P0= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200904194848-62affa334b73 h1:MXfv8rhZWmFeqX3GNZRsd6vOLoaCHjYEX3qkRo3YBUA= golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 h1:DYfZAGf2WMFjMxbgTjaC+2HC7NkNAQs+6Q8b9WEB/F4= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f h1:Fqb3ao1hUmOR3GkUOg/Y+BadLwykBIzs5q8Ez2SbHyc= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606050223-4d9ae51c2468/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190611222205-d73e1c7e250b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc h1:NCy3Ohtk6Iny5V/reW2Ktypo4zIpWBdRJ1uFMjBxdg8= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200904185747-39188db58858 h1:xLt+iB5ksWcZVxqc+g9K41ZHy+6MKWfXCDsjSThnsPA= golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= ================================================ FILE: lesson69/bluebell/logger/logger.go ================================================ package logger import ( "bluebell/setting" "net" "net/http" "net/http/httputil" "os" "runtime/debug" "strings" "time" "github.com/gin-gonic/gin" "github.com/natefinch/lumberjack" "go.uber.org/zap" "go.uber.org/zap/zapcore" ) var lg *zap.Logger // Init 初始化lg func Init(cfg *setting.LogConfig, mode string) (err error) { writeSyncer := getLogWriter(cfg.Filename, cfg.MaxSize, cfg.MaxBackups, cfg.MaxAge) encoder := getEncoder() var l = new(zapcore.Level) err = l.UnmarshalText([]byte(cfg.Level)) if err != nil { return } var core zapcore.Core if mode == "dev" { // 进入开发模式,日志输出到终端 consoleEncoder := zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig()) core = zapcore.NewTee( zapcore.NewCore(encoder, writeSyncer, l), zapcore.NewCore(consoleEncoder, zapcore.Lock(os.Stdout), zapcore.DebugLevel), ) } else { core = zapcore.NewCore(encoder, writeSyncer, l) } lg = zap.New(core, zap.AddCaller()) zap.ReplaceGlobals(lg) zap.L().Info("init logger success") return } func getEncoder() zapcore.Encoder { encoderConfig := zap.NewProductionEncoderConfig() encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder encoderConfig.TimeKey = "time" encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder encoderConfig.EncodeDuration = zapcore.SecondsDurationEncoder encoderConfig.EncodeCaller = zapcore.ShortCallerEncoder return zapcore.NewJSONEncoder(encoderConfig) } func getLogWriter(filename string, maxSize, maxBackup, maxAge int) zapcore.WriteSyncer { lumberJackLogger := &lumberjack.Logger{ Filename: filename, MaxSize: maxSize, MaxBackups: maxBackup, MaxAge: maxAge, } return zapcore.AddSync(lumberJackLogger) } // GinLogger 接收gin框架默认的日志 func GinLogger() gin.HandlerFunc { return func(c *gin.Context) { start := time.Now() path := c.Request.URL.Path query := c.Request.URL.RawQuery c.Next() cost := time.Since(start) lg.Info(path, zap.Int("status", c.Writer.Status()), zap.String("method", c.Request.Method), zap.String("path", path), zap.String("query", query), zap.String("ip", c.ClientIP()), zap.String("user-agent", c.Request.UserAgent()), zap.String("errors", c.Errors.ByType(gin.ErrorTypePrivate).String()), zap.Duration("cost", cost), ) } } // GinRecovery recover掉项目可能出现的panic,并使用zap记录相关日志 func GinRecovery(stack bool) gin.HandlerFunc { return func(c *gin.Context) { defer func() { if err := recover(); err != nil { // Check for a broken connection, as it is not really a // condition that warrants a panic stack trace. var brokenPipe bool if ne, ok := err.(*net.OpError); ok { if se, ok := ne.Err.(*os.SyscallError); ok { if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") { brokenPipe = true } } } httpRequest, _ := httputil.DumpRequest(c.Request, false) if brokenPipe { lg.Error(c.Request.URL.Path, zap.Any("error", err), zap.String("request", string(httpRequest)), ) // If the connection is dead, we can't write a status to it. c.Error(err.(error)) // nolint: errcheck c.Abort() return } if stack { lg.Error("[Recovery from panic]", zap.Any("error", err), zap.String("request", string(httpRequest)), zap.String("stack", string(debug.Stack())), ) } else { lg.Error("[Recovery from panic]", zap.Any("error", err), zap.String("request", string(httpRequest)), ) } c.AbortWithStatus(http.StatusInternalServerError) } }() c.Next() } } ================================================ FILE: lesson69/bluebell/logic/community.go ================================================ package logic import ( "bluebell/dao/mysql" "bluebell/models" ) func GetCommunityList() ([]*models.Community, error) { // 查数据库 查找到所有的community 并返回 return mysql.GetCommunityList() } func GetCommunityDetail(id int64) (*models.CommunityDetail, error) { return mysql.GetCommunityDetailByID(id) } ================================================ FILE: lesson69/bluebell/logic/post.go ================================================ package logic import ( "bluebell/dao/mysql" "bluebell/dao/redis" "bluebell/models" "bluebell/pkg/snowflake" "go.uber.org/zap" ) func CreatePost(p *models.Post) (err error) { // 1. 生成post id p.ID = snowflake.GenID() // 2. 保存到数据库 err = mysql.CreatePost(p) if err != nil { return err } err = redis.CreatePost(p.ID, p.CommunityID) return // 3. 返回 } // GetPostById 根据帖子id查询帖子详情数据 func GetPostById(pid int64) (data *models.ApiPostDetail, err error) { // 查询并组合我们接口想用的数据 post, err := mysql.GetPostById(pid) if err != nil { zap.L().Error("mysql.GetPostById(pid) failed", zap.Int64("pid", pid), zap.Error(err)) return } // 根据作者id查询作者信息 user, err := mysql.GetUserById(post.AuthorID) if err != nil { zap.L().Error("mysql.GetUserById(post.AuthorID) failed", zap.Int64("author_id", post.AuthorID), zap.Error(err)) return } // 根据社区id查询社区详细信息 community, err := mysql.GetCommunityDetailByID(post.CommunityID) if err != nil { zap.L().Error("mysql.GetUserById(post.AuthorID) failed", zap.Int64("community_id", post.CommunityID), zap.Error(err)) return } // 接口数据拼接 data = &models.ApiPostDetail{ AuthorName: user.Username, Post: post, CommunityDetail: community, } return } // GetPostList 获取帖子列表 func GetPostList(page, size int64) (data []*models.ApiPostDetail, err error) { posts, err := mysql.GetPostList(page, size) if err != nil { return nil, err } data = make([]*models.ApiPostDetail, 0, len(posts)) for _, post := range posts { // 根据作者id查询作者信息 user, err := mysql.GetUserById(post.AuthorID) if err != nil { zap.L().Error("mysql.GetUserById(post.AuthorID) failed", zap.Int64("author_id", post.AuthorID), zap.Error(err)) continue } // 根据社区id查询社区详细信息 community, err := mysql.GetCommunityDetailByID(post.CommunityID) if err != nil { zap.L().Error("mysql.GetUserById(post.AuthorID) failed", zap.Int64("community_id", post.CommunityID), zap.Error(err)) continue } postDetail := &models.ApiPostDetail{ AuthorName: user.Username, Post: post, CommunityDetail: community, } data = append(data, postDetail) } return } func GetPostList2(p *models.ParamPostList) (data []*models.ApiPostDetail, err error) { // 2. 去redis查询id列表 ids, err := redis.GetPostIDsInOrder(p) if err != nil { return } if len(ids) == 0 { zap.L().Warn("redis.GetPostIDsInOrder(p) return 0 data") return } zap.L().Debug("GetPostList2", zap.Any("ids", ids)) // 3. 根据id去MySQL数据库查询帖子详细信息 // 返回的数据还要按照我给定的id的顺序返回 posts, err := mysql.GetPostListByIDs(ids) if err != nil { return } zap.L().Debug("GetPostList2", zap.Any("posts", posts)) // 提前查询好每篇帖子的投票数 voteData, err := redis.GetPostVoteData(ids) if err != nil { return } // 将帖子的作者及分区信息查询出来填充到帖子中 for idx, post := range posts { // 根据作者id查询作者信息 user, err := mysql.GetUserById(post.AuthorID) if err != nil { zap.L().Error("mysql.GetUserById(post.AuthorID) failed", zap.Int64("author_id", post.AuthorID), zap.Error(err)) continue } // 根据社区id查询社区详细信息 community, err := mysql.GetCommunityDetailByID(post.CommunityID) if err != nil { zap.L().Error("mysql.GetUserById(post.AuthorID) failed", zap.Int64("community_id", post.CommunityID), zap.Error(err)) continue } postDetail := &models.ApiPostDetail{ AuthorName: user.Username, VoteNum: voteData[idx], Post: post, CommunityDetail: community, } data = append(data, postDetail) } return } func GetCommunityPostList(p *models.ParamPostList) (data []*models.ApiPostDetail, err error) { // 2. 去redis查询id列表 ids, err := redis.GetCommunityPostIDsInOrder(p) if err != nil { return } if len(ids) == 0 { zap.L().Warn("redis.GetPostIDsInOrder(p) return 0 data") return } zap.L().Debug("GetCommunityPostIDsInOrder", zap.Any("ids", ids)) // 3. 根据id去MySQL数据库查询帖子详细信息 // 返回的数据还要按照我给定的id的顺序返回 posts, err := mysql.GetPostListByIDs(ids) if err != nil { return } zap.L().Debug("GetPostList2", zap.Any("posts", posts)) // 提前查询好每篇帖子的投票数 voteData, err := redis.GetPostVoteData(ids) if err != nil { return } // 将帖子的作者及分区信息查询出来填充到帖子中 for idx, post := range posts { // 根据作者id查询作者信息 user, err := mysql.GetUserById(post.AuthorID) if err != nil { zap.L().Error("mysql.GetUserById(post.AuthorID) failed", zap.Int64("author_id", post.AuthorID), zap.Error(err)) continue } // 根据社区id查询社区详细信息 community, err := mysql.GetCommunityDetailByID(post.CommunityID) if err != nil { zap.L().Error("mysql.GetUserById(post.AuthorID) failed", zap.Int64("community_id", post.CommunityID), zap.Error(err)) continue } postDetail := &models.ApiPostDetail{ AuthorName: user.Username, VoteNum: voteData[idx], Post: post, CommunityDetail: community, } data = append(data, postDetail) } return } // GetPostListNew 将两个查询帖子列表逻辑合二为一的函数 func GetPostListNew(p *models.ParamPostList) (data []*models.ApiPostDetail, err error) { // 根据请求参数的不同,执行不同的逻辑。 if p.CommunityID == 0 { // 查所有 data, err = GetPostList2(p) } else { // 根据社区id查询 data, err = GetCommunityPostList(p) } if err != nil { zap.L().Error("GetPostListNew failed", zap.Error(err)) return nil, err } return } ================================================ FILE: lesson69/bluebell/logic/user.go ================================================ package logic import ( "bluebell/dao/mysql" "bluebell/models" "bluebell/pkg/jwt" "bluebell/pkg/snowflake" ) // 存放业务逻辑的代码 func SignUp(p *models.ParamSignUp) (err error) { // 1.判断用户存不存在 if err := mysql.CheckUserExist(p.Username); err != nil { return err } // 2.生成UID userID := snowflake.GenID() // 构造一个User实例 user := &models.User{ UserID: userID, Username: p.Username, Password: p.Password, } // 3.保存进数据库 return mysql.InsertUser(user) } func Login(p *models.ParamLogin) (user *models.User, err error) { user = &models.User{ Username: p.Username, Password: p.Password, } // 传递的是指针,就能拿到user.UserID if err := mysql.Login(user); err != nil { return nil, err } // 生成JWT token, err := jwt.GenToken(user.UserID, user.Username) if err != nil { return } user.Token = token return } ================================================ FILE: lesson69/bluebell/logic/vote.go ================================================ package logic import ( "bluebell/dao/redis" "bluebell/models" "strconv" "go.uber.org/zap" ) // 推荐阅读 // 基于用户投票的相关算法:http://www.ruanyifeng.com/blog/algorithm/ // 本项目使用简化版的投票分数 // 投一票就加432分 86400/200 --> 200张赞成票可以给你的帖子续一天 /* 投票的几种情况: direction=1时,有两种情况: 1. 之前没有投过票,现在投赞成票 --> 更新分数和投票记录 2. 之前投反对票,现在改投赞成票 --> 更新分数和投票记录 direction=0时,有两种情况: 1. 之前投过赞成票,现在要取消投票 --> 更新分数和投票记录 2. 之前投过反对票,现在要取消投票 --> 更新分数和投票记录 direction=-1时,有两种情况: 1. 之前没有投过票,现在投反对票 --> 更新分数和投票记录 2. 之前投赞成票,现在改投反对票 --> 更新分数和投票记录 投票的限制: 每个贴子自发表之日起一个星期之内允许用户投票,超过一个星期就不允许再投票了。 1. 到期之后将redis中保存的赞成票数及反对票数存储到mysql表中 2. 到期之后删除那个 KeyPostVotedZSetPF */ // VoteForPost 为帖子投票的函数 func VoteForPost(userID int64, p *models.ParamVoteData) error { zap.L().Debug("VoteForPost", zap.Int64("userID", userID), zap.String("postID", p.PostID), zap.Int8("direction", p.Direction)) return redis.VoteForPost(strconv.Itoa(int(userID)), p.PostID, float64(p.Direction)) } ================================================ FILE: lesson69/bluebell/main.go ================================================ package main import ( "bluebell/controller" "bluebell/dao/mysql" "bluebell/dao/redis" "bluebell/logger" "bluebell/pkg/snowflake" "bluebell/router" "bluebell/setting" "fmt" "os" ) // @title bluebell项目接口文档 // @version 1.0 // @description Go web开发进阶项目实战课程bluebell // @contact.name liwenzhou // @contact.url http://www.liwenzhou.com // @host 127.0.0.1:8084 // @BasePath /api/v1 func main() { if len(os.Args) < 2 { fmt.Println("need config file.eg: bluebell config.yaml") return } // 加载配置 if err := setting.Init(os.Args[1]); err != nil { fmt.Printf("load config failed, err:%v\n", err) return } if err := logger.Init(setting.Conf.LogConfig, setting.Conf.Mode); err != nil { fmt.Printf("init logger failed, err:%v\n", err) return } if err := mysql.Init(setting.Conf.MySQLConfig); err != nil { fmt.Printf("init mysql failed, err:%v\n", err) return } defer mysql.Close() // 程序退出关闭数据库连接 if err := redis.Init(setting.Conf.RedisConfig); err != nil { fmt.Printf("init redis failed, err:%v\n", err) return } defer redis.Close() if err := snowflake.Init(setting.Conf.StartTime, setting.Conf.MachineID); err != nil { fmt.Printf("init snowflake failed, err:%v\n", err) return } // 初始化gin框架内置的校验器使用的翻译器 if err := controller.InitTrans("zh"); err != nil { fmt.Printf("init validator trans failed, err:%v\n", err) return } // 注册路由 r := router.SetupRouter(setting.Conf.Mode) err := r.Run(fmt.Sprintf(":%d", setting.Conf.Port)) if err != nil { fmt.Printf("run server failed, err:%v\n", err) return } } ================================================ FILE: lesson69/bluebell/middlewares/auth.go ================================================ package middlewares import ( "bluebell/controller" "bluebell/pkg/jwt" "strings" "github.com/gin-gonic/gin" ) // JWTAuthMiddleware 基于JWT的认证中间件 func JWTAuthMiddleware() func(c *gin.Context) { return func(c *gin.Context) { // 客户端携带Token有三种方式 1.放在请求头 2.放在请求体 3.放在URI // 这里假设Token放在Header的Authorization中,并使用Bearer开头 // Authorization: Bearer xxxxxxx.xxx.xxx / X-TOKEN: xxx.xxx.xx // 这里的具体实现方式要依据你的实际业务情况决定 authHeader := c.Request.Header.Get("Authorization") if authHeader == "" { controller.ResponseError(c, controller.CodeNeedLogin) c.Abort() return } // 按空格分割 parts := strings.SplitN(authHeader, " ", 2) if !(len(parts) == 2 && parts[0] == "Bearer") { controller.ResponseError(c, controller.CodeInvalidToken) c.Abort() return } // parts[1]是获取到的tokenString,我们使用之前定义好的解析JWT的函数来解析它 mc, err := jwt.ParseToken(parts[1]) if err != nil { controller.ResponseError(c, controller.CodeInvalidToken) c.Abort() return } // 将当前请求的userID信息保存到请求的上下文c上 c.Set(controller.CtxUserIDKey, mc.UserID) c.Next() // 后续的处理请求的函数中 可以用过c.Get(CtxUserIDKey) 来获取当前请求的用户信息 } } ================================================ FILE: lesson69/bluebell/models/community.go ================================================ package models import "time" type Community struct { ID int64 `json:"id" db:"community_id"` Name string `json:"name" db:"community_name"` } type CommunityDetail struct { ID int64 `json:"id" db:"community_id"` Name string `json:"name" db:"community_name"` Introduction string `json:"introduction,omitempty" db:"introduction"` CreateTime time.Time `json:"create_time" db:"create_time"` } ================================================ FILE: lesson69/bluebell/models/create_table.sql ================================================ -- CREATE TABLE `user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `user_id` bigint(20) NOT NULL, `username` varchar(64) COLLATE utf8mb4_general_ci NOT NULL, `password` varchar(64) COLLATE utf8mb4_general_ci NOT NULL, `email` varchar(64) COLLATE utf8mb4_general_ci, `gender` tinyint(4) NOT NULL DEFAULT '0', `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP, `update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `idx_username` (`username`) USING BTREE, UNIQUE KEY `idx_user_id` (`user_id`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; DROP TABLE IF EXISTS `community`; CREATE TABLE `community` ( `id` int(11) NOT NULL AUTO_INCREMENT, `community_id` int(10) unsigned NOT NULL, `community_name` varchar(128) COLLATE utf8mb4_general_ci NOT NULL, `introduction` varchar(256) COLLATE utf8mb4_general_ci NOT NULL, `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `idx_community_id` (`community_id`), UNIQUE KEY `idx_community_name` (`community_name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; INSERT INTO `community` VALUES ('1', '1', 'Go', 'Golang', '2016-11-01 08:10:10', '2016-11-01 08:10:10'); INSERT INTO `community` VALUES ('2', '2', 'leetcode', '刷题刷题刷题', '2020-01-01 08:00:00', '2020-01-01 08:00:00'); INSERT INTO `community` VALUES ('3', '3', 'CS:GO', 'Rush B。。。', '2018-08-07 08:30:00', '2018-08-07 08:30:00'); INSERT INTO `community` VALUES ('4', '4', 'LOL', '欢迎来到英雄联盟!', '2016-01-01 08:00:00', '2016-01-01 08:00:00'); DROP TABLE IF EXISTS `post`; CREATE TABLE `post` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `post_id` bigint(20) NOT NULL COMMENT '帖子id', `title` varchar(128) COLLATE utf8mb4_general_ci NOT NULL COMMENT '标题', `content` varchar(8192) COLLATE utf8mb4_general_ci NOT NULL COMMENT '内容', `author_id` bigint(20) NOT NULL COMMENT '作者的用户id', `community_id` bigint(20) NOT NULL COMMENT '所属社区', `status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '帖子状态', `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), UNIQUE KEY `idx_post_id` (`post_id`), KEY `idx_author_id` (`author_id`), KEY `idx_community_id` (`community_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; ================================================ FILE: lesson69/bluebell/models/params.go ================================================ package models // 定义请求的参数结构体 const ( OrderTime = "time" OrderScore = "score" ) // ParamSignUp 注册请求参数 type ParamSignUp struct { Username string `json:"username" binding:"required"` Password string `json:"password" binding:"required"` RePassword string `json:"re_password" binding:"required,eqfield=Password"` } // ParamLogin 登录请求参数 type ParamLogin struct { Username string `json:"username" binding:"required"` Password string `json:"password" binding:"required"` } // ParamVoteData 投票数据 type ParamVoteData struct { // UserID 从请求中获取当前的用户 PostID string `json:"post_id" binding:"required"` // 贴子id Direction int8 `json:"direction,string" binding:"oneof=1 0 -1" ` // 赞成票(1)还是反对票(-1)取消投票(0) } // ParamPostList 获取帖子列表query string参数 type ParamPostList struct { CommunityID int64 `json:"community_id" form:"community_id"` // 可以为空 Page int64 `json:"page" form:"page"` // 页码 Size int64 `json:"size" form:"size"` // 每页数据量 Order string `json:"order" form:"order" example:"score"` // 排序依据 } ================================================ FILE: lesson69/bluebell/models/post.go ================================================ package models import "time" // 内存对齐概念 type Post struct { ID int64 `json:"id,string" db:"post_id"` // 帖子id AuthorID int64 `json:"author_id" db:"author_id"` // 作者id CommunityID int64 `json:"community_id" db:"community_id" binding:"required"` // 社区id Status int32 `json:"status" db:"status"` // 帖子状态 Title string `json:"title" db:"title" binding:"required"` // 帖子标题 Content string `json:"content" db:"content" binding:"required"` // 帖子内容 CreateTime time.Time `json:"create_time" db:"create_time"` // 帖子创建时间 } // ApiPostDetail 帖子详情接口的结构体 type ApiPostDetail struct { AuthorName string `json:"author_name"` // 作者 VoteNum int64 `json:"vote_num"` // 投票数 *Post // 嵌入帖子结构体 *CommunityDetail `json:"community"` // 嵌入社区信息 } ================================================ FILE: lesson69/bluebell/models/struct_test.go ================================================ package models import ( "fmt" "testing" "unsafe" ) // Go 内存对齐 type s1 struct { a int8 // 1 b string // 3 c int8 // 1 } type s2 struct { a int8 c int8 // 1 b string // 2 } func TestStruct(t *testing.T) { v1 := s1{ a: 1, b: "七米", c: 2, } v2 := s2{ a: 1, b: "七米", c: 2, } fmt.Println(unsafe.Sizeof(v1), unsafe.Sizeof(v2)) } ================================================ FILE: lesson69/bluebell/models/user.go ================================================ package models type User struct { UserID int64 `db:"user_id"` Username string `db:"username"` Password string `db:"password"` Token string } ================================================ FILE: lesson69/bluebell/pkg/jwt/jwt.go ================================================ package jwt import ( "errors" "time" "github.com/spf13/viper" "github.com/dgrijalva/jwt-go" ) var mySecret = []byte("夏天夏天悄悄过去") // MyClaims 自定义声明结构体并内嵌jwt.StandardClaims // jwt包自带的jwt.StandardClaims只包含了官方字段 // 我们这里需要额外记录一个username字段,所以要自定义结构体 // 如果想要保存更多信息,都可以添加到这个结构体中 type MyClaims struct { UserID int64 `json:"user_id"` Username string `json:"username"` jwt.StandardClaims } // GenToken 生成JWT func GenToken(userID int64, username string) (string, error) { // 创建一个我们自己的声明的数据 c := MyClaims{ userID, "username", // 自定义字段 jwt.StandardClaims{ ExpiresAt: time.Now().Add( time.Duration(viper.GetInt("auth.jwt_expire")) * time.Hour).Unix(), // 过期时间 Issuer: "bluebell", // 签发人 }, } // 使用指定的签名方法创建签名对象 token := jwt.NewWithClaims(jwt.SigningMethodHS256, c) // 使用指定的secret签名并获得完整的编码后的字符串token return token.SignedString(mySecret) } // ParseToken 解析JWT func ParseToken(tokenString string) (*MyClaims, error) { // 解析token var mc = new(MyClaims) token, err := jwt.ParseWithClaims(tokenString, mc, func(token *jwt.Token) (i interface{}, err error) { return mySecret, nil }) if err != nil { return nil, err } if token.Valid { // 校验token return mc, nil } return nil, errors.New("invalid token") } ================================================ FILE: lesson69/bluebell/pkg/snowflake/snowflake.go ================================================ package snowflake import ( "time" sf "github.com/bwmarrin/snowflake" ) var node *sf.Node func Init(startTime string, machineID int64) (err error) { var st time.Time st, err = time.Parse("2006-01-02", startTime) if err != nil { return } sf.Epoch = st.UnixNano() / 1000000 node, err = sf.NewNode(machineID) return } func GenID() int64 { return node.Generate().Int64() } ================================================ FILE: lesson69/bluebell/router/route.go ================================================ package router import ( "bluebell/controller" "bluebell/logger" "bluebell/middlewares" "net/http" ginSwagger "github.com/swaggo/gin-swagger" "github.com/swaggo/gin-swagger/swaggerFiles" _ "bluebell/docs" "github.com/gin-gonic/gin" ) func SetupRouter(mode string) *gin.Engine { if mode == gin.ReleaseMode { gin.SetMode(gin.ReleaseMode) // gin设置成发布模式 } r := gin.New() r.Use(logger.GinLogger(), logger.GinRecovery(true)) r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) v1 := r.Group("/api/v1") // 注册 v1.POST("/signup", controller.SignUpHandler) // 登录 v1.POST("/login", controller.LoginHandler) v1.Use(middlewares.JWTAuthMiddleware()) // 应用JWT认证中间件 { v1.GET("/community", controller.CommunityHandler) v1.GET("/community/:id", controller.CommunityDetailHandler) v1.POST("/post", controller.CreatePostHandler) v1.GET("/post/:id", controller.GetPostDetailHandler) v1.GET("/posts", controller.GetPostListHandler) // 根据时间或分数获取帖子列表 v1.GET("/posts2", controller.GetPostListHandler2) // 投票 v1.POST("/vote", controller.PostVoteController) } r.NoRoute(func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "msg": "404", }) }) return r } ================================================ FILE: lesson69/bluebell/setting/setting.go ================================================ package setting import ( "fmt" "github.com/fsnotify/fsnotify" "github.com/spf13/viper" ) var Conf = new(AppConfig) type AppConfig struct { Name string `mapstructure:"name"` Mode string `mapstructure:"mode"` Version string `mapstructure:"version"` StartTime string `mapstructure:"start_time"` MachineID int64 `mapstructure:"machine_id"` Port int `mapstructure:"port"` *LogConfig `mapstructure:"log"` *MySQLConfig `mapstructure:"mysql"` *RedisConfig `mapstructure:"redis"` } type MySQLConfig struct { Host string `mapstructure:"host"` User string `mapstructure:"user"` Password string `mapstructure:"password"` DB string `mapstructure:"dbname"` Port int `mapstructure:"port"` MaxOpenConns int `mapstructure:"max_open_conns"` MaxIdleConns int `mapstructure:"max_idle_conns"` } type RedisConfig struct { Host string `mapstructure:"host"` Password string `mapstructure:"password"` Port int `mapstructure:"port"` DB int `mapstructure:"db"` PoolSize int `mapstructure:"pool_size"` MinIdleConns int `mapstructure:"min_idle_conns"` } type LogConfig struct { Level string `mapstructure:"level"` Filename string `mapstructure:"filename"` MaxSize int `mapstructure:"max_size"` MaxAge int `mapstructure:"max_age"` MaxBackups int `mapstructure:"max_backups"` } func Init(filePath string) (err error) { // 方式1:直接指定配置文件路径(相对路径或者绝对路径) // 相对路径:相对执行的可执行文件的相对路径 //viper.SetConfigFile("./conf/config.yaml") // 绝对路径:系统中实际的文件路径 //viper.SetConfigFile("/Users/liwenzhou/Desktop/bluebell/conf/config.yaml") // 方式2:指定配置文件名和配置文件的位置,viper自行查找可用的配置文件 // 配置文件名不需要带后缀 // 配置文件位置可配置多个 //viper.SetConfigName("config") // 指定配置文件名(不带后缀) //viper.AddConfigPath(".") // 指定查找配置文件的路径(这里使用相对路径) //viper.AddConfigPath("./conf") // 指定查找配置文件的路径(这里使用相对路径) // 基本上是配合远程配置中心使用的,告诉viper当前的数据使用什么格式去解析 //viper.SetConfigType("json") viper.SetConfigFile(filePath) err = viper.ReadInConfig() // 读取配置信息 if err != nil { // 读取配置信息失败 fmt.Printf("viper.ReadInConfig failed, err:%v\n", err) return } // 把读取到的配置信息反序列化到 Conf 变量中 if err := viper.Unmarshal(Conf); err != nil { fmt.Printf("viper.Unmarshal failed, err:%v\n", err) } viper.WatchConfig() viper.OnConfigChange(func(in fsnotify.Event) { fmt.Println("配置文件修改了...") if err := viper.Unmarshal(Conf); err != nil { fmt.Printf("viper.Unmarshal failed, err:%v\n", err) } }) return } ================================================ FILE: lesson70/bluebell/.air.conf ================================================ # [Air](https://github.com/cosmtrek/air) TOML 格式的配置文件 # 工作目录 # 使用 . 或绝对路径,请注意 `tmp_dir` 目录必须在 `root` 目录下 root = "." tmp_dir = "tmp" [build] # 只需要写你平常编译使用的shell命令。你也可以使用 `make` cmd = "swag init && go build -o ./tmp/main" # 由`cmd`命令得到的二进制文件名 bin = "tmp/main" # 自定义的二进制,可以添加额外的编译标识例如添加 GIN_MODE=release full_bin = "./tmp/main ./conf/config.yaml" # 监听以下文件扩展名的文件. include_ext = ["go", "tpl", "tmpl", "html", "yaml"] # 忽略这些文件扩展名或目录 exclude_dir = ["assets", "tmp", "vendor", "frontend/node_modules"] # 监听以下指定目录的文件 include_dir = [] # 排除以下文件 exclude_file = [] # 如果文件更改过于频繁,则没有必要在每次更改时都触发构建。可以设置触发构建的延迟时间 delay = 1000 # ms # 发生构建错误时,停止运行旧的二进制文件。 stop_on_error = true # air的日志文件名,该日志文件放置在你的`tmp_dir`中 log = "air_errors.log" [log] # 显示日志时间 time = true [color] # 自定义每个部分显示的颜色。如果找不到颜色,使用原始的应用程序日志。 main = "magenta" watcher = "cyan" build = "yellow" runner = "green" [misc] # 退出时删除tmp目录 clean_on_exit = true ================================================ FILE: lesson70/bluebell/Makefile ================================================ .PHONY: all build run gotool clean help BINARY="xx" all: gotool build build: CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o ${BINARY} run: @go run ./main.go conf/config.yaml gotool: go fmt ./ go vet ./ clean: @if [ -f ${BINARY} ] ; then rm ${BINARY} ; fi help: @echo "make - 格式化 Go 代码, 并编译生成二进制文件" @echo "make build - 编译 Go 代码, 生成二进制文件" @echo "make run - 直接运行 Go 代码" @echo "make clean - 移除二进制文件和 vim swap files" @echo "make gotool - 运行 Go 工具 'fmt' and 'vet'" ================================================ FILE: lesson70/bluebell/conf/config.yaml ================================================ name: "bluebell" mode: "dev" port: 8084 version: "v0.0.1" start_time: "2020-07-01" machine_id: 1 auth: jwt_expire: 8760 log: level: "debug" filename: "web_app.log" max_size: 200 max_age: 30 max_backups: 7 mysql: host: "127.0.0.1" port: 13306 user: "root" password: "root1234" dbname: "bluebell" max_open_conns: 200 max_idle_conns: 50 redis: host: "127.0.0.1" port: 16379 password: "" db: 0 pool_size: 100 ================================================ FILE: lesson70/bluebell/controller/code.go ================================================ package controller type ResCode int64 const ( CodeSuccess ResCode = 1000 + iota CodeInvalidParam CodeUserExist CodeUserNotExist CodeInvalidPassword CodeServerBusy CodeNeedLogin CodeInvalidToken ) var codeMsgMap = map[ResCode]string{ CodeSuccess: "success", CodeInvalidParam: "请求参数错误", CodeUserExist: "用户名已存在", CodeUserNotExist: "用户名不存在", CodeInvalidPassword: "用户名或密码错误", CodeServerBusy: "服务繁忙", CodeNeedLogin: "需要登录", CodeInvalidToken: "无效的token", } func (c ResCode) Msg() string { msg, ok := codeMsgMap[c] if !ok { msg = codeMsgMap[CodeServerBusy] } return msg } ================================================ FILE: lesson70/bluebell/controller/community.go ================================================ package controller import ( "bluebell/logic" "strconv" "github.com/gin-gonic/gin" "go.uber.org/zap" ) // ---- 跟社区相关的 ---- func CommunityHandler(c *gin.Context) { // 查询到所有的社区(community_id, community_name) 以列表的形式返回 data, err := logic.GetCommunityList() if err != nil { zap.L().Error("logic.GetCommunityList() failed", zap.Error(err)) ResponseError(c, CodeServerBusy) // 不轻易把服务端报错暴露给外面 return } ResponseSuccess(c, data) } // CommunityDetailHandler 社区分类详情 func CommunityDetailHandler(c *gin.Context) { // 1. 获取社区id idStr := c.Param("id") // 获取URL参数 id, err := strconv.ParseInt(idStr, 10, 64) if err != nil { ResponseError(c, CodeInvalidParam) return } // 2. 根据id获取社区详情 data, err := logic.GetCommunityDetail(id) if err != nil { zap.L().Error("logic.GetCommunityList() failed", zap.Error(err)) ResponseError(c, CodeServerBusy) // 不轻易把服务端报错暴露给外面 return } ResponseSuccess(c, data) } ================================================ FILE: lesson70/bluebell/controller/doc_response_models.go ================================================ package controller import "bluebell/models" // 专门用来放接口文档用到的model // 因为我们的接口文档返回的数据格式是一致的,但是具体的data类型不一致 // _ResponsePostList 帖子列表接口响应数据 type _ResponsePostList struct { Code ResCode `json:"code"` // 业务响应状态码 Message string `json:"message"` // 提示信息 Data []*models.ApiPostDetail `json:"data"` // 数据 } ================================================ FILE: lesson70/bluebell/controller/post.go ================================================ package controller import ( "bluebell/logic" "bluebell/models" "strconv" "github.com/gin-gonic/gin" "go.uber.org/zap" // swagger 嵌入文件 ) // CreatePostHandler 创建帖子的处理函数 func CreatePostHandler(c *gin.Context) { // 1. 获取参数及参数的校验 //c.ShouldBindJSON() // validator --> binding tag p := new(models.Post) if err := c.ShouldBindJSON(p); err != nil { zap.L().Debug("c.ShouldBindJSON(p) error", zap.Any("err", err)) zap.L().Error("create post with invalid param") ResponseError(c, CodeInvalidParam) return } // 从 c 取到当前发请求的用户的ID userID, err := getCurrentUserID(c) if err != nil { ResponseError(c, CodeNeedLogin) return } p.AuthorID = userID // 2. 创建帖子 if err := logic.CreatePost(p); err != nil { zap.L().Error("logic.CreatePost(p) failed", zap.Error(err)) ResponseError(c, CodeServerBusy) return } // 3. 返回响应 ResponseSuccess(c, nil) } // GetPostDetailHandler 获取帖子详情的处理函数 func GetPostDetailHandler(c *gin.Context) { // 1. 获取参数(从URL中获取帖子的id) pidStr := c.Param("id") pid, err := strconv.ParseInt(pidStr, 10, 64) if err != nil { zap.L().Error("get post detail with invalid param", zap.Error(err)) ResponseError(c, CodeInvalidParam) return } // 2. 根据id取出帖子数据(查数据库) data, err := logic.GetPostById(pid) if err != nil { zap.L().Error("logic.GetPostById(pid) failed", zap.Error(err)) ResponseError(c, CodeServerBusy) return } // 3. 返回响应 ResponseSuccess(c, data) } // GetPostListHandler 获取帖子列表的处理函数 func GetPostListHandler(c *gin.Context) { // 获取分页参数 page, size := getPageInfo(c) // 获取数据 data, err := logic.GetPostList(page, size) if err != nil { zap.L().Error("logic.GetPostList() failed", zap.Error(err)) ResponseError(c, CodeServerBusy) return } ResponseSuccess(c, data) // 返回响应 } // GetPostListHandler2 升级版帖子列表接口 // @Summary 升级版帖子列表接口 // @Description 可按社区按时间或分数排序查询帖子列表接口 // @Tags 帖子相关接口(api分组展示使用的) // @Accept application/json // @Produce application/json // @Param Authorization header string true "Bearer JWT" // @Param object query models.ParamPostList false "查询参数" // @Security ApiKeyAuth // @Success 200 {object} _ResponsePostList // @Router /posts2 [get] func GetPostListHandler2(c *gin.Context) { // GET请求参数(query string):/api/v1/posts2?page=1&size=10&order=time // 初始化结构体时指定初始参数 p := &models.ParamPostList{ Page: 1, Size: 10, Order: models.OrderTime, // magic string } //c.ShouldBind() 根据请求的数据类型选择相应的方法去获取数据 //c.ShouldBindJSON() 如果请求中携带的是json格式的数据,才能用这个方法获取到数据 if err := c.ShouldBindQuery(p); err != nil { zap.L().Error("GetPostListHandler2 with invalid params", zap.Error(err)) ResponseError(c, CodeInvalidParam) return } data, err := logic.GetPostListNew(p) // 更新:合二为一 // 获取数据 if err != nil { zap.L().Error("logic.GetPostList() failed", zap.Error(err)) ResponseError(c, CodeServerBusy) return } ResponseSuccess(c, data) // 返回响应 } // 根据社区去查询帖子列表 //func GetCommunityPostListHandler(c *gin.Context) { // // 初始化结构体时指定初始参数 // p := &models.ParamCommunityPostList{ // ParamPostList: &models.ParamPostList{ // Page: 1, // Size: 10, // Order: models.OrderTime, // }, // } // //c.ShouldBind() 根据请求的数据类型选择相应的方法去获取数据 // //c.ShouldBindJSON() 如果请求中携带的是json格式的数据,才能用这个方法获取到数据 // if err := c.ShouldBindQuery(p); err != nil { // zap.L().Error("GetCommunityPostListHandler with invalid params", zap.Error(err)) // ResponseError(c, CodeInvalidParam) // return // } // // // 获取数据 // data, err := logic.GetCommunityPostList(p) // if err != nil { // zap.L().Error("logic.GetPostList() failed", zap.Error(err)) // ResponseError(c, CodeServerBusy) // return // } // ResponseSuccess(c, data) //} ================================================ FILE: lesson70/bluebell/controller/post_test.go ================================================ package controller import ( "bytes" "encoding/json" "net/http" "net/http/httptest" "testing" "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" ) func TestCreatePostHandler(t *testing.T) { gin.SetMode(gin.TestMode) r := gin.Default() url := "/api/v1/post" r.POST(url, CreatePostHandler) body := `{ "community_id": 1, "title": "test", "content": "just a test" }` req, _ := http.NewRequest(http.MethodPost, url, bytes.NewReader([]byte(body))) w := httptest.NewRecorder() r.ServeHTTP(w, req) assert.Equal(t, 200, w.Code) // 判断响应的内容是不是按预期返回了需要登录的错误 // 方法1:判断响应内容中是不是包含指定的字符串 //assert.Contains(t, w.Body.String(), "需要登录") // 方法2:将响应的内容反序列化到ResponseData 然后判断字段与预期是否一致 res := new(ResponseData) if err := json.Unmarshal(w.Body.Bytes(), res); err != nil { t.Fatalf("json.Unmarshal w.Body failed, err:%v\n", err) } assert.Equal(t, res.Code, CodeNeedLogin) } ================================================ FILE: lesson70/bluebell/controller/request.go ================================================ package controller import ( "errors" "strconv" "github.com/gin-gonic/gin" ) const CtxUserIDKey = "userID" var ErrorUserNotLogin = errors.New("用户未登录") // getCurrentUserID 获取当前登录的用户ID func getCurrentUserID(c *gin.Context) (userID int64, err error) { uid, ok := c.Get(CtxUserIDKey) if !ok { err = ErrorUserNotLogin return } userID, ok = uid.(int64) if !ok { err = ErrorUserNotLogin return } return } func getPageInfo(c *gin.Context) (int64, int64) { pageStr := c.Query("page") sizeStr := c.Query("size") var ( page int64 size int64 err error ) page, err = strconv.ParseInt(pageStr, 10, 64) if err != nil { page = 1 } size, err = strconv.ParseInt(sizeStr, 10, 64) if err != nil { size = 10 } return page, size } ================================================ FILE: lesson70/bluebell/controller/response.go ================================================ package controller import ( "net/http" "github.com/gin-gonic/gin" ) /* { "code": 10000, // 程序中的错误码 "msg": xx, // 提示信息 "data": {}, // 数据 } */ type ResponseData struct { Code ResCode `json:"code"` Msg interface{} `json:"msg"` Data interface{} `json:"data,omitempty"` } func ResponseError(c *gin.Context, code ResCode) { c.JSON(http.StatusOK, &ResponseData{ Code: code, Msg: code.Msg(), Data: nil, }) } func ResponseErrorWithMsg(c *gin.Context, code ResCode, msg interface{}) { c.JSON(http.StatusOK, &ResponseData{ Code: code, Msg: msg, Data: nil, }) } func ResponseSuccess(c *gin.Context, data interface{}) { c.JSON(http.StatusOK, &ResponseData{ Code: CodeSuccess, Msg: CodeSuccess.Msg(), Data: data, }) } ================================================ FILE: lesson70/bluebell/controller/user.go ================================================ package controller import ( "bluebell/dao/mysql" "bluebell/logic" "bluebell/models" "errors" "fmt" "github.com/go-playground/validator/v10" "go.uber.org/zap" "github.com/gin-gonic/gin" ) // SignUpHandler 处理注册请求的函数 func SignUpHandler(c *gin.Context) { // 1. 获取参数和参数校验 p := new(models.ParamSignUp) if err := c.ShouldBindJSON(p); err != nil { // 请求参数有误,直接返回响应 zap.L().Error("SignUp with invalid param", zap.Error(err)) // 判断err是不是validator.ValidationErrors 类型 errs, ok := err.(validator.ValidationErrors) if !ok { ResponseError(c, CodeInvalidParam) return } ResponseErrorWithMsg(c, CodeInvalidParam, removeTopStruct(errs.Translate(trans))) return } // 2. 业务处理 if err := logic.SignUp(p); err != nil { zap.L().Error("logic.SignUp failed", zap.Error(err)) if errors.Is(err, mysql.ErrorUserExist) { ResponseError(c, CodeUserExist) return } ResponseError(c, CodeServerBusy) return } // 3. 返回响应 ResponseSuccess(c, nil) } func LoginHandler(c *gin.Context) { // 1.获取请求参数及参数校验 p := new(models.ParamLogin) if err := c.ShouldBindJSON(p); err != nil { // 请求参数有误,直接返回响应 zap.L().Error("Login with invalid param", zap.Error(err)) // 判断err是不是validator.ValidationErrors 类型 errs, ok := err.(validator.ValidationErrors) if !ok { ResponseError(c, CodeInvalidParam) return } ResponseErrorWithMsg(c, CodeInvalidParam, removeTopStruct(errs.Translate(trans))) return } // 2.业务逻辑处理 user, err := logic.Login(p) if err != nil { zap.L().Error("logic.Login failed", zap.String("username", p.Username), zap.Error(err)) if errors.Is(err, mysql.ErrorUserNotExist) { ResponseError(c, CodeUserNotExist) return } ResponseError(c, CodeInvalidPassword) return } // 3.返回响应 ResponseSuccess(c, gin.H{ "user_id": fmt.Sprintf("%d", user.UserID), // id值大于1<<53-1 int64类型的最大值是1<<63-1 "user_name": user.Username, "token": user.Token, }) } ================================================ FILE: lesson70/bluebell/controller/validator.go ================================================ package controller import ( "bluebell/models" "fmt" "reflect" "strings" "github.com/gin-gonic/gin/binding" "github.com/go-playground/locales/en" "github.com/go-playground/locales/zh" ut "github.com/go-playground/universal-translator" "github.com/go-playground/validator/v10" enTranslations "github.com/go-playground/validator/v10/translations/en" zhTranslations "github.com/go-playground/validator/v10/translations/zh" ) // 定义一个全局翻译器T var trans ut.Translator // InitTrans 初始化翻译器 func InitTrans(locale string) (err error) { // 修改gin框架中的Validator引擎属性,实现自定制 if v, ok := binding.Validator.Engine().(*validator.Validate); ok { // 注册一个获取json tag的自定义方法 v.RegisterTagNameFunc(func(fld reflect.StructField) string { name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0] if name == "-" { return "" } return name }) // 为SignUpParam注册自定义校验方法 v.RegisterStructValidation(SignUpParamStructLevelValidation, models.ParamSignUp{}) zhT := zh.New() // 中文翻译器 enT := en.New() // 英文翻译器 // 第一个参数是备用(fallback)的语言环境 // 后面的参数是应该支持的语言环境(支持多个) // uni := ut.New(zhT, zhT) 也是可以的 uni := ut.New(enT, zhT, enT) // locale 通常取决于 http 请求头的 'Accept-Language' var ok bool // 也可以使用 uni.FindTranslator(...) 传入多个locale进行查找 trans, ok = uni.GetTranslator(locale) if !ok { return fmt.Errorf("uni.GetTranslator(%s) failed", locale) } // 注册翻译器 switch locale { case "en": err = enTranslations.RegisterDefaultTranslations(v, trans) case "zh": err = zhTranslations.RegisterDefaultTranslations(v, trans) default: err = enTranslations.RegisterDefaultTranslations(v, trans) } return } return } // removeTopStruct 去除提示信息中的结构体名称 func removeTopStruct(fields map[string]string) map[string]string { res := map[string]string{} for field, err := range fields { res[field[strings.Index(field, ".")+1:]] = err } return res } // SignUpParamStructLevelValidation 自定义SignUpParam结构体校验函数 func SignUpParamStructLevelValidation(sl validator.StructLevel) { su := sl.Current().Interface().(models.ParamSignUp) if su.Password != su.RePassword { // 输出错误提示信息,最后一个参数就是传递的param sl.ReportError(su.RePassword, "re_password", "RePassword", "eqfield", "password") } } ================================================ FILE: lesson70/bluebell/controller/vote.go ================================================ package controller import ( "bluebell/logic" "bluebell/models" "go.uber.org/zap" "github.com/go-playground/validator/v10" "github.com/gin-gonic/gin" ) // 投票 //type VoteData struct { // // UserID 从请求中获取当前的用户 // PostID int64 `json:"post_id,string"` // 贴子id // Direction int `json:"direction,string"` // 赞成票(1)还是反对票(-1) //} func PostVoteController(c *gin.Context) { // 参数校验 p := new(models.ParamVoteData) if err := c.ShouldBindJSON(p); err != nil { errs, ok := err.(validator.ValidationErrors) // 类型断言 if !ok { ResponseError(c, CodeInvalidParam) return } errData := removeTopStruct(errs.Translate(trans)) // 翻译并去除掉错误提示中的结构体标识 ResponseErrorWithMsg(c, CodeInvalidParam, errData) return } // 获取当前请求的用户的id userID, err := getCurrentUserID(c) if err != nil { ResponseError(c, CodeNeedLogin) return } // 具体投票的业务逻辑 if err := logic.VoteForPost(userID, p); err != nil { zap.L().Error("logic.VoteForPost() failed", zap.Error(err)) ResponseError(c, CodeServerBusy) return } ResponseSuccess(c, nil) } ================================================ FILE: lesson70/bluebell/dao/mysql/community.go ================================================ package mysql import ( "bluebell/models" "database/sql" "go.uber.org/zap" ) func GetCommunityList() (communityList []*models.Community, err error) { sqlStr := "select community_id, community_name from community" if err := db.Select(&communityList, sqlStr); err != nil { if err == sql.ErrNoRows { zap.L().Warn("there is no community in db") err = nil } } return } // GetCommunityDetailByID 根据ID查询社区详情 func GetCommunityDetailByID(id int64) (community *models.CommunityDetail, err error) { community = new(models.CommunityDetail) sqlStr := `select community_id, community_name, introduction, create_time from community where community_id = ? ` if err := db.Get(community, sqlStr, id); err != nil { if err == sql.ErrNoRows { err = ErrorInvalidID } } return community, err } ================================================ FILE: lesson70/bluebell/dao/mysql/error_code.go ================================================ package mysql import "errors" var ( ErrorUserExist = errors.New("用户已存在") ErrorUserNotExist = errors.New("用户不存在") ErrorInvalidPassword = errors.New("用户名或密码错误") ErrorInvalidID = errors.New("无效的ID") ) ================================================ FILE: lesson70/bluebell/dao/mysql/mysql.go ================================================ package mysql import ( "bluebell/setting" "fmt" _ "github.com/go-sql-driver/mysql" "github.com/jmoiron/sqlx" ) var db *sqlx.DB // Init 初始化MySQL连接 func Init(cfg *setting.MySQLConfig) (err error) { // "user:password@tcp(host:port)/dbname" dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?parseTime=true&loc=Local", cfg.User, cfg.Password, cfg.Host, cfg.Port, cfg.DB) db, err = sqlx.Connect("mysql", dsn) if err != nil { return } db.SetMaxOpenConns(cfg.MaxOpenConns) db.SetMaxIdleConns(cfg.MaxIdleConns) return } // Close 关闭MySQL连接 func Close() { _ = db.Close() } ================================================ FILE: lesson70/bluebell/dao/mysql/post.go ================================================ package mysql import ( "bluebell/models" "strings" "github.com/jmoiron/sqlx" ) // CreatePost 创建帖子 func CreatePost(p *models.Post) (err error) { sqlStr := `insert into post( post_id, title, content, author_id, community_id) values (?, ?, ?, ?, ?) ` _, err = db.Exec(sqlStr, p.ID, p.Title, p.Content, p.AuthorID, p.CommunityID) return } // GetPostById 根据id查询单个贴子数据 func GetPostById(pid int64) (post *models.Post, err error) { post = new(models.Post) sqlStr := `select post_id, title, content, author_id, community_id, create_time from post where post_id = ? ` err = db.Get(post, sqlStr, pid) return } // GetPostList 查询帖子列表函数 func GetPostList(page, size int64) (posts []*models.Post, err error) { sqlStr := `select post_id, title, content, author_id, community_id, create_time from post ORDER BY create_time DESC limit ?,? ` posts = make([]*models.Post, 0, 2) // 不要写成make([]*models.Post, 2) err = db.Select(&posts, sqlStr, (page-1)*size, size) return } // GetPostListByIDs 根据给定的id列表查询帖子数据 func GetPostListByIDs(ids []string) (postList []*models.Post, err error) { sqlStr := `select post_id, title, content, author_id, community_id, create_time from post where post_id in (?) order by FIND_IN_SET(post_id, ?) ` // https: //www.liwenzhou.com/posts/Go/sqlx/ query, args, err := sqlx.In(sqlStr, ids, strings.Join(ids, ",")) if err != nil { return nil, err } query = db.Rebind(query) err = db.Select(&postList, query, args...) // !!!!!! return } ================================================ FILE: lesson70/bluebell/dao/mysql/post_test.go ================================================ package mysql import ( "bluebell/models" "bluebell/setting" "testing" ) func init() { dbCfg := setting.MySQLConfig{ Host: "127.0.0.1", User: "root", Password: "root1234", DB: "bluebell", Port: 13306, MaxOpenConns: 10, MaxIdleConns: 10, } err := Init(&dbCfg) if err != nil { panic(err) } } func TestCreatePost(t *testing.T) { post := models.Post{ ID: 10, AuthorID: 123, CommunityID: 1, Title: "test", Content: "just a test", } err := CreatePost(&post) if err != nil { t.Fatalf("CreatePost insert record into mysql failed, err:%v\n", err) } t.Logf("CreatePost insert record into mysql success") } ================================================ FILE: lesson70/bluebell/dao/mysql/user.go ================================================ package mysql import ( "bluebell/models" "crypto/md5" "database/sql" "encoding/hex" ) // 把每一步数据库操作封装成函数 // 待logic层根据业务需求调用 const secret = "liwenzhou.com" // CheckUserExist 检查指定用户名的用户是否存在 func CheckUserExist(username string) (err error) { sqlStr := `select count(user_id) from user where username = ?` var count int64 if err := db.Get(&count, sqlStr, username); err != nil { return err } if count > 0 { return ErrorUserExist } return } // InsertUser 想数据库中插入一条新的用户记录 func InsertUser(user *models.User) (err error) { // 对密码进行加密 user.Password = encryptPassword(user.Password) // 执行SQL语句入库 sqlStr := `insert into user(user_id, username, password) values(?,?,?)` _, err = db.Exec(sqlStr, user.UserID, user.Username, user.Password) return } // encryptPassword 密码加密 func encryptPassword(oPassword string) string { h := md5.New() h.Write([]byte(secret)) return hex.EncodeToString(h.Sum([]byte(oPassword))) } func Login(user *models.User) (err error) { oPassword := user.Password // 用户登录的密码 sqlStr := `select user_id, username, password from user where username=?` err = db.Get(user, sqlStr, user.Username) if err == sql.ErrNoRows { return ErrorUserNotExist } if err != nil { // 查询数据库失败 return err } // 判断密码是否正确 password := encryptPassword(oPassword) if password != user.Password { return ErrorInvalidPassword } return } // GetUserById 根据id获取用户信息 func GetUserById(uid int64) (user *models.User, err error) { user = new(models.User) sqlStr := `select user_id, username from user where user_id = ?` err = db.Get(user, sqlStr, uid) return } ================================================ FILE: lesson70/bluebell/dao/redis/keys.go ================================================ package redis // redis key // redis key注意使用命名空间的方式,方便查询和拆分 const ( Prefix = "bluebell:" // 项目key前缀 KeyPostTimeZSet = "post:time" // zset;贴子及发帖时间 KeyPostScoreZSet = "post:score" // zset;贴子及投票的分数 KeyPostVotedZSetPF = "post:voted:" // zset;记录用户及投票类型;参数是post id KeyCommunitySetPF = "community:" // set;保存每个分区下帖子的id ) // 给redis key加上前缀 func getRedisKey(key string) string { return Prefix + key } ================================================ FILE: lesson70/bluebell/dao/redis/post.go ================================================ package redis import ( "bluebell/models" "strconv" "time" "github.com/go-redis/redis" ) func getIDsFormKey(key string, page, size int64) ([]string, error) { start := (page - 1) * size end := start + size - 1 // 3. ZREVRANGE 按分数从大到小的顺序查询指定数量的元素 return client.ZRevRange(key, start, end).Result() } func GetPostIDsInOrder(p *models.ParamPostList) ([]string, error) { // 从redis获取id // 1. 根据用户请求中携带的order参数确定要查询的redis key key := getRedisKey(KeyPostTimeZSet) if p.Order == models.OrderScore { key = getRedisKey(KeyPostScoreZSet) } // 2. 确定查询的索引起始点 return getIDsFormKey(key, p.Page, p.Size) } // GetPostVoteData 根据ids查询每篇帖子的投赞成票的数据 func GetPostVoteData(ids []string) (data []int64, err error) { //data = make([]int64, 0, len(ids)) //for _, id := range ids { // key := getRedisKey(KeyPostVotedZSetPF + id) // // 查找key中分数是1的元素的数量->统计每篇帖子的赞成票的数量 // v := client.ZCount(key, "1", "1").Val() // data = append(data, v) //} // 使用pipeline一次发送多条命令,减少RTT pipeline := client.Pipeline() for _, id := range ids { key := getRedisKey(KeyPostVotedZSetPF + id) pipeline.ZCount(key, "1", "1") } cmders, err := pipeline.Exec() if err != nil { return nil, err } data = make([]int64, 0, len(cmders)) for _, cmder := range cmders { v := cmder.(*redis.IntCmd).Val() data = append(data, v) } return } // GetCommunityPostIDsInOrder 按社区查询ids func GetCommunityPostIDsInOrder(p *models.ParamPostList) ([]string, error) { orderKey := getRedisKey(KeyPostTimeZSet) if p.Order == models.OrderScore { orderKey = getRedisKey(KeyPostScoreZSet) } // 使用 zinterstore 把分区的帖子set与帖子分数的 zset 生成一个新的zset // 针对新的zset 按之前的逻辑取数据 // 社区的key cKey := getRedisKey(KeyCommunitySetPF + strconv.Itoa(int(p.CommunityID))) // 利用缓存key减少zinterstore执行的次数 key := orderKey + strconv.Itoa(int(p.CommunityID)) if client.Exists(key).Val() < 1 { // 不存在,需要计算 pipeline := client.Pipeline() pipeline.ZInterStore(key, redis.ZStore{ Aggregate: "MAX", }, cKey, orderKey) // zinterstore 计算 pipeline.Expire(key, 60*time.Second) // 设置超时时间 _, err := pipeline.Exec() if err != nil { return nil, err } } // 存在的话就直接根据key查询ids return getIDsFormKey(key, p.Page, p.Size) } ================================================ FILE: lesson70/bluebell/dao/redis/redis.go ================================================ package redis import ( "bluebell/setting" "fmt" "github.com/go-redis/redis" ) var ( client *redis.Client Nil = redis.Nil ) // Init 初始化连接 func Init(cfg *setting.RedisConfig) (err error) { client = redis.NewClient(&redis.Options{ Addr: fmt.Sprintf("%s:%d", cfg.Host, cfg.Port), Password: cfg.Password, // no password set DB: cfg.DB, // use default DB PoolSize: cfg.PoolSize, MinIdleConns: cfg.MinIdleConns, }) _, err = client.Ping().Result() if err != nil { return err } return nil } func Close() { _ = client.Close() } ================================================ FILE: lesson70/bluebell/dao/redis/vote.go ================================================ package redis import ( "errors" "math" "strconv" "time" "github.com/go-redis/redis" ) // 推荐阅读 // 基于用户投票的相关算法:http://www.ruanyifeng.com/blog/algorithm/ // 本项目使用简化版的投票分数 // 投一票就加432分 86400/200 --> 200张赞成票可以给你的帖子续一天 /* 投票的几种情况: direction=1时,有两种情况: 1. 之前没有投过票,现在投赞成票 --> 更新分数和投票记录 差值的绝对值:1 +432 2. 之前投反对票,现在改投赞成票 --> 更新分数和投票记录 差值的绝对值:2 +432*2 direction=0时,有两种情况: 1. 之前投过反对票,现在要取消投票 --> 更新分数和投票记录 差值的绝对值:1 +432 2. 之前投过赞成票,现在要取消投票 --> 更新分数和投票记录 差值的绝对值:1 -432 direction=-1时,有两种情况: 1. 之前没有投过票,现在投反对票 --> 更新分数和投票记录 差值的绝对值:1 -432 2. 之前投赞成票,现在改投反对票 --> 更新分数和投票记录 差值的绝对值:2 -432*2 投票的限制: 每个贴子自发表之日起一个星期之内允许用户投票,超过一个星期就不允许再投票了。 1. 到期之后将redis中保存的赞成票数及反对票数存储到mysql表中 2. 到期之后删除那个 KeyPostVotedZSetPF */ const ( oneWeekInSeconds = 7 * 24 * 3600 scorePerVote = 432 // 每一票值多少分 ) var ( ErrVoteTimeExpire = errors.New("投票时间已过") ErrVoteRepeated = errors.New("不允许重复投票") ) func CreatePost(postID, communityID int64) error { pipeline := client.TxPipeline() // 帖子时间 pipeline.ZAdd(getRedisKey(KeyPostTimeZSet), redis.Z{ Score: float64(time.Now().Unix()), Member: postID, }) // 帖子分数 pipeline.ZAdd(getRedisKey(KeyPostScoreZSet), redis.Z{ Score: float64(time.Now().Unix()), Member: postID, }) // 更新:把帖子id加到社区的set cKey := getRedisKey(KeyCommunitySetPF + strconv.Itoa(int(communityID))) pipeline.SAdd(cKey, postID) _, err := pipeline.Exec() return err } func VoteForPost(userID, postID string, value float64) error { // 1. 判断投票限制 // 去redis取帖子发布时间 postTime := client.ZScore(getRedisKey(KeyPostTimeZSet), postID).Val() if float64(time.Now().Unix())-postTime > oneWeekInSeconds { return ErrVoteTimeExpire } // 2和3需要放到一个pipeline事务中操作 // 2. 更新贴子的分数 // 先查当前用户给当前帖子的投票记录 ov := client.ZScore(getRedisKey(KeyPostVotedZSetPF+postID), userID).Val() // 更新:如果这一次投票的值和之前保存的值一致,就提示不允许重复投票 if value == ov { return ErrVoteRepeated } var op float64 if value > ov { op = 1 } else { op = -1 } diff := math.Abs(ov - value) // 计算两次投票的差值 pipeline := client.TxPipeline() pipeline.ZIncrBy(getRedisKey(KeyPostScoreZSet), op*diff*scorePerVote, postID) // 3. 记录用户为该贴子投票的数据 if value == 0 { pipeline.ZRem(getRedisKey(KeyPostVotedZSetPF+postID), userID) } else { pipeline.ZAdd(getRedisKey(KeyPostVotedZSetPF+postID), redis.Z{ Score: value, // 赞成票还是反对票 Member: userID, }) } _, err := pipeline.Exec() return err } ================================================ FILE: lesson70/bluebell/docs/docs.go ================================================ // GENERATED BY THE COMMAND ABOVE; DO NOT EDIT // This file was generated by swaggo/swag package docs import ( "bytes" "encoding/json" "strings" "github.com/alecthomas/template" "github.com/swaggo/swag" ) var doc = `{ "schemes": {{ marshal .Schemes }}, "swagger": "2.0", "info": { "description": "{{.Description}}", "title": "{{.Title}}", "contact": { "name": "liwenzhou", "url": "http://www.liwenzhou.com" }, "license": {}, "version": "{{.Version}}" }, "host": "{{.Host}}", "basePath": "{{.BasePath}}", "paths": { "/posts2": { "get": { "security": [ { "ApiKeyAuth": [] } ], "description": "可按社区按时间或分数排序查询帖子列表接口", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "帖子相关接口(api分组展示使用的)" ], "summary": "升级版帖子列表接口", "parameters": [ { "type": "string", "description": "Bearer JWT", "name": "Authorization", "in": "header", "required": true }, { "type": "integer", "description": "可以为空", "name": "community_id", "in": "query" }, { "type": "string", "example": "score", "description": "排序依据", "name": "order", "in": "query" }, { "type": "integer", "description": "页码", "name": "page", "in": "query" }, { "type": "integer", "description": "每页数据量", "name": "size", "in": "query" } ], "responses": { "200": { "description": "OK", "schema": { "$ref": "#/definitions/controller._ResponsePostList" } } } } } }, "definitions": { "controller._ResponsePostList": { "type": "object", "properties": { "code": { "description": "业务响应状态码", "type": "integer" }, "data": { "description": "数据", "type": "array", "items": { "$ref": "#/definitions/models.ApiPostDetail" } }, "message": { "description": "提示信息", "type": "string" } } }, "models.ApiPostDetail": { "type": "object", "required": [ "community_id", "content", "title" ], "properties": { "author_id": { "description": "作者id", "type": "integer" }, "author_name": { "description": "作者", "type": "string" }, "community_id": { "description": "社区id", "type": "integer" }, "content": { "description": "帖子内容", "type": "string" }, "create_time": { "type": "string" }, "id": { "type": "integer" }, "introduction": { "type": "string" }, "name": { "type": "string" }, "status": { "description": "帖子状态", "type": "integer" }, "title": { "description": "帖子标题", "type": "string" }, "vote_num": { "description": "投票数", "type": "integer" } } }, "models.ParamPostList": { "type": "object", "properties": { "community_id": { "description": "可以为空", "type": "integer" }, "order": { "description": "排序依据", "type": "string", "example": "score" }, "page": { "description": "页码", "type": "integer" }, "size": { "description": "每页数据量", "type": "integer" } } } } }` type swaggerInfo struct { Version string Host string BasePath string Schemes []string Title string Description string } // SwaggerInfo holds exported Swagger Info so clients can modify it var SwaggerInfo = swaggerInfo{ Version: "1.0", Host: "127.0.0.1:8084", BasePath: "/api/v1", Schemes: []string{}, Title: "bluebell项目接口文档", Description: "Go web开发进阶项目实战课程bluebell", } type s struct{} func (s *s) ReadDoc() string { sInfo := SwaggerInfo sInfo.Description = strings.Replace(sInfo.Description, "\n", "\\n", -1) t, err := template.New("swagger_info").Funcs(template.FuncMap{ "marshal": func(v interface{}) string { a, _ := json.Marshal(v) return string(a) }, }).Parse(doc) if err != nil { return doc } var tpl bytes.Buffer if err := t.Execute(&tpl, sInfo); err != nil { return doc } return tpl.String() } func init() { swag.Register(swag.Name, &s{}) } ================================================ FILE: lesson70/bluebell/docs/swagger.json ================================================ { "swagger": "2.0", "info": { "description": "Go web开发进阶项目实战课程bluebell", "title": "bluebell项目接口文档", "contact": { "name": "liwenzhou", "url": "http://www.liwenzhou.com" }, "license": {}, "version": "1.0" }, "host": "127.0.0.1:8084", "basePath": "/api/v1", "paths": { "/posts2": { "get": { "security": [ { "ApiKeyAuth": [] } ], "description": "可按社区按时间或分数排序查询帖子列表接口", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "帖子相关接口(api分组展示使用的)" ], "summary": "升级版帖子列表接口", "parameters": [ { "type": "string", "description": "Bearer JWT", "name": "Authorization", "in": "header", "required": true }, { "type": "integer", "description": "可以为空", "name": "community_id", "in": "query" }, { "type": "string", "example": "score", "description": "排序依据", "name": "order", "in": "query" }, { "type": "integer", "description": "页码", "name": "page", "in": "query" }, { "type": "integer", "description": "每页数据量", "name": "size", "in": "query" } ], "responses": { "200": { "description": "OK", "schema": { "$ref": "#/definitions/controller._ResponsePostList" } } } } } }, "definitions": { "controller._ResponsePostList": { "type": "object", "properties": { "code": { "description": "业务响应状态码", "type": "integer" }, "data": { "description": "数据", "type": "array", "items": { "$ref": "#/definitions/models.ApiPostDetail" } }, "message": { "description": "提示信息", "type": "string" } } }, "models.ApiPostDetail": { "type": "object", "required": [ "community_id", "content", "title" ], "properties": { "author_id": { "description": "作者id", "type": "integer" }, "author_name": { "description": "作者", "type": "string" }, "community_id": { "description": "社区id", "type": "integer" }, "content": { "description": "帖子内容", "type": "string" }, "create_time": { "type": "string" }, "id": { "type": "integer" }, "introduction": { "type": "string" }, "name": { "type": "string" }, "status": { "description": "帖子状态", "type": "integer" }, "title": { "description": "帖子标题", "type": "string" }, "vote_num": { "description": "投票数", "type": "integer" } } }, "models.ParamPostList": { "type": "object", "properties": { "community_id": { "description": "可以为空", "type": "integer" }, "order": { "description": "排序依据", "type": "string", "example": "score" }, "page": { "description": "页码", "type": "integer" }, "size": { "description": "每页数据量", "type": "integer" } } } } } ================================================ FILE: lesson70/bluebell/docs/swagger.yaml ================================================ basePath: /api/v1 definitions: controller._ResponsePostList: properties: code: description: 业务响应状态码 type: integer data: description: 数据 items: $ref: '#/definitions/models.ApiPostDetail' type: array message: description: 提示信息 type: string type: object models.ApiPostDetail: properties: author_id: description: 作者id type: integer author_name: description: 作者 type: string community_id: description: 社区id type: integer content: description: 帖子内容 type: string create_time: type: string id: type: integer introduction: type: string name: type: string status: description: 帖子状态 type: integer title: description: 帖子标题 type: string vote_num: description: 投票数 type: integer required: - community_id - content - title type: object models.ParamPostList: properties: community_id: description: 可以为空 type: integer order: description: 排序依据 example: score type: string page: description: 页码 type: integer size: description: 每页数据量 type: integer type: object host: 127.0.0.1:8084 info: contact: name: liwenzhou url: http://www.liwenzhou.com description: Go web开发进阶项目实战课程bluebell license: {} title: bluebell项目接口文档 version: "1.0" paths: /posts2: get: consumes: - application/json description: 可按社区按时间或分数排序查询帖子列表接口 parameters: - description: Bearer JWT in: header name: Authorization required: true type: string - description: 可以为空 in: query name: community_id type: integer - description: 排序依据 example: score in: query name: order type: string - description: 页码 in: query name: page type: integer - description: 每页数据量 in: query name: size type: integer produces: - application/json responses: "200": description: OK schema: $ref: '#/definitions/controller._ResponsePostList' security: - ApiKeyAuth: [] summary: 升级版帖子列表接口 tags: - 帖子相关接口(api分组展示使用的) swagger: "2.0" ================================================ FILE: lesson70/bluebell/go.mod ================================================ module bluebell go 1.14 require ( github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 github.com/bwmarrin/snowflake v0.3.0 github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/fsnotify/fsnotify v1.4.9 github.com/gin-gonic/gin v1.6.3 github.com/go-openapi/spec v0.19.9 // indirect github.com/go-openapi/swag v0.19.9 // indirect github.com/go-playground/locales v0.13.0 github.com/go-playground/universal-translator v0.17.0 github.com/go-playground/validator/v10 v10.3.0 github.com/go-redis/redis v6.15.8+incompatible github.com/go-sql-driver/mysql v1.5.0 github.com/jmoiron/sqlx v1.2.0 github.com/json-iterator/go v1.1.10 // indirect github.com/mailru/easyjson v0.7.6 // indirect github.com/natefinch/lumberjack v2.0.0+incompatible github.com/onsi/ginkgo v1.14.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/sony/sonyflake v1.0.0 // indirect github.com/spf13/viper v1.7.0 github.com/stretchr/testify v1.4.0 github.com/swaggo/gin-swagger v1.2.0 github.com/swaggo/swag v1.6.7 github.com/urfave/cli/v2 v2.2.0 // indirect go.uber.org/zap v1.15.0 golang.org/x/net v0.0.0-20200904194848-62affa334b73 // indirect golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f // indirect golang.org/x/tools v0.0.0-20200904185747-39188db58858 // indirect google.golang.org/protobuf v1.25.0 // indirect gopkg.in/go-playground/assert.v1 v1.2.1 gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect ) ================================================ FILE: lesson70/bluebell/go.sum ================================================ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/PuerkitoBio/purell v1.1.0 h1:rmGxhojJlM0tuKtfdvliR84CFHljx9ag64t2xmVkjK4= github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0= github.com/bwmarrin/snowflake v0.3.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/E9WsDpxqwE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/deckarep/golang-set v1.7.1/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/gzip v0.0.1/go.mod h1:fGBJBCdt6qCZuCAOwWuFhBB4OOq9EFqlo5dEaFhhu5w= github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.3.0/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y= github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14= github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-openapi/jsonpointer v0.17.0 h1:nH6xp8XdXHx8dqveo0ZuJBluCO2qGrPbDNZ0dwoRHP0= github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= github.com/go-openapi/jsonreference v0.19.0 h1:BqWKpV1dFd+AuiKlgtddwVIFQsuMpxfBDBHGfM2yNpk= github.com/go-openapi/jsonreference v0.19.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= github.com/go-openapi/jsonreference v0.19.4 h1:3Vw+rh13uq2JFNxgnMTGE1rnoieU9FmyE1gvnyylsYg= github.com/go-openapi/jsonreference v0.19.4/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= github.com/go-openapi/spec v0.19.0 h1:A4SZ6IWh3lnjH0rG0Z5lkxazMGBECtrZcbyYQi+64k4= github.com/go-openapi/spec v0.19.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= github.com/go-openapi/spec v0.19.4/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= github.com/go-openapi/spec v0.19.9 h1:9z9cbFuZJ7AcvOHKIY+f6Aevb4vObNDkTEyoMfO7rAc= github.com/go-openapi/spec v0.19.9/go.mod h1:vqK/dIdLGCosfvYsQV3WfC7N3TiZSnGY2RZKoFK7X28= github.com/go-openapi/swag v0.17.0 h1:iqrgMg7Q7SvtbWLlltPrkMs0UBJI6oTSs79JFRUi880= github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.9 h1:1IxuqvBUU3S2Bi4YC7tlP9SJF1gVpCvqN0T2Qof4azE= github.com/go-openapi/swag v0.19.9/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY= github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= github.com/go-playground/validator/v10 v10.3.0 h1:nZU+7q+yJoFmwvNgv/LnPUkwPal62+b2xXj0AU1Es7o= github.com/go-playground/validator/v10 v10.3.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= github.com/go-redis/redis v6.15.8+incompatible h1:BKZuG6mCnRj5AOaWJXoCgf6rqTYnYJLe4en2hxT7r9o= github.com/go-redis/redis v6.15.8+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA= github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329 h1:2gxZ0XQIU/5z3Z3bUBu+FXuk2pFbkN6tcwi/pjyaDic= github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-sqlite3 v1.9.0 h1:pDRiWfl+++eC2FEFRy6jXmQlvp4Yh3z1MJKg4UeYM/4= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/natefinch/lumberjack v2.0.0+incompatible h1:4QJd3OLAMgj7ph+yZTuX13Ld4UpgHp07nNdFX7mqFfM= github.com/natefinch/lumberjack v2.0.0+incompatible/go.mod h1:Wi9p2TTF5DG5oU+6YfsmYQpsTIOm0B1VNzQg9Mw6nPk= github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/sony/sonyflake v1.0.0 h1:MpU6Ro7tfXwgn2l5eluf9xQvQJDROTBImNCfRXn/YeM= github.com/sony/sonyflake v1.0.0/go.mod h1:Jv3cfhf/UFtolOTTRd3q4Nl6ENqM+KfyZ5PseKfZGF4= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14 h1:PyYN9JH5jY9j6av01SpfRMb+1DWg/i3MbGOKPxJ2wjM= github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14/go.mod h1:gxQT6pBGRuIGunNf/+tSOB5OHvguWi8Tbt82WOkf35E= github.com/swaggo/gin-swagger v1.2.0 h1:YskZXEiv51fjOMTsXrOetAjrMDfFaXD79PEoQBOe2W0= github.com/swaggo/gin-swagger v1.2.0/go.mod h1:qlH2+W7zXGZkczuL+r2nEBR2JTT+/lX05Nn6vPhc7OI= github.com/swaggo/swag v1.5.1 h1:2Agm8I4K5qb00620mHq0VJ05/KT4FtmALPIcQR9lEZM= github.com/swaggo/swag v1.5.1/go.mod h1:1Bl9F/ZBpVWh22nY0zmYyASPO1lI/zIwRDrpZU+tv8Y= github.com/swaggo/swag v1.6.7 h1:e8GC2xDllJZr3omJkm9YfmK0Y56+rMO3cg0JBKNz09s= github.com/swaggo/swag v1.6.7/go.mod h1:xDhTyuFIujYiN3DKWC/H/83xcfHp+UE/IzWWampG7Zc= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go v1.1.5-pre/go.mod h1:FwP/aQVg39TXzItUBMwnWp9T9gPQnXw4Poh4/oBQZ/0= github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v0.0.0-20181022190402-e5e69e061d4f/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/ugorji/go/codec v1.1.5-pre/go.mod h1:tULtS6Gy1AE1yCENaw4Vb//HLH5njI2tfCQDUqRd8fI= github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli/v2 v2.1.1 h1:Qt8FeAtxE/vfdrLmR3rxR6JRE0RoVmbXu8+6kZtYU4k= github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= github.com/urfave/cli/v2 v2.2.0 h1:JTTnM6wKzdA0Jqodd966MVj4vWbbquZykeX1sKbe2C4= github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.15.0 h1:ZZCA22JRF2gQE5FoNmhmrf7jeJJ2uhqDUNRYKm8dvmM= go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190611141213-3f473d35a33a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 h1:AeiKBIuRw3UomYXSbLy0Mc2dDLfdtbT/IVn4keq83P0= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200904194848-62affa334b73 h1:MXfv8rhZWmFeqX3GNZRsd6vOLoaCHjYEX3qkRo3YBUA= golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 h1:DYfZAGf2WMFjMxbgTjaC+2HC7NkNAQs+6Q8b9WEB/F4= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f h1:Fqb3ao1hUmOR3GkUOg/Y+BadLwykBIzs5q8Ez2SbHyc= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606050223-4d9ae51c2468/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190611222205-d73e1c7e250b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc h1:NCy3Ohtk6Iny5V/reW2Ktypo4zIpWBdRJ1uFMjBxdg8= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200904185747-39188db58858 h1:xLt+iB5ksWcZVxqc+g9K41ZHy+6MKWfXCDsjSThnsPA= golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= ================================================ FILE: lesson70/bluebell/logger/logger.go ================================================ package logger import ( "bluebell/setting" "net" "net/http" "net/http/httputil" "os" "runtime/debug" "strings" "time" "github.com/gin-gonic/gin" "github.com/natefinch/lumberjack" "go.uber.org/zap" "go.uber.org/zap/zapcore" ) var lg *zap.Logger // Init 初始化lg func Init(cfg *setting.LogConfig, mode string) (err error) { writeSyncer := getLogWriter(cfg.Filename, cfg.MaxSize, cfg.MaxBackups, cfg.MaxAge) encoder := getEncoder() var l = new(zapcore.Level) err = l.UnmarshalText([]byte(cfg.Level)) if err != nil { return } var core zapcore.Core if mode == "dev" { // 进入开发模式,日志输出到终端 consoleEncoder := zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig()) core = zapcore.NewTee( zapcore.NewCore(encoder, writeSyncer, l), zapcore.NewCore(consoleEncoder, zapcore.Lock(os.Stdout), zapcore.DebugLevel), ) } else { core = zapcore.NewCore(encoder, writeSyncer, l) } lg = zap.New(core, zap.AddCaller()) zap.ReplaceGlobals(lg) zap.L().Info("init logger success") return } func getEncoder() zapcore.Encoder { encoderConfig := zap.NewProductionEncoderConfig() encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder encoderConfig.TimeKey = "time" encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder encoderConfig.EncodeDuration = zapcore.SecondsDurationEncoder encoderConfig.EncodeCaller = zapcore.ShortCallerEncoder return zapcore.NewJSONEncoder(encoderConfig) } func getLogWriter(filename string, maxSize, maxBackup, maxAge int) zapcore.WriteSyncer { lumberJackLogger := &lumberjack.Logger{ Filename: filename, MaxSize: maxSize, MaxBackups: maxBackup, MaxAge: maxAge, } return zapcore.AddSync(lumberJackLogger) } // GinLogger 接收gin框架默认的日志 func GinLogger() gin.HandlerFunc { return func(c *gin.Context) { start := time.Now() path := c.Request.URL.Path query := c.Request.URL.RawQuery c.Next() cost := time.Since(start) lg.Info(path, zap.Int("status", c.Writer.Status()), zap.String("method", c.Request.Method), zap.String("path", path), zap.String("query", query), zap.String("ip", c.ClientIP()), zap.String("user-agent", c.Request.UserAgent()), zap.String("errors", c.Errors.ByType(gin.ErrorTypePrivate).String()), zap.Duration("cost", cost), ) } } // GinRecovery recover掉项目可能出现的panic,并使用zap记录相关日志 func GinRecovery(stack bool) gin.HandlerFunc { return func(c *gin.Context) { defer func() { if err := recover(); err != nil { // Check for a broken connection, as it is not really a // condition that warrants a panic stack trace. var brokenPipe bool if ne, ok := err.(*net.OpError); ok { if se, ok := ne.Err.(*os.SyscallError); ok { if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") { brokenPipe = true } } } httpRequest, _ := httputil.DumpRequest(c.Request, false) if brokenPipe { lg.Error(c.Request.URL.Path, zap.Any("error", err), zap.String("request", string(httpRequest)), ) // If the connection is dead, we can't write a status to it. c.Error(err.(error)) // nolint: errcheck c.Abort() return } if stack { lg.Error("[Recovery from panic]", zap.Any("error", err), zap.String("request", string(httpRequest)), zap.String("stack", string(debug.Stack())), ) } else { lg.Error("[Recovery from panic]", zap.Any("error", err), zap.String("request", string(httpRequest)), ) } c.AbortWithStatus(http.StatusInternalServerError) } }() c.Next() } } ================================================ FILE: lesson70/bluebell/logic/community.go ================================================ package logic import ( "bluebell/dao/mysql" "bluebell/models" ) func GetCommunityList() ([]*models.Community, error) { // 查数据库 查找到所有的community 并返回 return mysql.GetCommunityList() } func GetCommunityDetail(id int64) (*models.CommunityDetail, error) { return mysql.GetCommunityDetailByID(id) } ================================================ FILE: lesson70/bluebell/logic/post.go ================================================ package logic import ( "bluebell/dao/mysql" "bluebell/dao/redis" "bluebell/models" "bluebell/pkg/snowflake" "go.uber.org/zap" ) func CreatePost(p *models.Post) (err error) { // 1. 生成post id p.ID = snowflake.GenID() // 2. 保存到数据库 err = mysql.CreatePost(p) if err != nil { return err } err = redis.CreatePost(p.ID, p.CommunityID) return // 3. 返回 } // GetPostById 根据帖子id查询帖子详情数据 func GetPostById(pid int64) (data *models.ApiPostDetail, err error) { // 查询并组合我们接口想用的数据 post, err := mysql.GetPostById(pid) if err != nil { zap.L().Error("mysql.GetPostById(pid) failed", zap.Int64("pid", pid), zap.Error(err)) return } // 根据作者id查询作者信息 user, err := mysql.GetUserById(post.AuthorID) if err != nil { zap.L().Error("mysql.GetUserById(post.AuthorID) failed", zap.Int64("author_id", post.AuthorID), zap.Error(err)) return } // 根据社区id查询社区详细信息 community, err := mysql.GetCommunityDetailByID(post.CommunityID) if err != nil { zap.L().Error("mysql.GetUserById(post.AuthorID) failed", zap.Int64("community_id", post.CommunityID), zap.Error(err)) return } // 接口数据拼接 data = &models.ApiPostDetail{ AuthorName: user.Username, Post: post, CommunityDetail: community, } return } // GetPostList 获取帖子列表 func GetPostList(page, size int64) (data []*models.ApiPostDetail, err error) { posts, err := mysql.GetPostList(page, size) if err != nil { return nil, err } data = make([]*models.ApiPostDetail, 0, len(posts)) for _, post := range posts { // 根据作者id查询作者信息 user, err := mysql.GetUserById(post.AuthorID) if err != nil { zap.L().Error("mysql.GetUserById(post.AuthorID) failed", zap.Int64("author_id", post.AuthorID), zap.Error(err)) continue } // 根据社区id查询社区详细信息 community, err := mysql.GetCommunityDetailByID(post.CommunityID) if err != nil { zap.L().Error("mysql.GetUserById(post.AuthorID) failed", zap.Int64("community_id", post.CommunityID), zap.Error(err)) continue } postDetail := &models.ApiPostDetail{ AuthorName: user.Username, Post: post, CommunityDetail: community, } data = append(data, postDetail) } return } func GetPostList2(p *models.ParamPostList) (data []*models.ApiPostDetail, err error) { // 2. 去redis查询id列表 ids, err := redis.GetPostIDsInOrder(p) if err != nil { return } if len(ids) == 0 { zap.L().Warn("redis.GetPostIDsInOrder(p) return 0 data") return } zap.L().Debug("GetPostList2", zap.Any("ids", ids)) // 3. 根据id去MySQL数据库查询帖子详细信息 // 返回的数据还要按照我给定的id的顺序返回 posts, err := mysql.GetPostListByIDs(ids) if err != nil { return } zap.L().Debug("GetPostList2", zap.Any("posts", posts)) // 提前查询好每篇帖子的投票数 voteData, err := redis.GetPostVoteData(ids) if err != nil { return } // 将帖子的作者及分区信息查询出来填充到帖子中 for idx, post := range posts { // 根据作者id查询作者信息 user, err := mysql.GetUserById(post.AuthorID) if err != nil { zap.L().Error("mysql.GetUserById(post.AuthorID) failed", zap.Int64("author_id", post.AuthorID), zap.Error(err)) continue } // 根据社区id查询社区详细信息 community, err := mysql.GetCommunityDetailByID(post.CommunityID) if err != nil { zap.L().Error("mysql.GetUserById(post.AuthorID) failed", zap.Int64("community_id", post.CommunityID), zap.Error(err)) continue } postDetail := &models.ApiPostDetail{ AuthorName: user.Username, VoteNum: voteData[idx], Post: post, CommunityDetail: community, } data = append(data, postDetail) } return } func GetCommunityPostList(p *models.ParamPostList) (data []*models.ApiPostDetail, err error) { // 2. 去redis查询id列表 ids, err := redis.GetCommunityPostIDsInOrder(p) if err != nil { return } if len(ids) == 0 { zap.L().Warn("redis.GetPostIDsInOrder(p) return 0 data") return } zap.L().Debug("GetCommunityPostIDsInOrder", zap.Any("ids", ids)) // 3. 根据id去MySQL数据库查询帖子详细信息 // 返回的数据还要按照我给定的id的顺序返回 posts, err := mysql.GetPostListByIDs(ids) if err != nil { return } zap.L().Debug("GetPostList2", zap.Any("posts", posts)) // 提前查询好每篇帖子的投票数 voteData, err := redis.GetPostVoteData(ids) if err != nil { return } // 将帖子的作者及分区信息查询出来填充到帖子中 for idx, post := range posts { // 根据作者id查询作者信息 user, err := mysql.GetUserById(post.AuthorID) if err != nil { zap.L().Error("mysql.GetUserById(post.AuthorID) failed", zap.Int64("author_id", post.AuthorID), zap.Error(err)) continue } // 根据社区id查询社区详细信息 community, err := mysql.GetCommunityDetailByID(post.CommunityID) if err != nil { zap.L().Error("mysql.GetUserById(post.AuthorID) failed", zap.Int64("community_id", post.CommunityID), zap.Error(err)) continue } postDetail := &models.ApiPostDetail{ AuthorName: user.Username, VoteNum: voteData[idx], Post: post, CommunityDetail: community, } data = append(data, postDetail) } return } // GetPostListNew 将两个查询帖子列表逻辑合二为一的函数 func GetPostListNew(p *models.ParamPostList) (data []*models.ApiPostDetail, err error) { // 根据请求参数的不同,执行不同的逻辑。 if p.CommunityID == 0 { // 查所有 data, err = GetPostList2(p) } else { // 根据社区id查询 data, err = GetCommunityPostList(p) } if err != nil { zap.L().Error("GetPostListNew failed", zap.Error(err)) return nil, err } return } ================================================ FILE: lesson70/bluebell/logic/user.go ================================================ package logic import ( "bluebell/dao/mysql" "bluebell/models" "bluebell/pkg/jwt" "bluebell/pkg/snowflake" ) // 存放业务逻辑的代码 func SignUp(p *models.ParamSignUp) (err error) { // 1.判断用户存不存在 if err := mysql.CheckUserExist(p.Username); err != nil { return err } // 2.生成UID userID := snowflake.GenID() // 构造一个User实例 user := &models.User{ UserID: userID, Username: p.Username, Password: p.Password, } // 3.保存进数据库 return mysql.InsertUser(user) } func Login(p *models.ParamLogin) (user *models.User, err error) { user = &models.User{ Username: p.Username, Password: p.Password, } // 传递的是指针,就能拿到user.UserID if err := mysql.Login(user); err != nil { return nil, err } // 生成JWT token, err := jwt.GenToken(user.UserID, user.Username) if err != nil { return } user.Token = token return } ================================================ FILE: lesson70/bluebell/logic/vote.go ================================================ package logic import ( "bluebell/dao/redis" "bluebell/models" "strconv" "go.uber.org/zap" ) // 推荐阅读 // 基于用户投票的相关算法:http://www.ruanyifeng.com/blog/algorithm/ // 本项目使用简化版的投票分数 // 投一票就加432分 86400/200 --> 200张赞成票可以给你的帖子续一天 /* 投票的几种情况: direction=1时,有两种情况: 1. 之前没有投过票,现在投赞成票 --> 更新分数和投票记录 2. 之前投反对票,现在改投赞成票 --> 更新分数和投票记录 direction=0时,有两种情况: 1. 之前投过赞成票,现在要取消投票 --> 更新分数和投票记录 2. 之前投过反对票,现在要取消投票 --> 更新分数和投票记录 direction=-1时,有两种情况: 1. 之前没有投过票,现在投反对票 --> 更新分数和投票记录 2. 之前投赞成票,现在改投反对票 --> 更新分数和投票记录 投票的限制: 每个贴子自发表之日起一个星期之内允许用户投票,超过一个星期就不允许再投票了。 1. 到期之后将redis中保存的赞成票数及反对票数存储到mysql表中 2. 到期之后删除那个 KeyPostVotedZSetPF */ // VoteForPost 为帖子投票的函数 func VoteForPost(userID int64, p *models.ParamVoteData) error { zap.L().Debug("VoteForPost", zap.Int64("userID", userID), zap.String("postID", p.PostID), zap.Int8("direction", p.Direction)) return redis.VoteForPost(strconv.Itoa(int(userID)), p.PostID, float64(p.Direction)) } ================================================ FILE: lesson70/bluebell/main.go ================================================ package main import ( "bluebell/controller" "bluebell/dao/mysql" "bluebell/dao/redis" "bluebell/logger" "bluebell/pkg/snowflake" "bluebell/router" "bluebell/setting" "fmt" "os" ) // @title bluebell项目接口文档 // @version 1.0 // @description Go web开发进阶项目实战课程bluebell // @contact.name liwenzhou // @contact.url http://www.liwenzhou.com // @host 127.0.0.1:8084 // @BasePath /api/v1 func main() { if len(os.Args) < 2 { fmt.Println("need config file.eg: bluebell config.yaml") return } // 加载配置 if err := setting.Init(os.Args[1]); err != nil { fmt.Printf("load config failed, err:%v\n", err) return } if err := logger.Init(setting.Conf.LogConfig, setting.Conf.Mode); err != nil { fmt.Printf("init logger failed, err:%v\n", err) return } if err := mysql.Init(setting.Conf.MySQLConfig); err != nil { fmt.Printf("init mysql failed, err:%v\n", err) return } defer mysql.Close() // 程序退出关闭数据库连接 if err := redis.Init(setting.Conf.RedisConfig); err != nil { fmt.Printf("init redis failed, err:%v\n", err) return } defer redis.Close() if err := snowflake.Init(setting.Conf.StartTime, setting.Conf.MachineID); err != nil { fmt.Printf("init snowflake failed, err:%v\n", err) return } // 初始化gin框架内置的校验器使用的翻译器 if err := controller.InitTrans("zh"); err != nil { fmt.Printf("init validator trans failed, err:%v\n", err) return } // 注册路由 r := router.SetupRouter(setting.Conf.Mode) err := r.Run(fmt.Sprintf(":%d", setting.Conf.Port)) if err != nil { fmt.Printf("run server failed, err:%v\n", err) return } } ================================================ FILE: lesson70/bluebell/middlewares/auth.go ================================================ package middlewares import ( "bluebell/controller" "bluebell/pkg/jwt" "strings" "github.com/gin-gonic/gin" ) // JWTAuthMiddleware 基于JWT的认证中间件 func JWTAuthMiddleware() func(c *gin.Context) { return func(c *gin.Context) { // 客户端携带Token有三种方式 1.放在请求头 2.放在请求体 3.放在URI // 这里假设Token放在Header的Authorization中,并使用Bearer开头 // Authorization: Bearer xxxxxxx.xxx.xxx / X-TOKEN: xxx.xxx.xx // 这里的具体实现方式要依据你的实际业务情况决定 authHeader := c.Request.Header.Get("Authorization") if authHeader == "" { controller.ResponseError(c, controller.CodeNeedLogin) c.Abort() return } // 按空格分割 parts := strings.SplitN(authHeader, " ", 2) if !(len(parts) == 2 && parts[0] == "Bearer") { controller.ResponseError(c, controller.CodeInvalidToken) c.Abort() return } // parts[1]是获取到的tokenString,我们使用之前定义好的解析JWT的函数来解析它 mc, err := jwt.ParseToken(parts[1]) if err != nil { controller.ResponseError(c, controller.CodeInvalidToken) c.Abort() return } // 将当前请求的userID信息保存到请求的上下文c上 c.Set(controller.CtxUserIDKey, mc.UserID) c.Next() // 后续的处理请求的函数中 可以用过c.Get(CtxUserIDKey) 来获取当前请求的用户信息 } } ================================================ FILE: lesson70/bluebell/models/community.go ================================================ package models import "time" type Community struct { ID int64 `json:"id" db:"community_id"` Name string `json:"name" db:"community_name"` } type CommunityDetail struct { ID int64 `json:"id" db:"community_id"` Name string `json:"name" db:"community_name"` Introduction string `json:"introduction,omitempty" db:"introduction"` CreateTime time.Time `json:"create_time" db:"create_time"` } ================================================ FILE: lesson70/bluebell/models/create_table.sql ================================================ -- CREATE TABLE `user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `user_id` bigint(20) NOT NULL, `username` varchar(64) COLLATE utf8mb4_general_ci NOT NULL, `password` varchar(64) COLLATE utf8mb4_general_ci NOT NULL, `email` varchar(64) COLLATE utf8mb4_general_ci, `gender` tinyint(4) NOT NULL DEFAULT '0', `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP, `update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `idx_username` (`username`) USING BTREE, UNIQUE KEY `idx_user_id` (`user_id`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; DROP TABLE IF EXISTS `community`; CREATE TABLE `community` ( `id` int(11) NOT NULL AUTO_INCREMENT, `community_id` int(10) unsigned NOT NULL, `community_name` varchar(128) COLLATE utf8mb4_general_ci NOT NULL, `introduction` varchar(256) COLLATE utf8mb4_general_ci NOT NULL, `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `idx_community_id` (`community_id`), UNIQUE KEY `idx_community_name` (`community_name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; INSERT INTO `community` VALUES ('1', '1', 'Go', 'Golang', '2016-11-01 08:10:10', '2016-11-01 08:10:10'); INSERT INTO `community` VALUES ('2', '2', 'leetcode', '刷题刷题刷题', '2020-01-01 08:00:00', '2020-01-01 08:00:00'); INSERT INTO `community` VALUES ('3', '3', 'CS:GO', 'Rush B。。。', '2018-08-07 08:30:00', '2018-08-07 08:30:00'); INSERT INTO `community` VALUES ('4', '4', 'LOL', '欢迎来到英雄联盟!', '2016-01-01 08:00:00', '2016-01-01 08:00:00'); DROP TABLE IF EXISTS `post`; CREATE TABLE `post` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `post_id` bigint(20) NOT NULL COMMENT '帖子id', `title` varchar(128) COLLATE utf8mb4_general_ci NOT NULL COMMENT '标题', `content` varchar(8192) COLLATE utf8mb4_general_ci NOT NULL COMMENT '内容', `author_id` bigint(20) NOT NULL COMMENT '作者的用户id', `community_id` bigint(20) NOT NULL COMMENT '所属社区', `status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '帖子状态', `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), UNIQUE KEY `idx_post_id` (`post_id`), KEY `idx_author_id` (`author_id`), KEY `idx_community_id` (`community_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; ================================================ FILE: lesson70/bluebell/models/params.go ================================================ package models // 定义请求的参数结构体 const ( OrderTime = "time" OrderScore = "score" ) // ParamSignUp 注册请求参数 type ParamSignUp struct { Username string `json:"username" binding:"required"` Password string `json:"password" binding:"required"` RePassword string `json:"re_password" binding:"required,eqfield=Password"` } // ParamLogin 登录请求参数 type ParamLogin struct { Username string `json:"username" binding:"required"` Password string `json:"password" binding:"required"` } // ParamVoteData 投票数据 type ParamVoteData struct { // UserID 从请求中获取当前的用户 PostID string `json:"post_id" binding:"required"` // 贴子id Direction int8 `json:"direction,string" binding:"oneof=1 0 -1" ` // 赞成票(1)还是反对票(-1)取消投票(0) } // ParamPostList 获取帖子列表query string参数 type ParamPostList struct { CommunityID int64 `json:"community_id" form:"community_id"` // 可以为空 Page int64 `json:"page" form:"page" example:"1"` // 页码 Size int64 `json:"size" form:"size" example:"10"` // 每页数据量 Order string `json:"order" form:"order" example:"score"` // 排序依据 } ================================================ FILE: lesson70/bluebell/models/post.go ================================================ package models import "time" // 内存对齐概念 type Post struct { ID int64 `json:"id,string" db:"post_id"` // 帖子id AuthorID int64 `json:"author_id" db:"author_id"` // 作者id CommunityID int64 `json:"community_id" db:"community_id" binding:"required"` // 社区id Status int32 `json:"status" db:"status"` // 帖子状态 Title string `json:"title" db:"title" binding:"required"` // 帖子标题 Content string `json:"content" db:"content" binding:"required"` // 帖子内容 CreateTime time.Time `json:"create_time" db:"create_time"` // 帖子创建时间 } // ApiPostDetail 帖子详情接口的结构体 type ApiPostDetail struct { AuthorName string `json:"author_name"` // 作者 VoteNum int64 `json:"vote_num"` // 投票数 *Post // 嵌入帖子结构体 *CommunityDetail `json:"community"` // 嵌入社区信息 } ================================================ FILE: lesson70/bluebell/models/struct_test.go ================================================ package models import ( "fmt" "testing" "unsafe" ) // Go 内存对齐 type s1 struct { a int8 // 1 b string // 3 c int8 // 1 } type s2 struct { a int8 c int8 // 1 b string // 2 } func TestStruct(t *testing.T) { v1 := s1{ a: 1, b: "七米", c: 2, } v2 := s2{ a: 1, b: "七米", c: 2, } fmt.Println(unsafe.Sizeof(v1), unsafe.Sizeof(v2)) } ================================================ FILE: lesson70/bluebell/models/user.go ================================================ package models type User struct { UserID int64 `db:"user_id"` Username string `db:"username"` Password string `db:"password"` Token string } ================================================ FILE: lesson70/bluebell/pkg/jwt/jwt.go ================================================ package jwt import ( "errors" "time" "github.com/spf13/viper" "github.com/dgrijalva/jwt-go" ) var mySecret = []byte("夏天夏天悄悄过去") // MyClaims 自定义声明结构体并内嵌jwt.StandardClaims // jwt包自带的jwt.StandardClaims只包含了官方字段 // 我们这里需要额外记录一个username字段,所以要自定义结构体 // 如果想要保存更多信息,都可以添加到这个结构体中 type MyClaims struct { UserID int64 `json:"user_id"` Username string `json:"username"` jwt.StandardClaims } // GenToken 生成JWT func GenToken(userID int64, username string) (string, error) { // 创建一个我们自己的声明的数据 c := MyClaims{ userID, "username", // 自定义字段 jwt.StandardClaims{ ExpiresAt: time.Now().Add( time.Duration(viper.GetInt("auth.jwt_expire")) * time.Hour).Unix(), // 过期时间 Issuer: "bluebell", // 签发人 }, } // 使用指定的签名方法创建签名对象 token := jwt.NewWithClaims(jwt.SigningMethodHS256, c) // 使用指定的secret签名并获得完整的编码后的字符串token return token.SignedString(mySecret) } // ParseToken 解析JWT func ParseToken(tokenString string) (*MyClaims, error) { // 解析token var mc = new(MyClaims) token, err := jwt.ParseWithClaims(tokenString, mc, func(token *jwt.Token) (i interface{}, err error) { return mySecret, nil }) if err != nil { return nil, err } if token.Valid { // 校验token return mc, nil } return nil, errors.New("invalid token") } ================================================ FILE: lesson70/bluebell/pkg/snowflake/snowflake.go ================================================ package snowflake import ( "time" sf "github.com/bwmarrin/snowflake" ) var node *sf.Node func Init(startTime string, machineID int64) (err error) { var st time.Time st, err = time.Parse("2006-01-02", startTime) if err != nil { return } sf.Epoch = st.UnixNano() / 1000000 node, err = sf.NewNode(machineID) return } func GenID() int64 { return node.Generate().Int64() } ================================================ FILE: lesson70/bluebell/router/route.go ================================================ package router import ( "bluebell/controller" "bluebell/logger" "bluebell/middlewares" "net/http" ginSwagger "github.com/swaggo/gin-swagger" "github.com/swaggo/gin-swagger/swaggerFiles" _ "bluebell/docs" "github.com/gin-gonic/gin" ) func SetupRouter(mode string) *gin.Engine { if mode == gin.ReleaseMode { gin.SetMode(gin.ReleaseMode) // gin设置成发布模式 } r := gin.New() r.Use(logger.GinLogger(), logger.GinRecovery(true)) r.GET("/ping", func(c *gin.Context) { c.String(http.StatusOK, "pong") }) r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) v1 := r.Group("/api/v1") // 注册 v1.POST("/signup", controller.SignUpHandler) // 登录 v1.POST("/login", controller.LoginHandler) v1.Use(middlewares.JWTAuthMiddleware()) // 应用JWT认证中间件 { v1.GET("/community", controller.CommunityHandler) v1.GET("/community/:id", controller.CommunityDetailHandler) v1.POST("/post", controller.CreatePostHandler) v1.GET("/post/:id", controller.GetPostDetailHandler) v1.GET("/posts", controller.GetPostListHandler) // 根据时间或分数获取帖子列表 v1.GET("/posts2", controller.GetPostListHandler2) // 投票 v1.POST("/vote", controller.PostVoteController) } r.NoRoute(func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "msg": "404", }) }) return r } ================================================ FILE: lesson70/bluebell/setting/setting.go ================================================ package setting import ( "fmt" "github.com/fsnotify/fsnotify" "github.com/spf13/viper" ) var Conf = new(AppConfig) type AppConfig struct { Name string `mapstructure:"name"` Mode string `mapstructure:"mode"` Version string `mapstructure:"version"` StartTime string `mapstructure:"start_time"` MachineID int64 `mapstructure:"machine_id"` Port int `mapstructure:"port"` *LogConfig `mapstructure:"log"` *MySQLConfig `mapstructure:"mysql"` *RedisConfig `mapstructure:"redis"` } type MySQLConfig struct { Host string `mapstructure:"host"` User string `mapstructure:"user"` Password string `mapstructure:"password"` DB string `mapstructure:"dbname"` Port int `mapstructure:"port"` MaxOpenConns int `mapstructure:"max_open_conns"` MaxIdleConns int `mapstructure:"max_idle_conns"` } type RedisConfig struct { Host string `mapstructure:"host"` Password string `mapstructure:"password"` Port int `mapstructure:"port"` DB int `mapstructure:"db"` PoolSize int `mapstructure:"pool_size"` MinIdleConns int `mapstructure:"min_idle_conns"` } type LogConfig struct { Level string `mapstructure:"level"` Filename string `mapstructure:"filename"` MaxSize int `mapstructure:"max_size"` MaxAge int `mapstructure:"max_age"` MaxBackups int `mapstructure:"max_backups"` } func Init(filePath string) (err error) { // 方式1:直接指定配置文件路径(相对路径或者绝对路径) // 相对路径:相对执行的可执行文件的相对路径 //viper.SetConfigFile("./conf/config.yaml") // 绝对路径:系统中实际的文件路径 //viper.SetConfigFile("/Users/liwenzhou/Desktop/bluebell/conf/config.yaml") // 方式2:指定配置文件名和配置文件的位置,viper自行查找可用的配置文件 // 配置文件名不需要带后缀 // 配置文件位置可配置多个 //viper.SetConfigName("config") // 指定配置文件名(不带后缀) //viper.AddConfigPath(".") // 指定查找配置文件的路径(这里使用相对路径) //viper.AddConfigPath("./conf") // 指定查找配置文件的路径(这里使用相对路径) // 基本上是配合远程配置中心使用的,告诉viper当前的数据使用什么格式去解析 //viper.SetConfigType("json") viper.SetConfigFile(filePath) err = viper.ReadInConfig() // 读取配置信息 if err != nil { // 读取配置信息失败 fmt.Printf("viper.ReadInConfig failed, err:%v\n", err) return } // 把读取到的配置信息反序列化到 Conf 变量中 if err := viper.Unmarshal(Conf); err != nil { fmt.Printf("viper.Unmarshal failed, err:%v\n", err) } viper.WatchConfig() viper.OnConfigChange(func(in fsnotify.Event) { fmt.Println("配置文件修改了...") if err := viper.Unmarshal(Conf); err != nil { fmt.Printf("viper.Unmarshal failed, err:%v\n", err) } }) return } ================================================ FILE: lesson77/bluebell/.air.conf ================================================ # [Air](https://github.com/cosmtrek/air) TOML 格式的配置文件 # 工作目录 # 使用 . 或绝对路径,请注意 `tmp_dir` 目录必须在 `root` 目录下 root = "." tmp_dir = "tmp" [build] # 只需要写你平常编译使用的shell命令。你也可以使用 `make` cmd = "swag init && go build -o ./tmp/main" # 由`cmd`命令得到的二进制文件名 bin = "tmp/main" # 自定义的二进制,可以添加额外的编译标识例如添加 GIN_MODE=release full_bin = "./tmp/main ./conf/config.yaml" # 监听以下文件扩展名的文件. include_ext = ["go", "tpl", "tmpl", "html", "yaml"] # 忽略这些文件扩展名或目录 exclude_dir = ["assets", "tmp", "vendor", "frontend/node_modules"] # 监听以下指定目录的文件 include_dir = [] # 排除以下文件 exclude_file = [] # 如果文件更改过于频繁,则没有必要在每次更改时都触发构建。可以设置触发构建的延迟时间 delay = 1000 # ms # 发生构建错误时,停止运行旧的二进制文件。 stop_on_error = true # air的日志文件名,该日志文件放置在你的`tmp_dir`中 log = "air_errors.log" [log] # 显示日志时间 time = true [color] # 自定义每个部分显示的颜色。如果找不到颜色,使用原始的应用程序日志。 main = "magenta" watcher = "cyan" build = "yellow" runner = "green" [misc] # 退出时删除tmp目录 clean_on_exit = true ================================================ FILE: lesson77/bluebell/Dockerfile ================================================ FROM golang:alpine AS builder # 为我们的镜像设置必要的环境变量 ENV GO111MODULE=on \ CGO_ENABLED=0 \ GOOS=linux \ GOARCH=amd64 # 移动到工作目录:/build WORKDIR /build # 复制项目中的 go.mod 和 go.sum文件并下载依赖信息 COPY go.mod . COPY go.sum . RUN go mod download # 将代码复制到容器中 COPY . . # 将我们的代码编译成二进制可执行文件 bluebell_app RUN go build -o bluebell_app . ################### # 接下来创建一个小镜像 ################### FROM debian:stretch-slim COPY ./wait-for.sh / COPY ./templates /templates COPY ./static /static COPY ./conf /conf # 从builder镜像中把/dist/app 拷贝到当前目录 COPY --from=builder /build/bluebell_app / RUN set -eux; \ apt-get update; \ apt-get install -y \ --no-install-recommends \ netcat; \ chmod 755 wait-for.sh # 声明服务端口 EXPOSE 8084 # 需要运行的命令 #ENTRYPOINT ["/bluebell_app", "conf/config.yaml"] ================================================ FILE: lesson77/bluebell/Dockerfile.back ================================================ FROM golang:alpine AS builder # 为我们的镜像设置必要的环境变量 ENV GO111MODULE=on \ CGO_ENABLED=0 \ GOOS=linux \ GOARCH=amd64 # 移动到工作目录:/build WORKDIR /build # 复制项目中的 go.mod 和 go.sum文件并下载依赖信息 COPY go.mod . COPY go.sum . RUN go mod download # 将代码复制到容器中 COPY . . # 将我们的代码编译成二进制可执行文件 bluebell_app RUN go build -o bluebell_app . ################### # 接下来创建一个小镜像 ################### FROM scratch COPY ./templates /templates COPY ./static /static COPY ./conf /conf # 从builder镜像中把/dist/app 拷贝到当前目录 COPY --from=builder /build/bluebell_app / # 声明服务端口 EXPOSE 8084 # 需要运行的命令 ENTRYPOINT ["/bluebell_app", "conf/config.yaml"] ================================================ FILE: lesson77/bluebell/Makefile ================================================ .PHONY: all build run gotool clean help BINARY="xx" all: gotool build build: CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o ${BINARY} run: @go run ./main.go conf/config.yaml gotool: go fmt ./ go vet ./ clean: @if [ -f ${BINARY} ] ; then rm ${BINARY} ; fi help: @echo "make - 格式化 Go 代码, 并编译生成二进制文件" @echo "make build - 编译 Go 代码, 生成二进制文件" @echo "make run - 直接运行 Go 代码" @echo "make clean - 移除二进制文件和 vim swap files" @echo "make gotool - 运行 Go 工具 'fmt' and 'vet'" ================================================ FILE: lesson77/bluebell/bluebell ================================================ [File too large to display: 40.7 MB] ================================================ FILE: lesson77/bluebell/conf/config.yaml ================================================ name: "bluebell" mode: "dev" port: 8084 version: "v0.0.1" start_time: "2020-07-01" machine_id: 1 auth: jwt_expire: 8760 log: level: "debug" filename: "web_app.log" max_size: 200 max_age: 30 max_backups: 7 mysql: host: mysql8019 port: 3306 user: "root" password: "root1234" dbname: "bluebell" max_open_conns: 200 max_idle_conns: 50 redis: host: redis507 port: 6379 password: "" db: 0 pool_size: 100 ================================================ FILE: lesson77/bluebell/controller/code.go ================================================ package controller type ResCode int64 const ( CodeSuccess ResCode = 1000 + iota CodeInvalidParam CodeUserExist CodeUserNotExist CodeInvalidPassword CodeServerBusy CodeNeedLogin CodeInvalidToken ) var codeMsgMap = map[ResCode]string{ CodeSuccess: "success", CodeInvalidParam: "请求参数错误", CodeUserExist: "用户名已存在", CodeUserNotExist: "用户名不存在", CodeInvalidPassword: "用户名或密码错误", CodeServerBusy: "服务繁忙", CodeNeedLogin: "需要登录", CodeInvalidToken: "无效的token", } func (c ResCode) Msg() string { msg, ok := codeMsgMap[c] if !ok { msg = codeMsgMap[CodeServerBusy] } return msg } ================================================ FILE: lesson77/bluebell/controller/community.go ================================================ package controller import ( "bluebell/logic" "strconv" "github.com/gin-gonic/gin" "go.uber.org/zap" ) // ---- 跟社区相关的 ---- func CommunityHandler(c *gin.Context) { // 查询到所有的社区(community_id, community_name) 以列表的形式返回 data, err := logic.GetCommunityList() if err != nil { zap.L().Error("logic.GetCommunityList() failed", zap.Error(err)) ResponseError(c, CodeServerBusy) // 不轻易把服务端报错暴露给外面 return } ResponseSuccess(c, data) } // CommunityDetailHandler 社区分类详情 func CommunityDetailHandler(c *gin.Context) { // 1. 获取社区id idStr := c.Param("id") // 获取URL参数 id, err := strconv.ParseInt(idStr, 10, 64) if err != nil { ResponseError(c, CodeInvalidParam) return } // 2. 根据id获取社区详情 data, err := logic.GetCommunityDetail(id) if err != nil { zap.L().Error("logic.GetCommunityList() failed", zap.Error(err)) ResponseError(c, CodeServerBusy) // 不轻易把服务端报错暴露给外面 return } ResponseSuccess(c, data) } ================================================ FILE: lesson77/bluebell/controller/doc_response_models.go ================================================ package controller import "bluebell/models" // 专门用来放接口文档用到的model // 因为我们的接口文档返回的数据格式是一致的,但是具体的data类型不一致 // _ResponsePostList 帖子列表接口响应数据 type _ResponsePostList struct { Code ResCode `json:"code"` // 业务响应状态码 Message string `json:"message"` // 提示信息 Data []*models.ApiPostDetail `json:"data"` // 数据 } ================================================ FILE: lesson77/bluebell/controller/post.go ================================================ package controller import ( "bluebell/logic" "bluebell/models" "strconv" "github.com/gin-gonic/gin" "go.uber.org/zap" // swagger 嵌入文件 ) // CreatePostHandler 创建帖子的处理函数 func CreatePostHandler(c *gin.Context) { // 1. 获取参数及参数的校验 //c.ShouldBindJSON() // validator --> binding tag p := new(models.Post) if err := c.ShouldBindJSON(p); err != nil { zap.L().Debug("c.ShouldBindJSON(p) error", zap.Any("err", err)) zap.L().Error("create post with invalid param") ResponseError(c, CodeInvalidParam) return } // 从 c 取到当前发请求的用户的ID userID, err := getCurrentUserID(c) if err != nil { ResponseError(c, CodeNeedLogin) return } p.AuthorID = userID // 2. 创建帖子 if err := logic.CreatePost(p); err != nil { zap.L().Error("logic.CreatePost(p) failed", zap.Error(err)) ResponseError(c, CodeServerBusy) return } // 3. 返回响应 ResponseSuccess(c, nil) } // GetPostDetailHandler 获取帖子详情的处理函数 func GetPostDetailHandler(c *gin.Context) { // 1. 获取参数(从URL中获取帖子的id) pidStr := c.Param("id") pid, err := strconv.ParseInt(pidStr, 10, 64) if err != nil { zap.L().Error("get post detail with invalid param", zap.Error(err)) ResponseError(c, CodeInvalidParam) return } // 2. 根据id取出帖子数据(查数据库) data, err := logic.GetPostById(pid) if err != nil { zap.L().Error("logic.GetPostById(pid) failed", zap.Error(err)) ResponseError(c, CodeServerBusy) return } // 3. 返回响应 ResponseSuccess(c, data) } // GetPostListHandler 获取帖子列表的处理函数 func GetPostListHandler(c *gin.Context) { // 获取分页参数 page, size := getPageInfo(c) // 获取数据 data, err := logic.GetPostList(page, size) if err != nil { zap.L().Error("logic.GetPostList() failed", zap.Error(err)) ResponseError(c, CodeServerBusy) return } ResponseSuccess(c, data) // 返回响应 } // GetPostListHandler2 升级版帖子列表接口 // @Summary 升级版帖子列表接口 // @Description 可按社区按时间或分数排序查询帖子列表接口 // @Tags 帖子相关接口(api分组展示使用的) // @Accept application/json // @Produce application/json // @Param Authorization header string true "Bearer JWT" // @Param object query models.ParamPostList false "查询参数" // @Security ApiKeyAuth // @Success 200 {object} _ResponsePostList // @Router /posts2 [get] func GetPostListHandler2(c *gin.Context) { // GET请求参数(query string):/api/v1/posts2?page=1&size=10&order=time // 初始化结构体时指定初始参数 p := &models.ParamPostList{ Page: 1, Size: 10, Order: models.OrderTime, // magic string } //c.ShouldBind() 根据请求的数据类型选择相应的方法去获取数据 //c.ShouldBindJSON() 如果请求中携带的是json格式的数据,才能用这个方法获取到数据 if err := c.ShouldBindQuery(p); err != nil { zap.L().Error("GetPostListHandler2 with invalid params", zap.Error(err)) ResponseError(c, CodeInvalidParam) return } data, err := logic.GetPostListNew(p) // 更新:合二为一 // 获取数据 if err != nil { zap.L().Error("logic.GetPostList() failed", zap.Error(err)) ResponseError(c, CodeServerBusy) return } ResponseSuccess(c, data) // 返回响应 } // 根据社区去查询帖子列表 //func GetCommunityPostListHandler(c *gin.Context) { // // 初始化结构体时指定初始参数 // p := &models.ParamCommunityPostList{ // ParamPostList: &models.ParamPostList{ // Page: 1, // Size: 10, // Order: models.OrderTime, // }, // } // //c.ShouldBind() 根据请求的数据类型选择相应的方法去获取数据 // //c.ShouldBindJSON() 如果请求中携带的是json格式的数据,才能用这个方法获取到数据 // if err := c.ShouldBindQuery(p); err != nil { // zap.L().Error("GetCommunityPostListHandler with invalid params", zap.Error(err)) // ResponseError(c, CodeInvalidParam) // return // } // // // 获取数据 // data, err := logic.GetCommunityPostList(p) // if err != nil { // zap.L().Error("logic.GetPostList() failed", zap.Error(err)) // ResponseError(c, CodeServerBusy) // return // } // ResponseSuccess(c, data) //} ================================================ FILE: lesson77/bluebell/controller/post_test.go ================================================ package controller import ( "bytes" "encoding/json" "net/http" "net/http/httptest" "testing" "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" ) func TestCreatePostHandler(t *testing.T) { gin.SetMode(gin.TestMode) r := gin.Default() url := "/api/v1/post" r.POST(url, CreatePostHandler) body := `{ "community_id": 1, "title": "test", "content": "just a test" }` req, _ := http.NewRequest(http.MethodPost, url, bytes.NewReader([]byte(body))) w := httptest.NewRecorder() r.ServeHTTP(w, req) assert.Equal(t, 200, w.Code) // 判断响应的内容是不是按预期返回了需要登录的错误 // 方法1:判断响应内容中是不是包含指定的字符串 //assert.Contains(t, w.Body.String(), "需要登录") // 方法2:将响应的内容反序列化到ResponseData 然后判断字段与预期是否一致 res := new(ResponseData) if err := json.Unmarshal(w.Body.Bytes(), res); err != nil { t.Fatalf("json.Unmarshal w.Body failed, err:%v\n", err) } assert.Equal(t, res.Code, CodeNeedLogin) } ================================================ FILE: lesson77/bluebell/controller/request.go ================================================ package controller import ( "errors" "strconv" "github.com/gin-gonic/gin" ) const CtxUserIDKey = "userID" var ErrorUserNotLogin = errors.New("用户未登录") // getCurrentUserID 获取当前登录的用户ID func getCurrentUserID(c *gin.Context) (userID int64, err error) { uid, ok := c.Get(CtxUserIDKey) if !ok { err = ErrorUserNotLogin return } userID, ok = uid.(int64) if !ok { err = ErrorUserNotLogin return } return } func getPageInfo(c *gin.Context) (int64, int64) { pageStr := c.Query("page") sizeStr := c.Query("size") var ( page int64 size int64 err error ) page, err = strconv.ParseInt(pageStr, 10, 64) if err != nil { page = 1 } size, err = strconv.ParseInt(sizeStr, 10, 64) if err != nil { size = 10 } return page, size } ================================================ FILE: lesson77/bluebell/controller/response.go ================================================ package controller import ( "net/http" "github.com/gin-gonic/gin" ) /* { "code": 10000, // 程序中的错误码 "msg": xx, // 提示信息 "data": {}, // 数据 } */ type ResponseData struct { Code ResCode `json:"code"` Msg interface{} `json:"msg"` Data interface{} `json:"data,omitempty"` } func ResponseError(c *gin.Context, code ResCode) { c.JSON(http.StatusOK, &ResponseData{ Code: code, Msg: code.Msg(), Data: nil, }) } func ResponseErrorWithMsg(c *gin.Context, code ResCode, msg interface{}) { c.JSON(http.StatusOK, &ResponseData{ Code: code, Msg: msg, Data: nil, }) } func ResponseSuccess(c *gin.Context, data interface{}) { c.JSON(http.StatusOK, &ResponseData{ Code: CodeSuccess, Msg: CodeSuccess.Msg(), Data: data, }) } ================================================ FILE: lesson77/bluebell/controller/user.go ================================================ package controller import ( "bluebell/dao/mysql" "bluebell/logic" "bluebell/models" "errors" "fmt" "github.com/go-playground/validator/v10" "go.uber.org/zap" "github.com/gin-gonic/gin" ) // SignUpHandler 处理注册请求的函数 func SignUpHandler(c *gin.Context) { // 1. 获取参数和参数校验 p := new(models.ParamSignUp) if err := c.ShouldBindJSON(p); err != nil { // 请求参数有误,直接返回响应 zap.L().Error("SignUp with invalid param", zap.Error(err)) // 判断err是不是validator.ValidationErrors 类型 errs, ok := err.(validator.ValidationErrors) if !ok { ResponseError(c, CodeInvalidParam) return } ResponseErrorWithMsg(c, CodeInvalidParam, removeTopStruct(errs.Translate(trans))) return } // 2. 业务处理 if err := logic.SignUp(p); err != nil { zap.L().Error("logic.SignUp failed", zap.Error(err)) if errors.Is(err, mysql.ErrorUserExist) { ResponseError(c, CodeUserExist) return } ResponseError(c, CodeServerBusy) return } // 3. 返回响应 ResponseSuccess(c, nil) } func LoginHandler(c *gin.Context) { // 1.获取请求参数及参数校验 p := new(models.ParamLogin) if err := c.ShouldBindJSON(p); err != nil { // 请求参数有误,直接返回响应 zap.L().Error("Login with invalid param", zap.Error(err)) // 判断err是不是validator.ValidationErrors 类型 errs, ok := err.(validator.ValidationErrors) if !ok { ResponseError(c, CodeInvalidParam) return } ResponseErrorWithMsg(c, CodeInvalidParam, removeTopStruct(errs.Translate(trans))) return } // 2.业务逻辑处理 user, err := logic.Login(p) if err != nil { zap.L().Error("logic.Login failed", zap.String("username", p.Username), zap.Error(err)) if errors.Is(err, mysql.ErrorUserNotExist) { ResponseError(c, CodeUserNotExist) return } ResponseError(c, CodeInvalidPassword) return } // 3.返回响应 ResponseSuccess(c, gin.H{ "user_id": fmt.Sprintf("%d", user.UserID), // id值大于1<<53-1 int64类型的最大值是1<<63-1 "user_name": user.Username, "token": user.Token, }) } ================================================ FILE: lesson77/bluebell/controller/validator.go ================================================ package controller import ( "bluebell/models" "fmt" "reflect" "strings" "github.com/gin-gonic/gin/binding" "github.com/go-playground/locales/en" "github.com/go-playground/locales/zh" ut "github.com/go-playground/universal-translator" "github.com/go-playground/validator/v10" enTranslations "github.com/go-playground/validator/v10/translations/en" zhTranslations "github.com/go-playground/validator/v10/translations/zh" ) // 定义一个全局翻译器T var trans ut.Translator // InitTrans 初始化翻译器 func InitTrans(locale string) (err error) { // 修改gin框架中的Validator引擎属性,实现自定制 if v, ok := binding.Validator.Engine().(*validator.Validate); ok { // 注册一个获取json tag的自定义方法 v.RegisterTagNameFunc(func(fld reflect.StructField) string { name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0] if name == "-" { return "" } return name }) // 为SignUpParam注册自定义校验方法 v.RegisterStructValidation(SignUpParamStructLevelValidation, models.ParamSignUp{}) zhT := zh.New() // 中文翻译器 enT := en.New() // 英文翻译器 // 第一个参数是备用(fallback)的语言环境 // 后面的参数是应该支持的语言环境(支持多个) // uni := ut.New(zhT, zhT) 也是可以的 uni := ut.New(enT, zhT, enT) // locale 通常取决于 http 请求头的 'Accept-Language' var ok bool // 也可以使用 uni.FindTranslator(...) 传入多个locale进行查找 trans, ok = uni.GetTranslator(locale) if !ok { return fmt.Errorf("uni.GetTranslator(%s) failed", locale) } // 注册翻译器 switch locale { case "en": err = enTranslations.RegisterDefaultTranslations(v, trans) case "zh": err = zhTranslations.RegisterDefaultTranslations(v, trans) default: err = enTranslations.RegisterDefaultTranslations(v, trans) } return } return } // removeTopStruct 去除提示信息中的结构体名称 func removeTopStruct(fields map[string]string) map[string]string { res := map[string]string{} for field, err := range fields { res[field[strings.Index(field, ".")+1:]] = err } return res } // SignUpParamStructLevelValidation 自定义SignUpParam结构体校验函数 func SignUpParamStructLevelValidation(sl validator.StructLevel) { su := sl.Current().Interface().(models.ParamSignUp) if su.Password != su.RePassword { // 输出错误提示信息,最后一个参数就是传递的param sl.ReportError(su.RePassword, "re_password", "RePassword", "eqfield", "password") } } ================================================ FILE: lesson77/bluebell/controller/vote.go ================================================ package controller import ( "bluebell/logic" "bluebell/models" "go.uber.org/zap" "github.com/go-playground/validator/v10" "github.com/gin-gonic/gin" ) // 投票 //type VoteData struct { // // UserID 从请求中获取当前的用户 // PostID int64 `json:"post_id,string"` // 贴子id // Direction int `json:"direction,string"` // 赞成票(1)还是反对票(-1) //} func PostVoteController(c *gin.Context) { // 参数校验 p := new(models.ParamVoteData) if err := c.ShouldBindJSON(p); err != nil { errs, ok := err.(validator.ValidationErrors) // 类型断言 if !ok { ResponseError(c, CodeInvalidParam) return } errData := removeTopStruct(errs.Translate(trans)) // 翻译并去除掉错误提示中的结构体标识 ResponseErrorWithMsg(c, CodeInvalidParam, errData) return } // 获取当前请求的用户的id userID, err := getCurrentUserID(c) if err != nil { ResponseError(c, CodeNeedLogin) return } // 具体投票的业务逻辑 if err := logic.VoteForPost(userID, p); err != nil { zap.L().Error("logic.VoteForPost() failed", zap.Error(err)) ResponseError(c, CodeServerBusy) return } ResponseSuccess(c, nil) } ================================================ FILE: lesson77/bluebell/dao/mysql/community.go ================================================ package mysql import ( "bluebell/models" "database/sql" "go.uber.org/zap" ) func GetCommunityList() (communityList []*models.Community, err error) { sqlStr := "select community_id, community_name from community" if err := db.Select(&communityList, sqlStr); err != nil { if err == sql.ErrNoRows { zap.L().Warn("there is no community in db") err = nil } } return } // GetCommunityDetailByID 根据ID查询社区详情 func GetCommunityDetailByID(id int64) (community *models.CommunityDetail, err error) { community = new(models.CommunityDetail) sqlStr := `select community_id, community_name, introduction, create_time from community where community_id = ? ` if err := db.Get(community, sqlStr, id); err != nil { if err == sql.ErrNoRows { err = ErrorInvalidID } } return community, err } ================================================ FILE: lesson77/bluebell/dao/mysql/error_code.go ================================================ package mysql import "errors" var ( ErrorUserExist = errors.New("用户已存在") ErrorUserNotExist = errors.New("用户不存在") ErrorInvalidPassword = errors.New("用户名或密码错误") ErrorInvalidID = errors.New("无效的ID") ) ================================================ FILE: lesson77/bluebell/dao/mysql/mysql.go ================================================ package mysql import ( "bluebell/setting" "fmt" _ "github.com/go-sql-driver/mysql" "github.com/jmoiron/sqlx" ) var db *sqlx.DB // Init 初始化MySQL连接 func Init(cfg *setting.MySQLConfig) (err error) { // "user:password@tcp(host:port)/dbname" dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?parseTime=true&loc=Local", cfg.User, cfg.Password, cfg.Host, cfg.Port, cfg.DB) db, err = sqlx.Connect("mysql", dsn) if err != nil { return } db.SetMaxOpenConns(cfg.MaxOpenConns) db.SetMaxIdleConns(cfg.MaxIdleConns) return } // Close 关闭MySQL连接 func Close() { _ = db.Close() } ================================================ FILE: lesson77/bluebell/dao/mysql/post.go ================================================ package mysql import ( "bluebell/models" "strings" "github.com/jmoiron/sqlx" ) // CreatePost 创建帖子 func CreatePost(p *models.Post) (err error) { sqlStr := `insert into post( post_id, title, content, author_id, community_id) values (?, ?, ?, ?, ?) ` _, err = db.Exec(sqlStr, p.ID, p.Title, p.Content, p.AuthorID, p.CommunityID) return } // GetPostById 根据id查询单个贴子数据 func GetPostById(pid int64) (post *models.Post, err error) { post = new(models.Post) sqlStr := `select post_id, title, content, author_id, community_id, create_time from post where post_id = ? ` err = db.Get(post, sqlStr, pid) return } // GetPostList 查询帖子列表函数 func GetPostList(page, size int64) (posts []*models.Post, err error) { sqlStr := `select post_id, title, content, author_id, community_id, create_time from post ORDER BY create_time DESC limit ?,? ` posts = make([]*models.Post, 0, 2) // 不要写成make([]*models.Post, 2) err = db.Select(&posts, sqlStr, (page-1)*size, size) return } // GetPostListByIDs 根据给定的id列表查询帖子数据 func GetPostListByIDs(ids []string) (postList []*models.Post, err error) { sqlStr := `select post_id, title, content, author_id, community_id, create_time from post where post_id in (?) order by FIND_IN_SET(post_id, ?) ` // https: //www.liwenzhou.com/posts/Go/sqlx/ query, args, err := sqlx.In(sqlStr, ids, strings.Join(ids, ",")) if err != nil { return nil, err } query = db.Rebind(query) err = db.Select(&postList, query, args...) // !!!!!! return } ================================================ FILE: lesson77/bluebell/dao/mysql/post_test.go ================================================ package mysql import ( "bluebell/models" "bluebell/setting" "testing" ) func init() { dbCfg := setting.MySQLConfig{ Host: "127.0.0.1", User: "root", Password: "root1234", DB: "bluebell", Port: 13306, MaxOpenConns: 10, MaxIdleConns: 10, } err := Init(&dbCfg) if err != nil { panic(err) } } func TestCreatePost(t *testing.T) { post := models.Post{ ID: 10, AuthorID: 123, CommunityID: 1, Title: "test", Content: "just a test", } err := CreatePost(&post) if err != nil { t.Fatalf("CreatePost insert record into mysql failed, err:%v\n", err) } t.Logf("CreatePost insert record into mysql success") } ================================================ FILE: lesson77/bluebell/dao/mysql/user.go ================================================ package mysql import ( "bluebell/models" "crypto/md5" "database/sql" "encoding/hex" ) // 把每一步数据库操作封装成函数 // 待logic层根据业务需求调用 const secret = "liwenzhou.com" // CheckUserExist 检查指定用户名的用户是否存在 func CheckUserExist(username string) (err error) { sqlStr := `select count(user_id) from user where username = ?` var count int64 if err := db.Get(&count, sqlStr, username); err != nil { return err } if count > 0 { return ErrorUserExist } return } // InsertUser 想数据库中插入一条新的用户记录 func InsertUser(user *models.User) (err error) { // 对密码进行加密 user.Password = encryptPassword(user.Password) // 执行SQL语句入库 sqlStr := `insert into user(user_id, username, password) values(?,?,?)` _, err = db.Exec(sqlStr, user.UserID, user.Username, user.Password) return } // encryptPassword 密码加密 func encryptPassword(oPassword string) string { h := md5.New() h.Write([]byte(secret)) return hex.EncodeToString(h.Sum([]byte(oPassword))) } func Login(user *models.User) (err error) { oPassword := user.Password // 用户登录的密码 sqlStr := `select user_id, username, password from user where username=?` err = db.Get(user, sqlStr, user.Username) if err == sql.ErrNoRows { return ErrorUserNotExist } if err != nil { // 查询数据库失败 return err } // 判断密码是否正确 password := encryptPassword(oPassword) if password != user.Password { return ErrorInvalidPassword } return } // GetUserById 根据id获取用户信息 func GetUserById(uid int64) (user *models.User, err error) { user = new(models.User) sqlStr := `select user_id, username from user where user_id = ?` err = db.Get(user, sqlStr, uid) return } ================================================ FILE: lesson77/bluebell/dao/redis/keys.go ================================================ package redis // redis key // redis key注意使用命名空间的方式,方便查询和拆分 const ( Prefix = "bluebell:" // 项目key前缀 KeyPostTimeZSet = "post:time" // zset;贴子及发帖时间 KeyPostScoreZSet = "post:score" // zset;贴子及投票的分数 KeyPostVotedZSetPF = "post:voted:" // zset;记录用户及投票类型;参数是post id KeyCommunitySetPF = "community:" // set;保存每个分区下帖子的id ) // 给redis key加上前缀 func getRedisKey(key string) string { return Prefix + key } ================================================ FILE: lesson77/bluebell/dao/redis/post.go ================================================ package redis import ( "bluebell/models" "strconv" "time" "github.com/go-redis/redis" ) func getIDsFormKey(key string, page, size int64) ([]string, error) { start := (page - 1) * size end := start + size - 1 // 3. ZREVRANGE 按分数从大到小的顺序查询指定数量的元素 return client.ZRevRange(key, start, end).Result() } func GetPostIDsInOrder(p *models.ParamPostList) ([]string, error) { // 从redis获取id // 1. 根据用户请求中携带的order参数确定要查询的redis key key := getRedisKey(KeyPostTimeZSet) if p.Order == models.OrderScore { key = getRedisKey(KeyPostScoreZSet) } // 2. 确定查询的索引起始点 return getIDsFormKey(key, p.Page, p.Size) } // GetPostVoteData 根据ids查询每篇帖子的投赞成票的数据 func GetPostVoteData(ids []string) (data []int64, err error) { //data = make([]int64, 0, len(ids)) //for _, id := range ids { // key := getRedisKey(KeyPostVotedZSetPF + id) // // 查找key中分数是1的元素的数量->统计每篇帖子的赞成票的数量 // v := client.ZCount(key, "1", "1").Val() // data = append(data, v) //} // 使用pipeline一次发送多条命令,减少RTT pipeline := client.Pipeline() for _, id := range ids { key := getRedisKey(KeyPostVotedZSetPF + id) pipeline.ZCount(key, "1", "1") } cmders, err := pipeline.Exec() if err != nil { return nil, err } data = make([]int64, 0, len(cmders)) for _, cmder := range cmders { v := cmder.(*redis.IntCmd).Val() data = append(data, v) } return } // GetCommunityPostIDsInOrder 按社区查询ids func GetCommunityPostIDsInOrder(p *models.ParamPostList) ([]string, error) { orderKey := getRedisKey(KeyPostTimeZSet) if p.Order == models.OrderScore { orderKey = getRedisKey(KeyPostScoreZSet) } // 使用 zinterstore 把分区的帖子set与帖子分数的 zset 生成一个新的zset // 针对新的zset 按之前的逻辑取数据 // 社区的key cKey := getRedisKey(KeyCommunitySetPF + strconv.Itoa(int(p.CommunityID))) // 利用缓存key减少zinterstore执行的次数 key := orderKey + strconv.Itoa(int(p.CommunityID)) if client.Exists(key).Val() < 1 { // 不存在,需要计算 pipeline := client.Pipeline() pipeline.ZInterStore(key, redis.ZStore{ Aggregate: "MAX", }, cKey, orderKey) // zinterstore 计算 pipeline.Expire(key, 60*time.Second) // 设置超时时间 _, err := pipeline.Exec() if err != nil { return nil, err } } // 存在的话就直接根据key查询ids return getIDsFormKey(key, p.Page, p.Size) } ================================================ FILE: lesson77/bluebell/dao/redis/redis.go ================================================ package redis import ( "bluebell/setting" "fmt" "github.com/go-redis/redis" ) var ( client *redis.Client Nil = redis.Nil ) // Init 初始化连接 func Init(cfg *setting.RedisConfig) (err error) { client = redis.NewClient(&redis.Options{ Addr: fmt.Sprintf("%s:%d", cfg.Host, cfg.Port), Password: cfg.Password, // no password set DB: cfg.DB, // use default DB PoolSize: cfg.PoolSize, MinIdleConns: cfg.MinIdleConns, }) _, err = client.Ping().Result() if err != nil { return err } return nil } func Close() { _ = client.Close() } ================================================ FILE: lesson77/bluebell/dao/redis/vote.go ================================================ package redis import ( "errors" "math" "strconv" "time" "github.com/go-redis/redis" ) // 推荐阅读 // 基于用户投票的相关算法:http://www.ruanyifeng.com/blog/algorithm/ // 本项目使用简化版的投票分数 // 投一票就加432分 86400/200 --> 200张赞成票可以给你的帖子续一天 /* 投票的几种情况: direction=1时,有两种情况: 1. 之前没有投过票,现在投赞成票 --> 更新分数和投票记录 差值的绝对值:1 +432 2. 之前投反对票,现在改投赞成票 --> 更新分数和投票记录 差值的绝对值:2 +432*2 direction=0时,有两种情况: 1. 之前投过反对票,现在要取消投票 --> 更新分数和投票记录 差值的绝对值:1 +432 2. 之前投过赞成票,现在要取消投票 --> 更新分数和投票记录 差值的绝对值:1 -432 direction=-1时,有两种情况: 1. 之前没有投过票,现在投反对票 --> 更新分数和投票记录 差值的绝对值:1 -432 2. 之前投赞成票,现在改投反对票 --> 更新分数和投票记录 差值的绝对值:2 -432*2 投票的限制: 每个贴子自发表之日起一个星期之内允许用户投票,超过一个星期就不允许再投票了。 1. 到期之后将redis中保存的赞成票数及反对票数存储到mysql表中 2. 到期之后删除那个 KeyPostVotedZSetPF */ const ( oneWeekInSeconds = 7 * 24 * 3600 scorePerVote = 432 // 每一票值多少分 ) var ( ErrVoteTimeExpire = errors.New("投票时间已过") ErrVoteRepeated = errors.New("不允许重复投票") ) func CreatePost(postID, communityID int64) error { pipeline := client.TxPipeline() // 帖子时间 pipeline.ZAdd(getRedisKey(KeyPostTimeZSet), redis.Z{ Score: float64(time.Now().Unix()), Member: postID, }) // 帖子分数 pipeline.ZAdd(getRedisKey(KeyPostScoreZSet), redis.Z{ Score: float64(time.Now().Unix()), Member: postID, }) // 更新:把帖子id加到社区的set cKey := getRedisKey(KeyCommunitySetPF + strconv.Itoa(int(communityID))) pipeline.SAdd(cKey, postID) _, err := pipeline.Exec() return err } func VoteForPost(userID, postID string, value float64) error { // 1. 判断投票限制 // 去redis取帖子发布时间 postTime := client.ZScore(getRedisKey(KeyPostTimeZSet), postID).Val() if float64(time.Now().Unix())-postTime > oneWeekInSeconds { return ErrVoteTimeExpire } // 2和3需要放到一个pipeline事务中操作 // 2. 更新贴子的分数 // 先查当前用户给当前帖子的投票记录 ov := client.ZScore(getRedisKey(KeyPostVotedZSetPF+postID), userID).Val() // 更新:如果这一次投票的值和之前保存的值一致,就提示不允许重复投票 if value == ov { return ErrVoteRepeated } var op float64 if value > ov { op = 1 } else { op = -1 } diff := math.Abs(ov - value) // 计算两次投票的差值 pipeline := client.TxPipeline() pipeline.ZIncrBy(getRedisKey(KeyPostScoreZSet), op*diff*scorePerVote, postID) // 3. 记录用户为该贴子投票的数据 if value == 0 { pipeline.ZRem(getRedisKey(KeyPostVotedZSetPF+postID), userID) } else { pipeline.ZAdd(getRedisKey(KeyPostVotedZSetPF+postID), redis.Z{ Score: value, // 赞成票还是反对票 Member: userID, }) } _, err := pipeline.Exec() return err } ================================================ FILE: lesson77/bluebell/docker-compose.yml ================================================ # yaml 配置 version: "3.7" services: mysql8019: image: "mysql:8.0.19" ports: - "33061:3306" command: "--default-authentication-plugin=mysql_native_password --init-file /data/application/init.sql" environment: MYSQL_ROOT_PASSWORD: "root1234" MYSQL_DATABASE: "bluebell" MYSQL_PASSWORD: "root1234" volumes: - ./init.sql:/data/application/init.sql redis507: image: "redis:5.0.7" ports: - "26379:6379" bluebell_app: build: . command: sh -c "./wait-for.sh mysql8019:3306 redis507:6379 -- ./bluebell_app ./conf/config.yaml" depends_on: - mysql8019 - redis507 ports: - "8888:8084" ================================================ FILE: lesson77/bluebell/docs/docs.go ================================================ // GENERATED BY THE COMMAND ABOVE; DO NOT EDIT // This file was generated by swaggo/swag package docs import ( "bytes" "encoding/json" "strings" "github.com/alecthomas/template" "github.com/swaggo/swag" ) var doc = `{ "schemes": {{ marshal .Schemes }}, "swagger": "2.0", "info": { "description": "{{.Description}}", "title": "{{.Title}}", "contact": { "name": "liwenzhou", "url": "http://www.liwenzhou.com" }, "license": {}, "version": "{{.Version}}" }, "host": "{{.Host}}", "basePath": "{{.BasePath}}", "paths": { "/posts2": { "get": { "security": [ { "ApiKeyAuth": [] } ], "description": "可按社区按时间或分数排序查询帖子列表接口", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "帖子相关接口(api分组展示使用的)" ], "summary": "升级版帖子列表接口", "parameters": [ { "type": "string", "description": "Bearer JWT", "name": "Authorization", "in": "header", "required": true }, { "type": "integer", "description": "可以为空", "name": "community_id", "in": "query" }, { "type": "string", "example": "score", "description": "排序依据", "name": "order", "in": "query" }, { "type": "integer", "example": 1, "description": "页码", "name": "page", "in": "query" }, { "type": "integer", "example": 10, "description": "每页数据量", "name": "size", "in": "query" } ], "responses": { "200": { "description": "OK", "schema": { "$ref": "#/definitions/controller._ResponsePostList" } } } } } }, "definitions": { "controller._ResponsePostList": { "type": "object", "properties": { "code": { "description": "业务响应状态码", "type": "integer" }, "data": { "description": "数据", "type": "array", "items": { "$ref": "#/definitions/models.ApiPostDetail" } }, "message": { "description": "提示信息", "type": "string" } } }, "models.ApiPostDetail": { "type": "object", "required": [ "community_id", "content", "title" ], "properties": { "author_id": { "description": "作者id", "type": "integer" }, "author_name": { "description": "作者", "type": "string" }, "community_id": { "description": "社区id", "type": "integer" }, "content": { "description": "帖子内容", "type": "string" }, "create_time": { "type": "string" }, "id": { "type": "integer" }, "introduction": { "type": "string" }, "name": { "type": "string" }, "status": { "description": "帖子状态", "type": "integer" }, "title": { "description": "帖子标题", "type": "string" }, "vote_num": { "description": "投票数", "type": "integer" } } }, "models.ParamPostList": { "type": "object", "properties": { "community_id": { "description": "可以为空", "type": "integer" }, "order": { "description": "排序依据", "type": "string", "example": "score" }, "page": { "description": "页码", "type": "integer", "example": 1 }, "size": { "description": "每页数据量", "type": "integer", "example": 10 } } } } }` type swaggerInfo struct { Version string Host string BasePath string Schemes []string Title string Description string } // SwaggerInfo holds exported Swagger Info so clients can modify it var SwaggerInfo = swaggerInfo{ Version: "1.0", Host: "127.0.0.1:8084", BasePath: "/api/v1", Schemes: []string{}, Title: "bluebell项目接口文档", Description: "Go web开发进阶项目实战课程bluebell", } type s struct{} func (s *s) ReadDoc() string { sInfo := SwaggerInfo sInfo.Description = strings.Replace(sInfo.Description, "\n", "\\n", -1) t, err := template.New("swagger_info").Funcs(template.FuncMap{ "marshal": func(v interface{}) string { a, _ := json.Marshal(v) return string(a) }, }).Parse(doc) if err != nil { return doc } var tpl bytes.Buffer if err := t.Execute(&tpl, sInfo); err != nil { return doc } return tpl.String() } func init() { swag.Register(swag.Name, &s{}) } ================================================ FILE: lesson77/bluebell/docs/swagger.json ================================================ { "swagger": "2.0", "info": { "description": "Go web开发进阶项目实战课程bluebell", "title": "bluebell项目接口文档", "contact": { "name": "liwenzhou", "url": "http://www.liwenzhou.com" }, "license": {}, "version": "1.0" }, "host": "127.0.0.1:8084", "basePath": "/api/v1", "paths": { "/posts2": { "get": { "security": [ { "ApiKeyAuth": [] } ], "description": "可按社区按时间或分数排序查询帖子列表接口", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "帖子相关接口(api分组展示使用的)" ], "summary": "升级版帖子列表接口", "parameters": [ { "type": "string", "description": "Bearer JWT", "name": "Authorization", "in": "header", "required": true }, { "type": "integer", "description": "可以为空", "name": "community_id", "in": "query" }, { "type": "string", "example": "score", "description": "排序依据", "name": "order", "in": "query" }, { "type": "integer", "example": 1, "description": "页码", "name": "page", "in": "query" }, { "type": "integer", "example": 10, "description": "每页数据量", "name": "size", "in": "query" } ], "responses": { "200": { "description": "OK", "schema": { "$ref": "#/definitions/controller._ResponsePostList" } } } } } }, "definitions": { "controller._ResponsePostList": { "type": "object", "properties": { "code": { "description": "业务响应状态码", "type": "integer" }, "data": { "description": "数据", "type": "array", "items": { "$ref": "#/definitions/models.ApiPostDetail" } }, "message": { "description": "提示信息", "type": "string" } } }, "models.ApiPostDetail": { "type": "object", "required": [ "community_id", "content", "title" ], "properties": { "author_id": { "description": "作者id", "type": "integer" }, "author_name": { "description": "作者", "type": "string" }, "community_id": { "description": "社区id", "type": "integer" }, "content": { "description": "帖子内容", "type": "string" }, "create_time": { "type": "string" }, "id": { "type": "integer" }, "introduction": { "type": "string" }, "name": { "type": "string" }, "status": { "description": "帖子状态", "type": "integer" }, "title": { "description": "帖子标题", "type": "string" }, "vote_num": { "description": "投票数", "type": "integer" } } }, "models.ParamPostList": { "type": "object", "properties": { "community_id": { "description": "可以为空", "type": "integer" }, "order": { "description": "排序依据", "type": "string", "example": "score" }, "page": { "description": "页码", "type": "integer", "example": 1 }, "size": { "description": "每页数据量", "type": "integer", "example": 10 } } } } } ================================================ FILE: lesson77/bluebell/docs/swagger.yaml ================================================ basePath: /api/v1 definitions: controller._ResponsePostList: properties: code: description: 业务响应状态码 type: integer data: description: 数据 items: $ref: '#/definitions/models.ApiPostDetail' type: array message: description: 提示信息 type: string type: object models.ApiPostDetail: properties: author_id: description: 作者id type: integer author_name: description: 作者 type: string community_id: description: 社区id type: integer content: description: 帖子内容 type: string create_time: type: string id: type: integer introduction: type: string name: type: string status: description: 帖子状态 type: integer title: description: 帖子标题 type: string vote_num: description: 投票数 type: integer required: - community_id - content - title type: object models.ParamPostList: properties: community_id: description: 可以为空 type: integer order: description: 排序依据 example: score type: string page: description: 页码 example: 1 type: integer size: description: 每页数据量 example: 10 type: integer type: object host: 127.0.0.1:8084 info: contact: name: liwenzhou url: http://www.liwenzhou.com description: Go web开发进阶项目实战课程bluebell license: {} title: bluebell项目接口文档 version: "1.0" paths: /posts2: get: consumes: - application/json description: 可按社区按时间或分数排序查询帖子列表接口 parameters: - description: Bearer JWT in: header name: Authorization required: true type: string - description: 可以为空 in: query name: community_id type: integer - description: 排序依据 example: score in: query name: order type: string - description: 页码 example: 1 in: query name: page type: integer - description: 每页数据量 example: 10 in: query name: size type: integer produces: - application/json responses: "200": description: OK schema: $ref: '#/definitions/controller._ResponsePostList' security: - ApiKeyAuth: [] summary: 升级版帖子列表接口 tags: - 帖子相关接口(api分组展示使用的) swagger: "2.0" ================================================ FILE: lesson77/bluebell/go.mod ================================================ module bluebell go 1.14 require ( github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 github.com/bwmarrin/snowflake v0.3.0 github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/fsnotify/fsnotify v1.4.9 github.com/gin-contrib/pprof v1.3.0 github.com/gin-gonic/gin v1.6.3 github.com/go-openapi/spec v0.19.9 // indirect github.com/go-openapi/swag v0.19.9 // indirect github.com/go-playground/locales v0.13.0 github.com/go-playground/universal-translator v0.17.0 github.com/go-playground/validator/v10 v10.3.0 github.com/go-redis/redis v6.15.8+incompatible github.com/go-sql-driver/mysql v1.5.0 github.com/jmoiron/sqlx v1.2.0 github.com/json-iterator/go v1.1.10 // indirect github.com/juju/ratelimit v1.0.1 github.com/mailru/easyjson v0.7.6 // indirect github.com/natefinch/lumberjack v2.0.0+incompatible github.com/onsi/ginkgo v1.14.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/spf13/viper v1.7.0 github.com/stretchr/testify v1.4.0 github.com/swaggo/gin-swagger v1.2.0 github.com/swaggo/swag v1.6.7 go.uber.org/zap v1.15.0 golang.org/x/net v0.0.0-20200904194848-62affa334b73 // indirect golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f // indirect golang.org/x/tools v0.0.0-20200904185747-39188db58858 // indirect google.golang.org/protobuf v1.25.0 // indirect gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect ) ================================================ FILE: lesson77/bluebell/go.sum ================================================ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/PuerkitoBio/purell v1.1.0 h1:rmGxhojJlM0tuKtfdvliR84CFHljx9ag64t2xmVkjK4= github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0= github.com/bwmarrin/snowflake v0.3.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/E9WsDpxqwE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/gzip v0.0.1 h1:ezvKOL6jH+jlzdHNE4h9h8q8uMpDQjyl0NN0Jd7jozc= github.com/gin-contrib/gzip v0.0.1/go.mod h1:fGBJBCdt6qCZuCAOwWuFhBB4OOq9EFqlo5dEaFhhu5w= github.com/gin-contrib/pprof v1.3.0 h1:G9eK6HnbkSqDZBYbzG4wrjCsA4e+cvYAHUZw6W+W9K0= github.com/gin-contrib/pprof v1.3.0/go.mod h1:waMjT1H9b179t3CxuG1cV3DHpga6ybizwfBaM5OXaB0= github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.3.0/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y= github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= github.com/gin-gonic/gin v1.6.2/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14= github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-openapi/jsonpointer v0.17.0 h1:nH6xp8XdXHx8dqveo0ZuJBluCO2qGrPbDNZ0dwoRHP0= github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= github.com/go-openapi/jsonreference v0.19.0 h1:BqWKpV1dFd+AuiKlgtddwVIFQsuMpxfBDBHGfM2yNpk= github.com/go-openapi/jsonreference v0.19.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= github.com/go-openapi/jsonreference v0.19.4 h1:3Vw+rh13uq2JFNxgnMTGE1rnoieU9FmyE1gvnyylsYg= github.com/go-openapi/jsonreference v0.19.4/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= github.com/go-openapi/spec v0.19.0 h1:A4SZ6IWh3lnjH0rG0Z5lkxazMGBECtrZcbyYQi+64k4= github.com/go-openapi/spec v0.19.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= github.com/go-openapi/spec v0.19.4/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= github.com/go-openapi/spec v0.19.9 h1:9z9cbFuZJ7AcvOHKIY+f6Aevb4vObNDkTEyoMfO7rAc= github.com/go-openapi/spec v0.19.9/go.mod h1:vqK/dIdLGCosfvYsQV3WfC7N3TiZSnGY2RZKoFK7X28= github.com/go-openapi/swag v0.17.0 h1:iqrgMg7Q7SvtbWLlltPrkMs0UBJI6oTSs79JFRUi880= github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.9 h1:1IxuqvBUU3S2Bi4YC7tlP9SJF1gVpCvqN0T2Qof4azE= github.com/go-openapi/swag v0.19.9/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY= github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= github.com/go-playground/validator/v10 v10.3.0 h1:nZU+7q+yJoFmwvNgv/LnPUkwPal62+b2xXj0AU1Es7o= github.com/go-playground/validator/v10 v10.3.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= github.com/go-redis/redis v6.15.8+incompatible h1:BKZuG6mCnRj5AOaWJXoCgf6rqTYnYJLe4en2hxT7r9o= github.com/go-redis/redis v6.15.8+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA= github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/juju/ratelimit v1.0.1 h1:+7AIFJVQ0EQgq/K9+0Krm7m530Du7tIz0METWzN0RgY= github.com/juju/ratelimit v1.0.1/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329 h1:2gxZ0XQIU/5z3Z3bUBu+FXuk2pFbkN6tcwi/pjyaDic= github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-sqlite3 v1.9.0 h1:pDRiWfl+++eC2FEFRy6jXmQlvp4Yh3z1MJKg4UeYM/4= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/natefinch/lumberjack v2.0.0+incompatible h1:4QJd3OLAMgj7ph+yZTuX13Ld4UpgHp07nNdFX7mqFfM= github.com/natefinch/lumberjack v2.0.0+incompatible/go.mod h1:Wi9p2TTF5DG5oU+6YfsmYQpsTIOm0B1VNzQg9Mw6nPk= github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14 h1:PyYN9JH5jY9j6av01SpfRMb+1DWg/i3MbGOKPxJ2wjM= github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14/go.mod h1:gxQT6pBGRuIGunNf/+tSOB5OHvguWi8Tbt82WOkf35E= github.com/swaggo/gin-swagger v1.2.0 h1:YskZXEiv51fjOMTsXrOetAjrMDfFaXD79PEoQBOe2W0= github.com/swaggo/gin-swagger v1.2.0/go.mod h1:qlH2+W7zXGZkczuL+r2nEBR2JTT+/lX05Nn6vPhc7OI= github.com/swaggo/swag v1.5.1 h1:2Agm8I4K5qb00620mHq0VJ05/KT4FtmALPIcQR9lEZM= github.com/swaggo/swag v1.5.1/go.mod h1:1Bl9F/ZBpVWh22nY0zmYyASPO1lI/zIwRDrpZU+tv8Y= github.com/swaggo/swag v1.6.7 h1:e8GC2xDllJZr3omJkm9YfmK0Y56+rMO3cg0JBKNz09s= github.com/swaggo/swag v1.6.7/go.mod h1:xDhTyuFIujYiN3DKWC/H/83xcfHp+UE/IzWWampG7Zc= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go v1.1.5-pre/go.mod h1:FwP/aQVg39TXzItUBMwnWp9T9gPQnXw4Poh4/oBQZ/0= github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v0.0.0-20181022190402-e5e69e061d4f/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/ugorji/go/codec v1.1.5-pre/go.mod h1:tULtS6Gy1AE1yCENaw4Vb//HLH5njI2tfCQDUqRd8fI= github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli/v2 v2.1.1 h1:Qt8FeAtxE/vfdrLmR3rxR6JRE0RoVmbXu8+6kZtYU4k= github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.15.0 h1:ZZCA22JRF2gQE5FoNmhmrf7jeJJ2uhqDUNRYKm8dvmM= go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190611141213-3f473d35a33a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 h1:AeiKBIuRw3UomYXSbLy0Mc2dDLfdtbT/IVn4keq83P0= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200904194848-62affa334b73 h1:MXfv8rhZWmFeqX3GNZRsd6vOLoaCHjYEX3qkRo3YBUA= golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 h1:DYfZAGf2WMFjMxbgTjaC+2HC7NkNAQs+6Q8b9WEB/F4= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f h1:Fqb3ao1hUmOR3GkUOg/Y+BadLwykBIzs5q8Ez2SbHyc= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606050223-4d9ae51c2468/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190611222205-d73e1c7e250b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc h1:NCy3Ohtk6Iny5V/reW2Ktypo4zIpWBdRJ1uFMjBxdg8= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200904185747-39188db58858 h1:xLt+iB5ksWcZVxqc+g9K41ZHy+6MKWfXCDsjSThnsPA= golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= ================================================ FILE: lesson77/bluebell/init.sql ================================================ -- create the databases CREATE DATABASE IF NOT EXISTS bluebell; ================================================ FILE: lesson77/bluebell/logger/logger.go ================================================ package logger import ( "bluebell/setting" "net" "net/http" "net/http/httputil" "os" "runtime/debug" "strings" "time" "github.com/gin-gonic/gin" "github.com/natefinch/lumberjack" "go.uber.org/zap" "go.uber.org/zap/zapcore" ) var lg *zap.Logger // Init 初始化lg func Init(cfg *setting.LogConfig, mode string) (err error) { writeSyncer := getLogWriter(cfg.Filename, cfg.MaxSize, cfg.MaxBackups, cfg.MaxAge) encoder := getEncoder() var l = new(zapcore.Level) err = l.UnmarshalText([]byte(cfg.Level)) if err != nil { return } var core zapcore.Core if mode == "dev" { // 进入开发模式,日志输出到终端 consoleEncoder := zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig()) core = zapcore.NewTee( zapcore.NewCore(encoder, writeSyncer, l), zapcore.NewCore(consoleEncoder, zapcore.Lock(os.Stdout), zapcore.DebugLevel), ) } else { core = zapcore.NewCore(encoder, writeSyncer, l) } lg = zap.New(core, zap.AddCaller()) zap.ReplaceGlobals(lg) zap.L().Info("init logger success") return } func getEncoder() zapcore.Encoder { encoderConfig := zap.NewProductionEncoderConfig() encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder encoderConfig.TimeKey = "time" encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder encoderConfig.EncodeDuration = zapcore.SecondsDurationEncoder encoderConfig.EncodeCaller = zapcore.ShortCallerEncoder return zapcore.NewJSONEncoder(encoderConfig) } func getLogWriter(filename string, maxSize, maxBackup, maxAge int) zapcore.WriteSyncer { lumberJackLogger := &lumberjack.Logger{ Filename: filename, MaxSize: maxSize, MaxBackups: maxBackup, MaxAge: maxAge, } return zapcore.AddSync(lumberJackLogger) } // GinLogger 接收gin框架默认的日志 func GinLogger() gin.HandlerFunc { return func(c *gin.Context) { start := time.Now() path := c.Request.URL.Path query := c.Request.URL.RawQuery c.Next() cost := time.Since(start) lg.Info(path, zap.Int("status", c.Writer.Status()), zap.String("method", c.Request.Method), zap.String("path", path), zap.String("query", query), zap.String("ip", c.ClientIP()), zap.String("user-agent", c.Request.UserAgent()), zap.String("errors", c.Errors.ByType(gin.ErrorTypePrivate).String()), zap.Duration("cost", cost), ) } } // GinRecovery recover掉项目可能出现的panic,并使用zap记录相关日志 func GinRecovery(stack bool) gin.HandlerFunc { return func(c *gin.Context) { defer func() { if err := recover(); err != nil { // Check for a broken connection, as it is not really a // condition that warrants a panic stack trace. var brokenPipe bool if ne, ok := err.(*net.OpError); ok { if se, ok := ne.Err.(*os.SyscallError); ok { if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") { brokenPipe = true } } } httpRequest, _ := httputil.DumpRequest(c.Request, false) if brokenPipe { lg.Error(c.Request.URL.Path, zap.Any("error", err), zap.String("request", string(httpRequest)), ) // If the connection is dead, we can't write a status to it. c.Error(err.(error)) // nolint: errcheck c.Abort() return } if stack { lg.Error("[Recovery from panic]", zap.Any("error", err), zap.String("request", string(httpRequest)), zap.String("stack", string(debug.Stack())), ) } else { lg.Error("[Recovery from panic]", zap.Any("error", err), zap.String("request", string(httpRequest)), ) } c.AbortWithStatus(http.StatusInternalServerError) } }() c.Next() } } ================================================ FILE: lesson77/bluebell/logic/community.go ================================================ package logic import ( "bluebell/dao/mysql" "bluebell/models" ) func GetCommunityList() ([]*models.Community, error) { // 查数据库 查找到所有的community 并返回 return mysql.GetCommunityList() } func GetCommunityDetail(id int64) (*models.CommunityDetail, error) { return mysql.GetCommunityDetailByID(id) } ================================================ FILE: lesson77/bluebell/logic/post.go ================================================ package logic import ( "bluebell/dao/mysql" "bluebell/dao/redis" "bluebell/models" "bluebell/pkg/snowflake" "go.uber.org/zap" ) func CreatePost(p *models.Post) (err error) { // 1. 生成post id p.ID = snowflake.GenID() // 2. 保存到数据库 err = mysql.CreatePost(p) if err != nil { return err } err = redis.CreatePost(p.ID, p.CommunityID) return // 3. 返回 } // GetPostById 根据帖子id查询帖子详情数据 func GetPostById(pid int64) (data *models.ApiPostDetail, err error) { // 查询并组合我们接口想用的数据 post, err := mysql.GetPostById(pid) if err != nil { zap.L().Error("mysql.GetPostById(pid) failed", zap.Int64("pid", pid), zap.Error(err)) return } // 根据作者id查询作者信息 user, err := mysql.GetUserById(post.AuthorID) if err != nil { zap.L().Error("mysql.GetUserById(post.AuthorID) failed", zap.Int64("author_id", post.AuthorID), zap.Error(err)) return } // 根据社区id查询社区详细信息 community, err := mysql.GetCommunityDetailByID(post.CommunityID) if err != nil { zap.L().Error("mysql.GetUserById(post.AuthorID) failed", zap.Int64("community_id", post.CommunityID), zap.Error(err)) return } // 接口数据拼接 data = &models.ApiPostDetail{ AuthorName: user.Username, Post: post, CommunityDetail: community, } return } // GetPostList 获取帖子列表 func GetPostList(page, size int64) (data []*models.ApiPostDetail, err error) { posts, err := mysql.GetPostList(page, size) if err != nil { return nil, err } data = make([]*models.ApiPostDetail, 0, len(posts)) for _, post := range posts { // 根据作者id查询作者信息 user, err := mysql.GetUserById(post.AuthorID) if err != nil { zap.L().Error("mysql.GetUserById(post.AuthorID) failed", zap.Int64("author_id", post.AuthorID), zap.Error(err)) continue } // 根据社区id查询社区详细信息 community, err := mysql.GetCommunityDetailByID(post.CommunityID) if err != nil { zap.L().Error("mysql.GetUserById(post.AuthorID) failed", zap.Int64("community_id", post.CommunityID), zap.Error(err)) continue } postDetail := &models.ApiPostDetail{ AuthorName: user.Username, Post: post, CommunityDetail: community, } data = append(data, postDetail) } return } func GetPostList2(p *models.ParamPostList) (data []*models.ApiPostDetail, err error) { // 2. 去redis查询id列表 ids, err := redis.GetPostIDsInOrder(p) if err != nil { return } if len(ids) == 0 { zap.L().Warn("redis.GetPostIDsInOrder(p) return 0 data") return } zap.L().Debug("GetPostList2", zap.Any("ids", ids)) // 3. 根据id去MySQL数据库查询帖子详细信息 // 返回的数据还要按照我给定的id的顺序返回 posts, err := mysql.GetPostListByIDs(ids) if err != nil { return } zap.L().Debug("GetPostList2", zap.Any("posts", posts)) // 提前查询好每篇帖子的投票数 voteData, err := redis.GetPostVoteData(ids) if err != nil { return } // 将帖子的作者及分区信息查询出来填充到帖子中 for idx, post := range posts { // 根据作者id查询作者信息 user, err := mysql.GetUserById(post.AuthorID) if err != nil { zap.L().Error("mysql.GetUserById(post.AuthorID) failed", zap.Int64("author_id", post.AuthorID), zap.Error(err)) continue } // 根据社区id查询社区详细信息 community, err := mysql.GetCommunityDetailByID(post.CommunityID) if err != nil { zap.L().Error("mysql.GetUserById(post.AuthorID) failed", zap.Int64("community_id", post.CommunityID), zap.Error(err)) continue } postDetail := &models.ApiPostDetail{ AuthorName: user.Username, VoteNum: voteData[idx], Post: post, CommunityDetail: community, } data = append(data, postDetail) } return } func GetCommunityPostList(p *models.ParamPostList) (data []*models.ApiPostDetail, err error) { // 2. 去redis查询id列表 ids, err := redis.GetCommunityPostIDsInOrder(p) if err != nil { return } if len(ids) == 0 { zap.L().Warn("redis.GetPostIDsInOrder(p) return 0 data") return } zap.L().Debug("GetCommunityPostIDsInOrder", zap.Any("ids", ids)) // 3. 根据id去MySQL数据库查询帖子详细信息 // 返回的数据还要按照我给定的id的顺序返回 posts, err := mysql.GetPostListByIDs(ids) if err != nil { return } zap.L().Debug("GetPostList2", zap.Any("posts", posts)) // 提前查询好每篇帖子的投票数 voteData, err := redis.GetPostVoteData(ids) if err != nil { return } // 将帖子的作者及分区信息查询出来填充到帖子中 for idx, post := range posts { // 根据作者id查询作者信息 user, err := mysql.GetUserById(post.AuthorID) if err != nil { zap.L().Error("mysql.GetUserById(post.AuthorID) failed", zap.Int64("author_id", post.AuthorID), zap.Error(err)) continue } // 根据社区id查询社区详细信息 community, err := mysql.GetCommunityDetailByID(post.CommunityID) if err != nil { zap.L().Error("mysql.GetUserById(post.AuthorID) failed", zap.Int64("community_id", post.CommunityID), zap.Error(err)) continue } postDetail := &models.ApiPostDetail{ AuthorName: user.Username, VoteNum: voteData[idx], Post: post, CommunityDetail: community, } data = append(data, postDetail) } return } // GetPostListNew 将两个查询帖子列表逻辑合二为一的函数 func GetPostListNew(p *models.ParamPostList) (data []*models.ApiPostDetail, err error) { // 根据请求参数的不同,执行不同的逻辑。 if p.CommunityID == 0 { // 查所有 data, err = GetPostList2(p) } else { // 根据社区id查询 data, err = GetCommunityPostList(p) } if err != nil { zap.L().Error("GetPostListNew failed", zap.Error(err)) return nil, err } return } ================================================ FILE: lesson77/bluebell/logic/user.go ================================================ package logic import ( "bluebell/dao/mysql" "bluebell/models" "bluebell/pkg/jwt" "bluebell/pkg/snowflake" ) // 存放业务逻辑的代码 func SignUp(p *models.ParamSignUp) (err error) { // 1.判断用户存不存在 if err := mysql.CheckUserExist(p.Username); err != nil { return err } // 2.生成UID userID := snowflake.GenID() // 构造一个User实例 user := &models.User{ UserID: userID, Username: p.Username, Password: p.Password, } // 3.保存进数据库 return mysql.InsertUser(user) } func Login(p *models.ParamLogin) (user *models.User, err error) { user = &models.User{ Username: p.Username, Password: p.Password, } // 传递的是指针,就能拿到user.UserID if err := mysql.Login(user); err != nil { return nil, err } // 生成JWT token, err := jwt.GenToken(user.UserID, user.Username) if err != nil { return } user.Token = token return } ================================================ FILE: lesson77/bluebell/logic/vote.go ================================================ package logic import ( "bluebell/dao/redis" "bluebell/models" "strconv" "go.uber.org/zap" ) // 推荐阅读 // 基于用户投票的相关算法:http://www.ruanyifeng.com/blog/algorithm/ // 本项目使用简化版的投票分数 // 投一票就加432分 86400/200 --> 200张赞成票可以给你的帖子续一天 /* 投票的几种情况: direction=1时,有两种情况: 1. 之前没有投过票,现在投赞成票 --> 更新分数和投票记录 2. 之前投反对票,现在改投赞成票 --> 更新分数和投票记录 direction=0时,有两种情况: 1. 之前投过赞成票,现在要取消投票 --> 更新分数和投票记录 2. 之前投过反对票,现在要取消投票 --> 更新分数和投票记录 direction=-1时,有两种情况: 1. 之前没有投过票,现在投反对票 --> 更新分数和投票记录 2. 之前投赞成票,现在改投反对票 --> 更新分数和投票记录 投票的限制: 每个贴子自发表之日起一个星期之内允许用户投票,超过一个星期就不允许再投票了。 1. 到期之后将redis中保存的赞成票数及反对票数存储到mysql表中 2. 到期之后删除那个 KeyPostVotedZSetPF */ // VoteForPost 为帖子投票的函数 func VoteForPost(userID int64, p *models.ParamVoteData) error { zap.L().Debug("VoteForPost", zap.Int64("userID", userID), zap.String("postID", p.PostID), zap.Int8("direction", p.Direction)) return redis.VoteForPost(strconv.Itoa(int(userID)), p.PostID, float64(p.Direction)) } ================================================ FILE: lesson77/bluebell/main.go ================================================ package main import ( "bluebell/controller" "bluebell/dao/mysql" "bluebell/dao/redis" "bluebell/logger" "bluebell/pkg/snowflake" "bluebell/router" "bluebell/setting" "fmt" "os" ) // @title bluebell项目接口文档 // @version 1.0 // @description Go web开发进阶项目实战课程bluebell // @contact.name liwenzhou // @contact.url http://www.liwenzhou.com // @host 127.0.0.1:8084 // @BasePath /api/v1 func main() { if len(os.Args) < 2 { fmt.Println("need config file.eg: bluebell config.yaml") return } // 加载配置 if err := setting.Init(os.Args[1]); err != nil { fmt.Printf("load config failed, err:%v\n", err) return } if err := logger.Init(setting.Conf.LogConfig, setting.Conf.Mode); err != nil { fmt.Printf("init logger failed, err:%v\n", err) return } if err := mysql.Init(setting.Conf.MySQLConfig); err != nil { fmt.Printf("init mysql failed, err:%v\n", err) return } defer mysql.Close() // 程序退出关闭数据库连接 if err := redis.Init(setting.Conf.RedisConfig); err != nil { fmt.Printf("init redis failed, err:%v\n", err) return } defer redis.Close() if err := snowflake.Init(setting.Conf.StartTime, setting.Conf.MachineID); err != nil { fmt.Printf("init snowflake failed, err:%v\n", err) return } // 初始化gin框架内置的校验器使用的翻译器 if err := controller.InitTrans("zh"); err != nil { fmt.Printf("init validator trans failed, err:%v\n", err) return } // 注册路由 r := router.SetupRouter(setting.Conf.Mode) err := r.Run(fmt.Sprintf(":%d", setting.Conf.Port)) if err != nil { fmt.Printf("run server failed, err:%v\n", err) return } } ================================================ FILE: lesson77/bluebell/middlewares/auth.go ================================================ package middlewares import ( "bluebell/controller" "bluebell/pkg/jwt" "strings" "github.com/gin-gonic/gin" ) // JWTAuthMiddleware 基于JWT的认证中间件 func JWTAuthMiddleware() func(c *gin.Context) { return func(c *gin.Context) { // 客户端携带Token有三种方式 1.放在请求头 2.放在请求体 3.放在URI // 这里假设Token放在Header的Authorization中,并使用Bearer开头 // Authorization: Bearer xxxxxxx.xxx.xxx / X-TOKEN: xxx.xxx.xx // 这里的具体实现方式要依据你的实际业务情况决定 authHeader := c.Request.Header.Get("Authorization") if authHeader == "" { controller.ResponseError(c, controller.CodeNeedLogin) c.Abort() return } // 按空格分割 parts := strings.SplitN(authHeader, " ", 2) if !(len(parts) == 2 && parts[0] == "Bearer") { controller.ResponseError(c, controller.CodeInvalidToken) c.Abort() return } // parts[1]是获取到的tokenString,我们使用之前定义好的解析JWT的函数来解析它 mc, err := jwt.ParseToken(parts[1]) if err != nil { controller.ResponseError(c, controller.CodeInvalidToken) c.Abort() return } // 将当前请求的userID信息保存到请求的上下文c上 c.Set(controller.CtxUserIDKey, mc.UserID) c.Next() // 后续的处理请求的函数中 可以用过c.Get(CtxUserIDKey) 来获取当前请求的用户信息 } } ================================================ FILE: lesson77/bluebell/middlewares/ratelimit.go ================================================ package middlewares import ( "net/http" "time" "github.com/gin-gonic/gin" "github.com/juju/ratelimit" ) func RateLimitMiddleware(fillInterval time.Duration, cap int64) func(c *gin.Context) { bucket := ratelimit.NewBucket(fillInterval, cap) return func(c *gin.Context) { // 如果取不到令牌就返回响应 //if bucket.Take(1) > 0 { if bucket.TakeAvailable(1) != 1 { c.String(http.StatusOK, "rate limit...") c.Abort() return } // 取到令牌就放行 c.Next() } } ================================================ FILE: lesson77/bluebell/models/community.go ================================================ package models import "time" type Community struct { ID int64 `json:"id" db:"community_id"` Name string `json:"name" db:"community_name"` } type CommunityDetail struct { ID int64 `json:"id" db:"community_id"` Name string `json:"name" db:"community_name"` Introduction string `json:"introduction,omitempty" db:"introduction"` CreateTime time.Time `json:"create_time" db:"create_time"` } ================================================ FILE: lesson77/bluebell/models/create_table.sql ================================================ -- CREATE TABLE `user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `user_id` bigint(20) NOT NULL, `username` varchar(64) COLLATE utf8mb4_general_ci NOT NULL, `password` varchar(64) COLLATE utf8mb4_general_ci NOT NULL, `email` varchar(64) COLLATE utf8mb4_general_ci, `gender` tinyint(4) NOT NULL DEFAULT '0', `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP, `update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `idx_username` (`username`) USING BTREE, UNIQUE KEY `idx_user_id` (`user_id`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; DROP TABLE IF EXISTS `community`; CREATE TABLE `community` ( `id` int(11) NOT NULL AUTO_INCREMENT, `community_id` int(10) unsigned NOT NULL, `community_name` varchar(128) COLLATE utf8mb4_general_ci NOT NULL, `introduction` varchar(256) COLLATE utf8mb4_general_ci NOT NULL, `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `idx_community_id` (`community_id`), UNIQUE KEY `idx_community_name` (`community_name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; INSERT INTO `community` VALUES ('1', '1', 'Go', 'Golang', '2016-11-01 08:10:10', '2016-11-01 08:10:10'); INSERT INTO `community` VALUES ('2', '2', 'leetcode', '刷题刷题刷题', '2020-01-01 08:00:00', '2020-01-01 08:00:00'); INSERT INTO `community` VALUES ('3', '3', 'CS:GO', 'Rush B。。。', '2018-08-07 08:30:00', '2018-08-07 08:30:00'); INSERT INTO `community` VALUES ('4', '4', 'LOL', '欢迎来到英雄联盟!', '2016-01-01 08:00:00', '2016-01-01 08:00:00'); DROP TABLE IF EXISTS `post`; CREATE TABLE `post` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `post_id` bigint(20) NOT NULL COMMENT '帖子id', `title` varchar(128) COLLATE utf8mb4_general_ci NOT NULL COMMENT '标题', `content` varchar(8192) COLLATE utf8mb4_general_ci NOT NULL COMMENT '内容', `author_id` bigint(20) NOT NULL COMMENT '作者的用户id', `community_id` bigint(20) NOT NULL COMMENT '所属社区', `status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '帖子状态', `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), UNIQUE KEY `idx_post_id` (`post_id`), KEY `idx_author_id` (`author_id`), KEY `idx_community_id` (`community_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; ================================================ FILE: lesson77/bluebell/models/params.go ================================================ package models // 定义请求的参数结构体 const ( OrderTime = "time" OrderScore = "score" ) // ParamSignUp 注册请求参数 type ParamSignUp struct { Username string `json:"username" binding:"required"` Password string `json:"password" binding:"required"` RePassword string `json:"re_password" binding:"required,eqfield=Password"` } // ParamLogin 登录请求参数 type ParamLogin struct { Username string `json:"username" binding:"required"` Password string `json:"password" binding:"required"` } // ParamVoteData 投票数据 type ParamVoteData struct { // UserID 从请求中获取当前的用户 PostID string `json:"post_id" binding:"required"` // 贴子id Direction int8 `json:"direction,string" binding:"oneof=1 0 -1" ` // 赞成票(1)还是反对票(-1)取消投票(0) } // ParamPostList 获取帖子列表query string参数 type ParamPostList struct { CommunityID int64 `json:"community_id" form:"community_id"` // 可以为空 Page int64 `json:"page" form:"page" example:"1"` // 页码 Size int64 `json:"size" form:"size" example:"10"` // 每页数据量 Order string `json:"order" form:"order" example:"score"` // 排序依据 } ================================================ FILE: lesson77/bluebell/models/post.go ================================================ package models import "time" // 内存对齐概念 type Post struct { ID int64 `json:"id,string" db:"post_id"` // 帖子id AuthorID int64 `json:"author_id" db:"author_id"` // 作者id CommunityID int64 `json:"community_id" db:"community_id" binding:"required"` // 社区id Status int32 `json:"status" db:"status"` // 帖子状态 Title string `json:"title" db:"title" binding:"required"` // 帖子标题 Content string `json:"content" db:"content" binding:"required"` // 帖子内容 CreateTime time.Time `json:"create_time" db:"create_time"` // 帖子创建时间 } // ApiPostDetail 帖子详情接口的结构体 type ApiPostDetail struct { AuthorName string `json:"author_name"` // 作者 VoteNum int64 `json:"vote_num"` // 投票数 *Post // 嵌入帖子结构体 *CommunityDetail `json:"community"` // 嵌入社区信息 } ================================================ FILE: lesson77/bluebell/models/struct_test.go ================================================ package models import ( "fmt" "testing" "unsafe" ) // Go 内存对齐 type s1 struct { a int8 // 1 b string // 3 c int8 // 1 } type s2 struct { a int8 c int8 // 1 b string // 2 } func TestStruct(t *testing.T) { v1 := s1{ a: 1, b: "七米", c: 2, } v2 := s2{ a: 1, b: "七米", c: 2, } fmt.Println(unsafe.Sizeof(v1), unsafe.Sizeof(v2)) } ================================================ FILE: lesson77/bluebell/models/user.go ================================================ package models type User struct { UserID int64 `db:"user_id"` Username string `db:"username"` Password string `db:"password"` Token string } ================================================ FILE: lesson77/bluebell/pkg/jwt/jwt.go ================================================ package jwt import ( "errors" "time" "github.com/spf13/viper" "github.com/dgrijalva/jwt-go" ) var mySecret = []byte("夏天夏天悄悄过去") // MyClaims 自定义声明结构体并内嵌jwt.StandardClaims // jwt包自带的jwt.StandardClaims只包含了官方字段 // 我们这里需要额外记录一个username字段,所以要自定义结构体 // 如果想要保存更多信息,都可以添加到这个结构体中 type MyClaims struct { UserID int64 `json:"user_id"` Username string `json:"username"` jwt.StandardClaims } // GenToken 生成JWT func GenToken(userID int64, username string) (string, error) { // 创建一个我们自己的声明的数据 c := MyClaims{ userID, "username", // 自定义字段 jwt.StandardClaims{ ExpiresAt: time.Now().Add( time.Duration(viper.GetInt("auth.jwt_expire")) * time.Hour).Unix(), // 过期时间 Issuer: "bluebell", // 签发人 }, } // 使用指定的签名方法创建签名对象 token := jwt.NewWithClaims(jwt.SigningMethodHS256, c) // 使用指定的secret签名并获得完整的编码后的字符串token return token.SignedString(mySecret) } // ParseToken 解析JWT func ParseToken(tokenString string) (*MyClaims, error) { // 解析token var mc = new(MyClaims) token, err := jwt.ParseWithClaims(tokenString, mc, func(token *jwt.Token) (i interface{}, err error) { return mySecret, nil }) if err != nil { return nil, err } if token.Valid { // 校验token return mc, nil } return nil, errors.New("invalid token") } ================================================ FILE: lesson77/bluebell/pkg/snowflake/snowflake.go ================================================ package snowflake import ( "time" sf "github.com/bwmarrin/snowflake" ) var node *sf.Node func Init(startTime string, machineID int64) (err error) { var st time.Time st, err = time.Parse("2006-01-02", startTime) if err != nil { return } sf.Epoch = st.UnixNano() / 1000000 node, err = sf.NewNode(machineID) return } func GenID() int64 { return node.Generate().Int64() } ================================================ FILE: lesson77/bluebell/router/route.go ================================================ package router import ( "bluebell/controller" "bluebell/logger" "bluebell/middlewares" "net/http" ginSwagger "github.com/swaggo/gin-swagger" "github.com/swaggo/gin-swagger/swaggerFiles" _ "bluebell/docs" "github.com/gin-contrib/pprof" "github.com/gin-gonic/gin" ) func SetupRouter(mode string) *gin.Engine { if mode == gin.ReleaseMode { gin.SetMode(gin.ReleaseMode) // gin设置成发布模式 } r := gin.New() //r.Use(logger.GinLogger(), logger.GinRecovery(true), middlewares.RateLimitMiddleware(2*time.Second, 1)) r.Use(logger.GinLogger(), logger.GinRecovery(true)) r.LoadHTMLFiles("./templates/index.html") r.Static("/static", "./static") r.GET("/", func(c *gin.Context) { c.HTML(http.StatusOK, "index.html", nil) }) r.GET("/ping", func(c *gin.Context) { c.String(http.StatusOK, "pong") }) r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) v1 := r.Group("/api/v1") // 注册 v1.POST("/signup", controller.SignUpHandler) // 登录 v1.POST("/login", controller.LoginHandler) v1.Use(middlewares.JWTAuthMiddleware()) // 应用JWT认证中间件 // 根据时间或分数获取帖子列表 v1.GET("/posts2", controller.GetPostListHandler2) v1.GET("/posts", controller.GetPostListHandler) { v1.GET("/community", controller.CommunityHandler) v1.GET("/community/:id", controller.CommunityDetailHandler) v1.POST("/post", controller.CreatePostHandler) v1.GET("/post/:id", controller.GetPostDetailHandler) // 投票 v1.POST("/vote", controller.PostVoteController) } pprof.Register(r) // 注册pprof相关路由 r.NoRoute(func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "msg": "404", }) }) return r } ================================================ FILE: lesson77/bluebell/setting/setting.go ================================================ package setting import ( "fmt" "github.com/fsnotify/fsnotify" "github.com/spf13/viper" ) var Conf = new(AppConfig) type AppConfig struct { Name string `mapstructure:"name"` Mode string `mapstructure:"mode"` Version string `mapstructure:"version"` StartTime string `mapstructure:"start_time"` MachineID int64 `mapstructure:"machine_id"` Port int `mapstructure:"port"` *LogConfig `mapstructure:"log"` *MySQLConfig `mapstructure:"mysql"` *RedisConfig `mapstructure:"redis"` } type MySQLConfig struct { Host string `mapstructure:"host"` User string `mapstructure:"user"` Password string `mapstructure:"password"` DB string `mapstructure:"dbname"` Port int `mapstructure:"port"` MaxOpenConns int `mapstructure:"max_open_conns"` MaxIdleConns int `mapstructure:"max_idle_conns"` } type RedisConfig struct { Host string `mapstructure:"host"` Password string `mapstructure:"password"` Port int `mapstructure:"port"` DB int `mapstructure:"db"` PoolSize int `mapstructure:"pool_size"` MinIdleConns int `mapstructure:"min_idle_conns"` } type LogConfig struct { Level string `mapstructure:"level"` Filename string `mapstructure:"filename"` MaxSize int `mapstructure:"max_size"` MaxAge int `mapstructure:"max_age"` MaxBackups int `mapstructure:"max_backups"` } func Init(filePath string) (err error) { // 方式1:直接指定配置文件路径(相对路径或者绝对路径) // 相对路径:相对执行的可执行文件的相对路径 //viper.SetConfigFile("./conf/config.yaml") // 绝对路径:系统中实际的文件路径 //viper.SetConfigFile("/Users/liwenzhou/Desktop/bluebell/conf/config.yaml") // 方式2:指定配置文件名和配置文件的位置,viper自行查找可用的配置文件 // 配置文件名不需要带后缀 // 配置文件位置可配置多个 //viper.SetConfigName("config") // 指定配置文件名(不带后缀) //viper.AddConfigPath(".") // 指定查找配置文件的路径(这里使用相对路径) //viper.AddConfigPath("./conf") // 指定查找配置文件的路径(这里使用相对路径) // 基本上是配合远程配置中心使用的,告诉viper当前的数据使用什么格式去解析 //viper.SetConfigType("json") viper.SetConfigFile(filePath) err = viper.ReadInConfig() // 读取配置信息 if err != nil { // 读取配置信息失败 fmt.Printf("viper.ReadInConfig failed, err:%v\n", err) return } // 把读取到的配置信息反序列化到 Conf 变量中 if err := viper.Unmarshal(Conf); err != nil { fmt.Printf("viper.Unmarshal failed, err:%v\n", err) } viper.WatchConfig() viper.OnConfigChange(func(in fsnotify.Event) { fmt.Println("配置文件修改了...") if err := viper.Unmarshal(Conf); err != nil { fmt.Printf("viper.Unmarshal failed, err:%v\n", err) } }) return } ================================================ FILE: lesson77/bluebell/static/css/app.6310b918.css ================================================ .header[data-v-75d7d08c]{width:100%;height:48px;position:fixed;background:#fff;display:flex;display:-webkit-flex;align-items:center;top:0;z-index:1}.header .logo[data-v-75d7d08c]{margin-left:10px;height:32px;background:url(../../static/img/logo.da56125f.png) no-repeat;background-size:32px 32px;background-position:0;padding-left:35px;line-height:32px;flex-grow:0;margin-right:16px;cursor:pointer}.header .search[data-v-75d7d08c]{flex-grow:1;margin:0 auto;max-width:690px;position:relative;display:flex;display:-webkit-flex}.header .search .s-logo[data-v-75d7d08c]{width:18px;height:18px;background:url(../../static/img/search.8e85063d.png) no-repeat;background-size:cover;display:inline-block;position:absolute;top:50%;margin-top:-9px;left:15px}.header .search .s-input[data-v-75d7d08c]{flex-grow:1;-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#f6f7f8;border-radius:4px;border:1px solid #edeff1;box-shadow:none;color:#c1c1c1;display:block;height:36px;outline:none;padding:0 16px 0 40px;width:100%}.header .btns[data-v-75d7d08c]{flex-grow:0;margin-left:16px;margin-right:10px;display:flex;display:-webkit-flex;align-items:center}.header .btns .login-btn[data-v-75d7d08c]{border:1px solid transparent;border-radius:4px;box-sizing:border-box;text-align:center;letter-spacing:1px;text-decoration:none;font-size:12px;font-weight:700;letter-spacing:.5px;line-height:24px;text-transform:uppercase;padding:3px 16px;border-color:#0079d3;color:#0079d3;fill:#0079d3;display:inline-block;cursor:pointer}.header .btns .login-btn[data-v-75d7d08c]:first-child{margin-right:5px}.header .btns .login-btn[data-v-75d7d08c]:nth-child(2){margin-right:10px}.header .btns .user[data-v-75d7d08c]{width:auto;height:24px;background:url(../../static/img/avatar.7b0a9835.png) no-repeat;background-size:24px 24px;background-position:0;padding-left:28px;display:flex;display:-webkit-flex;align-items:center;cursor:pointer;padding:12px 12px 12px 28px}.header .btns .user[data-v-75d7d08c]:after{content:"";width:0;height:0;border-top:5px solid #878a8c;border-right:5px solid transparent;border-bottom:5px solid transparent;border-left:5px solid transparent;margin-top:5px;margin-left:10px}.header .btns .dropdown-content[data-v-75d7d08c]{display:none;position:absolute;background-color:#f9f9f9;min-width:160px;box-shadow:0 8px 16px 0 rgba(0,0,0,.2)}.header .btns .dropdown-content a[data-v-75d7d08c]{color:#000;padding:12px 16px;text-decoration:none;display:block;cursor:pointer}.header .btns .dropdown-content a[data-v-75d7d08c]:hover{background-color:#f1f1f1}.header .btns .user-box:hover .dropdown-content[data-v-75d7d08c]{display:block}@font-face{font-family:iconfont;src:url(data:application/vnd.ms-fontobject;base64,FAwAAGwLAAABAAIAAAAAAAIABQMAAAAAAAABAJABAAAAAExQAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAA/zftSwAAAAAAAAAAAAAAAAAAAAAAABAAaQBjAG8AbgBmAG8AbgB0AAAADgBSAGUAZwB1AGwAYQByAAAAFgBWAGUAcgBzAGkAbwBuACAAMQAuADAAAAAQAGkAYwBvAG4AZgBvAG4AdAAAAAAAAAEAAAALAIAAAwAwR1NVQrD+s+0AAAE4AAAAQk9TLzI8fEi5AAABfAAAAFZjbWFwlyE39gAAAfgAAAIKZ2x5ZhJIvHEAAAQYAAAEfGhlYWQZkfxxAAAA4AAAADZoaGVhCN8EiQAAALwAAAAkaG10eCYfAAAAAAHUAAAAJGxvY2EEZgVyAAAEBAAAABRtYXhwARgAXQAAARgAAAAgbmFtZT5U/n0AAAiUAAACbXBvc3Rl44HVAAALBAAAAGUAAQAAA4D/gABcBQAAAP//BQAAAQAAAAAAAAAAAAAAAAAAAAkAAQAAAAEAAEvtN/9fDzz1AAsEAAAAAADauVw6AAAAANq5XDoAAP+ABQADgAAAAAgAAgAAAAAAAAABAAAACQBRAAUAAAAAAAIAAAAKAAoAAAD/AAAAAAAAAAEAAAAKAB4ALAABREZMVAAIAAQAAAAAAAAAAQAAAAFsaWdhAAgAAAABAAAAAQAEAAQAAAABAAgAAQAGAAAAAQAAAAAAAQQ8AZAABQAIAokCzAAAAI8CiQLMAAAB6wAyAQgAAAIABQMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUGZFZABA5gbmiwOA/4AAXAOAAIAAAAABAAAAAAAABAAAAASqAAAEAAAABAAAAAR1AAAEAAAABAAAAAQAAAAFAAAAAAAABQAAAAMAAAAsAAAABAAAAZoAAQAAAAAAlAADAAEAAAAsAAMACgAAAZoABABoAAAAEgAQAAMAAuYG5gjmIuYu5kXmSeaA5ov//wAA5gbmCOYi5i7mReZJ5oDmi///AAAAAAAAAAAAAAAAAAAAAAABABIAEgASABIAEgASABIAEgAAAAEAAgAIAAQABgAHAAUAAwAAAQYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAcAAAAAAAAAAIAADmBgAA5gYAAAABAADmCAAA5ggAAAACAADmIgAA5iIAAAAIAADmLgAA5i4AAAAEAADmRQAA5kUAAAAGAADmSQAA5kkAAAAHAADmgAAA5oAAAAAFAADmiwAA5osAAAADAAAAAAAAADIAVgDYATABhgGuAdYCPgABAAD/gASrA4AAIQAAJSMRNCYrASIGFREjESMiBhURIyIGHQEhNS4BKwERLgErAQMvbRgSgBMYbasSGIASGQSrARgSewEYEqpVAwERGRgS/P8BKxgT/wAZEaurEhgB1hIYAAAAAQAA/4AD7gOAABMAAAUzPwEnNy8BIycHIw8BFwcfATMXAni5OYMyMoM5uXh4uTmDMjKDObl4KbBfmppfsFdXsF+aml+wVwAABQAA/4cD+QOAABkAJAAoACsAUAAAJSMiLgI/ATUBPgEzMhYfAR4BFRQGBwE0BgE3NCYjJyYiDwEXARcBJwE3JyUyFhURDgEHIS4BJxE+ATchHgEUBiMhDgEVERQWFyEyNjURNDYBMwcGCwsGBTMB6gsfEBYjESsRDQsL/g5gAhcIAwUsBg8BM0n+HlABZlD+WF9CAlgQFAE/LfzxLT8CAj8tAfkQFBQQ/g4QFBQQAwgQFBRbAgsUC74IAfgLCxEMMxAjFhEeDP4ABhYCUgcGCTMGBixQ/qhQAW1Y/fkWUOoUEP4kMkIBAUIyAw8yQgEFFiITARoR/PgRGgEWFgHcEBQAAAAEAAD/gARyA4AAFwAgACwANQAAASEOAQcRHgEXMxceATI2PwEzPgE3ES4BATIWFAYiJjQ2BTQ+AR4CDgEjIiYFIiY0NjIWFAYDlv1KX34DA35fw2AKHCAbCmDEX34DA3v+RiEsLEIsLP7fGiwuIgkTJhghLAI2ISwsQiwtA4ADfl/+Vl9+An0MDg4MfQJ+XwGqXn7+qixCLCxCLE0YJhMJIi0tGSwsLEIsLEEtAAAAAAEAAP+fA6ADYwAzAAABJicmJyYnLgIGFRYCBwYWFxY2Jy4BNz4BNz4BFxYXFgYHBhY3PgE3LgEnJgYHDgEHBiYCvgExKEIlKgkhIAkD2S87hJcTBQ83KRUZUhgEFw5tQRcYZQ4cFZLaBAFALwsTAgMTIgwPAVFvZGRWKSMHGBkEEbr+71Gf1RkDCwwmekA7XGQQBwxlhjh4SQsQAhPLdCuQRQ4EES9OJw4GAAABAAD/pgOLA4AAFQAABQYiJwEmNjczET4BMyEyFhcRMx4BBwI2Fz4X/sIWESSqARgSAQASGAGqJBEWPxsbAX4bJQEB1RIZGRL+KwElGwAAAAEAAP+AA4sDWgAVAAABJiIHAQYWFzMRHgEzITI2NxEzPgEnAjYXPhf+whYRJKoBGBIBABIYAaokERYDPxsb/oIbJQH+KxIZGRIB1QElGwAABQAA/4AFAAOAABAAGwAoADQAPQAAEyEeARcRDgEHIS4BJxE+ATcRMSERJQEOAS8BBzU3PgEfAQE+AR8BESEFHgEXDgEHLgEnPgEXMjY0JiIGFBZnBDIsOgEBOiz7ziw6AQE6LAQy/vf+yQ0lEeXKqQ0gD94BPRAsEOb7zgEPPlECAlE+PVECAlE9EhcXIxgYA4ABNin8wCk2AQE2KQNAKTYB/GABG/3+mQ4ECpBYeEEJAQiMAW4PAQ7cAZxiAUw5OksCAks6OUysFiEWFiEWAAAAEgDeAAEAAAAAAAAAFQAAAAEAAAAAAAEACAAVAAEAAAAAAAIABwAdAAEAAAAAAAMACAAkAAEAAAAAAAQACAAsAAEAAAAAAAUACwA0AAEAAAAAAAYACAA/AAEAAAAAAAoAKwBHAAEAAAAAAAsAEwByAAMAAQQJAAAAKgCFAAMAAQQJAAEAEACvAAMAAQQJAAIADgC/AAMAAQQJAAMAEADNAAMAAQQJAAQAEADdAAMAAQQJAAUAFgDtAAMAAQQJAAYAEAEDAAMAAQQJAAoAVgETAAMAAQQJAAsAJgFpCkNyZWF0ZWQgYnkgaWNvbmZvbnQKaWNvbmZvbnRSZWd1bGFyaWNvbmZvbnRpY29uZm9udFZlcnNpb24gMS4waWNvbmZvbnRHZW5lcmF0ZWQgYnkgc3ZnMnR0ZiBmcm9tIEZvbnRlbGxvIHByb2plY3QuaHR0cDovL2ZvbnRlbGxvLmNvbQAKAEMAcgBlAGEAdABlAGQAIABiAHkAIABpAGMAbwBuAGYAbwBuAHQACgBpAGMAbwBuAGYAbwBuAHQAUgBlAGcAdQBsAGEAcgBpAGMAbwBuAGYAbwBuAHQAaQBjAG8AbgBmAG8AbgB0AFYAZQByAHMAaQBvAG4AIAAxAC4AMABpAGMAbwBuAGYAbwBuAHQARwBlAG4AZQByAGEAdABlAGQAIABiAHkAIABzAHYAZwAyAHQAdABmACAAZgByAG8AbQAgAEYAbwBuAHQAZQBsAGwAbwAgAHAAcgBvAGoAZQBjAHQALgBoAHQAdABwADoALwAvAGYAbwBuAHQAZQBsAGwAbwAuAGMAbwBtAAAAAAIAAAAAAAAACgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACQECAQMBBAEFAQYBBwEIAQkBCgADdG9wCnBvbHlnb25yZWQEZWRpdAdjb21tZW50A2hvdARkb3duAnVwBWltYWdlAAAAAAA=);src:url(data:application/vnd.ms-fontobject;base64,FAwAAGwLAAABAAIAAAAAAAIABQMAAAAAAAABAJABAAAAAExQAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAA/zftSwAAAAAAAAAAAAAAAAAAAAAAABAAaQBjAG8AbgBmAG8AbgB0AAAADgBSAGUAZwB1AGwAYQByAAAAFgBWAGUAcgBzAGkAbwBuACAAMQAuADAAAAAQAGkAYwBvAG4AZgBvAG4AdAAAAAAAAAEAAAALAIAAAwAwR1NVQrD+s+0AAAE4AAAAQk9TLzI8fEi5AAABfAAAAFZjbWFwlyE39gAAAfgAAAIKZ2x5ZhJIvHEAAAQYAAAEfGhlYWQZkfxxAAAA4AAAADZoaGVhCN8EiQAAALwAAAAkaG10eCYfAAAAAAHUAAAAJGxvY2EEZgVyAAAEBAAAABRtYXhwARgAXQAAARgAAAAgbmFtZT5U/n0AAAiUAAACbXBvc3Rl44HVAAALBAAAAGUAAQAAA4D/gABcBQAAAP//BQAAAQAAAAAAAAAAAAAAAAAAAAkAAQAAAAEAAEvtN/9fDzz1AAsEAAAAAADauVw6AAAAANq5XDoAAP+ABQADgAAAAAgAAgAAAAAAAAABAAAACQBRAAUAAAAAAAIAAAAKAAoAAAD/AAAAAAAAAAEAAAAKAB4ALAABREZMVAAIAAQAAAAAAAAAAQAAAAFsaWdhAAgAAAABAAAAAQAEAAQAAAABAAgAAQAGAAAAAQAAAAAAAQQ8AZAABQAIAokCzAAAAI8CiQLMAAAB6wAyAQgAAAIABQMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUGZFZABA5gbmiwOA/4AAXAOAAIAAAAABAAAAAAAABAAAAASqAAAEAAAABAAAAAR1AAAEAAAABAAAAAQAAAAFAAAAAAAABQAAAAMAAAAsAAAABAAAAZoAAQAAAAAAlAADAAEAAAAsAAMACgAAAZoABABoAAAAEgAQAAMAAuYG5gjmIuYu5kXmSeaA5ov//wAA5gbmCOYi5i7mReZJ5oDmi///AAAAAAAAAAAAAAAAAAAAAAABABIAEgASABIAEgASABIAEgAAAAEAAgAIAAQABgAHAAUAAwAAAQYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAcAAAAAAAAAAIAADmBgAA5gYAAAABAADmCAAA5ggAAAACAADmIgAA5iIAAAAIAADmLgAA5i4AAAAEAADmRQAA5kUAAAAGAADmSQAA5kkAAAAHAADmgAAA5oAAAAAFAADmiwAA5osAAAADAAAAAAAAADIAVgDYATABhgGuAdYCPgABAAD/gASrA4AAIQAAJSMRNCYrASIGFREjESMiBhURIyIGHQEhNS4BKwERLgErAQMvbRgSgBMYbasSGIASGQSrARgSewEYEqpVAwERGRgS/P8BKxgT/wAZEaurEhgB1hIYAAAAAQAA/4AD7gOAABMAAAUzPwEnNy8BIycHIw8BFwcfATMXAni5OYMyMoM5uXh4uTmDMjKDObl4KbBfmppfsFdXsF+aml+wVwAABQAA/4cD+QOAABkAJAAoACsAUAAAJSMiLgI/ATUBPgEzMhYfAR4BFRQGBwE0BgE3NCYjJyYiDwEXARcBJwE3JyUyFhURDgEHIS4BJxE+ATchHgEUBiMhDgEVERQWFyEyNjURNDYBMwcGCwsGBTMB6gsfEBYjESsRDQsL/g5gAhcIAwUsBg8BM0n+HlABZlD+WF9CAlgQFAE/LfzxLT8CAj8tAfkQFBQQ/g4QFBQQAwgQFBRbAgsUC74IAfgLCxEMMxAjFhEeDP4ABhYCUgcGCTMGBixQ/qhQAW1Y/fkWUOoUEP4kMkIBAUIyAw8yQgEFFiITARoR/PgRGgEWFgHcEBQAAAAEAAD/gARyA4AAFwAgACwANQAAASEOAQcRHgEXMxceATI2PwEzPgE3ES4BATIWFAYiJjQ2BTQ+AR4CDgEjIiYFIiY0NjIWFAYDlv1KX34DA35fw2AKHCAbCmDEX34DA3v+RiEsLEIsLP7fGiwuIgkTJhghLAI2ISwsQiwtA4ADfl/+Vl9+An0MDg4MfQJ+XwGqXn7+qixCLCxCLE0YJhMJIi0tGSwsLEIsLEEtAAAAAAEAAP+fA6ADYwAzAAABJicmJyYnLgIGFRYCBwYWFxY2Jy4BNz4BNz4BFxYXFgYHBhY3PgE3LgEnJgYHDgEHBiYCvgExKEIlKgkhIAkD2S87hJcTBQ83KRUZUhgEFw5tQRcYZQ4cFZLaBAFALwsTAgMTIgwPAVFvZGRWKSMHGBkEEbr+71Gf1RkDCwwmekA7XGQQBwxlhjh4SQsQAhPLdCuQRQ4EES9OJw4GAAABAAD/pgOLA4AAFQAABQYiJwEmNjczET4BMyEyFhcRMx4BBwI2Fz4X/sIWESSqARgSAQASGAGqJBEWPxsbAX4bJQEB1RIZGRL+KwElGwAAAAEAAP+AA4sDWgAVAAABJiIHAQYWFzMRHgEzITI2NxEzPgEnAjYXPhf+whYRJKoBGBIBABIYAaokERYDPxsb/oIbJQH+KxIZGRIB1QElGwAABQAA/4AFAAOAABAAGwAoADQAPQAAEyEeARcRDgEHIS4BJxE+ATcRMSERJQEOAS8BBzU3PgEfAQE+AR8BESEFHgEXDgEHLgEnPgEXMjY0JiIGFBZnBDIsOgEBOiz7ziw6AQE6LAQy/vf+yQ0lEeXKqQ0gD94BPRAsEOb7zgEPPlECAlE+PVECAlE9EhcXIxgYA4ABNin8wCk2AQE2KQNAKTYB/GABG/3+mQ4ECpBYeEEJAQiMAW4PAQ7cAZxiAUw5OksCAks6OUysFiEWFiEWAAAAEgDeAAEAAAAAAAAAFQAAAAEAAAAAAAEACAAVAAEAAAAAAAIABwAdAAEAAAAAAAMACAAkAAEAAAAAAAQACAAsAAEAAAAAAAUACwA0AAEAAAAAAAYACAA/AAEAAAAAAAoAKwBHAAEAAAAAAAsAEwByAAMAAQQJAAAAKgCFAAMAAQQJAAEAEACvAAMAAQQJAAIADgC/AAMAAQQJAAMAEADNAAMAAQQJAAQAEADdAAMAAQQJAAUAFgDtAAMAAQQJAAYAEAEDAAMAAQQJAAoAVgETAAMAAQQJAAsAJgFpCkNyZWF0ZWQgYnkgaWNvbmZvbnQKaWNvbmZvbnRSZWd1bGFyaWNvbmZvbnRpY29uZm9udFZlcnNpb24gMS4waWNvbmZvbnRHZW5lcmF0ZWQgYnkgc3ZnMnR0ZiBmcm9tIEZvbnRlbGxvIHByb2plY3QuaHR0cDovL2ZvbnRlbGxvLmNvbQAKAEMAcgBlAGEAdABlAGQAIABiAHkAIABpAGMAbwBuAGYAbwBuAHQACgBpAGMAbwBuAGYAbwBuAHQAUgBlAGcAdQBsAGEAcgBpAGMAbwBuAGYAbwBuAHQAaQBjAG8AbgBmAG8AbgB0AFYAZQByAHMAaQBvAG4AIAAxAC4AMABpAGMAbwBuAGYAbwBuAHQARwBlAG4AZQByAGEAdABlAGQAIABiAHkAIABzAHYAZwAyAHQAdABmACAAZgByAG8AbQAgAEYAbwBuAHQAZQBsAGwAbwAgAHAAcgBvAGoAZQBjAHQALgBoAHQAdABwADoALwAvAGYAbwBuAHQAZQBsAGwAbwAuAGMAbwBtAAAAAAIAAAAAAAAACgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACQECAQMBBAEFAQYBBwEIAQkBCgADdG9wCnBvbHlnb25yZWQEZWRpdAdjb21tZW50A2hvdARkb3duAnVwBWltYWdlAAAAAAA=#iefix) format("embedded-opentype"),url("data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAAAXwAAsAAAAAC2wAAAWiAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHEIGVgCECgqIfIcWATYCJAMkCxQABCAFhG0HZRunCSMRdntwMpD9RYJ5wtTNaA/lYPNf05ILYQVbigOMr5PFFM7087iZ7+dDCEklBL/zmipy4tWZVixsDpsjVTs4E4G2E1cCgOf/vd23/TOmiQ10WCYBRpBJJ6zNH54B7zvXH7BR+ShHcvaTRztMu2L6shz9sZ/LsyYWir4+6cXs/Zlp+2m3tLyEXecISUSjaIRQIVIpsaEibk8x2y7uwPUAAaAtEwbNzzINUATYbggA2qZpM1AON0EED6FsZMCxlZbXQIhqLxLXAOBV7efRCzGhAAjCDuxGVY359ZD1OcmEeTcP6zAPfHFBADifBIABhAEQAHQFhs3YKIfB6OfGAs0AwGFhRFG/S/ERIV8x/nOT2w1eMjYP4XgZACGG4l8eSUQIAzSArB4K8bCjQoHPSckJ6IkUnEAAfaCNxgg4QQDMhwWyYDGcIALycIIQaALCsqGJj+EASAFaC0APMGaYIIIOVoGmNvbvVh/ZIkaA/VViD3GcQiFhPLF5By0y2GnaOAskjY8WQk6CFPUHWDAKDzB6vY7L6GidhtZotF9Fy2jraKPV2q/UoybbDGM67J89NQR8r6ze4GxRGqx0jtUoK9PWa5t0zXrdQ6atQIw649gDcz6iiflET8tWfpb2MFd2AX6rXGJyIGxwdHg5nRrC5Sr23pYp04xSz8zP6/HcXL3pOH/MeMJwcpNh59H9x4e2HaIdlgduGt/AY3M6fv4TGdYjdjO4FjROl3bKprgxF7t3Xm+bkR+adjs15qnAQh53eE5nnVQNOtPM1gdmjSGN9p2JDB5wMJ6mrWqC2b89qMNuDKqf3R7PEH1bA57EsRgzB3eGdEj3315KDt6K4w7cWSYauZtIIineUm/eF6DToPwT2ibbQfnBMbf5vGvlqDPvwpzPxLzW5DDYjbP81pgftQFJWjle2sKhB0IdXIsUeO4FiYMVUQvXMYf17NASdDhp51Mbovu4SlEZgcMtOiL1fCBUa0TkmL22JOY49dUf9q9ZxQpm3PuMQrLd8ZObbOSK6ifJIOEerbcwtKjyI31lZcP9lfoZcoM8W13vKzaWfRdlZJ/yPiUHJd4S4/5//RrvE8P+/GOyhJIk/0ix8Yp32VhIruMPbCC6uogNdlF1WGcDOruxc+D4iu0DQuTcBhQ0tr6967B25dEcftyKMpVlz4LkpbBVqpVH40PvnzH7GhyvghhVF1iXrS164znRn09LiMMLbERVyvDTTqvKp+SbhqC2SmP1WmVdZuM799dnPdukbKkvevtk9uvcW1fwY3TmqxF5233ZCK5ccfwziM+4F39NetcaQZTdhVo54FqRyx4lWANPdZmf3XaP8TG7LFQWZdmlPeQoxCym/k1ovJ4r/vTVy/rW2axLP37xnintgf93/ffZO4MxXptvVZWq1FUlt09LDrXKumiag2tY2eD7xMon4BIBALh5/D0eCZ7Hf4lG8CiuC09jk9PMg1jzL9gOUj/1N5b2U8wvY8Hbh9fVlHLzQkieBQqxQRG+86sJAcDtFl4MzCWuR/gG0CgmT0u3M+gyRMSqbdpizTSfAfimxznvEyFP3A2AUOQWEESWAkbxowgYYSDEiAMSJRNooQpPZkg9ChgqMQCEeFYAhLUTCF5OAsa6ThEw3gMhhe+AxAYDTYek52Qc9hpzemPsBEfo72AGikr0yUnv/UZ99h3bDZrwH3k2OSizwl37wohcxAHzRVciChRTgE/hZug9QWKyOEg2iaQ6z1XQM2UDhcXJG2MnOIL+DsxAUSnz5eT5/m/UZ99xBOlT+R95NmsHpUyRgPJljYlImzI8X3RFqKiAOndTAJ8URM/LBFLwVhYHyUwZiqmW0zSV1JpVT4R5vQIAGnsQcQiEkQAJEYlEiEJiRMP6oHRK5O+aIuO4EYNG9n166YBR1hPJZqRrXJ3T1oRO42IBAAA=") format("woff2"),url(data:font/woff;base64,d09GRgABAAAAAAfwAAsAAAAAC2wAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABHU1VCAAABCAAAADMAAABCsP6z7U9TLzIAAAE8AAAARAAAAFY8fEi5Y21hcAAAAYAAAACQAAACCpchN/ZnbHlmAAACEAAAA6UAAAR8Eki8cWhlYWQAAAW4AAAALwAAADYZkfxxaGhlYQAABegAAAAeAAAAJAjfBIlobXR4AAAGCAAAABkAAAAkJh8AAGxvY2EAAAYkAAAAFAAAABQEZgVybWF4cAAABjgAAAAfAAAAIAEYAF1uYW1lAAAGWAAAAUUAAAJtPlT+fXBvc3QAAAegAAAATQAAAGVl44HVeJxjYGRgYOBikGPQYWB0cfMJYeBgYGGAAJAMY05meiJQDMoDyrGAaQ4gZoOIAgCKIwNPAHicY2BksWGcwMDKwMHUyXSGgYGhH0IzvmYwYuRgYGBiYGVmwAoC0lxTGByesT3rZm7438AQw9zA0AAUZgTJAQD7aAyweJzlkTsOwjAQRJ/jYCxEgThCapRbpMhNKDlC+rRUHHCuEXa9NHxuwFjP0o681moH2AHZuBg9pAcJ193c1PzMofk9V6vPnMzpVFQ1aNSkWYvWbYNf3oeS9b8f9zqq/V7Y2zQ2Sypfff+nY7tvr6r6dgNPRTWwzaEhaG/GwJPUFOB9c2AbRkvgyWsNyE++qCzQeJx1UkFsG1UQ/fO//TfOOuv9Xnt3HRcnuxvvNnhtq7vOmqSK3VgCqbQgpxJtBAhHkMIpICEOEVUtiyI4lEPFAUSFVE4IEg69tocKARdA6i1IoApOcOgBxIlGTTbMuhVwQf/vzM5o5s2fmUeAkMNRaoeNiEVI1REttwY2LwpHOGNl80fAWmhADUQiWHPTzI80c3Mnb47ypdQOmPkL+G0/w0CUzPz+IdRM7ZCUxA5GwA95kxAyrsF+xxoaIemwC167CY4nOTkwpBkIDbp1Y/FSEFxavLH179/89cHVq4Pr58491JiKOO+yPcQpkTlylNRIP3mz3aBdWIAOhIE+AxUoFrgELQ7tlut4ro1F8HjQ9qqBXhQKSFYDPNGBtlWBAncsBYqioBtWEC2IVgShxGWZp0O4K8+ouiNqYkqWY2WdGhMs7fMchCfjSh9e7sdrgx5dUwvQre//We9S2q3DnlooqLGSSDaB8jkqF+RbE3BPlkU2VB1dVLIx4To9I/FMyLnfjz/rw+bawZ7ev4uZc0EPoBewHOq0bmswLfbviWnQdbijFnCWqfG+XscZGGSW+GQBx4sdSKICRmhUIIi6EGJvuC0I9AK33VaUbnWgQhVwbDed2ImffXDw5GDI2HDw9frkkdny5Po3iXkhfsLy/Z7vx79M+w07o7mm5dNo7KuzEYbHZwdDejGrKNmLdDiA7ReG8XaS0POfMl0tY9frJX+MsFInD3d/jX3CXiIhGq6XnAblRZ1KXDf0yGtAu5NcAy2OvsTC/bhcwq64S2/BsaO96qMZazbDfmwef/tDLZ1rzxdLZ8yUoWyuGOZ55Ujx/Z9ScKIpa5RpdjYHq69tbJyddySzlBI34z9Wr+2WmJx13zxx/PkNVcqef+exrZOySrXv36hdeVxJiebTnsIfvPVTdhlnW0SycdsDN2qHyJTQCnRDhBWQaGR0jPgrXcxtI+uBIMe354TeLZdhWK4C7OZLpXxcg2r5H95fZs8meODaEmDLIW4K8aK2wDV5/4PHEDB+CwHjWgIIu2PAhP+jNMH3qaSM/G+RZUI0ZLHxX1aLY5aoggJNkBZwmDMAiRBWGuMwDKNw2EHUcm1e0F9JBf4SwJJ///YDnQriv+Jvp6ri1+8+n5rN/QzLqq/+dv825DqrlK52lhO5nDcMxzTZCKL5/S/nI0DNTqDeX4fyQfyRkpq8sra1koGJ9+DVHCh34OMX4fTi0ilKTy0tnv5Ct3S8fwMKbd/oAAAAeJxjYGRgYABi77eS5+P5bb4ycLMwgMCtnTFWCPp/AysDcwOQy8HABBIFACtoCjQAeJxjYGRgYG7438AQw8rAwPD/P5AEiqAATgByEgRyAAB4nGNhYGBgWQXEDFBcisQGYlYgBgAgPQFFAAAAAAAAAAAyAFYA2AEwAYYBrgHWAj54nGNgZGBg4GQIZGBlAAEmIOYCQgaG/2A+AwARmQF2AHicZY9NTsMwEIVf+gekEqqoYIfkBWIBKP0Rq25YVGr3XXTfpk6bKokjx63UA3AejsAJOALcgDvwSCebNpbH37x5Y08A3OAHHo7fLfeRPVwyO3INF7gXrlN/EG6QX4SbaONVuEX9TdjHM6bCbXRheYPXuGL2hHdhDx18CNdwjU/hOvUv4Qb5W7iJO/wKt9Dx6sI+5l5XuI1HL/bHVi+cXqnlQcWhySKTOb+CmV7vkoWt0uqca1vEJlODoF9JU51pW91T7NdD5yIVWZOqCas6SYzKrdnq0AUb5/JRrxeJHoQm5Vhj/rbGAo5xBYUlDowxQhhkiMro6DtVZvSvsUPCXntWPc3ndFsU1P9zhQEC9M9cU7qy0nk6T4E9XxtSdXQrbsuelDSRXs1JErJCXta2VELqATZlV44RelzRiT8oZ0j/AAlabsgAAAB4nG3BSw6AIAwFwD4+ItzSSAMkQompMd7ehVtnyNAn0b8IAwsHjwUBKyISWZWZphxPkXFydpybhl1656G2iros9zDX9K1vhYletHgQlQAAAA==) format("woff"),url(data:font/ttf;base64,AAEAAAALAIAAAwAwR1NVQrD+s+0AAAE4AAAAQk9TLzI8fEi5AAABfAAAAFZjbWFwlyE39gAAAfgAAAIKZ2x5ZhJIvHEAAAQYAAAEfGhlYWQZkfxxAAAA4AAAADZoaGVhCN8EiQAAALwAAAAkaG10eCYfAAAAAAHUAAAAJGxvY2EEZgVyAAAEBAAAABRtYXhwARgAXQAAARgAAAAgbmFtZT5U/n0AAAiUAAACbXBvc3Rl44HVAAALBAAAAGUAAQAAA4D/gABcBQAAAP//BQAAAQAAAAAAAAAAAAAAAAAAAAkAAQAAAAEAAEvtGc9fDzz1AAsEAAAAAADauVw6AAAAANq5XDoAAP+ABQADgAAAAAgAAgAAAAAAAAABAAAACQBRAAUAAAAAAAIAAAAKAAoAAAD/AAAAAAAAAAEAAAAKAB4ALAABREZMVAAIAAQAAAAAAAAAAQAAAAFsaWdhAAgAAAABAAAAAQAEAAQAAAABAAgAAQAGAAAAAQAAAAAAAQQ8AZAABQAIAokCzAAAAI8CiQLMAAAB6wAyAQgAAAIABQMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUGZFZABA5gbmiwOA/4AAXAOAAIAAAAABAAAAAAAABAAAAASqAAAEAAAABAAAAAR1AAAEAAAABAAAAAQAAAAFAAAAAAAABQAAAAMAAAAsAAAABAAAAZoAAQAAAAAAlAADAAEAAAAsAAMACgAAAZoABABoAAAAEgAQAAMAAuYG5gjmIuYu5kXmSeaA5ov//wAA5gbmCOYi5i7mReZJ5oDmi///AAAAAAAAAAAAAAAAAAAAAAABABIAEgASABIAEgASABIAEgAAAAEAAgAIAAQABgAHAAUAAwAAAQYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAcAAAAAAAAAAIAADmBgAA5gYAAAABAADmCAAA5ggAAAACAADmIgAA5iIAAAAIAADmLgAA5i4AAAAEAADmRQAA5kUAAAAGAADmSQAA5kkAAAAHAADmgAAA5oAAAAAFAADmiwAA5osAAAADAAAAAAAAADIAVgDYATABhgGuAdYCPgABAAD/gASrA4AAIQAAJSMRNCYrASIGFREjESMiBhURIyIGHQEhNS4BKwERLgErAQMvbRgSgBMYbasSGIASGQSrARgSewEYEqpVAwERGRgS/P8BKxgT/wAZEaurEhgB1hIYAAAAAQAA/4AD7gOAABMAAAUzPwEnNy8BIycHIw8BFwcfATMXAni5OYMyMoM5uXh4uTmDMjKDObl4KbBfmppfsFdXsF+aml+wVwAABQAA/4cD+QOAABkAJAAoACsAUAAAJSMiLgI/ATUBPgEzMhYfAR4BFRQGBwE0BgE3NCYjJyYiDwEXARcBJwE3JyUyFhURDgEHIS4BJxE+ATchHgEUBiMhDgEVERQWFyEyNjURNDYBMwcGCwsGBTMB6gsfEBYjESsRDQsL/g5gAhcIAwUsBg8BM0n+HlABZlD+WF9CAlgQFAE/LfzxLT8CAj8tAfkQFBQQ/g4QFBQQAwgQFBRbAgsUC74IAfgLCxEMMxAjFhEeDP4ABhYCUgcGCTMGBixQ/qhQAW1Y/fkWUOoUEP4kMkIBAUIyAw8yQgEFFiITARoR/PgRGgEWFgHcEBQAAAAEAAD/gARyA4AAFwAgACwANQAAASEOAQcRHgEXMxceATI2PwEzPgE3ES4BATIWFAYiJjQ2BTQ+AR4CDgEjIiYFIiY0NjIWFAYDlv1KX34DA35fw2AKHCAbCmDEX34DA3v+RiEsLEIsLP7fGiwuIgkTJhghLAI2ISwsQiwtA4ADfl/+Vl9+An0MDg4MfQJ+XwGqXn7+qixCLCxCLE0YJhMJIi0tGSwsLEIsLEEtAAAAAAEAAP+fA6ADYwAzAAABJicmJyYnLgIGFRYCBwYWFxY2Jy4BNz4BNz4BFxYXFgYHBhY3PgE3LgEnJgYHDgEHBiYCvgExKEIlKgkhIAkD2S87hJcTBQ83KRUZUhgEFw5tQRcYZQ4cFZLaBAFALwsTAgMTIgwPAVFvZGRWKSMHGBkEEbr+71Gf1RkDCwwmekA7XGQQBwxlhjh4SQsQAhPLdCuQRQ4EES9OJw4GAAABAAD/pgOLA4AAFQAABQYiJwEmNjczET4BMyEyFhcRMx4BBwI2Fz4X/sIWESSqARgSAQASGAGqJBEWPxsbAX4bJQEB1RIZGRL+KwElGwAAAAEAAP+AA4sDWgAVAAABJiIHAQYWFzMRHgEzITI2NxEzPgEnAjYXPhf+whYRJKoBGBIBABIYAaokERYDPxsb/oIbJQH+KxIZGRIB1QElGwAABQAA/4AFAAOAABAAGwAoADQAPQAAEyEeARcRDgEHIS4BJxE+ATcRMSERJQEOAS8BBzU3PgEfAQE+AR8BESEFHgEXDgEHLgEnPgEXMjY0JiIGFBZnBDIsOgEBOiz7ziw6AQE6LAQy/vf+yQ0lEeXKqQ0gD94BPRAsEOb7zgEPPlECAlE+PVECAlE9EhcXIxgYA4ABNin8wCk2AQE2KQNAKTYB/GABG/3+mQ4ECpBYeEEJAQiMAW4PAQ7cAZxiAUw5OksCAks6OUysFiEWFiEWAAAAEgDeAAEAAAAAAAAAFQAAAAEAAAAAAAEACAAVAAEAAAAAAAIABwAdAAEAAAAAAAMACAAkAAEAAAAAAAQACAAsAAEAAAAAAAUACwA0AAEAAAAAAAYACAA/AAEAAAAAAAoAKwBHAAEAAAAAAAsAEwByAAMAAQQJAAAAKgCFAAMAAQQJAAEAEACvAAMAAQQJAAIADgC/AAMAAQQJAAMAEADNAAMAAQQJAAQAEADdAAMAAQQJAAUAFgDtAAMAAQQJAAYAEAEDAAMAAQQJAAoAVgETAAMAAQQJAAsAJgFpCkNyZWF0ZWQgYnkgaWNvbmZvbnQKaWNvbmZvbnRSZWd1bGFyaWNvbmZvbnRpY29uZm9udFZlcnNpb24gMS4waWNvbmZvbnRHZW5lcmF0ZWQgYnkgc3ZnMnR0ZiBmcm9tIEZvbnRlbGxvIHByb2plY3QuaHR0cDovL2ZvbnRlbGxvLmNvbQAKAEMAcgBlAGEAdABlAGQAIABiAHkAIABpAGMAbwBuAGYAbwBuAHQACgBpAGMAbwBuAGYAbwBuAHQAUgBlAGcAdQBsAGEAcgBpAGMAbwBuAGYAbwBuAHQAaQBjAG8AbgBmAG8AbgB0AFYAZQByAHMAaQBvAG4AIAAxAC4AMABpAGMAbwBuAGYAbwBuAHQARwBlAG4AZQByAGEAdABlAGQAIABiAHkAIABzAHYAZwAyAHQAdABmACAAZgByAG8AbQAgAEYAbwBuAHQAZQBsAGwAbwAgAHAAcgBvAGoAZQBjAHQALgBoAHQAdABwADoALwAvAGYAbwBuAHQAZQBsAGwAbwAuAGMAbwBtAAAAAAIAAAAAAAAACgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACQECAQMBBAEFAQYBBwEIAQkBCgADdG9wCnBvbHlnb25yZWQEZWRpdAdjb21tZW50A2hvdARkb3duAnVwBWltYWdlAAAAAAA=) format("truetype"),url(../../static/img/iconfont.cdbe38a0.svg#iconfont) format("svg")}.iconfont{font-family:iconfont!important;font-size:16px;font-style:normal;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.icon-top:before{content:"\e606"}.icon-polygonred:before{content:"\e608"}.icon-edit:before{content:"\e68b"}.icon-comment:before{content:"\e62e"}.icon-hot:before{content:"\e680"}.icon-down:before{content:"\e645"}.icon-up:before{content:"\e649"}.icon-image:before{content:"\e622"}body,html{width:100%;height:100%;font-family:IBMPlexSans,Arial,sans-serif;background:#eee}a,abbr,acronym,address,applet,article,aside,audio,b,big,blockquote,body,button,canvas,caption,center,cite,code,dd,del,details,dfn,div,dl,dt,em,embed,fieldset,figcaption,figure,footer,form,h1,h2,h3,h4,h5,h6,header,hgroup,html,i,iframe,img,input,ins,kbd,label,legend,li,mark,menu,nav,object,ol,output,p,pre,q,ruby,s,samp,section,small,span,strike,strong,sub,summary,sup,table,tbody,td,tfoot,th,thead,time,tr,tt,u,ul,var,video{margin:0;padding:0;border:0;outline:0}a .page,abbr .page,acronym .page,address .page,applet .page,article .page,aside .page,audio .page,b .page,big .page,blockquote .page,body .page,button .page,canvas .page,caption .page,center .page,cite .page,code .page,dd .page,del .page,details .page,dfn .page,div .page,dl .page,dt .page,em .page,embed .page,fieldset .page,figcaption .page,figure .page,footer .page,form .page,h1 .page,h2 .page,h3 .page,h4 .page,h5 .page,h6 .page,header .page,hgroup .page,html .page,i .page,iframe .page,img .page,input .page,ins .page,kbd .page,label .page,legend .page,li .page,mark .page,menu .page,nav .page,object .page,ol .page,output .page,p .page,pre .page,q .page,ruby .page,s .page,samp .page,section .page,small .page,span .page,strike .page,strong .page,sub .page,summary .page,sup .page,table .page,tbody .page,td .page,tfoot .page,th .page,thead .page,time .page,tr .page,tt .page,u .page,ul .page,var .page,video .page{width:100%;height:auto}.content[data-v-2baf73fc]{max-width:100%;box-sizing:border-box;display:flex;flex-direction:row;justify-content:center;margin:48px auto 0;padding:20px 24px}.content .left[data-v-2baf73fc]{width:640px;padding-bottom:10px}.content .left .c-l-title[data-v-2baf73fc]{font-size:14px;font-weight:500;line-height:18px;color:#1a1a1b;text-transform:unset;padding-bottom:10px}.content .left .c-l-header[data-v-2baf73fc]{align-items:center;background-color:#fff;border:1px solid #ccc;border-radius:4px;box-sizing:border-box;display:flex;flex-flow:row nowrap;height:56px;justify-content:flex-start;margin-bottom:16px;padding:0 12px}.content .left .c-l-header .iconfont[data-v-2baf73fc]{margin-right:4px}.content .left .c-l-header .btn-iconfont[data-v-2baf73fc]{display:flex;display:-webkit-flex}.content .left .c-l-header .active[data-v-2baf73fc]{background:#f6f7f8;color:#0079d3;fill:#0079d3;border-radius:20px;height:32px;line-height:32px;margin-right:8px;padding:0 10px}.content .left .c-l-header .new[data-v-2baf73fc]{font-size:14px;margin-right:18px}.content .left .c-l-header .top[data-v-2baf73fc]{font-size:14px}.content .left .c-l-header .btn-publish[data-v-2baf73fc]{width:64px;height:32px;line-height:32px;background-color:#54b351;color:#fff;border:1px solid transparent;border-radius:4px;box-sizing:border-box;text-align:center;margin-left:auto;cursor:pointer}.content .left .c-l-header .sort[data-v-2baf73fc]{margin-left:300px;display:flex;color:#0079d3;display:-webkit-flex;align-items:center}.content .left .c-l-header .sort .sort-triangle[data-v-2baf73fc]{width:0;height:0;border-top:5px solid #0079d3;border-right:5px solid transparent;border-bottom:5px solid transparent;border-left:5px solid transparent;margin-top:5px;margin-left:10px}.content .left .c-l-list .c-l-item[data-v-2baf73fc]{list-style:none;border-radius:4px;padding-left:40px;cursor:pointer;border:1px solid #ccc;margin-bottom:10px;background-color:hsla(0,0%,100%,.8);color:#878a8c;position:relative}.content .left .c-l-list .c-l-item .post[data-v-2baf73fc]{align-items:center;box-sizing:border-box;display:flex;flex-direction:column;height:100%;left:0;padding:8px 4px 8px 0;position:absolute;top:0;width:40px;border-left:4px solid transparent;background:#f8f9fa}.content .left .c-l-list .c-l-item .post .iconfont[data-v-2baf73fc]{margin-right:0}.content .left .c-l-list .c-l-item .post .down[data-v-2baf73fc]{transform:scaleY(-1)}.content .left .c-l-list .c-l-item .post .text[data-v-2baf73fc]{color:#1a1a1b;font-size:12px;font-weight:700;line-height:16px;pointer-events:none;word-break:normal}.content .left .c-l-list .c-l-item .l-container[data-v-2baf73fc]{padding:15px}.content .left .c-l-list .c-l-item .l-container .con-title[data-v-2baf73fc]{color:#000;font-size:18px;font-weight:500;line-height:22px;text-decoration:none;word-break:break-word}.content .left .c-l-list .c-l-item .l-container .con-memo[data-v-2baf73fc]{margin-top:10px;margin-bottom:10px}.content .left .c-l-list .c-l-item .l-container .con-cover[data-v-2baf73fc]{height:512px;width:100%;background:url(https://timgsa.baidu.com/timg?di=7e9061211c23e3ed9f0c4375bb3822dc&image=&imgtype=0&quality=80&sec=1585999647247&size=b9999_10000&src=http%3A%2F%2Fi1.hdslb.com%2Fbfs%2Farchive%2F04d8cda08e170f4a58c18c45a93c539375c22162.jpg) no-repeat;background-size:cover;margin-top:10px;margin-bottom:10px}.content .left .c-l-list .c-l-item .l-container .user-btn[data-v-2baf73fc]{font-size:14px;display:flex;display:-webkit-flex}.content .left .c-l-list .c-l-item .l-container .user-btn .btn-item[data-v-2baf73fc]{display:flex;display:-webkit-flex;margin-right:10px}.content .left .c-l-list .c-l-item .l-container .user-btn .btn-item .iconfont[data-v-2baf73fc]{margin-right:4px}.content .right[data-v-2baf73fc]{width:312px;margin-left:24px;margin-top:28px}.content .right .communities[data-v-2baf73fc]{background-color:#fff;color:#1a1a1b;border:1px solid #ccc;border-radius:4px;overflow:visible;word-wrap:break-word;margin-bottom:20px}.content .right .communities .r-c-title[data-v-2baf73fc]{background-image:linear-gradient(0deg,rgba(0,0,0,.3),transparent);background-color:#0079d3;height:80px;width:100%;color:#fff;font-size:20px;line-height:120px;padding-left:10px;box-sizing:border-box;text-align:center}.content .right .communities .r-c-content .r-c-item[data-v-2baf73fc]{align-items:center;display:flex;display:-webkit-flex;height:48px;padding:0 12px;border-bottom:thin solid #edeff1;font-size:14px}.content .right .communities .r-c-content .r-c-item .index[data-v-2baf73fc]{width:20px;color:#1c1c1c;font-size:14px;font-weight:500;line-height:18px}.content .right .communities .r-c-content .r-c-item .icon[data-v-2baf73fc]{width:32px;height:32px;background-image:url(../../static/img/avatar.7b0a9835.png);background-repeat:no-repeat;background-size:cover;margin-right:20px}.content .right .communities .r-c-content .r-c-item[data-v-2baf73fc]:last-child{border-bottom:none}.content .right .communities .view-all[data-v-2baf73fc]{background-color:#0079d3;border:1px solid transparent;border-radius:4px;box-sizing:border-box;text-align:center;letter-spacing:1px;text-decoration:none;font-size:12px;font-weight:700;letter-spacing:.5px;line-height:24px;text-transform:uppercase;padding:3px 0;width:280px;color:#fff;margin:20px 0 20px 16px}.content .right .r-trending[data-v-2baf73fc]{padding-top:16px;width:312px;background-color:#fff;color:#1a1a1b;fill:#1a1a1b;border:1px solid #ccc;border-radius:4px;overflow:visible;word-wrap:break-word}.content .right .r-trending .r-t-title[data-v-2baf73fc]{font-size:10px;font-weight:700;letter-spacing:.5px;line-height:12px;text-transform:uppercase;background-color:#fff;border-radius:3px 3px 0 0;color:#1a1a1b;display:flex;fill:#1a1a1b;padding:0 12px 12px}.content .right .r-trending .rank[data-v-2baf73fc]{padding:12px}.content .right .r-trending .rank .r-t-cell[data-v-2baf73fc]{display:flex;display:-webkit-flex;align-items:center;justify-content:space-between;margin-bottom:16px}.content .right .r-trending .rank .r-t-cell .r-t-cell-info[data-v-2baf73fc]{display:flex}.content .right .r-trending .rank .r-t-cell .avatar[data-v-2baf73fc]{width:32px;height:32px;background:url(../../static/img/avatar.7b0a9835.png) no-repeat;background-size:cover;margin-right:10px}.content .right .r-trending .rank .r-t-cell .info[data-v-2baf73fc]{margin-right:10px}.content .right .r-trending .rank .r-t-cell .info .info-title[data-v-2baf73fc]{font-size:12px;font-weight:500;line-height:16px;text-overflow:ellipsis;width:144px}.content .right .r-trending .rank .r-t-cell .info .info-num[data-v-2baf73fc]{font-size:12px;font-weight:400;line-height:16px;padding-bottom:4px}.content .right .r-trending .rank .r-t-cell .join-btn[data-v-2baf73fc]{width:106px;height:32px;line-height:32px;background-color:#0079d3;color:#fff;border:1px solid transparent;border-radius:4px;box-sizing:border-box;text-align:center}.content[data-v-4466eba1]{max-width:100%;box-sizing:border-box;display:flex;flex-direction:row;justify-content:center;margin:0 auto;padding:20px 24px;margin-top:48px}.content .left[data-v-4466eba1]{flex-grow:1;max-width:740px;border-radius:4px;word-break:break-word;background:#fff;border:#edeff1;flex:1;margin:32px;margin-right:12px;padding-bottom:30px;position:relative}.content .left .container[data-v-4466eba1]{width:100%;height:auto;position:relative}.content .left .container .post[data-v-4466eba1]{align-items:center;box-sizing:border-box;display:flex;flex-direction:column;height:100%;left:0;padding:8px 4px 8px 0;position:absolute;top:0;width:40px;border-left:4px solid transparent}.content .left .container .post .text[data-v-4466eba1]{color:#1a1a1b;font-size:12px;font-weight:700;line-height:16px;pointer-events:none;word-break:normal}.content .left .container .l-container[data-v-4466eba1]{padding:15px;margin-left:40px}.content .left .container .l-container .con-title[data-v-4466eba1]{color:#000;font-size:18px;font-weight:500;line-height:22px;text-decoration:none;word-break:break-word}.content .left .container .l-container .con-info[data-v-4466eba1]{margin:25px 0;padding:15px 0;border-bottom:1px solid grey}.content .left .container .l-container .con-cover[data-v-4466eba1]{height:512px;width:100%;background:url(https://timgsa.baidu.com/timg?di=7e9061211c23e3ed9f0c4375bb3822dc&image=&imgtype=0&quality=80&sec=1585999647247&size=b9999_10000&src=http%3A%2F%2Fi1.hdslb.com%2Fbfs%2Farchive%2F04d8cda08e170f4a58c18c45a93c539375c22162.jpg) no-repeat;background-size:cover;margin-top:10px;margin-bottom:10px}.content .left .container .l-container .user-btn[data-v-4466eba1]{font-size:12px;display:flex;display:-webkit-flex}.content .left .container .l-container .user-btn .btn-item[data-v-4466eba1]{display:flex;display:-webkit-flex;align-items:center;margin-right:10px}.content .left .container .l-container .user-btn .btn-item .iconfont[data-v-4466eba1]{margin-right:4px}.content .left .comment[data-v-4466eba1]{width:100%;height:auto;position:relative}.content .left .comment .c-left .line[data-v-4466eba1]{border-right:2px solid #edeff1;height:100%;position:absolute;left:20px}.content .left .comment .c-left .c-arrow[data-v-4466eba1]{display:flex;display:-webkit-flex;position:absolute;z-index:2;flex-direction:column;left:12px;background:#fff;padding-bottom:5px}.content .left .comment .c-right[data-v-4466eba1]{margin-left:40px;padding-right:10px}.content .left .comment .c-right .c-user-info[data-v-4466eba1]{margin-bottom:10px}.content .left .comment .c-right .c-user-info .name[data-v-4466eba1]{color:#1c1c1c;font-size:12px;font-weight:400;line-height:16px}.content .left .comment .c-right .c-user-info .num[data-v-4466eba1]{padding-left:4px;font-size:12px;font-weight:400;line-height:16px;color:#7c7c7c}.content .left .comment .c-right .c-content[data-v-4466eba1]{font-family:Noto Sans,Arial,sans-serif;font-size:14px;font-weight:400;line-height:21px;word-break:break-word;color:#1a1a1b}.content .right[data-v-4466eba1]{flex-grow:0;width:312px;margin-top:32px}.content .right .topic-info[data-v-4466eba1]{width:100%;cursor:pointer;background-color:#fff;color:#1a1a1b;border:1px solid #ccc;border-radius:4px;overflow:visible;word-wrap:break-word;padding-bottom:30px}.content .right .topic-info .t-header[data-v-4466eba1]{width:100%;height:34px;background:#0079d3}.content .right .topic-info .t-info[data-v-4466eba1]{padding:0 12px;display:flex;display:-webkit-flex;width:100%;height:54px;align-items:center}.content .right .topic-info .t-info .avatar[data-v-4466eba1]{width:54px;height:54px;background:url(../../static/img/avatar.7b0a9835.png) no-repeat;background-size:cover;margin-right:10px}.content .right .topic-info .t-info .topic-name[data-v-4466eba1]{height:100%;line-height:54px;font-size:16px;font-weight:500}.content .right .topic-info .t-desc[data-v-4466eba1]{font-family:Noto Sans,Arial,sans-serif;font-size:14px;line-height:21px;font-weight:400;word-wrap:break-word;margin-bottom:8px;padding:0 12px}.content .right .topic-info .t-num[data-v-4466eba1]{padding:0 12px 20px 12px;display:flex;display:-webkit-flex;align-items:center;border-bottom:1px solid #edeff1}.content .right .topic-info .t-num .t-num-item[data-v-4466eba1]{list-style:none;display:flex;display:-webkit-flex;flex-direction:column;width:50%}.content .right .topic-info .t-num .t-num-item .number[data-v-4466eba1]{font-size:16px;font-weight:500;line-height:20px}.content .right .topic-info .t-num .t-num-item .unit[data-v-4466eba1]{font-size:12px;font-weight:500;line-height:16px;word-break:break-word}.content .right .topic-info .date[data-v-4466eba1]{font-family:Noto Sans,Arial,sans-serif;font-size:14px;font-weight:400;line-height:18px;margin-top:20px;padding:0 12px}.content .right .topic-info .topic-btn[data-v-4466eba1]{width:286px;height:34px;line-height:34px;color:#fff;margin:12px auto 0 auto;background:#003f6d;border-radius:4px;box-sizing:border-box;margin-left:13px}.content[data-v-7a22bae9]{max-width:100%;box-sizing:border-box;display:flex;flex-direction:row;justify-content:center;margin:0 auto;padding:20px 24px;margin-top:48px}.content .left[data-v-7a22bae9]{flex-grow:1;max-width:740px;word-break:break-word;flex:1;margin:32px;margin-right:12px;padding-bottom:30px;position:relative}.content .left .post-name[data-v-7a22bae9]{padding:4px;margin:16px 0;border-bottom:1px solid #edeff1;display:flex;justify-content:space-between}.content .left .post-name .p-btn[data-v-7a22bae9]{font-size:12px;font-weight:700;letter-spacing:.5px;line-height:24px;text-transform:uppercase;border:none;padding:0;margin-left:10px;color:#0079d3}.content .left .post-name .p-num[data-v-7a22bae9]{font-size:12px;font-weight:400;line-height:16px;background:#878a8c;border-radius:2px;color:#fff;margin-left:4px;padding:1px 3px}.content .left .post-type[data-v-7a22bae9]{box-sizing:border-box;width:300px;height:40px;border-radius:4px;transition:box-shadow .2s ease;box-shadow:0 0 0 0 #fff;border:1px solid #edeff1;background-color:#fff;padding-left:10px;position:relative}.content .left .post-type .post-type-value[data-v-7a22bae9]{font-size:14px;font-weight:500;line-height:40px;width:100%;vertical-align:middle;color:#1c1c1c;background-color:transparent;cursor:pointer}.content .left .post-type .post-type-options[data-v-7a22bae9]{position:absolute;width:100%;background-color:#fff;left:0;z-index:1;border-radius:4px}.content .left .post-type .post-type-options .post-type-cell[data-v-7a22bae9]{margin:14px 8px 5px;font-size:14px;list-style:none;border-bottom:1px solid #edeff1;padding-bottom:8px;color:#1c1c1c;cursor:pointer}.content .left .post-type .p-icon[data-v-7a22bae9]{width:0;height:0;border-top:5px solid #878a8c;border-right:5px solid transparent;border-bottom:5px solid transparent;border-left:5px solid transparent;margin-left:10px;position:absolute;top:50%;right:10px;cursor:pointer}.content .left .post-content[data-v-7a22bae9]{background-color:#fff;margin:10px 0;padding-bottom:15px;border-radius:5px}.content .left .post-content .cat[data-v-7a22bae9]{display:flex;display:-webkit-flex;justify-content:space-between;align-items:center;width:100%;height:53px}.content .left .post-content .cat .cat-item[data-v-7a22bae9]{padding:10px 0;width:50%;height:40px;line-height:40px;text-align:center;list-style:none;border-bottom:1px solid #edeff1;border-right:1px solid #edeff1;color:#878a8c}.content .left .post-content .cat .cat-item .iconfont[data-v-7a22bae9]{margin-right:4px}.content .left .post-content .cat .active[data-v-7a22bae9]{color:#0079d3;font-weight:bolder;background:none}.content .left .post-content .post-sub-container[data-v-7a22bae9]{padding:16px}.content .left .post-content .post-sub-container .post-sub-header[data-v-7a22bae9]{position:relative}.content .left .post-content .post-sub-container .post-sub-header .post-title[data-v-7a22bae9]{resize:none;box-sizing:border-box;overflow:hidden;display:block;width:100%;height:40px;padding:0 0 0 10px;outline:none;border:1px solid #edeff1;border-radius:4px;color:#1c1c1c;font-size:14px;font-weight:400;line-height:40px}.content .left .post-content .post-sub-container .post-sub-header .textarea-num[data-v-7a22bae9]{font-size:10px;font-weight:700;letter-spacing:.5px;line-height:12px;text-transform:uppercase;bottom:12px;color:#878a8c;pointer-events:none;position:absolute;right:12px}.content .left .post-content .post-sub-container .post-text-con[data-v-7a22bae9]{width:100%;height:200px;border:1px solid #edeff1;margin-top:20px}.content .left .post-content .post-sub-container .post-text-con .post-content-t[data-v-7a22bae9]{resize:none;box-sizing:border-box;overflow:hidden;display:block;width:100%;height:200px;padding:12px 8px;outline:none;border:1px solid #edeff1;border-radius:4px;color:#1c1c1c;font-size:14px;font-weight:400;line-height:21px}.content .left .post-content .post-footer[data-v-7a22bae9]{display:flex;display:-webkit-flex;margin:0 16px;justify-content:flex-end}.content .left .post-content .post-footer .sign[data-v-7a22bae9]{display:flex;display:-webkit-flex}.content .left .post-content .post-footer .sign .sign-item[data-v-7a22bae9]{list-style:none;padding:5px 8px;border:1px solid #edeff1;margin-right:10px;color:#878a8c;font-size:12px;font-weight:700}.content .left .post-content .post-footer .btns .btn[data-v-7a22bae9]{border:1px solid transparent;border-radius:4px;box-sizing:border-box;text-align:center;text-decoration:none;font-size:12px;font-weight:700;letter-spacing:.5px;line-height:24px;text-transform:uppercase;padding:3px 16px;background:#0079d3;color:#fff;margin-left:8px;cursor:pointer}.content .left .post-content .alias[data-v-7a22bae9]{background-color:#f6f7f8;border-radius:0 0 6px 6px;border-top:1px solid #edeff1;display:flex;flex-flow:column;padding:8px 16px 21px;position:relative}.content .left .post-content .alias .send-post[data-v-7a22bae9]{font-size:14px;font-weight:500;line-height:18px;color:#1c1c1c;margin-right:4px}.content .left .post-content .alias .connect[data-v-7a22bae9]{font-size:14px;font-weight:500;line-height:18px;color:#0079d3;display:block;margin-right:4px;margin-top:10px}.content .right[data-v-7a22bae9]{flex-grow:0;width:312px;margin-top:62px}.content .right .post-rank[data-v-7a22bae9]{background-color:#fff;border-radius:4px;margin-top:15px;padding:12px}.content .right .post-rank .p-r-title[data-v-7a22bae9]{display:flex;display:-webkit-flex;font-size:16px;font-weight:500;line-height:20px;align-items:center;border-bottom:1px solid #edeff1;color:#1c1c1c;padding-bottom:10px}.content .right .post-rank .p-r-title .p-r-icon[data-v-7a22bae9]{width:40px;height:40px;background:url(../../static/img/avatar.7b0a9835.png) no-repeat;background-size:cover;margin-right:10px}.content .right .post-rank .p-r-content[data-v-7a22bae9]{display:flex;display:-webkit-flex;flex-direction:column}.content .right .post-rank .p-r-content .p-r-item[data-v-7a22bae9]{list-style:none;border-bottom:1px solid #edeff1;color:#1c1c1c;padding:10px 5px}.main[data-v-5d78ac92]{background:#f8f8f8;padding:150px 0}.main .container[data-v-5d78ac92]{width:600px;background:#fff;margin:0 auto;max-width:1200px;padding:20px}.main .container .form-title[data-v-5d78ac92]{margin-bottom:33px;text-align:center}.main .container .form-group[data-v-5d78ac92]{margin:15px}.main .container .form-group label[data-v-5d78ac92]{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:700}.main .container .form-group .form-control[data-v-5d78ac92]{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857143;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px}.main .container .form-btn[data-v-5d78ac92]{display:flex;justify-content:center}.main .container .form-btn .btn[data-v-5d78ac92]{padding:6px 20px;font-size:18px;line-height:1.3333333;border-radius:6px;display:inline-block;margin-bottom:0;font-weight:400;text-align:center;white-space:nowrap;vertical-align:middle;touch-action:manipulation;cursor:pointer;border:1px solid transparent}.main .container .form-btn .btn-info[data-v-5d78ac92]{color:#fff;background-color:#5bc0de;border-color:#46b8da}.main[data-v-61f1d9ff]{background:#f8f8f8;padding:150px 0}.main .container[data-v-61f1d9ff]{width:600px;background:#fff;margin:0 auto;max-width:1200px;padding:20px}.main .container .form-title[data-v-61f1d9ff]{margin-bottom:33px;text-align:center}.main .container .form-group[data-v-61f1d9ff]{margin:15px}.main .container .form-group label[data-v-61f1d9ff]{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:700}.main .container .form-group .form-control[data-v-61f1d9ff]{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857143;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px}.main .container .form-btn[data-v-61f1d9ff]{display:flex;justify-content:center}.main .container .form-btn .btn[data-v-61f1d9ff]{padding:6px 20px;font-size:18px;line-height:1.3333333;border-radius:6px;display:inline-block;margin-bottom:0;font-weight:400;text-align:center;white-space:nowrap;vertical-align:middle;touch-action:manipulation;cursor:pointer;border:1px solid transparent}.main .container .form-btn .btn-info[data-v-61f1d9ff]{color:#fff;background-color:#5bc0de;border-color:#46b8da} ================================================ FILE: lesson77/bluebell/static/js/app.ea0d453e.js ================================================ (function(t){function s(s){for(var n,o,c=s[0],r=s[1],l=s[2],m=0,p=[];mh;h++)if(y=f?b(r(g=t[h])[0],g[1]):b(t[h]),y&&y instanceof u)return y;return new u(!1)}p=d.call(t)}m=p.next;while(!(g=m.call(p)).done)if(y=c(p,b,g.value,f),"object"==typeof y&&y&&y instanceof u)return y;return new u(!1)};f.stop=function(t){return new u(!0,t)}},"23cb":function(t,e,n){var r=n("a691"),o=Math.max,i=Math.min;t.exports=function(t,e){var n=r(t);return n<0?o(n+e,0):i(n,e)}},"23e7":function(t,e,n){var r=n("da84"),o=n("06cf").f,i=n("9112"),a=n("6eeb"),s=n("ce4e"),c=n("e893"),u=n("94ca");t.exports=function(t,e){var n,f,l,p,d,h,v=t.target,y=t.global,m=t.stat;if(f=y?r:m?r[v]||s(v,{}):(r[v]||{}).prototype,f)for(l in e){if(d=e[l],t.noTargetGet?(h=o(f,l),p=h&&h.value):p=f[l],n=u(y?l:v+(m?".":"#")+l,t.forced),!n&&void 0!==p){if(typeof d===typeof p)continue;c(d,p)}(t.sham||p&&p.sham)&&i(d,"sham",!0),a(f,l,d,t)}}},"241c":function(t,e,n){var r=n("ca84"),o=n("7839"),i=o.concat("length","prototype");e.f=Object.getOwnPropertyNames||function(t){return r(t,i)}},2444:function(t,e,n){"use strict";(function(e){var r=n("c532"),o=n("c8af"),i={"Content-Type":"application/x-www-form-urlencoded"};function a(t,e){!r.isUndefined(t)&&r.isUndefined(t["Content-Type"])&&(t["Content-Type"]=e)}function s(){var t;return("undefined"!==typeof XMLHttpRequest||"undefined"!==typeof e&&"[object process]"===Object.prototype.toString.call(e))&&(t=n("b50d")),t}var c={adapter:s(),transformRequest:[function(t,e){return o(e,"Accept"),o(e,"Content-Type"),r.isFormData(t)||r.isArrayBuffer(t)||r.isBuffer(t)||r.isStream(t)||r.isFile(t)||r.isBlob(t)?t:r.isArrayBufferView(t)?t.buffer:r.isURLSearchParams(t)?(a(e,"application/x-www-form-urlencoded;charset=utf-8"),t.toString()):r.isObject(t)?(a(e,"application/json;charset=utf-8"),JSON.stringify(t)):t}],transformResponse:[function(t){if("string"===typeof t)try{t=JSON.parse(t)}catch(e){}return t}],timeout:0,xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN",maxContentLength:-1,validateStatus:function(t){return t>=200&&t<300},headers:{common:{Accept:"application/json, text/plain, */*"}}};r.forEach(["delete","get","head"],(function(t){c.headers[t]={}})),r.forEach(["post","put","patch"],(function(t){c.headers[t]=r.merge(i)})),t.exports=c}).call(this,n("4362"))},2626:function(t,e,n){"use strict";var r=n("d066"),o=n("9bf2"),i=n("b622"),a=n("83ab"),s=i("species");t.exports=function(t){var e=r(t),n=o.f;a&&e&&!e[s]&&n(e,s,{configurable:!0,get:function(){return this}})}},2877:function(t,e,n){"use strict";function r(t,e,n,r,o,i,a,s){var c,u="function"===typeof t?t.options:t;if(e&&(u.render=e,u.staticRenderFns=n,u._compiled=!0),r&&(u.functional=!0),i&&(u._scopeId="data-v-"+i),a?(c=function(t){t=t||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext,t||"undefined"===typeof __VUE_SSR_CONTEXT__||(t=__VUE_SSR_CONTEXT__),o&&o.call(this,t),t&&t._registeredComponents&&t._registeredComponents.add(a)},u._ssrRegister=c):o&&(c=s?function(){o.call(this,this.$root.$options.shadowRoot)}:o),c)if(u.functional){u._injectStyles=c;var f=u.render;u.render=function(t,e){return c.call(e),f(t,e)}}else{var l=u.beforeCreate;u.beforeCreate=l?[].concat(l,c):[c]}return{exports:t,options:u}}n.d(e,"a",(function(){return r}))},"2b0e":function(t,e,n){"use strict";(function(t){ /*! * Vue.js v2.6.11 * (c) 2014-2019 Evan You * Released under the MIT License. */ var n=Object.freeze({});function r(t){return void 0===t||null===t}function o(t){return void 0!==t&&null!==t}function i(t){return!0===t}function a(t){return!1===t}function s(t){return"string"===typeof t||"number"===typeof t||"symbol"===typeof t||"boolean"===typeof t}function c(t){return null!==t&&"object"===typeof t}var u=Object.prototype.toString;function f(t){return"[object Object]"===u.call(t)}function l(t){return"[object RegExp]"===u.call(t)}function p(t){var e=parseFloat(String(t));return e>=0&&Math.floor(e)===e&&isFinite(t)}function d(t){return o(t)&&"function"===typeof t.then&&"function"===typeof t.catch}function h(t){return null==t?"":Array.isArray(t)||f(t)&&t.toString===u?JSON.stringify(t,null,2):String(t)}function v(t){var e=parseFloat(t);return isNaN(e)?t:e}function y(t,e){for(var n=Object.create(null),r=t.split(","),o=0;o-1)return t.splice(n,1)}}var b=Object.prototype.hasOwnProperty;function _(t,e){return b.call(t,e)}function w(t){var e=Object.create(null);return function(n){var r=e[n];return r||(e[n]=t(n))}}var x=/-(\w)/g,C=w((function(t){return t.replace(x,(function(t,e){return e?e.toUpperCase():""}))})),O=w((function(t){return t.charAt(0).toUpperCase()+t.slice(1)})),A=/\B([A-Z])/g,k=w((function(t){return t.replace(A,"-$1").toLowerCase()}));function j(t,e){function n(n){var r=arguments.length;return r?r>1?t.apply(e,arguments):t.call(e,n):t.call(e)}return n._length=t.length,n}function S(t,e){return t.bind(e)}var $=Function.prototype.bind?S:j;function E(t,e){e=e||0;var n=t.length-e,r=new Array(n);while(n--)r[n]=t[n+e];return r}function T(t,e){for(var n in e)t[n]=e[n];return t}function P(t){for(var e={},n=0;n0,nt=Z&&Z.indexOf("edge/")>0,rt=(Z&&Z.indexOf("android"),Z&&/iphone|ipad|ipod|ios/.test(Z)||"ios"===Q),ot=(Z&&/chrome\/\d+/.test(Z),Z&&/phantomjs/.test(Z),Z&&Z.match(/firefox\/(\d+)/)),it={}.watch,at=!1;if(J)try{var st={};Object.defineProperty(st,"passive",{get:function(){at=!0}}),window.addEventListener("test-passive",null,st)}catch(Ca){}var ct=function(){return void 0===K&&(K=!J&&!Y&&"undefined"!==typeof t&&(t["process"]&&"server"===t["process"].env.VUE_ENV)),K},ut=J&&window.__VUE_DEVTOOLS_GLOBAL_HOOK__;function ft(t){return"function"===typeof t&&/native code/.test(t.toString())}var lt,pt="undefined"!==typeof Symbol&&ft(Symbol)&&"undefined"!==typeof Reflect&&ft(Reflect.ownKeys);lt="undefined"!==typeof Set&&ft(Set)?Set:function(){function t(){this.set=Object.create(null)}return t.prototype.has=function(t){return!0===this.set[t]},t.prototype.add=function(t){this.set[t]=!0},t.prototype.clear=function(){this.set=Object.create(null)},t}();var dt=R,ht=0,vt=function(){this.id=ht++,this.subs=[]};vt.prototype.addSub=function(t){this.subs.push(t)},vt.prototype.removeSub=function(t){g(this.subs,t)},vt.prototype.depend=function(){vt.target&&vt.target.addDep(this)},vt.prototype.notify=function(){var t=this.subs.slice();for(var e=0,n=t.length;e-1)if(i&&!_(o,"default"))a=!1;else if(""===a||a===k(t)){var c=te(String,o.type);(c<0||s0&&(a=je(a,(e||"")+"_"+n),ke(a[0])&&ke(u)&&(f[c]=xt(u.text+a[0].text),a.shift()),f.push.apply(f,a)):s(a)?ke(u)?f[c]=xt(u.text+a):""!==a&&f.push(xt(a)):ke(a)&&ke(u)?f[c]=xt(u.text+a.text):(i(t._isVList)&&o(a.tag)&&r(a.key)&&o(e)&&(a.key="__vlist"+e+"_"+n+"__"),f.push(a)));return f}function Se(t){var e=t.$options.provide;e&&(t._provided="function"===typeof e?e.call(t):e)}function $e(t){var e=Ee(t.$options.inject,t);e&&($t(!1),Object.keys(e).forEach((function(n){It(t,n,e[n])})),$t(!0))}function Ee(t,e){if(t){for(var n=Object.create(null),r=pt?Reflect.ownKeys(t):Object.keys(t),o=0;o0,a=t?!!t.$stable:!i,s=t&&t.$key;if(t){if(t._normalized)return t._normalized;if(a&&r&&r!==n&&s===r.$key&&!i&&!r.$hasNormal)return r;for(var c in o={},t)t[c]&&"$"!==c[0]&&(o[c]=Ie(e,c,t[c]))}else o={};for(var u in e)u in o||(o[u]=Me(e,u));return t&&Object.isExtensible(t)&&(t._normalized=o),z(o,"$stable",a),z(o,"$key",s),z(o,"$hasNormal",i),o}function Ie(t,e,n){var r=function(){var t=arguments.length?n.apply(null,arguments):n({});return t=t&&"object"===typeof t&&!Array.isArray(t)?[t]:Ae(t),t&&(0===t.length||1===t.length&&t[0].isComment)?void 0:t};return n.proxy&&Object.defineProperty(t,e,{get:r,enumerable:!0,configurable:!0}),r}function Me(t,e){return function(){return t[e]}}function Ne(t,e){var n,r,i,a,s;if(Array.isArray(t)||"string"===typeof t)for(n=new Array(t.length),r=0,i=t.length;r1?E(n):n;for(var r=E(arguments,1),o='event handler for "'+t+'"',i=0,a=n.length;idocument.createEvent("Event").timeStamp&&(Kn=function(){return Xn.now()})}function Jn(){var t,e;for(Wn=Kn(),Vn=!0,Un.sort((function(t,e){return t.id-e.id})),zn=0;znzn&&Un[n].id>t.id)n--;Un.splice(n+1,0,t)}else Un.push(t);Hn||(Hn=!0,he(Jn))}}var er=0,nr=function(t,e,n,r,o){this.vm=t,o&&(t._watcher=this),t._watchers.push(this),r?(this.deep=!!r.deep,this.user=!!r.user,this.lazy=!!r.lazy,this.sync=!!r.sync,this.before=r.before):this.deep=this.user=this.lazy=this.sync=!1,this.cb=n,this.id=++er,this.active=!0,this.dirty=this.lazy,this.deps=[],this.newDeps=[],this.depIds=new lt,this.newDepIds=new lt,this.expression="","function"===typeof e?this.getter=e:(this.getter=W(e),this.getter||(this.getter=R)),this.value=this.lazy?void 0:this.get()};nr.prototype.get=function(){var t;mt(this);var e=this.vm;try{t=this.getter.call(e,e)}catch(Ca){if(!this.user)throw Ca;ee(Ca,e,'getter for watcher "'+this.expression+'"')}finally{this.deep&&ye(t),gt(),this.cleanupDeps()}return t},nr.prototype.addDep=function(t){var e=t.id;this.newDepIds.has(e)||(this.newDepIds.add(e),this.newDeps.push(t),this.depIds.has(e)||t.addSub(this))},nr.prototype.cleanupDeps=function(){var t=this.deps.length;while(t--){var e=this.deps[t];this.newDepIds.has(e.id)||e.removeSub(this)}var n=this.depIds;this.depIds=this.newDepIds,this.newDepIds=n,this.newDepIds.clear(),n=this.deps,this.deps=this.newDeps,this.newDeps=n,this.newDeps.length=0},nr.prototype.update=function(){this.lazy?this.dirty=!0:this.sync?this.run():tr(this)},nr.prototype.run=function(){if(this.active){var t=this.get();if(t!==this.value||c(t)||this.deep){var e=this.value;if(this.value=t,this.user)try{this.cb.call(this.vm,t,e)}catch(Ca){ee(Ca,this.vm,'callback for watcher "'+this.expression+'"')}else this.cb.call(this.vm,t,e)}}},nr.prototype.evaluate=function(){this.value=this.get(),this.dirty=!1},nr.prototype.depend=function(){var t=this.deps.length;while(t--)this.deps[t].depend()},nr.prototype.teardown=function(){if(this.active){this.vm._isBeingDestroyed||g(this.vm._watchers,this);var t=this.deps.length;while(t--)this.deps[t].removeSub(this);this.active=!1}};var rr={enumerable:!0,configurable:!0,get:R,set:R};function or(t,e,n){rr.get=function(){return this[e][n]},rr.set=function(t){this[e][n]=t},Object.defineProperty(t,n,rr)}function ir(t){t._watchers=[];var e=t.$options;e.props&&ar(t,e.props),e.methods&&hr(t,e.methods),e.data?sr(t):Rt(t._data={},!0),e.computed&&fr(t,e.computed),e.watch&&e.watch!==it&&vr(t,e.watch)}function ar(t,e){var n=t.$options.propsData||{},r=t._props={},o=t.$options._propKeys=[],i=!t.$parent;i||$t(!1);var a=function(i){o.push(i);var a=Jt(i,e,n,t);It(r,i,a),i in t||or(t,"_props",i)};for(var s in e)a(s);$t(!0)}function sr(t){var e=t.$options.data;e=t._data="function"===typeof e?cr(e,t):e||{},f(e)||(e={});var n=Object.keys(e),r=t.$options.props,o=(t.$options.methods,n.length);while(o--){var i=n[o];0,r&&_(r,i)||V(i)||or(t,"_data",i)}Rt(e,!0)}function cr(t,e){mt();try{return t.call(e,e)}catch(Ca){return ee(Ca,e,"data()"),{}}finally{gt()}}var ur={lazy:!0};function fr(t,e){var n=t._computedWatchers=Object.create(null),r=ct();for(var o in e){var i=e[o],a="function"===typeof i?i:i.get;0,r||(n[o]=new nr(t,a||R,R,ur)),o in t||lr(t,o,i)}}function lr(t,e,n){var r=!ct();"function"===typeof n?(rr.get=r?pr(e):dr(n),rr.set=R):(rr.get=n.get?r&&!1!==n.cache?pr(e):dr(n.get):R,rr.set=n.set||R),Object.defineProperty(t,e,rr)}function pr(t){return function(){var e=this._computedWatchers&&this._computedWatchers[t];if(e)return e.dirty&&e.evaluate(),vt.target&&e.depend(),e.value}}function dr(t){return function(){return t.call(this,this)}}function hr(t,e){t.$options.props;for(var n in e)t[n]="function"!==typeof e[n]?R:$(e[n],t)}function vr(t,e){for(var n in e){var r=e[n];if(Array.isArray(r))for(var o=0;o-1)return this;var n=E(arguments,1);return n.unshift(this),"function"===typeof t.install?t.install.apply(t,n):"function"===typeof t&&t.apply(null,n),e.push(t),this}}function Ar(t){t.mixin=function(t){return this.options=Kt(this.options,t),this}}function kr(t){t.cid=0;var e=1;t.extend=function(t){t=t||{};var n=this,r=n.cid,o=t._Ctor||(t._Ctor={});if(o[r])return o[r];var i=t.name||n.options.name;var a=function(t){this._init(t)};return a.prototype=Object.create(n.prototype),a.prototype.constructor=a,a.cid=e++,a.options=Kt(n.options,t),a["super"]=n,a.options.props&&jr(a),a.options.computed&&Sr(a),a.extend=n.extend,a.mixin=n.mixin,a.use=n.use,U.forEach((function(t){a[t]=n[t]})),i&&(a.options.components[i]=a),a.superOptions=n.options,a.extendOptions=t,a.sealedOptions=T({},a.options),o[r]=a,a}}function jr(t){var e=t.options.props;for(var n in e)or(t.prototype,"_props",n)}function Sr(t){var e=t.options.computed;for(var n in e)lr(t.prototype,n,e[n])}function $r(t){U.forEach((function(e){t[e]=function(t,n){return n?("component"===e&&f(n)&&(n.name=n.name||t,n=this.options._base.extend(n)),"directive"===e&&"function"===typeof n&&(n={bind:n,update:n}),this.options[e+"s"][t]=n,n):this.options[e+"s"][t]}}))}function Er(t){return t&&(t.Ctor.options.name||t.tag)}function Tr(t,e){return Array.isArray(t)?t.indexOf(e)>-1:"string"===typeof t?t.split(",").indexOf(e)>-1:!!l(t)&&t.test(e)}function Pr(t,e){var n=t.cache,r=t.keys,o=t._vnode;for(var i in n){var a=n[i];if(a){var s=Er(a.componentOptions);s&&!e(s)&&Rr(n,i,r,o)}}}function Rr(t,e,n,r){var o=t[e];!o||r&&o.tag===r.tag||o.componentInstance.$destroy(),t[e]=null,g(n,e)}br(Cr),mr(Cr),$n(Cr),Rn(Cr),gn(Cr);var Ir=[String,RegExp,Array],Mr={name:"keep-alive",abstract:!0,props:{include:Ir,exclude:Ir,max:[String,Number]},created:function(){this.cache=Object.create(null),this.keys=[]},destroyed:function(){for(var t in this.cache)Rr(this.cache,t,this.keys)},mounted:function(){var t=this;this.$watch("include",(function(e){Pr(t,(function(t){return Tr(e,t)}))})),this.$watch("exclude",(function(e){Pr(t,(function(t){return!Tr(e,t)}))}))},render:function(){var t=this.$slots.default,e=Cn(t),n=e&&e.componentOptions;if(n){var r=Er(n),o=this,i=o.include,a=o.exclude;if(i&&(!r||!Tr(i,r))||a&&r&&Tr(a,r))return e;var s=this,c=s.cache,u=s.keys,f=null==e.key?n.Ctor.cid+(n.tag?"::"+n.tag:""):e.key;c[f]?(e.componentInstance=c[f].componentInstance,g(u,f),u.push(f)):(c[f]=e,u.push(f),this.max&&u.length>parseInt(this.max)&&Rr(c,u[0],u,this._vnode)),e.data.keepAlive=!0}return e||t&&t[0]}},Nr={KeepAlive:Mr};function Lr(t){var e={get:function(){return q}};Object.defineProperty(t,"config",e),t.util={warn:dt,extend:T,mergeOptions:Kt,defineReactive:It},t.set=Mt,t.delete=Nt,t.nextTick=he,t.observable=function(t){return Rt(t),t},t.options=Object.create(null),U.forEach((function(e){t.options[e+"s"]=Object.create(null)})),t.options._base=t,T(t.options.components,Nr),Or(t),Ar(t),kr(t),$r(t)}Lr(Cr),Object.defineProperty(Cr.prototype,"$isServer",{get:ct}),Object.defineProperty(Cr.prototype,"$ssrContext",{get:function(){return this.$vnode&&this.$vnode.ssrContext}}),Object.defineProperty(Cr,"FunctionalRenderContext",{value:Ye}),Cr.version="2.6.11";var Dr=y("style,class"),Fr=y("input,textarea,option,select,progress"),Ur=function(t,e,n){return"value"===n&&Fr(t)&&"button"!==e||"selected"===n&&"option"===t||"checked"===n&&"input"===t||"muted"===n&&"video"===t},Br=y("contenteditable,draggable,spellcheck"),qr=y("events,caret,typing,plaintext-only"),Hr=function(t,e){return Kr(e)||"false"===e?"false":"contenteditable"===t&&qr(e)?e:"true"},Vr=y("allowfullscreen,async,autofocus,autoplay,checked,compact,controls,declare,default,defaultchecked,defaultmuted,defaultselected,defer,disabled,enabled,formnovalidate,hidden,indeterminate,inert,ismap,itemscope,loop,multiple,muted,nohref,noresize,noshade,novalidate,nowrap,open,pauseonexit,readonly,required,reversed,scoped,seamless,selected,sortable,translate,truespeed,typemustmatch,visible"),zr="http://www.w3.org/1999/xlink",Gr=function(t){return":"===t.charAt(5)&&"xlink"===t.slice(0,5)},Wr=function(t){return Gr(t)?t.slice(6,t.length):""},Kr=function(t){return null==t||!1===t};function Xr(t){var e=t.data,n=t,r=t;while(o(r.componentInstance))r=r.componentInstance._vnode,r&&r.data&&(e=Jr(r.data,e));while(o(n=n.parent))n&&n.data&&(e=Jr(e,n.data));return Yr(e.staticClass,e.class)}function Jr(t,e){return{staticClass:Qr(t.staticClass,e.staticClass),class:o(t.class)?[t.class,e.class]:e.class}}function Yr(t,e){return o(t)||o(e)?Qr(t,Zr(e)):""}function Qr(t,e){return t?e?t+" "+e:t:e||""}function Zr(t){return Array.isArray(t)?to(t):c(t)?eo(t):"string"===typeof t?t:""}function to(t){for(var e,n="",r=0,i=t.length;r-1?so[t]=e.constructor===window.HTMLUnknownElement||e.constructor===window.HTMLElement:so[t]=/HTMLUnknownElement/.test(e.toString())}var uo=y("text,number,password,search,email,tel,url");function fo(t){if("string"===typeof t){var e=document.querySelector(t);return e||document.createElement("div")}return t}function lo(t,e){var n=document.createElement(t);return"select"!==t||e.data&&e.data.attrs&&void 0!==e.data.attrs.multiple&&n.setAttribute("multiple","multiple"),n}function po(t,e){return document.createElementNS(no[t],e)}function ho(t){return document.createTextNode(t)}function vo(t){return document.createComment(t)}function yo(t,e,n){t.insertBefore(e,n)}function mo(t,e){t.removeChild(e)}function go(t,e){t.appendChild(e)}function bo(t){return t.parentNode}function _o(t){return t.nextSibling}function wo(t){return t.tagName}function xo(t,e){t.textContent=e}function Co(t,e){t.setAttribute(e,"")}var Oo=Object.freeze({createElement:lo,createElementNS:po,createTextNode:ho,createComment:vo,insertBefore:yo,removeChild:mo,appendChild:go,parentNode:bo,nextSibling:_o,tagName:wo,setTextContent:xo,setStyleScope:Co}),Ao={create:function(t,e){ko(e)},update:function(t,e){t.data.ref!==e.data.ref&&(ko(t,!0),ko(e))},destroy:function(t){ko(t,!0)}};function ko(t,e){var n=t.data.ref;if(o(n)){var r=t.context,i=t.componentInstance||t.elm,a=r.$refs;e?Array.isArray(a[n])?g(a[n],i):a[n]===i&&(a[n]=void 0):t.data.refInFor?Array.isArray(a[n])?a[n].indexOf(i)<0&&a[n].push(i):a[n]=[i]:a[n]=i}}var jo=new bt("",{},[]),So=["create","activate","update","remove","destroy"];function $o(t,e){return t.key===e.key&&(t.tag===e.tag&&t.isComment===e.isComment&&o(t.data)===o(e.data)&&Eo(t,e)||i(t.isAsyncPlaceholder)&&t.asyncFactory===e.asyncFactory&&r(e.asyncFactory.error))}function Eo(t,e){if("input"!==t.tag)return!0;var n,r=o(n=t.data)&&o(n=n.attrs)&&n.type,i=o(n=e.data)&&o(n=n.attrs)&&n.type;return r===i||uo(r)&&uo(i)}function To(t,e,n){var r,i,a={};for(r=e;r<=n;++r)i=t[r].key,o(i)&&(a[i]=r);return a}function Po(t){var e,n,a={},c=t.modules,u=t.nodeOps;for(e=0;ev?(l=r(n[g+1])?null:n[g+1].elm,C(t,l,n,h,g,i)):h>g&&A(e,p,v)}function S(t,e,n,r){for(var i=n;i-1?Ho(t,e,n):Vr(e)?Kr(n)?t.removeAttribute(e):(n="allowfullscreen"===e&&"EMBED"===t.tagName?"true":e,t.setAttribute(e,n)):Br(e)?t.setAttribute(e,Hr(e,n)):Gr(e)?Kr(n)?t.removeAttributeNS(zr,Wr(e)):t.setAttributeNS(zr,e,n):Ho(t,e,n)}function Ho(t,e,n){if(Kr(n))t.removeAttribute(e);else{if(tt&&!et&&"TEXTAREA"===t.tagName&&"placeholder"===e&&""!==n&&!t.__ieph){var r=function(e){e.stopImmediatePropagation(),t.removeEventListener("input",r)};t.addEventListener("input",r),t.__ieph=!0}t.setAttribute(e,n)}}var Vo={create:Bo,update:Bo};function zo(t,e){var n=e.elm,i=e.data,a=t.data;if(!(r(i.staticClass)&&r(i.class)&&(r(a)||r(a.staticClass)&&r(a.class)))){var s=Xr(e),c=n._transitionClasses;o(c)&&(s=Qr(s,Zr(c))),s!==n._prevClass&&(n.setAttribute("class",s),n._prevClass=s)}}var Go,Wo={create:zo,update:zo},Ko="__r",Xo="__c";function Jo(t){if(o(t[Ko])){var e=tt?"change":"input";t[e]=[].concat(t[Ko],t[e]||[]),delete t[Ko]}o(t[Xo])&&(t.change=[].concat(t[Xo],t.change||[]),delete t[Xo])}function Yo(t,e,n){var r=Go;return function o(){var i=e.apply(null,arguments);null!==i&&ti(t,o,n,r)}}var Qo=ae&&!(ot&&Number(ot[1])<=53);function Zo(t,e,n,r){if(Qo){var o=Wn,i=e;e=i._wrapper=function(t){if(t.target===t.currentTarget||t.timeStamp>=o||t.timeStamp<=0||t.target.ownerDocument!==document)return i.apply(this,arguments)}}Go.addEventListener(t,e,at?{capture:n,passive:r}:n)}function ti(t,e,n,r){(r||Go).removeEventListener(t,e._wrapper||e,n)}function ei(t,e){if(!r(t.data.on)||!r(e.data.on)){var n=e.data.on||{},o=t.data.on||{};Go=e.elm,Jo(n),_e(n,o,Zo,ti,Yo,e.context),Go=void 0}}var ni,ri={create:ei,update:ei};function oi(t,e){if(!r(t.data.domProps)||!r(e.data.domProps)){var n,i,a=e.elm,s=t.data.domProps||{},c=e.data.domProps||{};for(n in o(c.__ob__)&&(c=e.data.domProps=T({},c)),s)n in c||(a[n]="");for(n in c){if(i=c[n],"textContent"===n||"innerHTML"===n){if(e.children&&(e.children.length=0),i===s[n])continue;1===a.childNodes.length&&a.removeChild(a.childNodes[0])}if("value"===n&&"PROGRESS"!==a.tagName){a._value=i;var u=r(i)?"":String(i);ii(a,u)&&(a.value=u)}else if("innerHTML"===n&&oo(a.tagName)&&r(a.innerHTML)){ni=ni||document.createElement("div"),ni.innerHTML=""+i+"";var f=ni.firstChild;while(a.firstChild)a.removeChild(a.firstChild);while(f.firstChild)a.appendChild(f.firstChild)}else if(i!==s[n])try{a[n]=i}catch(Ca){}}}}function ii(t,e){return!t.composing&&("OPTION"===t.tagName||ai(t,e)||si(t,e))}function ai(t,e){var n=!0;try{n=document.activeElement!==t}catch(Ca){}return n&&t.value!==e}function si(t,e){var n=t.value,r=t._vModifiers;if(o(r)){if(r.number)return v(n)!==v(e);if(r.trim)return n.trim()!==e.trim()}return n!==e}var ci={create:oi,update:oi},ui=w((function(t){var e={},n=/;(?![^(]*\))/g,r=/:(.+)/;return t.split(n).forEach((function(t){if(t){var n=t.split(r);n.length>1&&(e[n[0].trim()]=n[1].trim())}})),e}));function fi(t){var e=li(t.style);return t.staticStyle?T(t.staticStyle,e):e}function li(t){return Array.isArray(t)?P(t):"string"===typeof t?ui(t):t}function pi(t,e){var n,r={};if(e){var o=t;while(o.componentInstance)o=o.componentInstance._vnode,o&&o.data&&(n=fi(o.data))&&T(r,n)}(n=fi(t.data))&&T(r,n);var i=t;while(i=i.parent)i.data&&(n=fi(i.data))&&T(r,n);return r}var di,hi=/^--/,vi=/\s*!important$/,yi=function(t,e,n){if(hi.test(e))t.style.setProperty(e,n);else if(vi.test(n))t.style.setProperty(k(e),n.replace(vi,""),"important");else{var r=gi(e);if(Array.isArray(n))for(var o=0,i=n.length;o-1?e.split(wi).forEach((function(e){return t.classList.add(e)})):t.classList.add(e);else{var n=" "+(t.getAttribute("class")||"")+" ";n.indexOf(" "+e+" ")<0&&t.setAttribute("class",(n+e).trim())}}function Ci(t,e){if(e&&(e=e.trim()))if(t.classList)e.indexOf(" ")>-1?e.split(wi).forEach((function(e){return t.classList.remove(e)})):t.classList.remove(e),t.classList.length||t.removeAttribute("class");else{var n=" "+(t.getAttribute("class")||"")+" ",r=" "+e+" ";while(n.indexOf(r)>=0)n=n.replace(r," ");n=n.trim(),n?t.setAttribute("class",n):t.removeAttribute("class")}}function Oi(t){if(t){if("object"===typeof t){var e={};return!1!==t.css&&T(e,Ai(t.name||"v")),T(e,t),e}return"string"===typeof t?Ai(t):void 0}}var Ai=w((function(t){return{enterClass:t+"-enter",enterToClass:t+"-enter-to",enterActiveClass:t+"-enter-active",leaveClass:t+"-leave",leaveToClass:t+"-leave-to",leaveActiveClass:t+"-leave-active"}})),ki=J&&!et,ji="transition",Si="animation",$i="transition",Ei="transitionend",Ti="animation",Pi="animationend";ki&&(void 0===window.ontransitionend&&void 0!==window.onwebkittransitionend&&($i="WebkitTransition",Ei="webkitTransitionEnd"),void 0===window.onanimationend&&void 0!==window.onwebkitanimationend&&(Ti="WebkitAnimation",Pi="webkitAnimationEnd"));var Ri=J?window.requestAnimationFrame?window.requestAnimationFrame.bind(window):setTimeout:function(t){return t()};function Ii(t){Ri((function(){Ri(t)}))}function Mi(t,e){var n=t._transitionClasses||(t._transitionClasses=[]);n.indexOf(e)<0&&(n.push(e),xi(t,e))}function Ni(t,e){t._transitionClasses&&g(t._transitionClasses,e),Ci(t,e)}function Li(t,e,n){var r=Fi(t,e),o=r.type,i=r.timeout,a=r.propCount;if(!o)return n();var s=o===ji?Ei:Pi,c=0,u=function(){t.removeEventListener(s,f),n()},f=function(e){e.target===t&&++c>=a&&u()};setTimeout((function(){c0&&(n=ji,f=a,l=i.length):e===Si?u>0&&(n=Si,f=u,l=c.length):(f=Math.max(a,u),n=f>0?a>u?ji:Si:null,l=n?n===ji?i.length:c.length:0);var p=n===ji&&Di.test(r[$i+"Property"]);return{type:n,timeout:f,propCount:l,hasTransform:p}}function Ui(t,e){while(t.length1}function Gi(t,e){!0!==e.data.show&&qi(e)}var Wi=J?{create:Gi,activate:Gi,remove:function(t,e){!0!==t.data.show?Hi(t,e):e()}}:{},Ki=[Vo,Wo,ri,ci,_i,Wi],Xi=Ki.concat(Uo),Ji=Po({nodeOps:Oo,modules:Xi});et&&document.addEventListener("selectionchange",(function(){var t=document.activeElement;t&&t.vmodel&&oa(t,"input")}));var Yi={inserted:function(t,e,n,r){"select"===n.tag?(r.elm&&!r.elm._vOptions?we(n,"postpatch",(function(){Yi.componentUpdated(t,e,n)})):Qi(t,e,n.context),t._vOptions=[].map.call(t.options,ea)):("textarea"===n.tag||uo(t.type))&&(t._vModifiers=e.modifiers,e.modifiers.lazy||(t.addEventListener("compositionstart",na),t.addEventListener("compositionend",ra),t.addEventListener("change",ra),et&&(t.vmodel=!0)))},componentUpdated:function(t,e,n){if("select"===n.tag){Qi(t,e,n.context);var r=t._vOptions,o=t._vOptions=[].map.call(t.options,ea);if(o.some((function(t,e){return!N(t,r[e])}))){var i=t.multiple?e.value.some((function(t){return ta(t,o)})):e.value!==e.oldValue&&ta(e.value,o);i&&oa(t,"change")}}}};function Qi(t,e,n){Zi(t,e,n),(tt||nt)&&setTimeout((function(){Zi(t,e,n)}),0)}function Zi(t,e,n){var r=e.value,o=t.multiple;if(!o||Array.isArray(r)){for(var i,a,s=0,c=t.options.length;s-1,a.selected!==i&&(a.selected=i);else if(N(ea(a),r))return void(t.selectedIndex!==s&&(t.selectedIndex=s));o||(t.selectedIndex=-1)}}function ta(t,e){return e.every((function(e){return!N(e,t)}))}function ea(t){return"_value"in t?t._value:t.value}function na(t){t.target.composing=!0}function ra(t){t.target.composing&&(t.target.composing=!1,oa(t.target,"input"))}function oa(t,e){var n=document.createEvent("HTMLEvents");n.initEvent(e,!0,!0),t.dispatchEvent(n)}function ia(t){return!t.componentInstance||t.data&&t.data.transition?t:ia(t.componentInstance._vnode)}var aa={bind:function(t,e,n){var r=e.value;n=ia(n);var o=n.data&&n.data.transition,i=t.__vOriginalDisplay="none"===t.style.display?"":t.style.display;r&&o?(n.data.show=!0,qi(n,(function(){t.style.display=i}))):t.style.display=r?i:"none"},update:function(t,e,n){var r=e.value,o=e.oldValue;if(!r!==!o){n=ia(n);var i=n.data&&n.data.transition;i?(n.data.show=!0,r?qi(n,(function(){t.style.display=t.__vOriginalDisplay})):Hi(n,(function(){t.style.display="none"}))):t.style.display=r?t.__vOriginalDisplay:"none"}},unbind:function(t,e,n,r,o){o||(t.style.display=t.__vOriginalDisplay)}},sa={model:Yi,show:aa},ca={name:String,appear:Boolean,css:Boolean,mode:String,type:String,enterClass:String,leaveClass:String,enterToClass:String,leaveToClass:String,enterActiveClass:String,leaveActiveClass:String,appearClass:String,appearActiveClass:String,appearToClass:String,duration:[Number,String,Object]};function ua(t){var e=t&&t.componentOptions;return e&&e.Ctor.options.abstract?ua(Cn(e.children)):t}function fa(t){var e={},n=t.$options;for(var r in n.propsData)e[r]=t[r];var o=n._parentListeners;for(var i in o)e[C(i)]=o[i];return e}function la(t,e){if(/\d-keep-alive$/.test(e.tag))return t("keep-alive",{props:e.componentOptions.propsData})}function pa(t){while(t=t.parent)if(t.data.transition)return!0}function da(t,e){return e.key===t.key&&e.tag===t.tag}var ha=function(t){return t.tag||xn(t)},va=function(t){return"show"===t.name},ya={name:"transition",props:ca,abstract:!0,render:function(t){var e=this,n=this.$slots.default;if(n&&(n=n.filter(ha),n.length)){0;var r=this.mode;0;var o=n[0];if(pa(this.$vnode))return o;var i=ua(o);if(!i)return o;if(this._leaving)return la(t,o);var a="__transition-"+this._uid+"-";i.key=null==i.key?i.isComment?a+"comment":a+i.tag:s(i.key)?0===String(i.key).indexOf(a)?i.key:a+i.key:i.key;var c=(i.data||(i.data={})).transition=fa(this),u=this._vnode,f=ua(u);if(i.data.directives&&i.data.directives.some(va)&&(i.data.show=!0),f&&f.data&&!da(i,f)&&!xn(f)&&(!f.componentInstance||!f.componentInstance._vnode.isComment)){var l=f.data.transition=T({},c);if("out-in"===r)return this._leaving=!0,we(l,"afterLeave",(function(){e._leaving=!1,e.$forceUpdate()})),la(t,o);if("in-out"===r){if(xn(i))return u;var p,d=function(){p()};we(c,"afterEnter",d),we(c,"enterCancelled",d),we(l,"delayLeave",(function(t){p=t}))}}return o}}},ma=T({tag:String,moveClass:String},ca);delete ma.mode;var ga={props:ma,beforeMount:function(){var t=this,e=this._update;this._update=function(n,r){var o=Tn(t);t.__patch__(t._vnode,t.kept,!1,!0),t._vnode=t.kept,o(),e.call(t,n,r)}},render:function(t){for(var e=this.tag||this.$vnode.data.tag||"span",n=Object.create(null),r=this.prevChildren=this.children,o=this.$slots.default||[],i=this.children=[],a=fa(this),s=0;sn)e.push(arguments[n++]);return _[++b]=function(){("function"==typeof t?t:Function(t)).apply(void 0,e)},r(b),b},v=function(t){delete _[t]},"process"==c(y)?r=function(t){y.nextTick(C(t))}:g&&g.now?r=function(t){g.now(C(t))}:m&&!p?(o=new m,i=o.port2,o.port1.onmessage=O,r=u(i.postMessage,i,1)):!a.addEventListener||"function"!=typeof postMessage||a.importScripts||s(A)?r=w in l("script")?function(t){f.appendChild(l("script"))[w]=function(){f.removeChild(this),x(t)}}:function(t){setTimeout(C(t),0)}:(r=A,a.addEventListener("message",O,!1))),t.exports={set:h,clear:v}},"2d00":function(t,e,n){var r,o,i=n("da84"),a=n("342f"),s=i.process,c=s&&s.versions,u=c&&c.v8;u?(r=u.split("."),o=r[0]+r[1]):a&&(r=a.match(/Edge\/(\d+)/),(!r||r[1]>=74)&&(r=a.match(/Chrome\/(\d+)/),r&&(o=r[1]))),t.exports=o&&+o},"2d83":function(t,e,n){"use strict";var r=n("387f");t.exports=function(t,e,n,o,i){var a=new Error(t);return r(a,e,n,o,i)}},"2e67":function(t,e,n){"use strict";t.exports=function(t){return!(!t||!t.__CANCEL__)}},"2f62":function(t,e,n){"use strict";(function(t){ /** * vuex v3.1.3 * (c) 2020 Evan You * @license MIT */ function n(t){var e=Number(t.version.split(".")[0]);if(e>=2)t.mixin({beforeCreate:r});else{var n=t.prototype._init;t.prototype._init=function(t){void 0===t&&(t={}),t.init=t.init?[r].concat(t.init):r,n.call(this,t)}}function r(){var t=this.$options;t.store?this.$store="function"===typeof t.store?t.store():t.store:t.parent&&t.parent.$store&&(this.$store=t.parent.$store)}}var r="undefined"!==typeof window?window:"undefined"!==typeof t?t:{},o=r.__VUE_DEVTOOLS_GLOBAL_HOOK__;function i(t){o&&(t._devtoolHook=o,o.emit("vuex:init",t),o.on("vuex:travel-to-state",(function(e){t.replaceState(e)})),t.subscribe((function(t,e){o.emit("vuex:mutation",t,e)})))}function a(t,e){Object.keys(t).forEach((function(n){return e(t[n],n)}))}function s(t){return null!==t&&"object"===typeof t}function c(t){return t&&"function"===typeof t.then}function u(t,e){return function(){return t(e)}}var f=function(t,e){this.runtime=e,this._children=Object.create(null),this._rawModule=t;var n=t.state;this.state=("function"===typeof n?n():n)||{}},l={namespaced:{configurable:!0}};l.namespaced.get=function(){return!!this._rawModule.namespaced},f.prototype.addChild=function(t,e){this._children[t]=e},f.prototype.removeChild=function(t){delete this._children[t]},f.prototype.getChild=function(t){return this._children[t]},f.prototype.update=function(t){this._rawModule.namespaced=t.namespaced,t.actions&&(this._rawModule.actions=t.actions),t.mutations&&(this._rawModule.mutations=t.mutations),t.getters&&(this._rawModule.getters=t.getters)},f.prototype.forEachChild=function(t){a(this._children,t)},f.prototype.forEachGetter=function(t){this._rawModule.getters&&a(this._rawModule.getters,t)},f.prototype.forEachAction=function(t){this._rawModule.actions&&a(this._rawModule.actions,t)},f.prototype.forEachMutation=function(t){this._rawModule.mutations&&a(this._rawModule.mutations,t)},Object.defineProperties(f.prototype,l);var p=function(t){this.register([],t,!1)};function d(t,e,n){if(e.update(n),n.modules)for(var r in n.modules){if(!e.getChild(r))return void 0;d(t.concat(r),e.getChild(r),n.modules[r])}}p.prototype.get=function(t){return t.reduce((function(t,e){return t.getChild(e)}),this.root)},p.prototype.getNamespace=function(t){var e=this.root;return t.reduce((function(t,n){return e=e.getChild(n),t+(e.namespaced?n+"/":"")}),"")},p.prototype.update=function(t){d([],this.root,t)},p.prototype.register=function(t,e,n){var r=this;void 0===n&&(n=!0);var o=new f(e,n);if(0===t.length)this.root=o;else{var i=this.get(t.slice(0,-1));i.addChild(t[t.length-1],o)}e.modules&&a(e.modules,(function(e,o){r.register(t.concat(o),e,n)}))},p.prototype.unregister=function(t){var e=this.get(t.slice(0,-1)),n=t[t.length-1];e.getChild(n).runtime&&e.removeChild(n)};var h;var v=function(t){var e=this;void 0===t&&(t={}),!h&&"undefined"!==typeof window&&window.Vue&&$(window.Vue);var n=t.plugins;void 0===n&&(n=[]);var r=t.strict;void 0===r&&(r=!1),this._committing=!1,this._actions=Object.create(null),this._actionSubscribers=[],this._mutations=Object.create(null),this._wrappedGetters=Object.create(null),this._modules=new p(t),this._modulesNamespaceMap=Object.create(null),this._subscribers=[],this._watcherVM=new h,this._makeLocalGettersCache=Object.create(null);var o=this,a=this,s=a.dispatch,c=a.commit;this.dispatch=function(t,e){return s.call(o,t,e)},this.commit=function(t,e,n){return c.call(o,t,e,n)},this.strict=r;var u=this._modules.root.state;_(this,u,[],this._modules.root),b(this,u),n.forEach((function(t){return t(e)}));var f=void 0!==t.devtools?t.devtools:h.config.devtools;f&&i(this)},y={state:{configurable:!0}};function m(t,e){return e.indexOf(t)<0&&e.push(t),function(){var n=e.indexOf(t);n>-1&&e.splice(n,1)}}function g(t,e){t._actions=Object.create(null),t._mutations=Object.create(null),t._wrappedGetters=Object.create(null),t._modulesNamespaceMap=Object.create(null);var n=t.state;_(t,n,[],t._modules.root,!0),b(t,n,e)}function b(t,e,n){var r=t._vm;t.getters={},t._makeLocalGettersCache=Object.create(null);var o=t._wrappedGetters,i={};a(o,(function(e,n){i[n]=u(e,t),Object.defineProperty(t.getters,n,{get:function(){return t._vm[n]},enumerable:!0})}));var s=h.config.silent;h.config.silent=!0,t._vm=new h({data:{$$state:e},computed:i}),h.config.silent=s,t.strict&&k(t),r&&(n&&t._withCommit((function(){r._data.$$state=null})),h.nextTick((function(){return r.$destroy()})))}function _(t,e,n,r,o){var i=!n.length,a=t._modules.getNamespace(n);if(r.namespaced&&(t._modulesNamespaceMap[a],t._modulesNamespaceMap[a]=r),!i&&!o){var s=j(e,n.slice(0,-1)),c=n[n.length-1];t._withCommit((function(){h.set(s,c,r.state)}))}var u=r.context=w(t,a,n);r.forEachMutation((function(e,n){var r=a+n;C(t,r,e,u)})),r.forEachAction((function(e,n){var r=e.root?n:a+n,o=e.handler||e;O(t,r,o,u)})),r.forEachGetter((function(e,n){var r=a+n;A(t,r,e,u)})),r.forEachChild((function(r,i){_(t,e,n.concat(i),r,o)}))}function w(t,e,n){var r=""===e,o={dispatch:r?t.dispatch:function(n,r,o){var i=S(n,r,o),a=i.payload,s=i.options,c=i.type;return s&&s.root||(c=e+c),t.dispatch(c,a)},commit:r?t.commit:function(n,r,o){var i=S(n,r,o),a=i.payload,s=i.options,c=i.type;s&&s.root||(c=e+c),t.commit(c,a,s)}};return Object.defineProperties(o,{getters:{get:r?function(){return t.getters}:function(){return x(t,e)}},state:{get:function(){return j(t.state,n)}}}),o}function x(t,e){if(!t._makeLocalGettersCache[e]){var n={},r=e.length;Object.keys(t.getters).forEach((function(o){if(o.slice(0,r)===e){var i=o.slice(r);Object.defineProperty(n,i,{get:function(){return t.getters[o]},enumerable:!0})}})),t._makeLocalGettersCache[e]=n}return t._makeLocalGettersCache[e]}function C(t,e,n,r){var o=t._mutations[e]||(t._mutations[e]=[]);o.push((function(e){n.call(t,r.state,e)}))}function O(t,e,n,r){var o=t._actions[e]||(t._actions[e]=[]);o.push((function(e){var o=n.call(t,{dispatch:r.dispatch,commit:r.commit,getters:r.getters,state:r.state,rootGetters:t.getters,rootState:t.state},e);return c(o)||(o=Promise.resolve(o)),t._devtoolHook?o.catch((function(e){throw t._devtoolHook.emit("vuex:error",e),e})):o}))}function A(t,e,n,r){t._wrappedGetters[e]||(t._wrappedGetters[e]=function(t){return n(r.state,r.getters,t.state,t.getters)})}function k(t){t._vm.$watch((function(){return this._data.$$state}),(function(){0}),{deep:!0,sync:!0})}function j(t,e){return e.reduce((function(t,e){return t[e]}),t)}function S(t,e,n){return s(t)&&t.type&&(n=e,e=t,t=t.type),{type:t,payload:e,options:n}}function $(t){h&&t===h||(h=t,n(h))}y.state.get=function(){return this._vm._data.$$state},y.state.set=function(t){0},v.prototype.commit=function(t,e,n){var r=this,o=S(t,e,n),i=o.type,a=o.payload,s=(o.options,{type:i,payload:a}),c=this._mutations[i];c&&(this._withCommit((function(){c.forEach((function(t){t(a)}))})),this._subscribers.slice().forEach((function(t){return t(s,r.state)})))},v.prototype.dispatch=function(t,e){var n=this,r=S(t,e),o=r.type,i=r.payload,a={type:o,payload:i},s=this._actions[o];if(s){try{this._actionSubscribers.slice().filter((function(t){return t.before})).forEach((function(t){return t.before(a,n.state)}))}catch(u){0}var c=s.length>1?Promise.all(s.map((function(t){return t(i)}))):s[0](i);return c.then((function(t){try{n._actionSubscribers.filter((function(t){return t.after})).forEach((function(t){return t.after(a,n.state)}))}catch(u){0}return t}))}},v.prototype.subscribe=function(t){return m(t,this._subscribers)},v.prototype.subscribeAction=function(t){var e="function"===typeof t?{before:t}:t;return m(e,this._actionSubscribers)},v.prototype.watch=function(t,e,n){var r=this;return this._watcherVM.$watch((function(){return t(r.state,r.getters)}),e,n)},v.prototype.replaceState=function(t){var e=this;this._withCommit((function(){e._vm._data.$$state=t}))},v.prototype.registerModule=function(t,e,n){void 0===n&&(n={}),"string"===typeof t&&(t=[t]),this._modules.register(t,e),_(this,this.state,t,this._modules.get(t),n.preserveState),b(this,this.state)},v.prototype.unregisterModule=function(t){var e=this;"string"===typeof t&&(t=[t]),this._modules.unregister(t),this._withCommit((function(){var n=j(e.state,t.slice(0,-1));h.delete(n,t[t.length-1])})),g(this)},v.prototype.hotUpdate=function(t){this._modules.update(t),g(this,!0)},v.prototype._withCommit=function(t){var e=this._committing;this._committing=!0,t(),this._committing=e},Object.defineProperties(v.prototype,y);var E=L((function(t,e){var n={};return M(e).forEach((function(e){var r=e.key,o=e.val;n[r]=function(){var e=this.$store.state,n=this.$store.getters;if(t){var r=D(this.$store,"mapState",t);if(!r)return;e=r.context.state,n=r.context.getters}return"function"===typeof o?o.call(this,e,n):e[o]},n[r].vuex=!0})),n})),T=L((function(t,e){var n={};return M(e).forEach((function(e){var r=e.key,o=e.val;n[r]=function(){var e=[],n=arguments.length;while(n--)e[n]=arguments[n];var r=this.$store.commit;if(t){var i=D(this.$store,"mapMutations",t);if(!i)return;r=i.context.commit}return"function"===typeof o?o.apply(this,[r].concat(e)):r.apply(this.$store,[o].concat(e))}})),n})),P=L((function(t,e){var n={};return M(e).forEach((function(e){var r=e.key,o=e.val;o=t+o,n[r]=function(){if(!t||D(this.$store,"mapGetters",t))return this.$store.getters[o]},n[r].vuex=!0})),n})),R=L((function(t,e){var n={};return M(e).forEach((function(e){var r=e.key,o=e.val;n[r]=function(){var e=[],n=arguments.length;while(n--)e[n]=arguments[n];var r=this.$store.dispatch;if(t){var i=D(this.$store,"mapActions",t);if(!i)return;r=i.context.dispatch}return"function"===typeof o?o.apply(this,[r].concat(e)):r.apply(this.$store,[o].concat(e))}})),n})),I=function(t){return{mapState:E.bind(null,t),mapGetters:P.bind(null,t),mapMutations:T.bind(null,t),mapActions:R.bind(null,t)}};function M(t){return N(t)?Array.isArray(t)?t.map((function(t){return{key:t,val:t}})):Object.keys(t).map((function(e){return{key:e,val:t[e]}})):[]}function N(t){return Array.isArray(t)||s(t)}function L(t){return function(e,n){return"string"!==typeof e?(n=e,e=""):"/"!==e.charAt(e.length-1)&&(e+="/"),t(e,n)}}function D(t,e,n){var r=t._modulesNamespaceMap[n];return r}var F={Store:v,install:$,version:"3.1.3",mapState:E,mapMutations:T,mapGetters:P,mapActions:R,createNamespacedHelpers:I};e["a"]=F}).call(this,n("c8ba"))},"30b5":function(t,e,n){"use strict";var r=n("c532");function o(t){return encodeURIComponent(t).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,"+").replace(/%5B/gi,"[").replace(/%5D/gi,"]")}t.exports=function(t,e,n){if(!e)return t;var i;if(n)i=n(e);else if(r.isURLSearchParams(e))i=e.toString();else{var a=[];r.forEach(e,(function(t,e){null!==t&&"undefined"!==typeof t&&(r.isArray(t)?e+="[]":t=[t],r.forEach(t,(function(t){r.isDate(t)?t=t.toISOString():r.isObject(t)&&(t=JSON.stringify(t)),a.push(o(e)+"="+o(t))})))})),i=a.join("&")}if(i){var s=t.indexOf("#");-1!==s&&(t=t.slice(0,s)),t+=(-1===t.indexOf("?")?"?":"&")+i}return t}},"342f":function(t,e,n){var r=n("d066");t.exports=r("navigator","userAgent")||""},"35a1":function(t,e,n){var r=n("f5df"),o=n("3f8c"),i=n("b622"),a=i("iterator");t.exports=function(t){if(void 0!=t)return t[a]||t["@@iterator"]||o[r(t)]}},"37e8":function(t,e,n){var r=n("83ab"),o=n("9bf2"),i=n("825a"),a=n("df75");t.exports=r?Object.defineProperties:function(t,e){i(t);var n,r=a(e),s=r.length,c=0;while(s>c)o.f(t,n=r[c++],e[n]);return t}},"387f":function(t,e,n){"use strict";t.exports=function(t,e,n,r,o){return t.config=e,n&&(t.code=n),t.request=r,t.response=o,t.isAxiosError=!0,t.toJSON=function(){return{message:this.message,name:this.name,description:this.description,number:this.number,fileName:this.fileName,lineNumber:this.lineNumber,columnNumber:this.columnNumber,stack:this.stack,config:this.config,code:this.code}},t}},3934:function(t,e,n){"use strict";var r=n("c532");t.exports=r.isStandardBrowserEnv()?function(){var t,e=/(msie|trident)/i.test(navigator.userAgent),n=document.createElement("a");function o(t){var r=t;return e&&(n.setAttribute("href",r),r=n.href),n.setAttribute("href",r),{href:n.href,protocol:n.protocol?n.protocol.replace(/:$/,""):"",host:n.host,search:n.search?n.search.replace(/^\?/,""):"",hash:n.hash?n.hash.replace(/^#/,""):"",hostname:n.hostname,port:n.port,pathname:"/"===n.pathname.charAt(0)?n.pathname:"/"+n.pathname}}return t=o(window.location.href),function(e){var n=r.isString(e)?o(e):e;return n.protocol===t.protocol&&n.host===t.host}}():function(){return function(){return!0}}()},"3bbe":function(t,e,n){var r=n("861d");t.exports=function(t){if(!r(t)&&null!==t)throw TypeError("Can't set "+String(t)+" as a prototype");return t}},"3f8c":function(t,e){t.exports={}},"428f":function(t,e,n){var r=n("da84");t.exports=r},4362:function(t,e,n){e.nextTick=function(t){var e=Array.prototype.slice.call(arguments);e.shift(),setTimeout((function(){t.apply(null,e)}),0)},e.platform=e.arch=e.execPath=e.title="browser",e.pid=1,e.browser=!0,e.env={},e.argv=[],e.binding=function(t){throw new Error("No such module. (Possibly not yet loaded)")},function(){var t,r="/";e.cwd=function(){return r},e.chdir=function(e){t||(t=n("df7c")),r=t.resolve(e,r)}}(),e.exit=e.kill=e.umask=e.dlopen=e.uptime=e.memoryUsage=e.uvCounters=function(){},e.features={}},"44ad":function(t,e,n){var r=n("d039"),o=n("c6b6"),i="".split;t.exports=r((function(){return!Object("z").propertyIsEnumerable(0)}))?function(t){return"String"==o(t)?i.call(t,""):Object(t)}:Object},"44d2":function(t,e,n){var r=n("b622"),o=n("7c73"),i=n("9bf2"),a=r("unscopables"),s=Array.prototype;void 0==s[a]&&i.f(s,a,{configurable:!0,value:o(null)}),t.exports=function(t){s[a][t]=!0}},"44de":function(t,e,n){var r=n("da84");t.exports=function(t,e){var n=r.console;n&&n.error&&(1===arguments.length?n.error(t):n.error(t,e))}},"467f":function(t,e,n){"use strict";var r=n("2d83");t.exports=function(t,e,n){var o=n.config.validateStatus;!o||o(n.status)?t(n):e(r("Request failed with status code "+n.status,n.config,null,n.request,n))}},4840:function(t,e,n){var r=n("825a"),o=n("1c0b"),i=n("b622"),a=i("species");t.exports=function(t,e){var n,i=r(t).constructor;return void 0===i||void 0==(n=r(i)[a])?e:o(n)}},4930:function(t,e,n){var r=n("d039");t.exports=!!Object.getOwnPropertySymbols&&!r((function(){return!String(Symbol())}))},"4a7b":function(t,e,n){"use strict";var r=n("c532");t.exports=function(t,e){e=e||{};var n={},o=["url","method","params","data"],i=["headers","auth","proxy"],a=["baseURL","url","transformRequest","transformResponse","paramsSerializer","timeout","withCredentials","adapter","responseType","xsrfCookieName","xsrfHeaderName","onUploadProgress","onDownloadProgress","maxContentLength","validateStatus","maxRedirects","httpAgent","httpsAgent","cancelToken","socketPath"];r.forEach(o,(function(t){"undefined"!==typeof e[t]&&(n[t]=e[t])})),r.forEach(i,(function(o){r.isObject(e[o])?n[o]=r.deepMerge(t[o],e[o]):"undefined"!==typeof e[o]?n[o]=e[o]:r.isObject(t[o])?n[o]=r.deepMerge(t[o]):"undefined"!==typeof t[o]&&(n[o]=t[o])})),r.forEach(a,(function(r){"undefined"!==typeof e[r]?n[r]=e[r]:"undefined"!==typeof t[r]&&(n[r]=t[r])}));var s=o.concat(i).concat(a),c=Object.keys(e).filter((function(t){return-1===s.indexOf(t)}));return r.forEach(c,(function(r){"undefined"!==typeof e[r]?n[r]=e[r]:"undefined"!==typeof t[r]&&(n[r]=t[r])})),n}},"4d64":function(t,e,n){var r=n("fc6a"),o=n("50c4"),i=n("23cb"),a=function(t){return function(e,n,a){var s,c=r(e),u=o(c.length),f=i(a,u);if(t&&n!=n){while(u>f)if(s=c[f++],s!=s)return!0}else for(;u>f;f++)if((t||f in c)&&c[f]===n)return t||f||0;return!t&&-1}};t.exports={includes:a(!0),indexOf:a(!1)}},"50c4":function(t,e,n){var r=n("a691"),o=Math.min;t.exports=function(t){return t>0?o(r(t),9007199254740991):0}},5135:function(t,e){var n={}.hasOwnProperty;t.exports=function(t,e){return n.call(t,e)}},5270:function(t,e,n){"use strict";var r=n("c532"),o=n("c401"),i=n("2e67"),a=n("2444");function s(t){t.cancelToken&&t.cancelToken.throwIfRequested()}t.exports=function(t){s(t),t.headers=t.headers||{},t.data=o(t.data,t.headers,t.transformRequest),t.headers=r.merge(t.headers.common||{},t.headers[t.method]||{},t.headers),r.forEach(["delete","get","head","post","put","patch","common"],(function(e){delete t.headers[e]}));var e=t.adapter||a.adapter;return e(t).then((function(e){return s(t),e.data=o(e.data,e.headers,t.transformResponse),e}),(function(e){return i(e)||(s(t),e&&e.response&&(e.response.data=o(e.response.data,e.response.headers,t.transformResponse))),Promise.reject(e)}))}},5692:function(t,e,n){var r=n("c430"),o=n("c6cd");(t.exports=function(t,e){return o[t]||(o[t]=void 0!==e?e:{})})("versions",[]).push({version:"3.6.4",mode:r?"pure":"global",copyright:"© 2020 Denis Pushkarev (zloirock.ru)"})},"56ef":function(t,e,n){var r=n("d066"),o=n("241c"),i=n("7418"),a=n("825a");t.exports=r("Reflect","ownKeys")||function(t){var e=o.f(a(t)),n=i.f;return n?e.concat(n(t)):e}},"5c6c":function(t,e){t.exports=function(t,e){return{enumerable:!(1&t),configurable:!(2&t),writable:!(4&t),value:e}}},"60da":function(t,e,n){"use strict";var r=n("83ab"),o=n("d039"),i=n("df75"),a=n("7418"),s=n("d1e7"),c=n("7b0b"),u=n("44ad"),f=Object.assign,l=Object.defineProperty;t.exports=!f||o((function(){if(r&&1!==f({b:1},f(l({},"a",{enumerable:!0,get:function(){l(this,"b",{value:3,enumerable:!1})}}),{b:2})).b)return!0;var t={},e={},n=Symbol(),o="abcdefghijklmnopqrst";return t[n]=7,o.split("").forEach((function(t){e[t]=t})),7!=f({},t)[n]||i(f({},e)).join("")!=o}))?function(t,e){var n=c(t),o=arguments.length,f=1,l=a.f,p=s.f;while(o>f){var d,h=u(arguments[f++]),v=l?i(h).concat(l(h)):i(h),y=v.length,m=0;while(y>m)d=v[m++],r&&!p.call(h,d)||(n[d]=h[d])}return n}:f},"69f3":function(t,e,n){var r,o,i,a=n("7f9a"),s=n("da84"),c=n("861d"),u=n("9112"),f=n("5135"),l=n("f772"),p=n("d012"),d=s.WeakMap,h=function(t){return i(t)?o(t):r(t,{})},v=function(t){return function(e){var n;if(!c(e)||(n=o(e)).type!==t)throw TypeError("Incompatible receiver, "+t+" required");return n}};if(a){var y=new d,m=y.get,g=y.has,b=y.set;r=function(t,e){return b.call(y,t,e),e},o=function(t){return m.call(y,t)||{}},i=function(t){return g.call(y,t)}}else{var _=l("state");p[_]=!0,r=function(t,e){return u(t,_,e),e},o=function(t){return f(t,_)?t[_]:{}},i=function(t){return f(t,_)}}t.exports={set:r,get:o,has:i,enforce:h,getterFor:v}},"6eeb":function(t,e,n){var r=n("da84"),o=n("9112"),i=n("5135"),a=n("ce4e"),s=n("8925"),c=n("69f3"),u=c.get,f=c.enforce,l=String(String).split("String");(t.exports=function(t,e,n,s){var c=!!s&&!!s.unsafe,u=!!s&&!!s.enumerable,p=!!s&&!!s.noTargetGet;"function"==typeof n&&("string"!=typeof e||i(n,"name")||o(n,"name",e),f(n).source=l.join("string"==typeof e?e:"")),t!==r?(c?!p&&t[e]&&(u=!0):delete t[e],u?t[e]=n:o(t,e,n)):u?t[e]=n:a(e,n)})(Function.prototype,"toString",(function(){return"function"==typeof this&&u(this).source||s(this)}))},7418:function(t,e){e.f=Object.getOwnPropertySymbols},7839:function(t,e){t.exports=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"]},"7a77":function(t,e,n){"use strict";function r(t){this.message=t}r.prototype.toString=function(){return"Cancel"+(this.message?": "+this.message:"")},r.prototype.__CANCEL__=!0,t.exports=r},"7aac":function(t,e,n){"use strict";var r=n("c532");t.exports=r.isStandardBrowserEnv()?function(){return{write:function(t,e,n,o,i,a){var s=[];s.push(t+"="+encodeURIComponent(e)),r.isNumber(n)&&s.push("expires="+new Date(n).toGMTString()),r.isString(o)&&s.push("path="+o),r.isString(i)&&s.push("domain="+i),!0===a&&s.push("secure"),document.cookie=s.join("; ")},read:function(t){var e=document.cookie.match(new RegExp("(^|;\\s*)("+t+")=([^;]*)"));return e?decodeURIComponent(e[3]):null},remove:function(t){this.write(t,"",Date.now()-864e5)}}}():function(){return{write:function(){},read:function(){return null},remove:function(){}}}()},"7b0b":function(t,e,n){var r=n("1d80");t.exports=function(t){return Object(r(t))}},"7c73":function(t,e,n){var r,o=n("825a"),i=n("37e8"),a=n("7839"),s=n("d012"),c=n("1be4"),u=n("cc12"),f=n("f772"),l=">",p="<",d="prototype",h="script",v=f("IE_PROTO"),y=function(){},m=function(t){return p+h+l+t+p+"/"+h+l},g=function(t){t.write(m("")),t.close();var e=t.parentWindow.Object;return t=null,e},b=function(){var t,e=u("iframe"),n="java"+h+":";return e.style.display="none",c.appendChild(e),e.src=String(n),t=e.contentWindow.document,t.open(),t.write(m("document.F=Object")),t.close(),t.F},_=function(){try{r=document.domain&&new ActiveXObject("htmlfile")}catch(e){}_=r?g(r):b();var t=a.length;while(t--)delete _[d][a[t]];return _()};s[v]=!0,t.exports=Object.create||function(t,e){var n;return null!==t?(y[d]=o(t),n=new y,y[d]=null,n[v]=t):n=_(),void 0===e?n:i(n,e)}},"7dd0":function(t,e,n){"use strict";var r=n("23e7"),o=n("9ed3"),i=n("e163"),a=n("d2bb"),s=n("d44e"),c=n("9112"),u=n("6eeb"),f=n("b622"),l=n("c430"),p=n("3f8c"),d=n("ae93"),h=d.IteratorPrototype,v=d.BUGGY_SAFARI_ITERATORS,y=f("iterator"),m="keys",g="values",b="entries",_=function(){return this};t.exports=function(t,e,n,f,d,w,x){o(n,e,f);var C,O,A,k=function(t){if(t===d&&T)return T;if(!v&&t in $)return $[t];switch(t){case m:return function(){return new n(this,t)};case g:return function(){return new n(this,t)};case b:return function(){return new n(this,t)}}return function(){return new n(this)}},j=e+" Iterator",S=!1,$=t.prototype,E=$[y]||$["@@iterator"]||d&&$[d],T=!v&&E||k(d),P="Array"==e&&$.entries||E;if(P&&(C=i(P.call(new t)),h!==Object.prototype&&C.next&&(l||i(C)===h||(a?a(C,h):"function"!=typeof C[y]&&c(C,y,_)),s(C,j,!0,!0),l&&(p[j]=_))),d==g&&E&&E.name!==g&&(S=!0,T=function(){return E.call(this)}),l&&!x||$[y]===T||c($,y,T),p[e]=T,d)if(O={values:k(g),keys:w?T:k(m),entries:k(b)},x)for(A in O)(v||S||!(A in $))&&u($,A,O[A]);else r({target:e,proto:!0,forced:v||S},O);return O}},"7f9a":function(t,e,n){var r=n("da84"),o=n("8925"),i=r.WeakMap;t.exports="function"===typeof i&&/native code/.test(o(i))},"825a":function(t,e,n){var r=n("861d");t.exports=function(t){if(!r(t))throw TypeError(String(t)+" is not an object");return t}},"83ab":function(t,e,n){var r=n("d039");t.exports=!r((function(){return 7!=Object.defineProperty({},1,{get:function(){return 7}})[1]}))},"83b9":function(t,e,n){"use strict";var r=n("d925"),o=n("e683");t.exports=function(t,e){return t&&!r(e)?o(t,e):e}},"861d":function(t,e){t.exports=function(t){return"object"===typeof t?null!==t:"function"===typeof t}},8925:function(t,e,n){var r=n("c6cd"),o=Function.toString;"function"!=typeof r.inspectSource&&(r.inspectSource=function(t){return o.call(t)}),t.exports=r.inspectSource},"8c4f":function(t,e,n){"use strict"; /*! * vue-router v3.1.6 * (c) 2020 Evan You * @license MIT */function r(t,e){0}function o(t){return Object.prototype.toString.call(t).indexOf("Error")>-1}function i(t,e){return e instanceof t||e&&(e.name===t.name||e._name===t._name)}function a(t,e){for(var n in e)t[n]=e[n];return t}var s={name:"RouterView",functional:!0,props:{name:{type:String,default:"default"}},render:function(t,e){var n=e.props,r=e.children,o=e.parent,i=e.data;i.routerView=!0;var s=o.$createElement,u=n.name,f=o.$route,l=o._routerViewCache||(o._routerViewCache={}),p=0,d=!1;while(o&&o._routerRoot!==o){var h=o.$vnode?o.$vnode.data:{};h.routerView&&p++,h.keepAlive&&o._directInactive&&o._inactive&&(d=!0),o=o.$parent}if(i.routerViewDepth=p,d){var v=l[u],y=v&&v.component;return y?(v.configProps&&c(y,i,v.route,v.configProps),s(y,i,r)):s()}var m=f.matched[p],g=m&&m.components[u];if(!m||!g)return l[u]=null,s();l[u]={component:g},i.registerRouteInstance=function(t,e){var n=m.instances[u];(e&&n!==t||!e&&n===t)&&(m.instances[u]=e)},(i.hook||(i.hook={})).prepatch=function(t,e){m.instances[u]=e.componentInstance},i.hook.init=function(t){t.data.keepAlive&&t.componentInstance&&t.componentInstance!==m.instances[u]&&(m.instances[u]=t.componentInstance)};var b=m.props&&m.props[u];return b&&(a(l[u],{route:f,configProps:b}),c(g,i,f,b)),s(g,i,r)}};function c(t,e,n,r){var o=e.props=u(n,r);if(o){o=e.props=a({},o);var i=e.attrs=e.attrs||{};for(var s in o)t.props&&s in t.props||(i[s]=o[s],delete o[s])}}function u(t,e){switch(typeof e){case"undefined":return;case"object":return e;case"function":return e(t);case"boolean":return e?t.params:void 0;default:0}}var f=/[!'()*]/g,l=function(t){return"%"+t.charCodeAt(0).toString(16)},p=/%2C/g,d=function(t){return encodeURIComponent(t).replace(f,l).replace(p,",")},h=decodeURIComponent;function v(t,e,n){void 0===e&&(e={});var r,o=n||y;try{r=o(t||"")}catch(a){r={}}for(var i in e)r[i]=e[i];return r}function y(t){var e={};return t=t.trim().replace(/^(\?|#|&)/,""),t?(t.split("&").forEach((function(t){var n=t.replace(/\+/g," ").split("="),r=h(n.shift()),o=n.length>0?h(n.join("=")):null;void 0===e[r]?e[r]=o:Array.isArray(e[r])?e[r].push(o):e[r]=[e[r],o]})),e):e}function m(t){var e=t?Object.keys(t).map((function(e){var n=t[e];if(void 0===n)return"";if(null===n)return d(e);if(Array.isArray(n)){var r=[];return n.forEach((function(t){void 0!==t&&(null===t?r.push(d(e)):r.push(d(e)+"="+d(t)))})),r.join("&")}return d(e)+"="+d(n)})).filter((function(t){return t.length>0})).join("&"):null;return e?"?"+e:""}var g=/\/?$/;function b(t,e,n,r){var o=r&&r.options.stringifyQuery,i=e.query||{};try{i=_(i)}catch(s){}var a={name:e.name||t&&t.name,meta:t&&t.meta||{},path:e.path||"/",hash:e.hash||"",query:i,params:e.params||{},fullPath:C(e,o),matched:t?x(t):[]};return n&&(a.redirectedFrom=C(n,o)),Object.freeze(a)}function _(t){if(Array.isArray(t))return t.map(_);if(t&&"object"===typeof t){var e={};for(var n in t)e[n]=_(t[n]);return e}return t}var w=b(null,{path:"/"});function x(t){var e=[];while(t)e.unshift(t),t=t.parent;return e}function C(t,e){var n=t.path,r=t.query;void 0===r&&(r={});var o=t.hash;void 0===o&&(o="");var i=e||m;return(n||"/")+i(r)+o}function O(t,e){return e===w?t===e:!!e&&(t.path&&e.path?t.path.replace(g,"")===e.path.replace(g,"")&&t.hash===e.hash&&A(t.query,e.query):!(!t.name||!e.name)&&(t.name===e.name&&t.hash===e.hash&&A(t.query,e.query)&&A(t.params,e.params)))}function A(t,e){if(void 0===t&&(t={}),void 0===e&&(e={}),!t||!e)return t===e;var n=Object.keys(t),r=Object.keys(e);return n.length===r.length&&n.every((function(n){var r=t[n],o=e[n];return"object"===typeof r&&"object"===typeof o?A(r,o):String(r)===String(o)}))}function k(t,e){return 0===t.path.replace(g,"/").indexOf(e.path.replace(g,"/"))&&(!e.hash||t.hash===e.hash)&&j(t.query,e.query)}function j(t,e){for(var n in e)if(!(n in t))return!1;return!0}function S(t,e,n){var r=t.charAt(0);if("/"===r)return t;if("?"===r||"#"===r)return e+t;var o=e.split("/");n&&o[o.length-1]||o.pop();for(var i=t.replace(/^\//,"").split("/"),a=0;a=0&&(e=t.slice(r),t=t.slice(0,r));var o=t.indexOf("?");return o>=0&&(n=t.slice(o+1),t=t.slice(0,o)),{path:t,query:n,hash:e}}function E(t){return t.replace(/\/\//g,"/")}var T=Array.isArray||function(t){return"[object Array]"==Object.prototype.toString.call(t)},P=Y,R=D,I=F,M=q,N=J,L=new RegExp(["(\\\\.)","([\\/.])?(?:(?:\\:(\\w+)(?:\\(((?:\\\\.|[^\\\\()])+)\\))?|\\(((?:\\\\.|[^\\\\()])+)\\))([+*?])?|(\\*))"].join("|"),"g");function D(t,e){var n,r=[],o=0,i=0,a="",s=e&&e.delimiter||"/";while(null!=(n=L.exec(t))){var c=n[0],u=n[1],f=n.index;if(a+=t.slice(i,f),i=f+c.length,u)a+=u[1];else{var l=t[i],p=n[2],d=n[3],h=n[4],v=n[5],y=n[6],m=n[7];a&&(r.push(a),a="");var g=null!=p&&null!=l&&l!==p,b="+"===y||"*"===y,_="?"===y||"*"===y,w=n[2]||s,x=h||v;r.push({name:d||o++,prefix:p||"",delimiter:w,optional:_,repeat:b,partial:g,asterisk:!!m,pattern:x?V(x):m?".*":"[^"+H(w)+"]+?"})}}return i1||!w.length)return 0===w.length?t():t("span",{},w)}if("a"===this.tag)_.on=g,_.attrs={href:c};else{var x=st(this.$slots.default);if(x){x.isStatic=!1;var C=x.data=a({},x.data);for(var A in C.on=C.on||{},C.on){var j=C.on[A];A in g&&(C.on[A]=Array.isArray(j)?j:[j])}for(var S in g)S in C.on?C.on[S].push(g[S]):C.on[S]=m;var $=x.data.attrs=a({},x.data.attrs);$.href=c}else _.on=g}return t(this.tag,_,this.$slots.default)}};function at(t){if(!(t.metaKey||t.altKey||t.ctrlKey||t.shiftKey)&&!t.defaultPrevented&&(void 0===t.button||0===t.button)){if(t.currentTarget&&t.currentTarget.getAttribute){var e=t.currentTarget.getAttribute("target");if(/\b_blank\b/i.test(e))return}return t.preventDefault&&t.preventDefault(),!0}}function st(t){if(t)for(var e,n=0;n-1&&(s.params[p]=n.params[p]);return s.path=Z(u.path,s.params,'named route "'+c+'"'),f(u,s,a)}if(s.path){s.params={};for(var d=0;d=t.length?n():t[o]?e(t[o],(function(){r(o+1)})):r(o+1)};r(0)}function Dt(t){return function(e,n,r){var i=!1,a=0,s=null;Ft(t,(function(t,e,n,c){if("function"===typeof t&&void 0===t.cid){i=!0,a++;var u,f=Ht((function(e){qt(e)&&(e=e.default),t.resolved="function"===typeof e?e:et.extend(e),n.components[c]=e,a--,a<=0&&r()})),l=Ht((function(t){var e="Failed to resolve async component "+c+": "+t;s||(s=o(t)?t:new Error(e),r(s))}));try{u=t(f,l)}catch(d){l(d)}if(u)if("function"===typeof u.then)u.then(f,l);else{var p=u.component;p&&"function"===typeof p.then&&p.then(f,l)}}})),i||r()}}function Ft(t,e){return Ut(t.map((function(t){return Object.keys(t.components).map((function(n){return e(t.components[n],t.instances[n],t,n)}))})))}function Ut(t){return Array.prototype.concat.apply([],t)}var Bt="function"===typeof Symbol&&"symbol"===typeof Symbol.toStringTag;function qt(t){return t.__esModule||Bt&&"Module"===t[Symbol.toStringTag]}function Ht(t){var e=!1;return function(){var n=[],r=arguments.length;while(r--)n[r]=arguments[r];if(!e)return e=!0,t.apply(this,n)}}var Vt=function(t){function e(e){t.call(this),this.name=this._name="NavigationDuplicated",this.message='Navigating to current location ("'+e.fullPath+'") is not allowed',Object.defineProperty(this,"stack",{value:(new t).stack,writable:!0,configurable:!0})}return t&&(e.__proto__=t),e.prototype=Object.create(t&&t.prototype),e.prototype.constructor=e,e}(Error);Vt._name="NavigationDuplicated";var zt=function(t,e){this.router=t,this.base=Gt(e),this.current=w,this.pending=null,this.ready=!1,this.readyCbs=[],this.readyErrorCbs=[],this.errorCbs=[]};function Gt(t){if(!t)if(ut){var e=document.querySelector("base");t=e&&e.getAttribute("href")||"/",t=t.replace(/^https?:\/\/[^\/]+/,"")}else t="/";return"/"!==t.charAt(0)&&(t="/"+t),t.replace(/\/$/,"")}function Wt(t,e){var n,r=Math.max(t.length,e.length);for(n=0;n-1?decodeURI(t.slice(0,r))+t.slice(r):decodeURI(t)}else t=decodeURI(t.slice(0,n))+t.slice(n);return t}function ce(t){var e=window.location.href,n=e.indexOf("#"),r=n>=0?e.slice(0,n):e;return r+"#"+t}function ue(t){It?Mt(ce(t)):window.location.hash=t}function fe(t){It?Nt(ce(t)):window.location.replace(ce(t))}var le=function(t){function e(e,n){t.call(this,e,n),this.stack=[],this.index=-1}return t&&(e.__proto__=t),e.prototype=Object.create(t&&t.prototype),e.prototype.constructor=e,e.prototype.push=function(t,e,n){var r=this;this.transitionTo(t,(function(t){r.stack=r.stack.slice(0,r.index+1).concat(t),r.index++,e&&e(t)}),n)},e.prototype.replace=function(t,e,n){var r=this;this.transitionTo(t,(function(t){r.stack=r.stack.slice(0,r.index).concat(t),e&&e(t)}),n)},e.prototype.go=function(t){var e=this,n=this.index+t;if(!(n<0||n>=this.stack.length)){var r=this.stack[n];this.confirmTransition(r,(function(){e.index=n,e.updateRoute(r)}),(function(t){i(Vt,t)&&(e.index=n)}))}},e.prototype.getCurrentLocation=function(){var t=this.stack[this.stack.length-1];return t?t.fullPath:"/"},e.prototype.ensureURL=function(){},e}(zt),pe=function(t){void 0===t&&(t={}),this.app=null,this.apps=[],this.options=t,this.beforeHooks=[],this.resolveHooks=[],this.afterHooks=[],this.matcher=ht(t.routes||[],this);var e=t.mode||"hash";switch(this.fallback="history"===e&&!It&&!1!==t.fallback,this.fallback&&(e="hash"),ut||(e="abstract"),this.mode=e,e){case"history":this.history=new ne(this,t.base);break;case"hash":this.history=new oe(this,t.base,this.fallback);break;case"abstract":this.history=new le(this,t.base);break;default:0}},de={currentRoute:{configurable:!0}};function he(t,e){return t.push(e),function(){var n=t.indexOf(e);n>-1&&t.splice(n,1)}}function ve(t,e,n){var r="hash"===n?"#"+e:e;return t?E(t+"/"+r):r}pe.prototype.match=function(t,e,n){return this.matcher.match(t,e,n)},de.currentRoute.get=function(){return this.history&&this.history.current},pe.prototype.init=function(t){var e=this;if(this.apps.push(t),t.$once("hook:destroyed",(function(){var n=e.apps.indexOf(t);n>-1&&e.apps.splice(n,1),e.app===t&&(e.app=e.apps[0]||null)})),!this.app){this.app=t;var n=this.history;if(n instanceof ne)n.transitionTo(n.getCurrentLocation());else if(n instanceof oe){var r=function(){n.setupListeners()};n.transitionTo(n.getCurrentLocation(),r,r)}n.listen((function(t){e.apps.forEach((function(e){e._route=t}))}))}},pe.prototype.beforeEach=function(t){return he(this.beforeHooks,t)},pe.prototype.beforeResolve=function(t){return he(this.resolveHooks,t)},pe.prototype.afterEach=function(t){return he(this.afterHooks,t)},pe.prototype.onReady=function(t,e){this.history.onReady(t,e)},pe.prototype.onError=function(t){this.history.onError(t)},pe.prototype.push=function(t,e,n){var r=this;if(!e&&!n&&"undefined"!==typeof Promise)return new Promise((function(e,n){r.history.push(t,e,n)}));this.history.push(t,e,n)},pe.prototype.replace=function(t,e,n){var r=this;if(!e&&!n&&"undefined"!==typeof Promise)return new Promise((function(e,n){r.history.replace(t,e,n)}));this.history.replace(t,e,n)},pe.prototype.go=function(t){this.history.go(t)},pe.prototype.back=function(){this.go(-1)},pe.prototype.forward=function(){this.go(1)},pe.prototype.getMatchedComponents=function(t){var e=t?t.matched?t:this.resolve(t).route:this.currentRoute;return e?[].concat.apply([],e.matched.map((function(t){return Object.keys(t.components).map((function(e){return t.components[e]}))}))):[]},pe.prototype.resolve=function(t,e,n){e=e||this.history.current;var r=tt(t,e,n,this),o=this.match(r,e),i=o.redirectedFrom||o.fullPath,a=this.history.base,s=ve(a,i,this.mode);return{location:r,route:o,href:s,normalizedTo:r,resolved:o}},pe.prototype.addRoutes=function(t){this.matcher.addRoutes(t),this.history.current!==w&&this.history.transitionTo(this.history.getCurrentLocation())},Object.defineProperties(pe.prototype,de),pe.install=ct,pe.version="3.1.6",ut&&window.Vue&&window.Vue.use(pe),e["a"]=pe},"8df4":function(t,e,n){"use strict";var r=n("7a77");function o(t){if("function"!==typeof t)throw new TypeError("executor must be a function.");var e;this.promise=new Promise((function(t){e=t}));var n=this;t((function(t){n.reason||(n.reason=new r(t),e(n.reason))}))}o.prototype.throwIfRequested=function(){if(this.reason)throw this.reason},o.source=function(){var t,e=new o((function(e){t=e}));return{token:e,cancel:t}},t.exports=o},"90e3":function(t,e){var n=0,r=Math.random();t.exports=function(t){return"Symbol("+String(void 0===t?"":t)+")_"+(++n+r).toString(36)}},9112:function(t,e,n){var r=n("83ab"),o=n("9bf2"),i=n("5c6c");t.exports=r?function(t,e,n){return o.f(t,e,i(1,n))}:function(t,e,n){return t[e]=n,t}},"94ca":function(t,e,n){var r=n("d039"),o=/#|\.prototype\./,i=function(t,e){var n=s[a(t)];return n==u||n!=c&&("function"==typeof e?r(e):!!e)},a=i.normalize=function(t){return String(t).replace(o,".").toLowerCase()},s=i.data={},c=i.NATIVE="N",u=i.POLYFILL="P";t.exports=i},"9bdd":function(t,e,n){var r=n("825a");t.exports=function(t,e,n,o){try{return o?e(r(n)[0],n[1]):e(n)}catch(a){var i=t["return"];throw void 0!==i&&r(i.call(t)),a}}},"9bf2":function(t,e,n){var r=n("83ab"),o=n("0cfb"),i=n("825a"),a=n("c04e"),s=Object.defineProperty;e.f=r?s:function(t,e,n){if(i(t),e=a(e,!0),i(n),o)try{return s(t,e,n)}catch(r){}if("get"in n||"set"in n)throw TypeError("Accessors not supported");return"value"in n&&(t[e]=n.value),t}},"9ed3":function(t,e,n){"use strict";var r=n("ae93").IteratorPrototype,o=n("7c73"),i=n("5c6c"),a=n("d44e"),s=n("3f8c"),c=function(){return this};t.exports=function(t,e,n){var u=e+" Iterator";return t.prototype=o(r,{next:i(1,n)}),a(t,u,!1,!0),s[u]=c,t}},a691:function(t,e){var n=Math.ceil,r=Math.floor;t.exports=function(t){return isNaN(t=+t)?0:(t>0?r:n)(t)}},a79d:function(t,e,n){"use strict";var r=n("23e7"),o=n("c430"),i=n("fea9"),a=n("d039"),s=n("d066"),c=n("4840"),u=n("cdf9"),f=n("6eeb"),l=!!i&&a((function(){i.prototype["finally"].call({then:function(){}},(function(){}))}));r({target:"Promise",proto:!0,real:!0,forced:l},{finally:function(t){var e=c(this,s("Promise")),n="function"==typeof t;return this.then(n?function(n){return u(e,t()).then((function(){return n}))}:t,n?function(n){return u(e,t()).then((function(){throw n}))}:t)}}),o||"function"!=typeof i||i.prototype["finally"]||f(i.prototype,"finally",s("Promise").prototype["finally"])},ae93:function(t,e,n){"use strict";var r,o,i,a=n("e163"),s=n("9112"),c=n("5135"),u=n("b622"),f=n("c430"),l=u("iterator"),p=!1,d=function(){return this};[].keys&&(i=[].keys(),"next"in i?(o=a(a(i)),o!==Object.prototype&&(r=o)):p=!0),void 0==r&&(r={}),f||c(r,l)||s(r,l,d),t.exports={IteratorPrototype:r,BUGGY_SAFARI_ITERATORS:p}},b041:function(t,e,n){"use strict";var r=n("00ee"),o=n("f5df");t.exports=r?{}.toString:function(){return"[object "+o(this)+"]"}},b50d:function(t,e,n){"use strict";var r=n("c532"),o=n("467f"),i=n("30b5"),a=n("83b9"),s=n("c345"),c=n("3934"),u=n("2d83");t.exports=function(t){return new Promise((function(e,f){var l=t.data,p=t.headers;r.isFormData(l)&&delete p["Content-Type"];var d=new XMLHttpRequest;if(t.auth){var h=t.auth.username||"",v=t.auth.password||"";p.Authorization="Basic "+btoa(h+":"+v)}var y=a(t.baseURL,t.url);if(d.open(t.method.toUpperCase(),i(y,t.params,t.paramsSerializer),!0),d.timeout=t.timeout,d.onreadystatechange=function(){if(d&&4===d.readyState&&(0!==d.status||d.responseURL&&0===d.responseURL.indexOf("file:"))){var n="getAllResponseHeaders"in d?s(d.getAllResponseHeaders()):null,r=t.responseType&&"text"!==t.responseType?d.response:d.responseText,i={data:r,status:d.status,statusText:d.statusText,headers:n,config:t,request:d};o(e,f,i),d=null}},d.onabort=function(){d&&(f(u("Request aborted",t,"ECONNABORTED",d)),d=null)},d.onerror=function(){f(u("Network Error",t,null,d)),d=null},d.ontimeout=function(){var e="timeout of "+t.timeout+"ms exceeded";t.timeoutErrorMessage&&(e=t.timeoutErrorMessage),f(u(e,t,"ECONNABORTED",d)),d=null},r.isStandardBrowserEnv()){var m=n("7aac"),g=(t.withCredentials||c(y))&&t.xsrfCookieName?m.read(t.xsrfCookieName):void 0;g&&(p[t.xsrfHeaderName]=g)}if("setRequestHeader"in d&&r.forEach(p,(function(t,e){"undefined"===typeof l&&"content-type"===e.toLowerCase()?delete p[e]:d.setRequestHeader(e,t)})),r.isUndefined(t.withCredentials)||(d.withCredentials=!!t.withCredentials),t.responseType)try{d.responseType=t.responseType}catch(b){if("json"!==t.responseType)throw b}"function"===typeof t.onDownloadProgress&&d.addEventListener("progress",t.onDownloadProgress),"function"===typeof t.onUploadProgress&&d.upload&&d.upload.addEventListener("progress",t.onUploadProgress),t.cancelToken&&t.cancelToken.promise.then((function(t){d&&(d.abort(),f(t),d=null)})),void 0===l&&(l=null),d.send(l)}))}},b575:function(t,e,n){var r,o,i,a,s,c,u,f,l=n("da84"),p=n("06cf").f,d=n("c6b6"),h=n("2cf4").set,v=n("1cdc"),y=l.MutationObserver||l.WebKitMutationObserver,m=l.process,g=l.Promise,b="process"==d(m),_=p(l,"queueMicrotask"),w=_&&_.value;w||(r=function(){var t,e;b&&(t=m.domain)&&t.exit();while(o){e=o.fn,o=o.next;try{e()}catch(n){throw o?a():i=void 0,n}}i=void 0,t&&t.enter()},b?a=function(){m.nextTick(r)}:y&&!v?(s=!0,c=document.createTextNode(""),new y(r).observe(c,{characterData:!0}),a=function(){c.data=s=!s}):g&&g.resolve?(u=g.resolve(void 0),f=u.then,a=function(){f.call(u,r)}):a=function(){h.call(l,r)}),t.exports=w||function(t){var e={fn:t,next:void 0};i&&(i.next=e),o||(o=e,a()),i=e}},b622:function(t,e,n){var r=n("da84"),o=n("5692"),i=n("5135"),a=n("90e3"),s=n("4930"),c=n("fdbf"),u=o("wks"),f=r.Symbol,l=c?f:f&&f.withoutSetter||a;t.exports=function(t){return i(u,t)||(s&&i(f,t)?u[t]=f[t]:u[t]=l("Symbol."+t)),u[t]}},bc3a:function(t,e,n){t.exports=n("cee4")},c04e:function(t,e,n){var r=n("861d");t.exports=function(t,e){if(!r(t))return t;var n,o;if(e&&"function"==typeof(n=t.toString)&&!r(o=n.call(t)))return o;if("function"==typeof(n=t.valueOf)&&!r(o=n.call(t)))return o;if(!e&&"function"==typeof(n=t.toString)&&!r(o=n.call(t)))return o;throw TypeError("Can't convert object to primitive value")}},c345:function(t,e,n){"use strict";var r=n("c532"),o=["age","authorization","content-length","content-type","etag","expires","from","host","if-modified-since","if-unmodified-since","last-modified","location","max-forwards","proxy-authorization","referer","retry-after","user-agent"];t.exports=function(t){var e,n,i,a={};return t?(r.forEach(t.split("\n"),(function(t){if(i=t.indexOf(":"),e=r.trim(t.substr(0,i)).toLowerCase(),n=r.trim(t.substr(i+1)),e){if(a[e]&&o.indexOf(e)>=0)return;a[e]="set-cookie"===e?(a[e]?a[e]:[]).concat([n]):a[e]?a[e]+", "+n:n}})),a):a}},c401:function(t,e,n){"use strict";var r=n("c532");t.exports=function(t,e,n){return r.forEach(n,(function(n){t=n(t,e)})),t}},c430:function(t,e){t.exports=!1},c532:function(t,e,n){"use strict";var r=n("1d2b"),o=Object.prototype.toString;function i(t){return"[object Array]"===o.call(t)}function a(t){return"undefined"===typeof t}function s(t){return null!==t&&!a(t)&&null!==t.constructor&&!a(t.constructor)&&"function"===typeof t.constructor.isBuffer&&t.constructor.isBuffer(t)}function c(t){return"[object ArrayBuffer]"===o.call(t)}function u(t){return"undefined"!==typeof FormData&&t instanceof FormData}function f(t){var e;return e="undefined"!==typeof ArrayBuffer&&ArrayBuffer.isView?ArrayBuffer.isView(t):t&&t.buffer&&t.buffer instanceof ArrayBuffer,e}function l(t){return"string"===typeof t}function p(t){return"number"===typeof t}function d(t){return null!==t&&"object"===typeof t}function h(t){return"[object Date]"===o.call(t)}function v(t){return"[object File]"===o.call(t)}function y(t){return"[object Blob]"===o.call(t)}function m(t){return"[object Function]"===o.call(t)}function g(t){return d(t)&&m(t.pipe)}function b(t){return"undefined"!==typeof URLSearchParams&&t instanceof URLSearchParams}function _(t){return t.replace(/^\s*/,"").replace(/\s*$/,"")}function w(){return("undefined"===typeof navigator||"ReactNative"!==navigator.product&&"NativeScript"!==navigator.product&&"NS"!==navigator.product)&&("undefined"!==typeof window&&"undefined"!==typeof document)}function x(t,e){if(null!==t&&"undefined"!==typeof t)if("object"!==typeof t&&(t=[t]),i(t))for(var n=0,r=t.length;nc)r(s,n=e[c++])&&(~i(u,n)||u.push(n));return u}},cc12:function(t,e,n){var r=n("da84"),o=n("861d"),i=r.document,a=o(i)&&o(i.createElement);t.exports=function(t){return a?i.createElement(t):{}}},cca6:function(t,e,n){var r=n("23e7"),o=n("60da");r({target:"Object",stat:!0,forced:Object.assign!==o},{assign:o})},cdf9:function(t,e,n){var r=n("825a"),o=n("861d"),i=n("f069");t.exports=function(t,e){if(r(t),o(e)&&e.constructor===t)return e;var n=i.f(t),a=n.resolve;return a(e),n.promise}},ce4e:function(t,e,n){var r=n("da84"),o=n("9112");t.exports=function(t,e){try{o(r,t,e)}catch(n){r[t]=e}return e}},cee4:function(t,e,n){"use strict";var r=n("c532"),o=n("1d2b"),i=n("0a06"),a=n("4a7b"),s=n("2444");function c(t){var e=new i(t),n=o(i.prototype.request,e);return r.extend(n,i.prototype,e),r.extend(n,e),n}var u=c(s);u.Axios=i,u.create=function(t){return c(a(u.defaults,t))},u.Cancel=n("7a77"),u.CancelToken=n("8df4"),u.isCancel=n("2e67"),u.all=function(t){return Promise.all(t)},u.spread=n("0df6"),t.exports=u,t.exports.default=u},d012:function(t,e){t.exports={}},d039:function(t,e){t.exports=function(t){try{return!!t()}catch(e){return!0}}},d066:function(t,e,n){var r=n("428f"),o=n("da84"),i=function(t){return"function"==typeof t?t:void 0};t.exports=function(t,e){return arguments.length<2?i(r[t])||i(o[t]):r[t]&&r[t][e]||o[t]&&o[t][e]}},d1e7:function(t,e,n){"use strict";var r={}.propertyIsEnumerable,o=Object.getOwnPropertyDescriptor,i=o&&!r.call({1:2},1);e.f=i?function(t){var e=o(this,t);return!!e&&e.enumerable}:r},d2bb:function(t,e,n){var r=n("825a"),o=n("3bbe");t.exports=Object.setPrototypeOf||("__proto__"in{}?function(){var t,e=!1,n={};try{t=Object.getOwnPropertyDescriptor(Object.prototype,"__proto__").set,t.call(n,[]),e=n instanceof Array}catch(i){}return function(n,i){return r(n),o(i),e?t.call(n,i):n.__proto__=i,n}}():void 0)},d3b7:function(t,e,n){var r=n("00ee"),o=n("6eeb"),i=n("b041");r||o(Object.prototype,"toString",i,{unsafe:!0})},d44e:function(t,e,n){var r=n("9bf2").f,o=n("5135"),i=n("b622"),a=i("toStringTag");t.exports=function(t,e,n){t&&!o(t=n?t:t.prototype,a)&&r(t,a,{configurable:!0,value:e})}},d925:function(t,e,n){"use strict";t.exports=function(t){return/^([a-z][a-z\d\+\-\.]*:)?\/\//i.test(t)}},da84:function(t,e,n){(function(e){var n=function(t){return t&&t.Math==Math&&t};t.exports=n("object"==typeof globalThis&&globalThis)||n("object"==typeof window&&window)||n("object"==typeof self&&self)||n("object"==typeof e&&e)||Function("return this")()}).call(this,n("c8ba"))},df75:function(t,e,n){var r=n("ca84"),o=n("7839");t.exports=Object.keys||function(t){return r(t,o)}},df7c:function(t,e,n){(function(t){function n(t,e){for(var n=0,r=t.length-1;r>=0;r--){var o=t[r];"."===o?t.splice(r,1):".."===o?(t.splice(r,1),n++):n&&(t.splice(r,1),n--)}if(e)for(;n--;n)t.unshift("..");return t}function r(t){"string"!==typeof t&&(t+="");var e,n=0,r=-1,o=!0;for(e=t.length-1;e>=0;--e)if(47===t.charCodeAt(e)){if(!o){n=e+1;break}}else-1===r&&(o=!1,r=e+1);return-1===r?"":t.slice(n,r)}function o(t,e){if(t.filter)return t.filter(e);for(var n=[],r=0;r=-1&&!r;i--){var a=i>=0?arguments[i]:t.cwd();if("string"!==typeof a)throw new TypeError("Arguments to path.resolve must be strings");a&&(e=a+"/"+e,r="/"===a.charAt(0))}return e=n(o(e.split("/"),(function(t){return!!t})),!r).join("/"),(r?"/":"")+e||"."},e.normalize=function(t){var r=e.isAbsolute(t),a="/"===i(t,-1);return t=n(o(t.split("/"),(function(t){return!!t})),!r).join("/"),t||r||(t="."),t&&a&&(t+="/"),(r?"/":"")+t},e.isAbsolute=function(t){return"/"===t.charAt(0)},e.join=function(){var t=Array.prototype.slice.call(arguments,0);return e.normalize(o(t,(function(t,e){if("string"!==typeof t)throw new TypeError("Arguments to path.join must be strings");return t})).join("/"))},e.relative=function(t,n){function r(t){for(var e=0;e=0;n--)if(""!==t[n])break;return e>n?[]:t.slice(e,n-e+1)}t=e.resolve(t).substr(1),n=e.resolve(n).substr(1);for(var o=r(t.split("/")),i=r(n.split("/")),a=Math.min(o.length,i.length),s=a,c=0;c=1;--i)if(e=t.charCodeAt(i),47===e){if(!o){r=i;break}}else o=!1;return-1===r?n?"/":".":n&&1===r?"/":t.slice(0,r)},e.basename=function(t,e){var n=r(t);return e&&n.substr(-1*e.length)===e&&(n=n.substr(0,n.length-e.length)),n},e.extname=function(t){"string"!==typeof t&&(t+="");for(var e=-1,n=0,r=-1,o=!0,i=0,a=t.length-1;a>=0;--a){var s=t.charCodeAt(a);if(47!==s)-1===r&&(o=!1,r=a+1),46===s?-1===e?e=a:1!==i&&(i=1):-1!==e&&(i=-1);else if(!o){n=a+1;break}}return-1===e||-1===r||0===i||1===i&&e===r-1&&e===n+1?"":t.slice(e,r)};var i="b"==="ab".substr(-1)?function(t,e,n){return t.substr(e,n)}:function(t,e,n){return e<0&&(e=t.length+e),t.substr(e,n)}}).call(this,n("4362"))},e163:function(t,e,n){var r=n("5135"),o=n("7b0b"),i=n("f772"),a=n("e177"),s=i("IE_PROTO"),c=Object.prototype;t.exports=a?Object.getPrototypeOf:function(t){return t=o(t),r(t,s)?t[s]:"function"==typeof t.constructor&&t instanceof t.constructor?t.constructor.prototype:t instanceof Object?c:null}},e177:function(t,e,n){var r=n("d039");t.exports=!r((function(){function t(){}return t.prototype.constructor=null,Object.getPrototypeOf(new t)!==t.prototype}))},e260:function(t,e,n){"use strict";var r=n("fc6a"),o=n("44d2"),i=n("3f8c"),a=n("69f3"),s=n("7dd0"),c="Array Iterator",u=a.set,f=a.getterFor(c);t.exports=s(Array,"Array",(function(t,e){u(this,{type:c,target:r(t),index:0,kind:e})}),(function(){var t=f(this),e=t.target,n=t.kind,r=t.index++;return!e||r>=e.length?(t.target=void 0,{value:void 0,done:!0}):"keys"==n?{value:r,done:!1}:"values"==n?{value:e[r],done:!1}:{value:[r,e[r]],done:!1}}),"values"),i.Arguments=i.Array,o("keys"),o("values"),o("entries")},e2cc:function(t,e,n){var r=n("6eeb");t.exports=function(t,e,n){for(var o in e)r(t,o,e[o],n);return t}},e667:function(t,e){t.exports=function(t){try{return{error:!1,value:t()}}catch(e){return{error:!0,value:e}}}},e683:function(t,e,n){"use strict";t.exports=function(t,e){return e?t.replace(/\/+$/,"")+"/"+e.replace(/^\/+/,""):t}},e6cf:function(t,e,n){"use strict";var r,o,i,a,s=n("23e7"),c=n("c430"),u=n("da84"),f=n("d066"),l=n("fea9"),p=n("6eeb"),d=n("e2cc"),h=n("d44e"),v=n("2626"),y=n("861d"),m=n("1c0b"),g=n("19aa"),b=n("c6b6"),_=n("8925"),w=n("2266"),x=n("1c7e"),C=n("4840"),O=n("2cf4").set,A=n("b575"),k=n("cdf9"),j=n("44de"),S=n("f069"),$=n("e667"),E=n("69f3"),T=n("94ca"),P=n("b622"),R=n("2d00"),I=P("species"),M="Promise",N=E.get,L=E.set,D=E.getterFor(M),F=l,U=u.TypeError,B=u.document,q=u.process,H=f("fetch"),V=S.f,z=V,G="process"==b(q),W=!!(B&&B.createEvent&&u.dispatchEvent),K="unhandledrejection",X="rejectionhandled",J=0,Y=1,Q=2,Z=1,tt=2,et=T(M,(function(){var t=_(F)!==String(F);if(!t){if(66===R)return!0;if(!G&&"function"!=typeof PromiseRejectionEvent)return!0}if(c&&!F.prototype["finally"])return!0;if(R>=51&&/native code/.test(F))return!1;var e=F.resolve(1),n=function(t){t((function(){}),(function(){}))},r=e.constructor={};return r[I]=n,!(e.then((function(){}))instanceof n)})),nt=et||!x((function(t){F.all(t)["catch"]((function(){}))})),rt=function(t){var e;return!(!y(t)||"function"!=typeof(e=t.then))&&e},ot=function(t,e,n){if(!e.notified){e.notified=!0;var r=e.reactions;A((function(){var o=e.value,i=e.state==Y,a=0;while(r.length>a){var s,c,u,f=r[a++],l=i?f.ok:f.fail,p=f.resolve,d=f.reject,h=f.domain;try{l?(i||(e.rejection===tt&&ct(t,e),e.rejection=Z),!0===l?s=o:(h&&h.enter(),s=l(o),h&&(h.exit(),u=!0)),s===f.promise?d(U("Promise-chain cycle")):(c=rt(s))?c.call(s,p,d):p(s)):d(o)}catch(v){h&&!u&&h.exit(),d(v)}}e.reactions=[],e.notified=!1,n&&!e.rejection&&at(t,e)}))}},it=function(t,e,n){var r,o;W?(r=B.createEvent("Event"),r.promise=e,r.reason=n,r.initEvent(t,!1,!0),u.dispatchEvent(r)):r={promise:e,reason:n},(o=u["on"+t])?o(r):t===K&&j("Unhandled promise rejection",n)},at=function(t,e){O.call(u,(function(){var n,r=e.value,o=st(e);if(o&&(n=$((function(){G?q.emit("unhandledRejection",r,t):it(K,t,r)})),e.rejection=G||st(e)?tt:Z,n.error))throw n.value}))},st=function(t){return t.rejection!==Z&&!t.parent},ct=function(t,e){O.call(u,(function(){G?q.emit("rejectionHandled",t):it(X,t,e.value)}))},ut=function(t,e,n,r){return function(o){t(e,n,o,r)}},ft=function(t,e,n,r){e.done||(e.done=!0,r&&(e=r),e.value=n,e.state=Q,ot(t,e,!0))},lt=function(t,e,n,r){if(!e.done){e.done=!0,r&&(e=r);try{if(t===n)throw U("Promise can't be resolved itself");var o=rt(n);o?A((function(){var r={done:!1};try{o.call(n,ut(lt,t,r,e),ut(ft,t,r,e))}catch(i){ft(t,r,i,e)}})):(e.value=n,e.state=Y,ot(t,e,!1))}catch(i){ft(t,{done:!1},i,e)}}};et&&(F=function(t){g(this,F,M),m(t),r.call(this);var e=N(this);try{t(ut(lt,this,e),ut(ft,this,e))}catch(n){ft(this,e,n)}},r=function(t){L(this,{type:M,done:!1,notified:!1,parent:!1,reactions:[],rejection:!1,state:J,value:void 0})},r.prototype=d(F.prototype,{then:function(t,e){var n=D(this),r=V(C(this,F));return r.ok="function"!=typeof t||t,r.fail="function"==typeof e&&e,r.domain=G?q.domain:void 0,n.parent=!0,n.reactions.push(r),n.state!=J&&ot(this,n,!1),r.promise},catch:function(t){return this.then(void 0,t)}}),o=function(){var t=new r,e=N(t);this.promise=t,this.resolve=ut(lt,t,e),this.reject=ut(ft,t,e)},S.f=V=function(t){return t===F||t===i?new o(t):z(t)},c||"function"!=typeof l||(a=l.prototype.then,p(l.prototype,"then",(function(t,e){var n=this;return new F((function(t,e){a.call(n,t,e)})).then(t,e)}),{unsafe:!0}),"function"==typeof H&&s({global:!0,enumerable:!0,forced:!0},{fetch:function(t){return k(F,H.apply(u,arguments))}}))),s({global:!0,wrap:!0,forced:et},{Promise:F}),h(F,M,!1,!0),v(M),i=f(M),s({target:M,stat:!0,forced:et},{reject:function(t){var e=V(this);return e.reject.call(void 0,t),e.promise}}),s({target:M,stat:!0,forced:c||et},{resolve:function(t){return k(c&&this===i?F:this,t)}}),s({target:M,stat:!0,forced:nt},{all:function(t){var e=this,n=V(e),r=n.resolve,o=n.reject,i=$((function(){var n=m(e.resolve),i=[],a=0,s=1;w(t,(function(t){var c=a++,u=!1;i.push(void 0),s++,n.call(e,t).then((function(t){u||(u=!0,i[c]=t,--s||r(i))}),o)})),--s||r(i)}));return i.error&&o(i.value),n.promise},race:function(t){var e=this,n=V(e),r=n.reject,o=$((function(){var o=m(e.resolve);w(t,(function(t){o.call(e,t).then(n.resolve,r)}))}));return o.error&&r(o.value),n.promise}})},e893:function(t,e,n){var r=n("5135"),o=n("56ef"),i=n("06cf"),a=n("9bf2");t.exports=function(t,e){for(var n=o(e),s=a.f,c=i.f,u=0;ubluebell_frontend
================================================ FILE: lesson77/bluebell/wait-for.sh ================================================ #!/bin/bash TIMEOUT=15 QUIET=0 ADDRS=() echoerr() { if [ "$QUIET" -ne 1 ]; then printf "%s\n" "$*" 1>&2; fi } usage() { exitcode="$1" cat << USAGE >&2 client: $cmdname host:port [host:port] [host:port] [-t timeout] [-- command args] -q | --quiet Do not output any status messages -t TIMEOUT | --timeout=timeout Timeout in seconds, zero for no timeout -- COMMAND ARGS Execute command with args after the test finishes USAGE exit "$exitcode" } wait_for() { results=() for addr in ${ADDRS[@]} do HOST=$(printf "%s\n" "$addr"| cut -d : -f 1) PORT=$(printf "%s\n" "$addr"| cut -d : -f 2) result=1 for i in `seq $TIMEOUT` ; do nc -z "$HOST" "$PORT" > /dev/null 2>&1 result=$? if [ $result -ne 0 ] ; then sleep 1 continue fi break done results=(${results[@]} $result) done num=${#results[@]} for result in ${results[@]} do if [ $result -eq 0 ] ; then num=`expr $num - 1` fi done if [ $num -eq 0 ] ; then if [ $# -gt 0 ] ; then exec "$@" fi exit 0 fi echo "Operation timed out" >&2 exit 1 } while [ $# -gt 0 ] do case "$1" in *:* ) ADDRS=(${ADDRS[@]} $1) shift 1 ;; -q | --quiet) QUIET=1 shift 1 ;; -t) TIMEOUT="$2" if [ "$TIMEOUT" = "" ]; then break; fi shift 2 ;; --timeout=*) TIMEOUT="${1#*=}" shift 1 ;; --) shift break ;; --help) usage 0 ;; *) echoerr "Unknown argument: $1" usage 1 ;; esac done if [ "${#ADDRS[@]}" -eq 0 ]; then echoerr "Error: you need to provide a host and port to test." usage 2 fi wait_for "$@" ================================================ FILE: lesson80/bluebell/.air.conf ================================================ # [Air](https://github.com/cosmtrek/air) TOML 格式的配置文件 # 工作目录 # 使用 . 或绝对路径,请注意 `tmp_dir` 目录必须在 `root` 目录下 root = "." tmp_dir = "tmp" [build] # 只需要写你平常编译使用的shell命令。你也可以使用 `make` cmd = "swag init && go build -o ./tmp/main" # 由`cmd`命令得到的二进制文件名 bin = "tmp/main" # 自定义的二进制,可以添加额外的编译标识例如添加 GIN_MODE=release full_bin = "./tmp/main ./conf/config.yaml" # 监听以下文件扩展名的文件. include_ext = ["go", "tpl", "tmpl", "html", "yaml"] # 忽略这些文件扩展名或目录 exclude_dir = ["assets", "tmp", "vendor", "frontend/node_modules"] # 监听以下指定目录的文件 include_dir = [] # 排除以下文件 exclude_file = [] # 如果文件更改过于频繁,则没有必要在每次更改时都触发构建。可以设置触发构建的延迟时间 delay = 1000 # ms # 发生构建错误时,停止运行旧的二进制文件。 stop_on_error = true # air的日志文件名,该日志文件放置在你的`tmp_dir`中 log = "air_errors.log" [log] # 显示日志时间 time = true [color] # 自定义每个部分显示的颜色。如果找不到颜色,使用原始的应用程序日志。 main = "magenta" watcher = "cyan" build = "yellow" runner = "green" [misc] # 退出时删除tmp目录 clean_on_exit = true ================================================ FILE: lesson80/bluebell/.gitignore ================================================ .idea/ .DS_Store *.log ================================================ FILE: lesson80/bluebell/Dockerfile ================================================ FROM golang:alpine AS builder # 为我们的镜像设置必要的环境变量 ENV GO111MODULE=on \ CGO_ENABLED=0 \ GOOS=linux \ GOARCH=amd64 # 移动到工作目录:/build WORKDIR /build # 复制项目中的 go.mod 和 go.sum文件并下载依赖信息 COPY go.mod . COPY go.sum . RUN go mod download # 将代码复制到容器中 COPY . . # 将我们的代码编译成二进制可执行文件 bluebell_app RUN go build -o bluebell_app . ################### # 接下来创建一个小镜像 ################### FROM debian:stretch-slim COPY ./wait-for.sh / COPY ./templates /templates COPY ./static /static COPY ./conf /conf # 从builder镜像中把/dist/app 拷贝到当前目录 COPY --from=builder /build/bluebell_app / RUN set -eux; \ apt-get update; \ apt-get install -y \ --no-install-recommends \ netcat; \ chmod 755 wait-for.sh # 声明服务端口 EXPOSE 8084 # 需要运行的命令 #ENTRYPOINT ["/bluebell_app", "conf/config.yaml"] ================================================ FILE: lesson80/bluebell/Dockerfile.back ================================================ FROM golang:alpine AS builder # 为我们的镜像设置必要的环境变量 ENV GO111MODULE=on \ CGO_ENABLED=0 \ GOOS=linux \ GOARCH=amd64 # 移动到工作目录:/build WORKDIR /build # 复制项目中的 go.mod 和 go.sum文件并下载依赖信息 COPY go.mod . COPY go.sum . RUN go mod download # 将代码复制到容器中 COPY . . # 将我们的代码编译成二进制可执行文件 bluebell_app RUN go build -o bluebell_app . ################### # 接下来创建一个小镜像 ################### FROM scratch COPY ./templates /templates COPY ./static /static COPY ./conf /conf # 从builder镜像中把/dist/app 拷贝到当前目录 COPY --from=builder /build/bluebell_app / # 声明服务端口 EXPOSE 8084 # 需要运行的命令 ENTRYPOINT ["/bluebell_app", "conf/config.yaml"] ================================================ FILE: lesson80/bluebell/Makefile ================================================ .PHONY: all build run gotool clean help BINARY="bluebell" all: gotool build build: CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "-s -w" -o ./bin/${BINARY} run: @go run ./main.go conf/config.yaml gotool: go fmt ./ go vet ./ clean: @if [ -f ${BINARY} ] ; then rm ${BINARY} ; fi help: @echo "make - 格式化 Go 代码, 并编译生成二进制文件" @echo "make build - 编译 Go 代码, 生成二进制文件" @echo "make run - 直接运行 Go 代码" @echo "make clean - 移除二进制文件和 vim swap files" @echo "make gotool - 运行 Go 工具 'fmt' and 'vet'" ================================================ FILE: lesson80/bluebell/README.md ================================================ # 指南 ## 请按如下顺序启动项目 1. 请根据你的实际情况修改 conf/config.yaml 文件中 MySQL 和 Redis 部分的配置!!! 2. 连接上你的MySQL数据库,按顺序依次执行项目中SQL文件夹中的sql文件,完成建库、建表和导入初始数据 1. init.sql 2. bluebell_user.sql 3. bluebell_community.sql 4. bluebell_post.sql 3. 执行 `go build -o ./bin/bluebell`,编译可执行文件至项目的bin目录 4. 执行 `./bin/bluebell conf/config.yaml`,启动程序 5. 打开你的浏览器输入 [http://127.0.0.1:8084](http://127.0.0.1:8084),默认端口是 8084,你可以在配置文件中修改 ## 注意事项 1. 确保你的MySQL配置是正确的 2. 确保你的Redis配置是正确的 3. 可点击右上角自行注册测试账号 ## 常见错误 如果本地编译遇到以下报错,请在终端执行 `go get -u golang.org/x/sys` 命令。 ```bash # golang.org/x/sys/unix ../../../../learngo/pkg/mod/golang.org/x/sys@v0.0.0-20200905004654-be1d3432aa8f/unix/syscall_darwin.1_13.go:29:3: //go:linkname must refer to declared function or variable ../../../../learngo/pkg/mod/golang.org/x/sys@v0.0.0-20200905004654-be1d3432aa8f/unix/zsyscall_darwin_amd64.1_13.go:27:3: //go:linkname must refer to declared function or variable ../../../../learngo/pkg/mod/golang.org/x/sys@v0.0.0-20200905004654-be1d3432aa8f/unix/zsyscall_darwin_amd64.1_13.go:40:3: //go:linkname must refer to declared function or variable ../../../../learngo/pkg/mod/golang.org/x/sys@v0.0.0-20200905004654-be1d3432aa8f/unix/zsyscall_darwin_amd64.go:28:3: //go:linkname must refer to declared function or variable ../../../../learngo/pkg/mod/golang.org/x/sys@v0.0.0-20200905004654-be1d3432aa8f/unix/zsyscall_darwin_amd64.go:43:3: //go:linkname must refer to declared function or variable ../../../../learngo/pkg/mod/golang.org/x/sys@v0.0.0-20200905004654-be1d3432aa8f/unix/zsyscall_darwin_amd64.go:59:3: //go:linkname must refer to declared function or variable ../../../../learngo/pkg/mod/golang.org/x/sys@v0.0.0-20200905004654-be1d3432aa8f/unix/zsyscall_darwin_amd64.go:75:3: //go:linkname must refer to declared function or variable ../../../../learngo/pkg/mod/golang.org/x/sys@v0.0.0-20200905004654-be1d3432aa8f/unix/zsyscall_darwin_amd64.go:90:3: //go:linkname must refer to declared function or variable ../../../../learngo/pkg/mod/golang.org/x/sys@v0.0.0-20200905004654-be1d3432aa8f/unix/zsyscall_darwin_amd64.go:105:3: //go:linkname must refer to declared function or variable ../../../../learngo/pkg/mod/golang.org/x/sys@v0.0.0-20200905004654-be1d3432aa8f/unix/zsyscall_darwin_amd64.go:121:3: //go:linkname must refer to declared function or variable ../../../../learngo/pkg/mod/golang.org/x/sys@v0.0.0-20200905004654-be1d3432aa8f/unix/zsyscall_darwin_amd64.go:121:3: too many errors ``` ================================================ FILE: lesson80/bluebell/bin/.gitkeep ================================================ ================================================ FILE: lesson80/bluebell/conf/config.yaml ================================================ name: "bluebell" mode: "release" port: 8084 version: "v0.0.1" start_time: "2020-07-01" machine_id: 1 auth: jwt_expire: 8760 log: level: "info" filename: "web_app.log" max_size: 200 max_age: 30 max_backups: 7 mysql: host: mysql8019 port: 3306 user: "root" password: "root1234" dbname: "bluebell" max_open_conns: 200 max_idle_conns: 50 redis: host: redis507 port: 6379 password: "" db: 0 pool_size: 100 ================================================ FILE: lesson80/bluebell/controller/code.go ================================================ package controller type ResCode int64 const ( CodeSuccess ResCode = 1000 + iota CodeInvalidParam CodeUserExist CodeUserNotExist CodeInvalidPassword CodeServerBusy CodeNeedLogin CodeInvalidToken ) var codeMsgMap = map[ResCode]string{ CodeSuccess: "success", CodeInvalidParam: "请求参数错误", CodeUserExist: "用户名已存在", CodeUserNotExist: "用户名不存在", CodeInvalidPassword: "用户名或密码错误", CodeServerBusy: "服务繁忙", CodeNeedLogin: "需要登录", CodeInvalidToken: "无效的token", } func (c ResCode) Msg() string { msg, ok := codeMsgMap[c] if !ok { msg = codeMsgMap[CodeServerBusy] } return msg } ================================================ FILE: lesson80/bluebell/controller/community.go ================================================ package controller import ( "bluebell/logic" "strconv" "github.com/gin-gonic/gin" "go.uber.org/zap" ) // ---- 跟社区相关的 ---- func CommunityHandler(c *gin.Context) { // 查询到所有的社区(community_id, community_name) 以列表的形式返回 data, err := logic.GetCommunityList() if err != nil { zap.L().Error("logic.GetCommunityList() failed", zap.Error(err)) ResponseError(c, CodeServerBusy) // 不轻易把服务端报错暴露给外面 return } ResponseSuccess(c, data) } // CommunityDetailHandler 社区分类详情 func CommunityDetailHandler(c *gin.Context) { // 1. 获取社区id idStr := c.Param("id") // 获取URL参数 id, err := strconv.ParseInt(idStr, 10, 64) if err != nil { ResponseError(c, CodeInvalidParam) return } // 2. 根据id获取社区详情 data, err := logic.GetCommunityDetail(id) if err != nil { zap.L().Error("logic.GetCommunityList() failed", zap.Error(err)) ResponseError(c, CodeServerBusy) // 不轻易把服务端报错暴露给外面 return } ResponseSuccess(c, data) } ================================================ FILE: lesson80/bluebell/controller/doc_response_models.go ================================================ package controller import "bluebell/models" // 专门用来放接口文档用到的model // 因为我们的接口文档返回的数据格式是一致的,但是具体的data类型不一致 // _ResponsePostList 帖子列表接口响应数据 type _ResponsePostList struct { Code ResCode `json:"code"` // 业务响应状态码 Message string `json:"message"` // 提示信息 Data []*models.ApiPostDetail `json:"data"` // 数据 } ================================================ FILE: lesson80/bluebell/controller/post.go ================================================ package controller import ( "bluebell/logic" "bluebell/models" "strconv" "github.com/gin-gonic/gin" "go.uber.org/zap" // swagger 嵌入文件 ) // CreatePostHandler 创建帖子的处理函数 func CreatePostHandler(c *gin.Context) { // 1. 获取参数及参数的校验 //c.ShouldBindJSON() // validator --> binding tag p := new(models.Post) if err := c.ShouldBindJSON(p); err != nil { zap.L().Debug("c.ShouldBindJSON(p) error", zap.Any("err", err)) zap.L().Error("create post with invalid param") ResponseError(c, CodeInvalidParam) return } // 从 c 取到当前发请求的用户的ID userID, err := getCurrentUserID(c) if err != nil { ResponseError(c, CodeNeedLogin) return } p.AuthorID = userID // 2. 创建帖子 if err := logic.CreatePost(p); err != nil { zap.L().Error("logic.CreatePost(p) failed", zap.Error(err)) ResponseError(c, CodeServerBusy) return } // 3. 返回响应 ResponseSuccess(c, nil) } // GetPostDetailHandler 获取帖子详情的处理函数 func GetPostDetailHandler(c *gin.Context) { // 1. 获取参数(从URL中获取帖子的id) pidStr := c.Param("id") pid, err := strconv.ParseInt(pidStr, 10, 64) if err != nil { zap.L().Error("get post detail with invalid param", zap.Error(err)) ResponseError(c, CodeInvalidParam) return } // 2. 根据id取出帖子数据(查数据库) data, err := logic.GetPostById(pid) if err != nil { zap.L().Error("logic.GetPostById(pid) failed", zap.Error(err)) ResponseError(c, CodeServerBusy) return } // 3. 返回响应 ResponseSuccess(c, data) } // GetPostListHandler 获取帖子列表的处理函数 func GetPostListHandler(c *gin.Context) { // 获取分页参数 page, size := getPageInfo(c) // 获取数据 data, err := logic.GetPostList(page, size) if err != nil { zap.L().Error("logic.GetPostList() failed", zap.Error(err)) ResponseError(c, CodeServerBusy) return } ResponseSuccess(c, data) // 返回响应 } // GetPostListHandler2 升级版帖子列表接口 // @Summary 升级版帖子列表接口 // @Description 可按社区按时间或分数排序查询帖子列表接口 // @Tags 帖子相关接口(api分组展示使用的) // @Accept application/json // @Produce application/json // @Param Authorization header string true "Bearer JWT" // @Param object query models.ParamPostList false "查询参数" // @Security ApiKeyAuth // @Success 200 {object} _ResponsePostList // @Router /posts2 [get] func GetPostListHandler2(c *gin.Context) { // GET请求参数(query string):/api/v1/posts2?page=1&size=10&order=time // 初始化结构体时指定初始参数 p := &models.ParamPostList{ Page: 1, Size: 10, Order: models.OrderTime, // magic string } //c.ShouldBind() 根据请求的数据类型选择相应的方法去获取数据 //c.ShouldBindJSON() 如果请求中携带的是json格式的数据,才能用这个方法获取到数据 if err := c.ShouldBindQuery(p); err != nil { zap.L().Error("GetPostListHandler2 with invalid params", zap.Error(err)) ResponseError(c, CodeInvalidParam) return } data, err := logic.GetPostListNew(p) // 更新:合二为一 // 获取数据 if err != nil { zap.L().Error("logic.GetPostList() failed", zap.Error(err)) ResponseError(c, CodeServerBusy) return } ResponseSuccess(c, data) // 返回响应 } // 根据社区去查询帖子列表 //func GetCommunityPostListHandler(c *gin.Context) { // // 初始化结构体时指定初始参数 // p := &models.ParamCommunityPostList{ // ParamPostList: &models.ParamPostList{ // Page: 1, // Size: 10, // Order: models.OrderTime, // }, // } // //c.ShouldBind() 根据请求的数据类型选择相应的方法去获取数据 // //c.ShouldBindJSON() 如果请求中携带的是json格式的数据,才能用这个方法获取到数据 // if err := c.ShouldBindQuery(p); err != nil { // zap.L().Error("GetCommunityPostListHandler with invalid params", zap.Error(err)) // ResponseError(c, CodeInvalidParam) // return // } // // // 获取数据 // data, err := logic.GetCommunityPostList(p) // if err != nil { // zap.L().Error("logic.GetPostList() failed", zap.Error(err)) // ResponseError(c, CodeServerBusy) // return // } // ResponseSuccess(c, data) //} ================================================ FILE: lesson80/bluebell/controller/post_test.go ================================================ package controller import ( "bytes" "encoding/json" "net/http" "net/http/httptest" "testing" "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" ) func TestCreatePostHandler(t *testing.T) { gin.SetMode(gin.TestMode) r := gin.Default() url := "/api/v1/post" r.POST(url, CreatePostHandler) body := `{ "community_id": 1, "title": "test", "content": "just a test" }` req, _ := http.NewRequest(http.MethodPost, url, bytes.NewReader([]byte(body))) w := httptest.NewRecorder() r.ServeHTTP(w, req) assert.Equal(t, 200, w.Code) // 判断响应的内容是不是按预期返回了需要登录的错误 // 方法1:判断响应内容中是不是包含指定的字符串 //assert.Contains(t, w.Body.String(), "需要登录") // 方法2:将响应的内容反序列化到ResponseData 然后判断字段与预期是否一致 res := new(ResponseData) if err := json.Unmarshal(w.Body.Bytes(), res); err != nil { t.Fatalf("json.Unmarshal w.Body failed, err:%v\n", err) } assert.Equal(t, res.Code, CodeNeedLogin) } ================================================ FILE: lesson80/bluebell/controller/request.go ================================================ package controller import ( "errors" "strconv" "github.com/gin-gonic/gin" ) const CtxUserIDKey = "userID" var ErrorUserNotLogin = errors.New("用户未登录") // getCurrentUserID 获取当前登录的用户ID func getCurrentUserID(c *gin.Context) (userID int64, err error) { uid, ok := c.Get(CtxUserIDKey) if !ok { err = ErrorUserNotLogin return } userID, ok = uid.(int64) if !ok { err = ErrorUserNotLogin return } return } func getPageInfo(c *gin.Context) (int64, int64) { pageStr := c.Query("page") sizeStr := c.Query("size") var ( page int64 size int64 err error ) page, err = strconv.ParseInt(pageStr, 10, 64) if err != nil { page = 1 } size, err = strconv.ParseInt(sizeStr, 10, 64) if err != nil { size = 10 } return page, size } ================================================ FILE: lesson80/bluebell/controller/response.go ================================================ package controller import ( "net/http" "github.com/gin-gonic/gin" ) /* { "code": 10000, // 程序中的错误码 "msg": xx, // 提示信息 "data": {}, // 数据 } */ type ResponseData struct { Code ResCode `json:"code"` Msg interface{} `json:"msg"` Data interface{} `json:"data,omitempty"` } func ResponseError(c *gin.Context, code ResCode) { c.JSON(http.StatusOK, &ResponseData{ Code: code, Msg: code.Msg(), Data: nil, }) } func ResponseErrorWithMsg(c *gin.Context, code ResCode, msg interface{}) { c.JSON(http.StatusOK, &ResponseData{ Code: code, Msg: msg, Data: nil, }) } func ResponseSuccess(c *gin.Context, data interface{}) { c.JSON(http.StatusOK, &ResponseData{ Code: CodeSuccess, Msg: CodeSuccess.Msg(), Data: data, }) } ================================================ FILE: lesson80/bluebell/controller/user.go ================================================ package controller import ( "bluebell/dao/mysql" "bluebell/logic" "bluebell/models" "errors" "fmt" "github.com/go-playground/validator/v10" "go.uber.org/zap" "github.com/gin-gonic/gin" ) // SignUpHandler 处理注册请求的函数 func SignUpHandler(c *gin.Context) { // 1. 获取参数和参数校验 p := new(models.ParamSignUp) if err := c.ShouldBindJSON(p); err != nil { // 请求参数有误,直接返回响应 zap.L().Error("SignUp with invalid param", zap.Error(err)) // 判断err是不是validator.ValidationErrors 类型 errs, ok := err.(validator.ValidationErrors) if !ok { ResponseError(c, CodeInvalidParam) return } ResponseErrorWithMsg(c, CodeInvalidParam, removeTopStruct(errs.Translate(trans))) return } // 2. 业务处理 if err := logic.SignUp(p); err != nil { zap.L().Error("logic.SignUp failed", zap.Error(err)) if errors.Is(err, mysql.ErrorUserExist) { ResponseError(c, CodeUserExist) return } ResponseError(c, CodeServerBusy) return } // 3. 返回响应 ResponseSuccess(c, nil) } func LoginHandler(c *gin.Context) { // 1.获取请求参数及参数校验 p := new(models.ParamLogin) if err := c.ShouldBindJSON(p); err != nil { // 请求参数有误,直接返回响应 zap.L().Error("Login with invalid param", zap.Error(err)) // 判断err是不是validator.ValidationErrors 类型 errs, ok := err.(validator.ValidationErrors) if !ok { ResponseError(c, CodeInvalidParam) return } ResponseErrorWithMsg(c, CodeInvalidParam, removeTopStruct(errs.Translate(trans))) return } // 2.业务逻辑处理 user, err := logic.Login(p) if err != nil { zap.L().Error("logic.Login failed", zap.String("username", p.Username), zap.Error(err)) if errors.Is(err, mysql.ErrorUserNotExist) { ResponseError(c, CodeUserNotExist) return } ResponseError(c, CodeInvalidPassword) return } // 3.返回响应 ResponseSuccess(c, gin.H{ "user_id": fmt.Sprintf("%d", user.UserID), // id值大于1<<53-1 int64类型的最大值是1<<63-1 "user_name": user.Username, "token": user.Token, }) } ================================================ FILE: lesson80/bluebell/controller/validator.go ================================================ package controller import ( "bluebell/models" "fmt" "reflect" "strings" "github.com/gin-gonic/gin/binding" "github.com/go-playground/locales/en" "github.com/go-playground/locales/zh" ut "github.com/go-playground/universal-translator" "github.com/go-playground/validator/v10" enTranslations "github.com/go-playground/validator/v10/translations/en" zhTranslations "github.com/go-playground/validator/v10/translations/zh" ) // 定义一个全局翻译器T var trans ut.Translator // InitTrans 初始化翻译器 func InitTrans(locale string) (err error) { // 修改gin框架中的Validator引擎属性,实现自定制 if v, ok := binding.Validator.Engine().(*validator.Validate); ok { // 注册一个获取json tag的自定义方法 v.RegisterTagNameFunc(func(fld reflect.StructField) string { name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0] if name == "-" { return "" } return name }) // 为SignUpParam注册自定义校验方法 v.RegisterStructValidation(SignUpParamStructLevelValidation, models.ParamSignUp{}) zhT := zh.New() // 中文翻译器 enT := en.New() // 英文翻译器 // 第一个参数是备用(fallback)的语言环境 // 后面的参数是应该支持的语言环境(支持多个) // uni := ut.New(zhT, zhT) 也是可以的 uni := ut.New(enT, zhT, enT) // locale 通常取决于 http 请求头的 'Accept-Language' var ok bool // 也可以使用 uni.FindTranslator(...) 传入多个locale进行查找 trans, ok = uni.GetTranslator(locale) if !ok { return fmt.Errorf("uni.GetTranslator(%s) failed", locale) } // 注册翻译器 switch locale { case "en": err = enTranslations.RegisterDefaultTranslations(v, trans) case "zh": err = zhTranslations.RegisterDefaultTranslations(v, trans) default: err = enTranslations.RegisterDefaultTranslations(v, trans) } return } return } // removeTopStruct 去除提示信息中的结构体名称 func removeTopStruct(fields map[string]string) map[string]string { res := map[string]string{} for field, err := range fields { res[field[strings.Index(field, ".")+1:]] = err } return res } // SignUpParamStructLevelValidation 自定义SignUpParam结构体校验函数 func SignUpParamStructLevelValidation(sl validator.StructLevel) { su := sl.Current().Interface().(models.ParamSignUp) if su.Password != su.RePassword { // 输出错误提示信息,最后一个参数就是传递的param sl.ReportError(su.RePassword, "re_password", "RePassword", "eqfield", "password") } } ================================================ FILE: lesson80/bluebell/controller/vote.go ================================================ package controller import ( "bluebell/logic" "bluebell/models" "go.uber.org/zap" "github.com/go-playground/validator/v10" "github.com/gin-gonic/gin" ) // 投票 //type VoteData struct { // // UserID 从请求中获取当前的用户 // PostID int64 `json:"post_id,string"` // 贴子id // Direction int `json:"direction,string"` // 赞成票(1)还是反对票(-1) //} func PostVoteController(c *gin.Context) { // 参数校验 p := new(models.ParamVoteData) if err := c.ShouldBindJSON(p); err != nil { errs, ok := err.(validator.ValidationErrors) // 类型断言 if !ok { ResponseError(c, CodeInvalidParam) return } errData := removeTopStruct(errs.Translate(trans)) // 翻译并去除掉错误提示中的结构体标识 ResponseErrorWithMsg(c, CodeInvalidParam, errData) return } // 获取当前请求的用户的id userID, err := getCurrentUserID(c) if err != nil { ResponseError(c, CodeNeedLogin) return } // 具体投票的业务逻辑 if err := logic.VoteForPost(userID, p); err != nil { zap.L().Error("logic.VoteForPost() failed", zap.Error(err)) ResponseError(c, CodeServerBusy) return } ResponseSuccess(c, nil) } ================================================ FILE: lesson80/bluebell/dao/mysql/community.go ================================================ package mysql import ( "bluebell/models" "database/sql" "go.uber.org/zap" ) func GetCommunityList() (communityList []*models.Community, err error) { sqlStr := "select community_id, community_name from community" if err := db.Select(&communityList, sqlStr); err != nil { if err == sql.ErrNoRows { zap.L().Warn("there is no community in db") err = nil } } return } // GetCommunityDetailByID 根据ID查询社区详情 func GetCommunityDetailByID(id int64) (community *models.CommunityDetail, err error) { community = new(models.CommunityDetail) sqlStr := `select community_id, community_name, introduction, create_time from community where community_id = ? ` if err := db.Get(community, sqlStr, id); err != nil { if err == sql.ErrNoRows { err = ErrorInvalidID } } return community, err } ================================================ FILE: lesson80/bluebell/dao/mysql/error_code.go ================================================ package mysql import "errors" var ( ErrorUserExist = errors.New("用户已存在") ErrorUserNotExist = errors.New("用户不存在") ErrorInvalidPassword = errors.New("用户名或密码错误") ErrorInvalidID = errors.New("无效的ID") ) ================================================ FILE: lesson80/bluebell/dao/mysql/mysql.go ================================================ package mysql import ( "bluebell/setting" "fmt" _ "github.com/go-sql-driver/mysql" "github.com/jmoiron/sqlx" ) var db *sqlx.DB // Init 初始化MySQL连接 func Init(cfg *setting.MySQLConfig) (err error) { // "user:password@tcp(host:port)/dbname" dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?parseTime=true&loc=Local", cfg.User, cfg.Password, cfg.Host, cfg.Port, cfg.DB) db, err = sqlx.Connect("mysql", dsn) if err != nil { return } db.SetMaxOpenConns(cfg.MaxOpenConns) db.SetMaxIdleConns(cfg.MaxIdleConns) return } // Close 关闭MySQL连接 func Close() { _ = db.Close() } ================================================ FILE: lesson80/bluebell/dao/mysql/post.go ================================================ package mysql import ( "bluebell/models" "strings" "github.com/jmoiron/sqlx" ) // CreatePost 创建帖子 func CreatePost(p *models.Post) (err error) { sqlStr := `insert into post( post_id, title, content, author_id, community_id) values (?, ?, ?, ?, ?) ` _, err = db.Exec(sqlStr, p.ID, p.Title, p.Content, p.AuthorID, p.CommunityID) return } // GetPostById 根据id查询单个贴子数据 func GetPostById(pid int64) (post *models.Post, err error) { post = new(models.Post) sqlStr := `select post_id, title, content, author_id, community_id, create_time from post where post_id = ? ` err = db.Get(post, sqlStr, pid) return } // GetPostList 查询帖子列表函数 func GetPostList(page, size int64) (posts []*models.Post, err error) { sqlStr := `select post_id, title, content, author_id, community_id, create_time from post ORDER BY create_time DESC limit ?,? ` posts = make([]*models.Post, 0, 2) // 不要写成make([]*models.Post, 2) err = db.Select(&posts, sqlStr, (page-1)*size, size) return } // GetPostListByIDs 根据给定的id列表查询帖子数据 func GetPostListByIDs(ids []string) (postList []*models.Post, err error) { sqlStr := `select post_id, title, content, author_id, community_id, create_time from post where post_id in (?) order by FIND_IN_SET(post_id, ?) ` // https: //www.liwenzhou.com/posts/Go/sqlx/ query, args, err := sqlx.In(sqlStr, ids, strings.Join(ids, ",")) if err != nil { return nil, err } query = db.Rebind(query) err = db.Select(&postList, query, args...) // !!!!!! return } ================================================ FILE: lesson80/bluebell/dao/mysql/post_test.go ================================================ package mysql import ( "bluebell/models" "bluebell/setting" "testing" ) func init() { dbCfg := setting.MySQLConfig{ Host: "127.0.0.1", User: "root", Password: "root1234", DB: "bluebell", Port: 13306, MaxOpenConns: 10, MaxIdleConns: 10, } err := Init(&dbCfg) if err != nil { panic(err) } } func TestCreatePost(t *testing.T) { post := models.Post{ ID: 10, AuthorID: 123, CommunityID: 1, Title: "test", Content: "just a test", } err := CreatePost(&post) if err != nil { t.Fatalf("CreatePost insert record into mysql failed, err:%v\n", err) } t.Logf("CreatePost insert record into mysql success") } ================================================ FILE: lesson80/bluebell/dao/mysql/user.go ================================================ package mysql import ( "bluebell/models" "crypto/md5" "database/sql" "encoding/hex" ) // 把每一步数据库操作封装成函数 // 待logic层根据业务需求调用 const secret = "liwenzhou.com" // CheckUserExist 检查指定用户名的用户是否存在 func CheckUserExist(username string) (err error) { sqlStr := `select count(user_id) from user where username = ?` var count int64 if err := db.Get(&count, sqlStr, username); err != nil { return err } if count > 0 { return ErrorUserExist } return } // InsertUser 想数据库中插入一条新的用户记录 func InsertUser(user *models.User) (err error) { // 对密码进行加密 user.Password = encryptPassword(user.Password) // 执行SQL语句入库 sqlStr := `insert into user(user_id, username, password) values(?,?,?)` _, err = db.Exec(sqlStr, user.UserID, user.Username, user.Password) return } // encryptPassword 密码加密 func encryptPassword(oPassword string) string { h := md5.New() h.Write([]byte(secret)) h.Write([]byte(oPassword)) return hex.EncodeToString(h.Sum(nil)) } func Login(user *models.User) (err error) { oPassword := user.Password // 用户登录的密码 sqlStr := `select user_id, username, password from user where username=?` err = db.Get(user, sqlStr, user.Username) if err == sql.ErrNoRows { return ErrorUserNotExist } if err != nil { // 查询数据库失败 return err } // 判断密码是否正确 password := encryptPassword(oPassword) if password != user.Password { return ErrorInvalidPassword } return } // GetUserById 根据id获取用户信息 func GetUserById(uid int64) (user *models.User, err error) { user = new(models.User) sqlStr := `select user_id, username from user where user_id = ?` err = db.Get(user, sqlStr, uid) return } ================================================ FILE: lesson80/bluebell/dao/redis/keys.go ================================================ package redis // redis key // redis key注意使用命名空间的方式,方便查询和拆分 const ( Prefix = "bluebell:" // 项目key前缀 KeyPostTimeZSet = "post:time" // zset;贴子及发帖时间 KeyPostScoreZSet = "post:score" // zset;贴子及投票的分数 KeyPostVotedZSetPF = "post:voted:" // zset;记录用户及投票类型;参数是post id KeyCommunitySetPF = "community:" // set;保存每个分区下帖子的id ) // 给redis key加上前缀 func getRedisKey(key string) string { return Prefix + key } ================================================ FILE: lesson80/bluebell/dao/redis/post.go ================================================ package redis import ( "bluebell/models" "context" "strconv" "time" "github.com/go-redis/redis/v8" ) func getIDsFormKey(key string, page, size int64) ([]string, error) { start := (page - 1) * size end := start + size - 1 // 3. ZREVRANGE 按分数从大到小的顺序查询指定数量的元素 return client.ZRevRange(context.Background(), key, start, end).Result() } func GetPostIDsInOrder(p *models.ParamPostList) ([]string, error) { // 从redis获取id // 1. 根据用户请求中携带的order参数确定要查询的redis key key := getRedisKey(KeyPostTimeZSet) if p.Order == models.OrderScore { key = getRedisKey(KeyPostScoreZSet) } // 2. 确定查询的索引起始点 return getIDsFormKey(key, p.Page, p.Size) } // GetPostVoteData 根据ids查询每篇帖子的投赞成票的数据 func GetPostVoteData(ids []string) (data []int64, err error) { //data = make([]int64, 0, len(ids)) //for _, id := range ids { // key := getRedisKey(KeyPostVotedZSetPF + id) // // 查找key中分数是1的元素的数量->统计每篇帖子的赞成票的数量 // v := client.ZCount(key, "1", "1").Val() // data = append(data, v) //} // 使用pipeline一次发送多条命令,减少RTT pipeline := client.Pipeline() for _, id := range ids { key := getRedisKey(KeyPostVotedZSetPF + id) pipeline.ZCount(context.Background(), key, "1", "1") } cmders, err := pipeline.Exec(context.Background()) if err != nil { return nil, err } data = make([]int64, 0, len(cmders)) for _, cmder := range cmders { v := cmder.(*redis.IntCmd).Val() data = append(data, v) } return } // GetCommunityPostIDsInOrder 按社区查询ids func GetCommunityPostIDsInOrder(p *models.ParamPostList) ([]string, error) { orderKey := getRedisKey(KeyPostTimeZSet) if p.Order == models.OrderScore { orderKey = getRedisKey(KeyPostScoreZSet) } // 使用 zinterstore 把分区的帖子set与帖子分数的 zset 生成一个新的zset // 针对新的zset 按之前的逻辑取数据 // 社区的key cKey := getRedisKey(KeyCommunitySetPF + strconv.Itoa(int(p.CommunityID))) // 利用缓存key减少zinterstore执行的次数 key := orderKey + strconv.Itoa(int(p.CommunityID)) if client.Exists(context.Background(), key).Val() < 1 { // 不存在,需要计算 pipeline := client.Pipeline() pipeline.ZInterStore(context.Background(), key, &redis.ZStore{ Keys: []string{cKey, orderKey}, Aggregate: "MAX", }) // zinterstore 计算 pipeline.Expire(context.Background(), key, 60*time.Second) // 设置超时时间 _, err := pipeline.Exec(context.Background()) if err != nil { return nil, err } } // 存在的话就直接根据key查询ids return getIDsFormKey(key, p.Page, p.Size) } ================================================ FILE: lesson80/bluebell/dao/redis/redis.go ================================================ package redis import ( "context" "fmt" "github.com/go-redis/redis/v8" "bluebell/setting" ) // 实际生产环境下 context.Background() 按需替换 var ( client *redis.Client Nil = redis.Nil ) // Init 初始化连接 func Init(cfg *setting.RedisConfig) (err error) { client = redis.NewClient(&redis.Options{ Addr: fmt.Sprintf("%s:%d", cfg.Host, cfg.Port), Password: cfg.Password, // no password set DB: cfg.DB, // use default DB PoolSize: cfg.PoolSize, MinIdleConns: cfg.MinIdleConns, }) _, err = client.Ping(context.Background()).Result() if err != nil { return err } return nil } func Close() { _ = client.Close() } ================================================ FILE: lesson80/bluebell/dao/redis/vote.go ================================================ package redis import ( "context" "errors" "math" "strconv" "time" "github.com/go-redis/redis/v8" ) // 推荐阅读 // 基于用户投票的相关算法:http://www.ruanyifeng.com/blog/algorithm/ // 本项目使用简化版的投票分数 // 投一票就加432分 86400/200 --> 200张赞成票可以给你的帖子续一天 /* 投票的几种情况: direction=1时,有两种情况: 1. 之前没有投过票,现在投赞成票 --> 更新分数和投票记录 差值的绝对值:1 +432 2. 之前投反对票,现在改投赞成票 --> 更新分数和投票记录 差值的绝对值:2 +432*2 direction=0时,有两种情况: 1. 之前投过反对票,现在要取消投票 --> 更新分数和投票记录 差值的绝对值:1 +432 2. 之前投过赞成票,现在要取消投票 --> 更新分数和投票记录 差值的绝对值:1 -432 direction=-1时,有两种情况: 1. 之前没有投过票,现在投反对票 --> 更新分数和投票记录 差值的绝对值:1 -432 2. 之前投赞成票,现在改投反对票 --> 更新分数和投票记录 差值的绝对值:2 -432*2 投票的限制: 每个贴子自发表之日起一个星期之内允许用户投票,超过一个星期就不允许再投票了。 1. 到期之后将redis中保存的赞成票数及反对票数存储到mysql表中 2. 到期之后删除那个 KeyPostVotedZSetPF */ // 实际生产环境下 context.Background() 按需替换 const ( oneWeekInSeconds = 7 * 24 * 3600 scorePerVote = 432 // 每一票值多少分 ) var ( ErrVoteTimeExpire = errors.New("投票时间已过") ErrVoteRepeated = errors.New("不允许重复投票") ) func CreatePost(postID, communityID int64) error { pipeline := client.TxPipeline() // 帖子时间 pipeline.ZAdd(context.Background(), getRedisKey(KeyPostTimeZSet), &redis.Z{ Score: float64(time.Now().Unix()), Member: postID, }) // 帖子分数 pipeline.ZAdd(context.Background(), getRedisKey(KeyPostScoreZSet), &redis.Z{ Score: float64(time.Now().Unix()), Member: postID, }) // 更新:把帖子id加到社区的set cKey := getRedisKey(KeyCommunitySetPF + strconv.Itoa(int(communityID))) pipeline.SAdd(context.Background(), cKey, postID) _, err := pipeline.Exec(context.Background()) return err } func VoteForPost(userID, postID string, value float64) error { // 1. 判断投票限制 // 去redis取帖子发布时间 postTime := client.ZScore(context.Background(), getRedisKey(KeyPostTimeZSet), postID).Val() if float64(time.Now().Unix())-postTime > oneWeekInSeconds { return ErrVoteTimeExpire } // 2和3需要放到一个pipeline事务中操作 // 2. 更新贴子的分数 // 先查当前用户给当前帖子的投票记录 ov := client.ZScore(context.Background(), getRedisKey(KeyPostVotedZSetPF+postID), userID).Val() // 更新:如果这一次投票的值和之前保存的值一致,就提示不允许重复投票 if value == ov { return ErrVoteRepeated } var op float64 if value > ov { op = 1 } else { op = -1 } diff := math.Abs(ov - value) // 计算两次投票的差值 pipeline := client.TxPipeline() pipeline.ZIncrBy(context.Background(), getRedisKey(KeyPostScoreZSet), op*diff*scorePerVote, postID) // 3. 记录用户为该贴子投票的数据 if value == 0 { pipeline.ZRem(context.Background(), getRedisKey(KeyPostVotedZSetPF+postID), userID) } else { pipeline.ZAdd(context.Background(), getRedisKey(KeyPostVotedZSetPF+postID), &redis.Z{ Score: value, // 赞成票还是反对票 Member: userID, }) } _, err := pipeline.Exec(context.Background()) return err } ================================================ FILE: lesson80/bluebell/docker-compose.yml ================================================ # yaml 配置 version: "3.7" services: mysql8019: image: "mysql:8.0.19" ports: - "33061:3306" command: "--default-authentication-plugin=mysql_native_password --init-file /data/application/init.sql" environment: MYSQL_ROOT_PASSWORD: "root1234" MYSQL_DATABASE: "bluebell" MYSQL_PASSWORD: "root1234" volumes: - ./init.sql:/data/application/init.sql redis507: image: "redis:5.0.7" ports: - "26379:6379" bluebell_app: build: . command: sh -c "./wait-for.sh mysql8019:3306 redis507:6379 -- ./bluebell_app ./conf/config.yaml" depends_on: - mysql8019 - redis507 ports: - "8888:8084" ================================================ FILE: lesson80/bluebell/docs/docs.go ================================================ // GENERATED BY THE COMMAND ABOVE; DO NOT EDIT // This file was generated by swaggo/swag package docs import ( "bytes" "encoding/json" "strings" "github.com/alecthomas/template" "github.com/swaggo/swag" ) var doc = `{ "schemes": {{ marshal .Schemes }}, "swagger": "2.0", "info": { "description": "{{.Description}}", "title": "{{.Title}}", "contact": { "name": "liwenzhou", "url": "http://www.liwenzhou.com" }, "license": {}, "version": "{{.Version}}" }, "host": "{{.Host}}", "basePath": "{{.BasePath}}", "paths": { "/posts2": { "get": { "security": [ { "ApiKeyAuth": [] } ], "description": "可按社区按时间或分数排序查询帖子列表接口", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "帖子相关接口(api分组展示使用的)" ], "summary": "升级版帖子列表接口", "parameters": [ { "type": "string", "description": "Bearer JWT", "name": "Authorization", "in": "header", "required": true }, { "type": "integer", "description": "可以为空", "name": "community_id", "in": "query" }, { "type": "string", "example": "score", "description": "排序依据", "name": "order", "in": "query" }, { "type": "integer", "example": 1, "description": "页码", "name": "page", "in": "query" }, { "type": "integer", "example": 10, "description": "每页数据量", "name": "size", "in": "query" } ], "responses": { "200": { "description": "OK", "schema": { "$ref": "#/definitions/controller._ResponsePostList" } } } } } }, "definitions": { "controller._ResponsePostList": { "type": "object", "properties": { "code": { "description": "业务响应状态码", "type": "integer" }, "data": { "description": "数据", "type": "array", "items": { "$ref": "#/definitions/models.ApiPostDetail" } }, "message": { "description": "提示信息", "type": "string" } } }, "models.ApiPostDetail": { "type": "object", "required": [ "community_id", "content", "title" ], "properties": { "author_id": { "description": "作者id", "type": "integer" }, "author_name": { "description": "作者", "type": "string" }, "community_id": { "description": "社区id", "type": "integer" }, "content": { "description": "帖子内容", "type": "string" }, "create_time": { "type": "string" }, "id": { "type": "integer" }, "introduction": { "type": "string" }, "name": { "type": "string" }, "status": { "description": "帖子状态", "type": "integer" }, "title": { "description": "帖子标题", "type": "string" }, "vote_num": { "description": "投票数", "type": "integer" } } }, "models.ParamPostList": { "type": "object", "properties": { "community_id": { "description": "可以为空", "type": "integer" }, "order": { "description": "排序依据", "type": "string", "example": "score" }, "page": { "description": "页码", "type": "integer", "example": 1 }, "size": { "description": "每页数据量", "type": "integer", "example": 10 } } } } }` type swaggerInfo struct { Version string Host string BasePath string Schemes []string Title string Description string } // SwaggerInfo holds exported Swagger Info so clients can modify it var SwaggerInfo = swaggerInfo{ Version: "1.0", Host: "127.0.0.1:8084", BasePath: "/api/v1", Schemes: []string{}, Title: "bluebell项目接口文档", Description: "Go web开发进阶项目实战课程bluebell", } type s struct{} func (s *s) ReadDoc() string { sInfo := SwaggerInfo sInfo.Description = strings.Replace(sInfo.Description, "\n", "\\n", -1) t, err := template.New("swagger_info").Funcs(template.FuncMap{ "marshal": func(v interface{}) string { a, _ := json.Marshal(v) return string(a) }, }).Parse(doc) if err != nil { return doc } var tpl bytes.Buffer if err := t.Execute(&tpl, sInfo); err != nil { return doc } return tpl.String() } func init() { swag.Register(swag.Name, &s{}) } ================================================ FILE: lesson80/bluebell/docs/swagger.json ================================================ { "swagger": "2.0", "info": { "description": "Go web开发进阶项目实战课程bluebell", "title": "bluebell项目接口文档", "contact": { "name": "liwenzhou", "url": "http://www.liwenzhou.com" }, "license": {}, "version": "1.0" }, "host": "127.0.0.1:8084", "basePath": "/api/v1", "paths": { "/posts2": { "get": { "security": [ { "ApiKeyAuth": [] } ], "description": "可按社区按时间或分数排序查询帖子列表接口", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "帖子相关接口(api分组展示使用的)" ], "summary": "升级版帖子列表接口", "parameters": [ { "type": "string", "description": "Bearer JWT", "name": "Authorization", "in": "header", "required": true }, { "type": "integer", "description": "可以为空", "name": "community_id", "in": "query" }, { "type": "string", "example": "score", "description": "排序依据", "name": "order", "in": "query" }, { "type": "integer", "example": 1, "description": "页码", "name": "page", "in": "query" }, { "type": "integer", "example": 10, "description": "每页数据量", "name": "size", "in": "query" } ], "responses": { "200": { "description": "OK", "schema": { "$ref": "#/definitions/controller._ResponsePostList" } } } } } }, "definitions": { "controller._ResponsePostList": { "type": "object", "properties": { "code": { "description": "业务响应状态码", "type": "integer" }, "data": { "description": "数据", "type": "array", "items": { "$ref": "#/definitions/models.ApiPostDetail" } }, "message": { "description": "提示信息", "type": "string" } } }, "models.ApiPostDetail": { "type": "object", "required": [ "community_id", "content", "title" ], "properties": { "author_id": { "description": "作者id", "type": "integer" }, "author_name": { "description": "作者", "type": "string" }, "community_id": { "description": "社区id", "type": "integer" }, "content": { "description": "帖子内容", "type": "string" }, "create_time": { "type": "string" }, "id": { "type": "integer" }, "introduction": { "type": "string" }, "name": { "type": "string" }, "status": { "description": "帖子状态", "type": "integer" }, "title": { "description": "帖子标题", "type": "string" }, "vote_num": { "description": "投票数", "type": "integer" } } }, "models.ParamPostList": { "type": "object", "properties": { "community_id": { "description": "可以为空", "type": "integer" }, "order": { "description": "排序依据", "type": "string", "example": "score" }, "page": { "description": "页码", "type": "integer", "example": 1 }, "size": { "description": "每页数据量", "type": "integer", "example": 10 } } } } } ================================================ FILE: lesson80/bluebell/docs/swagger.yaml ================================================ basePath: /api/v1 definitions: controller._ResponsePostList: properties: code: description: 业务响应状态码 type: integer data: description: 数据 items: $ref: '#/definitions/models.ApiPostDetail' type: array message: description: 提示信息 type: string type: object models.ApiPostDetail: properties: author_id: description: 作者id type: integer author_name: description: 作者 type: string community_id: description: 社区id type: integer content: description: 帖子内容 type: string create_time: type: string id: type: integer introduction: type: string name: type: string status: description: 帖子状态 type: integer title: description: 帖子标题 type: string vote_num: description: 投票数 type: integer required: - community_id - content - title type: object models.ParamPostList: properties: community_id: description: 可以为空 type: integer order: description: 排序依据 example: score type: string page: description: 页码 example: 1 type: integer size: description: 每页数据量 example: 10 type: integer type: object host: 127.0.0.1:8084 info: contact: name: liwenzhou url: http://www.liwenzhou.com description: Go web开发进阶项目实战课程bluebell license: {} title: bluebell项目接口文档 version: "1.0" paths: /posts2: get: consumes: - application/json description: 可按社区按时间或分数排序查询帖子列表接口 parameters: - description: Bearer JWT in: header name: Authorization required: true type: string - description: 可以为空 in: query name: community_id type: integer - description: 排序依据 example: score in: query name: order type: string - description: 页码 example: 1 in: query name: page type: integer - description: 每页数据量 example: 10 in: query name: size type: integer produces: - application/json responses: "200": description: OK schema: $ref: '#/definitions/controller._ResponsePostList' security: - ApiKeyAuth: [] summary: 升级版帖子列表接口 tags: - 帖子相关接口(api分组展示使用的) swagger: "2.0" ================================================ FILE: lesson80/bluebell/go.mod ================================================ module bluebell go 1.18 require ( github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 github.com/bwmarrin/snowflake v0.3.0 github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/fsnotify/fsnotify v1.4.9 github.com/gin-contrib/pprof v1.3.0 github.com/gin-gonic/gin v1.6.3 github.com/go-playground/locales v0.13.0 github.com/go-playground/universal-translator v0.17.0 github.com/go-playground/validator/v10 v10.3.0 github.com/go-redis/redis v6.15.8+incompatible github.com/go-redis/redis/v8 v8.11.5 github.com/go-sql-driver/mysql v1.5.0 github.com/jmoiron/sqlx v1.2.0 github.com/juju/ratelimit v1.0.1 github.com/natefinch/lumberjack v2.0.0+incompatible github.com/spf13/viper v1.7.0 github.com/stretchr/testify v1.5.1 github.com/swaggo/gin-swagger v1.2.0 github.com/swaggo/swag v1.6.7 go.uber.org/zap v1.15.0 ) require ( github.com/KyleBanks/depth v1.2.1 // indirect github.com/PuerkitoBio/purell v1.1.1 // indirect github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-openapi/jsonpointer v0.19.3 // indirect github.com/go-openapi/jsonreference v0.19.4 // indirect github.com/go-openapi/spec v0.19.9 // indirect github.com/go-openapi/swag v0.19.9 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.10 // indirect github.com/leodido/go-urn v1.2.0 // indirect github.com/magiconair/properties v1.8.1 // indirect github.com/mailru/easyjson v0.7.6 // indirect github.com/mattn/go-isatty v0.0.12 // indirect github.com/mitchellh/mapstructure v1.1.2 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.1 // indirect github.com/pelletier/go-toml v1.2.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/spf13/afero v1.1.2 // indirect github.com/spf13/cast v1.3.0 // indirect github.com/spf13/jwalterweatherman v1.0.0 // indirect github.com/spf13/pflag v1.0.3 // indirect github.com/subosito/gotenv v1.2.0 // indirect github.com/ugorji/go/codec v1.1.7 // indirect go.uber.org/atomic v1.6.0 // indirect go.uber.org/multierr v1.5.0 // indirect golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 // indirect golang.org/x/sys v0.3.0 // indirect golang.org/x/text v0.3.6 // indirect golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e // indirect google.golang.org/protobuf v1.26.0 // indirect gopkg.in/ini.v1 v1.51.0 // indirect gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) ================================================ FILE: lesson80/bluebell/go.sum ================================================ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0= github.com/bwmarrin/snowflake v0.3.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/E9WsDpxqwE= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/gzip v0.0.1 h1:ezvKOL6jH+jlzdHNE4h9h8q8uMpDQjyl0NN0Jd7jozc= github.com/gin-contrib/gzip v0.0.1/go.mod h1:fGBJBCdt6qCZuCAOwWuFhBB4OOq9EFqlo5dEaFhhu5w= github.com/gin-contrib/pprof v1.3.0 h1:G9eK6HnbkSqDZBYbzG4wrjCsA4e+cvYAHUZw6W+W9K0= github.com/gin-contrib/pprof v1.3.0/go.mod h1:waMjT1H9b179t3CxuG1cV3DHpga6ybizwfBaM5OXaB0= github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.3.0/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y= github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= github.com/gin-gonic/gin v1.6.2/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14= github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= github.com/go-openapi/jsonreference v0.19.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= github.com/go-openapi/jsonreference v0.19.4 h1:3Vw+rh13uq2JFNxgnMTGE1rnoieU9FmyE1gvnyylsYg= github.com/go-openapi/jsonreference v0.19.4/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= github.com/go-openapi/spec v0.19.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= github.com/go-openapi/spec v0.19.4/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= github.com/go-openapi/spec v0.19.9 h1:9z9cbFuZJ7AcvOHKIY+f6Aevb4vObNDkTEyoMfO7rAc= github.com/go-openapi/spec v0.19.9/go.mod h1:vqK/dIdLGCosfvYsQV3WfC7N3TiZSnGY2RZKoFK7X28= github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.9 h1:1IxuqvBUU3S2Bi4YC7tlP9SJF1gVpCvqN0T2Qof4azE= github.com/go-openapi/swag v0.19.9/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= github.com/go-playground/validator/v10 v10.3.0 h1:nZU+7q+yJoFmwvNgv/LnPUkwPal62+b2xXj0AU1Es7o= github.com/go-playground/validator/v10 v10.3.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= github.com/go-redis/redis v6.15.8+incompatible h1:BKZuG6mCnRj5AOaWJXoCgf6rqTYnYJLe4en2hxT7r9o= github.com/go-redis/redis v6.15.8+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA= github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/juju/ratelimit v1.0.1 h1:+7AIFJVQ0EQgq/K9+0Krm7m530Du7tIz0METWzN0RgY= github.com/juju/ratelimit v1.0.1/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-sqlite3 v1.9.0 h1:pDRiWfl+++eC2FEFRy6jXmQlvp4Yh3z1MJKg4UeYM/4= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/natefinch/lumberjack v2.0.0+incompatible h1:4QJd3OLAMgj7ph+yZTuX13Ld4UpgHp07nNdFX7mqFfM= github.com/natefinch/lumberjack v2.0.0+incompatible/go.mod h1:Wi9p2TTF5DG5oU+6YfsmYQpsTIOm0B1VNzQg9Mw6nPk= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14/go.mod h1:gxQT6pBGRuIGunNf/+tSOB5OHvguWi8Tbt82WOkf35E= github.com/swaggo/gin-swagger v1.2.0 h1:YskZXEiv51fjOMTsXrOetAjrMDfFaXD79PEoQBOe2W0= github.com/swaggo/gin-swagger v1.2.0/go.mod h1:qlH2+W7zXGZkczuL+r2nEBR2JTT+/lX05Nn6vPhc7OI= github.com/swaggo/swag v1.5.1/go.mod h1:1Bl9F/ZBpVWh22nY0zmYyASPO1lI/zIwRDrpZU+tv8Y= github.com/swaggo/swag v1.6.7 h1:e8GC2xDllJZr3omJkm9YfmK0Y56+rMO3cg0JBKNz09s= github.com/swaggo/swag v1.6.7/go.mod h1:xDhTyuFIujYiN3DKWC/H/83xcfHp+UE/IzWWampG7Zc= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go v1.1.5-pre/go.mod h1:FwP/aQVg39TXzItUBMwnWp9T9gPQnXw4Poh4/oBQZ/0= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v0.0.0-20181022190402-e5e69e061d4f/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/ugorji/go/codec v1.1.5-pre/go.mod h1:tULtS6Gy1AE1yCENaw4Vb//HLH5njI2tfCQDUqRd8fI= github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.15.0 h1:ZZCA22JRF2gQE5FoNmhmrf7jeJJ2uhqDUNRYKm8dvmM= go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190611141213-3f473d35a33a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606050223-4d9ae51c2468/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190611222205-d73e1c7e250b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e h1:4nW4NLDYnU28ojHaHO8OVxFHk/aQ33U01a9cjED+pzE= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= ================================================ FILE: lesson80/bluebell/logger/logger.go ================================================ package logger import ( "bluebell/setting" "net" "net/http" "net/http/httputil" "os" "runtime/debug" "strings" "time" "github.com/gin-gonic/gin" "github.com/natefinch/lumberjack" "go.uber.org/zap" "go.uber.org/zap/zapcore" ) var lg *zap.Logger // Init 初始化lg func Init(cfg *setting.LogConfig, mode string) (err error) { writeSyncer := getLogWriter(cfg.Filename, cfg.MaxSize, cfg.MaxBackups, cfg.MaxAge) encoder := getEncoder() var l = new(zapcore.Level) err = l.UnmarshalText([]byte(cfg.Level)) if err != nil { return } var core zapcore.Core if mode == "dev" { // 进入开发模式,日志输出到终端 consoleEncoder := zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig()) core = zapcore.NewTee( zapcore.NewCore(encoder, writeSyncer, l), zapcore.NewCore(consoleEncoder, zapcore.Lock(os.Stdout), zapcore.DebugLevel), ) } else { core = zapcore.NewCore(encoder, writeSyncer, l) } lg = zap.New(core, zap.AddCaller()) zap.ReplaceGlobals(lg) zap.L().Info("init logger success") return } func getEncoder() zapcore.Encoder { encoderConfig := zap.NewProductionEncoderConfig() encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder encoderConfig.TimeKey = "time" encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder encoderConfig.EncodeDuration = zapcore.SecondsDurationEncoder encoderConfig.EncodeCaller = zapcore.ShortCallerEncoder return zapcore.NewJSONEncoder(encoderConfig) } func getLogWriter(filename string, maxSize, maxBackup, maxAge int) zapcore.WriteSyncer { lumberJackLogger := &lumberjack.Logger{ Filename: filename, MaxSize: maxSize, MaxBackups: maxBackup, MaxAge: maxAge, } return zapcore.AddSync(lumberJackLogger) } // GinLogger 接收gin框架默认的日志 func GinLogger() gin.HandlerFunc { return func(c *gin.Context) { start := time.Now() path := c.Request.URL.Path query := c.Request.URL.RawQuery c.Next() cost := time.Since(start) lg.Info(path, zap.Int("status", c.Writer.Status()), zap.String("method", c.Request.Method), zap.String("path", path), zap.String("query", query), zap.String("ip", c.ClientIP()), zap.String("user-agent", c.Request.UserAgent()), zap.String("errors", c.Errors.ByType(gin.ErrorTypePrivate).String()), zap.Duration("cost", cost), ) } } // GinRecovery recover掉项目可能出现的panic,并使用zap记录相关日志 func GinRecovery(stack bool) gin.HandlerFunc { return func(c *gin.Context) { defer func() { if err := recover(); err != nil { // Check for a broken connection, as it is not really a // condition that warrants a panic stack trace. var brokenPipe bool if ne, ok := err.(*net.OpError); ok { if se, ok := ne.Err.(*os.SyscallError); ok { if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") { brokenPipe = true } } } httpRequest, _ := httputil.DumpRequest(c.Request, false) if brokenPipe { lg.Error(c.Request.URL.Path, zap.Any("error", err), zap.String("request", string(httpRequest)), ) // If the connection is dead, we can't write a status to it. c.Error(err.(error)) // nolint: errcheck c.Abort() return } if stack { lg.Error("[Recovery from panic]", zap.Any("error", err), zap.String("request", string(httpRequest)), zap.String("stack", string(debug.Stack())), ) } else { lg.Error("[Recovery from panic]", zap.Any("error", err), zap.String("request", string(httpRequest)), ) } c.AbortWithStatus(http.StatusInternalServerError) } }() c.Next() } } ================================================ FILE: lesson80/bluebell/logic/community.go ================================================ package logic import ( "bluebell/dao/mysql" "bluebell/models" ) func GetCommunityList() ([]*models.Community, error) { // 查数据库 查找到所有的community 并返回 return mysql.GetCommunityList() } func GetCommunityDetail(id int64) (*models.CommunityDetail, error) { return mysql.GetCommunityDetailByID(id) } ================================================ FILE: lesson80/bluebell/logic/post.go ================================================ package logic import ( "bluebell/dao/mysql" "bluebell/dao/redis" "bluebell/models" "bluebell/pkg/snowflake" "go.uber.org/zap" ) func CreatePost(p *models.Post) (err error) { // 1. 生成post id p.ID = snowflake.GenID() // 2. 保存到数据库 err = mysql.CreatePost(p) if err != nil { return err } err = redis.CreatePost(p.ID, p.CommunityID) return // 3. 返回 } // GetPostById 根据帖子id查询帖子详情数据 func GetPostById(pid int64) (data *models.ApiPostDetail, err error) { // 查询并组合我们接口想用的数据 post, err := mysql.GetPostById(pid) if err != nil { zap.L().Error("mysql.GetPostById(pid) failed", zap.Int64("pid", pid), zap.Error(err)) return } // 根据作者id查询作者信息 user, err := mysql.GetUserById(post.AuthorID) if err != nil { zap.L().Error("mysql.GetUserById(post.AuthorID) failed", zap.Int64("author_id", post.AuthorID), zap.Error(err)) return } // 根据社区id查询社区详细信息 community, err := mysql.GetCommunityDetailByID(post.CommunityID) if err != nil { zap.L().Error("mysql.GetUserById(post.AuthorID) failed", zap.Int64("community_id", post.CommunityID), zap.Error(err)) return } // 接口数据拼接 data = &models.ApiPostDetail{ AuthorName: user.Username, Post: post, CommunityDetail: community, } return } // GetPostList 获取帖子列表 func GetPostList(page, size int64) (data []*models.ApiPostDetail, err error) { posts, err := mysql.GetPostList(page, size) if err != nil { return nil, err } data = make([]*models.ApiPostDetail, 0, len(posts)) for _, post := range posts { // 根据作者id查询作者信息 user, err := mysql.GetUserById(post.AuthorID) if err != nil { zap.L().Error("mysql.GetUserById(post.AuthorID) failed", zap.Int64("author_id", post.AuthorID), zap.Error(err)) continue } // 根据社区id查询社区详细信息 community, err := mysql.GetCommunityDetailByID(post.CommunityID) if err != nil { zap.L().Error("mysql.GetUserById(post.AuthorID) failed", zap.Int64("community_id", post.CommunityID), zap.Error(err)) continue } postDetail := &models.ApiPostDetail{ AuthorName: user.Username, Post: post, CommunityDetail: community, } data = append(data, postDetail) } return } func GetPostList2(p *models.ParamPostList) (data []*models.ApiPostDetail, err error) { // 2. 去redis查询id列表 ids, err := redis.GetPostIDsInOrder(p) if err != nil { return } if len(ids) == 0 { zap.L().Warn("redis.GetPostIDsInOrder(p) return 0 data") return } zap.L().Debug("GetPostList2", zap.Any("ids", ids)) // 3. 根据id去MySQL数据库查询帖子详细信息 // 返回的数据还要按照我给定的id的顺序返回 posts, err := mysql.GetPostListByIDs(ids) if err != nil { return } zap.L().Debug("GetPostList2", zap.Any("posts", posts)) // 提前查询好每篇帖子的投票数 voteData, err := redis.GetPostVoteData(ids) if err != nil { return } // 将帖子的作者及分区信息查询出来填充到帖子中 for idx, post := range posts { // 根据作者id查询作者信息 user, err := mysql.GetUserById(post.AuthorID) if err != nil { zap.L().Error("mysql.GetUserById(post.AuthorID) failed", zap.Int64("author_id", post.AuthorID), zap.Error(err)) continue } // 根据社区id查询社区详细信息 community, err := mysql.GetCommunityDetailByID(post.CommunityID) if err != nil { zap.L().Error("mysql.GetUserById(post.AuthorID) failed", zap.Int64("community_id", post.CommunityID), zap.Error(err)) continue } postDetail := &models.ApiPostDetail{ AuthorName: user.Username, VoteNum: voteData[idx], Post: post, CommunityDetail: community, } data = append(data, postDetail) } return } func GetCommunityPostList(p *models.ParamPostList) (data []*models.ApiPostDetail, err error) { // 2. 去redis查询id列表 ids, err := redis.GetCommunityPostIDsInOrder(p) if err != nil { return } if len(ids) == 0 { zap.L().Warn("redis.GetPostIDsInOrder(p) return 0 data") return } zap.L().Debug("GetCommunityPostIDsInOrder", zap.Any("ids", ids)) // 3. 根据id去MySQL数据库查询帖子详细信息 // 返回的数据还要按照我给定的id的顺序返回 posts, err := mysql.GetPostListByIDs(ids) if err != nil { return } zap.L().Debug("GetPostList2", zap.Any("posts", posts)) // 提前查询好每篇帖子的投票数 voteData, err := redis.GetPostVoteData(ids) if err != nil { return } // 将帖子的作者及分区信息查询出来填充到帖子中 for idx, post := range posts { // 根据作者id查询作者信息 user, err := mysql.GetUserById(post.AuthorID) if err != nil { zap.L().Error("mysql.GetUserById(post.AuthorID) failed", zap.Int64("author_id", post.AuthorID), zap.Error(err)) continue } // 根据社区id查询社区详细信息 community, err := mysql.GetCommunityDetailByID(post.CommunityID) if err != nil { zap.L().Error("mysql.GetUserById(post.AuthorID) failed", zap.Int64("community_id", post.CommunityID), zap.Error(err)) continue } postDetail := &models.ApiPostDetail{ AuthorName: user.Username, VoteNum: voteData[idx], Post: post, CommunityDetail: community, } data = append(data, postDetail) } return } // GetPostListNew 将两个查询帖子列表逻辑合二为一的函数 func GetPostListNew(p *models.ParamPostList) (data []*models.ApiPostDetail, err error) { // 根据请求参数的不同,执行不同的逻辑。 if p.CommunityID == 0 { // 查所有 data, err = GetPostList2(p) } else { // 根据社区id查询 data, err = GetCommunityPostList(p) } if err != nil { zap.L().Error("GetPostListNew failed", zap.Error(err)) return nil, err } return } ================================================ FILE: lesson80/bluebell/logic/user.go ================================================ package logic import ( "bluebell/dao/mysql" "bluebell/models" "bluebell/pkg/jwt" "bluebell/pkg/snowflake" ) // 存放业务逻辑的代码 func SignUp(p *models.ParamSignUp) (err error) { // 1.判断用户存不存在 if err := mysql.CheckUserExist(p.Username); err != nil { return err } // 2.生成UID userID := snowflake.GenID() // 构造一个User实例 user := &models.User{ UserID: userID, Username: p.Username, Password: p.Password, } // 3.保存进数据库 return mysql.InsertUser(user) } func Login(p *models.ParamLogin) (user *models.User, err error) { user = &models.User{ Username: p.Username, Password: p.Password, } // 传递的是指针,就能拿到user.UserID if err := mysql.Login(user); err != nil { return nil, err } // 生成JWT token, err := jwt.GenToken(user.UserID, user.Username) if err != nil { return } user.Token = token return } ================================================ FILE: lesson80/bluebell/logic/vote.go ================================================ package logic import ( "bluebell/dao/redis" "bluebell/models" "strconv" "go.uber.org/zap" ) // 推荐阅读 // 基于用户投票的相关算法:http://www.ruanyifeng.com/blog/algorithm/ // 本项目使用简化版的投票分数 // 投一票就加432分 86400/200 --> 200张赞成票可以给你的帖子续一天 /* 投票的几种情况: direction=1时,有两种情况: 1. 之前没有投过票,现在投赞成票 --> 更新分数和投票记录 2. 之前投反对票,现在改投赞成票 --> 更新分数和投票记录 direction=0时,有两种情况: 1. 之前投过赞成票,现在要取消投票 --> 更新分数和投票记录 2. 之前投过反对票,现在要取消投票 --> 更新分数和投票记录 direction=-1时,有两种情况: 1. 之前没有投过票,现在投反对票 --> 更新分数和投票记录 2. 之前投赞成票,现在改投反对票 --> 更新分数和投票记录 投票的限制: 每个贴子自发表之日起一个星期之内允许用户投票,超过一个星期就不允许再投票了。 1. 到期之后将redis中保存的赞成票数及反对票数存储到mysql表中 2. 到期之后删除那个 KeyPostVotedZSetPF */ // VoteForPost 为帖子投票的函数 func VoteForPost(userID int64, p *models.ParamVoteData) error { zap.L().Debug("VoteForPost", zap.Int64("userID", userID), zap.String("postID", p.PostID), zap.Int8("direction", p.Direction)) return redis.VoteForPost(strconv.Itoa(int(userID)), p.PostID, float64(p.Direction)) } ================================================ FILE: lesson80/bluebell/main.go ================================================ package main import ( "bluebell/controller" "bluebell/dao/mysql" "bluebell/dao/redis" "bluebell/logger" "bluebell/pkg/snowflake" "bluebell/router" "bluebell/setting" "fmt" "os" ) // @title bluebell项目接口文档 // @version 1.0 // @description Go web开发进阶项目实战课程bluebell // @contact.name liwenzhou // @contact.url http://www.liwenzhou.com // @host 127.0.0.1:8084 // @BasePath /api/v1 func main() { if len(os.Args) < 2 { fmt.Println("need config file.eg: bluebell config.yaml") return } // 加载配置 if err := setting.Init(os.Args[1]); err != nil { fmt.Printf("load config failed, err:%v\n", err) return } if err := logger.Init(setting.Conf.LogConfig, setting.Conf.Mode); err != nil { fmt.Printf("init logger failed, err:%v\n", err) return } if err := mysql.Init(setting.Conf.MySQLConfig); err != nil { fmt.Printf("init mysql failed, err:%v\n", err) return } defer mysql.Close() // 程序退出关闭数据库连接 if err := redis.Init(setting.Conf.RedisConfig); err != nil { fmt.Printf("init redis failed, err:%v\n", err) return } defer redis.Close() if err := snowflake.Init(setting.Conf.StartTime, setting.Conf.MachineID); err != nil { fmt.Printf("init snowflake failed, err:%v\n", err) return } // 初始化gin框架内置的校验器使用的翻译器 if err := controller.InitTrans("zh"); err != nil { fmt.Printf("init validator trans failed, err:%v\n", err) return } // 注册路由 r := router.SetupRouter(setting.Conf.Mode) err := r.Run(fmt.Sprintf(":%d", setting.Conf.Port)) if err != nil { fmt.Printf("run server failed, err:%v\n", err) return } } ================================================ FILE: lesson80/bluebell/middlewares/auth.go ================================================ package middlewares import ( "bluebell/controller" "bluebell/pkg/jwt" "strings" "github.com/gin-gonic/gin" ) // JWTAuthMiddleware 基于JWT的认证中间件 func JWTAuthMiddleware() func(c *gin.Context) { return func(c *gin.Context) { // 客户端携带Token有三种方式 1.放在请求头 2.放在请求体 3.放在URI // 这里假设Token放在Header的Authorization中,并使用Bearer开头 // Authorization: Bearer xxxxxxx.xxx.xxx / X-TOKEN: xxx.xxx.xx // 这里的具体实现方式要依据你的实际业务情况决定 authHeader := c.Request.Header.Get("Authorization") if authHeader == "" { controller.ResponseError(c, controller.CodeNeedLogin) c.Abort() return } // 按空格分割 parts := strings.SplitN(authHeader, " ", 2) if !(len(parts) == 2 && parts[0] == "Bearer") { controller.ResponseError(c, controller.CodeInvalidToken) c.Abort() return } // parts[1]是获取到的tokenString,我们使用之前定义好的解析JWT的函数来解析它 mc, err := jwt.ParseToken(parts[1]) if err != nil { controller.ResponseError(c, controller.CodeInvalidToken) c.Abort() return } // 将当前请求的userID信息保存到请求的上下文c上 c.Set(controller.CtxUserIDKey, mc.UserID) c.Next() // 后续的处理请求的函数中 可以用过c.Get(CtxUserIDKey) 来获取当前请求的用户信息 } } ================================================ FILE: lesson80/bluebell/middlewares/ratelimit.go ================================================ package middlewares import ( "net/http" "time" "github.com/gin-gonic/gin" "github.com/juju/ratelimit" ) func RateLimitMiddleware(fillInterval time.Duration, cap int64) func(c *gin.Context) { bucket := ratelimit.NewBucket(fillInterval, cap) return func(c *gin.Context) { // 如果取不到令牌就返回响应 //if bucket.Take(1) > 0 { if bucket.TakeAvailable(1) != 1 { c.String(http.StatusOK, "rate limit...") c.Abort() return } // 取到令牌就放行 c.Next() } } ================================================ FILE: lesson80/bluebell/models/community.go ================================================ package models import "time" type Community struct { ID int64 `json:"id" db:"community_id"` Name string `json:"name" db:"community_name"` } type CommunityDetail struct { ID int64 `json:"id" db:"community_id"` Name string `json:"name" db:"community_name"` Introduction string `json:"introduction,omitempty" db:"introduction"` CreateTime time.Time `json:"create_time" db:"create_time"` } ================================================ FILE: lesson80/bluebell/models/create_table.sql ================================================ -- CREATE TABLE `user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `user_id` bigint(20) NOT NULL, `username` varchar(64) COLLATE utf8mb4_general_ci NOT NULL, `password` varchar(64) COLLATE utf8mb4_general_ci NOT NULL, `email` varchar(64) COLLATE utf8mb4_general_ci, `gender` tinyint(4) NOT NULL DEFAULT '0', `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP, `update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `idx_username` (`username`) USING BTREE, UNIQUE KEY `idx_user_id` (`user_id`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; DROP TABLE IF EXISTS `community`; CREATE TABLE `community` ( `id` int(11) NOT NULL AUTO_INCREMENT, `community_id` int(10) unsigned NOT NULL, `community_name` varchar(128) COLLATE utf8mb4_general_ci NOT NULL, `introduction` varchar(256) COLLATE utf8mb4_general_ci NOT NULL, `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `idx_community_id` (`community_id`), UNIQUE KEY `idx_community_name` (`community_name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; INSERT INTO `community` VALUES ('1', '1', 'Go', 'Golang', '2016-11-01 08:10:10', '2016-11-01 08:10:10'); INSERT INTO `community` VALUES ('2', '2', 'leetcode', '刷题刷题刷题', '2020-01-01 08:00:00', '2020-01-01 08:00:00'); INSERT INTO `community` VALUES ('3', '3', 'CS:GO', 'Rush B。。。', '2018-08-07 08:30:00', '2018-08-07 08:30:00'); INSERT INTO `community` VALUES ('4', '4', 'LOL', '欢迎来到英雄联盟!', '2016-01-01 08:00:00', '2016-01-01 08:00:00'); DROP TABLE IF EXISTS `post`; CREATE TABLE `post` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `post_id` bigint(20) NOT NULL COMMENT '帖子id', `title` varchar(128) COLLATE utf8mb4_general_ci NOT NULL COMMENT '标题', `content` varchar(8192) COLLATE utf8mb4_general_ci NOT NULL COMMENT '内容', `author_id` bigint(20) NOT NULL COMMENT '作者的用户id', `community_id` bigint(20) NOT NULL COMMENT '所属社区', `status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '帖子状态', `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), UNIQUE KEY `idx_post_id` (`post_id`), KEY `idx_author_id` (`author_id`), KEY `idx_community_id` (`community_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; ================================================ FILE: lesson80/bluebell/models/params.go ================================================ package models // 定义请求的参数结构体 const ( OrderTime = "time" OrderScore = "score" ) // ParamSignUp 注册请求参数 type ParamSignUp struct { Username string `json:"username" binding:"required"` Password string `json:"password" binding:"required"` RePassword string `json:"confirm_password" binding:"required,eqfield=Password"` } // ParamLogin 登录请求参数 type ParamLogin struct { Username string `json:"username" binding:"required"` Password string `json:"password" binding:"required"` } // ParamVoteData 投票数据 type ParamVoteData struct { // UserID 从请求中获取当前的用户 PostID string `json:"post_id" binding:"required"` // 贴子id Direction int8 `json:"direction,string" binding:"oneof=1 0 -1" ` // 赞成票(1)还是反对票(-1)取消投票(0) } // ParamPostList 获取帖子列表query string参数 type ParamPostList struct { CommunityID int64 `json:"community_id" form:"community_id"` // 可以为空 Page int64 `json:"page" form:"page" example:"1"` // 页码 Size int64 `json:"size" form:"size" example:"10"` // 每页数据量 Order string `json:"order" form:"order" example:"score"` // 排序依据 } ================================================ FILE: lesson80/bluebell/models/post.go ================================================ package models import "time" // 内存对齐概念 type Post struct { ID int64 `json:"id,string" db:"post_id"` // 帖子id AuthorID int64 `json:"author_id" db:"author_id"` // 作者id CommunityID int64 `json:"community_id" db:"community_id" binding:"required"` // 社区id Status int32 `json:"status" db:"status"` // 帖子状态 Title string `json:"title" db:"title" binding:"required"` // 帖子标题 Content string `json:"content" db:"content" binding:"required"` // 帖子内容 CreateTime time.Time `json:"create_time" db:"create_time"` // 帖子创建时间 } // ApiPostDetail 帖子详情接口的结构体 type ApiPostDetail struct { AuthorName string `json:"author_name"` // 作者 VoteNum int64 `json:"vote_num"` // 投票数 *Post // 嵌入帖子结构体 *CommunityDetail `json:"community"` // 嵌入社区信息 } ================================================ FILE: lesson80/bluebell/models/struct_test.go ================================================ package models import ( "fmt" "testing" "unsafe" ) // Go 内存对齐 type s1 struct { a int8 // 1 b string // 3 c int8 // 1 } type s2 struct { a int8 c int8 // 1 b string // 2 } func TestStruct(t *testing.T) { v1 := s1{ a: 1, b: "七米", c: 2, } v2 := s2{ a: 1, b: "七米", c: 2, } fmt.Println(unsafe.Sizeof(v1), unsafe.Sizeof(v2)) } ================================================ FILE: lesson80/bluebell/models/user.go ================================================ package models type User struct { UserID int64 `db:"user_id"` Username string `db:"username"` Password string `db:"password"` Token string } ================================================ FILE: lesson80/bluebell/nginx.conf ================================================ worker_processes 1; events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; server { listen 80; server_name localhost; access_log /var/log/bluebell-access.log; error_log /var/log/bluebell-error.log; location / { proxy_pass http://127.0.0.1:8084; proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } } } ================================================ FILE: lesson80/bluebell/nginx2.conf ================================================ worker_processes 1; events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; server { listen 80; server_name bluebell; access_log /var/log/bluebell-access.log; error_log /var/log/bluebell-error.log; # 静态文件请求 location ~ .*\.(gif|jpg|jpeg|png|js|css|eot|ttf|woff|svg|otf)$ { access_log off; expires 1d; root /data/app/bluebell/static; } # index.html页面请求 # 因为是单页面应用这里使用 try_files 处理一下,避免刷新页面时出现404的问题 location / { root /data/app/bluebell/templates; index index.html; try_files $uri $uri/ /index.html; } # API请求 location /api/v1 { proxy_pass http://127.0.0.1:8084; proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } } } ================================================ FILE: lesson80/bluebell/pkg/jwt/jwt.go ================================================ package jwt import ( "errors" "time" "github.com/spf13/viper" "github.com/dgrijalva/jwt-go" ) var mySecret = []byte("夏天夏天悄悄过去") // MyClaims 自定义声明结构体并内嵌jwt.StandardClaims // jwt包自带的jwt.StandardClaims只包含了官方字段 // 我们这里需要额外记录一个username字段,所以要自定义结构体 // 如果想要保存更多信息,都可以添加到这个结构体中 type MyClaims struct { UserID int64 `json:"user_id"` Username string `json:"username"` jwt.StandardClaims } // GenToken 生成JWT func GenToken(userID int64, username string) (string, error) { // 创建一个我们自己的声明的数据 c := MyClaims{ userID, "username", // 自定义字段 jwt.StandardClaims{ ExpiresAt: time.Now().Add( time.Duration(viper.GetInt("auth.jwt_expire")) * time.Hour).Unix(), // 过期时间 Issuer: "bluebell", // 签发人 }, } // 使用指定的签名方法创建签名对象 token := jwt.NewWithClaims(jwt.SigningMethodHS256, c) // 使用指定的secret签名并获得完整的编码后的字符串token return token.SignedString(mySecret) } // ParseToken 解析JWT func ParseToken(tokenString string) (*MyClaims, error) { // 解析token var mc = new(MyClaims) token, err := jwt.ParseWithClaims(tokenString, mc, func(token *jwt.Token) (i interface{}, err error) { return mySecret, nil }) if err != nil { return nil, err } if token.Valid { // 校验token return mc, nil } return nil, errors.New("invalid token") } ================================================ FILE: lesson80/bluebell/pkg/snowflake/snowflake.go ================================================ package snowflake import ( "time" sf "github.com/bwmarrin/snowflake" ) var node *sf.Node func Init(startTime string, machineID int64) (err error) { var st time.Time st, err = time.Parse("2006-01-02", startTime) if err != nil { return } sf.Epoch = st.UnixNano() / 1000000 node, err = sf.NewNode(machineID) return } func GenID() int64 { return node.Generate().Int64() } ================================================ FILE: lesson80/bluebell/router/route.go ================================================ package router import ( "bluebell/controller" "bluebell/logger" "bluebell/middlewares" "net/http" ginSwagger "github.com/swaggo/gin-swagger" "github.com/swaggo/gin-swagger/swaggerFiles" _ "bluebell/docs" "github.com/gin-contrib/pprof" "github.com/gin-gonic/gin" ) func SetupRouter(mode string) *gin.Engine { if mode == gin.ReleaseMode { gin.SetMode(gin.ReleaseMode) // gin设置成发布模式 } r := gin.New() //r.Use(logger.GinLogger(), logger.GinRecovery(true), middlewares.RateLimitMiddleware(2*time.Second, 1)) r.Use(logger.GinLogger(), logger.GinRecovery(true)) r.LoadHTMLFiles("./templates/index.html") r.Static("/static", "./static") r.GET("/", func(c *gin.Context) { c.HTML(http.StatusOK, "index.html", nil) }) r.GET("/ping", func(c *gin.Context) { c.String(http.StatusOK, "pong") }) r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) v1 := r.Group("/api/v1") // 注册 v1.POST("/signup", controller.SignUpHandler) // 登录 v1.POST("/login", controller.LoginHandler) // 根据时间或分数获取帖子列表 v1.GET("/posts2", controller.GetPostListHandler2) v1.GET("/posts", controller.GetPostListHandler) v1.GET("/community", controller.CommunityHandler) v1.GET("/community/:id", controller.CommunityDetailHandler) v1.GET("/post/:id", controller.GetPostDetailHandler) v1.Use(middlewares.JWTAuthMiddleware()) // 应用JWT认证中间件 { v1.POST("/post", controller.CreatePostHandler) // 投票 v1.POST("/vote", controller.PostVoteController) } pprof.Register(r) // 注册pprof相关路由 r.NoRoute(func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "msg": "404", }) }) return r } ================================================ FILE: lesson80/bluebell/setting/setting.go ================================================ package setting import ( "fmt" "github.com/fsnotify/fsnotify" "github.com/spf13/viper" ) var Conf = new(AppConfig) type AppConfig struct { Name string `mapstructure:"name"` Mode string `mapstructure:"mode"` Version string `mapstructure:"version"` StartTime string `mapstructure:"start_time"` MachineID int64 `mapstructure:"machine_id"` Port int `mapstructure:"port"` *LogConfig `mapstructure:"log"` *MySQLConfig `mapstructure:"mysql"` *RedisConfig `mapstructure:"redis"` } type MySQLConfig struct { Host string `mapstructure:"host"` User string `mapstructure:"user"` Password string `mapstructure:"password"` DB string `mapstructure:"dbname"` Port int `mapstructure:"port"` MaxOpenConns int `mapstructure:"max_open_conns"` MaxIdleConns int `mapstructure:"max_idle_conns"` } type RedisConfig struct { Host string `mapstructure:"host"` Password string `mapstructure:"password"` Port int `mapstructure:"port"` DB int `mapstructure:"db"` PoolSize int `mapstructure:"pool_size"` MinIdleConns int `mapstructure:"min_idle_conns"` } type LogConfig struct { Level string `mapstructure:"level"` Filename string `mapstructure:"filename"` MaxSize int `mapstructure:"max_size"` MaxAge int `mapstructure:"max_age"` MaxBackups int `mapstructure:"max_backups"` } func Init(filePath string) (err error) { // 方式1:直接指定配置文件路径(相对路径或者绝对路径) // 相对路径:相对执行的可执行文件的相对路径 //viper.SetConfigFile("./conf/config.yaml") // 绝对路径:系统中实际的文件路径 //viper.SetConfigFile("/Users/liwenzhou/Desktop/bluebell/conf/config.yaml") // 方式2:指定配置文件名和配置文件的位置,viper自行查找可用的配置文件 // 配置文件名不需要带后缀 // 配置文件位置可配置多个 //viper.SetConfigName("config") // 指定配置文件名(不带后缀) //viper.AddConfigPath(".") // 指定查找配置文件的路径(这里使用相对路径) //viper.AddConfigPath("./conf") // 指定查找配置文件的路径(这里使用相对路径) // 基本上是配合远程配置中心使用的,告诉viper当前的数据使用什么格式去解析 //viper.SetConfigType("json") viper.SetConfigFile(filePath) err = viper.ReadInConfig() // 读取配置信息 if err != nil { // 读取配置信息失败 fmt.Printf("viper.ReadInConfig failed, err:%v\n", err) return } // 把读取到的配置信息反序列化到 Conf 变量中 if err := viper.Unmarshal(Conf); err != nil { fmt.Printf("viper.Unmarshal failed, err:%v\n", err) } viper.WatchConfig() viper.OnConfigChange(func(in fsnotify.Event) { fmt.Println("配置文件修改了...") if err := viper.Unmarshal(Conf); err != nil { fmt.Printf("viper.Unmarshal failed, err:%v\n", err) } }) return } ================================================ FILE: lesson80/bluebell/sql/bluebell_community.sql ================================================ create table community ( id int auto_increment primary key, community_id int unsigned not null, community_name varchar(128) not null, introduction varchar(256) not null, create_time timestamp default CURRENT_TIMESTAMP not null, update_time timestamp default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP, constraint idx_community_id unique (community_id), constraint idx_community_name unique (community_name) ) collate = utf8mb4_general_ci; INSERT INTO bluebell.community (id, community_id, community_name, introduction, create_time, update_time) VALUES (1, 1, 'Go', 'Golang', '2016-11-01 08:10:10', '2016-11-01 08:10:10'); INSERT INTO bluebell.community (id, community_id, community_name, introduction, create_time, update_time) VALUES (2, 2, 'leetcode', '刷题刷题刷题', '2020-01-01 08:00:00', '2020-01-01 08:00:00'); INSERT INTO bluebell.community (id, community_id, community_name, introduction, create_time, update_time) VALUES (3, 3, 'CS:GO', 'Rush B。。。', '2018-08-07 08:30:00', '2018-08-07 08:30:00'); INSERT INTO bluebell.community (id, community_id, community_name, introduction, create_time, update_time) VALUES (4, 4, 'LOL', '欢迎来到英雄联盟!', '2016-01-01 08:00:00', '2016-01-01 08:00:00'); ================================================ FILE: lesson80/bluebell/sql/bluebell_post.sql ================================================ create table post ( id bigint auto_increment primary key, post_id bigint not null comment '帖子id', title varchar(128) not null comment '标题', content varchar(8192) not null comment '内容', author_id bigint not null comment '作者的用户id', community_id bigint not null comment '所属社区', status tinyint default 1 not null comment '帖子状态', create_time timestamp default CURRENT_TIMESTAMP null comment '创建时间', update_time timestamp default CURRENT_TIMESTAMP null on update CURRENT_TIMESTAMP comment '更新时间', constraint idx_post_id unique (post_id) ) collate = utf8mb4_general_ci; create index idx_author_id on post (author_id); create index idx_community_id on post (community_id); INSERT INTO bluebell.post (id, post_id, title, content, author_id, community_id, status, create_time, update_time) VALUES (1, 14283784123846656, '学习使我快乐', '只有学习才能变得更强', 28018727488323585, 1, 1, '2020-08-09 09:58:39', '2020-08-09 09:58:39'); INSERT INTO bluebell.post (id, post_id, title, content, author_id, community_id, status, create_time, update_time) VALUES (2, 14373128436191232, 'CSGO开箱子好上瘾', '花了钱不出金,我好气啊', 28018727488323585, 2, 1, '2020-08-09 15:53:40', '2020-08-09 15:53:40'); INSERT INTO bluebell.post (id, post_id, title, content, author_id, community_id, status, create_time, update_time) VALUES (3, 14373246019309568, 'IG牛逼', '打得好啊。。。', 28018727488323585, 3, 1, '2020-08-09 15:54:08', '2020-08-09 15:54:08'); INSERT INTO bluebell.post (id, post_id, title, content, author_id, community_id, status, create_time, update_time) VALUES (4, 19432670719119360, '投票功能真好玩', '12345', 28018727488323585, 2, 1, '2020-08-23 14:58:29', '2020-08-23 14:58:29'); INSERT INTO bluebell.post (id, post_id, title, content, author_id, community_id, status, create_time, update_time) VALUES (5, 19433711036534784, '投票功能真好玩2', '12345', 28018727488323585, 2, 1, '2020-08-23 15:02:37', '2020-08-23 15:02:37'); INSERT INTO bluebell.post (id, post_id, title, content, author_id, community_id, status, create_time, update_time) VALUES (6, 19434165682311168, '投票功能真好玩2', '12345', 28018727488323585, 2, 1, '2020-08-23 15:04:26', '2020-08-23 15:04:26'); INSERT INTO bluebell.post (id, post_id, title, content, author_id, community_id, status, create_time, update_time) VALUES (7, 21810561880690688, '看图说话', '4321', 28018727488323585, 2, 1, '2020-08-30 04:27:23', '2020-08-30 04:27:23'); INSERT INTO bluebell.post (id, post_id, title, content, author_id, community_id, status, create_time, update_time) VALUES (8, 21810685746876416, '永远不要高估自己', '做个普通人也挺难', 28018727488323585, 3, 1, '2020-08-30 04:27:52', '2020-08-30 04:27:52'); INSERT INTO bluebell.post (id, post_id, title, content, author_id, community_id, status, create_time, update_time) VALUES (9, 21810865955147776, '你知道泛型是什么吗?', '不知道泛型是什么却一直在问泛型什么时候出', 28018727488323585, 1, 1, '2020-08-30 04:28:35', '2020-08-30 04:28:35'); INSERT INTO bluebell.post (id, post_id, title, content, author_id, community_id, status, create_time, update_time) VALUES (10, 21810938202034176, '国庆假期哪里玩?', '走遍四海,还是威海。', 28018727488323585, 1, 1, '2020-08-30 04:28:52', '2020-08-30 04:28:52'); INSERT INTO bluebell.post (id, post_id, title, content, author_id, community_id, status, create_time, update_time) VALUES (11, 1, 'test', 'just for test', 1, 1, 1, '2020-09-12 14:03:18', '2020-09-12 14:03:18'); INSERT INTO bluebell.post (id, post_id, title, content, author_id, community_id, status, create_time, update_time) VALUES (12, 92636388033302528, 'test', 'just a test', 1, 1, 1, '2020-09-12 15:03:56', '2020-09-12 15:03:56'); INSERT INTO bluebell.post (id, post_id, title, content, author_id, community_id, status, create_time, update_time) VALUES (13, 92636388142354432, 'test', 'just a test', 1, 1, 1, '2020-09-12 15:03:56', '2020-09-12 15:03:56'); INSERT INTO bluebell.post (id, post_id, title, content, author_id, community_id, status, create_time, update_time) VALUES (15, 123, 'test', 'just a test', 1, 1, 1, '2020-09-13 03:31:50', '2020-09-13 03:31:50'); INSERT INTO bluebell.post (id, post_id, title, content, author_id, community_id, status, create_time, update_time) VALUES (16, 10, 'test', 'just a test', 123, 1, 1, '2020-09-13 04:12:44', '2020-09-13 04:12:44'); ================================================ FILE: lesson80/bluebell/sql/bluebell_user.sql ================================================ create table user ( id bigint auto_increment primary key, user_id bigint not null, username varchar(64) not null, password varchar(64) not null, email varchar(64) null, gender tinyint default 0 not null, create_time timestamp default CURRENT_TIMESTAMP null, update_time timestamp default CURRENT_TIMESTAMP null on update CURRENT_TIMESTAMP, constraint idx_user_id unique (user_id), constraint idx_username unique (username) ) collate = utf8mb4_general_ci; INSERT INTO bluebell.user (id, user_id, username, password, email, gender, create_time, update_time) VALUES (1, 28018727488323585, 'q1mi', '313233343536639a9119599647d841b1bef6ce5ea293', null, 0, '2020-07-12 07:01:03', '2020-07-12 07:01:03'); INSERT INTO bluebell.user (id, user_id, username, password, email, gender, create_time, update_time) VALUES (2, 4183532125556736, '七米', '313233639a9119599647d841b1bef6ce5ea293', null, 0, '2020-07-12 13:03:51', '2020-07-12 13:03:51'); ================================================ FILE: lesson80/bluebell/sql/init.sql ================================================ -- create the databases CREATE DATABASE IF NOT EXISTS bluebell; ================================================ FILE: lesson80/bluebell/static/css/app.0afe9dae.css ================================================ .header[data-v-75d7d08c]{width:100%;height:48px;position:fixed;background:#fff;display:flex;display:-webkit-flex;align-items:center;top:0;z-index:1}.header .logo[data-v-75d7d08c]{margin-left:10px;height:32px;background:url(../../static/img/logo.da56125f.png) no-repeat;background-size:32px 32px;background-position:0;padding-left:35px;line-height:32px;flex-grow:0;margin-right:16px;cursor:pointer}.header .search[data-v-75d7d08c]{flex-grow:1;margin:0 auto;max-width:690px;position:relative;display:flex;display:-webkit-flex}.header .search .s-logo[data-v-75d7d08c]{width:18px;height:18px;background:url(../../static/img/search.8e85063d.png) no-repeat;background-size:cover;display:inline-block;position:absolute;top:50%;margin-top:-9px;left:15px}.header .search .s-input[data-v-75d7d08c]{flex-grow:1;-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#f6f7f8;border-radius:4px;border:1px solid #edeff1;box-shadow:none;color:#c1c1c1;display:block;height:36px;outline:none;padding:0 16px 0 40px;width:100%}.header .btns[data-v-75d7d08c]{flex-grow:0;margin-left:16px;margin-right:10px;display:flex;display:-webkit-flex;align-items:center}.header .btns .login-btn[data-v-75d7d08c]{border:1px solid transparent;border-radius:4px;box-sizing:border-box;text-align:center;letter-spacing:1px;text-decoration:none;font-size:12px;font-weight:700;letter-spacing:.5px;line-height:24px;text-transform:uppercase;padding:3px 16px;border-color:#0079d3;color:#0079d3;fill:#0079d3;display:inline-block;cursor:pointer}.header .btns .login-btn[data-v-75d7d08c]:first-child{margin-right:5px}.header .btns .login-btn[data-v-75d7d08c]:nth-child(2){margin-right:10px}.header .btns .user[data-v-75d7d08c]{width:auto;height:24px;background:url(../../static/img/avatar.7b0a9835.png) no-repeat;background-size:24px 24px;background-position:0;padding-left:28px;display:flex;display:-webkit-flex;align-items:center;cursor:pointer;padding:12px 12px 12px 28px}.header .btns .user[data-v-75d7d08c]:after{content:"";width:0;height:0;border-top:5px solid #878a8c;border-right:5px solid transparent;border-bottom:5px solid transparent;border-left:5px solid transparent;margin-top:5px;margin-left:10px}.header .btns .dropdown-content[data-v-75d7d08c]{display:none;position:absolute;background-color:#f9f9f9;min-width:160px;box-shadow:0 8px 16px 0 rgba(0,0,0,.2)}.header .btns .dropdown-content a[data-v-75d7d08c]{color:#000;padding:12px 16px;text-decoration:none;display:block;cursor:pointer}.header .btns .dropdown-content a[data-v-75d7d08c]:hover{background-color:#f1f1f1}.header .btns .user-box:hover .dropdown-content[data-v-75d7d08c]{display:block}@font-face{font-family:iconfont;src:url(data:application/vnd.ms-fontobject;base64,FAwAAGwLAAABAAIAAAAAAAIABQMAAAAAAAABAJABAAAAAExQAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAA/zftSwAAAAAAAAAAAAAAAAAAAAAAABAAaQBjAG8AbgBmAG8AbgB0AAAADgBSAGUAZwB1AGwAYQByAAAAFgBWAGUAcgBzAGkAbwBuACAAMQAuADAAAAAQAGkAYwBvAG4AZgBvAG4AdAAAAAAAAAEAAAALAIAAAwAwR1NVQrD+s+0AAAE4AAAAQk9TLzI8fEi5AAABfAAAAFZjbWFwlyE39gAAAfgAAAIKZ2x5ZhJIvHEAAAQYAAAEfGhlYWQZkfxxAAAA4AAAADZoaGVhCN8EiQAAALwAAAAkaG10eCYfAAAAAAHUAAAAJGxvY2EEZgVyAAAEBAAAABRtYXhwARgAXQAAARgAAAAgbmFtZT5U/n0AAAiUAAACbXBvc3Rl44HVAAALBAAAAGUAAQAAA4D/gABcBQAAAP//BQAAAQAAAAAAAAAAAAAAAAAAAAkAAQAAAAEAAEvtN/9fDzz1AAsEAAAAAADauVw6AAAAANq5XDoAAP+ABQADgAAAAAgAAgAAAAAAAAABAAAACQBRAAUAAAAAAAIAAAAKAAoAAAD/AAAAAAAAAAEAAAAKAB4ALAABREZMVAAIAAQAAAAAAAAAAQAAAAFsaWdhAAgAAAABAAAAAQAEAAQAAAABAAgAAQAGAAAAAQAAAAAAAQQ8AZAABQAIAokCzAAAAI8CiQLMAAAB6wAyAQgAAAIABQMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUGZFZABA5gbmiwOA/4AAXAOAAIAAAAABAAAAAAAABAAAAASqAAAEAAAABAAAAAR1AAAEAAAABAAAAAQAAAAFAAAAAAAABQAAAAMAAAAsAAAABAAAAZoAAQAAAAAAlAADAAEAAAAsAAMACgAAAZoABABoAAAAEgAQAAMAAuYG5gjmIuYu5kXmSeaA5ov//wAA5gbmCOYi5i7mReZJ5oDmi///AAAAAAAAAAAAAAAAAAAAAAABABIAEgASABIAEgASABIAEgAAAAEAAgAIAAQABgAHAAUAAwAAAQYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAcAAAAAAAAAAIAADmBgAA5gYAAAABAADmCAAA5ggAAAACAADmIgAA5iIAAAAIAADmLgAA5i4AAAAEAADmRQAA5kUAAAAGAADmSQAA5kkAAAAHAADmgAAA5oAAAAAFAADmiwAA5osAAAADAAAAAAAAADIAVgDYATABhgGuAdYCPgABAAD/gASrA4AAIQAAJSMRNCYrASIGFREjESMiBhURIyIGHQEhNS4BKwERLgErAQMvbRgSgBMYbasSGIASGQSrARgSewEYEqpVAwERGRgS/P8BKxgT/wAZEaurEhgB1hIYAAAAAQAA/4AD7gOAABMAAAUzPwEnNy8BIycHIw8BFwcfATMXAni5OYMyMoM5uXh4uTmDMjKDObl4KbBfmppfsFdXsF+aml+wVwAABQAA/4cD+QOAABkAJAAoACsAUAAAJSMiLgI/ATUBPgEzMhYfAR4BFRQGBwE0BgE3NCYjJyYiDwEXARcBJwE3JyUyFhURDgEHIS4BJxE+ATchHgEUBiMhDgEVERQWFyEyNjURNDYBMwcGCwsGBTMB6gsfEBYjESsRDQsL/g5gAhcIAwUsBg8BM0n+HlABZlD+WF9CAlgQFAE/LfzxLT8CAj8tAfkQFBQQ/g4QFBQQAwgQFBRbAgsUC74IAfgLCxEMMxAjFhEeDP4ABhYCUgcGCTMGBixQ/qhQAW1Y/fkWUOoUEP4kMkIBAUIyAw8yQgEFFiITARoR/PgRGgEWFgHcEBQAAAAEAAD/gARyA4AAFwAgACwANQAAASEOAQcRHgEXMxceATI2PwEzPgE3ES4BATIWFAYiJjQ2BTQ+AR4CDgEjIiYFIiY0NjIWFAYDlv1KX34DA35fw2AKHCAbCmDEX34DA3v+RiEsLEIsLP7fGiwuIgkTJhghLAI2ISwsQiwtA4ADfl/+Vl9+An0MDg4MfQJ+XwGqXn7+qixCLCxCLE0YJhMJIi0tGSwsLEIsLEEtAAAAAAEAAP+fA6ADYwAzAAABJicmJyYnLgIGFRYCBwYWFxY2Jy4BNz4BNz4BFxYXFgYHBhY3PgE3LgEnJgYHDgEHBiYCvgExKEIlKgkhIAkD2S87hJcTBQ83KRUZUhgEFw5tQRcYZQ4cFZLaBAFALwsTAgMTIgwPAVFvZGRWKSMHGBkEEbr+71Gf1RkDCwwmekA7XGQQBwxlhjh4SQsQAhPLdCuQRQ4EES9OJw4GAAABAAD/pgOLA4AAFQAABQYiJwEmNjczET4BMyEyFhcRMx4BBwI2Fz4X/sIWESSqARgSAQASGAGqJBEWPxsbAX4bJQEB1RIZGRL+KwElGwAAAAEAAP+AA4sDWgAVAAABJiIHAQYWFzMRHgEzITI2NxEzPgEnAjYXPhf+whYRJKoBGBIBABIYAaokERYDPxsb/oIbJQH+KxIZGRIB1QElGwAABQAA/4AFAAOAABAAGwAoADQAPQAAEyEeARcRDgEHIS4BJxE+ATcRMSERJQEOAS8BBzU3PgEfAQE+AR8BESEFHgEXDgEHLgEnPgEXMjY0JiIGFBZnBDIsOgEBOiz7ziw6AQE6LAQy/vf+yQ0lEeXKqQ0gD94BPRAsEOb7zgEPPlECAlE+PVECAlE9EhcXIxgYA4ABNin8wCk2AQE2KQNAKTYB/GABG/3+mQ4ECpBYeEEJAQiMAW4PAQ7cAZxiAUw5OksCAks6OUysFiEWFiEWAAAAEgDeAAEAAAAAAAAAFQAAAAEAAAAAAAEACAAVAAEAAAAAAAIABwAdAAEAAAAAAAMACAAkAAEAAAAAAAQACAAsAAEAAAAAAAUACwA0AAEAAAAAAAYACAA/AAEAAAAAAAoAKwBHAAEAAAAAAAsAEwByAAMAAQQJAAAAKgCFAAMAAQQJAAEAEACvAAMAAQQJAAIADgC/AAMAAQQJAAMAEADNAAMAAQQJAAQAEADdAAMAAQQJAAUAFgDtAAMAAQQJAAYAEAEDAAMAAQQJAAoAVgETAAMAAQQJAAsAJgFpCkNyZWF0ZWQgYnkgaWNvbmZvbnQKaWNvbmZvbnRSZWd1bGFyaWNvbmZvbnRpY29uZm9udFZlcnNpb24gMS4waWNvbmZvbnRHZW5lcmF0ZWQgYnkgc3ZnMnR0ZiBmcm9tIEZvbnRlbGxvIHByb2plY3QuaHR0cDovL2ZvbnRlbGxvLmNvbQAKAEMAcgBlAGEAdABlAGQAIABiAHkAIABpAGMAbwBuAGYAbwBuAHQACgBpAGMAbwBuAGYAbwBuAHQAUgBlAGcAdQBsAGEAcgBpAGMAbwBuAGYAbwBuAHQAaQBjAG8AbgBmAG8AbgB0AFYAZQByAHMAaQBvAG4AIAAxAC4AMABpAGMAbwBuAGYAbwBuAHQARwBlAG4AZQByAGEAdABlAGQAIABiAHkAIABzAHYAZwAyAHQAdABmACAAZgByAG8AbQAgAEYAbwBuAHQAZQBsAGwAbwAgAHAAcgBvAGoAZQBjAHQALgBoAHQAdABwADoALwAvAGYAbwBuAHQAZQBsAGwAbwAuAGMAbwBtAAAAAAIAAAAAAAAACgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACQECAQMBBAEFAQYBBwEIAQkBCgADdG9wCnBvbHlnb25yZWQEZWRpdAdjb21tZW50A2hvdARkb3duAnVwBWltYWdlAAAAAAA=);src:url(data:application/vnd.ms-fontobject;base64,FAwAAGwLAAABAAIAAAAAAAIABQMAAAAAAAABAJABAAAAAExQAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAA/zftSwAAAAAAAAAAAAAAAAAAAAAAABAAaQBjAG8AbgBmAG8AbgB0AAAADgBSAGUAZwB1AGwAYQByAAAAFgBWAGUAcgBzAGkAbwBuACAAMQAuADAAAAAQAGkAYwBvAG4AZgBvAG4AdAAAAAAAAAEAAAALAIAAAwAwR1NVQrD+s+0AAAE4AAAAQk9TLzI8fEi5AAABfAAAAFZjbWFwlyE39gAAAfgAAAIKZ2x5ZhJIvHEAAAQYAAAEfGhlYWQZkfxxAAAA4AAAADZoaGVhCN8EiQAAALwAAAAkaG10eCYfAAAAAAHUAAAAJGxvY2EEZgVyAAAEBAAAABRtYXhwARgAXQAAARgAAAAgbmFtZT5U/n0AAAiUAAACbXBvc3Rl44HVAAALBAAAAGUAAQAAA4D/gABcBQAAAP//BQAAAQAAAAAAAAAAAAAAAAAAAAkAAQAAAAEAAEvtN/9fDzz1AAsEAAAAAADauVw6AAAAANq5XDoAAP+ABQADgAAAAAgAAgAAAAAAAAABAAAACQBRAAUAAAAAAAIAAAAKAAoAAAD/AAAAAAAAAAEAAAAKAB4ALAABREZMVAAIAAQAAAAAAAAAAQAAAAFsaWdhAAgAAAABAAAAAQAEAAQAAAABAAgAAQAGAAAAAQAAAAAAAQQ8AZAABQAIAokCzAAAAI8CiQLMAAAB6wAyAQgAAAIABQMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUGZFZABA5gbmiwOA/4AAXAOAAIAAAAABAAAAAAAABAAAAASqAAAEAAAABAAAAAR1AAAEAAAABAAAAAQAAAAFAAAAAAAABQAAAAMAAAAsAAAABAAAAZoAAQAAAAAAlAADAAEAAAAsAAMACgAAAZoABABoAAAAEgAQAAMAAuYG5gjmIuYu5kXmSeaA5ov//wAA5gbmCOYi5i7mReZJ5oDmi///AAAAAAAAAAAAAAAAAAAAAAABABIAEgASABIAEgASABIAEgAAAAEAAgAIAAQABgAHAAUAAwAAAQYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAcAAAAAAAAAAIAADmBgAA5gYAAAABAADmCAAA5ggAAAACAADmIgAA5iIAAAAIAADmLgAA5i4AAAAEAADmRQAA5kUAAAAGAADmSQAA5kkAAAAHAADmgAAA5oAAAAAFAADmiwAA5osAAAADAAAAAAAAADIAVgDYATABhgGuAdYCPgABAAD/gASrA4AAIQAAJSMRNCYrASIGFREjESMiBhURIyIGHQEhNS4BKwERLgErAQMvbRgSgBMYbasSGIASGQSrARgSewEYEqpVAwERGRgS/P8BKxgT/wAZEaurEhgB1hIYAAAAAQAA/4AD7gOAABMAAAUzPwEnNy8BIycHIw8BFwcfATMXAni5OYMyMoM5uXh4uTmDMjKDObl4KbBfmppfsFdXsF+aml+wVwAABQAA/4cD+QOAABkAJAAoACsAUAAAJSMiLgI/ATUBPgEzMhYfAR4BFRQGBwE0BgE3NCYjJyYiDwEXARcBJwE3JyUyFhURDgEHIS4BJxE+ATchHgEUBiMhDgEVERQWFyEyNjURNDYBMwcGCwsGBTMB6gsfEBYjESsRDQsL/g5gAhcIAwUsBg8BM0n+HlABZlD+WF9CAlgQFAE/LfzxLT8CAj8tAfkQFBQQ/g4QFBQQAwgQFBRbAgsUC74IAfgLCxEMMxAjFhEeDP4ABhYCUgcGCTMGBixQ/qhQAW1Y/fkWUOoUEP4kMkIBAUIyAw8yQgEFFiITARoR/PgRGgEWFgHcEBQAAAAEAAD/gARyA4AAFwAgACwANQAAASEOAQcRHgEXMxceATI2PwEzPgE3ES4BATIWFAYiJjQ2BTQ+AR4CDgEjIiYFIiY0NjIWFAYDlv1KX34DA35fw2AKHCAbCmDEX34DA3v+RiEsLEIsLP7fGiwuIgkTJhghLAI2ISwsQiwtA4ADfl/+Vl9+An0MDg4MfQJ+XwGqXn7+qixCLCxCLE0YJhMJIi0tGSwsLEIsLEEtAAAAAAEAAP+fA6ADYwAzAAABJicmJyYnLgIGFRYCBwYWFxY2Jy4BNz4BNz4BFxYXFgYHBhY3PgE3LgEnJgYHDgEHBiYCvgExKEIlKgkhIAkD2S87hJcTBQ83KRUZUhgEFw5tQRcYZQ4cFZLaBAFALwsTAgMTIgwPAVFvZGRWKSMHGBkEEbr+71Gf1RkDCwwmekA7XGQQBwxlhjh4SQsQAhPLdCuQRQ4EES9OJw4GAAABAAD/pgOLA4AAFQAABQYiJwEmNjczET4BMyEyFhcRMx4BBwI2Fz4X/sIWESSqARgSAQASGAGqJBEWPxsbAX4bJQEB1RIZGRL+KwElGwAAAAEAAP+AA4sDWgAVAAABJiIHAQYWFzMRHgEzITI2NxEzPgEnAjYXPhf+whYRJKoBGBIBABIYAaokERYDPxsb/oIbJQH+KxIZGRIB1QElGwAABQAA/4AFAAOAABAAGwAoADQAPQAAEyEeARcRDgEHIS4BJxE+ATcRMSERJQEOAS8BBzU3PgEfAQE+AR8BESEFHgEXDgEHLgEnPgEXMjY0JiIGFBZnBDIsOgEBOiz7ziw6AQE6LAQy/vf+yQ0lEeXKqQ0gD94BPRAsEOb7zgEPPlECAlE+PVECAlE9EhcXIxgYA4ABNin8wCk2AQE2KQNAKTYB/GABG/3+mQ4ECpBYeEEJAQiMAW4PAQ7cAZxiAUw5OksCAks6OUysFiEWFiEWAAAAEgDeAAEAAAAAAAAAFQAAAAEAAAAAAAEACAAVAAEAAAAAAAIABwAdAAEAAAAAAAMACAAkAAEAAAAAAAQACAAsAAEAAAAAAAUACwA0AAEAAAAAAAYACAA/AAEAAAAAAAoAKwBHAAEAAAAAAAsAEwByAAMAAQQJAAAAKgCFAAMAAQQJAAEAEACvAAMAAQQJAAIADgC/AAMAAQQJAAMAEADNAAMAAQQJAAQAEADdAAMAAQQJAAUAFgDtAAMAAQQJAAYAEAEDAAMAAQQJAAoAVgETAAMAAQQJAAsAJgFpCkNyZWF0ZWQgYnkgaWNvbmZvbnQKaWNvbmZvbnRSZWd1bGFyaWNvbmZvbnRpY29uZm9udFZlcnNpb24gMS4waWNvbmZvbnRHZW5lcmF0ZWQgYnkgc3ZnMnR0ZiBmcm9tIEZvbnRlbGxvIHByb2plY3QuaHR0cDovL2ZvbnRlbGxvLmNvbQAKAEMAcgBlAGEAdABlAGQAIABiAHkAIABpAGMAbwBuAGYAbwBuAHQACgBpAGMAbwBuAGYAbwBuAHQAUgBlAGcAdQBsAGEAcgBpAGMAbwBuAGYAbwBuAHQAaQBjAG8AbgBmAG8AbgB0AFYAZQByAHMAaQBvAG4AIAAxAC4AMABpAGMAbwBuAGYAbwBuAHQARwBlAG4AZQByAGEAdABlAGQAIABiAHkAIABzAHYAZwAyAHQAdABmACAAZgByAG8AbQAgAEYAbwBuAHQAZQBsAGwAbwAgAHAAcgBvAGoAZQBjAHQALgBoAHQAdABwADoALwAvAGYAbwBuAHQAZQBsAGwAbwAuAGMAbwBtAAAAAAIAAAAAAAAACgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACQECAQMBBAEFAQYBBwEIAQkBCgADdG9wCnBvbHlnb25yZWQEZWRpdAdjb21tZW50A2hvdARkb3duAnVwBWltYWdlAAAAAAA=#iefix) format("embedded-opentype"),url("data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAAAXwAAsAAAAAC2wAAAWiAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHEIGVgCECgqIfIcWATYCJAMkCxQABCAFhG0HZRunCSMRdntwMpD9RYJ5wtTNaA/lYPNf05ILYQVbigOMr5PFFM7087iZ7+dDCEklBL/zmipy4tWZVixsDpsjVTs4E4G2E1cCgOf/vd23/TOmiQ10WCYBRpBJJ6zNH54B7zvXH7BR+ShHcvaTRztMu2L6shz9sZ/LsyYWir4+6cXs/Zlp+2m3tLyEXecISUSjaIRQIVIpsaEibk8x2y7uwPUAAaAtEwbNzzINUATYbggA2qZpM1AON0EED6FsZMCxlZbXQIhqLxLXAOBV7efRCzGhAAjCDuxGVY359ZD1OcmEeTcP6zAPfHFBADifBIABhAEQAHQFhs3YKIfB6OfGAs0AwGFhRFG/S/ERIV8x/nOT2w1eMjYP4XgZACGG4l8eSUQIAzSArB4K8bCjQoHPSckJ6IkUnEAAfaCNxgg4QQDMhwWyYDGcIALycIIQaALCsqGJj+EASAFaC0APMGaYIIIOVoGmNvbvVh/ZIkaA/VViD3GcQiFhPLF5By0y2GnaOAskjY8WQk6CFPUHWDAKDzB6vY7L6GidhtZotF9Fy2jraKPV2q/UoybbDGM67J89NQR8r6ze4GxRGqx0jtUoK9PWa5t0zXrdQ6atQIw649gDcz6iiflET8tWfpb2MFd2AX6rXGJyIGxwdHg5nRrC5Sr23pYp04xSz8zP6/HcXL3pOH/MeMJwcpNh59H9x4e2HaIdlgduGt/AY3M6fv4TGdYjdjO4FjROl3bKprgxF7t3Xm+bkR+adjs15qnAQh53eE5nnVQNOtPM1gdmjSGN9p2JDB5wMJ6mrWqC2b89qMNuDKqf3R7PEH1bA57EsRgzB3eGdEj3315KDt6K4w7cWSYauZtIIineUm/eF6DToPwT2ibbQfnBMbf5vGvlqDPvwpzPxLzW5DDYjbP81pgftQFJWjle2sKhB0IdXIsUeO4FiYMVUQvXMYf17NASdDhp51Mbovu4SlEZgcMtOiL1fCBUa0TkmL22JOY49dUf9q9ZxQpm3PuMQrLd8ZObbOSK6ifJIOEerbcwtKjyI31lZcP9lfoZcoM8W13vKzaWfRdlZJ/yPiUHJd4S4/5//RrvE8P+/GOyhJIk/0ix8Yp32VhIruMPbCC6uogNdlF1WGcDOruxc+D4iu0DQuTcBhQ0tr6967B25dEcftyKMpVlz4LkpbBVqpVH40PvnzH7GhyvghhVF1iXrS164znRn09LiMMLbERVyvDTTqvKp+SbhqC2SmP1WmVdZuM799dnPdukbKkvevtk9uvcW1fwY3TmqxF5233ZCK5ccfwziM+4F39NetcaQZTdhVo54FqRyx4lWANPdZmf3XaP8TG7LFQWZdmlPeQoxCym/k1ovJ4r/vTVy/rW2axLP37xnintgf93/ffZO4MxXptvVZWq1FUlt09LDrXKumiag2tY2eD7xMon4BIBALh5/D0eCZ7Hf4lG8CiuC09jk9PMg1jzL9gOUj/1N5b2U8wvY8Hbh9fVlHLzQkieBQqxQRG+86sJAcDtFl4MzCWuR/gG0CgmT0u3M+gyRMSqbdpizTSfAfimxznvEyFP3A2AUOQWEESWAkbxowgYYSDEiAMSJRNooQpPZkg9ChgqMQCEeFYAhLUTCF5OAsa6ThEw3gMhhe+AxAYDTYek52Qc9hpzemPsBEfo72AGikr0yUnv/UZ99h3bDZrwH3k2OSizwl37wohcxAHzRVciChRTgE/hZug9QWKyOEg2iaQ6z1XQM2UDhcXJG2MnOIL+DsxAUSnz5eT5/m/UZ99xBOlT+R95NmsHpUyRgPJljYlImzI8X3RFqKiAOndTAJ8URM/LBFLwVhYHyUwZiqmW0zSV1JpVT4R5vQIAGnsQcQiEkQAJEYlEiEJiRMP6oHRK5O+aIuO4EYNG9n166YBR1hPJZqRrXJ3T1oRO42IBAAA=") format("woff2"),url(data:font/woff;base64,d09GRgABAAAAAAfwAAsAAAAAC2wAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABHU1VCAAABCAAAADMAAABCsP6z7U9TLzIAAAE8AAAARAAAAFY8fEi5Y21hcAAAAYAAAACQAAACCpchN/ZnbHlmAAACEAAAA6UAAAR8Eki8cWhlYWQAAAW4AAAALwAAADYZkfxxaGhlYQAABegAAAAeAAAAJAjfBIlobXR4AAAGCAAAABkAAAAkJh8AAGxvY2EAAAYkAAAAFAAAABQEZgVybWF4cAAABjgAAAAfAAAAIAEYAF1uYW1lAAAGWAAAAUUAAAJtPlT+fXBvc3QAAAegAAAATQAAAGVl44HVeJxjYGRgYOBikGPQYWB0cfMJYeBgYGGAAJAMY05meiJQDMoDyrGAaQ4gZoOIAgCKIwNPAHicY2BksWGcwMDKwMHUyXSGgYGhH0IzvmYwYuRgYGBiYGVmwAoC0lxTGByesT3rZm7438AQw9zA0AAUZgTJAQD7aAyweJzlkTsOwjAQRJ/jYCxEgThCapRbpMhNKDlC+rRUHHCuEXa9NHxuwFjP0o681moH2AHZuBg9pAcJ193c1PzMofk9V6vPnMzpVFQ1aNSkWYvWbYNf3oeS9b8f9zqq/V7Y2zQ2Sypfff+nY7tvr6r6dgNPRTWwzaEhaG/GwJPUFOB9c2AbRkvgyWsNyE++qCzQeJx1UkFsG1UQ/fO//TfOOuv9Xnt3HRcnuxvvNnhtq7vOmqSK3VgCqbQgpxJtBAhHkMIpICEOEVUtiyI4lEPFAUSFVE4IEg69tocKARdA6i1IoApOcOgBxIlGTTbMuhVwQf/vzM5o5s2fmUeAkMNRaoeNiEVI1REttwY2LwpHOGNl80fAWmhADUQiWHPTzI80c3Mnb47ypdQOmPkL+G0/w0CUzPz+IdRM7ZCUxA5GwA95kxAyrsF+xxoaIemwC167CY4nOTkwpBkIDbp1Y/FSEFxavLH179/89cHVq4Pr58491JiKOO+yPcQpkTlylNRIP3mz3aBdWIAOhIE+AxUoFrgELQ7tlut4ro1F8HjQ9qqBXhQKSFYDPNGBtlWBAncsBYqioBtWEC2IVgShxGWZp0O4K8+ouiNqYkqWY2WdGhMs7fMchCfjSh9e7sdrgx5dUwvQre//We9S2q3DnlooqLGSSDaB8jkqF+RbE3BPlkU2VB1dVLIx4To9I/FMyLnfjz/rw+bawZ7ev4uZc0EPoBewHOq0bmswLfbviWnQdbijFnCWqfG+XscZGGSW+GQBx4sdSKICRmhUIIi6EGJvuC0I9AK33VaUbnWgQhVwbDed2ImffXDw5GDI2HDw9frkkdny5Po3iXkhfsLy/Z7vx79M+w07o7mm5dNo7KuzEYbHZwdDejGrKNmLdDiA7ReG8XaS0POfMl0tY9frJX+MsFInD3d/jX3CXiIhGq6XnAblRZ1KXDf0yGtAu5NcAy2OvsTC/bhcwq64S2/BsaO96qMZazbDfmwef/tDLZ1rzxdLZ8yUoWyuGOZ55Ujx/Z9ScKIpa5RpdjYHq69tbJyddySzlBI34z9Wr+2WmJx13zxx/PkNVcqef+exrZOySrXv36hdeVxJiebTnsIfvPVTdhlnW0SycdsDN2qHyJTQCnRDhBWQaGR0jPgrXcxtI+uBIMe354TeLZdhWK4C7OZLpXxcg2r5H95fZs8meODaEmDLIW4K8aK2wDV5/4PHEDB+CwHjWgIIu2PAhP+jNMH3qaSM/G+RZUI0ZLHxX1aLY5aoggJNkBZwmDMAiRBWGuMwDKNw2EHUcm1e0F9JBf4SwJJ///YDnQriv+Jvp6ri1+8+n5rN/QzLqq/+dv825DqrlK52lhO5nDcMxzTZCKL5/S/nI0DNTqDeX4fyQfyRkpq8sra1koGJ9+DVHCh34OMX4fTi0ilKTy0tnv5Ct3S8fwMKbd/oAAAAeJxjYGRgYABi77eS5+P5bb4ycLMwgMCtnTFWCPp/AysDcwOQy8HABBIFACtoCjQAeJxjYGRgYG7438AQw8rAwPD/P5AEiqAATgByEgRyAAB4nGNhYGBgWQXEDFBcisQGYlYgBgAgPQFFAAAAAAAAAAAyAFYA2AEwAYYBrgHWAj54nGNgZGBg4GQIZGBlAAEmIOYCQgaG/2A+AwARmQF2AHicZY9NTsMwEIVf+gekEqqoYIfkBWIBKP0Rq25YVGr3XXTfpk6bKokjx63UA3AejsAJOALcgDvwSCebNpbH37x5Y08A3OAHHo7fLfeRPVwyO3INF7gXrlN/EG6QX4SbaONVuEX9TdjHM6bCbXRheYPXuGL2hHdhDx18CNdwjU/hOvUv4Qb5W7iJO/wKt9Dx6sI+5l5XuI1HL/bHVi+cXqnlQcWhySKTOb+CmV7vkoWt0uqca1vEJlODoF9JU51pW91T7NdD5yIVWZOqCas6SYzKrdnq0AUb5/JRrxeJHoQm5Vhj/rbGAo5xBYUlDowxQhhkiMro6DtVZvSvsUPCXntWPc3ndFsU1P9zhQEC9M9cU7qy0nk6T4E9XxtSdXQrbsuelDSRXs1JErJCXta2VELqATZlV44RelzRiT8oZ0j/AAlabsgAAAB4nG3BSw6AIAwFwD4+ItzSSAMkQompMd7ehVtnyNAn0b8IAwsHjwUBKyISWZWZphxPkXFydpybhl1656G2iros9zDX9K1vhYletHgQlQAAAA==) format("woff"),url(data:font/ttf;base64,AAEAAAALAIAAAwAwR1NVQrD+s+0AAAE4AAAAQk9TLzI8fEi5AAABfAAAAFZjbWFwlyE39gAAAfgAAAIKZ2x5ZhJIvHEAAAQYAAAEfGhlYWQZkfxxAAAA4AAAADZoaGVhCN8EiQAAALwAAAAkaG10eCYfAAAAAAHUAAAAJGxvY2EEZgVyAAAEBAAAABRtYXhwARgAXQAAARgAAAAgbmFtZT5U/n0AAAiUAAACbXBvc3Rl44HVAAALBAAAAGUAAQAAA4D/gABcBQAAAP//BQAAAQAAAAAAAAAAAAAAAAAAAAkAAQAAAAEAAEvtGc9fDzz1AAsEAAAAAADauVw6AAAAANq5XDoAAP+ABQADgAAAAAgAAgAAAAAAAAABAAAACQBRAAUAAAAAAAIAAAAKAAoAAAD/AAAAAAAAAAEAAAAKAB4ALAABREZMVAAIAAQAAAAAAAAAAQAAAAFsaWdhAAgAAAABAAAAAQAEAAQAAAABAAgAAQAGAAAAAQAAAAAAAQQ8AZAABQAIAokCzAAAAI8CiQLMAAAB6wAyAQgAAAIABQMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUGZFZABA5gbmiwOA/4AAXAOAAIAAAAABAAAAAAAABAAAAASqAAAEAAAABAAAAAR1AAAEAAAABAAAAAQAAAAFAAAAAAAABQAAAAMAAAAsAAAABAAAAZoAAQAAAAAAlAADAAEAAAAsAAMACgAAAZoABABoAAAAEgAQAAMAAuYG5gjmIuYu5kXmSeaA5ov//wAA5gbmCOYi5i7mReZJ5oDmi///AAAAAAAAAAAAAAAAAAAAAAABABIAEgASABIAEgASABIAEgAAAAEAAgAIAAQABgAHAAUAAwAAAQYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAcAAAAAAAAAAIAADmBgAA5gYAAAABAADmCAAA5ggAAAACAADmIgAA5iIAAAAIAADmLgAA5i4AAAAEAADmRQAA5kUAAAAGAADmSQAA5kkAAAAHAADmgAAA5oAAAAAFAADmiwAA5osAAAADAAAAAAAAADIAVgDYATABhgGuAdYCPgABAAD/gASrA4AAIQAAJSMRNCYrASIGFREjESMiBhURIyIGHQEhNS4BKwERLgErAQMvbRgSgBMYbasSGIASGQSrARgSewEYEqpVAwERGRgS/P8BKxgT/wAZEaurEhgB1hIYAAAAAQAA/4AD7gOAABMAAAUzPwEnNy8BIycHIw8BFwcfATMXAni5OYMyMoM5uXh4uTmDMjKDObl4KbBfmppfsFdXsF+aml+wVwAABQAA/4cD+QOAABkAJAAoACsAUAAAJSMiLgI/ATUBPgEzMhYfAR4BFRQGBwE0BgE3NCYjJyYiDwEXARcBJwE3JyUyFhURDgEHIS4BJxE+ATchHgEUBiMhDgEVERQWFyEyNjURNDYBMwcGCwsGBTMB6gsfEBYjESsRDQsL/g5gAhcIAwUsBg8BM0n+HlABZlD+WF9CAlgQFAE/LfzxLT8CAj8tAfkQFBQQ/g4QFBQQAwgQFBRbAgsUC74IAfgLCxEMMxAjFhEeDP4ABhYCUgcGCTMGBixQ/qhQAW1Y/fkWUOoUEP4kMkIBAUIyAw8yQgEFFiITARoR/PgRGgEWFgHcEBQAAAAEAAD/gARyA4AAFwAgACwANQAAASEOAQcRHgEXMxceATI2PwEzPgE3ES4BATIWFAYiJjQ2BTQ+AR4CDgEjIiYFIiY0NjIWFAYDlv1KX34DA35fw2AKHCAbCmDEX34DA3v+RiEsLEIsLP7fGiwuIgkTJhghLAI2ISwsQiwtA4ADfl/+Vl9+An0MDg4MfQJ+XwGqXn7+qixCLCxCLE0YJhMJIi0tGSwsLEIsLEEtAAAAAAEAAP+fA6ADYwAzAAABJicmJyYnLgIGFRYCBwYWFxY2Jy4BNz4BNz4BFxYXFgYHBhY3PgE3LgEnJgYHDgEHBiYCvgExKEIlKgkhIAkD2S87hJcTBQ83KRUZUhgEFw5tQRcYZQ4cFZLaBAFALwsTAgMTIgwPAVFvZGRWKSMHGBkEEbr+71Gf1RkDCwwmekA7XGQQBwxlhjh4SQsQAhPLdCuQRQ4EES9OJw4GAAABAAD/pgOLA4AAFQAABQYiJwEmNjczET4BMyEyFhcRMx4BBwI2Fz4X/sIWESSqARgSAQASGAGqJBEWPxsbAX4bJQEB1RIZGRL+KwElGwAAAAEAAP+AA4sDWgAVAAABJiIHAQYWFzMRHgEzITI2NxEzPgEnAjYXPhf+whYRJKoBGBIBABIYAaokERYDPxsb/oIbJQH+KxIZGRIB1QElGwAABQAA/4AFAAOAABAAGwAoADQAPQAAEyEeARcRDgEHIS4BJxE+ATcRMSERJQEOAS8BBzU3PgEfAQE+AR8BESEFHgEXDgEHLgEnPgEXMjY0JiIGFBZnBDIsOgEBOiz7ziw6AQE6LAQy/vf+yQ0lEeXKqQ0gD94BPRAsEOb7zgEPPlECAlE+PVECAlE9EhcXIxgYA4ABNin8wCk2AQE2KQNAKTYB/GABG/3+mQ4ECpBYeEEJAQiMAW4PAQ7cAZxiAUw5OksCAks6OUysFiEWFiEWAAAAEgDeAAEAAAAAAAAAFQAAAAEAAAAAAAEACAAVAAEAAAAAAAIABwAdAAEAAAAAAAMACAAkAAEAAAAAAAQACAAsAAEAAAAAAAUACwA0AAEAAAAAAAYACAA/AAEAAAAAAAoAKwBHAAEAAAAAAAsAEwByAAMAAQQJAAAAKgCFAAMAAQQJAAEAEACvAAMAAQQJAAIADgC/AAMAAQQJAAMAEADNAAMAAQQJAAQAEADdAAMAAQQJAAUAFgDtAAMAAQQJAAYAEAEDAAMAAQQJAAoAVgETAAMAAQQJAAsAJgFpCkNyZWF0ZWQgYnkgaWNvbmZvbnQKaWNvbmZvbnRSZWd1bGFyaWNvbmZvbnRpY29uZm9udFZlcnNpb24gMS4waWNvbmZvbnRHZW5lcmF0ZWQgYnkgc3ZnMnR0ZiBmcm9tIEZvbnRlbGxvIHByb2plY3QuaHR0cDovL2ZvbnRlbGxvLmNvbQAKAEMAcgBlAGEAdABlAGQAIABiAHkAIABpAGMAbwBuAGYAbwBuAHQACgBpAGMAbwBuAGYAbwBuAHQAUgBlAGcAdQBsAGEAcgBpAGMAbwBuAGYAbwBuAHQAaQBjAG8AbgBmAG8AbgB0AFYAZQByAHMAaQBvAG4AIAAxAC4AMABpAGMAbwBuAGYAbwBuAHQARwBlAG4AZQByAGEAdABlAGQAIABiAHkAIABzAHYAZwAyAHQAdABmACAAZgByAG8AbQAgAEYAbwBuAHQAZQBsAGwAbwAgAHAAcgBvAGoAZQBjAHQALgBoAHQAdABwADoALwAvAGYAbwBuAHQAZQBsAGwAbwAuAGMAbwBtAAAAAAIAAAAAAAAACgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACQECAQMBBAEFAQYBBwEIAQkBCgADdG9wCnBvbHlnb25yZWQEZWRpdAdjb21tZW50A2hvdARkb3duAnVwBWltYWdlAAAAAAA=) format("truetype"),url(../../static/img/iconfont.cdbe38a0.svg#iconfont) format("svg")}.iconfont{font-family:iconfont!important;font-size:16px;font-style:normal;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.icon-top:before{content:"\e606"}.icon-polygonred:before{content:"\e608"}.icon-edit:before{content:"\e68b"}.icon-comment:before{content:"\e62e"}.icon-hot:before{content:"\e680"}.icon-down:before{content:"\e645"}.icon-up:before{content:"\e649"}.icon-image:before{content:"\e622"}body,html{width:100%;height:100%;font-family:IBMPlexSans,Arial,sans-serif;background:#eee}a,abbr,acronym,address,applet,article,aside,audio,b,big,blockquote,body,button,canvas,caption,center,cite,code,dd,del,details,dfn,div,dl,dt,em,embed,fieldset,figcaption,figure,footer,form,h1,h2,h3,h4,h5,h6,header,hgroup,html,i,iframe,img,input,ins,kbd,label,legend,li,mark,menu,nav,object,ol,output,p,pre,q,ruby,s,samp,section,small,span,strike,strong,sub,summary,sup,table,tbody,td,tfoot,th,thead,time,tr,tt,u,ul,var,video{margin:0;padding:0;border:0;outline:0}a .page,abbr .page,acronym .page,address .page,applet .page,article .page,aside .page,audio .page,b .page,big .page,blockquote .page,body .page,button .page,canvas .page,caption .page,center .page,cite .page,code .page,dd .page,del .page,details .page,dfn .page,div .page,dl .page,dt .page,em .page,embed .page,fieldset .page,figcaption .page,figure .page,footer .page,form .page,h1 .page,h2 .page,h3 .page,h4 .page,h5 .page,h6 .page,header .page,hgroup .page,html .page,i .page,iframe .page,img .page,input .page,ins .page,kbd .page,label .page,legend .page,li .page,mark .page,menu .page,nav .page,object .page,ol .page,output .page,p .page,pre .page,q .page,ruby .page,s .page,samp .page,section .page,small .page,span .page,strike .page,strong .page,sub .page,summary .page,sup .page,table .page,tbody .page,td .page,tfoot .page,th .page,thead .page,time .page,tr .page,tt .page,u .page,ul .page,var .page,video .page{width:100%;height:auto}.content[data-v-2baf73fc]{max-width:100%;box-sizing:border-box;display:flex;flex-direction:row;justify-content:center;margin:48px auto 0;padding:20px 24px}.content .left[data-v-2baf73fc]{width:640px;padding-bottom:10px}.content .left .c-l-title[data-v-2baf73fc]{font-size:14px;font-weight:500;line-height:18px;color:#1a1a1b;text-transform:unset;padding-bottom:10px}.content .left .c-l-header[data-v-2baf73fc]{align-items:center;background-color:#fff;border:1px solid #ccc;border-radius:4px;box-sizing:border-box;display:flex;flex-flow:row nowrap;height:56px;justify-content:flex-start;margin-bottom:16px;padding:0 12px}.content .left .c-l-header .iconfont[data-v-2baf73fc]{margin-right:4px}.content .left .c-l-header .btn-iconfont[data-v-2baf73fc]{display:flex;display:-webkit-flex}.content .left .c-l-header .active[data-v-2baf73fc]{background:#f6f7f8;color:#0079d3;fill:#0079d3;border-radius:20px;height:32px;line-height:32px;margin-right:8px;padding:0 10px}.content .left .c-l-header .new[data-v-2baf73fc]{font-size:14px;margin-right:18px}.content .left .c-l-header .top[data-v-2baf73fc]{font-size:14px}.content .left .c-l-header .btn-publish[data-v-2baf73fc]{width:64px;height:32px;line-height:32px;background-color:#54b351;color:#fff;border:1px solid transparent;border-radius:4px;box-sizing:border-box;text-align:center;margin-left:auto;cursor:pointer}.content .left .c-l-header .sort[data-v-2baf73fc]{margin-left:300px;display:flex;color:#0079d3;display:-webkit-flex;align-items:center}.content .left .c-l-header .sort .sort-triangle[data-v-2baf73fc]{width:0;height:0;border-top:5px solid #0079d3;border-right:5px solid transparent;border-bottom:5px solid transparent;border-left:5px solid transparent;margin-top:5px;margin-left:10px}.content .left .c-l-list .c-l-item[data-v-2baf73fc]{list-style:none;border-radius:4px;padding-left:40px;cursor:pointer;border:1px solid #ccc;margin-bottom:10px;background-color:hsla(0,0%,100%,.8);color:#878a8c;position:relative}.content .left .c-l-list .c-l-item .post[data-v-2baf73fc]{align-items:center;box-sizing:border-box;display:flex;flex-direction:column;height:100%;left:0;padding:8px 4px 8px 0;position:absolute;top:0;width:40px;border-left:4px solid transparent;background:#f8f9fa}.content .left .c-l-list .c-l-item .post .iconfont[data-v-2baf73fc]{margin-right:0}.content .left .c-l-list .c-l-item .post .down[data-v-2baf73fc]{transform:scaleY(-1)}.content .left .c-l-list .c-l-item .post .text[data-v-2baf73fc]{color:#1a1a1b;font-size:12px;font-weight:700;line-height:16px;pointer-events:none;word-break:normal}.content .left .c-l-list .c-l-item .l-container[data-v-2baf73fc]{padding:15px}.content .left .c-l-list .c-l-item .l-container .con-title[data-v-2baf73fc]{color:#000;font-size:18px;font-weight:500;line-height:22px;text-decoration:none;word-break:break-word}.content .left .c-l-list .c-l-item .l-container .con-memo[data-v-2baf73fc]{margin-top:10px;margin-bottom:10px}.content .left .c-l-list .c-l-item .l-container .con-cover[data-v-2baf73fc]{height:512px;width:100%;background:url(https://timgsa.baidu.com/timg?di=7e9061211c23e3ed9f0c4375bb3822dc&image=&imgtype=0&quality=80&sec=1585999647247&size=b9999_10000&src=http%3A%2F%2Fi1.hdslb.com%2Fbfs%2Farchive%2F04d8cda08e170f4a58c18c45a93c539375c22162.jpg) no-repeat;background-size:cover;margin-top:10px;margin-bottom:10px}.content .left .c-l-list .c-l-item .l-container .user-btn[data-v-2baf73fc]{font-size:14px;display:flex;display:-webkit-flex}.content .left .c-l-list .c-l-item .l-container .user-btn .btn-item[data-v-2baf73fc]{display:flex;display:-webkit-flex;margin-right:10px}.content .left .c-l-list .c-l-item .l-container .user-btn .btn-item .iconfont[data-v-2baf73fc]{margin-right:4px}.content .right[data-v-2baf73fc]{width:312px;margin-left:24px;margin-top:28px}.content .right .communities[data-v-2baf73fc]{background-color:#fff;color:#1a1a1b;border:1px solid #ccc;border-radius:4px;overflow:visible;word-wrap:break-word;margin-bottom:20px}.content .right .communities .r-c-title[data-v-2baf73fc]{background-image:linear-gradient(0deg,rgba(0,0,0,.3),transparent);background-color:#0079d3;height:80px;width:100%;color:#fff;font-size:20px;line-height:120px;padding-left:10px;box-sizing:border-box;text-align:center}.content .right .communities .r-c-content .r-c-item[data-v-2baf73fc]{align-items:center;display:flex;display:-webkit-flex;height:48px;padding:0 12px;border-bottom:thin solid #edeff1;font-size:14px}.content .right .communities .r-c-content .r-c-item .index[data-v-2baf73fc]{width:20px;color:#1c1c1c;font-size:14px;font-weight:500;line-height:18px}.content .right .communities .r-c-content .r-c-item .icon[data-v-2baf73fc]{width:32px;height:32px;background-image:url(../../static/img/avatar.7b0a9835.png);background-repeat:no-repeat;background-size:cover;margin-right:20px}.content .right .communities .r-c-content .r-c-item[data-v-2baf73fc]:last-child{border-bottom:none}.content .right .communities .view-all[data-v-2baf73fc]{background-color:#0079d3;border:1px solid transparent;border-radius:4px;box-sizing:border-box;text-align:center;letter-spacing:1px;text-decoration:none;font-size:12px;font-weight:700;letter-spacing:.5px;line-height:24px;text-transform:uppercase;padding:3px 0;width:280px;color:#fff;margin:20px 0 20px 16px}.content .right .r-trending[data-v-2baf73fc]{padding-top:16px;width:312px;background-color:#fff;color:#1a1a1b;fill:#1a1a1b;border:1px solid #ccc;border-radius:4px;overflow:visible;word-wrap:break-word}.content .right .r-trending .r-t-title[data-v-2baf73fc]{font-size:10px;font-weight:700;letter-spacing:.5px;line-height:12px;text-transform:uppercase;background-color:#fff;border-radius:3px 3px 0 0;color:#1a1a1b;display:flex;fill:#1a1a1b;padding:0 12px 12px}.content .right .r-trending .rank[data-v-2baf73fc]{padding:12px}.content .right .r-trending .rank .r-t-cell[data-v-2baf73fc]{display:flex;display:-webkit-flex;align-items:center;justify-content:space-between;margin-bottom:16px}.content .right .r-trending .rank .r-t-cell .r-t-cell-info[data-v-2baf73fc]{display:flex}.content .right .r-trending .rank .r-t-cell .avatar[data-v-2baf73fc]{width:32px;height:32px;background:url(../../static/img/avatar.7b0a9835.png) no-repeat;background-size:cover;margin-right:10px}.content .right .r-trending .rank .r-t-cell .info[data-v-2baf73fc]{margin-right:10px}.content .right .r-trending .rank .r-t-cell .info .info-title[data-v-2baf73fc]{font-size:12px;font-weight:500;line-height:16px;text-overflow:ellipsis;width:144px}.content .right .r-trending .rank .r-t-cell .info .info-num[data-v-2baf73fc]{font-size:12px;font-weight:400;line-height:16px;padding-bottom:4px}.content .right .r-trending .rank .r-t-cell .join-btn[data-v-2baf73fc]{width:106px;height:32px;line-height:32px;background-color:#0079d3;color:#fff;border:1px solid transparent;border-radius:4px;box-sizing:border-box;text-align:center}.content[data-v-4466eba1]{max-width:100%;box-sizing:border-box;display:flex;flex-direction:row;justify-content:center;margin:0 auto;padding:20px 24px;margin-top:48px}.content .left[data-v-4466eba1]{flex-grow:1;max-width:740px;border-radius:4px;word-break:break-word;background:#fff;border:#edeff1;flex:1;margin:32px;margin-right:12px;padding-bottom:30px;position:relative}.content .left .container[data-v-4466eba1]{width:100%;height:auto;position:relative}.content .left .container .post[data-v-4466eba1]{align-items:center;box-sizing:border-box;display:flex;flex-direction:column;height:100%;left:0;padding:8px 4px 8px 0;position:absolute;top:0;width:40px;border-left:4px solid transparent}.content .left .container .post .text[data-v-4466eba1]{color:#1a1a1b;font-size:12px;font-weight:700;line-height:16px;pointer-events:none;word-break:normal}.content .left .container .l-container[data-v-4466eba1]{padding:15px;margin-left:40px}.content .left .container .l-container .con-title[data-v-4466eba1]{color:#000;font-size:18px;font-weight:500;line-height:22px;text-decoration:none;word-break:break-word}.content .left .container .l-container .con-info[data-v-4466eba1]{margin:25px 0;padding:15px 0;border-bottom:1px solid grey}.content .left .container .l-container .con-cover[data-v-4466eba1]{height:512px;width:100%;background:url(https://timgsa.baidu.com/timg?di=7e9061211c23e3ed9f0c4375bb3822dc&image=&imgtype=0&quality=80&sec=1585999647247&size=b9999_10000&src=http%3A%2F%2Fi1.hdslb.com%2Fbfs%2Farchive%2F04d8cda08e170f4a58c18c45a93c539375c22162.jpg) no-repeat;background-size:cover;margin-top:10px;margin-bottom:10px}.content .left .container .l-container .user-btn[data-v-4466eba1]{font-size:12px;display:flex;display:-webkit-flex}.content .left .container .l-container .user-btn .btn-item[data-v-4466eba1]{display:flex;display:-webkit-flex;align-items:center;margin-right:10px}.content .left .container .l-container .user-btn .btn-item .iconfont[data-v-4466eba1]{margin-right:4px}.content .left .comment[data-v-4466eba1]{width:100%;height:auto;position:relative}.content .left .comment .c-left .line[data-v-4466eba1]{border-right:2px solid #edeff1;height:100%;position:absolute;left:20px}.content .left .comment .c-left .c-arrow[data-v-4466eba1]{display:flex;display:-webkit-flex;position:absolute;z-index:2;flex-direction:column;left:12px;background:#fff;padding-bottom:5px}.content .left .comment .c-right[data-v-4466eba1]{margin-left:40px;padding-right:10px}.content .left .comment .c-right .c-user-info[data-v-4466eba1]{margin-bottom:10px}.content .left .comment .c-right .c-user-info .name[data-v-4466eba1]{color:#1c1c1c;font-size:12px;font-weight:400;line-height:16px}.content .left .comment .c-right .c-user-info .num[data-v-4466eba1]{padding-left:4px;font-size:12px;font-weight:400;line-height:16px;color:#7c7c7c}.content .left .comment .c-right .c-content[data-v-4466eba1]{font-family:Noto Sans,Arial,sans-serif;font-size:14px;font-weight:400;line-height:21px;word-break:break-word;color:#1a1a1b}.content .right[data-v-4466eba1]{flex-grow:0;width:312px;margin-top:32px}.content .right .topic-info[data-v-4466eba1]{width:100%;cursor:pointer;background-color:#fff;color:#1a1a1b;border:1px solid #ccc;border-radius:4px;overflow:visible;word-wrap:break-word;padding-bottom:30px}.content .right .topic-info .t-header[data-v-4466eba1]{width:100%;height:34px;background:#0079d3}.content .right .topic-info .t-info[data-v-4466eba1]{padding:0 12px;display:flex;display:-webkit-flex;width:100%;height:54px;align-items:center}.content .right .topic-info .t-info .avatar[data-v-4466eba1]{width:54px;height:54px;background:url(../../static/img/avatar.7b0a9835.png) no-repeat;background-size:cover;margin-right:10px}.content .right .topic-info .t-info .topic-name[data-v-4466eba1]{height:100%;line-height:54px;font-size:16px;font-weight:500}.content .right .topic-info .t-desc[data-v-4466eba1]{font-family:Noto Sans,Arial,sans-serif;font-size:14px;line-height:21px;font-weight:400;word-wrap:break-word;margin-bottom:8px;padding:0 12px}.content .right .topic-info .t-num[data-v-4466eba1]{padding:0 12px 20px 12px;display:flex;display:-webkit-flex;align-items:center;border-bottom:1px solid #edeff1}.content .right .topic-info .t-num .t-num-item[data-v-4466eba1]{list-style:none;display:flex;display:-webkit-flex;flex-direction:column;width:50%}.content .right .topic-info .t-num .t-num-item .number[data-v-4466eba1]{font-size:16px;font-weight:500;line-height:20px}.content .right .topic-info .t-num .t-num-item .unit[data-v-4466eba1]{font-size:12px;font-weight:500;line-height:16px;word-break:break-word}.content .right .topic-info .date[data-v-4466eba1]{font-family:Noto Sans,Arial,sans-serif;font-size:14px;font-weight:400;line-height:18px;margin-top:20px;padding:0 12px}.content .right .topic-info .topic-btn[data-v-4466eba1]{width:286px;height:34px;line-height:34px;color:#fff;margin:12px auto 0 auto;background:#003f6d;border-radius:4px;box-sizing:border-box;margin-left:13px}.content[data-v-6236a644]{max-width:100%;box-sizing:border-box;display:flex;flex-direction:row;justify-content:center;margin:0 auto;padding:20px 24px;margin-top:48px}.content .left[data-v-6236a644]{flex-grow:1;max-width:740px;word-break:break-word;flex:1;margin:32px;margin-right:12px;padding-bottom:30px;position:relative}.content .left .post-name[data-v-6236a644]{padding:4px;margin:16px 0;border-bottom:1px solid #edeff1;display:flex;justify-content:space-between}.content .left .post-name .p-btn[data-v-6236a644]{font-size:12px;font-weight:700;letter-spacing:.5px;line-height:24px;text-transform:uppercase;border:none;padding:0;margin-left:10px;color:#0079d3}.content .left .post-name .p-num[data-v-6236a644]{font-size:12px;font-weight:400;line-height:16px;background:#878a8c;border-radius:2px;color:#fff;margin-left:4px;padding:1px 3px}.content .left .post-type[data-v-6236a644]{box-sizing:border-box;width:300px;height:40px;border-radius:4px;transition:box-shadow .2s ease;box-shadow:0 0 0 0 #fff;border:1px solid #edeff1;background-color:#fff;padding-left:10px;position:relative}.content .left .post-type .post-type-value[data-v-6236a644]{font-size:14px;font-weight:500;line-height:40px;width:100%;vertical-align:middle;color:#1c1c1c;background-color:transparent;cursor:pointer}.content .left .post-type .post-type-options[data-v-6236a644]{position:absolute;width:100%;background-color:#fff;left:0;z-index:1;border-radius:4px}.content .left .post-type .post-type-options .post-type-cell[data-v-6236a644]{margin:14px 8px 5px;font-size:14px;list-style:none;border-bottom:1px solid #edeff1;padding-bottom:8px;color:#1c1c1c;cursor:pointer}.content .left .post-type .p-icon[data-v-6236a644]{width:0;height:0;border-top:5px solid #878a8c;border-right:5px solid transparent;border-bottom:5px solid transparent;border-left:5px solid transparent;margin-left:10px;position:absolute;top:50%;right:10px;cursor:pointer}.content .left .post-content[data-v-6236a644]{background-color:#fff;margin:10px 0;padding-bottom:15px;border-radius:5px}.content .left .post-content .cat[data-v-6236a644]{display:flex;display:-webkit-flex;justify-content:space-between;align-items:center;width:100%;height:53px}.content .left .post-content .cat .cat-item[data-v-6236a644]{padding:10px 0;width:50%;height:40px;line-height:40px;text-align:center;list-style:none;border-bottom:1px solid #edeff1;border-right:1px solid #edeff1;color:#878a8c}.content .left .post-content .cat .cat-item .iconfont[data-v-6236a644]{margin-right:4px}.content .left .post-content .cat .active[data-v-6236a644]{color:#0079d3;font-weight:bolder;background:none}.content .left .post-content .post-sub-container[data-v-6236a644]{padding:16px}.content .left .post-content .post-sub-container .post-sub-header[data-v-6236a644]{position:relative}.content .left .post-content .post-sub-container .post-sub-header .post-title[data-v-6236a644]{resize:none;box-sizing:border-box;overflow:hidden;display:block;width:100%;height:40px;padding:0 0 0 10px;outline:none;border:1px solid #edeff1;border-radius:4px;color:#1c1c1c;font-size:14px;font-weight:400;line-height:40px}.content .left .post-content .post-sub-container .post-sub-header .textarea-num[data-v-6236a644]{font-size:10px;font-weight:700;letter-spacing:.5px;line-height:12px;text-transform:uppercase;bottom:12px;color:#878a8c;pointer-events:none;position:absolute;right:12px}.content .left .post-content .post-sub-container .post-text-con[data-v-6236a644]{width:100%;height:200px;border:1px solid #edeff1;margin-top:20px}.content .left .post-content .post-sub-container .post-text-con .post-content-t[data-v-6236a644]{resize:none;box-sizing:border-box;overflow:hidden;display:block;width:100%;height:200px;padding:12px 8px;outline:none;border:1px solid #edeff1;border-radius:4px;color:#1c1c1c;font-size:14px;font-weight:400;line-height:21px}.content .left .post-content .post-footer[data-v-6236a644]{display:flex;display:-webkit-flex;margin:0 16px;justify-content:flex-end}.content .left .post-content .post-footer .sign[data-v-6236a644]{display:flex;display:-webkit-flex}.content .left .post-content .post-footer .sign .sign-item[data-v-6236a644]{list-style:none;padding:5px 8px;border:1px solid #edeff1;margin-right:10px;color:#878a8c;font-size:12px;font-weight:700}.content .left .post-content .post-footer .btns .btn[data-v-6236a644]{border:1px solid transparent;border-radius:4px;box-sizing:border-box;text-align:center;text-decoration:none;font-size:12px;font-weight:700;letter-spacing:.5px;line-height:24px;text-transform:uppercase;padding:3px 16px;background:#0079d3;color:#fff;margin-left:8px;cursor:pointer}.content .left .post-content .alias[data-v-6236a644]{background-color:#f6f7f8;border-radius:0 0 6px 6px;border-top:1px solid #edeff1;display:flex;flex-flow:column;padding:8px 16px 21px;position:relative}.content .left .post-content .alias .send-post[data-v-6236a644]{font-size:14px;font-weight:500;line-height:18px;color:#1c1c1c;margin-right:4px}.content .left .post-content .alias .connect[data-v-6236a644]{font-size:14px;font-weight:500;line-height:18px;color:#0079d3;display:block;margin-right:4px;margin-top:10px}.content .right[data-v-6236a644]{flex-grow:0;width:312px;margin-top:62px}.content .right .post-rank[data-v-6236a644]{background-color:#fff;border-radius:4px;margin-top:15px;padding:12px}.content .right .post-rank .p-r-title[data-v-6236a644]{display:flex;display:-webkit-flex;font-size:16px;font-weight:500;line-height:20px;align-items:center;border-bottom:1px solid #edeff1;color:#1c1c1c;padding-bottom:10px}.content .right .post-rank .p-r-title .p-r-icon[data-v-6236a644]{width:40px;height:40px;background:url(../../static/img/avatar.7b0a9835.png) no-repeat;background-size:cover;margin-right:10px}.content .right .post-rank .p-r-content[data-v-6236a644]{display:flex;display:-webkit-flex;flex-direction:column}.content .right .post-rank .p-r-content .p-r-item[data-v-6236a644]{list-style:none;border-bottom:1px solid #edeff1;color:#1c1c1c;padding:10px 5px}.main[data-v-5d78ac92]{background:#f8f8f8;padding:150px 0}.main .container[data-v-5d78ac92]{width:600px;background:#fff;margin:0 auto;max-width:1200px;padding:20px}.main .container .form-title[data-v-5d78ac92]{margin-bottom:33px;text-align:center}.main .container .form-group[data-v-5d78ac92]{margin:15px}.main .container .form-group label[data-v-5d78ac92]{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:700}.main .container .form-group .form-control[data-v-5d78ac92]{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857143;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px}.main .container .form-btn[data-v-5d78ac92]{display:flex;justify-content:center}.main .container .form-btn .btn[data-v-5d78ac92]{padding:6px 20px;font-size:18px;line-height:1.3333333;border-radius:6px;display:inline-block;margin-bottom:0;font-weight:400;text-align:center;white-space:nowrap;vertical-align:middle;touch-action:manipulation;cursor:pointer;border:1px solid transparent}.main .container .form-btn .btn-info[data-v-5d78ac92]{color:#fff;background-color:#5bc0de;border-color:#46b8da}.main[data-v-61f1d9ff]{background:#f8f8f8;padding:150px 0}.main .container[data-v-61f1d9ff]{width:600px;background:#fff;margin:0 auto;max-width:1200px;padding:20px}.main .container .form-title[data-v-61f1d9ff]{margin-bottom:33px;text-align:center}.main .container .form-group[data-v-61f1d9ff]{margin:15px}.main .container .form-group label[data-v-61f1d9ff]{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:700}.main .container .form-group .form-control[data-v-61f1d9ff]{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857143;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px}.main .container .form-btn[data-v-61f1d9ff]{display:flex;justify-content:center}.main .container .form-btn .btn[data-v-61f1d9ff]{padding:6px 20px;font-size:18px;line-height:1.3333333;border-radius:6px;display:inline-block;margin-bottom:0;font-weight:400;text-align:center;white-space:nowrap;vertical-align:middle;touch-action:manipulation;cursor:pointer;border:1px solid transparent}.main .container .form-btn .btn-info[data-v-61f1d9ff]{color:#fff;background-color:#5bc0de;border-color:#46b8da} ================================================ FILE: lesson80/bluebell/static/js/app.9f3efa6d.js ================================================ (function(t){function s(s){for(var n,o,c=s[0],r=s[1],l=s[2],m=0,p=[];mh;h++)if(y=f?b(r(g=t[h])[0],g[1]):b(t[h]),y&&y instanceof u)return y;return new u(!1)}p=d.call(t)}m=p.next;while(!(g=m.call(p)).done)if(y=c(p,b,g.value,f),"object"==typeof y&&y&&y instanceof u)return y;return new u(!1)};f.stop=function(t){return new u(!0,t)}},"23cb":function(t,e,n){var r=n("a691"),o=Math.max,i=Math.min;t.exports=function(t,e){var n=r(t);return n<0?o(n+e,0):i(n,e)}},"23e7":function(t,e,n){var r=n("da84"),o=n("06cf").f,i=n("9112"),a=n("6eeb"),s=n("ce4e"),c=n("e893"),u=n("94ca");t.exports=function(t,e){var n,f,l,p,d,h,v=t.target,y=t.global,m=t.stat;if(f=y?r:m?r[v]||s(v,{}):(r[v]||{}).prototype,f)for(l in e){if(d=e[l],t.noTargetGet?(h=o(f,l),p=h&&h.value):p=f[l],n=u(y?l:v+(m?".":"#")+l,t.forced),!n&&void 0!==p){if(typeof d===typeof p)continue;c(d,p)}(t.sham||p&&p.sham)&&i(d,"sham",!0),a(f,l,d,t)}}},"241c":function(t,e,n){var r=n("ca84"),o=n("7839"),i=o.concat("length","prototype");e.f=Object.getOwnPropertyNames||function(t){return r(t,i)}},2444:function(t,e,n){"use strict";(function(e){var r=n("c532"),o=n("c8af"),i={"Content-Type":"application/x-www-form-urlencoded"};function a(t,e){!r.isUndefined(t)&&r.isUndefined(t["Content-Type"])&&(t["Content-Type"]=e)}function s(){var t;return("undefined"!==typeof XMLHttpRequest||"undefined"!==typeof e&&"[object process]"===Object.prototype.toString.call(e))&&(t=n("b50d")),t}var c={adapter:s(),transformRequest:[function(t,e){return o(e,"Accept"),o(e,"Content-Type"),r.isFormData(t)||r.isArrayBuffer(t)||r.isBuffer(t)||r.isStream(t)||r.isFile(t)||r.isBlob(t)?t:r.isArrayBufferView(t)?t.buffer:r.isURLSearchParams(t)?(a(e,"application/x-www-form-urlencoded;charset=utf-8"),t.toString()):r.isObject(t)?(a(e,"application/json;charset=utf-8"),JSON.stringify(t)):t}],transformResponse:[function(t){if("string"===typeof t)try{t=JSON.parse(t)}catch(e){}return t}],timeout:0,xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN",maxContentLength:-1,validateStatus:function(t){return t>=200&&t<300},headers:{common:{Accept:"application/json, text/plain, */*"}}};r.forEach(["delete","get","head"],(function(t){c.headers[t]={}})),r.forEach(["post","put","patch"],(function(t){c.headers[t]=r.merge(i)})),t.exports=c}).call(this,n("4362"))},2626:function(t,e,n){"use strict";var r=n("d066"),o=n("9bf2"),i=n("b622"),a=n("83ab"),s=i("species");t.exports=function(t){var e=r(t),n=o.f;a&&e&&!e[s]&&n(e,s,{configurable:!0,get:function(){return this}})}},2877:function(t,e,n){"use strict";function r(t,e,n,r,o,i,a,s){var c,u="function"===typeof t?t.options:t;if(e&&(u.render=e,u.staticRenderFns=n,u._compiled=!0),r&&(u.functional=!0),i&&(u._scopeId="data-v-"+i),a?(c=function(t){t=t||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext,t||"undefined"===typeof __VUE_SSR_CONTEXT__||(t=__VUE_SSR_CONTEXT__),o&&o.call(this,t),t&&t._registeredComponents&&t._registeredComponents.add(a)},u._ssrRegister=c):o&&(c=s?function(){o.call(this,this.$root.$options.shadowRoot)}:o),c)if(u.functional){u._injectStyles=c;var f=u.render;u.render=function(t,e){return c.call(e),f(t,e)}}else{var l=u.beforeCreate;u.beforeCreate=l?[].concat(l,c):[c]}return{exports:t,options:u}}n.d(e,"a",(function(){return r}))},"2b0e":function(t,e,n){"use strict";(function(t){ /*! * Vue.js v2.6.11 * (c) 2014-2019 Evan You * Released under the MIT License. */ var n=Object.freeze({});function r(t){return void 0===t||null===t}function o(t){return void 0!==t&&null!==t}function i(t){return!0===t}function a(t){return!1===t}function s(t){return"string"===typeof t||"number"===typeof t||"symbol"===typeof t||"boolean"===typeof t}function c(t){return null!==t&&"object"===typeof t}var u=Object.prototype.toString;function f(t){return"[object Object]"===u.call(t)}function l(t){return"[object RegExp]"===u.call(t)}function p(t){var e=parseFloat(String(t));return e>=0&&Math.floor(e)===e&&isFinite(t)}function d(t){return o(t)&&"function"===typeof t.then&&"function"===typeof t.catch}function h(t){return null==t?"":Array.isArray(t)||f(t)&&t.toString===u?JSON.stringify(t,null,2):String(t)}function v(t){var e=parseFloat(t);return isNaN(e)?t:e}function y(t,e){for(var n=Object.create(null),r=t.split(","),o=0;o-1)return t.splice(n,1)}}var b=Object.prototype.hasOwnProperty;function _(t,e){return b.call(t,e)}function w(t){var e=Object.create(null);return function(n){var r=e[n];return r||(e[n]=t(n))}}var x=/-(\w)/g,C=w((function(t){return t.replace(x,(function(t,e){return e?e.toUpperCase():""}))})),O=w((function(t){return t.charAt(0).toUpperCase()+t.slice(1)})),A=/\B([A-Z])/g,k=w((function(t){return t.replace(A,"-$1").toLowerCase()}));function j(t,e){function n(n){var r=arguments.length;return r?r>1?t.apply(e,arguments):t.call(e,n):t.call(e)}return n._length=t.length,n}function S(t,e){return t.bind(e)}var $=Function.prototype.bind?S:j;function E(t,e){e=e||0;var n=t.length-e,r=new Array(n);while(n--)r[n]=t[n+e];return r}function T(t,e){for(var n in e)t[n]=e[n];return t}function P(t){for(var e={},n=0;n0,nt=Z&&Z.indexOf("edge/")>0,rt=(Z&&Z.indexOf("android"),Z&&/iphone|ipad|ipod|ios/.test(Z)||"ios"===Q),ot=(Z&&/chrome\/\d+/.test(Z),Z&&/phantomjs/.test(Z),Z&&Z.match(/firefox\/(\d+)/)),it={}.watch,at=!1;if(J)try{var st={};Object.defineProperty(st,"passive",{get:function(){at=!0}}),window.addEventListener("test-passive",null,st)}catch(Ca){}var ct=function(){return void 0===K&&(K=!J&&!Y&&"undefined"!==typeof t&&(t["process"]&&"server"===t["process"].env.VUE_ENV)),K},ut=J&&window.__VUE_DEVTOOLS_GLOBAL_HOOK__;function ft(t){return"function"===typeof t&&/native code/.test(t.toString())}var lt,pt="undefined"!==typeof Symbol&&ft(Symbol)&&"undefined"!==typeof Reflect&&ft(Reflect.ownKeys);lt="undefined"!==typeof Set&&ft(Set)?Set:function(){function t(){this.set=Object.create(null)}return t.prototype.has=function(t){return!0===this.set[t]},t.prototype.add=function(t){this.set[t]=!0},t.prototype.clear=function(){this.set=Object.create(null)},t}();var dt=R,ht=0,vt=function(){this.id=ht++,this.subs=[]};vt.prototype.addSub=function(t){this.subs.push(t)},vt.prototype.removeSub=function(t){g(this.subs,t)},vt.prototype.depend=function(){vt.target&&vt.target.addDep(this)},vt.prototype.notify=function(){var t=this.subs.slice();for(var e=0,n=t.length;e-1)if(i&&!_(o,"default"))a=!1;else if(""===a||a===k(t)){var c=te(String,o.type);(c<0||s0&&(a=je(a,(e||"")+"_"+n),ke(a[0])&&ke(u)&&(f[c]=xt(u.text+a[0].text),a.shift()),f.push.apply(f,a)):s(a)?ke(u)?f[c]=xt(u.text+a):""!==a&&f.push(xt(a)):ke(a)&&ke(u)?f[c]=xt(u.text+a.text):(i(t._isVList)&&o(a.tag)&&r(a.key)&&o(e)&&(a.key="__vlist"+e+"_"+n+"__"),f.push(a)));return f}function Se(t){var e=t.$options.provide;e&&(t._provided="function"===typeof e?e.call(t):e)}function $e(t){var e=Ee(t.$options.inject,t);e&&($t(!1),Object.keys(e).forEach((function(n){It(t,n,e[n])})),$t(!0))}function Ee(t,e){if(t){for(var n=Object.create(null),r=pt?Reflect.ownKeys(t):Object.keys(t),o=0;o0,a=t?!!t.$stable:!i,s=t&&t.$key;if(t){if(t._normalized)return t._normalized;if(a&&r&&r!==n&&s===r.$key&&!i&&!r.$hasNormal)return r;for(var c in o={},t)t[c]&&"$"!==c[0]&&(o[c]=Ie(e,c,t[c]))}else o={};for(var u in e)u in o||(o[u]=Me(e,u));return t&&Object.isExtensible(t)&&(t._normalized=o),z(o,"$stable",a),z(o,"$key",s),z(o,"$hasNormal",i),o}function Ie(t,e,n){var r=function(){var t=arguments.length?n.apply(null,arguments):n({});return t=t&&"object"===typeof t&&!Array.isArray(t)?[t]:Ae(t),t&&(0===t.length||1===t.length&&t[0].isComment)?void 0:t};return n.proxy&&Object.defineProperty(t,e,{get:r,enumerable:!0,configurable:!0}),r}function Me(t,e){return function(){return t[e]}}function Ne(t,e){var n,r,i,a,s;if(Array.isArray(t)||"string"===typeof t)for(n=new Array(t.length),r=0,i=t.length;r1?E(n):n;for(var r=E(arguments,1),o='event handler for "'+t+'"',i=0,a=n.length;idocument.createEvent("Event").timeStamp&&(Kn=function(){return Xn.now()})}function Jn(){var t,e;for(Wn=Kn(),Vn=!0,Un.sort((function(t,e){return t.id-e.id})),zn=0;znzn&&Un[n].id>t.id)n--;Un.splice(n+1,0,t)}else Un.push(t);Hn||(Hn=!0,he(Jn))}}var er=0,nr=function(t,e,n,r,o){this.vm=t,o&&(t._watcher=this),t._watchers.push(this),r?(this.deep=!!r.deep,this.user=!!r.user,this.lazy=!!r.lazy,this.sync=!!r.sync,this.before=r.before):this.deep=this.user=this.lazy=this.sync=!1,this.cb=n,this.id=++er,this.active=!0,this.dirty=this.lazy,this.deps=[],this.newDeps=[],this.depIds=new lt,this.newDepIds=new lt,this.expression="","function"===typeof e?this.getter=e:(this.getter=W(e),this.getter||(this.getter=R)),this.value=this.lazy?void 0:this.get()};nr.prototype.get=function(){var t;mt(this);var e=this.vm;try{t=this.getter.call(e,e)}catch(Ca){if(!this.user)throw Ca;ee(Ca,e,'getter for watcher "'+this.expression+'"')}finally{this.deep&&ye(t),gt(),this.cleanupDeps()}return t},nr.prototype.addDep=function(t){var e=t.id;this.newDepIds.has(e)||(this.newDepIds.add(e),this.newDeps.push(t),this.depIds.has(e)||t.addSub(this))},nr.prototype.cleanupDeps=function(){var t=this.deps.length;while(t--){var e=this.deps[t];this.newDepIds.has(e.id)||e.removeSub(this)}var n=this.depIds;this.depIds=this.newDepIds,this.newDepIds=n,this.newDepIds.clear(),n=this.deps,this.deps=this.newDeps,this.newDeps=n,this.newDeps.length=0},nr.prototype.update=function(){this.lazy?this.dirty=!0:this.sync?this.run():tr(this)},nr.prototype.run=function(){if(this.active){var t=this.get();if(t!==this.value||c(t)||this.deep){var e=this.value;if(this.value=t,this.user)try{this.cb.call(this.vm,t,e)}catch(Ca){ee(Ca,this.vm,'callback for watcher "'+this.expression+'"')}else this.cb.call(this.vm,t,e)}}},nr.prototype.evaluate=function(){this.value=this.get(),this.dirty=!1},nr.prototype.depend=function(){var t=this.deps.length;while(t--)this.deps[t].depend()},nr.prototype.teardown=function(){if(this.active){this.vm._isBeingDestroyed||g(this.vm._watchers,this);var t=this.deps.length;while(t--)this.deps[t].removeSub(this);this.active=!1}};var rr={enumerable:!0,configurable:!0,get:R,set:R};function or(t,e,n){rr.get=function(){return this[e][n]},rr.set=function(t){this[e][n]=t},Object.defineProperty(t,n,rr)}function ir(t){t._watchers=[];var e=t.$options;e.props&&ar(t,e.props),e.methods&&hr(t,e.methods),e.data?sr(t):Rt(t._data={},!0),e.computed&&fr(t,e.computed),e.watch&&e.watch!==it&&vr(t,e.watch)}function ar(t,e){var n=t.$options.propsData||{},r=t._props={},o=t.$options._propKeys=[],i=!t.$parent;i||$t(!1);var a=function(i){o.push(i);var a=Jt(i,e,n,t);It(r,i,a),i in t||or(t,"_props",i)};for(var s in e)a(s);$t(!0)}function sr(t){var e=t.$options.data;e=t._data="function"===typeof e?cr(e,t):e||{},f(e)||(e={});var n=Object.keys(e),r=t.$options.props,o=(t.$options.methods,n.length);while(o--){var i=n[o];0,r&&_(r,i)||V(i)||or(t,"_data",i)}Rt(e,!0)}function cr(t,e){mt();try{return t.call(e,e)}catch(Ca){return ee(Ca,e,"data()"),{}}finally{gt()}}var ur={lazy:!0};function fr(t,e){var n=t._computedWatchers=Object.create(null),r=ct();for(var o in e){var i=e[o],a="function"===typeof i?i:i.get;0,r||(n[o]=new nr(t,a||R,R,ur)),o in t||lr(t,o,i)}}function lr(t,e,n){var r=!ct();"function"===typeof n?(rr.get=r?pr(e):dr(n),rr.set=R):(rr.get=n.get?r&&!1!==n.cache?pr(e):dr(n.get):R,rr.set=n.set||R),Object.defineProperty(t,e,rr)}function pr(t){return function(){var e=this._computedWatchers&&this._computedWatchers[t];if(e)return e.dirty&&e.evaluate(),vt.target&&e.depend(),e.value}}function dr(t){return function(){return t.call(this,this)}}function hr(t,e){t.$options.props;for(var n in e)t[n]="function"!==typeof e[n]?R:$(e[n],t)}function vr(t,e){for(var n in e){var r=e[n];if(Array.isArray(r))for(var o=0;o-1)return this;var n=E(arguments,1);return n.unshift(this),"function"===typeof t.install?t.install.apply(t,n):"function"===typeof t&&t.apply(null,n),e.push(t),this}}function Ar(t){t.mixin=function(t){return this.options=Kt(this.options,t),this}}function kr(t){t.cid=0;var e=1;t.extend=function(t){t=t||{};var n=this,r=n.cid,o=t._Ctor||(t._Ctor={});if(o[r])return o[r];var i=t.name||n.options.name;var a=function(t){this._init(t)};return a.prototype=Object.create(n.prototype),a.prototype.constructor=a,a.cid=e++,a.options=Kt(n.options,t),a["super"]=n,a.options.props&&jr(a),a.options.computed&&Sr(a),a.extend=n.extend,a.mixin=n.mixin,a.use=n.use,U.forEach((function(t){a[t]=n[t]})),i&&(a.options.components[i]=a),a.superOptions=n.options,a.extendOptions=t,a.sealedOptions=T({},a.options),o[r]=a,a}}function jr(t){var e=t.options.props;for(var n in e)or(t.prototype,"_props",n)}function Sr(t){var e=t.options.computed;for(var n in e)lr(t.prototype,n,e[n])}function $r(t){U.forEach((function(e){t[e]=function(t,n){return n?("component"===e&&f(n)&&(n.name=n.name||t,n=this.options._base.extend(n)),"directive"===e&&"function"===typeof n&&(n={bind:n,update:n}),this.options[e+"s"][t]=n,n):this.options[e+"s"][t]}}))}function Er(t){return t&&(t.Ctor.options.name||t.tag)}function Tr(t,e){return Array.isArray(t)?t.indexOf(e)>-1:"string"===typeof t?t.split(",").indexOf(e)>-1:!!l(t)&&t.test(e)}function Pr(t,e){var n=t.cache,r=t.keys,o=t._vnode;for(var i in n){var a=n[i];if(a){var s=Er(a.componentOptions);s&&!e(s)&&Rr(n,i,r,o)}}}function Rr(t,e,n,r){var o=t[e];!o||r&&o.tag===r.tag||o.componentInstance.$destroy(),t[e]=null,g(n,e)}br(Cr),mr(Cr),$n(Cr),Rn(Cr),gn(Cr);var Ir=[String,RegExp,Array],Mr={name:"keep-alive",abstract:!0,props:{include:Ir,exclude:Ir,max:[String,Number]},created:function(){this.cache=Object.create(null),this.keys=[]},destroyed:function(){for(var t in this.cache)Rr(this.cache,t,this.keys)},mounted:function(){var t=this;this.$watch("include",(function(e){Pr(t,(function(t){return Tr(e,t)}))})),this.$watch("exclude",(function(e){Pr(t,(function(t){return!Tr(e,t)}))}))},render:function(){var t=this.$slots.default,e=Cn(t),n=e&&e.componentOptions;if(n){var r=Er(n),o=this,i=o.include,a=o.exclude;if(i&&(!r||!Tr(i,r))||a&&r&&Tr(a,r))return e;var s=this,c=s.cache,u=s.keys,f=null==e.key?n.Ctor.cid+(n.tag?"::"+n.tag:""):e.key;c[f]?(e.componentInstance=c[f].componentInstance,g(u,f),u.push(f)):(c[f]=e,u.push(f),this.max&&u.length>parseInt(this.max)&&Rr(c,u[0],u,this._vnode)),e.data.keepAlive=!0}return e||t&&t[0]}},Nr={KeepAlive:Mr};function Lr(t){var e={get:function(){return q}};Object.defineProperty(t,"config",e),t.util={warn:dt,extend:T,mergeOptions:Kt,defineReactive:It},t.set=Mt,t.delete=Nt,t.nextTick=he,t.observable=function(t){return Rt(t),t},t.options=Object.create(null),U.forEach((function(e){t.options[e+"s"]=Object.create(null)})),t.options._base=t,T(t.options.components,Nr),Or(t),Ar(t),kr(t),$r(t)}Lr(Cr),Object.defineProperty(Cr.prototype,"$isServer",{get:ct}),Object.defineProperty(Cr.prototype,"$ssrContext",{get:function(){return this.$vnode&&this.$vnode.ssrContext}}),Object.defineProperty(Cr,"FunctionalRenderContext",{value:Ye}),Cr.version="2.6.11";var Dr=y("style,class"),Fr=y("input,textarea,option,select,progress"),Ur=function(t,e,n){return"value"===n&&Fr(t)&&"button"!==e||"selected"===n&&"option"===t||"checked"===n&&"input"===t||"muted"===n&&"video"===t},Br=y("contenteditable,draggable,spellcheck"),qr=y("events,caret,typing,plaintext-only"),Hr=function(t,e){return Kr(e)||"false"===e?"false":"contenteditable"===t&&qr(e)?e:"true"},Vr=y("allowfullscreen,async,autofocus,autoplay,checked,compact,controls,declare,default,defaultchecked,defaultmuted,defaultselected,defer,disabled,enabled,formnovalidate,hidden,indeterminate,inert,ismap,itemscope,loop,multiple,muted,nohref,noresize,noshade,novalidate,nowrap,open,pauseonexit,readonly,required,reversed,scoped,seamless,selected,sortable,translate,truespeed,typemustmatch,visible"),zr="http://www.w3.org/1999/xlink",Gr=function(t){return":"===t.charAt(5)&&"xlink"===t.slice(0,5)},Wr=function(t){return Gr(t)?t.slice(6,t.length):""},Kr=function(t){return null==t||!1===t};function Xr(t){var e=t.data,n=t,r=t;while(o(r.componentInstance))r=r.componentInstance._vnode,r&&r.data&&(e=Jr(r.data,e));while(o(n=n.parent))n&&n.data&&(e=Jr(e,n.data));return Yr(e.staticClass,e.class)}function Jr(t,e){return{staticClass:Qr(t.staticClass,e.staticClass),class:o(t.class)?[t.class,e.class]:e.class}}function Yr(t,e){return o(t)||o(e)?Qr(t,Zr(e)):""}function Qr(t,e){return t?e?t+" "+e:t:e||""}function Zr(t){return Array.isArray(t)?to(t):c(t)?eo(t):"string"===typeof t?t:""}function to(t){for(var e,n="",r=0,i=t.length;r-1?so[t]=e.constructor===window.HTMLUnknownElement||e.constructor===window.HTMLElement:so[t]=/HTMLUnknownElement/.test(e.toString())}var uo=y("text,number,password,search,email,tel,url");function fo(t){if("string"===typeof t){var e=document.querySelector(t);return e||document.createElement("div")}return t}function lo(t,e){var n=document.createElement(t);return"select"!==t||e.data&&e.data.attrs&&void 0!==e.data.attrs.multiple&&n.setAttribute("multiple","multiple"),n}function po(t,e){return document.createElementNS(no[t],e)}function ho(t){return document.createTextNode(t)}function vo(t){return document.createComment(t)}function yo(t,e,n){t.insertBefore(e,n)}function mo(t,e){t.removeChild(e)}function go(t,e){t.appendChild(e)}function bo(t){return t.parentNode}function _o(t){return t.nextSibling}function wo(t){return t.tagName}function xo(t,e){t.textContent=e}function Co(t,e){t.setAttribute(e,"")}var Oo=Object.freeze({createElement:lo,createElementNS:po,createTextNode:ho,createComment:vo,insertBefore:yo,removeChild:mo,appendChild:go,parentNode:bo,nextSibling:_o,tagName:wo,setTextContent:xo,setStyleScope:Co}),Ao={create:function(t,e){ko(e)},update:function(t,e){t.data.ref!==e.data.ref&&(ko(t,!0),ko(e))},destroy:function(t){ko(t,!0)}};function ko(t,e){var n=t.data.ref;if(o(n)){var r=t.context,i=t.componentInstance||t.elm,a=r.$refs;e?Array.isArray(a[n])?g(a[n],i):a[n]===i&&(a[n]=void 0):t.data.refInFor?Array.isArray(a[n])?a[n].indexOf(i)<0&&a[n].push(i):a[n]=[i]:a[n]=i}}var jo=new bt("",{},[]),So=["create","activate","update","remove","destroy"];function $o(t,e){return t.key===e.key&&(t.tag===e.tag&&t.isComment===e.isComment&&o(t.data)===o(e.data)&&Eo(t,e)||i(t.isAsyncPlaceholder)&&t.asyncFactory===e.asyncFactory&&r(e.asyncFactory.error))}function Eo(t,e){if("input"!==t.tag)return!0;var n,r=o(n=t.data)&&o(n=n.attrs)&&n.type,i=o(n=e.data)&&o(n=n.attrs)&&n.type;return r===i||uo(r)&&uo(i)}function To(t,e,n){var r,i,a={};for(r=e;r<=n;++r)i=t[r].key,o(i)&&(a[i]=r);return a}function Po(t){var e,n,a={},c=t.modules,u=t.nodeOps;for(e=0;ev?(l=r(n[g+1])?null:n[g+1].elm,C(t,l,n,h,g,i)):h>g&&A(e,p,v)}function S(t,e,n,r){for(var i=n;i-1?Ho(t,e,n):Vr(e)?Kr(n)?t.removeAttribute(e):(n="allowfullscreen"===e&&"EMBED"===t.tagName?"true":e,t.setAttribute(e,n)):Br(e)?t.setAttribute(e,Hr(e,n)):Gr(e)?Kr(n)?t.removeAttributeNS(zr,Wr(e)):t.setAttributeNS(zr,e,n):Ho(t,e,n)}function Ho(t,e,n){if(Kr(n))t.removeAttribute(e);else{if(tt&&!et&&"TEXTAREA"===t.tagName&&"placeholder"===e&&""!==n&&!t.__ieph){var r=function(e){e.stopImmediatePropagation(),t.removeEventListener("input",r)};t.addEventListener("input",r),t.__ieph=!0}t.setAttribute(e,n)}}var Vo={create:Bo,update:Bo};function zo(t,e){var n=e.elm,i=e.data,a=t.data;if(!(r(i.staticClass)&&r(i.class)&&(r(a)||r(a.staticClass)&&r(a.class)))){var s=Xr(e),c=n._transitionClasses;o(c)&&(s=Qr(s,Zr(c))),s!==n._prevClass&&(n.setAttribute("class",s),n._prevClass=s)}}var Go,Wo={create:zo,update:zo},Ko="__r",Xo="__c";function Jo(t){if(o(t[Ko])){var e=tt?"change":"input";t[e]=[].concat(t[Ko],t[e]||[]),delete t[Ko]}o(t[Xo])&&(t.change=[].concat(t[Xo],t.change||[]),delete t[Xo])}function Yo(t,e,n){var r=Go;return function o(){var i=e.apply(null,arguments);null!==i&&ti(t,o,n,r)}}var Qo=ae&&!(ot&&Number(ot[1])<=53);function Zo(t,e,n,r){if(Qo){var o=Wn,i=e;e=i._wrapper=function(t){if(t.target===t.currentTarget||t.timeStamp>=o||t.timeStamp<=0||t.target.ownerDocument!==document)return i.apply(this,arguments)}}Go.addEventListener(t,e,at?{capture:n,passive:r}:n)}function ti(t,e,n,r){(r||Go).removeEventListener(t,e._wrapper||e,n)}function ei(t,e){if(!r(t.data.on)||!r(e.data.on)){var n=e.data.on||{},o=t.data.on||{};Go=e.elm,Jo(n),_e(n,o,Zo,ti,Yo,e.context),Go=void 0}}var ni,ri={create:ei,update:ei};function oi(t,e){if(!r(t.data.domProps)||!r(e.data.domProps)){var n,i,a=e.elm,s=t.data.domProps||{},c=e.data.domProps||{};for(n in o(c.__ob__)&&(c=e.data.domProps=T({},c)),s)n in c||(a[n]="");for(n in c){if(i=c[n],"textContent"===n||"innerHTML"===n){if(e.children&&(e.children.length=0),i===s[n])continue;1===a.childNodes.length&&a.removeChild(a.childNodes[0])}if("value"===n&&"PROGRESS"!==a.tagName){a._value=i;var u=r(i)?"":String(i);ii(a,u)&&(a.value=u)}else if("innerHTML"===n&&oo(a.tagName)&&r(a.innerHTML)){ni=ni||document.createElement("div"),ni.innerHTML=""+i+"";var f=ni.firstChild;while(a.firstChild)a.removeChild(a.firstChild);while(f.firstChild)a.appendChild(f.firstChild)}else if(i!==s[n])try{a[n]=i}catch(Ca){}}}}function ii(t,e){return!t.composing&&("OPTION"===t.tagName||ai(t,e)||si(t,e))}function ai(t,e){var n=!0;try{n=document.activeElement!==t}catch(Ca){}return n&&t.value!==e}function si(t,e){var n=t.value,r=t._vModifiers;if(o(r)){if(r.number)return v(n)!==v(e);if(r.trim)return n.trim()!==e.trim()}return n!==e}var ci={create:oi,update:oi},ui=w((function(t){var e={},n=/;(?![^(]*\))/g,r=/:(.+)/;return t.split(n).forEach((function(t){if(t){var n=t.split(r);n.length>1&&(e[n[0].trim()]=n[1].trim())}})),e}));function fi(t){var e=li(t.style);return t.staticStyle?T(t.staticStyle,e):e}function li(t){return Array.isArray(t)?P(t):"string"===typeof t?ui(t):t}function pi(t,e){var n,r={};if(e){var o=t;while(o.componentInstance)o=o.componentInstance._vnode,o&&o.data&&(n=fi(o.data))&&T(r,n)}(n=fi(t.data))&&T(r,n);var i=t;while(i=i.parent)i.data&&(n=fi(i.data))&&T(r,n);return r}var di,hi=/^--/,vi=/\s*!important$/,yi=function(t,e,n){if(hi.test(e))t.style.setProperty(e,n);else if(vi.test(n))t.style.setProperty(k(e),n.replace(vi,""),"important");else{var r=gi(e);if(Array.isArray(n))for(var o=0,i=n.length;o-1?e.split(wi).forEach((function(e){return t.classList.add(e)})):t.classList.add(e);else{var n=" "+(t.getAttribute("class")||"")+" ";n.indexOf(" "+e+" ")<0&&t.setAttribute("class",(n+e).trim())}}function Ci(t,e){if(e&&(e=e.trim()))if(t.classList)e.indexOf(" ")>-1?e.split(wi).forEach((function(e){return t.classList.remove(e)})):t.classList.remove(e),t.classList.length||t.removeAttribute("class");else{var n=" "+(t.getAttribute("class")||"")+" ",r=" "+e+" ";while(n.indexOf(r)>=0)n=n.replace(r," ");n=n.trim(),n?t.setAttribute("class",n):t.removeAttribute("class")}}function Oi(t){if(t){if("object"===typeof t){var e={};return!1!==t.css&&T(e,Ai(t.name||"v")),T(e,t),e}return"string"===typeof t?Ai(t):void 0}}var Ai=w((function(t){return{enterClass:t+"-enter",enterToClass:t+"-enter-to",enterActiveClass:t+"-enter-active",leaveClass:t+"-leave",leaveToClass:t+"-leave-to",leaveActiveClass:t+"-leave-active"}})),ki=J&&!et,ji="transition",Si="animation",$i="transition",Ei="transitionend",Ti="animation",Pi="animationend";ki&&(void 0===window.ontransitionend&&void 0!==window.onwebkittransitionend&&($i="WebkitTransition",Ei="webkitTransitionEnd"),void 0===window.onanimationend&&void 0!==window.onwebkitanimationend&&(Ti="WebkitAnimation",Pi="webkitAnimationEnd"));var Ri=J?window.requestAnimationFrame?window.requestAnimationFrame.bind(window):setTimeout:function(t){return t()};function Ii(t){Ri((function(){Ri(t)}))}function Mi(t,e){var n=t._transitionClasses||(t._transitionClasses=[]);n.indexOf(e)<0&&(n.push(e),xi(t,e))}function Ni(t,e){t._transitionClasses&&g(t._transitionClasses,e),Ci(t,e)}function Li(t,e,n){var r=Fi(t,e),o=r.type,i=r.timeout,a=r.propCount;if(!o)return n();var s=o===ji?Ei:Pi,c=0,u=function(){t.removeEventListener(s,f),n()},f=function(e){e.target===t&&++c>=a&&u()};setTimeout((function(){c0&&(n=ji,f=a,l=i.length):e===Si?u>0&&(n=Si,f=u,l=c.length):(f=Math.max(a,u),n=f>0?a>u?ji:Si:null,l=n?n===ji?i.length:c.length:0);var p=n===ji&&Di.test(r[$i+"Property"]);return{type:n,timeout:f,propCount:l,hasTransform:p}}function Ui(t,e){while(t.length1}function Gi(t,e){!0!==e.data.show&&qi(e)}var Wi=J?{create:Gi,activate:Gi,remove:function(t,e){!0!==t.data.show?Hi(t,e):e()}}:{},Ki=[Vo,Wo,ri,ci,_i,Wi],Xi=Ki.concat(Uo),Ji=Po({nodeOps:Oo,modules:Xi});et&&document.addEventListener("selectionchange",(function(){var t=document.activeElement;t&&t.vmodel&&oa(t,"input")}));var Yi={inserted:function(t,e,n,r){"select"===n.tag?(r.elm&&!r.elm._vOptions?we(n,"postpatch",(function(){Yi.componentUpdated(t,e,n)})):Qi(t,e,n.context),t._vOptions=[].map.call(t.options,ea)):("textarea"===n.tag||uo(t.type))&&(t._vModifiers=e.modifiers,e.modifiers.lazy||(t.addEventListener("compositionstart",na),t.addEventListener("compositionend",ra),t.addEventListener("change",ra),et&&(t.vmodel=!0)))},componentUpdated:function(t,e,n){if("select"===n.tag){Qi(t,e,n.context);var r=t._vOptions,o=t._vOptions=[].map.call(t.options,ea);if(o.some((function(t,e){return!N(t,r[e])}))){var i=t.multiple?e.value.some((function(t){return ta(t,o)})):e.value!==e.oldValue&&ta(e.value,o);i&&oa(t,"change")}}}};function Qi(t,e,n){Zi(t,e,n),(tt||nt)&&setTimeout((function(){Zi(t,e,n)}),0)}function Zi(t,e,n){var r=e.value,o=t.multiple;if(!o||Array.isArray(r)){for(var i,a,s=0,c=t.options.length;s-1,a.selected!==i&&(a.selected=i);else if(N(ea(a),r))return void(t.selectedIndex!==s&&(t.selectedIndex=s));o||(t.selectedIndex=-1)}}function ta(t,e){return e.every((function(e){return!N(e,t)}))}function ea(t){return"_value"in t?t._value:t.value}function na(t){t.target.composing=!0}function ra(t){t.target.composing&&(t.target.composing=!1,oa(t.target,"input"))}function oa(t,e){var n=document.createEvent("HTMLEvents");n.initEvent(e,!0,!0),t.dispatchEvent(n)}function ia(t){return!t.componentInstance||t.data&&t.data.transition?t:ia(t.componentInstance._vnode)}var aa={bind:function(t,e,n){var r=e.value;n=ia(n);var o=n.data&&n.data.transition,i=t.__vOriginalDisplay="none"===t.style.display?"":t.style.display;r&&o?(n.data.show=!0,qi(n,(function(){t.style.display=i}))):t.style.display=r?i:"none"},update:function(t,e,n){var r=e.value,o=e.oldValue;if(!r!==!o){n=ia(n);var i=n.data&&n.data.transition;i?(n.data.show=!0,r?qi(n,(function(){t.style.display=t.__vOriginalDisplay})):Hi(n,(function(){t.style.display="none"}))):t.style.display=r?t.__vOriginalDisplay:"none"}},unbind:function(t,e,n,r,o){o||(t.style.display=t.__vOriginalDisplay)}},sa={model:Yi,show:aa},ca={name:String,appear:Boolean,css:Boolean,mode:String,type:String,enterClass:String,leaveClass:String,enterToClass:String,leaveToClass:String,enterActiveClass:String,leaveActiveClass:String,appearClass:String,appearActiveClass:String,appearToClass:String,duration:[Number,String,Object]};function ua(t){var e=t&&t.componentOptions;return e&&e.Ctor.options.abstract?ua(Cn(e.children)):t}function fa(t){var e={},n=t.$options;for(var r in n.propsData)e[r]=t[r];var o=n._parentListeners;for(var i in o)e[C(i)]=o[i];return e}function la(t,e){if(/\d-keep-alive$/.test(e.tag))return t("keep-alive",{props:e.componentOptions.propsData})}function pa(t){while(t=t.parent)if(t.data.transition)return!0}function da(t,e){return e.key===t.key&&e.tag===t.tag}var ha=function(t){return t.tag||xn(t)},va=function(t){return"show"===t.name},ya={name:"transition",props:ca,abstract:!0,render:function(t){var e=this,n=this.$slots.default;if(n&&(n=n.filter(ha),n.length)){0;var r=this.mode;0;var o=n[0];if(pa(this.$vnode))return o;var i=ua(o);if(!i)return o;if(this._leaving)return la(t,o);var a="__transition-"+this._uid+"-";i.key=null==i.key?i.isComment?a+"comment":a+i.tag:s(i.key)?0===String(i.key).indexOf(a)?i.key:a+i.key:i.key;var c=(i.data||(i.data={})).transition=fa(this),u=this._vnode,f=ua(u);if(i.data.directives&&i.data.directives.some(va)&&(i.data.show=!0),f&&f.data&&!da(i,f)&&!xn(f)&&(!f.componentInstance||!f.componentInstance._vnode.isComment)){var l=f.data.transition=T({},c);if("out-in"===r)return this._leaving=!0,we(l,"afterLeave",(function(){e._leaving=!1,e.$forceUpdate()})),la(t,o);if("in-out"===r){if(xn(i))return u;var p,d=function(){p()};we(c,"afterEnter",d),we(c,"enterCancelled",d),we(l,"delayLeave",(function(t){p=t}))}}return o}}},ma=T({tag:String,moveClass:String},ca);delete ma.mode;var ga={props:ma,beforeMount:function(){var t=this,e=this._update;this._update=function(n,r){var o=Tn(t);t.__patch__(t._vnode,t.kept,!1,!0),t._vnode=t.kept,o(),e.call(t,n,r)}},render:function(t){for(var e=this.tag||this.$vnode.data.tag||"span",n=Object.create(null),r=this.prevChildren=this.children,o=this.$slots.default||[],i=this.children=[],a=fa(this),s=0;sn)e.push(arguments[n++]);return _[++b]=function(){("function"==typeof t?t:Function(t)).apply(void 0,e)},r(b),b},v=function(t){delete _[t]},"process"==c(y)?r=function(t){y.nextTick(C(t))}:g&&g.now?r=function(t){g.now(C(t))}:m&&!p?(o=new m,i=o.port2,o.port1.onmessage=O,r=u(i.postMessage,i,1)):!a.addEventListener||"function"!=typeof postMessage||a.importScripts||s(A)?r=w in l("script")?function(t){f.appendChild(l("script"))[w]=function(){f.removeChild(this),x(t)}}:function(t){setTimeout(C(t),0)}:(r=A,a.addEventListener("message",O,!1))),t.exports={set:h,clear:v}},"2d00":function(t,e,n){var r,o,i=n("da84"),a=n("342f"),s=i.process,c=s&&s.versions,u=c&&c.v8;u?(r=u.split("."),o=r[0]+r[1]):a&&(r=a.match(/Edge\/(\d+)/),(!r||r[1]>=74)&&(r=a.match(/Chrome\/(\d+)/),r&&(o=r[1]))),t.exports=o&&+o},"2d83":function(t,e,n){"use strict";var r=n("387f");t.exports=function(t,e,n,o,i){var a=new Error(t);return r(a,e,n,o,i)}},"2e67":function(t,e,n){"use strict";t.exports=function(t){return!(!t||!t.__CANCEL__)}},"2f62":function(t,e,n){"use strict";(function(t){ /** * vuex v3.1.3 * (c) 2020 Evan You * @license MIT */ function n(t){var e=Number(t.version.split(".")[0]);if(e>=2)t.mixin({beforeCreate:r});else{var n=t.prototype._init;t.prototype._init=function(t){void 0===t&&(t={}),t.init=t.init?[r].concat(t.init):r,n.call(this,t)}}function r(){var t=this.$options;t.store?this.$store="function"===typeof t.store?t.store():t.store:t.parent&&t.parent.$store&&(this.$store=t.parent.$store)}}var r="undefined"!==typeof window?window:"undefined"!==typeof t?t:{},o=r.__VUE_DEVTOOLS_GLOBAL_HOOK__;function i(t){o&&(t._devtoolHook=o,o.emit("vuex:init",t),o.on("vuex:travel-to-state",(function(e){t.replaceState(e)})),t.subscribe((function(t,e){o.emit("vuex:mutation",t,e)})))}function a(t,e){Object.keys(t).forEach((function(n){return e(t[n],n)}))}function s(t){return null!==t&&"object"===typeof t}function c(t){return t&&"function"===typeof t.then}function u(t,e){return function(){return t(e)}}var f=function(t,e){this.runtime=e,this._children=Object.create(null),this._rawModule=t;var n=t.state;this.state=("function"===typeof n?n():n)||{}},l={namespaced:{configurable:!0}};l.namespaced.get=function(){return!!this._rawModule.namespaced},f.prototype.addChild=function(t,e){this._children[t]=e},f.prototype.removeChild=function(t){delete this._children[t]},f.prototype.getChild=function(t){return this._children[t]},f.prototype.update=function(t){this._rawModule.namespaced=t.namespaced,t.actions&&(this._rawModule.actions=t.actions),t.mutations&&(this._rawModule.mutations=t.mutations),t.getters&&(this._rawModule.getters=t.getters)},f.prototype.forEachChild=function(t){a(this._children,t)},f.prototype.forEachGetter=function(t){this._rawModule.getters&&a(this._rawModule.getters,t)},f.prototype.forEachAction=function(t){this._rawModule.actions&&a(this._rawModule.actions,t)},f.prototype.forEachMutation=function(t){this._rawModule.mutations&&a(this._rawModule.mutations,t)},Object.defineProperties(f.prototype,l);var p=function(t){this.register([],t,!1)};function d(t,e,n){if(e.update(n),n.modules)for(var r in n.modules){if(!e.getChild(r))return void 0;d(t.concat(r),e.getChild(r),n.modules[r])}}p.prototype.get=function(t){return t.reduce((function(t,e){return t.getChild(e)}),this.root)},p.prototype.getNamespace=function(t){var e=this.root;return t.reduce((function(t,n){return e=e.getChild(n),t+(e.namespaced?n+"/":"")}),"")},p.prototype.update=function(t){d([],this.root,t)},p.prototype.register=function(t,e,n){var r=this;void 0===n&&(n=!0);var o=new f(e,n);if(0===t.length)this.root=o;else{var i=this.get(t.slice(0,-1));i.addChild(t[t.length-1],o)}e.modules&&a(e.modules,(function(e,o){r.register(t.concat(o),e,n)}))},p.prototype.unregister=function(t){var e=this.get(t.slice(0,-1)),n=t[t.length-1];e.getChild(n).runtime&&e.removeChild(n)};var h;var v=function(t){var e=this;void 0===t&&(t={}),!h&&"undefined"!==typeof window&&window.Vue&&$(window.Vue);var n=t.plugins;void 0===n&&(n=[]);var r=t.strict;void 0===r&&(r=!1),this._committing=!1,this._actions=Object.create(null),this._actionSubscribers=[],this._mutations=Object.create(null),this._wrappedGetters=Object.create(null),this._modules=new p(t),this._modulesNamespaceMap=Object.create(null),this._subscribers=[],this._watcherVM=new h,this._makeLocalGettersCache=Object.create(null);var o=this,a=this,s=a.dispatch,c=a.commit;this.dispatch=function(t,e){return s.call(o,t,e)},this.commit=function(t,e,n){return c.call(o,t,e,n)},this.strict=r;var u=this._modules.root.state;_(this,u,[],this._modules.root),b(this,u),n.forEach((function(t){return t(e)}));var f=void 0!==t.devtools?t.devtools:h.config.devtools;f&&i(this)},y={state:{configurable:!0}};function m(t,e){return e.indexOf(t)<0&&e.push(t),function(){var n=e.indexOf(t);n>-1&&e.splice(n,1)}}function g(t,e){t._actions=Object.create(null),t._mutations=Object.create(null),t._wrappedGetters=Object.create(null),t._modulesNamespaceMap=Object.create(null);var n=t.state;_(t,n,[],t._modules.root,!0),b(t,n,e)}function b(t,e,n){var r=t._vm;t.getters={},t._makeLocalGettersCache=Object.create(null);var o=t._wrappedGetters,i={};a(o,(function(e,n){i[n]=u(e,t),Object.defineProperty(t.getters,n,{get:function(){return t._vm[n]},enumerable:!0})}));var s=h.config.silent;h.config.silent=!0,t._vm=new h({data:{$$state:e},computed:i}),h.config.silent=s,t.strict&&k(t),r&&(n&&t._withCommit((function(){r._data.$$state=null})),h.nextTick((function(){return r.$destroy()})))}function _(t,e,n,r,o){var i=!n.length,a=t._modules.getNamespace(n);if(r.namespaced&&(t._modulesNamespaceMap[a],t._modulesNamespaceMap[a]=r),!i&&!o){var s=j(e,n.slice(0,-1)),c=n[n.length-1];t._withCommit((function(){h.set(s,c,r.state)}))}var u=r.context=w(t,a,n);r.forEachMutation((function(e,n){var r=a+n;C(t,r,e,u)})),r.forEachAction((function(e,n){var r=e.root?n:a+n,o=e.handler||e;O(t,r,o,u)})),r.forEachGetter((function(e,n){var r=a+n;A(t,r,e,u)})),r.forEachChild((function(r,i){_(t,e,n.concat(i),r,o)}))}function w(t,e,n){var r=""===e,o={dispatch:r?t.dispatch:function(n,r,o){var i=S(n,r,o),a=i.payload,s=i.options,c=i.type;return s&&s.root||(c=e+c),t.dispatch(c,a)},commit:r?t.commit:function(n,r,o){var i=S(n,r,o),a=i.payload,s=i.options,c=i.type;s&&s.root||(c=e+c),t.commit(c,a,s)}};return Object.defineProperties(o,{getters:{get:r?function(){return t.getters}:function(){return x(t,e)}},state:{get:function(){return j(t.state,n)}}}),o}function x(t,e){if(!t._makeLocalGettersCache[e]){var n={},r=e.length;Object.keys(t.getters).forEach((function(o){if(o.slice(0,r)===e){var i=o.slice(r);Object.defineProperty(n,i,{get:function(){return t.getters[o]},enumerable:!0})}})),t._makeLocalGettersCache[e]=n}return t._makeLocalGettersCache[e]}function C(t,e,n,r){var o=t._mutations[e]||(t._mutations[e]=[]);o.push((function(e){n.call(t,r.state,e)}))}function O(t,e,n,r){var o=t._actions[e]||(t._actions[e]=[]);o.push((function(e){var o=n.call(t,{dispatch:r.dispatch,commit:r.commit,getters:r.getters,state:r.state,rootGetters:t.getters,rootState:t.state},e);return c(o)||(o=Promise.resolve(o)),t._devtoolHook?o.catch((function(e){throw t._devtoolHook.emit("vuex:error",e),e})):o}))}function A(t,e,n,r){t._wrappedGetters[e]||(t._wrappedGetters[e]=function(t){return n(r.state,r.getters,t.state,t.getters)})}function k(t){t._vm.$watch((function(){return this._data.$$state}),(function(){0}),{deep:!0,sync:!0})}function j(t,e){return e.reduce((function(t,e){return t[e]}),t)}function S(t,e,n){return s(t)&&t.type&&(n=e,e=t,t=t.type),{type:t,payload:e,options:n}}function $(t){h&&t===h||(h=t,n(h))}y.state.get=function(){return this._vm._data.$$state},y.state.set=function(t){0},v.prototype.commit=function(t,e,n){var r=this,o=S(t,e,n),i=o.type,a=o.payload,s=(o.options,{type:i,payload:a}),c=this._mutations[i];c&&(this._withCommit((function(){c.forEach((function(t){t(a)}))})),this._subscribers.slice().forEach((function(t){return t(s,r.state)})))},v.prototype.dispatch=function(t,e){var n=this,r=S(t,e),o=r.type,i=r.payload,a={type:o,payload:i},s=this._actions[o];if(s){try{this._actionSubscribers.slice().filter((function(t){return t.before})).forEach((function(t){return t.before(a,n.state)}))}catch(u){0}var c=s.length>1?Promise.all(s.map((function(t){return t(i)}))):s[0](i);return c.then((function(t){try{n._actionSubscribers.filter((function(t){return t.after})).forEach((function(t){return t.after(a,n.state)}))}catch(u){0}return t}))}},v.prototype.subscribe=function(t){return m(t,this._subscribers)},v.prototype.subscribeAction=function(t){var e="function"===typeof t?{before:t}:t;return m(e,this._actionSubscribers)},v.prototype.watch=function(t,e,n){var r=this;return this._watcherVM.$watch((function(){return t(r.state,r.getters)}),e,n)},v.prototype.replaceState=function(t){var e=this;this._withCommit((function(){e._vm._data.$$state=t}))},v.prototype.registerModule=function(t,e,n){void 0===n&&(n={}),"string"===typeof t&&(t=[t]),this._modules.register(t,e),_(this,this.state,t,this._modules.get(t),n.preserveState),b(this,this.state)},v.prototype.unregisterModule=function(t){var e=this;"string"===typeof t&&(t=[t]),this._modules.unregister(t),this._withCommit((function(){var n=j(e.state,t.slice(0,-1));h.delete(n,t[t.length-1])})),g(this)},v.prototype.hotUpdate=function(t){this._modules.update(t),g(this,!0)},v.prototype._withCommit=function(t){var e=this._committing;this._committing=!0,t(),this._committing=e},Object.defineProperties(v.prototype,y);var E=L((function(t,e){var n={};return M(e).forEach((function(e){var r=e.key,o=e.val;n[r]=function(){var e=this.$store.state,n=this.$store.getters;if(t){var r=D(this.$store,"mapState",t);if(!r)return;e=r.context.state,n=r.context.getters}return"function"===typeof o?o.call(this,e,n):e[o]},n[r].vuex=!0})),n})),T=L((function(t,e){var n={};return M(e).forEach((function(e){var r=e.key,o=e.val;n[r]=function(){var e=[],n=arguments.length;while(n--)e[n]=arguments[n];var r=this.$store.commit;if(t){var i=D(this.$store,"mapMutations",t);if(!i)return;r=i.context.commit}return"function"===typeof o?o.apply(this,[r].concat(e)):r.apply(this.$store,[o].concat(e))}})),n})),P=L((function(t,e){var n={};return M(e).forEach((function(e){var r=e.key,o=e.val;o=t+o,n[r]=function(){if(!t||D(this.$store,"mapGetters",t))return this.$store.getters[o]},n[r].vuex=!0})),n})),R=L((function(t,e){var n={};return M(e).forEach((function(e){var r=e.key,o=e.val;n[r]=function(){var e=[],n=arguments.length;while(n--)e[n]=arguments[n];var r=this.$store.dispatch;if(t){var i=D(this.$store,"mapActions",t);if(!i)return;r=i.context.dispatch}return"function"===typeof o?o.apply(this,[r].concat(e)):r.apply(this.$store,[o].concat(e))}})),n})),I=function(t){return{mapState:E.bind(null,t),mapGetters:P.bind(null,t),mapMutations:T.bind(null,t),mapActions:R.bind(null,t)}};function M(t){return N(t)?Array.isArray(t)?t.map((function(t){return{key:t,val:t}})):Object.keys(t).map((function(e){return{key:e,val:t[e]}})):[]}function N(t){return Array.isArray(t)||s(t)}function L(t){return function(e,n){return"string"!==typeof e?(n=e,e=""):"/"!==e.charAt(e.length-1)&&(e+="/"),t(e,n)}}function D(t,e,n){var r=t._modulesNamespaceMap[n];return r}var F={Store:v,install:$,version:"3.1.3",mapState:E,mapMutations:T,mapGetters:P,mapActions:R,createNamespacedHelpers:I};e["a"]=F}).call(this,n("c8ba"))},"30b5":function(t,e,n){"use strict";var r=n("c532");function o(t){return encodeURIComponent(t).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,"+").replace(/%5B/gi,"[").replace(/%5D/gi,"]")}t.exports=function(t,e,n){if(!e)return t;var i;if(n)i=n(e);else if(r.isURLSearchParams(e))i=e.toString();else{var a=[];r.forEach(e,(function(t,e){null!==t&&"undefined"!==typeof t&&(r.isArray(t)?e+="[]":t=[t],r.forEach(t,(function(t){r.isDate(t)?t=t.toISOString():r.isObject(t)&&(t=JSON.stringify(t)),a.push(o(e)+"="+o(t))})))})),i=a.join("&")}if(i){var s=t.indexOf("#");-1!==s&&(t=t.slice(0,s)),t+=(-1===t.indexOf("?")?"?":"&")+i}return t}},"342f":function(t,e,n){var r=n("d066");t.exports=r("navigator","userAgent")||""},"35a1":function(t,e,n){var r=n("f5df"),o=n("3f8c"),i=n("b622"),a=i("iterator");t.exports=function(t){if(void 0!=t)return t[a]||t["@@iterator"]||o[r(t)]}},"37e8":function(t,e,n){var r=n("83ab"),o=n("9bf2"),i=n("825a"),a=n("df75");t.exports=r?Object.defineProperties:function(t,e){i(t);var n,r=a(e),s=r.length,c=0;while(s>c)o.f(t,n=r[c++],e[n]);return t}},"387f":function(t,e,n){"use strict";t.exports=function(t,e,n,r,o){return t.config=e,n&&(t.code=n),t.request=r,t.response=o,t.isAxiosError=!0,t.toJSON=function(){return{message:this.message,name:this.name,description:this.description,number:this.number,fileName:this.fileName,lineNumber:this.lineNumber,columnNumber:this.columnNumber,stack:this.stack,config:this.config,code:this.code}},t}},3934:function(t,e,n){"use strict";var r=n("c532");t.exports=r.isStandardBrowserEnv()?function(){var t,e=/(msie|trident)/i.test(navigator.userAgent),n=document.createElement("a");function o(t){var r=t;return e&&(n.setAttribute("href",r),r=n.href),n.setAttribute("href",r),{href:n.href,protocol:n.protocol?n.protocol.replace(/:$/,""):"",host:n.host,search:n.search?n.search.replace(/^\?/,""):"",hash:n.hash?n.hash.replace(/^#/,""):"",hostname:n.hostname,port:n.port,pathname:"/"===n.pathname.charAt(0)?n.pathname:"/"+n.pathname}}return t=o(window.location.href),function(e){var n=r.isString(e)?o(e):e;return n.protocol===t.protocol&&n.host===t.host}}():function(){return function(){return!0}}()},"3bbe":function(t,e,n){var r=n("861d");t.exports=function(t){if(!r(t)&&null!==t)throw TypeError("Can't set "+String(t)+" as a prototype");return t}},"3f8c":function(t,e){t.exports={}},"428f":function(t,e,n){var r=n("da84");t.exports=r},4362:function(t,e,n){e.nextTick=function(t){var e=Array.prototype.slice.call(arguments);e.shift(),setTimeout((function(){t.apply(null,e)}),0)},e.platform=e.arch=e.execPath=e.title="browser",e.pid=1,e.browser=!0,e.env={},e.argv=[],e.binding=function(t){throw new Error("No such module. (Possibly not yet loaded)")},function(){var t,r="/";e.cwd=function(){return r},e.chdir=function(e){t||(t=n("df7c")),r=t.resolve(e,r)}}(),e.exit=e.kill=e.umask=e.dlopen=e.uptime=e.memoryUsage=e.uvCounters=function(){},e.features={}},"44ad":function(t,e,n){var r=n("d039"),o=n("c6b6"),i="".split;t.exports=r((function(){return!Object("z").propertyIsEnumerable(0)}))?function(t){return"String"==o(t)?i.call(t,""):Object(t)}:Object},"44d2":function(t,e,n){var r=n("b622"),o=n("7c73"),i=n("9bf2"),a=r("unscopables"),s=Array.prototype;void 0==s[a]&&i.f(s,a,{configurable:!0,value:o(null)}),t.exports=function(t){s[a][t]=!0}},"44de":function(t,e,n){var r=n("da84");t.exports=function(t,e){var n=r.console;n&&n.error&&(1===arguments.length?n.error(t):n.error(t,e))}},"467f":function(t,e,n){"use strict";var r=n("2d83");t.exports=function(t,e,n){var o=n.config.validateStatus;!o||o(n.status)?t(n):e(r("Request failed with status code "+n.status,n.config,null,n.request,n))}},4840:function(t,e,n){var r=n("825a"),o=n("1c0b"),i=n("b622"),a=i("species");t.exports=function(t,e){var n,i=r(t).constructor;return void 0===i||void 0==(n=r(i)[a])?e:o(n)}},4930:function(t,e,n){var r=n("d039");t.exports=!!Object.getOwnPropertySymbols&&!r((function(){return!String(Symbol())}))},"4a7b":function(t,e,n){"use strict";var r=n("c532");t.exports=function(t,e){e=e||{};var n={},o=["url","method","params","data"],i=["headers","auth","proxy"],a=["baseURL","url","transformRequest","transformResponse","paramsSerializer","timeout","withCredentials","adapter","responseType","xsrfCookieName","xsrfHeaderName","onUploadProgress","onDownloadProgress","maxContentLength","validateStatus","maxRedirects","httpAgent","httpsAgent","cancelToken","socketPath"];r.forEach(o,(function(t){"undefined"!==typeof e[t]&&(n[t]=e[t])})),r.forEach(i,(function(o){r.isObject(e[o])?n[o]=r.deepMerge(t[o],e[o]):"undefined"!==typeof e[o]?n[o]=e[o]:r.isObject(t[o])?n[o]=r.deepMerge(t[o]):"undefined"!==typeof t[o]&&(n[o]=t[o])})),r.forEach(a,(function(r){"undefined"!==typeof e[r]?n[r]=e[r]:"undefined"!==typeof t[r]&&(n[r]=t[r])}));var s=o.concat(i).concat(a),c=Object.keys(e).filter((function(t){return-1===s.indexOf(t)}));return r.forEach(c,(function(r){"undefined"!==typeof e[r]?n[r]=e[r]:"undefined"!==typeof t[r]&&(n[r]=t[r])})),n}},"4d64":function(t,e,n){var r=n("fc6a"),o=n("50c4"),i=n("23cb"),a=function(t){return function(e,n,a){var s,c=r(e),u=o(c.length),f=i(a,u);if(t&&n!=n){while(u>f)if(s=c[f++],s!=s)return!0}else for(;u>f;f++)if((t||f in c)&&c[f]===n)return t||f||0;return!t&&-1}};t.exports={includes:a(!0),indexOf:a(!1)}},"50c4":function(t,e,n){var r=n("a691"),o=Math.min;t.exports=function(t){return t>0?o(r(t),9007199254740991):0}},5135:function(t,e){var n={}.hasOwnProperty;t.exports=function(t,e){return n.call(t,e)}},5270:function(t,e,n){"use strict";var r=n("c532"),o=n("c401"),i=n("2e67"),a=n("2444");function s(t){t.cancelToken&&t.cancelToken.throwIfRequested()}t.exports=function(t){s(t),t.headers=t.headers||{},t.data=o(t.data,t.headers,t.transformRequest),t.headers=r.merge(t.headers.common||{},t.headers[t.method]||{},t.headers),r.forEach(["delete","get","head","post","put","patch","common"],(function(e){delete t.headers[e]}));var e=t.adapter||a.adapter;return e(t).then((function(e){return s(t),e.data=o(e.data,e.headers,t.transformResponse),e}),(function(e){return i(e)||(s(t),e&&e.response&&(e.response.data=o(e.response.data,e.response.headers,t.transformResponse))),Promise.reject(e)}))}},5692:function(t,e,n){var r=n("c430"),o=n("c6cd");(t.exports=function(t,e){return o[t]||(o[t]=void 0!==e?e:{})})("versions",[]).push({version:"3.6.4",mode:r?"pure":"global",copyright:"© 2020 Denis Pushkarev (zloirock.ru)"})},"56ef":function(t,e,n){var r=n("d066"),o=n("241c"),i=n("7418"),a=n("825a");t.exports=r("Reflect","ownKeys")||function(t){var e=o.f(a(t)),n=i.f;return n?e.concat(n(t)):e}},"5c6c":function(t,e){t.exports=function(t,e){return{enumerable:!(1&t),configurable:!(2&t),writable:!(4&t),value:e}}},"60da":function(t,e,n){"use strict";var r=n("83ab"),o=n("d039"),i=n("df75"),a=n("7418"),s=n("d1e7"),c=n("7b0b"),u=n("44ad"),f=Object.assign,l=Object.defineProperty;t.exports=!f||o((function(){if(r&&1!==f({b:1},f(l({},"a",{enumerable:!0,get:function(){l(this,"b",{value:3,enumerable:!1})}}),{b:2})).b)return!0;var t={},e={},n=Symbol(),o="abcdefghijklmnopqrst";return t[n]=7,o.split("").forEach((function(t){e[t]=t})),7!=f({},t)[n]||i(f({},e)).join("")!=o}))?function(t,e){var n=c(t),o=arguments.length,f=1,l=a.f,p=s.f;while(o>f){var d,h=u(arguments[f++]),v=l?i(h).concat(l(h)):i(h),y=v.length,m=0;while(y>m)d=v[m++],r&&!p.call(h,d)||(n[d]=h[d])}return n}:f},"69f3":function(t,e,n){var r,o,i,a=n("7f9a"),s=n("da84"),c=n("861d"),u=n("9112"),f=n("5135"),l=n("f772"),p=n("d012"),d=s.WeakMap,h=function(t){return i(t)?o(t):r(t,{})},v=function(t){return function(e){var n;if(!c(e)||(n=o(e)).type!==t)throw TypeError("Incompatible receiver, "+t+" required");return n}};if(a){var y=new d,m=y.get,g=y.has,b=y.set;r=function(t,e){return b.call(y,t,e),e},o=function(t){return m.call(y,t)||{}},i=function(t){return g.call(y,t)}}else{var _=l("state");p[_]=!0,r=function(t,e){return u(t,_,e),e},o=function(t){return f(t,_)?t[_]:{}},i=function(t){return f(t,_)}}t.exports={set:r,get:o,has:i,enforce:h,getterFor:v}},"6eeb":function(t,e,n){var r=n("da84"),o=n("9112"),i=n("5135"),a=n("ce4e"),s=n("8925"),c=n("69f3"),u=c.get,f=c.enforce,l=String(String).split("String");(t.exports=function(t,e,n,s){var c=!!s&&!!s.unsafe,u=!!s&&!!s.enumerable,p=!!s&&!!s.noTargetGet;"function"==typeof n&&("string"!=typeof e||i(n,"name")||o(n,"name",e),f(n).source=l.join("string"==typeof e?e:"")),t!==r?(c?!p&&t[e]&&(u=!0):delete t[e],u?t[e]=n:o(t,e,n)):u?t[e]=n:a(e,n)})(Function.prototype,"toString",(function(){return"function"==typeof this&&u(this).source||s(this)}))},7418:function(t,e){e.f=Object.getOwnPropertySymbols},7839:function(t,e){t.exports=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"]},"7a77":function(t,e,n){"use strict";function r(t){this.message=t}r.prototype.toString=function(){return"Cancel"+(this.message?": "+this.message:"")},r.prototype.__CANCEL__=!0,t.exports=r},"7aac":function(t,e,n){"use strict";var r=n("c532");t.exports=r.isStandardBrowserEnv()?function(){return{write:function(t,e,n,o,i,a){var s=[];s.push(t+"="+encodeURIComponent(e)),r.isNumber(n)&&s.push("expires="+new Date(n).toGMTString()),r.isString(o)&&s.push("path="+o),r.isString(i)&&s.push("domain="+i),!0===a&&s.push("secure"),document.cookie=s.join("; ")},read:function(t){var e=document.cookie.match(new RegExp("(^|;\\s*)("+t+")=([^;]*)"));return e?decodeURIComponent(e[3]):null},remove:function(t){this.write(t,"",Date.now()-864e5)}}}():function(){return{write:function(){},read:function(){return null},remove:function(){}}}()},"7b0b":function(t,e,n){var r=n("1d80");t.exports=function(t){return Object(r(t))}},"7c73":function(t,e,n){var r,o=n("825a"),i=n("37e8"),a=n("7839"),s=n("d012"),c=n("1be4"),u=n("cc12"),f=n("f772"),l=">",p="<",d="prototype",h="script",v=f("IE_PROTO"),y=function(){},m=function(t){return p+h+l+t+p+"/"+h+l},g=function(t){t.write(m("")),t.close();var e=t.parentWindow.Object;return t=null,e},b=function(){var t,e=u("iframe"),n="java"+h+":";return e.style.display="none",c.appendChild(e),e.src=String(n),t=e.contentWindow.document,t.open(),t.write(m("document.F=Object")),t.close(),t.F},_=function(){try{r=document.domain&&new ActiveXObject("htmlfile")}catch(e){}_=r?g(r):b();var t=a.length;while(t--)delete _[d][a[t]];return _()};s[v]=!0,t.exports=Object.create||function(t,e){var n;return null!==t?(y[d]=o(t),n=new y,y[d]=null,n[v]=t):n=_(),void 0===e?n:i(n,e)}},"7dd0":function(t,e,n){"use strict";var r=n("23e7"),o=n("9ed3"),i=n("e163"),a=n("d2bb"),s=n("d44e"),c=n("9112"),u=n("6eeb"),f=n("b622"),l=n("c430"),p=n("3f8c"),d=n("ae93"),h=d.IteratorPrototype,v=d.BUGGY_SAFARI_ITERATORS,y=f("iterator"),m="keys",g="values",b="entries",_=function(){return this};t.exports=function(t,e,n,f,d,w,x){o(n,e,f);var C,O,A,k=function(t){if(t===d&&T)return T;if(!v&&t in $)return $[t];switch(t){case m:return function(){return new n(this,t)};case g:return function(){return new n(this,t)};case b:return function(){return new n(this,t)}}return function(){return new n(this)}},j=e+" Iterator",S=!1,$=t.prototype,E=$[y]||$["@@iterator"]||d&&$[d],T=!v&&E||k(d),P="Array"==e&&$.entries||E;if(P&&(C=i(P.call(new t)),h!==Object.prototype&&C.next&&(l||i(C)===h||(a?a(C,h):"function"!=typeof C[y]&&c(C,y,_)),s(C,j,!0,!0),l&&(p[j]=_))),d==g&&E&&E.name!==g&&(S=!0,T=function(){return E.call(this)}),l&&!x||$[y]===T||c($,y,T),p[e]=T,d)if(O={values:k(g),keys:w?T:k(m),entries:k(b)},x)for(A in O)(v||S||!(A in $))&&u($,A,O[A]);else r({target:e,proto:!0,forced:v||S},O);return O}},"7f9a":function(t,e,n){var r=n("da84"),o=n("8925"),i=r.WeakMap;t.exports="function"===typeof i&&/native code/.test(o(i))},"825a":function(t,e,n){var r=n("861d");t.exports=function(t){if(!r(t))throw TypeError(String(t)+" is not an object");return t}},"83ab":function(t,e,n){var r=n("d039");t.exports=!r((function(){return 7!=Object.defineProperty({},1,{get:function(){return 7}})[1]}))},"83b9":function(t,e,n){"use strict";var r=n("d925"),o=n("e683");t.exports=function(t,e){return t&&!r(e)?o(t,e):e}},"861d":function(t,e){t.exports=function(t){return"object"===typeof t?null!==t:"function"===typeof t}},8925:function(t,e,n){var r=n("c6cd"),o=Function.toString;"function"!=typeof r.inspectSource&&(r.inspectSource=function(t){return o.call(t)}),t.exports=r.inspectSource},"8c4f":function(t,e,n){"use strict"; /*! * vue-router v3.1.6 * (c) 2020 Evan You * @license MIT */function r(t,e){0}function o(t){return Object.prototype.toString.call(t).indexOf("Error")>-1}function i(t,e){return e instanceof t||e&&(e.name===t.name||e._name===t._name)}function a(t,e){for(var n in e)t[n]=e[n];return t}var s={name:"RouterView",functional:!0,props:{name:{type:String,default:"default"}},render:function(t,e){var n=e.props,r=e.children,o=e.parent,i=e.data;i.routerView=!0;var s=o.$createElement,u=n.name,f=o.$route,l=o._routerViewCache||(o._routerViewCache={}),p=0,d=!1;while(o&&o._routerRoot!==o){var h=o.$vnode?o.$vnode.data:{};h.routerView&&p++,h.keepAlive&&o._directInactive&&o._inactive&&(d=!0),o=o.$parent}if(i.routerViewDepth=p,d){var v=l[u],y=v&&v.component;return y?(v.configProps&&c(y,i,v.route,v.configProps),s(y,i,r)):s()}var m=f.matched[p],g=m&&m.components[u];if(!m||!g)return l[u]=null,s();l[u]={component:g},i.registerRouteInstance=function(t,e){var n=m.instances[u];(e&&n!==t||!e&&n===t)&&(m.instances[u]=e)},(i.hook||(i.hook={})).prepatch=function(t,e){m.instances[u]=e.componentInstance},i.hook.init=function(t){t.data.keepAlive&&t.componentInstance&&t.componentInstance!==m.instances[u]&&(m.instances[u]=t.componentInstance)};var b=m.props&&m.props[u];return b&&(a(l[u],{route:f,configProps:b}),c(g,i,f,b)),s(g,i,r)}};function c(t,e,n,r){var o=e.props=u(n,r);if(o){o=e.props=a({},o);var i=e.attrs=e.attrs||{};for(var s in o)t.props&&s in t.props||(i[s]=o[s],delete o[s])}}function u(t,e){switch(typeof e){case"undefined":return;case"object":return e;case"function":return e(t);case"boolean":return e?t.params:void 0;default:0}}var f=/[!'()*]/g,l=function(t){return"%"+t.charCodeAt(0).toString(16)},p=/%2C/g,d=function(t){return encodeURIComponent(t).replace(f,l).replace(p,",")},h=decodeURIComponent;function v(t,e,n){void 0===e&&(e={});var r,o=n||y;try{r=o(t||"")}catch(a){r={}}for(var i in e)r[i]=e[i];return r}function y(t){var e={};return t=t.trim().replace(/^(\?|#|&)/,""),t?(t.split("&").forEach((function(t){var n=t.replace(/\+/g," ").split("="),r=h(n.shift()),o=n.length>0?h(n.join("=")):null;void 0===e[r]?e[r]=o:Array.isArray(e[r])?e[r].push(o):e[r]=[e[r],o]})),e):e}function m(t){var e=t?Object.keys(t).map((function(e){var n=t[e];if(void 0===n)return"";if(null===n)return d(e);if(Array.isArray(n)){var r=[];return n.forEach((function(t){void 0!==t&&(null===t?r.push(d(e)):r.push(d(e)+"="+d(t)))})),r.join("&")}return d(e)+"="+d(n)})).filter((function(t){return t.length>0})).join("&"):null;return e?"?"+e:""}var g=/\/?$/;function b(t,e,n,r){var o=r&&r.options.stringifyQuery,i=e.query||{};try{i=_(i)}catch(s){}var a={name:e.name||t&&t.name,meta:t&&t.meta||{},path:e.path||"/",hash:e.hash||"",query:i,params:e.params||{},fullPath:C(e,o),matched:t?x(t):[]};return n&&(a.redirectedFrom=C(n,o)),Object.freeze(a)}function _(t){if(Array.isArray(t))return t.map(_);if(t&&"object"===typeof t){var e={};for(var n in t)e[n]=_(t[n]);return e}return t}var w=b(null,{path:"/"});function x(t){var e=[];while(t)e.unshift(t),t=t.parent;return e}function C(t,e){var n=t.path,r=t.query;void 0===r&&(r={});var o=t.hash;void 0===o&&(o="");var i=e||m;return(n||"/")+i(r)+o}function O(t,e){return e===w?t===e:!!e&&(t.path&&e.path?t.path.replace(g,"")===e.path.replace(g,"")&&t.hash===e.hash&&A(t.query,e.query):!(!t.name||!e.name)&&(t.name===e.name&&t.hash===e.hash&&A(t.query,e.query)&&A(t.params,e.params)))}function A(t,e){if(void 0===t&&(t={}),void 0===e&&(e={}),!t||!e)return t===e;var n=Object.keys(t),r=Object.keys(e);return n.length===r.length&&n.every((function(n){var r=t[n],o=e[n];return"object"===typeof r&&"object"===typeof o?A(r,o):String(r)===String(o)}))}function k(t,e){return 0===t.path.replace(g,"/").indexOf(e.path.replace(g,"/"))&&(!e.hash||t.hash===e.hash)&&j(t.query,e.query)}function j(t,e){for(var n in e)if(!(n in t))return!1;return!0}function S(t,e,n){var r=t.charAt(0);if("/"===r)return t;if("?"===r||"#"===r)return e+t;var o=e.split("/");n&&o[o.length-1]||o.pop();for(var i=t.replace(/^\//,"").split("/"),a=0;a=0&&(e=t.slice(r),t=t.slice(0,r));var o=t.indexOf("?");return o>=0&&(n=t.slice(o+1),t=t.slice(0,o)),{path:t,query:n,hash:e}}function E(t){return t.replace(/\/\//g,"/")}var T=Array.isArray||function(t){return"[object Array]"==Object.prototype.toString.call(t)},P=Y,R=D,I=F,M=q,N=J,L=new RegExp(["(\\\\.)","([\\/.])?(?:(?:\\:(\\w+)(?:\\(((?:\\\\.|[^\\\\()])+)\\))?|\\(((?:\\\\.|[^\\\\()])+)\\))([+*?])?|(\\*))"].join("|"),"g");function D(t,e){var n,r=[],o=0,i=0,a="",s=e&&e.delimiter||"/";while(null!=(n=L.exec(t))){var c=n[0],u=n[1],f=n.index;if(a+=t.slice(i,f),i=f+c.length,u)a+=u[1];else{var l=t[i],p=n[2],d=n[3],h=n[4],v=n[5],y=n[6],m=n[7];a&&(r.push(a),a="");var g=null!=p&&null!=l&&l!==p,b="+"===y||"*"===y,_="?"===y||"*"===y,w=n[2]||s,x=h||v;r.push({name:d||o++,prefix:p||"",delimiter:w,optional:_,repeat:b,partial:g,asterisk:!!m,pattern:x?V(x):m?".*":"[^"+H(w)+"]+?"})}}return i1||!w.length)return 0===w.length?t():t("span",{},w)}if("a"===this.tag)_.on=g,_.attrs={href:c};else{var x=st(this.$slots.default);if(x){x.isStatic=!1;var C=x.data=a({},x.data);for(var A in C.on=C.on||{},C.on){var j=C.on[A];A in g&&(C.on[A]=Array.isArray(j)?j:[j])}for(var S in g)S in C.on?C.on[S].push(g[S]):C.on[S]=m;var $=x.data.attrs=a({},x.data.attrs);$.href=c}else _.on=g}return t(this.tag,_,this.$slots.default)}};function at(t){if(!(t.metaKey||t.altKey||t.ctrlKey||t.shiftKey)&&!t.defaultPrevented&&(void 0===t.button||0===t.button)){if(t.currentTarget&&t.currentTarget.getAttribute){var e=t.currentTarget.getAttribute("target");if(/\b_blank\b/i.test(e))return}return t.preventDefault&&t.preventDefault(),!0}}function st(t){if(t)for(var e,n=0;n-1&&(s.params[p]=n.params[p]);return s.path=Z(u.path,s.params,'named route "'+c+'"'),f(u,s,a)}if(s.path){s.params={};for(var d=0;d=t.length?n():t[o]?e(t[o],(function(){r(o+1)})):r(o+1)};r(0)}function Dt(t){return function(e,n,r){var i=!1,a=0,s=null;Ft(t,(function(t,e,n,c){if("function"===typeof t&&void 0===t.cid){i=!0,a++;var u,f=Ht((function(e){qt(e)&&(e=e.default),t.resolved="function"===typeof e?e:et.extend(e),n.components[c]=e,a--,a<=0&&r()})),l=Ht((function(t){var e="Failed to resolve async component "+c+": "+t;s||(s=o(t)?t:new Error(e),r(s))}));try{u=t(f,l)}catch(d){l(d)}if(u)if("function"===typeof u.then)u.then(f,l);else{var p=u.component;p&&"function"===typeof p.then&&p.then(f,l)}}})),i||r()}}function Ft(t,e){return Ut(t.map((function(t){return Object.keys(t.components).map((function(n){return e(t.components[n],t.instances[n],t,n)}))})))}function Ut(t){return Array.prototype.concat.apply([],t)}var Bt="function"===typeof Symbol&&"symbol"===typeof Symbol.toStringTag;function qt(t){return t.__esModule||Bt&&"Module"===t[Symbol.toStringTag]}function Ht(t){var e=!1;return function(){var n=[],r=arguments.length;while(r--)n[r]=arguments[r];if(!e)return e=!0,t.apply(this,n)}}var Vt=function(t){function e(e){t.call(this),this.name=this._name="NavigationDuplicated",this.message='Navigating to current location ("'+e.fullPath+'") is not allowed',Object.defineProperty(this,"stack",{value:(new t).stack,writable:!0,configurable:!0})}return t&&(e.__proto__=t),e.prototype=Object.create(t&&t.prototype),e.prototype.constructor=e,e}(Error);Vt._name="NavigationDuplicated";var zt=function(t,e){this.router=t,this.base=Gt(e),this.current=w,this.pending=null,this.ready=!1,this.readyCbs=[],this.readyErrorCbs=[],this.errorCbs=[]};function Gt(t){if(!t)if(ut){var e=document.querySelector("base");t=e&&e.getAttribute("href")||"/",t=t.replace(/^https?:\/\/[^\/]+/,"")}else t="/";return"/"!==t.charAt(0)&&(t="/"+t),t.replace(/\/$/,"")}function Wt(t,e){var n,r=Math.max(t.length,e.length);for(n=0;n-1?decodeURI(t.slice(0,r))+t.slice(r):decodeURI(t)}else t=decodeURI(t.slice(0,n))+t.slice(n);return t}function ce(t){var e=window.location.href,n=e.indexOf("#"),r=n>=0?e.slice(0,n):e;return r+"#"+t}function ue(t){It?Mt(ce(t)):window.location.hash=t}function fe(t){It?Nt(ce(t)):window.location.replace(ce(t))}var le=function(t){function e(e,n){t.call(this,e,n),this.stack=[],this.index=-1}return t&&(e.__proto__=t),e.prototype=Object.create(t&&t.prototype),e.prototype.constructor=e,e.prototype.push=function(t,e,n){var r=this;this.transitionTo(t,(function(t){r.stack=r.stack.slice(0,r.index+1).concat(t),r.index++,e&&e(t)}),n)},e.prototype.replace=function(t,e,n){var r=this;this.transitionTo(t,(function(t){r.stack=r.stack.slice(0,r.index).concat(t),e&&e(t)}),n)},e.prototype.go=function(t){var e=this,n=this.index+t;if(!(n<0||n>=this.stack.length)){var r=this.stack[n];this.confirmTransition(r,(function(){e.index=n,e.updateRoute(r)}),(function(t){i(Vt,t)&&(e.index=n)}))}},e.prototype.getCurrentLocation=function(){var t=this.stack[this.stack.length-1];return t?t.fullPath:"/"},e.prototype.ensureURL=function(){},e}(zt),pe=function(t){void 0===t&&(t={}),this.app=null,this.apps=[],this.options=t,this.beforeHooks=[],this.resolveHooks=[],this.afterHooks=[],this.matcher=ht(t.routes||[],this);var e=t.mode||"hash";switch(this.fallback="history"===e&&!It&&!1!==t.fallback,this.fallback&&(e="hash"),ut||(e="abstract"),this.mode=e,e){case"history":this.history=new ne(this,t.base);break;case"hash":this.history=new oe(this,t.base,this.fallback);break;case"abstract":this.history=new le(this,t.base);break;default:0}},de={currentRoute:{configurable:!0}};function he(t,e){return t.push(e),function(){var n=t.indexOf(e);n>-1&&t.splice(n,1)}}function ve(t,e,n){var r="hash"===n?"#"+e:e;return t?E(t+"/"+r):r}pe.prototype.match=function(t,e,n){return this.matcher.match(t,e,n)},de.currentRoute.get=function(){return this.history&&this.history.current},pe.prototype.init=function(t){var e=this;if(this.apps.push(t),t.$once("hook:destroyed",(function(){var n=e.apps.indexOf(t);n>-1&&e.apps.splice(n,1),e.app===t&&(e.app=e.apps[0]||null)})),!this.app){this.app=t;var n=this.history;if(n instanceof ne)n.transitionTo(n.getCurrentLocation());else if(n instanceof oe){var r=function(){n.setupListeners()};n.transitionTo(n.getCurrentLocation(),r,r)}n.listen((function(t){e.apps.forEach((function(e){e._route=t}))}))}},pe.prototype.beforeEach=function(t){return he(this.beforeHooks,t)},pe.prototype.beforeResolve=function(t){return he(this.resolveHooks,t)},pe.prototype.afterEach=function(t){return he(this.afterHooks,t)},pe.prototype.onReady=function(t,e){this.history.onReady(t,e)},pe.prototype.onError=function(t){this.history.onError(t)},pe.prototype.push=function(t,e,n){var r=this;if(!e&&!n&&"undefined"!==typeof Promise)return new Promise((function(e,n){r.history.push(t,e,n)}));this.history.push(t,e,n)},pe.prototype.replace=function(t,e,n){var r=this;if(!e&&!n&&"undefined"!==typeof Promise)return new Promise((function(e,n){r.history.replace(t,e,n)}));this.history.replace(t,e,n)},pe.prototype.go=function(t){this.history.go(t)},pe.prototype.back=function(){this.go(-1)},pe.prototype.forward=function(){this.go(1)},pe.prototype.getMatchedComponents=function(t){var e=t?t.matched?t:this.resolve(t).route:this.currentRoute;return e?[].concat.apply([],e.matched.map((function(t){return Object.keys(t.components).map((function(e){return t.components[e]}))}))):[]},pe.prototype.resolve=function(t,e,n){e=e||this.history.current;var r=tt(t,e,n,this),o=this.match(r,e),i=o.redirectedFrom||o.fullPath,a=this.history.base,s=ve(a,i,this.mode);return{location:r,route:o,href:s,normalizedTo:r,resolved:o}},pe.prototype.addRoutes=function(t){this.matcher.addRoutes(t),this.history.current!==w&&this.history.transitionTo(this.history.getCurrentLocation())},Object.defineProperties(pe.prototype,de),pe.install=ct,pe.version="3.1.6",ut&&window.Vue&&window.Vue.use(pe),e["a"]=pe},"8df4":function(t,e,n){"use strict";var r=n("7a77");function o(t){if("function"!==typeof t)throw new TypeError("executor must be a function.");var e;this.promise=new Promise((function(t){e=t}));var n=this;t((function(t){n.reason||(n.reason=new r(t),e(n.reason))}))}o.prototype.throwIfRequested=function(){if(this.reason)throw this.reason},o.source=function(){var t,e=new o((function(e){t=e}));return{token:e,cancel:t}},t.exports=o},"90e3":function(t,e){var n=0,r=Math.random();t.exports=function(t){return"Symbol("+String(void 0===t?"":t)+")_"+(++n+r).toString(36)}},9112:function(t,e,n){var r=n("83ab"),o=n("9bf2"),i=n("5c6c");t.exports=r?function(t,e,n){return o.f(t,e,i(1,n))}:function(t,e,n){return t[e]=n,t}},"94ca":function(t,e,n){var r=n("d039"),o=/#|\.prototype\./,i=function(t,e){var n=s[a(t)];return n==u||n!=c&&("function"==typeof e?r(e):!!e)},a=i.normalize=function(t){return String(t).replace(o,".").toLowerCase()},s=i.data={},c=i.NATIVE="N",u=i.POLYFILL="P";t.exports=i},"9bdd":function(t,e,n){var r=n("825a");t.exports=function(t,e,n,o){try{return o?e(r(n)[0],n[1]):e(n)}catch(a){var i=t["return"];throw void 0!==i&&r(i.call(t)),a}}},"9bf2":function(t,e,n){var r=n("83ab"),o=n("0cfb"),i=n("825a"),a=n("c04e"),s=Object.defineProperty;e.f=r?s:function(t,e,n){if(i(t),e=a(e,!0),i(n),o)try{return s(t,e,n)}catch(r){}if("get"in n||"set"in n)throw TypeError("Accessors not supported");return"value"in n&&(t[e]=n.value),t}},"9ed3":function(t,e,n){"use strict";var r=n("ae93").IteratorPrototype,o=n("7c73"),i=n("5c6c"),a=n("d44e"),s=n("3f8c"),c=function(){return this};t.exports=function(t,e,n){var u=e+" Iterator";return t.prototype=o(r,{next:i(1,n)}),a(t,u,!1,!0),s[u]=c,t}},a691:function(t,e){var n=Math.ceil,r=Math.floor;t.exports=function(t){return isNaN(t=+t)?0:(t>0?r:n)(t)}},a79d:function(t,e,n){"use strict";var r=n("23e7"),o=n("c430"),i=n("fea9"),a=n("d039"),s=n("d066"),c=n("4840"),u=n("cdf9"),f=n("6eeb"),l=!!i&&a((function(){i.prototype["finally"].call({then:function(){}},(function(){}))}));r({target:"Promise",proto:!0,real:!0,forced:l},{finally:function(t){var e=c(this,s("Promise")),n="function"==typeof t;return this.then(n?function(n){return u(e,t()).then((function(){return n}))}:t,n?function(n){return u(e,t()).then((function(){throw n}))}:t)}}),o||"function"!=typeof i||i.prototype["finally"]||f(i.prototype,"finally",s("Promise").prototype["finally"])},ae93:function(t,e,n){"use strict";var r,o,i,a=n("e163"),s=n("9112"),c=n("5135"),u=n("b622"),f=n("c430"),l=u("iterator"),p=!1,d=function(){return this};[].keys&&(i=[].keys(),"next"in i?(o=a(a(i)),o!==Object.prototype&&(r=o)):p=!0),void 0==r&&(r={}),f||c(r,l)||s(r,l,d),t.exports={IteratorPrototype:r,BUGGY_SAFARI_ITERATORS:p}},b041:function(t,e,n){"use strict";var r=n("00ee"),o=n("f5df");t.exports=r?{}.toString:function(){return"[object "+o(this)+"]"}},b50d:function(t,e,n){"use strict";var r=n("c532"),o=n("467f"),i=n("30b5"),a=n("83b9"),s=n("c345"),c=n("3934"),u=n("2d83");t.exports=function(t){return new Promise((function(e,f){var l=t.data,p=t.headers;r.isFormData(l)&&delete p["Content-Type"];var d=new XMLHttpRequest;if(t.auth){var h=t.auth.username||"",v=t.auth.password||"";p.Authorization="Basic "+btoa(h+":"+v)}var y=a(t.baseURL,t.url);if(d.open(t.method.toUpperCase(),i(y,t.params,t.paramsSerializer),!0),d.timeout=t.timeout,d.onreadystatechange=function(){if(d&&4===d.readyState&&(0!==d.status||d.responseURL&&0===d.responseURL.indexOf("file:"))){var n="getAllResponseHeaders"in d?s(d.getAllResponseHeaders()):null,r=t.responseType&&"text"!==t.responseType?d.response:d.responseText,i={data:r,status:d.status,statusText:d.statusText,headers:n,config:t,request:d};o(e,f,i),d=null}},d.onabort=function(){d&&(f(u("Request aborted",t,"ECONNABORTED",d)),d=null)},d.onerror=function(){f(u("Network Error",t,null,d)),d=null},d.ontimeout=function(){var e="timeout of "+t.timeout+"ms exceeded";t.timeoutErrorMessage&&(e=t.timeoutErrorMessage),f(u(e,t,"ECONNABORTED",d)),d=null},r.isStandardBrowserEnv()){var m=n("7aac"),g=(t.withCredentials||c(y))&&t.xsrfCookieName?m.read(t.xsrfCookieName):void 0;g&&(p[t.xsrfHeaderName]=g)}if("setRequestHeader"in d&&r.forEach(p,(function(t,e){"undefined"===typeof l&&"content-type"===e.toLowerCase()?delete p[e]:d.setRequestHeader(e,t)})),r.isUndefined(t.withCredentials)||(d.withCredentials=!!t.withCredentials),t.responseType)try{d.responseType=t.responseType}catch(b){if("json"!==t.responseType)throw b}"function"===typeof t.onDownloadProgress&&d.addEventListener("progress",t.onDownloadProgress),"function"===typeof t.onUploadProgress&&d.upload&&d.upload.addEventListener("progress",t.onUploadProgress),t.cancelToken&&t.cancelToken.promise.then((function(t){d&&(d.abort(),f(t),d=null)})),void 0===l&&(l=null),d.send(l)}))}},b575:function(t,e,n){var r,o,i,a,s,c,u,f,l=n("da84"),p=n("06cf").f,d=n("c6b6"),h=n("2cf4").set,v=n("1cdc"),y=l.MutationObserver||l.WebKitMutationObserver,m=l.process,g=l.Promise,b="process"==d(m),_=p(l,"queueMicrotask"),w=_&&_.value;w||(r=function(){var t,e;b&&(t=m.domain)&&t.exit();while(o){e=o.fn,o=o.next;try{e()}catch(n){throw o?a():i=void 0,n}}i=void 0,t&&t.enter()},b?a=function(){m.nextTick(r)}:y&&!v?(s=!0,c=document.createTextNode(""),new y(r).observe(c,{characterData:!0}),a=function(){c.data=s=!s}):g&&g.resolve?(u=g.resolve(void 0),f=u.then,a=function(){f.call(u,r)}):a=function(){h.call(l,r)}),t.exports=w||function(t){var e={fn:t,next:void 0};i&&(i.next=e),o||(o=e,a()),i=e}},b622:function(t,e,n){var r=n("da84"),o=n("5692"),i=n("5135"),a=n("90e3"),s=n("4930"),c=n("fdbf"),u=o("wks"),f=r.Symbol,l=c?f:f&&f.withoutSetter||a;t.exports=function(t){return i(u,t)||(s&&i(f,t)?u[t]=f[t]:u[t]=l("Symbol."+t)),u[t]}},bc3a:function(t,e,n){t.exports=n("cee4")},c04e:function(t,e,n){var r=n("861d");t.exports=function(t,e){if(!r(t))return t;var n,o;if(e&&"function"==typeof(n=t.toString)&&!r(o=n.call(t)))return o;if("function"==typeof(n=t.valueOf)&&!r(o=n.call(t)))return o;if(!e&&"function"==typeof(n=t.toString)&&!r(o=n.call(t)))return o;throw TypeError("Can't convert object to primitive value")}},c345:function(t,e,n){"use strict";var r=n("c532"),o=["age","authorization","content-length","content-type","etag","expires","from","host","if-modified-since","if-unmodified-since","last-modified","location","max-forwards","proxy-authorization","referer","retry-after","user-agent"];t.exports=function(t){var e,n,i,a={};return t?(r.forEach(t.split("\n"),(function(t){if(i=t.indexOf(":"),e=r.trim(t.substr(0,i)).toLowerCase(),n=r.trim(t.substr(i+1)),e){if(a[e]&&o.indexOf(e)>=0)return;a[e]="set-cookie"===e?(a[e]?a[e]:[]).concat([n]):a[e]?a[e]+", "+n:n}})),a):a}},c401:function(t,e,n){"use strict";var r=n("c532");t.exports=function(t,e,n){return r.forEach(n,(function(n){t=n(t,e)})),t}},c430:function(t,e){t.exports=!1},c532:function(t,e,n){"use strict";var r=n("1d2b"),o=Object.prototype.toString;function i(t){return"[object Array]"===o.call(t)}function a(t){return"undefined"===typeof t}function s(t){return null!==t&&!a(t)&&null!==t.constructor&&!a(t.constructor)&&"function"===typeof t.constructor.isBuffer&&t.constructor.isBuffer(t)}function c(t){return"[object ArrayBuffer]"===o.call(t)}function u(t){return"undefined"!==typeof FormData&&t instanceof FormData}function f(t){var e;return e="undefined"!==typeof ArrayBuffer&&ArrayBuffer.isView?ArrayBuffer.isView(t):t&&t.buffer&&t.buffer instanceof ArrayBuffer,e}function l(t){return"string"===typeof t}function p(t){return"number"===typeof t}function d(t){return null!==t&&"object"===typeof t}function h(t){return"[object Date]"===o.call(t)}function v(t){return"[object File]"===o.call(t)}function y(t){return"[object Blob]"===o.call(t)}function m(t){return"[object Function]"===o.call(t)}function g(t){return d(t)&&m(t.pipe)}function b(t){return"undefined"!==typeof URLSearchParams&&t instanceof URLSearchParams}function _(t){return t.replace(/^\s*/,"").replace(/\s*$/,"")}function w(){return("undefined"===typeof navigator||"ReactNative"!==navigator.product&&"NativeScript"!==navigator.product&&"NS"!==navigator.product)&&("undefined"!==typeof window&&"undefined"!==typeof document)}function x(t,e){if(null!==t&&"undefined"!==typeof t)if("object"!==typeof t&&(t=[t]),i(t))for(var n=0,r=t.length;nc)r(s,n=e[c++])&&(~i(u,n)||u.push(n));return u}},cc12:function(t,e,n){var r=n("da84"),o=n("861d"),i=r.document,a=o(i)&&o(i.createElement);t.exports=function(t){return a?i.createElement(t):{}}},cca6:function(t,e,n){var r=n("23e7"),o=n("60da");r({target:"Object",stat:!0,forced:Object.assign!==o},{assign:o})},cdf9:function(t,e,n){var r=n("825a"),o=n("861d"),i=n("f069");t.exports=function(t,e){if(r(t),o(e)&&e.constructor===t)return e;var n=i.f(t),a=n.resolve;return a(e),n.promise}},ce4e:function(t,e,n){var r=n("da84"),o=n("9112");t.exports=function(t,e){try{o(r,t,e)}catch(n){r[t]=e}return e}},cee4:function(t,e,n){"use strict";var r=n("c532"),o=n("1d2b"),i=n("0a06"),a=n("4a7b"),s=n("2444");function c(t){var e=new i(t),n=o(i.prototype.request,e);return r.extend(n,i.prototype,e),r.extend(n,e),n}var u=c(s);u.Axios=i,u.create=function(t){return c(a(u.defaults,t))},u.Cancel=n("7a77"),u.CancelToken=n("8df4"),u.isCancel=n("2e67"),u.all=function(t){return Promise.all(t)},u.spread=n("0df6"),t.exports=u,t.exports.default=u},d012:function(t,e){t.exports={}},d039:function(t,e){t.exports=function(t){try{return!!t()}catch(e){return!0}}},d066:function(t,e,n){var r=n("428f"),o=n("da84"),i=function(t){return"function"==typeof t?t:void 0};t.exports=function(t,e){return arguments.length<2?i(r[t])||i(o[t]):r[t]&&r[t][e]||o[t]&&o[t][e]}},d1e7:function(t,e,n){"use strict";var r={}.propertyIsEnumerable,o=Object.getOwnPropertyDescriptor,i=o&&!r.call({1:2},1);e.f=i?function(t){var e=o(this,t);return!!e&&e.enumerable}:r},d2bb:function(t,e,n){var r=n("825a"),o=n("3bbe");t.exports=Object.setPrototypeOf||("__proto__"in{}?function(){var t,e=!1,n={};try{t=Object.getOwnPropertyDescriptor(Object.prototype,"__proto__").set,t.call(n,[]),e=n instanceof Array}catch(i){}return function(n,i){return r(n),o(i),e?t.call(n,i):n.__proto__=i,n}}():void 0)},d3b7:function(t,e,n){var r=n("00ee"),o=n("6eeb"),i=n("b041");r||o(Object.prototype,"toString",i,{unsafe:!0})},d44e:function(t,e,n){var r=n("9bf2").f,o=n("5135"),i=n("b622"),a=i("toStringTag");t.exports=function(t,e,n){t&&!o(t=n?t:t.prototype,a)&&r(t,a,{configurable:!0,value:e})}},d925:function(t,e,n){"use strict";t.exports=function(t){return/^([a-z][a-z\d\+\-\.]*:)?\/\//i.test(t)}},da84:function(t,e,n){(function(e){var n=function(t){return t&&t.Math==Math&&t};t.exports=n("object"==typeof globalThis&&globalThis)||n("object"==typeof window&&window)||n("object"==typeof self&&self)||n("object"==typeof e&&e)||Function("return this")()}).call(this,n("c8ba"))},df75:function(t,e,n){var r=n("ca84"),o=n("7839");t.exports=Object.keys||function(t){return r(t,o)}},df7c:function(t,e,n){(function(t){function n(t,e){for(var n=0,r=t.length-1;r>=0;r--){var o=t[r];"."===o?t.splice(r,1):".."===o?(t.splice(r,1),n++):n&&(t.splice(r,1),n--)}if(e)for(;n--;n)t.unshift("..");return t}function r(t){"string"!==typeof t&&(t+="");var e,n=0,r=-1,o=!0;for(e=t.length-1;e>=0;--e)if(47===t.charCodeAt(e)){if(!o){n=e+1;break}}else-1===r&&(o=!1,r=e+1);return-1===r?"":t.slice(n,r)}function o(t,e){if(t.filter)return t.filter(e);for(var n=[],r=0;r=-1&&!r;i--){var a=i>=0?arguments[i]:t.cwd();if("string"!==typeof a)throw new TypeError("Arguments to path.resolve must be strings");a&&(e=a+"/"+e,r="/"===a.charAt(0))}return e=n(o(e.split("/"),(function(t){return!!t})),!r).join("/"),(r?"/":"")+e||"."},e.normalize=function(t){var r=e.isAbsolute(t),a="/"===i(t,-1);return t=n(o(t.split("/"),(function(t){return!!t})),!r).join("/"),t||r||(t="."),t&&a&&(t+="/"),(r?"/":"")+t},e.isAbsolute=function(t){return"/"===t.charAt(0)},e.join=function(){var t=Array.prototype.slice.call(arguments,0);return e.normalize(o(t,(function(t,e){if("string"!==typeof t)throw new TypeError("Arguments to path.join must be strings");return t})).join("/"))},e.relative=function(t,n){function r(t){for(var e=0;e=0;n--)if(""!==t[n])break;return e>n?[]:t.slice(e,n-e+1)}t=e.resolve(t).substr(1),n=e.resolve(n).substr(1);for(var o=r(t.split("/")),i=r(n.split("/")),a=Math.min(o.length,i.length),s=a,c=0;c=1;--i)if(e=t.charCodeAt(i),47===e){if(!o){r=i;break}}else o=!1;return-1===r?n?"/":".":n&&1===r?"/":t.slice(0,r)},e.basename=function(t,e){var n=r(t);return e&&n.substr(-1*e.length)===e&&(n=n.substr(0,n.length-e.length)),n},e.extname=function(t){"string"!==typeof t&&(t+="");for(var e=-1,n=0,r=-1,o=!0,i=0,a=t.length-1;a>=0;--a){var s=t.charCodeAt(a);if(47!==s)-1===r&&(o=!1,r=a+1),46===s?-1===e?e=a:1!==i&&(i=1):-1!==e&&(i=-1);else if(!o){n=a+1;break}}return-1===e||-1===r||0===i||1===i&&e===r-1&&e===n+1?"":t.slice(e,r)};var i="b"==="ab".substr(-1)?function(t,e,n){return t.substr(e,n)}:function(t,e,n){return e<0&&(e=t.length+e),t.substr(e,n)}}).call(this,n("4362"))},e163:function(t,e,n){var r=n("5135"),o=n("7b0b"),i=n("f772"),a=n("e177"),s=i("IE_PROTO"),c=Object.prototype;t.exports=a?Object.getPrototypeOf:function(t){return t=o(t),r(t,s)?t[s]:"function"==typeof t.constructor&&t instanceof t.constructor?t.constructor.prototype:t instanceof Object?c:null}},e177:function(t,e,n){var r=n("d039");t.exports=!r((function(){function t(){}return t.prototype.constructor=null,Object.getPrototypeOf(new t)!==t.prototype}))},e260:function(t,e,n){"use strict";var r=n("fc6a"),o=n("44d2"),i=n("3f8c"),a=n("69f3"),s=n("7dd0"),c="Array Iterator",u=a.set,f=a.getterFor(c);t.exports=s(Array,"Array",(function(t,e){u(this,{type:c,target:r(t),index:0,kind:e})}),(function(){var t=f(this),e=t.target,n=t.kind,r=t.index++;return!e||r>=e.length?(t.target=void 0,{value:void 0,done:!0}):"keys"==n?{value:r,done:!1}:"values"==n?{value:e[r],done:!1}:{value:[r,e[r]],done:!1}}),"values"),i.Arguments=i.Array,o("keys"),o("values"),o("entries")},e2cc:function(t,e,n){var r=n("6eeb");t.exports=function(t,e,n){for(var o in e)r(t,o,e[o],n);return t}},e667:function(t,e){t.exports=function(t){try{return{error:!1,value:t()}}catch(e){return{error:!0,value:e}}}},e683:function(t,e,n){"use strict";t.exports=function(t,e){return e?t.replace(/\/+$/,"")+"/"+e.replace(/^\/+/,""):t}},e6cf:function(t,e,n){"use strict";var r,o,i,a,s=n("23e7"),c=n("c430"),u=n("da84"),f=n("d066"),l=n("fea9"),p=n("6eeb"),d=n("e2cc"),h=n("d44e"),v=n("2626"),y=n("861d"),m=n("1c0b"),g=n("19aa"),b=n("c6b6"),_=n("8925"),w=n("2266"),x=n("1c7e"),C=n("4840"),O=n("2cf4").set,A=n("b575"),k=n("cdf9"),j=n("44de"),S=n("f069"),$=n("e667"),E=n("69f3"),T=n("94ca"),P=n("b622"),R=n("2d00"),I=P("species"),M="Promise",N=E.get,L=E.set,D=E.getterFor(M),F=l,U=u.TypeError,B=u.document,q=u.process,H=f("fetch"),V=S.f,z=V,G="process"==b(q),W=!!(B&&B.createEvent&&u.dispatchEvent),K="unhandledrejection",X="rejectionhandled",J=0,Y=1,Q=2,Z=1,tt=2,et=T(M,(function(){var t=_(F)!==String(F);if(!t){if(66===R)return!0;if(!G&&"function"!=typeof PromiseRejectionEvent)return!0}if(c&&!F.prototype["finally"])return!0;if(R>=51&&/native code/.test(F))return!1;var e=F.resolve(1),n=function(t){t((function(){}),(function(){}))},r=e.constructor={};return r[I]=n,!(e.then((function(){}))instanceof n)})),nt=et||!x((function(t){F.all(t)["catch"]((function(){}))})),rt=function(t){var e;return!(!y(t)||"function"!=typeof(e=t.then))&&e},ot=function(t,e,n){if(!e.notified){e.notified=!0;var r=e.reactions;A((function(){var o=e.value,i=e.state==Y,a=0;while(r.length>a){var s,c,u,f=r[a++],l=i?f.ok:f.fail,p=f.resolve,d=f.reject,h=f.domain;try{l?(i||(e.rejection===tt&&ct(t,e),e.rejection=Z),!0===l?s=o:(h&&h.enter(),s=l(o),h&&(h.exit(),u=!0)),s===f.promise?d(U("Promise-chain cycle")):(c=rt(s))?c.call(s,p,d):p(s)):d(o)}catch(v){h&&!u&&h.exit(),d(v)}}e.reactions=[],e.notified=!1,n&&!e.rejection&&at(t,e)}))}},it=function(t,e,n){var r,o;W?(r=B.createEvent("Event"),r.promise=e,r.reason=n,r.initEvent(t,!1,!0),u.dispatchEvent(r)):r={promise:e,reason:n},(o=u["on"+t])?o(r):t===K&&j("Unhandled promise rejection",n)},at=function(t,e){O.call(u,(function(){var n,r=e.value,o=st(e);if(o&&(n=$((function(){G?q.emit("unhandledRejection",r,t):it(K,t,r)})),e.rejection=G||st(e)?tt:Z,n.error))throw n.value}))},st=function(t){return t.rejection!==Z&&!t.parent},ct=function(t,e){O.call(u,(function(){G?q.emit("rejectionHandled",t):it(X,t,e.value)}))},ut=function(t,e,n,r){return function(o){t(e,n,o,r)}},ft=function(t,e,n,r){e.done||(e.done=!0,r&&(e=r),e.value=n,e.state=Q,ot(t,e,!0))},lt=function(t,e,n,r){if(!e.done){e.done=!0,r&&(e=r);try{if(t===n)throw U("Promise can't be resolved itself");var o=rt(n);o?A((function(){var r={done:!1};try{o.call(n,ut(lt,t,r,e),ut(ft,t,r,e))}catch(i){ft(t,r,i,e)}})):(e.value=n,e.state=Y,ot(t,e,!1))}catch(i){ft(t,{done:!1},i,e)}}};et&&(F=function(t){g(this,F,M),m(t),r.call(this);var e=N(this);try{t(ut(lt,this,e),ut(ft,this,e))}catch(n){ft(this,e,n)}},r=function(t){L(this,{type:M,done:!1,notified:!1,parent:!1,reactions:[],rejection:!1,state:J,value:void 0})},r.prototype=d(F.prototype,{then:function(t,e){var n=D(this),r=V(C(this,F));return r.ok="function"!=typeof t||t,r.fail="function"==typeof e&&e,r.domain=G?q.domain:void 0,n.parent=!0,n.reactions.push(r),n.state!=J&&ot(this,n,!1),r.promise},catch:function(t){return this.then(void 0,t)}}),o=function(){var t=new r,e=N(t);this.promise=t,this.resolve=ut(lt,t,e),this.reject=ut(ft,t,e)},S.f=V=function(t){return t===F||t===i?new o(t):z(t)},c||"function"!=typeof l||(a=l.prototype.then,p(l.prototype,"then",(function(t,e){var n=this;return new F((function(t,e){a.call(n,t,e)})).then(t,e)}),{unsafe:!0}),"function"==typeof H&&s({global:!0,enumerable:!0,forced:!0},{fetch:function(t){return k(F,H.apply(u,arguments))}}))),s({global:!0,wrap:!0,forced:et},{Promise:F}),h(F,M,!1,!0),v(M),i=f(M),s({target:M,stat:!0,forced:et},{reject:function(t){var e=V(this);return e.reject.call(void 0,t),e.promise}}),s({target:M,stat:!0,forced:c||et},{resolve:function(t){return k(c&&this===i?F:this,t)}}),s({target:M,stat:!0,forced:nt},{all:function(t){var e=this,n=V(e),r=n.resolve,o=n.reject,i=$((function(){var n=m(e.resolve),i=[],a=0,s=1;w(t,(function(t){var c=a++,u=!1;i.push(void 0),s++,n.call(e,t).then((function(t){u||(u=!0,i[c]=t,--s||r(i))}),o)})),--s||r(i)}));return i.error&&o(i.value),n.promise},race:function(t){var e=this,n=V(e),r=n.reject,o=$((function(){var o=m(e.resolve);w(t,(function(t){o.call(e,t).then(n.resolve,r)}))}));return o.error&&r(o.value),n.promise}})},e893:function(t,e,n){var r=n("5135"),o=n("56ef"),i=n("06cf"),a=n("9bf2");t.exports=function(t,e){for(var n=o(e),s=a.f,c=i.f,u=0;ubluebell_frontend
================================================ FILE: lesson80/bluebell/wait-for.sh ================================================ #!/bin/bash TIMEOUT=15 QUIET=0 ADDRS=() echoerr() { if [ "$QUIET" -ne 1 ]; then printf "%s\n" "$*" 1>&2; fi } usage() { exitcode="$1" cat << USAGE >&2 client: $cmdname host:port [host:port] [host:port] [-t timeout] [-- command args] -q | --quiet Do not output any status messages -t TIMEOUT | --timeout=timeout Timeout in seconds, zero for no timeout -- COMMAND ARGS Execute command with args after the test finishes USAGE exit "$exitcode" } wait_for() { results=() for addr in ${ADDRS[@]} do HOST=$(printf "%s\n" "$addr"| cut -d : -f 1) PORT=$(printf "%s\n" "$addr"| cut -d : -f 2) result=1 for i in `seq $TIMEOUT` ; do nc -z "$HOST" "$PORT" > /dev/null 2>&1 result=$? if [ $result -ne 0 ] ; then sleep 1 continue fi break done results=(${results[@]} $result) done num=${#results[@]} for result in ${results[@]} do if [ $result -eq 0 ] ; then num=`expr $num - 1` fi done if [ $num -eq 0 ] ; then if [ $# -gt 0 ] ; then exec "$@" fi exit 0 fi echo "Operation timed out" >&2 exit 1 } while [ $# -gt 0 ] do case "$1" in *:* ) ADDRS=(${ADDRS[@]} $1) shift 1 ;; -q | --quiet) QUIET=1 shift 1 ;; -t) TIMEOUT="$2" if [ "$TIMEOUT" = "" ]; then break; fi shift 2 ;; --timeout=*) TIMEOUT="${1#*=}" shift 1 ;; --) shift break ;; --help) usage 0 ;; *) echoerr "Unknown argument: $1" usage 1 ;; esac done if [ "${#ADDRS[@]}" -eq 0 ]; then echoerr "Error: you need to provide a host and port to test." usage 2 fi wait_for "$@" ================================================ FILE: mysql_demo/go.mod ================================================ module mysql_demo go 1.14 require github.com/go-sql-driver/mysql v1.5.0 ================================================ FILE: mysql_demo/go.sum ================================================ github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= ================================================ FILE: mysql_demo/main.go ================================================ package main import ( "database/sql" "fmt" _ "github.com/go-sql-driver/mysql" // init() ) var db *sql.DB func initMySQL() (err error) { // DSN:Data Source Name dsn := "root:root1234@tcp(127.0.0.1:13306)/sql_demo" // 去初始化全局的db对象而不是新声明一个db变量 db, err = sql.Open("mysql", dsn) if err != nil { panic(err) } // 做完错误检查之后,确保db不为nil // 尝试与数据库建立连接(校验dsn是否正确) err = db.Ping() if err != nil { fmt.Printf("connect to db failed, err:%v\n", err) return } // 数值需要业务具体情况来确定 //db.SetConnMaxLifetime(time.Second*10) db.SetMaxOpenConns(100) // 最大连接数 db.SetMaxIdleConns(10) // 最大空闲连接数 return } type user struct { id int age int name string } // 查询单条数据示例 func queryRowDemo() { sqlStr := "select id, name, age from user where id=?" var u user // 非常重要:确保QueryRow之后调用Scan方法,否则持有的数据库链接不会被释放 //row := db.QueryRow(sqlStr, 1) // 并没有对其做scan操作 err := db.QueryRow(sqlStr, 2).Scan(&u.id, &u.name, &u.age) if err != nil { fmt.Printf("scan failed, err:%v\n", err) return } fmt.Printf("id:%d name:%s age:%d\n", u.id, u.name, u.age) } // 查询多条数据示例 func queryMultiRowDemo() { sqlStr := "select id, name, age from user where id > ?" rows, err := db.Query(sqlStr, 0) if err != nil { fmt.Printf("query failed, err:%v\n", err) return } // 非常重要:关闭rows释放持有的数据库链接 defer rows.Close() // 循环读取结果集中的数据 for rows.Next() { var u user err := rows.Scan(&u.id, &u.name, &u.age) if err != nil { fmt.Printf("scan failed, err:%v\n", err) return } fmt.Printf("id:%d name:%s age:%d\n", u.id, u.name, u.age) } } // 插入数据 func insertRowDemo() { sqlStr := "insert into user(name, age) values (?,?)" ret, err := db.Exec(sqlStr, "七米", 29) if err != nil { fmt.Printf("insert failed, err:%v\n", err) return } var theID int64 theID, err = ret.LastInsertId() // 新插入数据的id if err != nil { fmt.Printf("get lastinsert ID failed, err:%v\n", err) return } fmt.Printf("insert success, the id is %d.\n", theID) } // 更新数据 func updateRowDemo() { sqlStr := "update user set age=? where id = ?" ret, err := db.Exec(sqlStr, 39, 3) if err != nil { fmt.Printf("update failed, err:%v\n", err) return } var n int64 n, err = ret.RowsAffected() // 操作影响的行数 if err != nil { fmt.Printf("get RowsAffected failed, err:%v\n", err) return } fmt.Printf("update success, affected rows:%d\n", n) } // 删除数据 func deleteRowDemo() { sqlStr := "delete from user where id = ?" ret, err := db.Exec(sqlStr, 3) if err != nil { fmt.Printf("delete failed, err:%v\n", err) return } var n int64 n, err = ret.RowsAffected() // 操作影响的行数 if err != nil { fmt.Printf("get RowsAffected failed, err:%v\n", err) return } fmt.Printf("delete success, affected rows:%d\n", n) } // 预处理查询示例 func prepareQueryDemo() { sqlStr := "select id, name, age from user where id > ?" stmt, err := db.Prepare(sqlStr) if err != nil { fmt.Printf("prepare failed, err:%v\n", err) return } defer stmt.Close() rows, err := stmt.Query(0) if err != nil { fmt.Printf("query failed, err:%v\n", err) return } defer rows.Close() // 循环读取结果集中的数据 for rows.Next() { var u user err := rows.Scan(&u.id, &u.name, &u.age) if err != nil { fmt.Printf("scan failed, err:%v\n", err) return } fmt.Printf("id:%d name:%s age:%d\n", u.id, u.name, u.age) } } // 预处理插入示例 func prepareInsertDemo() { sqlStr := "insert into user(name, age) values (?,?)" stmt, err := db.Prepare(sqlStr) if err != nil { fmt.Printf("prepare failed, err:%v\n", err) return } defer stmt.Close() _, err = stmt.Exec("小王子", 18) if err != nil { fmt.Printf("insert failed, err:%v\n", err) return } _, err = stmt.Exec("七米", 29) if err != nil { fmt.Printf("insert failed, err:%v\n", err) return } fmt.Println("insert success.") } // sql注入示例 func sqlInjectDemo(name string) { sqlStr := fmt.Sprintf("select id, name, age from user where name='%s'", name) fmt.Printf("SQL:%s\n", sqlStr) var u user err := db.QueryRow(sqlStr).Scan(&u.id, &u.name, &u.age) if err != nil { fmt.Printf("exec failed, err:%v\n", err) return } fmt.Printf("user:%#v\n", u) } // 事务操作示例 func transactionDemo() { tx, err := db.Begin() // 开启事务 if err != nil { if tx != nil { tx.Rollback() // 回滚 } fmt.Printf("begin trans failed, err:%v\n", err) return } sqlStr1 := "Update user set age=30 where id=?" ret1, err := tx.Exec(sqlStr1, 2) if err != nil { tx.Rollback() // 回滚 fmt.Printf("exec sql1 failed, err:%v\n", err) return } affRow1, err := ret1.RowsAffected() if err != nil { tx.Rollback() // 回滚 fmt.Printf("exec ret1.RowsAffected() failed, err:%v\n", err) return } sqlStr2 := "Update user set age=40 where id=?" ret2, err := tx.Exec(sqlStr2, 3) if err != nil { tx.Rollback() // 回滚 fmt.Printf("exec sql2 failed, err:%v\n", err) return } affRow2, err := ret2.RowsAffected() if err != nil { tx.Rollback() // 回滚 fmt.Printf("exec ret1.RowsAffected() failed, err:%v\n", err) return } // 当affRow1 == 1 && affRow2 == 1 fmt.Println(affRow1, affRow2) if affRow1 == 1 && affRow2 == 1 { fmt.Println("事务提交啦...") tx.Commit() // 提交事务 } else { tx.Rollback() fmt.Println("事务回滚啦...") } fmt.Println("exec trans success!") } func main() { if err := initMySQL(); err != nil { fmt.Printf("connect to db failed, err:%v\n", err) } // Close() 用来释放掉数据库连接相关的资源 defer db.Close() // 注意这行代码要写在上面err判断的下面 fmt.Println("connect to db success") // db.xx() 去使用数据库操作。。。 //queryRowDemo() //queryMultiRowDemo() //insertRowDemo() //updateRowDemo() //deleteRowDemo() //prepareQueryDemo() //prepareInsertDemo() //sqlInjectDemo("七米") //select id, name, age from user where name='xxx ' or 1=1#' //sqlInjectDemo("xxx ' or 1=1#") transactionDemo() fmt.Println("查询结束了...") } ================================================ FILE: ratelimit_demo/go.mod ================================================ module ratelimit_demo go 1.14 require ( github.com/gin-gonic/gin v1.6.3 github.com/juju/ratelimit v1.0.1 go.uber.org/atomic v1.7.0 // indirect go.uber.org/ratelimit v0.1.0 ) ================================================ FILE: ratelimit_demo/go.sum ================================================ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14= github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY= github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/juju/ratelimit v1.0.1 h1:+7AIFJVQ0EQgq/K9+0Krm7m530Du7tIz0METWzN0RgY= github.com/juju/ratelimit v1.0.1/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/ratelimit v0.1.0 h1:U2AruXqeTb4Eh9sYQSTrMhH8Cb7M0Ian2ibBOnBcnAw= go.uber.org/ratelimit v0.1.0/go.mod h1:2X8KaoNd1J0lZV+PxJk/5+DGbO/tpwLR1m++a7FnB/Y= golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= ================================================ FILE: ratelimit_demo/main.go ================================================ package main import ( "net/http" "time" "github.com/gin-gonic/gin" ratelimit2 "github.com/juju/ratelimit" ratelimit1 "go.uber.org/ratelimit" // 漏桶 // 令牌桶 ) func pingHandler(c *gin.Context) { c.String(http.StatusOK, "pong") } func heiHandler(c *gin.Context) { c.String(http.StatusOK, "ha") } // 基于漏桶的限流中间件1 func rateLimit1() func(ctx *gin.Context) { // 生成一个限流器, rl := ratelimit1.New(100) return func(c *gin.Context) { // 取水滴 if rl.Take().Sub(time.Now()) > 0 { //time.Sleep(rl.Take().Sub(time.Now())) // 需要等这么长时间下一滴水才会滴下来 c.String(http.StatusOK, "rate limit...") c.Abort() return } c.Next() } } // 基于令牌桶的限流中间件2 func rateLimit2(fillInterval time.Duration, cap int64) func(ctx *gin.Context) { rl := ratelimit2.NewBucket(fillInterval, cap) return func(c *gin.Context) { //rl.Take() // 这一次可以欠账 // rl.TakeAvailable // 有令牌的时候才会取出令牌 if rl.TakeAvailable(1) == 1 { // 此次取到令牌 c.Next() return } c.String(http.StatusOK, "rate limit....") c.Abort() } } func main() { r := gin.Default() r.GET("/ping", rateLimit1(), pingHandler) r.GET("/hei", rateLimit2(2*time.Second, 1), heiHandler) r.Run() } ================================================ FILE: redis_demo/go.mod ================================================ module redis_demo go 1.14 require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-redis/redis v6.15.9+incompatible // indirect github.com/go-redis/redis/v8 v8.3.3 github.com/kr/pretty v0.1.0 // indirect gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect ) ================================================ FILE: redis_demo/go.sum ================================================ github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg= github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= github.com/go-redis/redis/v8 v8.3.3 h1:e0CL9fsFDK92pkIJH2XAeS/NwO2VuIOAoJvI6yktZFk= github.com/go-redis/redis/v8 v8.3.3/go.mod h1:jszGxBCez8QA1HWSmQxJO9Y82kNibbUmeYhKWrBejTU= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.2 h1:8mVmC9kjFFmA8H4pKMUhcblgifdkOIXPvbhN1T36q1M= github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.10.3 h1:gph6h/qe9GSUw1NhH1gp+qb+h8rXD8Cy60Z32Qw3ELA= github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= go.opentelemetry.io/otel v0.13.0 h1:2isEnyzjjJZq6r2EKMsFj4TxiQiexsM04AVhwbR/oBA= go.opentelemetry.io/otel v0.13.0/go.mod h1:dlSNewoRYikTkotEnxdmuBHgzT+k/idJSfDv/FxEnOY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 h1:AeiKBIuRw3UomYXSbLy0Mc2dDLfdtbT/IVn4keq83P0= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0 h1:wBouT66WTYFXdxfVdz9sVWARVd/2vfGcmI45D2gj45M= golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 h1:DYfZAGf2WMFjMxbgTjaC+2HC7NkNAQs+6Q8b9WEB/F4= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= ================================================ FILE: redis_demo/main.go ================================================ package main // //import ( // "errors" // "fmt" // "sync" // "time" // // "github.com/go-redis/redis" //) // //// 声明一个全局的rdb变量 //var rdb *redis.Client // //// 初始化连接 //func initClient() (err error) { // rdb = redis.NewClient(&redis.Options{ // Addr: "localhost:6379", // Password: "", // no password set // DB: 0, // use default DB // PoolSize: 100, // 连接池大小 // }) // // _, err = rdb.Ping().Result() // return err //} // //func redisExample() { // err := rdb.Set("score", 100, 0).Err() // if err != nil { // fmt.Printf("set score failed, err:%v\n", err) // return // } // // val, err := rdb.Get("score").Result() // if err != nil { // fmt.Printf("get score failed, err:%v\n", err) // return // } // fmt.Println("score", val) // // val2, err := rdb.Get("name").Result() // if err == redis.Nil { // fmt.Println("name does not exist") // } else if err != nil { // fmt.Printf("get name failed, err:%v\n", err) // return // } else { // fmt.Println("name", val2) // } //} // //func hgetDemo() { // v, err := rdb.HGetAll("user").Result() // if err != nil { // // redis.Nil // // 其他错误 // fmt.Printf("hgetall failed, err:%v\n", err) // return // } // fmt.Println(v) // // v2 := rdb.HMGet("user", "name", "age").Val() // fmt.Println(v2) // // v3 := rdb.HGet("user", "age").Val() // fmt.Println(v3) // //} // //func redisExample2() { // zsetKey := "language_rank" // languages := []redis.Z{ // redis.Z{Score: 90.0, Member: "Golang"}, // redis.Z{Score: 98.0, Member: "Java"}, // redis.Z{Score: 95.0, Member: "Python"}, // redis.Z{Score: 97.0, Member: "JavaScript"}, // redis.Z{Score: 99.0, Member: "C/C++"}, // } // // ZADD // num, err := rdb.ZAdd(zsetKey, languages...).Result() // if err != nil { // fmt.Printf("zadd failed, err:%v\n", err) // return // } // fmt.Printf("zadd %d succ.\n", num) // // // 把Golang的分数加10 // newScore, err := rdb.ZIncrBy(zsetKey, 10.0, "Golang").Result() // if err != nil { // fmt.Printf("zincrby failed, err:%v\n", err) // return // } // fmt.Printf("Golang's score is %f now.\n", newScore) // // // 取分数最高的3个 // ret, err := rdb.ZRevRangeWithScores(zsetKey, 0, 2).Result() // if err != nil { // fmt.Printf("zrevrange failed, err:%v\n", err) // return // } // for _, z := range ret { // fmt.Println(z.Member, z.Score) // } // // // 取95~100分的 // op := redis.ZRangeBy{ // Min: "95", // Max: "100", // } // ret, err = rdb.ZRangeByScoreWithScores(zsetKey, op).Result() // if err != nil { // fmt.Printf("zrangebyscore failed, err:%v\n", err) // return // } // for _, z := range ret { // fmt.Println(z.Member, z.Score) // } //} // //func watchDemo() { // // 监视watch_count的值,并在值不变的前提下将其值+1 // key := "watch_count" // err := rdb.Watch(func(tx *redis.Tx) error { // n, err := tx.Get(key).Int() // if err != nil && err != redis.Nil { // return err // } // _, err = tx.TxPipelined(func(pipe redis.Pipeliner) error { // // 业务逻辑 // time.Sleep(time.Second * 5) // pipe.Set(key, n+1, 0) // return nil // }) // return err // }, key) // if err != nil { // fmt.Printf("tx exec failed, err:%v\n", err) // return // } // fmt.Println("tx exec success") //} // //// nilDemo 使用pipeline查询多个key时某个key有空值 //// 使用pipeline多次执行HGetAll 不会因为某个key没有值而出现err //func nilDemo() { // rdb.HMSet("nilDemo:k1", map[string]interface{}{"name": "q1mi", "score": 18}) // rdb.HMSet("nilDemo:k3", map[string]interface{}{"name": "q1mi", "score": 18}) // // pipeline := rdb.Pipeline() // pipeline.HGetAll("nilDemo:k1") // pipeline.HGetAll("nilDemo:k2") // pipeline.HGetAll("nilDemo:k3") // cmders, err := pipeline.Exec() // if err != nil { // fmt.Printf("nilDemo pipeline.Exec() failed, err:%v\n", err) // return // } // for _, cmder := range cmders { // fmt.Println(cmder == nil) // v, ok := cmder.(*redis.StringStringMapCmd) // if !ok { // fmt.Println("cmder.(*redis.StringStringMapCmd) failed") // continue // } // // 打印值 // fmt.Println(v.Val()) // } //} // //// nilDemo2 使用pipeline查询多个key时某个key有空值 //// 使用pipeline多次执行 Get 会因为某个key没有值而出现redis.Nil的err //func nilDemo2() { // rdb.Set("nilDemo2:k1", "v1", 0) // rdb.Set("nilDemo2:k3", "v3", 0) // // pipeline := rdb.Pipeline() // pipeline.Get("nilDemo2:k1") // pipeline.Get("nilDemo2:k2") // pipeline.Get("nilDemo2:k3") // cmders, err := pipeline.Exec() // if err != nil { // fmt.Printf("nilDemo2 pipeline.Exec() failed, err:%v\n", err) // return // } // for _, cmder := range cmders { // fmt.Println(cmder == nil) // v, ok := cmder.(*redis.StringCmd) // if !ok { // fmt.Println("cmder.(*redis.StringCmd) failed") // continue // } // // 打印值 // fmt.Println(v.Val()) // } //} // //// nilDemo3 使用pipeline查询多个key时某个key有空值 //// 使用pipeline多次执行 HGet 会因为某个key没有值而出现redis.Nil的err //func nilDemo3() { // rdb.HMSet("nilDemo3:k1", map[string]interface{}{"name": "q1mi", "score": 18}) // rdb.HMSet("nilDemo3:k3", map[string]interface{}{"name": "q1mi", "score": 18}) // // pipeline := rdb.Pipeline() // pipeline.HGet("nilDemo3:k1", "score") // pipeline.HGet("nilDemo3:k2", "score") // pipeline.HGet("nilDemo3:k3", "score") // cmders, err := pipeline.Exec() // if err != nil { // fmt.Printf("nilDemo3 pipeline.Exec() failed, err:%v\n", err) // return // } // for _, cmder := range cmders { // fmt.Println(cmder == nil) // v, ok := cmder.(*redis.StringCmd) // if !ok { // fmt.Println("cmder.(*redis.StringCmd) failed") // continue // } // // 打印值 // fmt.Println(v.Val()) // } //} // //// nilDemo4 使用pipeline查询多个key中某个没有值的field //// 使用pipeline多次执行 HGet 会因为某个field没有值而出现redis.Nil的err //func nilDemo4() { // rdb.HMSet("nilDemo4:k1", map[string]interface{}{"name": "q1mi", "score": 18}) // rdb.HMSet("nilDemo4:k2", map[string]interface{}{"name": "q1mi"}) // rdb.HMSet("nilDemo4:k3", map[string]interface{}{"name": "q1mi", "score": 18}) // // pipeline := rdb.Pipeline() // pipeline.HGet("nilDemo4:k1", "score") // pipeline.HGet("nilDemo4:k2", "score") // pipeline.HGet("nilDemo4:k3", "score") // cmders, err := pipeline.Exec() // if err != nil && err != redis.Nil { // fmt.Printf("nilDemo4 pipeline.Exec() failed, err:%v\n", err) // return // } // for _, cmder := range cmders { // fmt.Println(cmder == nil) // v, ok := cmder.(*redis.StringCmd) // if !ok { // fmt.Println("cmder.(*redis.StringCmd) failed") // continue // } // // 打印值 // fmt.Println(v.Val()) // } //} // //func main() { // if err := initClient(); err != nil { // fmt.Printf("init redis client failed, err:%v\n", err) // return // } // fmt.Println("connect redis success...") // // 程序退出时释放相关资源 // defer rdb.Close() // // //redisExample() // //hgetDemo() // //redisExample2() // //watchDemo() // //nilDemo() // //nilDemo2() // //nilDemo3() // //nilDemo4() // // transDemo() //} // //func transDemo() { // const routineCount = 100 // // increment := func(key string) error { // txf := func(tx *redis.Tx) error { // // 获得当前值或零值 // n, err := tx.Get(key).Int() // if err != nil && err != redis.Nil { // return err // } // // // 实际操作(乐观锁定中的本地操作) // n++ // // // 仅在监视的Key保持不变的情况下运行 // _, err = tx.TxPipelined(func(pipe redis.Pipeliner) error { // // pipe 处理错误情况 // pipe.Set(key, n, 0) // return nil // }) // return err // } // // for retries := routineCount; retries > 0; retries-- { // err := rdb.Watch(txf, key) // if err != redis.TxFailedErr { // return err // } // // 乐观锁丢失 // } // return errors.New("increment reached maximum number of retries") // } // // var wg sync.WaitGroup // wg.Add(routineCount) // for i := 0; i < routineCount; i++ { // go func() { // defer wg.Done() // // if err := increment("counter3"); err != nil { // fmt.Println("increment error:", err) // } // }() // } // wg.Wait() // // n, err := rdb.Get("counter3").Int() // fmt.Println("ended with", n, err) //} ================================================ FILE: redis_demo/new.go ================================================ package main import ( "context" "errors" "fmt" "sync" "time" "github.com/go-redis/redis/v8" // 注意导入的是新版本 ) var ( rdb *redis.Client ) // 初始化连接 func initClient() (err error) { rdb = redis.NewClient(&redis.Options{ Addr: "localhost:6379", Password: "", // no password set DB: 0, // use default DB PoolSize: 100, // 连接池大小 }) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() _, err = rdb.Ping(ctx).Result() return err } func V8Example() { ctx := context.Background() if err := initClient(); err != nil { return } err := rdb.Set(ctx, "key", "value", 0).Err() if err != nil { panic(err) } val, err := rdb.Get(ctx, "key").Result() if err != nil { panic(err) } fmt.Println("key", val) val2, err := rdb.Get(ctx, "key2").Result() if err == redis.Nil { fmt.Println("key2 does not exist") } else if err != nil { panic(err) } else { fmt.Println("key2", val2) } // Output: key value // key2 does not exist } func transDemo() { var ( maxRetries = 1000 routineCount = 10 ) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() // Increment 使用GET和SET命令以事务方式递增Key的值 increment := func(key string) error { // 事务函数 txf := func(tx *redis.Tx) error { // 获得key的当前值或零值 n, err := tx.Get(ctx, key).Int() if err != nil && err != redis.Nil { return err } // 实际的操作代码(乐观锁定中的本地操作) n++ // 操作仅在 Watch 的 Key 没发生变化的情况下提交 _, err = tx.TxPipelined(ctx, func(pipe redis.Pipeliner) error { pipe.Set(ctx, key, n, 0) return nil }) return err } // 最多重试 maxRetries 次 for i := 0; i < maxRetries; i++ { err := rdb.Watch(ctx, txf, key) if err == nil { // 成功 return nil } if err == redis.TxFailedErr { // 乐观锁丢失 重试 continue } // 返回其他的错误 return err } return errors.New("increment reached maximum number of retries") } // 模拟 routineCount 个并发同时去修改 counter3 的值 var wg sync.WaitGroup wg.Add(routineCount) for i := 0; i < routineCount; i++ { go func() { defer wg.Done() if err := increment("counter3"); err != nil { fmt.Println("increment error:", err) } }() } wg.Wait() n, err := rdb.Get(context.TODO(), "counter3").Int() fmt.Println("ended with", n, err) } func incrementWithOutTX(key string) error { n, err := rdb.Get(context.Background(), key).Int() if err != nil { return err } n++ rdb.Set(context.Background(), key, n, 0) return nil } func main() { if err := initClient(); err != nil { return } //ctx := context.Background() //iter := rdb.Scan(ctx, 0, "prefix*", 0).Iterator() //for iter.Next(ctx) { // err := rdb.Del(ctx, iter.Val()).Err() // if err != nil { // panic(err) // } //} //if err := iter.Err(); err != nil { // panic(err) //} transDemo() } ================================================ FILE: shutdown_demo/go.mod ================================================ module shutdown_demo go 1.14 require github.com/gin-gonic/gin v1.6.3 ================================================ FILE: shutdown_demo/go.sum ================================================ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14= github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY= github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= ================================================ FILE: shutdown_demo/main.go ================================================ // +build go1.8 package main import ( "context" "log" "net/http" "os" "os/signal" "syscall" "time" "github.com/gin-gonic/gin" ) func main() { router := gin.Default() router.GET("/", func(c *gin.Context) { time.Sleep(5 * time.Second) c.String(http.StatusOK, "Welcome Gin Server") }) srv := &http.Server{ Addr: ":8080", Handler: router, } go func() { // 开启一个goroutine启动服务 if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Fatalf("listen: %s\n", err) } }() // 等待中断信号来优雅地关闭服务器,为关闭服务器操作设置一个5秒的超时 quit := make(chan os.Signal, 1) // 创建一个接收信号的通道 // kill 默认会发送 syscall.SIGTERM 信号 // kill -2 发送 syscall.SIGINT 信号,我们常用的Ctrl+C就是触发系统SIGINT信号 // kill -9 发送 syscall.SIGKILL 信号,但是不能被捕获,所以不需要添加它 // signal.Notify把收到的 syscall.SIGINT或syscall.SIGTERM 信号转发给quit signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) // 此处不会阻塞 <-quit // 阻塞在此,当接收到上述两种信号时才会往下执行 log.Println("Shutdown Server ...") // 创建一个5秒超时的context ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() // 5秒内优雅关闭服务(将未处理完的请求处理完再关闭服务),超过5秒就超时退出 if err := srv.Shutdown(ctx); err != nil { log.Fatal("Server Shutdown: ", err) } log.Println("Server exiting") } ================================================ FILE: sqlx_demo/go.mod ================================================ module sqlx_demo go 1.14 require ( github.com/jmoiron/sqlx v1.2.1-0.20200615141059-0794cb1f47ee google.golang.org/appengine v1.6.6 // indirect ) ================================================ FILE: sqlx_demo/go.sum ================================================ github.com/go-sql-driver/mysql v1.4.0 h1:7LxgVwFb2hIQtMm87NdgAVfXjnt4OePseqT1tKx+opk= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA= github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= github.com/jmoiron/sqlx v1.2.1-0.20200615141059-0794cb1f47ee h1:59lyMGvZusByi7Rvctn8cxdVAjhiOnqCv3G5DrYApYQ= github.com/jmoiron/sqlx v1.2.1-0.20200615141059-0794cb1f47ee/go.mod h1:ClpsPFzLpSBl7MvJ+BhV0JHz4vmKRBarpvZ9644v9Oo= github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/mattn/go-sqlite3 v1.9.0 h1:pDRiWfl+++eC2FEFRy6jXmQlvp4Yh3z1MJKg4UeYM/4= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= ================================================ FILE: sqlx_demo/main.go ================================================ package main import ( "database/sql/driver" "errors" "fmt" "github.com/jmoiron/sqlx" _ "github.com/go-sql-driver/mysql" "strings" ) var db *sqlx.DB func initDB() (err error) { dsn := "root:root1234@tcp(127.0.0.1:13306)/sql_demo?charset=utf8mb4&parseTime=True" // 也可以使用MustConnect连接不成功就panic db, err = sqlx.Connect("mysql", dsn) if err != nil { fmt.Printf("connect DB failed, err:%v\n", err) return } db.SetMaxOpenConns(200) db.SetMaxIdleConns(10) return } type user struct { ID int `db:"id"` Age int `db:"age"` Name string `db:"name"` } func (u user) Value() (driver.Value, error) { return []interface{}{u.Name, u.Age}, nil } // BatchInsertUsers2 使用sqlx.In帮我们拼接语句和参数, 注意传入的参数是[]interface{} func BatchInsertUsers2(users []interface{}) error { query, args, _ := sqlx.In( "INSERT INTO user (name, age) VALUES (?), (?), (?)", users..., // 如果arg实现了 driver.Valuer, sqlx.In 会通过调用 Value()来展开它 ) fmt.Println(query) // 查看生成的querystring fmt.Println(args) // 查看生成的args _, err := db.Exec(query, args...) return err } // BatchInsertUsers3 使用NamedExec实现批量插入 func BatchInsertUsers3(users []*user) error { _, err := db.NamedExec("INSERT INTO user (name, age) VALUES (:name, :age)", users) return err } // 查询单条数据示例 func queryRowDemo() { sqlStr := "select id, name, age from user where id=?" var u user err := db.Get(&u, sqlStr, 1) if err != nil { fmt.Printf("get failed, err:%v\n", err) return } fmt.Printf("id:%d name:%s age:%d\n", u.ID, u.Name, u.Age) } // 查询多条数据示例 func queryMultiRowDemo() { sqlStr := "select id, name, age from user where id > ?" var users []user err := db.Select(&users, sqlStr, 0) if err != nil { fmt.Printf("query failed, err:%v\n", err) return } fmt.Printf("users:%#v\n", users) } // 插入数据 func insertRowDemo() { sqlStr := "insert into user(name, age) values (?,?)" ret, err := db.Exec(sqlStr, "沙河小王子", 19) if err != nil { fmt.Printf("insert failed, err:%v\n", err) return } theID, err := ret.LastInsertId() // 新插入数据的id if err != nil { fmt.Printf("get lastinsert ID failed, err:%v\n", err) return } fmt.Printf("insert success, the id is %d.\n", theID) } // 更新数据 func updateRowDemo() { sqlStr := "update user set age=? where id = ?" ret, err := db.Exec(sqlStr, 39, 6) if err != nil { fmt.Printf("update failed, err:%v\n", err) return } n, err := ret.RowsAffected() // 操作影响的行数 if err != nil { fmt.Printf("get RowsAffected failed, err:%v\n", err) return } fmt.Printf("update success, affected rows:%d\n", n) } // 删除数据 func deleteRowDemo() { sqlStr := "delete from user where id = ?" ret, err := db.Exec(sqlStr, 6) if err != nil { fmt.Printf("delete failed, err:%v\n", err) return } n, err := ret.RowsAffected() // 操作影响的行数 if err != nil { fmt.Printf("get RowsAffected failed, err:%v\n", err) return } fmt.Printf("delete success, affected rows:%d\n", n) } func insertUserDemo()(err error){ sqlStr := "INSERT INTO user (name,age) VALUES (:name,:age)" _, err = db.NamedExec(sqlStr, map[string]interface{}{ "name": "七米", "age": 28, }) return } func namedQuery(){ sqlStr := "SELECT * FROM user WHERE name=:name" // 使用map做命名查询 rows, err := db.NamedQuery(sqlStr, map[string]interface{}{"name": "七米"}) if err != nil { fmt.Printf("db.NamedQuery failed, err:%v\n", err) return } defer rows.Close() for rows.Next(){ var u user err := rows.StructScan(&u) if err != nil { fmt.Printf("scan failed, err:%v\n", err) continue } fmt.Printf("user:%#v\n", u) } u := user{ Name: "七米", } // 使用结构体命名查询,根据结构体字段的 db tag进行映射 rows, err = db.NamedQuery(sqlStr, u) if err != nil { fmt.Printf("db.NamedQuery failed, err:%v\n", err) return } defer rows.Close() for rows.Next(){ var u user err := rows.StructScan(&u) if err != nil { fmt.Printf("scan failed, err:%v\n", err) continue } fmt.Printf("user:%#v\n", u) } } func transactionDemo2()(err error) { tx, err := db.Beginx() // 开启事务 if err != nil { fmt.Printf("begin trans failed, err:%v\n", err) return err } defer func() { if p := recover(); p != nil { tx.Rollback() panic(p) // re-throw panic after Rollback } else if err != nil { fmt.Println("rollback") tx.Rollback() // err is non-nil; don't change it } else { err = tx.Commit() // err is nil; if Commit returns error update err fmt.Println("commit") } }() sqlStr1 := "Update user set age=20 where id=?" rs, err := tx.Exec(sqlStr1, 1) if err!= nil{ return err } n, err := rs.RowsAffected() if err != nil { return err } if n != 1 { return errors.New("exec sqlStr1 failed") } sqlStr2 := "Update user set age=50 where i=?" rs, err = tx.Exec(sqlStr2, 3) if err!=nil{ return err } n, err = rs.RowsAffected() if err != nil { return err } if n != 1 { return errors.New("exec sqlStr1 failed") } return err } // QueryByIDs 根据给定ID查询 func QueryByIDs(ids []int)(users []user, err error){ // 动态填充id query, args, err := sqlx.In("SELECT name, age FROM user WHERE id IN (?)", ids) if err != nil { return } // sqlx.In 返回带 `?` bindvar的查询语句, 我们使用Rebind()重新绑定它 query = db.Rebind(query) err = db.Select(&users, query, args...) return } // QueryAndOrderByIDs 按照指定id查询并维护顺序 func QueryAndOrderByIDs(ids []int)(users []user, err error){ // 动态填充id strIDs := make([]string, 0, len(ids)) for _, id := range ids { strIDs = append(strIDs, fmt.Sprintf("%d", id)) } query, args, err := sqlx.In("SELECT name, age FROM user WHERE id IN (?) ORDER BY FIND_IN_SET(id, ?)", ids, strings.Join(strIDs, ",")) if err != nil { return } // sqlx.In 返回带 `?` bindvar的查询语句, 我们使用Rebind()重新绑定它 query = db.Rebind(query) err = db.Select(&users, query, args...) return } func main() { if err :=initDB();err!= nil{ fmt.Printf("init DB failed, err:%v\n", err) return } fmt.Println("init DB success...") //queryRowDemo() //queryMultiRowDemo() //insertUserDemo() //namedQuery() //transactionDemo2() //u1 := user{Name: "xx", Age: 18} //u2 := user{Name: "xxx", Age: 28} //u3 := user{Name: "xxxx", Age: 38} //users := []interface{}{u1, u2, u3} //BatchInsertUsers2(users) users , err := QueryByIDs([]int{7,5,6, 1}) if err != nil { fmt.Printf("QueryByIDs failed, err:%v\n", err) return } for _, user := range users{ fmt.Printf("user:%#v\n", user) } // 1. 用代码去做排序 // 2. 让MySQL排序 fmt.Println("----") users ,err = QueryAndOrderByIDs([]int{7,5,6, 1}) if err != nil { fmt.Printf("QueryByIDs failed, err:%v\n", err) return } for _, user := range users{ fmt.Printf("user:%#v\n", user) } } ================================================ FILE: swagger_demo/docs/docs.go ================================================ // GENERATED BY THE COMMAND ABOVE; DO NOT EDIT // This file was generated by swaggo/swag package docs import ( "bytes" "encoding/json" "strings" "github.com/alecthomas/template" "github.com/swaggo/swag" ) var doc = `{ "schemes": {{ marshal .Schemes }}, "swagger": "2.0", "info": { "description": "{{.Description}}", "title": "{{.Title}}", "termsOfService": "http://swagger.io/terms/", "contact": { "name": "API Support", "url": "http://www.swagger.io/support", "email": "support@swagger.io" }, "license": { "name": "Apache 2.0", "url": "http://www.apache.org/licenses/LICENSE-2.0.html" }, "version": "{{.Version}}" }, "host": "{{.Host}}", "basePath": "{{.BasePath}}", "paths": {} }` type swaggerInfo struct { Version string Host string BasePath string Schemes []string Title string Description string } // SwaggerInfo holds exported Swagger Info so clients can modify it var SwaggerInfo = swaggerInfo{ Version: "1.0", Host: "petstore.swagger.io", BasePath: "/v2", Schemes: []string{}, Title: "Swagger Example API", Description: "This is a sample server Petstore server.", } type s struct{} func (s *s) ReadDoc() string { sInfo := SwaggerInfo sInfo.Description = strings.Replace(sInfo.Description, "\n", "\\n", -1) t, err := template.New("swagger_info").Funcs(template.FuncMap{ "marshal": func(v interface{}) string { a, _ := json.Marshal(v) return string(a) }, }).Parse(doc) if err != nil { return doc } var tpl bytes.Buffer if err := t.Execute(&tpl, sInfo); err != nil { return doc } return tpl.String() } func init() { swag.Register(swag.Name, &s{}) } ================================================ FILE: swagger_demo/docs/swagger.json ================================================ { "swagger": "2.0", "info": { "description": "This is a sample server Petstore server.", "title": "Swagger Example API", "termsOfService": "http://swagger.io/terms/", "contact": { "name": "API Support", "url": "http://www.swagger.io/support", "email": "support@swagger.io" }, "license": { "name": "Apache 2.0", "url": "http://www.apache.org/licenses/LICENSE-2.0.html" }, "version": "1.0" }, "host": "petstore.swagger.io", "basePath": "/v2", "paths": {} } ================================================ FILE: swagger_demo/docs/swagger.yaml ================================================ basePath: /v2 host: petstore.swagger.io info: contact: email: support@swagger.io name: API Support url: http://www.swagger.io/support description: This is a sample server Petstore server. license: name: Apache 2.0 url: http://www.apache.org/licenses/LICENSE-2.0.html termsOfService: http://swagger.io/terms/ title: Swagger Example API version: "1.0" paths: {} swagger: "2.0" ================================================ FILE: swagger_demo/go.mod ================================================ module swagger_demo go 1.14 require ( github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect github.com/gin-gonic/gin v1.6.3 github.com/go-openapi/spec v0.19.9 // indirect github.com/go-openapi/swag v0.19.9 // indirect github.com/mailru/easyjson v0.7.6 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14 github.com/swaggo/gin-swagger v1.2.0 github.com/swaggo/swag v1.6.7 // indirect github.com/urfave/cli v1.22.4 // indirect github.com/urfave/cli/v2 v2.2.0 // indirect golang.org/x/net v0.0.0-20200904194848-62affa334b73 // indirect golang.org/x/tools v0.0.0-20200904185747-39188db58858 // indirect gopkg.in/yaml.v2 v2.3.0 // indirect ) ================================================ FILE: swagger_demo/go.sum ================================================ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= github.com/PuerkitoBio/purell v1.1.0 h1:rmGxhojJlM0tuKtfdvliR84CFHljx9ag64t2xmVkjK4= github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/gzip v0.0.1 h1:ezvKOL6jH+jlzdHNE4h9h8q8uMpDQjyl0NN0Jd7jozc= github.com/gin-contrib/gzip v0.0.1/go.mod h1:fGBJBCdt6qCZuCAOwWuFhBB4OOq9EFqlo5dEaFhhu5w= github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.3.0/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y= github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14= github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= github.com/go-openapi/jsonpointer v0.17.0 h1:nH6xp8XdXHx8dqveo0ZuJBluCO2qGrPbDNZ0dwoRHP0= github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= github.com/go-openapi/jsonreference v0.19.0 h1:BqWKpV1dFd+AuiKlgtddwVIFQsuMpxfBDBHGfM2yNpk= github.com/go-openapi/jsonreference v0.19.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= github.com/go-openapi/jsonreference v0.19.4 h1:3Vw+rh13uq2JFNxgnMTGE1rnoieU9FmyE1gvnyylsYg= github.com/go-openapi/jsonreference v0.19.4/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= github.com/go-openapi/spec v0.19.0 h1:A4SZ6IWh3lnjH0rG0Z5lkxazMGBECtrZcbyYQi+64k4= github.com/go-openapi/spec v0.19.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= github.com/go-openapi/spec v0.19.4/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= github.com/go-openapi/spec v0.19.9 h1:9z9cbFuZJ7AcvOHKIY+f6Aevb4vObNDkTEyoMfO7rAc= github.com/go-openapi/spec v0.19.9/go.mod h1:vqK/dIdLGCosfvYsQV3WfC7N3TiZSnGY2RZKoFK7X28= github.com/go-openapi/swag v0.17.0 h1:iqrgMg7Q7SvtbWLlltPrkMs0UBJI6oTSs79JFRUi880= github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.9 h1:1IxuqvBUU3S2Bi4YC7tlP9SJF1gVpCvqN0T2Qof4azE= github.com/go-openapi/swag v0.19.9/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY= github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329 h1:2gxZ0XQIU/5z3Z3bUBu+FXuk2pFbkN6tcwi/pjyaDic= github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14 h1:PyYN9JH5jY9j6av01SpfRMb+1DWg/i3MbGOKPxJ2wjM= github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14/go.mod h1:gxQT6pBGRuIGunNf/+tSOB5OHvguWi8Tbt82WOkf35E= github.com/swaggo/gin-swagger v1.2.0 h1:YskZXEiv51fjOMTsXrOetAjrMDfFaXD79PEoQBOe2W0= github.com/swaggo/gin-swagger v1.2.0/go.mod h1:qlH2+W7zXGZkczuL+r2nEBR2JTT+/lX05Nn6vPhc7OI= github.com/swaggo/swag v1.5.1 h1:2Agm8I4K5qb00620mHq0VJ05/KT4FtmALPIcQR9lEZM= github.com/swaggo/swag v1.5.1/go.mod h1:1Bl9F/ZBpVWh22nY0zmYyASPO1lI/zIwRDrpZU+tv8Y= github.com/swaggo/swag v1.6.7 h1:e8GC2xDllJZr3omJkm9YfmK0Y56+rMO3cg0JBKNz09s= github.com/swaggo/swag v1.6.7/go.mod h1:xDhTyuFIujYiN3DKWC/H/83xcfHp+UE/IzWWampG7Zc= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go v1.1.5-pre/go.mod h1:FwP/aQVg39TXzItUBMwnWp9T9gPQnXw4Poh4/oBQZ/0= github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v0.0.0-20181022190402-e5e69e061d4f/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/ugorji/go/codec v1.1.5-pre/go.mod h1:tULtS6Gy1AE1yCENaw4Vb//HLH5njI2tfCQDUqRd8fI= github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.4 h1:u7tSpNPPswAFymm8IehJhy4uJMlUuU/GmqSkvJ1InXA= github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli/v2 v2.1.1 h1:Qt8FeAtxE/vfdrLmR3rxR6JRE0RoVmbXu8+6kZtYU4k= github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= github.com/urfave/cli/v2 v2.2.0 h1:JTTnM6wKzdA0Jqodd966MVj4vWbbquZykeX1sKbe2C4= github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190611141213-3f473d35a33a h1:+KkCgOMgnKSgenxTBoiwkMqTiouMIy/3o8RLdmSbGoY= golang.org/x/net v0.0.0-20190611141213-3f473d35a33a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200904194848-62affa334b73 h1:MXfv8rhZWmFeqX3GNZRsd6vOLoaCHjYEX3qkRo3YBUA= golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190606050223-4d9ae51c2468/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190611222205-d73e1c7e250b h1:/mJ+GKieZA6hFDQGdWZrjj4AXPl5ylY+5HusG80roy0= golang.org/x/tools v0.0.0-20190611222205-d73e1c7e250b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200904185747-39188db58858 h1:xLt+iB5ksWcZVxqc+g9K41ZHy+6MKWfXCDsjSThnsPA= golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= ================================================ FILE: swagger_demo/main.go ================================================ package main import ( "github.com/gin-gonic/gin" swaggerFiles "github.com/swaggo/files" "github.com/swaggo/gin-swagger" _ "github.com/swaggo/gin-swagger/example/basic/docs" // 将Swag CLI生成的docs在这里导入 ) // @title Swagger Example API // @version 1.0 // @description This is a sample server Petstore server. // @termsOfService http://swagger.io/terms/ // @contact.name API Support // @contact.url http://www.swagger.io/support // @contact.email support@swagger.io // @license.name Apache 2.0 // @license.url http://www.apache.org/licenses/LICENSE-2.0.html // @host petstore.swagger.io // @BasePath /v2 func main() { r := gin.New() url := ginSwagger.URL("/swagger/doc.json") // 指向API定义的网址 r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler, url)) r.GET("/swagger/*any", ginSwagger.DisablingWrapHandler(swaggerFiles.Handler, "NAME_OF_ENV_VARIABLE")) r.Run() } ================================================ FILE: swagger_demo/swagger_demo ================================================ [File too large to display: 25.6 MB] ================================================ FILE: viper_demo/config.yaml ================================================ port: 8081 version: "v0.0.2" mysql: host: "127.0.0.1" port: 13306 dbname: "sql_demo" ================================================ FILE: viper_demo/go.mod ================================================ module viper_demo go 1.14 require ( github.com/fsnotify/fsnotify v1.4.7 github.com/gin-gonic/gin v1.6.3 github.com/spf13/viper v1.7.0 ) ================================================ FILE: viper_demo/go.sum ================================================ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14= github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY= github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0 h1:HyfiK1WMnHj5FXFXatD+Qs1A/xC2Run6RzeW1SyHxpc= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= ================================================ FILE: viper_demo/main.go ================================================ package main import ( "fmt" "github.com/fsnotify/fsnotify" "github.com/spf13/viper" ) type Config struct { Port int `mapstructure:"port"` Version string `mapstructure:"version"` MySQLConfig `mapstructure:"mysql"` } type MySQLConfig struct { Host string `mapstructure:"host"` DbName string `mapstructure:"dbname"` Port int `mapstructure:"port"` } func main() { // 设置默认值 viper.SetDefault("fileDir", "./") // 读取配置文件 viper.SetConfigName("config") // 配置文件名称(无扩展名) viper.SetConfigType("yaml") // 如果配置文件的名称中没有扩展名,则需要配置此项 //viper.SetConfigFile("config.yaml") viper.AddConfigPath("/etc/appname/") // 查找配置文件所在的路径 viper.AddConfigPath("$HOME/.appname") // 多次调用以添加多个搜索路径 viper.AddConfigPath(".") // 还可以在工作目录中查找配置 err := viper.ReadInConfig() // 查找并读取配置文件 if err != nil { // 处理读取配置文件的错误 panic(fmt.Errorf("Fatal error config file: %s \n", err)) } // 实时监控配置文件的变化 viper.WatchConfig() // 当配置变化之后调用的一个回调函数 viper.OnConfigChange(func(e fsnotify.Event) { // 配置文件发生变更之后会调用的回调函数 fmt.Println("Config file changed:", e.Name) }) var c Config if err := viper.Unmarshal(&c); err != nil { fmt.Printf("viper.Unmarshal failed, err:%v\n", err) return } fmt.Printf("c:%#v\n", c) //r := gin.Default() //r.GET("/version", func(c *gin.Context) { // c.String(http.StatusOK, viper.GetString("version")) //}) //r.Run() } ================================================ FILE: web_app/config.yaml ================================================ app: name: "web_app" mode: "dev" port: 8081 log: level: "debug" filename: "web_app.log" max_size: 200 max_age: 30 max_backups: 7 mysql: host: "127.0.0.1" port: 13306 user: "root" password: "root1234" dbname: "sql_demo" max_open_conns: 200 max_idle_conns: 50 redis: host: "127.0.0.1" port: 16379 password: "" db: 0 pool_size: 100 ================================================ FILE: web_app/dao/mysql/mysql.go ================================================ package mysql import ( "fmt" "github.com/spf13/viper" "go.uber.org/zap" _ "github.com/go-sql-driver/mysql" "github.com/jmoiron/sqlx" ) var db *sqlx.DB func Init() (err error) { dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True", viper.GetString("mysql.user"), viper.GetString("mysql.password"), viper.GetString("mysql.host"), viper.GetInt("mysql.port"), viper.GetString("mysql.dbname"), ) // 也可以使用MustConnect连接不成功就panic db, err = sqlx.Connect("mysql", dsn) if err != nil { zap.L().Error("connect DB failed", zap.Error(err)) return } db.SetMaxOpenConns(viper.GetInt("mysql.max_open_conns")) db.SetMaxIdleConns(viper.GetInt("mysql.max_idle_conns")) return } func Close() { _ = db.Close() } ================================================ FILE: web_app/dao/redis/redis.go ================================================ package redis import ( "fmt" "github.com/go-redis/redis" "github.com/spf13/viper" ) // 声明一个全局的rdb变量 var rdb *redis.Client // Init 初始化连接 func Init() (err error) { rdb = redis.NewClient(&redis.Options{ Addr: fmt.Sprintf("%s:%d", viper.GetString("redis.host"), viper.GetInt("redis.port"), ), Password: viper.GetString("redis.password"), // no password set DB: viper.GetInt("redis.db"), // use default DB PoolSize: viper.GetInt("redis.pool_size"), }) _, err = rdb.Ping().Result() return } func Close() { _ = rdb.Close() } ================================================ FILE: web_app/go.mod ================================================ module web_app go 1.14 require ( github.com/fsnotify/fsnotify v1.4.7 github.com/gin-gonic/gin v1.6.3 github.com/go-redis/redis v6.15.8+incompatible github.com/go-sql-driver/mysql v1.5.0 github.com/jmoiron/sqlx v1.2.0 github.com/natefinch/lumberjack v2.0.0+incompatible github.com/spf13/viper v1.7.0 go.uber.org/zap v1.10.0 gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect ) ================================================ FILE: web_app/go.sum ================================================ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14= github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY= github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= github.com/go-redis/redis v6.15.8+incompatible h1:BKZuG6mCnRj5AOaWJXoCgf6rqTYnYJLe4en2hxT7r9o= github.com/go-redis/redis v6.15.8+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA= github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-sqlite3 v1.9.0 h1:pDRiWfl+++eC2FEFRy6jXmQlvp4Yh3z1MJKg4UeYM/4= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/natefinch/lumberjack v2.0.0+incompatible h1:4QJd3OLAMgj7ph+yZTuX13Ld4UpgHp07nNdFX7mqFfM= github.com/natefinch/lumberjack v2.0.0+incompatible/go.mod h1:Wi9p2TTF5DG5oU+6YfsmYQpsTIOm0B1VNzQg9Mw6nPk= github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.13.0 h1:M76yO2HkZASFjXL0HSoZJ1AYEmQxNJmY41Jx1zNUq1Y= github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 h1:AeiKBIuRw3UomYXSbLy0Mc2dDLfdtbT/IVn4keq83P0= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 h1:DYfZAGf2WMFjMxbgTjaC+2HC7NkNAQs+6Q8b9WEB/F4= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= ================================================ FILE: web_app/logger/logger.go ================================================ package logger import ( "net" "net/http" "net/http/httputil" "os" "runtime/debug" "strings" "time" "github.com/spf13/viper" "github.com/gin-gonic/gin" "github.com/natefinch/lumberjack" "go.uber.org/zap" "go.uber.org/zap/zapcore" ) // Init 初始化Logger func Init() (err error) { writeSyncer := getLogWriter( viper.GetString("log.filename"), viper.GetInt("log.max_size"), viper.GetInt("log.max_backups"), viper.GetInt("log.max_age"), ) encoder := getEncoder() var l = new(zapcore.Level) err = l.UnmarshalText([]byte(viper.GetString("log.level"))) if err != nil { return } core := zapcore.NewCore(encoder, writeSyncer, l) lg := zap.New(core, zap.AddCaller()) // 替换zap库中全局的logger zap.ReplaceGlobals(lg) return } func getEncoder() zapcore.Encoder { encoderConfig := zap.NewProductionEncoderConfig() encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder encoderConfig.TimeKey = "time" encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder encoderConfig.EncodeDuration = zapcore.SecondsDurationEncoder encoderConfig.EncodeCaller = zapcore.ShortCallerEncoder return zapcore.NewJSONEncoder(encoderConfig) } func getLogWriter(filename string, maxSize, maxBackup, maxAge int) zapcore.WriteSyncer { lumberJackLogger := &lumberjack.Logger{ Filename: filename, MaxSize: maxSize, MaxBackups: maxBackup, MaxAge: maxAge, } return zapcore.AddSync(lumberJackLogger) } // GinLogger 接收gin框架默认的日志 func GinLogger() gin.HandlerFunc { return func(c *gin.Context) { start := time.Now() path := c.Request.URL.Path query := c.Request.URL.RawQuery c.Next() cost := time.Since(start) zap.L().Info(path, zap.Int("status", c.Writer.Status()), zap.String("method", c.Request.Method), zap.String("path", path), zap.String("query", query), zap.String("ip", c.ClientIP()), zap.String("user-agent", c.Request.UserAgent()), zap.String("errors", c.Errors.ByType(gin.ErrorTypePrivate).String()), zap.Duration("cost", cost), ) } } // GinRecovery recover掉项目可能出现的panic,并使用zap记录相关日志 func GinRecovery(stack bool) gin.HandlerFunc { return func(c *gin.Context) { defer func() { if err := recover(); err != nil { // Check for a broken connection, as it is not really a // condition that warrants a panic stack trace. var brokenPipe bool if ne, ok := err.(*net.OpError); ok { if se, ok := ne.Err.(*os.SyscallError); ok { if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") { brokenPipe = true } } } httpRequest, _ := httputil.DumpRequest(c.Request, false) if brokenPipe { zap.L().Error(c.Request.URL.Path, zap.Any("error", err), zap.String("request", string(httpRequest)), ) // If the connection is dead, we can't write a status to it. c.Error(err.(error)) // nolint: errcheck c.Abort() return } if stack { zap.L().Error("[Recovery from panic]", zap.Any("error", err), zap.String("request", string(httpRequest)), zap.String("stack", string(debug.Stack())), ) } else { zap.L().Error("[Recovery from panic]", zap.Any("error", err), zap.String("request", string(httpRequest)), ) } c.AbortWithStatus(http.StatusInternalServerError) } }() c.Next() } } ================================================ FILE: web_app/main.go ================================================ package main import ( "context" "fmt" "log" "net/http" "os" "os/signal" "syscall" "time" "web_app/dao/mysql" "web_app/dao/redis" "web_app/logger" "web_app/routes" "web_app/settings" "github.com/spf13/viper" "go.uber.org/zap" ) // Go Web开发较通用的脚手架模板 func main() { // 1. 加载配置 if err := settings.Init(); err != nil { fmt.Printf("init settings failed, err:%v\n", err) return } // 2. 初始化日志 if err := logger.Init(); err != nil { fmt.Printf("init logger failed, err:%v\n", err) return } defer zap.L().Sync() zap.L().Debug("logger init success...") // 3. 初始化MySQL连接 if err := mysql.Init(); err != nil { fmt.Printf("init mysql failed, err:%v\n", err) return } defer mysql.Close() // 4. 初始化Redis连接 if err := redis.Init(); err != nil { fmt.Printf("init redis failed, err:%v\n", err) return } defer redis.Close() // 5. 注册路由 r := routes.Setup() // 6. 启动服务(优雅关机) srv := &http.Server{ Addr: fmt.Sprintf(":%d", viper.GetInt("app.port")), Handler: r, } go func() { // 开启一个goroutine启动服务 if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Fatalf("listen: %s\n", err) } }() // 等待中断信号来优雅地关闭服务器,为关闭服务器操作设置一个5秒的超时 quit := make(chan os.Signal, 1) // 创建一个接收信号的通道 // kill 默认会发送 syscall.SIGTERM 信号 // kill -2 发送 syscall.SIGINT 信号,我们常用的Ctrl+C就是触发系统SIGINT信号 // kill -9 发送 syscall.SIGKILL 信号,但是不能被捕获,所以不需要添加它 // signal.Notify把收到的 syscall.SIGINT或syscall.SIGTERM 信号转发给quit signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) // 此处不会阻塞 <-quit // 阻塞在此,当接收到上述两种信号时才会往下执行 zap.L().Info("Shutdown Server ...") // 创建一个5秒超时的context ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() // 5秒内优雅关闭服务(将未处理完的请求处理完再关闭服务),超过5秒就超时退出 if err := srv.Shutdown(ctx); err != nil { zap.L().Fatal("Server Shutdown", zap.Error(err)) } zap.L().Info("Server exiting") } ================================================ FILE: web_app/routes/routes.go ================================================ package routes import ( "net/http" "web_app/logger" "github.com/gin-gonic/gin" ) func Setup() *gin.Engine { r := gin.New() r.Use(logger.GinLogger(), logger.GinRecovery(true)) r.GET("/", func(c *gin.Context) { c.String(http.StatusOK, "ok") }) return r } ================================================ FILE: web_app/settings/settings.go ================================================ package settings import ( "fmt" "github.com/fsnotify/fsnotify" "github.com/spf13/viper" ) func Init() (err error) { viper.SetConfigName("config") // 指定配置文件名称(不需要带后缀) viper.SetConfigType("yaml") // 指定配置文件类型 viper.AddConfigPath(".") // 指定查找配置文件的路径(这里使用相对路径) err = viper.ReadInConfig() // 读取配置信息 if err != nil { // 读取配置信息失败 fmt.Printf("viper.ReadInConfig() failed, err:%v\n", err) return } viper.WatchConfig() viper.OnConfigChange(func(in fsnotify.Event) { fmt.Println("配置文件修改了...") }) return } ================================================ FILE: web_app/web_app ================================================ [File too large to display: 18.5 MB] ================================================ FILE: web_app2/config.json ================================================ { "version": "v1.1.1" } ================================================ FILE: web_app2/config.yaml ================================================ name: "web_app" mode: "dev" port: 8080 version: "v0.1.4" log: level: "debug" filename: "web_app.log" max_size: 200 max_age: 30 max_backups: 7 mysql: host: "127.0.0.1" port: 13306 user: "root" password: "root1234" dbname: "sql_demo" max_open_conns: 200 max_idle_conns: 50 redis: host: "127.0.0.1" port: 16379 password: "" db: 0 pool_size: 100 ================================================ FILE: web_app2/dao/mysql/mysql.go ================================================ package mysql import ( "fmt" "web_app/settings" "go.uber.org/zap" _ "github.com/go-sql-driver/mysql" "github.com/jmoiron/sqlx" ) var db *sqlx.DB func Init(cfg *settings.MySQLConfig) (err error) { dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True", cfg.User, cfg.Password, cfg.Host, cfg.Port, cfg.DbName, ) // 也可以使用MustConnect连接不成功就panic db, err = sqlx.Connect("mysql", dsn) if err != nil { zap.L().Error("connect DB failed", zap.Error(err)) return } db.SetMaxOpenConns(cfg.MaxOpenConns) db.SetMaxIdleConns(cfg.MaxIdleConns) return } func Close() { _ = db.Close() } ================================================ FILE: web_app2/dao/redis/redis.go ================================================ package redis import ( "fmt" "web_app/settings" "github.com/go-redis/redis" ) // 声明一个全局的rdb变量 var rdb *redis.Client // Init 初始化连接 func Init(cfg *settings.RedisConfig) (err error) { rdb = redis.NewClient(&redis.Options{ Addr: fmt.Sprintf("%s:%d", cfg.Host, cfg.Port, ), Password: cfg.Password, // no password set DB: cfg.DB, // use default DB PoolSize: cfg.PoolSize, }) _, err = rdb.Ping().Result() return } func Close() { _ = rdb.Close() } ================================================ FILE: web_app2/go.mod ================================================ module web_app go 1.14 require ( github.com/fsnotify/fsnotify v1.4.9 github.com/gin-gonic/gin v1.6.3 github.com/go-redis/redis v6.15.8+incompatible github.com/go-sql-driver/mysql v1.5.0 github.com/jmoiron/sqlx v1.2.0 github.com/natefinch/lumberjack v2.0.0+incompatible github.com/onsi/ginkgo v1.13.0 // indirect github.com/spf13/viper v1.7.0 go.uber.org/zap v1.10.0 gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect ) ================================================ FILE: web_app2/go.sum ================================================ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14= github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY= github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= github.com/go-redis/redis v6.15.8+incompatible h1:BKZuG6mCnRj5AOaWJXoCgf6rqTYnYJLe4en2hxT7r9o= github.com/go-redis/redis v6.15.8+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA= github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-sqlite3 v1.9.0 h1:pDRiWfl+++eC2FEFRy6jXmQlvp4Yh3z1MJKg4UeYM/4= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/natefinch/lumberjack v2.0.0+incompatible h1:4QJd3OLAMgj7ph+yZTuX13Ld4UpgHp07nNdFX7mqFfM= github.com/natefinch/lumberjack v2.0.0+incompatible/go.mod h1:Wi9p2TTF5DG5oU+6YfsmYQpsTIOm0B1VNzQg9Mw6nPk= github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.13.0 h1:M76yO2HkZASFjXL0HSoZJ1AYEmQxNJmY41Jx1zNUq1Y= github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 h1:AeiKBIuRw3UomYXSbLy0Mc2dDLfdtbT/IVn4keq83P0= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 h1:DYfZAGf2WMFjMxbgTjaC+2HC7NkNAQs+6Q8b9WEB/F4= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= ================================================ FILE: web_app2/logger/logger.go ================================================ package logger import ( "net" "net/http" "net/http/httputil" "os" "runtime/debug" "strings" "time" "web_app/settings" "github.com/gin-gonic/gin" "github.com/natefinch/lumberjack" "go.uber.org/zap" "go.uber.org/zap/zapcore" ) // Init 初始化Logger func Init(cfg *settings.LogConfig) (err error) { writeSyncer := getLogWriter( cfg.Filename, cfg.MaxSize, cfg.MaxBackups, cfg.MaxAge, ) encoder := getEncoder() var l = new(zapcore.Level) err = l.UnmarshalText([]byte(cfg.Level)) if err != nil { return } core := zapcore.NewCore(encoder, writeSyncer, l) lg := zap.New(core, zap.AddCaller()) // 替换zap库中全局的logger zap.ReplaceGlobals(lg) return } func getEncoder() zapcore.Encoder { encoderConfig := zap.NewProductionEncoderConfig() encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder encoderConfig.TimeKey = "time" encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder encoderConfig.EncodeDuration = zapcore.SecondsDurationEncoder encoderConfig.EncodeCaller = zapcore.ShortCallerEncoder return zapcore.NewJSONEncoder(encoderConfig) } func getLogWriter(filename string, maxSize, maxBackup, maxAge int) zapcore.WriteSyncer { lumberJackLogger := &lumberjack.Logger{ Filename: filename, MaxSize: maxSize, MaxBackups: maxBackup, MaxAge: maxAge, } return zapcore.AddSync(lumberJackLogger) } // GinLogger 接收gin框架默认的日志 func GinLogger() gin.HandlerFunc { return func(c *gin.Context) { start := time.Now() path := c.Request.URL.Path query := c.Request.URL.RawQuery c.Next() cost := time.Since(start) zap.L().Info(path, zap.Int("status", c.Writer.Status()), zap.String("method", c.Request.Method), zap.String("path", path), zap.String("query", query), zap.String("ip", c.ClientIP()), zap.String("user-agent", c.Request.UserAgent()), zap.String("errors", c.Errors.ByType(gin.ErrorTypePrivate).String()), zap.Duration("cost", cost), ) } } // GinRecovery recover掉项目可能出现的panic,并使用zap记录相关日志 func GinRecovery(stack bool) gin.HandlerFunc { return func(c *gin.Context) { defer func() { if err := recover(); err != nil { // Check for a broken connection, as it is not really a // condition that warrants a panic stack trace. var brokenPipe bool if ne, ok := err.(*net.OpError); ok { if se, ok := ne.Err.(*os.SyscallError); ok { if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") { brokenPipe = true } } } httpRequest, _ := httputil.DumpRequest(c.Request, false) if brokenPipe { zap.L().Error(c.Request.URL.Path, zap.Any("error", err), zap.String("request", string(httpRequest)), ) // If the connection is dead, we can't write a status to it. c.Error(err.(error)) // nolint: errcheck c.Abort() return } if stack { zap.L().Error("[Recovery from panic]", zap.Any("error", err), zap.String("request", string(httpRequest)), zap.String("stack", string(debug.Stack())), ) } else { zap.L().Error("[Recovery from panic]", zap.Any("error", err), zap.String("request", string(httpRequest)), ) } c.AbortWithStatus(http.StatusInternalServerError) } }() c.Next() } } ================================================ FILE: web_app2/main.go ================================================ package main import ( "context" "fmt" "log" "net/http" "os" "os/signal" "syscall" "time" "web_app/dao/mysql" "web_app/dao/redis" "web_app/logger" "web_app/routes" "web_app/settings" "go.uber.org/zap" ) // Go Web开发较通用的脚手架模板 func main() { // 1. 加载配置 if err := settings.Init(); err != nil { fmt.Printf("init settings failed, err:%v\n", err) return } fmt.Println(settings.Conf) fmt.Println(settings.Conf.LogConfig == nil) // 2. 初始化日志 if err := logger.Init(settings.Conf.LogConfig); err != nil { fmt.Printf("init logger failed, err:%v\n", err) return } defer zap.L().Sync() zap.L().Debug("logger init success...") // 3. 初始化MySQL连接 if err := mysql.Init(settings.Conf.MySQLConfig); err != nil { fmt.Printf("init mysql failed, err:%v\n", err) return } defer mysql.Close() // 4. 初始化Redis连接 if err := redis.Init(settings.Conf.RedisConfig); err != nil { fmt.Printf("init redis failed, err:%v\n", err) return } defer redis.Close() // 5. 注册路由 r := routes.Setup(settings.Conf.Mode) // 6. 启动服务(优雅关机) fmt.Println(settings.Conf.Port) srv := &http.Server{ Addr: fmt.Sprintf(":%d", settings.Conf.Port), Handler: r, } go func() { // 开启一个goroutine启动服务 if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Fatalf("listen: %s\n", err) } }() // 等待中断信号来优雅地关闭服务器,为关闭服务器操作设置一个5秒的超时 quit := make(chan os.Signal, 1) // 创建一个接收信号的通道 // kill 默认会发送 syscall.SIGTERM 信号 // kill -2 发送 syscall.SIGINT 信号,我们常用的Ctrl+C就是触发系统SIGINT信号 // kill -9 发送 syscall.SIGKILL 信号,但是不能被捕获,所以不需要添加它 // signal.Notify把收到的 syscall.SIGINT或syscall.SIGTERM 信号转发给quit signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) // 此处不会阻塞 <-quit // 阻塞在此,当接收到上述两种信号时才会往下执行 zap.L().Info("Shutdown Server ...") // 创建一个5秒超时的context ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() // 5秒内优雅关闭服务(将未处理完的请求处理完再关闭服务),超过5秒就超时退出 if err := srv.Shutdown(ctx); err != nil { zap.L().Fatal("Server Shutdown", zap.Error(err)) } zap.L().Info("Server exiting") } ================================================ FILE: web_app2/routes/routes.go ================================================ package routes import ( "net/http" "web_app/logger" "web_app/settings" "github.com/gin-gonic/gin" ) func Setup(mode string) *gin.Engine { if mode == gin.ReleaseMode { gin.SetMode(gin.ReleaseMode) } r := gin.New() r.Use(logger.GinLogger(), logger.GinRecovery(true)) r.GET("/version", func(c *gin.Context) { c.String(http.StatusOK, settings.Conf.Version) }) return r } ================================================ FILE: web_app2/settings/settings.go ================================================ package settings import ( "fmt" "github.com/fsnotify/fsnotify" "github.com/spf13/viper" ) // Conf 全局变量,用来保存程序的所有配置信息 var Conf = new(AppConfig) type AppConfig struct { Name string `mapstructure:"name"` Mode string `mapstructure:"mode"` Version string `mapstructure:"version"` Port int `mapstructure:"port"` *LogConfig `mapstructure:"log"` *MySQLConfig `mapstructure:"mysql"` *RedisConfig `mapstructure:"redis"` } type LogConfig struct { Level string `mapstructure:"level"` Filename string `mapstructure:"filename"` MaxSize int `mapstructure:"max_size"` MaxAge int `mapstructure:"max_age"` MaxBackups int `mapstructure:"max_backups"` } type MySQLConfig struct { Host string `mapstructure:"host"` User string `mapstructure:"user"` Password string `mapstructure:"password"` DbName string `mapstructure:"db_name"` Port int `mapstructure:"port"` MaxOpenConns int `mapstructure:"max_open_conns"` MaxIdleConns int `mapstructure:"max_idle_conns"` } type RedisConfig struct { Host string `mapstructure:"host"` Password string `mapstructure:"password"` Port int `mapstructure:"port"` DB int `mapstructure:"db"` PoolSize int `mapstructure:"pool_size"` } func Init() (err error) { viper.SetConfigFile("config.yaml") //viper.SetConfigName("config") // 指定配置文件名称(不需要带后缀) //viper.SetConfigType("yaml") // 指定配置文件类型(专用于从远程获取配置信息时指定配置文件类型的) viper.AddConfigPath(".") // 指定查找配置文件的路径(这里使用相对路径) err = viper.ReadInConfig() // 读取配置信息 if err != nil { // 读取配置信息失败 fmt.Printf("viper.ReadInConfig() failed, err:%v\n", err) return } // 把读取到的配置信息反序列化到 Conf 变量中 if err := viper.Unmarshal(Conf); err != nil { fmt.Printf("viper.Unmarshal failed, err:%v\n", err) } viper.WatchConfig() viper.OnConfigChange(func(in fsnotify.Event) { fmt.Println("配置文件修改了...") if err := viper.Unmarshal(Conf); err != nil { fmt.Printf("viper.Unmarshal failed, err:%v\n", err) } }) return } ================================================ FILE: web_app2/web_app ================================================ [File too large to display: 20.0 MB] ================================================ FILE: zap_demo/go.mod ================================================ module zap_demo go 1.14 require ( github.com/gin-gonic/gin v1.6.3 github.com/natefinch/lumberjack v2.0.0+incompatible go.uber.org/zap v1.15.0 gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect ) ================================================ FILE: zap_demo/go.sum ================================================ github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14= github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY= github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/natefinch/lumberjack v2.0.0+incompatible h1:4QJd3OLAMgj7ph+yZTuX13Ld4UpgHp07nNdFX7mqFfM= github.com/natefinch/lumberjack v2.0.0+incompatible/go.mod h1:Wi9p2TTF5DG5oU+6YfsmYQpsTIOm0B1VNzQg9Mw6nPk= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.15.0 h1:ZZCA22JRF2gQE5FoNmhmrf7jeJJ2uhqDUNRYKm8dvmM= go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5 h1:hKsoRgsbwY1NafxrwTs+k64bikrLBkAgPir1TNCj3Zs= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= ================================================ FILE: zap_demo/main.go ================================================ package main import ( "net" "net/http" "net/http/httputil" "os" "runtime/debug" "strings" "time" "github.com/gin-gonic/gin" "github.com/natefinch/lumberjack" "go.uber.org/zap" "go.uber.org/zap/zapcore" ) var logger *zap.Logger var sugarLogger *zap.SugaredLogger //func main() { // InitLogger() // defer logger.Sync() // // for i:=0;i<10000;i++{ // logger.Info("test for log rotate...") // } // simpleHttpGet("www.sogo.com") // simpleHttpGet("http://www.sogo.com") //} func main() { InitLogger() //r := gin.Default() r := gin.New() r.Use(GinLogger(logger), GinRecovery(logger, true)) r.GET("/hello", func(c *gin.Context) { c.String(http.StatusOK, "hello q1mi!") }) r.Run() } func InitLogger() { writeSyncer := getLogWriter() encoder := getEncoder() core := zapcore.NewCore(encoder, writeSyncer, zapcore.DebugLevel) logger = zap.New(core, zap.AddCaller()) sugarLogger = logger.Sugar() } func getEncoder() zapcore.Encoder { //return zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()) encoderConfig := zapcore.EncoderConfig{ TimeKey: "ts", LevelKey: "level", NameKey: "logger", CallerKey: "caller", MessageKey: "msg", StacktraceKey: "stacktrace", LineEnding: zapcore.DefaultLineEnding, EncodeLevel: zapcore.LowercaseLevelEncoder, EncodeTime: zapcore.ISO8601TimeEncoder, EncodeDuration: zapcore.SecondsDurationEncoder, EncodeCaller: zapcore.ShortCallerEncoder, } return zapcore.NewConsoleEncoder(encoderConfig) } //func getLogWriter() zapcore.WriteSyncer { // file, _ := os.OpenFile("./test.log", os.O_CREATE|os.O_APPEND|os.O_RDWR, 0744) // return zapcore.AddSync(file) //} func getLogWriter() zapcore.WriteSyncer { lumberJackLogger := &lumberjack.Logger{ Filename: "./test.log", MaxSize: 1, // M MaxBackups: 5, // 最大备份数量 MaxAge: 30, // 最大备份天数 Compress: false, // 是否压缩 } return zapcore.AddSync(lumberJackLogger) } func simpleHttpGet(url string) { resp, err := http.Get(url) if err != nil { sugarLogger.Error( "Error fetching url..", zap.String("url", url), zap.Error(err)) } else { sugarLogger.Info("Success..", zap.String("statusCode", resp.Status), zap.String("url", url)) resp.Body.Close() } } // GinLogger 接收gin框架默认的日志 func GinLogger(logger *zap.Logger) gin.HandlerFunc { return func(c *gin.Context) { start := time.Now() path := c.Request.URL.Path query := c.Request.URL.RawQuery c.Next() cost := time.Since(start) logger.Info(path, zap.Int("status", c.Writer.Status()), zap.String("method", c.Request.Method), zap.String("path", path), zap.String("query", query), zap.String("ip", c.ClientIP()), zap.String("user-agent", c.Request.UserAgent()), zap.String("errors", c.Errors.ByType(gin.ErrorTypePrivate).String()), zap.Duration("cost", cost), ) } } // GinRecovery recover掉项目可能出现的panic func GinRecovery(logger *zap.Logger, stack bool) gin.HandlerFunc { return func(c *gin.Context) { defer func() { if err := recover(); err != nil { // Check for a broken connection, as it is not really a // condition that warrants a panic stack trace. var brokenPipe bool if ne, ok := err.(*net.OpError); ok { if se, ok := ne.Err.(*os.SyscallError); ok { if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") { brokenPipe = true } } } httpRequest, _ := httputil.DumpRequest(c.Request, false) if brokenPipe { logger.Error(c.Request.URL.Path, zap.Any("error", err), zap.String("request", string(httpRequest)), ) // If the connection is dead, we can't write a status to it. c.Error(err.(error)) // nolint: errcheck c.Abort() return } if stack { logger.Error("[Recovery from panic]", zap.Any("error", err), zap.String("request", string(httpRequest)), zap.String("stack", string(debug.Stack())), ) } else { logger.Error("[Recovery from panic]", zap.Any("error", err), zap.String("request", string(httpRequest)), ) } c.AbortWithStatus(http.StatusInternalServerError) } }() c.Next() } }