南宁网站开发gxjzdrj,北京建设网上银行,wordpress做的好看,wordpress 主题 love背景
最近在 vue-design-editor 开源项目中实现 psd 等多种文件格式上传解析成模板过程中, 发现搞定设计文件上传没有使用 input 实现文件上传, 所以我研究了一下相关技术, 总结了以下三种文件上传方法 input 文件选择window.showOpenFilePicker 和 window.showDirectoryPicke…背景
最近在 vue-design-editor 开源项目中实现 psd 等多种文件格式上传解析成模板过程中, 发现搞定设计文件上传没有使用 input 实现文件上传, 所以我研究了一下相关技术, 总结了以下三种文件上传方法 input 文件选择window.showOpenFilePicker 和 window.showDirectoryPicker 文件选择和文件夹选择拖拽上传
为什么我不使用 file 里面的 type 字段直接当文件类型判断, 下图就是 psd 文件类型 大家发现是不是其实可以通过 type 字段进行判断, 但是告诉大家这是我把 png 图片直接修改了后缀名为 psd, 所以直接通过 type 进行文件类型校验是不准确的, 会有隐患, 如果我们通过二进制流及文件头校验就非常准确了
文件上传
input 文件上传
如果大家实现文件上传第一想到的应该是 input 文件上传, 这里就不多赘述了
input typefile accept.png, .jpeg, .jpg, .psd /缺点就是样式很丑, 定制样式麻烦, 所以实现点击文件上传功能没有直接上手实现
window.showOpenFilePicker 和 window.showDirectoryPicker
支持唤起文件和文件夹, 属于浏览器全局方法直接调用即可
showSaveFilePicker 保持文件功能
如果有以上三个 api 就可以在浏览器中实现代码编辑器功能
代码实现如下
const arrFileHandle await window.showOpenFilePicker({types: [{accept: {image/*: [.psd, .png, .gif, .jpeg, .jpg, .webp],},},],// 可以选择多个图片multiple: false,
});
// 遍历选择的文件
for (const fileHandle of arrFileHandle) {// 获取文件内容const fileData await fileHandle.getFile();fileList.value.push(fileData);
}不过使用该 api 有限制
需要 https 环境如果是本地 localhost 不受此限制。不能在 iframe 内使用因为被认为不安全
所以我在使用该 api 过程中也有兜底逻辑
如果不支持也能点击唤起, 模拟 input 点击唤起
const input document.createElement(input);
input.type file;
input.multiple multiple;
input.accept .png, .jpeg, .jpg, .psd;
input.click();
input.addEventListener(change, (file) {});拖拽上传
搞定设计支持拖拽上传文件, 所以也支持拖拽上传 import { tryOnMounted, useEventListener } from vueuse/core;
const dragArea ref();
tryOnMounted(() {useEventListener(dragArea.value, dragover, onDragOver);useEventListener(dragArea.value, dragleave, onDragLeave);useEventListener(dragArea.value, drop, onDrop);
});
function onDragOver(e: Event) {e.preventDefault();// 拖拽区域样式提示// 也可以通过变量的形式控制domdragArea.value.classList.add(dragover);isDrag.value true;
}function onDragLeave(e: Event) {e.preventDefault();dragArea.value.classList.remove(dragover);isDrag.value false;
}async function onDrop(e: any) {e.preventDefault();dragArea.value.classList.remove(dragover);isDrag.value false;const files e.dataTransfer.files;
}比 input 上传的优点是支持上传文件夹
二进制流及文件头校验文件类型
其他校验方法
在背景中也提过通过文件 type 字段校验类型的缺陷
还有一个方法也是有和 type 校验类型一样的缺陷 就是使用文件后缀来判断
const file e.files[0];
//获取最后一个.的位置
const index file.name.lastIndexOf(.);
//获取后缀
const ext file.name.substr(index 1);
console.log(ext);文件后缀名
概念我们了解一下
文件扩展名是文件让电脑识别它的识别器文件本身的格式是内在的扩展名是外在的一般情况下他们是相互对应的但如果扩展名被操作或修改就不能与文件本身的格式对应就会遇到打不开打开乱码或无法显示无法识别等情况。
解决
同样可以将其他类型的文件上传至服务器或者文件压根就没有后缀那又要怎么判断呢因此前端需要使用一个更加合理的方式。
所以使用二进制及文件头的形式来校验文件格式
虽然文件后缀可以手动改因此可以直接通过读取文件的二进制来判断。 通常来说固定类型的文件头都是相同的比如说 jpeg 的文件头是 FF D8 FF E0。
枚举类型相关的文件头
const signatureList [{mime: video/mp4,ext: mp4,offset: 4,signature: [0x66, 0x74, 0x79, 0x70, 0x69, 0x73, 0x6f, 0x6d],},{mime: video/mp4,ext: mp4,offset: 4,signature: [0x66, 0x74, 0x79, 0x70, 0x6d, 0x70, 0x34],},{mime: image/jpeg,ext: jpeg,signature: [0xff, 0xd8, 0xff],},{mime: image/png,ext: png,signature: [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a],},{mime: image/gif,ext: gif,signature: [0x47, 0x49, 0x46, 0x38, 0x37, 0x61],},{mime: image/gif,ext: gif,signature: [0x47, 0x49, 0x46, 0x38, 0x39, 0x61],},{mime: image/vnd.adobe.photoshop,ext: psd,signature: [0x38, 0x42, 0x50, 0x53],},
];FileReader 读取文件的二进制之后判断二进制的前几位是否跟符合相应类型文件的文件头。
/*** description 校验给出的字节数据是否符合某种MIME Type的signature* param {Array} bufferss 字节数据* param {Object} typeItem 校验项 { signature, offset }*/
const check (bufferss: Buffer, { signature, offset 0 }: any) {for (let i 0, len signature.length; i len; i) {// 传入字节数据与文件signature不匹配// 需考虑有offset的情况以及signature中有值为undefined的情况if (bufferss[i offset] ! signature[i] signature[i] ! undefined) return false;}return true;
};/*** description 获取文件二进制数据* param {File} file 文件对象实例* param {Object} options 配置项指定读取的起止范围*/
const getArrayBuffer (file: File, { start, end }: any) {return new Promise((reslove, reject) {try {const reader new FileReader();reader.onload (e: any) {const buffers new Uint8Array(e.target.result);reslove(buffers);};reader.onerror (err) reject(err);reader.onabort (err) reject(err);reader.readAsArrayBuffer(file.slice(start, end));} catch (err) {reject(err);}});
};/*** description 获取文件的真实类型* param {File} file 文件对象实例* param {Object} options 配置项指定读取的起止范围*/
const getFileType (file: File, options { start: 0, end: 32 }) getArrayBuffer(file, options).then((buffers: any) {// 找出签名列表中定义好的类型并返回for (let i 0, len signatureList.length; i len; i) {if (check(buffers, signatureList[i])) {const { mime, ext } signatureList[i];return { mime, ext };}}// 未找到则返回file对象中的信息return { mime: file.type, ext: };}).catch((err) err);