Reactjs + Nodejs + Mongodb 实现文件上传功能实例详解
作者:知见秋
Reactjs + Nodejs + Mongodb 实现文件上传
概述
今天是使用 Reactjs + Nodejs + Mongodb 实现文件上传功能。前端我们使用 Reactjs + Axios 来搭建前端上传文件应用,后端我们使用 Node.js + Express + Multer + Mongodb 来搭建后端上传文件处理应用。
React + Node.js + 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
Reactjs 前端部分
App.js
: 把我们的组件导入到 React 的起始页components/UploadFiles.js
: 文件上传组件http-common.js
: 使用 HTTP 基础 Url 和标头初始化 Axios。- 我们在.env中为我们的应用程序配置端口
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入口文件
前端部分-上传文件 React + Axios
配置 React 环境
这里我们使用 pnpm vite 创建一个 React 项目
npx create-react-app react-multiple-files-upload
项目创建完成后,cd 进入项目,安装项目运行需要的依赖包和 Axios 终端分别依次如下命令
pnpm install pnpm install axios
执行完成我们启动项目 pnpm start
可以看到控制台中已经输出了信息,在浏览器地址栏中输入控制台输出的地址,项目已经跑起来了
导入 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 的地址是我们后端服务器的 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 文件上传组件
接下来我们来创建文件上传组件,首先组件要满足功能有文件上传,上传进度条信息展示,文件预览,提示信息,文件下载等功能
这里我们使用 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;
将文件上传组件添加到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;
上传文件配置端口
考虑到大多数的 HTTP Server 服务器使用 CORS 配置,我们这里在根目录下新建一个 .env 的文件,添加如下内容
PORT=8081
项目运行
到这里我们可以运行下前端项目了,使用命令 pnpm start
,浏览器地址栏输入 http://localhost:8081/, ok 项目正常运行
文件选择器、上传按钮、文件列表都已经可以显示出来了,但还无法上传。这是因为后端部分还没有跑起来,接下来,我带领大家手把手搭建上传文件的后端部分。
后端部分
后端部分我们使用 Nodejs + Express + Multer + Mongodb 来搭建文件上传的项目,配合前端 Reactjs + Axios 来共同实现文件上传功能。
后端项目我们提供以下几个API
- POST
/upload
文件上传接口 - GET
/files
文件列表获取接口 - GET
/files/[filename]
下载指定文件
配置 Node.js 开发环境
我们先使用命令 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" } }
配置 MongoDB 数据库
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
函数,并将这个函数导出去
- 我们使用 文件上传中间件函数处理上传的文件
- 使用 Multer 捕获相关错误
- 返回响应
文件列表数据获取和下载
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;
- POST
/upload
: 调用uploadFiles
控制器的功能。 - GET
/files
获取/files图像列表。 - GET
/files/:name
下载带有文件名的图像。
创建 Express 服务器
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 用于构建 Rest api
- Cors提供 Express 中间件以启用具有各种选项的 CORS。
创建一个 Express 应用程序,然后使用方法添加cors中间件 在端口 8080 上侦听传入请求。
运行项目并测试
在项目根目录下在终端中输入命令 node src/server.js, 控制台显示
Running at localhost:8080
使用 postman 工具测试,ok 项目正常运行
文件上传接口
文件列表接口
数据库
React + Node.js 上传文件前后端一起运行
在 nodejs-mongodb-upload-files 文件夹根目录运行后端 Nodejs
node src/server.js
在 react-multiple-files-upload 文件夹根目录运行前端 React
pnpm start
然后打开浏览器输入前端访问网址:
到这里整个前后端「上传文件」管理工具就搭建完成了。
到此这篇关于Reactjs + Nodejs + Mongodb 实现文件上传功能的文章就介绍到这了,更多相关Reactjs Nodejs Mongodb文件上传内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!