feat(plugin): P2-P4 插件平台演进 — 通用服务 + 质量保障 + 市场

P2 平台通用服务:
- manifest 扩展: settings/numbering/templates/trigger_events/importable/exportable 声明
- 插件配置 UI: PluginSettingsForm 自动表单 + 后端校验 + 详情抽屉 Settings 标签页
- 编号规则: Host API numbering-generate + PostgreSQL 序列 + manifest 绑定
- 触发事件: data_service create/update/delete 自动发布 DomainEvent
- WIT 接口: 新增 numbering-generate/setting-get Host API

P3 质量保障:
- plugin_validator.rs: 安全扫描(WASM大小/实体数量/字段校验) + 复杂度评分
- 运行时监控指标: RuntimeMetrics (错误率/响应时间/Fuel/内存)
- 性能基准: BenchmarkResult 阈值定义
- 上传时自动安全扫描 + /validate API 端点

P4 插件市场:
- 数据库迁移: plugin_market_entries + plugin_market_reviews 表
- 前端 PluginMarket 页面: 分类浏览/搜索/详情/评分
- 路由注册: /plugins/market

测试: 269 全通过 (71 erp-plugin + 41 auth + 57 config + 34 core + 50 message + 16 workflow)
This commit is contained in:
iven
2026-04-19 12:16:24 +08:00
parent c4b1e9e56d
commit e429448c42
20 changed files with 1889 additions and 46 deletions

View File

@@ -1,6 +1,6 @@
use std::collections::HashMap;
use sea_orm::DatabaseConnection;
use sea_orm::{ConnectionTrait, DatabaseConnection};
use uuid::Uuid;
use wasmtime::StoreLimits;
@@ -58,6 +58,19 @@ pub struct HostState {
pub(crate) event_bus: Option<erp_core::events::EventBus>,
// 跨插件实体映射:"erp-crm.customer" → "plugin_erp_crm__customer"
pub(crate) cross_plugin_entities: HashMap<String, String>,
// 编号规则映射:"invoice" → "INV-{YEAR}-{SEQ:4}"
pub(crate) numbering_rules: HashMap<String, NumberingRule>,
// 插件配置值
pub(crate) plugin_config: serde_json::Value,
}
/// 编号规则缓存
#[derive(Debug, Clone)]
pub struct NumberingRule {
pub prefix: String,
pub format: String,
pub seq_length: u32,
pub reset_rule: String,
}
impl HostState {
@@ -85,6 +98,8 @@ impl HostState {
db: None,
event_bus: None,
cross_plugin_entities: HashMap::new(),
numbering_rules: HashMap::new(),
plugin_config: serde_json::json!({}),
}
}
@@ -289,4 +304,66 @@ impl host_api::Host for HostState {
fn check_permission(&mut self, permission: String) -> Result<bool, String> {
Ok(self.permissions.contains(&permission))
}
fn numbering_generate(&mut self, rule_key: String) -> Result<String, String> {
let rule = self.numbering_rules
.get(&rule_key)
.ok_or_else(|| format!("编号规则 '{}' 未声明", rule_key))?;
let db = self.db.clone()
.ok_or("编号生成需要数据库连接")?;
// 使用 advisory lock 生成编号
let rt = tokio::runtime::Handle::current();
rt.block_on(async {
// 简单实现:基于日期+序列
let now = chrono::Utc::now();
let year = now.format("%Y").to_string();
let month = now.format("%m").to_string();
// 使用 PostgreSQL 序列确保并发安全
use sea_orm::{Statement, FromQueryResult};
#[derive(Debug, FromQueryResult)]
struct SeqVal { nextval: i64 }
let seq_name = format!("plugin_{}_{}_seq", self.plugin_id.replace('-', "_"), rule_key);
let create_sql = format!(
"CREATE SEQUENCE IF NOT EXISTS {} START WITH 1 INCREMENT BY 1",
seq_name
);
let result: Result<sea_orm::ExecResult, sea_orm::DbErr> = db.execute(Statement::from_string(
sea_orm::DatabaseBackend::Postgres,
create_sql,
)).await;
result.map_err(|e| format!("创建序列失败: {}", e))?;
let seq_sql = format!("SELECT nextval('{}') as nextval", seq_name);
let result: Option<SeqVal> = SeqVal::find_by_statement(Statement::from_string(
sea_orm::DatabaseBackend::Postgres,
seq_sql,
)).one(&db).await.map_err(|e| format!("获取序列失败: {}", e))?;
let seq = result.map(|r| r.nextval).unwrap_or(1);
let seq_str = format!("{:0>width$}", seq, width = rule.seq_length as usize);
let number = rule.format
.replace("{PREFIX}", &rule.prefix)
.replace("{YEAR}", &year)
.replace("{MONTH}", &month)
.replace(&format!("{{SEQ:{}}}", rule.seq_length), &seq_str)
.replace("{SEQ}", &seq_str);
Ok(number)
})
}
fn setting_get(&mut self, key: String) -> Result<Vec<u8>, String> {
let config = self.plugin_config.as_object()
.ok_or("插件配置不是有效对象")?;
let value = config.get(&key)
.cloned()
.unwrap_or(serde_json::Value::Null);
serde_json::to_vec(&value).map_err(|e| e.to_string())
}
}