SpringSecurity5.7+最新案例 -- 用户名密码+验证码+记住我······

news2024/11/15 8:45:41

简介

根据最近一段时间的设计以及摸索,对SpringSecurity进行总结,目前security采用的是5.7+版本,和以前的版本最大的差别就是,以前创建SecurityConfig需要继承WebSecurityConfigurerAdapter,而到了5.7以后,并不推荐这种做法,查了网上一些教程,其实并不好,绝大多数用的都是老版本,所以出此文案。

一些原理什么的,就不过多说明了,一般搜索资料的,其实根本不想你说什么原理 T·T。

写法区别

最大的区别就是不需要继承WebSecurityConfigurerAdapter(官方也开始弃用此方法),所有配置不需要用and()方法链接,采用lambda处理,个人觉得lambda写法更加的美观,可阅读性更高

这是以前的写法
@Configuration
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
         http.authorizeHttpRequests()
                .mvcMatchers("/login.html").permitAll()
                .mvcMatchers("/index").permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .loginPage("/login.html")
                .loginProcessingUrl("/doLogin")
                .usernameParameter("uname")
                .passwordParameter("passwd")
                .successForwardUrl("/index") 		 //forward 跳转           注意:不会跳转到之前请求路径
                //.defaultSuccessUrl("/index")   //redirect 重定向    注意:如果之前请求路径,会有优先跳转之前请求路径
                .failureUrl("/login.html")
                .and()
                .csrf().disable();//关闭 CSRF
    }
}
这是现在的写法
@Configuration
public class WebSecurityConfigurer{
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        return http.authorizeHttpRequests(auth ->
                        auth.mvcMatchers(loadExcludePath()).permitAll()
                                .anyRequest().authenticated()
                )
                .cors(conf ->
                        conf.configurationSource(corsConfigurationSource())
                )
                .rememberMe(conf -> {
                    conf.useSecureCookie(true)
                            .rememberMeServices(rememberMeServices());
                })
                .formLogin(conf ->
                        conf.loginPage(loginPage)
                                .defaultSuccessUrl(defaultSuccessUrl, true)
                                .failureUrl(loginPage)
                )
                .logout(conf ->
                        conf.invalidateHttpSession(true)
                                .clearAuthentication(true)
                                .logoutSuccessUrl(logoutSuccessUrl)
                )
                .csrf(AbstractHttpConfigurer::disable)
                .build();
    }
}

案例包含

自定义认证数据源、密码加密、remember-me、session会话管理、csrf漏洞保护、跨域处理、异常处理 等核心模块,授权以后再说

目录结构

image-20230803194417226

核心代码

主配置SecurityConfig
@Configuration
public class SecurityConfig<S extends Session> {

    // 基于数据库验证,自定义实现UserDetailService
    @Resource
    MyUserDetailsService myUserDetailsService;

    // 注入自定义认证失败处理器
    @Bean
    MyAuthFailureHandler myAuthFailureHandler() {
        return new MyAuthFailureHandler();
    }

    // 注入数据源
    @Resource
    DataSource dataSource;

    // 注入自定义认证成功处理器
    @Bean
    MyAuthSuccessHandler myAuthSuccessHandler() {
        return new MyAuthSuccessHandler();
    }

    // 注入自定义注销登录处理器
    @Bean
    MyLogoutSuccessHandler myLogoutSuccessHandler() {
        return new MyLogoutSuccessHandler();
    }

    // 注入自定义未认证访问处理器
    @Bean
    MyAuthEntryPointHandler myAuthEntryPointHandler() {
        return new MyAuthEntryPointHandler();
    }

    // 注入自定义session会话管理处理器
    @Bean
    MySessionExpiredHandler mySessionExpiredHandler() {
        return new MySessionExpiredHandler();
    }

    // 注入自定义未授权访问处理器
    @Bean
    MyAccessDeniedHandler myAccessDeniedHandler() {
        return new MyAccessDeniedHandler();
    }

    // 注入redis-session管理
    @Resource
    private FindByIndexNameSessionRepository<S> sessionRepository;

    // 注入redis-session管理
    @Bean
    public SpringSessionBackedSessionRegistry<S> sessionRegistry() {
        return new SpringSessionBackedSessionRegistry<>(sessionRepository);
    }

    // 登录url
    private final String loginUrl = "/login";

    /**
     * 配置放行请求
     */
    private String[] loadExcludePath() {
        return new String[]{
                "/pm", loginUrl, "/error"
        };
    }

    /**
     * 配置密码加密规则
     */
    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 跨域配置
     */
    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.setAllowedHeaders(Collections.singletonList("*"));
        corsConfiguration.setAllowedMethods(Collections.singletonList("*"));
        corsConfiguration.setAllowedOrigins(Collections.singletonList("*"));
        corsConfiguration.setMaxAge(3600L);
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", corsConfiguration);
        return source;
    }

    /**
     * 记住我 令牌 持久化存储
     */
    @Bean
    public PersistentTokenRepository persistentTokenRepository() {
        // 这个sql可手动执行到数据库中,当setCreateTableOnStartup 为 false的时候
        String initSql = "create table persistent_logins (username varchar(64) not null, series varchar(64) primary key,token varchar(64) not null, last_used timestamp not null)";
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        //只需要没有表时设置为 true,也就是说,第一次启动的时候设置为true,后续都要设置为false
        jdbcTokenRepository.setCreateTableOnStartup(false);
        jdbcTokenRepository.setDataSource(dataSource);
        return jdbcTokenRepository;
    }

    /**
     * 记住我 service 注入
     */
    @Bean
    public RememberMeServices rememberMeServices() {
        return new MyRememberMeServices(UUID.randomUUID().toString(), myUserDetailsService, persistentTokenRepository());
    }

    /**
     * 构建认证管理器
     */
    @Bean
    AuthenticationManager authenticationManager(HttpSecurity httpSecurity) throws Exception {
        // 开启自定义userDetail,开启密码加密
        return httpSecurity.getSharedObject(AuthenticationManagerBuilder.class)
                .userDetailsService(myUserDetailsService)
                .passwordEncoder(passwordEncoder())
                .and()
                .build();
    }

    /**
     * 自定义认证过滤器
     */
    @Bean
    public MyAuthenticationFilter myAuthenticationFilter(HttpSecurity httpSecurity) throws Exception {
        MyAuthenticationFilter myAuthenticationFilter = new MyAuthenticationFilter();
        // 设置认证管理器
        myAuthenticationFilter.setAuthenticationManager(authenticationManager(httpSecurity));
        // 设置登录成功后返回
        myAuthenticationFilter.setAuthenticationSuccessHandler(myAuthSuccessHandler());
        // 设置登录失败后返回
        myAuthenticationFilter.setAuthenticationFailureHandler(myAuthFailureHandler());
        // 设置记住我功能  认证登录的时候,往数据库写值使用
//        myAuthenticationFilter.setRememberMeServices(rememberMeServices());
        return myAuthenticationFilter;
    }

    /**
     * 安全认证过滤器链
     */
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        return http.authorizeHttpRequests(auth ->
                // 配置需要放行的请求
                        auth.mvcMatchers(loadExcludePath()).permitAll()
                                // 除了以上放行请求,其它都需要进行认证
                                .anyRequest().authenticated()
                )

                // 跨域处理
                .cors(conf ->
                        // 配置跨域
                        conf.configurationSource(corsConfigurationSource())
                )

                // csrf 关闭
                .csrf(AbstractHttpConfigurer::disable)
                // csrf 开启请求,并且将login请求放行, 登录成功后,在cookies中会有一个XCSRF-TOKEN的值(value)
                // 后续的所有接口,在 header中加入 X-XSRF-TOKEN:value即可
//                .csrf(conf -> conf.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()).ignoringAntMatchers(loginUrl))

                // 开启请求登录
                .formLogin(
                        // 这里是自定义json body请求登录
                        // 将默认登录页面关闭,只能采用发请求的方式登录
                        AbstractHttpConfigurer::disable

                        // 这里适用于form表单登录
//                        conf ->
//                                conf.successHandler(myAuthSuccessHandler())
//                                        .failureHandler(myAuthFailureHandler())
                )

                // 开启记住我功能  --  自动登录的时候使用
//                .rememberMe(conf ->
//                        conf
//                                .useSecureCookie(true)
//                                .rememberMeServices(rememberMeServices())
//                                .tokenRepository(persistentTokenRepository())
//                )

                // 请求 未认证,未授权 时提示
                .exceptionHandling(conf ->
                        // 未认证
                        conf.authenticationEntryPoint(myAuthEntryPointHandler())
                                // 未授权
                                .accessDeniedHandler(myAccessDeniedHandler())
                )

                // 注销登录返回提示
                .logout(conf ->
                        conf.logoutSuccessHandler(myLogoutSuccessHandler())
                                .invalidateHttpSession(true)
                                .clearAuthentication(true)
                )

                // session 会话管理
                .sessionManagement(conf ->
                        // 同一个用户 只允许 创建 多少个 会话
                        conf.maximumSessions(2)
                                // 同一个用户登录之后,禁止再次登录
                                .maxSessionsPreventsLogin(true)
                                // 会话过期处理
                                .expiredSessionStrategy(mySessionExpiredHandler())
                                // 会话信息注册,交由redis管理
                                // 需要引入 org.springframework.boot:spring-boot-starter-data-redis
                                // 和 org.springframework.session:spring-session-data-redis
//                                .sessionRegistry(sessionRegistry())
                )

                // 自定义过滤器替换 默认的 UsernamePasswordAuthenticationFilter
                // 如果用form表单登录,这里的过滤器就需要注释掉
                .addFilterAt(myAuthenticationFilter(http), UsernamePasswordAuthenticationFilter.class)

                // 构建 HttpSecurity
                .build();
    }

}
MyAuthenticationFilter
public class MyAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        if (!request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        }
        Map<String, String> loginInfo;
        try {
            loginInfo = new ObjectMapper().readValue(request.getInputStream(), Map.class);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        String username = loginInfo.get(getUsernameParameter());// 用来接收用户名
        String password = loginInfo.get(getPasswordParameter());// 用来接收密码
        String code = loginInfo.get("code");// 用来接收验证码

        // 获取记住我 值
        String rememberValue = loginInfo.get(AbstractRememberMeServices.DEFAULT_PARAMETER);
        if (!ObjectUtils.isEmpty(rememberValue)) {
            request.setAttribute(AbstractRememberMeServices.DEFAULT_PARAMETER, rememberValue);
        }

		// 咱们 假装 对验证码进行校验
        if (StringUtils.isEmpty(code))
            throw new BadCredentialsException("验证码不能为空 !");
        if (!"123".equalsIgnoreCase(code))
            throw new BadCredentialsException("验证码错误 !");

        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
        setDetails(request, authRequest);
        return this.getAuthenticationManager().authenticate(authRequest);
    }
}

MyRememberMeServices
/**
 * 自定义记住我 services 实现类
 */
public class MyRememberMeServices extends PersistentTokenBasedRememberMeServices {

    public MyRememberMeServices(String key, UserDetailsService userDetailsService, PersistentTokenRepository tokenRepository) {
        super(key, userDetailsService, tokenRepository);
    }

    /**
     * 自定义前后端分离获取 remember-me 方式
     */
    @Override
    protected boolean rememberMeRequested(HttpServletRequest request, String parameter) {
        Object paramValue = request.getAttribute(parameter);
        if (paramValue != null) {
            String paramValue2 = paramValue.toString();
            return paramValue2.equalsIgnoreCase("true") || paramValue2.equalsIgnoreCase("on")
                    || paramValue2.equalsIgnoreCase("yes") || paramValue2.equals("1");
        }
        return false;
    }

}
MyUserDetails
public class MyUserDetails implements UserDetails {

    private final String uname;
    private final String passwd;

    public MyUserDetails(String uname, String passwd) {
        this.uname = uname;
        this.passwd = passwd;
    }

    // 这里是设置权限的,需要使用的话,你自定义吧
    // 也就是说,在MyUserDetailsService中从数据库里获取,然后调用set方法即可
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return AuthorityUtils.createAuthorityList();
    }

    @Override
    public String getPassword() {
        return passwd;
    }

    @Override
    public String getUsername() {
        return uname;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

MyUserDetailsService
@Service
public class MyUserDetailsService implements UserDetailsService {

    // 这里是获取数据库里的数据,你自定义把
    @Resource
    UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 1. find user
        List<UserEntity> users = userRepository.findByName(username);
        if (CollectionUtils.isEmpty(users)) throw new UsernameNotFoundException("用户不存在");

        UserEntity user = users.get(0);

        return new MyUserDetails(user.getName(), user.getPasswd());
    }
}

handler
MyAccessDeniedHandler
public class MyAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        response.setCharacterEncoding(StandardCharsets.UTF_8.name());
        response.setStatus(HttpStatus.FORBIDDEN.value());
        response.getWriter().write("无权访问!");
    }
}
MyAuthEntryPointHandler
public class MyAuthEntryPointHandler implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        response.setCharacterEncoding(StandardCharsets.UTF_8.name());
        response.setStatus(HttpStatus.BAD_REQUEST.value());
        response.getWriter().println("必须认证之后才能访问!");
    }
}
MyAuthFailureHandler
public class MyAuthFailureHandler implements AuthenticationFailureHandler {
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException {
        Map<String, Object> result = new HashMap<>();
        result.put("msg", "登录失败: " + exception.getMessage()); // 用户名或密码错误
        response.setStatus(HttpStatus.UNAUTHORIZED.value());
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        response.setCharacterEncoding(StandardCharsets.UTF_8.name());
        String s = new ObjectMapper().writeValueAsString(result);
        response.getWriter().println(s);
    }
}

MyAuthSuccessHandler
public class MyAuthSuccessHandler implements AuthenticationSuccessHandler {

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
        Map<String, Object> result = new HashMap<>();
        result.put("msg", "登录成功");
        response.setStatus(HttpStatus.OK.value());
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        response.setCharacterEncoding(StandardCharsets.UTF_8.name());
        String s = new ObjectMapper().writeValueAsString(result);
        response.getWriter().println(s);
    }
}
MyLogoutSuccessHandler
public class MyLogoutSuccessHandler implements LogoutSuccessHandler {
    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
        Map<String, Object> result = new HashMap<>();
        result.put("msg", "注销成功");
        response.setStatus(HttpStatus.OK.value());
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        response.setCharacterEncoding(StandardCharsets.UTF_8.name());
        String s = new ObjectMapper().writeValueAsString(result);
        response.getWriter().println(s);
    }
}

MySessionExpiredHandler
public class MySessionExpiredHandler implements SessionInformationExpiredStrategy {
    @Override
    public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException, ServletException {
        HttpServletResponse response = event.getResponse();
        Map<String, Object> result = new HashMap<>();
        result.put("msg", "当前会话已经失效,请重新登录!");
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        response.setCharacterEncoding(StandardCharsets.UTF_8.name());
        response.setStatus(HttpStatus.BAD_REQUEST.value());
        String s = new ObjectMapper().writeValueAsString(result);
        response.getWriter().println(s);
        response.flushBuffer();
    }
}

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

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

相关文章

【2023全网最全最火教程】Web UI自动化测试之元素定位(超详细~)

目前&#xff0c;在自动化测试的实际应用中&#xff0c;接口自动化测试被广泛使用&#xff0c;但UI自动化测试也并不会被替代。让我们看看二者的对比&#xff1a; 接口自动化测试是跳过前端界面直接对服务端的测试&#xff0c;执行效率和覆盖率更高&#xff0c;维护成本更低&a…

做完两年外包,感觉自己废了一半....

先说一下自己的情况。大专生&#xff0c;17年通过校招进入湖南某软件公司&#xff0c;干了接近2年的点点点&#xff0c;今年年上旬&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落&#xff01;而我已经在一个企业干了五年的功能测试…

C/C++的5大内存分区

1、堆区&#xff08;heap&#xff09;——由程序员分配和释放&#xff0c; 若程序员不释放&#xff0c;程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事 2、栈区&#xff08;stack&#xff09;——由编译器自动分配释放 &#xff0c;存放函数的参数值&#xff0c;局…

linux 查看磁盘大小 内存 目录下各目录大小

查看当前服务器挂在的磁盘大小及已使用、可使用、使用占比、磁盘对应的目录等 df -TH 查看当前目录下 各子目录或文件的大小&#xff1a;右边是目录或文件名&#xff0c;左边是占用的大小 du -sh * 使用 rm命令删除目录或文件&#xff1a;删了数据找不回来&#xff0c;&#x…

阿里巴巴国际站、速卖通如何通过测评补单打造爆款产品?

在现如今竞争激烈的跨境电商行业中&#xff0c;测评补单虽然被认为是公开的秘密&#xff0c;但无论是消费者还是官方都对其持反感和不屑的态度。然而&#xff0c;在互联网时代&#xff0c;如果产品价格、运营等没有绝对的优势&#xff0c;尤其对于新手店铺&#xff0c;要在市场…

性能测试怎么做?测试工具怎么选择?

在当前软件测试行业&#xff0c;熟练掌握性能测试已经是测试工程师们面试的敲门砖了&#xff0c;当然还有很多测试朋友们每天的工作更多的是点点点&#xff0c;性能方面可能也只是做过简单的并发测试&#xff0c;对于编写脚本&#xff0c;搭建环境方面也比较陌生。今天这篇文章…

Qt+联想电脑管家

1.自定义按钮类 效果&#xff1a; (1)仅当未选中&#xff0c;未悬浮时 (2)其他三种情况&#xff0c;均如图 #ifndef BTN_H #define BTN_H#include <QPushButton> class btn : public QPushButton {Q_OBJECT public:btn(QWidget * parent nullptr);void set_normal_icon(…

十分钟python入门 日期时间

1.Python 日期 Python 中的日期不是其自身的数据类型&#xff0c;但是我们可以导入名为 datetime 的模块&#xff0c;把日期视作日期对象进行处理。 1.1 导入 datetime 模块并显示当前日期&#xff1a; import datetime#导入 datetime 模块并显示当前日期&#xff1a; x da…

Dockerfile构建apache镜像 下载源码

创建工作目录 [rootlocalhost ~]# mkdir tomcat [rootlocalhost ~]# cd tomcat/ 编写配置文件 [rootlocalhost tomcat]# cat Dockerfile #基于的基础镜像 FROM centos:7 #镜像作者信息 MAINTAINER liu <wewed163.com> #安装相关依赖包 RUN yum install -y wget net-…

python与深度学习(十五):CNN和宝可梦模型

目录 1. 说明2. 宝可梦模型2.1 导入相关库2.2 建立模型2.3 模型编译2.4 数据生成器2.5 模型训练2.6 模型保存 3. 宝可梦的CNN模型可视化结果图4. 完整代码5. 宝可梦的迁移学习 1. 说明 本篇文章是CNN的另外一个例子&#xff0c;宝可梦模型&#xff0c;是自制数据集的例子。之前…

从C语言到C++_30(哈希)闭散列和开散列(哈希桶)的实现

目录 1. 哈希结构 1.1 哈希的概念 1.2 哈希冲突(碰撞) 1.3 哈希函数 2. 闭散列/开散列解决哈希冲突 2.1 闭散列概念和代码 2.1.1 闭散列线性探测&#xff08;实现&#xff09; 闭散列线性探测完整代码 2.1.2 闭散列二次探测&#xff08;了解&#xff09; 2.2 开散列(…

无边界野望:高通也有痛点?

外界给予高通的称呼无非是性价屠夫&#xff0c;因为即可“拳打”联发科&#xff0c;又能四度包下上海ChinaJoy展馆做游戏技术支持的王者&#xff0c;对高通来说它的路是无边界的&#xff0c;也是跨界之路上前行的“诗和远方”。 这里&#xff0c;既有生成式AI在终端侧布局、与…

类与对象【上】

欢迎来到Cefler的博客&#x1f601; &#x1f54c;博客主页&#xff1a;那个传说中的man的主页 &#x1f3e0;个人专栏&#xff1a;题目解析 &#x1f30e;推荐文章&#xff1a;题目大解析2 目录 &#x1f449;&#x1f3fb;面向过程和面向对象&#x1f449;&#x1f3fb;类的引…

SAM在医学图像分割的一些研究(Segment Anything Model for Medical Images?(2023))

使用预训练模型通过两种主要模式进行分割&#xff0c;包括自动一切和手动提示(例如&#xff0c;点和框)。SAM在各种自然图像分割任务上取得了令人印象深刻的效果。然而&#xff0c;由于医学图像的形态复杂、解剖结构精细、物体边界不确定和复杂、物体尺度大&#xff0c;使得医学…

中小企业如何做好MES管理系统实施建设

中小企业在生产制造领域面临着诸多挑战&#xff0c;包括提升产品竞争力、规范生产制造等。为了应对这些挑战&#xff0c;越来越多的中小企业开始实施MES生产管理系统。然而&#xff0c;由于企业规模小、资源实力不足等原因&#xff0c;很多企业在实施MES管理系统时存在一定的困…

前端下载文化部几种方法(excel,zip,html,markdown、图片等等)和导出 zip 压缩包

文章目录 1、location.href2、location.href3、a标签4、请求后端的方式5、文件下载的方式6、Blob和Base647、下载附件方法(excel,zip,html,markdown)8、封装下载函数9、导出 zip 压缩包相关方法(流方式) 总结 1、location.href //get请求 window.location.href url;2、locati…

流程图模板有哪些?这几款高频使用模板收好

流程图模板有哪些&#xff1f;流程图是一种常用的图形表现形式&#xff0c;用于展示业务流程或操作流程。对于需要频繁制作流程图的用户&#xff0c;选择一个易用、高效的流程图制作工具非常重要。下面介绍几款高频使用的流程图模板&#xff0c;一起来看看吧&#xff01; 第一款…

iTOP-RK3588开发板Ubuntu 系统交叉编译 Qt 工程-命令行交叉编译

使用源码 rk3588_linux/buildroot/output/rockchip_rk3588/host/bin/qmake 交叉编译 QT 工程。 最后烧写编译好的 buildroot 镜像&#xff0c;将编译好的 QT 工程可执行程序在 buildroot 系统上运行。 交叉编译 QT 工程如下所示&#xff0c;首先进入 QLed 的工程目录下。 然后…

将word每页页眉单独设置

在进行论文排版的时候&#xff0c;总是会出现页眉的页码设置问题&#xff0c;比如出现奇数或偶数页码一致&#xff0c;尝试将前面页码改掉&#xff0c;后面再修改前面也进行了变动&#xff0c;将每页页眉单独设置&#xff1a; &#xff08;1&#xff09;在第一页的最后一行输入…

反步控制理论

一.简单反步控制器 简单反步控制器的设计思想是将复杂的非线性系统分解成不超过系统最高阶数的子系统&#xff0c;然后为每个子系统分别设计Lyapunov函数和中间虚拟控制量&#xff0c;一直“后退” 到整个系统&#xff0c;直到完成整个控制律的设计。 以二阶反步控制器为例&a…