package jnpf.dmsService; import jnpf.base.UserInfo; import jnpf.dmsEntity.download.form.DmsFileDownloadTicketRequest; import jnpf.dmsEntity.download.model.DmsFileDownloadTicket; import jnpf.dmsEntity.download.vo.DmsFileDownloadTicketVO; import jnpf.dmsEntity.permission.entity.DmsFileVersionEntity; import jnpf.dmsEntity.permission.enums.DmsPermissionAction; import jnpf.dmsEntity.permission.enums.DmsResourceType; import jnpf.dmsEntity.storage.entity.DmsFileObjectEntity; import jnpf.dmsMapper.DmsFileObjectMapper; import jnpf.dmsMapper.DmsFileVersionMapper; import jnpf.dmsPermission.DmsCurrentIdentity; import jnpf.dmsPermission.DmsPermissionError; import jnpf.dmsPermission.DmsPermissionException; import jnpf.dmsPermission.DmsPermissionExecutionContext; import jnpf.dmsPermission.DmsPermissionGuard; import jnpf.dmsPermission.DmsPermissionOperation; import jnpf.dmsService.pdf.DmsPersistentPdfSources; import jnpf.dmsService.pdf.DmsPdfGenerationTaskService; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import java.util.Date; import java.util.UUID; @Service @RequiredArgsConstructor public class DmsFileDownloadService { static final long TICKET_TTL_SECONDS = 5L * 60L; private final DmsCurrentIdentity currentIdentity; private final DmsPermissionGuard permissionGuard; private final DmsFileVersionMapper fileVersionMapper; private final DmsFileObjectMapper fileObjectMapper; private final DmsFileDownloadTicketIssuer ticketIssuer; private final DmsFileOperationLogService operationLogService; private final DmsWatermarkPreviewResolver watermarkResolver; private final DmsPdfGenerationTaskService pdfGenerationTaskService; public DmsFileDownloadTicketVO createTicket(DmsFileDownloadTicketRequest request) { request.normalized(); UserInfo user = currentIdentity.requireUser(); String tenantId = currentIdentity.tenantId(user); UUID fileVersionId = resolveFileVersionId(request, tenantId); long expiresAtMillis = System.currentTimeMillis() + TICKET_TTL_SECONDS * 1000L; DmsPermissionExecutionContext context = new DmsPermissionExecutionContext(); context.setResourceType(DmsResourceType.FILE_VERSION); context.setResourceId(fileVersionId); context.setActionCode(DmsPermissionAction.DOWNLOAD); context.setWorkflowDownloadOnly(request.isWorkflowDownloadOnly()); context.setOperationId(request.getOperationId()); context.setSessionExpiresAt(new Date(expiresAtMillis)); DownloadSource source = permissionGuard.execute(context, new DmsPermissionOperation() { @Override public DownloadSource execute() { return loadDownloadSource(tenantId, fileVersionId); } @Override public DownloadSource replay() { return loadDownloadSource(tenantId, fileVersionId); } }); if (context.getSessionExpiresAt() != null) { expiresAtMillis = Math.min(expiresAtMillis, context.getSessionExpiresAt().getTime()); } DmsFileDownloadTicket payload = payload(user, tenantId, source, expiresAtMillis, request); String accessTicket = ticketIssuer.issue(payload, TICKET_TTL_SECONDS); DmsFileDownloadTicketVO result = new DmsFileDownloadTicketVO(); result.setFileVersionId(fileVersionId); result.setFileName(source.version.getFileName()); result.setMimeType(source.version.getMimeType()); result.setSizeBytes(source.version.getSizeBytes()); result.setAccessTicket(accessTicket); result.setExpiresAt(new Date(expiresAtMillis)); operationLogService.recordDownload(user, tenantId, source.version.getDocumentId(), source.version.getId(), source.version.getFileName(), request.getOperationId()); return result; } private UUID resolveFileVersionId(DmsFileDownloadTicketRequest request, String tenantId) { if (request.getResourceType() == DmsResourceType.DOCUMENT) { DmsFileVersionEntity current = fileVersionMapper.selectCurrentEffective(tenantId, request.getResourceId()); if (current == null) { throw DmsPermissionException.of(DmsPermissionError.RESOURCE_NOT_FOUND, "文件没有现行有效版本"); } return current.getId(); } if (request.getResourceType() == DmsResourceType.FILE_VERSION) { DmsFileVersionEntity version = fileVersionMapper.selectReadableByTenantAndId(tenantId, request.getResourceId()); if (version == null) { throw DmsPermissionException.of(DmsPermissionError.RESOURCE_NOT_FOUND, "可下载的文件版本不存在"); } return version.getId(); } throw DmsPermissionException.of(DmsPermissionError.INVALID_REQUEST, "仅支持下载 DOCUMENT 或 FILE_VERSION"); } private DownloadSource loadDownloadSource(String tenantId, UUID fileVersionId) { DmsFileVersionEntity version = fileVersionMapper.selectReadableByTenantAndId( tenantId, fileVersionId); DmsFileObjectEntity object = fileObjectMapper.selectByTenantAndFileVersionId(tenantId, fileVersionId); if (version == null || object == null) { throw DmsPermissionException.of(DmsPermissionError.RESOURCE_NOT_FOUND, "文件版本或存储对象不存在"); } return new DownloadSource(version, object); } private DmsFileDownloadTicket payload(UserInfo user, String tenantId, DownloadSource source, long expiresAtMillis, DmsFileDownloadTicketRequest request) { DmsFileDownloadTicket payload = new DmsFileDownloadTicket(); payload.setScope(DmsFileDownloadTicket.SCOPE); payload.setTenantId(tenantId); payload.setUserId(user.getUserId().trim()); payload.setFileVersionId(source.version.getId()); payload.setHistoricalAccess(source.version.getLifecycleStatus() != null && !"EFFECTIVE".equals(source.version.getLifecycleStatus())); payload.setFileName(source.version.getFileName()); payload.setMimeType(source.version.getMimeType()); payload.setSizeBytes(source.version.getSizeBytes()); payload.setStoragePlatform(source.object.getStoragePlatform()); payload.setBucketName(source.object.getBucketName()); payload.setBasePath(source.object.getBasePath()); payload.setObjectPath(source.object.getObjectPath()); payload.setObjectName(source.object.getObjectName()); payload.setPdfSource(DmsPersistentPdfSources.valid(source.version, source.object)); if (payload.getPdfSource() == null) { pdfGenerationTaskService.enqueueMissingEligible(tenantId, source.version.getId(), source.version.getFileName(), source.version.getChecksumSha256()); } payload.setWatermarks(watermarkResolver.resolveDownload(user, tenantId, source.version, request)); payload.setExpiresAt(expiresAtMillis); return payload; } private static final class DownloadSource { private final DmsFileVersionEntity version; private final DmsFileObjectEntity object; private DownloadSource(DmsFileVersionEntity version, DmsFileObjectEntity object) { this.version = version; this.object = object; } } }