分片上传

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);