Commit eeb1dc4a077b451652dddde2cb1628082917daef

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