SpringSecurity原理解析(五):HttpSecurity 类处理流程

news2024/9/25 5:23:22

1、SpringSecurity 在spring boot中与SSM项目中基于配置文件的区别

      通过前边的笔记我们可以知道,在传统的SSM项目中 SpringSecurity的使用是基于配置文件

      的,然后spring 容器初始化的时候将 SpringSecurity 中的各种标签解析成对应的Bean对象,

      SpringSecurity 配置文件如下所示:

<!-- SpringSecurity配置文件 -->
        <!--
             auto-config:表示自动加载SpringSecurity的配置文件
             use-expressions:表示使用Spring的EL表达式
         -->
        <security:http auto-config="true" use-expressions="true">
            <!--定义匿名访问,跳转到登录页面 -->
            <security:intercept-url pattern="/login.jsp" access="permitAll()"/>
            <!--
                拦截资源
                pattern="/**" 拦截所有的资源
                access="hasAnyRole('ROLE_USER')" 表示只有ROLE_USER 这个角色可以访问资源
             -->
            <security:intercept-url pattern="/**" access="hasAnyRole('ROLE_USER')" ></security:intercept-url>

            <!--
                配置认证(用户登录)信息,覆盖security 默认的登录页面
                 login-page:登录页面地址
                 login-processing-url:登录的请求url
                 default-target-url:登录成功皇后的目标地址
                 authentication-failure-url:登录校验失败后的地址
             -->
            <security:form-login login-page="/login.jsp" login-processing-url="/login" default-target-url="/home.jsp" authentication-failure-url="/error.jsp"/>

            <!--开启csrf校验 -->
            <security:csrf disabled="true"/>

            <!--
                开启“记住我” 登录用户缓存功能,该功能默认是关闭的,需要手动开启
                remember-me-parameter 是登录页面配置的 “记住我” 功能的属性名称,如login.jsp 中的 "remember-me"
                token-validity-seconds 设置 “记住我” 登录的数据保存的超时时间,
                注意:当前 这种配置只是把“登录数据” 临时保存在页面的Cookie(token) 中,保存在页面中的数据安全性很差,很容故意被盗取;
                     为了解决这个问题 spring security提供了把“记住我” 功能的数据保存到数据库中,需要在 <security:remember-me>
                     中添加配置属性 data-source-ref,并 指定数据源,如:data-source-ref="dataSource"
            -->
            <security:remember-me
                    token-validity-seconds="1200"
                    data-source-ref="dataSource"
                    remember-me-parameter="remember-me"/>

            <!--自定义错误页面 -->
            <security:access-denied-handler error-page="error.jsp"/>

        </security:http>

        <!--向IOC容器注入一个bean-->
        <bean class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" id="bCryptPasswordEncoder"/>

        <!-- 认证用户信息 -->
        <security:authentication-manager>
           <security:authentication-provider user-service-ref="userServiceImpl"><!--指向自己定义的认证service,在service 中根据登录用户与数据库的数据进行用户认证处理,这样保密性比较好 -->
               <!--
               <security:user-service >
                  设置一个账号 zhangsan 密码123 {noop} 表示不加密 具有的角色是  ROLE_USER
                 <security:user name="zhangsan" authorities="ROLE_USER" password="{noop}123" ></security:user>
                 <security:user name="lisi" authorities="ROLE_USER" password="{noop}123456" ></security:user>
               </security:user-service>
            -->
               <!--引入用户认证密码加密方式 -->
               <security:password-encoder ref="bCryptPasswordEncoder"></security:password-encoder>
           </security:authentication-provider>
        </security:authentication-manager>

      也就是在配置文件中通过 security:http 等标签来定义了认证需要的相关信息,但是在

      SpringBoot项目中,我们慢慢脱离了xml配置文件的方式,通过配置类的方式来配置

      SpringSecurity。SpringSecurity配置类需要继承类 WebSecurityConfigurerAdapter,并重写

      configure(AuthenticationManagerBuilder auth) 和 configure(HttpSecurity http) 方法;

       SpringSecurity 配置类如下所示:     

