前端上传文件有两种方式
- 方式一:使用
FormData
发送 ajax 上传
javascript
// 创建FormData对象
var formData = new FormData();
// 添加文件数据
var fileInput = document.querySelector('input[type="file"]');
formData.append('file', fileInput.files[0]);
// 创建XMLHttpRequest对象
var xhr = new XMLHttpRequest();
// 设置请求的配置信息
xhr.open('POST', 'api/upload', true);
xhr.send(formData);
// 监听请求的完成事件
xhr.onload = function() {
if (xhr.status === 200) {
console.log('上传成功!');
} else {
console.error('上传失败!');
}
};
- 方式二:使用 Form 表单上传(需要指定
enctype="multipart/form-data
")
html
<form action="/api/upload" method="POST" enctype="multipart/form-data">
<p>
<input type="text" name="a" />
</p>
<p>
<input type="file" name="img" />
</p>
<p>
<button>提交</button>
</p>
</form>
文件上传的两种方式,他们有什么关系?
FormData
构造函数,用于创建FormData
实例。- 允许通过
append()
方法向FormData
对象添加表单字段和文件数据。 - 自动将表单字段和文件数据封装成multipart/form-data 格式的请求体,以便通过XMLHttpRequest 或Fetch API发送。
- 在发送请求时,会自动将请求的Content-Type 头部设置为multipart/form-data。
multipart/form-data 是什么?
Multipart/form-data是一种在HTTP传输协议中用于在Web表单中传输文件的编码方式。它是一种灵活的编码方式,能够同时处理表单中的多个字段和文件上传。
在multipart/form-data 编码中,表单中的每个数据字段都被封装成一个消息体,并以一定的分隔符分隔消息体。这个编码方式使用了类似于multipart/mixed的格式,但主要用于表单数据的传输。每个消息体包含一个字段名称和字段值,以及一个可选的文件上传字段。
当使用multipart/form-data编码方式时,表单数据被封装成一条消息,每个控件对应消息的一部分。这种编码方式支持在提交表单时上传文件,因此被广泛应用于文件上传功能的实现中。
总结来说,multipart/form-data的编码方式是一种将表单中的每个数据字段封装成一个消息体的编码方式,并以一定的分隔符分隔消息体。这种编码方式支持在提交表单时上传文件,并能够处理多个字段和文件上传。
multipart/form-data 实现原理
在form-data编码中,表单数据被封装成一条消息,以标签为单元,用分隔符分开。这意味着表单数据被分割成多个部分,每个部分都以标签开头,以分隔符结尾。这样可以同时上传多个键值对或文件。
当上传文件时,除了普通的键值对数据外,每个文件都会被封装为一个独立的消息部分。每个文件部分都包含一个Content-Type 头部,用于指定文件的媒体类型(即文件类型),以及一个Content-Disposition头部,用于描述该文件部分的名称和是否应该被显示或保存。
由于有分隔符隔离每个部分,所以form-data既可以上传键值对,也可以上传文件。同时,由于它采用了键值对的方式,所以可以上传多个文件或键值对。
POST / HTTP/1.1
Content-Length: 551
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="a"
1
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="img"; filename="截屏2024-01-17 10.13.02.png"
Content-Type: <Content-Type header here>
(data)
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="b"
2
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="file"; filename="截屏2024-01-17 16.40.30.png"
Content-Type: <Content-Type header here>
(data)
------WebKitFormBoundary7MA4YWxkTrZu0gW--
上传文件为什么只能是 form-data 类型的请求体?
HTTP请求体主要有以下几种格式:
- multipart/form-data:主要用于表单数据的提交,特别是文件上传。它会把表单数据封装成一条消息,以标签为单位,用分隔符分开。既可以上传键值对,也可以上传文件。
- application/x-www-form-urlencoded:会将表单内的数据转换为键值对,用&分隔。当method为get时,会将表单数据编码为(name1=value1&name2=value2...),然后把这个字符串append到url后面,用?分隔。这个格式不能提交文件,区别于multipart/form-data。
- raw:可以上传任意格式的文本,包括text、json、xml、html等。
- binary:相当于Content-Type:application/octet-stream,只可以上传二进制数据,用来上传文件。由于没有键值,一次只能上传一个文件。
由此可以看出 application/x-www-form-urlencoded 和 raw ,只支持字符串,不支持二进制文件。binary 和 multipart/form-data 都支持二进制文件,但 multipart/form-data可以以键值对的形式进行文件上传,试用起来更灵活。
node 服务如何获取到客户端上传的文件
我们可以借用 Express 官方维护的 multer 中间件
javascript
const express = require("express");
const multer = require("multer");
const path = require("path");
const app = express();
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, path.resolve(__dirname, "./public/upload"));
},
filename: function (req, file, cb) {
// 时间戳-6位随机字符.文件后缀
const timeStamp = Date.now();
const ramdomStr = Math.random().toString(36).slice(-6);
const ext = path.extname(file.originalname);
const filename = `${timeStamp}-${ramdomStr}${ext}`;
cb(null, filename);
},
});
const upload = multer({
storage,
limits: {
fileSize: 150 * 1024,
},
fileFilter(req, file, cb) {
//验证文件后缀名
const extname = path.extname(file.originalname);
const whitelist = [".jpg", ".gif", "png"];
if (whitelist.includes(extname)) {
cb(null, true);
} else {
cb(new Error(`your ext name of ${extname} is not support`));
}
},
});
app.post("/api/upload", upload.single("img"), (req, res) => {
const url = `/upload/${req.file.filename}`;
res.send({
code: 0,
msg: "",
data: url,
});
});
const port = 5008;
app.listen(port, () => {
console.log(`server listen on ${port}`);
});