Gin 框架学习实录 · 第8篇:使用 config.yaml 管理全局配置项

946 阅读4分钟

前言

在前面的章节中,我们已经完成了以下模块的封装:

  • 用户模块的增删改查
  • 通用分页 + 统一响应结构
  • zap + lumberjack 封装的日志模块

但是目前仍然存在一个“隐患”:

我们将很多重要的配置信息硬编码在了代码中,例如:

dsn := "root:123456@tcp(127.0.0.1:3306)/demo?..."
r.Run(":8080")

这不仅不利于环境迁移、测试切换,还增加了维护成本。

所以这一篇,我们将封装一个 全局配置模块

配置内容示例
服务端口:8080
数据库连接用户名、密码、地址、库名
日志配置日志路径、最大文件、是否压缩等

我们会将这些内容统一写入 config.yaml,在项目启动时自动读取并绑定到 Go 结构体中,全局可用。

🛠️ 我们即将动手:

  1. 创建配置文件 config.yaml
  2. 创建配置模块 config/config.go
  3. 使用 viper 读取配置并加载
  4. 全局访问配置项,替换掉硬编码部分

使用 Viper 管理配置文件

在 Go 中读取配置文件的方式有很多,例如:

  • 手动读取 YAML → 使用 yaml.Unmarshal
  • 使用第三方库(如 viper

我们选择 Viper,原因如下:

优点说明
支持多种配置格式JSON / YAML / TOML / ENV 等
热更新(可选)支持监听配置文件变化
绑定结构体配置直接映射到 Go 结构体中,代码更优雅
内建默认值提高健壮性

安装 Viper

go get github.com/spf13/viper

创建配置文件 config.yaml

在项目根目录下创建一个配置文件 config.yaml,用于统一管理服务端口、数据库连接、日志路径等内容。

# config.yaml

app:
  name: go-learn-notes
  port: 8080

mysql:
  host: 127.0.0.1
  port: 3306
  user: root
  password: 123456
  database: demo
  charset: utf8mb4

logger:
  level: info            # 支持:debug / info / warn / error
  file: logs/app.log     # 日志输出文件

创建配置模块 config/config.go

package config

import "github.com/spf13/viper"

type App struct {
    Name string `mapstructure:"name"`
    Port int    `mapstructure:"port"`
}

type MySQLInfo struct {
    Host     string `mapstructure:"host"`
    Port     int    `mapstructure:"port"`
    User     string `mapstructure:"user"`
    Password string `mapstructure:"password"`
    Database string `mapstructure:"database"`
    Charset  string `mapstructure:"charset"`
}

type Logger struct {
    Level string `mapstructure:"level"`
    File  string `mapstructure:"file"`
}

// Config 总配置结构体(对应 config.yaml)
type Config struct {
    App    App       `mapstructure:"app"`
    MySQL  MySQLInfo `mapstructure:"mysql"`
    Logger Logger    `mapstructure:"logger"`
}

var Conf *Config

// InitConfig 初始化配置
func InitConfig() {
    viper.SetConfigName("config") // 不带扩展名
    viper.SetConfigType("yaml")
    viper.AddConfigPath(".") // 当前目录

    if err := viper.ReadInConfig(); err != nil {
       panic("读取配置文件失败: " + err.Error())
    }

    if err := viper.Unmarshal(&Conf); err != nil {
       panic("解析配置文件失败: " + err.Error())
    }

    println("配置加载成功")
}

初始化加载配置(在 main.go 中)

package main

import (
    "gin-learn-notes/config"
    "gin-learn-notes/logger"
    "gin-learn-notes/router"
)

func main() {

    // 初始化配置
    config.InitConfig()

    // 初始化数据库
    config.InitDB()

    // 初始化日志
    logger.InitLogger()
    defer logger.Log.Sync()

    // 初始化路由
    r := router.InitRouter()

    // 启动服务
    r.Run(":8080")
}

修改数据库连接逻辑使用配置

config/database.go

package config

import (
    "fmt"
    "gorm.io/driver/mysql"
    "gorm.io/gorm"
    "gorm.io/gorm/schema"
)

var DB *gorm.DB

func InitDB() {
    mysqlConf := Conf.MySQL

    dsn := fmt.Sprintf(
       "%s:%s@tcp(%s:%d)/%s?charset=%s&parseTime=True&loc=Local",
       mysqlConf.User,
       mysqlConf.Password,
       mysqlConf.Host,
       mysqlConf.Port,
       mysqlConf.Database,
       mysqlConf.Charset,
    )
    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
       NamingStrategy: schema.NamingStrategy{
          SingularTable: true, // 表名不加 s,user 而不是 users
       },
    })
    if err != nil {
       panic("数据库连接失败:" + err.Error())
    }
    DB = db
}

修改日志链接逻辑使用配置

package logger

import (
    "gin-learn-notes/config"
    "github.com/natefinch/lumberjack"
    "go.uber.org/zap"
    "go.uber.org/zap/zapcore"
    "os"
    "strings"
)

var Log *zap.SugaredLogger

func InitLogger() {
    logConf := config.Conf.Logger

    // 设置日志输出格式为 JSON
    encoder := zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())

    // 设置输出位置(文件写入器)
    writeSyncer := getLogWriter(logConf.File)

    // 设置日志等级
    level := getLogLevel(logConf.Level)

    // 创建 core
    core := zapcore.NewCore(encoder, writeSyncer, level)

    // 创建 logger
    logger := zap.New(core, zap.AddCaller())
    Log = logger.Sugar()
}

// 日志等级转换
func getLogLevel(level string) zapcore.Level {
    switch strings.ToLower(level) {
    case "debug":
       return zapcore.DebugLevel
    case "warn":
       return zapcore.WarnLevel
    case "error":
       return zapcore.ErrorLevel
    default:
       return zapcore.InfoLevel
    }
}

func getLogWriter(filepath string) zapcore.WriteSyncer {
    lumberJackLogger := &lumberjack.Logger{
       Filename:   filepath,
       MaxSize:    10,
       MaxBackups: 5,
       MaxAge:     30,
       Compress:   true,
    }

    // 同时写入日志文件 + 控制台(开发环境)
    return zapcore.NewMultiWriteSyncer(
       zapcore.AddSync(lumberJackLogger),
       zapcore.AddSync(os.Stdout),
    )
}

修改端口配置:

func main() {

    // 初始化配置
    config.InitConfig()

    // 初始化数据库
    config.InitDB()

    // 初始化日志
    logger.InitLogger()
    defer logger.Log.Sync()

    // 初始化路由
    r := router.InitRouter()

    // 启动服务
    addr := fmt.Sprintf(":%d", config.Conf.App.Port)
    r.Run(addr)
}

现在我们就已经完成了:

  • 配置驱动的数据库连接
  • 配置驱动的日志输出路径 + 日志等级
  • 配置驱动的服务端口监听

项目结构继续演进(统一配置 + 日志模块优化)

gin-learn-notes/
├── config/
│   ├── config.go         // ✅ 配置读取模块(使用 viper)
│   └── database.go       // ✅ 使用配置初始化数据库
│
├── controller/
│   ├── hello.go
│   ├── index.go
│   └── user.go           // ✅ 用户模块接口
│
├── core/
│   └── response/
│       ├── code.go       // ✅ 错误码常量
│       ├── page.go       // ✅ 分页响应结构
│       └── response.go   // ✅ 通用响应封装
│
├── logger/
│   └── logger.go         // ✅ zap 日志封装(支持配置 + 文件切割)
│
├── logs/
│   └── app.log           // ✅ 日志文件目录(日志写入路径)
│
├── model/
│   └── user.go
│
├── request/
│   ├── page_request.go   // ✅ 通用分页请求结构
│   └── user_request.go   // ✅ 用户请求参数定义
│
├── router/
│   └── router.go         // ✅ 路由注册
│
├── service/
│   └── user_service.go   // ✅ 用户业务逻辑处理
│
├── utils/
│   ├── paginate.go       // ✅ 通用分页逻辑(泛型)
│   ├── response.go       // ✅ Success / Fail 响应封装(旧版)
│   └── validator.go      // ✅ 参数校验错误翻译工具
│
├── config.yaml           // ✅ 全局配置文件(端口、数据库、日志等)
├── go.mod
├── LICENSE
├── main.go               // ✅ 启动入口(按配置加载服务)
├── README.md

最后

在本篇中,我们继续完善了整个项目的配置能力,完成了:

  • 使用 viper 管理全局配置项;
  • 创建 config.yaml,集中管理服务端口、数据库、日志等配置;
  • 支持将数据库连接信息、日志路径、日志等级从配置文件中读取;
  • 启动入口 main.go 不再写死参数,更加灵活可控;

接下来我们开始引入Redis 缓存模块封装 并为接下来的 JWT 登录状态缓存 做好准备。

本篇对应代码提交记录

commit: f4aee1d5ab1888fb20686571e441c0c0e962fddf

👉 GitHub 源码地址:github.com/luokakale-k…