Go开发上传REST API如何同时返回JSON统计数据与CSV/XLS失败文件
实现方案说明
HTTP协议本身不支持单个响应设置两个不同的Content-Type,所以不存在直接同时返回JSON和二进制文件的标准方法,但Go支持以下两种行业常用的替代实现,完全可以满足你的需求:
方案1:multipart/mixed 多部分响应(符合HTTP标准)
这个方案通过multipart/mixed类型的响应体,将JSON统计信息和文件拆分为两个独立的响应段返回,单次请求即可获取全部数据。
Go实现示例
package main import ( "encoding/csv" "encoding/json" "fmt" "mime/multipart" "net/http" ) type UploadStat struct { SuccessCount int `json:"success_count"` FailCount int `json:"fail_count"` } func uploadHandler(w http.ResponseWriter, r *http.Request) { // 模拟业务处理得到的统计数据和失败数据 stat := UploadStat{ SuccessCount: 92, FailCount: 8, } failRecords := [][]string{ {"行号", "错误原因"}, {"3", "手机号格式错误"}, {"7", "身份证号重复"}, // 剩余失败数据按需补充 } // 设置响应头为multipart/mixed,生成随机分隔符 mw := multipart.NewWriter(w) defer mw.Close() w.Header().Set("Content-Type", fmt.Sprintf("multipart/mixed; boundary=%s", mw.Boundary())) // 第一部分:写入JSON统计数据 jsonPart, err := mw.CreatePart(map[string][]string{ "Content-Type": {"application/json"}, }) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } if err := json.NewEncoder(jsonPart).Encode(stat); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } // 第二部分:写入CSV文件 csvPart, err := mw.CreatePart(map[string][]string{ "Content-Type": {"text/csv"}, "Content-Disposition": {`attachment; filename="fail_records.csv"`}, }) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } cw := csv.NewWriter(csvPart) if err := cw.WriteAll(failRecords); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } cw.Flush() } func main() { http.HandleFunc("/upload", uploadHandler) http.ListenAndServe(":8080", nil) }
适用场景
- 失败数据量较大的场景,不会额外增加数据体积
- 客户端支持解析multipart响应
方案2:Base64嵌入JSON响应
将生成的CSV/XLS文件内容编码为Base64字符串,和统计数据一起放到JSON结构体中返回,整个响应的Content-Type为application/json。
Go实现示例
package main import ( "encoding/base64" "encoding/csv" "encoding/json" "bytes" "net/http" ) type UploadResponse struct { SuccessCount int `json:"success_count"` FailCount int `json:"fail_count"` FailFileB64 string `json:"fail_file_base64"` // CSV/XLS文件的Base64编码 FileType string `json:"file_type"` // 标识文件是csv还是xls } func uploadHandler(w http.ResponseWriter, r *http.Request) { // 模拟业务数据 successCount := 92 failCount := 8 failRecords := [][]string{ {"行号", "错误原因"}, {"3", "手机号格式错误"}, {"7", "身份证号重复"}, } // 生成CSV文件到内存缓冲区 var buf bytes.Buffer cw := csv.NewWriter(&buf) _ = cw.WriteAll(failRecords) cw.Flush() // 编码为Base64 b64Str := base64.StdEncoding.EncodeToString(buf.Bytes()) // 构造响应返回 resp := UploadResponse{ SuccessCount: successCount, FailCount: failCount, FailFileB64: b64Str, FileType: "csv", } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(resp) } func main() { http.HandleFunc("/upload", uploadHandler) http.ListenAndServe(":8080", nil) }
适用场景
- 失败数据量较小的场景,实现成本极低
- 客户端不想处理复杂的multipart解析,直接拿到Base64即可生成文件下载
选型建议
如果上传失败的数据量通常小于10M,优先选择方案2,前后端实现都更简单;如果失败数据量很大,选择方案1避免Base64带来的额外33%体积开销。
内容的提问来源于stack exchange,提问作者Denny Sutedja
相关产品推荐
相关产品推荐

