- CLAUDE.md §12 新增 CRM 插件完成记录和 erp-plugin-crm 模块 - §13 新增动态表 SQL 注入防护和插件权限注册反模式 - §10 scope 表补充 plugin/crm 范围 - §11 设计文档索引补充 CRM 插件设计和实施计划 - 新建 .claude/skills/plugin-development/SKILL.md 可复用插件开发流程
5.1 KiB
5.1 KiB
插件开发 Skill
基于 CRM 客户管理插件的开发经验,提炼可复用的插件开发流程和模式。
触发场景
- 用户说"开发一个新插件"、"新建行业模块"、"创建插件"
- 用户提到需要在 ERP 平台上扩展新的业务模块
插件开发流程
第一步:需求分析 → 数据模型
- 确定插件 ID(如
erp-crm、erp-inventory) - 列出实体及其字段,为每个字段标注:
field_type: String/Integer/Float/Boolean/Date/DateTime/Uuid/Decimal/Jsonrequired/unique/searchable/filterable/sortablevisible_when: 条件显示表达式(如type == 'enterprise')ui_widget: 表单控件(input/select/textarea/datepicker)options: select 类型的选项列表
第二步:编写 plugin.toml manifest
[metadata]
id = "erp-xxx"
name = "模块名称"
version = "0.1.0"
description = "描述"
author = "ERP Team"
min_platform_version = "0.1.0"
# 权限:{entity}.{list|manage}
[[permissions]]
code = "entity.list"
name = "查看 XX"
[[permissions]]
code = "entity.manage"
name = "管理 XX"
# 实体定义
[[schema.entities]]
name = "entity"
display_name = "实体名"
[[schema.entities.fields]]
name = "field_name"
field_type = "String"
required = true
display_name = "字段名"
searchable = true
# 页面声明
[[ui.pages]]
type = "crud"
entity = "entity"
label = "页面标题"
icon = "icon-name"
enable_search = true
第三步:创建 Rust crate
mkdir -p crates/erp-plugin-xxx/src
Cargo.toml:
[package]
name = "erp-plugin-xxx"
version = "0.1.0"
edition = "2024"
[lib]
crate-type = ["cdylib"]
[dependencies]
wit-bindgen = "0.55"
serde = { workspace = true }
serde_json = { workspace = true }
src/lib.rs:
wit_bindgen::generate!({
path: "../erp-plugin-prototype/wit/plugin.wit",
world: "plugin-world",
});
use crate::exports::erp::plugin::plugin_api::Guest;
struct XxxPlugin;
impl Guest for XxxPlugin {
fn init() -> Result<(), String> { Ok(()) }
fn on_tenant_created(_tenant_id: String) -> Result<(), String> { Ok(()) }
fn handle_event(_event_type: String, _payload: Vec<u8>) -> Result<(), String> { Ok(()) }
}
export!(XxxPlugin);
第四步:注册到 workspace
根 Cargo.toml 的 [workspace] members 添加 "crates/erp-plugin-xxx"。
第五步:编译和转换
cargo build -p erp-plugin-xxx --target wasm32-unknown-unknown --release
wasm-tools component new target/wasm32-unknown-unknown/release/erp_plugin_xxx.wasm -o target/erp_plugin_xxx.component.wasm
第六步:上传和测试
PluginAdmin 页面上传 .component.wasm + plugin.toml:上传 → 安装 → 启用。
可用页面类型
| 类型 | 说明 | 必填配置 |
|---|---|---|
crud |
增删改查表格 | entity, label |
tree |
树形展示 | entity, label, id_field, parent_field, label_field |
detail |
详情 Drawer | entity, label, sections |
tabs |
标签页容器 | label, tabs(子页面列表) |
detail section 类型
| 类型 | 说明 | 配置 |
|---|---|---|
fields |
字段描述列表 | label, fields(字段名数组) |
crud |
嵌套 CRUD 表格 | label, entity, filter_field |
字段属性速查
| 属性 | 说明 |
|---|---|
searchable |
可搜索,自动创建 B-tree 索引 |
filterable |
可筛选,前端渲染 Select |
sortable |
可排序,表格列头排序图标 |
visible_when |
条件显示,格式 field == 'value' |
unique |
唯一约束,CREATE UNIQUE INDEX |
ui_widget |
控件:select / textarea |
options |
select 选项 [{label, value}] |
权限规则
- 格式:
{entity}.{list|manage} - 安装时自动加 manifest_id 前缀
- REST API 动态检查,无精细权限时回退
plugin.list/plugin.admin
REST API
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/v1/plugins/{id}/{entity} |
列表(filter/search/sort) |
| POST | /api/v1/plugins/{id}/{entity} |
创建(required 校验) |
| GET | /api/v1/plugins/{id}/{entity}/{rid} |
详情 |
| PUT | /api/v1/plugins/{id}/{entity}/{rid} |
更新(乐观锁) |
| DELETE | /api/v1/plugins/{id}/{entity}/{rid} |
软删除 |
| GET | /api/v1/plugins/{id}/{entity}/count |
统计 |
| GET | /api/v1/plugins/{id}/{entity}/aggregate |
聚合 |
测试检查清单
cargo check --workspace通过cargo test --workspace通过- WASM 编译 + Component 转换成功
- 上传 → 安装 → 启用流程正常
- CRUD 完整可用
- 唯一字段重复插入返回冲突
- filter/search/sort 查询正常
- visible_when 条件字段动态显示
- 侧边栏菜单正确生成
常见陷阱
- 表名格式:
plugin_{sanitized_id}_{sanitized_entity},连字符变下划线 - edition 必须是 "2024"
- WIT 路径:
../erp-plugin-prototype/wit/plugin.wit,不是erp-plugin - JSONB 无外键约束,Uuid 字段不自动校验引用完整性
- Fuel 限制 1000 万,简单逻辑足够,避免重计算循环
- manifest 中只写
entity.action,安装时自动加 manifest_id 前缀