# UdminAI 错误处理模块文档 ## 概述 UdminAI 项目的错误处理模块提供了统一的错误定义、处理和响应机制。该模块基于 Rust 的 `Result` 类型和 `thiserror` 库构建,确保错误信息的一致性、可追踪性和用户友好性。 ## 设计原则 ### 核心理念 - **统一性**: 所有模块使用统一的错误类型 - **可追踪性**: 错误包含足够的上下文信息 - **用户友好**: 面向用户的错误消息清晰易懂 - **开发友好**: 面向开发者的错误信息详细准确 - **类型安全**: 编译时错误类型检查 ### 错误分层 1. **应用层错误**: 业务逻辑错误 2. **服务层错误**: 服务调用错误 3. **数据层错误**: 数据库和缓存错误 4. **网络层错误**: HTTP 和网络通信错误 5. **系统层错误**: 系统资源和配置错误 ## 错误类型定义 (error.rs) ### 主要错误类型 ```rust use axum::{ http::StatusCode, response::{IntoResponse, Response}, Json, }; use serde::{Deserialize, Serialize}; use std::fmt; use thiserror::Error; use tracing::error; /// 应用主错误类型 #[derive(Error, Debug, Clone, Serialize, Deserialize)] pub enum AppError { // 认证和授权错误 #[error("认证失败: {message}")] AuthenticationFailed { message: String }, #[error("授权失败: {message}")] AuthorizationFailed { message: String }, #[error("令牌无效: {message}")] InvalidToken { message: String }, #[error("令牌已过期")] TokenExpired, // 验证错误 #[error("验证失败: {field} - {message}")] ValidationFailed { field: String, message: String }, #[error("请求参数无效: {message}")] InvalidRequest { message: String }, #[error("必需字段缺失: {field}")] MissingField { field: String }, // 资源错误 #[error("资源未找到: {resource}")] NotFound(String), #[error("资源已存在: {resource}")] AlreadyExists(String), #[error("资源冲突: {message}")] Conflict { message: String }, // 业务逻辑错误 #[error("业务规则违反: {message}")] BusinessRuleViolation { message: String }, #[error("操作不被允许: {message}")] OperationNotAllowed { message: String }, #[error("状态无效: 当前状态 {current}, 期望状态 {expected}")] InvalidState { current: String, expected: String }, // 数据库错误 #[error("数据库错误: {0}")] DatabaseError(String), #[error("数据库连接失败: {message}")] DatabaseConnectionFailed { message: String }, #[error("事务失败: {message}")] TransactionFailed { message: String }, // 缓存错误 #[error("缓存错误: {0}")] CacheError(String), #[error("缓存连接失败: {message}")] CacheConnectionFailed { message: String }, // 网络和外部服务错误 #[error("网络错误: {message}")] NetworkError { message: String }, #[error("外部服务错误: {service} - {message}")] ExternalServiceError { service: String, message: String }, #[error("HTTP 请求失败: {status} - {message}")] HttpRequestFailed { status: u16, message: String }, // 文件和 I/O 错误 #[error("文件操作失败: {message}")] FileOperationFailed { message: String }, #[error("文件未找到: {path}")] FileNotFound { path: String }, #[error("文件权限不足: {path}")] FilePermissionDenied { path: String }, // 配置和环境错误 #[error("配置错误: {message}")] ConfigurationError { message: String }, #[error("环境变量缺失: {variable}")] MissingEnvironmentVariable { variable: String }, // 序列化和反序列化错误 #[error("序列化失败: {message}")] SerializationFailed { message: String }, #[error("反序列化失败: {message}")] DeserializationFailed { message: String }, #[error("JSON 格式错误: {message}")] JsonFormatError { message: String }, // 流程引擎错误 #[error("流程执行失败: {flow_id} - {message}")] FlowExecutionFailed { flow_id: String, message: String }, #[error("流程解析失败: {message}")] FlowParsingFailed { message: String }, #[error("节点执行失败: {node_id} - {message}")] NodeExecutionFailed { node_id: String, message: String }, // 调度任务错误 #[error("任务调度失败: {job_id} - {message}")] JobSchedulingFailed { job_id: String, message: String }, #[error("Cron 表达式无效: {expression}")] InvalidCronExpression { expression: String }, #[error("任务执行超时: {job_id}")] JobExecutionTimeout { job_id: String }, // 系统错误 #[error("内部服务器错误: {message}")] InternalServerError { message: String }, #[error("服务不可用: {message}")] ServiceUnavailable { message: String }, #[error("请求超时: {message}")] RequestTimeout { message: String }, #[error("资源耗尽: {resource}")] ResourceExhausted { resource: String }, // 限流和安全错误 #[error("请求频率过高: {message}")] RateLimitExceeded { message: String }, #[error("请求体过大: 当前大小 {current}, 最大允许 {max}")] PayloadTooLarge { current: usize, max: usize }, #[error("不支持的媒体类型: {media_type}")] UnsupportedMediaType { media_type: String }, } /// 错误响应结构 #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ErrorResponse { pub error: ErrorDetail, pub timestamp: chrono::DateTime, pub request_id: Option, } /// 错误详情 #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ErrorDetail { pub code: String, pub message: String, pub details: Option, pub field: Option, } impl AppError { /// 获取错误代码 pub fn error_code(&self) -> &'static str { match self { // 认证和授权 AppError::AuthenticationFailed { .. } => "AUTH_FAILED", AppError::AuthorizationFailed { .. } => "AUTHORIZATION_FAILED", AppError::InvalidToken { .. } => "INVALID_TOKEN", AppError::TokenExpired => "TOKEN_EXPIRED", // 验证 AppError::ValidationFailed { .. } => "VALIDATION_FAILED", AppError::InvalidRequest { .. } => "INVALID_REQUEST", AppError::MissingField { .. } => "MISSING_FIELD", // 资源 AppError::NotFound(_) => "NOT_FOUND", AppError::AlreadyExists(_) => "ALREADY_EXISTS", AppError::Conflict { .. } => "CONFLICT", // 业务逻辑 AppError::BusinessRuleViolation { .. } => "BUSINESS_RULE_VIOLATION", AppError::OperationNotAllowed { .. } => "OPERATION_NOT_ALLOWED", AppError::InvalidState { .. } => "INVALID_STATE", // 数据库 AppError::DatabaseError(_) => "DATABASE_ERROR", AppError::DatabaseConnectionFailed { .. } => "DATABASE_CONNECTION_FAILED", AppError::TransactionFailed { .. } => "TRANSACTION_FAILED", // 缓存 AppError::CacheError(_) => "CACHE_ERROR", AppError::CacheConnectionFailed { .. } => "CACHE_CONNECTION_FAILED", // 网络 AppError::NetworkError { .. } => "NETWORK_ERROR", AppError::ExternalServiceError { .. } => "EXTERNAL_SERVICE_ERROR", AppError::HttpRequestFailed { .. } => "HTTP_REQUEST_FAILED", // 文件 AppError::FileOperationFailed { .. } => "FILE_OPERATION_FAILED", AppError::FileNotFound { .. } => "FILE_NOT_FOUND", AppError::FilePermissionDenied { .. } => "FILE_PERMISSION_DENIED", // 配置 AppError::ConfigurationError { .. } => "CONFIGURATION_ERROR", AppError::MissingEnvironmentVariable { .. } => "MISSING_ENV_VAR", // 序列化 AppError::SerializationFailed { .. } => "SERIALIZATION_FAILED", AppError::DeserializationFailed { .. } => "DESERIALIZATION_FAILED", AppError::JsonFormatError { .. } => "JSON_FORMAT_ERROR", // 流程引擎 AppError::FlowExecutionFailed { .. } => "FLOW_EXECUTION_FAILED", AppError::FlowParsingFailed { .. } => "FLOW_PARSING_FAILED", AppError::NodeExecutionFailed { .. } => "NODE_EXECUTION_FAILED", // 调度任务 AppError::JobSchedulingFailed { .. } => "JOB_SCHEDULING_FAILED", AppError::InvalidCronExpression { .. } => "INVALID_CRON_EXPRESSION", AppError::JobExecutionTimeout { .. } => "JOB_EXECUTION_TIMEOUT", // 系统 AppError::InternalServerError { .. } => "INTERNAL_SERVER_ERROR", AppError::ServiceUnavailable { .. } => "SERVICE_UNAVAILABLE", AppError::RequestTimeout { .. } => "REQUEST_TIMEOUT", AppError::ResourceExhausted { .. } => "RESOURCE_EXHAUSTED", // 限流和安全 AppError::RateLimitExceeded { .. } => "RATE_LIMIT_EXCEEDED", AppError::PayloadTooLarge { .. } => "PAYLOAD_TOO_LARGE", AppError::UnsupportedMediaType { .. } => "UNSUPPORTED_MEDIA_TYPE", } } /// 获取 HTTP 状态码 pub fn status_code(&self) -> StatusCode { match self { // 4xx 客户端错误 AppError::AuthenticationFailed { .. } => StatusCode::UNAUTHORIZED, AppError::AuthorizationFailed { .. } => StatusCode::FORBIDDEN, AppError::InvalidToken { .. } => StatusCode::UNAUTHORIZED, AppError::TokenExpired => StatusCode::UNAUTHORIZED, AppError::ValidationFailed { .. } => StatusCode::BAD_REQUEST, AppError::InvalidRequest { .. } => StatusCode::BAD_REQUEST, AppError::MissingField { .. } => StatusCode::BAD_REQUEST, AppError::NotFound(_) => StatusCode::NOT_FOUND, AppError::AlreadyExists(_) => StatusCode::CONFLICT, AppError::Conflict { .. } => StatusCode::CONFLICT, AppError::BusinessRuleViolation { .. } => StatusCode::BAD_REQUEST, AppError::OperationNotAllowed { .. } => StatusCode::FORBIDDEN, AppError::InvalidState { .. } => StatusCode::BAD_REQUEST, AppError::FileNotFound { .. } => StatusCode::NOT_FOUND, AppError::FilePermissionDenied { .. } => StatusCode::FORBIDDEN, AppError::JsonFormatError { .. } => StatusCode::BAD_REQUEST, AppError::InvalidCronExpression { .. } => StatusCode::BAD_REQUEST, AppError::RateLimitExceeded { .. } => StatusCode::TOO_MANY_REQUESTS, AppError::PayloadTooLarge { .. } => StatusCode::PAYLOAD_TOO_LARGE, AppError::UnsupportedMediaType { .. } => StatusCode::UNSUPPORTED_MEDIA_TYPE, // 5xx 服务器错误 AppError::DatabaseError(_) => StatusCode::INTERNAL_SERVER_ERROR, AppError::DatabaseConnectionFailed { .. } => StatusCode::SERVICE_UNAVAILABLE, AppError::TransactionFailed { .. } => StatusCode::INTERNAL_SERVER_ERROR, AppError::CacheError(_) => StatusCode::INTERNAL_SERVER_ERROR, AppError::CacheConnectionFailed { .. } => StatusCode::SERVICE_UNAVAILABLE, AppError::NetworkError { .. } => StatusCode::BAD_GATEWAY, AppError::ExternalServiceError { .. } => StatusCode::BAD_GATEWAY, AppError::HttpRequestFailed { .. } => StatusCode::BAD_GATEWAY, AppError::FileOperationFailed { .. } => StatusCode::INTERNAL_SERVER_ERROR, AppError::ConfigurationError { .. } => StatusCode::INTERNAL_SERVER_ERROR, AppError::MissingEnvironmentVariable { .. } => StatusCode::INTERNAL_SERVER_ERROR, AppError::SerializationFailed { .. } => StatusCode::INTERNAL_SERVER_ERROR, AppError::DeserializationFailed { .. } => StatusCode::INTERNAL_SERVER_ERROR, AppError::FlowExecutionFailed { .. } => StatusCode::INTERNAL_SERVER_ERROR, AppError::FlowParsingFailed { .. } => StatusCode::BAD_REQUEST, AppError::NodeExecutionFailed { .. } => StatusCode::INTERNAL_SERVER_ERROR, AppError::JobSchedulingFailed { .. } => StatusCode::INTERNAL_SERVER_ERROR, AppError::JobExecutionTimeout { .. } => StatusCode::REQUEST_TIMEOUT, AppError::InternalServerError { .. } => StatusCode::INTERNAL_SERVER_ERROR, AppError::ServiceUnavailable { .. } => StatusCode::SERVICE_UNAVAILABLE, AppError::RequestTimeout { .. } => StatusCode::REQUEST_TIMEOUT, AppError::ResourceExhausted { .. } => StatusCode::SERVICE_UNAVAILABLE, } } /// 获取错误字段(如果适用) pub fn error_field(&self) -> Option { match self { AppError::ValidationFailed { field, .. } => Some(field.clone()), AppError::MissingField { field } => Some(field.clone()), _ => None, } } /// 是否为客户端错误 pub fn is_client_error(&self) -> bool { self.status_code().is_client_error() } /// 是否为服务器错误 pub fn is_server_error(&self) -> bool { self.status_code().is_server_error() } /// 创建错误响应 pub fn to_error_response(&self, request_id: Option) -> ErrorResponse { ErrorResponse { error: ErrorDetail { code: self.error_code().to_string(), message: self.to_string(), details: None, field: self.error_field(), }, timestamp: chrono::Utc::now(), request_id, } } /// 记录错误日志 pub fn log_error(&self, request_id: Option<&str>) { let level = if self.is_server_error() { tracing::Level::ERROR } else { tracing::Level::WARN }; match level { tracing::Level::ERROR => { error!( target = "udmin", error_code = %self.error_code(), error_message = %self, request_id = ?request_id, "application.error.server" ); } _ => { tracing::warn!( target = "udmin", error_code = %self.error_code(), error_message = %self, request_id = ?request_id, "application.error.client" ); } } } } /// 实现 IntoResponse,使错误可以直接作为 HTTP 响应返回 impl IntoResponse for AppError { fn into_response(self) -> Response { // 从请求上下文获取 request_id(实际实现中可能需要通过中间件传递) let request_id = None; // 这里应该从上下文获取 // 记录错误日志 self.log_error(request_id.as_deref()); // 创建错误响应 let error_response = self.to_error_response(request_id); let status_code = self.status_code(); (status_code, Json(error_response)).into_response() } } /// 应用结果类型别名 pub type AppResult = Result; /// 错误转换实现 impl From for AppError { fn from(err: sea_orm::DbErr) -> Self { match err { sea_orm::DbErr::RecordNotFound(_) => AppError::NotFound("记录不存在".to_string()), sea_orm::DbErr::Custom(msg) => AppError::DatabaseError(msg), sea_orm::DbErr::Conn(msg) => AppError::DatabaseConnectionFailed { message: msg }, sea_orm::DbErr::Exec(msg) => AppError::DatabaseError(msg), sea_orm::DbErr::Query(msg) => AppError::DatabaseError(msg), _ => AppError::DatabaseError("未知数据库错误".to_string()), } } } impl From for AppError { fn from(err: redis::RedisError) -> Self { match err.kind() { redis::ErrorKind::IoError => AppError::CacheConnectionFailed { message: err.to_string(), }, redis::ErrorKind::AuthenticationFailed => AppError::CacheConnectionFailed { message: "Redis 认证失败".to_string(), }, _ => AppError::CacheError(err.to_string()), } } } impl From for AppError { fn from(err: reqwest::Error) -> Self { if err.is_timeout() { AppError::RequestTimeout { message: "HTTP 请求超时".to_string(), } } else if err.is_connect() { AppError::NetworkError { message: "网络连接失败".to_string(), } } else if let Some(status) = err.status() { AppError::HttpRequestFailed { status: status.as_u16(), message: err.to_string(), } } else { AppError::NetworkError { message: err.to_string(), } } } } impl From for AppError { fn from(err: serde_json::Error) -> Self { if err.is_syntax() { AppError::JsonFormatError { message: "JSON 语法错误".to_string(), } } else if err.is_data() { AppError::DeserializationFailed { message: err.to_string(), } } else { AppError::SerializationFailed { message: err.to_string(), } } } } impl From for AppError { fn from(err: std::io::Error) -> Self { match err.kind() { std::io::ErrorKind::NotFound => AppError::FileNotFound { path: "未知路径".to_string(), }, std::io::ErrorKind::PermissionDenied => AppError::FilePermissionDenied { path: "未知路径".to_string(), }, _ => AppError::FileOperationFailed { message: err.to_string(), }, } } } impl From for AppError { fn from(_: tokio::time::error::Elapsed) -> Self { AppError::RequestTimeout { message: "操作超时".to_string(), } } } /// 错误构建器 pub struct ErrorBuilder { error: AppError, } impl ErrorBuilder { pub fn new(error: AppError) -> Self { Self { error } } pub fn with_details(mut self, details: serde_json::Value) -> Self { // 这里可以扩展错误以包含更多详情 self } pub fn with_field(mut self, field: String) -> Self { // 设置错误字段 self } pub fn build(self) -> AppError { self.error } } /// 错误宏 #[macro_export] macro_rules! app_error { ($error_type:ident, $($field:ident = $value:expr),*) => { AppError::$error_type { $($field: $value.into()),* } }; } /// 结果扩展 trait pub trait ResultExt { /// 将错误转换为 AppError fn map_app_error(self, f: F) -> AppResult where F: FnOnce() -> AppError; /// 添加错误上下文 fn with_context(self, f: F) -> AppResult where F: FnOnce() -> String; } impl ResultExt for Result where E: Into, { fn map_app_error(self, f: F) -> AppResult where F: FnOnce() -> AppError, { self.map_err(|_| f()) } fn with_context(self, f: F) -> AppResult where F: FnOnce() -> String, { self.map_err(|e| { let context = f(); match e.into() { AppError::InternalServerError { message } => AppError::InternalServerError { message: format!("{}: {}", context, message), }, other => other, } }) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_error_codes() { let error = AppError::NotFound("用户".to_string()); assert_eq!(error.error_code(), "NOT_FOUND"); assert_eq!(error.status_code(), StatusCode::NOT_FOUND); } #[test] fn test_error_response() { let error = AppError::ValidationFailed { field: "email".to_string(), message: "格式无效".to_string(), }; let response = error.to_error_response(Some("req-123".to_string())); assert_eq!(response.error.code, "VALIDATION_FAILED"); assert_eq!(response.error.field, Some("email".to_string())); assert_eq!(response.request_id, Some("req-123".to_string())); } #[test] fn test_error_conversion() { let db_error = sea_orm::DbErr::RecordNotFound("test".to_string()); let app_error: AppError = db_error.into(); match app_error { AppError::NotFound(_) => assert!(true), _ => assert!(false, "Expected NotFound error"), } } #[test] fn test_error_macro() { let error = app_error!(ValidationFailed, field = "username", message = "用户名已存在" ); match error { AppError::ValidationFailed { field, message } => { assert_eq!(field, "username"); assert_eq!(message, "用户名已存在"); } _ => assert!(false, "Expected ValidationFailed error"), } } #[test] fn test_result_ext() { let result: Result = Err("test error"); let app_result = result.map_app_error(|| AppError::InternalServerError { message: "转换错误".to_string(), }); assert!(app_result.is_err()); } } ``` ## 错误处理策略 ### 错误传播 ```rust /// 错误传播示例 pub async fn create_user(req: CreateUserReq) -> AppResult { // 验证请求 validate_create_user_request(&req)?; // 检查用户是否已存在 if user_exists(&req.email).await? { return Err(AppError::AlreadyExists("用户邮箱已存在".to_string())); } // 创建用户 let user = User::create(req).await .with_context(|| "创建用户失败")?; Ok(user.into()) } ``` ### 错误恢复 ```rust /// 错误恢复示例 pub async fn get_user_with_fallback(id: &str) -> AppResult { // 首先尝试从缓存获取 match get_user_from_cache(id).await { Ok(user) => return Ok(user), Err(AppError::CacheError(_)) => { // 缓存错误,尝试从数据库获取 tracing::warn!("缓存获取用户失败,尝试数据库"); } Err(e) => return Err(e), } // 从数据库获取 let user = get_user_from_db(id).await?; // 尝试更新缓存(忽略错误) if let Err(e) = set_user_cache(id, &user).await { tracing::warn!(error = %e, "更新用户缓存失败"); } Ok(user) } ``` ### 错误聚合 ```rust /// 错误聚合示例 pub struct ValidationErrors { pub errors: Vec, } impl ValidationErrors { pub fn new() -> Self { Self { errors: Vec::new() } } pub fn add(&mut self, error: AppError) { self.errors.push(error); } pub fn is_empty(&self) -> bool { self.errors.is_empty() } pub fn into_result(self) -> AppResult<()> { if self.errors.is_empty() { Ok(()) } else { // 返回第一个错误,或者可以创建一个聚合错误类型 Err(self.errors.into_iter().next().unwrap()) } } } pub fn validate_user_data(data: &CreateUserReq) -> AppResult<()> { let mut errors = ValidationErrors::new(); if data.email.is_empty() { errors.add(AppError::MissingField { field: "email".to_string() }); } if data.password.len() < 8 { errors.add(AppError::ValidationFailed { field: "password".to_string(), message: "密码长度至少8位".to_string(), }); } errors.into_result() } ``` ## 中间件集成 ### 错误处理中间件 ```rust use axum::{ extract::Request, middleware::Next, response::Response, }; use uuid::Uuid; /// 错误处理中间件 pub async fn error_handler_middleware( mut request: Request, next: Next, ) -> Response { // 生成请求 ID let request_id = Uuid::new_v4().to_string(); request.extensions_mut().insert(request_id.clone()); // 执行请求 let response = next.run(request).await; // 如果响应是错误,添加请求 ID if response.status().is_client_error() || response.status().is_server_error() { // 这里可以修改响应头,添加请求 ID tracing::info!( target = "udmin", request_id = %request_id, status = %response.status(), "request.completed.error" ); } response } ``` ## 监控和告警 ### 错误指标收集 ```rust use prometheus::{Counter, Histogram, Registry}; use std::sync::Arc; /// 错误指标收集器 #[derive(Clone)] pub struct ErrorMetrics { error_counter: Counter, error_duration: Histogram, } impl ErrorMetrics { pub fn new(registry: &Registry) -> Self { let error_counter = Counter::new( "app_errors_total", "Total number of application errors" ).unwrap(); let error_duration = Histogram::new( "error_handling_duration_seconds", "Time spent handling errors" ).unwrap(); registry.register(Box::new(error_counter.clone())).unwrap(); registry.register(Box::new(error_duration.clone())).unwrap(); Self { error_counter, error_duration, } } pub fn record_error(&self, error: &AppError) { self.error_counter.inc(); // 可以根据错误类型添加标签 tracing::info!( target = "udmin", error_code = %error.error_code(), error_type = %std::any::type_name::(), "metrics.error.recorded" ); } } ``` ## 最佳实践 ### 错误设计原则 1. **明确性**: 错误消息应该清楚地说明发生了什么 2. **可操作性**: 错误消息应该告诉用户如何解决问题 3. **一致性**: 相同类型的错误应该有一致的格式和处理方式 4. **安全性**: 不要在错误消息中泄露敏感信息 ### 错误处理模式 1. **快速失败**: 尽早检测和报告错误 2. **优雅降级**: 在可能的情况下提供备选方案 3. **错误隔离**: 防止错误在系统中传播 4. **错误恢复**: 在适当的时候尝试从错误中恢复 ### 日志记录 1. **结构化日志**: 使用结构化格式记录错误信息 2. **上下文信息**: 包含足够的上下文信息用于调试 3. **敏感信息**: 避免在日志中记录敏感信息 4. **日志级别**: 根据错误严重程度选择合适的日志级别 ## 总结 UdminAI 的错误处理模块提供了完整的错误管理解决方案,具有以下特点: - **类型安全**: 编译时错误类型检查 - **统一处理**: 所有错误使用统一的类型和格式 - **用户友好**: 清晰的错误消息和适当的 HTTP 状态码 - **可观测性**: 完整的错误日志和指标收集 - **可扩展性**: 易于添加新的错误类型和处理逻辑 通过合理的错误处理设计,确保了系统的稳定性、可维护性和用户体验。