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...

程序人生,一路踩坑,一路填坑

4 声望
1 粉丝
0 条评论
推荐阅读
SSM SpringBoot vue餐饮安全与卫生防御管理系统
登录 首页 个人中心 学生信息管理 学工部人员管理 后勤部人员管理 食品健康宣传管理 职业道德技能管理 食堂信息管理 食品采集管理 厨余垃圾回收管理 消毒设施管理  食品安全资讯管理 食堂评价管理

阿亮说技术阅读 251

前端如何入门 Go 语言
类比法是一种学习方法,它是通过将新知识与已知知识进行比较,从而加深对新知识的理解。在学习 Go 语言的过程中,我发现,通过类比已有的前端知识,可以更好地理解 Go 语言的特性。

robin23阅读 3.2k评论 6

封面图
Golang 中 []byte 与 string 转换
string 类型和 []byte 类型是我们编程时最常使用到的数据结构。本文将探讨两者之间的转换方式,通过分析它们之间的内在联系来拨开迷雾。

机器铃砍菜刀24阅读 58.1k评论 2

年度最佳【golang】map详解
这篇文章主要讲 map 的赋值、删除、查询、扩容的具体执行过程,仍然是从底层的角度展开。结合源码,看完本文一定会彻底明白 map 底层原理。

去去100216阅读 11.5k评论 2

年度最佳【golang】GMP调度详解
Golang最大的特色可以说是协程(goroutine)了, 协程让本来很复杂的异步编程变得简单, 让程序员不再需要面对回调地狱, 虽然现在引入了协程的语言越来越多, 但go中的协程仍然是实现的是最彻底的. 这篇文章将通过分析...

去去100215阅读 11.9k评论 4

万字详解,吃透 MongoDB!
MongoDB 是一个基于 分布式文件存储 的开源 NoSQL 数据库系统,由 C++ 编写的。MongoDB 提供了 面向文档 的存储方式,操作起来比较简单和容易,支持“无模式”的数据建模,可以存储比较复杂的数据类型,是一款非常...

JavaGuide8阅读 1.7k

封面图
数据结构与算法:二分查找
一、常见数据结构简单数据结构(必须理解和掌握)有序数据结构:栈、队列、链表。有序数据结构省空间(储存空间小)无序数据结构:集合、字典、散列表,无序数据结构省时间(读取时间快)复杂数据结构树、 堆图二...

白鲸鱼9阅读 5.3k

程序人生,一路踩坑,一路填坑

4 声望
1 粉丝
宣传栏