You've already forked FrameTour-BE
feat(puzzle): 实现拼图自动填充规则引擎及相关功能
- 新增拼图填充规则管理Controller、DTO、Entity等核心类 - 实现条件评估策略模式,支持多种匹配规则 - 实现数据源解析策略模式,支持多种数据来源 - 新增拼图元素自动填充引擎,支持优先级匹配和动态填充 - 在SourceMapper中增加设备统计和查询相关方法 - 在PuzzleGenerateRequest中新增faceId字段用于触发自动填充 - 完善相关枚举类和工具类,提升系统可维护性和扩展性
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
package com.ycwl.basic.puzzle.service;
|
||||
|
||||
import com.ycwl.basic.puzzle.dto.PuzzleFillRuleDTO;
|
||||
import com.ycwl.basic.puzzle.dto.PuzzleFillRuleSaveRequest;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 拼图填充规则服务接口
|
||||
*/
|
||||
public interface IPuzzleFillRuleService {
|
||||
|
||||
/**
|
||||
* 创建规则(主+明细)
|
||||
*
|
||||
* @param request 保存请求
|
||||
* @return 规则ID
|
||||
*/
|
||||
Long create(PuzzleFillRuleSaveRequest request);
|
||||
|
||||
/**
|
||||
* 更新规则(主+明细)
|
||||
*
|
||||
* @param request 保存请求
|
||||
* @return 是否成功
|
||||
*/
|
||||
Boolean update(PuzzleFillRuleSaveRequest request);
|
||||
|
||||
/**
|
||||
* 删除规则(级联删除明细)
|
||||
*
|
||||
* @param id 规则ID
|
||||
* @return 是否成功
|
||||
*/
|
||||
Boolean delete(Long id);
|
||||
|
||||
/**
|
||||
* 查询单条规则(含明细)
|
||||
*
|
||||
* @param id 规则ID
|
||||
* @return 规则DTO
|
||||
*/
|
||||
PuzzleFillRuleDTO getById(Long id);
|
||||
|
||||
/**
|
||||
* 查询模板的所有规则(含明细)
|
||||
*
|
||||
* @param templateId 模板ID
|
||||
* @return 规则列表
|
||||
*/
|
||||
List<PuzzleFillRuleDTO> listByTemplateId(Long templateId);
|
||||
|
||||
/**
|
||||
* 启用/禁用规则
|
||||
*
|
||||
* @param id 规则ID
|
||||
* @param enabled 是否启用
|
||||
* @return 是否成功
|
||||
*/
|
||||
Boolean toggleEnabled(Long id, Integer enabled);
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
package com.ycwl.basic.puzzle.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.ycwl.basic.puzzle.dto.PuzzleFillRuleDTO;
|
||||
import com.ycwl.basic.puzzle.dto.PuzzleFillRuleItemDTO;
|
||||
import com.ycwl.basic.puzzle.dto.PuzzleFillRuleSaveRequest;
|
||||
import com.ycwl.basic.puzzle.entity.PuzzleFillRuleEntity;
|
||||
import com.ycwl.basic.puzzle.entity.PuzzleFillRuleItemEntity;
|
||||
import com.ycwl.basic.puzzle.mapper.PuzzleFillRuleItemMapper;
|
||||
import com.ycwl.basic.puzzle.mapper.PuzzleFillRuleMapper;
|
||||
import com.ycwl.basic.puzzle.service.IPuzzleFillRuleService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 拼图填充规则服务实现
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class PuzzleFillRuleServiceImpl implements IPuzzleFillRuleService {
|
||||
|
||||
@Autowired
|
||||
private PuzzleFillRuleMapper ruleMapper;
|
||||
|
||||
@Autowired
|
||||
private PuzzleFillRuleItemMapper itemMapper;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long create(PuzzleFillRuleSaveRequest request) {
|
||||
// 1. 保存主规则
|
||||
PuzzleFillRuleEntity ruleEntity = new PuzzleFillRuleEntity();
|
||||
BeanUtils.copyProperties(request, ruleEntity);
|
||||
ruleMapper.insert(ruleEntity);
|
||||
|
||||
Long ruleId = ruleEntity.getId();
|
||||
log.info("创建填充规则成功, ruleId={}, ruleName={}", ruleId, request.getRuleName());
|
||||
|
||||
// 2. 批量保存明细
|
||||
if (request.getItems() != null && !request.getItems().isEmpty()) {
|
||||
List<PuzzleFillRuleItemEntity> itemEntities = request.getItems().stream()
|
||||
.map(dto -> {
|
||||
PuzzleFillRuleItemEntity entity = new PuzzleFillRuleItemEntity();
|
||||
BeanUtils.copyProperties(dto, entity);
|
||||
entity.setRuleId(ruleId);
|
||||
return entity;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
itemMapper.batchInsert(itemEntities);
|
||||
log.info("批量保存规则明细成功, count={}", itemEntities.size());
|
||||
}
|
||||
|
||||
return ruleId;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Boolean update(PuzzleFillRuleSaveRequest request) {
|
||||
if (request.getId() == null) {
|
||||
throw new IllegalArgumentException("更新规则时ID不能为空");
|
||||
}
|
||||
|
||||
// 1. 更新主规则
|
||||
PuzzleFillRuleEntity ruleEntity = new PuzzleFillRuleEntity();
|
||||
BeanUtils.copyProperties(request, ruleEntity);
|
||||
ruleMapper.updateById(ruleEntity);
|
||||
|
||||
// 2. 删除旧明细
|
||||
itemMapper.deleteByRuleId(request.getId());
|
||||
|
||||
// 3. 批量插入新明细
|
||||
if (request.getItems() != null && !request.getItems().isEmpty()) {
|
||||
List<PuzzleFillRuleItemEntity> itemEntities = request.getItems().stream()
|
||||
.map(dto -> {
|
||||
PuzzleFillRuleItemEntity entity = new PuzzleFillRuleItemEntity();
|
||||
BeanUtils.copyProperties(dto, entity);
|
||||
entity.setRuleId(request.getId());
|
||||
return entity;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
itemMapper.batchInsert(itemEntities);
|
||||
}
|
||||
|
||||
log.info("更新填充规则成功, ruleId={}, itemCount={}", request.getId(),
|
||||
request.getItems() != null ? request.getItems().size() : 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Boolean delete(Long id) {
|
||||
// MyBatis-Plus会级联删除明细(通过外键ON DELETE CASCADE)
|
||||
ruleMapper.deleteById(id);
|
||||
log.info("删除填充规则成功, ruleId={}", id);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PuzzleFillRuleDTO getById(Long id) {
|
||||
PuzzleFillRuleEntity ruleEntity = ruleMapper.selectById(id);
|
||||
if (ruleEntity == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
PuzzleFillRuleDTO dto = new PuzzleFillRuleDTO();
|
||||
BeanUtils.copyProperties(ruleEntity, dto);
|
||||
|
||||
// 查询明细
|
||||
List<PuzzleFillRuleItemEntity> itemEntities = itemMapper.listByRuleId(id);
|
||||
if (itemEntities != null && !itemEntities.isEmpty()) {
|
||||
List<PuzzleFillRuleItemDTO> itemDTOs = itemEntities.stream()
|
||||
.map(entity -> {
|
||||
PuzzleFillRuleItemDTO itemDTO = new PuzzleFillRuleItemDTO();
|
||||
BeanUtils.copyProperties(entity, itemDTO);
|
||||
return itemDTO;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
dto.setItems(itemDTOs);
|
||||
}
|
||||
|
||||
return dto;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PuzzleFillRuleDTO> listByTemplateId(Long templateId) {
|
||||
List<PuzzleFillRuleEntity> ruleEntities = ruleMapper.listByTemplateId(templateId);
|
||||
if (ruleEntities == null || ruleEntities.isEmpty()) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
return ruleEntities.stream()
|
||||
.map(ruleEntity -> {
|
||||
PuzzleFillRuleDTO dto = new PuzzleFillRuleDTO();
|
||||
BeanUtils.copyProperties(ruleEntity, dto);
|
||||
|
||||
// 查询明细
|
||||
List<PuzzleFillRuleItemEntity> itemEntities = itemMapper.listByRuleId(ruleEntity.getId());
|
||||
if (itemEntities != null && !itemEntities.isEmpty()) {
|
||||
List<PuzzleFillRuleItemDTO> itemDTOs = itemEntities.stream()
|
||||
.map(entity -> {
|
||||
PuzzleFillRuleItemDTO itemDTO = new PuzzleFillRuleItemDTO();
|
||||
BeanUtils.copyProperties(entity, itemDTO);
|
||||
return itemDTO;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
dto.setItems(itemDTOs);
|
||||
}
|
||||
|
||||
return dto;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean toggleEnabled(Long id, Integer enabled) {
|
||||
LambdaUpdateWrapper<PuzzleFillRuleEntity> updateWrapper = new LambdaUpdateWrapper<>();
|
||||
updateWrapper.eq(PuzzleFillRuleEntity::getId, id)
|
||||
.set(PuzzleFillRuleEntity::getEnabled, enabled);
|
||||
|
||||
int count = ruleMapper.update(null, updateWrapper);
|
||||
log.info("切换规则启用状态, ruleId={}, enabled={}", id, enabled);
|
||||
return count > 0;
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import com.ycwl.basic.puzzle.dto.PuzzleGenerateResponse;
|
||||
import com.ycwl.basic.puzzle.entity.PuzzleElementEntity;
|
||||
import com.ycwl.basic.puzzle.entity.PuzzleGenerationRecordEntity;
|
||||
import com.ycwl.basic.puzzle.entity.PuzzleTemplateEntity;
|
||||
import com.ycwl.basic.puzzle.fill.PuzzleElementFillEngine;
|
||||
import com.ycwl.basic.puzzle.mapper.PuzzleElementMapper;
|
||||
import com.ycwl.basic.puzzle.mapper.PuzzleGenerationRecordMapper;
|
||||
import com.ycwl.basic.puzzle.mapper.PuzzleTemplateMapper;
|
||||
@@ -23,7 +24,9 @@ import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
@@ -41,6 +44,7 @@ public class PuzzleGenerateServiceImpl implements IPuzzleGenerateService {
|
||||
private final PuzzleElementMapper elementMapper;
|
||||
private final PuzzleGenerationRecordMapper recordMapper;
|
||||
private final PuzzleImageRenderer imageRenderer;
|
||||
private final PuzzleElementFillEngine fillEngine;
|
||||
|
||||
@Override
|
||||
public PuzzleGenerateResponse generate(PuzzleGenerateRequest request) {
|
||||
@@ -67,13 +71,16 @@ public class PuzzleGenerateServiceImpl implements IPuzzleGenerateService {
|
||||
elements.sort(Comparator.comparing(PuzzleElementEntity::getZIndex,
|
||||
Comparator.nullsFirst(Comparator.naturalOrder())));
|
||||
|
||||
// 3. 创建生成记录
|
||||
// 3. 准备dynamicData(合并自动填充和手动数据)
|
||||
Map<String, String> finalDynamicData = buildDynamicData(template, request);
|
||||
|
||||
// 4. 创建生成记录
|
||||
PuzzleGenerationRecordEntity record = createRecord(template, request);
|
||||
recordMapper.insert(record);
|
||||
|
||||
try {
|
||||
// 4. 渲染图片
|
||||
BufferedImage resultImage = imageRenderer.render(template, elements, request.getDynamicData());
|
||||
// 5. 渲染图片
|
||||
BufferedImage resultImage = imageRenderer.render(template, elements, finalDynamicData);
|
||||
|
||||
// 5. 上传到OSS
|
||||
String imageUrl = uploadImage(resultImage, template.getCode(), request.getOutputFormat(), request.getQuality());
|
||||
@@ -179,4 +186,39 @@ public class PuzzleGenerateServiceImpl implements IPuzzleGenerateService {
|
||||
return 0L;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建dynamicData(合并自动填充和手动数据)
|
||||
* 优先级: 手动传入的数据 > 自动填充的数据
|
||||
*/
|
||||
private Map<String, String> buildDynamicData(PuzzleTemplateEntity template, PuzzleGenerateRequest request) {
|
||||
Map<String, String> dynamicData = new HashMap<>();
|
||||
|
||||
// 1. 自动填充(基于faceId和规则)
|
||||
if (request.getFaceId() != null && request.getScenicId() != null) {
|
||||
try {
|
||||
Map<String, String> autoFilled = fillEngine.execute(
|
||||
template.getId(),
|
||||
request.getFaceId(),
|
||||
request.getScenicId()
|
||||
);
|
||||
if (autoFilled != null && !autoFilled.isEmpty()) {
|
||||
dynamicData.putAll(autoFilled);
|
||||
log.info("自动填充成功, 填充了{}个元素", autoFilled.size());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("自动填充异常, templateId={}, faceId={}", template.getId(), request.getFaceId(), e);
|
||||
// 自动填充失败不影响整体流程,继续执行
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 手动数据覆盖(优先级更高)
|
||||
if (request.getDynamicData() != null && !request.getDynamicData().isEmpty()) {
|
||||
dynamicData.putAll(request.getDynamicData());
|
||||
log.debug("合并手动传入的dynamicData, count={}", request.getDynamicData().size());
|
||||
}
|
||||
|
||||
log.info("最终dynamicData: {}", dynamicData.keySet());
|
||||
return dynamicData;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user