【深入浅出 Spring Security(七)】RememberMe的实现原理详讲

news2024/11/29 5:34:00

RememberMe 的实现原理

  • 一、RememberMe 的基本使用
  • 二、RememberMeAuthenticationFilter 源码分析
    • RememberMeServices
      • TokenBasedRememberMeServices
        • TokenBasedRememberMeServices 中对 processAutoLoginCookie 方法的实现
        • 总结
        • 原理图式
  • 三、提高安全性
    • PersistentTokenBasedRememberMeServices
    • 内存令牌登录测试
  • 四、令牌数据库的持久化
  • 五、总结

一、RememberMe 的基本使用

先看看最简单用法的默认页面效果变化。

SecurityConfig 配置类

@EnableWebSecurity
public class SecurityConfig {


    @Bean
    public InMemoryUserDetailsManager inMemoryUserDetailsManager(){
        return new InMemoryUserDetailsManager(
                User.withUsername("admin")
                        .password("{noop}123")
                        .roles("admin")
                        .build()
        );
    }

    @Bean
    public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception {
        return http.getSharedObject(AuthenticationManagerBuilder.class)
                .userDetailsService(inMemoryUserDetailsManager())
                .and()
                .build();
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        return http.authorizeRequests()
                .anyRequest()
                .authenticated()
                .and()
                .formLogin()
                .and()
                .rememberMe()
                .and()
                .csrf()
                .disable().build();
    }

}

测试 TestController 代码

@RestController
public class TestController {

    @GetMapping("/test")
    public String test(){
        return "test";
    }

}

以下是给出的默认的登录页面。

在这里插入图片描述
观察页面源代码可以发现,比原先没配置 RememberMe 之前多了个 name 为remember-me 的 checkbox 选项。

在这里插入图片描述
如果我们勾选了它并且登录成功后,当我们关闭掉当前浏览器,重新打开,是不必进行重新登录就可以访问登录用户所能访问了的资源的。在 【深入浅出Spring Security(二)】Spring Security的实现原理 中小编列举了 Spring Security 中自带的过滤器,其中不是默认加载的队列中有个叫 RememberMeAuthenticationFilter ,它是用来处理 RememberMe 登录的。下面来通过对 RememberMeAuthenticationFilter 进行源码分析,了解 RememberMe 登录的实现原理。

二、RememberMeAuthenticationFilter 源码分析

我们知道 RememberMeAuthenticationFilter 是一个过滤器,其核心代码即在 doFilter 方法中,接下来就是对这个方法的源码分析。

doFilter 方法中主要实现可分为三步:

  1. 请求到达过滤器后,首先判断 SecurityContextHolder 中是否有值,没值的话表示用户尚未登录,此时调用 autoLogin 方法进行自动登录。
  2. 当自动登录成功后返回的 rememberMeAuth 不为 null 时,表示自动登录成功,此时调用 authenticate方法对 key 进行校验,并且将登录成功的用户信息保存到 SecurityContextHolder 对象中,然后调用登录成功的回调,并发布登录成功时间。需要注意的是:登录成功的回调并不包含 RememberMeServices 中的 loginSuccess 方法。
  3. 失败的话会进行一些登录失败的回调,打印失败的日志信息。

可以看见其实现还得看 rememberMeServices 中的 autoLogin 方法,是否登录成功决定了后面成功和失败的回调。

