当前位置:

首页 > 编程开发 > SpringBoot安全管理之Spring Security如何配置

SpringBoot安全管理之Spring Security如何配置

在Java开发领域常见的安全框架有Shiro和SpringSecurity。Shiro是一个轻量级的安全管理框架,提供了认证、授权、会话管理、密码管理、缓存管理等功能。SpringSecurity是一个相对复杂的安全管理框架,功能比Shiro更加强大,权限控制细粒度更高,对OAuth2的支持也很友好,又因为SpringSecurity源自Spring家族,因此可以和Spring框架无缝整合,特别是SpringBoot中提供的自动化配置方案,可以让SpringSecurity的使用更加便捷。SpringSe

本文目录

      在 Java 开发领域常见的安全框架有 Shiro 和 Spring Security。Shiro 是一个轻量级的安全管理框架,提供了认证、授权、会话管理、密码管理、缓存管理等功能。Spring Security 是一个相对复杂的安全管理框架,功能比 Shiro 更加强大,权限控制细粒度更高,对 OAuth 2 的支持也很友好,又因为 Spring Security 源自 Spring 家族,因此可以和 Spring 框架无缝整合,特别是 Spring Boot 中提供的自动化配置方案,可以让 Spring Security 的使用更加便捷。

      Spring Security 的基本配置

      基本用法

      1. 创建项目添加依赖

      创建一个 Spring Boot 项目,然后添加 spring-boot-starter-security 依赖即可

      
        org.springframework.boot
        spring-boot-starter-web
      
      
        org.springframework.boot
        spring-boot-starter-security
      
      2. 添加 hello 接口
      @RestController
      public class HelloController {
          @GetMapping("/hello")
          public String hello() {
              return "hello";
          }
      }
      3. 启动项目测试

      启动成功后,访问 /hello 接口就会自动跳转到登录页面,这个登录页面是由 Spring Security 提供的

      SpringBoot安全管理之Spring Security如何配置

      默认的用户名是 user ,默认的登录密码则在每次启动项目时随机生成,查看项目启动日志

      Using generated security password: 4f845a17-7b09-479c-8701-48000e89d364

      登录成功后,用户就可以访问 /hello 接口了

      配置用户名和密码

      如果开发者对默认的用户名和密码不满意,可以在 application.properties 中配置默认的用户名、密码以及用户角色

      spring.security.user.name=tangsan
      spring.security.user.password=tangsan
      spring.security.user.roles=admin

      基于内存的认证

      开发者也可以自定义类继承自 WebSecurityConfigurer,进而实现对 Spring Security 更多的自定义配置,例如基于内存的认证,配置方式如下:

      @Configuration
      public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {
          @Bean
          PasswordEncoder passwordEncoder() {
              return NoOpPasswordEncoder.getInstance();
          }
          @Override
          protected void configure(AuthenticationManagerBuilder auth) throws Exception {
              auth.inMemoryAuthentication()
                      .withUser("admin").password("123123").roles("ADMIN", "USER")
                      .and()
                      .withUser("tangsan").password("123123").roles("USER");
          }
      }

      代码解释:

      • 自定义 MyWebSecurityConfig 继承自 WebSecurityConfigurerAdapter ,并重写 configure(AuthenticationManagerBuilder auth) 方法,在该方法中配置两个用户,一个用户是 admin ,具备两个角色 ADMIN、USER;另一个用户是 tangsan ,具备一个角色 USER

      • 此处使用的 Spring Security 版本是 5.0.6 ,在 Spring Security 5.x 中引入了多种密码加密方式,开发者必须指定一种,此处使用 NoOpPasswordEncoder ,即不对密码进行加密

      注意:基于内存的用户配置,在配置角色时不需要添加 “ROLE_” 前缀,这点和后面 10.2 节中基于数据库的认证有差别。

      配置完成后,重启项目,就可以使用这里配置的两个用户进行登录了。

      HttpSecurity

      虽然现在可以实现认证功能,但是受保护的资源都是默认的,而且不能根据实际情况进行角色管理,如果要实现这些功能,就需要重写 WebSecurityConfigurerAdapter 中的另一个方法

      @Configuration
      public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {
          @Bean
          PasswordEncoder passwordEncoder() {
              return NoOpPasswordEncoder.getInstance();
          }
          @Override
          protected void configure(AuthenticationManagerBuilder auth) throws Exception {
              auth.inMemoryAuthentication()
                      .withUser("root").password("123123").roles("ADMIN", "DBA")
                      .and()
                      .withUser("admin").password("123123").roles("ADMIN", "USER")
                      .and()
                      .withUser("tangsan").password("123123").roles("USER");
          }
          @Override
          protected void configure(HttpSecurity http) throws Exception {
              http.authorizeRequests()
                      .antMatchers("/admin/**")
                      .hasRole("ADMIN")
                      .antMatchers("/user/**")
                      .access("hasAnyRole('ADMIN','USER')")
                      .antMatchers("/db/**")
                      .access("hasRole('ADMIN') and hasRole('DBA')")
                      .anyRequest()
                      .authenticated()
                      .and()
                      .formLogin()
                      .loginProcessingUrl("/login")
                      .permitAll()
                      .and()
                      .csrf()
                      .disable();
          }
      }

      代码解释:

      • 首先配置了三个用户,root 用户具备 ADMIN 和 DBA 的角色,admin 用户具备 ADMIN 和 USER 角色,tangsan 用于具备 USER 角色

      • 调用 authorizeRequests() 方法开启 HttpSecurity 的配置,antMatchers() ,hasRole() ,access() 方法配置访问不同的路径需要不同的用户及角色

      • anyRequest(),authenticated() 表示出了前面定义的之外,用户访问其他的 URL 都必须认证后访问

      • formLogin(),loginProcessingUrl(“/login”),permitAll(),表示开启表单登录,前面看到的登录页面,同时配置了登录接口为 /login 即可以直接调用 /login 接口,发起一个 POST 请求进行登录,登录参数中用户名必须命名为 username ,密码必须命名为 password,配置 loginProcessingUrl 接口主要是方便 Ajax 或者移动端调用登录接口。最后还配置了 permitAll,表示和登录相关的接口都不需要认证即可访问。

      配置完成后,在 Controller 中添加如下接口进行测试:

      @RestController
      public class HelloController {
          @GetMapping("/admin/hello")
          public String admin() {
              return "hello admin";
          }
          @GetMapping("/user/hello")
          public String user() {
              return "hello user";
          }
          @GetMapping("/db/hello")
          public String dba() {
              return "hello dba";
          }
          @GetMapping("/hello")
          public String hello() {
              return "hello";
          }
      }

      根据上文配置,“/admin/hello” 接口 root 和 admin 用户具有访问权限;“/user/hello” 接口 admin 和 tangsan 用户具有访问权限;“/db/hello” 只有 root 用户有访问权限。浏览器中的测试很容易,这里不再赘述。

      登录表单详细配置

      目前为止,登录表单一直使用 Spring Security 提供的页面,登录成功后也是默认的页面跳转,但是,前后端分离已经成为企业级应用开发的主流,在前后端分离的开发方式中,前后端的数据交互通过 JSON 进行,这时,登录成功后就不是页面跳转了,而是一段 JSON 提示。要实现这些功能,只需要继续完善上文的配置

      @Override
      protected void configure(HttpSecurity http) throws Exception {
          http.authorizeRequests()
              .antMatchers("/admin/**")
              .hasRole("ADMIN")
              .antMatchers("/user/**")
              .access("hasAnyRole('ADMIN','USER')")
              .antMatchers("/db/**")
              .access("hasRole('ADMIN') and hasRole('DBA')")
              .anyRequest()
              .authenticated()
              .and()
              .formLogin()
              .loginPage("/login_page")
              .loginProcessingUrl("/login")
              .usernameParameter("name")
              .passwordParameter("passwd")
              .successHandler(new AuthenticationSuccessHandler() {
                  @Override
                  public void onAuthenticationSuccess(HttpServletRequest req,
                                                      HttpServletResponse resp,
                                                      Authentication auth)
                      throws IOException {
                      Object principal = auth.getPrincipal();
                      resp.setContentType("application/json;charset=utf-8");
                      PrintWriter out = resp.getWriter();
                      resp.setStatus(200);
                      Map map = new HashMap<>();
                      map.put("status", 200);
                      map.put("msg", principal);
                      ObjectMapper om = new ObjectMapper();
                      out.write(om.writeValueAsString(map));
                      out.flush();
                      out.close();
                  }
              })
              .failureHandler(new AuthenticationFailureHandler() {
                  @Override
                  public void onAuthenticationFailure(HttpServletRequest req,
                                                      HttpServletResponse resp,
                                                      AuthenticationException e)
                      throws IOException {
                      resp.setContentType("application/json;charset=utf-8");
                      PrintWriter out = resp.getWriter();
                      resp.setStatus(401);
                      Map map = new HashMap<>();
                      map.put("status", 401);
                      if (e instanceof LockedException) {
                          map.put("msg", "账户被锁定,登录失败!");
                      } else if (e instanceof BadCredentialsException) {
                          map.put("msg", "账户名或密码输入错误,登录失败!");
                      } else if (e instanceof DisabledException) {
                          map.put("msg", "账户被禁用,登录失败!");
                      } else if (e instanceof AccountExpiredException) {
                          map.put("msg", "账户已过期,登录失败!");
                      } else if (e instanceof CredentialsExpiredException) {
                          map.put("msg", "密码已过期,登录失败!");
                      } else {
                          map.put("msg", "登录失败!");
                      }
                      ObjectMapper om = new ObjectMapper();
                      out.write(om.writeValueAsString(map));
                      out.flush();
                      out.close();
                  }
              })
              .permitAll()
              .and()
              .csrf()
              .disable();
      }

      代码解释:

      • loginPage(“/login_page”) 表示如果用户未获授权就访问一个需要授权才能访问的接口,就会自动跳转到 login_page 页面让用户登录,这个 login_page 就是开发者自定义的登录页面,而不再是 Spring Security 提供的默认登录页

      • loginProcessingUrl(“/login”) 表示登录请求处理接口,无论是自定义登录页面还是移动端登录,都需要使用该接口

      • usernameParameter(“name”),passwordParameter(“passwd”) 定义了认证所需要的用户名和密码的参数,默认用户名参数是 username,密码参数是 password,可以在这里定义

      • successHandler() 方法定义了登录成功的处理逻辑。用户登录成功后可以跳转到某一个页面,也可以返回一段 JSON ,这个要看具体业务逻辑,此处假设是第二种,用户登录成功后,返回一段登录成功的 JSON 。onAuthenticationSuccess 方法的第三个参数一般用来获取当前登录用户的信息,在登录后,可以获取当前登录用户的信息一起返回给客户端

      • failureHandler 方法定义了登录失败的处理逻辑,和登录成功类似,不同的是,登录失败的回调方法里有一个 AuthenticationException 参数,通过这个异常参数可以获取登录失败的原因,进而给用户一个明确的提示

      配置完成后,使用 Postman 进行测试

      SpringBoot安全管理之Spring Security如何配置

      如果登录失败也会有相应的提示

      SpringBoot安全管理之Spring Security如何配置

      注销登录配置

      如果想要注销登录,也只需要提供简单的配置即可

      @Override
      protected void configure(HttpSecurity http) throws Exception {
          http.authorizeRequests()
              .antMatchers("/admin/**")
              .hasRole("ADMIN")
              .antMatchers("/user/**")
              .access("hasAnyRole('ADMIN','USER')")
              .antMatchers("/db/**")
              .access("hasRole('ADMIN') and hasRole('DBA')")
              .anyRequest()
              .authenticated()
              .and()
              .formLogin()
              .loginPage("/login_page")
              .loginProcessingUrl("/login")
              .usernameParameter("name")
              .passwordParameter("passwd")
              .successHandler(new AuthenticationSuccessHandler() {
                  @Override
                  public void onAuthenticationSuccess(HttpServletRequest req,
                                                      HttpServletResponse resp,
                                                      Authentication auth)
                      throws IOException {
                      Object principal = auth.getPrincipal();
                      resp.setContentType("application/json;charset=utf-8");
                      PrintWriter out = resp.getWriter();
                      resp.setStatus(200);
                      Map map = new HashMap<>();
                      map.put("status", 200);
                      map.put("msg", principal);
                      ObjectMapper om = new ObjectMapper();
                      out.write(om.writeValueAsString(map));
                      out.flush();
                      out.close();
                  }
              })
              .failureHandler(new AuthenticationFailureHandler() {
                  @Override
                  public void onAuthenticationFailure(HttpServletRequest req,
                                                      HttpServletResponse resp,
                                                      AuthenticationException e)
                      throws IOException {
                      resp.setContentType("application/json;charset=utf-8");
                      PrintWriter out = resp.getWriter();
                      resp.setStatus(401);
                      Map map = new HashMap<>();
                      map.put("status", 401);
                      if (e instanceof LockedException) {
                          map.put("msg", "账户被锁定,登录失败!");
                      } else if (e instanceof BadCredentialsException) {
                          map.put("msg", "账户名或密码输入错误,登录失败!");
                      } else if (e instanceof DisabledException) {
                          map.put("msg", "账户被禁用,登录失败!");
                      } else if (e instanceof AccountExpiredException) {
                          map.put("msg", "账户已过期,登录失败!");
                      } else if (e instanceof CredentialsExpiredException) {
                          map.put("msg", "密码已过期,登录失败!");
                      } else {
                          map.put("msg", "登录失败!");
                      }
                      ObjectMapper om = new ObjectMapper();
                      out.write(om.writeValueAsString(map));
                      out.flush();
                      out.close();
                  }
              })
              .permitAll()
              .and()
              .logout()
              .logoutUrl("/logout")
              .clearAuthentication(true)
              .invalidateHttpSession(true)
              .addLogoutHandler(new LogoutHandler() {
                  @Override
                  public void logout(HttpServletRequest req,
                                     HttpServletResponse resp,
                                     Authentication auth) {
                  }
              })
              .logoutSuccessHandler(new LogoutSuccessHandler() {
                  @Override
                  public void onLogoutSuccess(HttpServletRequest req,
                                              HttpServletResponse resp,
                                              Authentication auth)
                      throws IOException {
                      resp.sendRedirect("/login_page");
                  }
              })
              .and()
              .csrf()
              .disable();
      }

      代码解释:

      • logout() 表示开启注销登录的配置

      • logoutUrl(“/logout”) 表示注销登录请求 URL 为 /logout ,默认也是 /logout

      • clearAuthentication(true) 表示是否清楚身份认证信息,默认为 true

      • invalidateHttpSession(true) 表示是否使 Session 失效,默认为 true

      • addLogoutHandler 方法中完成一些数据清楚工作,例如 Cookie 的清楚

      • logoutSuccessHandler 方法用于处理注销成功后的业务逻辑,例如返回一段 JSON 提示或者跳转到登录页面等

      多个 HttpSecurity

      如果业务比较复杂,也可以配置多个 HttpSecurity ,实现对 WebSecurityConfigurerAdapter 的多次扩展

      @Configuration
      public class MultiHttpSecurityConfig {
          @Bean
          PasswordEncoder passwordEncoder() {
              return NoOpPasswordEncoder.getInstance();
          }
          @Autowired
          protected void configure(AuthenticationManagerBuilder auth) throws Exception {
              auth.inMemoryAuthentication()
                      .withUser("admin").password("123123").roles("ADMIN", "USER")
                      .and()
                      .withUser("tangsan").password("123123").roles("USER");
          }
          @Configuration
          @Order(1)
          public static class AdminSecurityConfig extends WebSecurityConfigurerAdapter{
              @Override
              protected void configure(HttpSecurity http) throws Exception {
                  http.antMatcher("/admin/**").authorizeRequests()
                          .anyRequest().hasRole("ADMIN");
              }
          }
          @Configuration
          public static class OtherSecurityConfig extends WebSecurityConfigurerAdapter{
              @Override
              protected void configure(HttpSecurity http) throws Exception {
                  http.authorizeRequests()
                          .anyRequest().authenticated()
                          .and()
                          .formLogin()
                          .loginProcessingUrl("/login")
                          .permitAll()
                          .and()
                          .csrf()
                          .disable();
              }
          }
      }

      代码解释:

      • 配置多个 HttpSecurity 时,MultiHttpSecurityConfig 不需要继承 WebSecurityConfigurerAdapter ,在 MultiHttpSecurityConfig 中创建静态内部类继承 WebSecurityConfigurerAdapter 即可,静态内部类上添加 @Configuration 注解和 @Order注解,数字越大优先级越高,未加 @Order 注解的配置优先级最低

      • AdminSecurityConfig 类表示该类主要用来处理 “/admin/**” 模式的 URL ,其它 URL 将在 OtherSecurityConfig 类中处理

      密码加密

      1. 为什么要加密

      略

      2. 加密方案

      Spring Security 提供了多种密码加密方案,官方推荐使用 BCryptPasswordEncoder,BCryptPasswordEncoder 使用 BCrypt 强哈希函数,开发者在使用时可以选择提供 strength 和 SecureRandom 实例。strength 越大,密码的迭代次数越多,密钥迭代次数为 2^strength 。strength 取值在 4~31 之间,默认为 10 。

      3. 实践

      只需要修改上文配置的 PasswordEncoder 这个 Bean 的实现即可

       @Bean
      PasswordEncoder passwordEncoder() {
          return new BCryptPasswordEncoder(10);
      }

      参数 10 就是 strength ,即密钥的迭代次数(也可以不配置,默认为 10)。

      使用以下方式获取加密后的密码。

      public static void main(String[] args) {
          BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder(10);
          String encode = bCryptPasswordEncoder.encode("123123");
          System.out.println(encode);
      }

      修改配置的内存用户的密码

      auth.inMemoryAuthentication()
          .withUser("admin")
          .password("$2a$10$.hZESNfpLSDUnuqnbnVaF..Xb2KsAqwvzN7hN65Gd9K0VADuUbUzy")
          .roles("ADMIN", "USER")
          .and()
          .withUser("tangsan")
          .password("$2a$10$4LJ/xgqxSnBqyuRjoB8QJeqxmUeL2ynD7Q.r8uWtzOGs8oFMyLZn2")
          .roles("USER");

      虽然 admin 和 tangsan 加密后的密码不一样,但是明文都是 123123 配置完成后,使用 admin/123123,或 tangsan/123123 就可以实现登录,一般情况下,用户信息是存储在数据库中的,因此需要用户注册时对密码进行加密处理

      @Service
      public class RegService {
          public int reg(String username, String password) {
              BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(10);
              String encodePasswod = encoder.encode(password);
              return saveToDb(username, encodePasswod);
          }
          private int saveToDb(String username, String encodePasswod) {
              // 业务处理
              return 0;
          }
      }

      用户将密码从前端传来之后,通过 BCryptPasswordEncoder 实例中的 encode 方法对密码进行加密处理,加密完成后将密文存入数据库。

      方法安全

      上文介绍的认证和授权都是基于 URL 的,开发者也可通过注解来灵活配置方法安全,使用相关注解,首先要通过 @EnableGlobalMethodSecurity 注解开启基于注解的安全配置

      @Configuration
      @EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled = true)
      public class MultiHttpSecurityConfig{
      }

      代码解释:

      • prePostEnabled = true 会解锁 @PreAuthorize 和 @PostAuthorize 两个注解, @PreAuthorize 注解会在方法执行前进行验证,而 @PostAuthorize 注解在方法执行后进行验证

      • securedEnabled = true 会解锁 @Secured 注解

      开启注解安全后,创建一个 MethodService 进行测试

      @Service
      public class MethodService {
          @Secured("ROLE_ADMIN")
          public String admin() {
              return "hello admin";
          }
          @PreAuthorize("hasRole('ADMIN') and hasRole('DBA')")
          public String dba() {
              return "hello dba";
          }
          @PreAuthorize("hasAnyRole('ADMIN','DBA','USER')")
          public String user() {
              return "user";
          }
      }

      代码解释:

      • @Secured(“ROLE_ADMIN”) 注解表示访问该方法需要 ADMIN 角色,注意这里需要在角色前加一个前缀 ROLE_

      • @PreAuthorize(“hasRole(‘ADMIN’) and hasRole(‘DBA’)”) 注解表示访问该方法既需要 ADMIN 角色又需要 DBA 角色

      • @PreAuthorize(“hasAnyRole(‘ADMIN’,‘DBA’,‘USER’)”) 表示访问该方法需要 ADMIN 、DBA 或 USER 角色中至少一个

      • @PostAuthorize 和 @PreAuthorize 中都可以使用基于表达式的语法

      最后在 Controller 中注入 Service 并调用 Service 中的方法进行测试

      @RestController
      public class HelloController {
          @Autowired
          MethodService methodService;
          @GetMapping("/hello")
          public String hello() {
              String user = methodService.user();
              return user;
          }
          @GetMapping("/hello2")
          public String hello2() {
              String admin = methodService.admin();
              return admin;
          }
          @GetMapping("/hello3")
          public String hello3() {
              String dba = methodService.dba();
              return dba;
          }
      }

      admin 访问 hello

      SpringBoot安全管理之Spring Security如何配置

      admin 访问 hello2

      SpringBoot安全管理之Spring Security如何配置

      admin 访问 hello3

      SpringBoot安全管理之Spring Security如何配置

      本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系bd@zhengruan.com
      作者最新文章
      编程开发
      相关文章 更多
      spring hibernate 详细教程:新手也能快速学会
      spring hibernate 详细教程:新手也能快速学会

      Spring与Hibernate是Java企业开发的核心框架。Spring通过依赖注入管理对象生命周期,简化开发;Hibernate作为ORM工具,实现对象与数据库映射,避免繁琐SQL。两者结合能构建结构清晰、易于维护的应用。教程涵盖环境搭建、实体映射、数据操作及事务管理等实践内容,帮助新手快速掌握整合开发。

      Spring Boot 注册 Servlet 的五种方法(最新整理)
      Spring Boot 注册 Servlet 的五种方法(最新整理)

      在 Spring Boot 里注册 Servlet,其实有不止一种做法,而且每种方式适合的场景也不太一样:有的更偏配置化,有的更适合注解扫描,还有的更强调运行时动态注册。下面把常见的五种方式完整梳理一遍,方便直接对照使用。 一、Spring Boot 提供了 ServletRegistrationB

      从IDEA到Docker部署Spring Boot环境变量配置详解
      从IDEA到Docker部署Spring Boot环境变量配置详解

      一、问题背景 在 Spring Boot 项目里,配置文件用占位符引用环境变量可以说是家常便饭了,比如下面这种写法: url: jdbc:mysql://${datasource_addr:10.xxx.xxx.xxx:5236}/mydb 但有一个现象挺让人头疼的:同样的配置,在 IDEA 里跑得

      Spring MVC+ajax进行信息验证的方法
      Spring MVC+ajax进行信息验证的方法

      采用AJAX与SpringMVC的异步验证,通过XMLHttpRequest对象实现局部数据交换,利用@ResponseBody返回JSON或字符串,结合@RequestBody解析请求体,完成表单无刷新校验,包括用户名重复检测与密码一致性验证。该方法提升交互效率,减少带宽占用,有效确保数据准确性,提升用户体验。

      PHP | Work 2005 讲座之 PHP效率,PHP安全,WebServices
      PHP | Work 2005 讲座之 PHP效率,PHP安全,WebServices

      PHP|Work2005讲座三大主题:PHP效率优化、WebService实现与陷阱、安全防御(注入、XSS等),每份幻灯片超46页,附资源链接下载。

      Spring Security 2026 构建安全、可靠的企业应用实践指南
      Spring Security 2026 构建安全、可靠的企业应用实践指南

      SpringSecurity2026是一款强大的安全框架,提供多因素认证、OAuth2.0与OpenIDConnect协议支持以及强密码管理功能;支持基于角色、权限和表达式的细粒度授权控制;同时涵盖跨站请求伪造与跨站脚本攻击等安全防护最佳实践,助力企业级应用安全构建。

      Spring Security 6+ JWT 配置正确用法详解
      Spring Security 6+ JWT 配置正确用法详解

      SpringSecurity6.x版本废弃了OAuth2ResourceServerConfigurer的jwt()等返回子配置器的方法,改为统一采用Lambda表达式和Customizer方式配置JWT认证。开发者需调用.jwt(Customizer.withDefaults())或自定义解码器,并配合authorizeHttpRequests()完成请求

      如何在 Spring 应用中实现每 15 秒一次的外部 API 调用限流
      如何在 Spring 应用中实现每 15 秒一次的外部 API 调用限流

      在Spring应用中,使用阻塞式RateLimiter与单线程执行器,通过acquire()阻塞实现固定间隔限流,确保外部API调用间隔至少15秒,防止突发请求,方案轻量可靠。

      Spring 中多事务管理器下 @Transactional 的行为解析
      Spring 中多事务管理器下 @Transactional 的行为解析

      在Spring多数据源场景中,@Transactional的REQUIRED级别不会跨不同事务管理器自动合并事务。各事务管理器彼此隔离,无法协调回滚。可通过ChainedTransactionManager将多个管理器串成顺序事务链,实现“全成功才提交,任一失败全部回滚”的近似一致性,但非强事务。

      Spring Data JPA 接入金仓数据库的详细步骤介绍
      Spring Data JPA 接入金仓数据库的详细步骤介绍

      配置Kingbase8Dialect方言并启用自动表结构更新,SpringDataJPA可接入金仓数据库。通过继承JpaRepository实现基础CRUD,支持自定义SQL及分页查询。相比MyBatis减少了手动编写映射文件的工作,显著提升开发效率。

      查看更多
      精品专题 更多
      装机必备
      装机必备

      正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

      Windows
      Windows

      正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

      PDF教程
      PDF教程

      正软商城PDF教程频道提供PDF编辑、转换、合并、拆分、压缩及格式处理方法,同时介绍常用PDF软件和工具的使用技巧。

      Mac软件 更多
      讯飞智作 网页版
      讯飞智作 网页版
      Windows/ macOS/ Linux

      讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

      火山引擎 网页版
      火山引擎 网页版
      Windows/ macOS/ Linux

      火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

      Aperty macOS版
      Aperty macOS版
      macOS

      Aperty 是 Skylum 推出的 AI 人像修图软件,支持自然肤质修饰、瑕疵处理、面部与身体调整、预设及批量同步。

      WINDOWS 更多
      讯飞智作 网页版
      讯飞智作 网页版
      Windows/ macOS/ Linux

      讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

      火山引擎 网页版
      火山引擎 网页版
      Windows/ macOS/ Linux

      火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

      PixTV 网页版
      PixTV 网页版
      Windows

      PixTV 是一款在浏览器中使用的 AI 视觉内容创作平台,提供图片、视频、音频、文字生成、AI 分镜、局部重绘、提示词反推和无限画布等工具,适合短剧、漫剧、广告及社交媒体内容制作。