SpringSecurity(十七)---OAuth2的运行机制(下)-实现一个简单的单点登录应用程序

news2024/11/15 8:17:49

一、前言

本章实现第一个使用带有Spring Boot和Spring Security 的OAuth2框架的应用程序。这个示例将展示如何将OAuth2应用到Spring Security中,并阐释你需要了解的一些接口的内容。顾名思义,单点登录(SSO)应用程序是通过授权服务器进行身份验证的应用程序,然后将使用刷新令牌让用户保持登陆状态。在我们的示例中,它只代表来自OAuth2架构的客户端。
在这个应用程序中,我们要使用Gitee作为授权和资源服务器,并重点关注使用授权码授权类型的组件之间的通信.在后面的学习中我们将在OAuth2架构中实现一个授权服务器和一个资源服务器。
在这里插入图片描述

二、项目前准备

大家如果是从之前跟过来的,我这里推荐大家重新构建一个OAuth2项目取名spring_security_oauth2_sso,然后将之前写的短信认证的那个项目,除了security包的内容不copy,其他都copy下来就好,然后对报错的地方做一下相关适配(例如passwordEncoder,这个自己新建一个security包,将以前的security对应内容放进去就好),这里就不重新展现了。

三、管理授权服务器

本节将配置授权服务器。本章不会实现我们自己的授权服务器,而是使用一个现有的:Gitee。
如何使用Gitee这样的第三方作为授权服务器呢?这意味着,最终,我们的应用程序不会管理它的用户,任何人都可以使用他们的Gitee账户登录到我们的应用程序。与其他授权服务器一样,Gitee需要知道它要向哪个客户端应用程序发出令牌。因此,OAuth应用程序必须向Gitee授权服务器进行注册。为此,需要使用以下链接完成一个简短的表单。

https://gitee.com/oauth/applications/new

在这里插入图片描述
上图的“应用回调地址”就是我们注册的应用程序client(客户端)的接收授权码和access-token的地址其中,/login/oauth2/code必须是这样,这是由Client引入的oauth2AuthenticationFilter的内部默认路径。而后面的gitee是registrationid,与我们在自己的Client代码中配置的一致,Client的oauth2AuthenticationFilter要靠这个值去对应配置在程序中的clientid和clientSecret然后发给gitee认证服务器。且将来gitee授权服务器收到授权请求后,会将配置的这个应用回调地址与请求参数中的redirect_uri匹配,正确才回传授权码以及access_token。否则会报无效的回调地址(别问我为什么知道,问就是鸽那么多天都在尝试解决这个)
在这里插入图片描述
创建后会给我产生一个clientId和clientSecret,这个就是Gitee为我们提供的客户端ID和客户端密钥信息。当然我这里打上了马赛克,大家一定要自己去生成自己的凭据,另外在使用这样的凭据编写应用程序时要小心,特别是在使用Gitee存储库存储它们的时候
在这里插入图片描述
这个配置就是需要为授权服务器做的所有处理。现在我们有了客户端凭据,可以开始处理应用程序了。

四、开始实现

4.1、准备工作

本节将开始实现一个SSO应用程序。我们首先需要将一下依赖添加到Pom文件中:
以前的项目中已加入spring-boot-starter-security和spring-boot-starter-web依赖,如果已经加了可以不用加后面两个。

<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-security</artifactId>
</dependency>

首先需要确保某些东西的安全:一个网页。为此,要创建一个控制器类和一个表示应用程序的简单的HTML页面。如下代码展示了HomeController类,它定义了应用程序的单个端点:

@Controller
@Slf4j
public class HomeController {
	@GetMapping("/")
	public String home(OAuth2AuthenticationToken token)
	{
		log.info(String.valueOf(token.getPrincipal()));
		return "home";
	}
}

这里的OAuth2AuthenticationToken你可以类比为UsernamePasswordAuthenticationToken,同样实现Authentication接口表示身份验证请求事件,并且会保存请求访问应用程序的实体的详细信息。大家在写短信认证登录的时候,应该知道Authentication是分请求前和请求后,不知道大家是否还记得Principal这个对象,在认证前存放的是认证所需的信息,例如手机号,认证后存放的是认证后的用户详细信息,这里也是一样的,如果SSO认证通过后,我们也可以拿到资源服务器(这里仍然是gitee)给我们的用户信息,通过token.getPrincipal()获取。
而home的页面很简单

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>Welcome</h1>
</body>
</html>