// RememberMeAuthenticationFilter 中的 doFilter 方法
	private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
			throws IOException, ServletException {
			/*
			1. 请求到达过滤器后,首先判断 SecurityContextHolder 中是否有值,
			没值的话表示用户尚未登录,此时调用 autoLogin 方法进行自动登录
*/
		if (SecurityContextHolder.getContext().getAuthentication() != null) {
			this.logger.debug(LogMessage
					.of(() -> "SecurityContextHolder not populated with remember-me token, as it already contained: '"
							+ SecurityContextHolder.getContext().getAuthentication() + "'"));
			chain.doFilter(request, response);
			return;
		}
		Authentication rememberMeAuth = this.rememberMeServices.autoLogin(request, response);
		/*
		2. 当自动登录成功后返回的 rememberMeAuth 不为 null 时,
		表示自动登录成功,此时调用 authentication 方法对 key 进行校验,
		并且将登录成功的用户信息保存到 SecurityContextHolder 对象中,
		然后调用登录成功的回调,并发布登录成功时间。
		需要注意的是:登录成功的回调并不包含 RememberMeServices 中的 loginSuccess 方法。
		*/
		if (rememberMeAuth != null) {
			// Attempt authenticaton via AuthenticationManager
			try {
				rememberMeAuth = this.authenticationManager.authenticate(rememberMeAuth);
				SecurityContext context = SecurityContextHolder.createEmptyContext();
				context.setAuthentication(rememberMeAuth);
				SecurityContextHolder.setContext(context);
				onSuccessfulAuthentication(request, response, rememberMeAuth);
				// 这里打印了保存到 SecurityContextHolder 中的authentication日志信息
				this.logger.debug(LogMessage.of(() -> "SecurityContextHolder populated with remember-me token: '"
						+ SecurityContextHolder.getContext().getAuthentication() + "'"));
				this.securityContextRepository.saveContext(context, request, response);
				if (this.eventPublisher != null) {
					this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(
							SecurityContextHolder.getContext().getAuthentication(), this.getClass()));
				}
				if (this.successHandler != null) {
					this.successHandler.onAuthenticationSuccess(request, response, rememberMeAuth);
					return;
				}
			}
			catch (AuthenticationException ex) {
			/*
			3. 失败的话会进行一些登录失败的回调,
			打印失败的日志信息
*/
				this.logger.debug(LogMessage
						.format("SecurityContextHolder not populated with remember-me token, as AuthenticationManager "
								+ "rejected Authentication returned by RememberMeServices: '%s'; "
								+ "invalidating remember-me token", rememberMeAuth),
						ex);
				this.rememberMeServices.loginFail(request, response);
				onUnsuccessfulAuthentication(request, response, ex);
			}
		}
		chain.doFilter(request, response);
	}

RememberMeServices

RememberMeServices 是一个接口,其中定义了三个方法:

  • autoLogin 方法可以从请求中提取出需要的参数,完成自动登录功能;
  • loginFail 方法是自动登录失败的回调;
  • loginSuccess 方法是自动登录成功的回调。

下面是 Spring Security 中对 RememberMeServices 的实现类
在这里插入图片描述

TokenBasedRememberMeServices

TokenBasedRememberMeServices 是 Spring Security 的默认实现,接下来对其 autoLogin 进行源码分析,其实 autoLogin 是 AbstractRememberMeServices 中的一个方法,其子类并没有进行重写。子类重点重写的是 processAutoLoginCookie 方法。

先看看 AbstractRememberMeServices 中实现的 autoLogin 方法(下面再对其内部调用的核心方法进行分析)

// AbstractRememberMeServices 中实现的 autoLogin 方法
	@Override
	public final Authentication autoLogin(HttpServletRequest request, HttpServletResponse response) {
	// 从request 对象中取对应 remember-me 名的 cookie 值
		String rememberMeCookie = extractRememberMeCookie(request);
		// 如果不存在的话就返回空
		if (rememberMeCookie == null) {
			return null;
		}
		this.logger.debug("Remember-me cookie detected");
		// 或者说是一个空值也返回空
		if (rememberMeCookie.length() == 0) {
			this.logger.debug("Cookie was empty");
			cancelCookie(request, response);
			return null;
		}
		try {
		// 先进行base64解码
		// 然后对rememberMeCookie进行 :分割
			String[] cookieTokens = decodeCookie(rememberMeCookie);
			// 自动登录认证吧,算是
			// 去把用户名、密码、时长搞成MD5然后和cookie返回来的进行比对,认证过程在这个方法里
			UserDetails user = processAutoLoginCookie(cookieTokens, request, response);
			this.userDetailsChecker.check(user);
			this.logger.debug("Remember-me cookie accepted");
			// 创建一个RememberMeAuthenticationToken即为认证数据源
			return createSuccessfulAuthentication(request, user);
		}
		cancelCookie(request, response);
		return null;
}

获取对应 remember-name 名的 Cookie 值的方法源码如下

在这里插入图片描述
将 Cookie 值进行 base64 解码,分割成字符串数组进行返回。

在这里插入图片描述

TokenBasedRememberMeServices 中对 processAutoLoginCookie 方法的实现

