在日常工作中,文件上传是一个很常见的功能。在项目开发过程中,我们通常都会使用一些成熟的上传组件来实现对应的功能。一般来说,成熟的上传组件不仅会提供漂亮 UI 或好的交互体验,而且还会提供多种不同的上传方式,以满足不同的场景需求。
一般在我们工作中,主要会涉及到 8 种文件上传的场景,每一种场景背后都使用不同的技术,其中也有很多细节需要我们额外注意。今天阿宝哥就来带大家总结一下这 8 种场景,让大家能更好地理解成熟上传组件所提供的功能。阅读本文后,你将会了解以下的内容:
-
单文件上传:利用
input
元素的accept
属性限制上传文件的类型、利用 JS 检测文件的类型及使用 Koa 实现单文件上传的功能; -
多文件上传:利用
input
元素的multiple
属性支持选择多文件及使用 Koa 实现多文件上传的功能; -
目录上传:利用
input
元素上的webkitdirectory
属性支持目录上传的功能及使用 Koa 实现目录上传并按文件目录结构存放的功能; -
压缩目录上传:在目录上传的基础上,利用 JSZip 实现压缩目录上传的功能;
-
拖拽上传:利用拖拽事件和 DataTransfer 对象实现拖拽上传的功能;
-
剪贴板上传:利用剪贴板事件和 Clipboard API 实现剪贴板上传的功能;
-
大文件分块上传:利用 Blob.slice、SparkMD5 和第三方库 async-pool 实现大文件并发上传的功能;
-
服务端上传:利用第三方库 form-data 实现服务端文件流式上传的功能。
一、单文件上传
对于单文件上传的场景来说,最常见的是图片上传的场景,所以我们就以图片上传为例,先来介绍单文件上传的基本流程。
1.1 前端代码
html
在以下代码中,我们通过 input
元素的 accept
属性限制了上传文件的类型。这里使用 image/*
限制只能选择图片文件,当然你也可以设置特定的类型,比如 image/png
或 image/png,image/jpeg
。
<input id="uploadFile" type="file" accept="image/*" />
<button id="submit" onclick="uploadFile()">上传文件</button>
复制代码
需要注意的是,虽然我们把 input 元素的 accept
属性设置为 image/png
。但如果用户把 jpg/jpeg
格式的图片后缀名改为 .png
,就可以成功绕过这个限制。要解决这个问题,我们可以通过读取文件中的二进制数据来识别正确的文件类型。
要查看图片对应的二进制数据,我们可以借助一些现成的编辑器,比如 Windows 平台下的 WinHex 或 macOS 平台下的 Synalyze It! Pro 十六进制编辑器。这里我们使用 Synalyze It! Pro 这个编辑器,来查看阿宝哥头像对应的二进制数据。
那么在前端能否不借助工具,读取文件的二进制数据呢?答案是可以的,这里阿宝哥就不展开介绍了。感兴趣的话,你可以阅读 JavaScript 如何检测文件的类型? 这篇文章。另外,需要注意的是 input
元素 accept
属性有存在兼容性问题。比如 IE 9 以下不支持,具体如下图所示:
(图片来源 —— caniuse.com/input-file-…
js
const uploadFileEle = document.querySelector("#uploadFile");
const request = axios.create({
baseURL: "http://localhost:3000/upload",
timeout: 60000,
});
async function uploadFile() {
if (!uploadFileEle.files.length) return;
const file = uploadFileEle.files[0]; // 获取单个文件
// 省略文件的校验过程,比如文件类型、大小校验
upload({
url: "/single",
file,
});
}
function upload({ url, file, fieldName = "file" }) {
let formData = new FormData();
formData.set(fieldName, file);
request.post(url, formData, {
// 监听上传进度
onUploadProgress: function (progressEvent) {
const percentCompleted = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
);
console.log(percentCompleted);
},
});
}
复制代码
在以上代码中,我们先把读取的 File 对象封装成 FormData 对象,然后利用 Axios 实例的 post
方法实现文件上传的功能。 在上传前,通过设置请求配置对象的 onUploadProgress
属性,就可以获取文件的上传进度。
1.2 服务端代码
Koa 是一个简单易用的 Web 框架,它的特点是优雅、简洁、轻量、自由度高。所以我们选择它来搭建文件服务,并使用以下中间件来实现相应的功能:
- koa-static:处理静态资源的中间件;
- @koa/cors:处理跨域请求的中间件;
- @koa/multer:处理
multipart/form-data
的中间件; - @koa/router:处理路由的中间件。
const path = require("path");
const Koa = require("koa");
const serve = require("koa-static");
const cors = require("@koa/cors");
const multer = require("@koa/multer");
const Router = require("@koa/router");
const app = new Koa();
const router = new Router();
const PORT = 3000;
// 上传后资源的URL地址
const RESOURCE_URL = `http://localhost:${PORT}`;
// 存储上传文件的目录
const UPLOAD_DIR = path.join(__dirname, "/public/upload");
const storage = multer.diskStorage({
destination: async function (req, file, cb) {
// 设置文件的存储目录
cb(null, UPLOAD_DIR);
},
filename: function (req, file, cb) {
// 设置文件名
cb(null, `${file.originalname}`);
},
});
const multerUpload = multer({ storage });
router.get("/", async (ctx) => {
ctx.body = "欢迎使用文件服务(by 阿宝哥)";
});
router.post(
"/upload/single",
async (ctx, next) => {
try {
await next();
ctx.body = {
code: 1,
msg: "文件上传成功",