项目4-图书管理系统2+统一功能处理

1. 拦截器(Interceptor)

我们完成了强制登录的功能, 后端程序根据Session来判断用户是否登录, 但是实现⽅法是比较麻烦的。

所需要处理的内容:

• 需要修改每个接⼝的处理逻辑
• 需要修改每个接⼝的返回结果
• 接⼝定义修改, 前端代码也需要跟着修改

很麻烦!!!

1.1 什么是拦截器

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

也就是说, 允许开发⼈员提前预定义⼀些逻辑, 在⽤⼾的请求响应前后执⾏. 也可以在⽤⼾请求前阻止其执行.
在拦截器当中,开发⼈员可以在应⽤程序中做⼀些通⽤性的操作, ⽐如通过拦截器来拦截前端发来的请求,判断Session中是否有登录用户的信息.如果有就可以放行,如果没有就进行拦截

1.2 拦截器的基本使用 

下⾯我们先来学习下拦截器的基本使⽤.
拦截器的使⽤步骤分为两步:
1. 定义拦截器
2. 注册配置拦截器

1.2.1 ⾃定义拦截器

实现HandlerInterceptor接⼝,并重写其所有⽅法

package com.example.demo.component;import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;@Component
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {log.info("LoginInterceptor ⽬标⽅法执⾏前执⾏..");return true;}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {log.info("LoginInterceptor ⽬标⽅法执⾏后执⾏");}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {log.info("LoginInterceptor 视图渲染完毕后执⾏,最后执⾏");}
}

• preHandle()⽅法:⽬标⽅法执⾏前执⾏. 返回true: 继续执⾏后续操作; 返回false: 中断后续操作.
• postHandle()⽅法:⽬标⽅法执⾏后执⾏
• afterCompletion()⽅法:视图渲染完毕后执⾏,最后执⾏(后端开发现在⼏乎不涉及视图, 暂不了解)

1.2.2 注册配置拦截器

实现WebMvcConfigurer接⼝,并重写addInterceptors⽅法 

package com.example.demo.configuration;import com.example.demo.component.LoginInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;public class WebConfig implements WebMvcConfigurer {@Autowiredprivate LoginInterceptor loginInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(loginInterceptor).addPathPatterns("/**");//设置拦截器拦截的请求路径( /** 表⽰拦截所有请求)}
}

启动服务, 试试访问任意请求, 观察后端⽇志

可以看到preHandle ⽅法执⾏之后就放⾏了, 开始执⾏⽬标⽅法, ⽬标⽅法执⾏完成之后执⾏
postHandle和afterCompletion⽅法

我们把拦截器中preHandle⽅法的返回值改为false, 再观察运⾏结果

 可以看到, 拦截器拦截了请求, 没有进⾏响应.

1.3 拦截器详解

拦截器的⼊⻔程序完成之后,接下来我们来介绍拦截器的使⽤细节。

拦截器的使⽤细节我们主要介绍两个部分:
1. 拦截器的拦截路径配置
2. 拦截器实现原理

1.3.1 拦截路径

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

我们在注册配置拦截器的时候,

