Spring MVC 和 WebFlux 上传文件

news2024/10/7 8:26:10

WebFlux 上传文件

    • 1. 表单上传方式
      • 1.1 Spring MVC
      • 1.2 Spring WebFlux
    • 2. 二进制流
      • 2.1 Spring MVC
      • 2.2 Spring WebFlux

开发环境:jdk 11
WebFlux:jdk 8+

1. 表单上传方式

1.1 Spring MVC

  • multipart大小限制
spring:
  servlet:
    multipart:
      max-file-size: 512MB
      max-request-size: 1024MB

  • controller
	private static final Path BASE_PATH = Paths.get("./uploads");

	@PostMapping("/uploadForm")
    public String uploadForm(@RequestPart("files") MultipartFile[] files) throws IOException {
        for (MultipartFile file : files) {
            String fileName = UUID.randomUUID() + "-" + file.getOriginalFilename();
            final Path path = BASE_PATH.resolve(fileName);
            try (InputStream inputStream = file.getInputStream()) {
                Files.write(path, inputStream.readAllBytes());
            }
        }
        // 处理上传的二进制数据
        return "success";
    }

  • 客户端
    String url = "http://localhost:8080/upload";
    final File file = new File("D:\\test\\2023032201-205680e2622740b5bb888b7e4801ebf0.mp4");
    RestTemplate restTemplate = new RestTemplate();
    // 设置请求头和请求体
    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.MULTIPART_FORM_DATA);
    final Resource resource = new FileSystemResource(file);
    final MultiValueMap<String, Object> valueMap = new LinkedMultiValueMap<>();
    valueMap.add("files", resource);
    HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(valueMap, headers);
    ResponseEntity<String> responseEntity = restTemplate.postForEntity(url, requestEntity, String.class);
    System.out.println(responseEntity.getStatusCode());

1.2 Spring WebFlux

  • multipart大小限制
spring:
  codec:
    # DataBuffer方式
    max-in-memory-size: 512MB
  webflux:
    multipart:
      max-in-memory-size: 512MB
      # 默认非流式
      streaming: false

  • controller
    private static final Path BASE_PATH = Paths.get("./uploads");

    @PostMapping("/upload")
    public Mono<List<String>> upload(@RequestPart("files") Flux<FilePart> files) {
        return files.flatMap(filePart -> {
            String fileName = UUID.randomUUID() + "-" + filePart.filename();
            final Path path = BASE_PATH.resolve(fileName);
            final File dest = path.toFile();
            return filePart.transferTo(dest).thenReturn(dest.getName());
        }).collectList();
    }

或者

    private static final Path BASE_PATH = Paths.get("./uploads");

	// 不推荐此种方式(文件名丢失)
    @PostMapping("/upload")
    public Mono<List<String>> upload(@RequestPart("files") Flux<DataBuffer> files) {
        return files.flatMap(dataBuffer -> {
            String fileName = UUID.randomUUID() + ".dat";
            final Path path = BASE_PATH.resolve(fileName);
            return DataBufferUtils.write(Mono.just(dataBuffer), path, StandardOpenOption.CREATE)
                    .then(Mono.just(fileName))
                    .onErrorResume(ex -> {
                        log.error("上传失败", ex);
                        return Mono.just("fail: " + ex.getMessage());
                    });
        }).collectList();
    }
  • 客户端
    String url = "http://localhost:8080/upload";
    final File file = new File("D:\\test\\2023032201-205680e2622740b5bb888b7e4801ebf0.mp4");
    final MediaType contentType = MediaType.MULTIPART_FORM_DATA;
    WebClient webClient = WebClient.builder().build();       
    webClient.post()
        .uri(url)
        .contentType(contentType)
        .body(BodyInserters.fromMultipartData("files", new FileSystemResource(file)))
        .exchangeToMono(response -> response.bodyToMono(String.class))
        .subscribe(System.out::println);

 	// 无限期等待结果(线上不允许使用)
    Thread.currentThread().join();

