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:
@@ -15,9 +15,28 @@ use erp_core::events::EventBus;
|
||||
use crate::PluginWorld;
|
||||
use crate::dynamic_table::DynamicTableManager;
|
||||
use crate::error::{PluginError, PluginResult};
|
||||
use crate::host::{HostState, PendingOp};
|
||||
use crate::host::{HostState, NumberingRule, PendingOp};
|
||||
use crate::manifest::PluginManifest;
|
||||
|
||||
/// 从 manifest 的 numbering 声明构建 HostState 缓存映射
|
||||
fn numbering_rules_from_manifest(manifest: &PluginManifest) -> HashMap<String, NumberingRule> {
|
||||
let mut rules = HashMap::new();
|
||||
if let Some(numbering) = &manifest.numbering {
|
||||
for n in numbering {
|
||||
rules.insert(
|
||||
n.entity.clone(),
|
||||
NumberingRule {
|
||||
prefix: n.prefix.clone(),
|
||||
format: n.format.clone(),
|
||||
seq_length: n.seq_length,
|
||||
reset_rule: format!("{:?}", n.reset_rule).to_lowercase(),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
rules
|
||||
}
|
||||
|
||||
/// 插件引擎配置
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PluginEngineConfig {
|
||||
@@ -472,6 +491,9 @@ impl PluginEngine {
|
||||
// 构建跨插件实体映射(从 manifest 的 ref_plugin 字段提取)
|
||||
let cross_plugin_entities = Self::build_cross_plugin_map(&loaded.manifest, &self.db, exec_ctx.tenant_id).await;
|
||||
|
||||
// 加载插件配置(从数据库)
|
||||
let plugin_config = Self::load_plugin_config(plugin_id, exec_ctx.tenant_id, &self.db).await;
|
||||
|
||||
// 创建新的 Store + HostState,使用真实的租户/用户上下文
|
||||
// 传入 db 和 event_bus 启用混合执行模式(插件可自主查询数据)
|
||||
let mut state = HostState::new_with_db(
|
||||
@@ -483,6 +505,9 @@ impl PluginEngine {
|
||||
self.event_bus.clone(),
|
||||
);
|
||||
state.cross_plugin_entities = cross_plugin_entities;
|
||||
// 注入编号规则和插件配置
|
||||
state.numbering_rules = numbering_rules_from_manifest(&loaded.manifest);
|
||||
state.plugin_config = plugin_config;
|
||||
let mut store = Store::new(&self.engine, state);
|
||||
store
|
||||
.set_fuel(self.config.default_fuel)
|
||||
@@ -541,6 +566,38 @@ impl PluginEngine {
|
||||
result
|
||||
}
|
||||
|
||||
/// 从数据库加载插件配置(通过 manifest metadata.id 匹配)
|
||||
fn load_plugin_config(
|
||||
plugin_id: &str,
|
||||
tenant_id: Uuid,
|
||||
db: &DatabaseConnection,
|
||||
) -> std::pin::Pin<Box<dyn std::future::Future<Output = serde_json::Value> + Send + 'static>> {
|
||||
let db = db.clone();
|
||||
let pid = plugin_id.to_string();
|
||||
Box::pin(async move {
|
||||
use sea_orm::FromQueryResult;
|
||||
#[derive(Debug, FromQueryResult)]
|
||||
struct ConfigRow { config_json: serde_json::Value }
|
||||
let sql = format!(
|
||||
"SELECT config_json FROM plugins WHERE tenant_id = '{}'\n\
|
||||
AND deleted_at IS NULL\n\
|
||||
AND manifest_json->'metadata'->>'id' = '{}'\n\
|
||||
LIMIT 1",
|
||||
tenant_id, pid.replace('\'', "''")
|
||||
);
|
||||
ConfigRow::find_by_statement(Statement::from_string(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
sql,
|
||||
))
|
||||
.one(&db)
|
||||
.await
|
||||
.ok()
|
||||
.flatten()
|
||||
.map(|r| r.config_json)
|
||||
.unwrap_or_default()
|
||||
})
|
||||
}
|
||||
|
||||
/// 从 manifest 的 ref_plugin 字段构建跨插件实体映射
|
||||
/// 返回: { "erp-crm.customer" → "plugin_erp_crm__customer", ... }
|
||||
async fn build_cross_plugin_map(
|
||||
|
||||
Reference in New Issue
Block a user