# anycms-core
支持多种 Rust Web 框架的统一 API 响应库。
## 特性
- **框架无关的核心**:`ApiResult<T>` 结构独立于任何 Web 框架工作
- **多框架支持**:内置 actix-web 和 axum 集成
- **Feature flags**:按需使用 - 零未使用的依赖
- **灵活的响应格式**:支持单值、列表、分页、错误码和额外元数据
- **字段级验证错误**:`FieldError` 支持表单验证场景
- **业务错误码**:`bizCode` 独立于 HTTP 状态码,支持自定义业务语义
- **响应时间戳**:内置 `timestamp` 字段,支持自动注入当前时间
- **请求追踪**:内置 `traceId` 字段,方便生产环境排查
- **序列化风格可配置**:支持 camelCase(默认)和 snake_case
- **tracing 集成**:自动按响应级别记录日志(4xx -> WARN, 5xx -> ERROR)
## 安装
在 `Cargo.toml` 中添加:
```toml
[dependencies]
anycms-core = "0.5"
```
### Feature Flags
- `actix` (默认):启用 actix-web 集成
- `axum`:启用 axum 集成
- `validator`:启用 `validator` crate 集成,自动转换验证错误
- `tracing`:启用 `tracing` crate 集成,自动按级别记录错误日志
- `full`:启用所有集成(actix + axum + validator + tracing)
- `camel-case` (默认):JSON 字段使用 camelCase
- `snake-case`:JSON 字段使用 snake_case
#### 示例
```toml
# 默认(actix-web + camelCase)
anycms-core = "0.5"
# 仅 axum
anycms-core = { version = "0.5", default-features = false, features = ["axum"] }
# snake_case 风格
anycms-core = { version = "0.5", features = ["snake-case"] }
# 启用 validator 集成
anycms-core = { version = "0.5", features = ["validator"] }
# 启用 tracing 集成
anycms-core = { version = "0.5", features = ["tracing"] }
# 启用全部
anycms-core = { version = "0.5", features = ["full"] }
```
## 项目结构
```
src/
├── lib.rs # 库入口,带 feature 条件导出
├── result.rs # 核心 ApiResult<T>、错误类型、FieldError
├── pagination.rs # 分页元数据结构
├── tracing.rs # tracing 集成 (feature = "tracing")
├── validator.rs # validator crate 集成 (feature = "validator")
└── frameworks/
├── mod.rs # 框架模块声明
├── actix.rs # actix-web 集成 (Responder trait + 中间件)
└── axum.rs # axum 集成 (IntoResponse trait + 中间件)
```
## 架构设计
该库采用清晰的关注点分离设计:
1. **核心层** ([result.rs](src/result.rs), [pagination.rs](src/pagination.rs))
- 包含数据结构和构建器方法
- 零框架依赖
- 无论启用哪个 feature 都会编译
2. **框架层** ([frameworks/](src/frameworks/))
- 实现框架特定的 trait
- 基于 feature flags 条件编译
- 相互隔离,避免冲突
3. **集成层** ([tracing.rs](src/tracing.rs), [validator.rs](src/validator.rs))
- 可选的第三方 crate 集成
- 通过 feature flags 按需启用
## 使用方法
### 成功响应
```rust
use anycms_core::{ApiResult, ResultPagination};
// 单值
let result = ApiResult::value(user);
// 列表 + 分页
let result = ApiResult::list(users)
.with_pagination(ResultPagination::new(100, 1, 10))
.with_extra("has_more", serde_json::json!(true));
// 空响应
let result = ApiResult::<()>::ok();
```
### 错误响应
```rust
use anycms_core::{ApiResult, ApiError};
// 使用 ApiError
let result: ApiResult<User> = ApiError::not_found("用户不存在").into();
// 使用快捷方法
let result = ApiResult::<User>::fail("操作失败").with_code(500);
```
### 业务错误码
`bizCode` 独立于 HTTP 状态码,用于前端业务逻辑判断:
```rust
use anycms_core::ApiResult;
// HTTP 404 + 业务码 10001
let result = ApiResult::<User>::fail("用户不存在")
.with_code(404)
.with_biz_code(10001);
```
### 时间戳
```rust
use anycms_core::ApiResult;
// 自动注入当前时间(Unix 毫秒)
let result = ApiResult::value(user).with_current_timestamp();
// 手动指定时间戳
let result = ApiResult::ok().with_timestamp(1700000000000);
```
### 验证错误
```rust
use anycms_core::{ApiResult, FieldError};
// 一次性传入所有字段错误
let result: ApiResult<User> = ApiResult::validation_errors(vec![
FieldError::new("email", "格式不正确"),
FieldError::new("name", "不能为空"),
]);
// 或链式添加
let result = ApiResult::<User>::fail("验证失败")
.with_code(422)
.with_error("email", "格式不正确")
.with_error("name", "不能为空");
```
### validator crate 集成
启用 `validator` feature 后,可以自动将 `validator::ValidationErrors` 转换为 `ApiResult` 响应:
```rust
use anycms_core::ApiResult;
use validator::Validate;
#[derive(Validate)]
struct CreateUser {
#[validate(email(message = "邮箱格式不正确"))]
email: String,
#[validate(length(min = 1, message = "用户名不能为空"))]
name: String,
}
async fn create_user(data: CreateUser) -> ApiResult<User> {
// validate() 返回 Err 时自动转换为 422 响应
if let Err(errors) = data.validate() {
return errors.into(); // From<ValidationErrors> for ApiResult<T>
}
// ... 业务逻辑
ApiResult::value(user)
}
```
嵌套结构体的字段名会自动加前缀(如 `address.city`),支持与自定义业务错误合并:
```rust
use anycms_core::{validation_errors_to_field_errors, ApiResult, FieldError};
fn handle(data: CreateUser) -> ApiResult<User> {
let mut errors: Vec<FieldError> = Vec::new();
// 收集 validator 错误
if let Err(ve) = data.validate() {
errors.extend(validation_errors_to_field_errors(&ve));
}
// 添加自定义业务校验
if data.name == "admin" {
errors.push(FieldError::new("name", "用户名 'admin' 已被保留"));
}
if !errors.is_empty() {
return ApiResult::validation_errors(errors);
}
// ...
}
```
### 请求追踪
```rust
use anycms_core::ApiResult;
let result = ApiResult::<User>::fail("用户不存在")
.with_code(404)
.with_trace_id("req-abc123");
```
### 分页
```rust
use anycms_core::ResultPagination;
let pagination = ResultPagination::new(95, 3, 10);
// 自动计算并输出:
// - total: 95
// - page: 3
// - pageSize: 10
// - totalPages: 10
// - hasNextPage: true
// - hasPrevPage: true
```
### 错误码
`ErrorCode` 覆盖常用 HTTP 状态码,每个变体都有对应的 `ApiError` 快捷方法:
```rust
use anycms_core::ApiError;
ApiError::bad_request("参数错误"); // 400
ApiError::unauthorized("未认证"); // 401
ApiError::forbidden("无权限"); // 403
ApiError::not_found("资源不存在"); // 404
ApiError::conflict("冲突"); // 409
ApiError::request_timeout("请求超时"); // 408
ApiError::payload_too_large("文件过大"); // 413
ApiError::too_many_requests("限流"); // 429
ApiError::validation("校验失败"); // 422
ApiError::internal("内部错误"); // 500
ApiError::gateway_timeout("网关超时"); // 504
```
### 框架集成
#### Actix-web
```rust
use actix_web::get;
use anycms_core::ApiResult;
#[get("/users/{id}")]
async fn get_user(path: web::Path<u32>) -> ApiResult<User> {
match find_user(path.into_inner()).await {
Some(user) => ApiResult::value(user),
None => ApiResult::<User>::fail("用户不存在").with_code(404),
}
}
```
#### Axum
```rust
use axum::extract::Path;
use anycms_core::ApiResult;
async fn get_user(Path(id): Path<u32>) -> ApiResult<User> {
match find_user(id).await {
Some(user) => ApiResult::value(user),
None => ApiResult::<User>::fail("用户不存在").with_code(404),
}
}
```
## Tracing 集成
启用 `tracing` feature 后,可以通过 `with_tracing()` 方法自动按响应级别记录日志:
```toml
[dependencies]
anycms-core = { version = "0.5", features = ["tracing"] }
```
### 使用方法
```rust
use anycms_core::{ApiResult, ApiResultTracing};
// 成功响应:不产生任何日志(避免噪音)
let result = ApiResult::value(user).with_tracing();
// 4xx 错误:WARN 级别
let result = ApiResult::<User>::fail("用户不存在")
.with_code(404)
.with_tracing(); // -> tracing::warn!
// 5xx 错误:ERROR 级别
let result = ApiResult::<User>::fail("内部错误")
.with_code(500)
.with_tracing(); // -> tracing::error!
```
### 日志级别规则
| 成功 (`success: true`) | - | 无日志 | 避免噪音 |
| 客户端错误 | 400-499 | `WARN` | 如参数错误、权限不足 |
| 服务端错误 | 500+ | `ERROR` | 如内部异常、网关超时 |
日志会自动包含 `code`、`trace_id`、`biz_code` 等结构化字段。
## 请求追踪中间件
`anycms-core` 为 actix-web 和 axum 提供了中间件,自动为响应注入 `traceId`。
### 工作原理
1. 从请求头中提取 trace ID(默认检查 `X-Request-ID` 和 `X-Trace-ID`)
2. 若请求头中不存在,则自动生成 UUID v4
3. 将 trace ID 注入到 `ApiResult` 响应的 `traceId` 字段
### Actix-web 使用
```rust
use actix_web::{web, App, HttpServer};
use anycms_core::actix::ApiResultLayer;
.wrap(ApiResultLayer::default()) // 自动注入 trace_id
.route("/users", web::get().to(list_users))
.route("/users/{id}", web::get().to(get_user))
})
```
### Axum 使用
```rust
use axum::{Router, routing::get, middleware};
use anycms_core::axum::api_result_middleware;
// 零配置(使用默认配置)
let app = Router::new()
.route("/users", get(list_users))
.layer(middleware::from_fn(api_result_middleware));
// 自定义配置
use anycms_core::axum::{ApiResultMiddlewareConfig, api_result_middleware_with_config};
let config = ApiResultMiddlewareConfig {
trace_id_headers: vec!["x-request-id".to_string()],
inject_timestamp: true, // 同时自动注入 timestamp
};
let app = Router::new()
.route("/users", get(list_users))
.layer(middleware::from_fn_with_state(config, api_result_middleware_with_config));
```
## ResultPagination 数据库查询辅助
`ResultPagination` 提供了数据库分页查询的辅助方法,可以直接用于 SQL `OFFSET` / `LIMIT` 子句:
```rust
use anycms_core::ResultPagination;
let pagination = ResultPagination::new(100, 3, 10);
// 用于 SQL OFFSET 子句: (page - 1) * page_size
let offset = pagination.offset(); // 20
// 用于 SQL LIMIT 子句: 等同于 page_size
let limit = pagination.limit(); // 10
// 页面判断
pagination.is_first_page(); // false (当前是第 3 页)
pagination.is_last_page(); // false (总共 10 页)
// 翻页
pagination.next_page(); // Some(4)
pagination.prev_page(); // Some(2)
```
### 典型数据库查询场景
```rust
use anycms_core::{ApiResult, ResultPagination};
async fn list_users(page: i64, page_size: i64) -> ApiResult<User> {
let total = count_users().await; // SELECT COUNT(*)
let pagination = ResultPagination::new(total, page, page_size);
let users = query_users(
pagination.offset(), // OFFSET
pagination.limit(), // LIMIT
).await;
ApiResult::list(users).with_pagination(pagination)
}
```
## API 响应格式
### 成功响应(单值)
```json
{
"success": true,
"data": { "id": 1, "name": "张三" }
}
```
### 成功响应(列表 + 分页)
```json
{
"success": true,
"data": [
{ "id": 1, "name": "张三" },
{ "id": 2, "name": "李四" }
],
"pagination": {
"total": 100,
"page": 1,
"pageSize": 10,
"totalPages": 10,
"hasNextPage": true,
"hasPrevPage": false
}
}
```
### 空成功响应
```json
{
"success": true
}
```
### 错误响应
```json
{
"success": false,
"message": "用户不存在",
"code": 404
}
```
### 业务错误码响应
```json
{
"success": false,
"message": "用户不存在",
"code": 404,
"bizCode": 10001,
"timestamp": 1780542642018
}
```
### 验证错误响应
```json
{
"success": false,
"message": "Validation failed",
"code": 422,
"errors": [
{ "field": "email", "message": "格式不正确" },
{ "field": "name", "message": "不能为空" }
]
}
```
### 带追踪的错误响应
```json
{
"success": false,
"message": "用户不存在",
"code": 404,
"traceId": "req-abc123"
}
```
## 示例
运行演示示例:
```bash
# Actix-web 演示
cargo run --example actix_demo --features actix
# Axum 演示
cargo run --example axum_demo --features axum
# 验证错误演示
cargo run --example validation_demo
# validator crate 集成演示
cargo run --example validator_demo --features validator
# 请求追踪演示
cargo run --example trace_id_demo --features actix
# 业务错误码 + 时间戳演示
cargo run --example biz_code_demo
# ResponseData From trait 演示
cargo run --example from_trait_demo
# 中间件演示 (Actix-web)
cargo run --example middleware_actix_demo --features actix
# 中间件演示 (Axum)
cargo run --example middleware_axum_demo --features axum
# tracing 集成演示
cargo run --example tracing_demo --features tracing
# 检查所有示例
cargo check --examples --all-features
```
## 许可证
MIT