🎉 初始化项目,基础功能已完成

This commit is contained in:
李寻欢 2021-08-20 14:59:13 +08:00
commit 174410ffa0
46 changed files with 1518 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
.idea
vendor
logs

15
config/alisms.go Normal file
View File

@ -0,0 +1,15 @@
package config
// 阿里云通讯配置信息
type aliSmsConfig struct {
AccessKey string
AccessSecret string
}
// InitAliSmsConfig 初始化阿里云通信配置
func InitAliSmsConfig() {
key := getEnvVal("ALI_SMS_KEY", "")
secret := getEnvVal("ALI_SMS_SECRET", "")
AliSmsConfig = aliSmsConfig{AccessKey: key, AccessSecret: secret}
}

46
config/config.go Normal file
View File

@ -0,0 +1,46 @@
package config
import (
"os"
"strconv"
)
var (
AliSmsConfig aliSmsConfig
RedisConfig redisConfig
)
// 从环境变量获取字符串类型值
func getEnvVal(key, defaultVal string) string {
val, exist := os.LookupEnv(key)
if !exist {
return defaultVal
}
return val
}
// 从环境变量获取数字类型值
func getEnvIntVal(key string, defaultVal int) int {
valStr, exist := os.LookupEnv(key)
if !exist {
return defaultVal
}
val, err := strconv.Atoi(valStr)
if err != nil {
return defaultVal
}
return val
}
// 从环境变量获取数字类型值
func getEnvBoolVal(key string, defaultVal bool) bool {
valStr, exist := os.LookupEnv(key)
if !exist {
return defaultVal
}
val, err := strconv.ParseBool(valStr)
if err != nil {
return defaultVal
}
return val
}

1
config/mysql.go Normal file
View File

@ -0,0 +1 @@
package config

28
config/redis.go Normal file
View File

@ -0,0 +1,28 @@
package config
// Redis配置
type redisConfig struct {
Host string // Redis主机
Port string // Redis端口
Password string // Redis密码
Db int // Redis库
}
// InitRedisConfig 初始化Redis配置
func InitRedisConfig() {
// RedisHost Redis主机
host := getEnvVal("REDIS_HOST", "redis")
// RedisPort Redis端口
port := getEnvVal("REDIS_PORT", "6379")
// RedisPassword Redis密码
password := getEnvVal("REDIS_PWD", "")
// Redis库
db := getEnvIntVal("REDIS_DB", 0)
RedisConfig = redisConfig{
Host: host,
Port: port,
Password: password,
Db: db,
}
}

94
controller/login.go Normal file
View File

@ -0,0 +1,94 @@
package controller
import (
"fmt"
"github.com/gin-gonic/gin"
. "go_api_tmpl/core"
. "go_api_tmpl/global"
"go_api_tmpl/repository"
"go_api_tmpl/utils"
"math/rand"
"net/http"
"time"
)
// GetSmsCodeController 获取登录验证码
func GetSmsCodeController(ctx *gin.Context) {
type requestBody struct {
Phone string `json:"phone"`
}
// 取出并判断设备指纹是否存在
deviceId := ctx.GetHeader("di")
// 取出手机号
var params requestBody
err := ctx.ShouldBindJSON(&params)
if err != nil {
Log.Errorf("[发送短信验证码]读取参数错误: %v", err.Error())
R(ctx).FailWithMessage("参数错误")
return
}
phone := params.Phone
Log.Debugf("设备指纹: %v -----> 手机号: %v", deviceId, phone)
// 判断设备是否发送过
phoneKey := fmt.Sprintf("auth:sms:phone:%v", phone)
flag := RedisConn.ExistKey(phoneKey)
if flag {
R(ctx).FailWithMessage("发送间隔过短,请稍后重试")
return
}
// 开始发送短信
rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
code := fmt.Sprintf("%06v", rnd.Int31n(1000000))
// 发送
flag = AliSmsBot.SentSms(phone, code)
if !flag {
R(ctx).FailWithMessage("验证码发送失败,请稍后重试")
return
}
err = RedisConn.SetWithTimeout(phoneKey, code, "600")
if err != nil {
R(ctx).FailWithMessage("验证码发送失败,请稍后重试")
return
}
R(ctx).OkWithMessage("验证码已发送,有效期十分钟,请尽快使用")
}
// LoginController 登录
func LoginController(ctx *gin.Context) {
type loginParams struct {
Phone string
Code string
}
var params loginParams
if err := ctx.ShouldBindJSON(&params); err != nil {
R(ctx).FailWithMessage("参数错误")
return
}
phoneKey := fmt.Sprintf("auth:sms:phone:%v", params.Phone)
if !RedisConn.ExistKey(phoneKey) {
R(ctx).FailWithMessageAndCode("未发送验证码或已过期", http.StatusForbidden)
return
}
cacheCode, err := RedisConn.GetData(phoneKey)
if err != nil {
R(ctx).FailWithMessage("系统异常")
return
}
if cacheCode != params.Code {
R(ctx).FailWithMessageAndCode("验证码错误", http.StatusForbidden)
return
}
// 验证通过,查询数据库数据
ur := repository.NewUserRepository()
dbUser, err := ur.GetUserByPhone(params.Phone)
if err != nil {
Log.Errorf("用户数据获取失败: %v", err.Error())
R(ctx).FailWithMessage("系统错误")
return
}
token := utils.GenerateToken(dbUser.ID)
result := map[string]interface{}{}
result["token"] = token
R(ctx).OkDetailed(result, "登录成功")
}

