Spring Boot 登录实现:JWT 与 Session 全面对比与实战讲解
Spring Boot 登录实现:JWT 与 Session 全面对比与实战讲解
2025.5.21-23:11今天在学习黑马点评时突然发现用的是与苍穹外卖jwt不一样的登录方式-Session,于是就想记录一下这两种方式有什么不同
在实际开发中,登录认证是后端最基础也是最重要的模块之一。常见的实现方式主要有两种:Session 登录 和 JWT(JSON Web Token)登录。
下面将从原理、使用场景、优缺点、代码结构等角度,来对比这两种方案,然后给出 Spring Boot 中的实际应用建议,帮助在实际项目中做出合理选择。
一、登录认证基本流程
无论是 JWT 还是 Session,登录的本质流程都是:
- 用户发送用户名和密码;
- 后端验证成功后,生成认证信息;
- 后端将认证信息返回给客户端;
- 客户端携带认证信息访问受保护接口;
- 后端验证该认证信息是否合法。
二、Session 登录机制
1. 原理说明
- 用户第一次登录成功后,服务器创建一个
Session
,并在服务器内存或 Redis 中保存用户信息; - 同时将 Session ID 写入到浏览器的 Cookie 中;
- 用户每次请求都会自动携带该 Cookie,服务器根据 Session ID 获取用户信息。
2. 代码实现示例
登录接口:
@PostMapping("/login")
public Result login(@RequestBody LoginDTO loginDTO, HttpSession session) {User user = userService.login(loginDTO);session.setAttribute("user", user);return Result.success();
}
拦截器判断是否登录:
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {Object user = request.getSession().getAttribute("user");if (user == null) {response.setStatus(401);return false;}return true;
}
三、JWT 登录机制
1. 原理说明
- 用户登录成功后,服务器签发一个加密的 JWT Token;
- 客户端将 Token 保存在 LocalStorage 或 Cookie;
- 每次请求都将 Token 放在 Authorization 请求头中;
- 服务器解析并验证 Token,从中读取用户信息。
2. JWT Token 的组成
JWT 一般由三部分组成:
Header.Payload.Signature
例如:
eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOjEyMywidXNlcm5hbWUiOiJsaTIzIn0.sD_Kpdi2M...
3. JWT 登录代码示例
登录生成 Token:
@PostMapping("/login")
public Result login(@RequestBody LoginDTO loginDTO) {User user = userService.login(loginDTO);String token = JwtUtil.generateToken(user); // 自定义工具类生成 tokenreturn Result.success(token);
}
前端请求携带 Token:
GET /user/info HTTP/1.1
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...
拦截器验证 Token:
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {String token = request.getHeader("Authorization");Claims claims = JwtUtil.parseToken(token);if (claims == null) {response.setStatus(401);return false;}return true;
}
四、Session vs JWT 对比总结
特性 | Session | JWT |
---|---|---|
存储位置 | 服务端(内存/Redis) | 客户端(LocalStorage/Cookie) |
状态性 | 有状态 | 无状态 |
跨域支持 | 不友好(需处理 Cookie) | 友好 |
性能 | 频繁读写服务端存储 | 只需签名验证 |
安全性 | 较高(信息在服务端) | 中等(需防止 Token 泄露) |
适用场景 | 传统 Web 应用 | 前后端分离、微服务、移动端 |
五、实战项目推荐
在实际项目中,建议根据以下场景选择:
- 中小型 Web 应用:推荐使用 Session,实现简单,安全性高;
- 前后端分离项目:推荐使用 JWT,无状态,跨域友好;
- 大型系统:考虑 JWT + Session 混合使用,结合两者优势。
六、JWT 常见安全建议
- 设置过期时间(exp),避免 token 永久有效;
- 使用 HTTPS,防止 Token 被中间人截获;
- 配合 Refresh Token 实现续签;
- 用户登出时将 Token 加入 Redis 黑名单;
- Token 不应包含敏感信息(如密码、身份证号等)。
七、总结
Session 和 JWT 各有优缺点,选择时需根据项目实际情况权衡:
- Session 适合传统 Web 应用,安全性高,实现简单;
- JWT 适合前后端分离、微服务架构,无状态,扩展性强。
在实际开发中,还可以结合两者的优势,实现更灵活、安全的认证方案。
八、登录超时控制
1. Session 登录的过期控制
# application.yml 中配置 Session 失效时间(单位分钟)
server:servlet:session:timeout: 30
Session 登录通常依赖浏览器 Cookie,每次请求自动续期,适合长期在线场景。
2. JWT 的过期控制
JWT 自带 exp 字段,在生成 Token 时设置过期时间:
// JwtUtil 生成 Token 示例
public static String generateToken(User user) {Date now = new Date();Date expireDate = new Date(now.getTime() + EXPIRE_TIME); // 设置过期时间return Jwts.builder().setHeaderParam("typ", "JWT").setSubject(user.getId().toString()).setIssuedAt(now).setExpiration(expireDate).claim("username", user.getUsername()).signWith(SignatureAlgorithm.HS512, SECRET) // 签名加密.compact();
}
九、JWT 的刷新机制(Refresh Token)
为了避免用户频繁登录,可以实现 Refresh Token 机制:
- 登录时生成两个 Token:AccessToken(短过期)和 RefreshToken(长过期);
- AccessToken 过期后,使用 RefreshToken 重新获取 AccessToken;
- RefreshToken 也过期时,才需要用户重新登录。
// 登录接口返回两个 Token
@PostMapping("/login")
public Result login(@RequestBody LoginDTO loginDTO) {User user = userService.login(loginDTO);String accessToken = JwtUtil.generateAccessToken(user);String refreshToken = JwtUtil.generateRefreshToken(user);Map<String, String> tokens = new HashMap<>();tokens.put("accessToken", accessToken);tokens.put("refreshToken", refreshToken);return Result.success(tokens);
}// 刷新 Token 接口
@PostMapping("/refreshToken")
public Result refreshToken(@RequestBody RefreshTokenDTO dto) {// 验证 RefreshToken 有效性Claims claims = JwtUtil.parseRefreshToken(dto.getRefreshToken());if (claims == null) {return Result.error("登录已过期,请重新登录");}// 重新生成 AccessTokenUser user = userService.getById(Long.valueOf(claims.getSubject()));String accessToken = JwtUtil.generateAccessToken(user);return Result.success(accessToken);
}
十、登出与 Redis 黑名单机制
JWT 本身无法主动失效,但可以通过 Redis 黑名单实现:
// 登出接口
@PostMapping("/logout")
public Result logout(HttpServletRequest request) {String token = request.getHeader("Authorization");if (token != null && token.startsWith("Bearer ")) {token = token.substring(7);// 解析 Token 获取过期时间Claims claims = JwtUtil.parseToken(token);if (claims != null) {Date expireDate = claims.getExpiration();long expireSeconds = (expireDate.getTime() - System.currentTimeMillis()) / 1000;// 将 Token 加入 Redis 黑名单,直到过期redisTemplate.opsForValue().set("jwt:blacklist:" + token, "invalid", expireSeconds, TimeUnit.SECONDS);}}return Result.success();
}// 拦截器中检查黑名单
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {String token = request.getHeader("Authorization");if (token != null && token.startsWith("Bearer ")) {token = token.substring(7);// 检查黑名单if (redisTemplate.hasKey("jwt:blacklist:" + token)) {response.setStatus(401);return false;}// 验证 TokenClaims claims = JwtUtil.parseToken(token);if (claims == null) {response.setStatus(401);return false;}// 将用户信息存入请求request.setAttribute("userId", claims.getSubject());}return true;
}
十一、实际开发中推荐的 JWT 完整实现流程图
用户登录请求
│
▼
后端验证用户名密码
│
▼
验证成功
│
├─┬─ 生成 AccessToken(含用户 ID、角色等)
│ │
│ ├─ 生成 RefreshToken(与用户 ID 绑定)
│ │
│ └─ 将 RefreshToken 存入 Redis(设置过期时间)
│
▼
返回 AccessToken 和 RefreshToken 给前端
│
▼
前端保存 Token(如 LocalStorage)
│
▼
前端每次请求携带 AccessToken(放在 Header)
│
▼
后端拦截器验证 AccessToken
│
├─ 验证失败 ──┐
│ │
│ ▼
│ 返回 401 未授权
│ │
│ ▼
│ 前端使用 RefreshToken 请求新 AccessToken
│ │
│ ▼
│ 后端验证 RefreshToken
│ │
│ ┌───────┴───────┐
│ │ │
│ 有效 无效
│ │ │
│ ▼ ▼
│ 生成新 Token 用户重新登录
│ │
│ ▼
返回新 AccessToken
十二、附录:JWT 工具类参考实现
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;import java.util.Date;
import java.util.HashMap;
import java.util.Map;@Component
public class JwtUtil {@Value("${jwt.secret}")private String secret;@Value("${jwt.accessTokenExpireTime}")private long accessTokenExpireTime; // 分钟@Value("${jwt.refreshTokenExpireTime}")private long refreshTokenExpireTime; // 天/*** 生成 Access Token*/public String generateAccessToken(User user) {Map<String, Object> claims = new HashMap<>();claims.put("username", user.getUsername());claims.put("roles", user.getRoles());return generateToken(user.getId().toString(), claims, accessTokenExpireTime * 60 * 1000);}/*** 生成 Refresh Token*/public String generateRefreshToken(User user) {return generateToken(user.getId().toString(), null, refreshTokenExpireTime * 24 * 60 * 60 * 1000);}/*** 生成 Token*/private String generateToken(String subject, Map<String, Object> claims, long expireTime) {Date now = new Date();Date expireDate = new Date(now.getTime() + expireTime);return Jwts.builder().setHeaderParam("typ", "JWT").setClaims(claims).setSubject(subject).setIssuedAt(now).setExpiration(expireDate).signWith(SignatureAlgorithm.HS512, secret).compact();}/*** 解析 Token*/public Claims parseToken(String token) {try {return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();} catch (Exception e) {return null;}}/*** 验证 Token 是否过期*/public boolean isTokenExpired(String token) {Claims claims = parseToken(token);if (claims == null) {return true;}Date expiration = claims.getExpiration();return expiration.before(new Date());}
}
十三、JWT 与 Session 混合使用的实践方案
在一些大型系统中,可能存在不同端口或子系统使用不同的认证方式(如后台管理系统用 Session,移动端用 JWT),这时可以采用 混合认证策略:
1. 使用建议
模块 | 建议认证方式 |
---|---|
管理后台(单体、Spring MVC) | 使用 Session(Cookie 自动管理,方便权限控制) |
移动端 / 小程序 / H5 | 使用 JWT(无状态认证,跨域安全,适合 API) |
多端统一用户体系 | JWT + Session 混合,并通过 Redis 存储登录状态 |
2. 核心实现思路
- 登录成功时,服务端生成 JWT,同时创建一个短 Session,用于后台系统交互;
- Redis 中统一存储用户 Token 状态,便于管理和失效控制;
- 拦截器判断请求来源(如是否含
Authorization
),自动适配认证方式。
十四、常见问题 FAQ
Q1:JWT 一旦泄露是否会被无限使用?
是的,如果没有设置过期时间或失效机制(如黑名单),Token 会一直有效。所以必须:
- 设置过期时间;
- 使用 HTTPS;
- 实现登出逻辑(Redis 黑名单);
Q2:JWT 是不是比 Session 更安全?
不完全正确。JWT 只是在无状态架构下更合适,但它暴露信息在客户端,若加密不严密,反而更容易被篡改或伪造。而 Session 只存在服务端,反而更隐蔽和安全。
Q3:JWT 必须存放在 LocalStorage 吗?
不一定。也可以存放在:
LocalStorage
(常见方式,刷新页面不丢失);SessionStorage
(更安全,但刷新页面会清空);HttpOnly Cookie
(防 XSS,但不支持 JS 访问,需配合后端设置跨域 Cookie);
十六、推荐学习与实践路径
1. 学会使用 Spring MVC + Session 登录机制
在 Spring MVC 项目中,Session 登录是一种传统且有效的认证方式。
登录接口实现:
通过 HttpSession
对象将用户信息存入 Session:
@PostMapping("/login")
public String login(@RequestParam String username, @RequestParam String password, HttpSession session) {User user = userService.login(username, password);if (user != null) {session.setAttribute("user", user); // 存入 Sessionreturn "redirect:/home"; // 登录成功跳转}return "login"; // 登录失败返回登录页
}
拦截器验证登录状态:
编写拦截器检查 Session 中是否存在用户信息:
@Component
public class LoginInterceptor extends HandlerInterceptorAdapter {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {HttpSession session = request.getSession();User user = (User) session.getAttribute("user");if (user == null) { // 未登录则重定向到登录页response.sendRedirect("/login");return false;}return true; // 已登录,允许访问}
}
注册拦截器:
在 Spring MVC 配置类中指定拦截路径(如 /api/**
)和排除路径(如 /login
):
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {@Autowiredprivate LoginInterceptor loginInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(loginInterceptor).addPathPatterns("/api/**") // 拦截所有 API 接口.excludePathPatterns("/login", "/static/**"); // 排除登录页和静态资源}
}
2. 掌握前后端分离项目中 JWT 的基本使用方式
JWT 工具类实现:
生成和解析 Token,包含过期时间和签名加密:
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;public class JwtUtil {private static final String SECRET = "your_secret_key_keep_it_secure"; // 密钥(需保密)private static final long EXPIRATION_TIME = 3600000; // 1小时(毫秒)// 生成 Tokenpublic static String generateToken(String username) {Claims claims = Jwts.claims().setSubject(username); // 载荷存储用户标识Date now = new Date();Date expiration = new Date(now.getTime() + EXPIRATION_TIME); // 设置过期时间return Jwts.builder().setClaims(claims).setIssuedAt(now).setExpiration(expiration).signWith(SignatureAlgorithm.HS256, SECRET) // HS256 签名算法.compact();}// 解析 Tokenpublic static Claims parseToken(String token) {try {return Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody(); // 成功解析返回载荷} catch (Exception e) {return null; // 解析失败返回 null}}
}
登录接口返回 Token:
验证用户信息后生成 Token 并返回给前端:
@PostMapping("/login")
public ResponseEntity<Map<String, String>> login(@RequestBody UserLoginRequest request) {User user = userService.authenticate(request.getUsername(), request.getPassword());if (user != null) {String token = JwtUtil.generateToken(user.getUsername());Map<String, String> result = new HashMap<>();result.put("token", token);return ResponseEntity.ok(result); // 返回 Token}return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(null); // 认证失败
}
后端验证 Token:
通过拦截器从请求头中获取 Token 并解析验证:
public class JwtInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {String token = request.getHeader("Authorization");if (token != null && token.startsWith("Bearer ")) {token = token.substring(7); // 去除 "Bearer " 前缀Claims claims = JwtUtil.parseToken(token);if (claims != null) {// 解析成功,将用户信息存入请求(如用户 ID、角色)request.setAttribute("userId", claims.getSubject());return true;}}response.setStatus(HttpStatus.UNAUTHORIZED); // 未认证或 Token 无效return false;}
}
3. 实现 Token 过期 + 刷新机制
双 Token 设计:
- AccessToken:短有效期(如 1 小时),用于接口认证;
- RefreshToken:长有效期(如 7 天),用于刷新 AccessToken。
登录时返回双 Token:
@PostMapping("/login")
public ResponseEntity<Map<String, String>> login(@RequestBody UserLoginRequest request) {User user = userService.authenticate(request.getUsername(), request.getPassword());if (user != null) {String accessToken = JwtUtil.generateToken(user.getUsername(), "access"); // 短过期String refreshToken = JwtUtil.generateToken(user.getUsername(), "refresh"); // 长过期Map<String, String> tokens = new HashMap<>();tokens.put("accessToken", accessToken);tokens.put("refreshToken", refreshToken);return ResponseEntity.ok(tokens);}return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(null);
}
刷新 Token 接口:
使用 RefreshToken 生成新的 AccessToken:
@PostMapping("/refresh-token")
public ResponseEntity<Map<String, String>> refreshToken(@RequestHeader("Refresh-Token") String refreshToken) {Claims claims = JwtUtil.parseToken(refreshToken);if (claims != null && "refresh".equals(claims.get("type"))) { // 验证 Token 类型String username = claims.getSubject();String newAccessToken = JwtUtil.generateToken(username, "access"); // 生成新 AccessTokenMap<String, String> result = new HashMap<>();result.put("accessToken", newAccessToken);return ResponseEntity.ok(result);}return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(null);
}
4. 实现 Redis 黑名单、登录限制、踢出机制
Redis 黑名单:
用户登出时将 Token 加入 Redis 黑名单,设置与 Token 剩余有效期一致的过期时间:
@Autowired
private RedisTemplate<String, Object> redisTemplate;// 登出逻辑
public void logout(String token) {Claims claims = JwtUtil.parseToken(token);if (claims != null) {long expireTime = claims.getExpiration().getTime() - System.currentTimeMillis();if (expireTime > 0) {redisTemplate.opsForValue().set("blacklist:" + token, "invalid", expireTime, TimeUnit.MILLISECONDS);}}
}// 拦截器中检查黑名单
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {String token = request.getHeader("Authorization");if (redisTemplate.hasKey("blacklist:" + token)) { // 存在于黑名单则拒绝访问response.setStatus(HttpStatus.UNAUTHORIZED);return false;}// 其他验证逻辑...return true;
}
登录频率限制:
使用 Redis 记录用户登录次数,限制每分钟最多 5 次尝试:
public boolean checkLoginFrequency(String username) {String key = "login:attempts:" + username;Integer count = (Integer) redisTemplate.opsForValue().get(key);if (count != null && count >= 5) { // 超过限制return false; // 禁止登录}// 次数加 1,设置过期时间 1 分钟redisTemplate.opsForValue().increment(key, 1);redisTemplate.expire(key, 1, TimeUnit.MINUTES);return true;
}
管理员踢出用户:
将用户所有有效 Token 加入黑名单(需结合用户 ID 批量操作):
public void kickUser(String userId) {// 查询用户所有有效 Token(需业务系统存储 Token 与用户的映射)List<String> tokens = tokenRepository.findByUserId(userId);tokens.forEach(token -> logout(token)); // 批量加入黑名单
}
5. 掌握 Spring Security 对 JWT 的整合
自定义 JWT 认证过滤器:
继承 OncePerRequestFilter
,解析 Token 并设置安全上下文:
public class JwtAuthenticationFilter extends OncePerRequestFilter {private final JwtUtil jwtUtil;public JwtAuthenticationFilter(JwtUtil jwtUtil) {this.jwtUtil = jwtUtil;}@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {String token = request.getHeader("Authorization");if (token != null && token.startsWith("Bearer ")) {token = token.substring(7);Claims claims = jwtUtil.parseToken(token);if (claims != null) {String username = claims.getSubject();// 构建认证对象(可添加角色权限)Authentication authentication = new UsernamePasswordAuthenticationToken(username, null, Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER")));SecurityContextHolder.getContext().setAuthentication(authentication); // 设置安全上下文}}filterChain.doFilter(request, response); // 继续执行后续过滤器}
}
Spring Security 配置:
禁用 CSRF,添加 JWT 过滤器并配置权限规则:
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {private final JwtUtil jwtUtil;public SecurityConfig(JwtUtil jwtUtil) {this.jwtUtil = jwtUtil;}@Overrideprotected void configure(HttpSecurity http) throws Exception {http.csrf().disable() // 前后端分离场景禁用 CSRF.authorizeRequests().antMatchers(HttpMethod.POST, "/login").permitAll() // 允许登录接口.antMatchers("/admin/**").hasRole("ADMIN") // 管理员接口需 ADMIN 角色.anyRequest().authenticated() // 其他接口需认证.and().addFilterBefore(new JwtAuthenticationFilter(jwtUtil), UsernamePasswordAuthenticationFilter.class); // 添加 JWT 过滤器}
}
通过 Spring Security 的整合,可更便捷地实现细粒度权限控制(如 @PreAuthorize
注解)和安全策略管理。
相关文章:

Spring Boot 登录实现:JWT 与 Session 全面对比与实战讲解
Spring Boot 登录实现:JWT 与 Session 全面对比与实战讲解 2025.5.21-23:11今天在学习黑马点评时突然发现用的是与苍穹外卖jwt不一样的登录方式-Session,于是就想记录一下这两种方式有什么不同 在实际开发中,登录认证是后端最基础也是最重要…...
【HTML-5】HTML 实体:完整指南与最佳实践
1. 什么是 HTML 实体? HTML 实体是一种在 HTML 文档中表示特殊字符的方法,这些字符如果直接使用可能会与 HTML 标记混淆,或者无法通过键盘直接输入。实体由 & 符号开始,以 ; 分号结束。 <p>这是一个小于符号的实体&am…...

SpringBoot 项目实现操作日志的记录(使用 AOP 注解模式)
本文是博主在做关于如何记录用户操作日志时做的记录,常见的项目中难免存在一些需要记录重要日志的部分,例如权限和角色设定,重要数据的操作等部分。 博主使用 Spring 中的 AOP 功能,结合注解的方式,对用户操作过的一些…...

AI|Java开发 IntelliJ IDEA中接入本地部署的deepseek方法
目录 连接本地部署的deepseek: IntelliJ IDEA中使用deepseek等AI: 用法一:让AI写代码 用法二:选中这段代码,右键,可以让其解释这段代码的含义。这时显示的解释是英文的。 连接本地部署的deepseek&#…...
【疑难杂症】Vue前端下载文件无法打开 已解决
由于刚学了VUE不久,不清楚底层逻辑。我遇到从后台下载文件无法打开的问题。 测试下来是,请求时未设置 responseType: blob。 axios 默认的 responseType 是 json ,会尝试将响应体解析为JSON。但文件下载场景需要后端返回二进制流࿰…...

【1——Android端添加隐私协议(unity)1/3】
前言:这篇仅对于unity 发布Android端上架国内应用商店添加隐私协议,隐私协议是很重要的东西,没有这个东西,是不上了应用商店的。 对于仅仅添加隐私协议,我知道有三种方式,第一种和第二种基本一样 1.直接在unity里面新…...

Linux之概述和安装vm虚拟机
文章目录 操作系统概述硬件和软件操作系统常见操作系统 初识LinuxLinux的诞生Linux内核Linux发行版 虚拟机介绍虚拟机 VMware WorkStation安装虚拟化软件VMware WorkStation 安装查看VM网络连接设置VM存储位置 在VMware上安装Linux(发行版CentOS7)安装包获取CentOS7 安装 Mac系…...
深入理解 Linux 的 set、env 和 printenv 命令
在 Linux 和类 Unix 系统中,环境变量是配置和管理 Shell 及进程行为的核心机制。set、env 和 printenv 是与环境变量交互的三个重要命令,每个命令都有其独特的功能和用途。本文将详细探讨这三个命令的区别,帮助大家更好地理解和使用这些命令。…...

LeetCode热题100--19.删除链表的倒数第N个结点--中等
1. 题目 给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。 示例 1: 输入:head [1,2,3,4,5], n 2 输出:[1,2,3,5] 示例 2: 输入:head [1], n 1 输出:[] 示例…...
开发AR导航助手:ARKit+Unity+Mapbox全流程实战教程
引言 在增强现实技术飞速发展的今天,AR导航应用正逐步改变人们的出行方式。本文将手把手教你使用UnityARKitMapbox开发跨平台AR导航助手,实现从虚拟路径叠加到空间感知的完整技术闭环。通过本教程,你将掌握: AR空间映射与场景理…...

git学习与使用(远程仓库、分支、工作流)
文章目录 前言简介git的工作流程git的安装配置git环境:git config --globalgit的基本使用新建目录初始化仓库(repository)添加到暂存区新增/修改/删除 文件状态会改变 提交到仓库查看提交(commit)的历史记录git其他命令…...
嵌入式预处理链接脚本lds和map文件
在嵌入式开发中,.lds.S 文件是一个 预处理后的链接脚本(Linker Script),它结合了 C 预处理器(Preprocessor) 的功能和链接脚本的语法。它的核心作用仍然是 定义内存布局和链接规则,但通过预处理…...
9. Spring AI 各版本的详细功能与发布时间整理
目录 一、旧版本(Legacy) 0.8.1(2024年3月) 二、里程碑版本(Milestone) 1.0.0-M1(2024年5月30日) 1.0.0-M2(2024年7月) 1.0.0-M3(2024年10月8日) 1.0.0-M4(2024年12月) 1.0.0-M5(2025年1月9日) 1.0.0-M6(2025年3月) 1.0.0-M7(2025年4月14日) 1.…...

《Android 应用开发基础教程》——第十四章:Android 多线程编程与异步任务机制(Handler、AsyncTask、线程池等)
目录 第十四章:Android 多线程编程与异步任务机制(Handler、AsyncTask、线程池等) 🔸 14.1 为什么需要多线程? 🔸 14.2 Handler Thread 模型 ✦ 使用 Handler 与 Thread 进行线程通信 ✦ 简要说明&am…...
Apache 高级配置实战:从连接保持到日志分析的完整指南
Apache 高级配置实战:从连接保持到日志分析的完整指南 前言 最近在深入学习 Apache 服务器配置时,发现很多朋友对 Apache 的高级功能还不够了解。作为一个在运维路上摸爬滚打的技术人,我想把这些实用的配置技巧分享给大家。今天这篇文章会带…...
开源 OIDC(OpenID Connect)身份提供方(IdP)、iam选型
文章目录 开源 OIDC(OpenID Connect)身份提供方(IdP)、iam选型主流开源 OIDC(OpenID Connect)身份提供方(IdP)zitadeldexory开源 OIDC(OpenID Connect)身份提供方(IdP)、iam选型 主流开源 OIDC(OpenID Connect)身份提供方(IdP) 当前主流的**开源 OIDC(OpenI…...
Android OkHttp控制链:深入理解网络请求的流程管理
OkHttp作为Android和Java平台上广泛使用的HTTP客户端,其核心设计之一就是"控制链"(Chain)机制。本文将深入探讨OkHttp控制链的工作原理、实现细节以及如何利用这一机制进行高级定制。 一、什么是OkHttp控制链 OkHttp控制链是一种责任链模式的实现&#…...

【JVM 01-引言入门篇】
JVM 引言篇01 笔记记录 1. 什么是JVM?2. 学习JVM有什么用?3. 常见的JVM4. 学习路线 学习资料来源-b站黑马 1. 什么是JVM? 定义:Java虚拟机(Java Virtual Machine 简称JVM)是运行所有Java程序的抽象计算机&a…...

Pandas数据规整
(1)层次化索引 1.创建带层次化索引的df 第一种,直接创建 import pandas as pd import numpy as npdata pd.Series(np.random.randn(9),index [[a, a, a, b, b, c, c, d, d],[1, 2, 3, 1, 3, 1, 2, 2, 3]]) print(data) # a 1 -0.6416…...

ThreadLocal线程本地变量在dubbo服务使用时候遇到的一个坑
我昨天遇到一个问题,就是我springboot项目里面有一个提供代办服务审核的dubbo接口,这个接口给房源项目调用,但是碰到一个问题就是,房源项目每天凌晨5点会查询满足条件过期的数据,然后调用我这边的代办审核dubbo接口&am…...
pga 作用
Oracle pga的作用 PGA 内存结构与功能解释: PGA ├── 1. Private SQL Area ├── 2. Session Memory ├── 3. SQL Work Areas │ ├── Sort Area │ ├── Hash Area │ ├── Bitmap Merge Area │ └── Bitmap Create Area └── 4. Stack S…...
setup.py Pip wheel
. ├── my_package │ ├── __init__.py │ └── my_file.py └── setup.pymy_file.py def my_func():print("Hello World")setup.py from setuptools import setup, find_packages import datetimesetup(namemy_package, # 记得改version0.1.1,packag…...
GO 语言进阶之 时间处理和Json 处理
更多个人笔记见: github个人笔记仓库 gitee 个人笔记仓库 个人学习,学习过程中还会不断补充~ (后续会更新在github上) 文章目录 时间处理基本例子 Json处理基础案例 时间处理 时间格式化必须使用:2006-01-…...
对WireShark 中的UDP抓包数据进行解析
对WireShark 中的UDP抓包数据进行解析 本文尝试对 WireShark 中抓包的 UDP 数据进行解析。 但是在尝试对 TCP 中的 FTP 数据进行解析的时候,发现除了从端口号进行区分之外, 没有什么好的方式来进行处理。 import numpy as np import matplotlib.pyplot …...
Flannel后端为UDP模式下,分析数据包的发送方式(二)
发往 10.244.2.5 的数据包最终会经过物理网卡 enp0s3,尽管路由表直接指定通过 flannel.1 发出。以下以 Markdown 格式详细解释为什么会经过 enp0s3,结合 Kubernetes 和 Flannel UDP 模式的背景。 问题分析 在 Kubernetes 环境中,使用 Flanne…...

从 0 到 1:Spring Boot 与 Spring AI 深度实战(基于深度求索 DeepSeek)
在人工智能技术与企业级开发深度融合的今天,传统软件开发模式与 AI 工程化开发的差异日益显著。作为 Spring 生态体系中专注于 AI 工程化的核心框架,Spring AI通过标准化集成方案大幅降低 AI 应用开发门槛。本文将以国产大模型代表 ** 深度求索ÿ…...

upload-labs通关笔记-第20关 文件上传之杠点绕过
系列目录 upload-labs通关笔记-第1关 文件上传之前端绕过(3种渗透方法) upload-labs通关笔记-第2关 文件上传之MIME绕过-CSDN博客 upload-labs通关笔记-第3关 文件上传之黑名单绕过-CSDN博客 upload-labs通关笔记-第4关 文件上传之.htacess绕过-CSDN…...

Vscode +Keil Assistant编译报错处理
Vscode Keil Assistant编译报错处理 1.报错图片内容 所在位置 行:1 字符: 25 chcp.com 65001 -Command & c:\Users\92170.vscode\extensions\cl.keil-a … ~ 不允许使用与号(&)。& 运算符是为将来使用而保留的;请用双引号将与号引起来(“&”)&…...
记录python在excel中添加一列新的列
思路是,先将需要添加为新的列存储到一个暂时的列表中,然后用到以下函数来存储 data_.loc[:, "新列的名字"] save_list_ 上面的save_list_就是暂时存储了信息的列表了。 以下是我的代码,供以后快速回忆。 schools_data {"98…...
WebRTC:实时通信的未来之路
WebRTC:实时通信的未来之路 目录 WebRTC:实时通信的未来之路一、背景介绍二、使用方式三、前途展望 一、背景介绍 随着互联网的飞速发展,实时音视频通信需求日益增长。传统的音视频通信多依赖于专有协议和插件(如Flash、ActiveX等…...