/**
 * SpringSecurity的配置文件
 * WebSecurityCofniguration中 @Bean注解 把 FilterChainProxy 注入到了容器中 而且名称为springSecurityFilterChain
 * 而 FilterChainProxy 对象是通过 WebSecurity 构建的
 *
 * @EnableWebSecurity
 */
@Configuration
@EnableWebSecurity
public class SpringSecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserService userService;

    @Autowired
    private BCryptPasswordEncoder bCryptPasswordEncoder;

    @Autowired
    private PersistentTokenRepository persistentTokenRepository;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userService)// 数据库认证,绑定需要执行用户认证操作的service
            .passwordEncoder(bCryptPasswordEncoder); // 设置加密处理的方式
        //auth.inMemoryAuthentication().withUser("root").password("123")
    }

    /**
     * 容器中注入 BCryptPasswordEncoder
     * @return
     */
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder(){
        return new BCryptPasswordEncoder();
    }

    /**
     * HttpSecurity 相当于 SpringSecurity配置文件中 http 标签
     *
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()

                //匿名访问资源
                .antMatchers("/login.html","/css/**","/img/**")
                // 配置需要放过的资源,表示前边 antMatchers 配置的资源都需要放过
                .permitAll()
                .antMatchers("/**")
                //认证的资源及所具备的权限
                .hasAnyRole("USER")
                .anyRequest()//表示所有请求都需要认证
                .authenticated()//需要认证
                //and() 返回一个HttpSecurity
                .and()
                // 配置登录表单相关的信息
                .formLogin()
                // 指定自定义的登录页面
                //todo 注意:
                //      对于前后端分离的项目,不需要指定跳转的页面,只需要 loginProcessingUrl
                //      设置请求资源url就行了
                .loginPage("/login.html") //认证表单相关信息
                .loginProcessingUrl("/login") // 表单提交的登录地址
                .defaultSuccessUrl("/home.html")
                //表示上边与form表单提交得资源都要放过
                .permitAll()
                .and()
                .rememberMe() // 放开 记住我 的功能
                .tokenRepository(persistentTokenRepository) // 持久化
                .and()
                //csrf设置
                .csrf()
                .disable();

        HttpSecurity http1 = http.authorizeRequests()
                // 配置需要放过的资源
                .antMatchers("/login.html", "/css/**", "/img/**")
                .permitAll()//表示放过前边 antMatchers 配置得资源
                .antMatchers("/**")
                .hasAnyRole("USER")
                .anyRequest()
                .authenticated()
                .and();
    }

    /**
     * 向Spring容器中注入 PersistentTokenRepository 对象
     * @param dataSource
     * @return
     */
    @Bean
    public PersistentTokenRepository persistentTokenRepository(DataSource dataSource){
        JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
        //绑定数据源
        tokenRepository.setDataSource(dataSource);
        return tokenRepository;
    }

    public static void main(String[] args) {
        BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
        String password = "admin";
        // 每次都会生成一个随机的salt,同一个明文加密多次编码得到的密文其实都不一样
        System.out.println(encoder.encode(password));
        System.out.println(encoder.encode(password));
        System.out.println(encoder.encode(password));
    }
}

       在SpringSecurity中提供了HttpSecurity等工具类,这里HttpSecurity就等同于我们在配置

      文件中定义的<security:http>标签,而。

      通过代码结果来看和配置文件的效果是一样的。基于配置文件的方式我们之前分析过,是通过

     标签对应的handler来解析处理的,那么HttpSecurity这块是如何处理的呢?

2、HttpSecurity 类的处理过程

2.1、HttpSecurity 类图:

         

         由该类图可以清晰得发现,HttpSecurity 继承了父类 AbstractConfiguredSecurityBuilder

         并实现了接口 SecurityBuilder 和 HttpSecurityBuilder

         HttpSecurity 类的定义如下:

                

2.2、SecurityBuilder 接口

         SecurityBuilder 定义如下:

