Gin 学习笔记

教程地址:https://www.bilibili.com/video/BV1FV4y1C72M?spm_id_from=333.788.videopod.sections\&vd_source=707ec8983cc32e6e065d5496a7f79ee6


01-项目搭建


02-优雅启停

  • 用gin启动web服务器
go 复制代码
package main

import (
	"context"
	"github.com/gin-gonic/gin"
	"log"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"time"
)

func main() {
	r := gin.Default()
	srv := &http.Server{
		Addr:    ":5000",
		Handler: r,
	}

	// 通过协程启动服务
	go func() {
		log.Printf("server listen at %s", srv.Addr)
		if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			log.Fatalf("listen: %s\n", err)
		}
	}()

	// 制作按Ctrl+C退出功能,此处阻塞
	quit := make(chan os.Signal)
	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
	<-quit

	// 系统停止开始
	log.Println("Shutdown Server ...")
	
	// 等待2秒
	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
	defer cancel()
	// 停止服务
	if err := srv.Shutdown(ctx); err != nil {
		log.Fatal("Server Shutdown:", err)
	}
	// 2秒后打印
	select {
	case <-ctx.Done():
		log.Println("timeout of 2 seconds.")
	}
	log.Println("Server exiting")
}

03-路由

  • 路由通过InitRouter初始化;路由与api操作分离开来;api的路由设置与路由执行函数分开
  • router/router.go :路由启动,并挂载user模块的路由
go 复制代码
package router

import (
	"Gin_gPRC/api/user"
	"github.com/gin-gonic/gin"
)

// Router 接口,规定里面有一个Route函数
type Router interface {
	Route(r *gin.Engine)
}

// 定义一个RegisterRouter 注册类,这个类有一个Route方法
// Route方法接收一个符合Router接口规范的对象
// Route方法里运行了接收对象里的Route方法,用以绑定路由
type RegisterRouter struct{}
func New() *RegisterRouter {
	return &RegisterRouter{}
}
func (*RegisterRouter) Route(router Router, r *gin.Engine) {
	router.Route(r)
}

// 初始化路由
func InitRouter(r *gin.Engine) {
	router := New()
	// 把user.RouterUser对象给到注册类,委托运行了user里的Route方法,传递r参数
	router.Route(&user.RouterUser{}, r)
}
  • api/user/route.go;注册了/login/getCaptcha的POST接口,执行函数写再user.go中
go 复制代码
package user

import "github.com/gin-gonic/gin"

type RouterUser struct{}

func (*RouterUser) Route(r *gin.Engine) {
	handler := &HandlerUser{}
	r.POST("/login/getCaptcha", handler.getCaptcha)
}
  • api/user/user.go;执行POST
go 复制代码
package user

import "github.com/gin-gonic/gin"

type HandlerUser struct{}

func (*HandlerUser) getCaptcha(ctx *gin.Context) {
	ctx.JSON(200, "getCaptcha test")
}

04-发送验证码

  • 建立消息模型
go 复制代码
package model

type BusinessCode int
type Result struct {
	Code BusinessCode `json:"code"`
	Msg  string       `json:"msg"`
	Data any          `json:"data"`
}

func (r *Result) Success(data any) *Result {
	r.Code = 200
	r.Msg = "success"
	r.Data = data
	return r
}

func (r *Result) Fail(code BusinessCode, msg string) *Result {
	r.Code = code
	r.Msg = msg
	return r
}
  • 手机验证
go 复制代码
package lib

import "regexp"

func CheckMobile(mobile string) bool {
	if mobile == "" {
		return false
	}
	regular := "^1[3-9]\\d{9}$"
	reg := regexp.MustCompile(regular)
	return reg.MatchString(mobile)
}
  • 运行状态代码
go 复制代码
package model

const (
	NoLegalMobile BusinessCode = 2001
)
  • 修改getCaptcha,返回123456为验证码
go 复制代码
package user

import (
	"Gin_gPRC/lib"
	"Gin_gPRC/model"
	"github.com/gin-gonic/gin"
	"log"
	"time"
)

type HandlerUser struct{}

