Spring Security 6.x 系列(10)—— SecurityConfigurer 配置器及其分支实现源码分析(二)

一、前言

在本系列文章:

Spring Security 6.x 系列(4)—— 基于过滤器链的源码分析(一)
中着重分析了Spring SecuritySpring Boot自动配置、 DefaultSecurityFilterChainFilterChainProxy 的构造过程。

Spring Security 6.x 系列(7)—— SecurityBuilder 继承链源码分析
中详细分析了Spring SecurityWebSecurityHttpSecurityAuthenticationManagerBuilder 三个构造器的公共继承链。

Spring Security 6.x 系列(8)—— SecurityConfigurer 配置器及其分支实现源码分析(一)
中分析SecurityConfigurer配置器及其主要分支实现。

Spring Security 6.x 系列(9)—— 基于过滤器链的源码分析(二)
着重分析了@EnableGlobalAuthentication注解的作用、对AuthenticationConfiguration构造AuthenticationManager过程和上文中未介绍的GlobalAuthenticationConfigurerAdapter 配置器的五个分支实现进行了详细的说明。

今天我们就从未被介绍的SecurityConfigurerAdapter配置器的具体分支实现进行展开。

二、SecurityConfigurerAdapter

SecurityConfigurerAdapter在上文中有过详解介绍,它是SecurityConfigurer的基类,它允许子类仅实现它们感兴趣的方法。它还提供了使用 SecurityConfigurer以及完成后获取正在配置的SecurityBuilder(构造器)的访问权限的机制。

SecurityConfigurerAdapter 的实现主要有三大类:

  • UserDetailsAwareConfigurer
  • AbstractHttpConfigurer
  • LdapAuthenticationProviderConfigurer

考虑到 LDAP 现在使用很少,所以重点介绍前两个。

三、UserDetailsAwareConfigurer

这个类名就能大概知道是和用户详细信息配置有关。

再通过继承关系图,看看UserDetailsAwareConfigurer的顶层架构设计:

在这里插入图片描述

UserDetailsAwareConfigurer是一个抽象类,源码比较简单:

/**
 * Base class that allows access to the {@link UserDetailsService} for using as a default
 * value with {@link AuthenticationManagerBuilder}.
 *
 * @param <B> the type of the {@link ProviderManagerBuilder}
 * @param <U> the type of {@link UserDetailsService}
 * @author Rob Winch
 */
public abstract class UserDetailsAwareConfigurer<B extends ProviderManagerBuilder<B>, U extends UserDetailsService>
		extends SecurityConfigurerAdapter<AuthenticationManager, B> {

	/**
	 * Gets the {@link UserDetailsService} or null if it is not available
	 * @return the {@link UserDetailsService} or null if it is not available
	 */
	public abstract U getUserDetailsService();

}

通过源码我们可知:

  • 泛型U继承了UserDetailsService接口,也就意味着
    getUserDetailsService()方法返回的对象肯定是UserDetailsService接口的实现。

  • 泛型B继承了ProviderManagerBuilder接口,ProviderManagerBuilder构造器的作用是用来构建AuthenticationManager对象,可就意味UserDetailsAwareConfigurer(配置器)用来配置ProviderManagerBuilder构造器。

3.1 AbstractDaoAuthenticationConfigurer

AbstractDaoAuthenticationConfigurer也是一个抽象类,是模版模式:

/**
 * Allows configuring a {@link DaoAuthenticationProvider}
 *
 * @param <B> the type of the {@link SecurityBuilder}
 * @param <C> the type of {@link AbstractDaoAuthenticationConfigurer} this is
 * @param <U> The type of {@link UserDetailsService} that is being used
 * @author Rob Winch
 * @since 3.2
 */
