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 希望在防…...
stm32G473的flash模式是单bank还是双bank?
今天突然有人stm32G473的flash模式是单bank还是双bank?由于时间太久,我真忘记了。搜搜发现,还真有人和我一样。见下面的链接:https://shequ.stmicroelectronics.cn/forum.php?modviewthread&tid644563 根据STM32G4系列参考手…...
云计算——弹性云计算器(ECS)
弹性云服务器:ECS 概述 云计算重构了ICT系统,云计算平台厂商推出使得厂家能够主要关注应用管理而非平台管理的云平台,包含如下主要概念。 ECS(Elastic Cloud Server):即弹性云服务器,是云计算…...
基于距离变化能量开销动态调整的WSN低功耗拓扑控制开销算法matlab仿真
目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.算法仿真参数 5.算法理论概述 6.参考文献 7.完整程序 1.程序功能描述 通过动态调整节点通信的能量开销,平衡网络负载,延长WSN生命周期。具体通过建立基于距离的能量消耗模型&am…...
抖音增长新引擎:品融电商,一站式全案代运营领跑者
抖音增长新引擎:品融电商,一站式全案代运营领跑者 在抖音这个日活超7亿的流量汪洋中,品牌如何破浪前行?自建团队成本高、效果难控;碎片化运营又难成合力——这正是许多企业面临的增长困局。品融电商以「抖音全案代运营…...
跨链模式:多链互操作架构与性能扩展方案
跨链模式:多链互操作架构与性能扩展方案 ——构建下一代区块链互联网的技术基石 一、跨链架构的核心范式演进 1. 分层协议栈:模块化解耦设计 现代跨链系统采用分层协议栈实现灵活扩展(H2Cross架构): 适配层…...
鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院查看报告小程序
一、开发环境准备 工具安装: 下载安装DevEco Studio 4.0(支持HarmonyOS 5)配置HarmonyOS SDK 5.0确保Node.js版本≥14 项目初始化: ohpm init harmony/hospital-report-app 二、核心功能模块实现 1. 报告列表…...
leetcodeSQL解题:3564. 季节性销售分析
leetcodeSQL解题:3564. 季节性销售分析 题目: 表:sales ---------------------- | Column Name | Type | ---------------------- | sale_id | int | | product_id | int | | sale_date | date | | quantity | int | | price | decimal | -…...
html-<abbr> 缩写或首字母缩略词
定义与作用 <abbr> 标签用于表示缩写或首字母缩略词,它可以帮助用户更好地理解缩写的含义,尤其是对于那些不熟悉该缩写的用户。 title 属性的内容提供了缩写的详细说明。当用户将鼠标悬停在缩写上时,会显示一个提示框。 示例&#x…...
让回归模型不再被异常值“带跑偏“,MSE和Cauchy损失函数在噪声数据环境下的实战对比
在机器学习的回归分析中,损失函数的选择对模型性能具有决定性影响。均方误差(MSE)作为经典的损失函数,在处理干净数据时表现优异,但在面对包含异常值的噪声数据时,其对大误差的二次惩罚机制往往导致模型参数…...
【JVM】Java虚拟机(二)——垃圾回收
目录 一、如何判断对象可以回收 (一)引用计数法 (二)可达性分析算法 二、垃圾回收算法 (一)标记清除 (二)标记整理 (三)复制 (四ÿ…...
