当前位置: 首页 > news >正文

什么是 JWT?它是如何工作的?

松哥最近辅导了几个小伙伴秋招,有小伙伴在面小红书时遇到这个问题,这个问题想回答全面还是有些挑战,松哥结合之前的一篇旧文和大伙一起来聊聊。

一 无状态登录

1.1 什么是有状态

有状态服务,即服务端需要记录每次会话的客户端信息,从而识别客户端身份,根据用户身份进行请求的处理,典型的设计如 Tomcat 中的 Session。例如登录:用户登录后,我们把用户的信息保存在服务端 session 中,并且给用户一个 cookie 值,记录对应的 session,然后下次请求,用户携带 cookie 值来(这一步有浏览器自动完成),我们就能识别到对应 session,从而找到用户的信息。这种方式目前来看最方便,但是也有一些缺陷,如下:

  • 服务端保存大量数据,增加服务端压力
  • 服务端保存用户状态,不支持集群化部署

1.2 什么是无状态

微服务集群中的每个服务,对外提供的都使用 RESTful 风格的接口。而 RESTful 风格的一个最重要的规范就是:服务的无状态性,即:

  • 服务端不保存任何客户端请求者信息
  • 客户端的每次请求必须具备自描述信息,通过这些信息识别客户端身份

那么这种无状态性有哪些好处呢?

  • 客户端请求不依赖服务端的信息,多次请求不需要必须访问到同一台服务器
  • 服务端的集群和状态对客户端透明
  • 服务端可以任意的迁移和伸缩(可以方便的进行集群化部署)
  • 减小服务端存储压力

1.3 如何实现无状态

无状态登录的流程:

  • 首先客户端发送账户名/密码到服务端进行认证
  • 认证通过后,服务端将用户信息加密并且编码成一个 token(一般是 JWT),返回给客户端
  • 以后客户端每次发送请求,都需要携带认证的 token
  • 服务端对客户端发送来的 token 进行解密,判断是否有效,并且获取用户登录信息

二 JWT

2.1 JWT 简介

JWT,全称是 Json Web Token, 是一种 JSON 风格的轻量级的授权和身份认证规范,可实现无状态、分布式的 Web 应用授权:

JWT 作为一种规范,并没有和某一种语言绑定在一起,常用的 Java 实现是 GitHub 上的开源项目 jjwt,地址如下:https://github.com/jwtk/jjwt

2.2 JWT 数据格式

JWT 包含三部分数据:

  • Header:头部,通常头部有两部分信息:

    • 声明类型,这里是JWT
    • 加密算法,自定义

我们会对头部进行 Base64Url 编码(可解码),得到第一部分数据。

  • Payload:载荷,就是有效数据,在官方文档中(RFC7519),这里给了7个示例信息:

    • iss (issuer):表示签发人
    • exp (expiration time):表示token过期时间
    • sub (subject):主题
    • aud (audience):受众
    • nbf (Not Before):生效时间
    • iat (Issued At):签发时间
    • jti (JWT ID):编号

这部分也会采用 Base64Url 编码,得到第二部分数据。

  • Signature:签名,是整个数据的认证信息。一般根据前两步的数据,再加上服务的的密钥secret(密钥保存在服务端,不能泄露给客户端),通过 Header 中配置的加密算法生成。用于验证整个数据完整和可靠性。

生成的数据格式如下图:

注意,这里的数据通过 . 隔开成了三部分,分别对应前面提到的三部分,另外,这里数据是不换行的,图片换行只是为了展示方便而已。

2.3 JWT 交互流程

流程图:

步骤翻译:

  1. 应用程序或客户端向授权服务器请求授权
  2. 获取到授权后,授权服务器会向应用程序返回访问令牌
  3. 应用程序使用访问令牌来访问受保护资源(如 API)

因为 JWT 签发的 token 中已经包含了用户的身份信息,并且每次请求都会携带,这样服务的就无需保存用户信息,甚至无需去数据库查询,这样就完全符合了 RESTful 的无状态规范。

2.4 JWT 存在的问题

