From d15d070cb4aaf05c4593d6a28cef00ef4c37cb6c Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Fri, 16 Jan 2026 10:41:54 +0800 Subject: [PATCH] =?UTF-8?q?refactor(puzzle):=20=E9=87=8D=E6=9E=84=E6=8B=BC?= =?UTF-8?q?=E5=9B=BE=E5=8A=9F=E8=83=BD=E5=AE=9E=E7=8E=B0=E4=BC=9A=E5=91=98?= =?UTF-8?q?=E6=8B=BC=E5=9B=BE=E5=85=B3=E8=81=94=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 移除原有的图片裁切功能和userArea字段 - 删除originalImageUrl字段,统一使用resultImageUrl - 添加MemberPuzzleEntity实体类管理会员拼图关联关系 - 创建MemberPuzzleMapper接口及XML映射文件 - 实现PuzzleRelationProcessor处理器负责关联记录创建 - 在拼图生成完成后自动创建会员拼图关联记录 - 添加景区配置中的免费拼图数量设置 - 实现免费拼图逻辑控制 - 更新拼图模板和生成记录的数据结构 - 修改AppPuzzleController中图片URL的获取方式 - 优化PuzzleEdgeRenderTaskService中的图片处理流程 --- .../mobile/AppPuzzleController.java | 6 +- .../pc/puzzle/entity/MemberPuzzleEntity.java | 21 ++++ .../basic/puzzle/dto/PuzzleTemplateDTO.java | 5 - .../puzzle/dto/TemplateCreateRequest.java | 5 - .../task/PuzzleEdgeRenderTaskService.java | 24 ++--- .../entity/PuzzleGenerationRecordEntity.java | 6 -- .../puzzle/entity/PuzzleTemplateEntity.java | 6 -- .../puzzle/mapper/MemberPuzzleMapper.java | 49 ++++++++++ .../mapper/PuzzleGenerationRecordMapper.java | 1 - .../impl/PuzzleGenerateServiceImpl.java | 95 +++++-------------- .../service/pc/helper/ScenicConfigFacade.java | 17 ++++ .../pc/processor/PuzzleRelationProcessor.java | 84 ++++++++++++++++ .../resources/mapper/MemberPuzzleMapper.xml | 70 ++++++++++++++ .../mapper/PuzzleGenerationRecordMapper.xml | 9 +- .../resources/mapper/PuzzleTemplateMapper.xml | 8 +- ...uzzleGenerateServiceDeduplicationTest.java | 8 +- .../impl/PuzzleGenerateServiceImplTest.java | 4 +- 17 files changed, 295 insertions(+), 123 deletions(-) create mode 100644 src/main/java/com/ycwl/basic/model/pc/puzzle/entity/MemberPuzzleEntity.java create mode 100644 src/main/java/com/ycwl/basic/puzzle/mapper/MemberPuzzleMapper.java create mode 100644 src/main/java/com/ycwl/basic/service/pc/processor/PuzzleRelationProcessor.java create mode 100644 src/main/resources/mapper/MemberPuzzleMapper.xml diff --git a/src/main/java/com/ycwl/basic/controller/mobile/AppPuzzleController.java b/src/main/java/com/ycwl/basic/controller/mobile/AppPuzzleController.java index 6f0e7a64..39350ca0 100644 --- a/src/main/java/com/ycwl/basic/controller/mobile/AppPuzzleController.java +++ b/src/main/java/com/ycwl/basic/controller/mobile/AppPuzzleController.java @@ -147,8 +147,8 @@ public class AppPuzzleController { } // 检查是否有图片URL - String originalImageUrl = record.getOriginalImageUrl(); - if (originalImageUrl == null || originalImageUrl.isEmpty()) { + String imageUrl = record.getResultImageUrl(); + if (imageUrl == null || imageUrl.isEmpty()) { return ApiResponse.fail("该拼图记录没有可用的图片URL"); } @@ -163,7 +163,7 @@ public class AppPuzzleController { face.getMemberId(), face.getScenicId(), record.getFaceId(), - originalImageUrl, + imageUrl, 0L // 打印特有 ); diff --git a/src/main/java/com/ycwl/basic/model/pc/puzzle/entity/MemberPuzzleEntity.java b/src/main/java/com/ycwl/basic/model/pc/puzzle/entity/MemberPuzzleEntity.java new file mode 100644 index 00000000..9d1d7151 --- /dev/null +++ b/src/main/java/com/ycwl/basic/model/pc/puzzle/entity/MemberPuzzleEntity.java @@ -0,0 +1,21 @@ +package com.ycwl.basic.model.pc.puzzle.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +/** + * 会员拼图关联实体 + * 记录人脸与拼图生成记录的关联关系,包含免费和购买状态 + */ +@Data +@TableName("member_puzzle") +public class MemberPuzzleEntity { + private Long id; + private Long memberId; + private Long scenicId; + private Long faceId; + private Long recordId; + private Integer isBuy; + private Long orderId; + private Integer isFree; +} diff --git a/src/main/java/com/ycwl/basic/puzzle/dto/PuzzleTemplateDTO.java b/src/main/java/com/ycwl/basic/puzzle/dto/PuzzleTemplateDTO.java index 21c64f90..ecf552bc 100644 --- a/src/main/java/com/ycwl/basic/puzzle/dto/PuzzleTemplateDTO.java +++ b/src/main/java/com/ycwl/basic/puzzle/dto/PuzzleTemplateDTO.java @@ -88,11 +88,6 @@ public class PuzzleTemplateDTO { */ private Integer canPrint; - /** - * 用户查看区域(裁切区域),格式:x,y,w,h - */ - private String userArea; - /** * 元素列表 */ diff --git a/src/main/java/com/ycwl/basic/puzzle/dto/TemplateCreateRequest.java b/src/main/java/com/ycwl/basic/puzzle/dto/TemplateCreateRequest.java index e9c17d20..aad2c841 100644 --- a/src/main/java/com/ycwl/basic/puzzle/dto/TemplateCreateRequest.java +++ b/src/main/java/com/ycwl/basic/puzzle/dto/TemplateCreateRequest.java @@ -71,11 +71,6 @@ public class TemplateCreateRequest { */ private Integer canPrint; - /** - * 用户查看区域(裁切区域),格式:x,y,w,h - */ - private String userArea; - /** * 状态:0-禁用 1-启用 */ diff --git a/src/main/java/com/ycwl/basic/puzzle/edge/task/PuzzleEdgeRenderTaskService.java b/src/main/java/com/ycwl/basic/puzzle/edge/task/PuzzleEdgeRenderTaskService.java index 87723251..b892aa11 100644 --- a/src/main/java/com/ycwl/basic/puzzle/edge/task/PuzzleEdgeRenderTaskService.java +++ b/src/main/java/com/ycwl/basic/puzzle/edge/task/PuzzleEdgeRenderTaskService.java @@ -17,6 +17,7 @@ import com.ycwl.basic.puzzle.entity.PuzzleGenerationRecordEntity; import com.ycwl.basic.puzzle.entity.PuzzleTemplateEntity; import com.ycwl.basic.puzzle.mapper.PuzzleGenerationRecordMapper; import com.ycwl.basic.puzzle.repository.PuzzleRepository; +import com.ycwl.basic.service.pc.processor.PuzzleRelationProcessor; import com.ycwl.basic.service.printer.PrinterService; import com.ycwl.basic.storage.StorageFactory; import com.ycwl.basic.storage.adapters.IStorageAdapter; @@ -134,6 +135,7 @@ public class PuzzleEdgeRenderTaskService { private final PuzzleGenerationRecordMapper recordMapper; private final PuzzleRepository puzzleRepository; private final PrinterService printerService; + private final PuzzleRelationProcessor puzzleRelationProcessor; /** * 固定的 workerId,用于标识通过 IP CIDR 验证的 worker @@ -205,10 +207,7 @@ public class PuzzleEdgeRenderTaskService { } IStorageAdapter storage = StorageFactory.use(); - String originalImageUrl = storage.getUrl(task.getOriginalObjectKey()); - String resultImageUrl = StrUtil.isNotBlank(task.getCroppedObjectKey()) - ? storage.getUrl(task.getCroppedObjectKey()) - : originalImageUrl; + String resultImageUrl = storage.getUrl(task.getOriginalObjectKey()); Long resultFileSize = req != null ? req.getResultFileSize() : null; Integer resultWidth = req != null ? req.getResultWidth() : null; @@ -218,7 +217,6 @@ public class PuzzleEdgeRenderTaskService { recordMapper.updateSuccess( record.getId(), resultImageUrl, - originalImageUrl, resultFileSize, resultWidth, resultHeight, @@ -228,6 +226,14 @@ public class PuzzleEdgeRenderTaskService { // 清除生成记录缓存(状态已更新) puzzleRepository.clearRecordCache(record.getId(), record.getFaceId()); + // 创建member_puzzle关联记录(使用INSERT IGNORE避免重复) + puzzleRelationProcessor.createPuzzleRelation( + record.getUserId(), + record.getScenicId(), + record.getFaceId(), + record.getId() + ); + // 通知等待方任务完成 completeWaitFuture(taskId, TaskWaitResult.success(resultImageUrl)); @@ -238,7 +244,7 @@ public class PuzzleEdgeRenderTaskService { record.getUserId(), record.getScenicId(), record.getFaceId(), - originalImageUrl, + resultImageUrl, 0L // 打印特有 ); log.info("自动添加到打印队列成功: recordId={}, printRecordId={}", record.getId(), printRecordId); @@ -421,9 +427,6 @@ public class PuzzleEdgeRenderTaskService { String fileName = UUID.randomUUID().toString().replace("-", "") + "." + ext; String originalObjectKey = String.format("puzzle/%s/%s", template.getCode(), fileName); - String croppedObjectKey = StrUtil.isNotBlank(template.getUserArea()) - ? String.format("puzzle/%s_cropped/%s", template.getCode(), fileName) - : null; Map payload = new HashMap<>(); payload.put("recordId", record.getId()); @@ -436,7 +439,6 @@ public class PuzzleEdgeRenderTaskService { templatePayload.put("backgroundType", template.getBackgroundType()); templatePayload.put("backgroundColor", template.getBackgroundColor()); templatePayload.put("backgroundImage", template.getBackgroundImage()); - templatePayload.put("userArea", template.getUserArea()); payload.put("template", templatePayload); List> elementPayloadList = new ArrayList<>(); @@ -476,7 +478,7 @@ public class PuzzleEdgeRenderTaskService { task.setOutputFormat(normalizedFormat); task.setOutputQuality(outputQuality); task.setOriginalObjectKey(originalObjectKey); - task.setCroppedObjectKey(croppedObjectKey); + task.setCroppedObjectKey(null); task.setPayloadJson(JacksonUtil.toJson(payload)); Long taskId = taskIdSequence.incrementAndGet(); 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 da3dc989..56f6e07c 100644 --- a/src/main/java/com/ycwl/basic/puzzle/entity/PuzzleGenerationRecordEntity.java +++ b/src/main/java/com/ycwl/basic/puzzle/entity/PuzzleGenerationRecordEntity.java @@ -76,12 +76,6 @@ 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 7dcc1d49..324c56b1 100644 --- a/src/main/java/com/ycwl/basic/puzzle/entity/PuzzleTemplateEntity.java +++ b/src/main/java/com/ycwl/basic/puzzle/entity/PuzzleTemplateEntity.java @@ -109,12 +109,6 @@ public class PuzzleTemplateEntity { @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/MemberPuzzleMapper.java b/src/main/java/com/ycwl/basic/puzzle/mapper/MemberPuzzleMapper.java new file mode 100644 index 00000000..b5322cb2 --- /dev/null +++ b/src/main/java/com/ycwl/basic/puzzle/mapper/MemberPuzzleMapper.java @@ -0,0 +1,49 @@ +package com.ycwl.basic.puzzle.mapper; + +import com.ycwl.basic.model.pc.puzzle.entity.MemberPuzzleEntity; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 会员拼图关联Mapper接口 + */ +@Mapper +public interface MemberPuzzleMapper { + + /** + * 添加关联记录 + */ + int addRelation(MemberPuzzleEntity entity); + + /** + * 批量添加关联记录 + */ + int addRelations(@Param("list") List list); + + /** + * 根据人脸ID查询关联列表 + */ + List listByFaceId(@Param("faceId") Long faceId); + + /** + * 根据人脸ID统计免费数量 + */ + int countFreeByFaceId(@Param("faceId") Long faceId); + + /** + * 更新关联记录(购买状态、订单ID等) + */ + int updateRelation(MemberPuzzleEntity entity); + + /** + * 批量标记为免费 + */ + int freeRelations(@Param("ids") List ids); + + /** + * 根据人脸ID和记录ID查询 + */ + MemberPuzzleEntity getByFaceAndRecord(@Param("faceId") Long faceId, @Param("recordId") Long recordId); +} 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 f4666527..2b4bc208 100644 --- a/src/main/java/com/ycwl/basic/puzzle/mapper/PuzzleGenerationRecordMapper.java +++ b/src/main/java/com/ycwl/basic/puzzle/mapper/PuzzleGenerationRecordMapper.java @@ -52,7 +52,6 @@ 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 370d79ec..3cc06114 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 @@ -18,6 +18,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.pc.processor.PuzzleRelationProcessor; import com.ycwl.basic.service.printer.PrinterService; import com.ycwl.basic.storage.StorageFactory; import com.ycwl.basic.utils.WxMpUtil; @@ -58,6 +59,7 @@ public class PuzzleGenerateServiceImpl implements IPuzzleGenerateService { private final PrinterService printerService; private final PuzzleEdgeRenderTaskService puzzleEdgeRenderTaskService; private final FaceStatusManager faceStatusManager; + private final PuzzleRelationProcessor puzzleRelationProcessor; public PuzzleGenerateServiceImpl( PuzzleRepository puzzleRepository, @@ -68,7 +70,8 @@ public class PuzzleGenerateServiceImpl implements IPuzzleGenerateService { @Lazy PuzzleDuplicationDetector duplicationDetector, @Lazy PrinterService printerService, PuzzleEdgeRenderTaskService puzzleEdgeRenderTaskService, - @Lazy FaceStatusManager faceStatusManager) { + @Lazy FaceStatusManager faceStatusManager, + @Lazy PuzzleRelationProcessor puzzleRelationProcessor) { this.puzzleRepository = puzzleRepository; this.recordMapper = recordMapper; this.imageRenderer = imageRenderer; @@ -78,6 +81,7 @@ public class PuzzleGenerateServiceImpl implements IPuzzleGenerateService { this.printerService = printerService; this.puzzleEdgeRenderTaskService = puzzleEdgeRenderTaskService; this.faceStatusManager = faceStatusManager; + this.puzzleRelationProcessor = puzzleRelationProcessor; } @Override @@ -206,6 +210,14 @@ public class PuzzleGenerateServiceImpl implements IPuzzleGenerateService { faceStatusManager.markPuzzleSourceVersion(request.getFaceId(), template.getId(), 0); } + // 创建member_puzzle关联记录 + puzzleRelationProcessor.createPuzzleRelation( + request.getUserId(), + resolvedScenicId, + request.getFaceId(), + record.getId() + ); + // 重新查询记录获取完整信息(边缘渲染回调已更新) PuzzleGenerationRecordEntity updatedRecord = recordMapper.getById(record.getId()); if (updatedRecord != null && updatedRecord.getResultImageUrl() != null) { @@ -456,43 +468,27 @@ public class PuzzleGenerateServiceImpl implements IPuzzleGenerateService { // 渲染图片 BufferedImage resultImage = imageRenderer.render(template, elements, finalDynamicData); - // 上传原图到OSS(未裁切) - String originalImageUrl = uploadImage(resultImage, template.getCode(), request.getOutputFormat(), request.getQuality()); - log.info("原图上传成功: url={}", originalImageUrl); - - // 处理用户区域裁切 - 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); - } - } + // 上传图片到OSS + String imageUrl = uploadImage(resultImage, template.getCode(), request.getOutputFormat(), request.getQuality()); + log.info("图片上传成功: url={}", imageUrl); // 更新记录为成功 long duration = System.currentTimeMillis() - startTime; - long fileSize = estimateFileSize(finalImage, request.getOutputFormat()); + long fileSize = estimateFileSize(resultImage, request.getOutputFormat()); recordMapper.updateSuccess( record.getId(), - finalImageUrl, - originalImageUrl, + imageUrl, fileSize, - finalImage.getWidth(), - finalImage.getHeight(), + resultImage.getWidth(), + resultImage.getHeight(), (int) duration ); // 清除生成记录缓存(状态已更新) puzzleRepository.clearRecordCache(record.getId(), request.getFaceId()); - log.info("拼图生成成功: recordId={}, originalUrl={}, finalUrl={}, duration={}ms", - record.getId(), originalImageUrl, finalImageUrl, duration); + log.info("拼图生成成功: recordId={}, imageUrl={}, duration={}ms", + record.getId(), imageUrl, duration); // 检查是否自动添加到打印队列 if (template.getAutoAddPrint() != null && template.getAutoAddPrint() == 1) { @@ -501,7 +497,7 @@ public class PuzzleGenerateServiceImpl implements IPuzzleGenerateService { request.getUserId(), resolvedScenicId, request.getFaceId(), - originalImageUrl, + imageUrl, 0L // 打印特有 ); log.info("自动添加到打印队列成功: recordId={}, printRecordId={}", record.getId(), printRecordId); @@ -511,10 +507,10 @@ public class PuzzleGenerateServiceImpl implements IPuzzleGenerateService { } return PuzzleGenerateResponse.success( - finalImageUrl, + imageUrl, fileSize, - finalImage.getWidth(), - finalImage.getHeight(), + resultImage.getWidth(), + resultImage.getHeight(), (int) duration, record.getId(), false, @@ -761,43 +757,4 @@ 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/java/com/ycwl/basic/service/pc/helper/ScenicConfigFacade.java b/src/main/java/com/ycwl/basic/service/pc/helper/ScenicConfigFacade.java index 7b429aad..f685cd48 100644 --- a/src/main/java/com/ycwl/basic/service/pc/helper/ScenicConfigFacade.java +++ b/src/main/java/com/ycwl/basic/service/pc/helper/ScenicConfigFacade.java @@ -168,6 +168,23 @@ public class ScenicConfigFacade { return config.getInteger("photo_free_num"); } + // ==================== 拼图相关配置 ==================== + + /** + * 获取免费拼图数量 + * 新用户首次识别时赠送的免费拼图数量 + * + * @param scenicId 景区ID + * @return 免费拼图数量,null 或 0 表示不赠送 + */ + public Integer getPuzzleFreeNum(Long scenicId) { + ScenicConfigManager config = getConfig(scenicId); + if (config == null) { + return null; + } + return config.getInteger("puzzle_free_num"); + } + // ==================== 游玩时间相关配置 ==================== /** diff --git a/src/main/java/com/ycwl/basic/service/pc/processor/PuzzleRelationProcessor.java b/src/main/java/com/ycwl/basic/service/pc/processor/PuzzleRelationProcessor.java new file mode 100644 index 00000000..ec0d2f40 --- /dev/null +++ b/src/main/java/com/ycwl/basic/service/pc/processor/PuzzleRelationProcessor.java @@ -0,0 +1,84 @@ +package com.ycwl.basic.service.pc.processor; + +import com.ycwl.basic.constant.FreeStatus; +import com.ycwl.basic.model.pc.puzzle.entity.MemberPuzzleEntity; +import com.ycwl.basic.puzzle.mapper.MemberPuzzleMapper; +import com.ycwl.basic.service.pc.helper.ScenicConfigFacade; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * 拼图关联记录处理器 + * 负责创建和管理member_puzzle关联记录 + */ +@Slf4j +@Component +public class PuzzleRelationProcessor { + + @Autowired + private MemberPuzzleMapper memberPuzzleMapper; + + @Autowired + private ScenicConfigFacade scenicConfigFacade; + + /** + * 创建拼图关联记录 + * + * @param memberId 会员ID + * @param scenicId 景区ID + * @param faceId 人脸ID + * @param recordId 拼图生成记录ID + */ + public void createPuzzleRelation(Long memberId, Long scenicId, Long faceId, Long recordId) { + if (faceId == null || recordId == null) { + log.warn("创建拼图关联记录失败:faceId或recordId为空, faceId={}, recordId={}", faceId, recordId); + return; + } + + MemberPuzzleEntity entity = new MemberPuzzleEntity(); + entity.setMemberId(memberId); + entity.setScenicId(scenicId); + entity.setFaceId(faceId); + entity.setRecordId(recordId); + entity.setIsBuy(0); + + // 处理免费逻辑 + Integer isFree = processFreeLogic(scenicId, faceId); + entity.setIsFree(isFree); + + try { + memberPuzzleMapper.addRelation(entity); + log.debug("创建拼图关联记录成功: faceId={}, recordId={}, isFree={}", faceId, recordId, isFree); + } catch (Exception e) { + log.error("创建拼图关联记录失败: faceId={}, recordId={}", faceId, recordId, e); + } + } + + /** + * 处理免费逻辑 + * 根据景区配置的puzzle_free_num决定是否免费 + * + * @param scenicId 景区ID + * @param faceId 人脸ID + * @return 免费状态码 + */ + private Integer processFreeLogic(Long scenicId, Long faceId) { + Integer puzzleFreeNum = scenicConfigFacade.getPuzzleFreeNum(scenicId); + + if (puzzleFreeNum == null || puzzleFreeNum <= 0) { + return FreeStatus.PAID.getCode(); + } + + // 统计已有的免费拼图数量 + int existingFreeCount = memberPuzzleMapper.countFreeByFaceId(faceId); + + if (existingFreeCount < puzzleFreeNum) { + log.debug("免费拼图逻辑: scenicId={}, faceId={}, 配置免费数量={}, 已免费={}, 本次免费", + scenicId, faceId, puzzleFreeNum, existingFreeCount); + return FreeStatus.FREE.getCode(); + } + + return FreeStatus.PAID.getCode(); + } +} diff --git a/src/main/resources/mapper/MemberPuzzleMapper.xml b/src/main/resources/mapper/MemberPuzzleMapper.xml new file mode 100644 index 00000000..b016ac57 --- /dev/null +++ b/src/main/resources/mapper/MemberPuzzleMapper.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + id, member_id, scenic_id, face_id, record_id, is_buy, order_id, is_free + + + + INSERT IGNORE INTO member_puzzle(scenic_id, face_id, member_id, record_id, is_buy, order_id, is_free) + VALUES (#{scenicId}, #{faceId}, #{memberId}, #{recordId}, #{isBuy}, #{orderId}, #{isFree}) + + + + INSERT IGNORE INTO member_puzzle(scenic_id, face_id, member_id, record_id, is_buy, order_id, is_free) + VALUES + + (#{item.scenicId}, #{item.faceId}, #{item.memberId}, #{item.recordId}, #{item.isBuy}, #{item.orderId}, #{item.isFree}) + + + + + + + + + UPDATE member_puzzle + + is_buy = #{isBuy}, + order_id = #{orderId}, + is_free = #{isFree}, + + WHERE id = #{id} + + + + UPDATE member_puzzle SET is_free = 1 + WHERE id IN + + #{id} + + + + + + diff --git a/src/main/resources/mapper/PuzzleGenerationRecordMapper.xml b/src/main/resources/mapper/PuzzleGenerationRecordMapper.xml index cbc29d03..db944984 100644 --- a/src/main/resources/mapper/PuzzleGenerationRecordMapper.xml +++ b/src/main/resources/mapper/PuzzleGenerationRecordMapper.xml @@ -14,7 +14,6 @@ - @@ -33,7 +32,7 @@ id, template_id, template_code, user_id, face_id, business_type, generation_params, content_hash, - result_image_url, original_image_url, result_file_size, result_width, result_height, + result_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 @@ -78,13 +77,13 @@ INSERT INTO puzzle_generation_record ( template_id, template_code, user_id, face_id, business_type, generation_params, content_hash, - result_image_url, original_image_url, result_file_size, result_width, result_height, + result_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}, #{originalImageUrl}, #{resultFileSize}, #{resultWidth}, #{resultHeight}, + #{resultImageUrl}, #{resultFileSize}, #{resultWidth}, #{resultHeight}, #{status}, #{errorMessage}, #{generationDuration}, #{retryCount}, #{scenicId}, #{clientIp}, #{userAgent}, NOW(), NOW() ) @@ -95,7 +94,6 @@ UPDATE puzzle_generation_record result_image_url = #{resultImageUrl}, - original_image_url = #{originalImageUrl}, result_file_size = #{resultFileSize}, result_width = #{resultWidth}, result_height = #{resultHeight}, @@ -113,7 +111,6 @@ 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 acff2264..0acbfda2 100644 --- a/src/main/resources/mapper/PuzzleTemplateMapper.xml +++ b/src/main/resources/mapper/PuzzleTemplateMapper.xml @@ -20,7 +20,6 @@ - @@ -31,7 +30,7 @@ id, name, code, canvas_width, canvas_height, background_type, background_color, background_image, cover_image, description, category, status, scenic_id, - auto_add_print, can_print, user_area, create_time, update_time, deleted, deleted_at + auto_add_print, can_print, create_time, update_time, deleted, deleted_at @@ -73,11 +72,11 @@ INSERT INTO puzzle_template ( name, code, canvas_width, canvas_height, background_type, background_color, background_image, cover_image, description, category, status, scenic_id, - auto_add_print, can_print, user_area, create_time, update_time, deleted + auto_add_print, can_print, create_time, update_time, deleted ) VALUES ( #{name}, #{code}, #{canvasWidth}, #{canvasHeight}, #{backgroundType}, #{backgroundColor}, #{backgroundImage}, #{coverImage}, #{description}, #{category}, #{status}, #{scenicId}, - #{autoAddPrint}, #{canPrint}, #{userArea}, NOW(), NOW(), 0 + #{autoAddPrint}, #{canPrint}, NOW(), NOW(), 0 ) @@ -99,7 +98,6 @@ scenic_id = #{scenicId}, auto_add_print = #{autoAddPrint}, can_print = #{canPrint}, - user_area = #{userArea}, update_time = NOW() WHERE id = #{id} AND deleted = 0 diff --git a/src/test/java/com/ycwl/basic/puzzle/service/impl/PuzzleGenerateServiceDeduplicationTest.java b/src/test/java/com/ycwl/basic/puzzle/service/impl/PuzzleGenerateServiceDeduplicationTest.java index 6560a69f..1ddc9abd 100644 --- a/src/test/java/com/ycwl/basic/puzzle/service/impl/PuzzleGenerateServiceDeduplicationTest.java +++ b/src/test/java/com/ycwl/basic/puzzle/service/impl/PuzzleGenerateServiceDeduplicationTest.java @@ -107,7 +107,7 @@ class PuzzleGenerateServiceDeduplicationTest { }).when(recordMapper).insert(any()); // Mock更新成功 - when(recordMapper.updateSuccess(anyLong(), anyString(), anyString(), anyLong(), anyInt(), anyInt(), anyInt())) + when(recordMapper.updateSuccess(anyLong(), anyString(), anyLong(), anyInt(), anyInt(), anyInt())) .thenReturn(1); // 执行 @@ -119,7 +119,7 @@ class PuzzleGenerateServiceDeduplicationTest { assertNull(response.getOriginalRecordId()); verify(imageRenderer, times(1)).render(any(), any(), any()); // 确实进行了渲染 verify(recordMapper, times(1)).insert(any()); // 插入了一条记录 - verify(recordMapper, times(1)).updateSuccess(anyLong(), anyString(), anyString(), anyLong(), anyInt(), anyInt(), anyInt()); + verify(recordMapper, times(1)).updateSuccess(anyLong(), anyString(), anyLong(), anyInt(), anyInt(), anyInt()); } /** @@ -157,7 +157,7 @@ class PuzzleGenerateServiceDeduplicationTest { assertEquals("https://example.com/old-image.jpg", response.getImageUrl()); // 复用的URL verify(imageRenderer, never()).render(any(), any(), any()); // 没有进行渲染 verify(recordMapper, never()).insert(any()); // 没有插入新记录 - verify(recordMapper, never()).updateSuccess(anyLong(), anyString(), anyString(), anyLong(), anyInt(), anyInt(), anyInt()); + verify(recordMapper, never()).updateSuccess(anyLong(), anyString(), anyLong(), anyInt(), anyInt(), anyInt()); } /** @@ -234,7 +234,7 @@ class PuzzleGenerateServiceDeduplicationTest { return 1; }).when(recordMapper).insert(any()); - when(recordMapper.updateSuccess(anyLong(), anyString(), anyString(), anyLong(), anyInt(), anyInt(), anyInt())) + when(recordMapper.updateSuccess(anyLong(), anyString(), anyLong(), anyInt(), anyInt(), anyInt())) .thenReturn(1); // 执行两次生成 diff --git a/src/test/java/com/ycwl/basic/puzzle/service/impl/PuzzleGenerateServiceImplTest.java b/src/test/java/com/ycwl/basic/puzzle/service/impl/PuzzleGenerateServiceImplTest.java index 5c794941..a9048f9d 100644 --- a/src/test/java/com/ycwl/basic/puzzle/service/impl/PuzzleGenerateServiceImplTest.java +++ b/src/test/java/com/ycwl/basic/puzzle/service/impl/PuzzleGenerateServiceImplTest.java @@ -84,7 +84,7 @@ class PuzzleGenerateServiceImplTest { record.setId(555L); return 1; }).when(recordMapper).insert(any()); - when(recordMapper.updateSuccess(anyLong(), anyString(), anyString(), anyLong(), anyInt(), anyInt(), anyInt())).thenReturn(1); + when(recordMapper.updateSuccess(anyLong(), anyString(), anyLong(), anyInt(), anyInt(), anyInt())).thenReturn(1); PuzzleGenerateRequest request = new PuzzleGenerateRequest(); request.setTemplateCode("ticket"); @@ -131,7 +131,7 @@ class PuzzleGenerateServiceImplTest { record.setId(777L); return 1; }).when(recordMapper).insert(any()); - when(recordMapper.updateSuccess(anyLong(), anyString(), anyString(), anyLong(), anyInt(), anyInt(), anyInt())).thenReturn(1); + when(recordMapper.updateSuccess(anyLong(), anyString(), anyLong(), anyInt(), anyInt(), anyInt())).thenReturn(1); PuzzleGenerateRequest request = new PuzzleGenerateRequest(); request.setTemplateCode("ticket");