使用Gin框架快速构建Web项目

1 概述

Gin是Golang的一个web框架, 封装比较优雅, API友好, 源码注释比较明确, 具有快速灵活, 容错方便等特点.对于想我这样的Go语言初学者, 选用Gin来构建Web服务是一个不错的选择.

构建一个简单的Web项目,除了Gin框架还用到的库有

  • viper: 读取配置文件,将配置文件的内容映射到结构体上
  • zap: 非常快的, 结构化的, 分级别的日志库
  • lumberjack: 处理日志文件轮转, 日志打到一定大小时进行轮转,避免日志文件过大
  • gorm: 对象关系映射(ORM)框架
  • swagger: 接口文档

创建Go的项目,目录如图所示:

引入gin:

 go get -u github.com/gin-gonic/gin

2 解析配置文件

创建配置文件初始化函数,使用viper解析配置文件, 将yaml文件的内容映射到结构体中,使用哪个文件可以在启动命令中指定。项目启动的时候在主函数调用此函数初始化配置文件。

2.1 引入viper

go get github.com/spf13/viper

 

2.2 创建配置文件

name: "shopping"
mode: "dev"
ip: "127.0.0.1"
port: 8080
version: "v0.0.1"
# 停机超时时间 单位秒
shutdown_overtime: 5

# 雪花算法配置
# 起始时间, 可以自定义开始时间
start_time: "2022-06-01"
#机器id, 多实例机器id不能相同
machine_id: 1

# 日志配置
log:
  # 日志等级
  level: "debug"
  # 日志文件的位置
  filename: "./log/shopping.log"
  # 在进行切割之前,日志文件的最大大小(以MB为单位)
  max_size: 200
  # 保留旧文件的最大天数
  max_age: 30
  # 保留旧文件的最大个数
  max_backups: 7

# mysql数据库配置
mysql:
  host: "127.0.0.1"
  port: 3306
  user: user
  password: psaaword
  dbname: "go_shopping"
  # 最大连接数
  max_open_conns: 100
  # 最大空闲数
  max_idle_conns: 10

jwt:
  secret_key: "abcde"

2.3 解析配置文件

package config

import (
	"fmt"
	"github.com/fsnotify/fsnotify"
	"github.com/spf13/viper"
)

// Conf 定义全局变量
// new()函数 获取指定类型的指针, 有点像Java中的创建对象
var Conf = new(ServConfig)

type ServConfig struct {
	Name             string `mapstructure:"name"`              // 服务名称
	Mode             string `mapstructure:"mode"`              // 环境
	Version          string `mapstructure:"version"`           // 版本
	ShutdownOvertime int    `mapstructure:"shutdown_overtime"` // 停机超时时间

	IP   string `mapstructure:"ip"`
	Port string `mapstructure:"port"`

	// 雪花算法配置
	StartTime string `mapstructure:"start_time"`
	MachineId int64  `mapstructure:"machine_id"`

	// 日志配置
	*LogConfig `mapstructure:"log"`
	// MySQL配置
	*MySQLConfig `mapstructure:"mysql"`
	// JWT 配置信息
	*JwtConfig `mapstructure:"jwt"`
}

// LogConfig 日志配置
type LogConfig struct {
	// 日志级别
	Level string `mapstructure:"level"`
	// 志文件的位置
	Filename string `mapstructure:"filename"`
	// 在进行切割之前,日志文件的最大大小(以MB为单位)
	MaxSize int `mapstructure:"max_size"`
	// 保留旧文件的最大天数
	MaxAge int `mapstructure:"max_age"`
	// 保留旧文件的最大个数
	MaxBackups int `mapstructure:"max_backups"`
}

// MySQLConfig MySQL配置信息
type MySQLConfig struct {
	Host     string `mapstructure:"host"`
	User     string `mapstructure:"user"`
	Password string `mapstructure:"password"`
	DB       string `mapstructure:"dbname"`
	Port     int    `mapstructure:"port"`
	// 最大连接数
	MaxOpenConns int `mapstructure:"max_open_conns"`
	// 最大空闲连接数
	MaxIdleConns int `mapstructure:"max_idle_conns"`
}

