package jnpf.flowable.service; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import jnpf.base.entity.VisualdevEntity; import jnpf.exception.WorkFlowException; import jnpf.flowable.entity.TaskEntity; import jnpf.flowable.mapper.FilePermissionSnapshotMapper; import jnpf.flowable.model.filepermission.FilePermissionTaskState; import jnpf.flowable.model.filepermission.FilePermissionPolicy; import jnpf.flowable.enums.NodeEnum; import jnpf.flowable.model.templatenode.nodejson.NodeModel; import jnpf.flowable.util.ServiceUtil; import jnpf.flowable.util.filepermission.FilePermissionPolicyCompiler; import jnpf.flowable.util.filepermission.FilePermissionSnapshot; import jnpf.util.UserProvider; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import java.util.*; /** 只扩展原生任务 INSERT 的快照内容,绝不在已有任务读写时补启用新规则。 */ @Service @RequiredArgsConstructor public class FilePermissionSnapshotService { /** 仅成功查库且完全无任务行时产生;查询失败或已删除任务不能冒充菜单草稿。 */ public static final class TaskNotFoundException extends IllegalArgumentException { } private static final ObjectMapper JSON = new ObjectMapper(); private final FilePermissionSnapshotMapper mapper; private final ServiceUtil serviceUtil; /** 只能由原生“新任务”分支调用;返回值与任务在同一 INSERT 中持久化。 */ public void prepareNewTask(TaskEntity task, String sourceFlowId, Map nodes) throws WorkFlowException { try { Map policy = FilePermissionPolicyCompiler.compile(nodes, this::loadForm, task.getFlowType()); if (FilePermissionPolicy.parse(policy).isEmpty()) { task.setFilePermissionSnapshot(null); return; } String created = FilePermissionSnapshot.create(tenantId(), task.getId(), sourceFlowId, policy); List starts = new ArrayList<>(); for (Map.Entry entry : nodes.entrySet()) { NodeModel node = entry.getValue(); if (node != null && NodeEnum.START.getType().equals(node.getType())) { starts.add(node.getNodeId() == null || node.getNodeId().trim().isEmpty() ? entry.getKey() : node.getNodeId()); } } task.setFilePermissionSnapshot(FilePermissionSnapshot.read(created, tenantId(), task.getId()).withEnteredNodes(starts)); } catch (RuntimeException e) { // 原生提交在 WorkFlowException 分支补偿已启动的引擎实例;读取配置失败同样进入该分支。 throw configurationError(e); } } /** 在原生发布入口、引擎部署之前校验;不写表单或接管流程发布。 */ public void validateConfiguration(Map nodes, Integer flowType) throws WorkFlowException { try { FilePermissionPolicyCompiler.compile(nodes, this::loadForm, flowType); } catch (RuntimeException e) { throw configurationError(e); } } /** 内部读取不代表用户有访问任务的权限;调用方仍须原生准入与附件归属检查。 */ public FilePermissionSnapshot find(String taskId) { if (taskId == null || taskId.trim().isEmpty()) throw new IllegalArgumentException("缺少流程任务标识"); FilePermissionTaskState state = mapper.find(taskId); if (state == null) throw new TaskNotFoundException(); if (!taskId.equals(state.getTaskId()) || state.getDeleteMark() != null) throw new IllegalArgumentException("流程任务不可用"); return FilePermissionSnapshot.read(state.getSnapshot(), tenantId(), taskId); } public void prepareDerivedTask(TaskEntity task, String sourceTaskId) throws WorkFlowException { try { FilePermissionSnapshot source = find(sourceTaskId); task.setFilePermissionSnapshot(source == null ? null : source.forDerivedTask(task.getId())); } catch (IllegalArgumentException e) { throw new WorkFlowException("无法读取来源流程的文件权限:" + e.getMessage()); } } /** 在原生节点激活路径内单调追加;CAS 防止并行激活互相覆盖。 */ public void markEntered(String taskId, Collection nodeIds) throws WorkFlowException { if (nodeIds == null || nodeIds.isEmpty()) return; update(taskId, snapshot -> snapshot.withEnteredNodes(nodeIds), "无法记录流程文件权限节点状态:"); } /** 与原生退回使用同一后续节点集合;只有本轮被作废的触发节点才解除持续规则。 */ public void markReturnedPast(String taskId, Collection invalidatedNodeIds) throws WorkFlowException { if (invalidatedNodeIds == null || invalidatedNodeIds.isEmpty()) return; update(taskId, snapshot -> snapshot.withReturnedPastNodes(invalidatedNodeIds), "无法更新退回后的文件权限:"); } private void update(String taskId, java.util.function.Function change, String errorPrefix) throws WorkFlowException { try { for (int attempt = 0; attempt < 3; attempt++) { FilePermissionTaskState state = mapper.find(taskId); if (state == null || state.getDeleteMark() != null) throw new IllegalArgumentException("流程任务不可用"); if (state.getSnapshot() == null) return; FilePermissionSnapshot snapshot = FilePermissionSnapshot.read(state.getSnapshot(), tenantId(), taskId); String updated = change.apply(snapshot); if (updated.equals(state.getSnapshot()) || mapper.compareAndSetSnapshot(taskId, state.getSnapshot(), updated) == 1) return; } throw new IllegalArgumentException("流程节点状态已变化,请重试"); } catch (RuntimeException e) { throw new WorkFlowException(errorPrefix + e.getMessage()); } } private Map loadForm(String id) { VisualdevEntity form = serviceUtil.getReleaseInfo(id); if (form == null || form.getFormData() == null) throw new IllegalArgumentException("发布表单不存在"); try { return JSON.readValue(form.getFormData(), new TypeReference>() { }); } catch (java.io.IOException e) { throw new IllegalArgumentException("发布表单配置无效", e); } } private String tenantId() { String tenant = UserProvider.getUser().getTenantId(); return tenant == null || tenant.trim().isEmpty() ? "0" : tenant; } private WorkFlowException configurationError(RuntimeException e) { return new WorkFlowException(e instanceof IllegalArgumentException ? "文件权限配置无效:" + e.getMessage() : "暂时无法读取流程文件权限配置,请稍后重试"); } }