swagger jwt email access_log middleware

This commit is contained in:
xing 2021-06-01 16:19:40 +08:00
parent 6459a79c32
commit fe2f6850f9
17 changed files with 259 additions and 26 deletions

View File

@ -31,3 +31,12 @@ JWT:
Secret: eddycjy Secret: eddycjy
Issuer: blog-service Issuer: blog-service
Expire: 7200 Expire: 7200
Email:
Host: smtp.qq.com
Port: 465
UserName: xxxx@qq.com
Password: xxxxxxxx
IsSSL: true
From: xxxx@qq.com
To:
- xxxx@qq.com

View File

@ -27,18 +27,16 @@ var doc = `{
"paths": { "paths": {
"/api/v1/tags": { "/api/v1/tags": {
"get": { "get": {
"security": [
{
"ApiKeyAuth": []
}
],
"produces": [ "produces": [
"application/json" "application/json"
], ],
"summary": "获取多个标签", "summary": "获取多个标签",
"parameters": [ "parameters": [
{
"type": "string",
"description": "token",
"name": "token",
"in": "query",
"required": true
},
{ {
"maxLength": 100, "maxLength": 100,
"type": "string", "type": "string",
@ -94,6 +92,11 @@ var doc = `{
} }
}, },
"post": { "post": {
"security": [
{
"ApiKeyAuth": []
}
],
"produces": [ "produces": [
"application/json" "application/json"
], ],
@ -153,6 +156,11 @@ var doc = `{
}, },
"/api/v1/tags/{id}": { "/api/v1/tags/{id}": {
"put": { "put": {
"security": [
{
"ApiKeyAuth": []
}
],
"produces": [ "produces": [
"application/json" "application/json"
], ],
@ -218,6 +226,11 @@ var doc = `{
} }
}, },
"delete": { "delete": {
"security": [
{
"ApiKeyAuth": []
}
],
"produces": [ "produces": [
"application/json" "application/json"
], ],
@ -311,6 +324,11 @@ var doc = `{
}, },
"/upload/file": { "/upload/file": {
"post": { "post": {
"security": [
{
"ApiKeyAuth": []
}
],
"produces": [ "produces": [
"application/json" "application/json"
], ],
@ -437,6 +455,13 @@ var doc = `{
} }
} }
} }
},
"securityDefinitions": {
"ApiKeyAuth": {
"type": "apiKey",
"name": "Authorization",
"in": "header"
}
} }
}` }`

View File

@ -10,18 +10,16 @@
"paths": { "paths": {
"/api/v1/tags": { "/api/v1/tags": {
"get": { "get": {
"security": [
{
"ApiKeyAuth": []
}
],
"produces": [ "produces": [
"application/json" "application/json"
], ],
"summary": "获取多个标签", "summary": "获取多个标签",
"parameters": [ "parameters": [
{
"type": "string",
"description": "token",
"name": "token",
"in": "query",
"required": true
},
{ {
"maxLength": 100, "maxLength": 100,
"type": "string", "type": "string",
@ -77,6 +75,11 @@
} }
}, },
"post": { "post": {
"security": [
{
"ApiKeyAuth": []
}
],
"produces": [ "produces": [
"application/json" "application/json"
], ],
@ -136,6 +139,11 @@
}, },
"/api/v1/tags/{id}": { "/api/v1/tags/{id}": {
"put": { "put": {
"security": [
{
"ApiKeyAuth": []
}
],
"produces": [ "produces": [
"application/json" "application/json"
], ],
@ -201,6 +209,11 @@
} }
}, },
"delete": { "delete": {
"security": [
{
"ApiKeyAuth": []
}
],
"produces": [ "produces": [
"application/json" "application/json"
], ],
@ -294,6 +307,11 @@
}, },
"/upload/file": { "/upload/file": {
"post": { "post": {
"security": [
{
"ApiKeyAuth": []
}
],
"produces": [ "produces": [
"application/json" "application/json"
], ],
@ -420,5 +438,12 @@
} }
} }
} }
},
"securityDefinitions": {
"ApiKeyAuth": {
"type": "apiKey",
"name": "Authorization",
"in": "header"
}
} }
} }

View File

