分片上传
private Map<String, List<File>> chunksMap = new ConcurrentHashMap<>();
public void upload(PartFile fileInfo, MultipartFile chunk) {
//
int currentChunk = fileInfo.getCurrentChunk();
int totalChunks = fileInfo.getTotalChunks();
String fileName = fileInfo.getFileName();
String chunkName = chunk.getOriginalFilename() + '.' + currentChunk;
File chunkFile = new File(uploadCachePath, chunkName);
try (BufferedInputStream bis = new BufferedInputStream(chunk.getInputStream());
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(chunkFile))) {
byte[] buffer = new byte[1024];
int i;
while ((i = bis.read(buffer)) != -1) {
bos.write(buffer, 0, i);
}
bos.flush();
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException('分片文件写入异常');
}
List<File> chunkList = chunksMap.computeIfAbsent(fileName, k -> new ArrayList<>(totalChunks));
chunkList.add(chunkFile);
}
1、根据文件信息(PartFile)获取当前分片的索引 (currentChunk)、总分片数 (totalChunks)、文件名 (fileName)。
2、构建当前分片的文件名(chunkName),将原始文件名和当前分片索引连接起来,形成新的文件名。
3、创建用于写入当前分片文件的 File 对象 (chunkFile),该文件位于指定的上传缓存路径和构建的文件名。
4、使用 BufferedInputStream 和 BufferedOutputStream 分别创建输入和输出流对象,用于读取分片文件内容并写入到指定文件中。
5、循环读取 bis(分片文件输入流)中的数据到 buffer 字节数组中,返回实际读取的字节数 i,如果读取到文件末尾则返回 -1。
6、将 buffer 中从 0 到 i 的字节写入 bos(分片文件输出流)中。
7、循环执行步骤 5 和 6,直到读取完整个分片文件。
8、刷新并关闭 bos,确保数据被写入磁盘。
9、如果在文件写入过程中发生 IOException 异常,打印异常信息并抛出一个 RuntimeException 异常。
10、从 chunksMap 中获取与当前文件名 (fileName) 对应的分片文件列表 (chunkList),如果分片文件列表不存在,则通过 computeIfAbsent 创建一个空的 ArrayList 并将其与当前文件名关联。
11、将当前写入完成的分片文件 (chunkFile) 添加到分片文件列表中供合并使用。
分片合并
public String merge(String uploadPath, String fileName) throws IOException {
List<File> chunkList = chunksMap.get(fileName);
if (chunkList == null || chunkList.size() == 0) {
throw new RuntimeException('分片不存在');
}
File outputFile = new File(uploadPath + '/' + fileName);
if (!outputFile.getParentFile().exists()) outputFile.getParentFile().mkdirs();
if (!outputFile.exists()) outputFile.createNewFile();
try (FileChannel outChannel = new FileOutputStream(outputFile).getChannel()) {
for (File file : chunkList) {
try (FileChannel inChannel = new FileInputStream(file).getChannel()) {
inChannel.transferTo(0, inChannel.size(), outChannel);
}
file.delete();
}
}
chunksMap.remove(fileName);
return outputFile.getCanonicalPath();
}
1、这段代码是一个用于合并文件分片的方法。它接收两个参数:uploadPath是文件上传的路径,fileName是要合并的文件名。
2、首先,代码通过chunksMap获取文件分片对应的列表chunkList,如果chunkList为空或者没有任何分片,就抛出一个运行时异常,提示"分片不存在"。
3、接下来,代码创建一个输出文件对象outputFile,路径为 uploadPath + ‘/’ + fileName。如果输出文件的父目录不存在,就创建该目录。如果输出文件本身不存在,就创建一个新的空文件。
4、然后,使用try-with-resources语句打开一个输出文件通道outChannel,通过FileOutputStream将输出文件与通道关联。之后开始循环遍历chunkList中的每个文件。
5、在每次循环中,代码打开一个输入文件通道inChannel,通过FileInputStream将当前分片文件与输入通道关联。然后使用inChannel.transferTo()方法将输入通道的内容传输到输出通道outChannel中,实现文件内容的合并。
6、完成一次合并后,代码删除当前分片文件。
7、循环结束后,代码从chunksMap中移除当前文件的分片列表。
8、最后,代码返回合并后的文件路径,即输出文件的规范路径。
需要注意的是,代码中抛出了IOException,因此在调用该方法时需要处理可能抛出的异常。
下载
public void download(HttpServletResponse response, String path) {
File file = new File(path);
response.setHeader('Access-Control-Allow-Origin', '*');
response.setHeader('Content-Disposition', 'attachment;filename=' + file.getName());
response.setContentType('application/octet-stream;charset=UTF-8');
response.setHeader('Access-Control-Expose-Headers', 'Content-Disposition,application/octet-stream');
if (!file.exists() || file.isDirectory()) throw new RuntimeException('文件不存在或者格式不支持下载');
try (BufferedOutputStream bos = new BufferedOutputStream(response.getOutputStream());
FileChannel channel = new FileInputStream(file).getChannel()) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
while (channel.read(buffer) != -1) {
buffer.flip();
bos.write(buffer.array(), 0, buffer.limit());
buffer.clear();
}
bos.flush();
} catch (Exception e) {
e.printStackTrace();
}
}
1、这段代码是一个用于文件下载的方法。它接收两个参数:response是HTTP响应对象,path是要下载的文件路径。
2、代码首先根据文件路径创建一个文件对象file。然后设置HTTP响应头中的一些参数,包括允许跨域访问、设置文件名、设置内容类型等。
3、接着,如果文件不存在或者是一个目录,就抛出一个运行时异常,提示"文件不存在或者格式不支持下载"。
4、最后,代码使用try-with-resources语句打开一个输出流bos和一个输入通道channel。输出流与response对象的输出流关联,使得文件的内容可以通过HTTP响应返回给客户端;输入通道与文件对象关联,以便从文件中读取数据。
5、在一个循环内部,代码使用ByteBuffer对象暂存每次从输入通道中读取的数据,判断是否读到了文件的末尾,如果没有就将数据写入输出流中,随后清空ByteBuffer以便下一次读取数据。最后刷新输出流,将缓冲区中的数据全部发送到客户端。
需要注意的是,一定要设置HTTP响应头中的一些参数,否则不能下载成功。
预览图片
public ResponseEntity<byte[]> previewImage(String path) throws IOException {
File imageFile = new File(path);
byte[] imageData = new byte[(int) imageFile.length()];
FileInputStream fis = new FileInputStream(imageFile);
int byteRead = fis.read(imageData);
fis.close();
String formatName = null;
try(MemoryCacheImageInputStream inputStream = new MemoryCacheImageInputStream(new ByteArrayInputStream(imageData))) {
Iterator<ImageReader> iterator = ImageIO.getImageReaders(inputStream);
if (iterator.hasNext()) {
ImageReader reader = iterator.next();
formatName = reader.getFormatName();
}
}
HttpHeaders headers = new HttpHeaders();
if (formatName != null) {
headers.setContentType(MediaType.parseMediaType('image/' + formatName.toLowerCase()));
}
return new ResponseEntity<>(imageData, headers, HttpStatus.OK);
}
}
1、这段代码是一个用于预览图片的方法。它接收一个path参数,表示图片文件的路径,并返回一个ResponseEntity<byte[]>对象。
2、代码首先根据图片文件路径创建一个File对象imageFile,然后根据文件大小创建一个对应大小的字节数组imageData。
3、接下来,代码通过FileInputStream读取图片文件的内容,并将其保存到字节数组imageData中。读取完毕后,关闭输入流。
4、之后,代码使用MemoryCacheImageInputStream包装字节数组imageData,并通过ImageIO.getImageReaders()获取图片文件的格式。如果成功获取到图片格式,将其赋值给formatName。
5、接着,代码创建一个HttpHeaders对象headers,用于设置响应头部信息。如果成功获取到图片格式formatName,则设置响应的Content-Type为对应的媒体类型。
6、最后,代码使用ResponseEntity封装字节数组imageData、响应头部headers和HttpStatus.OK状态码,并返回该对象。
需要注意的是,要在webMvc的消息转换器中添加支持图片的转换器
//支持图片预览
ByteArrayHttpMessageConverter byteArrayHttpMessageConverter = new ByteArrayHttpMessageConverter();
List<MediaType> mediaTypes = new ArrayList<>();
mediaTypes.add(MediaType.IMAGE_JPEG);
mediaTypes.add(MediaType.IMAGE_PNG);
mediaTypes.add(MediaType.IMAGE_GIF);
byteArrayHttpMessageConverter.setSupportedMediaTypes(mediaTypes);
converters.add(byteArrayHttpMessageConverter);