Reactjs?+?Nodejs?+?Mongodb?實(shí)現(xiàn)文件上傳功能實(shí)例詳解
Reactjs + Nodejs + Mongodb 實(shí)現(xiàn)文件上傳
概述
今天是使用 Reactjs + Nodejs + Mongodb 實(shí)現(xiàn)文件上傳功能。前端我們使用 Reactjs + Axios 來搭建前端上傳文件應(yīng)用,后端我們使用 Node.js + Express + Multer + Mongodb 來搭建后端上傳文件處理應(yīng)用。

React + Node.js + Mongodb「上傳文件」前后端項(xiàng)目結(jié)構(gòu)
前端項(xiàng)目結(jié)構(gòu)
├── README.md
├── package-lock.json
└── node_modules
└── ...
├── package.json
├── public
│ └── index.html
└── src
├── App.css
├── App.js
├── components
│ └── UploadFiles.js
├── http-common.js
├── index.js
└── services
└── UploadFilesService.jsReactjs 前端部分
App.js: 把我們的組件導(dǎo)入到 React 的起始頁components/UploadFiles.js: 文件上傳組件http-common.js: 使用 HTTP 基礎(chǔ) Url 和標(biāo)頭初始化 Axios。- 我們在.env中為我們的應(yīng)用程序配置端口
services/UploadFilesService.js: 這個(gè)文件中的函數(shù)用于文件上傳和獲取數(shù)據(jù)庫中文件數(shù)據(jù)
后端項(xiàng)目結(jié)構(gòu)
├── README.md
├── package.json
├── pnpm-lock.yaml
└── node_modules
└── ...
└── src
├── config
│ └── db.js
├── controllers
│ └── flileUploadController.js
├── middleware
│ └── upload.js
├── routes
│ └── index.js
└── server.js后端項(xiàng)目結(jié)構(gòu)
src/db.js包括 MongoDB 和 Multer 的配置(url、數(shù)據(jù)庫、文件存儲桶)。middleware/upload.js:初始化 Multer GridFs 存儲引擎(包括 MongoDB)并定義中間件函數(shù)。controllers/flileUploadController.js:配置 Rest APIroutes/index.js:路由,定義前端請求后端如何執(zhí)行server.js:Node.js入口文件
前端部分-上傳文件 React + Axios
配置 React 環(huán)境
這里我們使用 pnpm vite 創(chuàng)建一個(gè) React 項(xiàng)目
npx create-react-app react-multiple-files-upload
項(xiàng)目創(chuàng)建完成后,cd 進(jìn)入項(xiàng)目,安裝項(xiàng)目運(yùn)行需要的依賴包和 Axios 終端分別依次如下命令
pnpm install pnpm install axios
執(zhí)行完成我們啟動項(xiàng)目 pnpm start
可以看到控制臺中已經(jīng)輸出了信息,在瀏覽器地址欄中輸入控制臺輸出的地址,項(xiàng)目已經(jīng)跑起來了
導(dǎo)入 bootstrap 到項(xiàng)目中
運(yùn)行如下命令
npm install bootstrap
bootstrap 安裝完成后,我們打開 src/App.js 文件, 添加如下代碼
import React from "react";
import "./App.css";
import "bootstrap/dist/css/bootstrap.min.css";
function App() {
return (
<div className="container">
...
</div>
);
}
export default App;初始化 Axios
在 src 目錄下 我們新建 http-common.js文件,并添加如下代碼
import axios from "axios";
export default axios.create({
baseURL: "http://localhost:8080",
headers: {
"Content-type": "application/json"
}
});這里 baseURL 的地址是我們后端服務(wù)器的 REST API 地址,要根據(jù)個(gè)人實(shí)際情況進(jìn)行修改。本教程后文,教你搭建上傳文件的后端部分,請繼續(xù)閱讀。
創(chuàng)建上傳文件組件
src/services/UploadFilesService.js,這個(gè)文件主要的作用就是和后端項(xiàng)目通訊,以進(jìn)行文件的上傳和文件列表數(shù)據(jù)的獲取等
在文件中我們加入如下內(nèi)容
import http from "../http-common";
const upload = (file, onUploadProgress) => {
let formData = new FormData();
formData.append("file", file);
return http.post("/upload", formData, {
headers: {
"Content-Type": "multipart/form-data",
},
onUploadProgress,
});
};
const getFiles = () => {
return http.get("/files");
};
const FileUploadService = {
upload,
getFiles,
};
export default FileUploadService;首先導(dǎo)入我們之前寫好的 Axios HTTP 配置文件 http-common.js,并定義一個(gè)對象,在對象中添加兩個(gè)屬性函數(shù),作用如下
upload:函數(shù)以 POST 的方式將數(shù)據(jù)提交到后端,接收兩個(gè)參數(shù)file和onUploadProgressfile上傳的文件,以FormData的形式上傳onUploadProgress文件上傳進(jìn)度條事件,監(jiān)測進(jìn)度條信息
getFiles: 函數(shù)用于獲取存儲在 Mongodb 數(shù)據(jù)庫中的數(shù)據(jù)
最后將這個(gè)對象導(dǎo)出去
創(chuàng)建 React 文件上傳組件
接下來我們來創(chuàng)建文件上傳組件,首先組件要滿足功能有文件上傳,上傳進(jìn)度條信息展示,文件預(yù)覽,提示信息,文件下載等功能
這里我們使用 React Hooks 和 useState 來創(chuàng)建文件上傳組件,創(chuàng)建文件 src/components/UploadFiles,添加如下代碼
import React, { useState, useEffect, useRef } from "react";
import UploadService from "../services/UploadFilesService";
const UploadFiles = () => {
return (
);
};
export default UploadFiles;然后我們使用 React Hooks 定義狀態(tài)
const UploadFiles = () => {
const [selectedFiles, setSelectedFiles] = useState(undefined);
const [progressInfos, setProgressInfos] = useState({ val: [] });
const [message, setMessage] = useState([]);
const [fileInfos, setFileInfos] = useState([]);
const progressInfosRef = useRef(null)
}狀態(tài)定義好后,我們在添加一個(gè)獲取文件的方法 selectFiles()
const UploadFiles = () => {
...
const selectFiles = (event) => {
setSelectedFiles(event.target.files);
setProgressInfos({ val: [] });
};
...
}selectedFiles 用來存儲當(dāng)前選定的文件,每個(gè)文件都有一個(gè)相應(yīng)的進(jìn)度信息如文件名和進(jìn)度信息等,我們將這些信息存儲在 fileInfos中。
const UploadFiles = () => {
...
const uploadFiles = () => {
const files = Array.from(selectedFiles);
let _progressInfos = files.map(file => ({ percentage: 0, fileName: file.name }));
progressInfosRef.current = {
val: _progressInfos,
}
const uploadPromises = files.map((file, i) => upload(i, file));
Promise.all(uploadPromises)
.then(() => UploadService.getFiles())
.then((files) => {
setFileInfos(files.data);
});
setMessage([]);
};
...
}我們上傳多個(gè)文件的時(shí)候會將文件信息存儲在 selectedFiles, 在上面的代碼中 我們使用 Array.from 方法將可迭代數(shù)據(jù)轉(zhuǎn)換數(shù)組形式的數(shù)據(jù),接著使用 map 方法將文件的進(jìn)度信息,名稱信息存儲到 _progressInfos 中
接著我們使用 map 方法調(diào)用 files 數(shù)組中的每一項(xiàng),使 files 中的每一項(xiàng)都經(jīng)過 upload 函數(shù)的處理,在 upload 函數(shù)中我們會返回上傳文件請求函數(shù) UploadService.upload 的 Promise 狀態(tài)
所以 uploadPromises 中存儲的就是處于 Promise 狀態(tài)的上傳文件函數(shù),接著我們使用 Promise.all 同時(shí)發(fā)送多個(gè)文件上傳請求,在所有文件都上傳成功后,我們將會調(diào)用獲取所有文件數(shù)據(jù)的接口,并將獲取到的數(shù)據(jù)展示出來。
upload 函數(shù)代碼如下
const upload = (idx, file) => {
let _progressInfos = [...progressInfosRef.current.val];
return UploadService.upload(file, (event) => {
_progressInfos[idx].percentage = Math.round(
(100 * event.loaded) / event.total
);
setProgressInfos({ val: _progressInfos });
})
.then(() => {
setMessage((prevMessage) => ([
...prevMessage,
"文件上傳成功: " + file.name,
]));
})
.catch(() => {
_progressInfos[idx].percentage = 0;
setProgressInfos({ val: _progressInfos });
setMessage((prevMessage) => ([
...prevMessage,
"不能上傳文件: " + file.name,
]));
});
};每個(gè)文件的上傳進(jìn)度信息根據(jù) event.loaded 和 event.total 百分比值來計(jì)算,因?yàn)樵谡{(diào)用 upload 函數(shù)的時(shí)候,已經(jīng)將對應(yīng)文件的索引傳遞進(jìn)來了,所有我們根據(jù)對應(yīng)的索引設(shè)置對應(yīng)文件的上傳進(jìn)度
除了這些工作,我們還需要在 Effect HookuseEffect() 做如下功能,這部分代碼的作用其實(shí) componentDidMount 中起到的作用一致
const UploadFiles = () => {
...
useEffect(() => {
UploadService.getFiles().then((response) => {
setFileInfos(response.data);
});
}, []);
...
}到這里我們就需要將文件上傳的 UI 代碼添加上了,代碼如下
const UploadFiles = () => {
...
return (
<div>
{progressInfos && progressInfos.val.length > 0 &&
progressInfos.val.map((progressInfo, index) => (
<div className="mb-2" key={index}>
<span>{progressInfo.fileName}</span>
<div className="progress">
<div
className="progress-bar progress-bar-info"
role="progressbar"
aria-valuenow={progressInfo.percentage}
aria-valuemin="0"
aria-valuemax="100"
style={{ width: progressInfo.percentage + "%" }}
>
{progressInfo.percentage}%
</div>
</div>
</div>
))}
<div className="row my-3">
<div className="col-8">
<label className="btn btn-default p-0">
<input type="file" multiple onChange={selectFiles} />
</label>
</div>
<div className="col-4">
<button
className="btn btn-success btn-sm"
disabled={!selectedFiles}
onClick={uploadFiles}
>
上傳
</button>
</div>
</div>
{message.length > 0 && (
<div className="alert alert-secondary" role="alert">
<ul>
{message.map((item, i) => {
return <li key={i}>{item}</li>;
})}
</ul>
</div>
)}
<div className="card">
<div className="card-header">List of Files</div>
<ul className="list-group list-group-flush">
{fileInfos &&
fileInfos.map((file, index) => (
<li className="list-group-item" key={index}>
<a href={file.url}>{file.name}</a>
</li>
))}
</ul>
</div>
</div>
);
};
在 UI 相關(guān)的代碼中, 我們使用了 Bootstrap 的進(jìn)度條
- 使用
.progress作為最外層包裝 - 內(nèi)部使用
.progress-bar顯示進(jìn)度信息 .progress-bar需要 style 按百分比設(shè)置進(jìn)度信息.progress-bar進(jìn)度條還可以設(shè)置role和aria屬性
文件列表信息的展示我們使用 map 遍歷 fileInfos 數(shù)組,并且將文件的 url,name 信息展示出來
最后,我們將上傳文件組件導(dǎo)出
const UploadFiles = () => {
...
}
export default UploadFiles;將文件上傳組件添加到App組件
import React from "react";
import "./App.css";
import "bootstrap/dist/css/bootstrap.min.css";
import UploadFiles from "./components/UploadFiles.js"
function App() {
return (
<div className="container">
<h4> 使用 React 搭建文件上傳 Demo</h4>
<div className="content">
<UploadFiles />
</div>
</div>
);
}
export default App;上傳文件配置端口
考慮到大多數(shù)的 HTTP Server 服務(wù)器使用 CORS 配置,我們這里在根目錄下新建一個(gè) .env 的文件,添加如下內(nèi)容
PORT=8081
項(xiàng)目運(yùn)行
到這里我們可以運(yùn)行下前端項(xiàng)目了,使用命令 pnpm start,瀏覽器地址欄輸入 http://localhost:8081/, ok 項(xiàng)目正常運(yùn)行
文件選擇器、上傳按鈕、文件列表都已經(jīng)可以顯示出來了,但還無法上傳。這是因?yàn)楹蠖瞬糠诌€沒有跑起來,接下來,我?guī)ьI(lǐng)大家手把手搭建上傳文件的后端部分。
后端部分
后端部分我們使用 Nodejs + Express + Multer + Mongodb 來搭建文件上傳的項(xiàng)目,配合前端 Reactjs + Axios 來共同實(shí)現(xiàn)文件上傳功能。
后端項(xiàng)目我們提供以下幾個(gè)API
- POST
/upload文件上傳接口 - GET
/files文件列表獲取接口 - GET
/files/[filename]下載指定文件
配置 Node.js 開發(fā)環(huán)境
我們先使用命令 mkdir 創(chuàng)建一個(gè)空文件夾,然后 cd 到文件夾里面 這個(gè)文件夾就是我們的項(xiàng)目文件夾
mkdir nodejs-mongodb-upload-files cd nodejs-mongodb-upload-files
接著使用命令
npm init --yes
初始化項(xiàng)目,接著安裝項(xiàng)目需要的依賴包, 輸入如下命令
npm install express cors multer multer-gridfs-storage mongodb
package.js 文件
{
"name": "nodejs-mongodb-upload-files",
"version": "1.0.0",
"description": "Node.js upload multiple files to MongoDB",
"main": "src/server.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"node",
"upload",
"multiple",
"files",
"mongodb"
],
"license": "ISC",
"dependencies": {
"cors": "^2.8.5",
"express": "^4.17.1",
"mongodb": "^4.1.3",
"multer": "^1.4.3",
"multer-gridfs-storage": "^5.0.2"
}
}配置 MongoDB 數(shù)據(jù)庫
src/config/db.js
module.exports = {
url: "mongodb://localhost:27017/",
database: "files_db",
filesBucket: "photos",
};配置文件上傳存儲的中間件
src/middleware/upload.js
const util = require("util");
const multer = require("multer");
const { GridFsStorage } = require("multer-gridfs-storage");
const dbConfig = require("../config/db");
var storage = new GridFsStorage({
url: dbConfig.url + dbConfig.database,
options: { useNewUrlParser: true, useUnifiedTopology: true },
file: (req, file) => {
const match = ["image/png", "image/jpeg", "image/gif"];
if (match.indexOf(file.mimetype) === -1) {
const filename = `${Date.now()}-zhijianqiu-${file.originalname}`;
return filename;
}
return {
bucketName: dbConfig.filesBucket,
filename: `${Date.now()}-zhijianqiu-${file.originalname}`
};
}
});
const maxSize = 2 * 1024 * 1024;
var uploadFiles = multer({ storage: storage, limits: { fileSize: maxSize } }).single("file");
var uploadFilesMiddleware = util.promisify(uploadFiles);
module.exports = uploadFilesMiddleware;這里我們定義一個(gè) storage 的配置對象 GridFsStorage
url: 必須是指向MongoDB數(shù)據(jù)庫的標(biāo)準(zhǔn)MongoDB連接字符串。multer-gridfs-storage模塊將自動為您創(chuàng)建一個(gè)mongodb連接。options: 自定義如何建立連接file: 這是控制數(shù)據(jù)庫中文件存儲的功能。該函數(shù)的返回值是一個(gè)具有以下屬性的對象:filename, metadata, chunkSize, bucketName, contentType...我們還檢查文件是否為圖像file.mimetype。bucketName表示文件將存儲在photos.chunks和photos.files集合中。接下來我們使用
multer模塊來初始化中間件util.promisify()并使導(dǎo)出的中間件對象可以與async-await.single()帶參數(shù)的函數(shù)是 input 標(biāo)簽的名稱這里使用
Multer API來限制上傳文件大小,添加limits: { fileSize: maxSize }以限制文件大小
創(chuàng)建文件上傳的控制器
controllers/flileUploadController.js
這個(gè)主要用于文件上傳,我們創(chuàng)建一個(gè)名 upload 函數(shù),并將這個(gè)函數(shù)導(dǎo)出去
- 我們使用 文件上傳中間件函數(shù)處理上傳的文件
- 使用 Multer 捕獲相關(guān)錯(cuò)誤
- 返回響應(yīng)
文件列表數(shù)據(jù)獲取和下載
getListFiles: 函數(shù)主要是獲取photos.files,返回url, namedownload(): 接收文件name作為輸入?yún)?shù),從mongodb內(nèi)置打開下載流GridFSBucket,然后response.write(chunk)API 將文件傳輸?shù)娇蛻舳恕?/li>
const upload = require("../middleware/upload");
const dbConfig = require("../config/db");
const MongoClient = require("mongodb").MongoClient;
const GridFSBucket = require("mongodb").GridFSBucket;
const url = dbConfig.url;
const baseUrl = "http://localhost:8080/files/";
const mongoClient = new MongoClient(url);
const uploadFiles = async (req, res) => {
try {
await upload(req, res);
if (req.file == undefined) {
return res.status(400).send({ message: "請選擇要上傳的文件" });
}
return res.status(200).send({
message: "文件上傳成功" + req.file.originalname,
});
} catch (error) {
console.log(error);
if (error.code == "LIMIT_FILE_SIZE") {
return res.status(500).send({
message: "文件大小不能超過 2MB",
});
}
return res.status(500).send({
message: `無法上傳文件:, ${error}`
});
}
};
const getListFiles = async (req, res) => {
try {
await mongoClient.connect();
const database = mongoClient.db(dbConfig.database);
const files = database.collection(dbConfig.filesBucket + ".files");
let fileInfos = [];
if ((await files.estimatedDocumentCount()) === 0) {
fileInfos = []
}
let cursor = files.find({})
await cursor.forEach((doc) => {
fileInfos.push({
name: doc.filename,
url: baseUrl + doc.filename,
});
});
return res.status(200).send(fileInfos);
} catch (error) {
return res.status(500).send({
message: error.message,
});
}
};
const download = async (req, res) => {
try {
await mongoClient.connect();
const database = mongoClient.db(dbConfig.database);
const bucket = new GridFSBucket(database, {
bucketName: dbConfig.filesBucket,
});
let downloadStream = bucket.openDownloadStreamByName(req.params.name);
downloadStream.on("data", function (data) {
return res.status(200).write(data);
});
downloadStream.on("error", function (err) {
return res.status(404).send({ message: "無法獲取文件" });
});
downloadStream.on("end", () => {
return res.end();
});
} catch (error) {
return res.status(500).send({
message: error.message,
});
}
};
module.exports = {
uploadFiles,
getListFiles,
download,
};定義路由
在 routes 文件夾中,使用 Express Router 在 index.js 中定義路由
const express = require("express");
const router = express.Router();
const uploadController = require("../controllers/flileUploadController");
let routes = app => {
router.post("/upload", uploadController.uploadFiles);
router.get("/files", uploadController.getListFiles);
router.get("/files/:name", uploadController.download);
return app.use("/", router);
};
module.exports = routes;- POST
/upload: 調(diào)用uploadFiles控制器的功能。 - GET
/files獲取/files圖像列表。 - GET
/files/:name下載帶有文件名的圖像。
創(chuàng)建 Express 服務(wù)器
const cors = require("cors");
const express = require("express");
const app = express();
global.__basedir = __dirname;
var corsOptions = {
origin: "http://localhost:8081"
};
app.use(cors(corsOptions));
const initRoutes = require("./routes");
app.use(express.urlencoded({ extended: true }));
initRoutes(app);
let port = 8080;
app.listen(port, () => {
console.log(`Running at localhost:${port}`);
});這里我們導(dǎo)入了 Express 和 Cors,
- Express 用于構(gòu)建 Rest api
- Cors提供 Express 中間件以啟用具有各種選項(xiàng)的 CORS。
創(chuàng)建一個(gè) Express 應(yīng)用程序,然后使用方法添加cors中間件 在端口 8080 上偵聽傳入請求。
運(yùn)行項(xiàng)目并測試
在項(xiàng)目根目錄下在終端中輸入命令 node src/server.js, 控制臺顯示
Running at localhost:8080
使用 postman 工具測試,ok 項(xiàng)目正常運(yùn)行
文件上傳接口


