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

SpringBoot+Vue+MySQL 高校学科竞赛平台 完整可运行版(源码+文档) 本项目基于SpringBoot+Vue+MySQL构建高校学科竞赛管理平台,覆盖学生、教师、领队教师、管理员四大角色,实现竞赛发布、报名、刷题、评分、公示等全流程闭环管理。系统架构清晰,技术主流,配套完整源码、数据库脚本、万字论文、开题报告、答辩PPT及部署文档,支持一键运行,适用于毕业设计、课程设计及院系二次开发,真正实现“开箱即用”。 阅读详情

我们项目里采用的领域驱动设计的四层架构,包括领域服务层,它负责实现领域的业务逻辑;聚合领域用来封装相关的业务实体;还有基础设施层,承担数据存储等基础功能;以及应用层,负责协调各层交互,处理用户请求。 
简单说明下 有些同学可能接触这类项目
 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协议为基础、安全认证为保障、标准化数据为载体”,实现“平台决策-设备执行-数据反馈-评估优化”的业务闭环。

YT8531 PHY芯片硬件设计与MDIO/RGMII实战指南 以太网PHY芯片是FPGA系统实现网络通信的物理层核心,其关键在于标准接口(如MDIO管理总线和RGMII数据接口)的正确实现与协同。MDIO作为IEEE 802.3定义的低速配置通道,负责寄存器级控制与状态监控;RGMII则通过双沿采样精简引脚,在千兆速率下兼顾性能与PCB布线可行性。技术价值体现在电压兼容性(支持1.8V/2.5V/3.3V)、可编程时序调节(RX_DELAY/TX_DELAY)及多级环回测试能力,显著提升国产FPGA平台(如Xilinx Zynq)的工程鲁棒性。典型应用场景涵盖嵌入式网 阅读详情

相关推荐

达梦数据库与sharding-jdbc的深度适配实践

本文详细介绍了达梦数据库与sharding-jdbc的深度适配实践,包括驱动与依赖管理、核心适配实现、注册与配置等关键步骤。通过扩展shardingSphere的方式,成功实现了达梦数据库的分库分表功能,并提供了常见问题解决方案和性能优化建议,助力企业级应用高效处理海量数据。

weixin_30300225的博客 623

计算机毕业设计springboot基于单片机及spring框架的高血压患者居家监测系统 基于Spring Boot框架与单片机的高血压患者家庭健康监测系统 结合单片机技术的高血压患者居家健康管理平台

随着科技的飞速发展和互联网技术的广泛应用,医疗健康领域也在不断革新。高血压作为一种常见的慢性疾病,对患者的日常生活带来了诸多不便。传统的高血压监测方式往往依赖于医院的定期检查,这种方式不仅耗时耗力,而且难以实现对患者血压的实时监测。为了更好地满足高血压患者的居家监测需求,提高患者的自我管理能力和生活质量,基于Spring Boot框架和单片机技术的高血压患者居家监测系统应运而生。 系统功能概述 该系统通过整合Spring Boot框架的强大后端开发能力和单片机的硬件控制功能,为高血压患者提供了一个便捷、高效

毕设源码-张学姐 861

STM32F103多通道PWM输出避坑指南:TIM1_CH1异常输出的解决方案

本文深入解析STM32F103多通道PWM输出中的TIM1_CH1异常问题,提供从时钟配置到GPIO复用的系统化解决方案。通过黄金法则配置流程和高级调试技巧,帮助开发者快速定位并解决PWM输出不稳定、占空比异常等问题,同时拓展多定时器协同和互补PWM等高级应用场景。

weixin_29194817的博客 117

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

摘要 企业级物联网开发中,ESP32和STM32是主流单片机选型,分别适用于消费级和工业级场景。Java后端与设备交互应采用"边缘网关+MQTT"分层架构,通过标准化主题和JSON格式实现数据上传与指令下发。关键措施包括设备认证、TLS加密、QoS保障和异常处理。传统C51系列由于性能不足、缺乏通信能力和安全特性,无法满足企业级需求。相比之下,ESP32/STM32在运算性能、联网能力、外设资源和开发支持方面具有显著优势,更适合现代物联网应用开发。

Coder_Boy_的博客 821

【SAP HANA 46】关于HANA CPU高峰到 80-100%

关于HANA CPU高峰到 80-100%

