Go web框架初整合 | 青训营

188 阅读5分钟

当你在 Go 中构建一个 Web 应用程序时,使用 Gin 框架、Gorm ORM、Viper 配置库、Zap 日志库和 Redis 数据库可以让你高效地开发功能丰富的应用程序。

1、项目初始化

我为里通过Goland创建一个go 项目

位置:就是你项目保存在本地的路径

GOROOT:Go 语言安装根目录的路径,也就是 GO 语言的安装路径。

环境:一般是直接访问国内地址,我上面这是国内七牛云的 GOPROXY=https://goproxy.cn,direct

1.1 安装依赖

go get github.com/redis/go-redis/v8 //v8 对应Redis6
go get github.com/spf13/viper
go get go.uber.org/zap
go get gorm.io/driver/mysql
go get gorm.io/gorm
go get github.com/gin-gonic/gin

1.2 配置文件管理

webapp 下创建一个 config.yaml 配置文件,示例如下:

port: "8080"
log:
  level: "debug"
  filename: "日志文件名字.log"
  max_size: 200
  max_age: 30
  max_backups: 7

mysql:
  host: "你的ip地址"
  port: 3306
  username: "你的用户名"
  password: "你的密码"
  dbname: "你的数据库名"

redis:
  host: "你的ip地址"
  port: 6379
  db: 7
  password: "你的密码"
  pool_size: 100

1.3 初始化viper

是适用于Go应用程序的完整配置解决方案。它被设计用于在应用程序中工作,并且可以处理所有类型的配置需求和格式。

webappconfig目录,然后创建一个settings.go文件,示例如下:

package config

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

var Conf = new(AppConfig)

type AppConfig struct {
    Port         string `mapstructure:"port"`
    *LogConfig   `mapstructure:"log"`
    *MySQLConfig `mapstructure:"mysql"`
    *RedisConfig `mapstructure:"redis"`
}

type LogConfig struct {
    Level      string `mapstructure:"level"`
    Filename   string `mapstructure:"filename"`
    MaxSize    int    `mapstructure:"max_size"`
    MaxAge     int    `mapstructure:"max_age"`
    MaxBackups int    `mapstructure:"max_backups"`
}

type MySQLConfig struct {
    Host     string `mapstructure:"host"`
    Port     int    `mapstructure:"port"`
    Username string `mapstructure:"username"`
    Password string `mapstructure:"password"`
    Dbname   string `mapstructure:"dbname"`
}

type RedisConfig struct {
    Host     string `mapstructure:"host"`
    Port     int    `mapstructure:"port"`
    DB       int    `mapstructture:"db"`
    Password string `mapstructure:"password"`
    PoolSize int    `mapstructure:"pool_size"`
}

// Init 结构体形式
func Init() (err error) {
    viper.SetConfigName("config")     //指定配置文件名称(不需要带后缀)
    viper.SetConfigType("yaml")       //指定配置文件类型(专用于从远程获取配置信息时,指定配置)
    viper.AddConfigPath("./settings") //指定配置文件路径(这里是相对路径)
    err = viper.ReadInConfig()
    if err != nil {
       fmt.Printf("viper.ReadInConfig() failed,err:%v\n", err)
       return
    }
    //把读取到的配置信息反序列化到Conf变量中
    if err := viper.Unmarshal(Conf); err != nil {
       fmt.Printf("viper.Unmarshal failed,err:%v\n", err)
    }
    return
}

1.4 初始化zap日志

zap是非常快的、结构化的,分日志级别的Go日志库

在config目录下创建logger.go文件,示例如下

package config

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

var Conf = new(AppConfig)

type AppConfig struct {
    Port         string `mapstructure:"port"`
    *LogConfig   `mapstructure:"log"`
    *MySQLConfig `mapstructure:"mysql"`
    *RedisConfig `mapstructure:"redis"`
}

type LogConfig struct {
    Level      string `mapstructure:"level"`
    Filename   string `mapstructure:"filename"`
    MaxSize    int    `mapstructure:"max_size"`
    MaxAge     int    `mapstructure:"max_age"`
    MaxBackups int    `mapstructure:"max_backups"`
}

type MySQLConfig struct {
    Host     string `mapstructure:"host"`
    Port     int    `mapstructure:"port"`
    Username string `mapstructure:"username"`
    Password string `mapstructure:"password"`
    Dbname   string `mapstructure:"dbname"`
}

type RedisConfig struct {
    Host     string `mapstructure:"host"`
    Port     int    `mapstructure:"port"`
    DB       int    `mapstructture:"db"`
    Password string `mapstructure:"password"`
    PoolSize int    `mapstructure:"pool_size"`
}

// Init 结构体形式
func Init() (err error) {
    viper.SetConfigName("config")     //指定配置文件名称(不需要带后缀)
    viper.SetConfigType("yaml")       //指定配置文件类型(专用于从远程获取配置信息时,指定配置)
    viper.AddConfigPath(".")          //指定配置文件路径(这里是相对路径)
    err = viper.ReadInConfig()
    if err != nil {
       fmt.Printf("viper.ReadInConfig() failed,err:%v\n", err)
       return
    }
    //把读取到的配置信息反序列化到Conf变量中
    if err := viper.Unmarshal(Conf); err != nil {
       fmt.Printf("viper.Unmarshal failed,err:%v\n", err)
    }
    return
}

1.5 初始化Gorm

webapp下创建一个dao目录再创建mysql目录,然后创建一个mysql.go文件,示例如下:

package mysql

