双门两路锁四摄-售货机硬件形态的软件建模

0 阅读7分钟

03-双门两路锁四摄-售货机硬件形态的软件建模

作者:黒漂技术佬|系列:URM Ultra 方案理念与架构篇

前两篇我们聊了全景和需求翻译。这一篇要把镜头推到最近——盯着那台柜子看。

新手写物联网系统最容易犯的错,是把设备当成一个"黑盒子 ID":建个表就存个 device_no 和 status,别的啥也不记。等真设备到了现场,运营问"这台柜子几扇门、几个摄像头、走什么网、固件啥版本",你傻眼了——表里没字段。

URM Ultra 的 Device 实体,就是把需求的硬件事实原封不动搬进数据库。今天我就拿这台"双门两路锁四摄 4G"的柜子,讲清楚怎么把一块铁疙瘩,变成一行软件记录。


一、先建立共识:什么是"硬件形态的软件建模"

说人话:现实里一台售货机有门、有锁、有摄像头、有网络模块、有固件。软件里它得"有对应字段",否则系统根本不知道自己管的是个啥。

我管这叫**"把硬件事实变成软件字段"**。三个原则:

  1. 设备是什么 → 用编号 + 型号 + 状态描述;
  2. 设备能怎样 → 用门数 / 锁数 / 摄像头数描述能力;
  3. 设备联网吗 → 用网络类型 + 固件版本 + 最后心跳描述可达性。

URM Ultra 的 Device 表(dms_device)就是这三条原则的产物。


二、Device 实体逐字段拆解

下面这段是项目源码里 Device 实体的关键字段(已加讲解注释):

// 项目源码:Device 实体(售货机硬件建模)
@Entity
@Table(name = "dms_device")
public class Device extends BaseEntity {
    @Column(unique = true)
    private String deviceNo;        // 设备编号,全局唯一,如 DEV-0001
    private String name;            // 设备别名,运营好认
    private String model;           // 机型,如"双门智能柜"
    private DeviceStatus status;    // 在线状态:ONLINE / OFFLINE / FAULT
    private int doorCount;          // 门数量 → 双门=2
    private int lockCount;          // 电控锁路数 → 2路锁=2
    private int cameraCount;        // 摄像头数量 → 4摄=4
    private String network;         // 网络类型,如"4G"
    private String location;        // 安装点位
    private String address;         // 详细地址
    private String firmwareVersion; // 固件版本,如 1.0.0
    private LocalDateTime lastHeartbeat; // 最后心跳时间
}

DeviceStatus 是个枚举,只有三种:

// 项目源码
public enum DeviceStatus { ONLINE, OFFLINE, FAULT }

我逐个字段翻译回"硬件语言",让你感受建模的对应关系:

字段类型硬件含义演示数据
deviceNoString(唯一)出厂序列号 / 资产编号DEV-0001
modelString机型双门智能柜
doorCountint柜门数2
lockCountint电控锁路数2
cameraCountint摄像头数4
networkString通信网络4G
firmwareVersionString固件版本1.0.0
status枚举在线/离线/故障默认 OFFLINE
lastHeartbeat时间最后报到时间心跳时刷新

三、为什么这些字段一个都不能少

很多新手会问:doorCount/lockCount 后台用得上吗?又不是要去远程开门。我给你逐条说它在真实业务里的用处:

doorCount / lockCount——能力边界 柜子有几扇门、每扇门几路锁,决定了后台能下发的"开锁指令"粒度。双门柜你可能要分别控制左门右门;如果 lockCount=2 但你代码写死开一把锁,那就是 Bug。建模时把"能力"记下来,控制逻辑才不会越界。

cameraCount——识别依据 纯视觉方案靠摄像头。4 摄意味着柜内 4 个角度都有画面,视觉算法才能拼出完整拿货过程。cameraCount 是后台判断是否"识别硬件齐备"的凭据。

network——排障线索 4G 柜子一旦交易失败,第一反应可能是"网络抖动"。network 字段让运营一眼看出"这是 4G 设备,信号可能不稳",而不是瞎猜。

firmwareVersion——灰度与回滚 固件升级是硬件系统的常态。记版本号,你才能知道"DEV-0003 还是 1.0.0,没升到 1.1.0,那个识别优化对它不生效"。这字段是运维的生命线。

status + lastHeartbeat——可达性 4G 设备会离线。光有 status 不够,还得有 lastHeartbeat:如果 status=ONLINE 但 lastHeartbeat 是三小时前,那它其实是"假在线"。后台靠这两个字段判断设备是否真能接任务。

一句话总结:字段不是摆设,每一个都对应一个真实运维动作。 建模偷懒,运维还债。


