feat(plugin): P1-P4 审计修复 — 第二批 (运行时监控 + 通知引擎 + 编号reset)
2.1 运行时监控:
- LoadedPlugin 新增 RuntimeMetrics (调用次数/错误/响应时间/燃料消耗)
- execute_wasm 自动采集每次调用的耗时和状态
- GET /admin/plugins/{id}/metrics 端点
2.2 通知规则引擎:
- notification.rs: 订阅 plugin.trigger.* 事件
- 触发时自动给管理员发送消息通知
- emit_trigger_events 增加 manifest_id 到 payload
2.3 编号 reset_rule:
- 替换 PostgreSQL SEQUENCE 为表行 + pg_advisory_xact_lock
- 支持 daily/monthly/yearly/never 重置周期
- 每个周期独立计数,切换时自动重置为 1
This commit is contained in:
@@ -308,49 +308,101 @@ impl host_api::Host for HostState {
|
||||
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))?;
|
||||
.ok_or_else(|| format!("编号规则 '{}' 未声明", rule_key))?
|
||||
.clone();
|
||||
|
||||
let db = self.db.clone()
|
||||
.ok_or("编号生成需要数据库连接")?;
|
||||
|
||||
// 使用 advisory lock 生成编号
|
||||
let tenant_id = self.tenant_id;
|
||||
let plugin_id = self.plugin_id.clone();
|
||||
|
||||
let rt = tokio::runtime::Handle::current();
|
||||
|
||||
rt.block_on(async {
|
||||
// 简单实现:基于日期+序列
|
||||
use sea_orm::{Statement, FromQueryResult, ConnectionTrait};
|
||||
|
||||
let now = chrono::Utc::now();
|
||||
let year = now.format("%Y").to_string();
|
||||
let month = now.format("%m").to_string();
|
||||
let day = now.format("%d").to_string();
|
||||
|
||||
// 使用 PostgreSQL 序列确保并发安全
|
||||
use sea_orm::{Statement, FromQueryResult};
|
||||
#[derive(Debug, FromQueryResult)]
|
||||
struct SeqVal { nextval: i64 }
|
||||
// 计算当前周期的 key(用于 reset_rule 判断)
|
||||
let period_key = match rule.reset_rule.as_str() {
|
||||
"daily" => format!("{}-{}-{}", year, month, day),
|
||||
"monthly" => format!("{}-{}", year, month),
|
||||
"yearly" => year.clone(),
|
||||
_ => String::new(), // "never" — 不需要周期 key
|
||||
};
|
||||
|
||||
let seq_name = format!("plugin_{}_{}_seq", self.plugin_id.replace('-', "_"), rule_key);
|
||||
// 序列表名
|
||||
let table_name = format!("plugin_numbering_seq_{}", plugin_id.replace('-', "_"));
|
||||
|
||||
// 确保序列表存在
|
||||
let create_sql = format!(
|
||||
"CREATE SEQUENCE IF NOT EXISTS {} START WITH 1 INCREMENT BY 1",
|
||||
seq_name
|
||||
"CREATE TABLE IF NOT EXISTS {} (\
|
||||
rule_key VARCHAR(255) NOT NULL, \
|
||||
period_key VARCHAR(64) NOT NULL DEFAULT '', \
|
||||
current_val BIGINT NOT NULL DEFAULT 0, \
|
||||
PRIMARY KEY (rule_key, period_key)\
|
||||
)",
|
||||
table_name
|
||||
);
|
||||
let result: Result<sea_orm::ExecResult, sea_orm::DbErr> = db.execute(Statement::from_string(
|
||||
db.execute(Statement::from_string(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
create_sql,
|
||||
)).await;
|
||||
result.map_err(|e| format!("创建序列失败: {}", e))?;
|
||||
)).await.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(
|
||||
// 使用 advisory lock 保证并发安全
|
||||
// lock_id 基于规则名哈希
|
||||
let lock_id: i64 = {
|
||||
use std::hash::{Hash, Hasher};
|
||||
let mut hasher = std::collections::hash_map::DefaultHasher::new();
|
||||
(plugin_id.clone() + &rule_key).hash(&mut hasher);
|
||||
(hasher.finish() as i64).abs()
|
||||
};
|
||||
|
||||
let lock_sql = format!("SELECT pg_advisory_xact_lock({})", lock_id);
|
||||
db.execute(Statement::from_string(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
seq_sql,
|
||||
)).one(&db).await.map_err(|e| format!("获取序列失败: {}", e))?;
|
||||
lock_sql,
|
||||
)).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);
|
||||
// 读取当前值
|
||||
#[derive(Debug, FromQueryResult)]
|
||||
struct SeqRow { current_val: i64 }
|
||||
|
||||
let read_sql = format!(
|
||||
"SELECT current_val FROM {} WHERE rule_key = $1 AND period_key = $2",
|
||||
table_name
|
||||
);
|
||||
let current = SeqRow::find_by_statement(Statement::from_sql_and_values(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
read_sql,
|
||||
[rule_key.clone().into(), period_key.clone().into()],
|
||||
)).one(&db).await.map_err(|e| format!("读取序列失败: {}", e))?;
|
||||
|
||||
let next_val = current.map(|r| r.current_val + 1).unwrap_or(1);
|
||||
|
||||
// UPSERT 新值
|
||||
let upsert_sql = format!(
|
||||
"INSERT INTO {} (rule_key, period_key, current_val) VALUES ($1, $2, $3) \
|
||||
ON CONFLICT (rule_key, period_key) DO UPDATE SET current_val = $3",
|
||||
table_name
|
||||
);
|
||||
db.execute(Statement::from_sql_and_values(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
upsert_sql,
|
||||
[rule_key.clone().into(), period_key.clone().into(), next_val.into()],
|
||||
)).await.map_err(|e| format!("更新序列失败: {}", e))?;
|
||||
|
||||
let seq_str = format!("{:0>width$}", next_val, width = rule.seq_length as usize);
|
||||
|
||||
let number = rule.format
|
||||
.replace("{PREFIX}", &rule.prefix)
|
||||
.replace("{YEAR}", &year)
|
||||
.replace("{MONTH}", &month)
|
||||
.replace("{DAY}", &day)
|
||||
.replace(&format!("{{SEQ:{}}}", rule.seq_length), &seq_str)
|
||||
.replace("{SEQ}", &seq_str);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user