使用Spring Security+jwt+redis实现登录注册逻辑

Spring Security

Spring Security 是一个提供身份验证、授权和防御常见攻击的框架。它为保护命令式和响应式应用程序提供了一流的支持,是保护基于 Spring 的应用程序的事实标准。
这篇博客主要是记录自己第一次使用springSecurity实现登录逻辑的过程。

使用Spring Security+jwt+redis实现登录注册逻辑

1. 导入依赖
<!--jwt依赖-->
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId>
</dependency>
<dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId>
</dependency>
<dependency><groupId>javax.xml.bind</groupId><artifactId>jaxb-api</artifactId>
</dependency>
<!--SpringSecurity启动器-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- redis -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
  1. 创建JwtUtils工具类
package com.h3m.utils;import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;import java.util.Date;@Data
@Component
@ConfigurationProperties(prefix = "config.jwt",ignoreInvalidFields = true)
public class JwtUtils {private String secret;private long expire;// private String header;  /*** 生成token, 根据用户名* @param username* @return*/public String createToken(String username){// 获取当前时间Date nowDate = new Date();// 过期时间Date expireDate = new Date(nowDate.getTime() + expire * 1000);// 生成tokenreturn Jwts.builder().setHeaderParam("typ", "JWT").setSubject(username).setIssuedAt(nowDate).setExpiration(expireDate).signWith(SignatureAlgorithm.HS512, secret).compact();}/*** 获取token中的信息* @param token* @return*/public Claims getTokenClaim(String token){try {return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();}catch (Exception e){e.printStackTrace();return null;}}/*** 获取token中的用户名* @param token* @return*/public String getUsernameFromToken(String token){Claims claims = getTokenClaim(token);if(claims == null){return null;}return claims.getSubject();}/*** Validate the token* @param token* @param userDetails* @return 返回true表示有效*/public boolean validateToken(String token, UserDetails userDetails) {String username = getUsernameFromToken(token);return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));}/*** Check if the token is expired* @param token* @return 返回true表示过期*/private boolean isTokenExpired(String token) {Date expiration = getTokenClaim(token).getExpiration();return expiration.before(new Date());}
}
  1. 创建Result结果类
package com.h3m.domain;import com.h3m.constants.SYSConstant;
import lombok.Data;@Data
public class Result {private Integer code;private String message;private Object data;public Result() {}public Result(Integer code) {this.code = code;}public Result(Integer code, String message) {this.code = code;this.message = message;}public Result(Integer code, String message, Object data) {this.code = code;this.message = message;this.data = data;}// 创建一些静态常量字段public static final Result ADD_SUCCESS = new Result(SYSConstant.CODE_SUCCESS,SYSConstant.ADD_SUCCESS);public static final Result ADD_ERROR = new Result(SYSConstant.CODE_ERROR, SYSConstant.ADD_ERROR);public static final Result UPDATE_SUCCESS = new Result(SYSConstant.CODE_SUCCESS, SYSConstant.UPDATE_SUCCESS);public static final Result UPDATE_ERROR = new Result(SYSConstant.CODE_ERROR, SYSConstant.UPDATE_ERROR);public static final Result DELETE_SUCCESS = new Result(SYSConstant.CODE_SUCCESS, SYSConstant.DELETE_SUCCESS);public static final Result DELETE_ERROR = new Result(SYSConstant.CODE_ERROR, SYSConstant.DELETE_ERROR);public static final Result LOGIN_SUCCESS = new Result(SYSConstant.CODE_SUCCESS, SYSConstant.LOGIN_SUCCESS);public static final Result LOGIN_ERROR = new Result(SYSConstant.CODE_ERROR, SYSConstant.LOGIN_ERROR);
}
  1. 创建SecurityUser实体类

​ 我们需要创建一个用于Security的实体类,并实现UserDetails接口

package com.h3m.domain.entity;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;@Data
@AllArgsConstructor
@NoArgsConstructor
public class SecurityUser implements UserDetails {private User CurrentUserInfo;
//    private Collection<? extends GrantedAuthority> authorities;private List<Access> accessList;@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {List<GrantedAuthority> authorities = accessList.stream().map(access -> new SimpleGrantedAuthority(access.getAccessName())).collect(Collectors.toList());return authorities;}@Overridepublic String getPassword() {return CurrentUserInfo.getPassword();}@Overridepublic String getUsername() {return CurrentUserInfo.getUserName();}@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic boolean isEnabled() {return true;}
}
  1. 创建ResponseUtils工具类

​ 这个工具类主要是用于返回数据到前端的。

package com.h3m.utils;import com.fasterxml.jackson.databind.ObjectMapper;
import com.h3m.domain.Result;import javax.servlet.http.HttpServletResponse;
import java.io.IOException;public class ResponseUtils {public static void writeResponse(HttpServletResponse response, Result result) throws IOException {response.setCharacterEncoding("UTF-8");response.setContentType("application/json; charset=UTF-8");ObjectMapper objectMapper = new ObjectMapper();String jsonResponse = objectMapper.writeValueAsString(result);response.getWriter().write(jsonResponse);}
}
  1. 创建登录过滤器jwtLoginFilter

​ 这里我们主要是重写三个方法,实现整个登录的过滤。

package com.h3m.filter;import com.fasterxml.jackson.databind.ObjectMapper;
import com.h3m.constants.SYSConstant;
import com.h3m.domain.Result;
import com.h3m.utils.JwtUtils;
import com.h3m.domain.entity.SecurityUser;
import com.h3m.domain.entity.User;
import com.h3m.utils.ResponseUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;public class JwtLoginFilter extends UsernamePasswordAuthenticationFilter {private JwtUtils jwtConfig;private AuthenticationManager authenticationManager;private RedisTemplate redisTemplate;public JwtLoginFilter(AuthenticationManager authenticationManager, JwtUtils jwtConfig, RedisTemplate redisTemplate) {this.authenticationManager = authenticationManager;this.jwtConfig = jwtConfig;this.redisTemplate = redisTemplate;super.setFilterProcessesUrl("/login");}/*** 这个方法是在用户登录的时候调用的方法* @param request* @param response* @return* @throws AuthenticationException*/@Overridepublic Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {//获取表单提供的数据ObjectMapper objectMapper = new ObjectMapper();try {User user = objectMapper.readValue(request.getInputStream(), User.class);//校验==认证的过程Authentication authenticate = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(user.getUserName(), user.getPassword(), new ArrayList<>()));return authenticate;} catch (IOException e) {e.printStackTrace();throw new RuntimeException("认证失败");}}/*** 这个方法是在用户登录成功后调用的方法* @param request* @param response* @param chain* @param authResult*/@Overrideprotected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws ServletException, IOException {//得到用户名SecurityUser securityUser = (SecurityUser) authResult.getPrincipal();System.out.println("securityUser = " + securityUser);String username = securityUser.getUsername();//生成tokenString token = jwtConfig.createToken(username);//存入到redis  username: 权限redisTemplate.opsForValue().set(username, securityUser.getAuthorities());// 返回tokenresponse.addHeader("Authorization", "Bearer " + token);// 登录成功后,封装用户以及对应权限信息返回// 返回用户信息Result result = new Result(SYSConstant.CODE_SUCCESS, SYSConstant.LOGIN_SUCCESS, securityUser);ResponseUtils.writeResponse(response, result);}/*** 这个方法是在用户登录失败后调用的方法* @param request* @param response* @param failed* @throws AuthenticationException*/@Overrideprotected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws AuthenticationException, ServletException, IOException {ResponseUtils.writeResponse(response, new Result(SYSConstant.CODE_ERROR, "登录失败, 主要原因:"+failed.getMessage()));}
}
  1. 创建权限过滤器 AuthFilter
package com.h3m.filter;import com.h3m.utils.JwtUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;public class AuthFilter extends BasicAuthenticationFilter {private JwtUtils jwtConfig;private RedisTemplate redisTemplate;public AuthFilter(AuthenticationManager authenticationManager, RedisTemplate redisTemplate, JwtUtils jwtConfig) {super(authenticationManager);this.jwtConfig = jwtConfig;this.redisTemplate = redisTemplate;}/*** 这里我们只需要重写过滤方法,在其中实现我们的逻辑即可**/@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {// 从请求头中获取token所在的字段String authorizationHeader = request.getHeader("Authorization");if (authorizationHeader != null && !authorizationHeader.isEmpty() && authorizationHeader.startsWith("Bearer ")) {// 获取tokenString token = authorizationHeader.substring(7);// 解析token,获得用户名String username = jwtConfig.getUsernameFromToken(token);//从redis中获得该用户名对应的权限List<String> authList = (List<String>) redisTemplate.opsForValue().get(username);//将取出的权限存入到权限上下文中,表示当前token对应的用户具备哪些权限Collection<GrantedAuthority> authorities = new ArrayList<>();if (authList != null) {for (String auth : authList) {authorities.add(new SimpleGrantedAuthority(auth));}}// 生成认证信息对象UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, null, authorities);// 把认证信息对象存入到权限上下文中SecurityContextHolder.getContext().setAuthentication(authenticationToken);}// 放行chain.doFilter(request, response);}}
  1. 创建登出处理器
package com.h3m.handler;import com.h3m.constants.SYSConstant;
import com.h3m.domain.Result;
import com.h3m.utils.JwtUtils;
import com.h3m.utils.ResponseUtils;
import com.mysql.cj.util.StringUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.stereotype.Component;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;@RequiredArgsConstructor
@Component
public class TokenLogOutHandler implements LogoutHandler {private final JwtUtils jwtConfig;private final RedisTemplate redisTemplate;@Overridepublic void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {//1.获取token所在请求头String authorizationHeader = request.getHeader("Authorization");if (!StringUtils.isNullOrEmpty(authorizationHeader) && authorizationHeader.startsWith("Bearer ")) {String token = authorizationHeader.substring(7); // 去掉 "Bearer " 前缀,得到 token// 使用 token 进行后续操作String username = jwtConfig.getUsernameFromToken(token);redisTemplate.delete(username);}try {ResponseUtils.writeResponse(response, new Result(SYSConstant.CODE_SUCCESS, "登出成功"));} catch (IOException e) {throw new RuntimeException(e);}}
}
  1. 创建UserDetailServiceImpl实现类
package com.h3m.service.ServiceImpl;import com.h3m.domain.entity.Access;
import com.h3m.domain.entity.SecurityUser;
import com.h3m.domain.entity.User;
import com.h3m.domain.entity.UserRole;
import com.h3m.service.AccessService;
import com.h3m.service.RoleAccessService;
import com.h3m.service.UserRoleService;
import com.h3m.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.PortResolverImpl;
import org.springframework.stereotype.Service;import java.math.BigInteger;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;@RequiredArgsConstructor
@Service
public class UserDetailServiceImpl implements UserDetailsService {private final UserService userService;private final UserRoleService userRoleService;private final RoleAccessService roleAccessService;private final AccessService accessService;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {//根据用户名从数据库查询到该用户的信息User user = userService.selectByUsername(username);if(Objects.isNull(user)) {throw new UsernameNotFoundException("当前用户不存在");}// 查询用户的权限信息// 1. 根据user_id查询用户的角色_idUserRole userRole = userRoleService.selectByUserId(user.getId());// 2. 根据角色id查询用户的权限_idList<BigInteger> AccessIdList = roleAccessService.selectByRoleId(userRole.getRoleId());// 3. 根据权限id查询用户的权限信息List<Access> accessList = accessService.selectByAccessIdList(AccessIdList);// 将权限信息转换为 GrantedAuthority 集合
//        List<GrantedAuthority> authorities = accessList.stream()
//                .map(access -> new SimpleGrantedAuthority(access.getAccessName()))
//                .collect(Collectors.toList());// 返回SecurityUser对象return new SecurityUser(user, accessList);}
}
  1. 创建SpringSecurity配置类
package com.h3m.config;import com.h3m.filter.AuthFilter;
import com.h3m.filter.JwtLoginFilter;import com.h3m.handler.TokenLogOutHandler;
import com.h3m.utils.JwtUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;import javax.servlet.http.HttpServletResponse;@Configuration
@RequiredArgsConstructor
public class SecurityConfig {private final JwtUtils jwtConfig;private final TokenLogOutHandler logoutHandler;private final RedisTemplate redisTemplate;@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http, AuthenticationManager authenticationManager) throws Exception {JwtLoginFilter jwtLoginFilter = new JwtLoginFilter(authenticationManager, jwtConfig, redisTemplate);AuthFilter authFilter = new AuthFilter(authenticationManager, redisTemplate, jwtConfig);http.csrf().disable().authorizeHttpRequests().antMatchers("/login", "/user/register").permitAll().anyRequest().authenticated().and().addFilter(jwtLoginFilter)  // 登录请求需要经过这个过滤器.addFilterBefore(authFilter, UsernamePasswordAuthenticationFilter.class)  // 确保 authFilter 在合适的位置.logout().logoutUrl("/logout").addLogoutHandler(logoutHandler).logoutSuccessHandler((request, response, authentication) -> response.setStatus(HttpServletResponse.SC_OK));return http.build();}@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}@Beanpublic AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {return authenticationConfiguration.getAuthenticationManager();}}
  1. 编写接口

/login

由于我们在过滤器中已经实现了jwt的生成,以及返回信息,实际上登录接口这里我们不需要实现任何内容。

    /*** 登录接口* @return*/@PostMapping("/login")public void login() {// 获取当前登录用户的信息
//        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
//        SecurityUser currentUser = (SecurityUser) authentication.getPrincipal();
//        return new Result(SYSConstant.CODE_SUCCESS, SYSConstant.LOGIN_SUCCESS, currentUser);}

/register 我们在注册的时候需要对密码进行编码

private final PasswordEncoder passwordEncoder;private final UserService userService;private final UserRoleService userRoleService;@PostMapping("/register")public Result register(@RequestBody User user) {log.info("用户注册: {}", user);// 对密码进行加密user.setPassword(passwordEncoder.encode(user.getPassword()));// 注册用户, 调用save方法插入一条记录boolean flag_1 = userService.save(user);// 创建默认用户角色UserRole userRole = new UserRole();userRole.setUserId(user.getId());// 插入一条记录, 虽然这里只传入了一个userId,但是roleId字段有默认值为2,即普通用户boolean flag_2 = userRoleService.save(userRole);if (flag_1 && flag_2) {return new Result(SYSConstant.CODE_SUCCESS, "注册成功");} else {return new Result(SYSConstant.CODE_ERROR, "注册失败");}}

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

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

相关文章

【Unity学习笔记】解决疑似升级Win11或使用Unity6导致Unity旧版本无法打开的问题

【Unity学习笔记】解决疑似升级Win11或使用Unity6导致Unity旧版本无法打开的问题 一句话省流&#xff1a; 确保项目地址没有任何中文&#xff0c;重新申请个许可证&#xff0c;然后该咋就咋&#xff0c;完事。 ——————————————————————————————…

滚雪球学Redis[2.2讲]:列表(List)

全文目录&#xff1a; 前言列表类型的使用场景典型使用场景示例 常用命令1. LPUSH&#xff1a;在列表左侧插入元素2. RPUSH&#xff1a;在列表右侧插入元素3. LPOP&#xff1a;从列表左侧弹出元素4. RPOP&#xff1a;从列表右侧弹出元素5. LLEN&#xff1a;获取列表的长度6. LR…

华为云应用侧Android测试APP

05.华为云应用侧Android测试APP 本APP在填写或修改部分参数后能够完成token获取&#xff0c;影子消息读取&#xff0c;命令下发。APP共包含三个界面&#xff1a;主界面获取token、影子消息获取界面、命令下发界面。 实现过程参见&#xff1a;华为云应用侧Android Studio开发-…

SQLITE 构建多表查询

在SQLite中,构建多表查询时,最常用的方式是使用 JOIN 操作符来连接多个表。下面是一些常见的查询方式及其示例: INNER JOIN INNER JOIN 是最常见的联接方式,它返回在两个表中都匹配的记录。```sql sql 复制代码 SELECT 表1.列名, 表2.列名 FROM 表1 INNER JOIN 表2 ON 表1.…

企业如何制定适合自己的专利布局策略

在竞争激烈的市场环境中&#xff0c;专利布局对于企业的发展和竞争优势的建立至关重要。以下将分要点解析企业如何制定适合自己的专利布局策略。 1、明确企业的发展战略和市场定位 企业首先需要深入了解自身的长期发展规划和短期业务目标。明确是要通过技术创新来开拓新市场&am…

微服务之间的相互调用的几种常见实现方式对比

目录 微服务之间的相互调用的几种实现方式 一、HTTP HTTP/RESTful API调用工作原理 二、RPC 设计理念与实现方式 协议与传输层 RPC远程调用工作原理 应用场景与性能考量 特点 三、Feign 设计理念与实现方式 协议与传输层 Feign调用的基本流程 Feign调用的工作原理…

Electron + ts + vue3 + vite

正常搭建脚手架&#xff1a;npm create vitelatest 项目名称 安装electron的相关依赖&#xff1a;注&#xff1a;安装时终端url要项目名那一层 安装npm install electron -D安装打包工具&#xff1a;npm install electron-builder -D开发工具&#xff1a;npm install electron-…

前端_001_html扫盲

文章目录 概念标签及属性常用全局属性head里常用标签body里常用标签表情符号 url编码 概念 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</title> </head> <body></bod…

python爬虫 - 深入requests模块

&#x1f308;个人主页&#xff1a;https://blog.csdn.net/2401_86688088?typeblog &#x1f525; 系列专栏&#xff1a;https://blog.csdn.net/2401_86688088/category_12797772.html 目录 前言 一、下载网络文件 &#xff08;一&#xff09;基本步骤 &#xff08;二&…

高级java每日一道面试题-2024年10月8日-数据库篇[Redis篇]-谈—谈缓存穿透、缓存击穿和缓存雪崩,以及解决办法?

如果有遗漏,评论区告诉我进行补充 面试官: 谈—谈缓存穿透、缓存击穿和缓存雪崩&#xff0c;以及解决办法? 我回答: 在分布式系统和高并发场景中&#xff0c;缓存是提高系统性能和响应速度的重要手段。然而&#xff0c;如果缓存使用不当&#xff0c;可能会遇到一些问题&…

Windows下MYSQL8.0如何恢复root权限

误操作把root权限清掉导致数据库无法登录&#xff08;确实很难受&#xff09;&#xff0c;在网上找了很多方法&#xff0c;发现没有很行之有效的方法&#xff0c;在多方尝试终于找到了适合敏感宝宝体质的方法。 C:\Users\Administrator>mysql -u root -P3307 ERROR 1045 (2…

数据结构——遍历二叉树

目录 什么是遍历二叉树 根据遍历序列确定二叉树 例题&#xff08;根据先序中序以及后序中序求二叉树&#xff09; 遍历的算法实现 先序遍历 中序遍历 后序遍历 遍历算法的分析 二叉树的层次遍历 二叉树遍历算法的应用 二叉树的建立 复制二叉树 计算二叉树深度 计算二…

Redis 高可用方案

Redis 高可用性&#xff08;High Availability&#xff09;是指在 Redis 系统中实现持续的可用性&#xff0c;即使在发生硬件故障或其他意外情况下&#xff0c;系统仍能保持运行。 1 常用方案 为了实现 Redis 的高可用性&#xff0c;以下是几种常用方案&#xff1a; 1.1 使用…

常见校验算法介绍

文章目录 一、奇偶校验二、 校验和三、 BCC&#xff08;Block Check Character&#xff0c;块校验字符&#xff09;校验四、CRC&#xff08;循环冗余校验&#xff09;五、海明码校验六、MD5&#xff08;消息摘要算法第五版&#xff09;和 SHA&#xff08;安全哈希算法&#xff…

1688商品评论接口技术深度解析与实战代码实现

引言 在电商领域&#xff0c;商品评论是消费者购物决策的重要依据。1688作为国内领先的B2B电商平台&#xff0c;提供了丰富的商品评论接口&#xff0c;供商家和开发者获取、管理并展示商品评论数据。本文将详细介绍如何调用1688商品评论接口&#xff0c;并提供Python实战代码示…

系统架构设计师教程 第16章 16.1 嵌入式系统概述 笔记

16.1 嵌入式系统概述 嵌入式系统 (Embedded System) 是为了特定应用专门构建的计算机系统&#xff0c;其架构是随着嵌入式系统的逐步应用而发展形成。 16.1.1 嵌入式系统发展历程 五个阶段&#xff1a; 一&#xff1a;单片微型计算机 (SCM) 阶段&#xff0c;即单片机时代。…

小猿口算自动PK脚本

大家好&#xff0c;我是小黄。 近期&#xff0c;众多大学生炸鱼小猿口算APP,把一众小学生都快虐哭了&#xff0c;小黄听闻后&#xff0c;也跃跃欲试。对此小黄也参考网上的资料写了一个自动Pk的脚步。 首先大家需要安装一个pytorch环境过程中&#xff0c;如果小伙伴对此不熟悉的…

C语言-输入输出

实验一&#xff1a;编写一个输出两行自定义字符的 C 程序 一、实验目的 熟悉 C 语言的基本结构和语法。掌握 printf() 函数的使用方法。了解在 Code::Blocks 中编写、编译和运行程序的过程。 二、实验内容 编写一个 C 程序&#xff0c;要求输出两行字符&#xff0c;内容自定…

软考《信息系统运行管理员》- 4.3 信息系统软件运维的过程

4.3 信息系统软件运维的过程 文章目录 4.3 信息系统软件运维的过程日常运维日常运维的内容日常运行例行测试维护例行测试流程的关键点例行维护流程的关键点 定期测试维护 缺陷诊断与修复信息系统软件缺陷的概念信息系统软件缺陷的分类信息系统软件缺陷诊断与修复流程缺陷诊断与…

springboot kafka多数据源,通过配置动态加载发送者和消费者

前言 最近做项目&#xff0c;需要支持kafka多数据源&#xff0c;实际上我们也可以通过代码固定写死多套kafka集群逻辑&#xff0c;但是如果需要不修改代码扩展呢&#xff0c;因为kafka本身不处理额外逻辑&#xff0c;只是起到削峰&#xff0c;和数据的传递&#xff0c;那么就需…