选择文件后修改再上传失败(element-自定义上传)

1,534 阅读5分钟

我正在参加「掘金·启航计划」

上传的场景

先说一下是怎么遇到这个问题的,项目里面有上传组件的使用,这个是非常正常的,几乎没什么项目没有这个玩意,element-ui里面也有el-upload组件,一般的就是选择完文件就自动上传了,如图:

(自动上传效果)

我这里的需求是:

  1. 点击【选择】按钮先选择文件,展示文件的详情:类型,大小,日期......
  2. 点击【上传】按钮这个时候才去上传文件

如图:

(选择再上传效果)

修改文件导致的上传bug

然后开发好了之后,某天......测试同学反馈了一个bug,在测试上传功能的时候选择了一个文件,发现自己填漏了一个数据,后端立刻又打开文件填上去,继续点击上传按钮,发现这个时候就报错了。

这个时候我就开始思考(baidu),发现是浏览器的安全机制,如果要上传的文件先选择然后进行了修改再上传则会被拒绝上传,这个时候两个思路

  1. 通过把选择的文件转成base64,从而使得和本地的文件状态失去关联,无论你怎么修改文件再点击上传,我上传的还是原来那份 ❌

这种做法我个人不是很推荐,毕竟用户去做了修改就是想得到可以上传到最新的文件的意愿,这个时候你没有通知到用户这样不可行,还是按照原文件去上传了,会让用户误会已经按照自己意愿传了新的文件上去。(如果真的需要这个做法参考的链接也放在下面了,本人没有实验过,就是看到有这种方法)

  1. 反馈提示给到用户,告知用户文件已经修改请重新上传 ⭕️

这个是我采取的一种方法,既然用户做了修改,我们还是遵循浏览器的本意,让用户把最新的文件重新上传一遍,毕竟这也不是一个费时费力的操作,那下面就说一下这个做法

开始解决问题

整理思路

首先我们来整理一下思路,我们做自定义上传的往往都是拿到上传上来的文件变量,也就是一个file,拿到后我们往往是创建一个formData对象然后把file作为一个属性append进去,然后还要append一些别的我们需要自定义的属性,然后把组装好的formData上传也就是像下面这样:

const file = '假装我是一个文件'; // 上传控件拿到的file文件,也就是你上传的文件
const remark = '备注'; // 也是需要上传接口添加的属性
const formData = new FromData(); // 创建formData对象
formData.append('file', file); // 添加文件
formData.append('remark', remark); // 添加备注
axios.post('attachment/upload', formData); // 把formData作为参数请求接口

前置知识,看看我们拿到得file文件是怎么样得:

{status: "success", uid: 1664333398524, lastModified: 1663224825714, lastModifiedDate: Thu Sep 15 2022 14:53:45 GMT+0800 (中国标准时间) {}, name: "我的待办.xls", size: 106496, type: "application/vnd.ms-excel", webkitRelativePath: ""}

上传的流程就是这样,那么我们通过选择文件的操作拿到了file之后要怎么判断文件是否有过更改呢?

  • 是否有监听文件的改变的监听函数?
  • 答:好像没有,反正我没找到 ❌
  • 既然我们的接口请求会失败那是否可以通过promisecatch捕获错误然后提示用户?
  • 答:这个确实会catch到个错误,但是没有可以捕获一个特定的异常,例如你以为有类似 file has change这种报错信息就没有,没办法确定这是通过修改了文件无法上传报的错误 ❌
  • 想通过file的改变时间来判断是否文件有更改
  • 答:你会发现你的file文件的时间是一样的并没有更改 ❌
  • 通过读取当前的文件判断是否有更改
  • 答: 这个是可取的,因为如果文件进行了修改,你去读取的同样会得到拒绝,因为文件发生了更改也是无法读取的,这就可以判断到是否有过更改,这就是我努力思考(google)的结果

代码实现

先看代码

// 判断是否修改了选择的文件
        try {
          // 这里读取文件
          // file文件是个对象按道理不能slice,但是继承自blob
          // blob可以看成二进制字符串,有slice方法
          // blob得arrayBuffer会把二进制转成arraybuffer,并且返回promise,转得过程就要读取文件
          await this.file.slice(0 ,1).arrayBuffer();
        }catch (e) {
          this.$error(`选择的文件已经修改,请重新选择上传:${e}`);
          return;
        }

再看解析

  1. 我们是可以得到一个file文件,这个是确定的,其实我们的file对象是特殊的Blobfile接口也继承了Blob接口的属性
  2. 那我们读取这个文件是没必要去读取整个文件我们通过file.slice(0, 1)截取一点即可(你会说为什么file是个对象也可以slice,因为Blob对象表示一个不可变、原始数据的类文件对象,file继承了Blob,所以也有slice的方法调用)
  3. 然后我们把截取到的转arrayBufferfile.slice(0, 1).arrayBuffer()这个转的过程中,就会读取到文件返回一个promise,这个时候如果读取成功了说明文件没有进行更改,如果被拒绝了则说明文件进行了更改,我们就告知用户需要重新选择文件

参考文档

把选择的文件转成base64,使得和本地的文件状态失去关联从而不会被浏览器拒绝上传

Blob.arrayBuffer() 用法文档

Blob对象 文档

File对象 文档