【代码评审】IoT:产品脚本的逻辑
This commit is contained in:
parent
0ae893272b
commit
eeb1dc4a07
|
@ -43,4 +43,5 @@ public enum IotProductScriptLanguageEnum implements ArrayValuable<String> {
|
|||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
}
|
|
@ -6,6 +6,7 @@ import lombok.Getter;
|
|||
|
||||
import java.util.Arrays;
|
||||
|
||||
// TODO @haohao:要不复用 commonstatus?
|
||||
/**
|
||||
* IoT 产品脚本状态枚举
|
||||
*
|
||||
|
|
|
@ -8,6 +8,7 @@ import lombok.*;
|
|||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
// TODO @haohao:类似阿里云的脚本,貌似是一个?这个可以简化么?【微信讨论哈】类似阿里云,貌似是加了个 topic?
|
||||
/**
|
||||
* IoT 产品脚本信息 DO
|
||||
*
|
||||
|
|
|
@ -26,6 +26,7 @@ import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionU
|
|||
import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.PRODUCT_NOT_EXISTS;
|
||||
import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.PRODUCT_SCRIPT_NOT_EXISTS;
|
||||
|
||||
// TODO @芋艿:后续再 review 哈!
|
||||
/**
|
||||
* IoT 产品脚本信息 Service 实现类
|
||||
*
|
||||
|
|
|
@ -5,17 +5,15 @@ import org.springframework.boot.SpringApplication;
|
|||
import org.springframework.boot.WebApplicationType;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
// TODO @芋艿:是不是搞成 cn.iocoder.yudao.module.iot.plugin?或者 common、script 要自动配置
|
||||
/**
|
||||
* 独立运行入口
|
||||
*/
|
||||
@Slf4j
|
||||
@SpringBootApplication(scanBasePackages = {
|
||||
// common 的包
|
||||
"cn.iocoder.yudao.module.iot.plugin.common",
|
||||
// http 的包
|
||||
"cn.iocoder.yudao.module.iot.plugin.http",
|
||||
// script 的包
|
||||
"cn.iocoder.yudao.module.iot.plugin.script"
|
||||
"cn.iocoder.yudao.module.iot.plugin.common", // common 的包
|
||||
"cn.iocoder.yudao.module.iot.plugin.http", // http 的包
|
||||
"cn.iocoder.yudao.module.iot.plugin.script" // script 的包
|
||||
})
|
||||
public class IotHttpPluginApplication {
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ import java.util.Map;
|
|||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* HTTP协议脚本处理服务
|
||||
* HTTP 协议脚本处理服务
|
||||
* 用于管理和执行设备数据解析脚本
|
||||
*
|
||||
* @author haohao
|
||||
|
@ -25,8 +25,10 @@ public class HttpScriptService {
|
|||
|
||||
private final ScriptService scriptService;
|
||||
|
||||
// TODO @haohao:后续可以考虑放到 guava 缓存
|
||||
// TODO @haohao:可能要抽一个 script factory 之类的?方便多个 emqx、http 之类复用?
|
||||
/**
|
||||
* 脚本缓存,按产品Key缓存脚本内容
|
||||
* 脚本缓存,按产品 Key 缓存脚本内容
|
||||
*/
|
||||
private final Map<String, String> scriptCache = new ConcurrentHashMap<>();
|
||||
|
||||
|
@ -76,6 +78,7 @@ public class HttpScriptService {
|
|||
productKey, deviceName, e);
|
||||
}
|
||||
|
||||
// TODO @芋艿:解析失败,是不是不能返回空?!
|
||||
// 解析失败,返回空数据
|
||||
return new HashMap<>();
|
||||
}
|
||||
|
@ -115,13 +118,14 @@ public class HttpScriptService {
|
|||
productKey, deviceName, identifier, payload, result);
|
||||
|
||||
// 处理结果
|
||||
// TODO @haohao:处理结果,可以复用么?
|
||||
if (result instanceof Map) {
|
||||
return (Map<String, Object>) result;
|
||||
} else if (result instanceof String) {
|
||||
try {
|
||||
return new JsonObject((String) result).getMap();
|
||||
} catch (Exception e) {
|
||||
log.warn("[parseEventData][脚本返回的字符串不是有效的JSON] result:{}", result);
|
||||
log.warn("[parseEventData][脚本返回的字符串不是有效的 JSON] result:{}", result);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
|
@ -129,6 +133,7 @@ public class HttpScriptService {
|
|||
productKey, deviceName, identifier, e);
|
||||
}
|
||||
|
||||
// TODO @芋艿:解析失败,是不是不能返回空?!
|
||||
// 解析失败,返回空数据
|
||||
return new HashMap<>();
|
||||
}
|
||||
|
@ -191,10 +196,11 @@ public class HttpScriptService {
|
|||
/**
|
||||
* 设置产品解析脚本
|
||||
*
|
||||
* @param productKey 产品Key
|
||||
* @param productKey 产品 Key
|
||||
* @param script 脚本内容
|
||||
*/
|
||||
public void setScript(String productKey, String script) {
|
||||
// TODO @haohao:if return 会好点哈
|
||||
if (StrUtil.isNotBlank(productKey) && StrUtil.isNotBlank(script)) {
|
||||
// 验证脚本是否有效
|
||||
if (scriptService.validateScript("js", script)) {
|
||||
|
@ -209,13 +215,14 @@ public class HttpScriptService {
|
|||
/**
|
||||
* 清除产品解析脚本
|
||||
*
|
||||
* @param productKey 产品Key
|
||||
* @param productKey 产品 Key
|
||||
*/
|
||||
public void clearScript(String productKey) {
|
||||
if (StrUtil.isNotBlank(productKey)) {
|
||||
scriptCache.remove(productKey);
|
||||
log.info("[clearScript][清除产品:{}的解析脚本]", productKey);
|
||||
if (StrUtil.isBlank(productKey)) {
|
||||
return;
|
||||
}
|
||||
scriptCache.remove(productKey);
|
||||
log.info("[clearScript][清除产品({})的解析脚本]", productKey);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -225,4 +232,5 @@ public class HttpScriptService {
|
|||
scriptCache.clear();
|
||||
log.info("[clearAllScripts][清除所有脚本缓存]");
|
||||
}
|
||||
|
||||
}
|
|
@ -35,8 +35,7 @@ public class IotDeviceUpstreamServer {
|
|||
router.route().handler(BodyHandler.create()); // 处理 Body
|
||||
|
||||
// 使用统一的 Handler 处理所有上行请求
|
||||
IotDeviceUpstreamVertxHandler upstreamHandler = new IotDeviceUpstreamVertxHandler(deviceUpstreamApi,
|
||||
applicationContext);
|
||||
IotDeviceUpstreamVertxHandler upstreamHandler = new IotDeviceUpstreamVertxHandler(deviceUpstreamApi, applicationContext);
|
||||
router.post(IotDeviceUpstreamVertxHandler.PROPERTY_PATH).handler(upstreamHandler);
|
||||
router.post(IotDeviceUpstreamVertxHandler.EVENT_PATH).handler(upstreamHandler);
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package cn.iocoder.yudao.module.iot.plugin.http.upstream.router;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.hutool.core.util.ObjUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
|
@ -150,10 +151,11 @@ public class IotDeviceUpstreamVertxHandler implements Handler<RoutingContext> {
|
|||
Map<String, Object> properties = scriptService.parsePropertyData(productKey, deviceName, body);
|
||||
|
||||
// 如果脚本解析结果为空,使用默认解析逻辑
|
||||
if (properties.isEmpty()) {
|
||||
// TODO @芋艿:注释说明一下,为什么要这么处理?
|
||||
if (CollUtil.isNotEmpty(properties)) {
|
||||
properties = new HashMap<>();
|
||||
Map<String, Object> params = body.getJsonObject("params") != null ? body.getJsonObject("params").getMap()
|
||||
: null;
|
||||
Map<String, Object> params = body.getJsonObject("params") != null ?
|
||||
body.getJsonObject("params").getMap() : null;
|
||||
if (params != null) {
|
||||
// 将标准格式的 params 转换为平台需要的 properties 格式
|
||||
for (Map.Entry<String, Object> entry : params.entrySet()) {
|
||||
|
@ -193,7 +195,7 @@ public class IotDeviceUpstreamVertxHandler implements Handler<RoutingContext> {
|
|||
Map<String, Object> params = scriptService.parseEventData(productKey, deviceName, identifier, body);
|
||||
|
||||
// 如果脚本解析结果为空,使用默认解析逻辑
|
||||
if (params.isEmpty()) {
|
||||
if (CollUtil.isNotEmpty(params)) {
|
||||
if (body.containsKey("params")) {
|
||||
params = body.getJsonObject("params").getMap();
|
||||
} else {
|
||||
|
|
|
@ -9,6 +9,7 @@ import org.springframework.stereotype.Component;
|
|||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
// TODO @haohao:写到单测类里;
|
||||
/**
|
||||
* 脚本使用示例类
|
||||
*/
|
||||
|
|
|
@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.iot.plugin.script.service.ScriptServiceImpl;
|
|||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
// TODO @haohao:这个模块,是不是融合到 plugin-common 里哈?
|
||||
/**
|
||||
* 脚本模块配置类
|
||||
*/
|
||||
|
@ -31,7 +32,7 @@ public class ScriptConfiguration {
|
|||
@Bean
|
||||
public ScriptService scriptService(ScriptEngineFactory engineFactory) {
|
||||
ScriptServiceImpl service = new ScriptServiceImpl();
|
||||
// 如果有其他配置可以在这里设置
|
||||
// TODO @haohao:如果有其他配置可以在这里设置
|
||||
return service;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
package cn.iocoder.yudao.module.iot.plugin.script.context;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
|
@ -11,18 +13,22 @@ public class PluginScriptContext implements ScriptContext {
|
|||
/**
|
||||
* 上下文参数
|
||||
*/
|
||||
@Getter
|
||||
private final Map<String, Object> parameters = new HashMap<>();
|
||||
|
||||
/**
|
||||
* 上下文函数
|
||||
*/
|
||||
@Getter
|
||||
private final Map<String, Object> functions = new HashMap<>();
|
||||
|
||||
/**
|
||||
* 日志函数接口
|
||||
*/
|
||||
public interface LogFunction {
|
||||
|
||||
void log(String message);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -46,16 +52,6 @@ public class PluginScriptContext implements ScriptContext {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getParameters() {
|
||||
return parameters;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getFunctions() {
|
||||
return functions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setParameter(String key, Object value) {
|
||||
parameters.put(key, value);
|
||||
|
@ -71,6 +67,7 @@ public class PluginScriptContext implements ScriptContext {
|
|||
functions.put(name, function);
|
||||
}
|
||||
|
||||
// TODO @haohao:setParameters?这样的话,with 都是一些比较个性的参数
|
||||
/**
|
||||
* 批量设置参数
|
||||
*
|
||||
|
@ -87,11 +84,13 @@ public class PluginScriptContext implements ScriptContext {
|
|||
/**
|
||||
* 添加设备相关的上下文参数
|
||||
*
|
||||
* @param deviceId 设备ID
|
||||
* @param deviceId 设备 ID
|
||||
* @param deviceData 设备数据
|
||||
* @return 当前上下文对象
|
||||
*/
|
||||
// TODO @haohao:是不是加个 (String productKey, String deviceName, Map<String, Object> deviceData) {
|
||||
public PluginScriptContext withDeviceContext(String deviceId, Map<String, Object> deviceData) {
|
||||
// TODO @haohao:deviceId 一般是分开,还是合并哈?
|
||||
parameters.put("deviceId", deviceId);
|
||||
parameters.put("deviceData", deviceData);
|
||||
return this;
|
||||
|
@ -110,6 +109,7 @@ public class PluginScriptContext implements ScriptContext {
|
|||
return this;
|
||||
}
|
||||
|
||||
// TODO @haohao:setParameter 可以融合哈?
|
||||
/**
|
||||
* 设置单个参数
|
||||
*
|
||||
|
@ -121,4 +121,5 @@ public class PluginScriptContext implements ScriptContext {
|
|||
parameters.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
|
@ -37,6 +37,7 @@ public interface ScriptContext {
|
|||
*/
|
||||
Object getParameter(String key);
|
||||
|
||||
// TODO @haohao:这个要不也是 setFunction
|
||||
/**
|
||||
* 注册函数
|
||||
*
|
||||
|
@ -44,4 +45,5 @@ public interface ScriptContext {
|
|||
* @param function 函数对象
|
||||
*/
|
||||
void registerFunction(String name, Object function);
|
||||
}
|
||||
|
||||
}
|
|
@ -48,4 +48,4 @@ public abstract class AbstractScriptEngine {
|
|||
public void setSandbox(ScriptSandbox sandbox) {
|
||||
this.sandbox = sandbox;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -12,8 +12,8 @@ import java.util.concurrent.Callable;
|
|||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* JavaScript脚本引擎实现
|
||||
* 使用JSR-223 Nashorn脚本引擎
|
||||
* JavaScript 脚本引擎实现
|
||||
* 使用 JSR-223 Nashorn 脚本引擎
|
||||
*/
|
||||
@Slf4j
|
||||
public class JsScriptEngine extends AbstractScriptEngine {
|
||||
|
@ -24,7 +24,7 @@ public class JsScriptEngine extends AbstractScriptEngine {
|
|||
private static final long DEFAULT_TIMEOUT_MS = 5000;
|
||||
|
||||
/**
|
||||
* JavaScript引擎名称
|
||||
* JavaScript 引擎名称
|
||||
*/
|
||||
private static final String JS_ENGINE_NAME = "nashorn";
|
||||
|
||||
|
@ -45,25 +45,24 @@ public class JsScriptEngine extends AbstractScriptEngine {
|
|||
|
||||
@Override
|
||||
public void init() {
|
||||
log.info("初始化JavaScript脚本引擎");
|
||||
log.info("初始化 JavaScript 脚本引擎");
|
||||
|
||||
// 创建脚本引擎管理器
|
||||
engineManager = new ScriptEngineManager();
|
||||
|
||||
// 获取JavaScript引擎
|
||||
// 获取 JavaScript 引擎
|
||||
engine = engineManager.getEngineByName(JS_ENGINE_NAME);
|
||||
if (engine == null) {
|
||||
log.error("无法创建JavaScript引擎,尝试使用JavaScript名称获取");
|
||||
log.error("无法创建JavaScript引擎,尝试使用 JavaScript 名称获取");
|
||||
engine = engineManager.getEngineByName("JavaScript");
|
||||
}
|
||||
|
||||
if (engine == null) {
|
||||
throw new IllegalStateException("无法创建JavaScript引擎,请检查环境配置");
|
||||
throw new IllegalStateException("无法创建 JavaScript 引擎,请检查环境配置");
|
||||
}
|
||||
|
||||
log.info("成功创建JavaScript引擎: {}", engine.getClass().getName());
|
||||
|
||||
// 默认使用JS沙箱
|
||||
// 默认使用 JS 沙箱
|
||||
if (sandbox == null) {
|
||||
setSandbox(new JsSandbox());
|
||||
}
|
||||
|
@ -99,7 +98,7 @@ public class JsScriptEngine extends AbstractScriptEngine {
|
|||
// 执行脚本
|
||||
return engine.eval(script, bindings);
|
||||
} catch (ScriptException e) {
|
||||
log.error("执行JavaScript脚本异常: {}", e.getMessage());
|
||||
log.error("执行 JavaScript 脚本异常: {}", e.getMessage());
|
||||
throw new RuntimeException("脚本执行异常: " + e.getMessage(), e);
|
||||
}
|
||||
};
|
||||
|
@ -136,7 +135,7 @@ public class JsScriptEngine extends AbstractScriptEngine {
|
|||
// 执行脚本
|
||||
return engine.eval(script, bindings);
|
||||
} catch (ScriptException e) {
|
||||
log.error("执行JavaScript脚本异常: {}", e.getMessage());
|
||||
log.error("执行 JavaScript 脚本异常: {}", e.getMessage());
|
||||
throw new RuntimeException("脚本执行异常: " + e.getMessage(), e);
|
||||
}
|
||||
};
|
||||
|
@ -152,9 +151,10 @@ public class JsScriptEngine extends AbstractScriptEngine {
|
|||
|
||||
@Override
|
||||
public void destroy() {
|
||||
log.info("销毁JavaScript脚本引擎");
|
||||
log.info("销毁 JavaScript 脚本引擎");
|
||||
cachedScripts.clear();
|
||||
engine = null;
|
||||
engineManager = null;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
package cn.iocoder.yudao.module.iot.plugin.script.engine;
|
||||
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
|
@ -11,12 +12,12 @@ import org.springframework.stereotype.Component;
|
|||
public class ScriptEngineFactory {
|
||||
|
||||
/**
|
||||
* 创建JavaScript脚本引擎
|
||||
* 创建 JavaScript 脚本引擎
|
||||
*
|
||||
* @return JavaScript脚本引擎
|
||||
*/
|
||||
public JsScriptEngine createJsEngine() {
|
||||
log.debug("创建JavaScript脚本引擎");
|
||||
log.debug("创建 JavaScript 脚本引擎");
|
||||
return new JsScriptEngine();
|
||||
}
|
||||
|
||||
|
@ -27,10 +28,7 @@ public class ScriptEngineFactory {
|
|||
* @return 脚本引擎
|
||||
*/
|
||||
public AbstractScriptEngine createEngine(String scriptType) {
|
||||
if (scriptType == null || scriptType.isEmpty()) {
|
||||
throw new IllegalArgumentException("脚本类型不能为空");
|
||||
}
|
||||
|
||||
Assert.notBlank(scriptType, "脚本类型不能为空");
|
||||
switch (scriptType.toLowerCase()) {
|
||||
case "js":
|
||||
case "javascript":
|
||||
|
@ -40,4 +38,5 @@ public class ScriptEngineFactory {
|
|||
throw new IllegalArgumentException("不支持的脚本类型: " + scriptType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
package cn.iocoder.yudao.module.iot.plugin.script.sandbox;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import javax.script.ScriptEngine;
|
||||
|
@ -8,8 +9,9 @@ import java.util.HashSet;
|
|||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
// TODO @haohao:这个是不是融合到 ScriptEngine 里
|
||||
/**
|
||||
* JavaScript脚本沙箱,限制脚本的执行权限
|
||||
* JavaScript 脚本沙箱,限制脚本的执行权限
|
||||
*/
|
||||
@Slf4j
|
||||
public class JsSandbox implements ScriptSandbox {
|
||||
|
@ -34,6 +36,7 @@ public class JsSandbox implements ScriptSandbox {
|
|||
"(?:\\bchild_process\\b)|" +
|
||||
"(?:\\bwindow\\b)");
|
||||
|
||||
// TODO @haohao:这个没用到哈。
|
||||
/**
|
||||
* 脚本执行超时时间(毫秒)
|
||||
*/
|
||||
|
@ -44,30 +47,28 @@ public class JsSandbox implements ScriptSandbox {
|
|||
if (!(engineContext instanceof ScriptEngine)) {
|
||||
throw new IllegalArgumentException("引擎上下文类型不正确,无法应用JavaScript沙箱");
|
||||
}
|
||||
|
||||
ScriptEngine engine = (ScriptEngine) engineContext;
|
||||
|
||||
// 在Nashorn引擎中,可以通过以下方式设置安全限制
|
||||
// 在 Nashorn 引擎中,可以通过以下方式设置安全限制
|
||||
try {
|
||||
// 设置严格模式
|
||||
String securityPrefix = "'use strict';\n";
|
||||
|
||||
// 禁用Java.type等访问系统资源的功能
|
||||
// 禁用 Java.type 等访问系统资源的功能
|
||||
engine.eval("var Java = undefined;");
|
||||
engine.eval("var JavaImporter = undefined;");
|
||||
engine.eval("var Packages = undefined;");
|
||||
|
||||
// 增强安全控制可以在这里添加
|
||||
log.debug("已应用JavaScript安全沙箱限制");
|
||||
|
||||
log.debug("已应用 JavaScript 安全沙箱限制");
|
||||
} catch (Exception e) {
|
||||
log.warn("应用JavaScript沙箱限制失败: {}", e.getMessage());
|
||||
log.warn("应用 JavaScript 沙箱限制失败: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validateScript(String script) {
|
||||
if (script == null || script.isEmpty()) {
|
||||
if (StrUtil.isNotEmpty(script)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -86,11 +87,12 @@ public class JsSandbox implements ScriptSandbox {
|
|||
}
|
||||
|
||||
// 脚本长度限制
|
||||
if (script.length() > 1024 * 100) { // 限制100KB
|
||||
if (script.length() > 1024 * 100) { // 限制 100 KB
|
||||
log.warn("脚本太大,超过了限制");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
|
@ -20,4 +20,5 @@ public interface ScriptSandbox {
|
|||
* @return 是否安全
|
||||
*/
|
||||
boolean validateScript(String script);
|
||||
}
|
||||
|
||||
}
|
|
@ -12,7 +12,7 @@ public interface ScriptService {
|
|||
/**
|
||||
* 执行脚本
|
||||
*
|
||||
* @param scriptType 脚本类型(如js、groovy等)
|
||||
* @param scriptType 脚本类型(如 js、groovy 等)
|
||||
* @param script 脚本内容
|
||||
* @param context 脚本上下文
|
||||
* @return 脚本执行结果
|
||||
|
@ -22,7 +22,7 @@ public interface ScriptService {
|
|||
/**
|
||||
* 执行脚本
|
||||
*
|
||||
* @param scriptType 脚本类型(如js、groovy等)
|
||||
* @param scriptType 脚本类型(如 js、groovy 等)
|
||||
* @param script 脚本内容
|
||||
* @param params 脚本参数
|
||||
* @return 脚本执行结果
|
||||
|
@ -30,7 +30,7 @@ public interface ScriptService {
|
|||
Object executeScript(String scriptType, String script, Map<String, Object> params);
|
||||
|
||||
/**
|
||||
* 执行JavaScript脚本
|
||||
* 执行 JavaScript 脚本
|
||||
*
|
||||
* @param script 脚本内容
|
||||
* @param context 脚本上下文
|
||||
|
@ -39,7 +39,7 @@ public interface ScriptService {
|
|||
Object executeJavaScript(String script, ScriptContext context);
|
||||
|
||||
/**
|
||||
* 执行JavaScript脚本
|
||||
* 执行 JavaScript 脚本
|
||||
*
|
||||
* @param script 脚本内容
|
||||
* @param params 脚本参数
|
||||
|
@ -55,4 +55,5 @@ public interface ScriptService {
|
|||
* @return 脚本是否安全
|
||||
*/
|
||||
boolean validateScript(String scriptType, String script);
|
||||
}
|
||||
|
||||
}
|
|
@ -38,6 +38,7 @@ public class ScriptServiceImpl implements ScriptService {
|
|||
@PostConstruct
|
||||
public void init() {
|
||||
// 初始化常用的脚本引擎和沙箱
|
||||
// TODO @haohao:js 是不是要枚举下哈。
|
||||
getEngine("js");
|
||||
sandboxCache.put("js", new JsSandbox());
|
||||
}
|
||||
|
@ -49,6 +50,7 @@ public class ScriptServiceImpl implements ScriptService {
|
|||
try {
|
||||
engine.destroy();
|
||||
} catch (Exception e) {
|
||||
// TODO @haohao:engine 类名
|
||||
log.error("销毁脚本引擎失败", e);
|
||||
}
|
||||
}
|
||||
|
@ -58,6 +60,7 @@ public class ScriptServiceImpl implements ScriptService {
|
|||
|
||||
@Override
|
||||
public Object executeScript(String scriptType, String script, ScriptContext context) {
|
||||
// TODO @haohao:可以使用 hutool assert
|
||||
if (scriptType == null || script == null) {
|
||||
throw new IllegalArgumentException("脚本类型和内容不能为空");
|
||||
}
|
||||
|
@ -74,6 +77,7 @@ public class ScriptServiceImpl implements ScriptService {
|
|||
// 执行脚本
|
||||
return engine.execute(script, context);
|
||||
} catch (Exception e) {
|
||||
// TODO @haohao:最好把 e 堆栈出来哈;然后,engine 类名
|
||||
log.error("执行脚本失败: {}", e.getMessage());
|
||||
throw new RuntimeException("执行脚本失败: " + e.getMessage(), e);
|
||||
}
|
||||
|
@ -83,16 +87,19 @@ public class ScriptServiceImpl implements ScriptService {
|
|||
public Object executeScript(String scriptType, String script, Map<String, Object> params) {
|
||||
// 创建默认上下文
|
||||
ScriptContext context = new PluginScriptContext(params);
|
||||
// 执行脚本
|
||||
return executeScript(scriptType, script, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object executeJavaScript(String script, ScriptContext context) {
|
||||
// TODO @haohao:枚举哈
|
||||
return executeScript("js", script, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object executeJavaScript(String script, Map<String, Object> params) {
|
||||
// TODO @haohao:枚举哈
|
||||
return executeScript("js", script, params);
|
||||
}
|
||||
|
||||
|
@ -100,7 +107,8 @@ public class ScriptServiceImpl implements ScriptService {
|
|||
public boolean validateScript(String scriptType, String script) {
|
||||
ScriptSandbox sandbox = sandboxCache.get(scriptType.toLowerCase());
|
||||
if (sandbox == null) {
|
||||
log.warn("找不到脚本类型[{}]对应的沙箱,使用默认JS沙箱", scriptType);
|
||||
// TODO @haohao:疑问,为啥默认 JsSandbox 哈?
|
||||
log.warn("[validateScript][找不到脚本类型[{}]对应的沙箱,使用默认 JS 沙箱]", scriptType);
|
||||
sandbox = new JsSandbox();
|
||||
sandboxCache.put(scriptType.toLowerCase(), sandbox);
|
||||
}
|
||||
|
@ -120,4 +128,4 @@ public class ScriptServiceImpl implements ScriptService {
|
|||
return engine;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,6 +6,9 @@ import lombok.extern.slf4j.Slf4j;
|
|||
import java.util.Map;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
// TODO @haohao:【重要】 ScriptUtil.createGroovyEngine() 可以服用 hutool 的封装么?
|
||||
// TODO @haohao:【重要】 js 引擎,可能要看下 jdk8 的兼容性;
|
||||
// TODO @haohao:【重要】我们要不 script 配置的时候,支持 scriptType?!感觉会更通用一些???groovy、python、js
|
||||
/**
|
||||
* 脚本工具类,提供执行脚本的辅助方法
|
||||
*/
|
||||
|
@ -66,6 +69,7 @@ public class ScriptUtils {
|
|||
* 关闭工具类的线程池
|
||||
*/
|
||||
public static void shutdown() {
|
||||
// TODO @芋艿:有没默认工具类,可以 shutdown
|
||||
SCRIPT_EXECUTOR.shutdown();
|
||||
try {
|
||||
if (!SCRIPT_EXECUTOR.awaitTermination(10, TimeUnit.SECONDS)) {
|
||||
|
@ -77,8 +81,9 @@ public class ScriptUtils {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO @芋艿:要不要使用 JsonUtils
|
||||
/**
|
||||
* 将JSON字符串转换为Map
|
||||
* 将 JSON 字符串转换为 Map
|
||||
*
|
||||
* @param json JSON字符串
|
||||
* @return Map对象,转换失败则返回null
|
||||
|
@ -86,19 +91,20 @@ public class ScriptUtils {
|
|||
@SuppressWarnings("unchecked")
|
||||
public static Map<String, Object> parseJson(String json) {
|
||||
try {
|
||||
// 使用hutool的JSONUtil工具类解析JSON
|
||||
return JSONUtil.toBean(json, Map.class);
|
||||
} catch (Exception e) {
|
||||
log.error("解析JSON失败: {}", e.getMessage());
|
||||
// TODO @haohao:json、e 都打印出来哈
|
||||
log.error("[parseJson][解析JSON失败: {}]", e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO @芋艿:要不要封装成 utils
|
||||
/**
|
||||
* 尝试将对象转换为整数
|
||||
*
|
||||
* @param obj 需要转换的对象
|
||||
* @return 转换后的整数,如果无法转换则返回null
|
||||
* @return 转换后的整数,如果无法转换则返回 null
|
||||
*/
|
||||
public static Integer toInteger(Object obj) {
|
||||
if (obj == null) {
|
||||
|
@ -122,6 +128,7 @@ public class ScriptUtils {
|
|||
return null;
|
||||
}
|
||||
|
||||
// TODO @芋艿:要不要封装成 utils
|
||||
/**
|
||||
* 尝试将对象转换为双精度浮点数
|
||||
*
|
||||
|
@ -158,10 +165,12 @@ public class ScriptUtils {
|
|||
* @return 如果两个数值相等则返回true,否则返回false
|
||||
*/
|
||||
public static boolean numbersEqual(Number a, Number b) {
|
||||
// TODO @haohao:NumberUtil.equals(1, 1D)
|
||||
if (a == null || b == null) {
|
||||
return a == b;
|
||||
}
|
||||
|
||||
return Math.abs(a.doubleValue() - b.doubleValue()) < 0.0000001;
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue