You've already forked FrameTour-BE
feat(image): 实现图像增强与质量检测功能
- 新增ImageEnhancerFactory工厂类,支持创建不同类型的图像增强器 - 添加图像清晰度增强和超分辨率两种增强模式 - 实现ImageEnhanceStage图像增强处理阶段 - 新增ImageQualityCheckStage图像质量检测阶段 - 支持根据图片质量动态添加图像增强处理 - 完善Stage配置注解和可选性控制机制 - 优化Pipeline执行流程,支持动态插入Stage - 增加Stage执行计数和循环依赖防护机制 - 改进StageResult结构,支持携带后续Stage列表 - 统一抽象Stage的执行条件判断逻辑
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为拼图类型
|
||||
*/
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user