6

全栈实战:React + Nodejs 搭建带预览的「上传图片/预览」管理后台

 2 years ago
source link: https://kalacloud.com/blog/react-upload-image-axios/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

全栈实战:React + Nodejs 搭建带预览的「上传图片/预览」管理后台

前端工程师
最近更新 2022年06月27日

全栈实战:React + Nodejs 搭建带预览的「上传图片/预览」管理后台

手把手教你开发带预览的 React 图片上传组件,即图片上传管理后台。只要你跟本教程一步一步走,最终能很好的理解整个前后端传图的工程逻辑。前端我们使用 React + Axios + Multipart 来搭建前端上传图片应用,后端我们使用 Node.js + Express + Multer 来搭建后端上传图片的后端处理。

本教程还会教给大家如何写一个可限制上传图片大小,有进度条,可报错,可显示图片列表,可下载已上传图片的图片管理后台。

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

React + Nodejs 搭建带预览的「上传图片/预览」管理后台-完成图

实现的功能:

  • 图片上传功能
  • 图片上传显示进度条功能
  • 图片预览功能
  • 图片列表功能
  • 图片下载功能

前端使用的技术:

  • Reactjs
  • Bootstrap
  • Axios

后端使用的技术/数据库:

  • Nodejs
  • Express
  • Multer
  • Mongodb
  • Multer-gridfs-storage

跟随本示例学习,你也可以搭建出来。

全栈实战教程:

后端实战教程:

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

✦ 前端部分 - React + React 图片上传组件 + Axios + Multipart

React 前端项目结构

├── README.md
├── node_modules
├── package-lock.json
├── package.json
├── public
│   └── index.html
└── src
    ├── App.css
    ├── App.js
    ├── components
    │   └── uploadImages.js
    ├── http-common.js     
    ├── index.js             
    └── services
        └── fileUpload.js 

配置 React 环境

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

npx create-react-app kalacloud-react-upload-images

完成后,cd 进入项目根目录

安装 Axios

npm install axios

安装完成后,我们使用命令 npm start 启动项目,可以看到 项目已经正常启动了

这里我们使用 bootstrap 的样式,导入 Bootstrap 到项目中

打开文件 public/index.html ,将以下代码添加到 head

<link type="text/css" rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" />

配置 Axios

src 文件夹下新建文件 http-common.js, 并添加如下内容

import axios from "axios";
export default axios.create({
  baseURL: "http://localhost:8080",
  headers: {
    "Content-type": "application/json"
  }
});

这里 baseURL 是你本地的地址。

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

React 图片上传功能

前端项目的基本配置已经完成,接下来我们开始图片上传功能,在 src 文件下 创建如下文件

src/services/UploadFilesService.js,这个文件主要的作用就是和后端项目通讯,以进行文件的上传和文件列表数据的获取等

在文件中我们加入如下内容

import http from "../http-common";

class FileUploadService {
  upload(file, onUploadProgress) {
    let formData = new FormData();
    formData.append("file", file);
    return http.post("/upload", formData, {
      headers: {
        "Content-Type": "multipart/form-data",
      },
      onUploadProgress,
    });
  }

  getFiles() {
    return http.get("/files");
  }
}

export default new FileUploadService();
  • upload: 用于将文件数据以 post 请求格式,FormData 键值对的形式提交到后端, onUploadProgress 则是用于获取进度条数据
  • getFiles: 用于获取服务器上的文件列表数据

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

home-demo-66b9606d95623721268b764851017602.gif

React 搭建后台太难?

试试卡拉云,无需懂前端,拖拽组件连接 API 和数据库直接生成后台系统,两个月的工期降低至1天

在 React 中创建图片上传组件

图片上传组件要满足功能有 图片上传,进度条,预览,提示信息等

首先建一个文件,并引入 import UploadService from "../services/fileUpload"

src/components/UploadFiles

代码如下:

import React, { Component } from "react";
import UploadService from "../services/UploadFilesService"

export default class UploadImages extends Component {
  constructor(props) {
    super(props);
  }

