保存目录不存在时给一句人话再退出,不再 panic 崩进程

为什么:

checkConfig() 里检查 directory / mergeOutputDir 用的是 os.Stat + checkErr(err)。
目录不在时(盘没插、路径拼错)前者失败会直接 panic,打印一屏 goroutine 堆栈后崩掉
整个进程,而不是像同函数里其它检查那样给一句提示再 os.Exit(1)。服务被 nssm 之类托管
时崩了还会被反复拉起来,用户看到的只是一堆堆栈。

处理方式是复用原本只给 web 用的 checkWebDir,把它提升成通用的 checkDirField 并搬到
config.go:启动读配置、web 改设置、转码排队从此共用同一份实现,不会有两份各自漂移的
写法。文案沿用原来 IsDir 分支的措辞(「X 必须是存在的文件夹:路径」),只是不再依赖
os.Stat 先成功,且空串仍然表示「不指定」而不是错误。

验证:

- 实跑对比。修前 directory 指向不存在的目录:
  panic: GetFileAttributesEx ...: The system cannot find the file specified.
  修后:[ERROR] config.json里的directory 必须是存在的文件夹:… 退出码 1,
  日志里 panic 出现 0 次。
- 同一个 exe 另验两件事:directory 指向存在的目录能正常启动并建出 record/merged;
  web /api/config 传不存在的目录仍返回 400 + 同一套文案(改名没走样)。
- 新增 2 条测试,其中 TestCheckConfigNeverPanicsOnMissingDir 是静态检查——这条失败
  路径是 os.Exit(1),真跑一遍会把整个测试进程带走,所以改为读 config.go 源码断言
  checkConfig 函数体里不出现 checkErr(,与项目里既有的静态一致性检查同一套路。
- 反向验证新增一项:把两处换回老的 checkErr 写法后,该测试确实变红。
This commit is contained in:
tamakyi
2026-09-28 02:03:56 +08:00
parent 7ab80ec429
commit 2b1705a970
4 changed files with 71 additions and 29 deletions
+22 -14
View File
@@ -404,6 +404,20 @@ func currentAuthToken() string {
return config.AuthToken
}
// 检查配置里的文件夹字段,返回错误信息,空字符串表示通过。
// 启动读配置、web UI 改设置、转码排队都用这一份 —— 以前各自实现,
// 启动那份直接 os.Stat + checkErr,目录不存在时会 panic 崩掉整个进程。
func checkDirField(path, name string) string {
if path == "" {
return ""
}
info, err := os.Stat(path)
if err != nil || !info.IsDir() {
return name + " 必须是存在的文件夹:" + path
}
return ""
}
// 检查 config.json 里的配置
func checkConfig() {
for _, u := range config.UIDs {
@@ -416,21 +430,15 @@ func checkConfig() {
lPrintErr(configFile + "里的 webPort 必须为 0(不启动 web UI)或者大于 1023 且少于 65536")
os.Exit(1)
}
if config.Directory != "" {
info, err := os.Stat(config.Directory)
checkErr(err)
if !info.IsDir() {
lPrintErrf("%s里的directory必须是存在的文件夹:%s", configFile, config.Directory)
os.Exit(1)
}
// 目录不在(盘没插、路径拼错)要给人话提示然后退出,不能直接 panic:
// 崩溃时文件系统风险未知,而且服务被托管时会被反复拉起来
if msg := checkDirField(config.Directory, configFile+"里的directory"); msg != "" {
lPrintErr(msg)
os.Exit(1)
}
if config.MergeOutputDir != "" {
info, err := os.Stat(config.MergeOutputDir)
checkErr(err)
if !info.IsDir() {
lPrintErrf("%s里的mergeOutputDir必须是存在的文件夹:%s", configFile, config.MergeOutputDir)
os.Exit(1)
}
if msg := checkDirField(config.MergeOutputDir, configFile+"里的mergeOutputDir"); msg != "" {
lPrintErr(msg)
os.Exit(1)
}
if config.MergeBitrate < 0 {
lPrintErrf("%s里的mergeBitrate不能小于 0", configFile)