环境变量

项目里经常需要配置数据库地址、端口、密钥、第三方服务地址等信息。这些内容不适合直接写死在代码里,一般会通过环境变量或配置文件管理。

常见有两种方式:

  • 方式 1:运行时直接读取外部 .env
  • 方式 2:打包默认 .env,运行时再允许外部配置覆盖

方式 1:直接 load 外部配置

这种方式是开发和部署时最常见的方式。

项目启动时,直接从外部 .env 文件读取配置。

xxx
  |-- main.go
  |-- .env

示例 .env

APP_PORT=8080
DB_HOST=127.0.0.1
DB_PORT=3306
DB_USER=root
DB_PASSWORD=123456
DB_NAME=demo

Go 代码示例:

package main

import (
	"log"
	"os"

	"github.com/joho/godotenv"
)

func main() {
	err := godotenv.Load(".env")
	if err != nil {
		log.Println("未找到 .env 文件,将使用系统环境变量")
	}

	port := os.Getenv("APP_PORT")
	if port == "" {
		port = "8080"
	}

	log.Println("server port:", port)
}

这种方式的特点是:.env 文件不会被打包进 Go 二进制,程序运行时从当前目录读取 .env

部署时需要保证 .env 文件和二进制程序一起放到服务器上:

server
  |-- main
  |-- .env

优点:

  • 配置完全外置,修改配置不需要重新编译。
  • 适合测试、预发、生产等不同环境。
  • 敏感信息不会进入二进制文件。

缺点:

  • 部署时必须带上 .env 文件。
  • 如果服务器缺少 .env,程序可能启动失败或使用默认值。
  • 需要额外管理不同环境的配置文件。

适合场景:

  • 数据库账号密码、JWT Secret、第三方 API Key 等敏感配置。
  • 测试、预发、生产配置差异较大的项目。
  • 需要运维在服务器上直接调整配置的项目。

方式 2:打包默认配置,再由外部读取覆盖

这种方式是把一份默认配置打包进 Go 二进制里,保证程序没有外部配置时也能启动。

同时,程序启动时再尝试读取外部 .env,如果外部配置存在,就用外部配置覆盖默认配置。

项目结构示例:

xxx
  |-- main.go
  |-- config
      |-- default.env

示例 config/default.env

APP_PORT=8080
DB_HOST=127.0.0.1
DB_PORT=3306
DB_USER=root
DB_PASSWORD=
DB_NAME=demo

Go 代码示例:

package main

import (
	"embed"
	"log"
	"os"
	"strings"

	"github.com/joho/godotenv"
)

//go:embed config/default.env
var defaultEnvFS embed.FS

func main() {
	// 先读取内置默认配置
	data, err := defaultEnvFS.ReadFile("config/default.env")
	if err == nil {
		defaultEnv, err := godotenv.Parse(strings.NewReader(string(data)))
		if err == nil {
			for key, value := range defaultEnv {
				os.Setenv(key, value)
			}
		}
	}

	// 再读取外部 .env,覆盖默认配置
	err = godotenv.Overload(".env")
	if err != nil {
		log.Println("未找到外部 .env 文件,将使用内置默认配置或系统环境变量")
	}

	port := os.Getenv("APP_PORT")
	log.Println("server port:", port)
}

这里的顺序是:

读取内置 default.env -> 写入环境变量 -> 读取外部 .env -> 覆盖默认值

部署时可以只部署一个二进制:

server
  |-- main

也可以额外放一个 .env 覆盖配置:

server
  |-- main
  |-- .env

需要注意:如果内置配置里包含密码、Token、Secret,这些内容会进入二进制文件,不适合放真正敏感的生产密钥。

优点:

  • 没有外部 .env 时也能用默认配置启动。
  • 部署更方便,可以只发布一个二进制。
  • 外部 .env 仍然可以覆盖默认配置,兼顾灵活性。

缺点:

  • 默认配置会进入二进制,不适合放敏感信息。
  • 修改内置默认配置后,需要重新 go build
  • 配置来源多了一层,需要明确优先级,避免排查困难。

适合场景:

  • 本地开发默认配置。
  • Demo、教学项目、内部工具。
  • 默认端口、默认开关、非敏感的功能配置。
  • 希望程序开箱即用,但又允许部署时外部覆盖配置的项目。

两种方式对比

直接 load 外部配置:
配置不进二进制,运行时读取 .env。
优点是安全、灵活,适合正式部署。
缺点是部署时必须管理外部配置文件。

打包默认配置 + 外部覆盖:
默认配置进二进制,运行时再读取外部 .env 覆盖。
优点是开箱即用,部署简单。
缺点是内置配置不适合放敏感信息,修改默认值需要重新编译。

总结

正式项目里,更推荐把敏感配置放在外部环境变量或外部 .env 中:

godotenv.Load(".env")

如果是 Demo、教学项目、内部工具,或者希望程序没有 .env 也能启动,可以使用内置默认配置:

//go:embed config/default.env
var defaultEnvFS embed.FS

再通过外部 .env 覆盖它。

简单记:

生产环境:外部配置优先,不把敏感信息打进二进制。
开发 / Demo:可以打包默认配置,降低启动成本。