22
core/error_handle.go Normal file
View File

@ -0,0 +1,22 @@
package core
import (
"github.com/gin-gonic/gin"
"net/http"
)
// CustomHTTPErrorHandler 默认全局异常处理
func CustomHTTPErrorHandler() gin.HandlerFunc {
return func(ctx *gin.Context) {
R(ctx).FailWithMessage("不知道出了啥异常")
ctx.Abort()
}
}
// NotFoundErrorHandler 404异常处理
func NotFoundErrorHandler() gin.HandlerFunc {
return func(ctx *gin.Context) {
R(ctx).FailWithMessageAndCode("请求接口不存在", http.StatusNotFound)
ctx.Abort()
}
}

74
core/response.go Normal file
View File

@ -0,0 +1,74 @@
package core
import (
"github.com/gin-gonic/gin"
"net/http"
)
// 返回数据包装
type response struct {
Code int `json:"code"`
Data interface{} `json:"data"`
Msg string `json:"msg"`
}
type rs struct {
ctx *gin.Context
}
// 定义状态码
const (
ERROR = http.StatusInternalServerError
SUCCESS = http.StatusOK
)
func R(ctx *gin.Context) *rs {
return &rs{ctx: ctx}
}
// Result 手动组装返回结果
func (r rs) Result(code int, data interface{}, msg string) {
if data == nil {
data = map[string]interface{}{}
}
r.ctx.JSON(code, response{
code,
data,
msg,
})
}
// Ok 返回无数据的成功
func (r rs) Ok() {
r.Result(SUCCESS, nil, "操作成功")
}
// OkWithMessage 返回自定义成功的消息
func (r rs) OkWithMessage(message string) {
r.Result(SUCCESS, nil, message)
}
// OkWithData 自定义内容的成功返回
func (r rs) OkWithData(data interface{}) {
r.Result(SUCCESS, data, "操作成功")
}
// OkDetailed 自定义消息和内容的成功返回
func (r rs) OkDetailed(data interface{}, message string) {
r.Result(SUCCESS, data, message)
}
// Fail 返回默认失败
func (r rs) Fail() {
r.Result(ERROR, nil, "操作失败")
}
// FailWithMessage 返回默认状态码自定义消息的失败
func (r rs) FailWithMessage(message string) {
r.Result(ERROR, nil, message)
}
// FailWithMessageAndCode 返回自定义消息和状态码的失败
func (r rs) FailWithMessageAndCode(message string, code int) {
r.Result(code, nil, message)
}

28
global/alisms.go Normal file
View File

@ -0,0 +1,28 @@
package global
import (
"fmt"
"github.com/aliyun/alibaba-cloud-sdk-go/services/dysmsapi"
"go_api_tmpl/config"
)
type aliSmsBot struct{}
// SentSms 发送短信验证码
func (b aliSmsBot) SentSms(phone string, code string) bool {
client, err := dysmsapi.NewClientWithAccessKey("ap-northeast-1", config.AliSmsConfig.AccessKey, config.AliSmsConfig.AccessSecret)
request := dysmsapi.CreateSendSmsRequest()
request.Scheme = "https"
request.PhoneNumbers = phone
request.SignName = "悟空单车"
request.TemplateCode = "SMS_34310120"
request.TemplateParam = fmt.Sprintf("{\"name\":\"%v\",\"code\":\"%v\"}", phone, code)
response, err := client.SendSms(request)
if err != nil {
Log.Errorf("验证码发送失败: %v", err.Error())
return false
}
return response.IsSuccess()
}

8
global/error.go Normal file
View File

@ -0,0 +1,8 @@
package global
// CheckError 处理错误
func CheckError(err error, msgTmpl string) {
if err != nil {
Log.Panicf(msgTmpl, err)
}
}

15
global/global.go Normal file
View File

@ -0,0 +1,15 @@
package global
import (
"github.com/casbin/casbin/v2"
"go.uber.org/zap"
"gorm.io/gorm"
)
var (
MySQLConn *gorm.DB
RedisConn RedisClient
AliSmsBot aliSmsBot
Enforcer *casbin.Enforcer
Log *zap.SugaredLogger
)

32
global/logger.go Normal file
View File

@ -0,0 +1,32 @@
package global
import (
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"os"
"time"
)
// InitLogger 初始化日志工具
func InitLogger() {
// 配置 sugaredLogger
writer := zapcore.AddSync(os.Stdout)
// 自定义时间输出格式
customTimeEncoder := func(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
enc.AppendString("[" + t.Format("2006-01-02 15:04:05.000") + "]")
}
// 格式相关的配置
encoderConfig := zap.NewProductionEncoderConfig()
// 修改时间戳的格式
encoderConfig.EncodeTime = customTimeEncoder
// 日志级别使用大写带颜色显示
encoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
encoder := zapcore.NewConsoleEncoder(encoderConfig)
// 将日志级别设置为 DEBUG
core := zapcore.NewCore(encoder, writer, zapcore.DebugLevel)
// 增加 caller 信息
logger := zap.New(core, zap.AddCaller())
Log = logger.Sugar()
}

36
global/mysql.go Normal file
View File

@ -0,0 +1,36 @@
package global
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"log"
"os"
"time"
)
// InitMySQLClient 初始化MySQL连接
func InitMySQLClient() {
USER := "casbin"
PASS := "casbin123"
HOST := "192.168.3.9"
PORT := "3306"
DBNAME := "casbin_demo"
newLogger := logger.New(
log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer
logger.Config{
SlowThreshold: time.Second, // Slow SQL threshold
LogLevel: logger.Info, // Log level
Colorful: true, // Disable color
},
)
url := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local", USER, PASS, HOST, PORT, DBNAME)
conn, err := gorm.Open(mysql.Open(url), &gorm.Config{Logger: newLogger})
if err != nil {
Log.Panicf("初始化MySQL连接失败, 错误信息: %v", err)
}
MySQLConn = conn
}

57
global/redis.go Normal file
View File

@ -0,0 +1,57 @@
package global
import (
"errors"
"github.com/garyburd/redigo/redis"
)
// RedisClient Redis连接对象
type RedisClient struct {
Client redis.Conn
}
// GetData 获取数据
func (r *RedisClient) GetData(key string) (string, error) {
return redis.String(r.Client.Do("get", key))
}
// GetKeys 获取key列表
func (r *RedisClient) GetKeys(key string) ([]string, error) {
return redis.Strings(r.Client.Do("keys", key))
}
// ExistKey 判断是否存在key
func (r *RedisClient) ExistKey(key string) bool {
keys, err := redis.Strings(r.Client.Do("keys", key))
if err != nil {
return false
}
return len(keys) > 0
}
// Set 保存数据
func (r *RedisClient) Set(key string, value string) error {
_, err := r.Client.Do("set", key, value)
if err != nil {
return errors.New("Redis保存数据失败")
}
return nil
}
// SetWithTimeout 保存带过期时间的数据(单位:秒)
func (r *RedisClient) SetWithTimeout(key string, value string, timeout string) error {
_, err := r.Client.Do("set", key, value, "EX", timeout)
if err != nil {
return errors.New("Redis保存数据失败")
}
return nil
}
// Del 根据key删除Redis数据
func (r *RedisClient) Del(key string) error {
_, err := r.Client.Do("DEL", key)
if err != nil {
return err
}
return nil
}

16
go.mod Normal file
View File

@ -0,0 +1,16 @@
module go_api_tmpl
go 1.16
require (
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1238
github.com/casbin/casbin/v2 v2.28.3
github.com/casbin/gorm-adapter/v3 v3.3.3
github.com/garyburd/redigo v1.6.2
github.com/gin-gonic/gin v1.7.4
github.com/golang-jwt/jwt v3.2.2+incompatible
go.uber.org/zap v1.19.0
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
gorm.io/driver/mysql v1.1.2
gorm.io/gorm v1.21.13
)

277
go.sum Normal file
View File

@ -0,0 +1,277 @@
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible h1:1G1pk05UrOh0NlF1oeaaix1x8XzrfjIDK47TY0Zehcw=
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1238 h1:K0l7Nb8xi2nb/j6evXwGofebgW+6XT/AuVOEuWGI344=
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1238/go.mod h1:pUKYbK5JQ+1Dfxk80P0qxGqe5dkxDoabbZS7zOcouyA=
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/casbin/casbin/v2 v2.28.3 h1:iHxxEsNHwSciRoYh+54etVUA8AXKS9OKzNy6/39UWvY=
github.com/casbin/casbin/v2 v2.28.3/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg=
github.com/casbin/gorm-adapter/v3 v3.3.3 h1:aO3ixIs+FsGh/in+iXXY8rLpdmeucs+s50T/RI2Rsdc=
github.com/casbin/gorm-adapter/v3 v3.3.3/go.mod h1:z0J/CpAznL6MyXMPnzltbLBI6lykprGc1rusy+vMJps=
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
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-20200428022330-06a60b6afbbc h1:VRRKCwnzqk8QCaRC4os14xoKDdbHqqlJtJA0oc1ZAjg=
github.com/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
github.com/garyburd/redigo v1.6.2 h1:yE/pwKCrbLpLpQICzYTeZ7JsTA/C53wFTJHaEtRqniM=
github.com/garyburd/redigo v1.6.2/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
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.7.4 h1:QmUZXrvJ9qZ3GfWvQ+2wnW/1ePrTEJqPKMYEU3lD/DM=
github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
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.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE=
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
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/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/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/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0=
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
github.com/jackc/pgconn v1.4.0/go.mod h1:Y2O3ZDF0q4mMacyWV3AstPJpeHXWGEetiFttmq5lahk=
github.com/jackc/pgconn v1.5.0/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI=
github.com/jackc/pgconn v1.5.1-0.20200601181101-fa742c524853/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI=
github.com/jackc/pgconn v1.8.0 h1:FmjZ0rOyXTr1wfWs45i4a9vjnjWUAGpMuQLD9OSs+lw=
github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2 h1:JVX6jT/XfzNqIjye4717ITLaNwV9mWbJx0dLCpcRzdA=
github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A=
github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
github.com/jackc/pgproto3/v2 v2.0.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgproto3/v2 v2.0.6 h1:b1105ZGEMFe7aCvrT1Cca3VoVb4ZFMaFJLJcg/3zD+8=
github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgservicefile v0.0.0-20200307190119-3430c5407db8/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg=
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
github.com/jackc/pgtype v1.2.0/go.mod h1:5m2OfMh1wTK7x+Fk952IDmI4nw3nPrvtQdM0ZT4WpC0=
github.com/jackc/pgtype v1.3.1-0.20200510190516-8cd94a14c75a/go.mod h1:vaogEUkALtxZMCH411K+tKzNpwzCKU+AnPzBKZ+I+Po=
github.com/jackc/pgtype v1.3.1-0.20200606141011-f6355165a91c/go.mod h1:cvk9Bgu/VzJ9/lxTO5R5sf80p0DiucVtN7ZxvaC4GmQ=
github.com/jackc/pgtype v1.6.2 h1:b3pDeuhbbzBYcg5kwNmNDun4pFUD/0AAr1kLXZLeNt8=
github.com/jackc/pgtype v1.6.2/go.mod h1:JCULISAZBFGrHaOXIIFiyfzW5VY0GRitRr8NeJsrdig=
github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
github.com/jackc/pgx/v4 v4.5.0/go.mod h1:EpAKPLdnTorwmPUUsqrPxy5fphV18j9q3wrfRXgo+kA=
github.com/jackc/pgx/v4 v4.6.1-0.20200510190926-94ba730bb1e9/go.mod h1:t3/cdRQl6fOLDxqtlyhe9UWgfIi9R8+8v8GKV5TRA/o=
github.com/jackc/pgx/v4 v4.6.1-0.20200606145419-4e5062306904/go.mod h1:ZDaNWkt9sW1JMiNn0kdYBaLelIhw7Pg4qd+Vk6tw7Hg=
github.com/jackc/pgx/v4 v4.10.1 h1:/6Q3ye4myIj6AaplUm+eRcz4OhK9HAvFf4ePsG40LJY=
github.com/jackc/pgx/v4 v4.10.1/go.mod h1:QlrWebbs3kqEZPHCTGyxecvzG6tvIsYu+A5b1raylkA=
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.1.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
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.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jinzhu/now v1.1.2 h1:eVKgfIdy9b6zbWBMgFpfDPoAMifwSZagU9HmEU6zgiI=
github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/json-iterator/go v1.1.5/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/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/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/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.8.0 h1:9xohqzkUwzR4Ga4ivdTcawVS89YSDVxXMa3xJX3cGzg=
github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
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.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc h1:jUIKcSPO9MoMJBbEoyE/RJoE8vz7Mb8AjvifMMwSyvY=
github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
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 v0.0.0-20190330032615-68dc04aab96a h1:pa8hGb/2YqsZKovtsgrwcDH1RZhVbTKCjLp47XpqCDs=
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0=
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.19.0 h1:mZQZefskPPCMIBCSEH0v2/iUqqLrYtaeqwD6FUGUnFE=
go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
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-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/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/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/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-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/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-20191108193012-7d206e10da11 h1:Yq9t9jnGoR+dBuitxdo9l6Q7xh/zOyNnYUtDKaQ3x0E=
golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
gopkg.in/ini.v1 v1.42.0 h1:7N3gPTt50s8GuLortA00n8AqRTk75qOP98+mTPpgzRk=
gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/mysql v1.0.3/go.mod h1:twGxftLBlFgNVNakL7F+P/x9oYqoymG3YYT8cAfI9oI=
gorm.io/driver/mysql v1.1.2 h1:OofcyE2lga734MxwcCW9uB4mWNXMr50uaGRVwQL2B0M=
gorm.io/driver/mysql v1.1.2/go.mod h1:4P/X9vSc3WTrhTLZ259cpFd6xKNYiSSdSZngkSBGIMM=
gorm.io/driver/postgres v1.0.8 h1:PAgM+PaHOSAeroTjHkCHCBIHHoBIf9RgPWGo8dF2DA8=
gorm.io/driver/postgres v1.0.8/go.mod h1:4eOzrI1MUfm6ObJU/UcmbXyiHSs8jSwH95G5P5dxcAg=
gorm.io/driver/sqlserver v1.0.4 h1:V15fszi0XAo7fbx3/cF50ngshDSN4QT0MXpWTylyPTY=
gorm.io/driver/sqlserver v1.0.4/go.mod h1:ciEo5btfITTBCj9BkoUVDvgQbUdLWQNqdFY5OGuGnRg=
gorm.io/gorm v1.20.0/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
gorm.io/gorm v1.20.4/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
gorm.io/gorm v1.20.11/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
gorm.io/gorm v1.20.12/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
gorm.io/gorm v1.21.9/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0=
gorm.io/gorm v1.21.12/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0=
gorm.io/gorm v1.21.13 h1:JU5A4yVemRjdMndJ0oZU7VX+Nr2ICE3C60U5bgR6mHE=
gorm.io/gorm v1.21.13/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0=
gorm.io/plugin/dbresolver v1.1.0 h1:cegr4DeprR6SkLIQlKhJLYxH8muFbJ4SmnojXvoeb00=
gorm.io/plugin/dbresolver v1.1.0/go.mod h1:tpImigFAEejCALOttyhWqsy4vfa2Uh/vAUVnL5IRF7Y=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=

14
handle/apis.go Normal file
View File

@ -0,0 +1,14 @@
package handle
import (
"github.com/gin-gonic/gin"
"go_api_tmpl/global"
"go_api_tmpl/repository"
)
// SaveAllRoute 保存入库所有接口路由信息
func SaveAllRoute(routes gin.RoutesInfo) {
global.Log.Debugf("待入库路由数量: %v", len(routes))
ar := repository.NewApisRepository()
ar.SaveAllApi(routes)
}