说了这么多,JWT 也不是天衣无缝,由客户端维护登录状态带来的一些问题在这里依然存在,举例如下:

  1. 续签问题,这是被很多人诟病的问题之一,传统的 cookie+session 的方案天然的支持续签,但是 jwt 由于服务端不保存用户状态,因此很难完美解决续签问题,如果引入 redis,虽然可以解决问题,但是 jwt 也变得不伦不类了。
  2. 注销问题,由于服务端不再保存用户信息,所以一般可以通过修改 secret 来实现注销,服务端 secret 修改后,已经颁发的未过期的 token 就会认证失败,进而实现注销,不过毕竟没有传统的注销方便。
  3. 密码重置,密码重置后,原本的 token 依然可以访问系统,这时候也需要强制修改 secret。
  4. 基于第 2 点和第 3 点,一般建议不同用户取不同 secret。

对于上面提到的这些问题,在目前具体的项目实践中,更多的是通过引入 Redis 来解决这些问题。

三 实战

说了这么久,接下来我们就来看看这个东西到底要怎么用?

3.1 环境搭建

首先我们来创建一个 Spring Boot 项目,创建时需要添加 Spring Security 依赖,创建完成后,添加 jjwt 依赖,完整的 pom.xml 文件如下:

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId>
</dependency>

然后在项目中创建一个简单的 User 对象实现 UserDetails 接口,如下:

public class User implements UserDetails {private String username;private String password;private List<GrantedAuthority> authorities;public String getUsername() {return username;}@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic boolean isEnabled() {return true;}//省略getter/setter
}

这个就是我们的用户对象,先放着备用,再创建一个 HelloController,内容如下:

@RestController
public class HelloController {@GetMapping("/hello")public String hello() {return "hello jwt !";}@GetMapping("/admin")public String admin() {return "hello admin !";}
}

HelloController 很简单,这里有两个接口,设计是 /hello 接口可以被具有 user 角色的用户访问,而 /admin 接口则可以被具有 admin 角色的用户访问。

3.2 JWT 配置

先准备一个 JWT 操作的工具类:

public class JwtUtils {public static final Integer EXPIRE_TIME = 7 * 24 * 60 * 60;public static final Integer REDIS_TOKEN_EXPIRE_TIME = 30 * 60;public static final String TOKEN_ISSUER = "javaboy";/*** 生成 JWT 字符串** @return*/public static String createJWT() throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {Authentication authentication = SecurityContextHolder.getContext().getAuthentication();String jws = Jwts.builder()//设置头部信息(相当于 JWT 字符串第一部分的内容).header().add("type", "JWT").and()//相当于 JWT 字符串第二部分的内容//签名密钥.signWith(getPrivateKey())//设置过期时间.expiration(java.util.Date.from(java.time.Instant.now().plusSeconds(EXPIRE_TIME)))//设置用户名.subject(authentication.getName())//设置用户权限,相当于把用户权限转为字符串 admin,user,xxx.claim("authorities", authentication.getAuthorities().stream().map(a -> a.getAuthority()).collect(Collectors.joining(",")))//令牌签发时间.issuedAt(new Date()).issuer(TOKEN_ISSUER).compact();return jws;}public static PrivateKey getPrivateKey() throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {ClassPathResource resource = new ClassPathResource("private_key.pem");byte[] privateKeyBytes = FileCopyUtils.copyToByteArray(resource.getInputStream());String key = new String(privateKeyBytes, StandardCharsets.UTF_8);String privateStr = key.replace("-----BEGIN PRIVATE KEY-----", "").replaceAll(System.lineSeparator(), "").replace("-----END PRIVATE KEY-----", "");byte[] privateKeyDecodedBytes = Base64.getDecoder().decode(privateStr);PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyDecodedBytes);KeyFactory keyFactory = KeyFactory.getInstance("RSA");PrivateKey privateKey = keyFactory.generatePrivate(keySpec);return privateKey;}public static RSAPublicKey getPublicKey() throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {ClassPathResource resource = new ClassPathResource("public_key.pem");byte[] publicKeyBytes = FileCopyUtils.copyToByteArray(resource.getInputStream());String key = new String(publicKeyBytes, StandardCharsets.UTF_8);String publicStr = key.replace("-----BEGIN PUBLIC KEY-----", "").replaceAll(System.lineSeparator(), "").replace("-----END PUBLIC KEY-----", "");KeyFactory keyFactory = KeyFactory.getInstance("RSA");byte[] publicKeyBase64Bytes = Base64.getDecoder().decode(publicStr);X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(publicKeyBase64Bytes);PublicKey rsaPublicKey = keyFactory.generatePublic(publicKeySpec);return (RSAPublicKey) rsaPublicKey;}public static Authentication verifyToken(String token) {try {Claims claims = Jwts.parser().verifyWith(getPublicKey()).build().parseSignedClaims(token).getPayload();String username = claims.getSubject();// user,admin,xxxString authorities = claims.get("authorities", String.class);// 解析用户权限List<SimpleGrantedAuthority> list = Arrays.stream(authorities.split(",")).map(s -> new SimpleGrantedAuthority(s)).collect(Collectors.toList());return UsernamePasswordAuthenticationToken.authenticated(username, null, list);} catch (Exception e) {//令牌校验失败return null;}}
}

接下来,在用户登录成功的时候,生成 JWT 字符串,并存入到 Redis 中,如下:

@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http.authorizeHttpRequests(a -> a.anyRequest().authenticated()).csrf(c -> c.disable()).sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS)).formLogin(f -> f.successHandler((req, resp, auth) -> {//登录成功,生成 JWT 字符串try {String token = JwtUtils.createJWT();//将 JWT 字符串保存到 redis 中redisTemplate.opsForValue().set(auth.getName() + ":" + token, auth.getName());// 设置过期时间redisTemplate.expire(auth.getName() + ":" + token, JwtUtils.REDIS_TOKEN_EXPIRE_TIME, TimeUnit.SECONDS);resp.setContentType("application/json;charset=utf-8");RespBean respBean = new RespBean(200, token, null);resp.getWriter().write(new ObjectMapper().writeValueAsString(respBean));} catch (NoSuchAlgorithmException e) {throw new RuntimeException(e);} catch (InvalidKeySpecException e) {throw new RuntimeException(e);}}).permitAll()).logout(l -> l.logoutSuccessHandler((req, resp, auth) -> {// 删除 redis 中的 tokenString key = auth.getName() + ":" + req.getHeader("Authorization").replace("Bearer ", "");System.out.println("key = " + key);redisTemplate.delete(key);resp.getWriter().write("logout success");}).logoutUrl("/logout").permitAll());http.addFilterAfter(new JwtFilter(redisTemplate), SecurityContextHolderFilter.class);return http.build();
}

最后再来一个校验的过滤器,在用法发送请求的时候,校验 JWT 字符串,如下:

public class JwtFilter extends GenericFilterBean {StringRedisTemplate redisTemplate;public JwtFilter(StringRedisTemplate redisTemplate) {this.redisTemplate = redisTemplate;}@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) {RespBean result = null;HttpServletRequest req = (HttpServletRequest) servletRequest;HttpServletResponse resp = (HttpServletResponse) servletResponse;resp.setContentType("application/json;charset=utf-8");try {//如果是登录请求,则直接放行if (req.getRequestURI().equals("/login")) {filterChain.doFilter(servletRequest, servletResponse);return;}//获取请求头中的tokenString token = req.getHeader("Authorization");if (token == null) {result = new RespBean(500, "令牌为空,请求失败", null);PrintWriter out = resp.getWriter();out.write(new ObjectMapper().writeValueAsString(result));} else {//验证tokentoken = token.replace("Bearer ", "");//验证token是否有效//校验通过,则返回认证后的 Auhtentication 对象,否则返回 nullAuthentication authentication = JwtUtils.verifyToken(token);String s = redisTemplate.opsForValue().get(authentication.getName() + ":" + token);if (s == null) {// 令牌失效result = new RespBean(501, "令牌失效,请重新登录", null);resp.getWriter().write(new ObjectMapper().writeValueAsString(result));} else {//令牌有效,检查令牌过期时间,如果临近过期,则为令牌续期Long expire = redisTemplate.getExpire(authentication.getName() + ":" + token);if (expire != null && expire < 10 * 60) {//说明令牌还有 10 分钟过期,则重新设置新的过期时间redisTemplate.expire(authentication.getName() + ":" + token, Duration.ofSeconds(JwtUtils.REDIS_TOKEN_EXPIRE_TIME));}SecurityContextHolder.getContext().setAuthentication(authentication);filterChain.doFilter(servletRequest, servletResponse);}}} catch (Exception e) {} finally {// 清除上下文SecurityContextHolder.clearContext();}}
}

关于这个过滤器,我说如下几点:

  1. 首先从请求头中提取出 Authorization 字段,这个字段对应的 value 就是用户的 token。
  2. 将提取出来的 token 字符串调用 JwtUtils.verifyToken 方法进行验证,将验证结果放到当前的 SecurityContext 中,然后执行过滤链使请求继续执行下去。

3.3 Spring Security 配置

最后,在 Spring Security 中配置这个过滤器。

http.addFilterAfter(new JwtFilter(redisTemplate), SecurityContextHolderFilter.class);

搞定!

3.4 测试

做完这些之后,我们的环境就算完全搭建起来了,接下来启动项目然后在 POSTMAN 中进行测试,如下:

登录成功后返回的字符串就是经过 base64url 转码的 token,一共有三部分,通过一个 . 隔开,我们可以对第一个 . 之前的字符串进行解码,即 Header,如下:

再对两个 . 之间的字符解码,即 payload:

可以看到,我们设置信息,由于 base64 并不是加密方案,只是一种编码方案,因此,不建议将敏感的用户信息放到 token 中。

接下来再去访问 /hello 接口,注意认证方式选择 Bearer Token,Token 值为刚刚获取到的值,如下:

可以看到,访问成功。

四 总结

这就是 JWT 结合 Spring Security 的一个简单用法。

相关文章:

什么是 JWT?它是如何工作的?

松哥最近辅导了几个小伙伴秋招&#xff0c;有小伙伴在面小红书时遇到这个问题&#xff0c;这个问题想回答全面还是有些挑战&#xff0c;松哥结合之前的一篇旧文和大伙一起来聊聊。 一 无状态登录 1.1 什么是有状态 有状态服务&#xff0c;即服务端需要记录每次会话的客户端信…...

微信小程序使用picker,数组怎么设置默认值

默认先显示请选择XXX。然后点击弹出选择列表。如果默认value是0的话&#xff0c;他就直接默认显示数组的第一个了。<picker mode"selector" :value"planIndex" :range"planStatus" range-key"label" change"bindPlanChange&qu…...

Springboot生成树工具类,可通过 id/code 编码生成 2.0版本

优化工具类中&#xff0c;查询父级时便利多次的问题 import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.mutable.MutableLong; import org.springframework.lang.NonNull; import org.springframework.lang.Nullable; import org.spri…...

17、CPU缓存架构详解高性能内存队列Disruptor实战

1.CPU缓存架构详解 1.1 CPU高速缓存概念 CPU缓存即高速缓冲存储器&#xff0c;是位于CPU与主内存间的一种容量较小但速度很高的存储器。CPU高速缓存可以分为一级缓存&#xff0c;二级缓存&#xff0c;部分高端CPU还具有三级缓存&#xff0c;每一级缓存中所储存的全部数据都是…...

算法训练营打卡Day18

目录 二叉搜索树的最小绝对差二叉搜索树中的众数二叉树的最近公共祖先额外练手题目 题目1、二叉搜索树的最小绝对差 力扣题目链接(opens new window) 给你一棵所有节点为非负值的二叉搜索树&#xff0c;请你计算树中任意两节点的差的绝对值的最小值。 示例&#xff1a; 思…...

【leetcode】169.多数元素

boyer-moore算法最简单理解方法&#xff1a; 假设你在投票选人 如果你和候选人&#xff08;利益&#xff09;相同&#xff0c;你就会给他投一票&#xff08;count1&#xff09;&#xff0c;如果不同&#xff0c;你就会踩他一下&#xff08;count-1&#xff09;当候选人票数为0&…...

MyBatis<foreach>标签的用法与实践

foreach标签简介 实践 demo1 简单的一个批量更新&#xff0c;这里传入了一个List类型的集合作为参数&#xff0c;拼接到 in 的后面 &#xff0c;来实现一个简单的批量更新 <update id"updateVislxble" parameterType"java.util.List">update model…...

R语言Shiny包新手教程

R语言Shiny包新手教程 1. 简介 Shiny 是一个 R 包&#xff0c;用于创建交互式网页应用。它非常适合展示数据分析结果和可视化效果。 2. 环境准备 安装R和RStudio 确保你的计算机上安装了 R 和 RStudio。你可以从 CRAN 下载 R&#xff0c;或从 RStudio 官网 下载 RStudio。…...

[大象快讯]:PostgreSQL 17 重磅发布!

家人们&#xff0c;数据库界的大新闻来了&#xff01;&#x1f4e3; PostgreSQL 17 正式发布&#xff0c;全球开发者社区的心血结晶&#xff0c;带来了一系列令人兴奋的新特性和性能提升。 发版通告全文如下 PostgreSQL 全球开发小组今天&#xff08;2024-09-26&#xff09;宣布…...

CHI trans--Home节点发起的操作

总目录&#xff1a; CHI协议简读汇总-CSDN博客https://blog.csdn.net/zhangshangjie1/article/details/131877216 Home节点能够发起的操作&#xff0c;包含如下几类&#xff1a; Home to Subordinate Read transactionsHome to Subordinate Write transactionsHome to Subor…...

Rust和Go谁会更胜一筹

在国内&#xff0c;我认为Go语言会成为未来的主流&#xff0c;因为国内程序员号称码农&#xff0c;比较适合搬砖&#xff0c;而Rust对心智要求太高了&#xff0c;不适合搬砖。 就个人经验来看&#xff0c;Go语言简单&#xff0c;下限低&#xff0c;没有什么心智成本&#xff0c…...

记HttpURLConnection下载图片

目录 一、示例代码1 二、示例代码2 一、示例代码1 import java.io.*; import java.net.HttpURLConnection; import java.net.URL;public class Test {/*** 下载图片*/public void getNetImg() {InputStream inStream null;FileOutputStream fOutStream null;try {// URL 统…...

物联网实训室建设的必要性

物联网实训室建设的必要性 一、物联网发展的背景 物联网&#xff08;IoT&#xff09;是指通过信息传感设备&#xff0c;按照约定的协议&#xff0c;将任何物品与互联网连接起来&#xff0c;进行信息交换和通信&#xff0c;以实现智能化识别、定位、跟踪、监控和管理的一种网络…...

初识C语言(四)

目录 前言 十一、常见关键字&#xff08;补充&#xff09; &#xff08;1&#xff09;register —寄存器 &#xff08;2&#xff09;typedef类型重命名 &#xff08;3&#xff09;static静态的 1、修饰局部变量 2、修饰全局变量 3、修饰函数 十二、#define定义常量和宏…...

产品架构图:从概念到实践

在当今快速发展的科技时代&#xff0c;产品架构图已成为产品经理和设计师不可或缺的工具。它不仅帮助我们理解复杂的产品体系&#xff0c;还能指导我们进行有效的产品设计和开发。本文将深入探讨产品架构图的概念、重要性以及绘制方法。 整个内容框架分为三个部分&#xff0c;…...

smartctl 命令:查看硬盘健康状态

一、命令简介 ​smartctl​ 命令用于获取硬盘的 SMART 信息。 介绍硬盘SMART 硬盘的 SMART (Self-Monitoring, Analysis, and Reporting Technology) 技术用于监控硬盘的健康状态&#xff0c;并能提供一些潜在故障的预警信息。通过查看 SMART 数据&#xff0c;用户可以了解硬…...

BBR 为什么没有替代 CUBIC 成为 Linux 内核缺省算法

自 2017 年底 bbr 发布以来&#xff0c;随着媒体的宣讲&#xff0c;各大站点陆续部署 bbr&#xff0c;很多网友不禁问&#xff0c;bbr 这么好&#xff0c;为什么不替代 cubic 成为 linux 的缺省算法。仅仅因为它尚未标准化&#xff1f;这么好的算法又为什么没被标准化&#xff…...

Git忽略规则原理和.gitignore文件不生效的原因和解决办法

在使用Git进行版本控制时&#xff0c;.gitignore文件扮演着至关重要的角色。它允许我们指定哪些文件或目录应该被Git忽略&#xff0c;从而避免将不必要的文件&#xff08;如日志文件、编译产物等&#xff09;纳入版本控制中。然而&#xff0c;在实际使用过程中&#xff0c;有时…...

MySQL-数据库设计

1.范式 数据库的范式是⼀组规则。在设计关系数据库时&#xff0c;遵从不同的规范要求&#xff0c;设计出合理的关系型数 据库&#xff0c;这些不同的规范要求被称为不同的范式。 关系数据库有六种范式&#xff1a;第⼀范式&#xff08;1NF&#xff09;、第⼆范式&#xff08;…...

Unity开发绘画板——04.笔刷大小调节

笔刷大小调节 上面的代码中其实我们已经提供了笔刷大小的字段&#xff0c;即brushSize&#xff0c;现在只需要将该字段和界面中的Slider绑定即可&#xff0c;Slider值的范围我们设置为1~20 代码中只需要做如下改动&#xff1a; public Slider brushSizeSlider; //控制笔刷大…...

./mnt/container_run_medium.sh

#!/bin/bash# 清理旧的日志文件 rm -f *.log rm -f nohup.out rm -f cssd.dat# 启动 pwbox_simu 和 MediumBoxBase nohup /mnt/simutools/pwbox_simu /mnt/simutools/pw_box.conf > /dev/null 2>&1 & nohup /mnt/mediumSimu/MediumBoxBase /mnt/mediumSimu/hynn_…...

数学建模研赛总结

目录 前言进度问题四分析问题五分析数模论文经验分享总结 前言 本文为博主数学建模比赛第五天的内容记录&#xff0c;希望所写的一些内容能够对大家有所帮助&#xff0c;不足之处欢迎大家批评指正&#x1f91d;&#x1f91d;&#x1f91d; 进度 今天已经是最后一天了&#xf…...

通信工程学习:什么是TCF技术控制设施

TCF&#xff08;Technical Control Facility&#xff09;&#xff1a;技术控制设施 首先&#xff0c;需要明确的是&#xff0c;通信工程是一门涉及电子科学与技术、信息与通信工程和光学工程学科领域的基础理论、工程设计及系统实现技术的学科。它主要关注通信过程中的信息传输…...

stm32 bootloader跳转程序设计

文章目录 1、bootloader跳转程序设计&#xff08;1&#xff09;跳转程序&#xff08;2&#xff09;、app程序中需要注意<1>、在keil中ROM起始地址和分配的空间大小<2>、在system_stm32f4xx.c中设置VECT_TAB_OFFSET为需要偏移的地址<3>、main函数中使能中断 总…...

科技赋能环保:静电与光解技术在油烟净化中的卓越应用

我最近分析了餐饮市场的油烟净化器等产品报告&#xff0c;解决了餐饮业厨房油腻的难题&#xff0c;更加方便了在餐饮业和商业场所有需求的小伙伴们。 随着环保政策的不断升级&#xff0c;餐饮行业的油烟治理成为重要课题。油烟净化器的技术革新不仅提升了净化效率&#xff0c;…...

FCA-FineBI试卷答案

1、【判断题】FineBI数据加工建模中只支持文本、数值、日期三种数据类型。 正确答案&#xff1a;A A. 正确 B. 错误 2、【判断题】Excel 支持批量导入&#xff0c;可以一次导入多个 sheet 或 Excel&#xff1f; 正确答案&#xff1a;A A.正确 B. 错误 3、【判断题】FineBI V6.…...

Spring - @Import注解

文章目录 基本用法源码分析ConfigurationClassPostProcessorConfigurationClass SourceClassgetImportsprocessImports处理 ImportSelectorImportSelector 接口DeferredImportSelector 处理 ImportBeanDefinitionRegistrarImportBeanDefinitionRegistrar 接口 处理Configuratio…...

新能源汽车储充机器人:能源高效与智能调度

新能源汽车储充机器人&#xff1a;开启能源高效利用与智能调度的未来之门 随着全球能源危机的日益加剧和环境污染问题的不断恶化&#xff0c;新能源汽车成为了未来交通领域的重要发展方向。然而&#xff0c;新能源汽车的普及不仅需要解决电池技术的瓶颈&#xff0c;还需要构建一…...

【Linux网络】详解TCP协议(2)

&#x1f389;博主首页&#xff1a; 有趣的中国人 &#x1f389;专栏首页&#xff1a; Linux网络 &#x1f389;其它专栏&#xff1a; C初阶 | C进阶 | 初阶数据结构 小伙伴们大家好&#xff0c;本片文章将会讲解 TCP协议的三次握手和四次挥手 的相关内容。 如果看到最后您觉得…...

STM32DMA学习日记

STM32 DMA学习日记 写于2024/9/28晚 文章目录 STM32 DMA学习日记1. DMA简介2. I/O方式2.1 程序查询方式2.2 程序中断方式2.3 DMA方式 3.DMA框图4. 相关寄存器4.1 DMA中断状态寄存器&#xff08;DMA_ISR&#xff09;4.2 DMA中断标志清除寄存器&#xff08;DMA_IFCR&#xff09;…...

wordpress静态页面生成/电商平台怎么运营的

作为Hadoop生态圈中的重要组件&#xff0c;Hive在数据分析、处理方面扮演着异常重要的角色。另外&#xff0c;Hive作为大数据组件&#xff0c;处理的数据量往往很大&#xff0c;合适的优化技巧在运行效率方面往往可以起到非常好的效果。 1、筛选重复记录 这是在业务中经常遇到…...

做网站要注意的/病毒什么时候才能消失

1. 异常检测 VS 监督学习 0x1&#xff1a;异常检测算法和监督学习算法的对比 总结来讲&#xff1a; 1. 在异常检测中&#xff0c;异常点是少之又少&#xff0c;大部分是正常样本&#xff0c;异常只是相对小概率事件 2. 异常点的特征表现非常不集中&#xff0c;即异常种类非常多…...

wordpress security/谷歌seo靠谱吗

安装nodejs 安装nodejs建议直接下载二进制包&#xff0c;把官网上的64位二进制版本下载地址复制下来&#xff0c;执行 wget https://nodejs.org/dist/v6.9.2/node-v6.9.2-linux-x64.tar.xz xz格式的文件按照以下命令解压&#xff1a; xz -d xxx.tar.xz 将 xxx.tar.xz解压成 xxx…...

接单类型网站建设费用/网站制作公司有哪些

我试图在prompt()框中显示“大于正常”的文本量 . 在Internet Explorer 11中调用javascript prompt()函数时&#xff0c;我的大部分文本都被隐藏了 . 它似乎只支持2行文本 . Chrome&#xff0c;Firefox和Opera似乎运行良好 . 这似乎只是一个IE问题 .prompt("Lorem Ipsum i…...

滁州网站建设公司/seo多久可以学会

任何使用CSS已有一段时间的人都会知道绝对和相对定位的优点。 回顾一下&#xff1a; position: relative允许元素从其原始位置水平&#xff08;使用left或right &#xff09;或垂直位置&#xff08;使用top或bottom &#xff09;移动。 position: absolute允许使用left &#x…...

无锡网站制作公司排名/百度一下生活更好

型号&#xff1a;TD1000&#xff0c;TD2000华为TD1000&#xff0c;TD2000系列变频器1&#xff0e;TD1000G系列(单相&#xff0c;220V)型号 功率(KW)TD1000-2S0007G 0&#xff0e;75TD1000-2S0015G 1.5TD1000-2S0022G 2.22&#xff0e;TD1000G系列(三相&#xff0c;380V)型号 功…...