删除内容: - 前端: health/(67文件), ai/(2文件), Copilot, MediaPicker, 相关API/Store/Hook - 后端: wechat_handler, wechat_service, wechat_user entity, analytics handler, ai_workflow_seed - 配置: WechatConfig, AppConfig.wechat, AuthState wechat 字段 - 启动: 微信凭据检查块, ensure_ai_workflows() 调用 - 迁移: 新增 m20260613_000170_drop_wechat_users.rs - 脚本: api_test_health_alert.py, api_test_mp.py, mpsync.sh/ps1 - E2E: health-data page, flows/ 目录 保留: erp-core/auth/workflow/message/config/plugin + 基座前端 + 通用组件
136 lines
4.0 KiB
Rust
136 lines
4.0 KiB
Rust
use axum::Router;
|
|
use axum::extract::State;
|
|
use axum::response::Json;
|
|
use axum::routing::get;
|
|
use serde::Serialize;
|
|
|
|
use crate::state::AppState;
|
|
|
|
#[derive(Debug, Serialize)]
|
|
pub struct HealthResponse {
|
|
pub status: String,
|
|
pub version: String,
|
|
pub modules: Vec<String>,
|
|
}
|
|
|
|
/// GET /health — 轻量存活检查
|
|
pub async fn health_check(State(state): State<AppState>) -> Json<HealthResponse> {
|
|
let modules = state
|
|
.module_registry
|
|
.modules()
|
|
.iter()
|
|
.map(|m| m.name().to_string())
|
|
.collect();
|
|
|
|
Json(HealthResponse {
|
|
status: "ok".to_string(),
|
|
version: env!("CARGO_PKG_VERSION").to_string(),
|
|
modules,
|
|
})
|
|
}
|
|
|
|
#[derive(Debug, Serialize)]
|
|
pub struct ReadyResponse {
|
|
pub status: String,
|
|
pub version: String,
|
|
pub database: ComponentStatus,
|
|
pub redis: ComponentStatus,
|
|
pub modules: Vec<String>,
|
|
}
|
|
|
|
#[derive(Debug, Serialize)]
|
|
pub struct ComponentStatus {
|
|
pub status: String,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub latency_ms: Option<u64>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub error: Option<String>,
|
|
}
|
|
|
|
/// GET /health/ready — 就绪检查(含 DB + Redis 连通性)
|
|
pub async fn readiness_check(State(state): State<AppState>) -> Json<ReadyResponse> {
|
|
let modules = state
|
|
.module_registry
|
|
.modules()
|
|
.iter()
|
|
.map(|m| m.name().to_string())
|
|
.collect();
|
|
|
|
let (db_status, redis_status) =
|
|
tokio::join!(check_database(&state.db), check_redis(&state.redis),);
|
|
|
|
let overall = if db_status.status == "ok" && redis_status.status == "ok" {
|
|
"ok"
|
|
} else if db_status.status == "ok" {
|
|
"degraded"
|
|
} else {
|
|
"unavailable"
|
|
};
|
|
|
|
Json(ReadyResponse {
|
|
status: overall.to_string(),
|
|
version: env!("CARGO_PKG_VERSION").to_string(),
|
|
database: db_status,
|
|
redis: redis_status,
|
|
modules,
|
|
})
|
|
}
|
|
|
|
async fn check_database(db: &sea_orm::DatabaseConnection) -> ComponentStatus {
|
|
use sea_orm::ConnectionTrait;
|
|
let start = std::time::Instant::now();
|
|
let stmt =
|
|
sea_orm::Statement::from_string(sea_orm::DatabaseBackend::Postgres, "SELECT 1".to_string());
|
|
match db.query_one(stmt).await {
|
|
Ok(_) => ComponentStatus {
|
|
status: "ok".to_string(),
|
|
latency_ms: Some(start.elapsed().as_millis() as u64),
|
|
error: None,
|
|
},
|
|
Err(e) => {
|
|
tracing::error!(error = %e, "Database health check failed");
|
|
ComponentStatus {
|
|
status: "error".to_string(),
|
|
latency_ms: Some(start.elapsed().as_millis() as u64),
|
|
error: Some("connection failed".to_string()),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
async fn check_redis(client: &redis::Client) -> ComponentStatus {
|
|
let start = std::time::Instant::now();
|
|
match client.get_multiplexed_async_connection().await {
|
|
Ok(mut conn) => match redis::cmd("PING").query_async::<String>(&mut conn).await {
|
|
Ok(_) => ComponentStatus {
|
|
status: "ok".to_string(),
|
|
latency_ms: Some(start.elapsed().as_millis() as u64),
|
|
error: None,
|
|
},
|
|
Err(e) => {
|
|
tracing::error!(error = %e, "Redis PING failed");
|
|
ComponentStatus {
|
|
status: "error".to_string(),
|
|
latency_ms: Some(start.elapsed().as_millis() as u64),
|
|
error: Some("connection failed".to_string()),
|
|
}
|
|
}
|
|
},
|
|
Err(e) => {
|
|
tracing::error!(error = %e, "Redis connection failed");
|
|
ComponentStatus {
|
|
status: "error".to_string(),
|
|
latency_ms: Some(start.elapsed().as_millis() as u64),
|
|
error: Some("connection failed".to_string()),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn health_check_router() -> Router<AppState> {
|
|
Router::new()
|
|
.route("/health", get(health_check))
|
|
.route("/health/live", get(health_check))
|
|
.route("/health/ready", get(readiness_check))
|
|
}
|