通过 addPathPatterns() ⽅法指定要拦截哪些请求.(即就是让哪些地方的拦截操作生效)【上述代码中, 我们配置的是 /** , 表⽰拦截所有的请求】

也可以通过excludePathPatterns() 指定不拦截哪些请求

⽐如⽤⼾登录校验, 我们希望可以对除了登录之外所有的路径⽣效

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

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

//后缀名被拦截:*.html,访问后缀名为html资源时,过滤器都会被执行

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

1.3.2 拦截器执行流程

正常的调⽤顺序:

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

1. 添加拦截器后, 执⾏Controller的⽅法之前, 请求会先被拦截器拦截住. 执⾏ preHandle() ⽅法,这个⽅法需要返回⼀个布尔类型的值. 如果返回true, 就表⽰放⾏本次操作, 继续访问controller中的方法. 如果返回false,则不会放⾏(controller中的⽅法也不会执⾏).
2. controller当中的⽅法执⾏完毕后,再回过来执⾏ postHandle() 这个⽅法以及 afterCompletion() ⽅法,执⾏完毕之后,最终给浏览器响应数据.

2.登录校验

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

2.1 定义拦截器

从session中获取⽤⼾信息, 如果session中不存在, 则返回false,并设置http状态码为401, 否则返回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状态码表⽰不接受这些凭据。

2.2 注册配置拦截器 

同时,我们调用方法时发现可以传递List<>

 故上述代码也可以改成

//asList->将数组转成list
//使用该方法 可以将一个变长参数或者数组转换成List

删除之前的登录校验代码  

运行程序, 通过Postman进⾏测试:
1. 查看图书列表

 2. 登录之后再次进行图书列表的查看

 

 3.DispatcherServlet 源码分析(dispatch派遣)

当Tomcat启动之后, 有⼀个核⼼的类DispatcherServlet, 它来控制程序的执⾏顺序.

所有请求都会先进到DispatcherServlet,执⾏doDispatch 调度⽅法.

如果有拦截器, 会先执⾏拦截器preHandle() ⽅法的代码, 如果 preHandle() 返回true, 继续访问controller中的⽅法. controller当中的⽅法执⾏完毕后,再回过来执⾏ postHandle() 和 afterCompletion() ,返回给DispatcherServlet,最终给浏览器响应数据.

3.1 初始化

DispatcherServlet的初始化⽅法 init() 在其⽗类 HttpServletBean 中实现的.
主要作⽤是加载 web.xml 中 DispatcherServlet 的 配置, 并调⽤⼦类的初始化.
web.xml是web项⽬的配置⽂件,⼀般的web⼯程都会⽤到web.xml来配置,主要⽤来配置
Listener,Filter,Servlet等, Spring框架从3.1版本开始⽀持Servlet3.0, 并且从3.2版本开始通过配置DispatcherServlet, 实现不再使⽤web.xml

3.2 处理请求

DispatcherServlet 接收到请求后, 执⾏doDispatch 调度⽅法, 再将请求转给Controller.
我们来看doDispatch ⽅法的具体实现

查看源码方式:

ctrl+N全局搜索类,ctrl+R在一个类中搜索方法

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;}}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);}}}

HandlerAdapter 在 Spring MVC 中使⽤了适配器模式
适配器模式, 也叫包装器模式. 简单来说就是⽬标类不能直接使⽤, 通过⼀个新类进⾏包装⼀下, 适配调⽤⽅使⽤.
把两个不兼容的接⼝通过⼀定的⽅式使之兼容.
HandlerAdapter 主要⽤于⽀持不同类型的处理器(如 Controller、HttpRequestHandler 或者
Servlet 等),让它们能够适配统⼀的请求处理流程。这样,Spring MVC 可以通过⼀个统⼀的接⼝来处理来⾃各种处理器的请求

4.3 适配器模式

HandlerAdapter 在 Spring MVC 中使⽤了适配器模式
适配器模式定义
适配器模式, 也叫包装器模式.

将⼀个类的接⼝,转换成客⼾期望的另⼀个接⼝, 适配器让原本接⼝不兼容的类可以合作⽆间.
简单来说就是⽬标类不能直接使⽤, 通过⼀个新类进⾏包装⼀下, 适配调⽤⽅使⽤. 把两个不兼容的接⼝通过⼀定的⽅式使之兼容.
⽐如下⾯两个接⼝, 本⾝是不兼容的(参数类型不⼀样, 参数个数不⼀样等等) 

不兼容的两个接口

通过适配器的⽅式, 兼容的A,B接口
适配器模式⻆⾊
Target: ⽬标接⼝ (可以是抽象类或接⼝), 客⼾希望直接⽤的接⼝
Adaptee: 适配者, 但是与Target不兼容
Adapter: 适配器类, 此模式的核⼼. 通过继承或者引⽤适配者的对象, 把适配者转为⽬标接⼝
client: 需要使⽤适配器的对象
适配器模式应⽤场景
⼀般来说,适配器模式可以看作⼀种"补偿模式",⽤来补救设计上的缺陷. 应⽤这种模式算是"⽆奈之 举", 如果在设计初期,我们就能协调规避接⼝不兼容的问题, 就不需要使⽤适配器模式了所以适配器模式更多的应⽤场景主要是对正在运⾏的代码进⾏改造, 并且希望可以复⽤原有代码实现新 的功能. ⽐如版本升级等.

4. 统⼀数据返回格式

强制登录案例中, 我们共做了两部分⼯作
1. 通过Session来判断⽤⼾是否登录
2. 对后端返回数据进⾏封装, 告知前端处理的结果
拦截器帮我们实现了第⼀个功能, 接下来看SpringBoot对第⼆个功能如何⽀持
统⼀的数据返回格式使⽤ @ControllerAdvice ResponseBodyAdvice 的⽅式实现
@ControllerAdvice 表⽰控制器通知类
添加类 ResponseAdvice , 实现 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);}
}
supports⽅法:
判断是否要执⾏beforeBodyWrite⽅法. true为执⾏, false不执⾏. 通过该⽅法可以选择哪些类或哪些⽅法的response要进⾏处理, 其他的不进⾏处理.
从returnType获取类名和⽅法名
// 获取执⾏的类
Class<?> declaringClass = returnType.getMethod().getDeclaringClass();
// 获取执⾏的⽅法
Method method = returnType.getMethod();

beforeBodyWrite⽅法: 对response⽅法进⾏具体操作处理

4.1 测试

测试

添加统⼀数据返回格式之前:

添加统⼀数据返回格式之后:

加此注解

 4.2 存在问题

结果显⽰(500), 发⽣内部错误
查看数据库, 发现数据操作成功

 

查看⽇志, ⽇志报错

4.2.1 测试方法 

多测试⼏种不同的返回结果
测试代码:
测试结果

 发现只有返回结果为String类型时才有这种错误发⽣!!!

4.2.2 解决⽅案

@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {private static ObjectMapper mapper=new ObjectMapper();@Overridepublic boolean supports(MethodParameter returnType, Class converterType) {return true;}@Overridepublic Object beforeBodyWrite(Object body, MethodParameter returnType,MediaType selectedContentType, Class selectedConverterType,ServerHttpRequest request, ServerHttpResponse response) {if(body instanceof String){//如果返回结果为String类型, 使⽤SpringBoot内置提供的Jackson来实现信息的序列化try {return mapper.writeValueAsString(Result.success(body));} catch (JsonProcessingException e) {e.printStackTrace();}}return Result.success(body);}
}

成功!!!

4.2.3 原因分析

  • SpringMVC默认会注册⼀些⾃带的 HttpMessageConverter (从先后顺序排列分别为
  • ByteArrayHttpMessageConverter , StringHttpMessageConverter , SourceHttpMessageConverter , SourceHttpMessageConverter , AllEncompassingFormHttpMessageConverter )
  • 其中AllEncompassingFormHttpMessageConverter 会根据项⽬依赖情况 添加对应的
  • HttpMessageConverter
  • 在依赖中引⼊jackson包后,容器会把 MappingJackson2HttpMessageConverter ⾃动注册到messageConverters 链的末尾.
  • Spring会根据返回的数据类型, 从 messageConverters 链选择合适的 HttpMessageConverter .
  • 当返回的数据是⾮字符串时, 使⽤的 MappingJackson2HttpMessageConverter 写⼊返回对象.
  • 当返回的数据是字符串时, StringHttpMessageConverter 会先被遍历到,这时会认为 StringHttpMessageConverter 可以使⽤.
  • ((HttpMessageConverter) converter).write(body, selectedMediaType,
    outputMessage) 的处理中, 调⽤⽗类的write⽅法
  • 由于 StringHttpMessageConverter 重写了addDefaultHeaders⽅法, 所以会执⾏⼦类的⽅法
  • 然⽽⼦类 StringHttpMessageConverter 的addDefaultHeaders⽅法定义接收参数为String, 此时t为Result类型, 所以出现类型不匹配"Result cannot be cast to java.lang.String"的异常

 4.2.4 案例代码修改

如果⼀些⽅法返回的结果已经是Result类型了, 那就直接返回Result类型的结果即可

5.统⼀异常处理 

统⼀异常处理使⽤的是 @ControllerAdvice + @ExceptionHandler 来实现的, @ControllerAdvice 表⽰控制器通知类, @ExceptionHandler 是异常处理器,两个结合表⽰当出现异常的时候执⾏某个通知,也就是执⾏某个⽅法事件
类名, ⽅法名和返回值可以⾃定义, 重要的是注解
接⼝返回为数据时, 需要加 @ResponseBody 注解
以上代码表⽰,如果代码出现Exception异常(包括Exception的⼦类), 就返回⼀个 Result的对象, Result 对象的设置参考 Result.fail(e.getMessage()

 我们可以针对不同的异常, 返回不同的结果

测试结果

 

当有多个异常通知时,匹配顺序为当前类及其⼦类向上依次匹配

6.@ControllerAdvice 源码分析

统⼀数据返回和统⼀异常都是基于 @ControllerAdvice 注解来实现的, 通过分析@ControllerAdvice 的源码, 可以知道他们的执⾏流程.

  • 从上述源码可以看出 @ControllerAdvice 派⽣于 @Component 组件, 这也就是为什么没有五 ⼤注解, ControllerAdvice 就⽣效的原因.
  • 下⾯我们看看Spring是怎么实现的, 还是从 DispatcherServlet 的代码开始分析.
  • DispatcherServlet 对象在创建时会初始化⼀系列的对象
public class DispatcherServlet extends FrameworkServlet {//...@Overrideprotected void onRefresh(ApplicationContext context) {initStrategies(context);}/*** Initialize the strategy objects that this servlet uses.* <p>May be overridden in subclasses in order to initialize further 
strategy objects.*/protected void initStrategies(ApplicationContext context) {initMultipartResolver(context);initLocaleResolver(context);initThemeResolver(context);initHandlerMappings(context);initHandlerAdapters(context);initHandlerExceptionResolvers(context);initRequestToViewNameTranslator(context);initViewResolvers(context);initFlashMapManager(context);}//...
}
对于 @ControllerAdvice 注解,我们重点关注 initHandlerAdapters(context)
initHandlerExceptionResolvers(context) 这两个⽅法.

