深入学习ErrorWebExceptionHandler

news/2024/4/20 20:04:49/文章来源:https://blog.csdn.net/aofengdaxia/article/details/129265983

我们来学习和了解下GatewayExceptionHandler有助于我们处理spring gateway和webFlux中的异常自定义处理。

它继承自ErrorWebExceptionHandler
类关系图如下:

WebExceptionHandler
+handle(ServerWebExchange exchange, Throwable ex)
ErrorWebExceptionHandler
AbstractErrorWebExceptionHandler
DefaultErrorWebExceptionHandler
+handle(ServerWebExchange exchange, Throwable ex)

通过类上述关系图,我们可以看到,DefaultErrorWebExceptionHandlerErrorWebExceptionHandler的实现类,如果我们不自定义类异常处理器,系统将自动装配DefaultErrorWebExceptionHandler

我们就来庖丁解牛,一步步地来学习下这一组类和接口。

AbstractErrorWebExceptionHandler

ErrorWebExceptionHandler
AbstractErrorWebExceptionHandler
InitializingBean
WebExceptionHandler
+handle(ServerWebExchange exchange, Throwable ex)

AbstractErrorWebExceptionHandler实现了ErrorWebExceptionHandler接口,其中handle方法是其核心方法。我们来看下其实现代码:

