Files
udmin/docs/RESPONSE.md
ayou a3f2f99a68 docs: 添加项目文档包括总览、架构、流程引擎和服务层
新增以下文档文件:
- PROJECT_OVERVIEW.md 项目总览文档
- BACKEND_ARCHITECTURE.md 后端架构文档
- FRONTEND_ARCHITECTURE.md 前端架构文档
- FLOW_ENGINE.md 流程引擎文档
- SERVICES.md 服务层文档
- ERROR_HANDLING.md 错误处理模块文档

文档内容涵盖项目整体介绍、技术架构、核心模块设计和实现细节
2025-09-24 20:21:45 +08:00

1161 lines
30 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# UdminAI 响应格式模块文档
## 概述
UdminAI 项目的响应格式模块定义了统一的 API 响应结构,确保所有接口返回一致的数据格式。该模块基于 Axum 框架和 Serde 序列化库构建,提供了类型安全的响应处理机制。
## 设计原则
### 核心理念
- **一致性**: 所有 API 接口使用统一的响应格式
- **类型安全**: 编译时类型检查,避免运行时错误
- **可扩展性**: 支持不同类型的响应数据
- **用户友好**: 清晰的响应结构和错误信息
- **标准化**: 遵循 RESTful API 设计规范
### 响应分类
1. **成功响应**: 操作成功时的数据返回
2. **分页响应**: 列表数据的分页返回
3. **错误响应**: 操作失败时的错误信息
4. **空响应**: 无数据返回的成功操作
## 响应结构定义 (response.rs)
### 基础响应类型
```rust
use axum::{
http::StatusCode,
response::{IntoResponse, Response},
Json,
};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
/// API 响应的基础结构
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ApiResponse<T> {
/// 响应状态码
pub code: u16,
/// 响应消息
pub message: String,
/// 响应数据
pub data: Option<T>,
/// 响应时间戳
pub timestamp: chrono::DateTime<chrono::Utc>,
/// 请求追踪 ID
pub request_id: Option<String>,
}
/// 分页响应结构
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PageResponse<T> {
/// 数据列表
pub items: Vec<T>,
/// 分页信息
pub pagination: PaginationInfo,
}
/// 分页信息
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PaginationInfo {
/// 当前页码从1开始
pub current_page: u64,
/// 每页大小
pub page_size: u64,
/// 总记录数
pub total_count: u64,
/// 总页数
pub total_pages: u64,
/// 是否有下一页
pub has_next: bool,
/// 是否有上一页
pub has_prev: bool,
}
/// 批量操作响应
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BatchResponse<T> {
/// 成功处理的项目
pub success: Vec<T>,
/// 失败的项目及错误信息
pub failed: Vec<BatchError>,
/// 成功数量
pub success_count: usize,
/// 失败数量
pub failed_count: usize,
}
/// 批量操作错误
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BatchError {
/// 项目标识
pub id: String,
/// 错误代码
pub error_code: String,
/// 错误消息
pub error_message: String,
}
/// 统计响应
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StatsResponse {
/// 统计数据
pub stats: HashMap<String, serde_json::Value>,
/// 统计时间范围
pub time_range: Option<TimeRange>,
/// 更新时间
pub updated_at: chrono::DateTime<chrono::Utc>,
}
/// 时间范围
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TimeRange {
/// 开始时间
pub start: chrono::DateTime<chrono::Utc>,
/// 结束时间
pub end: chrono::DateTime<chrono::Utc>,
}
/// 健康检查响应
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HealthResponse {
/// 服务状态
pub status: HealthStatus,
/// 版本信息
pub version: String,
/// 启动时间
pub uptime: String,
/// 依赖服务状态
pub dependencies: HashMap<String, DependencyStatus>,
}
/// 健康状态
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum HealthStatus {
Healthy,
Degraded,
Unhealthy,
}
/// 依赖服务状态
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DependencyStatus {
/// 状态
pub status: HealthStatus,
/// 响应时间(毫秒)
pub response_time_ms: Option<u64>,
/// 错误信息
pub error: Option<String>,
}
impl<T> ApiResponse<T> {
/// 创建成功响应
pub fn success(data: T) -> Self {
Self {
code: 200,
message: "操作成功".to_string(),
data: Some(data),
timestamp: chrono::Utc::now(),
request_id: None,
}
}
/// 创建成功响应(带自定义消息)
pub fn success_with_message(data: T, message: impl Into<String>) -> Self {
Self {
code: 200,
message: message.into(),
data: Some(data),
timestamp: chrono::Utc::now(),
request_id: None,
}
}
/// 创建创建成功响应
pub fn created(data: T) -> Self {
Self {
code: 201,
message: "创建成功".to_string(),
data: Some(data),
timestamp: chrono::Utc::now(),
request_id: None,
}
}
/// 创建无内容响应
pub fn no_content() -> ApiResponse<()> {
ApiResponse {
code: 204,
message: "操作成功".to_string(),
data: None,
timestamp: chrono::Utc::now(),
request_id: None,
}
}
/// 设置请求 ID
pub fn with_request_id(mut self, request_id: impl Into<String>) -> Self {
self.request_id = Some(request_id.into());
self
}
/// 设置状态码
pub fn with_code(mut self, code: u16) -> Self {
self.code = code;
self
}
/// 设置消息
pub fn with_message(mut self, message: impl Into<String>) -> Self {
self.message = message.into();
self
}
}
impl<T> PageResponse<T> {
/// 创建分页响应
pub fn new(
items: Vec<T>,
current_page: u64,
page_size: u64,
total_count: u64,
) -> Self {
let total_pages = if total_count == 0 {
1
} else {
(total_count + page_size - 1) / page_size
};
let has_next = current_page < total_pages;
let has_prev = current_page > 1;
Self {
items,
pagination: PaginationInfo {
current_page,
page_size,
total_count,
total_pages,
has_next,
has_prev,
},
}
}
/// 创建空分页响应
pub fn empty(page_size: u64) -> Self {
Self::new(Vec::new(), 1, page_size, 0)
}
}
impl<T> BatchResponse<T> {
/// 创建批量响应
pub fn new(success: Vec<T>, failed: Vec<BatchError>) -> Self {
let success_count = success.len();
let failed_count = failed.len();
Self {
success,
failed,
success_count,
failed_count,
}
}
/// 创建全部成功的批量响应
pub fn all_success(success: Vec<T>) -> Self {
Self::new(success, Vec::new())
}
/// 创建全部失败的批量响应
pub fn all_failed(failed: Vec<BatchError>) -> Self {
Self::new(Vec::new(), failed)
}
}
impl StatsResponse {
/// 创建统计响应
pub fn new(stats: HashMap<String, serde_json::Value>) -> Self {
Self {
stats,
time_range: None,
updated_at: chrono::Utc::now(),
}
}
/// 设置时间范围
pub fn with_time_range(
mut self,
start: chrono::DateTime<chrono::Utc>,
end: chrono::DateTime<chrono::Utc>,
) -> Self {
self.time_range = Some(TimeRange { start, end });
self
}
}
impl HealthResponse {
/// 创建健康响应
pub fn new(
status: HealthStatus,
version: impl Into<String>,
uptime: impl Into<String>,
) -> Self {
Self {
status,
version: version.into(),
uptime: uptime.into(),
dependencies: HashMap::new(),
}
}
/// 添加依赖状态
pub fn with_dependency(
mut self,
name: impl Into<String>,
status: DependencyStatus,
) -> Self {
self.dependencies.insert(name.into(), status);
self
}
}
impl DependencyStatus {
/// 创建健康的依赖状态
pub fn healthy(response_time_ms: u64) -> Self {
Self {
status: HealthStatus::Healthy,
response_time_ms: Some(response_time_ms),
error: None,
}
}
/// 创建不健康的依赖状态
pub fn unhealthy(error: impl Into<String>) -> Self {
Self {
status: HealthStatus::Unhealthy,
response_time_ms: None,
error: Some(error.into()),
}
}
/// 创建降级的依赖状态
pub fn degraded(response_time_ms: u64, error: impl Into<String>) -> Self {
Self {
status: HealthStatus::Degraded,
response_time_ms: Some(response_time_ms),
error: Some(error.into()),
}
}
}
/// 实现 IntoResponse使响应可以直接返回
impl<T> IntoResponse for ApiResponse<T>
where
T: Serialize,
{
fn into_response(self) -> Response {
let status_code = StatusCode::from_u16(self.code)
.unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
(status_code, Json(self)).into_response()
}
}
/// 响应构建器
pub struct ResponseBuilder<T> {
response: ApiResponse<T>,
}
impl<T> ResponseBuilder<T> {
/// 创建新的响应构建器
pub fn new(data: T) -> Self {
Self {
response: ApiResponse::success(data),
}
}
/// 设置状态码
pub fn code(mut self, code: u16) -> Self {
self.response.code = code;
self
}
/// 设置消息
pub fn message(mut self, message: impl Into<String>) -> Self {
self.response.message = message.into();
self
}
/// 设置请求 ID
pub fn request_id(mut self, request_id: impl Into<String>) -> Self {
self.response.request_id = Some(request_id.into());
self
}
/// 构建响应
pub fn build(self) -> ApiResponse<T> {
self.response
}
}
/// 响应宏
#[macro_export]
macro_rules! ok_response {
($data:expr) => {
ApiResponse::success($data)
};
($data:expr, $message:expr) => {
ApiResponse::success_with_message($data, $message)
};
}
#[macro_export]
macro_rules! created_response {
($data:expr) => {
ApiResponse::created($data)
};
}
#[macro_export]
macro_rules! no_content_response {
() => {
ApiResponse::no_content()
};
}
#[macro_export]
macro_rules! page_response {
($items:expr, $page:expr, $size:expr, $total:expr) => {
ApiResponse::success(PageResponse::new($items, $page, $size, $total))
};
}
/// 响应扩展 trait
pub trait ResponseExt<T> {
/// 转换为 API 响应
fn into_api_response(self) -> ApiResponse<T>;
/// 转换为分页响应
fn into_page_response(
self,
current_page: u64,
page_size: u64,
total_count: u64,
) -> ApiResponse<PageResponse<T>>
where
Self: IntoIterator<Item = T>,
Self::IntoIter: ExactSizeIterator;
}
impl<T> ResponseExt<T> for T {
fn into_api_response(self) -> ApiResponse<T> {
ApiResponse::success(self)
}
fn into_page_response(
self,
current_page: u64,
page_size: u64,
total_count: u64,
) -> ApiResponse<PageResponse<T>>
where
Self: IntoIterator<Item = T>,
Self::IntoIter: ExactSizeIterator,
{
let items: Vec<T> = self.into_iter().collect();
let page_response = PageResponse::new(items, current_page, page_size, total_count);
ApiResponse::success(page_response)
}
}
impl<T> ResponseExt<T> for Vec<T> {
fn into_api_response(self) -> ApiResponse<Vec<T>> {
ApiResponse::success(self)
}
fn into_page_response(
self,
current_page: u64,
page_size: u64,
total_count: u64,
) -> ApiResponse<PageResponse<T>>
where
Self: IntoIterator<Item = T>,
Self::IntoIter: ExactSizeIterator,
{
let page_response = PageResponse::new(self, current_page, page_size, total_count);
ApiResponse::success(page_response)
}
}
/// 常用响应类型别名
pub type JsonResponse<T> = ApiResponse<T>;
pub type ListResponse<T> = ApiResponse<Vec<T>>;
pub type PageResp<T> = ApiResponse<PageResponse<T>>;
pub type BatchResp<T> = ApiResponse<BatchResponse<T>>;
pub type StatsResp = ApiResponse<StatsResponse>;
pub type HealthResp = ApiResponse<HealthResponse>;
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
struct TestData {
id: u32,
name: String,
}
#[test]
fn test_success_response() {
let data = TestData {
id: 1,
name: "test".to_string(),
};
let response = ApiResponse::success(data.clone());
assert_eq!(response.code, 200);
assert_eq!(response.message, "操作成功");
assert_eq!(response.data, Some(data));
}
#[test]
fn test_created_response() {
let data = TestData {
id: 1,
name: "test".to_string(),
};
let response = ApiResponse::created(data.clone());
assert_eq!(response.code, 201);
assert_eq!(response.message, "创建成功");
assert_eq!(response.data, Some(data));
}
#[test]
fn test_no_content_response() {
let response = ApiResponse::no_content();
assert_eq!(response.code, 204);
assert_eq!(response.message, "操作成功");
assert_eq!(response.data, None);
}
#[test]
fn test_page_response() {
let items = vec![
TestData { id: 1, name: "test1".to_string() },
TestData { id: 2, name: "test2".to_string() },
];
let page_response = PageResponse::new(items.clone(), 1, 10, 25);
assert_eq!(page_response.items, items);
assert_eq!(page_response.pagination.current_page, 1);
assert_eq!(page_response.pagination.page_size, 10);
assert_eq!(page_response.pagination.total_count, 25);
assert_eq!(page_response.pagination.total_pages, 3);
assert_eq!(page_response.pagination.has_next, true);
assert_eq!(page_response.pagination.has_prev, false);
}
#[test]
fn test_batch_response() {
let success_items = vec![
TestData { id: 1, name: "test1".to_string() },
TestData { id: 2, name: "test2".to_string() },
];
let failed_items = vec![
BatchError {
id: "3".to_string(),
error_code: "VALIDATION_FAILED".to_string(),
error_message: "名称不能为空".to_string(),
},
];
let batch_response = BatchResponse::new(success_items.clone(), failed_items.clone());
assert_eq!(batch_response.success, success_items);
assert_eq!(batch_response.failed, failed_items);
assert_eq!(batch_response.success_count, 2);
assert_eq!(batch_response.failed_count, 1);
}
#[test]
fn test_stats_response() {
let mut stats = HashMap::new();
stats.insert("total_users".to_string(), json!(100));
stats.insert("active_users".to_string(), json!(85));
let stats_response = StatsResponse::new(stats.clone());
assert_eq!(stats_response.stats, stats);
assert!(stats_response.time_range.is_none());
}
#[test]
fn test_health_response() {
let health_response = HealthResponse::new(
HealthStatus::Healthy,
"1.0.0",
"2 days",
)
.with_dependency(
"database",
DependencyStatus::healthy(50),
)
.with_dependency(
"redis",
DependencyStatus::degraded(200, "连接缓慢"),
);
assert!(matches!(health_response.status, HealthStatus::Healthy));
assert_eq!(health_response.version, "1.0.0");
assert_eq!(health_response.uptime, "2 days");
assert_eq!(health_response.dependencies.len(), 2);
}
#[test]
fn test_response_builder() {
let data = TestData {
id: 1,
name: "test".to_string(),
};
let response = ResponseBuilder::new(data.clone())
.code(201)
.message("自定义消息")
.request_id("req-123")
.build();
assert_eq!(response.code, 201);
assert_eq!(response.message, "自定义消息");
assert_eq!(response.request_id, Some("req-123".to_string()));
assert_eq!(response.data, Some(data));
}
#[test]
fn test_response_macros() {
let data = TestData {
id: 1,
name: "test".to_string(),
};
let ok_resp = ok_response!(data.clone());
assert_eq!(ok_resp.code, 200);
let ok_resp_with_msg = ok_response!(data.clone(), "自定义消息");
assert_eq!(ok_resp_with_msg.message, "自定义消息");
let created_resp = created_response!(data.clone());
assert_eq!(created_resp.code, 201);
let no_content_resp = no_content_response!();
assert_eq!(no_content_resp.code, 204);
let items = vec![data.clone()];
let page_resp = page_response!(items, 1, 10, 1);
assert_eq!(page_resp.code, 200);
}
#[test]
fn test_response_ext() {
let data = TestData {
id: 1,
name: "test".to_string(),
};
let response = data.clone().into_api_response();
assert_eq!(response.code, 200);
assert_eq!(response.data, Some(data));
let items = vec![
TestData { id: 1, name: "test1".to_string() },
TestData { id: 2, name: "test2".to_string() },
];
let page_response = items.clone().into_page_response(1, 10, 25);
assert_eq!(page_response.code, 200);
if let Some(page_data) = page_response.data {
assert_eq!(page_data.items, items);
assert_eq!(page_data.pagination.total_count, 25);
}
}
}
```
## 使用示例
### 基础响应
```rust
use axum::{extract::Path, Json};
use crate::response::{ApiResponse, ok_response, created_response};
/// 获取用户信息
pub async fn get_user(Path(id): Path<String>) -> impl IntoResponse {
match user_service::get_by_id(&id).await {
Ok(user) => ok_response!(user, "获取用户信息成功"),
Err(e) => e.into_response(),
}
}
/// 创建用户
pub async fn create_user(Json(req): Json<CreateUserReq>) -> impl IntoResponse {
match user_service::create(req).await {
Ok(user) => created_response!(user),
Err(e) => e.into_response(),
}
}
/// 删除用户
pub async fn delete_user(Path(id): Path<String>) -> impl IntoResponse {
match user_service::delete(&id).await {
Ok(_) => no_content_response!(),
Err(e) => e.into_response(),
}
}
```
### 分页响应
```rust
use axum::extract::Query;
use crate::response::{PageResponse, page_response};
/// 获取用户列表
pub async fn list_users(Query(params): Query<ListUsersReq>) -> impl IntoResponse {
match user_service::list(params).await {
Ok((users, total)) => {
page_response!(users, params.page, params.page_size, total)
}
Err(e) => e.into_response(),
}
}
```
### 批量操作响应
```rust
use crate::response::{BatchResponse, BatchError};
/// 批量创建用户
pub async fn batch_create_users(
Json(req): Json<BatchCreateUsersReq>,
) -> impl IntoResponse {
let mut success = Vec::new();
let mut failed = Vec::new();
for (index, user_req) in req.users.into_iter().enumerate() {
match user_service::create(user_req).await {
Ok(user) => success.push(user),
Err(e) => failed.push(BatchError {
id: index.to_string(),
error_code: e.error_code().to_string(),
error_message: e.to_string(),
}),
}
}
let batch_response = BatchResponse::new(success, failed);
ApiResponse::success(batch_response)
}
```
### 统计响应
```rust
use std::collections::HashMap;
use serde_json::json;
use crate::response::StatsResponse;
/// 获取用户统计
pub async fn get_user_stats() -> impl IntoResponse {
let mut stats = HashMap::new();
match user_service::get_stats().await {
Ok(user_stats) => {
stats.insert("total_users".to_string(), json!(user_stats.total));
stats.insert("active_users".to_string(), json!(user_stats.active));
stats.insert("new_users_today".to_string(), json!(user_stats.new_today));
let stats_response = StatsResponse::new(stats)
.with_time_range(
chrono::Utc::now() - chrono::Duration::days(30),
chrono::Utc::now(),
);
ApiResponse::success(stats_response)
}
Err(e) => e.into_response(),
}
}
```
### 健康检查响应
```rust
use crate::response::{HealthResponse, HealthStatus, DependencyStatus};
/// 健康检查
pub async fn health_check() -> impl IntoResponse {
let mut health_response = HealthResponse::new(
HealthStatus::Healthy,
env!("CARGO_PKG_VERSION"),
format_uptime(),
);
// 检查数据库连接
match check_database().await {
Ok(response_time) => {
health_response = health_response.with_dependency(
"database",
DependencyStatus::healthy(response_time),
);
}
Err(e) => {
health_response = health_response.with_dependency(
"database",
DependencyStatus::unhealthy(e.to_string()),
);
}
}
// 检查 Redis 连接
match check_redis().await {
Ok(response_time) => {
health_response = health_response.with_dependency(
"redis",
DependencyStatus::healthy(response_time),
);
}
Err(e) => {
health_response = health_response.with_dependency(
"redis",
DependencyStatus::unhealthy(e.to_string()),
);
}
}
ApiResponse::success(health_response)
}
```
## 中间件集成
### 响应处理中间件
```rust
use axum::{
extract::Request,
middleware::Next,
response::Response,
};
use uuid::Uuid;
/// 响应处理中间件
pub async fn response_middleware(
mut request: Request,
next: Next,
) -> Response {
// 生成请求 ID
let request_id = Uuid::new_v4().to_string();
request.extensions_mut().insert(request_id.clone());
// 执行请求
let mut response = next.run(request).await;
// 添加响应头
response.headers_mut().insert(
"X-Request-ID",
request_id.parse().unwrap(),
);
response.headers_mut().insert(
"X-Response-Time",
chrono::Utc::now().timestamp_millis().to_string().parse().unwrap(),
);
response
}
```
### 响应压缩中间件
```rust
use axum::{
body::Body,
http::{HeaderValue, header},
response::Response,
};
use tower_http::compression::CompressionLayer;
/// 响应压缩配置
pub fn compression_layer() -> CompressionLayer {
CompressionLayer::new()
.gzip(true)
.deflate(true)
.br(true)
}
```
## 性能优化
### 响应缓存
```rust
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;
use chrono::{DateTime, Utc, Duration};
/// 响应缓存
#[derive(Clone)]
pub struct ResponseCache {
cache: Arc<RwLock<HashMap<String, CachedResponse>>>,
}
#[derive(Clone)]
struct CachedResponse {
data: Vec<u8>,
expires_at: DateTime<Utc>,
content_type: String,
}
impl ResponseCache {
pub fn new() -> Self {
Self {
cache: Arc::new(RwLock::new(HashMap::new())),
}
}
/// 获取缓存的响应
pub async fn get(&self, key: &str) -> Option<Response<Body>> {
let cache = self.cache.read().await;
if let Some(cached) = cache.get(key) {
if cached.expires_at > Utc::now() {
let mut response = Response::new(Body::from(cached.data.clone()));
response.headers_mut().insert(
header::CONTENT_TYPE,
HeaderValue::from_str(&cached.content_type).unwrap(),
);
response.headers_mut().insert(
"X-Cache",
HeaderValue::from_static("HIT"),
);
return Some(response);
}
}
None
}
/// 设置响应缓存
pub async fn set(
&self,
key: String,
data: Vec<u8>,
content_type: String,
ttl: Duration,
) {
let mut cache = self.cache.write().await;
cache.insert(key, CachedResponse {
data,
expires_at: Utc::now() + ttl,
content_type,
});
}
/// 清理过期缓存
pub async fn cleanup_expired(&self) {
let mut cache = self.cache.write().await;
let now = Utc::now();
cache.retain(|_, cached| cached.expires_at > now);
}
}
```
### 响应流式处理
```rust
use axum::{
body::Body,
response::{IntoResponse, Response},
};
use futures::stream::{self, StreamExt};
use tokio_util::codec::{FramedWrite, LinesCodec};
/// 流式响应
pub struct StreamResponse<T> {
items: Vec<T>,
}
impl<T> StreamResponse<T>
where
T: Serialize + Send + 'static,
{
pub fn new(items: Vec<T>) -> Self {
Self { items }
}
}
impl<T> IntoResponse for StreamResponse<T>
where
T: Serialize + Send + 'static,
{
fn into_response(self) -> Response {
let stream = stream::iter(self.items)
.map(|item| {
serde_json::to_string(&item)
.map(|json| format!("{}\n", json))
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))
})
.map(|result| result.map(axum::body::Bytes::from));
let body = Body::from_stream(stream);
Response::builder()
.header("Content-Type", "application/x-ndjson")
.header("Transfer-Encoding", "chunked")
.body(body)
.unwrap()
}
}
```
## 测试支持
### 响应测试工具
```rust
use axum::{
body::Body,
http::{Request, StatusCode},
};
use tower::ServiceExt;
use serde::de::DeserializeOwned;
/// 响应测试助手
pub struct ResponseTester;
impl ResponseTester {
/// 测试成功响应
pub async fn assert_success_response<T>(
response: axum::response::Response,
expected_code: u16,
) -> T
where
T: DeserializeOwned,
{
assert_eq!(response.status(), StatusCode::from_u16(expected_code).unwrap());
let body = hyper::body::to_bytes(response.into_body()).await.unwrap();
let api_response: ApiResponse<T> = serde_json::from_slice(&body).unwrap();
assert_eq!(api_response.code, expected_code);
api_response.data.unwrap()
}
/// 测试分页响应
pub async fn assert_page_response<T>(
response: axum::response::Response,
expected_total: u64,
) -> PageResponse<T>
where
T: DeserializeOwned,
{
let page_data: PageResponse<T> = Self::assert_success_response(response, 200).await;
assert_eq!(page_data.pagination.total_count, expected_total);
page_data
}
/// 测试错误响应
pub async fn assert_error_response(
response: axum::response::Response,
expected_status: StatusCode,
expected_error_code: &str,
) {
assert_eq!(response.status(), expected_status);
let body = hyper::body::to_bytes(response.into_body()).await.unwrap();
let error_response: crate::error::ErrorResponse =
serde_json::from_slice(&body).unwrap();
assert_eq!(error_response.error.code, expected_error_code);
}
}
#[cfg(test)]
mod response_tests {
use super::*;
use axum::{
routing::get,
Router,
};
async fn test_handler() -> impl IntoResponse {
ok_response!("test data")
}
#[tokio::test]
async fn test_success_response_integration() {
let app = Router::new().route("/test", get(test_handler));
let request = Request::builder()
.uri("/test")
.body(Body::empty())
.unwrap();
let response = app.oneshot(request).await.unwrap();
let data: String = ResponseTester::assert_success_response(response, 200).await;
assert_eq!(data, "test data");
}
}
```
## 最佳实践
### 响应设计原则
1. **一致性**: 所有接口使用统一的响应格式
2. **可预测性**: 响应结构应该是可预测的
3. **信息完整性**: 包含足够的信息用于客户端处理
4. **向后兼容性**: 新增字段不应破坏现有客户端
### 性能考虑
1. **序列化优化**: 使用高效的序列化库
2. **响应压缩**: 对大响应启用压缩
3. **缓存策略**: 对适当的响应启用缓存
4. **流式处理**: 对大数据集使用流式响应
### 安全考虑
1. **敏感信息**: 避免在响应中暴露敏感信息
2. **错误信息**: 错误响应不应泄露系统内部信息
3. **响应头**: 设置适当的安全响应头
4. **数据验证**: 确保响应数据的完整性
## 总结
UdminAI 的响应格式模块提供了完整的 API 响应解决方案,具有以下特点:
- **统一格式**: 所有接口使用一致的响应结构
- **类型安全**: 编译时类型检查,避免运行时错误
- **功能丰富**: 支持多种响应类型(成功、分页、批量、统计等)
- **易于使用**: 提供宏和扩展 trait 简化使用
- **高性能**: 支持缓存、压缩和流式处理
- **可测试**: 提供完整的测试支持工具
通过统一的响应格式设计,确保了 API 的一致性、可维护性和用户体验。