日志的处理

使用的并不是gin框架自带的logger包,而是logrus,还用到了分割日志的包

go get github.com/sirupsen/logrus
// 分割日志
go get github.com/lestrrat-go/file-rotatelogs
// 分段记录
go get github.com/rifflock/lfshook

文档

资料

在根目录下新建一个log文件夹,然后在middleware下新建一个logger.go文件,来写日志中间件。

package middleware

import (
	"fmt"
	"github.com/gin-gonic/gin"
	retalog "github.com/lestrrat-go/file-rotatelogs"
	"github.com/rifflock/lfshook"
	"github.com/sirupsen/logrus"
	"math"
	"os"
	"time"
)

func Logger() gin.HandlerFunc {
	// 定义文件路径
	filePath := "log/blog.log"
	// 软连接路径(可以根据运维来进行调整)
	// linkName := "latest_log.log"
	// 初始化
	logger := logrus.New()
	// 定位到log文件夹(路径,os新建文件,赋值权限)
	src,err := os.OpenFile(filePath, os.O_RDWR|os.O_CREATE, 0755)
	if err!=nil {
		fmt.Println(err)
	}
	// 输出到日志文件中
	logger.Out = src

	logger.SetLevel(logrus.DebugLevel)
	logWriter, _ := retalog.New(
		filePath+"%Y%m%d.log",
		retalog.WithMaxAge(7*24*time.Hour),
		retalog.WithRotationTime(24*time.Hour),
		// retalog.WithLinkName(linkName),
	)
  // 日志级别
	writeMap := lfshook.WriterMap{
		logrus.InfoLevel:  logWriter,
		logrus.FatalLevel: logWriter,
		logrus.DebugLevel: logWriter,
		logrus.WarnLevel:  logWriter,
		logrus.ErrorLevel: logWriter,
		logrus.PanicLevel: logWriter,
	}
  // 时间格式化分割
	Hook := lfshook.NewHook(writeMap, &logrus.TextFormatter{
		TimestampFormat: "2006-01-02 15:04:05",
	})
	logger.AddHook(Hook)


	return func(c *gin.Context) {
		// 输出开始时间
		startTime := time.Now()
		c.Next()
		// 结束时间运行时间
		stopTime := time.Since(startTime)
		spendTime := fmt.Sprintf("%d ms", int(math.Ceil(float64(stopTime.Nanoseconds())/1000000.0)))
		// 主机名
		hostName, err := os.Hostname()
		if err != nil {
			hostName = "unKnown"
		}
		// 状态码
		statusCode := c.Writer.Status()
		// 客户端ip
		clientIP := c.ClientIP()
		// 客户端信息
		userAgent := c.Request.UserAgent()
		// 请求大小
		dataSize := c.Writer.Size()
		if dataSize < 0{
			dataSize = 0
		}
		// 请求方法和路径
		method := c.Request.Method
		path := c.Request.RequestURI
		// 组合数据
		entry := logger.WithFields(logrus.Fields{
			"HostName":hostName,
			"Status":statusCode,
			"SpendTime":spendTime,
			"IP":clientIP,
			"Method":method,
			"Path":path,
			"DataSize":dataSize,
			"Agent": userAgent,
		})
		// 分级别
		if len(c.Errors) > 0{
			entry.Error(c.Errors.ByType(gin.ErrorTypePrivate).String())
		}
		if statusCode >= 500{
			entry.Error()
		}else if statusCode >= 400{
			entry.Warn()
		}else {
			entry.Info()
		}
	}
	
}

然后将router.go下实例的r改为New方法