// JwtConfig Jwt 配置信息
type JwtConfig struct {
	SecretKey string `mapstructure:"secret_key"`
}

// Init 整个服务配置文件初始化的方法 (程序启动的时候加载配置文件)
func Init(filPath string) (err error) {
	// 直接指定配置文件路径(相对路径或者绝对路径)
	viper.SetConfigFile(filPath)

	// 读取配置文件信息
	if err = viper.ReadInConfig(); err != nil {
		return err
	}

	// 把配置文件中的信息反序列化到Conf全局变量中, 在其他地方用到配置文件中的属性的时候,可以使用全局变量中的属性
	if err = viper.Unmarshal(Conf); err != nil {
		return err
	}

	// 配置文件的监听
	viper.WatchConfig()
	// 配置文件发生变化之后,会执行下面钩子函数的监听代码
	viper.OnConfigChange(func(in fsnotify.Event) {
		fmt.Println("配置文件被修改了")
		if err := viper.Unmarshal(Conf); err != nil {
			fmt.Printf("viper.Unmarshal failed, err:%v\n", err)
		}
	})
	return
}

3 初始化日志

go语言提供的默认的Go Lagger只提供了基本的日志级别,不支持INFO/DEBUG等多级别。缺乏日志格式化的能力,不能记录调用者的函数名和行号,格式化日期和时间格式。不提供日志切割的能力。

Zap提供了两种类型的日志记录器—Sugared Logger和Logger。

在性能很好但不是很关键的上下文中,使用SugaredLogger。它比其他结构化日志记录包快4-10倍,并且支持结构化和printf风格的日志记录。

在每一微秒和每一次内存分配都很重要的上下文中,使用Logger。它甚至比SugaredLogger更快,内存分配次数也更少,但它只支持强类型的结构化日志记录。

创建初始化日志的函数对zap及逆行初始化,在项目主函数中进行调用。

3.1 引入zap&lumberjack

go get go.uber.org/zap
go get go.uber.org/zap/zapcore
go get gopkg.in/natefinch/lumberjack.v2

3.2 初始化日志配置

package logger

import (
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"gopkg.in/natefinch/lumberjack.v2"
	"os"
	"shopping/config"
)

var Log *zap.Logger

// zap日志的三要素
// 1. encdor编码  2.输出位置  3. 日志级别

// Init 初始化日志
// 如果需要根据环境打印不同的日志, 也可以根据配置文件中的mode 来作为判断条件, 在这个函数的入参里面增加一个mode, 将配置文件里的数据传进来
func Init(cfg *config.LogConfig) (err error) {
	writerSyncer := getLogWriter(cfg.Filename, cfg.MaxSize, cfg.MaxBackups, cfg.MaxAge)
	encoder := getEncoder()
	var l = new(zapcore.Level)
	if err = l.UnmarshalText([]byte(cfg.Level)); err != nil {
		return err
	}
	core := zapcore.NewTee(
		zapcore.NewCore(encoder, writerSyncer, l),
		zapcore.NewCore(encoder, zapcore.Lock(os.Stdout), l),
	)
	// 初始化一个全局对象, 并添加调用栈信息
	Log = zap.New(core, zap.AddCaller())
	zap.ReplaceGlobals(Log) // 替换zap包全局的logger
	zap.L().Info("日志初始化成功")
	return
}

// getLogWriter 指定日志将写到哪里去
// 使用第三方库做日志切分,记录日志的时候同时做一个日志的轮转和切分
func getLogWriter(filename string, maxSize, maxBackup, maxAge int) zapcore.WriteSyncer {
	lumberJackLogger := &lumberjack.Logger{
		Filename:   filename,
		MaxSize:    maxSize,
		MaxBackups: maxBackup,
		MaxAge:     maxAge,
	}
	return zapcore.AddSync(lumberJackLogger)
}

