package jnpf.base.service; import jnpf.base.entity.VisualdevEntity; import jnpf.base.mapper.UploadEditDraftMapper; import jnpf.base.model.uploadedit.UploadEditContext; import jnpf.base.model.uploadedit.UploadEditDraft; import jnpf.base.util.FlowFormDataUtil; import jnpf.base.util.UploadEditStateMachine; import jnpf.base.util.UploadEditWorkingCopy; import jnpf.exception.DataException; import jnpf.flowable.WorkFlowApi; import jnpf.flowable.entity.TaskEntity; import jnpf.flowable.model.filepermission.FilePermissionPolicy.Action; import jnpf.flowable.model.filepermission.FilePermissionView; import jnpf.model.fileaccess.WorkflowFileGrant; import jnpf.model.fileaccess.WorkflowFileRequest; import jnpf.onlinedev.model.OnlineInfoModel; import jnpf.util.JsonUtil; import jnpf.util.UserProvider; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.ObjectProvider; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import java.util.*; /** 通用附件读取准入;不依赖 onlineEdit,不从请求中的 taskId 推断实际归属。 */ @Service @RequiredArgsConstructor public class WorkflowFileAccessService { private final ObjectProvider visualdevService; private final ObjectProvider formDataUtil; private final UploadEditDraftMapper drafts; private final WorkFlowApi workflows; public WorkflowFileGrant authorize(WorkflowFileRequest request) { if (request == null || !text(request.getModelId()) || !text(request.getRecordId()) || !text(request.getField()) || !text(request.getFileId())) throw denied(); String user = UserProvider.getLoginUserId(); String tenant = UploadEditDraftService.tenant(UserProvider.getUser()); if (!text(user) || !Objects.equals(user, request.getOwnerId()) || !Objects.equals(tenant, request.getTenantId())) throw denied(); Action action; try { action = Action.valueOf(request.getAction()); } catch (RuntimeException invalid) { throw denied(); } if (action != Action.VIEW && action != Action.DOWNLOAD) throw denied(); VisualdevEntity model = visualdevService.getObject().getReleaseInfo(request.getModelId()); if (model == null) throw denied(); Map form = JsonUtil.getJsonToBean(model.getFormData(), Map.class); Map field = findField(form.get("fields"), request.getField(), request.getParentField()); if (field == null || !"uploadFile".equals(((Map) field.get("__config__")).get("jnpfKey"))) throw denied(); Map data = formDataUtil.getObject().getFilePermissionDataInfo(model, request.getRecordId(), OnlineInfoModel.builder().build()); if (data == null || !text(string(data.get("id"))) || !data.containsKey("flowTaskId")) throw denied(); String taskId = string(data.get("flowTaskId")); // 此入口专供启用实例权限的新分支;无实例/上游查询失败均不得降级为旧接口。 TaskEntity task = text(taskId) ? workflows.getInfoSubmit(taskId) : null; if (task == null || !Objects.equals(taskId, task.getId()) || text(request.getTaskId()) && !request.getTaskId().equals(taskId)) throw denied(); FilePermissionView permissions = workflows.filePermissions(taskId, request.getModelId()); if (permissions == null || !permissions.isEnabled() || !Objects.equals(taskId, permissions.getTaskId()) || !Objects.equals(request.getModelId(), permissions.getFormId()) || !"ready".equals(permissions.getStatus())) throw denied(); String fieldKey = text(request.getParentField()) ? request.getParentField() + "-" + request.getField() : request.getField(); if (!permissions.allows(fieldKey, action)) throw denied(); if (text(request.getLeaseId()) && (action != Action.VIEW || !text(request.getDraftId()) || !permissions.allows(fieldKey, Action.EDIT) || !Boolean.TRUE.equals(field.get("onlineEdit")))) throw denied(); boolean allowDownload = (!field.containsKey("allowDownload") || Boolean.TRUE.equals(field.get("allowDownload"))) && permissions.allows(fieldKey, Action.DOWNLOAD); if (action == Action.DOWNLOAD && !allowDownload) throw denied(); // DMS shared preview reads the same immutable saved content as the submission manifest. boolean sharedContent = text(request.getDraftId()) && !text(request.getLeaseId()); UploadEditContext context = context(request); context.setRecordId(string(data.get("id"))); List> baseline = UploadEditStateMachine.files(UploadEditAccess.fieldValue(data, context)); List> candidates = baseline; if (text(request.getLeaseId()) || sharedContent) { UploadEditDraft draft = drafts.find(tenant, request.getDraftId()); if (draft == null || !Objects.equals(request.getDraftId(), draft.getId()) || !Objects.equals(tenant, draft.getTenantId()) || !(UploadEditWorkingCopy.shared(draft) && Objects.equals(taskId, draft.getTaskId())) && !Objects.equals(user, draft.getOwnerId()) || !Objects.equals(request.getModelId(), draft.getModelId()) || !Objects.equals(context.getRecordId(), draft.getRecordId()) || !Objects.equals(request.getField(), draft.getField()) || !same(request.getParentField(), draft.getParentField()) || !same(request.getRowId(), draft.getRowId()) || !UploadEditStateMachine.sameFiles(draft.getBaselineJson(), baseline)) throw denied(); if (sharedContent && (!UploadEditWorkingCopy.shared(draft) || !Objects.equals(taskId, draft.getTaskId()) || !Boolean.TRUE.equals(field.get("onlineEdit")) || !(Set.of("dms_wenjian_xinzeng_shenhe", "dms_wenjian_xiuding_shenhe").contains(Objects.toString(form.get("tableName"), "")) && ((!text(context.getParentField()) && "wenjian".equals(context.getField())) || ("jilu_mingxi".equals(context.getParentField()) && "jilu_wenjian".equals(context.getField())))) || !Objects.equals(request.getContentToken(), UploadEditStateMachine.contentToken(draft.getCurrentJson())))) throw denied(); if (text(request.getLeaseId())) { String activeFile = draft.getLeaseResultFileId() == null ? draft.getLeaseFileId() : draft.getLeaseResultFileId(); if (!Objects.equals(user, draft.getOwnerId()) || !Objects.equals(request.getLeaseId(), draft.getLeaseId()) || !Objects.equals(request.getFileId(), activeFile)) throw denied(); } candidates = UploadEditStateMachine.files(draft.getCurrentJson()); } Map file = candidates.stream().filter(f -> request.getFileId().equals(f.get("fileId"))) .findFirst().orElseThrow(WorkflowFileAccessService::denied); WorkflowFileRequest canonical = JsonUtil.getJsonToBean(JsonUtil.getObjectToString(request), WorkflowFileRequest.class); canonical.setRecordId(context.getRecordId()); canonical.setTaskId(taskId); WorkflowFileGrant result = new WorkflowFileGrant(); result.setContext(canonical); result.setFile(new LinkedHashMap<>(file)); result.setControlled(true); result.setAllowDownload(allowDownload); result.setRevision(permissions.getRevision()); return result; } private static UploadEditContext context(WorkflowFileRequest request) { UploadEditContext context = new UploadEditContext(); context.setModelId(request.getModelId()); context.setRecordId(request.getRecordId()); context.setField(request.getField()); context.setParentField(request.getParentField()); context.setRowId(request.getRowId()); context.setMenuId(request.getMenuId()); return context; } @SuppressWarnings("unchecked") private static Map findField(Object fields, String field, String parent) { if (!(fields instanceof List)) return null; for (Object item : (List) fields) { if (!(item instanceof Map)) continue; Map node = (Map) item; if (!(node.get("__config__") instanceof Map)) continue; Map config = (Map) node.get("__config__"); if (text(parent) && parent.equals(node.get("__vModel__")) && "table".equals(config.get("jnpfKey"))) return findField(config.get("children"), field, null); if (!text(parent) && field.equals(node.get("__vModel__"))) return (Map) node; if (!"table".equals(config.get("jnpfKey"))) { Map found = findField(config.get("children"), field, parent); if (found != null) return found; } } return null; } private static boolean same(String a, String b) { return Objects.toString(a, "").equals(Objects.toString(b, "")); } private static boolean text(String value) { return StringUtils.hasText(value); } private static String string(Object value) { return Objects.toString(value, ""); } private static DataException denied() { return new DataException("附件不属于当前表单位置,或当前没有文件访问权限"); } }