说明一:
所谓的下载是浏览器的行为,服务端只需要做到以下几点:
1.设置header头 Content-Type:application/octet-stream,Content-Disposition:attachment; filename=20221117_103544.csv

2.response 返回了数据


剩下的事情就是前端的问题

前端实现:如果是简单的 get 请求直接用html 的a标签,浏览器会根据响应的 header 头自动下载,如果是想用 ajax,fetch,或者axios 等,则需要先请求到数据,然后前端再模拟a标签才能实现下载

$("#query").click(function () {
        fetch("url", {
                headers: {
                'Authorization': 'Bearer xxx',
                'content-type': 'application/json'
                },
                method: 'POST', 
                mode: 'cors', 
            })
            .then(response => {
                fileName = response.headers.get("Content-Disposition").split(';')[1].split('=')[1].replace(/\"/g, '')
                result = response.blob()
                console.log(fileName)
                console.log(result)
                downloadFile(result, fileName);
            })
    })
//下载文件,我也是网上抄来的,其实这是属于前端的知识
function downloadFile(res, fileName) { // res为后端传来的文件流,// fileName为文件名称,自己根据实际情况赋值
        if (!res) {
            return
        };
        if (window.navigator.msSaveBlob) { // IE以及IE内核的浏览器
            try {
                window.navigator.msSaveBlob(res, fileName) // res为接口返回数据,这里请求的时候已经处理了,如果没处理需要在此之前自行处理var data = new Blob([res.data]) 注意这里需要是数组形式的,fileName就是下载之后的文件名
                // window.navigator.msSaveOrOpenBlob(res, fileName);  //此方法类似上面的方法,区别可自行百度
            } catch (e) {
                console.log(e)
            }
        } else {
            let url = window.URL.createObjectURL(new Blob([res], {
                type: 'text/plain;charset=uft-8' // 前后端一定要统一utf-8编码,否则会乱码
            }));
            let link = document.createElement('a')
            link.style.display = 'none'
            link.href = url
            link.setAttribute('download', fileName) // 文件名
            document.body.appendChild(link)
            link.click()
            document.body.removeChild(link) // 下载完成移除元素
            window.URL.revokeObjectURL(url) // 释放掉blob对象
        }
    };

go 的服务端的代码也非常简单:

        buffer := &bytes.Buffer{}
    //buffer.WriteString("xEFxBBxBF") 这个会原样输出到文件中
        buffer.WriteString("\xEF\xBB\xBF") //这个是不可见字符
    writer := csv.NewWriter(buffer)
    writer.Write([]string{"编号", "姓名", "年龄"})
    writer.Write([]string{"1", "张三", "23"})

    writer.Flush() // 此时才会将缓冲区数据写入

    fileName := time.Now().Format("2006-01-02 15:04") + ".csv"
    c.Header("Content-Type", "application/octet-stream")
    c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", fileName))

    c.Data(http.StatusOK, "text/csv", buffer.Bytes())

这段代码看着很简单,可是我改了很久,因为我用两种方式测试都没有达到我想要的效果,一个是1.apipost 服务端返回200,但response 没有数据,只有发送并保存才有期望的文件保存下来
2.我自己写了个测试文件,用ajax 方式来请求,在response 里有数据返回,可以没有文件下载下来

这两种都不是我想要的,因此我想着应该是我的代码错了,然后在服务端生成了csv文件并把这个文件通过c.File()方法发送到前端,经测试结果还是跟之前一样,所以我没有办法了,最后没有办法,我抱着试试看的心态把 response 的header头,Content-Type: application/octet-stream 拿去搜,结果真让我搜到了,具体详见:https://new.qq.com/rain/a/20210610A0A8W400
最后把问题解决了,我想起以前写 php 时,那时候是前后端不分离的,确实是用的html 的 a 标签来实现的,原来一个 下载文件涉及到浏览器的默认行为!!!这体现了基础知识的用处。