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:
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user