# Microservice Common Lib (Rust) 这是微服务架构的通用基础库 (`common-telemetry`),用于统一服务的响应结构(成功/错误)、错误处理标准、日志格式以及分布式链路追踪。 ## ✨ 核心特性 * **统一 API 响应 (Response)**: * **成功响应**:`AppResponse` 自动映射 HTTP 状态码(200/201/202)并返回统一 JSON 结构 `{ code, message, data?, trace_id? }`。 * **错误响应**:`AppError` 实现 `IntoResponse`,自动映射 HTTP 状态码并返回统一 JSON 结构 `{ code, message, details?, trace_id? }`。 * **业务码 (BizCode)**:前端可根据 `code` 做稳定分支;成功恒为 `0`。 * **双 Token 场景**:明确区分 `AccessTokenExpired` (20001) 与 `RefreshTokenExpired` (20002)。 * **第三方库适配**:可选集成 `sqlx` / `redis` / `validator` 的错误转换。 * 例如:`sqlx::Error::RowNotFound` 自动转换为 **404 Not Found**。 * **可观测性 (Telemetry)**: * 基于 `tracing` 生态。 * 支持 **JSON 结构化日志** (适配 ELK/Loki)。 * 支持 **控制台 + 文件双写**。 * 支持 **非阻塞 (Non-blocking)** 异步日志写入与按天滚动。 * **模块化设计**: 通过 Feature Flags 按需引入,保持依赖轻量。 --- ## 🚀 发布指南 你可以选择将此库发布到私有 Cargo 仓库 (Kellnr),或者直接作为 Git 依赖发布到 Gitea。 ### 方法一:发布到 Kellnr (推荐) Kellnr 是一个私有的 Crates.io 镜像与仓库。 #### 1. 配置本地 Cargo 在本项目根目录(或全局 `~/.cargo/config.toml`)创建/修改 `.cargo/config.toml`,注册你的私有仓库: ```toml # .cargo/config.toml [registries.kellnr] # 注册表名称自定义,这里叫 "kellnr" index = "sparse+https://kellnr.shay7sev.site/api/v1/crates/" [net] git-fetch-with-cli = true ``` #### 2. 登录认证 使用你的 Kellnr 账户 Token 进行登录(只需执行一次): ```bash cargo login --registry kellnr ``` #### 3. 修改 Cargo.toml 确保 `Cargo.toml` 中配置了禁止发布到公网,并指定了私有仓库: ```toml [package] name = "common-telemetry" version = "0.1.0" # ... publish = ["kellnr"] # 关键:防止误发到 crates.io ``` #### 4. 执行发布 ```bash cargo publish --registry kellnr ``` --- ### 方法二:推送到 Gitea (Git 依赖) 如果你不想走 Cargo Registry 流程,可以直接作为 Git 仓库使用。 ```bash # 初始化 git (如果尚未初始化) git init git branch -M main # 添加远程仓库 (替换为你的实际仓库地址) git remote add origin ssh://git@gitea.shay7sev.site:2222/admin/common-telemetry.git # 推送代码 git add . git commit -m "Initial commit" git push -u origin main ``` --- ## 📦 如何在其他服务中使用 假设你要在 `user-service` 中使用此库。 ### 1. 引入依赖 #### 方式 A: 通过 Kellnr 引入 (如果已发布) 在 `user-service` 的 `Cargo.toml` 中,你可以按需选择“全功能”或“最小依赖”。 ```toml [dependencies] # 方案 1:默认全功能(等价于 features = ["full"]) common-telemetry = { version = "0.1", registry = "kellnr" } # 方案 2:只启用响应模型 + SQLX/Validator(更轻量) # common-telemetry = { version = "0.1", registry = "kellnr", default-features = false, features = ["response", "with-sqlx", "with-validator"] } # 注意:你需要确保服务本身引用的 sqlx 版本与 common-telemetry 兼容 sqlx = { version = "0.7", features = ["postgres", "runtime-tokio"] } ``` *注意:使用此方式,`user-service` 项目也需要在 `.cargo/config.toml` 中配置 registry 地址。* #### 方式 B: 通过 Gitea (Git) 引入 ```toml [dependencies] common-telemetry = { git = "ssh://git@gitea.shay7sev.site:2222/admin/common-telemetry.git", branch = "main", features = ["full"] } ``` ### 2. 代码集成示例 #### A. 初始化日志 (main.rs) ```rust use common_telemetry::telemetry::{self, TelemetryConfig}; #[tokio::main] async fn main() { // 1. 配置 let config = TelemetryConfig { service_name: "user-service".into(), log_level: "info".into(), // 或 "debug,sqlx=error" log_to_file: true, // 生产环境建议开启 log_dir: Some("./logs".into()), log_file: Some("user.log".into()), }; // 2. 初始化 Tracing // !!! 警告:必须将 guard 赋值给一个变量 (_guard),并保持它在 main 函数整个生命周期内存活 // 否则日志文件写入线程会被立即销毁! let _guard = telemetry::init(config); tracing::info!("User Service started success!"); // ... 启动 Axum ... } ``` **重要:环境变量优先级** `telemetry::init` 会优先读取 `RUST_LOG` 环境变量;若未设置则使用 `TelemetryConfig.log_level`。 #### B. 错误处理与第三方库集成 (Handler) 由于实现了 `From`,你可以直接使用 `?` 操作符,库会自动处理类型转换和 HTTP 映射。 ```rust use axum::{Json, response::IntoResponse}; use common_telemetry::AppError; // 引入统一错误 use validator::Validate; #[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))); } Ok(Json("Created".into())) } ``` #### C. 统一成功响应 (可选) 如果你希望“成功”和“错误”都返回统一的 JSON 结构(成功包含 `code/message/data/trace_id`;错误包含 `code/message/details/trace_id`),建议让 handler 返回 `AppResponse`: ```rust use common_telemetry::{AppError, AppResponse}; async fn get_profile() -> Result, AppError> { Ok(AppResponse::ok("profile data".to_string())) } ``` #### D. 响应体格式(前后端对齐) 成功响应(`AppResponse`): ```json { "code": 0, "message": "Success", "data": { "any": "payload" }, "trace_id": null } ``` 错误响应(`AppError`): ```json { "code": 30001, "message": "Validation error: ...", "details": "field required", "trace_id": null } ``` --- ## ⚙️ 功能模块说明 (Feature Flags) 本库采用高度模块化设计,建议按需开启 Feature 以减少编译体积: | Feature | 说明 | 包含依赖 | | :--- | :--- | :--- | | **`default`** | 默认开启全功能 | `full` | | **`full`** | 包含基础功能及所有第三方集成 | `response`, `telemetry`, `with-sqlx`, `with-redis`, `with-anyhow`, `with-validator` | | **`response`** | 统一成功/错误响应 + 错误处理 | `thiserror`, `axum`, `serde` | | **`telemetry`** | 仅使用日志与链路追踪 | `tracing` 全家桶 | | **`with-sqlx`** | 集成 `sqlx` 错误转换 | `sqlx` (自动处理 RowNotFound) | | **`with-redis`** | 集成 `redis` 错误转换 | `redis` | | **`with-validator`** | 集成 `validator` 错误转换 | `validator` | | **`with-anyhow`** | 集成 `anyhow` 兜底错误 | `anyhow` | 兼容性说明:历史上的 `error` feature 已被重命名为 `response`,但仍保留 `error` 作为别名(`error = ["response"]`),以降低已有服务的迁移成本。 **示例 (只用响应 + SQLX支持):** ```toml common-telemetry = { version = "0.1", default-features = false, features = ["response", "with-sqlx"] } ``` --- ## 📝 错误码对照表 前端开发请参考以下业务状态码 (Code): | Code | 枚举 | HTTP | 含义 | 前端建议动作 | | :--- | :--- | :--- | :--- | :--- | | `0` | `Success` | 200 | 成功 | - | | **10xxx: 基础设施** | | | | | | `10000` | `ServerError` | 500 | 服务器内部错误 | 提示“系统繁忙” | | `10001` | `DbError` | 500 | 数据库错误 | 提示“系统繁忙” | | `10002` | `CacheError` | 500 | 缓存服务错误 | 提示“系统繁忙” | | `10003` | `SerializationError` | 500 | 序列化/反序列化失败 | 提示“系统繁忙” | | `10004` | `ExternalServiceError` | 500 | 下游/第三方调用失败 | 提示“系统繁忙” | | `10005` | `ConfigError` | 500 | 配置加载失败 | 提示“系统繁忙” | | **20xxx: 认证授权** | | | | | | `20000` | `Unauthorized` | 401 | 未授权/签名无效 | 跳转登录 | | **`20001`** | **`AccessTokenExpired`** | **401** | **Access Token 过期** | **使用 Refresh Token 静默刷新** | | **`20002`** | **`RefreshTokenExpired`** | **401** | **Refresh Token 过期** | **强制登出,跳转登录页** | | `20003` | `PermissionDenied` | 403 | 权限不足 | 提示无权访问 | | `20004` | `AccountDisabled` | 401 | 账号禁用/锁定 | 跳转登录或提示 | | `20005` | `InvalidCredentials` | 401 | 账号或密码错误 | 提示重试 | | `20006` | `MissingHeader` | 401 | 缺少必要 Header | 提示重试 | | **30xxx: 客户端错误** | | | | | | `30000` | `BadRequest` | 400 | 请求参数通用错误 | 提示错误信息 | | `30001` | `ValidationError` | 400 | 表单校验失败 | 提示具体字段错误 | | `30002` | `ResourceNotFound` | 404 | 资源不存在 | 提示“未找到数据” | | `30003` | `ResourceAlreadyExists`| 409 | 资源已存在 | 提示“重复创建” | | `30004` | `MethodNotAllowed` | 405 | HTTP 方法不支持 | 提示“请求方式错误” | | **40xxx: 业务流控/状态** | | | | | | `40000` | `RateLimitExceeded` | 429 | 请求过于频繁 | 退避重试 | | `40001` | `PreconditionFailed` | 400 | 业务前置条件不满足 | 提示原因 | --- ## 🛠 开发注意事项 1. **Cargo.lock**: 本项目已将 `Cargo.lock` 加入 `.gitignore`,这是作为 Library 的最佳实践。 2. **测试**: 运行集成测试以验证全链路功能。 ```bash # 使用 --nocapture 查看详细步骤打印 cargo test -- --nocapture ``` --- ## 🧩 前端 TypeScript 类型 (ApiResponse) 本仓库提供前后端对齐的 TypeScript 类型定义文件(非 NPM 包形式发布): - 入口: [types/index.ts](file:///home/shay/project/backend/common-telemetry/types/index.ts) - 定义: [types/api-response.ts](file:///home/shay/project/backend/common-telemetry/types/api-response.ts) - 便于引用的单文件 re-export: [types.ts](file:///home/shay/project/backend/common-telemetry/types.ts) 前端可直接在代码库中引用(例如作为 git 子模块/直接拷贝),并使用: ```ts import { ApiResponse, isSuccessResponse } from "./path/to/common-telemetry/types"; function handle(res: ApiResponse) { if (isSuccessResponse(res)) return res.data; throw new Error(res.message); } ```