本教程手把手带领大家搭建一套通过 React + Node.js + Mongodb 上传文件的后台系统,只要你跟随本教程一步步走,一定能很好的理解整个前后端上传文件的代码逻辑。前端我们使用 Reactjs + Axios 来搭建前端上传文件应用,后端我们使用 Node.js + Express + Multer + Mongodb 来搭建后端上传文件处理应用。
当然,本教程还会教给大家如何写一个可以限制上传文件大小、有百分比进度条、可报错、可显示服务器上文件列表、可点击下载文件的前端操作界面。
最后完成的上传文件工具后台如下图,跟随本教学习,你也可以搭建出来。
全栈实战教程:
后端实战教程:
使用 Node.js + MySQL 开发 RESTful API 接口(Node.js + Express + Sequelize + MySQL)
使用 Node.js + MongoDB 开发 RESTful API 接口(Node.js + Express + MongoDB)
如果你正在搭建后台管理工具,又不想处理前端问题,推荐使用卡拉云,卡拉云是新一代低代码开发工具,可一键接入常见数据库及 API ,无需懂前端,仅需拖拽即可快速搭建属于你自己的后台管理工具,一周工作量缩减至一天,详见本文文末。
├── 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入口文件扩展阅读:《React Echarts 使用教程 - 如何在 React 中加入图表》
这里我们使用 pnpm vite 创建一个 React 项目
npx create-react-app kalacloud-react-multiple-files-upload
项目创建完成后,cd 进入项目,安装项目运行需要的依赖包和 Axios 终端分别依次如下命令
pnpm install
pnpm install axios
执行完成我们启动项目 pnpm start
可以看到控制台中已经输出了信息,在浏览器地址栏中输入控制台输出的地址,项目已经跑起来了
运行如下命令
bootstrap
安装完成后,我们打开 src/App.js
文件, 添加如下代码
import React from "react";
import "./App.css";
import "bootstrap/dist/css/bootstrap.min.css";
function App() {
return (
...
);
}
export default App;
扩展阅读:《7 款最棒的开源 React 移动端 UI 组件库和模版框架》
在 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 数据库中的数据最后将这个对象导出去。
扩展阅读:《7 款最棒的开源 React UI 组件库和模版框架测评》
接下来我们来创建文件上传组件,首先组件要满足功能有文件上传,上传进度条信息展示,文件预览,提示信息,文件下载等功能
这里我们使用 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 (
{progressInfos && progressInfos.val.length > 0 &&
progressInfos.val.map((progressInfo, index) => (
{progressInfo.fileName}
{progressInfo.percentage}%
))}
{message.length > 0 && (
{message.map((item, i) => {
return - {item}
;
})}
)}
List of Files
{fileInfos &&
fileInfos.map((file, index) => (
-
{file.name}
))}
);
};
在 UI 相关的代码中, 我们使用了 Bootstrap 的进度条
.progress
作为最外层包装.progress-bar
显示进度信息.progress-bar
需要 style 按百分比设置进度信息.progress-bar
进度条还可以设置 role
和 aria
属性文件列表信息的展示我们使用 map
遍历 fileInfos
数组,并且将文件的 url
,name
信息展示出来
最后,我们将上传文件组件导出
const UploadFiles = () => {
...
}
export default UploadFiles;
扩展阅读:《最好用的 8 款 React Datepicker 时间日期选择器测评推荐》
import React from "react";
import "./App.css";
import "bootstrap/dist/css/bootstrap.min.css";
import UploadFiles from "./components/UploadFiles.js"
function App() {
return (
);
}
export default App;
考虑到大多数的 HTTP Server 服务器使用 CORS 配置,我们这里在根目录下新建一个 .env 的文件,添加如下内容
到这里我们可以运行下前端项目了,使用命令 pnpm start
,浏览器地址栏输入 http://localhost:8081/, ok 项目正常运行
文件选择器、上传按钮、文件列表都已经可以显示出来了,但还无法上传。这是因为后端部分还没有跑起来,接下来,我带领大家手把手搭建上传文件的后端部分。
你可以在我们的 github 上下载到完整的 React 图片上传 Demo。
当然你也可以不用这么费劲搭建前端做图片上传功能,直接使用卡拉云,无需懂前后端,简单拖拽即可生成一套属于你自己的后台管理工具。
扩展阅读:《React form 表单验证终极教程》
后端部分我们使用 Nodejs + Express + Multer + Mongodb 来搭建文件上传的项目,配合前端 Reactjs + Axios 来共同实现文件上传功能。
后端项目我们提供以下几个API
/upload
文件上传接口/files
文件列表获取接口/files/[filename]
下载指定文件我们先使用命令 mkdir 创建一个空文件夹,然后 cd 到文件夹里面 这个文件夹就是我们的项目文件夹
mkdir kalacloud-nodejs-mongodb-upload-files
cd kalacloud-nodejs-mongodb-upload-files
接着使用命令
初始化项目,接着安装项目需要的依赖包, 输入如下命令
npm install express cors multer multer-gridfs-storage mongodb
package.js 文件
{
"name": "kalacloud-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"
}
}
扩展阅读:《React Router 6 (React路由) 最详细教程》
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()}-kalacloud-${file.originalname}`;
return filename;
}
return {
bucketName: dbConfig.filesBucket,
filename: `${Date.now()}-kalacloud-${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 }
以限制文件大小
扩展阅读:《最好的 6 个 React Table 组件详细亲测推荐》
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,
};
扩展阅读:《React Draggable 实现拖拽 - 最详细中文教程》
在 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
,
在项目根目录下在终端中输入命令 node src/server.js, 控制台显示
Running at localhost:8080
使用 postman 工具测试,ok 项目正常运行
文件上传接口
文件列表接口
MongoDB 数据库
在 kalacloud-nodejs-mongodb-upload-files 文件夹根目录运行后端 Nodejs
在 kalacloud-react-multiple-files-upload 文件夹根目录运行前端 React
然后打开浏览器输入前端访问网址:
到这里整个前后端「上传文件」管理工具就搭建完成了。
Node.js 后端「文件上传」源码 你可以在我们的 github 上下载到完整的 Node.js 后端「文件上传」源码。
如果你还没搞懂,也不用着急,直接使用卡拉云,无需懂任何前后端技术,仅需简单的鼠标拖拽即可快速生成包括「文件上传」管理在内的任何后台管理工具。
本教程手把手教大家搭建 React 前端 + Node.js 后端 的「文件上传」管理工具,如果你一步步跟着走,一定已经把 Demo 跑起来了。但如果你会使用最新的低代码开发工具「卡拉云」,完全不需要这么繁琐,仅需 1 分钟,就能搭建起属于自己的「文件上传」管理工具。
立即开通卡拉云,从侧边工具栏直接拖拽组件到页面,生成上传组件和文件管理工具。1 分钟搞定「上传文件」管理工具。
再看个卡拉云的 Demo 案例,下面是用卡拉云搭建的数据库 CURD 后台管理系统,只需拖拽组件,即可在10分钟内完成搭建。
可直接分享给同事一起使用:https://my.kalacloud.com/apps/8z9z3yf9fy/published
卡拉云可帮你快速搭建企业内部工具,下图为使用卡拉云搭建的内部广告投放监测系统,无需懂前端,仅需拖拽组件,10 分钟搞定。你也可以快速搭建一套属于你的后台管理工具。
卡拉云是新一代低代码开发平台,与前端框架 Vue、React等相比,卡拉云的优势在于不用首先搭建开发环境,直接注册即可开始使用。开发者完全不用处理任何前端问题,只需简单拖拽,即可快速生成所需组件,可一键接入常见数据库及 API,根据引导简单几步打通前后端,数周的开发时间,缩短至 1 小时。立即免费试用卡拉云。
全栈实战教程:
后端实战教程: