Files
erp/plans/flickering-tinkering-pebble-agent-a409e20167941384b.md
iven 841766b168
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
fix(用户管理): 修复用户列表页面加载失败问题
修复用户列表页面加载失败导致测试超时的问题,确保页面元素正确渲染
2026-04-19 08:46:28 +08:00

668 lines
27 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# CRM WASM 插件架构审查报告
> 审查人:后端架构师
> 日期2026-04-15
> 审查对象CRM 客户管理 WASM 插件设计方案
---
## 0. 审查前提:当前系统实际状态
经过对代码库的详细审查,我确认以下事实作为审查基础:
- **WIT 接口** (`crates/erp-plugin/wit/plugin.wit`)9 个 Host API 函数db-insert/db-query/db-update/db-delete/event-publish/config-get/log-write/current-user/check-permission
- **动态表结构** (`crates/erp-plugin/src/dynamic_table.rs`):每张表只有 `id`, `tenant_id`, `data`(JSONB), `created_at`, `updated_at`, `created_by`, `updated_by`, `deleted_at`, `version` 这 9 列。所有业务字段存储在 `data` JSONB 列中。
- **索引策略**:只为 `required``unique` 字段创建 GIN 索引(`data->>'field_name'`),没有复合索引能力。
- **查询能力** (`data_service.rs`)`list()` 方法只支持按 tenant_id 过滤 + 分页,**不支持任何业务字段过滤**。`PluginDataListParams` 只有 `page`, `page_size`, `search` 三个参数。
- **前端 CRUD** (`PluginCRUDPage.tsx`):纯粹的表单驱动,无过滤、无关联实体展示、无自定义操作。
- **设计规格文档** (`docs/superpowers/specs/2026-04-13-wasm-plugin-system-design.md`) 附录 D 中规划了 `db-aggregate` 接口,但**实际 WIT 文件中并未实现**。
- **设计规格文档**规划了 `render-page` / `handle-action` 插件导出函数,但**实际 WIT 中也没有**。
**结论:当前系统是一个 MVP 级别的插件原型,距离支撑 CRM 这种多实体、多关系的业务插件还有显著差距。**
---
## 1. JSONB 动态表能否支撑 CRM 数据模型?
### 1.1 结论:基本可行,但需要大幅增强查询能力
JSONB 存储本身不是问题。PostgreSQL 的 JSONB 在索引支持下,单字段等值查询和范围查询性能完全可以接受(毫秒级)。关键问题在于当前系统的**查询能力几乎为零**。
### 1.2 关系查询的实现路径
对于 CRM 中"A 的所有子客户"这类关系查询:
**方案 A利用 JSONB 索引的等值过滤(推荐)**
`parent_id` 存储在 `data` JSONB 中。当前系统已有为 `required` 字段创建索引的逻辑:
```sql
-- 动态表已有的索引模式
CREATE INDEX idx_xxx_parent_id ON plugin_crm_customer (data->>'parent_id') WHERE deleted_at IS NULL;
```
查询"A 的直接子节点"只需:
```sql
SELECT * FROM plugin_crm_customer
WHERE tenant_id = $1 AND data->>'parent_id' = $2 AND deleted_at IS NULL;
```
这在有索引的情况下性能完全没问题10 万条记录也能在 5ms 内返回。
**方案 B递归查询查询所有子孙节点需要 db-query 增强**
当前的 `db_query` Host API 是预填充模式——Host 在执行 WASM 前就把查询结果塞进 `query_results` HashMap。WASM 内部只是读缓存。这意味着**插件无法控制 SQL 的构建过程**。
递归查询需要 PostgreSQL 的 `WITH RECURSIVE` CTE
```sql
WITH RECURSIVE descendants AS (
SELECT id, data, 0 as depth FROM plugin_crm_customer
WHERE id = $1 AND tenant_id = $2 AND deleted_at IS NULL
UNION ALL
SELECT c.id, c.data, d.depth + 1 FROM plugin_crm_customer c
JOIN descendants d ON c.data->>'parent_id' = d.id::text
WHERE c.tenant_id = $2 AND c.deleted_at IS NULL AND d.depth < 10
)
SELECT * FROM descendants ORDER BY depth;
```
**当前系统无法执行这种查询。** 需要 Host API 层面支持。
### 1.3 具体建议
1. **为外键字段显式声明索引**:在 `PluginField` 中新增 `indexed: bool` 属性,或者引入 `references` 属性自动创建索引。CRM 中 `contact.customer_id``communication.customer_id``customer_tag.customer_id``customer_relationship.from_customer_id``customer_relationship.to_customer_id` 这些外键字段必须有索引。
2. **对 unique 约束的处理需要修正**:当前 `customer.code` 声明为 unique但索引只过滤了 `deleted_at IS NULL`。如果软删除后重建同名 code唯一索引会冲突。需要在索引中加入排除已删除记录的逻辑或使用 partial unique index
```sql
CREATE UNIQUE INDEX idx_xxx_code_uniq ON plugin_crm_customer (data->>'code') WHERE deleted_at IS NULL;
```
当前代码已经使用了 `WHERE deleted_at IS NULL`,这一点做得正确。但要确认 `sanitize_identifier` 生成的索引名不会因为表名过长而截断。
---
## 2. Host API 限制:跨实体查询
### 2.1 问题诊断
这是 CRM 插件面临的最严重的架构缺陷。CRM 的核心场景全部涉及跨实体:
- **客户列表 + 联系人数量**:需要 JOIN 或子查询
- **客户详情页展示联系人列表**:需要按 `customer_id` 过滤 contact
- **沟通记录按客户筛选**:需要按 `customer_id` 过滤 communication
- **标签筛选客户**:需要按 `tag_name` 在 customer_tag 中查,再反查 customer
当前 `db_query` 的实现(`host.rs` 第 99-109 行)只是从预填充的 HashMap 中取缓存:
```rust
fn db_query(&mut self, entity: String, _filter: Vec<u8>, _pagination: Vec<u8>) -> Result<Vec<u8>, String> {
self.query_results.get(&entity).cloned()
.ok_or_else(|| format!("实体 '{}' 的查询结果未预填充", entity))
}
```
`_filter` 和 `_pagination` 参数被完全忽略了。
### 2.2 解决方案:引入结构化查询 API
不是加 JOIN那会让 Host API 变成 SQL 构建器),而是引入**两级查询增强**
**第一级(必须实现):单实体过滤查询**
在 Host API 中新增 `db-query-filtered`,或在现有 `db_query` 中实际处理 filter 参数:
```wit
/// 结构化过滤查询
db-query: func(entity: string, filter: list<u8>, pagination: list<u8>) -> result<list<u8>, string>;
```
filter 的 JSON 结构:
```json
{
"conditions": [
{"field": "customer_id", "op": "eq", "value": "uuid-here"},
{"field": "status", "op": "in", "value": ["active", "vip"]},
{"field": "occurred_at", "op": "gte", "value": "2026-01-01"}
],
"order_by": [{"field": "created_at", "dir": "desc"}],
"search": "关键词"
}
```
Host 层将其安全转换为参数化 SQL
```sql
SELECT id, data, created_at, updated_at, version
FROM plugin_crm_contact
WHERE tenant_id = $1 AND deleted_at IS NULL
AND data->>'customer_id' = $2
ORDER BY created_at DESC
LIMIT $3 OFFSET $4
```
这样 CRM 插件就能实现"查某个客户的所有联系人"、"查某个客户的所有沟通记录"。
**第二级(必须实现):聚合查询**
设计规格中规划了 `db-aggregate` 但未实现。CRM 需要它来显示统计信息:
```wit
db-aggregate: func(entity: string, query: list<u8>) -> result<list<u8>, string>;
```
query 结构:
```json
{
"group_by": ["data->>'status'"],
"aggregates": [
{"alias": "count", "func": "count", "field": "id"},
{"alias": "total_value", "func": "sum", "field": "data->>'amount'"}
],
"filter": {"field": "data->>'level'", "op": "eq", "value": "vip"}
}
```
**第三级(推荐实现):跨实体关联查询**
不要实现通用 JOIN太复杂且安全风险高而是实现一种受限的"关联加载"模式:
```wit
/// 按外键值批量查询(一次加载多个关联记录)
db-query-batch: func(entity: string, field: string, ids: list<string>) -> result<list<u8>, string>;
```
这解决了 CRM 中最常见的 N+1 查询问题。例如加载 20 个客户后,一次性查出所有关联的联系人:
```json
// 请求:查 contact 表中 customer_id 在 [id1, id2, ...] 中的记录
{
"entity": "contact",
"field": "customer_id",
"ids": ["uuid-1", "uuid-2", "..."]
}
```
Host 生成:
```sql
SELECT id, data, created_at, updated_at, version
FROM plugin_crm_contact
WHERE tenant_id = $1 AND deleted_at IS NULL
AND data->>'customer_id' = ANY($2::text[])
```
### 2.3 关于"多次 API 调用"的问题
在 WASM 插件模型下,**每次 API 调用都是在同一个 Store 内完成的**,不涉及网络开销。多次 `db_query` 调用的成本是:
- WASM 到 Host 的函数调用(纳秒级)
- Host 从预填充缓存中读取(纳秒级)
所以即使需要多次调用,性能也不是问题。**真正的问题是当前预填充机制不支持按业务字段过滤**。只要实现了第一级的结构化过滤CRM 插件的跨实体查询就可以通过以下模式实现:
```
// 插件内伪代码
let contacts = db_query("contact", {"field": "customer_id", "op": "eq", "value": customer_id}, pagination);
let communications = db_query("communication", {"field": "customer_id", "op": "eq", "value": customer_id}, pagination);
```
前端也可以直接通过 REST API 调用:
```
GET /api/v1/plugins/crm/contact?filter={"field":"customer_id","op":"eq","value":"uuid"}&page=1&page_size=20
```
---
## 3. 关系图谱的可行性
### 3.1 结论:可行但有数据量上限
CRM 关系图谱的核心查询是给定一个客户找出它的所有直接关系parent_child/partner/supplier 等)。
**查询模式**
```sql
-- 查询 A 的所有关系
SELECT * FROM plugin_crm_customer_relationship
WHERE tenant_id = $1 AND deleted_at IS NULL
AND (data->>'from_customer_id' = $2 OR data->>'to_customer_id' = $2);
```
这个查询在索引支持下完全可行。需要为 `from_customer_id` 和 `to_customer_id` 都创建索引。
**前端加载策略**
"加载全量关系+客户来渲染图谱"这个方案在以下条件下可行:
- 单租户客户量 < 5000
- 关系记录 < 10000
超出这个量级,需要改为"按中心节点展开"的模式:
1. 先加载中心客户的直接关系1 跳)
2. 用户点击某个关联客户时加载该客户的直接关系2 跳)
3. 前端用力导向图逐步渲染
### 3.2 建议
1. **不要设计成全量加载**。manifest 页面类型 `graph` 应支持配置 `depth` 参数(默认 1 跳,最大 3 跳),以及 `center_entity_id` 参数。
2. **后端提供"关系网络"专用查询**。不是新增 Host API而是在 data_handler 层增加一个通用的关系查询端点:
```
GET /api/v1/plugins/{plugin_id}/{entity}/graph?center_id=uuid&depth=2&relationship_types=parent_child,partner
```
这可以由基座的前端通用组件调用,不需要插件 WASM 参与。
3. **前端 graph 组件选型**:推荐 Ant Design 的 `@ant-design/graph` 或 G6。图谱渲染是纯前端能力不需要 WASM 介入。
---
## 4. parent_id 层级(递归查询)
### 4.1 问题分析
"查询某客户的所有子孙节点"在当前架构下确实是个难题。三种场景需要递归:
1. **组织架构式展示**:树形组件显示客户层级
2. **汇总统计**:某集团下所有子公司的总交易额
3. **权限继承**:父客户的负责人可以看到子客户
### 4.2 解决方案
**方案 AHost 端提供递归查询 API推荐**
新增 Host API
```wit
/// 递归查询树形结构的所有子孙节点
db-query-tree: func(entity: string, parent-field: string, root-id: string, max-depth: s32) -> result<list<u8>, string>;
```
Host 层使用 `WITH RECURSIVE` 实现:
```sql
WITH RECURSIVE tree AS (
-- 锚点:根节点
SELECT id, data, 0 as depth
FROM plugin_crm_customer
WHERE id = $1 AND tenant_id = $2 AND deleted_at IS NULL
UNION ALL
-- 递归:子节点
SELECT c.id, c.data, t.depth + 1
FROM plugin_crm_customer c
JOIN tree t ON c.data->>'parent_id' = t.id::text
WHERE c.tenant_id = $2 AND c.deleted_at IS NULL AND t.depth < $3
)
SELECT id, data, depth FROM tree ORDER BY depth;
```
**方案 B物化路径denormalization**
在 customer 的 data 中存储物化路径 `path: "root_id/parent_id/self_id"`,然后可以用前缀匹配:
```sql
SELECT * FROM plugin_crm_customer
WHERE data->>'path' LIKE 'root_id/%' AND tenant_id = $1 AND deleted_at IS NULL;
```
这需要 GIN 索引支持 `LIKE` 前缀查询(`pg_trgm` 扩展或 `text_pattern_ops`)。
**推荐方案 A**。物化路径有数据一致性问题(移动节点时需要更新所有子节点的 path而递归 CTE 是 PostgreSQL 原生支持的,一次查询搞定。`max_depth` 参数防止无限递归。
### 4.3 对 tree 页面类型的建议
manifest 中 `type: tree` 的页面需要额外的配置:
```toml
[[ui.pages]]
route = "/crm/customer-tree"
entity = "customer"
type = "tree"
tree_config = { parent_field = "parent_id", label_field = "name", icon_field = "customer_type", max_depth = 5 }
```
基座前端根据 `tree_config` 渲染 Ant Design Tree 组件,使用 `db-query-tree` API 加载数据。
---
## 5. 缺失的关键能力
### 5.1 必须新增的 Host API
按优先级排序:
| 优先级 | API | 原因 |
|--------|-----|------|
| P0 | **db-query-filtered**(结构化过滤) | 没有它 CRM 插件完全无法工作。当前 db_query 的 filter 参数被忽略。 |
| P0 | **db-aggregate**(聚合查询) | CRM 仪表盘需要"按状态统计客户数"、"按级别统计客户数"等。设计规格中已规划但未实现。 |
| P0 | **db-query-by-id**(按 ID 查单条) | 当前 `db_query` 不支持按 ID 查询。虽然 REST API 有 GET by ID但 WASM 内部没有。 |
| P1 | **db-query-batch**(批量外键查询) | 解决 N+1 问题,关联实体加载。 |
| P1 | **db-query-tree**(递归树查询) | 支持客户层级树、组织架构等树形结构。 |
| P1 | **db-batch-insert**(批量插入) | 导入客户数据、批量创建联系人。 |
| P2 | **db-exists**(存在性检查) | 检查 code 是否已存在,比 count 更高效。 |
| P2 | **db-count**(计数) | 单独的计数查询,不返回数据。 |
### 5.2 必须增强的基座能力
| 能力 | 现状 | 需要 |
|------|------|------|
| **REST API 过滤** | `PluginDataListParams` 只有 `page/page_size/search` | 需要支持 `filter` 查询参数,支持 JSONB 字段的等值/范围/IN 查询 |
| **REST API 排序** | 固定 `ORDER BY created_at DESC` | 需要支持 `order_by` 参数 |
| **REST API 关联加载** | 无 | 需要支持 `?include=contact,communication` 参数,一次返回客户+联系人+沟通记录 |
| **Schema 唯一约束校验** | 只创建了索引,没有插入前校验 | 需要在 `PluginDataService::create` 中检查 unique 字段 |
| **实体间引用完整性** | 无 | 删除客户时应提示有关联的联系人/沟通记录。至少提供 `GET /plugins/{id}/{entity}/{record_id}/references` 端点 |
### 5.3 WASM 插件端需要的能力
设计规格中规划了 `render-page` 和 `handle-action`,但 WIT 中未实现。对于 CRM 插件,以下场景需要这两个函数:
- **客户 360 度视图**:在同一个页面展示客户基本信息 + 联系人列表 + 最近沟通记录 + 标签 + 关系图谱。这不是一个简单的 CRUD 页面,需要 WASM 返回复合 UI 指令。
- **自定义操作**"转为正式客户"、"合并客户"、"批量分配"等操作需要 WASM 处理业务逻辑。
建议在 WIT 中新增:
```wit
interface plugin-api {
init: func() -> result<_, string>;
on-tenant-created: func(tenant-id: string) -> result<_, string>;
handle-event: func(event-type: string, payload: list<u8>) -> result<_, string>;
// 新增
render-page: func(page-path: string, params: list<u8>) -> result<list<u8>, string>;
handle-action: func(page-path: string, action: string, data: list<u8>) -> result<list<u8>, string>;
}
```
---
## 6. Manifest ui.pages 设计审查
### 6.1 当前类型覆盖度评估
| 类型 | CRM 场景 | 是否覆盖 | 说明 |
|------|---------|---------|------|
| `crud` | 客户列表、联系人列表、沟通记录列表 | 覆盖 | 基本够用 |
| `tree` | 客户层级树 | 需增强 | 需要增加 `tree_config` 配置 |
| `graph` | 关系图谱 | 需增强 | 需要增加 `graph_config`relationship_entity, center_id, depth |
| `timeline` | 沟通记录时间线 | 新增 | Ant Design Timeline 组件天然支持 |
| `tabs` | 客户 360 度视图 | 需增强 | 需要支持嵌套子页面 |
| `dashboard` | CRM 首页统计 | 缺失 | 需要 `stat_cards` + `charts` 配置 |
| `kanban` | 客户跟进阶段看板 | 缺失 | 按客户等级/跟进状态分列 |
| `detail` | 客户详情页 | 缺失 | 不是 CRUD是复合视图 |
### 6.2 建议的页面类型扩展
```typescript
interface PluginPage {
route: string;
entity: string;
display_name: string;
icon?: string;
menu_group?: string;
// 页面类型扩展
type: "crud" | "tree" | "graph" | "timeline" | "tabs" | "dashboard" | "detail" | "custom";
// 类型专属配置
crud_config?: CrudConfig; // 列/过滤/排序/表单/操作
tree_config?: TreeConfig; // 父节点字段/标签字段/最大深度
graph_config?: GraphConfig; // 关系实体/关系类型/默认深度
dashboard_config?: DashboardConfig; // 统计卡片/图表
detail_config?: DetailConfig; // 关联实体/布局
tabs_config?: TabsConfig; // 子标签页列表
}
```
### 6.3 对 CRM 最关键的缺失detail 页面类型
CRM 最核心的交互不是列表 CRUD而是**客户 360 度详情页**
```
┌────────────────────────────────────────────────┐
│ 客户名称:某某集团 [编辑] [更多 ▾] │
│ 类型:企业 | 行业:制造业 | 级别VIP │
├────────────────────────────────────────────────┤
│ [基本信息] [联系人] [沟通记录] [关系图谱] [标签] │
├────────────────────────────────────────────────┤
│ 联系人列表(联系人的 CRUD自动过滤当前客户
│ + 新增联系人 │
│ ┌─────┬──────┬──────┬───────┬────┐ │
│ │姓名 │职位 │电话 │邮箱 │操作│ │
│ └─────┴──────┴──────┴───────┴────┘ │
└────────────────────────────────────────────────┘
```
这种页面需要:
1. 顶部展示主实体详情
2. 底部 tabs 展示关联实体列表
3. 关联实体自动按主实体 ID 过滤
建议 `detail` 类型配置:
```toml
[[ui.pages]]
route = "/crm/customer/:id"
entity = "customer"
type = "detail"
display_name = "客户详情"
[ui.pages.detail_config.header]
title_field = "name"
subtitles = [
{ field = "customer_type", label = "类型" },
{ field = "industry", label = "行业" },
{ field = "level", label = "级别", tag_colors = {vip = "gold", svip = "red"} }
]
actions = ["edit", "delete", { label = "转为正式客户", action = "activate", permission = "crm.customer.update" }]
[[ui.pages.detail_config.tabs]]
label = "联系人"
entity = "contact"
filter_field = "customer_id"
type = "crud"
[[ui.pages.detail_config.tabs]]
label = "沟通记录"
entity = "communication"
filter_field = "customer_id"
type = "timeline"
[[ui.pages.detail_config.tabs]]
label = "关系图谱"
entity = "customer_relationship"
type = "graph"
graph_config = { center_from = "from_customer_id", center_to = "to_customer_id" }
```
---
## 7. 权限模型审查
### 7.1 当前方案的 8 个权限码
根据 CRM 插件常见需求,推测 8 个权限码为:
- `crm.customer.list` / `crm.customer.create` / `crm.customer.update` / `crm.customer.delete`
- `crm.contact.list` / `crm.contact.create` / `crm.contact.update` / `crm.contact.delete`
### 7.2 不足之处
**问题 1缺少沟通记录和关系图谱的权限**
沟通记录communication和关系图谱customer_relationship是独立实体应该有独立的权限码。
**问题 2缺少数据范围权限data scope**
CRM 中最常见的权限需求不是"能不能看联系人",而是"能看到哪些客户的联系人"。典型的数据范围:
- **本人**:只看自己创建的客户
- **本部门**:看本部门所有人创建的客户
- **本部门及下级**:看本部门及子部门创建的客户
- **全部**:看所有客户
当前的 `check_permission` 只支持布尔型权限检查(有没有这个权限),不支持数据范围过滤。
**问题 3级联权限问题**
"联系人/沟通记录的访问是否应该依赖客户级别的权限?"——**是的,应该依赖**。如果用户看不到某个客户,就不应该看到该客户下的联系人和沟通记录。
### 7.3 建议的权限模型
**权限码16 个)**
```
crm.customer.list # 查看客户列表
crm.customer.create # 创建客户
crm.customer.update # 编辑客户
crm.customer.delete # 删除客户
crm.customer.export # 导出客户数据
crm.contact.list # 查看联系人
crm.contact.create # 创建联系人
crm.contact.update # 编辑联系人
crm.contact.delete # 删除联系人
crm.communication.list # 查看沟通记录
crm.communication.create # 创建沟通记录
crm.communication.update # 编辑沟通记录
crm.communication.delete # 删除沟通记录
crm.relationship.view # 查看关系图谱
crm.tag.manage # 管理标签分类
crm.customer.all # 数据范围:查看所有客户(默认只看自己的)
```
**数据范围过滤的实现**
这不是插件自身能解决的,需要基座支持。建议在 `current_user` API 返回的数据中包含数据范围信息,或者在 Host API 层增加数据范围过滤:
```wit
/// 扩展 current_user 返回值
current-user: func() -> result<list<u8>, string>;
// 返回结构增加 data_scope 字段
// { "id": "...", "tenant_id": "...", "data_scope": "all" | "department" | "department_tree" | "self" }
```
然后在 `db_query` 的过滤逻辑中自动注入 `created_by` 过滤。
**关于级联权限**:建议在 REST API 层处理。当查询联系人时,如果用户没有 `crm.customer.all`,则联系人查询自动 JOIN customer 表检查 `created_by`
```sql
SELECT c.* FROM plugin_crm_contact c
JOIN plugin_crm_customer cu ON c.data->>'customer_id' = cu.id::text
WHERE c.tenant_id = $1 AND c.deleted_at IS NULL
AND (cu.data->>'created_by' = $2 OR cu.data->>'level' IN ('vip', 'svip'))
-- VIP 客户所有人可见
```
这种过滤逻辑复杂且与业务强相关,建议在 WASM 插件的 `handle_action` 中实现,或者作为基座的通用数据范围机制。
---
## 8. 综合评估与实施建议
### 8.1 可行性评估
| 维度 | 评分 | 说明 |
|------|------|------|
| JSONB 数据存储 | 8/10 | PostgreSQL JSONB 足够支撑,索引机制需增强 |
| 单实体 CRUD | 6/10 | 基本可用,但查询/排序/过滤能力严重不足 |
| 跨实体关联 | 3/10 | 当前完全不支持,是最需要补强的部分 |
| 树形层级 | 3/10 | 需要递归查询支持 |
| 关系图谱 | 5/10 | 存储可行,查询/渲染需要增强 |
| 权限模型 | 5/10 | 权限码够用,数据范围权限缺失 |
| UI 配置驱动 | 4/10 | 缺少 detail/dashboard/kanban 类型 |
### 8.2 实施路径建议
**Phase 1基座查询能力增强前置条件约 3-5 天)**
1. 实现 `db_query` 的 filter 参数解析和 SQL 构建
2. 实现 `db-aggregate` Host API
3. REST API 支持 filter / order_by 参数
4. 为外键字段自动创建索引
5. 实现 unique 约束的插入前校验
**Phase 2CRM 核心功能(约 5-7 天)**
1. 创建 CRM 插件 crate5 个实体)
2. 实现客户 + 联系人 + 沟通记录的 CRUD
3. 实现 `type: detail` 前端通用组件
4. 实现客户详情页tabs 嵌套关联实体)
**Phase 3高级功能约 3-5 天)**
1. 实现 `db-query-tree` 支持客户层级
2. 实现关系图谱页面
3. 实现数据范围权限过滤
4. 实现 `render-page` / `handle-action` 支持自定义操作
### 8.3 风险提示
1. **最大的风险不是技术,是 Host API 的演进方向**。每增加一个 Host API 函数,就意味着所有已安装的插件都依赖这个接口。建议在 CRM 插件开发前先冻结 Host API v2包含上述增强然后所有插件基于 v2 开发。
2. **JSONB 动态表的性能天花板**。单表数据量超过 100 万条时JSONB 的查询性能会显著下降(相比原生列)。如果 CRM 模块被高频使用长期应考虑将高频实体customer升级为原生列存储。
3. **前端通用组件的复杂度**。detail + graph + tree + timeline 这四种页面类型,每种都需要基座提供通用实现。这不是 CRM 独有的需求,而是所有行业插件的共同需求。建议将这些组件沉淀为基座的 `PluginUI` 组件库,而不是 CRM 插件的一部分。
---
## 附录Host API v2 建议的 WIT 定义
```wit
package erp:plugin;
interface host-api {
// === 基础 CRUD ===
db-insert: func(entity: string, data: list<u8>) -> result<list<u8>, string>;
db-get-by-id: func(entity: string, id: string) -> result<list<u8>, string>;
db-query: func(entity: string, filter: list<u8>, pagination: list<u8>) -> result<list<u8>, string>;
db-update: func(entity: string, id: string, data: list<u8>, version: s64) -> result<list<u8>, string>;
db-delete: func(entity: string, id: string) -> result<_, string>;
db-exists: func(entity: string, filter: list<u8>) -> result<bool, string>;
db-count: func(entity: string, filter: list<u8>) -> result<s64, string>;
// === 批量操作 ===
db-query-batch: func(entity: string, field: string, ids: list<string>) -> result<list<u8>, string>;
db-batch-insert: func(entity: string, items: list<list<u8>>) -> result<list<list<u8>>, string>;
// === 高级查询 ===
db-aggregate: func(entity: string, query: list<u8>) -> result<list<u8>, string>;
db-query-tree: func(entity: string, parent-field: string, root-id: string, max-depth: s32) -> result<list<u8>, string>;
// === 事件总线 ===
event-publish: func(event-type: string, payload: list<u8>) -> result<_, string>;
// === 配置 ===
config-get: func(key: string) -> result<list<u8>, string>;
// === 日志 ===
log-write: func(level: string, message: string);
// === 用户/权限 ===
current-user: func() -> result<list<u8>, string>;
check-permission: func(permission: string) -> result<bool, string>;
}
interface plugin-api {
init: func() -> result<_, string>;
on-tenant-created: func(tenant-id: string) -> result<_, string>;
on-tenant-deleted: func(tenant-id: string) -> result<_, string>;
handle-event: func(event-type: string, payload: list<u8>) -> result<_, string>;
render-page: func(page-path: string, params: list<u8>) -> result<list<u8>, string>;
handle-action: func(page-path: string, action: string, data: list<u8>) -> result<list<u8>, string>;
}
world plugin-world {
import host-api;
export plugin-api;
}
```
共计 15 个 Host API 函数vs 当前 9 个6 个 Plugin API 函数vs 当前 3 个)。