Spring Boot 实战:基于 JWT 优化 Spring Security 无状态登录

Spring Boot 实战:基于 JWT 优化 Spring Security 无状态登录

在上一篇文章中,我们通过 Spring Security 实现了基础的接口权限控制,但采用的 HTTP Basic 登录存在明显缺陷:安全性,用户名和密码只是简单的通过 Base64 编码之后就开始传送了,很容易被破解,进而暴露用户信息。

本文将引入 JWT(JSON Web Token) 技术,重构登录流程,让接口即安全又贴合企业级项目需求。

一、先搞懂:为什么需要 JWT?

Jwt的核心优势是 无状态

  1. 登录成功后,服务器生成一个包含用户信息和权限的加密 Token,直接返回给客户端;
  2. 客户端后续请求时,只需在请求头携带 Token,服务器无需存储任何状态,直接通过 Token 验证身份和权限;
  3. 多台服务器共用一套 Token 验证逻辑,无需同步状态,完美适配分布式部署。

二、准备工作:添加 JWT 依赖

pom.xml<dependencies> 标签中,新增 JWT 相关依赖(基于 JJWT 框架,Spring 官方推荐):

xml 复制代码
        <!-- JWT 核心依赖(JJWT) -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-api</artifactId>
            <version>0.11.5</version>
        </dependency>

        <!-- JWT 实现依赖 -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-impl</artifactId>
            <version>0.11.5</version>
            <scope>runtime</scope>
        </dependency>

        <!-- JWT 加密算法依赖 -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-jackson</artifactId>
            <version>0.11.5</version>
            <scope>runtime</scope>
        </dependency>

添加后点击 IDEA 右上角的 "Load Maven Changes" 刷新依赖,确保无红色报错。

三、第一步:实现 JWT 工具类(核心)

JWT 的核心操作包括 生成 Token验证 Token解析 Token 中的用户信息,我们创建一个工具类封装这些逻辑,方便后续调用。

com.example.firstspringbootproject.utils 包下创建 JwtUtil 类:

typescript 复制代码
package com.example.firstspringbootproject.utils;

import io.jsonwebtoken.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;

@Component
public class JwtUtil {

    // 1. 从配置文件读取 JWT 关键参数(避免硬编码)
    @Value("${jwt.secret}") // 密钥(必须保密,建议在生产环境用环境变量配置)
    private String secret;

    @Value("${jwt.expiration}") // Token 过期时间(单位:毫秒,这里配置 2 小时)
    private long expiration;

    @Value("${jwt.header}") // 请求头中携带 Token 的字段名(如 Authorization)
    private String tokenHeader;

    @Value("${jwt.prefix}") // Token 前缀(如 Bearer,规范要求加空格)
    private String tokenPrefix;

    // 2. 生成 Token:基于用户信息(用户名、角色)
    public String generateToken(UserDetails userDetails) {

        // 存储 Token 中的自定义信息(Payload)
        Map<String, Object> claims = new HashMap<>();

        // 将用户角色存入 Token(后续验证权限用)
        claims.put("roles", userDetails.getAuthorities().stream()
                .map(authority -> authority.getAuthority())
                .toList());

        // 构建 Token 并返回
        return Jwts.builder()
                .setClaims(claims) // 自定义信息
                .setSubject(userDetails.getUsername()) // 用户名(唯一标识)
                .setIssuedAt(new Date()) // 签发时间
                .setExpiration(new Date(System.currentTimeMillis() + expiration)) // 过期时间
                .signWith(SignatureAlgorithm.HS512, secret) // 加密算法(HS512)+ 密钥
                .compact();
    }

    // 3. 从 Token 中获取用户名
    public String getUsernameFromToken(String token) {
        return getClaimFromToken(token, Claims::getSubject);
    }

    // 4. 验证 Token 是否有效(未过期 + 用户名匹配)
    public boolean validateToken(String token, UserDetails userDetails) {
        String username = getUsernameFromToken(token);

        // 验证逻辑:用户名一致 + Token 未过期 + Token 未被篡改
        return username.equals(userDetails.getUsername())
                && !isTokenExpired(token);

    }

    // 5. 从 Token 中获取自定义角色信息
    public String getRoleFromToken(String token) {

        Claims claims = getAllClaimsFromToken(token);
        // 从自定义信息中获取角色列表(这里简化为单个角色,多角色可返回 List)
        return ((List<String>) claims.get("roles")).get(0);

    }

    // ------------------------------

    // 以下是内部工具方法(无需外部调用)

    // ------------------------------

    // 解析 Token,获取所有自定义信息(Payload)
    private Claims getAllClaimsFromToken(String token) {

        try {
            return Jwts.parser()
                    .setSigningKey(secret) // 用密钥解密
                    .parseClaimsJws(token)
                    .getBody();
        } catch (ExpiredJwtException | MalformedJwtException | SignatureException
                 | IllegalArgumentException | UnsupportedJwtException e) {
            // 捕获 Token 异常(过期、格式错误、签名错误等)
            throw new RuntimeException("无效的 Token:" + e.getMessage());
        }

    }

    // 从 Token 中获取指定信息(通用方法)
    private <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
        final Claims claims = getAllClaimsFromToken(token);
        return claimsResolver.apply(claims);

    }

    // 判断 Token 是否过期
    private boolean isTokenExpired(String token) {

        final Date expirationDate = getClaimFromToken(token, Claims::getExpiration);
        return expirationDate.before(new Date());

    }

    // 6. 辅助方法:从请求头中提取 Token(去除前缀)
    public String extractTokenFromHeader(String header) {

        if (header != null && header.startsWith(tokenPrefix)) {
            // 例如:header = "Bearer eyJhbGciOiJIUzI1NiJ9...",返回后面的 Token 部分
            return header.substring(tokenPrefix.length()).trim();
        }
        return null;

    }

    // Getter 方法(供外部获取配置参数)
    public String getTokenHeader() {

        return tokenHeader;

    }

    public String getTokenPrefix() {
        return tokenPrefix;
    }

}

四、第二步:配置 JWT 参数(避免硬编码)

src/main/resources/application.yml(或 application.properties)中,添加 JWT 相关配置(替换硬编码,方便后续修改):

yaml 复制代码
# JWT 配置
jwt:
  secret: firstspringbootproject2025secretkeyfirstspringbootproject2025secretkeyfirstspringbootproject2025secretkey # 密钥(生产环境建议用 64 位以上随机字符串)
#  expiration: 7200000 # Token 过期时间(7200000 毫秒 = 2 小时)
  expiration: 1000 # Token 过期时间(1000 毫秒 = 1 秒)
  header: Authorization # 请求头字段名
  prefix: Bearer  # Token 前缀(注意末尾有空格)

五、第三步:实现 JWT 登录接口(替换 HTTP Basic)

HTTP Basic 登录是通过浏览器弹窗输入账号密码,体验较差;我们需要自定义一个 登录接口 (如 /api/login),客户端通过 JSON 提交账号密码,服务器验证通过后返回 JWT Token。

1. 创建登录请求参数实体类

com.example.firstspringbootproject.dto 包下创建 LoginRequestDTO(接收客户端提交的账号密码):

less 复制代码
package com.example.firstspringbootproject.dto;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;

@Data
@Schema(name = "LoginRequestDTO", description = "登录请求参数")
public class LoginRequestDTO {

    @NotBlank(message = "用户名不能为空")
    @Schema(description = "用户名", example = "admin")
    private String username;

    @NotBlank(message = "密码不能为空")
    @Schema(description = "密码", example = "123456")
    private String password;

}

2. 创建登录控制器

com.example.firstspringbootproject.controller 包下创建 AuthController,实现登录接口:

less 复制代码
package com.example.firstspringbootproject.controller;

import com.example.firstspringbootproject.dto.LoginRequestDTO;
import com.example.firstspringbootproject.common.Result;
import com.example.firstspringbootproject.utils.JwtUtil;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/api")
@Tag(name = "认证接口", description = "登录、Token 相关接口")
public class AuthController {

    @Autowired
    private AuthenticationManager authenticationManager; // Spring Security 认证管理器

    @Autowired
    private JwtUtil jwtUtil; // 自定义 JWT 工具类

    /**
     * 登录接口:接收账号密码,验证通过后返回 JWT Token
     */

    @PostMapping("/login")
    @Operation(summary = "用户登录", description = "提交用户名和密码,获取 JWT Token")
    public Result<Map<String, String>> login(@Valid @RequestBody LoginRequestDTO loginRequest) {

        // 1. 调用 Spring Security 认证管理器,验证账号密码
        Authentication authentication = authenticationManager.authenticate(
                new UsernamePasswordAuthenticationToken(
                        loginRequest.getUsername(),
                        loginRequest.getPassword()
                )
        );

        // 2. 认证通过:从认证结果中获取用户信息
        UserDetails userDetails = (UserDetails) authentication.getPrincipal();

        // 3. 生成 JWT Token
        String token = jwtUtil.generateToken(userDetails);

        // 4. 构建返回结果(包含 Token 和过期提示)
        Map<String, String> resultMap = new HashMap<>();
        resultMap.put("token", token);
        resultMap.put("expiration", "Token 有效期 2 小时,请及时刷新");
        resultMap.put("role", jwtUtil.getRoleFromToken(token)); // 返回用户角色,方便前端处理

        return Result.success(resultMap);
    }

}

六、第四步:实现 JWT 认证过滤器(核心拦截逻辑)

客户端登录成功后,后续请求会在 Authorization 头中携带 Token(格式:Bearer eyJhbGciOiJIUzI1NiJ9...)。我们需要自定义一个 过滤器,在请求到达接口前拦截 Token,完成以下操作:

  1. 从请求头中提取 Token;
  2. 验证 Token 有效性;
  3. 从 Token 中解析用户信息和角色;
  4. 将用户信息存入 Spring Security 上下文,让后续权限校验生效。

com.example.firstspringbootproject.filter 包下创建 JwtAuthenticationFilter 类:

scala 复制代码
package com.example.firstspringbootproject.filter;

import com.example.firstspringbootproject.utils.JwtUtil;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;

// 自定义 JWT 认证过滤器:每次请求都会执行(OncePerRequestFilter 确保一次请求只执行一次)
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    @Autowired
    private JwtUtil jwtUtil;

    @Autowired
    private UserDetailsService userDetailsService; // 之前实现的用户查询服务

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain
    ) throws ServletException, IOException {

        try {
            // 1. 从请求头中提取 Token
            String token = jwtUtil.extractTokenFromHeader(
                    request.getHeader(jwtUtil.getTokenHeader())
            );

            // 2. 验证 Token:非空 + 有效
            if (token != null) {

                // 2.1 从 Token 中获取用户名
                String username = jwtUtil.getUsernameFromToken(token);

                // 2.2 若用户名存在,且 Spring Security 上下文未存储用户信息(未登录)
                if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {

                    // 2.3 从数据库查询用户完整信息(UserDetails)
                    UserDetails userDetails = userDetailsService.loadUserByUsername(username);

                    // 2.4 验证 Token 有效性(未过期 + 用户名匹配)
                    if (jwtUtil.validateToken(token, userDetails)) {

                        // 3. 构建认证对象,存入 Spring Security 上下文
                        UsernamePasswordAuthenticationToken authentication =
                                new UsernamePasswordAuthenticationToken(
                                        userDetails, // 用户信息
                                        null, // 密码(已验证,无需存储)
                                        userDetails.getAuthorities() // 用户权限(角色)
                                );

                        // 设置请求详情(如 IP、会话 ID)
                        authentication.setDetails(
                                new WebAuthenticationDetailsSource().buildDetails(request)
                        );

                        // 将认证对象存入上下文:后续接口权限校验会从这里获取用户信息
                        SecurityContextHolder.getContext().setAuthentication(authentication);
                    }
                }
            }
        } catch (Exception e) {

            // Token 验证失败(如过期、篡改),打印日志但不阻断请求(后续会返回 401)
            logger.error("JWT Token 验证失败:" + e.getMessage());

        }

        // 4. 继续执行过滤器链(让请求到达后续接口或过滤器)
        filterChain.doFilter(request, response);
    }

}

七、第五步:重构 SecurityConfig(适配 JWT)

之前的 SecurityConfig 基于 HTTP Basic 登录,现在需要修改为 JWT 无状态登录,核心调整点:

  1. 关闭 Session(无状态登录不需要 Session);
  2. 放行登录接口(/api/login);
  3. 将 JWT 过滤器加入过滤器链;
  4. 移除 HTTP Basic 配置,保留统一异常处理。

修改 com.example.firstspringbootproject.config.SecurityConfig 类:

kotlin 复制代码
package com.example.firstspringbootproject.config;

import com.example.firstspringbootproject.common.Result;
import com.example.firstspringbootproject.entity.SysUser;
import com.example.firstspringbootproject.filter.JwtAuthenticationFilter;
import com.example.firstspringbootproject.mapper.SysUserMapper;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@EnableWebSecurity
@EnableMethodSecurity // 启用方法级权限控制(如 @PreAuthorize("hasRole('ADMIN')"))
public class SecurityConfig {