6.1. initHandlerAdapters(context)

initHandlerAdapters(context) ⽅法会取得所有实现了 HandlerAdapter 接⼝的bean并
保存起来,其中有⼀个类型为 RequestMappingHandlerAdapter 的bean,这个bean就是
@RequestMapping 注解能起作⽤的关键,这个bean在应⽤启动过程中会获取所有被@ControllerAdvice 注解标注的bean对象, 并做进⼀步处理。

6.2. initHandlerExceptionResolvers(context)

接下来看 DispatcherServlet initHandlerExceptionResolvers(context) ⽅法,
这个⽅法会取得所有实现了 HandlerExceptionResolver 接⼝的bean并保存起来,其中就有⼀
个类型为 ExceptionHandlerExceptionResolver 的bean,这个bean在应⽤启动过程中会获
取所有被 @ControllerAdvice 注解标注的bean对象做进⼀步处理

7. 案例代码

通过上⾯统⼀功能的添加, 我们后端的接⼝已经发⽣了变化(后端返回的数据格式统⼀变成了Result类型), 所以我们需要对前端代码进⾏修改

7.1 登录页面

登录界⾯没有拦截, 只是返回结果发⽣了变化, 所以只需要根据返回结果修改对应代码即可

登录结果代码修改
tips:可以根据返回的东西更新前端返回代码

 

