springboot3+springsecurity+redis 整合登录认证以及权限校验

1. 架构说明

整体架构如下(提供的对应的模块引入),围绕着springsecurity中的三大核心展开:

​ 1、Authentication:存储了认证信息,代表当前登录用户

​ 2、SeucirtyContext:上下文对象,用来获取Authentication

​ 3、SecurityContextHolder:上下文管理对象,用来在程序任何地方获取SecurityContext

重点放在FilterChain的编写上,引入认证的和授权的Filter,并通过调用AuthenticationManager.authenticate和AuthenticationProvider中 写入的加密器、userdetailservice做认证

在这里插入图片描述

2. 依赖引入

由于需要在登录时查询数据库所以需要引入对数据库操作的依赖

<dependencies><!--mybatis和springboot整合--><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId></dependency><!--Mysql数据库驱动8 --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><!--persistence--><dependency><groupId>javax.persistence</groupId><artifactId>persistence-api</artifactId></dependency><!--通用Mapper4--><dependency><groupId>tk.mybatis</groupId><artifactId>mapper</artifactId></dependency><!--SpringBoot集成druid连接池--><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId></dependency><!--lombok--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.28</version><scope>provided</scope></dependency><!--cloud_commons_utils--><dependency><groupId>com.simple.cloud</groupId><artifactId>simpleCloud_api_commons</artifactId><version>1.0-SNAPSHOT</version></dependency><!-- Spring Security依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><scope>provided </scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency></dependencies>

3. application.yml配置文件编写

