package cn.iocoder.yudao.module.qcreport.service.aiimport.document; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import java.util.List; import java.util.Set; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.qcreport.enums.ErrorCodeConstants.AI_IMPORT_FILE_EMPTY; /** * 图片(照片 / 截图)抽取器。 *

* 三种适配器里唯一「什么都不用做」的一个:字节原样交给多模态模型,不解码、不缩放、不转码。 * 刻意不在这里用 {@code ImageIO} 读一遍——那不仅多一次内存拷贝,还会在遇到 webp 这类 * JDK 原生不支持的格式时白白失败,而模型那边其实完全能认。 *

* 排在第一位:它只认图片扩展名,与另外两个的匹配集不相交,放哪都不会引起歧义, * 但放在最前面能让「一张照片」这条最常见的路径以最短的路走到头。 */ @Component @Order(1) public class ImageImportAdapter implements QcReportImportAdapter { private static final Set EXTENSIONS = Set.of("png", "jpg", "jpeg", "bmp", "gif", "webp"); @Override public boolean supports(String extension, String contentType) { return EXTENSIONS.contains(extension); } @Override public QcReportDocumentExtract extract(byte[] content, String originalFilename) { if (content == null || content.length == 0) { throw exception(AI_IMPORT_FILE_EMPTY, originalFilename, 1); } String mimeType = mimeTypeOf(extensionOf(originalFilename)); return QcReportDocumentExtract.ofImages( List.of(new QcReportDocumentExtract.ImagePart(content, mimeType, "第 1 页")), 1, "raw-image", List.of()); } /** * PDF 适配器需要同一套映射来标注渲染出来的 PNG,所以公开出来复用。 */ static String mimeTypeOf(String extension) { return switch (extension) { case "jpg", "jpeg" -> "image/jpeg"; case "gif" -> "image/gif"; case "bmp" -> "image/bmp"; case "webp" -> "image/webp"; default -> "image/png"; }; } private static String extensionOf(String filename) { int dot = filename == null ? -1 : filename.lastIndexOf('.'); return dot < 0 ? "" : filename.substring(dot + 1).toLowerCase(); } }