现在才要开始真正的工作!接下来设置安全配置,以允许应用程序使用GItee登录。首先要编写一个配置类,就像我们过往所做的那样,这里扩展了WebSecurityConfigurerAdapter并重写了configure(HttpSecurity http)方法。现在有了一个不同之处:此处调用了另一个名为**oauth2Login()**方法,而不是之前介绍的httpBasic()或formLogin()。代码如下:

package com.mbw.security.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.DelegatingPasswordEncoder;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.crypto.scrypt.SCryptPasswordEncoder;

import java.util.HashMap;

@Configuration
public class ProjectConfig extends WebSecurityConfigurerAdapter {

	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http.oauth2Login();
		http.authorizeRequests()
				.anyRequest().authenticated();
	}
	
	@Bean
	public PasswordEncoder passwordEncoder() {
		HashMap<String, PasswordEncoder> encoders = new HashMap<>();
		encoders.put("noop", NoOpPasswordEncoder.getInstance());
		encoders.put("bcrypt", new BCryptPasswordEncoder());
		encoders.put("scrypt", new SCryptPasswordEncoder());
		return new DelegatingPasswordEncoder("bcrypt", encoders);
	}
}

这里出现了一个新方法:oauth2Login(),但经过了之前的学习的你应该需要能反应过来其中进行了什么处理。与httpBasic()和formLogin()一样,oauth2Login()只是将一个新的身份验证过滤器添加到过滤器链中。我们之前说过SpringSecurity有一些过滤器实现,并且还可以向过滤器链添加自定义的过滤器。在本示例中,当调用oauthLogin()方法时,框架添加到过滤器链中的过滤器就是之前提到过的OAuth2LoginAuthenticationFilter.这个过滤器会拦截请求,并应用OAuth2身份验证所需的逻辑。
在这里插入图片描述
ps:securityFilterChain中不再有usernamePasswordAuthenticationFilter和basicAuthenticationFitler,因为你配置的本client的http.oauth2Login().那么本client的“认证凭证”就由oauth2AuthenticationFilter来提供oauth2AuthenticationToken了。

4.2、实现ClientRegistration

本节将讨论如何实现OAuth2客户端和授权服务器之间的连接。如果想让应用程序真正做一些事情,这是至关重要的。如果现在就启动该应用程序,那么将无法访问主页。无法访问该页面的原因是由于指定了对于任何请求,用户都需要进行身份验证,但是这里还没有提供任何身份验证方法。我们需要将gitee确立为授权服务器。为此Spring Security定义了ClientRegistration契约。
ClientRegistration接口表示OAuth2架构中的客户端。对于该客户端,需要定义其所需的所有详情,其中包括:

  • 客户端ID和密钥
  • 用于身份验证的授权类型
  • 重定向URI
  • 作用域
    你可能还记得在之前讲解授权码授权类型时,应用程序需要将所有这些详细信息用于身份验证过程中,Spring Security还提供了一种创建构建器实例的简单方法,类似于一开始构造UserDetails方法是一样的。下面代码展示了如何构建这样一个表示客户端实现的实例:
GiteeClient.java
package com.mbw.security.client.gitee;

import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.stereotype.Component;

@Component
public class GiteeClient {

	public ClientRegistration clientRegistration(){
		return ClientRegistration.withRegistrationId("gitee")  //起个名字,代表client,如clientId和clientSecret
				.clientId("your clientId")  //此处要换成你在gitee上创建应用得到的
				.clientSecret("your clientSecret") //此处要换成你在gitee上创建应用得到的
				.scope(new String[]{"user_info"})    //读取用户权限,参见你gitee上创建应用时的授权勾选
				.authorizationUri("https://gitee.com/oauth/authorize")   //这要看gitee的api,是user认证以及client认证获取授权码的地址
				.tokenUri("https://gitee.com/oauth/token") //这要看gitee的api,是client得到授权码后去换token的gitee地址
				.userInfoUri("https://gitee.com/api/v5/user") //资源服务器api地址-也是client用access-token去获取用户user详情的“用户详情资源服务器地址”-这里也是gitee】】
				.userNameAttributeName("id")
				.clientName("gitee")  //为我们的应用client起了个名字
				.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)  //注是授权码模式
				.redirectUriTemplate("{baseUrl}/{action}/oauth2/code/{registrationId}")  //本应用配置的gitee发回授权码的地址
				.build();
	}
}