探索 Java 编程的技巧、最佳实践和解决方案。本博客提供针对 Java 开发者的实用指南,涵盖从基础到高级话题,旨在提升编程效率和技能。 429

大疆RoboMaster嵌入式面试,电机FOC调不出来当场走人

摘要:大疆RoboMaster嵌入式工程师面试以实战能力著称,重点考察FreeRTOS任务调度、FOC电机控制和CAN总线三大核心技能。FreeRTOS部分涉及优先级调度、栈溢出排查及信号量与互斥锁的区别;FOC控制要求现场编写电流环代码,并解释Clarke/Park变换原理;CAN总线则关注仲裁机制、CANopen协议应用及错误帧排查方法。面试强调工程实践,需具备STM32开发经验,建议搭建实际环境进行练习。该岗位被誉为"硬件版修罗场",充分体现大疆对实际开发能力的严格要求。

郑伟强dev的专栏 150

【人工智能应用技术】-基础实战-小程序应用(基于springAI+百度语音技术)智能语音控制-Java部分核心逻辑

核心目标:定义与ESP32完全对齐的Java数据模型(DTO),确保JSON序列化/反序列化无偏差,这是双方交互的基础。

Coder_Boy_的博客 920

LDO的原理及测试方法

这是LM317芯片的核心,这个电路单元称为Bandgap Reference带隙基准源。属于模拟集成电路中的经典电路结构。常见的基本结构利用VBE的负温度系数,而VT是正温度系数,正负温度系数抵消就的得到稳定的基准参考电压了(三极管的方程VBE=VT*In(lC/IS))。

shiwei0813的博客 1157

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

本文专为ESP32新手设计,核心目标是让你快速搞定开发环境搭建,并写出第一个极简的Hello World程序(串口打印)。全程避开复杂术语,步骤清晰可复制,为后续开发ESP32与Java平台交互程序打下基础。

Coder_Boy_的博客 878

OpenClaw进阶实战(二十五):抖音企业号——私信回复与视频评论分析

本文介绍了抖音企业号自动化运营的技术方案,重点解决私信互动效率低和风控严格两大挑战。文章分析了抖音集成的三大技术壁垒(视频复杂性、风控体系、封闭API)并对比了三种技术路径的优缺点。针对不同规模的运营主体,推荐官方API+RPA混合架构或真机RPA+AI辅助的方案。 详细讲解了环境准备步骤,包括抖音Skill安装、Cookie获取和配置文件设置。核心实现部分展示了私信自动回复功能的安全合规实现,包含意图分类、优先级排序、延迟发送等防封禁机制,并提供了完整的Python代码示例。该方案通过模拟人工操作、限制日

PeterPan的博客 159

汽车电子RTOS--AUTOSAR OS

AUTOSAR OS(AUTomotive Open System ARchitecture Operating System)是AUTOSAR(汽车开放系统架构)标准中的一个重要组件,它是一个专门为汽车电子控制单元(ECU)设计的实时操作系统(RTOS)。AUTOSAR OS旨在提供一个标准化的软件平台,以促进不同供应商和汽车制造商之间的组件复用和协同开发,提高软件质量和降低成本。

MHD0815的博客 1798

InnoDB 内存结构总览:Change Buffer、AHI 与 Log Buffer

本文系统梳理InnoDB内存结构中的ChangeBuffer、Adaptive Hash Index(AHI)与LogBuffer。ChangeBuffer缓存二级索引页不在内存时的变更,减少随机IO,仅在页加载时合并;聚簇索引修改立即执行,因数据本身不可延后。AHI基于高频等值查询自动构建哈希索引,加速定位,仅支持等值操作。LogBuffer为redo log内存缓冲,配合磁盘日志实现崩溃恢复。三者均服务于性能优化,其中ChangeBuffer与AHI共享BufferPool空间,而LogBuffer独立

bksczm的博客 289

使用Python爬虫获取Facebook帖子和评论数据

Web爬虫是用于自动抓取网页数据的程序。它通过发送HTTP请求,获取网页的HTML源代码,再对源代码进行解析,提取所需的内容。爬虫常用于数据收集、信息提取、数据分析等应用。

2201_76125261的博客 1998

V8 机制、手写核心函数与 TypeScript 类型体操

