feat(router): 为exports路由添加免认证下载接口

添加exportsDownloadHandler处理函数用于免认证下载文件,并修改路由配置使/download路径跳过认证中间件。当文件URI获取失败时,自动尝试从本地storage目录查找匹配文件。
This commit is contained in:
zhouyonggao 2025-12-26 15:29:34 +08:00
parent d0f65da375
commit 20deec5879
2 changed files with 71 additions and 1 deletions

View File

@ -1961,3 +1961,60 @@ func parseIntVal(s string) int {
} }
return n 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_<id>_*.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)
}
}

View File

@ -4,6 +4,7 @@ import (
"database/sql" "database/sql"
"net/http" "net/http"
"os" "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 { 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/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)))))
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/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)))))
mux.Handle("/api/fields/", withAccess(withTrace(authMiddleware(FieldsHandler(marketingDB, ymtDB))))) mux.Handle("/api/fields/", withAccess(withTrace(authMiddleware(FieldsHandler(marketingDB, ymtDB)))))