整合SpringSecurity

目录

前言

数据库设计

用户表

角色表

用户角色表

权限表

角色权限表

插入数据

表的实体类

用户表实体类

角色表实体类

权限表实体类

mapper层接口

UserMapper

RoleMapper

AuthorityMapper

封装登录信息

统一响应结果

上下文相关类

jwt令牌工具类

依赖导入

属性类

yml配置文件

jwt令牌工具类

SpringContextUtils工具类 

实现接口UserDetailsService

登录接口LoginController

自定义token验证过滤器

配置过滤器链

SecurityConfig完整配置

测试接口

开始测试

登录测试

权限测试

测试管理员权限访问

测试用户权限访问

总结


前言

接着《初识SpringSecurity》来看如何在项目中整合SpringSecurity这个安全框架。

https://blog.csdn.net/qq_74312711/article/details/134978245?spm=1001.2014.3001.5501

上次我们是将用户添加到内存中,实际开发中肯定是要存储在数据库里的。先来看数据库是如何设计的,以及如何把用户信息交给Security处理。

数据库设计

用户表

存放用户信息,主要是存放用户名和密码。

create table if not exists tb_user
(id       bigint auto_increment comment '主键'primary key,username varchar(16) not null comment '用户名',password varchar(64) not null comment '密码',constraint usernameunique (username)
)comment '用户表';

角色表

存放角色信息,角色是用户的身份。

create table if not exists tb_role
(id   bigint auto_increment comment '主键'primary key,role varchar(8) not null comment '角色',constraint roleunique (role)
)comment '角色表';

用户角色表

用户和角色的关系:一个用户可以有多个角色身份,一个角色身份可以有多个用户对应。

create table if not exists tb_user_role
(id       bigint auto_increment comment '主键'primary key,username varchar(16) not null comment '用户名;不唯一',role     varchar(8)  not null comment '角色;不唯一'
)comment '用户角色表';

权限表

存放权限信息,角色拥有权限。

create table if not exists tb_authority
(id        bigint auto_increment comment '主键'primary key,authority varchar(16) not null comment '权限',constraint authorityunique (authority)
)comment '权限表';

角色权限表

角色和权限的关系:一个角色可以拥有多个权限,一个权限可以被多个角色拥有。

create table if not exists tb_role_authority
(id        bigint auto_increment comment '主键'primary key,role      varchar(8)  not null comment '角色;不唯一',authority varchar(16) not null comment '权限;不唯一'
)comment '角色权限表';

插入数据

注意密码不能明文存储,要先经过编码处理。

@Test
void getEncodePassword() {String password = "123abc";BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();String encode = bCryptPasswordEncoder.encode(password);System.out.println(encode); // $2a$10$3OF9ij55dB7X2ffXby16Qu8n6Y96NV.RtHcza4vWO1EjoFO2JrsiW
}
insert into tb_user (id, username, password)
values (null, '艾伦', '$2a$10$3OF9ij55dB7X2ffXby16Qu8n6Y96NV.RtHcza4vWO1EjoFO2JrsiW');insert into tb_role (id, role)
values (null, '管理员'),(null, '用户');insert into tb_user_role (id, username, role)
values (null, '艾伦', '管理员'),(null, '艾伦', '用户');insert into tb_authority (id, authority)
values (null, '权限1'),(null, '权限2'),(null, '权限3');insert into tb_role_authority (id, role, authority)
values (null, '管理员', '权限1'),(null, '管理员', '权限2'),(null, '管理员', '权限3'),(null, '用户', '权限1'),(null, '用户', '权限2');

表的实体类

用户表实体类

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;@AllArgsConstructor
@Data
@NoArgsConstructor
public class UserEntity {private Long id;private String username;private String password;
}

角色表实体类

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;@AllArgsConstructor
@Data
@NoArgsConstructor
public class RoleEntity {private Long id;private String role;
}

权限表实体类

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;@AllArgsConstructor
@Data
@NoArgsConstructor
public class AuthorityEntity {private Long id;private String authority;
}

mapper层接口

UserMapper

