Gateway全局异常处理及请求响应监控

前言

我们在上一篇文章基于压测进行Feign调优完成的服务间调用的性能调优,此时我们也关注到一个问题,如果我们统一从网关调用服务,但是网关因为某些原因报错或者没有找到服务怎么办呢?

如下所示,笔者通过网关调用account服务,但是account服务还没起来。此时请求还没有到达account就报错了,这就意味着我们服务中编写的@RestControllerAdvice对网关没有任何作用。

curl 127.0.0.1:8090/account/getByCode/zsy

响应结果如下,可以看到响应结果如下所示,要知道现如今的开发模式为前后端分离模式,前后端交互完全是基于协商好的格式,如果网关响应格式与我们规定的格式完全不一致,前端就需要特殊处理,这使得代码不仅会变得丑陋,对于后续的功能扩展的交互复杂度也会增加,而gateway默认响应错误如下:

{
    "timestamp":"2023-02-09T15:22:20.278+0000",
    "path":"/account/getByCode/zsy",
    "status":500,
    "error":"Internal Server Error",
    "message":"Connection refused: no further information: /192.168.43.73:9000"
}

在这里插入图片描述

网关异常默认处理

所以我们必须了解一下是什么原因导致网关报错会响应这个值。

我们在gateway源码中找到ErrorWebFluxAutoConfiguration这个自动装配类,可以看到下面这段代码,我们从中得知网关报错时默认使用DefaultErrorWebExceptionHandler 来返回结果,所以我们不妨看看这个类做了那些事情。

@Bean
	@ConditionalOnMissingBean(value = ErrorWebExceptionHandler.class, search = SearchStrategy.CURRENT)
	@Order(-1)
	public ErrorWebExceptionHandler errorWebExceptionHandler(ErrorAttributes errorAttributes) {
	//网关默认异常处理的handler
		DefaultErrorWebExceptionHandler exceptionHandler = new DefaultErrorWebExceptionHandler(errorAttributes,
				this.resourceProperties, this.serverProperties.getError(), this.applicationContext);
		exceptionHandler.setViewResolvers(this.viewResolvers);
		exceptionHandler.setMessageWriters(this.serverCodecConfigurer.getWriters());
		exceptionHandler.setMessageReaders(this.serverCodecConfigurer.getReaders());
		return exceptionHandler;
	}

我们不妨基于debug了解一下这个类,当我们服务没有注册到nacos,并通过网关调用报错时,代码就会走到下方,route 方法第一个参数是RequestPredicate谓词,而后者则是谓词的处理,进行renderErrorViewandRoute同理将报错的请求通过renderErrorResponse返回错误结果

@Override
//route 方法第一个参数是RequestPredicate谓词,而后者则是谓词的处理,进行renderErrorView,然后通过然后通过andRoute将报错的请求通过renderErrorResponse返回错误结果
	protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
		return route(acceptsTextHtml(), this::renderErrorView).andRoute(all(), this::renderErrorResponse);
	}

我们不妨看看renderErrorResponse,可以看到一行getErrorAttributes,一旦步入我们就可以看到上文请求错误的结果格式

protected Mono<ServerResponse> renderErrorResponse(ServerRequest request) {
		boolean includeStackTrace = isIncludeStackTrace(request, MediaType.ALL);
		Map<String, Object> error = getErrorAttributes(request, includeStackTrace);
		return ServerResponse.status(getHttpStatus(error)).contentType(MediaType.APPLICATION_JSON_UTF8)
				.body(BodyInserters.fromObject(error));
	}

getErrorAttributes源码,可以看到组装的key值就是我们调试时响应的参数:

@Override
	public Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) {
		Map<String, Object> errorAttributes = new LinkedHashMap<>();
		errorAttributes.put("timestamp", new Date());
		errorAttributes.put("path", request.path());
		Throwable error = getError(request);
		HttpStatus errorStatus = determineHttpStatus(error);
		errorAttributes.put("status", errorStatus.value());
		errorAttributes.put("error", errorStatus.getReasonPhrase());
		errorAttributes.put("message", determineMessage(error));
		handleException(errorAttributes, determineException(error), includeStackTrace);
		return errorAttributes;
	}