    @Autowired
    private SysUserMapper sysUserMapper;

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private JwtAuthenticationFilter jwtAuthenticationFilter; // 自定义 JWT 过滤器

    // 1. 密码编码器(不变)

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();

    }

    // 2. 用户详情服务(不变)
    @Bean
    public UserDetailsService userDetailsService() {
        return username -> {

            SysUser sysUser = sysUserMapper.findByUsername(username);

            if (sysUser == null) {
                throw new UsernameNotFoundException("用户不存在: " + username);
            }
            return sysUser;

        };

    }

    // 3. 认证提供者(不变)
    @Bean
    public DaoAuthenticationProvider authenticationProvider() {

        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
        provider.setUserDetailsService(userDetailsService());
        provider.setPasswordEncoder(passwordEncoder());
        return provider;
    }

    // 4. 认证管理器(不变)
    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
        return config.getAuthenticationManager();
    }

    // 5. 核心规则配置(重点修改:适配 JWT)
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {

        http
                // 1. 关闭 CSRF(前后端分离必须关)
                .csrf(csrf -> csrf.disable())
                // 2. 关闭 Session(无状态登录核心:不创建和使用 Session)
                .sessionManagement(session -> session
                        .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                )
                // 3. 配置接口访问规则(调整放行接口)
                .authorizeHttpRequests(auth -> auth
                        // ① 放行:接口文档(Knife4j)、登录接口
                        .requestMatchers("/doc.html", "/webjars/**", "/v3/api-docs/**", "/api/login").permitAll()
                        // ② 管理员接口:仅 ADMIN 可访问
                        .requestMatchers("/api/user/all").hasRole("ADMIN")
                        // ③ 普通用户接口:USER/ADMIN 可访问
                        .requestMatchers("/api/user/**", "/api/product/**").hasAnyRole("USER", "ADMIN")
                        // ④ 其他接口:必须登录(Token 有效)
                        .anyRequest().authenticated()
                )

                // 4. 加入 JWT 过滤器:在 UsernamePasswordAuthenticationFilter 之前执行
                // (先验证 Token,再执行后续认证逻辑)
                .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
                // 5. 统一异常处理(不变:未登录返回 401,权限不足返回 403)
                .exceptionHandling(ex -> ex
                        .authenticationEntryPoint((request, response, authException) -> {
                            response.setContentType("application/json;charset=UTF-8");
                            Result<Void> result = Result.error(401, "未登录或 Token 已过期,请重新登录");
                            response.getWriter().write(objectMapper.writeValueAsString(result));
                        })
                        .accessDeniedHandler((request, response, accessDeniedException) -> {
                            response.setContentType("application/json;charset=UTF-8");
                            Result<Void> result = Result.error(403, "权限不足,无法访问");
                            response.getWriter().write(objectMapper.writeValueAsString(result));
                        })
                );

        // 关联认证提供者
        http.authenticationProvider(authenticationProvider());
        return http.build();

    }

}

八、第六步:测试 JWT 无状态登录流程

启动项目,用 Apifox 测试完整流程,验证无状态登录和权限控制是否生效:

1. 测试 1:调用登录接口,获取 Token

  • 请求地址http://localhost:8080/api/login
  • 请求方法:POST
  • 请求体(JSON)
json 复制代码
{
    "username": "admin",
    "password": "123456"
}
  • 响应结果(成功)
css 复制代码
{
    "code": 200,
    "msg": "success",
    "data": {
        "role": "ROLE_ADMIN",
        "expiration": "Token 有效期 2 小时,请及时刷新",
        "token": "eyJhbGciOiJIUzUxMiJ9.eyJyb2xlcyI6WyJST0xFX0FETUlOIl0sInN1YiI6ImFkbWluIiwiaWF0IjoxNzYzNDgxMDk0LCJleHAiOjE3NjM0ODgyOTR9.hSxBCTlQo5tUai-knfM2Sh4nVmehizOslcHkvG86jdC-U7-EztskPkF0r4G0DcaTMV3eFcJUu4pCewDdnniq2A"
    }
}

复制返回的 token,后续请求会用到。

