docs: 添加项目文档包括总览、架构、流程引擎和服务层
新增以下文档文件: - PROJECT_OVERVIEW.md 项目总览文档 - BACKEND_ARCHITECTURE.md 后端架构文档 - FRONTEND_ARCHITECTURE.md 前端架构文档 - FLOW_ENGINE.md 流程引擎文档 - SERVICES.md 服务层文档 - ERROR_HANDLING.md 错误处理模块文档 文档内容涵盖项目整体介绍、技术架构、核心模块设计和实现细节
This commit is contained in:
878
docs/ERROR_HANDLING.md
Normal file
878
docs/ERROR_HANDLING.md
Normal file
@ -0,0 +1,878 @@
|
||||
# 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<chrono::Utc>,
|
||||
pub request_id: Option<String>,
|
||||
}
|
||||
|
||||
/// 错误详情
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ErrorDetail {
|
||||
pub code: String,
|
||||
pub message: String,
|
||||
pub details: Option<serde_json::Value>,
|
||||
pub field: Option<String>,
|
||||
}
|
||||
|
||||
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<String> {
|
||||
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<String>) -> 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<T> = Result<T, AppError>;
|
||||
|
||||
/// 错误转换实现
|
||||
impl From<sea_orm::DbErr> 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<redis::RedisError> 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<reqwest::Error> 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<serde_json::Error> 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<std::io::Error> 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<tokio::time::error::Elapsed> 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<T> {
|
||||
/// 将错误转换为 AppError
|
||||
fn map_app_error<F>(self, f: F) -> AppResult<T>
|
||||
where
|
||||
F: FnOnce() -> AppError;
|
||||
|
||||
/// 添加错误上下文
|
||||
fn with_context<F>(self, f: F) -> AppResult<T>
|
||||
where
|
||||
F: FnOnce() -> String;
|
||||
}
|
||||
|
||||
impl<T, E> ResultExt<T> for Result<T, E>
|
||||
where
|
||||
E: Into<AppError>,
|
||||
{
|
||||
fn map_app_error<F>(self, f: F) -> AppResult<T>
|
||||
where
|
||||
F: FnOnce() -> AppError,
|
||||
{
|
||||
self.map_err(|_| f())
|
||||
}
|
||||
|
||||
fn with_context<F>(self, f: F) -> AppResult<T>
|
||||
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<i32, &str> = 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<UserDoc> {
|
||||
// 验证请求
|
||||
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<UserDoc> {
|
||||
// 首先尝试从缓存获取
|
||||
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<AppError>,
|
||||
}
|
||||
|
||||
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::<AppError>(),
|
||||
"metrics.error.recorded"
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 错误设计原则
|
||||
|
||||
1. **明确性**: 错误消息应该清楚地说明发生了什么
|
||||
2. **可操作性**: 错误消息应该告诉用户如何解决问题
|
||||
3. **一致性**: 相同类型的错误应该有一致的格式和处理方式
|
||||
4. **安全性**: 不要在错误消息中泄露敏感信息
|
||||
|
||||
### 错误处理模式
|
||||
|
||||
1. **快速失败**: 尽早检测和报告错误
|
||||
2. **优雅降级**: 在可能的情况下提供备选方案
|
||||
3. **错误隔离**: 防止错误在系统中传播
|
||||
4. **错误恢复**: 在适当的时候尝试从错误中恢复
|
||||
|
||||
### 日志记录
|
||||
|
||||
1. **结构化日志**: 使用结构化格式记录错误信息
|
||||
2. **上下文信息**: 包含足够的上下文信息用于调试
|
||||
3. **敏感信息**: 避免在日志中记录敏感信息
|
||||
4. **日志级别**: 根据错误严重程度选择合适的日志级别
|
||||
|
||||
## 总结
|
||||
|
||||
UdminAI 的错误处理模块提供了完整的错误管理解决方案,具有以下特点:
|
||||
|
||||
- **类型安全**: 编译时错误类型检查
|
||||
- **统一处理**: 所有错误使用统一的类型和格式
|
||||
- **用户友好**: 清晰的错误消息和适当的 HTTP 状态码
|
||||
- **可观测性**: 完整的错误日志和指标收集
|
||||
- **可扩展性**: 易于添加新的错误类型和处理逻辑
|
||||
|
||||
通过合理的错误处理设计,确保了系统的稳定性、可维护性和用户体验。
|
||||
Reference in New Issue
Block a user