1.场景

服务端将文件内容或图片以流的形式发送给客户端,客户端实现点击按钮下载文件的功能,需要注意的是服务端可能返回文件流以外的错误情况,客户端需要进行判断,正确下载文件或做出错误提醒。

2.后端实现

后端以golang gin为例,实现简单的下载功能:

r.POST("/download", func(ctx *gin.Context) {
	var attachment models.Attachment
	if err := ctx.ShouldBind(&attachment); err != nil {
		ctx.JSON(http.StatusOK, gin.H{"msg": "参数格式错误!","code": 202})
		return
	}
	if attachment.Path == nil {
		ctx.JSON(http.StatusOK, gin.H{"msg": "文件不存在!","code": 202})
	} else {
		ctx.File(attachment.Path)
	}
})

3.前端实现

根据后端的返回情况可以分为两类,一类是正常的文件流返回结果,第二类是JSON格式的错误消息提醒。
前端处理时有两个点需要特别注意:第一点是如果封装过axios响应拦截器且进行过数据解构返回的需要进行一下特殊的处理,因为实际的响应体对象即为文件数据。第二点是需要将axios配置中responseType的类型设置为arraybuffer。

文件流下载基本原理:

      1.从服务器获取数据流。
      2.将数据流转换成Blob对象。
      3.创建一个URL指向该Blob对象。
      4.创建一个a标签,设置其href属性为该URL,download属性为文件名。
      5.模拟点击a标签,触发文件下载。
export const reqDownloadAttachment = (data: any) => request.post<any, any>(API.AttACHMENT_DOWNLOAD, data, { responseType: 'arraybuffer' }) // 一定要设置responseType为arraybuffer

const downLoadFile = async (row:any) => {
    const res = await reqDownloadAttachment(row)  
    try {
        const enc = new TextDecoder('utf-8')
        const data = JSON.parse(enc.decode(new Uint8Array(res.data)))  // 尝试将 arraybuffer 转换为 json
        if (data.code !== 200) { // 能正常转换,说明返回的就是json数据
            ElMessage.error(data.msg || data.error || '下载文件失败!')
        }
    } catch (error) { // 转换失败,说明返回的是文件流 将其转换为 Blob 对象并触发a标签的下载
        const blob = new Blob([res.data], { type: res.headers['content-type'] || 'application/octet-stream' })
        const a = document.createElement('a')
        a.style.display = 'none'
        a.href = URL.createObjectURL(blob)
        a.download = row.origin_name // 文件名
        document.body.appendChild(a)
        a.click()
        document.body.removeChild(a)
    }
}