diff --git a/Cargo.toml b/Cargo.toml index e7b2240..20d6a4d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "common-telemetry" -version = "0.1.1" +version = "0.1.2" edition = "2024" description = "Microservice infrastructure library" diff --git a/README.md b/README.md index 901bb1f..fa6c1f3 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Microservice Common Lib (Rust) -这是微服务架构的通用基础库,旨在统一所有服务的错误处理标准、日志格式以及分布式链路追踪。 +这是微服务架构的通用基础库 (`common-telemetry`),旨在统一所有服务的错误处理标准、日志格式以及分布式链路追踪。 ## ✨ 核心特性 @@ -8,12 +8,14 @@ * 基于 `thiserror` 和 `anyhow` 的最佳实践。 * **双 Token 支持**: 明确区分 `AccessTokenExpired` (20001) 和 `RefreshTokenExpired` (20002)。 * **Axum 集成**: 实现了 `IntoResponse`,自动将错误转换为标准 JSON 格式并设置正确的 HTTP 状态码。 + * **第三方库适配**: 提供了 `sqlx`, `redis`, `validator` 的可选集成。 + * *智能转换*: 例如 `sqlx::Error::RowNotFound` 会自动转换为 **404 Not Found**,而不是 500 Database Error。 * **可观测性 (Telemetry)**: * 基于 `tracing` 生态。 * 支持 **JSON 结构化日志** (适配 ELK/Loki)。 * 支持 **控制台 + 文件双写**。 * 支持 **非阻塞 (Non-blocking)** 异步日志写入与按天滚动。 -* **模块化设计**: 通过 Feature Flags 按需引入。 +* **模块化设计**: 通过 Feature Flags 按需引入,保持依赖轻量。 --- @@ -90,21 +92,21 @@ git push -u origin main ### 1. 引入依赖 #### 方式 A: 通过 Kellnr 引入 (如果已发布) -在 `user-service` 的 `Cargo.toml` 中: +在 `user-service` 的 `Cargo.toml` 中,你可以根据需要开启 `sqlx` 或 `redis` 支持: ```toml [dependencies] -# 指定 registry -common-telemetry = { version = "0.1", registry = "kellnr", features = ["full"] } +# 引入基础功能 + SQLX 支持 +common-telemetry = { version = "0.1", registry = "kellnr", features = ["with-sqlx", "with-validator"] } + +# 注意:你需要确保服务本身引用的 sqlx 版本与 common-telemetry 兼容 +sqlx = { version = "0.7", features = ["postgres", "runtime-tokio"] } ``` *注意:使用此方式,`user-service` 项目也需要在 `.cargo/config.toml` 中配置 registry 地址。* -#### 方式 B: 通过 Gitea (Git) 引入 (简单直接) -在 `user-service` 的 `Cargo.toml` 中: - +#### 方式 B: 通过 Gitea (Git) 引入 ```toml [dependencies] -# 指定 git 地址 common-telemetry = { git = "ssh://git@gitea.shay7sev.site:2222/admin/common-telemetry.git", branch = "main", features = ["full"] } ``` @@ -137,25 +139,39 @@ async fn main() { } ``` -#### B. 错误处理与响应 (Handler) +#### B. 错误处理与第三方库集成 (Handler) + +由于实现了 `From`,你可以直接使用 `?` 操作符,库会自动处理类型转换和 HTTP 映射。 ```rust use axum::{Json, response::IntoResponse}; use common_telemetry::AppError; // 引入统一错误 +use validator::Validate; -async fn get_profile(token: String) -> Result, AppError> { - // 模拟:Token 过期 - if token_is_expired(&token) { - // 直接返回枚举,库会自动处理为 JSON Response (Code: 20001) - // 并且会自动打印 Error 级别的日志 - return Err(AppError::AccessTokenExpired); +#[derive(serde::Deserialize, Validate)] +struct CreateUserReq { + #[validate(email)] + email: String, +} + +async fn create_user( + Json(payload): Json, +) -> Result, AppError> { + // 1. 校验错误 (自动转 AppError::ValidationError -> HTTP 400) + payload.validate()?; + + // 2. 数据库查询 (自动转 AppError::DbError -> HTTP 500) + // 特殊情况:如果是 RowNotFound,会自动转为 AppError::NotFound -> HTTP 404 + let _user = sqlx::query!("SELECT * FROM users WHERE email = $1", payload.email) + .fetch_optional(&pool) + .await?; + + // 3. 业务逻辑错误 (手动返回) + if _user.is_some() { + return Err(AppError::AlreadyExists(format!("User {} already exists", payload.email))); } - - // 模拟:数据库错误 - let user = db::find_user().await - .map_err(|e| AppError::DbError(e.to_string()))?; - Ok(Json(user)) + Ok(Json("Created".into())) } ``` @@ -163,33 +179,47 @@ async fn get_profile(token: String) -> Result, AppError> { ## ⚙️ 功能模块说明 (Feature Flags) -为了减少编译时间和依赖大小,你可以按需开启功能: +本库采用高度模块化设计,建议按需开启 Feature 以减少编译体积: | Feature | 说明 | 包含依赖 | | :--- | :--- | :--- | -| **`default`** | 默认开启所有功能 | `full` | -| **`full`** | 全功能集合 | `error`, `telemetry` | -| **`error`** | 仅使用错误处理与 HTTP 响应 | `thiserror`, `axum`, `serde` | -| **`telemetry`** | 仅使用日志与链路追踪 | `tracing`, `tracing-subscriber`, `tracing-appender` | +| **`default`** | 默认开启全功能 | `full` | +| **`full`** | 包含基础功能及所有第三方集成 | `error`, `telemetry`, `with-sqlx`, `with-redis`, `with-anyhow`, `with-validator` | +| **`error`** | 仅使用基础错误处理 | `thiserror`, `axum`, `serde` | +| **`telemetry`** | 仅使用日志与链路追踪 | `tracing` 全家桶 | +| **`with-sqlx`** | 集成 `sqlx` 错误转换 | `sqlx` (自动处理 RowNotFound) | +| **`with-redis`** | 集成 `redis` 错误转换 | `redis` | +| **`with-validator`** | 集成 `validator` 错误转换 | `validator` | +| **`with-anyhow`** | 集成 `anyhow` 兜底错误 | `anyhow` | -**示例 (只用日志模块):** +**示例 (只用错误 + SQLX支持):** ```toml -common-telemetry = { version = "0.1", default-features = false, features = ["telemetry"] } +common-telemetry = { version = "0.1", default-features = false, features = ["error", "with-sqlx"] } ``` +--- + ## 📝 错误码对照表 前端开发请参考以下业务状态码 (Code): -| Code | 枚举 | 含义 | 前端建议动作 | -| :--- | :--- | :--- | :--- | -| `0` | `Success` | 成功 | - | -| `10000` | `ServerError` | 服务器内部错误 | 提示“系统繁忙” | -| `10001` | `BadRequest` | 请求参数错误 | 提示错误信息 | -| `20000` | `Unauthorized` | 未授权/签名无效 | 跳转登录 | -| **`20001`** | **`AccessTokenExpired`** | **Access Token 过期** | **使用 Refresh Token 静默刷新** | -| **`20002`** | **`RefreshTokenExpired`** | **Refresh Token 过期** | **强制登出,跳转登录页** | -| `20003` | `PermissionDenied` | 权限不足 | 提示无权访问 | +| Code | 枚举 | HTTP | 含义 | 前端建议动作 | +| :--- | :--- | :--- | :--- | :--- | +| `0` | `Success` | 200 | 成功 | - | +| **10xxx: 基础设施** | | | | | +| `10000` | `ServerError` | 500 | 服务器内部错误 | 提示“系统繁忙” | +| `10001` | `DbError` | 500 | 数据库错误 | 提示“系统繁忙” | +| `10002` | `CacheError` | 500 | 缓存服务错误 | 提示“系统繁忙” | +| **20xxx: 认证授权** | | | | | +| `20000` | `Unauthorized` | 401 | 未授权/签名无效 | 跳转登录 | +| **`20001`** | **`AccessTokenExpired`** | **401** | **Access Token 过期** | **使用 Refresh Token 静默刷新** | +| **`20002`** | **`RefreshTokenExpired`** | **401** | **Refresh Token 过期** | **强制登出,跳转登录页** | +| `20003` | `PermissionDenied` | 403 | 权限不足 | 提示无权访问 | +| **30xxx: 客户端错误** | | | | | +| `30000` | `BadRequest` | 400 | 请求参数通用错误 | 提示错误信息 | +| `30001` | `ValidationError` | 400 | 表单校验失败 | 提示具体字段错误 | +| `30002` | `ResourceNotFound` | 404 | 资源不存在 | 提示“未找到数据” | +| `30003` | `ResourceAlreadyExists`| 409 | 资源已存在 | 提示“重复创建” | ---