And*_*ios 13 java filesystems zip web-applications
我需要从我的Web应用程序执行大量的文件下载.
显然,这是一个长期运行的行动(它将每年使用一次[-per-customer]),所以时间不是问题(除非它达到一些超时,但我可以通过创造某种形式的keepalive心跳).我知道如何创建一个隐藏的iframe并使用它content-disposition: attachment来尝试下载文件而不是在浏览器中打开它,以及如何实例化客户端 - 服务器通信以绘制进度表;
下载的实际大小(和文件数量)是未知的,但为了简单起见,我们实际上可以将其视为1GB,由100个文件组成,每个10MB.
由于这应该是一键操作,我的第一个想法是将所有文件分组,同时从动态生成的ZIP中从数据库中读取它们,然后要求用户保存ZIP.
问题是:在WebApp中从多个小字节数组创建大型存档时,最佳实践是什么,以及已知的缺点和陷阱是什么?
这可以随机分为:
And*_*ios 12
通过将每个BLOB从数据库直接流式传输到客户端的文件系统而创建的完全动态ZIP文件的启动示例.
经过大型档案测试,具有以下表现:
- 服务器磁盘空间成本:0兆字节
- 服务器RAM成本:
~xx兆字节.内存消耗是不可测试的(或者至少我不知道如何正确地执行它),因为我Runtime.getRuntime().freeMemory()在循环之前,期间和之后多次(通过使用)运行相同的例程得到了不同的,显然是随机的结果.但是,内存消耗低于使用byte [],这就足够了.
使用FileStreamDto.javaInputStream代替byte[]
public class FileStreamDto implements Serializable {
@Getter @Setter private String filename;
@Getter @Setter private InputStream inputStream;
}
Run Code Online (Sandbox Code Playgroud)
Java Servlet(或Struts2 Action)
/* Read the amount of data to be streamed from Database to File System,
summing the size of all Oracle's BLOB, PostgreSQL's ABYTE etc:
SELECT sum(length(my_blob_field)) FROM my_table WHERE my_conditions
*/
Long overallSize = getMyService().precalculateZipSize();
// Tell the browser is a ZIP
response.setContentType("application/zip");
// Tell the browser the filename, and that it needs to be downloaded instead of opened
response.addHeader("Content-Disposition", "attachment; filename=\"myArchive.zip\"");
// Tell the browser the overall size, so it can show a realistic progressbar
response.setHeader("Content-Length", String.valueOf(overallSize));
ServletOutputStream sos = response.getOutputStream();
ZipOutputStream zos = new ZipOutputStream(sos);
// Set-up a list of filenames to prevent duplicate entries
HashSet<String> entries = new HashSet<String>();
/* Read all the ID from the interested records in the database,
to query them later for the streams:
SELECT my_id FROM my_table WHERE my_conditions */
List<Long> allId = getMyService().loadAllId();
for (Long currentId : allId){
/* Load the record relative to the current ID:
SELECT my_filename, my_blob_field FROM my_table WHERE my_id = :currentId
Use resultset.getBinaryStream("my_blob_field") while mapping the BLOB column */
FileStreamDto fileStream = getMyService().loadFileStream(currentId);
// Create a zipEntry with a non-duplicate filename, and add it to the ZipOutputStream
ZipEntry zipEntry = new ZipEntry(getUniqueFileName(entries,fileStream.getFilename()));
zos.putNextEntry(zipEntry);
// Use Apache Commons to transfer the InputStream from the DB to the OutputStream
// on the File System; at this moment, your file is ALREADY being downloaded and growing
IOUtils.copy(fileStream.getInputStream(), zos);
zos.flush();
zos.closeEntry();
fileStream.getInputStream().close();
}
zos.close();
sos.close();
Run Code Online (Sandbox Code Playgroud)
用于处理重复条目的帮助方法
private String getUniqueFileName(HashSet<String> entries, String completeFileName){
if (entries.contains(completeFileName)){
int extPos = completeFileName.lastIndexOf('.');
String extension = extPos>0 ? completeFileName.substring(extPos) : "";
String partialFileName = extension.length()==0 ? completeFileName : completeFileName.substring(0,extPos);
int x=1;
while (entries.contains(completeFileName = partialFileName + "(" + x + ")" + extension))
x++;
}
entries.add(completeFileName);
return completeFileName;
}
Run Code Online (Sandbox Code Playgroud)
非常感谢@prunge为我提供直接流媒体的想法.
对于不能同时适合内存的大型内容,将内容从数据库流式传输到响应.
这种事实际上非常简单.您不需要AJAX或websockets,可以通过用户点击的简单链接来流式传输大型文件.现代浏览器拥有不错的下载管理器和自己的进度条 - 为什么重新发明轮子?
如果为此从头开始编写servlet,则可以访问数据库BLOB,获取其输入流并将内容复制到HTTP响应输出流.如果你有Apache Commons IO库,你可以使用IOUtils.copy(),否则你可以自己做.
可以使用ZipOutputStream动态创建ZIP文件.在响应输出流(从servlet或您的框架提供的任何内容)中创建其中一个,然后putNextEntry()首先使用数据库获取每个BLOB ,然后如前所述流式传输每个BLOB.
潜在的陷阱/问题:
ZipOutputStream如果这是一个问题,在创建时可能需要关闭/关闭压缩.