Files
hms/crates/erp-health/src/module.rs
iven 6a7d83ec4d
Some checks failed
CI / rust-check (push) Has been cancelled
CI / rust-test (push) Has been cancelled
CI / frontend-build (push) Has been cancelled
CI / security-audit (push) Has been cancelled
refactor(health): 集中管理事件类型常量 + 积分过期发布事件
- event.rs 新增 20 个事件类型常量(PATIENT_CREATED 等)
- 10 个 service 文件引用常量替代硬编码字符串
- expire_points 增加 EventBus 参数,处理完成后发布 points.expired 事件
- start_points_expiration_checker 传入 EventBus
2026-04-27 11:11:33 +08:00

831 lines
33 KiB
Rust
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.
use axum::Router;
use uuid::Uuid;
use erp_core::error::AppResult;
use erp_core::events::EventBus;
use erp_core::module::{ErpModule, PermissionDescriptor};
use crate::handler::{
alert_handler, alert_rule_handler,
appointment_handler, article_category_handler, article_handler, article_tag_handler, consultation_handler, consent_handler, critical_value_threshold_handler, daily_monitoring_handler, device_reading_handler, diagnosis_handler, dialysis_handler, doctor_handler, follow_up_handler,
health_data_handler, patient_handler, points_handler, stats_handler,
};
pub struct HealthModule;
impl HealthModule {
pub fn new() -> Self {
Self
}
/// 启动定时逾期随访检查(每 6 小时运行一次),返回 JoinHandle 用于优雅关闭
pub fn start_overdue_checker(db: sea_orm::DatabaseConnection) -> tokio::task::JoinHandle<()> {
tokio::spawn(async move {
let mut interval = tokio::time::interval(std::time::Duration::from_secs(6 * 3600));
loop {
tokio::select! {
_ = interval.tick() => {
match crate::service::follow_up_service::check_overdue_tasks(&db).await {
Ok(count) if count > 0 => tracing::info!(count = count, "随访逾期检查完成"),
Ok(_) => {}
Err(e) => tracing::warn!(error = %e, "随访逾期检查失败"),
}
}
_ = tokio::signal::ctrl_c() => {
tracing::info!("随访逾期检查任务收到关闭信号,正在停止");
break;
}
}
}
})
}
/// 启动积分过期清理(每 24 小时运行一次),返回 JoinHandle 用于优雅关闭
pub fn start_points_expiration_checker(db: sea_orm::DatabaseConnection, event_bus: erp_core::events::EventBus) -> tokio::task::JoinHandle<()> {
tokio::spawn(async move {
let mut interval = tokio::time::interval(std::time::Duration::from_secs(24 * 3600));
loop {
tokio::select! {
_ = interval.tick() => {
match crate::service::points_service::expire_points(&db, &event_bus).await {
Ok(count) if count > 0 => tracing::info!(count = count, "积分过期清理完成"),
Ok(_) => {}
Err(e) => tracing::warn!(error = %e, "积分过期清理失败"),
}
}
_ = tokio::signal::ctrl_c() => {
tracing::info!("积分过期清理任务收到关闭信号,正在停止");
break;
}
}
}
})
}
pub fn public_routes<S>() -> Router<S>
where
crate::state::HealthState: axum::extract::FromRef<S>,
S: Clone + Send + Sync + 'static,
{
Router::new()
}
pub fn protected_routes<S>() -> Router<S>
where
crate::state::HealthState: axum::extract::FromRef<S>,
S: Clone + Send + Sync + 'static,
{
Router::new()
// 患者管理
.route(
"/health/patients",
axum::routing::get(patient_handler::list_patients)
.post(patient_handler::create_patient),
)
.route(
"/health/patients/{id}",
axum::routing::get(patient_handler::get_patient)
.put(patient_handler::update_patient)
.delete(patient_handler::delete_patient),
)
.route(
"/health/patients/{id}/tags",
axum::routing::post(patient_handler::manage_patient_tags),
)
.route(
"/health/patient-tags",
axum::routing::get(patient_handler::list_tags)
.post(patient_handler::create_tag),
)
.route(
"/health/patient-tags/{id}",
axum::routing::put(patient_handler::update_tag)
.delete(patient_handler::delete_tag),
)
.route(
"/health/patients/{id}/health-summary",
axum::routing::get(patient_handler::get_health_summary),
)
.route(
"/health/patients/{id}/family-members",
axum::routing::get(patient_handler::list_family_members)
.post(patient_handler::create_family_member),
)
.route(
"/health/patients/{id}/family-members/{fid}",
axum::routing::put(patient_handler::update_family_member)
.delete(patient_handler::delete_family_member),
)
.route(
"/health/patients/{id}/doctors",
axum::routing::post(patient_handler::assign_doctor),
)
.route(
"/health/patients/{id}/doctors/{did}",
axum::routing::delete(patient_handler::remove_doctor),
)
// 健康数据
.route(
"/health/patients/{id}/vital-signs",
axum::routing::get(health_data_handler::list_vital_signs)
.post(health_data_handler::create_vital_signs),
)
// 诊断记录
.route(
"/health/patients/{id}/diagnoses",
axum::routing::get(diagnosis_handler::list_diagnoses)
.post(diagnosis_handler::create_diagnosis),
)
.route(
"/health/diagnoses/{id}",
axum::routing::put(diagnosis_handler::update_diagnosis)
.delete(diagnosis_handler::delete_diagnosis),
)
.route(
"/health/patients/{id}/vital-signs/{vid}",
axum::routing::put(health_data_handler::update_vital_signs)
.delete(health_data_handler::delete_vital_signs),
)
.route(
"/health/patients/{id}/lab-reports",
axum::routing::get(health_data_handler::list_lab_reports)
.post(health_data_handler::create_lab_report),
)
.route(
"/health/patients/{id}/lab-reports/{rid}",
axum::routing::put(health_data_handler::update_lab_report)
.delete(health_data_handler::delete_lab_report),
)
.route(
"/health/patients/{id}/health-records",
axum::routing::get(health_data_handler::list_health_records)
.post(health_data_handler::create_health_record),
)
.route(
"/health/patients/{id}/health-records/{rid}",
axum::routing::put(health_data_handler::update_health_record)
.delete(health_data_handler::delete_health_record),
)
.route(
"/health/patients/{id}/trends",
axum::routing::get(health_data_handler::list_trends),
)
.route(
"/health/patients/{id}/trends/generate",
axum::routing::post(health_data_handler::generate_trend),
)
.route(
"/health/patients/{id}/trends/{indicator}",
axum::routing::get(health_data_handler::get_indicator_timeseries),
)
// 小程序趋势查询(通过 JWT user_id 关联 patient无需传 patient_id
.route(
"/health/vital-signs/trend",
axum::routing::get(health_data_handler::get_mini_trend),
)
// 小程序今日体征摘要
.route(
"/health/vital-signs/today",
axum::routing::get(health_data_handler::get_mini_today),
)
// 透析记录(血透专科)
.route(
"/health/patients/{id}/dialysis-records",
axum::routing::get(dialysis_handler::list_dialysis_records),
)
.route(
"/health/dialysis-records",
axum::routing::post(dialysis_handler::create_dialysis_record),
)
.route(
"/health/dialysis-records/{id}",
axum::routing::get(dialysis_handler::get_dialysis_record)
.put(dialysis_handler::update_dialysis_record)
.delete(dialysis_handler::delete_dialysis_record),
)
.route(
"/health/dialysis-records/{id}/review",
axum::routing::put(dialysis_handler::review_dialysis_record),
)
// 日常监测
.route(
"/health/patients/{id}/daily-monitoring",
axum::routing::get(daily_monitoring_handler::list_daily_monitoring),
)
.route(
"/health/daily-monitoring",
axum::routing::post(daily_monitoring_handler::create_daily_monitoring),
)
.route(
"/health/daily-monitoring/{id}",
axum::routing::get(daily_monitoring_handler::get_daily_monitoring)
.put(daily_monitoring_handler::update_daily_monitoring)
.delete(daily_monitoring_handler::delete_daily_monitoring),
)
// 化验报告审阅
.route(
"/health/patients/{id}/lab-reports/{rid}/review",
axum::routing::put(health_data_handler::review_lab_report),
)
// 预约排班
.route(
"/health/appointments",
axum::routing::get(appointment_handler::list_appointments)
.post(appointment_handler::create_appointment),
)
.route(
"/health/appointments/{id}/status",
axum::routing::put(appointment_handler::update_appointment_status),
)
.route(
"/health/appointments/{id}",
axum::routing::get(appointment_handler::get_appointment),
)
.route(
"/health/doctor-schedules",
axum::routing::get(appointment_handler::list_schedules)
.post(appointment_handler::create_schedule),
)
.route(
"/health/doctor-schedules/{id}",
axum::routing::put(appointment_handler::update_schedule),
)
.route(
"/health/doctor-schedules/calendar",
axum::routing::get(appointment_handler::calendar_view),
)
// 随访管理
.route(
"/health/follow-up-tasks",
axum::routing::get(follow_up_handler::list_tasks)
.post(follow_up_handler::create_task),
)
.route(
"/health/follow-up-tasks/{id}",
axum::routing::get(follow_up_handler::get_task)
.put(follow_up_handler::update_task)
.delete(follow_up_handler::delete_task),
)
.route(
"/health/follow-up-tasks/{id}/records",
axum::routing::post(follow_up_handler::create_record),
)
.route(
"/health/follow-up-records",
axum::routing::get(follow_up_handler::list_records),
)
// 咨询管理
.route(
"/health/consultation-sessions",
axum::routing::get(consultation_handler::list_sessions)
.post(consultation_handler::create_session),
)
.route(
"/health/consultation-sessions/export",
axum::routing::get(consultation_handler::export_sessions),
)
.route(
"/health/consultation-sessions/{id}",
axum::routing::get(consultation_handler::get_session),
)
.route(
"/health/consultation-sessions/{id}/messages",
axum::routing::get(consultation_handler::list_messages),
)
.route(
"/health/consultation-sessions/{id}/close",
axum::routing::put(consultation_handler::close_session),
)
.route(
"/health/consultation-sessions/{id}/read",
axum::routing::put(consultation_handler::mark_session_read),
)
.route(
"/health/consultation-messages",
axum::routing::post(consultation_handler::create_message),
)
// 医生仪表盘
.route(
"/health/doctor/dashboard",
axum::routing::get(consultation_handler::get_doctor_dashboard),
)
// 医护管理
.route(
"/health/doctors",
axum::routing::get(doctor_handler::list_doctors)
.post(doctor_handler::create_doctor),
)
.route(
"/health/doctors/{id}",
axum::routing::get(doctor_handler::get_doctor)
.put(doctor_handler::update_doctor)
.delete(doctor_handler::delete_doctor),
)
// 健康资讯
.route(
"/health/articles",
axum::routing::get(article_handler::list_articles)
.post(article_handler::create_article),
)
.route(
"/health/articles/{id}",
axum::routing::get(article_handler::get_article)
.put(article_handler::update_article)
.delete(article_handler::delete_article),
)
// 资讯审核工作流
.route(
"/health/articles/{id}/submit",
axum::routing::post(article_handler::submit_article),
)
.route(
"/health/articles/{id}/approve",
axum::routing::post(article_handler::approve_article),
)
.route(
"/health/articles/{id}/reject",
axum::routing::post(article_handler::reject_article),
)
.route(
"/health/articles/{id}/unpublish",
axum::routing::post(article_handler::unpublish_article),
)
.route(
"/health/articles/{id}/view",
axum::routing::post(article_handler::view_article),
)
// 资讯分类
.route(
"/health/article-categories",
axum::routing::get(article_category_handler::list_categories)
.post(article_category_handler::create_category),
)
.route(
"/health/article-categories/{id}",
axum::routing::put(article_category_handler::update_category)
.delete(article_category_handler::delete_category),
)
// 资讯标签
.route(
"/health/article-tags",
axum::routing::get(article_tag_handler::list_tags)
.post(article_tag_handler::create_tag),
)
.route(
"/health/article-tags/{id}",
axum::routing::put(article_tag_handler::update_tag)
.delete(article_tag_handler::delete_tag),
)
// 积分商城 — 患者端
.route(
"/health/points/account",
axum::routing::get(points_handler::get_my_account),
)
.route(
"/health/points/checkin",
axum::routing::post(points_handler::daily_checkin),
)
.route(
"/health/points/checkin/status",
axum::routing::get(points_handler::get_checkin_status),
)
.route(
"/health/points/transactions",
axum::routing::get(points_handler::list_my_transactions),
)
.route(
"/health/points/products",
axum::routing::get(points_handler::list_products),
)
.route(
"/health/points/products/{id}",
axum::routing::get(points_handler::get_product),
)
.route(
"/health/points/exchange",
axum::routing::post(points_handler::exchange_product),
)
.route(
"/health/points/orders",
axum::routing::get(points_handler::list_my_orders),
)
// 线下活动 — 患者端
.route(
"/health/offline-events",
axum::routing::get(points_handler::list_offline_events),
)
.route(
"/health/offline-events/{id}/register",
axum::routing::post(points_handler::register_event),
)
// 积分商城 — 管理端
.route(
"/health/points/verify",
axum::routing::post(points_handler::verify_order),
)
.route(
"/health/admin/points/rules",
axum::routing::get(points_handler::list_rules)
.post(points_handler::create_rule),
)
.route(
"/health/admin/points/rules/{id}",
axum::routing::put(points_handler::update_rule)
.delete(points_handler::delete_rule),
)
.route(
"/health/admin/points/products",
axum::routing::post(points_handler::admin_create_product),
)
.route(
"/health/admin/points/products/{id}",
axum::routing::put(points_handler::admin_update_product)
.delete(points_handler::admin_delete_product),
)
.route(
"/health/admin/points/orders",
axum::routing::get(points_handler::admin_list_orders),
)
// 线下活动 — 管理端
.route(
"/health/admin/offline-events",
axum::routing::get(points_handler::admin_list_events)
.post(points_handler::admin_create_event),
)
.route(
"/health/admin/offline-events/{id}",
axum::routing::put(points_handler::admin_update_event)
.delete(points_handler::admin_delete_event),
)
.route(
"/health/admin/offline-events/{id}/checkin",
axum::routing::post(points_handler::admin_checkin_event),
)
// 积分统计 — 管理端
.route(
"/health/admin/points/statistics",
axum::routing::get(points_handler::get_points_statistics),
)
// 统计数据 — 管理端
.route(
"/health/admin/statistics/patients",
axum::routing::get(stats_handler::get_patient_stats),
)
.route(
"/health/admin/statistics/consultations",
axum::routing::get(stats_handler::get_consultation_stats),
)
.route(
"/health/admin/statistics/follow-ups",
axum::routing::get(stats_handler::get_follow_up_stats),
)
.route(
"/health/admin/statistics/dashboard",
axum::routing::get(stats_handler::get_dashboard_stats),
)
.route(
"/health/admin/statistics/dialysis",
axum::routing::get(stats_handler::get_dialysis_stats),
)
.route(
"/health/admin/statistics/lab-reports",
axum::routing::get(stats_handler::get_lab_report_stats),
)
.route(
"/health/admin/statistics/appointments",
axum::routing::get(stats_handler::get_appointment_stats),
)
.route(
"/health/admin/statistics/vital-signs-report-rate",
axum::routing::get(stats_handler::get_vital_signs_report_rate),
)
.route(
"/health/admin/statistics/health-data",
axum::routing::get(stats_handler::get_health_data_stats),
)
// 危急值阈值配置
.route(
"/health/critical-value-thresholds",
axum::routing::get(critical_value_threshold_handler::list_thresholds)
.post(critical_value_threshold_handler::create_threshold),
)
.route(
"/health/critical-value-thresholds/{id}",
axum::routing::put(critical_value_threshold_handler::update_threshold)
.delete(critical_value_threshold_handler::delete_threshold),
)
// 知情同意记录
.route(
"/health/patients/{patient_id}/consents",
axum::routing::get(consent_handler::list_consents),
)
.route(
"/health/consents",
axum::routing::post(consent_handler::grant_consent),
)
.route(
"/health/consents/{consent_id}/revoke",
axum::routing::put(consent_handler::revoke_consent),
)
// 设备数据采集
.route(
"/health/patients/{patient_id}/device-readings/batch",
axum::routing::post(device_reading_handler::batch_create),
)
.route(
"/health/patients/{patient_id}/device-readings",
axum::routing::get(device_reading_handler::list_readings),
)
.route(
"/health/patients/{patient_id}/device-readings/hourly",
axum::routing::get(device_reading_handler::list_hourly),
)
// 告警路由
.route(
"/health/alerts",
axum::routing::get(alert_handler::list_alerts),
)
.route(
"/health/alerts/{id}/acknowledge",
axum::routing::put(alert_handler::acknowledge),
)
.route(
"/health/alerts/{id}/dismiss",
axum::routing::put(alert_handler::dismiss),
)
.route(
"/health/alerts/{id}/resolve",
axum::routing::put(alert_handler::resolve),
)
.route(
"/health/alert-rules",
axum::routing::get(alert_rule_handler::list_rules)
.post(alert_rule_handler::create),
)
.route(
"/health/alert-rules/{id}",
axum::routing::put(alert_rule_handler::update),
)
.route(
"/health/alert-rules/{id}/deactivate",
axum::routing::put(alert_rule_handler::deactivate),
)
}
}
impl Default for HealthModule {
fn default() -> Self {
Self::new()
}
}
#[async_trait::async_trait]
impl ErpModule for HealthModule {
fn name(&self) -> &str {
"health"
}
fn version(&self) -> &str {
env!("CARGO_PKG_VERSION")
}
fn dependencies(&self) -> Vec<&str> {
vec!["auth"]
}
fn register_event_handlers(&self, _bus: &EventBus) {
// 事件处理器已迁移到 on_startup此处保留空实现以兼容 trait 签名
}
async fn on_startup(&self, ctx: &erp_core::module::ModuleContext) -> erp_core::error::AppResult<()> {
let crypto = match erp_core::crypto::PiiCrypto::from_kek_hex(
&std::env::var("ERP__CRYPTO__KEK").unwrap_or_default(),
) {
Ok(c) => c,
Err(_) => {
#[cfg(debug_assertions)]
{
tracing::warn!("ERP__CRYPTO__KEK 未设置或无效,使用开发默认密钥");
erp_core::crypto::PiiCrypto::dev_default()
}
#[cfg(not(debug_assertions))]
{
panic!("ERP__CRYPTO__KEK 必须设置为有效的 64 字符 hex 字符串(生产环境不允许回退到开发密钥)");
}
}
};
let state = crate::state::HealthState {
db: ctx.db.clone(),
event_bus: ctx.event_bus.clone(),
crypto,
};
crate::event::register_handlers_with_state(state.clone());
tracing::info!(module = "health", "Health module event handlers registered via on_startup");
// 启动逾期随访检查器(立即执行一次 + 每 6 小时重复)
{
let state_clone = state.clone();
tokio::spawn(async move {
match crate::service::follow_up_service::check_overdue_and_notify(&state_clone).await {
Ok(count) if count > 0 => tracing::info!(count = count, "启动时逾期随访检查完成"),
Ok(_) => tracing::info!("启动时逾期随访检查完成(无逾期任务)"),
Err(e) => tracing::warn!(error = %e, "启动时逾期随访检查失败"),
}
});
}
let _overdue_handle = Self::start_overdue_checker(state.db.clone());
tracing::info!(module = "health", "Overdue follow-up checker started");
// 启动积分过期清理(启动时执行一次 + 每 24 小时重复)
{
let db = ctx.db.clone();
let event_bus = ctx.event_bus.clone();
tokio::spawn(async move {
match crate::service::points_service::expire_points(&db, &event_bus).await {
Ok(count) if count > 0 => tracing::info!(count = count, "启动时积分过期清理完成"),
Ok(_) => tracing::info!("启动时积分过期清理完成(无过期积分)"),
Err(e) => tracing::warn!(error = %e, "启动时积分过期清理失败"),
}
});
}
let _expire_handle = Self::start_points_expiration_checker(ctx.db.clone(), ctx.event_bus.clone());
tracing::info!(module = "health", "Points expiration checker started");
Ok(())
}
async fn on_tenant_created(
&self,
tenant_id: Uuid,
db: &sea_orm::DatabaseConnection,
_event_bus: &EventBus,
) -> AppResult<()> {
crate::service::seed::seed_tenant_health(db, tenant_id)
.await
.map_err(|e| erp_core::error::AppError::Internal(e.to_string()))?;
tracing::info!(tenant_id = %tenant_id, "Health module tenant initialized");
Ok(())
}
async fn on_tenant_deleted(
&self,
tenant_id: Uuid,
db: &sea_orm::DatabaseConnection,
) -> AppResult<()> {
crate::service::seed::soft_delete_tenant_data(db, tenant_id)
.await
.map_err(|e| erp_core::error::AppError::Internal(e.to_string()))?;
tracing::info!(tenant_id = %tenant_id, "Health module tenant data soft-deleted");
Ok(())
}
fn permissions(&self) -> Vec<PermissionDescriptor> {
vec![
PermissionDescriptor {
code: "health.patient.list".into(),
name: "查看患者列表".into(),
description: "查看和搜索患者列表、详情".into(),
module: "health".into(),
},
PermissionDescriptor {
code: "health.patient.manage".into(),
name: "管理患者".into(),
description: "创建、编辑、删除患者".into(),
module: "health".into(),
},
PermissionDescriptor {
code: "health.health-data.list".into(),
name: "查看健康数据".into(),
description: "查看体检记录、监测数据、化验报告".into(),
module: "health".into(),
},
PermissionDescriptor {
code: "health.health-data.manage".into(),
name: "管理健康数据".into(),
description: "录入、编辑、删除健康数据".into(),
module: "health".into(),
},
PermissionDescriptor {
code: "health.appointment.list".into(),
name: "查看预约".into(),
description: "查看预约列表和排班".into(),
module: "health".into(),
},
PermissionDescriptor {
code: "health.appointment.manage".into(),
name: "管理预约".into(),
description: "创建、确认、取消预约".into(),
module: "health".into(),
},
PermissionDescriptor {
code: "health.follow-up.list".into(),
name: "查看随访".into(),
description: "查看随访任务和记录".into(),
module: "health".into(),
},
PermissionDescriptor {
code: "health.follow-up.manage".into(),
name: "管理随访".into(),
description: "创建、分配、完成随访任务".into(),
module: "health".into(),
},
PermissionDescriptor {
code: "health.consultation.list".into(),
name: "查看咨询".into(),
description: "查看咨询会话和消息记录".into(),
module: "health".into(),
},
PermissionDescriptor {
code: "health.consultation.manage".into(),
name: "管理咨询".into(),
description: "关闭会话、导出记录".into(),
module: "health".into(),
},
PermissionDescriptor {
code: "health.doctor.list".into(),
name: "查看医护".into(),
description: "查看医护列表和详情".into(),
module: "health".into(),
},
PermissionDescriptor {
code: "health.doctor.manage".into(),
name: "管理医护".into(),
description: "创建、编辑医护档案、排班".into(),
module: "health".into(),
},
PermissionDescriptor {
code: "health.articles.list".into(),
name: "查看资讯".into(),
description: "查看健康资讯文章列表和详情".into(),
module: "health".into(),
},
PermissionDescriptor {
code: "health.articles.manage".into(),
name: "管理资讯".into(),
description: "创建、编辑、删除健康资讯文章".into(),
module: "health".into(),
},
PermissionDescriptor {
code: "health.articles.review".into(),
name: "审核资讯".into(),
description: "审核通过或拒绝资讯文章发布".into(),
module: "health".into(),
},
PermissionDescriptor {
code: "health.points.list".into(),
name: "查看积分".into(),
description: "查看积分规则、订单列表".into(),
module: "health".into(),
},
PermissionDescriptor {
code: "health.points.manage".into(),
name: "管理积分".into(),
description: "创建积分规则、管理商品、核销订单".into(),
module: "health".into(),
},
PermissionDescriptor {
code: "health.device-readings.list".into(),
name: "查看设备数据".into(),
description: "查看患者的设备采集数据".into(),
module: "health".into(),
},
PermissionDescriptor {
code: "health.device-readings.manage".into(),
name: "管理设备数据".into(),
description: "提交设备采集数据".into(),
module: "health".into(),
},
PermissionDescriptor {
code: "health.alerts.list".into(),
name: "查看告警".into(),
description: "查看告警记录".into(),
module: "health".into(),
},
PermissionDescriptor {
code: "health.alerts.manage".into(),
name: "管理告警".into(),
description: "确认/处置告警".into(),
module: "health".into(),
},
PermissionDescriptor {
code: "health.alert-rules.list".into(),
name: "查看告警规则".into(),
description: "查看告警规则配置".into(),
module: "health".into(),
},
PermissionDescriptor {
code: "health.alert-rules.manage".into(),
name: "管理告警规则".into(),
description: "创建/编辑/启停告警规则".into(),
module: "health".into(),
},
]
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
}