何喜阳博客

如何在uniapp中读取文件ArrayBuffer和sha256哈希值,支持H5、APP、小程序

uniapp 没有提供跨平台的 API 来获取文件的 sha256 哈希值和读取文件的 ArrayBuffer,因此需要开发者自己手动兼容各个平台。在小程序端使用FileSystemManager、app 端是plus.io、H5 端是FileReader,这些 API 都是平台特有的,而且实际调用存在各种问题,也缺乏相关教程,为了实现一个兼容多平台的获取文件 sha256 功能,我查阅了各种讨论、github 项目和大量测试,本文旨在分享如何实现这个功能。

嫌内容太多的,可以直接翻到最后看完整代码。

首先是文件来源,我项目里是通过chooseImageAPI 获取的,理论上也可以通过其他 API 读取文件,我们最终只需要一个临时文件路径。

// 读取图像文件
uni.chooseImage({
  count: 9,
  sizeType: ['original', 'compressed'],
  sourceType: ['album', 'camera'],
  success: (res) => {
    const tempFilePaths = res.tempFilePaths
    // tempFilePaths 是一个数组,可以通过索引获取图片路径,比如tempFilePaths[0],我们需要的就是这个路径
  },
})

小程序获取 ArrayBuffer

先说最简单的,是通过getFileSystemManager实现,根据文档上面显示的兼容性,目前支持 微信小程序、字节跳动小程序、QQ 小程序。我们传入一个文件路径,会返回一个ArrayBuffer,代码如下,filePath就是上面拿到的临时路径。

// 小程序,filePath 是一个本地文件路径
const fs = uni.getFileSystemManager()
return fs.readFileSync(filePath)

APP 获取 ArrayBuffer

在 APP 端就稍微麻烦一些,因为 plus.io 并没有提供直接读取 ArrayBuffer 的 API,我们需要先通过 readAsDataURL 读取文件的 base64 内容,然后再通过uni.base64ToArrayBuffer转换成 ArrayBuffer,代码如下,同样filePath是临时路径,返回一个ArrayBuffer

// APP,filePath 是一个本地文件路径
const H5PlusReadFileArrayBuffer = (filePath) => {
  return new Promise((resolve, reject) => {
    try {
      plus.io.resolveLocalFileSystemURL(
        filePath,
        function (entry) {
          entry.file(function (file) {
            const fileReader = new plus.io.FileReader()
            /* 
              2023.5 更新此处注释:
              如果是Android10+,且用户选择的是原图(原始文件路径),可能无法使用readAsDataURL读取,会导致一直等待。
              暂时没有什么好的办法,具体参考:https://ask.dcloud.net.cn/article/36199
            */
            fileReader.readAsDataURL(file, 'utf-8')
            fileReader.onloadend = function (evt) {
              const result = {
                base64: evt.target.result.split(',')[1],
                size: file.size,
              }
              resolve(uni.base64ToArrayBuffer(result.base64))
            }
          })
        },
        function (error) {
          reject(error)
        },
      )
    } catch (error) {
      reject(error)
    }
  })
}

H5 获取 ArrayBuffer

需要兼容 H5 时又复杂了一些,因为返回的临时路径是blob:xxx格式的,不是 File 对象,我们需要把 blob:xxx 的临时路径转换为 File 对象,代码如下,这个代码是在社区翻了半天发现的,url 就是上面说的临时路径。

// H5 传入blob:xxx的url,返回File对象
const blobURLToBlob = (url) => {
  return new Promise((resolve, reject) => {
    var http = new XMLHttpRequest()
    http.open('GET', url, true)
    http.responseType = 'blob'
    http.onload = function (e) {
      if (this.status == 200 || this.status === 0) {
        resolve(this.response)
      } else {
        reject(this.status)
      }
    }
    http.send()
  })
}

blobURLToBlob方法是把blob:xxx的临时路径转换为 File 对象,然后我们就可以通过FileReader读取 File 对象的 ArrayBuffer 了,H5 获取文件的ArrayBuffer完整代码如下,blobUrl就是上面说的临时路径,里面会先调用blobURLToBlob方法 。

// H5 读取blob:xxx的url,返回ArrayBuffer
const H5ReadBlobUrlArrayBuffer = (blobUrl) => {
  return new Promise(async (resolve, reject) => {
    try {
      const reader = new FileReader()
      // blob数据转File对象数据
      const fileBlob: any = await blobURLToBlob(blobUrl)
      const file = new window.File([fileBlob], 'file.name', { type: 'file.type' })
      // 读取File对象ArrayBuffer
      reader.readAsArrayBuffer(file)
      reader.onload = function (e) {
        resolve(e?.target?.result)
      }
    } catch (e) {
      reject(e)
    }
  })
}

兼容多端代码

