SpringBoot统⼀功能处理

前言🍭

❤️❤️❤️SSM专栏更新中,各位大佬觉得写得不错,支持一下,感谢了!❤️❤️❤️

Spring + Spring MVC + MyBatis_冷兮雪的博客-CSDN博客

本章是讲Spring Boot 统⼀功能处理模块,也是 AOP 的实战环节,要实现的目标有以下 3 个:

  1. 使用拦截器实现用户登录权限的统一验证;
  2. 统⼀数据格式返回;
  3. 统⼀异常处理。

一、用户登录权限效验🍭

1、最初用户登录验证🍉

用户登录权限的发展从之前每个方法中自己验证用户登录权限,到现在统⼀的用户登录验证处理,它是⼀个逐渐完善和逐渐优化的过程。

@RestController
@RequestMapping("/user")
public class UserController {
    /**
     * 某⽅法 1
     */
    @RequestMapping("/m1")
    public Object method(HttpServletRequest request) {
        // 有 session 就获取,没有不会创建
        HttpSession session = request.getSession(false);
        if (session != null && session.getAttribute("userinfo") != null) {
            // 说明已经登录,业务处理
            return true;
        } else {
            // 未登录
            return false;
        }
    }

    /**
     * 某⽅法 2
     */
    @RequestMapping("/m2")
    public Object method2(HttpServletRequest request) {
        // 有 session 就获取,没有不会创建
        HttpSession session = request.getSession(false);
        if (session != null && session.getAttribute("userinfo") != null) {
            // 说明已经登录,业务处理
            return true;
        } else {
            // 未登录
            return false;
        }
    }
    // 其他⽅法。。。
}

从上述代码可以看出,每个方法中都有相同的用户登录验证权限,它的缺点是:

  1. 每个方法中都要单独写用户登录验证的方法,即使封装成公共方法,也⼀样要传参调用和在方法中进行判断。
  2. 添加控制器越多,调用用户登录验证的方法也越多,这样就增加了后期的修改成本和维护成本。
  3. 这些用户登录验证的方法和接下来要实现的业务几何没有任何关联,但每个方法中都要写⼀遍。 所以提供⼀个公共的 AOP 方法来进行统⼀的用户登录权限验证迫在眉睫。

2、Spring AOP 用户统⼀登录验证的问题🍉

说到统⼀的用户登录验证,我们想到的第⼀个实现方案是 Spring AOP 前置通知或环绕通知来实现,具体实现代码如下:

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class UserAspect {
    // 定义切点⽅法 controller 包下、⼦孙包下所有类的所有⽅法
    @Pointcut("execution(* com.example.demo.controller..*.*(..))")
    public void pointcut() {
    }

    // 前置⽅法
    @Before("pointcut()")
    public void doBefore() {

    }

    // 环绕⽅法
    @Around("pointcut()")
    public Object doAround(ProceedingJoinPoint joinPoint) {
        Object obj = null;
        System.out.println("Around ⽅法开始执⾏");
        try {
            // 执⾏拦截⽅法
            obj = joinPoint.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }

        System.out.println("Around 方法结束执行");
        return obj;
    }
}

如果要在以上 Spring AOP 的切面中实现用户登录权限效验的功能,有以下两个问题:

  1. 定义拦截的规则(表达式)非常难。(我们要对⼀部分方法进行拦截,而另⼀部分方法不拦截,如注册方法和登录方法是不拦截的,这样 的话排除方法的规则很难定义,甚至没办法定义)。
  2. 在切面类中拿到 HttpSession 比较难

那这样如何解决呢?

Spring 拦截器🍉

对于以上问题 Spring 中提供了具体的实现拦截器:HandlerInterceptor,拦截器的实现分为以下两个步骤:

  1. 创建自定义拦截器,实现 HandlerInterceptor 接口的 preHandle(执行具体方法之前的预处理方法。
  2. 将自定义拦截器加入WebMvcConfigurer 的 addInterceptors 方法中。

具体实现如下:

目录结构:

Ⅰ、实现拦截器🍓

关键步骤:
a.实现 HandlerInterceptor 接口
b.重写 preHeadler 方法,在方法中编写自己的业务代码

package com.example.demo.config;

import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

public class LoginInterceptor implements HandlerInterceptor {
    /**
     * 此方法返回一个 boolean,如果为 true 表示验证成功,可以继续执行后续流程如果是 false 表示验证失败,后面的流程不能执行了
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //用户登录业务判断
        HttpSession session=request.getSession(false);
        if (session != null && session.getAttribute("userinfo") != null) {
            // 说明用户已经登录
            return true;
        }
        // 可以调整到登录页面 or 返回一个 401/403 没有权限码
        response.sendRedirect("/login.html");
//        response.setStatus(403);
        return false;
    }
}

Ⅱ、将拦截器添加到配置文件中,并且设置拦截的规则🍓

package com.example.demo.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class AppConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor())
                .addPathPatterns("/**") // 拦截所有请求
                .excludePathPatterns("/user/login") // 排除的url地址(不拦截的url地址)
                .excludePathPatterns("/user/reg");
    }
}

其中:

  • addPathPatterns:表示需要拦截的 URL,**表示拦截任意方法(也就是所有方法)。
  • excludePathPatterns:表示需要排除的 URL。

说明:以上拦截规则可以拦截此项目中的使用 URL,包括静态文件(图片文件、JS 和 CSS 等文件)。

排除所有的静态资源:

    // 拦截器
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor())
                .addPathPatterns("/**") // 拦截所有接⼝
                .excludePathPatterns("/**/*.js")
                .excludePathPatterns("/**/*.css")
                .excludePathPatterns("/**/*.jpg")
                .excludePathPatterns("/login.html")
                .excludePathPatterns("/**/login"); // 排除接⼝
    }

UserController:

package com.example.demo.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


@RestController
@RequestMapping("/user")
public class UserController {

    /*    @GetMapping
        public String getMethod(){
            return "执行GET请求!";
        }
        @PostMapping
        public String postMethod(){
            return "执行POST请求!";
        }*/
    @RequestMapping("/getuser")
    public String getUser() {
        System.out.println("执行了 get User~");
        return "get user";
    }

    @RequestMapping("/login")
    public String login() {
        System.out.println("执行了 login~");
        return "login~";
    }

    @RequestMapping("/reg")
    public String reg() {
        System.out.println("执行了 reg~");
        return "reg~";
    }
}

Ⅲ、启动项目:🍓

不拦截:

 拦截:http://localhost:8080/user/getuser

 为什么会显示重定向次数过多?

这是因为这个login.html页面也被拦截了,所以它去访问时候就会一直重定向重定向去访问。

解决方法:

Ⅳ、拦截器实现原理🍓

正常情况下的调用顺序:

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

 所有的 Controller 执行都会通过⼀个调度器 DispatcherServlet 来实现,这⼀点可以从 Spring Boot 控制台的打印信息看出,如下图所示:

而所有方法都会执行 DispatcherServlet 中的 doDispatch 调度方法,doDispatch 源码如下: 

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        boolean multipartRequestParsed = false;
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

        try {
            try {
                ModelAndView mv = null;
                Exception dispatchException = null;

                try {
                    processedRequest = this.checkMultipart(request);
                    multipartRequestParsed = processedRequest != request;
                    mappedHandler = this.getHandler(processedRequest);
                    if (mappedHandler == null) {
                        this.noHandlerFound(processedRequest, response);
                        return;
                    }

                    HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
                    String method = request.getMethod();
                    boolean isGet = HttpMethod.GET.matches(method);
                    if (isGet || HttpMethod.HEAD.matches(method)) {
                        long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                        if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {
                            return;
                        }
                    }

                    if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                        return;
                    }

                    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                    if (asyncManager.isConcurrentHandlingStarted()) {
                        return;
                    }

                    this.applyDefaultViewName(processedRequest, mv);
                    mappedHandler.applyPostHandle(processedRequest, response, mv);
                } catch (Exception var20) {
                    dispatchException = var20;
                } catch (Throwable var21) {
                    dispatchException = new NestedServletException("Handler dispatch failed", var21);
                }

                this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
            } catch (Exception var22) {
                this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
            } catch (Throwable var23) {
                this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
            }

        } finally {
            if (asyncManager.isConcurrentHandlingStarted()) {
                if (mappedHandler != null) {
                    mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
                }
            } else if (multipartRequestParsed) {
                this.cleanupMultipart(processedRequest);
            }

        }
    }