import org.apache.ibatis.annotations.Mapper;@Mapper
public interface UserMapper {UserEntity selectUserByUsername(String username);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.example.mapper.UserMapper"><!--查询用户--><select id="selectUserByUsername" resultType="UserEntity">select id, username, passwordfrom tb_userwhere username = #{username};</select>
</mapper>

RoleMapper

import org.apache.ibatis.annotations.Mapper;import java.util.List;@Mapper
public interface RoleMapper {List<RoleEntity> selectRoleByUsername(String username);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.example.mapper.RoleMapper"><!--查询角色--><select id="selectRoleByUsername" resultType="RoleEntity">select tr.id, tr.rolefrom tb_user_role turleft join tb_role tr on tur.role = tr.rolewhere tur.username = #{username};</select>
</mapper>

AuthorityMapper

import org.apache.ibatis.annotations.Mapper;import java.util.List;@Mapper
public interface AuthorityMapper {List<AuthorityEntity> selectAuthorityByRole(String role);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.example.mapper.AuthorityMapper"><!--查询权限--><select id="selectAuthorityByRole" resultType="AuthorityEntity">select ta.id, ta.authorityfrom tb_role_authority traleft join tb_authority ta on tra.authority = ta.authoritywhere tra.role = #{role};</select>
</mapper>

封装登录信息

登录请求必须要提供用户名、密码和角色,后面都会用到。

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;@AllArgsConstructor
@Data
@NoArgsConstructor
public class UserLogin {private String username;private String password;private String role;
}

统一响应结果

import lombok.Data;import java.io.Serializable;@Data
public class Result<T> implements Serializable {// 响应码:1代表成功,0代表失败private Integer code;// 提示信息private String message;// 响应数据private T data;public static <T> Result<T> success() {Result<T> result = new Result<>();result.code = 1;return result;}public static <T> Result<T> success(T object) {Result<T> result = new Result<>();result.code = 1;result.data = object;return result;}public static <T> Result<T> error(String message) {Result<T> result = new Result<>();result.code = 0;result.message = message;return result;}
}

上下文相关类

ThreadLocal线程局部变量,将信息放入上下文,后面要用可以直接取出。

public class BaseContext {public static ThreadLocal<String> threadLocal = new ThreadLocal<>();public static void setContext(String context) {threadLocal.set(context);}public static String getContext() {return threadLocal.get();}public static void removeContext() {threadLocal.remove();}
}

jwt令牌工具类

依赖导入

jwt令牌依赖

<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version>
</dependency>

配置处理器依赖

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-configuration-processor</artifactId><optional>true</optional>
</dependency>

以下依赖必须导入,否则jwt令牌用不了。 

<dependency><groupId>javax.xml.bind</groupId><artifactId>jaxb-api</artifactId><version>2.3.1</version>
</dependency>
<dependency><groupId>javax.activation</groupId><artifactId>activation</artifactId><version>1.1.1</version>
</dependency>
<!-- no more than 2.3.3-->
<dependency><groupId>org.glassfish.jaxb</groupId><artifactId>jaxb-runtime</artifactId><version>2.3.3</version>
</dependency>

属性类

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;@Data
@Component
@ConfigurationProperties(prefix = "token.jwt")
public class JwtTokenProperties {// 签名密钥private String signingKey;// 有效时间private Long expire;
}

yml配置文件

token:jwt:signing-key: jwt-token-signing-key #签名密钥expire: 7200000 #有效时间

jwt令牌工具类

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;import java.util.Date;
import java.util.Map;@Component
@RequiredArgsConstructor
public class JwtTokenUtils {private final JwtTokenProperties jwtTokenProperties;public String getJwtToken(Map<String, Object> claims) {String signingKey = jwtTokenProperties.getSigningKey();Long expire = jwtTokenProperties.getExpire();return Jwts.builder().setClaims(claims).signWith(SignatureAlgorithm.HS256, signingKey).setExpiration(new Date(System.currentTimeMillis() + expire)).compact();}public Claims parseJwtToken(String jwtToken) {String signingKey = jwtTokenProperties.getSigningKey();return Jwts.parser().setSigningKey(signingKey).parseClaimsJws(jwtToken).getBody();}
}

SpringContextUtils工具类 

SpringContextUtils工具类用于在过滤器中获取Bean,因为在过滤器中无法初始化Bean组件,所以使用上下文获取。

import jakarta.annotation.Nonnull;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;@Component
public class SpringContextUtils implements ApplicationContextAware {private static ApplicationContext applicationContext;public static ApplicationContext getApplicationContext() {return applicationContext;}@Overridepublic void setApplicationContext(@Nonnull ApplicationContext applicationContext) throws BeansException {SpringContextUtils.applicationContext = applicationContext;}@SuppressWarnings("unchecked")public static <T> T getBean(String name) throws BeansException {if (applicationContext == null) {return null;}return (T) applicationContext.getBean(name);}
}

实现接口UserDetailsService

实现UserDetailsService接口,重写loadUserByUsername方法。方法返回一个User对象,即为UserDetails对象。我们将用户的信息封装到User对象中,返回给Security处理。

import lombok.RequiredArgsConstructor;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Service;import java.util.List;
import java.util.StringJoiner;@Service
@RequiredArgsConstructor
public class UserLoginService implements UserDetailsService {private final UserMapper userMapper;private final RoleMapper roleMapper;private final AuthorityMapper authorityMapper;@Overridepublic UserDetails loadUserByUsername(String username) {// 查询用户UserEntity userEntity = userMapper.selectUserByUsername(username);if (userEntity == null) {throw new RuntimeException("用户不存在");}// 获取用户登录身份roleString role = BaseContext.getContext();List<RoleEntity> roles = roleMapper.selectRoleByUsername(username);// 判断用户是否有role身份boolean flag = true;for (RoleEntity r : roles) {if (r.getRole().equals(role)) {flag = false;break;}}if (flag) {throw new RuntimeException("用户" + username + "没有" + role + "身份");}// 查询角色权限List<AuthorityEntity> authorities = authorityMapper.selectAuthorityByRole(role);// 权限之间用","分隔StringJoiner stringJoiner = new StringJoiner(",", "", "");authorities.forEach(authority -> stringJoiner.add(authority.getAuthority()));return new User(userEntity.getUsername(), userEntity.getPassword(),AuthorityUtils.commaSeparatedStringToAuthorityList(stringJoiner.toString()));}
}

登录接口LoginController

将角色信息放入上下文中,在UserLoginService中会用到角色信息。

import lombok.RequiredArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.StringJoiner;@RestController
@RequiredArgsConstructor
public class LoginController {private final UserLoginService userLoginService;private final JwtTokenUtils jwtTokenUtils;@PostMapping("/login")public Result<String> login(@RequestBody UserLogin userLogin) {try {// 将登录用户角色放入上下文BaseContext.setContext(userLogin.getRole());UserDetails userDetails = userLoginService.loadUserByUsername(userLogin.getUsername());// 获取用户权限StringJoiner authorityString = new StringJoiner(",", "", "");Collection<? extends GrantedAuthority> authorities = userDetails.getAuthorities();for (GrantedAuthority authority : authorities) {authorityString.add(authority.getAuthority());}Map<String, Object> claims = new HashMap<>();claims.put("username", userLogin.getUsername());claims.put("role", userLogin.getRole());claims.put("authorityString", authorityString.toString());String jwtToken = jwtTokenUtils.getJwtToken(claims);return Result.success(jwtToken);} catch (Exception e) {return Result.error(e.getMessage());}}
}

自定义token验证过滤器

注意:

1. 在过滤器中无法初始化Bean组件

2. 在过滤器中抛出的异常无法被全局异常处理器捕获

import com.fasterxml.jackson.databind.ObjectMapper;
import io.jsonwebtoken.Claims;
import jakarta.servlet.FilterChain;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;import java.io.IOException;@Component
public class JwtAuthenticationFilter extends BasicAuthenticationFilter {public JwtAuthenticationFilter(AuthenticationManager authenticationManager) {super(authenticationManager);}@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException {try {// 获取请求路径String url = request.getRequestURL().toString();// 为登录请求放行if (url.contains("/login")) {filterChain.doFilter(request, response);return; // 结束方法}// 获取请求头中的tokenString jwtToken = request.getHeader("token");if (!StringUtils.hasLength(jwtToken)) {// token不存在,交给其他过滤器处理filterChain.doFilter(request, response);return; // 结束方法}// 过滤器中无法初始化Bean组件,使用上下文获取JwtTokenUtils jwtTokenUtils = SpringContextUtils.getBean("jwtTokenUtils");if (jwtTokenUtils == null) {throw new RuntimeException();}// 解析jwt令牌Claims claims;try {claims = jwtTokenUtils.parseJwtToken(jwtToken);} catch (Exception e) {throw new RuntimeException();}// 获取用户信息String username = (String) claims.get("username"); // 用户名String authorityString = (String) claims.get("authorityString"); // 权限Authentication authentication = new UsernamePasswordAuthenticationToken(username, null,AuthorityUtils.commaSeparatedStringToAuthorityList(authorityString));// 将用户信息放入SecurityContext上下文SecurityContextHolder.getContext().setAuthentication(authentication);// 将用户名放入线程局部变量BaseContext.setContext(username);filterChain.doFilter(request, response);} catch (Exception e) {// 过滤器中抛出的异常无法被全局异常处理器捕获,直接返回错误结果response.setCharacterEncoding("utf-8");response.setContentType("application/json; charset=utf-8");String value = new ObjectMapper().writeValueAsString(Result.error("token验证失败"));response.getWriter().write(value);}}
}

配置过滤器链

注意给登录请求放行,要不然访问不了登录接口。

// 配置过滤器链
@Bean
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity httpSecurity) throws Exception {httpSecurity.authorizeHttpRequests(authorizeHttpRequests -> authorizeHttpRequests.requestMatchers(HttpMethod.POST, "/login").permitAll() // 登录请求放行.requestMatchers(HttpMethod.GET, "/test1").hasAnyAuthority("权限1", "权限2").requestMatchers(HttpMethod.GET, "/test2").hasAuthority("权限3"));httpSecurity.authenticationProvider(authenticationProvider());// 禁用登录页面httpSecurity.formLogin(AbstractHttpConfigurer::disable);// 禁用登出页面httpSecurity.logout(AbstractHttpConfigurer::disable);// 禁用sessionhttpSecurity.sessionManagement(AbstractHttpConfigurer::disable);// 禁用httpBasichttpSecurity.httpBasic(AbstractHttpConfigurer::disable);// 禁用csrf保护httpSecurity.csrf(AbstractHttpConfigurer::disable);// 通过上下文获取AuthenticationManagerAuthenticationManager authenticationManager = SpringContextUtils.getBean("authenticationManager");// 添加自定义token验证过滤器httpSecurity.addFilterBefore(new JwtAuthenticationFilter(authenticationManager), UsernamePasswordAuthenticationFilter.class);return httpSecurity.build();
}

SecurityConfig完整配置

import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {private final UserDetailsService userDetailsService;// 加载用户信息@Beanpublic UserDetailsService userDetailsService() {return userDetailsService;}// 身份验证管理器@Beanpublic AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {return configuration.getAuthenticationManager();}// 处理身份验证@Beanpublic AuthenticationProvider authenticationProvider() {DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());daoAuthenticationProvider.setUserDetailsService(userDetailsService);return daoAuthenticationProvider;}// 密码编码器@Beanpublic BCryptPasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}// 配置过滤器链@Beanpublic SecurityFilterChain defaultSecurityFilterChain(HttpSecurity httpSecurity) throws Exception {httpSecurity.authorizeHttpRequests(authorizeHttpRequests -> authorizeHttpRequests.requestMatchers(HttpMethod.POST, "/login").permitAll() // 登录请求放行.requestMatchers(HttpMethod.GET, "/test1").hasAnyAuthority("权限1", "权限2").requestMatchers(HttpMethod.GET, "/test2").hasAuthority("权限3"));httpSecurity.authenticationProvider(authenticationProvider());// 禁用登录页面httpSecurity.formLogin(AbstractHttpConfigurer::disable);// 禁用登出页面httpSecurity.logout(AbstractHttpConfigurer::disable);// 禁用sessionhttpSecurity.sessionManagement(AbstractHttpConfigurer::disable);// 禁用httpBasichttpSecurity.httpBasic(AbstractHttpConfigurer::disable);// 禁用csrf保护httpSecurity.csrf(AbstractHttpConfigurer::disable);// 通过上下文获取AuthenticationManagerAuthenticationManager authenticationManager = SpringContextUtils.getBean("authenticationManager");// 添加自定义token验证过滤器httpSecurity.addFilterBefore(new JwtAuthenticationFilter(authenticationManager), UsernamePasswordAuthenticationFilter.class);return httpSecurity.build();}
}

测试接口

用户有权限访问/test1,没有权限访问/test2。管理员有权限访问/test1和/test2。

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
public class DemoController {@GetMapping("/test1")public String demo1() {System.out.println("test1访问成功!");return "test1访问成功!";}@GetMapping("/test2")public String demo2() {System.out.println("test2访问成功!");return "test2访问成功!";}
}

开始测试

登录测试

测试成功

权限测试

测试管理员权限访问

/test1访问成功

/test2测试成功

测试用户权限访问

用户登录

/test1访问成功

/test2访问失败

可以看到测试的结果都是正确的,说明成功地实现了权限控制。

总结

以上就是如何在项目中整合SpringSecurity的基本用法,我们再来看一下官方的描述:

Spring Security是一个强大且高度可定制的身份验证和访问控制框架。它是保护基于Spring的应用程序的事实标准。

Spring Security是一个专注于为Java应用程序提供身份验证和授权的框架。与所有Spring项目一样,Spring Security的真正力量在于它可以多么容易地扩展以满足自定义需求。

强大且高度可定制就是SpringSecurity受欢迎的关键,我们还可以对以上的案例进行优化。例如,我们不将用户角色的权限放在token令牌中,而是放在Redis中。在进行token验证的时候,解析出用户名,拿用户名去Redis中找对应的权限。又或者我们可以自定义处理器,处理用户未登录(未携带token),处理用户权限不足等。

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

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

相关文章

2023.12.17 关于 Redis 的特性和应用场景

目录 引言 Redis 特性 内存中存储数据 可编程性 可扩展性 持久化 支持集群 高可用性 Redis 优势 Redis 用作数据库 Redis 相较于 MySQL 优势 Redis 相较于 MySQL 劣势 Redis 用作缓存 典型场景 Redis 存储 session 信息 Redis 用作消息队列 初心 消息队列的…

智能优化算法应用:基于适应度相关算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于适应度相关算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于适应度相关算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.适应度相关算法4.实验参数设定5.算法…

构建高效统一的音视频联动融合通信调度平台

在信息技术日益高度发展的今天&#xff0c;越来越多的企业或者组织机构重视如何提高内外部的工作效率&#xff0c;但由于传统的通信方式如电话、邮件、短信、传真等方式无法满足企业高效、快速的沟通要求&#xff0c;因此需要一个可以将各种通信方式结合在一起的通信系统来满足…

驭见大模型 智领新征程丨泰迪智能科技荣登2023年度广东省人工智能应用项目风云榜

12月15日&#xff0c;由广东省科学技术厅、广东省工业和信息化厅、广东省人力资源和社会保障厅、广东省政务服务数据管理局、广东省科学技术协会指导&#xff0c;广东省人工智能产业协会主办的2023年粤港澳大湾区人工智能产业大会正式举办&#xff0c;大会以“驭见大模型、智领…

PTFE四氟托盘应用于化工、医药、食品行业

PTFE托盘是一种广泛应用于化工、医药、食品等行业的特种托盘&#xff0c;采用聚四氟乙烯&#xff08;PTFE&#xff09;材料制造而成。PTFE是一种具有出色耐腐蚀性、高温稳定性和优异物理特性的材料。 PTFE托盘在化工领域中被广泛应用&#xff0c;主要用于处理腐蚀性的化学物质。…

【leetcode876】链表的中间结点Java代码讲解

12.19 链表的中间结点 给你单链表的头结点 head &#xff0c;请你找出并返回链表的中间结点。 如果有两个中间结点&#xff0c;则返回第二个中间结点。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5] 输出&#xff1a;[3,4,5] 解释&#xff1a;链表只有一个中间结点&a…

工作纪实38-排查cpu彪高

昨天晚上上线了一个服务&#xff0c;第二天发现CPU持续飙高到70&#xff5e;90%&#xff0c;触发平台的自动扩容&#xff0c;后定位出问题后降低到3% 怀疑部分代码使用的线程在持续工作没有释放&#xff08;死循环&#xff09;进入机器&#xff0c;使用top -H 找出系统中使用C…

接口自动化测试难点:数据库验证解决方案

接口自动化中的数据库验证&#xff1a;确保数据的一致性和准确性 接口自动化测试是现代软件开发中不可或缺的一环&#xff0c;而数据库验证则是确保接口返回数据与数据库中的数据一致性的重要步骤。本文将介绍接口自动化中的数据库验证的原理、步骤以及示例代码&#xff0c;帮…

机器学习 | 贝叶斯方法

不同于KNN最近邻算法的空间思维&#xff0c;线性算法的线性思维&#xff0c;决策树算法的树状思维&#xff0c;神经网络的网状思维&#xff0c;SVM的升维思维。 贝叶斯方法强调的是 先后的因果思维。 监督式模型分为判别式模型和生成式模型。 判别模型和生成模型的区别&#xf…

【LeetCode刷题-树】--257.二叉树的所有路径

257.二叉树的所有路径 方法&#xff1a;深度优先搜索 /*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNode(int val) { this.val val; }* TreeNode(int val, T…

win环境下启动kafka Port already in use: 6688; nested exception is

背景 zk启动成功后&#xff0c;接下来启动kafka&#xff0c;再启动kafka后一直说端口被占用。 端口占用解决办法: netstat -aon|findstr 9092 taskkill -f -pid 7780 杀掉后&#xff0c;再次启动kafka时&#xff0c;问题并未解决 后来修改了批处理文件kafka-run-class.bat中…

深度剖析知识图谱:方法、工具与实战案例

&#x1f482; 个人网站:【 海拥】【神级代码资源网站】【办公神器】&#x1f91f; 基于Web端打造的&#xff1a;&#x1f449;轻量化工具创作平台&#x1f485; 想寻找共同学习交流的小伙伴&#xff0c;请点击【全栈技术交流群】 知识图谱作为一种强大的知识表示和关联技术&am…

SAP 特殊采购类80简介

前面我们已经测试特殊采购类40、70,我们今天测试一下特殊采购类80。 特殊采购类80:在替代工厂生产,在成品层维护特殊采购类,需求和收货在计划工厂完成,成品生产和组件采购在生产工厂完成。 80采购类也是我们在SAP系统中实现跨工厂需求传递的一种方式。它具有传递方式简单、…

Python tkinter控件全集之组合选择框 ttk.ComboBox

Tkinter标准库 Tkinter是Python的标准GUI库&#xff0c;也是最常用的Python GUI库之一&#xff0c;提供了丰富的组件和功能&#xff0c;包括窗口、按钮、标签、文本框、列表框、滚动条、画布、菜单等&#xff0c;方便开发者进行图形界面的开发。Tkinter库基于Tk for Unix/Wind…

Sui限定版冬季任务来袭,100万份礼物和50万SUI在等你!

在这个渐趋寒冷但节日氛围却日渐浓厚的冬日&#xff0c;Mysten Labs非常高兴地向大家宣布&#xff0c;限定版冬季任务已于北京时间12月19日凌晨3点正式启动啦&#xff01;这次冒险任务充满刺激&#xff0c;奖励丰厚&#xff0c;快来Mysten Labs任务网站体验吧&#xff01; 对于…

查看git的帮助信息

说明 在cmd窗口、或者git Bash shell下执行git --help或者git -h命令&#xff0c;可以查看git的帮助信息。 执行git <command> --help命令可以查看某个命令的帮助信息&#xff0c;其中<command>表示某个具体的命令。 示例1&#xff1a;在git Bash shell下运行git…

Linux文本三剑客grep练习

1、显示/etc/rc.d/rc.sysinit文件中以不区分大小的h开头的行&#xff1b; [rootshell ~]# grep "^[hH]" /etc/rc.d/rc.rc.sysinit [rootshell ~]# grep -i "h" /etc/rc.d/rc.sysinit 2、显示/etc/passwd中以sh结尾的行; [rootshell ~]# grep "sh$&quo…

windows10 固定电脑IP地址操作说明

windows10 固定电脑IP地址操作说明 一、无线网络的IP地址设置方法二、有线网络的IP地址设置方法 本文主要介绍&#xff0c;windows10操作系统下&#xff0c;不同的网络类型&#xff0c;对应的电脑IP地址设置方法。 一、无线网络的IP地址设置方法 在桌面右下角&#xff0c;点击…

A01、深入了解性能优化

1、常用性能评价/测试指标 1.1、响应时间 提交请求和返回该请求的响应时间之间使用的时间&#xff0c;一般比较关注平均响应时间。常用操作的响应时间列表&#xff1a; 操作响应时间打开一个站点几秒数据库查询一条记录&#xff08;有索引&#xff09;十几毫秒机械磁盘一次寻…

2023年超声波清洗机实测!清洁力强的超声波清洗机到底如何选择?

超声波清洗机对眼镜党来说是再熟悉不过的一款清洁设备&#xff0c;每次眼镜只要脏了就会第一时间想到拿去眼镜店去清洗&#xff0c;眼镜店老板数分钟就给自己解决了&#xff0c;不需要自己动手去清洗。随着科技的发展&#xff0c;超声波清洗机已经开始慢慢衍生到家里也能看到它…