@ -59,11 +59,6 @@ paths:
/api/v1/tags: /api/v1/tags:
get: get:
parameters: parameters:
- description: token
in: query
name: token
required: true
type: string
- description: 标签名称 - description: 标签名称
in: query in: query
maxLength: 100 maxLength: 100
@ -102,6 +97,8 @@ paths:
description: 内部错误 description: 内部错误
schema: schema:
$ref: '#/definitions/errorcode.Error' $ref: '#/definitions/errorcode.Error'
security:
- ApiKeyAuth: [ ]
summary: 获取多个标签 summary: 获取多个标签
post: post:
parameters: parameters:
@ -142,6 +139,8 @@ paths:
description: 内部错误 description: 内部错误
schema: schema:
$ref: '#/definitions/errorcode.Error' $ref: '#/definitions/errorcode.Error'
security:
- ApiKeyAuth: [ ]
summary: 新增标签 summary: 新增标签
/api/v1/tags/{id}: /api/v1/tags/{id}:
delete: delete:
@ -166,6 +165,8 @@ paths:
description: 内部错误 description: 内部错误
schema: schema:
$ref: '#/definitions/errorcode.Error' $ref: '#/definitions/errorcode.Error'
security:
- ApiKeyAuth: [ ]
summary: 删除标签 summary: 删除标签
put: put:
parameters: parameters:
@ -211,6 +212,8 @@ paths:
description: 内部错误 description: 内部错误
schema: schema:
$ref: '#/definitions/errorcode.Error' $ref: '#/definitions/errorcode.Error'
security:
- ApiKeyAuth: [ ]
summary: 更新标签 summary: 更新标签
/auth: /auth:
post: post:
@ -278,5 +281,12 @@ paths:
description: 内部错误 description: 内部错误
schema: schema:
$ref: '#/definitions/errorcode.Error' $ref: '#/definitions/errorcode.Error'
security:
- ApiKeyAuth: [ ]
summary: 上传图片 summary: 上传图片
securityDefinitions:
ApiKeyAuth:
in: header
name: Authorization
type: apiKey
swagger: "2.0" swagger: "2.0"

View File

@ -11,4 +11,5 @@ var (
DatabaseSetting *setting.DatabaseSettingS DatabaseSetting *setting.DatabaseSettingS
Logger *logger.Logger Logger *logger.Logger
JWTSetting *setting.JWT JWTSetting *setting.JWT
EmailSetting *setting.EmailSettingS
) )

2
go.mod
View File

