5.2. 利用gin搭建一个api框架(上篇)

未匹配的标注

在前一篇文章中,我们简单使用了net/http搭建了一个server,其实在日常开发中,比较少去使用标准库去直接写api,更多的是使用前人搭建好的轮子(我呢,是个不太喜欢重复造轮子的开发者,有开源的靠谱的,直接用就好,自己调整成自己需要的即可),那么说的go的框架,不得不说gin了。

对于gin的介绍,是github上star最好的go框架了,其他不多说,我们上手写起来吧!

目标:

  • 自定义配置
  • 整合mysql和redis
  • 独立路由管理
  • 日志
  • 平滑重启
  • 脚本打包

使用到的库:

初始化

modules的引入之后,我们就可以不必使用gopath去管理项目目录了,对于modules的基本使用,建议看文章:

github.13sai.com/2019/12/27/219/#m...

我们开始:

go mod init sai0556/demo2-gin-frame

因为我们暂时本地开发:

// 使用本地module
go mod edit -require=local.com/sai0556/demo2-gin-frame@v1.0.0
go mod edit -replace=local.com/sai0556/demo2-gin-frame@v1.0.0=$PWD

可以看到go.mod已生成:

module sai0556/demo2-gin-frame

go 1.13

require local.com/sai0556/demo2-gin-frame v1.0.0

replace local.com/sai0556/demo2-gin-frame v1.0.0 => /Users/@/Work/golang/go-example/demo2-gin-frame

/Users/@/Work/golang/go-example/demo2-gin-frame 此目录就是项目目录,视具体情况不一

自定义配置与读取:

在我们使用redis和mysql之前,我们先来读取一下配置,配置呢我们使用常见的yaml,当然你也可以使用其他,比如env等。

新建config目录,用来读取与监听配置文件(config.yaml):

package config

import (
    "fmt"

    "github.com/fsnotify/fsnotify"
    "github.com/spf13/viper"
)

type Config struct {
    Name string
}

// 对外的初始化配置方法
func Run(cfg string) error {
    c := Config{
        Name: cfg,
    }

    if err := c.init(); err != nil {
        return err
    }

    c.watchConfig()

    return nil
}

func (c *Config) init() error {
    if c.Name != "" {
        viper.SetConfigFile(c.Name)
    } else {
        // 默认配置文件是./config.yaml
        viper.AddConfigPath(".")
        viper.SetConfigName("config")
    }

    viper.SetConfigType("yaml")
    // viper解析配置文件
    err := viper.ReadInConfig() 
    if err != nil {
        panic(fmt.Errorf("Fatal error config file: %s \n", err))
    }

    // 简单打印下配置
    fmt.Println(viper.GetString("name"))

    return nil
}

func (c *Config) watchConfig() {
    viper.WatchConfig()
    viper.OnConfigChange(func(e fsnotify.Event) {
        fmt.Println("Config file changed:", e.Name)
    })
}

main:

package main

import (
    "github.com/spf13/pflag"

    "local.com/sai0556/demo2-gin-frame/config"
)

var (
    conf = pflag.StringP("config", "c", "", "config filepath")
)

func main() {
    pflag.Parse()

    // 初始化配置
    if err := config.Run(*conf); err != nil {
        panic(err)
    }

}

这里有用到大牛spf13的两个包,pflag和viper,命令行参数解析包pflag可以看作flag的进阶版本,在我们这里可以用来指定配置文件,viper是读取配置文件的包,配合fsnotify可以实现配置的热更新。(spf13大神还有其他有用的包,相信在你的go编码生涯会用到的)

写完我们可以运行一下:

go run main.go -c ./config.yaml

可以看到有打印出我们配置的name。

整合mysql与redis

mysql包我们就选用gorm,redis的使用比较多的是redigo和go-redis,redigo曾在我使用中出现过问题,因而我们选择后者,后者也支持连接池。

mysql:

package db

import (
    "fmt"
    "sync"
    "errors"

    orm "github.com/jinzhu/gorm"
    _ "github.com/jinzhu/gorm/dialects/mysql"
    "github.com/spf13/viper"
)

type MySqlPool struct {}

var instance *MySqlPool
var once sync.Once

var db *orm.DB
var err error 

