package jnpf.dmsService; import jnpf.base.UserInfo; import jnpf.dmsEntity.permission.entity.DmsFileVersionEntity; import jnpf.dmsEntity.permission.enums.DmsPermissionAction; import jnpf.dmsEntity.permission.enums.DmsResourceType; import jnpf.dmsEntity.preview.form.DmsFilePreviewTicketRequest; import jnpf.dmsEntity.preview.model.DmsFilePreviewTicket; import jnpf.dmsEntity.preview.vo.DmsFilePreviewTicketVO; import jnpf.dmsEntity.storage.entity.DmsFileObjectEntity; import jnpf.dmsMapper.DmsFileObjectMapper; import jnpf.dmsMapper.DmsFileVersionMapper; import jnpf.dmsPermission.DmsCurrentIdentity; import jnpf.dmsPermission.DmsFileControlPolicy; 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 DmsFilePreviewService { 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 DmsFilePreviewTicketIssuer ticketIssuer; private final DmsFileOperationLogService operationLogService; private final DmsWatermarkPreviewResolver watermarkPreviewResolver; private final DmsPreviewPreparationClient previewPreparationClient; private final DmsPdfGenerationTaskService pdfGenerationTaskService; public DmsFilePreviewTicketVO createTicket(DmsFilePreviewTicketRequest request) { request.normalized(); UserInfo user = currentIdentity.requireUser(); String tenantId = currentIdentity.tenantId(user); DmsFileVersionEntity resolvedVersion = resolveFileVersion(request, tenantId); UUID fileVersionId = resolvedVersion.getId(); long expiresAtMillis = System.currentTimeMillis() + TICKET_TTL_SECONDS * 1000L; PreviewSource source = DmsFileControlPolicy.requiresPermissionCheck( resolvedVersion.getShifouShoukong()) ? loadPreviewSourceWithPermission(request, tenantId, fileVersionId, expiresAtMillis) : loadPreviewSource(tenantId, fileVersionId); DmsFilePreviewTicket payload = previewPreparationClient.prepare( payload(user, tenantId, source, expiresAtMillis, request)); String accessTicket = ticketIssuer.issue(payload, TICKET_TTL_SECONDS); DmsFilePreviewTicketVO result = new DmsFilePreviewTicketVO(); result.setFileVersionId(fileVersionId); result.setFileName(payload.getFileName()); result.setMimeType(payload.getMimeType()); result.setSizeBytes(payload.getSizeBytes()); result.setAccessTicket(accessTicket); result.setExpiresAt(new Date(expiresAtMillis)); operationLogService.recordView(user, tenantId, source.version.getDocumentId(), source.version.getId(), source.version.getFileName(), request.getOperationId()); return result; } private DmsFileVersionEntity resolveFileVersion(DmsFilePreviewTicketRequest 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; } 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; } throw DmsPermissionException.of(DmsPermissionError.INVALID_REQUEST, "仅支持预览 DOCUMENT 或 FILE_VERSION"); } private PreviewSource loadPreviewSourceWithPermission(DmsFilePreviewTicketRequest request, String tenantId, UUID fileVersionId, long expiresAtMillis) { DmsPermissionExecutionContext context = new DmsPermissionExecutionContext(); context.setResourceType(DmsResourceType.FILE_VERSION); context.setResourceId(fileVersionId); context.setActionCode(DmsPermissionAction.READ); context.setOperationId(request.getOperationId()); context.setSessionExpiresAt(new Date(expiresAtMillis)); return permissionGuard.execute(context, new DmsPermissionOperation() { @Override public PreviewSource execute() { return loadPreviewSource(tenantId, fileVersionId); } @Override public PreviewSource replay() { return loadPreviewSource(tenantId, fileVersionId); } }); } private PreviewSource loadPreviewSource(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 PreviewSource(version, object); } private DmsFilePreviewTicket payload(UserInfo user, String tenantId, PreviewSource source, long expiresAtMillis, DmsFilePreviewTicketRequest request) { DmsFilePreviewTicket payload = new DmsFilePreviewTicket(); payload.setScope(DmsFilePreviewTicket.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.setPreviewFormat(request.getPreviewFormat()); if ("download".equals(request.getWatermarkType())) { payload.setWatermarks(watermarkPreviewResolver.resolveDownload(user, tenantId, source.version, request)); } else if ("print".equals(request.getWatermarkType())) { payload.setWatermarks(watermarkPreviewResolver.resolvePrint(user, tenantId, source.version, request)); } else { payload.setWatermarks(watermarkPreviewResolver.resolve(user, tenantId, source.version, request)); } payload.setExpiresAt(expiresAtMillis); return payload; } private static final class PreviewSource { private final DmsFileVersionEntity version; private final DmsFileObjectEntity object; private PreviewSource(DmsFileVersionEntity version, DmsFileObjectEntity object) { this.version = version; this.object = object; } } }