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 希望在防…...

日语AI面试高效通关秘籍:专业解读与青柚面试智能助攻
在如今就业市场竞争日益激烈的背景下,越来越多的求职者将目光投向了日本及中日双语岗位。但是,一场日语面试往往让许多人感到步履维艰。你是否也曾因为面试官抛出的“刁钻问题”而心生畏惧?面对生疏的日语交流环境,即便提前恶补了…...

Xshell远程连接Kali(默认 | 私钥)Note版
前言:xshell远程连接,私钥连接和常规默认连接 任务一 开启ssh服务 service ssh status //查看ssh服务状态 service ssh start //开启ssh服务 update-rc.d ssh enable //开启自启动ssh服务 任务二 修改配置文件 vi /etc/ssh/ssh_config //第一…...
【Linux】C语言执行shell指令
在C语言中执行Shell指令 在C语言中,有几种方法可以执行Shell指令: 1. 使用system()函数 这是最简单的方法,包含在stdlib.h头文件中: #include <stdlib.h>int main() {system("ls -l"); // 执行ls -l命令retu…...
Qt Widget类解析与代码注释
#include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this); }Widget::~Widget() {delete ui; }//解释这串代码,写上注释 当然可以!这段代码是 Qt …...
【解密LSTM、GRU如何解决传统RNN梯度消失问题】
解密LSTM与GRU:如何让RNN变得更聪明? 在深度学习的世界里,循环神经网络(RNN)以其卓越的序列数据处理能力广泛应用于自然语言处理、时间序列预测等领域。然而,传统RNN存在的一个严重问题——梯度消失&#…...
多模态商品数据接口:融合图像、语音与文字的下一代商品详情体验
一、多模态商品数据接口的技术架构 (一)多模态数据融合引擎 跨模态语义对齐 通过Transformer架构实现图像、语音、文字的语义关联。例如,当用户上传一张“蓝色连衣裙”的图片时,接口可自动提取图像中的颜色(RGB值&…...
unix/linux,sudo,其发展历程详细时间线、由来、历史背景
sudo 的诞生和演化,本身就是一部 Unix/Linux 系统管理哲学变迁的微缩史。来,让我们拨开时间的迷雾,一同探寻 sudo 那波澜壮阔(也颇为实用主义)的发展历程。 历史背景:su的时代与困境 ( 20 世纪 70 年代 - 80 年代初) 在 sudo 出现之前,Unix 系统管理员和需要特权操作的…...
【服务器压力测试】本地PC电脑作为服务器运行时出现卡顿和资源紧张(Windows/Linux)
要让本地PC电脑作为服务器运行时出现卡顿和资源紧张的情况,可以通过以下几种方式模拟或触发: 1. 增加CPU负载 运行大量计算密集型任务,例如: 使用多线程循环执行复杂计算(如数学运算、加密解密等)。运行图…...

VM虚拟机网络配置(ubuntu24桥接模式):配置静态IP
编辑-虚拟网络编辑器-更改设置 选择桥接模式,然后找到相应的网卡(可以查看自己本机的网络连接) windows连接的网络点击查看属性 编辑虚拟机设置更改网络配置,选择刚才配置的桥接模式 静态ip设置: 我用的ubuntu24桌…...
PAN/FPN
import torch import torch.nn as nn import torch.nn.functional as F import mathclass LowResQueryHighResKVAttention(nn.Module):"""方案 1: 低分辨率特征 (Query) 查询高分辨率特征 (Key, Value).输出分辨率与低分辨率输入相同。"""def __…...