seate TCC模式案例

場景描述

  1. 用戶下單時,需要創建訂單并從用戶賬戶中扣除相應的余額。
  2. 如果訂單創建成功但余額劃扣失敗,則需要回滾訂單創建操作。
  3. 使用 Seata 的 TCC 模式來保證分布式事務的一致性。

1. 項目結構

假設我們有兩個微服務:

  • Order Service:負責創建訂單。
  • Account Service:負責扣除用戶余額。

此外,還需要一個 Seata Server 來協調分布式事務。


2. 數據庫設計

Order 表
CREATE TABLE `orders` (`id` BIGINT AUTO_INCREMENT PRIMARY KEY,`user_id` VARCHAR(32) NOT NULL,`product_id` VARCHAR(32) NOT NULL,`amount` DECIMAL(10, 2) NOT NULL,`status` VARCHAR(16) DEFAULT 'INIT' -- 狀態:INIT(初始化)、CONFIRMED(確認)、CANCELLED(取消)
);
Account 表
CREATE TABLE `accounts` (`id` BIGINT AUTO_INCREMENT PRIMARY KEY,`user_id` VARCHAR(32) NOT NULL,`balance` DECIMAL(10, 2) NOT NULL
);

3. Order Service

(1) 定義 TCC 接口

OrderService 中定義 Try、Confirm 和 Cancel 方法。

@LocalTCC
public interface OrderTccService {@TwoPhaseBusinessAction(name = "createOrder", commitMethod = "confirmOrder", rollbackMethod = "cancelOrder")boolean createOrder(BusinessActionContext context, String userId, String productId, BigDecimal amount);boolean confirmOrder(BusinessActionContext context);boolean cancelOrder(BusinessActionContext context);
}
(2) 實現 TCC 方法
@Service
public class OrderTccServiceImpl implements OrderTccService {@Autowiredprivate OrderMapper orderMapper;@Overridepublic boolean createOrder(BusinessActionContext context, String userId, String productId, BigDecimal amount) {// Try 階段:創建訂單,狀態為 INITOrder order = new Order();order.setUserId(userId);order.setProductId(productId);order.setAmount(amount);order.setStatus("INIT");orderMapper.insert(order);// 將訂單 ID 存入上下文,供 Confirm 和 Cancel 使用context.getActionContext().put("orderId", order.getId());return true;}@Overridepublic boolean confirmOrder(BusinessActionContext context) {// Confirm 階段:將訂單狀態更新為 CONFIRMEDLong orderId = (Long) context.getActionContext("orderId");orderMapper.updateStatus(orderId, "CONFIRMED");return true;}@Overridepublic boolean cancelOrder(BusinessActionContext context) {// Cancel 階段:將訂單狀態更新為 CANCELLEDLong orderId = (Long) context.getActionContext("orderId");orderMapper.updateStatus(orderId, "CANCELLED");return true;}
}
(3) Mapper 定義
@Mapper
public interface OrderMapper {void insert(Order order);void updateStatus(Long orderId, String status);
}

4. Account Service

(1) 定義 TCC 接口

AccountService 中定義 Try、Confirm 和 Cancel 方法。

@LocalTCC
public interface AccountTccService {@TwoPhaseBusinessAction(name = "deductBalance", commitMethod = "confirmDeduct", rollbackMethod = "cancelDeduct")boolean deductBalance(BusinessActionContext context, String userId, BigDecimal amount);boolean confirmDeduct(BusinessActionContext context);boolean cancelDeduct(BusinessActionContext context);
}
(2) 實現 TCC 方法
@Service
public class AccountTccServiceImpl implements AccountTccService {@Autowiredprivate AccountMapper accountMapper;@Overridepublic boolean deductBalance(BusinessActionContext context, String userId, BigDecimal amount) {// Try 階段:檢查余額是否足夠,并凍結相應金額Account account = accountMapper.findByUserId(userId);if (account.getBalance().compareTo(amount) < 0) {throw new RuntimeException("Insufficient balance");}accountMapper.freezeBalance(userId, amount);// 將凍結金額存入上下文,供 Confirm 和 Cancel 使用context.getActionContext().put("userId", userId);context.getActionContext().put("amount", amount);return true;}@Overridepublic boolean confirmDeduct(BusinessActionContext context) {// Confirm 階段:扣除已凍結的金額String userId = (String) context.getActionContext("userId");BigDecimal amount = (BigDecimal) context.getActionContext("amount");accountMapper.confirmDeduct(userId, amount);return true;}@Overridepublic boolean cancelDeduct(BusinessActionContext context) {// Cancel 階段:釋放已凍結的金額String userId = (String) context.getActionContext("userId");BigDecimal amount = (BigDecimal) context.getActionContext("amount");accountMapper.cancelDeduct(userId, amount);return true;}
}
(3) Mapper 定義
@Mapper
public interface AccountMapper {Account findByUserId(String userId);void freezeBalance(String userId, BigDecimal amount);void confirmDeduct(String userId, BigDecimal amount);void cancelDeduct(String userId, BigDecimal amount);
}

5. 調用方(API Gateway 或其他服務)

在調用方使用 @GlobalTransactional 注解開啟全局事務。

@RestController
@RequestMapping("/api/orders")
public class OrderController {@Autowiredprivate OrderTccService orderTccService;@Autowiredprivate AccountTccService accountTccService;@PostMapping("/create")@GlobalTransactionalpublic ResponseEntity<String> createOrder(@RequestBody CreateOrderRequest request) {try {// 創建訂單orderTccService.createOrder(null, request.getUserId(), request.getProductId(), request.getAmount());// 扣除余額accountTccService.deductBalance(null, request.getUserId(), request.getAmount());return ResponseEntity.ok("Order created successfully");} catch (Exception e) {return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Failed to create order: " + e.getMessage());}}
}

6. 測試流程

  1. 啟動 Seata Server。
  2. 啟動 Order Service 和 Account Service。
  3. 發送請求到?/api/orders/create?接口,創建訂單并扣除余額。
  4. 如果任意一個步驟失敗,Seata 會自動觸發回滾邏輯。

7. 關鍵點總結

  1. TCC 模式的核心

    • Try:預留資源。
    • Confirm:確認操作。
    • Cancel:補償操作。
  2. Spring Cloud 集成

    • 使用?@LocalTCC?和?@TwoPhaseBusinessAction?注解定義 TCC 接口。
    • 使用?@GlobalTransactional?開啟全局事務。
  3. 事務一致性

    • 如果任意一步失敗,Seata 會自動調用 Cancel 方法進行回滾,確保數據一致。

TCC模式還會存在空回滾,冪等,懸掛等問題?

1. 空回滾

問題描述

  • 定義:在 TCC 模式中,如果 Try 階段沒有執行(例如由于網絡超時或服務不可用),但 Cancel 階段被調用了,則會導致空回滾。
  • 原因
    • Try 請求未到達服務端,或者未成功執行。
    • Seata Server 在協調事務時檢測到失敗,直接觸發了 Cancel 階段。

解決方案

  • 解決思路:在 Cancel 方法中判斷是否需要執行回滾操作。
  • 實現方式
    • 在數據庫中增加一個狀態字段,用于標記資源是否已經被預留(Try 階段是否執行過)。
    • 如果狀態字段表明資源未被預留,則直接跳過 Cancel 操作。
示例代碼
@Override
public boolean cancelDeduct(BusinessActionContext context) {String userId = (String) context.getActionContext("userId");Account account = accountMapper.findByUserId(userId);// 判斷是否需要回滾(賬戶是否有凍結金額)if (account.getFrozenAmount().compareTo(BigDecimal.ZERO) == 0) {return true; // 跳過空回滾}// 執行取消邏輯accountMapper.cancelDeduct(userId, (BigDecimal) context.getActionContext("amount"));return true;
}

2. 冪等性

問題描述

  • 定義:TCC 的 Confirm 或 Cancel 方法可能因為網絡重試等原因被多次調用,導致重復操作。
  • 原因
    • Seata Server 可能會多次嘗試調用 Confirm 或 Cancel 方法。
    • 客戶端或網絡層可能引發重復請求。

解決方案

  • 解決思路:確保 Confirm 和 Cancel 方法是冪等的。
  • 實現方式
    • 使用數據庫的狀態字段來記錄操作是否已經完成。
    • 如果某個操作已經完成,則直接返回成功,不再重復執行。
示例代碼
@Override
public boolean confirmDeduct(BusinessActionContext context) {String userId = (String) context.getActionContext("userId");Account account = accountMapper.findByUserId(userId);// 判斷是否已經確認if ("CONFIRMED".equals(account.getStatus())) {return true; // 已經確認,直接返回}// 執行確認邏輯accountMapper.confirmDeduct(userId, (BigDecimal) context.getActionContext("amount"));accountMapper.updateStatus(userId, "CONFIRMED");return true;
}@Override
public boolean cancelDeduct(BusinessActionContext context) {String userId = (String) context.getActionContext("userId");Account account = accountMapper.findByUserId(userId);// 判斷是否已經取消if ("CANCELLED".equals(account.getStatus())) {return true; // 已經取消,直接返回}// 執行取消邏輯accountMapper.cancelDeduct(userId, (BigDecimal) context.getActionContext("amount"));accountMapper.updateStatus(userId, "CANCELLED");return true;
}

3. 懸掛

問題描述

  • 定義:Confirm 或 Cancel 方法比 Try 方法先執行,導致業務邏輯異常。
  • 原因
    • Try 請求在網絡傳輸中延遲,而 Seata Server 認為 Try 失敗并提前觸發了 Confirm 或 Cancel。
    • Try 請求最終到達服務端時,發現 Confirm 或 Cancel 已經執行。

解決方案

  • 解決思路:通過狀態字段和事務上下文信息,避免懸掛問題。
  • 實現方式
    • 在數據庫中記錄事務的執行狀態。
    • 在 Try 方法中檢查是否存在對應的 Confirm 或 Cancel 操作。如果有,則直接跳過 Try 操作。
示例代碼
@Override
public boolean deductBalance(BusinessActionContext context, String userId, BigDecimal amount) {Account account = accountMapper.findByUserId(userId);// 判斷是否已經確認或取消if ("CONFIRMED".equals(account.getStatus()) || "CANCELLED".equals(account.getStatus())) {return true; // 懸掛處理:直接返回}// 執行 Try 邏輯if (account.getBalance().compareTo(amount) < 0) {throw new RuntimeException("Insufficient balance");}accountMapper.freezeBalance(userId, amount);return true;
}

4. 總結

問題原因解決方案
空回滾Try 未執行,但 Cancel 被調用在 Cancel 方法中檢查 Try 是否已執行,未執行則跳過。
冪等性Confirm 或 Cancel 方法被多次調用使用狀態字段記錄操作是否已完成,避免重復執行。
懸掛Confirm 或 Cancel 比 Try 先執行在 Try 方法中檢查 Confirm 或 Cancel 是否已執行,已執行則跳過 Try。

通過以上方法,可以有效解決 TCC 模式中的空回滾、冪等性和懸掛問題,從而保證分布式事務的一致性和可靠性。


用字段狀態檢測以上問題,程序并不健壯,如果在高并發情況下還會出現一些問題,為了程序健壯性,達到強一致,我們還需要引入令牌和分布式鎖


1. 狀態字段的作用

  • 狀態字段?是最基礎的冪等性保障方式。
  • 它通過記錄操作的狀態(如?INITCONFIRMEDCANCELLED)來判斷某個操作是否已經完成。
  • 優點:簡單直觀,易于實現。
  • 缺點:在高并發場景下可能會出現競爭條件(race condition),導致狀態更新不一致。

2. 引入令牌機制

為什么需要令牌?

  • 定義:令牌是一種唯一標識符,用于確保每個請求只被執行一次。
  • 在分布式系統中,網絡重試可能導致同一個請求被多次發送到服務端。如果服務端無法區分這些重復請求,則會導致重復操作。
  • 適用場景
    • 請求可能因為網絡問題被重復發送。
    • 需要嚴格避免重復操作的場景(如支付、扣款等)。

實現方式

  • 每個請求生成一個唯一的令牌(如 UUID)。
  • 服務端在接收到請求時,先檢查該令牌是否已經被處理過。
  • 如果已處理過,則直接返回成功;否則執行業務邏輯并記錄該令牌。
示例代碼
@Override
public boolean confirmDeduct(BusinessActionContext context) {String token = (String) context.getActionContext("token");if (StringUtils.isEmpty(token)) {throw new RuntimeException("Token is missing");}// 檢查令牌是否已經處理過if (deductTokenRepository.existsByToken(token)) {return true; // 冪等性處理:直接返回}// 執行確認邏輯String userId = (String) context.getActionContext("userId");BigDecimal amount = (BigDecimal) context.getActionContext("amount");accountMapper.confirmDeduct(userId, amount);// 記錄令牌DeductToken deductToken = new DeductToken();deductToken.setToken(token);deductToken.setStatus("CONFIRMED");deductTokenRepository.save(deductToken);return true;
}
數據庫表設計
CREATE TABLE `deduct_token` (`id` BIGINT AUTO_INCREMENT PRIMARY KEY,`token` VARCHAR(64) NOT NULL UNIQUE,`status` VARCHAR(16) NOT NULL,`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

3. 引入分布式鎖

為什么需要分布式鎖?

  • 定義:分布式鎖是一種協調機制,用于保證多個節點對共享資源的操作是互斥的。
  • 在高并發場景下,即使有狀態字段或令牌機制,也可能因為多個線程同時訪問同一資源而導致數據不一致。
  • 適用場景
    • 多個服務實例同時處理同一個請求。
    • 需要強一致性保障的場景。

實現方式

  • 使用 Redis 或 Zookeeper 實現分布式鎖。
  • 在業務邏輯執行前獲取鎖,在業務邏輯完成后釋放鎖。
  • 如果無法獲取鎖,則等待或直接返回失敗。
示例代碼(基于 Redis)
@Autowired
private RedisTemplate<String, String> redisTemplate;@Override
public boolean confirmDeduct(BusinessActionContext context) {String lockKey = "lock:confirmDeduct:" + context.getXid(); // XID 是全局事務 IDString userId = (String) context.getActionContext("userId");// 嘗試獲取分布式鎖Boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, userId, 10, TimeUnit.SECONDS);if (Boolean.FALSE.equals(locked)) {throw new RuntimeException("Failed to acquire lock");}try {// 檢查狀態字段Account account = accountMapper.findByUserId(userId);if ("CONFIRMED".equals(account.getStatus())) {return true; // 已經確認,直接返回}// 執行確認邏輯accountMapper.confirmDeduct(userId, (BigDecimal) context.getActionContext("amount"));accountMapper.updateStatus(userId, "CONFIRMED");return true;} finally {// 釋放分布式鎖redisTemplate.delete(lockKey);}
}

4. 綜合解決方案

在實際項目中,通常會結合 狀態字段令牌機制分布式鎖 來實現全面的冪等性保障:

  1. 狀態字段
    • 用來記錄操作的狀態,避免重復執行。
  2. 令牌機制
    • 為每個請求分配唯一標識符,確保每個請求只被執行一次。
  3. 分布式鎖
    • 在高并發場景下,使用分布式鎖保護共享資源,避免競爭條件。
示例流程
  1. 客戶端生成令牌
    • 客戶端在發送請求時生成一個唯一的令牌(如 UUID),并將令牌附加到請求中。
  2. 服務端校驗令牌
    • 服務端接收到請求后,首先檢查令牌是否存在。
    • 如果令牌已存在,則直接返回成功。
  3. 獲取分布式鎖
    • 如果令牌不存在,則嘗試獲取分布式鎖。
    • 如果鎖獲取成功,則繼續執行業務邏輯;否則返回失敗或等待。
  4. 更新狀態字段
    • 執行業務邏輯后,更新狀態字段以標記操作已完成。
  5. 記錄令牌
    • 將令牌保存到數據庫中,以便后續重復請求可以直接跳過。

5. 總結

方法適用場景優缺點
狀態字段基礎的冪等性保障,適用于大多數場景。優點:簡單易用;缺點:高并發下可能存在問題。
令牌機制適用于需要嚴格避免重復操作的場景(如支付、扣款)。優點:能有效防止重復請求;缺點:需要額外存儲令牌信息。
分布式鎖適用于高并發場景,需要強一致性保障的場景。優點:避免競爭條件;缺點:增加了系統復雜性和性能開銷。

通過結合 狀態字段令牌機制分布式鎖,可以構建一個健壯的冪等性保障機制,從而更好地應對分布式事務中的各種挑戰。

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

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

相關文章

【Linux】Rhcsa復習5

一、Linux文件系統權限 1、文件的一般權限 文件權限針對三類對象進行定義&#xff1a; owner 屬主&#xff0c;縮寫u group 屬組&#xff0c; 縮寫g other 其他&#xff0c;縮寫o 每個文件針對每類訪問者定義了三種主要權限&#xff1a; r&#xff1a;read 讀 w&…

《Operating System Concepts》閱讀筆記:p748-p748

《Operating System Concepts》學習第 64 天&#xff0c;p748-p748 總結&#xff0c;總計 1 頁。 一、技術總結 1.Transmission Control Protocol(TCP) 重點是要自己能畫出其過程&#xff0c;這里就不贅述了。 二、英語總結(生詞&#xff1a;3) transfer, transport, tran…

C語言之圖像文件的屬性

&#x1f31f; 嗨&#xff0c;我是LucianaiB&#xff01; &#x1f30d; 總有人間一兩風&#xff0c;填我十萬八千夢。 &#x1f680; 路漫漫其修遠兮&#xff0c;吾將上下而求索。 圖像文件屬性提取系統設計與實現 目錄 設計題目設計內容系統分析總體設計詳細設計程序實現…

opencv--基礎

opencv OpenCV是一個實現數字圖像處理和計算機視覺通用算法的開源跨平臺庫。 鏈接 opencv中的cv是什么意思 在OpenCV中&#xff0c;"cv" 是 "Computer Vision"&#xff08;計算機視覺&#xff09; 的縮寫。 opencv的實現語言 opencv的底層實現代碼是使…

Java創建對象的方式

1、通過new關鍵字創建新對象 用new關鍵字創建對象是我們在開發中最常用的方式&#xff0c;new關鍵字會為我們在堆內存中開辟一塊空間以存放對象的引用&#xff08;包含對象本身以及內部屬性的引用&#xff09;。 2、通過newInstance()方法創建新對象 newInstance()方法本質上是…

構建具備推理與反思能力的高級 Prompt:LLM 智能代理設計指南

在構建強大的 AI 系統&#xff0c;尤其是基于大語言模型&#xff08;LLM&#xff09;的智能代理&#xff08;Agent&#xff09;時&#xff0c;Prompt 設計的質量決定了系統的智能程度。傳統 Prompt 通常是簡單的問答或填空式指令&#xff0c;而高級任務需要更具結構性、策略性和…

豬行為視頻數據集

豬行為數據集包含 23 天(超過 6 周)的日間豬行為視頻,這些視頻由近乎架空的攝像機拍攝。視頻已配準顏色和深度信息。數據以每秒 6 幀的速度捕獲,并以 1800 幀(5 分鐘)為一批次進行存儲。大多數幀顯示 8 頭豬。 這里可以看到顏色和深度圖像的示例: 喂食器位于圖片底部中…

C++運算符重載詳解

C++ 中的運算符重載允許為用戶自定義類型(類或結構體)賦予運算符特定功能,使其操作更直觀。以下是運算符重載的關鍵點: 1. 基本語法 成員函數重載:運算符作為類的成員函數,左操作數為當前對象 (this),右操作數為參數。 class Complex {public:Complex operator+(const …

deep-share開源瀏覽器擴展,用于分享 DeepSeek 對話,使用戶能夠將對話內容保存為圖片或文本以便輕松分享

一、軟件介紹 文末提供程序和源碼下載學習 deep-share開源瀏覽器擴展&#xff0c;用于分享 DeepSeek 對話&#xff0c;使用戶能夠將對話內容保存為圖片或文本以便輕松分享。 二、軟件功能 One-click capture of DeepSeek chat content一鍵捕獲 DeepSeek 聊天內容Support sha…

Unity之如何實現RenderStreaming視頻推流

文章目錄 前言引入 UnityRenderStreaming 的好處教程步驟 1:設置環境步驟 2: 創建項目步驟 3:安裝軟件包步驟 5:下載示例步驟 6:檢查配置環境步驟 7:打開推流場景步驟 8: 準備用于流式傳輸的WebServer應用程序步驟 9: 運行 示例場景步驟 10:檢查視頻是否在瀏覽器中顯示…

30天開發操作系統 第26天 -- 為窗口移動提速

前言 昨天我們增加了可同時啟動的應用程序的數量&#xff0c;窗口也跟著變多了&#xff0c;整個畫面變得熱鬧起來。 話說&#xff0c;在對比color.hrb和color2.hrb的時候我們需要移動窗口&#xff0c;那個時候筆者感到窗口移動的速度很慢。在真機環境下的速度還算可以接受&…

9.QT-顯示類控件|Label|顯示不同格式的文本|顯示圖片|文本對齊|自動換行|縮進|邊距|設置伙伴(C++)

Label QLabel 可以?來顯??本和圖? 屬性說明textQLabel中的?本textFormat?本的格式.? Qt::PlainText 純?本? Qt::RichText 富?本(?持html標簽)? Qt::MarkdownText markdown格式? Qt::AutoText 根據?本內容?動決定?本格式pixmapQLabel 內部包含的圖?.scaledCo…

非參數檢驗題目集

非參數檢驗題目集 對醫學計量資料成組比較&#xff0c;相對參數檢驗來說&#xff0c;非參數秩和檢驗的優點是&#xff08; &#xff09; A. 適用范圍廣 B. 檢驗效能高 C. 檢驗結果更準確 D. 充分利用資料信息 E. 不易出現假陰性錯誤 對于計量資料的比較&#xff0c;在滿足參數…

libdxfrw庫使用總結

在 Win11VS2022CMake 平臺編譯 libdxfrw 庫的挑戰與應對 在當今數字化設計與開發領域&#xff0c;高效處理 CAD 文件格式如 DXF 是眾多項目的關鍵需求。libdxfrw 庫作為一種功能強大的工具&#xff0c;能助力開發者精準解析與寫入 DXF 文件&#xff0c;使其在眾多應用場景中備…

C++學習:六個月從基礎到就業——內存管理:RAII原則

C學習&#xff1a;六個月從基礎到就業——內存管理&#xff1a;RAII原則 本文是我C學習之旅系列的第十九篇技術文章&#xff0c;也是第二階段"C進階特性"的第四篇&#xff0c;主要介紹C中的RAII原則及其在資源管理中的應用。查看完整系列目錄了解更多內容。 引言 在…

【愚公系列】《Python網絡爬蟲從入門到精通》056-Scrapy_Redis分布式爬蟲(Scrapy-Redis 模塊)

&#x1f31f;【技術大咖愚公搬代碼&#xff1a;全棧專家的成長之路&#xff0c;你關注的寶藏博主在這里&#xff01;】&#x1f31f; &#x1f4e3;開發者圈持續輸出高質量干貨的"愚公精神"踐行者——全網百萬開發者都在追更的頂級技術博主&#xff01; &#x1f…

PyTorch基礎筆記

PyTorch張量 多維數組&#xff1a;張量可以是標量&#xff08;0D&#xff09;、向量&#xff08;1D&#xff09;、矩陣&#xff08;2D&#xff09;或更高維的數據&#xff08;3D&#xff09;。 數據類型&#xff1a;支持多種數據類型&#xff08;如 float32, int64, bool 等&a…

OSCP - Proving Grounds - Sar

主要知識點 路徑爆破cronjob 腳本劫持提權 具體步驟 依舊nmap 開始,開放了22和80端口 Nmap scan report for 192.168.192.35 Host is up (0.43s latency). Not shown: 65524 closed tcp ports (reset) PORT STATE SERVICE VERSION 22/tcp open ssh Open…

存儲/服務器內存的基本概念簡介

為什么寫這個文章&#xff1f;今天處理一個powerstore 3000T 控制器&#xff0c;控制器上電后&#xff0c;亮一下燈就很快熄滅了&#xff0c;然后embedded module上和io module不加電&#xff0c;過一整子系統自動就下電了&#xff0c;串口沒有任何輸出。剛開始判斷是主板的問題…

軟件開發指南——GUI 開發方案推薦

1. LVGL (Light and Versatile Graphics Library) 適用場景&#xff1a;嵌入式設備、資源受限環境 優勢&#xff1a; 專為嵌入式設計的開源 GUI 庫&#xff0c;內存占用極小&#xff08;最低僅需 64KB RAM&#xff09;支持觸摸屏、硬件加速&#xff08;如 STM32 的 LTDC&…