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

Mall脚手架总结(一)——SpringSecurity实现鉴权认证

前言

        在结束理论知识的学习后,荔枝开始项目学习,这个系列文章将围绕荔枝学习mall项目过程中总结的知识点来梳理。本篇文章主要涉及如何整合Spring Security和JWT实现鉴权认证的功能!希望能帮助到一起学习mall项目的小伙伴~~~


文章目录

前言

一、JWT和Spring Security的整合知识点

1.1 JWTtoken的生成流程

1.1.1 什么是CSRF:

1.1.2 为什么需要JWT 

1.1.3 JWTtoken的生成流程 

1.2 UserDetails接口

1.3 CollUtil类

1.4 PasswordEncoder接口

1.5 为什么要实现Serializable接口

总结


一、JWT和Spring Security的整合知识点

这部分会根据mall_learning中的后台用户管理实现类来做系统的梳理学习,目的主要是弄清楚JWT token的生成流程以及相应的类的使用。首先我们来看一下该实现类的demo:

package com.crj.crj_mall_learning.service.impl;import cn.hutool.core.collection.CollUtil;
import com.crj.crj_mall_learning.utils.JwtTokenUtil;
import com.crj.crj_mall_learning.domain.AdminUserDetails;
import com.crj.crj_mall_learning.domain.UmsResource;
import com.crj.crj_mall_learning.service.UmsAdminService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;/*** @auther lzddl* @description 后台用户管理Service实现类*/
@Slf4j
@Service
public class UmsAdminServiceImpl implements UmsAdminService {/*** 存放默认用户信息*/private List<AdminUserDetails> adminUserDetailsList = new ArrayList<>();/*** 存放默认资源信息*/private List<UmsResource> resourceList = new ArrayList<>();@Autowiredprivate JwtTokenUtil jwtTokenUtil;//spring security中的一种对密码的编译方法@Autowiredprivate PasswordEncoder passwordEncoder;// Spring IOC容器的初始化中就会执行该init方法@PostConstructprivate void init(){adminUserDetailsList.add(AdminUserDetails.builder().username("admin").password(passwordEncoder.encode("123456")).authorityList(CollUtil.toList("brand:create","brand:update","brand:delete","brand:list","brand:listAll")).build());adminUserDetailsList.add(AdminUserDetails.builder().username("lzddl").password(passwordEncoder.encode("123456")).authorityList(CollUtil.toList("brand:listAll")).build());resourceList.add(UmsResource.builder().id(1L).name("brand:create").url("/brand/create").build());resourceList.add(UmsResource.builder().id(2L).name("brand:update").url("/brand/update/**").build());resourceList.add(UmsResource.builder().id(3L).name("brand:delete").url("/brand/delete/**").build());resourceList.add(UmsResource.builder().id(4L).name("brand:list").url("/brand/list").build());resourceList.add(UmsResource.builder().id(5L).name("brand:listAll").url("/brand/listAll").build());}@Overridepublic AdminUserDetails getAdminByUsername(String username) {//在存放默认用户对象的集合中来查找指定的用户信息,如果有就返回符合条件的第一条数据;// 如果没有就会返回一个nullList<AdminUserDetails> findList = adminUserDetailsList.stream().filter(item -> item.getUsername().equals(username)).collect(Collectors.toList());if(CollUtil.isNotEmpty(findList)){return findList.get(0);}return null;}@Overridepublic List<UmsResource> getResourceList() {return resourceList;}@Overridepublic String login(String username, String password) {String token = null;try {//这里的UserDetails是Spring Security中的一个接口,该接口实现仅仅存储用户的信息,后续会将该接口提供的用户信息封装到认证对象Authentication中去UserDetails userDetails = getAdminByUsername(username);if(userDetails==null){return token;}if (!passwordEncoder.matches(password, userDetails.getPassword())) {throw new BadCredentialsException("密码不正确");}UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());SecurityContextHolder.getContext().setAuthentication(authentication);token = jwtTokenUtil.generateToken(userDetails);} catch (AuthenticationException e) {log.warn("登录异常:{}", e.getMessage());}return token;}
}

1.1 JWTtoken的生成流程

        在正式了解整个认证鉴权的流程之前,我们首先需要弄清楚为什么需要使用JWT!在以往的前后端半分离的项目中我们经常使用会话来实现token值的缓存,由于前后端没有涉及过多的跨域操作,因此也就不会采用JWT来保证跨域请求的安全。那为什么跨域请求中需要采用JWT来认证授权呢?

1.1.1 什么是CSRF:

跨站请求伪造(Cross-site request forgery),也被称为 one-click attack 或者session riding,通常缩写为 CSRF 或者 XSRF,是一种挟制用户在当前已登录的Web应用程序上执行非本意的操作的攻击方法。

1.1.2 为什么需要JWT 

        JWT的token值主要是由三部分组成的:header.payload.signature该token是用户访问正规网站会拿到的并缓存在用户浏览器的localStorage本地缓存里面header里面是加密算法的信息,里面放type=jwt,alg(加密算法)=HS512;payload里面存放的是用户名、token的创建时间和过期时间;signature通过使用head中定义的加密算法和后端已经设置好的加密密钥,将head+payload这两部分加密生成一个signature签名,最终拼接所有密钥数据加起来生成一个JWT令牌也就是token。由于JWT的token存在用户浏览器的localStorage本地缓存里面,下次这个用户发送请求给网站的后台时,就会把这个JWT发给正规网站的后台进行安全性校验,而不会向之前那种模式那样使用到cookie。这就可以避免用户在浏览网站的时候被恶意链接获取到cookies,从而绕开网站的安全性校验而导致用户的信息被窃取或造成其它损失。

1.1.3 JWTtoken的生成流程 

首先用户通过后端提供的校验授权的接口来执行登录的操作,这部分的功能主要有两个:校验和授权。首先当用户第一次登录的时候会根据账号和密码来获得JWT生成的token,在上面的介绍中我们已经知晓该token值的功能。

首次登录

        首先用户根据账号和密码进行校验,在Spring Security中我们可以选择在初始化接口实现类的时候就将符合条件的用户信息加载在一个UserDetails对象构成的列表中,并根据默认要求对密码采用passwordEncoder类方法进行encode。因此我们在校验密码的正误的时候也需要采用该类方法对用户输入的密码进行比较。完成身份校验后才会开始生成token,首先需要将用户信息对象UserDetails和用户的访问权限列表交给UsernamePasswordAuthenticationToken对象的构造方法构造一个UsernamePasswordAuthenticationToken对象,之后会交给一个SecurityContextHolder来管理该用户的信息,最后才会调用你自定义的token工具类生成JWT token值。

//用户校验完成,获取一个UsernamePasswordAuthenticationToken对象
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
//将UsernamePasswordAuthenticationToken对象交给SecurityContextHolder,表示用户已经通过身份验证,这一步是为了让SpringSecurity知晓用户对象是谁及其权限,并判断该用户是否有相应的访问资源的权限
SecurityContextHolder.getContext().setAuthentication(authentication);
//获取token
token = jwtTokenUtil.generateToken(userDetails);
token生成的具体流程

        在下面的demo中使用了一个map对象来设置主题信息,并在重载方法中通过setClaims方法来实现主题信息的声明,后续可以根据UserDetails对象的getSubject()方法来获得用户名。这个UserDetails对象的获取其实就是根据用户输入的用户名在已经缓存的用户信息List中找到对应的用户信息对象。

/*** 根据负载生成JWT的token*/private String generateToken(Map<String, Object> claims) {return Jwts.builder().setClaims(claims) // 设置JWT的声明(claims),即要在JWT中存储的信息。这个参数是一个Map<String, Object>,表示JWT的payload部分。.setExpiration(generateExpirationDate()) //过期时间.signWith(SignatureAlgorithm.HS512, secret) //根据后台设置的密钥来生成签名.compact();   //构建并返回最终的JWT字符串}
/*** 根据用户信息生成token*/public String generateToken(UserDetails userDetails) {Map<String, Object> claims = new HashMap<>();claims.put(CLAIM_KEY_USERNAME, userDetails.getUsername());claims.put(CLAIM_KEY_CREATED, new Date());return generateToken(claims);}

        拿到token后用户每次调用接口都在http的headert中添加一个叫Authorization的头,值为WT的token,后台程序通过对Authorization头中信息的解码及数字签名校验来获取其中的用户信息,从而实现认证和授权。 

授权

        在上文中我们已经弄清楚了整个JWT的token令牌生成的流程了,接下来我们需要搞清楚如何整合Spring Security完成用户权限的授权。首先同样的我们事先需要获取到所有用户的权限以及信息,这部分内容并不是现在我们的重点。前面我们知道我们需要把用户信息存在一个个UserDetails对象,当用户成功登录时,UserDetails对象会被封装成Authentication对象,并且设置到Spring Security的安全上下文中(通常是SecurityContextHolder),以便在后续的请求中进行鉴权和授权。首先我们需要弄清楚Spring Security的配置类:

/*** @auther lzddl* @description SpringSecurity的配置*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class SecurityConfig {@Autowiredprivate RestfulAccessDeniedHandler restfulAccessDeniedHandler;@Autowiredprivate RestAuthenticationEntryPoint restAuthenticationEntryPoint;@Autowiredprivate IgnoreUrlsConfig ignoreUrlsConfig;@BeanSecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = httpSecurity.authorizeRequests();//不需要保护的资源路径允许访问for (String url : ignoreUrlsConfig.getUrls()) {registry.antMatchers(url).permitAll();}//允许跨域请求的OPTIONS请求,这是因为在跨域访问的正式请求发送前会发送一个Option请求,我们要开启跨域访问就必须允许所有的option请求registry.antMatchers(HttpMethod.OPTIONS).permitAll();httpSecurity.csrf()// 由于使用的是JWT,我们这里不需要csrf.disable().sessionManagement()// 基于token,所以不需要session,下面设置session为无状态的.sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests().anyRequest()// 除上面外的所有请求全部需要鉴权认证.authenticated();// 禁用缓存httpSecurity.headers().cacheControl();// 添加JWT filterhttpSecurity.addFilterBefore(jwtAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class);//添加自定义未授权和未登录结果返回httpSecurity.exceptionHandling().accessDeniedHandler(restfulAccessDeniedHandler).authenticationEntryPoint(restAuthenticationEntryPoint);return httpSecurity.build();}@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}@Beanpublic JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter(){return new JwtAuthenticationTokenFilter();}}

        我们需要使用SecurityFilterChain来配置相关的操作,首先我们需要获取到httpSecurity对象,然后配置相应的访问路径白名单、允许跨域的Option请求、禁用csrf和session、开启除白名单外的鉴权认证功能、禁用缓存并添加JWT过滤器、添加自定义未授权和未登录结果返回。以上就是我们需要整合SpringSecurity的内容。其中鉴权部分我们需要注意的是JwtAuthenticationTokenFilter 部分,因为我们在配置类中开启了将filter放置在登录验证的功能前,因此这个功能其实就是为我们提供一个可以通过token记住用户态的功能

/*** @auther lzddl* @description JWT登录授权过滤器*/
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {private static final Logger LOGGER = LoggerFactory.getLogger(JwtAuthenticationTokenFilter.class);@Autowiredprivate UserDetailsService userDetailsService;@Autowiredprivate JwtTokenUtil jwtTokenUtil;@Value("${jwt.tokenHeader}")private String tokenHeader;@Value("${jwt.tokenHead}")private String tokenHead;@Overrideprotected void doFilterInternal(HttpServletRequest request,HttpServletResponse response,FilterChain chain) throws ServletException, IOException {String authHeader = request.getHeader(this.tokenHeader);if (authHeader != null && authHeader.startsWith(this.tokenHead)) {
//            获取tokenString authToken = authHeader.substring(this.tokenHead.length());// The part after "Bearer "
//            根据token获取用户名String username = jwtTokenUtil.getUserNameFromToken(authToken);LOGGER.info("checking username:{}", username);//若有,则获取用户信息并创建authentication对象if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);if (jwtTokenUtil.validateToken(authToken, userDetails)) {UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));LOGGER.info("authenticated user:{}", username);SecurityContextHolder.getContext().setAuthentication(authentication);}}}chain.doFilter(request, response);}
}

真正的授权其实在用户信息对象的加载过程中就完成了! 

当然了接口也需要使用@PreAuthorize注解来定义接口需要的权限,在配置类中我们还要@EnableGlobalMethodSecurity(prePostEnabled=true)注解来开启方法级的安全性!

1.2 UserDetails接口

        该接口和UsernamePasswordAuthenticationToken类其实都是Spring Security中core包下管理。该接口定义了一个用户信息管理的api,要想使用该接口,我们需要自定义实现一个实现类在Spring Security中按照我们自定义的来动态权限配置列表,这里荔枝将其命名为AdminUserDetails类。

/*** @auther lzddl* @description SpringSecurity用户信息封装类*/
@Data
@EqualsAndHashCode(callSuper = false)
@Builder
public class AdminUserDetails implements UserDetails {private String username;private String password;private List<String> authorityList;@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return this.authorityList.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());}@Overridepublic String getPassword() {return this.password;}@Overridepublic String getUsername() {return this.username;}@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic boolean isEnabled() {return true;}
}

        this.authorityList.stream()是将authorityList列表对象转化成一个Stream方便我们操作集合对象,map()定义了authorityList中的每个字符串对象到SimpleGrantedAuthority 对象的映射关系!SimpleGrantedAuthority 是Spring Security提供的一个实现了GrantedAuthority 接口的简单权限授予类,它表示用户的权限。最后就是借助collect将SimpleGrantedAuthority 对象收集在一个List对象中。也就是说,这个List包含了用户具有的权限,可以被Spring Security用于进行身份验证和授权。

1.3 CollUtil类

CollUtil类是Hutool插件中有关集合操作的工具类,里面封装了大量的操作集合数据的方法。

中文文档:https://www.hutool.cn/docs/#/

API手册:https://apidoc.gitee.com/dromara/hutool/

1.4 PasswordEncoder接口

该接口是一个密码解析器,一般用来加密。Spring Security 要求容器中必须有 PasswordEncoder 实例,因此我们在用户的信息中必须使用该解析器来对密码进行解析。

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//package org.springframework.security.crypto.password;public interface PasswordEncoder {String encode(CharSequence rawPassword);boolean matches(CharSequence rawPassword, String encodedPassword);default boolean upgradeEncoding(String encodedPassword) {return false;}
}

 当然了该接口有很多实现类,我们可以创建该接口的实现类对象来指定加密的规则。但是如果我们使用的是@AutoWired注解的方式来将接口进行依赖注入,那么默认使用加密算法BCrypt。如果要修改加密算法,我们可以在配置类声明bean对象的时候修改:

/*** @auther lzddl* @description SpringSecurity的配置*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class SecurityConfig {@Autowiredprivate RestfulAccessDeniedHandler restfulAccessDeniedHandler;@Autowiredprivate RestAuthenticationEntryPoint restAuthenticationEntryPoint;@Autowiredprivate IgnoreUrlsConfig ignoreUrlsConfig;@BeanSecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {......}@Beanpublic PasswordEncoder passwordEncoder() {//使用MD4加密算法return new Md4PasswordEncoder();}@Beanpublic JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter(){return new JwtAuthenticationTokenFilter();}}

1.5 为什么要实现Serializable接口

说起Serializable接口,我们一定会提到的一个名词就是序列化,那么什么是序列化呢?

        Serializable接口是一个语义级别的一个接口,属于Java的io包中定义的接口,该接口没有定义任何的方法,只有实现了该接口的类才会有序列化和反序列化的状态。序列化和反序列化时Java在执行底层的IO读写操作,也就是实现对象数据和文件字节流转化的时候,告诉JVM的一种方式。实现了Serializable接口的类可以被ObjectOutputStream转换为字节流,同时也可以通过ObjectInputStream再将其解析为对象。

可序列化是一个可以被继承的状态。同时为了保证不可序列化类的子类被序列化,子类必须有一个无参的构造方法

serialVersionUID

        序列化运行时与每个可序列化类关联一个版本号,称为serialVersionUlD。在反序列化期间使用它来验证序列化对象的发送方和接收方已经为该对象加载了类与序列化兼容。类的一个类对象的serialVersionUlD与相应发送方的serialVersionUlD不同时会导致在反序列化时抛出异常InvalidclassException。所以我们在实现Serializable接口的时候,一般还会要去尽量显示地定义serialVersionUID就比如:

private static final long serialVersionUID = 1L; 

 记住一定要是final类型的!


总结

        在正式开发项目中我个人认为可能鉴权这部分的功能会更加复杂一点,因为脚手架只是一个快速让我们接触上手的内容,因此这部分token拼接和请求头的生成其实是在Swagger中手动输入的哈哈哈。然后最重要的还是要弄清楚整个整合的流程,需要自定义的配置以及相应的鉴权流程,当然了JWTtoken的结构也是比较重要的!

今朝已然成为过去,明日依然向往未来!我是荔枝,在技术成长之路上与您相伴~~~

如果博文对您有帮助的话,可以给荔枝一键三连嘿,您的支持和鼓励是荔枝最大的动力!

如果博文内容有误,也欢迎各位大佬在下方评论区批评指正!!!

相关文章:

Mall脚手架总结(一)——SpringSecurity实现鉴权认证

前言 在结束理论知识的学习后&#xff0c;荔枝开始项目学习&#xff0c;这个系列文章将围绕荔枝学习mall项目过程中总结的知识点来梳理。本篇文章主要涉及如何整合Spring Security和JWT实现鉴权认证的功能&#xff01;希望能帮助到一起学习mall项目的小伙伴~~~ 文章目录 前言 …...

beego-简单项目写法--路径已经放进去了

Beego案例-新闻发布系统 1.注册 后台代码和昨天案例代码一致。,所以这里面只写一个注册的业务流程图。 **业务流程图 ** 2.登陆 业务流程图 登陆和注册业务和我们昨天登陆和注册基本一样&#xff0c;所以就不再重复写这个代码 但是我们遇到的问题是如何做代码的迁移&…...

Linux-CPU相关常用命令合集

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、cpu相关常用命令 二、cpuinfo 参数详细对照表 前言 本篇文章主要记录平时Linux-常用命令整理&#xff01; 提示&#xff1a;以下是本篇文章正文内容&#…...

vue 百度地图/天地图设置铺满屏幕100%,解决空隙问题

设置100%无效&#xff0c;刷新依然右侧有空隙&#xff0c;解决&#xff1a;min-width: 100vw; <div class"aui-flex-col" style"width: 100%; height:100%"><div id"mapAllCon" style"width: 100%; min-width: 100vw; height: 10…...

2023年安全员安徽题库,精准题库,历年真题,模拟试题

...

第五章:最新版零基础学习 PYTHON 教程—Python 字符串操作指南(第六节 - Python 中字符串的逻辑运算符)

对于 python 中的字符串,布尔运算符(and、or、not)起作用。让我们考虑两个字符串,即 str1 和 str2,并在它们上尝试布尔运算符: Python3 str1 = str2 = geeks# 使用 repr 打印带引号的字符串# 返回 str1 print(repr(str1 and str2)) # 返回 str1 print(repr(str2 and…...

Bark Ai 文本转语音 模型缓存位置修改

默认缓存位置在&#xff1a;~/.cache 加入环境变量&#xff1a;XDG_CACHE_HOME&#xff0c;指定缓存位置 修改后新的位置为&#xff1a; D:\Ai\Bark\Bark Cache...

Docker 镜像的创建

目录 一、Docker镜像的创建 1、基于已有镜像创建 2、基于本地模板创建 3、基于dockerfile创建 3.1 dockerfile结构 3.2 构建镜像命令 二、镜像分层的原理 1、联合文件系统&#xff08;UnionFS&#xff09; 2、镜像加载的原理 三、Dockerfile 操作常用的指令 案例实验…...

【ORM】浅聊C#和Java的ORM底层框架

给自己一个目标&#xff0c;然后坚持一段时间&#xff0c;总会有收获和感悟&#xff01; 国庆假期马上结束&#xff0c;闲暇时间&#xff0c;突然对Ado.Net这个词的由来感兴趣&#xff0c;然后就一顿复习了一遍&#xff0c;顺便也了解了下java关于ORM框架的底层是什么&#xff…...

windows redis 自启动 Redis服务无法启动报错1067问题

如果你的系统服务里面已经有redis服务并且无法启动&#xff0c;则使用下面的命令卸载此服务 ! 1、停止Redis服务&#xff1a; redis-server --service-uninstall 2、删除系统服务 sc delete redis 进入到你的Redis安装目录&#xff0c;我的在以下目录&#xff0c;谨记此时不…...

Ubuntu Server CLI专业提示

基础 网络 获取所有接口的IP地址 networkctl status 显示主机的所有IP地址 hostname -I 启用/禁用接口 ip link set <interface> up ip link set <interface> down 显示路线 ip route 将使用哪条路线到达主机 ip route get <IP> 安全 显示已登录的用户 w…...

Centos7升级OpenSSH9.1

最近遇到了服务器漏洞&#xff0c;需要对服务器的OpenSSH版本进行升级&#xff0c;查阅了相关资料&#xff0c;总结出了一套比较简单的方案。中间遇到的个别问题也进行了记录&#xff0c;供大家参考。 下载准备 从https://ftp.jaist.ac.jp/pub/OpenBSD/OpenSSH/portable/opens…...

linux——信号

目录 一.信号的保存 二.信号集操作 1.信号集 2.信号集操作函数 3.sigprocmask 4.sigpending 三. 信号的捕捉 1.内核态和用户态 2. sigaction 四.可重入函数 五.SIGCHLD信号 一.信号的保存 实际执行信号的处理动作称为信号递达(Delivery)。信号从产生到递达之间的状…...

存档&改造【03】Apex-Fancy-Tree-Select花式树的导入及学习

Apex-Fancy-Tree-Select git学习网页 GitHub - RonnyWeiss/Apex-Fancy-Tree-Select: Fancy Tree Plug-in for Oracle APEX 如何从其他应用程序导出已有插件到新应用程序中 1.从其他应用程序导出插件 其他应用程序-【共享组件】-【插件】-【任务 导出插件】-选择想要导出的…...

【单片机】14-I2C通信之EEPROM

1.EEPROM概念 1.EEPROM 1.1 一些概念 &#xff08;1&#xff09;一些概念&#xff1a;ROM【只读存储器---硬盘】&#xff0c;RAM【随机访问存储器--内存】&#xff0c;PROM【可编程的ROM】&#xff0c;EPROM【可擦除ROM】&#xff0c;EEPROM【电可擦除ROM】 1.2 为什么需要EE…...

Mini-dashboard 和meilisearch配合使用

下载的meilisearch一般是development模式&#xff0c;内置客户端&#xff0c;修改客户端后需要重要全部编译&#xff0c;花时间太长了。前后端分离才是正道&#xff0c;客户端修改不用重新编译后端。 方法如下&#xff1a; 1、修改配置文件/etc/meilisearch.toml&#xff0c;…...

leetcode 886. 可能的二分法

给定一组 n 人&#xff08;编号为 1, 2, …, n&#xff09;&#xff0c; 我们想把每个人分进任意大小的两组。每个人都可能不喜欢其他人&#xff0c;那么他们不应该属于同一组。 给定整数 n 和数组 dislikes &#xff0c;其中 dislikes[i] [ai, bi] &#xff0c;表示不允许将…...

Elasticsearch:使用 ELSER 文本扩展进行语义搜索

在今天的文章里&#xff0c;我来详细地介绍如何使用 ELSER 进行文本扩展驱动的语义搜索。 安装 Elasticsearch 及 Kibana 如果你还没有安装好自己的 Elasticsearch 及 Kibana&#xff0c;请参考如下的链接来进行安装&#xff1a; 如何在 Linux&#xff0c;MacOS 及 Windows 上…...

OpenRadar DOA函数 Bartlett/CBF和Capon使用

Bartlett / CBF原理看这里 Capon原理看这里 这里只讲怎么调用openradar提供的aoa_bartlett和aoa_capon函数&#xff1a; 一些吐槽&#xff1a;虽然看起来openradar的作者代码水平很高&#xff0c;但里面有很多匪夷所思的写法&#xff0c;比如他demo里的解析文件格式就很迷啊等…...

二叉树--翻转二叉树

文章前言&#xff1a;如果有小白同学还是对于二叉树不太清楚&#xff0c;作者推荐&#xff1a;二叉树的初步认识_加瓦不加班的博客-CSDN博客 给你一棵二叉树的根节点 root &#xff0c;翻转这棵二叉树&#xff0c;并返回其根节点。 如果思路不清楚&#xff0c;请看动态页面&am…...

《Qt C++ 与 OpenCV:解锁视频播放程序设计的奥秘》

引言:探索视频播放程序设计之旅 在当今数字化时代,多媒体应用已渗透到我们生活的方方面面,从日常的视频娱乐到专业的视频监控、视频会议系统,视频播放程序作为多媒体应用的核心组成部分,扮演着至关重要的角色。无论是在个人电脑、移动设备还是智能电视等平台上,用户都期望…...

基于uniapp+WebSocket实现聊天对话、消息监听、消息推送、聊天室等功能,多端兼容

基于 ​UniApp + WebSocket​实现多端兼容的实时通讯系统,涵盖WebSocket连接建立、消息收发机制、多端兼容性配置、消息实时监听等功能,适配​微信小程序、H5、Android、iOS等终端 目录 技术选型分析WebSocket协议优势UniApp跨平台特性WebSocket 基础实现连接管理消息收发连接…...

CentOS下的分布式内存计算Spark环境部署

一、Spark 核心架构与应用场景 1.1 分布式计算引擎的核心优势 Spark 是基于内存的分布式计算框架&#xff0c;相比 MapReduce 具有以下核心优势&#xff1a; 内存计算&#xff1a;数据可常驻内存&#xff0c;迭代计算性能提升 10-100 倍&#xff08;文档段落&#xff1a;3-79…...

自然语言处理——Transformer

自然语言处理——Transformer 自注意力机制多头注意力机制Transformer 虽然循环神经网络可以对具有序列特性的数据非常有效&#xff0c;它能挖掘数据中的时序信息以及语义信息&#xff0c;但是它有一个很大的缺陷——很难并行化。 我们可以考虑用CNN来替代RNN&#xff0c;但是…...

【论文阅读28】-CNN-BiLSTM-Attention-(2024)

本文把滑坡位移序列拆开、筛优质因子&#xff0c;再用 CNN-BiLSTM-Attention 来动态预测每个子序列&#xff0c;最后重构出总位移&#xff0c;预测效果超越传统模型。 文章目录 1 引言2 方法2.1 位移时间序列加性模型2.2 变分模态分解 (VMD) 具体步骤2.3.1 样本熵&#xff08;S…...

html-<abbr> 缩写或首字母缩略词

定义与作用 <abbr> 标签用于表示缩写或首字母缩略词&#xff0c;它可以帮助用户更好地理解缩写的含义&#xff0c;尤其是对于那些不熟悉该缩写的用户。 title 属性的内容提供了缩写的详细说明。当用户将鼠标悬停在缩写上时&#xff0c;会显示一个提示框。 示例&#x…...

云原生安全实战:API网关Kong的鉴权与限流详解

&#x1f525;「炎码工坊」技术弹药已装填&#xff01; 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 一、基础概念 1. API网关&#xff08;API Gateway&#xff09; API网关是微服务架构中的核心组件&#xff0c;负责统一管理所有API的流量入口。它像一座…...

深入浅出Diffusion模型:从原理到实践的全方位教程

I. 引言&#xff1a;生成式AI的黎明 – Diffusion模型是什么&#xff1f; 近年来&#xff0c;生成式人工智能&#xff08;Generative AI&#xff09;领域取得了爆炸性的进展&#xff0c;模型能够根据简单的文本提示创作出逼真的图像、连贯的文本&#xff0c;乃至更多令人惊叹的…...

ubuntu22.04有线网络无法连接,图标也没了

今天突然无法有线网络无法连接任何设备&#xff0c;并且图标都没了 错误案例 往上一顿搜索&#xff0c;试了很多博客都不行&#xff0c;比如 Ubuntu22.04右上角网络图标消失 最后解决的办法 下载网卡驱动&#xff0c;重新安装 操作步骤 查看自己网卡的型号 lspci | gre…...

【java面试】微服务篇

【java面试】微服务篇 一、总体框架二、Springcloud&#xff08;一&#xff09;Springcloud五大组件&#xff08;二&#xff09;服务注册和发现1、Eureka2、Nacos &#xff08;三&#xff09;负载均衡1、Ribbon负载均衡流程2、Ribbon负载均衡策略3、自定义负载均衡策略4、总结 …...