Commit eeb1dc4a077b451652dddde2cb1628082917daef
1 parent
0ae89327
【代码评审】IoT:产品脚本的逻辑
Showing
20 changed files
with
111 additions
and
75 deletions
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/product/IotProductScriptLanguageEnum.java
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/product/IotProductScriptStatusEnum.java
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/product/IotProductScriptDO.java
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductScriptServiceImpl.java
| ... | ... | @@ -26,6 +26,7 @@ import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionU |
| 26 | 26 | import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.PRODUCT_NOT_EXISTS; |
| 27 | 27 | import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.PRODUCT_SCRIPT_NOT_EXISTS; |
| 28 | 28 | |
| 29 | +// TODO @芋艿:后续再 review 哈! | |
| 29 | 30 | /** |
| 30 | 31 | * IoT 产品脚本信息 Service 实现类 |
| 31 | 32 | * | ... | ... |
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-http/src/main/java/cn/iocoder/yudao/module/iot/plugin/http/IotHttpPluginApplication.java
| ... | ... | @@ -5,17 +5,15 @@ import org.springframework.boot.SpringApplication; |
| 5 | 5 | import org.springframework.boot.WebApplicationType; |
| 6 | 6 | import org.springframework.boot.autoconfigure.SpringBootApplication; |
| 7 | 7 | |
| 8 | +// TODO @芋艿:是不是搞成 cn.iocoder.yudao.module.iot.plugin?或者 common、script 要自动配置 | |
| 8 | 9 | /** |
| 9 | 10 | * 独立运行入口 |
| 10 | 11 | */ |
| 11 | 12 | @Slf4j |
| 12 | 13 | @SpringBootApplication(scanBasePackages = { |
| 13 | - // common 的包 | |
| 14 | - "cn.iocoder.yudao.module.iot.plugin.common", | |
| 15 | - // http 的包 | |
| 16 | - "cn.iocoder.yudao.module.iot.plugin.http", | |
| 17 | - // script 的包 | |
| 18 | - "cn.iocoder.yudao.module.iot.plugin.script" | |
| 14 | + "cn.iocoder.yudao.module.iot.plugin.common", // common 的包 | |
| 15 | + "cn.iocoder.yudao.module.iot.plugin.http", // http 的包 | |
| 16 | + "cn.iocoder.yudao.module.iot.plugin.script" // script 的包 | |
| 19 | 17 | }) |
| 20 | 18 | public class IotHttpPluginApplication { |
| 21 | 19 | ... | ... |
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-http/src/main/java/cn/iocoder/yudao/module/iot/plugin/http/script/HttpScriptService.java
| ... | ... | @@ -13,7 +13,7 @@ import java.util.Map; |
| 13 | 13 | import java.util.concurrent.ConcurrentHashMap; |
| 14 | 14 | |
| 15 | 15 | /** |
| 16 | - * HTTP协议脚本处理服务 | |
| 16 | + * HTTP 协议脚本处理服务 | |
| 17 | 17 | * 用于管理和执行设备数据解析脚本 |
| 18 | 18 | * |
| 19 | 19 | * @author haohao |
| ... | ... | @@ -25,8 +25,10 @@ public class HttpScriptService { |
| 25 | 25 | |
| 26 | 26 | private final ScriptService scriptService; |
| 27 | 27 | |
| 28 | + // TODO @haohao:后续可以考虑放到 guava 缓存 | |
| 29 | + // TODO @haohao:可能要抽一个 script factory 之类的?方便多个 emqx、http 之类复用? | |
| 28 | 30 | /** |
| 29 | - * 脚本缓存,按产品Key缓存脚本内容 | |
| 31 | + * 脚本缓存,按产品 Key 缓存脚本内容 | |
| 30 | 32 | */ |
| 31 | 33 | private final Map<String, String> scriptCache = new ConcurrentHashMap<>(); |
| 32 | 34 | |
| ... | ... | @@ -76,6 +78,7 @@ public class HttpScriptService { |
| 76 | 78 | productKey, deviceName, e); |
| 77 | 79 | } |
| 78 | 80 | |
| 81 | + // TODO @芋艿:解析失败,是不是不能返回空?! | |
| 79 | 82 | // 解析失败,返回空数据 |
| 80 | 83 | return new HashMap<>(); |
| 81 | 84 | } |
| ... | ... | @@ -115,13 +118,14 @@ public class HttpScriptService { |
| 115 | 118 | productKey, deviceName, identifier, payload, result); |
| 116 | 119 | |
| 117 | 120 | // 处理结果 |
| 121 | + // TODO @haohao:处理结果,可以复用么? | |
| 118 | 122 | if (result instanceof Map) { |
| 119 | 123 | return (Map<String, Object>) result; |
| 120 | 124 | } else if (result instanceof String) { |
| 121 | 125 | try { |
| 122 | 126 | return new JsonObject((String) result).getMap(); |
| 123 | 127 | } catch (Exception e) { |
| 124 | - log.warn("[parseEventData][脚本返回的字符串不是有效的JSON] result:{}", result); | |
| 128 | + log.warn("[parseEventData][脚本返回的字符串不是有效的 JSON] result:{}", result); | |
| 125 | 129 | } |
| 126 | 130 | } |
| 127 | 131 | } catch (Exception e) { |
| ... | ... | @@ -129,6 +133,7 @@ public class HttpScriptService { |
| 129 | 133 | productKey, deviceName, identifier, e); |
| 130 | 134 | } |
| 131 | 135 | |
| 136 | + // TODO @芋艿:解析失败,是不是不能返回空?! | |
| 132 | 137 | // 解析失败,返回空数据 |
| 133 | 138 | return new HashMap<>(); |
| 134 | 139 | } |
| ... | ... | @@ -191,10 +196,11 @@ public class HttpScriptService { |
| 191 | 196 | /** |
| 192 | 197 | * 设置产品解析脚本 |
| 193 | 198 | * |
| 194 | - * @param productKey 产品Key | |
| 199 | + * @param productKey 产品 Key | |
| 195 | 200 | * @param script 脚本内容 |
| 196 | 201 | */ |
| 197 | 202 | public void setScript(String productKey, String script) { |
| 203 | + // TODO @haohao:if return 会好点哈 | |
| 198 | 204 | if (StrUtil.isNotBlank(productKey) && StrUtil.isNotBlank(script)) { |
| 199 | 205 | // 验证脚本是否有效 |
| 200 | 206 | if (scriptService.validateScript("js", script)) { |
| ... | ... | @@ -209,13 +215,14 @@ public class HttpScriptService { |
| 209 | 215 | /** |
| 210 | 216 | * 清除产品解析脚本 |
| 211 | 217 | * |
| 212 | - * @param productKey 产品Key | |
| 218 | + * @param productKey 产品 Key | |
| 213 | 219 | */ |
| 214 | 220 | public void clearScript(String productKey) { |
| 215 | - if (StrUtil.isNotBlank(productKey)) { | |
| 216 | - scriptCache.remove(productKey); | |
| 217 | - log.info("[clearScript][清除产品:{}的解析脚本]", productKey); | |
| 221 | + if (StrUtil.isBlank(productKey)) { | |
| 222 | + return; | |
| 218 | 223 | } |
| 224 | + scriptCache.remove(productKey); | |
| 225 | + log.info("[clearScript][清除产品({})的解析脚本]", productKey); | |
| 219 | 226 | } |
| 220 | 227 | |
| 221 | 228 | /** |
| ... | ... | @@ -225,4 +232,5 @@ public class HttpScriptService { |
| 225 | 232 | scriptCache.clear(); |
| 226 | 233 | log.info("[clearAllScripts][清除所有脚本缓存]"); |
| 227 | 234 | } |
| 235 | + | |
| 228 | 236 | } |
| 229 | 237 | \ No newline at end of file | ... | ... |
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-http/src/main/java/cn/iocoder/yudao/module/iot/plugin/http/upstream/IotDeviceUpstreamServer.java
| ... | ... | @@ -35,8 +35,7 @@ public class IotDeviceUpstreamServer { |
| 35 | 35 | router.route().handler(BodyHandler.create()); // 处理 Body |
| 36 | 36 | |
| 37 | 37 | // 使用统一的 Handler 处理所有上行请求 |
| 38 | - IotDeviceUpstreamVertxHandler upstreamHandler = new IotDeviceUpstreamVertxHandler(deviceUpstreamApi, | |
| 39 | - applicationContext); | |
| 38 | + IotDeviceUpstreamVertxHandler upstreamHandler = new IotDeviceUpstreamVertxHandler(deviceUpstreamApi, applicationContext); | |
| 40 | 39 | router.post(IotDeviceUpstreamVertxHandler.PROPERTY_PATH).handler(upstreamHandler); |
| 41 | 40 | router.post(IotDeviceUpstreamVertxHandler.EVENT_PATH).handler(upstreamHandler); |
| 42 | 41 | ... | ... |
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-http/src/main/java/cn/iocoder/yudao/module/iot/plugin/http/upstream/router/IotDeviceUpstreamVertxHandler.java
| 1 | 1 | package cn.iocoder.yudao.module.iot.plugin.http.upstream.router; |
| 2 | 2 | |
| 3 | +import cn.hutool.core.collection.CollUtil; | |
| 3 | 4 | import cn.hutool.core.util.IdUtil; |
| 4 | 5 | import cn.hutool.core.util.ObjUtil; |
| 5 | 6 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| ... | ... | @@ -150,10 +151,11 @@ public class IotDeviceUpstreamVertxHandler implements Handler<RoutingContext> { |
| 150 | 151 | Map<String, Object> properties = scriptService.parsePropertyData(productKey, deviceName, body); |
| 151 | 152 | |
| 152 | 153 | // 如果脚本解析结果为空,使用默认解析逻辑 |
| 153 | - if (properties.isEmpty()) { | |
| 154 | + // TODO @芋艿:注释说明一下,为什么要这么处理? | |
| 155 | + if (CollUtil.isNotEmpty(properties)) { | |
| 154 | 156 | properties = new HashMap<>(); |
| 155 | - Map<String, Object> params = body.getJsonObject("params") != null ? body.getJsonObject("params").getMap() | |
| 156 | - : null; | |
| 157 | + Map<String, Object> params = body.getJsonObject("params") != null ? | |
| 158 | + body.getJsonObject("params").getMap() : null; | |
| 157 | 159 | if (params != null) { |
| 158 | 160 | // 将标准格式的 params 转换为平台需要的 properties 格式 |
| 159 | 161 | for (Map.Entry<String, Object> entry : params.entrySet()) { |
| ... | ... | @@ -193,7 +195,7 @@ public class IotDeviceUpstreamVertxHandler implements Handler<RoutingContext> { |
| 193 | 195 | Map<String, Object> params = scriptService.parseEventData(productKey, deviceName, identifier, body); |
| 194 | 196 | |
| 195 | 197 | // 如果脚本解析结果为空,使用默认解析逻辑 |
| 196 | - if (params.isEmpty()) { | |
| 198 | + if (CollUtil.isNotEmpty(params)) { | |
| 197 | 199 | if (body.containsKey("params")) { |
| 198 | 200 | params = body.getJsonObject("params").getMap(); |
| 199 | 201 | } else { | ... | ... |
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-script/src/main/java/cn/iocoder/yudao/module/iot/plugin/script/ScriptExample.java
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-script/src/main/java/cn/iocoder/yudao/module/iot/plugin/script/config/ScriptConfiguration.java
| ... | ... | @@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.iot.plugin.script.service.ScriptServiceImpl; |
| 6 | 6 | import org.springframework.context.annotation.Bean; |
| 7 | 7 | import org.springframework.context.annotation.Configuration; |
| 8 | 8 | |
| 9 | +// TODO @haohao:这个模块,是不是融合到 plugin-common 里哈? | |
| 9 | 10 | /** |
| 10 | 11 | * 脚本模块配置类 |
| 11 | 12 | */ |
| ... | ... | @@ -31,7 +32,7 @@ public class ScriptConfiguration { |
| 31 | 32 | @Bean |
| 32 | 33 | public ScriptService scriptService(ScriptEngineFactory engineFactory) { |
| 33 | 34 | ScriptServiceImpl service = new ScriptServiceImpl(); |
| 34 | - // 如果有其他配置可以在这里设置 | |
| 35 | + // TODO @haohao:如果有其他配置可以在这里设置 | |
| 35 | 36 | return service; |
| 36 | 37 | } |
| 37 | -} | |
| 38 | 38 | \ No newline at end of file |
| 39 | +} | |
| 39 | 40 | \ No newline at end of file | ... | ... |
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-script/src/main/java/cn/iocoder/yudao/module/iot/plugin/script/context/PluginScriptContext.java
| 1 | 1 | package cn.iocoder.yudao.module.iot.plugin.script.context; |
| 2 | 2 | |
| 3 | +import lombok.Getter; | |
| 4 | + | |
| 3 | 5 | import java.util.HashMap; |
| 4 | 6 | import java.util.Map; |
| 5 | 7 | |
| ... | ... | @@ -11,18 +13,22 @@ public class PluginScriptContext implements ScriptContext { |
| 11 | 13 | /** |
| 12 | 14 | * 上下文参数 |
| 13 | 15 | */ |
| 16 | + @Getter | |
| 14 | 17 | private final Map<String, Object> parameters = new HashMap<>(); |
| 15 | 18 | |
| 16 | 19 | /** |
| 17 | 20 | * 上下文函数 |
| 18 | 21 | */ |
| 22 | + @Getter | |
| 19 | 23 | private final Map<String, Object> functions = new HashMap<>(); |
| 20 | 24 | |
| 21 | 25 | /** |
| 22 | 26 | * 日志函数接口 |
| 23 | 27 | */ |
| 24 | 28 | public interface LogFunction { |
| 29 | + | |
| 25 | 30 | void log(String message); |
| 31 | + | |
| 26 | 32 | } |
| 27 | 33 | |
| 28 | 34 | /** |
| ... | ... | @@ -47,16 +53,6 @@ public class PluginScriptContext implements ScriptContext { |
| 47 | 53 | } |
| 48 | 54 | |
| 49 | 55 | @Override |
| 50 | - public Map<String, Object> getParameters() { | |
| 51 | - return parameters; | |
| 52 | - } | |
| 53 | - | |
| 54 | - @Override | |
| 55 | - public Map<String, Object> getFunctions() { | |
| 56 | - return functions; | |
| 57 | - } | |
| 58 | - | |
| 59 | - @Override | |
| 60 | 56 | public void setParameter(String key, Object value) { |
| 61 | 57 | parameters.put(key, value); |
| 62 | 58 | } |
| ... | ... | @@ -71,6 +67,7 @@ public class PluginScriptContext implements ScriptContext { |
| 71 | 67 | functions.put(name, function); |
| 72 | 68 | } |
| 73 | 69 | |
| 70 | + // TODO @haohao:setParameters?这样的话,with 都是一些比较个性的参数 | |
| 74 | 71 | /** |
| 75 | 72 | * 批量设置参数 |
| 76 | 73 | * |
| ... | ... | @@ -87,11 +84,13 @@ public class PluginScriptContext implements ScriptContext { |
| 87 | 84 | /** |
| 88 | 85 | * 添加设备相关的上下文参数 |
| 89 | 86 | * |
| 90 | - * @param deviceId 设备ID | |
| 87 | + * @param deviceId 设备 ID | |
| 91 | 88 | * @param deviceData 设备数据 |
| 92 | 89 | * @return 当前上下文对象 |
| 93 | 90 | */ |
| 91 | + // TODO @haohao:是不是加个 (String productKey, String deviceName, Map<String, Object> deviceData) { | |
| 94 | 92 | public PluginScriptContext withDeviceContext(String deviceId, Map<String, Object> deviceData) { |
| 93 | + // TODO @haohao:deviceId 一般是分开,还是合并哈? | |
| 95 | 94 | parameters.put("deviceId", deviceId); |
| 96 | 95 | parameters.put("deviceData", deviceData); |
| 97 | 96 | return this; |
| ... | ... | @@ -110,6 +109,7 @@ public class PluginScriptContext implements ScriptContext { |
| 110 | 109 | return this; |
| 111 | 110 | } |
| 112 | 111 | |
| 112 | + // TODO @haohao:setParameter 可以融合哈? | |
| 113 | 113 | /** |
| 114 | 114 | * 设置单个参数 |
| 115 | 115 | * |
| ... | ... | @@ -121,4 +121,5 @@ public class PluginScriptContext implements ScriptContext { |
| 121 | 121 | parameters.put(key, value); |
| 122 | 122 | return this; |
| 123 | 123 | } |
| 124 | + | |
| 124 | 125 | } |
| 125 | 126 | \ No newline at end of file | ... | ... |
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-script/src/main/java/cn/iocoder/yudao/module/iot/plugin/script/context/ScriptContext.java
| ... | ... | @@ -37,6 +37,7 @@ public interface ScriptContext { |
| 37 | 37 | */ |
| 38 | 38 | Object getParameter(String key); |
| 39 | 39 | |
| 40 | + // TODO @haohao:这个要不也是 setFunction | |
| 40 | 41 | /** |
| 41 | 42 | * 注册函数 |
| 42 | 43 | * |
| ... | ... | @@ -44,4 +45,5 @@ public interface ScriptContext { |
| 44 | 45 | * @param function 函数对象 |
| 45 | 46 | */ |
| 46 | 47 | void registerFunction(String name, Object function); |
| 47 | -} | |
| 48 | 48 | \ No newline at end of file |
| 49 | + | |
| 50 | +} | |
| 49 | 51 | \ No newline at end of file | ... | ... |
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-script/src/main/java/cn/iocoder/yudao/module/iot/plugin/script/engine/AbstractScriptEngine.java
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-script/src/main/java/cn/iocoder/yudao/module/iot/plugin/script/engine/JsScriptEngine.java
| ... | ... | @@ -12,8 +12,8 @@ import java.util.concurrent.Callable; |
| 12 | 12 | import java.util.concurrent.ConcurrentHashMap; |
| 13 | 13 | |
| 14 | 14 | /** |
| 15 | - * JavaScript脚本引擎实现 | |
| 16 | - * 使用JSR-223 Nashorn脚本引擎 | |
| 15 | + * JavaScript 脚本引擎实现 | |
| 16 | + * 使用 JSR-223 Nashorn 脚本引擎 | |
| 17 | 17 | */ |
| 18 | 18 | @Slf4j |
| 19 | 19 | public class JsScriptEngine extends AbstractScriptEngine { |
| ... | ... | @@ -24,7 +24,7 @@ public class JsScriptEngine extends AbstractScriptEngine { |
| 24 | 24 | private static final long DEFAULT_TIMEOUT_MS = 5000; |
| 25 | 25 | |
| 26 | 26 | /** |
| 27 | - * JavaScript引擎名称 | |
| 27 | + * JavaScript 引擎名称 | |
| 28 | 28 | */ |
| 29 | 29 | private static final String JS_ENGINE_NAME = "nashorn"; |
| 30 | 30 | |
| ... | ... | @@ -45,25 +45,24 @@ public class JsScriptEngine extends AbstractScriptEngine { |
| 45 | 45 | |
| 46 | 46 | @Override |
| 47 | 47 | public void init() { |
| 48 | - log.info("初始化JavaScript脚本引擎"); | |
| 48 | + log.info("初始化 JavaScript 脚本引擎"); | |
| 49 | 49 | |
| 50 | 50 | // 创建脚本引擎管理器 |
| 51 | 51 | engineManager = new ScriptEngineManager(); |
| 52 | 52 | |
| 53 | - // 获取JavaScript引擎 | |
| 53 | + // 获取 JavaScript 引擎 | |
| 54 | 54 | engine = engineManager.getEngineByName(JS_ENGINE_NAME); |
| 55 | 55 | if (engine == null) { |
| 56 | - log.error("无法创建JavaScript引擎,尝试使用JavaScript名称获取"); | |
| 56 | + log.error("无法创建JavaScript引擎,尝试使用 JavaScript 名称获取"); | |
| 57 | 57 | engine = engineManager.getEngineByName("JavaScript"); |
| 58 | 58 | } |
| 59 | - | |
| 60 | 59 | if (engine == null) { |
| 61 | - throw new IllegalStateException("无法创建JavaScript引擎,请检查环境配置"); | |
| 60 | + throw new IllegalStateException("无法创建 JavaScript 引擎,请检查环境配置"); | |
| 62 | 61 | } |
| 63 | 62 | |
| 64 | 63 | log.info("成功创建JavaScript引擎: {}", engine.getClass().getName()); |
| 65 | 64 | |
| 66 | - // 默认使用JS沙箱 | |
| 65 | + // 默认使用 JS 沙箱 | |
| 67 | 66 | if (sandbox == null) { |
| 68 | 67 | setSandbox(new JsSandbox()); |
| 69 | 68 | } |
| ... | ... | @@ -99,7 +98,7 @@ public class JsScriptEngine extends AbstractScriptEngine { |
| 99 | 98 | // 执行脚本 |
| 100 | 99 | return engine.eval(script, bindings); |
| 101 | 100 | } catch (ScriptException e) { |
| 102 | - log.error("执行JavaScript脚本异常: {}", e.getMessage()); | |
| 101 | + log.error("执行 JavaScript 脚本异常: {}", e.getMessage()); | |
| 103 | 102 | throw new RuntimeException("脚本执行异常: " + e.getMessage(), e); |
| 104 | 103 | } |
| 105 | 104 | }; |
| ... | ... | @@ -136,7 +135,7 @@ public class JsScriptEngine extends AbstractScriptEngine { |
| 136 | 135 | // 执行脚本 |
| 137 | 136 | return engine.eval(script, bindings); |
| 138 | 137 | } catch (ScriptException e) { |
| 139 | - log.error("执行JavaScript脚本异常: {}", e.getMessage()); | |
| 138 | + log.error("执行 JavaScript 脚本异常: {}", e.getMessage()); | |
| 140 | 139 | throw new RuntimeException("脚本执行异常: " + e.getMessage(), e); |
| 141 | 140 | } |
| 142 | 141 | }; |
| ... | ... | @@ -152,9 +151,10 @@ public class JsScriptEngine extends AbstractScriptEngine { |
| 152 | 151 | |
| 153 | 152 | @Override |
| 154 | 153 | public void destroy() { |
| 155 | - log.info("销毁JavaScript脚本引擎"); | |
| 154 | + log.info("销毁 JavaScript 脚本引擎"); | |
| 156 | 155 | cachedScripts.clear(); |
| 157 | 156 | engine = null; |
| 158 | 157 | engineManager = null; |
| 159 | 158 | } |
| 159 | + | |
| 160 | 160 | } |
| 161 | 161 | \ No newline at end of file | ... | ... |
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-script/src/main/java/cn/iocoder/yudao/module/iot/plugin/script/engine/ScriptEngineFactory.java
| 1 | 1 | package cn.iocoder.yudao.module.iot.plugin.script.engine; |
| 2 | 2 | |
| 3 | +import cn.hutool.core.lang.Assert; | |
| 3 | 4 | import lombok.extern.slf4j.Slf4j; |
| 4 | 5 | import org.springframework.stereotype.Component; |
| 5 | 6 | |
| ... | ... | @@ -11,12 +12,12 @@ import org.springframework.stereotype.Component; |
| 11 | 12 | public class ScriptEngineFactory { |
| 12 | 13 | |
| 13 | 14 | /** |
| 14 | - * 创建JavaScript脚本引擎 | |
| 15 | + * 创建 JavaScript 脚本引擎 | |
| 15 | 16 | * |
| 16 | 17 | * @return JavaScript脚本引擎 |
| 17 | 18 | */ |
| 18 | 19 | public JsScriptEngine createJsEngine() { |
| 19 | - log.debug("创建JavaScript脚本引擎"); | |
| 20 | + log.debug("创建 JavaScript 脚本引擎"); | |
| 20 | 21 | return new JsScriptEngine(); |
| 21 | 22 | } |
| 22 | 23 | |
| ... | ... | @@ -27,10 +28,7 @@ public class ScriptEngineFactory { |
| 27 | 28 | * @return 脚本引擎 |
| 28 | 29 | */ |
| 29 | 30 | public AbstractScriptEngine createEngine(String scriptType) { |
| 30 | - if (scriptType == null || scriptType.isEmpty()) { | |
| 31 | - throw new IllegalArgumentException("脚本类型不能为空"); | |
| 32 | - } | |
| 33 | - | |
| 31 | + Assert.notBlank(scriptType, "脚本类型不能为空"); | |
| 34 | 32 | switch (scriptType.toLowerCase()) { |
| 35 | 33 | case "js": |
| 36 | 34 | case "javascript": |
| ... | ... | @@ -40,4 +38,5 @@ public class ScriptEngineFactory { |
| 40 | 38 | throw new IllegalArgumentException("不支持的脚本类型: " + scriptType); |
| 41 | 39 | } |
| 42 | 40 | } |
| 43 | -} | |
| 44 | 41 | \ No newline at end of file |
| 42 | + | |
| 43 | +} | |
| 45 | 44 | \ No newline at end of file | ... | ... |
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-script/src/main/java/cn/iocoder/yudao/module/iot/plugin/script/sandbox/JsSandbox.java
| 1 | 1 | package cn.iocoder.yudao.module.iot.plugin.script.sandbox; |
| 2 | 2 | |
| 3 | +import cn.hutool.core.util.StrUtil; | |
| 3 | 4 | import lombok.extern.slf4j.Slf4j; |
| 4 | 5 | |
| 5 | 6 | import javax.script.ScriptEngine; |
| ... | ... | @@ -8,8 +9,9 @@ import java.util.HashSet; |
| 8 | 9 | import java.util.Set; |
| 9 | 10 | import java.util.regex.Pattern; |
| 10 | 11 | |
| 12 | +// TODO @haohao:这个是不是融合到 ScriptEngine 里 | |
| 11 | 13 | /** |
| 12 | - * JavaScript脚本沙箱,限制脚本的执行权限 | |
| 14 | + * JavaScript 脚本沙箱,限制脚本的执行权限 | |
| 13 | 15 | */ |
| 14 | 16 | @Slf4j |
| 15 | 17 | public class JsSandbox implements ScriptSandbox { |
| ... | ... | @@ -34,6 +36,7 @@ public class JsSandbox implements ScriptSandbox { |
| 34 | 36 | "(?:\\bchild_process\\b)|" + |
| 35 | 37 | "(?:\\bwindow\\b)"); |
| 36 | 38 | |
| 39 | + // TODO @haohao:这个没用到哈。 | |
| 37 | 40 | /** |
| 38 | 41 | * 脚本执行超时时间(毫秒) |
| 39 | 42 | */ |
| ... | ... | @@ -44,30 +47,28 @@ public class JsSandbox implements ScriptSandbox { |
| 44 | 47 | if (!(engineContext instanceof ScriptEngine)) { |
| 45 | 48 | throw new IllegalArgumentException("引擎上下文类型不正确,无法应用JavaScript沙箱"); |
| 46 | 49 | } |
| 47 | - | |
| 48 | 50 | ScriptEngine engine = (ScriptEngine) engineContext; |
| 49 | 51 | |
| 50 | - // 在Nashorn引擎中,可以通过以下方式设置安全限制 | |
| 52 | + // 在 Nashorn 引擎中,可以通过以下方式设置安全限制 | |
| 51 | 53 | try { |
| 52 | 54 | // 设置严格模式 |
| 53 | 55 | String securityPrefix = "'use strict';\n"; |
| 54 | 56 | |
| 55 | - // 禁用Java.type等访问系统资源的功能 | |
| 57 | + // 禁用 Java.type 等访问系统资源的功能 | |
| 56 | 58 | engine.eval("var Java = undefined;"); |
| 57 | 59 | engine.eval("var JavaImporter = undefined;"); |
| 58 | 60 | engine.eval("var Packages = undefined;"); |
| 59 | 61 | |
| 60 | 62 | // 增强安全控制可以在这里添加 |
| 61 | - log.debug("已应用JavaScript安全沙箱限制"); | |
| 62 | - | |
| 63 | + log.debug("已应用 JavaScript 安全沙箱限制"); | |
| 63 | 64 | } catch (Exception e) { |
| 64 | - log.warn("应用JavaScript沙箱限制失败: {}", e.getMessage()); | |
| 65 | + log.warn("应用 JavaScript 沙箱限制失败: {}", e.getMessage()); | |
| 65 | 66 | } |
| 66 | 67 | } |
| 67 | 68 | |
| 68 | 69 | @Override |
| 69 | 70 | public boolean validateScript(String script) { |
| 70 | - if (script == null || script.isEmpty()) { | |
| 71 | + if (StrUtil.isNotEmpty(script)) { | |
| 71 | 72 | return false; |
| 72 | 73 | } |
| 73 | 74 | |
| ... | ... | @@ -86,11 +87,12 @@ public class JsSandbox implements ScriptSandbox { |
| 86 | 87 | } |
| 87 | 88 | |
| 88 | 89 | // 脚本长度限制 |
| 89 | - if (script.length() > 1024 * 100) { // 限制100KB | |
| 90 | + if (script.length() > 1024 * 100) { // 限制 100 KB | |
| 90 | 91 | log.warn("脚本太大,超过了限制"); |
| 91 | 92 | return false; |
| 92 | 93 | } |
| 93 | 94 | |
| 94 | 95 | return true; |
| 95 | 96 | } |
| 97 | + | |
| 96 | 98 | } |
| 97 | 99 | \ No newline at end of file | ... | ... |
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-script/src/main/java/cn/iocoder/yudao/module/iot/plugin/script/sandbox/ScriptSandbox.java
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-script/src/main/java/cn/iocoder/yudao/module/iot/plugin/script/service/ScriptService.java
| ... | ... | @@ -12,7 +12,7 @@ public interface ScriptService { |
| 12 | 12 | /** |
| 13 | 13 | * 执行脚本 |
| 14 | 14 | * |
| 15 | - * @param scriptType 脚本类型(如js、groovy等) | |
| 15 | + * @param scriptType 脚本类型(如 js、groovy 等) | |
| 16 | 16 | * @param script 脚本内容 |
| 17 | 17 | * @param context 脚本上下文 |
| 18 | 18 | * @return 脚本执行结果 |
| ... | ... | @@ -22,7 +22,7 @@ public interface ScriptService { |
| 22 | 22 | /** |
| 23 | 23 | * 执行脚本 |
| 24 | 24 | * |
| 25 | - * @param scriptType 脚本类型(如js、groovy等) | |
| 25 | + * @param scriptType 脚本类型(如 js、groovy 等) | |
| 26 | 26 | * @param script 脚本内容 |
| 27 | 27 | * @param params 脚本参数 |
| 28 | 28 | * @return 脚本执行结果 |
| ... | ... | @@ -30,7 +30,7 @@ public interface ScriptService { |
| 30 | 30 | Object executeScript(String scriptType, String script, Map<String, Object> params); |
| 31 | 31 | |
| 32 | 32 | /** |
| 33 | - * 执行JavaScript脚本 | |
| 33 | + * 执行 JavaScript 脚本 | |
| 34 | 34 | * |
| 35 | 35 | * @param script 脚本内容 |
| 36 | 36 | * @param context 脚本上下文 |
| ... | ... | @@ -39,7 +39,7 @@ public interface ScriptService { |
| 39 | 39 | Object executeJavaScript(String script, ScriptContext context); |
| 40 | 40 | |
| 41 | 41 | /** |
| 42 | - * 执行JavaScript脚本 | |
| 42 | + * 执行 JavaScript 脚本 | |
| 43 | 43 | * |
| 44 | 44 | * @param script 脚本内容 |
| 45 | 45 | * @param params 脚本参数 |
| ... | ... | @@ -55,4 +55,5 @@ public interface ScriptService { |
| 55 | 55 | * @return 脚本是否安全 |
| 56 | 56 | */ |
| 57 | 57 | boolean validateScript(String scriptType, String script); |
| 58 | -} | |
| 59 | 58 | \ No newline at end of file |
| 59 | + | |
| 60 | +} | |
| 60 | 61 | \ No newline at end of file | ... | ... |
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-script/src/main/java/cn/iocoder/yudao/module/iot/plugin/script/service/ScriptServiceImpl.java
| ... | ... | @@ -38,6 +38,7 @@ public class ScriptServiceImpl implements ScriptService { |
| 38 | 38 | @PostConstruct |
| 39 | 39 | public void init() { |
| 40 | 40 | // 初始化常用的脚本引擎和沙箱 |
| 41 | + // TODO @haohao:js 是不是要枚举下哈。 | |
| 41 | 42 | getEngine("js"); |
| 42 | 43 | sandboxCache.put("js", new JsSandbox()); |
| 43 | 44 | } |
| ... | ... | @@ -49,6 +50,7 @@ public class ScriptServiceImpl implements ScriptService { |
| 49 | 50 | try { |
| 50 | 51 | engine.destroy(); |
| 51 | 52 | } catch (Exception e) { |
| 53 | + // TODO @haohao:engine 类名 | |
| 52 | 54 | log.error("销毁脚本引擎失败", e); |
| 53 | 55 | } |
| 54 | 56 | } |
| ... | ... | @@ -58,6 +60,7 @@ public class ScriptServiceImpl implements ScriptService { |
| 58 | 60 | |
| 59 | 61 | @Override |
| 60 | 62 | public Object executeScript(String scriptType, String script, ScriptContext context) { |
| 63 | + // TODO @haohao:可以使用 hutool assert | |
| 61 | 64 | if (scriptType == null || script == null) { |
| 62 | 65 | throw new IllegalArgumentException("脚本类型和内容不能为空"); |
| 63 | 66 | } |
| ... | ... | @@ -74,6 +77,7 @@ public class ScriptServiceImpl implements ScriptService { |
| 74 | 77 | // 执行脚本 |
| 75 | 78 | return engine.execute(script, context); |
| 76 | 79 | } catch (Exception e) { |
| 80 | + // TODO @haohao:最好把 e 堆栈出来哈;然后,engine 类名 | |
| 77 | 81 | log.error("执行脚本失败: {}", e.getMessage()); |
| 78 | 82 | throw new RuntimeException("执行脚本失败: " + e.getMessage(), e); |
| 79 | 83 | } |
| ... | ... | @@ -83,16 +87,19 @@ public class ScriptServiceImpl implements ScriptService { |
| 83 | 87 | public Object executeScript(String scriptType, String script, Map<String, Object> params) { |
| 84 | 88 | // 创建默认上下文 |
| 85 | 89 | ScriptContext context = new PluginScriptContext(params); |
| 90 | + // 执行脚本 | |
| 86 | 91 | return executeScript(scriptType, script, context); |
| 87 | 92 | } |
| 88 | 93 | |
| 89 | 94 | @Override |
| 90 | 95 | public Object executeJavaScript(String script, ScriptContext context) { |
| 96 | + // TODO @haohao:枚举哈 | |
| 91 | 97 | return executeScript("js", script, context); |
| 92 | 98 | } |
| 93 | 99 | |
| 94 | 100 | @Override |
| 95 | 101 | public Object executeJavaScript(String script, Map<String, Object> params) { |
| 102 | + // TODO @haohao:枚举哈 | |
| 96 | 103 | return executeScript("js", script, params); |
| 97 | 104 | } |
| 98 | 105 | |
| ... | ... | @@ -100,7 +107,8 @@ public class ScriptServiceImpl implements ScriptService { |
| 100 | 107 | public boolean validateScript(String scriptType, String script) { |
| 101 | 108 | ScriptSandbox sandbox = sandboxCache.get(scriptType.toLowerCase()); |
| 102 | 109 | if (sandbox == null) { |
| 103 | - log.warn("找不到脚本类型[{}]对应的沙箱,使用默认JS沙箱", scriptType); | |
| 110 | + // TODO @haohao:疑问,为啥默认 JsSandbox 哈? | |
| 111 | + log.warn("[validateScript][找不到脚本类型[{}]对应的沙箱,使用默认 JS 沙箱]", scriptType); | |
| 104 | 112 | sandbox = new JsSandbox(); |
| 105 | 113 | sandboxCache.put(scriptType.toLowerCase(), sandbox); |
| 106 | 114 | } |
| ... | ... | @@ -120,4 +128,4 @@ public class ScriptServiceImpl implements ScriptService { |
| 120 | 128 | return engine; |
| 121 | 129 | }); |
| 122 | 130 | } |
| 123 | -} | |
| 124 | 131 | \ No newline at end of file |
| 132 | +} | |
| 125 | 133 | \ No newline at end of file | ... | ... |
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-script/src/main/java/cn/iocoder/yudao/module/iot/plugin/script/util/ScriptUtils.java
| ... | ... | @@ -6,6 +6,9 @@ import lombok.extern.slf4j.Slf4j; |
| 6 | 6 | import java.util.Map; |
| 7 | 7 | import java.util.concurrent.*; |
| 8 | 8 | |
| 9 | +// TODO @haohao:【重要】 ScriptUtil.createGroovyEngine() 可以服用 hutool 的封装么? | |
| 10 | +// TODO @haohao:【重要】 js 引擎,可能要看下 jdk8 的兼容性; | |
| 11 | +// TODO @haohao:【重要】我们要不 script 配置的时候,支持 scriptType?!感觉会更通用一些???groovy、python、js | |
| 9 | 12 | /** |
| 10 | 13 | * 脚本工具类,提供执行脚本的辅助方法 |
| 11 | 14 | */ |
| ... | ... | @@ -66,6 +69,7 @@ public class ScriptUtils { |
| 66 | 69 | * 关闭工具类的线程池 |
| 67 | 70 | */ |
| 68 | 71 | public static void shutdown() { |
| 72 | + // TODO @芋艿:有没默认工具类,可以 shutdown | |
| 69 | 73 | SCRIPT_EXECUTOR.shutdown(); |
| 70 | 74 | try { |
| 71 | 75 | if (!SCRIPT_EXECUTOR.awaitTermination(10, TimeUnit.SECONDS)) { |
| ... | ... | @@ -77,8 +81,9 @@ public class ScriptUtils { |
| 77 | 81 | } |
| 78 | 82 | } |
| 79 | 83 | |
| 84 | + // TODO @芋艿:要不要使用 JsonUtils | |
| 80 | 85 | /** |
| 81 | - * 将JSON字符串转换为Map | |
| 86 | + * 将 JSON 字符串转换为 Map | |
| 82 | 87 | * |
| 83 | 88 | * @param json JSON字符串 |
| 84 | 89 | * @return Map对象,转换失败则返回null |
| ... | ... | @@ -86,19 +91,20 @@ public class ScriptUtils { |
| 86 | 91 | @SuppressWarnings("unchecked") |
| 87 | 92 | public static Map<String, Object> parseJson(String json) { |
| 88 | 93 | try { |
| 89 | - // 使用hutool的JSONUtil工具类解析JSON | |
| 90 | 94 | return JSONUtil.toBean(json, Map.class); |
| 91 | 95 | } catch (Exception e) { |
| 92 | - log.error("解析JSON失败: {}", e.getMessage()); | |
| 96 | + // TODO @haohao:json、e 都打印出来哈 | |
| 97 | + log.error("[parseJson][解析JSON失败: {}]", e.getMessage()); | |
| 93 | 98 | return null; |
| 94 | 99 | } |
| 95 | 100 | } |
| 96 | 101 | |
| 102 | + // TODO @芋艿:要不要封装成 utils | |
| 97 | 103 | /** |
| 98 | 104 | * 尝试将对象转换为整数 |
| 99 | 105 | * |
| 100 | 106 | * @param obj 需要转换的对象 |
| 101 | - * @return 转换后的整数,如果无法转换则返回null | |
| 107 | + * @return 转换后的整数,如果无法转换则返回 null | |
| 102 | 108 | */ |
| 103 | 109 | public static Integer toInteger(Object obj) { |
| 104 | 110 | if (obj == null) { |
| ... | ... | @@ -122,6 +128,7 @@ public class ScriptUtils { |
| 122 | 128 | return null; |
| 123 | 129 | } |
| 124 | 130 | |
| 131 | + // TODO @芋艿:要不要封装成 utils | |
| 125 | 132 | /** |
| 126 | 133 | * 尝试将对象转换为双精度浮点数 |
| 127 | 134 | * |
| ... | ... | @@ -158,10 +165,12 @@ public class ScriptUtils { |
| 158 | 165 | * @return 如果两个数值相等则返回true,否则返回false |
| 159 | 166 | */ |
| 160 | 167 | public static boolean numbersEqual(Number a, Number b) { |
| 168 | + // TODO @haohao:NumberUtil.equals(1, 1D) | |
| 161 | 169 | if (a == null || b == null) { |
| 162 | 170 | return a == b; |
| 163 | 171 | } |
| 164 | 172 | |
| 165 | 173 | return Math.abs(a.doubleValue() - b.doubleValue()) < 0.0000001; |
| 166 | 174 | } |
| 175 | + | |
| 167 | 176 | } |
| 168 | 177 | \ No newline at end of file | ... | ... |