spring:data:redis: # redis使用host: localhostport: 6379database: 0timeout: 1800000password:jedis:pool:max-active: 20 #最大连接数max-wait: -1    #最大阻塞等待时间(负数表示没限制)max-idle: 5    #最大空闲min-idle: 0     #最小空闲datasource:type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver # 注意修改为用户存在的数据库url: jdbc:mysql://localhost:3306/对应库名称?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=trueusername: rootpassword: abc123# ========================mybatis===================
mybatis:mapper-locations: classpath:mapper/*.xmltype-aliases-package: com.atguigu.cloud.entitiesconfiguration:map-underscore-to-camel-case: true

3. config核心配置文件

注意:security以及摒弃了之前继承 WebSecurityConfigurerAdapter 的方法,鼓励开发者自己写配置类将bean注入容器中,所以之前的springboot2的整合方法已经不好用啦,但是不用慌,大体上的实现还是一样的,毕竟只是换了一个壳子😁

@Configuration
@EnableWebSecurity //@EnableWebSecurity是开启SpringSecurity的默认行为
public class WebSecurityConfig{
}

3.1 加密器引入

自定义一个Md5 加密器

@Component
public class CustomMd5PasswordEncoder implements PasswordEncoder {public String encode(CharSequence rawPassword) {return MD5Helper.encrypt(rawPassword.toString());}public boolean matches(CharSequence rawPassword, String encodedPassword) {return encodedPassword.equals(MD5Helper.encrypt(rawPassword.toString()));}
}

3.1.1 MD5Helper

public final class MD5Helper {public static String encrypt(String strSrc) {try {char hexChars[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8','9', 'a', 'b', 'c', 'd', 'e', 'f' };byte[] bytes = strSrc.getBytes();MessageDigest md = MessageDigest.getInstance("MD5");md.update(bytes);bytes = md.digest();int j = bytes.length;char[] chars = new char[j * 2];int k = 0;for (int i = 0; i < bytes.length; i++) {byte b = bytes[i];chars[k++] = hexChars[b >>> 4 & 0xf];chars[k++] = hexChars[b & 0xf];}return new String(chars);} catch (NoSuchAlgorithmException e) {e.printStackTrace();throw new RuntimeException("MD5加密出错!!+" + e);}}
}

3.1.2 config中注入依赖

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

3.2 UserDetailServer 引入

直接使用Lambda表达式在config中注入

@Bean
public UserDetailsService userDetailsService(){// 调用 JwtUserDetailService实例执行实际校验return email -> {//实际上用email进行匹配 从数据库中获取SysUser authUser = sysUserMapper.getUserByEmail(email);if(null == authUser) {throw new UsernameNotFoundException("邮箱不存在!");}return new CustomUser(authUser, Collections.emptyList());};
}

基于mybatis实现对数据库的查找功能 —

3.2.1 sysUserMapper.java

public interface SysUserMapper extends Mapper<SysUser> {// 根据email获取用户SysUser getUserByEmail(String email);
}

3.2.2 sysUserMapper.xml 实现类

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.simple.cloud.mapper.SysUserMapper"><resultMap id="BaseResultMap" type="com.simple.cloud.entities.SysUser"><id column="id" jdbcType="BIGINT" property="id" /><result column="username" jdbcType="VARCHAR" property="username" /><result column="email" jdbcType="VARCHAR" property="email" /><result column="password" jdbcType="VARCHAR" property="password" /><result column="phone" jdbcType="VARCHAR" property="phone" /><result column="name" jdbcType="VARCHAR" property="name" /><result column="is_deleted" jdbcType="TINYINT" property="isDeleted" /></resultMap><!-- // 根据email获取用户SysUser getUserByEmail(String email);--><select id="getUserByEmail" resultMap="BaseResultMap">SELECT * FROM system_user WHERE email = #{email} AND is_deleted = 0</select>
</mapper>

3.3 config中引入AuthenticationProvider

有了我们的加密器和userdetail后就可以引入AuthenticationProvider

在安全框架中,AuthenticationProvider 是一个接口或类,用于处理身份验证逻辑。它通常用于验证用户的身份信息,例如用户名和密码、令牌或其他认证凭据。

当用户尝试进行身份验证时,系统会将用户的认证请求传递给相应的 AuthenticationProvider。该提供者负责根据配置的规则和算法来验证用户提供的凭据是否有效。如果验证成功,AuthenticationProvider 通常会创建一个表示经过身份验证的主体(如用户)的 Authentication 对象,并返回该对象以供进一步处理。这个 Authentication 对象包含了主体的相关信息,如用户名、角色、权限等。

/*** 调用loadUserByUsername获得UserDetail信息,在AbstractUserDetailsAuthenticationProvider里执行用户状态检查** @return*/
@Bean
public AuthenticationProvider authenticationProvider() {DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();// DaoAuthenticationProvider 从自定义的 userDetailsService.loadUserByUsername 方法获取UserDetailsauthProvider.setUserDetailsService(userDetailsService());// 设置密码编辑器authProvider.setPasswordEncoder(passwordEncoder());return authProvider;
}

3.4 AuthenticationManager

上面一点提到了Authentication ,其中包含了

​ 1、Principal:用户信息,没有认证时一般是用户名,认证后一般是用户对象

​ 2、Credentials:用户凭证,一般是密码(在获取用户权限后会销毁,以保证安全,防止泄露)

​ 3、Authorities:用户权限

而在security中使用AuthenticationManager 来对其执行校验,所以需要引入相应的bean

/*** 登录时需要调用AuthenticationManager.authenticate执行一次校验** @param config* @return* @throws Exception*/
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {return config.getAuthenticationManager();
}

3.5 两个过滤器

在完成上面的配置之后就到了实际处理请求的filter了,我们知道springsecurity就是由一系列的filter构成,具体引入的如下两个

3.5.1 TokenLoginFilter 登录认证

本案例中用email , password做为登录的loginVo(email换成username也是可以的)