@ -40,7 +40,9 @@ require (
golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea // indirect golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea // indirect
golang.org/x/text v0.3.6 // indirect golang.org/x/text v0.3.6 // indirect
golang.org/x/tools v0.1.2 // indirect golang.org/x/tools v0.1.2 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/go-playground/validator.v9 v9.31.0 // indirect gopkg.in/go-playground/validator.v9 v9.31.0 // indirect
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df // indirect
gopkg.in/ini.v1 v1.62.0 // indirect gopkg.in/ini.v1 v1.62.0 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect

View File

@ -0,0 +1,43 @@
package middleware
import (
"blog/global"
"blog/pkg/logger"
"bytes"
"github.com/gin-gonic/gin"
"time"
)
type AccessLogWriter struct {
gin.ResponseWriter
body *bytes.Buffer
}
func (w AccessLogWriter) Write(p []byte) (int, error) {
if n, err := w.body.Write(p); err != nil {
return n, err
}
return w.ResponseWriter.Write(p)
}
func AccessLog() gin.HandlerFunc {
return func(c *gin.Context) {
bodyWriter := &AccessLogWriter{body: bytes.NewBufferString(""), ResponseWriter: c.Writer}
c.Writer = bodyWriter
beginTime := time.Now().Unix()
c.Next()
endTime := time.Now().Unix()
fields := logger.Fields{
"request": c.Request.PostForm.Encode(),
"response": bodyWriter.body.String(),
}
global.Logger.WithFields(fields).Infof("access log: method: %s, status_code: %d, begin_time: %d, end_time: %d",
c.Request.Method,
bodyWriter.Status(),
beginTime,
endTime,
)
}
}

View File

@ -0,0 +1,11 @@
package middleware
import "github.com/gin-gonic/gin"
func AppInfo() gin.HandlerFunc {
return func(c *gin.Context) {
c.Set("app_name", "blog-service")
c.Set("app_version", "1.0.0")
c.Next()
}
}

View File

@ -13,10 +13,10 @@ func JWT() gin.HandlerFunc {
token string token string
ecode = errorcode.Success ecode = errorcode.Success
) )
if s, exist := c.GetQuery("token"); exist { if s, exist := c.GetQuery("Authorization"); exist {
token = s token = s
} else { } else {
token = c.GetHeader("token") token = c.GetHeader("Authorization")
} }
if token == "" { if token == "" {
ecode = errorcode.InvalidParams ecode = errorcode.InvalidParams

View File

@ -0,0 +1,42 @@
package middleware
import (
"blog/global"
"blog/pkg/app"
"blog/pkg/email"
"blog/pkg/errorcode"
"fmt"
"github.com/gin-gonic/gin"
"time"
)
func Recovery() gin.HandlerFunc {
defailtMailer := email.NewEmail(&email.SMTPInfo{
Host: global.EmailSetting.Host,
Port: global.EmailSetting.Port,
IsSSL: global.EmailSetting.IsSSL,
UserName: global.EmailSetting.UserName,
Password: global.EmailSetting.Password,
From: global.EmailSetting.From,
})
return func(c *gin.Context) {
defer func() {
if err := recover(); err != nil {
global.Logger.WithCallersFrames().Errorf("panic recover err: %v", err)
err := defailtMailer.SendMail(
global.EmailSetting.To,
fmt.Sprintf("异常抛出,发生时间: %d", time.Now().Unix()),
fmt.Sprintf("错误信息: %v", err),
)
if err != nil {
global.Logger.Panicf("mail.SendMail err: %v", err)
}
app.NewResponse(c).ToErrorResponse(errorcode.ServerError)
c.Abort()
}
}()
c.Next()
}
}

View File

@ -33,8 +33,10 @@ func ReadFile(c *gin.Context) {
c.File(fileName) c.File(fileName)
} }
// UploadFile
// @Summary 上传图片 // @Summary 上传图片
// @Produce json // @Produce json
// @Security ApiKeyAuth
// @Param type formData int true "文件类型1图片" required Enums(1,2,3) // @Param type formData int true "文件类型1图片" required Enums(1,2,3)
// @Param file formData file true "文件" // @Param file formData file true "文件"
// @Success 200 {object} service.FileInfo "成功" // @Success 200 {object} service.FileInfo "成功"

View File

@ -18,9 +18,10 @@ func NewTag() Tag {
func (t Tag) Get(c *gin.Context) {} func (t Tag) Get(c *gin.Context) {}
// List
// @Summary 获取多个标签 // @Summary 获取多个标签
// @Produce json // @Produce json
// @Param token query string true "token" // @Security ApiKeyAuth
// @Param name query string false "标签名称" maxlength(100) // @Param name query string false "标签名称" maxlength(100)
// @Param state query int false "状态" Enums(0, 1) default(1) // @Param state query int false "状态" Enums(0, 1) default(1)
// @Param page query int false "页码" default(1) // @Param page query int false "页码" default(1)
@ -60,6 +61,7 @@ func (t Tag) List(c *gin.Context) {
// @Summary 新增标签 // @Summary 新增标签
// @Produce json // @Produce json
// @Security ApiKeyAuth
// @Param name formData string true "标签名称" minlength(3) maxlength(100) // @Param name formData string true "标签名称" minlength(3) maxlength(100)
// @Param state formData int false "状态" Enums(0, 1) default(1) // @Param state formData int false "状态" Enums(0, 1) default(1)
// @Param created_by formData string true "创建者" minlength(3) maxlength(100) // @Param created_by formData string true "创建者" minlength(3) maxlength(100)
@ -91,6 +93,7 @@ func (t Tag) Create(c *gin.Context) {
// @Summary 更新标签 // @Summary 更新标签
// @Produce json // @Produce json
// @Security ApiKeyAuth
// @Param id path int true "标签 ID" // @Param id path int true "标签 ID"
// @Param name formData string false "标签名称" minlength(3),maxlength(100) // @Param name formData string false "标签名称" minlength(3),maxlength(100)
// @Param state formData int false "状态" Enums(0, 1) default(1) // @Param state formData int false "状态" Enums(0, 1) default(1)
@ -121,6 +124,7 @@ func (t Tag) Update(c *gin.Context) {
// @Summary 删除标签 // @Summary 删除标签
// @Produce json // @Produce json
// @Security ApiKeyAuth
// @Param id path int true "标签 ID" // @Param id path int true "标签 ID"
// @Success 200 {string} string "成功" // @Success 200 {string} string "成功"
// @Failure 400 {object} errorcode.Error "请求错误" // @Failure 400 {object} errorcode.Error "请求错误"

View File

@ -17,15 +17,18 @@ func NewRouter() *gin.Engine {
r.Use(gin.Logger()) r.Use(gin.Logger())
r.Use(gin.Recovery()) r.Use(gin.Recovery())
r.Use(middleware.Translations()) r.Use(middleware.Translations())
r.Use(middleware.Recovery())
r.Use(middleware.AccessLog())
article := v1.NewArticle() article := v1.NewArticle()
tag := v1.NewTag() tag := v1.NewTag()
upload := api.NewUpload() upload := api.NewUpload()
r.POST("/upload/file", upload.UploadFile)
r.POST("/upload/file", middleware.JWT(), upload.UploadFile)
r.StaticFS("/static", http.Dir(global.AppSetting.UploadSavePath)) r.StaticFS("/static", http.Dir(global.AppSetting.UploadSavePath))
//r.GET("/static/*any",api.ReadFile) //r.GET("/static/*any",api.ReadFile)
r.GET("/doc/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) r.GET("/doc/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
r.POST("/auth", api.GetAuth) r.POST("/auth", api.GetAuth)
apiv1 := r.Group("/api/v1") apiv1 := r.Group("/api/v1")
apiv1.Use(middleware.JWT()) apiv1.Use(middleware.JWT())
{ {

View File

@ -68,7 +68,10 @@ func setupSetting() error {
if err != nil { if err != nil {
return err return err
} }
err = newSetting.ReadSection("Email", &global.EmailSetting)
if err != nil {
return err
}
global.JWTSetting.Expire *= time.Second global.JWTSetting.Expire *= time.Second
global.ServerSetting.ReadTimeout *= time.Second global.ServerSetting.ReadTimeout *= time.Second
global.ServerSetting.WriteTimeout *= time.Second global.ServerSetting.WriteTimeout *= time.Second
@ -79,6 +82,9 @@ func setupSetting() error {
// @version 1.0 // @version 1.0
// @description Go 语言编程之旅:一起用 Go 做项目 // @description Go 语言编程之旅:一起用 Go 做项目
// @termsOfService https://github.com/go-programming-tour-book // @termsOfService https://github.com/go-programming-tour-book
// @securityDefinitions.apikey ApiKeyAuth
// @in header
// @name Authorization
func main() { func main() {
gin.SetMode(global.ServerSetting.RunMode) gin.SetMode(global.ServerSetting.RunMode)

35
pkg/email/email.go Normal file
View File

@ -0,0 +1,35 @@
package email
import (
"crypto/tls"
"gopkg.in/gomail.v2"
)
type Email struct {
*SMTPInfo
}
type SMTPInfo struct {
Host string
Port int
IsSSL bool
UserName string
Password string
From string
}
func NewEmail(info *SMTPInfo) *Email {
return &Email{SMTPInfo: info}
}
func (e *Email) SendMail(to []string, subject, body string) error {
m := gomail.NewMessage()
m.SetHeader("From", e.From)
m.SetHeader("To", to...)
m.SetHeader("Subject", subject)
m.SetBody("text/html", body)
dialer := gomail.NewDialer(e.Host, e.Port, e.UserName, e.Password)
dialer.TLSConfig = &tls.Config{InsecureSkipVerify: e.IsSSL}
return dialer.DialAndSend(m)
}

View File

@ -163,3 +163,8 @@ func (l *Logger) Error(v ...interface{}) {
func (l *Logger) Errorf(format string, v ...interface{}) { func (l *Logger) Errorf(format string, v ...interface{}) {
l.Output(LevelError, fmt.Sprintf(format, v...)) l.Output(LevelError, fmt.Sprintf(format, v...))
} }
func (l *Logger) Panicf(format string, v ...interface{}) {
l.Output(LevelPanic, fmt.Sprintf(format, v...))
}

View File

@ -48,3 +48,13 @@ func (s *Setting) ReadSection(k string, v interface{}) error {
return nil return nil
} }
type EmailSettingS struct {
Host string
Port int
UserName string
Password string
IsSSL bool
From string
To []string
}