feat(puzzle): 实现智能自动填充引擎和安全增强

- 新增拼图元素自动填充引擎 PuzzleElementFillEngine
- 支持基于规则的条件匹配和数据源解析
- 实现机位数量、机位ID等多维度条件策略
- 添加 DEVICE_IMAGE、USER_AVATAR 等数据源类型支持
- 增加景区隔离校验确保模板使用安全性
- 强化图片下载安全校验,防范 SSRF 攻击
- 支持本地文件路径解析和公网 URL 安全检查
- 完善静态值数据源策略支持 localPath 配置
- 优化生成流程中 faceId 和 scenicId 的校验逻辑
- 补充相关单元测试覆盖核心功能点
This commit is contained in:
2025-11-19 17:28:41 +08:00
parent cb17ea527b
commit cfb3625ac0
12 changed files with 748 additions and 57 deletions

View File

@@ -55,6 +55,11 @@ public class PuzzleElementFillEngine {
public Map<String, String> execute(Long templateId, Long faceId, Long scenicId) {
Map<String, String> dynamicData = new HashMap<>();
if (faceId == null || scenicId == null) {
log.debug("自动填充被跳过, templateId={}, faceId={}, scenicId={}", templateId, faceId, scenicId);
return dynamicData;
}
try {
// 1. 查询模板的所有启用规则(按priority DESC排序)
List<PuzzleFillRuleEntity> rules = ruleMapper.listByTemplateAndScenic(templateId, scenicId);
@@ -94,7 +99,7 @@ public class PuzzleElementFillEngine {
if (items == null || items.isEmpty()) {
log.warn("规则[{}]没有配置明细项", rule.getRuleName());
break;
continue;
}
// 5. 批量填充dynamicData

View File

@@ -1,13 +1,19 @@
package com.ycwl.basic.puzzle.fill.datasource;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.databind.JsonNode;
import com.ycwl.basic.puzzle.fill.enums.DataSourceType;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
/**
* 静态值数据源策略
* 直接返回配置的静态
* 支持直接返回配置值或指向本地文件的路径
*/
@Slf4j
@Component
@@ -15,14 +21,55 @@ public class StaticValueDataSourceStrategy implements DataSourceStrategy {
@Override
public String resolve(JsonNode sourceFilter, String sortStrategy, DataSourceContext context) {
if (sourceFilter != null && sourceFilter.has("value")) {
if (sourceFilter == null) {
return null;
}
if (sourceFilter.hasNonNull("localPath")) {
String localPath = sourceFilter.get("localPath").asText();
return resolveLocalPath(localPath);
}
if (sourceFilter.hasNonNull("value")) {
String value = sourceFilter.get("value").asText();
log.debug("解析STATIC_VALUE成功, value={}", value);
return value;
}
return null;
}
private String resolveLocalPath(String rawPath) {
if (StrUtil.isBlank(rawPath)) {
log.warn("localPath为空, 无法解析静态值数据源");
return null;
}
try {
Path path;
if (StrUtil.startWithIgnoreCase(rawPath, "file:")) {
path = Paths.get(new URI(rawPath));
} else {
path = Paths.get(rawPath);
}
if (!path.isAbsolute()) {
path = path.toAbsolutePath();
}
if (!Files.exists(path) || !Files.isRegularFile(path)) {
log.warn("localPath不存在或不是文件: {}", path);
return null;
}
log.debug("解析STATIC_VALUE本地路径成功: {}", path);
return path.toString();
} catch (Exception e) {
log.error("解析本地路径失败: {}", rawPath, e);
return null;
}
}
@Override
public String getSupportedType() {
return DataSourceType.STATIC_VALUE.getCode();