无人售货柜设备消息体系设计:心跳上报、故障告警、状态同步

售货柜设备消息体系设计:心跳上报、故障告警、状态同步

作者:黒漂技术佬 适用读者:有RocketMQ基础,想设计一套IoT设备消息体系的同学

一、无人售货柜设备消息全景

一台无人售货柜,本质上是一个IoT设备。它需要跟服务端持续通信:报状态、告故障、收指令。这些通信全走RocketMQ。

消息分两个方向:

复制代码
设备 → 服务端(上行)              服务端 → 设备(下行)
┌─────────────┐                  ┌──────────────────┐
│ 心跳上报     │                  │ 远程开门          │
│ 状态变更     │                  │ 关门指令          │
│ 故障告警     │                  │ 重启指令          │
│ 交易上报     │                  │ 配置更新          │
└─────────────┘                  └──────────────────┘

上行 :设备主动发,服务端消费。 下行:服务端发,设备消费。设备作为RocketMQ消费者常驻运行。


二、Topic和Tag设计

IoT设备消息量大、种类多,Topic和Tag的设计直接决定系统的可维护性和性能。

2.1 设计原则

  • Topic粒度大:一个业务域一个Topic,不要一个消息类型一个Topic
  • Tag粒度细:同Topic下用Tag区分不同事件
  • 上下行分离:上行和下行用不同Topic,避免互相干扰

2.2 完整Topic/Tag规划

yaml 复制代码
设备上行 Topic: device_upward
├── Tag: heartbeat       心跳上报
├── Tag: status           状态变更
├── Tag: alert           故障告警
└── Tag: transaction     交易上报

设备下行 Topic: device_downward
├── Tag: open_door       远程开门
├── Tag: close_door      关门指令
├── Tag: reboot          重启设备
└── Tag: config_update   配置更新

2.3 为什么不用一个Topic搞定?

有人问:为什么不全部用 device_topic,全部用Tag区分上下行?

原因有三:

  1. 消费隔离:上行消费者(服务端)和下行消费者(设备端)消费模式完全不同,混在一个Topic容易互相影响
  2. 权限控制:上行Topic只允许设备写,下行Topic只允许服务端写,便于权限管理
  3. 扩容独立:上行消息量大(1000台设备每5秒一次心跳),下行消息量小(偶尔发指令),两个Topic可以独立配置队列数和存储策略

三、心跳消息设计

心跳是设备消息体系的基础------服务端通过心跳判断设备是否在线。

3.1 消息体结构

java 复制代码
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class HeartbeatMessage implements Serializable {
    private String deviceId;        // 设备唯一标识
    private Long timestamp;         // 上报时间戳
    private Double temperature;     // 柜内温度(℃)
    private Double voltage;         // 电池电压(V)
    private Integer signalStrength; // 网络信号强度
    private String networkType;     // 网络类型(4G/5G/WiFi)
    private Integer cpuUsage;       // CPU使用率(%)
    private Integer memoryUsage;    // 内存使用率(%)
    private String softwareVersion; // 固件版本号
}

3.2 设备端发送心跳

设备端定时发送,通常5-10秒一次:

java 复制代码
@Component
public class HeartbeatSender {

    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    @Value("${device.id}")
    private String deviceId;

    /**
     * 每5秒发送一次心跳(用Spring定时任务)
     */
    @Scheduled(fixedRate = 5000)
    public void sendHeartbeat() {
        HeartbeatMessage heartbeat = HeartbeatMessage.builder()
                .deviceId(deviceId)
                .timestamp(System.currentTimeMillis())
                .temperature(readTemperature())
                .voltage(readVoltage())
                .signalStrength(readSignal())
                .networkType(getNetworkType())
                .cpuUsage(readCpuUsage())
                .memoryUsage(readMemoryUsage())
                .softwareVersion(getSoftwareVersion())
                .build();

        // 心跳用单向发送,不需要确认结果
        rocketMQTemplate.sendOneWay("device_upward:heartbeat", heartbeat);
    }

    // 以下方法读取设备硬件传感器数据(省略具体实现)
    private Double readTemperature() { return 4.5; }
    private Double readVoltage() { return 12.3; }
    private Integer readSignal() { return 85; }
    private String getNetworkType() { return "4G"; }
    private Integer readCpuUsage() { return 23; }
    private Integer readMemoryUsage() { return 45; }
    private String getSoftwareVersion() { return "v2.1.3"; }
}