public interface SecurityBuilder<O> {
    //构建 SecurityBuilder 指定泛型类型的对象
    O build() throws Exception;
}

         由接口 SecurityBuilder 的定义可以发现,SecurityBuilder 接口只提供了一个 build() 方法,用

         来构建 SecurityBuilder 泛型指定类型的bean对象。

         结合 HttpSecurity 类中实现 SecurityBuilder 接口时的泛型是什么,就知道在 HttpSecurity 类

         中 SecurityBuilder 是用来创建什么对象,HttpSecurity 定义如下:

                   

          由 HttpSecurity 的定义可以发现,在 HttpSecurity 类中 SecurityBuilder 指定的泛型是

          DefaultSecurityFilterChain,DefaultSecurityFilterChain 是拦截器链SecurityFilterChain

          一个默认实现,所以 DefaultSecurityFilterChain 是一个拦截器链,所以在 HttpSecurity

          中,SecurityBuilder 是用来创建拦截器链的。

2.2.1、SecurityBuilder.build() 方法的实现

            下面看下 SecurityBuilder.build() 的实现过程,及连接器链的创建过程

             SecurityBuilder 的默认实现是类 AbstractSecurityBuilder

             SecurityBuilder.build() 方法的实现如下:

public abstract class AbstractSecurityBuilder<O> implements SecurityBuilder<O> {
    //CAS类型
    private AtomicBoolean building = new AtomicBoolean();
    //返回创建的O的对象
    private O object;

    public AbstractSecurityBuilder() {
    }

    //创建泛型O的对象
    public final O build() throws Exception {
        //基于CAS,保证在整个环境中O只被创建一次
        if (this.building.compareAndSet(false, true)) {
            //真正创建泛型O的对象
            this.object = this.doBuild();
            return this.object;
        } else {
            throw new AlreadyBuiltException("This object has already been built");
        }
    }

    //返回O的对象
    public final O getObject() {
        if (!this.building.get()) {
            throw new IllegalStateException("This object has not been built");
        } else {
            return this.object;
        }
    }

    //抽象方法,由子类实现
    protected abstract O doBuild() throws Exception;
}

             由 AbstractSecurityBuilder 的定义可以发现,真正创建泛型O(在 HttpSecurity 中O是

             拦截器链 DefaultSecurityFilterChain )的对象是在doBuild 发给发中完成,而doBuild是

             一个抽象方法,由子类 AbstractConfiguredSecurityBuilder 实现,

             doBuild 方法实现如下:

protected final O doBuild() throws Exception {
        synchronized(this.configurers) {
            this.buildState = AbstractConfiguredSecurityBuilder.BuildState.INITIALIZING;
            this.beforeInit();
            //执行当前类的init方法进行初始化操作
            this.init();
            this.buildState = AbstractConfiguredSecurityBuilder.BuildState.CONFIGURING;
            this.beforeConfigure();
            this.configure();
            this.buildState = AbstractConfiguredSecurityBuilder.BuildState.BUILDING;
            //获取构建的对象,上面的方法可以先忽略
            O result = this.performBuild();
            this.buildState = AbstractConfiguredSecurityBuilder.BuildState.BUILT;
            return result;
        }
    }

             performBuild() 是一个抽象方法,由AuthenticationManagerBuilder、HttpSecurity、

             WebSecurity 三个子类实现,在这里我们应该看 HttpSecurity 中的实现

             HttpSecurity.performBuild() 方法的实现如下:        

@Override
	protected DefaultSecurityFilterChain performBuild() {
        //filters:保存所有的过滤器
        // 对所有的过滤器做排序
		this.filters.sort(OrderComparator.INSTANCE);
		List<Filter> sortedFilters = new ArrayList<>(this.filters.size());
		for (Filter filter : this.filters) {
			sortedFilters.add(((OrderedFilter) filter).filter);
		}
		// 然后生成 DefaultSecurityFilterChain
		return new DefaultSecurityFilterChain(this.requestMatcher, sortedFilters);
	}

             DefaultSecurityFilterChain 构造方法如下:

public DefaultSecurityFilterChain(RequestMatcher requestMatcher, List<Filter> filters) {
        if (!filters.isEmpty()) {
            logger.info(LogMessage.format("Will not secure %s", requestMatcher));
        } else {
            logger.info(LogMessage.format("Will secure %s with %s", requestMatcher, filters));
        }
      
        //绑定请求匹配规则
        this.requestMatcher = requestMatcher;
        //绑定过滤器集合
        this.filters = new ArrayList(filters);
    }

             在HttpSecurity构造方法中绑定了对应的请求匹配器和过滤器集合。

                     

             对应的请求匹配器则是 AnyRequestMatcher 匹配所有的请求。当然我们会比较关心

            默认的过滤器链中的过滤器是哪来的,这块儿我们继续来分析

2.3、AbstractConfiguredSecurityBuilder

        AbstractConfiguredSecurityBuilder 是一个抽象类,也可以看成是 SecurityBuilder 接口的

        实现,AbstractConfiguredSecurityBuilder 类图如下所示:

               

               

类型功能
SecurityBuilder声明了build方法
AbstractSecurityBuilder提供了获取对象的方法以及控制一个对象只能build一次
AbstractConfiguredSecurityBuilder除了提供对对象细粒度的控制外还扩展了对configurer的操作

       AbstractConfiguredSecurityBuilder 对应的三个实现类,如下所示:

                   

2.3.1、BuildState

            AbstractConfiguredSecurityBuilder 中定义了一个枚举类BuildState,将整个构建过程

            分为 5 种状态,也可 以理解为构建过程生命周期的五个阶段,通过这些阶段来管理需

           要构建的对象的不同阶段 如下:   

private enum BuildState {

		/**
		 * 还没开始构建
		 */
		UNBUILT(0),

		/**
		 * 构建中
		 */
		INITIALIZING(1),

		/**
		 * 配置中
		 */
		CONFIGURING(2),

		/**
		 * 构建中
		 */
		BUILDING(3),

		/**
		 * 构建完成
		 */
		BUILT(4);

		private final int order;

		BuildState(int order) {
			this.order = order;
		}

		public boolean isInitializing() {
			return INITIALIZING.order == this.order;
		}

		/**
		 * Determines if the state is CONFIGURING or later
		 * @return
		 */
		public boolean isConfigured() {
			return this.order >= CONFIGURING.order;
		}

	}

          

2.3.2、AbstractConfiguredSecurityBuilder 常见方法

2.3.2.1、add() 方法

              add 方法,这相当于是在收集所有的配置类。将所有的 xxxConfigure 收集起来存储到

             configurers 中,将来再统一初始化并配置,configurers 本身是一个 LinkedHashMap ,

             key 是配置类的 class, value 是一个集合,集合里边放着 xxxConfigure 配置类。当

             需要对这些配置类进行集中配置的时候, 会通过 getConfigurers 方法获取配置类,

              这个获取过程就是把 LinkedHashMap 中的 value 拿出来, 放到一个集合中返回。

              add 方法代码如下:        

private <C extends SecurityConfigurer<O, B>> void add(C configurer) {
        Assert.notNull(configurer, "configurer cannot be null");
        //configurer必须是 SecurityConfigurer 的子类
        Class<? extends SecurityConfigurer<O, B>> clazz = configurer.getClass();
        synchronized(this.configurers) {
            if (this.buildState.isConfigured()) {
                throw new IllegalStateException("Cannot apply " + configurer + " to already built object");
            } else {
                List<SecurityConfigurer<O, B>> configs = null;
                if (this.allowConfigurersOfSameType) {
                    configs = (List)this.configurers.get(clazz);
                }

                List<SecurityConfigurer<O, B>> configs = configs != null ? configs : new ArrayList(1);
                ((List)configs).add(configurer);
                this.configurers.put(clazz, configs);
                if (this.buildState.isInitializing()) {
                    this.configurersAddedInInitializing.add(configurer);
                }

            }
        }
    }

//获取指定的配置类
@SuppressWarnings("unchecked")
	public <C extends SecurityConfigurer<O, B>> List<C> getConfigurers(Class<C> clazz) {
		List<C> configs = (List<C>) this.configurers.get(clazz);
		if (configs == null) {
			return new ArrayList<>();
		}
		return new ArrayList<>(configs);
	}

2.3.2.2、doBuild()方法      

@Override
	protected final O doBuild() throws Exception {
		synchronized (this.configurers) {
			this.buildState = BuildState.INITIALIZING;
			beforeInit(); //是一个预留方法,没有任何实现
			init(); // 就是找到所有的 xxxConfigure,挨个调用其 init 方法进行初始化,完成默认过滤器的初始化
			this.buildState = BuildState.CONFIGURING;
			beforeConfigure(); // 是一个预留方法,没有任何实现
			configure(); // 就是找到所有的 xxxConfigure,挨个调用其 configure 方法进行配置。
			this.buildState = BuildState.BUILDING;
			O result = performBuild();
// 是真正的过滤器链构建方法,但是在 AbstractConfiguredSecurityBuilder中 performBuild 方法只是一个抽象方法,具体的实现在 HttpSecurity 中
			this.buildState = BuildState.BUILT;
			return result;
		}
	}

 init方法:完成所有相关过滤器的初始化

private void init() throws Exception {
		Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();
		for (SecurityConfigurer<O, B> configurer : configurers) {
			configurer.init((B) this); // 初始化对应的过滤器
		}
		for (SecurityConfigurer<O, B> configurer : this.configurersAddedInInitializing) {
			configurer.init((B) this);
		}
	}

configure方法:完成HttpSecurity和对应的过滤器的绑定。

private void configure() throws Exception {
		Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();
		for (SecurityConfigurer<O, B> configurer : configurers) {
			configurer.configure((B) this);
		}
	}

2.4、HttpSecurity

         HttpSecurity 做的事情,就是对各种各样的 xxxConfigurer 进行配置;

         HttpSecurity 部分方法列表如下:

                

         HttpSecurity 中有大量类似的方法,过滤器链中的过滤器就是这样一个一个配置的。我们

          就不一一介绍 了。每个配置方法的结尾都会调用一次 getOrApply 方法,getOrApply

         方法是做什么的?

         getOrApply 方法如下:

	private <C extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity>> C getOrApply(C configurer)
			throws Exception {
		C existingConfig = (C) getConfigurer(configurer.getClass());
		if (existingConfig != null) {
			return existingConfig;
		}
		return apply(configurer);
	}

          getConfigurer 方法是在它的父类 AbstractConfiguredSecurityBuilder 中定义的,目的就

          是去查看当前 这个 xxxConfigurer 是否已经配置过了。   

          如果当前 xxxConfigurer 已经配置过了,则直接返回,否则调用 apply 方法,这个 apply

           方法最终会调 用到 AbstractConfiguredSecurityBuilder#add 方法,将当前配置

          configurer 收集起来 HttpSecurity 中还有一个 addFilter 方法.

          addFilter 方法如下所示:

@Override
	public HttpSecurity addFilter(Filter filter) {
		Integer order = this.filterOrders.getOrder(filter.getClass());
		if (order == null) {
			throw new IllegalArgumentException("The Filter class " + filter.getClass().getName()
					+ " does not have a registered order and cannot be added without a specified order. Consider using addFilterBefore or addFilterAfter instead.");
		}
		this.filters.add(new OrderedFilter(filter, order));
		return this;
	}

          这个 addFilter 方法的作用,主要是在各个 xxxConfigurer 进行配置的时候,会调用到这

         个方法, (xxxConfigurer 就是用来配置过滤器的),把 Filter 都添加到 fitlers 变量中。

3、总结

      这就是 HttpSecurity 的一个大致工作流程。把握住了这个工作流程,剩下的就只是一些简

      单的重 复的 xxxConfigurer 配置了

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

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

相关文章

Cortex-M3架构学习:

异常类型 Cortex-M3 在内核水平上搭载了一个异常响应系统&#xff0c;支持为数众多的系统异常和外部中断。其 中&#xff0c;编号为 1 &#xff0d; 15 的对应系统异常&#xff0c;大于等于 16 的则全是外部中断。 Cortex-M3支持的中断源数目为 240 个&#xff0c;做成芯片后…

TensorFlow深度学习框架改进K-means、SOM自组织映射聚类算法及上海招生政策影响分析研究|附代码数据

全文链接&#xff1a;https://tecdat.cn/?p37652 原文出处&#xff1a;拓端数据部落公众号 分析师&#xff1a;Chen Zhang 在教育政策研究领域&#xff0c;准确评估政策对不同区域和学生群体的影响至关重要。2021 年上海市出台的《上海市初中学业水平考试实施办法》对招生…

PDF转Excel小达人养成记

在现代职场&#xff0c;数据管理与格式转换可谓是日常任务的重头戏&#xff1b;有时我们手头有一份PDF文件&#xff0c;但需要将其中的数据整理成Excel表格&#xff0c;这该如何是好&#xff1f;别急&#xff0c;今天我就来给大家介绍几款好用的PDF转Excel工具&#xff0c;以及…

使用您自己的图像微调 FLUX.1 LORA 并使用 Azure 机器学习进行部署

目录 介绍 了解 Flux.1 模型系列 什么是 Dreambooth&#xff1f; 先决条件 使用 Dreambooth 微调 Flux 的步骤 步骤 1&#xff1a;设置环境 第 2 步&#xff1a;加载库 步骤 3&#xff1a;准备数据集 3.1 通过 AML 数据资产&#xff08;URI 文件夹&#xff09;将图像上传到…

minio集群

1 集群部署 minio集群的搭建并不复杂&#xff0c;别人也有很多的例子&#xff0c;这里只是先把自己的集群搭建记录下来&#xff0c;重点是后面的章节&#xff0c;遇到问题如何解决。 1.1 修改主机名 hostnamectl set-hostname minio1 hostnamectl set-hostname minio2 hostna…

【深度学习】训练过程中一个OOM的问题,太难查了

现象&#xff1a; 各位大佬又遇到过ubuntu的这个问题么&#xff1f; 现象是在训练过程中&#xff0c;ssh 上不去了&#xff0c;能ping通&#xff0c;没死机&#xff0c;但是ubunutu 的pc侧的显示器&#xff0c;鼠标啥都不好用了。只能重启。 问题原因&#xff1a; OOM了95G&a…

【C++】C++11-基础

目录 1、统一的列表初始化 1.1 {}初始化 1.2 std::initializer_list 2、声明 2.1 auto 2.2 decltype 2.3 nullptr 3、范围for 4、智能指针 5、STL中的一些变化 5.1 新容器 5.2 新方法 1、统一的列表初始化 1.1 {}初始化 在C98中&#xff0c;标准允许使用花括号{}…

光伏发电量如何计算?

真实光伏发电情况&#xff0c;需要根据几十种复杂因素&#xff0c;再通过鹧鸪云算法&#xff0c;快速计算出实际发电量情况。 1、自研算法&#xff0c;技术创新引领未来 鹧鸪云光伏发电量自研算法&#xff0c;是研发团队历经8个月&#xff0c;基于深度学习、大数据分析等前沿技…

Aqara澳门体验店开业 品牌实力与市场前景备受瞩目

在全球经济持续波动的背景下&#xff0c;许多品牌选择采取保守策略。然而&#xff0c;Aqara却还能稳步前行&#xff0c;展现出了强劲的发展势头。7月20日&#xff0c;Aqara在澳门市场迈出了重要一步&#xff0c;开设了该地区首家标准4S智能家居体验馆。这一战略性的布局不仅凸显…

跟《经济学人》学英文:2024年09月07日这期 Can IKEA disrupt the furniture business again?

Can IKEA disrupt the furniture business again? It wants to help you sell your Billy bookcase disrupt&#xff1a; 颠覆&#xff0c;彻底改变 这里的 “disrupt” 是指“颠覆”或“彻底改变”某个行业或市场的现有运作方式&#xff0c;通常通过引入创新的做法或技术来…

Django-Celery-Flower实现异步和定时爬虫及其监控邮件告警

1 Django中集成celery # Django --->python 的web框架-web项目--》用浏览器访问 # celery--->分布式异步任务框架-定时任务-延迟任务-异步任务1.1 安装模块 #官方文档&#xff1a;https://docs.celeryq.dev/en/stable/django/first-steps-with-django.htmlpip install …

JavaEE:文件内容操作(二)

文章目录 文件内容操作读文件(字节流)read介绍read() 使用read(byte[] b) 使用 read(byte[] b, int off, int len) 使用 写文件(字节流)write介绍write(int b) 使用write(byte[] b) 使用write(byte[] b, int off, int len) 使用 读文件(字符流)read() 使用read(char[] cbuf) 使…

[产品管理-6]:NPDP新产品开发 - 4 - 战略 - 创新支持战略,支持组织的总体创新战略(平台战略、技术战略、营销战略、知识产权战略、能力建设战略)

目录 一、创新支持战略概述 二、平台战略&#xff1a;大平台小产品战略 2.1 概述 1、平台战略的定义 2、平台战略的特点 3、平台战略的应用领域 4、平台战略的成功案例 5、平台战略的发展趋势 2.2 大平台小产品战略 1&#xff09;大平台的建设 2&#xff09;、小产品…

day-53 两个线段获得的最多奖品

思路 dp[right] 表示右端点不超过 prizePositions[right] 的线段可以覆盖最大奖品数量&#xff0c;然后枚举后面一条线段的右端点&#xff08;第一条线段在第二条线段左边&#xff09;&#xff0c;第二条线段的长度可以表示为right-left1;第一条线段的长度可以表示dp[left] 解…

MINICPM-V2_6图像得到embedding-代码解读

目的 基于上一篇MINICPM-V2_6图像预处理流程-代码解读将输入图片得到了input_ids、attention_mask、pixel_values、image_sizes、image_bound、tgt_sizes&#xff0c;但是要怎么通过这些得到图片对应的embedding呢&#xff1f; 这里接着从MINICPM-V2_6入手&#xff0c;了解如何…

在 Windows 系统上,文件传输到虚拟机(VM)可以通过 VS Code 的图形界面(GUI)或命令行工具进行操作

在 Windows 系统上&#xff0c;文件传输到虚拟机&#xff08;VM&#xff09;可以通过 VS Code 的图形界面&#xff08;GUI&#xff09;或命令行工具进行操作。以下是几种方法&#xff1a; ### 方法 1: 使用 VS Code 图形界面 1. **连接到远程 VM**&#xff1a; - 在 VS Cod…

eNUM 原理概述(VoNR VoLTE适用) eNUM 报文解析

目录 1. eNUM 原理概述(VoNR VoLTE适用) 1.1 主要内容 1.2 什么是 eNUM 及 FAQ 1.3 eNUM 的主要信令场景 1.4 eNUM 查询结果为空&#xff0c;意味着什么&#xff1f; 1.5 eNUM 典型流程举例(VoNR 呼叫流程) 1.6 案例&#xff1a;因 eNUM 配置错误导致呼叫失败&#xff…

【STM32】BH1750光敏传感

1.BH1750介绍 BH1750是一个光敏传感&#xff0c;采用I2C协议&#xff0c;对于I2C的从机&#xff0c;都有自己的地址&#xff0c;用来主机选择和哪个从机通信&#xff0c;对于OLED来说&#xff0c;只有单片机通过I2C往OLED中写数据。而BH1750来说&#xff0c;有单片机往BH1750写…

illusionX——一个从理解情感到改变学习、创新教育体验集成情感计算的混合现实系统

概述 论文地址&#xff1a;https://arxiv.org/pdf/2402.07924.pdf 近年来&#xff0c;情感计算在丰富人类与计算机和机器的交互方式方面备受关注。这一创新领域旨在通过理解和响应用户的情绪和心理状态&#xff0c;将人机交互转变得更加自然和直观。无论是情感识别、面部表情分…

DAY74

#ifndef WIDGET_H #define WIDGET_H#include <QWidget>#include <QPainter> //画家类 #include <QTimer> //定时器类 #include <QTime> //时间类QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACEclass Widget : …