React与Node项目中axios.get获取子文件夹报404错误求助
子文件夹路径导致Axios请求404错误
我是React和Node新手,正在做一个根据用户选择展示文件夹内容的功能。父文件夹能正常加载,但点击子文件夹时,客户端Axios的GET请求返回404错误——明明路径是正确的,硬编码路径却能正常工作,怀疑是路径中的/导致请求失败。
文件夹结构:
public_html/collection:
- logos
- Test Folder
- anotherTestfolder
访问logos下的子文件夹时都会触发该错误。
Server.js 代码
const express = require("express"); const bodyParser = require("body-parser"); const { error } = require("console"); const cors = require("cors"); const fs = require("fs-extra"); const path = require("path"); const multer = require("multer"); const app = express(); // Middleware app.use(cors()); app.use(bodyParser.json()); // Function to get the folder structure const getFolderStructure = async (folderPath) => { const folderName = path.basename(folderPath); const files = await fs.readdir(folderPath); const folder = { name: folderName, files: [], children: [], }; for (const file of files) { const filePath = path.join(folderPath, file); const stats = await fs.stat(filePath); if (stats.isFile()) { folder.files.push({ name: file, type: path.extname(file).toLowerCase(), size: stats.size, // Size of the file in bytes lastModified: stats.mtime, }); } else if (stats.isDirectory()) { const subFolder = await getFolderStructure(filePath); folder.children.push(subFolder); } } return folder; }; // Route to get the folder structure app.get("/api/folders", async (req, res) => { try { const rootFolder = await getFolderStructure("../public_html/collection/"); res.json(rootFolder); } catch (error) { console.error("Error fetching folder structure:", error); res.status(500).json({ error: "Internal server error" }); } }); // getting folder structure for selected file app.get(`/api/folders/:folderName`, async (req, res) => { const folderName = req.params.folderName; console.log("from the server: ", folderName); try { console.log("this was the folder name", folderName); const folderPath = path.join("../public_html/collection/", folderName); console.log("here is the folder path", folderPath); const folderNameData = await getFolderStructure(folderPath); res.json(folderNameData); } catch (error) { console.error("Error fetching folder structure:", error); res.status(500).json({ error: "Internal server error" }); } });
Body.js 代码
import React, { useState, useEffect } from "react"; import { DataGrid } from "@mui/x-data-grid"; import axios from "axios"; function formatFileSize(sizeInBytes) { if (sizeInBytes >= 1024 * 1024) { return (sizeInBytes / (1024 * 1024)).toFixed(2) + " MB"; } else if (isNaN(sizeInBytes)) { return "-"; } else { return (sizeInBytes / 1024).toFixed(2) + " KB"; } } // format the timestamp to a readable format for last modified timestamp function formatModifiedDate(last_modified) { try { const date = new Date(last_modified); // Convert to Date object return new Intl.DateTimeFormat("en-US", { year: "numeric", month: "short", day: "numeric", hour: "2-digit", minute: "2-digit", }).format(date); } catch (error) { return "-"; // Return a default message for invalid dates } } const Body = () => { const [folderData, setFolderData] = useState([]); const [selectedFile, setSelectedFile] = useState(null); const [selectedFolder, setSelectedFolder] = useState(null); const [fileData, setFileData] = useState([]); const [currentPath, setCurrentPath] = useState(""); const combinedContent = []; useEffect(() => { fetchFolderData(); }, []); // connect to server to get the first set of folders const fetchFolderData = async () => { console.log("going to fetch folder data..."); try { const response = await axios.get(`/api/folders${currentPath}`); setFolderData(response.data.children); console.log("here is the response from server", response.data.children); } catch (error) { console.error("Error fetching folder data", error); } }; const handleRowClick = async (params) => { const clickedFolderName = params.row.name; setSelectedFolder(clickedFolderName); console.log("this file was selected:", params.row.name); const newPath = currentPath ? `${currentPath}/${clickedFolderName}` : clickedFolderName; try { console.log("trying to use this path", newPath); const response = await axios.get(`/api/folders/${newPath}`); console.log("this was the response: ", response); setFolderData(response.data.children); // Update the currentPath with the new path setCurrentPath(newPath); } catch (error) { console.error("Error fetching folder data:", error); } }; return ( <div style={{ height: 500, width: "100%" }}> <DataGrid rows={folderData.map((folder, index) => ({ id: index, // Unique id for each folder row name: folder.name, type: folder.type, size: formatFileSize(folder.size), last_modified: formatModifiedDate(folder.last_modified), }))} columns={[ { field: "name", headerName: "Name", width: 200 }, { field: "type", headerName: "Type", width: 120 }, { field: "size", headerName: "Size", width: 120 }, { field: "last_modified", headerName: "Last Modified", width: 120 }, ]} onRowClick={handleRowClick} /> </div> ); }; export default Body;
问题原因与修复方案
问题根源
Express默认的路由参数:folderName只能匹配不含斜杠/的单个路径片段。当你请求/api/folders/logos/subfolder时,Express会认为logos是:folderName参数,而/subfolder是路由的一部分,找不到对应的路由规则,因此返回404。
修复步骤
修改Express路由,支持完整路径匹配
将原来的/api/folders/:folderName路由改为/api/folders/*,这样可以捕获/api/folders/之后的完整路径:// 替换原有的子文件夹路由 app.get(`/api/folders/*`, async (req, res) => { // 获取*匹配到的完整相对路径 const relativePath = req.params[0]; try { // 使用绝对路径避免歧义,同时防止路径遍历 const rootDir = path.resolve(__dirname, "../public_html/collection/"); const fullPath = path.resolve(rootDir, relativePath); // 校验路径是否在目标目录内,防止安全风险 if (!fullPath.startsWith(rootDir)) { return res.status(403).json({ error: "Invalid path" }); } const folderData = await getFolderStructure(fullPath); res.json(folderData); } catch (error) { console.error("Error fetching folder structure:", error); res.status(500).json({ error: "Internal server error" }); } });客户端代码无需修改
原来的axios.get(/api/folders/${newPath})会生成正确的请求路径,比如/api/folders/logos/subfolder,会被新的路由正确捕获。
额外优化建议
- 使用绝对路径:把
../public_html/collection/换成path.resolve(__dirname, "../public_html/collection/"),避免因启动目录不同导致的路径错误。 - 路径安全校验:加入
fullPath.startsWith(rootDir)的判断,防止用户通过构造路径跳出目标目录,引发安全问题。
内容的提问来源于stack exchange,提问作者Kam
相关产品推荐
相关产品推荐

