From 60dc4dba7a665f8c2e45cafa18301f0b14613b9b Mon Sep 17 00:00:00 2001 From: iven Date: Thu, 7 May 2026 13:51:16 +0800 Subject: [PATCH] =?UTF-8?q?fix(health):=20=E4=BF=AE=E5=A4=8D=205=20?= =?UTF-8?q?=E8=A7=92=E8=89=B2=E6=B7=B1=E5=BA=A6=E6=B5=8B=E8=AF=95=E5=8F=91?= =?UTF-8?q?=E7=8E=B0=E7=9A=84=E6=9D=83=E9=99=90=E8=B6=8A=E6=9D=83=E5=92=8C?= =?UTF-8?q?=E5=91=8A=E8=AD=A6=E7=AB=AF=E7=82=B9=E7=BC=BA=E5=A4=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - auth: token_service 查询 role_permissions/user_roles 添加 deleted_at 过滤, 修复软删除的权限仍被加载到 JWT 的越权漏洞 - health: 新增 GET /health/alerts/{id} 告警详情端点(含 handler + service + 路由) - web: AlertList 操作按钮增加 active 状态判断,修复按钮不显示 - migration: 新增 000127 清理 doctor 角色多余的 health-data.manage/ai.analysis.manage --- apps/web/src/pages/health/AlertList.tsx | 3 +- crates/erp-auth/src/service/token_service.rs | 3 ++ .../erp-health/src/handler/alert_handler.rs | 14 ++++++++ crates/erp-health/src/module.rs | 4 +++ .../erp-health/src/service/alert_service.rs | 15 ++++++++ crates/erp-server/migration/src/lib.rs | 2 ++ ...507_000127_fix_doctor_extra_permissions.rs | 36 +++++++++++++++++++ 7 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 crates/erp-server/migration/src/m20260507_000127_fix_doctor_extra_permissions.rs diff --git a/apps/web/src/pages/health/AlertList.tsx b/apps/web/src/pages/health/AlertList.tsx index 5882c1a..bb35d10 100644 --- a/apps/web/src/pages/health/AlertList.tsx +++ b/apps/web/src/pages/health/AlertList.tsx @@ -200,7 +200,7 @@ export default function AlertList() { render: (_: unknown, record: Alert) => ( - {record.status === 'pending' && ( + {(record.status === 'pending' || record.status === 'active') && ( handleAcknowledge(record)} @@ -218,6 +218,7 @@ export default function AlertList() { )} {(record.status === 'pending' || + record.status === 'active' || record.status === 'acknowledged') && ( ( + State(state): State, + Extension(ctx): Extension, + Path(id): Path, +) -> Result +where + HealthState: FromRef, + S: Clone + Send + Sync + 'static, +{ + require_permission(&ctx, "health.alerts.list")?; + let alert = alert_service::get_alert(&state, ctx.tenant_id, id).await?; + Ok(axum::Json(ApiResponse::ok(alert))) +} + pub async fn acknowledge( State(state): State, Extension(ctx): Extension, diff --git a/crates/erp-health/src/module.rs b/crates/erp-health/src/module.rs index 981a927..a240307 100644 --- a/crates/erp-health/src/module.rs +++ b/crates/erp-health/src/module.rs @@ -760,6 +760,10 @@ impl HealthModule { "/health/alerts", axum::routing::get(alert_handler::list_alerts), ) + .route( + "/health/alerts/{id}", + axum::routing::get(alert_handler::get_alert), + ) .route( "/health/alerts/{id}/acknowledge", axum::routing::put(alert_handler::acknowledge), diff --git a/crates/erp-health/src/service/alert_service.rs b/crates/erp-health/src/service/alert_service.rs index 4cb0e8f..47ca393 100644 --- a/crates/erp-health/src/service/alert_service.rs +++ b/crates/erp-health/src/service/alert_service.rs @@ -13,6 +13,21 @@ use crate::error::{HealthError, HealthResult}; use crate::service::validation; use crate::state::HealthState; +pub async fn get_alert( + state: &HealthState, + tenant_id: Uuid, + alert_id: Uuid, +) -> HealthResult { + let model = alerts::Entity::find_by_id(alert_id) + .filter(alerts::Column::TenantId.eq(tenant_id)) + .filter(alerts::Column::DeletedAt.is_null()) + .one(&state.db) + .await? + .ok_or(HealthError::AlertNotFound)?; + + enrich_alert_response(&state.db, tenant_id, model).await +} + pub async fn list_alerts( state: &HealthState, tenant_id: Uuid, diff --git a/crates/erp-server/migration/src/lib.rs b/crates/erp-server/migration/src/lib.rs index f3f600b..2602d87 100644 --- a/crates/erp-server/migration/src/lib.rs +++ b/crates/erp-server/migration/src/lib.rs @@ -126,6 +126,7 @@ mod m20260505_000123_update_ai_prompts_system_instruction; mod m20260505_000124_freeze_deferred_menus; mod m20260506_000125_restructure_menus_and_roles; mod m20260506_000126_fix_role_permissions_cleanup; +mod m20260507_000127_fix_doctor_extra_permissions; pub struct Migrator; @@ -259,6 +260,7 @@ impl MigratorTrait for Migrator { Box::new(m20260505_000124_freeze_deferred_menus::Migration), Box::new(m20260506_000125_restructure_menus_and_roles::Migration), Box::new(m20260506_000126_fix_role_permissions_cleanup::Migration), + Box::new(m20260507_000127_fix_doctor_extra_permissions::Migration), ] } } diff --git a/crates/erp-server/migration/src/m20260507_000127_fix_doctor_extra_permissions.rs b/crates/erp-server/migration/src/m20260507_000127_fix_doctor_extra_permissions.rs new file mode 100644 index 0000000..35b906c --- /dev/null +++ b/crates/erp-server/migration/src/m20260507_000127_fix_doctor_extra_permissions.rs @@ -0,0 +1,36 @@ +use sea_orm_migration::prelude::*; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + let db = manager.get_connection(); + + // Doctor 移除 health.health-data.manage 和 ai.analysis.manage + // 000125 正确分配了 health.health-data.list 和 ai.analysis.list, + // 但早期迁移分配了 .manage 权限且未被 000126 清理 + let doctor_remove = vec![ + "health.health-data.manage", + "ai.analysis.manage", + ]; + for code in &doctor_remove { + db.execute(sea_orm::Statement::from_string( + sea_orm::DatabaseBackend::Postgres, + format!( + "UPDATE role_permissions SET deleted_at = NOW() \ + FROM roles r, permissions p \ + WHERE role_permissions.role_id = r.id AND role_permissions.permission_id = p.id \ + AND r.code = 'doctor' AND p.code = '{code}' AND role_permissions.deleted_at IS NULL", + ), + )).await?; + } + + Ok(()) + } + + async fn down(&self, _manager: &SchemaManager) -> Result<(), DbErr> { + Ok(()) + } +}