当前位置: 首页 > news >正文

ReactiveStreams、Reactor、SpringWebFlux

注意: 本文内容于 2024-12-28 21:22:12 创建,可能不会在此平台上进行更新。如果您希望查看最新版本或更多相关内容,请访问原文地址:ReactiveStreams、Reactor、SpringWebFlux。感谢您的关注与支持!

ReactiveStreams是一个处理异步流的规范,定义了Publisher、Subscriber、Subscription、Processor接口。

Reactor是ReactiveStreams的实现,对于Publisher提供了两个核心实现——Mono与Flux。

SpringWebFlux是构建在Reactor之上的响应式Web框架。

本文源码

一、Reactive Streams

Reactive Streams 是一个用于处理异步流数据的标准规范,特别适合处理非阻塞背压控制的场景。

所谓的背压控制,是指在异步数据流中,消费者根据自身的能力向生产者获取数据进行消费,以避免数据积压导致系统过载或者崩溃。

TCP中的拥塞控制,也可以看作是背压控制的一种实现。

1.1 API规范

Reactive Streams 的四大API接口如下

  1. org.reactivestreams.Publisher: 发布者接口,提供数据流。
    • void subscribe(Subscriber<? super T> subscriber)
  2. org.reactivestreams.Subscriber: 订阅者接口,接收数据流。
    • void onSubscribe(Subscription subscription)
    • void onNext(T item)
    • void onError(Throwable throwable)
    • void onComplete()
  3. org.reactivestreams.Subscription: 订阅关系接口,提供控制机制。
    • void request(long n)
    • void cancel()
  4. org.reactivestreams.Processor: 继承Publisher和Subscriber的接口。

简单绘制一个时序图,加深对整个链路的理解。

使用Publisher、Subscriber、Subscription实现一个简单的订阅功能,示例如下

以下代码,并没有异步相关的内容。只是为了学习整个API流转链路。

