All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jetlinks.registry.redis.lettuce.LettuceDeviceMessageSender Maven / Gradle / Ivy

The newest version!
package org.jetlinks.registry.redis.lettuce;

import com.alibaba.fastjson.JSON;
import io.lettuce.core.api.StatefulRedisConnection;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.jetlinks.core.device.DeviceMessageSender;
import org.jetlinks.core.device.DeviceOperation;
import org.jetlinks.core.device.registry.DeviceMessageHandler;
import org.jetlinks.core.enums.ErrorCode;
import org.jetlinks.core.message.*;
import org.jetlinks.core.message.exception.FunctionUndefinedException;
import org.jetlinks.core.message.exception.ParameterUndefinedException;
import org.jetlinks.core.message.function.FunctionInvokeMessage;
import org.jetlinks.core.message.function.FunctionInvokeMessageReply;
import org.jetlinks.core.message.function.FunctionParameter;
import org.jetlinks.core.message.interceptor.DeviceMessageSenderInterceptor;
import org.jetlinks.core.message.property.ReadPropertyMessage;
import org.jetlinks.core.message.property.ReadPropertyMessageReply;
import org.jetlinks.core.message.property.WritePropertyMessage;
import org.jetlinks.core.message.property.WritePropertyMessageReply;
import org.jetlinks.core.metadata.FunctionMetadata;
import org.jetlinks.core.metadata.PropertyMetadata;
import org.jetlinks.core.metadata.ValidateResult;
import org.jetlinks.core.utils.IdUtils;
import org.jetlinks.lettuce.LettucePlus;

import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import static org.jetlinks.core.enums.ErrorCode.NO_REPLY;

/**
 * @author zhouhao
 * @since 1.0.0
 */
@Slf4j
public class LettuceDeviceMessageSender implements DeviceMessageSender {

    private LettucePlus plus;

    protected Supplier connectionServerIdSupplier;

    protected Runnable deviceStateChecker;

    protected String deviceId;

    protected DeviceOperation operation;

    //从元数据中获取异步
    @Getter
    @Setter
    private boolean asyncFromMetadata = Boolean.getBoolean("device.message.async.from-metadata");

    @Getter
    @Setter
    protected DeviceMessageSenderInterceptor interceptor;

    private DeviceMessageHandler messageHandler;

    public LettuceDeviceMessageSender(String deviceId,
                                      LettucePlus plus,
                                      DeviceMessageHandler messageHandler,
                                      DeviceOperation operation) {
        this.plus = plus;
        this.operation = operation;
        this.messageHandler = messageHandler;
        this.connectionServerIdSupplier = operation::getServerId;
        this.deviceStateChecker = operation::checkState;
        this.deviceId = deviceId;
    }

    //最大等待30秒
    @Getter
    @Setter
    private int maxSendAwaitSeconds = Integer.getInteger("device.message.await.max-seconds", 30);

    @SuppressWarnings("all")
    protected  R convertReply(Object deviceReply, RepayableDeviceMessage message, Supplier replyNewInstance) {
        R reply = replyNewInstance.get();
        if (deviceReply == null) {
            reply.error(NO_REPLY);
        } else if (deviceReply instanceof ErrorCode) {
            reply.error((ErrorCode) deviceReply);
        } else {
            if (reply.getClass().isAssignableFrom(deviceReply.getClass())) {
                return reply = (R) deviceReply;
            } else if (deviceReply instanceof String) {
                reply.fromJson(JSON.parseObject(String.valueOf(deviceReply)));
            } else if (deviceReply instanceof DeviceMessage) {
                reply.fromJson(((DeviceMessage) deviceReply).toJson());
            } else {
                reply.error(ErrorCode.UNSUPPORTED_MESSAGE);
                log.warn("不支持的消息类型:{}", deviceReply.getClass());
            }
        }
        if (message != null) {
            reply.from(message);
        }
        return reply;
    }


    public  CompletionStage retrieveReply(String messageId, Supplier replyNewInstance) {
        return plus
                .getConnection()
                .thenApply(StatefulRedisConnection::async)
                .thenCompose(redis->redis.get("device:message:reply:".concat(messageId)))
                .thenApply(deviceReply -> {
                    R reply = convertReply(deviceReply, null, replyNewInstance);
                    reply.messageId(messageId);
                    return reply;
                });
    }

