詳解springcloud gateway工作原理、斷言、filter、uri、id、全局跨域、globalfilter等以及關鍵源碼實現

1.gateway概念
  1. 網關就是當前微服務項目的"統一入口"
  2. 程序中的網關就是當前微服務項目對外界開放的統一入口
  3. 所有外界的請求都需要先經過網關才能訪問到我們的程序
  4. 提供了統一入口之后,方便對所有請求進行統一的檢查和管理
2. 網關的主要功能
  • 將所有請求統一經過網關
  • 網關可以對這些請求進行檢查
  • 網關方便記錄所有請求的日志
  • 網關可以統一將所有請求路由到正確的模塊\服務上

“路由"的近義詞就是"分配”
在這里插入圖片描述

3. 工作原理

在這里插入圖片描述
在這里插入圖片描述
Spring Gateway的工作原理基于路由、斷言(Predicates)和過濾器(Filters)三大核心概念:

  • ?路由(Route):定義了請求的轉發規則,包括目標URL和匹配條件。
  • 斷言(Predicates):用于匹配HTTP請求的各種條件,如路徑、頭信息、參數等。只有匹配成功的請求才會被路由處理。
  • 過濾器(Filters):在請求處理前后執行特定的邏輯,例如權限校驗、日志記錄
4. 配置和使用示例
spring:cloud:gateway:routes:- id: myrouteuri: http://example.compredicates:- Path=/api/**filters:- AddRequestHeader=X-Request-ID, \${requestId}

這個配置定義了一個路由,所有路徑以/api/開頭的請求都會被轉發到http://example.com,并且在請求頭中添加一個X-Request-ID字段?

springcloud gateway中配置uri有3種方式:

  • ws(websocket)方式: uri: ws://localhost:9000
  • http方式: uri: http://localhost:8130/
  • lb(注冊中心中服務名字)方式: uri: lb://brilliance-consumer

springcloud gatetway命名規范

能被gateway的lb方式識別到的命名規則為:

   "[a-zA-Z]([a-zA-Z]|\\d|\\+|\\.|-)*:.*"

如果名字中有非*“a-zA-Z:.”*規則字符或者使用“_”,則會報錯

5.網關gateway routes的組成

1. id:必須唯一

2. predicates(斷言)
在這里插入圖片描述
關鍵類源碼分析:

package org.springframework.cloud.gateway.handler.predicate;import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.style.ToStringCreator;
import org.springframework.http.server.PathContainer;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.pattern.PathPattern;
import org.springframework.web.util.pattern.PathPattern.PathMatchInfo;
import org.springframework.web.util.pattern.PathPatternParser;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_PREDICATE_MATCHED_PATH_ATTR;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_PREDICATE_MATCHED_PATH_ROUTE_ID_ATTR;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_PREDICATE_PATH_CONTAINER_ATTR;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_PREDICATE_ROUTE_ATTR;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.putUriTemplateVariables;
import static org.springframework.http.server.PathContainer.parsePath;/*** @author Spencer Gibb* @author Dhawal Kapil*/
public class PathRoutePredicateFactory extends AbstractRoutePredicateFactory<PathRoutePredicateFactory.Config> {private static final Log log = LogFactory.getLog(PathRoutePredicateFactory.class);private static final String MATCH_TRAILING_SLASH = "matchTrailingSlash";private PathPatternParser pathPatternParser = new PathPatternParser();public PathRoutePredicateFactory() {super(Config.class);}private static void traceMatch(String prefix, Object desired, Object actual, boolean match) {if (log.isTraceEnabled()) {String message = String.format("%s \"%s\" %s against value \"%s\"", prefix, desired,match ? "matches" : "does not match", actual);log.trace(message);}}public void setPathPatternParser(PathPatternParser pathPatternParser) {this.pathPatternParser = pathPatternParser;}@Overridepublic List<String> shortcutFieldOrder() {return Arrays.asList("patterns", MATCH_TRAILING_SLASH);}@Overridepublic ShortcutType shortcutType() {return ShortcutType.GATHER_LIST_TAIL_FLAG;}@Overridepublic Predicate<ServerWebExchange> apply(Config config) {final ArrayList<PathPattern> pathPatterns = new ArrayList<>();synchronized (this.pathPatternParser) {pathPatternParser.setMatchOptionalTrailingSeparator(config.isMatchTrailingSlash());config.getPatterns().forEach(pattern -> {PathPattern pathPattern = this.pathPatternParser.parse(pattern);pathPatterns.add(pathPattern);});}return new GatewayPredicate() {@Overridepublic boolean test(ServerWebExchange exchange) {PathContainer path = (PathContainer) exchange.getAttributes().computeIfAbsent(GATEWAY_PREDICATE_PATH_CONTAINER_ATTR,s -> parsePath(exchange.getRequest().getURI().getRawPath()));PathPattern match = null;for (int i = 0; i < pathPatterns.size(); i++) {PathPattern pathPattern = pathPatterns.get(i);if (pathPattern.matches(path)) {match = pathPattern;break;}}if (match != null) {traceMatch("Pattern", match.getPatternString(), path, true);PathMatchInfo pathMatchInfo = match.matchAndExtract(path);putUriTemplateVariables(exchange, pathMatchInfo.getUriVariables());exchange.getAttributes().put(GATEWAY_PREDICATE_MATCHED_PATH_ATTR, match.getPatternString());String routeId = (String) exchange.getAttributes().get(GATEWAY_PREDICATE_ROUTE_ATTR);if (routeId != null) {// populated in RoutePredicateHandlerMappingexchange.getAttributes().put(GATEWAY_PREDICATE_MATCHED_PATH_ROUTE_ID_ATTR, routeId);}return true;}else {traceMatch("Pattern", config.getPatterns(), path, false);return false;}}@Overridepublic Object getConfig() {return config;}@Overridepublic String toString() {return String.format("Paths: %s, match trailing slash: %b", config.getPatterns(),config.isMatchTrailingSlash());}};}@Validatedpublic static class Config {private List<String> patterns = new ArrayList<>();private boolean matchTrailingSlash = true;public List<String> getPatterns() {return patterns;}public Config setPatterns(List<String> patterns) {this.patterns = patterns;return this;}/*** @deprecated use {@link #isMatchTrailingSlash()}*/@Deprecatedpublic boolean isMatchOptionalTrailingSeparator() {return isMatchTrailingSlash();}/*** @deprecated use {@link #setMatchTrailingSlash(boolean)}*/@Deprecatedpublic Config setMatchOptionalTrailingSeparator(boolean matchOptionalTrailingSeparator) {setMatchTrailingSlash(matchOptionalTrailingSeparator);return this;}public boolean isMatchTrailingSlash() {return matchTrailingSlash;}public Config setMatchTrailingSlash(boolean matchTrailingSlash) {this.matchTrailingSlash = matchTrailingSlash;return this;}@Overridepublic String toString() {return new ToStringCreator(this).append("patterns", patterns).append(MATCH_TRAILING_SLASH, matchTrailingSlash).toString();}}}
package org.springframework.cloud.gateway.handler.predicate;import java.util.function.Consumer;
import java.util.function.Predicate;
import org.springframework.cloud.gateway.handler.AsyncPredicate;
import org.springframework.cloud.gateway.support.Configurable;
import org.springframework.cloud.gateway.support.NameUtils;
import org.springframework.cloud.gateway.support.ShortcutConfigurable;
import org.springframework.web.server.ServerWebExchange;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.toAsyncPredicate;/*** @author Spencer Gibb*/
@FunctionalInterface
public interface RoutePredicateFactory<C> extends ShortcutConfigurable, Configurable<C> {/*** Pattern key.*/String PATTERN_KEY = "pattern";// useful for javadsldefault Predicate<ServerWebExchange> apply(Consumer<C> consumer) {C config = newConfig();consumer.accept(config);beforeApply(config);return apply(config);}default AsyncPredicate<ServerWebExchange> applyAsync(Consumer<C> consumer) {C config = newConfig();consumer.accept(config);beforeApply(config);return applyAsync(config);}default Class<C> getConfigClass() {throw new UnsupportedOperationException("getConfigClass() not implemented");}@Overridedefault C newConfig() {throw new UnsupportedOperationException("newConfig() not implemented");}default void beforeApply(C config) {}Predicate<ServerWebExchange> apply(C config);default AsyncPredicate<ServerWebExchange> applyAsync(C config) {return toAsyncPredicate(apply(config));}default String name() {return NameUtils.normalizeRoutePredicateName(getClass());}}

自定義Vip路由斷言工廠實現

package com.wemedia.gateway.config;import jakarta.validation.constraints.NotEmpty;
import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.CookieRoutePredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.GatewayPredicate;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.server.ServerWebExchange;import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;/*** 自定義Vip路由斷言工廠*/
@Component
public class VipRoutePredicateFactory extends AbstractRoutePredicateFactory<VipRoutePredicateFactory.Config> {public VipRoutePredicateFactory(){super(Config.class);}@Overridepublic List<String> shortcutFieldOrder() {return Arrays.asList("param","value");}@Overridepublic Predicate<ServerWebExchange> apply(Config config) {return new GatewayPredicate() {@Overridepublic boolean test(ServerWebExchange serverWebExchange) {//localhost/search?q=hhh&user=jackmaServerHttpRequest request = serverWebExchange.getRequest();String first = request.getQueryParams().getFirst(config.param);return StringUtils.hasText(first)&&first.equals(config.value);}};}@Validatedpublic static class Config {@NotEmptyprivate String value;@NotEmptyprivate String param;public String getParam() {return param;}public void setParam(String param) {this.param = param;}public String getValue() {return value;}public void setValue(String value) {this.value = value;}}
}在這里插入代碼片

配置Vip斷言
在這里插入圖片描述
3. Filter(過濾器)
在這里插入圖片描述
3.1 rewritePath(路徑重寫)
在這里插入圖片描述
在這里插入圖片描述

  • 添加RewritePath過濾器,重寫原先路徑/readDb,在訪問路徑前面追加/api/order/readDb,不然網關無法直接訪問/readDb;
  • 添加AddReponseHeader過濾器,給響應頭增加參數X-Response-ABC,值為123

3.2 默認過濾器filter:
在這里插入圖片描述
增加默認過濾器default-filters, 參數Add-ReponseHeader=X-Reponse-Abc ,值為123 給所有服務的相應頭中

3.3 全局過濾器GlobalFilter

package com.wemedia.gateway.filter;import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;/*** 實現響應時間全局過濾器*/
@Component
@Slf4j
public class RTGlobalFilter implements GlobalFilter, Ordered {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {ServerHttpRequest request = exchange.getRequest();ServerHttpResponse response = exchange.getResponse();String uri = request.getURI().toString();long start=System.currentTimeMillis();log.info("請求【{}】開始時間:{}",uri,start );//================以上是前置邏輯==============Mono<Void> filter = chain.filter(exchange).doFinally((result) -> {//================后置邏輯long end = System.currentTimeMillis();log.info("請求【{}】結束時間:{},耗時:{}ms", uri, end, end - start);});return filter;}@Overridepublic int getOrder() {return 0;}
}

過濾器filter 列表:
在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述
3.4 自定義過濾器工廠

關鍵源碼分析

package org.springframework.cloud.gateway.filter.factory;
import reactor.core.publisher.Mono;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.web.server.ServerWebExchange;import static org.springframework.cloud.gateway.support.GatewayToStringStyler.filterToStringCreator;/*** @author Spencer Gibb*/
public class AddResponseHeaderGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory {@Overridepublic GatewayFilter apply(NameValueConfig config) {return new GatewayFilter() {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {return chain.filter(exchange).then(Mono.fromRunnable(() -> addHeader(exchange, config)));}@Overridepublic String toString() {return filterToStringCreator(AddResponseHeaderGatewayFilterFactory.this).append(config.getName(), config.getValue()).toString();}};}void addHeader(ServerWebExchange exchange, NameValueConfig config) {final String value = ServerWebExchangeUtils.expand(exchange, config.getValue());HttpHeaders headers = exchange.getResponse().getHeaders();// if response has been commited, no more response headers will bee added.if (!exchange.getResponse().isCommitted()) {headers.add(config.getName(), value);}}}

1.實現一次性token自定義過濾器工廠

package com.wemedia.gateway.filter;import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.AbstractNameValueGatewayFilterFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.UUID;/*** 實現一次性令牌的自定義過濾器工場*/
@Component
@Slf4j
public class OnceTokenGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory {@Overridepublic GatewayFilter apply(NameValueConfig config) {return new GatewayFilter() {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {//每次響應之前,添加一個一次性令牌,支持uuid,jwt等格式return chain.filter(exchange).then(Mono.fromRunnable(()->{ServerHttpResponse response = exchange.getResponse();HttpHeaders headers = response.getHeaders();String value = config.getValue();if("uuid".equalsIgnoreCase(value)){value = UUID.randomUUID().toString();}if("jwt".equalsIgnoreCase(value)){value="";}headers.add(config.getName(),value);}));}};}
}

2.配置一次性token過濾器
在這里插入圖片描述
3.訪問api響應結果如下:
在這里插入圖片描述
3.5 實現全局跨域
在這里插入圖片描述

  • 解決單機跨域方法:直接在每個controller上增加注解@CrossOrigin

在這里插入圖片描述

  • 在分布式系統上解決跨域問題,在gateway上統一處理跨域問題
    配置全局跨域:
    在這里插入圖片描述
    運行結果如下,增加了跨域處理:
    在這里插入圖片描述

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/80612.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/80612.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/80612.shtml

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

相關文章

C#中的弱引用使用

弱引用&#xff08;Weak Reference&#xff09;是一種特殊的引用類型&#xff0c;它允許你引用一個對象&#xff0c;但不會阻止該對象被垃圾回收器&#xff08;GC&#xff09;回收。弱引用通常用于需要緩存或跟蹤對象&#xff0c;但又不希望因保留引用而導致內存泄漏的場景。弱…

spring響應式編程系列:異步生產數據

目錄 示例 大致流程 create new MonoCreate subscribe new LambdaMonoSubscriber monoCreate.subscribe accept success onNext 時序圖 類圖 數據發布者 MonoCreate 數據訂閱者 LambdaMonoSubscriber 訂閱的消息體 DefaultMonoSink 本篇文章我們來研究如何將…

MCP Python SDK構建的**SQLite瀏覽器**的完整操作指南

以下是使用MCP Python SDK構建的SQLite瀏覽器的完整操作指南&#xff1a; 一、環境準備 安裝依賴 # 安裝MCP SDK及SQLite支持 pip install mcp sqlite3創建測試數據庫 sqlite3 test.db <<EOF CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT); IN…

【Python爬蟲基礎篇】--3.cookie和session

目錄 1.cookie 1.1.定義 1.2.參數 1.3.分類 2.session 3.使用cookie登錄微博 4.使用session登錄 1.cookie 由于http是一個無狀態的協議&#xff0c;請求與請求之間無法相互傳遞或者記錄一些信息&#xff0c;cookie和session正是為了解決這個問題而產生。 例子&#xff1…

風車郵箱系統詳細使用指南:Windows與Ubuntu雙平臺解析

風車郵箱系統V1.2使用手冊 風車郵箱系統詳細使用指南&#xff1a;Windows與Ubuntu雙平臺解析 前言 在日常網絡活動中&#xff0c;我們經常需要一個臨時郵箱來注冊各類網站或接收驗證碼&#xff0c;但不想使用自己的真實郵箱。「風車無線郵箱系統」作為一款優秀的臨時郵箱工具…

同樣的接口用postman/apifox能跑通,用jmeter跑就報錯500

之前沒用過jmeter,第一次用調試壓測腳本遇到了問題 一樣的接口用postman能跑通&#xff0c;用jmeter跑就報錯500&#xff0c;百度很多文章都說是該接口需要加一個‘內容編碼’改成utf-8,我加了還是不行 后來我就想到apifox好像有隱藏的header&#xff0c;然后開始比較apifox的…

1656打印路徑-Floyd回溯/圖論-鏈表/數據結構

藍橋賬戶中心 1.稅收&#xff1a; “城市的稅收”&#xff1a;所以是中介點的稅收&#xff0c;經過該點后加上 2.路徑&#xff1a; 用數組存儲前驅節點從而串成鏈表 pre[ i ][ j ]代表的是從 i 到 j 的最短路徑上 j 的前驅節點是什么 那么便可以pre[ i ][ j ]k 把k加入pa…

Eigen矩陣操作類 (Map, Block, 視圖類)

1. Map 類&#xff1a;內存映射&#xff08;零拷貝操作&#xff09; 核心功能 將現有的 C/C 數組或緩沖區映射為 Eigen 矩陣/向量&#xff0c;不復制數據&#xff0c;直接操作原內存。 模板參數 cpp Map<Matrix<Scalar, Rows, Cols, Options, MaxRows, MaxCols>&…

多系統安裝經驗,移動硬盤,ubuntu grub修改/etc/fstab 移動硬盤需要改成nfts格式才能放steam游戲

總結&#xff1a;我硬盤會自動掛載&#xff0c;直接格式化nfts&#xff0c;steam就能裝里面了 機械硬盤裝系統真的不行&#xff0c;超級慢游戲還跑不了 --------------------------------------------------------------------底下都不用看 筆記本一個系統&#xff0c;移動硬盤…

JFLAP SOFTWARE 編譯原理用(自動機繪圖)

csdn全是蛆蟲&#xff0c;2mb的軟件&#xff0c;都在那里搞收費&#xff0c;我就看不慣&#xff0c;我就放出來&#xff0c;那咋了&#xff01;&#xff01;&#xff01; https://pan.baidu.com/s/1IuEfHScynjCCUF5ScF26KA 通過網盤分享的文件&#xff1a;JFLAP7.1.jar 鏈接: h…

[Windows] Disk Sorter文件分類管理軟件 v16.7.18

[Windows] Disk Sorter文件分類管理 鏈接&#xff1a;https://pan.xunlei.com/s/VOOl0sDntAdHvlMkc7N0ZOD-A1?pwd966n# Disk Sorter是一個功能強大的文件分類管理軟件&#xff0c;允許對本地磁盤、網絡共享、NAS設備和企業存儲系統中的文件進行分類&#xff0c;并且支持生成…

STM32提高篇: 藍牙通訊

STM32提高篇: 藍牙通訊 一.藍牙通訊介紹1.藍牙技術類型 二.藍牙協議棧1.藍牙芯片架構2.BLE低功耗藍牙協議棧框架 三.ESP32-C3中的藍牙功能1.廣播2.掃描3.通訊 四.發送和接收 一.藍牙通訊介紹 藍牙&#xff0c;是一種利用低功率無線電&#xff0c;支持設備短距離通信的無線電技…

6.1.多級緩存架構

目錄 一、多級緩存基礎與核心概念 緩存的定義與價值 ? 緩存的應用場景&#xff08;高并發、低延遲、減輕數據庫壓力&#xff09; ? 多級緩存 vs 單級緩存的優劣對比 多級緩存核心組件 ? 本地緩存&#xff08;Caffeine、Guava Cache&#xff09; ? 分布式緩存&#xff08;…

MySQL的MVCC【學習筆記】

MVCC 事務的隔離級別分為四種&#xff0c;其中Read Committed和Repeatable Read隔離級別&#xff0c;部分實現就是通過MVCC&#xff08;Multi-Version Concurrency Control&#xff0c;多版本并發控制&#xff09; 版本鏈 版本鏈是通過undo日志實現的&#xff0c; 事務每次修改…

基于OpenMV+STM32+OLED與YOLOv11+PaddleOCR的嵌入式車牌識別系統開發筆記

基于OpenMV、STM32與OLED的嵌入式車牌識別系統開發筆記 基于OpenMV、STM32與OLED的嵌入式車牌識別系統開發筆記系統架構全景 一、實物演示二、OpenMV端設計要點1. 硬件配置優化2. 智能幀率控制算法3. 數據傳輸協議設計 三、PyTorch后端核心實現&#xff1a;YOLOv11與PaddleOCR的…

C#中常見的設計模式

文章目錄 引言設計模式的分類創建型模式 (Creational Patterns)1. 單例模式 (Singleton)2. 工廠方法模式 (Factory Method)3. 抽象工廠模式 (Abstract Factory)4. 建造者模式 (Builder) 結構型模式 (Structural Patterns)5. 適配器模式 (Adapter)6. 裝飾器模式 (Decorator)7. 外…

Nacos簡介—3.Nacos的配置簡介

大綱 1.Nacos生產集群Web端口與數據庫配置 2.Nacos生產集群的Distro協議核心參數 3.Nacos打通CMDB實現跨機房的就近訪問 4.Nacos基于SPI動態擴展機制來獲取CMDB的數據 5.基于Nacos SPI機制開發CMDB動態擴展 6.Nacos基于CMDB來實現多機房就近訪問 7.Nacos生產集群Prometh…

Jest 快照測試

以下是關于 Jest 快照測試的系統化知識總結,從基礎使用到底層原理全面覆蓋: 一、快照測試核心原理 1. 工作機制三階段 #mermaid-svg-GC46t2NBvGv7RF0M {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-GC46t2NBvGv…

第十六屆藍橋杯大賽軟件賽省賽 C/C++ 大學B組 [京津冀]

由于官方沒有公布題目的數據, 所以代碼僅供參考 1. 密密擺放 題目鏈接&#xff1a;P12337 [藍橋杯 2025 省 AB/Python B 第二場] 密密擺放 - 洛谷 題目描述 小藍有一個大箱子&#xff0c;內部的長寬高分別是 200、250、240&#xff08;單位&#xff1a;毫米&#xff09;&…

Spring 學習筆記之 @Transactional 異常不回滾匯總

使用springboot時&#xff0c;只要引入spring-jdbc/jpa相關的依賴后&#xff0c;在想要啟用事務的方法上加上Transactional注解就能開啟事務&#xff0c;碰到異常就能自動回滾。大大的提高了編碼的便捷性性&#xff0c;同時也不侵入代碼&#xff0c;保持了代碼的簡潔性。 默認情…