7.2图书列表

针对图书列表⻚有两处变化
1. 拦截器进⾏了强制登录校验, 如果校验失败, 则http状态码返回401, 此时会⾛ajax的error逻辑处理
2. 接⼝返回结果发⽣了变化
图书列表代码修改

7.3 其他

参考图书列表, 对删除图书, 批量删除图书,添加图书, 修改图书接⼝添加⽤⼾强制登录以及统⼀格式返回的逻辑处理

7.4 测试

先后端接口测试后前端测试

//修改时遇到的问题

//统一返回String类型出现错误

//改正1.前端处理

//改正2.接口设置返回类型

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

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

相关文章

淘宝商品详情API数据采集接口|如何快速采集淘宝商品数据?

如何快速采集淘宝商品数据 无论是谁&#xff0c;如果单凭人工的方式去收集淘宝、天猫等平台的商品数据信息&#xff0c;工作量是巨大的&#xff0c;如果借助有采集软件的第三方公司操作&#xff0c;则可实现对大数据的轻松掌握&#xff0c;但是外包给第三方公司需要支付一定的…

javaee初阶———多线程(三)

T04BF &#x1f44b;专栏: 算法|JAVA|MySQL|C语言 &#x1faf5; 小比特 大梦想 此篇文章与大家分享多线程专题第三篇,关于线程安全方面的内容 如果有不足的或者错误的请您指出! 目录 八、线程安全问题(重点)1.一个典型的线程不安全的例子2.出现线程不安全的原因3.解决线程不安…

