Axum 最佳實踐:如何構建優雅的 Rust 錯誤處理系統?(三)

引言

作為開發者,我們都經歷過這樣的場景:項目上線后,你打開日志監控,鋪天蓋地的 500 Internal Server Error 撲面而來。這些錯誤像個黑洞,吞噬著你的調試時間,你甚至不知道它們是從數據庫查詢失敗,還是某個第三方 API 調用超時。

更糟的是,這些錯誤未經處理,直接甩給了前端。用戶看到一個冰冷的 500 頁面,或者一個包含敏感信息的 JSON 響應,這不僅破壞了用戶體驗,更暴露了服務器的內部實現。

從異常種類來說,異常的種類有很多,有前端傳來的參數不對導致的異常,有數據庫連接超時異常,有查詢數據查不到需要返回業務異常。不同的異常,我們希望有錯誤發生時,提供更優雅的響應提示客戶。同時,也可以對不同異常設置不同的異常響應碼,使前端更方便的處理接口返回值。

Rust 開發 Web 程序時的常見痛點:

  1. 大量的 match 語句,代碼變得冗長且難以閱讀。
  2. 錯誤信息不統一,前端拿到一堆難以處理的 500 錯誤。
  3. 調試困難,服務器內部的詳細錯誤信息沒有被記錄。
  4. 如何優雅區分服務器異常與業務異常。

我們使用四招來實現優雅處理 Axum Web 應用中的錯誤處理。

本文相關源碼來自本人 Rust Axum 開發 Websocket as a Service 項目。 GitHub 地址:HTTPS://GitHub.com/BruceZhang54110/RTMate

第一招:定義統一接口響應結構

定義一個結構體 RtResponse 統一接口返回結構,代碼如下。業務成功時調用 ok_with_data 默認 code 是 200,業務失敗時,調用 err 方法。那么如何讓系統異常和業務異常都轉換為 RtResponse 呢?這時候就要看第二招了。

#[derive(Serialize, Debug)]
pub struct RtResponse<T> {code: i32,message: String,data: Option<T>,
}impl<T> RtResponse<T> {/// 創建一個帶數據的業務成功響應pub fn ok_with_data(data: T) -> Self {RtResponse {code: 200,message: 「success」.to_string(),data: Some(data),}}/// 創建一個無數據的業務成功響應pub fn ok() -> Self {RtResponse {code: 200,message: 「success」.to_string(),data: None,}}/// 創建一個業務失敗響應pub fn err(code: i32, message: &str) -> Self {RtResponse {code,message: message.to_string(),data: None,}}
}

接口返回的 JSON 結果就會是如下 JSON 格式:

{「code」: 200,「message」: 「success」,「data」: {「app_id」: 「abc」,「token」: 「eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhcHBfaWQiOiJhYmMiLCJjbGllbnRfaWQiOiJlZDU0ZDE2My1iM2EyLTRhZmMtODc4OC01MjAyODA4Yjk0OTEiLCJpYXQiOjE3NTcxNzYwNjgsImV4cCI6MTc1NzE4MzI2OH0.3LNM7jAeG3YL4jb88p4_Ew96gXvw4AoE38MBEYLvK-s」}
}
{「code」: 500,「message」: 「系統內部錯誤」,「data」: null
}

第二招:統一異常封裝

我們不能將數據庫錯誤或文件寫入錯誤信息原封不動的返回給前端,這時需要有一個統一異常處理。因此我們創建一個 ArrError 結構體:

pub struct AppError {pub code: i32,pub message: String,pub source: Option<anyhow::Error>,
}

code:業務錯誤碼,用于前端精確判斷錯誤類型。

message:對前端友好的提示信息。

source:一個 anyhow::Error,用于在日志中打印完整的錯誤鏈,這個字段永遠不會發送給前端。