TokenBasedRememberMeServices 中对 processAutoLoginCookie 方法的实现,它主要是用来验证 Cookie 中的令牌信息是否合法的(下面说的 CookieTokens 是上面用base64解密,再分割的字符串数组):

  1. 首先判断 CookieTokens 长度是否为 3,不为 3 就抛出异常。
  2. 从 CookieTokens 中下标为 1 的值,也就是过期时间,判断令牌是否过期,如果已经过期,则抛出异常;
  3. 根据用户名(CookieTokens 下标为 0 的值)查询当前用户对象;
  4. 调用 makeTokenSignature 方法生成一个签名,签名的生成过程如下:首先将用户名、令牌过期时间、用户名密码以及key 组成一个字符串,中间用“:”隔开,然后通过 MD5 消息摘要算法对该字符串进行加密,并将加密结果转换为一个字符串返回。
  5. 判断第 4 步生成的签名和通过 Cookie 传来的签名是否相等(即 cookieTokens 数组小标为 2 的值),如果相等,则表示令牌合法,直接返回用户对象,否则抛出异常。

下面是其源码,小编标号编号,与上解析相互对应

	@Override
	protected UserDetails processAutoLoginCookie(String[] cookieTokens, HttpServletRequest request,
			HttpServletResponse response) {
			// 1
		if (cookieTokens.length != 3) {
			throw new InvalidCookieException(
					"Cookie token did not contain 3" + " tokens, but contained '" + Arrays.asList(cookieTokens) + "'");
		}
		// 2
		long tokenExpiryTime = getTokenExpiryTime(cookieTokens);
		if (isTokenExpired(tokenExpiryTime)) {
			throw new InvalidCookieException("Cookie token[1] has expired (expired on '" + new Date(tokenExpiryTime)
					+ "'; current time is '" + new Date() + "')");
		}
		// 3
		UserDetails userDetails = getUserDetailsService().loadUserByUsername(cookieTokens[0]);
		Assert.notNull(userDetails, () -> "UserDetailsService " + getUserDetailsService()
				+ " returned null for username " + cookieTokens[0] + ". " + "This is an interface contract violation");
		// 4
		String expectedTokenSignature = makeTokenSignature(tokenExpiryTime, userDetails.getUsername(),
				userDetails.getPassword());
			// 5
		if (!equals(expectedTokenSignature, cookieTokens[2])) {
			throw new InvalidCookieException("Cookie token[2] contained signature '" + cookieTokens[2]
					+ "' but expected '" + expectedTokenSignature + "'");
		}
		return userDetails;
	}

总结

当用户通过用户名/密码的形式登录成功后,系统会根据用户的用户名、密码以及令牌的过期时间计算出一个签名,这个签名使用 MD5 消息摘要算法生成,是不可逆的。然后再将用户名、令牌过期时间以及签名拼接成一个字符串,中间用":"隔开,对拼接好的字符串进行 Base64 编码,然后将编码后的字符串一起封装成 Cookie 值,返回给前端,也就是我们在浏览器中看到的令牌。当关闭浏览器再次打开,访问系统资源时会自动携带上Cookie中的令牌,服务端拿到 Cookie 中的令牌后,先进行 Base64 解码,解码之后分别提取令牌中的三项数据;接着根据令牌中的数据判断令牌是否过期,如果没有过期,则根据令牌中的用户名查询出用户信息;接着再计算出一个签名和令牌中的签名进行比对,如果一致,表示会牌是合法令牌,自动登录成功,否则自动登录失败。

在这里插入图片描述

原理图式

在这里插入图片描述

三、提高安全性

通过上面的源码分析,我们知道,登录认证成功会去调用 TokenBasedRememberMeServices 中的 onLoginSuccess 方法生成 Cookie 响应给浏览器(其实对应的Cookie值就是Base6
编码后的值
),浏览器会保存下来,当我们关闭浏览器后,可以利用请求报文中带有的这个 Cookie 进行认证从而访问到资源。这种免登录的方式是缺乏不安全的,有关 remember-me 的 Cookie 值暴露在外面,对隐私是有害的,那有什么办法让它变得安全吗?答案是没有绝对的安全,只能说是提高它的安全性。