1
handle/request_log.go Normal file
View File

@ -0,0 +1 @@
package handle

33
initialization/casbin.go Normal file
View File

@ -0,0 +1,33 @@
package initialization
import (
"fmt"
"github.com/casbin/casbin/v2"
"github.com/casbin/casbin/v2/model"
gormAdapter "github.com/casbin/gorm-adapter/v3"
"go_api_tmpl/global"
)
// Casbin 初始化Casbin的RABC模型
func Casbin() {
// Initialize casbin adapter
adapter, err := gormAdapter.NewAdapterByDBUseTableName(global.MySQLConn, "", "t_role_rule")
if err != nil {
panic(fmt.Sprintf("failed to initialize casbin adapter: %v", err))
}
// 初始化模型
model := model.NewModel()
model.AddDef("r", "r", "sub, obj, act")
model.AddDef("p", "p", "sub, obj, act")
model.AddDef("g", "g", "_, _")
model.AddDef("e", "e", "some(where (p.eft == allow))")
// 匹配规则(下面的规则适用与根据Gin的Route的Path和Method做校验最后那个r.sub == 1是用来判断超级管理员的)
model.AddDef("m", "m", "g(r.sub, p.sub) && keyMatch2(r.obj, p.obj) && regexMatch(r.act, p.act) || r.sub == \"1\"")
enf, err := casbin.NewEnforcer(model, adapter)
if err != nil {
panic(fmt.Sprintf("failed to create casbin enforcer: %v", err))
}
global.Enforcer = enf
}

48
initialization/db.go Normal file
View File

@ -0,0 +1,48 @@
package initialization
import (
. "go_api_tmpl/global"
"go_api_tmpl/repository"
)
// DatabaseTable 初始化数据库表结构
func DatabaseTable() {
// 初始化菜单表
Log.Debug("开始初始化接口路由表")
ar := repository.NewApisRepository()
err := ar.Migrate()
CheckError(err, "接口路由表初始化失败: %v")
Log.Debug("开始初始化用户表")
ur := repository.NewUserRepository()
err = ur.Migrate()
CheckError(err, "用户表初始化失败: %v")
Log.Debug("开始初始化角色表")
rr := repository.NewRoleRepository()
err = rr.Migrate()
CheckError(err, "角色表初始化失败: %v")
Log.Debug("开始初始化菜单表")
mr := repository.NewMenuRepository()
err = mr.Migrate()
CheckError(err, "菜单表初始化失败: %v")
Log.Debug("开始初始化用户角色表")
urr := repository.NewUserRoleRepository()
err = urr.Migrate()
CheckError(err, "用户角色表初始化失败: %v")
Log.Debug("开始初始化角色菜单表")
rmr := repository.NewRoleMenuRepository()
err = rmr.Migrate()
CheckError(err, "角色菜单表初始化失败: %v")
Log.Debug("开始初始化接口访问记录表")
rlr := repository.NewRequestLogRepository()
err = rlr.Migrate()
CheckError(err, "接口访问记录表初始化失败: %v")
// TODO 初始化其他表
Log.Debug("所有数据库表初始化完成")
}

14
initialization/init.go Normal file
View File

@ -0,0 +1,14 @@
package initialization
import "go_api_tmpl/config"
func Init() {
// 初始化数据库表
DatabaseTable()
// 初始化Casbin
Casbin()
// 初始化Redis
InitRedisConn()
// 阿里云短信配置
config.InitAliSmsConfig()
}

35
initialization/redis.go Normal file
View File

@ -0,0 +1,35 @@
package initialization
import (
"fmt"
"github.com/garyburd/redigo/redis"
"go_api_tmpl/config"
"go_api_tmpl/global"
"time"
)
// InitRedisConn 初始化Redis连接对象
func InitRedisConn() {
// 读取配置
config.InitRedisConfig()
// 初始化连接
conn, err := redis.Dial("tcp",
// Redis连接信息
fmt.Sprintf("%s:%s", config.RedisConfig.Host, config.RedisConfig.Port),
// 密码
redis.DialPassword(config.RedisConfig.Password),
// 默认使用数据库
redis.DialDatabase(config.RedisConfig.Db),
redis.DialKeepAlive(1*time.Second),
redis.DialConnectTimeout(5*time.Second),
redis.DialReadTimeout(1*time.Second),
redis.DialWriteTimeout(1*time.Second))
if err != nil {
global.Log.Panicf("Redis初始化连接失败: %v", err.Error())
} else {
global.Log.Info("Redis连接初始化成功")
global.RedisConn = global.RedisClient{
Client: conn,
}
}
}

36
main.go Normal file
View File

@ -0,0 +1,36 @@
package main
import (
"github.com/gin-gonic/gin"
"go_api_tmpl/core"
"go_api_tmpl/global"
"go_api_tmpl/handle"
"go_api_tmpl/initialization"
"go_api_tmpl/middleware"
"go_api_tmpl/route"
)
func main() {
// 初始化日志工具
global.InitLogger()
// 初始化数据库连接
global.InitMySQLClient()
// 初始化相关组件
initialization.Init()
// 初始化Gin
//app := gin.New()
//app.Use(middleware.ZapLogger(), gin.Recovery())
app := gin.Default()
// 定义全局异常处理
app.NoRoute(core.NotFoundErrorHandler())
// 保存接口访问记录
app.Use(middleware.SaveRequestLog(), middleware.CheckDeviceId())
// 初始化路由
route.InitRoute(app)
// 路由初始化完毕,入库所有接口
handle.SaveAllRoute(app.Routes())
// 启动项目
_ = app.Run(":8888")
}

