Переглянути джерело

添加 covert脚本 js解析

jay 2 роки тому
батько
коміт
63a905c01f

+ 16 - 0
iot-components/iot-component-converter/pom.xml

@@ -33,6 +33,22 @@
             <artifactId>iot-model</artifactId>
         </dependency>
 
+        <!--javascript运行环境-->
+        <dependency>
+            <groupId>org.graalvm.sdk</groupId>
+            <artifactId>graal-sdk</artifactId>
+
+        </dependency>
+        <dependency>
+            <groupId>org.graalvm.js</groupId>
+            <artifactId>js</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.graalvm.js</groupId>
+            <artifactId>js-scriptengine</artifactId>
+
+        </dependency>
+
     </dependencies>
 
 </project>

+ 47 - 0
iot-components/iot-component-converter/src/main/java/cc/iotkit/converter/CovertUtils.java

@@ -0,0 +1,47 @@
+package cc.iotkit.converter;
+
+import org.graalvm.polyglot.Value;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class CovertUtils {
+
+    private static final Map<Class<?>, Map<String, Field>> FIELD_CACHE = new ConcurrentHashMap<>();
+    private static final Map<Class<?>, Map<String, Method>> SETTER_CACHE = new ConcurrentHashMap<>();
+
+    public static void copyProperties(Object javaObj, Value jsObj) {
+        Map<String, Field> fieldMap = FIELD_CACHE.computeIfAbsent(javaObj.getClass(), clazz -> {
+            Map<String, Field> fields = new ConcurrentHashMap<>();
+            for (Field field : clazz.getDeclaredFields()) {
+                fields.put(field.getName(), field);
+            }
+            return fields;
+        });
+        Map<String, Method> setterMap = SETTER_CACHE.computeIfAbsent(javaObj.getClass(), clazz -> {
+            Map<String, Method> setters = new ConcurrentHashMap<>();
+            for (Method method : clazz.getDeclaredMethods()) {
+                if (method.getName().startsWith("set") && method.getParameterCount() == 1) {
+                    String propName = method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4);
+                    setters.put(propName, method);
+                }
+            }
+            return setters;
+        });
+        for (String propName : jsObj.getMemberKeys()) {
+            try {
+                Field field = fieldMap.get(propName);
+                Method setter = setterMap.get(propName);
+                if (field != null && setter != null) {
+                    Class<?> propType = field.getType();
+                    Object propValue = jsObj.getMember(propName).as(propType);
+                    setter.invoke(javaObj, propValue);
+                }
+            } catch (Exception e) {
+                // ignore errors
+            }
+        }
+    }
+}

+ 77 - 0
iot-components/iot-component-converter/src/main/java/cc/iotkit/converter/GraalJsScriptConverter.java

@@ -0,0 +1,77 @@
+/*
+ * +----------------------------------------------------------------------
+ * | Copyright (c) 奇特物联 2021-2022 All rights reserved.
+ * +----------------------------------------------------------------------
+ * | Licensed 未经许可不能去掉「奇特物联」相关版权
+ * +----------------------------------------------------------------------
+ * | Author: xw2sy@163.com
+ * +----------------------------------------------------------------------
+ */
+package cc.iotkit.converter;
+
+import cc.iotkit.common.thing.ThingService;
+import cc.iotkit.common.utils.JsonUtil;
+import cc.iotkit.model.device.message.ThingModelMessage;
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.beanutils.BeanUtils;
+
+import javax.script.Invocable;
+import javax.script.ScriptEngine;
+import javax.script.ScriptEngineManager;
+import javax.script.ScriptException;
+import java.util.Map;
+import org.graalvm.polyglot.*;
+
+@Slf4j
+@Data
+public class GraalJsScriptConverter implements IConverter {
+
+
+    private final Context context = Context.newBuilder("js").allowHostAccess(true).build();
+
+    private Object scriptObj;
+
+    private Value decoder;
+    private Value encoder;
+
+    public void setScript(String script) {
+
+        Value myFunctions = context.eval("js",String.format("new (function () {\n%s})()", script));
+        // 调用JavaScript函数
+        decoder = myFunctions.getMember("decode");
+        encoder = myFunctions.getMember("encode");
+
+    }
+
+    public ThingModelMessage decode(DeviceMessage msg) {
+        try {
+//            String msgJson = JsonUtil.toJsonString(msg);
+            Value rst = decoder.execute(msg);
+            ThingModelMessage modelMessage = new ThingModelMessage();
+            CovertUtils.copyProperties(modelMessage, rst);
+            return modelMessage;
+        } catch (Throwable e) {
+            log.error("execute decode script error", e);
+        }
+        return null;
+    }
+
+
+    @Override
+    public DeviceMessage encode(ThingService<?> service, Device device) {
+        try {
+            Value rst = encoder.execute(service,device);
+            DeviceMessage modelMessage = rst.as(DeviceMessage.class);
+            return modelMessage;
+        } catch (Throwable e) {
+            log.error("execute encode script error", e);
+        }
+        return null;
+    }
+
+    @Override
+    public void putScriptEnv(String key, Object value) {
+        context.getBindings("js").putMember(key, value);
+    }
+}

+ 173 - 0
iot-components/iot-mqtt-component/src/main/resources/graaljsconverter.js