func (*HandlerUser) getCaptcha(ctx *gin.Context) {
	//ctx.JSON(200, "getCaptcha test")
	rsp := &model.Result{}
	//1. 获取参数
	mobile := ctx.PostForm("mobile")
	//2. 校验参数
	if !lib.CheckMobile(mobile) {
		ctx.JSON(200, rsp.Fail(model.NoLegalMobile, "手机号码错误"))
		return
	}
	//3. 生成验证码
	code := "123456"
	//4. 调用短信平台接口
	go func() {
		time.Sleep(2 * time.Second)
		log.Println("短信平台调用成功")
	}()
	ctx.JSON(200, rsp.Success(code))
}

05-redis操作

go 复制代码
package dao

import (
	"context"
	"github.com/go-redis/redis/v8"
	"time"
)

var Rc *RedisCache
type RedisCache struct {
	rdb *redis.Client
}

func init() {
	rdb := redis.NewClient(&redis.Options{
		Addr:     "localhost:6379",
		Password: "",
		DB:       0,
	})

	Rc = &RedisCache{rdb: rdb}
}

func (rc *RedisCache) Put(ctx context.Context, key string, value string, expire time.Duration) error {
	err := rc.rdb.Set(ctx, key, value, expire).Err()
	return err
}

func (rc *RedisCache) Get(ctx context.Context, key string) (string, error) {
	result, err := rc.rdb.Get(ctx, key).Result()
	return result, err
}
  • repo/cache.go,定义Cache的接口,再由dao里的redis.go去实现
go 复制代码
package repo

import (
	"context"
	"time"
)

type Cache interface {
	Put(ctx context.Context, key string, value string, expire time.Duration) error
	Get(ctx context.Context, key string) (string, error)
}
  • user.go,加入redis保存
go 复制代码
package user

import (
	"Gin_gPRC/dao"
	"Gin_gPRC/lib"
	"Gin_gPRC/model"
	"Gin_gPRC/repo"
	"context"
	"github.com/gin-gonic/gin"
	"log"
	"time"
)

type HandlerUser struct {
	cache repo.Cache
}

func New() *HandlerUser {
	return &HandlerUser{
		cache: dao.Rc,
	}
}

func (h *HandlerUser) getCaptcha(ctx *gin.Context) {
	//ctx.JSON(200, "getCaptcha test")
	rsp := &model.Result{}
	//1. 获取参数
	mobile := ctx.PostForm("mobile")
	//2. 校验参数
	if !lib.CheckMobile(mobile) {
		ctx.JSON(200, rsp.Fail(model.NoLegalMobile, "手机号码错误"))
		return
	}
	//3. 生成验证码
	code := "123456"
	//4. 调用短信平台接口
	go func() {
		time.Sleep(2 * time.Second)
		log.Println("短信平台调用成功")

		// 制作一个超时的上下文
		c, cancel := context.WithTimeout(context.Background(), 2*time.Second)
		defer cancel()

		// redis加入
		err := h.cache.Put(c, "REGISTER"+mobile, code, 15*time.Minute)
		if err != nil {
			log.Printf("验证码存入redis出错,%v \n", err)
		}
	}()
	ctx.JSON(200, rsp.Success(code))
}

06-日志

安装:go get -u go.uber.org/zap

安装:go get -u github.com/natefinch/lumberjack

  • logs.go
go 复制代码
package lib

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

var lg *zap.Logger

type LogConfig struct {
	DebugFileName string `json:"debugFileName"`
	InfoFileName  string `json:"infoFileName"`
	WarnFileName  string `json:"warnFileName"`
	MaxSize       int    `json:"maxSize"`
	MaxAge        int    `json:"maxAge"`
	MaxBackups    int    `json:"maxBackups"`
}

func InitLogger(cfg *LogConfig) (err error) {
	writeSyncerDebug := getLogWriter(cfg.DebugFileName, cfg.MaxSize, cfg.MaxBackups, cfg.MaxAge)
	writeSyncerInfo := getLogWriter(cfg.InfoFileName, cfg.MaxSize, cfg.MaxBackups, cfg.MaxAge)
	writeSyncerWarn := getLogWriter(cfg.WarnFileName, cfg.MaxSize, cfg.MaxBackups, cfg.MaxAge)
	encoder := getEncoder()

	debugCore := zapcore.NewCore(encoder, writeSyncerDebug, zapcore.DebugLevel)
	infoCore := zapcore.NewCore(encoder, writeSyncerInfo, zapcore.InfoLevel)
	warnCore := zapcore.NewCore(encoder, writeSyncerWarn, zapcore.WarnLevel)

	consoleEncoder := zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig())
	std := zapcore.NewCore(consoleEncoder, zapcore.Lock(os.Stdout), zapcore.DebugLevel)
	core := zapcore.NewTee(debugCore, infoCore, warnCore, std)
	lg = zap.New(core, zap.AddCaller())
	zap.ReplaceGlobals(lg)
	return

}

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.NewJSONEncoder(encoderConfig)
}

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)
}

