Antd Upload 上传组件校验文件真实类型

7,811 阅读2分钟

前言

本文篇幅较短,用时约为5分钟,偏向实用性,不讲分析。

背景

有个小项目中有一个上传功能,因为小,所以本次后端是没有做任何校验的。校验全交给前端,测试有个case是:改了后缀名的的文件是否能绕过上传。本着对Antd的信任,我对QA拍胸口表示可以。结果后面发现其实不行,为了不被diss,想方设法完成了这个功能。

原因

修改文件类型后,浏览器会将其对应的file.type也会随着更改。如下代码中的file.type ,完全可以改为根据文件最后的后缀名进行判断。

// 官方demo
function beforeUpload(file) {
  const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png';
  if (!isJpgOrPng) {
    message.error('You can only upload JPG/PNG file!');
  }
  const isLt2M = file.size / 1024 / 1024 < 2;
  if (!isLt2M) {
    message.error('Image must smaller than 2MB!');
  }
  return isJpgOrPng && isLt2M;
}

解决方法

核心实现是通过二进制读取文件,校验真实文件内容的前4位的16进制。本代码中允许上传的文件是xlsx类型,其余文件二进制头可以自行查找,比如二进制头字节对应的文件类型

    getFileMimeType: (file) => {
        const reader = new FileReader();
        reader.readAsArrayBuffer(file);
        return new Promise((resolve, reject) => {
            reader.onload = (event) => {
                try {
                    let buffer = [...Buffer.from(event.target.result)];
                    // 仅要文件的前四位就够了
                    buffer = buffer.splice(0, 4);
                    buffer.forEach((num, i, arr) => {
                        arr[i] = num.toString(16).padStart(2, '0');
                    });
                    // 504b0304 是 xlsx 的文件头
                    resolve(buffer.join('') === '504b0304');
                } catch (e) {
                    // 读取文件头出错 默认不是合法文件类型
                    reject();
                }
            };
        });
    }

接下来是Antd upload组件的beforeUpload,稍微修改下官方demo即可

    beforeUpload: (file) => {
        return new Promise(async (resolve, reject) => {
            const isExcel = await getFileMimeType(file); // 调用上面代码
            if (!isExcel) {
                message.error('上传失败!仅支持文件类型为xlsx的文件');
                reject();
            }
            const isLt10M = file.size / 1024 / 1024 < 10;
            if (!isLt10M) {
                message.error('上传文件不能超过 10MB!');
                reject();
            }
            resolve();
        });
    }

注意点

  • readAsArrayBuffer是异步的,因此个人转换成了Promise 用于同步写法
  • 一开始beforeUpload中是直接这样写的:
beforeUpload: async (file) => {
        const isExcel = await getFileMimeType(file); // 调用上面代码
        if (!isExcel) {
            message.error('上传失败!仅支持文件类型为xlsx的文件');
            return false;
        }
        const isLt10M = file.size / 1024 / 1024 < 10;
        if (!isLt10M) {
            message.error('上传文件不能超过 10MB!');
            return false;
        }
        return true;
}

用这种写法文件都会上传成功,因此改造了beforeUpload,让其返回Promise。