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

如何在Nextjs中点击按钮下载AWS S3预签名URL的外部图片

方案总览

你的核心痛点是后端中转大文件导致超时,最优解就是避免后端中转,直接让前端从S3预签名URL下载,可落地的方案分为以下几个部分:

一、后端改造

原有同步中转逻辑需要在单次请求中完成S3下载、TIFF编解码、返回流全流程,1GB文件必然超时,且会大量占用服务器带宽和算力,按业务需求选择对应改造方案:

场景1:不需要实时调整TIFF压缩参数、DPI

直接返回原文件的预签名URL即可,改造后的Go代码示例:

// GetDownloadBatchFile handles GET requests to get s3 presigned download url
func GetDownloadBatchFile(ctx *atreugo.RequestCtx) error {
    // Parse Batch ID
    batchIntfID := ctx.UserValue("batch_id")
    batchStrID, isStr := batchIntfID.(string)
    if !isStr {
        return ctx.TextResponse("User error. No batch ID provided", 400)
    }
    batchIntID, err := strconv.Atoi(batchStrID)
    if err != nil {
        return ctx.TextResponse("User error. Invalid batch ID provided", 400)
    }

    // Get Batch & Batch Setting
    batch, err := models.GetBatchByID(batchIntID)
    if err != nil {
        return ctx.TextResponse("User Error. No batch associated with provided batch ID", 404)
    }
    batch.BatchSetting, err = models.GetBatchSettingByID(batch.BatchSettingID)
    if err != nil {
        return ctx.TextResponse("User Error. No batch setting associated with provided batch setting ID", 404)
    }

    // Generate presigned url with custom response headers
    req, _ := util.AWSS3Svc.GetObjectRequest(&s3.GetObjectInput{
        Bucket: aws.String(batch.BatchSetting.AWSBucket),
        Key:    aws.String(batch.OutputFileKey),
        // 直接指定响应头,前端下载时自动触发保存,文件名自定义
        ResponseContentDisposition: aws.String("attachment; filename=custom-output.tiff"),
        ResponseContentType: aws.String("image/tiff"),
    })
    // 过期时间按文件大小设置,1GB文件建议设2小时以上
    URL, err := req.Presign(time.Hour * 3)
    if err != nil {
        return ctx.TextResponse("System Error. Failed to generate download link", 500)
    }

    // 直接返回JSON格式的预签名URL
    ctx.Response.Header.SetContentType("application/json")
    return ctx.JSONResponse(map[string]string{
        "presignedUrl": URL,
    })
}

场景2:必须实时生成指定DPI、无压缩的TIFF

实时处理1GB文件必然超时,必须改成异步方案:

  • 用户点击下载时,后端先返回「任务已提交」的响应,同时提交转码任务到异步队列(可以用RabbitMQ或者Go原生channel实现轻量队列)
  • 后台Worker异步下载S3原文件,转码完成后把新文件上传到S3
  • 转码完成后通过站内信、邮件通知用户,或者前端轮询任务状态,完成后返回新文件的预签名URL供下载

二、Next.js前端实现

前置要求:先配置S3桶的CORS规则,允许你的前端域名发起GET请求,避免跨域拦截。

方式1:原生a标签下载(优先推荐,适合大文件)

调用浏览器原生下载能力,支持进度显示、断点续传,不会占用前端页面内存,1GB文件无压力。代码示例:

'use client' // Next.js 13+ App Router需要添加该标记
import { useState } from 'react'

export default function DownloadButton({ batchId }: { batchId: number }) {
  const [loading, setLoading] = useState(false)

  const handleDownload = async () => {
    setLoading(true)
    try {
      // 调用后端接口获取预签名URL
      const res = await fetch(`/你的后端接口路径/batch/${batchId}/download-url`)
      const { presignedUrl } = await res.json()

      // 动态创建a标签触发下载
      const a = document.createElement('a')
      a.href = presignedUrl
      // 如果后端生成预签名时已经设置了Content-Disposition,可省略download属性
      a.download = 'custom-output.tiff'
      document.body.appendChild(a)
      a.click()
      document.body.removeChild(a)
    } catch (err) {
      console.error('下载失败', err)
    } finally {
      setLoading(false)
    }
  }

  return (
    <button onClick={handleDownload} disabled={loading}>
      {loading ? '获取下载地址中...' : '下载图片'}
    </button>
  )
}

方式2:流式下载(需要自定义页面内下载进度时使用)

如果需要在页面内嵌入下载进度条,可以用fetch的ReadableStream处理,注意1GB以上文件不建议转Blob存入内存,仅适合中等大小的文件:

const handleDownloadWithProgress = async () => {
  setLoading(true)
  try {
    const res = await fetch(`/你的后端接口路径/batch/${batchId}/download-url`)
    const { presignedUrl } = await res.json()

    const response = await fetch(presignedUrl)
    const contentLength = +response.headers.get('Content-Length')!
    let receivedLength = 0
    const chunks = []

    const reader = response.body?.getReader()
    if (!reader) return

    while(true) {
      const { done, value } = await reader.read()
      if (done) break

      chunks.push(value)
      receivedLength += value.length
      // 这里可以更新自定义进度条的状态
      console.log(`下载进度: ${(receivedLength / contentLength * 100).toFixed(2)}%`)
    }

    // 生成Blob触发下载
    const blob = new Blob(chunks)
    const url = URL.createObjectURL(blob)
    const a = document.createElement('a')
    a.href = url
    a.download = 'custom-output.tiff'
    a.click()
    URL.revokeObjectURL(url)
  } catch (err) {
    console.error('下载失败', err)
  } finally {
    setLoading(false)
  }
}

注意事项

  • S3预签名URL的过期时间要根据文件大小设置足够长,1GB文件建议设置2小时以上,避免下载到一半URL过期
  • 如果用CloudFront加速S3的话,要单独配置CloudFront的缓存策略和CORS,避免预签名URL被缓存
  • 大文件下载建议开启S3的分段下载支持,浏览器原生会自动处理,不需要额外开发

内容的提问来源于stack exchange,提问作者Brady Agranoff

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.29 22:36:00