Microservice Common Lib (Rust)
这是微服务架构的通用基础库 (common-telemetry),用于统一服务的响应结构(成功/错误)、错误处理标准、日志格式以及分布式链路追踪。
✨ 核心特性
- 统一 API 响应 (Response):
- 成功响应:
AppResponse<T>自动映射 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,注册你的私有仓库:
# .cargo/config.toml
[registries.kellnr]
# 注册表名称自定义,这里叫 "kellnr"
index = "sparse+https://kellnr.shay7sev.site/api/v1/crates/"
[net]
git-fetch-with-cli = true
2. 登录认证
使用你的 Kellnr 账户 Token 进行登录(只需执行一次):
cargo login --registry kellnr <YOUR_AUTH_TOKEN>
3. 修改 Cargo.toml
确保 Cargo.toml 中配置了禁止发布到公网,并指定了私有仓库:
[package]
name = "common-telemetry"
version = "0.1.0"
# ...
publish = ["kellnr"] # 关键:防止误发到 crates.io
4. 执行发布
cargo publish --registry kellnr
方法二:推送到 Gitea (Git 依赖)
如果你不想走 Cargo Registry 流程,可以直接作为 Git 仓库使用。
# 初始化 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 中,你可以按需选择“全功能”或“最小依赖”。
[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) 引入
[dependencies]
common-telemetry = { git = "ssh://git@gitea.shay7sev.site:2222/admin/common-telemetry.git", branch = "main", features = ["full"] }
2. 代码集成示例
A. 初始化日志 (main.rs)
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<T>,你可以直接使用 ? 操作符,库会自动处理类型转换和 HTTP 映射。
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<CreateUserReq>,
) -> Result<Json<String>, 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<T>:
use common_telemetry::{AppError, AppResponse};
async fn get_profile() -> Result<AppResponse<String>, AppError> {
Ok(AppResponse::ok("profile data".to_string()))
}
D. 响应体格式(前后端对齐)
成功响应(AppResponse<T>):
{ "code": 0, "message": "Success", "data": { "any": "payload" }, "trace_id": null }
错误响应(AppError):
{ "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支持):
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 | 业务前置条件不满足 | 提示原因 |
🛠 开发注意事项
- Cargo.lock: 本项目已将
Cargo.lock加入.gitignore,这是作为 Library 的最佳实践。 - 测试: 运行集成测试以验证全链路功能。
# 使用 --nocapture 查看详细步骤打印 cargo test -- --nocapture
🧩 前端 TypeScript 类型 (ApiResponse)
本仓库提供前后端对齐的 TypeScript 类型定义文件(非 NPM 包形式发布):
- 入口: types/index.ts
- 定义: types/api-response.ts
- 便于引用的单文件 re-export: types.ts
前端可直接在代码库中引用(例如作为 git 子模块/直接拷贝),并使用:
import { ApiResponse, isSuccessResponse } from "./path/to/common-telemetry/types";
function handle<T>(res: ApiResponse<T>) {
if (isSuccessResponse(res)) return res.data;
throw new Error(res.message);
}