  render() {

    return (
      <div>

        
      </div>
    );
  }
}

接着我们在 constructor() 方法内部定义状态, 代码如下

export default class UploadImages extends Component {
  constructor(props) {
    super(props);
    ...
    this.state = {
      selectedFiles: undefined,
      previewImages: [],
      progressInfos: [],
      message: [],
      imageInfos: [],
    };
  }
}

状态定义好后,我们在添加一个选择图片的方法 selectFiles(),并添加 <input type="file" >,

export default class UploadImages extends Component {
  ...
  selectFiles(event) {
    let images = [];
    for (let i = 0; i < event.target.files.length; i++) {
      images.push(URL.createObjectURL(event.target.files[i]))
    }
    this.setState({
      progressInfos: [],
      message: [],
      selectedFiles: event.target.files,
      previewImages: images
    });
  }
}

我们使用 URL.createObjectURL() 用来获取图片预览 URL 并将其放入 previewImages 数组中, URL.createObjectURL()方法会创建一个 DOMString ,其中包含一个表示参数中提供的对象的URL。这个 URL 的生命周期和创建它的窗口中的 document 绑定,

接着我们定义文件上传方法 upload

export default class UploadImages extends Component {
  ...
  upload(idx, file) {
    let _progressInfos = [...this.state.progressInfos];
    UploadService.upload(file, (event) => {
      _progressInfos[idx].percentage = Math.round((100 * event.loaded) / event.total);
      this.setState({
        progressInfos: _progressInfos,
      });
    })
      .then(() => {
        this.setState((prev) => {
        let nextMessage = [...prev.message, `${file.name} 上传成功!`];
          return {
            message: nextMessage
          };
        });
        return UploadService.getFiles();
      })
      .then((files) => {
        this.setState({
          imageInfos: files.data,
        });
      })
      .catch(() => {
        _progressInfos[idx].percentage = 0;
        this.setState((prev) => {
          let nextMessage = [...prev.message, "Could not upload the image: " + file.name];
          return {
            progressInfos: _progressInfos,
            message: nextMessage
          };
        });
      });
  }
}

这里图片的上传进度根据 event.loaded 和计算 event.total,图片上传完毕后,我们调用 UploadService.getFiles(),来获取服务器上图片信息来展示,另外,我们要需要在 componentDidMount 钩子函数中调用,以便在初始状态时获取数据

export default class UploadImages extends Component {
  ...
  componentDidMount() {
    UploadService.getFiles().then((response) => {
      this.setState({
        imageInfos: response.data,
      });
    });
  }
}

这里我们将界面的上传的 UI 渲染代码添加上

 <div>
        <div className="row">
          <div className="col-8">
            <label className="btn btn-default p-0">
              <input type="file" accept="image/*" onChange={this.selectFiles} />
            </label>
          </div>

          <div className="col-4">
            <button
              className="btn btn-success btn-sm"
              disabled={!selectedFiles}
              onClick={this.uploadImages}
            >
              上传
            </button>
          </div>
        </div>

        {progressInfos &&
          progressInfos.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>
          ))}

        {previewImages && (
          <div>
            {previewImages.map((img, i) => {
              return <img className="preview" src={img} alt={"image-" + i}  key={i}/>;
            })}
          </div>
        )}

        {message.length > 0 && (
          <div className="alert alert-secondary mt-2" role="alert">
            <ul>
              {message.map((item, i) => {
                return <li key={i}>{item}</li>;
              })}
            </ul>
          </div>
        )}

        <div className="card mt-3">
          <div className="card-header">图片列表</div>
          <ul className="list-group list-group-flush">
            {imageInfos &&
              imageInfos.map((img, index) => (
                <li className="list-group-item " key={index}>
                  <img src={img.url} alt={img.name} height="80px" />
                  <span className="ml-10"><a href={img.url} target="_blank">{img.name}</a></span>
                </li>
              ))}
          </ul>
        </div>
      </div>

