From 57d53196a75ecfb03ea2e97f2f781ad5509a26f5 Mon Sep 17 00:00:00 2001 From: zhaokai Date: Wed, 11 Mar 2026 16:27:19 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=8F=8F=E8=BF=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../query/archives/ArchivesQueryExe.java | 9 +- .../edu/domain/utils/FixedPictureUtils.java | 198 ++++++++++++++++++ 2 files changed, 203 insertions(+), 4 deletions(-) create mode 100644 web-domain/src/main/java/com/zcloud/edu/domain/utils/FixedPictureUtils.java diff --git a/web-app/src/main/java/com/zcloud/edu/command/query/archives/ArchivesQueryExe.java b/web-app/src/main/java/com/zcloud/edu/command/query/archives/ArchivesQueryExe.java index 2e905c3..7226c89 100644 --- a/web-app/src/main/java/com/zcloud/edu/command/query/archives/ArchivesQueryExe.java +++ b/web-app/src/main/java/com/zcloud/edu/command/query/archives/ArchivesQueryExe.java @@ -20,6 +20,7 @@ import com.zcloud.edu.domain.model.archives.ArchivesPdfFileE; import com.zcloud.edu.domain.model.archives.ClassArchivesE; import com.zcloud.edu.domain.model.archives.PersonArchivesE; import com.zcloud.edu.domain.model.study.*; +import com.zcloud.edu.domain.utils.FixedPictureUtils; import com.zcloud.edu.domain.utils.ImageUtil; import com.zcloud.edu.dto.archives.ArchivesPdfFilePageQry; import com.zcloud.edu.dto.archives.ArchivesQry; @@ -918,18 +919,18 @@ public class ArchivesQueryExe { Map itemMap = PropertyUtils.describe(item); if(!ObjectUtils.isEmpty(item.getSignFaceUrl())){ // PictureRenderData signPicture = Pictures.ofUrl(fileUrlConfig.getPrefixUrl() + item.getSignFaceUrl(), PictureType.JPEG).size(50, 50).create();//网络图片地址 - PictureRenderData signPicture = ImageUtil.createWithThumbnailator(fileUrlConfig.getPrefixUrl() + item.getSignFaceUrl(), 50, 40); + PictureRenderData signPicture = ImageUtil.createWithThumbnailator(fileUrlConfig.getPrefixUrl() + item.getSignFaceUrl(), 50, 50); itemMap.put("signPicture", signPicture); } if(!ObjectUtils.isEmpty(item.getExamFaceUrl())){ // PictureRenderData examPicture = Pictures.ofUrl(fileUrlConfig.getPrefixUrl() + item.getExamFaceUrl(), PictureType.JPEG).size(50, 50).create();//网络图片地址 - PictureRenderData examPicture = ImageUtil.createWithThumbnailator(fileUrlConfig.getPrefixUrl() + item.getExamFaceUrl(), 40, 60); - +// PictureRenderData examPicture = ImageUtil.createWithThumbnailator(fileUrlConfig.getPrefixUrl() + item.getExamFaceUrl(), 50, 50); + PictureRenderData examPicture = FixedPictureUtils.ofNormalUrl(fileUrlConfig.getPrefixUrl() + item.getExamFaceUrl(), 50, 50); itemMap.put("examPicture", examPicture); } if(!ObjectUtils.isEmpty(item.getUserAvatarUrl())){ // PictureRenderData facePicture = Pictures.ofUrl(fileUrlConfig.getPrefixUrl() + item.getUserAvatarUrl(), PictureType.JPEG).size(50, 50).style("rotation:90;").create();//网络图片地址 - PictureRenderData facePicture = ImageUtil.createWithThumbnailator(fileUrlConfig.getPrefixUrl() + item.getUserAvatarUrl(), 40, 60); + PictureRenderData facePicture = ImageUtil.createWithThumbnailator(fileUrlConfig.getPrefixUrl() + item.getUserAvatarUrl(), 50, 50); itemMap.put("facePicture", facePicture); } diff --git a/web-domain/src/main/java/com/zcloud/edu/domain/utils/FixedPictureUtils.java b/web-domain/src/main/java/com/zcloud/edu/domain/utils/FixedPictureUtils.java new file mode 100644 index 0000000..7ee128a --- /dev/null +++ b/web-domain/src/main/java/com/zcloud/edu/domain/utils/FixedPictureUtils.java @@ -0,0 +1,198 @@ +package com.zcloud.edu.domain.utils; + +import com.deepoove.poi.data.PictureRenderData; +import com.deepoove.poi.data.Pictures; + +import javax.imageio.ImageIO; +import javax.imageio.ImageReader; +import javax.imageio.metadata.IIOMetadata; +import javax.imageio.metadata.IIOMetadataNode; +import javax.imageio.stream.ImageInputStream; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Iterator; + +public class FixedPictureUtils { + /** + * 替代Pictures.ofUrl(),返回正常方向的PictureRenderData + * @param imgUrl 图片URL + * @param width 插入宽度(px) + * @param height 插入高度(px) + * @return 正常显示的PictureRenderData + * @throws IOException IO异常 + */ + public static PictureRenderData ofNormalUrl(String imgUrl, int width, int height) throws IOException { + // 1. 从URL读取图片字节(纯JDK8原生) + byte[] imgBytes = readBytesFromUrl(imgUrl); + // 2. 修复图片旋转(核心:移除containsTree()依赖) + byte[] fixedImgBytes = fixImageRotationForJdk8(imgBytes); + // 3. 构建PictureRenderData(使用ofBytes方法) + return Pictures.ofBytes(fixedImgBytes) + .size(width, height) + .create(); + } + + /** + * JDK8原生:从URL读取字节数组(无第三方依赖) + */ + private static byte[] readBytesFromUrl(String url) throws IOException { + URL picUrl = new URL(url); + try (InputStream is = picUrl.openStream(); + ByteArrayOutputStream baos = new ByteArrayOutputStream()) { + byte[] buffer = new byte[1024]; + int len; + while ((len = is.read(buffer)) != -1) { + baos.write(buffer, 0, len); + } + return baos.toByteArray(); + } + } + + /** + * 核心:修复图片EXIF旋转(移除containsTree()依赖) + */ + private static byte[] fixImageRotationForJdk8(byte[] imageBytes) throws IOException { + try (ByteArrayInputStream bais = new ByteArrayInputStream(imageBytes); + ImageInputStream iis = ImageIO.createImageInputStream(bais)) { + + // 获取图片读取器 + Iterator readers = ImageIO.getImageReaders(iis); + if (!readers.hasNext()) { + return imageBytes; // 无读取器,返回原字节 + } + + ImageReader reader = readers.next(); + reader.setInput(iis, true); + + // 1. 获取旋转角度(绕开containsTree()) + int rotation = getExifRotationWithoutContainsTree(reader); + if (rotation == 0) { + reader.dispose(); + return imageBytes; // 无需旋转,直接返回 + } + + // 2. 读取原始图片 + BufferedImage original = reader.read(0); + reader.dispose(); + + // 3. 旋转图片 + BufferedImage rotated = rotateImageForJdk8(original, rotation); + + // 4. 转为字节返回 + try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) { + String format = getImageFormatForJdk8(imageBytes); + ImageIO.write(rotated, format, baos); + return baos.toByteArray(); + } + } + } + + /** + * 关键修正:绕开containsTree(),解析EXIF旋转角度 + */ + private static int getExifRotationWithoutContainsTree(ImageReader reader) { + try { + IIOMetadata metadata = reader.getImageMetadata(0); + String exifFormat = "javax_imageio_jpeg_image_1.0"; + + // 直接尝试获取元数据树,不先判断containsTree() + // 捕获异常即可,无需提前检查 + Object metadataTree; + try { + metadataTree = metadata.getAsTree(exifFormat); + } catch (IllegalArgumentException e) { + // 不支持该格式,返回0度 + return 0; + } + + if (!(metadataTree instanceof IIOMetadataNode)) { + return 0; + } + + IIOMetadataNode root = (IIOMetadataNode) metadataTree; + IIOMetadataNode exifNode = getChildNodeForJdk8(root, "app0JFIF"); + if (exifNode == null) { + exifNode = getChildNodeForJdk8(root, "exif"); + } + + if (exifNode == null) return 0; + + // 解析Orientation参数 + String orientation = exifNode.getAttribute("Orientation"); + if (orientation == null || orientation.trim().isEmpty()) return 0; + + int ori = Integer.parseInt(orientation); + switch (ori) { + case 3: return 180; + case 6: return 90; + case 8: return 270; + default: return 0; + } + } catch (Exception e) { + // 任何异常都返回0度(比如元数据解析失败) + return 0; + } + } + + /** + * JDK8适配:获取XML子节点 + */ + private static IIOMetadataNode getChildNodeForJdk8(IIOMetadataNode parent, String name) { + for (int i = 0; i < parent.getLength(); i++) { + Object item = parent.item(i); + if (item instanceof IIOMetadataNode) { + IIOMetadataNode node = (IIOMetadataNode) item; + if (name.equals(node.getNodeName())) { + return node; + } + } + } + return null; + } + + /** + * JDK8适配:旋转图片(解决JDK8 Graphics2D兼容问题) + */ + private static BufferedImage rotateImageForJdk8(BufferedImage img, int angle) { + int width = img.getWidth(); + int height = img.getHeight(); + + // 计算旋转后的画布尺寸 + int newWidth = (angle % 180 == 0) ? width : height; + int newHeight = (angle % 180 == 0) ? height : width; + + // JDK8兼容:创建BufferedImage(避免透明通道问题) + BufferedImage rotated = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_RGB); + Graphics2D g2d = rotated.createGraphics(); + + // 抗锯齿,避免旋转后模糊(JDK8必须) + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); + + // 旋转图片 + g2d.rotate(Math.toRadians(angle), newWidth / 2.0, newHeight / 2.0); + g2d.drawImage(img, (newWidth - width) / 2, (newHeight - height) / 2, null); + g2d.dispose(); + + return rotated; + } + + /** + * JDK8适配:识别图片格式(jpg/png) + */ + private static String getImageFormatForJdk8(byte[] imageBytes) { + if (imageBytes.length < 4) return "jpg"; + // JDK8字节判断,兼容所有系统 + if (imageBytes[0] == (byte)0xFF && imageBytes[1] == (byte)0xD8) { + return "jpg"; + } else if (imageBytes[0] == (byte)0x89 && imageBytes[1] == (byte)0x50) { + return "png"; + } + return "jpg"; + } +}