java对MultipartFile操作,文件下载

发布于:2025-09-10 ⋅ 阅读:(19) ⋅ 点赞:(0)

1、把MultipartFile转File

/**
     * 通过IO流转换(避免临时文件残留)
     *
     * @param multipartFile 上传文件对象
     * @return 转换后的File对象
     * @throws IOException
     */
    public static File convertByStream(MultipartFile multipartFile) throws IOException {
        InputStream inputStream = multipartFile.getInputStream();
        File file = new File(multipartFile.getOriginalFilename());
        try (OutputStream os = new FileOutputStream(file)) {
            byte[] buffer = new byte[8192];
            int bytesRead;
            while ((bytesRead = inputStream.read(buffer)) != -1) {
                os.write(buffer, 0, bytesRead);
            }
        }
        inputStream.close();
        return file;
    }

2、把MultipartFile切割成多个小文件

把一个大文件切割成很多个小文件时用到。
下面例子是按照每个文件50KB切割。

/**
     * 切割MultipartFile
     *
     * @param file
     * @param chunkSize
     * @return
     * @throws IOException
     */
    private static final Integer chunkSize = 5 * 1024*10; // 50KB分片
    
    public static List<MultipartFile> splitMultipartFile(MultipartFile file, int chunkSize) throws IOException {
        List<MultipartFile> chunks = new ArrayList<>();
        byte[] buffer = new byte[chunkSize];

        try (InputStream inputStream = file.getInputStream()) {
            int bytesRead;
            int chunkIndex = 0;

            while ((bytesRead = inputStream.read(buffer)) != -1) {
                byte[] chunkData = Arrays.copyOf(buffer, bytesRead);
                MultipartFile chunk = new MockMultipartFile(
                        "chunk_" + chunkIndex,
                        file.getOriginalFilename() + ".part" + chunkIndex,
                        file.getContentType(),
                        chunkData
                );
                chunks.add(chunk);
                chunkIndex++;
            }
        }
        return chunks;
    }

3、MultipartFile切割的小文件保存到磁盘

private static final Integer chunkSize = 5 * 1024 * 10; // 50KB分片
private static final String destDir = "D:\\merge"; // 分片后文件保存目录
    /**
     * 对上传的文件进行切割
     * @param file
     * @throws Exception
     */
    @PostMapping("/fileCutting")
    public void fileCutting(MultipartFile file) throws Exception {
        File sourceFile = convertByStream(file);
        
        try (RandomAccessFile raf = new RandomAccessFile(sourceFile, "r");
             FileChannel channel = raf.getChannel()) {

            long fileSize = sourceFile.length();
            int chunkCount = (int) Math.ceil((double) fileSize / chunkSize);
            ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

            for (int i = 0; i < chunkCount; i++) {
                final int index = i;
                executor.execute(() -> {
                    try {
                        long startPos = (long) index * chunkSize;
                        long endPos = Math.min(startPos + chunkSize, fileSize);
                        String chunkPath = destDir + "/" + sourceFile.getName() + ".part" + index;

                        try (FileOutputStream fos = new FileOutputStream(chunkPath);
                             FileChannel outChannel = fos.getChannel()) {
                            channel.transferTo(startPos, endPos - startPos, outChannel);
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                });
            }
            executor.shutdown();
            executor.awaitTermination(1, TimeUnit.HOURS);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

4、切割后的文件进行合并

    private static final String destDir = "D:\\merge"; // 分片后文件保存目录

    @PostMapping("/fileMerger")
    public void fileMerger() throws Exception {
        File dir = new File(destDir);
        List<File> chunkFiles = Arrays.asList(dir.listFiles());
        //指定生成的文件路径
        File outputFile = new File("D:\\merge\\xxx.pdf");
        /**
         * 按文件名自然排序
         * 这个地方一定要按照切割文件的顺序
         */
        chunkFiles.sort(Comparator.comparing(File::getName));
        ExecutorService executor = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
        try (FileOutputStream fos = new FileOutputStream(outputFile);
             BufferedOutputStream bos = new BufferedOutputStream(fos)) {

            List<Future<Integer>> futures = new ArrayList<>();
            for (File chunk : chunkFiles) {
                futures.add(executor.submit(new MergeTask(chunk, bos)));
            }

            // 等待所有任务完成
            for (Future<Integer> future : futures) {
                future.get();
            }
        } catch (ExecutionException e) {
            throw new RuntimeException(e);
        } finally {
            executor.shutdown();
        }
    }

    private static class MergeTask implements Callable<Integer> {
        private final File chunkFile;
        private final BufferedOutputStream outputStream;

        MergeTask(File chunkFile, BufferedOutputStream outputStream) {
            this.chunkFile = chunkFile;
            this.outputStream = outputStream;
        }

        @Override
        public Integer call() throws Exception {
            byte[] buffer = new byte[BUFFER_SIZE];
            int bytesRead;
            try (FileInputStream fis = new FileInputStream(chunkFile);
                 BufferedInputStream bis = new BufferedInputStream(fis)) {

                while ((bytesRead = bis.read(buffer)) != -1) {
                    synchronized (outputStream) {
                        outputStream.write(buffer, 0, bytesRead);
                    }
                }
            }
            return (int) chunkFile.length();
        }
    }

5、SpringBoot实现文件下载

/**
     * @param response
     * @功能描述 下载文件:将输入流中的数据循环写入到响应输出流中,而不是一次性读取到内存
     */
    @RequestMapping("/downloadLocal")
    public void downloadLocal(HttpServletResponse response) throws IOException {
        //要下载的文件
        File file = new File("D:\\xxx.docx");
        // 读到流中
        InputStream inputStream = new FileInputStream(file);// 文件的存放路径
        response.reset();
        response.setContentType("application/octet-stream");
        String filename = file.getName();
        //Content-Disposition的作用:告知浏览器以何种方式显示响应返回的文件,用浏览器打开还是以附件的形式下载到本地保存
        //attachment 表示以附件方式下载   inline 表示在线打开
        //filename表示文件的默认名称,因为网络传输只支持URL编码的相关支付,因此需要将文件名URL编码后进行传输,前端收到后需要反编码才能获取到真正的名称
        response.addHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(filename, "UTF-8"));
        // 告知浏览器文件的大小
        response.addHeader("Content-Length", "" + file.length());
        ServletOutputStream outputStream = response.getOutputStream();
        byte[] b = new byte[1024];
        int len;
        //从输入流中读取一定数量的字节,并将其存储在缓冲区字节数组中,读到末尾返回-1
        while ((len = inputStream.read(b)) > 0) {
            outputStream.write(b, 0, len);
        }
        inputStream.close();
        outputStream.close();
    }

网站公告

今日签到

点亮在社区的每一天
去签到