值得一说的是FilePartFormFieldPart均继承自Part,两者是平级关系。需要说明的是,客户端传输的org.springframework.core.io.Resource file part 对应的并不一定是 FilePart类型,也有可能是FormFieldPart,两者最主要的区别是,FilePart可以取到原始的文件名,FormFieldPart无法取得原始的文件名,这也是合理的,因为Resource有个派生类ByteArrayResource是基于内存的不存在文件名。

@see org.springframework.http.client.MultipartBodyBuilder

请添加图片描述
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sgI2MMRo-1683793805127)(WebFlux 上传文件.assets/image-20230510172435334.png)]

2. 二进制流

文件名参数的上传可以以post请求的url参数(注意)的形式进行上传,也可以在请求头header上传输。

URL参数编解码:

jsencodeURI(str)

javaURLEncoder.encode(str, "UTF-8")URLDecoder.decode(encodedStr, "UTF-8")

2.1 Spring MVC

  • 服务端
	private static final Path BASE_PATH = Paths.get("./uploads");

	@PostMapping("/upload")
    public String upload(@RequestBody Resource resource) throws IOException {
        // 此处Resource类型一定是ByteArrayResource,哪怕客户端传输的是FileSystemResource
        String fileName = UUID.randomUUID() + ".dat";
        final Path path = BASE_PATH.resolve(fileName);
        try (InputStream inputStream = resource.getInputStream()) {
            Files.write(path, inputStream.readAllBytes());
        }
        // 处理上传的二进制数据
        return "success";
    }

或者

	private static final Path BASE_PATH = Paths.get("./uploads");

	@PostMapping("/upload")
    public String upload(@RequestBody byte[] bytes) throws IOException {
        String fileName = UUID.randomUUID() + ".dat";
        final Path path = BASE_PATH.resolve(fileName);
        Files.write(path, bytes);
        // 处理上传的二进制数据
        return "success";
    }

使用HttpHeaders.CONTENT_DISPOSITION请求头传输文件名

@PostMapping("/upload")
    public String upload(HttpServletRequest request, @RequestBody Resource resource) throws IOException {
        String originalFilename = extractFileName(request);
        String fileName;
        if (Objects.isNull(originalFilename)) {
            fileName =  UUID.randomUUID() + ".dat";
        } else {
            fileName =  UUID.randomUUID() + "-" + originalFilename;
        }
        final Path path = BASE_PATH.resolve(fileName);
        try (InputStream inputStream = resource.getInputStream()) {
            Files.write(path, inputStream.readAllBytes());
        }
        // 处理上传的二进制数据
        return "success";
    }

    /**
     * 从请求头提取文件名
     *
     * @param request 请求对象
     * @return 文件名
     */
    private String extractFileName(HttpServletRequest request) {
        final String disposition = request.getHeader(HttpHeaders.CONTENT_DISPOSITION);
        if (Objects.nonNull(disposition)) {
            Pattern pattern = Pattern.compile("filename=\"(.+)\"");
            Matcher matcher = pattern.matcher(disposition);
            if(matcher.find()) {
                return matcher.group(1);
            }
        }
        return null;
    }
  • 客户端
    public static void main(String[] args) throws IOException {
        String url = "http://localhost:8080/upload";
        final File file = new File("D:\\test\\2023032201-205680e2622740b5bb888b7e4801ebf0.mp4");
        RestTemplate restTemplate = new RestTemplate();
        // 设置请求头和请求体
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
        headers.set(HttpHeaders.CONTENT_DISPOSITION, String.format("attachment; filename=\"%s\"", file.getName()));
        try (FileInputStream inputStream = new FileInputStream(file)) {
            HttpEntity<ByteArrayResource> requestEntity = new HttpEntity<>(new ByteArrayResource(inputStream.readAllBytes()), headers);
            ResponseEntity<String> responseEntity = restTemplate.postForEntity(url, requestEntity, String.class);
            System.out.println(responseEntity.getStatusCode());
        }
    }