第一眼看上去要设置的东西有点多,但它只不过是设置客户端ID和密钥而已。此外,还定义了作用域(授予的权限)、客户端名称和所选择的注册ID。除了这些信息,还必须提供授权服务器的URL。

  • 授权URI:客户端将用户重定向到其进行身份验证的URI。
  • 令牌URI客户端为获取访问令牌和刷新令牌而调用的URI。
  • 用户信息URI客户端在获得访问令牌后可以调用的URI,以获得关于用户的更多详细信息

这些URI是从哪里获得的?如果授权服务器不是由我们开发的,则需要从说明文档中获取它们。以Gitee为例,可以在这里找到它们:

https://gitee.com/api/v5/oauth_doc#/

当然如果你的授权服务器提供者不是gitee,而是Github,Google,FaceBook,Okta这四个中的任意一个,那么Spring security给我们提供了CommonOAuth2Provider的类,这个类部分定义了可以用于身份验证的最常见提供程序的ClientRegistration实例,拿Github为例,你可以这样如下配置:

	public ClientRegistration githubClient(){
		return CommonOAuth2Provider.GITHUB
				.getBuilder("github")
				.clientId("your clientId")
				.clientSecret("your clientSecret")
				.build();
	}

如上所示,这样更为清晰,并且我们不必手动查找和设置授权服务器的URL。当然,这只适用于公共提供程序。如果授权服务器不在公共提供程序之列,则只能完全定义ClientRegistration。
 然后我们之前定义的GiteeClient已经交由Spring容器进行管理,我们可以在配置类中注入它。但是这样身份验证过滤器仍不能直接获取关于授权服务器客户端注册的详细信息,我们需要实现clientRegistrationRepository!

4.3、实现ClientRegistrationRepository

我们之前讲到配置了ClientRegistration还不够,需要对其进行设置,以便将其用于身份验证。为此,Spirng Security使用了类型为ClientRegistrationRepository的对象
在这里插入图片描述
ClientRegistrationRepository会检索ClientRegistration详细信息(客户端ID、客户端密钥、URL、作用域等)。身份验证过滤器需要将这些详细信息用于身份验证流程。
ClientRegistrationRepository接口类似于前面介绍过的UserDetailsService接口。与UserDetailsService对象通过其用户名查找UserDetails相同,ClientRegistrationRepository对象通过其注册ID查找ClientRegistration
可以实现ClientRegistrationRepository接口来告知框架在哪里找到ClientRegistration实例。Spring Security为ClientRegistrationRepository提供了一个实现,该实现会将ClientRegistration的实例存储在内存中,也就是InMemoryClientRegistrationRepository。是不是很熟悉,这与InMemoryUserDetailsManager对UserDetails实例所做的处理类似。
而且可以实现ClientRegistrationRepository也说明我们可以像之前通过mysql管理userDetails一样去管理ClientRegistration,不知道大家还记不记得之前写过的mybatisUserDetailsService.但是这里就暂时不作展示了,大家有兴趣可以自行尝试。
为了完成该应用程序实现,这里使用InMemoryClientRegistrationRepository实现定义了一个ClientRegistrationRepository,并将构建的ClientRegistration实例添加到InMemoryClientRegistrationRepository中,这是通过将其作为InMemoryClientRegistrationRepository构造函数的参数来完成的。然后将构造 ClientRegistrationRepository的方法通过oauth2Login()的customizaer设置,代码如下:

package com.mbw.security.config;

import com.mbw.security.client.gitee.GiteeClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.DelegatingPasswordEncoder;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.crypto.scrypt.SCryptPasswordEncoder;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;

import java.util.HashMap;

@Configuration
public class ProjectConfig extends WebSecurityConfigurerAdapter {
	@Autowired
	private GiteeClient giteeClient;

	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http.oauth2Login(c->c.clientRegistrationRepository(clientRegistrationRepository()));
		http.authorizeRequests()
				.anyRequest().authenticated();
	}

	private ClientRegistrationRepository clientRegistrationRepository(){
		return new InMemoryClientRegistrationRepository(giteeClient.clientRegistration());
	}

	@Bean
	public PasswordEncoder passwordEncoder() {
		HashMap<String, PasswordEncoder> encoders = new HashMap<>();
		encoders.put("noop", NoOpPasswordEncoder.getInstance());
		encoders.put("bcrypt", new BCryptPasswordEncoder());
		encoders.put("scrypt", new SCryptPasswordEncoder());
		return new DelegatingPasswordEncoder("bcrypt", encoders);
	}
}

4.4、Spring boot配置的纯粹方式

