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

posted @ 2019-12-16 22:20  InWcl  阅读(1990)  评论(0编辑  收藏  举报