所以我在我的服务器上使用 Java,在客户端上使用 Angular。我目前正在开发一项功能,您可以从表格中选择多个文件,当您按下下载时,它会生成一个 zip 文件并将其下载到您的浏览器。截至目前,服务器现在创建了 zip 文件,我可以在服务器文件中访问它。剩下要做的就是让它在客户端的浏览器上下载。 (zip文件在客户端下载后被删除)
经过一些研究,我发现您可以使用 fileOutputStream 来执行此操作。我还看到了一些类似改造的工具......我正在使用 REST,这就是我的代码的样子。我将如何尽可能简单地实现我的目标?
Angular
httpGetDownloadZip(target: string[]): Observable<ServerAnswer> {
const params = new HttpParams().set('target', String(target)).set('numberOfFiles', String(target.length));
const headers = new HttpHeaders().set('token', this.tokenService.getStorageToken());
const options = {
headers,
params,
};
return this.http
.get<ServerAnswer>(this.BASE_URL + '/files/downloadZip', options)
.pipe(catchError(this.handleError<ServerAnswer>('httpGetZip')));
}
Java压缩方法
public void getDownloadZip(String[] files, String folderName) throws IOException {
[...] // The method is huge but basically I generate a folder called "Download/" in the server
// Zipping the "Download/" folder
ZipUtil.pack(new File("Download"), new File("selected-files.zip"));
// what do I return ???
return;
}
Java 上下文
server.createContext("/files/downloadZip", new HttpHandler() {
@Override
public void handle(HttpExchange exchange) throws IOException {
if (!handleTokenPreflight(exchange)) { return; }
System.out.println(exchange.getRequestURI());
Map<String, String> queryParam = parseQueryParam(exchange.getRequestURI().getQuery());
String authToken = exchange.getRequestHeaders().getFirst("token");
String target = queryParam.get("target") + ",";
String[] files = new String[Integer.parseInt(queryParam.get("numberOfFiles"))];
[...] // I process the data in this entire method and send it to the previous method that creates a zip
Controller.getDownloadZip(files, folderName);
// what do I return to download the file on the client's browser ????
return;
}
});
最佳答案
成功下载 zip 文件的可能方法如下文所述。
首先,考虑在您的 downloadZip
方法中返回对作为压缩结果获得的 zip 文件的引用:
public File getDownloadZip(String[] files, String folderName) throws IOException {
[...] // The method is huge but basically I generate a folder called "Download/" in the server
// Zipping the "Download/" folder
File selectedFilesZipFile = new File("selected-files.zip")
ZipUtil.pack(new File("Download"), selectedFilesZipFile);
// return the zipped file obtained as result of the previous operation
return selectedFilesZipFile;
}
现在,修改您的 HttpHandler
以执行下载:
server.createContext("/files/downloadZip", new HttpHandler() {
@Override
public void handle(HttpExchange exchange) throws IOException {
if (!handleTokenPreflight(exchange)) { return; }
System.out.println(exchange.getRequestURI());
Map<String, String> queryParam = parseQueryParam(exchange.getRequestURI().getQuery());
String authToken = exchange.getRequestHeaders().getFirst("token");
String target = queryParam.get("target") + ",";
String[] files = new String[Integer.parseInt(queryParam.get("numberOfFiles"))];
[...] // I process the data in this entire method and send it to the previous method that creates a zip
// Get a reference to the zipped file
File selectedFilesZipFile = Controller.getDownloadZip(files, folderName);
// Set the appropiate Content-Type
exchange.getResponseHeaders().set("Content-Type", "application/zip");
// Optionally, if the file is downloaded in an anchor, set the appropiate content disposition
// exchange.getResponseHeaders().add("Content-Disposition", "attachment; filename=selected-files.zip");
// Download the file. I used java.nio.Files to copy the file contents, but please, feel free
// to use other option like java.io or the Commons-IO library, for instance
exchange.sendResponseHeaders(200, selectedFilesZipFile.length());
try (OutputStream responseBody = httpExchange.getResponseBody()) {
Files.copy(selectedFilesZipFile.toPath(), responseBody);
responseBody.flush();
}
}
});
现在的问题是如何处理Angular中的下载。
如前面代码中所建议的,如果资源是公共(public)的,或者您有办法管理您的安全 token ,例如,将其作为参数包含在 URL 中,一种可能的解决方案是不使用 Angular HttpClient
但带有 href
的 anchor 直接指向您曾经的后端处理程序方法。
如果您需要使用 Angular HttpClient
,也许要包含您的身份验证 token ,那么您可以尝试这个很棒的 SO question 中提出的方法.
首先,在您的 handler
中,将压缩文件内容编码为 Base64 以简化字节处理任务(在一般用例中,您通常可以从服务器返回一个包含文件的 JSON 对象内容和描述该内容的元数据,如内容类型等):
server.createContext("/files/downloadZip", new HttpHandler() {
@Override
public void handle(HttpExchange exchange) throws IOException {
if (!handleTokenPreflight(exchange)) { return; }
System.out.println(exchange.getRequestURI());
Map<String, String> queryParam = parseQueryParam(exchange.getRequestURI().getQuery());
String authToken = exchange.getRequestHeaders().getFirst("token");
String target = queryParam.get("target") + ",";
String[] files = new String[Integer.parseInt(queryParam.get("numberOfFiles"))];
[...] // I process the data in this entire method and send it to the previous method that creates a zip
// Get a reference to the zipped file
File selectedFilesZipFile = Controller.getDownloadZip(files, folderName);
// Set the appropiate Content-Type
exchange.getResponseHeaders().set("Content-Type", "application/zip");
// Download the file
byte[] fileContent = Files.readAllBytes(selectedFilesZipFile.toPath());
byte[] base64Data = Base64.getEncoder().encode(fileContent);
exchange.sendResponseHeaders(200, base64Data.length);
try (OutputStream responseBody = httpExchange.getResponseBody()) {
// Here I am using Commons-IO IOUtils: again, please, feel free to use other alternatives for writing
// the base64 data to the response outputstream
IOUtils.write(base64Data, responseBody);
responseBody.flush();
}
}
});
之后,在您的客户端 Angular 组件中使用以下代码来执行下载:
this.downloadService.httpGetDownloadZip(['target1','target2']).pipe(
tap((b64Data) => {
const blob = this.b64toBlob(b64Data, 'application/zip');
const blobUrl = URL.createObjectURL(blob);
window.open(blobUrl);
})
).subscribe()
如上述问题所示,b64toBlob
将如下所示:
private b64toBlob(b64Data: string, contentType = '', sliceSize = 512) {
const byteCharacters = atob(b64Data);
const byteArrays = [];
for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
const slice = byteCharacters.slice(offset, offset + sliceSize);
const byteNumbers = new Array(slice.length);
for (let i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i);
}
const byteArray = new Uint8Array(byteNumbers);
byteArrays.push(byteArray);
}
const blob = new Blob(byteArrays, {type: contentType});
return blob;
}
您可能需要稍微修改服务中的 httpGetDownloadZip
方法以考虑返回的 base 64 数据 - 基本上,将 ServerAnswer
更改为 string
作为返回信息类型:
httpGetDownloadZip(target: string[]): Observable<string> {
const params = new HttpParams().set('target', String(target)).set('numberOfFiles', String(target.length));
const headers = new HttpHeaders().set('token', this.tokenService.getStorageToken());
const options = {
headers,
params,
};
return this.http
.get<string>(this.BASE_URL + '/files/downloadZip', options)
.pipe(catchError(this.handleError<ServerAnswer>('httpGetZip')));
}
关于java - 从客户端浏览器 (REST) 上的服务器返回一个 zip(或任何文件),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/69367700/