import org.reactivestreams.Publisher;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;public class Example01 {private static final Logger log = LoggerFactory.getLogger(Example01.class);/*** 订阅关系*/public static Subscription getSubscription(Subscriber<? super String> subscriber, String... items) {return new Subscription() {private final AtomicBoolean canceled = new AtomicBoolean(false);private final AtomicInteger sendItems = new AtomicInteger(0);/*** request数据* 内部onNext会request后面的数据,而onComplete应该要等所有的数据消费完毕后,才会执行。* 故需要加锁保证线程安全,此处采取CAS。源码参考reactor.core.publisher.Operators.ScalarSubscription#request(long)*/@Overridepublic void request(long n) {if (n > 0) {if (canceled.get()) {return;}if (sendItems.get() >= items.length) {subscriber.onComplete();} else {subscriber.onNext(items[sendItems.getAndIncrement()]);}}}@Overridepublic void cancel() {canceled.compareAndSet(true, true);}};}/*** 发布者*/private static Publisher<String> getPublisher(String... items) {return new Publisher<String>() {@Overridepublic void subscribe(Subscriber<? super String> subscriber) {subscriber.onSubscribe(getSubscription(subscriber, items));}};}/*** 订阅者*/private static Subscriber<String> getSubscriber() {return new Subscriber<String>() {private Subscription subscription;@Overridepublic void onSubscribe(Subscription s) {this.subscription = s;log.info("Subscribed to {}", s);// 请求第一个元素subscription.request(1);}@Overridepublic void onNext(String s) {log.info("Received {}", s);// 请求下一个元素subscription.request(1);}@Overridepublic void onError(Throwable t) {log.error("Error occurred", t);}@Overridepublic void onComplete() {log.info("All items received");}};}public static void main(String[] args) {// 订阅Flux// Flux.just("first", "second", "third").delayElements(Duration.ofSeconds(2))//         .subscribe(getSubscriber());/*** org.reactivestreams.Publisher: 发布者* org.reactivestreams.Subscriber: 订阅者* org.reactivestreams.Subscription: 发布者和订阅者之间的桥梁,数据流控制的核心机制。*/// 订阅自定义PublishergetPublisher("first", "second", "third", "fourth", "fifth").subscribe(getSubscriber());while (true) {}}
}

运行结果

1.2 API实现库

Reactive Streams实现如下

  • Java9+ java.util.concurrent.Flow
  • RxJava: Reactive Extension Java
  • Reactor: Reactor Framework

Java9+中提供了java.util.concurrent.Flow,在标准库中提供ReactiveStreams规范的接口。

ReactiveStreams内部也提供了适配JDK中Flow的适配器org.reactivestreams.FlowAdapters

RxJava以及Reactor,分别用于Java开发中不同领域。RxJava一般用于Android开发,Reactor一般用于Spring开发。

二、Reactor

Reactor提供了两个核心类

  1. reactor.core.publisher.Flux:发布0或N个元素的异步数据流
  2. reactor.core.publisher.Mono:发布0或1个元素的异步数据流

这两者都是Publisher,主要区别在于发送数据的数量。因此在使用上,相关的API都是差不多的。

2.1 Mono

Mono中的静态方法,用于创建Mono实例。

Mono实例中的成员方法如下

方法名说明
and合并多个 Mono 实例,等所有 Mono 完成后返回一个新的 Mono
as用指定的类型包裹当前 Mono,通常用于类型转换。
block阻塞并获取 Mono 的结果,直到执行完成。
blockOptional类似于 block,但返回 Optional 包裹的结果。
cache缓存当前 Mono 的值,使得未来的订阅者可以共享相同的结果。
cacheInvalidateIf缓存失效条件满足时重新缓存,适用于动态失效策略。
cacheInvalidateWhen在指定条件下使缓存失效。
cancelOn当给定的 Publisher 发出信号时,取消当前 Mono
cast强制类型转换为指定的类型。
checkpoint在流的执行过程中插入检查点,用于调试。
concatWith与另一个 MonoFlux 连接,按顺序执行。
contextWrite修改 Mono 的上下文。
defaultIfEmpty如果 Mono 为空,返回默认值。
delayElement延迟发出元素的时间。
delaySubscription延迟订阅,等到指定的时间或事件发生才开始订阅。
delayUntil延迟直到指定的 Publisher 发出信号时才开始执行。
dematerialize将一个包含 SignalMono 转换为原始值的 Mono
doAfterSuccessOrError在执行成功或出错后执行的操作。
doAfterTerminateMono 结束时执行的操作,不论成功或失败。
doFinallyMono 完成时执行的最终操作。
doFirstMono 执行前执行的操作。
doOnCancel当订阅者取消时执行的操作。
doOnDiscard当元素被丢弃时执行的操作。
doOnEach对每个发出的信号执行操作。
doOnError当发生错误时执行的操作。
doOnNext每次元素发出时执行的操作。
doOnRequest在请求信号到达时执行的操作。
doOnSubscribe在订阅时执行的操作。
doOnSuccess当成功完成时执行的操作。
doOnSuccessOrError无论成功还是失败,都执行的操作。
doOnTerminate在终止时执行的操作。
elapsed返回每个信号的时间戳。
expand展开 Mono,生成新的 Mono,直到满足某个条件。
expandDeep深度展开 Mono,通常递归调用直到满足条件。
filter过滤元素,只有符合条件的元素才会发出。
filterWhen使用 Publisher 的元素条件来过滤当前 Mono
flatMap转换元素,返回新的 MonoFlux
flatMapIterable将每个元素转换为一个可迭代的元素。
flatMapMany将元素转换为 Flux
fluxMono 转换为 Flux
handle基于元素的条件来决定如何处理流。
hasElement判断是否包含元素。
hide隐藏 Mono 的实现细节,返回一个不可观察的 Mono
ignoreElement忽略元素,只关心是否完成。
log记录 Mono 中的信号,便于调试。
map将元素映射为另一个元素。
mapNotNull映射并排除空值。
materialize将信号转化为一个 Signal 对象。
mergeWith合并当前 Mono 和另一个 Mono
metrics获取流的度量信息。
nameMono 设置名称,用于调试和监控。
ofType根据类型过滤信号。
onErrorContinue在发生错误时继续执行。
onErrorMap将错误映射为其他类型。
onErrorResume在发生错误时恢复操作。
onErrorReturn在发生错误时返回默认值。
onErrorStop在发生错误时终止流。
onTerminateDetach在终止时解除与订阅者的连接。
or连接另一个 Mono,如果当前 Mono 没有值或为空时执行。
publish启动 Mono 并返回一个共享的流。
publishOn指定在哪个线程调度上下文中执行 Mono
repeat重复执行 Mono,直到满足某个条件。
repeatWhen基于另一个 Publisher 的信号来控制重复。
repeatWhenEmptyMono 为空时重复执行。
retry在发生错误时重试操作。
retryWhen基于另一个 Publisher 来控制重试。
share共享执行的结果,避免重复执行。
single获取 Mono 中唯一的元素。
subscribe启动流的执行并订阅。
subscribeOn指定在哪个线程调度上下文中订阅 Mono
subscribeWith通过指定的 Subscriber 订阅 Mono
subscriberContext获取或修改订阅时的上下文。
switchIfEmpty如果 Mono 为空,则切换到另一个 Mono
tagMono 打上标签,用于调试和日志。
take限制只获取前 N 个元素。
takeUntilOther当另一个 Publisher 发出信号时停止当前 Mono
then在当前 Mono 执行完后执行另一个操作。
thenEmpty在当前 Mono 执行完后返回一个空的 Mono
thenMany在当前 Mono 执行完后返回一个 Flux
thenReturn在当前 Mono 执行完后返回指定的值。
timed返回元素和其时间戳。
timeout如果 Mono 在指定时间内没有发出信号,则触发超时。
timestamp返回元素及其时间戳。
toFutureMono 转换为 Future
toProcessorMono 转换为 Processor,适用于与 Flux 的结合。
toString返回 Mono 的字符串表示。
transform使用转换函数修改 Mono
transformDeferred延迟转换,直到订阅发生。
transformDeferredContextual延迟转换并访问上下文。
zipWhen与另一个 Mono 的信号配对,形成 Mono 的组合。
zipWith与另一个 Mono 的信号进行合并,形成 Mono 的组合。

2.2 Flux

Flux中的静态方法,用于创建Flux实例。

Flux实例中的成员方法如下

方法名说明
all判断 Flux 中的所有元素是否满足给定条件。
any判断 Flux 中是否有任何一个元素满足给定条件。
asFlux 转换为指定类型的 Publisher
blockFirst阻塞并返回 Flux 中的第一个元素。
blockLast阻塞并返回 Flux 中的最后一个元素。
bufferFlux 中的元素分成固定大小的缓冲区。
bufferTimeout按照指定的时间或缓冲区大小将元素分块。
bufferUntil在满足某个条件时开始一个新的缓冲区。
bufferUntilChanged将相邻相同的元素合并到同一个缓冲区。
bufferWhen根据外部 Publisher 切换缓冲区。
bufferWhile按照指定条件将元素分组为缓冲区。
cache缓存 Flux 的值,使得未来的订阅者可以共享相同的结果。
cancelOn当另一个 Publisher 发出信号时取消当前的 Flux
castFlux 强制转换为指定的类型。
checkpoint在执行流中插入检查点,用于调试和分析。
collect收集流中的元素,按给定规则生成结果。
collectList收集 Flux 中的所有元素并返回一个 List
collectMapFlux 中的元素收集为一个 Map
collectMultimapFlux 中的元素收集为一个多值 Map
collectSortedListFlux 中的元素收集为排序的 List
concatMap将元素转换为 Mono,按顺序处理。
concatMapDelayErrorconcatMap 类似,但在错误发生时延迟处理。
concatMapIterable将每个元素转换为可迭代的元素,并按顺序合并。
concatWith与另一个 Flux 连接,按顺序执行。
concatWithValues连接多个值作为新的 Flux
contextWrite修改 Flux 的上下文。
count统计 Flux 中元素的数量。
defaultIfEmpty如果 Flux 为空,则返回默认值。
delayElements延迟元素的发出。
delaySequence延迟整个序列的发出。
delaySubscription延迟订阅,直到指定的时间或事件发生。
delayUntil延迟直到另一个 Publisher 发出信号。
dematerialize将一个包含 SignalFlux 转换为原始元素的 Flux
distinct过滤掉重复的元素,保持唯一性。
distinctUntilChanged过滤掉相邻重复的元素。
doAfterTerminateFlux 完成后执行的操作。
doFinallyFlux 终止时执行的操作。
doFirstFlux 执行前执行的操作。
doOnCancelFlux 被取消时执行的操作。
doOnCompleteFlux 完成时执行的操作。
doOnDiscard在元素被丢弃时执行的操作。
doOnEachFlux 发出的每个元素执行操作。
doOnError在发生错误时执行的操作。
doOnNext每次 Flux 发出元素时执行的操作。
doOnRequest在请求信号到达时执行的操作。
doOnSubscribe在订阅时执行的操作。
doOnTerminateFlux 终止时执行的操作。
elapsed获取每个元素的时间戳和持续时间。
elementAt获取指定索引处的元素。
expand对每个元素进行展开,生成新的元素流。
expandDeep深度展开 Flux,通常递归展开元素。
filter过滤出符合条件的元素。
filterWhen使用外部 Publisher 的信号过滤 Flux 中的元素。
flatMap将元素转换为 Flux,并合并其发出的所有元素。
flatMapDelayError在发生错误时延迟元素的转换。
flatMapIterable将元素转换为可迭代的 Flux
flatMapSequential顺序地将元素转换为 Flux
flatMapSequentialDelayError顺序转换,并在发生错误时延迟。
getPrefetch获取 Flux 的预取量。
groupBy将元素按指定的键分组。
groupJoin类似 groupBy,但用于联接多个流。
handle根据元素的条件进行流的处理。
hasElement判断 Flux 中是否包含某个元素。
hasElements判断 Flux 中是否包含多个元素。
hide隐藏 Flux 的实现细节,返回不可观察的流。
ignoreElements忽略 Flux 中的所有元素,只关心终止信号。
index返回元素在流中的索引。
join将多个 Flux 中的元素合并为一个字符串。
last获取 Flux 中的最后一个元素。
limitRate限制从流中请求的元素数量。
limitRequest限制从流中请求的最大元素数量。
log记录流中的元素,用于调试。
map将元素映射为新的类型。
mapNotNull映射并排除空值。
materialize将信号转换为 Signal 对象。
mergeComparingWith将两个 Flux 合并并根据比较条件排序。
mergeOrderedWith将两个有序的 Flux 合并。
mergeWith合并当前 Flux 和另一个 Flux
metrics获取流的度量信息。
nameFlux 设置名称,便于调试。
next获取 Flux 中的下一个元素。
ofType根据类型过滤信号。
onBackpressureBuffer在背压时缓存元素。
onBackpressureDrop在背压时丢弃元素。
onBackpressureError在背压时触发错误。
onBackpressureLatest在背压时保留最新的元素。
onErrorContinue在发生错误时继续执行。
onErrorMap在错误时将其映射为其他类型。
onErrorResume在错误时恢复操作。
onErrorReturn在错误时返回默认值。
onErrorStop在错误时终止流。
onTerminateDetach在终止时分离与订阅者的连接。
or连接另一个 Flux,如果当前 Flux 为空时执行。
parallelFlux 分发到多个线程进行并行处理。
publish启动 Flux 并返回一个共享流。
publishNext在流的每个元素发出时开始新的发布。
publishOn指定在哪个线程调度上下文中执行流。
reduce将流中的所有元素合并为单一值。
reduceWith使用指定初始值对元素进行合并。
repeat重复执行 Flux 直到满足某个条件。
repeatWhen基于另一个 Publisher 的信号来控制重复。
replay缓存并重播流中的元素。
retry在发生错误时重试操作。
retryWhen基于另一个 Publisher 来控制重试。
sample每隔指定时间间隔取一个元素。
sampleFirst获取流中的第一个元素。
sampleTimeout超过指定时间间隔时触发超时操作。
scan对流中的元素执行累加操作。
scanWith使用给定的初始值对元素执行累加操作。
share共享流的执行,避免重复执行。
shareNext将下一个发出的元素共享给多个订阅者。
single获取 Flux 中唯一的元素。
singleOrEmpty获取 Flux 中唯一的元素,如果为空返回空。
skip跳过流中的前 N 个元素。
skipLast跳过流中的最后 N 个元素。
skipUntil跳过直到满足某个条件的元素。
skipUntilOther跳过直到另一个 Flux 发出信号时的元素。
skipWhile跳过直到满足条件的元素。
sort对流中的元素进行排序。
startWith在流的开始处添加额外元素。
subscribe订阅并启动 Flux
subscribeOn指定在哪个线程调度上下文中订阅流。
subscribeWith通过指定的 Subscriber 订阅流。
subscriberContext获取或修改订阅时的上下文。
switchIfEmpty如果 Flux 为空,则切换到另一个 Flux
switchMap将元素转换为另一个 Flux 并切换执行。
switchOnFirst在流开始时选择一个 Flux 进行切换。
tagFlux 打标签,便于调试和日志。
take限制只获取前 N 个元素。
takeLast获取流中的最后 N 个元素。
takeUntil获取直到满足条件为止的元素。
takeUntilOther获取直到另一个 Flux 发出信号时的元素。
takeWhile获取满足条件的元素,直到条件不满足为止。
then在当前流完成后执行另一个操作。
thenEmpty在当前流完成后返回一个空流。
thenMany在当前流完成后返回另一个 Flux
timed返回每个元素的时间戳和持续时间。
timeout如果 Flux 在指定时间

三、SpringWebFlux

3.1 WebHandler与WebFilter

在SpringMVC中,有Servlet、Filter。

在SpringWebFlux中,有WebHandler、WebFilter,对标的其实就是Servlet API中的Servlet、Filter。甚至执行链也是相似的设计。

Servlet相关知识阅读Servlet - 言成言成啊

Filter相关知识阅读Filter和Listener - 言成言成啊

WebFilter的注册如下

@Bean
@Order(0) // 值越小,优先级越高
@ConditionalOnProperty(name = "allowAllCors.learnFilter", havingValue = "true")
public WebFilter aFilter() {/*** 在servlet中。请求的扭转是 aFilter-->bFilter-->servlet-->bFilter-->aFilter* 在webflux中同理。Filter对应WebFilter,Servlet对应WebHandler*/return (exchange, chain) -> {log.info("aFilter start");return chain.filter(exchange).doOnSuccess(t -> log.info("aFilter end"));};
}

3.2 实际案例

跨域配置

@Bean
@Order(Integer.MIN_VALUE)
@ConditionalOnProperty(name = "allowAllCors.personal", havingValue = "true")
public WebFilter personalCorsFilter(WebSocketHandlerAdapter webFluxWebSocketHandlerAdapter) {WebFilter webFilter = (exchange, chain) -> {ServerHttpRequest request = exchange.getRequest();ServerHttpResponse response = exchange.getResponse();HttpHeaders headers = response.getHeaders();//用*会导致范围过大,浏览器出于安全考虑,在allowCredentials为true时会不认*这个操作,因此可以使用如下代码,间接实现允许跨域headers.set(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, request.getHeaders().getFirst("origin"));headers.set(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, "*");headers.set(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, "*");//允许跨域发送cookieheaders.set(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");if ("OPTIONS".equalsIgnoreCase(request.getMethod().name())) {response.setStatusCode(HttpStatus.OK);return Mono.empty();} else {return chain.filter(exchange);}};log.info("allowAllCors.personal is set to true");return webFilter;}

全局异常拦截/定义响应格式

首先,定义通用响应格式

import lombok.Data;
import reactor.core.publisher.Mono;@Data
public class Resp<T> {private int code;private String msg;private T data;public static <T> Resp<T> ok(T t) {Resp<T> resp = new Resp<>();resp.setCode(0);resp.setMsg("成功");resp.setData(t);return resp;}public static Resp<Void> failure(String msg) {Resp<Void> resp = new Resp<>();resp.setCode(1);resp.setMsg("失败: " + msg);return resp;}public static Resp<Void> error() {Resp<Void> resp = new Resp<>();resp.setCode(500);resp.setMsg("服务器内部错误");return resp;}public static <T> Mono<Resp<T>> getSuccessResp(Mono<T> mono) {return mono.map(Resp::ok);}public static Mono<Resp<Void>> getFailureResp(String msg) {return Mono.just(failure(msg));}public static Mono<Resp<Void>> getErrorResp() {return Mono.just(error());}
}

其次,定义自定义异常DIYException。

最后,配置全局异常拦截。

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import reactor.core.publisher.Mono;
import top.meethigher.utils.Resp;@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {@ExceptionHandler(Exception.class)public Mono<Resp<Void>> handleException(Exception e) {log.error("api occurred exception", e);return Resp.getErrorResp();}@ExceptionHandler(DIYException.class)public Mono<Resp<Void>> handleDiyException(DIYException e) {log.error("api occurred exception", e);return Resp.getFailureResp(e.getMessage());}
}

相关文章:

ReactiveStreams、Reactor、SpringWebFlux

注意&#xff1a; 本文内容于 2024-12-28 21:22:12 创建&#xff0c;可能不会在此平台上进行更新。如果您希望查看最新版本或更多相关内容&#xff0c;请访问原文地址&#xff1a;ReactiveStreams、Reactor、SpringWebFlux。感谢您的关注与支持&#xff01; ReactiveStreams是…...

Qt 的信号槽机制详解:之信号槽引发的 Segmentation Fault 问题拆析(下)

Qt 的信号槽机制详解&#xff1a;之信号槽引发的 Segmentation Fault 问题拆析&#xff08;下&#xff09; 前言一. 信号槽的误用导致崩溃的常见原因1.信号和槽连接的对象被提前释放案例解决方法 2.参数类型不匹配案例解决方法 3. 多线程信号槽使用不当案例解决方法 4. 信号重复…...

opencv(cpp) Mat使用总结

opencv访问矩阵的通道数 #include <opencv2/opencv.hpp> #include <iostream>int main() {// 创建一个3通道的彩色图像&#xff08;例如&#xff0c;BGR格式&#xff09;cv::Mat colorImage cv::Mat::zeros(100, 100, CV_8UC3);// 创建一个单通道的灰度图像cv::M…...

【Hackthebox 中英 Write-Up】Web Request | 分析 HTTP 请求和响应

欢迎来到我的writeup分享&#xff01;我希望大家不要只关注结果或答案&#xff0c;而是通过耐心阅读&#xff0c;尝试逆向工程理解背后的运作原理。在这里&#xff0c;你不仅能找到解题的思路&#xff0c;还能学到更多与Hack The Box等平台相关的技术和技巧&#xff0c;期待与你…...

c#多线程之生产者-消费者模型

在 C# 中实现 生产者-消费者模式&#xff0c;通常需要多个线程来处理数据的生产和消费。我们可以使用 Queue<T> 来作为存储数据的队列&#xff0c;并使用 Thread、Mutex 或 Monitor 来确保线程安全。BlockingCollection<T> 是 C# 提供的一个线程安全的集合&#xf…...

Spring Boot中幂等性的应用

在 Spring Boot 中&#xff0c;幂等性是实现分布式系统设计和接口调用的一个重要概念&#xff0c;尤其在高并发、分布式环境下&#xff0c;确保接口重复调用不会引发系统数据异常至关重要。 幂等性概念 幂等性&#xff08;Idempotence&#xff09;是指一次请求和重复多次请求…...

【机器学习】分类

文章目录 1. 能否用回归解决分类问题2. 生成模型&#xff08;概率生成&#xff09;3. 判别模型&#xff08;逻辑回归&#xff09;4. 多分类问题 1. 能否用回归解决分类问题 二元分类 数据分布不规律&#xff0c;回归函数会尽量减少误差&#xff0c;导致不合理的偏移离分界较远…...

5.若依的角色权限控制

RBAC 基于角色的访问控制&#xff0c;通过角色来分配和管理用户的菜单权限。 修改课程管理的菜单到主类目下 新建角色并分配菜单 新建用户并分配角色 添加一个根菜单&#xff0c;父级为主类目...

Lumos学习王佩丰Excel第二十三讲:饼图美化与PPT图表

一、双坐标柱形图的补充知识 1、主次坐标设置 2、主次坐标柱形避让&#xff08;通过增加两个系列&#xff0c;挤压使得两个柱形挨在一起&#xff09; 增加两个系列 将一个系列设置成主坐标轴&#xff0c;另一个设成次坐标轴 调整系列位置 二、饼图美化 1、饼图美化常见设置 …...

安装winserver2008R2虚拟机步骤

一、服务器系统介绍 1.1什么是服务器&#xff1f; 服务器英文名称为“Server”&#xff0c;指的是网络环境下为客户机(Client)提供某种服务的专用计算机&#xff0c;服务器安装有网络操作系统(如Windows 2000 Server、Linux、Unix等)和各种服务器应用系统软件(如Web服务、电子…...

ACPI PM Timer

ACPI PM Timer 概述&#xff1a; ACPI PM Timer是一个非常简单的计时器&#xff0c;它以 3.579545 MHz 运行&#xff0c;在计数器溢出时生成系统控制中断&#xff08;SCI&#xff09;。它精度较低&#xff0c;建议使用其他定时器&#xff0c;如HPET或APIC定时器。 检测ACPI P…...

Linux 和设备树

“开放固件设备树”&#xff0c;简称 Devicetree (DT)&#xff0c;是一种用于描述硬件的数据结构和语言。更具体地说&#xff0c;它是操作系统可读取的硬件描述&#xff0c;因此操作系统无需对机器的详细信息进行硬编码。 从结构上看&#xff0c;DT 是一棵树&#xff0c;或具有…...

Qt仿音乐播放器:QFileDialog添加本地文件

一、套路 QFileDialog fileDialog(this);// 创建对话框&#xff0c;并设置父元素&#xff1b;fileDialog.setWindowTitle("添加本地下载的音乐");//设置窗口标题//设置文件对话框的默认打开路径 QString projectPathQDir::currentPath();//获取当前目录 QDir dir(pr…...

Odoo 引用字段 fields.Reference:动态关系的选择器

在 Odoo 模型开发中&#xff0c;关系型字段是构建复杂应用的基础。 然而&#xff0c;传统的 m2o、o2m 和 m2m 字段需要在模型定义时就明确指定关系的目标模型&#xff0c;这在某些场景下会显得不够灵活。 为了解决这个问题&#xff0c;Odoo 提供了 fields.Reference 引用字段&a…...

Android笔试面试题AI答之Android基础(6)

Android入门请看《Android应用开发项目式教程》 文章目录 1.Android Studio版本与Gradle版本有什么关联&#xff1f;**1. Gradle 的作用****2. Android Studio 与 Gradle 的关系****3. 版本对应关系****4. 如何查看和修改版本****查看当前版本****修改版本** **5. 版本不兼容的…...

C# 中的记录类型简介 【代码之美系列】

&#x1f380;&#x1f380;&#x1f380;代码之美系列目录&#x1f380;&#x1f380;&#x1f380; 一、C# 命名规则规范 二、C# 代码约定规范 三、C# 参数类型约束 四、浅析 B/S 应用程序体系结构原则 五、浅析 C# Async 和 Await 六、浅析 ASP.NET Core SignalR 双工通信 …...

利用Java爬虫速卖通按关键字搜索AliExpress商品

在这个信息爆炸的时代&#xff0c;数据的价值日益凸显。对于电商领域的从业者来说&#xff0c;能够快速获取商品信息成为了一项重要的技能。速卖通&#xff08;AliExpress&#xff09;作为全球领先的跨境电商平台&#xff0c;拥有海量的商品数据。本文将介绍如何使用Java语言编…...

gitlab runner 实现 微信小程序自动化部署

微信小程序多人开发的情况下&#xff0c;开发人员都只能在本机上发布体验版&#xff0c;且需要到小程序管理后台自行切换到自己发布的版本&#xff0c;会出现体验版本覆盖的问题。给开发测试带来问题。 miniprogram-ci 的发布&#xff0c;使得开发人员可以通过命令行上传小程序…...

Playwright爬虫xpath获取技巧

示例一 <button class"MuiButtonBase-root MuiButton-root MuiLoadingButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeLarge MuiButton-containedSizeLarge MuiButton-colorPrimary MuiButton-fullWidth MuiButton-root MuiLoadingButton…...

总结TCP/IP四层模型

总结TCP/IP四层模型 阅读目录(Content) 一、TCP/IP参考模型概述 1.1、TCP/IP参考模型的层次结构二、TCP/IP四层功能概述 2.1、主机到网络层  2.2、网络互连层  2.3、传输层  2.3、应用层 三、TCP/IP报文格式 3.1、IP报文格式3.2、TCP数据段格式3.3、UDP数据段格式3.4、套…...

微软PowerBI考试 PL300-选择 Power BI 模型框架【附练习数据】

微软PowerBI考试 PL300-选择 Power BI 模型框架 20 多年来&#xff0c;Microsoft 持续对企业商业智能 (BI) 进行大量投资。 Azure Analysis Services (AAS) 和 SQL Server Analysis Services (SSAS) 基于无数企业使用的成熟的 BI 数据建模技术。 同样的技术也是 Power BI 数据…...

Appium+python自动化(十六)- ADB命令

简介 Android 调试桥(adb)是多种用途的工具&#xff0c;该工具可以帮助你你管理设备或模拟器 的状态。 adb ( Android Debug Bridge)是一个通用命令行工具&#xff0c;其允许您与模拟器实例或连接的 Android 设备进行通信。它可为各种设备操作提供便利&#xff0c;如安装和调试…...

阿里云ACP云计算备考笔记 (5)——弹性伸缩

目录 第一章 概述 第二章 弹性伸缩简介 1、弹性伸缩 2、垂直伸缩 3、优势 4、应用场景 ① 无规律的业务量波动 ② 有规律的业务量波动 ③ 无明显业务量波动 ④ 混合型业务 ⑤ 消息通知 ⑥ 生命周期挂钩 ⑦ 自定义方式 ⑧ 滚的升级 5、使用限制 第三章 主要定义 …...

基于ASP.NET+ SQL Server实现(Web)医院信息管理系统

医院信息管理系统 1. 课程设计内容 在 visual studio 2017 平台上&#xff0c;开发一个“医院信息管理系统”Web 程序。 2. 课程设计目的 综合运用 c#.net 知识&#xff0c;在 vs 2017 平台上&#xff0c;进行 ASP.NET 应用程序和简易网站的开发&#xff1b;初步熟悉开发一…...

376. Wiggle Subsequence

376. Wiggle Subsequence 代码 class Solution { public:int wiggleMaxLength(vector<int>& nums) {int n nums.size();int res 1;int prediff 0;int curdiff 0;for(int i 0;i < n-1;i){curdiff nums[i1] - nums[i];if( (prediff > 0 && curdif…...

2025盘古石杯决赛【手机取证】

前言 第三届盘古石杯国际电子数据取证大赛决赛 最后一题没有解出来&#xff0c;实在找不到&#xff0c;希望有大佬教一下我。 还有就会议时间&#xff0c;我感觉不是图片时间&#xff0c;因为在电脑看到是其他时间用老会议系统开的会。 手机取证 1、分析鸿蒙手机检材&#x…...

企业如何增强终端安全?

在数字化转型加速的今天&#xff0c;企业的业务运行越来越依赖于终端设备。从员工的笔记本电脑、智能手机&#xff0c;到工厂里的物联网设备、智能传感器&#xff0c;这些终端构成了企业与外部世界连接的 “神经末梢”。然而&#xff0c;随着远程办公的常态化和设备接入的爆炸式…...

GruntJS-前端自动化任务运行器从入门到实战

Grunt 完全指南&#xff1a;从入门到实战 一、Grunt 是什么&#xff1f; Grunt是一个基于 Node.js 的前端自动化任务运行器&#xff0c;主要用于自动化执行项目开发中重复性高的任务&#xff0c;例如文件压缩、代码编译、语法检查、单元测试、文件合并等。通过配置简洁的任务…...

C++.OpenGL (20/64)混合(Blending)

混合(Blending) 透明效果核心原理 #mermaid-svg-SWG0UzVfJms7Sm3e {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-SWG0UzVfJms7Sm3e .error-icon{fill:#552222;}#mermaid-svg-SWG0UzVfJms7Sm3e .error-text{fill…...

LabVIEW双光子成像系统技术

双光子成像技术的核心特性 双光子成像通过双低能量光子协同激发机制&#xff0c;展现出显著的技术优势&#xff1a; 深层组织穿透能力&#xff1a;适用于活体组织深度成像 高分辨率观测性能&#xff1a;满足微观结构的精细研究需求 低光毒性特点&#xff1a;减少对样本的损伤…...