四、心跳:让"铁疙瘩"活过来

设备不会自己填 status。它靠心跳(heartbeat)告诉后台"我还活着"。URM Ultra 里,售货机设备端调用:

POST /device-api/vending/{deviceNo}/heartbeat

后台 DeviceService.heartbeat 的逻辑(项目源码简化):

// 项目源码:心跳置在线 + 刷新时间
public void heartbeat(String deviceNo) {
    Device device = findByDeviceNo(deviceNo);
    device.setStatus(DeviceStatus.ONLINE);     // 收到心跳 → 在线
    device.setLastHeartbeat(LocalDateTime.now());
    deviceRepository.save(device);
}

默认设备是 OFFLINE(刚灌数据还没连),一旦设备端开始心跳,就翻成 ONLINE。小程序里设备列表会按 status 染色:在线蓝、离线灰、故障红。这就是"把硬件可达性可视化"的最小实现。


五、货道:硬件之上的"销售单元"

光记"柜子长啥样"还不够。柜子里真正卖货的是货道(slot)——一格一格的格子,每格放一种商品。URM Ultra 用 DeviceProduct 这张表(dms_device_product)把"货道"和"商品"绑定:

// 项目源码:设备商品(货道绑定)
@Entity
@Table(name = "dms_device_product")
public class DeviceProduct {
    private Long deviceId;     // 属于哪台柜子
    private Long productId;    // 绑哪个商品
    private String slotNo;     // 货道号,如 A01 / B02
    private int capacity;      // 容量(这格最多放几个)
    private int stock;         // 当前库存
    private BigDecimal price;  // 货道售价(可覆盖商品原价)
    private int enabled;       // 1启用 0停用
}

演示数据里,一台双门柜的货道大概长这样:

货道号商品容量库存启用
A01可乐1081
A03橙汁1031
B01纸巾1011
B02巧克力1021

注意 slotNo 和 cameraCount 的关系:4 个摄像头要覆盖这些货道的画面,识别时才能定位"用户从 A03 拿了橙汁"。货道是视觉识别结果的落点,也是扣库存的落点——第 02 篇 deductStock(deviceId, slotNo, qty) 扣的就是这里的 stock。


六、低库存:从硬件事实引出补货动机

DeviceProduct.stock 持续下降,降到阈值以下,就该补货了。后台 DeviceService.listLowStock(threshold) 干这事:

// 项目源码:低库存过滤(默认阈值 5)
public List<DeviceProduct> listLowStock(int threshold) {
    return deviceProductRepository.findAll().stream()
        .filter(dp -> dp.getStock() != null && dp.getStock() <= threshold)
        .collect(Collectors.toList());
}

这里有个真实工程细节(新手必看):当前实现是把全表捞出来在内存里过滤,不是 WHERE stock <= ?。演示数据小无所谓;真上生产,几万台柜子时这行 findAll 会撑爆内存,必须改成 SQL 条件查询。我在代码注释里也标了这点——Demo 和生产的差距,往往就藏在这种"能跑但不够好"的地方。

低库存列表,正是第 04、05 篇"无人车 + 机械臂补货"的触发源。你看,一台柜子的硬件建模,一路牵出了整条补货链路。


七、建设备的后台契约

运营怎么把一台新柜子录进系统?AdminDeviceController 暴露了对设备的增删改查,核心路径:

动作路径说明
列表GET /admin-api/device/list全部柜子
新增POST /admin-api/device/录入一台柜子(含门数/锁数/摄数)
货道GET /admin-api/device/{id}/products看这台的货道
绑货道POST /admin-api/device/{id}/products把商品塞进某货道
改库存PUT /admin-api/device/{id}/products/{slotNo}/stock上货后回写库存
低库存GET /admin-api/device/low-stock阈值默认 5

这些接口把"硬件建模"从数据库一路通到了运营界面。你录入 doorCount=2、cameraCount=4,后台就真知道这是台双门四摄柜。


八、一张图收尾:硬件 → 字段 → 业务

现实柜子(双门/2锁/4摄/4G)
      │ 建模
      ▼
Device 实体(deviceNo/doorCount/lockCount/cameraCount/network/...)
      │ 衍生
      ├── status+lastHeartbeat ──▶ 心跳在线判定
      ├── DeviceProduct(slotNo/capacity/stock) ──▶ 货道销售 + 识别落点
      └── stock<=threshold ──▶ 低库存 ──▶ 触发无人车/机械臂补货

把硬件事实变成软件字段,听起来朴素,却是物联网系统的地基。地基打歪,上面跑的车、臂、订单全得塌。

下一篇,我们离开柜子,去看把货运过来的那位——无人车,以及它在三端里当的"枢纽"角色。