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) {//網關默認異常處理的handlerDefaultErrorWebExceptionHandler 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值就是我們調試時響應的參數:

@Overridepublic 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;@Overridepublic Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {ServerHttpResponse response = exchange.getResponse();if (response.isCommitted()) {return Mono.error(ex);}// 設置返回值類型為jsonresponse.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 {@Autowiredprivate MongoTemplate mongoTemplate;//collection名稱private final String collectionName="gatewayLog" ;@Overridepublic 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");@Autowiredprivate AccessLogService accessLogService;@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {GatewayLog gatewayLog = new GatewayLog();ServerHttpRequest request = exchange.getRequest();//獲取請求的ip,url,method,bodyString 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 {//寫入日志信息到mongoDbreturn 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()) {@Overridepublic 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;}@Overridepublic 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@Overridepublic 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*/@Overridepublic 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.pswp.cn/news/213433.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/213433.shtml
英文地址,請注明出處:http://en.pswp.cn/news/213433.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

中小企業管理者如何培育團隊精神?

某石油工程有限公司總經理曾提問&#xff1a;“作為中小企業的管理者如何才能更好的激發團隊精神呢&#xff1f;” 每個企業都向往和號召團隊精神&#xff0c;但是往往事與愿違。在各種羨慕嫉妒恨的情緒影響下&#xff0c;難免會產生一些落差&#xff0c;影響到團隊精神。 所…

超聲波清洗機會損傷物品嗎?一文明白超聲波清洗機有哪些優點

正確使用超聲波清洗機且買對超聲波清洗機是不會對清洗物品造成傷害的&#xff01; 一、超聲波清洗機工作原理是如何的&#xff1f; 超聲波清洗機的工作原理是利用超聲波產生的空化振動來清潔物體。當超聲波在清洗液中傳播時&#xff0c;它會產生微小的氣泡和振動&#xff0c;這…

論jenkins的使用方法(初步)

&#x1f4d1;打牌 &#xff1a; da pai ge的個人主頁 &#x1f324;?個人專欄 &#xff1a; da pai ge的博客專欄 ??寶劍鋒從磨礪出&#xff0c;梅花香自苦寒來 目錄 &#x1f4d1;什么是持續集成&…

1-1、Java概述

語雀原文鏈接 文章目錄 1、Java發展2、Java體系結構3、Java特點 1、Java發展 1990年&#xff0c;Sun公司(Stanford University Network,斯坦福大學網絡公司)詹姆斯高斯林推出的一門語言最開始注冊的名字oak語言(橡樹)&#xff0c;重名了被迫改成Java2009年Sun公司被甲骨文Ora…

Docker 容器中使用 Docker - DinD 和 DooD

突然間研究這個來的緣由是正在從 Jenkins 往 Harness 的過度, 而完全用命令來構建 Docker 鏡像變得不一樣了。在 Jenkins 中 Agent 本身也是一個 Docker Daemon, 所以 Docker 命令執行無障礙&#xff0c;而 Harness 的所謂的 Agent 就是一個個的運行在 Kubernetes 中的 Docker …

error:gmapping

– Could not find the required component ‘gmapping’. The following CMake error indicates that you either need to install the package with the same name or change your environment so that it can be found. CMake Error at /opt/ros/kinetic/share/catkin/cmake…

logstash插件簡單介紹

logstash插件 輸入插件(input) Input&#xff1a;輸入插件。 Input plugins | Logstash Reference [8.11] | Elastic 所有輸入插件都支持的配置選項 SettingInput typeRequiredDefaultDescriptionadd_fieldhashNo{}添加一個字段到一個事件codeccodecNoplain用于輸入數據的…

【SpringBoot教程】SpringBoot Thymeleaf 基于HTML5的現代模板引擎

作者簡介&#xff1a;大家好&#xff0c;我是擼代碼的羊駝&#xff0c;前阿里巴巴架構師&#xff0c;現某互聯網公司CTO 聯系v&#xff1a;sulny_ann&#xff08;17362204968&#xff09;&#xff0c;加我進群&#xff0c;大家一起學習&#xff0c;一起進步&#xff0c;一起對抗…

error:move_base_msgs

CMake Warning at /opt/ros/kinetic/share/catkin/cmake/catkinConfig.cmake:76 (find_package): Could not find a package configuration file provided by “move_base_msgs” with any of the following names: move_base_msgsConfig.cmake move_base_msgs-config.cmake …

鼠標光標不見了怎么辦?速速get這4個方法!

“非常奇怪&#xff0c;我的鼠標光標用著用著就不見了&#xff0c;這是為什么呢&#xff1f;有什么方法可以解決這個問題嗎&#xff1f;” 在電腦使用過程中&#xff0c;有時候會遇到鼠標光標突然消失的情況&#xff0c;這無疑會給我們日常操作帶來很大的不便。那么&#xff0c…

Linux bin包生成

需求背景&#xff1a; 在實際項目時我們很少把源碼用個tar給到客戶&#xff0c;這樣顯得很不專業&#xff0c;且有的時候我們提供補丁&#xff0c;那么這個時候我們提供一個補丁的bin包可以直接安裝運行就顯得很高大上了。 物料準備 準備一臺liunx&#xff0c;虛擬機亦可&am…

自定義插件vue-router簡單實現hashRouter設計思路

步驟 1.掛載 vue.prototype.$router 2.聲明兩個組件 router-view this.$router.current>component > h(component) router-link h(a,{attrs:{href:#this.to}},this.$slots.default) 3.url的監聽&#xff1a;window hashchange的改變 4.定義響應式current&#xff0…

使用Python提取PDF文件中指定頁面的內容

在日常工作和學習中&#xff0c;我們經常需要從PDF文件中提取特定頁面的內容。在本篇文章中&#xff0c;我們將介紹如何使用Python編程語言和兩個強大的庫——pymupdf和wxPython&#xff0c;來實現這個任務。 1. 準備工作 首先&#xff0c;確保你已經安裝了以下兩個Python庫&…

JavaScript深拷貝和淺拷貝

對于原始數據類型&#xff0c;并沒有深淺拷貝的區別&#xff0c;深淺拷貝都是對于引用數據類型而言&#xff0c;如果我們要賦值對象的所有屬性都是引用類型可以用淺拷貝 淺拷貝&#xff1a;只復制一層對象&#xff0c;當對象的屬性是引用類型時&#xff0c;實質復制的是其引用&…

【辦公軟件】Outlook啟動一直顯示“正在啟動”的解決方法

早上打開電腦Outlook2016以后&#xff0c;半個多小時了&#xff0c;一直顯示這個界面&#xff1a; 解決辦法 按WIN R鍵打開“運行”&#xff0c;輸入如下命令&#xff1a; outlook.exe /safe 然后點擊“確定” 這樣就進入了Outlook的安全模式。 點擊“文件”->“選項”-…

第6節:Vue3 調用函數

在Vue3中&#xff0c;你可以使用setup函數來調用函數。 <template><button click"handleClick">點擊我</button> </template><script> import { ref } from vue;export default {setup() {// 創建一個響應式的引用const count ref(0…

nbcio-vue下載安裝后運行報錯,diagram-js沒有安裝

更多nbcio-boot功能請看演示系統 gitee源代碼地址 后端代碼&#xff1a; https://gitee.com/nbacheng/nbcio-boot 前端代碼&#xff1a;https://gitee.com/nbacheng/nbcio-vue.git 在線演示&#xff08;包括H5&#xff09; &#xff1a; http://122.227.135.243:9888 根據…

047:vue加載循環倒計時 示例

第047個 查看專欄目錄: VUE ------ element UI 專欄目標 在vue和element UI聯合技術棧的操控下&#xff0c;本專欄提供行之有效的源代碼示例和信息點介紹&#xff0c;做到靈活運用。 &#xff08;1&#xff09;提供vue2的一些基本操作&#xff1a;安裝、引用&#xff0c;模板使…

基于java web的網上書城系統的設計與實現論文

摘 要 隨著科學技術的飛速發展&#xff0c;各行各業都在努力與現代先進技術接軌&#xff0c;通過科技手段提高自身的優勢&#xff0c;商品交易當然也不能排除在外&#xff0c;隨著商品交易管理的不斷成熟&#xff0c;它徹底改變了過去傳統的經營管理方式&#xff0c;不僅使商品…

32、Bean的生產順序是由什么決定的?

Bean的生產順序是由什么決定的? BeanDefinition的注冊順序是有什么決定的? Bean在生產之前有個臨時狀態:BeanDefinition;存儲著bean的描述信息。由BeanDefinition決定著Bean的生產順序。會按照BeanDefinition的注冊順序來決定Bean的生產順序。因為所有的BeanDefinition存…