【人工智能应用技术】-基础实战-小程序应用(基于springAI+百度语音技术)智能语音控制-单片机交互代码

我们项目里采用的领域驱动设计的四层架构,包括领域服务层,它负责实现领域的业务逻辑;聚合领域用来封装相关的业务实体;还有基础设施层,承担数据存储等基础功能;以及应用层,负责协调各层交互,处理用户请求。 
简单说明下 有些同学可能接触这类项目
 DDD 里的领域服务更强调业务规则的实现,和传统 service
 单纯处理业务逻辑还是有区别的。基础设施层不只是数据访问,还包括外部接口调用这些。应用层也不只是简单接收请求响应,更注重对领域功能的服务编排。以前的
dao 层主要负责数据库操作,实体呢主要是数据的载体。而领域(Domain)是对业务概念和规则的抽象,它包含了实体、值对象这些,比简单把
dao 层和实体组合要丰富得多,更关注业务本身的逻辑和规则呢。

后端Java业务系统与远程设备的单片机系统的交互核心

Java平台与ESP32单片机的核心交互代码,聚焦认证、指令下发、数据上报三大核心逻辑。

ESP32单片机与Java后端业务平台交互逻辑

摘要:本文聚焦物联网场景下ESP32单片机与Java智能控制业务平台的交互逻辑,从架构设计、通信协议、核心流程、安全机制及代码实现五个维度,系统拆解“设备接入-指令下发-状态上报-业务评估”全链路交互体系,阐明Java业务平台在设备管控、数据校验、业务决策中的核心作用,以及ESP32单片机在数据采集与指令执行中的终端适配能力,为物联网设备与后端业务平台的标准化交互提供实践参考。

一、交互背景与核心目标

1.1 应用场景

在智能设备评估业务中,Java平台承担“业务逻辑处理、设备状态评估、指令策略决策”核心职责,ESP32单片机作为终端感知与执行单元,负责采集环境数据(温湿度、光照、气体浓度等)、接收并执行平台指令(启停控制、参数调节、采样频率配置),并将设备运行状态、采集数据回传至Java平台,形成“感知-决策-执行-评估”的业务闭环。典型场景包括智能家居环境评估、工业现场设备运行状态评估、校园物联网监测评估等。

1.2 核心交互目标

  • 可靠通信:保障指令下发与状态上报的实时性、完整性,避免数据丢包或延迟;

  • 安全可控:实现设备身份认证、数据加密传输,防止指令伪造与数据篡改;

  • 标准化适配:定义统一的数据格式与交互协议,降低平台与设备的耦合度;

  • 业务联动:Java平台基于设备数据完成业务评估(如设备在线率、数据有效性、控制响应速度),并动态调整交互策略。

二、整体交互架构设计

交互架构采用“三层架构”设计,自上而下分为Java控制业务平台层、通信协议层、ESP32单片机终端层,各层职责清晰、通过标准化接口实现解耦,具体架构如下:

2.1 各层核心组件与职责

  1. Java控制业务平台层(核心决策层)

    1. 业务控制模块:基于设备上报数据完成在线状态评估、数据有效性校验、控制响应延迟统计;

    2. 指令管理模块:解析业务需求、生成标准化指令(如启停、参数调节)、管理指令生命周期;

    3. MQTT通信模块:通过MQTT生产者下发指令、消费者接收设备状态数据,集成协议栈与连接池;

    4. 安全认证模块:实现设备密钥管理、指令/数据签名生成与验证;

    5. 事件驱动模块:处理设备状态变更事件(如离线告警、数据异常),触发评估结果推送。

  2. 通信协议层(传输适配层)

    1. 核心协议:采用MQTT 3.1.1协议,基于主题订阅/发布模式实现双向通信,QoS等级设为1(确保消息至少送达一次);

    2. 数据格式:统一采用JSON格式封装指令与状态数据,定义标准化字段(设备编码、动作类型、参数、时间戳、签名等);

    3. 传输保障:通过TCP/IP协议实现底层传输,配置自动重连、心跳检测机制保障连接稳定性。

  3. ESP32单片机终端层(感知执行层)

    1. MQTT客户端:实现与平台的MQTT连接、主题订阅/发布;

    2. 数据采集模块:通过传感器采集环境数据(温湿度、光照等),完成数据预处理;

    3. 指令执行模块:解析平台指令,驱动执行单元(继电器、电机、传感器)完成操作;

    4. 安全校验模块:验证平台指令签名、生成上报数据签名;

    5. 状态管理模块:维护设备运行状态,触发异常状态上报。

