图片编辑器中实现文件上传的三种方式和二进制流及文件头校验文件类型
背景
最近在 vue-design-editor 开源项目中实现 psd 等多种文件格式上传解析成模板过程中, 发现搞定设计文件上传没有使用 input 实现文件上传, 所以我研究了一下相关技术, 总结了以下三种文件上传方法
- input 文件选择
- window.showOpenFilePicker 和 window.showDirectoryPicker 文件选择和文件夹选择
- 拖拽上传
为什么我不使用 file 里面的 type 字段直接当文件类型判断, 下图就是 psd 文件类型
大家发现是不是其实可以通过 type 字段进行判断, 但是告诉大家这是我把 png 图片直接修改了后缀名为 psd, 所以直接通过 type 进行文件类型校验是不准确的, 会有隐患, 如果我们通过二进制流及文件头校验就非常准确了
文件上传
input 文件上传
如果大家实现文件上传第一想到的应该是 input 文件上传, 这里就不多赘述了
<input type="file" 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);
相关文章:

图片编辑器中实现文件上传的三种方式和二进制流及文件头校验文件类型
背景 最近在 vue-design-editor 开源项目中实现 psd 等多种文件格式上传解析成模板过程中, 发现搞定设计文件上传没有使用 input 实现文件上传, 所以我研究了一下相关技术, 总结了以下三种文件上传方法 input 文件选择window.showOpenFilePicker 和 window.showDirectoryPicke…...

深度学习,CRNN+CTC和Attention OCR你更青睐哪一种?
深度学习在OCR领域的应用已经取得了瞩目的成果,而选择合适的算法对于提升OCR的识别准确率至关重要。在众多算法中,CRNN和Attention OCR犹如两颗璀璨的明珠,备受瞩目。 CRNN,这位结合了卷积神经网络(CNN)和…...

飞桨AI应用@riscv OpenKylin
在riscv编译安装飞桨PaddlePaddle参见: 算能RISC-V通用云编译飞桨paddlepaddleopenKylin留档_在riscv下进行paddlelite源码编译-CSDN博客 安装好飞桨,就可以用飞桨进行推理了。刚开始计划用ONNX推理,但是在算能云没有装上,所以最…...

在MongoDB建模1对N关系的基本方法
“我在 SQL 和规范化数据库方面拥有丰富的经验,但我只是 MongoDB 的初学者。如何建立一对 N 关系模型?” 这是我从参加 MongoDB 分享日活动的用户那里得到的最常见问题之一。 我对这个问题没有简短的答案,因为方法不只有一种,还有…...

C++基础之运算符重载(十一)
首先为什么要对运算符进行重载?因为C内置的运算符只能作用于一些基本数据类型,而对类和结构体这种自定义数据类型是不管用的。所以这时我们需要对运算符进行重新定义满足一定的运算规则。 运算符重载的三种形式 1.以普通的函数进行重载 #include <…...

初始Java篇(JavaSE基础语法)(2)(逻辑控制)
个人主页(找往期文章包括但不限于本期文章中不懂的知识点):我要学编程(ಥ_ಥ)-CSDN博客 目录 逻辑控制 顺序结构 分支结构 if语句 switch 语句 循环结构 while 循环 for 循环 do while 循环 输入输出 输出到控制台 从键盘输入 …...

