diff --git a/src/main/java/com/ycwl/basic/image/pipeline/core/PhotoProcessContext.java b/src/main/java/com/ycwl/basic/image/pipeline/core/PhotoProcessContext.java index e19f9d4d..1550916c 100644 --- a/src/main/java/com/ycwl/basic/image/pipeline/core/PhotoProcessContext.java +++ b/src/main/java/com/ycwl/basic/image/pipeline/core/PhotoProcessContext.java @@ -92,6 +92,13 @@ public class PhotoProcessContext { private File processedFile; private boolean isLandscape = true; private boolean needRotation = false; + + /** + * 图像需要旋转的角度(用于后续Stage使用) + * 由 ImageOrientationStage 从 Crop.rotation 中提取并设置 + */ + private Integer imageRotation; + private String resultUrl; private IStorageAdapter storageAdapter; private WatermarkInfo watermarkInfo; diff --git a/src/main/java/com/ycwl/basic/image/pipeline/stages/ConditionalRotateStage.java b/src/main/java/com/ycwl/basic/image/pipeline/stages/ConditionalRotateStage.java index e4281639..30f07ba4 100644 --- a/src/main/java/com/ycwl/basic/image/pipeline/stages/ConditionalRotateStage.java +++ b/src/main/java/com/ycwl/basic/image/pipeline/stages/ConditionalRotateStage.java @@ -13,13 +13,13 @@ import java.io.File; /** * 条件旋转Stage - * 如果是竖图,旋转90度变成横图(便于后续水印处理) + * 根据图片需要旋转的角度进行旋转(便于后续水印处理) */ @Slf4j @StageConfig( stageId = "rotate", optionalMode = StageOptionalMode.UNSUPPORT, - description = "竖图旋转90度" + description = "根据需要旋转图片" ) public class ConditionalRotateStage extends AbstractPipelineStage { @@ -43,11 +43,18 @@ public class ConditionalRotateStage extends AbstractPipelineStage {}", currentFile.getName(), rotatedFile.getName()); - ImageUtils.rotateImage90(currentFile, rotatedFile); + // 根据实际角度进行旋转 + log.debug("旋转图片{}度: {} -> {}", rotation, currentFile.getName(), rotatedFile.getName()); + rotateByDegree(currentFile, rotatedFile, rotation); if (!rotatedFile.exists()) { return StageResult.failed("旋转后的文件未生成"); @@ -57,12 +64,31 @@ public class ConditionalRotateStage extends AbstractPipelineStage { + @Override + protected StageResult doExecute(PhotoProcessContext context) { + return StageResult.skipped("无操作"); + } + + @Override + public String getName() { + return "NoOpStage"; + } +} diff --git a/src/main/java/com/ycwl/basic/task/DeviceVideoContinuityCheckTask.java b/src/main/java/com/ycwl/basic/task/DeviceVideoContinuityCheckTask.java index 60847411..d45d8f2b 100644 --- a/src/main/java/com/ycwl/basic/task/DeviceVideoContinuityCheckTask.java +++ b/src/main/java/com/ycwl/basic/task/DeviceVideoContinuityCheckTask.java @@ -36,7 +36,7 @@ import java.util.concurrent.TimeUnit; @Slf4j @Component @EnableScheduling -@Profile("prod") +//@Profile("prod") public class DeviceVideoContinuityCheckTask { private static final String REDIS_KEY_PREFIX = "device:video:continuity:"; diff --git a/src/test/java/com/ycwl/basic/image/pipeline/stages/ConditionalRotateStageTest.java b/src/test/java/com/ycwl/basic/image/pipeline/stages/ConditionalRotateStageTest.java index d62fe892..04f3f0d3 100644 --- a/src/test/java/com/ycwl/basic/image/pipeline/stages/ConditionalRotateStageTest.java +++ b/src/test/java/com/ycwl/basic/image/pipeline/stages/ConditionalRotateStageTest.java @@ -4,6 +4,12 @@ import com.ycwl.basic.image.pipeline.core.PhotoProcessContext; import com.ycwl.basic.image.pipeline.core.StageResult; import com.ycwl.basic.image.pipeline.enums.ImageType; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.File; +import java.nio.file.Path; import static org.junit.jupiter.api.Assertions.*; @@ -81,4 +87,174 @@ class ConditionalRotateStageTest { assertTrue(result.isFailed()); assertEquals("当前文件不存在", result.getMessage()); } + + /** + * 测试旋转90度 + */ + @Test + void testExecute_Rotate90(@TempDir Path tempDir) throws Exception { + ConditionalRotateStage stage = new ConditionalRotateStage(); + + File testFile = createTestImage(tempDir, "test.jpg", 1080, 1920); + + PhotoProcessContext context = PhotoProcessContext.builder() + .processId("test-rotate-90") + .originalUrl("https://example.com/test.jpg") + .scenicId(123L) + .imageType(ImageType.NORMAL_PHOTO) + .build(); + + context.setOriginalFile(testFile); + context.setLandscape(false); + context.setImageRotation(90); + + StageResult result = stage.execute(context); + + assertTrue(result.isSuccess()); + assertEquals("已旋转90度", result.getMessage()); + assertTrue(context.isNeedRotation()); + assertNotNull(context.getCurrentFile()); + } + + /** + * 测试旋转180度 + */ + @Test + void testExecute_Rotate180(@TempDir Path tempDir) throws Exception { + ConditionalRotateStage stage = new ConditionalRotateStage(); + + File testFile = createTestImage(tempDir, "test.jpg", 1080, 1920); + + PhotoProcessContext context = PhotoProcessContext.builder() + .processId("test-rotate-180") + .originalUrl("https://example.com/test.jpg") + .scenicId(123L) + .imageType(ImageType.NORMAL_PHOTO) + .build(); + + context.setOriginalFile(testFile); + context.setLandscape(false); + context.setImageRotation(180); + + StageResult result = stage.execute(context); + + assertTrue(result.isSuccess()); + assertEquals("已旋转180度", result.getMessage()); + assertTrue(context.isNeedRotation()); + } + + /** + * 测试旋转270度 + */ + @Test + void testExecute_Rotate270(@TempDir Path tempDir) throws Exception { + ConditionalRotateStage stage = new ConditionalRotateStage(); + + File testFile = createTestImage(tempDir, "test.jpg", 1080, 1920); + + PhotoProcessContext context = PhotoProcessContext.builder() + .processId("test-rotate-270") + .originalUrl("https://example.com/test.jpg") + .scenicId(123L) + .imageType(ImageType.NORMAL_PHOTO) + .build(); + + context.setOriginalFile(testFile); + context.setLandscape(false); + context.setImageRotation(270); + + StageResult result = stage.execute(context); + + assertTrue(result.isSuccess()); + assertEquals("已旋转270度", result.getMessage()); + assertTrue(context.isNeedRotation()); + } + + /** + * 测试无需旋转(rotation=0) + */ + @Test + void testExecute_NoRotationNeeded(@TempDir Path tempDir) throws Exception { + ConditionalRotateStage stage = new ConditionalRotateStage(); + + File testFile = createTestImage(tempDir, "test.jpg", 1080, 1920); + + PhotoProcessContext context = PhotoProcessContext.builder() + .processId("test-no-rotation") + .originalUrl("https://example.com/test.jpg") + .scenicId(123L) + .imageType(ImageType.NORMAL_PHOTO) + .build(); + + context.setOriginalFile(testFile); + context.setLandscape(false); + context.setImageRotation(0); + + StageResult result = stage.execute(context); + + assertTrue(result.isSkipped()); + assertEquals("无需旋转", result.getMessage()); + } + + /** + * 测试无需旋转(rotation=null) + */ + @Test + void testExecute_NullRotation(@TempDir Path tempDir) throws Exception { + ConditionalRotateStage stage = new ConditionalRotateStage(); + + File testFile = createTestImage(tempDir, "test.jpg", 1080, 1920); + + PhotoProcessContext context = PhotoProcessContext.builder() + .processId("test-null-rotation") + .originalUrl("https://example.com/test.jpg") + .scenicId(123L) + .imageType(ImageType.NORMAL_PHOTO) + .build(); + + context.setOriginalFile(testFile); + context.setLandscape(false); + context.setImageRotation(null); + + StageResult result = stage.execute(context); + + assertTrue(result.isSkipped()); + assertEquals("无需旋转", result.getMessage()); + } + + /** + * 测试不支持的旋转角度 + */ + @Test + void testExecute_UnsupportedRotationAngle(@TempDir Path tempDir) throws Exception { + ConditionalRotateStage stage = new ConditionalRotateStage(); + + File testFile = createTestImage(tempDir, "test.jpg", 1080, 1920); + + PhotoProcessContext context = PhotoProcessContext.builder() + .processId("test-unsupported-rotation") + .originalUrl("https://example.com/test.jpg") + .scenicId(123L) + .imageType(ImageType.NORMAL_PHOTO) + .build(); + + context.setOriginalFile(testFile); + context.setLandscape(false); + context.setImageRotation(45); + + StageResult result = stage.execute(context); + + assertTrue(result.isFailed()); + assertTrue(result.getMessage().contains("不支持的旋转角度")); + } + + /** + * 创建测试用的图片文件 + */ + private File createTestImage(Path tempDir, String filename, int width, int height) throws Exception { + BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + File file = tempDir.resolve(filename).toFile(); + ImageIO.write(image, "jpg", file); + return file; + } } diff --git a/src/test/java/com/ycwl/basic/image/pipeline/stages/ImageOrientationStageTest.java b/src/test/java/com/ycwl/basic/image/pipeline/stages/ImageOrientationStageTest.java index 57e751e3..334dffb8 100644 --- a/src/test/java/com/ycwl/basic/image/pipeline/stages/ImageOrientationStageTest.java +++ b/src/test/java/com/ycwl/basic/image/pipeline/stages/ImageOrientationStageTest.java @@ -3,7 +3,15 @@ package com.ycwl.basic.image.pipeline.stages; import com.ycwl.basic.image.pipeline.core.PhotoProcessContext; import com.ycwl.basic.image.pipeline.core.StageResult; import com.ycwl.basic.image.pipeline.enums.ImageType; +import com.ycwl.basic.model.Crop; +import com.ycwl.basic.utils.ImageUtils; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.File; +import java.nio.file.Path; import static org.junit.jupiter.api.Assertions.*; @@ -72,4 +80,162 @@ class ImageOrientationStageTest { assertTrue(result.isFailed()); assertEquals("当前文件不存在", result.getMessage()); } + + /** + * 测试综合判断:物理横图 + rotation=0 → 横图 + */ + @Test + void testExecute_LandscapeImage_NoRotation(@TempDir Path tempDir) throws Exception { + ImageOrientationStage stage = new ImageOrientationStage(); + + // 创建物理横图 (1920x1080) + File landscapeFile = createTestImage(tempDir, "landscape.jpg", 1920, 1080); + + PhotoProcessContext context = PhotoProcessContext.builder() + .processId("test-landscape-no-rotation") + .originalUrl("https://example.com/test.jpg") + .scenicId(123L) + .imageType(ImageType.NORMAL_PHOTO) + .build(); + + context.setOriginalFile(landscapeFile); + + // 无 rotation 信息 + Crop crop = new Crop(); + crop.setRotation(0); + context.setCrop(crop); + + StageResult result = stage.execute(context); + + assertTrue(result.isSuccess()); + assertTrue(context.isLandscape(), "应该判定为横图"); + } + + /** + * 测试综合判断:物理横图 + rotation=90 → 竖图 + * 场景:一张横向分辨率的图片,但内容是竖着的(需要旋转90度才能正确显示) + */ + @Test + void testExecute_LandscapeImage_Rotation90_ShouldBePortrait(@TempDir Path tempDir) throws Exception { + ImageOrientationStage stage = new ImageOrientationStage(); + + // 创建物理横图 (1920x1080),但内容实际是竖着的 + File landscapeFile = createTestImage(tempDir, "landscape_rotated.jpg", 1920, 1080); + + PhotoProcessContext context = PhotoProcessContext.builder() + .processId("test-landscape-rotation90") + .originalUrl("https://example.com/test.jpg") + .scenicId(123L) + .imageType(ImageType.NORMAL_PHOTO) + .build(); + + context.setOriginalFile(landscapeFile); + + // rotation=90 表示需要旋转90度才能正确显示 + Crop crop = new Crop(); + crop.setRotation(90); + context.setCrop(crop); + + StageResult result = stage.execute(context); + + assertTrue(result.isSuccess()); + assertFalse(context.isLandscape(), "旋转90度后应该是竖图"); + } + + /** + * 测试综合判断:物理竖图 + rotation=90 → 横图 + * 场景:一张竖向分辨率的图片,但内容是横着的(需要旋转90度才能正确显示) + */ + @Test + void testExecute_PortraitImage_Rotation90_ShouldBeLandscape(@TempDir Path tempDir) throws Exception { + ImageOrientationStage stage = new ImageOrientationStage(); + + // 创建物理竖图 (1080x1920),但内容实际是横着的 + File portraitFile = createTestImage(tempDir, "portrait_rotated.jpg", 1080, 1920); + + PhotoProcessContext context = PhotoProcessContext.builder() + .processId("test-portrait-rotation90") + .originalUrl("https://example.com/test.jpg") + .scenicId(123L) + .imageType(ImageType.NORMAL_PHOTO) + .build(); + + context.setOriginalFile(portraitFile); + + // rotation=90 表示需要旋转90度才能正确显示 + Crop crop = new Crop(); + crop.setRotation(90); + context.setCrop(crop); + + StageResult result = stage.execute(context); + + assertTrue(result.isSuccess()); + assertTrue(context.isLandscape(), "旋转90度后应该是横图"); + } + + /** + * 测试综合判断:物理横图 + rotation=270 → 竖图 + */ + @Test + void testExecute_LandscapeImage_Rotation270_ShouldBePortrait(@TempDir Path tempDir) throws Exception { + ImageOrientationStage stage = new ImageOrientationStage(); + + File landscapeFile = createTestImage(tempDir, "landscape_270.jpg", 1920, 1080); + + PhotoProcessContext context = PhotoProcessContext.builder() + .processId("test-landscape-rotation270") + .originalUrl("https://example.com/test.jpg") + .scenicId(123L) + .imageType(ImageType.NORMAL_PHOTO) + .build(); + + context.setOriginalFile(landscapeFile); + + Crop crop = new Crop(); + crop.setRotation(270); + context.setCrop(crop); + + StageResult result = stage.execute(context); + + assertTrue(result.isSuccess()); + assertFalse(context.isLandscape(), "旋转270度后应该是竖图"); + } + + /** + * 测试综合判断:物理横图 + rotation=180 → 横图 + */ + @Test + void testExecute_LandscapeImage_Rotation180_ShouldBeLandscape(@TempDir Path tempDir) throws Exception { + ImageOrientationStage stage = new ImageOrientationStage(); + + File landscapeFile = createTestImage(tempDir, "landscape_180.jpg", 1920, 1080); + + PhotoProcessContext context = PhotoProcessContext.builder() + .processId("test-landscape-rotation180") + .originalUrl("https://example.com/test.jpg") + .scenicId(123L) + .imageType(ImageType.NORMAL_PHOTO) + .build(); + + context.setOriginalFile(landscapeFile); + + Crop crop = new Crop(); + crop.setRotation(180); + context.setCrop(crop); + + StageResult result = stage.execute(context); + + assertTrue(result.isSuccess()); + assertTrue(context.isLandscape(), "旋转180度后仍然是横图"); + } + + /** + * 创建测试用的图片文件 + */ + private File createTestImage(Path tempDir, String filename, int width, int height) throws Exception { + BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + File file = tempDir.resolve(filename).toFile(); + ImageIO.write(image, "jpg", file); + return file; + } }