如何优雅的判断文件上传的格式

354 阅读3分钟

前言

在日常工作中,文件上传是一个非常常见的功能。在项目开发中,我们通常都会使用一些成熟的上传组件来实现对应的功能。

一般来说,成熟的上传组件不仅会提供漂亮的UI或好的交互体验,但是对于一些文件小的上传方式是可以满足的,那么如果文件过大呢就有点满足不了。

那么接下来我们一起来看看,如何实现大文件的上传吧,由于内容比较多,可能会分几篇文章进行描述,今天会详细简述如何优雅判断文件的上传格式。

实现

  1. 我们先来实现简单的上传功能。

    创建html文件,使用input的file文件进行文件获取,使用button点击进行上传操作。

    <!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>文件上传</title>
    </head><body>
      <div id="warp" style="height: 100px; border: 4px dashed #eee; text-align: center; line-height: 100px;">
        <input type="file" id="file">
      </div>
      <button id="btn">上传</button>
    </body>
    

    创建js文件,获取指定元素加入方法。

    const fileEle = document.getElementById('file')
    const btnEle = document.getElementById('btn')
    let file = null // 文件流
    ​
    fileEle.addEventListener('change', function (e) {
      file = e.target.files[0]
    })
    ​
    async function uplodaFile(params) {
      if (await isImage(file)) {
        console.log('文件格式正确')
      } else {
        console.log('文件格式不对')
      }
    }
    ​
    btnEle.addEventListener('click', async function (e) {
      e.preventDefault()
      uplodaFile()
    })
    

    编写isImage函数使用文件转换二进制的方式来判断文件格式,这样可以很好的避免恶意文件。

    // 文件流转换二进制
    function blobToString(blob) {
      return new Promise(resolve => {
        // 读取文件流
        const reader = new FileReader()
        reader.onload = function () {
          // 文件截取并转换charCodeAt在进行16进制转换拿到文件的二进制
          const ret = reader.result.split('')
            .map(v => v.charCodeAt())
            .map(v => v.toString(16).toUpperCase())
            .join(' ')
          resolve(ret)
        }
        reader.readAsBinaryString(blob)
      })
    }
    ​
    // 判断文件是不是jpg的,分别取文件流二进制的前2个和后2个
    async function isJpg(file) {
      const len = file.size
      const start = await blobToString(file.slice(0, 2))
      const tail = await blobToString(file.slice(-2, len))
    ​
      const isJpg = (start == 'FF D8') && (tail == 'FF D9')
      return isJpg
    }
    ​
    // 判断文件是不是gif的,取文件二进制的前6个
    async function isGif(file) {
      const ret = await blobToString(file.slice(0, 6))
      return isGIf = (ret == '47 49 46 38 39 61') || (ret == '47 49 46 38 37 61')
    }
    ​
    // 判断文件是不是png的,取文件二进制的前8个
    async function isPng(file) {
      const ret = await blobToString(file.slice(0, 8))
      return isPng = ret == '89 50 4E 47 0D 0A 1A 0A'
    }
    ​
    // 判断文件
    function isImage(file) {
      return isJpg(file) || isGif(file) || isPng(file)
    }
    
  2. 为了实现更好的体验,我们在加入拖拽上传的功能。

    const warpEle = document.getElementById('warp')
    ​
    // 拖拽
    fileEle.addEventListener('drop', e => {
      e.preventDefault()
      file = e.dataTransfer.files[0]
      warpEle.style.borderColor = '#eee'
    })
    ​
    // 拖拽进入
    fileEle.addEventListener('dragover', e => {
      warpEle.style.borderColor = 'red'
      e.preventDefault()
    })
    ​
    // 拖拽离开
    fileEle.addEventListener('dragleave', e => {
      warpEle.style.borderColor = '#eee'
      e.preventDefault()
    })
    

结语

如果是pdf文件,普通的上传文件组件如果修改pdf后缀改为png或者jpg就可以上传成功,这种是比较危险,如果是恶意文件就会导致程序出现问题,转换二进制的方式不管怎么修改文件后缀文件流信息不变,就会上传不成功,这样就有效限制文件格式了。

为了用户体验我们还加入了文件拖拽上传的功能。这样的话,文件上传 + 拖拽文件上传 + 文件上传格式的校验我们就做完了。

目前的文件上传还是有一定的问题,比如文件名称如果一样怎么限制,文件过大断网怎么上传,所以下一篇我们讲解文件名称转换MD5值进行上传。

xdm,尽情期待吧。