[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,中间件与综合实战也都会在同一套结构上扩展。

相关推荐
Lun3866buzha2 小时前
内窥镜设备部件检测与识别——基于Mask R-CNN的改进模型训练与实现
开发语言·r语言·cnn
Never_Satisfied2 小时前
在JavaScript中,如何给字符串数组中的元素去重
开发语言·javascript·ecmascript
倔强的石头1062 小时前
国产化时序替换落地指南:用金仓数据库管好海量时序数据
数据库·kingbase
生命因何探索2 小时前
Redis—主从复制+哨兵
数据库·redis·php
测试_AI_一辰2 小时前
项目实战15:Agent主观题怎么评测?先定底线,再做回归
开发语言·人工智能·功能测试·数据挖掘·ai编程
me8322 小时前
【Java面试】Java核心关键字解析(static_final_访问修饰符)小白易懂
java·开发语言·面试
undefinedType2 小时前
rails知识扫盲
数据库·后端·敏捷开发
小飞学编程...2 小时前
【Java相关八股文(一)】
android·java·开发语言
wcbsky062 小时前
MySQL数据库误删恢复_mysql 数据 误删
数据库·mysql·adb