乐趣区

关于go:Gin微服务框架golang-web框架完整示例Demo

Gin 简介

Gin 是一个 golang 的微框架,封装比拟优雅,API 敌对,源码正文比拟明确,具备疾速灵便,容错不便等特点。其实对于 golang 而言,web 框架的依赖要远比 Python,Java 之类的要小。本身的 net/http 足够简略,性能也十分不错。框架更像是一些罕用函数或者工具的汇合。借助框架开发,不仅能够省去很多罕用的封装带来的工夫,也有助于团队的编码格调和造成标准。

gin 特点
  • 性能优良
  • 基于官网的 net/http 的无限封装
  • 不便 灵便的中间件
  • 数据绑定很弱小
  • 社区比拟沉闷

官网源代码地址: https://github.com/gin-gonic/gin

gin web 微服务框架示例

总体性能

  • 集成 logrus.Logger 日志按天切割,json 格局打印
  • 集成 swagger 文档
  • 指定 yml 配置文件启动
  • 异样解决
  • 拦截器打印申请和响应参数
main.go 我的项目入口

init 办法: 初始化相干配置
main 办法: 下面的正文定义了 swagger 信息, 而后 gin 初始化, 路由初始化, 是否启用 swagger


package main

import (
    "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 router

import (
    "github.com/gin-gonic/gin"
    "net/http"
)

var Router *gin.Engine

func 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 router

import (
    "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 log

import (
    "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.Logger

func 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 at  docs/docs.go

#启动我的项目
go run main.go 
#打印如下, 示意胜利启动 8080 端口
Listening and serving HTTP on :8080

浏览器拜访接口:
http://127.0.0.1:8080/gin_dem…

{“ping”:”pong”}

浏览器拜访 swagger:

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…

退出移动版