func GinLogger() gin.HandlerFunc {
	return func(c *gin.Context) {
		start := time.Now()
		path := c.Request.URL.Path
		query := c.Request.URL.RawQuery
		c.Next()

		cost := time.Since(start)
		lg.Info(path,
			zap.Int("status", c.Writer.Status()),
			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()),
			zap.String("errors", c.Errors.ByType(gin.ErrorTypePrivate).String()),
			zap.Duration("cost", cost),
		)
	}
}

func GinRecovery(stack bool) gin.HandlerFunc {
	return func(c *gin.Context) {
		defer func() {
			if err := recover(); err != nil {
				var brokenPipe bool
				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
						}
					}
				}

				httpRequest, _ := httputil.DumpRequest(c.Request, false)
				if brokenPipe {
					lg.Error(c.Request.URL.Path,
						zap.Any("error", err),
						zap.String("request", string(httpRequest)))
					c.Error(err.(error))
					c.Abort()
					return
				}

				if stack {
					lg.Error("[Recovery from panic]",
						zap.Any("error", err),
						zap.String("request", string(httpRequest)),
						zap.String("stack", string(debug.Stack())))
				} else {
					lg.Error("[Recovery from panic]",
						zap.Any("error", err),
						zap.String("request", string(httpRequest)))
				}
				c.AbortWithStatus(http.StatusInternalServerError)
			}
		}()
		c.Next()
	}
}
  • main.go里加入log
go 复制代码
package main

import (
	"Gin_gPRC/lib"
	"Gin_gPRC/router"
	"context"
	"github.com/gin-gonic/gin"
	"log"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"time"
)

func main() {
	r := gin.Default()

	//log
	lc := &lib.LogConfig{
		DebugFileName: "./logs/debug.log",
		InfoFileName:  "./logs/info.log",
		WarnFileName:  "./logs/warn.log",
		MaxSize:       500,
		MaxAge:        28,
		MaxBackups:    3,
	}
	err := lib.InitLogger(lc)
	if err != nil {
		log.Fatal(err)
	}

	router.InitRouter(r)

	srv := &http.Server{
		Addr:    ":5000",
		Handler: r,
	}

	go func() {
		log.Printf("server listen at %s", srv.Addr)
		if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			log.Fatalf("listen: %s\n", err)
		}
	}()

	quit := make(chan os.Signal)
	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
	<-quit

	log.Println("Shutdown Server ...")

	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
	defer cancel()
	if err := srv.Shutdown(ctx); err != nil {
		log.Fatal("Server Shutdown:", err)
	}
	select {
	case <-ctx.Done():
		log.Println("timeout of 2 seconds.")
	}
	log.Println("Server exiting")
}
  • 在user.go里应用
go 复制代码
package user

import (
	"Gin_gPRC/dao"
	"Gin_gPRC/lib"
	"Gin_gPRC/model"
	"Gin_gPRC/repo"
	"context"
	"github.com/gin-gonic/gin"
	"go.uber.org/zap"
	"time"
)

type HandlerUser struct {
	cache repo.Cache
}

func New() *HandlerUser {
	return &HandlerUser{
		cache: dao.Rc,
	}
}

func (h *HandlerUser) getCaptcha(ctx *gin.Context) {
	//ctx.JSON(200, "getCaptcha test")
	rsp := &model.Result{}
	//1. 获取参数
	mobile := ctx.PostForm("mobile")
	//2. 校验参数
	if !lib.CheckMobile(mobile) {
		ctx.JSON(200, rsp.Fail(model.NoLegalMobile, "手机号码错误"))
		return
	}
	//3. 生成验证码
	code := "123456"
	//4. 调用短信平台接口
	go func() {
		time.Sleep(2 * time.Second)
		zap.L().Info("短信平台调用成功")

		// 制作一个超时的上下文
		c, cancel := context.WithTimeout(context.Background(), 2*time.Second)
		defer cancel()

		// redis加入
		err := h.cache.Put(c, "REGISTER"+mobile, code, 15*time.Minute)
		if err != nil {
			zap.L().Error("验证码存入redis出错" + err.Error())
		}
	}()
	ctx.JSON(200, rsp.Success(code))
}

