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

30 KiB
Raw Permalink Blame History

UdminAI 响应格式模块文档

概述

UdminAI 项目的响应格式模块定义了统一的 API 响应结构,确保所有接口返回一致的数据格式。该模块基于 Axum 框架和 Serde 序列化库构建,提供了类型安全的响应处理机制。

设计原则

核心理念

  • 一致性: 所有 API 接口使用统一的响应格式
  • 类型安全: 编译时类型检查,避免运行时错误
  • 可扩展性: 支持不同类型的响应数据
  • 用户友好: 清晰的响应结构和错误信息
  • 标准化: 遵循 RESTful API 设计规范

响应分类

  1. 成功响应: 操作成功时的数据返回
  2. 分页响应: 列表数据的分页返回
  3. 错误响应: 操作失败时的错误信息
  4. 空响应: 无数据返回的成功操作

响应结构定义 (response.rs)

基础响应类型

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);
        }
    }
}

使用示例

基础响应

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(),
    }
}

分页响应

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(),
    }
}

批量操作响应

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)
}

统计响应

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(),
    }
}

健康检查响应

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)
}

中间件集成

响应处理中间件

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
}

响应压缩中间件

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)
}

性能优化

响应缓存

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);
    }
}

响应流式处理

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()
    }
}

测试支持

响应测试工具

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 的一致性、可维护性和用户体验。