03-双门两路锁四摄-售货机硬件形态的软件建模
作者:黒漂技术佬|系列:URM Ultra 方案理念与架构篇
前两篇我们聊了全景和需求翻译。这一篇要把镜头推到最近------盯着那台柜子看。
新手写物联网系统最容易犯的错,是把设备当成一个"黑盒子 ID":建个表就存个 device_no 和 status,别的啥也不记。等真设备到了现场,运营问"这台柜子几扇门、几个摄像头、走什么网、固件啥版本",你傻眼了------表里没字段。
URM Ultra 的 Device 实体,就是把需求的硬件事实 原封不动搬进数据库。今天我就拿这台"双门两路锁四摄 4G"的柜子,讲清楚怎么把一块铁疙瘩,变成一行软件记录。
一、先建立共识:什么是"硬件形态的软件建模"
说人话:现实里一台售货机有门、有锁、有摄像头、有网络模块、有固件。软件里它得"有对应字段",否则系统根本不知道自己管的是个啥。
我管这叫**"把硬件事实变成软件字段"**。三个原则:
- 设备是什么 → 用编号 + 型号 + 状态描述;
- 设备能怎样 → 用门数 / 锁数 / 摄像头数描述能力;
- 设备联网吗 → 用网络类型 + 固件版本 + 最后心跳描述可达性。
URM Ultra 的 Device 表(dms_device)就是这三条原则的产物。
二、Device 实体逐字段拆解
下面这段是项目源码里 Device 实体的关键字段(已加讲解注释):
java
// 项目源码: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 是个枚举,只有三种:
java
// 项目源码
public enum DeviceStatus { ONLINE, OFFLINE, FAULT }
我逐个字段翻译回"硬件语言",让你感受建模的对应关系:
| 字段 | 类型 | 硬件含义 | 演示数据 |
|---|---|---|---|
deviceNo |
String(唯一) | 出厂序列号 / 资产编号 | DEV-0001 |
model |
String | 机型 | 双门智能柜 |
doorCount |
int | 柜门数 | 2 |
lockCount |
int | 电控锁路数 | 2 |
cameraCount |
int | 摄像头数 | 4 |
network |
String | 通信网络 | 4G |
firmwareVersion |
String | 固件版本 | 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 里,售货机设备端调用:
bash
POST /device-api/vending/{deviceNo}/heartbeat
后台 DeviceService.heartbeat 的逻辑(项目源码简化):
java
// 项目源码:心跳置在线 + 刷新时间
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)把"货道"和"商品"绑定:
java
// 项目源码:设备商品(货道绑定)
@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 | 可乐 | 10 | 8 | 1 |
| A03 | 橙汁 | 10 | 3 | 1 |
| B01 | 纸巾 | 10 | 1 | 1 |
| B02 | 巧克力 | 10 | 2 | 1 |
注意 slotNo 和 cameraCount 的关系:4 个摄像头要覆盖这些货道的画面,识别时才能定位"用户从 A03 拿了橙汁"。货道是视觉识别结果的落点,也是扣库存的落点 ------第 02 篇 deductStock(deviceId, slotNo, qty) 扣的就是这里的 stock。
六、低库存:从硬件事实引出补货动机
DeviceProduct.stock 持续下降,降到阈值以下,就该补货了。后台 DeviceService.listLowStock(threshold) 干这事:
java
// 项目源码:低库存过滤(默认阈值 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,后台就真知道这是台双门四摄柜。
八、一张图收尾:硬件 → 字段 → 业务
bash
现实柜子(双门/2锁/4摄/4G)
│ 建模
▼
Device 实体(deviceNo/doorCount/lockCount/cameraCount/network/...)
│ 衍生
├── status+lastHeartbeat ──▶ 心跳在线判定
├── DeviceProduct(slotNo/capacity/stock) ──▶ 货道销售 + 识别落点
└── stock<=threshold ──▶ 低库存 ──▶ 触发无人车/机械臂补货
把硬件事实变成软件字段,听起来朴素,却是物联网系统的地基。地基打歪,上面跑的车、臂、订单全得塌。
下一篇,我们离开柜子,去看把货运过来的那位------无人车,以及它在三端里当的"枢纽"角色。