至此,我们就完成了 H5、小程序、APP 端获取文件的ArrayBuffer的方法。 我们再封装一个方法,根据不同的平台调用不同的方法,就更完美了。完整代码如下,filePath是临时路径,返回一个ArrayBuffer

// #ifdef H5
// 通过blob:url读取实际的blob数据
// 参考:https://ask.dcloud.net.cn/question/75697
const blobURLToBlob = (url) => {
  return new Promise((resolve, reject) => {
    var http = new XMLHttpRequest()
    http.open('GET', url, true)
    http.responseType = 'blob'
    http.onload = function (e) {
      if (this.status == 200 || this.status === 0) {
        resolve(this.response)
      } else {
        reject(this.status)
      }
    }
    http.send()
  })
}

//  通过blob:url读取实际的ArrayBuffer数据
const H5ReadBlobUrlArrayBuffer = (blobUrl) => {
  return new Promise(async (resolve, reject) => {
    try {
      const reader = new FileReader()
      // blob数据转file对象数据
      const fileBlob: any = await blobURLToBlob(blobUrl)
      const file = new window.File([fileBlob], 'file.name', { type: 'file.type' })
      // 读取file对象ArrayBuffer
      reader.readAsArrayBuffer(file)
      reader.onload = function (e) {
        resolve(e?.target?.result)
      }
    } catch (e) {
      reject(e)
    }
  })
}
// #endif

// #ifdef APP-PLUS
// 通过plus.io读取文件的ArrayBuffer数据
const H5PlusReadFileArrayBuffer = (filePath) => {
  return new Promise((resolve, reject) => {
    try {
      plus.io.resolveLocalFileSystemURL(
        filePath,
        function (entry) {
          entry?.file(function (file) {
            const fileReader = new plus.io.FileReader()
            fileReader.readAsDataURL(file, 'utf-8')
            fileReader.onloadend = function (evt) {
              const result = {
                base64: evt.target.result.split(',')[1],
                size: file.size,
              }
              resolve(uni.base64ToArrayBuffer(result.base64))
            }
          })
        },
        function (error) {
          reject(error)
        },
      )
    } catch (error) {
      reject(error)
    }
  })
}
// #endif

// 对外暴露的方法,通过这个方法获取文件的ArrayBuffer,内部会根据平台调用不同的方法
export const getFileArrayBuffer = async (filePath) => {
  if (uni.canIUse('getFileSystemManager') && uni.getFileSystemManager) {
    const fs = uni.getFileSystemManager()
    return fs.readFileSync(filePath)
  }
  // #ifdef APP-PLUS
  else if (plus.io) {
    return H5PlusReadFileArrayBuffer(filePath)
  }
  // #endif
  // #ifdef H5
  else if (XMLHttpRequest) {
    return H5ReadBlobUrlArrayBuffer(filePath)
  }
  // #endif
  else {
    throw new Error('不支持的平台')
  }
}

到这里,就已经完美的获取文件的 ArrayBuffer ,而且兼容 微信小程序、H5、APP 等平台,理论上也兼容字节跳动小程序、QQ 小程序,看起来 FileSystemManager 的 API 是一样的,未做测试。

获取文件 sha256

接着可以通过 ArrayBuffer 获取文件的 sha256

import { lib as CryptoLib, SHA256 } from 'crypto-js'

const arrayBufferToWordArray = (ab: any) => {
  const i8a = new Uint8Array(ab)
  const a = []
  for (let i = 0; i < i8a.length; i += 4) {
    a.push((i8a[i] << 24) | (i8a[i + 1] << 16) | (i8a[i + 2] << 8) | i8a[i + 3])
  }
  return CryptoLib.WordArray.create(a, i8a.length)
}

// ... 省略上面的 getFileArrayBuffer 方法

// 获取文件的sha256值方法
export const getFileSha256 = async (filePath) => {
  if (!filePath) return Promise.reject('缺少文件路径')
  const arrayBuffer = await getFileArrayBuffer(filePath)
  return SHA256(arrayBufferToWordArray(arrayBuffer)).toString()
}

如果需要获取 md5、sha1 等也是类似的,可以自己去尝试。

以上代码也通过 Gist 提供,如果是 ts 项目可以直接复制使用,js 项目需要适当修改。
地址 :https://gist.github.com/iamxiyang/33d539355c6b22d17c55037fe7b3a67bopen in new window

uniapp 交流 QQ 群: 485464921

以上就是全部内容了。如果你觉得内容有帮助,可以扫码打赏,请我吃一顿饭。

微信支付宝

当前页面地址:https://hexiyang.cn/article/uniapp-read-file-arraybuffer-and-calculate-hash.html

内容基于 《自由转载-非商用-非衍生-保持署名》 协议发布,允许非商用转载,需要保留作者姓名和当前页面链接。