写点什么

React + Node.js 全栈实战教程 - 手把手教你搭建「文件上传」管理后台

作者:蒋川
  • 2022 年 7 月 11 日
  • 本文字数:10304 字

    阅读完需:约 34 分钟

React + Node.js 全栈实战教程 - 手把手教你搭建「文件上传」管理后台

本教程手把手带领大家搭建一套通过 React + Node.js + Mongodb 上传文件的后台系统,只要你跟随本教程一步步走,一定能很好的理解整个前后端上传文件的代码逻辑。前端我们使用 Reactjs + Axios 来搭建前端上传文件应用,后端我们使用 Node.js + Express + Multer + Mongodb 来搭建后端上传文件处理应用。


当然,本教程还会教给大家如何写一个可以限制上传文件大小、有百分比进度条、可报错、可显示服务器上文件列表、可点击下载文件的前端操作界面。


最后完成的上传文件工具后台如下图,跟随本教学习,你也可以搭建出来。



全栈实战教程:



后端实战教程:



如果你正在搭建后台管理工具,又不想处理前端问题,推荐使用卡拉云,卡拉云是新一代低代码开发工具,可一键接入常见数据库及 API ,无需懂前端,仅需拖拽即可快速搭建属于你自己的后台管理工具,一周工作量缩减至一天,详见本文文末。

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 API

  • routes/index.js:路由,定义前端请求后端如何执行

  • server.js:Node.js 入口文件


扩展阅读:《React Echarts 使用教程 - 如何在 React 中加入图表

✦ 前端部分-上传文件 React + Axios

配置 React 环境

这里我们使用 pnpm vite 创建一个 React 项目


npx create-react-app kalacloud-react-multiple-files-upload
复制代码


项目创建完成后,cd 进入项目,安装项目运行需要的依赖包和 Axios 终端分别依次如下命令


pnpm install
pnpm install axios
复制代码


执行完成我们启动项目 pnpm start


可以看到控制台中已经输出了信息,在浏览器地址栏中输入控制台输出的地址,项目已经跑起来了

导入 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;
复制代码


扩展阅读:《7 款最棒的开源 React 移动端 UI 组件库和模版框架

初始化 Axios HTTP 客户端

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 的方式将数据提交到后端,接收两个参数 fileonUploadProgress

  • file 上传的文件,以 FormData 的形式上传

  • onUploadProgress 文件上传进度条事件,监测进度条信息

  • getFiles: 函数用于获取存储在 Mongodb 数据库中的数据


最后将这个对象导出去。


扩展阅读:《7 款最棒的开源 React UI 组件库和模版框架测评

创建 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.uploadPromise 状态


所以 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.loadedevent.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 进度条还可以设置 rolearia 属性


文件列表信息的展示我们使用 map 遍历 fileInfos 数组,并且将文件的 url,name 信息展示出来


最后,我们将上传文件组件导出


const UploadFiles = () => {  ...}export default UploadFiles;
复制代码


扩展阅读:《最好用的 8 款 React Datepicker 时间日期选择器测评推荐

将文件上传组件添加到 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> <p> <a href="https://kalacloud.com/">卡拉云</a>-低代码开发工具1秒搭建 </p> <p>使用卡拉云无需懂任何前端技术,仅需拖拽即可搭建属于你的后台管理系统</p> <div className="content"> <UploadFiles /> </div> </div> );}
export default App;
复制代码

上传文件配置端口

考虑到大多数的 HTTP Server 服务器使用 CORS 配置,我们这里在根目录下新建一个 .env 的文件,添加如下内容

运行 React 项目

到这里我们可以运行下前端项目了,使用命令 pnpm start,浏览器地址栏输入 http://localhost:8081/, ok 项目正常运行



文件选择器、上传按钮、文件列表都已经可以显示出来了,但还无法上传。这是因为后端部分还没有跑起来,接下来,我带领大家手把手搭建上传文件的后端部分。

React 前端「文件上传」源码

你可以在我们的 github 上下载到完整的 React 图片上传 Demo。


当然你也可以不用这么费劲搭建前端做图片上传功能,直接使用卡拉云,无需懂前后端,简单拖拽即可生成一套属于你自己的后台管理工具。


扩展阅读:《React form 表单验证终极教程

✦ 后端部分 - 文件上传 Node.js + Express + Multer + MongoDB

后端部分我们使用 Nodejs + Express + Multer + Mongodb 来搭建文件上传的项目,配合前端 Reactjs + Axios 来共同实现文件上传功能。


后端项目我们提供以下几个 API


  • POST /upload 文件上传接口

  • GET /files 文件列表获取接口

  • GET /files/[filename] 下载指定文件

配置 Node.js 开发环境

我们先使用命令 mkdir 创建一个空文件夹,然后 cd 到文件夹里面 这个文件夹就是我们的项目文件夹


mkdir kalacloud-nodejs-mongodb-upload-filescd 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路由) 最详细教程

配置 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()}-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.mimetypebucketName 表示文件将存储在 photos.chunksphotos.files 集合中。

  • 接下来我们使用 multer 模块来初始化中间件 util.promisify() 并使导出的中间件对象可以与 async-await.

  • single() 带参数的函数是 input 标签的名称

  • 这里使用 Multer API 来限制上传文件大小,添加 limits: { fileSize: maxSize } 以限制文件大小


扩展阅读:《最好的 6 个 React Table 组件详细亲测推荐

创建文件上传的控制器

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,};
复制代码


扩展阅读:《React Draggable 实现拖拽 - 最详细中文教程

定义 routes 路由

routes 文件夹中,使用 Express Routerindex.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}`);});
复制代码


这里我们导入了 ExpressCors,


  • Express 用于构建 Rest api

  • Cors 提供 Express 中间件以启用具有各种选项的 CORS。 创建一个 Express 应用程序,然后使用方法添加 cors 中间件 在端口 8080 上侦听传入请求。

运行项目并测试

在项目根目录下在终端中输入命令 node src/server.js, 控制台显示


Running at localhost:8080
复制代码


使用 postman 工具测试,ok 项目正常运行


文件上传接口



文件列表接口



MongoDB 数据库


React + Node.js 上传文件前后端一起运行

在 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 小时。立即免费试用卡拉云


全栈实战教程:



后端实战教程:


发布于: 刚刚阅读数: 3
用户头像

蒋川

关注

我的微信:HiJiangChuan 2020.09.08 加入

卡拉云 CMO 卡拉云是一套帮助后端程序员搭建企业内部工具的系统,欢迎试用 www.kalacloud.com

评论

发布
暂无评论
React + Node.js 全栈实战教程 - 手把手教你搭建「文件上传」管理后台_node.js_蒋川_InfoQ写作社区