feat(image): 实现源图片超分辨率增强流水线

- 引入Pipeline模式重构图片处理流程
- 新增SourcePhotoUpdateStage用于上传并更新源图片URL
- 扩展PhotoProcessContext支持超分场景配置
- 增加SOURCE_PHOTO_SUPER_RESOLUTION枚举值
- 修改各Stage判断逻辑适配新的图片类型系统
- 调整SourceService接口支持File类型参数
- 优化超分处理日志记录和异常处理机制
This commit is contained in:
2025-11-25 19:17:55 +08:00
parent bcebe5defe
commit 7b18d7c2af
17 changed files with 491 additions and 60 deletions

View File

@@ -4,6 +4,13 @@ import cn.hutool.http.HttpUtil;
import com.ycwl.basic.annotation.IgnoreToken;
import com.ycwl.basic.image.enhancer.adapter.BceImageEnhancer;
import com.ycwl.basic.image.enhancer.entity.BceEnhancerConfig;
import com.ycwl.basic.image.pipeline.core.Pipeline;
import com.ycwl.basic.image.pipeline.core.PipelineBuilder;
import com.ycwl.basic.image.pipeline.core.PhotoProcessContext;
import com.ycwl.basic.image.pipeline.stages.DownloadStage;
import com.ycwl.basic.image.pipeline.stages.ImageEnhanceStage;
import com.ycwl.basic.image.pipeline.stages.SourcePhotoUpdateStage;
import com.ycwl.basic.image.pipeline.stages.CleanupStage;
import com.ycwl.basic.mapper.AioDeviceMapper;
import com.ycwl.basic.mapper.MemberMapper;
import com.ycwl.basic.model.aio.entity.AioDeviceBannerEntity;
@@ -41,7 +48,9 @@ import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@Slf4j
@@ -129,27 +138,39 @@ public class AioDeviceController {
redisTemplate.opsForValue().set("aio:faceId:"+resp.getFaceId().toString()+":pass", "1", 1, TimeUnit.DAYS);
return;
}
log.info("超分开始!");
log.info("超分开始!共{}张图片待处理", sourcePhotoList.size());
sourcePhotoList.forEach(photo -> {
if (StringUtils.contains(photo.getUrl(), "_q_")) {
log.debug("跳过已增强的图片: {}", photo.getUrl());
return;
}
try {
File dstFile = new File(photo.getGoodsId()+".jpg");
long fileSize = HttpUtil.downloadFile(photo.getUrl(), dstFile);
log.info("超分开始:{}", fileSize);
BceImageEnhancer enhancer = getEnhancer();
MultipartFile enhancedFile = enhancer.enhance(dstFile.getName());
log.info("超分结束:{}", photo.getUrl());
String url = sourceService.uploadAndUpdateUrl(photo.getGoodsId(), enhancedFile);
log.info("上传结束:->{}", url);
// 创建超分Pipeline
Pipeline<PhotoProcessContext> superResolutionPipeline = createSuperResolutionPipeline(photo.getGoodsId());
// 使用静态工厂方法创建Context
PhotoProcessContext context = PhotoProcessContext.forSuperResolution(
photo.getGoodsId(), photo.getUrl(), photo.getScenicId()
);
// 启用图像增强Stage
Map<String, Boolean> stageConfig = new HashMap<>();
stageConfig.put("image_enhance", true);
context.loadStageConfig(null, stageConfig);
// 执行Pipeline
boolean success = superResolutionPipeline.execute(context);
if (success) {
log.info("超分成功: {} -> {}", photo.getUrl(), context.getResultUrl());
} else {
log.error("超分失败: {}", photo.getGoodsId());
}
} catch (Exception e) {
log.error("超分失败:{}", photo.getGoodsId(), e);
} finally {
File _file = new File(photo.getGoodsId()+".jpg");
if (_file.exists()) {
_file.delete();
}
}
});
redisTemplate.opsForValue().set("aio:faceId:"+sourcePhotoList.getFirst().getFaceId().toString()+":pass", "1", 1, TimeUnit.DAYS);
@@ -206,6 +227,28 @@ public class AioDeviceController {
return ApiResponse.success(orderService.queryOrder(orderId));
}
/**
* 创建源图片超分辨率增强Pipeline
*
* @param sourceId 源图片ID
* @return 超分Pipeline
*/
private Pipeline<PhotoProcessContext> createSuperResolutionPipeline(Long sourceId) {
// 创建带有百度云配置的ImageEnhanceStage
BceEnhancerConfig config = new BceEnhancerConfig();
config.setQps(1);
config.setAppId("119554288");
config.setApiKey("OX6QoijgKio3eVtA0PiUVf7f");
config.setSecretKey("dYatXReVriPeiktTjUblhfubpcmYfuMk");
return new PipelineBuilder<PhotoProcessContext>("SourcePhotoSuperResolutionPipeline")
.addStage(new DownloadStage()) // 1. 下载图片
.addStage(new ImageEnhanceStage(config)) // 2. 图像增强(超分)
.addStage(new SourcePhotoUpdateStage(sourceService, sourceId)) // 3. 上传并更新数据库
.addStage(new CleanupStage()) // 4. 清理临时文件
.build();
}
private BceImageEnhancer getEnhancer() {
BceImageEnhancer enhancer = new BceImageEnhancer();
BceEnhancerConfig config = new BceEnhancerConfig();

View File

@@ -0,0 +1,45 @@
package com.ycwl.basic.image.pipeline.annotation;
import com.ycwl.basic.image.pipeline.enums.StageOptionalMode;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Stage配置注解
* 用于声明Stage的元数据和可选性控制信息
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface StageConfig {
/**
* Stage的唯一标识
* 用于外部配置引用该Stage
* 例如: "watermark", "download", "upload"
*/
String stageId();
/**
* 可选性模式
* 默认为UNSUPPORT(不支持外部控制)
*/
StageOptionalMode optionalMode() default StageOptionalMode.UNSUPPORT;
/**
* Stage描述信息
* 用于文档和日志说明
*/
String description() default "";
/**
* 默认是否启用
* 仅当optionalMode=SUPPORT时有效
* 当外部配置未明确指定时,使用此默认值
*/
boolean defaultEnabled() default true;
}

View File

@@ -3,6 +3,7 @@ package com.ycwl.basic.image.pipeline.core;
import com.ycwl.basic.image.watermark.entity.WatermarkInfo;
import com.ycwl.basic.image.watermark.enums.ImageWatermarkOperatorEnum;
import com.ycwl.basic.image.pipeline.enums.ImageSource;
import com.ycwl.basic.image.pipeline.enums.ImageType;
import com.ycwl.basic.image.pipeline.enums.PipelineScene;
import com.ycwl.basic.integration.common.manager.ScenicConfigManager;
import com.ycwl.basic.model.Crop;
@@ -15,6 +16,8 @@ import lombok.Setter;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.function.Consumer;
/**
* 图片处理管线上下文
@@ -24,10 +27,43 @@ import java.util.Map;
@Setter
public class PhotoProcessContext {
private final PrinterOrderItem orderItem;
private final TempFileManager tempFileManager;
// ==================== 核心字段(构造时必填)====================
/**
* 处理过程唯一标识
* 用于 TempFileManager 创建隔离的临时文件目录
*/
private final String processId;
/**
* 原图 URL
*/
private final String originalUrl;
/**
* 景区 ID
*/
private final Long scenicId;
/**
* 临时文件管理器
*/
private final TempFileManager tempFileManager;
// ==================== 图片元信息 ====================
/**
* 图片类型
*/
private ImageType imageType = ImageType.NORMAL_PHOTO;
/**
* 裁剪/旋转信息
*/
private Crop crop;
// ==================== 管线配置 ====================
/**
* 景区配置管理器,用于获取景区相关配置
*/
@@ -50,6 +86,8 @@ public class PhotoProcessContext {
*/
private Map<String, Boolean> stageEnabledMap = new HashMap<>();
// ==================== 处理过程状态 ====================
private File originalFile;
private File processedFile;
private boolean isLandscape = true;
@@ -62,14 +100,78 @@ public class PhotoProcessContext {
private String dateFormat;
private File qrcodeFile;
private Integer offsetLeft;
private Crop crop;
public PhotoProcessContext(PrinterOrderItem orderItem, Long scenicId) {
this.orderItem = orderItem;
this.scenicId = scenicId;
this.tempFileManager = new TempFileManager(orderItem.getId().toString());
// ==================== 回调 ====================
/**
* 结果 URL 回调
* 用于在 setResultUrl 时通知外部更新相关数据
*/
private Consumer<String> resultUrlCallback;
// ==================== 构造函数(私有)====================
private PhotoProcessContext(Builder builder) {
this.processId = builder.processId;
this.originalUrl = builder.originalUrl;
this.scenicId = builder.scenicId;
this.tempFileManager = new TempFileManager(processId);
this.imageType = builder.imageType;
this.crop = builder.crop;
this.scene = builder.scene;
this.source = builder.source;
this.resultUrlCallback = builder.resultUrlCallback;
}
// ==================== 静态工厂方法 ====================
/**
* 从 PrinterOrderItem 创建 Context(打印场景兼容方法)
*
* @param orderItem 打印订单项
* @param scenicId 景区ID
* @return PhotoProcessContext
*/
public static PhotoProcessContext fromPrinterOrderItem(PrinterOrderItem orderItem, Long scenicId) {
return PhotoProcessContext.builder()
.processId(orderItem.getId().toString())
.originalUrl(orderItem.getCropUrl())
.scenicId(scenicId)
.imageType(ImageType.fromSourceId(orderItem.getSourceId()))
.crop(orderItem.getCrop())
.resultUrlCallback(url -> orderItem.setCropUrl(url))
.build();
}
/**
* 为超分辨率场景创建 Context
*
* @param itemId 项目ID(用于临时文件隔离)
* @param url 原图URL
* @param scenicId 景区ID
* @return PhotoProcessContext
*/
public static PhotoProcessContext forSuperResolution(Long itemId, String url, Long scenicId) {
return PhotoProcessContext.builder()
.processId(itemId.toString())
.originalUrl(url)
.scenicId(scenicId)
.imageType(ImageType.NORMAL_PHOTO)
.source(ImageSource.IPC)
.scene(PipelineScene.SOURCE_PHOTO_SUPER_RESOLUTION)
.build();
}
/**
* 获取 Builder
*/
public static Builder builder() {
return new Builder();
}
// ==================== 业务方法 ====================
/**
* 从景区配置和请求参数中加载Stage开关配置
*
@@ -94,33 +196,14 @@ public class PhotoProcessContext {
return stageEnabledMap.getOrDefault(stageId, defaultEnabled);
}
/**
* 是否为拼图类型
*/
public boolean isPuzzle() {
return orderItem.getSourceId() != null && orderItem.getSourceId() == 0;
}
/**
* 是否为普通照片
*/
public boolean isNormalPhoto() {
return orderItem.getSourceId() != null && orderItem.getSourceId() > 0;
}
/**
* 获取原图URL
*/
public String getOriginalUrl() {
return orderItem.getCropUrl();
}
/**
* 设置最终处理结果URL
*/
public void setResultUrl(String url) {
this.resultUrl = url;
this.orderItem.setCropUrl(url);
if (resultUrlCallback != null) {
resultUrlCallback.accept(url);
}
}
/**
@@ -145,4 +228,72 @@ public class PhotoProcessContext {
public void cleanup() {
tempFileManager.cleanup();
}
// ==================== Builder ====================
public static class Builder {
private String processId;
private String originalUrl;
private Long scenicId;
private ImageType imageType = ImageType.NORMAL_PHOTO;
private Crop crop;
private PipelineScene scene;
private ImageSource source;
private Consumer<String> resultUrlCallback;
public Builder processId(String processId) {
this.processId = processId;
return this;
}
public Builder originalUrl(String originalUrl) {
this.originalUrl = originalUrl;
return this;
}
public Builder scenicId(Long scenicId) {
this.scenicId = scenicId;
return this;
}
public Builder imageType(ImageType imageType) {
this.imageType = imageType;
return this;
}
public Builder crop(Crop crop) {
this.crop = crop;
return this;
}
public Builder scene(PipelineScene scene) {
this.scene = scene;
return this;
}
public Builder source(ImageSource source) {
this.source = source;
return this;
}
public Builder resultUrlCallback(Consumer<String> callback) {
this.resultUrlCallback = callback;
return this;
}
public PhotoProcessContext build() {
// 参数校验
if (originalUrl == null || originalUrl.isBlank()) {
throw new IllegalArgumentException("originalUrl is required");
}
if (scenicId == null) {
throw new IllegalArgumentException("scenicId is required");
}
// processId 可以自动生成
if (processId == null || processId.isBlank()) {
processId = UUID.randomUUID().toString();
}
return new PhotoProcessContext(this);
}
}
}

View File

@@ -0,0 +1,59 @@
package com.ycwl.basic.image.pipeline.enums;
/**
* 图片类型枚举
* 用于区分管线处理的图片类型,替代通过 sourceId 判断的逻辑
*/
public enum ImageType {
/**
* 普通照片
* 对应原 sourceId > 0 的情况(IPC设备拍摄)
*/
NORMAL_PHOTO("normal", "普通照片"),
/**
* 拼图
* 对应原 sourceId == 0 的情况
*/
PUZZLE("puzzle", "拼图"),
/**
* 手机上传
* 对应原 sourceId == null 的情况
*/
MOBILE_UPLOAD("mobile", "手机上传");
private final String code;
private final String description;
ImageType(String code, String description) {
this.code = code;
this.description = description;
}
public String getCode() {
return code;
}
public String getDescription() {
return description;
}
/**
* 从 sourceId 推断图片类型
* 用于兼容现有数据结构
*
* @param sourceId 源ID(null=手机上传, 0=拼图, >0=普通照片)
* @return 对应的图片类型
*/
public static ImageType fromSourceId(Long sourceId) {
if (sourceId == null) {
return MOBILE_UPLOAD;
} else if (sourceId == 0) {
return PUZZLE;
} else {
return NORMAL_PHOTO;
}
}
}

View File

@@ -16,7 +16,13 @@ public enum PipelineScene {
* 图片增强场景
* 包括图片美化、滤镜处理等
*/
IMAGE_ENHANCE("image_enhance", "图片增强");
IMAGE_ENHANCE("image_enhance", "图片增强"),
/**
* 源图片超分辨率增强场景
* IPC设备拍摄的源图片进行质量提升
*/
SOURCE_PHOTO_SUPER_RESOLUTION("source_photo_sr", "源图片超分辨率增强");
private final String code;
private final String description;

View File

@@ -0,0 +1,35 @@
package com.ycwl.basic.image.pipeline.enums;
import lombok.Getter;
/**
* Stage可选性模式枚举
* 定义Stage是否支持外部配置控制
*/
@Getter
public enum StageOptionalMode {
/**
* 不支持外部控制
* Stage的执行完全由代码中的业务逻辑决定
*/
UNSUPPORT("不支持外部控制"),
/**
* 支持外部控制
* Stage可以通过景区配置或请求参数进行开启/关闭
*/
SUPPORT("支持外部控制"),
/**
* 强制开启
* Stage必须执行,不允许外部配置关闭
*/
FORCE_ON("强制开启");
private final String description;
StageOptionalMode(String description) {
this.description = description;
}
}

View File

@@ -4,6 +4,7 @@ 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.ImageType;
import com.ycwl.basic.image.pipeline.enums.StageOptionalMode;
import com.ycwl.basic.utils.ImageUtils;
import lombok.extern.slf4j.Slf4j;
@@ -31,7 +32,7 @@ public class ConditionalRotateStage extends AbstractPipelineStage<PhotoProcessCo
@Override
protected boolean shouldExecuteByBusinessLogic(PhotoProcessContext context) {
return context.isNormalPhoto() && !context.isLandscape();
return context.getImageType() == ImageType.NORMAL_PHOTO && !context.isLandscape();
}
@Override

View File

@@ -5,6 +5,7 @@ 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.ImageType;
import com.ycwl.basic.image.pipeline.enums.StageOptionalMode;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
@@ -38,7 +39,7 @@ public class DownloadStage extends AbstractPipelineStage<PhotoProcessContext> {
try {
String fileExtension = determineFileExtension(context);
String filePrefix = context.isPuzzle() ? "puzzle" : "print";
String filePrefix = context.getImageType() == ImageType.PUZZLE ? "puzzle" : "print";
File downloadFile = context.getTempFileManager()
.createTempFile(filePrefix, fileExtension);
@@ -63,7 +64,7 @@ public class DownloadStage extends AbstractPipelineStage<PhotoProcessContext> {
}
private String determineFileExtension(PhotoProcessContext context) {
if (context.isPuzzle()) {
if (context.getImageType() == ImageType.PUZZLE) {
return ".png";
}
String url = context.getOriginalUrl();

View File

@@ -5,6 +5,7 @@ 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.ImageType;
import com.ycwl.basic.image.pipeline.enums.StageOptionalMode;
import com.ycwl.basic.utils.ImageUtils;
import lombok.extern.slf4j.Slf4j;
@@ -30,7 +31,7 @@ public class ImageOrientationStage extends AbstractPipelineStage<PhotoProcessCon
@Override
protected boolean shouldExecuteByBusinessLogic(PhotoProcessContext context) {
return context.isNormalPhoto();
return context.getImageType() == ImageType.NORMAL_PHOTO;
}
@Override

View File

@@ -4,6 +4,7 @@ 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.ImageType;
import com.ycwl.basic.image.pipeline.enums.StageOptionalMode;
import com.ycwl.basic.utils.ImageUtils;
import lombok.extern.slf4j.Slf4j;
@@ -45,7 +46,7 @@ public class ImageQualityCheckStage extends AbstractPipelineStage<PhotoProcessCo
@Override
protected boolean shouldExecuteByBusinessLogic(PhotoProcessContext context) {
// 仅对普通照片执行质量检测
return context.isNormalPhoto();
return context.getImageType() == ImageType.NORMAL_PHOTO;
}
@Override

View File

@@ -4,6 +4,7 @@ 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.ImageType;
import com.ycwl.basic.image.pipeline.enums.StageOptionalMode;
import com.ycwl.basic.utils.ImageUtils;
import lombok.extern.slf4j.Slf4j;
@@ -34,7 +35,7 @@ public class PuzzleBorderStage extends AbstractPipelineStage<PhotoProcessContext
@Override
protected boolean shouldExecuteByBusinessLogic(PhotoProcessContext context) {
return context.isPuzzle();
return context.getImageType() == ImageType.PUZZLE;
}
@Override

View File

@@ -4,6 +4,7 @@ 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.ImageType;
import com.ycwl.basic.image.pipeline.enums.StageOptionalMode;
import com.ycwl.basic.utils.ImageUtils;
import lombok.extern.slf4j.Slf4j;
@@ -29,7 +30,7 @@ public class RestoreOrientationStage extends AbstractPipelineStage<PhotoProcessC
@Override
protected boolean shouldExecuteByBusinessLogic(PhotoProcessContext context) {
return context.isNormalPhoto() && context.isNeedRotation();
return context.getImageType() == ImageType.NORMAL_PHOTO && context.isNeedRotation();
}
@Override

View File

@@ -0,0 +1,82 @@
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.service.pc.SourceService;
import lombok.extern.slf4j.Slf4j;
import java.io.File;
/**
* 源图片上传和更新Stage
* 专门用于将增强后的图片上传并更新数据库中的URL
*
* 与UploadStage的区别:
* - UploadStage: 通用上传,仅上传到存储并设置Context.resultUrl
* - SourcePhotoUpdateStage: 专门用于源图片,上传+更新数据库source表的URL字段
*/
@Slf4j
@StageConfig(
stageId = "source_photo_update",
optionalMode = StageOptionalMode.FORCE_ON,
description = "源图片上传和数据库更新",
defaultEnabled = true
)
public class SourcePhotoUpdateStage extends AbstractPipelineStage<PhotoProcessContext> {
private final SourceService sourceService;
private final Long sourceId;
/**
* 构造函数
*
* @param sourceService 源图片服务
* @param sourceId 源图片ID
*/
public SourcePhotoUpdateStage(SourceService sourceService, Long sourceId) {
this.sourceService = sourceService;
this.sourceId = sourceId;
}
@Override
public String getName() {
return "SourcePhotoUpdateStage";
}
@Override
protected StageResult doExecute(PhotoProcessContext context) {
File fileToUpload = context.getCurrentFile();
if (fileToUpload == null || !fileToUpload.exists()) {
return StageResult.failed("没有可上传的文件");
}
if (sourceService == null) {
return StageResult.failed("SourceService未注入");
}
if (sourceId == null) {
return StageResult.failed("SourceId为空");
}
try {
// 调用SourceService上传并更新URL
String uploadedUrl = sourceService.uploadAndUpdateUrl(sourceId, fileToUpload);
// 设置结果URL到Context
context.setResultUrl(uploadedUrl);
log.info("源图片上传并更新成功: sourceId={}, url={}", sourceId, uploadedUrl);
return StageResult.success("已上传并更新: " + uploadedUrl);
} catch (Exception e) {
log.error("源图片上传并更新失败: sourceId={}", sourceId, e);
return StageResult.failed("上传并更新失败: " + e.getMessage(), e);
}
}
}

View File

@@ -8,6 +8,7 @@ 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.ImageType;
import com.ycwl.basic.image.pipeline.enums.StageOptionalMode;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
@@ -37,7 +38,7 @@ public class WatermarkStage extends AbstractPipelineStage<PhotoProcessContext> {
@Override
protected boolean shouldExecuteByBusinessLogic(PhotoProcessContext context) {
return context.isNormalPhoto();
return context.getImageType() == ImageType.NORMAL_PHOTO;
}
@Override

View File

@@ -6,6 +6,7 @@ import com.ycwl.basic.model.pc.source.req.SourceReqQuery;
import com.ycwl.basic.model.pc.source.resp.SourceRespVO;
import com.ycwl.basic.utils.ApiResponse;
import java.io.File;
import java.util.List;
/**
@@ -21,5 +22,5 @@ public interface SourceService {
ApiResponse<Integer> update(SourceEntity source);
ApiResponse cutVideo(Long id);
String uploadAndUpdateUrl(Long id, org.springframework.web.multipart.MultipartFile file);
String uploadAndUpdateUrl(Long id, File file);
}

View File

@@ -25,6 +25,7 @@ import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import com.ycwl.basic.storage.StorageFactory;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collections;
@@ -216,14 +217,14 @@ public class SourceServiceImpl implements SourceService {
}
@Override
public String uploadAndUpdateUrl(Long id, MultipartFile file) {
public String uploadAndUpdateUrl(Long id, File file) {
SourceRespVO source = sourceMapper.getById(id);
if (source == null) {
throw new BaseException("该素材不存在");
}
try {
IStorageAdapter adapter = scenicService.getScenicStorageAdapter(source.getScenicId());
String uploadedUrl = adapter.uploadFile(file, PHOTO_PATH, id + "_q_.jpg");
String uploadedUrl = adapter.uploadFile("image/jpeg", file, PHOTO_PATH, id + "_q_.jpg");
SourceEntity sourceUpd = new SourceEntity();
sourceUpd.setId(id);

View File

@@ -8,6 +8,7 @@ import com.ycwl.basic.image.pipeline.core.PhotoProcessContext;
import com.ycwl.basic.image.pipeline.core.Pipeline;
import com.ycwl.basic.image.pipeline.core.PipelineBuilder;
import com.ycwl.basic.image.pipeline.enums.ImageSource;
import com.ycwl.basic.image.pipeline.enums.ImageType;
import com.ycwl.basic.image.pipeline.enums.PipelineScene;
import com.ycwl.basic.image.pipeline.stages.CleanupStage;
import com.ycwl.basic.image.pipeline.stages.ConditionalRotateStage;
@@ -776,7 +777,7 @@ public class PrinterServiceImpl implements PrinterService {
private String processPhotoWithPipeline(MemberPrintResp item, Long scenicId, File qrCodeFile) {
PrinterOrderItem orderItem = PrinterOrderItem.fromMemberPrintResp(item);
PhotoProcessContext context = new PhotoProcessContext(orderItem, scenicId);
PhotoProcessContext context = PhotoProcessContext.fromPrinterOrderItem(orderItem, scenicId);
context.setQrcodeFile(qrCodeFile);
try {
@@ -799,13 +800,13 @@ public class PrinterServiceImpl implements PrinterService {
context.setSource(ImageSource.UNKNOWN);
}
if (context.isNormalPhoto()) {
if (context.getImageType() == ImageType.NORMAL_PHOTO) {
prepareNormalPhotoContext(context);
} else if (context.isPuzzle()) {
} else if (context.getImageType() == ImageType.PUZZLE) {
prepareStorageAdapter(context);
}
Pipeline<PhotoProcessContext> pipeline = context.isPuzzle()
Pipeline<PhotoProcessContext> pipeline = context.getImageType() == ImageType.PUZZLE
? createPuzzlePipeline()
: createNormalPhotoPipeline();
@@ -814,7 +815,7 @@ public class PrinterServiceImpl implements PrinterService {
if (success && context.getResultUrl() != null) {
log.info("照片处理成功: photoId={}, type={}, url={}",
item.getId(),
context.isPuzzle() ? "拼图" : "普通照片",
context.getImageType() == ImageType.PUZZLE ? "拼图" : "普通照片",
context.getResultUrl());
return context.getResultUrl();
} else {