SpringSecurity自定义认证

news2024/11/26 8:31:58

一. 前言

​ 学习了SpringSecurity的使用,以及跟着源码分析了一遍认证流程,掌握了这个登录认证流程,才能更方便我们做自定义操作。

​ 下面我们来学习下怎么实现多种登录方式,比如新增加一种邮箱验证码登录的形式,但SpringSecurity默认的Usernamepassword方式不影响。

二. 自定义邮件验证码认证

0. 说明

自定义一个邮箱验证码的认证,将邮箱号码作为key,验证码作为value存放到Redis中缓存。

1. 回顾

首先回顾下之前源码分析的认证流程,如下图:

flow.jpg

2. 设计思路

  1. 首先前端是填写邮箱,点击获取验证码

  2. 输入获取到的验证码,点击登录按钮,发送登录接口(/emial/login,此处不能使用默认的/login,因为我们属于扩展)

  3. 自定义过滤器EmailCodeAuthenticationFilter(类似UsernamepasswordAuthenticationFilter),获取邮箱号码与验证码

  4. 将邮箱号码与验证码封装为一个需要认证的自定义Authentication对象EmailCodeAuthenticationToken(类似UsernamepasswordAuthenticationToken)

  5. EmailCodeAuthenticationToken传给AuthenticationManager接口的authenticate方法认证

  6. 因为AuthenticationManager的默认实现类为ProviderManager,而ProviderManager又是委托给了AuthenticationProvider,因此

    自定义一个AuthenticationProvider接口的实现类EmailCodeAuthenticationProvider,实现authenticate方法认证

  7. 认证成功与认证失败的处理:一种是直接在过滤器EmailCodeAuthenticationFilter中重写successfulAuthenticationunsuccessfulAuthentication,另一种是实现AuthenticationSuccessHandlerAuthenticationFailureHandler进行处理

  8. 总归一句:照猫画瓢

总结

需要实现以下几个类:

  • 过滤器EmailCodeAuthenticationFilter
  • Authentication对象EmailCodeAuthenticationToken
  • AuthenticationProvider类EmailCodeAuthenticationProvider
  • 自定义认证成功与认证失败的Handler

3. 代码实现

  1. 自定义Authentication对象(这里是EmailCodeAuthenticationToken)

    public class EmailCodeAuthenticationToken extends AbstractAuthenticationToken {
    
        private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
        // 邮箱账号
        private final Object principal;
        // 邮箱验证码
        private Object credentials;
    
        /**
         * 没有经过验证时,权限位空,setAuthenticated设置为不可信令牌
         * @param principal
         * @param credentials
         */
        public EmailCodeAuthenticationToken(Object principal, Object credentials) {
            super(null);
            this.principal = principal;
            this.credentials = credentials;
            setAuthenticated(false);
        }
    
        /**
         * 已认证后,将权限加上,setAuthenticated设置为可信令牌
         * @param principal
         * @param credentials
         * @param authorities
         */
        public EmailCodeAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {
            super(authorities);
            this.principal = principal;
            this.credentials = credentials;
            super.setAuthenticated(true);
        }
    
        @Override
        public Object getCredentials() {
            return this.credentials;
        }
    
        @Override
        public Object getPrincipal() {
            return this.principal;
        }
    
        @Override
        public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
            Assert.isTrue(!isAuthenticated,
                          "Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
            super.setAuthenticated(false);
        }
    
        @Override
        public void eraseCredentials() {
            super.eraseCredentials();
            this.credentials = null;
        }
    }
    

    说明:

    ​ 模仿UsernamepasswordAuthenticationToken定义,继承AbstractAuthenticationToken,这里注意的是要定义两个构造器,分别对应未认证和已认证的Token,已认证的调用super.setAuthenticated(true);

  2. 自定义Filter(这里是EmailCodeAuthenticationFilter)

    public class EmailCodeAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
        // 前端传来的参数名
        private final String SPRING_SECURITY_EMAIL_KEY = "email";
        private final String SPRING_SECURITY_EMAIL_CODE_KEY = "email_code";
    
        // 自定义的路径匹配器,拦截Url为:/email/login
        private static final AntPathRequestMatcher DEFAULT_ANT_PATH_REQUEST_MATCHER = new AntPathRequestMatcher("/email/login",
                                                                                                                "POST");
    
        // 是否仅POST方式
        private boolean postOnly = true;
    
        public EmailCodeAuthenticationFilter() {
            super(DEFAULT_ANT_PATH_REQUEST_MATCHER);
        }
    
        /**
         * 认证方法,在父类的doFilter中调用
         * @param request
         * @param response
         * @return
         * @throws AuthenticationException
         * @throws IOException
         * @throws ServletException
         */
        @Override
        public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
            if (this.postOnly && !request.getMethod().equals("POST")) {
                throw new AuthenticationServiceException("Authentication method not support : " + request.getMethod());
            }
            System.out.println("email attemptAuthentication");
            // 获取邮箱号码
            String email = obtainEmail(request);
            email = (email != null) ? email : "";
            email = email.trim();
            // 获取邮箱验证码
            String emailCode = obtainEmailCode(request);
            emailCode = (emailCode != null) ? emailCode : "";
            // 构造Token
            EmailCodeAuthenticationToken authRequest = new EmailCodeAuthenticationToken(email, emailCode);
            setDetails(request, authRequest);
            // 使用AuthenticationManager来进行认证
            return this.getAuthenticationManager().authenticate(authRequest);
        }
    
        /**
         * 获取请求中email参数
         * @param request
         * @return
         */
        @Nullable
        protected String obtainEmail(HttpServletRequest request) {
            return request.getParameter(this.SPRING_SECURITY_EMAIL_KEY);
        }
    
        /**
         * 获取请求中验证码参数email_code
         * @param request
         * @return
         */
        @Nullable
        protected String obtainEmailCode(HttpServletRequest request) {
            return request.getParameter(this.SPRING_SECURITY_EMAIL_CODE_KEY);
        }
    
        protected void setDetails(HttpServletRequest request, EmailCodeAuthenticationToken authRequest) {
            authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
        }
    }
    

    说明:

    ​ 模仿UsernamepasswordAuthentionFilter实现自定义的过滤器,核心是attemptAuthentication方法.

  3. 自定义AuthenticationProvider(这里是EmailCodeAuthenticationProvider)

    public class EmailCodeAuthenticationProvider implements AuthenticationProvider {
        protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
    
        private  EmailCodeUserDetailsService emailCodeUserDetailsService;
    
        @Override
        public Authentication authenticate(Authentication authentication) throws AuthenticationException {
            Assert.isInstanceOf(EmailCodeAuthenticationToken.class, authentication,
                                () -> this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports",
                                                               "Only UsernamePasswordAuthenticationToken is supported"));
            // 此时的authentication还没认证,获取邮箱号码
            EmailCodeAuthenticationToken unAuthenticationToken = (EmailCodeAuthenticationToken) authentication;
            // 做校验
            UserDetails user = this.emailCodeUserDetailsService.loadUserByEmail(unAuthenticationToken);
            if (user == null) {
                throw new InternalAuthenticationServiceException("EmailCodeUserDetailsService returned null, which is an interface contract violation");
            }
            System.out.println("authentication successful!");
    
            Object principalToReturn = user;
            return createSuccessAuthentication(principalToReturn, authentication, user);
        }
    
        @Override
        public boolean supports(Class<?> authentication) {
            return EmailCodeAuthenticationToken.class.isAssignableFrom(authentication);
        }
    
        protected Authentication createSuccessAuthentication(Object principal, Authentication authentication, UserDetails user) {
            EmailCodeAuthenticationToken result = new EmailCodeAuthenticationToken(principal,
                                                                                   authentication.getCredentials(), user.getAuthorities());
            result.setDetails(authentication.getDetails());
            return result;
        }
    
        public void setEmailCodeUserDetailsService(EmailCodeUserDetailsService emailCodeUserDetailsService) {
            this.emailCodeUserDetailsService = emailCodeUserDetailsService;
        }
    }
    

    说明:

    ​ Provider是真正做认证的地方,这里调用emailCodeUserDetailsService服务去执行验证,因为要用到这个Service,所以提供了一个set方法setEmailCodeUserDetailsService用于注入。这里的这个service是我们自定义的,可以不用实现UserDetailsService, Service里的逻辑可以自定义

  4. 自定义认证成功与失败的Handler

    public class EmailCodeAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
        @Override
        public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
            response.setContentType("text/plain;charset=UTF-8");
            response.getWriter().write(authentication.getName());
        }
    }
    
    public class EmailCodeAuthenticationFailureHandler implements AuthenticationFailureHandler {
        @Override
        public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
            response.setContentType("text/plain;charset=UTF-8");
            response.getWriter().write("邮箱验证码错误!");
        }
    }
    

    说明:

    ​ 这里的是认证成功或失败后的处理,需要实现对应的接口以及方法。这里的逻辑只是简单测试,具体逻辑以后根据业务逻辑去编写。

  5. 添加自定义认证的配置

    ​ 为了让我们自定义的认证生效,需要将我们的Filter和Provider加入到SpringSecurity的配置中。这里我们使用apply这个方法将其他一些配置合并到SpringSecurity的配置中,形成插件化。比如: httpSecurity.apply(new xxxxConfig());

    ​ 因此我们可以将我们的配置单独放到一个配置类中。

    public class EmailCodeAuthenticationSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
    
        // 注入email验证服务
        @Autowired
        private EmailCodeUserDetailsService emailCodeUserDetailsService;
    
        @Override
        public void configure(HttpSecurity http) {
            // 配置Filter
            EmailCodeAuthenticationFilter emailCodeAuthenticationFilter = new EmailCodeAuthenticationFilter();
            // 设置AuthenticationManager
            emailCodeAuthenticationFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
            // 设置认证成功处理Handler
            emailCodeAuthenticationFilter.setAuthenticationSuccessHandler(new EmailCodeAuthenticationSuccessHandler());
            // 设置认证失败处理Handler
            emailCodeAuthenticationFilter.setAuthenticationFailureHandler(new EmailCodeAuthenticationFailureHandler());
    
            // 配置Provider
            EmailCodeAuthenticationProvider emailCodeAuthenticationProvider = new EmailCodeAuthenticationProvider();
            // 设置email验证服务
            emailCodeAuthenticationProvider.setEmailCodeUserDetailsService(emailCodeUserDetailsService);
    
            // 将过滤器添加到过滤器链路中
            http.authenticationProvider(emailCodeAuthenticationProvider).addFilterAfter(emailCodeAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
        }
    }
    

    注意:

    ​ 这里需要注意的是,一定要将AuthenticationManager提供给Filter,如果没有这一步,那么在Filter中进行认证的时候无法找到对应的Provider,因为AuthenticationManger就是管理Provider的。
    http.getSharedObject(AuthenticationManager.class)解释:

    SharedObject是在配置中进行共享的一些对象,HttpSecurity共享了一些非常有用的对象可以供外部使用,比如AuthenticationManager

    最后在SpringSecurity的主配置中加入我们的自定义配置:

    @Configuration
    public class MySecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Autowired
        private EmailCodeAuthenticationSecurityConfig emailCodeAuthenticationSecurityConfig;
    
        @Autowired
        private DefaultUserDetailsService defaultUserDetailsService;
    
        @Bean
        public PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
    
        @Bean
        @Override
        protected AuthenticationManager authenticationManager() throws Exception {
            return super.authenticationManager();
        }
    
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.userDetailsService(defaultUserDetailsService);
        }
    
        @Override
        public void configure(WebSecurity web) throws Exception {
            web.ignoring().antMatchers("/getEmailCode", "/**/*.html");
        }
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests()
                .anyRequest()
                .authenticated()
                .and()
                .formLogin()
                .loginPage("/login.html")
                .loginProcessingUrl("/login")
                .permitAll()
                .and()
                .logout()
                .logoutUrl("/logout")
                .and()
                .apply(emailCodeAuthenticationSecurityConfig)
                .and()
                .csrf()
                .disable();
        }
    }
    

    说明:

    ​ 因为这里使用了数据库保存用户信息,所以在SpringSecurity的默认表单登录里,修改了UserDetailService,在这里进行校验,所以在主配置中要设置UserDetailService:auth.userDetailsService(defaultUserDetailsService);

  6. 其他一些文件

    查看我上传的gitee源码吧,整个工程都上传了。

  7. 前端页面实现

    <!DOCTYPE html>
    <html lang="en">
        <head>
            <meta charset="UTF-8">
            <meta http-equiv="X-UA-Compatible" content="IE=edge">
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <title>登录</title>
    
            <!-- 最新版本的 Bootstrap 核心 CSS 文件 -->
            <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css" integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu" crossorigin="anonymous">
            <script src="http://libs.baidu.com/jquery/2.1.4/jquery.min.js"></script>
            <!-- 最新的 Bootstrap 核心 JavaScript 文件 -->
            <script src="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js" integrity="sha384-aJ21OjlMXNL5UyIl/XNwTMqvzeRMZH2w8c5cRVpzpU8Y5bApTppSuUkhZXN0VxHd" crossorigin="anonymous"></script>
    
            <style>
                body {
                    background-color: gray;
                }
                .login-div {
                    width: 400px;
                    /* height: 200px; */
                    margin: 0 auto;
                    margin-top: 200px;
                    border: 1px solid black;
                    padding: 10px;
                }
            </style>
        </head>
        <body>
            <div class="login-div">
                <ul class="nav nav-tabs" role="tablist">
                    <li class="active">
                        <a href="#usernameLogin"   data-toggle="tab">用户名登录</a>
                    </li>
                    <li>
                        <a href="#emailLogin" data-toggle="tab">邮箱验证码登录</a>
                    </li>
                </ul>
    
                <!-- 用户名登录 -->
                <div class="tab-content">
                    <div class="tab-pane active" id="usernameLogin">
                        <form action="/login" method="POST">
                            <div class="form-group">
                                <label>用户名</label>
                                <input type="text" class="form-control" placeholder="Username" name="username">
                            </div>
                            <div class="form-group">
                                <label>密码</label>
                                <input type="password" class="form-control"  placeholder="Password" name="password">
                            </div>
    
                            <div class="checkbox">
                                <label>
                                    <input type="checkbox" name="rememberType"> 记住我
                                </label>
                            </div>
                            <button type="submit" class="btn btn-default">登录</button>
                        </form>
                    </div>
                    <!-- 邮箱登录 -->
                    <div class="tab-pane" id="emailLogin">            
                        <form action="/email/login" method="POST">
                            <div class="form-group" >
                                <label>邮箱地址</label>
                                <input type="email" class="form-control" placeholder="Email" name="email" id="email">
                            </div>
                            <div class="form-group">
                                <label>验证码</label>
                                <input type="text" class="form-control"  placeholder="Code" name="email_code">
                            </div>
    
                            <div class="form-group">
                                <label>
                                    <button type="button" class="btn btn-default" id="getCode">获取验证码</button>
                                    <span id="showCode" style="margin-left: 20px;"></span>
                                </label>
                            </div>
                            <button type="submit" class="btn btn-default">登录</button>
                        </form>
                    </div>
                </div>
    
            </div>
    
            <script>
                $('#nav a').on('click', function(e) {
                    e.preventDefault();
                    $(this).tab('show');
                }); 
    
                $('#getCode').on('click', function() {
                    $.ajax({
                        type: "GET",
                        url: "/getEmailCode",
                        data: {
                            email: $('#email').val()
                        },
                        // dataType: "dataType",
                        success: function (response) {
                            $('#showCode').text(response);
                        }
                    });
                });
            </script>
        </body>
    </html>
    

    说明:

    前端页面只是简单的显示使用两种方式来登录的操作,一些输入校验什么的没有详细实现,所以这里默认各位大佬都是正常操作哈。

    这个前端支持两种登录方式,用户名密码登录方式使用的SpringSecurity默认的UsernamepasswordAuthenticationFilter,邮箱验证码使用的是自定义的EmailCodeAuthenticationFilter,在邮箱登录页面,点击获取验证码按钮,会请求服务器获取一个随机的字符串作为验证码,并且存入Redis中,有效期60s(记住我功能在这里没有实现)

    demo-emailLogin.jpg

    demo-usernameLogin.jpg

  8. 数据库操作

    因为目前只是自定义认证,不涉及授权,所以只有一个用户表

    CREATE TABLE `user` (
      `id` INT(11) NOT NULL AUTO_INCREMENT,
      `username` VARCHAR(32) DEFAULT NULL,
      `password` VARCHAR(255) DEFAULT NULL,
      `email` VARCHAR(255) DEFAULT NULL,
      `enabled` TINYINT(1) DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=INNODB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8;
    
    INSERT INTO `user` VALUES ('1', 'root', '$2a$10$RMuFXGQ5AtH4wOvkUqyvuecpqUSeoxZYqilXzbz50dceRsga.WYiq','123456@qq.com', '1');
    

    随便插入一个用户,密码是123,数据库的是经过加密的。

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

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

相关文章

chatgpt赋能python:用Python建立Pipeline-优化你的数据处理流程

用Python建立Pipeline - 优化你的数据处理流程 如果你是一位数据科学家或是数据工程师&#xff0c;那么你一定知道数据处理流程的重要性。数据流程不只是数据的处理和清洗&#xff0c;还包括数据来源的获取以及对数据进行可视化、建模和验证。这个流程可以十分复杂&#xff0c…

solr快速上手:实现从mysql定时自动同步数据(六)

0. 引言 上一章节我们讲解了从msyql同步数据到solr&#xff0c;但是我们每次同步都需要在solr-admin中点击同步按钮&#xff0c;这在生产环境中肯定是不可行的&#xff0c;那么solr是否支持自动化同步了&#xff0c;答案当然是可以&#xff0c;我们今天继续来探索如何实现solr…

虚拟机安装和配置红帽企业版 7.4 操作系统及相关设置

虚拟机安装和配置红帽企业版 7.4 操作系统及相关设置 当安装红帽企业版 7.4 操作系统时&#xff0c;可以按照以下步骤进行配置和设置&#xff1a; 使用 VM16.0 安装软件&#xff0c;打开虚拟机管理程序&#xff0c;并选择创建新的虚拟机。在创建虚拟机的过程中&#xff0c;选…

ChatGPT在线网页版和接口

chat gpt能写公众号文章吗 ChatGPT是一种强大的自然语言生成技术&#xff0c;它可以用来自动生成大量的、质量较高的文本。根据OpenAI官方的演示和使用案例&#xff0c;ChatGPT已经被用于文本摘要、对话生成、语言翻译、文本分类等多种应用场景。 然而&#xff0c;ChatGPT作为…

文本三剑客之 awk

awk Linux/UNIX 系统中&#xff0c;awk 是一个功能强大的编辑工具。逐行读取输入文本 以空格作为分割符&#xff0c;多个空格他会自动压缩成一个空格 AWK信息的读入也是逐行指定的匹配模式进行查找&#xff0c;对符合条件的内容进行格式化输出或者过滤处理. 1按照命令找指定…

用户档案PDF报表

用户档案PDF报表 理解数据填充的两种方式熟练构造分组报表 熟练构造Chart图形报表实现个人档案的PDF输出 数据填充 我们介绍了如何使用JasperReport来生成简单的文本报表&#xff0c;正式企业开发中动态数据展示也是报表中最重要的一 环&#xff0c;接下来我们共同研究的就是填…

FastCorrect:语音识别快速纠错模型丨RTC Dev Meetup

前言 「语音处理」是实时互动领域中非常重要的一个场景&#xff0c;在声网发起的「RTC Dev Meetup丨语音处理在实时互动领域的技术实践和应用」活动中&#xff0c;来自百度、寰宇科技和依图的技术专家&#xff0c;围绕该话题进行了相关分享。 本文基于微软亚洲研究院主管研究…

chatgpt赋能python:Python应用图标的SEO优化策略

Python应用图标的SEO优化策略 作为一门跨平台解释型编程语言&#xff0c;Python已经成为越来越多企业和开发者的首选语言之一。在Python应用的推广过程中&#xff0c;图标的设计和SEO优化也变得越来越重要。在本文中&#xff0c;我们将探讨Python应用图标的SEO优化策略&#x…

CCE集群切换OBS共享存储方案

目录 一、背景 二、创建PVC 三、同步nfs数据到OBS 四、变更无状态负载数据储存为OBS 五、卸载弹性文件服务SFS&#xff08;nfs&#xff09; 一、背景 生产环境CEE集群&#xff0c;每个K8s节点挂载同一个华为云弹性文件服务SFS(可以简单的理解为nfs)。无状态应用通过hostPath…

chatgpt赋能python:Python就业岗位:行业热门,前景广阔

Python 就业岗位&#xff1a;行业热门&#xff0c;前景广阔 作为其中一门最受欢迎的编程语言&#xff0c;Python 经常出现在各个行业的招聘需求中。无论是大企业、初创公司&#xff0c;还是政府机构和非营利组织&#xff0c;都需要 Python 专业人士来应对日益增长的数据和技术…

chatgpt赋能python:Python库使用指南

Python库使用指南 Python是一种高级编程语言&#xff0c;拥有丰富而强大的标准库和第三方库。在本文中&#xff0c;将介绍一些主要Python库的使用方法以及如何通过使用它们来提高SEO。 Requests库 Requests是一个流行的第三方库&#xff0c;用于HTTP请求和响应。可以使用它来…

【内网安全-隧道搭建】内网穿透_Spp上线(全双工通信)

目录 Spp&#xff08;特殊协议上线&#xff09; 1、简述&#xff1a; 2、用法&#xff1a; 1、准备 2、服务器 3、客户机 4、cs、msf Spp&#xff08;特殊协议上线&#xff09; 1、简述&#xff1a; 1&#xff09;支持的协议&#xff1a;tcp、udp、rudp&#xff08;可靠…

基于动力学模型的无人驾驶车辆MPC轨迹跟踪算法及carsim+matlab联合仿真学习笔记

目录 1 模型推导及算法分析 1.1 模型推导 1.1.1 车辆动力学模型 1.1.2 线性时变预测模型推导 1.2 模型预测控制器设计 1.2.1 目标函数设计 1.2.2 约束设计 2 代码解析 2.1 模板框架 2.1.1 S-Function 2.1.2 mdlInitializeSizes函数 2.1.3 mdlUpdates()函数 2.1.4 …

机器学习中的数学原理——分类的正则化

通过这篇博客&#xff0c;你将清晰的明白什么是分类的正则化。这个专栏名为白话机器学习中数学学习笔记&#xff0c;主要是用来分享一下我在 机器学习中的学习笔记及一些感悟&#xff0c;也希望对你的学习有帮助哦&#xff01;感兴趣的小伙伴欢迎私信或者评论区留言&#xff01…

Go异步任务解决方案 Asynq

今天为大家介绍一个Go处理异步任务的解决方案&#xff1a;Asynq&#xff0c;是一个 Go 库&#xff0c;用于排队任务并与 worker 异步处理它们。它由Redis提供支持&#xff0c;旨在实现可扩展且易于上手。 一、概述 Asynq 是一个 Go 库&#xff0c;用于对任务进行排队并与工作人…

Python爬虫学习-简单爬取网页数据

疫情宅家无事&#xff0c;就随便写一些随笔吧QwQ… 这是一篇介绍如何用Python实现简单爬取网页数据并导入MySQL中的数据库的文章。主要用到BeautifulSoup requests 和 pymysql。 以网页https://jbk.39.net/mxyy/jbzs/为例&#xff0c;假设我们要爬取的部分数据如下图所示&#…

【板栗糖GIS】——如何下载哔哩哔哩的视频CC字幕为不带时间节点的纯文字

【板栗糖GIS】——如何下载哔哩哔哩的视频CC字幕为不带时间节点的纯文字 目录 1. 打开edge浏览器或谷歌浏览器 2. 安装油猴插件 3. 安装字幕插件 4. 打开哔哩哔哩视频播放页面&#xff0c;点击字幕 首先在想要下载之前需要先判定视频是否有云字幕&#xff0c;如果有才可以…

xxl-job详解

什么是xxl-job&#xff1f; ​ xxl-job是一个分布式的任务调度平台&#xff0c;其核心设计目标是&#xff1a;学习简单、开发迅速、轻量级、易扩展&#xff0c;现在已经开放源代码并接入多家公司的线上产品线&#xff0c;开箱即用。xxl是xxl-job的开发者大众点评的许雪里名称的…

SpringBoot+vue 实现监控视频rtsp播放(java+Nginx+ffmpeg+flv.js)

其实原理就是: 将监控通过FFMPEG,推送给Nginx,前端通过Nginx地址拉取视频&#xff0c;就可以播放了。 1:安装FFMPEG. 2:下载并且配置nginx. 3:使用java代码实现调用cmd或者linux窗口,并且运行操作FFMPEG的命令&#xff0c;进行监控视频rtsp格式的推送&#xff0c;推送给nginx. …

模板方法设计模式的学习和使用

1、模板方法设计模式的学习 当涉及到一系列具有相似行为的算法或操作时,模板方法设计模式是一种有效的设计模式。它允许你定义一个算法的骨架,同时将某些步骤的实现细节交给子类来实现。   模板方法模式基于以下两个核心原则&#xff1a; 抽象类定义模板方法骨架&#xff1a…