    @Override
    @SuppressWarnings("all")
    public  CompletionStage send(DeviceMessage requestMessage, Function replyMapping) {
        String serverId = connectionServerIdSupplier.get();
        //设备当前没有连接到任何服务器
        if (serverId == null) {
            R reply = replyMapping.apply(null);
            if (reply != null) {
                reply.error(ErrorCode.CLIENT_OFFLINE);
            }
            if (null != reply) {
                reply.from(requestMessage);
            }
            return CompletableFuture.completedFuture(reply);
        }
        CompletableFuture future = new CompletableFuture<>();
        if (interceptor != null) {
            requestMessage = interceptor.preSend(operation, requestMessage);
        }
        DeviceMessage message = requestMessage;
        //标记异步
        if (Headers.async.get(message).asBoolean().orElse(false)) {
            messageHandler.markMessageAsync(message.getMessageId())
                    .whenComplete((nil, error) -> {
                        if (error != null) {
                            log.error("设置异步消息标识[{}]失败", message, error);
                        }
                    });
        }
        //在头中标记超时
        int timeout = message.getHeader("timeout")
                .map(Number.class::cast)
                .map(Number::intValue)
                .orElse(maxSendAwaitSeconds);

        //处理返回结果,_reply可能为null,ErroCode,Object
        BiConsumer doReply = (_reply, error) -> {

            CompletionStage reply = CompletableFuture
                    .completedFuture(replyMapping.apply(error != null ? ErrorCode.SYSTEM_ERROR : _reply))
                    .thenApply(r -> {
                        if (error != null) {
                            r.addHeader("error", error.getMessage());
                        }
                        return r;
                    });

            if (interceptor != null) {
                reply = reply.thenCompose(r -> interceptor.afterReply(operation, message, r));
            }
            reply.whenComplete((r, throwable) -> {
                if (throwable != null) {//理论上不会出现
                    future.completeExceptionally(throwable);
                } else {
                    future.complete(r);
                }
            });
        };

        //监听返回
        CompletionStage stage = messageHandler.handleReply(message.getMessageId(), timeout, TimeUnit.SECONDS);

        stage.whenComplete(doReply);

        future.whenComplete((r, err) -> {
            if (future.isCancelled()) {
                log.info("取消等待设备[{}]消息[{}]返回", deviceId,message.getMessageId());
                stage.toCompletableFuture().cancel(true);
            }
        });
        //发送消息
        messageHandler.send(serverId, message)
                .whenComplete((deviceConnectedServerNumber, error) -> {
                    if (error != null) {
                        log.error("发送消息到设备网关服务失败:{}", message, error);
                        doReply.accept(ErrorCode.SYSTEM_ERROR, error);
                        return;
                    }
                    if (deviceConnectedServerNumber <= 0) {
                        //没有任何服务消费此topic,可能所在服务器已经宕机,注册信息没有更新。
                        //执行设备状态检查,尝试更新设备的真实状态
                        if (deviceStateChecker != null) {
                            deviceStateChecker.run();
                        }
                        doReply.accept(ErrorCode.CLIENT_OFFLINE, null);
                    }
                    //有多个相同名称的设备网关服务,可能是服务配置错误,启动了多台相同id的服务。
                    if (deviceConnectedServerNumber > 1) {
                        log.warn("存在多个相同的网关服务:{}", serverId);
                    }
                });

        return future;
    }

    @Override
    @SuppressWarnings("all")
    public  CompletionStage send(RepayableDeviceMessage message) {
        return send(message, deviceReply -> convertReply(deviceReply, message, message::newReply));
    }