文件列表接口

數(shù)據(jù)庫

React + Node.js 上傳文件前后端一起運(yùn)行
在 nodejs-mongodb-upload-files 文件夾根目錄運(yùn)行后端 Nodejs
node src/server.js
在 react-multiple-files-upload 文件夾根目錄運(yùn)行前端 React
pnpm start
然后打開瀏覽器輸入前端訪問網(wǎng)址:

到這里整個(gè)前后端「上傳文件」管理工具就搭建完成了。
到此這篇關(guān)于Reactjs + Nodejs + Mongodb 實(shí)現(xiàn)文件上傳功能的文章就介紹到這了,更多相關(guān)Reactjs Nodejs Mongodb文件上傳內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
淺談react.js中實(shí)現(xiàn)tab吸頂效果的問題
下面小編就為大家?guī)硪黄獪\談react.js中實(shí)現(xiàn)tab吸頂效果的問題。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-09-09
基于react hooks,zarm組件庫配置開發(fā)h5表單頁面的實(shí)例代碼
這篇文章主要介紹了基于react hooks,zarm組件庫配置開發(fā)h5表單頁面,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-04-04
React使用高德地圖的實(shí)現(xiàn)示例(react-amap)
這篇文章主要介紹了React使用高德地圖的實(shí)現(xiàn)示例(react-amap),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04
在React中實(shí)現(xiàn)txt文本文件預(yù)覽的完整指南
在前端開發(fā)中,從遠(yuǎn)程 URL 加載并預(yù)覽文本文件是一項(xiàng)實(shí)用且常見的功能,今天,我將帶你深入剖析一個(gè) React 組件 TextViewerURL,它通過 URL 加載文本文件,支持多種編碼),并搭配精心設(shè)計(jì)的樣式,讓文本展示更美觀、交互更友好,感興趣的小伙伴跟著小編一起來看看吧2025-03-03
create-react-app項(xiàng)目配置全解析
這篇文章主要為大家介紹了create-react-app項(xiàng)目配置全解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06
React Hook父組件如何獲取子組件的數(shù)據(jù)/函數(shù)
這篇文章主要介紹了React Hook父組件如何獲取子組件的數(shù)據(jù)/函數(shù),具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-09-09
React?中使用?Redux?的?4?種寫法小結(jié)
這篇文章主要介紹了在?React?中使用?Redux?的?4?種寫法,Redux 一般來說并不是必須的,只有在項(xiàng)目比較復(fù)雜的時(shí)候,比如多個(gè)分散在不同地方的組件使用同一個(gè)狀態(tài),本文就React使用?Redux的相關(guān)知識給大家介紹的非常詳細(xì),需要的朋友參考下吧2022-06-06

