Google Drive API+Spring Boot+Angular实现文件夹Zip下载:解决无法解压问题
Google Drive文件夹下载生成的Zip无法解压问题解决
我需要通过Google Drive API下载指定文件夹内的所有文件,已知API不支持批量下载,因此采用遍历文件夹内文件逐个下载的方案。单个文件下载已实现,但将文件夹内容打包为Zip后,生成的文件无法解压。
当前代码
后端Spring Boot代码
Controller层
@GetMapping("/download-folder/{id}") public ResponseEntity<StreamingResponseBody> download(@PathVariable String id) throws GeneralSecurityException, IOException { return ResponseEntity.ok().contentType(MediaType.APPLICATION_OCTET_STREAM).body(googleDriveService.downloadFolder(id)); }
Service层
public StreamingResponseBody downloadFolder(String id) throws GeneralSecurityException, IOException { Drive drive = getService(); List<File> files = getAllFilesInFolder(id); StreamingResponseBody stream = output -> { files.forEach(file -> { ByteArrayOutputStream baos = new ByteArrayOutputStream(); try { drive.files().get(file.getId()).executeMediaAndDownloadTo(baos); output.write(baos.toByteArray()); output.flush(); } catch (IOException e) { log.error("Error when download file in folder ", e); } }); }; return stream; }
前端Angular代码
Service层
downloadFolder(id: string): Observable<Blob> { return this.http.get(`${this.resourceUrl}/download-folder/${id}`, { responseType: 'blob' }); }
Component层
downloadFolder(): void { this.documentService.downloadFolder(this.folderId!).subscribe(res => saveAs(res, this.folderName + '.zip')); }
问题原因
后端只是将多个文件的字节流直接拼接输出,没有遵循Zip格式的规范进行打包。前端把这种拼接后的字节流当作Zip文件保存,自然无法被解压工具识别为合法的Zip包。
解决方案
后端需要使用ZipOutputStream将每个下载的文件作为独立条目写入Zip包,生成符合规范的Zip流输出。
修改后后端Service层代码
public StreamingResponseBody downloadFolder(String id) throws GeneralSecurityException, IOException { Drive drive = getService(); List<File> files = getAllFilesInFolder(id); StreamingResponseBody stream = output -> { try (ZipOutputStream zipOut = new ZipOutputStream(output)) { for (File file : files) { // 创建Zip条目,使用文件名称作为条目名 ZipEntry zipEntry = new ZipEntry(file.getName()); zipOut.putNextEntry(zipEntry); // 直接将Google Drive下载的内容写入Zip流,无需中转ByteArrayOutputStream drive.files().get(file.getId()).executeMediaAndDownloadTo(zipOut); zipOut.closeEntry(); } zipOut.finish(); } catch (IOException e) { log.error("Error when packaging folder into zip ", e); throw e; } }; return stream; }
优化Controller层(可选)
明确指定响应的MIME类型为Zip,并可直接在后端设置下载文件名:
@GetMapping("/download-folder/{id}") public ResponseEntity<StreamingResponseBody> download(@PathVariable String id) throws GeneralSecurityException, IOException { // 假设getFolderName方法可根据文件夹ID获取名称 String folderName = getFolderName(id); return ResponseEntity.ok() .contentType(MediaType.parseMediaType("application/zip")) .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + folderName + ".zip\"") .body(googleDriveService.downloadFolder(id)); }
前端优化(可选)
如果后端设置了Content-Disposition头,可以通过响应头获取文件名,避免前端硬编码:
修改Service层返回类型
downloadFolder(id: string): Observable<HttpResponse<Blob>> { return this.http.get(`${this.resourceUrl}/download-folder/${id}`, { responseType: 'blob', observe: 'response' }); }
修改Component层逻辑
downloadFolder(): void { this.documentService.downloadFolder(this.folderId!).subscribe({ next: (response) => { let fileName = this.folderName + '.zip'; const contentDisposition = response.headers.get('Content-Disposition'); if (contentDisposition) { const match = contentDisposition.match(/filename="?([^"]+)"?/); if (match && match.length > 1) { fileName = match[1]; } } saveAs(response.body!, fileName); }, error: (error) => { console.error('文件夹下载失败', error); } }); }
内容的提问来源于stack exchange,提问作者MIM
相关产品推荐
相关产品推荐

