01_学习使用javax_ws_rs_上传文件
文章目录
- 1 前言
- 2 Maven 依赖
- 3 上传接口
- 4 如何解析 MultipartFormDataInput
- 5 结语
1 前言
使用 Spring MVC 来处理文件上传,想必是大家耳熟能详的了,如下代码:
@ResponseBody
@PostMapping("/upload")
public String upload(@RequestPart("file") MultipartFile file) throws IOException {appService.upload(file);return "Success";
}
😜 但是现在,如果我们不使用 Spring MVC , 而是使用 javax.ws.rs 下的注解,该如何实现文件上传呢?
2 Maven 依赖
<properties><jboss.resteasy.version>3.6.3.Final</jboss.resteasy.version>
</properties><dependencies><dependency><groupId>org.jboss.resteasy</groupId><artifactId>resteasy-client</artifactId><version>${jboss.resteasy.version}</version></dependency><dependency><groupId>org.jboss.resteasy</groupId><artifactId>resteasy-jackson2-provider</artifactId><version>${jboss.resteasy.version}</version></dependency><dependency><groupId>org.jboss.resteasy</groupId><artifactId>resteasy-jaxb-provider</artifactId><version>${jboss.resteasy.version}</version></dependency><dependency><groupId>org.jboss.resteasy</groupId><artifactId>resteasy-jaxrs</artifactId><version>${jboss.resteasy.version}</version></dependency><dependency><groupId>org.jboss.resteasy</groupId><artifactId>resteasy-netty</artifactId><version>${jboss.resteasy.version}</version></dependency><!-- resteasy 文件上传的依赖开始 --><dependency><groupId>org.jboss.resteasy</groupId><artifactId>resteasy-multipart-provider</artifactId><version>${jboss.resteasy.version}</version></dependency><!-- resteasy 文件上传的依赖结束 -->
</dependencies>
3 上传接口
import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataInput;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;@Path("/test")
public interface TestController {@POST@Path("upload")@Produces(MediaType.APPLICATION_JSON)@Consumes(MediaType.MULTIPART_FORM_DATA)JsonResult<String> upload(MultipartFormDataInput input);
}
4 如何解析 MultipartFormDataInput
从 MultipartFormDataInput 中应当可以解析出文件 或者其他 form-data 的字段。通过下面的 getDtoFromMultipartFormDataInput() 方法,我们就可以把多部件转换为指定类型的 dto 了。然后就可以进行 DAO 层操作了。
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.jboss.resteasy.plugins.providers.multipart.InputPart;
import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataInput;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;import javax.ws.rs.core.MultivaluedMap;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;@Slf4j
public class TestService {/*** multipart-form/data 的文件的 key 统一为 file*/private static final String FORM_FILE_KEY = "file";/*** 把 {@link MultipartFormDataInput} 转换为指定类型的 dto** @param input 多部件上传的参数, 里面可能有文件, 也可能只是普通的 key-value* @param clazz dto 的类型* @return dto*/public <T> T getDtoFromMultipartFormDataInput(MultipartFormDataInput input, Class<? extends T> clazz) {T dto;try {dto = clazz.newInstance();} catch (InstantiationException | IllegalAccessException e) {log.error("[把多部件转换为 dto] 无法实例化 dto, 类名: {}", clazz.getName());throw new RuntimeException("[把多部件转换为 dto] 无法实例化 dto, 类名: " + clazz.getName(), e);}Map<String, List<InputPart>> formDataMap = input.getFormDataMap();// 处理多部件中的文件, 可能需要解析文件, 也可能不需要, 全看 dto 里面有没有 file 字段this.handleFileFieldIfNeed(clazz, formDataMap, dto);// 解析 multipart/form-data 的其他字段this.handleNonFileFields(clazz, formDataMap, dto);return dto;}/*** 处理 dto 中 非文件类型的字段** @param clazz dto 的类型* @param formDataMap 多部件的内容* @param dto dto 实例, new 出来的* @param <T> 泛型, dto 的类型*/private <T> void handleNonFileFields(Class<? extends T> clazz, Map<String, List<InputPart>> formDataMap, T dto) {Field[] dtoFields = clazz.getDeclaredFields();for (Field dtoField : dtoFields) {String fieldName = dtoField.getName();if (!"file".equals(fieldName)) {// 处理非文件的字段List<InputPart> inputParts = formDataMap.get(fieldName);if (CollectionUtils.isEmpty(inputParts)) {log.warn("[把多部件转换为 dto] dto 里面不存在 multipart/form-data 中的字段: {}", fieldName);} else {// 只取第一个InputPart inputPart = inputParts.get(0);try {Object dtoFieldValue = inputPart.getBody(dtoField.getType(), null);boolean accessible = dtoField.isAccessible();if (!accessible) {dtoField.setAccessible(true);}dtoField.set(dto, dtoFieldValue);if (!accessible) {dtoField.setAccessible(false);}} catch (IOException | IllegalAccessException e) {log.error("[把多部件转换为 dto] dto 字段: " + fieldName + " 无法从 form-data 中获取值", e);throw new RuntimeException("[把多部件转换为 dto] dto 字段: " + fieldName + " 无法从 form-data 中获取值", e);}}}}}/*** 处理多部件中的文件, 可能需要解析文件, 也可能不需要, 全看 dto 里面有没有 file 字段** @param clazz dto 的类型* @param formDataMap 多部件的内容* @param dto dto 实例, new 出来的* @param <T> 泛型, dto 的类型*/private <T> void handleFileFieldIfNeed(Class<? extends T> clazz, Map<String, List<InputPart>> formDataMap, T dto) {try {Field fileField = clazz.getDeclaredField("file");if (File.class.equals(fileField.getType())) {// 如果 dto 里面有 file 这个字段, 而且 file 字段的类型是 java.io.File, 那么就开始解析// multipart/form-data 的内容, 解析出一个文件出来List<InputPart> inputParts = formDataMap.get(FORM_FILE_KEY);if (CollectionUtils.isEmpty(inputParts)) {throw new RuntimeException("[把多部件转换为 dto] 上传文件的 form-data 的 key 应该为 file");}// 一个 key "file", 只对应一个 文件InputPart inputPart = inputParts.get(0);// 解析文件名MultivaluedMap<String, String> headers = inputPart.getHeaders();String filename = this.getFilename(headers).orElseThrow(() -> new RuntimeException("[把多部件转换为 dto] 解析文件名称失败"));// 解析文件流try (InputStream is = inputPart.getBody(InputStream.class, null)) {// 先生成本地的临时文件File file = this.stream2file(is, filename);// 然后把这个文件设置到 dto 的 file 字段里面boolean fileFieldAccessible = fileField.isAccessible();if (!fileFieldAccessible) {fileField.setAccessible(true);}fileField.set(dto, file);if (!fileFieldAccessible) {fileField.setAccessible(false);}} catch (IOException e) {log.error("[把多部件转换为 dto] 获取文件输入流 或者把此输入流转换为字节数组失败", e);throw new RuntimeException("[把多部件转换为 dto] 获取文件输入流 或者把此输入流转换为字节数组失败");} catch (IllegalAccessException e) {log.error("[把多部件转换为 dto] 把 File 设置到 dto 的 file 字段时, 失败", e);throw new RuntimeException("[把多部件转换为 dto] 把 File 设置到 dto 的 file 字段时, 失败");}}} catch (NoSuchFieldException e) {// 没有名称为 file 的字段log.warn("[把多部件转换为 dto] dto 里面没有 file 字段. class:{}", clazz.getName());}}/*** 从 http 请求头中, 获取文件的名称** @param headers http 请求头* @return 文件的名称, 可能为空*/private Optional<String> getFilename(MultivaluedMap<String, String> headers) {String[] contentDispositionArr = headers.getFirst("Content-Disposition").split(";");for (String contentDisposition : contentDispositionArr) {if (contentDisposition.trim().startsWith("filename")) {String[] filenameArr = contentDisposition.split("=");String filename = filenameArr[1].trim().replaceAll("\"", "");if (!StringUtils.hasText(filename)) {// 文件名为空的话, 也是不可以的return Optional.empty();}String finalFilename = this.urlDecodeFilename(filename);return Optional.ofNullable(finalFilename);}}return Optional.empty();}/*** 把字节输入流 转换为 临时文件.* 这些临时文件要记得及时清除** @param is 字节输入流* @param filename 文件名* @return 临时文件*/private File stream2file(InputStream is, String filename) {File file = this.createTempFile(filename);try (FileOutputStream fos = new FileOutputStream(file)) {IOUtils.copy(is, fos);} catch (IOException e) {log.error("把输入流的内容拷贝到文件输出流 失败", e);}return file;}/*** 创建临时文件** @param filename 临时文件名, 不包含路径* @return 临时文件*/public File createTempFile(String filename) {// 统一的临时文件上传目录. 目录加了日期和uuid的区分String tmpParentDirPath = this.getTmpFileParentDirPath()+ File.separator;File file = new File(tmpParentDirPath + filename);File parentDir = file.getParentFile();if (!parentDir.exists()) {// 生成父目录//noinspection ResultOfMethodCallIgnoredparentDir.mkdirs();}if (!file.exists()) {try {//noinspection ResultOfMethodCallIgnoredfile.createNewFile();} catch (IOException e) {log.error("创建临时文件失败, 文件名: {}", file.getAbsolutePath());throw new RuntimeException("创建临时文件失败, 文件名: " + file.getAbsolutePath(), e);}}return file;}/*** 获取临时文件所在的父目录*/private String getTmpFileParentDirPath() {// 父目录的格式是 D:/年月日/uuidreturn "D:" + File.separator+ DateTimeUtil.formatString(new Date(), "yyyy-MM-dd")+ File.separator+ UUID.randomUUID().toString().replaceAll("-", "");}
}
这个 dto 可以是:
import java.io.File;@Data
public class MyTestDto {private File file;private String queryParamOne;private String queryParamTwo;
}
5 结语
感谢阅读~
相关文章:
01_学习使用javax_ws_rs_上传文件
文章目录 1 前言2 Maven 依赖3 上传接口4 如何解析 MultipartFormDataInput5 结语 1 前言 使用 Spring MVC 来处理文件上传,想必是大家耳熟能详的了,如下代码: ResponseBody PostMapping("/upload") public String upload(Request…...
MFC 发布CLXHHandleEngine动态库1.0.0.0版本
第一版发布以下功能,此项目使用VS2013创建,项目配置包括Unicode的Mdd,md与多字节版本: //MFC Grid表格 #include "../MFCGridCtrl/GridCtrl.h" //使用AES与Base64加密解密可以与java中的AES加解密衔接 //AES加密解密 #include &q…...
MicroPython 基于microdot框架搭建网页服务器
MicroPython 基于microdot框架搭建网页服务器 简介简单demo 简介 Microdot是一个极简的Python web框架,灵感来自于Flask,它被设计用来运行在资源有限的系统上,如微控制器。它运行在标准的Python和MicroPython上。 API参考microdot 资源下载m…...
FL Studio21.2汉化永久中文语言包
FL Studio21.2这款软件在国内被广泛使用,因此又被称为"水果"。它提供音符编辑器,可以针对作曲者的要求编辑出不同音律的节奏,例如鼓、镲、锣、钢琴、笛、大提琴、筝、扬琴等等任何乐器的节奏律动。此外,它还提供了方便快…...
Glide结合OkHttp保证短信验证接口携带图形验证码接口返回Cookie值去做网络请求
一、实现效果 二、步骤 注意:仅展示核心部分代码 1、导入依赖 api com.github.bumptech.glide:glide:4.10.0 kapt com.github.bumptech.glide:compiler:4.10.0 api com.squareup.okhttp3:okhttp:3.11.0 api com.squareup.okhttp3:logging-interceptor:3.11.02、自…...
怎样用Ajax提交from表单并接收其中的json数据
怎样用Ajax提交表单并接收其中的json数据 需求:实现点击按钮后,数据以表单形式提交至服务器,并接收来自服务器的返回数据。过程中页面不刷新。 AJAX 不是新的编程语言,而是一种使用现有标准的新方法。AJAX 是与服务器交换数据并…...
【动态规划】LeetCode-746LCR 088.使用最小花费爬楼梯
🎈算法那些事专栏说明:这是一个记录刷题日常的专栏,每个文章标题前都会写明这道题使用的算法。专栏每日计划至少更新1道题目,在这立下Flag🚩 🏠个人主页:Jammingpro 📕专栏链接&…...
Unity 接入TapADN播放广告时闪退 LZ4JavaSafeCompressor
通过跟踪安卓日志,发现报如下错误 Didnt find class "com.tapadn.lz4.LZ4JavaSafeCompressor" 解决方案: 去掉Minify这边的勾选,再打包即可。...
【九】linux下部署frp客户端服务端实践(内网穿透)
linux下部署frp客户端服务端实践 简介: 今天有一个这样的需求,部署在公司内部局域网虚拟机上的服务需要在外网能够访问到,这不就是内网穿透的需求吗,之前通过路由器实现过,现在公司这块路由器不具备这个功能了&#x…...
华为1+x网络系统建设与运维(中级)-练习题2
一.设备命令 LSW1 [Huawei]sys LSW1 同理可得,给所有设备改名 二.VLAN LSW1 [LSW1]vlan ba 10 20 [LSW1]int g0/0/1 [LSW1-GigabitEthernet0/0/1]port link-type trunk [LSW1-GigabitEthernet0/0/1]port trunk allow-pass vlan 10 20 [LSW1-GigabitEthernet0/0/1]in…...
自定义类型-结构体,联合体和枚举-C语言
引言 能看到结构体,说明C语言想必学习的时间也不少了,在之前肯定也学习过基本数据类型,包括整型int,浮点型float等等。可是在日常生活中,想要描述一个事物并没有那么简单。比如,你要描述一本书,…...
Windows 安装redis,设置开机自启动
Windows 安装redis,设置开机自启动 文章目录 Windows 安装redis,设置开机自启动下载, 解压到指定目录设置redis密码启动redis服务端停止redis服务端设置自启动 下载, 解压到指定目录 官网地址: https://redis.io/ 安装包下载地址: https://github.com/tporadowski/redis/relea…...
Windows安装Mysql Workbench及常用操作
Mysql Workbench是mysql自带的可视化操作界面,功能是强大的,但界面和navicat比,就是觉得别扭,但其实用惯了也还好,各有特色吧。这里记录一下常用的操作。 官方手册:MySQL Workbench 一、安装 1. 下载 官方…...
【计算机网络】15、NAT、NAPT 网络地址转换、打洞
文章目录 一、概念二、分类(主要是传统 NAT)2.1 基本 NAT2.2 NAPT 三、访问NAT下的内网设备的方式3.1 多拨3.2 端口转发、DMZ3.3 UPnP IGD、NAT-PMP3.4 服务器中转:frp 内网穿透3.4.1 NAT 打洞3.4.2 NAT 类型与打洞成功率3.4.2.1 完全圆锥形 …...
【送书活动三期】解决docker服务假死问题
工作中使用docker-compose部署容器,有时候会出现使用docker-compose stop或docker-compose down命令想停掉容器,但是依然无法停止或者一直卡顿在停止中的阶段,这种问题很让人头疼啊! 目录 问题描述问题排查问题解决终极杀招-最粗暴…...
【每日一题】拼车+【差分数组】
文章目录 Tag题目来源解题思路方法一:差分 写在最后 Tag 【差分数组】【数组】【2023-12-02】 题目来源 1094. 拼车 解题思路 本题朴素的解题思路是统计题目中提到的每一个站点的车上人数,如果某个站点的车上人数大于车上的座位数直接返回 false&…...
【开源】基于JAVA的农村物流配送系统
项目编号: S 024 ,文末获取源码。 \color{red}{项目编号:S024,文末获取源码。} 项目编号:S024,文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 系统登录、注册界面2.2 系统功能2.2…...
7、Jenkins+Nexus3+Docker+K8s实现CICD
文章目录 基本环境配置一、Jenkins安装必要插件二、Jenkins系统配置三、新建流水线四、在项目工程里添加Jenkinsfile、deploy.yml五、在项目工程里添加Dockerfile在这里插入图片描述 总结 提示:本章主要记录各基本环境搭建好后如何配置Jenkins流水线部署微服务到K8s…...
解决git action发布失败报错:Error: Resource not accessible by integration
现象: 网上说的解决方法都是什么到github个人中心setting里面的action设置里面去找。 可这玩意根本就没有! 正确解决办法: 在你的仓库页面,注意是仓库页面的setting里面: Actions> General>Workflow permisss…...
[传智杯 #2 决赛] 补刀
题目描述 UIM 在写程序的空闲玩一款 MOBA 游戏。 当敌方的小兵进入到我方防御塔的范围内,就会持续受到防御塔造成的伤害;当然我方英雄也可以对它造成伤害。当小兵的血量降到了 0 或者更低,就会被击杀。为了获得经验,UIM 希望在防…...
Vue3+TS+Vite项目实战:5分钟搞定Mock数据接入(附完整代码)
Vue3TSVite项目实战:5分钟实现动态权限Mock系统 最近在重构后台管理系统时,遇到一个典型痛点:前端页面都开发完了,后端接口还在设计中。这种前后端进度不匹配的情况,相信每个前端开发者都深有体会。今天分享的这套Mock…...
Windows DLL注入工具Xenos全攻略:从原理到实践的系统指南
Windows DLL注入工具Xenos全攻略:从原理到实践的系统指南 【免费下载链接】Xenos Windows dll injector 项目地址: https://gitcode.com/gh_mirrors/xe/Xenos 一、技术原理:Xenos注入引擎的底层架构 1.1 三级注入引擎的工作机制 Xenos作为专业的…...
MOVA割草机器人:开启自主决策新时代
随着AI感知技术在户外场景加速落地,MOVA率先推出AI双目视觉割草机器人ViAX系列,实现多传感器融合,让割草机迈入“自主决策时代”,全球销量快速增长。技术跃迁:从自动到自主 AI感知技术向户外场景渗透,割草机…...
【衢州学院主办,上海交通大学协办 | IET出版(有ISSN号) | 往届两年已完成 EI 、 IEEE Xplore检索 | 大咖组委】第三届人工智能与电力系统国际学术会议(AIPS 2026)
第三届人工智能与电力系统国际学术会议(AIPS 2026) 2026 3rd International Conference on Artificial Intelligence and Power System 大会官网:www.icaips.org【参会投稿】 大会时间:2026年5月22-24日 大会地点:中国-浙江-衢…...
深入解析ARS_408毫米波雷达与SocketCAN的CAN总线通信实践
1. 从零开始:为什么我们需要SocketCAN来“对话”毫米波雷达? 大家好,我是老张,在智能驾驶和机器人领域摸爬滚打了十几年,和各种传感器打交道是家常便饭。今天想和大家深入聊聊一个非常具体、但又至关重要的技术点&…...
SMT波浪焊接工艺精准控制品质核心
SMT波浪焊接过程中,设备是基础,而工艺参数的精准控制则是决定焊接质量的核心。很多电子制造企业都会遇到这样的问题:同样的设备、同样的原材料,不同批次的产品焊接质量却参差不齐,有的焊点牢固、外观规整,有…...
3分钟掌握Playnite便携版:打造你的移动游戏库管理中心
3分钟掌握Playnite便携版:打造你的移动游戏库管理中心 【免费下载链接】Playnite Video game library manager with support for wide range of 3rd party libraries and game emulation support, providing one unified interface for your games. 项目地址: htt…...
DLSS Swapper完全指南:5步实现游戏性能自由切换
DLSS Swapper完全指南:5步实现游戏性能自由切换 【免费下载链接】dlss-swapper 项目地址: https://gitcode.com/GitHub_Trending/dl/dlss-swapper 你是否曾因游戏更新后DLSS版本不兼容导致帧率下降而烦恼?是否想要尝试新版本DLSS功能却发现手动替…...
通过精准电源管理延长Apple Silicon Mac电池寿命的解决方案
通过精准电源管理延长Apple Silicon Mac电池寿命的解决方案 【免费下载链接】Battery-Toolkit Control the platform power state of your Apple Silicon Mac. 项目地址: https://gitcode.com/gh_mirrors/ba/Battery-Toolkit 你是否注意到,新买的MacBook Pro…...
AI编程实战:工具选型、效率提升与代码优化技巧
2026年,AI编程已进入“自动驾驶时代”,据行业数据显示,AI编程工具可使开发者效率提升30%-70%,中小企业开发成本降低70%,个人开发者可快速实现产品落地。对于开发者而言,熟练运用AI编程工具,不是…...
