JavaScript 文件上传完整指南,附【图书中奖者名单】

6c49b1cad94c2efebf85ab96d2a58cb0.png

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

翻译 | 杨小爱

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

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

我们的目标

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

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

  • 上传单个文件

  • 同时上传多个文件

  • 上传整个文件夹

根据用户操作,有:

  • 选择要上传的文件

  • 将文件拖到框中然后上传

  • 从剪贴板上传

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

  • 压缩文件后上传

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

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

我们将依次讨论这些。

准备工作

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

  • 首先,在上传文件时,我们使用最流行的 HTTP 库 Axios。在实际开发中,我们一般不直接使用 XMLHttpRequest,使用 Axios 符合真实的开发模式。

  • 当我们在前端讨论上传文件的时候,要想全面了解相关原理,就必须了解相关的后端代码。这里我们使用 Koa 来实现我们的服务器。

  • 最后希望大家对formdata有一个简单的了解,我们使用这种数据格式来上传文件。

上传单个文件

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

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

这是项目预览:

0b1934a463a5e6963789a26b93508b78.gif上面的 Gif 展示了文件上传的完整过程:

  • 用户在浏览器中选择文件

  • 用户点击上传按钮

  • 上传的文件放在服务器的uploadFiles文件夹中

  • 然后服务器返回一个URL,就是上传文件的地址

  • 用户可以通过这个 URL 访问资源

编码

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

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

# clone it
$ git clone git@github.com:BytefishMedium/FileUploading.git
# and install npm package
$ cd FileUloading
$ npm install

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

  • client.html 与客户端代码相关。

  • server.js 与服务器端代码相关

98471f788c5ffeb95cfc9c4f901c0f18.png

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

$ node server.js

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

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

客户端代码

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

只需三步:

  1. 打开冰箱门

  2. 把大象放进去

  3. 关上门。

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

  1. 让用户选择要上传的文件

  2. 阅读此文件

  3. 使用axios上传文件

dfa4f7d11dbf5af51b48311544c43fb3.png

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

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

6fb58173e41e94155bdc743c3bf9b5cf.png

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

const uploadFileEle = document.getElementById("fileInput")
console.log(uploadFileEle.files[0]);

a705c0453a7baf376f56e1a74ff53425.png

最后,我们使用 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 是一种键值类型的数据格式。这是一个例子:

48619856c2c37080e630cde9e97a4604.png

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

<!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/axios@0.24.0/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>

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

f058c7398b861717aa8e7b156b219c02.png

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

  • 一是上传按钮。当用户点击上传按钮时,我们开始执行上传逻辑。

  • 然后我们再做一个判断,确保用户真的选择了一个文件。

然后,当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 表示文件的总字节数。

d99fb0e04a5c16e3060676cd2d5c4323.gif

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

服务器端代码

要启动服务器,我们可以使用 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中。

6e4d077ef35aaeeb684377a21166f9a1.png

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

如果我们想让 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,你可以直接在代码仓库中阅读。

84d68e7d5842fd384fd580732d34bb77.png

当前的流程图如下所示:

9da3987433a08e186f7791bf5feef93c.png

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

上传多个文件

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

首先,我们需要修改 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);
});

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

这是完整的代码:

856c167bda5910635a119a00a11e3622.png

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

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

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

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

b035916548c978ebf8d4a4f4ff9a467b.png

演示:

d93e290a56fa98184c2f6243697ab6e4.gif

上传目录

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

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

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

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

15ae10b64bda34675d357b632c4e8f00.png

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

formData.append('file', file, file.webkitRelativePath.replace(/\//g, "@"));

b793b1b53c63d1b68907e1501005f8a1.png

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

9e7381450f35ee79483f28db5858e122.png

演示:

fc515c20d97e784436b6433bc60f9d72.gif

总结

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

  • 使用输入元素让用户选择文件

  • 读取文件并构造FormData

  • 使用 Axios 上传 FormData

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

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

PS:

免费送书活动的中奖名单如下:

@Echo、@Mint、@知己、@璐、@Mr.simple、@咩咩咩 。

另外,因为合作出版社根据我们这边活动的阅读情况,又额外增加了2个名额,所以,还是一样按照规则顺序下来,这两位惊喜幸运者是,

@🧸ྀི。 、@Zxixii_chan 

因此,一共有8位中奖的小伙伴,请以上8位小伙伴,添加微信号【web_xiaoer】,备注:图书中奖,通过后,请及时提供一下收货信息,以便经尽快寄出图书奖品。

最后,真的非常感谢各位小伙伴的积极参与,没有中奖的小伙伴也不用灰心,后面我们依然会有相应的送书活动。

学习更多技能

请点击下方公众号

87308a4556794ce4b8d1dd3ea3272889.gif

52b0e6a68e9fcfcb8d01ffe3c4d46180.png

2b5f0bf5516032296249000c97443574.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值