2. 测试 2:携带 Token 访问管理员接口

  • 请求地址http://localhost:8080/api/user/all(需 ADMIN 角色)
  • 请求方法:GET
  • 请求头 :添加 Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJyb2xlcyI6WyJST0xFX0FETUlOIl0sInN1YiI6ImFkbWluIiwiaWF0IjoxNzYzNDgxMDk0LCJleHAiOjE3NjM0ODgyOTR9.hSxBCTlQo5tUai-knfM2Sh4nVmehizOslcHkvG86jdC-U7-EztskPkF0r4G0DcaTMV3eFcJUu4pCewDdnniq2A(注意 Bearer 后有空格)
  • 响应结果(成功)
css 复制代码
{
    "code": 200,
    "msg": "success",
    "data": [
        {
            "id": 1,
            "name": "小明",
            "age": 20,
            "phone": "13800138000"
        },
        {
            "id": 2,
            "name": "小红",
            "age": 19,
            "phone": "13900139000"
        },
        {
            "id": 3,
            "name": "小李",
            "age": 22,
            "phone": "13700137000"
        }
    ]
}

3. 测试 3:普通用户 Token 访问管理员接口(权限不足)

  • 先用 zhangsan(密码 123456)调用登录接口,获取普通用户 Token;
  • 携带普通用户 Token 访问 http://localhost:8080/api/user/all
  • 响应结果(失败)
json 复制代码
{
    "code": 403,
    "msg": "权限不足,无法访问",
    "data": null
}

4. 测试 4:Token 过期(模拟)

  • 修改 application.ymljwt.expiration1000(1 秒过期);
  • 重新登录获取 Token,1 秒后携带 Token 访问接口;
  • 响应结果(失败)
json 复制代码
{
    "code": 401,
    "msg": "未登录或 Token 已过期,请重新登录",
    "data": null
}

九、常见问题与优化建议

1. 问题:Token 被盗用怎么办?

  • 原因:JWT Token 一旦生成,在过期前无法主动作废;
  • 解决方案
  1. 缩短 Token 过期时间(如 30 分钟),同时实现 Token 刷新接口(用旧 Token 换取新 Token,避免频繁登录);
  2. 维护一个 黑名单(如 Redis),用户登出或 Token 被盗时,将 Token 加入黑名单,验证时先检查是否在黑名单中。

2. 问题:密钥(secret)泄露怎么办?

  • 原因:密钥是 JWT 安全的核心,泄露后攻击者可伪造 Token;
  • 解决方案
  1. 生产环境中,密钥通过 环境变量配置中心 注入,不写入代码或配置文件;
  2. 使用 非对称加密算法(如 RS256),用私钥生成 Token,公钥验证 Token,私钥严格保密。

## 十、总结:JWT 无状态登录的核心价值

通过本文的改造,我们实现了 Spring Boot + Spring Security + JWT 的无状态登录,核心优势总结如下:

  1. 分布式友好:服务器无需存储 Session,多台服务器可直接共用 Token 验证逻辑;
  2. 前后端分离适配:通过请求头携带 Token,无需依赖 Cookie,适配移动端、小程序等多端场景;
  3. 安全性提升:Token 包含过期时间和加密签名,避免身份信息被篡改;
  4. 扩展性强:Token 可自定义存储用户角色、权限等信息,减少数据库查询次数。

至此,恭喜你已具备初级程序员开发水平,可以试着投简历找工作啦。

👉 关注 + 私信 "Web基础源码",获取本文完整工程!有问题评论区见~

相关推荐
吴佳浩2 小时前
Python入门指南(五) - 为什么选择 FastAPI?
后端·python·fastapi
memgLIFE2 小时前
Springboot 分层结构
java·spring boot·spring
GoGeekBaird2 小时前
分享几个使用Nano Banana Pro 画信息图的提示词
后端·github
shoubepatien3 小时前
JAVA -- 08
java·后端·intellij-idea
yangminlei3 小时前
springboot pom.xml配置文件详细解析
java·spring boot·后端
黄俊懿3 小时前
【深入理解SpringCloud微服务】Seata(AT模式)源码解析——全局事务的提交
java·后端·spring·spring cloud·微服务·架构·架构师
白宇横流学长3 小时前
基于SpringBoot实现的历史馆藏系统设计与实现【源码+文档】
java·spring boot·后端
moxiaoran57533 小时前
Go语言结构体
开发语言·后端·golang
爱海贼的无处不在4 小时前
现在还有Java面试者不会开发Starter组件
后端·面试·架构
2501_921649494 小时前
免费获取股票历史行情与分时K线数据 API
开发语言·后端·python·金融·数据分析