    @Override
    public FunctionInvokeMessageSender invokeFunction(String function) {
        Objects.requireNonNull(function, "function");
        FunctionInvokeMessage message = new FunctionInvokeMessage();
        message.setTimestamp(System.currentTimeMillis());
        message.setDeviceId(deviceId);
        message.setFunctionId(function);
        message.setMessageId(IdUtils.newUUID());
        return new FunctionInvokeMessageSender() {
            boolean markAsync = false;

            @Override
            public FunctionInvokeMessageSender addParameter(String name, Object value) {
                message.addInput(name, value);
                return this;
            }

            @Override
            public FunctionInvokeMessageSender custom(Consumer messageConsumer) {
                messageConsumer.accept(message);
                return this;
            }

            @Override
            public FunctionInvokeMessageSender messageId(String messageId) {
                message.setMessageId(messageId);
                return this;
            }

            @Override
            public FunctionInvokeMessageSender setParameter(List parameter) {
                message.setInputs(parameter);
                return this;
            }

            @Override
            public FunctionInvokeMessageSender validate(BiConsumer resultConsumer) {
                //获取功能定义
                FunctionMetadata functionMetadata = operation.getMetadata().getFunction(function)
                        .orElseThrow(() -> new FunctionUndefinedException(function, "功能[" + function + "]未定义"));
                List metadataInputs = functionMetadata.getInputs();
                List inputs = message.getInputs();

                if (inputs.size() != metadataInputs.size()) {

                    log.warn("调用设备功能[{}]参数数量[需要{},传入{}]错误,功能:{}", function, metadataInputs.size(), inputs.size(), functionMetadata.toString());
                    throw new IllegalArgumentException("参数数量错误");
                }

                //参数定义转为map,避免n*n循环
                Map properties = metadataInputs.stream()
                        .collect(Collectors.toMap(PropertyMetadata::getId, Function.identity(), (t1, t2) -> t1));

                for (FunctionParameter input : message.getInputs()) {
                    PropertyMetadata metadata = Optional.ofNullable(properties.get(input.getName()))
                            .orElseThrow(() -> new ParameterUndefinedException(input.getName(), "参数[" + input.getName() + "]未定义"));
                    resultConsumer.accept(input, metadata.getValueType().validate(input.getValue()));
                }

                return this;
            }

            @Override
            public FunctionInvokeMessageSender header(String header, Object value) {
                message.addHeader(header, value);
                return this;
            }

            @Override
            public FunctionInvokeMessageSender async(Boolean async) {
                if (async != null) {
                    custom(async ? Headers.async.setter() : Headers.async.clear());
                }
                markAsync = true;
                return this;
            }

            @Override
            public CompletionStage retrieveReply() {
                return LettuceDeviceMessageSender.this.retrieveReply(
                        Objects.requireNonNull(message.getMessageId(), "messageId can not be null"),
                        FunctionInvokeMessageReply::new);
            }

            @Override
            public CompletionStage send() {

                //如果未明确指定是否异步,则获取元数据中定义的异步配置
                if (!markAsync && asyncFromMetadata && message.getHeader(Headers.async.getHeader()).isPresent()) {
                    message.addHeader(Headers.async.getHeader(),
                            operation.getMetadata()
                                    .getFunction(message.getFunctionId())
                                    .map(FunctionMetadata::isAsync)
                                    .orElse(false));
                }
                return LettuceDeviceMessageSender.this.send(message);
            }

        };
    }

    @Override
    public ReadPropertyMessageSender readProperty(String... property) {

        ReadPropertyMessage message = new ReadPropertyMessage();
        message.setDeviceId(deviceId);
        message.addProperties(Arrays.asList(property));
        message.setMessageId(IdUtils.newUUID());
        return new ReadPropertyMessageSender() {
            @Override
            public ReadPropertyMessageSender messageId(String messageId) {
                message.setMessageId(messageId);
                return this;
            }

            @Override
            public ReadPropertyMessageSender custom(Consumer messageConsumer) {
                messageConsumer.accept(message);
                return this;
            }

            @Override
            public ReadPropertyMessageSender read(List properties) {
                message.addProperties(properties);
                return this;
            }

            @Override
            public ReadPropertyMessageSender header(String header, Object value) {
                message.addHeader(header, value);
                return this;
            }

            @Override
            public CompletionStage retrieveReply() {
                return LettuceDeviceMessageSender.this.retrieveReply(
                        Objects.requireNonNull(message.getMessageId(), "messageId can not be null"),
                        ReadPropertyMessageReply::new);
            }

            @Override
            public CompletionStage send() {
                return LettuceDeviceMessageSender.this.send(message);
            }
        };
    }

    @Override
    public WritePropertyMessageSender writeProperty() {

        WritePropertyMessage message = new WritePropertyMessage();
        message.setDeviceId(deviceId);
        message.setMessageId(IdUtils.newUUID());
        return new WritePropertyMessageSender() {
            @Override
            public WritePropertyMessageSender write(String property, Object value) {
                message.addProperty(property, value);
                return this;
            }

            @Override
            public WritePropertyMessageSender custom(Consumer messageConsumer) {
                messageConsumer.accept(message);
                return this;
            }

            @Override
            public WritePropertyMessageSender header(String header, Object value) {
                message.addHeader(header, value);
                return this;
            }

            @Override
            public CompletionStage retrieveReply() {
                return LettuceDeviceMessageSender.this.retrieveReply(
                        Objects.requireNonNull(message.getMessageId(), "messageId can not be null"),
                        WritePropertyMessageReply::new);
            }

            @Override
            public CompletionStage send() {
                return LettuceDeviceMessageSender.this.send(message);
            }
        };
    }
}