Reactjs Nodejs Mongodb文件上传
知见秋 人气:0Reactjs + 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
然后打开浏览器输入前端访问网址:
到这里整个前后端「上传文件」管理工具就搭建完成了。
加载全部内容