From 20deec58792807b6cea1d2f8739fc1b23ace0234 Mon Sep 17 00:00:00 2001 From: zhouyonggao <1971162852@qq.com> Date: Fri, 26 Dec 2025 15:29:34 +0800 Subject: [PATCH] =?UTF-8?q?feat(router):=20=E4=B8=BAexports=E8=B7=AF?= =?UTF-8?q?=E7=94=B1=E6=B7=BB=E5=8A=A0=E5=85=8D=E8=AE=A4=E8=AF=81=E4=B8=8B?= =?UTF-8?q?=E8=BD=BD=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加exportsDownloadHandler处理函数用于免认证下载文件,并修改路由配置使/download路径跳过认证中间件。当文件URI获取失败时,自动尝试从本地storage目录查找匹配文件。 --- server/internal/api/exports.go | 57 ++++++++++++++++++++++++++++++++++ server/internal/api/router.go | 15 ++++++++- 2 files changed, 71 insertions(+), 1 deletion(-) diff --git a/server/internal/api/exports.go b/server/internal/api/exports.go index 9142825..55fb03d 100644 --- a/server/internal/api/exports.go +++ b/server/internal/api/exports.go @@ -1961,3 +1961,60 @@ func parseIntVal(s string) int { } return n } + +// exportsDownloadHandler 独立的下载处理函数(不需要认证) +func exportsDownloadHandler(metaDB, marketingDB, ymtDB *sql.DB) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + // 提取 ID,路径格式:/api/exports/{id}/download + path := r.URL.Path + if !strings.HasPrefix(path, "/api/exports/") || !strings.HasSuffix(path, "/download") { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte("invalid path")) + return + } + + // 移除 /api/exports/ 和 /download + path = strings.TrimPrefix(path, "/api/exports/") + path = strings.TrimSuffix(path, "/download") + id := strings.TrimSpace(path) + + if id == "" { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte("missing id")) + return + } + + rrepo := repo.NewExportRepo() + uri, err := rrepo.GetLatestFileURI(metaDB, id) + if err != nil { + // fallback: try to serve local storage file by job id + // search for files named export_job__*.zip/xlsx/csv + dir := "storage" + entries, e := os.ReadDir(dir) + if e == nil { + best := "" + var bestInfo os.FileInfo + for _, ent := range entries { + name := ent.Name() + if strings.HasPrefix(name, "export_job_"+id+"_") && (strings.HasSuffix(name, ".zip") || strings.HasSuffix(name, ".xlsx") || strings.HasSuffix(name, ".csv")) { + info, _ := os.Stat(filepath.Join(dir, name)) + if info != nil { + if best == "" || info.ModTime().After(bestInfo.ModTime()) { + best = name + bestInfo = info + } + } + } + } + if best != "" { + http.ServeFile(w, r, filepath.Join(dir, best)) + return + } + } + w.WriteHeader(http.StatusNotFound) + w.Write([]byte("not found")) + return + } + http.ServeFile(w, r, uri) + } +} diff --git a/server/internal/api/router.go b/server/internal/api/router.go index fbc10cf..10e03ce 100644 --- a/server/internal/api/router.go +++ b/server/internal/api/router.go @@ -4,6 +4,7 @@ import ( "database/sql" "net/http" "os" + "strings" ) func NewRouter(metaDB *sql.DB, marketingDB *sql.DB, marketingAuthDB *sql.DB, resellerDB *sql.DB, ymtDB *sql.DB, grpcAddr string, marketingAPIDomain string) http.Handler { @@ -16,7 +17,19 @@ func NewRouter(metaDB *sql.DB, marketingDB *sql.DB, marketingAuthDB *sql.DB, res mux.Handle("/api/templates", withAccess(withTrace(authMiddleware(TemplatesHandler(metaDB, marketingDB))))) mux.Handle("/api/templates/", withAccess(withTrace(authMiddleware(TemplatesHandler(metaDB, marketingDB))))) mux.Handle("/api/exports", withAccess(withTrace(authMiddleware(ExportsHandler(metaDB, marketingDB, ymtDB))))) - mux.Handle("/api/exports/", withAccess(withTrace(authMiddleware(ExportsHandler(metaDB, marketingDB, ymtDB))))) + + // exports 路由处理(特殊处理下载接口) + mux.HandleFunc("/api/exports/", func(w http.ResponseWriter, r *http.Request) { + path := r.URL.Path + // 如果是下载接口,不需要认证 + if strings.HasSuffix(path, "/download") { + exportsDownloadHandler(metaDB, marketingDB, ymtDB).ServeHTTP(w, r) + } else { + // 其他 exports 路由需要认证 + withAccess(withTrace(authMiddleware(ExportsHandler(metaDB, marketingDB, ymtDB)))).ServeHTTP(w, r) + } + }) + mux.Handle("/api/metadata/fields", withAccess(withTrace(authMiddleware(MetadataHandler(metaDB, marketingDB, ymtDB))))) mux.Handle("/api/fields", withAccess(withTrace(authMiddleware(FieldsHandler(marketingDB, ymtDB))))) mux.Handle("/api/fields/", withAccess(withTrace(authMiddleware(FieldsHandler(marketingDB, ymtDB)))))