diff --git a/cn-universal-data-bridge/cn-universal-data-bridge-core/src/main/java/cn/universal/databridge/engine/DataBridgeMagicScriptEngine.java b/cn-universal-data-bridge/cn-universal-data-bridge-core/src/main/java/cn/universal/databridge/engine/DataBridgeMagicScriptEngine.java index 992a54e..e652291 100644 --- a/cn-universal-data-bridge/cn-universal-data-bridge-core/src/main/java/cn/universal/databridge/engine/DataBridgeMagicScriptEngine.java +++ b/cn-universal-data-bridge/cn-universal-data-bridge-core/src/main/java/cn/universal/databridge/engine/DataBridgeMagicScriptEngine.java @@ -12,17 +12,20 @@ package cn.universal.databridge.engine; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.springframework.stereotype.Component; + import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONUtil; import cn.universal.core.engine.MagicScript; import cn.universal.core.engine.MagicScriptContext; import cn.universal.databridge.entity.DataBridgeConfig; import cn.universal.databridge.entity.ResourceConnection; import cn.universal.persistence.base.BaseUPRequest; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; /** * 数据桥接Magic脚本引擎 适配ProtocolCodecMagic到数据桥接场景 @@ -34,240 +37,306 @@ import org.springframework.stereotype.Component; @Slf4j public class DataBridgeMagicScriptEngine { - /** 脚本缓存 */ - private final Map scriptCache = new ConcurrentHashMap<>(); + /** + * 脚本缓存 + */ + private final Map scriptCache = new ConcurrentHashMap<>(); - /** 执行数据桥接Magic脚本 - 输出方向 (IoT -> 外部系统) */ - public Object executeIotToYourScript( - String script, - BaseUPRequest request, - DataBridgeConfig config, - ResourceConnection connection) { - try { - // 构建脚本上下文 - MagicScriptContext context = buildScriptContext(request, config, connection); + /** + * 执行数据桥接Magic脚本 - 输出方向 (IoT -> 外部系统) + */ + public Object executeIotToYourScript( + String script, + BaseUPRequest request, + DataBridgeConfig config, + ResourceConnection connection) { + try { + // 构建脚本上下文 + MagicScriptContext context = buildScriptContext(request, config, connection); - // 获取或创建脚本 - MagicScript magicScript = getOrCreateScript(script, "iotToYour"); + // 获取或创建脚本 + MagicScript magicScript = getOrCreateScript(script, "iotToYour"); - // 执行脚本 - Object result = magicScript.execute(context); + // 执行脚本 + Object result = magicScript.execute(context); - log.debug( - "Magic脚本iotToYour执行成功,返回类型: {}", - result != null ? result.getClass().getSimpleName() : "null"); - return result; + log.debug( + "Magic脚本iotToYour执行成功,返回类型: {}", + result != null ? result.getClass().getSimpleName() : "null"); + return result; - } catch (Exception e) { - log.error("Magic脚本iotToYour执行失败: {}", e.getMessage(), e); - throw new RuntimeException("Magic脚本iotToYour执行失败: " + e.getMessage(), e); - } - } - - /** 执行数据桥接Magic脚本 - 输入方向 (外部系统 -> IoT) */ - public Object executeYourToIotScript( - String script, Object externalData, DataBridgeConfig config, ResourceConnection connection) { - try { - // 构建脚本上下文 - MagicScriptContext context = buildYourToIotScriptContext(externalData, config, connection); - - // 获取或创建脚本 - MagicScript magicScript = getOrCreateScript(script, "yourToIot"); - - // 执行脚本 - Object result = magicScript.execute(context); - - log.debug( - "Magic脚本yourToIot执行成功,返回类型: {}", - result != null ? result.getClass().getSimpleName() : "null"); - return result; - - } catch (Exception e) { - log.error("Magic脚本yourToIot执行失败: {}", e.getMessage(), e); - throw new RuntimeException("Magic脚本yourToIot执行失败: " + e.getMessage(), e); - } - } - - /** 批量执行数据桥接Magic脚本 */ - public List executeBatchDataBridgeScript( - String script, - List requests, - DataBridgeConfig config, - ResourceConnection connection) { - try { - // 构建脚本上下文 - MagicScriptContext context = buildBatchScriptContext(requests, config, connection); - - // 获取或创建脚本 - MagicScript magicScript = getOrCreateScript(script, "iotToYour"); - - // 执行脚本 - Object result = magicScript.execute(context); - - // 处理返回结果 - if (result instanceof List) { - @SuppressWarnings("unchecked") - List resultList = (List) result; - log.debug("Magic脚本批量执行成功,返回数量: {}", resultList.size()); - return resultList; - } else { - log.warn( - "Magic脚本批量执行返回类型不是List: {}", - result != null ? result.getClass().getSimpleName() : "null"); - return List.of(result); - } - - } catch (Exception e) { - log.error("Magic脚本批量执行失败: {}", e.getMessage(), e); - throw new RuntimeException("Magic脚本批量执行失败: " + e.getMessage(), e); - } - } - - /** 验证Magic脚本语法 */ - public boolean validateScript(String script) { - try { - if (StrUtil.isBlank(script)) { - return false; - } - - // 创建脚本并编译 - MagicScript magicScript = MagicScript.create(script, null); - magicScript.compile(); - - return true; - } catch (Exception e) { - log.error("Magic脚本验证失败: {}", e.getMessage(), e); - return false; - } - } - - /** 构建脚本上下文 */ - private MagicScriptContext buildScriptContext( - BaseUPRequest request, DataBridgeConfig config, ResourceConnection connection) { - MagicScriptContext context = new MagicScriptContext(); - - // 基础数据 - context.set("request", request); - context.set("config", config); - context.set("connection", connection); - - // 设备信息 - if (request.getIoTDeviceDTO() != null) { - context.set("deviceId", request.getIoTDeviceDTO().getDeviceId()); - context.set("productKey", request.getIoTDeviceDTO().getProductKey()); + } catch (Exception e) { + log.error("Magic脚本iotToYour执行失败: {}", e.getMessage(), e); + throw new RuntimeException("Magic脚本iotToYour执行失败: " + e.getMessage(), e); + } } - // 消息信息 - context.set("messageType", request.getMessageType().name()); - context.set("timestamp", System.currentTimeMillis()); - context.set("properties", request.getProperties()); + /** + * 执行数据桥接Magic脚本 - 输入方向 (外部系统 -> IoT) + */ + public Object executeYourToIotScript( + String script, Object externalData, DataBridgeConfig config, ResourceConnection connection) { + try { + // 构建脚本上下文 + MagicScriptContext context = buildYourToIotScriptContext(externalData, config, connection); - // 配置信息 - if (StrUtil.isNotBlank(config.getConfig())) { - try { - context.set("configJson", cn.hutool.json.JSONUtil.parseObj(config.getConfig())); - } catch (Exception e) { - log.warn("配置JSON解析失败: {}", e.getMessage()); - } + // 获取或创建脚本 + MagicScript magicScript = getOrCreateScript(script, "yourToIot"); + + // 执行脚本 + Object result = magicScript.execute(context); + + log.debug( + "Magic脚本yourToIot执行成功,返回类型: {}", + result != null ? result.getClass().getSimpleName() : "null"); + return result; + + } catch (Exception e) { + log.error("Magic脚本yourToIot执行失败: {}", e.getMessage(), e); + throw new RuntimeException("Magic脚本yourToIot执行失败: " + e.getMessage(), e); + } } - return context; - } + /** + * 批量执行数据桥接Magic脚本 + */ + public List executeBatchDataBridgeScript( + String script, + List requests, + DataBridgeConfig config, + ResourceConnection connection) { + try { + // 构建脚本上下文 + MagicScriptContext context = buildBatchScriptContext(requests, config, connection); - /** 构建批量脚本上下文 */ - private MagicScriptContext buildBatchScriptContext( - List requests, DataBridgeConfig config, ResourceConnection connection) { - MagicScriptContext context = new MagicScriptContext(); + // 获取或创建脚本 + MagicScript magicScript = getOrCreateScript(script, "iotToYour"); - // 批量数据 - context.set("requests", requests); - context.set("config", config); - context.set("connection", connection); - context.set("batchSize", requests.size()); + // 执行脚本 + Object result = magicScript.execute(context); - // 配置信息 - if (StrUtil.isNotBlank(config.getConfig())) { - try { - context.set("configJson", cn.hutool.json.JSONUtil.parseObj(config.getConfig())); - } catch (Exception e) { - log.warn("配置JSON解析失败: {}", e.getMessage()); - } + // 处理返回结果 + if (result instanceof List) { + @SuppressWarnings("unchecked") + List resultList = (List) result; + log.debug("Magic脚本批量执行成功,返回数量: {}", resultList.size()); + return resultList; + } else { + log.warn( + "Magic脚本批量执行返回类型不是List: {}", + result != null ? result.getClass().getSimpleName() : "null"); + return List.of(result); + } + + } catch (Exception e) { + log.error("Magic脚本批量执行失败: {}", e.getMessage(), e); + throw new RuntimeException("Magic脚本批量执行失败: " + e.getMessage(), e); + } } - return context; - } + /** + * 验证Magic脚本语法 + */ + public boolean validateScript(String script) { + try { + if (StrUtil.isBlank(script)) { + return false; + } - /** 构建yourToIot脚本上下文 */ - private MagicScriptContext buildYourToIotScriptContext( - Object externalData, DataBridgeConfig config, ResourceConnection connection) { - MagicScriptContext context = new MagicScriptContext(); + // 容错:反解码可能被 XSS 过滤器转义的脚本 + script = htmlDecode(script); - // 外部数据 - context.set("externalData", externalData); - context.set("config", config); - context.set("connection", connection); - - // 配置信息 - if (StrUtil.isNotBlank(config.getConfig())) { - try { - context.set("configJson", cn.hutool.json.JSONUtil.parseObj(config.getConfig())); - } catch (Exception e) { - log.warn("配置JSON解析失败: {}", e.getMessage()); - } - } - - return context; - } - - /** 获取或创建脚本 */ - private MagicScript getOrCreateScript(String script, String method) { - // 使用脚本内容和方法的hash作为缓存key - String cacheKey = String.valueOf((script + method).hashCode()); - - return scriptCache.computeIfAbsent( - cacheKey, - key -> { - try { - // 包装脚本,添加必要的函数调用 - String wrappedScript = wrapScript(script, method); - - // 创建并编译脚本 - MagicScript magicScript = MagicScript.create(wrappedScript, null); + // 创建脚本并编译 + MagicScript magicScript = MagicScript.create(script, null); magicScript.compile(); - log.debug("创建新的Magic脚本,缓存key: {}, 方法: {}", cacheKey, method); - return magicScript; - - } catch (Exception e) { - log.error("创建Magic脚本失败: {}", e.getMessage(), e); - throw new RuntimeException("创建Magic脚本失败: " + e.getMessage(), e); - } - }); - } - - /** 包装脚本,添加必要的函数调用 */ - private String wrapScript(String script, String method) { - // 如果脚本已经包含return语句,直接返回 - if (script.contains("return")) { - return script; + return true; + } catch (Exception e) { + log.error("Magic脚本验证失败: {}", e.getMessage(), e); + return false; + } } - // 根据方法类型添加对应的return语句 - return switch (method) { - case "iotToYour" -> script + "\nreturn iotToYour(request, config, connection);"; - case "yourToIot" -> script + "\nreturn yourToIot(externalData, config, connection);"; - default -> script + "\nreturn processData(request, config, connection);"; - }; - } + /** + * 构建脚本上下文 + */ + private MagicScriptContext buildScriptContext( + BaseUPRequest request, DataBridgeConfig config, ResourceConnection connection) { + MagicScriptContext context = new MagicScriptContext(); - /** 清除脚本缓存 */ - public void clearCache() { - scriptCache.clear(); - log.info("Magic脚本缓存已清除"); - } + // 基础数据 + context.set("request", request); + context.set("config", config); + context.set("connection", connection); - /** 获取缓存大小 */ - public int getCacheSize() { - return scriptCache.size(); - } + // 设备信息 + if (request.getIoTDeviceDTO() != null) { + context.set("deviceId", request.getIoTDeviceDTO().getDeviceId()); + context.set("productKey", request.getIoTDeviceDTO().getProductKey()); + } + + // 消息信息 + context.set("messageType", request.getMessageType().name()); + context.set("timestamp", System.currentTimeMillis()); + context.set("properties", request.getProperties()); + + // 配置信息 + if (StrUtil.isNotBlank(config.getConfig())) { + try { + context.set("configJson", cn.hutool.json.JSONUtil.parseObj(config.getConfig())); + } catch (Exception e) { + log.warn("配置JSON解析失败: {}", e.getMessage()); + } + } + + return context; + } + + /** + * 构建批量脚本上下文 + */ + private MagicScriptContext buildBatchScriptContext( + List requests, DataBridgeConfig config, ResourceConnection connection) { + MagicScriptContext context = new MagicScriptContext(); + + // 批量数据 + context.set("requests", requests); + context.set("config", config); + context.set("connection", connection); + context.set("batchSize", requests.size()); + + // 配置信息 + if (StrUtil.isNotBlank(config.getConfig())) { + try { + context.set("configJson", cn.hutool.json.JSONUtil.parseObj(config.getConfig())); + } catch (Exception e) { + log.warn("配置JSON解析失败: {}", e.getMessage()); + } + } + + return context; + } + + /** + * 构建yourToIot脚本上下文 + */ + private MagicScriptContext buildYourToIotScriptContext( + Object externalData, DataBridgeConfig config, ResourceConnection connection) { + MagicScriptContext context = new MagicScriptContext(); + + // 外部数据 + context.set("externalData", externalData); + context.set("config", config); + context.set("connection", connection); + + // 配置信息 + if (StrUtil.isNotBlank(config.getConfig())) { + try { + context.set("configJson", cn.hutool.json.JSONUtil.parseObj(config.getConfig())); + } catch (Exception e) { + log.warn("配置JSON解析失败: {}", e.getMessage()); + } + } + + return context; + } + + /** + * 获取或创建脚本 + */ + private MagicScript getOrCreateScript(String script, String method) { + // 容错:反解码可能被 XSS 过滤器转义的脚本 + final String decodedScript = htmlDecode(script); + // 使用脚本内容和方法的hash作为缓存key + String cacheKey = String.valueOf((decodedScript + method).hashCode()); + + return scriptCache.computeIfAbsent( + cacheKey, + key -> { + try { + // 包装脚本,添加必要的函数调用 + String wrappedScript = wrapScript(decodedScript, method); + + // 创建并编译脚本 + MagicScript magicScript = MagicScript.create(wrappedScript, null); + magicScript.compile(); + + log.debug("创建新的Magic脚本,缓存key: {}, 方法: {}", cacheKey, method); + return magicScript; + + } catch (Exception e) { + log.error("创建Magic脚本失败: {}", e.getMessage(), e); + throw new RuntimeException("创建Magic脚本失败: " + e.getMessage(), e); + } + }); + } + + /** + * 包装脚本,添加必要的函数调用 + * + * 处理以下场景: + * 1. 脚本只定义函数,无调用 -> 自动添加调用语句 + * 2. 脚本已有顶层 return 调用 -> 直接返回 + * 3. 脚本是纯逻辑,无函数定义 -> 包装成脚本体 + */ + private String wrapScript(String script, String method) { + String trimmed = script.trim(); + + // 检查脚本是否为函数定义(var/function iotToYour/yourToIot) + boolean isFunctionDef = trimmed.matches("(?s).*(var|function)\\s+" + method + "\\s*[=(].*"); + + // 检查是否已有顶层的函数调用 return 语句 + boolean hasTopLevelReturn = trimmed.matches("(?s).*\\breturn\\s+" + method + "\\s*\\(.*"); + + // 如果是函数定义且没有调用,添加调用 + if (isFunctionDef && !hasTopLevelReturn) { + return script + "\nreturn " + method + "(request, config, connection);"; + } + + // 如果已有顶层 return 调用,直接返回 + if (hasTopLevelReturn) { + return script; + } + + // 默认场景:脚本是纯逻辑,需要包装调用 + return switch (method) { + case "iotToYour" -> script + "\nreturn iotToYour(request, config, connection);"; + case "yourToIot" -> script + "\nreturn yourToIot(externalData, config, connection);"; + default -> script + "\nreturn processData(request, config, connection);"; + }; + } + + /** + * 清除脚本缓存 + */ + public void clearCache() { + scriptCache.clear(); + log.info("Magic脚本缓存已清除"); + } + + /** + * 获取缓存大小 + */ + public int getCacheSize() { + return scriptCache.size(); + } + + /** + * HTML 实体反解码,避免 =>、< 等影响脚本解析 + */ + private String htmlDecode(String text) { + if (text == null) return null; + String cur = text; + for (int i = 0; i < 2; i++) { + String prev = cur; + cur = cur + .replace("<", "<") + .replace(">", ">") + .replace(""", "\"") + .replace("'", "'") + .replace("&", "&"); + if (prev.equals(cur)) break; + } + return cur; + } }