public abstract class AbstractDaoAuthenticationConfigurer<B extends ProviderManagerBuilder<B>, C extends AbstractDaoAuthenticationConfigurer<B, C, U>, U extends UserDetailsService>
		extends UserDetailsAwareConfigurer<B, U> {
	// 声明了一个 provider
	private DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
	// 声明了一个 userDetailsService 的泛型属性
	private final U userDetailsService;

	/**
	 * 创建一个实例
	 * @param userDetailsService,userDetailsService的类型可以是UserDetailsService或者UserDetailsPasswordService
	 */
	AbstractDaoAuthenticationConfigurer(U userDetailsService) {
		this.userDetailsService = userDetailsService;
		this.provider.setUserDetailsService(userDetailsService);
		if (userDetailsService instanceof UserDetailsPasswordService) {
			this.provider.setUserDetailsPasswordService((UserDetailsPasswordService) userDetailsService);
		}
	}

	/**
	 * Adds an {@link ObjectPostProcessor} for this class.
	 * @param objectPostProcessor
	 * @return the {@link AbstractDaoAuthenticationConfigurer} for further customizations
	 */
	@SuppressWarnings("unchecked")
	public C withObjectPostProcessor(ObjectPostProcessor<?> objectPostProcessor) {
		addObjectPostProcessor(objectPostProcessor);
		return (C) this;
	}

	/**
	 * Allows specifying the {@link PasswordEncoder} to use with the
	 * {@link DaoAuthenticationProvider}. The default is to use plain text.
	 * @param passwordEncoder The {@link PasswordEncoder} to use.
	 * @return the {@link AbstractDaoAuthenticationConfigurer} for further customizations
	 */
	@SuppressWarnings("unchecked")
	public C passwordEncoder(PasswordEncoder passwordEncoder) {
		this.provider.setPasswordEncoder(passwordEncoder);
		return (C) this;
	}

	public C userDetailsPasswordManager(UserDetailsPasswordService passwordManager) {
		this.provider.setUserDetailsPasswordService(passwordManager);
		return (C) this;
	}

	@Override
	public void configure(B builder) throws Exception {
		this.provider = postProcess(this.provider);
		// 向builder添加provider(配置构造器阶段)
		builder.authenticationProvider(this.provider);
	}

	/**
	 * Gets the {@link UserDetailsService} that is used with the
	 * {@link DaoAuthenticationProvider}
	 * @return the {@link UserDetailsService} that is used with the
	 * {@link DaoAuthenticationProvider}
	 */
	@Override
	public U getUserDetailsService() {
		return this.userDetailsService;
	}

}

通过源码我们可知:

  • AbstractDaoAuthenticationConfigurer初始时创建了一个DaoAuthenticationProvider类型的AuthenticationProvider实例。
  • 为使用者提供设置DaoAuthenticationProvider属性UserDetailsService的功能并指定类型为:UserDetailsService/U serDetailsPasswordService
  • 为使用者提供设置DaoAuthenticationProvider属性PasswordEncoder功能;
  • 为使用者提供设置对象后置处处理器的功能。
  • AbstractDaoAuthenticationConfigurer配置构造器对应的初始化阶段方法为空。
  • AbstractDaoAuthenticationConfigurer配置构造器对应的配置阶段方法:
    • DaoAuthenticationProvider执行后置处理
    • DaoAuthenticationProvider添加到构造器中

3.2 DaoAuthenticationConfigurer

DaoAuthenticationConfigurer继承自 AbstractDaoAuthenticationConfigurer,在构造方法中调用AbstractDaoAuthenticationConfigurer的构造方法。

/**
 * Allows configuring a {@link DaoAuthenticationProvider}
 *
 * @param <B> The type of {@link ProviderManagerBuilder} this is
 * @param <U> The type of {@link UserDetailsService} that is being used
 * @author Rob Winch
 * @since 3.2
 */
public class DaoAuthenticationConfigurer<B extends ProviderManagerBuilder<B>, U extends UserDetailsService>
		extends AbstractDaoAuthenticationConfigurer<B, DaoAuthenticationConfigurer<B, U>, U> {

	/**
	 * Creates a new instance
	 * @param userDetailsService
	 */
	public DaoAuthenticationConfigurer(U userDetailsService) {
		super(userDetailsService);
	}

}

3.3 UserDetailsServiceConfigurer

UserDetailsServiceConfigurer继承了AbstractDaoAuthenticationConfigurer,并重写了AbstractDaoAuthenticationConfigurer中的configure方法,在configure方法执行之前加入了initUserDetailsService方法,以方便开发时按照自己的方式去初始化 UserDetailsService。这里的initUserDetailsService方法是空的,会交于子类进行具体实现,也是模版模式。

