2026-08-06 16:33:45 +08:00
|
|
|
|
// Package config 负责运行参数和文件路径。
|
|
|
|
|
|
//
|
|
|
|
|
|
// 改动本文件前必读 admin/AGENTS.md。最容易踩的一条:
|
|
|
|
|
|
// 路径必须走 DataDir(),不许硬编码、不许直接用 os.Getwd(),
|
|
|
|
|
|
// 打包成 exe 后那些写法会失效。详见 docs/admin/02-architecture.md §6。
|
|
|
|
|
|
package config
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"os"
|
|
|
|
|
|
"path/filepath"
|
|
|
|
|
|
"strings"
|
2026-08-06 16:56:18 +08:00
|
|
|
|
"time"
|
2026-08-06 16:33:45 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
2026-08-06 16:56:18 +08:00
|
|
|
|
// OnlineThreshold 是判定客户端"在线"的时间窗:
|
|
|
|
|
|
// 最近一次调接口在这个时间之内就算在线,否则离线。
|
|
|
|
|
|
//
|
|
|
|
|
|
// 取值要**宽松**:客户端执行长任务期间不会调 claim,
|
|
|
|
|
|
// 太短会被误判成离线。取「轮询周期 + 最长任务时长」量级。
|
|
|
|
|
|
// 本项目有意不做心跳,见 docs/admin/04-client-api.md §3。
|
|
|
|
|
|
const OnlineThreshold = 10 * time.Minute
|
|
|
|
|
|
|
2026-08-06 16:33:45 +08:00
|
|
|
|
// 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") -> <data>/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")
|
|
|
|
|
|
}
|