或固定初始化(如数组先定长、对象先给全字段)保持类型与形状稳定;这些都比"换框架"更基础。只能处理纯数据(丢函数、Date、RegExp、循环引用报错)。生产级深拷贝要处理:原始值短路、循环引用、V8(Chrome / Node 的 JS 引擎)不是"解释一行跑一行"那么简单,它用。图:V8 执行流程——解释执行 + 热点优化,优化失败会 Deopt 回退。JS 内存自动管理,但"自动"不等于"不会漏"。是"固定间隔最多执行一次"。手写 Promise 是"懂异步"的试金石。是"停下后才执行最后一次",

qq_38468358的博客 346

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

深度学习工具包 DeepLearningToolbox

深度学习工具包Deprecation notice.-----This toolbox is outdated and no longer maintained.There are much better tools available for deep learning than this toolbox, e.g. [Theano](http://deeplearning.net/software/theano/), [torch](http://torch.ch/) or [tensorflow](http://www.tensorflow.org/)I would suggest you use one of the tools mentioned above rather than use this toolbox.Best, Rasmus.DeepLearnToolbox================A Matlab toolbox for Deep Learning.Deep Learning is a new subfield of machine learning that focuses on learning deep hierarchical models of data.It is inspired by the human brain's apparent deep (layered, hierarchical) architecture.A good overview of the theory of Deep Learning theory is[Learning Deep Architectures for AI](http://www.iro.umontreal.ca/~bengioy/papers/ftml_book.pdf)For a more informal introduction, see the following videos by Geoffrey Hinton and Andrew Ng.* [The Next Generation of Neural Networks](http://www.youtube.com/watch?v=AyzOUbkUf3M) (Hinton, 2007)* [Recent Developments in Deep Learning](http://www.youtube.com/watch?v=VdIURAu1-aU) (Hinton, 2010)* [Unsupervised Feature Learning and Deep Learning](http://www.youtube.com/watch?v=ZmNOAtZIgIk) (Ng, 2011)If you use this toolbox in your research please cite [Prediction as a candidate for learning deep hierarchical models of data](http://www2.imm.dtu.dk/pubdb/views/publication_details.php?id=6284)```@MASTERSTHESIS\{IMM2012-06284, author = "R. B. Palm", title = "Prediction as a candidate for learning deep hierarchical models of data", year = "2012",}```Contact: rasmusbergpalm at gmail dot comDirectories included in the toolbox-----------------------------------`NN/` - A library for Feedforward Backpropagation Neural Networks`CNN/` - A library for Convolutional Neural Networks`DBN/` - A library for Deep Belief Networks`SAE/` - A library for Stacked Auto-Encoders`CAE/` - A library for Convolutional Auto-Encoders`util/` - Utility functions used by the libraries`data/` - Data used by the examples`tests/` - unit tests to verify toolbox is workingFor references on each library check REFS.mdSetup-----1. Download.2. addpath(genpath('DeepLearnToolbox'));Example: Deep Belief Network---------------------```matlabfunction test_example_DBNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit RBM and visualize its weightsrand('state',0)dbn.sizes = [100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);figure; visualize(dbn.rbm{1}.W'); % Visualize the RBM weights%% ex2 train a 100-100 hidden unit DBN and use its weights to initialize a NNrand('state',0)%train dbndbn.sizes = [100 100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);%unfold dbn to nnnn = dbnunfoldtonn(dbn, 10);nn.activation_function = 'sigm';%train nnopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.10, 'Too big error');```Example: Stacked Auto-Encoders---------------------```matlabfunction test_example_SAEload mnist_uint8;train_x = double(train_x)/255;test_x = double(test_x)/255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit SDAE and use it to initialize a FFNN% Setup and train a stacked denoising autoencoder (SDAE)rand('state',0)sae = saesetup([784 100]);sae.ae{1}.activation_function = 'sigm';sae.ae{1}.learningRate = 1;sae.ae{1}.inputZeroMaskedFraction = 0.5;opts.numepochs = 1;opts.batchsize = 100;sae = saetrain(sae, train_x, opts);visualize(sae.ae{1}.W{1}(:,2:end)')% Use the SDAE to initialize a FFNNnn = nnsetup([784 100 10]);nn.activation_function = 'sigm';nn.learningRate = 1;nn.W{1} = sae.ae{1}.W{1};% Train the FFNNopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.16, 'Too big error');```Example: Convolutional Neural Nets---------------------```matlabfunction test_example_CNNload mnist_uint8;train_x = double(reshape(train_x',28,28,60000))/255;test_x = double(reshape(test_x',28,28,10000))/255;train_y = double(train_y');test_y = double(test_y');%% ex1 Train a 6c-2s-12c-2s Convolutional neural network %will run 1 epoch in about 200 second and get around 11% error. %With 100 epochs you'll get around 1.2% errorrand('state',0)cnn.layers = { struct('type', 'i') %input layer struct('type', 'c', 'outputmaps', 6, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %sub sampling layer struct('type', 'c', 'outputmaps', 12, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %subsampling layer};cnn = cnnsetup(cnn, train_x, train_y);opts.alpha = 1;opts.batchsize = 50;opts.numepochs = 1;cnn = cnntrain(cnn, train_x, train_y, opts);[er, bad] = cnntest(cnn, test_x, test_y);%plot mean squared errorfigure; plot(cnn.rL);assert(er<0.12, 'Too big error');```Example: Neural Networks---------------------```matlabfunction test_example_NNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);% normalize[train_x, mu, sigma] = zscore(train_x);test_x = normalize(test_x, mu, sigma);%% ex1 vanilla neural netrand('state',0)nn = nnsetup([784 100 10]);opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samples[nn, L] = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.08, 'Too big error');%% ex2 neural net with L2 weight decayrand('state',0)nn = nnsetup([784 100 10]);nn.weightPenaltyL2 = 1e-4; % L2 weight decayopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex3 neural net with dropoutrand('state',0)nn = nnsetup([784 100 10]);nn.dropoutFraction = 0.5; % Dropout fraction opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex4 neural net with sigmoid activation functionrand('state',0)nn = nnsetup([784 100 10]);nn.activation_function = 'sigm'; % Sigmoid activation functionnn.learningRate = 1; % Sigm require a lower learning rateopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex5 plotting functionalityrand('state',0)nn = nnsetup([784 20 10]);opts.numepochs = 5; % Number of full sweeps through datann.output = 'softmax'; % use softmax outputopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex6 neural net with sigmoid activation and plotting of validation and training error% split training data into training and validation datavx = train_x(1:10000,:);tx = train_x(10001:end,:);vy = train_y(1:10000,:);ty = train_y(10001:end,:);rand('state',0)nn = nnsetup([784 20 10]); nn.output = 'softmax'; % use softmax outputopts.numepochs = 5; % Number of full sweeps through dataopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, tx, ty, opts, vx, vy); % nntrain takes validation set as last two arguments (optionally)[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');```[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/rasmusbergpalm/deeplearntoolbox/trend.png)](https://bitdeli.com/free "Bitdeli Badge")

Spring Boot 数据源与 MyBatis(-Plus) 自定义配置详解

本文详解 Spring Boot 中单数据源环境下 HikariCP 与 MyBatis-Plus 的自定义配置。通过手动声明 SqlSessionFactory,实现下划线转驼峰、日志输出、一级缓存策略等精细化控制。结合 HikariCP 核心参数调优(如最大连接数、连接生命周期),确保高性能与稳定性。适用于仅需访问单一数据库的场景,避免多数据源复杂性,兼顾性能与可维护性。

BADAO_LIUMANG_QIZHI的博客 198

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

Java EE 进阶:8.MyBatis 操作数据库(入门)(第一弹)

Java EE 进阶:8.MyBatis 操作数据库(入门)(第一弹)

要努力去发光,而不是被照亮~ 130

【从零理解 Spring AI · 12】MCP 的两种角色:做客户端还是做服务端

上一篇讲了 MCP 的概念:标准协议、三大角色、M+N 替代 M×N。

nice_lcj520的博客 501

LeetCode Hot 100 二叉树专题(二)3 题(Java)

本篇文章共 3 题:543. 二叉树的直径、102. 二叉树的层序遍历、108. 将有序数组转换为二叉搜索树。 每道题都统一按「① 题目分析 & 解题思路 → ② 常见解法(附代码)及讲解 → ③ 知识点总结」的顺序组织;代码使用 Java 编写,节点定义沿用 LeetCode 默认的 TreeNode。

2401_88612756的博客 365

JDK 下载安装:装完 Java 提示「不是内部或外部命令」?环境变量没配

JDK 是写 Java 程序必须装的东西。它包含运行 Java 的虚拟机(JRE)和编译 Java 的编译器(javac)——装了它,能跑、.java文件能编译成可执行程序。免费,学习和小规模生产都不花钱。

科小怪的博客 635

java框架大全

java框架大全

qq_39326137的博客 183

Java 多态 学习笔记

多态是面向对象编程的重要特性,指同一操作作用于不同对象时产生不同行为。其核心在于“父类引用指向子类对象”,结合方法重写实现运行时动态绑定。多态使代码更灵活、可扩展,如用Person p = new Student();统一处理各类人员注册,无需修改调用逻辑。但多态无法直接调用子类特有方法,需通过instanceof判断并强制转换。编译时看左边(类型),运行时看右边(实际对象),实现解耦与维护便利。

AA_AA114514的博客 361

SecureRandom做随机源,这个Java随机字符串工具类有3个方法

本文分享一款JDK17环境下的Java随机字符串工具类,内置3个开箱即用的方法,全部采用SecureRandom加密安全随机源,不存在弱随机、可预测、取模偏差等问题。工具类支持默认62字符集随机串、纯数字随机串、自定义字符集随机串三种生成模式,支持Unicode码点级别的字符集处理,兼容Emoji等增补字符,边界处理严格,异常信息明确,适合验证码、邀请码、盐值、随机口令、短链码等场景,源码可直接复制使用。

Java后端 | AI智能体应用实战派 | 电脑技能行家 299

IDEA 简介:从零认识 IntelliJ IDEA

IDEA 全称,是由 JetBrains 公司开发的一款主流 Java 集成开发环境(IDE)。它集代码编辑、编译、调试、测试、版本控制、构建管理等功能于一体,被大量 Java 开发者视为日常开发的首选工具。所谓 IDE(Integrated Development Environment,集成开发环境),就是把写代码所需的各类工具整合到同一个界面中。相比只用文本编辑器加命令行编译,IDE 能显著减少重复操作,让开发者把精力集中在业务逻辑上。

ztI10的博客 152

Java实现批量线段相交检测(跨积判定+扫描线优化与踩坑复盘)

【代码】Java实现批量线段相交检测(跨积判定+扫描线优化与踩坑复盘)

m0_64476639的博客 329

TRingBuffer两端都能增删(快递站版)

TRingBuffer 是一个支持两端增删、下标访问且容量动态增长的环形双端队列。它通过无符号索引的永不归零设计,利用减法自动模运算实现空满判断与元素计数,避免了传统环形缓冲浪费格子的问题。内部以票号(索引)标记首尾,扩容时重排元素为连续存储,保证 O(1) 操作与高缓存效率。虽不线程安全,但其高效性使其成为游戏开发中事件队列、命令缓冲等场景的理想选择。

u013768914的博客 228

ubuntu安装hadoop系统

本文详细介绍了基于VMware克隆虚拟机搭建三节点Hadoop分布式集群的全过程:使用同一Ubuntu镜像克隆出master、worker1、worker2三台虚拟机,依次配置主机名、固定IP(如192.168.82.10/11/12)、SSH服务及免密登录,确保节点间网络互通。随后安装Java,下载配置Hadoop并分发至各节点,最终初始化启动集群,验证其存储与计算功能,实现高效的大数据环境部署。

qq_43592674的博客 336

Mac上装了最新版本的Android Studio,但是没找到jdk的设置选项也没找到jbr的目录

新版Android Studio(2025.3.1+)因三项变更导致常见问题:一、JDK设置移至“DaemonJVM criteria”,新项目默认自动探测或下载JDK;二、JBR目录改名为带版本号的jbr-21等,需进入.app/Contents/查看,或通过About窗口确认运行时;三、命令行缺adb因platform-tools未加入PATH,需在SDK Manager中勾选并配置~/.zshrc中的ANDROID_HOME及PATH。重装或清理残留可解决缺失问题,切勿手动设JAVA_HOME或安装冲

Programming is a highly artistic endeavor 356
上一篇: 【人工智能应用技术】-基础实战-小程序应用(基于springAI+百度语音技术)智能语音控制-单片机交互
下一篇: 【人工智能应用技术】-基础实战-小程序应用(基于springAI+百度语音技术)智能语音控制-单片机环境搭建
Coder_Boy_
博客等级 码龄10年 2804粉丝 819原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

打赏作者

Coder_Boy_

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

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

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

打赏作者

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

抵扣说明:

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

余额充值