upgrade
This commit is contained in:
88
README.md
88
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
|
||||
|
||||
1
cmd/api/README.md
Normal file
1
cmd/api/README.md
Normal file
@@ -0,0 +1 @@
|
||||
## HTTP API 入口文件
|
||||
@@ -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()
|
||||
1
cmd/cron/README.md
Normal file
1
cmd/cron/README.md
Normal file
@@ -0,0 +1 @@
|
||||
## 定时任务入口文件
|
||||
11
cmd/cron/main.go
Normal file
11
cmd/cron/main.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/xinliangnote/go-gin-api/pkg/env"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Println(env.Active().Value())
|
||||
}
|
||||
91
configs/configs.go
Normal file
91
configs/configs.go
Normal file
@@ -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"
|
||||
}
|
||||
@@ -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
|
||||
|
||||
191
docs/docs.go
191
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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
10
go.mod
10
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
|
||||
)
|
||||
|
||||
90
go.sum
90
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=
|
||||
|
||||
5
init/README.md
Normal file
5
init/README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
## init
|
||||
|
||||
项目初始脚本。
|
||||
|
||||
- DB 相关 SQL;
|
||||
10
init/db/tables.sql
Normal file
10
init/db/tables.sql
Normal file
@@ -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表';
|
||||
@@ -1,7 +1,7 @@
|
||||
## 错误码规则
|
||||
|
||||
- 错误码需在 `code.go` 文件中定义。
|
||||
- 错误码需为 > 0 的数,反之表示正确。
|
||||
- 错误码需为 > 1 的数,反之表示正确。
|
||||
|
||||
#### 错误码为 5 位数
|
||||
|
||||
28
internal/api/code/code.go
Normal file
28
internal/api/code/code.go
Normal file
@@ -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, "查询用户失败")
|
||||
|
||||
// ...
|
||||
)
|
||||
9
internal/api/controller/README.md
Normal file
9
internal/api/controller/README.md
Normal file
@@ -0,0 +1,9 @@
|
||||
## controller
|
||||
|
||||
控制器层。
|
||||
|
||||
主要接收参数、验证参数、调用 `service` 层的业务逻辑处理,最后返回数据。
|
||||
|
||||
命名规范:
|
||||
|
||||
- 包名以 `_handler` 结尾。
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
180
internal/api/controller/user_handler/user.go
Normal file
180
internal/api/controller/user_handler/user.go
Normal file
@@ -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))
|
||||
}
|
||||
}
|
||||
10
internal/api/model/README.md
Normal file
10
internal/api/model/README.md
Normal file
@@ -0,0 +1,10 @@
|
||||
## model
|
||||
|
||||
实体层。
|
||||
|
||||
- 请求实体、返回实体。
|
||||
- 数据库实体。
|
||||
|
||||
命名规范:
|
||||
|
||||
- 包名以 `_model` 结尾。
|
||||
68
internal/api/model/user_model/user.go
Normal file
68
internal/api/model/user_model/user.go
Normal file
@@ -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"` // 手机号
|
||||
}
|
||||
29
internal/api/repository/README.md
Normal file
29
internal/api/repository/README.md
Normal file
@@ -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` 命名;
|
||||
120
internal/api/repository/cache_repo/init.go
Normal file
120
internal/api/repository/cache_repo/init.go
Normal file
@@ -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 <key,value> 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()
|
||||
}
|
||||
112
internal/api/repository/db_repo/init.go
Normal file
112
internal/api/repository/db_repo/init.go
Normal file
@@ -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
|
||||
}
|
||||
86
internal/api/repository/db_repo/plugin.go
Normal file
86
internal/api/repository/db_repo/plugin.go
Normal file
@@ -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{}
|
||||
71
internal/api/repository/db_repo/user_demo_repo/user_demo.go
Normal file
71
internal/api/repository/db_repo/user_demo_repo/user_demo.go
Normal file
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
55
internal/api/router/router.go
Normal file
55
internal/api/router/router.go
Normal file
@@ -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
|
||||
}
|
||||
10
internal/api/service/README.md
Normal file
10
internal/api/service/README.md
Normal file
@@ -0,0 +1,10 @@
|
||||
## service
|
||||
|
||||
业务逻辑层。
|
||||
|
||||
处于 `controller` 层和 `repository` 层之间,依赖接口开发。
|
||||
|
||||
命令规范:
|
||||
|
||||
- 包名以 `_service` 结尾。
|
||||
|
||||
67
internal/api/service/user_service/user.go
Normal file
67
internal/api/service/user_service/user.go
Normal file
@@ -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
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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, "用户验证码有误")
|
||||
|
||||
// ...
|
||||
)
|
||||
@@ -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() {}
|
||||
@@ -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
|
||||
|
||||
81
internal/pkg/trace/README.md
Normal file
81
internal/pkg/trace/README.md
Normal file
@@ -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
|
||||
|
||||
6
internal/pkg/trace/debug.go
Normal file
6
internal/pkg/trace/debug.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package trace
|
||||
|
||||
type Debug struct {
|
||||
Key string `json:"key"` //标示
|
||||
Value interface{} `json:"value"` //值
|
||||
}
|
||||
32
internal/pkg/trace/dialog.go
Normal file
32
internal/pkg/trace/dialog.go
Normal file
@@ -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()
|
||||
}
|
||||
9
internal/pkg/trace/sql.go
Normal file
9
internal/pkg/trace/sql.go
Normal file
@@ -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"` // 影响行数
|
||||
}
|
||||
124
internal/pkg/trace/trace.go
Normal file
124
internal/pkg/trace/trace.go
Normal file
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
10
pkg/env/env.go
vendored
10
pkg/env/env.go
vendored
@@ -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.")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
7
pkg/env/env_test.go
vendored
7
pkg/env/env_test.go
vendored
@@ -1,7 +0,0 @@
|
||||
package env
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestActive(t *testing.T) {
|
||||
t.Log(Active().Value())
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
})
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
47
pkg/p/print.go
Normal file
47
pkg/p/print.go
Normal file
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
33
pkg/time_parse/time_parse.go
Normal file
33
pkg/time_parse/time_parse.go
Normal file
@@ -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)
|
||||
}
|
||||
@@ -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 {
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user