<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
今天是使用 Reactjs + Nodejs + Mongodb 實現檔案上傳功能。前端我們使用 Reactjs + Axios 來搭建前端上傳檔案應用,後端我們使用 Node.js + Express + Multer + Mongodb 來搭建後端上傳檔案處理應用。
├── 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.js
App.js
: 把我們的元件匯入到 React 的起始頁components/UploadFiles.js
: 檔案上傳元件http-common.js
: 使用 HTTP 基礎 Url 和檔頭初始化 Axios。services/UploadFilesService.js
: 這個檔案中的函數用於檔案上傳和獲取資料庫中檔案資料├── README.md ├── package.json ├── pnpm-lock.yaml └── node_modules └── ... └── src ├── config │ └── db.js ├── controllers │ └── flileUploadController.js ├── middleware │ └── upload.js ├── routes │ └── index.js └── server.js
src/db.js
包括 MongoDB 和 Multer 的設定(url、資料庫、檔案儲存桶)。middleware/upload.js
:初始化 Multer GridFs 儲存引擎(包括 MongoDB)並定義中介軟體函數。controllers/flileUploadController.js
:設定 Rest APIroutes/index.js
:路由,定義前端請求後端如何執行server.js
:Node.js入口檔案這裡我們使用 pnpm vite 建立一個 React 專案
npx create-react-app react-multiple-files-upload
專案建立完成後,cd 進入專案,安裝專案執行需要的依賴包和 Axios 終端分別依次如下命令
pnpm install pnpm install axios
執行完成我們啟動專案 pnpm start
可以看到控制檯中已經輸出了資訊,在瀏覽器位址列中輸入控制檯輸出的地址,專案已經跑起來了
執行如下命令
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;
在 src
目錄下 我們新建 http-common.js
檔案,並新增如下程式碼
import axios from "axios"; export default axios.create({ baseURL: "http://localhost:8080", headers: { "Content-type": "application/json" } });
這裡 baseURL 的地址是我們後端伺服器的 REST API 地址,要根據個人實際情況進行修改。本教學後文,教你搭建上傳檔案的後端部分,請繼續閱讀。
src/services/UploadFilesService.js
,這個檔案主要的作用就是和後端專案通訊,以進行檔案的上傳和檔案列表資料的獲取等
在檔案中我們加入如下內容
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,並定義一個物件,在物件中新增兩個屬性函數,作用如下
upload
:函數以 POST 的方式將資料提交到後端,接收兩個引數 file
和 onUploadProgress
file
上傳的檔案,以 FormData
的形式上傳onUploadProgress
檔案上傳進度條事件,監測進度條資訊getFiles
: 函數用於獲取儲存在 Mongodb 資料庫中的資料最後將這個物件匯出去
接下來我們來建立檔案上傳元件,首先元件要滿足功能有檔案上傳,上傳進度條資訊展示,檔案預覽,提示資訊,檔案下載等功能
這裡我們使用 React Hooks 和 useState 來建立檔案上傳元件,建立檔案 src/components/UploadFiles
,新增如下程式碼
import React, { useState, useEffect, useRef } from "react"; import UploadService from "../services/UploadFilesService"; const UploadFiles = () => { return ( ); }; export default UploadFiles;
然後我們使用 React Hooks 定義狀態
const UploadFiles = () => { const [selectedFiles, setSelectedFiles] = useState(undefined); const [progressInfos, setProgressInfos] = useState({ val: [] }); const [message, setMessage] = useState([]); const [fileInfos, setFileInfos] = useState([]); const progressInfosRef = useRef(null) }
狀態定義好後,我們在新增一個獲取檔案的方法 selectFiles()
const UploadFiles = () => { ... const selectFiles = (event) => { setSelectedFiles(event.target.files); setProgressInfos({ val: [] }); }; ... }
selectedFiles
用來儲存當前選定的檔案,每個檔案都有一個相應的進度資訊如檔名和進度資訊等,我們將這些資訊儲存在 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
方法將可迭代資料轉換陣列形式的資料,接著使用 map
方法將檔案的進度資訊,名稱資訊儲存到 _progressInfos
中
接著我們使用 map
方法呼叫 files
陣列中的每一項,使 files
中的每一項都經過 upload
函數的處理,在 upload
函數中我們會返回上傳檔案請求函數 UploadService.upload
的 Promise
狀態
所以 uploadPromises
中儲存的就是處於 Promise 狀態的上傳檔案函數,接著我們使用 Promise.all
同時傳送多個檔案上傳請求,在所有檔案都上傳成功後,我們將會呼叫獲取所有檔案資料的介面,並將獲取到的資料展示出來。
upload
函數程式碼如下
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, ])); }); };
每個檔案的上傳進度資訊根據 event.loaded
和 event.total
百分比值來計算,因為在呼叫 upload
函數的時候,已經將對應檔案的索引傳遞進來了,所有我們根據對應的索引設定對應檔案的上傳進度
除了這些工作,我們還需要在 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 相關的程式碼中, 我們使用了 Bootstrap 的進度條
.progress
作為最外層包裝.progress-bar
顯示進度資訊.progress-bar
需要 style 按百分比設定進度資訊.progress-bar
進度條還可以設定 role
和 aria
屬性檔案列表資訊的展示我們使用 map
遍歷 fileInfos
陣列,並且將檔案的 url
,name
資訊展示出來
最後,我們將上傳檔案元件匯出
const UploadFiles = () => { ... } export default UploadFiles;
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;
考慮到大多數的 HTTP Server 伺服器使用 CORS 設定,我們這裡在根目錄下新建一個 .env 的檔案,新增如下內容
PORT=8081
到這裡我們可以執行下前端專案了,使用命令 pnpm start
,瀏覽器位址列輸入 http://localhost:8081/, ok 專案正常執行
檔案選擇器、上傳按鈕、檔案列表都已經可以顯示出來了,但還無法上傳。這是因為後端部分還沒有跑起來,接下來,我帶領大家手把手搭建上傳檔案的後端部分。
後端部分我們使用 Nodejs + Express + Multer + Mongodb 來搭建檔案上傳的專案,配合前端 Reactjs + Axios 來共同實現檔案上傳功能。
後端專案我們提供以下幾個API
/upload
檔案上傳介面/files
檔案列表獲取介面/files/[filename]
下載指定檔案我們先使用命令 mkdir 建立一個空資料夾,然後 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" } }
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
資料庫的標準 MongoDB
連線字串。multer-gridfs-storage
模組將自動為您建立一個 mongodb
連線。
options
: 自定義如何建立連線
file
: 這是控制資料庫中檔案儲存的功能。該函數的返回值是一個具有以下屬性的物件:filename, metadata, chunkSize, bucketName, contentType...
我們還檢查檔案是否為影象 file.mimetype
。bucketName
表示檔案將儲存在 photos.chunks
和 photos.files
集合中。
接下來我們使用 multer
模組來初始化中介軟體 util.promisify()
並使匯出的中介軟體物件可以與 async-await
.
single()
帶引數的函數是 input 標籤的名稱
這裡使用 Multer API
來限制上傳檔案大小,新增 limits: { fileSize: maxSize }
以限制檔案大小
controllers/flileUploadController.js
這個主要用於檔案上傳,我們建立一個名 upload
函數,並將這個函數匯出去
檔案列表資料獲取和下載
getListFiles
: 函數主要是獲取 photos.files
,返回 url, name
download()
: 接收檔案 name
作為輸入引數,從 mongodb
內建開啟下載流 GridFSBucket
,然後 response.write(chunk)
API 將檔案傳輸到使用者端。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;
/upload
: 呼叫 uploadFiles
控制器的功能。/files
獲取/files影象列表。/files/:name
下載帶有檔名的影象。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 應用程式,然後使用方法新增cors中介軟體 在埠 8080 上偵聽傳入請求。
在專案根目錄下在終端中輸入命令 node src/server.js, 控制檯顯示
Running at localhost:8080
使用 postman 工具測試,ok 專案正常執行
檔案上傳介面
檔案列表介面
資料庫
在 nodejs-mongodb-upload-files 資料夾根目錄執行後端 Nodejs
node src/server.js
在 react-multiple-files-upload 資料夾根目錄執行前端 React
pnpm start
然後開啟瀏覽器輸入前端存取網址:
到這裡整個前後端「上傳檔案」管理工具就搭建完成了。
到此這篇關於Reactjs + Nodejs + Mongodb 實現檔案上傳功能的文章就介紹到這了,更多相關Reactjs Nodejs Mongodb檔案上傳內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45