mqtt重连

 

重新连接是针对与mqtt服务器的连接断开时,直接用mqttConnectOptions去重新连接
如果代码报错导致连接断开,则需要通过重新初始化去连接
package com.xw.elevator.platform.mqtt;

import org.eclipse.paho.client.mqttv3.*;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

import java.util.concurrent.ScheduledExecutorService;

@Component
public class Client {

    private static final Logger logger = LoggerFactory.getLogger(PushCallback.class);

    public static final String HOST = "tcp://192.168.121.36";
    private static final String clientid = "client124";

    public static String Issue_Topic = "Fatri/Devs/{DeviceNo}/Issue";
    public static final String DevInfo_Topic = "Fatri/Devs/Report/DevInfo";
    public static final String EleStatus_Topic = "Fatri/Devs/Report/EleStatus";
    public static final String EleWran_Topic = "Fatri/Devs/Report/EleWarn";

    static MqttClient client;
    private static MqttConnectOptions mqttConnectOptions = null;
    private static String userName = "admin";
    private static String passWord = "123456";

    private ScheduledExecutorService scheduler;

    MqttMessage message;

    volatile static MqttTopic topic;

    MqttTopic topicD;

    MqttTopic topicR;

    static {
        init(clientid);
        start();
    }

    //初始化,连接mqtt服务器
    public static void init(String clientId) {
        mqttConnectOptions = new MqttConnectOptions();
        if (null != mqttConnectOptions) {
            // 设置是否清空session,这里如果设置为false表示服务器会保留客户端的连接记录,设置为true表示每次连接到服务器都以新的身份连接
            mqttConnectOptions.setCleanSession(false);
            mqttConnectOptions.setUserName(userName);
            mqttConnectOptions.setPassword(passWord.toCharArray());
            // 设置超时时间
            mqttConnectOptions.setConnectionTimeout(10);
            // 设置会话心跳时间
            mqttConnectOptions.setKeepAliveInterval(20);

            try {
                //new MemoryPersistence() 存储方式
                client = new MqttClient(HOST, clientid, new MemoryPersistence());
            } catch (MqttException e) {
                e.printStackTrace();
            }

            //设置回调
            if (null != client) {
                client.setCallback(new PushCallback());
                try {
                    //连接
                    client.connect(mqttConnectOptions);
                } catch (MqttException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    //订阅主题
    public static void start() {
        try {
            int[] Qos = new int[]{0, 0, 0};
            String[] topics = new String[]{DevInfo_Topic, EleStatus_Topic, EleWran_Topic};
            client.subscribe(topics, Qos);
            logger.info("订阅主题成功");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    //发布
    public void publish(MqttTopic topic, MqttMessage message) throws MqttPersistenceException, MqttException {
        MqttDeliveryToken token = topic.publish(message);
        token.waitForCompletion();
        logger.info("Client message is published completely! " + token.isComplete());
    }

    //重连
    public void reConnect() throws MqttException {
        logger.info("进reConnect方法了");
        if (null != client) {
            logger.info("client:{}", client);
            logger.info("isConnected?{}", client.isConnected());
            if (!client.isConnected()) {
//                logger.info("mqttConnectOptions:{}",mqttConnectOptions);
                if (null != mqttConnectOptions) {
                    logger.info("重新连接");
                    client.setCallback(new PushCallback());
//                    try {
                    logger.info("连接");
                    //此处的重新连接是针对与mqtt服务器的连接断开时,直接用mqttConnectOptions去重新连接
                    client.connect(mqttConnectOptions);
                    //如果代码报错导致连接断开,则需要通过重新初始化去连接
//                        init(clientid);
                    start();
                } else {
                    logger.info("mqttConnectOptions is null");
                    init(clientid);
                    start();
                }
            }
        } else {
            logger.info("重新初始化");
            init(clientid);
            start();
        }
    }
}

回调函数

package com.xw.elevator.platform.mqtt;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.eclipse.paho.client.mqttv3.MqttPersistenceException;
import org.eclipse.paho.client.mqttv3.MqttTopic;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;

import net.sf.json.JSONObject;

import com.alibaba.fastjson.JSON;
import com.xw.elevator.platform.biz.dto.Feature;
import com.xw.elevator.platform.biz.dto.ReveiveElevatorInfoDTO;
import com.xw.elevator.platform.web.ResponseOfReveiveElevatorInfo;

/**
 * 发布消息的回调类
 * <p>
 * 必须实现MqttCallback的接口并实现对应的相关接口方法CallBack 类将实现 MqttCallBack。
 * 每个客户机标识都需要一个回调实例。在此示例中,构造函数传递客户机标识以另存为实例数据。 在回调中,将它用来标识已经启动了该回调的哪个实例。
 * 必须在回调类中实现三个方法:
 * <p>
 * public void messageArrived(MqttTopic topic, MqttMessage message)接收已经预订的发布。
 * <p>
 * public void connectionLost(Throwable cause)在断开连接时调用。
 * <p>
 * public void deliveryComplete(MqttDeliveryToken token)) 接收到已经发布的 QoS 1 或 QoS 2
 * 消息的传递令牌时调用。 由 MqttClient.connect 激活此回调。
 */
public class PushCallback implements MqttCallback {
    private static final Logger logger = LoggerFactory.getLogger(PushCallback.class);

    private static int number = 0;// 唯一数字,集群第一台=0,第二台=200000,第三台=400000
    private static int maxNum = 200000;// 最大值,集群第一台=200000,第二台=400000,第三台=600000
    private static SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS");// 年月日格式

    MqttTopic topic1;

    public void connectionLost(Throwable cause) {
        // 连接丢失后,一般在这里面进行重连
        logger.info("连接断开,可以做重连");
//        Client client = new Client();
        Client myMqttClient = new Client();
        for (int i = 0; i < 10; i++) {
            try {
                myMqttClient.reConnect();
                logger.info("客户端重新连接成功");
            } catch (Exception e) {
                logger.info("重新连接失败:{},"+"正在第"+i+"次尝试",e.getMessage());
                continue;
            }
            return;
        }
        throw new RuntimeException("无法连接服务器");
    }

    public void deliveryComplete(IMqttDeliveryToken token) {
        logger.info("deliveryComplete---------" + token.isComplete());
    }

    public void publish(MqttTopic topic, MqttMessage message) throws MqttPersistenceException, MqttException {
        MqttDeliveryToken token = topic.publish(message);
        token.waitForCompletion();
        logger.info("message is published completely! " + token.isComplete());
    }

    public void messageArrived(String topic, MqttMessage message) throws Exception {
        // subscribe后得到的消息会执行到这里面
        logger.info("接收消息主题:{}", topic);
        logger.info("接收消息Qos:{}", message.getQos());
        logger.info("接收消息内容:{}", new String(message.getPayload()));

        JSONObject jsonObject = JSONObject.fromObject(new String(message.getPayload()));

        logger.info("jsonObject:" + jsonObject);

        String DeviceNo = jsonObject.getString("DeviceNo");
        String MessageType = jsonObject.getString("MessageType");

        // 获取当前时间的字符串
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String date = sdf.format(new Date());

        logger.info("MessageType:" + MessageType);
        // 1.接收心跳报文不用应答
        if (null != MessageType && MessageType.equals("AliveInfo")) {
            logger.info("接收的是心跳报文");
            return;
        }
        logger.info("DeviceNo:" + DeviceNo);

        // 2.接收的是注册报文,则需要发布注册应答报文.
        if (null != MessageType && MessageType.equals("DeviceReg")) {
            logger.info("接收的是注册报文,需要发布注册应答报文.");
            Client client = new Client();

            client.message = new MqttMessage();
            client.message.setQos(2);
            client.message.setRetained(true);

            // client.Issue_Topic.replace("{DeviceNo}", DeviceNo);

            client.topic = client.client.getTopic(client.Issue_Topic.replace("{DeviceNo}", DeviceNo));

            RegReply r = new RegReply();
            Data d = new Data();
            d.setDateTimestamp(date);
            r.setMessageType("RegReply");
            r.setDeviceNo(DeviceNo);
            r.setData(d);

            logger.info(r.toString());

            String s = JSON.toJSONString(r);
            logger.info("s:" + s);

            client.message.setPayload(s.getBytes());

            logger.info("client.topic:" + client.topic + "," + "client.message:" + client.message);
            client.publish(client.topic, client.message);
            logger.info("注册应答报文发送成功");
        }

        // 3.接收的是设备信息报文,则需要发送设备信息应答报文
        if (null != MessageType && MessageType.equals("DeviceInfo")) {
            logger.info("接收的是设备信息报文,需要发送设备信息应答报文");
            Client client = new Client();

            client.message = new MqttMessage();
            client.message.setQos(2);
            client.message.setRetained(true);

            // client.Issue_Topic.replace("{DeviceNo}", DeviceNo);

            Client.topic = Client.client.getTopic(Client.Issue_Topic.replace("{DeviceNo}", DeviceNo));

            RegReply r = new RegReply();
            Data d = new Data();
            d.setDateTimestamp(date);
            r.setMessageType("InfoReply");
            r.setDeviceNo(DeviceNo);
            r.setData(d);

            logger.info(r.toString());

            String s = JSON.toJSONString(r);
            logger.info("s:" + s);

            client.message.setPayload(s.getBytes());

            logger.info("client.topic:" + client.topic + "," + "client.message:" + client.message);
            client.publish(client.topic, client.message);
            logger.info("设备信息应答报文发送成功");
        }

        // 4.接收的是电梯状态信息报文
        if (null != MessageType && MessageType.equals("ElevatorInfo")) {
            logger.info("接收的是电梯状态信息报文");
            String Data = jsonObject.getString("Data");
            JSONObject jsonData = JSONObject.fromObject(Data);
            String DateTimestamp = jsonData.getString("DateTimestamp");
            String Floor = jsonData.getString("Floor");
            String Direction = jsonData.getString("Direction");
            String DoorStatus = jsonData.getString("DoorStatus");
            String Speed = jsonData.getString("Speed");
            String WaterDepth = null;
            String ActivePower = null;
            String ReactivePower = null;
            String SeismicWavePeak = null;
            if (jsonData.has("WaterDepth")) {
                WaterDepth = jsonData.getString("WaterDepth");
            }
            if (jsonData.has("ActivePower")) {
                ActivePower = jsonData.getString("ActivePower");
            }
            if (jsonData.has("ReactivePower")) {
                ReactivePower = jsonData.getString("ReactivePower");
            }
            if (jsonData.has("SeismicWavePeak")) {
                SeismicWavePeak = jsonData.getString("SeismicWavePeak");
            }
//			number++;// 唯一数字自增
//			if (number >= maxNum) { // 值的上限,超过就归零
//				number = maxNum - 200000;
//			}
//			String deviceId = sdf.format(new Date()) + number;// 返回时间+一毫秒内唯一数字的编号,区分机器可以加字母ABC...

            ReveiveElevatorInfoDTO reveiveElevatorInfoDTO = new ReveiveElevatorInfoDTO();
            reveiveElevatorInfoDTO.setDeviceId(DeviceNo);
            List<Feature> list = new ArrayList<>();

            // Long updatedAt = new Date().getTime() / 1000;

            long updatedAt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(DateTimestamp).getTime() / 1000;
            if (null != Floor) {
                Feature feature = new Feature();
                feature.setIdentifier("Floor");
                feature.setName("当前楼层");
                feature.setValue(Floor);
                feature.setType("STRING");
                feature.setUpdatedAt(updatedAt);

                list.add(feature);
            }
            if (null != Direction) {
                Feature feature = new Feature();
                feature.setIdentifier("Direction");
                feature.setName("电梯当前运行方向");
                feature.setValue(Direction);
                feature.setType("STRING");
                feature.setUpdatedAt(updatedAt);

                list.add(feature);
            }
            if (null != DoorStatus) {
                Feature feature = new Feature();
                feature.setIdentifier("DoorStatus");
                feature.setName("电梯门开关状态");
                feature.setValue(DoorStatus);
                feature.setType("STRING");
                feature.setUpdatedAt(updatedAt);

                list.add(feature);
            }
            if (null != Speed) {
                Feature feature = new Feature();
                feature.setIdentifier("Speed");
                feature.setName("速度");
                feature.setValue(Speed);
                feature.setType("STRING");
                feature.setUpdatedAt(updatedAt);

                list.add(feature);
            }
            if (null != WaterDepth) {
                Feature feature = new Feature();
                feature.setIdentifier("WaterDepth");
                feature.setName("电梯井水位深度");
                feature.setValue(WaterDepth);
                feature.setType("STRING");
                feature.setUpdatedAt(updatedAt);

                list.add(feature);
            }
            if (null != ActivePower) {
                Feature feature = new Feature();
                feature.setIdentifier("ActivePower");
                feature.setName("电梯主电源总有功功率");
                feature.setValue(ActivePower);
                feature.setType("STRING");
                feature.setUpdatedAt(updatedAt);

                list.add(feature);
            }
            if (null != ReactivePower) {
                Feature feature = new Feature();
                feature.setIdentifier("ReactivePower");
                feature.setName("电梯主电源总无功功率");
                feature.setValue(ReactivePower);
                feature.setType("STRING");
                feature.setUpdatedAt(updatedAt);

                list.add(feature);
            }
            if (null != SeismicWavePeak) {
                Feature feature = new Feature();
                feature.setIdentifier("SeismicWavePeak");
                feature.setName("楼宇震幅(方向)");
                feature.setValue(SeismicWavePeak);
                feature.setType("STRING");
                feature.setUpdatedAt(updatedAt);

                list.add(feature);
            }

            reveiveElevatorInfoDTO.setData(list);
            String url = "http://192.168.121.189:8081/receive/elevatorInfo";
            String url1 = "http://dev2.sefuture.cn/receive/elevatorInfo";
            RestTemplate restTemplate = new RestTemplate();
            try {
            restTemplate.postForEntity(url, reveiveElevatorInfoDTO, ResponseOfReveiveElevatorInfo.class);
            restTemplate.postForEntity(url1, reveiveElevatorInfoDTO, ResponseOfReveiveElevatorInfo.class);
            logger.info("推送电梯状态消息消息成功");
            } catch (RestClientException e) {
                logger.error("推送消息异常:{}",e.getMessage());
            }
        }

        // 5.接收的是电梯故障预警报文
        if (null != MessageType && MessageType.equals("ElevatorWarn")) {
            logger.info("接收的是电梯故障预警报文");
            String Data = jsonObject.getString("Data");
            JSONObject jsonData = JSONObject.fromObject(Data);
            String DateTimestamp = jsonData.getString("DateTimestamp");
            String WarnType = jsonData.getString("WarnType");
            String WarnStatus = jsonData.getString("WarnStatus");
            String Detail = null;
            if (jsonData.has("Detail")) {
                Detail = jsonData.getString("Detail");
            }

            // String OriginalData = jsonData.getString("OriginalData");

            long updatedAt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(DateTimestamp).getTime() / 1000;

            ReveiveElevatorInfoDTO reveiveElevatorInfoDTO = new ReveiveElevatorInfoDTO();
            // reveiveElevatorInfoDTO.setDeviceId(deviceId);
            List<Feature> list = new ArrayList<>();

            if ("DoorLock".equals(WarnType)) {
                Feature feature = new Feature();
                feature.setIdentifier("Warn_DoorLock");
                feature.setName("门锁回路告警");
                feature.setValue(WarnStatus);
                feature.setUpdatedAt(updatedAt);
                if (null != Detail) {
                    feature.setDetail(Detail);
                }
                list.add(feature);
            }
            if ("Noise".equals(WarnType)) {
                Feature feature = new Feature();
                feature.setIdentifier("Warn_Noise");
                feature.setName("轿厢噪音告警");
                feature.setValue(WarnStatus);
                feature.setUpdatedAt(updatedAt);
                if (null != Detail) {
                    feature.setDetail(Detail);
                }
                list.add(feature);
            }
            if ("Shake".equals(WarnType)) {
                Feature feature = new Feature();
                feature.setIdentifier("Warn_Shake");
                feature.setName("轿厢晃动超标预警");
                feature.setValue(WarnStatus);
                feature.setUpdatedAt(updatedAt);
                if (null != Detail) {
                    feature.setDetail(Detail);
                }
                list.add(feature);
            }
            if ("OpenDoorRun".equals(WarnType)) {
                Feature feature = new Feature();
                feature.setIdentifier("Warn_OpenDoorRun");
                feature.setName("开门走梯告警");
                feature.setValue(WarnStatus);
                feature.setUpdatedAt(updatedAt);
                if (null != Detail) {
                    feature.setDetail(Detail);
                }
                list.add(feature);
            }
            if ("KeepOpen".equals(WarnType)) {
                Feature feature = new Feature();
                feature.setIdentifier("Warn_KeepOpen");
                feature.setName("开门时间过长告警");
                feature.setValue(WarnStatus);
                feature.setUpdatedAt(updatedAt);
                if (null != Detail) {
                    feature.setDetail(Detail);
                }
                list.add(feature);
            }
            if ("Speed".equals(WarnType)) {
                Feature feature = new Feature();
                feature.setIdentifier("Warn_Speed");
                feature.setName("电梯移动超速预警");
                feature.setValue(WarnStatus);
                feature.setUpdatedAt(updatedAt);
                if (null != Detail) {
                    feature.setDetail(Detail);
                }
                list.add(feature);
            }
            if ("InvalidStop".equals(WarnType)) {
                Feature feature = new Feature();
                feature.setIdentifier("Warn_InvalidStop");
                feature.setName("异常停层预警");
                feature.setValue(WarnStatus);
                feature.setUpdatedAt(updatedAt);
                if (null != Detail) {
                    feature.setDetail(Detail);
                }
                list.add(feature);
                logger.info("接到异常停层预警");
            }
            if ("SafetyLoop".equals(WarnType)) {
                Feature feature = new Feature();
                feature.setIdentifier("Warn_SafetyLoop");
                feature.setName("安全回路告警");
                feature.setValue(WarnStatus);
                feature.setUpdatedAt(updatedAt);
                if (null != Detail) {
                    feature.setDetail(Detail);
                }
                list.add(feature);
            }
            if ("Brake".equals(WarnType)) {
                Feature feature = new Feature();
                feature.setIdentifier("Warn_Brake");
                feature.setName("曳引机抱闸异常预警");
                feature.setValue(WarnStatus);
                feature.setUpdatedAt(updatedAt);
                if (null != Detail) {
                    feature.setDetail(Detail);
                }
                list.add(feature);
            }
            if ("Displacement".equals(WarnType)) {
                Feature feature = new Feature();
                feature.setIdentifier("Warn_Displacement");
                feature.setName("抱闸移位预警");
                feature.setValue(WarnStatus);
                feature.setUpdatedAt(updatedAt);
                if (null != Detail) {
                    feature.setDetail(Detail);
                }
                list.add(feature);
            }
            if ("OverTop".equals(WarnType)) {
                Feature feature = new Feature();
                feature.setIdentifier("Warn_OverTop");
                feature.setName("轿厢冲顶预警");
                feature.setValue(WarnStatus);
                feature.setUpdatedAt(updatedAt);
                if (null != Detail) {
                    feature.setDetail(Detail);
                }
                list.add(feature);
            }
            if ("UnderBottom".equals(WarnType)) {
                Feature feature = new Feature();
                feature.setIdentifier("Warn_UnderBottom");
                feature.setName("轿厢蹲底预警");
                feature.setValue(WarnStatus);
                feature.setUpdatedAt(updatedAt);
                if (null != Detail) {
                    feature.setDetail(Detail);
                }
                list.add(feature);
            }
            if ("ErrorLayer".equals(WarnType)) {
                Feature feature = new Feature();
                feature.setIdentifier("Warn_ErrorLayer");
                feature.setName("开门未能准确平层告警");
                feature.setValue(WarnStatus);
                feature.setUpdatedAt(updatedAt);
                if (null != Detail) {
                    feature.setDetail(Detail);
                }
                list.add(feature);
            }
            if ("CarStuck".equals(WarnType)) {
                Feature feature = new Feature();
                feature.setIdentifier("Warn_CarStuck");
                feature.setName("轿厢卡在井道中告警");
                feature.setValue(WarnStatus);
                feature.setUpdatedAt(updatedAt);
                if (null != Detail) {
                    feature.setDetail(Detail);
                }
                list.add(feature);
            }
            if ("TractionTemperature".equals(WarnType)) {
                Feature feature = new Feature();
                feature.setIdentifier("Warn_TractionTemperature");
                feature.setName("曳引机温度告警");
                feature.setValue(WarnStatus);
                feature.setUpdatedAt(updatedAt);
                if (null != Detail) {
                    feature.setDetail(Detail);
                }
                list.add(feature);
            }
            reveiveElevatorInfoDTO.setData(list);
            //把设备序列号作为设备id,原先设备id是通过设备序列号绑定获得的
            reveiveElevatorInfoDTO.setDeviceId(DeviceNo);
            String url = "http://dev2.sefuture.cn/receive/elevatorInfo";
            String url1 = "http://192.168.121.189:8081/receive/elevatorInfo";
//            String url1 = "http://192.168.121.11:8888";
            RestTemplate restTemplate = new RestTemplate();
            if ("WARN".equals(WarnStatus) || "ERROR".equals(WarnStatus)) {
                try {
                    restTemplate.postForEntity(url, reveiveElevatorInfoDTO, ResponseOfReveiveElevatorInfo.class);
                    restTemplate.postForEntity(url1, reveiveElevatorInfoDTO, ResponseOfReveiveElevatorInfo.class);
                    logger.info("推送电梯报警消息成功");
                } catch (RestClientException e) {
                    logger.error("推送消息异常:{}", e.getMessage());
                }
            }
        }
    }
}

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MQTT 客户端在连接断开时需要进行重连重连时间可以根据具体需求进行设置。一般来说,重连时间需要考虑以下因素: 1. 重连间隔:重连的时间间隔需要足够长,避免频繁重连对服务器造成过大的负担。 2. 重连次数:重连的次数需要有限制,避免无限制地重连,导致设备资源浪费和服务器负担过大。 3. 重连策略:重连策略需要根据具体情况进行设置,例如指数退避重连、随机延迟重连等。 在 Python 中,可以使用 `paho-mqtt` 库实现 MQTT 客户端的重连功能。`paho-mqtt` 库提供了 `reconnect_delay_set()` 方法来设置重连间隔,`reconnect_max_retries_set()` 方法来设置重连次数,`on_disconnect()` 方法中可以编写重连策略。 示例代码如下: ```python import time import paho.mqtt.client as mqtt def on_connect(client, userdata, flags, rc): print("Connected with result code " + str(rc)) def on_disconnect(client, userdata, rc): print("Disconnected with result code " + str(rc)) # 重连间隔和次数设置 client.reconnect_delay_set(min_delay=1, max_delay=120) client.reconnect_max_retries_set(10) # 重连策略 while not client.is_connected(): try: client.reconnect() except Exception as e: print("Reconnect failed: " + str(e)) time.sleep(1) client = mqtt.Client() client.on_connect = on_connect client.on_disconnect = on_disconnect client.connect("mqtt.eclipse.org", 1883, 60) client.loop_forever() ``` 在上述示例代码中,`reconnect_delay_set()` 方法设置了重连间隔为 1 秒到 120 秒之间的随机值,`reconnect_max_retries_set()` 方法设置了最大重连次数为 10 次。在 `on_disconnect()` 方法中,使用了 `while` 循环进行重连,直到连接成功为止。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值