Spring Security+JWT+Redis实现项目级前后端分离认证授权

1. 整体概述

        权限管理包括用户身份认证和授权两部分,简称认证授权。对于需要访问控制到资源,用户首先经过身份认证,认证通过后用户具有该资源的访问权限方可访问。

1.1 认证概述

 
认证是确认用户身份的过程,确保用户是谁。

1.1.1 认证组件

Spring Security的认证过程涉及以下组件

AuthenticationManager:作为认证的核心,AuthenticationManager负责处理用户的认证请求。它会委托不同的认证提供者(例如DaoAuthenticationProvider)来验证用户身份。AuthenticationManager接收一个包含用户凭证(如用户名、密码)的Authentication对象,然后通过认证提供者验证这些凭证的正确性。如果验证通过,AuthenticationManager会返回一个包含认证信息的Authentication对象。

Authentication:Authentication表示用户的认证信息,通常包括用户名、密码以及认证后得到的角色和权限。它是Spring Security中用户身份验证的核心数据结构。Authentication接口通常由UsernamePasswordAuthenticationToken等实现类来表示。认证完成后,Authentication对象将包含用户的认证状态和权限信息。

SecurityContextHolder:SecurityContextHolder是Spring Security中的核心类之一,它用来存储当前用户的认证信息。认证成功后,Authentication对象会被保存在SecurityContext中,SecurityContext会由SecurityContextHolder管理。在每个请求周期内,SecurityContextHolder提供对当前用户认证信息的访问,使得后续的请求可以通过SecurityContextHolder.getContext().getAuthentication()来获取当前用户的身份信息。

 1.1.2 认证过滤器链

1.1.3 认证流程步骤

1.2 授权概述


授权是根据用户身份信息判断用户是否有权限访问某些资源的过程。Spring Security的授权过程涉及以下组件:

AccessDecisionManager:AccessDecisionManager负责根据用户的认证信息和请求的资源,做出是否允许访问的决策。它会根据配置的权限要求以及用户的角色信息,决定用户是否能够访问特定的资源。AccessDecisionManager会使用多个AccessDecisionVoter来进行投票,综合多个投票结果后,做出最终的访问决策。

AccessDecisionVoter:AccessDecisionVoter是负责对用户访问权限进行投票的组件。它根据用户的Authentication对象和访问资源的ConfigAttribute(例如角色或权限要求)进行匹配。每个AccessDecisionVoter会判断用户是否符合特定的访问要求,并给出投票结果。所有投票的结果会由AccessDecisionManager汇总,最终决定是否允许访问。

ConfigAttribute:ConfigAttribute用于描述受保护资源的权限要求。它定义了访问某个资源所需的权限(如角色或操作权限)。ConfigAttribute通常与AccessDecisionManager结合使用,它告知AccessDecisionVoter该资源需要哪些权限,AccessDecisionVoter则根据这些要求判断用户是否具备访问该资源的权限。

1.3. 引入依赖

        一旦引入Spring security依赖后,系统中所有的资源都受保护起来,必须进行认证之后才能够访问,没有认证直接访问资源时,会跳转到Spring security默认的登录页面:http://localhost:8080/login

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

        Spring security会默认提供一个用户和密码,用户是user,密码是启动时的一个字符串:

 如果觉得启动时生成的默认密码比较长不方便登录,也可以使用配置文件配置固定的用户名和密码,密码前缀{noop}表示密码是明文。

2. 实现思路

        整个基于security框架实现认证授权思路如上所示,绿色框是框架已经实现的内容,白色框需要用户改写和实现的内容。 

2.1 认证登陆

        完成上图中白色框中的内容,具体步骤如下所示。

2.1.1 查询数据库用户

步骤1: 通过登录用户名查数据库中用户信息

通过自定义UserDetailsService,改写里面的loadUserByUsername方法,利用mybatis或其他框架从数据库中查询出用户信息。

除此之外,改写UserDetails接口中的方法,把数据库中查出的用户信息封装成UserDetails进行返回。

@Service
public class UserDetailsServiceImpl implements UserDetailsService {@Autowiredprivate UserMapper userMapper;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {//查询用户信息LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(User::getUserAccount,username);User user = userMapper.selectOne(queryWrapper);if(user==null){throw  new RuntimeException("此用户"+username+" 不存在");}//TODO 查询对应的权限信息// 把数据封装成UserDetailsreturn new LoginUser(user);}
}
@Data // get set 方法
@NoArgsConstructor // 空参构造器
@AllArgsConstructor // 全参构造器
public class LoginUser implements UserDetails {private User user;@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return null;}@Overridepublic String getPassword() {return user.getPassWord();}@Overridepublic String getUsername() {return user.getUserAccount();}@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic boolean isEnabled() {return true;}
}

 完成以上代码后,在数据库中存入用户,打开security默认登录页面测试是否能够使用数据库中存入的用户进行页面登录。

注意:因为目前还未做用户密码加密,密码是按明文存储的,所以在密码前需要加上{noop}标识

弹出以上界面说明登录成功,第一步操作完成。 

2.1.2 密码加密功能

步骤2: 用户密码加密功能

增加security配置,指定加密方式,从而自动实现用户密码按加密方式匹配,并要求用户新增接口和修改接口中,密码字段要调用配置中的加密方式进行明文加密,然后再存储数据库中。

增加security配置,定义密码加密方式。

@Configuration
@EnableWebSecurity
public class SecurityConfig  {// 配置密码加密方式,全局自动按这个方式加密@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}
}

增加此配置后,所有密码均采用此方式进行加密和匹配,当用户输入明文密码登录时,security框架会自动进行此方法进行加密后和数据库密码进行匹配。

注意:此时数据库存储的密码不应该是明文了,在用户注册或修改密码时,也应该调用此加密方法对明文进行加密并存储到数据库中。

此接口提供了两个方法,一个是明文加密,一个是密文匹配,测试方法如下:

 2.1.3 登录接口编写

步骤3: 登录接口实现

        首先在security配置类中配置认证管理器AuthenticationManager,此组件的作用是通过传入前端输入的用户名和密码,调用UserDetailsServicel去后台数据库比对用户信息,如果认证成功返回数据库用户的详情信息,失败返回null。

        传入用户名和密码前需要封装成authenticationToken对象,根据认证结果编写代码处理逻辑。如果认证失败,抛出异常报错给前端,如果认证成功,返回UserDetailsServicel的LoginUser用户,通过LoginUser获取userID,生成jwt返回给前端 

// 配置认证管理器@Beanpublic AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {return authenticationConfiguration.getAuthenticationManager();}

编写登录接口controller以及service实现类

@RestController
@CrossOrigin
@RequestMapping("api/v1")
@Tag(name= "用户登录接口文档") // 描述controller类的作用
@Slf4j
public class LoginController {@AutowiredLoginService loginService;@PostMapping("/authentication/login")@Operation(summary = "用户登陆接口")public R login(@RequestBody UserLoginVo user){try {Map<String, String> userLogin = loginService.login(user);return R.ok("登录成功",userLogin);}catch (Exception e){log.error(e.getMessage());return R.error(500,e.getMessage());}}
}
@Override
public Map<String, String> login(UserLoginVo user) {// AuthenticationManger authenticate 进行用户认证UsernamePasswordAuthenticationToken authenticationToken =new UsernamePasswordAuthenticationToken(user.getUserAccount(),user.getPassWord()); // 用户名和密码封装成 authenticationToken对象Authentication authenticate = authenticationManager.authenticate(authenticationToken); // 通过authenticationManager进行验证// 如果认证没通过,给出对应提示if(Objects.isNull(authenticate)){throw new RuntimeException("登录失败");}// 如果认证通过了,使用userid生成一个jwt, jwt存入ResponseResult返回LoginUser loginUser = (LoginUser) authenticate.getPrincipal();String userId = loginUser.getUser().getUserId().toString();String jwt = JwtUtil.createJWT(userId);// 把用户信息存入redis
//        redisService.setValue("login:" + userId, JSON.toJSONString(loginUser));redisService.setObject("login:" + userId,loginUser);// 把jwt封装成map返回前端HashMap<String, String> map = new HashMap<>();map.put("token",jwt);return map;
}

值得注意的是,由于登录接口是匿名登录的,需要进行放行,否则接口需要认证,无法访问。具体配置如下:

在配置中建议登录接口以及注册接口使用.anonymous(),仅对未认证(匿名)用户开放,已认证用户不可访问此资源。

在一些公开资源、静态资源、开放接口等建议使用.permitAll(),不需要任何身份验证,即允许已认证和未认证的用户。

// 3.设置路径权限
http.authorizeHttpRequests().requestMatchers("/api/v1/authentication/login").anonymous()  //对于登录接口,允许匿名访问.requestMatchers("/doc.html", "/swagger-ui/**", "/v3/api-docs/**").permitAll() // 允许匿名访问这些路径.anyRequest().authenticated(); // 除上面外的所有请求全部需要鉴权认证

 通过如上步骤,最终接口登录成功后会返回token

2.2 校验

2.2.1 定义jwt认证过滤器

        步骤1:获取token, 解析token获取其中的userid

        步骤2:通过userid 从 redis中获取用户信息

        步骤3: 将用户信息存入securityContextHolder

        如果在登录时存入 SecurityContextHolder,但应用是无状态的,每次请求时SecurityContextHolder 其实都是空的,无法保持状态。因为 Spring Security 的 SecurityContextHolder 只是一个线程级变量(ThreadLocal),它的生命周期仅限于当前请求的处理过程中。当一次 HTTP 请求到达服务器时:服务器分配一个线程处理请求。SecurityContextHolder 仅在该线程内存储 SecurityContext(认证信息)。当请求处理结束后,线程被回收,SecurityContextHolder 也被清空。
        正确的做法是在用户每次请求时,解析 Token,并动态地将用户信息存入 SecurityContextHolder。

@Component
@Slf4j
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {@Autowiredprivate RedisService redisService;@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {// 获取tokenString token = request.getHeader("token");if (!StringUtils.hasText(token)) {// 没有token就放行,让后面过滤器处理filterChain.doFilter(request, response);return;}// 解析tokenString userID;try {Claims claims = JwtUtil.parseJWT(token);userID = claims.getSubject();} catch (Exception e) {throw new RuntimeException("token非法:" + e.getMessage());}// 获取用户信息
//        Object userObj  = redisService.getValue("login:" + userID);
//        LoginUser user = JSON.parseObject((String) userObj, LoginUser.class);LoginUser user = redisService.getObject(("login:" + userID), LoginUser.class);if(user==null){throw new RuntimeException("用户未登录");}// 存入SecurityContextHolder//TODO 获取权限信息封装到Authentication中UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =new UsernamePasswordAuthenticationToken(user,null,null);  // 用户、密码、权限集合SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);// 放行filterChain.doFilter(request,response);}
}

2.2.2 添加到过滤器链中

先注入过滤器实例,然后在security配置中添加倒数第二行代码

// 4. 添加认证过滤器
http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);

2.2.3 登出接口编写

        因为认证过滤器对用户进行校验时,会根据token获取userid,并读取登录时存入redis中的用户信息,如果redis的用户信息读不到,就会被认证过滤器拦截,根据这一特性,可以设计登出接口,即删除用户在redis中的用户信息。

        用户访问登出接口后,在SecurityContextHolder中获取userid,并删除redis中的信息

@Override
public void logout() {// 获取SecurityContextHolder中的用户IDUsernamePasswordAuthenticationToken authentication =(UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();LoginUser loginUser = (LoginUser) authentication.getPrincipal();Integer userId = loginUser.getUser().getUserId();// 删除redis中的值redisService.deleteValue("login:"+userId);
}

2.3 授权

2.3.1 设置资源权限

        通过注解的方式进行资源权限的标识,决定各个接口需要什么样的权限才能访问。

        步骤1:开启注解,在security配置类添加注解 @EnableMethodSecurity  // 开启权限注解

@EnableMethodSecurity  // 开启权限注解
public class SecurityConfig  {...
}

        步骤2: 在接口上添加注解配置权限 @PreAuthorize("hasAuthority('test')")

@PreAuthorize("hasAuthority('sys:device:getList')")
@GetMapping("/device/getList")
public R getDevice(@PathVariable("id") Integer id){... 
}

步骤2中除了可以在接口上添加注解的方式设置权限,还可以基于配置进行权限设置,在securityConfig配置类中添加如下代码等同于注解:

http
.authorizeHttpRequests()
.requestMatchers("/api/v1/device/getList")
.hasAuthority("sys:device:getList");

2.3.2 封装权限信息

        将用户的权限信息封装成security需要的对象,以便在登录和校验时,能够将权限信息输入到对应的结构中。

在LoginUser类中构造有参构造器,并重写getAuthorities方法;

public class LoginUser implements UserDetails {private User user;private List<String> permissions;/*** 有参构造器* */public LoginUser(User user,List<String> permissions){this.user = user;this.permissions = permissions;}/*** 获取权限对象* */@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {log.debug("permissions:"+permissions);ArrayList<GrantedAuthority> authorities = new ArrayList<>();// 把permissions中string类型的权限信息封装成simpleGrantedAuthority对象for (String permission : permissions) {SimpleGrantedAuthority authority = new SimpleGrantedAuthority(permission);authorities.add(authority);log.debug("authorities:"+authorities);}return authorities;}
}

        在UserDetailsService中调用LoginUser的有参构造器,将用户信息和权限信息注入到LoginUser实例中,以供登录时AuthenticationManager认证管理器调用返回用户权限信息。登录接口获取到认证管理器的用户权限信息后会存入redis中,在下一步中,认证过滤器会取出相关权限信息存入到SecurityContextHolder中以供后续过滤器校验。

@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {//查询用户信息LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(User::getUserAccount,username);User user = userMapper.selectOne(queryWrapper);if(user==null){throw  new RuntimeException("此用户"+username+" 不存在");}//查询对应的权限信息List<PermissionSelectVo> permissions = userService.getUserByAccount(username).getPermissions();ArrayList<String> permissionList = new ArrayList<>();for (PermissionSelectVo permission:permissions){permissionList.add(permission.getPermissionCode());}// 把数据封装成UserDetailsreturn new LoginUser(user,permissionList);}

2.3.3 注入权限信息

        在用户登录时,需要把权限信息注入到LoginUser中并存入redis,因为在上一步中已经将权限信息放入LoginUser中,所以在登录后,直接将LoginUser存入redis中就自然存入了权限信息。

        在校验用户时,过滤器中从redis读出权限信息,传入UsernamePasswordAuthenticationToken中,生成带权限的用户信息存入SecurityContextHolder让其后续的过滤器进行权限校验。

// 存入SecurityContextHolder// 获取权限信息封装到Authentication中
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
new UsernamePasswordAuthenticationToken(loginUser,null,loginUser.getAuthorities());  // 用户、密码、权限集合    SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);

3 其他功能

3.1  异常捕获

        如果在认证过程中出现的异常会被封装成AuthenticationException然后调用AuthenticationEntryPoint对象的方法进行一场处理。

        如果是授权过程中出现的异常会被封装成AccessDeniedException然后调用AccessDeniedHandler对象的方法去进行异常处理。所以如果我们需要自定义异常处理,给前端返回异常信息,只需要自定义AuthenticationEntryPoin 和 AccessDeniedHandler然后配置给Spring Security 即可。

3.1. 1 认证异常

@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {@Overridepublic void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {// 处理异常response.setStatus(HttpStatus.UNAUTHORIZED.value()); // 设置状态码response.setContentType("application/json");response.setCharacterEncoding("utf-8");response.getWriter().write(JSON.toJSONString(R.error(401,"尚未认证,请进行认证操作!")));}
}

3.1.2 权限异常 

@Component
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {@Overridepublic void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {response.setStatus(HttpStatus.FORBIDDEN.value()); // 设置状态码response.setContentType("application/json");response.setCharacterEncoding("utf-8");response.getWriter().write(JSON.toJSONString(R.error(403,"无权访问!")));}
}

3.2 跨域

        在Spring Security框架中,如果已经配置了Spring的跨域处理,通常还需要针对Spring Security进行跨域配置,两者都需要配置才能确保跨域请求能够顺利通过Spring Security的安全检查。一般情况下,为了保证配置的统一性,会把配置集中使用其中一个配置,另一个配置直接放行

3.2.1 Spring跨域配置

@Configuration
public class WebConfig implements WebMvcConfigurer {@Overridepublic void addCorsMappings(CorsRegistry registry) {registry.addMapping("/**")		//设置允许跨域的路径.allowedOrigins()		//设置允许跨域请求的域名,例如:allowedOrigins("http://localhost:3000", "http://example.com"),如果为空则是允许所有.allowCredentials(true) 	//是否允许发送凭证token.allowedMethods("GET","POST","PUT","DELETE")  //指定允许的 HTTP 方法.maxAge(3600 * 24);		//预检请求有效期}
}

3.2.2 Security跨域配置

// 跨域配置
http.cors().configurationSource(corsConfigurationSource()) //跨域解决方案
@Bean
CorsConfigurationSource corsConfigurationSource(){CorsConfiguration corsConfiguration = new CorsConfiguration();corsConfiguration.setAllowedHeaders(Arrays.asList("*"));corsConfiguration.setAllowedMethods(Arrays.asList("*"));corsConfiguration.setAllowedOrigins(Arrays.asList("*"));corsConfiguration.setMaxAge(3600L);UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();source.registerCorsConfiguration("/**",corsConfiguration);return source;}

3.3 csrf攻击

        前后端分离架构本身就是无状态token校验的,所以天然防范csrf攻击,无需进行设置,默认打开的,所以在前后端架构下需要设置为关闭。

http.csrf().disable(); // 关闭csrf

4 总结

本文通过引入security框架,在登录接口中通过传入页面用户登录信息(用户名、密码)给UsernamePasswordAuthenticationToken来验证用户身份,通过后返回token给前端并存入redis用户信息。其中验证的用户身份是通过实现接口UserDetailsService从数据库中获取用户信息并封装到LoginUser对象中。校验时,通过添加认证过滤器完成从token获取用户信息,并调用UsernamePasswordAuthenticationToken验证用户权限。

security的完整配置文件如下所示:

@Configuration
@EnableWebSecurity
@EnableMethodSecurity  // 开启权限注解
public class SecurityConfig  {@Autowiredprivate JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;// 注入token校验过滤器@Autowiredprivate AuthenticationEntryPoint authenticationEntryPoint;  //注入授权异常处理器@Autowiredprivate AccessDeniedHandler accessDeniedHandler; //注入认证异常处理器// 配置密码加密方式,全局自动按这个方式加密@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}// 配置认证管理器@Beanpublic AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {return authenticationConfiguration.getAuthenticationManager();}// security 配置@Beanpublic SecurityFilterChain httpSecurity(HttpSecurity http) throws Exception {// 1.前后端分离架构本身就是无状态token校验的,所以天然防范csrf攻击,可以关闭http.csrf().disable(); // 关闭csrf
//                .csrfTokenRepository(CookieCsrfTokenRepository. withHttpOnlyFalse());  // 将令牌保存到cookie中允许cookie前端获取// 2.前后端分离架构不通过Session获取SecurityContexthttp.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);// 3.设置路径权限http.authorizeHttpRequests().requestMatchers("/api/v1/authentication/login").anonymous()  //对于登录接口,允许匿名访问.requestMatchers("/doc.html", "/swagger-ui/**", "/v3/api-docs/**").permitAll() // 允许匿名访问这些路径.anyRequest().authenticated(); // 除上面外的所有请求全部需要鉴权认证// 4. 添加认证过滤器http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);// 5. 配置异常处理器http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint)  // 配置认证失败处理器.accessDeniedHandler(accessDeniedHandler);  // 配置授权失败处理器// 6.跨域配置http.cors(); // 允许跨域return http.build();}
}

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据结构系列三:List+顺序表+ArrayList

数据结构系列三 一、List&#xff08;1&#xff09;什么是List&#xff08;2&#xff09;常见接口介绍&#xff08;3&#xff09;List的使用 二、顺序表与ArrayList&#xff08;1&#xff09;线性表&#xff08;2&#xff09;顺序表&#xff08;3&#xff09;顺序表常用方法的模…

全局变量,局部变量

在main函数中又定义一遍全局变量&#xff1a;会导致程序出错 因为在函数中调用这个全局变量时&#xff0c;调用的值是在头文件下面的初始值&#xff0c;虽然你在main函数中改变了变量的值&#xff0c;但是你在main函数中重新定义了 如果这样写会过50%的数据&#xff0c;因为在…

Unity贴图与模型相关知识

一、贴图 1.贴图的类型与形状 贴图类型 贴图形状 2.在Unity中可使用一张普通贴图来生成对应的法线贴图&#xff08;但并不规范&#xff09; 复制一张该贴图将复制后的贴图类型改为Normal Map 3.贴图的sRGB与Alpha sRGB&#xff1a;勾选此选项代表此贴图存储于Gamma空间中…

互联网搜索、联网搜索 API 的探索与公开接口、大模型联网搜索接口、全网搜索接口

互联网搜索、联网搜索 API 的探索与公开接口、大模型联网搜索接口、全网搜索接口 关键词&#xff1a;互联网搜索、API 接口、实时数据、大模型联网、智能问答、数据采集、技术实践、成本优势、市场对比 概述 在当前大模型及人工智能技术迅速发展的背景下&#xff0c;如何让离…

牛客练习赛134 —— B题 python 补题 + 题解

牛客练习赛134 B 题目描述 示例输入&#xff1a; 1 5 1 2 4 5 6 2 5 4 6 9示例输出&#xff1a; 32解题思路&#xff1a; 题目大意 给定一个2行n列的矩阵&#xff0c;允许交换两列一次&#xff0c;从左上角(1,1)走到右下角(2,n)&#xff0c;每一步只能向右或向下移动&#x…

电脑开机一段时间就断网,只有重启才能恢复网络(就算插网线都不行),本篇文章直接解决,不要再看别人的垃圾方法啦

下面的是我解决问题的心路历程&#xff0c;不想看的可以直接跳到解决方法上面&#xff01; 内心思路&#xff1a; w11电脑更新过系统后&#xff0c;我的电脑是常年不关机的&#xff0c;但是一天突然断网&#xff0c;试了很多方法都连不上&#xff0c;重启电脑就会好&#xff0…

Ubuntu部署ktransformers

准备工作 一台服务器 CPU&#xff1a;500G GPU&#xff1a;48G&#xff08;NVIDIA4090&#xff09; 系统&#xff1a;Ubuntu20.04&#xff08;github的文档好像用的是22.04&#xff09; 第一步&#xff1a;下载权重文件 1.下载hfd wget https://hf-mirror.com/hfd/hfd.s…

【Elasticsearch】同一台服务器部署集群

【Elasticsearch】同一台服务器部署集群 1. 同一台服务器搭建ES集群2. 配置不同的node节点3. ES集群中安装IK分词器4. 启动es集群5. Kibana访问集群6. es-head7. 集群中创建索引7.1 什么是分片以及分片的好处7.2 副本&#xff08;Replication&#xff09;7.3 通过es-head创建索…

1-1 VS Code+Keil5+STM32CubeMX开发环境搭建

1.0 卸载相关程序 使用这个方式安装工具&#xff0c;先将原先下载安装的软件去掉&#xff0c;然后再安装新的软件&#xff0c;这个卸载过程需要将原来的工具干净的卸载掉&#xff0c;使用专门的卸载工具&#xff0c;将注册表等文件也全部删除掉。 对于STM32CubeMX还要删除&…

C# 从基础神经元到实现在0~9数字识别

训练图片:mnist160 测试结果:1000次训练学习率为0.1时,准确率在60%以上 学习的图片越多&#xff0c;训练的时候越长(比如把 epochs*10 10000或更高时)效果越好 using System; using System.Collections.Generic; using System.Drawing; using System.IO; using System.Windo…

【算法与数据结构】单调队列

目录 单调队列 使用单调队列维护滑动窗口 具体过程&#xff1a; 代码实现&#xff1a; 复杂度分析&#xff1a; 使用单调队列优化动态规划 例题 单调队列 单调队列(deque)是一种特殊的队列&#xff0c;队列中的元素始终按严格递增或者递减排列。这样就可以保证队头元素…

矩阵的扩展运算(MATLAB和pytorch实例)

秩&#xff08;Rank&#xff09;的定义 秩的计算 初等行变换法&#xff08;最常用&#xff09;行列式法&#xff08;仅适用于方阵&#xff09; 满秩的分类方阵的满秩非方阵的满秩几何意义应用场景判断方法 矩阵的特征值 定义求解特征值 特征方程步骤 关键性质 迹与行列式相似矩…

python面试题整理

Python 如何处理异常&#xff1f; Python中&#xff0c;使用try 和 except 关键字来捕获和处理异常 try 块中放置可能会引发异常的代码&#xff0c;然后在except块中处理这些异常。 能补充一下finally的作用吗&#xff1f; finally 块中的代码无论是否发生异常都会执行&#xf…

linux之perf(17)PMU事件采集脚本

Linux之perf(17)PMU事件采集脚本 Author: Once Day Date: 2025年2月22日 一位热衷于Linux学习和开发的菜鸟&#xff0c;试图谱写一场冒险之旅&#xff0c;也许终点只是一场白日梦… 漫漫长路&#xff0c;有人对你微笑过嘛… 全系列文章可参考专栏: Perf性能分析_Once_day的博…

Java数据结构-排序

目录 一.本文关注焦点 二.七大排序分析及相关实现 1.冒泡排序 2.简单选择排序 3.直接插入排序 4.希尔排序 5.堆排序 ​编辑 6.归并排序 7.快速排序 一.本文关注焦点 各种排序的代码实现及各自的时间空间复杂度分析及稳定性。 时间复杂度&#xff1a;在比较排序中主…

改进收敛因子和比例权重的灰狼优化算法【期刊论文完美复现】(Matlab代码实现)

2 灰狼优化算法 2.1 基本灰狼优化算法 灰狼优化算法是一种模拟灰狼捕猎自然群体行为的社会启发式优化算法&#xff0c;属于一种新型的群体智能优化算法。灰狼优化算法具有高度的灵活性&#xff0c;是当前较为流行的优化算法之一。灰狼优化算法主要分为三个阶段&#xff1a;追…

创建Linux虚拟环境并远程连接

目录 下载VMware软件 下载CentOS 创建虚拟环境 远程连接Linux系统 下载VMware软件 不会的可以参考 传送门 下载CentOS 不会的可以参考 传送门 创建虚拟环境 打开VMware软件&#xff0c;创建虚拟机 选择典型安装 找到我们安装好的centOS文件&#xff0c;之后会自动检…

汽车智能制造企业数字化转型SAP解决方案总结

一、项目实施概述 项目阶段划分&#xff1a; 蓝图设计阶段主数据管理方案各模块蓝图设计方案下一阶段工作计划 关键里程碑&#xff1a; 2022年6月6日&#xff1a;项目启动会2022年12月1日&#xff1a;系统上线 二、总体目标 通过SAP实施&#xff0c;构建研产供销协同、业财一…

《Head First设计模式》读书笔记 —— 命令模式

文章目录 本节用例餐厅类比点餐流程角色与职责从餐厅到命令模式 命令模式第一个命令对象实现命令接口实现一个命令 使用命令对象NoCommand与空对象 定义命令模式支持撤销功能使用状态实现撤销多层次撤销 One One One …… more things宏命令使用宏命令 队列请求日志请求 总结 《…

基于YOLO11深度学习的运动鞋品牌检测与识别系统【python源码+Pyqt5界面+数据集+训练代码】

《------往期经典推荐------》 一、AI应用软件开发实战专栏【链接】 项目名称项目名称1.【人脸识别与管理系统开发】2.【车牌识别与自动收费管理系统开发】3.【手势识别系统开发】4.【人脸面部活体检测系统开发】5.【图片风格快速迁移软件开发】6.【人脸表表情识别系统】7.【…