diff --git a/configs/config.yaml b/configs/config.yaml index ecf6a6a..1d5f7a9 100644 --- a/configs/config.yaml +++ b/configs/config.yaml @@ -30,4 +30,13 @@ Database: JWT: Secret: eddycjy Issuer: blog-service - Expire: 7200 \ No newline at end of file + 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 \ No newline at end of file diff --git a/docs/docs.go b/docs/docs.go index 1cbe6c8..4590f99 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -27,18 +27,16 @@ var doc = `{ "paths": { "/api/v1/tags": { "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], "produces": [ "application/json" ], "summary": "获取多个标签", "parameters": [ - { - "type": "string", - "description": "token", - "name": "token", - "in": "query", - "required": true - }, { "maxLength": 100, "type": "string", @@ -94,6 +92,11 @@ var doc = `{ } }, "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], "produces": [ "application/json" ], @@ -153,6 +156,11 @@ var doc = `{ }, "/api/v1/tags/{id}": { "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], "produces": [ "application/json" ], @@ -218,6 +226,11 @@ var doc = `{ } }, "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], "produces": [ "application/json" ], @@ -311,6 +324,11 @@ var doc = `{ }, "/upload/file": { "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], "produces": [ "application/json" ], @@ -437,6 +455,13 @@ var doc = `{ } } } + }, + "securityDefinitions": { + "ApiKeyAuth": { + "type": "apiKey", + "name": "Authorization", + "in": "header" + } } }` diff --git a/docs/swagger.json b/docs/swagger.json index 342133b..7e3a4eb 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -10,18 +10,16 @@ "paths": { "/api/v1/tags": { "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], "produces": [ "application/json" ], "summary": "获取多个标签", "parameters": [ - { - "type": "string", - "description": "token", - "name": "token", - "in": "query", - "required": true - }, { "maxLength": 100, "type": "string", @@ -77,6 +75,11 @@ } }, "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], "produces": [ "application/json" ], @@ -136,6 +139,11 @@ }, "/api/v1/tags/{id}": { "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], "produces": [ "application/json" ], @@ -201,6 +209,11 @@ } }, "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], "produces": [ "application/json" ], @@ -294,6 +307,11 @@ }, "/upload/file": { "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], "produces": [ "application/json" ], @@ -420,5 +438,12 @@ } } } + }, + "securityDefinitions": { + "ApiKeyAuth": { + "type": "apiKey", + "name": "Authorization", + "in": "header" + } } } \ No newline at end of file diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 18197e1..806c605 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -59,11 +59,6 @@ paths: /api/v1/tags: get: parameters: - - description: token - in: query - name: token - required: true - type: string - description: 标签名称 in: query maxLength: 100 @@ -102,6 +97,8 @@ paths: description: 内部错误 schema: $ref: '#/definitions/errorcode.Error' + security: + - ApiKeyAuth: [ ] summary: 获取多个标签 post: parameters: @@ -142,6 +139,8 @@ paths: description: 内部错误 schema: $ref: '#/definitions/errorcode.Error' + security: + - ApiKeyAuth: [ ] summary: 新增标签 /api/v1/tags/{id}: delete: @@ -166,6 +165,8 @@ paths: description: 内部错误 schema: $ref: '#/definitions/errorcode.Error' + security: + - ApiKeyAuth: [ ] summary: 删除标签 put: parameters: @@ -211,6 +212,8 @@ paths: description: 内部错误 schema: $ref: '#/definitions/errorcode.Error' + security: + - ApiKeyAuth: [ ] summary: 更新标签 /auth: post: @@ -278,5 +281,12 @@ paths: description: 内部错误 schema: $ref: '#/definitions/errorcode.Error' + security: + - ApiKeyAuth: [ ] summary: 上传图片 +securityDefinitions: + ApiKeyAuth: + in: header + name: Authorization + type: apiKey swagger: "2.0" diff --git a/global/setting.go b/global/setting.go index ad6f8ea..7e8443b 100644 --- a/global/setting.go +++ b/global/setting.go @@ -11,4 +11,5 @@ var ( DatabaseSetting *setting.DatabaseSettingS Logger *logger.Logger JWTSetting *setting.JWT + EmailSetting *setting.EmailSettingS ) diff --git a/go.mod b/go.mod index 8811e9c..14aba28 100644 --- a/go.mod +++ b/go.mod @@ -40,7 +40,9 @@ require ( golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea // indirect golang.org/x/text v0.3.6 // 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/gomail.v2 v2.0.0-20160411212932-81ebce5c23df // indirect gopkg.in/ini.v1 v1.62.0 // indirect gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/internal/middleware/access_log.go b/internal/middleware/access_log.go new file mode 100644 index 0000000..6a769be --- /dev/null +++ b/internal/middleware/access_log.go @@ -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, + ) + } +} diff --git a/internal/middleware/app_info.go b/internal/middleware/app_info.go new file mode 100644 index 0000000..979a090 --- /dev/null +++ b/internal/middleware/app_info.go @@ -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() + } +} diff --git a/internal/middleware/jwt.go b/internal/middleware/jwt.go index efc6414..b17ce6e 100644 --- a/internal/middleware/jwt.go +++ b/internal/middleware/jwt.go @@ -13,10 +13,10 @@ func JWT() gin.HandlerFunc { token string ecode = errorcode.Success ) - if s, exist := c.GetQuery("token"); exist { + if s, exist := c.GetQuery("Authorization"); exist { token = s } else { - token = c.GetHeader("token") + token = c.GetHeader("Authorization") } if token == "" { ecode = errorcode.InvalidParams diff --git a/internal/middleware/recovery.go b/internal/middleware/recovery.go new file mode 100644 index 0000000..098b5a3 --- /dev/null +++ b/internal/middleware/recovery.go @@ -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() + } +} diff --git a/internal/routess/api/upload.go b/internal/routess/api/upload.go index 533fe07..485288d 100644 --- a/internal/routess/api/upload.go +++ b/internal/routess/api/upload.go @@ -33,8 +33,10 @@ func ReadFile(c *gin.Context) { c.File(fileName) } +// UploadFile // @Summary 上传图片 // @Produce json +// @Security ApiKeyAuth // @Param type formData int true "文件类型1图片" required Enums(1,2,3) // @Param file formData file true "文件" // @Success 200 {object} service.FileInfo "成功" diff --git a/internal/routess/api/v1/tag.go b/internal/routess/api/v1/tag.go index acca23e..1033a7f 100644 --- a/internal/routess/api/v1/tag.go +++ b/internal/routess/api/v1/tag.go @@ -18,9 +18,10 @@ func NewTag() Tag { func (t Tag) Get(c *gin.Context) {} +// List // @Summary 获取多个标签 // @Produce json -// @Param token query string true "token" +// @Security ApiKeyAuth // @Param name query string false "标签名称" maxlength(100) // @Param state query int false "状态" Enums(0, 1) default(1) // @Param page query int false "页码" default(1) @@ -60,6 +61,7 @@ func (t Tag) List(c *gin.Context) { // @Summary 新增标签 // @Produce json +// @Security ApiKeyAuth // @Param name formData string true "标签名称" minlength(3) maxlength(100) // @Param state formData int false "状态" Enums(0, 1) default(1) // @Param created_by formData string true "创建者" minlength(3) maxlength(100) @@ -91,6 +93,7 @@ func (t Tag) Create(c *gin.Context) { // @Summary 更新标签 // @Produce json +// @Security ApiKeyAuth // @Param id path int true "标签 ID" // @Param name formData string false "标签名称" minlength(3),maxlength(100) // @Param state formData int false "状态" Enums(0, 1) default(1) @@ -121,6 +124,7 @@ func (t Tag) Update(c *gin.Context) { // @Summary 删除标签 // @Produce json +// @Security ApiKeyAuth // @Param id path int true "标签 ID" // @Success 200 {string} string "成功" // @Failure 400 {object} errorcode.Error "请求错误" diff --git a/internal/routess/router.go b/internal/routess/router.go index 22c021d..9528f3d 100644 --- a/internal/routess/router.go +++ b/internal/routess/router.go @@ -17,15 +17,18 @@ func NewRouter() *gin.Engine { r.Use(gin.Logger()) r.Use(gin.Recovery()) r.Use(middleware.Translations()) - + r.Use(middleware.Recovery()) + r.Use(middleware.AccessLog()) article := v1.NewArticle() tag := v1.NewTag() 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.GET("/static/*any",api.ReadFile) r.GET("/doc/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) r.POST("/auth", api.GetAuth) + apiv1 := r.Group("/api/v1") apiv1.Use(middleware.JWT()) { diff --git a/main.go b/main.go index 711b2fc..8dcaa73 100644 --- a/main.go +++ b/main.go @@ -68,7 +68,10 @@ func setupSetting() error { if err != nil { return err } - + err = newSetting.ReadSection("Email", &global.EmailSetting) + if err != nil { + return err + } global.JWTSetting.Expire *= time.Second global.ServerSetting.ReadTimeout *= time.Second global.ServerSetting.WriteTimeout *= time.Second @@ -79,6 +82,9 @@ func setupSetting() error { // @version 1.0 // @description Go 语言编程之旅:一起用 Go 做项目 // @termsOfService https://github.com/go-programming-tour-book +// @securityDefinitions.apikey ApiKeyAuth +// @in header +// @name Authorization func main() { gin.SetMode(global.ServerSetting.RunMode) diff --git a/pkg/email/email.go b/pkg/email/email.go new file mode 100644 index 0000000..c13025a --- /dev/null +++ b/pkg/email/email.go @@ -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) +} diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go index a9b13cd..3d9c26a 100644 --- a/pkg/logger/logger.go +++ b/pkg/logger/logger.go @@ -163,3 +163,8 @@ func (l *Logger) Error(v ...interface{}) { func (l *Logger) Errorf(format string, v ...interface{}) { l.Output(LevelError, fmt.Sprintf(format, v...)) } + +func (l *Logger) Panicf(format string, v ...interface{}) { + l.Output(LevelPanic, fmt.Sprintf(format, v...)) + +} diff --git a/pkg/setting/section.go b/pkg/setting/section.go index af4ea42..09b64be 100644 --- a/pkg/setting/section.go +++ b/pkg/setting/section.go @@ -48,3 +48,13 @@ func (s *Setting) ReadSection(k string, v interface{}) error { return nil } + +type EmailSettingS struct { + Host string + Port int + UserName string + Password string + IsSSL bool + From string + To []string +}