// getEncoder 编码器(定义如何写入日志)
func getEncoder() zapcore.Encoder {
	encoderConfig := zap.NewProductionEncoderConfig()
	// 修改时间格式
	encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
	encoderConfig.TimeKey = "time"
	encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
	encoderConfig.EncodeDuration = zapcore.SecondsDurationEncoder
	encoderConfig.EncodeCaller = zapcore.ShortCallerEncoder
	return zapcore.NewConsoleEncoder(encoderConfig)
}

4 初始化Gorm创建数据库链接

4.1 引入gorm

go get -u gorm.io/gorm
go get -u gorm.io/driver/mysql

4.2 初始化数据库连接

package mysql

import (
	"context"
	"fmt"
	"go.uber.org/zap"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	gormLogger "gorm.io/gorm/logger"
	"shopping/config"
	"shopping/logger"
	"time"
)

// Db 全局的Db对象
var Db *gorm.DB

// Init 初始化数据库
func Init(cfg *config.MySQLConfig) (err error) {
	// 参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 获取详情
	// parseTime=True可以将数据库中的时间类型映射成Go语言中的时间类型  	loc=Local 地区
	dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local", cfg.User, cfg.Password, cfg.Host, cfg.Port, cfg.DB)
	Db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{
		Logger: loggerCallback{logger.Log},
	})
	if err != nil {
		return err
	}
	// 额外的连接配置
	// 使用sqlDB设置连接池
	sqlDB, err := Db.DB() // database/sql.DB
	if err != nil {
		return err
	}
	// 以下配置要配合 my.conf 进行配置
	// SetMaxIdleConns 设置空闲连接池中连接的最大数量
	sqlDB.SetMaxOpenConns(cfg.MaxIdleConns)
	// SetMaxOpenConns 设置打开数据库连接的最大数量。
	sqlDB.SetMaxOpenConns(cfg.MaxOpenConns)
	// SetConnMaxLifetime 设置了连接可复用的最大时间。
	sqlDB.SetConnMaxLifetime(time.Hour)
	logger.Log.Info("数据库初始化成功")
	return
}

// loggerCallback 实现了 GORM 的回调接口
type loggerCallback struct {
	logger *zap.Logger
}

// LogMode 使用 Zap 打印 SQL 语句
func (l loggerCallback) LogMode(level gormLogger.LogLevel) gormLogger.Interface {
	return l
}

// Info 使用 Zap 打印 SQL 语句
func (l loggerCallback) Info(ctx context.Context, msg string, data ...interface{}) {
	l.logger.Info(fmt.Sprintf(msg, data...))
}

// Warn 使用 Zap 打印 SQL 语句
func (l loggerCallback) Warn(ctx context.Context, msg string, data ...interface{}) {
	l.logger.Warn(fmt.Sprintf(msg, data...))
}

// Error 使用 Zap 打印 SQL 语句
func (l loggerCallback) Error(ctx context.Context, msg string, data ...interface{}) {
	l.logger.Error(fmt.Sprintf(msg, data...))
}

// Trace 使用 Zap 打印 SQL 语句
func (l loggerCallback) Trace(ctx context.Context, begin time.Time, fc func() (string, int64), err error) {
	if err != nil {
		sql, rows := fc()
		l.logger.Error("gorm trace error", zap.Error(err), zap.String("sql", sql), zap.Int64("rows", rows))
	} else {
		sql, rows := fc()
		l.logger.Debug("gorm trace", zap.String("sql", sql), zap.Int64("rows", rows), zap.Duration("elapsed", time.Since(begin)))
	}
}

4.3 初始话雪花算法

4.3.1 引入雪花算法

go get github.com/bwmarrin/snowflake

4.3.2 初始化

package snowflake

import (
	"errors"
	"time"

	sf "github.com/bwmarrin/snowflake"
)

const (
	_dafaultStartTime = "2020-12-31" // 默认开始时间
)

var node *sf.Node

