You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React+Redux Async Thunk下载文件跳转404问题求助

问题描述

我有一个React Redux应用和Django后端,后端接口从S3获取文件并转为Base64编码返回字符串。希望Redux异步请求完成后,无需跳转新页面即可下载文件,但当前用window.location.href触发下载时会跳转到404页面。

前端代码

DocumentReducer.tsx(含必要导入)

export const getDocument = createAsyncThunk("document/download", async (payload: any) => {
  const {id} = payload
  const response = await instance.get(`tables/document/download/${id}`)
  return response.data
})

const documentSlice = createSlice({
  name: "document",
  initialState: initialState,
  reducers:{},
  extraReducers: (builder) => {
    ...
    builder.addCase(getDocument.fulfilled, (state, action) => {
      const file = action.payload.file
      window.location.href = 'data:application/octet-stream;base64,' + file
      state.status = 'fulfilled'
    })
  } 
})

ExportComponent.tsx(含必要导入)

export const ExportButton: React.FC<ExportButtonProps> = ({document_id}) => {
  const dispatch = useAppDispatch()
  const handleClick = () => {
    const payload = {id: document_id}
    dispatch(getDocument(payload))
  }
  return (
    <Button variant="contained" size="small" onClick={handleClick}>
      <div className="flex gap-3">
        <p>Export</p>
        <ArrowDownTrayIcon className='w-4 text-white'/>
      </div>
    </Button>
  )
}

后端代码(views.py)

def encode_document(s3_key):
    """ get document from s3 and base 64 encode the document """
    buffer = BytesIO()
    output_stream = BytesIO()
    S3_CLIENT.download_fileobj("BUCKET_NAME", s3_key, buffer)
    encoded = b64encode(buffer.getvalue()).decode()
    return encoded

class DocumentS3(APIView):
    parser_classes = [JSONParser]
    permission_classes = [IsAuthenticated]

    def get(self, request, document_id):
        document = Document.objects.get(id=document_id)
        encoded = encode_document(document.s3_key)
        print(encoded[:10])
        return Response({"file": encoded}, status=status.HTTP_200_OK)

后端已成功返回编码字节,但下载链接跳转到404,求Redux中实现正确下载的方法。


解决方案

window.location.href直接设置data URL的方式易被浏览器拦截或解析异常,导致跳转404。正确做法是创建隐藏<a>标签模拟点击触发下载,完成后销毁标签,完全不会跳转页面。

修改Redux回调逻辑

更新DocumentReducer.tsx中getDocument.fulfilled的处理代码:

builder.addCase(getDocument.fulfilled, (state, action) => {
  const file = action.payload.file;
  // 解码Base64字符串为Blob对象
  const byteCharacters = atob(file);
  const byteNumbers = new Array(byteCharacters.length);
  for (let i = 0; i < byteCharacters.length; i++) {
    byteNumbers[i] = byteCharacters.charCodeAt(i);
  }
  const byteArray = new Uint8Array(byteNumbers);
  const blob = new Blob([byteArray], { type: 'application/octet-stream' });
  
  // 创建临时下载链接
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  // 自定义文件名,可根据后端返回或业务需求调整
  a.download = `document-${action.meta.arg.id}.pdf`;
  document.body.appendChild(a);
  a.click();
  
  // 清理临时资源,避免内存泄漏
  document.body.removeChild(a);
  URL.revokeObjectURL(url);
  
  state.status = 'fulfilled';
})

优化说明

  • 将Base64转Blob比直接用data URL更稳定,尤其适配大文件场景
  • 通过临时<a>标签触发下载,彻底避免页面跳转问题
  • 记得调用URL.revokeObjectURL释放内存
  • 若知道文件具体MIME类型(如PDF用application/pdf),替换application/octet-stream可提升浏览器文件识别度

可选优化:后端直接返回文件流

如果文件体积较大,Base64会增加33%的传输体积,推荐后端直接返回文件流:

  1. 后端修改返回逻辑:
def get(self, request, document_id):
    document = Document.objects.get(id=document_id)
    buffer = BytesIO()
    S3_CLIENT.download_fileobj("BUCKET_NAME", document.s3_key, buffer)
    buffer.seek(0)
    response = HttpResponse(buffer, content_type='application/octet-stream')
    response['Content-Disposition'] = f'attachment; filename="document-{document_id}.pdf"'
    return response
  1. 前端修改请求配置:
export const getDocument = createAsyncThunk("document/download", async (payload: any) => {
  const {id} = payload
  const response = await instance.get(`tables/document/download/${id}`, {
    responseType: 'blob' // 指定响应类型为Blob
  })
  return { blob: response.data, id }
})
  1. Redux回调简化:
builder.addCase(getDocument.fulfilled, (state, action) => {
  const { blob, id } = action.payload;
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = `document-${id}.pdf`;
  document.body.appendChild(a);
  a.click();
  document.body.removeChild(a);
  URL.revokeObjectURL(url);
  state.status = 'fulfilled';
})

内容的提问来源于stack exchange,提问作者Rob Campbell

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 01:37:44