天天看点

JavaScript 文件上传完整指南

JavaScript 文件上传完整指南

https://betterprogramming.pub/a-complete-guide-of-file-uploading-in-javascript-2c29c61336f5

翻译 | 杨小爱

文件上传是 Web 项目的常用功能。相信大家在开发过程中或多或少都遇到过相关的需求。

在今天的这篇文章中,我总结了一些场景和解决方案,希望能帮助你彻底掌握文件上传相关的问题。

我们的目标

首先,我们来明确一下文件上传的具体功能。

根据上传目标,有3种任务:

  • 上传单个文件
  • 同时上传多个文件
  • 上传整个文件夹

根据用户操作,有:

  • 选择要上传的文件
  • 将文件拖到框中然后上传
  • 从剪贴板上传

从性能的角度来看,我们可能需要:

  • 压缩文件后上传
  • 将大文件分成块然后上传

另外,有时我们可能不会在客户端浏览器上传文件,而是通过服务器上传到另一台服务器。

我们将依次讨论这些。

准备工作

在开始编程工作之前,我们还是需要了解一些背景知识。

  • 首先,在上传文件时,我们使用最流行的 HTTP 库 Axios。在实际开发中,我们一般不直接使用 XMLHttpRequest,使用 Axios 符合真实的开发模式。
  • 当我们在前端讨论上传文件的时候,要想全面了解相关原理,就必须了解相关的后端代码。这里我们使用 Koa 来实现我们的服务器。
  • 最后希望大家对formdata有一个简单的了解,我们使用这种数据格式来上传文件。

上传单个文件

传单个文件的需要太常见了。例如,当我们注册微信后,我们可能需要上传更改一个头像。

文件上传功能需要客户端和服务器的配合。在我们的项目中,用户在客户端选择一个文件,然后上传到服务器;服务器保存文件并返回它的 URL。

这是项目预览:

JavaScript 文件上传完整指南

上面的 Gif 展示了文件上传的完整过程:

  • 用户在浏览器中选择文件
  • 用户点击上传按钮
  • 上传的文件放在服务器的uploadFiles文件夹中
  • 然后服务器返回一个URL,就是上传文件的地址
  • 用户可以通过这个 URL 访问资源

编码

这个项目的所有代码都保存在 GitHub 上:

你可以将其克隆到你的计算机:

# clone it
$ git clone [email protected]:BytefishMedium/FileUploading.git
# and install npm package
$ cd FileUloading
$ npm install      

所有与单个文件上传相关的代码都放在 1-SingleFile 文件夹中。

  • client.html 与客户端代码相关。
  • server.js 与服务器端代码相关
JavaScript 文件上传完整指南

要运行服务器,你可以转到该文件夹并运行以下命令:

$ node server.js      

然后,我们可以在任何浏览器上打开 client.html。

具体操作我已经在上面的gif中展示过了。你可以先自己尝试一下,然后继续阅读。

客户端代码

嗯,把大象放进冰箱需要多少步骤?

只需三步:

  1. 打开冰箱门
  2. 把大象放进去
  3. 关上门。

上传文件也是如此,我们只需要三个步骤:

  1. 让用户选择要上传的文件
  2. 阅读此文件
  3. 使用axios上传文件
JavaScript 文件上传完整指南

在 HTML 中,我们可以使用 input 元素。只需将此元素的类型设置为文件,然后该元素即可用于选择文件。

<input id="fileInput" type="file"/>      
JavaScript 文件上传完整指南

用户选择文件后,该文件的元数据将存储在此输入元素的 files 属性中。

const uploadFileEle = document.getElementById("fileInput")
console.log(uploadFileEle.files[0]);      
JavaScript 文件上传完整指南

最后,我们使用 Axios 的 post 方法来上传文件。但是在上传文件之前,我们还需要把这个文件打包成FormData格式。

let file = fileElement.files[0];
let formData = new FormData();
formData.set('file', file);
axios.post("http://localhost:3001/upload-single-file", formData)
  .then(res => {
  console.log(res)
})      

提示:FormData 是一种键值类型的数据格式。这是一个例子:

JavaScript 文件上传完整指南

好了,这些就是文件上传相关的知识点了,更完整的代码如下:

<!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>Document</title>
  <script src="  https://unpkg.com/[email protected]/dist/axios.min.js"></script>
</head>


<body>
  <input type="file" id="fileInput">
  <button id="uploadButton">upload</button>
  <script>


    document.getElementById("uploadButton").onclick = () => {
      let fileElement = document.getElementById('fileInput')


      // check if user had selected a file
      if (fileElement.files.length === 0) {
        alert('please choose a file')
        return
      }


      let file = fileElement.files[0]


      let formData = new FormData();
      formData.set('file', file);


      axios.post("http://localhost:3001/upload-single-file", formData, {
        onUploadProgress: progressEvent => {
          const percentCompleted = Math.round(
            (progressEvent.loaded * 100) / progressEvent.total
          );
          console.log(`upload process: ${percentCompleted}%`);
        }
      })
        .then(res => {
          console.log(res.data)
          console.log(res.data.url)
        })
    }
</script>
</body>


</html>      

这段代码其实就是为了实现我们之前说的三个步骤:

JavaScript 文件上传完整指南

只是我们增加了两个额外的功能:

  • 一是上传按钮。当用户点击上传按钮时,我们开始执行上传逻辑。
  • 然后我们再做一个判断,确保用户真的选择了一个文件。

然后,当axios上传文件时,它可以让我们监控文件上传的进度。

我们知道 HTTP 是建立在 TCP 之上的。如果一个HTTP报文比较大,可能会分解成多个不同的TCP报文在网络中传输。

