170 lines
7.0 KiB
Markdown
170 lines
7.0 KiB
Markdown
# 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()` 为顶层 ES `import`
|
||
- 移除不存在的 `enableSearch` prop 传递
|
||
|
||
**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:
|
||
```typescript
|
||
| { 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}/count`
|
||
- `aggregatePluginData(pluginId, entity, groupBy, filter?)` → `GET /plugins/{id}/{entity}/aggregate`
|
||
|
||
## Batch 2 — HIGH(稳定性和正确性)
|
||
|
||
### Fix 6: AbortController 防竞态
|
||
**文件**: 所有 5 个页面组件的 useEffect
|
||
|
||
为数据加载 useEffect 添加 AbortController:
|
||
```typescript
|
||
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 行)
|
||
|
||
搜索操作改为直接传参模式:
|
||
```typescript
|
||
onSearch={(value) => {
|
||
setSearchText(value);
|
||
setPage(1);
|
||
fetchData(1, { search: value }); // 直接传参
|
||
}}
|
||
```
|
||
修改 `fetchData` 签名允许覆盖搜索参数。
|
||
|
||
### Fix 9: Canvas 高 DPI 支持
|
||
**文件**: `apps/web/src/pages/PluginGraphPage.tsx` (第 114-118 行)
|
||
|
||
```typescript
|
||
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 变量读取主题色:
|
||
```typescript
|
||
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` | 搜索/排序修复 |
|
||
|
||
## 验证计划
|
||
|
||
1. `cargo check --workspace` 通过
|
||
2. `cargo test --workspace` 通过
|
||
3. `cd apps/web && pnpm build` 通过(验证 require → import 修复)
|
||
4. 手动验证:
|
||
- 侧边栏点击 CRM tabs 菜单 → 页面正常渲染
|
||
- CRUD 页面搜索/筛选/排序正常
|
||
- Tree 页面展示树形结构
|
||
- Graph 页面渲染图谱(高 DPI 清晰)
|
||
- Dashboard 页面显示统计
|