===============【需求:在小程序中将后端返回的字节流写入文件并转发】==============
==============【中间一堆是记录走的弯路,可跳过,最后两段代码是最终结果】==========
背景:服务器将生成的文件二进制流返回给前端,(即服务器写入本地文件,需要下载时读文件内容并通过response 的 outputStream返回给前端)
原需求:仅有网页版,采用构造<a href=''>的方式通过浏览器下载,体验很好,网上文章很多,不做赘述。
后续需求:要求做个app或小程序,方便客户使用,经过尝试,上述<a >的方式不再适用,效果如下(该方法并不规范,是通过将网页版程序build 后的 dist文件 放进5+App中运行出来的),解决未果
第二次尝试:创建uni-app项目,使用uni.downloadFile接口,该方法下载之后,日志提示下载成功,但通过返回路径再进行 uni.saveFile, 报路径不存在。将路径打印出来检查发现确实不存在,很迷。经过百度发现以下方法可以成功:
export function createFlie(fileName, data) {
return new Promise(resolve => {//这里封装了个是一个promise异步请求
// plus.io.requestFileSystem是请求本地文件系统对象
plus.io.requestFileSystem(
plus.io.PUBLIC_DOCUMENTS, // 文件系统中的根目录下的DOCUMENTS目录
fs => {
// 创建或打开文件, fs.root是根目录操作对象,直接fs表示当前操作对象
fs.root.getFile(fileName, {
create: true // 文件不存在则创建
}, fileEntry => {
// 文件在手机中的路径
console.log(fileEntry.fullPath)
fileEntry.createWriter(writer => {
// 写入文件成功完成的回调函数
writer.onwrite = e => {
console.log("写入本地文件成功");
resolve("写入本地文件")
};
// 写入数据
writer.write(JSON.stringify(data));
})
}, e => {
console.log("getFile failed: " + e.message);
});
},
e => {
console.log(e.message);
}
);
})
}
第三次尝试:由于做 ios app太麻烦,放弃,转战小程序。注意:坑又来了,上述方法仅适用于app,因为plus是安卓和ios的增强类,在小程序中会报 “plus 未定义”。换 wx.downloadFile 接口,微信文档:网络 / 下载 / wx.downloadFile (qq.com)
贴出数据处理部分:
uni.request({
url: `${BASE_URI}/products/download-lk`,
method: 'POST',
data: frmIds,
header: {
'content-type': 'application/json',
'Authorization': 'Bearer ' + wx.getStorageSync('token')
},
responseType: 'arraybuffer',
success: (res2) => {
if (res2.data instanceof ArrayBuffer) {
// createFlie(fileName, res.data)
var url_file = wx.createBufferURL(new Uint8Array(res2.data))
//writeFile(fileName, url_file)
dowloadFile(fileName, url_file)
}
},
fail(err) {
console.log(err)
}
})
export function dowloadFile(fileName, url_file) {
var path = `${wx.env.USER_DATA_PATH}/` + fileName
console.log('url: ', url_file)
wx.downloadFile({
url: url_file,
filePath: path,
success(res) {
console.log("成功:", res)
uni.showModal({
title: '下载成功',
content:'是否分享该文件',
showCancel: true,
success: function(res) {
if (res.confirm){
wx.shareFileMessage({
filePath: path,
success() {
console.log("分享成功")
},
fail(err) {
console.log("分享失败: ", err)
}
})
}
}
})
},
fail(err) {
console.log(err)
}
})
}
在小程序调试中该方法可以正常下载,不出意外的话就不会出意外,发布体验版测试,该来的它还是来了,protocol must be http or https
打印出来看一下,小程序开发工具里,是http
真机调试中
这个问题我没有去深究,采用备选方案:自己写入文件,但如果直接将resData写入,下载的文件大小是不对的
终极解决方案它来了:先读再写
文档:文件 / FileSystemManager / FileSystemManager.readFile (qq.com)
export function writeFile(fileName, resData) {
var path = `${wx.env.USER_DATA_PATH}/` + fileName
console.log("wx path: ", path)
const fs = wx.getFileSystemManager()
fs.readFile({
filePath: resData,
success(res) {
console.log("文件读取成功")
fs.writeFile({
filePath: path,
data: res.data,
success(res) {
console.log("成功:", res)
uni.showModal({
title: '下载成功',
content:'是否分享该文件',
showCancel: true,
success: function(res) {
if (res.confirm){
wx.shareFileMessage({
filePath: path,
success() {
console.log("分享成功")
},
fail(err) {
console.log("分享失败: ", err)
}
})
}
}
})
},
fail(res) {
console.error("失败: ", res)
}
})
},
fail(err) {
console.log(err)
}
})
}
最后终于成功拿到了文件并解析成功,中间还经历了文件内容处理的坎坷,我这个文件是二进制格式的,处理方法都在上面的代码里了
后续:读写的方法,在安卓上可以下载成功,ios上不行
==============================【最终结果】=================================
后经过调整发现走了弯路,最终解决方案,获取到buffer,直接写,真机调试安卓,ios均可用
uni.request({
url: `${BASE_URI}/products/download-lk`,
method: 'POST',
data: frmIds,
header: {
'content-type': 'application/json',
'Authorization': 'Bearer ' + wx.getStorageSync('token')
},
responseType: 'arraybuffer',
success: (res2) => {
if (res2.data instanceof ArrayBuffer) {
var url_file = wx.createBufferURL(new Uint8Array(res2.data))
writeFile(fileName, new Uint8Array(res2.data).buffer)
}
},
fail(err) {
console.log(err)
}
})
export function writeFile(fileName: string, data: ArrayBuffer) {
var path = `${wx.env.USER_DATA_PATH}/` + fileName
console.log("wx path: ", path)
const fs = wx.getFileSystemManager()
fs.writeFile({
filePath: path,
data: data,
success(res) {
shareFile(path) //调用 wx.shareFileMessage
},
fail(res) {
wx.showToast({
title: '下载失败',
icon: 'error'
})
console.error("写入文件失败: ", res)
}
})
}