07-配置

安装:go get github.com/spf13/viper

  • config.yaml
yaml 复制代码
server:
  name: "Gin_gRPC"
  addr: "127.0.0.1:5000"
zap:
  debugFileName: "./logs/debug.log"
  infoFileName: "./logs/info.log"
  warnFileName: "./logs/warn.log"
  maxSize: 500,
  maxAge: 28,
  maxBackups: 3
redis:
  host: "localhost"
  port: 6379
  password: ""
  db: 0
  • config/config.go
go 复制代码
package config

import (
	"Gin_gPRC/lib"
	"github.com/go-redis/redis/v8"
	"github.com/spf13/viper"
	"log"
)

var Conf = InitConfig()

type Config struct {
	viper *viper.Viper
	SC    *ServerConfig
}

type ServerConfig struct {
	Name string
	Addr string
}

func InitConfig() *Config {
	conf := &Config{viper: viper.New()}
	//workDir, _ := os.Getwd()
	// 确定配置文件的名称、类型与位置
	conf.viper.SetConfigName("config")
	conf.viper.SetConfigType("yaml")
	conf.viper.AddConfigPath("./")

	err := conf.viper.ReadInConfig()
	if err != nil {
		log.Fatalf("Fatal error config file: %s \n", err)
	}

	// 读取Server的配置
	conf.ReadServerConfig()
	// 初始化zapLog
	conf.InitZapLog()
	return conf
}

func (c *Config) InitZapLog() {
	lc := &lib.LogConfig{
		DebugFileName: c.viper.GetString("zap.debugFileName"),
		InfoFileName:  c.viper.GetString("zap.infoFileName"),
		WarnFileName:  c.viper.GetString("zap.warnFileName"),
		MaxSize:       c.viper.GetInt("zap.maxSize"),
		MaxAge:        c.viper.GetInt("zap.maxAge"),
		MaxBackups:    c.viper.GetInt("zap.maxBackups"),
	}
	err := lib.InitLogger(lc)
	if err != nil {
		log.Fatal(err)
	}
}

func (c *Config) ReadServerConfig() {
	sc := &ServerConfig{}
	sc.Name = c.viper.GetString("server.name")
	sc.Addr = c.viper.GetString("server.addr")
	c.SC = sc
}

func (c *Config) ReadRedisConfig() *redis.Options {
	return &redis.Options{
		Addr:     c.viper.GetString("redis.host") + ":" + c.viper.GetString("redis.port"),
		Password: c.viper.GetString("redis.password"),
		DB:       c.viper.GetInt("redis.db"),
	}
}
  • 在main.go中应用
go 复制代码
func main() {
	r := gin.Default()
	
	config.InitConfig()

	router.InitRouter(r)

	srv := &http.Server{
		Addr:    config.Conf.SC.Addr,
		Handler: r,
	}
	...
}
  • 在redis.go中应用
go 复制代码
func init() {
	rdb := redis.NewClient(config.Conf.ReadRedisConfig())
	Rc = &RedisCache{rdb: rdb}
}

结束:

对于微服务,考虑尝试下使用Go-micro + Gin的方式,后续继续记录

相关推荐
海尔辛1 小时前
学习黑客三次握手快速熟悉
网络·学习·tcp/ip
_Jyuan_2 小时前
镜头内常见的马达类型(私人笔记)
经验分享·笔记·数码相机
丰锋ff4 小时前
考研英一学习笔记 2018年
笔记·学习·考研
1296004524 小时前
pytorch基础的学习
人工智能·pytorch·学习
岂是尔等觊觎4 小时前
软件设计师教程——第一章 计算机系统知识(下)
经验分享·笔记·其他
Oll Correct4 小时前
计算机二级WPS Office第三套电子表格
笔记
睡不着还睡不醒5 小时前
【笔记】unsqueeze
笔记
LouSean5 小时前
Unity按钮事件冒泡
经验分享·笔记·学习·unity·游戏引擎
pq113_65 小时前
OrangePi Zero 3学习笔记(Android篇)4 - eudev编译(获取libudev.so)
android·笔记·学习
AI新视界6 小时前
『Python学习笔记』ubuntu解决matplotlit中文乱码的问题!
linux·笔记·ubuntu