/**
 * Allows configuring a {@link UserDetailsService} within a
 * {@link AuthenticationManagerBuilder}.
 *
 * @param <B> the type of the {@link ProviderManagerBuilder}
 * @param <C> the {@link UserDetailsServiceConfigurer} (or this)
 * @param <U> the type of UserDetailsService being used to allow for returning the
 * concrete UserDetailsService.
 * @author Rob Winch
 * @since 3.2
 */
public class UserDetailsServiceConfigurer<B extends ProviderManagerBuilder<B>, C extends UserDetailsServiceConfigurer<B, C, U>, U extends UserDetailsService>
		extends AbstractDaoAuthenticationConfigurer<B, C, U> {

	/**
	 * Creates a new instance
	 * @param userDetailsService the {@link UserDetailsService} that should be used
	 */
	public UserDetailsServiceConfigurer(U userDetailsService) {
		super(userDetailsService);
	}

	@Override
	public void configure(B builder) throws Exception {
		initUserDetailsService();
		super.configure(builder);
	}

	/**
	 * Allows subclasses to initialize the {@link UserDetailsService}. For example, it
	 * might add users, initialize schema, etc.
	 */
	protected void initUserDetailsService() throws Exception {
	}

}

3.4 UserDetailsManagerConfigurer

UserDetailsManagerConfigurer继承了UserDetailsServiceConfigurer,并实现了 UserDetailsServiceConfigurer中定义的initUserDetailsService空方法,具体的实现逻辑就是将UserDetailsBuilder所构建出来的 UserDetails以及提前准备好的UserDetails中的用户存储到UserDetailsService中。

该类同时添加 withUser方法用来添加用户,同时还增加了一个UserDetailsBuilder用来构建用户,这些逻辑都比较简单,大家可以自行查看。

/**
 * Base class for populating an
 * {@link org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder}
 * with a {@link UserDetailsManager}.
 *
 * @param <B> the type of the {@link SecurityBuilder} that is being configured
 * @param <C> the type of {@link UserDetailsManagerConfigurer}
 * @author Rob Winch
 * @since 3.2
 */
