• Spring boot 集成 阿里 Mqtt


    因为公司业务需求,需要接入 阿里Mqtt,自己基于Spring写了一个小demo,记录下来,已备以后需要。

    第一步

    创建一个实体bean用来装载 MqttClient

    private MqttClient mqttClient;
    
        @Autowired
        private MqttConnectOptions mqttConnectOptions;
    
        @Autowired
        private   MqttConfig mqttConfig;
    
        @Autowired
        private MqttCallback mqttCallback;
    
    
        private void start() throws MqttException {
           final MemoryPersistence memoryPersistence = new MemoryPersistence();
            /**
             * 客户端使用的协议和端口必须匹配,具体参考文档 https://help.aliyun.com/document_detail/44866.html?spm=a2c4g.11186623.6.552.25302386RcuYFB
             * 如果是 SSL 加密则设置ssl://endpoint:8883
             */
            this.mqttClient= new MqttClient("tcp://" + mqttConfig.getConnectEndpoint() + ":1883",
                    mqttConfig.getGroupId() + "@@@" + mqttConfig.getClientId(), memoryPersistence);
            mqttClient.setTimeToWait(mqttConfig.getTimeToWait());
            mqttClient.setCallback(mqttCallback);
            mqttClient.connect(mqttConnectOptions);
        }
    
        private void shutdown() throws MqttException {
            this.mqttClient.disconnect();
        }
        public MqttClient getMqttClient(){
            return this.mqttClient;
        }
    

    第二步

    对MqClient 进行加载

    
    @Autowired
        private MqttConfig mqttConfig;
    @Bean
    public MqttConnectOptions getMqttConnectOptions() throws NoSuchAlgorithmException, InvalidKeyException {
        MqttConnectOptions mqttConnectOptions=new MqttConnectOptions();
        //组装用户名密码
        mqttConnectOptions.setUserName("Signature|" + mqttConfig.getAccessKey() + "|" + mqttConfig.getInstanceId());
        //密码签名
        mqttConnectOptions.setPassword(info.feibiao.live.config.mqtt.Tools.macSignature(mqttConfig.getGroupId()+"@@@"+mqttConfig.getClientId(), mqttConfig.getSecretKey()).toCharArray());
        mqttConnectOptions.setCleanSession(true);
        mqttConnectOptions.setKeepAliveInterval(90);
        mqttConnectOptions.setAutomaticReconnect(true);
        mqttConnectOptions.setMqttVersion(MQTT_VERSION_3_1_1);
        //连接超时时间
        mqttConnectOptions.setConnectionTimeout(5000);
        mqttConnectOptions.setKeepAliveInterval(2);
        return mqttConnectOptions;
    }
    @Bean(initMethod = "start", destroyMethod = "shutdown")
    public MqttClientBean getClient() {
        return new MqttClientBean();
    }
    

    第三步

    创建接收消息,连接成功,连接丢失 回调类
    连接成功后需要订阅相关主题

    @Autowired
        MqttClientBean mqttClientBean;
        @Autowired
        MqttConfig mqttConfig;
        @Override
        public void connectComplete(boolean reconnect, String serverURI) {
            /**
             * 客户端连接成功后就需要尽快订阅需要的 topic
             */
            System.out.println("connect success");
    
            ExecutorService mqttExecutorService = new ThreadPoolExecutor(1, 1, 0, TimeUnit.MILLISECONDS,
                    new LinkedBlockingQueue<Runnable>());
            mqttExecutorService.submit(() -> {
                try {
                    //订阅主题,主主题后面可以跟子主题 过滤规则 +:过滤一级 ,#:过滤所有
                    final String[] topicFilter = {mqttConfig.getTopicId() + "/" + "testMq4Iot"};
                    int qosLevel=0;
                    final int[] qos = {qosLevel};
                    MqttClient mqttClient = mqttClientBean.getMqttClient();
                    mqttClient.subscribe(topicFilter, qos);
                } catch (MqttException e) {
                    e.printStackTrace();
                }
            });
        }
        
        @Override
        public void connectionLost(Throwable throwable) {
            throwable.printStackTrace();
        }
        
        @Override
        public void messageArrived(String s, MqttMessage mqttMessage) throws Exception {
            /**
             * 这个地方消费
             * 消费消息的回调接口,需要确保该接口不抛异常,该接口运行返回即代表消息消费成功。
             * 消费消息需要保证在规定时间内完成,如果消费耗时超过服务端约定的超时时间,对于可靠传输的模式,服务端可能会重试推送,业务需要做好幂等去重处理。超时时间约定参考限制
             * https://help.aliyun.com/document_detail/63620.html?spm=a2c4g.11186623.6.546.229f1f6ago55Fj
             */
            System.out.println(
                    "receive msg from topic " + s + " , body is " + new String(mqttMessage.getPayload()));
        }
        
        @Override
        public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) {
            System.out.println("send msg succeed topic is : " + iMqttDeliveryToken.getTopics()[0]);
        }
    

    第四步

    添加配置实体类,从yml配置文件中读取配置数据

    /**
         * 可在阿里云控制台找到(实例id)
         */
        private String instanceId;
        /**
         * accessKey
         */
        private String accessKey;
        /**
         * 密钥
         */
        private String secretKey;
        /**
         * TCP 协议接入点
         */
        private String connectEndpoint;
        /**
         * 话题id
         */
        private String topicId;
        /**
         * 群组id
         */
        private String groupId;
        /**
         * 消息模式(广播订阅, 集群订阅)
         */
        private String messageModel;
    
        /**
         * 超时时间
         */
        private String sendMsgTimeoutMillis;
        
        /**
         * 顺序消息消费失败进行重试前的等待时间 单位(毫秒)
         */
        private String suspendTimeMillis;
        
        /**
         * 消息消费失败时的最大重试次数
         */
        private String maxReconsumeTimes;
        
        /**
         * 公网token服务器
         */
        private String mqttClientTokenServer;
        /**
         * 过期时间(默认1个月)
         */
        private Long mqttClientTokenExpireTime;
        /**
         * 分发给客户端的token的操作权限
         */
        private String mqttAction;
        /**
         * 客户端标识
         */
        private String clientId;
        
        /**
         * QoS参数代表传输质量,可选0,1,2,根据实际需求合理设置,具体参考 https://help.aliyun.com/document_detail/42420.html?spm=a2c4g.11186623.6.544.1ea529cfAO5zV3
         */
        private int qosLevel = 0;
        /**
         * 客户端超时时间
         */
        private  int timeToWait;
    

    配置文件:

    spring.application.name: mqtt-server-demo
    server.port: 18005
    # mqtt消息
    mqtt.msg:
      instanceId: post-cn-0pp13c3gn0u #实例Id
      accessKey: LTAIPZjAd2naVfA0	#appId
      secretKey: 38ZLMHoP5r4p0a4gUEGUhzL46EdzQx #密钥 阿里云控制台查看
      connectEndpoint: post-cn-0pp13c3gn0u.mqtt.aliyuncs.com #端点
      topicId: TID_liveChat #父级主题
      groupId: GID_liveChat #分组
      messageModel: BROADCASTING #广播订阅方式, 默认是 CLUSTERING 集群订阅
      sendMsgTimeoutMillis: 20000 # 发消息超时时间30s
      suspendTimeMillis: 500 #顺序消息消费失败进行重试前的等待时间 单位(毫秒)
      maxReconsumeTimes: 3 #消息消费失败时的最大重试次数
      mqttClientTokenServer: mqauth.aliyuncs.com # 公网token服务器
      mqttClientTokenExpireTime: 2592000000 # token 过期时间1个月
      mqttAction: R,W # 读写操作
      clientId: FEI_JAVA #客户端名称
      qosLevel: 0 #QoS参数代表传输质量,可选0,1,2
      timeToWait: 5000 #客户端超时时间
    spring.main.allow-bean-definition-overriding: true
    

    最后贴上签名方法:

    /**
     * 计算签名,参数分别是参数对以及密钥
     *
     * @param requestParams 参数对,即参与计算签名的参数
     * @param secretKey 密钥
     * @return 签名字符串
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeyException
     */
    public static String doHttpSignature(Map<String, String> requestParams,
        String secretKey) throws NoSuchAlgorithmException, InvalidKeyException {
        List<String> paramList = new ArrayList<String>();
        for (Map.Entry<String, String> entry : requestParams.entrySet()) {
            paramList.add(entry.getKey() + "=" + entry.getValue());
        }
        Collections.sort(paramList);
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < paramList.size(); i++) {
            if (i > 0) {
                sb.append('&');
            }
            sb.append(paramList.get(i));
        }
        return macSignature(sb.toString(), secretKey);
    }
    
    /**
     * @param text 要签名的文本
     * @param secretKey 阿里云MQ secretKey
     * @return 加密后的字符串
     * @throws InvalidKeyException
     * @throws NoSuchAlgorithmException
     */
    public static String macSignature(String text,
        String secretKey) throws InvalidKeyException, NoSuchAlgorithmException {
        Charset charset = Charset.forName("UTF-8");
        String algorithm = "HmacSHA1";
        Mac mac = Mac.getInstance(algorithm);
        mac.init(new SecretKeySpec(secretKey.getBytes(charset), algorithm));
        byte[] bytes = mac.doFinal(text.getBytes(charset));
        return new String(Base64.encodeBase64(bytes), charset);
    }
    

    阿里云mqtt支持Token 模式:

    获取token以及销毁token:

    private static final String applyTokenUrl = "/token/apply";
    private static final String revokeTokenUrl = "/token/revoke";
    
    
    
    /**
     * 申请 Token 接口,具体参数参考链接
     * https://help.aliyun.com/document_detail/54276.html?spm=a2c4g.11186623.6.562.f12033f5ay6nu5
     *
     * @param apiUrl token 服务器地址,参考文档设置正确的地址
     * @param accessKey 账号 AccessKey,由控制台获取
     * @param secretKey 账号 SecretKey,由控制台获取
     * @param topics 申请的 topic 列表
     * @param action Token类型
     * @param expireTime Token 过期的时间戳
     * @param instanceId MQ4IoT 实例 Id
     * @return 如果申请成功则返回 token 内容
     * @throws InvalidKeyException
     * @throws NoSuchAlgorithmException
     * @throws IOException
     * @throws KeyStoreException
     * @throws UnrecoverableKeyException
     * @throws KeyManagementException
     */
    public String applyToken(String apiUrl, String accessKey, String secretKey, List<String> topics,
                                    String action,
                                    long expireTime,
                                    String instanceId) throws InvalidKeyException, NoSuchAlgorithmException, IOException, KeyStoreException, UnrecoverableKeyException, KeyManagementException {
        Map<String, String> paramMap = new HashMap<>();
        Collections.sort(topics);
        StringBuilder builder = new StringBuilder();
        for (String topic : topics) {
            builder.append(topic).append(",");
        }
        if (builder.length() > 0) {
            builder.setLength(builder.length() - 1);
        }
        paramMap.put("resources", builder.toString());
        paramMap.put("actions", action);
        paramMap.put("serviceName", "mq");
        paramMap.put("expireTime", String.valueOf(System.currentTimeMillis() + expireTime));
        paramMap.put("instanceId", instanceId);
        String signature = Tools.doHttpSignature(paramMap, secretKey);
        paramMap.put("proxyType", "MQTT");
        paramMap.put("accessKey", accessKey);
        paramMap.put("signature", signature);
        JSONObject object = Tools.httpsPost("http://"+apiUrl + applyTokenUrl, paramMap);
        if (object != null) {
            return (String) object.get("tokenData");
        }
        return null;
    }
    
    /**
     * 提前注销 token,一般在 token 泄露出现安全问题时,提前禁用特定的客户端
     *
     * @param apiUrl token 服务器地址,参考文档设置正确的地址
     * @param accessKey 账号 AccessKey,由控制台获取
     * @param secretKey 账号 SecretKey,由控制台获取
     * @param token 禁用的 token 内容
     * @throws InvalidKeyException
     * @throws NoSuchAlgorithmException
     * @throws IOException
     * @throws UnrecoverableKeyException
     * @throws KeyStoreException
     * @throws KeyManagementException
     */
    public void revokeToken(String apiUrl, String accessKey, String secretKey,
                                   String token) throws InvalidKeyException, NoSuchAlgorithmException, IOException, UnrecoverableKeyException, KeyStoreException, KeyManagementException {
        Map<String, String> paramMap = new HashMap<String, String>();
        paramMap.put("token", token);
        String signature = Tools.doHttpSignature(paramMap, secretKey);
        paramMap.put("signature", signature);
        paramMap.put("accessKey", accessKey);
        JSONObject object = Tools.httpsPost("http://"+apiUrl + revokeTokenUrl, paramMap);
    }
    

    Token模式客户端使用方式:

    在构建ConnectionOptionWrapper的时候使用签发的token:

    String token="LzMT+XLFl5u**********************************KhCznZx";
    Map<String, String> tokenData = new HashMap<String, String>();
    tokenData.put("RW", token);
    ConnectionOptionWrapper connectionOptionWrapper = new ConnectionOptionWrapper(instanceId, accessKey, clientId, tokenData);
    

    码云地址:
    https://gitee.com/ioso/mqtt-demo

  • 相关阅读:
    H50068:html页面清除缓存
    CSS0019: 样式中高度百分比无效时,这样写 height:calc(100%)
    H50067:body 背景颜色 背景图片 background 的 简写属性
    40、在last_update后面新增加一列名字为create_date
    39、使用强制索引查询
    38、针对actor表创建视图actor_name_view
    37、对first_name创建唯一索引uniq_idx_firstname,对last_name创建普通索引idx_lastname
    36、创建一个actor_name表,将actor表中的所有first_name以及last_name导入改表
    35、批量插入如下数据,不使用replace操作
    34、批量插入如下数据
  • 原文地址:https://www.cnblogs.com/INWCL/p/12051752.html
Copyright © 2020-2023  润新知