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

Spring Security 认证流程,长话简说

一、代码先行

1、设计模式

SpringSecurity 采用的是 责任链 的设计模式,是一堆过滤器链的组合,它有一条很长的过滤器链。

不过我们不需要去仔细了解每一个过滤器的含义和用法,只需要搞定以下几个问题即可:怎么登录、怎么校验账户、认证失败处理。

2、 POM依赖

没啥好说的,maven导入即可。
不写版本号,默认就会下载 最新的 版本。

<!--springSecurity-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId>
</dependency> 

3、登录

不管你用哪种权限框架,第一个要解决的问题就是登录。就是在我们的登录接口中,将账户密码委托给权限框架接管,让权限框架帮我们做校验和权限认证。

新建一个登录方法,目的是 验证用户的 用户名密码,并在验证成功后为该用户生成一个JWT。

@GetMapping("/login")
public String securityLogin(String username,String password){UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(username,password);// 使用authenticationManager调用loadUserByUsername获取数据库中的用户信息,Authentication authentication = authenticationManager.authenticate(authToken);if(authentication == null) {throw new RuntimeException("登录失败啦呀");}//获取符合security规范的UserSecurityUser securityUser = (SecurityUser) authentication.getPrincipal();String token = jwtUtil.createToken(securityUser.getUser());return token;
}

UsernamePasswordAuthenticationToken就是 我们委托 框架 帮我们托管 的 登录凭证

然后是:

单词重点说明: authenticate (v) 证明…是事实; authentication (n)证明

Authentication authentication = authenticationManager.authenticate(authToken);   

这就是spring security帮我们 执行 认证授权 的方法,最终返回一个 认证结果

大家思考一下,正常登录的逻辑无非是 4步走:

  1. 输入账号密码
  2. 根据账号从 DB数据库 中获取用户实体
  3. 校验密码是否正确
  4. 校验成功,将用户生成token后返回

我们再回过来看 上面的代码,第2步和第3步没见到。
肯定是spring security已经帮我们做了,但是,这并不代表我们可以省略这两步,只是需要我们写在别的地方,仅此而已。

4、根据账号从DB中获取用户实体

这个步骤是不可能不写的,只是写到了别处。
说明一点,spring security中的 用户概念,有自己的一套规则,不能直接用 我们系统里面的 User类。

因此如果我们要用spring security,就得 实现 他的 用户接口UserDetails
所以,我们新建一个SecurityUser类:

@Data
@NoArgsConstructor
public class SecurityUser implements UserDetails {// 使用聚合模式,将我们自己的User对象聚合到SecurityUser中// user字段存储了用户的一些信息,例如用户名和密码等private User user;// 覆盖UserDetails接口中的getAuthorities方法,返回用户的权限集合// 这里返回null,表示未实现获取权限的逻辑@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return null;}// 覆盖UserDetails接口中的getPassword方法,返回用户的密码// 这里通过调用user对象的getPwd方法获取密码@Overridepublic String getPassword() {return user.getPwd();}// 覆盖UserDetails接口中的getUsername方法,返回用户的用户名// 这里通过调用user对象的getUserName方法获取用户名,并注释说明有的地方可能会用email作为用户名,这里还是使用userName@Overridepublic String getUsername() {// 用户名:有的地方可能会用email作为用户名,我们这还是userNamereturn user.getUserName();}// 覆盖UserDetails接口中的isAccountNonExpired方法,判断账户是否过期// 这里直接返回true,表示账户不过期@Overridepublic boolean isAccountNonExpired() {return true;}// 覆盖UserDetails接口中的isAccountNonLocked方法,判断账户是否被锁定// 这里直接返回true,表示账户未被锁定@Overridepublic boolean isAccountNonLocked() {return true;}// 覆盖UserDetails接口中的isCredentialsNonExpired方法,判断凭证是否过期// 这里直接返回true,表示凭证不过期@Overridepublic boolean isCredentialsNonExpired() {return true;}// 覆盖UserDetails接口中的isEnabled方法,判断用户是否启用// 这里直接返回true,表示用户启用状态为true@Overridepublic boolean isEnabled() {return true;}
}

虽然我们系统里面有自己的 user 了,但是为了适配,所以就 聚合 进来。

然后是如何查询DB,肯定得有个 Service 去查询,我们已经有自己的UserService了,但是很可惜,spring security 这个地方也有自己的规范,我们自己写的user Service,人家不认,唉。

没办法,重新写个Service,自己写都写了,也不能丢了,所以 依照上面的操作,还是聚合进来。

@Service
@Slf4j
public class UserDetailService implements UserDetailsService {@ResourceUserService userService;/*** 根据用户名直接从DB中查询用户数据,作为登录校验的依据* @param username* @return* @throws UsernameNotFoundException*/@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {User user = userService.getByUsername(username);if (user == null) {log.info("username not found");throw new UsernameNotFoundException("username not found");}SecurityUser securityUser = new SecurityUser();securityUser.setUser(user);return securityUser;}
}

核心逻辑 是,我们还是用之前的方法拿到 User,但为了适配,就塞到 SecurityUser 里面去。

UserDetailsServicespring security认可的接口,我们得实现这个接口,并且实现loadUserByUsername方法,这个方法在spring security的认证逻辑里面会用到。目的就是拿到DB中真实的User,跟我们登录的账号密码进行比对。

5、校验密码是否正确

很多时候我们的密码是要进行加密的,但是我们登录肯定传的是明文密码,所以需要转换后再去比对。

这个 校验密码 的逻辑,需要写在spring security配置类 中。

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {@ResourceUserDetailService userService;/*** 新增security账户* @param auth* @throws Exception*/@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userService).passwordEncoder(new PasswordEncoder() {@Overridepublic String encode(CharSequence rawPassword) {return rawPassword.toString();}@Overridepublic boolean matches(CharSequence rawPassword, String encodedPassword) {return rawPassword.equals(encodedPassword);}});}}

因为我们的项目并没有对密码加密,所以就直接比较了。

在 LoginController 中,我们用到了AuthenticationManager这个对象,需要在配置类中注册。

@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {// 身份验证管理器, 直接继承即可.return super.authenticationManagerBean();
} 

AuthenticationManager 是 Spring Security框架中的一个核心接口,它负责处理身份验证请求。
在认证过程中,用户提交身份验证信息(如用户名和密码),AuthenticationManager 会验证这些信息的有效性。

最后是 路由 的相关配置

    @Override  protected void configure(HttpSecurity http) throws Exception {  http  // 禁用跨站请求伪造保护  .csrf().disable()   // 设置会话管理策略为无会话,因为我们使用token作为信息传递介质  .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)   .and()  // 进行认证请求的配置  .authorizeRequests()   // 将所有登入和注册的接口放开,这些都是无需认证就访问的  .antMatchers("/security/login").anonymous()   // 除了上面的那些,剩下的任何接口请求都需要经过认证  .anyRequest().authenticated()   .and()  // 允许跨域请求  .cors()   ;  // 在UsernamePasswordAuthenticationFilter之前添加JWT认证过滤器  http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);  }

这段代码是Spring Security框架中用于配置HTTP安全的部分。它主要涉及到跨站请求伪造(CSRF)的禁用、会话管理策略的设置、认证请求的配置以及跨域请求的允许。同时,还添加了一个JWT(JSON Web Token)认证过滤器。

因为我们项目用到了jwt,所以在进行账号密码验证之前,要先走jwt的过滤器。

jwt过滤器代码如下:

@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {@ResourceJWTUtil jwtUtil;@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException, ServletException, IOException {//获取tokenString token = request.getHeader("token");if (!StringUtils.hasText(token)) {//token为空的话, 就不管它, 让SpringSecurity中的其他过滤器处理请求//请求放行filterChain.doFilter(request, response);return;}//token不为空时, 解析tokenUser user = null;try {user = jwtUtil.verify(token);} catch (Exception e) {// 过滤器中抛出的异常,无法被统一异常捕获,所以在这里直接返回e.printStackTrace();Result result = new Result();result.setCode(403);result.setMsg("Token无效:" + e.getMessage());WebUtils.response(response,result);return;}SecurityUser securityUser = new SecurityUser();securityUser.setUser(user);//将用户安全信息存入SecurityContextHolder, 在之后SpringSecurity的过滤器就不会拦截UsernamePasswordAuthenticationToken authenticationToken =new UsernamePasswordAuthenticationToken(securityUser, null, null);SecurityContextHolder.getContext().setAuthentication(authenticationToken);//放行filterChain.doFilter(request, response);}
}  

流程参考这个图:
在这里插入图片描述

如果校验成功了,那么在登录方法中就会往下走,生成token返回,结束。

6、全局异常返回

认证过程中,难免出现各种异常,我们一般会做一个通用的返回

Result

@Data
public class Result implements Serializable {private int code;private String msg;private Object data;public static Result succ(Object data) {return success(200, "操作成功", data);}public static Result error(String msg) {return error(400, msg, null);}public static Result success (int code, String msg, Object data) {Result result = new Result();result.setCode(code);result.setMsg(msg);result.setData(data);return result;}public static Result error (int code, String msg, Object data) {Result result = new Result();result.setCode(code);result.setMsg(msg);result.setData(data);return result;}
}

GlobalExceptionHandler

/*** 全局异常处理*/
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {/*** 400 错误:运行时异常* @param e* @return*/@ResponseStatus(HttpStatus.BAD_REQUEST)@ExceptionHandler(value = RuntimeException.class)public Result handler(RuntimeException e) {log.error("运行时异常:----------------{}", e.getMessage());return Result.error(e.getMessage());}/*** 403 错误:权限不足* @param e* @return*/@ResponseStatus(HttpStatus.FORBIDDEN)@ExceptionHandler(value = AccessDeniedException.class)public Result handler(AccessDeniedException e) {log.info("security权限不足:----------------{}", e.getMessage());return Result.error("权限不足");}/*** 400 错误:异常请求-方法参数不匹配* @param e* @return*/@ResponseStatus(HttpStatus.BAD_REQUEST)@ExceptionHandler(value = MethodArgumentNotValidException.class)public Result handler(MethodArgumentNotValidException e) {log.info("实体校验异常:----------------{}", e.getMessage());BindingResult bindingResult = e.getBindingResult();ObjectError objectError = bindingResult.getAllErrors().stream().findFirst().get();return Result.error(objectError.getDefaultMessage());}/*** 400 错误:异常请求-非法参数* @param e* @return*/@ResponseStatus(HttpStatus.BAD_REQUEST)@ExceptionHandler(value = IllegalArgumentException.class)public Result handler(IllegalArgumentException e) {log.error("Assert异常:----------------{}", e.getMessage());return Result.error(e.getMessage());}}

二、原理亮明

Spring Security 它专注于身份验证和授权,并可根据需求进行配置和自定义。在实际应用中,了解身份验证组件的概念对于使用 Spring Security 进行实现和自定义非常有帮助。

1、没有使用 Spring Security

如果我们没有使用 Spring Security,那么请求将被 DispatcherServlet 拦截。

  • DispatcherServlet 服务分发器
    它拦截任何 HTTP 请求并将其转发到正确的控制器。
    在 Spring Boot 中,DispatcherServlet 会被自动配置。
    在这里插入图片描述

2、DispatcherServlet 是如何工作的

在深入了解 Spring Security 之前,让我们先了解一下 DispatcherServlet 如何分发请求。
当我们在一个端点(比如 /hello/world)发出请求时,DispatcherServlet 会如何处理它?

DispatcherServlet 创建了一个 IOC 容器,它是 Spring Framework 的核心组件之一,用于管理 bean 的创建和依赖关系。DispatcherServlet 创建的是 WebApplicationContext,这是一个专门用于 Web 应用程序的 IOC 容器。WebApplicationContext 是根据配置文件由 DispatcherServlet 进行配置的。

该 IOC 容器 会创建控制器 bean 的实例。当请求到达时,DispatcherServlet 会使用 IOC 容器 查找适当的 控制器 bean,并 委托 给它来 处理请求。

3、使用 Spring Security

当将 Spring Security 添加到 Spring Boot 应用程序中时,所有请求在到达 DispatcherServlet 之前 都会被 Spring Security 拦截。

在这里插入图片描述

4、认证 流程

身份验证请求和响应

虽然 Spring Security 可以与不同类型的身份验证方法一起使用,但在本文中,我们讨论的是 用户名和密码 方式的 身份验证,以便深入了解完整的身份验证流程。

  1. Filter Chain 在将请求转发给 Dispatcher Servlet 之前拦截传入请求。

  2. 请求进入认证过滤器(其中一个过滤器是 UsernamePasswordAuthenticationFilter)。

  3. 过滤器从请求(HttpServletRequest 对象)中提取用户名和密码。

  4. 然后使用凭据创建 UsernamePasswordAuthenticationToken

  5. 调用 AuthenticationManager 的 authenticate() 方法。

  6. AuthenticationManager 的 authenticate() 方法实现将尝试使用其拥有的 AuthenticationProvider 之一进行身份验证。

  7. 如果一个身份验证提供程序能够成功进行身份验证,它将返回一个包含凭据和权限的完整的 UsernamePasswordAuthenticationToken。

  8. 提供程序返回的此令牌用于在 Spring Security 上下文中将用户设置为已认证。

  9. 一旦用户通过身份验证,请求就会被转发到处理请求的 DispatcherServlet。

5、认证流程 中涉及到的 组件

5.1 什么是过滤器

Spring Filter 是一个组件,可以拦截任何传入请求,并在将其传递给 DispatcherServlet 之前执行某些操作。
过滤器可以处理请求,然后将其转发到 Filter Chain 中的下一个过滤器,或者可以停止并发送回 HTTP 响应。其中一个过滤器是存在于 FilterChain 中的 UsernamePasswordAuthenticationFilter。此过滤器尝试查找 HTTP Post 请求中传递的用户名和密码,如果找到,则尝试使用凭据对用户进行身份验证。
我们可以创建自己的 Filter 并将其添加到 SecurityFilterChain 中,以提供自己的逻辑来处理请求。

5.2 什么是 AuthenticationManager

AuthenticationManager 是一个接口,用于处理 身份验证 的过程。它只有一个方法 authenticate(Authentication authentication),该方法将一个身份验证对象作为参数,并返回 已经认证的 身份验证对象。身份验证对象通常是一个包含用户名和密码等凭据的 AuthenticationToken 对象。

Authentication authenticate(Authentication authentication) throws AuthenticationException;   

AuthenticationManager 的 实现类 是 ProviderManager 类,它提供了 authenticate() 方法的逻辑。
我们可以提供我们自己的 AuthenticationProvider 实现类 或 使用默认实现。

5.3 什么是 ProviderManager

实现 了 AuthenticationManager 接口 并覆盖了 authenticate() 方法。
它使用一组 AuthenticationProvider 来验证 Authentication 对象中发送的凭据。如果 AuthenticationProvider 支持身份验证类型,则将用于验证用户。如果没有提供者支持身份验证类型,则将抛 AuthenticationException。

每个身份验证提供程序的 supports() 方法用于检查它是否可以支持所需的身份验证类型。

5.4 什么是 AuthenticationProvider

AuthenticationProvider 是一个接口,用于定义验证用户的协议。它负责接收 Authentication 对象(表示用户凭据)并返回已经认证的 Authentication 对象,如果凭据有效。如果凭据无效,则 AuthenticationProvider 应该抛出 AuthenticationException。

AuthenticationProvider 接口有两个方法:

  • authenticate():此方法接受 Authentication 对象作为输入,并在凭据有效时返回已认证的 Authentication 对象。如果凭据无效,则 AuthenticationProvider 应该抛出 AuthenticationException。

  • supports():此方法接受 Authentication 对象作为输入,并且如果 AuthenticationProvider 可以验证该对象,则返回 true。如果 AuthenticationProvider 无法验证该对象,则应返回 false。

Spring Security 中的默认 AuthenticationProvider 是 DaoAuthenticationProvider。此提供程序使用 UserDetailsService 从数据库加载用户详细信息。如果用户凭据与数据库中的详细信息匹配,则 DaoAuthenticationProvider 将返回已经认证的 Authentication 对象。

我们可以添加我们自己的 AuthenticationProvider 实现来提供不同的身份验证方法。

5.5 身份验证和 UsernamePasswordAuthenticationToken

Authentication 和 UsernamePasswordAuthenticationToken 是什么?

  • Authentication

这是 Spring Security 中的一个接口,表示传入身份验证请求的令牌或已认证的主体(表示一个实体,比如个人)AuthenticationManager.authenticate() 方法。

提供的一些方法为:

 Collection<? extends GrantedAuthority> getAuthorities();Object getCredentials();boolean isAuthenticated();void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;  
  • UsernamePasswordAuthenticationToken

此类扩展了 AbstractAuthenticationToken 类(身份验证对象的基类),可用于用户名/密码身份验证请求。

此类有两个构造函数:

public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {super((Collection)null);this.principal = principal;this.credentials = credentials;this.setAuthenticated(false);
}public UsernamePasswordAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {super(authorities);this.principal = principal;this.credentials = credentials;super.setAuthenticated(true);
}

第一个构造函数可用于传入请求以创建未经身份验证的 Authentication 对象。

Authentication authentication = new UsernamePasswordAuthenticationToken(username,password);   

第二个构造函数可用于创建完全经过身份验证的 Authentication 对象。

Authentication authToken = new UsernamePasswordAuthenticationToken(username, password, userAuthorities);   

然后,此完全经过身份验证的 Authentication 对象从 AuthenticationProvider/AuthenticationManager 返回并表示已认证的用户。然后将此已认证的对象设置在 SecurityContext 中。Spring Security 中的 SecurityContext 是当前执行线程的安全上下文的表示。它包含有关当前已认证用户的信息,例如其用户名、权限和会话信息。

相关文章:

Spring Security 认证流程,长话简说

一、代码先行 1、设计模式 SpringSecurity 采用的是 责任链 的设计模式&#xff0c;是一堆过滤器链的组合&#xff0c;它有一条很长的过滤器链。 不过我们不需要去仔细了解每一个过滤器的含义和用法,只需要搞定以下几个问题即可&#xff1a;怎么登录、怎么校验账户、认证失败…...

74HC245

74HC245&#xff1a;典型的CMOS型缓冲门电路 在这里用于增加电压...

Java的static关键字和静态代码块

一、当static关键字用来修饰属性时&#xff0c;所修饰的属性就是类属性&#xff0c;而不是对象属性&#xff0c;所以可以做到全类共享。 不能用对象名去调用&#xff0c;只能用类名调用。 二、静态方法只能调用同为静态的方法和属性&#xff0c;非静态方法什么都可以调用。 三…...

Apex 批处理将 account owner 转移,同时实现关联的 opp 和 case 转移

实现和 mass transfer account 一样的功能&#xff1a; global class AccountBatchScript implements Database.Batchable<sObject>,Schedulable{String query;Id oldOwnerId xxxxxxxxxxxx;Id newOwnerId yyyyyyyyyyyy;List<Id> AccountIds new List<Id>(…...

Python | Leetcode Python题解之第557题反转字符串中的单词III

题目&#xff1a; 题解&#xff1a; class Solution:def reverseWords(self, s: str) -> str:stack, res, s [], "", s " "for i in s:stack.append(i)if i " ":while(stack):res stack.pop()return res[1:]...

Spring设计模式

设计模式 是一种软件开发中的解决方案&#xff0c;设计原则。目的是使代码具有扩展性&#xff0c;可维护性&#xff0c;可读性&#xff0c;如&#xff1a; 单例模式&#xff08;Singleton Pattern&#xff09; Spring IoC 容器默认会将 Bean 创建为单例&#xff0c;保证一个类…...

信号保存和信号处理

目录 信号保存中重要的概念 内核中信号的保存 对sigset_t操作的函数 对block&#xff0c;pendding&#xff0c;handler三张表的操作 sigpromask ​编辑 sigpending 是否有sighandler函数呢&#xff1f; 案例 信号处理 操作系统是如何运行的&#xff1f; 硬件中断 …...

网站小程序app怎么查有没有备案?

网站小程序app怎么查有没有备案&#xff1f;只需要官方一个网址就可以&#xff0c;工信部备案查询官网地址有且只有一个&#xff0c;百度搜索 "ICP备案查询" 找到官方gov.cn网站即可查询&#xff01; 注&#xff1a;网站小程序app备案查询&#xff0c;可通过输入单位…...

如何利用宏和VBA来提高文档编辑排版速度?

一个真实的文档修改需求 为什么我会去研究VBA呢&#xff1f;主要原因是今年在一个项目里写了太多的文档。文档中很多操作其实都是机械的、重复的&#xff0c;但是偏偏又很耗时。举个例子&#xff0c;当时有这么一个修改需求&#xff0c;修改文档中所有“输入输出需求表格中”添…...

Kafka - 启用安全通信和认证机制_SSL + SASL

文章目录 官方资料概述制作kakfa证书1.1 openssl 生成CA1.2 生成server端秘钥对以及证书仓库1.3 CA 签名证书1.4 服务端秘钥库导入签名证书以及CA根证书1.5 生成服务端信任库并导入CA根数据1.6 生成客户端信任库并导入CA根证书 2 配置zookeeper SASL认证2.1 编写zk_server_jass…...

c++基础32输入和输出

输入和输出 C风格&#xff08;使用printf和scanf&#xff09;输出字符输入字符 C风格&#xff08;使用cin和cout&#xff09;输出字符输入字符 注意事项 在C和C中&#xff0c;字符的输入和输出可以通过多种方式实现&#xff0c;包括使用标准输入输出库函数如 printf和 scanf&…...

[C++] 函数详解

前言 今天zty带来的是函数的详解&#xff0c;搞了4个小时&#xff0c;大家给个赞呗&#xff0c;zty还要上学&#xff0c;发作品会少一点 先 赞 后 看 养 成 习 惯 先 赞 后 看 养 成 习 惯 先 赞 后 看 养 成 习 惯 演示用编译器及其…...

AMD CPU下pytorch 多GPU运行卡死和死锁解决

参考链接 https://medium.com/amitparekh/solving-ddp-deadlock-with-multiple-gpus-and-amd-cpus-442186632034 简要说明 AMD的IOMMU和NVIDIA的NCCL不兼容问题导致AMD的IOMMU是BIOS 级组件,它基本上充当将虚拟地址映射到 GPU 上的物理地址的接口,它的全部目的是让 CPU 和 G…...

Swift 开发教程系列 - 第12章:协议与协议扩展

协议&#xff08;Protocol&#xff09;是 Swift 的一种重要特性&#xff0c;它定义了实现特定功能的方法、属性或其他要求。通过协议&#xff0c;可以将行为定义从具体实现中分离&#xff0c;使代码更具可读性和扩展性。Swift 的协议支持协议扩展&#xff0c;这一特性允许我们为…...

麒麟V10,arm64,离线安装docker和docker-compose

文章目录 一、下载1.1 docker1.2 docker-compose1.3 docker.service 二、安装三、验证安装成功3.1 docker3.2 docker-compose 需要在离线环境的系统了里面安装docker。目前国产化主推的是麒麟os和鲲鹏的cpu&#xff0c;这块的教程还比较少&#xff0c;记录一下。 # cat /etc/ky…...

NUXT3学习日记二(样式配置、引入组件库、区分在服务端还是在客户端渲染)

上一章已经给大家分享官网下载的nuxt3了&#xff0c;下面正式进入我所要说的内容吧 一、初始化样式 想必大家从我的git下载下来的nuxt3&#xff0c;能看到nuxt.config.ts这个文件了吧。 这里我们有两种css配置方式 1、css:[~/assets/base.scss] 这种方式不能在scss文件中定义…...

FPGA/Verilog,Quartus环境下if-else语句和case语句RT视图对比/学习记录

基本概念 RTL&#xff08;Register - Transfer - Level&#xff09;视图&#xff1a;是一种硬件描述语言的抽象层次&#xff0c;用于描述数字电路中寄存器之间的数据传输和操作。在这个层次上&#xff0c;可以看到电路的基本结构&#xff0c;如寄存器、组合逻辑、多路复用器等…...

Javascript高级—闭包问题

闭包问题 循环中赋值为引用的问题 for (var i 1; i < 5; i) {setTimeout(function timer() {console.log(i)}, i * 1000) }解决方法有3种 第一种&#xff0c;使用立即执行函数方式 for (var i 1; i < 5; i) {(fuction(j){setTimeout(function timer() {console.log…...

C#入门 017 字段,属性,索引器,常量

字段&#xff0c;属性&#xff0c;索引器&#xff0c;常量都表示数据 字段 什么是字段 字段(field)是一种表示与对象或类型(类与结构体)关联的变量字段是类型的成员&#xff0c;又称“成员变量&#xff0c;写在类体里面与对象关联的字段亦称“实例字段&#xff0c;表示某个对…...

磐石云语音助手拦截介绍

呼叫中心用户实际应用场景下最高会有超过30%的和语音助手&#xff1b;无声主要是进入了语音信箱;如&#xff1a;“听到滴声后留言”&#xff0c;”漏话提醒““发送请按1&#xff0c;重录请按2”以及拨打过程中客户主动拒接产生的”您拨打的用户正忙“&#xff0c;”关机“”停…...

synchronized 学习

学习源&#xff1a; https://www.bilibili.com/video/BV1aJ411V763?spm_id_from333.788.videopod.episodes&vd_source32e1c41a9370911ab06d12fbc36c4ebc 1.应用场景 不超卖&#xff0c;也要考虑性能问题&#xff08;场景&#xff09; 2.常见面试问题&#xff1a; sync出…...

51c自动驾驶~合集58

我自己的原文哦~ https://blog.51cto.com/whaosoft/13967107 #CCA-Attention 全局池化局部保留&#xff0c;CCA-Attention为LLM长文本建模带来突破性进展 琶洲实验室、华南理工大学联合推出关键上下文感知注意力机制&#xff08;CCA-Attention&#xff09;&#xff0c;…...

中南大学无人机智能体的全面评估!BEDI:用于评估无人机上具身智能体的综合性基准测试

作者&#xff1a;Mingning Guo, Mengwei Wu, Jiarun He, Shaoxian Li, Haifeng Li, Chao Tao单位&#xff1a;中南大学地球科学与信息物理学院论文标题&#xff1a;BEDI: A Comprehensive Benchmark for Evaluating Embodied Agents on UAVs论文链接&#xff1a;https://arxiv.…...

Vue3 + Element Plus + TypeScript中el-transfer穿梭框组件使用详解及示例

使用详解 Element Plus 的 el-transfer 组件是一个强大的穿梭框组件&#xff0c;常用于在两个集合之间进行数据转移&#xff0c;如权限分配、数据选择等场景。下面我将详细介绍其用法并提供一个完整示例。 核心特性与用法 基本属性 v-model&#xff1a;绑定右侧列表的值&…...

前端倒计时误差!

提示:记录工作中遇到的需求及解决办法 文章目录 前言一、误差从何而来?二、五大解决方案1. 动态校准法(基础版)2. Web Worker 计时3. 服务器时间同步4. Performance API 高精度计时5. 页面可见性API优化三、生产环境最佳实践四、终极解决方案架构前言 前几天听说公司某个项…...

数据链路层的主要功能是什么

数据链路层&#xff08;OSI模型第2层&#xff09;的核心功能是在相邻网络节点&#xff08;如交换机、主机&#xff09;间提供可靠的数据帧传输服务&#xff0c;主要职责包括&#xff1a; &#x1f511; 核心功能详解&#xff1a; 帧封装与解封装 封装&#xff1a; 将网络层下发…...

Mac软件卸载指南,简单易懂!

刚和Adobe分手&#xff0c;它却总在Library里给你写"回忆录"&#xff1f;卸载的Final Cut Pro像电子幽灵般阴魂不散&#xff1f;总是会有残留文件&#xff0c;别慌&#xff01;这份Mac软件卸载指南&#xff0c;将用最硬核的方式教你"数字分手术"&#xff0…...

[Java恶补day16] 238.除自身以外数组的乘积

给你一个整数数组 nums&#xff0c;返回 数组 answer &#xff0c;其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法&#xff0c;且在 O(n) 时间复杂度…...

【JVM面试篇】高频八股汇总——类加载和类加载器

目录 1. 讲一下类加载过程&#xff1f; 2. Java创建对象的过程&#xff1f; 3. 对象的生命周期&#xff1f; 4. 类加载器有哪些&#xff1f; 5. 双亲委派模型的作用&#xff08;好处&#xff09;&#xff1f; 6. 讲一下类的加载和双亲委派原则&#xff1f; 7. 双亲委派模…...

RabbitMQ入门4.1.0版本(基于java、SpringBoot操作)

RabbitMQ 一、RabbitMQ概述 RabbitMQ RabbitMQ最初由LShift和CohesiveFT于2007年开发&#xff0c;后来由Pivotal Software Inc.&#xff08;现为VMware子公司&#xff09;接管。RabbitMQ 是一个开源的消息代理和队列服务器&#xff0c;用 Erlang 语言编写。广泛应用于各种分布…...