// 单例模式
func GetInstance() *MySqlPool {
    once.Do(func() {
        instance = &MySqlPool{}
    })

    return instance
}

func (pool *MySqlPool) InitPool() (isSuc bool) {
    // 这里有一种常见的拼接字符串的方式
    dsn := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=%s", viper.GetString("db.username"), viper.GetString("db.password"), viper.GetString("db.host"), viper.GetString("db.name"), viper.GetString("db.charset"))
    db, err = orm.Open("mysql", dsn)
    if err != nil {
        panic(errors.New("mysql连接失败"))
        return false
    }

    // 连接数配置也可以写入配置,在此读取
    db.DB().SetMaxIdleConns(viper.GetInt("db.MaxIdleConns"))
    db.DB().SetMaxOpenConns(viper.GetInt("db.MaxOpenConns"))
    // db.LogMode(true)
    return true
}

后面获取连接池就可以直接使用 db.GetInstance()

redis:

package db

import (
    "fmt"

    "github.com/spf13/viper"
    "github.com/go-redis/redis"
)

var RedisClient *redis.Client

func InitRedis() {
    RedisClient = redis.NewClient(&redis.Options{
        Addr:     fmt.Sprintf("%s:%s", viper.GetString("redis.host"), viper.GetString("redis.port")),
        Password: viper.GetString("redis.auth"),
        DB:       0,
    })

    _, err := RedisClient.Ping().Result()
    if err != nil {
        panic("redis ping error")
    }
}

RedisClient就是我们后面可以用的redis连接池。

在main中加入初始化连接池的代码即可:

// 连接mysql数据库
btn := db.GetInstance().InitPool()
if !btn {
    log.Println("init database pool failure...")
    panic(errors.New("init database pool failure"))
}

// redis
db.InitRedis()

路由与控制器

为了方便路由,我们把路由管理单独到router。

package router

import (
    "net/http"

    "github.com/gin-gonic/gin"

    "local.com/sai0556/demo2-gin-frame/controller"
)

func Load(g *gin.Engine) *gin.Engine {
    g.Use(gin.Recovery())
    // 404
    g.NoRoute(func (c *gin.Context)  {
        c.String(http.StatusNotFound, "404 not found");
    })

    g.GET("/", controller.Index)

    return g
}

controller:

package controller

import (
    "net/http"

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

// 返回
type Response struct {
    Code int `json:"code"`
    Message string `json:"message"`
    Data interface{} `json:"data"`
}

// api返回结构
func ApiResponse(c *gin.Context, code int, message string, data interface{}) {
    c.JSON(http.StatusOK, Response{
        Code: code,
        Message: message,
        Data: data,
    })
}

func Index(c *gin.Context) {
    ApiResponse(c, 0, "success", nil)
}

到这呢,基本也就差不多了。

我们来看下完整的main:

package main

// import 这里我习惯把官方库,开源库,本地module依次分开列出
import (
    "log"
    "errors"

    "github.com/spf13/pflag"
    "github.com/spf13/viper"
    "github.com/gin-gonic/gin"

    "local.com/sai0556/demo2-gin-frame/config"
    "local.com/sai0556/demo2-gin-frame/db"
    "local.com/sai0556/demo2-gin-frame/router"
)

var (
    conf = pflag.StringP("config", "c", "", "config filepath")
)

func main() {
    pflag.Parse()

    // 初始化配置
    if err := config.Run(*conf); err != nil {
        panic(err)
    }

    // 连接mysql数据库
    btn := db.GetInstance().InitPool()
    if !btn {
        log.Println("init database pool failure...")
        panic(errors.New("init database pool failure"))
    }

    // redis
    db.InitRedis()

    gin.SetMode(viper.GetString("mode"))
    g := gin.New()
    g = router.Load(g)
    g.Run(viper.GetString("addr"))
}

篇幅原因,剩下的日志、平滑重启等我们下一篇接着说。

详细代码件下面:

https://github.com/13sai/go-example/tree/main/demo2-gin-frame 有用不妨star一个


关注和赞赏都是对笔者最大的支持
关注和赞赏都是对笔者最大的支持

本文章首发在 LearnKu.com 网站上。

上一篇 下一篇
讨论数量: 0
发起讨论 只看当前版本


暂无话题~