// PersistPostInterceptor —— 移植自 rust lib.rs PersistPostInterceptor(spec/09)
// ARCHIVE(结束同意归档 INSERT,幂等键 process_instance_id)/
// SYNC(发起 INSERT → 任务 UPDATE 按字段权限 → 结束定稿)。
// 字段提取:实例变量 f_/tf_ 前缀键去前缀(L2-06/07:不是 exec.args)。
///|
pub(all) struct PersistPostInterceptor {
meta_provider : InMemoryMetaProvider
table_writer : &DynamicTableWriter
}
///|
pub fn PersistPostInterceptor::make(
meta_provider : InMemoryMetaProvider,
table_writer : &DynamicTableWriter
) -> PersistPostInterceptor {
{ meta_provider, table_writer }
}
///|
/// 装配为引擎拦截器(order=100,后置)
pub fn PersistPostInterceptor::as_interceptor(
self : PersistPostInterceptor
) -> @spi.InterceptorFn {
let it = self
{ order: 100, run: fn(exec) { it.intercept(exec) } }
}
///|
pub fn PersistPostInterceptor::intercept(
self : PersistPostInterceptor,
exec : @exec.Execution
) -> Unit raise @error.JeeflowError {
let persist_mode = exec.process_model.persist_mode
match persist_mode {
None => return // 未配置持久化
Some(pm) =>
match PersistMode::from_str(pm) {
None => return // 未知模式:静默跳过
Some(mode) => {
let table = match self.resolve_table(exec.process_model) {
Some(t) => t
None => return
}
if !is_table_name_safe(table) {
// 配置错误显性暴露(对齐 Java/Go,issues/60 原则)
raise @error.Business("非法业务表名: \{table}")
}
match mode {
Archive => self.intercept_archive(exec, table)
Sync => self.intercept_sync(exec, table)
}
}
}
}
}
///|
fn PersistPostInterceptor::intercept_archive(
self : PersistPostInterceptor,
exec : @exec.Execution,
table : String
) -> Unit raise @error.JeeflowError {
// 时机:仅结束节点 + FINISHED(20) + submitType=1
let is_end = match exec.current_node {
Some(n) => n.node_type is @parser.End
None => false
}
if !is_end {
return
}
if exec.process_instance.state != 20 {
return
}
if exec.args.get_i64("submitType") != Some(1L) {
return
}
// 幂等:process_instance_id 先查后插(C16)
if (self.table_writer).exists_by_key(table, "process_instance_id", exec.process_instance.instance_id) {
return
}
let data = self.extract_fields(exec.process_instance.variables, None, false, true)
self.fill_context(data, exec)
let meta = self.meta(table)
let filtered = self.filter_editable(meta, data)
if filtered.is_empty() {
return
}
fill_system_fields(filtered, exec.operator, true)
let _ = (self.table_writer).insert(table, filtered)
}
///|
fn PersistPostInterceptor::intercept_sync(
self : PersistPostInterceptor,
exec : @exec.Execution,
table : String
) -> Unit raise @error.JeeflowError {
let node = match exec.current_node {
Some(n) => n
None => return
}
let instance = exec.process_instance
let is_task = node.node_type is @parser.Task || node.node_type is @parser.Custom
let exists = (self.table_writer).exists_by_key(table, "process_instance_id", instance.instance_id)
// 任务节点按目标节点 field.PERMISSION_* 过滤;start/结束不带 f_(start 首次 INSERT 例外全量)
let field_perm = if is_task {
self.resolve_field_permission(node)
} else {
None
}
let include_fields = !exists || is_task
let data = self.extract_fields(instance.variables, field_perm, true, include_fields)
// 状态字段:任务节点写 DOING(10);结束节点写实例终态;start 无状态列跳过
let state_code : Int? = if is_task {
Some(10)
} else if node.node_type is @parser.End {
Some(instance.state)
} else {
None
}
match state_code {
Some(code) => self.put_state_field(table, data, node.id, code)
None => ()
}
self.fill_context(data, exec)
let meta = self.meta(table)
let filtered = self.filter_editable(meta, data)
if filtered.is_empty() {
return
}
if exists {
fill_system_fields(filtered, exec.operator, false)
(self.table_writer).update_by_key(table, filtered, "process_instance_id", instance.instance_id)
} else {
fill_system_fields(filtered, exec.operator, true)
let _ = (self.table_writer).insert(table, filtered)
}
}
///|
/// 表名:relTableName 缺省回落流程 name
fn PersistPostInterceptor::resolve_table(self : PersistPostInterceptor, model : @parser.ProcessModel) -> String? {
let name = match model.rel_table_name {
Some(t) => t
None => model.name
}
let trimmed = name.trim(chars=" \t\r\n").to_string()
if trimmed.is_empty() {
None
} else {
Some(trimmed)
}
}
///|
fn PersistPostInterceptor::meta(self : PersistPostInterceptor, table : String) -> TableMeta raise @error.JeeflowError {
match (self.meta_provider).get_table_meta(table) {
Some(m) => m
None => raise @error.Business("表元数据不存在: \{table}")
}
}
///|
/// 按列权限过滤可写字段
fn PersistPostInterceptor::filter_editable(
self : PersistPostInterceptor,
meta : TableMeta,
data : Map[String, Json]
) -> Map[String, Json] {
let filtered : Map[String, Json] = Map::new()
for field in meta.fields {
if field.is_editable() {
match data.get(field.column_name) {
Some(v) => filtered[field.column_name] = v
None => ()
}
}
}
filtered
}
///|
/// 提取字段:f_/tf_ 去前缀(SYNC 任务节点按字段权限过滤 f_)
fn PersistPostInterceptor::extract_fields(
self : PersistPostInterceptor,
variables : @json.FlowData,
field_perm : Map[String, Json]?,
include_task_fields : Bool,
include_form_fields : Bool
) -> Map[String, Json] {
let data : Map[String, Json] = Map::new()
for key, value in variables.inner() {
if key.starts_with("f_") {
let name = key.substring(start=2).to_string()
if !name.is_empty() && include_form_fields && self.is_editable(field_perm, name) {
data[name] = value
}
} else if key.starts_with("tf_") {
let name = key.substring(start=3).to_string()
if !name.is_empty() && include_task_fields {
data[name] = value
}
}
}
data
}
///|
/// 节点字段权限(properties.field 的 PERMISSION_x)
fn PersistPostInterceptor::resolve_field_permission(
self : PersistPostInterceptor,
node : @parser.NodeModel
) -> Map[String, Json]? {
let _ = self
match node.properties.get("field") {
Some(f) =>
match f {
Json::Object(obj) => if obj.size() == 0 { None } else { Some(obj) }
_ => None
}
None => None
}
}
///|
/// 字段可编辑判定(issues/25 双格式键:PERMISSION_f_{全名} 优先 / PERMISSION_{去前缀})
fn PersistPostInterceptor::is_editable(
self : PersistPostInterceptor,
field_perm : Map[String, Json]?,
field_name : String
) -> Bool {
let _ = self
let perm = match field_perm {
Some(p) => if p.size() == 0 { return true } else { p }
None => return true
}
let val = match perm.get("PERMISSION_f_\{field_name}") {
Some(v) => Some(v)
None => perm.get("PERMISSION_\{field_name}")
}
match val {
None => true
Some(v) =>
match @json.as_i64(v) {
Some(n) => n == 2L
None => false
}
}
}
///|
/// 状态字段:优先 {节点ID}_{状态码} 列,无则 {节点ID} 列(列探测过滤)
fn PersistPostInterceptor::put_state_field(
self : PersistPostInterceptor,
table : String,
data : Map[String, Json],
node_id : String,
state_code : Int
) -> Unit raise @error.JeeflowError {
if node_id.is_empty() {
return
}
let candidates = ["\{node_id}_\{state_code}", node_id]
let kept = (self.table_writer).columns(table, candidates)
match kept.get(0) {
Some(col) => data[col] = @json.number_of(state_code.to_int64())
None => ()
}
}
///|
/// 流程上下文字段(putIfAbsent 语义,C17:create_user 回落 apply_user_id)
fn PersistPostInterceptor::fill_context(self : PersistPostInterceptor, data : Map[String, Json], exec : @exec.Execution) -> Unit {
let _ = self
let instance = exec.process_instance
if data.get("process_instance_id") is None {
data["process_instance_id"] = @json.number_of(instance.instance_id)
}
if data.get("apply_user_id") is None {
data["apply_user_id"] = @json.string_of(instance.operator)
}
match instance.variables.get_str("u_deptId") {
Some(dept) =>
if data.get("apply_dept_id") is None {
data["apply_dept_id"] = @json.string_of(dept)
}
None => ()
}
}