springboot3+jdk17+MP整合最新版jersey详细案例,正真做到拿来即用

如题,springboot3.x + java17 + MP 整合最新jersey,各种请求类型(实战/详解) + 文件上传下载 + jersey资源注册 + 拦截器(JWT) + 跨域处理 + 全局异常 + Valid注解校验 等等 ,除非你必须整合security,否则或许吧,再加上redis,直接用吧

一、首先从请求资源说起!

1. jersey基础请求,定义资源,添加注解等等,,,

post,put请求遇到的坑,下面有标注

import com.xxx.config.api.AbstractResource;
import com.xxx.entity.UserEntity;
import com.xxx.service.UserService;
import com.xxx.util.jwt.PassToken;
import jakarta.validation.Valid;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import org.springframework.beans.factory.annotation.Autowired;// 资源可访问路径, 注意这个前面的 “/” 别吃了
@Path("/user")
// 回参以json格式返回
@Produces(MediaType.APPLICATION_JSON)
public class UserResource extends AbstractResource {@Autowiredprivate UserService userService;@GET@Path("/info")// 获取个人信息,登录者,id通过header中的token获取public Response mine() {return this.ok(userService.mine());}@PUT@Path("/edit")// 这里增加了 @Valid 校验,看看实体对象中哪些属性不要的删掉// 另外,使用@Valid 校验,由于这里是java17,注意不要导入了javax的包,否则无效public Response edit(@Valid UserEntity entity) {userService.edit(entity);return this.successEdit();}@POST@Path("/login")// 自定义@PassToken 注解,不需要token也能访问@PassToken(canPass = true)// form传参,且请求类型必须为 application/x-www-form-urlencodedpublic Response login(@FormParam("username") String username,@FormParam("password") String password) {return this.ok(userService.login(username, password));}@PUT@Path("/logout")// 登出,清除tokenpublic Response logout() {userService.logout();return this.success();}@PUT@Path("/register")// 使用 BeanParam 注解,可以自动将请求参数绑定到实体类上,但是实体必须使用 @FormParam 注解修饰属性,否则会报错// 而且,请求类型必须是 application/x-www-form-urlencoded// 并且必须加上无参构造和所有有参构造......不太建议使用......// 不加 @BeanParam 注解,请求类型必须是 application/json,简单,推荐
//    public Response register(@Valid @BeanParam UserEntity entity) {public Response register(@Valid UserEntity entity) {userService.register(entity);return this.success();}}// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------// ----------------------------------------------------------------------------// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------// 针对如上的 @BeanParam ,实体为
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import jakarta.validation.constraints.NotNull;
import jakarta.ws.rs.FormParam;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;import java.io.Serial;
import java.io.Serializable;/*** @TableName user_t*/
@TableName(value = "user_t")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserEntity implements Serializable {/*** 用户id*/@FormParam("id")@TableId(type = IdType.AUTO)private Integer id;/*** 用户名称*/@FormParam("name")@NotNull(message = "用户名不能为空!")private String name;/*** 密码*/@FormParam("password")@NotNull(message = "密码不能为空!")@TableField(select = false)private String password;/*** 电话*/@FormParam("phone")@NotNull(message = "电话号码不能为空!")private String phone;@Serial@TableField(exist = false)private static final long serialVersionUID = 1L;}
2. 文件上传,本地文件下载/预览实现
import com.xxx.config.api.AbstractResource;
import com.xxx.config.exception.GenExceptCode;
import com.xxx.config.exception.ServiceException;
import com.xxx.util.FileUtil;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import jdk.jfr.Description;
import org.glassfish.jersey.media.multipart.FormDataContentDisposition;
import org.glassfish.jersey.media.multipart.FormDataParam;import java.io.File;
import java.io.InputStream;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;@Path("/file")
@Produces(MediaType.APPLICATION_JSON)
public class FileResource extends AbstractResource {@POST@Path("/upload")@Description("上传文件")@Consumes(MediaType.MULTIPART_FORM_DATA + ";charset=UTF-8")public Response upload(@FormDataParam("file") InputStream inputStream,@FormDataParam("file") FormDataContentDisposition disposition) {// fileName utf8 处理上传文件名称// 先decode转码,在用char转码String fileName = disposition.getFileName();// 兼容处理 RFC 6266规范 上传 文件编码格式String fileNamePrefix = "UTF-8''";boolean charFlag = fileName.contains(fileNamePrefix);if (charFlag) {fileName = URLDecoder.decode(fileName.replace(fileNamePrefix, ""), StandardCharsets.UTF_8);} else {fileName = new String(fileName.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);}// file full pathString filePath = "D:/files/" + fileName;File tempFile = new File(filePath);// 判断是否村子if (tempFile.isFile() && tempFile.exists()) {throw new ServiceException(GenExceptCode.Request_Param.name(), "上传失败,该文件已存在!");}// save fileFile file = FileUtil.saveFile(inputStream, tempFile);// 文件大小, 不要用 disposition 获取文件大小
//        disposition.getSize(); // 这个获取不到文件大小,不知道为啥。。。file.length();return this.success();}@GET@Path("/{id}/download")@Description("下载文件")// 浏览器请求:http://localhost:8080/{fileId}/downloadpublic Response download(@PathParam("id") Integer id) {// 通过id在数据库中获取到文件的具体路径,如"D:/files/xxx.docx"String filePath = "";File file = new File(filePath);//如果文件不存在,提示404if (!file.exists()) {return this.err404();}// 直接将file对象给jersey处理就好,这里只是简单的封装了下return this.successDownload(file);}@GET@Path("/{id}/preview")@Description("预览查看文件")// 浏览器请求:http://localhost:8080/{fileId}/previewpublic Response preview(@PathParam("id") Integer id) {// 通过id在数据库中获取到文件的具体路径,如"D:/files/xxx.docx"String filePath = "";File file = new File(filePath);//如果文件不存在,提示404if (!file.exists()) {return this.err404();}// 直接将file对象给jersey处理就好,这里只是简单的封装了下return this.successPreview(file);}}

二、 跨域处理

(----------后续要在JerseyConfig中注册,这里先上代码----------)

import jakarta.servlet.http.HttpServletResponse;
import jakarta.ws.rs.container.ContainerRequestContext;
import jakarta.ws.rs.container.ContainerResponseContext;
import jakarta.ws.rs.container.ContainerResponseFilter;import java.io.IOException;/*** 跨域处理 此方法需要在 Jersey 中注册 资源 后才能使用*/
public class CorsFilter implements ContainerResponseFilter {public void filter(ContainerRequestContext request, ContainerResponseContext c) throws IOException {if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {//浏览器会先通过options请求来确认服务器是否可以正常访问,此时应放行c.setStatus(HttpServletResponse.SC_OK);}c.getHeaders().add("Access-Control-Allow-Origin", "*");c.getHeaders().add("Access-Control-Allow-Headers", "origin, content-type, accept, authorization");c.getHeaders().add("Access-Control-Allow-Credentials", "true");c.getHeaders().add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");// CORS策略的缓存时间c.getHeaders().add("Access-Control-Max-Age", "1209600");}}

三、去除APPLICATION_FORM_URLENCODED请求的警告信息,extends HiddenHttpMethodFilter,输出请求响应

(这个不需要注册jersey,直接使用配置)

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.ws.rs.core.MediaType;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.jetty.http.HttpStatus;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.filter.HiddenHttpMethodFilter;import java.io.IOException;
import java.time.Duration;
import java.time.LocalDateTime;/*** 去除APPLICATION_FORM_URLENCODED请求的警告信息** @version 1.0* @since 1.0*/
@Slf4j
@Configuration
public class HttpMethodFilter extends HiddenHttpMethodFilter {@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse r,FilterChain fc) throws ServletException, IOException {LocalDateTime startTime = LocalDateTime.now();if ((RequestMethod.POST.name().equals(request.getMethod()) || RequestMethod.PUT.name().equals(request.getMethod()))&& MediaType.APPLICATION_FORM_URLENCODED.equals(request.getContentType())) {//Skip this filter and call the next filter in the chain.fc.doFilter(request, r);} else {//Continue with processing this filter.super.doFilterInternal(request, r, fc);}LocalDateTime endTime = LocalDateTime.now();Duration duration = Duration.between(startTime, endTime);String info = request.getMethod()+ ">>>>>>"+ request.getRequestURI()+ "======>请求耗时: " + duration.toMillis() + " 毫秒,返回状态为:" + r.getStatus();if ("favicon.ico".equalsIgnoreCase(request.getRequestURI())) {return;}if (HttpStatus.OK_200 == r.getStatus()) log.info(info);else log.error(info);}
}

四、自定义RequestFilter implements ContainerRequestFilter

(----------后续要在JerseyConfig中注册,这里先上代码----------)

import com.xxx.config.api.AuditAware;
import com.xxx.config.api.ReturnResult;
import com.xxx.config.exception.GenExceptCode;
import com.xxx.util.jwt.JwtUtil;
import com.xxx.util.jwt.PassToken;
import jakarta.ws.rs.container.ContainerRequestContext;
import jakarta.ws.rs.container.ContainerRequestFilter;
import jakarta.ws.rs.container.ResourceInfo;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.jetty.http.HttpStatus;import java.lang.reflect.Method;/*** 自定义拦截器实现*/
public class RequestFilter implements ContainerRequestFilter {@Contextprivate ResourceInfo resourceInfo;@Overridepublic void filter(ContainerRequestContext requestContext) {// 在这里进行拦截和处理操作// 可以获取请求的信息,进行身份验证、参数校验等操作// 如果需要终止请求并返回响应,可以使用requestContext.abortWith方法// 获取自定义注解,当存在,则放行Method method = resourceInfo.getResourceMethod();if (method != null && method.isAnnotationPresent(PassToken.class)) {PassToken aspect = method.getAnnotation(PassToken.class);if (aspect != null && aspect.canPass()) {return;}}// 获取tokenString headerToken = JwtUtil.getJwtFromHeader(requestContext);String urlToken = JwtUtil.getJwtFromUrl(requestContext);if (StringUtils.isEmpty(headerToken) && StringUtils.isEmpty(urlToken)) {requestContext.abortWith(Response.ok(new ReturnResult(GenExceptCode.Operation_Denial.name(), "操作被拒!")).status(HttpStatus.UNAUTHORIZED_401).type(MediaType.APPLICATION_JSON_TYPE).build());} else {if (StringUtils.isEmpty(headerToken)) {if (StringUtils.isEmpty(urlToken)) {requestContext.abortWith(Response.ok(new ReturnResult(GenExceptCode.Operation_Denial.name(), "操作被拒!")).status(HttpStatus.UNAUTHORIZED_401).type(MediaType.APPLICATION_JSON_TYPE).build());} else {AuditAware.setUserId(JwtUtil.getUserIdFromToken(urlToken));}} else {AuditAware.setUserId(JwtUtil.getUserIdFromToken(headerToken));}}}
}

五、jersey配置类

这里必须注意,registerClasses的使用方法,不然你就一个一个资源添加吧
更多案例 ——》》 Springboot集成jersey打包jar找不到class处理

import com.xxx.config.exception.support.DefaultExceptionMapperSupport;
import com.xxx.config.exception.support.JsonMapperExceptionSupport;
import com.xxx.config.exception.support.UncaughtExceptionMapperSupport;
import com.xxx.config.exception.support.ValidationExceptionMapperSupport;
import org.glassfish.jersey.jackson.internal.jackson.jaxrs.json.JacksonJsonProvider;
import org.glassfish.jersey.logging.LoggingFeature;
import org.glassfish.jersey.media.multipart.MultiPartFeature;
import org.glassfish.jersey.server.ResourceConfig;
import org.springframework.context.annotation.Configuration;/*** Jersey配置类*/
@Configuration
public class JerseyConfig extends ResourceConfig {public JerseyConfig() {// 开启日志register(LoggingFeature.class);// 注册文件上传register(MultiPartFeature.class);// 注册跨域处理register(CorsFilter.class);// 注册自定义拦截器实现register(RequestFilter.class);// 注册json序列化register(JacksonJsonProvider.class);// 注册异常类资源register(DefaultExceptionMapperSupport.class);register(JsonMapperExceptionSupport.class);register(UncaughtExceptionMapperSupport.class);register(ValidationExceptionMapperSupport.class);// 注册包扫描 这个方法在开发使用没问题,但是打包jar后,找不到 class 文件// packages("com.xxx.api");// 定义扫描包含接口资源包registerClasses(ClassUtil.findAllClasses("com.xxx.api"));}
}

六、mabatis-plus简单配置

(看看就行)

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;@Configuration
// mapper java文件 路径扫描
@MapperScan("com.xxx.mapper")
// 开启事务管理
@EnableTransactionManagement
public class MybatisConfig {/*** 注册mybatis-plus分页插件*/@BeanMybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));return interceptor;}
}

七、自定义注解

使用见–userResource,实际逻辑见—RequestFilter

import java.lang.annotation.*;@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface PassToken {boolean canPass() default false;
}

八、全局异常,先上使用案例,具体详见代码

当然,你也可以用最优雅的断言方式,,,

...
...public String login(String username, String password) {if (username == null || password == null) {throw new ServiceException(GenExceptCode.Request_Param.name(), "账户或密码不能为空!");}UserEntity userEntity = this.getOneByUsername(username, password);if (userEntity == null) {throw new ServiceException(GenExceptCode.Request_Param.name(), "账户或密码错误!");}AuditAware.setUserId(userEntity.getId());// -1 永不过期return JwtUtil.generateToken(userEntity.getId(), -1);// TODO 生成token后保存到redis}...
...

九、效果

第一个有token,第二个没有token的效果

十、详见代码

配置文件,pom文件,以及其它源码

♥ – – – – git代码 – – – – ♥

欢迎指正

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

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

相关文章

SpringCloud Alibaba Nacos简单应用(二)

😀前言 本篇博文是关于SpringCloud Alibaba Nacos简单应用,希望你能够喜欢 🏠个人主页:晨犀主页 🧑个人简介:大家好,我是晨犀,希望我的文章可以帮助到大家,您的满意是我的…

【spring】@Lazy注解学习

Lazy介绍 Lazy 注解是一个配置注解,用于指示 Spring 容器在创建 bean 时采用延迟初始化的策略。这意味着,除非 bean 被实际使用,否则不会被创建和初始化。 在 Spring 框架中,默认情况下,所有的单例 bean 在容器启动时…

【Linux】模拟实现shell(bash)

目录 常见的与shell互动场景 实现代码 全部代码 homepath()接口 const char *getUsername()接口 const char *getHostname()接口 const char *getCwd()接口 int getUserCommand(char *command, int num)接口 void commandSplit(char *in, char *out[])接口 int execut…

aurora仿真使用等

IP设置 代码 aurora_8b10b aurora_8b10b_inst (/**********************************************************************************///axi_stream tx.s_axi_tx_tdata(s_axi_tx_tdata), // input wire [0 : 31] s_axi_tx_tdata.s_axi_tx_tkeep(s_axi_tx_…

2024开年首展,加速科技展台“热辣滚烫”

3月20日,备受瞩目的半导体行业盛会SEMICON China 2024在上海新国际博览中心盛大启幕,展会汇集了来自全球的半导体领域顶尖企业与专业人士。加速科技作为业界领先的半导体测试设备供应商携重磅测试设备及解决方案精彩亮相,展示了最新的半导体测…

KIMI爆了!对比文心一言和通义千问它到底有多强?

原文:赵侠客 前言 最近国产大模型KIMI爆了大部分人应该都知道了,从我个人的感受来看这次KIMI爆了我不是从技术领域接触到的,而是从各种金融领域接触到的。目前国内大模型可以说是百模大战,前几年新能源大战,今年资本割完韭菜后留…

简述TCP的三次握手与四次挥手

A表示客户client,即主动发起连接的应用程序;B表示服务器,即被动等待连接建立的应用进程。 TCP建立连接,三报文握手,也可以是四报文握手。 总流程可以简化为:A向B发送连接请求,B向A确认&#xf…

JAVA学习-NIO.Files工具类

Java NIO(New IO)提供了一组用于处理文件和文件系统的工具类,其中包含了Files工具类。Files工具类提供了对文件和目录进行操作的静态方法,如创建、复制、删除、移动、重命名等等。以下是Files工具类的特点、常用方法以及与其他类的…

面试宝典:MySQL索引进阶深度分析

在数据库高级开发的面试中,索引是一个绕不开的重点话题。MySQL中的索引机制对于查询性能有着决定性的影响。本文将深入探讨MySQL索引的高级特性和优化策略,帮助开发者在面试中展现出对数据库索引机制的深刻理解和高级应用能力。 索引的基本概念 MySQL中…

Linux:Prometheus的源码包安装及操作(2)

环境介绍 三台centos 7系统,运行内存都2G 1.prometheus监控服务器:192.168.6.1 主机名:pm 2.grafana展示服务器:192.168.6.2 主机名:gr 3.被监控服务器:192.168.6.3 …

C++之循环中使用auto关键字

在C中,auto是一个类型说明符,用于自动推导变量的类型。编译器会根据初始化表达式的类型来自动推断变量的类型。使用auto可以使代码更加简洁,并减少手动键入类型信息的需要,尤其是在处理复杂类型或模板时。 在C中,使用…

codeTop102:二叉树的层序遍历

前言 在已知BFS的方式后,知道每次从队列中取一个节点,就要将这个节点的所有子节点按照顺序放入队列。 难点在于怎么确定将同一层的节点放在一个数组里面的输出,也就是输出一个二维数组? 解决方法: 每次while循环将队列上轮放入的…

android四大组件之一ContentProvider

ContentProvider ContentProvider 是 Android 中用于实现数据共享的一种组件,它可以让不同的应用程序之间共享数据。数据通常以表格的形式组织,类似于数据库的表。其他应用程序可以通过 ContentResolver 来查询或修改这些数据。 ContentProvider、Conten…

什么是C++中的指针和引用?它们有什么区别?/ 数组名和指针(这里为指向数组首元素的指针)区别?

一、什么是C中的指针和引用?它们有什么区别? 在C中,指针和引用都是重要的概念,它们用于处理内存地址和变量之间的关系,但两者之间存在明显的区别。 指针是一个特殊的变量,其值为另一个变量的地址。换句话…

【课程】Mysql优化

Mysql优化教程01-关键技术.wmvMysql优化教程02-表的设计.wmvMysql优化教程03-慢查询(一).wmvMysql优化教程04-慢查询(二).wmvMysql优化教程05-慢查询(三).wmv Mysql优化教程06-索引(一).wmv Mysql优化教程07-索(二).wmv Mysql优化教程08-索引(三).wmv Mysql优化教程09-索(四).w…

Vue2(十):全局事件总线、消息订阅与发布、TodoList的编辑功能、$nextTick、动画

一、全局事件总线!! 任意组件间通信 比如a想收到别的组件的数据,那么就在a里面给x绑定一个demo自定义事件,所以a里面就得有一个回调函数吧,然后我要是想让d组件给a穿数据,那就让d去触发x的自定义事件&…

洛谷_P2678 [NOIP2015 提高组] 跳石头_python写法

P2678 [NOIP2015 提高组] 跳石头 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) d, n, m map(int,input().split())data [0] for i in range(n):value int(input())data.append(value) data.append(d)def check(mid):now 0cnt 0for i in range(1,n2):if abs(data[now]-da…

【课程】Nginx核心知识100讲

02.Nginx 适用于哪些场景 ?.mp4 03.Nginx 出现的历史背景.mp4 04.为什么用 Nginx : 它的 5 个主要优点mp4 05.Nginx 的四个主要组成部分.mp4 06.Nginx 的版本发布历史.mp4 07.选择哪一个 Nginx 发行版本 ?.mp4 08.编译出适合自己的 Nginx.mp4 09.Nginx 配置文件的通用语法介绍…

机器学习(27)

文章目录 文献阅读1. 题目2. abstract3. 网络架构3.1 Theoretical Results 4. 文献解读4.1 Introduction4.2 创新点4.3 实验过程4.3.1 数据集4.3.2 参数设置 4.4 结论 三、实现GAN1. 任务要求2. 实验结果3.实验代码3.1数据准备3.2 模型构建3.3 展示函数3.4 训练过程 小结本周内…

从0写一个问卷调查APP的第13天-1

1.今日任务 我也只是一个大学生,有什么思路不对的地方给我指出来哟! 分析:上次我们实现了任务调查的插入。但是我们插入的问卷调查只有它的标题,也就是这个问卷调查是什么我们告诉数据库了,但是现在我们还没有给它添加任何问题&…