溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

怎么使用Reactjs+Nodejs+Mongodb實現(xiàn)文件上傳功能

發(fā)布時間:2022-09-23 14:41:50 來源:億速云 閱讀:147 作者:iii 欄目:開發(fā)技術

本文小編為大家詳細介紹“怎么使用Reactjs+Nodejs+Mongodb實現(xiàn)文件上傳功能”,內容詳細,步驟清晰,細節(jié)處理妥當,希望這篇“怎么使用Reactjs+Nodejs+Mongodb實現(xiàn)文件上傳功能”文章能幫助大家解決疑惑,下面跟著小編的思路慢慢深入,一起來學習新知識吧。

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: 這個文件中的函數(shù)用于文件上傳和獲取數(shù)據庫中文件數(shù)據

后端項目結構

├── 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、數(shù)據庫、文件存儲桶)。

  • middleware/upload.js:初始化 Multer GridFs 存儲引擎(包括 MongoDB)并定義中間件函數(shù)。

  • controllers/flileUploadController.js:配置 Rest API

  • routes/index.js:路由,定義前端請求后端如何執(zhí)行

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

前端部分-上傳文件 React + Axios

配置 React 環(huán)境

這里我們使用 pnpm vite 創(chuàng)建一個 React 項目

npx create-react-app react-multiple-files-upload

項目創(chuàng)建完成后,cd 進入項目,安裝項目運行需要的依賴包和 Axios 終端分別依次如下命令

pnpm install
pnpm install axios

執(zhí)行完成我們啟動項目 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 地址,要根據個人實際情況進行修改。本教程后文,教你搭建上傳文件的后端部分,請繼續(xù)閱讀。

創(chuàng)建上傳文件組件

src/services/UploadFilesService.js,這個文件主要的作用就是和后端項目通訊,以進行文件的上傳和文件列表數(shù)據的獲取等

在文件中我們加入如下內容

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,并定義一個對象,在對象中添加兩個屬性函數(shù),作用如下

  • upload:函數(shù)以 POST 的方式將數(shù)據提交到后端,接收兩個參數(shù) fileonUploadProgress

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

    • onUploadProgress 文件上傳進度條事件,監(jiān)測進度條信息

  • getFiles: 函數(shù)用于獲取存儲在 Mongodb 數(shù)據庫中的數(shù)據

最后將這個對象導出去

創(chuàng)建 React 文件上傳組件

接下來我們來創(chuàng)建文件上傳組件,首先組件要滿足功能有文件上傳,上傳進度條信息展示,文件預覽,提示信息,文件下載等功能

這里我們使用 React Hooks 和 useState 來創(chuàng)建文件上傳組件,創(chuàng)建文件 src/components/UploadFiles,添加如下代碼

import React, { useState, useEffect, useRef } from "react";
import UploadService from "../services/UploadFilesService";
const UploadFiles = () => {
  return (
    
  );
};
export default UploadFiles;

然后我們使用 React Hooks 定義狀態(tài)

const UploadFiles = () => {
    const [selectedFiles, setSelectedFiles] = useState(undefined);
    const [progressInfos, setProgressInfos] = useState({ val: [] });
    const [message, setMessage] = useState([]);
    const [fileInfos, setFileInfos] = useState([]);
    const progressInfosRef = useRef(null)
}

狀態(tài)定義好后,我們在添加一個獲取文件的方法 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 方法將可迭代數(shù)據轉換數(shù)組形式的數(shù)據,接著使用 map 方法將文件的進度信息,名稱信息存儲到 _progressInfos

接著我們使用 map 方法調用 files 數(shù)組中的每一項,使 files 中的每一項都經過 upload 函數(shù)的處理,在 upload 函數(shù)中我們會返回上傳文件請求函數(shù) UploadService.uploadPromise 狀態(tài)

所以 uploadPromises 中存儲的就是處于 Promise 狀態(tài)的上傳文件函數(shù),接著我們使用 Promise.all 同時發(fā)送多個文件上傳請求,在所有文件都上傳成功后,我們將會調用獲取所有文件數(shù)據的接口,并將獲取到的數(shù)據展示出來。

upload 函數(shù)代碼如下

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 函數(shù)的時候,已經將對應文件的索引傳遞進來了,所有我們根據對應的索引設置對應文件的上傳進度

