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爆了我不是从技术领域接触到的,而是从各种金融领域接触到的。目前国内大模型可以说是百模大战,前几年新能源大战,今年资本割完韭菜后留…

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 …

codeTop102:二叉树的层序遍历

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

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…

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

蓝桥杯真题:幸运数字

这道题可以用 integer.string()求每个进制的数,但这里要每一位数相加,所以用这个方法会比较麻烦,如下 import java.util.Scanner; public class Main {public static void main(String[] args) {Scanner scan new Sc…

华为OD机试真题-推荐多样性-2024年OD统一考试(C卷)

题目描述: 推荐多样性需要从多个列表中选择元素,一次性要返回N屏数据(窗口数量),每屏展示K个元素(窗口大小),选择策略: 1. 各个列表元素需要做穿插处理,即先从第一个列表中为每屏选择一个元素,再从第二个列表中为每屏选择一个元素,依次类推 2. 每个列表的元素尽量均…

spring注解驱动系列--AOP探究二

上篇中记录了AnnotationAwareAspectJAutoProxyCreator的创建以及注册,主要是 1、EnableAspectJAutoProxy 注解会开启AOP功能 2、然后这个注解会往容器中注册一个AnnotationAwareAspectJAutoProxyCreator组件。 3、之后在容器创建过程中,注册后置处理器&a…

关于四篇GNN论文的阅读笔记PPT:包括GATNE,AM-GCN,HGSL和coGSL

关于四篇GNN论文的阅读笔记PPT:包括GATNE,AM-GCN,HGSL和coGSL 前言GATNEAM-GCNHGSLcoGSL 前言 这里的PPT主要是在跟Graph Transformer一起的: 【图-注意力笔记,篇章1】Graph Transformer:包括Graph Trans…

mysql基础3索引

存储引擎 存储引擎就是存储数据、建立索引、更新/查询数据等技术的实现方式 。存储引擎是基于表的,而不是 基于库的,所以存储引擎也可被称为表类型。 1). 建表时指定存储引擎 CREATE TABLE 表名(字段1 字段1类型 [ COMMENT 字段1注释 ] ,......字段n…

JAVA的学习日记DAY4

算术运算符 关系运算符(比较运算符) 关系运算符的结果都是boolean型,也就是要么是true,要么是false 关系表达式 经常用在if结构的条件中或循环结构的条件中 逻辑运算符 && 和 & 使用区别 &&短路与&#xff…

python学习9:python的代码中的数据类型转换

python中数据类型的转换 1.为什么需要转换类型呢? 数据类型之间,在特定的场景下,是可以相互转换的,如字符串转数字,数字转字符串等;数据类型转换,在以后是我们经常使用到的功能,例如…

五、分布式锁-redission

源码仓库地址:gitgitee.com:chuangchuang-liu/hm-dingping.git 1、redission介绍 目前基于redis的setnx特性实现的自定义分布式锁仍存在的问题: 问题描述重入问题同一个线程无法多次获取统一把锁。当方法A成功获取锁后,调用方法B&#xff0…