在上面的代码中我们使用了 Boostrap 的进度条, 具体可查看 Bootstrap 文档。

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

在 React 里将图片上传组件添加到 App 组件

打开 App.js 文件,将组件 UploadImages 引入并添加

import React from "react";
import "./App.css";
import "bootstrap/dist/css/bootstrap.min.css";

import UploadImages from "./components/UploadFiles.js"

function App() {
  return (
    <div className="container">
      <h4>卡拉云-低代码开发工具 1秒搭建图片上传组件 Demo</h4>
      <p>使用卡拉云无需懂任何前端技术,仅需要拖拽即可搭建属于您的后台管理系统</p>
      <div className="content">
        <UploadImages />
      </div>
    </div>
  );
}

export default App;

配置上传图片端口

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

PORT = 8081

运行 React 项目

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

react 前端界面

前端项目到这里,大部分已经完成了,现在我们搭建后端部分,连通前后端。

React 前端「图片上传」源码

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

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

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

✦ 后端部分 - 图片上传 Node.js + Express + Multer + MongoDB

后端部分我们使用 Nodejs + Express + Multer 来搭建图片上传的项目,配合前端 React + Axios 来共同实现图片上传的前后端项目。

后端项目目录结构

├── README.md
├── node_modules
├── package-lock.json
├── package.json
└── src
    ├── config
    │   └── db.js
    ├── controllers
    │   ├── home.js
    │   └── upload.js
    ├── middleware
    │   └── upload.js
    ├── routes
    │   └── index.js
    ├── server.js
    └── views
        └── index.html
  • config/db.js:包括 MongoDB 和 Multer 的配置(url、数据库、图像存储桶)。
  • routes/index.js:定义从视图调用的端点的路由,使用控制器来处理请求。
  • controllers: home.js返回views/index.html upload.js处理上传、存储、显示和下载图像
  • middleware/upload.js:初始化 Multer GridFs 存储引擎(包括 MongoDB)并定义中间件函数。
  • server.js:初始化路由,配置 CORS,入口文件

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

  • 文件上传接口
  • 文件列表获取接口
  • 使用 url 下载文件接口

我们可以使用 postman 工具先看下接口的情况,如下图

向后端服务器发 POST 请求图片上传

postman-post-update

获取文件列表接口

postman-get-update

打开数据库连接工具,可以看到,数据库里,已经有上传的文件了

mongodb-gui

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

创建项目 配置模块

我们先使用命令 mkdir 创建一个空文件夹,然后 cd 到文件夹里面

这个文件夹就是我们的项目文件夹

mkdir kalacloud-nodejs-express-upload-files
cd kalacloud-nodejs-express-upload-files

接着使用命令

npm init

初始化项目,接着安装项目需要的依赖包, 输入如下命令

npm install express cors multer multer-gridfs-storage mongodb

package.js 文件

{
  "name": "kalacloud-nodejs-express-upload-files",
  "version": "1.0.0",
  "description": "Node.js upload multiple files/images to MongoDB",
  "main": "src/server.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [
    "node",
    "upload",
    "multiple",
    "files",
    "images",
    "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 数据库

config/db.js

module.exports = {
  url: "mongodb://localhost:27017/",
  database: "files_db",
  imgBucket: "photos",
};

配置图片上传存储的中间件

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"];

    if (match.indexOf(file.mimetype) === -1) {
      const filename = file.originalname;
      return filename;
    }

    return {
      bucketName: dbConfig.imgBucket,
      filename: file.originalname
    };
  }
});
var uploadFiles = multer({ storage: storage }).array("file", 10);
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标签的名称

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

创建文件上传的控制器

controllers/upload.js

这个文件主要用于图片上传,我们创建一个名 upload 函数,并将这个函数导出去

  • 我们使用 文件上传中间件函数处理上传的文件
  • 使用 Multer 捕获相关错误