func InitRouter()  {
	gin.SetMode(utils.AppMode)
	r := gin.New()
	r.Use(middleware.Logger())
	r.Use(gin.Recovery())

	Auth := r.Group("api/admin")
	Auth.Use(middleware.JwtToken())
	{
		// User模块的路由接口
		Auth.PUT("user/:id/", v1.EditUser)
		Auth.DELETE("user/:id/", v1.DeleteUser)
		// Category模块的路由接口
		Auth.POST("category/", v1.AddCategory)
		Auth.PUT("category/:id/", v1.EditCategory)
		Auth.DELETE("category/:id/", v1.DeleteCategory)
		// Article模块的路由接口
		Auth.POST("article/", v1.AddArticle)
		Auth.PUT("article/:id/", v1.EditArticle)
		Auth.DELETE("article/:id/", v1.DeleteArticle)
		Auth.POST("upload/", v1.UploadFile)
	}

	router := r.Group("api/v1")
	{
		router.GET("user/", v1.GetUsers)
		Auth.POST("user/", v1.AddUser)

		router.POST("login/", v1.Login)

		router.GET("category/", v1.GetCategorys)
		router.GET("category/:id/", v1.GetCateInfo)

		router.GET("article/", v1.GetArticles)
		router.GET("article/:id/", v1.GetArticleInfo)
		router.GET("catelist/:id/", v1.GetCateArt)
	}

	_ = r.Run(utils.HttpPort)

}

跨域配置与数据限制

数据限制

go get github.com/go-playground/locales/zh_Hans_CN@v0.13.0

go get github.com/gin-contrib/cors

数据限制,改造model/User.go,添加validate。

type User struct {
	gorm.Model
	Username string `gorm:"type:varchar(20);not null " json:"username" validate:"required,min=4,max=12" label:"用户名"`
	Password string `gorm:"type:varchar(500);not null" json:"password" validate:"required,min=6,max=120" label:"密码"`
	Avatar string `gorm:"type:varchar(40);not null" json:"avatar" label:"头像"`
	Role     int    `gorm:"type:int;DEFAULT:2" json:"role" validate:"required,gte=2" label:"角色"`
}

在utils下,新建validator文件夹和文件

package validator

import (
	"blog/utils/errmsg"
	"fmt"
	"github.com/go-playground/locales/zh_Hans_CN"
	unTrans "github.com/go-playground/universal-translator"
	"github.com/go-playground/validator/v10"
	zhTrans "github.com/go-playground/validator/v10/translations/zh"
	"reflect"
)

func Validate(data interface{}) (string, int) {
	validate := validator.New()
	uni := unTrans.New(zh_Hans_CN.New())
	trans, _ := uni.GetTranslator("zh_Hans_CN")

	err := zhTrans.RegisterDefaultTranslations(validate, trans)
	if err != nil {
		fmt.Println("err:", err)
	}
	validate.RegisterTagNameFunc(func(field reflect.StructField) string {
		label := field.Tag.Get("label")
		return label
	})

	err = validate.Struct(data)
	if err != nil {
		for _, v := range err.(validator.ValidationErrors) {
			return v.Translate(trans), errmsg.ERROR
		}
	}
	return "", errmsg.SUCCSE
}

工具写好后,在api/user.go使用验证,保证添加的数据是正确的。

func AddUser(c *gin.Context)  {
	var data model.User
	var msg string
	_ = c.ShouldBindJSON(&data)
	msg, code = validator.Validate(&data)
	if code != errmsg.SUCCSE {
		c.JSON(
			http.StatusOK, gin.H{
				"status":  code,
				"message": msg,
			},
		)
		c.Abort()
		return
	}

	code = model.CheckUser(data.Username)
	if code == errmsg.SUCCSE {
		model.CreateUser(&data)
	}
	if code == errmsg.ERROR_USERNAME_USED {
		code = errmsg.ERROR_USERNAME_USED
	}

	c.JSON(http.StatusOK,gin.H{
		"code":code,
		"msg":errmsg.GetErrorMsg(code),
		"data":data,
	})
}

跨域配置

在middleware下,新建cors.go文件,编写跨域中间件

package middleware

import (
	"github.com/gin-contrib/cors"
	"github.com/gin-gonic/gin"
	"time"
)

func Cors() gin.HandlerFunc {
	return cors.New(
		cors.Config{
			AllowAllOrigins:  true,
			AllowMethods:     []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
			AllowHeaders:     []string{"*"},
			ExposeHeaders:    []string{"Content-Length", "text/plain", "Authorization", "Content-Type"},
			AllowCredentials: true,
			MaxAge:           12 * time.Hour,
		},
	)
}

和之前的中间件一样在router中配置即可。

大体功能基本实现,在后期可能会加一些新的,不断的完善,不过目前这种程度就已经能够做一个博客了,后期项目先到此为止,然后前端的话,使用vue来做,未完待续…