From f2cd04993f87c2c2714b46608b7dcd684fcc735c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=96=B0=E4=BA=AE?= Date: Sat, 9 Jan 2021 20:10:13 +0800 Subject: [PATCH] upgrade --- README.md | 88 ++++++-- cmd/api/README.md | 1 + main.go => cmd/api/main.go | 23 ++- cmd/cron/README.md | 1 + cmd/cron/main.go | 11 + configs/configs.go | 91 +++++++++ configs/fat_configs.toml | 25 +++ docs/docs.go | 191 +++++++++++++++++- docs/swagger.json | 191 +++++++++++++++++- docs/swagger.yaml | 134 +++++++++++- go.mod | 10 +- go.sum | 90 +++++++++ init/README.md | 5 + init/db/tables.sql | 10 + internal/{pkg/errno => api/code}/README.md | 2 +- internal/api/code/code.go | 28 +++ internal/api/controller/README.md | 9 + internal/api/controller/demo/demo.go | 104 ++-------- internal/api/controller/demo/login.go | 47 ----- internal/api/controller/user_handler/user.go | 180 +++++++++++++++++ internal/api/model/README.md | 10 + internal/api/model/user_model/user.go | 68 +++++++ internal/api/repository/README.md | 29 +++ internal/api/repository/cache_repo/init.go | 120 +++++++++++ internal/api/repository/db_repo/init.go | 112 ++++++++++ internal/api/repository/db_repo/plugin.go | 86 ++++++++ .../db_repo/user_demo_repo/user_demo.go | 71 +++++++ internal/{ => api}/router/middleware/auth.go | 15 +- internal/api/router/router.go | 55 +++++ internal/api/service/README.md | 10 + internal/api/service/user_service/user.go | 67 ++++++ internal/pkg/configs/configs.go | 55 ----- internal/pkg/core/context.go | 61 ++++-- internal/pkg/core/core.go | 85 ++++---- internal/pkg/errno/code.go | 22 -- internal/pkg/journal/journal.go | 121 ----------- internal/pkg/notify/notify.go | 4 +- internal/pkg/trace/README.md | 81 ++++++++ internal/pkg/trace/debug.go | 6 + internal/pkg/trace/dialog.go | 32 +++ internal/pkg/trace/sql.go | 9 + internal/pkg/trace/trace.go | 124 ++++++++++++ internal/router/router.go | 53 ----- pkg/aes/aes_test.go | 17 +- pkg/env/env.go | 10 +- pkg/env/env_test.go | 7 - {internal/pkg => pkg}/errno/errno.go | 0 pkg/httpclient/client.go | 32 +-- pkg/httpclient/option.go | 20 +- pkg/httpclient/util.go | 12 +- pkg/md5/md5_test.go | 7 + pkg/p/print.go | 47 +++++ pkg/rsa/rsa_test.go | 39 ++-- pkg/time_parse/time_parse.go | 33 +++ {internal/pkg => pkg}/token/token.go | 30 ++- {internal/pkg => pkg}/token/token_test.go | 15 +- 56 files changed, 2239 insertions(+), 567 deletions(-) create mode 100644 cmd/api/README.md rename main.go => cmd/api/main.go (65%) create mode 100644 cmd/cron/README.md create mode 100644 cmd/cron/main.go create mode 100644 configs/configs.go create mode 100644 init/README.md create mode 100644 init/db/tables.sql rename internal/{pkg/errno => api/code}/README.md (91%) create mode 100644 internal/api/code/code.go create mode 100644 internal/api/controller/README.md delete mode 100644 internal/api/controller/demo/login.go create mode 100644 internal/api/controller/user_handler/user.go create mode 100644 internal/api/model/README.md create mode 100644 internal/api/model/user_model/user.go create mode 100644 internal/api/repository/README.md create mode 100644 internal/api/repository/cache_repo/init.go create mode 100644 internal/api/repository/db_repo/init.go create mode 100644 internal/api/repository/db_repo/plugin.go create mode 100644 internal/api/repository/db_repo/user_demo_repo/user_demo.go rename internal/{ => api}/router/middleware/auth.go (50%) create mode 100644 internal/api/router/router.go create mode 100644 internal/api/service/README.md create mode 100644 internal/api/service/user_service/user.go delete mode 100644 internal/pkg/configs/configs.go delete mode 100644 internal/pkg/errno/code.go delete mode 100644 internal/pkg/journal/journal.go create mode 100644 internal/pkg/trace/README.md create mode 100644 internal/pkg/trace/debug.go create mode 100644 internal/pkg/trace/dialog.go create mode 100644 internal/pkg/trace/sql.go create mode 100644 internal/pkg/trace/trace.go delete mode 100644 internal/router/router.go delete mode 100644 pkg/env/env_test.go rename {internal/pkg => pkg}/errno/errno.go (100%) create mode 100644 pkg/p/print.go create mode 100644 pkg/time_parse/time_parse.go rename {internal/pkg => pkg}/token/token.go (65%) rename {internal/pkg => pkg}/token/token_test.go (59%) diff --git a/README.md b/README.md index 133aa93..906a9e6 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ ╚██████╔╝╚██████╔╝ ╚██████╔╝██║██║ ╚████║ ██║ ██║██║ ██║ ╚═════╝ ╚═════╝ ╚═════╝ ╚═╝╚═╝ ╚═══╝ ╚═╝ ╚═╝╚═╝ ╚═╝ +* [register -env fat] * [register cors] * [register rate] * [register panic notify] @@ -18,12 +19,41 @@ 基于 [Gin](https://github.com/gin-gonic/gin) 进行模块化设计的 API 框架,封装了常用的功能,使用简单,致力于进行快速的业务研发,同时增加了更多限制,以约束项目组开发成员、规避混乱无序和自由随意。 +亮点功能: + +- :star: [trace] 开发调试的辅助工具。 + +可记录如下信息: + +- trace_id,当前请求的唯一ID +- request,当前请求的请求信息 +- response,当前请求的返回信息 +- third_party_requests,当前请求涉及到调用第三方的信息 +- debugs,当前请求的调试信息 +- sqls,当前请求执行的 sql 信息 +- success,当前请求结果 +- cost_seconds,执行时长,单位秒 + 供参考学习,线上使用请谨慎! **查看 Jaeger 链路追踪代码,请查看 [v1.0版](https://github.com/xinliangnote/go-gin-api/releases/tag/v1.0),文档 [jaeger.md](https://github.com/xinliangnote/go-gin-api/blob/master/docs/jaeger.md) 点这里**。 持续更新... +## Catalogue + +```cassandraql +├── cmd # 项目入口文件,api/main.go 为启动 HTTP API 服务 +├── configs # 配置文件统一存放目录 +├── docs # Swagger 文档,执行 swag init 生成的 +├── init # 项目初始脚本,比如初始化 SQL 语句 +├── internal # 业务目录 +│ ├── api # 业务代码 +│ ├── core # 脚本代码 +│ ├── pkg # 内部使用的 package +├── logs # 存放日志的目录 +└── pkg # 一些封装好的 package +``` ## Features - [x] 包管理工具 [Go Modules](https://github.com/golang/go/wiki/Modules) @@ -31,19 +61,20 @@ - [x] 配置文件解析库 [Viper](https://github.com/spf13/viper) - [x] 文档使用 [Swagger](https://swagger.io/) 生成 - [x] 性能分析使用 [pprof](github.com/gin-contrib/pprof) -- [x] 集成 - - [x] [JWT](https://jwt.io/) 身份认证 - - [x] [zap](go.uber.org/zap) 日志记录 - - [x] [rate](golang.org/x/time/rate) 限流 - - [x] 异常捕获并邮件告警 - - [x] 每个请求具备链路ID - - [x] 统一定义错误码 - - [x] 支持 FAT、UAT、PRO 环境 - - [x] MD5、AES 对称加密、RSA 非对称加密 -- [ ] 存储 - - [ ] MySQL - - [ ] Redis - - [ ] MongoDB +- [x] [zap](go.uber.org/zap) 日志记录 +- [x] [rate](golang.org/x/time/rate) 限流 +- [x] [token] 基于[JWT](github.com/dgrijalva/jwt-go) 身份认证 +- [x] [notify] 异常捕获并进行邮件告警 +- [x] [trace] 开发调试的辅助工具 +- [x] [errno] 统一定义错误码 +- [x] [env] 支持 FAT、UAT、PRO 环境 +- [x] [aes] AES 对称加密 +- [x] [rsa] RSA 非对称加密 +- [x] 数据库组件使用 [GORM V2](gorm.io/gorm) +- [x] Redis 组件使用 [go-redis](https://github.com/go-redis/redis) +- [ ] MongoDB +- [ ] Prometheus +- [ ] 任务调度 - [ ] gRPC - [ ] ... @@ -51,7 +82,7 @@ #### Requirements -- Go version >= 1.12 +- Go version >= 1.15 - Global environment configure (Linux/Mac) ``` @@ -59,12 +90,41 @@ export GO111MODULE=on export GOPROXY=https://goproxy.io ``` +#### Environment + +``` +-env fat + +// dev 开发环境 +// fat 测试环境[默认] +// uat 预发布环境 +// pro 正式环境 +``` + +#### Configs + +配置文件目录:`./configs`,根据不同的环境变量使用不同的配置文件。 + +项目启动时,需配置如下配置: + +- MySQL 配置,主、从和基础项; +- Redis 配置; +- Mail 配置; + +#### Init + +项目初始化目录:`./init`。 + +- db/tables.sql,初始化 MySQL 表结构; + #### Build & Run ``` cd go-gin-api go run main.go + +// 启动成功后,可访问:http://127.0.0.1:9999/h/info ``` #### swagger diff --git a/cmd/api/README.md b/cmd/api/README.md new file mode 100644 index 0000000..212c734 --- /dev/null +++ b/cmd/api/README.md @@ -0,0 +1 @@ +## HTTP API 入口文件 \ No newline at end of file diff --git a/main.go b/cmd/api/main.go similarity index 65% rename from main.go rename to cmd/api/main.go index 014f7db..717a374 100644 --- a/main.go +++ b/cmd/api/main.go @@ -6,8 +6,10 @@ import ( "net/http" "time" - "github.com/xinliangnote/go-gin-api/internal/pkg/configs" - "github.com/xinliangnote/go-gin-api/internal/router" + "github.com/xinliangnote/go-gin-api/configs" + "github.com/xinliangnote/go-gin-api/internal/api/repository/cache_repo" + "github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo" + "github.com/xinliangnote/go-gin-api/internal/api/router" "github.com/xinliangnote/go-gin-api/pkg/logger" "github.com/xinliangnote/go-gin-api/pkg/shutdown" @@ -25,6 +27,7 @@ import ( // @host 127.0.0.1:9999 // @BasePath func main() { + // 初始化日志 loggers, err := logger.NewJSONLogger( logger.WithField("domain", configs.ProjectName()), logger.WithTimeLayout("2006-01-02 15:04:05"), @@ -35,7 +38,20 @@ func main() { } defer loggers.Sync() - mux, err := router.NewHTTPMux(loggers) + // 初始化数据库 + dbRepo, err := db_repo.New() + if err != nil { + loggers.Fatal("new db err", zap.Error(err)) + } + + // 初始化缓存 + cacheRepo, err := cache_repo.New() + if err != nil { + loggers.Fatal("new cache err", zap.Error(err)) + } + + // 初始化 HTTP 服务 + mux, err := router.NewHTTPMux(loggers, dbRepo, cacheRepo) if err != nil { panic(err) } @@ -51,6 +67,7 @@ func main() { } }() + // 优雅关闭 shutdown.NewHook().Close(func() { ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) defer cancel() diff --git a/cmd/cron/README.md b/cmd/cron/README.md new file mode 100644 index 0000000..1944202 --- /dev/null +++ b/cmd/cron/README.md @@ -0,0 +1 @@ +## 定时任务入口文件 \ No newline at end of file diff --git a/cmd/cron/main.go b/cmd/cron/main.go new file mode 100644 index 0000000..f6f86b4 --- /dev/null +++ b/cmd/cron/main.go @@ -0,0 +1,11 @@ +package main + +import ( + "fmt" + + "github.com/xinliangnote/go-gin-api/pkg/env" +) + +func main() { + fmt.Println(env.Active().Value()) +} diff --git a/configs/configs.go b/configs/configs.go new file mode 100644 index 0000000..e2f6162 --- /dev/null +++ b/configs/configs.go @@ -0,0 +1,91 @@ +package configs + +import ( + "time" + + "github.com/xinliangnote/go-gin-api/pkg/env" + + "github.com/spf13/viper" +) + +var config = new(Config) + +type Config struct { + MySQL struct { + Read struct { + Addr string `toml:"addr"` + User string `toml:"user"` + Pass string `toml:"pass"` + Name string `toml:"name"` + } `toml:"read"` + Write struct { + Addr string `toml:"addr"` + User string `toml:"user"` + Pass string `toml:"pass"` + Name string `toml:"name"` + } `toml:"write"` + Base struct { + MaxOpenConn int `toml:"maxOpenConn"` + MaxIdleConn int `toml:"maxIdleConn"` + ConnMaxLifeTime time.Duration `toml:"connMaxLifeTime"` + } `toml:"base"` + } `toml:"mysql"` + + Redis struct { + Addr string `toml:"addr"` + Pass string `toml:"pass"` + Db int `toml:"db"` + MaxRetries int `toml:"maxRetries"` + PoolSize int `toml:"poolSize"` + MinIdleConns int `toml:"minIdleConns"` + } `toml:"redis"` + + Mail struct { + Host string `toml:"host"` + Port int `toml:"port"` + User string `toml:"user"` + Pass string `toml:"pass"` + To string `toml:"to"` + } `toml:"mail"` + + JWT struct { + Secret string `toml:"secret"` + } `toml:"jwt"` + + Aes struct { + Key string `toml:"key"` + Iv string `toml:"iv"` + } `toml:"aes"` + + Rsa struct { + Private string `toml:"private"` + Public string `toml:"public"` + } `toml:"rsa"` +} + +func init() { + viper.SetConfigName(env.Active().Value() + "_configs") + viper.SetConfigType("toml") + viper.AddConfigPath("./configs") + viper.AddConfigPath("../../configs") // 兼容 cmd/cron/main.go 引用配置文件 + + if err := viper.ReadInConfig(); err != nil { + panic(err) + } + + if err := viper.Unmarshal(config); err != nil { + panic(err) + } +} + +func Get() Config { + return *config +} + +func ProjectName() string { + return "go-gin-api" +} + +func ProjectVersion() string { + return "v2.0" +} diff --git a/configs/fat_configs.toml b/configs/fat_configs.toml index 8469772..3498dc4 100644 --- a/configs/fat_configs.toml +++ b/configs/fat_configs.toml @@ -1,3 +1,28 @@ +[mysql] + +[mysql.read] + addr = '' + user = '' + pass = '' + name = '' +[mysql.write] + addr = '' + user = '' + pass = '' + name = '' +[mysql.base] + maxOpenConn = 10 + maxIdleConn = 60 + connMaxLifeTime = 60 + +[redis] +addr = '' +pass = '' +db = 0 +maxRetries = 3 +poolSize = 10 +minIdleConns = 5 + [mail] host = 'smtp.163.com' port = 465 diff --git a/docs/docs.go b/docs/docs.go index 3c4bf6f..05ff3e7 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -76,9 +76,9 @@ var doc = `{ } } }, - "/user/login": { + "/user/create": { "post": { - "description": "登录获取 Authorization 码", + "description": "创建用户", "consumes": [ "application/json" ], @@ -88,23 +88,123 @@ var doc = `{ "tags": [ "Demo" ], - "summary": "登录获取 Authorization 码", + "summary": "创建用户", "parameters": [ { "description": "请求信息", - "name": "loginRequest", + "name": "RequestInfo", "in": "body", "required": true, "schema": { - "$ref": "#/definitions/demo.loginRequest" + "$ref": "#/definitions/user_model.CreateRequest" } } ], "responses": { "200": { - "description": "签名信息", + "description": "返回信息", "schema": { - "$ref": "#/definitions/demo.loginResponse" + "$ref": "#/definitions/user_model.CreateResponse" + } + } + } + } + }, + "/user/info/{username}": { + "get": { + "description": "用户详情", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Demo" + ], + "summary": "用户详情", + "parameters": [ + { + "type": "string", + "description": "用户名", + "name": "username", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "返回信息", + "schema": { + "$ref": "#/definitions/user_model.DetailResponse" + } + } + } + } + }, + "/user/login": { + "post": { + "description": "用户登录", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Demo" + ], + "summary": "用户登录", + "parameters": [ + { + "description": "请求信息", + "name": "RequestInfo", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/user_model.LoginRequest" + } + } + ], + "responses": { + "200": { + "description": "返回信息", + "schema": { + "$ref": "#/definitions/user_model.LoginResponse" + } + } + } + } + }, + "/user/update": { + "post": { + "description": "更新用户名称", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Demo" + ], + "summary": "更新用户名称", + "parameters": [ + { + "description": "请求信息", + "name": "RequestInfo", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/user_model.UpdateNickNameByIDRequest" + } + } + ], + "responses": { + "200": { + "description": "返回信息", + "schema": { + "$ref": "#/definitions/user_model.UpdateNickNameByIDResponse" } } } @@ -112,7 +212,54 @@ var doc = `{ } }, "definitions": { - "demo.loginRequest": { + "user_model.CreateRequest": { + "type": "object", + "properties": { + "mobile": { + "description": "手机号", + "type": "string" + }, + "nick_name": { + "description": "昵称", + "type": "string" + }, + "user_name": { + "description": "用户名", + "type": "string" + } + } + }, + "user_model.CreateResponse": { + "type": "object", + "properties": { + "id": { + "description": "主键ID", + "type": "integer" + } + } + }, + "user_model.DetailResponse": { + "type": "object", + "properties": { + "id": { + "description": "用户主键ID", + "type": "integer" + }, + "mobile": { + "description": "手机号", + "type": "string" + }, + "nick_name": { + "description": "昵称", + "type": "string" + }, + "user_name": { + "description": "用户名", + "type": "string" + } + } + }, + "user_model.LoginRequest": { "type": "object", "properties": { "user_id": { @@ -125,12 +272,38 @@ var doc = `{ } } }, - "demo.loginResponse": { + "user_model.LoginResponse": { "type": "object", "properties": { "authorization": { "description": "签名", "type": "string" + }, + "expire_time": { + "description": "过期时间", + "type": "integer" + } + } + }, + "user_model.UpdateNickNameByIDRequest": { + "type": "object", + "properties": { + "id": { + "description": "用户主键ID", + "type": "integer" + }, + "nick_name": { + "description": "昵称", + "type": "string" + } + } + }, + "user_model.UpdateNickNameByIDResponse": { + "type": "object", + "properties": { + "id": { + "description": "用户主键ID", + "type": "integer" } } } diff --git a/docs/swagger.json b/docs/swagger.json index f119341..feb764c 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -58,9 +58,9 @@ } } }, - "/user/login": { + "/user/create": { "post": { - "description": "登录获取 Authorization 码", + "description": "创建用户", "consumes": [ "application/json" ], @@ -70,23 +70,123 @@ "tags": [ "Demo" ], - "summary": "登录获取 Authorization 码", + "summary": "创建用户", "parameters": [ { "description": "请求信息", - "name": "loginRequest", + "name": "RequestInfo", "in": "body", "required": true, "schema": { - "$ref": "#/definitions/demo.loginRequest" + "$ref": "#/definitions/user_model.CreateRequest" } } ], "responses": { "200": { - "description": "签名信息", + "description": "返回信息", "schema": { - "$ref": "#/definitions/demo.loginResponse" + "$ref": "#/definitions/user_model.CreateResponse" + } + } + } + } + }, + "/user/info/{username}": { + "get": { + "description": "用户详情", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Demo" + ], + "summary": "用户详情", + "parameters": [ + { + "type": "string", + "description": "用户名", + "name": "username", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "返回信息", + "schema": { + "$ref": "#/definitions/user_model.DetailResponse" + } + } + } + } + }, + "/user/login": { + "post": { + "description": "用户登录", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Demo" + ], + "summary": "用户登录", + "parameters": [ + { + "description": "请求信息", + "name": "RequestInfo", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/user_model.LoginRequest" + } + } + ], + "responses": { + "200": { + "description": "返回信息", + "schema": { + "$ref": "#/definitions/user_model.LoginResponse" + } + } + } + } + }, + "/user/update": { + "post": { + "description": "更新用户名称", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Demo" + ], + "summary": "更新用户名称", + "parameters": [ + { + "description": "请求信息", + "name": "RequestInfo", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/user_model.UpdateNickNameByIDRequest" + } + } + ], + "responses": { + "200": { + "description": "返回信息", + "schema": { + "$ref": "#/definitions/user_model.UpdateNickNameByIDResponse" } } } @@ -94,7 +194,54 @@ } }, "definitions": { - "demo.loginRequest": { + "user_model.CreateRequest": { + "type": "object", + "properties": { + "mobile": { + "description": "手机号", + "type": "string" + }, + "nick_name": { + "description": "昵称", + "type": "string" + }, + "user_name": { + "description": "用户名", + "type": "string" + } + } + }, + "user_model.CreateResponse": { + "type": "object", + "properties": { + "id": { + "description": "主键ID", + "type": "integer" + } + } + }, + "user_model.DetailResponse": { + "type": "object", + "properties": { + "id": { + "description": "用户主键ID", + "type": "integer" + }, + "mobile": { + "description": "手机号", + "type": "string" + }, + "nick_name": { + "description": "昵称", + "type": "string" + }, + "user_name": { + "description": "用户名", + "type": "string" + } + } + }, + "user_model.LoginRequest": { "type": "object", "properties": { "user_id": { @@ -107,12 +254,38 @@ } } }, - "demo.loginResponse": { + "user_model.LoginResponse": { "type": "object", "properties": { "authorization": { "description": "签名", "type": "string" + }, + "expire_time": { + "description": "过期时间", + "type": "integer" + } + } + }, + "user_model.UpdateNickNameByIDRequest": { + "type": "object", + "properties": { + "id": { + "description": "用户主键ID", + "type": "integer" + }, + "nick_name": { + "description": "昵称", + "type": "string" + } + } + }, + "user_model.UpdateNickNameByIDResponse": { + "type": "object", + "properties": { + "id": { + "description": "用户主键ID", + "type": "integer" } } } diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 480225e..c766813 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -1,5 +1,38 @@ definitions: - demo.loginRequest: + user_model.CreateRequest: + properties: + mobile: + description: 手机号 + type: string + nick_name: + description: 昵称 + type: string + user_name: + description: 用户名 + type: string + type: object + user_model.CreateResponse: + properties: + id: + description: 主键ID + type: integer + type: object + user_model.DetailResponse: + properties: + id: + description: 用户主键ID + type: integer + mobile: + description: 手机号 + type: string + nick_name: + description: 昵称 + type: string + user_name: + description: 用户名 + type: string + type: object + user_model.LoginRequest: properties: user_id: description: 用户ID(>0) @@ -8,11 +41,29 @@ definitions: description: 用户名 type: string type: object - demo.loginResponse: + user_model.LoginResponse: properties: authorization: description: 签名 type: string + expire_time: + description: 过期时间 + type: integer + type: object + user_model.UpdateNickNameByIDRequest: + properties: + id: + description: 用户主键ID + type: integer + nick_name: + description: 昵称 + type: string + type: object + user_model.UpdateNickNameByIDResponse: + properties: + id: + description: 用户主键ID + type: integer type: object host: 127.0.0.1:9999 info: @@ -54,26 +105,91 @@ paths: summary: 获取用户信息 tags: - Demo - /user/login: + /user/create: post: consumes: - application/json - description: 登录获取 Authorization 码 + description: 创建用户 parameters: - description: 请求信息 in: body - name: loginRequest + name: RequestInfo required: true schema: - $ref: '#/definitions/demo.loginRequest' + $ref: '#/definitions/user_model.CreateRequest' produces: - application/json responses: "200": - description: 签名信息 + description: 返回信息 schema: - $ref: '#/definitions/demo.loginResponse' - summary: 登录获取 Authorization 码 + $ref: '#/definitions/user_model.CreateResponse' + summary: 创建用户 + tags: + - Demo + /user/info/{username}: + get: + consumes: + - application/json + description: 用户详情 + parameters: + - description: 用户名 + in: path + name: username + required: true + type: string + produces: + - application/json + responses: + "200": + description: 返回信息 + schema: + $ref: '#/definitions/user_model.DetailResponse' + summary: 用户详情 + tags: + - Demo + /user/login: + post: + consumes: + - application/json + description: 用户登录 + parameters: + - description: 请求信息 + in: body + name: RequestInfo + required: true + schema: + $ref: '#/definitions/user_model.LoginRequest' + produces: + - application/json + responses: + "200": + description: 返回信息 + schema: + $ref: '#/definitions/user_model.LoginResponse' + summary: 用户登录 + tags: + - Demo + /user/update: + post: + consumes: + - application/json + description: 更新用户名称 + parameters: + - description: 请求信息 + in: body + name: RequestInfo + required: true + schema: + $ref: '#/definitions/user_model.UpdateNickNameByIDRequest' + produces: + - application/json + responses: + "200": + description: 返回信息 + schema: + $ref: '#/definitions/user_model.UpdateNickNameByIDResponse' + summary: 更新用户名称 tags: - Demo swagger: "2.0" diff --git a/go.mod b/go.mod index 54357f2..19d56c7 100644 --- a/go.mod +++ b/go.mod @@ -1,13 +1,19 @@ module github.com/xinliangnote/go-gin-api -go 1.12 +go 1.15 require ( github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 + github.com/davecgh/go-spew v1.1.1 github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/gin-contrib/pprof v1.2.1 github.com/gin-gonic/gin v1.6.3 github.com/go-openapi/spec v0.20.0 // indirect + github.com/go-redis/redis/v7 v7.4.0 + github.com/google/go-cmp v0.5.4 // indirect + github.com/jinzhu/gorm v1.9.16 + github.com/onsi/ginkgo v1.14.2 // indirect + github.com/onsi/gomega v1.10.4 // indirect github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v0.9.3 github.com/rs/cors v1.7.0 @@ -26,4 +32,6 @@ require ( gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df gopkg.in/natefinch/lumberjack.v2 v2.0.0 + gorm.io/driver/mysql v1.0.3 + gorm.io/gorm v1.20.9 ) diff --git a/go.sum b/go.sum index f5ce9fe..033817e 100644 --- a/go.sum +++ b/go.sum @@ -17,6 +17,7 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym 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/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= 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= @@ -26,6 +27,7 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy 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/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= 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= @@ -34,6 +36,7 @@ github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0= 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 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= 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= @@ -41,17 +44,24 @@ github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc 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/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd h1:83Wprp6ROGeiHFAP8WJdI2RoxALQYgdllERc3N5N2DM= +github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= 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/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y= +github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= 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/gzip v0.0.1 h1:ezvKOL6jH+jlzdHNE4h9h8q8uMpDQjyl0NN0Jd7jozc= github.com/gin-contrib/gzip v0.0.1/go.mod h1:fGBJBCdt6qCZuCAOwWuFhBB4OOq9EFqlo5dEaFhhu5w= @@ -95,9 +105,15 @@ github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD87 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/v7 v7.4.0 h1:7obg6wUoj05T0EpY0o8B59S9w5yeMWql7sw2kwNW1x4= +github.com/go-redis/redis/v7 v7.4.0/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg= +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-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= 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= @@ -108,10 +124,21 @@ github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y 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/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M= +github.com/google/go-cmp v0.5.4/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= @@ -146,6 +173,15 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO 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/jinzhu/gorm v1.9.16 h1:+IyIjPEABKRpsu/F8OvDPy9fyQlgsg2luMV2ZIH5i5o= +github.com/jinzhu/gorm v1.9.16/go.mod h1:G3LB3wezTOWM2ITLzPxEXgSkOXAntiLHS7UdBefADcs= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.0.1 h1:HjfetcXq097iXP0uoPCdnM4Efp5/9MsM0/M+XOTeR3M= +github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jinzhu/now v1.1.1 h1:g39TucaRWyV3dwDO++eEc6qf8TVIQ/Da48WmqjZ3i7E= +github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= 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= @@ -170,6 +206,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.2.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.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4= +github.com/lib/pq v1.1.1/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= @@ -184,6 +222,8 @@ github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd 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.14.0 h1:mLyGNKR8+Vv9CAU7PphKa2hkEqxxhn8i32J6FPj1/QA= +github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= 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= @@ -203,7 +243,19 @@ github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9 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/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +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.10.1/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.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.10.4 h1:NiTx7EEvBzu9sFOD1zORteLSt3o8gnlvZZwSE9TnY9U= +github.com/onsi/gomega v1.10.4/go.mod h1:g/HbgYopi++010VEqkFgJHKC09uJiW9UkXvMUuKHUCQ= 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= @@ -231,9 +283,11 @@ github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6So github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +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/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= @@ -282,7 +336,9 @@ github.com/ugorji/go/codec v0.0.0-20181022190402-e5e69e061d4f/go.mod h1:VFNgLljT github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/ugorji/go/codec v1.1.13 h1:013LbFhocBoIqgHeIHKlV4JWYhqogATYWZhIcH0WHn4= github.com/ugorji/go/codec v1.1.13/go.mod h1:oNVt3Dq+FO91WNQ/9JnHKQP2QJxTzoN7wCBFCq1OeuU= +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.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -303,9 +359,12 @@ go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= 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-20190325154230-a5d413f7728c/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-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= 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= @@ -329,8 +388,10 @@ 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/mod v0.4.0 h1:8pl+sMODzuvGJkmj2W4kZihvVb5mKm8pB/X44PIQHv8= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 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= @@ -346,8 +407,13 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR golang.org/x/net v0.0.0-20190611141213-3f473d35a33a/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-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201224014010-6772e930b67b h1:iFwSg7t5GZmB/Q5TjiEAsdoLDrdJRC1RiF2WhuV29Qw= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -362,6 +428,7 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ 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= @@ -375,7 +442,13 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae/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-20191010194322-b09406accb47/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/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/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-20201223074533-0d417f636930 h1:vRgIt+nup/B/BwIS0g2oC0haq0iqbV3ZA+u6+0TlNCo= @@ -418,6 +491,7 @@ golang.org/x/tools v0.0.0-20201226215659-b1c90890d22a h1:pdfjQ7VswBeGam3EpuEJ4e8 golang.org/x/tools v0.0.0-20201226215659-b1c90890d22a/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= @@ -441,15 +515,24 @@ google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvx 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/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/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/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE= @@ -459,6 +542,8 @@ 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= @@ -471,6 +556,11 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/mysql v1.0.3 h1:+JKBYPfn1tygR1/of/Fh2T8iwuVwzt+PEJmKaXzMQXg= +gorm.io/driver/mysql v1.0.3/go.mod h1:twGxftLBlFgNVNakL7F+P/x9oYqoymG3YYT8cAfI9oI= +gorm.io/gorm v1.20.4/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw= +gorm.io/gorm v1.20.9 h1:M3aIZKXAC1PtPVu9t3WGwkBTE1le5c2telz3I/qjRNg= +gorm.io/gorm v1.20.9/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw= 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= diff --git a/init/README.md b/init/README.md new file mode 100644 index 0000000..e28108a --- /dev/null +++ b/init/README.md @@ -0,0 +1,5 @@ +## init + +项目初始脚本。 + +- DB 相关 SQL; \ No newline at end of file diff --git a/init/db/tables.sql b/init/db/tables.sql new file mode 100644 index 0000000..fe31b39 --- /dev/null +++ b/init/db/tables.sql @@ -0,0 +1,10 @@ +CREATE TABLE `user_demo` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `user_name` varchar(32) NOT NULL DEFAULT '' COMMENT '用户名', + `nick_name` varchar(100) NOT NULL DEFAULT '' COMMENT '昵称', + `mobile` varchar(20) NOT NULL DEFAULT '' COMMENT '手机号', + `is_deleted` tinyint(1) NOT NULL DEFAULT '-1' COMMENT '是否删除 1:是 -1:否', + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户Demo表'; \ No newline at end of file diff --git a/internal/pkg/errno/README.md b/internal/api/code/README.md similarity index 91% rename from internal/pkg/errno/README.md rename to internal/api/code/README.md index a8842a0..41a0c20 100644 --- a/internal/pkg/errno/README.md +++ b/internal/api/code/README.md @@ -1,7 +1,7 @@ ## 错误码规则 - 错误码需在 `code.go` 文件中定义。 -- 错误码需为 > 0 的数,反之表示正确。 +- 错误码需为 > 1 的数,反之表示正确。 #### 错误码为 5 位数 diff --git a/internal/api/code/code.go b/internal/api/code/code.go new file mode 100644 index 0000000..654f86a --- /dev/null +++ b/internal/api/code/code.go @@ -0,0 +1,28 @@ +package code + +import ( + "net/http" + + "github.com/xinliangnote/go-gin-api/pkg/errno" +) + +var ( + // OK + OK = errno.NewError(1, "OK") + + // 服务级错误码 + ErrServer = errno.NewError(10101, http.StatusText(http.StatusInternalServerError)) + ErrManyRequest = errno.NewError(10102, "Too many requests") + + ErrParam = errno.NewError(10110, "参数有误") + ErrSignParam = errno.NewError(10111, "缺少签名") + ErrSign = errno.NewError(10112, "签名有误") + + // 模块级错误码 - 用户模块 + ErrUser = errno.NewError(20101, "非法用户") + ErrUserCreate = errno.NewError(20102, "创建用户失败") + ErrUserUpdate = errno.NewError(20103, "更新用户失败") + ErrUserSearch = errno.NewError(20104, "查询用户失败") + + // ... +) diff --git a/internal/api/controller/README.md b/internal/api/controller/README.md new file mode 100644 index 0000000..7a7eb9b --- /dev/null +++ b/internal/api/controller/README.md @@ -0,0 +1,9 @@ +## controller + +控制器层。 + +主要接收参数、验证参数、调用 `service` 层的业务逻辑处理,最后返回数据。 + +命名规范: + +- 包名以 `_handler` 结尾。 \ No newline at end of file diff --git a/internal/api/controller/demo/demo.go b/internal/api/controller/demo/demo.go index 643b9d2..02c134e 100644 --- a/internal/api/controller/demo/demo.go +++ b/internal/api/controller/demo/demo.go @@ -1,18 +1,13 @@ package demo import ( - "fmt" "net/url" "time" - "github.com/xinliangnote/go-gin-api/internal/pkg/configs" + "github.com/xinliangnote/go-gin-api/internal/api/code" "github.com/xinliangnote/go-gin-api/internal/pkg/core" - "github.com/xinliangnote/go-gin-api/internal/pkg/errno" "github.com/xinliangnote/go-gin-api/internal/pkg/jsonparse" - "github.com/xinliangnote/go-gin-api/pkg/aes" "github.com/xinliangnote/go-gin-api/pkg/httpclient" - "github.com/xinliangnote/go-gin-api/pkg/md5" - "github.com/xinliangnote/go-gin-api/pkg/rsa" "go.uber.org/zap" ) @@ -41,16 +36,16 @@ func (d *Demo) Get() core.HandlerFunc { return func(c core.Context) { req := new(request) if err := c.ShouldBindURI(req); err != nil { - c.SetPayload(errno.ErrParam) + c.SetPayload(code.ErrParam) return } if req.Name != "Tom" { - c.SetPayload(errno.ErrUser) + c.SetPayload(code.ErrUser) return } - c.SetPayload(errno.OK.WithData(&response{ + c.SetPayload(code.OK.WithData(&response{ Name: "Tom", Job: "Student", })) @@ -70,16 +65,16 @@ func (d *Demo) Post() core.HandlerFunc { return func(c core.Context) { req := new(request) if err := c.ShouldBindPostForm(req); err != nil { - c.SetPayload(errno.ErrParam) + c.SetPayload(code.ErrParam) return } if req.Name != "Jack" { - c.SetPayload(errno.ErrUser) + c.SetPayload(code.ErrUser) return } - c.SetPayload(errno.OK.WithData(&response{ + c.SetPayload(code.OK.WithData(&response{ Name: "Jack", Job: "Teacher", })) @@ -109,18 +104,18 @@ func (d *Demo) User() core.HandlerFunc { return func(c core.Context) { req := new(request) if err := c.ShouldBindURI(req); err != nil { - c.SetPayload(errno.ErrParam) + c.SetPayload(code.ErrParam) return } if req.Name != "Tom" { - c.SetPayload(errno.ErrUser) + c.SetPayload(code.ErrUser) return } body1, err1 := httpclient.Get("http://127.0.0.1:9999/demo/get/"+req.Name, nil, httpclient.WithTTL(time.Second*2), - httpclient.WithJournal(c.Journal()), + httpclient.WithTrace(c.Trace()), httpclient.WithLogger(c.Logger()), httpclient.WithHeader("Authorization", c.GetHeader("Authorization")), ) @@ -132,7 +127,7 @@ func (d *Demo) User() core.HandlerFunc { params.Set("name", "Jack") body2, err2 := httpclient.PostForm("http://127.0.0.1:9999/demo/post", params, httpclient.WithTTL(time.Second*2), - httpclient.WithJournal(c.Journal()), + httpclient.WithTrace(c.Trace()), httpclient.WithLogger(c.Logger()), httpclient.WithHeader("Authorization", c.GetHeader("Authorization")), ) @@ -153,81 +148,6 @@ func (d *Demo) User() core.HandlerFunc { }, } } - c.SetPayload(errno.OK.WithData(data)) - } -} - -func (d *Demo) RsaTest() core.HandlerFunc { - return func(c core.Context) { - startTime := time.Now() - encryptStr := "param_1=xxx¶m_2=xxx&ak=xxx&ts=1111111111" - count := 500 - - cfg := configs.Get().Rsa - rsaPublic := rsa.NewPublic(cfg.Public) - rsaPrivate := rsa.NewPrivate(cfg.Private) - - for i := 0; i < count; i++ { - // 生成签名 - sn, err := rsaPublic.Encrypt(encryptStr) - if err != nil { - d.logger.Error("rsa public encrypt err", zap.Error(err)) - } - - // 验证签名 - _, err = rsaPrivate.Decrypt(sn) - if err != nil { - d.logger.Error("rsa private decrypt err", zap.Error(err)) - } - } - c.SetPayload(errno.OK. - WithData(fmt.Sprintf("%v次 - %v", count, time.Since(startTime))), - ) - } -} - -func (d *Demo) AesTest() core.HandlerFunc { - return func(c core.Context) { - startTime := time.Now() - encryptStr := "param_1=xxx¶m_2=xxx&ak=xxx&ts=1111111111" - count := 1000000 - - cfg := configs.Get().Aes - aes := aes.New(cfg.Key, cfg.Iv) - for i := 0; i < count; i++ { - // 生成签名 - sn, err := aes.Encrypt(encryptStr) - if err != nil { - d.logger.Error("aes encrypt err", zap.Error(err)) - } - - // 验证签名 - _, err = aes.Decrypt(sn) - if err != nil { - d.logger.Error("aes decrypt err", zap.Error(err)) - } - } - c.SetPayload(errno.OK. - WithData(fmt.Sprintf("%v次 - %v", count, time.Since(startTime)))) - } -} - -func (d *Demo) MD5Test() core.HandlerFunc { - return func(c core.Context) { - startTime := time.Now() - encryptStr := "param_1=xxx¶m_2=xxx&ak=xxx&ts=1111111111" - count := 1000000 - - md5 := md5.New() - for i := 0; i < count; i++ { - // 生成签名 - md5.Encrypt(encryptStr) - - // 验证签名 - md5.Encrypt(encryptStr) - } - c.SetPayload(errno.OK. - WithData(fmt.Sprintf("%v次 - %v", count, time.Since(startTime))), - ) + c.SetPayload(code.OK.WithData(data)) } } diff --git a/internal/api/controller/demo/login.go b/internal/api/controller/demo/login.go deleted file mode 100644 index 06d3b69..0000000 --- a/internal/api/controller/demo/login.go +++ /dev/null @@ -1,47 +0,0 @@ -package demo - -import ( - "github.com/xinliangnote/go-gin-api/internal/pkg/core" - "github.com/xinliangnote/go-gin-api/internal/pkg/errno" - "github.com/xinliangnote/go-gin-api/internal/pkg/token" - - "go.uber.org/zap" -) - -type loginRequest struct { - UserID int `json:"user_id" form:"user_id"` // 用户ID(>0) - UserName string `json:"user_name" form:"user_name"` // 用户名 -} - -type loginResponse struct { - Authorization string `json:"authorization"` // 签名 -} - -// 登录获取 Authorization 码 -// @Summary 登录获取 Authorization 码 -// @Description 登录获取 Authorization 码 -// @Tags Demo -// @Accept json -// @Produce json -// @Param loginRequest body loginRequest true "请求信息" -// @Success 200 {object} loginResponse "签名信息" -// @Router /user/login [post] -func (d *Demo) Login() core.HandlerFunc { - return func(c core.Context) { - req := new(loginRequest) - res := new(loginResponse) - if err := c.ShouldBindJSON(req); err != nil { - c.SetPayload(errno.ErrParam) - return - } - - tokenString, err := token.Sign(req.UserID, req.UserName) - if err != nil { - d.logger.Error("token sign err", zap.Error(err)) - res.Authorization = "" - } else { - res.Authorization = tokenString - } - c.SetPayload(errno.OK.WithData(res)) - } -} diff --git a/internal/api/controller/user_handler/user.go b/internal/api/controller/user_handler/user.go new file mode 100644 index 0000000..e05f4b4 --- /dev/null +++ b/internal/api/controller/user_handler/user.go @@ -0,0 +1,180 @@ +package user_handler + +import ( + "github.com/xinliangnote/go-gin-api/configs" + "github.com/xinliangnote/go-gin-api/internal/api/code" + "github.com/xinliangnote/go-gin-api/internal/api/model/user_model" + "github.com/xinliangnote/go-gin-api/internal/api/repository/cache_repo" + "github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo" + "github.com/xinliangnote/go-gin-api/internal/api/service/user_service" + "github.com/xinliangnote/go-gin-api/internal/pkg/core" + "github.com/xinliangnote/go-gin-api/pkg/token" + + "go.uber.org/zap" +) + +var _ UserDemo = (*userDemo)(nil) + +type UserDemo interface { + // i 为了避免被其他包实现 + i() + // 创建用户 + Create() core.HandlerFunc + // 通过用户主键ID更新用户昵称 + UpdateNickNameByID() core.HandlerFunc + // 用户登录 + Login() core.HandlerFunc + // 用户详情 + Detail() core.HandlerFunc +} + +type userDemo struct { + logger *zap.Logger + userService user_service.UserService +} + +func NewUserDemo(logger *zap.Logger, db db_repo.Repo, cache cache_repo.Repo) UserDemo { + return &userDemo{ + logger: logger, + userService: user_service.NewUserService(db, cache), + } +} + +func (u *userDemo) i() {} + +// 创建用户 +// @Summary 创建用户 +// @Description 创建用户 +// @Tags Demo +// @Accept json +// @Produce json +// @Param RequestInfo body user_model.CreateRequest true "请求信息" +// @Success 200 {object} user_model.CreateResponse "返回信息" +// @Router /user/create [post] +func (u *userDemo) Create() core.HandlerFunc { + return func(c core.Context) { + req := new(user_model.CreateRequest) + res := new(user_model.CreateResponse) + if err := c.ShouldBindJSON(req); err != nil { + u.logger.Error("[user] should bind json err", zap.Error(err)) + c.SetPayload(code.ErrParam) + return + } + + id, err := u.userService.Create(c, req) + if err != nil { + u.logger.Error("[user] Create err", zap.Error(err)) + c.SetPayload(code.ErrUserCreate) + return + } + + res.Id = id + c.SetPayload(code.OK.WithData(res)) + } +} + +// 更新用户名称 +// @Summary 更新用户名称 +// @Description 更新用户名称 +// @Tags Demo +// @Accept json +// @Produce json +// @Param RequestInfo body user_model.UpdateNickNameByIDRequest true "请求信息" +// @Success 200 {object} user_model.UpdateNickNameByIDResponse "返回信息" +// @Router /user/update [post] +func (u *userDemo) UpdateNickNameByID() core.HandlerFunc { + return func(c core.Context) { + req := new(user_model.UpdateNickNameByIDRequest) + res := new(user_model.UpdateNickNameByIDResponse) + if err := c.ShouldBindJSON(req); err != nil { + u.logger.Error("[user] should bind json err", zap.Error(err)) + c.SetPayload(code.ErrParam) + return + } + + err := u.userService.UpdateNickNameByID(c, req.Id, req.NickName) + if err != nil { + u.logger.Error("[user] UpdateNickNameByID err", zap.Error(err)) + c.SetPayload(code.ErrUserUpdate) + return + } + + res.Id = req.Id + c.SetPayload(code.OK.WithData(res)) + } +} + +// 用户登录 +// @Summary 用户登录 +// @Description 用户登录 +// @Tags Demo +// @Accept json +// @Produce json +// @Param RequestInfo body user_model.LoginRequest true "请求信息" +// @Success 200 {object} user_model.LoginResponse "返回信息" +// @Router /user/login [post] +func (u *userDemo) Login() core.HandlerFunc { + return func(c core.Context) { + req := new(user_model.LoginRequest) + res := new(user_model.LoginResponse) + if err := c.ShouldBindJSON(req); err != nil { + u.logger.Error("should bind json err", zap.Error(err)) + c.SetPayload(code.ErrParam) + return + } + + cfg := configs.Get().JWT + tokenString, err := token.New(cfg.Secret).Sign(req.UserID, req.UserName) + if err != nil { + u.logger.Error("token sign err", zap.Error(err)) + c.SetPayload(code.ErrSign) + return + } + + claims, err := token.New(cfg.Secret).Parse(tokenString) + if err != nil { + u.logger.Error("token parse err", zap.Error(err)) + c.SetPayload(code.ErrSign) + return + } + + res.Authorization = tokenString + res.ExpireTime = claims.ExpiresAt + + c.SetPayload(code.OK.WithData(res)) + } +} + +// 用户详情 +// @Summary 用户详情 +// @Description 用户详情 +// @Tags Demo +// @Accept json +// @Produce json +// @Param username path string true "用户名" +// @Success 200 {object} user_model.DetailResponse "返回信息" +// @Router /user/info/{username} [get] +func (u *userDemo) Detail() core.HandlerFunc { + return func(c core.Context) { + req := new(user_model.DetailRequest) + res := new(user_model.DetailResponse) + if err := c.ShouldBindURI(req); err != nil { + u.logger.Error("should bind uri err", zap.Error(err)) + c.SetPayload(code.ErrParam) + return + } + + user, err := u.userService.GetUserByUserName(c, req.UserName) + if err != nil { + u.logger.Error("[user] GetUserByUserName err", zap.Error(err)) + c.SetPayload(code.ErrUserSearch) + return + } + + res.Id = user.Id + res.UserName = user.UserName + res.NickName = user.NickName + res.Mobile = user.Mobile + c.SetPayload(code.OK.WithData(res)) + } +} diff --git a/internal/api/model/README.md b/internal/api/model/README.md new file mode 100644 index 0000000..9a8218b --- /dev/null +++ b/internal/api/model/README.md @@ -0,0 +1,10 @@ +## model + +实体层。 + +- 请求实体、返回实体。 +- 数据库实体。 + +命名规范: + +- 包名以 `_model` 结尾。 \ No newline at end of file diff --git a/internal/api/model/user_model/user.go b/internal/api/model/user_model/user.go new file mode 100644 index 0000000..961af32 --- /dev/null +++ b/internal/api/model/user_model/user.go @@ -0,0 +1,68 @@ +package user_model + +import ( + "time" +) + +// 用户Demo表 +type UserDemo struct { + Id uint `gorm:"column:id;primary_key;AUTO_INCREMENT"` // 主键 + UserName string `gorm:"column:user_name;NOT NULL"` // 用户名 + NickName string `gorm:"column:nick_name;NOT NULL"` // 昵称 + Mobile string `gorm:"column:mobile;NOT NULL"` // 手机号 + IsDeleted int `gorm:"column:is_deleted;default:-1;NOT NULL"` // 是否删除 1:是 -1:否 + CreatedAt time.Time `gorm:"column:created_at;default:CURRENT_TIMESTAMP;NOT NULL"` // 创建时间 + UpdatedAt time.Time `gorm:"column:updated_at;default:CURRENT_TIMESTAMP;NOT NULL"` // 更新时间 +} + +func (m *UserDemo) TableName() string { + return "user_demo" +} + +// user_handler Create Request +type CreateRequest struct { + UserName string `json:"user_name"` // 用户名 + NickName string `json:"nick_name"` // 昵称 + Mobile string `json:"mobile"` // 手机号 +} + +// user_handler Create Response +type CreateResponse struct { + Id uint `json:"id"` //主键ID +} + +// user_handler UpdateNickNameByID Request +type UpdateNickNameByIDRequest struct { + Id uint `json:"id"` // 用户主键ID + NickName string `json:"nick_name"` // 昵称 +} + +// user_handler UpdateNickNameByID Response +type UpdateNickNameByIDResponse struct { + Id uint `json:"id"` // 用户主键ID +} + +// user_handler Login Request +type LoginRequest struct { + UserID int `json:"user_id" form:"user_id"` // 用户ID(>0) + UserName string `json:"user_name" form:"user_name"` // 用户名 +} + +// user_handler Login Response +type LoginResponse struct { + Authorization string `json:"authorization"` // 签名 + ExpireTime int64 `json:"expire_time"` // 过期时间 +} + +// user_handler Detail Request +type DetailRequest struct { + UserName string `uri:"username"` // 用户名 +} + +// user_handler Detail Response +type DetailResponse struct { + Id uint `json:"id"` // 用户主键ID + UserName string `json:"user_name"` // 用户名 + NickName string `json:"nick_name"` // 昵称 + Mobile string `json:"mobile"` // 手机号 +} diff --git a/internal/api/repository/README.md b/internal/api/repository/README.md new file mode 100644 index 0000000..bfb3823 --- /dev/null +++ b/internal/api/repository/README.md @@ -0,0 +1,29 @@ +## repository + +数据访问层。 + +- `./db_repo` 访问 DB 数据 +- `./cache_repo` 访问 Cache 数据 +- `./third_party_request` 访问外部 HTTP 接口数据。 + +SQL 建议: +- 禁止使用 SQL k v 拼接,好处是避免 SQL 注入; +- 禁止使用连表查询,好处是易扩展,比如分库分表; +- 禁止使用万能方法,好处是便于后期维护,比如字段调整; +- 禁止使用删除方法,好处是避免数据丢失; +- 建议每张表需包含字段:主键(id)、标记删除(is_deteled)、创建时间(created_at)、更新时间(updated_at) + +```mysql +`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', +`is_deleted` tinyint(1) NOT NULL DEFAULT '-1' COMMENT '是否删除 1:是 -1:否', +`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', +`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', +``` +什么是万能方法? + +指的是特别灵活的查询,比如通过非固定的参数返回全部字段,建议做到需要什么返回什么,不要返回大而全的数据,更新时也不能传递什么参数更新什么参数,更新字段要提前约定好。 + +命名规范: + +- 包名应以 `_repo` 结尾; +- `./db_repo` 目录下的包名以 `数据表名`+ `_repo` 命名; diff --git a/internal/api/repository/cache_repo/init.go b/internal/api/repository/cache_repo/init.go new file mode 100644 index 0000000..f16d60c --- /dev/null +++ b/internal/api/repository/cache_repo/init.go @@ -0,0 +1,120 @@ +package cache_repo + +import ( + "time" + + "github.com/xinliangnote/go-gin-api/configs" + + "github.com/go-redis/redis/v7" + "github.com/pkg/errors" +) + +var _ Repo = (*cacheRepo)(nil) + +type Repo interface { + i() + Set(key, value string, ttl time.Duration) error + Get(key string) (string, error) + TTL(key string) (time.Duration, error) + Expire(key string, ttl time.Duration) bool + ExpireAt(key string, ttl time.Time) bool + Del(keys ...string) bool + Incr(key string) int64 + Close() +} + +type cacheRepo struct { + client *redis.Client +} + +func New() (Repo, error) { + client, err := redisConnect() + if err != nil { + return nil, err + } + + return &cacheRepo{ + client: client, + }, nil +} + +func (c *cacheRepo) i() {} + +func redisConnect() (*redis.Client, error) { + cfg := configs.Get().Redis + client := redis.NewClient(&redis.Options{ + Addr: cfg.Addr, + Password: cfg.Pass, + DB: cfg.Db, + MaxRetries: cfg.MaxRetries, + PoolSize: cfg.PoolSize, + MinIdleConns: cfg.MinIdleConns, + }) + + if err := client.Ping().Err(); err != nil { + return nil, errors.Wrap(err, "ping redis err") + } + + return client, nil +} + +// Set set some into redis +func (c *cacheRepo) Set(key, value string, ttl time.Duration) error { + if err := c.client.Set(key, value, ttl).Err(); err != nil { + return errors.Wrapf(err, "redis set key: %s err", key) + } + + return nil +} + +// Get get some key from redis +func (c *cacheRepo) Get(key string) (string, error) { + value, err := c.client.Get(key).Result() + if err != nil { + return "", errors.Wrapf(err, "redis get key: %s err", key) + } + + return value, nil +} + +// TTL get some key from redis +func (c *cacheRepo) TTL(key string) (time.Duration, error) { + ttl, err := c.client.TTL(key).Result() + if err != nil { + return -1, errors.Wrapf(err, "redis get key: %s err", key) + } + + return ttl, nil +} + +// Expire expire some key +func (c *cacheRepo) Expire(key string, ttl time.Duration) bool { + ok, _ := c.client.Expire(key, ttl).Result() + return ok +} + +// ExpireAt expire some key at some time +func (c *cacheRepo) ExpireAt(key string, ttl time.Time) bool { + ok, _ := c.client.ExpireAt(key, ttl).Result() + return ok +} + +// Del del some key from redis +func (c *cacheRepo) Del(keys ...string) bool { + if len(keys) == 0 { + return true + } + + value, _ := c.client.Del(keys...).Result() + return value > 0 +} + +func (c *cacheRepo) Incr(key string) int64 { + value, _ := c.client.Incr(key).Result() + return value +} + +// Close close redis client +func (c *cacheRepo) Close() { + c.client.Close() +} diff --git a/internal/api/repository/db_repo/init.go b/internal/api/repository/db_repo/init.go new file mode 100644 index 0000000..c0aa3fc --- /dev/null +++ b/internal/api/repository/db_repo/init.go @@ -0,0 +1,112 @@ +package db_repo + +import ( + "fmt" + "time" + + "github.com/xinliangnote/go-gin-api/configs" + + "github.com/pkg/errors" + "gorm.io/driver/mysql" + "gorm.io/gorm" +) + +var _ Repo = (*dbRepo)(nil) + +type Repo interface { + i() + GetDbR() *gorm.DB + GetDbW() *gorm.DB + DbRClose() error + DbWClose() error +} + +type dbRepo struct { + DbR *gorm.DB + DbW *gorm.DB +} + +func New() (Repo, error) { + cfg := configs.Get().MySQL + dbr, err := dbConnect(cfg.Read.User, cfg.Read.Pass, cfg.Read.Addr, cfg.Read.Name) + if err != nil { + return nil, err + } + + dbw, err := dbConnect(cfg.Write.User, cfg.Write.Pass, cfg.Write.Addr, cfg.Write.Name) + if err != nil { + return nil, err + } + + return &dbRepo{ + DbR: dbr, + DbW: dbw, + }, nil +} + +func (d *dbRepo) i() {} + +func (d *dbRepo) GetDbR() *gorm.DB { + return d.DbR +} + +func (d *dbRepo) GetDbW() *gorm.DB { + return d.DbW +} + +func (d *dbRepo) DbRClose() error { + sqlDB, err := d.DbR.DB() + if err != nil { + return err + } + return sqlDB.Close() +} + +func (d *dbRepo) DbWClose() error { + sqlDB, err := d.DbW.DB() + if err != nil { + return err + } + return sqlDB.Close() +} + +func dbConnect(user, pass, addr, dbName string) (*gorm.DB, error) { + dsn := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8mb4&parseTime=%t&loc=%s", + user, + pass, + addr, + dbName, + true, + "Local") + + db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{ + //Logger: logger.Default.LogMode(logger.Info), // 日志配置 + }) + + if err != nil { + return nil, errors.Wrap(err, fmt.Sprintf("[db connection failed] Database name: %s", dbName)) + } + + db.Set("gorm:table_options", "CHARSET=utf8mb4") + + cfg := configs.Get().MySQL.Base + + sqlDB, err := db.DB() + if err != nil { + return nil, err + } + + // 设置连接池 用于设置最大打开的连接数,默认值为0表示不限制.设置最大的连接数,可以避免并发太高导致连接mysql出现too many connections的错误。 + sqlDB.SetMaxOpenConns(cfg.MaxOpenConn) + + // 设置最大连接数 用于设置闲置的连接数.设置闲置的连接数则当开启的一个连接使用完成后可以放在池里等候下一次使用。 + sqlDB.SetMaxIdleConns(cfg.MaxIdleConn) + + // 设置最大连接超时 + sqlDB.SetConnMaxLifetime(time.Minute * cfg.ConnMaxLifeTime) + + // 使用插件 + db.Use(&TracePlugin{}) + + return db, nil +} diff --git a/internal/api/repository/db_repo/plugin.go b/internal/api/repository/db_repo/plugin.go new file mode 100644 index 0000000..b415466 --- /dev/null +++ b/internal/api/repository/db_repo/plugin.go @@ -0,0 +1,86 @@ +package db_repo + +import ( + "time" + + "github.com/xinliangnote/go-gin-api/internal/pkg/core" + "github.com/xinliangnote/go-gin-api/internal/pkg/trace" + "github.com/xinliangnote/go-gin-api/pkg/time_parse" + + "gorm.io/gorm" + "gorm.io/gorm/utils" +) + +const ( + callBackBeforeName = "core:before" + callBackAfterName = "core:after" + coreContext = "_core_context" + startTime = "_start_time" +) + +type TracePlugin struct{} + +func (op *TracePlugin) Name() string { + return "tracePlugin" +} + +func before(db *gorm.DB) { + db.InstanceSet(coreContext, db.Statement.Context) + db.InstanceSet(startTime, time.Now()) + return +} + +func after(db *gorm.DB) { + _ctx, isExist := db.InstanceGet(coreContext) + if !isExist { + return + } + + ctx, ok := _ctx.(core.Context) + if !ok { + return + } + + _ts, isExist := db.InstanceGet(startTime) + if !isExist { + return + } + + ts, ok := _ts.(time.Time) + if !ok { + return + } + + sql := db.Dialector.Explain(db.Statement.SQL.String(), db.Statement.Vars...) + + sqlInfo := new(trace.SQL) + sqlInfo.Time = time_parse.CSTLayoutString() + sqlInfo.SQL = sql + sqlInfo.Src = utils.FileWithLineNum() + sqlInfo.Rows = db.Statement.RowsAffected + sqlInfo.Duration = time.Since(ts).Seconds() + ctx.Trace().AppendSQL(sqlInfo) + + return +} + +func (op *TracePlugin) Initialize(db *gorm.DB) (err error) { + // 开始前 + _ = db.Callback().Create().Before("gorm:before_create").Register(callBackBeforeName, before) + _ = db.Callback().Query().Before("gorm:query").Register(callBackBeforeName, before) + _ = db.Callback().Delete().Before("gorm:before_delete").Register(callBackBeforeName, before) + _ = db.Callback().Update().Before("gorm:setup_reflect_value").Register(callBackBeforeName, before) + _ = db.Callback().Row().Before("gorm:row").Register(callBackBeforeName, before) + _ = db.Callback().Raw().Before("gorm:raw").Register(callBackBeforeName, before) + + // 结束后 + _ = db.Callback().Create().After("gorm:after_create").Register(callBackAfterName, after) + _ = db.Callback().Query().After("gorm:after_query").Register(callBackAfterName, after) + _ = db.Callback().Delete().After("gorm:after_delete").Register(callBackAfterName, after) + _ = db.Callback().Update().After("gorm:after_update").Register(callBackAfterName, after) + _ = db.Callback().Row().After("gorm:row").Register(callBackAfterName, after) + _ = db.Callback().Raw().After("gorm:raw").Register(callBackAfterName, after) + return +} + +var _ gorm.Plugin = &TracePlugin{} diff --git a/internal/api/repository/db_repo/user_demo_repo/user_demo.go b/internal/api/repository/db_repo/user_demo_repo/user_demo.go new file mode 100644 index 0000000..cffc2e3 --- /dev/null +++ b/internal/api/repository/db_repo/user_demo_repo/user_demo.go @@ -0,0 +1,71 @@ +package user_demo_repo + +import ( + "github.com/xinliangnote/go-gin-api/internal/api/model/user_model" + "github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo" + "github.com/xinliangnote/go-gin-api/internal/pkg/core" + + "github.com/jinzhu/gorm" + "github.com/pkg/errors" +) + +var _ UserRepo = (*userRepo)(nil) + +type UserRepo interface { + // i 为了避免被其他包实现 + i() + Create(ctx core.Context, user user_model.UserDemo) (id uint, err error) + UpdateNickNameByID(ctx core.Context, id uint, username string) (err error) + GetUserByUserName(ctx core.Context, username string) (*user_model.UserDemo, error) + getUserByID(ctx core.Context, id uint) (*user_model.UserDemo, error) +} + +type userRepo struct { + db db_repo.Repo +} + +func NewUserRepo(db db_repo.Repo) UserRepo { + return &userRepo{ + db: db, + } +} + +func (u *userRepo) i() {} + +func (u *userRepo) Create(ctx core.Context, user user_model.UserDemo) (id uint, err error) { + err = u.db.GetDbW().WithContext(ctx).Create(&user).Error + if err != nil { + return 0, errors.Wrap(err, "[user_repo] create user err") + } + return user.Id, nil +} + +func (u *userRepo) getUserByID(ctx core.Context, id uint) (*user_model.UserDemo, error) { + data := new(user_model.UserDemo) + err := u.db.GetDbR().WithContext(ctx).First(data, id).Error + if err != nil && err != gorm.ErrRecordNotFound { + return nil, errors.Wrap(err, "[user_demo] get user data err") + } + return data, nil +} + +func (u *userRepo) UpdateNickNameByID(ctx core.Context, id uint, nickname string) (err error) { + user, err := u.getUserByID(ctx, id) + if err != nil { + return errors.Wrap(err, "[user_demo] update user data err") + } + return u.db.GetDbW().WithContext(ctx).Model(user).Update("nick_name", nickname).Error +} + +func (u *userRepo) GetUserByUserName(ctx core.Context, username string) (*user_model.UserDemo, error) { + data := new(user_model.UserDemo) + err := u.db.GetDbR(). + WithContext(ctx). + Select([]string{"id", "user_name", "nick_name", "mobile"}). + Where("user_name = ?", username). + First(data).Error + if err != nil && err != gorm.ErrRecordNotFound { + return nil, errors.Wrap(err, "[user_demo] get user data err") + } + return data, nil +} diff --git a/internal/router/middleware/auth.go b/internal/api/router/middleware/auth.go similarity index 50% rename from internal/router/middleware/auth.go rename to internal/api/router/middleware/auth.go index 62e0c28..105f352 100644 --- a/internal/router/middleware/auth.go +++ b/internal/api/router/middleware/auth.go @@ -1,27 +1,30 @@ package middleware import ( + "github.com/xinliangnote/go-gin-api/configs" + "github.com/xinliangnote/go-gin-api/internal/api/code" "github.com/xinliangnote/go-gin-api/internal/pkg/core" - "github.com/xinliangnote/go-gin-api/internal/pkg/errno" - "github.com/xinliangnote/go-gin-api/internal/pkg/token" + "github.com/xinliangnote/go-gin-api/pkg/errno" + "github.com/xinliangnote/go-gin-api/pkg/token" ) func AuthHandler(ctx core.Context) (userId int, userName string, err errno.Error) { auth := ctx.GetHeader("Authorization") if auth == "" { - err = errno.ErrSignParam + err = code.ErrSignParam return } - claims, errParse := token.Parse(auth) + cfg := configs.Get().JWT + claims, errParse := token.New(cfg.Secret).Parse(auth) if errParse != nil { - err = errno.ErrSignParam + err = code.ErrSignParam return } userId = claims.UserID if userId <= 0 { - err = errno.ErrSignParam + err = code.ErrSignParam return } userName = claims.UserName diff --git a/internal/api/router/router.go b/internal/api/router/router.go new file mode 100644 index 0000000..317a533 --- /dev/null +++ b/internal/api/router/router.go @@ -0,0 +1,55 @@ +package router + +import ( + "github.com/xinliangnote/go-gin-api/internal/api/controller/demo" + "github.com/xinliangnote/go-gin-api/internal/api/controller/user_handler" + "github.com/xinliangnote/go-gin-api/internal/api/repository/cache_repo" + "github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo" + auth "github.com/xinliangnote/go-gin-api/internal/api/router/middleware" + "github.com/xinliangnote/go-gin-api/internal/pkg/core" + "github.com/xinliangnote/go-gin-api/internal/pkg/metrics" + "github.com/xinliangnote/go-gin-api/internal/pkg/notify" + + "github.com/pkg/errors" + "go.uber.org/zap" +) + +func NewHTTPMux(logger *zap.Logger, db db_repo.Repo, cache cache_repo.Repo) (core.Mux, error) { + + if logger == nil { + return nil, errors.New("logger required") + } + + mux, err := core.New(logger, + core.WithEnableCors(), + core.WithEnableRate(), + core.WithPanicNotify(notify.OnPanicNotify), + core.WithRecordMetrics(metrics.RecordMetrics), + ) + + if err != nil { + panic(err) + } + + demoHandler := demo.NewDemo(logger) + userHandler := user_handler.NewUserDemo(logger, db, cache) + + u := mux.Group("/user") + { + u.POST("/login", userHandler.Login()) + u.POST("/create", userHandler.Create()) + u.GET("/info/:username", userHandler.Detail()) + u.POST("/update", userHandler.UpdateNickNameByID()) + } + + d := mux.Group("/demo", core.WrapAuthHandler(auth.AuthHandler)) //使用 auth 验证 + { + d.GET("user/:name", demoHandler.User()) + + // 模拟数据 + d.GET("get/:name", demoHandler.Get(), core.DisableTrace) + d.POST("post", demoHandler.Post(), core.DisableTrace) + } + + return mux, nil +} diff --git a/internal/api/service/README.md b/internal/api/service/README.md new file mode 100644 index 0000000..c51825c --- /dev/null +++ b/internal/api/service/README.md @@ -0,0 +1,10 @@ +## service + +业务逻辑层。 + +处于 `controller` 层和 `repository` 层之间,依赖接口开发。 + +命令规范: + +- 包名以 `_service` 结尾。 + diff --git a/internal/api/service/user_service/user.go b/internal/api/service/user_service/user.go new file mode 100644 index 0000000..09aa152 --- /dev/null +++ b/internal/api/service/user_service/user.go @@ -0,0 +1,67 @@ +package user_service + +import ( + "github.com/xinliangnote/go-gin-api/internal/api/model/user_model" + "github.com/xinliangnote/go-gin-api/internal/api/repository/cache_repo" + "github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo" + "github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/user_demo_repo" + "github.com/xinliangnote/go-gin-api/internal/pkg/core" +) + +var _ UserService = (*userSer)(nil) + +type UserService interface { + // i 为了避免被其他包实现 + i() + + Create(ctx core.Context, user *user_model.CreateRequest) (id uint, err error) + UpdateNickNameByID(ctx core.Context, id uint, username string) (err error) + GetUserByUserName(ctx core.Context, username string) (user *user_model.UserDemo, err error) +} + +type userSer struct { + db db_repo.Repo + cache cache_repo.Repo + userRepo user_demo_repo.UserRepo +} + +func NewUserService(db db_repo.Repo, cache cache_repo.Repo) UserService { + userRepo := user_demo_repo.NewUserRepo(db) + return &userSer{ + db: db, + cache: cache, + userRepo: userRepo, + } +} + +func (u *userSer) i() {} + +func (u *userSer) Create(ctx core.Context, user *user_model.CreateRequest) (id uint, err error) { + create := user_model.UserDemo{ + UserName: user.UserName, + NickName: user.NickName, + Mobile: user.Mobile, + } + + id, err = u.userRepo.Create(ctx, create) + if err != nil { + return 0, err + } + return +} + +func (u *userSer) UpdateNickNameByID(ctx core.Context, id uint, username string) (err error) { + err = u.userRepo.UpdateNickNameByID(ctx, id, username) + if err != nil { + return nil + } + return nil +} + +func (u *userSer) GetUserByUserName(ctx core.Context, username string) (user *user_model.UserDemo, err error) { + user, err = u.userRepo.GetUserByUserName(ctx, username) + if err != nil { + return nil, err + } + return user, nil +} diff --git a/internal/pkg/configs/configs.go b/internal/pkg/configs/configs.go deleted file mode 100644 index 9cf030d..0000000 --- a/internal/pkg/configs/configs.go +++ /dev/null @@ -1,55 +0,0 @@ -package configs - -import ( - "github.com/xinliangnote/go-gin-api/pkg/env" - - "github.com/spf13/viper" -) - -var config = new(Config) - -type Config struct { - Mail struct { - Host string `mapstructure:"host"` - Port int `mapstructure:"port"` - User string `mapstructure:"user"` - Pass string `mapstructure:"pass"` - To string `mapstructure:"to"` - } `mapstructure:"mail"` - - JWT struct { - Secret string `mapstructure:"secret"` - } `mapstructure:"jwt"` - - Aes struct { - Key string `mapstructure:"key"` - Iv string `mapstructure:"iv"` - } `mapstructure:"aes"` - - Rsa struct { - Private string `mapstructure:"private"` - Public string `mapstructure:"public"` - } `mapstructure:"rsa"` -} - -func init() { - viper.SetConfigName(env.Active().Value() + "_configs") - viper.SetConfigType("toml") - viper.AddConfigPath("./configs") - - if err := viper.ReadInConfig(); err != nil { - panic(err) - } - - if err := viper.Unmarshal(config); err != nil { - panic(err) - } -} - -func Get() Config { - return *config -} - -func ProjectName() string { - return "go-gin-api" -} diff --git a/internal/pkg/core/context.go b/internal/pkg/core/context.go index 11c722d..5d1a18b 100644 --- a/internal/pkg/core/context.go +++ b/internal/pkg/core/context.go @@ -7,9 +7,10 @@ import ( "net/url" "strings" "sync" + "time" - "github.com/xinliangnote/go-gin-api/internal/pkg/errno" - "github.com/xinliangnote/go-gin-api/internal/pkg/journal" + "github.com/xinliangnote/go-gin-api/internal/pkg/trace" + "github.com/xinliangnote/go-gin-api/pkg/errno" "github.com/gin-gonic/gin" "github.com/gin-gonic/gin/binding" @@ -18,11 +19,11 @@ import ( type HandlerFunc func(c Context) -type Journal = journal.T +type Trace = trace.T const ( _Alias = "_alias_" - _JournalName = "_journal_" + _TraceName = "_trace_" _LoggerName = "_logger_" _BodyName = "_body_" _PayloadName = "_payload_" @@ -78,9 +79,9 @@ type Context interface { // Redirect 重定向 Redirect(code int, location string) - Journal() Journal - setJournal(journal Journal) - disableJournal() + Trace() Trace + setTrace(trace Trace) + disableTrace() Logger() *zap.Logger setLogger(logger *zap.Logger) @@ -109,6 +110,11 @@ type Context interface { Host() string Path() string URI() string + + Deadline() (deadline time.Time, ok bool) + Done() <-chan struct{} + Err() error + Value(key interface{}) interface{} } type context struct { @@ -121,7 +127,7 @@ func (c *context) init() { panic(err) } - c.ctx.Set(_BodyName, body) // cache body是为了journal使用 + c.ctx.Set(_BodyName, body) // cache body是为了trace使用 c.ctx.Request.Body = ioutil.NopCloser(bytes.NewBuffer(body)) // re-construct req body } @@ -161,21 +167,21 @@ func (c *context) Redirect(code int, location string) { c.ctx.Redirect(code, location) } -func (c *context) Journal() Journal { - j, ok := c.ctx.Get(_JournalName) - if !ok || j == nil { +func (c *context) Trace() Trace { + t, ok := c.ctx.Get(_TraceName) + if !ok || t == nil { return nil } - return j.(Journal) + return t.(Trace) } -func (c *context) setJournal(journal Journal) { - c.ctx.Set(_JournalName, journal) +func (c *context) setTrace(trace Trace) { + c.ctx.Set(_TraceName, trace) } -func (c *context) disableJournal() { - c.setJournal(nil) +func (c *context) disableTrace() { + c.setTrace(nil) } func (c *context) Logger() *zap.Logger { @@ -303,3 +309,26 @@ func (c *context) URI() string { uri, _ := url.QueryUnescape(c.ctx.Request.URL.RequestURI()) return uri } + +func (c *context) Deadline() (deadline time.Time, ok bool) { + return +} + +func (c *context) Done() <-chan struct{} { + return nil +} + +func (c *context) Err() error { + return nil +} + +func (c *context) Value(key interface{}) interface{} { + if key == 0 { + return c.ctx.Request + } + if keyAsString, ok := key.(string); ok { + val, _ := c.ctx.Get(keyAsString) + return val + } + return nil +} diff --git a/internal/pkg/core/core.go b/internal/pkg/core/core.go index 3217acd..5430e90 100644 --- a/internal/pkg/core/core.go +++ b/internal/pkg/core/core.go @@ -8,9 +8,11 @@ import ( "time" _ "github.com/xinliangnote/go-gin-api/docs" - "github.com/xinliangnote/go-gin-api/internal/pkg/errno" - "github.com/xinliangnote/go-gin-api/internal/pkg/journal" + "github.com/xinliangnote/go-gin-api/internal/api/code" + "github.com/xinliangnote/go-gin-api/internal/pkg/trace" "github.com/xinliangnote/go-gin-api/pkg/color" + "github.com/xinliangnote/go-gin-api/pkg/env" + "github.com/xinliangnote/go-gin-api/pkg/errno" "github.com/gin-contrib/pprof" "github.com/gin-gonic/gin" @@ -105,9 +107,8 @@ func WithEnableRate() Option { } } -// DisableJournal 标识某些请求不记录journal -func DisableJournal(ctx Context) { - ctx.disableJournal() +func DisableTrace(ctx Context) { + ctx.disableTrace() } // AliasForRecordMetrics 对请求uri起个别名,用于prometheus记录指标。 @@ -242,8 +243,10 @@ func New(logger *zap.Logger, options ...Option) (Mux, error) { fmt.Println(color.Blue(_UI)) + fmt.Println(color.Green(fmt.Sprintf("* [register -env %s]", env.Active().Value()))) + // withoutLogPaths 这些请求,默认不记录日志 - withoutJournalPaths := map[string]bool{ + withoutTracePaths := map[string]bool{ "/metrics": true, "/debug/pprof/": true, @@ -259,6 +262,9 @@ func New(logger *zap.Logger, options ...Option) (Mux, error) { "/debug/pprof/threadcreate": true, "/favicon.ico": true, + + "/h/ping": true, + "/h/info": true, } opt := new(option) @@ -318,11 +324,11 @@ func New(logger *zap.Logger, options ...Option) (Mux, error) { context.init() context.setLogger(logger) - if !withoutJournalPaths[ctx.Request.URL.Path] { - if journalID := context.GetHeader(journal.JournalHeader); journalID != "" { - context.setJournal(journal.NewJournal(journalID)) + if !withoutTracePaths[ctx.Request.URL.Path] { + if traceId := context.GetHeader(trace.Header); traceId != "" { + context.setTrace(trace.New(traceId)) } else { - context.setJournal(journal.NewJournal("")) + context.setTrace(trace.New("")) } } @@ -330,7 +336,7 @@ func New(logger *zap.Logger, options ...Option) (Mux, error) { if err := recover(); err != nil { stackInfo := string(debug.Stack()) logger.Error("got panic", zap.String("panic", fmt.Sprintf("%+v", err)), zap.String("stack", stackInfo)) - context.SetPayload(errno.ErrServer) + context.SetPayload(code.ErrServer) if notify := opt.panicNotify; notify != nil { notify(context, err, stackInfo) @@ -342,8 +348,10 @@ func New(logger *zap.Logger, options ...Option) (Mux, error) { } var ( - response errno.Error - abortErr error + response errno.Error + businessCode int + businessCodeMsg string + abortErr error ) if ctx.IsAborted() { @@ -360,12 +368,14 @@ func New(logger *zap.Logger, options ...Option) (Mux, error) { } if response != nil { - if x := context.Journal(); x != nil { - context.SetHeader(journal.JournalHeader, x.ID()) + if x := context.Trace(); x != nil { + context.SetHeader(trace.Header, x.ID()) response.WithID(x.ID()) } else { response.WithID("") } + businessCode = response.GetCode() + businessCodeMsg = response.GetMsg() ctx.JSON(http.StatusOK, response) } @@ -375,23 +385,18 @@ func New(logger *zap.Logger, options ...Option) (Mux, error) { uri = alias } - businessCode := 0 - if response != nil { - businessCode = response.GetCode() - } - opt.recordMetrics(context.Method(), uri, !ctx.IsAborted() && ctx.Writer.Status() == http.StatusOK, ctx.Writer.Status(), businessCode, time.Since(ts).Seconds()) } - var j *journal.Journal - if x := context.Journal(); x != nil { - j = x.(*journal.Journal) + var t *trace.Trace + if x := context.Trace(); x != nil { + t = x.(*trace.Trace) } else { return } decodedURL, _ := url.QueryUnescape(ctx.Request.URL.RequestURI()) - j.WithRequest(&journal.Request{ + t.WithRequest(&trace.Request{ TTL: "un-limit", Method: ctx.Request.Method, DecodedURL: decodedURL, @@ -399,17 +404,19 @@ func New(logger *zap.Logger, options ...Option) (Mux, error) { Body: string(context.RawData()), }) - j.WithResponse(&journal.Response{ - Header: ctx.Writer.Header(), - StatusCode: ctx.Writer.Status(), - Status: http.StatusText(ctx.Writer.Status()), - Body: response, + t.WithResponse(&trace.Response{ + Header: ctx.Writer.Header(), + HttpCode: ctx.Writer.Status(), + HttpCodeMsg: http.StatusText(ctx.Writer.Status()), + BusinessCode: businessCode, + BusinessCodeMsg: businessCodeMsg, + Body: response, }) - j.Success = !ctx.IsAborted() && ctx.Writer.Status() == http.StatusOK - j.CostSeconds = time.Since(ts).Seconds() + t.Success = !ctx.IsAborted() && ctx.Writer.Status() == http.StatusOK + t.CostSeconds = time.Since(ts).Seconds() - logger.Info("interceptor", zap.Any("journal", j)) + logger.Info("interceptor", zap.Any("trace", t)) }() ctx.Next() @@ -422,7 +429,7 @@ func New(logger *zap.Logger, options ...Option) (Mux, error) { defer releaseContext(context) if !limiter.Allow() { - context.SetPayload(errno.ErrManyRequest) + context.SetPayload(code.ErrManyRequest) ctx.Abort() return } @@ -430,24 +437,26 @@ func New(logger *zap.Logger, options ...Option) (Mux, error) { }) } - mux.engine.NoMethod(wrapHandlers(DisableJournal)...) - mux.engine.NoRoute(wrapHandlers(DisableJournal)...) + mux.engine.NoMethod(wrapHandlers(DisableTrace)...) + mux.engine.NoRoute(wrapHandlers(DisableTrace)...) - h := mux.Group("/h", DisableJournal) + h := mux.Group("/h") { h.GET("/ping", func(ctx Context) { - ctx.SetPayload(errno.OK.WithData("pong")) + ctx.SetPayload(code.OK.WithData("pong")) }) h.GET("/info", func(ctx Context) { resp := &struct { Header interface{} `json:"header"` Ts time.Time `json:"ts"` + Env string `json:"env"` }{ Header: ctx.Header(), Ts: time.Now(), + Env: env.Active().Value(), } - ctx.SetPayload(errno.OK.WithData(resp)) + ctx.SetPayload(code.OK.WithData(resp)) }) } diff --git a/internal/pkg/errno/code.go b/internal/pkg/errno/code.go deleted file mode 100644 index e54cc38..0000000 --- a/internal/pkg/errno/code.go +++ /dev/null @@ -1,22 +0,0 @@ -package errno - -import "net/http" - -var ( - // OK - OK = NewError(0, "OK") - - // 服务级错误码 - ErrServer = NewError(10101, http.StatusText(http.StatusInternalServerError)) - Err404 = NewError(10102, http.StatusText(http.StatusNotFound)) - ErrManyRequest = NewError(10103, "Too many requests") - - ErrParam = NewError(10002, "参数有误") - ErrSignParam = NewError(10003, "签名参数有误") - - // 模块级错误码 - 用户模块 - ErrUser = NewError(20101, "非法用户") - ErrUserCaptcha = NewError(20102, "用户验证码有误") - - // ... -) diff --git a/internal/pkg/journal/journal.go b/internal/pkg/journal/journal.go deleted file mode 100644 index 142451a..0000000 --- a/internal/pkg/journal/journal.go +++ /dev/null @@ -1,121 +0,0 @@ -package journal - -import ( - "crypto/rand" - "encoding/hex" - "io" - "sync" -) - -// JournalHeader http/rpc header中的名字 -const JournalHeader = "Journal-ID" - -var _ T = (*Journal)(nil) -var _ T = (*Dialog)(nil) - -// T 约束 -type T interface { - ID() string - t() -} - -// Journal 包含一次rpc请求的全部参数和内部调用其它方接口的过程 -type Journal struct { - mux sync.Mutex - Identifier string `json:"id"` - Request *Request `json:"request"` - Response *Response `json:"response"` - Dialogs []*Dialog `json:"dialogs"` - Success bool `json:"success"` - CostSeconds float64 `json:"cost_seconds"` -} - -// NewJournal 创建Journal -func NewJournal(id string) *Journal { - if id == "" { - buf := make([]byte, 10) - io.ReadFull(rand.Reader, buf) - id = string(hex.EncodeToString(buf)) - } - - return &Journal{ - Identifier: id, - } -} - -// ID 唯一标识符 -func (j *Journal) ID() string { - return j.Identifier -} - -// WithRequest 设置request -func (j *Journal) WithRequest(req *Request) *Journal { - j.Request = req - return j -} - -// WithResponse 设置response -func (j *Journal) WithResponse(resp *Response) *Journal { - j.Response = resp - return j -} - -// AppendDialog 安全的追加内部调用过程dialog -func (j *Journal) AppendDialog(dialog *Dialog) *Journal { - if dialog == nil { - return j - } - - j.mux.Lock() - defer j.mux.Unlock() - - j.Dialogs = append(j.Dialogs, dialog) - return j -} - -func (j *Journal) t() {} - -// Request 请求信息 -type Request struct { - TTL string `json:"ttl"` - Method string `json:"method"` - DecodedURL string `json:"decoded_url"` - Header interface{} `json:"header"` - Body interface{} `json:"body"` -} - -// Response 响应信息 -type Response struct { - Header interface{} `json:"header"` - StatusCode int `json:"status_code"` - Status string `json:"status"` - Body interface{} `json:"body"` - CostSeconds float64 `json:"cost_seconds"` -} - -// Dialog 内部调用其它方接口的会话信息;失败时会有retry操作,所以response会有多次。 -type Dialog struct { - mux sync.Mutex - Request *Request `json:"request"` - Responses []*Response `json:"responses"` - Success bool `json:"success"` - CostSeconds float64 `json:"cost_seconds"` -} - -// ID ... -func (d *Dialog) ID() string { - return "" -} - -// AppendResponse 按转的追加response信息 -func (d *Dialog) AppendResponse(resp *Response) { - if resp == nil { - return - } - - d.mux.Lock() - d.Responses = append(d.Responses, resp) - d.mux.Unlock() -} - -func (d *Dialog) t() {} diff --git a/internal/pkg/notify/notify.go b/internal/pkg/notify/notify.go index a4be346..807ecf8 100644 --- a/internal/pkg/notify/notify.go +++ b/internal/pkg/notify/notify.go @@ -1,7 +1,7 @@ package notify import ( - "github.com/xinliangnote/go-gin-api/internal/pkg/configs" + "github.com/xinliangnote/go-gin-api/configs" "github.com/xinliangnote/go-gin-api/internal/pkg/core" "github.com/xinliangnote/go-gin-api/internal/pkg/notify/mail" @@ -16,7 +16,7 @@ func OnPanicNotify(ctx core.Context, err interface{}, stackInfo string) { return } - subject, body, htmlErr := mail.NewPanicHTMLEmail(ctx.Method(), ctx.Host(), ctx.URI(), ctx.Journal().ID(), err, stackInfo) + subject, body, htmlErr := mail.NewPanicHTMLEmail(ctx.Method(), ctx.Host(), ctx.URI(), ctx.Trace().ID(), err, stackInfo) if htmlErr != nil { ctx.Logger().Error("NewPanicHTMLEmail error", zap.Error(htmlErr)) return diff --git a/internal/pkg/trace/README.md b/internal/pkg/trace/README.md new file mode 100644 index 0000000..ca176cb --- /dev/null +++ b/internal/pkg/trace/README.md @@ -0,0 +1,81 @@ +## trace + +一个用于开发调试的辅助工具。 + +可以实时显示当前页面的操作的请求信息、运行情况、SQL执行、错误提示等。 + +- `trace.go` 主入口文件; +- `dialog.go` 处理 third_party_requests 记录; +- `debug.go` 处理 debug 记录; + +#### 数据格式 + +##### trace_id + +当前 trace 的 ID,例如:938ff86be98439c6c1a7,便于搜索使用。 + +##### request + +请求信息,会包括: + +- ttl 请求超时时间,例如:2s 或 un-limit +- method 请求方式,例如:GET 或 POST +- decoded_url 请求地址 +- header 请求头信息 +- body 请求体信息 + +##### response + +- header 响应头信息 +- body 响应提信息 +- business_code 业务码,例如:10010 +- business_code_msg 业务码信息,例如:签名错误 +- http_code HTTP 状态码,例如:200 +- http_code_msg HTTP 状态码信息,例如:OK +- cost_seconds 耗费时长:单位秒,比如 0.001105661 + +##### third_party_requests + +每一个第三方 http 请求都会生成如下的一组数据,多个请求会生成多组数据。 + +- request,同上 request 结构一致 +- response,同上 response 结构一致 +- success,是否成功,true 或 false +- cost_seconds,耗费时长:单位秒 + +注意:response 中的 business_code、business_code_msg 为空,因为各个第三方返回结构不同,这两个字段为空。 + +##### sqls + +执行的 SQL 信息,多个 SQL 会记录多组数据。 + +- time,时间,格式:2006-01-02 15:04:05 +- src,文件地址和行号 +- duration,执行时长,单位:秒 +- sql,SQL 语句 +- rows_affected,影响行数 + +##### debugs + +- key 打印的标示 +- value 打印的值 + +```cassandraql +// 调试时,使用这个方法: +p.Print("key", "value", p.WithTrace(c.Trace())) +``` + +只有参数中增加了 `p.WithTrace(c.Trace())`,才会记录到 `debugs` 中。 + +##### success + +是否成功,true 或 false + +```cassandraql +success = !ctx.IsAborted() && ctx.Writer.Status() == http.StatusOK +``` + +##### cost_seconds + +耗费时长:单位秒,比如 0.001105661 + diff --git a/internal/pkg/trace/debug.go b/internal/pkg/trace/debug.go new file mode 100644 index 0000000..50ce360 --- /dev/null +++ b/internal/pkg/trace/debug.go @@ -0,0 +1,6 @@ +package trace + +type Debug struct { + Key string `json:"key"` //标示 + Value interface{} `json:"value"` //值 +} diff --git a/internal/pkg/trace/dialog.go b/internal/pkg/trace/dialog.go new file mode 100644 index 0000000..4fdb27e --- /dev/null +++ b/internal/pkg/trace/dialog.go @@ -0,0 +1,32 @@ +package trace + +import "sync" + +var _ D = (*Dialog)(nil) + +type D interface { + i() + AppendResponse(resp *Response) +} + +// Dialog 内部调用其它方接口的会话信息;失败时会有retry操作,所以response会有多次。 +type Dialog struct { + mux sync.Mutex + Request *Request `json:"request"` // 请求信息 + Responses []*Response `json:"responses"` // 返回信息 + Success bool `json:"success"` // 是否成功,true 或 false + CostSeconds float64 `json:"cost_seconds"` // 执行时长,单位:秒 +} + +func (d *Dialog) i() {} + +// AppendResponse 按转的追加response信息 +func (d *Dialog) AppendResponse(resp *Response) { + if resp == nil { + return + } + + d.mux.Lock() + d.Responses = append(d.Responses, resp) + d.mux.Unlock() +} diff --git a/internal/pkg/trace/sql.go b/internal/pkg/trace/sql.go new file mode 100644 index 0000000..f2d051b --- /dev/null +++ b/internal/pkg/trace/sql.go @@ -0,0 +1,9 @@ +package trace + +type SQL struct { + Time string `json:"time"` // 时间,格式:2006-01-02 15:04:05 + Src string `json:"src"` // 文件地址和行号 + Duration float64 `json:"duration"` // 执行时长,单位:秒 + SQL string `json:"sql"` // SQL 语句 + Rows int64 `json:"rows_affected"` // 影响行数 +} diff --git a/internal/pkg/trace/trace.go b/internal/pkg/trace/trace.go new file mode 100644 index 0000000..fdf26e6 --- /dev/null +++ b/internal/pkg/trace/trace.go @@ -0,0 +1,124 @@ +package trace + +import ( + "crypto/rand" + "encoding/hex" + "io" + "sync" +) + +const Header = "TRACE-ID" + +var _ T = (*Trace)(nil) + +type T interface { + i() + ID() string + WithRequest(req *Request) *Trace + WithResponse(resp *Response) *Trace + AppendDialog(dialog *Dialog) *Trace + AppendSQL(sql *SQL) *Trace +} + +// Trace 记录的参数 +type Trace struct { + mux sync.Mutex + Identifier string `json:"trace_id"` + Request *Request `json:"request"` + Response *Response `json:"response"` + ThirdPartyRequests []*Dialog `json:"third_party_requests"` + Debugs []*Debug `json:"debugs"` + SQLs []*SQL `json:"sqls"` + Success bool `json:"success"` + CostSeconds float64 `json:"cost_seconds"` +} + +// Request 请求信息 +type Request struct { + TTL string `json:"ttl"` + Method string `json:"method"` + DecodedURL string `json:"decoded_url"` + Header interface{} `json:"header"` + Body interface{} `json:"body"` +} + +// Response 响应信息 +type Response struct { + Header interface{} `json:"header"` + Body interface{} `json:"body"` + BusinessCode int `json:"business_code,omitempty"` + BusinessCodeMsg string `json:"business_code_msg,omitempty"` + HttpCode int `json:"http_code"` + HttpCodeMsg string `json:"http_code_msg"` + CostSeconds float64 `json:"cost_seconds"` +} + +func New(id string) *Trace { + if id == "" { + buf := make([]byte, 10) + io.ReadFull(rand.Reader, buf) + id = string(hex.EncodeToString(buf)) + } + + return &Trace{ + Identifier: id, + } +} + +func (t *Trace) i() {} + +// ID 唯一标识符 +func (t *Trace) ID() string { + return t.Identifier +} + +// WithRequest 设置request +func (t *Trace) WithRequest(req *Request) *Trace { + t.Request = req + return t +} + +// WithResponse 设置response +func (t *Trace) WithResponse(resp *Response) *Trace { + t.Response = resp + return t +} + +// AppendDialog 安全的追加内部调用过程dialog +func (t *Trace) AppendDialog(dialog *Dialog) *Trace { + if dialog == nil { + return t + } + + t.mux.Lock() + defer t.mux.Unlock() + + t.ThirdPartyRequests = append(t.ThirdPartyRequests, dialog) + return t +} + +// AppendDebug 追加 debug +func (t *Trace) AppendDebug(debug *Debug) *Trace { + if debug == nil { + return t + } + + t.mux.Lock() + defer t.mux.Unlock() + + t.Debugs = append(t.Debugs, debug) + return t +} + +// AppendSQL 追加 SQL +func (t *Trace) AppendSQL(sql *SQL) *Trace { + if sql == nil { + return t + } + + t.mux.Lock() + defer t.mux.Unlock() + + t.SQLs = append(t.SQLs, sql) + return t +} diff --git a/internal/router/router.go b/internal/router/router.go deleted file mode 100644 index 378875f..0000000 --- a/internal/router/router.go +++ /dev/null @@ -1,53 +0,0 @@ -package router - -import ( - "github.com/xinliangnote/go-gin-api/internal/api/controller/demo" - "github.com/xinliangnote/go-gin-api/internal/pkg/core" - "github.com/xinliangnote/go-gin-api/internal/pkg/metrics" - "github.com/xinliangnote/go-gin-api/internal/pkg/notify" - "github.com/xinliangnote/go-gin-api/internal/router/middleware" - - "github.com/pkg/errors" - "go.uber.org/zap" -) - -func NewHTTPMux(logger *zap.Logger) (core.Mux, error) { - - if logger == nil { - return nil, errors.New("logger required") - } - - mux, err := core.New(logger, - core.WithEnableCors(), - core.WithEnableRate(), - core.WithPanicNotify(notify.OnPanicNotify), - core.WithRecordMetrics(metrics.RecordMetrics), - ) - - if err != nil { - panic(err) - } - - demoHandler := demo.NewDemo(logger) - - u := mux.Group("/user") - { - u.POST("/login", demoHandler.Login()) - } - - d := mux.Group("/demo", core.WrapAuthHandler(middleware.AuthHandler)) //使用 auth 验证 - { - d.GET("user/:name", demoHandler.User()) - - // 模拟数据 - d.GET("get/:name", demoHandler.Get(), core.DisableJournal) - d.POST("post", demoHandler.Post(), core.DisableJournal) - - // 测试加密性能 - d.GET("/rsa/test", demoHandler.RsaTest()) - d.GET("/aes/test", demoHandler.AesTest()) - d.GET("/md5/test", demoHandler.MD5Test()) - } - - return mux, nil -} diff --git a/pkg/aes/aes_test.go b/pkg/aes/aes_test.go index 65d1aff..d5a5c8c 100644 --- a/pkg/aes/aes_test.go +++ b/pkg/aes/aes_test.go @@ -3,14 +3,23 @@ package aes import "testing" const ( - Key = "IgkibX71IEf382PT" - Iv = "IgkibX71IEf382PT" + key = "IgkibX71IEf382PT" + iv = "IgkibX71IEf382PT" ) func TestEncrypt(t *testing.T) { - t.Log(New(Key, Iv).Encrypt("123456")) + t.Log(New(key, iv).Encrypt("123456")) } func TestDecrypt(t *testing.T) { - t.Log(New(Key, Iv).Decrypt("GO-ri84zevE-z1biJwfQPw==")) + t.Log(New(key, iv).Decrypt("GO-ri84zevE-z1biJwfQPw==")) +} + +func BenchmarkEncryptAndDecrypt(b *testing.B) { + b.ResetTimer() + aes := New(key, iv) + for i := 0; i < b.N; i++ { + encryptString, _ := aes.Encrypt("123456") + aes.Decrypt(encryptString) + } } diff --git a/pkg/env/env.go b/pkg/env/env.go index 1ffd8ad..615bb8c 100644 --- a/pkg/env/env.go +++ b/pkg/env/env.go @@ -1,8 +1,8 @@ package env import ( + "flag" "fmt" - "os" "strings" ) @@ -53,8 +53,10 @@ func (e *environment) IsPro() bool { func (e *environment) t() {} func init() { - env := strings.ToLower(strings.TrimSpace(os.Getenv("ACTIVE"))) - switch env { + env := flag.String("env", "", "请输入运行环境:\n dev:开发环境\n fat:测试环境\n uat:预上线环境\n pro:正式环境\n") + flag.Parse() + + switch strings.ToLower(strings.TrimSpace(*env)) { case "dev": active = dev case "fat": @@ -65,7 +67,7 @@ func init() { active = pro default: active = fat - fmt.Println("Warning: env'ACTIVE' cannot be found, or it is illegal. The default 'fat' will be used.") + fmt.Println("Warning: '-env' cannot be found, or it is illegal. The default 'fat' will be used.") } } diff --git a/pkg/env/env_test.go b/pkg/env/env_test.go deleted file mode 100644 index b7b48bc..0000000 --- a/pkg/env/env_test.go +++ /dev/null @@ -1,7 +0,0 @@ -package env - -import "testing" - -func TestActive(t *testing.T) { - t.Log(Active().Value()) -} diff --git a/internal/pkg/errno/errno.go b/pkg/errno/errno.go similarity index 100% rename from internal/pkg/errno/errno.go rename to pkg/errno/errno.go diff --git a/pkg/httpclient/client.go b/pkg/httpclient/client.go index e866324..a6eb491 100644 --- a/pkg/httpclient/client.go +++ b/pkg/httpclient/client.go @@ -7,7 +7,7 @@ import ( httpURL "net/url" "time" - "github.com/xinliangnote/go-gin-api/internal/pkg/journal" + "github.com/xinliangnote/go-gin-api/internal/pkg/trace" "github.com/pkg/errors" ) @@ -74,10 +74,10 @@ func withoutBody(method, url string, form httpURL.Values, options ...Option) (bo opt := newOption() defer func() { - if opt.Journal != nil { + if opt.Trace != nil { opt.Dialog.Success = err == nil opt.Dialog.CostSeconds = time.Since(ts).Seconds() - opt.Journal.AppendDialog(opt.Dialog) + opt.Trace.AppendDialog(opt.Dialog) } }() @@ -85,8 +85,8 @@ func withoutBody(method, url string, form httpURL.Values, options ...Option) (bo f(opt) } opt.Header["Content-Type"] = "application/x-www-form-urlencoded; charset=utf-8" - if opt.Journal != nil { - opt.Header[journal.JournalHeader] = opt.Journal.ID() + if opt.Trace != nil { + opt.Header[trace.Header] = opt.Trace.ID() } ttl := opt.TTL @@ -99,7 +99,7 @@ func withoutBody(method, url string, form httpURL.Values, options ...Option) (bo if opt.Dialog != nil { decodedURL, _ := httpURL.QueryUnescape(url) - opt.Dialog.Request = &journal.Request{ + opt.Dialog.Request = &trace.Request{ TTL: ttl.String(), Method: method, DecodedURL: decodedURL, @@ -172,10 +172,10 @@ func withFormBody(method, url string, form httpURL.Values, options ...Option) (b opt := newOption() defer func() { - if opt.Journal != nil { + if opt.Trace != nil { opt.Dialog.Success = err == nil opt.Dialog.CostSeconds = time.Since(ts).Seconds() - opt.Journal.AppendDialog(opt.Dialog) + opt.Trace.AppendDialog(opt.Dialog) } }() @@ -183,8 +183,8 @@ func withFormBody(method, url string, form httpURL.Values, options ...Option) (b f(opt) } opt.Header["Content-Type"] = "application/x-www-form-urlencoded; charset=utf-8" - if opt.Journal != nil { - opt.Header[journal.JournalHeader] = opt.Journal.ID() + if opt.Trace != nil { + opt.Header[trace.Header] = opt.Trace.ID() } ttl := opt.TTL @@ -198,7 +198,7 @@ func withFormBody(method, url string, form httpURL.Values, options ...Option) (b formValue := form.Encode() if opt.Dialog != nil { decodedURL, _ := httpURL.QueryUnescape(url) - opt.Dialog.Request = &journal.Request{ + opt.Dialog.Request = &trace.Request{ TTL: ttl.String(), Method: method, DecodedURL: decodedURL, @@ -242,10 +242,10 @@ func withJSONBody(method, url string, raw json.RawMessage, options ...Option) (b opt := newOption() defer func() { - if opt.Journal != nil { + if opt.Trace != nil { opt.Dialog.Success = err == nil opt.Dialog.CostSeconds = time.Since(ts).Seconds() - opt.Journal.AppendDialog(opt.Dialog) + opt.Trace.AppendDialog(opt.Dialog) } }() @@ -253,8 +253,8 @@ func withJSONBody(method, url string, raw json.RawMessage, options ...Option) (b f(opt) } opt.Header["Content-Type"] = "application/json; charset=utf-8" - if opt.Journal != nil { - opt.Header[journal.JournalHeader] = opt.Journal.ID() + if opt.Trace != nil { + opt.Header[trace.Header] = opt.Trace.ID() } ttl := opt.TTL @@ -267,7 +267,7 @@ func withJSONBody(method, url string, raw json.RawMessage, options ...Option) (b if opt.Dialog != nil { decodedURL, _ := httpURL.QueryUnescape(url) - opt.Dialog.Request = &journal.Request{ + opt.Dialog.Request = &trace.Request{ TTL: ttl.String(), Method: method, DecodedURL: decodedURL, diff --git a/pkg/httpclient/option.go b/pkg/httpclient/option.go index 2bf8647..74f1c54 100644 --- a/pkg/httpclient/option.go +++ b/pkg/httpclient/option.go @@ -3,13 +3,13 @@ package httpclient import ( "time" - "github.com/xinliangnote/go-gin-api/internal/pkg/journal" + "github.com/xinliangnote/go-gin-api/internal/pkg/trace" "go.uber.org/zap" ) -// Journal 记录内部流转信息 -type Journal = journal.T +// Trace 记录内部流转信息 +type Trace = trace.T // Option 自定义设置http请求 type Option func(*option) @@ -17,8 +17,8 @@ type Option func(*option) type option struct { TTL time.Duration Header map[string]string - Journal *journal.Journal - Dialog *journal.Dialog + Trace *trace.Trace + Dialog *trace.Dialog Logger *zap.Logger RetryTimes int RetryDelay time.Duration @@ -44,12 +44,12 @@ func WithHeader(key, value string) Option { } } -// WithJournal 设置Journal以便记录内部流转信息 -func WithJournal(j Journal) Option { +// WithTrace 设置trace信息 +func WithTrace(t Trace) Option { return func(opt *option) { - if j != nil { - opt.Journal = j.(*journal.Journal) - opt.Dialog = new(journal.Dialog) + if t != nil { + opt.Trace = t.(*trace.Trace) + opt.Dialog = new(trace.Dialog) } } } diff --git a/pkg/httpclient/util.go b/pkg/httpclient/util.go index 48b3608..2a94db9 100644 --- a/pkg/httpclient/util.go +++ b/pkg/httpclient/util.go @@ -9,7 +9,7 @@ import ( "net/url" "time" - "github.com/xinliangnote/go-gin-api/internal/pkg/journal" + "github.com/xinliangnote/go-gin-api/internal/pkg/trace" "github.com/pkg/errors" "go.uber.org/zap" @@ -49,7 +49,7 @@ func doHTTP(ctx context.Context, method, url string, payload []byte, opt *option if err != nil { err = errors.Wrapf(err, "do request [%s %s] err", method, url) if opt.Dialog != nil { - opt.Dialog.AppendResponse(&journal.Response{ + opt.Dialog.AppendResponse(&trace.Response{ Body: err.Error(), CostSeconds: time.Since(ts).Seconds(), }) @@ -66,7 +66,7 @@ func doHTTP(ctx context.Context, method, url string, payload []byte, opt *option if err != nil { err = errors.Wrapf(err, "read resp body from [%s %s] err", method, url) if opt.Dialog != nil { - opt.Dialog.AppendResponse(&journal.Response{ + opt.Dialog.AppendResponse(&trace.Response{ Body: err.Error(), CostSeconds: time.Since(ts).Seconds(), }) @@ -80,10 +80,10 @@ func doHTTP(ctx context.Context, method, url string, payload []byte, opt *option defer func() { if opt.Dialog != nil { - opt.Dialog.AppendResponse(&journal.Response{ + opt.Dialog.AppendResponse(&trace.Response{ Header: resp.Header, - StatusCode: resp.StatusCode, - Status: resp.Status, + HttpCode: resp.StatusCode, + HttpCodeMsg: resp.Status, Body: string(body), // unsafe CostSeconds: time.Since(ts).Seconds(), }) diff --git a/pkg/md5/md5_test.go b/pkg/md5/md5_test.go index da687d2..14d56ba 100644 --- a/pkg/md5/md5_test.go +++ b/pkg/md5/md5_test.go @@ -5,3 +5,10 @@ import "testing" func TestEncrypt(t *testing.T) { t.Log(New().Encrypt("123456")) } + +func BenchmarkEncrypt(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + New().Encrypt("123456") + } +} diff --git a/pkg/p/print.go b/pkg/p/print.go new file mode 100644 index 0000000..f7731df --- /dev/null +++ b/pkg/p/print.go @@ -0,0 +1,47 @@ +package p + +import ( + "github.com/xinliangnote/go-gin-api/internal/pkg/trace" + + "github.com/davecgh/go-spew/spew" +) + +type Option func(*option) + +type Trace = trace.T + +type option struct { + Trace *trace.Trace + Debug *trace.Debug +} + +func newOption() *option { + return &option{} +} + +func Print(key string, value interface{}, options ...Option) { + opt := newOption() + defer func() { + if opt.Trace != nil { + opt.Debug.Key = key + opt.Debug.Value = value + opt.Trace.AppendDebug(opt.Debug) + } + }() + + for _, f := range options { + f(opt) + } + + spew.Dump(key, value) +} + +// WithTrace 设置trace信息 +func WithTrace(t Trace) Option { + return func(opt *option) { + if t != nil { + opt.Trace = t.(*trace.Trace) + opt.Debug = new(trace.Debug) + } + } +} diff --git a/pkg/rsa/rsa_test.go b/pkg/rsa/rsa_test.go index 45acb40..7d39746 100644 --- a/pkg/rsa/rsa_test.go +++ b/pkg/rsa/rsa_test.go @@ -4,8 +4,8 @@ import ( "testing" ) -func TestEncrypt(t *testing.T) { - publicKey := `-----BEGIN PUBLIC KEY----- +const ( + publicKey = `-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1O3p0JN0/RrP7eY3f81i zPf16FS0WMNGCJkd+y5c6yBzUvN0IEeoxiIWIBhoMKH0pzlzBg0rfttojSodOgNo m/UCAzAYEgdIsNee5LSN/7e0T2/QvsIAHINuA8gI8fGoGiSA2TEzpUo6aVXwhZT3 @@ -15,17 +15,7 @@ xLYEFN9h2MWYgxLm9Z0rLMrWwMM+E2rCs8tsxAD5sO9RZMJPl1C0FIsMR53ngqbz owIDAQAB -----END PUBLIC KEY-----` - rsaPublic := NewPublic(publicKey) - str, err := rsaPublic.Encrypt("123456") - if err != nil { - t.Error("rsa public encrypt error", err) - return - } - t.Log(str) -} - -func TestDecrypt(t *testing.T) { - privateKey := `-----BEGIN RSA PRIVATE KEY----- + privateKey = `-----BEGIN RSA PRIVATE KEY----- MIIEpgIBAAKCAQEA1O3p0JN0/RrP7eY3f81izPf16FS0WMNGCJkd+y5c6yBzUvN0 IEeoxiIWIBhoMKH0pzlzBg0rfttojSodOgNom/UCAzAYEgdIsNee5LSN/7e0T2/Q vsIAHINuA8gI8fGoGiSA2TEzpUo6aVXwhZT34GGRdrSJ+m4iVk/Kt95tavBNk+ND @@ -52,6 +42,19 @@ tN5Pb9To9gJTqpZRD+/cLOeFRrHBBjMK1z7fPKS/fN2B+JFVq7nD827t3+J0In4F mS8gU20MMPAeV2z7khyDcSxlHsUyL73eLeaakbQov9NMW7cc99XX4wnP4W7FRpmr QbHmKuHIRFHCFv+XX8c0aK2mDZMUlzJdy4FgD/YCEZ7kZMZKyvZW/ZuV -----END RSA PRIVATE KEY-----` +) + +func TestEncrypt(t *testing.T) { + rsaPublic := NewPublic(publicKey) + str, err := rsaPublic.Encrypt("123456") + if err != nil { + t.Error("rsa public encrypt error", err) + return + } + t.Log(str) +} + +func TestDecrypt(t *testing.T) { decryptStr := "KTKXckjkCLI6Vk_y_XROnY-a6nJpllruL-CX-v_2AFxfghA2tZ2nkQyS6d1-IIYMlgwm4ivwlzy-phLtaN9BB03htA5D9rwjA_JwYtqAG4iwuvgaDl2SiZ_H2ACv-aV1kNRgnyjh14hs0JiSt5VHEiJ3D2xYzOCKwtEzoo0WczJ-MYb3u_-bfcnm9YtvgtG5-y3Jy7WYr-IwXdBKqPO0E-jzrtY7m3Q1yC4znHdzjNpxCj0I6YRx4CZ362b706qNX7sl3E5KTJeSmYrsurB-SxQT1CaqGzVt7mshx1v2qGnv5NBNXpj7ZPKWGJbgaCUxcuxd1Mg0o81HnfbsGuSlFQ==" rsaPrivate := NewPrivate(privateKey) @@ -62,3 +65,13 @@ QbHmKuHIRFHCFv+XX8c0aK2mDZMUlzJdy4FgD/YCEZ7kZMZKyvZW/ZuV } t.Log(str) } + +func BenchmarkEncryptAndDecrypt(b *testing.B) { + b.ResetTimer() + rsaPublic := NewPublic(publicKey) + rsaPrivate := NewPrivate(privateKey) + for i := 0; i < b.N; i++ { + encryptString, _ := rsaPublic.Encrypt("123456") + rsaPrivate.Decrypt(encryptString) + } +} diff --git a/pkg/time_parse/time_parse.go b/pkg/time_parse/time_parse.go new file mode 100644 index 0000000..66df7b5 --- /dev/null +++ b/pkg/time_parse/time_parse.go @@ -0,0 +1,33 @@ +package time_parse + +import "time" + +var ( + cst *time.Location +) + +// CSTLayout China Standard Time Layout +const CSTLayout = "2006-01-02 15:04:05" + +func init() { + var err error + if cst, err = time.LoadLocation("Asia/Shanghai"); err != nil { + panic(err) + } +} + +// RFC3339ToCSTLayout convert rfc3339 value to china standard time layout +// 2020-11-08T08:18:46+08:00 => 2020-11-08 08:18:46 +func RFC3339ToCSTLayout(value string) (string, error) { + ts, err := time.Parse(time.RFC3339, value) + if err != nil { + return "", err + } + + return ts.In(cst).Format(CSTLayout), nil +} + +func CSTLayoutString() string { + ts := time.Now() + return ts.In(cst).Format(CSTLayout) +} diff --git a/internal/pkg/token/token.go b/pkg/token/token.go similarity index 65% rename from internal/pkg/token/token.go rename to pkg/token/token.go index 080fcd4..f3b7ded 100644 --- a/internal/pkg/token/token.go +++ b/pkg/token/token.go @@ -6,8 +6,18 @@ import ( "github.com/dgrijalva/jwt-go" ) -//var secret = configs.Get().JWT.Secret -var secret = "i1ydX9RtHyuJTrw7frcu" +var _ Token = (*token)(nil) + +type Token interface { + // i 为了避免被其他包实现 + i() + Sign(userId int, userName string) (tokenString string, err error) + Parse(tokenString string) (*claims, error) +} + +type token struct { + secret string +} type claims struct { UserID int @@ -15,7 +25,15 @@ type claims struct { jwt.StandardClaims } -func Sign(userId int, userName string) (tokenString string, err error) { +func New(secret string) Token { + return &token{ + secret: secret, + } +} + +func (t *token) i() {} + +func (t *token) Sign(userId int, userName string) (tokenString string, err error) { // The token content. // iss: (Issuer)签发者 // iat: (Issued At)签发时间,用Unix时间戳表示 @@ -34,13 +52,13 @@ func Sign(userId int, userName string) (tokenString string, err error) { Issuer: "go-gin-api", }, } - tokenString, err = jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString([]byte(secret)) + tokenString, err = jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString([]byte(t.secret)) return } -func Parse(tokenString string) (*claims, error) { +func (t *token) Parse(tokenString string) (*claims, error) { tokenClaims, err := jwt.ParseWithClaims(tokenString, &claims{}, func(token *jwt.Token) (interface{}, error) { - return []byte(secret), nil + return []byte(t.secret), nil }) if tokenClaims != nil { diff --git a/internal/pkg/token/token_test.go b/pkg/token/token_test.go similarity index 59% rename from internal/pkg/token/token_test.go rename to pkg/token/token_test.go index 63f9694..5cf232d 100644 --- a/internal/pkg/token/token_test.go +++ b/pkg/token/token_test.go @@ -4,10 +4,10 @@ import ( "testing" ) -// 执行 Test 时,先将 token.secret 设置值 +const secret = "i1ydX9RtHyuJTrw7frcu" func TestSign(t *testing.T) { - tokenString, err := Sign(123456789, "xinliangnote") + tokenString, err := New(secret).Sign(123456789, "xinliangnote") if err != nil { t.Error("sign error", err) return @@ -17,10 +17,19 @@ func TestSign(t *testing.T) { func TestParse(t *testing.T) { tokenString := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjEyMzQ1Njc4OSwidXNlcm5hbWUiOiJ4aW5saWFuZyIsImV4cCI6MTYwOTQ2NzcwNCwiaWF0IjoxNjA5MzgxMzA0LCJpc3MiOiJnby1naW4tYXBpIiwibmJmIjoxNjA5MzgxMzA0fQ.hccv8F713WpKcwiSldBrFLZz_2SZzOTPedPi-8ps7M4" - user, err := Parse(tokenString) + user, err := New(secret).Parse(tokenString) if err != nil { t.Error("parse error", err) return } t.Log(user) } + +func BenchmarkSignAndParse(b *testing.B) { + b.ResetTimer() + token := New(secret) + for i := 0; i < b.N; i++ { + tokenString, _ := token.Sign(123456789, "xinliangnote") + token.Parse(tokenString) + } +}