Ruoyi-Vue 升级JDK21、Springboot3、Mybatis3

发布于:2025-06-26 ⋅ 阅读:(18) ⋅ 点赞:(0)

一、需要升级的组件

名称 原版本 目标版本
JDK 1.8 21
Springboot、Redis 2.5.13 3.2.5
Servlet 4.0.1 6.0.0
Mybatis 2.0.7 3.0.3
Mysql驱动 8.0.28 8.3.0
Lombok 1.18.20 1.18.30
Kaptcha 2.3.2 2.3.3
Flowable 1.8 21
jaxb-api 2.3.0 2.3.1

二、详细说明

JDK
<java.version>1.8</java.version>

<!-- ↓↓↓改为下面↓↓↓ -->

<java.version>21</java.version>
Springboot、Redis
<springboot.version>2.5.13</springboot.version>

<!-- ↓↓↓改为下面↓↓↓ -->

<springboot.version>3.2.5</springboot.version>
Servlet

依赖调整

<!-- servlet包 -->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
</dependency>

<!-- ↓↓↓改为下面↓↓↓ -->

<dependency>
    <groupId>jakarta.servlet</groupId>
    <artifactId>jakarta.servlet-api</artifactId>
</dependency>

全局包名更换

javax.servlet  →  jakarta.servlet

javax.validation  →  jakarta.validation

javax.annotation  →  jakarta.annotation
Mybatis
<mybatis-spring-boot.version>2.2.2</mybatis-spring-boot.version>

<!-- ↓↓↓改为下面↓↓↓ -->

<mybatis-spring-boot.version>3.0.3</mybatis-spring-boot.version>
Mysql驱动
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>

<!-- ↓↓↓改为下面↓↓↓ -->

<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
</dependency>
Lombok
<lombok.version>1.18.20</lombok.version>

<!-- ↓↓↓改为下面↓↓↓ -->

<lombok.version>1.18.30</lombok.version>
Kaptcha
<kaptcha.version>2.3.2</kaptcha.version>
<!-- ↓↓↓改为下面↓↓↓ -->
<kaptcha.version>2.3.3</kaptcha.version>


<!-- 验证码 -->
<dependency>
    <groupId>com.github.penggle</groupId>
    <artifactId>kaptcha</artifactId>
    <version>${kaptcha.version}</version>
</dependency>

<!-- ↓↓↓改为下面↓↓↓ -->

<!-- 验证码 -->
 <dependency>
     <groupId>pro.fessional</groupId>
     <artifactId>kaptcha</artifactId>
     <exclusions>
         <exclusion>
             <artifactId>servlet-api</artifactId>
             <groupId>jakarta.servlet</groupId>
         </exclusion>
     </exclusions>
 </dependency>
Easy-captcha
<dependency>
    <groupId>com.github.whvcse</groupId>
    <artifactId>easy-captcha</artifactId>
    <version>1.6.2</version>
</dependency>

<!-- ↓↓↓后面添加↓↓↓ -->

<dependency>
    <groupId>org.openjdk.nashorn</groupId>
    <artifactId>nashorn-core</artifactId>
    <version>15.4</version>
</dependency>
Flowable
<dependency>
    <groupId>org.flowable</groupId>
    <artifactId>flowable-engine</artifactId>
    <version>6.7.2</version>
</dependency>
<dependency>
    <groupId>org.flowable</groupId>
    <artifactId>flowable-spring-boot-starter-basic</artifactId>
    <version>6.7.2</version>
</dependency>

<!-- ↓↓↓改为下面↓↓↓ -->

<dependency>
    <groupId>org.flowable</groupId>
    <artifactId>flowable-spring-boot-starter</artifactId>
    <version>7.1.0</version>
</dependency>
jaxb-api
<dependency>
    <groupId>javax.xml.bind</groupId>
    <artifactId>jaxb-api</artifactId>
    <version>2.3.0</version>
</dependency>

<!-- ↓↓↓后面添加↓↓↓ -->

<dependency>
    <groupId>javax.xml.bind</groupId>
    <artifactId>jaxb-api</artifactId>
    <version>2.3.1</version>