PersistentTokenBasedRememberMeServices

PersistentTokenBasedRememberMeServices 中的 onLoginSuccess 和 processAutoLoginCookie 不同于 TokenBasedRememberMeServices,下面看一下它的具体实现。

onLoginSuccess 具体实现

在这里插入图片描述

  1. 先从认证数据源中获取用户名,然后将一序列号和用户名封装成一个 PersistentRememberMeToken 数据源;
  2. 将这个数据源放入到 tokenRepository 仓库中,它是一个基于内存的仓库;
  3. 最后将Cookie创建出来响应给浏览器,这个Cookie值是由 series 和 token 两项合并然后进行Base64编码的值。

processAutlLoginCookie 具体实现

在这里插入图片描述

  1. 从 CookieTokens 数组中分别提取到 series 和 token,然后根据 series 去内存中查询出一个 PersistentRememberMeToken 对象。如果查询出来的对象为null,表示内存中并没有 series 对应的值,本次登录失败。如果查询出来的 token 和从 CookieTokens 中解析出来的 token 不相同,说明自动登录令牌已经泄露(恶意用户利用令牌登录后,内存中的token就变了),此时移除当前用户的所有自动登录记录并抛出异常。
  2. 根据数据库中查询出来的结果判断令牌是否过期,如果过期就抛出异常;
  3. 生成一个新的 PersistentRememberMeToken 对象,用户名和series不变,token重新生成,data 也使用当前时间。newToken 生成后,根据 series 去修改内存中的 token 和data(即每次自动登录后都会产生新的 token 和date)
  4. 调用 addCookie 方法添加 Cookie,在 addCookie 方法中,实质是去调用了父类 AbstractRememberMeServices 中的 setCookie 方法,但是要注意第一个数组参数中只有两项:series 和 token(即返回到前端的令牌是通过对 series 和 token 进行 base64 编码得到的)
  5. 最后将根据用户名查询用户对象并返回。

内存令牌登录测试

Security 配置类

@EnableWebSecurity
public class SecurityConfig {


    @Bean
    public InMemoryUserDetailsManager inMemoryUserDetailsManager(){
        return new InMemoryUserDetailsManager(
                User.withUsername("admin")
                        .password("{noop}123")
                        .roles("admin")
                        .build()
        );
    }

    @Bean
    public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception {
        return http.getSharedObject(AuthenticationManagerBuilder.class)
                .userDetailsService(inMemoryUserDetailsManager())
                .and()
                .build();
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        return http.authorizeRequests()
                .anyRequest()
                .authenticated()
                .and()
                .formLogin()
                .and()
                .rememberMe()
                // .rememberMeParameter("rememberMe")
                .rememberMeCookieName("rememberMe")
                .rememberMeServices(rememberMeServices())
                .and()
                .csrf()
                .disable().build();
    }

    @Bean
    public RememberMeServices rememberMeServices(){
        return new PersistentTokenBasedRememberMeServices(UUID.randomUUID().toString(),
                inMemoryUserDetailsManager(),
                new InMemoryTokenRepositoryImpl());
    }

}

测试结果:开一个浏览器进行登录认证,认证成功后携带了 remember-me Cookie,重开一个浏览器携带这个Cookie去访问服务器需认证的资源,然后再关闭刚开始的浏览器,再打开它进行访问一样的资源,会403重定向到登录页面且服务器端会报异常(此时内存中的令牌已经没了),报的是 CookieTheftException(Cookie被盗窃)。

注意:刷新没有报异常且可以访问资源,是因为存在SessionID,它在服务器端也可以进行认证,所以需要关闭浏览器让 SessionID 匹配不上(服务器端对应的 Session 自然会因超时被销毁)然后进行测试。

四、令牌数据库的持久化

在 PersistentTokenBasedRememberMeServices 中存储令牌的 PersistentTokenRepository 仓库默认是基于内存实现的(即 InMemoryTokenRepositoryImpl),使用这种方式会出现一个问题,当服务器端重启应用程序时,那浏览器用户登录则需要重新登录,因为内存中肯定是没有对应的令牌了。

Spring Security 除了提供了 InMemoryTokenRepositoryImpl 实现外,还提供了JdbcTokenRepositoryImpl 对 PersistentTokenRepository 的实现。即将令牌会保存置数据库中,无需担心上面阐述的问题。

