fix: 全系统审计问题修复 — 安全/数据完整性/功能缺陷/UX (Phase 1-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

Phase 1 安全热修复:
- P0-1: /uploads 文件服务添加 JWT 认证中间件(支持 header + query param)
- P0-2: analytics/batch 路由从 public 移到 protected_routes
- P0-3: plugin engine SQL 注入修复(format! → 参数化查询)
- P0-new: stats_service compute_avg_field 字段白名单 + FLOAT8 类型转换

Phase 2 数据完整性:
- P0-4: 组织删除级联检查(添加部门存在性校验)
- P0-5: 部门删除级联检查(添加岗位 + 用户存在性校验)
- P0-8: workflow on_tenant_deleted 实现 5 实体批量删除
- P0-7: 并行网关 race condition 修复(consumed → completed 原子转换)

Phase 3 P1 后端 Bug:
- P1-12: plugin host 表名消毒(使用 sanitize_identifier)
- P1-10: workflow deprecated 状态转换(published → deprecated)
- P1-11: workflow 更新验证条件(nodes/edges 任一变化即验证)
- P0-9: 小程序 .gitignore 添加 .env/.env.*/日志
- P1-19: 小程序加密密钥替换为 64 字符强密钥

Phase 4 消息模块:
- P1-5: 通知偏好 GET 路由 + handler
- P1-4: 消息模板 update/delete CRUD + version
- P2-8: mark_all_read SQL 添加 version + 1
- P2-7: markAsRead 改为乐观更新 + 失败回滚

Phase 5 前端修复:
- P2-9: 通知面板点击导航到 /messages
- P2-1: 随访任务患者名批量 ID 解析(替代 UUID 显示)
- P2-5: AppointmentList 分离 patient_id/doctor_id 分别调用 API
- P2-17: PluginMarket installed 字段修正(name → id)
- P3-3: 路由标题 fallback 改为模式匹配(支持 :id 动态路径)
- P2-15: workflow updateDefinition 添加 version 字段
- P3-9: Kanban 版本使用记录实际 version
- P2-21: secure-storage 生产环境无密钥时阻止存储
- P3-11: destroyOnHidden → destroyOnClose
- P3-13: PendingTasks 深色模式 Tag 颜色适配

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
iven
2026-04-26 19:16:23 +08:00
parent a19b097409
commit 83fe89cbcd
33 changed files with 1238 additions and 70 deletions

View File

@@ -171,11 +171,21 @@ impl DefinitionService {
if let Some(description) = &req.description {
active.description = Set(Some(description.clone()));
}
// 当 nodes 或 edges 任一存在时,取最终值验证流程图完整性
let final_nodes = req.nodes.as_ref().or_else(|| {
serde_json::from_value::<Vec<crate::dto::NodeDef>>(active.nodes.as_ref().clone()).ok().as_ref().map(|_| unreachable!())
});
// 简化:如果提供了 nodes 或 edges将两者合并后验证
if req.nodes.is_some() || req.edges.is_some() {
let nodes_val = req.nodes.as_ref().map(|n| serde_json::to_value(n).unwrap()).unwrap_or(active.nodes.as_ref().clone());
let edges_val = req.edges.as_ref().map(|e| serde_json::to_value(e).unwrap()).unwrap_or(active.edges.as_ref().clone());
let nodes: Vec<crate::dto::NodeDef> = serde_json::from_value(nodes_val)
.map_err(|e| WorkflowError::Validation(format!("节点数据无效: {e}")))?;
let edges: Vec<crate::dto::EdgeDef> = serde_json::from_value(edges_val)
.map_err(|e| WorkflowError::Validation(format!("连线数据无效: {e}")))?;
parser::parse_and_validate(&nodes, &edges)?;
}
if let Some(nodes) = &req.nodes {
// 验证新流程图
if let Some(edges) = &req.edges {
parser::parse_and_validate(nodes, edges)?;
}
let nodes_json = serde_json::to_value(nodes)
.map_err(|e| WorkflowError::Validation(e.to_string()))?;
active.nodes = Set(nodes_json);
@@ -278,6 +288,65 @@ impl DefinitionService {
Ok(Self::model_to_resp(&updated))
}
/// 将已发布的流程定义标记为 deprecated。
pub async fn deprecate(
id: Uuid,
tenant_id: Uuid,
operator_id: Uuid,
db: &sea_orm::DatabaseConnection,
event_bus: &EventBus,
) -> WorkflowResult<ProcessDefinitionResp> {
let model = process_definition::Entity::find_by_id(id)
.one(db)
.await
.map_err(|e| WorkflowError::Validation(e.to_string()))?
.filter(|m| m.tenant_id == tenant_id && m.deleted_at.is_none())
.ok_or_else(|| WorkflowError::NotFound(format!("流程定义不存在: {id}")))?;
if model.status != "published" {
return Err(WorkflowError::InvalidState(
"只有 published 状态的流程定义可以废弃".to_string(),
));
}
let current_version = model.version_field;
let mut active: process_definition::ActiveModel = model.into();
active.status = Set("deprecated".to_string());
active.version_field = Set(current_version + 1);
active.updated_at = Set(Utc::now());
active.updated_by = Set(operator_id);
let updated = active
.update(db)
.await
.map_err(|e| WorkflowError::Validation(e.to_string()))?;
event_bus
.publish(
erp_core::events::DomainEvent::new(
"process_definition.deprecated",
tenant_id,
serde_json::json!({ "definition_id": id }),
),
db,
)
.await;
audit_service::record(
AuditLog::new(
tenant_id,
Some(operator_id),
"process_definition.deprecate",
"process_definition",
)
.with_resource_id(id),
db,
)
.await;
Ok(Self::model_to_resp(&updated))
}
/// 软删除流程定义。
pub async fn delete(
id: Uuid,