// Init 雪花算法组件初始化,正常应该把雪花算法当成一个独立的服务部署
// startTime 开始时间
// machineID 机器id
func Init(startTime string, machineID int64) (err error) {
	if machineID < 0 {
		return errors.New("snowflake need machineID")
	}
	if len(startTime) == 0 {
		startTime = _dafaultStartTime
	}
	var st time.Time
	st, err = time.Parse("2006-01-02", startTime)
	if err != nil {
		return
	}
	sf.Epoch = st.UnixNano() / 1000000 // 时间戳的开始时间,默认从1970年开始计算
	node, err = sf.NewNode(machineID)  // 机器编号,最多1024
	return
}

// GenID 生成18位雪花算法id
func GenID() int64 {
	return node.Generate().Int64()
}

// GenIDStr 生成18位雪花算法id
func GenIDStr() string {
	return node.Generate().String()
}

5 创建接口

以查询接口举例

package category

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"net/http"
	"shopping/model"
	"shopping/service"
	"shopping/utils/api_helper"
	"shopping/utils/pagination"
	"shopping/utils/snowflake"
	"time"
)

// Controller 分类控制器
type Controller struct {
	categoryService *service.CategoryService
}

// NewCategoryController 实例化控制器
func NewCategoryController(s *service.CategoryService) *Controller {
	return &Controller{
		categoryService: s,
	}
}

// GetCategories 查询分类列表
// @Summary 获得分类列表
// @Tags Category
// @Produce json
// @param page query int false "Page number"
// @param pageSize query int false "Page size"
// @Success 200 {object} pagination.Pages
// @Router /category [get]
func (c *Controller) GetCategories(g *gin.Context) {
	page := pagination.NewFormGinRequest(g, -1)
	page = c.categoryService.GetALl(page)
	g.JSON(http.StatusOK, page)
}
// CategoryService 商品分类Service
type CategoryService struct {
	categoryDao *mysql.CategoryDao
}

// NewCategoryService 实例化商品分类service
func NewCategoryService(dao *mysql.CategoryDao) *CategoryService {
	return &CategoryService{
		categoryDao: dao,
	}
}

// GetALl 获得分页商品分类
func (service *CategoryService) GetALl(page *pagination.Pages) *pagination.Pages {
	categories, count, err := service.categoryDao.GetAll(page.Page, page.PageSize)
	if err != nil {

	}
	page.Items = categories
	page.TotalCount = count
	return page
}
// CategoryDao 商品分类Dao
type CategoryDao struct {
	db *gorm.DB
}

// NewCategoryDao 创建NewCategoryDao
func NewCategoryDao(db *gorm.DB) *CategoryDao {
	return &CategoryDao{
		db: db,
	}
}

// GetAll 获得分页商品分类
func (dao *CategoryDao) GetAll(pageIndex, pageSize int) ([]model.Category, int, error) {
	var categories []model.Category
	var count int64

	err := dao.db.Offset((pageIndex - 1) * pageSize).Limit(pageSize).Find(&categories).Count(&count).Error
	return categories, int(count), err
}

6 Swagger

6.1 引入swagger

go get -u github.com/swaggo/swag/cmd/swag
go get github.com/swaggo/gin-swagger
go get github.com/swaggo/gin-swagger/swaggerFiles

6.2 在接口和主函数中添加注释

