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

news2024/11/19 11:16:35

前言

        在结束理论知识的学习后,荔枝开始项目学习,这个系列文章将围绕荔枝学习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<>();
    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    //spring security中的一种对密码的编译方法
    @Autowired
    private PasswordEncoder passwordEncoder;

    // Spring IOC容器的初始化中就会执行该init方法
    @PostConstruct
    private 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());
    }
    @Override
    public AdminUserDetails getAdminByUsername(String username) {
        //在存放默认用户对象的集合中来查找指定的用户信息,如果有就返回符合条件的第一条数据;
        // 如果没有就会返回一个null
        List<AdminUserDetails> findList = adminUserDetailsList.stream().filter(item -> item.getUsername().equals(username)).collect(Collectors.toList());
        if(CollUtil.isNotEmpty(findList)){
            return findList.get(0);
        }
        return null;
    }

    @Override
    public List<UmsResource> getResourceList() {
        return resourceList;
    }

    @Override
    public 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 {
    @Autowired
    private RestfulAccessDeniedHandler restfulAccessDeniedHandler;
    @Autowired
    private RestAuthenticationEntryPoint restAuthenticationEntryPoint;
    @Autowired
    private IgnoreUrlsConfig ignoreUrlsConfig;

    @Bean
    SecurityFilterChain 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 filter
        httpSecurity.addFilterBefore(jwtAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class);
        //添加自定义未授权和未登录结果返回
        httpSecurity.exceptionHandling()
                .accessDeniedHandler(restfulAccessDeniedHandler)
                .authenticationEntryPoint(restAuthenticationEntryPoint);
        return httpSecurity.build();
    }

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

    @Bean
    public 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);

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private JwtTokenUtil jwtTokenUtil;
    @Value("${jwt.tokenHeader}")
    private String tokenHeader;
    @Value("${jwt.tokenHead}")
    private String tokenHead;

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain chain) throws ServletException, IOException {
        String authHeader = request.getHeader(this.tokenHeader);
        if (authHeader != null && authHeader.startsWith(this.tokenHead)) {
//            获取token
            String 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;
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return this.authorityList.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());
    }

    @Override
    public String getPassword() {
        return this.password;
    }

    @Override
    public String getUsername() {
        return this.username;
    }
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }
    @Override
    public 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 {
    @Autowired
    private RestfulAccessDeniedHandler restfulAccessDeniedHandler;
    @Autowired
    private RestAuthenticationEntryPoint restAuthenticationEntryPoint;
    @Autowired
    private IgnoreUrlsConfig ignoreUrlsConfig;

    @Bean
    SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
        ......
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        //使用MD4加密算法
        return new Md4PasswordEncoder();
    }

    @Bean
    public 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的结构也是比较重要的!

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

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1064801.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Pytorch笔记】6.Transforms

pytorch官方文档 - transforms transforms需要使用计算机视觉工具包&#xff1a;torchvision。 torchvision.transforms&#xff1a;常用的图像预处理方法&#xff1b; torchvision.datasets&#xff1a;常用数据集的dataset实现&#xff0c;如MNIST、CIFAR-10、ImageNet等&am…

基于python编写的excel表格数据标记的exe文件

目录 一、需求&#xff1a; 二、思路&#xff1a; 三、工具 四、设计过程 &#xff08;一&#xff09;根据需要导入相关的图形界面库 &#xff08;二&#xff09;创建图形窗口 &#xff08;三&#xff09;标签设计 &#xff08;四&#xff09;方法按钮设计 &#xff0…

值得推荐的阿里巴巴Java开源项目

说明&#xff1a;以下都是项目中使用过的&#xff0c;后续将持续更新&#xff01;&#xff01;&#xff01; 1、开源 Java 诊断工具 Arthas Arthas&#xff08;阿尔萨斯&#xff09;是阿里巴巴开源的 Java 诊断工具&#xff0c;深受开发者喜爱。 Arthas 采用命令行交互模式&…

「专题速递」JPEG AI、端到端图像编码的标准化及产品落地、深度学习

从最初的追随者到如今的领跑者&#xff0c;中国的超高清视频编解码技术已经走过20年的漫长征程。从开始制定不同的视频编解码标准&#xff0c;如H.264/265、AV1、VVC、AVS&#xff0c;再到积极地探索基于AI的视频编码技术。视频编解码——这一将视频数据高效压缩、传输和解码还…

软件项目和安全项目案例(承接软件和安全项目合作)

公司有专业的软件开发团队和安全研究团队&#xff0c;具备完善的安全测试、安全培训、安全开发、安全服务等安全解决方案&#xff0c;可以助力政企研发专业、高效、安全、稳定的软件产品&#xff0c;欢迎项目咨询、商务合作&#xff01; 一、软件开发项目咨询 1.承接车载等终…

了解了spring mvc web容器中一个http请求的全过程,能给我们提升多少武力值

继上一篇文章什么&#xff0c;这年头还有人不知道404_cow__sky的博客-CSDN博客后&#xff0c;有些同学发现&#xff0c;学了之后有啥用&#xff0c;有什么实际场景可以用到吗&#xff1f;程序员就是这样&#xff0c;不习惯于纸上谈兵&#xff0c;给一个场景show me code才是最实…

免交互输入

here document 免交互 对文本内容进行操作&#xff1a; 标准输入的替代品。 语法格式 命令 <<标记 内容 标记 命令&#xff1a;linux 命令 注意事项&#xff1a; 1.标记可以使用的任意字符。(字母和数字&#xff0c;一般不适用特殊字符。以字母开EOF) 2.结尾的标记一…

PHP8中的魔术方法-PHP8知识详解

在PHP 8中&#xff0c;魔术方法是一种特殊的方法&#xff0c;它们以两个下划线&#xff08;__&#xff09;开头。魔术方法允许您定义类的行为&#xff0c;例如创建对象、调用其他方法或访问和修改类的属性。以下是一些常见的魔术方法&#xff1a; __construct(): 类的构造函数…

【LeetCode高频SQL50题-基础版】打卡第1天:第1~10题

文章目录 【LeetCode高频SQL50题-基础版】打卡第1天&#xff1a;第1~10题⛅前言 可回收且低脂的产品&#x1f512;题目&#x1f511;题解 寻找用户推荐人&#x1f512;题目&#x1f511;题解 大的国家&#x1f512;题目&#x1f511;题解 文章浏览I&#x1f512;题目&#x1f5…

【计算机组成 课程笔记】7.3 高速缓存 Cache

课程链接&#xff1a; 计算机组成_北京大学_中国大学MOOC(慕课) 7 - 5 - 705-高速缓存的工作原理&#xff08;16-00--&#xff09;_哔哩哔哩_bilibili 在【计算机组成 课程笔记】7.1 存储层次结构概况_Elaine_Bao的博客-CSDN博客中提到&#xff0c;因为CPU和内存的速度差距越来…

R语言12篇文章带您深入了解限制立方条图(Restricted cubic spline,RCS)

临床上&#xff0c;因变量和临床的结局有时候不是线性关系&#xff0c;而回归模型有一个重要的假设就是自变量和因变量呈线性关联&#xff0c;因此非线性关系模型用回归分析来拟合受到限制。因此&#xff0c;一个更好的解决方法是拟合自变量与因变量之间的非线性关系&#xff0…

SICP第三章 模块化,对象和状态

赋值和局部状态 我们可以用一个或几个状态变量刻画一个对象的状态&#xff0c;在他们之中维持有关这一对象的历史&#xff0c;即能够确定该对象当前行为的充分的信息 局部状态变量 过程 dispatch 以一个消息为输入&#xff0c;返回两个局部过程之一 引进赋值带来的利益

【Windows】Win11重置网络设置后WLAN消失

问题描述 Windows11重置网络设置后WLAN消失。 原因分析 WLAN相关服务未启动。 解决方案 Win r 打开运行 运行 services.msc 按名称排序&#xff0c;找到这两个服务 右键启动 右键打开属性&#xff0c;找到启动类型&#xff0c;改为自动 WLAN已找回

七、【套索工具组】

文章目录 套索工具多边形套索工具磁性套索工具 套索工具 如下图&#xff0c;以我们抠图为例&#xff0c;当我们选用套索工具选中一块区域后&#xff0c;然后按ShiftF5调出填充工具菜单&#xff0c;然后再选中内容识别&#xff0c;就可以去掉该区域&#xff1a; 那么如何做到加…

云盘文件批量分享脚本

前言 偶尔需要用就心血来潮做了下目前支持 百度网盘批量分享115网盘批量分享天翼云盘批量分享123盘批量分享(2023年10月05日新增)夸克网盘批量分享(2023年10月06日新增)蓝奏网盘批量分享(2023年10月06日新增)进度条展示复制到剪贴板下载分享链接分享信息自定义配置自定义提取码…

HDLbits: Edgedetect

module top_module (input clk,input [7:0] in,output [7:0] pedge );reg [7:0] in_old;always(posedge clk)beginin_old < in; end assign pedge < in & ~in_old; endmodule 对于边缘检测而言&#xff0c;若是0→1和1→0都检测则为in^in_old&#xf…

智能家电经营小程序商城的作用是什么

大小家电是人们生活所需&#xff0c;如冰箱、电脑、电视机、饮水机等&#xff0c;都有很高的市场需求度&#xff0c;传统人们购买往往是前往当地商场&#xff0c;而随着如今互联网电商深入&#xff0c;越来越多的用户选择线上消费&#xff0c;这也促进着传统家电经营商家需要转…

k8s-10 ingress-nginx 特性

TLS加密 创建证书 测试 auth认证 创建认证文件 rewrite重定向 进入域名 会自动重定向hostname.html 示例二&#xff1a; 测试 后面必须跟westos 这个关键字 canary金丝雀发布 基于header灰度 场景&#xff1a;版本的升级迭代&#xff0c;比如一个service 升级到另…

【Hello Algorithm】认识一些简单的递归

本篇博客介绍&#xff1a; 认识一些简单的递归 认识一些简单的递归 打印一个字符串全部的子序列打印一个字符串的全排列不申请额外的空间 逆序输出一个栈 我在刚刚学习C语言的时候写过一个汉诺塔问题 大家可以参考下我之前写的这篇博客 汉诺塔问题 其实这个问题也可以这么解决…

【RK3588】YOLO V5在瑞芯微板子上部署问题记录汇总

YOLO V5训练模型部署到瑞芯微的板子上面&#xff0c;官方是有给出案例和转过详情的。并且也提供了Python版本的推理代码&#xff0c;以及C语言的代码。 但是&#xff0c;对于转换过程中的细节&#xff0c;哪些需要改&#xff1f;怎么改&#xff1f;如何改&#xff0c;和为什么…