本日关键词 (实战):项目结构、cmd、internal、config、环境变量、os.Getenv、.env、godotenv、viper、配置结构体、配置注入
本日语法/概念(实战):
| 语法/概念 | 实战用途 | 本日示例 |
|---|---|---|
cmd/、internal/、config/ |
标准目录划分,便于协作与部署 | day3 目录结构 |
os.Getenv("KEY") |
读环境变量,部署时改配置不改代码 | config.LoadWithOS(未调用,仅对比) |
.env + godotenv |
本地开发把配置写在 .env,不提交 Git | cmd/server 里 Load |
| viper(SetDefault、BindEnv、Unmarshal) | 多源配置、类型解析、默认值,贴近生产 | internal/config/config.go |
| 配置结构体 + 启动时加载 | 一次加载注入 handler/service,避免到处读 env | cmd/server、config.Load |
获取实战代码 :如需在本地跑通本文示例,请克隆仓库 WenSongWang/go-quickstart-7days,本文示例在 day3 目录,克隆后在项目根目录执行下文中的命令即可。
一、本篇目标
学完本文并跑通本目录示例,你将掌握:
| 模块 | 内容 |
|---|---|
| 目录 | 常见结构:cmd/、internal/、config/ |
| 环境变量 | os.Getenv、.env 文件(godotenv) |
| 配置 | viper 多源与类型解析、配置结构体在启动时加载并注入 |
二、前置要求
- 已完成 Day 1、Day 2。
- 本系列命令均在项目根目录执行。
三、示例与知识点(先混个眼熟)
| 示例目录 | 主要知识点 |
|---|---|
cmd/server/ |
main 入口、godotenv.Load 加载 .env、config.Load() 读配置、用 cfg.HTTPPort 起 HTTP 服务 |
internal/config/ |
Config 结构体、viper SetDefault/BindEnv/Unmarshal;Load 为主入口,LoadWithOS 仅作对比(本目录未调用) |
config/ |
.env.example 模板,复制为 .env 后勿提交 |
四、核心概念与最小示例(不看代码也能懂)
为什么要把配置放进结构体、启动时加载一次?
避免在 handler、service 里到处写 os.Getenv("HTTP_PORT"):改端口要搜全项目,且类型要自己转。启动时读进一个 Config 结构体,再把这个结构体(或指针)传给需要用的地方,改配置只动一处,类型在 Load 时统一解析好。
godotenv 和 viper 分别干啥?
- godotenv :把
.env文件里的键值对加载进进程的环境变量 (相当于执行了一堆os.Setenv)。之后os.Getenv或 viper 读环境变量时就能读到 .env 里的值。只负责"把 .env 灌进 env"。 - viper :从环境变量、配置文件、默认值 等多源读配置,支持类型解析(如 HTTP_PORT 直接 GetInt)、默认值、BindEnv 绑定。生产里多环境、多源配置常用 viper;本日先用 viper 读 env(.env 已由 godotenv 灌入 env)。
DB_DSN 和 Day 4 的关系(为何 .env 里是 day4 的路径?)
本日 Config 里有 DB_DSN,.env.example 里默认是 file:./day4/data.db。Day 3 不会连数据库 ,只是把「将来给数据库用的连接串」放进配置。Day 4(数据层) 才会真正用 cfg.DBDSN 去 sql.Open、做 CRUD;本系列先用 SQLite,所以默认写 SQLite 路径,且路径放在 day4 下是因为实际连库、建表的代码在 Day 4。学到 Day 3 时只需知道:这是为下一天的数据层预留的配置项即可。
易踩坑:配置加载顺序
若用 .env,必须在调用 config.Load() 之前 执行 godotenv.Load("day3/config/.env"),否则 viper 读环境变量时 .env 里的值还没进去,读到的仍是系统环境或空。解法 :main 里先 godotenv.Load,再 config.Load()。
五、Day 3 示例代码与逐段解读
1. cmd/server/main.go
go
// Day3 示例:带配置的 HTTP 服务(项目结构:cmd + config)
package main
import (
"encoding/json"
"log"
"net/http"
"strconv"
"github.com/go-quickstart-7days/day3/internal/config"
"github.com/joho/godotenv"
)
func main() {
// 可选:从 day3/config/.env 加载到环境变量,必须在 config.Load 之前
_ = godotenv.Load("day3/config/.env")
cfg := config.Load()
log.Printf("启动服务 env=%s port=%d", cfg.AppEnv, cfg.HTTPPort)
http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(`{"status":"ok"}`))
})
http.HandleFunc("/info", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
body, _ := json.Marshal(map[string]interface{}{"env": cfg.AppEnv, "port": cfg.HTTPPort})
w.Write(body)
})
addr := ":" + strconv.Itoa(cfg.HTTPPort)
log.Fatal(http.ListenAndServe(addr, nil))
}
解读 :易踩坑 :若先 config.Load() 再 godotenv.Load,.env 未灌入 env,viper 读不到;解法 即本段顺序:先 godotenv 再 Load。用 cfg.HTTPPort 拼监听地址;/health 做健康检查,/info 返回当前 cfg.AppEnv、cfg.HTTPPort,方便验证配置生效。
说明 :上面 import "github.com/go-quickstart-7days/day3/internal/config" 是本项目本地包路径 ,不会从 GitHub 在线拉代码,详见 Day 0 六、Go 模块与 import 路径 或仓库根目录 go.mod 顶部注释。
2. internal/config/config.go(viper 版 Load)
- Config 结构体 :
AppEnv、HTTPPort、DBDSN,带mapstructure:"app_env"等 tag,供下面 Unmarshal 时把 viper 里的 key 填到对应字段。 - 数据流关系 :BindEnv 管「环境变量名 → viper 内部 key」(如
APP_ENV→ viper 的app_env);mapstructure + Unmarshal 管「viper 内部 key → 结构体字段」(如 viper 的app_env→Config.AppEnv)。两处用同一套 key(app_env、http_port、db_dsn)串起来。 - Load() :
SetDefault设默认值,BindEnv绑定环境变量,Unmarshal(&cfg)按 mapstructure 填进 Config;类型解析由 viper 完成(如 HTTP_PORT 自动转 int)。 - LoadWithOS() :仅用
os.Getenv的简易版,便于对比「不用 viper 时」的写法;本目录 main 未调用,仅作对比。
六、运行当天代码
在项目根目录执行:
bash
cp day3/config/.env.example day3/config/.env
go run ./day3/cmd/server
访问:http://localhost:8080/health 或 http://localhost:8080/info(/info 返回当前 env、port,可验证配置生效)。演练时 :改 .env 里的 HTTP_PORT 或 APP_ENV,重启服务再访问 /info 观察变化;勿将 .env 提交到 Git。
七、学习建议
- 看目录 :打开
day3看cmd/、internal/、config/如何组织。 - 改配置 :改
.env或环境变量,重启go run ./day3/cmd/server,观察行为变化。 - 理解「配置只加载一次、在 main 里注入到各层」,后面 Day 4~7 都会沿用。
八、小结
Day 3 建立「项目结构 + 配置」的习惯,并引入 viper 做多源与类型解析;本日不连数据库 ,DB_DSN 只是留给 Day 4 数据层用的配置项。后面 Day 4 会用到这份配置真正连 SQLite/Postgres,中间件与综合实战也都会在同一套结构上扩展。