You've already forked FrameTour-BE
feat(puzzle): 实现智能自动填充引擎和安全增强
- 新增拼图元素自动填充引擎 PuzzleElementFillEngine - 支持基于规则的条件匹配和数据源解析 - 实现机位数量、机位ID等多维度条件策略 - 添加 DEVICE_IMAGE、USER_AVATAR 等数据源类型支持 - 增加景区隔离校验确保模板使用安全性 - 强化图片下载安全校验,防范 SSRF 攻击 - 支持本地文件路径解析和公网 URL 安全检查 - 完善静态值数据源策略支持 localPath 配置 - 优化生成流程中 faceId 和 scenicId 的校验逻辑 - 补充相关单元测试覆盖核心功能点
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user