三、核心交互流程全解析

ESP32与Java业务平台的交互核心围绕“设备接入认证-指令下发执行-状态数据上报-平台评估反馈”四大环节展开,形成完整业务链路。

3.1 环节一:设备接入与身份认证

此环节是交互的基础,核心目标是确保接入设备的合法性,防止非法设备接入平台。

  1. 前置准备:Java平台为ESP32设备分配唯一设备编码(如ESP001)与设备密钥(用于身份认证与签名),并存储至设备信息库;

  2. ESP32初始化:启动后初始化WiFi与MQTT客户端,配置平台MQTT Broker地址(如tcp://127.0.0.1:1883)、端口、订阅主题(如esp32/command/ESP001)与发布主题(如esp32/data/ESP001);

  3. 身份认证请求:ESP32向平台发送认证请求,携带设备编码与设备密钥;

  4. 平台认证校验:Java平台通过Esp32AuthUtils工具类校验设备编码合法性与密钥一致性,认证通过则记录设备在线状态,否则拒绝连接;

  5. 认证结果反馈:平台向ESP32推送认证结果,认证通过后建立稳定MQTT连接,开启心跳检测(每60秒发送一次心跳包)。

3.2 环节二:Java平台指令下发与ESP32执行

此环节实现平台对设备的远程控制,核心是确保指令的安全性与执行的准确性。

  1. 业务指令生成:Java业务平台根据业务需求(如用户操作、自动评估策略),通过指令管理模块生成标准化指令(如“调节传感器采样频率为5秒/次”);

  2. 指令适配与签名:通过Esp32MqttDeviceAdapter将标准化指令适配为ESP32可识别的JSON格式,再通过Esp32AuthUtils生成HMAC-SHA256签名(基于设备密钥、指令内容、时间戳),最终封装为“指令数据+时间戳+签名”的完整消息;

  3. 指令下发:Java平台通过MQTT生产者将指令发布至ESP32订阅的主题(如esp32/command/ESP001),QoS等级设为1确保送达;

  4. ESP32指令接收与校验:ESP32订阅主题接收指令后,先校验时间戳有效性(允许±30秒误差,防重放攻击),再通过本地存储的设备密钥重新计算签名,与指令中的签名比对,校验通过则解析指令,否则丢弃;

  5. 指令执行与结果反馈:ESP32驱动对应模块执行指令(如调整传感器采样频率),执行完成后生成执行结果(成功/失败),并准备状态数据上报。

3.3 环节三:ESP32状态上报与Java平台处理

此环节实现设备状态与采集数据的回传,为Java平台的业务评估提供数据支撑。

  1. 数据采集与封装:ESP32通过传感器采集环境数据(如温度25℃、湿度60%),结合设备开关状态、执行结果,封装为标准化JSON格式(含设备编码、状态、采集数据、时间戳);

  2. 上报数据签名:ESP32基于设备密钥、数据内容、时间戳生成HMAC-SHA256签名,将“数据+时间戳+签名”封装为最终上报消息;

  3. 数据上报:ESP32通过MQTT客户端将消息发布至平台订阅的主题(如esp32/data/ESP001);

  4. 平台数据校验:Java平台通过ESP32专属MQTT消费者接收消息,先校验时间戳与签名有效性,再解析数据内容,过滤无效数据(如超出合理范围的温湿度);

  5. 数据持久化与状态同步:平台将校验后的有效数据存储至数据库,通过DeviceStatusSyncService同步设备状态,并发布设备状态变更事件;

  6. 业务评估与反馈:Java评估模块基于同步的设备状态与采集数据,完成业务评估(如设备在线率、数据完整性、响应延迟),若出现异常(如设备离线、数据异常),通过事件驱动模块触发告警(短信/APP推送)。

3.4 环节四:异常处理与连接维护

保障交互的稳定性,处理连接中断、数据异常等场景:

  • 连接中断重连:ESP32检测到MQTT连接断开后,自动触发重连逻辑(间隔1秒,最多重试3次),重连成功后重新发起身份认证;

  • 指令超时重试:Java平台下发指令后,若3秒内未收到ESP32执行反馈,自动重试下发(最多2次),仍失败则标记指令执行失败并触发告警;

  • 数据丢失处理:平台通过心跳检测(每60秒)判断设备在线状态,若连续3次未收到心跳包,标记设备离线并触发告警;ESP32若未收到平台指令响应,主动重发指令请求(最多2次)。

四、安全交互机制详解

为防止非法接入、指令伪造、数据篡改,交互体系设计了“双重认证+签名校验+时间戳防重放”三重安全机制,核心实现依赖Java平台的Esp32AuthUtils工具类与ESP32的签名校验逻辑。

4.1 设备身份认证

基于“设备编码+设备密钥”的双向认证机制:

  • 设备编码唯一:Java平台为每个ESP32分配唯一编码,作为设备的身份标识;

  • 密钥预置与校验:设备密钥在出厂前预置到ESP32,接入平台时需提交密钥,Java平台通过authenticateEsp32Device方法校验密钥一致性,确保只有合法设备接入。

4.2 指令与数据签名校验

采用HMAC-SHA256算法对指令与上报数据进行签名,确保传输过程中不被篡改:

  1. 签名生成规则:基于“设备编码+指令/数据内容+时间戳”的固定顺序拼接原串,使用设备密钥作为加密密钥计算签名,最终通过Base64编码为字符串;

  2. 签名校验逻辑:接收方(平台/ESP32)采用相同规则重新计算签名,与接收的签名比对,一致则校验通过,否则视为无效数据/指令。

4.3 时间戳防重放攻击

通过时间戳限制消息的有效时间,防止攻击者截取消息后重复发送:

  • 时间戳携带:所有指令与上报消息均携带当前时间戳(毫秒级);

  • 有效性校验:接收方校验时间戳与当前时间的差值,超过30秒则视为无效消息,直接丢弃。

五、核心交互代码实现

本节提炼Java平台与ESP32单片机的核心交互代码,聚焦认证、指令下发、数据上报三大核心逻辑。

5.1 Java后端业务平台核心代码

5.1.1 ESP32设备认证工具类(Esp32AuthUtils.java)

package com.smart.device.control.common.utils;

import com.smart.device.control.domain.device.model.Device;
import com.smart.device.control.domain.device.repository.DeviceRepository;
import com.smart.device.control.common.exception.BusinessException;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;

/**
 * ESP32设备认证与签名工具类,核心安全组件
 */
@Component
@RequiredArgsConstructor
public class Esp32AuthUtils {

    private final DeviceRepository deviceRepository;

    /**
     * 设备身份认证:校验设备编码与密钥合法性
     */
    public boolean authenticateEsp32Device(String deviceCode, String deviceSecret) {
        Device device = deviceRepository.findByDeviceCode(deviceCode)
                .orElseThrow(() -> new BusinessException("认证失败:设备不存在,编码:" + deviceCode));
        if (!"ESP32_SENSOR".equals(device.getDeviceType())) {
            throw new BusinessException("认证失败:非ESP32设备,编码:" + deviceCode);
        }
        if (!deviceSecret.equals(device.getDeviceSecret())) {
            throw new BusinessException("认证失败:密钥错误,编码:" + deviceCode);
        }
        return true;
    }

    /**
     * 生成指令签名(平台下发指令时使用)
     */
    public String generateCommandSign(Device device, String commandContent, long timestamp) {
        try {
            String rawSignStr = String.format("%s%s%d", device.getDeviceCode(), commandContent, timestamp);
            Mac hmacSha256 = Mac.getInstance("HmacSHA256");
            SecretKeySpec secretKey = new SecretKeySpec(device.getDeviceSecret().getBytes(StandardCharsets.UTF_8), "HmacSHA256");
            hmacSha256.init(secretKey);
            byte[] signBytes = hmacSha256.doFinal(rawSignStr.getBytes(StandardCharsets.UTF_8));
            return Base64.getEncoder().encodeToString(signBytes);
        } catch (Exception e) {
            throw new BusinessException("签名生成失败:" + e.getMessage());
        }
    }

    /**
     * 验证上报数据签名(平台接收数据时使用)
     */
    public boolean verifyDataSign(String deviceCode, String dataContent, long timestamp, String sign) {
        Device device = deviceRepository.findByDeviceCode(deviceCode)
                .orElseThrow(() -> new BusinessException("签名验证失败:设备不存在"));
        try {
            String rawSignStr = String.format("%s%s%d", deviceCode, dataContent, timestamp);
            Mac hmacSha256 = Mac.getInstance("HmacSHA256");
            SecretKeySpec secretKey = new SecretKeySpec(device.getDeviceSecret().getBytes(StandardCharsets.UTF_8), "HmacSHA256");
            hmacSha256.init(secretKey);
            byte[] computedSignBytes = hmacSha256.doFinal(rawSignStr.getBytes(StandardCharsets.UTF_8));
            String computedSign = Base64.getEncoder().encodeToString(computedSignBytes);
            if (!computedSign.equals(sign)) {
                throw new BusinessException("签名验证失败:数据被篡改");
            }
            if (Math.abs(System.currentTimeMillis() - timestamp) > 30 * 1000) {
                throw new BusinessException("签名验证失败:时间戳过期");
            }
            return true;
        } catch (Exception e) {
            throw new BusinessException("签名验证失败:" + e.getMessage());
        }
    }
}

5.1.2 ESP32专属MQTT指令下发与数据接收代码

// 1. ESP32专属MQTT生产者(指令下发)- MqttProducer.java(重载方法)
public void publishEsp32Message(Device device, String message) {
    long timestamp = System.currentTimeMillis();
    // 生成指令签名
    String sign = esp32AuthUtils.generateCommandSign(device, message, timestamp);
    // 封装最终指令消息(数据+时间戳+签名)
    String finalMessage = String.format("{\"data\":%s,\"timestamp\":%d,\"sign\":\"%s\"}",
            message, timestamp, sign);
    // 发布到ESP32订阅主题
    publish(device.getConnectAddr(), finalMessage);
}

// 2. ESP32专属MQTT消费者(数据接收与校验)- Esp32MqttConsumerConfig.java
@Bean
@ServiceActivator(inputChannel = "mqttEsp32InboundChannel")
public MessageHandler mqttEsp32InboundMessageHandler() {
    return message -> {
        try {
            String topic = (String) message.getHeaders().get("mqtt_receivedTopic");
            String payload = (String) message.getPayload();
            // 解析设备编码(主题格式:esp32/data/设备编码)
            String deviceCode = topic.split("/")[2];
            // 解析上报消息(data+timestamp+sign)
            Map<String, Object> messageMap = JsonUtils.parseObject(payload, new TypeReference<Map<String, Object>>() {});
            String dataContent = JsonUtils.toJSONString(messageMap.get("data"));
            long timestamp = (long) messageMap.get("timestamp");
            String sign = (String) messageMap.get("sign");
            // 签名与时间戳校验
            esp32AuthUtils.verifyDataSign(deviceCode, dataContent, timestamp, sign);
            // 同步设备状态并触发评估
            deviceStatusSyncService.syncDeviceStatus(deviceCode, dataContent);
        } catch (Exception e) {
            throw new BusinessException("ESP32数据处理失败:" + e.getMessage());
        }
    };
}

5.2 ESP32单片机核心交互代码

基于Arduino框架实现,核心包括MQTT连接、身份认证、指令解析、数据采集与上报。

#include <WiFi.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
#include <WiFiClientSecure.h>
#include <SHA256.h>
#include <Base64.h>

// 配置参数
const char* WIFI_SSID = "your_wifi_ssid";
const char* WIFI_PASSWORD = "your_wifi_password";
const char* MQTT_BROKER = "127.0.0.1";
const int MQTT_PORT = 1883;
const char* DEVICE_CODE = "ESP001"; // 设备编码
const char* DEVICE_SECRET = "esp32_secret_123"; // 设备密钥
// MQTT主题
const char* SUBSCRIBE_TOPIC = "esp32/command/ESP001"; // 订阅指令主题
const char* PUBLISH_TOPIC = "esp32/data/ESP001"; // 发布数据主题

WiFiClient espClient;
PubSubClient client(espClient);
long lastHeartbeatTime = 0;

// 传感器数据结构
struct SensorData {
  float temperature;
  float humidity;
  String switchStatus;
} sensorData;

void setup() {
  Serial.begin(115200);
  // 连接WiFi
  connectWiFi();
  // 配置MQTT
  client.setServer(MQTT_BROKER, MQTT_PORT);
  client.setCallback(callback);
  // 连接MQTT并认证
  connectMQTT();
  // 初始化传感器
  initSensor();
}

void loop() {
  if (!client.connected()) {
    reconnectMQTT(); // 连接断开重连
  }
  client.loop();
  // 定时发送心跳包(60秒)
  long currentTime = millis();
  if (currentTime - lastHeartbeatTime > 60000) {
    sendHeartbeat();
    lastHeartbeatTime = currentTime;
  }
  // 定时采集并上报数据(5秒)
  static long lastReportTime = 0;
  if (currentTime - lastReportTime > 5000) {
    collectSensorData();
    reportSensorData();
    lastReportTime = currentTime;
  }
}

// 连接WiFi
void connectWiFi() {
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("WiFi connected");
}

// 连接MQTT并进行身份认证
void connectMQTT() {
  while (!client.connected()) {
    String clientId = "ESP32-Client-" + String(random(0xffff), HEX);
    if (client.connect(clientId.c_str())) {
      Serial.println("MQTT connected");
      // 订阅指令主题
      client.subscribe(SUBSCRIBE_TOPIC);
      // 发送身份认证请求
      sendAuthRequest();
    } else {
      Serial.print("MQTT connect failed, rc=");
      Serial.print(client.state());
      delay(2000);
    }
  }
}

// 重连MQTT
void reconnectMQTT() {
  while (!client.connected()) {
    String clientId = "ESP32-Client-" + String(random(0xffff), HEX);
    if (client.connect(clientId.c_str())) {
      Serial.println("MQTT reconnected");
      client.subscribe(SUBSCRIBE_TOPIC);
      sendAuthRequest();
    } else {
      Serial.print("MQTT reconnect failed, rc=");
      Serial.print(client.state());
      delay(2000);
    }
  }
}

// 发送身份认证请求
void sendAuthRequest() {
  DynamicJsonDocument doc(256);
  doc["deviceCode"] = DEVICE_CODE;
  doc["deviceSecret"] = DEVICE_SECRET;
  doc["timestamp"] = millis();
  String authMsg;
  serializeJson(doc, authMsg);
  client.publish("esp32/auth", authMsg.c_str());
}

// 接收平台指令回调函数
void callback(char* topic, byte* payload, unsigned int length) {
  Serial.print("Received command: ");
  String cmdMsg;
  for (int i = 0; i < length; i++) {
    cmdMsg += (char)payload[i];
  }
  Serial.println(cmdMsg);
  // 解析指令消息
  DynamicJsonDocument doc(512);
  DeserializationError error = deserializeJson(doc, cmdMsg);
  if (error) {
    Serial.println("Parse command failed");
    return;
  }
  // 提取数据、时间戳、签名
  String dataContent = doc["data"].as<String>();
  long timestamp = doc["timestamp"].as<long>();
  String sign = doc["sign"].as<String>();
  // 校验时间戳与签名
  if (!verifySign(dataContent, timestamp, sign)) {
    Serial.println("Command sign verify failed");
    return;
  }
  // 解析并执行指令
  executeCommand(dataContent);
}

// 签名验证
bool verifySign(String content, long timestamp, String sign) {
  // 校验时间戳(±30秒)
  if (abs(millis() - timestamp) > 30000) {
    return false;
  }
  // 构造签名原串
  String rawStr = String(DEVICE_CODE) + content + String(timestamp);
  // 计算HMAC-SHA256签名
  String computedSign = hmacSha256(rawStr, DEVICE_SECRET);
  // 比对签名
  return computedSign.equals(sign);
}

// 计算HMAC-SHA256签名并Base64编码
String hmacSha256(String data, String key) {
  SHA256 sha256;
  byte hmacResult[SHA256_HASH_SIZE];
  sha256.HMAC(key.c_str(), key.length(), data.c_str(), data.length(), hmacResult, sizeof(hmacResult));
  // Base64编码
  return Base64.encode(hmacResult, sizeof(hmacResult));
}

// 执行平台指令
void executeCommand(String dataContent) {
  DynamicJsonDocument doc(256);
  DeserializationError error = deserializeJson(doc, dataContent);
  if (error) {
    Serial.println("Parse command content failed");
    return;
  }
  String action = doc["action"].as<String>();
  String param = doc["param"].as<String>();
  // 执行对应动作(如调节采样频率、开关设备)
  if (action == "ADJUST") {
    // 调节采样频率逻辑
    int freq = param.toInt();
    Serial.print("Adjust sample frequency to: ");
    Serial.println(freq);
  } else if (action == "OPEN") {
    sensorData.switchStatus = "ON";
    Serial.println("Device opened");
  } else if (action == "CLOSE") {
    sensorData.switchStatus = "OFF";
    Serial.println("Device closed");
  }
}

// 采集传感器数据
void collectSensorData() {
  // 模拟传感器数据采集(实际项目替换为真实传感器读取)
  sensorData.temperature = 25.5;
  sensorData.humidity = 60.2;
  sensorData.switchStatus = "ON";
}

// 上报传感器数据
void reportSensorData() {
  // 封装数据内容
  DynamicJsonDocument dataDoc(256);
  dataDoc["deviceCode"] = DEVICE_CODE;
  dataDoc["switchStatus"] = sensorData.switchStatus;
  dataDoc["temperature"] = sensorData.temperature;
  dataDoc["humidity"] = sensorData.humidity;
  dataDoc["timestamp"] = millis();
  String dataContent;
  serializeJson(dataDoc, dataContent);
  // 生成签名
  long timestamp = millis();
  String sign = hmacSha256(dataContent, DEVICE_SECRET);
  // 封装上报消息
  DynamicJsonDocument reportDoc(512);
  reportDoc["data"] = dataContent;
  reportDoc["timestamp"] = timestamp;
  reportDoc["sign"] = sign;
  String reportMsg;
  serializeJson(reportDoc, reportMsg);
  // 发布消息
  client.publish(PUBLISH_TOPIC, reportMsg.c_str());
  Serial.print("Report data: ");
  Serial.println(reportMsg);
}

// 发送心跳包
void sendHeartbeat() {
  DynamicJsonDocument doc(256);
  doc["deviceCode"] = DEVICE_CODE;
  doc["status"] = "ONLINE";
  doc["timestamp"] = millis();
  String heartbeatMsg;
  serializeJson(doc, heartbeatMsg);
  client.publish("esp32/heartbeat", heartbeatMsg.c_str());
}

六、交互优势与业务适配价值

6.1 核心优势

  • 低耦合架构:通过MQTT协议与标准化接口实现平台与设备解耦,便于后续设备型号扩展(如新增ESP32-CAM)与平台功能升级;

  • 高安全性:三重安全机制保障交互安全,防止非法接入与数据篡改,符合企业级业务安全要求;

  • 高可靠性:完善的重连、重试机制,确保极端场景下的交互稳定性;

  • 可追溯性:所有指令与数据均携带时间戳与设备编码,便于业务审计与问题排查。

6.2 业务适配价值

本交互方案精准适配Java后端智能控制业务平台的核心需求:

  • 数据支撑:为平台提供真实、有效的设备运行与环境数据,保障控制结果的准确性;

  • 远程管控:实现平台对ESP32设备的远程指令下发,支撑控制策略的动态调整;

  • 异常预警:通过设备状态上报与平台事件驱动机制,及时发现设备异常,降低业务风险;

  • 标准化适配:统一的交互协议与数据格式,便于平台对接多类型物联网设备,提升业务扩展性。

七、总结

ESP32单片机与Java后端业务平台的交互逻辑,核心是通过“MQTT协议为基础、安全认证为保障、标准化数据为载体”,实现“平台决策-设备执行-数据反馈-评估优化”的业务闭环。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Coder_Boy_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值