You've already forked FrameTour-BE
refactor(image): 重构图片旋转和恢复逻辑
- 将 needRotation 标志重命名为 rotationApplied - 修改条件旋转阶段的执行逻辑,基于实际旋转角度判断 - 实现通用的图片恢复旋转功能,支持90/180/270度恢复 - 添加恢复旋转角度计算方法 getRestoreAngle - 更新水印阶段的旋转状态检查逻辑 - 完善单元测试覆盖各种旋转场景 - 优化日志记录和错误处理流程
This commit is contained in:
@@ -91,7 +91,7 @@ public class PhotoProcessContext {
|
|||||||
private File originalFile;
|
private File originalFile;
|
||||||
private File processedFile;
|
private File processedFile;
|
||||||
private boolean isLandscape = true;
|
private boolean isLandscape = true;
|
||||||
private boolean needRotation = false;
|
private boolean rotationApplied = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 图像需要旋转的角度(用于后续Stage使用)
|
* 图像需要旋转的角度(用于后续Stage使用)
|
||||||
|
|||||||
@@ -30,7 +30,8 @@ public class ConditionalRotateStage extends AbstractPipelineStage<PhotoProcessCo
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean shouldExecuteByBusinessLogic(PhotoProcessContext context) {
|
protected boolean shouldExecuteByBusinessLogic(PhotoProcessContext context) {
|
||||||
return context.getImageType() == ImageType.NORMAL_PHOTO && !context.isLandscape();
|
Integer rotation = context.getImageRotation();
|
||||||
|
return context.getImageType() == ImageType.NORMAL_PHOTO && (rotation != null && rotation != 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -59,7 +60,7 @@ public class ConditionalRotateStage extends AbstractPipelineStage<PhotoProcessCo
|
|||||||
}
|
}
|
||||||
|
|
||||||
context.updateProcessedFile(rotatedFile);
|
context.updateProcessedFile(rotatedFile);
|
||||||
context.setNeedRotation(true);
|
context.setRotationApplied(true);
|
||||||
|
|
||||||
log.info("图片已旋转{}度", rotation);
|
log.info("图片已旋转{}度", rotation);
|
||||||
return StageResult.success("已旋转" + rotation + "度");
|
return StageResult.success("已旋转" + rotation + "度");
|
||||||
|
|||||||
@@ -13,7 +13,9 @@ import java.io.File;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 恢复图片方向Stage
|
* 恢复图片方向Stage
|
||||||
* 如果之前旋转过竖图,现在旋转270度恢复为竖图
|
* 如果之前旋转过图片,现在反向旋转恢复原方向
|
||||||
|
* - rotation=90 时恢复需要旋转270度
|
||||||
|
* - rotation=270 时恢复需要旋转90度
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@StageConfig(
|
@StageConfig(
|
||||||
@@ -30,7 +32,7 @@ public class RestoreOrientationStage extends AbstractPipelineStage<PhotoProcessC
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean shouldExecuteByBusinessLogic(PhotoProcessContext context) {
|
protected boolean shouldExecuteByBusinessLogic(PhotoProcessContext context) {
|
||||||
return context.getImageType() == ImageType.NORMAL_PHOTO && context.isNeedRotation();
|
return context.getImageType() == ImageType.NORMAL_PHOTO && context.isRotationApplied();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -41,21 +43,29 @@ public class RestoreOrientationStage extends AbstractPipelineStage<PhotoProcessC
|
|||||||
return StageResult.failed("当前文件不存在");
|
return StageResult.failed("当前文件不存在");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Integer rotation = context.getImageRotation();
|
||||||
|
if (rotation == null) {
|
||||||
|
return StageResult.skipped("无旋转信息");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算恢复旋转需要的角度(反向旋转)
|
||||||
|
int restoreAngle = getRestoreAngle(rotation);
|
||||||
|
|
||||||
String extension = getFileExtension(currentFile);
|
String extension = getFileExtension(currentFile);
|
||||||
File finalFile = context.getTempFileManager()
|
File finalFile = context.getTempFileManager()
|
||||||
.createTempFile("final", extension);
|
.createTempFile("final", extension);
|
||||||
|
|
||||||
log.debug("恢复竖图方向(旋转270度): {} -> {}", currentFile.getName(), finalFile.getName());
|
log.debug("恢复图片方向(旋转{}度): {} -> {}", restoreAngle, currentFile.getName(), finalFile.getName());
|
||||||
ImageUtils.rotateImage270(currentFile, finalFile);
|
rotateByAngle(currentFile, finalFile, restoreAngle);
|
||||||
|
|
||||||
if (!finalFile.exists()) {
|
if (!finalFile.exists()) {
|
||||||
return StageResult.failed("旋转后的文件未生成");
|
return StageResult.failed("旋转后的文件未生成");
|
||||||
}
|
}
|
||||||
|
|
||||||
context.updateProcessedFile(finalFile);
|
context.updateProcessedFile(finalFile);
|
||||||
log.info("竖图方向已恢复(旋转270度)");
|
log.info("图片方向已恢复(旋转{}度)", restoreAngle);
|
||||||
|
|
||||||
return StageResult.success("已恢复竖图方向");
|
return StageResult.success("已恢复原方向");
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("图片旋转失败", e);
|
log.error("图片旋转失败", e);
|
||||||
@@ -63,6 +73,37 @@ public class RestoreOrientationStage extends AbstractPipelineStage<PhotoProcessC
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算恢复方向需要的旋转角度
|
||||||
|
* @param originalRotation 原始旋转角度
|
||||||
|
* @return 恢复需要的旋转角度
|
||||||
|
*/
|
||||||
|
private int getRestoreAngle(int originalRotation) {
|
||||||
|
// rotation=90 时,恢复需要旋转270度 (360-90=270)
|
||||||
|
// rotation=270 时,恢复需要旋转90度 (360-270=90)
|
||||||
|
// rotation=180 时,恢复需要旋转180度 (360-180=180)
|
||||||
|
return (360 - originalRotation) % 360;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据角度旋转图片
|
||||||
|
*/
|
||||||
|
private void rotateByAngle(File input, File output, int angle) throws Exception {
|
||||||
|
switch (angle) {
|
||||||
|
case 90:
|
||||||
|
ImageUtils.rotateImage90(input, output);
|
||||||
|
break;
|
||||||
|
case 180:
|
||||||
|
ImageUtils.rotateImage180(input, output);
|
||||||
|
break;
|
||||||
|
case 270:
|
||||||
|
ImageUtils.rotateImage270(input, output);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private String getFileExtension(File file) {
|
private String getFileExtension(File file) {
|
||||||
String name = file.getName();
|
String name = file.getName();
|
||||||
int lastDot = name.lastIndexOf('.');
|
int lastDot = name.lastIndexOf('.');
|
||||||
|
|||||||
@@ -171,7 +171,7 @@ public class WatermarkStage extends AbstractPipelineStage<PhotoProcessContext> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 根据旋转状态自己处理 offsetLeft
|
// 根据旋转状态自己处理 offsetLeft
|
||||||
if (context.isNeedRotation()) {
|
if (context.isRotationApplied()) {
|
||||||
if (context.getImageRotation() == 90) {
|
if (context.getImageRotation() == 90) {
|
||||||
info.setOffsetLeft(OFFSET_FOR_PRINTER);
|
info.setOffsetLeft(OFFSET_FOR_PRINTER);
|
||||||
} else if (context.getImageRotation() == 270) {
|
} else if (context.getImageRotation() == 270) {
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ class ConditionalRotateStageTest {
|
|||||||
.build();
|
.build();
|
||||||
|
|
||||||
context.setLandscape(false); // 竖图
|
context.setLandscape(false); // 竖图
|
||||||
|
context.setImageRotation(90); // 需要旋转
|
||||||
|
|
||||||
assertTrue(stage.shouldExecute(context));
|
assertTrue(stage.shouldExecute(context));
|
||||||
}
|
}
|
||||||
@@ -112,7 +113,7 @@ class ConditionalRotateStageTest {
|
|||||||
|
|
||||||
assertTrue(result.isSuccess());
|
assertTrue(result.isSuccess());
|
||||||
assertEquals("已旋转90度", result.getMessage());
|
assertEquals("已旋转90度", result.getMessage());
|
||||||
assertTrue(context.isNeedRotation());
|
assertTrue(context.isRotationApplied());
|
||||||
assertNotNull(context.getCurrentFile());
|
assertNotNull(context.getCurrentFile());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,7 +141,7 @@ class ConditionalRotateStageTest {
|
|||||||
|
|
||||||
assertTrue(result.isSuccess());
|
assertTrue(result.isSuccess());
|
||||||
assertEquals("已旋转180度", result.getMessage());
|
assertEquals("已旋转180度", result.getMessage());
|
||||||
assertTrue(context.isNeedRotation());
|
assertTrue(context.isRotationApplied());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -167,7 +168,7 @@ class ConditionalRotateStageTest {
|
|||||||
|
|
||||||
assertTrue(result.isSuccess());
|
assertTrue(result.isSuccess());
|
||||||
assertEquals("已旋转270度", result.getMessage());
|
assertEquals("已旋转270度", result.getMessage());
|
||||||
assertTrue(context.isNeedRotation());
|
assertTrue(context.isRotationApplied());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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.core.StageResult;
|
||||||
import com.ycwl.basic.image.pipeline.enums.ImageType;
|
import com.ycwl.basic.image.pipeline.enums.ImageType;
|
||||||
import org.junit.jupiter.api.Test;
|
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.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
@@ -20,7 +26,7 @@ class RestoreOrientationStageTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testShouldExecute_NormalPhotoAndNeedRotation() {
|
void testShouldExecute_NormalPhotoAndRotationApplied() {
|
||||||
RestoreOrientationStage stage = new RestoreOrientationStage();
|
RestoreOrientationStage stage = new RestoreOrientationStage();
|
||||||
|
|
||||||
PhotoProcessContext context = PhotoProcessContext.builder()
|
PhotoProcessContext context = PhotoProcessContext.builder()
|
||||||
@@ -29,13 +35,13 @@ class RestoreOrientationStageTest {
|
|||||||
.imageType(ImageType.NORMAL_PHOTO)
|
.imageType(ImageType.NORMAL_PHOTO)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
context.setNeedRotation(true);
|
context.setRotationApplied(true);
|
||||||
|
|
||||||
assertTrue(stage.shouldExecute(context));
|
assertTrue(stage.shouldExecute(context));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testShouldExecute_NormalPhotoButNoNeedRotation_ShouldSkip() {
|
void testShouldExecute_NormalPhotoButNoRotationApplied_ShouldSkip() {
|
||||||
RestoreOrientationStage stage = new RestoreOrientationStage();
|
RestoreOrientationStage stage = new RestoreOrientationStage();
|
||||||
|
|
||||||
PhotoProcessContext context = PhotoProcessContext.builder()
|
PhotoProcessContext context = PhotoProcessContext.builder()
|
||||||
@@ -44,7 +50,7 @@ class RestoreOrientationStageTest {
|
|||||||
.imageType(ImageType.NORMAL_PHOTO)
|
.imageType(ImageType.NORMAL_PHOTO)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
context.setNeedRotation(false);
|
context.setRotationApplied(false);
|
||||||
|
|
||||||
assertFalse(stage.shouldExecute(context));
|
assertFalse(stage.shouldExecute(context));
|
||||||
}
|
}
|
||||||
@@ -59,7 +65,7 @@ class RestoreOrientationStageTest {
|
|||||||
.imageType(ImageType.PUZZLE)
|
.imageType(ImageType.PUZZLE)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
context.setNeedRotation(true);
|
context.setRotationApplied(true);
|
||||||
|
|
||||||
assertFalse(stage.shouldExecute(context));
|
assertFalse(stage.shouldExecute(context));
|
||||||
}
|
}
|
||||||
@@ -74,11 +80,113 @@ class RestoreOrientationStageTest {
|
|||||||
.imageType(ImageType.NORMAL_PHOTO)
|
.imageType(ImageType.NORMAL_PHOTO)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
context.setNeedRotation(true);
|
context.setRotationApplied(true);
|
||||||
|
|
||||||
StageResult result = stage.execute(context);
|
StageResult result = stage.execute(context);
|
||||||
|
|
||||||
assertTrue(result.isFailed());
|
assertTrue(result.isFailed());
|
||||||
assertEquals("当前文件不存在", result.getMessage());
|
assertEquals("当前文件不存在", result.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testExecute_Restore90Rotation(@TempDir Path tempDir) throws Exception {
|
||||||
|
RestoreOrientationStage stage = new RestoreOrientationStage();
|
||||||
|
|
||||||
|
File testFile = createTestImage(tempDir, "test.jpg", 1920, 1080);
|
||||||
|
|
||||||
|
PhotoProcessContext context = PhotoProcessContext.builder()
|
||||||
|
.processId("test-restore-90")
|
||||||
|
.originalUrl("https://example.com/test.jpg")
|
||||||
|
.scenicId(123L)
|
||||||
|
.imageType(ImageType.NORMAL_PHOTO)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
context.setOriginalFile(testFile);
|
||||||
|
context.setRotationApplied(true);
|
||||||
|
context.setImageRotation(90); // 原始旋转了90度,恢复需要旋转270度
|
||||||
|
|
||||||
|
StageResult result = stage.execute(context);
|
||||||
|
|
||||||
|
assertTrue(result.isSuccess());
|
||||||
|
assertEquals("已恢复原方向", result.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testExecute_Restore270Rotation(@TempDir Path tempDir) throws Exception {
|
||||||
|
RestoreOrientationStage stage = new RestoreOrientationStage();
|
||||||
|
|
||||||
|
File testFile = createTestImage(tempDir, "test.jpg", 1920, 1080);
|
||||||
|
|
||||||
|
PhotoProcessContext context = PhotoProcessContext.builder()
|
||||||
|
.processId("test-restore-270")
|
||||||
|
.originalUrl("https://example.com/test.jpg")
|
||||||
|
.scenicId(123L)
|
||||||
|
.imageType(ImageType.NORMAL_PHOTO)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
context.setOriginalFile(testFile);
|
||||||
|
context.setRotationApplied(true);
|
||||||
|
context.setImageRotation(270); // 原始旋转了270度,恢复需要旋转90度
|
||||||
|
|
||||||
|
StageResult result = stage.execute(context);
|
||||||
|
|
||||||
|
assertTrue(result.isSuccess());
|
||||||
|
assertEquals("已恢复原方向", result.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testExecute_Restore180Rotation(@TempDir Path tempDir) throws Exception {
|
||||||
|
RestoreOrientationStage stage = new RestoreOrientationStage();
|
||||||
|
|
||||||
|
File testFile = createTestImage(tempDir, "test.jpg", 1920, 1080);
|
||||||
|
|
||||||
|
PhotoProcessContext context = PhotoProcessContext.builder()
|
||||||
|
.processId("test-restore-180")
|
||||||
|
.originalUrl("https://example.com/test.jpg")
|
||||||
|
.scenicId(123L)
|
||||||
|
.imageType(ImageType.NORMAL_PHOTO)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
context.setOriginalFile(testFile);
|
||||||
|
context.setRotationApplied(true);
|
||||||
|
context.setImageRotation(180); // 原始旋转了180度,恢复需要旋转180度
|
||||||
|
|
||||||
|
StageResult result = stage.execute(context);
|
||||||
|
|
||||||
|
assertTrue(result.isSuccess());
|
||||||
|
assertEquals("已恢复原方向", result.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testExecute_NoRotationInfo_ShouldSkip(@TempDir Path tempDir) throws Exception {
|
||||||
|
RestoreOrientationStage stage = new RestoreOrientationStage();
|
||||||
|
|
||||||
|
File testFile = createTestImage(tempDir, "test.jpg", 1920, 1080);
|
||||||
|
|
||||||
|
PhotoProcessContext context = PhotoProcessContext.builder()
|
||||||
|
.processId("test-no-rotation-info")
|
||||||
|
.originalUrl("https://example.com/test.jpg")
|
||||||
|
.scenicId(123L)
|
||||||
|
.imageType(ImageType.NORMAL_PHOTO)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
context.setOriginalFile(testFile);
|
||||||
|
context.setRotationApplied(true);
|
||||||
|
context.setImageRotation(null); // 无旋转信息
|
||||||
|
|
||||||
|
StageResult result = stage.execute(context);
|
||||||
|
|
||||||
|
assertTrue(result.isSkipped());
|
||||||
|
assertEquals("无旋转信息", result.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建测试用的图片文件
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user