47
middleware/casbin.go Normal file
View File

@ -0,0 +1,47 @@
package middleware
import (
"fmt"
"github.com/gin-gonic/gin"
"go_api_tmpl/core"
. "go_api_tmpl/global"
"net/http"
)
// AuthorityVerify 权限验证中间件
func AuthorityVerify() gin.HandlerFunc {
return func(c *gin.Context) {
// 取出用户ID
userId, existed := c.Get("userId")
if !existed {
core.R(c).FailWithMessageAndCode("请先登录", http.StatusUnauthorized)
c.Abort()
return
}
Log.Info(userId)
// 从数据库加载权限规则数据
err := Enforcer.LoadPolicy()
if err != nil {
core.R(c).FailWithMessage("权限加载失败")
c.Abort()
return
}
// 取出Path和Method
p := c.Request.URL.Path
m := c.Request.Method
// 验证权限
ok, err := Enforcer.Enforce(fmt.Sprint(userId), p, m)
if err != nil {
core.R(c).FailWithMessage("权限验证失败")
c.Abort()
return
}
if !ok {
core.R(c).FailWithMessage("权限不足")
c.Abort()
return
}
c.Next()
}
}

41
middleware/jwt.go Normal file
View File

@ -0,0 +1,41 @@
package middleware
import (
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt"
"go_api_tmpl/core"
"go_api_tmpl/global"
"go_api_tmpl/utils"
"net/http"
)
// AuthorizeJWT 验证JWT
func AuthorizeJWT() gin.HandlerFunc {
return func(ctx *gin.Context) {
const BearerSchema string = "Bearer "
authHeader := ctx.GetHeader("Authorization")
if authHeader == "" {
core.R(ctx).FailWithMessageAndCode("请先登录", http.StatusUnauthorized)
ctx.Abort()
return
}
tokenString := authHeader[len(BearerSchema):]
if token, err := utils.ValidateToken(tokenString); err != nil {
global.Log.Errorf("Token校验失败: %v ------> %v", tokenString, err.Error())
core.R(ctx).FailWithMessageAndCode("Token校验失败", http.StatusUnauthorized)
return
} else {
if claims, ok := token.Claims.(jwt.MapClaims); !ok {
ctx.AbortWithStatus(http.StatusUnauthorized)
} else {
if token.Valid {
ctx.Set("userId", claims["userId"])
} else {
ctx.AbortWithStatus(http.StatusUnauthorized)
}
}
}
}
}

39
middleware/logger.go Normal file
View File

@ -0,0 +1,39 @@
package middleware
import (
"github.com/gin-gonic/gin"
"go_api_tmpl/global"
"time"
)
// ZapLogger 接收gin框架的路由日志
func ZapLogger() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
path := c.Request.URL.Path
query := c.Request.URL.RawQuery
c.Next()
cost := time.Since(start)
//global.Log.Info(path,
// zap.Int("status", c.Writer.Status()),
// zap.String("method", c.Request.Method),
// zap.String("path", path),
// zap.String("query", query),
// zap.String("ip", c.ClientIP()),
// zap.String("user-agent", c.Request.UserAgent()),
// zap.String("errors", c.Errors.ByType(gin.ErrorTypePrivate).String()),
// zap.Duration("cost", cost),
//)
logTmpl := `
=================================================================================
Path: %v
Param: %v
Method: %v
IP: %v
Cost: %v
=================================================================================
`
global.Log.Debugf(logTmpl, path, query, c.Request.Method, c.ClientIP(), cost)
}
}

76
middleware/request.go Normal file
View File

@ -0,0 +1,76 @@
package middleware
import (
"bytes"
"github.com/gin-gonic/gin"
"go_api_tmpl/core"
"go_api_tmpl/model"
"go_api_tmpl/repository"
"io/ioutil"
"net/http"
"time"
)
// CheckDeviceId 检查是否存在设备指纹
func CheckDeviceId() gin.HandlerFunc {
return func(ctx *gin.Context) {
// 取出设备指纹
deviceId := ctx.Request.Header.Get("di")
if deviceId == "" {
// 设备指纹不存在
core.R(ctx).FailWithMessageAndCode("请求不合法", http.StatusBadRequest)
ctx.Abort()
return
}
ctx.Next()
}
}
// SaveRequestLog 保存所有请求接口日志
func SaveRequestLog() gin.HandlerFunc {
return func(ctx *gin.Context) {
// 开始时间
start := time.Now()
// 取出接口
path := ctx.Request.URL.Path
// 取出参数
query := ctx.Request.URL.RawQuery
// body参数
body, err := ctx.GetRawData()
bodyStr := ""
if err == nil {
bodyStr = string(body)
ctx.Request.Body = ioutil.NopCloser(bytes.NewBuffer(body))
}
// form参数
form := ctx.Request.Form.Encode()
// 取出请求方式
method := ctx.Request.Method
// 取出设备指纹
deviceId := ctx.Request.Header.Get("di")
// 取出IP
ip := ctx.ClientIP()
// UA
ua := ctx.Request.UserAgent()
ctx.Next()
// 计算耗时
cost := time.Since(start).Milliseconds()
// 组装实体
rlog := model.RequestLog{
Method: method,
Path: path,
Param: query,
Body: bodyStr,
Form: form,
Cost: cost,
StatusCode: ctx.Writer.Status(),
DeviceId: deviceId,
Ip: ip,
UserAgent: ua,
}
//global.Log.Debugf("接口请求结果: %v", rlog)
rlr := repository.NewRequestLogRepository()
rlr.SaveLog(&rlog)
}
}