3.3 服务端消费心跳

服务端消费心跳做两件事:更新Redis在线状态 + 写InfluxDB时序数据。

java 复制代码
@Slf4j
@Component
@RocketMQMessageListener(
    topic = "device_upward",
    selectorExpression = "heartbeat",
    consumerGroup = "heartbeat-consumer-group",
    messageModel = MessageModel.CLUSTERING
)
public class HeartbeatConsumer implements RocketMQListener<HeartbeatMessage> {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Autowired
    private InfluxDBMapper influxDBMapper;

    @Override
    public void onMessage(HeartbeatMessage heartbeat) {
        String deviceId = heartbeat.getDeviceId();

        // 1. 更新Redis设备在线状态(key设30秒过期,心跳超时自动过期)
        String key = "device:online:" + deviceId;
        redisTemplate.opsForValue().set(
            key,
            JSON.toJSONString(heartbeat),
            30, TimeUnit.SECONDS
        );

        // 2. 写入InfluxDB时序数据库(用于历史趋势查询)
        influxDBMapper.writeHeartbeat(heartbeat);

        // 3. 温度异常检测
        if (heartbeat.getTemperature() != null
                && heartbeat.getTemperature() > 10.0) {
            log.warn("设备温度异常: deviceId={}, temp={}",
                     deviceId, heartbeat.getTemperature());
            // 触发告警流程...
        }
    }
}

3.4 心跳超时检测:延时消息

Redis key设了30秒过期,但光过期不够------要主动检测超时设备。用RocketMQ的延时消息实现:

java 复制代码
/**
 * 发送延时检测消息
 * 每台设备发心跳后,同时发一条30秒后到达的延时消息
 * 延时消息到达时检查设备是否还在线
 */
public void sendHeartbeatCheck(String deviceId) {
    Message<String> msg = MessageBuilder
            .withPayload(deviceId)
            .build();

    // delayLevel=3 对应10秒延时(RocketMQ默认18个延时级别)
    // 级别对应: 1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h
    rocketMQTemplate.syncSend(
        "device_check_topic:heartbeat_timeout",
        msg,
        3000,     // sendTimeout
        3         // delayLevel=3 → 10秒后消费者收到
    );
}

延时消息消费者:

java 复制代码
@Slf4j
@Component
@RocketMQMessageListener(
    topic = "device_check_topic",
    selectorExpression = "heartbeat_timeout",
    consumerGroup = "heartbeat-check-consumer-group"
)
public class HeartbeatTimeoutConsumer implements RocketMQListener<String> {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Autowired
    private AlertService alertService;

    @Override
    public void onMessage(String deviceId) {
        String key = "device:online:" + deviceId;

        // 检查Redis中设备是否还在线
        Boolean exists = redisTemplate.hasKey(key);

        if (Boolean.FALSE.equals(exists)) {
            // Redis key已过期,说明设备心跳超时了
            log.warn("设备心跳超时: deviceId={}", deviceId);
            alertService.sendOfflineAlert(deviceId);
        }
        // 如果key存在,说明设备在30秒内又发了一次心跳,正常
    }
}

四、故障告警消息

设备出故障时要及时上报,服务端根据告警级别走不同的处理链路。

4.1 告警消息体

java 复制代码
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AlertMessage implements Serializable {
    private String deviceId;       // 设备ID
    private String alertId;        // 告警唯一ID
    private String alertLevel;      // 告警级别: INFO/WARN/ERROR/CRITICAL
    private String alertType;      // 告警类型: temp_high/voltage_low/network_offline/motor_fault
    private String description;    // 告警描述
    private Long timestamp;        // 告警时间
    private Map<String, String> extra; // 扩展字段
}

4.2 告警级别与处理链路

markdown 复制代码
告警消息到达
    │
    ▼
┌──────────────────────┐
│ 告警消费者            │
│ 1. 存入告警数据库     │
│ 2. 按级别分支处理     │
└──────┬───────────────┘
       │
  ┌────┼────────────┬────────────────┐
  │    │            │                │
  ▼    ▼            ▼                ▼
INFO  WARN        ERROR          CRITICAL
记日志  存库+通知   存库+推送通知   存库+电话告警
                   (企业微信/钉钉)   + 立即创建工单

4.3 告警消费者实现

java 复制代码
@Slf4j
@Component
@RocketMQMessageListener(
    topic = "device_upward",
    selectorExpression = "alert",
    consumerGroup = "alert-consumer-group",
    messageModel = MessageModel.CLUSTERING
)
public class AlertConsumer implements RocketMQListener<AlertMessage> {

    @Autowired
    private AlertMapper alertMapper;

    @Autowired
    private NotificationService notificationService;

    @Autowired
    private WorkOrderService workOrderService;

    @Override
    public void onMessage(AlertMessage alert) {
        log.warn("收到设备告警: deviceId={}, level={}, type={}",
                 alert.getDeviceId(), alert.getAlertLevel(), alert.getAlertType());

        // 1. 存库(所有级别都存)
        alertMapper.insert(alert);

        // 2. 按级别处理
        switch (alert.getAlertLevel()) {
            case "INFO":
                // INFO级别只记录,不通知
                break;

            case "WARN":
                // WARN级别推送通知
                notificationService.sendNotification(
                    alert.getDeviceId(),
                    "设备告警: " + alert.getDescription(),
                    NotificationChannel.WECHAT
                );
                break;

            case "ERROR":
                // ERROR级别推送+创建工单
                notificationService.sendNotification(
                    alert.getDeviceId(),
                    "设备故障: " + alert.getDescription(),
                    NotificationChannel.WECHAT,
                    NotificationChannel.DINGTALK
                );
                workOrderService.createWorkOrder(alert);
                break;

            case "CRITICAL":
                // CRITICAL级别:电话告警+立即创建紧急工单
                notificationService.sendUrgentNotification(alert);
                workOrderService.createUrgentWorkOrder(alert);
                break;

            default:
                log.warn("未知告警级别: {}", alert.getAlertLevel());
        }
    }
}

五、状态同步消息

售货柜有明确的状态机,状态变更需要保证顺序消费。

5.1 设备状态机

markdown 复制代码
        扫码开门
  IDLE ──────────→ OPEN
                       │
                  检测到开门完成
                       │
                       ▼
                   SHOPPING
                       │
                  检测到关门
                       │
                       ▼
                   CLOSING
                       │
                  计算完成扣款
                       │
                       ▼
                 TRANSACTION
                       │
                  事务完成
                       │
                       ▼
                   IDLE (回到空闲)

5.2 状态变更消息与顺序消费

一台设备的状态必须按顺序处理:不能先处理"SHOPPING"再处理"OPEN",逻辑会乱。用RocketMQ的顺序消息保证:同一台设备的状态消息按发送顺序消费。

java 复制代码
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class DeviceStatusMessage implements Serializable {
    private String deviceId;       // 设备ID
    private String fromStatus;     // 原状态
    private String toStatus;       // 目标状态
    private Long timestamp;        // 变更时间
    private String trigger;        // 触发原因(user_scan/close_detect/payment_done)
    private Map<String, Object> extra; // 扩展数据
}

生产者发送顺序消息------关键在 hashKeydeviceId

java 复制代码
@Service
public class DeviceStatusProducer {

    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    public void sendStatusChange(DeviceStatusMessage message) {
        // syncSendOrderly:发送顺序消息
        // 第二个参数 hashKey = deviceId,保证同一设备的状态消息路由到同一队列
        rocketMQTemplate.syncSendOrderly(
            "device_upward:status",
            MessageBuilder.withPayload(message).build(),
            message.getDeviceId()  // hashKey → 同一设备进同一队列
        );
    }
}

消费者使用顺序消费模式:

java 复制代码
@Slf4j
@Component
@RocketMQMessageListener(
    topic = "device_upward",
    selectorExpression = "status",
    consumerGroup = "device-status-consumer-group",
    messageModel = MessageModel.CLUSTERING,
    consumeMode = ConsumeMode.ORDERLY  // ← 顺序消费
)
public class DeviceStatusConsumer
        implements RocketMQListener<DeviceStatusMessage> {

    @Override
    public void onMessage(DeviceStatusMessage message) {
        log.info("设备状态变更: deviceId={}, {} → {}, trigger={}",
                 message.getDeviceId(),
                 message.getFromStatus(),
                 message.getToStatus(),
                 message.getTrigger());

        // 更新设备状态缓存
        updateDeviceStatus(message.getDeviceId(), message.getToStatus());

        // 根据目标状态执行对应逻辑
        switch (message.getToStatus()) {
            case "OPEN":
                // 设备开门了,启动购物计时
                startShoppingTimer(message.getDeviceId());
                break;
            case "SHOPPING":
                // 用户在购物中
                break;
            case "CLOSING":
                // 检测到关门,开始计算扣款
                triggerPaymentCalculation(message);
                break;
            case "TRANSACTION":
                // 交易完成
                completeTransaction(message);
                break;
            case "IDLE":
                // 回到空闲,释放设备锁
                releaseDeviceLock(message.getDeviceId());
                break;
        }
    }
}

小白疑问:ConsumeMode.ORDERLY 和 CONCURRENTLY 有什么区别?

  • CONCURRENTLY:多线程并发消费,不保证顺序,速度快
  • ORDERLY:单线程按队列顺序消费,保证同一队列内消息按序处理,速度慢

用ORDERLY时,hashKey选什么,就保证什么维度有序。选deviceId,就是同一设备的状态消息有序。


六、下行指令消息

服务端需要向设备下发控制指令:远程开门、重启、更新配置等。

6.1 指令消息体

java 复制代码
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CommandMessage implements Serializable {
    private String commandId;       // 指令唯一ID
    private String deviceId;        // 目标设备ID
    private String commandType;     // 指令类型: open_door/close_door/reboot/config_update
    private Map<String, Object> params; // 指令参数
    private Long timestamp;         // 下发时间
    private Integer timeout;        // 超时时间(秒),超时未确认则重发
}

6.2 指令下发与ACK确认机制

scss 复制代码
服务端                    RocketMQ                    设备
   │                         │                         │
   │ ① 发送指令消息            │                         │
   │────────────────────────→│                         │
   │                         │ ② 设备消费指令             │
   │                         │────────────────────────→│
   │                         │                         │ ③ 执行指令
   │                         │ ④ 设备回ACK               │
   │                         │←────────────────────────│
   │                         │                         │
   │ ⑤ 发送延时检查消息(30秒后) │                         │
   │────────────────────────→│                         │
   │                         │                         │
   │          (30秒后)        │ ⑥ 延时消息到达             │
   │←────────────────────────│                         │
   │ ⑦ 检查ACK是否收到         │                         │
   │   收到 → 指令完成          │                         │
   │   没收到 → 重发指令        │                         │

6.3 服务端发送指令

java 复制代码
@Service
public class DeviceCommandService {

    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    /**
     * 远程开门
     */
    public void sendOpenDoorCommand(String deviceId, String userId) {
        String commandId = UUID.randomUUID().toString();

        CommandMessage command = CommandMessage.builder()
                .commandId(commandId)
                .deviceId(deviceId)
                .commandType("open_door")
                .params(Map.of("userId", userId, "maxDuration", 120))
                .timestamp(System.currentTimeMillis())
                .timeout(30)
                .build();

        // 发送指令到下行Topic,用deviceId做hashKey保证顺序
        rocketMQTemplate.syncSendOrderly(
            "device_downward:open_door",
            MessageBuilder.withPayload(command).build(),
            deviceId
        );

        // 记录指令待确认状态到Redis
        redisTemplate.opsForValue().set(
            "device:command:pending:" + commandId,
            JSON.toJSONString(command),
            60, TimeUnit.SECONDS
        );

        // 发送延时检查消息(30秒后检查ACK)
        sendAckCheck(commandId, deviceId, 30);
    }

    private void sendAckCheck(String commandId, String deviceId, int delaySeconds) {
        // delayLevel映射: 1s=1, 5s=2, 10s=3, 30s=4
        int delayLevel = delaySeconds <= 1 ? 1
                       : delaySeconds <= 5 ? 2
                       : delaySeconds <= 10 ? 3 : 4;

        rocketMQTemplate.syncSend(
            "device_check_topic:command_ack_timeout",
            MessageBuilder.withPayload(
                commandId + ":" + deviceId
            ).build(),
            3000,
            delayLevel
        );
    }
}

6.4 设备端消费指令 + 回ACK

设备端作为RocketMQ消费者常驻运行:

java 复制代码
@Component
@RocketMQMessageListener(
    topic = "device_downward",
    selectorExpression = "open_door || close_door || reboot || config_update",
    consumerGroup = "${device.id}-command-consumer-group",
    messageModel = MessageModel.CLUSTERING,
    consumeMode = ConsumeMode.ORDERLY
)
public class DeviceCommandConsumer
        implements RocketMQListener<CommandMessage> {

    @Value("${device.id}")
    private String deviceId;

    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    @Autowired
    private HardwareController hardwareController;

    @Override
    public void onMessage(CommandMessage command) {
        log.info("收到指令: commandId={}, type={}",
                 command.getCommandId(), command.getCommandType());

        try {
            // 执行指令
            switch (command.getCommandType()) {
                case "open_door":
                    hardwareController.openDoor();
                    break;
                case "close_door":
                    hardwareController.closeDoor();
                    break;
                case "reboot":
                    hardwareController.scheduleReboot();
                    break;
                case "config_update":
                    hardwareController.updateConfig(command.getParams());
                    break;
            }

            // 回ACK:发送到上行Topic
            sendAck(command.getCommandId(), "SUCCESS", null);

        } catch (Exception e) {
            log.error("指令执行失败: commandId={}", command.getCommandId(), e);
            sendAck(command.getCommandId(), "FAILED", e.getMessage());
        }
    }

    private void sendAck(String commandId, String status, String errorMsg) {
        Map<String, Object> ack = new HashMap<>();
        ack.put("commandId", commandId);
        ack.put("deviceId", deviceId);
        ack.put("status", status);
        ack.put("timestamp", System.currentTimeMillis());
        if (errorMsg != null) {
            ack.put("error", errorMsg);
        }

        rocketMQTemplate.syncSend("device_upward:command_ack", ack);
    }
}

6.5 ACK超时检查消费者

java 复制代码
@Slf4j
@Component
@RocketMQMessageListener(
    topic = "device_check_topic",
    selectorExpression = "command_ack_timeout",
    consumerGroup = "command-ack-check-consumer-group"
)
public class CommandAckCheckConsumer implements RocketMQListener<String> {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Autowired
    private DeviceCommandService commandService;

    @Override
    public void onMessage(String payload) {
        // payload格式: commandId:deviceId
        String[] parts = payload.split(":");
        String commandId = parts[0];
        String deviceId = parts[1];

        // 检查Redis中ACK是否已收到
        String ackKey = "device:command:ack:" + commandId;
        Boolean ackReceived = redisTemplate.hasKey(ackKey);

        if (Boolean.FALSE.equals(ackReceived)) {
            log.warn("指令ACK超时未收到,重发: commandId={}", commandId);

            // 重发指令(重试次数限制)
            String pendingJson = redisTemplate.opsForValue()
                    .get("device:command:pending:" + commandId);
            if (pendingJson != null) {
                CommandMessage originalCmd = JSON.parseObject(
                    pendingJson, CommandMessage.class);
                commandService.resendCommand(originalCmd);
            }
        }
    }
}

七、完整消息体系架构

把所有组件拼起来,售货柜设备消息体系全景如下:

scss 复制代码
┌─────────────────────────────────────────────────────────────┐
│                      无人售货柜设备                           │
│                                                             │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐  │
│  │心跳发送器 │  │告警发送器 │  │状态发送器 │  │指令消费者 │  │
│  │(5秒/次)  │  │(事件触发) │  │(状态变更) │  │(常驻监听) │  │
│  └─────┬────┘  └─────┬────┘  └─────┬────┘  └────▲─────┘  │
│        │             │             │              │         │
└────────┼─────────────┼─────────────┼──────────────┼─────────┘
         │             │             │              │
    ═════╪═════════════╪═════════════╪══════════════╪════════
    RocketMQ
         │             │             │              │
    ═════╪═════════════╪═════════════╪══════════════╪════════
         │             │             │              │
         ▼             ▼             ▼              │
┌─────────────────────────────────────┐             │
│           服务端                     │             │
│                                     │             │
│  ┌──────────┐  ┌──────────┐         │             │
│  │心跳消费者 │  │告警消费者 │         │  ┌──────────┐│
│  │→Redis    │  │→存库     │         │  │指令发送器 ││
│  │→InfluxDB │  │→通知推送  │         │  │(远程开门等)│
│  │→超时检测  │  │→创建工单  │         │  └────┬─────┘│
│  └──────────┘  └──────────┘         │       │      │
│                                     │       │      │
│  ┌──────────────────────┐           │       │      │
│  │状态消费者(顺序消费)   │           │       │      │
│  │→状态机流转            │          │       │      │
│  │→触发业务逻辑          │           │       │      │
│  └──────────────────────┘           │       │      │
│                                     │       │      │
│  ┌──────────────────────┐           │       │      │
│  │ACK超时检查消费者       │          │       │      │
│  │→检查指令是否确认        │          │       │      │
│  │→超时重发              │           │       │      │
│  └──────────────────────┘           │       │      │
└─────────────────────────────────────┘       │      │
                                              │      │
                          device_downward ◀───┘      │
                          (服务端→设备)              │
                                                     │
                          device_upward ─────────────┘
                          (设备→服务端)

八、消息量评估

量级测算在架构设计阶段就要做,否则上线后容量不足就尴尬了。

8.1 基础数据

假设1000台售货柜,每台5秒发一次心跳:

yaml 复制代码
心跳消息量 = 1000台 / 5秒 = 200条/秒
消息类型 频率 日消息量 备注
心跳上报 200条/秒 ~1700万条/天 量最大,用异步刷盘
状态变更 10条/小时/台 ~24万条/天 中等
故障告警 0.1条/小时/台 ~2400条/天 量小但重要
交易上报 5条/小时/台 ~12万条/天 中等
下行指令 2条/小时/台 ~5万条/天 量小
ACK确认 2条/小时/台 ~5万条/天 与下行指令对应

8.2 Broker容量规划

以心跳为例,单条心跳消息约200字节:

复制代码
每秒写入量 = 200条 × 200字节 = 40KB/秒
每天写入量 = 40KB × 86400秒 ≈ 3.3GB/天

1000台设备每天约3.3GB心跳数据,加上其他消息,日总量约5GB。Broker存储建议保留7天,单节点35GB足够。考虑到主从复制和刷盘开销,用SSD硬盘。

8.3 消费者并发度

心跳消费者200条/秒,单线程消费可能跟不上。通过增加consumerGroup下的消费者实例来提升并发:

scss 复制代码
200条/秒 / 单消费者处理能力(约50条/秒) = 4个消费者实例

部署4个心跳消费者实例,配合RocketMQ的CLUSTERING模式自动负载均衡。


九、总结

售货柜设备消息体系设计的核心套路:

设计点 方案
Topic/Tag 上下行分离,按业务域分Topic,按事件分Tag
心跳检测 设备单向发送心跳 + Redis过期 + 延时消息超时检测
故障告警 按级别(INFO/WARN/ERROR/CRITICAL)分级处理
状态同步 ShardingKey=deviceId + 顺序消费保证状态机正确流转
下行指令 指令下发 + 设备回ACK + 延时消息检查ACK超时重发
消息量 1000台设备≈200条/秒心跳,Broker用SSD+异步刷盘

一句话总结:上行消息按重要程度选发送方式,下行指令靠ACK+延时检查保可靠,状态变更靠顺序消费保正确

相关推荐
阿拉斯攀登1 小时前
无人售货机订单业务MQ架构:创建订单、支付回调、出货完成、订单归档
架构
微三云 - 廖会灵 (私域系统开发)1 小时前
抖店 OPC(Operational Process Control)自动化运营系统:架构、痛点与服务商商业体系分析
运维·架构·自动化
jianqiang.xue1 小时前
收官:从v0.1到“研发新常态“,一套嵌入式智能体的落地与边界
stm32·单片机·物联网·架构·esp32
阿拉斯攀登2 小时前
SpringBoot整合RocketMQ:生产者、消费者快速搭建
架构
楚兴3 小时前
ACP 到底解决了什么?让 IDE 和 Coding Agent 解耦
人工智能·后端·架构
Rescenix3 小时前
流式瀑布渐变闪烁根治-硬核技术版
架构·github
楚兴3 小时前
DeepSeek Harness 到底在做什么?拆开 Agent 的运行时
人工智能·后端·架构
贺公子之数据科学与艺术3 小时前
向量数据库实操:Milvus 与 Chroma 的 Collection、索引、相似度度量与标量过滤
架构
用户6919026813393 小时前
Agent 记忆处理机制详解:从变量数据类型到内存模型
javascript·架构