上传文件
让我们实现应用程序业务逻辑中最核心的功能。
我们的应用程序的业务逻辑主要依赖于文件上传,因为我们正在构建一个可以视为我们自己私有图书馆的应用程序。文件上传使我们能够尽可能地扩展应用程序的功能,利用这些功能来操作和组织我们的文件。
先决条件
我们将使用 Multer,这是一个广为人知的 npm 包,作为上传文件的中间件。为了配置文件上传到服务器,我们需要安装以下内容:
multer负责处理上传到服务器的文件。body-parser负责解析请求体。
让我们在 index.js 中配置 body-parser。
1// backend/index.js 2 3// ... 其余代码 4const bodyParser = require("body-parser"); 5 6// ... 其余代码 7 8app.use(bodyParser.urlencoded({ extended: true })); 9app.use(bodyParser.json()); 10 11// ... 其余代码
静态资源
我们不会将实际文件存储在数据库中,因为这会降低查询性能。相反,我们将文件存储在服务器磁盘或任何其他磁盘存储中,而数据库仅存储指向这些文件的引用或指针。
这个指针或引用信息可以是任何有助于唯一标识已存储文件的内容。文件名是必须保存的关键信息之一。之后,我们可以根据所开发的应用程序需求,添加其他相关信息。
以下是存储上传文件的方法:
- 创建一个用于存储上传文件的新目录,命名为
backend/uploads,如下所示:1mkdir uploads - 配置 Express 使用
uploads目录来存储静态数据,即我们这里的上传文件。
1// backend/index.js 2 3// ... 其余代码 4 5app.use('/uploads', express.static('./uploads')); 6 7// ... 其余代码
模型目录
现在我们知道如何存储上传的文件了,那么需要的信息又该存放在哪里?我们将使用数据库为每个文件存储一条文档记录。这样不仅便于检索存储的信息,还能保存关于文件内容的一些元数据。
- 我们在
models目录中创建一个新的模式文件file.js:1cd models 2touch file.js - 我们创建文件的模式:
1// backend/models/file.js 2 3const mongoose = require("mongoose"); 4const Joi = require("joi"); 5 6const fileSchema = new mongoose.Schema({ 7 createdAt: { 8 type: Date, 9 default: Date.now, 10 }, 11 createdBy: { 12 type: mongoose.Schema.Types.ObjectId, 13 ref: "user", 14 required: true, 15 }, 16 filePath: { 17 type: String, 18 required: true, 19 }, 20 name: { 21 type: String, 22 required: [true, "上传的文件必须有名称"], 23 }, 24 description: { 25 type: String, 26 required: [true, "上传的文件必须有描述"], 27 }, 28}); 29 30const File = mongoose.model("file", fileSchema); 31 32const validate = (file) => { 33 const schema = Joi.object({ 34 filePath: Joi.string().required(), 35 name: Joi.string().required(), 36 description: Joi.string().required(), 37 }); 38 return schema.validate(file); 39}; 40 41module.exports = { File, validate };
每个文件必须包含以下内容:
name和description标记为required,并带有错误提示信息。createdBy是指向用户对象的外键。filePath用于快速从磁盘位置获取文件。createdAt如果未指定时间,则自动设置为当前时间。
Multer 配置
下一步是将 Multer 配置到应用中。我们可以将 Multer 的配置视为一种中间件,它在将请求传递给处理器之前提取上传的文件。
- 我们创建一个文件
middleware/multer.js:1cd middleware 2touch multer.js - 我们可以这样实现 Multer 的配置:
1// backend/middleware/multer.js 2 3const multer = require("multer"); 4 5const storage = multer.diskStorage({ 6 destination: (req, file, cb) => { 7 cb(null, "uploads"); 8 }, 9 filename: (req, file, cb) => { 10 const ext = file.mimetype.split("/")[1]; 11 cb(null, `item-${file.fieldname}-${Date.now()}.${ext}`); 12 }, 13}); 14 15exports.upload = multer({ storage });
destination指定上传文件的基础存储目录。filename指定 Multer 如何命名上传的文件。mimetype用于确定文件扩展名,以安全地重命名文件。Date.now()确保文件名始终唯一。
文件控制器
- 我们创建文件的控制器:
1cd controllers 2touch file.js - 然后定义上传功能:
1// backend/controllers/file.js 2 3const { File, validate } = require("../models/file"); 4const { upload } = require("../middleware/multer"); 5 6const BASE_URL = "http://localhost:3000"; 7 8exports.upload = async (req, res) => { 9 try { 10 const { error } = validate(req.body); 11 if (error) return res.status(400).send(error.details[0].message); 12 13 const file = new File({ 14 name: req.body.name, 15 description: req.body.description, 16 createdBy: req.user._id, 17 filePath: `${BASE_URL}/uploads/${req.file.filename}`, 18 createdAt: Date.now(), 19 }); 20 21 await file.save(); 22 res.status(201).send({ message: "文件上传成功" }); 23 } catch (err) { 24 res.status(500).send("服务器内部错误"); 25 } 26};
BASE_URL定义为服务器上所有上传项链接的基础地址。name和description在验证后从请求体中提取。createdBy是上传文件的认证创建者。createdAt表示文件上传到服务器的时间。filePath包含该文件在服务器上的公开 URL,以便轻松访问。
路由上传功能
我们可以这样定义上传功能的路由:
1// backend/routes/index.js 2 3// ...其余导入 4 5const auth = require("../middleware/auth"); 6const { upload } = require("../middleware/multer"); 7 8// ...其余代码 9 10// 将 'upload' 请求映射到上传函数 11router.post("/upload", auth, upload.single("file"), fileController.upload); 12 13module.exports = router;
- 我们应用
auth中间件,确保当前用户已成功认证。 - 我们使用
upload中间件,让 Multer 处理上传的文件,并将其传递给控制器。 - 我们将请求传递给
fileController.upload文件,由其处理数据写入数据库的过程。
我们可以在下方终端中运行上述任意命令:
测试上传功能
现在,我们可以使用 Postman 测试上传功能。
No configuration found for ID: f606f797d4218b3e5369b8206cfa3cf2a412d9fd95a184a4d76acaa3640b3b6c
我们可以使用 Postman 测试上传功能,步骤如下:
[]
如果成功,我们将收到一条提示:“文件上传成功”。
注意: 我们在请求体中使用
form-data类型来上传文件。前端也将采用相同方式。