Browse Source

app端设备状态和属性实时推送实现

xiwa 3 years ago
parent
commit
51a38d1f18

+ 4 - 0
common/src/main/java/cc/iotkit/common/Constants.java

@@ -64,6 +64,10 @@ public interface Constants {
      */
     String THING_MODEL_MESSAGE_TOPIC = "device_thing";
 
+    /**
+     * http消费设备信息的topic
+     */
+    String HTTP_CONSUMER_DEVICE_INFO_TOPIC = "device_info:";
 
     interface API {
 

+ 16 - 15
manager/src/main/java/cc/iotkit/manager/config/KeycloakSecurityConfig.java

@@ -58,21 +58,22 @@ public class KeycloakSecurityConfig extends KeycloakWebSecurityConfigurerAdapter
                 .antMatchers("/api/**").hasRole("iot_client_user")
                 .antMatchers("/aligenieDevice/invoke/**").hasRole("iot_client_user")
                 //客户端用户写权限
-                .antMatchers("/space/addSpace/**").hasRole("iot_client_user")
-                .antMatchers("/space/saveSpace/**").hasRole("iot_client_user")
-                .antMatchers("/space/delSpace/**").hasRole("iot_client_user")
-                .antMatchers("/space/saveHome/**").hasRole("iot_client_user")
-                .antMatchers("/space/currentHome/**").hasRole("iot_client_user")
-                .antMatchers("/space/myRecentDevices/**").hasRole("iot_client_user")
-                .antMatchers("/space/spaces/**").hasRole("iot_client_user")
-                .antMatchers("/space/myDevices/**").hasRole("iot_client_user")
-                .antMatchers("/space/findDevice/**").hasRole("iot_client_user")
-                .antMatchers("/space/addDevice/**").hasRole("iot_client_user")
-                .antMatchers("/space/saveDevice").hasRole("iot_client_user")
-                .antMatchers("/space/removeDevice").hasRole("iot_client_user")
-                .antMatchers("/space/device/*").hasRole("iot_client_user")
-                .antMatchers("/device/*/service/property/set").hasRole("iot_client_user")
-                .antMatchers("/device/*/service/*/invoke").hasRole("iot_client_user")
+                .antMatchers("/space/addSpace/**").hasAnyRole("iot_write","iot_client_user")
+                .antMatchers("/space/saveSpace/**").hasAnyRole("iot_write","iot_client_user")
+                .antMatchers("/space/delSpace/**").hasAnyRole("iot_write","iot_client_user")
+                .antMatchers("/space/saveHome/**").hasAnyRole("iot_write","iot_client_user")
+                .antMatchers("/space/currentHome/**").hasAnyRole("iot_write","iot_client_user")
+                .antMatchers("/space/myRecentDevices/**").hasAnyRole("iot_write","iot_client_user")
+                .antMatchers("/space/spaces/**").hasAnyRole("iot_write","iot_client_user")
+                .antMatchers("/space/myDevices/**").hasAnyRole("iot_write","iot_client_user")
+                .antMatchers("/space/findDevice/**").hasAnyRole("iot_write","iot_client_user")
+                .antMatchers("/space/addDevice/**").hasAnyRole("iot_write","iot_client_user")
+                .antMatchers("/space/saveDevice").hasAnyRole("iot_write","iot_client_user")
+                .antMatchers("/space/removeDevice").hasAnyRole("iot_write","iot_client_user")
+                .antMatchers("/space/device/*").hasAnyRole("iot_write","iot_client_user")
+                .antMatchers("/device/*/consumer/*").hasAnyRole("iot_write","iot_client_user")
+                .antMatchers("/device/*/service/property/set").hasAnyRole("iot_write","iot_client_user")
+                .antMatchers("/device/*/service/*/invoke").hasAnyRole("iot_write","iot_client_user")
 
 
                 .antMatchers(HttpMethod.DELETE).hasRole("iot_write")

+ 21 - 0
manager/src/main/java/cc/iotkit/manager/controller/DeviceController.java

@@ -8,6 +8,7 @@ import cc.iotkit.comps.service.DeviceBehaviourService;
 import cc.iotkit.dao.*;
 import cc.iotkit.manager.model.query.DeviceQuery;
 import cc.iotkit.manager.service.DataOwnerService;
+import cc.iotkit.manager.service.DeferredDataConsumer;
 import cc.iotkit.manager.service.DeviceService;
 import cc.iotkit.manager.utils.AuthUtil;
 import cc.iotkit.model.InvokeResult;
@@ -24,6 +25,7 @@ import org.springframework.context.annotation.Lazy;
 import org.springframework.data.domain.Example;
 import org.springframework.data.mongodb.core.query.Criteria;
 import org.springframework.web.bind.annotation.*;
+import org.springframework.web.context.request.async.DeferredResult;
 
 import java.util.List;
 import java.util.Map;
@@ -54,6 +56,8 @@ public class DeviceController {
     private DevicePropertyDao devicePropertyDao;
     @Autowired
     private DeviceBehaviourService behaviourService;
+    @Autowired
+    DeferredDataConsumer deferredDataConsumer;
 
     @PostMapping(Constants.API.DEVICE_INVOKE_SERVICE)
     public InvokeResult invokeService(@PathVariable("deviceId") String deviceId,
@@ -203,4 +207,21 @@ public class DeviceController {
         message.setTime(System.currentTimeMillis());
         behaviourService.reportMessage(message);
     }
+
+    /**
+     * 消费设备信息消息(实时推送设备信息)
+     */
+    @GetMapping("/{deviceId}/consumer/{clientId}")
+    public DeferredResult<ThingModelMessage> consumerDeviceInfo(
+            @PathVariable("deviceId") String deviceId,
+            @PathVariable("clientId") String clientId
+    ) {
+        String uid = AuthUtil.getUserId();
+        DeviceInfo deviceInfo = deviceRepository.findByDeviceId(deviceId);
+        dataOwnerService.checkOwner(deviceInfo);
+
+        //按用户+客户端ID订阅
+        return deferredDataConsumer.newConsumer(uid + clientId,
+                Constants.HTTP_CONSUMER_DEVICE_INFO_TOPIC + deviceId);
+    }
 }

+ 1 - 1
manager/src/main/java/cc/iotkit/manager/controller/SpaceDeviceController.java

@@ -66,7 +66,7 @@ public class SpaceDeviceController {
 
 
     private SpaceDeviceVo parseSpaceDevice(SpaceDevice sd) {
-        DeviceInfo device = deviceCache.get(sd.getDeviceId());
+        DeviceInfo device = deviceRepository.findByDeviceId(sd.getDeviceId());
         Space space = spaceCache.getSpace(sd.getSpaceId());
         Product product = productCache.findById(device.getProductKey());
         Category category = categoryCache.getById(product.getCategory());

+ 175 - 0
manager/src/main/java/cc/iotkit/manager/service/DeferredDataConsumer.java

@@ -0,0 +1,175 @@
+package cc.iotkit.manager.service;
+
+import cc.iotkit.common.Constants;
+import cc.iotkit.common.utils.JsonUtil;
+import cc.iotkit.comps.config.ServerConfig;
+import cc.iotkit.model.device.DeviceInfo;
+import cc.iotkit.model.device.message.ThingModelMessage;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.pulsar.client.api.*;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.web.context.request.async.DeferredResult;
+
+import javax.annotation.PostConstruct;
+import java.util.*;
+import java.util.concurrent.*;
+
+@Slf4j
+@Component
+public class DeferredDataConsumer implements MessageListener<ThingModelMessage>, Runnable {
+
+    private final Map<String, Set<String>> topicConsumers = new ConcurrentHashMap<>();
+    private final Map<String, DeferredResultInfo> consumerDeferred = new ConcurrentHashMap<>();
+    private final DelayQueue<DelayedPush> delayedPushes = new DelayQueue<>();
+
+    @Autowired
+    private ServerConfig serverConfig;
+
+    @PostConstruct
+    public void init() throws PulsarClientException {
+        PulsarClient client = PulsarClient.builder()
+                .serviceUrl(this.serverConfig.getPulsarBrokerUrl())
+                .build();
+
+        client.newConsumer(Schema.JSON(ThingModelMessage.class))
+                .topic("persistent://iotkit/default/" + Constants.THING_MODEL_MESSAGE_TOPIC)
+                .subscriptionName("device-info-push")
+                .consumerName("device-info-push-consumer")
+                .messageListener(this).subscribe();
+
+        Executors.newCachedThreadPool().submit(this);
+    }
+
+    public <T> DeferredResult<T> newConsumer(String consumerId, String topic) {
+        topicConsumers.putIfAbsent(topic, new HashSet<>());
+        Set<String> consumers = topicConsumers.get(topic);
+        consumers.add(consumerId);
+
+        String consumerKey = getConsumerKey(consumerId, topic);
+        DeferredResult<T> result = new DeferredResult<>(10000L, new DeviceInfo());
+        DeferredResultInfo resultInfo = new DeferredResultInfo(result, false);
+        result.onCompletion(() -> resultInfo.setCompleted(true));
+        result.onTimeout(() -> resultInfo.setCompleted(true));
+
+        consumerDeferred.put(consumerKey, resultInfo);
+        return result;
+    }
+
+    public <T> void publish(String topic, T msg, boolean republish) {
+        Set<String> consumers = topicConsumers.get(topic);
+        if (consumers == null) {
+            return;
+        }
+        for (String consumer : consumers) {
+            String consumerKey = getConsumerKey(consumer, topic);
+            DeferredResultInfo result = consumerDeferred.get(consumerKey);
+            if (result == null) {
+                continue;
+            }
+
+            //如果已经推送完成了,等待1秒再尝试发送,让客户端有时间重连
+            if (!republish && result.isCompleted() && !result.isExpired()) {
+                delayedPushes.offer(new DelayedPush<>(topic, System.currentTimeMillis(), msg),
+                        3, TimeUnit.SECONDS);
+            } else {
+                log.info("push {} to {},msg:{}", topic, consumer, JsonUtil.toJsonString(msg));
+                result.getDeferredResult().setResult(msg);
+            }
+        }
+    }
+
+    public <T> void publish(String topic, T msg) {
+        publish(topic, msg, false);
+    }
+
+    private String getConsumerKey(String consumerId, String topic) {
+        return consumerId + topic;
+    }
+
+    @Override
+    public void received(Consumer<ThingModelMessage> consumer, Message<ThingModelMessage> msg) {
+        ThingModelMessage thingModelMessage = msg.getValue();
+        String type = thingModelMessage.getType();
+        String identifier = thingModelMessage.getIdentifier();
+        //属性上报和上下线消息
+        if ((ThingModelMessage.TYPE_PROPERTY.equals(type) && "report".equals(identifier)) ||
+                ThingModelMessage.TYPE_STATE.equals(type)) {
+            publish(Constants.HTTP_CONSUMER_DEVICE_INFO_TOPIC + thingModelMessage.getDeviceId(),
+                    thingModelMessage);
+        }
+    }
+
+    @Override
+    public void reachedEndOfTopic(Consumer<ThingModelMessage> consumer) {
+    }
+
+    @Override
+    public void run() {
+        while (true) {
+            try {
+                DelayedPush<ThingModelMessage> delayedPush = delayedPushes.take();
+                ThingModelMessage modelMessage = delayedPush.getMsg();
+                publish(delayedPush.getTopic(), modelMessage, true);
+            } catch (Throwable e) {
+                log.error("delayed push error", e);
+            }
+        }
+    }
+
+    @Data
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class DeferredResultInfo {
+        private DeferredResult deferredResult;
+        private boolean completed;
+        private long completedTime;
+
+        public DeferredResultInfo(DeferredResult deferredResult, boolean completed) {
+            this.deferredResult = deferredResult;
+            this.completed = completed;
+            this.completedTime = System.currentTimeMillis();
+        }
+
+        public void setCompleted(boolean completed) {
+            this.completed = completed;
+            this.completedTime = System.currentTimeMillis();
+        }
+
+        public boolean isExpired() {
+            //完成超过3后视为过期,客户端可能已断开
+            return completed && System.currentTimeMillis() - completedTime > 3 * 1000L;
+        }
+    }
+
+    @Data
+    public static class DelayedPush<T> implements Delayed {
+
+        private String topic;
+
+        private T msg;
+
+        private long addTime;
+
+        public DelayedPush(String topic, long addTime, T message) {
+            this.topic = topic;
+            this.addTime = addTime;
+            this.msg = message;
+        }
+
+        @Override
+        public long getDelay(TimeUnit unit) {
+            return unit.convert(addTime - System.nanoTime(), TimeUnit.NANOSECONDS);
+        }
+
+        @Override
+        public int compareTo(Delayed o) {
+            long diff = o.getDelay(TimeUnit.NANOSECONDS) - getDelay(TimeUnit.NANOSECONDS);
+            return diff == 0 ? 0 : (diff > 0 ? 1 : -1);
+        }
+    }
+
+}

+ 0 - 0
protocol-gateway/emqx-component/dependency-reduced-pom.xml