对于普通人来说ChatGPT3.5和4.0的区别,要不要升级GPT4.0

ChatGPT3.5和4.0在官方给出的有哪些区别呢&#xff1f;简单罗列一下&#xff0c;我个人觉得官方给的都太高级&#xff0c;我们平时普通人很难问ChatGPT这种问题。 官方测试所涉及的能力&#xff1a; 视觉能力代码能力数学计算能力工具使用能力与人的交互能力人类专业考试的能…

Node.js从基础到高级运用】二十三、Node.js中自动重启服务器

引言 在Node.js开发过程中&#xff0c;我们经常需要修改代码后重启服务器来应用这些更改。手动重启不仅效率低下&#xff0c;而且会打断开发流程。幸运的是&#xff0c;有一些工具可以帮助我们自动化这个过程。本文将介绍如何使用nodemon来实现Node.js服务器的自动重启。 什么是…

AR智能眼镜方案_MTK平台安卓主板芯片|光学解决方案

AR眼镜作为一种引人注目的创新产品&#xff0c;其芯片、显示屏和光学方案是决定整机成本和性能的关键因素。在这篇文章中&#xff0c;我们将探讨AR眼镜的关键技术&#xff0c;并介绍一种高性能的AR眼镜方案&#xff0c;旨在为用户带来卓越的体验。 AR眼镜的芯片选型至关重要。一…

数据结构---绪论

一、绪论&#xff1a; 1.什么是数据&#xff1f; 数据是信息的载体&#xff0c;是描述客观事物属性的数&#xff0c;字符及所有能输入到计算机中并被计算机程序识别和处理的符号的集合。数据是计算机程序加工的原料。 数据元素--描述一个个体 数据元素&#xff0c;数据项&am…

React + 项目(从基础到实战) -- 第七期

