Reactjs + Nodejs + Express + Mongodb搭建[图片上传/预览]项目(下)

1,999 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第3天,点击查看活动详情

Reactjs + Nodejs + Express + Mongodb 搭建[图片上传/预览]前后端项目

概述

图片上传功能,在日常的项目开发中是很常见的功能,今天我们就来说说使用 Reactjs + Nodejs + Express + Mongodb 搭建前后端图片上传的例子

我们先看看完成后的效果

iShot_2022-05-27_23.10.44.gif

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

后端部分

实现的功能:

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

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

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

后端部分我们使用 Nodejs + Express + Multer + Mongodb 来搭建图片上传的项目,配合前端 Reactjs + 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:包括 MongoDBMulter 的配置(url、数据库、图像存储桶)。
  • routes/index.js:定义从视图调用的端点的路由,使用控制器来处理请求。
  • controllers: home.js返回 views/index.html upload.js处理上传、存储、显示和下载图像
  • middleware/upload.js:初始化 Multer GridFs 存储引擎(包括 MongoDB)并定义中间件函数。
  • server.js:初始化路由,配置 CORS,入口文件

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

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

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

文件上传接口

截屏2022-05-27 下午11.28.40.png

获取文件列表接口

截屏2022-05-27 下午11.29.10.png

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

截屏2022-05-27 下午11.30.50.png

创建项目 配置模块

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

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

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

接着使用命令

npm init

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

npm install express cors multer multer-gridfs-storage mongodb

package.js 文件

{
  "name": "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"
  }
}

配置 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标签的名称

创建文件上传的控制器

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: "文件上传成功",
    });

    // console.log(req.file);

    // if (req.file == undefined) {
    //   return res.send({
    //     message: "You must select a file.",
    //   });
    // }

    // return res.send({
    //   message: "File has been uploaded.",
    // });
  } 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}`,
    });

    // return res.send({
    //   message: "Error when trying upload image: ${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 文件夹中,使用 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下载带有文件名的图像。

创建 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 项目正常运行

文件上传接口

截屏2022-05-27 下午11.28.40.png

获取文件列表接口

截屏2022-05-27 下午11.29.10.png

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

截屏2022-05-27 下午11.30.50.png

联调

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

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

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