@Bean
public TokenLoginFilter authenticationTokenLoginFilter() {//这里通过上下文获取容器中的AuthenticationManagerreturn new TokenLoginFilter(applicationContext.getBean(AuthenticationManager.class),redisTemplate);}
3.5.1.1 TokenLoginFilter 具体实现 继承UsernamePasswordAuthenticationFilter

UsernamePasswordAuthenticationFilter 是 Spring Security 中的一个过滤器,用于处理基于用户名和密码的身份验证请求。它通常与 AuthenticationProvider 一起使用(里面调用的就是刚刚定义的userdetailserivce),以验证用户提供的凭据是否有效。

当用户尝试通过用户名和密码进行身份验证时,系统会将认证请求传递给 UsernamePasswordAuthenticationFilter。该过滤器负责从请求中提取用户名和密码,并将它们封装成一个 Authentication 对象。然后,它会调用配置的 AuthenticationProvider 来验证这个 Authentication 对象。

如果 AuthenticationProvider 验证成功,即用户名和密码匹配,那么 UsernamePasswordAuthenticationFilter 会创建一个表示经过身份验证的主体(如用户)的 Authentication 对象,并将其存储在安全上下文中。这样,后续的请求就可以访问到已认证的用户信息。

public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter {private RedisTemplate redisTemplate;public TokenLoginFilter(AuthenticationManager authenticationManager, RedisTemplate redisTemplate) {this.redisTemplate = redisTemplate;this.setAuthenticationManager(authenticationManager);this.setPostOnly(false);//指定登录接口及提交方式,可以指定任意路径this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/system/loginCon/login","POST"));}/*** 登录认证* @param req* @param res* @return* @throws AuthenticationException*/@Overridepublic Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res)throws AuthenticationException {try {LoginVo loginVo = new ObjectMapper().readValue(req.getInputStream(), LoginVo.class);Authentication authenticationToken = new UsernamePasswordAuthenticationToken(loginVo.getEmail(), loginVo.getPassword());return this.getAuthenticationManager().authenticate(authenticationToken);} catch (IOException e) {throw new RuntimeException(e);}}/*** 登录成功* @param request* @param response* @param chain* @param auth* @throws IOException* @throws ServletException*/@Overrideprotected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,Authentication auth) throws IOException, ServletException {//获取当前用户CustomUser customUser = (CustomUser)auth.getPrincipal();//生成tokenString token = JWTHelper.createToken(customUser.getSysUser().getId(),customUser.getSysUser().getEmail());//获取当前用户权限数据,放到Redis里面 key:id   value:当前用户//设置90s过期 即是登录之后会立即请求用户信息调用info() 这里存入的数据只是为了再info()处做双向认证//上面的info后面会讲到 , 在具体的业务中请求用户信息包括了权限信息redisTemplate.opsForValue().set(customUser.getSysUser().getId()+"——BUSINESS_KEY",JSON.toJSONString(customUser.getSysUser()) , 90 , TimeUnit.SECONDS);//返回token给前端(或者登录请求的调用者)Map<String,Object> map = new HashMap<>();map.put("token",token);ResponseUtil.out(response, ResultData.success(map));}/*** 登录失败* @param request* @param response* @param e* @throws IOException* @throws ServletException*/@Overrideprotected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,AuthenticationException e) throws IOException, ServletException {ResponseUtil.out(response, ResultData.fail(ResultCodeEnum.RC401.getCode(), e.getMessage()));}
}

3.5.2 TokenAuthenticationFilter 权限认证

@Bean
public TokenAuthenticationFilter authenticationJwtTokenFilter() {return new TokenAuthenticationFilter(redisTemplate);
}
3.5.2.1 TokenAuthenticationFilter 具体实现 继承OncePerRequestFilter

OncePerRequestFilter是Spring框架中的一个抽象类,它用于确保在一次请求中只执行一次过滤操作。这个类主要用于实现自定义的过滤器,继承OncePerRequestFilter类并重写doFilterInternal方法来实现具体的过滤逻辑。

public class TokenAuthenticationFilter extends OncePerRequestFilter {private RedisTemplate redisTemplate;public TokenAuthenticationFilter(RedisTemplate redisTemplate) {this.redisTemplate = redisTemplate;}@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)throws IOException, ServletException {logger.info("uri:"+request.getRequestURI());//如果是登录接口,直接放行if("/system/loginCon/login".equals(request.getRequestURI())) {chain.doFilter(request, response);return;}UsernamePasswordAuthenticationToken authentication = getAuthentication(request);if(null != authentication) {// 放到security上下文中,有需要的地方就可以获取到SecurityContextHolder.getContext().setAuthentication(authentication);chain.doFilter(request, response);} else {ResponseUtil.out(response, ResultData.fail(ResultCodeEnum.RC401.getCode(), ResultCodeEnum.RC401.getMessage()));}}private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {//请求头是否有tokenString token = request.getHeader("token");if(!StringUtils.isEmpty(token)) {String email = JWTHelper.getUsername(token);Long userId = JWTHelper.getUserId(token);if(!StringUtils.isEmpty(email)) {//当前用户信息放到ThreadLocal里面LoginUserInfoHelper.setUserId(userId);LoginUserInfoHelper.setEmail(email);//通过 userId 从redis获取权限数据//后续在info中放入 一开始从redis拿到的数据都为nullList<String> authStringList = (List<String>) redisTemplate.opsForValue().get(userId+"_LOGIN_BUSINESS_STORAGE_");//把redis获取字符串权限数据转换要求集合类型 List<SimpleGrantedAuthority>if(authStringList != null) {List<SimpleGrantedAuthority> authList = new ArrayList<>();for (String val : authStringList) {authList.add(new SimpleGrantedAuthority(val));}//由于授权信息必须是GrantedAuthority对象所以得转换一下return new UsernamePasswordAuthenticationToken(email,null, authList);} else {return new UsernamePasswordAuthenticationToken(email,null, new ArrayList<>());}}}return null;}
}

3.6 security过滤规则

有了上面两个过滤器后,我们应该以什么样的规则来使用呢? 这就引出config中最重要的一环 SecurityFilterChain的bean注入

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {http// 禁用basic明文验证.httpBasic().disable()// 前后端分离架构不需要csrf保护.csrf().disable()// 禁用默认登录页.formLogin().disable()// 禁用默认登出页.logout().disable()// 前后端分离是无状态的,不需要session了,直接禁用。.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)).authorizeHttpRequests(authorizeHttpRequests -> authorizeHttpRequests// 允许所有OPTIONS请求.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()// 允许直接访问授权登录接口.requestMatchers(HttpMethod.POST, "/system/loginCon/login").permitAll()// 允许 SpringMVC 的默认错误地址匿名访问.requestMatchers("/error").permitAll()// 允许任意请求被已登录用户访问,不检查Authority.anyRequest().authenticated()).authenticationProvider(authenticationProvider())// 加我们自定义的过滤器,替代UsernamePasswordAuthenticationFilter.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class)// 登录过滤器.addFilter(authenticationTokenLoginFilter());return http.build();
}

这里就偷个懒些不写logout的实现😭

3.8 获取用户权限信息的方法info()

前面的filter中多次提到这个info方法,那具体实现就放在这里啦,主要的目的是获取到权限信息,而前端调用info接口也可以获取到相应的登录用户信息,前端做他的权限控制,后端也做权限控制双重保障
由于前面在登录成功后TokenLoginFiltersuccessfulAuthentication方法向redis中放入了数据(90s过期时间)理想情况下,前端登录请求一成功,返回状态码200,此时会直接调用info方法再次发送请求,请求相应的用户具体信息(菜单按钮权限等用于渲染页面)而后端会把相应的按钮权限放入redis中,后续有请求匹配就会查找是否有相应的权限来决定是否执行

 /*** 获取用户信息* @return*/@PostMapping("/info")public ResultData info(HttpServletRequest request){String token = request.getHeader("token");//获取用户idLong userId = JWTHelper.getUserId(token);//根据id从redis中获取当前用户SysUser sysUser = JSON.parseObject((String) redisTemplate.opsForValue().get(userId + "——BUSINESS_KEY"), SysUser.class);if(sysUser == null)return ResultData.fail(ResultCodeEnum.RC401.getCode(), "当前用户未登录,请登录后再试");//获取用户的角色List<SysRole> sysRoles = sysUserService.selectAllByUserId(userId);sysUser.setRoleList(sysRoles);//根据id获取所有菜单列表List<RouterVo> routerList = sysMenuService.getAllRouterListByUserId(userId);//根据id获取所有按钮列表List<String> permsList = sysMenuService.getAllMenuListByUserId(userId);//map中插入相应的值Map<String, Object> map = new HashMap<>();map.put("routers",routerList);map.put("buttons",permsList);map.put("roles",sysUser.getRoleList());map.put("name",sysUser.getName());// 存放权限信息到redis中 , springsecurity通过 userId 做为key获取权限列表redisTemplate.opsForValue().set(sysUser.getId()+"_LOGIN_BUSINESS_STORAGE_", permsList);return ResultData.success(map);}

4. 最终版config配置类

@Configuration
@EnableWebSecurity //@EnableWebSecurity是开启SpringSecurity的默认行为
public class WebSecurityConfig{@Resourceprivate RedisTemplate redisTemplate;@Resourceprivate ApplicationContext applicationContext;@Resourceprivate SysUserMapper sysUserMapper;@Beanpublic PasswordEncoder passwordEncoder() {return new CustomMd5PasswordEncoder();}@Beanpublic TokenAuthenticationFilter authenticationJwtTokenFilter() {return new TokenAuthenticationFilter(redisTemplate);}@Beanpublic SecurityFilterChain filterChain(HttpSecurity http) throws Exception {http// 禁用basic明文验证.httpBasic().disable()// 前后端分离架构不需要csrf保护.csrf().disable()// 禁用默认登录页.formLogin().disable()// 禁用默认登出页.logout().disable()// 前后端分离是无状态的,不需要session了,直接禁用。.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)).authorizeHttpRequests(authorizeHttpRequests -> authorizeHttpRequests// 允许所有OPTIONS请求.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()// 允许直接访问授权登录接口.requestMatchers(HttpMethod.POST, "/system/loginCon/login").permitAll()// 允许 SpringMVC 的默认错误地址匿名访问.requestMatchers("/error").permitAll()// 允许任意请求被已登录用户访问,不检查Authority.anyRequest().authenticated()).authenticationProvider(authenticationProvider())// 加我们自定义的过滤器,替代UsernamePasswordAuthenticationFilter.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class).addFilter(authenticationTokenLoginFilter());return http.build();}@Beanpublic UserDetailsService userDetailsService(){// 调用 JwtUserDetailService实例执行实际校验return email -> {//实际上用email进行匹配 从数据库中获取SysUser authUser = sysUserMapper.getUserByEmail(email);if(null == authUser) {throw new UsernameNotFoundException("邮箱不存在!");}return new CustomUser(authUser, Collections.emptyList());};}/*** 调用loadUserByUsername获得UserDetail信息,在AbstractUserDetailsAuthenticationProvider里执行用户状态检查** @return*/@Beanpublic AuthenticationProvider authenticationProvider() {DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();// DaoAuthenticationProvider 从自定义的 userDetailsService.loadUserByUsername 方法获取UserDetailsauthProvider.setUserDetailsService(userDetailsService());// 设置密码编辑器authProvider.setPasswordEncoder(passwordEncoder());return authProvider;}/*** 登录时需要调用AuthenticationManager.authenticate执行一次校验** @param config* @return* @throws Exception*/@Beanpublic AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {return config.getAuthenticationManager();}@Beanpublic TokenLoginFilter authenticationTokenLoginFilter() {return new TokenLoginFilter(applicationContext.getBean(AuthenticationManager.class),redisTemplate);}
}

到此结束👍希望对各位能有帮助

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

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

相关文章

扩散模型~

推荐&#xff1a;write_own_pipeline.ipynb - Colab (google.com) 基本管道 一直显示NVIDIA有问题&#xff0c;所以就把.to("cuda")去掉了&#xff0c;使用Colab运行的&#xff0c;代码如下&#xff1a; from diffusers import DDPMPipelineddpm DDPMPipeline.fr…

Java实战:验证改进的哥德巴赫猜想

改进的哥德巴赫猜想&#xff08;Improved Goldbach’s Conjecture&#xff09;声称每个大于5的奇数都可以表示为三个素数之和。这个猜想是对原始哥德巴赫猜想的扩展&#xff0c;针对奇数的情况。原始哥德巴赫猜想是指每个大于2的偶数都可以表示为两个素数之和。尽管改进的哥德巴…

Python轻量级Web框架Flask(13)—— Flask个人博客项目

0、前言: ★这部分内容是基于之前Flask学习内容的一个实战项目梳理内容,没有可以直接抄下来跑的代码,是学习了之前Flask基础知识之后,再来看这部分内容,就会对Flask项目开发流程有更清楚的认知,对一些开发细节可以进一步的学习。项目功能,通过Flask制作个人博客。项目架…

自动化运维工具-Ansible

一、Ansible概述 Ansible是一种基于python开发的自动化运维工具&#xff0c;它只需要在服务端安装ansible&#xff0c;无需在每个客户端安装客户端程序&#xff0c;通过ssh的方式来进行客户端服务器的管理&#xff0c;基于模块来实现批量数据配置、批量设备部署以及批量命令执…

飞天使-k8s知识点31-rancher的正确打开方式

文章目录 安装之前优化一下内核参数以及系统内核版本 rancher安装主要是使用以下命令nginx的配置为解决办法 安装之前优化一下内核参数以及系统内核版本 内核版本 4.17 cat > /etc/modules-load.d/iptables.conf <<EOF ip_tables iptable_filter EOF 然后重启服务器…

Python运维之多进程!!

本节的快速导航目录如下喔&#xff01;&#xff01;&#xff01; 一、创建进程的类Process 二、进程并发控制之Semaphore 三、进程同步之Lock 四、进程同步之Event 五、进程优先队列Queue 六、多进程之进程池Pool 七、多进程之数据交换Pipe 一、创建进程的类Process mu…

【 书生·浦语大模型实战营】学习笔记(六):Lagent AgentLego 智能体应用搭建

&#x1f389;AI学习星球推荐&#xff1a; GoAI的学习社区 知识星球是一个致力于提供《机器学习 | 深度学习 | CV | NLP | 大模型 | 多模态 | AIGC 》各个最新AI方向综述、论文等成体系的学习资料&#xff0c;配有全面而有深度的专栏内容&#xff0c;包括不限于 前沿论文解读、…

暗区突围国际服pc端海外版如何快速致富 暗区突围pc端怎么赚钱

暗区突围是一款由腾讯魔方工作室研发的高拟真硬核射击手游&#xff0c;以现代战争为游戏题材&#xff0c;采用了全新的u3d引擎打造&#xff0c;整体游戏画风逼真写实&#xff0c;搭配上优秀的射击玩法&#xff0c;辅以史诗级的背景配乐&#xff0c;致力于带给玩家无与伦比的枪战…

【因特网中自治系统内部的路由选择,RIP 进程处理 OSPFOSPF(Open Shortest Path First)最短路径优先协议】

文章目录 因特网中自治系统内部的路由选择RIP&#xff08;Routing Information Protocol&#xff09;内部网关协议RIP通告&#xff08;advertisements&#xff09;RIP: 链路失效和恢复RIP 进程处理OSPF(Open Shortest Path First)最短路径优先协议OSPF “高级” 特性(在RIP中的…

SEO之高级搜索指令(三)

初创企业需要建站的朋友看这篇文章&#xff0c;谢谢支持&#xff1a; 我给不会敲代码又想搭建网站的人建议 新手上云 &#xff08;接上一篇。。。。&#xff09; 11、link: link:也是SEO 常用的指令&#xff0c;用来搜索某个url的反向链接&#xff0c;既包括内部链接&#xf…

【C++11新特性】lambda表达式和应用场景

创作不易&#xff0c;本篇文章如果帮助到了你&#xff0c;还请点赞 关注支持一下♡>&#x16966;<)!! 主页专栏有更多知识&#xff0c;如有疑问欢迎大家指正讨论&#xff0c;共同进步&#xff01; &#x1f525;c系列专栏&#xff1a;C/C零基础到精通 &#x1f525; 给大…

力扣每日一题112:路径总和

题目 简单 给你二叉树的根节点 root 和一个表示目标和的整数 targetSum 。判断该树中是否存在 根节点到叶子节点 的路径&#xff0c;这条路径上所有节点值相加等于目标和 targetSum 。如果存在&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 叶子节点 是…

java入门1.1.1版本

前言&#xff1a; 上面的内容是1.0.0~1.1的内容总结 秉持着先做再定义的理念&#xff0c;这里会带着大家先体验一下类与对象 第一步&#xff1a;新建一个java文件 鼠标右键 → 新建 → 文本文档 → 右键 → 点击重名 → 全选 → hello.java 第二步&#xff1a;用笔记本打开 …

C++进阶:map与set简单自实现

目录 1. map与set封装红黑树的方式1.1 大概实现思路1.2 红黑树模板抽象1.3 红黑树的迭代器 2. 红黑树模板的实现2.1 结点结构的定义2.2 红黑树迭代器的实现2.2.1 迭代器的结构2.2.2 迭代器的方法实现 2.3 树结构的定义2.4 红黑树接口实现2.4.1 插入2.4.2 查找2.4.3 迭代器相关 …

pytest + yaml 框架 - 参数化读取文件路径优化

针对小伙伴提出参数化时读取外部文件&#xff0c;在项目根路径运行没问题&#xff0c;但是进入到项目下子文件夹运行用例&#xff0c;就会找不到文件问题做了优化。 关于参数化读取外部文件相关内容参考前面这篇pytest yaml 框架 -25.参数化数据支持读取外部文件txt/csv/json/…

随手笔记-GNN(朴素图神经网络)

自己看代码随手写的一点备忘录&#xff0c;自己看的&#xff0c;不喜勿喷 GNN (《------ 代码) 刚开始我还在怀疑为什么没有加weigth bias&#xff0c;已经为什么权重才两个&#xff0c;原来是对node_feats进行的network的传播&#xff0c;而且自己内部直接进行了。 下面是一…

【api接口开通教程】YouTube Data API v3申请流程

一、背景调查 1.1 API接口介绍 采集youtube数据&#xff0c;大体分为两种方案&#xff1a;一种是基于爬虫&#xff0c;一种是基于API接口。 说人话就是&#xff1a;爬虫相当于走后门、爬窗户&#xff08;利用技术手段窃取&#xff0c;人家没说给&#xff0c;但我硬拿&#x…

ssrf漏洞学习——基础知识

一、SSRF是什么&#xff1f; SSRF(Server-Side Request Forgery:服务器端请求伪造) 是一种由攻击者构造形成由服务端发起请求的一个安全漏洞。 一般情况下&#xff0c;SSRF攻击的目标是从外网无法访问的内部系统。&#xff08;正是因为它是由服务端发起的&#xff0c;所以它能…

leetcode每日一题第七十二天

class Solution { public:TreeNode* searchBST(TreeNode* root, int val) {if(!root) return root;if(root->val val) return root;else if(root->val > val) return searchBST(root->left,val);else return searchBST(root->right,val);} };

日志打印传值 传引用 右值引用性能测试(Linux/QNX)

结论 Linux平台和qnx平台优化后传值性能都是比传引用的差&#xff0c;也比传右值的差&#xff0c;因此传参时有必要传递引用。 测试代码 #include <cstdint> #include <ctime> #include <string>#ifdef __linux__#define ITERATIONS 10000000 #else#defin…