值得一说的是MultipartFileResource均继承自InputStreamSource,两者是平级关系,MultipartFile用于接受表单文件参数,而Resource可用于接受二进制文件流。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YZSdIaoo-1683793805129)(WebFlux 上传文件.assets/image-20230511095308032.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YWipdn9L-1683793805130)(WebFlux 上传文件.assets/image-20230511094243404.png)]

2.2 Spring WebFlux

    @PostMapping("/upload")
    public Mono<String> upload(@RequestBody Mono<Resource> resourceMono) {
        return resourceMono.flatMap(resource -> {
            String fileName = UUID.randomUUID() + ".dat";
            final Path path = BASE_PATH.resolve(fileName);
            try (InputStream inputStream = resource.getInputStream()) {
                Files.write(path, inputStream.readAllBytes());
            } catch (IOException e) {
                log.error("上传失败", e);
                return Mono.error(e);
            }
            return Mono.just(fileName);
        });
    }

或者

    @PostMapping("/upload")
    public Mono<String> upload(@RequestBody Mono<byte[]> bytesMono) {
        return bytesMono.flatMap(bytes -> {
            String fileName = UUID.randomUUID() + ".dat";
            final Path path = BASE_PATH.resolve(fileName);
            try {
                Files.write(path, bytes);
            } catch (IOException e) {
                log.error("上传失败", e);
                return Mono.error(e);
            }
            return Mono.just(fileName);
        });
    }
  • 客户端
    String url = "http://localhost:8080/upload";
    final File file = new File("D:\\test\\2023032201-205680e2622740b5bb888b7e4801ebf0.mp4");
    final MediaType contentType = MediaType.APPLICATION_OCTET_STREAM;
    WebClient webClient = WebClient.builder().build();
    try (final FileInputStream inputStream = new FileInputStream(file)) {
        final InputStreamResource resource = new InputStreamResource(inputStream);
        final String encode = URLEncoder.encode(file.getName(), Charset.defaultCharset());
        webClient.post()
            .uri(url)
            .contentType(contentType)
            .header(HttpHeaders.CONTENT_DISPOSITION, String.format("attachment; filename=\"%s\"", encode))
            .body(BodyInserters.fromResource(resource))
            .exchangeToMono(response -> response.bodyToMono(String.class))
            .subscribe(System.out::println);
        // 无限期等待结果(线上不允许使用)
        Thread.currentThread().join();
    }