@@ -0,0 +1,173 @@
+
+var mid=1;
+
+function getMid(){
+  mid++;
+  if(mid>10000){
+	mid=1;
+  }
+  return mid+"";
+}
+
+this.decode = function (msg) {
+  //对msg进行解析,并返回物模型数据
+  var content=msg.getContent();
+  var topic = content.topic;
+  var payload = content.payload;
+  var identifier = topic.substring(topic.lastIndexOf("/") + 1);
+  
+  //透传上报
+  if(topic.endsWith("/event/rawReport")){
+	var rst= component.transparentDecode(payload.params);
+	if(!rst){
+	  return null;
+	}
+	rst.occur=new Date().getTime();
+	rst.time=new Date().getTime();
+	return rst;
+  }
+
+  if (topic.endsWith("/property/post")) {
+	//属性上报
+	return {
+	  mid: msg.mid,
+	  productKey: msg.productKey, 
+	  deviceName: msg.deviceName,
+	  type:"property",
+	  identifier: "report", //属性上报
+	  occur: new Date().getTime(), //时间戳,设备上的事件或数据产生的本地时间
+	  time: new Date().getTime(), //时间戳,消息上报时间
+	  data: payload,
+	};
+  } else if (topic.indexOf("/event/") > 0) {
+	//事件上报
+	return {
+	  mid: msg.mid,
+	  productKey: msg.productKey,
+	  deviceName: msg.deviceName,
+	  type:"event",
+	  identifier: identifier,
+	  occur: new Date().getTime(),
+	  time: new Date().getTime(),
+	  data: payload,
+	};
+  }else if(topic.endsWith("/service/property/set_reply")){
+	//属性设置回复
+	return {
+	  mid: msg.mid,
+	  productKey: msg.productKey,
+	  deviceName: msg.deviceName,
+	  type:"property",
+	  identifier: identifier,
+	  occur: new Date().getTime(),
+	  time: new Date().getTime(),
+	  code: payload.code
+	};
+  }else if(topic.endsWith("/config/set_reply")){
+	//设备配置设置回复
+	return {
+	  mid: msg.mid,
+	  productKey: msg.productKey,
+	  deviceName: msg.deviceName,
+	  type:"config",
+	  identifier: "set_reply",
+	  occur: new Date().getTime(),
+	  time: new Date().getTime(),
+	  code: payload.code
+	};
+  }else if(topic.endsWith("/config/get")){
+	//设备配置获取
+	return {
+	  mid: msg.mid,
+	  productKey: msg.productKey,
+	  deviceName: msg.deviceName,
+	  type:"config",
+	  identifier: "get",
+	  occur: new Date().getTime(),
+	  time: new Date().getTime(),
+	  data: {},
+	};
+  } else if (topic.endsWith("_reply")) {
+	//服务回复
+	return {
+	  mid: msg.mid,
+	  productKey: msg.productKey,
+	  deviceName: msg.deviceName,
+	  type:"service",
+	  identifier: identifier,
+	  occur: new Date().getTime(),
+	  time: new Date().getTime(),
+	  code: payload.code,
+	  data: payload.data,
+	};
+  }
+  return null;
+};
+
+this.encode = function (service,device) {
+  var deviceMid=getMid();
+  var method="thing.service.";
+  var topic="/sys/"+service.productKey+"/"+service.deviceName+"/c/service/";
+  var params={};
+  
+  //透传下发
+  if(device.transparent){
+	var rst=component.transparentEncode(service,device);
+	topic="/sys/"+rst.productKey+"/"+rst.deviceName+"/c/service/rawSend";
+	params.model=rst.content.model;
+	params.deviceName=rst.content.deviceName;
+	params.data=rst.content.data;
+	
+	return {
+	  productKey:rst.productKey,
+	  deviceName:rst.deviceName,
+	  mid:rst.mid,
+	  content:{
+		topic:topic,
+		payload:JSON.stringify({
+		  id:rst.mid,
+		  method:method+"rawSend",
+		  params:params
+		})
+	  }
+	}
+	
+  }
+  
+  var type=service.type;
+  var identifier=service.identifier;
+
+  if(type=="property"){
+	method+="property."+identifier;
+	topic+="property/"+identifier;
+  }else if(type=="service"){
+	method+=identifier;
+	topic+=identifier;
+  }else if(type=="config"){
+	//设备配置下发
+	method+=identifier;
+	topic="/sys/"+service.productKey+"/"+service.deviceName+"/c/config/"+identifier;
+  }else if(type="lifetime"){
+	//子设备注销下发
+	method+=identifier;
+	topic="/sys/"+service.productKey+"/"+service.deviceName+"/c/deregister";
+  }
+  
+  for(var p in service.params){
+	params[p]=service.params[p];
+  }
+  
+  return {
+	productKey:service.productKey,
+	deviceName:service.deviceName,
+	mid:deviceMid,
+	content:{
+	  topic:topic,
+	  payload:JSON.stringify({
+		id:deviceMid,
+		method:method,
+		params:params
+	  })
+	}
+  }
+};

+ 19 - 0
pom.xml

@@ -31,6 +31,7 @@
         <java.version>11</java.version>
         <vertx.version>4.2.2</vertx.version>
         <sa-token.version>1.30.0</sa-token.version>
+        <graalvm.version>21.1.0</graalvm.version>
     </properties>
 
     <dependencyManagement>
@@ -328,6 +329,24 @@
                 <version>${project.version}</version>
             </dependency>
 
+            <!--javascript运行环境-->
+            <dependency>
+                <groupId>org.graalvm.sdk</groupId>
+                <artifactId>graal-sdk</artifactId>
+                <version>${graalvm.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.graalvm.js</groupId>
+                <artifactId>js</artifactId>
+                <version>${graalvm.version}</version>
+                <scope>runtime</scope>
+            </dependency>
+            <dependency>
+                <groupId>org.graalvm.js</groupId>
+                <artifactId>js-scriptengine</artifactId>
+                <version>${graalvm.version}</version>
+            </dependency>
+
         </dependencies>
     </dependencyManagement>