feat(image): 实现图像增强与质量检测功能

- 新增ImageEnhancerFactory工厂类,支持创建不同类型的图像增强器
- 添加图像清晰度增强和超分辨率两种增强模式
- 实现ImageEnhanceStage图像增强处理阶段
- 新增ImageQualityCheckStage图像质量检测阶段
- 支持根据图片质量动态添加图像增强处理
- 完善Stage配置注解和可选性控制机制
- 优化Pipeline执行流程,支持动态插入Stage
- 增加Stage执行计数和循环依赖防护机制
- 改进StageResult结构,支持携带后续Stage列表
- 统一抽象Stage的执行条件判断逻辑
This commit is contained in:
2025-11-25 11:21:03 +08:00
parent 4a86849372
commit bcebe5defe
16 changed files with 559 additions and 17 deletions

View File

@@ -1,4 +1,62 @@
package com.ycwl.basic.image.enhancer;
import com.ycwl.basic.image.enhancer.adapter.BceImageEnhancer;
import com.ycwl.basic.image.enhancer.adapter.BceImageSR;
import com.ycwl.basic.image.enhancer.adapter.IEnhancer;
import com.ycwl.basic.image.enhancer.entity.BceEnhancerConfig;
/**
* 图像增强器工厂
* 用于创建不同类型的图像增强器实例
*/
public class ImageEnhancerFactory {
/**
* 增强器类型枚举
*/
public enum EnhancerType {
/**
* 图像清晰度增强
* 使用imageDefinitionEnhance接口,适合提升整体清晰度
*/
DEFINITION_ENHANCE,
/**
* 图像超分辨率
* 使用imageQualityEnhance接口,适合放大图片同时保持质量
*/
SUPER_RESOLUTION
}
/**
* 创建图像增强器
*
* @param type 增强器类型
* @param config 百度云配置
* @return 图像增强器实例
*/
public static IEnhancer createEnhancer(EnhancerType type, BceEnhancerConfig config) {
IEnhancer enhancer = switch (type) {
case DEFINITION_ENHANCE -> new BceImageEnhancer();
case SUPER_RESOLUTION -> new BceImageSR();
};
if (enhancer instanceof BceImageEnhancer) {
((BceImageEnhancer) enhancer).setConfig(config);
} else if (enhancer instanceof BceImageSR) {
((BceImageSR) enhancer).setConfig(config);
}
return enhancer;
}
/**
* 创建默认的图像增强器(清晰度增强)
*
* @param config 百度云配置
* @return 图像增强器实例
*/
public static IEnhancer createDefaultEnhancer(BceEnhancerConfig config) {
return createEnhancer(EnhancerType.DEFINITION_ENHANCE, config);
}
}

View File