// GetCategories 查询分类列表
// @receiver Controller
// @Summary 获得分类列表
// @Tags Category
// @Produce json
// @param page query int false "Page number"
// @param pageSize query int false "Page size"
// @Success 200 {object} pagination.Pages
// @Router /category [get]
func (c *Controller) GetCategories(g *gin.Context) {
	page := pagination.NewFormGinRequest(g, -1)
	page = c.categoryService.GetALl(page)
	g.JSON(http.StatusOK, page)
}
// CreateCategory
// @Description 创建分类
// @receiver Controller
// @Summary 根据给定的参数创建分类
// @Tags Category
// @Accept json
// @Produce json
// @param Authorization header string true "Authorization header"
// @param CreateCategoryRequest body CreateCategoryRequest true "category information"
// @Success 200 {object} api_helper.Response
// @Failure 400 {object} api_helper.ErrorResponse
// @Router /category [post]
func (c *Controller) CreateCategory(g *gin.Context) {
	var req CreateCategoryRequest
	if err := g.ShouldBind(&req); err != nil {
		// 解析参数异常
		api_helper.HandleError(g, err)
		return
	}
	newCategory := model.NewCategory(req.Name, req.Desc)
	newCategory.CreateTime = time.Now()
	newCategory.Id = snowflake.GenID()
	err := c.categoryService.Create(newCategory)
	if err != nil {
		api_helper.HandleError(g, err)
		return
	}
	g.JSON(http.StatusCreated, api_helper.Response{
		Message: "category created",
	})
}
package main

import (
	"shopping/bootstrap"
	_ "shopping/docs"
)

// @title 电商demo
// @description go语言学习项目
// @version 1.0
// @contact.name liu
// @contact.url http://.....
// @host localhost:8080
// @BasePath
func main() {
	// 运行项目
	bootstrap.Run()
}

 6.3 安装swap

go install github.com/swaggo/swag/cmd/swag

 使用swag.init生成swagger文件

7 编写主函数

在主函数中调用以上所有初始化函数,注册自定义中间件(日志和异常保护),注册控制器,启动服务,添加监听停机处理。

package main

import (
	"shopping/bootstrap"
	_ "shopping/docs"
)

// @title 电商demo
// @description go语言学习项目
// @version 1.0
// @contact.name liu
// @contact.url http://.....
// @host localhost:8080
// @BasePath
func main() {
	// 运行项目
	bootstrap.Run()
}
package bootstrap

import (
	"context"
	"flag"
	"fmt"
	"github.com/gin-gonic/gin"
	swaggerFiles "github.com/swaggo/files"
	ginSwagger "github.com/swaggo/gin-swagger"
	"go.uber.org/zap"
	"net/http"
	"os"
	"os/signal"
	"shopping/config"
	"shopping/controller"
	"shopping/dao/mysql"
	"shopping/logger"
	"shopping/utils/middleware"
	"shopping/utils/snowflake"
	"syscall"
	"time"
)

// Run 运行项目
func Run() {
	// 初始化资源
	initBeforeRun()
	// 启动服务
	srv := runGinServer()
	// 监听关闭服务
	shutdown(srv)
}

// initBeforeRun 初始化各种资源
func initBeforeRun() {
	// 加载配置文件
	initConfig()
	// 初始化日志
	initLogger()
	// 初始化MYSQL
	initMySQL()
	// 初始化雪花算法
	initSnowflake()
}

// initConfig 加载配置文件
func initConfig() {
	// 配置文件路径
	var cfn string
	// 用来解析命令行中的命令, 可以在命令行中执行配置, 命令行中没有指定配置文件路径, 使用项目中的配置文件
	// 无论是直接运行, 还是生成可执行文件运行, 都可以在命令后面追加 -conf="./conf/config-dev.yaml" 指定配置文件
	// go run main.go -conf="./conf/config-uat.yaml"
	flag.StringVar(&cfn, "conf", "./conf/config-dev.yaml", "指定配置文件路径")
	// 解析命令行标志
	flag.Parse()
	// 加载配置文件
	err := config.Init(cfn)
	if err != nil {
		panic(err)
	}
}

// initLogger 初始化日志
func initLogger() {
	err := logger.Init(config.Conf.LogConfig)
	if err != nil {
		panic(err)
	}
}

// initMySQL 初始化MySQL
func initMySQL() {
	err := mysql.Init(config.Conf.MySQLConfig)
	if err != nil {
		panic(err)
	}
}

// initSnowflake 初始化雪花算法
func initSnowflake() {
	err := snowflake.Init(config.Conf.StartTime, config.Conf.MachineId)
	if err != nil {
		panic(err)
	}
}