Springboot旨在使用其纯粹的配置方式直接从属性文件构建ClientRegistration和ClientRegistrationRepository对象。这种方法在Spring Boot项目并不少见。对于其他对象也是如此,例如数据源配置,下面代码展示了如何在yaml文件中为此处的示例设置客户端注册:

spring:
  security:
    oauth2:
      client:
        registration:
          gitee:
            client-id: your clientId
            client-secret: 4your clientSecret
            authorization-grant-type: authorization_code
            redirect-uri: '{baseUrl}/{action}/oauth2/code/{registrationId}'
            client-name: gitee
            provider: gitee
            scope:
              - user_info
        provider:
          gitee:
            authorization-uri: https://gitee.com/oauth/authorize
            token-uri: https://gitee.com/oauth/token
            user-info-uri: https://gitee.com/api/v5/user
            user-name-attribute: id

这样你的配置类就可以不再需要指定ClientRegistration和ClientRegistrationRepository的任何详情,因为它们是由Spring Boot根据属性文件自动创建的:

package com.mbw.security.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.DelegatingPasswordEncoder;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.crypto.scrypt.SCryptPasswordEncoder;

import java.util.HashMap;

@Configuration
public class ProjectConfig extends WebSecurityConfigurerAdapter {

	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http.oauth2Login();
		http.authorizeRequests()
				.anyRequest().authenticated();
	}
	
	@Bean
	public PasswordEncoder passwordEncoder() {
		HashMap<String, PasswordEncoder> encoders = new HashMap<>();
		encoders.put("noop", NoOpPasswordEncoder.getInstance());
		encoders.put("bcrypt", new BCryptPasswordEncoder());
		encoders.put("scrypt", new SCryptPasswordEncoder());
		return new DelegatingPasswordEncoder("bcrypt", encoders);
	}
}

4.5、测试应用程序

在这里插入图片描述

这个图在当时讲解授权码授权类型时给大家展示过,里面就是一个具体的流程,更加具体的步骤讲解可以参考下面的博客,讲的比较到位。

https://blog.csdn.net/longlivechina008/article/details/125007457

我们下面演示一下流程:
启动程序:
输入localhost:9090时,由于所有路径都需要认证,所以被过滤器拦截,然后重定向到gitee登录页面
在这里插入图片描述
输入自己的gitee的账号密码后,客户端将我们重定向到gitee的授权页面,这个就是上图中的⑤-⑧步
在这里插入图片描述
点击同意授权后,当Gitee认证服务器认证了user,同时也获得了clientid和用户授权的权限后,发回重定向指令到浏览器,带着【授权码】–实际上是 HTTP 302响应
在这里插入图片描述
接着浏览器被重定向,向Client发出请求,带着授权码GET请求

http://localhost:9090/login/oauth2/code/gitee?code=3d459a2fgttbb60c168e9df64175ab3739085b28d0a12071efd&state=-AVIglbqTn6a0GjcoQWJQE0efOtbDI1L1fxYxnlMp1k%3D

【千万注意】这个地址http://localhost:9090/login/oauth2/code/gitee
就是我们代码中配置的

.redirectUriTemplate("{baseUrl}/{action}/oauth2/code/{registrationId}")

【注意】redirectUriTemplate其中:

  1. baseUrl的值http://localhost:9090/就是我们的应用Client的根路径。
  2. {action}不是我们配置的,而是OauthAuthenticationFilter拦截请求Filter中固定的-login,不同的路径对应不同拦截功能。我们没有自己写filter,采用的是默认的。我们只要配置了http.oauth2Login();就会有此默认的拦截器OauthAuthenticationFilter,他的逻辑是固定的。
  3. oauth2/code也是OauthAuthenticationFilter固定的。因为我们没有自己写filter。
  4. .gitee是我们为Client起的名字,OauthAuthenticationFilter拦截请求后,会根据路径上的这个名字gitee 这个id取出clientid clientsecret 权限scope等等。