在 JdbcTokenRepositoryImpl 中,已经为我们写好了需要的 SQL,令牌的增删改查实现已为我们提供了与数据库进行交互的支持。

在这里插入图片描述

测试一波,下面是配置代码

@EnableWebSecurity
public class SecurityConfig {


    @Bean
    public InMemoryUserDetailsManager inMemoryUserDetailsManager(){
        return new InMemoryUserDetailsManager(
                User.withUsername("admin")
                        .password("{noop}123")
                        .roles("admin")
                        .build()
        );
    }

    @Bean
    public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception {
        return http.getSharedObject(AuthenticationManagerBuilder.class)
                .userDetailsService(inMemoryUserDetailsManager())
                .and()
                .build();
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        return http.authorizeRequests()
                .anyRequest()
                .authenticated()
                .and()
                .formLogin()
                .and()
                .rememberMe()
                // .rememberMeParameter("rememberMe")
                .rememberMeCookieName("remember-me")
                // .rememberMeServices(rememberMeServices())
                .tokenRepository(persistentTokenRepository())
                .and()
                .csrf()
                .disable()
                .build();
    }

    @Resource
    private DataSource dataSource;

    @Bean
    public PersistentTokenRepository persistentTokenRepository(){
        JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
        tokenRepository.setDataSource(dataSource);
        // 设置为true要保障数据库该表不存在,不然会报异常哦
        // 所以第二次打开服务器应用程序的时候得把它设为false
        tokenRepository.setCreateTableOnStartup(true);
        return tokenRepository;
    }

}

当开启服务器应用的时候,自动为我们创建了表,如下图所示

在这里插入图片描述
认证后数据库表中会新增数据

在这里插入图片描述

五、总结

  • RememberMe 实质呢就是将令牌以 Cookie 的形式响应给浏览器,然后浏览器进行了本地存储,等下次再次访问资源的时候,即会拿该令牌连请求一起到服务器端,令牌会使得后台进行自动登录(即用户认证)。
  • 当我们自定义 SecurityFilterChain 安全过滤器链的时候,如果配置了 rememberMe() 的话,那RememberMeAuthenticationFilter 即会是过滤器链中的一员。
  • 阐述一下流程(实现该功能最主要的类是 RememberMeServices,首次认证的响应和自动登录的认证它都是主角):在没有自定义过滤器的情况下,一般都是使用 UsernamePasswordAuthenticationFilter 进行用户认证的,当点击了 checkbox 按钮连同用户信息一起向服务器端请求时,首先是 UsernamePasswordAuthenticationFilter 去完成认证,如果认证成功会调用 RememberMeServices 中的 onLoginSuccess 方法(这个看具体实现),此时响应中就已存在其令牌信息了(即 Cookie)。当关闭浏览器或Session过期,访问服务器端需要认证的资源时,请求报文中会带着这个Cookie一起到服务器端,会进入到 RememberMeAuthenticationFilter 过滤器中,它会调用 RememberMeServices 中的 autoLogin 方法进行自动登录,在 autoLogin 自动登录过程中,会调用 processAutoLoginCookie 方法进行令牌认证(该方法取决于 RememberMeServices 的实现类),autoLogin 方法执行完成认证成功后,返回了用户数据源信息,接下来就是一些封装数据信息到 SecurityContextHolder 中等等一些操作。
  • RememberMeServices 是核心类,Spring Security 中提供了两种实现类,一种是 TokenBasedRememberMeServices,这种方式就是将用户名、超时时间、密码等信息进行Base64编码即一些操作组成的令牌给服务器,验证令牌就是对浏览器中发来的进行反编码看看是否一致,这种方式安全度很低;另一种实现是 PersistentTokenBasedRememberMeServices,它内部依赖于 PersistentTokenRepository 仓库,提供了基于内存和基于 Jdbc 的实现,它比前一种安全,原因是它每次自动认证后会更新令牌(即Cookie),如果在自动认证过程中发现令牌不一致会及时剔除(即从PersistentTokenRepository仓库中删除),报Cookie被盗窃异常。
  • PersistentTokenBasedRememberMeServices 中同 TokenBasedRememberMeServices 一样的是它也使用的是 Base64 编码后进行令牌设置,自动登录认证令牌的时候也需要解码;不同的是它是由两数据组成的(序列号(固定的),token(会变化的)),而 TokenBasedRememberMeServices 是三。也不能说序列号是固定的吧,就是说它是在 onLoginSuccess 方法中生成的,然后存在浏览器上,在 processAutoLoginCookie 方法中不会改变这个序列号,只会变token,可以说浏览器Cookie解码后的序列号是固定死的(没有重新登录验证的情况下)。
  • 使用起来很简单(一般使用的是PersistentTokenBasedRememberMeServices,且使用的仓库实现是 JdbcTokenRepositoryImpl),在配置 rememberMeConfigurer 时,配置一个 tokenRepository(PersistentTokenRepository) 即可,它会自动为我们配置 rememberMeServices 的,因为Spring Security就俩那实现,如果你配置了 PersistentTokenRepository,实质就默认你是使用了 PersistentTokenBasedRememberMeServices

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

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

