SpringBoot拦截器

目录

一、拦截器快速入门

(1)什么是拦截器

(2)拦截器的使用步骤

1、定义拦截器

🍀preHandle() 方法

🍀postHandle() 方法

🍀afterCompletion() 方法

2、注册配置拦截器

二、拦截器详解

1、拦截路径

2、拦截器执行流程

三、登录校验(图书管理系统)

1、定义拦截器

2、注册配置拦截器

3、更改图书馆管理系统代码(用户登录接口)

(1)后端代码

(2)前端代码

四、适配器模式

1、适配器模式定义

2、适配器模式角色

3、适配器模式的实现

4、适配器模式应用场景


        上篇博客完成了强制登录功能,后端程序需要根据 Session 来判断用户是否登录,但是实现方法是比较麻烦的,需要完成下面几个步骤:

1、需要修改每个接口的处理逻辑

2、需要修改每个接口的返回结果

3、接口定义修改,前段代码也需要跟着修改

        那有没有更简单的方式,统一拦截所有的请求,并进行 Session 的校验的?有,那就是Spring 框架提供的核心功能之一:拦截器


一、拦截器快速入门

(1)什么是拦截器

        拦截器是 Spring 框架提供的核心功能之一,主要用来拦截用户的请求,在指定的方法前后,根据业务需要执行预先设定的代码

        也就是说,允许开发人员提前预定义一些逻辑,在用户的请求、响应前后执行。也可以在用户请求前阻止其执行

        在拦截器当中,开发人员可以在应用程序中做一些通用性的操作,比如通过拦截器来拦截前端发来的请求,判断Session中是否有登录用户的信息,如果有 -> 就放行,如果没有 -> 就进行拦截。

        如图:

        这种情况就类似我们去银行办理业务,去银行办业务需要有带身份证,如果没有身份证,就不能取号,更不要提后续的业务,没有就要回家拿再过来,有就去取号,排队等待,办理你需要的业务

        拦截器在这里就类似你有没有身份证,有身份证才能办理后续的业务,没有就要执行其他逻辑(回家拿身份证)。

        接下来,我们来学习拦截器的基本使用。

(2)拦截器的使用步骤

        练习拦截器的使用以及后面的使用都是使用图书管理系统项目。

1、定义拦截器

        自定义拦截器,实现 HandlerInterceptor 接口,并且重写所有方法,代码如下:

@Slf4j
@Component
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.info("LoginInterceptor preHandle(目标方法执行前执行).....");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        log.info("LoginInterceptor postHandle(目标方案执行后执行).....");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        log.info("LoginInterceptor afterCompletion(视图渲染完毕后执行,最后执行).....");
    }
}

🍀preHandle() 方法

        目标方法执行前执行返回true:继续执行后续操作;返回false:中断后续操作。

🍀postHandle() 方法

        目标方法执行后执行

🍀afterCompletion() 方法

        视图渲染完毕后执行,最后执行(后端开发现在几乎不涉及视图了,暂不了解)。

2、注册配置拦截器

        创建一个 WebConfig 类,实现WebMvcConfigurer接口,并重写addInterceptors方法(),代码如下:

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Autowired
    LoginInterceptor loginInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //注册自定义拦截器对象
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/**")//设置拦截器拦截的请求路径( /** 表⽰拦截所有请求
                ;
    }
}

        现在启动服务器,访问任意接口,观察打印的日志

        查询bookId=1的图书信息,如图:

        观察日志:

        可以看到,在执行目标方法前,就执行了 preHandle() 方法了因为preHandle返回了true,才能执行执行目标方法,目标方法执行完后,执行 postHandle() 方法;最后才执行 afterCompletion() 方法

        现在我们把 preHandle() 方法的返回值修改成 false试试,观察打印结果

        再次访问 /book/queryBookById?bookId=1 ,观察返回的信息,这次没有返回信息了,如图:

        再看看控制台打印的日志:

        因为 preHandle() 方法返回的是 false所有没有打印 postHandle() 和afterCompletion() 方法,也就意味着中断了后续的操作


二、拦截器详解

        拦截器的入门程序完成后,接下来我们学习拦截器的使用细节。拦截器的使用细节主要介绍两个部分:1、拦截器的拦截路径配置      2、拦截器实现原理

1、拦截路径

        拦截路径是指我们定义的这个拦截器,对哪些请求生效。

        我们在注册配置拦截器的时候,通过 addPathPatterns() 方法指定要拦截哪些请求。也可以通过 excludePathPatterns() 指定不拦截哪些请求

        上述代码中,我们配置的是 /** ,表示拦截所有的请求。

        比如用户登录校验,我们希望可以对除了登录之外所有的路径生效,代码如下:

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Autowired
    LoginInterceptor loginInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //注册自定义拦截器对象
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/**")//设置拦截器拦截的请求路径( /** 表⽰拦截所有请求
                .excludePathPatterns("/user/login")//设置拦截器不拦截哪些请求路径
                ;
    }
}

        现在请求 /user/login 接口,就不会打印上面说的那三个接口了

        因为没有拦截它,也就不会执行那三个方法,打印其日志了。

        在拦截器中除了可以设置 /** 拦截所有资源外,还有一些常见拦截路径设置:

拦截路径含义举例
/*⼀级路径能匹配/user,/book,/login,不能匹配 /user/login
/**任意级路径能匹配/user,/user/login,/user/reg
/book/*/book下的⼀级路径能匹配/book/addBook,不能匹配/book/addBook/1,/book
/book/**/book下的任意级路径能匹配/book,/book/addBook,/book/addBook/2,不能匹
配/user/login

2、拦截器执行流程

        正常的调⽤顺序:

         

        有了拦截器后,会在调用 Controller 之前进行相应的业务处理,执行的流程如下图:

        

1添加拦截器后,执行 Controller 的方法之前,请求会先被拦截器拦截住,执行 preHadnle() 方法,这个方法需要返回一个布尔类型的值。如果返回true,就表示放行本次操作,继续访问 controller中的方法;如果返回false,则拦截(controller中的方法也不会执行)。

2controller当中的方法执行完毕后在回过来执行 postHandle() 这个方法 以及 afterCompletion() 方法,执行完毕之后,最终给浏览器响应数据。


三、登录校验(图书管理系统)

        学习了拦截器的基本操作之后,接下来我们需要完成最后一步操作:通过拦截器来完成图书管理系统中的登录校验功能。

1、定义拦截器

        从 session 中获取用户信息,如果 session 中不存在,就返回false,并设置http状态码为401,否则返回true。代码如下:

@Slf4j
@Component
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.info("LoginInterceptor preHandle(目标方法执行前执行).....");
        //获取Session,判断Session中存储的userinfo信息是否为空
        HttpSession session = request.getSession(true);
        UserInfo userInfo = (UserInfo) session.getAttribute(Constant.USER_SESSION_KEY);
        if(userInfo == null || userInfo.getId() <= 0) {
            //用户未登录
            response.setStatus(401);
            return false;
        }
        return true;
    }
}

http状态码401:Unauthorized
        Indicates that authentication is required and was either not provided or has failed. If the request already included authorization credentials, then the 401 status code indicates that those credentials were not accepted.

        中文解释:未经过认证。指示身份验证是必需的,没有提供身份验证或身份验证失败。如果请求已经包含授权凭据,那么401状态码表示不接受这些凭据。

        其中 request.getSession() 方法有下面两种形式,有参和无参,如图:

        其中,无参的 getSession() 方法 默认 是 getSession(true);

         getSession(true) 表示如果有Session,则返回Session,如果没有,则创建一个Session

        getSession(false) 表示 获取Session可能会是null

2、注册配置拦截器

        配置拦截器拦截所有的请求,除了 /user/login 和前端的页面,代码如下:

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Autowired
    private LoginInterceptor loginInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //注册自定义拦截器对象
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/**")//设置拦截器拦截的请求路径( /** 表⽰拦截所有请求
                .excludePathPatterns("/user/login")//设置拦截器不拦截哪些请求路径
                .excludePathPatterns("/css/**")//排除前端静态资源
                .excludePathPatterns("/js/**")
                .excludePathPatterns("/pic/**")
                .excludePathPatterns("/**/*.html")
        ;
    }
}

也可以改成下面这样子,定义一个链表,把要排除的路径都放进链表中,代码如下:

//也可以改成下面这样子
//定义一个链表,把要排除的路径都放进链表中
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Autowired
    LoginInterceptor loginInterceptor;
    private List<String> excludePaths = Arrays.asList(
            "/user/login",
            "/css/**",
            "/js/**",
            "/pic/**",
            "/**/*.html"
    );
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //注册自定义拦截器对象
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/**")//设置拦截器拦截的请求路径( /** 表⽰拦截所有请求
                .excludePathPatterns(excludePaths)//设置拦截器排除拦截的路径
        ;
    }
}

        运行程序,进行测试

        访问:http://127.0.0.1:8080/book_list.html?pageNum=1

        可以看到有401的状态码。

        通过fiddler抓包观察:

        返回的响应状态码就是401。

        现在进行登录,如图:

        再次访问:http://127.0.0.1:8080/book_list.html?pageNum=1,如图:有内容了

3、更改图书馆管理系统代码(用户登录接口)

(1)后端代码

        因为我们已经定义和注册配置了拦截器,所以这里统一帮我们做了校验用户是否登录,没有登录就返回false(拦截),并且设置响应的状态码为401;用户已经登录了,能获取到的Session能通过它拿到userInfo信息,userinfo也有对应的用户信息,就是用户登录了,则返回 true;就是上面定义拦截器的内容,代码如下:

@Slf4j
@Component
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.info("LoginInterceptor preHandle(目标方法执行前执行).....");
        //获取Session,判断Session中存储的userinfo信息是否为空
        HttpSession session = request.getSession(true);
        UserInfo userInfo = (UserInfo) session.getAttribute(Constant.USER_SESSION_KEY);
        if(userInfo == null || userInfo.getId() <= 0) {
            //用户未登录
            response.setStatus(401);
            return false;
        }
        return true;
    }
}

        修改 controller的"/book/getBookListByPage" 接口,也就是删除一些逻辑判断,因为拦截器已经帮我们做了这些逻辑处理,代码如下:

    @RequestMapping("/getBookListByPage")
    public Result<PageResult<BookInfo>> getBookListByPage(PageRequest pageRequest, HttpSession session) {
        log.info("查询图书的列表, 请求参数pageRequest: {}", pageRequest);
        //从session中获取用户信息
        //如果用户信息为空, 说明用户未登录
//        UserInfo loginUserInfo = (UserInfo) session.getAttribute(Constant.USER_SESSION_KEY);
//        if(loginUserInfo == null || loginUserInfo.getId() < 0) {
//            return Result.nologin();
//        }
//        //参数校验
//        if(pageRequest.getPageNum() == null) {
//            //返回默认第一页,如果pageSize也没设置,则会使用默认的10
//            pageRequest.setPageNum(1);
//        }
        PageResult<BookInfo> bookList = bookService.getBookListByPage(pageRequest);
        return Result.success(bookList);
    }

        现在进行测试,未登录,访问:http://127.0.0.1:8080/book_list.html?pageNum=1,返回401状态码

        登录后,再访问,结果如下:能返回信息

        但上面没有实现强制登录功能,不应该出现401的状态码,应该跳转到登录界面,这是因为还没有改前端代码,下面修改前端代码。

(2)前端代码

getBookList();
            function getBookList() {
                $.ajax({
                    url: "/book/getBookListByPage" + location.search,
                    type: "get",
                    success: function (result) {
                        // if (result.code == "NOLOGIN") {//用户登录
                        //     location.href = "login.html";
                        // }

                        if (result.data != null && result.data.records != null) {
                            console.log("拿到参数")
                            var finalHtml = "";
                            for (book of result.data.records) {
                                finalHtml += '<tr>';
                                finalHtml += '<td><input type="checkbox" name="selectBook" value="' + book.id + '" id="' + book.id + '" class="book-select"></td>'
                                finalHtml += '<td>' + book.id + '</td>';
                                finalHtml += '<td>' + book.bookName + '</td>';
                                finalHtml += '<td>' + book.author + '</td>';
                                finalHtml += '<td>' + book.count + '</td>';
                                finalHtml += '<td>' + book.price + '</td>';
                                finalHtml += '<td>' + book.publish + '</td>';
                                finalHtml += '<td>' + book.statusCN + '</td>';
                                finalHtml += '<td>';
                                finalHtml += '<div class="op">';
                                finalHtml += '<a href="book_update.html?bookId=' + book.id + '">修改</a>';
                                finalHtml += '<a href="javascript:void(0)" onclick="deleteBook(' + book.id + ')">删除</a>';
                                finalHtml += '</div>';
                                finalHtml += '</td>';
                                finalHtml += '</tr>';
                            }
                            $("tbody").html(finalHtml);

                            var data = result.data;
                            //翻页信息
                            $("#pageContainer").jqPaginator({
                                totalCounts: data.count, //总记录数
                                pageSize: 10,    //每页的个数
                                visiblePages: 5, //可视页数
                                currentPage: data.pageRequest.pageNum,  //当前页码
                                first: '<li class="page-item"><a class="page-link">首页</a></li>',
                                prev: '<li class="page-item"><a class="page-link" href="javascript:void(0);">上一页<\/a><\/li>',
                                next: '<li class="page-item"><a class="page-link" href="javascript:void(0);">下一页<\/a><\/li>',
                                last: '<li class="page-item"><a class="page-link" href="javascript:void(0);">最后一页<\/a><\/li>',
                                page: '<li class="page-item"><a class="page-link" href="javascript:void(0);">{{page}}<\/a><\/li>',
                                //页面初始化和页码点击时都会执行
                                onPageChange: function (page, type) {
                                    console.log("第" + page + "页, 类型:" + type);
                                    if (type == "change") {
                                        location.href = "book_list.html?pageNum=" + page;
                                    }
                                }
                            });
                        }
                    },

                    error: function (error) {
                        console.log(error);
                        if(error != null && error.status == 401) {
                            location.href = "login.html";
                        }
                    }
                });
            }

        也就是多增加 error 时,应该怎么做,上面是进行判断了返回响应的信息,响应是不是空,已经响应的状态码是不是401,是的话就给他跳转到登录界面。

        现在进行测试一下,URL:http://127.0.0.1:8080/book_list.html?pageNum=1,会跳转到登录界面

        观察日主,如图:

        打印了preHandle()方法执行后就没有后续的内容了。

        要是登录后,再访问 http://127.0.0.1:8080/book_list.html?pageNum=1,就会展示第一页的图书列表。

        还有其他接口的代码没改,后面会学习统一功能,到时候使用统一功能再一起改。


四、适配器模式

1、适配器模式定义

        适配器模式,也叫包装模式。将一个类的接口,转换成客户期望的另一个接口,适配器让原本接口不兼容的类也可以合作无间

        简单来说就是目标类不能直接使用,通过一个新类进行包装一下,适配调用方使用。把两个不兼容的接口通过一定的方式使之兼容。

        比如下图的两个接口,本身是不兼容的(参数类型不一样,参数个数不一样等等)

        可以通过适配器的方式,使之兼容

        而在日常生活中,适配器模式也非常常见,例如转换插头、网络转接头、耳机转接头等。

        出国旅游必备物品之一就是转换插头。不同国家的插头标准是不一样的,出国后我们手机 / 电脑充电器可能就没办法使用了。比如美国电器 110V,中国 220V,就要有一个适配器将 110V 转化为 220V。国内经常使用转换插头把两头转为三头,或者三头转两头。

2、适配器模式角色

Target目标接口(可以是抽象类或接口),客户希望直接用的接口

Adaptee适配者,但是与 Target 不兼容

Adapter适配器类,此模式的核心。通过继承或者引用适配者的对象,把适配者转为目标接口

client需要使用适配器的对象

3、适配器模式的实现

        场景:之前学习的 Slf4j 就使用了适配器模式Slf4j 提供了一系列打印日志的 API, 底层调用的是 log4j 或者 logback 来打日志,我们作为调用者,只需要调用 Slf4j 的API 即可

代码如下:

        Target:

public interface Slf4jLog {
    void log(String message);
}

        Adaptee:

public class Log4j {
    public void log4jPrint(String message) {
        System.out.println("我是Log4j, 打印内容是: " + message);
    }
}

        Adapter:

        Log4j 想使用 Slf4jLog log接口,但是不行,因为自身 打印的方法 和Slf4jLog 的不同。所以这时候就就需要使用适配器,定义一个类,通过继承 Slf4jLog 接口,来实现它的方法,再引入 Log4j,打印Log4j的内容。

public class Log4jAdapter implements Slf4jLog{
    private Log4j log4j;

    public Log4jAdapter(Log4j log4j) {
        this.log4j = log4j;
    }

    @Override
    public void log(String message) {
        log4j.log4jPrint("我是适配器, 打印日志为: " + message);
    }
}

        client

public class Main {
    public static void main(String[] args) {
        Slf4jLog slf4jLog = new Log4jAdapter(new Log4j());
        slf4jLog.log("我是客户端");
    }
}

        打印内容如下:

        可以看到,适配器就是起到一层包装的效果因为接口和 Slf4jLog 不同,所以就使用适配器类 实现 Slf4jLog 接口,同时引用 Log4j,使用 Log4j 的打印方法包装一层再给 Log4j 打印)。

        最终还是给 Log4j 打印适配器的作用就是给两个不同的接口,给其中一个接口进行包装一下,再丢给另一个接口进行使用

        可以看出,有了适配器模型,对已经定义好的接口,可以不进行修改,只需要通过适配器转换下,就可以更换日志框架,保障系统的平稳运行

4、适配器模式应用场景

        一般来说,适配器模式可以看做一种 “补偿模式”,用来 补救 设计上的缺陷。应用这种模式算是 “无奈之举”,如果在设计初期,我们就能协调规避接口不兼容的问题,就不需要使用适配器模式了

        所以,适配器模式更多的应用场景主要是对正在运行的代码进行改造,并且希望可以复用原有代码实现新的功能,比如版本升级等

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

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

相关文章

43、nginx的优化、防盗链、重定向、代理

nginx的优化、防盗链、重定向、代理 一、nginx的优化 1.1、隐藏版本号 server_tokens off;隐藏版本号 [roottest1 conf]# vim nginx.confserver_tokens off;[roottest1 conf]# nginx -t nginx: the configuration file /usr/local/nginx/conf/nginx.conf syntax is ok ngin…

顾客排队购买蛋挞问题(算法与数据结构设计)

课题内容和要求 顾客排队买蛋挞问题。有N个顾客排队&#xff0c;每人最多买M个。烘焙员每次烘焙1到K个蛋挞放入盘中&#xff0c;顾客只能购买盘中的蛋挞&#xff0c;未达到M个需重新排队。输出每个顾客购买情况和完成顺序。 例如—— 输入&#xff1a;N9&#xff0c;K5&#x…

Linux安装elasticsearch单机版

一、检查内核 uname -a uname -m 二、下载版本 下载版本选择自己服务器相同的内核版本 我这边是aaech64 ES下载地址 Kibana 下载地址 二、上传服务器解压 tar -xvf elasticsearch-8.14.1-linux-aarch64.tar.gz 三、安装ES 因为ES不能用root用户启动先创建用户 #新增 es …

Django QuerySet对象,all()方法

all()方法 在Django中&#xff0c;all()方法是QuerySet对象的一个方法&#xff0c;用于获取模型的所有实例。 当你调用ModelName.objects.all()时&#xff0c;Django会生成一个SQL查询&#xff0c;从数据库中获取该模型的所有记录&#xff0c;并返回一个QuerySet对象&#xf…

【产品运营】Saas的核心六大数据

国内头部软件公司的一季度表现惨不忍睹&#xff0c;为啥美国的还那么赚钱呢&#xff1f;其实核心是&#xff0c;没几个Saas产品经理是看数据的&#xff0c;也不知道看啥数据。 SaaS 行业&#xff0c;天天抛头露面、名头叫的响的 SaaS 产品&#xff0c;真没有几个赚钱的。 那为…

中国计量大学理学院访问赛氪网:共探校企合作新篇章来

2024年7月5日&#xff0c;中国计量大学理学院代表团莅临环球赛乐&#xff08;北京&#xff09;科技有限公司&#xff0c;进行了一场深入的调研交流活动。代表团成员包括中国计量大学理学院副院长王义康教授、数据科学系副主任刘学艺副教授以及金世举老师。此次访问旨在进一步强…

江洲的《家书》,岂止抵万金

题记 今晨6点钟&#xff0c;像往日一样的背上鱼具包&#xff0c;欲驾乘清凉舒适的晨风&#xff0c;前往味江河堤享受钓翁乐趣。孰料开门一看&#xff0c;朦胧的天空竟下着淅淅沥沥的小雨。 今年的天气异常&#xff0c;是笔者寄居“西川第一天”古镇5年来所未见&#xff1a;再…

stm32——外部中断EXTI

上回书说到定时器的级联&#xff0c;今天来谈谈外部中断EXTI。我使用的是STM32F103C8T6的学习板。仅供大家参考。 什么是中断呢&#xff1f;中断是指计算机在执行程序的过程中&#xff0c;当出现某些异常情况或特殊事件&#xff08;例如外部设备请求、定时时间到达、程序错误等…

YOLOV8花朵实例分割实战

原文:YOLOV8花朵实例分割实战 - 知乎 (zhihu.com) 一、代码: https://github.com/ultralytics/ultralytics​github.com/ultralytics/ultralytics 与先前几个版本相比,YOLOv8 模型更快、更准确,同时为训练模型提供统一框架,以执行以下基本任务: 目标检测;实例分割;图…

奇安信20240513笔试

题目一 解题思路 n转为字符串&#xff0c;如果位数为偶数&#xff0c;取前一半设为x&#xff0c;后一段为y&#xff0c;从x最低位开始&#xff0c;9&#xff0c;9*10&#xff0c;9*10*10。。。 到最高位&#xff0c;加x&#xff0c;如果x大于或等于y&#xff0c;加1. 位数为奇数…

武汉免费 【FPGA实战训练】 Vivado入门与设计师资课程

一&#xff0e;背景介绍 当今高度数字化和智能化的工业领域&#xff0c;对高效、灵活且可靠的技术解决方案的需求日益迫切。随着工业 4.0 时代的到来&#xff0c;工业生产过程正经历着前所未有的变革&#xff0c;从传统的机械化、自动化逐步迈向智能化和信息化。在这一背景下&…

全志A527 T527 cat /proc/cupinfo没有Serial问题

1.前言 我们有些客户是使用cpuinfo节点去获取系统的cpuid的,如下: cat /proc/cupinfo processor : 0 BogoMIPS : 48.00 Features : fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm lrcpc dcpop asimddp CPU impleme…

代码随想录-Day51

115. 不同的子序列 给你两个字符串 s 和 t &#xff0c;统计并返回在 s 的 子序列 中 t 出现的个数&#xff0c;结果需要对 109 7 取模。 示例 1&#xff1a; 输入&#xff1a;s “rabbbit”, t “rabbit” 输出&#xff1a;3 解释&#xff1a; 如下所示, 有 3 种可以从 …

基于 LlamaIndex、Claude-3.5 Sonnet 和 MongoDB,构建具有超级检索能力的智能体

节前&#xff0c;我们组织了一场算法岗技术&面试讨论会&#xff0c;邀请了一些互联网大厂朋友、今年参加社招和校招面试的同学。 针对大模型技术趋势、算法项目落地经验分享、新手如何入门算法岗、该如何准备面试攻略、面试常考点等热门话题进行了深入的讨论。 总结链接如…

Day1--每日一练

&#x1f341; 个人主页&#xff1a;爱编程的Tom&#x1f4ab; 本篇博文收录专栏&#xff1a;每日一练-算法篇&#x1f449; 目前其它专栏&#xff1a;c系列小游戏 c语言系列--万物的开始_ Java专栏等 &#x1f389; 欢迎 &#x1f44d;点赞✍评论⭐收藏&…

Java之父James Gosling宣布正式退休 创造无数人的饭碗

编程语言Java的创始人&#xff0c;被誉为“Java之父”的James Gosling&#xff0c;近日在社交媒体上宣布了自己正式退休的消息。Gosling表示&#xff1a;“我终于退休了。做了这么多年的软件工程师&#xff0c;现在是时候享受人生了。”他透露&#xff0c;在亚马逊的过去7年是非…

代码随想录算法训练营第四十七天|1143.最长公共子序列、 1035.不相交的线、53. 最大子序和、392.判断子序列

1143.最长公共子序列 题目链接&#xff1a;1143.最长公共子序列 文档讲解&#xff1a;代码随想录 状态&#xff1a;一开始没想明白为啥要 max(dp[i - 1][j], dp[i][j - 1]) 思路&#xff1a; 如果text1[i - 1] 与 text2[j - 1]相同&#xff0c;那么找到了一个公共元素&#xff…

GitLab介绍,以及add an SSH key

GitLab GitLab 是一个用于仓库管理系统的开源项目&#xff0c;现今并在国内外大中型互联网公司广泛使用。 git,gitlab,github区别 git 是一种基于命令的版本控制系统&#xff0c;全命令操作&#xff0c;没有可视化界面&#xff1b; gitlab 是一个基于git实现的在线代码仓库…

K8s驱逐场景以及规避方案参考 —— 筑梦之路

Pod 驱逐分为两种情况&#xff1a; 较安全驱逐 & 提高稳定性的良性驱逐 API 发起驱逐&#xff0c;典型案例&#xff1a;kubectl drain Node Not Ready 时&#xff0c;Controller Manager 发起的驱逐 有风险的驱逐 节点压力驱逐 节点磁盘空间不足、内存不足 或 Pid 不足&…

简易Qt串口助手

界面显示如下 关于串口类 初始化 设置串口号 设置波特率 打开串口 发送按钮功能实现 接收数据显示在控件中 关闭串口