所以,我们在Gitee上配置的客户端clientid和clientsecret一定要和Client传来的一致,且我们在gitee上配置的应用回调Url才是Gitee发出请求的依据。Gitee是根据配置的这个回调Url来进行请求的。所以,这个回调url必须和我们应用client配置一致才对。gitee对用户user认证并得到user对client的scope确认后,才会根据【gitee上的应用回调地址做出请求】。
而我们.redirectUriTemplate(“{baseUrl}/{action}/oauth2/code/{registrationId}”)这里的配置是配置应用client。当收到这样的请求,client会向gitee发出post请求。以便用得到的授权码到gitee上换取access_token,所以gitee上的用户配置的回调地址决定了Gitee向哪个地址发送认证码
而我们代码配置的.redirectUriTemplate(“{baseUrl}/{action}/oauth2/code/{registrationId}”
是这个地址的决定的Oauth2AuthenticationFilter的有效拦截功能启用两个地址必须一致才能收到Gitee来的授权码.
在这里插入图片描述
然后有了accessToken后,我们就可以自由的对整个程序的端点进行访问了。
如果想要移除用户已经存在的有效accessToken重新认证,可以到gitee的配置好的第三方应用处选择移除已授权用户的有效Token然后重启程序,等下次访问就有需要重新授权认证了。
在这里插入图片描述

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

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

相关文章

如何使用一台电脑远程控制多台电脑

如今&#xff0c;远程控制软件已经广泛应用于我们的日常生活中。我们使用远程桌面软件远程控制另一台电脑来完成我们的工作和学习。在某些情况下&#xff0c;我们可能还需要同时远程控制多台电脑。例如&#xff1a; 您是一名培训师&#xff0c;正在寻找远程访问软件来同时远程…

[激光原理与应用-15]:《激光原理与技术》-1- 什么是激光,激光概述

目录 第1章 什么是激光 1.1 什么是激光 1.2激光在生活中应用 第2章 激光的特点 2.1 方向性好&#xff08;平行性、直线性&#xff09; 2.2 单色性好&#xff08;颜色纯度高&#xff09; 2.3 相干性比太阳光好 2.4 亮度高 2.5 能量极大 第3章 光产生的方式与核心概念 …

又爆冷了啦,日本半场逆转德国,怎么利用共享经济搅乱世界杯格局

近日世界杯热点逐渐升高&#xff0c;在23号晚上亚洲劲旅日本以2-1逆转多次捧得大力神杯的德国队&#xff0c;此前德国还从未输过日本队&#xff0c;因此德国再次吃到闭门羹&#xff0c;爆出了本届世界杯开赛以来既阿根廷惨败的又一大冷门。赛后&#xff0c;日本全国人民共同庆祝…

Web(二)html5基础-超链接的应用(知识训练和编程训练)

web知识训练_html5_超链接的应用 web编程训练_html5_超链接的应用 第1关_创建热字超链接 编程要求 在右侧编辑器中的Begin - End区域内补充代码&#xff0c;创建热字超链接&#xff0c;具体要求是&#xff1a; 1.链源文字为“听音乐找酷我”。 2.链宿地址为“https://www.ku…

FPGA——多路选择器实现按键控制LED灯的亮灭

文章目录前言一、多路选择器二、绘制模块框图及波形图三、Verilog HDL代码及测试代码四、创建工程五、仿真六、上板验证1、分配引脚2、烧录七、效果演示八、总结前言 软件&#xff1a;Quartus Prime Standard 18.0仿真软件&#xff1a;modelsim 10.5代码编写软件&#xff1a;V…

【虹科新品】 HK-MR430330绝对式光纤编码器介绍合集(下)

HK-MR430系列ZapFREE光纤位置传感器是一款外形小巧、具有13位单圈分辨率的旋转位置传感器。MR430设计新颖&#xff0c;开发了新的应用和OEM产品功能&#xff0c;这在以前的电子传感器是无法实现的。该传感器100%无源&#xff0c;不受EMI、RFI、微波和磁场的影响。创新型全绝缘设…

我参加NVIDIA Sky Hackathon 后端修改

文件架构 前面两个分别是执行语音识别和图片识别的代码templates 存放的是网页的模板&#xff0c; 前端将文件写在这里即可uploads 存放的是上传至后台的文件server.ipynb 用于启动 flash 服务器app.py 内是用 flash 写的 Python 后端install_tools.sh 是用于安装相关工具的 sh…

(十四)Spring之回顾代理模式

文章目录回顾代理模式动态代理常用技术CGLIB动态代理技术上一篇&#xff1a;&#xff08;十三&#xff09;Spring之JdbcTemplate 回顾代理模式 参考&#xff1a;代理模式Proxy Pattern 不用JDK的动态代理&#xff0c;手写JDK动态代理 动态代理常用技术 在程序运行阶段&…

CentOS 7 手动安装OpenStack

官网文档 因为之前已经在 Ubuntu 20.04 下完成了 Ubuntu 20.04 手动安装OpenStack &#xff0c;最后&#xff0c;想要学习 OVN 的时候&#xff0c;发现 ubuntu 上的 OVN 安装很复杂&#xff0c;没有 TripleO/RDO based deployments &#xff0c;所以&#xff0c;又在 CentOS 7…

项目开源!基于PaddleDetection打造实时人体姿态检测的多关节控制皮影机器人

本文已在【飞桨PaddlePaddle】公众号平台发布&#xff0c;详情请戳链接&#xff1a;项目开源&#xff01;基于PaddleDetection打造实时人体姿态检测的多关节控制皮影机器人 皮影戏是一种以兽皮或纸板做成的人物剪影以表演故事的民间戏剧&#xff0c;皮影一般由头、躯干&#x…

2003-2019年各省市场分割指数全步骤数据+最终结果

2003-2019年市场分割指数 1、时间&#xff1a;2003-2019年 2、数据包含&#xff1a;31各省份市场分割指数全步骤数据和最终结果 3、具体内容&#xff1a;市场分割指数差分形式相对价格、市场分割指数去均值、市场分割指数方差、市场分割指数最终结果。 4、指标说明&#xf…

vue3 框架学习概念笔记

文章目录前情提要框架设计概览命令式声明式小结虚拟dom性能运行时和编译时框架设计核心要素声明式描述UI渲染器组件的本质vue.js模板响应式系统概念完善的响应式系统响应式系统的调度计算属性Computedwatch 原理竞态问题非原始值的响应式方案javaScript 对象原始值的响应式方案…

毕业后河北种水稻 国稻种芯·中国水稻节:安徽姑娘承德务农

毕业后河北种水稻 国稻种芯中国水稻节&#xff1a;安徽姑娘承德务农 (新华每日电讯记者刘金海、方欣、牟宇) 新闻中国采编网 中国新闻采编网 谋定研究中国智库网 中国农民丰收节国际贸易促进会 国稻种芯中国水稻节 中国三农智库网-功能性农业农业大健康大会报道&#xff1a;整…

【Linux】内存查看vmstat命令(虚拟内存统计)

vmstat命令&#xff1a;虚拟内存统计 CPU使用率内存试用虚拟内存交换情况IO读写情况 process r&#xff1a;运行和等待CPU时间片的进程数 超过cpu个数&#xff0c; 出现CPU瓶颈 长时间大于1&#xff0c;CPU不足&#xff0c;需要增加CPU b&#xff1a;正在等待资源的进程数&…

玩转MySQL:定位排查解决突发Bug

引言 前面MySQL优化、调化两文中&#xff0c;聊到了关于数据库性能优化的话题&#xff0c;而本文则再来聊一聊关于MySQL线上排查方面的话题。线上排查、性能优化等内容是面试过程中的“常客”&#xff0c;而对于线上遇到的“疑难杂症”&#xff0c;需要通过理性的思维去分析问…

Java岗位必备技能SpringBoot的面试题集锦

当下SpringBoot框架真的很火&#xff0c;大多数企业把它作为基础技能&#xff0c;考察求职者的能力。如下截图&#xff0c;是我从Boss直聘中找到的&#xff0c;要求SpringBoot是必备技能。 所以非常有必要为了面试&#xff0c;好好归纳下SpringBoot常被提起来的问题。 题目大纲…

Tesseract .Net SDK C# OCR 2022.1

Tesseract .Net SDK C# OCR 库 #将扫描的 PDF 转换为可搜索的文档 #快速准确的基于神经网络的引擎 #纠正低质量扫描 # 120 多种语言 # .Net 2.0, .Net 5, 标准, 核心 Tesseract OCR - industry-fastest .Net OCR library 4 行代码&#xff0c;仅此而已 var api OcrApi.Creat…

[附源码]java毕业设计疫情环境下的酒店管理系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

不知道什么的复习题

P4408 [NOI2003] 逃学的小孩&#xff0c;一眼就看出要化简题面&#xff1a;不就是找三个点使得ABBC最大嘛&#xff0c;不妨想起直径的性质&#xff0c;最长&#xff0c;而且其他点到它的距离最长。那么直接拿直径来做就行。推导出结论为ansmax(min(dis[A][k],dis[B][k])dis[A][…

CSS3------CSS选择器

界面中的“超文本“本质上就是通过一个一个矩形盒子包起来&#xff0c;然后进行排版组合&#xff0c;那么接下来要详细研究这个标签盒子的细节结构&#xff0c;以帮助学习如何使用它&#xff0c;但是在开始研究它之前&#xff0c;我们先来了解一个概念&#xff0c;选择器的概念…