文件列表数据获取和下载

  • getListFiles函数主要是获取 photos.files,返回 url, name, id
  • download(): 接收文件 id 作为输入参数,从 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);
    console.log(req.files);

    if (req.files.length <= 0) {
      return res
        .status(400)
        .send({ message: "You must select at least 1 file." });
    }

    return res.status(200).send({
      message: "文件上传成功",
    });

  } catch (error) {
    console.log(error);

    if (error.code === "LIMIT_UNEXPECTED_FILE") {
      return res.status(400).send({
        message: "Too many files to upload.",
      });
    }
    return res.status(500).send({
      message: `Error when trying upload many files: ${error}`,
    });
  }
};

const getListFiles = async (req, res) => {
  try {
    await mongoClient.connect();

    const database = mongoClient.db(dbConfig.database);
    const images = database.collection(dbConfig.imgBucket + ".files");
    let fileInfos = [];

    if ((await images.estimatedDocumentCount()) === 0) {
        fileInfos = []
    }

    let cursor = images.find({})
    await cursor.forEach((doc) => {
      fileInfos.push({
        id: doc._id,
        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.imgBucket,
    });

    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: "Cannot download the Image!" });
    });

    downloadStream.on("end", () => {
      return res.end();
    });
  } catch (error) {
    return res.status(500).send({
      message: error.message,
    });
  }
};

module.exports = {
  uploadFiles,
  getListFiles,
  download,
};

定义路由 routes

routes 文件夹中,使用 Express Routerindex.js 中定义路由

const express = require("express");
const router = express.Router();
const homeController = require("../controllers/home");
const uploadController = require("../controllers/upload");
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控制器的功能。
  • 获取/files图像列表。
  • GET/files/:name下载带有文件名的图像。

扩展阅读:《最好的 6 款 React admin 后台管理系统模板和框架

创建 Express 服务器

server.js

const cors = require("cors");
const express = require("express");
const app = express();
const initRoutes = require("./routes");

var corsOptions = {
  origin: "http://localhost:8081"
};

app.use(cors(corsOptions));
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 项目正常运行

向后端服务器发 POST 请求图片上传

postman-post-update

获取文件列表接口

postman-get-update

数据库

mongodbgui

React + Node.js 图片上传前后端一起运行联调

我们先启动后端项目 node src/server.js, 接着再启动前端项目 npm start,

测试上传,获取等接口,一切正常。

到这里整个前后端「上传图片」功能示例就算完成了。

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

Node.js 后端「上传图片」源码

你可以在我的 github 上下载到完整的 Node.js 后端「图片上传」源码。

如果你还没搞懂,也不用着急,直接使用卡拉云,无需懂任何前后端技术,仅需简单的鼠标拖拽即可快速生成包括「图片上传」管理在内的任何后台管理工具。立即试用卡拉云

「上传图片」前后端搭建总结及卡拉云

本教程手把手教大家搭建 React 前端 + Node.js 后端 的「上传图片」管理工具,如果你一步步跟着走,一定已经把 Demo 跑起来了。但如果你会使用最新的低代码开发工具「卡拉云」,完全不需要这么繁琐,仅需 1 分钟,就能搭建起属于自己的「上传图片」管理工具。

卡拉云图片上传管理

立即开通卡拉云,从侧边工具栏直接拖拽组件到页面,生成上传组件和文件管理工具。1 分钟搞定「图片上传」管理工具。

再看个卡拉云的 Demo 案例,下面是用卡拉云搭建的数据库 CURD 后台管理系统,只需拖拽组件,即可在10分钟内完成搭建。

卡拉云 SQL admin 后台管理系统

可直接分享给同事一起使用:https://my.kalacloud.com/apps/8z9z3yf9fy/published

卡拉云是新一代低代码开发平台,与 React 这类框架相比,卡拉云无需配置开发环境,直接注册即可开始搭建。开发者无需处理任何前端问题,简单拖拽即可生成图表、表格、表单、富文本等功能组件,一键接入数据库及 API,快速完成企业内部工具搭建,还可以分享给团队成员共享使用,数周的开发时间,缩短至 1 小时。

全栈实战教程:

后端实战教程:


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK