# 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 { /// 响应状态码 pub code: u16, /// 响应消息 pub message: String, /// 响应数据 pub data: Option, /// 响应时间戳 pub timestamp: chrono::DateTime, /// 请求追踪 ID pub request_id: Option, } /// 分页响应结构 #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PageResponse { /// 数据列表 pub items: Vec, /// 分页信息 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 { /// 成功处理的项目 pub success: Vec, /// 失败的项目及错误信息 pub failed: Vec, /// 成功数量 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, /// 统计时间范围 pub time_range: Option, /// 更新时间 pub updated_at: chrono::DateTime, } /// 时间范围 #[derive(Debug, Clone, Serialize, Deserialize)] pub struct TimeRange { /// 开始时间 pub start: chrono::DateTime, /// 结束时间 pub end: chrono::DateTime, } /// 健康检查响应 #[derive(Debug, Clone, Serialize, Deserialize)] pub struct HealthResponse { /// 服务状态 pub status: HealthStatus, /// 版本信息 pub version: String, /// 启动时间 pub uptime: String, /// 依赖服务状态 pub dependencies: HashMap, } /// 健康状态 #[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, /// 错误信息 pub error: Option, } impl ApiResponse { /// 创建成功响应 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) -> 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) -> 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) -> Self { self.message = message.into(); self } } impl PageResponse { /// 创建分页响应 pub fn new( items: Vec, 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 BatchResponse { /// 创建批量响应 pub fn new(success: Vec, failed: Vec) -> Self { let success_count = success.len(); let failed_count = failed.len(); Self { success, failed, success_count, failed_count, } } /// 创建全部成功的批量响应 pub fn all_success(success: Vec) -> Self { Self::new(success, Vec::new()) } /// 创建全部失败的批量响应 pub fn all_failed(failed: Vec) -> Self { Self::new(Vec::new(), failed) } } impl StatsResponse { /// 创建统计响应 pub fn new(stats: HashMap) -> Self { Self { stats, time_range: None, updated_at: chrono::Utc::now(), } } /// 设置时间范围 pub fn with_time_range( mut self, start: chrono::DateTime, end: chrono::DateTime, ) -> Self { self.time_range = Some(TimeRange { start, end }); self } } impl HealthResponse { /// 创建健康响应 pub fn new( status: HealthStatus, version: impl Into, uptime: impl Into, ) -> Self { Self { status, version: version.into(), uptime: uptime.into(), dependencies: HashMap::new(), } } /// 添加依赖状态 pub fn with_dependency( mut self, name: impl Into, 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) -> Self { Self { status: HealthStatus::Unhealthy, response_time_ms: None, error: Some(error.into()), } } /// 创建降级的依赖状态 pub fn degraded(response_time_ms: u64, error: impl Into) -> Self { Self { status: HealthStatus::Degraded, response_time_ms: Some(response_time_ms), error: Some(error.into()), } } } /// 实现 IntoResponse,使响应可以直接返回 impl IntoResponse for ApiResponse 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 { response: ApiResponse, } impl ResponseBuilder { /// 创建新的响应构建器 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) -> Self { self.response.message = message.into(); self } /// 设置请求 ID pub fn request_id(mut self, request_id: impl Into) -> Self { self.response.request_id = Some(request_id.into()); self } /// 构建响应 pub fn build(self) -> ApiResponse { 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 { /// 转换为 API 响应 fn into_api_response(self) -> ApiResponse; /// 转换为分页响应 fn into_page_response( self, current_page: u64, page_size: u64, total_count: u64, ) -> ApiResponse> where Self: IntoIterator, Self::IntoIter: ExactSizeIterator; } impl ResponseExt for T { fn into_api_response(self) -> ApiResponse { ApiResponse::success(self) } fn into_page_response( self, current_page: u64, page_size: u64, total_count: u64, ) -> ApiResponse> where Self: IntoIterator, Self::IntoIter: ExactSizeIterator, { let items: Vec = self.into_iter().collect(); let page_response = PageResponse::new(items, current_page, page_size, total_count); ApiResponse::success(page_response) } } impl ResponseExt for Vec { fn into_api_response(self) -> ApiResponse> { ApiResponse::success(self) } fn into_page_response( self, current_page: u64, page_size: u64, total_count: u64, ) -> ApiResponse> where Self: IntoIterator, Self::IntoIter: ExactSizeIterator, { let page_response = PageResponse::new(self, current_page, page_size, total_count); ApiResponse::success(page_response) } } /// 常用响应类型别名 pub type JsonResponse = ApiResponse; pub type ListResponse = ApiResponse>; pub type PageResp = ApiResponse>; pub type BatchResp = ApiResponse>; pub type StatsResp = ApiResponse; pub type HealthResp = ApiResponse; #[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) -> 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) -> 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) -> 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) -> 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, ) -> 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>>, } #[derive(Clone)] struct CachedResponse { data: Vec, expires_at: DateTime, 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> { 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, 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 { items: Vec, } impl StreamResponse where T: Serialize + Send + 'static, { pub fn new(items: Vec) -> Self { Self { items } } } impl IntoResponse for StreamResponse 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( 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 = serde_json::from_slice(&body).unwrap(); assert_eq!(api_response.code, expected_code); api_response.data.unwrap() } /// 测试分页响应 pub async fn assert_page_response( response: axum::response::Response, expected_total: u64, ) -> PageResponse where T: DeserializeOwned, { let page_data: PageResponse = 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 的一致性、可维护性和用户体验。