闽公网安备 35020302035485号
├── 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 前端部分├── README.md
├── package.json
├── pnpm-lock.yaml
└── node_modules
└── ...
└── src
├── config
│ └── db.js
├── controllers
│ └── flileUploadController.js
├── middleware
│ └── upload.js
├── routes
│ └── index.js
└── server.js
后端项目结构npx create-react-app kalacloud-react-multiple-files-upload项目创建完成后,cd 进入项目,安装项目运行需要的依赖包和 Axios 终端分别依次如下命令
pnpm install pnpm install axios执行完成我们启动项目 pnpm start
npm install bootstrapbootstrap 安装完成后,我们打开 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;
import axios from "axios";
export default axios.create({
baseURL: "http://localhost:8080",
headers: {
"Content-type": "application/json"
}
});
这里 baseURL 的地址是我们后端服务器的 REST API 地址,要根据个人实际情况进行修改。本教程后文,教你搭建上传文件的后端部分,请继续阅读。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,并定义一个对象,在对象中添加两个属性函数,作用如下: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 中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 函数的时候,已经将对应文件的索引传递进来了,所有我们根据对应的索引设置对应文件的上传进度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 的进度条:const UploadFiles = () => {
...
}
export default UploadFiles;
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;
上传文件配置端口PORT=8081运行 React 项目
GET /files/[filename] 下载指定文件
mkdir kalacloud-nodejs-mongodb-upload-files cd kalacloud-nodejs-mongodb-upload-files接着使用命令
npm init --yes初始化项目,接着安装项目需要的依赖包, 输入如下命令
npm install express cors multer multer-gridfs-storage mongodbpackage.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"
}
}
module.exports = {
url: "mongodb://localhost:27017/",
database: "files_db",
filesBucket: "photos",
};
配置文件上传存储的中间件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 的配置对象 GridFsStorage3. file: 这是控制数据库中文件存储的功能。该函数的返回值是一个具有以下属性的对象:filename, metadata, chunkSize, bucketName, contentType... 我们还检查文件是否为图像 file.mimetype。
4. bucketName 表示文件将存储在 photos.chunks 和 photos.files 集合中。
5. 接下来我们使用 multer 模块来初始化中间件 util.promisify() 并使导出的中间件对象可以与 async-await.3,返回响应
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,
};
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/: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:2. Cors提供 Express 中间件以启用具有各种选项的 CORS。 创建一个 Express 应用程序,然后使用方法添加cors中间件 在端口 8080 上侦听传入请求。
Running at localhost:8080使用 postman 工具测试,ok 项目正常运行



node src/server.js在 kalacloud-react-multiple-files-upload 文件夹根目录运行前端 React
pnpm start