使用HttpHeaders.CONTENT_DISPOSITION请求头传输文件名

    @PostMapping("/upload")
    public Mono<String> upload(@RequestBody Mono<byte[]> bytesMono, ServerHttpRequest request) {
        return bytesMono.flatMap(bytes -> {
            String originalFilename = extractFileName(request);
            String fileName;
            if (Objects.isNull(originalFilename)) {
                fileName =  UUID.randomUUID() + ".dat";
            } else {
                final String decode = URLDecoder.decode(originalFilename, Charset.defaultCharset());
                fileName =  UUID.randomUUID() + "-" + decode;
            }
            final Path path = BASE_PATH.resolve(fileName);
            try {
                Files.write(path, bytes);
            } catch (IOException e) {
                log.error("上传失败", e);
                return Mono.error(e);
            }
            return Mono.just(fileName);
        });
    }

    /**
     * 从请求头提取文件名
     *
     * @param request 请求对象
     * @return 文件名
     */
    private String extractFileName(ServerHttpRequest request) {
        final String disposition = request.getHeaders().getFirst(HttpHeaders.CONTENT_DISPOSITION);
        if (Objects.nonNull(disposition)) {
            Pattern pattern = Pattern.compile("filename=\"(.+)\"");
            Matcher matcher = pattern.matcher(disposition);
            if(matcher.find()) {
                return matcher.group(1);
            }
        }
        return null;
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/514068.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

IS220PPRFH1B输电线路的先导继电器保护

​ IS220PPRFH1B输电线路的先导继电器保护 导引线差动继电器是专为保护配电线路和输电线路而设计的高速继电器&#xff0c;主要用于40公里以内的短线路。它是开关设备继电器中速度最快的功率继电器&#xff0c;该方案的工作需要通信通道&#xff0c;以便它可以将系统电压和电流…

2023.05.11-使用纯CPU来运行RWKV大语言模型

1. 简介 使用CPU来运行C版本的RWKV rwkv.cpp 可以将 RWKV 原始模型的参数转化为 float16&#xff0c;并量化到 int4&#xff0c;可以在 CPU 上更快地运行&#xff0c;也可以节省更多的内存。 2. 下载项目 ## git clone --recursive https://github.com/saharNooby/rwkv.cpp…

PostgreSQL11 | 视图

上一篇讲了索引&#xff0c;索引提高了表查询的速度&#xff0c;这一篇讲视图。 视图 视图&#xff0c;数据库中的一个虚拟表。 目录 视图 视图概述 前期准备 创建视图 单表视图 多表视图 查询视图 删除视图 视图概述 视图同真实表一样具有表的功能&#xff0c;但是…

spring事务失效的12种场景

前言 对于从事java开发工作的同学来说&#xff0c;spring的事务肯定再熟悉不过了。 在某些业务场景下&#xff0c;如果一个请求中&#xff0c;需要同时写入多张表的数据。为了保证操作的原子性&#xff08;要么同时成功&#xff0c;要么同时失败&#xff09;&#xff0c;避免…

H. Binary Craziness

题目链接 2023 Hubei Provincial Collegiate Programming Contest Examples input 6 6 1 3 2 3 1 4 2 5 3 6 4 6 output 30 input 6 4 1 2 3 5 2 4 3 6 output 0 题目大意&#xff1a; 给出结点个数 n n n和边的个数 m m m 下面依此给出 m m m个边&#xff0c;边是无向的&am…

Linux三种网络模式 | 仅主机、桥接、NAT

&#x1f497;wei_shuo的个人主页 &#x1f4ab;wei_shuo的学习社区 &#x1f310;Hello World &#xff01; Linux三种网络模式 仅主机模式&#xff1a;虚拟机只能访问物理机&#xff0c;不能上网 桥接模式&#xff1a;虚拟机和物理机连接同一网络&#xff0c;虚拟机和物理机…

字典核心底层原理

字典对象的核心是散列表。散列表是一个稀疏数组&#xff08;总是有空白元素的数组&#xff09;&#xff0c;数组的每个单元叫做bucket。每个bucket有两部分&#xff1a;一个是键对象的引用&#xff0c;一个是值对象的引用。 由于&#xff0c;所有bucket结构和大小一致&#xf…

服装厂的管理系统如何选?内行人:这4点一定要注意!

服装厂管理水平偏低&#xff0c;耗费大量时间和资金成本&#xff0c;导致利润越来越低&#xff0c;是现在很多中小服装厂普遍面临的痛点。 依靠传统的管理模式&#xff0c;口头询问生产进度&#xff0c;手写统计数量&#xff0c;很显然不适合现代工厂的管理模式&#xff0c;服装…

java开发记录V1

编辑器vscode 在vscode中安装配置springboot 下载安装jdk oracle jdk BellSoft Liberica JDK version 17 在vscode编辑器中安装相关组件Extension Pack for Java、Spring Boot Extension Pack、Spring Initializr Java Support 创建springboot项目&#xff1a;ctrlshiftp后…

【Midjourney】Midjourney 辅助工具 ① ( 自定义命令工具 | 设置描述词 | 设置风格 | 设置灯光 | 设置相机 | 设置艺术家 )

文章目录 一、Midjourney Prompt Tool 自定义命令工具1、设置描述词2、设置风格3、设置灯光4、设置相机参数5、设置艺术家参数 Midjourney 提示词命令 可以使用 辅助工具 进行生成 , 辅助工具如下 : Midjourney Prompt Tool 自定义命令工具Midjourney Prompt Generator 命令生…

软件测试踏入这三个误区,就离滚蛋不远了

误区一&#xff1a;测试都是女生&#xff0c;男生不适合 误区二&#xff1a;这个职位很简单&#xff0c;不需要很多技术含量&#xff0c;每天很闲 误区三&#xff1a;起步即巅峰&#xff0c;薪资提升空间不大&#xff0c;一线才八九千 如果你听到这样的言论&#xff0c;赶紧走…

AssetBundle加载与卸载时的内存变化

AssetBundle.LoadFromFile加载一个80MB的assetbundle会分配1MB左右的pss内存 adb分析&#xff1a;private-otherUnityProfiler分析&#xff1a;有3块 1.Other/AssetBundle/LoadingCache 2.Other/SerializedFile/archive:/CAB-e42axxxxxxx 3.NotSaved/AssetBundle/xxxxxx.ab …

陷入“产品纠结”的王振滔,与学不来波司登的奥康

文|螳螂观察 作者| 青月 曾经的一代“鞋王”奥康&#xff0c;正在走下神坛。 4月底&#xff0c;奥康国际披露了2022年的年报&#xff0c;数据显示&#xff0c;公司归母净利润亏损3.7亿元&#xff0c;同比下滑1185.93%。此外&#xff0c;公司年报还被出具了保留意见的审计报告…

Revit创建装饰纹路柱及CAD生成柱

一、Revit中如何创建装饰纹路的柱子 在罗马柱的外观中&#xff0c;很少存在圆滑的柱身&#xff0c;在Revit中&#xff0c;可以用阵列的方式&#xff0c;更快的装饰柱子。 在族样板中&#xff0c;采用拉伸的方式先创建一个柱子 采用空心拉伸为柱子绘制花纹 选择圆形工具绘制&…

C语言的数据类型

数据类型 变量和常量 常量的2种定义方式&#xff1a; 1、关键字const const 数据类型 常量名 值; 如&#xff1a;const float pi 3.14159; 2、宏定义 #define 常量名 值 PS: 这里没有"“和”;" 如&#xff1a;#define PI 3.14159 推荐用宏定义的方式定义常量。 整…

Thradlocal底层原理

java引用&#xff1a;强软弱虚 软引用空间不足时会被回收 软引用非常适合做缓存 弱引用&#xff1a;只要有垃圾回收&#xff0c;就会被回收 虚引用回不回收都拿不到 他只有一个作用&#xff0c;管理直接内存 也是只要有垃圾回收就会被回收 ThreadLocal Spring事务&#x…

【职场新人备忘录】新人职场生存指南:快速适应、持续成长和个人提升

新人职场生存指南&#xff1a;快速适应、持续成长和个人提升 引言 职场对于新人来说充满了新的挑战和机遇。作为一名新人&#xff0c;如何在职场中快速适应、获得成长和提升自己是至关重要的技能。本备忘录旨在为职场新人提供实用的职场tips&#xff0c;帮助他们在职场中取得…

Ubuntu 20.04 安装 mysql 并配置远程访问

文章目录 一、使用 apt-get 安装 mysql 服务二、初始化 mysql 数据库管理员用户密码三、配置远程访问 一、使用 apt-get 安装 mysql 服务 # 更新软件源 apt-get install update# 安装mysql服务 apt-get install mysql-server# 使用mysqladmin工具查看mysql版本 mysqladmin --v…

Set和Map学习笔记

参考链接&#xff1a;https://blog.csdn.net/weixin_43359799/article/details/123131917 Set 集合&#xff0c;存储的是value值,以对象形式存储,并且不会存储重复的值&#xff0c;可以用来数组去重。 const s new Set() [1, 2, 3, 4, 3, 2, 1].forEach(x > s.add(x)) c…

景区户外剧本杀小程序冲关软件

景区户外剧本杀小程序具有以下几个方面的市场前景&#xff1a; 旅游市场需求增加&#xff1a;随着人们对于旅游方式的多样化需求增加&#xff0c;景区户外剧本杀作为一种互动性强、参与感强的旅游体验项目&#xff0c;将会得到越来越多游客的喜爱和关注。 移动互联网应…