Beego通过runmode值(如dev、prod)驱动配置加载,必须定义在app.conf全局区,大小写敏感;v2版本需手动调用core/config.LoadConfig加载配置,否则AppConfig为空。

Beego 的 runmode 是环境切换核心开关
Beego 不靠多个配置文件区分环境,而是靠 runmode 值(如 dev、prod、test)驱动配置加载顺序。它默认读取 conf/app.conf,先匹配 [runmode] 小节,再 fallback 到全局配置项。
常见错误是把 runmode=prod 写在 [prod] 段里——这会导致解析失败,因为 runmode 必须出现在全局区(即最顶部,无中括号包裹)。
-
runmode值必须与配置段名完全一致,大小写敏感:写成RunMode=prod或runmode=PROD都不会命中[prod] - 未定义的
runmode值(比如runmode=staging但没写[staging]段)会静默忽略该段,只用全局配置 - 若
runmode为空或未设,默认值为"dev",不是报错
app.conf 中如何写多环境配置段
INI 格式支持 section,每个环境对应一个命名段。段内参数优先级高于全局同名参数。例如:
appname = myapp
runmode = dev
httpport = 8080
[dev]
httpport = 9527
db_host = "127.0.0.1"
db_adapter = "sqlite3"
[prod]
httpport = 80
db_host = "${MINDOC_DB_HOST||127.0.0.1}"
db_adapter = "${MINDOC_DB_ADAPTER||mysql}"
注意两点:
- 段内变量仍可使用
${VAR||default}语法,但**整个值必须用双引号包裹**,否则 Beego v2 解析器直接跳过 - 不能在段内再嵌套段,也不能用点号层级(如
database.host),INI 不支持嵌套结构 -
beego.AppConfig.String("httpport")返回的是当前runmode下的值;而beego.AppConfig.String("dev::httpport")强制读[dev]段,不依赖当前模式
Beego v2 必须显式加载配置,否则 AppConfig 为空
v2 版本移除了自动加载逻辑。beego.Run() 不再触发 conf/app.conf 读取,如果跳过初始化步骤,所有 AppConfig.Xxx() 调用都返回空值或零值,监听端口变成 Go 默认的 :8080,而不是配置里的值。
正确做法是在 main() 开头手动加载:
cfg, err := core/config.LoadConfig("ini", "conf/app.conf")
if err != nil {
log.Fatal(err)
}
beego.BConfig = cfg.(*core/config.BConfig)
这个步骤必须在 beego.Run() 之前,且不能放在控制器或中间件里——配置解析是启动期行为,运行时调用 os.Getenv() 手动补救已无意义。
- 若用
bee run本地调试,确保终端已export MINDOC_DB_HOST=xxx,否则 fallback 到双竖线后的默认值 - Docker 场景下,在
docker-compose.yml的environment:下直接定义变量即可,无需改文件名或复制配置 - CI/CD 流水线中禁止
sed -i替换app.conf内容——这破坏镜像不可变性,也绕过 Beego 的变量注入机制
环境变量注入和硬编码的边界在哪
Beego 的变量注入只作用于 app.conf 解析阶段,不是运行时全局环境变量。你不能在 controller 里写 os.Getenv("DB_HOST") 并期望它和配置文件里 ${MINDOC_DB_HOST} 同步更新——它们是两条独立路径。
真正要统一管理的敏感参数(数据库地址、密钥、第三方 API token),必须全部走 ${VAR||default} 注入,并在 app.conf 中声明占位符。硬编码哪怕只有一处,比如在 model 里写死 "root:pass@tcp(127.0.0.1:3306)",就等于把部署逻辑锁进代码,后续任何自动化发布都会出问题。
最容易被忽略的是:v2 的 core/config.LoadConfig 成功后,才开始解析环境变量。如果加载失败(比如路径错、权限不足、格式非法),后续所有 ${...} 都不会生效,但程序仍可能启动——只是用了一堆空配置。


















