package jnpf.fileStorage; import jnpf.base.ActionResult; import jnpf.base.ActionResultCode; import jnpf.exception.DataException; import jnpf.file.WorkflowFileAccessApi; import jnpf.model.fileaccess.WorkflowFileGrant; import jnpf.model.fileaccess.WorkflowFileRequest; import jnpf.util.JsonUtil; import jnpf.util.TicketUtil; import jnpf.util.UserProvider; import lombok.Data; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.*; import java.util.function.Function; /** 流程动作准入与 Office 配置上下文绑定;文件传输使用原有公共能力。 */ @Service public class WorkflowFileTicketService { private static final String PREVIEW_CONFIG = "workflow-file-preview-config-v2"; private final WorkflowFileAccessApi access; private final Function reader; private final Function writer; @Autowired public WorkflowFileTicketService(WorkflowFileAccessApi access) { this(access, TicketUtil::parseTicket, payload -> TicketUtil.createTicket(payload, 300L)); } WorkflowFileTicketService(WorkflowFileAccessApi access, Function reader, Function writer) { this.access = access; this.reader = reader; this.writer = writer; } @Data public static class Ticket { private String purpose; private WorkflowFileRequest source; } public List authorizeDownloads(List requests) { if (requests == null || requests.isEmpty() || requests.size() > 100) throw denied(); List grants = new ArrayList<>(); // 全部校验完成之后,调用方才可委托原单文件/打包下载。 for (WorkflowFileRequest request : requests) grants.add(authorizeCurrent(request, "DOWNLOAD")); return grants; } public WorkflowFileGrant authorizePreview(WorkflowFileRequest request) { return authorizeCurrent(request, "VIEW"); } /** 仅供服务器用已验证的结果签发配置上下文,不签发新的文件读取地址。 */ public String previewTicket(WorkflowFileGrant grant) { Ticket ticket = new Ticket(); ticket.setPurpose(PREVIEW_CONFIG); ticket.setSource(grant.getContext()); return writer.apply(JsonUtil.getObjectToString(ticket)); } public WorkflowFileGrant previewAccess(String token) { if (token == null || !token.matches("[A-Za-z0-9_-]+")) throw denied(); Ticket ticket; try { ticket = JsonUtil.getJsonToBean(reader.apply(token), Ticket.class); } catch (RuntimeException invalid) { throw denied(); } if (ticket == null || !PREVIEW_CONFIG.equals(ticket.getPurpose()) || ticket.getSource() == null) throw denied(); WorkflowFileRequest source = ticket.getSource(); // 配置获取仍使用当前登录用户,不在 DS 回源请求中模拟票据属主。 if (!Objects.equals(UserProvider.getLoginUserId(), source.getOwnerId()) || !Objects.equals(tenant(), source.getTenantId()) || !"VIEW".equals(source.getAction())) throw denied(); WorkflowFileGrant grant = authorize(source); if (!Objects.equals(source.getRecordId(), grant.getContext().getRecordId()) || !same(source.getTaskId(), grant.getContext().getTaskId())) throw denied(); return grant; } private WorkflowFileGrant authorizeCurrent(WorkflowFileRequest request, String action) { if (request == null) throw denied(); String user = UserProvider.getLoginUserId(); if (user == null || user.isBlank()) throw denied(); WorkflowFileRequest copy = JsonUtil.getJsonToBean(JsonUtil.getObjectToString(request), WorkflowFileRequest.class); copy.setOwnerId(user); copy.setTenantId(tenant()); copy.setAction(action); return authorize(copy); } private WorkflowFileGrant authorize(WorkflowFileRequest source) { ActionResult response = access.authorize(source); if (response == null || !ActionResultCode.SUCCESS.getCode().equals(response.getCode()) || response.getData() == null) throw denied(); WorkflowFileGrant grant = response.getData(); WorkflowFileRequest actual = grant.getContext(); if (actual == null || grant.getFile() == null || !Objects.equals(source.getFileId(), grant.getFile().get("fileId")) || !Objects.equals(source.getFileId(), actual.getFileId()) || !Objects.equals(source.getTenantId(), actual.getTenantId()) || !Objects.equals(source.getOwnerId(), actual.getOwnerId()) || !Objects.equals(source.getModelId(), actual.getModelId()) || !Objects.equals(source.getField(), actual.getField()) || !Objects.equals(source.getAction(), actual.getAction()) || !same(source.getParentField(), actual.getParentField()) || !same(source.getRowId(), actual.getRowId()) || !same(source.getContentToken(), actual.getContentToken()) || !same(source.getDraftId(), actual.getDraftId()) || !same(source.getLeaseId(), actual.getLeaseId()) || source.getTaskId() != null && !Objects.equals(source.getTaskId(), actual.getTaskId())) throw denied(); return grant; } private static String tenant() { String value = UserProvider.getUser().getTenantId(); return value == null || value.isBlank() ? "0" : value; } private static boolean same(String a, String b) { return Objects.toString(a, "").equals(Objects.toString(b, "")); } private static DataException denied() { return new DataException("文件访问上下文无效、已过期或当前权限不足"); } }