public class UserDetailsManagerConfigurer<B extends ProviderManagerBuilder<B>, C extends UserDetailsManagerConfigurer<B, C>>
		extends UserDetailsServiceConfigurer<B, C, UserDetailsManager> {

	private final List<UserDetailsBuilder> userBuilders = new ArrayList<>();

	private final List<UserDetails> users = new ArrayList<>();

	protected UserDetailsManagerConfigurer(UserDetailsManager userDetailsManager) {
		super(userDetailsManager);
	}

	/**
	 * Populates the users that have been added.
	 * @throws Exception
	 */
	@Override
	protected void initUserDetailsService() throws Exception {
		for (UserDetailsBuilder userBuilder : this.userBuilders) {
			getUserDetailsService().createUser(userBuilder.build());
		}
		for (UserDetails userDetails : this.users) {
			getUserDetailsService().createUser(userDetails);
		}
	}

	/**
	 * Allows adding a user to the {@link UserDetailsManager} that is being created. This
	 * method can be invoked multiple times to add multiple users.
	 * @param userDetails the user to add. Cannot be null.
	 * @return the {@link UserDetailsBuilder} for further customizations
	 */
	@SuppressWarnings("unchecked")
	public final C withUser(UserDetails userDetails) {
		this.users.add(userDetails);
		return (C) this;
	}

	/**
	 * Allows adding a user to the {@link UserDetailsManager} that is being created. This
	 * method can be invoked multiple times to add multiple users.
	 * @param userBuilder the user to add. Cannot be null.
	 * @return the {@link UserDetailsBuilder} for further customizations
	 */
	@SuppressWarnings("unchecked")
	public final C withUser(User.UserBuilder userBuilder) {
		this.users.add(userBuilder.build());
		return (C) this;
	}

	/**
	 * Allows adding a user to the {@link UserDetailsManager} that is being created. This
	 * method can be invoked multiple times to add multiple users.
	 * @param username the username for the user being added. Cannot be null.
	 * @return the {@link UserDetailsBuilder} for further customizations
	 */
	@SuppressWarnings("unchecked")
	public final UserDetailsBuilder withUser(String username) {
		UserDetailsBuilder userBuilder = new UserDetailsBuilder((C) this);
		userBuilder.username(username);
		this.userBuilders.add(userBuilder);
		return userBuilder;
	}

	/**
	 * Builds the user to be added. At minimum the username, password, and authorities
	 * should provided. The remaining attributes have reasonable defaults.
	 */
	public final class UserDetailsBuilder {

		private UserBuilder user;

		private final C builder;

		/**
		 * Creates a new instance
		 * @param builder the builder to return
		 */
		private UserDetailsBuilder(C builder) {
			this.builder = builder;
		}

		/**
		 * Returns the {@link UserDetailsManagerConfigurer} for method chaining (i.e. to
		 * add another user)
		 * @return the {@link UserDetailsManagerConfigurer} for method chaining
		 */
		public C and() {
			return this.builder;
		}

		/**
		 * Populates the username. This attribute is required.
		 * @param username the username. Cannot be null.
		 * @return the {@link UserDetailsBuilder} for method chaining (i.e. to populate
		 * additional attributes for this user)
		 */
		private UserDetailsBuilder username(String username) {
			this.user = User.withUsername(username);
			return this;
		}

		/**
		 * Populates the password. This attribute is required.
		 * @param password the password. Cannot be null.
		 * @return the {@link UserDetailsBuilder} for method chaining (i.e. to populate
		 * additional attributes for this user)
		 */
		public UserDetailsBuilder password(String password) {
			this.user.password(password);
			return this;
		}

		/**
		 * Populates the roles. This method is a shortcut for calling
		 * {@link #authorities(String...)}, but automatically prefixes each entry with
		 * "ROLE_". This means the following:
		 *
		 * <code>
		 *     builder.roles("USER","ADMIN");
		 * </code>
		 *
		 * is equivalent to
		 *
		 * <code>
		 *     builder.authorities("ROLE_USER","ROLE_ADMIN");
		 * </code>
		 *
		 * <p>
		 * This attribute is required, but can also be populated with
		 * {@link #authorities(String...)}.
		 * </p>
		 * @param roles the roles for this user (i.e. USER, ADMIN, etc). Cannot be null,
		 * contain null values or start with "ROLE_"
		 * @return the {@link UserDetailsBuilder} for method chaining (i.e. to populate
		 * additional attributes for this user)
		 */
		public UserDetailsBuilder roles(String... roles) {
			this.user.roles(roles);
			return this;
		}

		/**
		 * Populates the authorities. This attribute is required.
		 * @param authorities the authorities for this user. Cannot be null, or contain
		 * null values
		 * @return the {@link UserDetailsBuilder} for method chaining (i.e. to populate
		 * additional attributes for this user)
		 * @see #roles(String...)
		 */
		public UserDetailsBuilder authorities(GrantedAuthority... authorities) {
			this.user.authorities(authorities);
			return this;
		}

		/**
		 * Populates the authorities. This attribute is required.
		 * @param authorities the authorities for this user. Cannot be null, or contain
		 * null values
		 * @return the {@link UserDetailsBuilder} for method chaining (i.e. to populate
		 * additional attributes for this user)
		 * @see #roles(String...)
		 */
		public UserDetailsBuilder authorities(List<? extends GrantedAuthority> authorities) {
			this.user.authorities(authorities);
			return this;
		}

		/**
		 * Populates the authorities. This attribute is required.
		 * @param authorities the authorities for this user (i.e. ROLE_USER, ROLE_ADMIN,
		 * etc). Cannot be null, or contain null values
		 * @return the {@link UserDetailsBuilder} for method chaining (i.e. to populate
		 * additional attributes for this user)
		 * @see #roles(String...)
		 */
		public UserDetailsBuilder authorities(String... authorities) {
			this.user.authorities(authorities);
			return this;
		}

		/**
		 * Defines if the account is expired or not. Default is false.
		 * @param accountExpired true if the account is expired, false otherwise
		 * @return the {@link UserDetailsBuilder} for method chaining (i.e. to populate
		 * additional attributes for this user)
		 */
		public UserDetailsBuilder accountExpired(boolean accountExpired) {
			this.user.accountExpired(accountExpired);
			return this;
		}

		/**
		 * Defines if the account is locked or not. Default is false.
		 * @param accountLocked true if the account is locked, false otherwise
		 * @return the {@link UserDetailsBuilder} for method chaining (i.e. to populate
		 * additional attributes for this user)
		 */
		public UserDetailsBuilder accountLocked(boolean accountLocked) {
			this.user.accountLocked(accountLocked);
			return this;
		}

		/**
		 * Defines if the credentials are expired or not. Default is false.
		 * @param credentialsExpired true if the credentials are expired, false otherwise
		 * @return the {@link UserDetailsBuilder} for method chaining (i.e. to populate
		 * additional attributes for this user)
		 */
		public UserDetailsBuilder credentialsExpired(boolean credentialsExpired) {
			this.user.credentialsExpired(credentialsExpired);
			return this;
		}

		/**
		 * Defines if the account is disabled or not. Default is false.
		 * @param disabled true if the account is disabled, false otherwise
		 * @return the {@link UserDetailsBuilder} for method chaining (i.e. to populate
		 * additional attributes for this user)
		 */
		public UserDetailsBuilder disabled(boolean disabled) {
			this.user.disabled(disabled);
			return this;
		}

		UserDetails build() {
			return this.user.build();
		}

	}

}

