fix(health): 穷尽审计修复 — 权限同步/编译错误/前端bug/审计日志
Some checks failed
CI / frontend-build (push) Has been cancelled
CI / rust-check (push) Has been cancelled
CI / rust-test (push) Has been cancelled
CI / security-audit (push) Has been cancelled

审计发现并修复的问题:

HIGH:
- H1: ConsultationDetail 使用 getSession(id) 替代错误的列表搜索
- H2: SessionResp 添加 version/updated_at 字段
- H3: 移除 FollowUpRecordList 调用不存在的导出端点
- H4: 新增 articles.ts 前端 API 模块

MEDIUM:
- M1: article delete 添加乐观锁 (expected_version)
- M2: 取消预约排班释放传播错误 (log::warn -> ?)
- M3: FollowUpTaskList 日期格式 Dayjs -> string
- M4: 补充 15 个缺失审计日志

LOW:
- L1: 替换 follow_up_service 中的 .unwrap()
- L2: PatientListItem 添加 version 字段

CRITICAL (新发现):
- 权限未同步: 健康模块 14 个权限从未写入数据库,添加启动时自动同步
- migration 表名错误: patients -> patient
- 编译错误: health_trend entity 未导入, ToPrimitive trait 未导入
- HealthError 缺少 From<AppError> 实现
This commit is contained in:
iven
2026-04-25 08:58:58 +08:00
parent 9ffb938128
commit 07f4ba41ba
31 changed files with 3373 additions and 445 deletions

View File

@@ -14,7 +14,7 @@ impl MigrationTrait for Migration {
manager
.alter_table(
Table::alter()
.table(Alias::new("patients"))
.table(Alias::new("patient"))
.add_column(
ColumnDef::new(Alias::new("id_number_hash"))
.string()
@@ -29,7 +29,7 @@ impl MigrationTrait for Migration {
manager
.alter_table(
Table::alter()
.table(Alias::new("patients"))
.table(Alias::new("patient"))
.drop_column(Alias::new("id_number_hash"))
.to_owned(),
)

View File

@@ -363,6 +363,9 @@ async fn main() -> anyhow::Result<()> {
registry.startup_all(&module_ctx).await?;
tracing::info!("All modules started");
// 同步所有模块声明的权限到数据库upsert
sync_module_permissions(&db, &registry, default_tenant_id).await?;
// 恢复运行中的插件(服务器重启后自动重新加载)
match plugin_engine.recover_plugins(&db).await {
Ok(recovered) => {
@@ -554,3 +557,67 @@ async fn shutdown_signal() {
},
}
}
/// 同步所有模块声明的权限到数据库。
///
/// 对每个模块的 `permissions()` 返回的权限执行 upsert
/// - 新权限INSERT
/// - 已有权限(同 tenant_id + code跳过
/// 同时将新权限分配给 admin 角色。
async fn sync_module_permissions(
db: &sea_orm::DatabaseConnection,
registry: &erp_core::module::ModuleRegistry,
tenant_id: uuid::Uuid,
) -> Result<(), anyhow::Error> {
let system_user_id = uuid::Uuid::nil();
let mut total_new = 0u32;
for module in registry.modules() {
let perms = module.permissions();
if perms.is_empty() {
continue;
}
for perm in perms {
let result = db.execute(sea_orm::Statement::from_sql_and_values(
sea_orm::DatabaseBackend::Postgres,
r#"INSERT INTO permissions (id, tenant_id, code, name, resource, action, description, created_at, updated_at, created_by, updated_by, deleted_at, version)
VALUES ($1, $2, $3, $4, $5, $6, $7, NOW(), NOW(), $8, $8, NULL, 1)
ON CONFLICT (tenant_id, code) WHERE deleted_at IS NULL DO NOTHING"#,
[
uuid::Uuid::now_v7().into(),
tenant_id.into(),
perm.code.clone().into(),
perm.name.clone().into(),
perm.module.clone().into(),
perm.code.split('.').last().unwrap_or("manage").into(),
perm.description.clone().into(),
system_user_id.into(),
],
)).await?;
let rows = result.rows_affected();
if rows > 0 {
total_new += 1;
}
}
}
if total_new > 0 {
// 将新权限分配给 admin 角色
db.execute(sea_orm::Statement::from_sql_and_values(
sea_orm::DatabaseBackend::Postgres,
r#"INSERT INTO role_permissions (role_id, permission_id, tenant_id, data_scope, created_at, updated_at, created_by, updated_by, deleted_at, version)
SELECT r.id, p.id, p.tenant_id, 'all', NOW(), NOW(), $1, $1, NULL, 1
FROM permissions p
JOIN roles r ON r.code = 'admin' AND r.tenant_id = p.tenant_id AND r.deleted_at IS NULL
WHERE p.tenant_id = $2 AND p.code LIKE 'health.%'
ON CONFLICT DO NOTHING"#,
[system_user_id.into(), tenant_id.into()],
)).await?;
tracing::info!(total_new, "Module permissions synced to database");
}
Ok(())
}