func Init() (db *gorm.DB, err error) {
    cfg := settings.Conf.MySQLConfig
    fmt.Printf("%v", cfg.Dbname)
    dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local",
       cfg.Username, cfg.Password, cfg.Host, cfg.Port, cfg.Dbname,
    )
    db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
       zap.L().Error("connect DB failed,err:%v\n", zap.Error(err))
       return
    }
    return
}

1.6 初始化Redis

webapp下创建一个dao目录再创建redis目录,然后创建一个redis.go文件,示例如下

package redis

var rdb *redis.Client

func Init(cfg *settings.RedisConfig) (err error) {
    ctx := context.Background()
    rdb = redis.NewClient(&redis.Options{
       Addr: fmt.Sprintf("%s:%d",
          cfg.Host, cfg.Port,
       ),
       Password: cfg.Password,
       DB:       cfg.DB,
       PoolSize: cfg.PoolSize,
    })
    _, err = rdb.Ping(ctx).Result()
    return
}

1.7 初始化路由

在webapp下创建routes路由,然后创建routes.go文件,示例如下

package routes

func Setup(h *controller.Handler) *gin.Engine {
    gin.SetMode(gin.ReleaseMode)
    r := gin.New()
    r.Use(logger.GinLogger(), logger.GinRecovery(true))
    r := gin.Default()

    r.Static("/static", "../static") //静态文件
    r.GET("/hello", func(c *gin.Context) {
       c.String(http.StatusOK, "ok")
    })
    r.POST("/ddd/user/register/", h.UserHandler.Register)
}

1.8 main函数

在webapp下,创建main.go文件,我们需要初始化Viper配置管理库,初始化日志,初始化Handler(构建依赖),注册路由,示例如下 :

package main

import (
    "fmt"
    "go.uber.org/zap"
    "go_web/config"
    "go_web/routes"
)

// go web 开发通用的脚手架模板
func main() {
    initServer()
}

func initServer() {
    //1、加载配置
    if err := config.Init(); err != nil {
       fmt.Printf("init settings failed,err:%v\n", err)
       return
    }
    zap.L().Debug("settings init success...")
    //2、初始化日志
    if err := config.Init(config.Conf.LogConfig); err != nil {
       fmt.Printf("init logger failed,err:%v\n", err)
       return
    }
    defer zap.L().Sync()
    zap.L().Debug("logger init success...")

    //3、初始化Handler
    handler, err := controller.BuildInjector()
    if err != nil {
       panic(err)
    }

    //4、注册路由
    r := routes.Setup(handler)
    fmt.Println("listen port:", config.Conf.Port)
    r.Run(fmt.Sprintf(":%s", config.Conf.Port))
}

2、具体实践

2.1 Controller层handler

在这里把所有依赖导入,这里也可以使用Wire框架,实现依赖注入。在参考文章有提供

package controller

import (
    "go_web/dao/mysql"
    "go_web/service/impl"
)

type Handler struct {
    UserHandler *UserHandler
}

func NewHandler(userHandler *UserHandler) *Handler {
    return &Handler{
       UserHandler: userHandler,
    }
}

// BuildInjector 导入所有依赖
func BuildInjector() (*Handler, error) {
    db, err := mysql.Init()
    if err != nil {
       return nil, err
    }

    //dao
    userDao := mysql.NewUserDao(db)

    //service
    userService := impl.NewUserService(userDao)

    //handler
    userHandler := NewUserHandler(userService)

    //处理层
    handler := NewHandler(userHandler)
    return handler, nil
}

2.2 UserHanler

处理层定义一个或多个Service,对外提供一个函数,实现依赖注入。其他层也一样

type UserHandler struct {
    UserService *impl.UserService
}

func NewUserHandler(userService *impl.UserService) *UserHandler {
    return &UserHandler{UserService: userService}
}

func (h *UserHandler) Register(ctx *gin.Context) {
    h.UserService.Register()
}

2.3 Service层

如下, 我只定义了一个接口,然后通过impl包下userServiceImpl.go实现接口。还有就是我会在userService下定义一个结构体,对应MySQL表的结构体不同,返回一些前端需要的参数,即字段相对于mysql表下的字段,可能增加或减少。

userService

type userService interface {
    Register(name, password string) (err error)
}

impl/userServiceImpl

type UserService struct {
    UserDao *mysql.UserDao
}

func NewUserService(userDao *mysql.UserDao) *UserService {
    return &UserService{UserDao: userDao}
}

func (srv *UserService) Register(name, password string) (id int64, err error) {
    newUser := &mysql.User{Name: name, Password: EnCoder(password)}
    return srv.UserDao.InsertUser(newUser)
}

也可以通过gorm的Transaction函数实现事务,保证数据的一致性。如下代码:

image.png

2.4 Dao 层

Dao层,最好保证方法的单一性,即一个方法实现一个功能。复杂的逻辑还是在业务层实现吧

func NewUserDao(db *gorm.DB) *UserDao {
    return &UserDao{DB: db}
}

type UserDao struct {
    DB *gorm.DB
}
type User struct {
    ID              int64 `json:"id"`
    Name string
    Password stirng
}
func (dal *UserDao) InsertUser(user *User) (id int64, err error) {
    if err = dal.DB.Create(&user).Error; err != nil {
       zap.L().Error("UserDao.InsertUser failed", zap.Error(err))
       return 0, ErrorInsert
    }
    return int64(user.ID), nil
}

3、项目结构图

参考

www.liwenzhou.com/posts/Go/za…

www.liwenzhou.com/posts/Go/za…

juejin.cn/post/714685…

www.liwenzhou.com/posts/Go/vi…