相关文章

【数据分析之道-Matplotlib(八)】Matplotlib箱线图

文章目录 专栏导读1、Matplotlib箱线图boxplot()基本语法2、Matplotlib箱线图boxplot()举例异常值3、Matplotlib箱线图boxplot()样式定义4、Matplotlib箱线图boxplot()举例带缺口5、Matplotlib箱线图boxplot()举例横向显示6、Matplotlib箱线图boxplot()举例显示平均值7、Matplo…

电子企业实施数字化工厂建设之前,需要注意哪些

随着工业4.0时代的到来,数字化工厂建设已成为越来越多电子企业的重要议题。数字化工厂管理系统能够提高生产效率、降低成本、提高产品质量等多个方面的优势,对于企业的可持续发展具有重要意义。然而,在实施电子企业数字化工厂建设之前&#x…

【Flutter】widgets (6) Stateful Widget 有状态组件的生命周期

文章目录 一、前言二、StatefulWidget的生命周期三、State对象的生命周期四、initState(), didUpdateWidget(), dispose()方法的用途五、StatefulWidget和State对象的生命周期六、代码示例七、总结一、前言 在上一篇文章中,我们初步认识了什么是Stateful Widget 有状态组件。…

minigpt4 部署踩坑记录

1,最近看网上minigpt4很火,下载下来试一下,把碰到问题记录一下。 2 访问 GitHub - Vision-CAIR/MiniGPT-4: MiniGPT-4: Enhancing Vision-language Understanding with Advanced Large Language Models 下载代码到 centos服务上。 3&#xf…

九耶丨阁瑞钛伦特-大型计算机硬件组成(一)

​ IBM大型机系统是由主机和多种外围设备组合形成的一个综合系统,上面是一个主机系统组成的例子。 ​以上是OS390操作系统的组成情况: MVS: 大型机操作系统的核心部分,有时又被使用为大型机操作系统的全称。 SMS: S…

文件管理开发指南全新登场,快来了解如何访问各类文件

原文:文件管理开发指南全新登场,快来了解如何访问各类文件,点击链接查看更多技术内容。 在应用开发的过程中,我们经常会用到各种各样的文件,例如文档、图片、音频、视频等,如何管理和访问这些文件&#xff…

【网络】· VTP虚拟局域网中继

目录 🍉VTP技术 🍉以太网通道配置 🍒sw1配置 🍒sw2配置 🍉VTP工作原理 🍒VTP模式 🍒VTP通告 🍒VTP的版本 🍒VTP修改编号 🍒VTP通告类型 🍒VTP修剪…

数据分析第16课seaborn可视化

FacetGrid与调色盘(参看语雀) 前言:有针对性的,选择不同的分析方式去整合数据,描述统计学: 对比分析:同期对比,环境对比等。分类分析:字段分为哪些类别,每个类别出现了多少次,哪个类别的权重是最大的。哪个类别是可以忽略的。哪个类别是干扰项。分布分析:例如身高,…

实现一个简单的事件驱动处理框架

点击上方“嵌入式应用研究院”,选择“置顶/星标公众号” 干货福利,第一时间送达! 来源 | 嵌入式应用研究院 整理&排版 | 嵌入式应用研究院 事件驱动框架允许程序处理外部事件,如网络连接、文件I/O、超时和信号。事件驱动框架可…

