1. 文件上传的发展史
1.1 form表单同步上传的方式
在ajax请求出来之前,人们都是直接通过form表单提交文件,或者form配合iframe(已废弃)进行文件上传,form配合input进行文件选择。
那么为了了解清楚他们的工作原理,我们有必要来理解一下form
以及input
元素。
form元素的介绍
<form>
标签用于为用户输入创建 HTML 表单。
表单能够包含 input 元素,比如文本字段、复选框、单选框、提交按钮等等。
表单还可以包含 menus、textarea、fieldset、legend 和 label 元素。
表单用于向服务器传输数据。
form的属性
属性 | 描述 |
accept | HTML 5 中不支持。 |
accept-charset | 规定服务器可处理的表单数据字符集。 |
action | 规定当提交表单时向何处发送表单数据。 |
autocomplete | 规定是否启用表单的自动完成功能。 |
enctype | 规定在发送表单数据之前如何对其进行编码。 |
method | 规定用于发送 form-data 的 HTTP 方法。 |
name | 规定表单的名称。 |
novalidate | 如果使用该属性,则提交表单时不进行验证。 |
rel | 规定链接资源和当前文档之间的关系。 |
target | 规定在何处打开 action URL。 |
说明
enctype 属性可能的值:
- application/x-www-form-urlencoded
- multipart/form-data
- text/plain
input
元素的介绍
使用 type="file"
的input
元素使得用户可以选择一个或多个元素以提交表单的方式上传到服务器上,或者通过 Javascript 的 File API 对文件进行操作。
input 的属性:
alt | 定义图像输入的替代文本。 (只针对type=“image”) |
autocomplete | autocomplete 属性规定 元素输入字段是否应该启用自动完成功能。 |
autofocus | 属性规定当页面加载时 元素应该自动获得焦点。 |
form | form 属性规定 元素所属的一个或多个表单。 |
formaction | 属性规定当表单提交时处理输入控件的文件的 URL。(只针对 type=“submit” 和 type=“image”) |
formenctype | 性规定当表单数据提交到服务器时如何编码(只适合 type=“submit” 和 type=“image”)。 |
formmethod | 定义发送表单数据到 action URL 的 HTTP 方法。 (只适合 type=“submit” 和 type=“image”) |
formnovalidate | formnovalidate 属性覆盖 元素的 novalidate 属性。 |
formtarget | 规定表示提交表单后在哪里显示接收到响应的名称或关键词。(只适合 type=“submit” 和 type=“image”) |
height | 规定 元素的高度。(只针对type=“image”) |
multiple | 属性规定允许用户输入到 元素的多个值。 |
name | name 属性规定 元素的名称。 |
pattern | pattern 属性规定用于验证 元素的值的正则表达式。 |
placeholder | placeholder 属性规定可描述输入 字段预期值的简短的提示信息 。 |
1. accept
accept
属性是一个字符串,它定义了文件 input 应该接受的文件类型。这个字符串是一个以逗号为分隔的唯一文件类型说明符列表。由于给定的文件类型可以用多种方式指定,因此当你需要给定格式的文件时,提供一组完整的类型指定符是非常有用的。
- 简单的来说,这个属性就是用来限制用户选择的文件类型的
- 来看一下例子吧
<!-- 比如我们要选择的是一个`word`文件,就可以这么使用 --> <input type='file' accept='.doc, .docx' /> <!-- 一个文件选择器需要能被表示成一张图片的内容,包括标准的图片格式和 PDF 文件 --> <input type="file" accept="image/*,.pdf">
2.file
属性
FileList
对象每个已选择的文件。如果 multiple
属性没有指定,则这个列表只有一个成员。
3. multiple
当指定布尔类型属性, 文件 input 允许用户选择多个文件。
<input type="file" multiple name="uploads" id="uploads"> <!-- 表示可以选择多个文件-->
4. name
指定上传的文件名。
1.2 文件的异步上传
借助ajax + formData
实现
2. 开始实现文件上传
talk is cheap, show me the code
2.1直接使用form表单同步上传
- 前端
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Form 文件上传 </title> </head> <body> <form action='http://127.0.0.1:3000/formData/uploadFile' method='POST' enctype='multipart/form-data' > <input type='file' name='file' /> <br /> <input type='submit' value='上传文件' /> </form> </body> </html>
- 后端
'use strict'; const koa = require('koa') const router = require('koa-router')(); const path = require('path') const cors = require('koa2-cors') const multer = require('koa-multer') const app = new koa() app.use(cors()) const storage = multer.diskStorage({ // 存储的位置 destination: path.join(process.cwd(), "upload"), // 文件名 filename(req, file, cb) { const filename = file.originalname.split(".") // 利用时间戳来命名接收到的文件 cb(null, `${Date.now()}.${filename[filename.length - 1]}`) } }) const upload = multer({ storage }); // const upload = multer({ dest: 'uploads/' }) // 使用简版的,只能保存文件,却不能保持文件格式,或者说接收到文件的保存命名自定义 router.post('/formData/uploadFile', upload.single("file"), async ctx => { console.log(ctx.req.file) // req是node原生的request对象 ctx.status = 201 ctx.body = { file: ctx.req.file, request: ctx.request } }) app.use(router.routes()).use(router.allowedMethods()) app.listen(3000, () => { console.log('server is running at:\r\nhttp://localhost:3000') })
这里使用的是koa, koa-multer
实现文件的上传,注意koa-multer
其实就是封装了express
版本的multer
,这也可以看出来koa2
的很多中间间都是直接封装express
的,在使用的时候,可能要注意ctx.req
跟ctx.request
的区别。
此外,该方式仅适用于较小文件的上传,大文件一般使用断点上传,避免传输中断,重新传输,浪费资源。
Multer 是一个 node.js 中间件,用于处理
multipart/form-data
类型的表单数据,它主要用于上传文件。它是写在 busboy 之上非常高效。
注意: Multer 不会处理任何非 multipart/form-data 类型的表单数据。
multer/README-zh-cn.md at master · expressjs/multer (github.com)
比如说,这个时候,我们给后端发一个js创建的FormData数据时,上面写的node服务就会报错
- 这个时候就要使用到下面的方法了。
2.2 通过FormData ajax
方式
- 前端
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <script src="https://cdn.bootcdn.net/ajax/libs/axios/0.21.1/axios.js"></script> <title>formdata文件上传</title> </head> <body> <form> <input type="file" id="uploadFile" /> <br /> <button onclick="upload(event)">upload</button> </form> <script> const uploadFileEle = document.querySelector("#uploadFile"); const $http = axios.create({ baseURL: "http://127.0.0.1:3000/ajax/", timeout: 60000, }); async function upload(event) { event.preventDefault(); if (!uploadFileEle.files.length) return; const file = uploadFileEle.files[0]; // 获取单个文件 console.log(file); let formData = new FormData() // 注意这里的key要跟后端的一致 formData.set('file', file) console.log(formData.entries()) $http.post('/uploadFile', formData).then(r=>{ console.log(r) }).catch(e=>{ console.error(e) }) } </script> </body> </html>
上传表单的样子
查看源
- 后端
'use strict'; const koa = require('koa') const koaBody = require('koa-body') const router = require('koa-router')(); const fs = require('fs') const path = require('path') const cors = require('koa2-cors') const app = new koa() app.use(koaBody({ multipart: true, // 支持文件格式 formidable: { maxFileSize: 200*1024*1024 // 设置上传文件大小最大限制,默认2M } })) app.use(cors()) router.get('/', async(ctx)=>{ ctx.body = 'hello' }) // 上传一个文件 router.post('/ajax/uploadFile', async(ctx)=>{ // 上传单个文件 // console.log(ctx.request) const { files } = ctx.request; const { file } = files // 获取上传文件 // 这里的file是前端formData文件的key // 读取文件的二进制数据 const fileData = fs.readFileSync(file.path); const prefix = path.join(__dirname, `/upload/`) const filePath = prefix + file.name // 创建目录,不然会报错呜呜 !fs.existsSync(prefix) && fs.mkdirSync(prefix) // 写文件 const writer = fs.createWriteStream(filePath, { flags:'w' }) writer.write(fileData); ctx.status = 201 ctx.body = { file: fileData } }) app.use(router.routes()).use(router.allowedMethods()) app.listen(3000,()=>{ console.log('server is running at:\r\n http://localhost:3000') })
- 后端使用的是
koa2, koa-body
2.3 大文件的断点上传
- 再说吧,休息一下
附:
唯一文件类型说明符
唯一文件类型说明符是一个字符串,表示在 file
类型的input
元素中用户可以选择的文件类型。每个唯一文件类型说明符可以采用下列形式之一:
- 一个以英文句号(“.”)开头的合法的不区分大小写的文件名扩展名。例如:
.jpg
,.pdf
或 .doc
。- 一个不带扩展名的 MIME 类型字符串。
- 字符串
audio/*
, 表示“任何音频文件”。 - 字符串
video/*
,表示 “任何视频文件”。 - 字符串
image/*
,表示 “任何图片文件”。
参考
- 漫谈前端进化史之从Form表单到文件上传 | 一个人的孤落时辰 (qinyuanpei.github.io)
- HTML 标签 (w3school.com.cn)
- - HTML(超文本标记语言) | MDN (mozilla.org)
- HTML input 标签 | 菜鸟教程 (runoob.com)