3.4.SynchronousMethodHandler组件之ResponseHandler
前言
feign发送完请求后, 拿到返回结果, 那么这个返回结果肯定是需要经过框架进一步处理然后再返回到调用者的, 其中ResponseHandler
就是用来处理这个返回结果的, 这也是符合正常思维的处理方式, 例如springmvc部分在调用在controller端点前后都会增加扩展点。
从图中可以看得feign的返回处理应该不会很复杂, 并且可以自定义日志对象,和日志级别,对返回值进行解码, 并允许我们使用责任链来处理返回结果。
代码解析
老规矩, 查看类结构, 看该对象给我们提供了哪些功能
ResponseHandler
public class ResponseHandler {/*** 日志等级; 默认是Logger.Level.NONE*/private final Level logLevel;/*** 日志对象; 默认是NoOpLogger*/private final Logger logger;/*** 对正常响应数据进行解码的解码器; 默认是Decoder.Default*/private final Decoder decoder;/*** 404异常时, 对错误信息进行解码的解码器, 默认是ErrorDecoder.Default*/private final ErrorDecoder errorDecoder;/*** 当返回类型不为void, 并且响应状态码是404 1.如果dismiss404为true时, 那么忽略异常 2.如果dismiss404为false,则会抛异常*/private final boolean dismiss404;/*** 是否在解码返回数据后关闭相应的流*/private final boolean closeAfterDecode;/*** 是否对void类型返回值进行解码*/private final boolean decodeVoid;/*** 响应拦截链*/private final ResponseInterceptor.Chain executionChain;/*** 唯一构造器*/public ResponseHandler(Level logLevel, Logger logger, Decoder decoder, ErrorDecoder errorDecoder,boolean dismiss404, boolean closeAfterDecode, boolean decodeVoid,ResponseInterceptor.Chain executionChain) {...}public Object handleResponse(String configKey,Response response,Type returnType,long elapsedTime)throws Exception {...}
}
ResponseHandler
类是同步请求结果处理器
- 它提供了一个参数非常多且唯一一的构造器
- 提供了一个方法
handleResponse
来处理返回对象
其中有个属性是ResponseInterceptor.Chain
, 它是用来处理返回对象的责任链, 我们简单认识一下它
ResponseInterceptor.Chain
public interface ResponseInterceptor {interface Chain {Chain DEFAULT = InvocationContext::proceed;Object next(InvocationContext context) throws Exception;}/*** 拦截器套娃包装*/default ResponseInterceptor andThen(ResponseInterceptor nextInterceptor) {return (ic, chain) -> intercept(ic,nextContext -> nextInterceptor.intercept(nextContext, chain));}/*** 执行拦截器*/Object intercept(InvocationContext invocationContext, Chain chain) throws Exception;/*** 执行责任链*/default Chain apply(Chain chain) {return request -> intercept(request, chain);}
}
它是内聚在ResponseInterceptor
中的一个接口。
- 定义了一个默认链
Chain.DEFAULT
- 提供了一个获取下一节点的方法
- ResponseInterceptor提供了一个静态的包装方法
andThen
, 用来拦截器套娃 - ResponseInterceptor提供了一个用于执行拦截器的方法
intercept
, - ResponseInterceptor提供了一个用于执行责任链的方法
apply
它在Feign.Builder
的父类BaseBuilder
中实例化
public abstract class BaseBuilder<B extends BaseBuilder<B, T>, T> implements Cloneable {/*** 返回结果拦截器*/protected final List<ResponseInterceptor> responseInterceptors = new ArrayList<>();// 用新的拦截器替换掉原有的拦截器public B responseInterceptors(Iterable<ResponseInterceptor> responseInterceptors) {this.responseInterceptors.clear();for (ResponseInterceptor responseInterceptor : responseInterceptors) {this.responseInterceptors.add(responseInterceptor);}return thisB;}/*** 添加单个拦截器*/public B responseInterceptor(ResponseInterceptor responseInterceptor) {this.responseInterceptors.add(responseInterceptor);return thisB;}/*** response拦截器组成链条*/protected ResponseInterceptor.Chain responseInterceptorChain() {ResponseInterceptor.Chain endOfChain =ResponseInterceptor.Chain.DEFAULT;ResponseInterceptor.Chain executionChain = this.responseInterceptors.stream().reduce(ResponseInterceptor::andThen).map(interceptor -> interceptor.apply(endOfChain)).orElse(endOfChain);return (ResponseInterceptor.Chain) Capability.enrich(executionChain,ResponseInterceptor.Chain.class, capabilities);}
}
该方法responseInterceptorChain
是protected修饰的
, 子类可以重写它。允许我们每次添加一个拦截器, 或者直接全部替换。
构建责任链
ResponseInterceptor.Chain executionChain = this.responseInterceptors.stream().reduce(ResponseInterceptor::andThen).map(interceptor -> interceptor.apply(endOfChain)).orElse(endOfChain);
这和我们传统看到的责任链有点不同, 传统的责任链一般是有前后节点以及上下文, 然后用责任链触发调用, 这里的区别在于责任链中节点的构建方式有点不同, 这里是嵌套包装的性质.
为了让大家更好的理解这坨代码, 我把它平铺开, 写段伪代码
public ResponseInterceptor.Chain buildRespChain() {ResponseInterceptor.Chain endOfChain = ResponseInterceptor.Chain.DEFAULT;// 合并所有拦截器成一个 ResponseInterceptorResponseInterceptor combinedInterceptor = null;for (ResponseInterceptor interceptor : this.responseInterceptors) {if (combinedInterceptor == null) {combinedInterceptor = interceptor;} else {ResponseInterceptor previousCombinedInterceptor = combinedInterceptor;combinedInterceptor = new ResponseInterceptor() {@Overridepublic Object intercept(InvocationContext ic, Chain chain) throws Exception {return previousCombinedInterceptor.intercept(ic, new Chain() {@Overridepublic Object next(InvocationContext context) throws Exception {return interceptor.intercept(context, chain);}});}};}}// 如果没有拦截器,直接返回 endOfChainif (combinedInterceptor == null) {return endOfChain;}ResponseInterceptor temp = combinedInterceptor;// 使用 apply 构造最终责任链return new ResponseInterceptor.Chain() {@Overridepublic Object next(InvocationContext request) throws Exception {return temp.intercept(request, endOfChain);}};}
-
核心就是调用每一个过滤节点(这里是拦截器)的时候把下一个传节点封装成Chain传进去, 然后我们就可以在拦截器的
intercept
方法中通过Chain的next方法调用下一个节点拦截器了 -
最后再构建一个最终的
Chain
, 在next
方法中调用构建出来的拦截器链, 并传入默认节点endOfChain
, 也就是说我们自定义的拦截器会先执行 -
最后再执行这个
endOfChain
(取决于各拦截器执行的行为, 可以决定是正序还是倒序), 返回整条连的执行结果
说到责任链, 我的这篇文章也介绍了一个我之前写的拦截器项目通用责任链在项目中使用
那么这个默认的的节点endOfChain
长什么样呢? 我们得研究一下
InvocationContext
它是用lambda表达式表示的一个Chain, 定义在响应链处理器的最后一个节点, 用来处理最终的返回结果
关于lambda表达式, 如果大家不是很了解, 可以去看看我的这篇文章 lambda表达式原理
Chain DEFAULT = InvocationContext::proceed;
public class InvocationContext {public Object proceed() throws Exception {// 方法返回类型是Response(一般也不会是这个)if (returnType == Response.class) {// 读取流中的数据; 1.如果响应体为空或者大于8k, 直接返回response 2.响应数据不为空且小于8k, 将数据流取出来return disconnectResponseBodyIfNeeded(response);}try {// 1.响应正常 或者 2.响应状态码是404 并且 dismiss404为true 并且 返回值类型不是void类型final boolean shouldDecodeResponseBody =(response.status() >= 200 && response.status() < 300)|| (response.status() == 404 && dismiss404&& !isVoidType(returnType));// shouldDecodeResponseBody为false的几种情况如下// 1.状态码不是200-300 并且 2.状态码不是404; 例如status为500// 2.状态码不是200-300 并且 状态码是404且dismiss404为false// 3.状态码不是200-300 并且 状态码是404且dismiss404为true 并且 返回类型是void类型if (!shouldDecodeResponseBody) {// 抛异常, 这里可能是重试异常RetryableExceptionthrow decodeError(configKey, response);}// 1.返回值类型是void 2.不允许对void类型进行解码if (isVoidType(returnType) && !decodeVoid) {// 关闭流ensureClosed(response.body());return null;}// 获取返回值的原始类型Class<?> rawType = Types.getRawType(returnType);// 返回类型是TypedResponse类型; 类似于泛型中的 <rawType extend TypedResponse>if (TypedResponse.class.isAssignableFrom(rawType)) {// 获取TypedResponse中参数泛型的类型, 也就是TypedResponse<T>中的TType bodyType = Types.resolveLastTypeParameter(returnType, TypedResponse.class);// 这里把response解码成TypedResponse<T>中的T类型然后设置给bodyreturn TypedResponse.builder(response).body(decode(response, bodyType)).build();}// 把response解码成bodyType类型return decode(response, returnType);} finally {// decode之后关闭流if (closeAfterDecode) {ensureClosed(response.body());}}}
}private static Response disconnectResponseBodyIfNeeded(Response response) throws IOException {// 如果数据小于8k, 证明数据已经返回完了, 不需要再读取数据; 否则返回response本身继续读取数据final boolean shouldDisconnectResponseBody = response.body() != null&& response.body().length() != null&& response.body().length() <= MAX_RESPONSE_BUFFER_SIZE;// 1.如果响应体为空或者大于8k, 直接返回responseif (!shouldDisconnectResponseBody) {return response;}try {// 响应数据不为空且小于8k, 将数据流取出来final byte[] bodyData = Util.toByteArray(response.body().asInputStream());return response.toBuilder().body(bodyData).build();} finally {// 关闭响应流(inputStream)ensureClosed(response.body());}}
小结一下
- 如果返回类型是
Response
类型
- 如果返回数据小于8k, 证明数据已经返回完了, 不需要再读取数据;
- 否则返回response本身继续读取数据, 并关闭响应流数据
- 如果请求失败, 如下情况将会抛异常(可能是重试异常)
- 状态码不是200-300 并且 2.状态码不是404; 例如status为500
- 状态码不是200-300 并且 状态码是404且dismiss404为false
- 状态码不是200-300 并且 状态码是404且dismiss404为true 并且 返回类型是void类型
- 当方法返回值不是空时, 如果不想404报错, dismiss404参数设置为true就行
- 如果返回类型是void, 并且不允许对void类型进行解码, 直接关闭流
- 如果返回值类型是
TypedResponse
, 那么对返回数据的body解码(这里只支持String和byte[]类型) - 如果返回类型既不是
Response
, 也不是TypedResponse
, 直接将返回的响应体数据解码成方法返回类型(这里只支持String和byte[]类型)
这里正常数据解码器是
Decoder.Default
, 异常数据解码器是ErrorDecoder.Default
泛型的解析工具类是
Types
, 如果想要更多的了解泛型, 可以看我的这篇文章 java泛型探究
这里了解一下Response
和TypedResponse
的区别
public final class Response implements Closeable {// ...忽略其它属性private final Body body;
}public final class TypedResponse<T> {// ...忽略其它属性private final T body;public Builder body(T body) {this.body = body;return this;}
}
- Response实现了
Closeable
接口, 可以用来自动关闭流 - Response的响应体对象是一个Body类型, 而
TypedResponse
的响应体是一个泛型, 该泛型是body进行解码转换的结果
关于异常状态码的响应的重试处理
// shouldDecodeResponseBody为false的几种情况如下
// 1.状态码不是200-300 并且 2.状态码不是404; 例如status为500
// 2.状态码不是200-300 并且 状态码是404且dismiss404为false
// 3.状态码不是200-300 并且 状态码是404且dismiss404为true 并且 返回类型是void类型
if (!shouldDecodeResponseBody) {// 抛异常throw decodeError(configKey, response);
}private Exception decodeError(String methodKey, Response response) {
try {// 默认是ErrorDecoder.Default; 如果有重试, 会抛RetryableExceptionreturn errorDecoder.decode(methodKey, response);
} finally {// 关闭响应流(inputStream)ensureClosed(response.body());
}
}
这里errorDecoder
默认是ErrorDecoder.Default
public class Default implements ErrorDecoder {public Exception decode(String methodKey, Response response) {FeignException exception = errorStatus(methodKey, response, maxBodyBytesLength,maxBodyCharsLength);// 重试的时间 毫秒类型; RETRY_AFTER:Retry-AfterLong retryAfter = retryAfterDecoder.apply(firstOrNull(response.headers(), RETRY_AFTER));if (retryAfter != null) {return new RetryableException(response.status(),exception.getMessage(),response.request().httpMethod(),exception,retryAfter,response.request());}return exception;}
}
-
这里
retryAfterDecoder
默认是ErrorDecoder.RetryAfterDecoder
, 定义在RetryAfterDecoder
内部. -
firstOrNull(response.headers(), RETRY_AFTER)
用与获取响应头中Retry-After
属性的值(毫秒) -
如果返回了正确的重试时间, 那么抛
RetryableException
异常, 否则抛FeignException
异常
static class RetryAfterDecoder {public Long apply(String retryAfter) {if (retryAfter == null) {return null;}// 也就是数字 或者数字.?0*, 例如 匹配:100、100.、100.0、100.00if (retryAfter.matches("^[0-9]+\\.?0*$")) {// 去掉小数部分, 例如 100.00 -> 100retryAfter = retryAfter.replaceAll("\\.0*$", "");// 转毫秒long deltaMillis = SECONDS.toMillis(Long.parseLong(retryAfter));// 下次重试时间return currentTimeMillis() + deltaMillis;}try {// 否则就是时间格式return ZonedDateTime.parse(retryAfter, dateTimeFormatter).toInstant().toEpochMilli();} catch (NullPointerException | DateTimeParseException ignored) {// 其它格式不重试return null;}}
}
- 响应头
Retry-After
属性为空或者对应的值为空, 返回null - 如果``Retry-After
属性返回的是数字, 那么计算下次重试的时间点(当前时间+响应头
Retry-After`设置的时间) - 如果``Retry-After
属性返回的是时间格式, 那么它只能是
Day-of-Week, DD Month YYYY HH:mm:ss GMT例如
Tue, 3 Jun 2008 11:05:30 GMT`这种
响应责任链的部分介绍完了, 下面回归到ResponseHandler
类
ResponseHandler详情
ResponseHandler
public Object handleResponse(String configKey,Response response,Type returnType,long elapsedTime)throws Exception {try {// 打印响应相关的日志response = logAndRebufferResponseIfNeeded(configKey, response, elapsedTime);// 执行责任链, 处理响应数据return executionChain.next(new InvocationContext(configKey, decoder, errorDecoder, dismiss404, closeAfterDecode,decodeVoid, response, returnType));} catch (final IOException e) {// 打印日常日志if (logLevel != Level.NONE) {logger.logIOException(configKey, logLevel, e, elapsedTime);}// 抛FeignException异常throw errorReading(response.request(), response, e);} catch (Exception e) {// 关闭响应流ensureClosed(response.body());throw e;}}
小结一下
- 打印相应结果相关的日志
- 使用响应责任链处理返回结果
- 异常情况打印日志然后包装成FeignException异常异常抛出
- 最后关闭响应流
logAndRebufferResponseIfNeeded
在执行相应责任链调用之前, 先打印了一段日志
private Response logAndRebufferResponseIfNeeded(String configKey,Response response,long elapsedTime)throws IOException {// 默认是NoOpLogger, 也就是不打印日志if (logLevel == Level.NONE) {return response;}return logger.logAndRebufferResponse(configKey, logLevel, response, elapsedTime);}
- 默认的
logLevel
是Level.NONE
, 也就是不打印日志 - 以Slf4j举例, Slf4j在feign中是以
Slf4jLogger
对象存在, 它包装了org.slf4j.Logger对象
Slf4jLogger#logAndRebufferResponse
@Overrideprotected Response logAndRebufferResponse(String configKey,Level logLevel,Response response,long elapsedTime)throws IOException {if (logger.isDebugEnabled()) {// 这里调用feign.Logger中的方法return super.logAndRebufferResponse(configKey, logLevel, response, elapsedTime);}return response;}
举例
@Test
void logFunc() {DemoClient client = Feign.builder().logLevel(feign.Logger.Level.FULL).logger(new Slf4jLogger()).dismiss404().target(DemoClient.class, "http://localhost:8080");client.getDemo1("uncleqiao");
}
这里将打印请求和响应的所以信息, 包括响应体; 这里dismiss404是为了测试时候, 接口不存在时的404不报错
总结
- Feign通过
ResponseHandler
来处理响应的结果 ResponseHandler
主要通过响应责任链来处理响应结果, 我们可以自定义其中的节点拦截器做自定义的事情- 责任链中默认添加了一个节点
InvocationContext
, 用来真正处理返回结果
- 如果方法返回类型是
Response
, 那么根据返回的数据长度是否为空或者大于8k, 如果满足, 直接返回Response
, 如果小于8k, 重新构建新的Response
返回, 并关闭响应流; 这里重新构建是因为流只能读取一次, 如果关闭了就读取不到了
- 如果状态码是404, 并且设置dismiss404为true, 那么将忽略异常
- 如果状态码不是200-300也不是404, 那么根据响应头是否鞋带
Retry-After
, 并有正确的值, 那么将会抛重试异常进行重试, 否则抛FeignException
, 然后关闭响应流 - 如果返回类型是void, 并且不允许对void进行编码(decodeVoid=false), 那么关闭响应流, 直接返回null
- 如果方法返回值是
TypedResponse
类型, 那么将响应体通过Decoder.Default
解码转为TypedResponse
中的参数泛型类型, 并返回TypedResponse
- 如果返回值是其它类型(非TypedResponse和Response), 那么直接将响应体通过
Decoder.Default
解码转为返回类型, 然后直接返回 - 如果开启了解码后关闭流的动作(closeAfterDecode=treu), 那么关闭响应流(一般情况下会走这个逻辑)
相关文章:
3.4.SynchronousMethodHandler组件之ResponseHandler
前言 feign发送完请求后, 拿到返回结果, 那么这个返回结果肯定是需要经过框架进一步处理然后再返回到调用者的, 其中ResponseHandler就是用来处理这个返回结果的, 这也是符合正常思维的处理方式, 例如springmvc部分在调用在controller端点前后都会增加扩展点。 从图中可以看得…...
Linux 下进程的状态
操作系统中常见进程状态 在操作系统中有六种常见进程状态: 新建状态: 进程正在被创建. 此时操作系统会为进程分配资源, 如: 内存空间等, 进行初始化就绪状态: 进程已经准备好运行了, 只需要等待被调度, 获取 CPU 资源就可以执行了, 操作系统中可能同时存在多个进程处于就绪状…...
【计算机网络】核心部分复习
目录 交换机 v.s. 路由器OSI七层更实用的TCP/IP四层TCPUDP 交换机 v.s. 路由器 交换机-MAC地址 链接设备和设备 路由器- IP地址 链接局域网和局域网 OSI七层 物理层:传输设备。原始电信号比特流。数据链路层:代表是交换机。物理地址寻址,交…...
Spring Boot开发实战:从入门到构建高效应用
Spring Boot 是 Java 开发者构建微服务、Web 应用和后端服务的首选框架之一。其凭借开箱即用的特性、大量的自动化配置和灵活的扩展性,极大简化了开发流程。本文将以实战为核心,从基础到高级,全面探讨 Spring Boot 的应用开发。 一、Spring B…...
pyshark安装使用,ubuntu:20.04
1.容器创建 命令 docker run -d --name pyshark -v D:\src:/root/share ubuntu:2004 /bin/bash -c "while true;do sleep 1000;done" 用于创建并启动一个新的 Docker 容器。 docker run -d --name pyshark -v D:\src:/root/share ubuntu:2004 /bin/bash -c "w…...
基本功能实现
目录 1、环境搭建 2、按键控制灯&电机 LED 电机 垂直按键(机械按键) 3、串口调试功能 4、定时器延时和定时器中断 5、振动强弱调节 6、万年历 7、五方向按键 1、原理及分析 2、程序设计 1、环境搭建 需求: 搭建一个STM32F411CEU6工程 分析: C / C 宏定义栏…...
《那个让服务器“跳舞”的bug》
在程序的世界里,bug 就像隐藏在暗处的小怪兽,时不时跳出来捣乱。而在我的职业生涯中,有一个bug让我至今难忘,它不仅让项目差点夭折,还让我熬了无数个通宵。这个故事发生在一个风和日丽的下午,我们正在开发一…...
Python 网络爬虫进阶:动态网页爬取与反爬机制应对
在上一篇文章中,我们学习了如何使用 Python 构建一个基本的网络爬虫。然而,在实际应用中,许多网站使用动态内容加载或实现反爬机制来阻止未经授权的抓取。因此,本篇文章将深入探讨以下进阶主题: 如何处理动态加载的网…...
创建可直接用 root 用户 ssh 登陆的 Docker 镜像
有时候我们在 Mac OS X 或 Windows 平台下需要开发以 Linux 为运行时的应用,IDE 或可直接使用 Docker 容器,或 SSH 远程连接。本地命令行下操作虽然可以用 docker exec 连接正在运行的容器,但 IDE 远程连接的话 SSH 总是一种较为通用的连接方…...
wordpress 中添加图片放大功能
功能描述 使用 Fancybox 实现图片放大和灯箱效果。自动为文章内容中的图片添加链接,使其支持 Fancybox。修改了 header.php 和 footer.php 以引入必要的 CSS 和 JS 文件。在 functions.php 中通过过滤器自动为图片添加 data-fancybox 属性。 最终代码 1. 修改 hea…...
数据结构 (7)线性表的链式存储
前言 线性表是一种基本的数据结构,用于存储线性序列的元素。线性表的存储方式主要有两种:顺序存储和链式存储。链式存储,即链表,是一种非常灵活和高效的存储方式,特别适用于需要频繁插入和删除操作的场景。 链表的基本…...
库的操作.
创建、删除数据库 创建语法: CREATE DATABASE [IF NOT EXISTS] db_name[ ]是可选项,IF NOT EXISTS 是表明如果不存在才能创建数据库 //查看数据库,假设7行 show databases; //创建数据库 --- 本质在Linux创建一个目录 create database databa…...
Vue进阶之Vue CLI服务—@vue/cli-service Vuex
Vue CLI服务—vue/cli-service & Vuex vue/cli-service初识bin/vue-cli-service.js代码执行解读 Vuexgenerator/index.jsstore/index.js插件化的能力怎么引入呢? vue/cli-service 初识 第一块是上一个讲述的cli是把我们代码的配置项,各种各样的插件…...
导入100道注会cpa题的方法,导入试题,自己刷题
一、问题描述 复习备考的小伙伴们,往往希望能够利用零碎的时间和手上的试题,来复习和备考 用一个能够导入自己试题的刷题工具,既能加强练习又能利用好零碎时间,是一个不错的解决方案 目前市面上刷题工具存下这些问题 1、要收费…...
数据库操作、锁特性
1. DML、DDL和DQL是数据库操作语言的三种主要类型 1.1 DML(Data Manipulation Language)数据操纵语言 DML是用于检索、插入、更新和删除数据库中数据的SQL语句。 主要的DML语句包括: SELECT:用于查询数据库中的数据。 INSERT&a…...
学习笔记039——SpringBoot整合Redis
文章目录 1、Redis 基本操作Redis 默认有 16 个数据库,使用的是第 0 个,切换数据库添加数据/修改数据查询数据批量添加批量查询删除数据查询所有的 key清除当前数据库清除所有数据库查看 key 是否存在设置有效期查看有效期 2、Redis 数据类型String追加字…...
(笔记)简单了解ZYNQ
1、zynq首先是一个片上操作系统(Soc),结合了arm(PS)和fpga(PL)两部分组成 Zynq系统主要由两部分组成:PS(Processing System)和PL(Programmable L…...
大众点评小程序mtgsig1.2算法
测试效果: var e function _typeof(o) {return "function" typeof Symbol && "symbol" typeof Symbol.iterator? function (o) {return typeof o;}: function (o) {return o && "function" typeof Symbol &…...
七牛云AIGC内容安全方案助力企业合规创新
随着人工智能生成内容(AIGC)技术的飞速发展,内容审核的难度也随之急剧上升。在传统审核场景中,涉及色情、政治、恐怖主义等内容的标准相对清晰明确,但在AIGC的应用场景中,这些界限变得模糊且难以界定。用户可能通过交互性引导AI生成违规内容,为审核工作带来了前所未有的不可预测…...
.net的winfrom程序 窗体透明打开窗体时出现在屏幕右上角
窗体透明, 将Form的属性Opacity,由默认的100% 调整到 80%,这个数字越小越透明(尽量别低于50%,不信你试试看)! 打开窗体时出现在屏幕右上角 //构造函数 public frmCalendarList() {InitializeComponent();//打开窗体&…...
基于YOLOv8深度学习的智慧课堂教师上课行为检测系统研究与实现(PyQt5界面+数据集+训练代码)
随着人工智能技术的迅猛发展,智能课堂行为分析逐渐成为提高教学质量和提升教学效率的关键工具之一。在现代教学环境中,能够实时了解教师的课堂表现和行为,对于促进互动式教学和个性化辅导具有重要意义。传统的课堂行为分析依赖于人工观测&…...
使用 Tkinter 创建一个简单的 GUI 应用程序来合并视频和音频文件
使用 Tkinter 创建一个简单的 GUI 应用程序来合并视频和音频文件 Python 是一门强大的编程语言,它不仅可以用于数据处理、自动化脚本,还可以用于创建图形用户界面 (GUI) 应用程序。在本教程中,我们将使用 Python 的标准库模块 tkinter 创建一…...
【C++笔记】模板进阶
前言 各位读者朋友们大家好!上一期我们讲了stack、queue以及仿函数。先前我们讲过模板的初阶内容,这一期我们来更深入的学习一下模板。 一. 非类型模板参数 1.1 非类型模板参数 模板参数分为类型形参和类类型形参: 类型形参:…...
Soul App创始人张璐团队亮相GITEX GLOBAL 2024,展示多模态AI的交互创新
随着全球AI领域的竞争加剧,越来越多的科技巨头和创新企业纷纷致力于多模态AI的开发。2024年10月14日至18日,GITEX GLOBAL海湾信息技术博览会在迪拜举行,吸引了超过6700家全球科技巨头和创新公司参与,展示了智能互联、人工智能等领域的新成果。 此次展会中,Soul App创始人张璐团…...
ffmpeg.wasm 在浏览器运行ffmpeg操作视频
利用ffmpeg.wasm,可以在浏览器里运行ffmpeg,实现对音视频的操作 参考链接: https://blog.csdn.net/jchsgwbr/article/details/143252044 https://gitee.com/CXBalCai/ffmpeg-template 其他参考 https://github.com/ffmpegwasm/ffmpeg.wasm https://b…...
用Python爬虫“偷窥”1688商品详情:一场数据的奇妙冒险
引言:数据的宝藏 在这个信息爆炸的时代,数据就像是一座座等待挖掘的宝藏。而对于我们这些电商界的探险家来说,1688上的商品详情就是那些闪闪发光的金子。今天,我们将化身为数据的海盗,用Python这把锋利的剑࿰…...
CentOS上如何离线批量自动化部署zabbix 7.0版本客户端
CentOS上如何离线批量自动化部署zabbix 7.0版本客户端 管理的服务器大部分都是CentOS操作系统,版本主要是CentOS 7。因为监控服务器需要,要在前两天搭建的Zabbix 7.0系统上把这些CentOS 7系统都监控起来。因为服务器数量众多,而且有些服务器…...
【开源项目】ChinaAddressCrawler 中国行政区划数据(1980-2023年)采集及转换(Java版),含SQL格式及JSON格式
ChinaAddressCrawler 开源项目地址:https://gitee.com/li_yu_jiang/ChinaAddressCrawler 来源于国家民政部的数据只包括省级(省/直辖市/自治区/特别行政区)、地级(地级市/地区/自治州/盟)、县级(县/市辖区…...
React中事件处理和合成事件:理解与使用
🤍 前端开发工程师、技术日更博主、已过CET6 🍨 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 🕠 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 🍚 蓝桥云课签约作者、上架课程《Vue.js 和 E…...
Local Changes不展示,DevEco Studio的git窗口中没有Local Changes
DevEco Studio的git窗口中,没有Local Changes,怎么设置可以调出? 进入File-->Settings-->Version Control,将Use non-modal commit interface前的勾选框取消勾选,点击OK即可在打开git窗口,就可以看到…...
网站建设制作一个网站的费用/如何写软文推广产品
字符串与字节数组的转换 String str "helloworld"; byte data[] str.getBytes(); for(int x 0 ; x < data.length ; x) { data[x]- 32 ; System.out.print(datd[x] ,); } System.out.println(new String(data)); 通过程序可以发现,字节并不适合处…...
沈阳网站搜索排名/百度广告登录入口
最后贴一张小米官方拆机零件汇总图片:软件篇:小米路由器的系统是在开源OpenWRT的基础上进行了定制。界面交互更加友好。经过几天的试用,《假装是极客》感受最深的是,小米路由的APP与小米系列硬件已经深度集成,如果是米…...
兴化网站开发/企业网站免费制作
Hive是一个基于Hadoop的数据仓库平台。通过hive,我们可以方便地进行ETL的工作。hive定义了一个类似于SQL的查询语言:HQL,能 够将用户编写的QL转化为相应的Mapreduce程序基于Hadoop执行。 Hive是Facebook 2008年8月刚开源的一个数据仓库框架&a…...
淮安做网站的公司/百度网盘下载的文件在哪
分形树绘制 v1.0 五角星绘制: 使用tutle库在Python中绘制简单图形 案例分析: import turtle:引入绘制图形的turtle库利用turtle库中的函数进行编程——模型编程上机实验: 1 """2 作者:李舵3 功…...
wordpress 网店/软件培训机构排名
增删节点 数据迁移一、所有事情开始之前,先要备份好cloudera manager的数据库,以及hadoop集群中的一些组件带的数据库。这里保存了很多元数据,像hive这种丢了很麻烦的。二、如果需要换nameNode的存储目录1、备份nameNode原始数据cp -r /ddhom…...
如何不用域名也可以做网站/品牌推广工作内容
file ./liveMedia/rtcp_from_spec.o ./liveMedia/rtcp_from_spec.o: ELF 32-bit LSB relocatable, MIPS, MIPS32 rel2 version 1 (SYSV), not stripped...