Gin微服务框架_golang web框架_完整示例Demo

news2024/11/24 20:28:30

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_demo/ping

{“ping”:“pong”}

浏览器访问swagger:

在这里插入图片描述

Demo源代码地址:https://github.com/tw-iot/gin_demo

参考链接地址:
http://www.topgoer.com/gin%E6%A1%86%E6%9E%B6/%E7%AE%80%E4%BB%8B.html
https://zhuanlan.zhihu.com/p/165633941
https://github.com/skyhee/gin-doc-cn
https://www.jianshu.com/p/98965b3ff638

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/639236.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Spark入门

Spark概述 1.1 什么是Spark 回顾:Hadoop主要解决,海量数据的存储和海量数据的分析计算。 Spark是一种基于内存的快速、通用、可扩展的大数据分析计算引擎。 1.2 Hadoop与Spark历史 MR是进程模型,ResourceManager NodeManager都是进程&…

107-Spring的底层原理(上篇)

Spring的底层原理 之前说明的都是Spring的应用(64章博客开始(包括其后面的相关博客)),现在来说明他为什么可以那样做 在说明之前,这里需要对64章博客中的spring的了解需要再次的说明: Spring…

Unity中UI方案。IMGUI、UIElement、UGUI、NGUI

引言 unity中有很多ui方案,各种方案有什么优势劣势,这里一一列举一下,知识扩充一下。 UI方案适用范围IMGUI仅用于Editor扩展,或运行时DebugUIElement可用于发布运行时和EditorUGUIRuntime,两大主流 UI 解决方案之一NG…

python语法-MySQL数据库(综合案例:读取文件,写入MySQL数据库中)

python语法-MySQL数据库 综合案例:读取文件,写入MySQL数据库中 (项目数据见文章末参考内容) 解析: sql代码如下: create database pysql charset utf8;use pysql;select database();create table orders…

华为OD机试真题 JavaScript 实现【求小球落地5次后所经历的路程和第5次反弹的高度】【牛客练习题 HJ38】

一、题目描述 假设一个球从任意高度自由落下,每次落地后反跳回原高度的一半; 再落下, 求它在第5次落地时,共经历多少米?第5次反弹多高? 数据范围:输入的小球初始高度满足 1 \le n \le 1000 \1≤n≤1000 ,且保证是一…

今年十八,期末速刷(操作系统篇1)

马上期末了,想问问各位期末考几科 我家学校网安考7科呜呜呜 只能出点文章一把梭了。。。 争取只挂一科 先来先算法(FCFS) 算法思想 我今天学FCFS只有一个要求 公平、公平 还是tnd公平 算法规则 按照进程的先后顺序来进行服务。 是否…

Web自动化测试:WebDriverWait元素等待和全局设置

由于现在部分web应用加载方式的选择,页面会需要一定时间逐渐加载完毕,也就是说有的页面元素先加载出来,有的元素后加载出来。如果直接定位所查找的元素的话,可能会由于此元素尚未加载完毕找不到元素从而报错,由于网络不…

leetcode 647.回文子串

题目描述 给你一个字符串 s ,请你统计并返回这个字符串中 回文子串 的数目。 回文字符串 是正着读和倒过来读一样的字符串。 子字符串 是字符串中的由连续字符组成的一个序列。 具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会…

【干货】有效的项目绩效管理评估,能成为组织成长的引擎

是谁已经开始在写年中总结了? 对于这件事,项目经理们肯定不会缺席,毕竟每周、每月、每个季度都少不了项目报告。这两天项目经理小刘,还在办公室吐槽项目绩效的数据实在太差了,询问如何能巧妙美化数据,这是…

算法学习day20

文章目录 513.找树左下角的值递归迭代 112 .路径总和递归迭代 113.路径总和II递归 106.从中序与后序遍历序列构造二叉树递归 105.从前序与中序遍历序列构造二叉树卡尔递归版本递归优化 总结 513.找树左下角的值 给定一个二叉树的 根节点 root,请找出该二叉树的 最底…

K8S从入门到精通之基本组件介绍

文章目录 0.前言k8s 的dashboard基本组件活动图 1. 基本概念1.1. kube-apiserver1.2. etcd1.3. kube-scheduler1.4. kube-controller-manager1.5. kubelet1.6. kube-proxy1.7. coredns:1.8. Container Runtime1.9. Ingress Controller1.10. Storage Plugin1.11. Das…

原点安全携“金融机构消费者个人信息保护解决方案”亮相 2023 中国金融数字化转型发展大会

6 月 7 日,由中国金融电子化集团有限公司、南京市建邺区人民政府、中国人民银行南京分行主办,主题为“数驱转型 智创未来”的「2023 中国金融数字化转型发展大会暨第十三届中国城市商业银行信息化发展创新座谈会」于南京国际博览中心隆重召开。 本次会议…

使用POI实现JAVA操作Excel

Apache POI POI提供API给JAVA程序对Microsoft Office格式档案读和写的功能 POI工具介绍 POI 是用Java编写的免费开源的跨平台的 Java API,Apache POI提供API给Java程式对Microsoft Office格式档案读和写的功能。主要是运用其中读取和输出excel的功能。 POI官网地…

每日一练 | 华为认证真题练习Day57

1、两台路由器通过PPP链路互连,管理员在两台路由器上配置了OSPF,且运行在同一个区域中,如果它们的Router ID相同,则下面描述正确的()。 A. 两台路由器将会建立正常的完全邻居关系 B. 两台路由器将不会互相…

看看苹果如何平衡Vision Pro性能和功耗

众所周知,Quest 2极为严格的控制SoC运行频率,目的就是保证整机的“散热性能”,说白了就是发热不能严重、风扇噪音不能大。这也是VR头戴设备中降低用户体验的两个关键指标。 对于Quest 2很直接的一个优势就是,用户大多时候听不到狂…

Javaweb学习路线(3)——SpringBoot入门、HTTP协议与Tomcat服务器

一、SpringBoot入门 &#xff08;一&#xff09;第一个Springboot案例 1、创建Springboot工程&#xff0c;添加依赖。 2、定义类&#xff0c;添加方法并添加注释 3、运行测试。 pom.xml&#xff08;框架自动生成&#xff09; <?xml version"1.0" encoding&quo…

linux上安装es、Kibana、ik分词

基本概念 ES是什么&#xff1f;我们可以把ES比作一个Mysql数据库&#xff0c;同样用来存储数据&#xff0c;不过比Mysql提供了更多的搜索功能,例如分词搜索&#xff0c;关联度搜索等&#xff0c;而且搜索速度也不是同一级别的&#xff0c; ES能够实现百万数据/秒的查询速度。接…

【Java|golang】1171. 从链表中删去总和值为零的连续节点

给你一个链表的头节点 head&#xff0c;请你编写代码&#xff0c;反复删去链表中由 总和 值为 0 的连续节点组成的序列&#xff0c;直到不存在这样的序列为止。 删除完毕后&#xff0c;请你返回最终结果链表的头节点。 你可以返回任何满足题目要求的答案。 &#xff08;注意…

Linux内核中内存管理相关配置项的详细解析9

接前一篇文章&#xff1a;Linux内核中内存管理相关配置项的详细解析8 十三、Enable recovery from hardware memory errors 对应配置变量为&#xff1a;CONFIG_MEMORY_FAILURE。 此项只有选中和不选中两种状态&#xff0c;默认为选中。 内核源码详细解释为&#xff1a; Enab…