You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将MultipartFile转换为Mono<FilePart>?非响应式API场景

无临时文件实现MultipartFile到FilePart的流式转换

问题场景

现有响应式API定义如下,接收Mono<FilePart>作为参数:

Mono<FileType> processAndReturnType(Mono<FilePart> partFlux)

需要从非响应式API getType(MultipartFile partFile) 中调用该响应式接口,核心需求是将MultipartFile转换为Mono<FilePart>。原实现通过临时文件中转触发了安全告警,需要改为直接流式处理的方案。

优化后的实现方案

下面是无需临时文件的FilePart包装类实现,完全基于输入流流式处理:

package com.maersk.clm.naas.tenderfile.convertor;

import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.codec.multipart.FilePart;
import org.springframework.web.multipart.MultipartFile;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.lang.NonNull;
import reactor.core.scheduler.Schedulers;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Path;
import java.util.Objects;

/**
 * 基于MultipartFile实现的FilePart包装类,无临时文件依赖
 */
public class MultipartFilePart implements FilePart {

    private final MultipartFile file;

    public MultipartFilePart(MultipartFile file) {
        this.file = file;
    }

    @Override
    @NonNull
    public Flux<DataBuffer> content() {
        // 直接从MultipartFile的输入流生成DataBuffer流,无需临时文件
        return DataBufferUtils.readInputStream(
                () -> {
                    try {
                        return file.getInputStream();
                    } catch (IOException e) {
                        throw new UncheckedIOException(e);
                    }
                },
                new DefaultDataBufferFactory(),
                8192 // 缓冲区大小,可根据业务调整,8192为Spring默认值
        ).onErrorMap(UncheckedIOException.class, IOException::new);
    }

    @Override
    @NonNull
    public String filename() {
        return Objects.requireNonNull(file.getOriginalFilename());
    }

    @Override
    @NonNull
    public String name() {
        return file.getName();
    }

    @Override
    @NonNull
    public Mono<Void> transferTo(@NonNull Path dest) {
        // 直接流式写入目标路径,无需清理临时文件
        return DataBufferUtils.write(content(), dest)
                .then()
                .publishOn(Schedulers.boundedElastic());
    }

    @Override
    @NonNull
    public HttpHeaders headers() {
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.parseMediaType(Objects.requireNonNull(file.getContentType())));
        return headers;
    }
}

关键优化点说明

  1. 流式内容生成:核心修改content()方法,使用DataBufferUtils.readInputStream直接从MultipartFile的输入流创建Flux<DataBuffer>,彻底避免临时文件的创建,解决安全告警问题。
  2. 异常处理:将输入流获取时的IOException包装为UncheckedIOException(因为Supplier不允许抛出受检异常),再通过onErrorMap转换回IOException,保证响应式流的错误信号符合预期。
  3. 简化transferTo逻辑:移除原实现中临时文件的清理逻辑,因为现在没有临时文件需要处理,实现更简洁。
  4. 缓冲区大小:使用Spring默认的8192字节缓冲区,可根据大文件场景调整为更大的值(如16384)以提升性能。

调用示例

在非响应式API中,按如下方式调用响应式接口:

public FileType getType(MultipartFile partFile) {
    // 将MultipartFile包装为FilePart并转为Mono
    Mono<FilePart> filePartMono = Mono.just(new MultipartFilePart(partFile));
    // 阻塞获取响应式API结果(非响应式环境下合理操作)
    return processAndReturnType(filePartMono).block();
}

注意:如果业务允许,建议逐步将上层API改造为响应式,避免使用block()带来的线程阻塞问题。

内容的提问来源于stack exchange,提问作者Vivek

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 06:02:14