3.5 JdbcUserDetailsManagerConfigurer

JdbcUserDetailsManagerConfigurer继承了UserDetailsManagerConfigurer,在父类的基础上补充了 DataSource对象,同时还提供了相应的数据库查询方法。

/**
 * Configures an
 * {@link org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder}
 * to have JDBC authentication. It also allows easily adding users to the database used
 * for authentication and setting up the schema.
 *
 * <p>
 * The only required method is the {@link #dataSource(javax.sql.DataSource)} all other
 * methods have reasonable defaults.
 *
 * @param <B> the type of the {@link ProviderManagerBuilder} that is being configured
 * @author Rob Winch
 * @since 3.2
 */
public class JdbcUserDetailsManagerConfigurer<B extends ProviderManagerBuilder<B>>
		extends UserDetailsManagerConfigurer<B, JdbcUserDetailsManagerConfigurer<B>> {

	private DataSource dataSource;

	private List<Resource> initScripts = new ArrayList<>();

	public JdbcUserDetailsManagerConfigurer(JdbcUserDetailsManager manager) {
		super(manager);
	}

	public JdbcUserDetailsManagerConfigurer() {
		this(new JdbcUserDetailsManager());
	}

	/**
	 * Populates the {@link DataSource} to be used. This is the only required attribute.
	 * @param dataSource the {@link DataSource} to be used. Cannot be null.
	 * @return The {@link JdbcUserDetailsManagerConfigurer} used for additional
	 * customizations
	 */
	public JdbcUserDetailsManagerConfigurer<B> dataSource(DataSource dataSource) {
		this.dataSource = dataSource;
		getUserDetailsService().setDataSource(dataSource);
		return this;
	}

	/**
	 * Sets the query to be used for finding a user by their username. For example:
	 *
	 * <code>
	 *     select username,password,enabled from users where username = ?
	 * </code>
	 * @param query The query to use for selecting the username, password, and if the user
	 * is enabled by username. Must contain a single parameter for the username.
	 * @return The {@link JdbcUserDetailsManagerConfigurer} used for additional
	 * customizations
	 */
	public JdbcUserDetailsManagerConfigurer<B> usersByUsernameQuery(String query) {
		getUserDetailsService().setUsersByUsernameQuery(query);
		return this;
	}

	/**
	 * Sets the query to be used for finding a user's authorities by their username. For
	 * example:
	 *
	 * <code>
	 *     select username,authority from authorities where username = ?
	 * </code>
	 * @param query The query to use for selecting the username, authority by username.
	 * Must contain a single parameter for the username.
	 * @return The {@link JdbcUserDetailsManagerConfigurer} used for additional
	 * customizations
	 */
	public JdbcUserDetailsManagerConfigurer<B> authoritiesByUsernameQuery(String query) {
		getUserDetailsService().setAuthoritiesByUsernameQuery(query);
		return this;
	}

	/**
	 * An SQL statement to query user's group authorities given a username. For example:
	 *
	 * <code>
	 *     select
	 *         g.id, g.group_name, ga.authority
	 *     from
	 *         groups g, group_members gm, group_authorities ga
	 *     where
	 *         gm.username = ? and g.id = ga.group_id and g.id = gm.group_id
	 * </code>
	 * @param query The query to use for selecting the authorities by group. Must contain
	 * a single parameter for the username.
	 * @return The {@link JdbcUserDetailsManagerConfigurer} used for additional
	 * customizations
	 */
	public JdbcUserDetailsManagerConfigurer<B> groupAuthoritiesByUsername(String query) {
		JdbcUserDetailsManager userDetailsService = getUserDetailsService();
		userDetailsService.setEnableGroups(true);
		userDetailsService.setGroupAuthoritiesByUsernameQuery(query);
		return this;
	}

	/**
	 * A non-empty string prefix that will be added to role strings loaded from persistent
	 * storage (default is "").
	 * @param rolePrefix
	 * @return The {@link JdbcUserDetailsManagerConfigurer} used for additional
	 * customizations
	 */
	public JdbcUserDetailsManagerConfigurer<B> rolePrefix(String rolePrefix) {
		getUserDetailsService().setRolePrefix(rolePrefix);
		return this;
	}

	/**
	 * Defines the {@link UserCache} to use
	 * @param userCache the {@link UserCache} to use
	 * @return the {@link JdbcUserDetailsManagerConfigurer} for further customizations
	 */
	public JdbcUserDetailsManagerConfigurer<B> userCache(UserCache userCache) {
		getUserDetailsService().setUserCache(userCache);
		return this;
	}

	@Override
	protected void initUserDetailsService() throws Exception {
		if (!this.initScripts.isEmpty()) {
			getDataSourceInit().afterPropertiesSet();
		}
		super.initUserDetailsService();
	}

	@Override
	public JdbcUserDetailsManager getUserDetailsService() {
		return (JdbcUserDetailsManager) super.getUserDetailsService();
	}

	/**
	 * Populates the default schema that allows users and authorities to be stored.
	 * @return The {@link JdbcUserDetailsManagerConfigurer} used for additional
	 * customizations
	 */
	public JdbcUserDetailsManagerConfigurer<B> withDefaultSchema() {
		this.initScripts.add(new ClassPathResource("org/springframework/security/core/userdetails/jdbc/users.ddl"));
		return this;
	}

	protected DatabasePopulator getDatabasePopulator() {
		ResourceDatabasePopulator dbp = new ResourceDatabasePopulator();
		dbp.setScripts(this.initScripts.toArray(new Resource[0]));
		return dbp;
	}

	private DataSourceInitializer getDataSourceInit() {
		DataSourceInitializer dsi = new DataSourceInitializer();
		dsi.setDatabasePopulator(getDatabasePopulator());
		dsi.setDataSource(this.dataSource);
		return dsi;
	}

}

在实例构造上进一步限制了父类中的U userDetailsService的类型为JdbcUserDetailsManager

JdbcUserDetailsManager的继承关系图:

在这里插入图片描述

3.6 InMemoryUserDetailsManagerConfigurer

InMemoryUserDetailsManagerConfigurer继承了UserDetailsManagerConfigurer,在实例构造上进一步限制了父类中的U userDetailsService的类型为InMemoryUserDetailsManager

/**
 * Configures an
 * {@link org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder}
 * to have in memory authentication. It also allows easily adding users to the in memory
 * authentication.
 *
 * @param <B> the type of the {@link ProviderManagerBuilder} that is being configured
 * @author Rob Winch
 * @since 3.2
 */
public class InMemoryUserDetailsManagerConfigurer<B extends ProviderManagerBuilder<B>>
		extends UserDetailsManagerConfigurer<B, InMemoryUserDetailsManagerConfigurer<B>> {

	/**
	 * Creates a new instance
	 */
	public InMemoryUserDetailsManagerConfigurer() {
		super(new InMemoryUserDetailsManager(new ArrayList<>()));
	}

}

InMemoryUserDetailsManager的继承关系图:

在这里插入图片描述

未完待续~~~~!!!!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

神经内科临床常用的焦虑、抑郁评估量表,医生必备!

根据神经内科医生的量表使用情况&#xff0c;常笑医学整理了神经内科临床上常用的焦虑、抑郁评估量表&#xff0c;为大家分享临床常见的焦虑、抑郁、睡眠等量表评估内容&#xff0c;均支持量表下载和在线使用&#xff0c;建议收藏&#xff01; 1.汉密顿抑郁量表&#xff08;Ham…

从零开发短视频电商 AWS OpenSearch Service开发环境申请以及Java客户端介绍

文章目录 创建域1.创建域2.输入配置部署选项数据节点网络精细访问控制访问策略 获取域端点数据如何插入到OpenSearch ServiceJava连接OpenSearch Servicespring-data-opensearchelasticsearch-rest-high-level-clientopensearch-rest-clientopensearch-java 因为是开发测试使用…

喜讯!云起无垠上榜《成长型初创企业推荐10强》

近期&#xff0c;由中国计算机学会抗恶劣环境计算机专业委员会、信息产业信息安全测评中心和安全牛联合发起的第十一版《中国网络安全企业100强》榜单正式发布。在这份备受关注的榜单中&#xff0c;云起无垠凭借其创新的技术能力&#xff0c;荣登《成长型初创企业推荐10强》榜单…

echarts绘制一个环形图

其他echarts&#xff1a; echarts绘制一个柱状图&#xff0c;柱状折线图 echarts绘制一个饼图 echarts绘制一个环形图2 效果图&#xff1a; 代码&#xff1a; <template><div class"wrapper"><!-- 环形图 --><div ref"doughnutChart…

CCKS2023-面向金融领域的主体事件检测-亚军方案分享

赛题分析 大赛地址 https://tianchi.aliyun.com/competition/entrance/532098/introduction?spma2c22.12281925.0.0.52b97137bpVnmh 任务描述 主体事件检测是语言文本分析和金融领域智能应用的重要任务之一&#xff0c;如在金融风控领域往往会对公司主体进行风险事件的检测…

【C#设计模式 + Filter】装饰器模式专项——过滤器

c# 装饰器模式专项——过滤器 装饰器模式专项——过滤器Filter1.winform实现通过特性改控件名称&#xff08;.Framework)2.手写过滤器 (.NET Core) 装饰器模式专项——过滤器Filter 左边为api启动流程。 右边为需要实现的winform启动流程。右边大框里面需要我们手动实现。 1.wi…

python pyaudio实时读取音频数据并展示波形图

python pyaudio实时读取音频数据并展示波形图 下面代码可以驱动电脑接受声音数据&#xff0c;并实时展示音波图&#xff1a; import numpy as np import matplotlib.pyplot as plt import matplotlib.animation as animation import pyaudio import wave import os import op…

Michael.W基于Foundry精读Openzeppelin第41期——ERC20Capped.sol

Michael.W基于Foundry精读Openzeppelin第41期——ERC20Capped.sol 0. 版本0.1 ERC20Capped.sol 1. 目标合约2. 代码精读2.1 constructor() && cap()2.2 _mint(address account, uint256 amount) 0. 版本 [openzeppelin]&#xff1a;v4.8.3&#xff0c;[forge-std]&…

2023年全球软件开发大会(QCon广州站2023)-核心PPT资料下载

一、峰会简介 本次峰会包含&#xff1a;泛娱乐时代的边缘计算与通讯、稳定性即生命线、下一代软件架构、出海的思考、现代数据架构、AGI 与 AIGC 落地、大前端技术探索、编程语言实战、DevOps vs 平台工程、新型数据库、AIGC 浪潮下的企业出海、AIGC 浪潮下的效能智能化、数据…

使用Rust 构建C 组件

协议解析&#xff0c;这不就很快了&#xff0c;而且原生的标准库红黑树和avl 树支持&#xff0c;异步tokio 这些库&#xff0c;编写应用组件就很快了 rust 标准库不支持 unix 的消息队列&#xff0c;但是支持 shm 和 uds&#xff0c;后者从多方面考虑都比&#xff0c;消息队列更…

python爬取 HTTP_2 网站超时问题的解决方案

问题背景 在进行网络数据爬取时&#xff0c;使用 Python 程序访问支持 HTTP/2 协议的网站时&#xff0c;有时会遇到超时问题。这可能会导致数据获取不完整&#xff0c;影响爬虫程序的正常运行。 问题描述 在实际操作中&#xff0c;当使用 Python 编写的爬虫程序访问支持 HTT…

SAP物料会计视图的价格确定MLAST为空后台补录

物料10013812 工厂会计视图的价格确定为空&#xff0c;前台目前无法修改&#xff0c;申请修改底表&#xff0c;将价格确定调整为2 解决&#xff1a; 该字段涉及&#xff1a;MBEW表和CKMLHD表的MLAST字段两个地方&#xff0c;经修改后&#xff0c;前后台数据一致。 只改技术信…

2023_Spark_实验二十五:SparkStreaming读取Kafka数据源:使用Direct方式

SparkStreaming读取Kafka数据源&#xff1a;使用Direct方式 一、前提工作 安装了zookeeper 安装了Kafka 实验环境&#xff1a;kafka zookeeper spark 实验流程 二、实验内容 实验要求&#xff1a;实现的从kafka读取实现wordcount程序 启动zookeeper zk.sh start# zk.sh…

性能测试(超详细)

近期公司为了节省成本搞了一波机房迁移&#xff0c;整合了一些南美部署架构。有一些上google云和有些下阿里云等大的调整。 在做机房迁移项目当中就需要思考如何进行性能测试&#xff0c;这种大的机房迁移SRE&#xff08;运维&#xff09;会针对组件会做一些单组件的性能测试&a…

服务器配置免密SSH

在当今互联网时代&#xff0c;远程工作和网络安全已成为信息技术领域的热点话题。无论是管理远程服务器、维护网络设备还是简单地从家中连接到办公室&#xff0c;安全始终是首要考虑的因素。这就是为什么 SSH&#xff08;Secure Shell&#xff09;成为了网络专业人士的首选工具…

【FreeRTOS】信号量——简介、常用API函数、注意事项、项目实现

在FreeRTOS中&#xff0c;信号量是一种非常重要的同步机制&#xff0c;用于实现任务间的互斥访问和同步操作。通过信号量&#xff0c;不同的任务可以安全地共享资源&#xff0c;避免竞争和冲突&#xff0c;从而确保系统的稳定性和可靠性。本篇博客将介绍FreeRTOS中信号量的基本…

MacBook 逆水寒下载安装使用教程,支持最新版本 MacOS 流畅不闪退

最近 MacBook 系统更新到了 MacOS 14.1 很多朋友的逆水寒玩不了了&#xff0c;我尝试了一番可以正常玩了&#xff0c;看图&#xff1a; 其实操作也很简单&#xff0c;我们从头开始&#xff0c;因为 MacOS 系统的更新所以我们也需要更新新版本的 playCover 来适配新的系统&#…

SpringSecurity(二)

SpringSecurity源码的初探 一、SpringSecurity中的核心组件 在SpringSecurity中的jar分为4个&#xff0c;作用分别为 jar作用spring-security-coreSpringSecurity的核心jar包&#xff0c;认证和授权的核心代码都在这里面spring-security-config如果使用Spring Security XML名称…

速查!软考出成绩了

2023年11月软考成绩出来啦&#xff01;大家赶紧查一下&#xff0c;各科都45分就是通过&#xff01; 01 如何查成绩 1、打开“中国计算机技术职业资格网”&#xff0c;网址&#xff1a;https://www.ruankao.org.cn/ 2、点击↘的“成绩查询”按钮。 3、输入“手机号/证件号密码验…

C //例10.3 从键盘读入若干个字符串,对它们按字母大小的顺序排序,然后把排好序的字符串送到磁盘文件中保存。

C程序设计 &#xff08;第四版&#xff09; 谭浩强 例10.3 例10.3 从键盘读入若干个字符串&#xff0c;对它们按字母大小的顺序排序&#xff0c;然后把排好序的字符串送到磁盘文件中保存。 IDE工具&#xff1a;VS2010 Note: 使用不同的IDE工具可能有部分差异。 代码块 方法…
最新文章