[7天实战入门Go语言后端] Day 3:项目结构与配置——目录组织、环境变量与 viper

本日关键词 (实战):项目结构、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.dbDay 3 不会连数据库 ,只是把「将来给数据库用的连接串」放进配置。Day 4(数据层) 才会真正用 cfg.DBDSNsql.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.AppEnvcfg.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 结构体AppEnvHTTPPortDBDSN,带 mapstructure:"app_env" 等 tag,供下面 Unmarshal 时把 viper 里的 key 填到对应字段。
  • 数据流关系BindEnv 管「环境变量名 → viper 内部 key」(如 APP_ENV → viper 的 app_env);mapstructure + Unmarshal 管「viper 内部 key → 结构体字段」(如 viper 的 app_envConfig.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/healthhttp://localhost:8080/info/info 返回当前 env、port,可验证配置生效)。演练时 :改 .env 里的 HTTP_PORTAPP_ENV,重启服务再访问 /info 观察变化;勿将 .env 提交到 Git。

七、学习建议

  1. 看目录 :打开 day3cmd/internal/config/ 如何组织。
  2. 改配置 :改 .env 或环境变量,重启 go run ./day3/cmd/server,观察行为变化。
  3. 理解「配置只加载一次、在 main 里注入到各层」,后面 Day 4~7 都会沿用。

八、小结

Day 3 建立「项目结构 + 配置」的习惯,并引入 viper 做多源与类型解析;本日不连数据库DB_DSN 只是留给 Day 4 数据层用的配置项。后面 Day 4 会用到这份配置真正连 SQLite/Postgres,中间件与综合实战也都会在同一套结构上扩展。

相关推荐
程序猿乐锅15 小时前
【Tilas|第三篇】多表SQL语句
数据库·经验分享·笔记·学习·mysql
淘矿人16 小时前
从0到1:用Claude启动你的第一个项目
开发语言·人工智能·git·python·github·php·pygame
Navicat中国16 小时前
使用 Navicat 导入向导导入 Excel 数据时,系统提示导入成功,表中也能看到数据,但行数统计显示为 0,这是什么原因?
数据库·excel·导入
cany100016 小时前
C++ -- 模板的声明和定义
开发语言·c++
澈20716 小时前
深耕进阶 Day1:C 与 C++ 核心差异 + C++ 入门基石
c语言·开发语言·c++
Felven16 小时前
C. Need More Arrays
c语言·开发语言
gmaajt16 小时前
Golang怎么做国际化多语言_Golang i18n教程【核心】
jvm·数据库·python
love530love16 小时前
Podman Machine 虚拟硬盘迁移实战二:用 Junction 把 vhdx 从 C 盘搬到其他盘
c语言·开发语言·人工智能·windows·wsl·podman·podman machine
折哥的程序人生 · 物流技术专研16 小时前
从“卡死”到“秒过”:WMS销售数据跨库回填的极限优化之旅
数据库·机器学习·oracle
李可以量化16 小时前
DeepSeek 量化交易实战:用标准化提示词模板实现 AI 辅助交易决策
大数据·数据库·人工智能