</dependency>

三、相关配置类调整

PermitAllUrlProperties
@Configuration
public class PermitAllUrlProperties implements InitializingBean, ApplicationContextAware {
    private static final Pattern PATTERN = Pattern.compile("\\{(.*?)\\}");

    private ApplicationContext applicationContext;

    private List<String> urls = new ArrayList<>();

    public String ASTERISK = "*";

    @Override
    public void afterPropertiesSet() {
        RequestMappingHandlerMapping mapping = applicationContext.getBean("requestMappingHandlerMapping", RequestMappingHandlerMapping.class);
        Map<RequestMappingInfo, HandlerMethod> map = mapping.getHandlerMethods();

        map.keySet().forEach(info -> {
            HandlerMethod handlerMethod = map.get(info);

            // 获取方法上边的注解 替代path variable 为 *
            Anonymous method = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), Anonymous.class);
//            Optional.ofNullable(method).ifPresent(anonymous -> Objects.requireNonNull(info.getPatternsCondition().getPatterns())
//                    .forEach(url -> urls.add(RegExUtils.replaceAll(url, PATTERN, ASTERISK))));
            Optional.ofNullable(method).flatMap(anonymous -> Optional.ofNullable(info.getPatternsCondition()))
                    .ifPresent(patterns -> patterns.getPatterns().forEach(url -> urls.add(RegExUtils.replaceAll(url, PATTERN, ASTERISK))));


            // 获取类上边的注解, 替代path variable 为 *
            Anonymous controller = AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), Anonymous.class);
//            Optional.ofNullable(controller).ifPresent(anonymous -> Objects.requireNonNull(info.getPatternsCondition().getPatterns())
//                    .forEach(url -> urls.add(RegExUtils.replaceAll(url, PATTERN, ASTERISK))));

            Optional.ofNullable(controller).flatMap(anonymous -> Optional.ofNullable(info.getPatternsCondition()))
                    .ifPresent(patterns -> patterns.getPatterns().forEach(url -> urls.add(RegExUtils.replaceAll(url, PATTERN, ASTERISK))));
        });
    }

    @Override
    public void setApplicationContext(ApplicationContext context) throws BeansException {
        this.applicationContext = context;
    }

    public List<String> getUrls() {
        return urls;
    }

    public void setUrls(List<String> urls) {
        this.urls = urls;
    }
}
SecurityConfig
@Configuration
@EnableMethodSecurity(securedEnabled = true)
public class SecurityConfig {
    /**
     * 允许匿名访问的地址
     */
    @Resource
    private PermitAllUrlProperties permitAllUrl;

    /**
     * 自定义用户认证逻辑
     */
    @Resource
    private UserDetailsService userDetailsService;

    /**
     * 认证失败处理类
     */
    @Resource
    private AuthenticationEntryPointImpl unauthorizedHandler;

    /**
     * 退出处理类
     */
    @Resource
    private LogoutSuccessHandlerImpl logoutSuccessHandler;

    /**
     * token认证过滤器
     */
    @Resource
    private JwtAuthenticationTokenFilter authenticationTokenFilter;

    /**
     * 跨域过滤器
     */
    @Resource
    private CorsFilter corsFilter;


    @Bean
    public AuthenticationManager authenticationManagerBean(){
        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
        provider.setUserDetailsService(userDetailsService);
        provider.setPasswordEncoder(bCryptPasswordEncoder());
        return new ProviderManager(provider);
    }

    /**
     * anyRequest          |   匹配所有请求路径
     * access              |   SpringEl表达式结果为true时可以访问
     * anonymous           |   匿名可以访问
     * denyAll             |   用户不能访问
     * fullyAuthenticated  |   用户完全认证可以访问(非remember-me下自动登录)
     * hasAnyAuthority     |   如果有参数,参数表示权限,则其中任何一个权限可以访问
     * hasAnyRole          |   如果有参数,参数表示角色,则其中任何一个角色可以访问
     * hasAuthority        |   如果有参数,参数表示权限,则其权限可以访问
     * hasIpAddress        |   如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问
     * hasRole             |   如果有参数,参数表示角色,则其角色可以访问
     * permitAll           |   用户可以任意访问
     * rememberMe          |   允许通过remember-me登录的用户访问
     * authenticated       |   用户登录后可访问
     */
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        // CSRF禁用,因为不使用session
        http.csrf(AbstractHttpConfigurer::disable)
                // authenticationEntryPoint认证失败处理类;accessDeniedHandler授权失败
                .exceptionHandling(exception -> exception.authenticationEntryPoint(unauthorizedHandler))
                // 基于token,所以不需要session
                .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                // 过滤请求
                // 对于登录login 允许匿名访问,但是如果携带认证token反而不能访问
                .authorizeHttpRequests(auth -> {
                    permitAllUrl.getUrls().forEach(url -> auth.requestMatchers(url).permitAll());
                    auth.requestMatchers("/login", "/app-user/login").anonymous()
                        .requestMatchers(
                            HttpMethod.GET,
                            "/",
                            "/*.html",
                            "/**.html",
                            "/*.css",
                            // 前端資源放行
                            "/static/**",
                            // 由于安卓手机微信公众号页面无法实现blog二进制流的读取下载pdf,
                            // 故这里暂时先放开,后续有客户沟通是否需要做公众号卡号绑定业务增强安全性
                            "/*.js",
                            // 文件上傳路徑放行
                            "/captcha/*",
                            "/home/management/uploadPath/**`").permitAll()
                        // 除上面外的所有请求全部需要鉴权认证
                        .anyRequest().authenticated();
                    }
                ).headers(AbstractHttpConfigurer::disable);

        //注销配置,退出后跳转到/logout,退出成功后处理logoutSuccessHandler
        http.logout(logout -> logout.logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler));
        // 添加JWT filter,添加该过滤器放在UsernamePasswordAuthenticationFilter过滤器之前
        http.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
        // 添加CORS filter
        http.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class);
        http.addFilterBefore(corsFilter, LogoutFilter.class);

        return http.build();
    }

    /**
     * 强散列哈希加密实现,这里会使得密码校验的时候默认使用该加密实现
     */
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }
application.yml

redis配置

spring:
  data:  # ←----- ######## 改动在这里,多了一级data #########
    # redis配置
    redis:
      # 地址
      host: localhost
      port: 6379
      # 数据库索引
      database: 2
      # 密码
      password: 
      # 连接超时时间
      timeout: 10s
      lettuce:
        pool:
          # 连接池中的最小空闲连接
          min-idle: 0
          # 连接池中的最大空闲连接
          max-idle: 8
          # 连接池的最大数据库连接数
          max-active: 8
          # #连接池最大阻塞等待时间(使用负值表示没有限制)
          max-wait: -1ms
pom.yml

maven打包插件

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <configuration>
        <source>1.8</source>
        <target>1.8</target>
        <encoding>UTF-8</encoding>
    </configuration>
</plugin>


<!-- ↓↓↓改为下面↓↓↓ -->

 <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <configuration>
        <source>${java.version}</source>
        <target>${java.version}</target>
        <encoding>${project.build.sourceEncoding}</encoding>
        <compilerArgs>   <!--←----- ######## 改动在这里,加了这个配置 ######### -->
            <arg>-parameters</arg>
        </compilerArgs>
    </configuration>
</plugin>

四、补充

  1. 项目启动报错
    Failed to start bean ‘documentationPluginsBootstrapper‘; nested exception is java.lang.NullPointerException

解决:暂时关闭swagger 或 升级
在这里插入图片描述

  1. 循环依赖问题
    解决:加@Lasy注解或根据实际业务情况调整代码调用

  2. 验证码问题
    在这里插入图片描述
    解决:添加解析器(上面已提到)

<dependency>
    <groupId>org.openjdk.nashorn</groupId>
    <artifactId>nashorn-core</artifactId>
    <version>15.4</version>
</dependency>
  1. 还有其它报错,可根据具体错误信息查看源码、百度、大模型解决,提升解决问题能力。相信自己是最棒的!!

网站公告

今日签到

点亮在社区的每一天
去签到