public Mono<Void> handle(ServerWebExchange exchange, Throwable throwable) {// 判断response是否已经提交,并且调用isDisconnectedClientError方法判断是否是客户端断开连接。// 如果已经断开连接,已经无法将消息发送给客户端,直接Mono.error(throwable)抛出异常。if (!exchange.getResponse().isCommitted() && !this.isDisconnectedClientError(throwable)) {// exchange.getAttributes().putIfAbsent(ERROR_INTERNAL_ATTRIBUTE, error);this.errorAttributes.storeErrorInformation(throwable, exchange);// 创建request ServerRequest request = ServerRequest.create(exchange, this.messageReaders);return this.getRoutingFunction(this.errorAttributes).route(request).switchIfEmpty(Mono.error(throwable)).flatMap((handler) -> {return handler.handle(request);}).doOnNext((response) -> {this.logError(request, response, throwable);}).flatMap((response) -> {return this.write(exchange, response);});} else {return Mono.error(throwable);}}

通过Handle方法的代码我们看出主要是实现了以下的事项:

  1. 判断response是否已经提交,并且调用isDisconnectedClientError方法判断是否是客户端断开连接。
  2. 如果已经断开连接,已经无法将消息发送给客户端,直接Mono.error(throwable)抛出异常。
  3. 将异常信息存储到exchange中。
  4. 创建request。
  5. 获取路由函数。
  6. 调用路由函数的handle方法。
  7. 然后执行日志记录。
  8. 最后将response写入到exchange中。

isDisconnectedClientError方法

private boolean isDisconnectedClientError(Throwable ex) {return DISCONNECTED_CLIENT_EXCEPTIONS.contains(ex.getClass().getSimpleName()) ||this.isDisconnectedClientErrorMessage(NestedExceptionUtils.getMostSpecificCause(ex).getMessage());
}

DISCONNECTED_CLIENT_EXCEPTIONS是一个集合,里面存放了一些异常信息,如果是这些异常信息, 就认为是客户端断开连接了。 或者通过isDisconnectedClientErrorMessage方法判断是否是客户端断开连接的异常信息。

下面我们来看看isDisconnectedClientErrorMessage方法的实现。

isDisconnectedClientErrorMessage方法:

private boolean isDisconnectedClientErrorMessage(String message) {message = message != null ? message.toLowerCase() : "";return message.contains("broken pipe") || message.contains("connection reset by peer");
}

上述代码的含义是:如果异常信息中包含“broken pipe”或者“connection reset by peer”,就认为是客户端断开连接了。

总结

综合起来,isDisconnectedClientError方法的含义是:如果DISCONNECTED_CLIENT_EXCEPTIONS集合中包含异常信息的类名或者异常信息中包含“broken pipe”或者“connection reset by peer”,就认为是客户端断开连接了。

NestedExceptionUtils

而isDisconnectedClientErrorMessage方法的参数message来自于NestedExceptionUtils.getMostSpecificCause(ex).getMessage()。我们进一步跟踪源码,可以看到NestedExceptionUtils.getMostSpecificCause(ex)方法的源码如下:

public static Throwable getMostSpecificCause(Throwable ex) {Throwable cause;Throwable result = ex;while(null != (cause = result.getCause()) && (result != cause)) {result = cause;}return result;
}

上述代码的含义是:如果ex.getCause()不为空,并且ex.getCause()不等于ex,就将ex.getCause()赋值给result,然后继续执行while循环。直到ex.getCause()为空或者ex.getCause()等于ex,就返回result。

getRoutingFunction

我们看到获得路由调用的是getRoutingFunction方法,而这个方法是一个抽象方法,需要在具体的继承类中实现。

logError

protected void logError(ServerRequest request, ServerResponse response, Throwable throwable) {if (logger.isDebugEnabled()) {logger.debug(request.exchange().getLogPrefix() + this.formatError(throwable, request));}if (HttpStatus.resolve(response.rawStatusCode()) != null && response.statusCode().equals(HttpStatus.INTERNAL_SERVER_ERROR)) {logger.error(LogMessage.of(() -> {return String.format("%s 500 Server Error for %s", request.exchange().getLogPrefix(), this.formatRequest(request));}), throwable);}}

上述代码的含义:

  1. 如果是debug级别的日志,就以debug的方式打印异常信息。
  2. 如果response的状态码是500,就打印异常信息。

用到的formatError方法就是简单的讲错误格式化,代码不再一一分析。

write

在把内容写入的时候用到了私有方法write,我们看看write方法的代码如下:

private Mono<? extends Void> write(ServerWebExchange exchange, ServerResponse response) {exchange.getResponse().getHeaders().setContentType(response.headers().getContentType());return response.writeTo(exchange, new AbstractErrorWebExceptionHandler.ResponseContext());}

上述代码的含义是:将response的contentType设置到exchange的response中,然后调用response的writeTo方法,将response写入到exchange中。
ResponseContext是一个内部类,主要是携带了外部类中的viewResolvers和messageWriters属性。

其他的方法

afterPropertiesSet

public void afterPropertiesSet() throws Exception {if (CollectionUtils.isEmpty(this.messageWriters)) {throw new IllegalArgumentException("Property 'messageWriters' is required");}
}

afterPropertiesSet主要是通过实现InitializingBean接口,实现afterPropertiesSet方法,判断messageWriters是否为空,如果为空就抛出异常。

renderDefaultErrorView

protected Mono<ServerResponse> renderDefaultErrorView(BodyBuilder responseBody, Map<String, Object> error) {StringBuilder builder = new StringBuilder();Date timestamp = (Date)error.get("timestamp");Object message = error.get("message");Object trace = error.get("trace");Object requestId = error.get("requestId");builder.append("<html><body><h1>Whitelabel Error Page</h1>").append("<p>This application has no configured error view, so you are seeing this as a fallback.</p>").append("<div id='created'>").append(timestamp).append("</div>").append("<div>[").append(requestId).append("] There was an unexpected error (type=").append(this.htmlEscape(error.get("error"))).append(", status=").append(this.htmlEscape(error.get("status"))).append(").</div>");if (message != null) {builder.append("<div>").append(this.htmlEscape(message)).append("</div>");}if (trace != null) {builder.append("<div style='white-space:pre-wrap;'>").append(this.htmlEscape(trace)).append("</div>");}builder.append("</body></html>");return responseBody.bodyValue(builder.toString());}

renderDefaultErrorView方法主要是渲染默认的错误页面,如果没有自定义的错误页面,就会使用这个方法渲染默认的错误页面。

renderErrorView

protected Mono<ServerResponse> renderErrorView(String viewName, BodyBuilder responseBody, Map<String, Object> error) {if (this.isTemplateAvailable(viewName)) {return responseBody.render(viewName, error);} else {Resource resource = this.resolveResource(viewName);return resource != null ? responseBody.body(BodyInserters.fromResource(resource)) : Mono.empty();}}

上述代码的含义是:

  1. 如果viewName对应的模板存在,就使用模板渲染错误页面。
  2. 否则就使用静态资源渲染错误页面。
    在使用资源渲染的时候,调用resolveResource方法,代码如下:
private Resource resolveResource(String viewName) {// 获得所有的静态资源的路径String[] var2 = this.resources.getStaticLocations();int var3 = var2.length;// 遍历所有的静态资源for(int var4 = 0; var4 < var3; ++var4) {String location = var2[var4];try {// 获得静态资源Resource resource = this.applicationContext.getResource(location);// 获得静态资源的文件resource = resource.createRelative(viewName + ".html");if (resource.exists()) {return resource;}} catch (Exception var7) {}}return null;}

DefaultErrorWebExceptionHandler

DefaultErrorWebExceptionHandler是ErrorWebExceptionHandler的默认实现,继承了AbstractErrorWebExceptionHandler。通过对AbstractErrorWebExceptionHandler的分析,我们知道了大致实现原理,下面我们来看看DefaultErrorWebExceptionHandler的具体实现。

getRoutingFunction

在AbstractErrorWebExceptionHandler中,getRoutingFunction方法是一个抽象方法,需要子类实现。DefaultErrorWebExceptionHandler的getRoutingFunction方法的实现如下:

protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {return RouterFunctions.route(this.acceptsTextHtml(), this::renderErrorView).andRoute(RequestPredicates.all(), this::renderErrorResponse);
}

我们看到代码中调用了RouterFunctions.route和andRoute方法。RouterFunctions.route的作用是根据请求的accept头信息,判断是否是text/html类型,如果是就调用renderErrorView方法,否则就调用renderErrorResponse方法。

acceptsTextHtml

protected RequestPredicate acceptsTextHtml() {return (serverRequest) -> {try {// 获得请求头中的accept信息List<MediaType> acceptedMediaTypes = serverRequest.headers().accept();// 定义一个MediaType.ALL的MediaType对象MediaType var10001 = MediaType.ALL;// 移除MediaType.ALLacceptedMediaTypes.removeIf(var10001::equalsTypeAndSubtype);// 根据类型和权重对MediaType进行排序MediaType.sortBySpecificityAndQuality(acceptedMediaTypes);// 获得acceptMediaTypes的stream对象Stream var10000 = acceptedMediaTypes.stream();// 获得MediaType.TEXT_HTML的MediaType对象var10001 = MediaType.TEXT_HTML;var10001.getClass();// 判断是否有MediaType.TEXT_HTMLreturn var10000.anyMatch(var10001::isCompatibleWith);} catch (InvalidMediaTypeException var2) {return false;}};}

acceptsTextHtml方法的作用是判断请求头中的accept信息是否是text/html类型。

renderErrorView

protected Mono<ServerResponse> renderErrorView(ServerRequest request) {// 通过getErrorAttributes方法获得错误信息Map<String, Object> error = this.getErrorAttributes(request, this.getErrorAttributeOptions(request, MediaType.TEXT_HTML));// 获得错误状态码int errorStatus = this.getHttpStatus(error);// 获得错误页面的模板名称BodyBuilder responseBody = ServerResponse.status(errorStatus).contentType(TEXT_HTML_UTF8);// 获得错误页面的模板名称return Flux.just(this.getData(errorStatus).toArray(new String[0])).flatMap((viewName) -> {return this.renderErrorView(viewName, responseBody, error);}).switchIfEmpty(this.errorProperties.getWhitelabel().isEnabled() ? this.renderDefaultErrorView(responseBody, error) : Mono.error(this.getError(request))).next();}

其中

  1. this.getData(errorStatus)通过错误的code获得错误页面的名称。
  2. this.renderErrorView(viewName, responseBody, error),通过错误页面的名称,错误信息,错误状态码,获得错误页面的响应体。
  3. this.renderDefaultErrorView(responseBody, error),通过错误信息,错误状态码,获得默认的错误页面的响应体。

renderErrorResponse方法

protected Mono<ServerResponse> renderErrorResponse(ServerRequest request) {// 通过getErrorAttributes方法获得错误信息Map<String, Object> error = this.getErrorAttributes(request, this.getErrorAttributeOptions(request, MediaType.ALL));// 获得错误状态码// 设置为json格式的响应体// 返回错误信息return ServerResponse.status(this.getHttpStatus(error)).contentType(MediaType.APPLICATION_JSON).body(BodyInserters.fromValue(error));}

通过上述代码看到主要执行了以下流程:

  1. 通过getErrorAttributes方法获得错误信息
  2. 获得错误状态码
  3. 设置为json格式的响应体
  4. 返回错误信息

getErrorAttributeOptions

protected ErrorAttributeOptions getErrorAttributeOptions(ServerRequest request, MediaType mediaType) {ErrorAttributeOptions options = ErrorAttributeOptions.defaults();if (this.errorProperties.isIncludeException()) {options = options.including(new Include[]{Include.EXCEPTION});}if (this.isIncludeStackTrace(request, mediaType)) {options = options.including(new Include[]{Include.STACK_TRACE});}if (this.isIncludeMessage(request, mediaType)) {options = options.including(new Include[]{Include.MESSAGE});}if (this.isIncludeBindingErrors(request, mediaType)) {options = options.including(new Include[]{Include.BINDING_ERRORS});}return options;}

我们看到上述代码执行了下面的流程:

  1. 获得ErrorAttributeOptions对象
  2. 判断是否包含异常信息
  3. 判断是否包含堆栈信息
  4. 判断是否包含错误信息
  5. 判断是否包含绑定错误信息
    如果包含这些信息,就将对应的信息加入到ErrorAttributeOptions对象中。而这些判断主要是通过ErrorProperties对象中的配置来判断的。

自定义自己的异常处理类

当认为默认的DefaultErrorWebExceptionHandler不满足需求时,我们可以自定义自己的异常处理类。

继承DefaultErrorWebExceptionHandler

只需要继承DefaultErrorWebExceptionHandler类,然后重写getErrorAttributes方法即可。

import org.springframework.context.annotation.Configuration;@Configuration
@Order(-1)
public class MyErrorWebExceptionHandler extends DefaultErrorWebExceptionHandler {public MyErrorWebExceptionHandler(ErrorAttributes errorAttributes, ResourceProperties resourceProperties, ErrorProperties errorProperties, ApplicationContext applicationContext) {super(errorAttributes, resourceProperties, errorProperties, applicationContext);}@Overrideprotected Map<String, Object> getErrorAttributes(ServerRequest request, ErrorAttributeOptions options) {Map<String, Object> errorAttributes = super.getErrorAttributes(request, options);errorAttributes.put("ext", "自定义异常处理类");return errorAttributes;}
}

继承AbstractErrorWebExceptionHandler

import org.springframework.context.annotation.Configuration;@Configuration
@Order(-1)
public class MyErrorWebExceptionHandler extends AbstractErrorWebExceptionHandler {public MyErrorWebExceptionHandler(ErrorAttributes errorAttributes, ResourceProperties resourceProperties, ErrorProperties errorProperties, ApplicationContext applicationContext) {super(errorAttributes, resourceProperties, errorProperties, applicationContext);}@Overrideprotected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);}@Overrideprotected Map<String, Object> getErrorAttributes(ServerRequest request, ErrorAttributeOptions options) {Map<String, Object> errorAttributes = super.getErrorAttributes(request, options);errorAttributes.put("ext", "自定义异常处理类");return errorAttributes;}
}

继承WebExceptionHandler

import org.springframework.context.annotation.Configuration;@Configuration
@Order(-1)
public class MyErrorWebExceptionHandler implements WebExceptionHandler {@Overridepublic Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {ServerHttpResponse response = exchange.getResponse();if (response.isCommitted()) {return Mono.error(ex);} else {response.getHeaders().setContentType(MediaType.APPLICATION_JSON);response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);return response.writeWith(Mono.just(response.bufferFactory().wrap("自定义异常处理类".getBytes())));}}
}

总结

总体工作流程

经过对AbstractErrorWebExceptionHandlerDefaultErrorWebExceptionHandler中代码的阅读和跟踪。我们不难看出其工作机制。考虑到有返回API方式的json错误信息和错误页面方式的错误信息。所以ExceptionHandler通过request中的accept的http头进行判断,如果是json格式则以json的方式进行响应,否则则以错误页面的方式进行响应。而错误页面的方式,通过错误的code获得错误页面的名称,然后通过错误页面的名称,错误信息,错误状态码,获得错误页面的响应体。

得到的经验

  1. 异步编程和同步编程之间差异很大,特别是在对request和response进行操作的时候。
  2. 源代码充分考虑各种情况和细节,在编程中值得我们去做参考。

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

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

相关文章

华为OD机试题,用 Java 解【入栈出栈】问题

最近更新的博客 华为OD机试题,用 Java 解【停车场车辆统计】问题华为OD机试题,用 Java 解【字符串变换最小字符串】问题华为OD机试题,用 Java 解【计算最大乘积】问题华为OD机试题,用 Java 解【DNA 序列】问题华为OD机试 - 组成最大数(Java) | 机试题算法思路 【2023】使…

软件分析笔记02---Intermediate Representation

整体contents compiler &#xff08;source code ——> machine code&#xff09; non-trivial非平凡的 经过 语义分析->语法分析->类型检查等各种trivial的分析&#xff08;前端&#xff09;&#xff0c;生成中间代码IR->进行non-trivial的分析&#xff08;及静…

47个SQL性能优化技巧,看到就是赚到

1、先了解MySQL的执行过程 了解了MySQL的执行过程&#xff0c;我们才知道如何进行sql优化。 &#xff08;1&#xff09;客户端发送一条查询语句到服务器&#xff1b; &#xff08;2&#xff09;服务器先查询缓存&#xff0c;如果命中缓存&#xff0c;则立即返回存储在缓存中的…

Python大数据培训班特色优势及工作方向

Python大数据培训班有多个大数据培训班类型&#xff0c;同时也包括训练营、学徒班、就业班等。 具体班型&#xff1a; 大数据挖掘与人工智能&#xff08;大数据分析&#xff09;学徒班、大数据应用开发学徒班 大数据挖掘与人工智能&#xff08;大数据分析&…

解决MySQL的 Row size too large (> 8126).

&#x1f4e2;欢迎点赞 &#xff1a;&#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff0c;赐人玫瑰&#xff0c;手留余香&#xff01;&#x1f4e2;本文作者&#xff1a;由webmote 原创&#x1f4e2;作者格言&#xff1a;无尽的折腾后&#xff0c;终于又回到…

第14天-ElasticSearch环境配置,构建检索服务及商品上架到ES库

1.ElasticSearch概念 官网介绍&#xff1a;https://www.elastic.co/cn/what-is/elasticsearch/ 官网学习文档&#xff1a;https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html 1.1.ElasticSearch与MySQL的比较 MySQL有事务性,而ElasticSearch没有…

论文笔记:A Time Series is Worth 64 Words: Long-term Forecasting with Transformers

ICLR 2023 比较简单&#xff0c;就不分intro、model这些了 1 核心思想1&#xff1a;patching 给定每个时间段的长度、划分的stride&#xff0c;将时间序列分成若干个时间段 时间段之间可以有重叠&#xff0c;也可以没有每一个时间段视为一个token 1.1 使用patching的好处 降…

糖化学试剂55520-67-7,5-vinyl-2-deoxyuridine,5-乙烯基-2-脱氧尿苷特点分析说明

5-vinyl-2-deoxyuridine(5-VdU)&#xff0c;5-vinyl-2-deoxyuridine&#xff0c;5-Vinyldeoxyuridine5-乙烯基-2-脱氧尿苷 | CAS&#xff1a;55520-67-7 | 纯度&#xff1a;95%试剂信息&#xff1a;CAS&#xff1a;55520-67-7所属类别&#xff1a;糖化学分子量&#xff1a;C11H…

MySQL索引类型(type)分析

type索引类型 system > const > eq_ref > ref > range > index > all 优化级别从左往右递减&#xff0c;没有索引的⼀般为’all’。推荐优化目标&#xff1a;至少要达到 range 级别&#xff0c; 要求是 ref 级别&#xff0c; 如果可以是 const 最好&#xff…

单通道说话人语音分离——DPRNN(Dual-Path Recurrent Neural Network)

参考文献&#xff1a;《DUAL-PATH RNN: EFFICIENT LONG SEQUENCE MODELING FOR TIME-DOMAIN SINGLE-CHANNEL SPEECH SEPARATION》 DPRNN网络是Con-Tasnet的改进网络 Con-Tasnet介绍详情请看上一篇文章 单通道说话人语音分离——Conv-TasNet(Convolutional Time-domain audio…

UWB到底是什么技术?

什么是空间感知能力 所谓的空间感知能力&#xff0c;就是感知方位的能力。更直接一点&#xff0c;就是定位能力。说白了&#xff0c;利用UWB技术&#xff0c;手机和智能设备可以更精准地实现室内定位&#xff0c;不仅可以感知自己的位置&#xff0c;还可以感知周边其它手机或设…

多任务学习概述

文章目录前言1 文章信息2 背景、目的、结论2.1 背景2.1.1 多任务的类型分类2.1.1.1 相关任务的分类2.1.1.2 将输入变输出的逆多任务学习2.1.1.3 对抗性多任务学习2.1.1.4 辅助任务提供注意力特征的多任务学习2.1.1.5 附加预测性辅助任务的多任务学习3 内容与讨论3.1 多任务学习…

大数据技术之Hadoop

第1章 Hadoop概述1.1 Hadoop是什么1.2 Hadoop发展历史&#xff08;了解&#xff09;1.3 Hadoop三大发行版本&#xff08;了解&#xff09;Hadoop三大发行版本&#xff1a;Apache、Cloudera、Hortonworks。Apache版本最原始&#xff08;最基础&#xff09;的版本&#xff0c;对于…

【unity】开发rts 3 出生点,创建建筑物

一 出生点、阵营类型、阵营 实例栏-GameManage&#xff0c;默认有一个插槽 size 插槽数量 role 权限&#xff0c;host是主人&#xff0c;权限高 type 阵营类型&#xff0c;不选不限制&#xff0c;选的效果没看懂&#xff0c;文档原文&#xff1a; The Type field in Data al…

Cookie、Session、JWT 那些事

文章目录前言一、概念1、Cookie&#xff1a;2、Session&#xff1a;3、JWT二、应用1. 基本使用2. 实现 “退出” 功能总结前言 目前 C/S 模式盛行&#xff0c;HTTP 是其中最常见的通信协议&#xff0c;我们知道 HTTP 协议是无状态的&#xff0c;但是这场景完全不够用。 比如&…

Python|每日一练|算法初阶|字符串|树|深度优先搜索|单选记录:循环随机取数组直到得出指定数字|有效数字|平衡二叉树

1、循环随机取数组直到得出指定数字&#xff1f;&#xff08;算法初阶&#xff09; 贡献者&#xff1a;weixin_30937093 举个例子&#xff1a; 随机数字范围&#xff1a;0~100 每组数字量&#xff1a;6&#xff08;s1,s2,s3,s4,s5,s6&#xff09; 第二轮开始随机数字范围&…

Linux 基础介绍-基础命令

文章目录01 学习目标02 Linux/Unix 操作系统简介2.1 Linux 操作系统的目标2.2 Linux 操作系统的作用2.3 Unix 家族历史2.4 Linux 家族历史2.5 Linux 和Unix 的联系2.6 Linux 内核介绍2.7 Linux 发行版本2.8 Unix/Linux 开发应用领域介绍03 Linux 目录结构3.1 Win 和Linux 文件系…

Mac iTerm2 rz sz

1、安装brew&#xff08;找了很多&#x1f517;&#xff0c;就这个博主的好用&#xff09; Mac如何安装brew&#xff1f;_行走的码农00的博客-CSDN博客_mac brew 2、安装lrzsz brew install lrzsz 检查是否安装成功 brew list 定位lrzsz的安装目录 brew list lrzsz 执…

git学习记录/菜鸟教程(基于Gitcode)

首先说明下为何使用Gitcode而不是hub或lab&#xff1a;只是因为国外的网站访问太慢了&#xff0c;而且还要翻译从初次使用开始说&#xff1a;首先安装Git&#xff0c;一路next就可以&#xff0c;安装好后打开&#xff0c;输入git version如果有显示版本号&#xff0c;说明安装成…

2020蓝桥杯真题跑步锻炼(填空题) C语言/C++

题目描述 本题为填空题&#xff0c;只需要算出结果后&#xff0c;在代码中使用输出语句将所填结果输出即可。 小蓝每天都锻炼身体。 正常情况下&#xff0c;小蓝每天跑 1 千米。如果某天是周一或者月初&#xff08;1 日&#xff09;&#xff0c;为了激励自己&#xff0c;小蓝…