Axum 提供了一個用來生成響應結果的 IntoResponse trait,一般情況下,使用 Axum 開發接口時不是必須要實現 IntoResponse trait,如果需要處理程序返回的自定義錯誤類型,這個時候則有必要使用。在這里實現方法中轉換為 RtResponse。

// Tell axum how to convert `AppError` into a response.
impl IntoResponse for AppError {fn into_response(self) -> axum::response::Response {// 使用 () 作為 T,表示沒有數據let response = RtResponse::<()> {code: self.code,message: self.message,data: None,};(StatusCode::OK, Json(response)).into_response()}}

第三招:轉換服務器異常類型

前面定義了統一異常結構體,這時候就要派上用場了。我們需求是,對于數據庫連接失敗,文件寫入失敗這些我們無法預料的錯誤,我們希望它們都統一返回 500,保證敏感錯誤信息不展示給前端的同時,在服務端有日志打印可以看到錯誤異常堆棧信息。

我們可以利用 anyhow 處理不同的錯誤,因為它能將任何實現了 std::error::Error 的類型封裝起來。anyhow 的錯誤信息如何轉換為 AppError 呢,這里我們要用到 From 與 Into,統一轉換為 code 是 500, message 是 “系統服務器異常” 的 AppError,代碼如下:

impl <E> From<E> for AppError
whereE: Into<anyhow::Error>
{fn from(value: E) -> Self {let source = value.into();tracing::error!(「Internal error: {:?}」, source);AppError {code: 500, // 500 表示服務器異常message: 「系統內部錯誤」.to_string(),source: Some(source),}}}

在這里有必要講一下 From trait ,它定義了一個類型定義如何從另一個類型創建自身,從而提供了一種非常簡單的在多種類型之間轉換的機制。標準庫中有許多此 trait 的實現,用于原始類型和常見類型的轉換。

例如,我們可以輕松地將 a 轉換 str 為 a String

let my_str = 「hello」;
let my_string = String::from(my_str);

Into trait 可以理解為 From trait 的反轉,如果一個類型實現了 From,那么編譯器會自動為它實現 Into 。

  • From 表示可以從 T 類型轉換為實現 From trait 的類型
  • Into 表示某個類型可以轉換為 T

所以在我們異常轉換的場景中就實現了 From trait ,將其他異常轉換為我們統一定義的 AppErrr**。**

impl <E> From<E> for AppError  // AppError 是目標類型
whereE: Into<anyhow::Error> // 這里被轉換類型

單元測試:

// 測試 anyhow::Error 是否能正確轉換為 AppError#[test]fn test_anyhow_error_to_app_error_conversion() {let anyhow_error = anyhow!(「數據庫連接失敗」);let app_error = AppError::from(anyhow_error);assert_eq!(app_error.code, 500);assert_eq!(app_error.message, 「系統內部錯誤」);}

執行結果:

running 1 test
test tests::test_anyhow_error_to_app_error_conversion ... oksuccesses:successes:tests::test_anyhow_error_to_app_error_conversiontest result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 1 filtered out; finished in 0.00s

這樣我們就實現了讓它們都統一返回 500,保證敏感錯誤信息不展示給前端的同時,在服務端有日志打印可以看到錯誤異常堆棧信息。

第四招:轉換自定義異常類型

我使用枚舉定義一些業務異常,這些業務異常對前端來說是重要的,所以業務異常 code 和 message 需要和系統異常區分開,讓前端清晰的判斷業務異常。

pub enum BizError {// 應用未找到AppNotFound,// 參數錯誤InvalidParams,// 非法簽名InvalidSignature,
}

為 AppError 實現 From trait,這樣業務異常也能轉換為 AppError 了,這里我們使用 match 對于不同的業務異常返回不同的 code 和 message。

impl From<BizError> for AppError {fn from(value: BizError) -> Self {match value {BizError::AppNotFound => AppError {code: 1004,message: 「您的 app 未找到,請檢查 appId」.to_string(),source: None,},BizError::InvalidParams => AppError {code: 400,message: 「參數錯誤」.to_string(),source: None,},BizError::InvalidSignature => AppError {code: 1005,message: 「簽名驗證失敗,請檢查您的請求是否合法」.to_string(),source: None,},}}
}

單元測試:

// 測試 BizError::AppNotFound 是否能正確轉換為 AppError#[test]fn test_biz_error_to_app_error_conversion() {// 創建一個 BizError 實例let biz_error = BizError::AppNotFound;let app_error = AppError::from(biz_error);assert_eq!(app_error.code, 1004);assert_eq!(app_error.message, 「您的 app 未找到,請檢查 appId」);}

執行結果:

running 1 test
test tests::test_biz_error_to_app_error_conversion ... oksuccesses:successes:tests::test_biz_error_to_app_error_conversion

那么如何使用呢 ?

fn get() -> Result<Json<RtResponse<AppAuthResult>>, AppError> {/////// 省略if signature != rt_app_param.signature {// 簽名不匹配,返回錯誤return Err(AppError::from(BizError::InvalidSignature));}/////// 省略Ok(Json(RtResponse::ok_with_data(result)))
}
let rt_app = web_context.dao.get_rt_app_by_app_id(&rt_app_param.app_id).await?.ok_or(BizError::AppNotFound))?;

這里有兩點需要注意:

  1. 第一是使用了錯誤傳播運算符? ,如果 Result 的值是 Ok,這個表達式將會返回 Ok 中的值而程序將繼續執行。如果值是 Err,Err 將作為整個函數的返回值,就好像使用了 return 關鍵字一樣,這樣錯誤值就被傳播給了調用者。

所以如果 get_rt_app_by_app_id 返回了 Error,錯誤就會立馬返回,而且因為 AppError 實現了 From trait,會自動將錯誤類型進行轉換。到了上方調用者錯誤就轉換為了 AppError。

  1. 第二,如果 get_rt_app_by_app_id 查不到返回 None,在這里使用 了 ok_or 將 None 轉換為 Err,參數是自定義的枚舉異常。如果查不到 app_id 就會返回 BizError::AppNotFound 業務異常。由于 BizError 實現了 From trait,同樣可以轉換為 AppError。

方法返回了 AppError 之后,Axum 根據我們實現的 IntoResponse 方法,將 AppError 的 code 和 message 轉換為 RtResponse 的 code 和 message,data 是 None。

總結一下,正常結果轉換為 RtResponse,業務異常先轉換為有業務自定義 code 和 message 的 AppError,AppError 轉換為 RtResponse,最后返回給前端響應。如果是系統異常,結合錯誤傳播運算符?,系統異常轉換為 AppError,AppError 轉換為 RtResponse,最后返回給前端響應。這樣我們就以一種簡潔優雅的方式統一了錯誤返回。

初次學習 Axum 開發 Web 應用,雖然 Rust 學習難度大,但是隨著學習的深入,發現類似 From trait 這種巧妙的設計,開發出簡潔優雅且性能并未打折的代碼,還有是小小的爽感的。在學習和寫項目過程中,寫技術博客幫助自己鞏固知識點,后續 Rust 相關技術文章繼續更新,歡迎點贊關注。

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

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

相關文章

MySQL高可用方案解析:從復制到云原生

MySQL 的高可用 (High Availability, HA) 方案旨在確保數據庫服務在硬件故障、軟件崩潰、網絡中斷或計劃維護時仍能持續可用&#xff0c;最小化停機時間&#xff08;通常目標為 99.9% 至 99.999% 可用性&#xff09;。以下是 MySQL 領域成熟且廣泛應用的幾種主流高可用方案&…

騰訊云語音接口實現會議系統

1.前言 在現代企業協作環境中&#xff0c;高效的會議管理是提升團隊生產力的關鍵。本文將深入解析一個完整的會議管理系統&#xff0c;涵蓋從會議創建到總結生成的完整生命周期。該系統構建一個基于AI技術的智能會議系統&#xff0c;實現會議全流程的智能化管理&#xff0c;包括…

【LeetCode 每日一題】1277. 統計全為 1 的正方形子矩陣

Problem: 1277. 統計全為 1 的正方形子矩陣 文章目錄整體思路完整代碼時空復雜度時間復雜度&#xff1a;O(m * n)空間復雜度&#xff1a;O(m * n)整體思路 這段代碼旨在解決一個經典的二維矩陣問題&#xff1a;統計全為 1 的正方形子矩陣個數 (Count Square Submatrices with …

【論文閱讀】MedResearcher-R1: 基于知識引導軌跡合成框架的專家級醫學深度研究員

論文鏈接&#xff1a;https://arxiv.org/pdf/2508.14880 【導讀】當通用大模型還在“背題庫”時&#xff0c;螞蟻集團聯合哈工大推出的 MedResearcher-R1 已把“臨床查房”搬進訓練場&#xff01;這篇 2025 年 9 月發布的論文&#xff0c;首次讓開源 32B 模型在醫學深度研究基準…

基于大語言模型的事件響應優化方案探索

程序員的技術管理推薦閱讀 當愿望遇上能力鴻溝&#xff1a;一位技術管理者眼中的團隊激勵思考 從“激勵”到“保健”&#xff1a;80后與90后程序員&#xff0c;到底想要什么&#xff1f; 從“激勵”到“保健”&#xff1a;80后與90后程序員&#xff0c;到底想要什么&#xff1f…

數字化浪潮下,傳統加工廠如何智能化轉型?

在制造業向高端化、服務化升級的今天&#xff0c;傳統加工廠正面臨前所未有的挑戰。訂單碎片化、人力成本攀升、設備OEE&#xff08;綜合效率&#xff09;長期低于50%、質量波動難以追溯……這些痛點不僅壓縮著企業利潤空間&#xff0c;更讓其在應對市場需求變化時顯得遲緩。當…

謂語動詞選擇指南

文章目錄謂語動詞的重要性謂語動詞類別一. 助動詞1. be&#xff08;am, is, are, was, were, been, being&#xff09;表示 存在、狀態、身份、特征。2. have&#xff08;have, has, had&#xff09;表示 擁有、經歷 或 完成時態的助動詞。3. do&#xff08;do, does, did&…

代碼隨想錄學習摘抄day7(二叉樹11-21)

一個樸實無華的目錄題型226.翻轉二叉樹思路&#xff1a;把每一個節點的左右孩子交換一下101. 對稱二叉樹思路&#xff1a;使用隊列來比較兩個樹&#xff08;根節點的左右子樹&#xff09;是否相互翻轉222.完全二叉樹的節點個數思路&#xff1a;本題直接就是求有多少個節點&…

Python+DRVT 從外部調用 Revit:批量創建樓板

今天繼續批量創建常用的基礎元素&#xff1a;樓板。這次以簡單的輪廓為矩形的樓板為例。讓我們來看一看如何讓Revit自動干活&#xff1a; from typing import List import math # drvt_pybind 支持多會話、多文檔&#xff0c;先從簡單的單會話、單文檔開始 # MyContext是在Pyt…

猿輔導數據分析面試題及參考答案

給定用戶成績表,編寫SQL查詢排名靠前的用戶(例如前10名),并說明rank()和dense_rank()的區別。 要查詢成績表中排名靠前的用戶(如前10名),需先明確排名依據(通常為成績降序),再通過排序和限制結果行數實現。假設用戶成績表名為user_scores,包含user_id(用戶ID)和s…

在樹莓派集群上部署 Distributed Llama (Qwen 3 14B) 詳細指南

項目地址&#xff1a;https://github.com/b4rtaz/distributed-llama 本文檔將指導您如何使用一個樹莓派5作為Root節點和三個樹莓派4作為Worker節點&#xff0c;共同搭建一個4節點的分布式LLM推理集群&#xff0c;并運行10.9GB的Qwen 3 14B模型。 中間要用到github和huggingface…

C++ 容器——unordered_xxx

自 C11 開始&#xff0c;STL 引入了基于 hash table 的 unordered_set、unordered_map 等容器&#xff0c;正如其名它們是無序容器。一定數量&#xff08;據說有測試數據是10000000&#xff09;元素時無序容器的性能要比對應的有序容器優。一、容器數據結構unordered_set、unor…

分布式常見面試題整理

一、分布式理論&#xff1a; CAP理論 分布式系統最多同時滿足一致性&#xff08;C&#xff09;、可用性&#xff08;A&#xff09;、分區容錯性&#xff08;P&#xff09;中的兩個&#xff0c;無法三者兼得。 BASE理論 對CAP中一致性和可用性的權衡&#xff0c;強調基本可用&a…

Python基礎入門常用198英語單詞詳解

最近&#xff0c;我總結了一份Python學習者入門常用單詞表&#xff0c;列出了Python學習中常見的198個高頻單詞&#xff0c;供初學者學習使用。 這些單詞都比較簡單&#xff0c;非常易于理解&#xff0c;在掌握好單詞的基礎上&#xff0c;再去學Python可以達到事半功倍的效果。…

EP-SPY 網路追蹤規避實驗:山脈通聯測試

EP-SPY V3.0 https://github.com/MartinxMax/ep-spy 基於 GI6E 編碼的無線電通信工具&#xff0c;用於保護您的隱私。 https://github.com/MartinxMax/gi6e 編寫了偽協議以防止內容被解密無法通過網絡追蹤&#xff0c;抵抗官方監控無線音頻廣播&#xff0c;用於隱蔽信息傳輸…

蘋果 FoundationModels 秘典俠客行:隱私為先的端側 AI 江湖

引子 話說俠客島之上&#xff0c;有一對年輕俠侶 ——「青鋒劍客」凌云與「素心仙子」蘇凝&#xff0c;二人自幼習武&#xff0c;尤擅拆解各路奇功秘籍。 近日聽聞蘋果谷&#xff08;Apple&#xff09;于 WWDC 2025 武林大會之上&#xff0c;亮出一門全新絕學「FoundationMod…

華為基于IPD的產品質量計劃模板

目錄 模板:產品質量計劃模板....................................... 1 1. 介紹...................................................................... 5 1.1. 范圍和目的.................................................... 5 1.2. 參考資料..…

事務管理的選擇:為何 @Transactional 并非萬能,TransactionTemplate 更值得信賴

在 Spring 生態的后端開發中&#xff0c;事務管理是保障數據一致性的核心環節。開發者常常會使用 Transactional 注解快速開啟事務&#xff0c;一行代碼似乎就能解決問題。但隨著業務復雜度提升&#xff0c;這種“簡單”的背后往往隱藏著難以察覺的隱患。本文將深入剖析 Spring…

CodePerfAI體驗:AI代碼性能分析工具如何高效排查性能瓶頸、優化SQL執行耗時?

前陣子幫同事排查用戶下單接口的性能問題時&#xff0c;我算是真切感受到 “找性能瓶頸比寫代碼還磨人”—— 接口偶爾會突然卡到 3 秒以上&#xff0c;查日志只看到 “SQL 執行耗時過長”&#xff0c;但具體是哪個查詢慢、為什么慢&#xff0c;翻了半天監控也沒頭緒&#xff0…

《sklearn機器學習——繪制分數以評估模型》驗證曲線、學習曲線

估計器的偏差、方差和噪聲 每一個估計器都有其優勢和劣勢。它的泛化誤差可以分解為偏差、方差和噪聲。估計器的偏差是不同訓練集的平均誤差。估計器的方差表示對不同訓練集&#xff0c;模型的敏感度。噪聲是數據的特質。 在下圖中&#xff0c;可以看見一個函數 f(x)cos?32πxf…