如果你需要写一个进度条来向用户展示上传的进度,你可以使用这个 API。

axios.post("http://localhost:3001/upload-single-file", formData, {
  onUploadProgress: (progressEvent) => {
    const percentCompleted = Math.round(
      (progressEvent.loaded * 100) / progressEvent.total
    );
    console.log(`upload process: ${percentCompleted}%`);
  },
});      

progressEvent.loaded 表示上传成功的字节数,progressEvent.total 表示文件的总字节数。

JavaScript 文件上传完整指南

好的,这是我们的客户端代码。

服务器端代码

要启动服务器,我们可以使用 Koa。这是一个使用 Koa 的小型服务器:

const path = require("path");
const Koa = require("koa");
const Router = require("@koa/router");


const app = new Koa();
const router = new Router();


const PORT = 3000;


router.get("/", async (ctx) => {
  ctx.body = "Hello friends!";
});


app.use(router.routes()).use(router.allowedMethods());


app.listen(PORT, () => {
  console.log(`app starting at port ${PORT}`);
});
view rawkoa.server.v1.js hosted with ❤ by GitHub      

这是最基本的 Koa 演示。由于本文重点介绍文件上传,所以我就不详细解释了。如果你不熟悉这个,你可以阅读官方文档。

  • Koa:https://github.com/koajs/koa
  • Koa-router:https://github.com/koajs/router

我们的客户端使用FormData的格式上传文件,那么,我们的服务器也需要解析FormData。而 Koa-multer 是一个帮助我们解析 FormData 数据的中间件:

const path = require("path");
const Koa = require("koa");
const Router = require("@koa/router");
const multer = require("@koa/multer");
const cors = require("@koa/cors");


const app = new Koa();
const router = new Router();


const PORT = 3000;


const upload = multer();


router.get("/", async (ctx) => {
  ctx.body = "Hello friends!";
});


// add a route for uploading single files
router.post("/upload-single-file", upload.single("file"), (ctx) => {
  console.log("ctx.request.file", ctx.request.file);
  ctx.body = `file ${ctx.request.file.filename} has saved on the server`;
});


app.use(cors());
app.use(router.routes()).use(router.allowedMethods());


app.listen(PORT, () => {
  console.log(`app starting at port ${PORT}`);
});      

关于 Koa-multer,你可以阅读他们的官方文档:

  • Koa-multer:https://github.com/koajs/multer
  • Multer:https://github.com/expressjs/multer

关键代码是uoload.single('file'),这行代码可以帮助我们解析FormData的数据,然后,把对应的信息放到ctx.request.file中。

JavaScript 文件上传完整指南

其实,此时我们的服务器已经可以接收到客户端上传的文件了,但是,接收到文件后并没有存储到磁盘中。

如果我们想让 Koa-multer 为我们将文件保存到磁盘,我们可以添加以下配置:

const storage = multer.diskStorage({
  destination: function (req, file, cb) {
    cb(null, UPLOAD_DIR);
  },
  filename: function (req, file, cb) {
    cb(null, `${file.originalname}`);
  },
});
const upload = multer({ storage: storage });      

完整的代码是 server.js,你可以直接在代码仓库中阅读。

JavaScript 文件上传完整指南

当前的流程图如下所示:

JavaScript 文件上传完整指南

无论如何,我们应该自己尝试一下。

上传多个文件

有了上面的基础,我们编写上传多个文件的代码就简单多了。

首先,我们需要修改 input 元素并为其添加 multiple 属性。

<input type="file" id="fileInput" multiple>      

这是告诉浏览器现在这个输入元素应该允许用户同时选择多个文件。

然后用户选择多个文件后,数据会放在fileElement.files中。我们在构造formdata的时候,需要遍历这个列表,把所有的文件都放到formdata中。

let files = Array.from(fileElement.files);
let formData = new FormData();
files.forEach((file) => {
  formData.append("file", file);
});      

那么上传文件的代码就不需要修改了。

这是完整的代码:

JavaScript 文件上传完整指南

该文件位于项目中的 2-MultipleFiles/client.html 上。

同时,我们还需要调整服务端的代码。

首先,我们需要添加对应的路由/upload-multiple-files

, 然后使用 upload.fields([{ name: “file” }]) 中间件来处理多个文件。之后request中的FormData数据会被放到ctx.files.file中。

JavaScript 文件上传完整指南

演示:

JavaScript 文件上传完整指南

上传目录

现在让我们看一下上传目录的步骤。

与之前类似,我们需要将 input 元素的属性设置为:

<input type="file" id="fileInput" webkitdirectory>      

那么在上传目录的时候,input元素的files对象会有webkitRlativePath属性,我们也将它们添加到formdata中。

JavaScript 文件上传完整指南

这里需要注意的是,当文件名中包含\时,koa-multer可能会报错。要解决此问题,我们需要将 \ 替换为 @ 符号。

formData.append('file', file, file.webkitRelativePath.replace(/\//g, "@"));      
JavaScript 文件上传完整指南

那么我们还需要修改对应的服务器代码:

JavaScript 文件上传完整指南

演示:

JavaScript 文件上传完整指南

总结

我们依次分析了上传单个文件、多个文件、目录的过程。其实很简单,只要3步:

  • 使用输入元素让用户选择文件
  • 读取文件并构造FormData
  • 使用 Axios 上传 FormData

所有代码都在 GitHub 上,大家可以自己试试。如果您有任何问题,可以发表评论。

由于文章篇幅关系,剩下的文件上传会在后面的文章中介绍,如果您对此内容感兴趣,感谢你的阅读。

JavaScript 文件上传完整指南

继续阅读