Spring Boot 统一功能处理

目录

1.用户登录权限效验

1.1 Spring AOP 用户统一登录验证的问题

1.2 Spring 拦截器

1.2.1 自定义拦截器

1.2.2 将自定义拦截器加入到系统配置

1.3 拦截器实现原理

1.3.1 实现原理源码分析

2. 统一异常处理

2.1 创建一个异常处理类

2.2 创建异常检测的类和处理业务方法

3. 统一数据返回格式

3.1 统一数据返回的实现

3.2 返回String报错问题

问题解决


1.用户登录权限效验

1.1 Spring AOP 用户统一登录验证的问题

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

@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) {
        System.out.println("Around 方法开始执行");
        Object obj = joinPoint.proceed();
        System.out.println("Around 方法结束执行");
        return obj;
    }
}

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

  1. 没办法获取到 HttpSession 对象。
  2. 我们要对一部分方法进行拦截,而另一部分方法不拦截,如注册方法和登录方法是不拦截的,这样的话排除方法的规则很难定义,甚至没办法定义。

那这样如何解决呢?

1.2 Spring 拦截器

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

  1. 创建自定义拦截器,实现 Handlerlnterceptor 接口的 preHandle(执行具体方法之前的预处理)方法。
  2. 将自定义拦截器配置到系统配置项, 并且设置合理的拦截规则, 也就是将自定义拦截器加入 WebMvcConfigureraddlnterceptors 方法中。具体实现如下.
Spring拦截器能够拿到参数并方便设置拦截规则, 也不需要AspectJ表达式.

1.2.1 自定义拦截器

新建一个普通的Spring Boot项目.

接下来使用代码来实现一个用户登录的权限效验,自定义拦截器是一个普通类,具体实现代码如下:

@Component
public class LoginInterceptor implements HandlerInterceptor {
    // 调用目标方法之前执行的方法
    // 此方法返回 boolean 类型的值,
    //      如果返回 true , 表示(拦截器)验证成功, 继续走后续的流程, 执行目标方法;
    //      如果返回 false , 这表示拦截器执行失败, 验证未通过, 后续的流程和目标方法不要执行了.
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 用户登录判断业务
        HttpSession session = request.getSession(false);
        if (session != null && session.getAttribute("session_userinfo") != null) {
            // 用户已经登录
            return true;
        }
//        response.sendRedirect("http://www.baidu.com");
//        response.setStatus(401);
        return false;
    }
}

1.2.2 将自定义拦截器加入到系统配置

要实现接口WebMvcConfigurer, 它里面有大量的方法, 其中addInterceptors方法, 需要我们实现.

也就是说当我们实现WebMvcConfigurer这个类, 那么这个类里面它内置了一个API, 那么我们去重写这个API就可以实现将我们自定义的拦截器写在项目当中.

可以看到, 这个是将registry注册器交给框架, 那么我们在重写的时候就拿这个registry去设置相应的规则即可.

具体实现代码如下:

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

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor)   // 自定义的拦截器添加到系统配置项中
                .addPathPatterns("/**")     // 拦截所有URL
                .excludePathPatterns("/user/login")     // 排除 url /user/login 不拦截
                .excludePathPatterns("/user/reg")
                .excludePathPatterns("/image/**")    // 排除 image 文件夹下的所有文件
        ;
    }
}

其中:

  • addPathPatterns: 表示需要拦截的 URL,“**”表示拦截任意方法 (多级的全部方法), "*"则表示一级目录的所有.
  • excludePathPatterns: 表示需要排除的 URL。

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


我们来看下代码是否能够实现拦截的目标.

@RestController
@RequestMapping("/user")
public class UserController {
    @RequestMapping("/login")
    public String login() {
        return "login";
    }

    @RequestMapping("/index")
    public String index() {
        return "index";
    }

    @RequestMapping("/reg")
    public String reg() {
        return "reg";
    }
}

启动项目后访问验证一下我们这个拦截器:

访问reg, reg是被排除故可访问.

访问index, 被拦截, 无响应信息.

通过F12可以看到index被拦截器拦截了, 这个目标方法没有被调用, 所以没有响应信息.

经过自定义拦截器中的设置可以看到响应:


如果拦截器执行失败了false, 那么后面的代码也不会走, 这个时候当我们返回false的时候, 前端人员如何拿到相关信息以知道是拦截器出错还是代码出错, 还是其他的问题出错?

LoginInterceptor中的return false前添加相关代码, 那么我们使用Servlet的方式打印给前端就可以解决.:

response.setContentType("application/json;charset=utf8");
response.getWriter().println("{\"code\":-1,\"msg\":\"登录失败\",\"data\":\"\"}");

启动项目, 访问index, 可以看到通过response打印了相关错误信息.


1.3 拦截器实现原理

对于一个标准的后端程序来说, 正常情况下的调用顺序:

用户访问后端程序, 那么访问的时候无论是用户还是前端程序员, 都是会把请求发送给控制器, 控制器进行参数的校验, 如果校验没问题之后会把请求发送给服务层(也就是调用服务层), 然后服务层再去决定要调用几个Mapper, 然后Mapper会去调用数据库, 数据库会把结果返回给Mapper, 然后按着 来时的路 回去给前端用户.

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

1.3.1 实现原理源码分析

所有的 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;
                Object 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;
                        }
                    }

                    // 调用预处理(重点) [执行我们拦截器的代码; 拦截器方法为false就直接返回否则调用Controller]
                    if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                        return;
                    }

                    // 执行 Controller 中的业务 [执行我们自己方法的代码, 过了拦截器之后的方法]
                    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 中会获取所有的拦截器 Handlerlinterceptor 并执行拦截器中的 preHandle 方法,这样就会咱们前面定义的拦截器对应上了,如下图所示:

2. 统一异常处理

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

2.1 创建一个异常处理类

@ControllerAdvice
public class MyExceptionAdvice {
}
@ControllerAdvice是针对于 controller的通知, 是针对于controller的增强方法.
当加了这个注解之后, 它会去监测控制器的异常, 如果控制器发生异常了, 那么底下的类就能感知的到, 感知到之后就能根据写的业务代码将相应的代码返回给前端.

2.2 创建异常检测的类和处理业务方法

拦截方法可以针对不同的拦截去写相应的处理代码.
@ControllerAdvice
@ResponseBody    // 加在类上表示类中所有方法都可以返回一个 JSON 的数据
public class MyExceptionAdvice {
    // 处理空指针异常
    // 如果出现了异常就返回给前端一个 HashMap 的对象
    @ExceptionHandler(NullPointerException.class)
    public HashMap<String, Object> doNullPointerException(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> doException(Exception e) {
        HashMap<String, Object> result = new HashMap<>();
        result.put("code", -300);
        result.put("msg", "Exception: " + e.getMessage());
        result.put("data", null);
        return result;
    }
}
PS: 方法名和返回值可以自定义,其中最重要的是 @ExceptionHandler(Exception.class) 注解。
@RestController
@RequestMapping("/user")
public class UserController {
    @RequestMapping("/login")
    public int login() {
        Object obj = null;
        System.out.println(obj.hashCode());
        return 1;
    }
}

启动以访问:

可以看到, 前端没有报错, 说明通知到了前端.

改为:

    @RequestMapping("/login")
    public int login() {
//        Object obj = null;
//        System.out.println(obj.hashCode());
        int num = 10 / 0;
        return 1;
    }

再次访问user/login,

说明上面的Advice只是处理了空指针异常.

解决: 在MyExceptionAdvice中加入默认异常处理:

    // 默认的异常处理(当具体的异常匹配不到时, 会执行此方法)
    @ExceptionHandler(Exception.class)
    public HashMap<String, Object> doException(Exception e) {
        HashMap<String, Object> result = new HashMap<>();
        result.put("code", -300);
        result.put("msg", "Exception: " + e.getMessage());
        result.put("data", null);
        return result;
    }

再次访问user/login之后, 可以看到返回了算数异常. 所以说明当子类没有找到相应的异常处理之后, 就会找父类的Exception.

异常较多的时候, 交给前端后, 前端如何处理?

如果异常会走到 MyExceptionAdvice,就说明这个异常是我们后端程序员不可知的, 所以这个异常检测类是用于意外异常的拦截. 因为正常的业务异常在业务代码中会直接报出, 而意外的异常里面, 重要的是以正常的格式返回给前端的状态码, 至于具体的内容是什么, 大概率前端用不到.

3. 统一数据返回格式

统一数据返回格式的优点有很多,比如以下几个:
1. 方便前端程序员更好的接收和解析后端数据接口返回的数据
2. 降低前端程序员和后端程序员的沟通成本,任何时候返回的都是状态, 状态描述符, 数据, 按照某个格式实现就行了,因为所有接口都是这样返回的。
3. 有利于项目统一数据的维护和修改
4. 有利于后端技术部门的统一规范的标准制定,不会出现稀奇古怪的返回内容

3.1 统一数据返回的实现

统一的数据返回格式可以使用 @ControllerAdvice + ResponseBodyAdvice 的方式实现,当写下面这些代码的时候, 就相当于是在返回之前做了一个拦截操作, 所有的返回之前都会走这里面的两个重写的方法(即在返回之前进行数据重写), 具体实现代码如下:

@ControllerAdvice   // 第一步
public class ResponseAdvice implements ResponseBodyAdvice {     
    // 第二步, 实现ResponseBodyAdvice接口并重写supports()与beforeBodyWrite()


    // 是否执行 beforeBodyWrite 方法, true=执行, 重写返回结果
    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        return true;
    }

    // 返回数据之前进行数据重写, body是业务代码的返回结果, 即原始返回值
    @SneakyThrows
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        if (body instanceof String){
            // 返回一个 将对象转换成 JSON String 字符串

//            objectMapper.writeValueAsString(request);

            return "{\"code\":200,\"msg\":\"\",\"data\":\"" + body +"\"}";
        }
        // 假定标准的返回值为 HashMap<String,Object>
        // 相关属性为 code,msg,data

        // 判断返回类型是否符合假定的标准返回值
        if (body instanceof HashMap) {
            return body;    // 符合假定的标准返回值则直接返回body
        }
        // 不符合假定的标准返回值则 重写返回结果, 让其返回一个统一的数据格式
        HashMap<String, Object> result = new HashMap<>();
        result.put("code", 200);
        result.put("data", body);
        result.put("msg", "");

        return result;
    }
}
    // 返回 int 类型    
    @RequestMapping("/login")
    public int login() {
        return 1;
    }

    // 返回假定的标准数据格式
    @RequestMapping("/reg")
    public HashMap<String, Object> reg() {
        HashMap<String, Object> result = new HashMap<>();
        result.put("code", 200);
        result.put("data", 1);
        result.put("msg", "");
        return result;
    }

可以看到, 返回的都是标准的数据格式.


3.2 返回String报错问题

但是, 统一异常处理在遇到String的时候返回会报错.

    @RequestMapping("/sayHi")
    public String sayHi() {
        return "say hi";
    }

异常日志:

2023-08-19 17:17:19.398 WARN 9316 --- [nio-8080-exec-8] .m.m.a.ExceptionHandlerExceptionResolver : Resolved [java.lang.ClassCastException: java.util.HashMap cannot be cast to java.lang.String]
注: 这里是由于之前设置了拦截器所以报了异常状态码是-300, 如果没有拦截器会直接报错500, 报错如下图.

没有拦截器的500报错图:

在异常状态码为-300的日志中可以看到, 显示了HashMap不能转换为String.

那么为什么我们明明在重写beforeBodyWrite()的时候已经是将String转换成HashMap了, 为什么又报错HashMap不能转换为String?

首先, String转换的执行流程是分为以下三步:

  1. 方法返回了String
  2. 统一数据返回之前的处理是: 将String转换成HashMap.
  3. 最终将HashMap转换成application/json字符串返回给前端(接口).

所以HashMap转换成String出错是在第三步发生的, 也就是异常日志的内容.

那么出错的原因就是在第三步时程序会去判断原body的类型是什么, 根据body的类型来选择相应的消息转换器进行转换. 也就是下面两种情况:

  • 如果是String, 那么它就会使用一个叫做StringHttpMessageConverter的转换器进行类型的转换.
  • 如果不是String, 那么它就会使用HttpMessageConverter的转换器进行类型转换.

正是因为上面选择转换器进行类型转换的动作, 所以就会触发bug, 在判断的时候是使用原类型进行判断的, 但是在转换的时候是拿HashMap进行转换的, 这个时候使用StringHttpMessageConverter去转换HashMap的时候这个转换器试图将HashMap转换成String JSON字符串, 但会发现无法转换, 此时就会直接报错. (可以认为这是Spring MVC在设计上的问题)

问题解决

  1. 将StringHttpMessageConverter这个转换器从项目中去掉.

我们可以通过修改当前项目的配置文件, 然后把StringHttpMessageConverter去掉, 这个时候就只能使用HttpMessageConverter来进行转换, 这个时候它就不会出错.

新建MyConfig类, 进行相关配置.

// 移除StringHttpMessageConverter
@Configuration  // 第一步, 加入 Spring 中
public class MyConfig implements WebMvcConfigurer {     // 实现 WebMvcConfigurer, 这样当前类才是一个系统配置项

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.removeIf(converter -> converter instanceof StringHttpMessageConverter);
    }
}

  1. 在统一数据返回格式代码重写时, 单独处理String类型, 让其返回一个String字符串,而非HashMap.
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
    @Autowired
    private ObjectMapper objectMapper;

    // ..此处省略 supports重写的代码, 节省篇幅, 具体代码同前文

    @SneakyThrows
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {

        // ..此处省略 body类型判断 以及 重写HashMap 代码, 见前文

        if (body instanceof String){
            // 返回一个 将对象转换成 JSON String 字符串
            return objectMapper.writeValueAsString(result);
//            return "{\"code\":200,\"msg\":\"\",\"data\":\"" + body +"\"}";
        }
        return result;
    }
}

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

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

相关文章

【C# 基础精讲】LINQ to XML查询

LINQ to XML 是 C# 中用于查询和操作 XML 数据的强大工具。它允许您使用 LINQ 查询语法对 XML 文档进行查询、过滤、投影等操作&#xff0c;从而更加方便地处理 XML 数据。本文将详细介绍 LINQ to XML 的基本概念、常见操作以及示例&#xff0c;帮助您了解如何在 C# 中使用 LIN…

微软Win11 Dev预览版Build23526发布

近日&#xff0c;微软Win11 Dev预览版Build23526发布&#xff0c;修复了不少问题。牛比如斯Microsoft&#xff0c;也有这么多bug&#xff0c;所以你写再多bug也不作为奇啊。 主要更新问题 [开始菜单&#xff3d; 修复了在高对比度主题下&#xff0c;打开开始菜单中的“所有应…

泛型编程 学习笔记

#include "iostream"using namespace std;template<typename T> void Print(T a) {cout << a << endl; }int main() {int a 5;double b 2.3;char c e;string d "sdfasd";Print(a);Print(b);Print(c);Print(d);return 0; } 它可以不用…

LLaMA-7B微调记录

Alpaca&#xff08;https://github.com/tatsu-lab/stanford_alpaca&#xff09;在70亿参数的LLaMA-7B上进行微调&#xff0c;通过52k指令数据&#xff08;https://github.com/tatsu-lab/stanford_alpaca/blob/main/alpaca_data.json&#xff09;&#xff0c;在8个80GB A100上训…

【Java转Go】快速上手学习笔记(一)之环境安装篇

前言 前两天开始学习Go&#xff0c;需要写篇笔记记录总结一下。 Go它也是可以做web开发的&#xff0c;就像Java一样&#xff0c;做JavaWeb项目&#xff0c;Go也可以做GoWeb项目。当然Go的作用用处肯定不止这个&#xff0c;还有很多&#xff0c;只是因为我目前的话&#xff0c…

threejs使用gui改变相机的参数

调节相机远近角度 定义相机的配置&#xff1a; const cameraConfg reactive({ fov: 45 }) gui中加入调节fov的方法 const gui new dat.GUI();const cameraFolder gui.addFolder("相机属性设置");cameraFolder.add(cameraConfg, "fov", 0, 100).name(…

代码随想录-字符串01 344.反转字符串541. 反转字符串II替换空格翻转字符串里的单词左旋转字符串

题目目录 ● 344.反转字符串 ● 541. 反转字符串II ● 剑指Offer 05.替换空格 ● 151.翻转字符串里的单词 ● 剑指Offer58-II.左旋转字符串 344.反转字符串 344.反转字符串 很经典的字符串考察点&#xff0c;考察对双指针的熟悉程度。 解法是通过双指针从字符串数组两边向中…

ajax-axios-url-form-serialize 插件

AJAX AJAX 概念 1.什么是 AJAX ? mdn 使用浏览器的 XMLHttpRequest 对象 与服务器通信 浏览器网页中&#xff0c;使用 AJAX技术&#xff08;XHR对象&#xff09;发起获取省份列表数据的请求&#xff0c;服务器代码响应准备好的省份列表数据给前端&#xff0c;前端拿到数据数…

Vue3 setup中使用$refs

在 Vue 3 中的 Composition API 中&#xff0c;$refs 并不直接可用于 setup 函数。这是因为 $refs 是 Vue 2 的实例属性&#xff0c;而在 Vue 3 中&#xff0c;setup 函数是与模板实例分离的&#xff0c;不再使用实例属性。 实际工作中确实有需求&#xff0c;在setup 函数使用…

windows服务器下java程序健康检测及假死崩溃后自动重启应用、开机自动启动

前两天由于项目需要&#xff0c;一个windows上的批处理任务&#xff08;kitchen.bat&#xff09;&#xff0c;需要接到mq的消息通知后执行&#xff0c;为了快速实现这里我们通过springboot写了一个jar程序&#xff0c;用于接收mq的消息&#xff0c;并调用bat文件。 本程序需要实…

MFC140.dll缺失的修复方法,安装MFC140.dll文件

大家好&#xff0c;今天我要和大家分享的是如何正确安装和使用MFC140.dll。MFC140.dll是一种常见的动态链接库文件&#xff0c;它是Microsoft Foundation Classes(MFC)的一部分&#xff0c;被广泛应用于Windows操作系统中的各种应用程序中。在本文中&#xff0c;我们将详细介绍…

Debian10: 安装nut服务器(UPS)

UPS说明&#xff1a; UPS的作用就不必讲了&#xff0c;我选择是SANTAKTGBOX-850&#xff0c;规格为 850VA/510W&#xff0c;可以满足所需&#xff0c;关键是Debian10自带了驱动可以支持&#xff0c;免去安装驱动&#xff0c;将UPS通过USB线连接服务器即可&#xff0c;如下图所示…

大模型基础02:GPT家族与提示学习

大模型基础&#xff1a;GPT 家族与提示学习 从 GPT-1 到 GPT-3.5 GPT(Generative Pre-trained Transformer)是 Google 于2018年提出的一种基于 Transformer 的预训练语言模型。它标志着自然语言处理领域从 RNN 时代进入 Transformer 时代。GPT 的发展历史和技术特点如下: GP…

Java 日期格式(yyyy-MM-dd HH:mm:ss SSS)

常用格式为&#xff1a;yyyy-MM-dd HH:mm:ss 以 2019-12-31 06:07:59:666 时间为例&#xff1a; 字符 含义 Example y&#xff08;小写的y&#xff09; 年 yyyy---->2019 M&#xff08;大写的M&#xff09; 月 MM---->12 d&#xff08;小写的d&#xff09; 一…

论文笔记 Graph Attention Networks

2018 ICLR 1 intro 1.1. GCN的不足 无法完成inductive任务 inductive任务是指&#xff1a; 训练阶段与测试阶段需要处理的graph不同。通常是训练阶段只是在子图上进行&#xff0c;测试阶段需要处理未知的顶点。GGN 的参数依赖于邻接矩阵A/拉普拉斯矩阵L&#xff0c;所以换了…

DTC 19服务学习1

在UDS&#xff08;统一诊断服务&#xff09;协议中&#xff0c;0x19是用于DTC&#xff08;诊断故障代码&#xff09;信息的服务。以下是你提到的子服务的功能和作用&#xff1a; 0x01 - 报告DTC按状态掩码。这个子服务用于获取当前存储在ECU中的DTC列表。状态掩码用于过滤DTC&a…

【Mariadb高可用MHA】

目录 一、概述 1.概念 2.组成 3.特点 4.工作原理 二、案例介绍 1.192.168.42.3 2.192.168.42.4 3.192.168.42.5 4.192.168.42.6 三、实际构建MHA 1.ssh免密登录 1.1 所有节点配置hosts 1.2 192.168.42.3 1.3 192.168.42.4 1.4 192.168.42.5 1.5 192.168.42.6 …

听GPT 讲Prometheus源代码--rules

Prometheus的rules目录主要包含规则引擎和管理规则的文件: engine.go 该文件定义了规则引擎的接口和主要结构,包括Rule,Record,RuleGroup等。它提供了规则的加载、匹配、评估和结果记录的功能。 api.go 定义了用于管理和查询规则的RESTful API,包括获取、添加、删除规则等方法。…

中大型无人机远程VHF语音电台系统方案

方案背景 中大型无人机在执行飞行任务时&#xff0c;特别是在管制空域飞行时地面航管人员需要通过语音与无人机通信。按《无人驾驶航空器飞行管理暂行条例》规定&#xff0c;中大型无人机应当进行适航管理。物流无人机和载人eVTOL都将进行适航管理&#xff0c;所以无人机也要有…

计算机组成与设计 Patterson Hennessy 笔记(二)MIPS 指令集

计算机的语言&#xff1a;汇编指令集 也就是指令集。本书主要介绍 MIPS 指令集。 汇编指令 算数运算&#xff1a; add a,b,c # abc sub a,b,c # ab-cMIPS 汇编的注释是 # 号。 由于MIPS中寄存器大小32位&#xff0c;是基本访问单位&#xff0c;因此也被称为一个字 word。M…