ReactJS调用NodeJS后端下载ZIP文件无法打开问题排查
React客户端无法正确处理NodeJS后端生成的ZIP文件下载
我尝试通过ReactJS客户端下载NodeJS后端生成的ZIP文件,下载得到的文件无法被压缩软件打开,但后端本地保存的调试ZIP文件完全正常。
React客户端代码
{this.state.selectedRows.length > 0 && <> <Tooltip title="Baixar arquivos" arrow> <IconButton onClick={() => { const dcs = (!!this.state.selectedRows && this.state.selectedRows.length === 0) ? this.state.fls.map(f => f.doc_id ) : this.state.selectedRows this.props.api.post('/mediateca/downloadzipfile', { docs: dcs }).then(r => { this.setState({ selectedRows: [] }) var blob = new Blob([r], { type: "application/octet-stream" }); saveAs(blob, "example.zip") }) }}> <FileDownload /> </IconButton> </Tooltip> <D size="0.5rem" /> </> }
打印POST响应时,看到类似PK<0x03><0x04><0x14>...的字符串,这是ZIP文件的ASCII序列,但下载的文件大小为238.2KB(后端本地文件为130.8KB),且无法打开。
NodeJS后端代码
async downloadZIPfile(ctx) { let u = await ctx.auth.getUser() Log.create(!!u ? u.email : "null", ctx.request.url()) const { docs } = ctx.request.all() const rdocs = !!docs && docs.length > 0 ? docs : [] //Recupera os pdfs do banco de dados e salva um zip no /tmp if(rdocs.length > 0){ var zip = new AdmZip() for(let x in rdocs){ const doc = await Documentos.findOne({ where: { id: rdocs[x] } }) if (!!doc) { const file = await Arquivos.findOne({ where: { id: doc.arquivo_id } }) var filePath = "/tmp/"+rdocs[x]+".pdf" zip.addFile(rdocs[x]+".pdf",file.binario) } } var data = zip.toBuffer() var fileName = "flavio.zip" zip.writeZip("/tmp/"+fileName) ctx.response.header('Content-Type', 'application/octet-stream') ctx.response.header('Content-Disposition', `attachment; filename=${fileName}`) ctx.response.header('Content-Length', data.length) ctx.response.send(data); } else { ctx.response.status(404).send() } return { status: 'ok' } }
问题原因
前端请求默认将响应解析为字符串/JSON格式,二进制ZIP数据被转换成UTF-8字符串后,原始二进制结构被破坏。再将这个字符串包装成Blob时,会引入额外的编码字节,导致文件损坏、体积变大。
解决方案
修改请求配置,指定响应类型为blob,让浏览器直接以二进制形式接收数据:
{this.state.selectedRows.length > 0 && <> <Tooltip title="Baixar arquivos" arrow> <IconButton onClick={() => { const dcs = (!!this.state.selectedRows && this.state.selectedRows.length === 0) ? this.state.fls.map(f => f.doc_id ) : this.state.selectedRows // 添加responseType: 'blob'配置 this.props.api.post('/mediateca/downloadzipfile', { docs: dcs }, { responseType: 'blob' }) .then(r => { this.setState({ selectedRows: [] }) // 直接使用响应返回的Blob对象 saveAs(r, "example.zip") }) }}> <FileDownload /> </IconButton> </Tooltip> <D size="0.5rem" /> </> }
补充说明
如果你的this.props.api是封装后的请求工具(比如axios),确保它支持传递responseType配置。如果底层是原生Fetch API,需要设置response.blob()来处理:
fetch('/mediateca/downloadzipfile', { method: 'POST', body: JSON.stringify({ docs: dcs }), headers: { 'Content-Type': 'application/json' } }) .then(response => response.blob()) .then(blob => { saveAs(blob, "example.zip") })
内容的提问来源于stack exchange,提问作者Flavio Luis de Mello
相关产品推荐
相关产品推荐

