// Package config 负责运行参数和文件路径。 // // 改动本文件前必读 admin/AGENTS.md。最容易踩的一条: // 路径必须走 DataDir(),不许硬编码、不许直接用 os.Getwd(), // 打包成 exe 后那些写法会失效。详见 docs/admin/02-architecture.md §6。 package config import ( "os" "path/filepath" "strings" "time" ) // OnlineThreshold 是判定客户端"在线"的时间窗: // 最近一次调接口在这个时间之内就算在线,否则离线。 // // 取值要**宽松**:客户端执行长任务期间不会调 claim, // 太短会被误判成离线。取「轮询周期 + 最长任务时长」量级。 // 本项目有意不做心跳,见 docs/admin/04-client-api.md §3。 const OnlineThreshold = 10 * time.Minute // DataDir 返回可写数据目录,不存在就创建。 // // 打包成 exe 后 = exe 旁边的 data/ // go run 时 = 当前工作目录下的 data/ // // 目录结构见 docs/admin/02-architecture.md §6: // // data/ // ├── admin.db // ├── logs/ // └── uploads/ func DataDir() (string, error) { exe, err := os.Executable() if err != nil { return "", err } dir := filepath.Join(filepath.Dir(exe), "data") // go run 会把程序编译到系统临时目录再执行, // 这种情况下 exe 旁边不是项目目录,要改用当前工作目录。 if isTempBuild(exe) { wd, err := os.Getwd() if err != nil { return "", err } dir = filepath.Join(wd, "data") } if err := os.MkdirAll(dir, 0o755); err != nil { return "", err } return dir, nil } // SubDir 返回 data/ 下的子目录,不存在就创建。 // 例如 SubDir("logs") -> /logs func SubDir(name string) (string, error) { base, err := DataDir() if err != nil { return "", err } dir := filepath.Join(base, name) if err := os.MkdirAll(dir, 0o755); err != nil { return "", err } return dir, nil } // isTempBuild 判断这个可执行文件是不是 go run 生成的临时产物。 func isTempBuild(exe string) bool { tmp := os.TempDir() if tmp != "" && strings.HasPrefix(exe, tmp) { return true } // go run 的产物路径里通常带 go-build 字样 return strings.Contains(filepath.ToSlash(exe), "/go-build") }