You've already forked FrameTour-BE
refactor(image): 重构水印处理逻辑以提高可维护性
- 移除 PhotoProcessContext 中的水印相关字段 - 新增 WatermarkConfig 类封装水印配置 - 修改 WatermarkStage 通过构造函数注入配置 - 调整 PrinterServiceImpl 中水印配置的传递方式 - 更新单元测试以适应新的配置注入方式 - 统一从配置对象读取水印参数而非上下文 - 优化日志记录与偏移量计算逻辑
This commit is contained in:
@@ -101,12 +101,6 @@ public class PhotoProcessContext {
|
|||||||
|
|
||||||
private String resultUrl;
|
private String resultUrl;
|
||||||
private IStorageAdapter storageAdapter;
|
private IStorageAdapter storageAdapter;
|
||||||
private WatermarkInfo watermarkInfo;
|
|
||||||
private ImageWatermarkOperatorEnum watermarkType;
|
|
||||||
private String scenicText;
|
|
||||||
private String dateFormat;
|
|
||||||
private File qrcodeFile;
|
|
||||||
private Integer offsetLeft;
|
|
||||||
|
|
||||||
// ==================== 回调 ====================
|
// ==================== 回调 ====================
|
||||||
|
|
||||||
|
|||||||
@@ -23,8 +23,6 @@ import java.io.File;
|
|||||||
)
|
)
|
||||||
public class ConditionalRotateStage extends AbstractPipelineStage<PhotoProcessContext> {
|
public class ConditionalRotateStage extends AbstractPipelineStage<PhotoProcessContext> {
|
||||||
|
|
||||||
private static final int OFFSET_LEFT_FOR_PORTRAIT = 40;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return "ConditionalRotateStage";
|
return "ConditionalRotateStage";
|
||||||
@@ -62,9 +60,8 @@ public class ConditionalRotateStage extends AbstractPipelineStage<PhotoProcessCo
|
|||||||
|
|
||||||
context.updateProcessedFile(rotatedFile);
|
context.updateProcessedFile(rotatedFile);
|
||||||
context.setNeedRotation(true);
|
context.setNeedRotation(true);
|
||||||
context.setOffsetLeft(OFFSET_LEFT_FOR_PORTRAIT);
|
|
||||||
|
|
||||||
log.info("图片已旋转{}度, offsetLeft={}", rotation, OFFSET_LEFT_FOR_PORTRAIT);
|
log.info("图片已旋转{}度", rotation);
|
||||||
return StageResult.success("已旋转" + rotation + "度");
|
return StageResult.success("已旋转" + rotation + "度");
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package com.ycwl.basic.image.pipeline.stages;
|
||||||
|
|
||||||
|
import com.ycwl.basic.image.watermark.enums.ImageWatermarkOperatorEnum;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 水印Stage配置
|
||||||
|
* 封装水印处理所需的所有配置参数
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
public class WatermarkConfig {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 水印类型
|
||||||
|
*/
|
||||||
|
private final ImageWatermarkOperatorEnum watermarkType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 景区文字
|
||||||
|
*/
|
||||||
|
private final String scenicText;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 日期格式
|
||||||
|
*/
|
||||||
|
@Builder.Default
|
||||||
|
private final String dateFormat = "yyyy.MM.dd";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 二维码文件
|
||||||
|
*/
|
||||||
|
private final File qrcodeFile;
|
||||||
|
}
|
||||||
@@ -31,6 +31,19 @@ import java.util.List;
|
|||||||
)
|
)
|
||||||
public class WatermarkStage extends AbstractPipelineStage<PhotoProcessContext> {
|
public class WatermarkStage extends AbstractPipelineStage<PhotoProcessContext> {
|
||||||
|
|
||||||
|
private static final int OFFSET_LEFT_FOR_PORTRAIT = 40;
|
||||||
|
|
||||||
|
private final WatermarkConfig config;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造函数
|
||||||
|
*
|
||||||
|
* @param config 水印配置
|
||||||
|
*/
|
||||||
|
public WatermarkStage(WatermarkConfig config) {
|
||||||
|
this.config = config;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return "WatermarkStage";
|
return "WatermarkStage";
|
||||||
@@ -43,12 +56,13 @@ public class WatermarkStage extends AbstractPipelineStage<PhotoProcessContext> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected StageResult doExecute(PhotoProcessContext context) {
|
protected StageResult doExecute(PhotoProcessContext context) {
|
||||||
ImageWatermarkOperatorEnum watermarkType = context.getWatermarkType();
|
if (config == null || config.getWatermarkType() == null) {
|
||||||
if (watermarkType == null) {
|
|
||||||
log.info("未配置水印类型,跳过水印处理");
|
log.info("未配置水印类型,跳过水印处理");
|
||||||
return StageResult.skipped("未配置水印");
|
return StageResult.skipped("未配置水印");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ImageWatermarkOperatorEnum watermarkType = config.getWatermarkType();
|
||||||
|
|
||||||
List<ImageWatermarkOperatorEnum> fallbackChain = buildFallbackChain(watermarkType);
|
List<ImageWatermarkOperatorEnum> fallbackChain = buildFallbackChain(watermarkType);
|
||||||
log.debug("水印降级链: {}", fallbackChain);
|
log.debug("水印降级链: {}", fallbackChain);
|
||||||
|
|
||||||
@@ -138,30 +152,29 @@ public class WatermarkStage extends AbstractPipelineStage<PhotoProcessContext> {
|
|||||||
info.setOriginalFile(originalFile);
|
info.setOriginalFile(originalFile);
|
||||||
info.setWatermarkedFile(watermarkedFile);
|
info.setWatermarkedFile(watermarkedFile);
|
||||||
|
|
||||||
String scenicText = context.getScenicText();
|
// 从 config 读取景区文字
|
||||||
|
String scenicText = config.getScenicText();
|
||||||
if (StringUtils.isNotBlank(scenicText)) {
|
if (StringUtils.isNotBlank(scenicText)) {
|
||||||
info.setScenicLine(scenicText);
|
info.setScenicLine(scenicText);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 从 config 读取日期格式
|
||||||
Date now = new Date();
|
Date now = new Date();
|
||||||
String dateFormat = context.getDateFormat();
|
String dateFormat = config.getDateFormat();
|
||||||
if (StringUtils.isBlank(dateFormat)) {
|
|
||||||
dateFormat = "yyyy.MM.dd";
|
|
||||||
}
|
|
||||||
info.setDatetime(now);
|
info.setDatetime(now);
|
||||||
info.setDtFormat(dateFormat);
|
info.setDtFormat(dateFormat);
|
||||||
|
|
||||||
File qrcodeFile = context.getQrcodeFile();
|
// 从 config 读取二维码文件
|
||||||
|
File qrcodeFile = config.getQrcodeFile();
|
||||||
if (qrcodeFile != null && qrcodeFile.exists()) {
|
if (qrcodeFile != null && qrcodeFile.exists()) {
|
||||||
info.setQrcodeFile(qrcodeFile);
|
info.setQrcodeFile(qrcodeFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
Integer offsetLeft = context.getOffsetLeft();
|
// 根据旋转状态自己处理 offsetLeft
|
||||||
if (offsetLeft != null) {
|
if (context.isNeedRotation()) {
|
||||||
info.setOffsetLeft(offsetLeft);
|
info.setOffsetLeft(OFFSET_LEFT_FOR_PORTRAIT);
|
||||||
}
|
}
|
||||||
|
|
||||||
context.setWatermarkInfo(info);
|
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import com.ycwl.basic.image.pipeline.stages.ImageOrientationStage;
|
|||||||
import com.ycwl.basic.image.pipeline.stages.PuzzleBorderStage;
|
import com.ycwl.basic.image.pipeline.stages.PuzzleBorderStage;
|
||||||
import com.ycwl.basic.image.pipeline.stages.RestoreOrientationStage;
|
import com.ycwl.basic.image.pipeline.stages.RestoreOrientationStage;
|
||||||
import com.ycwl.basic.image.pipeline.stages.UploadStage;
|
import com.ycwl.basic.image.pipeline.stages.UploadStage;
|
||||||
|
import com.ycwl.basic.image.pipeline.stages.WatermarkConfig;
|
||||||
import com.ycwl.basic.image.pipeline.stages.WatermarkStage;
|
import com.ycwl.basic.image.pipeline.stages.WatermarkStage;
|
||||||
import com.ycwl.basic.image.watermark.enums.ImageWatermarkOperatorEnum;
|
import com.ycwl.basic.image.watermark.enums.ImageWatermarkOperatorEnum;
|
||||||
import com.ycwl.basic.integration.common.manager.DeviceConfigManager;
|
import com.ycwl.basic.integration.common.manager.DeviceConfigManager;
|
||||||
@@ -742,13 +743,15 @@ public class PrinterServiceImpl implements PrinterService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建普通照片处理管线
|
* 创建普通照片处理管线
|
||||||
|
*
|
||||||
|
* @param watermarkConfig 水印配置
|
||||||
*/
|
*/
|
||||||
private Pipeline<PhotoProcessContext> createNormalPhotoPipeline() {
|
private Pipeline<PhotoProcessContext> createNormalPhotoPipeline(WatermarkConfig watermarkConfig) {
|
||||||
return new PipelineBuilder<PhotoProcessContext>("NormalPhotoPipeline")
|
return new PipelineBuilder<PhotoProcessContext>("NormalPhotoPipeline")
|
||||||
.addStage(new DownloadStage())
|
.addStage(new DownloadStage())
|
||||||
.addStage(new ImageOrientationStage())
|
.addStage(new ImageOrientationStage())
|
||||||
.addStage(new ConditionalRotateStage())
|
.addStage(new ConditionalRotateStage())
|
||||||
.addStage(new WatermarkStage())
|
.addStage(new WatermarkStage(watermarkConfig))
|
||||||
.addStage(new RestoreOrientationStage())
|
.addStage(new RestoreOrientationStage())
|
||||||
.addStage(new UploadStage())
|
.addStage(new UploadStage())
|
||||||
.addStage(new CleanupStage())
|
.addStage(new CleanupStage())
|
||||||
@@ -778,7 +781,6 @@ public class PrinterServiceImpl implements PrinterService {
|
|||||||
PrinterOrderItem orderItem = PrinterOrderItem.fromMemberPrintResp(item);
|
PrinterOrderItem orderItem = PrinterOrderItem.fromMemberPrintResp(item);
|
||||||
|
|
||||||
PhotoProcessContext context = PhotoProcessContext.fromPrinterOrderItem(orderItem, scenicId);
|
PhotoProcessContext context = PhotoProcessContext.fromPrinterOrderItem(orderItem, scenicId);
|
||||||
context.setQrcodeFile(qrCodeFile);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 设置景区配置管理器到context
|
// 设置景区配置管理器到context
|
||||||
@@ -800,16 +802,20 @@ public class PrinterServiceImpl implements PrinterService {
|
|||||||
context.setSource(ImageSource.UNKNOWN);
|
context.setSource(ImageSource.UNKNOWN);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Pipeline<PhotoProcessContext> pipeline;
|
||||||
if (context.getImageType() == ImageType.NORMAL_PHOTO) {
|
if (context.getImageType() == ImageType.NORMAL_PHOTO) {
|
||||||
prepareNormalPhotoContext(context);
|
// 准备水印配置
|
||||||
} else if (context.getImageType() == ImageType.PUZZLE) {
|
WatermarkConfig watermarkConfig = prepareWatermarkConfig(context, qrCodeFile);
|
||||||
|
// 准备存储适配器
|
||||||
prepareStorageAdapter(context);
|
prepareStorageAdapter(context);
|
||||||
|
// 创建普通照片管线
|
||||||
|
pipeline = createNormalPhotoPipeline(watermarkConfig);
|
||||||
|
} else {
|
||||||
|
// 拼图
|
||||||
|
prepareStorageAdapter(context);
|
||||||
|
pipeline = createPuzzlePipeline();
|
||||||
}
|
}
|
||||||
|
|
||||||
Pipeline<PhotoProcessContext> pipeline = context.getImageType() == ImageType.PUZZLE
|
|
||||||
? createPuzzlePipeline()
|
|
||||||
: createNormalPhotoPipeline();
|
|
||||||
|
|
||||||
boolean success = pipeline.execute(context);
|
boolean success = pipeline.execute(context);
|
||||||
|
|
||||||
if (success && context.getResultUrl() != null) {
|
if (success && context.getResultUrl() != null) {
|
||||||
@@ -832,29 +838,35 @@ public class PrinterServiceImpl implements PrinterService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 准备普通照片的Context配置
|
* 准备普通照片的水印配置
|
||||||
* 从context中的scenicConfigManager获取配置
|
* 从 scenicConfigManager 获取配置并构建 WatermarkConfig
|
||||||
|
*
|
||||||
|
* @param context 照片处理上下文
|
||||||
|
* @param qrCodeFile 二维码文件
|
||||||
|
* @return WatermarkConfig
|
||||||
*/
|
*/
|
||||||
private void prepareNormalPhotoContext(PhotoProcessContext context) {
|
private WatermarkConfig prepareWatermarkConfig(PhotoProcessContext context, File qrCodeFile) {
|
||||||
ScenicConfigManager scenicConfig = context.getScenicConfigManager();
|
ScenicConfigManager scenicConfig = context.getScenicConfigManager();
|
||||||
if (scenicConfig == null) {
|
if (scenicConfig == null) {
|
||||||
log.warn("scenicConfigManager未设置,跳过配置准备");
|
log.warn("scenicConfigManager未设置,返回空水印配置");
|
||||||
return;
|
return WatermarkConfig.builder().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ImageWatermarkOperatorEnum watermarkType = null;
|
||||||
String printWatermarkType = scenicConfig.getString("print_watermark_type");
|
String printWatermarkType = scenicConfig.getString("print_watermark_type");
|
||||||
if (StringUtils.isNotBlank(printWatermarkType)) {
|
if (StringUtils.isNotBlank(printWatermarkType)) {
|
||||||
ImageWatermarkOperatorEnum watermarkType = ImageWatermarkOperatorEnum.getByCode(printWatermarkType);
|
watermarkType = ImageWatermarkOperatorEnum.getByCode(printWatermarkType);
|
||||||
context.setWatermarkType(watermarkType);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String scenicText = scenicConfig.getString("print_watermark_scenic_text", "");
|
String scenicText = scenicConfig.getString("print_watermark_scenic_text", "");
|
||||||
context.setScenicText(scenicText);
|
|
||||||
|
|
||||||
String dateFormat = scenicConfig.getString("print_watermark_dt_format", "yyyy.MM.dd");
|
String dateFormat = scenicConfig.getString("print_watermark_dt_format", "yyyy.MM.dd");
|
||||||
context.setDateFormat(dateFormat);
|
|
||||||
|
|
||||||
prepareStorageAdapter(context);
|
return WatermarkConfig.builder()
|
||||||
|
.watermarkType(watermarkType)
|
||||||
|
.scenicText(scenicText)
|
||||||
|
.dateFormat(dateFormat)
|
||||||
|
.qrcodeFile(qrCodeFile)
|
||||||
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.ycwl.basic.image.pipeline.stages;
|
|||||||
import com.ycwl.basic.image.pipeline.core.PhotoProcessContext;
|
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 com.ycwl.basic.image.watermark.enums.ImageWatermarkOperatorEnum;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
@@ -14,14 +15,18 @@ class WatermarkStageTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testGetName() {
|
void testGetName() {
|
||||||
WatermarkStage stage = new WatermarkStage();
|
WatermarkConfig config = WatermarkConfig.builder().build();
|
||||||
|
WatermarkStage stage = new WatermarkStage(config);
|
||||||
|
|
||||||
assertEquals("WatermarkStage", stage.getName());
|
assertEquals("WatermarkStage", stage.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testShouldExecute_NormalPhoto() {
|
void testShouldExecute_NormalPhoto() {
|
||||||
WatermarkStage stage = new WatermarkStage();
|
WatermarkConfig config = WatermarkConfig.builder()
|
||||||
|
.watermarkType(ImageWatermarkOperatorEnum.PRINTER_DEFAULT)
|
||||||
|
.build();
|
||||||
|
WatermarkStage stage = new WatermarkStage(config);
|
||||||
|
|
||||||
PhotoProcessContext context = PhotoProcessContext.builder()
|
PhotoProcessContext context = PhotoProcessContext.builder()
|
||||||
.originalUrl("https://example.com/test.jpg")
|
.originalUrl("https://example.com/test.jpg")
|
||||||
@@ -34,7 +39,10 @@ class WatermarkStageTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testShouldExecute_Puzzle_ShouldSkip() {
|
void testShouldExecute_Puzzle_ShouldSkip() {
|
||||||
WatermarkStage stage = new WatermarkStage();
|
WatermarkConfig config = WatermarkConfig.builder()
|
||||||
|
.watermarkType(ImageWatermarkOperatorEnum.PRINTER_DEFAULT)
|
||||||
|
.build();
|
||||||
|
WatermarkStage stage = new WatermarkStage(config);
|
||||||
|
|
||||||
PhotoProcessContext context = PhotoProcessContext.builder()
|
PhotoProcessContext context = PhotoProcessContext.builder()
|
||||||
.originalUrl("https://example.com/puzzle.png")
|
.originalUrl("https://example.com/puzzle.png")
|
||||||
@@ -47,7 +55,10 @@ class WatermarkStageTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testShouldExecute_MobileUpload_ShouldSkip() {
|
void testShouldExecute_MobileUpload_ShouldSkip() {
|
||||||
WatermarkStage stage = new WatermarkStage();
|
WatermarkConfig config = WatermarkConfig.builder()
|
||||||
|
.watermarkType(ImageWatermarkOperatorEnum.PRINTER_DEFAULT)
|
||||||
|
.build();
|
||||||
|
WatermarkStage stage = new WatermarkStage(config);
|
||||||
|
|
||||||
PhotoProcessContext context = PhotoProcessContext.builder()
|
PhotoProcessContext context = PhotoProcessContext.builder()
|
||||||
.originalUrl("https://example.com/mobile.jpg")
|
.originalUrl("https://example.com/mobile.jpg")
|
||||||
@@ -60,7 +71,9 @@ class WatermarkStageTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testExecute_NoWatermarkType_ShouldSkip() {
|
void testExecute_NoWatermarkType_ShouldSkip() {
|
||||||
WatermarkStage stage = new WatermarkStage();
|
// 创建没有 watermarkType 的配置
|
||||||
|
WatermarkConfig config = WatermarkConfig.builder().build();
|
||||||
|
WatermarkStage stage = new WatermarkStage(config);
|
||||||
|
|
||||||
PhotoProcessContext context = PhotoProcessContext.builder()
|
PhotoProcessContext context = PhotoProcessContext.builder()
|
||||||
.originalUrl("https://example.com/test.jpg")
|
.originalUrl("https://example.com/test.jpg")
|
||||||
|
|||||||
Reference in New Issue
Block a user