除了這些工作,我們還需要在 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 數(shù)組,并且將文件的 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">
      <h5> 使用 React 搭建文件上傳 Demo</h5>
      <div className="content">
        <UploadFiles />
      </div>
    </div>
  );
}

export default App;

上傳文件配置端口

考慮到大多數(shù)的 HTTP Server 服務器使用 CORS 配置,我們這里在根目錄下新建一個 .env 的文件,添加如下內容

PORT=8081

項目運行

到這里我們可以運行下前端項目了,使用命令 pnpm start,瀏覽器地址欄輸入 http://localhost:8081/, ok 項目正常運行

文件選擇器、上傳按鈕、文件列表都已經可以顯示出來了,但還無法上傳。這是因為后端部分還沒有跑起來,接下來,我?guī)ьI大家手把手搭建上傳文件的后端部分。

后端部分

后端部分我們使用 Nodejs + Express + Multer + Mongodb 來搭建文件上傳的項目,配合前端 Reactjs + Axios 來共同實現(xiàn)文件上傳功能。

后端項目我們提供以下幾個API

  • POST /upload 文件上傳接口

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

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

配置 Node.js 開發(fā)環(huán)境

我們先使用命令 mkdir 創(chuàng)建一個空文件夾,然后 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 數(shù)據庫

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 數(shù)據庫的標準 MongoDB 連接字符串。multer-gridfs-storage 模塊將自動為您創(chuàng)建一個 mongodb 連接。

  • options: 自定義如何建立連接

  • file: 這是控制數(shù)據庫中文件存儲的功能。該函數(shù)的返回值是一個具有以下屬性的對象:filename, metadata, chunkSize, bucketName, contentType... 我們還檢查文件是否為圖像 file.mimetype。bucketName 表示文件將存儲在 photos.chunksphotos.files 集合中。

  • 接下來我們使用 multer 模塊來初始化中間件 util.promisify() 并使導出的中間件對象可以與 async-await.

  • single() 帶參數(shù)的函數(shù)是 input 標簽的名稱

  • 這里使用 Multer API 來限制上傳文件大小,添加 limits: { fileSize: maxSize } 以限制文件大小

創(chuàng)建文件上傳的控制器

controllers/flileUploadController.js

這個主要用于文件上傳,我們創(chuàng)建一個名 upload 函數(shù),并將這個函數(shù)導出去

  • 我們使用 文件上傳中間件函數(shù)處理上傳的文件

  • 使用 Multer 捕獲相關錯誤

  • 返回響應

文件列表數(shù)據獲取和下載

  • getListFiles: 函數(shù)主要是獲取 photos.files,返回 url, name

  • download(): 接收文件 name 作為輸入參數(shù),從 mongodb 內置打開下載流 GridFSBucket,然后 response.write(chunk) API 將文件傳輸?shù)娇蛻舳恕?/p>

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 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 下載帶有文件名的圖像。

創(chuàng)建 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。

創(chuàng)建一個 Express 應用程序,然后使用方法添加cors中間件 在端口 8080 上偵聽傳入請求。

運行項目并測試

在項目根目錄下在終端中輸入命令 node src/server.js, 控制臺顯示

Running at localhost:8080

使用 postman 工具測試,ok 項目正常運行

文件上傳接口

怎么使用Reactjs+Nodejs+Mongodb實現(xiàn)文件上傳功能

怎么使用Reactjs+Nodejs+Mongodb實現(xiàn)文件上傳功能

文件列表接口

怎么使用Reactjs+Nodejs+Mongodb實現(xiàn)文件上傳功能

數(shù)據庫

怎么使用Reactjs+Nodejs+Mongodb實現(xiàn)文件上傳功能

React + Node.js 上傳文件前后端一起運行

在 nodejs-mongodb-upload-files 文件夾根目錄運行后端 Nodejs

node src/server.js

在 react-multiple-files-upload 文件夾根目錄運行前端 React

pnpm start

然后打開瀏覽器輸入前端訪問網址:

怎么使用Reactjs+Nodejs+Mongodb實現(xiàn)文件上傳功能

讀到這里,這篇“怎么使用Reactjs+Nodejs+Mongodb實現(xiàn)文件上傳功能”文章已經介紹完畢,想要掌握這篇文章的知識點還需要大家自己動手實踐使用過才能領會,如果想了解更多相關內容的文章,歡迎關注億速云行業(yè)資訊頻道。

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI