【Spring】SpringBoot 统一功能处理

在这里插入图片描述

文章目录

  • 前言
  • 1. 拦截器
    • 1.1 什么是拦截器
    • 1.2 拦截器的使用
      • 1.2.1 自定义拦截器
      • 1.2.2 注册配置拦截器
    • 1.3 拦截器详解
      • 1.3.1 拦截路径
      • 1.3.2 拦截器执行流程
      • 1.3.3 适配器模式
  • 2. 统一数据返回格式
  • 3. 统一异常处理

前言

在日常使用 Spring 框架进行开发的时候,对于一些板块来说,可能需要实现一个相同的功能,这个功能可以是验证你的登录信息,也可以是其他的,但是由于各个板块实现这个功能的代码逻辑都是相同的,如果一个板块一个板块进行添加的话,开发效率就会很低,所以 Spring 也想到了这点,为我们程序员提供了 SpringBoot 统一功能处理的方法实现,我们是可以直接使用的。这篇文章我将带大家一起学习 SpringBoot 统一功能的处理。

1. 拦截器

正常的判断用户是否登录的逻辑就是通过 session 来判断,对于一些网站来说,很多的功能都是需要用户进行登录之后才可以使用的,如果此时通过 session 判断出来用户处于未登录状态的话,咱们的服务器会强制用户进行登录,通过代码来显示就是这样的:

//检验用户是否登录
HttpSession session = request.getSession(false);
if (session == null || session.getAttribute("userInfo") == null) {return "您未登录,请登录后再试试该功能吧";
}

我们想要在哪个功能前判断用户的登录信息,就需要在该功能的模块中添加上面这些代码,如果需要添加的功能较少还好,如果很多,那么就需要花费很多的时间,那么有人就说了:我是否可以将这些代码封装成函数,然后哪个模块需要使用只需要调用这些函数就可以了呢?可以是可以,但是使用函数封装代码还是需要在原代码的基础上调用这个函数,并且显得也不是那么优雅。那么是否有方法既不需要更改原代码,也可以使得我们写的代码很优雅呢?SpringBoot 为我们提供了一种功能——拦截器。

1.1 什么是拦截器

在 Spring Boot 中,拦截器是一种用于在处理请求之前或之后执行特定操作的组件。拦截器通常用于实现一些通用的功能,比如权限验证、日志记录等。拦截器可以拦截通过Controller的请求,并在请求处理前后执行特定的操作。

拦截器的思想正好符合我们执行其他功能之前进行身份验证验证,并且不仅在方法执行之前拦截器可以起作用,方法执行之后。我们的拦截器也可以起到作用。

在这里插入图片描述

1.2 拦截器的使用

拦截器的使用分为两个步骤:

  1. 定义拦截器
  2. 注册配置拦截器

1.2.1 自定义拦截器

我们自定义的拦截器需要实现 HandlerInterceptor 接口,并且重写这个接口中的方法。

package com.example.springbootbook2.interceptor;import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;@Component
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {log.info("目标方法执行前执行...");HttpSession session = request.getSession(false);if (session == null || session.getAttribute("userInfo") == null) {response.setStatus(401);return false;}return true;}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {log.info("目标方法执行后执行...");}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {log.info("视图渲染完毕之后执行,最后执行...");}
}
  • preHandle() 方法是在目标方法执行之前执行的,返回 true,继续执行后面的代码;返回 false,中断后续的操作
  • postHandle() 方法是在目标方法执行之后执行的
  • afterCompletion() 方法是在视图渲染之后才执行的,它还在 postHandle() 方法之后执行,并且现在因为前后端分离,我们后端基本上接触不到视图的渲染,所以这个方法使用的较少

1.2.2 注册配置拦截器

注册配置拦截器需要实现 WebMvcConfiguer 接口,并实现 addInterceptors 方法。

package com.example.springbootbook2.config;import com.example.springbootbook2.interceptor.LoginInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
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 WebConfig implements WebMvcConfigurer {@Autowiredprivate LoginInterceptor loginInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(loginInterceptor).addPathPatterns("/**")  //指定路径配置拦截器.excludePathPatterns("/user/login");  //指定路径不配置拦截器}
}

在添加拦截器进行身份校验之前,我们可以直接通过对应的 url 访问到指定功能页面。

在这里插入图片描述

而在我们添加拦截器之后,再看是否能直接访问某些功能。

在这里插入图片描述
在这里插入图片描述

我们启动添加了拦截器之后的代码之后,点击刷新就发现不能够直接访问到某些功能了,再搭配着前端我们就可以实现强制登录的功能了。

1.3 拦截器详解

拦截器的⼊⻔程序完成之后,接下来我们来介绍拦截器的使⽤细节。拦截器的使⽤细节我们主要介绍两个部分:

  1. 拦截器的拦截路径配置
  2. 拦截器实现原理

1.3.1 拦截路径

拦截路径是指我们定义的这个拦截器,对哪些请求生效,我们在注册配置拦截器的时候,通过 addPathPatterns() 方法指定要拦截哪些 HTTP 请求,也可以通过 excludePathPatterns() 方法指定不拦截哪些请求,上面我们的 /** 表示拦截所有的 HTTP 请求,除了可以设置拦截所有请求外,还有一些其他的拦截设置:

拦截路径含义举例
/*⼀级路径能匹配/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

这些拦截规则可以拦截此项⽬中的使⽤ URL,包括静态⽂件(图⽚⽂件、JS、和 CSS 等⽂件)。

知道了如何配置拦截路径之后,我们就可以解决网页上身份验证的问题了。因为 /** 会拦截所有的请求,包括前端页面请求,所以我们需要将前端页面请求排除在拦截之外。

如果我们不讲前端页面请求在外的话机会出现这种情况:

在这里插入图片描述
然后我们将前端页面请求不添加拦截器的话,就可以实现完整的身份校验强制登录功能了。

@Configuration
public class WebConfig implements WebMvcConfigurer {@Autowiredprivate LoginInterceptor loginInterceptor;private List<String> excludePath = Arrays.asList("/user/login","/css/**","/js/**","/pic/**","/**/*.html");  // /**/*html中的/**表示所有路径下的所有以html结尾的文件 * 表示通配符@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(loginInterceptor).addPathPatterns("/**").excludePathPatterns(excludePath);}
}

当我们未登录,然后访问其他功能的话,就会强制跳转到登录页面:

在这里插入图片描述

1.3.2 拦截器执行流程

正常的调用顺序是这样的:

在这里插入图片描述
有了拦截器之后,会在调⽤ Controller 之前进⾏相应的业务处理,执⾏的流程如下图:

在这里插入图片描述

  1. 当我们添加拦截器之后,在执行 Controller 方法之前,请求会先被拦截器拦截,执行 preHandle() 方法,这个方法会返回一个布尔类型的值,如果返回的是 true,那么会继续执行后面的操作;如果返回的是 false,则不会执行后面的操作
  2. Controller 当中的方法执行完毕之后,会继续执行 postHandle() 方法以及 afterHandle() 方法,执行完毕之后,返回给浏览器响应数据。

在源码中的 DispatcherServlet 类中的 doDispatch 方法中可以看到这三个方法的执行流程:

在这里插入图片描述

1.3.3 适配器模式

在拦截器执行的过程中还使用了适配器模式:

在这里插入图片描述
适配器模式(Adapter Pattern)在计算机编程中是一种常用的设计模式,主要用于解决两个不兼容的类之间的接口匹配问题。通过将一个类的接口转换成客户端所期望的另一种接口,原本因为接口不匹配而无法一起工作的两个类现在可以一起工作。

适配器模式的优点在于它能够使原本由于接口不兼容而无法一起工作的类一起工作,提高了系统的灵活性和可扩展性。同时,它也能够减少代码的重复性,因为多个源可以共享同一个适配器。

HandlerAdapter 主要⽤于⽀持不同类型的处理器(如 Controller、HttpRequestHandler 或者
Servlet 等),让它们能够适配统⼀的请求处理流程。这样,SpringMVC可以通过⼀个统⼀的接⼝
来处理来⾃各种处理器的请求。

在这里插入图片描述
本来 target 和 adaptee 是两个无法正常对接的事物,但是通过适配器 adapter,这两者之间就可以进行对接,也就类似于下面这个情况:

在这里插入图片描述
适配器模式⻆⾊:

  • Target:⽬标接口(可以是抽象类或接口)客户希望直接⽤的接口
  • Adaptee:适配者,但是与Target不兼容
  • Adapter:适配器类,此模式的核⼼。通过继承或者引⽤适配者的对象,把适配者转为⽬标接⼝
  • client:需要使⽤适配器的对象

前⾯学习的 slf4j 就使⽤了适配器模式,slf4j 提供了⼀系列打印⽇志的 api,底层调⽤的是 log4j 或者logback 来打⽇志,我们作为调⽤者,只需要调⽤ slf4j 的 api 就⾏了。

/**
* slf4j接⼝
*/
interface Slf4jApi{void log(String message);
}/**
* log4j 接⼝
*/
class Log4j{void log4jLog(String message){System.out.println("Log4j打印:"+message);}
}/**
* slf4j和log4j适配器
*/
class Slf4jLog4JAdapter implements Slf4jApi{private Log4j log4j;public Slf4jLog4JAdapter(Log4j log4j) {this.log4j = log4j;}@Overridepublic void log(String message) {log4j.log4jLog(message);}
}/**
* 客⼾端调⽤
*/
public class Slf4jDemo {public static void main(String[] args) {Slf4jApi slf4jApi = new Slf4jLog4JAdapter(new Log4j());slf4jApi.log("使⽤slf4j打印⽇志");}
}

可以看出,我们不需要改变 log4j 的api,只需要通过适配器转换下,就可以更换⽇志框架,保障系统的平稳运行。

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

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

2. 统一数据返回格式

如果我们完成了一个项目的开发,但是这时候,我们突然觉得后端的各个功能的返回值返回的信息不够全面,我们需要补充一些返回信息,那么这时候就意味着所有方法的返回值都需要做出修改,这也是一个不小的工作量。这时 SpringBot 又为我们提供了解决方法——统一数据返回格式。

SpringBoot 统一数据返回格式会在各个方法进行 return 返回值之前插入一些代码逻辑,从而达到改变返回值的功能。

统一的数据返回格式使用 @ControllerAdviceResponseBodyAdvice 的方式实现。ControllerAdvice 表示控制器通知类。

我们先定义一个统一的返回类型:

public enum ResultCode {SUCCESS(0),FAIL(-1),UNLOGIN(-2);//0-成功  -1 失败  -2 未登录private int code;ResultCode(int code) {this.code = code;}public int getCode() {return code;}public void setCode(int code) {this.code = code;}
}
@Data
public class Result<T> {/*** 业务状态码*/private ResultCode code;  //0-成功  -1 失败  -2 未登录/*** 错误信息*/private String errMsg;/*** 数据*/private T data;public static <T> Result<T> success(T data){Result result = new Result();result.setCode(ResultCode.SUCCESS);result.setErrMsg("");result.setData(data);return result;}public static <T> Result<T> fail(String errMsg){Result result = new Result();result.setCode(ResultCode.FAIL);result.setErrMsg(errMsg);result.setData(null);return result;}public static <T> Result<T> fail(String errMsg,Object data){Result result = new Result();result.setCode(ResultCode.FAIL);result.setErrMsg(errMsg);result.setData(data);return result;}public static <T> Result<T> unlogin(){Result result = new Result();result.setCode(ResultCode.UNLOGIN);result.setErrMsg("用户未登录");result.setData(null);return result;}}
package com.example.springbootbook2.config;import com.example.springbootbook2.model.Result;
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;@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {@Overridepublic boolean supports(MethodParameter returnType, Class converterType) {return true;}@Overridepublic Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {return Result.success(body);}
}

继承 ResponseBodyAdvice 接口后,需要实现该接口下的 supports 方法和 beforeBodyWrite 方法,supports 方法只需要更改返回值为 true 就可以了,表示是否要执行 beforeBodyWrite 方法,返回 true 表示执行,false 表示不执行,beforeBodyWrite 方法中的 body 参数就是我们原方法的返回值。

当我们关闭拦截器,访问图书列表页之后,就发现我们的返回类型发生了变化:

在这里插入图片描述
但是这个统一数据返回格式也存在问题,当我们的返回值为 String 类型的话机会出现错误:

在这里插入图片描述
在这里插入图片描述
这是为什么呢?SpringMVC默认会注册⼀些⾃带的 HttpMessageConverter (从先后顺序排列分别为
ByteArrayHttpMessageConverter ,StringHttpMessageConverter , SourceHttpMessageConverter ,AllEncompassingFormHttpMessageConverter )
在这里插入图片描述
其中 AllEncompassingFormHttpMessageConverter 会根据项⽬依赖情况添加对应的
HttpMessageConverter

在这里插入图片描述

在依赖中引⼊jackson包后,容器会把 MappingJackson2HttpMessageConverter ⾃动注册到
messageConverters 链的末尾。Spring会根据返回的数据类型,从 messageConverters 链选择合适的 HttpMessageConverter。当返回的数据是⾮字符串时,使⽤的MappingJackson2HttpMessageConverter 写⼊返回对象。当返回的数据是字符串时, StringHttpMessageConverter 会先被遍历到,这时会认为
StringHttpMessageConverter 可以使用。

在这里插入图片描述
在这里插入图片描述
然⽽⼦类 StringHttpMessageConverter 的addDefaultHeaders⽅法定义接收参数为String,此
时t为Result类型,所以出现类型不匹配"Result cannot be cast to java.lang.String"的异常。

那么如何解决返回类型为 String 类型的问题呢?如果返回结果为String类型, 使⽤SpringBoot内置提供的Jackson来实现信息的序列化。

@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {@Autowiredprivate ObjectMapper objectMapper;@Overridepublic boolean supports(MethodParameter returnType, Class converterType) {return true;}@SneakyThrows@Overridepublic Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {if (body instanceof String) {return objectMapper.writeValueAsString(Result.success(body));}else if (body instanceof Result) {return body;}else {return Result.success(body);}}
}
  • @SneakyThrows 的主要目的是解决 Java 的异常处理问题。当我们在代码中抛出一个异常时,如果这个异常被包裹在一个方法中,并且这个方法没有 throws 关键字来声明会抛出这个异常,那么编译器会报错。通过使用 @SneakyThrows,你可以告诉编译器:“我知道这个方法可能会抛出异常,但我保证在 catch 块中处理它。” 这样编译器就不会报错了。

通过这个处理,当返回的数据类型为 String 的时候就不会出现错误了。

在这里插入图片描述
统一数据返回格式的优点:

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

3. 统一异常处理

当我们的程序中出现异常的时候,我们需要解决这些异常,因为前面做了统一数据返回格式的处理,所以这里的异常也可以进行统一的处理。

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

package com.example.springbootbook2.config;import com.example.springbootbook2.model.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;@ControllerAdvice
@Slf4j
@ResponseBody  //因为返回的数据都不是视图类型,所以加上这个注解防止出现问题
public class ErrorHandler {@ExceptionHandlerpublic Result<String> exception(Exception e) {log.error("发生异常:e{}",e);return Result.fail("内部异常");}@ExceptionHandlerpublic Result<String> exception(NullPointerException e) {log.error("发生异常:e{}",e);return Result.fail("NullPointerException异常,请联系管理员");}@ExceptionHandlerpublic Result<String> exception(ArithmeticException e) {log.error("发生异常:e{}",e);return Result.fail("ArithmeticException异常,请联系管理员");}
}
@Controller
@RequestMapping("/test")
public class TestController {@RequestMapping("/t1")public Integer test1() {return 10/0;}
}

在这里插入图片描述

这个 Exception 和 ArithmeticException 的先后顺序可以不考虑,这个不会因为 Exception 在前面捕获就报的 Exception 异常,而是会根据自己出现的最接近的异常来捕获。

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

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

相关文章

《JVM由浅入深学习九】 2024-01-15》JVM由简入深学习提升分(生产项目内存飙升分析)

目录 开头语内存飙升问题分析与案例问题背景&#xff1a;我华为云的一个服务器运行我的一个项目“csdn-automatic-triplet-0.0.1-SNAPSHOT.jar”&#xff0c;由于只是用来测试的服务器&#xff0c;只有2G&#xff0c;所以分配给堆的内存1024M查询内存使用&#xff08;top指令&a…

恒温器探针样品座

恒温器探针样品座是一种用采用可移动探针完成恒温器电缆和被测样品的电学连接&#xff0c;避免了每次样品电引线的焊接&#xff0c;探针可移动&#xff0c;5mm--20mm大小的样品均可适用&#xff0c;探针可以安装6个&#xff0c;标准配置探针数量为4个。 恒温器探针样品座由T型…

云渲染农场渲染和自己搭建农场渲染怎么选?哪个更划算?

&#xfeff;当我们面临繁重或紧急的渲染任务时&#xff0c;通常会选择云渲染的解决方案。可能很多人会问&#xff0c;我们是否能够自行建立一个小型的个人农场进行渲染呢&#xff1f;与云渲染农场相比&#xff0c;哪个更划算&#xff1f;更方便&#xff1f;接下来就带大家看看…

vue-echarts踩坑,本地开发可以渲染echarts图表,线上环境图表渲染不出来

main.js全局注册v-chart组件 import VueECharts from "vue-echarts"; Vue.component("v-chart", VueECharts);在页面中使用 如上图&#xff0c;我开始写的静态数据&#xff0c;在data中定义了chartOption1:{…配置项…}, 在接口数据返回之后&#xff0c;…

Nodejs基础3之fs模块的文件重命名和移动、文件的删除、文件夹操作、查看资源状态、fs路径

Nodejs基础二 fs模块文件重命名和移动文件的重命名文件的移动同步重命名和移动 文件的删除使用unlink进行删除unlink异步删除unlinkSync同步删除 使用rm进行删除rm异步删除rmSync同步删除 文件夹操作创建文件夹递归创建文件夹 读取文件夹删除文件夹rmdir删除文件夹删除递归文件…

电脑怎么把照片的kb缩小?三种方法帮你解决

电脑怎么把照片的kb缩小&#xff1f;我们在进行上传图片的时候&#xff0c;经常遇到图片太大&#xff0c;请压缩后再上传的情况&#xff0c;这就是我们开头所需要了解的压缩图片大小的方法&#xff0c;图片缩小kb可以通过三种处理方式来达到效果&#xff0c;我们可以直接图片压…

在线协作白板WBO本地部署启动并结合内网穿透实现远程协同办公

文章目录 推荐前言1. 部署WBO白板2. 本地访问WBO白板3. Linux 安装cpolar4. 配置WBO公网访问地址5. 公网远程访问WBO白板6. 固定WBO白板公网地址 推荐 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击…

【LeetCode: 208. 实现 Trie (前缀树)】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

一分钟带你了解--电商控价

电商行业发展至今带来了许多机遇&#xff0c;但同时也伴随着一些挑战。品牌电商在运营过程中&#xff0c;面临着诸如乱价、低价、窜货和假货等问题&#xff0c;这些问题不仅损害了品牌的形象和价值&#xff0c;也破坏了市场秩序&#xff0c;侵害了消费者的权益。 电商控价是解…

深度探讨 Golang 中并发发送 HTTP 请求的最佳技术

&#x1f482; 个人网站:【 海拥】【神级代码资源网站】【办公神器】&#x1f91f; 基于Web端打造的&#xff1a;&#x1f449;轻量化工具创作平台&#x1f485; 想寻找共同学习交流的小伙伴&#xff0c;请点击【全栈技术交流群】 在 Golang 领域&#xff0c;并发发送 HTTP 请求…

java常用API(2)

1&#xff1a;String 1.1 String(构造方法) 首先&#xff0c;我们先来学习String类&#xff0c;它涉及到两个案例&#xff1a;用户登录和聊天室。 先来看用户登录案例&#xff1a;需要输入用户名和密码&#xff0c;和已知的用户名和密码进行比较&#xff0c;涉及到比较的方法…

抖音小店如何选品?新手怎么选有潜力的产品?常用选品玩法分享

大家好&#xff0c;我是电商花花。 选品是我们店铺出单的关键&#xff0c;也是我们做电商行业的小伙伴非常关心的一个点。 我们想要选好商品&#xff0c;我们就要先搞清楚&#xff0c;什么样的产品才算是有潜力的产品&#xff0c;什么样的产品才能出单&#xff0c;什么样的产…

【HTML5】 canvas 绘制图形

文章目录 一、基本用法二、用法详见2.0、方法属性2.1、绘制线条2.2、绘制矩形2.3、绘制圆形2.4、绘制文本2.5、填充图像 一、基本用法 canvas 标签&#xff1a;可用于在网页上绘制图形&#xff08;使用 JavaScript 在网页上绘制图像&#xff09;画布是一个矩形区域&#xff0c…

MATLAB R2023b for Mac 中文

MATLAB R2023b 是 MathWorks 发布的最新版本的 MATLAB&#xff0c;适用于进行算法开发、数据可视化、数据分析以及数值计算等任务的工程师和科学家。它包含了一系列新增功能和改进&#xff0c;如改进了数据导入工具&#xff0c;增加了对数据帧和表格对象的支持&#xff0c;增强…

手机上最危险的3个操作,千万小心!

普通人千万不要在手机上做这3个操作&#xff0c;否则你的手机早晚会被黑客入侵了。 第一种&#xff0c;苹果越狱 越狱虽然可以绕过限制给你的苹果安装上一些特殊软件&#xff0c;但只要是越狱之后的苹果手机&#xff0c;都将留下漏洞&#xff0c;黑客最喜欢寻找做过越狱的手机…

nginx离线安装

安装包准备&#xff1a; 上传安装包到linux系统 一、进入gcc目录&#xff0c;执行以下命令 rpm -Uvh *.rpm --nodeps --force gcc -v查看版本 二、 进入gcc-c目录&#xff0c;执行命令 rpm -Uvh *.rpm --nodeps --force g -v查看版本 三、安装PCRE 解压&#xff1a; t…

众和策略:券商又念“紧箍咒” 绕标套现的头疼了

又有券商对两融事务念“紧箍咒”。 近来&#xff0c;记者从广发证券获悉&#xff0c;该公司已发布《关于调整融资融券负债归还规矩的告诉》&#xff0c;自2024年1月15日起&#xff0c;决议禁止融资买入证券用于归还融券负债。 相关业内人士以为&#xff0c;自上一年10月融资融…

华为交换机配置NQA DNS检测IP网络DNS解析速度

华为HCIA视频教程&#xff1a;超级实用&#xff0c;华为VRP系统文件详解 华为HCIA视频教程&#xff1a;不会传输层协议&#xff0c;HCIA都考不过 华为HCIA视频教程&#xff1a;网络工程师的基本功&#xff1a;网络地址转换NAT 华为HCIP视频教程&#xff1a;DHCP协议原理与配…

安防视频云平台/可视化监控云平台ARM版EasyCVR无法下载录像文件,如何解决?

视频集中存储/云存储/视频监控管理平台EasyCVR能在复杂的网络环境中&#xff0c;将分散的各类视频资源进行统一汇聚、整合、集中管理&#xff0c;实现视频资源的鉴权管理、按需调阅、全网分发、智能分析等。GB28181视频监控/AI智能大数据视频分析EasyCVR平台已经广泛应用在工地…

如何使用Docker本地搭建Traefik服务并实现公网访问管理界面

文章目录 前言1. Docker 部署 Trfɪk2. 本地访问traefik测试3. Linux 安装cpolar4. 配置Traefik公网访问地址5. 公网远程访问Traefik6. 固定Traefik公网地址 前言 Trfɪk 是一个云原生的新型的 HTTP 反向代理、负载均衡软件&#xff0c;能轻易的部署微服务。它支持多种后端 (D…