自定义异常处理

了解的默认错误处理,我们就可以改造,返回一个和普通服务一样的格式给前端告知网关报错。从上文我们可知网关默认错误处理时DefaultErrorWebExceptionHandler,通过类图我们可以发现它继承了一个ErrorWebExceptionHandler,所以我们也可以继承这个类重写一个Handler

以笔者的代码如下,可以看到笔者使用Order注解强制获得最高异常处理优先级,然后使用bufferFactory.wrap方法传递自定义错误格式返回给前端。



@Slf4j
@Order(-1)
@Configuration
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class GlobalErrorWebExceptionHandler implements ErrorWebExceptionHandler {

    private final ObjectMapper objectMapper;

    @Override
    public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
        ServerHttpResponse response = exchange.getResponse();
        if (response.isCommitted()) {
            return Mono.error(ex);
        }

        // 设置返回值类型为json
        response.getHeaders().setContentType(MediaType.APPLICATION_JSON);

        //设置返回编码
        if (ex instanceof ResponseStatusException) {
            response.setStatusCode(((ResponseStatusException) ex).getStatus());
        }

        return response.writeWith(Mono.fromSupplier(() -> {
            DataBufferFactory bufferFactory = response.bufferFactory();
            try {
                //writeValueAsBytes 组装错误响应结果
                return bufferFactory.wrap(objectMapper.writeValueAsBytes(ResultData.fail(500, "网关捕获到异常:" + ex.getMessage())));
            } catch (JsonProcessingException e) {
                log.error("Error writing response", ex);
                return bufferFactory.wrap(new byte[0]);
            }
        }));
    }
}

最终返回的结果如下所示,可以看到结果和一般的服务调用报错格式一模一样,这样一来前端就无需为了网关报错加一个特殊处理的逻辑了

curl 127.0.0.1:8090/account/getByCode/zsy

输出结果

{
    "status":500,
    "message":"网关捕获到异常:503 SERVICE_UNAVAILABLE \"Unable to find instance for account-service\"",
    "data":null,
    "success":false,
    "timestamp":1675959617386
}

请求响应日志监控

对于微服务架构来说,监控是很重要的,在高并发场景情况下,很多问题我们都可以在网关请求响应中定位到,所以我们希望能有这么一种方式将用户日常请求响应的日志信息记录下来,便于日常运维和性能监控。

查阅了网上的资料发现,基于MongoDB进行网关请求响应数据采集是一种不错的方案,所以笔者本篇文章整理一下笔者如何基于网关过滤器结合MongoDB完成请求日志采集。

本篇文章可能会涉及MongoDB相关的知识,不了解的读者可以参考笔者的这篇文章:

MongoDB快速入门

gateway整合MongoDB采集日志步骤

  1. 添加MongoDB依赖并完成MongoDB配置:

首先在gateway中添加MongoDB依赖,需要注意的是,笔者后续的过滤器某些代码段会用到hutool的工具类,所以这里也添加了hutool的依赖。

 <!--mongodb依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>



        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
        </dependency>

然后我们在gateway的配置中添加MongoDB的连接参数配置:

# mongodb的ip地址
spring.data.mongodb.host=ip
# mongodb端口号
spring.data.mongodb.port=27017
# mongodb数据库名称
spring.data.mongodb.database=accesslog
# 用户名
spring.data.mongodb.username=xxxx
# 密码
spring.data.mongodb.password=xxx
  1. 编写MongoDB保存逻辑:

我们希望保存网关响应的内容到mongodb中,所以我们要把我们需要的内容封装成一个对象,如下GatewayLog

@Data
public class GatewayLog {
    /**
     * 请求相对路径
     */
    private String requestPath;
    /**
     *请求方法 :get post
     */
    private String requestMethod;
    /**
     *请求协议:http rpc
     */
    private String schema;
    /**
     *请求体内容
     */
    private String requestBody;
    /**
     *响应内容
     */
    private String responseBody;
    /**
     *ip地址
     */
    private String ip;
    /**
     * 请求时间
     */
    private String requestTime;
    /**
     *响应时间
     */
    private String responseTime;
    /**
     *执行时间 单位:毫秒
     */
    private Long executeTime;

}

