7.0 KiB
CRM 插件审计修复计划
Context
CRM 插件开发审计发现 4 个 CRITICAL、6 个 HIGH、7 个 MEDIUM 问题。本计划按优先级分 3 批修复,覆盖后端 Rust 和前端 React 两端。
Batch 1 — CRITICAL(阻断性,不修功能不可用)
Fix 1: 后端权限 SQL 参数化
文件: crates/erp-plugin/src/service.rs (第 578-714 行)
register_plugin_permissions(): 将format!()+Statement::from_string()改为Statement::from_sql_and_values()+ 参数化占位符$1, $2, ...unregister_plugin_permissions(): 同上,两段 UPDATE 都参数化- 需要新增一个辅助函数
resolve_manifest_id(plugin_id: Uuid, db) -> AppResult<String>从 plugins 表查 manifest_json 获取 metadata.id(供 Fix 2 使用)
Fix 2: 后端权限码使用 manifest_id 而非 UUID
文件: crates/erp-plugin/src/data_handler.rs (第 19-25 行, 第 49/107/145/177/211/256/301 行)
- 每个 handler 先从 DB 查 manifest_id:
let manifest_id = resolve_manifest_id(plugin_id, &state.db).await?; compute_permission_code改为接受 manifest_id- 添加
resolve_manifest_id辅助函数:查 plugins 表 → 解析 manifest_json → 提取 metadata.id - 考虑在 PluginState 中缓存 manifest_id 映射(或直接在 data_service 层缓存)
实现: 将 resolve_manifest_id 放在 data_service.rs 中(与 resolve_table_name 同级),handler 调用它。
Fix 3: 4 个路由页面组件自行加载 schema
文件: 4 个页面组件 + api/plugins.ts + api/pluginData.ts
统一模式:每个组件通过 useParams() 获取路由参数,内部调用 getPluginSchema() 加载 schema,从 schema 中提取所需数据。
3a. PluginTabsPage.tsx:
- 移除所有 props(pluginId/label/tabs/entities),改用
useParams<{ pluginId: string; pageLabel: string }>() - 内部调用
getPluginSchema(pluginId)获取 schema - 从
schema.ui.pages中找到 type='tabs' 且 label 匹配 pageLabel 的页面 - 替换
require()为顶层 ESimport - 移除不存在的
enableSearchprop 传递
3b. PluginTreePage.tsx:
- 移除 props(pluginId/entity/idField/parentField/labelField/fields),改用
useParams<{ pluginId: string; entityName: string }>() - 从 schema 加载 entity 字段和页面配置(tree 页面的 id_field/parent_field/label_field)
3c. PluginGraphPage.tsx:
- 移除所有 props,改用
useParams<{ pluginId: string; entityName: string }>() - 从 schema 中找到 type='graph' 的页面配置
3d. PluginDashboardPage.tsx:
- 移除 props(pluginId/entities),改用
useParams<{ pluginId: string }>() - 内部调用
getPluginSchema(pluginId)获取所有 entities
Fix 4: PluginPageSchema 补充 graph/dashboard 类型
文件: apps/web/src/api/plugins.ts (第 154-158 行)
扩展 union type:
| { type: 'graph'; entity: string; label: string; relationship_entity: string; source_field: string; target_field: string; edge_label_field: string; node_label_field: string }
| { type: 'dashboard'; label: string }
Fix 5: api/pluginData.ts 补充 count/aggregate API
文件: apps/web/src/api/pluginData.ts
新增两个函数:
countPluginData(pluginId, entity, options?)→GET /plugins/{id}/{entity}/countaggregatePluginData(pluginId, entity, groupBy, filter?)→GET /plugins/{id}/{entity}/aggregate
Batch 2 — HIGH(稳定性和正确性)
Fix 6: AbortController 防竞态
文件: 所有 5 个页面组件的 useEffect
为数据加载 useEffect 添加 AbortController:
useEffect(() => {
const abortController = new AbortController();
// ... async loadData 中检查 abortController.signal.aborted
return () => abortController.abort();
}, [deps]);
注意:当前 api client (axios) 不支持 AbortSignal 透传,简单方案是在 setState 前检查 abortController.signal.aborted 或使用一个 mounted flag。
Fix 7: Dashboard 改用后端 aggregate API
文件: apps/web/src/pages/PluginDashboardPage.tsx
- 使用
countPluginData()获取总数 - 使用
aggregatePluginData()获取分组统计 - 移除全量循环加载逻辑
- 保留 fallback:如果 aggregate API 失败,显示总数 + 提示
Fix 8: fetchData 双重请求修复
文件: apps/web/src/pages/PluginCRUDPage.tsx (第 501-511 行)
搜索操作改为直接传参模式:
onSearch={(value) => {
setSearchText(value);
setPage(1);
fetchData(1, { search: value }); // 直接传参
}}
修改 fetchData 签名允许覆盖搜索参数。
Fix 9: Canvas 高 DPI 支持
文件: apps/web/src/pages/PluginGraphPage.tsx (第 114-118 行)
const dpr = window.devicePixelRatio || 1;
canvas.width = width * dpr;
canvas.height = height * dpr;
canvas.style.width = `${width}px`;
canvas.style.height = `${height}px`;
ctx.scale(dpr, dpr);
Batch 3 — MEDIUM(建议修复)
Fix 10: 服务端排序替代前端排序
文件: apps/web/src/pages/PluginCRUDPage.tsx
- Table 的
onChange回调捕获 sortField/sortOrder - 传给
fetchData作为sort_by/sort_order参数 - 移除列定义中的
sorter: true
Fix 11: Canvas 暗色主题支持
文件: apps/web/src/pages/PluginGraphPage.tsx
从 CSS 变量读取主题色:
const style = getComputedStyle(canvas);
const textColor = style.getPropertyValue('--antd-color-text') || '#333';
const lineColor = style.getPropertyValue('--antd-color-border') || '#999';
Fix 12: schema 加载失败提示用户
文件: 所有页面组件的 .catch(() => {})
替换为 message.warning('Schema 加载失败,部分功能不可用')
Fix 13: 后端 data_service 缓存优化
文件: crates/erp-plugin/src/data_service.rs
合并 resolve_table_name 和 resolve_entity_fields 为一个函数 resolve_entity_info(),减少数据库查询次数。
关键文件清单
| 文件 | 修改类型 |
|---|---|
crates/erp-plugin/src/service.rs |
SQL 参数化 |
crates/erp-plugin/src/data_handler.rs |
manifest_id 查找 |
crates/erp-plugin/src/data_service.rs |
resolve_manifest_id + resolve_entity_info 缓存 |
apps/web/src/api/plugins.ts |
PluginPageSchema 扩展 |
apps/web/src/api/pluginData.ts |
count/aggregate API |
apps/web/src/pages/PluginTabsPage.tsx |
自加载 schema |
apps/web/src/pages/PluginTreePage.tsx |
自加载 schema |
apps/web/src/pages/PluginGraphPage.tsx |
自加载 schema + DPI + 暗色 |
apps/web/src/pages/PluginDashboardPage.tsx |
自加载 schema + 后端 aggregate |
apps/web/src/pages/PluginCRUDPage.tsx |
搜索/排序修复 |
验证计划
cargo check --workspace通过cargo test --workspace通过cd apps/web && pnpm build通过(验证 require → import 修复)- 手动验证:
- 侧边栏点击 CRM tabs 菜单 → 页面正常渲染
- CRUD 页面搜索/筛选/排序正常
- Tree 页面展示树形结构
- Graph 页面渲染图谱(高 DPI 清晰)
- Dashboard 页面显示统计