28 Commits
Author SHA1 Message Date
新亮 3823eee2ca #11 code generator 2021-02-27 09:29:34 +08:00
新亮 0b6b9dd69f upgrade 2021-02-20 20:43:39 +08:00
新亮 7327275b7a upgrade 2021-02-20 10:02:53 +08:00
新亮 9b5028e9de upgrade 2021-02-20 09:59:36 +08:00
新亮 cc8012b09d upgrade 2021-02-19 22:00:01 +08:00
新亮 334bfbb4aa upgrade 2021-02-09 09:52:50 +08:00
新亮 a4701309cb upgrade 2021-02-03 20:43:02 +08:00
新亮 e5ea279f2e upgrade 2021-02-03 09:51:48 +08:00
新亮 ad8639fea8 upgrade 2021-02-02 20:53:30 +08:00
新亮 b346216f79 upgrade 2021-02-02 20:42:51 +08:00
新亮 e1314cf139 upgrade 2021-01-31 15:37:30 +08:00
新亮 a1a9208a27 upgrade 2021-01-31 15:19:36 +08:00
新亮 4ed8b81c57 upgrade 2021-01-31 14:51:41 +08:00
新亮 106d5b65d7 upgrade 2021-01-23 16:46:01 +08:00
新亮 d17c58ffe1 upgrade 2021-01-17 18:37:43 +08:00
新亮 845ef70a77 upgrade 2021-01-16 22:18:55 +08:00
新亮 f32a8a1fb5 upgrade 2021-01-16 18:08:00 +08:00
新亮 2466dfcbfc upgrade 2021-01-16 14:45:42 +08:00
新亮 f96d33fdef upgrade 2021-01-11 13:13:07 +08:00
新亮 f2cd04993f upgrade 2021-01-09 20:10:13 +08:00
新亮 f4072dc5fb upgrade 2021-01-01 16:12:01 +08:00
新亮 458ebe10c6 upgrade 2021-01-01 10:51:55 +08:00
新亮 aa5dec6eeb upgrade 2020-12-27 20:40:43 +08:00
新亮 ef8989fe33 upgrade 2020-12-27 17:19:53 +08:00
新亮 34f85ad14b upgrade 2020-12-26 13:52:34 +08:00
新亮 155f5fca1b upgrade 2020-12-25 21:26:56 +08:00
新亮笔记andGitHub f8cfc2f16c Merge pull request #7 from xinliangnote/add-license-1
Create LICENSE
2020-12-25 21:11:46 +08:00
新亮笔记andGitHub a017831aba Create LICENSE 2020-12-25 21:10:47 +08:00
1123 changed files with 12088 additions and 436448 deletions
+5 -1
View File
@@ -1 +1,5 @@
.idea
/.vscode
.vscode
/.idea
.idea
mfmt
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2016 Gin-Gonic
Copyright (c) 2020 新亮笔记
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
+32 -142
View File
@@ -1,159 +1,49 @@
![](https://github.com/xinliangnote/Go/blob/master/03-go-gin-api%20%5B%E6%96%87%E6%A1%A3%5D/images/go-gin-api-logo.png)
## 关于
## go-gin-api
`go-gin-api` 是基于 [Gin](https://github.com/gin-gonic/gin) 进行模块化设计的 API 框架,封装了常用的功能,使用简单,致力于进行快速的业务研发,同时增加了更多限制,约束项目组开发成员,规避混乱无序及自由随意的编码。
基于 [Gin](https://github.com/gin-gonic/gin) 进行模块化设计的 API 框架,封装了常用的功能,使用简单,致力于进行快速的业务研发。
供参考学习,线上使用请谨慎!
持续更新...
集成组件:
## Features
1. 支持 [rate](https://golang.org/x/time/rate) 接口限流
1. 支持 panic 异常时邮件通知
1. 支持 [cors](https://github.com/rs/cors) 接口跨域
1. 支持 [Prometheus](https://github.com/prometheus/client_golang) 指标记录
1. 支持 [Swagger](https://github.com/swaggo/gin-swagger) 接口文档生成
1. 支持 [GraphQL](https://github.com/99designs/gqlgen) 查询语言
1. 支持 trace 项目内部链路追踪
1. 支持 [pprof](https://github.com/gin-contrib/pprof) 性能剖析
1. 支持 [jwt](https://github.com/dgrijalva/jwt-go) 接口鉴权
1. 支持 errno 统一定义错误码
1. 支持 [zap](https://go.uber.org/zap) 日志收集
1. 支持 [viper](https://github.com/spf13/viper) 配置文件解析
1. 支持 [gorm](https://gorm.io/gorm) 数据库组件
1. 支持 [go-redis](https://github.com/go-redis/redis/v7) 组件
1. 支持 RESTful API 返回值规范
- [x] 使用 go modules 初始化项目
- [x] 安装 Gin 框架
- [x] 性能分析工具(pprof
- [x] 支持优雅地重启或停止
- [x] 规划项目目录
- [x] 参数验证(validator.v9
- [x] 模型绑定和验证
- [x] 自定义验证器
- [x] 路由中间件
- [x] 签名验证
- [x] MD5 组合加密
- [x] AES 对称加密
- [x] RSA 非对称加密
- [x] 日志记录
- [x] 异常捕获
- [x] 链路追踪(Jaeger
- [x] 限流
- [x] 自定义告警
- [x] 邮件(gomail
- [ ] 微信
- [ ] 短信
- [ ] 钉钉
- [ ] 存储
- [ ] MySQL
- [ ] Redis
- [ ] MongoDB
- [ ] gRPC
- [ ] ...
## Download
```
git clone https://github.com/xinliangnote/go-gin-api.git
```
## 文档索引
## Quick start
go-gin-api 文档由以下几个主要部分组成:
#### Requirements
- 准备工作
- 快速开始
- 目录接口
- 核心封装
- 组件指南
- 工具包
- Go version >= 1.12
- Global environment configure (Linux/Mac)
**地址:[https://www.yuque.com/xinliangnote/go-gin-api/ngc3x5](https://www.yuque.com/xinliangnote/go-gin-api/ngc3x5)**
```
export GO111MODULE=on
export GOPROXY=https://goproxy.io
```
## 其他
#### Build & Run
查看 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) 。
```
cd go-gin-api
## Special Thanks
go run main.go
输出如下,表示 Http Server 启动成功。
|-----------------------------------|
| go-gin-api |
|-----------------------------------|
| Go Http Server Start Successful |
| Port:9999 Pid:xxxxx |
|-----------------------------------|
```
#### HTTP Demo
```
curl -X POST http://127.0.0.1:9999/product
```
#### Jaeger Demo
访问:
```
http://127.0.0.1:9999/jaeger_test
```
服务端测试代码:
- [https://github.com/xinliangnote/go-jaeger-demo](https://github.com/xinliangnote/go-jaeger-demo)
![](https://github.com/xinliangnote/Go/blob/master/03-go-gin-api%20%5B文档%5D/images/jaeger_demo_2.png)
![](https://github.com/xinliangnote/Go/blob/master/03-go-gin-api%20%5B文档%5D/images/jaeger_demo_3.png)
#### pprof
```go
http://127.0.0.1:9999/debug/pprof
```
说明文档:
```go
// 查看 CPU 信息
go tool pprof 127.0.0.1:9999/debug/pprof/profile
...
(pprof)
//输入 web,生成 svg 文件。
//输入 png,生成 png 文件。
//输入 top,查看排名前 20 的信息。
//查看更多命令,请执行 pprof help。
```
其他同理,比如:
```go
// 查看 内存 信息
go tool pprof 127.0.0.1:9999/debug/pprof/heap
// 查看 协程 信息
go tool pprof 127.0.0.1:9999/debug/pprof/goroutine
// 查看 锁 信息
go tool pprof 127.0.0.1:9999/debug/pprof/mutex
```
如果还想查看火焰图,请执行如下命令:
```go
// 1.下载 pprof 工具
go get -u github.com/google/pprof
// 2.启动可视化界面
pprof -http=:9998 xxx.cpu.prof
// 3.查看可视化界面
http://127.0.0.1:9998/ui/
```
## Dependence
- WEB 框架:github.com/gin-gonic/gin
- 链路追踪:github.com/jaegertracing/jaeger-client-go
- 限流:golang.org/x/time/rate
- 工具包:github.com/xinliangnote/go-util
## Document
- [1. 使用 go modules 初始化项目](https://mp.weixin.qq.com/s/1XNTEgZ0XGZZdxFOfR5f_A)
- [2. 规划项目目录和参数验证](https://mp.weixin.qq.com/s/11AuXptWGmL5QfiJArNLnA)
- [3. 路由中间件 - 日志记录](https://mp.weixin.qq.com/s/eTygPXnrYM2xfrRQyfn8Tg)
- [4. 路由中间件 - 捕获异常](https://mp.weixin.qq.com/s/SconDXB_x7Gan6T0Awdh9A)
- [5. 路由中间件 - Jaeger 链路追踪(理论篇)](https://mp.weixin.qq.com/s/28UBEsLOAHDv530ePilKQA)
- [6. 路由中间件 - Jaeger 链路追踪(实战篇)](https://mp.weixin.qq.com/s/Ea28475_UTNaM9RNfgPqJA)
- [7. 路由中间件 - 签名验证](https://mp.weixin.qq.com/s/0cozELotcpX3Gd6WPJiBbQ)
[@koketama](https://github.com/koketama)
## Learning together
-52
View File
@@ -1,52 +0,0 @@
package config
var (
ApiAuthConfig = map[string] map[string]string {
// 调用方
"DEMO" : {
"md5" : "IgkibX71IEf382PT",
"aes" : "IgkibX71IEf382PT",
"rsa" : "rsa/public.pem",
},
}
)
const (
AppMode = "release" //debug or release
AppPort = ":9999"
AppName = "go-gin-api"
// 签名超时时间
AppSignExpiry = "120"
// RSA Private File
AppRsaPrivateFile = "rsa/private.pem"
// 超时时间
AppReadTimeout = 120
AppWriteTimeout = 120
// 日志文件
AppAccessLogName = "log/" + AppName + "-access.log"
AppErrorLogName = "log/" + AppName + "-error.log"
AppGrpcLogName = "log/" + AppName + "-grpc.log"
// 系统告警邮箱信息
SystemEmailUser = "xinliangnote@163.com"
SystemEmailPass = "" //密码或授权码
SystemEmailHost = "smtp.163.com"
SystemEmailPort = 465
// 告警接收人
ErrorNotifyUser = "xinliangnote@163.com"
// 告警开关 1=开通 -1=关闭
ErrorNotifyOpen = -1
// Jaeger 配置信息
JaegerHostPort = "127.0.0.1:6831"
// Jaeger 配置开关 1=开通 -1=关闭
JaegerOpen = 1
)
-57
View File
@@ -1,57 +0,0 @@
package jaeger_conn
import (
"context"
"github.com/gin-gonic/gin"
"go-gin-api/app/model/proto/listen"
"go-gin-api/app/model/proto/read"
"go-gin-api/app/model/proto/speak"
"go-gin-api/app/model/proto/write"
"go-gin-api/app/util/grpc_client"
"go-gin-api/app/util/request"
"go-gin-api/app/util/response"
)
func JaegerTest(c *gin.Context) {
// 调用 gRPC 服务
conn := grpc_client.CreateServiceListenConn(c)
grpcListenClient := listen.NewListenClient(conn)
resListen, _ := grpcListenClient.ListenData(context.Background(), &listen.Request{Name: "listen"})
// 调用 gRPC 服务
conn = grpc_client.CreateServiceSpeakConn(c)
grpcSpeakClient := speak.NewSpeakClient(conn)
resSpeak, _ := grpcSpeakClient.SpeakData(context.Background(), &speak.Request{Name: "speak"})
// 调用 gRPC 服务
conn = grpc_client.CreateServiceReadConn(c)
grpcReadClient := read.NewReadClient(conn)
resRead, _ := grpcReadClient.ReadData(context.Background(), &read.Request{Name: "read"})
// 调用 gRPC 服务
conn = grpc_client.CreateServiceWriteConn(c)
grpcWriteClient := write.NewWriteClient(conn)
resWrite, _ := grpcWriteClient.WriteData(context.Background(), &write.Request{Name: "write"})
defer conn.Close()
// 调用 HTTP 服务
resHttpGet := ""
_, err := request.HttpGet("http://localhost:9905/sing", c)
if err == nil {
resHttpGet = "[HttpGetOk]"
}
// 业务处理...
msg := resListen.Message + "-" +
resSpeak.Message + "-" +
resRead.Message + "-" +
resWrite.Message + "-" +
resHttpGet
utilGin := response.Gin{Ctx:c}
utilGin.Response(1, msg, nil)
}
-5
View File
@@ -1,5 +0,0 @@
package param_bind
type ProductAdd struct {
Name string `form:"name" json:"name" validate:"required,NameValid"`
}
-13
View File
@@ -1,13 +0,0 @@
package param_verify
import (
"gopkg.in/go-playground/validator.v9"
)
func NameValid(fl validator.FieldLevel) bool {
val := fl.Field().String()
if val == "admin" {
return false
}
return true
}
-54
View File
@@ -1,54 +0,0 @@
package product
import (
"fmt"
"github.com/gin-gonic/gin"
"go-gin-api/app/controller/param_bind"
"go-gin-api/app/controller/param_verify"
"go-gin-api/app/util/bind"
"go-gin-api/app/util/response"
"gopkg.in/go-playground/validator.v9"
)
// 新增
func Add(c *gin.Context) {
utilGin := response.Gin{Ctx: c}
// 参数绑定
s, e := bind.Bind(&param_bind.ProductAdd{}, c)
if e != nil {
utilGin.Response(-1, e.Error(), nil)
return
}
// 参数验证
validate := validator.New()
// 注册自定义验证
_ = validate.RegisterValidation("NameValid", param_verify.NameValid)
if err := validate.Struct(s); err != nil {
utilGin.Response(-1, err.Error(), nil)
return
}
// 业务处理...
utilGin.Response(1, "success", nil)
}
// 编辑
func Edit(c *gin.Context) {
fmt.Println(c.Request.RequestURI)
}
// 删除
func Delete(c *gin.Context) {
fmt.Println(c.Request.RequestURI)
}
// 详情
func Detail(c *gin.Context) {
fmt.Println(c.Request.RequestURI)
}
-58
View File
@@ -1,58 +0,0 @@
package test
import (
"fmt"
"github.com/gin-gonic/gin"
"github.com/xinliangnote/go-util/aes"
"github.com/xinliangnote/go-util/md5"
"github.com/xinliangnote/go-util/rsa"
"go-gin-api/app/util/response"
"time"
)
func Md5Test(c *gin.Context) {
startTime := time.Now()
appSecret := "IgkibX71IEf382PT"
encryptStr := "param_1=xxx&param_2=xxx&ak=xxx&ts=1111111111"
count := 1000000
for i := 0; i < count; i++ {
// 生成签名
md5.MD5(appSecret + encryptStr + appSecret)
// 验证签名
md5.MD5(appSecret + encryptStr + appSecret)
}
utilGin := response.Gin{Ctx: c}
utilGin.Response(1, fmt.Sprintf("%v次 - %v", count, time.Since(startTime)), nil)
}
func AesTest(c *gin.Context) {
startTime := time.Now()
appSecret := "IgkibX71IEf382PT"
encryptStr := "param_1=xxx&param_2=xxx&ak=xxx&ts=1111111111"
count := 1000000
for i := 0; i < count; i++ {
// 生成签名
sn, _ := aes.Encrypt(encryptStr, []byte(appSecret), appSecret)
// 验证签名
aes.Decrypt(sn, []byte(appSecret), appSecret)
}
utilGin := response.Gin{Ctx: c}
utilGin.Response(1, fmt.Sprintf("%v次 - %v", count, time.Since(startTime)), nil)
}
func RsaTest(c *gin.Context) {
startTime := time.Now()
encryptStr := "param_1=xxx&param_2=xxx&ak=xxx&ts=1111111111"
count := 500
for i := 0; i < count; i++ {
// 生成签名
sn, _ := rsa.PublicEncrypt(encryptStr, "rsa/public.pem")
// 验证签名
rsa.PrivateDecrypt(sn, "rsa/private.pem")
}
utilGin := response.Gin{Ctx: c}
utilGin.Response(1, fmt.Sprintf("%v次 - %v", count, time.Since(startTime)), nil)
}
-207
View File
@@ -1,207 +0,0 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: listen.proto
package listen
import (
context "context"
fmt "fmt"
proto "github.com/golang/protobuf/proto"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
math "math"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
// Request 请求结构
type Request struct {
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *Request) Reset() { *m = Request{} }
func (m *Request) String() string { return proto.CompactTextString(m) }
func (*Request) ProtoMessage() {}
func (*Request) Descriptor() ([]byte, []int) {
return fileDescriptor_f05da38a4a3e5177, []int{0}
}
func (m *Request) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Request.Unmarshal(m, b)
}
func (m *Request) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Request.Marshal(b, m, deterministic)
}
func (m *Request) XXX_Merge(src proto.Message) {
xxx_messageInfo_Request.Merge(m, src)
}
func (m *Request) XXX_Size() int {
return xxx_messageInfo_Request.Size(m)
}
func (m *Request) XXX_DiscardUnknown() {
xxx_messageInfo_Request.DiscardUnknown(m)
}
var xxx_messageInfo_Request proto.InternalMessageInfo
func (m *Request) GetName() string {
if m != nil {
return m.Name
}
return ""
}
// Response 响应结构
type Response struct {
Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *Response) Reset() { *m = Response{} }
func (m *Response) String() string { return proto.CompactTextString(m) }
func (*Response) ProtoMessage() {}
func (*Response) Descriptor() ([]byte, []int) {
return fileDescriptor_f05da38a4a3e5177, []int{1}
}
func (m *Response) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Response.Unmarshal(m, b)
}
func (m *Response) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Response.Marshal(b, m, deterministic)
}
func (m *Response) XXX_Merge(src proto.Message) {
xxx_messageInfo_Response.Merge(m, src)
}
func (m *Response) XXX_Size() int {
return xxx_messageInfo_Response.Size(m)
}
func (m *Response) XXX_DiscardUnknown() {
xxx_messageInfo_Response.DiscardUnknown(m)
}
var xxx_messageInfo_Response proto.InternalMessageInfo
func (m *Response) GetMessage() string {
if m != nil {
return m.Message
}
return ""
}
func init() {
proto.RegisterType((*Request)(nil), "listen.Request")
proto.RegisterType((*Response)(nil), "listen.Response")
}
func init() { proto.RegisterFile("listen.proto", fileDescriptor_f05da38a4a3e5177) }
var fileDescriptor_f05da38a4a3e5177 = []byte{
// 133 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0xc9, 0xc9, 0x2c, 0x2e,
0x49, 0xcd, 0xd3, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x83, 0xf0, 0x94, 0x64, 0xb9, 0xd8,
0x83, 0x52, 0x0b, 0x4b, 0x53, 0x8b, 0x4b, 0x84, 0x84, 0xb8, 0x58, 0xf2, 0x12, 0x73, 0x53, 0x25,
0x18, 0x15, 0x18, 0x35, 0x38, 0x83, 0xc0, 0x6c, 0x25, 0x15, 0x2e, 0x8e, 0xa0, 0xd4, 0xe2, 0x82,
0xfc, 0xbc, 0xe2, 0x54, 0x21, 0x09, 0x2e, 0xf6, 0xdc, 0xd4, 0xe2, 0xe2, 0xc4, 0x74, 0x98, 0x12,
0x18, 0xd7, 0xc8, 0x9a, 0x8b, 0xcd, 0x07, 0x6c, 0x9c, 0x90, 0x21, 0x17, 0x17, 0x84, 0xe5, 0x92,
0x58, 0x92, 0x28, 0xc4, 0xaf, 0x07, 0xb5, 0x13, 0x6a, 0x85, 0x94, 0x00, 0x42, 0x00, 0x62, 0xa8,
0x12, 0x43, 0x12, 0x1b, 0xd8, 0x41, 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0xcb, 0xbf, 0x9d,
0x0d, 0xa0, 0x00, 0x00, 0x00,
}
// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConn
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
const _ = grpc.SupportPackageIsVersion4
// ListenClient is the client API for Listen service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type ListenClient interface {
// 定义 ListenData 方法
ListenData(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error)
}
type listenClient struct {
cc *grpc.ClientConn
}
func NewListenClient(cc *grpc.ClientConn) ListenClient {
return &listenClient{cc}
}
func (c *listenClient) ListenData(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) {
out := new(Response)
err := c.cc.Invoke(ctx, "/listen.Listen/ListenData", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// ListenServer is the server API for Listen service.
type ListenServer interface {
// 定义 ListenData 方法
ListenData(context.Context, *Request) (*Response, error)
}
// UnimplementedListenServer can be embedded to have forward compatible implementations.
type UnimplementedListenServer struct {
}
func (*UnimplementedListenServer) ListenData(ctx context.Context, req *Request) (*Response, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListenData not implemented")
}
func RegisterListenServer(s *grpc.Server, srv ListenServer) {
s.RegisterService(&_Listen_serviceDesc, srv)
}
func _Listen_ListenData_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(Request)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ListenServer).ListenData(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/listen.Listen/ListenData",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ListenServer).ListenData(ctx, req.(*Request))
}
return interceptor(ctx, in, info, handler)
}
var _Listen_serviceDesc = grpc.ServiceDesc{
ServiceName: "listen.Listen",
HandlerType: (*ListenServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "ListenData",
Handler: _Listen_ListenData_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "listen.proto",
}
-207
View File
@@ -1,207 +0,0 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: read.proto
package read
import (
context "context"
fmt "fmt"
proto "github.com/golang/protobuf/proto"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
math "math"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
// Request 请求结构
type Request struct {
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *Request) Reset() { *m = Request{} }
func (m *Request) String() string { return proto.CompactTextString(m) }
func (*Request) ProtoMessage() {}
func (*Request) Descriptor() ([]byte, []int) {
return fileDescriptor_7b10ec61df6818dd, []int{0}
}
func (m *Request) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Request.Unmarshal(m, b)
}
func (m *Request) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Request.Marshal(b, m, deterministic)
}
func (m *Request) XXX_Merge(src proto.Message) {
xxx_messageInfo_Request.Merge(m, src)
}
func (m *Request) XXX_Size() int {
return xxx_messageInfo_Request.Size(m)
}
func (m *Request) XXX_DiscardUnknown() {
xxx_messageInfo_Request.DiscardUnknown(m)
}
var xxx_messageInfo_Request proto.InternalMessageInfo
func (m *Request) GetName() string {
if m != nil {
return m.Name
}
return ""
}
// Response 响应结构
type Response struct {
Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *Response) Reset() { *m = Response{} }
func (m *Response) String() string { return proto.CompactTextString(m) }
func (*Response) ProtoMessage() {}
func (*Response) Descriptor() ([]byte, []int) {
return fileDescriptor_7b10ec61df6818dd, []int{1}
}
func (m *Response) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Response.Unmarshal(m, b)
}
func (m *Response) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Response.Marshal(b, m, deterministic)
}
func (m *Response) XXX_Merge(src proto.Message) {
xxx_messageInfo_Response.Merge(m, src)
}
func (m *Response) XXX_Size() int {
return xxx_messageInfo_Response.Size(m)
}
func (m *Response) XXX_DiscardUnknown() {
xxx_messageInfo_Response.DiscardUnknown(m)
}
var xxx_messageInfo_Response proto.InternalMessageInfo
func (m *Response) GetMessage() string {
if m != nil {
return m.Message
}
return ""
}
func init() {
proto.RegisterType((*Request)(nil), "read.Request")
proto.RegisterType((*Response)(nil), "read.Response")
}
func init() { proto.RegisterFile("read.proto", fileDescriptor_7b10ec61df6818dd) }
var fileDescriptor_7b10ec61df6818dd = []byte{
// 132 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x2a, 0x4a, 0x4d, 0x4c,
0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x01, 0xb1, 0x95, 0x64, 0xb9, 0xd8, 0x83, 0x52,
0x0b, 0x4b, 0x53, 0x8b, 0x4b, 0x84, 0x84, 0xb8, 0x58, 0xf2, 0x12, 0x73, 0x53, 0x25, 0x18, 0x15,
0x18, 0x35, 0x38, 0x83, 0xc0, 0x6c, 0x25, 0x15, 0x2e, 0x8e, 0xa0, 0xd4, 0xe2, 0x82, 0xfc, 0xbc,
0xe2, 0x54, 0x21, 0x09, 0x2e, 0xf6, 0xdc, 0xd4, 0xe2, 0xe2, 0xc4, 0x74, 0x98, 0x12, 0x18, 0xd7,
0xc8, 0x98, 0x8b, 0x25, 0x28, 0x35, 0x31, 0x45, 0x48, 0x1b, 0xa4, 0x3a, 0x31, 0xc5, 0x25, 0xb1,
0x24, 0x51, 0x88, 0x57, 0x0f, 0x6c, 0x17, 0xd4, 0x70, 0x29, 0x3e, 0x18, 0x17, 0x62, 0x98, 0x12,
0x43, 0x12, 0x1b, 0xd8, 0x19, 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0x42, 0x4a, 0x9b, 0x2d,
0x94, 0x00, 0x00, 0x00,
}
// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConn
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
const _ = grpc.SupportPackageIsVersion4
// ReadClient is the client API for Read service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type ReadClient interface {
// 定义方法
ReadData(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error)
}
type readClient struct {
cc *grpc.ClientConn
}
func NewReadClient(cc *grpc.ClientConn) ReadClient {
return &readClient{cc}
}
func (c *readClient) ReadData(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) {
out := new(Response)
err := c.cc.Invoke(ctx, "/read.Read/ReadData", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// ReadServer is the server API for Read service.
type ReadServer interface {
// 定义方法
ReadData(context.Context, *Request) (*Response, error)
}
// UnimplementedReadServer can be embedded to have forward compatible implementations.
type UnimplementedReadServer struct {
}
func (*UnimplementedReadServer) ReadData(ctx context.Context, req *Request) (*Response, error) {
return nil, status.Errorf(codes.Unimplemented, "method ReadData not implemented")
}
func RegisterReadServer(s *grpc.Server, srv ReadServer) {
s.RegisterService(&_Read_serviceDesc, srv)
}
func _Read_ReadData_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(Request)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ReadServer).ReadData(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/read.Read/ReadData",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ReadServer).ReadData(ctx, req.(*Request))
}
return interceptor(ctx, in, info, handler)
}
var _Read_serviceDesc = grpc.ServiceDesc{
ServiceName: "read.Read",
HandlerType: (*ReadServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "ReadData",
Handler: _Read_ReadData_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "read.proto",
}
-207
View File
@@ -1,207 +0,0 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: speak.proto
package speak
import (
context "context"
fmt "fmt"
proto "github.com/golang/protobuf/proto"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
math "math"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
// Request 请求结构
type Request struct {
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *Request) Reset() { *m = Request{} }
func (m *Request) String() string { return proto.CompactTextString(m) }
func (*Request) ProtoMessage() {}
func (*Request) Descriptor() ([]byte, []int) {
return fileDescriptor_3c7d3e2f29338937, []int{0}
}
func (m *Request) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Request.Unmarshal(m, b)
}
func (m *Request) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Request.Marshal(b, m, deterministic)
}
func (m *Request) XXX_Merge(src proto.Message) {
xxx_messageInfo_Request.Merge(m, src)
}
func (m *Request) XXX_Size() int {
return xxx_messageInfo_Request.Size(m)
}
func (m *Request) XXX_DiscardUnknown() {
xxx_messageInfo_Request.DiscardUnknown(m)
}
var xxx_messageInfo_Request proto.InternalMessageInfo
func (m *Request) GetName() string {
if m != nil {
return m.Name
}
return ""
}
// Response 响应结构
type Response struct {
Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *Response) Reset() { *m = Response{} }
func (m *Response) String() string { return proto.CompactTextString(m) }
func (*Response) ProtoMessage() {}
func (*Response) Descriptor() ([]byte, []int) {
return fileDescriptor_3c7d3e2f29338937, []int{1}
}
func (m *Response) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Response.Unmarshal(m, b)
}
func (m *Response) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Response.Marshal(b, m, deterministic)
}
func (m *Response) XXX_Merge(src proto.Message) {
xxx_messageInfo_Response.Merge(m, src)
}
func (m *Response) XXX_Size() int {
return xxx_messageInfo_Response.Size(m)
}
func (m *Response) XXX_DiscardUnknown() {
xxx_messageInfo_Response.DiscardUnknown(m)
}
var xxx_messageInfo_Response proto.InternalMessageInfo
func (m *Response) GetMessage() string {
if m != nil {
return m.Message
}
return ""
}
func init() {
proto.RegisterType((*Request)(nil), "speak.Request")
proto.RegisterType((*Response)(nil), "speak.Response")
}
func init() { proto.RegisterFile("speak.proto", fileDescriptor_3c7d3e2f29338937) }
var fileDescriptor_3c7d3e2f29338937 = []byte{
// 132 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x2e, 0x2e, 0x48, 0x4d,
0xcc, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x05, 0x73, 0x94, 0x64, 0xb9, 0xd8, 0x83,
0x52, 0x0b, 0x4b, 0x53, 0x8b, 0x4b, 0x84, 0x84, 0xb8, 0x58, 0xf2, 0x12, 0x73, 0x53, 0x25, 0x18,
0x15, 0x18, 0x35, 0x38, 0x83, 0xc0, 0x6c, 0x25, 0x15, 0x2e, 0x8e, 0xa0, 0xd4, 0xe2, 0x82, 0xfc,
0xbc, 0xe2, 0x54, 0x21, 0x09, 0x2e, 0xf6, 0xdc, 0xd4, 0xe2, 0xe2, 0xc4, 0x74, 0x98, 0x12, 0x18,
0xd7, 0xc8, 0x9c, 0x8b, 0x35, 0x18, 0x64, 0x9a, 0x90, 0x1e, 0x17, 0x27, 0x98, 0xe1, 0x92, 0x58,
0x92, 0x28, 0xc4, 0xa7, 0x07, 0xb1, 0x0f, 0x6a, 0xbe, 0x14, 0x3f, 0x9c, 0x0f, 0x31, 0x50, 0x89,
0x21, 0x89, 0x0d, 0xec, 0x16, 0x63, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0x4d, 0x04, 0x6f, 0x05,
0x9a, 0x00, 0x00, 0x00,
}
// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConn
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
const _ = grpc.SupportPackageIsVersion4
// SpeakClient is the client API for Speak service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type SpeakClient interface {
// 定义方法
SpeakData(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error)
}
type speakClient struct {
cc *grpc.ClientConn
}
func NewSpeakClient(cc *grpc.ClientConn) SpeakClient {
return &speakClient{cc}
}
func (c *speakClient) SpeakData(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) {
out := new(Response)
err := c.cc.Invoke(ctx, "/speak.Speak/SpeakData", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// SpeakServer is the server API for Speak service.
type SpeakServer interface {
// 定义方法
SpeakData(context.Context, *Request) (*Response, error)
}
// UnimplementedSpeakServer can be embedded to have forward compatible implementations.
type UnimplementedSpeakServer struct {
}
func (*UnimplementedSpeakServer) SpeakData(ctx context.Context, req *Request) (*Response, error) {
return nil, status.Errorf(codes.Unimplemented, "method SpeakData not implemented")
}
func RegisterSpeakServer(s *grpc.Server, srv SpeakServer) {
s.RegisterService(&_Speak_serviceDesc, srv)
}
func _Speak_SpeakData_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(Request)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(SpeakServer).SpeakData(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/speak.Speak/SpeakData",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(SpeakServer).SpeakData(ctx, req.(*Request))
}
return interceptor(ctx, in, info, handler)
}
var _Speak_serviceDesc = grpc.ServiceDesc{
ServiceName: "speak.Speak",
HandlerType: (*SpeakServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "SpeakData",
Handler: _Speak_SpeakData_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "speak.proto",
}
-207
View File
@@ -1,207 +0,0 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: write.proto
package write
import (
context "context"
fmt "fmt"
proto "github.com/golang/protobuf/proto"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
math "math"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
// Request 请求结构
type Request struct {
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *Request) Reset() { *m = Request{} }
func (m *Request) String() string { return proto.CompactTextString(m) }
func (*Request) ProtoMessage() {}
func (*Request) Descriptor() ([]byte, []int) {
return fileDescriptor_67966b2b12a73214, []int{0}
}
func (m *Request) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Request.Unmarshal(m, b)
}
func (m *Request) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Request.Marshal(b, m, deterministic)
}
func (m *Request) XXX_Merge(src proto.Message) {
xxx_messageInfo_Request.Merge(m, src)
}
func (m *Request) XXX_Size() int {
return xxx_messageInfo_Request.Size(m)
}
func (m *Request) XXX_DiscardUnknown() {
xxx_messageInfo_Request.DiscardUnknown(m)
}
var xxx_messageInfo_Request proto.InternalMessageInfo
func (m *Request) GetName() string {
if m != nil {
return m.Name
}
return ""
}
// Response 响应结构
type Response struct {
Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *Response) Reset() { *m = Response{} }
func (m *Response) String() string { return proto.CompactTextString(m) }
func (*Response) ProtoMessage() {}
func (*Response) Descriptor() ([]byte, []int) {
return fileDescriptor_67966b2b12a73214, []int{1}
}
func (m *Response) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Response.Unmarshal(m, b)
}
func (m *Response) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Response.Marshal(b, m, deterministic)
}
func (m *Response) XXX_Merge(src proto.Message) {
xxx_messageInfo_Response.Merge(m, src)
}
func (m *Response) XXX_Size() int {
return xxx_messageInfo_Response.Size(m)
}
func (m *Response) XXX_DiscardUnknown() {
xxx_messageInfo_Response.DiscardUnknown(m)
}
var xxx_messageInfo_Response proto.InternalMessageInfo
func (m *Response) GetMessage() string {
if m != nil {
return m.Message
}
return ""
}
func init() {
proto.RegisterType((*Request)(nil), "write.Request")
proto.RegisterType((*Response)(nil), "write.Response")
}
func init() { proto.RegisterFile("write.proto", fileDescriptor_67966b2b12a73214) }
var fileDescriptor_67966b2b12a73214 = []byte{
// 132 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x2e, 0x2f, 0xca, 0x2c,
0x49, 0xd5, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x05, 0x73, 0x94, 0x64, 0xb9, 0xd8, 0x83,
0x52, 0x0b, 0x4b, 0x53, 0x8b, 0x4b, 0x84, 0x84, 0xb8, 0x58, 0xf2, 0x12, 0x73, 0x53, 0x25, 0x18,
0x15, 0x18, 0x35, 0x38, 0x83, 0xc0, 0x6c, 0x25, 0x15, 0x2e, 0x8e, 0xa0, 0xd4, 0xe2, 0x82, 0xfc,
0xbc, 0xe2, 0x54, 0x21, 0x09, 0x2e, 0xf6, 0xdc, 0xd4, 0xe2, 0xe2, 0xc4, 0x74, 0x98, 0x12, 0x18,
0xd7, 0xc8, 0x9c, 0x8b, 0x35, 0x1c, 0x64, 0x9a, 0x90, 0x1e, 0x17, 0x27, 0x98, 0xe1, 0x92, 0x58,
0x92, 0x28, 0xc4, 0xa7, 0x07, 0xb1, 0x0f, 0x6a, 0xbe, 0x14, 0x3f, 0x9c, 0x0f, 0x31, 0x50, 0x89,
0x21, 0x89, 0x0d, 0xec, 0x16, 0x63, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0xb3, 0x7d, 0xc4, 0x7d,
0x9a, 0x00, 0x00, 0x00,
}
// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConn
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
const _ = grpc.SupportPackageIsVersion4
// WriteClient is the client API for Write service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type WriteClient interface {
// 定义方法
WriteData(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error)
}
type writeClient struct {
cc *grpc.ClientConn
}
func NewWriteClient(cc *grpc.ClientConn) WriteClient {
return &writeClient{cc}
}
func (c *writeClient) WriteData(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) {
out := new(Response)
err := c.cc.Invoke(ctx, "/write.Write/WriteData", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// WriteServer is the server API for Write service.
type WriteServer interface {
// 定义方法
WriteData(context.Context, *Request) (*Response, error)
}
// UnimplementedWriteServer can be embedded to have forward compatible implementations.
type UnimplementedWriteServer struct {
}
func (*UnimplementedWriteServer) WriteData(ctx context.Context, req *Request) (*Response, error) {
return nil, status.Errorf(codes.Unimplemented, "method WriteData not implemented")
}
func RegisterWriteServer(s *grpc.Server, srv WriteServer) {
s.RegisterService(&_Write_serviceDesc, srv)
}
func _Write_WriteData_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(Request)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WriteServer).WriteData(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/write.Write/WriteData",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WriteServer).WriteData(ctx, req.(*Request))
}
return interceptor(ctx, in, info, handler)
}
var _Write_serviceDesc = grpc.ServiceDesc{
ServiceName: "write.Write",
HandlerType: (*WriteServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "WriteData",
Handler: _Write_WriteData_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "write.proto",
}
@@ -1,51 +0,0 @@
package exception
import (
"fmt"
"github.com/gin-gonic/gin"
"github.com/xinliangnote/go-util/mail"
"github.com/xinliangnote/go-util/time"
"go-gin-api/app/config"
"go-gin-api/app/util/response"
"runtime/debug"
"strings"
)
func SetUp() gin.HandlerFunc {
return func(c *gin.Context) {
defer func() {
if err := recover(); err != nil {
DebugStack := ""
for _, v := range strings.Split(string(debug.Stack()), "\n") {
DebugStack += v + "<br>"
}
subject := fmt.Sprintf("【重要错误】%s 项目出错了!", config.AppName)
body := strings.ReplaceAll(MailTemplate, "{ErrorMsg}", fmt.Sprintf("%s", err))
body = strings.ReplaceAll(body, "{RequestTime}", time.GetCurrentDate())
body = strings.ReplaceAll(body, "{RequestURL}", c.Request.Method + " " + c.Request.Host + c.Request.RequestURI)
body = strings.ReplaceAll(body, "{RequestUA}", c.Request.UserAgent())
body = strings.ReplaceAll(body, "{RequestIP}", c.ClientIP())
body = strings.ReplaceAll(body, "{DebugStack}", DebugStack)
options := &mail.Options{
MailHost : config.SystemEmailHost,
MailPort : config.SystemEmailPort,
MailUser : config.SystemEmailUser,
MailPass : config.SystemEmailPass,
MailTo : config.ErrorNotifyUser,
Subject : subject,
Body : body,
}
_ = mail.Send(options)
utilGin := response.Gin{Ctx: c}
utilGin.Response(500, "系统异常,请联系管理员!", nil)
}
}()
c.Next()
}
}
-946
View File
@@ -1,946 +0,0 @@
package exception
var MailTemplate = `<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<div class="content-wrap" style="margin: 0px auto; overflow: hidden; padding-top: 15px; background-color: rgb(255, 255, 255); width: 600px;">
<!---->
<div>
<div style="margin: 0px auto; max-width: 600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" style="background-color: rgb(62, 207, 88); background-image: url(&quot;&quot;); background-repeat: no-repeat; background-size: 100px; background-position: 1% 50%;">
<tbody>
<tr>
<td style="direction: ltr; font-size: 0px; text-align: center; vertical-align: top; width: 600px;">
<table border="0" cellpadding="0" cellspacing="0">
<tbody>
<tr>
<td style="width: 100%; max-width: 100%; min-height: 1px; font-size: 13px; text-align: left; direction: ltr; vertical-align: top; padding: 0px;">
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="center" style="font-size: 0px; word-break: break-word;">
<table align="center" border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse; border-spacing: 0px; width: 100%;">
<tbody>
<tr>
<td>
<div class="full">
<div style="margin: 0px auto; max-width: 600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 600px;">
<tbody>
<tr>
<td style="direction: ltr; width: 600px; font-size: 0px; padding-bottom: 0px; text-align: center; vertical-align: top; background-image: url(&quot;&quot;); background-repeat: no-repeat; background-size: 100px; background-position: 10% 50%;">
<div style="font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top; width: 100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="left" style="font-size: 0px; padding: 20px; word-break: break-word;">
<div class="text" style="margin: 0px; text-align: center; color: rgb(255, 255, 255); font-size: 16px;">
<div>
<p style="line-height: 20px; margin: 0px;">系统告警</p>
</div>
</div></td>
</tr>
</tbody>
</table>
</div></td>
</tr>
</tbody>
</table>
</div>
</div></td>
</tr>
</tbody>
</table></td>
</tr>
</tbody>
</table></td>
</tr>
</tbody>
</table></td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="full">
<div style="margin: 0px auto; max-width: 600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 600px;">
<tbody>
<tr>
<td style="direction: ltr; font-size: 0px; padding-top: 0px; text-align: center; vertical-align: top;">
<div class="outlook-group-fix" style="font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top; width: 100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="center" vertical-align="middle" style="padding-top: 40px; width: 600px; background-image: url(&quot;&quot;); background-size: 100px; background-position: 10% 50%; background-repeat: no-repeat;"></td>
</tr>
</tbody>
</table>
</div></td>
</tr>
</tbody>
</table>
</div>
</div>
<div>
<div style="margin: 0px auto; max-width: 600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" style="background-color: rgb(255, 255, 255); background-image: url(&quot;&quot;); background-repeat: no-repeat; background-size: 100px; background-position: 1% 50%;">
<tbody>
<tr>
<td style="direction: ltr; font-size: 0px; text-align: center; vertical-align: top; width: 600px;">
<table border="0" cellpadding="0" cellspacing="0">
<tbody>
<tr>
<td style="width: 40%; max-width: 40%; min-height: 1px; font-size: 13px; text-align: left; direction: ltr; vertical-align: top; padding: 0px;">
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="center" style="font-size: 0px; word-break: break-word;">
<table align="center" border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse; border-spacing: 0px; width: 100%;">
<tbody>
<tr>
<td>
<div class="full">
<div style="margin: 0px auto; max-width: 600px; line-height: 0px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 240px;">
<tbody>
<tr>
<td style="direction: ltr; font-size: 0px; padding: 0px; text-align: center; vertical-align: top;">
<div class="outlook-group-fix" style="line-height: 0px; font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top; width: 100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="center" style="font-size: 0px; padding: 25px 0px; word-break: break-word; width: 240px; background-image: url(&quot;&quot;); background-repeat: no-repeat; background-size: 100px; background-position: 10% 50%;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse: collapse; border-spacing: 0px;">
<tbody>
<tr>
<td style="width: 375px; border-top: 1px solid rgb(204, 204, 204);"></td>
</tr>
</tbody>
</table></td>
</tr>
</tbody>
</table>
</div></td>
</tr>
</tbody>
</table>
</div>
</div></td>
</tr>
</tbody>
</table></td>
</tr>
</tbody>
</table></td>
<td style="width: 20%; max-width: 20%; min-height: 1px; font-size: 13px; text-align: left; direction: ltr; vertical-align: top; padding: 0px;">
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="center" style="font-size: 0px; word-break: break-word;">
<table align="center" border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse; border-spacing: 0px; width: 100%;">
<tbody>
<tr>
<td>
<div class="full">
<div style="margin: 0px auto; max-width: 600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 120px;">
<tbody>
<tr>
<td style="direction: ltr; width: 120px; font-size: 0px; padding-bottom: 0px; text-align: center; vertical-align: top; background-image: url(&quot;&quot;); background-repeat: no-repeat; background-size: 100px; background-position: 10% 50%;">
<div style="font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top; width: 100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="left" style="font-size: 0px; padding: 13px 20px; word-break: break-word;">
<div class="text" style="margin: 0px; text-align: center; color: rgb(51, 51, 51); font-size: 16px;">
<div>
<p style="line-height: 20px; margin: 0px;">告警</p>
</div>
</div></td>
</tr>
</tbody>
</table>
</div></td>
</tr>
</tbody>
</table>
</div>
</div></td>
</tr>
</tbody>
</table></td>
</tr>
</tbody>
</table></td>
<td style="width: 40%; max-width: 40%; min-height: 1px; font-size: 13px; text-align: left; direction: ltr; vertical-align: top; padding: 0px;">
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="center" style="font-size: 0px; word-break: break-word;">
<table align="center" border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse; border-spacing: 0px; width: 100%;">
<tbody>
<tr>
<td>
<div class="full">
<div style="margin: 0px auto; max-width: 600px; line-height: 0px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 240px;">
<tbody>
<tr>
<td style="direction: ltr; font-size: 0px; padding: 0px; text-align: center; vertical-align: top;">
<div class="outlook-group-fix" style="line-height: 0px; font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top; width: 100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="center" style="font-size: 0px; padding: 25px 0px; word-break: break-word; width: 240px; background-image: url(&quot;&quot;); background-repeat: no-repeat; background-size: 100px; background-position: 10% 50%;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse: collapse; border-spacing: 0px;">
<tbody>
<tr>
<td style="width: 375px; border-top: 1px solid rgb(204, 204, 204);"></td>
</tr>
</tbody>
</table></td>
</tr>
</tbody>
</table>
</div></td>
</tr>
</tbody>
</table>
</div>
</div></td>
</tr>
</tbody>
</table></td>
</tr>
</tbody>
</table></td>
</tr>
</tbody>
</table></td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="full">
<div style="margin: 0px auto; max-width: 600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 600px;">
<tbody>
<tr>
<td style="direction: ltr; width: 600px; font-size: 0px; padding-bottom: 0px; text-align: center; vertical-align: top; background-image: url(&quot;&quot;); background-repeat: no-repeat; background-size: 100px; background-position: 10% 50%;">
<div style="font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top; width: 100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="left" style="font-size: 0px; padding: 6px 20px; word-break: break-word;">
<div class="text" style="margin: 0px; text-align: left; color: rgb(188, 12, 39); font-size: 21px;">
<div>
<p style="line-height: 20px; margin: 0px;">{ErrorMsg}</p>
</div>
</div></td>
</tr>
</tbody>
</table>
</div></td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="full">
<div style="margin: 0px auto; max-width: 600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 600px;">
<tbody>
<tr>
<td style="direction: ltr; font-size: 0px; padding-top: 0px; text-align: center; vertical-align: top;">
<div class="outlook-group-fix" style="font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top; width: 100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="center" vertical-align="middle" style="padding-top: 15px; width: 600px; background-image: url(&quot;&quot;); background-size: 100px; background-position: 10% 50%; background-repeat: no-repeat;"></td>
</tr>
</tbody>
</table>
</div></td>
</tr>
</tbody>
</table>
</div>
</div>
<div>
<div style="margin: 0px auto; max-width: 600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" style="background-color: rgb(255, 255, 255); background-image: url(&quot;&quot;); background-repeat: no-repeat; background-size: 100px; background-position: 1% 50%;">
<tbody>
<tr>
<td style="direction: ltr; font-size: 0px; text-align: center; vertical-align: top; width: 600px;">
<table border="0" cellpadding="0" cellspacing="0">
<tbody>
<tr>
<td style="width: 40%; max-width: 40%; min-height: 1px; font-size: 13px; text-align: left; direction: ltr; vertical-align: top; padding: 0px;">
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="center" style="font-size: 0px; word-break: break-word;">
<table align="center" border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse; border-spacing: 0px; width: 100%;">
<tbody>
<tr>
<td>
<div class="full">
<div style="margin: 0px auto; max-width: 600px; line-height: 0px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 240px;">
<tbody>
<tr>
<td style="direction: ltr; font-size: 0px; padding: 0px; text-align: center; vertical-align: top;">
<div class="outlook-group-fix" style="line-height: 0px; font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top; width: 100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="center" style="font-size: 0px; padding: 25px 0px; word-break: break-word; width: 240px; background-image: url(&quot;&quot;); background-repeat: no-repeat; background-size: 100px; background-position: 10% 50%;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse: collapse; border-spacing: 0px;">
<tbody>
<tr>
<td style="width: 375px; border-top: 1px solid rgb(204, 204, 204);"></td>
</tr>
</tbody>
</table></td>
</tr>
</tbody>
</table>
</div></td>
</tr>
</tbody>
</table>
</div>
</div></td>
</tr>
</tbody>
</table></td>
</tr>
</tbody>
</table></td>
<td style="width: 20%; max-width: 20%; min-height: 1px; font-size: 13px; text-align: left; direction: ltr; vertical-align: top; padding: 0px;">
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="center" style="font-size: 0px; word-break: break-word;">
<table align="center" border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse; border-spacing: 0px; width: 100%;">
<tbody>
<tr>
<td>
<div class="full">
<div style="margin: 0px auto; max-width: 600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 120px;">
<tbody>
<tr>
<td style="direction: ltr; width: 120px; font-size: 0px; padding-bottom: 0px; text-align: center; vertical-align: top; background-image: url(&quot;&quot;); background-repeat: no-repeat; background-size: 100px; background-position: 10% 50%;">
<div style="font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top; width: 100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="left" style="font-size: 0px; padding: 13px 20px; word-break: break-word;">
<div class="text" style="margin: 0px; text-align: center; color: rgb(51, 51, 51); font-size: 16px;">
<div>
<p style="line-height: 20px; margin: 0px;">详情</p>
</div>
</div></td>
</tr>
</tbody>
</table>
</div></td>
</tr>
</tbody>
</table>
</div>
</div></td>
</tr>
</tbody>
</table></td>
</tr>
</tbody>
</table></td>
<td style="width: 40%; max-width: 40%; min-height: 1px; font-size: 13px; text-align: left; direction: ltr; vertical-align: top; padding: 0px;">
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="center" style="font-size: 0px; word-break: break-word;">
<table align="center" border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse; border-spacing: 0px; width: 100%;">
<tbody>
<tr>
<td>
<div class="full">
<div style="margin: 0px auto; max-width: 600px; line-height: 0px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 240px;">
<tbody>
<tr>
<td style="direction: ltr; font-size: 0px; padding: 0px; text-align: center; vertical-align: top;">
<div class="outlook-group-fix" style="line-height: 0px; font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top; width: 100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="center" style="font-size: 0px; padding: 25px 0px; word-break: break-word; width: 240px; background-image: url(&quot;&quot;); background-repeat: no-repeat; background-size: 100px; background-position: 10% 50%;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse: collapse; border-spacing: 0px;">
<tbody>
<tr>
<td style="width: 375px; border-top: 1px solid rgb(204, 204, 204);"></td>
</tr>
</tbody>
</table></td>
</tr>
</tbody>
</table>
</div></td>
</tr>
</tbody>
</table>
</div>
</div></td>
</tr>
</tbody>
</table></td>
</tr>
</tbody>
</table></td>
</tr>
</tbody>
</table></td>
</tr>
</tbody>
</table>
</div>
</div>
<div>
<div style="margin: 0px auto; max-width: 600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" style="background-color: rgb(241, 245, 240); background-image: url(&quot;&quot;); background-repeat: no-repeat; background-size: 100px; background-position: 1% 50%;">
<tbody>
<tr>
<td style="direction: ltr; font-size: 0px; text-align: center; vertical-align: top; width: 600px;">
<table border="0" cellpadding="0" cellspacing="0">
<tbody>
<tr>
<td style="width: 100%; max-width: 100%; min-height: 1px; font-size: 13px; text-align: left; direction: ltr; vertical-align: top; padding: 0px;">
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="center" style="font-size: 0px; word-break: break-word;">
<table align="center" border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse; border-spacing: 0px; width: 100%;">
<tbody>
<tr>
<td>
<div columnnumber="3">
<div>
<table align="center" border="0" cellpadding="0" cellspacing="0" style="width: 100%;">
<tbody>
<tr>
<td style="direction: ltr; font-size: 0px; text-align: center; vertical-align: top; border: 0px;"><a target="_blank" href="javascript:;" style="cursor: default;">
<div class="mj-column-per-25" style="width: 100%; max-width: 100%; font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top;">
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="center" border="0" style="font-size: 0px; word-break: break-word;">
<table align="center" border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse; border-spacing: 0px; width: 100%;">
<tbody>
<tr>
<td border="0">
<div class="full">
<div style="margin: 0px auto; max-width: 600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 600px;">
<tbody>
<tr>
<td style="direction: ltr; width: 600px; font-size: 0px; padding-bottom: 0px; text-align: center; vertical-align: top; background-image: url(&quot;&quot;); background-repeat: no-repeat; background-size: 100px; background-position: 10% 50%;">
<div style="font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top; width: 100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="left" style="font-size: 0px; padding: 10px 20px; word-break: break-word;">
<div class="text" style="margin: 0px; text-align: left; color: rgb(102, 102, 102); font-size: 12px;">
<div>
<p style="line-height: 20px; margin: 0px;">请求时间:</p>
</div>
</div></td>
</tr>
</tbody>
</table>
</div></td>
</tr>
</tbody>
</table>
</div>
</div></td>
</tr>
</tbody>
</table></td>
</tr>
</tbody>
</table>
</div>
<div class="mj-column-per-25" style="width: 100%; max-width: 100%; font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top;">
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="center" border="0" style="font-size: 0px; word-break: break-word;">
<table align="center" border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse; border-spacing: 0px; width: 100%;">
<tbody>
<tr>
<td border="0">
<div class="full">
<div style="margin: 0px auto; max-width: 600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 600px;">
<tbody>
<tr>
<td style="direction: ltr; width: 600px; font-size: 0px; padding-bottom: 0px; text-align: center; vertical-align: top; background-image: url(&quot;&quot;); background-repeat: no-repeat; background-size: 100px; background-position: 10% 50%;">
<div style="font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top; width: 100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="left" style="font-size: 0px; padding: 0px 20px; word-break: break-word;">
<div class="text" style="margin: 0px; text-align: left; color: rgb(102, 102, 102); font-size: 12px;">
<div>
<p style="line-height: 20px; margin: 0px;">{RequestTime}</p>
</div>
</div></td>
</tr>
</tbody>
</table>
</div></td>
</tr>
</tbody>
</table>
</div>
</div></td>
</tr>
</tbody>
</table></td>
</tr>
</tbody>
</table>
</div>
<div class="mj-column-per-25" style="width: 100%; max-width: 100%; font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top;">
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="center" border="0" style="font-size: 0px; word-break: break-word;">
<table align="center" border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse; border-spacing: 0px; width: 100%;">
<tbody>
<tr>
<td border="0">
<div class="full">
<div style="margin: 0px auto; max-width: 600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 600px;">
<tbody>
<tr>
<td style="direction: ltr; width: 600px; font-size: 0px; padding-bottom: 0px; text-align: center; vertical-align: top; background-image: url(&quot;&quot;); background-repeat: no-repeat; background-size: 100px; background-position: 10% 50%;">
<div style="font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top; width: 100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="left" style="font-size: 0px; padding: 10px 20px; word-break: break-word;">
<div class="text" style="margin: 0px; text-align: left; color: rgb(102, 102, 102); font-size: 12px;">
<div>
<p style="line-height: 20px; margin: 0px;">请求地址:</p>
</div>
</div></td>
</tr>
</tbody>
</table>
</div></td>
</tr>
</tbody>
</table>
</div>
</div></td>
</tr>
</tbody>
</table></td>
</tr>
</tbody>
</table>
</div>
<div class="mj-column-per-25" style="width: 100%; max-width: 100%; font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top;">
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="center" border="0" style="font-size: 0px; word-break: break-word;">
<table align="center" border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse; border-spacing: 0px; width: 100%;">
<tbody>
<tr>
<td border="0">
<div class="full">
<div style="margin: 0px auto; max-width: 600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 600px;">
<tbody>
<tr>
<td style="direction: ltr; width: 600px; font-size: 0px; padding-bottom: 0px; text-align: center; vertical-align: top; background-image: url(&quot;&quot;); background-repeat: no-repeat; background-size: 100px; background-position: 10% 50%;">
<div style="font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top; width: 100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="left" style="font-size: 0px; padding: 0px 20px; word-break: break-word;">
<div class="text" style="margin: 0px; text-align: left; color: rgb(102, 102, 102); font-size: 12px;">
<div>
<p style="line-height: 20px; margin: 0px;">{RequestURL}</p>
</div>
</div></td>
</tr>
</tbody>
</table>
</div></td>
</tr>
</tbody>
</table>
</div>
</div></td>
</tr>
</tbody>
</table></td>
</tr>
</tbody>
</table>
</div>
<div class="mj-column-per-25" style="width: 100%; max-width: 100%; font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top;">
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="center" border="0" style="font-size: 0px; word-break: break-word;">
<table align="center" border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse; border-spacing: 0px; width: 100%;">
<tbody>
<tr>
<td border="0">
<div class="full">
<div style="margin: 0px auto; max-width: 600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 600px;">
<tbody>
<tr>
<td style="direction: ltr; width: 600px; font-size: 0px; padding-bottom: 0px; text-align: center; vertical-align: top; background-image: url(&quot;&quot;); background-repeat: no-repeat; background-size: 100px; background-position: 10% 50%;">
<div style="font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top; width: 100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="left" style="font-size: 0px; padding: 10px 20px; word-break: break-word;">
<div class="text" style="margin: 0px; text-align: left; color: rgb(102, 102, 102); font-size: 12px;">
<div>
<p style="line-height: 20px; margin: 0px;">请求 UA</p>
</div>
</div></td>
</tr>
</tbody>
</table>
</div></td>
</tr>
</tbody>
</table>
</div>
</div></td>
</tr>
</tbody>
</table></td>
</tr>
</tbody>
</table>
</div>
<div class="mj-column-per-25" style="width: 100%; max-width: 100%; font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top;">
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="center" border="0" style="font-size: 0px; word-break: break-word;">
<table align="center" border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse; border-spacing: 0px; width: 100%;">
<tbody>
<tr>
<td border="0">
<div class="full">
<div style="margin: 0px auto; max-width: 600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 600px;">
<tbody>
<tr>
<td style="direction: ltr; width: 600px; font-size: 0px; padding-bottom: 0px; text-align: center; vertical-align: top; background-image: url(&quot;&quot;); background-repeat: no-repeat; background-size: 100px; background-position: 10% 50%;">
<div style="font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top; width: 100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="left" style="font-size: 0px; padding: 0px 20px; word-break: break-word;">
<div class="text" style="margin: 0px; text-align: left; color: rgb(102, 102, 102); font-size: 12px;">
<div>
<p style="line-height: 20px; margin: 0px;">{RequestUA}</p>
</div>
</div></td>
</tr>
</tbody>
</table>
</div></td>
</tr>
</tbody>
</table>
</div>
</div></td>
</tr>
</tbody>
</table></td>
</tr>
</tbody>
</table>
</div>
<div class="mj-column-per-25" style="width: 100%; max-width: 100%; font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top;">
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="center" border="0" style="font-size: 0px; word-break: break-word;">
<table align="center" border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse; border-spacing: 0px; width: 100%;">
<tbody>
<tr>
<td border="0">
<div class="full">
<div style="margin: 0px auto; max-width: 600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 600px;">
<tbody>
<tr>
<td style="direction: ltr; width: 600px; font-size: 0px; padding-bottom: 0px; text-align: center; vertical-align: top; background-image: url(&quot;&quot;); background-repeat: no-repeat; background-size: 100px; background-position: 10% 50%;">
<div style="font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top; width: 100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="left" style="font-size: 0px; padding: 10px 20px; word-break: break-word;">
<div class="text" style="margin: 0px; text-align: left; color: rgb(102, 102, 102); font-size: 12px;">
<div>
<p style="line-height: 20px; margin: 0px;">请求 IP</p>
</div>
</div></td>
</tr>
</tbody>
</table>
</div></td>
</tr>
</tbody>
</table>
</div>
</div></td>
</tr>
</tbody>
</table></td>
</tr>
</tbody>
</table>
</div>
<div class="mj-column-per-25" style="width: 100%; max-width: 100%; font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top;">
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="center" border="0" style="font-size: 0px; word-break: break-word;">
<table align="center" border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse; border-spacing: 0px; width: 100%;">
<tbody>
<tr>
<td border="0">
<div class="full">
<div style="margin: 0px auto; max-width: 600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 600px;">
<tbody>
<tr>
<td style="direction: ltr; width: 600px; font-size: 0px; padding-bottom: 0px; text-align: center; vertical-align: top; background-image: url(&quot;&quot;); background-repeat: no-repeat; background-size: 100px; background-position: 10% 50%;">
<div style="font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top; width: 100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="left" style="font-size: 0px; padding: 0px 20px; word-break: break-word;">
<div class="text" style="margin: 0px; text-align: left; color: rgb(102, 102, 102); font-size: 12px;">
<div>
<p style="line-height: 20px; margin: 0px;">{RequestIP}</p>
</div>
</div></td>
</tr>
</tbody>
</table>
</div></td>
</tr>
</tbody>
</table>
</div>
</div></td>
</tr>
</tbody>
</table></td>
</tr>
</tbody>
</table>
</div>
<div class="mj-column-per-25" style="width: 100%; max-width: 100%; font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top;">
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="center" border="0" style="font-size: 0px; word-break: break-word;">
<table align="center" border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse; border-spacing: 0px; width: 100%;">
<tbody>
<tr>
<td border="0">
<div class="full">
<div style="margin: 0px auto; max-width: 600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 600px;">
<tbody>
<tr>
<td style="direction: ltr; width: 600px; font-size: 0px; padding-bottom: 0px; text-align: center; vertical-align: top; background-image: url(&quot;&quot;); background-repeat: no-repeat; background-size: 100px; background-position: 10% 50%;">
<div style="font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top; width: 100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="left" style="font-size: 0px; padding: 10px 20px; word-break: break-word;">
<div class="text" style="margin: 0px; text-align: left; color: rgb(102, 102, 102); font-size: 12px;">
<div>
<p style="line-height: 20px; margin: 0px;">DebugStack</p>
</div>
</div></td>
</tr>
</tbody>
</table>
</div></td>
</tr>
</tbody>
</table>
</div>
</div></td>
</tr>
</tbody>
</table></td>
</tr>
</tbody>
</table>
</div>
<div class="mj-column-per-25" style="width: 100%; max-width: 100%; font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top;">
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="center" border="0" style="font-size: 0px; word-break: break-word;">
<table align="center" border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse; border-spacing: 0px; width: 100%;">
<tbody>
<tr>
<td border="0">
<div class="full">
<div style="margin: 0px auto; max-width: 600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 600px;">
<tbody>
<tr>
<td style="direction: ltr; width: 600px; font-size: 0px; padding-bottom: 0px; text-align: center; vertical-align: top; background-image: url(&quot;&quot;); background-repeat: no-repeat; background-size: 100px; background-position: 10% 50%;">
<div style="font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top; width: 100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="left" style="font-size: 0px; padding: 0px 20px; word-break: break-word;">
<div class="text" style="margin: 0px; text-align: left; color: rgb(102, 102, 102); font-size: 12px;">
<div>
<p style="line-height: 20px; margin: 0px;">{DebugStack}</p>
</div>
</div></td>
</tr>
</tbody>
</table>
</div></td>
</tr>
</tbody>
</table>
</div>
</div></td>
</tr>
</tbody>
</table></td>
</tr>
</tbody>
</table>
</div></a></td>
</tr>
</tbody>
</table>
</div>
</div></td>
</tr>
</tbody>
</table></td>
</tr>
</tbody>
</table></td>
</tr>
</tbody>
</table></td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="full">
<div style="margin: 0px auto; max-width: 600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 600px;">
<tbody>
<tr>
<td style="direction: ltr; font-size: 0px; padding-top: 0px; text-align: center; vertical-align: top;">
<div class="outlook-group-fix" style="font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top; width: 100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="center" vertical-align="middle" style="padding-top: 15px; width: 600px; background-image: url(&quot;&quot;); background-size: 100px; background-position: 10% 50%; background-repeat: no-repeat;"></td>
</tr>
</tbody>
</table>
</div></td>
</tr>
</tbody>
</table>
</div>
</div>
<div>
<div style="margin: 0px auto; max-width: 600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" style="background-color: rgb(65, 207, 88); background-image: url(&quot;&quot;); background-repeat: no-repeat; background-size: 100px; background-position: 1% 50%;">
<tbody>
<tr>
<td style="direction: ltr; font-size: 0px; text-align: center; vertical-align: top; width: 600px;">
<table border="0" cellpadding="0" cellspacing="0">
<tbody>
<tr>
<td style="width: 100%; max-width: 100%; min-height: 1px; font-size: 13px; text-align: left; direction: ltr; vertical-align: top; padding: 0px;">
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="center" style="font-size: 0px; word-break: break-word;">
<table align="center" border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse; border-spacing: 0px; width: 100%;">
<tbody>
<tr>
<td>
<div class="full">
<div style="margin: 0px auto; max-width: 600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 600px;">
<tbody>
<tr>
<td style="direction: ltr; width: 600px; font-size: 0px; padding-bottom: 0px; text-align: center; vertical-align: top; background-image: url(&quot;&quot;); background-repeat: no-repeat; background-size: 100px; background-position: 10% 50%;">
<div style="font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top; width: 100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="left" style="font-size: 0px; padding: 20px 10px; word-break: break-word;">
<div class="text" style="margin: 0px; text-align: left; color: rgb(255, 255, 255); font-size: 12px;">
<div>
<p style="line-height: 20px; margin: 0px;">请注意,该邮件地址不接收回复邮件。</p>
</div>
</div></td>
</tr>
</tbody>
</table>
</div></td>
</tr>
</tbody>
</table>
</div>
</div></td>
</tr>
</tbody>
</table></td>
</tr>
</tbody>
</table></td>
</tr>
</tbody>
</table></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</body>
</html>`
-39
View File
@@ -1,39 +0,0 @@
package jaeger
import (
"github.com/gin-gonic/gin"
"github.com/opentracing/opentracing-go"
"github.com/opentracing/opentracing-go/ext"
"go-gin-api/app/config"
"go-gin-api/app/util/jaeger_trace"
)
func SetUp() gin.HandlerFunc {
return func(c *gin.Context) {
if config.JaegerOpen == 1 {
var parentSpan opentracing.Span
tracer, closer := jaeger_trace.NewJaegerTracer(config.AppName, config.JaegerHostPort)
defer closer.Close()
spCtx, err := opentracing.GlobalTracer().Extract(opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(c.Request.Header))
if err != nil {
parentSpan = tracer.StartSpan(c.Request.URL.Path)
defer parentSpan.Finish()
} else {
parentSpan = opentracing.StartSpan(
c.Request.URL.Path,
opentracing.ChildOf(spCtx),
opentracing.Tag{Key: string(ext.Component), Value: "HTTP"},
ext.SpanKindRPCServer,
)
defer parentSpan.Finish()
}
c.Set("Tracer", tracer)
c.Set("ParentSpanContext", parentSpan.Context())
}
c.Next()
}
}
-25
View File
@@ -1,25 +0,0 @@
package limiter
import (
"fmt"
"github.com/gin-gonic/gin"
"go-gin-api/app/util/response"
"golang.org/x/time/rate"
"time"
)
func SetUp (maxBurstSize int) gin.HandlerFunc {
limiter := rate.NewLimiter(rate.Every(time.Second*1), maxBurstSize)
return func(c *gin.Context) {
if limiter.Allow() {
c.Next()
return
}
fmt.Println("Too many requests")
utilGin := response.Gin{Ctx: c}
utilGin.Response(-1, "Too many requests", nil)
c.Abort()
return
}
}
-103
View File
@@ -1,103 +0,0 @@
package logger
import (
"bytes"
"encoding/json"
"fmt"
"github.com/gin-gonic/gin"
jsonUtil "github.com/xinliangnote/go-util/json"
"github.com/xinliangnote/go-util/time"
"go-gin-api/app/config"
"go-gin-api/app/util/response"
"log"
"os"
)
type bodyLogWriter struct {
gin.ResponseWriter
body *bytes.Buffer
}
var accessChannel = make(chan string, 100)
func (w bodyLogWriter) Write(b []byte) (int, error) {
w.body.Write(b)
return w.ResponseWriter.Write(b)
}
func (w bodyLogWriter) WriteString(s string) (int, error) {
w.body.WriteString(s)
return w.ResponseWriter.WriteString(s)
}
func SetUp() gin.HandlerFunc {
go handleAccessChannel()
return func(c *gin.Context) {
bodyLogWriter := &bodyLogWriter{body: bytes.NewBufferString(""), ResponseWriter: c.Writer}
c.Writer = bodyLogWriter
// 开始时间
startTime := time.GetCurrentMilliUnix()
// 处理请求
c.Next()
responseBody := bodyLogWriter.body.String()
var responseCode int
var responseMsg string
var responseData interface{}
if responseBody != "" {
res := response.Response{}
err := json.Unmarshal([]byte(responseBody), &res)
if err == nil {
responseCode = res.Code
responseMsg = res.Message
responseData = res.Data
}
}
// 结束时间
endTime := time.GetCurrentMilliUnix()
if c.Request.Method == "POST" {
_ = c.Request.ParseForm()
}
// 日志格式
accessLogMap := make(map[string]interface{})
accessLogMap["request_time"] = startTime
accessLogMap["request_method"] = c.Request.Method
accessLogMap["request_uri"] = c.Request.RequestURI
accessLogMap["request_proto"] = c.Request.Proto
accessLogMap["request_ua"] = c.Request.UserAgent()
accessLogMap["request_referer"] = c.Request.Referer()
accessLogMap["request_post_data"] = c.Request.PostForm.Encode()
accessLogMap["request_client_ip"] = c.ClientIP()
accessLogMap["response_time"] = endTime
accessLogMap["response_code"] = responseCode
accessLogMap["response_msg"] = responseMsg
accessLogMap["response_data"] = responseData
accessLogMap["cost_time"] = fmt.Sprintf("%vms", endTime-startTime)
accessLogJson, _ := jsonUtil.Encode(accessLogMap)
accessChannel <- accessLogJson
}
}
func handleAccessChannel() {
if f, err := os.OpenFile(config.AppAccessLogName, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0666); err != nil {
log.Println(err)
} else {
for accessLog := range accessChannel {
_, _ = f.WriteString(accessLog + "\n")
}
}
return
}
@@ -1,19 +0,0 @@
package requestid
import (
"github.com/gin-gonic/gin"
"github.com/xinliangnote/go-util/uuid"
)
func SetUp() gin.HandlerFunc {
return func(c *gin.Context) {
requestId := c.Request.Header.Get("X-Request-Id")
if requestId == "" {
requestId = uuid.GenUUID()
}
c.Set("X-Request-Id", requestId)
c.Writer.Header().Set("X-Request-Id", requestId)
c.Next()
}
}
-122
View File
@@ -1,122 +0,0 @@
package sign_aes
import (
"errors"
"fmt"
"github.com/gin-gonic/gin"
"github.com/xinliangnote/go-util/aes"
timeUtil "github.com/xinliangnote/go-util/time"
"go-gin-api/app/config"
"go-gin-api/app/util/response"
"net/url"
"sort"
"strconv"
"strings"
"time"
)
var AppSecret string
// AES 对称加密
func SetUp() gin.HandlerFunc {
return func(c *gin.Context) {
utilGin := response.Gin{Ctx: c}
sign, err := verifySign(c)
if sign != nil {
utilGin.Response(-1, "Debug Sign", sign)
c.Abort()
return
}
if err != nil {
utilGin.Response(-1, err.Error(), sign)
c.Abort()
return
}
c.Next()
}
}
// 验证签名
func verifySign(c *gin.Context) (map[string]string, error) {
_ = c.Request.ParseForm()
req := c.Request.Form
debug := strings.Join(c.Request.Form["debug"], "")
ak := strings.Join(c.Request.Form["ak"], "")
sn := strings.Join(c.Request.Form["sn"], "")
ts := strings.Join(c.Request.Form["ts"], "")
// 验证来源
value, ok := config.ApiAuthConfig[ak]
if ok {
AppSecret = value["aes"]
} else {
return nil, errors.New("ak Error")
}
if debug == "1" {
currentUnix := timeUtil.GetCurrentUnix()
req.Set("ts", strconv.FormatInt(currentUnix, 10))
sn, err := createSign(req)
if err != nil {
return nil, errors.New("sn Exception")
}
res := map[string]string{
"ts": strconv.FormatInt(currentUnix, 10),
"sn": sn,
}
return res, nil
}
// 验证过期时间
timestamp := time.Now().Unix()
exp, _ := strconv.ParseInt(config.AppSignExpiry, 10, 64)
tsInt, _ := strconv.ParseInt(ts, 10, 64)
if tsInt > timestamp || timestamp - tsInt >= exp {
return nil, errors.New("ts Error")
}
// 验证签名
if sn == "" {
return nil, errors.New("sn Error")
}
decryptStr, decryptErr := aes.Decrypt(sn, []byte(AppSecret), AppSecret)
if decryptErr != nil {
return nil, errors.New(decryptErr.Error())
}
if decryptStr != createEncryptStr(req) {
return nil, errors.New("sn Error")
}
return nil, nil
}
// 创建签名
func createSign(params url.Values) (string, error) {
return aes.Encrypt(createEncryptStr(params), []byte(AppSecret), AppSecret)
}
func createEncryptStr(params url.Values) string {
var key []string
var str = ""
for k := range params {
if k != "sn" && k != "debug" {
key = append(key, k)
}
}
sort.Strings(key)
for i := 0; i < len(key); i++ {
if i == 0 {
str = fmt.Sprintf("%v=%v", key[i], params.Get(key[i]))
} else {
str = str + fmt.Sprintf("&%v=%v", key[i], params.Get(key[i]))
}
}
return str
}
-110
View File
@@ -1,110 +0,0 @@
package sign_md5
import (
"errors"
"fmt"
"github.com/gin-gonic/gin"
"github.com/xinliangnote/go-util/md5"
timeUtil "github.com/xinliangnote/go-util/time"
"go-gin-api/app/config"
"go-gin-api/app/util/response"
"net/url"
"sort"
"strconv"
"strings"
"time"
)
var AppSecret string
// MD5 组合加密
func SetUp() gin.HandlerFunc {
return func(c *gin.Context) {
utilGin := response.Gin{Ctx: c}
sign, err := verifySign(c)
if sign != nil {
utilGin.Response(-1, "Debug Sign", sign)
c.Abort()
return
}
if err != nil {
utilGin.Response(-1, err.Error(), sign)
c.Abort()
return
}
c.Next()
}
}
// 验证签名
func verifySign(c *gin.Context) (map[string]string, error) {
_ = c.Request.ParseForm()
req := c.Request.Form
debug := strings.Join(c.Request.Form["debug"], "")
ak := strings.Join(c.Request.Form["ak"], "")
sn := strings.Join(c.Request.Form["sn"], "")
ts := strings.Join(c.Request.Form["ts"], "")
// 验证来源
value, ok := config.ApiAuthConfig[ak]
if ok {
AppSecret = value["md5"]
} else {
return nil, errors.New("ak Error")
}
if debug == "1" {
currentUnix := timeUtil.GetCurrentUnix()
req.Set("ts", strconv.FormatInt(currentUnix, 10))
res := map[string]string{
"ts": strconv.FormatInt(currentUnix, 10),
"sn": createSign(req),
}
return res, nil
}
// 验证过期时间
timestamp := time.Now().Unix()
exp, _ := strconv.ParseInt(config.AppSignExpiry, 10, 64)
tsInt, _ := strconv.ParseInt(ts, 10, 64)
if tsInt > timestamp || timestamp - tsInt >= exp {
return nil, errors.New("ts Error")
}
// 验证签名
if sn == "" || sn != createSign(req) {
return nil, errors.New("sn Error")
}
return nil, nil
}
// 创建签名
func createSign(params url.Values) string {
// 自定义 MD5 组合
return md5.MD5(AppSecret + createEncryptStr(params) + AppSecret)
}
func createEncryptStr(params url.Values) string {
var key []string
var str = ""
for k := range params {
if k != "sn" && k != "debug" {
key = append(key, k)
}
}
sort.Strings(key)
for i := 0; i < len(key); i++ {
if i == 0 {
str = fmt.Sprintf("%v=%v", key[i], params.Get(key[i]))
} else {
str = str + fmt.Sprintf("&%v=%v", key[i], params.Get(key[i]))
}
}
return str
}
-122
View File
@@ -1,122 +0,0 @@
package sign_rsa
import (
"errors"
"fmt"
"github.com/gin-gonic/gin"
"github.com/xinliangnote/go-util/rsa"
timeUtil "github.com/xinliangnote/go-util/time"
"go-gin-api/app/config"
"go-gin-api/app/util/response"
"net/url"
"sort"
"strconv"
"strings"
"time"
)
var AppSecret string
// RSA 非对称加密
func SetUp() gin.HandlerFunc {
return func(c *gin.Context) {
utilGin := response.Gin{Ctx: c}
sign, err := verifySign(c)
if sign != nil {
utilGin.Response(-1, "Debug Sign", sign)
c.Abort()
return
}
if err != nil {
utilGin.Response(-1, err.Error(), sign)
c.Abort()
return
}
c.Next()
}
}
// 验证签名
func verifySign(c *gin.Context) (map[string]string, error) {
_ = c.Request.ParseForm()
req := c.Request.Form
debug := strings.Join(c.Request.Form["debug"], "")
ak := strings.Join(c.Request.Form["ak"], "")
sn := strings.Join(c.Request.Form["sn"], "")
ts := strings.Join(c.Request.Form["ts"], "")
// 验证来源
value, ok := config.ApiAuthConfig[ak]
if ok {
AppSecret = value["rsa"]
} else {
return nil, errors.New("ak Error")
}
if debug == "1" {
currentUnix := timeUtil.GetCurrentUnix()
req.Set("ts", strconv.FormatInt(currentUnix, 10))
sn, err := createSign(req)
if err != nil {
return nil, errors.New("sn Exception")
}
res := map[string]string{
"ts": strconv.FormatInt(currentUnix, 10),
"sn": sn,
}
return res, nil
}
// 验证过期时间
timestamp := time.Now().Unix()
exp, _ := strconv.ParseInt(config.AppSignExpiry, 10, 64)
tsInt, _ := strconv.ParseInt(ts, 10, 64)
if tsInt > timestamp || timestamp - tsInt >= exp {
return nil, errors.New("ts Error")
}
// 验证签名
if sn == "" {
return nil, errors.New("sn Error")
}
decryptStr, decryptErr := rsa.PrivateDecrypt(sn, config.AppRsaPrivateFile)
if decryptErr != nil {
return nil, errors.New(decryptErr.Error())
}
if decryptStr != createEncryptStr(req) {
return nil, errors.New("sn Error")
}
return nil, nil
}
// 创建签名
func createSign(params url.Values) (string, error) {
return rsa.PublicEncrypt(createEncryptStr(params), AppSecret)
}
func createEncryptStr(params url.Values) string {
var key []string
var str = ""
for k := range params {
if k != "sn" && k != "debug" {
key = append(key, k)
}
}
sort.Strings(key)
for i := 0; i < len(key); i++ {
if i == 0 {
str = fmt.Sprintf("%v=%v", key[i], params.Get(key[i]))
} else {
str = str + fmt.Sprintf("&%v=%v", key[i], params.Get(key[i]))
}
}
return str
}
-62
View File
@@ -1,62 +0,0 @@
package route
import (
"github.com/gin-gonic/gin"
"go-gin-api/app/controller/jaeger_conn"
"go-gin-api/app/controller/product"
"go-gin-api/app/controller/test"
"go-gin-api/app/route/middleware/exception"
"go-gin-api/app/route/middleware/jaeger"
"go-gin-api/app/route/middleware/logger"
"go-gin-api/app/util/response"
)
func SetupRouter(engine *gin.Engine) {
//设置路由中间件
engine.Use(logger.SetUp(), exception.SetUp(), jaeger.SetUp())
//404
engine.NoRoute(func(c *gin.Context) {
utilGin := response.Gin{Ctx: c}
utilGin.Response(404,"请求方法不存在", nil)
})
engine.GET("/ping", func(c *gin.Context) {
utilGin := response.Gin{Ctx: c}
utilGin.Response(1,"pong", nil)
})
// 测试链路追踪
engine.GET("/jaeger_test", jaeger_conn.JaegerTest)
//@todo 记录请求超时的路由
ProductRouter := engine.Group("/product")
{
// 新增产品
ProductRouter.POST("", product.Add)
// 更新产品
ProductRouter.PUT("/:id", product.Edit)
// 删除产品
ProductRouter.DELETE("/:id", product.Delete)
// 获取产品详情
ProductRouter.GET("/:id", product.Detail)
}
// 测试加密性能
TestRouter := engine.Group("/test")
{
// 测试 MD5 组合 的性能
TestRouter.GET("/md5", test.Md5Test)
// 测试 AES 的性能
TestRouter.GET("/aes", test.AesTest)
// 测试 RSA 的性能
TestRouter.GET("/rsa", test.RsaTest)
}
}
-14
View File
@@ -1,14 +0,0 @@
package bind
import (
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
)
func Bind(s interface{}, c *gin.Context) (interface{}, error) {
b := binding.Default(c.Request.Method, c.ContentType())
if err := c.ShouldBindWith(s, b); err != nil {
return nil, err
}
return s, nil
}
-97
View File
@@ -1,97 +0,0 @@
package error
import (
"fmt"
"github.com/xinliangnote/go-util/json"
"github.com/xinliangnote/go-util/mail"
timeUtil "github.com/xinliangnote/go-util/time"
"go-gin-api/app/config"
"go-gin-api/app/route/middleware/exception"
"log"
"os"
"runtime/debug"
"strings"
"time"
)
type errorString struct {
s string
}
func (e *errorString) Error() string {
return e.s
}
func ErrorNew (text string) error {
alarm("INFO", text)
return &errorString{text}
}
// 发邮件
func ErrorMail (text string) error {
alarm("MAIL",text)
return &errorString{text}
}
// 发短信
func ErrorSms (text string) error {
alarm("SMS", text)
return &errorString{text}
}
// 发微信
func ErrorWeChat (text string) error {
alarm("WX", text)
return &errorString{text}
}
// 告警方法
func alarm(level string, str string) {
if level == "MAIL" {
DebugStack := ""
for _, v := range strings.Split(string(debug.Stack()), "\n") {
DebugStack += v + "<br>"
}
subject := fmt.Sprintf("【系统告警】%s 项目出错了!", config.AppName)
body := strings.ReplaceAll(exception.MailTemplate, "{ErrorMsg}", fmt.Sprintf("%s", str))
body = strings.ReplaceAll(body, "{RequestTime}", timeUtil.GetCurrentDate())
body = strings.ReplaceAll(body, "{RequestURL}", "--")
body = strings.ReplaceAll(body, "{RequestUA}", "--")
body = strings.ReplaceAll(body, "{RequestIP}", "--")
body = strings.ReplaceAll(body, "{DebugStack}", DebugStack)
// 执行发邮件
options := &mail.Options{
MailHost : config.SystemEmailHost,
MailPort : config.SystemEmailPort,
MailUser : config.SystemEmailUser,
MailPass : config.SystemEmailPass,
MailTo : config.ErrorNotifyUser,
Subject : subject,
Body : body,
}
_ = mail.Send(options)
} else if level == "SMS" {
// 执行发短信
} else if level == "WX" {
// 执行发微信
} else if level == "INFO" {
// 执行记日志
if f, err := os.OpenFile(config.AppErrorLogName, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0666); err != nil {
log.Println(err)
} else {
errorLogMap := make(map[string]interface{})
errorLogMap["time"] = time.Now().Format("2006/01/02 - 15:04:05")
errorLogMap["info"] = str
errorLogJson, _ := json.Encode(errorLogMap)
_, _ = f.WriteString(errorLogJson + "\n")
}
}
}
-75
View File
@@ -1,75 +0,0 @@
package grpc_client
import (
"context"
"fmt"
"github.com/gin-gonic/gin"
grpc_middeware "github.com/grpc-ecosystem/go-grpc-middleware"
"github.com/opentracing/opentracing-go"
"go-gin-api/app/config"
"go-gin-api/app/util/grpc_log"
"go-gin-api/app/util/jaeger_trace"
"google.golang.org/grpc"
"time"
)
func CreateServiceListenConn(c *gin.Context) *grpc.ClientConn {
return createGrpcConn("127.0.0.1:9901", c)
}
func CreateServiceSpeakConn(c *gin.Context) *grpc.ClientConn {
return createGrpcConn("127.0.0.1:9902", c)
}
func CreateServiceReadConn(c *gin.Context) *grpc.ClientConn {
return createGrpcConn("127.0.0.1:9903", c)
}
func CreateServiceWriteConn(c *gin.Context) *grpc.ClientConn {
return createGrpcConn("127.0.0.1:9904", c)
}
func createGrpcConn(serviceAddress string, c *gin.Context) *grpc.ClientConn {
var conn *grpc.ClientConn
var err error
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond * 500)
defer cancel()
if config.JaegerOpen == 1 {
tracer, _ := c.Get("Tracer")
parentSpanContext, _ := c.Get("ParentSpanContext")
conn, err = grpc.DialContext(
ctx,
serviceAddress,
grpc.WithInsecure(),
grpc.WithBlock(),
grpc.WithUnaryInterceptor(
grpc_middeware.ChainUnaryClient(
jaeger_trace.ClientInterceptor(tracer.(opentracing.Tracer), parentSpanContext.(opentracing.SpanContext)),
grpc_log.ClientInterceptor(),
),
),
)
} else {
conn, err = grpc.DialContext(
ctx,
serviceAddress,
grpc.WithInsecure(),
grpc.WithBlock(),
grpc.WithUnaryInterceptor(
grpc_middeware.ChainUnaryClient(
grpc_log.ClientInterceptor(),
),
),
)
}
if err != nil {
fmt.Println(serviceAddress, "grpc conn err:", err)
}
return conn
}
-61
View File
@@ -1,61 +0,0 @@
package grpc_log
import (
"context"
"fmt"
"github.com/xinliangnote/go-util/json"
"github.com/xinliangnote/go-util/time"
"go-gin-api/app/config"
"google.golang.org/grpc"
"log"
"os"
)
var grpcChannel = make(chan string, 100)
func ClientInterceptor() grpc.UnaryClientInterceptor {
go handleGrpcChannel()
return func(ctx context.Context, method string,
req, reply interface{}, cc *grpc.ClientConn,
invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
// 开始时间
startTime := time.GetCurrentMilliUnix()
err := invoker(ctx, method, req, reply, cc, opts...)
// 结束时间
endTime := time.GetCurrentMilliUnix()
// 日志格式
grpcLogMap := make(map[string]interface{})
grpcLogMap["request_time"] = startTime
grpcLogMap["request_data"] = req
grpcLogMap["request_method"] = method
grpcLogMap["response_data"] = reply
grpcLogMap["response_error"] = err
grpcLogMap["cost_time"] = fmt.Sprintf("%vms", endTime-startTime)
grpcLogJson, _ := json.Encode(grpcLogMap)
grpcChannel <- grpcLogJson
return err
}
}
func handleGrpcChannel() {
if f, err := os.OpenFile(config.AppGrpcLogName, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0666); err != nil {
log.Println(err)
} else {
for accessLog := range grpcChannel {
_, _ = f.WriteString(accessLog + "\n")
}
}
return
}
-97
View File
@@ -1,97 +0,0 @@
package jaeger_trace
import (
"context"
"fmt"
"github.com/opentracing/opentracing-go"
"github.com/opentracing/opentracing-go/ext"
"github.com/opentracing/opentracing-go/log"
"github.com/uber/jaeger-client-go"
jaegerConfig "github.com/uber/jaeger-client-go/config"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
"io"
"strings"
)
func NewJaegerTracer(serviceName string, jaegerHostPort string) (opentracing.Tracer, io.Closer) {
cfg := &jaegerConfig.Configuration {
Sampler: &jaegerConfig.SamplerConfig{
Type : "const", //固定采样
Param : 1, //1=全采样、0=不采样
},
Reporter: &jaegerConfig.ReporterConfig{
LogSpans : true,
LocalAgentHostPort : jaegerHostPort,
},
ServiceName: serviceName,
}
tracer, closer, err := cfg.NewTracer(jaegerConfig.Logger(jaeger.StdLogger))
if err != nil {
panic(fmt.Sprintf("ERROR: cannot init Jaeger: %v\n", err))
}
opentracing.SetGlobalTracer(tracer)
return tracer, closer
}
type MDReaderWriter struct {
metadata.MD
}
// ForeachKey implements ForeachKey of opentracing.TextMapReader
func (c MDReaderWriter) ForeachKey(handler func(key, val string) error) error {
for k, vs := range c.MD {
for _, v := range vs {
if err := handler(k, v); err != nil {
return err
}
}
}
return nil
}
// Set implements Set() of opentracing.TextMapWriter
func (c MDReaderWriter) Set(key, val string) {
key = strings.ToLower(key)
c.MD[key] = append(c.MD[key], val)
}
// ClientInterceptor grpc client
func ClientInterceptor(tracer opentracing.Tracer, spanContext opentracing.SpanContext) grpc.UnaryClientInterceptor {
return func(ctx context.Context, method string,
req, reply interface{}, cc *grpc.ClientConn,
invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
span := opentracing.StartSpan(
"call gRPC",
opentracing.ChildOf(spanContext),
opentracing.Tag{Key: string(ext.Component), Value: "gRPC"},
ext.SpanKindRPCClient,
)
defer span.Finish()
md, ok := metadata.FromOutgoingContext(ctx)
if !ok {
md = metadata.New(nil)
} else {
md = md.Copy()
}
err := tracer.Inject(span.Context(), opentracing.TextMap, MDReaderWriter{md})
if err != nil {
span.LogFields(log.String("inject-error", err.Error()))
}
newCtx := metadata.NewOutgoingContext(ctx, md)
err = invoker(newCtx, method, req, reply, cc, opts...)
if err != nil {
span.LogFields(log.String("call-error", err.Error()))
}
return err
}
}
-62
View File
@@ -1,62 +0,0 @@
package request
import (
"crypto/tls"
"github.com/gin-gonic/gin"
"github.com/opentracing/opentracing-go"
"github.com/opentracing/opentracing-go/ext"
"go-gin-api/app/config"
"io/ioutil"
"log"
"net/http"
"time"
)
func HttpGet(url string, c *gin.Context) (string, error) {
tr := &http.Transport{
TLSClientConfig : &tls.Config{InsecureSkipVerify: true},
}
client := &http.Client{
Timeout : time.Second * 5, //默认5秒超时时间
Transport : tr,
}
req, err := http.NewRequest("GET", url,nil)
if err != nil {
return "", err
}
if config.JaegerOpen == 1 {
tracer, _ := c.Get("Tracer")
parentSpanContext, _ := c.Get("ParentSpanContext")
span := opentracing.StartSpan(
"call Http Get",
opentracing.ChildOf(parentSpanContext.(opentracing.SpanContext)),
opentracing.Tag{Key: string(ext.Component), Value: "HTTP"},
ext.SpanKindRPCClient,
)
span.Finish()
injectErr := tracer.(opentracing.Tracer).Inject(span.Context(), opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(req.Header))
if injectErr != nil {
log.Fatalf("%s: Couldn't inject headers", err)
}
}
resp ,err := client.Do(req)
if err != nil {
return "", err
}
content, err := ioutil.ReadAll(resp.Body)
defer resp.Body.Close()
if err != nil {
return "", err
}
return string(content), err
}
-22
View File
@@ -1,22 +0,0 @@
package response
import "github.com/gin-gonic/gin"
type Gin struct {
Ctx *gin.Context
}
type Response struct {
Code int `json:"code"`
Message string `json:"msg"`
Data interface{} `json:"data"`
}
func (g *Gin)Response(code int, msg string, data interface{}) {
g.Ctx.JSON(200, Response{
Code : code,
Message : msg,
Data : data,
})
return
}
+6
View File
@@ -0,0 +1,6 @@
## 执行命令
1. 定义生成的表,设置 config 中 cmd.genTables,可以自定义设置多张表,为空表示生成库中所有的表,如果设置多个表可用','分割;
1. 在根目录下执行脚本文件:`./scripts/gormgen.sh`
## 参考
- https://github.com/MohamedBassem/gormgen
+37
View File
@@ -0,0 +1,37 @@
package main
import (
"flag"
"log"
"os"
"strings"
"github.com/xinliangnote/go-gin-api/cmd/gormgen/pkg"
)
var (
input string
structs []string
)
func init() {
flagStructs := flag.String("structs", "", "[Required] The name of schema structs to generate structs for, comma seperated\n")
flagInput := flag.String("input", "", "[Required] The name of the input file dir\n")
flag.Parse()
if *flagStructs == "" || *flagInput == "" {
flag.Usage()
os.Exit(1)
}
structs = strings.Split(*flagStructs, ",")
input = *flagInput
}
func main() {
gen := pkg.NewGenerator(input)
p := pkg.NewParser(input)
if err := gen.ParserAST(p, structs).Generate().Format().Flush(); err != nil {
log.Fatalln(err)
}
}
+124
View File
@@ -0,0 +1,124 @@
package pkg
import (
"bytes"
"errors"
"go/format"
"io/ioutil"
"log"
"strings"
"github.com/jinzhu/gorm"
)
// fieldConfig
type fieldConfig struct {
FieldName string
ColumnName string
FieldType string
HumpName string
}
// structConfig
type structConfig struct {
config
StructName string
OnlyFields []fieldConfig
OptionFields []fieldConfig
}
type ImportPkg struct {
Pkg string
}
type structHelpers struct {
Titelize func(string) string
}
type config struct {
PkgName string
Helpers structHelpers
QueryBuilderName string
}
// The Generator is the one responsible for generating the code, adding the imports, formating, and writing it to the file.
type Generator struct {
buf map[string]*bytes.Buffer
inputFile string
config config
structConfigs []structConfig
}
// NewGenerator function creates an instance of the generator given the name of the output file as an argument.
func NewGenerator(outputFile string) *Generator {
return &Generator{
buf: map[string]*bytes.Buffer{},
inputFile: outputFile,
}
}
// ParserAST parse by go file
func (g *Generator) ParserAST(p *Parser, structs []string) (ret *Generator) {
for _, v := range structs {
g.buf[gorm.ToDBName(v)] = new(bytes.Buffer)
}
g.structConfigs = p.Parse()
g.config.PkgName = p.pkg.Name
g.config.Helpers = structHelpers{
Titelize: strings.Title,
}
g.config.QueryBuilderName = SQLColumnToHumpStyle(p.pkg.Name) + "QueryBuilder"
return g
}
func (g *Generator) checkConfig() (err error) {
if len(g.config.PkgName) == 0 {
err = errors.New("package name dose'n set")
return
}
for i := 0; i < len(g.structConfigs); i++ {
g.structConfigs[i].config = g.config
}
return
}
// Generate executes the template and store it in an internal buffer.
func (g *Generator) Generate() *Generator {
if err := g.checkConfig(); err != nil {
panic(err)
}
for _, v := range g.structConfigs {
if _, ok := g.buf[gorm.ToDBName(v.StructName)]; !ok {
continue
}
if err := outputTemplate.Execute(g.buf[gorm.ToDBName(v.StructName)], v); err != nil {
panic(err)
}
}
return g
}
// Format function formats the output of the generation.
func (g *Generator) Format() *Generator {
for k := range g.buf {
formattedOutput, err := format.Source(g.buf[k].Bytes())
if err != nil {
panic(err)
}
g.buf[k] = bytes.NewBuffer(formattedOutput)
}
return g
}
// Flush function writes the output to the output file.
func (g *Generator) Flush() error {
for k := range g.buf {
filename := g.inputFile + "/gen_" + strings.ToLower(k) + ".go"
if err := ioutil.WriteFile(filename, g.buf[k].Bytes(), 0777); err != nil {
log.Fatalln(err)
}
}
return nil
}
+120
View File
@@ -0,0 +1,120 @@
package pkg
import (
"go/ast"
"go/build"
"go/parser"
"go/token"
"log"
"strings"
"github.com/jinzhu/gorm"
)
// The Parser is used to parse a directory and expose information about the structs defined in the files of this directory.
type Parser struct {
dir string
pkg *build.Package
parsedFiles []*ast.File
}
// NewParser create a new parser instance.
func NewParser(dir string) *Parser {
return &Parser{
dir: dir,
}
}
// getPackage parse dir get go file and package
func (p *Parser) getPackage() {
pkg, err := build.Default.ImportDir(p.dir, build.ImportComment)
if err != nil {
log.Fatalf("cannot process directory %s: %s", p.dir, err)
}
p.pkg = pkg
}
// parseGoFiles parse go file
func (p *Parser) parseGoFiles() {
var parsedFiles []*ast.File
fs := token.NewFileSet()
for _, file := range p.pkg.GoFiles {
file = p.dir + "/" + file
parsedFile, err := parser.ParseFile(fs, file, nil, 0)
if err != nil {
log.Fatalf("parsing package: %s: %s\n", file, err)
}
parsedFiles = append(parsedFiles, parsedFile)
}
p.parsedFiles = parsedFiles
}
// parseTypes parse type of struct
func (p *Parser) parseTypes(file *ast.File) (ret []structConfig) {
ast.Inspect(file, func(n ast.Node) bool {
decl, ok := n.(*ast.GenDecl)
if !ok || decl.Tok != token.TYPE {
return true
}
for _, spec := range decl.Specs {
var (
data structConfig
)
typeSpec, _ok := spec.(*ast.TypeSpec)
if !_ok {
continue
}
// We only care about struct declaration (for now)
var structType *ast.StructType
if structType, ok = typeSpec.Type.(*ast.StructType); !ok {
continue
}
data.StructName = typeSpec.Name.Name
for _, v := range structType.Fields.List {
var (
optionField fieldConfig
)
// type is ident, get onlyField type
if t, _ok := v.Type.(*ast.Ident); _ok {
optionField.FieldType = t.String()
} else {
if v.Tag != nil {
if strings.Contains(v.Tag.Value, "gorm") && strings.Contains(v.Tag.Value, "time") {
optionField.FieldType = "time.Time"
}
}
}
// get file name
if len(v.Names) > 0 {
optionField.FieldName = v.Names[0].String()
optionField.ColumnName = gorm.ToDBName(optionField.FieldName)
optionField.HumpName = SQLColumnToHumpStyle(optionField.ColumnName)
}
data.OptionFields = append(data.OptionFields, optionField)
}
ret = append(ret, data)
}
return true
})
return
}
// Parse should be called before any type querying for the parser. It takes the directory to be parsed and extracts all the structs defined in this directory.
func (p *Parser) Parse() (ret []structConfig) {
var (
data []structConfig
)
p.getPackage()
p.parseGoFiles()
for _, f := range p.parsedFiles {
data = append(data, p.parseTypes(f)...)
}
return data
}
+149
View File
@@ -0,0 +1,149 @@
package pkg
import "text/template"
// Make sure that the template compiles during package initialization
func parseTemplateOrPanic(t string) *template.Template {
tpl, err := template.New("output_template").Parse(t)
if err != nil {
panic(err)
}
return tpl
}
var outputTemplate = parseTemplateOrPanic(`
///////////////////////////////////////////////////////////
// THIS FILE IS AUTO GENERATED by gormgen, DON'T EDIT IT //
// ANY CHANGES DONE HERE WILL BE LOST //
///////////////////////////////////////////////////////////
package {{.PkgName}}
import (
"fmt"
"time"
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo"
"github.com/pkg/errors"
"gorm.io/gorm"
)
func NewModel() *{{.StructName}} {
return new({{.StructName}})
}
func NewQueryBuilder() *{{.QueryBuilderName}} {
return new({{.QueryBuilderName}})
}
func (t *{{.StructName}}) Create(db *gorm.DB) (id int32, err error) {
if err = db.Create(t).Error; err != nil {
return 0, errors.Wrap(err, "create err")
}
return t.Id, nil
}
func (t *{{.StructName}}) Delete(db *gorm.DB) (err error) {
if err = db.Delete(t).Error; err != nil {
return errors.Wrap(err, "delete err")
}
return nil
}
func (t *{{.StructName}}) Updates(db *gorm.DB, m map[string]interface{}) (err error) {
if err = db.Model(&UserDemo{}).Where("id = ?", t.Id).Updates(m).Error; err != nil {
return errors.Wrap(err, "updates err")
}
return nil
}
type {{.QueryBuilderName}} struct {
order []string
where []struct {
prefix string
value interface{}
}
limit int
offset int
}
func (qb *{{.QueryBuilderName}}) buildQuery(db *gorm.DB) *gorm.DB {
ret := db
for _, where := range qb.where {
ret = ret.Where(where.prefix, where.value)
}
for _, order := range qb.order {
ret = ret.Order(order)
}
ret = ret.Limit(qb.limit).Offset(qb.offset)
return ret
}
func (qb *{{.QueryBuilderName}}) Count(db *gorm.DB) (int64, error) {
var c int64
res := qb.buildQuery(db).Model(&{{.StructName}}{}).Count(&c)
if res.Error != nil && res.Error == gorm.ErrRecordNotFound {
c = 0
}
return c, res.Error
}
func (qb *{{.QueryBuilderName}}) First(db *gorm.DB) (*{{.StructName}}, error) {
ret := &{{.StructName}}{}
res := qb.buildQuery(db).First(ret)
if res.Error != nil && res.Error == gorm.ErrRecordNotFound {
ret = nil
}
return ret, res.Error
}
func (qb *{{.QueryBuilderName}}) QueryOne(db *gorm.DB) (*{{.StructName}}, error) {
qb.limit = 1
ret, err := qb.QueryAll(db)
if len(ret) > 0 {
return ret[0], err
}
return nil, err
}
func (qb *{{.QueryBuilderName}}) QueryAll(db *gorm.DB) ([]*{{.StructName}}, error) {
var ret []*{{.StructName}}
err := qb.buildQuery(db).Find(&ret).Error
return ret, err
}
func (qb *{{.QueryBuilderName}}) Limit(limit int) *{{.QueryBuilderName}} {
qb.limit = limit
return qb
}
func (qb *{{.QueryBuilderName}}) Offset(offset int) *{{.QueryBuilderName}} {
qb.offset = offset
return qb
}
{{$queryBuilderName := .QueryBuilderName}}
{{range .OptionFields}}
func (qb *{{$queryBuilderName}}) Where{{call $.Helpers.Titelize .FieldName}}(p db_repo.Predicate, value {{.FieldType}}) *{{$queryBuilderName}} {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "{{.ColumnName}}", p),
value,
})
return qb
}
func (qb *{{$queryBuilderName}}) OrderBy{{call $.Helpers.Titelize .FieldName}}(asc bool) *{{$queryBuilderName}} {
order := "DESC"
if asc {
order = "ASC"
}
qb.order = append(qb.order, "{{.ColumnName}} " + order)
return qb
}
{{end}}
`)
+18
View File
@@ -0,0 +1,18 @@
package pkg
import "strings"
// SQLColumnToHumpStyle sql转换成驼峰模式
func SQLColumnToHumpStyle(in string) (ret string) {
for i := 0; i < len(in); i++ {
if i > 0 && in[i-1] == '_' && in[i] != '_' {
s := strings.ToUpper(string(in[i]))
ret += s
} else if in[i] == '_' {
continue
} else {
ret += string(in[i])
}
}
return
}
+6
View File
@@ -0,0 +1,6 @@
## 执行命令
```$xslt
// test_handler 为 ./internal/api/controller/ 中的包名
./scripts/handlergen.sh test_handler
```
+84
View File
@@ -0,0 +1,84 @@
package main
import (
"flag"
"fmt"
"go/ast"
"go/parser"
"go/token"
"log"
"os"
"strings"
"unicode"
)
var handlerName string
func init() {
handler := flag.String("handler", "", "请输入需要生成的 handler 名称\n")
flag.Parse()
handlerName = strings.ToLower(*handler)
}
func main() {
fs := token.NewFileSet()
file := fmt.Sprintf("./internal/api/controller/%s/handler.go", handlerName)
parsedFile, err := parser.ParseFile(fs, file, nil, 0)
if err != nil {
log.Fatalf("parsing package: %s: %s\n", file, err)
}
ast.Inspect(parsedFile, func(n ast.Node) bool {
decl, ok := n.(*ast.GenDecl)
if !ok || decl.Tok != token.TYPE {
return true
}
for _, spec := range decl.Specs {
typeSpec, _ok := spec.(*ast.TypeSpec)
if !_ok {
continue
}
var interfaceType *ast.InterfaceType
if interfaceType, ok = typeSpec.Type.(*ast.InterfaceType); !ok {
continue
}
for _, v := range interfaceType.Methods.List {
if len(v.Names) > 0 {
if v.Names[0].String() == "i" {
continue
}
filepath := "./internal/api/controller/" + handlerName
filename := fmt.Sprintf("%s/func_%s.go", filepath, strings.ToLower(v.Names[0].String()))
funcFile, err := os.OpenFile(filename, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0766)
if err != nil {
fmt.Printf("create and open func file error %v\n", err.Error())
}
funcContent := fmt.Sprintf("package %s\n\n", handlerName)
funcContent += "import (\n"
funcContent += `"github.com/xinliangnote/go-gin-api/internal/pkg/core"`
funcContent += "\n)\n\n"
funcContent += fmt.Sprintf("\n\ntype %sRequest struct {}\n\n", Lcfirst(v.Names[0].String()))
funcContent += fmt.Sprintf("type %sResponse struct {}\n\n", Lcfirst(v.Names[0].String()))
funcContent += fmt.Sprintf("func (h *handler) %s() core.HandlerFunc { \n return func(c core.Context) {\n\n}}", v.Names[0].String())
funcFile.WriteString(funcContent)
funcFile.Close()
}
}
}
return true
})
}
func Lcfirst(str string) string {
for i, v := range str {
return string(unicode.ToLower(v)) + str[i+1:]
}
return ""
}
+289
View File
@@ -0,0 +1,289 @@
package main
import (
"database/sql"
"fmt"
"os"
"regexp"
"strings"
"github.com/xinliangnote/go-gin-api/configs"
"github.com/xinliangnote/go-gin-api/internal/pkg/db"
"github.com/xinliangnote/go-gin-api/pkg/env"
"github.com/xinliangnote/go-gin-api/pkg/logger"
"go.uber.org/zap"
"gorm.io/gorm"
)
type tableInfo struct {
Name string `db:"table_name"` // name
Comment sql.NullString `db:"table_comment"` // comment
}
type tableColumn struct {
OrdinalPosition uint16 `db:"ORDINAL_POSITION"` // position
ColumnName string `db:"COLUMN_NAME"` // name
ColumnType string `db:"COLUMN_TYPE"` // column_type
DataType string `db:"DATA_TYPE"` // data_type
ColumnKey sql.NullString `db:"COLUMN_KEY"` // key
IsNullable string `db:"IS_NULLABLE"` // nullable
Extra sql.NullString `db:"EXTRA"` // extra
ColumnComment sql.NullString `db:"COLUMN_COMMENT"` // comment
ColumnDefault sql.NullString `db:"COLUMN_DEFAULT"` // default value
}
func main() {
// 初始化 logger
loggers, err := logger.NewJSONLogger(
logger.WithField("domain", fmt.Sprintf("%s[%s]", configs.ProjectName(), env.Active().Value())),
logger.WithTimeLayout("2006-01-02 15:04:05"),
logger.WithFileP(configs.ProjectLogFile()),
)
if err != nil {
panic(err)
}
defer loggers.Sync()
// 初始化 DB
dbRepo, err := db.New()
if err != nil {
loggers.Fatal("new db err", zap.Error(err))
}
defer func() {
if err := dbRepo.DbWClose(); err != nil {
loggers.Error("dbw close err", zap.Error(err))
}
if err := dbRepo.DbRClose(); err != nil {
loggers.Error("dbr close err", zap.Error(err))
}
}()
dbName := configs.Get().MySQL.Read.Name
genTables := configs.Get().Cmd.GenTables
tables, err := queryTables(dbRepo.GetDbR(), dbName, genTables)
if err != nil {
loggers.Error("query tables of database err", zap.Error(err))
return
}
for _, table := range tables {
filepath := "./internal/api/repository/db_repo/" + table.Name + "_repo"
_ = os.Mkdir(filepath, 0766)
mdName := fmt.Sprintf("%s/gen_table.md", filepath)
mdFile, err := os.OpenFile(mdName, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0766)
if err != nil {
fmt.Printf("create and open markdown file error %v\n", err.Error())
return
}
modelName := fmt.Sprintf("%s/gen_model.go", filepath)
modelFile, err := os.OpenFile(modelName, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0766)
if err != nil {
fmt.Printf("create and open model file error %v\n", err.Error())
return
}
modelContent := fmt.Sprintf("package %s%s\n", table.Name, "_repo")
modelContent += fmt.Sprintf(`import "time"`)
modelContent += fmt.Sprintf("\n\n// %s \n", table.Comment.String)
modelContent += fmt.Sprintf("//go:generate gormgen -structs %s -input . \n", capitalize(table.Name))
modelContent += fmt.Sprintf("type %s struct {\n", capitalize(table.Name))
tableContent := fmt.Sprintf("#### %s.%s \n", dbName, table.Name)
if table.Comment.String != "" {
tableContent += table.Comment.String + "\n"
}
tableContent += "\n" +
"| 序号 | 名称 | 描述 | 类型 | 键 | 为空 | 额外 | 默认值 |\n" +
"| :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: |\n"
columnInfo, columnInfoErr := queryTableColumn(dbRepo.GetDbR(), dbName, table.Name)
if columnInfoErr != nil {
continue
}
for _, info := range columnInfo {
tableContent += fmt.Sprintf(
"| %d | %s | %s | %s | %s | %s | %s | %s |\n",
info.OrdinalPosition,
info.ColumnName,
strings.ReplaceAll(strings.ReplaceAll(info.ColumnComment.String, "|", "\\|"), "\n", ""),
info.ColumnType,
info.ColumnKey.String,
info.IsNullable,
info.Extra.String,
info.ColumnDefault.String,
)
if textType(info.DataType) == "time.Time" {
modelContent += fmt.Sprintf("%s %s `%s` // %s\n", capitalize(info.ColumnName), textType(info.DataType), "gorm:\"time\"", info.ColumnComment.String)
} else {
modelContent += fmt.Sprintf("%s %s // %s\n", capitalize(info.ColumnName), textType(info.DataType), info.ColumnComment.String)
}
}
mdFile.WriteString(tableContent)
mdFile.Close()
modelContent += "}\n"
modelFile.WriteString(modelContent)
modelFile.Close()
}
}
func queryTables(db *gorm.DB, dbName string, tableName string) ([]tableInfo, error) {
var tableCollect []tableInfo
var tableArray []string
var commentArray []sql.NullString
sqlTables := fmt.Sprintf("SELECT `table_name`,`table_comment` FROM `information_schema`.`tables` WHERE `table_schema`= '%s'", dbName)
rows, err := db.Raw(sqlTables).Rows()
if err != nil {
return tableCollect, err
}
defer rows.Close()
for rows.Next() {
var info tableInfo
err = rows.Scan(&info.Name, &info.Comment)
if err != nil {
fmt.Printf("execute query tables action error,had ignored, detail is [%v]\n", err.Error())
continue
}
tableCollect = append(tableCollect, info)
tableArray = append(tableArray, info.Name)
commentArray = append(commentArray, info.Comment)
}
// filter tables when specified tables params
if tableName != "" {
tableCollect = nil
chooseTables := strings.Split(tableName, ",")
indexMap := make(map[int]int)
for _, item := range chooseTables {
subIndexMap := getTargetIndexMap(tableArray, item)
for k, v := range subIndexMap {
if _, ok := indexMap[k]; ok {
continue
}
indexMap[k] = v
}
}
if len(indexMap) != 0 {
for _, v := range indexMap {
var info tableInfo
info.Name = tableArray[v]
info.Comment = commentArray[v]
tableCollect = append(tableCollect, info)
}
}
}
return tableCollect, err
}
func queryTableColumn(db *gorm.DB, dbName string, tableName string) ([]tableColumn, error) {
// 定义承载列信息的切片
var columns []tableColumn
sqlTableColumn := fmt.Sprintf("SELECT `ORDINAL_POSITION`,`COLUMN_NAME`,`COLUMN_TYPE`,`DATA_TYPE`,`COLUMN_KEY`,`IS_NULLABLE`,`EXTRA`,`COLUMN_COMMENT`,`COLUMN_DEFAULT` FROM `information_schema`.`columns` WHERE `table_schema`= '%s' AND `table_name`= '%s' ORDER BY `ORDINAL_POSITION` ASC",
dbName, tableName)
rows, err := db.Raw(sqlTableColumn).Rows()
if err != nil {
fmt.Printf("execute query table column action error, detail is [%v]\n", err.Error())
return columns, err
}
defer rows.Close()
for rows.Next() {
var column tableColumn
err = rows.Scan(
&column.OrdinalPosition,
&column.ColumnName,
&column.ColumnType,
&column.DataType,
&column.ColumnKey,
&column.IsNullable,
&column.Extra,
&column.ColumnComment,
&column.ColumnDefault)
if err != nil {
fmt.Printf("query table column scan error, detail is [%v]\n", err.Error())
return columns, err
}
columns = append(columns, column)
}
return columns, err
}
func getTargetIndexMap(tableNameArr []string, item string) map[int]int {
indexMap := make(map[int]int)
for i := 0; i < len(tableNameArr); i++ {
if match, _ := regexp.MatchString(item, tableNameArr[i]); match {
if _, ok := indexMap[i]; ok {
continue
}
indexMap[i] = i
}
}
return indexMap
}
func capitalize(s string) string {
var upperStr string
chars := strings.Split(s, "_")
for _, val := range chars {
vv := []rune(val)
for i := 0; i < len(vv); i++ {
if i == 0 {
if vv[i] >= 97 && vv[i] <= 122 {
vv[i] -= 32
upperStr += string(vv[i])
}
} else {
upperStr += string(vv[i])
}
}
}
return upperStr
}
func textType(s string) string {
var mysqlTypeToGoType = map[string]string{
"tinyint": "int32",
"smallint": "int32",
"mediumint": "int32",
"int": "int32",
"integer": "int64",
"bigint": "int64",
"float": "float64",
"double": "float64",
"decimal": "float64",
"date": "string",
"time": "string",
"year": "string",
"datetime": "time.Time",
"timestamp": "time.Time",
"char": "string",
"varchar": "string",
"tinyblob": "string",
"tinytext": "string",
"blob": "string",
"text": "string",
"mediumblob": "string",
"mediumtext": "string",
"longblob": "string",
"longtext": "string",
}
return mysqlTypeToGoType[s]
}
+100
View File
@@ -0,0 +1,100 @@
package configs
import (
"fmt"
"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"`
ExpireDuration time.Duration `toml:"expireDuration"`
} `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"`
Cmd struct {
GenTables string `toml:"genTables"`
} `toml:"cmd"`
}
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"
}
func ProjectPort() string {
return ":9999"
}
func ProjectLogFile() string {
return fmt.Sprintf("./logs/%s-access.log", ProjectName())
}
+3
View File
@@ -0,0 +1,3 @@
[db]
host = '127.0.0.1'
port = 3306
+81
View File
@@ -0,0 +1,81 @@
[mysql]
[mysql.read] # 从库信息,可读
addr = '127.0.0.1:3306' # MySQL 地址:端口
user = 'root' # 用户名
pass = 'root' # 密码
name = 'go_gin_api' # 数据库名称
[mysql.write] # 主库信息,可读写
addr = '127.0.0.1:3306' # MySQL 地址:端口
user = 'root' # 用户名
pass = 'root' # 密码
name = 'go_gin_api' # 数据库名称
[mysql.base] # 基础配置
maxOpenConn = 10 # 最大打开的连接数
maxIdleConn = 60 # 闲置的连接数
connMaxLifeTime = 60 # 最大连接超时(单位:分)
[redis]
addr = '127.0.0.1:6379' # Redis 地址:端口
pass = '' # 密码
db = 0 # 序号从 0 开始,默认是0,可以不用设置
maxRetries = 3 # 命令执行失败时,最多重试多少次,默认为 0 即不重试
poolSize = 10 # 连接池最大连接数,默认为 CPU 数 * 10
minIdleConns = 5 # 最小空闲连接数
[mail]
host = 'smtp.163.com' # 邮箱服务器,比如 smtp.163.com
port = 465 # 端口
user = "" # 发件人邮箱
pass = "" # 发件人邮箱密码或授权码,根据邮箱服务器而定
to = "" # 收件人邮箱,多个可以逗号(,)分割
[jwt]
secret = 'i1ydX9RtHyuJTrw7frcu' # JWT secret
expireDuration = 24 # JWT ExpiresAt 过期时间(单位:小时)
[aes]
key = 'IgkibX71IEf382PT'
iv = 'IgkibX71IEf382PT'
[rsa]
private = '-----BEGIN RSA PRIVATE KEY-----
MIIEpgIBAAKCAQEA1O3p0JN0/RrP7eY3f81izPf16FS0WMNGCJkd+y5c6yBzUvN0
IEeoxiIWIBhoMKH0pzlzBg0rfttojSodOgNom/UCAzAYEgdIsNee5LSN/7e0T2/Q
vsIAHINuA8gI8fGoGiSA2TEzpUo6aVXwhZT34GGRdrSJ+m4iVk/Kt95tavBNk+ND
VSeb5xAjxBchT5BjAMMlE0ffGZb0MMjjO5+e9Tn8f99M2VMqpzXHXZzv1ABmqufz
S20iWcSvnjhWcJ9hiKwO8Z30GgJyACmml+HMxLYEFN9h2MWYgxLm9Z0rLMrWwMM+
E2rCs8tsxAD5sO9RZMJPl1C0FIsMR53ngqbzowIDAQABAoIBAQCO1RE1ItUlO6kj
Un0ENAgEqojAUqGvsT33Yo7kAZO+/cOeb0UEqk0iq5bf7L9ncBynWDg6ZPc6X3/g
wdFdKxAvHck9zjM3VL+EMP+bNyrR0K8ZYk5Kx+Q/PEK+Mp8dfRdgggAUsZaNWB+a
rVVspiMo1wo28KBl5x8NevTnJkOLqXAyB7UyLWqnOL1fb988lZvZPR7ZUYroVIZa
pyXtZcafIJeKyQ3bvWI5+eFqOe61Z4Bx1+TpfZ3fKfSDW0vhxzNqaimOa8jSXtMJ
jMeOctL4nZ0TPo/jS3I+XlaH4ZQlFLuUWGscpxwfEeBN23I8HRLkZXJsw66yvRN3
s4bUKPXRAoGBAP/3oSZAECvfsYYzs76tnrAmR/0GxCqgguxDlWn5DowQzdWFOdHC
ZbTo/hUVoMSQnO1EKCFlnBS+wg/3TuIzUO0ewC1aeT7qHbOMDl0zKbNpS2Z9/j+U
zro+qz7XmkWolMCfmDrCrw9CtCxcMSII+ajbI8SAgFVMz9XnDt+xW9E9AoGBANT0
4F6kCUJTEyqf2+v84tjQ2wGIF6XtZPU9JR806zeMyahQ9F6z3hY8BYb0tIy5b3uJ
VlJ9TG1qg/t59TWxIq43mYSUJHe0aJi3ilooObQtHlhPu8nwmmX47sX0PyG2hMoD
kBVxTpTDmBaDz7O9uBnlMXJN5qEygctaixpEbmZfAoGBAMBA9kEMjRjnAyeRXcgy
D6aumhNqKZz6wltCx864yjxZwsBFOJBcOpgPCAg+HmqFU9jCAIJVF05dmNT1I8Ky
WG5BUoa+FaMzpOtenstRylh/Far9pyGKW1t4BpdEyRLY9CFZvbUk1OfZagqHlD/E
DgDN16eX/MwUzWYUDg/l3tjhAoGBAKGip/ZNjVWRFpggs9z/mfK1O7WC5Wgksp9N
ZLK2CN6l9p3RrFmBLk00C4HulGfHi+15RVLhFbRqx3iFje/N3iPbwaMWikNtZIKd
tN5Pb9To9gJTqpZRD+/cLOeFRrHBBjMK1z7fPKS/fN2B+JFVq7nD827t3+J0In4F
4FT0odMDAoGBAJk3ELB/FHY8xzZ4jF1wG/a1CK681Xm6SuU5KIELDSAUNoou6OPG
mS8gU20MMPAeV2z7khyDcSxlHsUyL73eLeaakbQov9NMW7cc99XX4wnP4W7FRpmr
QbHmKuHIRFHCFv+XX8c0aK2mDZMUlzJdy4FgD/YCEZ7kZMZKyvZW/ZuV
-----END RSA PRIVATE KEY-----'
public = '-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1O3p0JN0/RrP7eY3f81i
zPf16FS0WMNGCJkd+y5c6yBzUvN0IEeoxiIWIBhoMKH0pzlzBg0rfttojSodOgNo
m/UCAzAYEgdIsNee5LSN/7e0T2/QvsIAHINuA8gI8fGoGiSA2TEzpUo6aVXwhZT3
4GGRdrSJ+m4iVk/Kt95tavBNk+NDVSeb5xAjxBchT5BjAMMlE0ffGZb0MMjjO5+e
9Tn8f99M2VMqpzXHXZzv1ABmqufzS20iWcSvnjhWcJ9hiKwO8Z30GgJyACmml+HM
xLYEFN9h2MWYgxLm9Z0rLMrWwMM+E2rCs8tsxAD5sO9RZMJPl1C0FIsMR53ngqbz
owIDAQAB
-----END PUBLIC KEY-----'
[cmd]
genTables = 'user_demo'
+4
View File
@@ -0,0 +1,4 @@
[db]
host = '127.0.0.1'
port = 3306
+4
View File
@@ -0,0 +1,4 @@
[db]
host = '127.0.0.1'
port = 3306
+44
View File
@@ -0,0 +1,44 @@
auth_enabled: false
server:
http_listen_port: 3100
ingester:
lifecycler:
address: 127.0.0.1
ring:
kvstore:
store: inmemory
replication_factor: 1
final_sleep: 0s
chunk_idle_period: 5m
chunk_retain_period: 30s
schema_config:
configs:
- from: 2020-01-01
store: boltdb
object_store: filesystem
schema: v9
index:
prefix: index_
period: 168h # 每张表的时间范围6天
storage_config:
boltdb:
directory: /data/loki/index # 索引文件存储地址
filesystem:
directory: /data/loki/chunks # 块存储地址
limits_config:
enforce_metric_name: false
reject_old_samples: true
reject_old_samples_max_age: 168h
chunk_store_config:
max_look_back_period: 0s
table_manager:
retention_deletes_enabled: false
retention_period: 0s
+20
View File
@@ -0,0 +1,20 @@
server:
http_listen_port: 9080
grpc_listen_port: 0
# Positions
positions:
filename: /data/loki/positions.yaml
# Loki服务器的地址
clients:
- url: http://127.0.0.1:3100/loki/api/v1/push
scrape_configs:
- job_name: go-gin-api
static_configs:
- targets:
- localhost
labels:
job: accesslog
__path__: /data/logs/*.log # 日志目录
+12
View File
@@ -0,0 +1,12 @@
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
- job_name: "prometheus"
static_configs:
- targets: ["127.0.0.1:9090"]
- job_name: "go_app_server"
static_configs:
- targets:
- "127.0.0.1:9999"
+479
View File
@@ -0,0 +1,479 @@
// GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
// This file was generated by swaggo/swag
package docs
import (
"bytes"
"encoding/json"
"strings"
"github.com/alecthomas/template"
"github.com/swaggo/swag"
)
var doc = `{
"schemes": {{ marshal .Schemes }},
"swagger": "2.0",
"info": {
"description": "{{.Description}}",
"title": "{{.Title}}",
"contact": {},
"license": {
"name": "MIT",
"url": "https://github.com/xinliangnote/go-gin-api/blob/master/LICENSE"
},
"version": "{{.Version}}"
},
"host": "{{.Host}}",
"basePath": "{{.BasePath}}",
"paths": {
"/auth/get": {
"post": {
"description": "获取授权信息",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Demo"
],
"summary": "获取授权信息",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/demo_handler.authResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/code.Failure"
}
}
}
}
},
"/demo/trace": {
"get": {
"description": "Trace 示例",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Demo"
],
"summary": "Trace 示例",
"parameters": [
{
"type": "string",
"description": "签名",
"name": "Authorization",
"in": "header",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"type": "object",
"properties": {
"job": {
"description": "工作",
"type": "string"
},
"name": {
"description": "用户名",
"type": "string"
}
}
}
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/code.Failure"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/code.Failure"
}
}
}
}
},
"/user/create": {
"post": {
"description": "创建用户",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"User"
],
"summary": "创建用户",
"parameters": [
{
"description": "请求信息",
"name": "Request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/user_handler.createRequest"
}
},
{
"type": "string",
"description": "签名",
"name": "Authorization",
"in": "header",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/user_handler.createResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/code.Failure"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/code.Failure"
}
}
}
}
},
"/user/delete/{id}": {
"patch": {
"description": "删除用户",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"User"
],
"summary": "删除用户",
"parameters": [
{
"type": "integer",
"description": "用户ID",
"name": "id",
"in": "path",
"required": true
},
{
"type": "string",
"description": "签名",
"name": "Authorization",
"in": "header",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/user_handler.deleteResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/code.Failure"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/code.Failure"
}
}
}
}
},
"/user/info/{username}": {
"get": {
"description": "用户详情",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"User"
],
"summary": "用户详情",
"parameters": [
{
"type": "string",
"description": "用户名",
"name": "username",
"in": "path",
"required": true
},
{
"type": "string",
"description": "签名",
"name": "Authorization",
"in": "header",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/user_handler.detailResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/code.Failure"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/code.Failure"
}
}
}
}
},
"/user/update": {
"put": {
"description": "编辑用户 - 通过用户主键ID更新用户昵称",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"User"
],
"summary": "编辑用户 - 通过用户主键ID更新用户昵称",
"parameters": [
{
"description": "请求信息",
"name": "Request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/user_handler.updateNickNameByIDRequest"
}
},
{
"type": "string",
"description": "签名",
"name": "Authorization",
"in": "header",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/user_handler.updateNickNameByIDResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/code.Failure"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/code.Failure"
}
}
}
}
}
},
"definitions": {
"code.Failure": {
"type": "object",
"properties": {
"code": {
"description": "业务码",
"type": "integer"
},
"message": {
"description": "描述信息",
"type": "string"
}
}
},
"demo_handler.authResponse": {
"type": "object",
"properties": {
"authorization": {
"description": "签名",
"type": "string"
},
"expire_time": {
"description": "过期时间",
"type": "integer"
}
}
},
"user_handler.createRequest": {
"type": "object",
"properties": {
"mobile": {
"description": "手机号",
"type": "string"
},
"nick_name": {
"description": "昵称",
"type": "string"
},
"user_name": {
"description": "用户名",
"type": "string"
}
}
},
"user_handler.createResponse": {
"type": "object",
"properties": {
"id": {
"description": "主键ID",
"type": "integer"
}
}
},
"user_handler.deleteResponse": {
"type": "object",
"properties": {
"id": {
"description": "用户主键ID",
"type": "integer"
}
}
},
"user_handler.detailResponse": {
"type": "object",
"properties": {
"id": {
"description": "用户主键ID",
"type": "integer"
},
"mobile": {
"description": "手机号(脱敏)",
"type": "string"
},
"nick_name": {
"description": "昵称",
"type": "string"
},
"user_name": {
"description": "用户名",
"type": "string"
}
}
},
"user_handler.updateNickNameByIDRequest": {
"type": "object",
"properties": {
"id": {
"description": "用户主键ID",
"type": "integer"
},
"nick_name": {
"description": "昵称",
"type": "string"
}
}
},
"user_handler.updateNickNameByIDResponse": {
"type": "object",
"properties": {
"id": {
"description": "用户主键ID",
"type": "integer"
}
}
}
}
}`
type swaggerInfo struct {
Version string
Host string
BasePath string
Schemes []string
Title string
Description string
}
// SwaggerInfo holds exported Swagger Info so clients can modify it
var SwaggerInfo = swaggerInfo{
Version: "2.0",
Host: "127.0.0.1:9999",
BasePath: "",
Schemes: []string{},
Title: "swagger 接口文档",
Description: "",
}
type s struct{}
func (s *s) ReadDoc() string {
sInfo := SwaggerInfo
sInfo.Description = strings.Replace(sInfo.Description, "\n", "\\n", -1)
t, err := template.New("swagger_info").Funcs(template.FuncMap{
"marshal": func(v interface{}) string {
a, _ := json.Marshal(v)
return string(a)
},
}).Parse(doc)
if err != nil {
return doc
}
var tpl bytes.Buffer
if err := t.Execute(&tpl, sInfo); err != nil {
return doc
}
return tpl.String()
}
func init() {
swag.Register(swag.Name, &s{})
}
+20
View File
@@ -0,0 +1,20 @@
## Jaeger Demo 代码
- 代码在 [v1.0](https://github.com/xinliangnote/go-gin-api/releases/tag/v1.0) 版本。
#### Jaeger Demo 文档
访问:
```
http://127.0.0.1:9999/jaeger_test
```
服务端测试代码:
- [https://github.com/xinliangnote/go-jaeger-demo](https://github.com/xinliangnote/go-jaeger-demo)
![](https://github.com/xinliangnote/Go/blob/master/03-go-gin-api%20%5B文档%5D/images/jaeger_demo_2.png)
![](https://github.com/xinliangnote/Go/blob/master/03-go-gin-api%20%5B文档%5D/images/jaeger_demo_3.png)
+415
View File
@@ -0,0 +1,415 @@
{
"swagger": "2.0",
"info": {
"title": "swagger 接口文档",
"contact": {},
"license": {
"name": "MIT",
"url": "https://github.com/xinliangnote/go-gin-api/blob/master/LICENSE"
},
"version": "2.0"
},
"host": "127.0.0.1:9999",
"paths": {
"/auth/get": {
"post": {
"description": "获取授权信息",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Demo"
],
"summary": "获取授权信息",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/demo_handler.authResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/code.Failure"
}
}
}
}
},
"/demo/trace": {
"get": {
"description": "Trace 示例",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Demo"
],
"summary": "Trace 示例",
"parameters": [
{
"type": "string",
"description": "签名",
"name": "Authorization",
"in": "header",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"type": "object",
"properties": {
"job": {
"description": "工作",
"type": "string"
},
"name": {
"description": "用户名",
"type": "string"
}
}
}
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/code.Failure"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/code.Failure"
}
}
}
}
},
"/user/create": {
"post": {
"description": "创建用户",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"User"
],
"summary": "创建用户",
"parameters": [
{
"description": "请求信息",
"name": "Request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/user_handler.createRequest"
}
},
{
"type": "string",
"description": "签名",
"name": "Authorization",
"in": "header",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/user_handler.createResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/code.Failure"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/code.Failure"
}
}
}
}
},
"/user/delete/{id}": {
"patch": {
"description": "删除用户",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"User"
],
"summary": "删除用户",
"parameters": [
{
"type": "integer",
"description": "用户ID",
"name": "id",
"in": "path",
"required": true
},
{
"type": "string",
"description": "签名",
"name": "Authorization",
"in": "header",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/user_handler.deleteResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/code.Failure"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/code.Failure"
}
}
}
}
},
"/user/info/{username}": {
"get": {
"description": "用户详情",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"User"
],
"summary": "用户详情",
"parameters": [
{
"type": "string",
"description": "用户名",
"name": "username",
"in": "path",
"required": true
},
{
"type": "string",
"description": "签名",
"name": "Authorization",
"in": "header",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/user_handler.detailResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/code.Failure"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/code.Failure"
}
}
}
}
},
"/user/update": {
"put": {
"description": "编辑用户 - 通过用户主键ID更新用户昵称",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"User"
],
"summary": "编辑用户 - 通过用户主键ID更新用户昵称",
"parameters": [
{
"description": "请求信息",
"name": "Request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/user_handler.updateNickNameByIDRequest"
}
},
{
"type": "string",
"description": "签名",
"name": "Authorization",
"in": "header",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/user_handler.updateNickNameByIDResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/code.Failure"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/code.Failure"
}
}
}
}
}
},
"definitions": {
"code.Failure": {
"type": "object",
"properties": {
"code": {
"description": "业务码",
"type": "integer"
},
"message": {
"description": "描述信息",
"type": "string"
}
}
},
"demo_handler.authResponse": {
"type": "object",
"properties": {
"authorization": {
"description": "签名",
"type": "string"
},
"expire_time": {
"description": "过期时间",
"type": "integer"
}
}
},
"user_handler.createRequest": {
"type": "object",
"properties": {
"mobile": {
"description": "手机号",
"type": "string"
},
"nick_name": {
"description": "昵称",
"type": "string"
},
"user_name": {
"description": "用户名",
"type": "string"
}
}
},
"user_handler.createResponse": {
"type": "object",
"properties": {
"id": {
"description": "主键ID",
"type": "integer"
}
}
},
"user_handler.deleteResponse": {
"type": "object",
"properties": {
"id": {
"description": "用户主键ID",
"type": "integer"
}
}
},
"user_handler.detailResponse": {
"type": "object",
"properties": {
"id": {
"description": "用户主键ID",
"type": "integer"
},
"mobile": {
"description": "手机号(脱敏)",
"type": "string"
},
"nick_name": {
"description": "昵称",
"type": "string"
},
"user_name": {
"description": "用户名",
"type": "string"
}
}
},
"user_handler.updateNickNameByIDRequest": {
"type": "object",
"properties": {
"id": {
"description": "用户主键ID",
"type": "integer"
},
"nick_name": {
"description": "昵称",
"type": "string"
}
}
},
"user_handler.updateNickNameByIDResponse": {
"type": "object",
"properties": {
"id": {
"description": "用户主键ID",
"type": "integer"
}
}
}
}
}
+278
View File
@@ -0,0 +1,278 @@
definitions:
code.Failure:
properties:
code:
description: 业务码
type: integer
message:
description: 描述信息
type: string
type: object
demo_handler.authResponse:
properties:
authorization:
description: 签名
type: string
expire_time:
description: 过期时间
type: integer
type: object
user_handler.createRequest:
properties:
mobile:
description: 手机号
type: string
nick_name:
description: 昵称
type: string
user_name:
description: 用户名
type: string
type: object
user_handler.createResponse:
properties:
id:
description: 主键ID
type: integer
type: object
user_handler.deleteResponse:
properties:
id:
description: 用户主键ID
type: integer
type: object
user_handler.detailResponse:
properties:
id:
description: 用户主键ID
type: integer
mobile:
description: 手机号(脱敏)
type: string
nick_name:
description: 昵称
type: string
user_name:
description: 用户名
type: string
type: object
user_handler.updateNickNameByIDRequest:
properties:
id:
description: 用户主键ID
type: integer
nick_name:
description: 昵称
type: string
type: object
user_handler.updateNickNameByIDResponse:
properties:
id:
description: 用户主键ID
type: integer
type: object
host: 127.0.0.1:9999
info:
contact: {}
license:
name: MIT
url: https://github.com/xinliangnote/go-gin-api/blob/master/LICENSE
title: swagger 接口文档
version: "2.0"
paths:
/auth/get:
post:
consumes:
- application/json
description: 获取授权信息
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/demo_handler.authResponse'
"400":
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
summary: 获取授权信息
tags:
- Demo
/demo/trace:
get:
consumes:
- application/json
description: Trace 示例
parameters:
- description: 签名
in: header
name: Authorization
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
items:
properties:
job:
description: 工作
type: string
name:
description: 用户名
type: string
type: object
type: array
"400":
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/code.Failure'
summary: Trace 示例
tags:
- Demo
/user/create:
post:
consumes:
- application/json
description: 创建用户
parameters:
- description: 请求信息
in: body
name: Request
required: true
schema:
$ref: '#/definitions/user_handler.createRequest'
- description: 签名
in: header
name: Authorization
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/user_handler.createResponse'
"400":
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/code.Failure'
summary: 创建用户
tags:
- User
/user/delete/{id}:
patch:
consumes:
- application/json
description: 删除用户
parameters:
- description: 用户ID
in: path
name: id
required: true
type: integer
- description: 签名
in: header
name: Authorization
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/user_handler.deleteResponse'
"400":
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/code.Failure'
summary: 删除用户
tags:
- User
/user/info/{username}:
get:
consumes:
- application/json
description: 用户详情
parameters:
- description: 用户名
in: path
name: username
required: true
type: string
- description: 签名
in: header
name: Authorization
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/user_handler.detailResponse'
"400":
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/code.Failure'
summary: 用户详情
tags:
- User
/user/update:
put:
consumes:
- application/json
description: 编辑用户 - 通过用户主键ID更新用户昵称
parameters:
- description: 请求信息
in: body
name: Request
required: true
schema:
$ref: '#/definitions/user_handler.updateNickNameByIDRequest'
- description: 签名
in: header
name: Authorization
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/user_handler.updateNickNameByIDResponse'
"400":
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/code.Failure'
summary: 编辑用户 - 通过用户主键ID更新用户昵称
tags:
- User
swagger: "2.0"
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

+34 -18
View File
@@ -1,25 +1,41 @@
module go-gin-api
module github.com/xinliangnote/go-gin-api
go 1.12
go 1.15
require (
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd // indirect
github.com/99designs/gqlgen v0.13.0
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/gin-contrib/pprof v1.2.1
github.com/gin-gonic/gin v1.4.0
github.com/go-playground/locales v0.12.1 // indirect
github.com/go-playground/universal-translator v0.16.0 // indirect
github.com/golang/protobuf v1.3.2
github.com/google/uuid v1.1.1 // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.1.0
github.com/leodido/go-urn v1.1.0 // indirect
github.com/opentracing/opentracing-go v1.1.0
github.com/uber-go/atomic v1.4.0 // indirect
github.com/uber/jaeger-client-go v2.18.1+incompatible
github.com/uber/jaeger-lib v2.1.1+incompatible // indirect
github.com/xinliangnote/go-util v0.0.0-20191115235314-e7d1576d41db
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/golang/protobuf v1.4.3
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
github.com/spf13/cast v1.3.0
github.com/spf13/viper v1.7.1
github.com/swaggo/gin-swagger v1.3.0
github.com/swaggo/swag v1.7.0
github.com/vektah/gqlparser/v2 v2.1.0
go.uber.org/multierr v1.5.0
go.uber.org/zap v1.16.0
golang.org/x/mod v0.4.0 // indirect
golang.org/x/net v0.0.0-20201224014010-6772e930b67b // indirect
golang.org/x/sys v0.0.0-20201223074533-0d417f636930 // indirect
golang.org/x/time v0.0.0-20190921001708-c4c64cad1fd0
google.golang.org/grpc v1.23.1
golang.org/x/tools v0.0.0-20201226215659-b1c90890d22a // indirect
google.golang.org/grpc v1.27.0
google.golang.org/protobuf v1.25.0 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/go-playground/validator.v9 v9.29.1
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df // indirect
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
)
+546 -50
View File
@@ -1,129 +1,625 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/99designs/gqlgen v0.13.0 h1:haLTcUp3Vwp80xMVEg5KRNwzfUrgFdRmtBY8fuB8scA=
github.com/99designs/gqlgen v0.13.0/go.mod h1:NV130r6f4tpRWuAI+zsrSdooO/eWUv+Gyyoi3rEfXIk=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/PuerkitoBio/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=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
github.com/agnivade/levenshtein v1.0.3 h1:M5ZnqLOoZR8ygVq0FfkXsNOKzMCk0xRiow0R5+5VkQ0=
github.com/agnivade/levenshtein v1.0.3/go.mod h1:4SFRZbbXWLF4MU1T9Qg0pGgH3Pjs+t6ie5efyrwRJXs=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q=
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0 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/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
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/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd h1:qMd81Ts1T2OTKmB4acZcyKaMtRnY5Y44NuXGX2GFJ1w=
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/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/dgryski/trifles v0.0.0-20190318185328-a8d75aae118c h1:TUuUh0Xgj97tLMNtWtNvI9mIV6isjEb9lBMNv+77IGM=
github.com/dgryski/trifles v0.0.0-20190318185328-a8d75aae118c/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/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 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gin-contrib/gzip v0.0.1 h1:ezvKOL6jH+jlzdHNE4h9h8q8uMpDQjyl0NN0Jd7jozc=
github.com/gin-contrib/gzip v0.0.1/go.mod h1:fGBJBCdt6qCZuCAOwWuFhBB4OOq9EFqlo5dEaFhhu5w=
github.com/gin-contrib/pprof v1.2.1 h1:p6mY/FsE3tDx2+Wp3ksrMe1QL5egQ7p+lsZ7WbQLT1U=
github.com/gin-contrib/pprof v1.2.1/go.mod h1:u2l4P4YNkLXYz+xBbrl7Pxu1Btng6VCD7j3O3pUPP2w=
github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3 h1:t8FVkw33L+wilf2QiWkw0UV77qRpcH/JHPKGpKa2E8g=
github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
github.com/gin-gonic/gin v1.4.0 h1:3tMoCCfM7ppqsR0ptz/wi1impNpT7/9wQtMZ8lr1mCQ=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.3.0/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y=
github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM=
github.com/go-playground/locales v0.12.1 h1:2FITxuFt/xuCNP1Acdhv62OzaCiviiE4kotfhkmOqEc=
github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM=
github.com/go-playground/universal-translator v0.16.0 h1:X++omBR/4cE2MNg91AoC3rmGrCjJ8eAeUP/K/EKx4DM=
github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY=
github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
github.com/go-chi/chi v3.3.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
github.com/go-openapi/jsonreference v0.19.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
github.com/go-openapi/jsonreference v0.19.4/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg=
github.com/go-openapi/jsonreference v0.19.5 h1:1WJP/wi4OjB4iV8KVbH73rQaoialJrqv8gitZLxGLtM=
github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg=
github.com/go-openapi/spec v0.19.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
github.com/go-openapi/spec v0.19.14/go.mod h1:gwrgJS15eCUgjLpMjBJmbZezCsw88LmgeEip0M63doA=
github.com/go-openapi/spec v0.20.0 h1:HGLc8AJ7ynOxwv0Lq4TsnwLsWMawHAYiJIFzbcML86I=
github.com/go-openapi/spec v0.20.0/go.mod h1:+81FIL1JwC5P3/Iuuozq3pPE9dXdIEGxFutcFKaVbmU=
github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.11/go.mod h1:Uc0gKkdR+ojzsEpjh39QChyu92vPgIr72POcgHMAgSY=
github.com/go-openapi/swag v0.19.12 h1:Bc0bnY2c3AoF7Gc+IMIAQQsD8fLHjHpc19wXvYuayQI=
github.com/go-openapi/swag v0.19.12/go.mod h1:eFdyEBkTdoAf/9RXBvj4cr1nH7GD8Kzo5HTt47gr72M=
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY=
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
github.com/go-redis/redis/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.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
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 h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
github.com/golang/protobuf v1.4.3/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/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grpc-ecosystem/go-grpc-middleware v1.1.0 h1:THDBEeQ9xZ8JEaCLyLQqXMMdRqNr0QAUJTIkQAUtFjg=
github.com/grpc-ecosystem/go-grpc-middleware v1.1.0/go.mod h1:f5nM7jw/oeRSadq3xCzHAvxcr8HZnzsqU6ILg/0NiiE=
github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs=
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.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=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/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/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=
github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/leodido/go-urn v1.1.0 h1:Sm1gr51B1kKyfD2BlRcLSiEkffoG96g6TPv6eRoEiB8=
github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw=
github.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
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/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/matryer/moq v0.0.0-20200106131100-75d0ddfc0007/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-sqlite3 v1.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=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v0.0.0-20180203102830-a4e142e9c047/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
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/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3 h1:9iH4JKXLzFbOAdtqv/a+j8aewx2Y8lAjAydhbaScPF8=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.4.0 h1:7etb9YClo3a6HjLzfl6rIQaU+FDfi0VSX39io3aQ+DM=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084 h1:sofwID9zm4tzrgykg80hfFph1mryUeLRsUfoocVVmRY=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
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/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
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/shurcooL/vfsgen v0.0.0-20180121065927-ffb13db8def0/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk=
github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/uber-go/atomic v1.4.0 h1:yOuPqEq4ovnhEjpHmfFwsqBXDYbQeT6Nb0bwD6XnD5o=
github.com/uber-go/atomic v1.4.0/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g=
github.com/uber/jaeger-client-go v2.18.1+incompatible h1:bI1w1jRSWBswo6LhGdqA2SbFQX/TSVleZhMty4IJ78A=
github.com/uber/jaeger-client-go v2.18.1+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
github.com/uber/jaeger-lib v2.1.1+incompatible h1:VY/6p2WopO09BPnw787RbaCIlfKbCRC/kq3p5D0F168=
github.com/uber/jaeger-lib v2.1.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
github.com/ugorji/go v1.1.4 h1:j4s+tAvLfL3bZyefP2SEWmhBzmuIlH/eqNuPdFPgngw=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14/go.mod h1:gxQT6pBGRuIGunNf/+tSOB5OHvguWi8Tbt82WOkf35E=
github.com/swaggo/gin-swagger v1.3.0 h1:eOmp7r57oUgZPw2dJOjcGNMse9cvXcI4tTqBcnZtPsI=
github.com/swaggo/gin-swagger v1.3.0/go.mod h1:oy1BRA6WvgtCp848lhxce7BnWH4C8Bxa0m5SkWx+cS0=
github.com/swaggo/swag v1.5.1/go.mod h1:1Bl9F/ZBpVWh22nY0zmYyASPO1lI/zIwRDrpZU+tv8Y=
github.com/swaggo/swag v1.7.0 h1:5bCA/MTLQoIqDXXyHfOpMeDvL9j68OY/udlK4pQoo4E=
github.com/swaggo/swag v1.7.0/go.mod h1:BdPIL73gvS9NBsdi7M1JOxLvlbfvNRaBP8m6WT6Aajo=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/xinliangnote/go-util v0.0.0-20191115235314-e7d1576d41db h1:yReVDmqk6oBUOcSRUofahYHJlIwr7wrJ8yx1Ymkz84E=
github.com/xinliangnote/go-util v0.0.0-20191115235314-e7d1576d41db/go.mod h1:3ZornrplU9tYj9ar9jchik1XPQ10ZbSXkbgxXR5PVaU=
go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go v1.1.13 h1:nB3O5kBSQGjEQAcfe1aLUYuxmXdFKmYgBZhY32rQb6Q=
github.com/ugorji/go v1.1.13/go.mod h1:jxau1n+/wyTGLQoCkjok9r5zFa/FxT6eI5HiHKQszjc=
github.com/ugorji/go/codec v0.0.0-20181022190402-e5e69e061d4f/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
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.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
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/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e/go.mod h1:/HUdMve7rvxZma+2ZELQeNh88+003LL7Pf/CZ089j8U=
github.com/vektah/gqlparser/v2 v2.1.0 h1:uiKJ+T5HMGGQM2kRKQ8Pxw8+Zq9qhhZhz/lieYvCMns=
github.com/vektah/gqlparser/v2 v2.1.0/go.mod h1:SyUiHgLATUR8BiYURfTirrTcGpcE+4XkV2se04Px1Ms=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk=
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.16.0 h1:uFRZXykJGK9lLY4HtgSw44DnIcAM+kRBP7x5m+NpAOM=
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=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
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=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c h1:uOCk1iQW6Vc18bnC13MfzScl+wdKBmM9Y9kU7Z83/lw=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190611141213-3f473d35a33a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-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=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-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=
golang.org/x/sys v0.0.0-20201223074533-0d417f636930/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190921001708-c4c64cad1fd0 h1:xQwXv67TxFo9nC1GJFyab5eq/5B590r6RlnL/G8Sz7w=
golang.org/x/time v0.0.0-20190921001708-c4c64cad1fd0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190515012406-7d7faa4812bd/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606050223-4d9ae51c2468/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190611222205-d73e1c7e250b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200114235610-7ae403b6b589/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20201120155355-20be4ac4bd6e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201226215659-b1c90890d22a h1:pdfjQ7VswBeGam3EpuEJ4e8EAb7JgaubV570LO/SIQM=
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=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a h1:Ob5/580gVHBJZgXnff1cZDbG+xLtMVE5mDRTe+nIsX4=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.1 h1:q4XQuHFC6I28BKZpo6IYyb3mNO+l7lSOxRuYTCiDfXk=
google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1 h1:j6XxA85m/6txkUCHvzlV5f+HBNl/1r5cZ2A/3IEFOO8=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.27.0 h1:rRYRFMVgRv6E0D70Skyfsr28tDXIuuPZyWGMPdMcnXg=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/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 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM=
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 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ=
gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
gopkg.in/go-playground/validator.v9 v9.29.1 h1:SvGtYmN60a5CVKTOzMSyfzWDeZRxRuGvRQyEAKbw1xc=
gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
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=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
sourcegraph.com/sourcegraph/appdash v0.0.0-20180110180208-2cc67fd64755/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
sourcegraph.com/sourcegraph/appdash-data v0.0.0-20151005221446-73f23eafcf67/go.mod h1:L5q+DGLGOQFpo1snNEkLOJT2d1YTW66rWNzatr3He1k=
+56
View File
@@ -0,0 +1,56 @@
# Where are all the schema files located? globs are supported eg src/**/*.graphqls
schema:
- internal/graph/schemas/*.graphql
# Where should the generated server code go?
exec:
filename: internal/graph/generated/generated.go
package: generated
# Uncomment to enable federation
# federation:
# filename: graph/generated/federation.go
# package: generated
# Where should any generated models go?
model:
filename: internal/graph/model/generated.go
package: model
# Where should the resolver implementations go?
resolver:
filename: internal/graph/resolvers/generated/generated.go
type: Resolver
package: resolvers
# Optional: turn on use `gqlgen:"fieldName"` tags in your models
# struct_tag: json
# Optional: turn on to use []Thing instead of []*Thing
# omit_slice_element_pointers: false
# Optional: set to speed up generation time by not performing a final validation pass.
# skip_validation: true
# gqlgen will search for any type names in the schema in these go packages
# if they match it will use them, otherwise it will generate them.
autobind:
- "github.com/xinliangnote/go-gin-api/internal/graph/model"
# This section declares type mapping between the GraphQL and go type systems
#
# The first line in each type will be used as defaults for resolver arguments and
# modelgen, the others will be allowed when binding to fields. Configure them to
# your liking
models:
ID:
model:
- github.com/99designs/gqlgen/graphql.ID
- github.com/99designs/gqlgen/graphql.Int
- github.com/99designs/gqlgen/graphql.Int64
- github.com/99designs/gqlgen/graphql.Int32
Int:
model:
- github.com/99designs/gqlgen/graphql.Int
- github.com/99designs/gqlgen/graphql.Int64
- github.com/99designs/gqlgen/graphql.Int32
+5
View File
@@ -0,0 +1,5 @@
## init
项目初始脚本。
- DB 相关 SQL
+10
View 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表';
+13
View File
@@ -0,0 +1,13 @@
## 错误码规则
- 错误码需在 `code` 包中进行定义。
#### 错误码为 5 位数
| 1 | 01 | 01 |
| :------ | :------ | :------ |
| 服务级错误码 | 模块级错误码 | 具体错误码 |
- 服务级错误码:1 位数进行表示,比如 1 为系统级错误;2 为普通错误,通常是由用户非法操作引起。
- 模块级错误码:2 位数进行表示,比如 01 为用户模块;02 为订单模块。
- 具体的错误码:2 位数进行表示,比如 01 为手机号不合法;02 为验证码输入错误。
+41
View File
@@ -0,0 +1,41 @@
package code
// 错误时返回结构
type Failure struct {
Code int `json:"code"` // 业务码
Message string `json:"message"` // 描述信息
}
const (
// 服务级错误码
ServerError = 10101
TooManyRequests = 10102
ParamBindError = 10103
AuthorizationError = 10104
CallHTTPError = 10105
// 模块级错误码 - 用户模块
IllegalUserName = 20101
UserCreateError = 20102
UserUpdateError = 20103
UserSearchError = 20104
// ...
)
var codeText = map[int]string{
ServerError: "Internal Server Error",
TooManyRequests: "Too Many Requests",
ParamBindError: "参数信息有误",
AuthorizationError: "签名信息有误",
CallHTTPError: "调用第三方 HTTP 接口失败",
IllegalUserName: "非法用户名",
UserCreateError: "创建用户失败",
UserUpdateError: "更新用户失败",
UserSearchError: "查询用户失败",
}
func Text(code int) string {
return codeText[code]
}
+9
View File
@@ -0,0 +1,9 @@
## controller
控制器层。
主要接收参数、验证参数、调用 `service` 层的业务逻辑处理,最后返回数据。
命名规范:
- 包名以 `_handler` 结尾。
@@ -0,0 +1,47 @@
package demo_handler
import (
"net/http"
"time"
"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/pkg/errno"
"github.com/xinliangnote/go-gin-api/pkg/token"
)
type authResponse struct {
Authorization string `json:"authorization"` // 签名
ExpireTime int64 `json:"expire_time"` // 过期时间
}
// 获取授权信息
// @Summary 获取授权信息
// @Description 获取授权信息
// @Tags Demo
// @Accept json
// @Produce json
// @Success 200 {object} authResponse
// @Failure 400 {object} code.Failure
// @Router /auth/get [post]
func (h *handler) Auth() core.HandlerFunc {
return func(c core.Context) {
cfg := configs.Get().JWT
tokenString, err := token.New(cfg.Secret).Sign(1, "xinliangnote", time.Hour*cfg.ExpireDuration)
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.AuthorizationError,
code.Text(code.AuthorizationError)).WithErr(err),
)
return
}
res := new(authResponse)
res.Authorization = tokenString
res.ExpireTime = time.Now().Add(time.Hour * cfg.ExpireDuration).Unix()
c.Payload(res)
}
}
@@ -0,0 +1,48 @@
package demo_handler
import (
"net/http"
"github.com/xinliangnote/go-gin-api/internal/api/code"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/pkg/errno"
"github.com/pkg/errors"
)
func (h *handler) Get() core.HandlerFunc {
type request struct {
Name string `uri:"name"`
}
type response struct {
Name string `json:"name"`
Job string `json:"job"`
}
return func(c core.Context) {
req := new(request)
if err := c.ShouldBindURI(req); err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
)
return
}
if req.Name != "Tom" {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.IllegalUserName,
code.Text(code.IllegalUserName)).WithErr(errors.New("req.Name != Tom")),
)
return
}
c.Payload(&response{
Name: "Tom",
Job: "Student",
})
}
}
@@ -0,0 +1,48 @@
package demo_handler
import (
"net/http"
"github.com/xinliangnote/go-gin-api/internal/api/code"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/pkg/errno"
"github.com/pkg/errors"
)
func (h *handler) Post() core.HandlerFunc {
type request struct {
Name string `form:"name"`
}
type response struct {
Name string `json:"name"`
Job string `json:"job"`
}
return func(c core.Context) {
req := new(request)
if err := c.ShouldBindPostForm(req); err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
)
return
}
if req.Name != "Jack" {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.IllegalUserName,
code.Text(code.IllegalUserName)).WithErr(errors.New("req.Name != Jack")),
)
return
}
c.Payload(&response{
Name: "Jack",
Job: "Teacher",
})
}
}
@@ -0,0 +1,107 @@
package demo_handler
import (
"net/http"
"time"
"github.com/xinliangnote/go-gin-api/internal/api/code"
"github.com/xinliangnote/go-gin-api/internal/api/repository/third_party_request/go_gin_api_repo"
"github.com/xinliangnote/go-gin-api/internal/pkg/cache"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/pkg/errno"
"github.com/xinliangnote/go-gin-api/pkg/httpclient"
"github.com/xinliangnote/go-gin-api/pkg/p"
"go.uber.org/zap"
)
type traceResponse []struct {
Name string `json:"name"` //用户名
Job string `json:"job"` //工作
}
// Trace 示例
// @Summary Trace 示例
// @Description Trace 示例
// @Tags Demo
// @Accept json
// @Produce json
// @Param Authorization header string true "签名"
// @Success 200 {object} traceResponse
// @Failure 400 {object} code.Failure
// @Failure 401 {object} code.Failure
// @Router /demo/trace [get]
func (h *handler) Trace() core.HandlerFunc {
return func(c core.Context) {
// 三方请求信息
res1, err := go_gin_api_repo.DemoGet("Tom",
httpclient.WithTTL(time.Second*5),
httpclient.WithTrace(c.Trace()),
httpclient.WithLogger(c.Logger()),
httpclient.WithHeader("Authorization", c.GetHeader("Authorization")),
httpclient.WithOnFailedRetry(3, time.Second*1, go_gin_api_repo.DemoGetRetryVerify),
)
if err != nil {
h.logger.Error("get [demo/get] err", zap.Error(err))
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.CallHTTPError,
code.Text(code.CallHTTPError)).WithErr(err),
)
return
}
// 调试信息
p.Println("res1.Name", res1.Name, p.WithTrace(c.Trace()))
// 三方请求信息
res2, err := go_gin_api_repo.DemoPost("Jack",
httpclient.WithTTL(time.Second*5),
httpclient.WithTrace(c.Trace()),
httpclient.WithLogger(c.Logger()),
httpclient.WithHeader("Authorization", c.GetHeader("Authorization")),
httpclient.WithOnFailedRetry(3, time.Second*1, go_gin_api_repo.DemoPostRetryVerify),
)
if err != nil {
h.logger.Error("post [demo/post] err", zap.Error(err))
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.CallHTTPError,
code.Text(code.CallHTTPError)).WithErr(err),
)
return
}
// 调试信息
p.Println("res2.Name",
res2.Name,
p.WithTrace(c.Trace()),
)
// 执行 SQL 信息
h.userService.GetUserByUserName(c, "test_user")
// 执行 Redis 信息
_ = h.cache.Set("name", "tom", time.Minute*10, cache.WithTrace(c.Trace()))
val, _ := h.cache.Get("name", cache.WithTrace(c.Trace()))
p.Println("redis-name", val, p.WithTrace(c.Trace()))
// 初始化客户端
// client := hello.NewHelloClient(d.grpConn.Conn())
// client.SayHello(grpc.ContextWithValueAndTimeout(c, time.Second*3), &hello.HelloRequest{Name: "Hello World"})
data := &traceResponse{
{
Name: res1.Name,
Job: res1.Job,
},
{
Name: res2.Name,
Job: res2.Job,
},
}
c.Payload(data)
}
}
@@ -0,0 +1,44 @@
package demo_handler
import (
"github.com/xinliangnote/go-gin-api/internal/api/service/user_service"
"github.com/xinliangnote/go-gin-api/internal/pkg/cache"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/pkg/db"
"github.com/xinliangnote/go-gin-api/internal/pkg/grpc"
"go.uber.org/zap"
)
var _ Handler = (*handler)(nil)
type Handler interface {
// i 为了避免被其他包实现
i()
// 示例:支持 get 请求的方法
Get() core.HandlerFunc
// 示例:支持 post 请求的方法
Post() core.HandlerFunc
// 获取授权信息
Auth() core.HandlerFunc
// Trace 示例
Trace() core.HandlerFunc
}
type handler struct {
logger *zap.Logger
cache cache.Repo
grpConn grpc.ClientConn
userService user_service.UserService
}
func New(logger *zap.Logger, db db.Repo, cache cache.Repo, grpConn grpc.ClientConn) Handler {
return &handler{
logger: logger,
cache: cache,
grpConn: grpConn,
userService: user_service.NewUserService(db, cache),
}
}
func (h *handler) i() {}
@@ -0,0 +1,77 @@
package user_handler
import (
"net/http"
"github.com/xinliangnote/go-gin-api/internal/api/code"
"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/errno"
"github.com/pkg/errors"
)
type createRequest struct {
UserName string `json:"user_name"` // 用户名
NickName string `json:"nick_name"` // 昵称
Mobile string `json:"mobile"` // 手机号
}
type createResponse struct {
Id int32 `json:"id"` // 主键ID
}
// 创建用户
// @Summary 创建用户
// @Description 创建用户
// @Tags User
// @Accept json
// @Produce json
// @Param Request body createRequest true "请求信息"
// @Param Authorization header string true "签名"
// @Success 200 {object} createResponse
// @Failure 400 {object} code.Failure
// @Failure 401 {object} code.Failure
// @Router /user/create [post]
func (h *handler) Create() core.HandlerFunc {
return func(c core.Context) {
req := new(createRequest)
res := new(createResponse)
if err := c.ShouldBindJSON(req); err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
)
return
}
if req.UserName == "" {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.IllegalUserName,
code.Text(code.IllegalUserName)).WithErr(errors.New("req.UserName = ''")),
)
return
}
createUserData := new(user_service.CreateUserInfo)
createUserData.Mobile = req.Mobile
createUserData.NickName = req.NickName
createUserData.UserName = req.UserName
id, err := h.userService.Create(c, createUserData)
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.UserCreateError,
code.Text(code.UserCreateError)).WithErr(err),
)
return
}
res.Id = id
c.Payload(res)
}
}
@@ -0,0 +1,57 @@
package user_handler
import (
"net/http"
"github.com/xinliangnote/go-gin-api/internal/api/code"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/pkg/errno"
)
type deleteRequest struct {
Id int32 `uri:"id"` // 用户ID
}
type deleteResponse struct {
Id int32 `json:"id"` // 用户主键ID
}
// 删除用户
// @Summary 删除用户
// @Description 删除用户
// @Tags User
// @Accept json
// @Produce json
// @Param id path int true "用户ID"
// @Param Authorization header string true "签名"
// @Success 200 {object} deleteResponse
// @Failure 400 {object} code.Failure
// @Failure 401 {object} code.Failure
// @Router /user/delete/{id} [patch]
func (h *handler) Delete() core.HandlerFunc {
return func(c core.Context) {
req := new(deleteRequest)
res := new(deleteResponse)
if err := c.ShouldBindURI(req); err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
)
return
}
err := h.userService.Delete(c, req.Id)
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.UserUpdateError,
code.Text(code.UserUpdateError)).WithErr(err),
)
return
}
res.Id = req.Id
c.Payload(res)
}
}
@@ -0,0 +1,65 @@
package user_handler
import (
"net/http"
"github.com/xinliangnote/go-gin-api/internal/api/code"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/pkg/ddm"
"github.com/xinliangnote/go-gin-api/pkg/errno"
)
type detailRequest struct {
UserName string `uri:"username"` // 用户名
}
type detailResponse struct {
Id int32 `json:"id"` // 用户主键ID
UserName string `json:"user_name"` // 用户名
NickName string `json:"nick_name"` // 昵称
Mobile ddm.Mobile `json:"mobile"` // 手机号(脱敏)
}
// 用户详情
// @Summary 用户详情
// @Description 用户详情
// @Tags User
// @Accept json
// @Produce json
// @Param username path string true "用户名"
// @Param Authorization header string true "签名"
// @Success 200 {object} detailResponse
// @Failure 400 {object} code.Failure
// @Failure 401 {object} code.Failure
// @Router /user/info/{username} [get]
func (h *handler) Detail() core.HandlerFunc {
return func(c core.Context) {
req := new(detailRequest)
res := new(detailResponse)
if err := c.ShouldBindURI(req); err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
)
return
}
user, err := h.userService.GetUserByUserName(c, req.UserName)
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.UserSearchError,
code.Text(code.UserSearchError)).WithErr(err),
)
return
}
res.Id = user.Id
res.UserName = user.UserName
res.NickName = user.NickName
res.Mobile = ddm.Mobile(user.Mobile)
c.Payload(res)
}
}
@@ -0,0 +1,59 @@
package user_handler
import (
"net/http"
"github.com/xinliangnote/go-gin-api/internal/api/code"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/pkg/errno"
)
type updateNickNameByIDRequest struct {
Id int32 `json:"id"` // 用户主键ID
NickName string `json:"nick_name"` // 昵称
}
type updateNickNameByIDResponse struct {
Id int32 `json:"id"` // 用户主键ID
}
// 编辑用户 - 通过用户主键ID更新用户昵称
// @Summary 编辑用户 - 通过用户主键ID更新用户昵称
// @Description 编辑用户 - 通过用户主键ID更新用户昵称
// @Tags User
// @Accept json
// @Produce json
// @Param Request body updateNickNameByIDRequest true "请求信息"
// @Param Authorization header string true "签名"
// @Success 200 {object} updateNickNameByIDResponse
// @Failure 400 {object} code.Failure
// @Failure 401 {object} code.Failure
// @Router /user/update [put]
func (h *handler) UpdateNickNameByID() core.HandlerFunc {
return func(c core.Context) {
req := new(updateNickNameByIDRequest)
res := new(updateNickNameByIDResponse)
if err := c.ShouldBindJSON(req); err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
)
return
}
err := h.userService.UpdateNickNameByID(c, req.Id, req.NickName)
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.UserUpdateError,
code.Text(code.UserUpdateError)).WithErr(err),
)
return
}
res.Id = req.Id
c.Payload(res)
}
}
@@ -0,0 +1,41 @@
package user_handler
import (
"github.com/xinliangnote/go-gin-api/internal/api/service/user_service"
"github.com/xinliangnote/go-gin-api/internal/pkg/cache"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/pkg/db"
"go.uber.org/zap"
)
var _ Handler = (*handler)(nil)
type Handler interface {
// i 为了避免被其他包实现
i()
// Create 创建用户
Create() core.HandlerFunc
// UpdateNickNameByID 编辑用户 - 通过主键ID更新用户昵称
UpdateNickNameByID() core.HandlerFunc
// Delete 删除用户
Delete() core.HandlerFunc
// Detail 用户详情
Detail() core.HandlerFunc
}
type handler struct {
logger *zap.Logger
cache cache.Repo
userService user_service.UserService
}
func New(logger *zap.Logger, db db.Repo, cache cache.Repo) Handler {
return &handler{
logger: logger,
cache: cache,
userService: user_service.NewUserService(db, cache),
}
}
func (h *handler) i() {}
+32
View File
@@ -0,0 +1,32 @@
## repository
#### 数据访问层。
- `./db_repo` 访问 DB 数据
- `./cache_repo` 访问 Cache 数据
- `./third_party_request` 访问外部 HTTP 接口数据。
#### 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` 命名;
#### 脚本生成 MySQL CURD
1. 定义生成的表,设置 config 中 cmd.genTables,可以自定义设置多张表,为空表示生成库中所有的表,如果设置多个表可用','分割;
1. 在根目录下执行脚本文件:`./scripts/gormgen.sh`
以用户表(user_demo)为例:
- 结构体文件:user_demo_repo/gen_model.go
- CURD 方法文件:user_demo_repo/gen_user_demo.go
- 表结构 MD 文件:user_demo_repo/gen_table.md
+14
View File
@@ -0,0 +1,14 @@
package db_repo
// Predicate is a string that acts as a condition in the where clause
type Predicate string
var (
EqualPredicate = Predicate("=")
NotEqualPredicate = Predicate("<>")
GreaterThanPredicate = Predicate(">")
GreaterThanOrEqualPredicate = Predicate(">=")
SmallerThanPredicate = Predicate("<")
SmallerThanOrEqualPredicate = Predicate("<=")
LikePredicate = Predicate("LIKE")
)
@@ -0,0 +1,15 @@
package user_demo_repo
import "time"
// 用户Demo表
//go:generate gormgen -structs UserDemo -input .
type UserDemo struct {
Id int32 // 主键
UserName string // 用户名
NickName string // 昵称
Mobile string // 手机号
IsDeleted int32 // 是否删除 1:是 -1:否
CreatedAt time.Time `gorm:"time"` // 创建时间
UpdatedAt time.Time `gorm:"time"` // 更新时间
}
@@ -0,0 +1,12 @@
#### xin_ceshi.user_demo
用户Demo表
| 序号 | 名称 | 描述 | 类型 | 键 | 为空 | 额外 | 默认值 |
| :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: |
| 1 | id | 主键 | int(11) unsigned | PRI | NO | auto_increment | |
| 2 | user_name | 用户名 | varchar(32) | | NO | | |
| 3 | nick_name | 昵称 | varchar(100) | | NO | | |
| 4 | mobile | 手机号 | varchar(20) | | NO | | |
| 5 | is_deleted | 是否删除 1:是 -1:否 | tinyint(1) | | NO | | -1 |
| 6 | created_at | 创建时间 | timestamp | | NO | | CURRENT_TIMESTAMP |
| 7 | updated_at | 更新时间 | timestamp | | NO | on update CURRENT_TIMESTAMP | CURRENT_TIMESTAMP |
@@ -0,0 +1,257 @@
///////////////////////////////////////////////////////////
// THIS FILE IS AUTO GENERATED by gormgen, DON'T EDIT IT //
// ANY CHANGES DONE HERE WILL BE LOST //
///////////////////////////////////////////////////////////
package user_demo_repo
import (
"fmt"
"time"
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo"
"github.com/pkg/errors"
"gorm.io/gorm"
)
func NewModel() *UserDemo {
return new(UserDemo)
}
func NewQueryBuilder() *userDemoRepoQueryBuilder {
return new(userDemoRepoQueryBuilder)
}
func (t *UserDemo) Create(db *gorm.DB) (id int32, err error) {
if err = db.Create(t).Error; err != nil {
return 0, errors.Wrap(err, "create err")
}
return t.Id, nil
}
func (t *UserDemo) Delete(db *gorm.DB) (err error) {
if err = db.Delete(t).Error; err != nil {
return errors.Wrap(err, "delete err")
}
return nil
}
func (t *UserDemo) Updates(db *gorm.DB, m map[string]interface{}) (err error) {
if err = db.Model(&UserDemo{}).Where("id = ?", t.Id).Updates(m).Error; err != nil {
return errors.Wrap(err, "updates err")
}
return nil
}
type userDemoRepoQueryBuilder struct {
order []string
where []struct {
prefix string
value interface{}
}
limit int
offset int
}
func (qb *userDemoRepoQueryBuilder) buildQuery(db *gorm.DB) *gorm.DB {
ret := db
for _, where := range qb.where {
ret = ret.Where(where.prefix, where.value)
}
for _, order := range qb.order {
ret = ret.Order(order)
}
ret = ret.Limit(qb.limit).Offset(qb.offset)
return ret
}
func (qb *userDemoRepoQueryBuilder) Count(db *gorm.DB) (int64, error) {
var c int64
res := qb.buildQuery(db).Model(&UserDemo{}).Count(&c)
if res.Error != nil && res.Error == gorm.ErrRecordNotFound {
c = 0
}
return c, res.Error
}
func (qb *userDemoRepoQueryBuilder) First(db *gorm.DB) (*UserDemo, error) {
ret := &UserDemo{}
res := qb.buildQuery(db).First(ret)
if res.Error != nil && res.Error == gorm.ErrRecordNotFound {
ret = nil
}
return ret, res.Error
}
func (qb *userDemoRepoQueryBuilder) QueryOne(db *gorm.DB) (*UserDemo, error) {
qb.limit = 1
ret, err := qb.QueryAll(db)
if len(ret) > 0 {
return ret[0], err
}
return nil, err
}
func (qb *userDemoRepoQueryBuilder) QueryAll(db *gorm.DB) ([]*UserDemo, error) {
var ret []*UserDemo
err := qb.buildQuery(db).Find(&ret).Error
return ret, err
}
func (qb *userDemoRepoQueryBuilder) Limit(limit int) *userDemoRepoQueryBuilder {
qb.limit = limit
return qb
}
func (qb *userDemoRepoQueryBuilder) Offset(offset int) *userDemoRepoQueryBuilder {
qb.offset = offset
return qb
}
func (qb *userDemoRepoQueryBuilder) WhereId(p db_repo.Predicate, value int32) *userDemoRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "id", p),
value,
})
return qb
}
func (qb *userDemoRepoQueryBuilder) OrderById(asc bool) *userDemoRepoQueryBuilder {
order := "DESC"
if asc {
order = "ASC"
}
qb.order = append(qb.order, "id "+order)
return qb
}
func (qb *userDemoRepoQueryBuilder) WhereUserName(p db_repo.Predicate, value string) *userDemoRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "user_name", p),
value,
})
return qb
}
func (qb *userDemoRepoQueryBuilder) OrderByUserName(asc bool) *userDemoRepoQueryBuilder {
order := "DESC"
if asc {
order = "ASC"
}
qb.order = append(qb.order, "user_name "+order)
return qb
}
func (qb *userDemoRepoQueryBuilder) WhereNickName(p db_repo.Predicate, value string) *userDemoRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "nick_name", p),
value,
})
return qb
}
func (qb *userDemoRepoQueryBuilder) OrderByNickName(asc bool) *userDemoRepoQueryBuilder {
order := "DESC"
if asc {
order = "ASC"
}
qb.order = append(qb.order, "nick_name "+order)
return qb
}
func (qb *userDemoRepoQueryBuilder) WhereMobile(p db_repo.Predicate, value string) *userDemoRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "mobile", p),
value,
})
return qb
}
func (qb *userDemoRepoQueryBuilder) OrderByMobile(asc bool) *userDemoRepoQueryBuilder {
order := "DESC"
if asc {
order = "ASC"
}
qb.order = append(qb.order, "mobile "+order)
return qb
}
func (qb *userDemoRepoQueryBuilder) WhereIsDeleted(p db_repo.Predicate, value int32) *userDemoRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "is_deleted", p),
value,
})
return qb
}
func (qb *userDemoRepoQueryBuilder) OrderByIsDeleted(asc bool) *userDemoRepoQueryBuilder {
order := "DESC"
if asc {
order = "ASC"
}
qb.order = append(qb.order, "is_deleted "+order)
return qb
}
func (qb *userDemoRepoQueryBuilder) WhereCreatedAt(p db_repo.Predicate, value time.Time) *userDemoRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "created_at", p),
value,
})
return qb
}
func (qb *userDemoRepoQueryBuilder) OrderByCreatedAt(asc bool) *userDemoRepoQueryBuilder {
order := "DESC"
if asc {
order = "ASC"
}
qb.order = append(qb.order, "created_at "+order)
return qb
}
func (qb *userDemoRepoQueryBuilder) WhereUpdatedAt(p db_repo.Predicate, value time.Time) *userDemoRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "updated_at", p),
value,
})
return qb
}
func (qb *userDemoRepoQueryBuilder) OrderByUpdatedAt(asc bool) *userDemoRepoQueryBuilder {
order := "DESC"
if asc {
order = "ASC"
}
qb.order = append(qb.order, "updated_at "+order)
return qb
}
@@ -0,0 +1,275 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: hello.proto
package hello
import (
context "context"
fmt "fmt"
math "math"
proto "github.com/golang/protobuf/proto"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
// HelloRequest 请求结构
type HelloRequest struct {
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *HelloRequest) Reset() { *m = HelloRequest{} }
func (m *HelloRequest) String() string { return proto.CompactTextString(m) }
func (*HelloRequest) ProtoMessage() {}
func (*HelloRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_61ef911816e0a8ce, []int{0}
}
func (m *HelloRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_HelloRequest.Unmarshal(m, b)
}
func (m *HelloRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_HelloRequest.Marshal(b, m, deterministic)
}
func (m *HelloRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_HelloRequest.Merge(m, src)
}
func (m *HelloRequest) XXX_Size() int {
return xxx_messageInfo_HelloRequest.Size(m)
}
func (m *HelloRequest) XXX_DiscardUnknown() {
xxx_messageInfo_HelloRequest.DiscardUnknown(m)
}
var xxx_messageInfo_HelloRequest proto.InternalMessageInfo
func (m *HelloRequest) GetName() string {
if m != nil {
return m.Name
}
return ""
}
// HelloResponse 响应结构
type HelloResponse struct {
Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *HelloResponse) Reset() { *m = HelloResponse{} }
func (m *HelloResponse) String() string { return proto.CompactTextString(m) }
func (*HelloResponse) ProtoMessage() {}
func (*HelloResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_61ef911816e0a8ce, []int{1}
}
func (m *HelloResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_HelloResponse.Unmarshal(m, b)
}
func (m *HelloResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_HelloResponse.Marshal(b, m, deterministic)
}
func (m *HelloResponse) XXX_Merge(src proto.Message) {
xxx_messageInfo_HelloResponse.Merge(m, src)
}
func (m *HelloResponse) XXX_Size() int {
return xxx_messageInfo_HelloResponse.Size(m)
}
func (m *HelloResponse) XXX_DiscardUnknown() {
xxx_messageInfo_HelloResponse.DiscardUnknown(m)
}
var xxx_messageInfo_HelloResponse proto.InternalMessageInfo
func (m *HelloResponse) GetMessage() string {
if m != nil {
return m.Message
}
return ""
}
func init() {
proto.RegisterType((*HelloRequest)(nil), "hello.HelloRequest")
proto.RegisterType((*HelloResponse)(nil), "hello.HelloResponse")
}
func init() { proto.RegisterFile("hello.proto", fileDescriptor_61ef911816e0a8ce) }
var fileDescriptor_61ef911816e0a8ce = []byte{
// 154 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0xce, 0x48, 0xcd, 0xc9,
0xc9, 0xd7, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x05, 0x73, 0x94, 0x94, 0xb8, 0x78, 0x3c,
0x40, 0x8c, 0xa0, 0xd4, 0xc2, 0xd2, 0xd4, 0xe2, 0x12, 0x21, 0x21, 0x2e, 0x96, 0xbc, 0xc4, 0xdc,
0x54, 0x09, 0x46, 0x05, 0x46, 0x0d, 0xce, 0x20, 0x30, 0x5b, 0x49, 0x93, 0x8b, 0x17, 0xaa, 0xa6,
0xb8, 0x20, 0x3f, 0xaf, 0x38, 0x55, 0x48, 0x82, 0x8b, 0x3d, 0x37, 0xb5, 0xb8, 0x38, 0x31, 0x1d,
0xa6, 0x0e, 0xc6, 0x35, 0x6a, 0x60, 0xe4, 0x62, 0x05, 0xab, 0x15, 0x32, 0xe7, 0xe2, 0x08, 0x4e,
0xac, 0x84, 0xb0, 0x85, 0xf5, 0x20, 0x36, 0x23, 0xdb, 0x24, 0x25, 0x82, 0x2a, 0x08, 0x31, 0x5a,
0x89, 0x41, 0xc8, 0x8e, 0x8b, 0xd7, 0x27, 0xbf, 0xa4, 0xd8, 0x3f, 0x2d, 0x28, 0xb5, 0x20, 0x27,
0x33, 0xb5, 0x98, 0x24, 0xdd, 0x06, 0x8c, 0x49, 0x6c, 0x60, 0xff, 0x19, 0x03, 0x02, 0x00, 0x00,
0xff, 0xff, 0x87, 0x17, 0x0f, 0x68, 0xee, 0x00, 0x00, 0x00,
}
// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConn
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
const _ = grpc.SupportPackageIsVersion4
// HelloClient is the client API for Hello service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type HelloClient interface {
// 定义 SayHello 方法
SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloResponse, error)
// 定义 LotsOfReplies 方法
LotsOfReplies(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (Hello_LotsOfRepliesClient, error)
}
type helloClient struct {
cc *grpc.ClientConn
}
func NewHelloClient(cc *grpc.ClientConn) HelloClient {
return &helloClient{cc}
}
func (c *helloClient) SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloResponse, error) {
out := new(HelloResponse)
err := c.cc.Invoke(ctx, "/hello.Hello/SayHello", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *helloClient) LotsOfReplies(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (Hello_LotsOfRepliesClient, error) {
stream, err := c.cc.NewStream(ctx, &_Hello_serviceDesc.Streams[0], "/hello.Hello/LotsOfReplies", opts...)
if err != nil {
return nil, err
}
x := &helloLotsOfRepliesClient{stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
type Hello_LotsOfRepliesClient interface {
Recv() (*HelloResponse, error)
grpc.ClientStream
}
type helloLotsOfRepliesClient struct {
grpc.ClientStream
}
func (x *helloLotsOfRepliesClient) Recv() (*HelloResponse, error) {
m := new(HelloResponse)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
// HelloServer is the server API for Hello service.
type HelloServer interface {
// 定义 SayHello 方法
SayHello(context.Context, *HelloRequest) (*HelloResponse, error)
// 定义 LotsOfReplies 方法
LotsOfReplies(*HelloRequest, Hello_LotsOfRepliesServer) error
}
// UnimplementedHelloServer can be embedded to have forward compatible implementations.
type UnimplementedHelloServer struct {
}
func (*UnimplementedHelloServer) SayHello(ctx context.Context, req *HelloRequest) (*HelloResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SayHello not implemented")
}
func (*UnimplementedHelloServer) LotsOfReplies(req *HelloRequest, srv Hello_LotsOfRepliesServer) error {
return status.Errorf(codes.Unimplemented, "method LotsOfReplies not implemented")
}
func RegisterHelloServer(s *grpc.Server, srv HelloServer) {
s.RegisterService(&_Hello_serviceDesc, srv)
}
func _Hello_SayHello_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(HelloRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(HelloServer).SayHello(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/hello.Hello/SayHello",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(HelloServer).SayHello(ctx, req.(*HelloRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Hello_LotsOfReplies_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(HelloRequest)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(HelloServer).LotsOfReplies(m, &helloLotsOfRepliesServer{stream})
}
type Hello_LotsOfRepliesServer interface {
Send(*HelloResponse) error
grpc.ServerStream
}
type helloLotsOfRepliesServer struct {
grpc.ServerStream
}
func (x *helloLotsOfRepliesServer) Send(m *HelloResponse) error {
return x.ServerStream.SendMsg(m)
}
var _Hello_serviceDesc = grpc.ServiceDesc{
ServiceName: "hello.Hello",
HandlerType: (*HelloServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "SayHello",
Handler: _Hello_SayHello_Handler,
},
},
Streams: []grpc.StreamDesc{
{
StreamName: "LotsOfReplies",
Handler: _Hello_LotsOfReplies_Handler,
ServerStreams: true,
},
},
Metadata: "hello.proto",
}
+23
View File
@@ -0,0 +1,23 @@
syntax = "proto3"; // 指定 proto 版本
package hello; // 指定包名
// 定义 Hello 服务
service Hello {
// 定义 SayHello 方法
rpc SayHello(HelloRequest) returns (HelloResponse) {}
// 定义 LotsOfReplies 方法
rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse){}
}
// HelloRequest 请求结构
message HelloRequest {
string name = 1;
}
// HelloResponse 响应结构
message HelloResponse {
string message = 1;
}
@@ -0,0 +1,24 @@
package third_party_request
import (
"github.com/xinliangnote/go-gin-api/pkg/httpclient"
"github.com/xinliangnote/go-gin-api/pkg/mail"
)
// 实现 AlarmObject 告警
var _ httpclient.AlarmObject = (*AlarmEmail)(nil)
type AlarmEmail struct{}
func (a *AlarmEmail) Send(subject, body string) error {
options := &mail.Options{
MailHost: "smtp.163.com",
MailPort: 465,
MailUser: "xx@163.com",
MailPass: "",
MailTo: "",
Subject: subject,
Body: body,
}
return mail.Send(options)
}
@@ -0,0 +1,70 @@
package go_gin_api_repo
import (
"encoding/json"
"net/url"
"github.com/xinliangnote/go-gin-api/pkg/httpclient"
"github.com/pkg/errors"
)
type demoGetResponse struct {
Name string `json:"name"`
Job string `json:"job"`
}
type demoPostResponse struct {
Name string `json:"name"`
Job string `json:"job"`
}
func DemoGet(name string, opts ...httpclient.Option) (res *demoGetResponse, err error) {
api := "http://127.0.0.1:9999/demo/get/" + name
body, err := httpclient.Get(api, nil, opts...)
if err != nil {
return nil, err
}
res = new(demoGetResponse)
err = json.Unmarshal(body, res)
if err != nil {
return nil, errors.Wrap(err, "DemoGet json unmarshal error")
}
return res, nil
}
func DemoGetRetryVerify(body []byte) (shouldRetry bool) {
if len(body) == 0 {
return true
}
return false
}
func DemoPost(name string, opts ...httpclient.Option) (res *demoPostResponse, err error) {
api := "http://127.0.0.1:9999/demo/post"
params := url.Values{}
params.Set("name", name)
body, err := httpclient.PostForm(api, params, opts...)
if err != nil {
return nil, err
}
res = new(demoPostResponse)
err = json.Unmarshal(body, res)
if err != nil {
return nil, errors.Wrap(err, "DemoPost json unmarshal error")
}
return res, nil
}
func DemoPostRetryVerify(body []byte) (shouldRetry bool) {
if len(body) == 0 {
return true
}
return false
}
@@ -0,0 +1,21 @@
package go_gin_api_repo
import "encoding/json"
func MockDemoGet() (body []byte) {
res := new(demoGetResponse)
res.Name = "AA"
res.Job = "AA_JOB"
body, _ = json.Marshal(res)
return body
}
func MockDemoPost() (body []byte) {
res := new(demoPostResponse)
res.Name = "BB"
res.Job = "BB_JOB"
body, _ = json.Marshal(res)
return body
}
@@ -0,0 +1,80 @@
package go_gin_api_repo
import (
"encoding/json"
"testing"
"time"
"github.com/xinliangnote/go-gin-api/internal/api/repository/third_party_request"
"github.com/xinliangnote/go-gin-api/pkg/httpclient"
)
var authorization = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySUQiOjEsIlVzZXJOYW1lIjoieGlubGlhbmdub3RlIiwiZXhwIjoxNjEzODI3MTEzLCJpYXQiOjE2MTM3NDA3MTMsIm5iZiI6MTYxMzc0MDcxM30.SnooP1ikO33ryGPdohsmOKqISa-bWzMkMvUNb5f2zc0"
func TestDemoGet(t *testing.T) {
res, err := DemoGet("Tom",
httpclient.WithTTL(time.Second*5),
//httpclient.WithTrace(ctx.Trace()),
//httpclient.WithLogger(ctx.Logger()),
httpclient.WithHeader("Authorization", authorization),
httpclient.WithOnFailedRetry(3, time.Second*1, retryVerify),
httpclient.WithOnFailedAlarm("接口告警", new(third_party_request.AlarmEmail), alarmVerify),
//httpclient.WithMock(MockDemoGet),
)
if err != nil {
t.Log("get [demo/get] err", err)
}
t.Log(res)
}
func TestDemoPost(t *testing.T) {
res, err := DemoPost("Jack",
httpclient.WithTTL(time.Second*5),
//httpclient.WithTrace(ctx.Trace()),
//httpclient.WithLogger(ctx.Logger()),
httpclient.WithHeader("Authorization", authorization),
httpclient.WithMock(MockDemoPost),
)
if err != nil {
t.Log("post [demo/post] err", err)
}
t.Log(res)
}
// 设置重试规则
func retryVerify(body []byte) (shouldRetry bool) {
if len(body) == 0 {
return true
}
type Response struct {
Code int `json:"code"`
}
resp := new(Response)
if err := json.Unmarshal(body, resp); err != nil {
return true
}
return resp.Code != 1
}
// 设置告警规则
func alarmVerify(body []byte) (shouldAlarm bool) {
if len(body) == 0 {
return true
}
type Response struct {
Code int `json:"code"`
}
resp := new(Response)
if err := json.Unmarshal(body, resp); err != nil {
return true
}
return resp.Code != 1
}
@@ -0,0 +1,48 @@
package auth
import (
"net/http"
"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/pkg/errno"
"github.com/xinliangnote/go-gin-api/pkg/token"
"github.com/pkg/errors"
)
func AuthHandler(ctx core.Context) (userId int64, userName string, err errno.Error) {
auth := ctx.GetHeader("Authorization")
if auth == "" {
err = errno.NewError(
http.StatusUnauthorized,
code.AuthorizationError,
code.Text(code.AuthorizationError)).WithErr(errors.New("Header 中缺少 Authorization 参数"))
return
}
cfg := configs.Get().JWT
claims, errParse := token.New(cfg.Secret).Parse(auth)
if errParse != nil {
err = errno.NewError(
http.StatusUnauthorized,
code.AuthorizationError,
code.Text(code.AuthorizationError)).WithErr(errParse)
return
}
userId = claims.UserID
if userId <= 0 {
err = errno.NewError(
http.StatusUnauthorized,
code.AuthorizationError,
code.Text(code.AuthorizationError)).WithErr(errors.New("claims.UserID <= 0 "))
return
}
userName = claims.UserName
return
}
+74
View File
@@ -0,0 +1,74 @@
package router
import (
"github.com/xinliangnote/go-gin-api/internal/api/controller/demo_handler"
"github.com/xinliangnote/go-gin-api/internal/api/controller/user_handler"
"github.com/xinliangnote/go-gin-api/internal/api/router/middleware/auth"
"github.com/xinliangnote/go-gin-api/internal/graph/handler"
"github.com/xinliangnote/go-gin-api/internal/pkg/cache"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/pkg/db"
"github.com/xinliangnote/go-gin-api/internal/pkg/grpc"
"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, cache cache.Repo, grpConn grpc.ClientConn) (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)
}
gqlHandler := handler.New(logger, db, cache)
gql := mux.Group("/graphql")
{
gql.GET("", gqlHandler.Playground())
gql.POST("/query", gqlHandler.Query())
}
demoHandler := demo_handler.New(logger, db, cache, grpConn)
userHandler := user_handler.New(logger, db, cache)
// user_demo CURD
user := mux.Group("/user", core.WrapAuthHandler(auth.AuthHandler))
{
user.POST("/create", userHandler.Create())
user.PUT("/update", userHandler.UpdateNickNameByID())
user.PATCH("/delete/:id", userHandler.Delete())
user.GET("/info/:username", core.AliasForRecordMetrics("/user/info"), userHandler.Detail())
}
// auth
a := mux.Group("/auth")
{
a.POST("/get", demoHandler.Auth())
}
// demo
d := mux.Group("/demo", core.WrapAuthHandler(auth.AuthHandler)) //使用 auth 验证
{
// 为了演示 Trace ,增加了一些看起来无意义的调试信息和 SQL 信息。
d.GET("/trace", demoHandler.Trace())
// 模拟数据
d.GET("get/:name", core.AliasForRecordMetrics("/demo/get"), demoHandler.Get())
d.POST("post", demoHandler.Post())
}
return mux, nil
}
+10
View File
@@ -0,0 +1,10 @@
## service
业务逻辑层。
处于 `controller` 层和 `repository` 层之间,依赖接口开发。
命令规范:
- 包名以 `_service` 结尾。
@@ -0,0 +1,25 @@
package user_service
import (
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/user_demo_repo"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
)
type CreateUserInfo struct {
UserName string `json:"user_name"` // 用户名
NickName string `json:"nick_name"` // 昵称
Mobile string `json:"mobile"` // 手机号
}
func (u *userSer) Create(ctx core.Context, user *CreateUserInfo) (id int32, err error) {
model := user_demo_repo.NewModel()
model.UserName = user.UserName
model.NickName = user.NickName
model.Mobile = user.Mobile
id, err = model.Create(u.db.GetDbW().WithContext(ctx.RequestContext()))
if err != nil {
return 0, err
}
return
}
@@ -0,0 +1,16 @@
package user_service
import (
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/user_demo_repo"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
)
func (u *userSer) Delete(ctx core.Context, id int32) (err error) {
model := user_demo_repo.NewModel()
model.Id = id
err = model.Delete(u.db.GetDbW().WithContext(ctx.RequestContext()))
if err != nil {
return nil
}
return nil
}
@@ -0,0 +1,23 @@
package user_service
import (
"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"
)
func (u *userSer) GetUserByUserName(ctx core.Context, username string) (user *user_demo_repo.UserDemo, err error) {
user, err = user_demo_repo.NewQueryBuilder().
WhereUserName(db_repo.EqualPredicate, username).
QueryOne(u.db.GetDbR().WithContext(ctx.RequestContext()))
if err != nil {
return user, err
}
if user == nil {
user = user_demo_repo.NewModel()
}
return user, nil
}
@@ -0,0 +1,22 @@
package user_service
import (
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/user_demo_repo"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
)
func (u *userSer) UpdateNickNameByID(ctx core.Context, id int32, nickname string) (err error) {
model := user_demo_repo.NewModel()
model.Id = id
data := map[string]interface{}{
"nick_name": nickname,
}
err = model.Updates(u.db.GetDbW().WithContext(ctx.RequestContext()), data)
if err != nil {
return err
}
return nil
}
@@ -0,0 +1,34 @@
package user_service
import (
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/user_demo_repo"
"github.com/xinliangnote/go-gin-api/internal/pkg/cache"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/pkg/db"
)
var _ UserService = (*userSer)(nil)
type UserService interface {
// i 为了避免被其他包实现
i()
Create(ctx core.Context, user *CreateUserInfo) (id int32, err error)
UpdateNickNameByID(ctx core.Context, id int32, username string) (err error)
GetUserByUserName(ctx core.Context, username string) (user *user_demo_repo.UserDemo, err error)
Delete(ctx core.Context, id int32) (err error)
}
type userSer struct {
db db.Repo
cache cache.Repo
}
func NewUserService(db db.Repo, cache cache.Repo) UserService {
return &userSer{
db: db,
cache: cache,
}
}
func (u *userSer) i() {}
+24
View File
@@ -0,0 +1,24 @@
## example
```cassandraql
1.
query {
bySex(sex: "男") {
id
name
sex
mobile
}
}
2.
mutation {
updateUserMobile(data: {id: "1", mobile: "13299999999"}) {
id
name
sex
mobile
}
}
```
File diff suppressed because it is too large Load Diff
+102
View File
@@ -0,0 +1,102 @@
package handler
import (
"context"
"time"
"github.com/xinliangnote/go-gin-api/internal/graph/generated"
"github.com/xinliangnote/go-gin-api/internal/graph/resolvers"
"github.com/xinliangnote/go-gin-api/internal/pkg/cache"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/pkg/db"
"github.com/99designs/gqlgen/graphql"
"github.com/99designs/gqlgen/graphql/handler"
"github.com/99designs/gqlgen/graphql/handler/extension"
"github.com/99designs/gqlgen/graphql/handler/lru"
"github.com/99designs/gqlgen/graphql/handler/transport"
"github.com/99designs/gqlgen/graphql/playground"
"go.uber.org/zap"
)
var _ Gql = (*gql)(nil)
type Gql interface {
i()
Playground() core.HandlerFunc
Query() core.HandlerFunc
}
type gql struct {
logger *zap.Logger
db db.Repo
cache cache.Repo
}
func New(logger *zap.Logger, db db.Repo, cache cache.Repo) Gql {
return &gql{
logger: logger,
cache: cache,
db: db,
}
}
func (g *gql) i() {}
func (g *gql) Query() core.HandlerFunc {
// 定义扩展字段
extensions := make(map[string]interface{})
h := handler.New(generated.NewExecutableSchema(
resolvers.NewRootResolvers(g.logger, g.db, g.cache)),
)
h.AddTransport(transport.Websocket{
KeepAlivePingInterval: 10 * time.Second,
})
// 设置 transport
h.AddTransport(transport.Options{})
h.AddTransport(transport.GET{})
h.AddTransport(transport.POST{})
h.SetQueryCache(lru.New(1000))
// 启用侧边栏文档
h.Use(extension.Introspection{})
h.Use(extension.AutomaticPersistedQuery{
Cache: lru.New(100),
})
return func(c core.Context) {
var responses interface{}
defer func() {
// 设置 core log
c.GraphPayload(responses)
}()
// 设置 core trace_id
extensions["trace_id"] = c.Trace().ID()
h.AroundResponses(func(ctx context.Context, next graphql.ResponseHandler) *graphql.Response {
resp := next(ctx)
resp.Extensions = extensions
responses = resp
return resp
})
// 设置 core context
coreContext := context.WithValue(c.Request().Context(), resolvers.CoreContextKey, c)
h.ServeHTTP(c.ResponseWriter(), c.Request().WithContext(coreContext))
}
}
func (g *gql) Playground() core.HandlerFunc {
h := playground.Handler("GraphQL", "/graphql/query")
return func(c core.Context) {
h.ServeHTTP(c.ResponseWriter(), c.Request())
}
}
+15
View File
@@ -0,0 +1,15 @@
// Code generated by github.com/99designs/gqlgen, DO NOT EDIT.
package model
type User struct {
ID string `json:"id"`
Name string `json:"name"`
Sex string `json:"sex"`
Mobile string `json:"mobile"`
}
type UpdateUserMobileInput struct {
ID string `json:"id"`
Mobile string `json:"mobile"`
}
@@ -0,0 +1,29 @@
package resolvers
// THIS CODE IS A STARTING POINT ONLY. IT WILL NOT BE UPDATED WITH SCHEMA CHANGES.
import (
"context"
"github.com/xinliangnote/go-gin-api/internal/graph/generated"
"github.com/xinliangnote/go-gin-api/internal/graph/model"
)
type Resolver struct{}
func (r *mutationResolver) UpdateUserMobile(ctx context.Context, data model.UpdateUserMobileInput) (*model.User, error) {
panic("not implemented")
}
func (r *queryResolver) BySex(ctx context.Context, sex string) ([]*model.User, error) {
panic("not implemented")
}
// Mutation returns generated.MutationResolver implementation.
func (r *Resolver) Mutation() generated.MutationResolver { return &mutationResolver{r} }
// Query returns generated.QueryResolver implementation.
func (r *Resolver) Query() generated.QueryResolver { return &queryResolver{r} }
type mutationResolver struct{ *Resolver }
type queryResolver struct{ *Resolver }
+51
View File
@@ -0,0 +1,51 @@
package resolvers
import (
"context"
"github.com/xinliangnote/go-gin-api/internal/api/service/user_service"
"github.com/xinliangnote/go-gin-api/internal/graph/generated"
"github.com/xinliangnote/go-gin-api/internal/pkg/cache"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/pkg/db"
"go.uber.org/zap"
)
type coreCtxKeyType struct{ name string }
var CoreContextKey = coreCtxKeyType{"_core_context"}
type mutationResolver struct{ *Resolver }
type queryResolver struct{ *Resolver }
type Resolver struct {
logger *zap.Logger
cache cache.Repo
userService user_service.UserService
}
func NewRootResolvers(logger *zap.Logger, db db.Repo, cache cache.Repo) generated.Config {
c := generated.Config{
Resolvers: &Resolver{
logger: logger,
cache: cache,
userService: user_service.NewUserService(db, cache),
},
}
return c
}
func (r *Resolver) Mutation() generated.MutationResolver {
return &mutationResolver{r}
}
func (r *Resolver) Query() generated.QueryResolver {
return &queryResolver{r}
}
// getCoreContextByCtx 获取 core context
func (r *Resolver) getCoreContextByCtx(ctx context.Context) core.Context {
return ctx.Value(CoreContextKey).(core.Context)
}
+47
View File
@@ -0,0 +1,47 @@
package resolvers
import (
"context"
"github.com/xinliangnote/go-gin-api/internal/graph/model"
"github.com/pkg/errors"
)
func (r *queryResolver) BySex(ctx context.Context, sex string) ([]*model.User, error) {
if sex == "" {
return nil, errors.New("sex required")
}
//模拟数据
var users []*model.User
users = append(users, &model.User{ID: "1", Name: "Tom", Sex: sex, Mobile: "13266666666"})
users = append(users, &model.User{ID: "1", Name: "Jack", Sex: sex, Mobile: "13288888888"})
return users, nil
}
func (r *mutationResolver) UpdateUserMobile(ctx context.Context, data model.UpdateUserMobileInput) (*model.User, error) {
if data.ID == "" {
return nil, errors.New("id required")
}
if data.Mobile == "" {
return nil, errors.New("mobile required")
}
//模拟数据
user := new(model.User)
user.ID = data.ID
user.Mobile = data.Mobile
user.Sex = "男"
user.Name = "Jack"
//操作数据库
//userData, err := r.userService.GetUserByUserName(r.getCoreContextByCtx(ctx), "test_user")
//if err != nil {
// return nil, err
//}
return user, nil
}
+22
View File
@@ -0,0 +1,22 @@
type User {
id: String!
name: String!
sex: String!
mobile: String!
}
# 查询 集合
type Query {
bySex(sex: String!): [User!]
}
# 输入类型: 一般用户更改资源中的输入是列表对象,完成复杂任务
input updateUserMobileInput {
id:ID!
mobile: String!
}
# 更改或者创建 集合
type Mutation {
updateUserMobile(data: updateUserMobileInput!): User
}
+194
View File
@@ -0,0 +1,194 @@
package cache
import (
"time"
"github.com/xinliangnote/go-gin-api/configs"
"github.com/xinliangnote/go-gin-api/pkg/time_parse"
"github.com/xinliangnote/go-gin-api/pkg/trace"
"github.com/go-redis/redis/v7"
"github.com/pkg/errors"
)
type Option func(*option)
type Trace = trace.T
type option struct {
Trace *trace.Trace
Redis *trace.Redis
}
func newOption() *option {
return &option{}
}
var _ Repo = (*cacheRepo)(nil)
type Repo interface {
i()
Set(key, value string, ttl time.Duration, options ...Option) error
Get(key string, options ...Option) (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, options ...Option) int64
Close() error
}
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, options ...Option) error {
ts := time.Now()
opt := newOption()
defer func() {
if opt.Trace != nil {
opt.Redis.Timestamp = time_parse.CSTLayoutString()
opt.Redis.Handle = "set"
opt.Redis.Key = key
opt.Redis.Value = value
opt.Redis.TTL = ttl.Minutes()
opt.Redis.CostSeconds = time.Since(ts).Seconds()
opt.Trace.AppendRedis(opt.Redis)
}
}()
for _, f := range options {
f(opt)
}
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, options ...Option) (string, error) {
ts := time.Now()
opt := newOption()
defer func() {
if opt.Trace != nil {
opt.Redis.Timestamp = time_parse.CSTLayoutString()
opt.Redis.Handle = "get"
opt.Redis.Key = key
opt.Redis.CostSeconds = time.Since(ts).Seconds()
opt.Trace.AppendRedis(opt.Redis)
}
}()
for _, f := range options {
f(opt)
}
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, options ...Option) int64 {
ts := time.Now()
opt := newOption()
defer func() {
if opt.Trace != nil {
opt.Redis.Timestamp = time_parse.CSTLayoutString()
opt.Redis.Handle = "incr"
opt.Redis.Key = key
opt.Redis.CostSeconds = time.Since(ts).Seconds()
opt.Trace.AppendRedis(opt.Redis)
}
}()
for _, f := range options {
f(opt)
}
value, _ := c.client.Incr(key).Result()
return value
}
// Close close redis client
func (c *cacheRepo) Close() error {
return c.client.Close()
}
// WithTrace 设置trace信息
func WithTrace(t Trace) Option {
return func(opt *option) {
if t != nil {
opt.Trace = t.(*trace.Trace)
opt.Redis = new(trace.Redis)
}
}
}
+376
View File
@@ -0,0 +1,376 @@
package core
import (
"bytes"
stdctx "context"
"io/ioutil"
"net/http"
"net/url"
"strings"
"sync"
"github.com/xinliangnote/go-gin-api/pkg/errno"
"github.com/xinliangnote/go-gin-api/pkg/trace"
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"go.uber.org/zap"
)
type HandlerFunc func(c Context)
type Trace = trace.T
const (
_Alias = "_alias_"
_TraceName = "_trace_"
_LoggerName = "_logger_"
_BodyName = "_body_"
_PayloadName = "_payload_"
_GraphPayloadName = "_graph_payload_"
_UserID = "_user_id_"
_UserName = "_user_name_"
_AbortErrorName = "_abort_error_"
)
var contextPool = &sync.Pool{
New: func() interface{} {
return new(context)
},
}
func newContext(ctx *gin.Context) Context {
context := contextPool.Get().(*context)
context.ctx = ctx
return context
}
func releaseContext(ctx Context) {
c := ctx.(*context)
c.ctx = nil
contextPool.Put(c)
}
var _ Context = (*context)(nil)
type Context interface {
init()
// ShouldBindQuery 反序列化 querystring
// tag: `form:"xxx"` (注:不要写成 query)
ShouldBindQuery(obj interface{}) error
// ShouldBindPostForm 反序列化 postform (querystring会被忽略)
// tag: `form:"xxx"`
ShouldBindPostForm(obj interface{}) error
// ShouldBindForm 同时反序列化 querystring 和 postform;
// 当 querystring 和 postform 存在相同字段时,postform 优先使用。
// tag: `form:"xxx"`
ShouldBindForm(obj interface{}) error
// ShouldBindJSON 反序列化 postjson
// tag: `json:"xxx"`
ShouldBindJSON(obj interface{}) error
// ShouldBindURI 反序列化 path 参数(如路由路径为 /user/:name)
// tag: `uri:"xxx"`
ShouldBindURI(obj interface{}) error
// Redirect 重定向
Redirect(code int, location string)
// Trace 获取 Trace 对象
Trace() Trace
setTrace(trace Trace)
disableTrace()
// Logger 获取 Logger 对象
Logger() *zap.Logger
setLogger(logger *zap.Logger)
// Payload 正确返回
Payload(payload interface{})
getPayload() interface{}
// GraphPayload GraphQL返回值 与 api 返回结构不同
GraphPayload(payload interface{})
getGraphPayload() interface{}
// AbortWithError 错误返回
AbortWithError(err errno.Error)
abortError() errno.Error
// Header 获取 Header 对象
Header() http.Header
// GetHeader 获取 Header
GetHeader(key string) string
// SetHeader 设置 Header
SetHeader(key, value string)
// UserID 获取 JWT 中 UserID
UserID() int64
setUserID(userID int64)
// UserName 获取 JWT 中 UserName
UserName() string
setUserName(userName string)
// Alias 设置路由别名 for metrics uri
Alias() string
setAlias(path string)
// Request 获取 Request 对象
Request() *http.Request
// RawData 获取 Request.Body
RawData() []byte
// Method 获取 Request.Method
Method() string
// Host 获取 Request.Host
Host() string
// Path 获取 请求的路径 Request.URL.Path (不附带 querystring)
Path() string
// URI 获取 unescape 后的 Request.URL.RequestURI()
URI() string
// RequestContext 获取请求的 context (当 client 关闭后,会自动 canceled)
RequestContext() StdContext
// ResponseWriter 获取 ResponseWriter 对象
ResponseWriter() gin.ResponseWriter
}
type context struct {
ctx *gin.Context
}
type StdContext struct {
stdctx.Context
Trace
*zap.Logger
}
func (c *context) init() {
body, err := c.ctx.GetRawData()
if err != nil {
panic(err)
}
c.ctx.Set(_BodyName, body) // cache body是为了trace使用
c.ctx.Request.Body = ioutil.NopCloser(bytes.NewBuffer(body)) // re-construct req body
}
// ShouldBindQuery 反序列化querystring
// tag: `form:"xxx"` (注:不要写成query)
func (c *context) ShouldBindQuery(obj interface{}) error {
return c.ctx.ShouldBindWith(obj, binding.Query)
}
// ShouldBindPostForm 反序列化 postform (querystring 会被忽略)
// tag: `form:"xxx"`
func (c *context) ShouldBindPostForm(obj interface{}) error {
return c.ctx.ShouldBindWith(obj, binding.FormPost)
}
// ShouldBindForm 同时反序列化querystring和postform;
// 当querystring和postform存在相同字段时,postform优先使用。
// tag: `form:"xxx"`
func (c *context) ShouldBindForm(obj interface{}) error {
return c.ctx.ShouldBindWith(obj, binding.Form)
}
// ShouldBindJSON 反序列化postjson
// tag: `json:"xxx"`
func (c *context) ShouldBindJSON(obj interface{}) error {
return c.ctx.ShouldBindWith(obj, binding.JSON)
}
// ShouldBindURI 反序列化path参数(如路由路径为 /user/:name)
// tag: `uri:"xxx"`
func (c *context) ShouldBindURI(obj interface{}) error {
return c.ctx.ShouldBindUri(obj)
}
// Redirect 重定向
func (c *context) Redirect(code int, location string) {
c.ctx.Redirect(code, location)
}
func (c *context) Trace() Trace {
t, ok := c.ctx.Get(_TraceName)
if !ok || t == nil {
return nil
}
return t.(Trace)
}
func (c *context) setTrace(trace Trace) {
c.ctx.Set(_TraceName, trace)
}
func (c *context) disableTrace() {
c.setTrace(nil)
}
func (c *context) Logger() *zap.Logger {
logger, ok := c.ctx.Get(_LoggerName)
if !ok {
return nil
}
return logger.(*zap.Logger)
}
func (c *context) setLogger(logger *zap.Logger) {
c.ctx.Set(_LoggerName, logger)
}
func (c *context) getPayload() interface{} {
if payload, ok := c.ctx.Get(_PayloadName); ok != false {
return payload
}
return nil
}
func (c *context) Payload(payload interface{}) {
c.ctx.Set(_PayloadName, payload)
}
func (c *context) getGraphPayload() interface{} {
if payload, ok := c.ctx.Get(_GraphPayloadName); ok != false {
return payload
}
return nil
}
func (c *context) GraphPayload(payload interface{}) {
c.ctx.Set(_GraphPayloadName, payload)
}
func (c *context) Header() http.Header {
header := c.ctx.Request.Header
clone := make(http.Header, len(header))
for k, v := range header {
value := make([]string, len(v))
copy(value, v)
clone[k] = value
}
return clone
}
func (c *context) GetHeader(key string) string {
return c.ctx.GetHeader(key)
}
func (c *context) SetHeader(key, value string) {
c.ctx.Header(key, value)
}
func (c *context) UserID() int64 {
val, ok := c.ctx.Get(_UserID)
if !ok {
return 0
}
return val.(int64)
}
func (c *context) setUserID(userID int64) {
c.ctx.Set(_UserID, userID)
}
func (c *context) UserName() string {
val, ok := c.ctx.Get(_UserName)
if !ok {
return ""
}
return val.(string)
}
func (c *context) setUserName(userName string) {
c.ctx.Set(_UserName, userName)
}
func (c *context) AbortWithError(err errno.Error) {
if err != nil {
httpCode := err.GetHttpCode()
if httpCode == 0 {
httpCode = http.StatusInternalServerError
}
c.ctx.AbortWithStatus(httpCode)
c.ctx.Set(_AbortErrorName, err)
}
}
func (c *context) abortError() errno.Error {
err, _ := c.ctx.Get(_AbortErrorName)
return err.(errno.Error)
}
func (c *context) Alias() string {
path, ok := c.ctx.Get(_Alias)
if !ok {
return ""
}
return path.(string)
}
func (c *context) setAlias(path string) {
if path = strings.TrimSpace(path); path != "" {
c.ctx.Set(_Alias, path)
}
}
// Request 获取 Request
func (c *context) Request() *http.Request {
return c.ctx.Request
}
func (c *context) RawData() []byte {
body, ok := c.ctx.Get(_BodyName)
if !ok {
return nil
}
return body.([]byte)
}
// Method 请求的method
func (c *context) Method() string {
return c.ctx.Request.Method
}
// Host 请求的host
func (c *context) Host() string {
return c.ctx.Request.Host
}
// Path 请求的路径(不附带querystring)
func (c *context) Path() string {
return c.ctx.Request.URL.Path
}
// URI unescape后的uri
func (c *context) URI() string {
uri, _ := url.QueryUnescape(c.ctx.Request.URL.RequestURI())
return uri
}
// RequestContext 获取请求的 context (当client关闭后,会自动canceled)
func (c *context) RequestContext() StdContext {
return StdContext{
c.ctx.Request.Context(),
c.Trace(),
c.Logger(),
}
}
// ResponseWriter 获取 ResponseWriter
func (c *context) ResponseWriter() gin.ResponseWriter {
return c.ctx.Writer
}

Some files were not shown because too many files have changed in this diff Show More