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<String, NodeModel> nodes) throws WorkFlowException {
|
try {
|
Map<String, Object> 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<String> starts = new ArrayList<>();
|
for (Map.Entry<String, NodeModel> 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<String, NodeModel> 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<String> nodeIds) throws WorkFlowException {
|
if (nodeIds == null || nodeIds.isEmpty()) return;
|
update(taskId, snapshot -> snapshot.withEnteredNodes(nodeIds), "无法记录流程文件权限节点状态:");
|
}
|
|
/** 与原生退回使用同一后续节点集合;只有本轮被作废的触发节点才解除持续规则。 */
|
public void markReturnedPast(String taskId, Collection<String> invalidatedNodeIds) throws WorkFlowException {
|
if (invalidatedNodeIds == null || invalidatedNodeIds.isEmpty()) return;
|
update(taskId, snapshot -> snapshot.withReturnedPastNodes(invalidatedNodeIds), "无法更新退回后的文件权限:");
|
}
|
|
private void update(String taskId, java.util.function.Function<FilePermissionSnapshot, String> 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<String, Object> 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<Map<String, Object>>() { }); }
|
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() : "暂时无法读取流程文件权限配置,请稍后重试");
|
}
|
}
|