家用路由器和企业路由器的区别?
一、家用路由器 家用路由器路由器交换机 它只有一个WAN口和一个LAN口,WAN口接公网一个地址,LAN口接你电脑一个IP地址,完全符合路由器的设计,而因为家里如果用了,说明要接多个电脑,那么如果还需要对每个接口…...
Gin简介(Go web基础知识)
Gin简介 https://geektutu.com/post/quick-go-gin.html我是从这个网站上面摘录的,就是做个笔记,仅分享。膜拜极客兔兔大佬 Go特性: 快速:路由不使用反射,基于Radix树,内存占用少。 中间件:HT…...
HBase的Bulk Load流程
目录 1. 数据准备 2. 文件移动 3. 加载数据 4. Region处理 5. 元数据更新 6. 完成加载 7. 清理 8. 异常处理 LoadIncrementalHFiles(也称为Bulk Load)是HBase中一种将大量数据高效导入到HBase表的机制。以下是LoadIncrementalHFiles的主要流程步…...
vue中图片替换 遇到问题
问题: 在img标签里动态绑定路径:<img v-bind:src"imgSrc" /> data里这样写是错误的:imgSrc:xx/xx.png 这样渲染的路径会有问题,导致出不来图片 解决了 是这样的 data(){return(){imgSrc:require("../…...
Android 观察者模式
在Android中,观察者模式(Observer Pattern)是一种常用的设计模式,用于在对象之间建立一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知并自动更新。在Android开发中࿰…...

阿里云部署MySQL、Redis、RocketMQ、Nacos集群
文章目录 🔊博主介绍🥤本文内容MySQL集群配置云服务器选购CPU选择内存选择云盘选择ESSD AutoPL云盘块存储性能(ESSD) 镜像选择带宽选择密码配置注意事项 安装docker和docker-compose部署MySQL三主六从半同步集群一主二从同步集群规…...

day05-店铺营业状态设置
1. Redis入门 1.1 Redis简介 Redis 是一个基于内存的 key-value 结构数据库。Redis 是互联网技术领域使用最为广泛的存储中间件。 官网: https://redis.io 中文网: https://www.redis.net.cn/ 主要特点: 基于内存存储,读写性能高…...

哈希表(c++)
1、介绍 哈希表,也称为散列表,是一种非常高效的数据结构。它通过将键(Key)映射到数组的特定位置来快速查找、插入和删除数据。这个映射过程由哈希函数(Hash Function)完成,该函数将键转化为一个…...
C#基础-标识符命名规则
目录 1、标识符定义 2、遵循规则 3、标识符的例子 4、MSDN中英文解释 英文...

Zabbix Web界面中文汉化
要想达到上图的效果,第一步先查看 /usr/share/zabbix/assets/fonts/ [rootservice yum.repos.d]# ll /usr/share/zabbix/assets/fonts/ 总用量 0 lrwxrwxrwx. 1 root root 33 3月 23 16:58 graphfont.ttf -> /etc/alternatives/zabbix-web-font 继续查看graph…...

esp32CAM环境搭建(arduino+MicroPython+thonny+固件)
arduino ide 开发工具 arduino版本:1.8.19 arduino ide 中文设置: file >> preferences >> arduino IDE 获取 ESP32 开发环境:打开 Arduino IDE ,找到 文件>首选项 ,将 ESP32 的配置链接填入附加开发板管理网…...

Spring Boot从入门到实战
课程介绍 本课程从SpringBoot的最基础的安装、配置开始到SpringBoot的日志管理、Web业务开发、数据存储、数据缓存,安全控制及相关企业级应用,全程案例贯穿,案例每一步的都会讲解实现思路,全程手敲代码实现。让你不仅能够掌Sprin…...
Spring Boot(七十一):整合RateLimiter实现接口限流
1 简介 RateLimiter 从概念上来讲,速率限制器会在可配置的速率下分配许可证。如果必要的话,每个acquire() 会阻塞当前线程直到许可证可用后获取该许可证。一旦获取到许可证,不需要再释放许可证。 RateLimiter使用的是一种叫令牌桶的流控算法,RateLimiter会按照一定的频率…...

通过jsDelivr实现Github的图床CDN加速
最近小伙伴们是否发现访问我的个人博客http://xiejava.ishareread.com/图片显示特别快了? 我的博客的图片是放在github上的,众所周知的原因,github访问不是很快,尤其是hexo博客用github做图床经常图片刷不出来。一直想换图床&…...
变量 varablie 声明- Rust 变量 let mut 声明与 C/C++ 变量声明对比分析
一、变量声明设计:let 与 mut 的哲学解析 Rust 采用 let 声明变量并通过 mut 显式标记可变性,这种设计体现了语言的核心哲学。以下是深度解析: 1.1 设计理念剖析 安全优先原则:默认不可变强制开发者明确声明意图 let x 5; …...

如何在看板中体现优先级变化
在看板中有效体现优先级变化的关键措施包括:采用颜色或标签标识优先级、设置任务排序规则、使用独立的优先级列或泳道、结合自动化规则同步优先级变化、建立定期的优先级审查流程。其中,设置任务排序规则尤其重要,因为它让看板视觉上直观地体…...

cf2117E
原题链接:https://codeforces.com/contest/2117/problem/E 题目背景: 给定两个数组a,b,可以执行多次以下操作:选择 i (1 < i < n - 1),并设置 或,也可以在执行上述操作前执行一次删除任意 和 。求…...

Android15默认授权浮窗权限
我们经常有那种需求,客户需要定制的apk集成在ROM中,并且默认授予其【显示在其他应用的上层】权限,也就是我们常说的浮窗权限,那么我们就可以通过以下方法在wms、ams等系统服务的systemReady()方法中调用即可实现预置应用默认授权浮…...

Redis数据倾斜问题解决
Redis 数据倾斜问题解析与解决方案 什么是 Redis 数据倾斜 Redis 数据倾斜指的是在 Redis 集群中,部分节点存储的数据量或访问量远高于其他节点,导致这些节点负载过高,影响整体性能。 数据倾斜的主要表现 部分节点内存使用率远高于其他节…...

C++:多态机制详解
目录 一. 多态的概念 1.静态多态(编译时多态) 二.动态多态的定义及实现 1.多态的构成条件 2.虚函数 3.虚函数的重写/覆盖 4.虚函数重写的一些其他问题 1).协变 2).析构函数的重写 5.override 和 final关键字 1&#…...
音视频——I2S 协议详解
I2S 协议详解 I2S (Inter-IC Sound) 协议是一种串行总线协议,专门用于在数字音频设备之间传输数字音频数据。它由飞利浦(Philips)公司开发,以其简单、高效和广泛的兼容性而闻名。 1. 信号线 I2S 协议通常使用三根或四根信号线&a…...

RSS 2025|从说明书学习复杂机器人操作任务:NUS邵林团队提出全新机器人装配技能学习框架Manual2Skill
视觉语言模型(Vision-Language Models, VLMs),为真实环境中的机器人操作任务提供了极具潜力的解决方案。 尽管 VLMs 取得了显著进展,机器人仍难以胜任复杂的长时程任务(如家具装配),主要受限于人…...

Scrapy-Redis分布式爬虫架构的可扩展性与容错性增强:基于微服务与容器化的解决方案
在大数据时代,海量数据的采集与处理成为企业和研究机构获取信息的关键环节。Scrapy-Redis作为一种经典的分布式爬虫架构,在处理大规模数据抓取任务时展现出强大的能力。然而,随着业务规模的不断扩大和数据抓取需求的日益复杂,传统…...
Python 高效图像帧提取与视频编码:实战指南
Python 高效图像帧提取与视频编码:实战指南 在音视频处理领域,图像帧提取与视频编码是基础但极具挑战性的任务。Python 结合强大的第三方库(如 OpenCV、FFmpeg、PyAV),可以高效处理视频流,实现快速帧提取、压缩编码等关键功能。本文将深入介绍如何优化这些流程,提高处理…...