@@ -1,5 +1,7 @@
package com.ycwl.basic.image.pipeline.core;
import com.ycwl.basic.image.pipeline.annotation.StageConfig;
import com.ycwl.basic.image.pipeline.enums.StageOptionalMode;
import lombok.extern.slf4j.Slf4j;
/**
@@ -10,11 +12,46 @@ import lombok.extern.slf4j.Slf4j;
public abstract class AbstractPipelineStage<C extends PhotoProcessContext> implements PipelineStage<C> {
/**
* 默认总是执行
* 子类可以覆盖此方法实现条件性执行
* 最终的shouldExecute判断
* 整合了外部配置控制和业务逻辑判断
*/
@Override
public boolean shouldExecute(C context) {
public final boolean shouldExecute(C context) {
// 1. 检查Stage配置注解
StageConfig config = getStageConfig();
if (config != null) {
String stageId = config.stageId();
StageOptionalMode mode = config.optionalMode();
// FORCE_ON:强制执行,不检查外部配置
if (mode == StageOptionalMode.FORCE_ON) {
return shouldExecuteByBusinessLogic(context);
}
// SUPPORT:检查外部配置
if (mode == StageOptionalMode.SUPPORT) {
boolean externalEnabled = context.isStageEnabled(stageId, config.defaultEnabled());
if (!externalEnabled) {
log.debug("[{}] Stage被外部配置禁用", stageId);
return false;
}
}
// UNSUPPORT:不检查外部配置,直接走业务逻辑
}
// 2. 执行业务逻辑判断
return shouldExecuteByBusinessLogic(context);
}
/**
* 子类实现业务逻辑判断
* 默认总是执行
*
* 子类可以覆盖此方法实现条件性执行
* 例如: 只有竖图才旋转, 只有普通照片才加水印等
*/
protected boolean shouldExecuteByBusinessLogic(C context) {
return true;
}

View File

@@ -13,6 +13,8 @@ import lombok.Getter;
import lombok.Setter;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
/**
* 图片处理管线上下文
@@ -41,6 +43,13 @@ public class PhotoProcessContext {
*/
private ImageSource source;
/**
* Stage开关配置表
* Key: stageId, Value: 是否启用
* 整合了景区配置和请求参数
*/
private Map<String, Boolean> stageEnabledMap = new HashMap<>();
private File originalFile;
private File processedFile;
private boolean isLandscape = true;
@@ -61,6 +70,30 @@ public class PhotoProcessContext {
this.tempFileManager = new TempFileManager(orderItem.getId().toString());
}
/**
* 从景区配置和请求参数中加载Stage开关配置
*
* @param scenicConfigManager 景区配置管理器
* @param requestParams 请求参数中的Stage配置
*/
public void loadStageConfig(ScenicConfigManager scenicConfigManager, Map<String, Boolean> requestParams) {
// 请求参数覆盖
if (requestParams != null) {
stageEnabledMap.putAll(requestParams);
}
}
/**
* 判断指定Stage是否启用
*
* @param stageId Stage唯一标识
* @param defaultEnabled 默认值(当配置未指定时使用)
* @return true-启用, false-禁用
*/
public boolean isStageEnabled(String stageId, boolean defaultEnabled) {
return stageEnabledMap.getOrDefault(stageId, defaultEnabled);
}
/**
* 是否为拼图类型
*/

View File

@@ -31,9 +31,16 @@ public class Pipeline<C extends PhotoProcessContext> {
public boolean execute(C context) {
log.info("[{}] 开始执行管线, Stage数量: {}", name, stages.size());
long startTime = System.currentTimeMillis();
int maxStages = 100; // 防止无限循环
int executedCount = 0;
try {
for (int i = 0; i < stages.size(); i++) {
if (executedCount >= maxStages) {
log.error("[{}] Stage执行数量超过最大限制({}),可能存在循环依赖", name, maxStages);
throw new PipelineException("Stage执行数量超过最大限制,可能存在循环依赖");
}
PipelineStage<C> stage = stages.get(i);
String stageName = stage.getName();
@@ -47,9 +54,22 @@ public class Pipeline<C extends PhotoProcessContext> {
long stageStartTime = System.currentTimeMillis();
StageResult result = stage.execute(context);
long stageDuration = System.currentTimeMillis() - stageStartTime;
executedCount++;
logStageResult(stageName, result, stageDuration);
// 动态添加后续Stage
if (result.getNextStages() != null && !result.getNextStages().isEmpty()) {
List<PipelineStage<?>> nextStages = result.getNextStages();
log.info("[{}] Stage {} 动态添加了 {} 个后续Stage", name, stageName, nextStages.size());
for (int j = 0; j < nextStages.size(); j++) {
PipelineStage<?> nextStage = nextStages.get(j);
stages.add(i + 1 + j, (PipelineStage<C>) nextStage);
log.debug("[{}] - 插入Stage: {} 到位置 {}", name, nextStage.getName(), i + 1 + j);
}
}
if (result.isFailed()) {
log.error("[{}] Stage {} 执行失败,管线终止", name, stageName);
return false;
@@ -57,7 +77,8 @@ public class Pipeline<C extends PhotoProcessContext> {
}
long totalDuration = System.currentTimeMillis() - startTime;
log.info("[{}] 管线执行完成, 耗时: {}ms", name, totalDuration);
log.info("[{}] 管线执行完成, 总Stage数: {}, 实际执行: {}, 耗时: {}ms",
name, stages.size(), executedCount, totalDuration);
return true;
} catch (Exception e) {

View File

@@ -1,5 +1,7 @@
package com.ycwl.basic.image.pipeline.core;
import com.ycwl.basic.image.pipeline.annotation.StageConfig;
/**
* 管线处理阶段接口
* 每个Stage负责一个独立的图片处理步骤
@@ -37,4 +39,12 @@ public interface PipelineStage<C extends PhotoProcessContext> {
default int getPriority() {
return 100;
}
/**
* 获取Stage配置注解(用于反射读取可选性控制信息)
* @return Stage配置注解,如果未标注则返回null
*/
default StageConfig getStageConfig() {
return this.getClass().getAnnotation(StageConfig.class);
}
}

View File

@@ -2,6 +2,11 @@ package com.ycwl.basic.image.pipeline.core;
import lombok.Getter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* Stage执行结果
*/
@@ -18,39 +23,56 @@ public class StageResult {
private final Status status;
private final String message;
private final Throwable exception;
private final List<PipelineStage<?>> nextStages;
private StageResult(Status status, String message, Throwable exception) {
private StageResult(Status status, String message, Throwable exception, List<PipelineStage<?>> nextStages) {
this.status = status;
this.message = message;
this.exception = exception;
this.nextStages = nextStages != null ? new ArrayList<>(nextStages) : Collections.emptyList();
}
public static StageResult success() {
return new StageResult(Status.SUCCESS, null, null);
return new StageResult(Status.SUCCESS, null, null, null);
}
public static StageResult success(String message) {
return new StageResult(Status.SUCCESS, message, null);
return new StageResult(Status.SUCCESS, message, null, null);
}
/**
* 成功执行并动态添加后续Stage
*/
@SafeVarargs
public static StageResult successWithNext(String message, PipelineStage<?>... stages) {
return new StageResult(Status.SUCCESS, message, null, Arrays.asList(stages));
}
/**
* 成功执行并动态添加后续Stage列表
*/
public static StageResult successWithNext(String message, List<PipelineStage<?>> stages) {
return new StageResult(Status.SUCCESS, message, null, stages);
}
public static StageResult skipped() {
return new StageResult(Status.SKIPPED, "条件不满足,跳过执行", null);
return new StageResult(Status.SKIPPED, "条件不满足,跳过执行", null, null);
}
public static StageResult skipped(String reason) {
return new StageResult(Status.SKIPPED, reason, null);
return new StageResult(Status.SKIPPED, reason, null, null);
}
public static StageResult failed(String message) {
return new StageResult(Status.FAILED, message, null);
return new StageResult(Status.FAILED, message, null, null);
}
public static StageResult failed(String message, Throwable exception) {
return new StageResult(Status.FAILED, message, exception);
return new StageResult(Status.FAILED, message, exception, null);
}
public static StageResult degraded(String message) {
return new StageResult(Status.DEGRADED, message, null);
return new StageResult(Status.DEGRADED, message, null, null);
}
public boolean isSuccess() {

View File

@@ -1,8 +1,10 @@
package com.ycwl.basic.image.pipeline.stages;
import com.ycwl.basic.image.pipeline.annotation.StageConfig;
import com.ycwl.basic.image.pipeline.core.AbstractPipelineStage;
import com.ycwl.basic.image.pipeline.core.PhotoProcessContext;
import com.ycwl.basic.image.pipeline.core.StageResult;
import com.ycwl.basic.image.pipeline.enums.StageOptionalMode;
import lombok.extern.slf4j.Slf4j;
/**
@@ -10,6 +12,12 @@ import lombok.extern.slf4j.Slf4j;
* 总是在管线最后执行,清理所有临时文件
*/
@Slf4j
@StageConfig(
stageId = "cleanup",
optionalMode = StageOptionalMode.FORCE_ON,
description = "清理临时文件",
defaultEnabled = true
)
public class CleanupStage extends AbstractPipelineStage<PhotoProcessContext> {
@Override

View File

@@ -1,8 +1,10 @@
package com.ycwl.basic.image.pipeline.stages;
import com.ycwl.basic.image.pipeline.annotation.StageConfig;
import com.ycwl.basic.image.pipeline.core.AbstractPipelineStage;
import com.ycwl.basic.image.pipeline.core.PhotoProcessContext;
import com.ycwl.basic.image.pipeline.core.StageResult;
import com.ycwl.basic.image.pipeline.enums.StageOptionalMode;
import com.ycwl.basic.utils.ImageUtils;
import lombok.extern.slf4j.Slf4j;
@@ -13,6 +15,11 @@ import java.io.File;
* 如果是竖图,旋转90度变成横图(便于后续水印处理)
*/
@Slf4j
@StageConfig(
stageId = "rotate",
optionalMode = StageOptionalMode.UNSUPPORT,
description = "竖图旋转90度"
)
public class ConditionalRotateStage extends AbstractPipelineStage<PhotoProcessContext> {
private static final int OFFSET_LEFT_FOR_PORTRAIT = 40;
@@ -23,7 +30,7 @@ public class ConditionalRotateStage extends AbstractPipelineStage<PhotoProcessCo
}
@Override
public boolean shouldExecute(PhotoProcessContext context) {
protected boolean shouldExecuteByBusinessLogic(PhotoProcessContext context) {
return context.isNormalPhoto() && !context.isLandscape();
}

View File

@@ -1,9 +1,11 @@
package com.ycwl.basic.image.pipeline.stages;
import cn.hutool.http.HttpUtil;
import com.ycwl.basic.image.pipeline.annotation.StageConfig;
import com.ycwl.basic.image.pipeline.core.AbstractPipelineStage;
import com.ycwl.basic.image.pipeline.core.PhotoProcessContext;
import com.ycwl.basic.image.pipeline.core.StageResult;
import com.ycwl.basic.image.pipeline.enums.StageOptionalMode;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
@@ -14,6 +16,12 @@ import java.io.File;
* 从URL下载原图到本地临时文件
*/
@Slf4j
@StageConfig(
stageId = "download",
optionalMode = StageOptionalMode.FORCE_ON,
description = "下载图片",
defaultEnabled = true
)
public class DownloadStage extends AbstractPipelineStage<PhotoProcessContext> {
@Override

View File

@@ -0,0 +1,177 @@
package com.ycwl.basic.image.pipeline.stages;
import com.ycwl.basic.image.enhancer.adapter.BceImageEnhancer;
import com.ycwl.basic.image.enhancer.entity.BceEnhancerConfig;
import com.ycwl.basic.image.pipeline.annotation.StageConfig;
import com.ycwl.basic.image.pipeline.core.AbstractPipelineStage;
import com.ycwl.basic.image.pipeline.core.PhotoProcessContext;
import com.ycwl.basic.image.pipeline.core.StageResult;
import com.ycwl.basic.image.pipeline.enums.ImageSource;
import com.ycwl.basic.image.pipeline.enums.StageOptionalMode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* 图像增强Stage
* 使用百度云图像增强API提升图片清晰度和质量
*
* 支持两种增强模式:
* 1. 图像清晰度增强 (BceImageEnhancer)
* 2. 图像超分辨率 (BceImageSR)
*/
@Slf4j
@StageConfig(
stageId = "image_enhance",
optionalMode = StageOptionalMode.SUPPORT,
description = "图像增强处理",
defaultEnabled = false // 默认不启用,需要外部配置开启
)
public class ImageEnhanceStage extends AbstractPipelineStage<PhotoProcessContext> {
private BceEnhancerConfig enhancerConfig;
/**
* 构造函数 - 使用默认配置
*/
public ImageEnhanceStage() {
this.enhancerConfig = new BceEnhancerConfig();
this.enhancerConfig.setAppId("119554288");
this.enhancerConfig.setApiKey("OX6QoijgKio3eVtA0PiUVf7f");
this.enhancerConfig.setSecretKey("dYatXReVriPeiktTjUblhfubpcmYfuMk");
this.enhancerConfig.setQps(1.0f);
}
/**
* 构造函数 - 使用自定义配置
*
* @param enhancerConfig 图像增强配置
*/
public ImageEnhanceStage(BceEnhancerConfig enhancerConfig) {
this.enhancerConfig = enhancerConfig;
}
@Override
public String getName() {
return "ImageEnhanceStage";
}
@Override
protected boolean shouldExecuteByBusinessLogic(PhotoProcessContext context) {
// 仅对照片源为IPC的图片进行增强
return context.getSource() == ImageSource.IPC;
}
@Override
protected StageResult doExecute(PhotoProcessContext context) {
// 检查配置是否完整
if (!isConfigValid()) {
log.warn("图像增强配置不完整,跳过增强处理。请在ImageEnhanceStage中配置百度云API凭证");
return StageResult.skipped("配置不完整,跳过增强");
}
File currentFile = context.getCurrentFile();
if (currentFile == null || !currentFile.exists()) {
return StageResult.failed("当前文件不存在");
}
try {
log.debug("开始图像增强: {}", currentFile.getName());
// 创建百度云图像增强客户端
BceImageEnhancer enhancer = new BceImageEnhancer();
enhancer.setConfig(enhancerConfig);
// 调用图像增强API
// 注意:百度云API需要传入图片URL,这里使用本地文件的绝对路径
String imageUrl = currentFile.getAbsolutePath();
MultipartFile enhancedImage = enhancer.enhance(imageUrl);
if (enhancedImage == null || enhancedImage.isEmpty()) {
log.warn("图像增强返回空结果,可能是API调用失败");
return StageResult.degraded("增强失败,使用原图");
}
// 保存增强后的图片到临时文件
File enhancedFile = context.getTempFileManager()
.createTempFile("enhanced", ".jpg");
saveMultipartFileToFile(enhancedImage, enhancedFile);
if (!enhancedFile.exists() || enhancedFile.length() == 0) {
return StageResult.degraded("增强结果保存失败,使用原图");
}
// 更新处理后的文件
context.updateProcessedFile(enhancedFile);
long originalSize = currentFile.length();
long enhancedSize = enhancedFile.length();
double sizeRatio = (double) enhancedSize / originalSize;
log.info("图像增强完成: 原始{}KB -> 增强后{}KB (比例: {})",
originalSize / 1024,
enhancedSize / 1024,
String.format("%.2f", sizeRatio));
return StageResult.success(String.format("已增强 (%dKB -> %dKB)",
originalSize / 1024,
enhancedSize / 1024));
} catch (Exception e) {
log.error("图像增强失败: {}", e.getMessage(), e);
// 增强失败时返回降级状态,继续使用原图
return StageResult.degraded("增强失败: " + e.getMessage());
}
}
/**
* 检查配置是否有效
*/
private boolean isConfigValid() {
if (enhancerConfig == null) {
return false;
}
String appId = enhancerConfig.getAppId();
String apiKey = enhancerConfig.getApiKey();
String secretKey = enhancerConfig.getSecretKey();
// 检查是否还是TODO占位符
if (appId == null || appId.startsWith("TODO_") ||
apiKey == null || apiKey.startsWith("TODO_") ||
secretKey == null || secretKey.startsWith("TODO_")) {
return false;
}
return true;
}
/**
* 保存MultipartFile到本地文件
*/
private void saveMultipartFileToFile(MultipartFile multipartFile, File targetFile) throws IOException {
try (FileOutputStream fos = new FileOutputStream(targetFile)) {
fos.write(multipartFile.getBytes());
fos.flush();
}
}
/**
* 获取当前配置(用于调试)
*/
public BceEnhancerConfig getEnhancerConfig() {
return enhancerConfig;
}
/**
* 设置配置(用于动态配置)
*/
public void setEnhancerConfig(BceEnhancerConfig enhancerConfig) {
this.enhancerConfig = enhancerConfig;
}
}

View File

@@ -1,9 +1,11 @@
package com.ycwl.basic.image.pipeline.stages;
import com.ycwl.basic.model.Crop;
import com.ycwl.basic.image.pipeline.annotation.StageConfig;
import com.ycwl.basic.image.pipeline.core.AbstractPipelineStage;
import com.ycwl.basic.image.pipeline.core.PhotoProcessContext;
import com.ycwl.basic.image.pipeline.core.StageResult;
import com.ycwl.basic.image.pipeline.enums.StageOptionalMode;
import com.ycwl.basic.utils.ImageUtils;
import lombok.extern.slf4j.Slf4j;
@@ -14,6 +16,11 @@ import java.io.File;
* 检测图片是横图还是竖图,并记录到Context
*/
@Slf4j
@StageConfig(
stageId = "orientation",
optionalMode = StageOptionalMode.UNSUPPORT,
description = "图片方向检测"
)
public class ImageOrientationStage extends AbstractPipelineStage<PhotoProcessContext> {
@Override
@@ -22,7 +29,7 @@ public class ImageOrientationStage extends AbstractPipelineStage<PhotoProcessCon
}
@Override
public boolean shouldExecute(PhotoProcessContext context) {
protected boolean shouldExecuteByBusinessLogic(PhotoProcessContext context) {
return context.isNormalPhoto();
}

View File

@@ -0,0 +1,123 @@
package com.ycwl.basic.image.pipeline.stages;
import com.ycwl.basic.image.pipeline.annotation.StageConfig;
import com.ycwl.basic.image.pipeline.core.AbstractPipelineStage;
import com.ycwl.basic.image.pipeline.core.PhotoProcessContext;
import com.ycwl.basic.image.pipeline.core.StageResult;
import com.ycwl.basic.image.pipeline.enums.StageOptionalMode;
import com.ycwl.basic.utils.ImageUtils;
import lombok.extern.slf4j.Slf4j;
import java.io.File;
/**
* 图像质量检测Stage
* 检测图片质量,如果检测到质量不佳则动态添加ImageEnhanceStage
*
* 此Stage展示了如何在运行时动态添加后续Stage的能力
*/
@Slf4j
@StageConfig(
stageId = "quality_check",
optionalMode = StageOptionalMode.SUPPORT,
description = "图像质量检测",
defaultEnabled = false // 默认不启用
)
public class ImageQualityCheckStage extends AbstractPipelineStage<PhotoProcessContext> {
/**
* 质量阈值:图片尺寸小于此阈值认为质量不佳
* 例如:小于100KB的图片可能需要增强
*/
private static final long QUALITY_THRESHOLD_BYTES = 100 * 1024; // 100KB
/**
* 分辨率阈值:图片分辨率低于此值认为质量不佳
*/
private static final int MIN_WIDTH = 800;
private static final int MIN_HEIGHT = 600;
@Override
public String getName() {
return "ImageQualityCheckStage";
}
@Override
protected boolean shouldExecuteByBusinessLogic(PhotoProcessContext context) {
// 仅对普通照片执行质量检测
return context.isNormalPhoto();
}
@Override
protected StageResult doExecute(PhotoProcessContext context) {
File currentFile = context.getCurrentFile();
if (currentFile == null || !currentFile.exists()) {
return StageResult.failed("当前文件不存在");
}
try {
// 检查文件大小
long fileSize = currentFile.length();
log.debug("图像质量检测: 文件大小={}KB, 阈值={}KB",
fileSize / 1024,
QUALITY_THRESHOLD_BYTES / 1024);
boolean needsEnhancement = false;
String reason = "";
// 检查:文件大小
if (fileSize < QUALITY_THRESHOLD_BYTES) {
needsEnhancement = true;
reason = String.format("文件过小(%dKB < %dKB)",
fileSize / 1024,
QUALITY_THRESHOLD_BYTES / 1024);
}
// TODO: 可以添加更多质量检测维度
// 例如:使用BufferedImage读取图片获取分辨率
// 例如:使用OpenCV进行图片质量评估
// 例如:检查图片的EXIF信息
// 如果需要增强,动态添加ImageEnhanceStage
if (needsEnhancement) {
log.info("检测到图片质量不佳({}), 动态添加ImageEnhanceStage", reason);
ImageEnhanceStage enhanceStage = new ImageEnhanceStage();
// 使用successWithNext返回,动态添加后续Stage
return StageResult.successWithNext(
"质量不佳,添加增强Stage: " + reason,
enhanceStage
);
}
// 质量良好,无需增强
log.info("图像质量良好,无需增强: {}KB", fileSize / 1024);
return StageResult.success("质量良好,无需增强");
} catch (Exception e) {
log.error("图像质量检测失败", e);
// 检测失败时不影响管线,继续执行
return StageResult.degraded("质量检测失败: " + e.getMessage());
}
}
/**
* 示例:也可以根据其他条件动态添加不同的Stage
*/
@SuppressWarnings("unused")
private StageResult checkAndAddMultipleStages(PhotoProcessContext context) {
// 示例:根据不同条件添加多个Stage
// 假设检测到需要多个增强操作
ImageEnhanceStage enhanceStage = new ImageEnhanceStage();
// 其他Stage...
// 可以一次性添加多个Stage
return StageResult.successWithNext(
"检测到需要多重处理",
enhanceStage
// 可以添加更多Stage
);
}
}

View File

@@ -1,8 +1,10 @@
package com.ycwl.basic.image.pipeline.stages;
import com.ycwl.basic.image.pipeline.annotation.StageConfig;
import com.ycwl.basic.image.pipeline.core.AbstractPipelineStage;
import com.ycwl.basic.image.pipeline.core.PhotoProcessContext;
import com.ycwl.basic.image.pipeline.core.StageResult;
import com.ycwl.basic.image.pipeline.enums.StageOptionalMode;
import com.ycwl.basic.utils.ImageUtils;
import lombok.extern.slf4j.Slf4j;
@@ -13,6 +15,12 @@ import java.io.File;
* 为拼图添加白边框并向上偏移
*/
@Slf4j
@StageConfig(
stageId = "puzzle_border",
optionalMode = StageOptionalMode.SUPPORT,
description = "拼图边框处理",
defaultEnabled = true
)
public class PuzzleBorderStage extends AbstractPipelineStage<PhotoProcessContext> {
private static final int BORDER_LR = 20;
@@ -25,7 +33,7 @@ public class PuzzleBorderStage extends AbstractPipelineStage<PhotoProcessContext
}
@Override
public boolean shouldExecute(PhotoProcessContext context) {
protected boolean shouldExecuteByBusinessLogic(PhotoProcessContext context) {
return context.isPuzzle();
}

View File

@@ -1,8 +1,10 @@
package com.ycwl.basic.image.pipeline.stages;
import com.ycwl.basic.image.pipeline.annotation.StageConfig;
import com.ycwl.basic.image.pipeline.core.AbstractPipelineStage;
import com.ycwl.basic.image.pipeline.core.PhotoProcessContext;
import com.ycwl.basic.image.pipeline.core.StageResult;
import com.ycwl.basic.image.pipeline.enums.StageOptionalMode;
import com.ycwl.basic.utils.ImageUtils;
import lombok.extern.slf4j.Slf4j;
@@ -13,6 +15,11 @@ import java.io.File;
* 如果之前旋转过竖图,现在旋转270度恢复为竖图
*/
@Slf4j
@StageConfig(
stageId = "restore",
optionalMode = StageOptionalMode.UNSUPPORT,
description = "恢复竖图方向"
)
public class RestoreOrientationStage extends AbstractPipelineStage<PhotoProcessContext> {
@Override
@@ -21,7 +28,7 @@ public class RestoreOrientationStage extends AbstractPipelineStage<PhotoProcessC
}
@Override
public boolean shouldExecute(PhotoProcessContext context) {
protected boolean shouldExecuteByBusinessLogic(PhotoProcessContext context) {
return context.isNormalPhoto() && context.isNeedRotation();
}

View File

@@ -1,8 +1,10 @@
package com.ycwl.basic.image.pipeline.stages;
import com.ycwl.basic.image.pipeline.annotation.StageConfig;
import com.ycwl.basic.image.pipeline.core.AbstractPipelineStage;
import com.ycwl.basic.image.pipeline.core.PhotoProcessContext;
import com.ycwl.basic.image.pipeline.core.StageResult;
import com.ycwl.basic.image.pipeline.enums.StageOptionalMode;
import com.ycwl.basic.storage.StorageFactory;
import com.ycwl.basic.storage.adapters.IStorageAdapter;
import com.ycwl.basic.storage.enums.StorageAcl;
@@ -15,6 +17,12 @@ import java.io.File;
* 支持降级: 配置的存储 -> 默认assets-ext存储
*/
@Slf4j
@StageConfig(
stageId = "upload",
optionalMode = StageOptionalMode.FORCE_ON,
description = "上传图片到存储",
defaultEnabled = true
)
public class UploadStage extends AbstractPipelineStage<PhotoProcessContext> {
private static final String DEFAULT_STORAGE = "assets-ext";

View File

@@ -4,9 +4,11 @@ import com.ycwl.basic.image.watermark.ImageWatermarkFactory;
import com.ycwl.basic.image.watermark.entity.WatermarkInfo;
import com.ycwl.basic.image.watermark.enums.ImageWatermarkOperatorEnum;
import com.ycwl.basic.image.watermark.operator.IOperator;
import com.ycwl.basic.image.pipeline.annotation.StageConfig;
import com.ycwl.basic.image.pipeline.core.AbstractPipelineStage;
import com.ycwl.basic.image.pipeline.core.PhotoProcessContext;
import com.ycwl.basic.image.pipeline.core.StageResult;
import com.ycwl.basic.image.pipeline.enums.StageOptionalMode;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
@@ -20,6 +22,12 @@ import java.util.List;
* 支持三级降级: 配置的水印类型 -> PRINTER_DEFAULT -> 无水印
*/
@Slf4j
@StageConfig(
stageId = "watermark",
optionalMode = StageOptionalMode.SUPPORT,
description = "水印处理",
defaultEnabled = true
)
public class WatermarkStage extends AbstractPipelineStage<PhotoProcessContext> {
@Override
@@ -28,7 +36,7 @@ public class WatermarkStage extends AbstractPipelineStage<PhotoProcessContext> {
}
@Override
public boolean shouldExecute(PhotoProcessContext context) {
protected boolean shouldExecuteByBusinessLogic(PhotoProcessContext context) {
return context.isNormalPhoto();
}