上一篇用 promise 嵌套实现了按 excel 行顺序上传数据,这篇要解决的问题是图片和视频格式校验,图片主要有 jpg png gif 视频 mp4前端
因为用户选择的资源可能并非真正的多媒体文件,使用 js 的 file.type 方法获取的文件类型可能不许确,好比将 .xlsx 改成 .jpg, file.type 获得的类型是image/jpeg数组
客户端拉取资源时,图片和视频的分辨率也一并获取,而上传由前端控制,因此上传时对资源要进行比较准确的判断。promise
个人判断策略:ide
// 获取图片的 width height getImgSize(file) { const imgFileType = ['image/jpeg', 'image/png', 'image/gif'] const filetype = file.type const suffix = filetype.substring(filetype.lastIndexOf('/')+1) // 返回一个 promise return new Promise((resolve, reject) => { let reader = new FileReader() reader.onload = function(e){ const data = e.target.result const img = new Image() img.onload = function(){ resolve({width: img.width, height: img.height, ext: suffix }) } img.onerror = function(){ reject(`[${file.name}]解析失败,可能图片格式不正确`) } img.src = data } reader.readAsDataURL(file) }) }, // 获取视频的 width height getVideoSize(file) { const videoType = ['video/mp4',] const filetype = file.type const suffix = filetype.substring(filetype.lastIndexOf('/')+1) // 返回一个 promise return new Promise((resolve, reject) => { const url = window.URL.createObjectURL(file) const video = document.createElement('video') video.onloadedmetadata = evt => { // Revoke when you don't need the url any more to release any reference window.URL.revokeObjectURL(url) resolve({width: video.videoWidth, height: video.videoHeight, ext: suffix }) } video.onerror = evt => { reject(`[${file.name}]解析失败,可能视频文件格式不正确`) } video.src = url video.load() }) },
依据 ISO 标准, jpg 文件的前2个字节为 0xFF, 0xD8
png 前8个字节 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A
gif 有"GIF87a" 和 "GIF89a",依据前6个字节判断函数
js 提供了 getUint8 以便读取字节码,只须要传入偏移量便可this
const JPEG_SOI = [0xFF, 0xD8] const JPEG_EOI = [0xFF, 0xD9] // png的文件头就是png图片的前8个字节,其值为[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A] const PNG_HEADER = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A] // GIF files start with a fixed-length header ("GIF87a" or "GIF89a") giving the version const GIF89A_HEADER = [0x47, 0x49, 0x46, 0x38, 0x39, 0x61] const GIF87A_HEADER = [0x47, 0x49, 0x46, 0x38, 0x37, 0x61] // 是否小端序 const isLittleEndian = (function() { var buffer = new ArrayBuffer(2); new DataView(buffer).setInt16(0, 256, true) return new Int16Array(buffer)[0] === 256 })() // byte数组元素是否相等 function isArrayEqual(a, b){ for(let i=0; i<a.length; i++){ if(a[i] !== b[i]){ return false } } return true } export function getImageTypeByHeadContent(file){ // file 其实是一个 Blob 对象 // 读取 Blob 对象的前8个字节 const fileHeader = file.slice(0, 8) return new Promise((resolve, reject) => { let reader = new FileReader() reader.onload = function(e){ const data = e.target.result const header = new DataView(data) let bytesArr = [] for(let i=0; i<header.byteLength; i++){ bytesArr.push(header.getUint8(i, isLittleEndian)) } if(isArrayEqual(JPEG_SOI, bytesArr.slice(0,2))){ resolve('jpg') }else if(isArrayEqual(PNG_HEADER, bytesArr)){ resolve('png') }else if(isArrayEqual(GIF89A_HEADER, bytesArr.slice(0,6)) || isArrayEqual(GIF87A_HEADER, bytesArr.slice(0,6)) ){ resolve('gif') }else{ reject() } } reader.readAsArrayBuffer(fileHeader) }) }
那么多媒体文件的校验函数url
// 若是传入的类型与实际不符,则不上传,防止图片类型上传视频,或视频类型上传图片 getMediaSize(file, validtype){ const _this = this return new Promise((resolve, reject) => { if(! _this.hasGotSizeObj.hasOwnProperty(file.name)){ if(file.type.startsWith('image') && validtype === 'image'){ _this.getImgSize(file) .then(data => { // 从文件头信息没法识别图片类型时,之后缀名为图片类型 getImageTypeByHeadContent(file) .then(type => { data.ext = type _this.hasGotSizeObj[file.name] = {extra: data} resolve(data) }) .catch(()=>{ _this.hasGotSizeObj[file.name] = {extra: data} resolve(data) }) }) .catch(err => { reject(err) }) }else if(file.type.startsWith('video') && validtype === 'video'){ _this.getVideoSize(file) .then(data => { _this.hasGotSizeObj[file.name] = {extra: data} resolve(data) }).catch(err => { reject(err) }) }else{ reject(`不容许的文件类型: ${file.type}`) } }else{ resolve(_this.hasGotSizeObj[file.name].extra) } }) },