package jnpf.dmsService.search.file; import feign.Response; import jnpf.dmsEntity.permission.entity.DmsFileVersionEntity; import jnpf.dmsEntity.preview.model.DmsFilePreviewTicket; import jnpf.dmsEntity.storage.entity.DmsFileObjectEntity; import jnpf.dmsMapper.DmsFileObjectMapper; import jnpf.dmsMapper.DmsFileVersionMapper; import jnpf.dmsService.DmsFilePreviewTicketIssuer; import jnpf.dmsService.file.DmsFileContentApi; import jnpf.dmsService.search.DmsSearchIndexTask; import jnpf.dmsService.search.DmsSearchProperties; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.file.Files; import java.nio.file.Path; import java.util.Locale; @Service @RequiredArgsConstructor public class DmsSearchFileSourceService { private static final long TICKET_TTL_SECONDS = 300L; private final DmsFileVersionMapper fileVersionMapper; private final DmsFileObjectMapper fileObjectMapper; private final DmsFilePreviewTicketIssuer ticketIssuer; private final DmsFileContentApi fileApi; private final DmsSearchProperties properties; public DmsTemporarySearchFile fetch(DmsSearchIndexTask task) throws IOException { DmsFileVersionEntity version = fileVersionMapper.selectByTenantAndId( task.getTenantId(), task.getFileVersionId()); DmsFileObjectEntity object = fileObjectMapper.selectByTenantAndFileVersionId( task.getTenantId(), task.getFileVersionId()); if (version == null || object == null) { throw new IOException("文件版本或存储对象不存在"); } String ticket = ticketIssuer.issue(ticket(task, version, object), TICKET_TTL_SECONDS); Response response = fileApi.stream(version.getId(), ticket); if (response == null || response.status() < 200 || response.status() >= 300 || response.body() == null) { if (response != null) { response.close(); } throw new IOException("文件服务读取失败"); } Path target = Files.createTempFile("dms-search-", suffix(version.getFileName())); try { copy(response, target); return new DmsTemporarySearchFile(target, version.getFileName(), version.getMimeType()); } catch (IOException exception) { Files.deleteIfExists(target); throw exception; } } private void copy(Response response, Path target) throws IOException { long copied = 0; byte[] buffer = new byte[64 * 1024]; try (Response ignored = response; InputStream input = response.body().asInputStream(); OutputStream output = Files.newOutputStream(target)) { int read; while ((read = input.read(buffer)) >= 0) { copied += read; if (copied > properties.getMaxFileBytes()) { throw new IOException("文件超过全文检索解析大小限制"); } output.write(buffer, 0, read); } } } private DmsFilePreviewTicket ticket(DmsSearchIndexTask task, DmsFileVersionEntity version, DmsFileObjectEntity object) { DmsFilePreviewTicket value = new DmsFilePreviewTicket(); value.setScope(DmsFilePreviewTicket.SCOPE); value.setTenantId(task.getTenantId()); value.setUserId("dms-search-indexer"); value.setFileVersionId(version.getId()); value.setFileName(version.getFileName()); value.setMimeType(version.getMimeType()); value.setSizeBytes(version.getSizeBytes()); value.setStoragePlatform(object.getStoragePlatform()); value.setBucketName(object.getBucketName()); value.setBasePath(object.getBasePath()); value.setObjectPath(object.getObjectPath()); value.setObjectName(object.getObjectName()); value.setExpiresAt(System.currentTimeMillis() + TICKET_TTL_SECONDS * 1000L); return value; } private String suffix(String fileName) { int separator = fileName == null ? -1 : fileName.lastIndexOf('.'); if (separator < 0) { return ".bin"; } String value = fileName.substring(separator).toLowerCase(Locale.ROOT); return value.matches("\\.[a-z0-9]{1,10}") ? value : ".bin"; } }