feat(puzzle): 增强拼图填充引擎功能

- 新增 requireRuleMatch 参数控制是否必须匹配规则
- 重构 DeviceCountConditionStrategy 支持两种匹配模式
- 移除已废弃的 DeviceCountRangeConditionStrategy
- 引入 FillResult 类封装填充结果信息
- 优化条件上下文和数据源上下文的 extra 字段类型
- 更新相关测试用例和文档说明
This commit is contained in:
2025-11-20 15:11:13 +08:00
parent aaa8d8310a
commit 2fd852c5c6
13 changed files with 470 additions and 261 deletions

View File

@@ -4,6 +4,7 @@ import lombok.Builder;
import lombok.Data;
import java.util.List;
import java.util.Map;
/**
* 条件评估上下文
@@ -31,5 +32,5 @@ public class ConditionContext {
/**
* 可扩展的其他上下文数据
*/
private Object extra;
private Map<String, Object> extra;
}

View File

@@ -2,28 +2,142 @@ package com.ycwl.basic.puzzle.fill.condition;
import com.fasterxml.jackson.databind.JsonNode;
import com.ycwl.basic.puzzle.fill.enums.ConditionType;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 机位数量精确匹配策略
*
* <p>支持两种匹配模式:</p>
* <ul>
* <li>模式1:全局数量匹配 - 只指定 deviceCount,匹配所有机位的数量</li>
* <li>模式2:指定列表数量匹配 - 同时指定 deviceCount + deviceIds,从指定列表中过滤并匹配数量</li>
* </ul>
*
* <p>配置示例:</p>
* <pre>
* // 模式1:全局数量匹配
* {
* "deviceCount": 4
* }
*
* // 模式2:指定列表数量匹配
* {
* "deviceCount": 2,
* "deviceIds": [200, 300, 400]
* }
* </pre>
*
* <p>模式2匹配逻辑:</p>
* <ul>
* <li>从 deviceIds 列表中过滤出实际存在的机位</li>
* <li>保持配置顺序(不按 deviceId 排序)</li>
* <li>判断过滤后的数量是否等于 deviceCount</li>
* <li>匹配成功后,将过滤后的机位列表存入 context.extra,供数据源解析使用</li>
* </ul>
*
* @author Claude
* @since 2025-01-20
*/
@Slf4j
@Component
public class DeviceCountConditionStrategy implements ConditionStrategy {
@Override
public boolean evaluate(JsonNode conditionValue, ConditionContext context) {
if (conditionValue == null || !conditionValue.has("deviceCount")) {
log.warn("DEVICE_COUNT条件缺少deviceCount字段");
return false;
}
int expectedCount = conditionValue.get("deviceCount").asInt();
Integer actualCount = context.getDeviceCount();
if (actualCount == null) {
if (expectedCount <= 0) {
log.warn("deviceCount必须大于0, 当前值: {}", expectedCount);
return false;
}
return actualCount == expectedCount;
// 检查是否指定了 deviceIds(模式2)
if (conditionValue.has("deviceIds")) {
return evaluateWithDeviceIdList(conditionValue, context, expectedCount);
} else {
// 模式1:全局数量匹配
return evaluateGlobalCount(context, expectedCount);
}
}
/**
* 模式1:全局数量匹配
*/
private boolean evaluateGlobalCount(ConditionContext context, int expectedCount) {
Integer actualCount = context.getDeviceCount();
if (actualCount == null) {
log.debug("上下文中没有机位数量信息");
return false;
}
boolean matched = actualCount == expectedCount;
if (matched) {
log.info("DEVICE_COUNT全局匹配成功: 期望数量={}, 实际数量={}", expectedCount, actualCount);
} else {
log.debug("DEVICE_COUNT全局匹配失败: 期望数量={}, 实际数量={}", expectedCount, actualCount);
}
return matched;
}
/**
* 模式2:指定列表数量匹配
*/
private boolean evaluateWithDeviceIdList(JsonNode conditionValue, ConditionContext context, int expectedCount) {
// 1. 读取配置的 deviceIds 列表
JsonNode deviceIdsNode = conditionValue.get("deviceIds");
if (!deviceIdsNode.isArray()) {
log.warn("deviceIds字段必须是数组");
return false;
}
List<Long> requiredDeviceIds = new ArrayList<>();
deviceIdsNode.forEach(node -> requiredDeviceIds.add(node.asLong()));
if (requiredDeviceIds.isEmpty()) {
log.warn("deviceIds数组为空");
return false;
}
// 2. 获取上下文中的机位列表
List<Long> contextDeviceIds = context.getDeviceIds();
if (contextDeviceIds == null || contextDeviceIds.isEmpty()) {
log.debug("上下文中没有机位ID列表");
return false;
}
// 3. 按配置顺序过滤出实际存在的机位
List<Long> matchedDeviceIds = requiredDeviceIds.stream()
.filter(contextDeviceIds::contains)
.collect(Collectors.toList()); // 保持配置顺序,不排序
// 4. 精确匹配数量
boolean matched = matchedDeviceIds.size() == expectedCount;
if (matched) {
// 5. 将过滤后的机位列表存入 context.extra
Map<String, Object> extra = new HashMap<>();
extra.put("filteredDeviceIds", matchedDeviceIds);
context.setExtra(extra);
log.info("DEVICE_COUNT列表匹配成功: 配置列表={}, 过滤后={}, 期望数量={}, 实际数量={}",
requiredDeviceIds, matchedDeviceIds, expectedCount, matchedDeviceIds.size());
} else {
log.debug("DEVICE_COUNT列表匹配失败: 配置列表={}, 过滤后={}, 期望数量={}, 实际数量={}",
requiredDeviceIds, matchedDeviceIds, expectedCount, matchedDeviceIds.size());
}
return matched;
}
@Override

View File

@@ -1,42 +0,0 @@
package com.ycwl.basic.puzzle.fill.condition;
import com.fasterxml.jackson.databind.JsonNode;
import com.ycwl.basic.puzzle.fill.enums.ConditionType;
import org.springframework.stereotype.Component;
/**
* 机位数量范围匹配策略
*/
@Component
public class DeviceCountRangeConditionStrategy implements ConditionStrategy {
@Override
public boolean evaluate(JsonNode conditionValue, ConditionContext context) {
if (conditionValue == null) {
return false;
}
Integer actualCount = context.getDeviceCount();
if (actualCount == null) {
return false;
}
Integer minCount = conditionValue.has("minCount") ? conditionValue.get("minCount").asInt() : null;
Integer maxCount = conditionValue.has("maxCount") ? conditionValue.get("maxCount").asInt() : null;
if (minCount != null && actualCount < minCount) {
return false;
}
if (maxCount != null && actualCount > maxCount) {
return false;
}
return true;
}
@Override
public String getSupportedType() {
return ConditionType.DEVICE_COUNT_RANGE.getCode();
}
}