From de9c45a944c3053af8afe142ea933763c55058ad Mon Sep 17 00:00:00 2001 From: Ginta <775650117@qq.com> Date: Mon, 27 Apr 2026 15:52:12 +0800 Subject: [PATCH] static --- src/config/config.go | 91 +++++++++++++++++++++++++++++++++++++++ src/config/config_test.go | 54 +++++++++++++++++++++++ 2 files changed, 145 insertions(+) diff --git a/src/config/config.go b/src/config/config.go index c8852d9..e998a89 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -3,6 +3,7 @@ package config import ( "errors" "fmt" + "io" "log" "net/url" "os" @@ -62,6 +63,9 @@ func Init() { LogLevel = normalizeLogLevel(viper.GetString("log_level")) StaticPath = normalizeStaticURLPath(viper.GetString("static_path")) StaticFilePath = filepath.Join(configRootPath, strings.TrimPrefix(StaticPath, "/")) + if err = ensureConfiguredStaticFiles(); err != nil { + panic(err) + } RuntimePath = resolvePathFromBase(configRootPath, viper.GetString("runtime_root_path"), filepath.Join(configRootPath, "runtime")) LogSavePath = resolvePathFromBase(RuntimePath, viper.GetString("log_save_path"), filepath.Join(RuntimePath, "logs")) mustMkdir(RuntimePath) @@ -82,6 +86,93 @@ func mustMkdir(path string) { } } +func ensureConfiguredStaticFiles() error { + if strings.TrimSpace(StaticFilePath) == "" { + return nil + } + exePath, err := os.Executable() + if err != nil { + return err + } + exePath, err = filepath.EvalSymlinks(exePath) + if err != nil { + return err + } + + srcDir := filepath.Join(filepath.Dir(exePath), "static") + srcInfo, err := os.Stat(srcDir) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return nil + } + return err + } + if !srcInfo.IsDir() { + return nil + } + + srcAbs, err := filepath.Abs(srcDir) + if err != nil { + return err + } + dstAbs, err := filepath.Abs(StaticFilePath) + if err != nil { + return err + } + if srcAbs == dstAbs { + return nil + } + + return copyMissingStaticFiles(srcAbs, dstAbs) +} + +func copyMissingStaticFiles(srcDir, dstDir string) error { + return filepath.WalkDir(srcDir, func(path string, d os.DirEntry, err error) error { + if err != nil { + return err + } + rel, err := filepath.Rel(srcDir, path) + if err != nil { + return err + } + dstPath := filepath.Join(dstDir, rel) + if d.IsDir() { + return os.MkdirAll(dstPath, 0o755) + } + if _, err = os.Stat(dstPath); err == nil { + return nil + } else if !errors.Is(err, os.ErrNotExist) { + return err + } + if err = os.MkdirAll(filepath.Dir(dstPath), 0o755); err != nil { + return err + } + return copyFile(path, dstPath) + }) +} + +func copyFile(srcPath, dstPath string) error { + in, err := os.Open(srcPath) + if err != nil { + return err + } + defer in.Close() + + out, err := os.OpenFile(dstPath, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0o644) + if err != nil { + if errors.Is(err, os.ErrExist) { + return nil + } + return err + } + + if _, err = io.Copy(out, in); err != nil { + _ = out.Close() + return err + } + return out.Close() +} + func normalizeLogLevel(level string) string { switch strings.ToLower(strings.TrimSpace(level)) { case "debug", "info", "warn", "error": diff --git a/src/config/config_test.go b/src/config/config_test.go index 2b8e880..88738d0 100644 --- a/src/config/config_test.go +++ b/src/config/config_test.go @@ -177,6 +177,60 @@ func TestResolveConfigFilePathPrefersExplicitOverEnv(t *testing.T) { } } +func TestCopyMissingStaticFilesCopiesAssetsWithoutOverwriting(t *testing.T) { + root := t.TempDir() + src := filepath.Join(root, "app-static") + dst := filepath.Join(root, "data-static") + + if err := os.MkdirAll(filepath.Join(src, "images"), 0o755); err != nil { + t.Fatalf("mkdir source: %v", err) + } + if err := os.WriteFile(filepath.Join(src, "index.html"), []byte("source-index"), 0o644); err != nil { + t.Fatalf("write source index: %v", err) + } + if err := os.WriteFile(filepath.Join(src, "payment.js"), []byte("source-payment"), 0o644); err != nil { + t.Fatalf("write source payment: %v", err) + } + if err := os.WriteFile(filepath.Join(src, "images", "logo.png"), []byte("source-logo"), 0o644); err != nil { + t.Fatalf("write source logo: %v", err) + } + + if err := os.MkdirAll(dst, 0o755); err != nil { + t.Fatalf("mkdir destination: %v", err) + } + if err := os.WriteFile(filepath.Join(dst, "index.html"), []byte("custom-index"), 0o644); err != nil { + t.Fatalf("write existing destination index: %v", err) + } + + if err := copyMissingStaticFiles(src, dst); err != nil { + t.Fatalf("copy missing static files: %v", err) + } + + index, err := os.ReadFile(filepath.Join(dst, "index.html")) + if err != nil { + t.Fatalf("read destination index: %v", err) + } + if string(index) != "custom-index" { + t.Fatalf("existing index was overwritten: %q", string(index)) + } + + payment, err := os.ReadFile(filepath.Join(dst, "payment.js")) + if err != nil { + t.Fatalf("read copied payment: %v", err) + } + if string(payment) != "source-payment" { + t.Fatalf("payment.js = %q, want source-payment", string(payment)) + } + + logo, err := os.ReadFile(filepath.Join(dst, "images", "logo.png")) + if err != nil { + t.Fatalf("read copied nested asset: %v", err) + } + if string(logo) != "source-logo" { + t.Fatalf("logo.png = %q, want source-logo", string(logo)) + } +} + func TestGetUsdtRatePrefersPositiveAdminOverride(t *testing.T) { viper.Reset() t.Cleanup(viper.Reset)