Gin简介
Gin是一个golang的微框架,封装比较优雅,API友好,源码注释比较明确,具有快速灵活,容错方便等特点。其实对于golang而言,web框架的依赖要远比Python,Java之类的要小。自身的net/http足够简单,性能也非常不错。框架更像是一些常用函数或者工具的集合。借助框架开发,不仅可以省去很多常用的封装带来的时间,也有助于团队的编码风格和形成规范。
gin特点
- 性能优秀
- 基于官方的net/http的有限封装
- 方便 灵活的中间件
- 数据绑定很强大
- 社区比较活跃
gin web微服务框架示例
总体功能
- 集成logrus.Logger日志按天切割,json格式打印
- 集成swagger文档
- 指定yml配置文件启动
- 异常处理
- 拦截器打印请求和响应参数
main方法: 上面的注释定义了swagger信息,然后gin初始化,路由初始化,是否启用swagger
package mainimport (
"flag"
"fmt"
. "gin_demo/config"
_ "gin_demo/docs"
. "gin_demo/log"
"gin_demo/router"
"github.com/gin-gonic/gin"
"github.com/swaggo/gin-swagger"
"github.com/swaggo/gin-swagger/swaggerFiles"
"runtime"
"time"
)var version = flag.Bool("version", true, "是否打印版本,默认打印")
var swagger = flag.Bool("swagger", true, "是否启动swagger接口文档,默认不启动")
var configFile = flag.String("configFile", "config/config.yml", "配置文件路径")
var projectPath = flag.String("projectPath", "/gin_demo", "项目访问路径前缀")func init(){
flag.Parse()ConfigRead(*configFile)LogInit()
}//@title gin示例 API
//@version 0.0.1
//@description相关接口文档
//@host 127.0.0.1:8080
//@BasePath
func main() {
if *version {
showVersion := fmt.Sprintf("%s %s@%s", "gin_demo", "1.0.0", time.Now().Format("2006-01-02 15:04:05"))
fmt.Println(showVersion)
fmt.Println("go version: " + runtime.Version())
}Log.Info("start server...")gin.SetMode(gin.DebugMode) //全局设置环境,此为开发环境,线上环境为gin.ReleaseMode
router.GinInit()//gin工程实例 *gin.Engine
r := router.Router//路由初始化
router.SetupRouter(*projectPath)if *swagger {
//启动访问swagger文档
r.GET(*projectPath + "/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
}Log.Info("listen on :%s", Cfg.ListenPort)
//监听端口
r.Run(":" + Cfg.ListenPort)}
router.go 路由
package routerimport (
"github.com/gin-gonic/gin"
"net/http"
)var Router *gin.Enginefunc GinInit(){
// 禁用控制台颜色
//gin.DisableConsoleColor()//gin.New()返回一个*Engine 指针
//而gin.Default()不但返回一个*Engine 指针,而且还进行了debugPrintWARNINGDefault()和engine.Use(Logger(), Recovery())其他的一些中间件操作
Router = gin.Default()
//Router = gin.New()
}func SetupRouter(projectPath string) {//使用日志
//Router.Use(gin.Logger())
//使用Panic处理方案
//Router.Use(gin.Recovery())Router.Use(InitErrorHandler)
Router.Use(InitAccessLogMiddleware)// 未知调用方式
Router.NoMethod(InitNoMethodJson)
// 未知路由处理
Router.NoRoute(InitNoRouteJson)// Ping
Router.GET(projectPath + "/ping", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"ping": "pong",
})
})Router.POST(projectPath + "/pp", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"ping": "post",
})
})}
middleware.go 中间件拦截器
package routerimport (
"encoding/json"
. "gin_demo/log"
. "gin_demo/threadlocal"
"github.com/gin-gonic/gin"
. "github.com/jtolds/gls"
"github.com/sirupsen/logrus"
"io/ioutil"
"net/http"
"strconv"
"time"
)// ErrorHandler is a middleware to handle errors encountered during requests
func InitErrorHandler(c *gin.Context) {
c.Next()
if len(c.Errors) > 0 {
c.JSON(http.StatusBadRequest, gin.H{
"errors": c.Errors,
})
}
}//未知路由处理 返回json
func InitNoRouteJson(c *gin.Context) {
c.JSON(http.StatusNotFound, gin.H{
"code": http.StatusNotFound,
"msg":"path not found",
})
}//未知调用方式 返回json
func InitNoMethodJson(c *gin.Context) {
c.JSON(http.StatusMethodNotAllowed, gin.H{
"code": http.StatusMethodNotAllowed,
"msg":"method not allowed",
})
}//打印请求和响应日志
func InitAccessLogMiddleware(c *gin.Context) {
//request id
requestId := c.Request.Header.Get("X-RequestId")
if requestId == "" {
requestId = strconv.FormatInt(time.Now().UnixNano(), 10)
}
//response requestId
c.Writer.Header().Set("X-RequestId", requestId)// 开始时间
startTime := time.Now()//处理请求 do chian
Mgr.SetValues(Values{Rid: requestId}, func() {
c.Next()
})// 结束时间
endTime := time.Now()
// 执行时间
latencyTime := endTime.Sub(startTime)
// 请求方式
reqMethod := c.Request.Method
// 请求路由
reqUri := c.Request.RequestURI
// 状态码
statusCode := c.Writer.Status()
// 请求IP
clientIP := c.ClientIP()
//请求参数
body, _ := ioutil.ReadAll(c.Request.Body)
//返回参数
responseMap := c.Keys
responseJson, _ := json.Marshal(responseMap)//日志格式
//LogAccess.Infof("| %3d | %13v | %15s | %s | %s | %s | %s | %s |",
//statusCode,
//latencyTime,
//clientIP,
//reqMethod,
//reqUri,
//requestId,
//string(body),
//string(responseJson),
//)// 日志格式
LogAccess.WithFields(logrus.Fields{
"status_code":statusCode,
"latency_time": latencyTime,
"client_ip":clientIP,
"req_method":reqMethod,
"req_uri":reqUri,
"req_Id":requestId,
"req_body":string(body),
"res_body":string(responseJson),
}).Info()}
logger.go 日志定义和配置
package logimport (
"fmt"
"gin_demo/config"
"github.com/sirupsen/logrus"
rotatelogs "github.com/lestrrat-go/file-rotatelogs"
"github.com/rifflock/lfshook"
"os"
"path"
"time"
)var Log *logrus.Logger
var LogAccess *logrus.Loggerfunc LogInit() {
logFilePath := ""
logPath := config.Cfg.LogPath
if len(logPath) == 0 {
//获取当前目录
if dir, err := os.Getwd();
err == nil {
logFilePath = dir + "/logs/"
}
} else {
//指定目录
logFilePath = logPath + "/logs/"
}if err := os.MkdirAll(logFilePath, 0777);
err != nil {
fmt.Println(err.Error())
}rootLogInit(logFilePath)
accessLogInit(logFilePath)
}func rootLogInit(logFilePath string) {
logFileName := "root.log"//日志文件
fileName := path.Join(logFilePath, logFileName)
if _, err := os.Stat(fileName);
err != nil {
if _, err := os.Create(fileName);
err != nil {
fmt.Println(err.Error())
}
}//写入文件
src, err := os.OpenFile(fileName, os.O_APPEND|os.O_WRONLY, os.ModeAppend)
if err != nil {
fmt.Println("err", err)
}//实例化
Log = logrus.New()
//设置输出
Log.Out = src
Log.Out = os.Stdout
//设置日志级别
Log.SetLevel(logrus.DebugLevel)// 设置 rotatelogs
logWriter, err := rotatelogs.New(
// 分割后的文件名称
fileName + "-%Y%m%d.log",// 生成软链,指向最新日志文件
rotatelogs.WithLinkName(fileName),// 设置最大保存时间(2天)
rotatelogs.WithMaxAge(2*24*time.Hour),// 设置日志切割时间间隔(1天)
rotatelogs.WithRotationTime(24*time.Hour),
)writeMap := lfshook.WriterMap{
logrus.InfoLevel:logWriter,
logrus.FatalLevel: logWriter,
logrus.DebugLevel: logWriter,
logrus.WarnLevel:logWriter,
logrus.ErrorLevel: logWriter,
logrus.PanicLevel: logWriter,
}//设置日志格式
lfHook := lfshook.NewHook(writeMap, &logrus.JSONFormatter{
TimestampFormat:"2006-01-02 15:04:05",
})// 新增 Hook
Log.AddHook(lfHook)}func accessLogInit(logFilePath string) {
logFileNameAccess := "access.log"fileNameAccess := path.Join(logFilePath, logFileNameAccess)
if _, err := os.Stat(fileNameAccess);
err != nil {
if _, err := os.Create(fileNameAccess);
err != nil {
fmt.Println(err.Error())
}
}srcAccess, err := os.OpenFile(fileNameAccess, os.O_APPEND|os.O_WRONLY, os.ModeAppend)
if err != nil {
fmt.Println("err", err)
}//实例化
LogAccess = logrus.New()
//设置输出
LogAccess.Out = srcAccess
LogAccess.Out = os.Stdout
//设置日志级别
LogAccess.SetLevel(logrus.DebugLevel)// 设置 rotatelogs
logWriterAccess, err := rotatelogs.New(
// 分割后的文件名称
fileNameAccess + "-%Y%m%d.log",// 生成软链,指向最新日志文件
rotatelogs.WithLinkName(fileNameAccess),// 设置最大保存时间(2天)
rotatelogs.WithMaxAge(2*24*time.Hour),// 设置日志切割时间间隔(1天)
rotatelogs.WithRotationTime(24*time.Hour),
)writeMapAccess := lfshook.WriterMap{
logrus.InfoLevel:logWriterAccess,
logrus.FatalLevel: logWriterAccess,
logrus.DebugLevel: logWriterAccess,
logrus.WarnLevel:logWriterAccess,
logrus.ErrorLevel: logWriterAccess,
logrus.PanicLevel: logWriterAccess,
}lfHookAccess := lfshook.NewHook(writeMapAccess, &logrus.JSONFormatter{
TimestampFormat:"2006-01-02 15:04:05",
})// 新增 Hook
LogAccess.AddHook(lfHookAccess)
}
Demo运行 swag 的安装使用后续会讲解
#执行:swag init 生成swagger文件
gin_demo git:(main) swag init
#显示如下,会在项目生成docs文件夹
2021/07/23 21:30:36 Generate swagger docs....
2021/07/23 21:30:36 Generate general API Info
2021/07/23 21:30:36 create docs.go atdocs/docs.go#启动项目
go run main.go
#打印如下,表示成功启动8080端口
Listening and serving HTTP on :8080
文章图片
浏览器访问接口:
http://127.0.0.1:8080/gin_dem...
{"ping":"pong"}
浏览器访问swagger:
【Gin微服务框架_golang web框架_完整示例Demo】
文章图片
Demo源代码地址:https://github.com/tw-iot/gin...
参考链接地址:
http://www.topgoer.com/gin%E6...
https://zhuanlan.zhihu.com/p/...
https://github.com/skyhee/gin...
https://www.jianshu.com/p/989...