fix: 修复角色测试发现的 5 个共性问题
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

- 修复前端路由守卫前缀碰撞(/health/articles 匹配 /health/article-categories)
- 补全 6 条缺失路由权限映射(appointments/follow-up-records/article-categories/article-tags/plugins/market)
- 修复 critical-alerts API 500(escalation_level 字段 INT2/i16 与 Entity i32 类型不匹配)
- 新增迁移 000128:告警状态修正 + 菜单权限码补全 + 非admin角色移除基础模块权限
This commit is contained in:
iven
2026-05-07 15:54:37 +08:00
parent 60dc4dba7a
commit 786f57c151
4 changed files with 92 additions and 3 deletions

View File

@@ -106,9 +106,12 @@ const ROUTE_PERMISSIONS: Record<string, string[]> = {
'/messages': ['message.list'],
'/settings': ['config.settings.list', 'config.settings.manage'],
'/plugins/admin': ['plugin.list', 'plugin.manage'],
'/plugins/market': ['plugin.list', 'plugin.manage'],
'/health/patients': ['health.patient.list', 'health.patient.manage'],
'/health/doctors': ['health.doctor.list', 'health.doctor.manage'],
'/health/appointments': ['health.appointment.list', 'health.appointment.manage'],
'/health/follow-up-tasks': ['health.follow-up.list', 'health.follow-up.manage'],
'/health/follow-up-records': ['health.follow-up.list', 'health.follow-up.manage'],
'/health/consultations': ['health.consultation.list', 'health.consultation.manage'],
'/health/action-inbox': ['health.action-inbox.list', 'health.action-inbox.manage'],
'/health/follow-up-templates': ['health.follow-up-templates.list', 'health.follow-up-templates.manage'],
@@ -121,6 +124,8 @@ const ROUTE_PERMISSIONS: Record<string, string[]> = {
'/health/ble-gateways': ['health.ble-gateways.list', 'health.ble-gateways.manage'],
'/health/critical-value-thresholds': ['health.critical-value-thresholds.list', 'health.critical-value-thresholds.manage'],
'/health/articles': ['health.articles.list', 'health.articles.manage'],
'/health/article-categories': ['health.articles.list', 'health.articles.manage'],
'/health/article-tags': ['health.articles.list', 'health.articles.manage'],
'/health/points-rules': ['health.points.list', 'health.points.manage'],
'/health/points-products': ['health.points.list', 'health.points.manage'],
'/health/points-orders': ['health.points.list', 'health.points.manage'],
@@ -153,13 +158,14 @@ function PrivateRoute({ children }: { children: React.ReactNode }) {
// 首页/工作台始终放行
if (path === '/' || path === '') return <>{children}</>;
const matchedPrefix = Object.keys(ROUTE_PERMISSIONS).find((prefix) => path.startsWith(prefix));
const matchedPrefix = Object.keys(ROUTE_PERMISSIONS).find(
(prefix) => path === prefix || path.startsWith(prefix + '/'),
);
if (matchedPrefix) {
const required = ROUTE_PERMISSIONS[matchedPrefix];
const hasAccess = required.some((r) => permissions.includes(r));
if (!hasAccess) return <ForbiddenPage />;
} else {
// 未在 ROUTE_PERMISSIONS 中注册的路由,默认拒绝
return <ForbiddenPage />;
}

View File

@@ -18,7 +18,7 @@ pub struct Model {
pub acknowledged_by: Option<Uuid>,
#[sea_orm(skip_serializing_if = "Option::is_none")]
pub acknowledged_at: Option<DateTimeUtc>,
pub escalation_level: i32,
pub escalation_level: i16,
pub created_at: DateTimeUtc,
pub updated_at: DateTimeUtc,
#[sea_orm(skip_serializing_if = "Option::is_none")]

View File

@@ -127,6 +127,7 @@ 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;
mod m20260507_000128_fix_alert_status_and_menu_perms;
pub struct Migrator;
@@ -261,6 +262,7 @@ impl MigratorTrait for Migrator {
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),
Box::new(m20260507_000128_fix_alert_status_and_menu_perms::Migration),
]
}
}

View File

@@ -0,0 +1,81 @@
//! 修复告警状态 + 菜单权限码 + 角色权限清理
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();
// === 1. 修复告警 seed 数据状态active → pending ===
// alerts 表中 status='active' 的记录统一改为 'pending'
db.execute_unprepared(
"UPDATE alerts SET status = 'pending', updated_at = NOW() \
WHERE status = 'active' AND deleted_at IS NULL",
)
.await?;
// critical_alert 表同理
db.execute_unprepared(
"UPDATE critical_alert SET status = 'pending', updated_at = NOW() \
WHERE status = 'active' AND deleted_at IS NULL",
)
.await?;
// === 2. 为缺失 permission 的菜单补上权限码 ===
// m20260505_000116 创建的菜单缺少 permission 字段
let menu_perms: &[(&str, &str)] = &[
("/health/care-plans", "health.care-plan.list"),
("/health/shifts", "health.shifts.list"),
("/health/medications", "health.medication-records.manage"),
("/health/ble-gateways", "health.ble-gateways.list"),
("/health/critical-value-thresholds", "health.critical-value-thresholds.list"),
("/health/diagnoses", "health.health-data.list"),
("/health/family-proxy", "health.patient.list"),
("/health/consents", "health.consent.list"),
("/health/realtime-monitor", "health.device-readings.list"),
("/health/oauth-clients", "health.oauth.list"),
("/health/follow-up-templates", "health.follow-up-templates.list"),
];
for &(path, perm) in menu_perms {
db.execute_unprepared(&format!(
"UPDATE menus SET permission = '{perm}', updated_at = NOW() \
WHERE path = '{path}' AND deleted_at IS NULL AND (permission IS NULL OR permission = '')"
))
.await?;
}
// === 3. 清理非 admin 角色不应有的基础模块权限 ===
// user.list / user.manage / config.settings.list / config.settings.manage
// 应只属于 admin 角色
let over_privileged_codes = vec![
"user.list",
"user.manage",
"config.settings.list",
"config.settings.manage",
];
for code in &over_privileged_codes {
db.execute_unprepared(&format!(
"UPDATE role_permissions SET deleted_at = NOW(), updated_at = NOW() \
FROM roles r, permissions p \
WHERE role_permissions.role_id = r.id \
AND role_permissions.permission_id = p.id \
AND r.code NOT IN ('admin') \
AND p.code = '{code}' \
AND role_permissions.deleted_at IS NULL"
))
.await?;
}
Ok(())
}
async fn down(&self, _manager: &SchemaManager) -> Result<(), DbErr> {
Ok(())
}
}