完成对象定义后,我们就可以编写service层接口和实现类的逻辑了:

public interface AccessLogService {

    /**
     * 保存AccessLog
     * @param gatewayLog 请求响应日志
     * @return 响应日志
     */
    GatewayLog saveAccessLog(GatewayLog gatewayLog);

}

实现类代码如下,可以看到笔者完全基于mongoTemplatesave方法将日志数据存到gatewayLog表中。

@Service
public class AccessLogServiceImpl implements AccessLogService {

    @Autowired
    private MongoTemplate mongoTemplate;

	//collection名称
    private final String collectionName="gatewayLog" ;

    @Override
    public GatewayLog saveAccessLog(GatewayLog gatewayLog) {
        GatewayLog result = mongoTemplate.save(gatewayLog, collectionName);
        return result;
    }
}
  1. 基于gateway过滤器完成请求相应日志采集,代码比较长,首先是CachedBodyOutputMessage,由于笔者用的是Spring boot 2.x版本,没有CachedBodyOutputMessage 这个类,所以笔者从网上找了一份。读者可以根据注释进行复制修改即可。
public class CachedBodyOutputMessage implements ReactiveHttpOutputMessage {
    private final DataBufferFactory bufferFactory;
    private final HttpHeaders httpHeaders;
    private Flux<DataBuffer> body = Flux.error(new IllegalStateException("The body is not set. Did handling complete with success? Is a custom \"writeHandler\" configured?"));
    private Function<Flux<DataBuffer>, Mono<Void>> writeHandler = this.initDefaultWriteHandler();

    public CachedBodyOutputMessage(ServerWebExchange exchange, HttpHeaders httpHeaders) {
        this.bufferFactory = exchange.getResponse().bufferFactory();
        this.httpHeaders = httpHeaders;
    }

    public void beforeCommit(Supplier<? extends Mono<Void>> action) {
    }

    public boolean isCommitted() {
        return false;
    }

    public HttpHeaders getHeaders() {
        return this.httpHeaders;
    }

    private Function<Flux<DataBuffer>, Mono<Void>> initDefaultWriteHandler() {
        return (body) -> {
            this.body = body.cache();
            return this.body.then();
        };
    }

    public DataBufferFactory bufferFactory() {
        return this.bufferFactory;
    }

    public Flux<DataBuffer> getBody() {
        return this.body;
    }

    public void setWriteHandler(Function<Flux<DataBuffer>, Mono<Void>> writeHandler) {
        Assert.notNull(writeHandler, "'writeHandler' is required");
        this.writeHandler = writeHandler;
    }

    public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
        return Mono.defer(() -> {
            return (Mono)this.writeHandler.apply(Flux.from(body));
        });
    }

    public Mono<Void> writeAndFlushWith(Publisher<? extends Publisher<? extends DataBuffer>> body) {
        return this.writeWith(Flux.from(body).flatMap((p) -> {
            return p;
        }));
    }

    public Mono<Void> setComplete() {
        return this.writeWith(Flux.empty());
    }
}

过滤器代码如下,笔者将核心内容都已注释了,读者可以基于此代码进行修改


@Slf4j
@Component
public class AccessLogGlobalFilter implements GlobalFilter, Ordered {
    private final List<HttpMessageReader<?>> messageReaders = HandlerStrategies.withDefaults().messageReaders();

    //todo 存在线程安全问题,后续需要优化掉
    SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
    @Autowired
    private AccessLogService accessLogService;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        GatewayLog gatewayLog = new GatewayLog();

        ServerHttpRequest request = exchange.getRequest();
        //获取请求的ip,url,method,body
        String requestPath = request.getPath().pathWithinApplication().value();
        String clientIp = request.getRemoteAddress().getHostString();
        String scheme = request.getURI().getScheme();
        String method = request.getMethodValue();
        
