fix(health): 四次审计修复 — 6 CRITICAL + 8 HIGH + 4 MEDIUM
CRITICAL: - C-1: consultation sender_id 改为从 JWT ctx.user_id 注入,防伪造 - C-2: consultation session 更新改为 CAS 原子操作,防并发丢失 - C-3: 随访记录创建包裹在事务中,保证记录/任务/后续任务一致性 - C-4/C-5/C-6: 唯一索引改为 partial index WHERE deleted_at IS NULL HIGH: - H-1: manage_patient_tags 添加 tag_ids 租户归属校验 - H-2: assign_doctor 添加重复关联检查 - H-3: calendar_view 限制日期范围最多 90 天 - H-4: export_sessions 添加 10000 条上限 - H-5: patient_tag_relation/patient_doctor_relation 添加 version 字段 - H-6: create_schedule 添加医生存在性检查 - H-7: 预约取消排班释放错误改为日志记录 - H-8: follow_up_task.related_appointment_id 添加 FK 约束 MEDIUM: - M-2: 修复 search LIKE 双重 % 包裹问题 - M-3: article_service 错误类型改为 ArticleNotFound - M-4: patient.created 事件移除 PII(姓名) - M-6: lab_report 添加 (tenant_id, report_type) 索引
This commit is contained in:
@@ -45,6 +45,7 @@ mod m20260423_000042_create_health_tables;
|
||||
mod m20260423_000043_create_wechat_users;
|
||||
mod m20260423_000044_create_articles;
|
||||
mod m20260424_000045_health_indexes;
|
||||
mod m20260424_000046_health_constraints_fix;
|
||||
|
||||
pub struct Migrator;
|
||||
|
||||
@@ -97,6 +98,7 @@ impl MigratorTrait for Migrator {
|
||||
Box::new(m20260423_000043_create_wechat_users::Migration),
|
||||
Box::new(m20260423_000044_create_articles::Migration),
|
||||
Box::new(m20260424_000045_health_indexes::Migration),
|
||||
Box::new(m20260424_000046_health_constraints_fix::Migration),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
/// 迁移 000046: 修复唯一索引软删除兼容 + 关联表添加 version + 补充索引/FK
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
let conn = manager.get_connection();
|
||||
|
||||
// C-4: patient.id_number 唯一索引 — 重建为 partial index WHERE deleted_at IS NULL
|
||||
conn.execute_unprepared("DROP INDEX IF EXISTS idx_patient_tenant_id_number").await?;
|
||||
conn.execute_unprepared(
|
||||
"CREATE UNIQUE INDEX idx_patient_tenant_id_number ON patient (tenant_id, id_number) WHERE deleted_at IS NULL AND id_number IS NOT NULL"
|
||||
).await?;
|
||||
|
||||
// C-5: patient_tag.name 唯一索引 — 重建为 partial index WHERE deleted_at IS NULL
|
||||
conn.execute_unprepared("DROP INDEX IF EXISTS idx_patient_tag_tenant_name_unique").await?;
|
||||
conn.execute_unprepared(
|
||||
"CREATE UNIQUE INDEX idx_patient_tag_tenant_name_unique ON patient_tag (tenant_id, name) WHERE deleted_at IS NULL"
|
||||
).await?;
|
||||
|
||||
// C-6: doctor_schedule 唯一索引 — 重建为 partial index,修正列选择为 (tenant_id, doctor_id, schedule_date, period_type)
|
||||
conn.execute_unprepared("DROP INDEX IF EXISTS idx_doctor_schedule_unique_slot").await?;
|
||||
conn.execute_unprepared(
|
||||
"CREATE UNIQUE INDEX idx_doctor_schedule_unique_slot ON doctor_schedule (tenant_id, doctor_id, schedule_date, period_type) WHERE deleted_at IS NULL"
|
||||
).await?;
|
||||
|
||||
// H-5: patient_tag_relation 添加 version 列
|
||||
conn.execute_unprepared(
|
||||
"ALTER TABLE patient_tag_relation ADD COLUMN IF NOT EXISTS version integer NOT NULL DEFAULT 1"
|
||||
).await?;
|
||||
|
||||
// H-5: patient_doctor_relation 添加 version 列
|
||||
conn.execute_unprepared(
|
||||
"ALTER TABLE patient_doctor_relation ADD COLUMN IF NOT EXISTS version integer NOT NULL DEFAULT 1"
|
||||
).await?;
|
||||
|
||||
// H-8: follow_up_task.related_appointment_id 添加 FK 约束
|
||||
conn.execute_unprepared(
|
||||
"ALTER TABLE follow_up_task DROP CONSTRAINT IF EXISTS fk_follow_up_task_appointment"
|
||||
).await?;
|
||||
conn.execute_unprepared(
|
||||
"ALTER TABLE follow_up_task ADD CONSTRAINT fk_follow_up_task_appointment \
|
||||
FOREIGN KEY (related_appointment_id) REFERENCES appointment(id) ON DELETE SET NULL"
|
||||
).await?;
|
||||
|
||||
// M-6: lab_report 添加 (tenant_id, report_type) 索引
|
||||
conn.execute_unprepared(
|
||||
"CREATE INDEX IF NOT EXISTS idx_lab_report_tenant_type ON lab_report (tenant_id, report_type)"
|
||||
).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
let conn = manager.get_connection();
|
||||
|
||||
// 恢复原始索引(非 partial)
|
||||
conn.execute_unprepared("DROP INDEX IF EXISTS idx_patient_tenant_id_number").await?;
|
||||
conn.execute_unprepared(
|
||||
"CREATE UNIQUE INDEX idx_patient_tenant_id_number ON patient (tenant_id, id_number)"
|
||||
).await?;
|
||||
|
||||
conn.execute_unprepared("DROP INDEX IF EXISTS idx_patient_tag_tenant_name_unique").await?;
|
||||
conn.execute_unprepared(
|
||||
"CREATE UNIQUE INDEX idx_patient_tag_tenant_name_unique ON patient_tag (tenant_id, name)"
|
||||
).await?;
|
||||
|
||||
conn.execute_unprepared("DROP INDEX IF EXISTS idx_doctor_schedule_unique_slot").await?;
|
||||
conn.execute_unprepared(
|
||||
"CREATE UNIQUE INDEX idx_doctor_schedule_unique_slot ON doctor_schedule (tenant_id, doctor_id, schedule_date, start_time)"
|
||||
).await?;
|
||||
|
||||
conn.execute_unprepared(
|
||||
"ALTER TABLE patient_tag_relation DROP COLUMN IF EXISTS version"
|
||||
).await?;
|
||||
|
||||
conn.execute_unprepared(
|
||||
"ALTER TABLE patient_doctor_relation DROP COLUMN IF EXISTS version"
|
||||
).await?;
|
||||
|
||||
conn.execute_unprepared(
|
||||
"ALTER TABLE follow_up_task DROP CONSTRAINT IF EXISTS fk_follow_up_task_appointment"
|
||||
).await?;
|
||||
|
||||
conn.execute_unprepared(
|
||||
"DROP INDEX IF EXISTS idx_lab_report_tenant_type"
|
||||
).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user