配置office和wps的数学公式编辑工具Mathtype7.4

一、概述 《数学公式编辑器(MathType)》 是一款专业的数学公式编辑工具,理科生专用的工具。mathtype公式编辑器能够帮助用户在各种文档中插入复杂的数学公式和符号。 数学公式编辑器工具可以轻松输入各种复杂的公式和符号,与Office文档完美结合&#xff…

imu预积分处理

VINS-Mono是HKUST的Shen Shaojie团队开源的一套非常优秀的Visual-Inertial融合定位算法。关于算法的介绍以及论文可以通过此链接查看。 IMU、ˆω和ˆa的原始陀螺仪和加速度计测量值由下式给出: 在车身坐标系测量的IMU测量结合了抗重力和平台动力学的力&#xff0c…

[蓝桥杯 2018 国 B] 矩阵求和

题目描述 经过重重笔试面试的考验,小明成功进入 Macrohard 公司工作。 今天小明的任务是填满这么一张表: 表有 n 行 n 列,行和列的编号都从 1 算起。 其中第 ii 行第 jj 个元素的值是 gcd(i,j) 的平方,gcd 表示最大公约数&#xf…

什么样的电容笔好用?好用的平板触控笔

现在使用电容笔的人越来越多了,尤其学生党和上班族,可以使用电容笔来提高生产效率,这个时候电容笔的书写流畅度就非常重要,今天给大家介绍四款平价又好用的电容笔。跟我来看看吧! 一、使用电容笔的用途: …

四、LLC 谐振变换器

变换器实现 ZVS 的限制 全面了解LLC谐振变换器实现ZVS的条件,把变换器主电路变形成图所示形式。其中 Coss1、Coss2分别为开关管 S1、S2 漏-源极间的寄生电容,并且Coss1Coss2 Coss 。Cstray为与谐振网络并联的等效寄生电容,则变换器在 ZVS 条…

【数据恢复、安全和备份解决方案】上海道宁与LSoft为企业提供先进的技术来处理现代数据安全和保存问题

需要备份和恢复磁盘、 恢复已删除的文档和照片、 安全擦除磁盘、 监控硬盘健康状况、 甚至在Windows 无法正常启动时修复 PC? Active Data Studio是 一组桌面应用程序 和可引导CD/DVD或USB磁盘 用于将任何系统引导至 Windows恢复环境 开发商介绍 LSoft Te…

MySQL数据库 – node使用

1 MySQL查询对象 2 MySQL查询数组 3 mysql2库介绍使用 4 mysql2预处理语句 5 mysql2连接池使用 6 mysql2的Promi 这里仅说明如何使用服务器连接数据库并进行操作。 预处理语句就是可以输入变量的语句(表现形式是有符号:?)。需…

计算机网络管理-SNMP网络管理中遇到的问题及解决

启动网络发现的网络拓扑呈现错误 在实验中我发现,进入SNMPc 工具后,因为先前我是开启了网络发现的功能的,因此程序会自动生成拓补图,自动生成的网络规划是错误的,如下图所示: 我的设备:cdypc没…

手机号码在网时长 API 探索:精准营销与用户洞察

随着移动互联网的蓬勃发展,手机在网时长成为评估用户活跃度和风险控制的关键指标。手机号码在网时长 API 提供了查询手机号在网时长、判断活跃程度以及个性化推荐和优惠等功能,为企业实现精准营销、有效风险控制和深入用户洞察提供了有力支持。 本文将深…

pnpm v8.6 正式发布,软件包管理器

导读pnpm 是一个快速、节省磁盘空间的软件包管理器。它使用一个内容可寻址的文件系统来存储磁盘上所有模块目录的所有文件。当使用 npm 或 Yarn 时,如果你有 100 个使用 lodash 的项目,你将在磁盘上有 100 份 lodash 的拷贝,而使用 pnpm 时&a…

分类树,我从2s优化到0.1s

前言 分类树查询功能,在各个业务系统中可以说随处可见,特别是在电商系统中。 但就是这样一个简单的分类树查询功能,我们却优化了5次。 到底是怎么回事呢? 背景 我们的网站使用了SpringBoot推荐的模板引擎:Thymelea…