// runGinServer 启动gin服务
func runGinServer() *http.Server {
	// 获取gin引擎
	r := gin.New()
	// 注册中间件
	registerMiddlewares(r)
	// 注册控制器
	controller.RegisterHandlers(r)
	// 注册swagger
	//url := ginSwagger.URL("/swagger/docs.json") // The url pointing to API definition
	r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
	// 创建服务器
	srv := &http.Server{
		Addr:    fmt.Sprintf("%s:%s", config.Conf.IP, config.Conf.Port),
		Handler: r,
	}
	go func() {
		// 启动HTTP服务器
		if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			logger.Log.Fatal("监听并启动服务失败: \n", zap.Error(err))
		}
	}()
	logger.Log.Info("服务已经启动",
		zap.String("url", fmt.Sprintf("http://localhost:%s", config.Conf.Port)),
		zap.String("swaggerUrl", fmt.Sprintf("http://localhost:%s/swagger/index.html", config.Conf.Port)))
	return srv
}

// shutdown 听信号, 执行关机操作
// param srv 需要关闭的Http Server实例
func shutdown(srv *http.Server) {
	// 定义一个触发退出的信号, 通道的元素是os.Signal, 是操作系统的信号
	quit := make(chan os.Signal)
	// 操作发出syscall.SIGTERM和syscall.SIGINT这两个信号的时候,会把它通知到自定义的quite中
	// kill (没有参数) 默认是 syscall.SIGTERM
	// kill -2 是 syscall.SIGINT
	// kill -9 是 syscall.
	signal.Notify(quit, syscall.SIGTERM, syscall.SIGINT)
	// 启动之后 这里会阻塞, 等到退出的时候(通道里面有值)才会往下执行
	<-quit
	logger.Log.Info("开始关闭服务...")

	// 创建context 并设置超时时间, 确保服务关闭的操作在指定时间内完成
	ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(config.Conf.ShutdownOvertime))
	defer cancel()

	// 其他操作, 例如在注册中心中注销服务

	// 调用Http实例的Shutdown方法 关闭服务器
	if err := srv.Shutdown(ctx); err != nil {
		logger.Log.Fatal("服务关闭错误: ", zap.Error(err))
	}
	logger.Log.Info("关闭服务完成...")
}

// registerMiddlewares 注册中间件
func registerMiddlewares(r *gin.Engine) {
	// 打印日志
	r.Use(middleware.GinLogger(logger.Log))
	// 异常保护
	r.Use(middleware.GinRecovery(logger.Log, true))
}
// Package middleware
// Author liuzhiyong
// Date 2023/11/16
// Description 基础的中间件 做日志打印 和 recover
package middleware

import (
	"bytes"
	"fmt"
	"github.com/gin-gonic/gin"
	"go.uber.org/zap"
	"net"
	"net/http"
	"net/http/httputil"
	"os"
	"runtime/debug"
	"strings"
	"time"
)

// CustomResponseWriter 自定义的ResponseWriter 用于获取响应数据
type CustomResponseWriter struct {
	gin.ResponseWriter
	body *bytes.Buffer // 响应体缓存
}

// Write
func (w CustomResponseWriter) Write(b []byte) (int, error) {
	w.body.Write(b)
	return w.ResponseWriter.Write(b)
}

// WriteString
func (w CustomResponseWriter) WriteString(s string) (int, error) {
	w.body.WriteString(s)
	return w.ResponseWriter.WriteString(s)
}

// GinLogger 日志中间件
func GinLogger(logger *zap.Logger) gin.HandlerFunc {
	return func(c *gin.Context) {
		start := time.Now()
		path := c.Request.URL.Path
		query := c.Request.URL.RawQuery
		logger.Info(fmt.Sprintf("请求开始: %s", path),
			zap.String("method", c.Request.Method),
			zap.String("path", path),
			zap.String("query", query),
			zap.String("ip", c.ClientIP()),
			zap.String("user-agent", c.Request.UserAgent()),
		)
		// 替换运来的Writer
		blw := &CustomResponseWriter{body: bytes.NewBufferString(""), ResponseWriter: c.Writer}
		c.Writer = blw
		c.Next()
		logger.Info(fmt.Sprintf("请求结束: %s", path),
			zap.Int("status", c.Writer.Status()),
			zap.String("path", path),
			zap.String("response", blw.body.String()),
			zap.String("errors", c.Errors.ByType(gin.ErrorTypePrivate).String()),
			zap.Int64("cost", time.Since(start).Microseconds()),
		)

	}
}

// GinRecovery  recover掉项目可能出现的panic
// param: logger
// param: stack 是否打印堆栈信息
func GinRecovery(logger *zap.Logger, stack bool) gin.HandlerFunc {
	return func(c *gin.Context) {
		defer func() {
			// 执行recover() 捕获异常
			if err := recover(); err != nil {
				//检查是否有断开的连接,因为这并不是一个真正需要进行紧急堆栈跟踪的条件。
				var brokenPipe bool
				// 如果异常时net.OpError类型 ==> 转为 对应类型 err.() 类型断言的写法, 判断类型, 转换类型
				if ne, ok := err.(*net.OpError); ok {
					if se, ok := ne.Err.(*os.SyscallError); ok {
						if strings.Contains(strings.ToLower(se.Error()), "broken pipe") ||
							strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") {
							brokenPipe = true
						}
					}
				}
				// 将http请求转换成字节切面, 第二个布尔类型的参数表示是否包括请求体, 为false的话表示只包括请求头
				httpRequest, _ := httputil.DumpRequest(c.Request, false)
				if brokenPipe {
					logger.Error(c.Request.URL.Path,
						zap.Any("error", err),
						zap.String("request", string(httpRequest)),
					)
					// If the connection is dead, we can't write a status to it.
					c.Error(err.(error)) // nolint: errcheck
					c.Abort()
					return
				}
				if stack {
					logger.Error("[Recovery from panic]",
						zap.Any("error", err),
						zap.String("request", string(httpRequest)),
						zap.String("stack", string(debug.Stack())),
					)
				} else {
					logger.Error("[Recovery from panic]",
						zap.Any("error", err),
						zap.String("request", string(httpRequest)),
					)
				}
				c.AbortWithStatus(http.StatusInternalServerError)
				// c.JSON(200, gin.H{"code": 1, "msg": "出错啦"})
				// return
			}
		}()
		c.Next() // 调用后续的逻辑
	}
}

  • 7
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
《Go Web开发进阶实战(Gin框架)》是一本针对Go语言的Web开发者的进阶实战书籍。该书主要以Gin框架作为核心内容,通过实际案例的讲解,帮助读者深入理解和掌握Gin框架使用技巧。 Gin框架是一个轻量级、高性能的Go语言Web框架,提供了许多强大的功能和特性,如路由、中间件、模板渲染等。本书主要介绍了Gin框架的基本概念和用法,并通过实战项目的方式,帮助读者实际运用Gin框架构建Web应用。 该书内容包括了Gin框架的安装与配置、路由和中间件的使用、参数的绑定与验证、错误处理、模板渲染、文件上传与下载、会话管理等方面的知识。通过对这些知识点的深入理解和实践,读者将能够掌握Gin框架的核心功能,并学会如何在实际项目中应用。 此外,本书还通过实战案例来演示如何使用Gin框架构建不同类型的Web应用,如博客系统、用户认证、API接口等。每个案例都包含详细的代码实现和讲解,读者可以通过跟随案例一步一步地实现,并深入理解Gin框架在实际项目中的应用场景。 总之,通过《Go Web开发进阶实战(Gin框架)》这本书的学习,读者可以系统地学习和掌握Gin框架使用,并通过实战案例的实践来提升自己的Web开发能力。无论是初学者还是有一定经验的开发者,都能够从中受益,快速掌握Go语言Web开发中使用Gin框架的技巧和经验。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值