如何使用TypeScript向REST API正确发送ZIP文件数据
问题根因
后端接收参数为null核心是三个问题:
- 前端发送请求时未和后端对齐Content-Type与参数序列化规则,后端无法按预期解析数据
- 手动拆分base64的逻辑存在冗余,未做非空判断,容易传递无效值
- 若后端默认按multipart表单格式接收文件,直接传纯base64字符串自然无法被正确识别
推荐实现方案(二进制直传,性能最优)
不要用dataURL格式读取base64内容,直接拿原始File对象走multipart/form-data格式上传,可省去30%左右的base64编解码开销,不会出现ZIP文件损坏问题,后端处理逻辑更简单。
前端TypeScript代码修正
- 直接存储原始文件对象,不需要等待组件读成base64
// 选中文件时直接存原始二进制对象 handleFileInputChange = (_event, file) => { this.setState({ filename: file.name, rawFile: file // 新增字段存原始文件 }); } // 清空时同步清理文件缓存 handleClear = _event => this.setState({ filename: '', rawFile: null, fileData: '' }); handleFileReadStarted = _fileHandle => this.setState({ isLoading: true }); handleFileReadFinished = _fileHandle => this.setState({ isLoading: false }); // 上传逻辑 saveFileData = (filename: string, rawFile: File) => { if (!rawFile) { this.setState({fileSavestatusText: "请先选择ZIP文件"}); return; } // 构造表单对象 const formData = new FormData(); formData.append("archive", rawFile, filename); // 字段名archive和后端保持一致即可 // 注意:发请求时不要手动设置Content-Type,浏览器会自动带上multipart/form-data和正确的boundary参数 API.sendZipFile(formData).then(response => { this.setState({ fileSavestatusText: response.data, }); }).catch(err => { this.setState({fileSavestatusText: `上传失败:${err.message}`}); }); };
- 调整组件配置,去掉不必要的base64读取逻辑
render(){ const { filename, rawFile, isLoading } = this.state; return ( <> <FileUpload id="archive-file" type="file" // 改为file模式,直接拿原始文件,不读base64 filename={filename} filenamePlaceholder="Drag and drop a file or upload one" onFileInputChange={this.handleFileInputChange} onReadStarted={this.handleFileReadStarted} onReadFinished={this.handleFileReadFinished} onClearClick={this.handleClear} hideDefaultPreview browseButtonText="Upload" height='425px' width='810px' > {filename && ( <div className="pf-u-m-md"> <FileUploadIcon size="md" /> Custom preview here for your file named {filename} </div> )} </FileUpload> <Button className={buttonstyle} variant="primary" onClick={() => this.saveFileData(this.state.filename, this.state.rawFile)}> Save </Button> </> ); }
type设为file后,不需要再监听
onDataChange/onTextChange事件读取文件内容,可减少不必要的内存占用。
备选方案(base64格式传输)
如果业务场景必须传递base64字符串,按如下逻辑修正:
saveFileData = (filename: string, fileData: string) => { if (!fileData || !fileData.startsWith("data:application/")) { this.setState({fileSavestatusText: "无效的文件数据"}); return; } // 拆分data头和base64内容,去掉无意义的循环 const [meta, base64Data] = fileData.split(","); const fileType = meta.match(/data:(.*?);base64/)?.[1] || "application/zip"; // 构造明确的JSON请求体,字段和后端对齐 const payload = { filename: filename, file_type: fileType, file_base64: base64Data }; // 该请求必须设置Content-Type: application/json头 API.sendFileNameAndFileData(payload).then(response => { this.setState({ fileSavestatusText: response.data, }); }); };
Golang后端接收示例
对应multipart上传方式(推荐)
直接解析表单文件存储到/temp目录即可:
func UploadZipHandler(w http.ResponseWriter, r *http.Request) { // 限制上传大小为1G,可按需调整 r.ParseMultipartForm(1 << 30) file, header, err := r.FormFile("archive") // 和前端formData字段名保持一致 if err != nil { http.Error(w, "接收文件失败", http.StatusBadRequest) return } defer file.Close() // 处理文件名,防止路径穿越 savePath := filepath.Join("/tmp", filepath.Base(header.Filename)) dst, err := os.Create(savePath) if err != nil { http.Error(w, "创建文件失败", http.StatusInternalServerError) return } defer dst.Close() // 写入文件 _, err = io.Copy(dst, file) if err != nil { http.Error(w, "存储文件失败", http.StatusInternalServerError) return } w.Write([]byte(fmt.Sprintf("文件上传成功,存储路径:%s", savePath))) }
对应base64上传方式
解析JSON后解码base64存储:
type ZipUploadReq struct { Filename string `json:"filename"` FileType string `json:"file_type"` FileBase64 string `json:"file_base64"` } func UploadZipBase64Handler(w http.ResponseWriter, r *http.Request) { var req ZipUploadReq err := json.NewDecoder(r.Body).Decode(&req) if err != nil { http.Error(w, "解析请求失败", http.StatusBadRequest) return } // 解码base64 fileData, err := base64.StdEncoding.DecodeString(req.FileBase64) if err != nil { http.Error(w, "base64解码失败", http.StatusBadRequest) return } savePath := filepath.Join("/tmp", filepath.Base(req.Filename)) err = os.WriteFile(savePath, fileData, 0644) if err != nil { http.Error(w, "存储文件失败", http.StatusInternalServerError) return } w.Write([]byte(fmt.Sprintf("文件上传成功,存储路径:%s", savePath))) }
避坑提醒
- 用multipart上传时绝对不要手动设置Content-Type头,手动设置会丢失boundary参数导致后端解析失败
- 存储文件时必须用
filepath.Base()处理文件名,防止恶意用户传带../的文件名穿越目录写入其他位置 - 如果用Nginx等反向代理,记得调整
client_max_body_size参数,避免大ZIP包被拦截 - 上传完成后可校验ZIP文件魔数(前2字节为
PK即0x50、0x4B),过滤损坏或格式错误的文件
内容的提问来源于stack exchange,提问作者Dhananjay Gahiwade
相关产品推荐
相关产品推荐

