SpringSecurity 快速入门
文章目录
- 1. 认证授权概述
- 1.1 认证授权概念
- 1.1.1 认证
- 1.1.2 授权
- 1.2 权限数据模型
- 1.3 RBAC权限模型
- 1.3.1 介绍
- 1.3.2 基于角色访问控制
- 1.3.3 基于资源访问控制
- 1.4 常见认证方式
- 1.4.1 Cookie-Session
- 1.4.2 jwt令牌无状态认证
- 1.5 技术实现
- 2. SpringSecurity入门
- 2.1 介绍
- 2.2 入门
- 2.2.1 工程搭建
- 2.2.2 认证配置
- 【1】自定义合法登录用户信息
- 2.2.3 授权配置
- 2.3 密码加密
- 2.3.1 可逆加密算法
- 2.3.2 不可逆加密算法
- 2.3.3 MD5与Bcrypt
- 2.4 程序完善
- 2.4.1 密码加密处理
- 2.4.2 动态查询用户
- 2.5 认证原理分析(==了解==)
1. 认证授权概述
1.1 认证授权概念
1.1.1 认证
在互联网中,我们每天都会使用到各种各样的APP和网站,在使用过程中通常还会遇到需要注册登录的情况,输入你的用户名和密码才能正常使用,也就是说成为这个应用的合法身份才可以访问应用的资源,这个过程就是认证。认证是为了保护系统的隐私数据与资源,用户的身份合法方可访问该系统的资源。
当然认证的方式有很多,常见的账号密码登录,手机验证码登录,指纹登录,刷脸登录等等。
简单说: 认证就是让系统知道我们是谁。
1.1.2 授权
认证是为了保护身份的合法性,授权则是为了更细粒度的对数据进行划分,授权是在认证通过的前提下发生的。控制不同的用户能够访问不同的资源。
授权是用户认证通过后根据用户的权限来控制用户访问资源的过程,拥有资源的访问权限则正常访问,没有权限则拒绝访问。
例如视频网站的VIP用户,可以查看到普通用户看不到的资源信息。
1.2 权限数据模型
授权过程中,我们需要知道如何对用户访问的资源进行控制,需要了解一些简单的授权数据模型。
授权可以非常简单的理解成谁(Who)对什么(What)进行怎么样(How)的操作。
名词 | 含义 | 备注 |
---|---|---|
Who | 主体(Subject) | 一般指用户,也可以是应用程序 |
What | 资源(Resource) | 例如商品信息,订单信息,页面按钮或程序中的接口等信息 |
How | 权限(Permission) | 规定了用户或程序对资源操作的许可。例如普通用户只能查看订单,管理员可修改或删除订单,这是因为普通用户和管理员用户对订单资源的操作权限不一样。 |
1). 主体、资源、权限的关系图:
主体、资源、权限相关的数据模型如下:
A. 主体(用户id、账号、密码、…)
B. 资源(资源id、资源名称、访问地址、…)
C. 权限(权限id、权限标识、权限名称、资源id、…)
D. 主体(用户)和权限关系(用户id、权限id、…)
2). 主体、资源、权限的表结构关系:
你会发现权限中包含了一个资源ID,多个权限可指向一个资源,我们是否可以直接在权限信息中把资源信息包含进来呢?当然,这也是很多企业开发中的做法,将权限和资源合并为 权限(权限ID、权限标识、权限名称、资源名称、资源访问地址、…)
1.3 RBAC权限模型
1.3.1 介绍
如何实现授权?业界通常基于RBAC模型(Role-Based Access Control -> 基于角色的访问控制)实现授权。 RBAC认为授权实际就是who,what,how三者之间的关系(3W),即who对 what 进行 how 的操作。
1.3.2 基于角色访问控制
RBAC基于角色的访问控制(Role-Based Access Control)是按角色进行授权,比如:主体的角色为总经理可以查询企业运营报表,查询员工工资信息等,访问控制流程如下:
根据上图中的判断逻辑,授权代码可表示如下:
if(主体.hasRole("总经理角色标识")){//查询工资
}else{//权限不足
}
如果上图中查询工资所需要的角色变化为总经理和部门经理,此时就需要修改判断逻辑为“判断用户的角色是否是 总经理或部门经理”,修改代码如下:
if(主体.hasRole("总经理角色标识") || 主体.hasRole("部门经理角色标识")){ //查询工资
}else{//权限不足
}
根据上边的例子发现,当需要修改角色的权限时就需要修改授权的相关代码,系统可扩展性差。
1.3.3 基于资源访问控制
RBAC基于资源的访问控制(Resource-Based Access Control)是按资源(或权限)进行授权。
同样是上面的需求,这时候我们的代码变成了
if(Subject.hasPermission("查询员工工资的权限标识")){// 查询员工工资
}
优点:系统设计时定义好查询工资的权限标识,即使查询工资所需要的角色变化为总经理和部门经理也不需要修授权代码,系统可扩展性强。
1.4 常见认证方式
认证(登录)几乎是任何一个系统的标配,web 系统、APP、PC客户端等都需要注册、登录、授权。
1.4.1 Cookie-Session
早期互联网以 web 为主,客户端是浏览器,所以 Cookie-Session 方式最那时候最常用的方式,直到现在,一些 web 网站依然用这种方式做认证。
认证过程大致如下:
A. 用户输入用户名、密码或者用短信验证码方式登录系统;
B. 服务端验证后,创建一个 Session 记录用户登录信息 ,并且将 SessionID 存到 cookie,响应回浏览器;
C. 下次客户端再发起请求,自动带上 cookie 信息,服务端通过 cookie 获取 Session 信息进行校验;
弊端
- 只能在 web 场景下使用,如果是 APP 中,不能使用 cookie 的情况下就不能用了;
- 即使能在 web 场景下使用,也要考虑跨域问题,因为 cookie 不能跨域;(域名或者ip一致,端口号一致,协议要一致)
- cookie 存在 CSRF(跨站请求伪造)的风险;
- 如果是分布式服务,需要考虑 Session 同步(同步)问题;
1.4.2 jwt令牌无状态认证
JSON Web Token(JWT-字符串)是一个非常轻巧的规范。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息。
认证过程:
A. 依然是用户登录系统;
B. 服务端验证,并通过指定的算法生成令牌返回给客户端;
C. 客户端拿到返回的 Token,存储到 local storage/Cookie中;
D. 下次客户端再次发起请求,将 Token 附加到 header 中;
E. 服务端获取 header 中的 Token ,通过相同的算法对 Token 进行验证,如果验证结果相同,则说明这个请求是正常的,没有被篡改。这个过程可以完全不涉及到查询 Redis 或其他存储;
优点
A. 使用 json 作为数据传输,有广泛的通用型,并且体积小,便于传输;
B. 不需要在服务器端保存相关信息,节省内存资源的开销;
C. jwt 载荷部分可以存储业务相关的信息(非敏感的),例如用户信息、角色等;
1.5 技术实现
技术 | 概述 |
---|---|
Apache Shiro | Apache旗下的一款安全框架 |
SpringSecurity | Spring家族的一部分, Spring体系中提供的安全框架, 包含认证、授权两个大的部分 |
CAS | CAS是一个单点登录(SSO)服务,开始是由耶鲁大学的一个组织开发,后来归到apereo去管 |
自行实现 | 自行通过业务代码实现, 实现繁琐, 代码量大 |
2. SpringSecurity入门
2.1 介绍
Spring Security是为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架(包含: 认证 , 授权两个方面)。它提供了完整的安全性解决方案,可以在Web请求级别和方法调用级别处理身份认证和授权充分利用了Spring IOC,DI(控制反转Inversion of Control ,DI:Dependency Injection 依赖注入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能。
官网地址
2.2 入门
2.2.1 工程搭建
创建测试工程并引入依赖 ;
A. pom.xml
<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.3.4.RELEASE</version><relativePath/>
</parent><dependencies><!-- web起步依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- springBoot整合Security --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><!-- lombok --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency></dependencies>
B. 引导类
@SpringBootApplication
public class StudySecurityApplication{public static void main(String[] args) {SpringApplication.run(StudySecurityApplication.class,args);}
}
C. Controller
@RestController
@RequestMapping("/hello")
public class HelloController {@GetMapping("/{msg}")public String Hello(@PathVariable("msg") String msg){return "hello:" + msg;}@GetMapping("/hello")public String hello(){return "hello security";}@GetMapping("/say")public String say(){return "say security";}}
D. 测试
访问: http://localhost:8080/hello/snow
会自动拦截,并跳转到登录页面(SpringSecurity提供),登录之后才可以访问; 而登录的用户名和密码都是SpringSecurity中内置的默认的用户名密码, 用户名为 user , 密码为控制台输出的一段随机数 (如下)
效果:
登录成功之后,会自动跳转到之前访问的地址:
注意:
# 我们也可在配置文件中配置用户名和密码,实际开发中密码不应明文配置
spring:security:user:name: adminpassword: 123456
再次重启,我们就用配置的用户和密码登录了
2.2.2 认证配置
【1】自定义合法登录用户信息
上述的入门程序中, 用户名密码是框架默认帮我们生成的, 我们并没有指定, 如果我们想指定系统的访问用户名及密码, 可以通过配置的形式声明 , 声明一个 UserDetailsService 类型的Bean。
@Configuration
// 开启web安全设置生效
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {/*** 构建认证服务,并将对象注入spring IOC容器,用户登录时,会调用该服务进行用户合法信息认证* @return*/@Beanpublic UserDetailsService userDetailsService(){// 定义加载用户信息的服务InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();// 构建用户UserDetails u1 = User.withUsername("admin").password("{noop}123456").authorities("P10", "ROLE_ADMIN").build();UserDetails u2 = User.withUsername("snow").password("{noop}123456").authorities("O10", "ROLE_USER").build();inMemoryUserDetailsManager.createUser(u1);inMemoryUserDetailsManager.createUser(u2);return inMemoryUserDetailsManager;}
}
在 userDetailsService()
方法中 ,我们返回了一个UserDetailsService
给 spring 容器,Spring Security 会使用它来获取用户信息。我们暂时使用 InMemoryUserDetailsManager
实现类,并在其中分别创建了 admin、snow 两个用户,并设置密码和权限。
2.2.3 授权配置
1). 编码方式
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Beanpublic UserDetailsService userDetailsService(){InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();inMemoryUserDetailsManager.createUser(User.withUsername("admin").password("{noop}123456").authorities("P10","ROLE_ADMIN").build());inMemoryUserDetailsManager.createUser(User.withUsername("snow").password("{noop}123456").authorities("O10","ROLE_USER").build());return inMemoryUserDetailsManager;}@Overrideprotected void configure(HttpSecurity http) throws Exception {http.formLogin().and().logout().and().csrf().disable().authorizeRequests().antMatchers("/register").permitAll() //不登录即可访问.antMatchers("/hello").hasAuthority("P1") //具有P1权限才可以访问.antMatchers("/say").hasRole("USER") //具有SELLER 角色才可以访问.anyRequest().authenticated(); //其他的登录之后就可以访问}
}
CSRF(Cross-site request forgery)跨站请求伪造,也被称为"One Click Attack"或者 Session Riding,通常缩写为 CSRF 或者 XSRF,是一种对网站的恶意利用。
再次重启项目 再访问就需要带相应的权限了
权限不足 如下:
2). 注解方式
在控制方法 /URL
的权限时, 可以通过配置类中配置的方式进行控制, 也可以使用 注解 @PreAuthorize 来进行控制, 推荐使用注解:
@GetMapping("/hello")@PreAuthorize("hasAuthority('P10')")public String hello(){return "hello security";}@GetMapping("/say")
@PreAuthorize("hasRole('USER')")
public String say(){return "say security";
}
使用@PreAuthorize,需要开启全局方法授权开关,加上注解@EnableGlobalMethodSecurity(prePostEnabled=true)
经过上述的入门程序的演示,我们对于SpringSecurity
的基本使用有了一定的了解,但是在入门程序中存在两个问题
A. 密码采用的是明文的,不安全 ;
B. 用户名/密码直接通过程序硬编码,不够灵活 ;
2.3 密码加密
2.3.1 可逆加密算法
加密后, 密文可以反向解密得到密码原文;
1). 对称加密
指加密和解密使用相同密钥的加密算法。
优点: 对称加密算法的优点是算法公开、计算量小、加密速度快、加密效率高。
缺点: 没有非对称加密安全。
常见的对称加密算法:DES、3DES、DESX、Blowfish、RC4、RC5、RC6和AES
2). 非对称加密
指加密和解密使用不同密钥的加密算法,也称为公私钥加密。假设两个用户要加密交换数据,双方交换公钥,使用时一方用对方的公钥加密,另一方即可用自己的私钥解密。
加密和解密:
- 私钥加密,持有私钥或公钥才可以解密
- 公钥加密,持有私钥才可解密
优点: 非对称加密与对称加密相比,其安全性更好;
缺点: 非对称加密的缺点是加密和解密花费时间长、速度慢,只适合对少量数据进行加密。
2.3.2 不可逆加密算法
一旦加密就不能反向解密得到密码原文 。通常用于密码数据加密。
常见的不可逆加密算法有: MD5 、SHA、HMAC
2.3.3 MD5与Bcrypt
1).MD5
MD5是比较常见的加密算法,广泛的应用于软件开发中的密码加密,通过MD5生成的密文,是无法解密得到明文密码的。但是现在在大数据背景下,很多的网站通过大数据可以将简单的MD5加密的密码破解。
网址: https://www.cmd5.com/
可以在用户注册时,限制用户输入密码的长度及复杂度,从而增加破解难度。
2). Bcrypt
用户表的密码通常使用 MD5 等不可逆算法加密后存储,为防止彩虹表破解,会先使用一个特定的字符串(如域名)加密,然后再使用一个随机的 salt(盐值)加密。 特定字符串是程序代码中固定的,salt 是每个密码单独随机,一般给用户表加一个字段单独存储,比较麻烦。
BCrypt 算法将 salt 随机并混入最终加密后的密码,验证时也无需单独提供之前的salt,从而无需单独处理 salt 问题。
加密密码:
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
for (int i = 0; i < 10; i++) {System.out.println(bCryptPasswordEncoder.encode("123456"));
}
得到结果:
$2a$10$MLJ9f5jViB3rRsQTtxLclumRsK5mdhXsMxe9YBUMyaorTHKwgUEsW
$2a$10$TTAKm8GHEHahRtNDVj8jjeuuJ2AnDaEORHO.3qbeXEpQ8YboPyV3C
$2a$10$wydZqWCBuppICk3YvCw/pOGHVuwtp8qCPCZ1aRjr6904vKXtGnjI6
$2a$10$Cu4ckrKXfTOITh5rl4dLx.h59Gtfn9kdaayVfO/d//4avMQrbTHMe
$2a$10$SKpM1IHOvIUgPTuvTlAtEu9mxANFJUiKc2YNMVQY.Y2.fFVtAdpuG
$2a$10$fKBz3tBR4vt02afJscNa0ulPE.V0/DdNYrv.miuou4mo8vuDIC27u
$2a$10$IludxV8mcTLMnm0wEAOinuvYnJmzCUbvrcwsoSTRw3l9JBXuvSM1q
$2a$10$yAvHC4KobuuPMP10Z9XYh.jXo6y0orDsKSH6xFVe3oZlWiNhDNg/6
$2a$10$WBPYtRG19HkPDtUUJK57r.NjHQ/xocucNfZuinwZQlddFdxEiejoG
$2a$10$YZOzdhz03TMagK4xJRpF7ek51EWNkereo0nu01GRZjfJsKuxf0hEG
验证密码:
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
boolean matches = bCryptPasswordEncoder.matches("123456", "$2a$10$c2sZT/LtM1ExWfZjO0yIPeTGSqMSlX7oi.SvliMbeZpT9Y4qIBDue");
System.out.println(matches);//返回值为true, 则代表验证通过; 反之, 验证不通过
2.4 程序完善
2.4.1 密码加密处理
在配置类 SecurityConfig中配置Bean:
//配置密码加密器 ;
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder(){return new BCryptPasswordEncoder();
}//配置认证信息 , 密码使用BCryptPasswordEncoder加密 ;
@Bean
public UserDetailsService userDetailsService(){InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();inMemoryUserDetailsManager.createUser(User.withUsername("admin").password("$2a$10$qcKkkvsoClF9tO8c9wlR/ebgU8VM39GP5ZUdsts.XSPDmE40l.BP2").authorities("P10","ROLE_ADMIN").build());inMemoryUserDetailsManager.createUser(User.withUsername("snow").password("$2a$10$qcKkkvsoClF9tO8c9wlR/ebgU8VM39GP5ZUdsts.XSPDmE40l.BP2").authorities("O10","ROLE_USER").build());return inMemoryUserDetailsManager;
}
2.4.2 动态查询用户
上述的案例中, 用户名密码都是在代码中写死的, 现在实际项目中, 是需要动态从数据库查询;简易的数据库表如下:
create database security_demo default charset=utf8mb4;
use security_demo;CREATE TABLE `tb_user` (`id` int(11) NOT NULL AUTO_INCREMENT,`username` varchar(100) DEFAULT NULL,`password` varchar(100) DEFAULT NULL,`roles` varchar(100) DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;INSERT INTO `tb_user` VALUES (1, 'admin', '$2a$10$f43iK9zKD9unmgLao1jqI.VluZ.Rr/XijizVEA73HeOu9xswaUBXC', 'ROLE_ADMIN,P10');
INSERT INTO `tb_user` VALUES (2, 'snow', '$2a$10$f43iK9zKD9unmgLao1jqI.VluZ.Rr/XijizVEA73HeOu9xswaUBXC', 'ROLE_USER,O10');
A. pom.xml
<dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.1.4</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency>
B. application.yml
略略略…
C. 实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class TbUser implements Serializable {/*** */private Integer id;/*** */private String username;/*** */private String password;/*** */private String roles;private static final long serialVersionUID = 1L;
}
D. mapper
略略略…
D. 自定义UserDetailsService
package com.sss.security.config;import com.sss.security.mapper.TbUserMapper;
import com.sss.security.pojo.TbUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
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.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;import java.util.List;/*** @Description*/
@Component
public class UserDetailsServiceImpl implements UserDetailsService {@Autowiredprivate TbUserMapper tbUserMapper;@Overridepublic UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {TbUser user = tbUserMapper.findByUserName(userName);if (user==null) {throw new UsernameNotFoundException("用户不存在");}//构建认证明细对象//获取用户权限List<GrantedAuthority> list = AuthorityUtils.commaSeparatedStringToAuthorityList(user.getRoles());User user1 = new User(user.getUsername(),user.getPassword(),list);return user1;}
}
UserDetails是一个接口,User是该接口的实现类,封装用户的数据及用户的权限数据, 注意不要导错包 ;
在SecurityConfig
中注释掉inMemoryUserDetailsManager
bean,并配置加密bean:
@Beanpublic PasswordEncoder passwordEncoder(){return new BCryptPasswordEncoder();}
整体访问流程如下:
2.5 认证原理分析(了解)
Spring Security所解决的问题就是安全访问控制,而安全访问控制功能其实就是对所有进入系统的请求进行拦截,校验每个请求是否能够访问它所期望的资源。根据前边知识的学习,可以通过Filter或AOP等技术来实现,Spring Security对Web资源的保护是靠Filter实现的,所以从这个Filter来入手,逐步深入Spring Security原理。 当初始化Spring Security时,会创建一个名为 SpringSecurityFilterChain的Servlet过滤器,类型为 org.springframework.security.web.FilterChainProxy,它实现了javax.servlet.Filter,因此外部的请求会经过此 类,下图是Spring Security过虑器链结构图:
FilterChainProxy是一个代理,真正起作用的是FilterChainProxy中SecurityFilterChain所包含的各个Filter,同时 这些Filter作为Bean被Spring管理,它们是Spring Security核心,各有各的职责,但他们并不直接处理用户的认 证,也不直接处理用户的授权,而是把它们交给了认证管理器
(AuthenticationManager)和决策管理器 (AccessDecisionManager)进行处理。
下面介绍过滤器链中主要的几个过滤器及其作用:
- SecurityContextPersistenceFilter 这个Filter是整个拦截过程的入口和出口(也就是第一个和最后一个拦截 器),会在请求开始时从配置好的 SecurityContextRepository 中获取SecurityContext,然后把它设置给 SecurityContextHolder。在请求完成后将SecurityContextHolder 持有的 SecurityContext 再保存到配置好 的SecurityContextRepository,同时清除 securityContextHolder 所持有的 SecurityContext;
- UsernamePasswordAuthenticationFilter用于处理来自表单提交的认证。该表单必须提供对应的用户名和密 码,其内部还有登录成功或失败后进行处理的 AuthenticationSuccessHandler 和 AuthenticationFailureHandler,这些都可以根据需求做相关改变;
- FilterSecurityInterceptor 是用于保护web资源的,使用AccessDecisionManager对当前用户进行授权访问,前面已经详细介绍过了;
- ExceptionTranslationFilter 能够捕获来自 FilterChain 所有的异常,并进行处理。但是它只会处理两类异常: AuthenticationException 和 AccessDeniedException,其它的异常它会继续抛出。
接处理用户的认 证,也不直接处理用户的授权,而是把它们交给了认证管理器
(AuthenticationManager)和决策管理器 (AccessDecisionManager)进行处理。
相关文章:

SpringSecurity 快速入门
文章目录 1. 认证授权概述1.1 认证授权概念1.1.1 认证1.1.2 授权 1.2 权限数据模型1.3 RBAC权限模型1.3.1 介绍1.3.2 基于角色访问控制1.3.3 基于资源访问控制 1.4 常见认证方式1.4.1 Cookie-Session1.4.2 jwt令牌无状态认证 1.5 技术实现 2. SpringSecurity入门2.1 介绍2.2 入…...

MySQL--执行一条 select 语句,期间发生了什么?
执行一条 SQL 查询语句,期间发生了什么? 连接器:建立连接,管理连接、校验用户身份;查询缓存:查询语句如果命中查询缓存则直接返回,否则继续往下执行。MySQL 8.0 已删除该模块;解析 …...

DeepL:word文档导出后不能编辑
参考解决用DeepL翻译文档后不能编辑问题_deepl翻译出来的文档怎么编辑-CSDN博客 1、将deepL导出的word文档另存为.xml文件 2、将.xml文件以txt格式打开,查找内容:<w:documentProtection,删除整个标签(包括<w: ...>&…...

PCL 约束Delaunay三角网(版本二)
目录 一、算法概述二、代码实现三、结果展示四、测试数据本文由CSDN点云侠原创,原文链接。如果你不是在点云侠的博客中看到该文章,那么此处便是不要脸的爬虫与GPT。 一、算法概述 PCL 点云Delaunay三角剖分一文给出了PCL中Delaunay三角网算法的基础用法。本文在基础用法的基…...

位运算#蓝桥杯
位运算#蓝桥杯 文章目录 位运算#蓝桥杯1、小蓝学位运算2、异或森林3、位移4、笨笨的机器人5、博弈论 1、小蓝学位运算 #include<bits/stdc.h> using namespace std; using LL long long; const LL N 1e97; template<int kcz> struct ModInt { #define T (*this)…...

Python yield from
yield from是Python生成器(generator)中的一个语法,用于简化生成器的操作。它可以使一个生成器委托部分操作给另一个生成器,从而简化代码。yield from在Python 3.3及更高版本中被引入。 在使用yield from之前,我们需要…...

Atcoder TUPC 2023(東北大学プログラミングコンテスト 2023)E. And DNA(矩阵快速幂+拆位讨论)
题目 长为n(3<n<1e9)的数组,第i个数ai在[0,m](m<1e9)之间 对于i∈[1,n],均满足a[i](a[i-1]&a[i1])m,求这样可能的数组的方案数 特别地,认为a[0]a[n],a[n1]a[1],即这个数组是个环形的数组 思路来源 官…...

Matlab/simulin光伏发电直流串联故障电弧模型仿真
参考文献 模型复现...

4款实用性前端动画特效分享(附在线演示)
分享4款非常不错的项目动画特效 其中有jQuery特效、canvas特效、CSS动画等等 下方效果图可能不是特别的生动 那么你可以点击在线预览进行查看相应的动画特效 同时也是可以下载该资源的 全屏图片视差旋转切换特效 基于anime.js制作全屏响应式的图片元素布局,通过左…...

LeetCode -- 76. 最小覆盖子串
1. 问题描述 给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 “” 。 注意: 对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量…...

【进阶五】Python实现SDVRP(需求拆分)常见求解算法——蚁群算法(ACO)
基于python语言,采用经典遗传算法(ACO)对 需求拆分车辆路径规划问题(SDVRP) 进行求解。 目录 往期优质资源1. 适用场景2. 代码调整3. 求解结果4. 代码片段参考 往期优质资源 经过一年多的创作,目前已经成熟…...

php.exe运行时,提示缺少VCRUNTIME140.dll
php.exe运行时,提示缺少VCRUNTIME140.dll 下载地址 https://www.microsoft.com/zh-cn/download/details.aspx?id48145根据需要选择下载3.运行安装后,再次运行php.exe。...

Android垃圾回收机制
1.垃圾回收机制 垃圾回收,也叫GC(Garbage Collection),指的是释放垃圾占用的空间,防止内存泄露。有效的使用可以使用的内存,对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收。 JVM的内存区域主要分为程序计数器、虚…...

深度学习专家学习计划
深度学习专家学习计划 一、学习背景与目标 作为一名有6年工作经验的Java开发人员,您已具备基本的编程能力和数据处理经验。现计划转岗至深度学习领域,成为深度学习专家。本计划将结合您的工作背景和现有知识,为您制定详细且精确的学习计划,帮助您逐步达到专家水平。 二、…...

关于Ubuntu虚拟机突然上不了网的问题
今天刚重新把Ubuntu虚拟机下回来准备大干一场,结果去吃饭回来虚拟机就上不去网了,具体体现为右上角没有网络的图标,下图是有网络的情况,废话不多说,直接给出解决方案:博客在此 我就是运行了这三行代码就成功…...

[mysql必备面试题]-InnoDB和MyISAM引擎的区别
InnoDB 是 MySQL 默认的事务型存储引擎,只有在需要它不支持的特性时,才考虑使用其它存储引擎。 实现了四个标准的隔离级别,默认级别是可重复读(REPEATABLE READ)。在可重复读隔离级别下,通过多版本并发控制(MVCC) 间隙锁(Next-K…...

android 播放rtsp流的三种方式,2024阿里Android高级面试题总结
使用SurfaceViewMediaPlayer <SurfaceView android:id“id/surface_view” android:layout_width“250dp” android:layout_height“250dp” app:layout_constraintRight_toRightOf“parent” app:layout_constraintTop_toTopOf“parent” /> private String uri …...

unity显示当前时间
1建立文本组件和一个空对象 2创建一个脚本并复制下面代码 using System.Collections; using System.Collections.Generic; using TMPro; using UnityEngine;public class showtime: MonoBehaviour {public TextMeshProUGUI time;private void Update(){string currentTime Sy…...

SDK报错(1)undefined reference to `f_mount‘
利用SDK读取sd卡时,添加了xilffs库,而且包含了ff.h头文件,还是对fat库的函数报错 网上有的说在ARM v7 gcc linker中添加xilffs的方法可以解决,但我试了没有用 最后在赛灵思论坛找到了一个解决方法,原文连接如下 在SDK…...

YOLOv8_pose-Openvino和ONNXRuntime推理【CPU】
纯检测系列: YOLOv5-Openvino和ONNXRuntime推理【CPU】 YOLOv6-Openvino和ONNXRuntime推理【CPU】 YOLOv8-Openvino和ONNXRuntime推理【CPU】 YOLOv7-Openvino和ONNXRuntime推理【CPU】 YOLOv9-Openvino和ONNXRuntime推理【CPU】 跟踪系列: YOLOv5/6/7-O…...

百科 | 光伏电站如何开展运维工作?
从目前太阳能光伏电站的运行管理工作实际经验看,要保证光伏发电系统安全、经济、高效运行,必须建立规范和有效的管理机制,特别是要加强电站的运行维护管理。 一、建立完善的技术文件管理体系 对每个电站都要建立全面完整的技术文件资料档案…...

监听抖音直播间的评论并实现存储
监听抖音直播间评论,主要是动态监听dom元素的变化,如果评论是图片类型的,获取alt的值 主要采用的是MutationObserver:https://developer.mozilla.org/zh-CN/docs/Web/API/MutationObserver index.js如下所示:function getPL() {…...

一体机电脑辐射超标整改
电脑一体机是目前台式机和笔记本电脑之间的一个新型的市场产物,它将主机部分、显示器部分整合到一起的新形态电脑,该产品的创新在于内部元件的高度集成。随着无线技术的发展,电脑一体机的键盘、鼠标与显示器可实现无线链接,机器只…...

重学SpringBoot3-路径匹配机制
更多SpringBoot3内容请关注我的专栏:《SpringBoot3》 期待您的点赞👍收藏⭐评论✍ 重学SpringBoot3-路径匹配机制 AntPathMatcherPathPatternParser 和 PathPattern演示AntPathMatcher 示例PathPattern 示例性能和精确度的提升 选择使用哪一种 在 Spring…...

【贪心算法】摆动序列
如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为 摆动序列 。第一个差(如果存在的话)可能是正数或负数。仅有一个元素或者含两个不等元素的序列也视作摆动序列。 例如, [1, 7, 4, 9, 2, 5] 是一个 摆动序列 &…...

Unload-labs
function checkFile() {var file document.getElementsByName(upload_file)[0].value;if (file null || file "") {alert("请选择要上传的文件!");return false;}//定义允许上传的文件类型var allow_ext ".jpg|.png|.gif";//提取上传文件的类…...

SRS-220VDC-4Z-10A静态中间继电器 额定电压DC220V 四副转换触点 JOSEF约瑟
系列型号: SRS-24VDC-4Z-8A静态中间继电器;SRS-24VDC-4Z-10A静态中间继电器; SRS-24VDC-4Z-16A静态中间继电器;SRS-24VAC-4Z-8A静态中间继电器; SRS-24VAC-4Z-10A静态中间继电器;SRS-24VAC-4Z-16A静态中…...

解决electron打包vue-element-admin项目页面无法跳转的问题
解决electron打包vue-element-admin项目页面无法跳转的问题 说明之前通过这个教程已经打包成功,但是发现进行账号密码登录后页面无法跳转的问题。现在已经解决,所以记录一下。 1、检查路由模式是否为hash模式,如果不是改成hash模式。 new Ro…...

Uniapp Vue2 image src动态绑定static目录下的图片
报错的static地址写法: this.url ../static/img.png this.url /static/img.png 正确的static地址写法: this.url /static/img.png 动态绑定 <image :src"url"></image>...

【UE5】动画混合空间的基本用法
项目资源文末百度网盘自取 什么是动画混合空间 混合空间分为两种: 通过一个数值控制通过两个数值控制 下面通过演示让大家更直观地了解 在Character文件夹中单击右键,选择动画(Animation),选择旧有的混合空间1D 然后选择骨骼(动画是基于骨骼显示的,所以需要选择…...