uniapp 没有提供跨平台的 API 来获取文件的 sha256 哈希值和读取文件的 ArrayBuffer,因此需要开发者自己手动兼容各个平台。在小程序端使用FileSystemManager
、app 端是plus.io
、H5 端是FileReader
,这些 API 都是平台特有的,而且实际调用存在各种问题,也缺乏相关教程,为了实现一个兼容多平台的获取文件 sha256 功能,我查阅了各种讨论、github 项目和大量测试,本文旨在分享如何实现这个功能。
嫌内容太多的,可以直接翻到最后看完整代码。
首先是文件来源,我项目里是通过chooseImage
API 获取的,理论上也可以通过其他 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/33d539355c6b22d17c55037fe7b3a67b
uniapp 交流 QQ 群: 485464921