使用ant design 表单组件,开发登录,注册,搜索功能 React 表单组件 ,受控组件 案列 使用defaultVlue属性 bug : 改变了数据源,但是页面未重新渲染 {/* 表单组件 */}<button onClick{()>{console.log(text);}}>打印</button><button onClick{()>[setText(&…

【InternLM 实战营第二期笔记01】书生·浦语大模型全链路开源体系+InternLM2技术报告

本次课程链接在GitHub上&#xff1a;InternLM/Tutorial at camp2 (github.com) 第一次课程录播链接&#xff1a;书生浦语大模型全链路开源体系_哔哩哔哩_bilibili InternLM2技术报告&#xff1a;arxiv.org/pdf/2403.17297.pdf 一、书生浦语大模型全链路开源体系笔记 Intern…

【读书笔记】自动驾驶与机器人中的SLAM技术——高翔

文章会对本书第五章节及以后章节进行总结概括。每日更新一部分。一起读书吧。 第五章——基础点云处理 重点&#xff1a;点云的相邻关系是许多算法的基础 5.1 激光雷达传感器与点云的数学模型 5.1.1激光雷达传感器的数学模型 雷达有两种&#xff1a;机械旋转式激光雷达&…

python 海龟画图tutle螺旋线

目录 初识turtle模块 基本绘图概念 示例&#xff1a;绘制一个正方形 示例&#xff1a;绘制彩色螺旋线 附录 常用命令 其它命令 在Python编程中&#xff0c;使用turtle模块进行图形绘制是一种非常有趣和富有教育意义的活动。通过控制一个小海龟&#xff08;Turtle&#x…

【产品经理修炼之道】- 厂商银业务之保兑仓

保兑仓 保兑仓是指供应商、购货商、银行签订三方协议&#xff0c;以银行信用为载体&#xff0c;以银行承兑汇票为结算工具&#xff0c;由银行控制货权&#xff0c;供应商受托保管货物并对银行承兑汇票保证金以外部分以货物回购为担保措施&#xff0c;购货商随缴保证金随提货而设…

《QT实用小工具·二十六》运行时间记录

1、概述 源码放在文章末尾 运行时间记录&#xff0c;包含如下功能&#xff1a; 可以启动和停止服务&#xff0c;在需要的时候启动。 可以指定日志文件存放目录。 可以指定时间日志输出间隔。 可以单独追加一条记录到日志文件。 日志为文本格式&#xff0c;清晰明了。 软…

AAAI24 - Model Reuse Tutorial

前言 如果你对这篇文章感兴趣&#xff0c;可以点击「【访客必读 - 指引页】一文囊括主页内所有高质量博客」&#xff0c;查看完整博客分类与对应链接。 该篇 Tutorial 主要对 Model Reuse 当下的进展进行了整理和总结。 The Paradigm Shifts Tutorial 中指出在一些数据量比较…

STM32学习和实践笔记(12):蜂鸣器实验

蜂鸣器主要分为两种&#xff0c;一种是压电式的无源蜂鸣器&#xff0c;一种是电磁式的有源蜂鸣器。 有源和无源是指其内部有没有振荡器。 无源的没有内部振荡器&#xff0c;需要输入1.5-5KHZ的音频信号来驱动压电蜂鸣片发声。 有源的内部有振荡器&#xff0c;因此只需要供给…

PostgreSQL入门到实战-第二十七弹

PostgreSQL入门到实战 PostgreSQL中数据分组操作(二)官网地址PostgreSQL概述PostgreSQL中HAVING命令理论PostgreSQL中HAVING命令实战更新计划 PostgreSQL中数据分组操作(二) 使用PostgreSQL HAVING子句来指定组或聚合的搜索条件 官网地址 声明: 由于操作系统, 版本更新等原因…

基于Python豆瓣电影数据可视化分析系统的设计与实现

大数据可视化项目——基于Python豆瓣电影数据可视化分析系统的设计与实现 2024最新项目 项目介绍 本项目旨在通过对豆瓣电影数据进行综合分析与可视化展示&#xff0c;构建一个基于Python的大数据可视化系统。通过数据爬取收集、清洗、分析豆瓣电影数据&#xff0c;我们提供了…

Docker:使用编排Compose快速部署容器化应用

1、简述 Docker Compose 是 Docker 官方提供的一个工具&#xff0c;用于定义和管理多容器应用。它通过一个简单的 YAML 文件来定义应用的服务、网络、卷等配置&#xff0c;并提供了一组命令来启动、停止、构建和管理应用。使用 Docker Compose 可以让开发人员轻松地在本地开发…

cdn加速与ssl加速

cdn CDN的全称是Content Delivery Network&#xff0c;即内容分发网络。其基本思路是尽可能避开互联网上有可能影响数据传输速度和稳定性的瓶颈和环节&#xff0c;使内容传输的更快、更稳定。 简单的来说&#xff0c;就是把原服务器上数据复制到其他服务器上&#xff0c;用户访…

蓝桥杯——松散子序列

题目 分析 很明显的动态规划问题&#xff0c;每次我们都取当前位置的最大值就可&#xff0c;从头开始&#xff0c;dp[i]max(dp[i-2],dp[i-3])num[i-3]. 代码 ninput() num[] for i in n:num.append(ord(i)-96) dp[0]*(len(num)3) for i in range(3,len(num)3):dp[i]max(dp[i…

锁策略总结

锁策略 悲观锁和乐观锁 乐观锁和悲观锁不是具体类型的锁而是指两种不同的对待加锁的态度&#xff0c;这两个锁面对锁冲突的态度是相反的。 乐观锁&#xff1a;认为不存在很多的并发操作&#xff0c;因此不需要加锁。悲观锁&#xff1a;认为存在很多并发操作&#xff0c;因此需…