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

背景

最近在 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.mfbz.cn/a/473630.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Follow-Your-Click——点选图像任意区域对象使用短提示语即可生成视频

简介 “I2V”&#xff08;图像到视频生成&#xff09;旨在将静态图像转换为具有合理动作的动态视频剪辑&#xff0c;在电影制作、增强现实和自动广告等领域有广泛应用。然而&#xff0c;现有的I2V方法存在一些问题&#xff0c;例如缺乏对图像中需要移动的部分的精准控制&#…

RAFT: Adapting Language Model to Domain Specific RAG

预备知识 RAG介绍一文搞懂大模型RAG应用&#xff08;附实践案例&#xff09; - 知乎 (zhihu.com) RAG的核心理解为“检索生成” 检索&#xff1a;者主要是利用向量数据库的高效存储和检索能力&#xff0c;召回目标知识&#xff1b; 生成&#xff1a;利用大模型和Prompt工程…

Android Studio实现内容丰富的安卓校园公告助手

获取源码请点击文章末尾QQ名片联系&#xff0c;源码不免费&#xff0c;尊重创作&#xff0c;尊重劳动 093校园助手 1.开发环境 android stuido3.6 jak1.8 eclipse mysql tomcat 2.功能介绍 具体往下看第三节&#xff0c;功能截图 安卓端&#xff1a; 1.注册登录 2.校园公告列表…

微信小程序订阅消息(一次性订阅消息)

1、准备工作 登录微信公众平台–>订阅消息–>在公共模板库中选中一个模版–>将模版id复制&#xff0c;前后端都需要。 点击详情–>查看详细内容模版 复制给后端 2、相关api的使用 前端使用&#xff1a;wx.requestSubscribeMessage wx.openSetting wx.getSetti…

[Qt学习笔记]QPushButton点击事件和长按事件使用功能

1、背景介绍 在使用QPushButton中&#xff0c;一般都在UI界面直接右键添加槽函数进入代码&#xff0c;很少去分析每个触发事件的功能&#xff0c;比如需要通过长按按钮来触发相应的操作&#xff0c;这里点击信号不可以达到预期的效果。 2、功能分析 首先分析QPushButton的点…

13014.Linux小知识点记录

文章目录 1 工具记录1.1 串口传输文件 1 工具记录 1.1 串口传输文件 打开SecureCRT的串口&#xff0c;执行rx 文件名指令从桌面将可执行文件&#xff0c;拖拽到串口终端即可

计算机三级——网络技术(综合题第二题)

路由器工作模式 用户模式 当通过Console或Telnet方式登录到路由器时&#xff0c;只要输入的密码正确&#xff0c;路由器就直接进入了用户模式。在该模式下&#xff0c;系统提示符为一个尖括号(>)。如果用户以前为路由器输入过名称&#xff0c;则该名称将会显示在尖指号的前…

opengl日记10-opengl使用多个纹理示例

文章目录 环境代码CMakeLists.txt文件内容不变。fragmentShaderSource.fsvertexShaderSource.vsmain.cpp 总结 环境 系统&#xff1a;ubuntu20.04opengl版本&#xff1a;4.6glfw版本&#xff1a;3.3glad版本&#xff1a;4.6cmake版本&#xff1a;3.16.3gcc版本&#xff1a;10.…

【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…

课时70:流程控制_for循环_嵌套循环

2.4.4 嵌套循环 学习目标 这一节&#xff0c;我们从 基础知识、简单实践、小结 三个方面来学习。 基础知识 简介 这里的嵌套实践&#xff0c;与选择语句的嵌套实践基本一致&#xff0c;只不过组合的方式发生了一些变化。常见的组合样式如下&#xff1a;for嵌套for语句for …

【Android】【Bluetooth Stack】蓝牙电话本协议分析(超详细)

1. 精讲蓝牙协议栈&#xff08;Bluetooth Stack&#xff09;&#xff1a;SPP/A2DP/AVRCP/HFP/PBAP/IAP2/HID/MAP/OPP/PAN/GATTC/GATTS/HOGP等协议理论 2. 欢迎大家关注和订阅&#xff0c;【蓝牙协议栈】专栏会持续更新中.....敬请期待&#xff01; 目录 1. 协议简述 1.1 PBAP…

Qt笔记 事件处理_鼠标事件

什么是事件&#xff1f; 点击鼠标左键&#xff0c;双击鼠标左键&#xff0c;鼠标来回移动&#xff0c;按下键盘按钮&#xff0c;这些都是事件。 那么事件的响应机制是什么样的呢&#xff1f; 首先main函数中有一个QApplication&#xff0c;其作用是创建一个应用程序对象&…

11种创造型设计模式(下)

观察者模式 我们可以比喻观察者模式是一种类似广播的设计模式 介绍 观察者模式&#xff1a;对象之间多对一依赖的一种设计方案&#xff0c;被依赖的对象是Subject&#xff0c;依赖的对象是Observer&#xff0c;Subject通知Observer变化。 代码 说明&#xff1a; WeatherStat…

手撕算法-判断是不是二叉搜索树

题目描述 分析 二叉搜索树的特性就是中序遍历是递增序。既然是判断是否是二叉搜索树&#xff0c;那我们可以使用中序递归遍历。只要之前的节点是二叉树搜索树&#xff0c;那么如果当前的节点大于上一个节点值那么就可以向下判断。 如果有出现当前的节点小于上一个节点值&…

Host xxx1 has more disk space than database expected (xxx2 GB > xxx3 GB)

在nova-compute.log中有时会看到日志“Host xxx1 has more disk space than database expected (xxx2 GB &#xff1e; xxx3 GB)”类似日志。 查看下源码&#xff0c;如下&#xff1a; 分析&#xff1a; 定时任务更新主机资源到内存或者对象中&#xff0c;当执行检测的定时任务…

颠覆传统:Web3如何塑造未来的数字经济

引言 近年来&#xff0c;随着数字化时代的到来&#xff0c;互联网已经成为人们生活中不可或缺的一部分。然而&#xff0c;随着技术的不断发展和社会的不断变迁&#xff0c;传统的Web2模式逐渐显露出一些弊端&#xff0c;如数据垄断、隐私泄露等问题&#xff0c;这促使人们寻求…

Linux账号管理与ACL权限设置

文章目录 Linux的账户和用户组用户标识符&#xff1a;UID与GID用户账号用户组&#xff1a;有效与初始用户组groups&#xff0c;newgrp 账号管理新增与删除用户&#xff1a;useradd、相关配置文件、passwd、usermod、userdel用户功能&#xff1a;id、finger、chfn、chsh新增与删…

【文件操作和IO】

文件操作和IO 1.文件2. 硬盘上文件的目录结构3. 文件路径4. 文件重要分类&#xff1a;5. Java中操作文件5.1 Java对于文件操作的API5.2 Java中使用File类来进行文件操作5.3 File类属性5.4 构造方法5.5 方法&#xff1a; 6. 文件内容的读写 -- 文件流&#xff08;数据流&#xf…

C++ List底层实现

文章目录 前言成员变量成员函数迭代器self& operator()前置self operator(int)后置self operator--()前置--self operator--(int)后置--bool operator!(const self & tmp)判断是否相等T* operator*() 解引用操作 list()初始化iterator begin()iterator end()const_iter…

视频无水印批量下载软件|抖音视频提取工具

视频无水印批量下载软件 在当今社交媒体充斥着大量优质视频内容的时代&#xff0c;很多用户都希望能够轻松下载自己喜爱的视频进行收藏或分享。为了满足用户的需求&#xff0c;我们特别推出了一款专业的视频无水印批量下载软件&#xff0c;让您可以方便快捷地获取喜爱的视频内容…
最新文章