11
model/apis.go Normal file
View File

@ -0,0 +1,11 @@
package model
type Apis struct {
ID uint `json:"id" gorm:"primarykey"`
Path string `json:"path" `
Method string `json:"method" `
}
func (Apis) TableName() string {
return "t_api_route"
}

11
model/menu.go Normal file
View File

@ -0,0 +1,11 @@
package model
import "gorm.io/gorm"
type Menu struct {
gorm.Model
}
func (Menu) TableName() string {
return "t_menu"
}

25
model/request_log.go Normal file
View File

@ -0,0 +1,25 @@
package model
import (
"time"
)
// RequestLog 请求日志实体
type RequestLog struct {
ID uint `gorm:"primarykey"`
CreatedAt time.Time
Method string `json:"method" gorm:"type:varchar(10) not null comment '请求方式';"` // 请求方式
Path string `json:"path" gorm:"type:varchar(100) not null comment '请求接口';"` // 请求接口
Param string `json:"param" gorm:"type:text comment 'Query参数';"` // Query参数
Body string `json:"body" gorm:"type:text comment 'Body参数';"` // Body参数
Form string `json:"form" gorm:"type:text comment 'Form参数';"` // Form参数
Cost int64 `json:"cost" gorm:"type:int comment 'Query参数';"` // 耗时(微秒)
StatusCode int `json:"status_code" gorm:"type:int comment '返回状态码';"` // 返回状态码
DeviceId string `json:"device_id" gorm:"type:varchar(50) not null comment '设备指纹';"` // 设备指纹
Ip string `json:"ip" gorm:"type:varchar(50) comment 'IP地址';"` // IP地址
UserAgent string `json:"user_agent" gorm:"type:varchar(500) comment 'UserAgent';"` // UA
}
func (RequestLog) TableName() string {
return "t_request_log"
}

11
model/role.go Normal file
View File

@ -0,0 +1,11 @@
package model
import "gorm.io/gorm"
type Role struct {
gorm.Model
}
func (Role) TableName() string {
return "t_role"
}

11
model/role_menu.go Normal file
View File

@ -0,0 +1,11 @@
package model
import "gorm.io/gorm"
type RoleMenu struct {
gorm.Model
}
func (RoleMenu) TableName() string {
return "t_role_menu"
}

12
model/user.go Normal file
View File

@ -0,0 +1,12 @@
package model
import "gorm.io/gorm"
type User struct {
gorm.Model
Phone string
}
func (User) TableName() string {
return "t_user"
}

11
model/user_role.go Normal file
View File

@ -0,0 +1,11 @@
package model
import "gorm.io/gorm"
type UserRole struct {
gorm.Model
}
func (UserRole) TableName() string {
return "t_user_role"
}

5
readme.md Normal file
View File

@ -0,0 +1,5 @@
### go_api_tmpl
### 我是谁
基于`Gin`、`Casbin`的权限框架的模板

View File

@ -0,0 +1,35 @@
package repository
import (
"github.com/gin-gonic/gin"
. "go_api_tmpl/global"
"go_api_tmpl/model"
)
type apisRepository struct{}
type ApisRepository interface {
SaveAllApi(info gin.RoutesInfo)
Migrate() error
}
// NewApisRepository 新建实例
func NewApisRepository() ApisRepository {
return apisRepository{}
}
// SaveAllApi 保存所有gin的路由
func (ar apisRepository) SaveAllApi(routes gin.RoutesInfo) {
for _, route := range routes {
m := model.Apis{Path: route.Path, Method: route.Method}
err := MySQLConn.FirstOrCreate(&m, &m).Error
if err != nil {
Log.Infof("保存菜单错误: %v", err)
}
}
}
// Migrate 初始化数据库结构
func (ar apisRepository) Migrate() error {
return MySQLConn.AutoMigrate(&model.Apis{})
}

View File

@ -0,0 +1,22 @@
package repository
import (
"go_api_tmpl/global"
"go_api_tmpl/model"
)
type menuRepository struct{}
type MenuRepository interface {
Migrate() error
}
// NewMenuRepository 新建实例
func NewMenuRepository() MenuRepository {
return menuRepository{}
}
// Migrate 初始化数据库结构
func (r menuRepository) Migrate() error {
return global.MySQLConn.AutoMigrate(&model.Menu{})
}

View File

@ -0,0 +1,30 @@
package repository
import (
"go_api_tmpl/global"
"go_api_tmpl/model"
)
type requestLogRepository struct{}
type RequestLogRepository interface {
SaveLog(log *model.RequestLog)
Migrate() error
}
// NewRequestLogRepository 新建实例
func NewRequestLogRepository() RequestLogRepository {
return requestLogRepository{}
}
// SaveLog 保存接口访问记录
func (r requestLogRepository) SaveLog(log *model.RequestLog) {
if err := global.MySQLConn.Create(log).Error; err != nil {
global.Log.Errorf("接口访问记录保存错误: %v", err)
}
}
// Migrate 初始化数据库结构
func (r requestLogRepository) Migrate() error {
return global.MySQLConn.AutoMigrate(&model.RequestLog{})
}