        //数据记录到gatwayLog中
        gatewayLog.setSchema(scheme);
        gatewayLog.setRequestMethod(method);
        gatewayLog.setRequestPath(requestPath);
        gatewayLog.setRequestTime(simpleDateFormat.format(new Date().getTime()));
        gatewayLog.setIp(clientIp);

        MediaType contentType = request.getHeaders().getContentType();
        if (MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(contentType) || MediaType.APPLICATION_JSON.isCompatibleWith(contentType)) {
            return writeBodyLog(exchange, chain, gatewayLog);
        } else {
            //写入日志信息到mongoDb
            return writeBasicLog(exchange, chain, gatewayLog);
        }
    }

    private Mono<Void> writeBasicLog(ServerWebExchange exchange, GatewayFilterChain chain, GatewayLog accessLog) {
        StringBuilder builder = new StringBuilder();
        MultiValueMap<String, String> queryParams = exchange.getRequest().getQueryParams();
        for (Map.Entry<String, List<String>> entry : queryParams.entrySet()) {
            builder.append(entry.getKey()).append("=").append(StringUtils.join(entry.getValue(), ","));
        }
        //记录响应内容
        accessLog.setRequestBody(builder.toString());

        //   获取响应体
        ServerHttpResponseDecorator decoratedResponse = recordResponseLog(exchange, accessLog);
        return chain.filter(exchange.mutate().response(decoratedResponse).build())
                .then(Mono.fromRunnable(() -> {
                    //打印日志
                    writeAccessLog(accessLog);
                }));
    }


    /**
     * 解决request body 只能读取一次问题
     *
     * @param exchange
     * @param chain
     * @param gatewayLog
     * @return
     */
    private Mono writeBodyLog(ServerWebExchange exchange, GatewayFilterChain chain, GatewayLog gatewayLog) {
        ServerRequest serverRequest = ServerRequest.create(exchange, messageReaders);
        Mono<String> modifiedBody = serverRequest.bodyToMono(String.class)
                .flatMap(body -> {
                    gatewayLog.setRequestBody(body);
                    return Mono.just(body);
                });

        // 通过 BodyInsert 插入 body(支持修改body), 避免 request body 只能获取一次
        BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, String.class);
        HttpHeaders headers = new HttpHeaders();
        headers.putAll(exchange.getRequest().getHeaders());

        headers.remove(HttpHeaders.CONTENT_LENGTH);
        CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, headers);

        return bodyInserter.insert(outputMessage, new BodyInserterContext())
                .then(Mono.defer(() -> {
                    // 重新封装请求
                    ServerHttpRequest decoratedRequest = requestDecorate(exchange, headers, outputMessage);

                    // 记录响应日志
                    ServerHttpResponseDecorator decoratedResponse = recordResponseLog(exchange, gatewayLog);

                    // 记录普通的
                    return chain.filter(exchange.mutate().request(decoratedRequest).response(decoratedResponse).build())
                            .then(Mono.fromRunnable(() -> {
                                // 打印日志
                                writeAccessLog(gatewayLog);
                            }));
                }));
    }


    /**
     * 打印日志并将日志内容写入mongodb
     *
     * @param gatewayLog
     */
    private void writeAccessLog(GatewayLog gatewayLog) {
        log.info("写入网关日志,日志内容:" + JSON.toJSONString(gatewayLog));
        accessLogService.saveAccessLog(gatewayLog);
    }

    /**
     * 请求装饰器,重新计算 headers
     *
     * @param exchange
     * @param headers
     * @param outputMessage
     * @return
     */
    private ServerHttpRequestDecorator requestDecorate(ServerWebExchange exchange, HttpHeaders headers,
                                                       CachedBodyOutputMessage outputMessage) {
        return new ServerHttpRequestDecorator(exchange.getRequest()) {
            @Override
            public HttpHeaders getHeaders() {
                long contentLength = headers.getContentLength();
                HttpHeaders httpHeaders = new HttpHeaders();
                httpHeaders.putAll(super.getHeaders());
                if (contentLength > 0) {
                    httpHeaders.setContentLength(contentLength);
                } else {
                    httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
                }
                return httpHeaders;
            }

            @Override
            public Flux<DataBuffer> getBody() {
                return outputMessage.getBody();
            }
        };
    }

    /**
     * 记录响应日志
     *
     * @param exchange
     * @param gatewayLog
     * @return
     */
    private ServerHttpResponseDecorator recordResponseLog(ServerWebExchange exchange, GatewayLog gatewayLog) {
        ServerHttpResponse response = exchange.getResponse();
        DataBufferFactory bufferFactory = response.bufferFactory();

        return new ServerHttpResponseDecorator(response) {
            @SneakyThrows
            @Override
            public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
                if (body instanceof Flux) {
                    String responseTime = simpleDateFormat.format(new Date().getTime());
                    gatewayLog.setResponseTime(responseTime);
                    // 计算执行时间
                    long executeTime = (simpleDateFormat.parse(responseTime).getTime() - simpleDateFormat.parse(gatewayLog.getRequestTime()).getTime());
                    gatewayLog.setExecuteTime(executeTime);

                    // 获取响应类型,如果是 json 就打印
                    String originalResponseContentType = exchange.getAttribute(ServerWebExchangeUtils.ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR);

                    if (ObjectUtils.equals(this.getStatusCode(), HttpStatus.OK)
                            && StringUtils.isNotBlank(originalResponseContentType)
                            && originalResponseContentType.contains("application/json")) {

                        Flux<? extends DataBuffer> fluxBody = Flux.from(body);
                        return super.writeWith(fluxBody.buffer().map(dataBuffers -> {

                            // 合并多个流集合,解决返回体分段传输
                            DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
                            DataBuffer join = dataBufferFactory.join(dataBuffers);
                            byte[] content = new byte[join.readableByteCount()];
                            join.read(content);

                            // 释放掉内存
                            DataBufferUtils.release(join);
                            String responseResult = new String(content, StandardCharsets.UTF_8);
                            gatewayLog.setResponseBody(responseResult);

                            return bufferFactory.wrap(content);
                        }));
                    }
                }
                return super.writeWith(body);
            }
        };
    }

    /**
     * 调小优先级使得该过滤器最先执行
     * @return
     */
    @Override
    public int getOrder() {
        return -100;
    }
}

  1. 测试

