八卦阵 发表于 2025-1-26 11:10:19

Gin 学习条记

教程地址:https://www.bilibili.com/video/BV1FV4y1C72M?spm_id_from=333.788.videopod.sections&vd_source=707ec8983cc32e6e065d5496a7f79ee6
01-项目搭建



[*]各常用目录的说明:
https://github.com/golang-standards/project-layout/blob/master/README_zh.md
02-优雅启停



[*]用gin启动web服务器
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-路由

https://i-blog.csdnimg.cn/direct/83babe9c759043c195dc3ee5429c0379.png


[*]路由通过InitRouter初始化;路由与api操纵分离开来;api的路由设置与路由执行函数分开
[*]router/router.go :路由启动,并挂载user模块的路由
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中
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
package user

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

type HandlerUser struct{}

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

04-发送验证码

https://i-blog.csdnimg.cn/direct/d3c0f22017c64cf38ae73e4db3d6efc0.png


[*]创建消息模子
package model

type BusinessCode int
type Result struct {
        Code BusinessCode `json:"code"`
        Msgstring       `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
}


[*]手机验证
package lib

import "regexp"

func CheckMobile(mobile string) bool {
        if mobile == "" {
                return false
        }
        regular := "^1\\d{9}$"
        reg := regexp.MustCompile(regular)
        return reg.MatchString(mobile)
}



[*]运行状态代码
package model

const (
        NoLegalMobile BusinessCode = 2001
)



[*]修改getCaptcha,返回123456为验证码
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操纵

https://i-blog.csdnimg.cn/direct/d1acab6f788a459cbc1cad7eaa763bbe.png


[*]redis.go,安装:go get github.com/go-redis/redis/v8
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去实现
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生存
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
https://i-blog.csdnimg.cn/direct/9ce2b965e2354ff685e8b7f48f8b62be.png


[*]logs.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"`
        InfoFileNamestring `json:"infoFileName"`
        WarnFileNamestring `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("",
                                                zap.Any("error", err),
                                                zap.String("request", string(httpRequest)),
                                                zap.String("stack", string(debug.Stack())))
                                } else {
                                        lg.Error("",
                                                zap.Any("error", err),
                                                zap.String("request", string(httpRequest)))
                                }
                                c.AbortWithStatus(http.StatusInternalServerError)
                        }
                }()
                c.Next()
        }
}



[*]main.go里加入log
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里应用
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
https://i-blog.csdnimg.cn/direct/76950da09c3444df898ae479dfa77510.png


[*]config.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
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中应用
func main() {
        r := gin.Default()
       
        config.InitConfig()

        router.InitRouter(r)

        srv := &http.Server{
                Addr:    config.Conf.SC.Addr,
                Handler: r,
        }
        ...
}


[*]在redis.go中应用
func init() {
        rdb := redis.NewClient(config.Conf.ReadRedisConfig())
        Rc = &RedisCache{rdb: rdb}
}
结束:

   对于微服务,思量实验下利用Go-micro + Gin的方式,后续继续记录

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: Gin 学习条记