图片编辑器中实现文件上传的三种方式和二进制流及文件头校验文件类型

news2024/12/29 9:18:05

背景

最近在 vue-design-editor 开源项目中实现 psd 等多种文件格式上传解析成模板过程中, 发现搞定设计文件上传没有使用 input 实现文件上传, 所以我研究了一下相关技术, 总结了以下三种文件上传方法

wecom-temp-1ca1ac3f69f021a80526d22768d8b64b.png

  1. input 文件选择
  2. window.showOpenFilePicker 和 window.showDirectoryPicker 文件选择和文件夹选择
  3. 拖拽上传

为什么我不使用 file 里面的 type 字段直接当文件类型判断, 下图就是 psd 文件类型

企业微信截图_60833287-f70b-4969-9840-fa034aa44d52.png

大家发现是不是其实可以通过 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 有限制

  1. 需要 https 环境,如果是本地 localhost 不受此限制。
  2. 不能在 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) => {});

拖拽上传

搞定设计支持拖拽上传文件, 所以也支持拖拽上传

企业微信截图_d6c53985-acc8-4d30-81c0-4275eb7bb531.png

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();
  // 拖拽区域样式提示
  // 也可以通过变量的形式控制dom
  dragArea.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 校验类型一样的缺陷

企业微信截图_aa8f8339-01cc-422f-abfe-7fe90bd1bf96.png

就是使用文件后缀来判断

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);

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1532669.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

树莓派夜视摄像头拍摄红外LED灯

NoIR相机是一种特殊类型的红外摄像头&#xff0c;其名称来源于"No Infrared"的缩写。与普通的彩色摄像头不同&#xff0c;NoIR相机具备红外摄影和低光条件下摄影的能力。 一般摄像头能够感知可见光&#xff0c;并用于普通摄影和视频拍摄。而NoIR相机则在设计上去除了…

【Hadoop】Hadoop 编译源码

目录 为什么要源码编译Hadoop 编译源码1前期工作准备2jar 包安装2.1安装 Maven2.2安装 ant2.3安装 glibc-headers 和 g2.4安装 make 和 cmake2.5安装 protobuf2.6安装 openssl 库2.7安装 ncurses-devel 库 3编译源码3.1解压源码到 /opt/ 目录3.2 进入到 hadoop 源码主目录 /opt…

Redis持久化笔记(3)

redis持久化&#xff1a;把内存的数据存放到磁盘&#xff0c;避免因为断电等导致数据丢失。 RDB&#xff08;Redis Database&#xff09; rdb就是在一定时间间隔内把当时的数据和状态保存为 .rdb文件放在磁盘中。 自动触发设置&#xff1a;在redis.conf 修改.rdb文件的保存位…

【Vue】三、使用ElementUI实现图片上传

目录 一、前端代码实现 二、后端代码实现 三、调试效果实现 一、前端代码实现 废话不多说直接上代码 <el-form-item prop"image" label"上传图片" v-model"form.image"><el-upload:action"http://localhost:8…

【prometheus-operator】k8s监控集群外redis

1、部署exporter GitHub - oliver006/redis_exporter: Prometheus Exporter for Redis Metrics. Supports Redis 2.x, 3.x, 4.x, 5.x, 6.x, and 7.x redis_exporter-v1.57.0.linux-386.tar.gz # 解压 tar -zxvf redis_exporter-v1.57.0.linux-386.tar.gz # 启动 nohup ./redi…

Go——指针和内存逃逸

区别于C/C中的指针&#xff0c;Go语言中的指针不能进行偏移和运算&#xff0c;是安全指针。 要搞明白Go语言中的指针概念需要先知道3个概念&#xff1a;指针地址&#xff0c;指针类型和指针取值。 一. Go语言的指针 Go语言中的函数传参都是值拷贝&#xff0c;当我们想修改某个…

# Django通过开关控制数据库参数(JS版)

目录 场景初始的视图层HTML部分JS代码视图层接受部分 场景 此时我的表单中有一排开关 数据库有一排状态 需求是要当开关开启时数据库state为1&#xff0c;关闭时为0 初始的视图层 将整个adv数据表返回给前端HTML def adv(request):adv_list Adv.objects.all()return rende…

语言教育App头牌Duolingo如何重新点燃用户增长350%?

Duolingo是全球最大的语言教育APP&#xff0c;拥有数亿用户&#xff0c;然而用户增长正在放缓&#xff0c;本案例以Duolingo增长 通过数据建模洞察关键指标&#xff0c;并围绕指标用增长实验驱动&#xff0c;设计植根于创新的增长模式&#xff0c;包括启动排行榜&#xff0c;重…

docker仓库登录及配置insecure-registries的方法

docker仓库登录及配置insecure-registries的方法 这篇文章主要介绍了docker仓库登录配置insecure-registries的方法,docker客户端如果配置中添加了insecure-registary配置&#xff0c;就不需要在docker 客户端配置上对应证书&#xff0c;如果不配置要在/etc/docker/certs.d/目…

【阅读论文】When Large Language Models Meet Vector Databases: A Survey

摘要 本调查探讨了大型语言模型&#xff08;LLM&#xff09;和向量数据库&#xff08;VecDB&#xff09;之间的协同潜力&#xff0c;这是一个新兴但迅速发展的研究领域。随着LLM的广泛应用&#xff0c;出现了许多挑战&#xff0c;包括产生虚构内容、知识过时、商业应用成本高昂…

流畅的 Python 第二版(GPT 重译)(十三)

第二十四章&#xff1a;类元编程 每个人都知道调试比一开始编写程序要困难两倍。所以如果你在编写时尽可能聪明&#xff0c;那么你将如何调试呢&#xff1f; Brian W. Kernighan 和 P. J. Plauger&#xff0c;《编程风格的要素》 类元编程是在运行时创建或自定义类的艺术。在 P…

ZYNQ EMIO MIO

1 概述 先来了解GPIO的BANK分布&#xff0c;在UG585文档GPIO一章中可以看到GPIO是有4个BANK&#xff0c; 注意与MIO的BANK区分。 BANK0 控制32个信号&#xff0c;BANK1控制22个信号&#xff0c;总共是MIO的54个引脚&#xff0c;也就是诸如 SPI,I2C,USB,SD 等 PS 端外设接口&am…

C语言字符函数与字符串函数:编织文字的舞会之梦(上)

欢迎来到白刘的领域 Miracle_86.-CSDN博客 系列专栏 C语言知识 先赞后看&#xff0c;已成习惯 创作不易&#xff0c;多多支持&#xff01; 在编程的过程中&#xff0c;我们经常要处理字符以及字符串&#xff0c;为了方便操作这些字符和字符串&#xff0c;C语言标准库中提供…

jmx_prometheus_javaagent-0.19.0.jar+Prometheus+Grafana 监控Tongweb嵌入式(by lqw)

文章目录 1.思路2.部署准备3.应用jar包修改配置和导入tw嵌入式的依赖&#xff08;参考&#xff09;4.Prometheus部署5.Prometheus配置6.安装和配置Grafana 1.思路 Tongweb嵌入式最终是把依赖打入到java应用&#xff08;也就是jar包里&#xff09;&#xff0c;然后启动jar包进行…

TinTin Web3 Bounty 挑战杯开启,Sui 向你发出挑战邀请

以下文章来源于TinTinLand &#xff0c;作者TinTinLand。 2024 年开年最火的是什么&#xff1f; 对 Web3 来说&#xff0c;Bounty 任务应该是普通人获得行业“一杯羹”的重要捷径&#xff01; 通过深入学习各类 Web3 技术&#xff0c;凭借实战锻炼开发创新项目&#xff0c;就…

Linux学习:git补充与调试工具gdb

目录 1. git版本控制器&#xff08;续&#xff09;1.1 git本地仓库结构1.2 git实现版本控制与多人协作的方式1.3 git相关指令&#xff0c;多分支模型与.gitignore文件 2. gdb调试工具2.1 企业项目开发流程简述与调试的必要性2.2 bug的调试思路方法与调式工具的使用 1. git版本控…

ResNet目标检测算法实现交通灯分类

红绿灯识别方案&#xff1a;https://zhuanlan.zhihu.com/p/674791906 目录 一、制作数据集二、ResNet算法三、pytorch转onnx文件四、onnx推理测试五、onnx转mnn 一、制作数据集 1、数据集划分 将红绿灯数据集大文件夹中不同类别的小文件夹中的图片按照9&#xff1a;1进行划分…

小程序绕过 sign 签名

之前看到了一篇文章【小程序绕过sign签名思路】之前在做小程序渗透时也遇到了这种情况&#xff0c;但是直接放弃测试了&#xff0c;发现这种思路后&#xff0c;又遇到了这种情况&#xff0c;记录下过程。 并没有漏洞分享&#xff0c;仅仅是把小程序也分享出来&#xff0c;方便…

Idea 不能创建JDK1.8的spring boot项目

由于https://start.springboot.io/ 不支持JDK1.8&#xff0c;那么我们需要换idea的springboot创建源&#xff0c;需要换成 https://start.aliyun.com&#xff0c;这也是网上大部分教程说的&#xff0c;但是我这边会报这样的错误&#xff1a; Initialization failed for https:…

Go --- Go语言垃圾处理

概念 垃圾回收&#xff08;GC-Garbage Collection&#xff09;暂停程序业务逻辑SWT&#xff08;stop the world&#xff09;程序根节点&#xff1a;程序中被直接或间接引用的对象集合&#xff0c;能通过他们找出所有可以被访问到的对象&#xff0c;所以Go程序的根节点通常包括…