diff --git a/src/main/java/com/ycwl/basic/puzzle/entity/PuzzleGenerationRecordEntity.java b/src/main/java/com/ycwl/basic/puzzle/entity/PuzzleGenerationRecordEntity.java index 56f6e07c..da3dc989 100644 --- a/src/main/java/com/ycwl/basic/puzzle/entity/PuzzleGenerationRecordEntity.java +++ b/src/main/java/com/ycwl/basic/puzzle/entity/PuzzleGenerationRecordEntity.java @@ -76,6 +76,12 @@ public class PuzzleGenerationRecordEntity { @TableField("result_image_url") private String resultImageUrl; + /** + * 原始图片URL(未裁切的图片,用于打印) + */ + @TableField("original_image_url") + private String originalImageUrl; + /** * 文件大小(字节) */ diff --git a/src/main/java/com/ycwl/basic/puzzle/entity/PuzzleTemplateEntity.java b/src/main/java/com/ycwl/basic/puzzle/entity/PuzzleTemplateEntity.java index 08906324..7dcc1d49 100644 --- a/src/main/java/com/ycwl/basic/puzzle/entity/PuzzleTemplateEntity.java +++ b/src/main/java/com/ycwl/basic/puzzle/entity/PuzzleTemplateEntity.java @@ -97,6 +97,24 @@ public class PuzzleTemplateEntity { @TableField("scenic_id") private Long scenicId; + /** + * 自动添加到打印队列:1-开启 0-关闭 + */ + @TableField("auto_add_print") + private Integer autoAddPrint; + + /** + * 是否可以打印:1-可以 0-不可以 + */ + @TableField("can_print") + private Integer canPrint; + + /** + * 用户查看区域(裁切区域),格式:x,y,w,h + */ + @TableField("user_area") + private String userArea; + /** * 创建时间 */ diff --git a/src/main/java/com/ycwl/basic/puzzle/mapper/PuzzleGenerationRecordMapper.java b/src/main/java/com/ycwl/basic/puzzle/mapper/PuzzleGenerationRecordMapper.java index 3a18b1e5..f5ec8bb7 100644 --- a/src/main/java/com/ycwl/basic/puzzle/mapper/PuzzleGenerationRecordMapper.java +++ b/src/main/java/com/ycwl/basic/puzzle/mapper/PuzzleGenerationRecordMapper.java @@ -52,6 +52,7 @@ public interface PuzzleGenerationRecordMapper { */ int updateSuccess(@Param("id") Long id, @Param("resultImageUrl") String resultImageUrl, + @Param("originalImageUrl") String originalImageUrl, @Param("resultFileSize") Long resultFileSize, @Param("resultWidth") Integer resultWidth, @Param("resultHeight") Integer resultHeight, diff --git a/src/main/java/com/ycwl/basic/puzzle/service/impl/PuzzleGenerateServiceImpl.java b/src/main/java/com/ycwl/basic/puzzle/service/impl/PuzzleGenerateServiceImpl.java index 4f06bb94..c840a2b9 100644 --- a/src/main/java/com/ycwl/basic/puzzle/service/impl/PuzzleGenerateServiceImpl.java +++ b/src/main/java/com/ycwl/basic/puzzle/service/impl/PuzzleGenerateServiceImpl.java @@ -17,6 +17,7 @@ import com.ycwl.basic.puzzle.service.IPuzzleGenerateService; import com.ycwl.basic.puzzle.util.PuzzleDuplicationDetector; import com.ycwl.basic.puzzle.util.PuzzleImageRenderer; import com.ycwl.basic.repository.ScenicRepository; +import com.ycwl.basic.service.printer.PrinterService; import com.ycwl.basic.storage.StorageFactory; import com.ycwl.basic.utils.WxMpUtil; import lombok.RequiredArgsConstructor; @@ -60,6 +61,8 @@ public class PuzzleGenerateServiceImpl implements IPuzzleGenerateService { private final ScenicRepository scenicRepository; @Lazy private final PuzzleDuplicationDetector duplicationDetector; + @Lazy + private final PrinterService printerService; @Override public PuzzleGenerateResponse generate(PuzzleGenerateRequest request) { @@ -136,30 +139,64 @@ public class PuzzleGenerateServiceImpl implements IPuzzleGenerateService { // 9. 渲染图片 BufferedImage resultImage = imageRenderer.render(template, elements, finalDynamicData); - // 10. 上传到OSS - String imageUrl = uploadImage(resultImage, template.getCode(), request.getOutputFormat(), request.getQuality()); - log.info("图片上传成功: url={}", imageUrl); + // 10. 上传原图到OSS(未裁切) + String originalImageUrl = uploadImage(resultImage, template.getCode(), request.getOutputFormat(), request.getQuality()); + log.info("原图上传成功: url={}", originalImageUrl); - // 11. 更新记录为成功 + // 11. 处理用户区域裁切 + String finalImageUrl = originalImageUrl; // 默认使用原图 + BufferedImage finalImage = resultImage; + + if (StrUtil.isNotBlank(template.getUserArea())) { + try { + BufferedImage croppedImage = cropImage(resultImage, template.getUserArea()); + finalImageUrl = uploadImage(croppedImage, template.getCode() + "_cropped", request.getOutputFormat(), request.getQuality()); + finalImage = croppedImage; + log.info("裁切后图片上传成功: userArea={}, url={}", template.getUserArea(), finalImageUrl); + } catch (Exception e) { + log.error("图片裁切失败,使用原图: userArea={}", template.getUserArea(), e); + // 裁切失败时使用原图 + } + } + + // 12. 更新记录为成功 long duration = (int) (System.currentTimeMillis() - startTime); - long fileSize = estimateFileSize(resultImage, request.getOutputFormat()); + long fileSize = estimateFileSize(finalImage, request.getOutputFormat()); recordMapper.updateSuccess( record.getId(), - imageUrl, + finalImageUrl, + originalImageUrl, fileSize, - resultImage.getWidth(), - resultImage.getHeight(), + finalImage.getWidth(), + finalImage.getHeight(), (int) duration ); - log.info("拼图生成成功(新生成): recordId={}, imageUrl={}, duration={}ms", - record.getId(), imageUrl, duration); + log.info("拼图生成成功(新生成): recordId={}, originalUrl={}, finalUrl={}, duration={}ms", + record.getId(), originalImageUrl, finalImageUrl, duration); + + // 13. 检查是否自动添加到打印队列 + if (template.getAutoAddPrint() != null && template.getAutoAddPrint() == 1) { + try { + Integer printRecordId = printerService.addUserPhotoFromPuzzle( + request.getUserId(), + resolvedScenicId, + request.getFaceId(), + originalImageUrl, // 使用原图URL添加到打印队列 + record.getId() + ); + log.info("自动添加到打印队列成功: recordId={}, printRecordId={}", record.getId(), printRecordId); + } catch (Exception e) { + log.error("自动添加到打印队列失败: recordId={}", record.getId(), e); + // 添加失败不影响拼图生成流程 + } + } return PuzzleGenerateResponse.success( - imageUrl, + finalImageUrl, fileSize, - resultImage.getWidth(), - resultImage.getHeight(), + finalImage.getWidth(), + finalImage.getHeight(), (int) duration, record.getId(), false, // isDuplicate=false @@ -405,4 +442,43 @@ public class PuzzleGenerateServiceImpl implements IPuzzleGenerateService { return templateScenicId; } + + /** + * 裁切图片 + * @param image 原图 + * @param userArea 裁切区域,格式:x,y,w,h + * @return 裁切后的图片 + */ + private BufferedImage cropImage(BufferedImage image, String userArea) { + if (StrUtil.isBlank(userArea)) { + return image; + } + + try { + String[] parts = userArea.split(","); + if (parts.length != 4) { + throw new IllegalArgumentException("userArea格式错误,应为:x,y,w,h"); + } + + int x = Integer.parseInt(parts[0].trim()); + int y = Integer.parseInt(parts[1].trim()); + int w = Integer.parseInt(parts[2].trim()); + int h = Integer.parseInt(parts[3].trim()); + + // 边界检查 + if (x < 0 || y < 0 || w <= 0 || h <= 0) { + throw new IllegalArgumentException("裁切区域参数必须为正数"); + } + + if (x + w > image.getWidth() || y + h > image.getHeight()) { + throw new IllegalArgumentException("裁切区域超出图片边界"); + } + + // 执行裁切 + return image.getSubimage(x, y, w, h); + + } catch (NumberFormatException e) { + throw new IllegalArgumentException("userArea格式错误,参数必须为数字", e); + } + } } diff --git a/src/main/resources/mapper/PuzzleGenerationRecordMapper.xml b/src/main/resources/mapper/PuzzleGenerationRecordMapper.xml index 0b2f7c79..00949f97 100644 --- a/src/main/resources/mapper/PuzzleGenerationRecordMapper.xml +++ b/src/main/resources/mapper/PuzzleGenerationRecordMapper.xml @@ -14,6 +14,7 @@ + @@ -32,7 +33,7 @@ id, template_id, template_code, user_id, face_id, business_type, generation_params, content_hash, - result_image_url, result_file_size, result_width, result_height, + result_image_url, original_image_url, result_file_size, result_width, result_height, status, error_message, generation_duration, retry_count, scenic_id, client_ip, user_agent, create_time, update_time @@ -77,13 +78,13 @@ INSERT INTO puzzle_generation_record ( template_id, template_code, user_id, face_id, business_type, generation_params, content_hash, - result_image_url, result_file_size, result_width, result_height, + result_image_url, original_image_url, result_file_size, result_width, result_height, status, error_message, generation_duration, retry_count, scenic_id, client_ip, user_agent, create_time, update_time ) VALUES ( #{templateId}, #{templateCode}, #{userId}, #{faceId}, #{businessType}, #{generationParams}, #{contentHash}, - #{resultImageUrl}, #{resultFileSize}, #{resultWidth}, #{resultHeight}, + #{resultImageUrl}, #{originalImageUrl}, #{resultFileSize}, #{resultWidth}, #{resultHeight}, #{status}, #{errorMessage}, #{generationDuration}, #{retryCount}, #{scenicId}, #{clientIp}, #{userAgent}, NOW(), NOW() ) @@ -94,6 +95,7 @@ UPDATE puzzle_generation_record result_image_url = #{resultImageUrl}, + original_image_url = #{originalImageUrl}, result_file_size = #{resultFileSize}, result_width = #{resultWidth}, result_height = #{resultHeight}, @@ -111,6 +113,7 @@ UPDATE puzzle_generation_record SET status = 1, result_image_url = #{resultImageUrl}, + original_image_url = #{originalImageUrl}, result_file_size = #{resultFileSize}, result_width = #{resultWidth}, result_height = #{resultHeight}, diff --git a/src/main/resources/mapper/PuzzleTemplateMapper.xml b/src/main/resources/mapper/PuzzleTemplateMapper.xml index 03a0bcf2..acff2264 100644 --- a/src/main/resources/mapper/PuzzleTemplateMapper.xml +++ b/src/main/resources/mapper/PuzzleTemplateMapper.xml @@ -18,6 +18,9 @@ + + + @@ -27,7 +30,8 @@ id, name, code, canvas_width, canvas_height, background_type, background_color, - background_image, cover_image, description, category, status, scenic_id, create_time, update_time, deleted, deleted_at + background_image, cover_image, description, category, status, scenic_id, + auto_add_print, can_print, user_area, create_time, update_time, deleted, deleted_at @@ -68,10 +72,12 @@ useGeneratedKeys="true" keyProperty="id"> INSERT INTO puzzle_template ( name, code, canvas_width, canvas_height, background_type, background_color, - background_image, cover_image, description, category, status, scenic_id, create_time, update_time, deleted + background_image, cover_image, description, category, status, scenic_id, + auto_add_print, can_print, user_area, create_time, update_time, deleted ) VALUES ( #{name}, #{code}, #{canvasWidth}, #{canvasHeight}, #{backgroundType}, #{backgroundColor}, - #{backgroundImage}, #{coverImage}, #{description}, #{category}, #{status}, #{scenicId}, NOW(), NOW(), 0 + #{backgroundImage}, #{coverImage}, #{description}, #{category}, #{status}, #{scenicId}, + #{autoAddPrint}, #{canPrint}, #{userArea}, NOW(), NOW(), 0 ) @@ -91,6 +97,9 @@ category = #{category}, status = #{status}, scenic_id = #{scenicId}, + auto_add_print = #{autoAddPrint}, + can_print = #{canPrint}, + user_area = #{userArea}, update_time = NOW() WHERE id = #{id} AND deleted = 0