fix: 固定开发环境后端数据目录,避免 cwd 不同导致头像 404
从 cwd 向上解析 backend 根,并写入本地开发启动约定。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -33,7 +33,7 @@ func main() {
|
||||
}
|
||||
|
||||
addr := fmt.Sprintf(":%d", cfg.Port)
|
||||
log.Printf("[jiang13-bbs] 服务启动于 %s (dev=%v)", addr, cfg.DevMode)
|
||||
log.Printf("[jiang13-bbs] 服务启动于 %s (dev=%v workPath=%s dataDir=%s)", addr, cfg.DevMode, cfg.WorkPath, cfg.DataDir)
|
||||
if err := r.Run(addr); err != nil {
|
||||
log.Fatalf("服务启动失败: %v", err)
|
||||
}
|
||||
|
||||
@@ -20,11 +20,13 @@ type Config struct {
|
||||
DevMode bool
|
||||
}
|
||||
|
||||
// Parse 解析配置:环境变量 > app.ini > 默认值
|
||||
// Parse 解析配置:环境变量 > app.ini > 默认值。
|
||||
// 工作目录不跟 os.Getwd() 走:从 cwd 向上查找 backend 根(含 app.ini 或源码标记),
|
||||
// 避免在仓库根 / cmd/jiang13 启动时把上传文件写进另一套 data/。
|
||||
func Parse() (*Config, error) {
|
||||
workPath, err := os.Getwd()
|
||||
workPath, err := resolveWorkPath()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取工作目录失败: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 默认值
|
||||
@@ -83,6 +85,10 @@ func Parse() (*Config, error) {
|
||||
cfg.DevMode = strings.EqualFold(v, "true") || v == "1"
|
||||
}
|
||||
|
||||
if err := rejectStrayDataDir(cfg.DataDir); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 确保数据目录存在
|
||||
if err := os.MkdirAll(cfg.DataDir, 0755); err != nil {
|
||||
return nil, fmt.Errorf("创建数据目录失败: %w", err)
|
||||
@@ -102,6 +108,68 @@ func Parse() (*Config, error) {
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
// resolveWorkPath 定位后端根目录(app.ini 与 data/ 所在处)。
|
||||
// 优先级:JIANG13_WORK_PATH > 从 cwd 向上找 app.ini > 源码树 backend/ > cwd。
|
||||
func resolveWorkPath() (string, error) {
|
||||
if v := strings.TrimSpace(os.Getenv("JIANG13_WORK_PATH")); v != "" {
|
||||
abs, err := filepath.Abs(v)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("解析 JIANG13_WORK_PATH 失败: %w", err)
|
||||
}
|
||||
return abs, nil
|
||||
}
|
||||
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("获取工作目录失败: %w", err)
|
||||
}
|
||||
|
||||
dir := cwd
|
||||
for range 8 {
|
||||
if hasAppIni(dir) {
|
||||
return dir, nil
|
||||
}
|
||||
nested := filepath.Join(dir, "backend")
|
||||
if hasAppIni(nested) {
|
||||
return nested, nil
|
||||
}
|
||||
if isSourceBackendRoot(dir) {
|
||||
return dir, nil
|
||||
}
|
||||
if isSourceBackendRoot(nested) {
|
||||
return nested, nil
|
||||
}
|
||||
parent := filepath.Dir(dir)
|
||||
if parent == dir {
|
||||
break
|
||||
}
|
||||
dir = parent
|
||||
}
|
||||
|
||||
return cwd, nil
|
||||
}
|
||||
|
||||
func hasAppIni(dir string) bool {
|
||||
_, err := os.Stat(filepath.Join(dir, "app.ini"))
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func isSourceBackendRoot(dir string) bool {
|
||||
_, e1 := os.Stat(filepath.Join(dir, "go.mod"))
|
||||
_, e2 := os.Stat(filepath.Join(dir, "cmd", "jiang13"))
|
||||
_, e3 := os.Stat(filepath.Join(dir, "app.ini.example"))
|
||||
return e1 == nil && e2 == nil && e3 == nil
|
||||
}
|
||||
|
||||
// rejectStrayDataDir 拦住把上传写进 cmd/jiang13/data 的错误 cwd。
|
||||
func rejectStrayDataDir(dataDir string) error {
|
||||
slash := filepath.ToSlash(dataDir)
|
||||
if strings.Contains(slash, "/cmd/jiang13/") {
|
||||
return fmt.Errorf("拒绝使用数据目录 %s:这是在 cmd/jiang13 下启动产生的错误路径。请在 backend/ 执行 go run ./cmd/jiang13,或设置 JIANG13_WORK_PATH 指向 backend 目录", dataDir)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func generateSecret(n int) string {
|
||||
const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||
b := make([]byte, n)
|
||||
|
||||
90
backend/config/config_test.go
Normal file
90
backend/config/config_test.go
Normal file
@@ -0,0 +1,90 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func backendRoot(t *testing.T) string {
|
||||
t.Helper()
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
root, err := filepath.Abs(filepath.Join(wd, ".."))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return root
|
||||
}
|
||||
|
||||
func assertPath(t *testing.T, got, want string) {
|
||||
t.Helper()
|
||||
g, err := filepath.Abs(got)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
w, err := filepath.Abs(want)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if filepath.Clean(g) != filepath.Clean(w) {
|
||||
t.Fatalf("路径不符:\n got %s\n want %s", g, w)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveWorkPathFromOfficialCwd(t *testing.T) {
|
||||
root := backendRoot(t)
|
||||
t.Chdir(root)
|
||||
got, err := resolveWorkPath()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assertPath(t, got, root)
|
||||
}
|
||||
|
||||
func TestResolveWorkPathFromCmdJiang13(t *testing.T) {
|
||||
root := backendRoot(t)
|
||||
t.Chdir(filepath.Join(root, "cmd", "jiang13"))
|
||||
got, err := resolveWorkPath()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assertPath(t, got, root)
|
||||
}
|
||||
|
||||
func TestResolveWorkPathFromRepoRoot(t *testing.T) {
|
||||
root := backendRoot(t)
|
||||
t.Chdir(filepath.Join(root, ".."))
|
||||
got, err := resolveWorkPath()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assertPath(t, got, root)
|
||||
}
|
||||
|
||||
func TestResolveWorkPathEnvOverride(t *testing.T) {
|
||||
root := backendRoot(t)
|
||||
t.Setenv("JIANG13_WORK_PATH", root)
|
||||
t.Chdir(t.TempDir())
|
||||
got, err := resolveWorkPath()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assertPath(t, got, root)
|
||||
}
|
||||
|
||||
func TestRejectStrayDataDir(t *testing.T) {
|
||||
err := rejectStrayDataDir(`C:\proj\backend\cmd\jiang13\data`)
|
||||
if err == nil {
|
||||
t.Fatal("期望拒绝 cmd/jiang13/data")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "拒绝使用数据目录") {
|
||||
t.Fatalf("错误文案不符: %v", err)
|
||||
}
|
||||
if err := rejectStrayDataDir(`C:\proj\backend\data`); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user