从上述源码可以看出在开始执行 Controller 之前,会先调用 预处理方法 applyPreHandle,而applyPreHandle 方法的实现源码如下:

boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
        for(int i = 0; i < this.interceptorList.size(); this.interceptorIndex = i++) {
// 获取项⽬中使⽤的拦截器 HandlerInterceptor
            HandlerInterceptor interceptor = (HandlerInterceptor)this.interceptorList.get(i);
            if (!interceptor.preHandle(request, response, this.handler)) {
                this.triggerAfterCompletion(request, response, (Exception)null);
                return false;
            }
        }

        return true;
    }

从上述源码可以看出,在 applyPreHandle 中会获取所有的拦截器 HandlerInterceptor 并执行拦截器中的 preHandle 方法,这样就和咱们前面定义的拦截器对应上了,如下图所示:

此时用户登录权限的验证方法就会执行,这就是拦截器的实现原理。  

二、统⼀异常处理🍭

Ⅰ、实现🍓

统一异常处理使用的是 @ControllerAdvice + @ExceptionHandler 来实现的,@ControllerAdvice 表示控制器通知类,@ExceptionHandler 是异常处理器,两个结合表示当出现异常的时候执行某个通知, 也就是执行某个方法事件,具体实现代码如下:

package com.example.demo.config;

import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.HashMap;

@ControllerAdvice
@ResponseBody
public class MyExHandler {

    /**
     * 拦截所有的空指针异常,进行统一的数据返回
     */
    @ExceptionHandler(NullPointerException.class)
    public HashMap<String, Object> nullException(NullPointerException e) {
        HashMap<String, Object> result = new HashMap<>();
        result.put("code", "-1");
        result.put("msg", "空指针异常:" + e.getMessage()); // 错误码的描述信息
        result.put("data", null);
        return result;
    }

    @ExceptionHandler(Exception.class)//保底的默认异常
    public HashMap<String, Object> exception(Exception e) {
        HashMap<String, Object> result = new HashMap<>();
        result.put("code", "-1");
        result.put("msg", "异常:" + e.getMessage()); // 错误码的描述信息
        result.put("data", null);
        return result;
    }

}

Ⅱ、添加异常🍓

UserController

 @RequestMapping("/login")
    public String login() {
        Object obj = null;
        obj.hashCode();
        System.out.println("执行了 login~");
        return "login~";
    }

    @RequestMapping("/reg")
    public String reg() {
        int num = 10 / 0;
        System.out.println("执行了 reg~");
        return "reg~";
    }

Ⅲ、启动程序:🍓

login:

 reg:

 方法名和返回值可以自定义,其中最重要的是 @ExceptionHandler(Exception.class) 注解。 

以上方法表示,如果出现了异常就返回给前端⼀个 HashMap 的对象,其中包含的字段如代码中定义的那样。 我们可以针对不同的异常,返回不同的结果。 

三、统一数据返回格式🍭

1.创建一个类,并添加 @ControllerAdvice
2.实现ResponseBodyAdvice接口,并重写supports和beforeBodywrite (统一对象就是此方法中实现的)

1、为什么需要统一数据返回格式?🍉