View File

@ -0,0 +1,22 @@
package repository
import (
"go_api_tmpl/global"
"go_api_tmpl/model"
)
type roleMenuRepository struct{}
type RoleMenuRepository interface {
Migrate() error
}
// NewRoleMenuRepository 新建实例
func NewRoleMenuRepository() RoleMenuRepository {
return roleMenuRepository{}
}
// Migrate 初始化数据库结构
func (r roleMenuRepository) Migrate() error {
return global.MySQLConn.AutoMigrate(&model.RoleMenu{})
}

View File

@ -0,0 +1,22 @@
package repository
import (
"go_api_tmpl/global"
"go_api_tmpl/model"
)
type roleRepository struct{}
type RoleRepository interface {
Migrate() error
}
// NewRoleRepository 新建实例
func NewRoleRepository() RoleRepository {
return roleRepository{}
}
// Migrate 初始化数据库结构
func (r roleRepository) Migrate() error {
return global.MySQLConn.AutoMigrate(&model.Role{})
}

View File

@ -0,0 +1,33 @@
package repository
import (
"go_api_tmpl/global"
"go_api_tmpl/model"
)
type userRepository struct{}
type UserRepository interface {
Migrate() error
GetUserByPhone(string) (*model.User, error)
}
// NewUserRepository 新建实例
func NewUserRepository() UserRepository {
return userRepository{}
}
// Migrate 初始化数据库结构
func (r userRepository) Migrate() error {
return global.MySQLConn.AutoMigrate(&model.User{})
}
// GetUserByPhone 根据手机号查询用户信息
func (r userRepository) GetUserByPhone(phone string) (*model.User, error) {
u := model.User{Phone: phone}
err := global.MySQLConn.FirstOrCreate(&u, &u).Error
if err != nil {
return nil, err
}
return &u, nil
}

View File

@ -0,0 +1,22 @@
package repository
import (
"go_api_tmpl/global"
"go_api_tmpl/model"
)
type userRoleRepository struct{}
type UserRoleRepository interface {
Migrate() error
}
// NewUserRoleRepository 新建实例
func NewUserRoleRepository() UserRoleRepository {
return userRoleRepository{}
}
// Migrate 初始化数据库结构
func (r userRoleRepository) Migrate() error {
return global.MySQLConn.AutoMigrate(&model.UserRole{})
}

14
route/login.go Normal file
View File

@ -0,0 +1,14 @@
package route
import (
"github.com/gin-gonic/gin"
"go_api_tmpl/controller"
)
// 初始化登录相关接口路由
func initLoginRoute(r *gin.RouterGroup) {
// 获取短信验证码
r.POST("/sms", controller.GetSmsCodeController)
// 登录
r.POST("/login", controller.LoginController)
}

17
route/route.go Normal file
View File

@ -0,0 +1,17 @@
package route
import (
"github.com/gin-gonic/gin"
"go_api_tmpl/core"
)
func InitRoute(app *gin.Engine) {
app.GET("/", func(context *gin.Context) {
core.R(context).OkWithMessage("瞅啥呢?")
})
group := app.Group("/api")
// 登录相关接口路由
initLoginRoute(group)
// 测试用的
initTestRoute(group)
}

19
route/test.go Normal file
View File

@ -0,0 +1,19 @@
package route
import (
"github.com/gin-gonic/gin"
"go_api_tmpl/core"
"go_api_tmpl/middleware"
)
func initTestRoute(r *gin.RouterGroup) {
group := r.Group("/test", middleware.AuthorizeJWT())
group.GET("/pub", func(context *gin.Context) {
core.R(context).OkWithMessage("已登录且无需权限可访问")
})
group.GET("/hello", middleware.AuthorityVerify(), func(context *gin.Context) {
core.R(context).OkWithMessage("已登录且有权限")
})
}

49
utils/bcrypt.go Normal file
View File

@ -0,0 +1,49 @@
package utils
import (
"fmt"
"github.com/golang-jwt/jwt"
"golang.org/x/crypto/bcrypt"
"os"
"time"
)
func HashPassword(pass *string) {
bytePass := []byte(*pass)
hPass, _ := bcrypt.GenerateFromPassword(bytePass, bcrypt.DefaultCost)
*pass = string(hPass)
}
func ComparePassword(dbPass, pass string) bool {
return bcrypt.CompareHashAndPassword([]byte(dbPass), []byte(pass)) == nil
}
//GenerateToken -> generates token
func GenerateToken(userId uint) string {
claims := jwt.MapClaims{
"exp": time.Now().Add(time.Hour * 3).Unix(),
"iat": time.Now().Unix(),
"userId": userId,
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
t, _ := token.SignedString([]byte(os.Getenv("JWT_SECRET")))
// 保存Token到Redis
//redisKey := fmt.Sprintf("auth:token:%v", userId)
//_ = global.RedisConn.SetWithTimeout(redisKey, t, "10800")
return t
}
//ValidateToken --> validate the given token
func ValidateToken(token string) (*jwt.Token, error) {
//2nd arg function return secret key after checking if the signing method is HMAC and returned key is used by 'Parse' to decode the token)
return jwt.Parse(token, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
//nil secret key
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
return []byte(os.Getenv("JWT_SECRET")), nil
})
}