以笔者项目为例,通过网关调用order服务:

curl 127.0.0.1:8090/order/getByCode/zsy

可以看到响应成功了,接下来我们就确认一下mongoDb中是否有存储网关请求响应信息


{"status":100,"message":"操作成功","data":{"id":1,"accountCode":"zsy","accountName":"zsy","amount":10000.00},"success":true,"timestamp":1676439102837}

通过数据库连接工具查询,可以看到网关请求响应日志也成功存储到MongoDB中。

在这里插入图片描述

参考文献

SpringCloud Alibaba微服务实战二十四 - SpringCloud Gateway的全局异常处理

软件开发设计中的上游与下游

SpringCloud Alibaba实战二十九 | SpringCloud Gateway 请求响应日志

MongoDB 数据查询操作

实战 | MongoDB的安装配置

spring cloud gateway中实现请求、响应参数日志打印

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

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

相关文章

dToF直方图之美_激光雷达多目标检测

直方图提供了一种简单有效的方法来分析信号分布并识别与目标存在相对应的峰值,并且能够可视化大量数据,让测距数形结合。在车载激光雷达中,对于多目标检测,多峰算法统计等,有着区别于摄像头以及其他雷达方案的天然优势。 如下图,当中有着清晰可见的三个峰值,我们可以非…

基于ssm的电动车租赁网站论文

摘 要 现代经济快节奏发展以及不断完善升级的信息化技术&#xff0c;让传统数据信息的管理升级为软件存储&#xff0c;归纳&#xff0c;集中处理数据信息的管理方式。本电动车租赁网站就是在这样的大环境下诞生&#xff0c;其可以帮助管理者在短时间内处理完毕庞大的数据信息&…

Draw.io or diagrams.net 使用方法

0 Preface/Foreword 在工作中&#xff0c;经常需要用到框图&#xff0c;流程图&#xff0c;时序图&#xff0c;等等&#xff0c;draw.io可以完成以上工作。 official website:draw.io 1 Usage 1.1 VS code插件 draw.io可以扩展到VS code工具中。

Java Web——过滤器 监听器

目录 1. Filter & 过滤器 1.1. 过滤器概述 1.2. 过滤器的使用 1.3. 过滤器生命周期 1.4. 过滤器链的使用 1.5. 注解方式配置过滤器 2. Listener & 监听器 2.1. 监听器概述 2.2. Java Web的监听器 2.2.1. 常用监听器 2.2.1.1. ServletContextListener监听器 …

SM4加密算法的侧信道攻击实现

SM4 算法有多个位置存在泄漏点&#xff0c;如下图所示&#xff1a; 在位置1和2&#xff0c;可以逐个字节攻击密钥&#xff0c;因为密钥和中间结果之间没有扩散&#xff0c;这时通常取Sbox的输出作为攻击点&#xff0c;因为在位置2处的功耗是大于位置1的&#xff0c;但是在FPGA…

ODOO领先其他ERP的王炸功能:作业路线!(含MTO模式配置图表)

和众多ERP系统比较&#xff0c;ODOO-ERP中的作业路线功能可谓相当强大&#xff0c;可以自行定义供应链路线&#xff0c;以及单据同步生成。极大地增强了不同业务场景的适应性和业务管理效率&#xff01; 自定义供应路线的特点&#xff1a;对于很多灵活多变的企业而言&#xff…

Java架构师系统架构实现高内聚低耦合

目录 1 导语2 边界内聚耦合概述3 聚焦内聚4 关注耦合5 如何实现高内聚低耦合6 内聚耦合规划不当的效果7 总结想学习架构师构建流程请跳转:Java架构师系统架构设计 1 导语 架构设计的核心维度,从系统的扩展性、高性能、高可用、高安全性和伸缩性五个维度进行了探讨,并介绍了…

JavaEE:计算机是如何工作的

JavaEE学什么&#xff1f; 主要学习Java开发网站后端&#xff0c;为后面学习Spring做铺垫 涉及的内容&#xff1a; 1&#xff09;操作系统基础知识 2&#xff09;多线程知识 3&#xff09;文件操作 4&#xff09;网络编程 5&#xff09;网络原理 6&#xff09;JVM 计算…

用到了C语言的函数指针功能。

请选择一个功能&#xff1a; 1. 加法 2. 减法 3. 乘法 4. 除法 5. 取模 6. 阶乘 7. 判断素数 8. 球体体积 9. 斐波那契数列 10. 幂运算 11. 最大公约数 12. 最小公倍数 13. 交换数字 14. 排序 15. 退出 请选择一个选项&#xff1a; #include <stdio.h> #include <stdl…

強強联手!M88明陞宣布与G2 电子竞技俱乐部成为官方合作伙伴!

M88明陞作为亚洲领先的在线游戏平台&#xff0c;正式宣布与G2电子竞技俱乐部建立具有突破性意义的官方合作伙伴关系&#xff0c;G2电子竞技俱乐部是全球领先的电子竞技品牌之一。作为官方合作伙伴关系&#xff0c;双方将合作开展一系列活动。 M88明陞将在G2 电子竞技俱乐部追求…

工单质检上线提升企业IT服务质量管理,智能服务能力再添新翼!甄知燕千云全新版本V1.26.0发布!

燕千云数智化业务服务平台在11月24日发布了V1.26.0版本&#xff0c;本次主要新增了工单质检、SLA绩效管理和上下游管理能力升级、以及自动预测工单流转趋势的功能&#xff0c;以支持更多的IT服务场景。同时&#xff0c;呼叫中心新增了智能分配客服、工单关联会话记录、客服消息…

RT-Thread学习笔记(六):RT_Thread系统死机日志定位

RT_Thread系统死机日志定位 一、RT_Thread系统死机日志定位二、Cortex-M3 / M4架构知识2.1 Cortex-M3 / M4架构概述2.2 寄存器用途 三、排查步骤 一、RT_Thread系统死机日志定位 RT-Thread 系统发生hardfault死机时&#xff0c;系统默认会打印出一系列寄存器状态帮助用户定位死…

NOIP2016提高组第二轮day2 - T3:愤怒的小鸟

题目链接 [NOIP2016 提高组] 愤怒的小鸟 题目描述 Kiana 最近沉迷于一款神奇的游戏无法自拔。简单来说&#xff0c;这款游戏是在一个平面上进行的。 有一架弹弓位于 ( 0 , 0 ) (0,0) (0,0) 处&#xff0c;每次 Kiana 可以用它向第一象限发射一只红色的小鸟&#xff0c;小鸟…

【T3】安装畅捷通T3软件,错误代码0x800A0146,描述:未找到标识符为‘138’的资源。

【问题描述】 安装畅捷通T3软件过程中&#xff0c;提示&#xff1a; 错误代码0x800A0146 描述&#xff1a;未找到标识符为‘138’的资源&#xff0c; 安装程序将立即终止。 【解决方法】 该错误是因为安装过程中检测到数据库版本过高。 首先T3普及版/标准版11.2版本支持数据库…

48.0/图片和多媒体文件的使用(详细版)

目录 48.1 网页中插入图片 48.1.1 基本语法 48.1.2 常见属性 48.2 图片超链接 48.3 设置图片热区链接 48.4 将图片作为网页背景 48.5 滚动字幕 48.6 插入多媒体文件 48.1 网页中插入图片 48.1.1 基本语法 <img src=“图片地址”> img 标记用于将图像插入到 HTML…

深度解析HarmonyOS开发-活动召集令元服务【鸿蒙北向应用开发实战】

目录 一&#xff0e;元服务和ArkTS语言简介1.1 学习元服务1.2 元服务带来的变革1.3 元服务全场景流量入口1.4 ArkTS学习1.5 ArkTS特点 二&#xff0e;DevEco Studio开发工具2.1 DevEco Studio学习2.2 DevEco Studio的主要特性2.3 端云一体化开发2.3.1端云一体化开发特点 2.4 低…

Zidebactam sodium salt β-内酰胺酶抑制剂 1706777-46-9科研

Zidebactam sodium salt β-内酰胺酶抑制剂 1706777-46-9 &#xff08;源自星戈瑞&#xff09; ATH686 FLT3 抑制剂 853299-52-2 Pelitrexol 抑制剂 446022-33-9 TBT1 转运蛋白抑制剂 52535-76-9 HFY-4A HDAC 抑制剂 2094810-82-7 SDR-04 BET 抑制剂 879593-54-1 Phthala…

年会抽奖【编程题】

&#x1f4d1;打牌 &#xff1a; da pai ge的个人主页 &#x1f324;️个人专栏 &#xff1a; da pai ge的博客专栏 ☁️宝剑锋从磨砺出&#xff0c;梅花香自苦寒来 目录 ☁️题目解析 ☁️解题思路…

JAVA 阻塞队列原理

JAVA 阻塞队列原理 阻塞队列&#xff0c;关键字是阻塞&#xff0c;先理解阻塞的含义&#xff0c;在阻塞队列中&#xff0c;线程阻塞有这样的两种情况&#xff1a; 当队列中没有数据的情况下&#xff0c;消费者端的所有线程都会被自动阻塞&#xff08;挂起&#xff09;&#x…

浅析不同NAND架构的差异与影响

SSD的存储介质是什么&#xff0c;它就是NAND闪存。那你知道NAND闪存是怎么工作的吗&#xff1f;其实&#xff0c;它就是由很多个晶体管组成的。这些晶体管里面存储着电荷&#xff0c;代表着我们的二进制数据&#xff0c;要么是“0”&#xff0c;要么是“1”。NAND闪存原理上是一…
最新文章