前言
在前面的章节中,我们已经完成了以下模块的封装:
- 用户模块的增删改查
- 通用分页 + 统一响应结构
- zap + lumberjack 封装的日志模块
但是目前仍然存在一个“隐患”:
我们将很多重要的配置信息硬编码在了代码中,例如:
dsn := "root:123456@tcp(127.0.0.1:3306)/demo?..."
r.Run(":8080")
这不仅不利于环境迁移、测试切换,还增加了维护成本。
所以这一篇,我们将封装一个 全局配置模块
| 配置内容 | 示例 |
|---|---|
| 服务端口 | :8080 |
| 数据库连接 | 用户名、密码、地址、库名 |
| 日志配置 | 日志路径、最大文件、是否压缩等 |
我们会将这些内容统一写入 config.yaml,在项目启动时自动读取并绑定到 Go 结构体中,全局可用。
🛠️ 我们即将动手:
- 创建配置文件
config.yaml - 创建配置模块
config/config.go - 使用
viper读取配置并加载 - 全局访问配置项,替换掉硬编码部分
使用 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…