统一数据返回格式的优点有很多,比如以下几个:

  1. 便前端程序员更好的接收和解析后端数据接口返回的数据。
  2. 降低前端程序员和后端程序员的沟通成本,按照某个格式实现就行了,因为所有接口都是这样返回的。
  3. 有利于项目统⼀数据的维护和修改。
  4. 有利于后端技术部门的统⼀规范的标准制定,不会出现稀奇古怪的返回内容。

2、统一数据返回格式的实现🍉

Ⅰ、实现🍓

统⼀的数据返回格式可以使用 @ControllerAdvice + ResponseBodyAdvice 的方式实现。

package com.example.demo.config;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import java.util.HashMap;

@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {

    @Autowired
    private ObjectMapper objectMapper;

    /**
     * 此方法返回 true 则执行下面 beforeBodyWrite 方法
     * 反之则不执行
     */
    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        return true;
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        HashMap<String, Object> result = new HashMap<>();
        result.put("code", 200);
        result.put("msg", "");
        result.put("data", body);
        // 需要特殊处理,因为 String 在转换的时候会报错
        if (body instanceof String) {
            try {
                return objectMapper.writeValueAsString(result);
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
        }
        return result;
    }
}

如果没有对Sting进行特殊处理,就会有异常出现: 

Ⅱ、添加实现类🍓

UserController

@RequestMapping("/getnum")
    public Integer getNumber() {
        return new Random().nextInt(10);
    }
    @RequestMapping("/getuser")
    public String getUser() {
        System.out.println("执行了 get User~");
        return "get user";
    }

Ⅲ、启动程序🍓

Ⅳ、总结🍓

上面只是以一种简单的方式来向大家介绍如何去统一数据返回格式,在实际工作中并不会这样去使用

而是将统⼀的返回格式(包含:status、data、msg 字段)进行封装成一个类:

根据不同操作成功和操作失败的情况,重写了不同的方法:

package com.example.demo.common;

import lombok.Data;

import java.io.Serializable;

/**
 * 统一数据格式返回
 */
@Data
public class AjaxResult implements Serializable {//支持序列化
    // 状态码
    private Integer code;
    // 状态码描述信息
    private String msg;
    // 返回的数据
    private Object data;

    /**
     * 操作成功返回的结果,需要 data
     */
    public static AjaxResult success(Object data) {
        AjaxResult result = new AjaxResult();
        result.setCode(200);
        result.setMsg("");
        result.setData(data);
        return result;
    }
    //重载success
    public static AjaxResult success(int code, Object data) {
        AjaxResult result = new AjaxResult();
        result.setCode(code);
        result.setMsg("");
        result.setData(data);
        return result;
    }

    public static AjaxResult success(int code, String msg, Object data) {
        AjaxResult result = new AjaxResult();
        result.setCode(code);
        result.setMsg(msg);
        result.setData(data);
        return result;
    }

    /**
     * 返回失败结果
     */
    public static AjaxResult fail(int code, String msg) {
        AjaxResult result = new AjaxResult();
        result.setCode(code);
        result.setMsg(msg);
        result.setData(null);
        return result;
    }

    public static AjaxResult fail(int code, String msg, Object data) {
        AjaxResult result = new AjaxResult();
        result.setCode(code);
        result.setMsg(msg);
        result.setData(data);
        return result;
    }
}

 

 

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

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

相关文章

Redis 扩展资料

Redis 扩展资料 1.缓存简介2.缓存分类3.常⻅缓存使⽤4.Redis 数据类型和使⽤5.持久化6.常⻅⾯试题7.Redis 集群&#xff08;选学&#xff09; 1.缓存简介 2.缓存分类 3.常⻅缓存使⽤ 4.Redis 数据类型和使⽤ 5.持久化 Redis 和 Memcached 有什么区别&#xff1f; 6.常⻅⾯试…

Java日志框架-JUL

JUL全称Java util logging 入门案例 先来看着入门案例&#xff0c;直接创建logger对象&#xff0c;然后传入日志级别和打印的信息&#xff0c;就能在控制台输出信息。 可以看出只输出了部分的信息&#xff0c;其实默认的日志控制器是有一个默认的日志级别的&#xff0c;默认就…

Linux命令200例:nc非常有用的网络工具(常用)

&#x1f3c6;作者简介&#xff0c;黑夜开发者&#xff0c;全栈领域新星创作者✌。CSDN专家博主&#xff0c;阿里云社区专家博主&#xff0c;2023年6月csdn上海赛道top4。 &#x1f3c6;数年电商行业从业经验&#xff0c;历任核心研发工程师&#xff0c;项目技术负责人。 &…

PHP-MD5注入

0x00 前言 有些零散的知识未曾关注过&#xff0c;偶然捡起反而更加欢喜。 0x01 md5 注入绕过 md5函数有两个参数&#xff0c;第一个参数是要进行md5的值&#xff0c;第二个值默认为false&#xff0c;如果为true则返回16位原始二进制格式的字符串。意思就是会将md5后的结果当…

MVCC 是否彻底解决了事物的隔离性 ?

目录 1. 什么是 MVCC 2. MVCC 是否彻底解决了事物的隔离性 3. MySQL 中如何实现共享锁和排他锁 4. MySQL 中如何实现悲观锁和乐观锁 1. 什么是 MVCC MVCC&#xff08;Multi-Version Concurrency Control&#xff0c;多版本并发控制&#xff09;是一种多版本并发控制机制&…

vue项目使用qrcodejs2遇到Cannot read property ‘appendChild‘ of null

这个问题是节点还没创建渲染完就读取节点&#xff0c;这个时候应该先让节点渲染完成在生成&#xff0c;解决方法有以下两种 1、使用$nextTick&#xff08;&#xff09;方法进行&#xff0c;这个方法是用来在节点创建渲染完成后进行的操作 that.$nextTick(() > {let qrcode …

Java基础知识小结(内部类、BigInteger、枚举、接口、重写重载和序列化)

一、Java内部类 1、内部类 在Java中&#xff0c;也可以嵌套类&#xff08;类中的类&#xff09;。嵌套类的目的是将属于同一类的类分组&#xff0c;这使代码更具可读性和可维护性。 要访问内部类&#xff0c;请创建外部类的对象&#xff0c;然后创建内部类的对象&#xff1a;…

SpringBoot中优雅的实现隐私数据脱敏(提供Gitee源码)

前言&#xff1a;在实际项目开发中&#xff0c;可能会对一些用户的隐私信息进行脱敏操作&#xff0c;传统的方式很多都是用replace方法进行手动替换&#xff0c;这样会由很多冗余的代码并且后续也不好维护&#xff0c;本期就讲解一下如何在SpringBoot中优雅的通过序列化的方式去…

【框架类】—MVVM框架

一、MVVM框架有哪些 Vue.jsReact.jsAngular.js 二、对MVVM的认识 1. MVC是什么 全称 Model View Controller, 它采用模型(Model)-视图(View)-控制器(controller)的方法把业务逻辑、数据与界面显示分离 2. MVVM的定义 MVVM是一种软件架构模式&#xff0c;它代表了模型 --视…

深入理解JVM——垃圾回收与内存分配机制详细讲解

所谓垃圾回收&#xff0c;也就是要回收已经“死了”的对象。 那我们如何判断哪些对象“存活”&#xff0c;哪些已经“死去”呢&#xff1f; 一、判断对象已死 1、引用计数算法 给对象中添加一个引用计数器&#xff0c;每当有一个地方引用它时&#xff0c;计数器就加一&…

探索无限创造力的星辰大道,画出想象的浩瀚宇宙!-turtle

