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

React + Node.js + Mongodb「上傳文件」前后端項目結(jié)構(gòu)
前端項目結(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: 把我們的組件導入到 React 的起始頁components/UploadFiles.js: 文件上傳組件http-common.js: 使用 HTTP 基礎(chǔ) Url 和標頭初始化 Axios。- 我們在.env中為我們的應(yīng)用程序配置端口
services/UploadFilesService.js: 這個文件中的函數(shù)用于文件上傳和獲取數(shù)據(jù)庫中文件數(shù)據(jù)
后端項目結(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后端項目結(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)建一個 React 項目
npx create-react-app react-multiple-files-upload
項目創(chuàng)建完成后,cd 進入項目,安裝項目運行需要的依賴包和 Axios 終端分別依次如下命令
pnpm install pnpm install axios
執(zhí)行完成我們啟動項目 pnpm start
可以看到控制臺中已經(jīng)輸出了信息,在瀏覽器地址欄中輸入控制臺輸出的地址,項目已經(jīng)跑起來了
導入 bootstrap 到項目中
運行如下命令
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ù)個人實際情況進行修改。本教程后文,教你搭建上傳文件的后端部分,請繼續(xù)閱讀。
創(chuàng)建上傳文件組件
src/services/UploadFilesService.js,這個文件主要的作用就是和后端項目通訊,以進行文件的上傳和文件列表數(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;首先導入我們之前寫好的 Axios HTTP 配置文件 http-common.js,并定義一個對象,在對象中添加兩個屬性函數(shù),作用如下
upload:函數(shù)以 POST 的方式將數(shù)據(jù)提交到后端,接收兩個參數(shù)file和onUploadProgressfile上傳的文件,以FormData的形式上傳onUploadProgress文件上傳進度條事件,監(jiān)測進度條信息
getFiles: 函數(shù)用于獲取存儲在 Mongodb 數(shù)據(jù)庫中的數(shù)據(jù)
最后將這個對象導出去
創(chuàng)建 React 文件上傳組件
接下來我們來創(chuàng)建文件上傳組件,首先組件要滿足功能有文件上傳,上傳進度條信息展示,文件預覽,提示信息,文件下載等功能
這里我們使用 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)定義好后,我們在添加一個獲取文件的方法 selectFiles()
const UploadFiles = () => {
...
const selectFiles = (event) => {
setSelectedFiles(event.target.files);
setProgressInfos({ val: [] });
};
...
}selectedFiles 用來存儲當前選定的文件,每個文件都有一個相應(yīng)的進度信息如文件名和進度信息等,我們將這些信息存儲在 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([]);
};
...
}我們上傳多個文件的時候會將文件信息存儲在 selectedFiles, 在上面的代碼中 我們使用 Array.from 方法將可迭代數(shù)據(jù)轉(zhuǎn)換數(shù)組形式的數(shù)據(jù),接著使用 map 方法將文件的進度信息,名稱信息存儲到 _progressInfos 中
接著我們使用 map 方法調(diào)用 files 數(shù)組中的每一項,使 files 中的每一項都經(jīng)過 upload 函數(shù)的處理,在 upload 函數(shù)中我們會返回上傳文件請求函數(shù) UploadService.upload 的 Promise 狀態(tài)
所以 uploadPromises 中存儲的就是處于 Promise 狀態(tài)的上傳文件函數(shù),接著我們使用 Promise.all 同時發(fā)送多個文件上傳請求,在所有文件都上傳成功后,我們將會調(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,
]));
});
};每個文件的上傳進度信息根據(jù) event.loaded 和 event.total 百分比值來計算,因為在調(diào)用 upload 函數(shù)的時候,已經(jīng)將對應(yīng)文件的索引傳遞進來了,所有我們根據(jù)對應(yīng)的索引設(shè)置對應(yīng)文件的上傳進度
除了這些工作,我們還需要在 Effect HookuseEffect() 做如下功能,這部分代碼的作用其實 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 的進度條
- 使用
.progress作為最外層包裝 - 內(nèi)部使用
.progress-bar顯示進度信息 .progress-bar需要 style 按百分比設(shè)置進度信息.progress-bar進度條還可以設(shè)置role和aria屬性
文件列表信息的展示我們使用 map 遍歷 fileInfos 數(shù)組,并且將文件的 url,name 信息展示出來
最后,我們將上傳文件組件導出
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 配置,我們這里在根目錄下新建一個 .env 的文件,添加如下內(nèi)容
PORT=8081
項目運行
到這里我們可以運行下前端項目了,使用命令 pnpm start,瀏覽器地址欄輸入 http://localhost:8081/, ok 項目正常運行
文件選擇器、上傳按鈕、文件列表都已經(jīng)可以顯示出來了,但還無法上傳。這是因為后端部分還沒有跑起來,接下來,我?guī)ьI(lǐng)大家手把手搭建上傳文件的后端部分。
后端部分
后端部分我們使用 Nodejs + Express + Multer + Mongodb 來搭建文件上傳的項目,配合前端 Reactjs + Axios 來共同實現(xiàn)文件上傳功能。
后端項目我們提供以下幾個API
- POST
/upload文件上傳接口 - GET
/files文件列表獲取接口 - GET
/files/[filename]下載指定文件
配置 Node.js 開發(fā)環(huán)境
我們先使用命令 mkdir 創(chuàng)建一個空文件夾,然后 cd 到文件夾里面 這個文件夾就是我們的項目文件夾
mkdir nodejs-mongodb-upload-files cd nodejs-mongodb-upload-files
接著使用命令
npm init --yes
初始化項目,接著安裝項目需要的依賴包, 輸入如下命令
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;這里我們定義一個 storage 的配置對象 GridFsStorage
url: 必須是指向MongoDB數(shù)據(jù)庫的標準MongoDB連接字符串。multer-gridfs-storage模塊將自動為您創(chuàng)建一個mongodb連接。options: 自定義如何建立連接file: 這是控制數(shù)據(jù)庫中文件存儲的功能。該函數(shù)的返回值是一個具有以下屬性的對象:filename, metadata, chunkSize, bucketName, contentType...我們還檢查文件是否為圖像file.mimetype。bucketName表示文件將存儲在photos.chunks和photos.files集合中。接下來我們使用
multer模塊來初始化中間件util.promisify()并使導出的中間件對象可以與async-await.single()帶參數(shù)的函數(shù)是 input 標簽的名稱這里使用
Multer API來限制上傳文件大小,添加limits: { fileSize: maxSize }以限制文件大小
創(chuàng)建文件上傳的控制器
controllers/flileUploadController.js
這個主要用于文件上傳,我們創(chuàng)建一個名 upload 函數(shù),并將這個函數(shù)導出去
- 我們使用 文件上傳中間件函數(shù)處理上傳的文件
- 使用 Multer 捕獲相關(guān)錯誤
- 返回響應(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}`);
});這里我們導入了 Express 和 Cors,
- Express 用于構(gòu)建 Rest api
- Cors提供 Express 中間件以啟用具有各種選項的 CORS。
創(chuàng)建一個 Express 應(yīng)用程序,然后使用方法添加cors中間件 在端口 8080 上偵聽傳入請求。
運行項目并測試
在項目根目錄下在終端中輸入命令 node src/server.js, 控制臺顯示
Running at localhost:8080
使用 postman 工具測試,ok 項目正常運行
文件上傳接口


文件列表接口

數(shù)據(jù)庫

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

到這里整個前后端「上傳文件」管理工具就搭建完成了。
到此這篇關(guān)于Reactjs + Nodejs + Mongodb 實現(xiàn)文件上傳功能的文章就介紹到這了,更多相關(guān)Reactjs Nodejs Mongodb文件上傳內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
React框架快速實現(xiàn)簡易的Markdown編輯器
這篇文章主要為大家介紹了使用React框架實現(xiàn)簡易的Markdown編輯器,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-04-04
react 實現(xiàn)圖片正在加載中 加載完成 加載失敗三個階段的原理解析
這篇文章主要介紹了react 實現(xiàn)圖片正在加載中 加載完成 加載失敗三個階段的,通過使用loading的圖片來占位,具體原理解析及實現(xiàn)代碼跟隨小編一起通過本文學習吧2021-05-05
React + webpack 環(huán)境配置的方法步驟
本篇文章主要介紹了React + webpack 環(huán)境配置的方法步驟,詳解的介紹了開發(fā)環(huán)境的配置搭建,有興趣的可以了解一下2017-09-09
Yarn安裝項目依賴報error?An?unexpected?error?occurred:?“XXXXX:E
這篇文章主要為大家介紹了Yarn安裝項目依賴報error?An?unexpected?error?occurred:?“XXXXX:ESOCKETTIMEOUT”問題解決,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-03-03
詳解Jotai Immer如何實現(xiàn)undo redo功能示例詳解
這篇文章主要為大家介紹了詳解Jotai Immer如何實現(xiàn)undo redo功能示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-04-04
React工作流程及Error Boundaries實現(xiàn)過程講解
這篇文章主要介紹了React工作流程及Error Boundaries實現(xiàn)過程講解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-02-02
基于React實現(xiàn)表單數(shù)據(jù)的添加和刪除詳解
這篇文章主要給大家介紹了基于React實現(xiàn)表單數(shù)據(jù)的添加和刪除的方法,文中給出了詳細的示例供大家參考,相信對大家具有一定的參考價值,需要的朋友們下面來一起看看吧。2017-03-03
React?Hook中的useState函數(shù)的詳細解析
Hook 就是 JavaScript 函數(shù),這個函數(shù)可以幫助你鉤入(hook into) React State以及生命周期等特性,這篇文章主要介紹了React?Hook?useState函數(shù)的詳細解析的相關(guān)資料,需要的朋友可以參考下2022-10-10