介绍 视频教程地址在此&#xff1a;https://www.bilibili.com/video/BV1Pm4y1H7Tb/ 大家好&#xff0c;欢迎来到本视频&#xff01;今天&#xff0c;我们将一同探索Python编程世界中的一个有趣而创意的库——Turtle库。无需专业绘画技能&#xff0c;你就可以轻松地用代码绘制…

人工智能在网络安全中的作用:当前的局限性和未来的可能性

人工智能 (AI) 激发了网络安全行业的想象力&#xff0c;有可能彻底改变安全和 IT 团队处理网络危机、漏洞和勒索软件攻击的方式。 然而&#xff0c;对人工智能的能力和局限性的现实理解至关重要&#xff0c;并且存在许多挑战阻碍人工智能对网络安全产生直接的变革性影响。 在…

概述、搭建Redis服务器、部署LNP+Redis、创建Redis集群、连接集群、集群工作原理

Top NSD DBA DAY09 案例1&#xff1a;搭建redis服务器案例2&#xff1a;常用命令限案例3&#xff1a;部署LNPRedis案例4&#xff1a;创建redis集群 1 案例1&#xff1a;搭建redis服务器 1.1 具体要求如下 在主机redis64运行redis服务修改服务运行参数 ip 地址192.168.88.6…

在 ubuntu 18.04 上使用源码升级 OpenSSH_7.6p1到 OpenSSH_9.3p1

1、检查系统已安装的当前 SSH 版本 使用命令 ssh -V 查看当前 ssh 版本&#xff0c;输出如下&#xff1a; OpenSSH_7.6p1 Ubuntu-4ubuntu0.7, OpenSSL 1.0.2n 7 Dec 20172、安装依赖&#xff0c;依次执行以下命令 sudo apt update sudo apt install build-essential zlib1g…

[论文笔记]Glancing Transformer for Non-Autoregressive Neural Machine Translation

引言 这是论文Glancing Transformer for Non-Autoregressive Neural Machine Translation的笔记。 传统的非自回归文本生成速度较慢,因为需要给定之前的token来预测下一个token。但自回归模型虽然效率高,但性能没那么好。 这篇论文提出了Glancing Transformer,可以只需要一…

6.RocketMQ之索引文件ConsumeQueue

本文着重分析为consumequeue/topic/queueId目录下的索引文件。 1.ConsumeQueueStore public class ConsumeQueueStore {protected final ConcurrentMap<String>, ConcurrentMap<Integer>, ConsumeQueueInterface>> consumeQueueTable;public boolean load(…

Selenium 测试用例编写

编写Selenium测试用例就是模拟用户在浏览器上的一系列操作&#xff0c;通过脚本来完成自动化测试。 编写测试用例的优势&#xff1a; 开源&#xff0c;免费。 支持多种浏览器 IE&#xff0c;Firefox&#xff0c;Chrome&#xff0c;Safari。 支持多平台 Windows&#xff0c;Li…

Xxl-job安装部署以及SpringBoot集成Xxl-job使用

1、安装Xxl-job&#xff1a; 可以使用docker拉取镜像部署和源码编译两种方式&#xff0c;这里选择源码编译安装。 代码拉取地址&#xff1a; https://github.com/xuxueli/xxl-job/tree/2.1.2 官方开发文档&#xff1a; https://www.xuxueli.com/xxl-job/#%E3%80%8A%E5%88%…

Android5:活动生命周期

创建项目Stopwatch activity_main.xml <?xml version"1.0" encoding"utf-8"?> <LinearLayoutxmlns:android"http://schemas.android.com/apk/res/android"xmlns:tools"http://schemas.android.com/tools"android:layout_w…

ROS-PyQt小案例

前言&#xff1a;目前还在学习ROS无人机框架中&#xff0c;&#xff0c;&#xff0c; 更多更新文章详见我的个人博客主页【前往】 ROS与PyQt5结合的小demo&#xff0c;用于学习如何设计一个界面&#xff0c;并与ROS中的Service和Topic结合&#xff0c;从而控制多个小乌龟的运动…