设备管理接口-从分页到心跳

发布时间:2026/10/23 10:09:56

设备管理接口-从分页到心跳 06-设备管理接口-从分页到心跳系列URM Ultra 商品/设备/订单/支付/视觉 业务实战主角urm-server 后端 · device 域 · AdminDeviceController DeviceService上篇第 05 篇我们把Device实体拆透它是双门智能柜的电子档案deviceNo是唯一身份证状态机三态心跳即脉搏。结尾预告过下一篇打开AdminDeviceController和DeviceService看设备增删改查接口尤其是heartbeat和listLowStock这两个带坑的实现。这一篇就把这两个类摊开。它们既是管理端入口也是订单扣库存、补货计划生成的地基。读完你会明白三件事管理端对设备做了哪些接口每个接口背后对应DeviceService的哪个方法create / heartbeat / bindProduct / deductStock / listLowStock这五个方法各自藏着什么规则与坑为什么分页是 CRUD 的标配而心跳和低库存却是物联网特有的灵魂。一、先上接口全景AdminDeviceController 干了啥AdminDeviceController位于com.urm.ultra.device.controller是所有管理端操作设备的 HTTP 入口统一挂在/admin-api/device下把请求转交DeviceService处理结果包成统一Result返回。先放完整接口清单与《API 接口文档》一致方法路径入参对应 Service 方法作用GET/admin-api/device/pagepageNum1, pageSize10page(pageNum, pageSize)分页查询设备GET/admin-api/device/list无list()设备列表不分页GET/admin-api/device/{id}路径 idget(id)设备详情POST/admin-api/deviceDevice 对象create(device)新增设备PUT/admin-api/device/{id}id Device 对象update(id, device)编辑设备DELETE/admin-api/device/{id}路径 iddelete(id)删除设备GET/admin-api/device/{deviceId}/products路径 deviceIdlistDeviceProducts(deviceId)货道/商品列表POST/admin-api/device/{deviceId}/productsslotNo, productId, capacity?, price?, enabled1bindProduct(...)绑定货道商品PUT/admin-api/device/{deviceId}/products/{slotNo}/stockstockupdateStock(...)更新货道库存GET/admin-api/device/low-stockthreshold5listLowStock(threshold)低库存货道注意路径里{id}是自增主键、{deviceId}同为 id另有products子路径管货道。设备本体与货道商品是两套资源用子路径区分是 RESTful 常规写法。二、分页CRUD 的开胃菜先看最基础也最易被轻视的page方法【项目源码】// 项目源码com.urm.ultra.device.service.DeviceServicepublicPageResultDevicepage(intpageNum,intpageSize){PageRequestpageablePageRequest.of(pageNum-1,pageSize,Sort.by(Sort.Direction.DESC,id));PageDevicepagedeviceRepository.findAll(pageable);returnPageResult.of(page,d-d);}三行逻辑藏三个点pageNum - 1前端页码从 1 开始人类习惯第 1 页Spring Data 分页从 0 开始“第 0 页”。所以这里减一换算PageResult里pageNum又加回保证前后端对齐。Sort.by(DESC, id)默认按 id 倒序最新的排最前。PageResult.of(page, d - d)把Page连同total/list/pageNum/pageSize包成统一分页体返回。PageResult是common包里的通用分页壳结构如下字段类型含义listListT当前页数据totallong总条数用于算总页数pageNumint当前页码从 1pageSizeint每页大小list()是不分页全量列表给下拉框/批量面板用如补货选设备。get(id)按主键查详情查不到抛 404。查不到直接抛 404由全局异常处理器转成前端能懂的错误提示而不是让null悄悄漏到后面引发空指针。这是异常即业务信号的一贯风格。三、create新建设备默认离线新增设备是管理端常用动作看create【项目源码】// 项目源码com.urm.ultra.device.service.DeviceServicepublicDevicecreate(Devicedevice){if(deviceRepository.findByDeviceNo(device.getDeviceNo()).isPresent()){thrownewBusinessException(设备编号已存在);}if(device.getStatus()null){device.setStatus(DeviceStatus.OFFLINE);}returndeviceRepository.save(device);}两个守卫缺一不可编号唯一校验findByDeviceNo(...).isPresent()先查一遍存在就抛设备编号已存在——业务层兜底比等数据库抛ConstraintViolationException更友好。默认离线status null时设为OFFLINE。刚录入系统的柜子还没通电联网凭什么在线必须等真正上报一次心跳才翻ONLINE。update(id, source)是取旧设备 → 覆盖可写字段 → 保存只改name/model/doorCount/lockCount/cameraCount/network/location/address/firmwareVersion这些硬件档案不改deviceNo/status/lastHeartbeat——编号不能改、状态归心跳、时间归心跳逻辑。delete(id)直接deleteById。四、heartbeat让一台柜子活过来这是物联网设备的命门【项目源码】// 项目源码com.urm.ultra.device.service.DeviceService/** 设备心跳更新在线状态与心跳时间。 */publicDeviceheartbeat(StringdeviceNo){DevicedevicegetByNo(deviceNo);device.setStatus(DeviceStatus.ONLINE);device.setLastHeartbeat(LocalDateTime.now());returndeviceRepository.save(device);}它接收deviceNo业务编号非 idgetByNo查设备后置ONLINE、lastHeartbeat刷当前时间并保存。调用方是VendingMachineAgentController路径POST /device-api/vending/{deviceNo}/heartbeat柜子每约 30 秒发一次我还活着后端收到即翻ONLINE。getByNo和get的差别get(id)用主键、getByNo(deviceNo)用业务编号查不到都抛 404只是消息不同。心跳、识别这类设备自报场景固件只认deviceNo故用getByNo。五、bindProduct把商品塞进货道柜子空壳没用得往货道放商品——bindProduct即干此活【项目源码】// 项目源码com.urm.ultra.device.service.DeviceServicepublicDeviceProductbindProduct(LongdeviceId,StringslotNo,LongproductId,Integercapacity,BigDecimalprice,Integerenabled){get(deviceId);ProductproductproductRepository.findById(productId).orElseThrow(()-newBusinessException(404,商品不存在));DeviceProductdpdeviceProductRepository.findByDeviceIdAndSlotNo(deviceId,slotNo).orElseGet(DeviceProduct::new);dp.setDeviceId(deviceId);dp.setSlotNo(slotNo);dp.setProductId(productId);dp.setCapacity(capacity);dp.setStock(capacity!null?capacity:0);dp.setPrice(price!null?price:product.getPrice());dp.setEnabled(enabled!null?enabled:1);returndeviceProductRepository.save(dp);}逐行看规则get(deviceId)先确认设备存在不存在直接 404。商品校验productId必须指向真实存在的商品pms_product否则 404。存在则覆盖不存在则新建findByDeviceIdAndSlotNo(...).orElseGet(DeviceProduct::new)——同一个deviceId slotNo再次绑定会覆盖旧绑定而不是插重复行。这避免了一个货道出现两条记录的脏数据。库存初始化stock capacity ! null ? capacity : 0。绑定时若给了容量capacity库存直接填满到容量没给容量就先记 0。价格回落price price ! null ? price : product.getPrice()。货道可设设备端特价覆盖标准售价没设就用商品标准价。启用默认 1enabled默认 1启用。下面第 17 篇讲 Mock 视觉时你会看到视觉识别只从enabled1的货道里挑。updateStock(deviceId, slotNo, stock)是补货上架后手动改库存的入口按deviceId slotNo找到货道设新值找不到抛货道不存在。六、deductStock订单扣库存的唯一正门这个方法不起眼却是下单链路的守门员【项目源码】// 项目源码com.urm.ultra.device.service.DeviceService/** 扣减库存识别出商品后调用。 */publicvoiddeductStock(LongdeviceId,StringslotNo,intquantity){DeviceProductdpdeviceProductRepository.findByDeviceIdAndSlotNo(deviceId,slotNo).orElseThrow(()-newBusinessException(404,货道不存在));intremain(dp.getStock()null?0:dp.getStock())-quantity;if(remain0){thrownewBusinessException(库存不足);}dp.setStock(remain);deviceProductRepository.save(dp);}它做了两层保护货道必须存在找不到deviceId slotNo组合直接 404。库存守卫remain 当前库存 - 要扣数量若 0抛库存不足。绝不允许扣成负数——这是库存铁律否则超卖用户付钱却没货。更重要的这才是订单真正扣库存的地方。回看第 13 篇createAndPay扣库存并非订单自己改而是调deviceService.deductStock(deviceId, slotNo, quantity)。库存这个敏感资源的改法被收口在DeviceService一处订单域只告诉它扣多少。为什么要收口库存是下单扣减、补货回补、人工改库存共享的状态。把怎么扣、扣之前校验什么集中在一处才能避免并发下对不上账。七、listLowStock低库存的报警器最后一个方法也是上篇预告过的带坑实现。【项目源码】// 项目源码com.urm.ultra.device.service.DeviceService/** 低库存货道供补货计划使用。 */publicListDeviceProductViewlistLowStock(intthreshold){returndeviceProductRepository.findAll().stream().filter(dp-dp.getStock()!nulldp.getStock()threshold).map(dp-toView(deviceRepository.findById(dp.getDeviceId()).orElse(null),dp)).toList();}逻辑简单把dms_device_product全表拉出用流过滤stock ! null 且 threshold默认 5的货道转成DeviceProductView返回。但坑就在这全表拉出来没走 SQLWHERE stock ?而是findAll()全表加载内存过滤Demo 才 10 条货道无所谓上万货道就难看。它被admin-api/device/low-stock直接调用也是ReplenishService.generate的地基那篇用Integer.MAX_VALUE当阈值取全部货道再筛。一处实现两处复用。这个纯内存过滤是 Demo 的有意简化《二次开发文档》点明大数据量应下推为findByStockLessThanEqual(threshold)之类——能跑和能扛是两码事。toView(...)把DeviceProduct拼上设备编号、商品名、条码组装成DeviceProductView含slotNo/productName/barcode/capacity/stock/price/enabled商品名条码按productId从pms_product取——又一次货道→标准库回落第 18 篇详讲。八、十个接口一张调用地图把本篇方法收成一张调用地图page/list/get→ 查create编号唯一默认 OFFLINE→ 增update→ 改delete→ 删listDeviceProducts→ 货道视图bindProduct覆盖/容量/价回落/默认启用updateStocklistLowStock内存过滤设备端心跳 →heartbeatOFFLINE→ONLINE。更重要的是跨域调用OrderService.createAndPay调deductStockReplenishService.generate调listLowStock。DeviceService还被订单域、补货域反向依赖——设备域是整个系统的地基层。九、本篇小结AdminDeviceController挂在/admin-api/device提供设备 CRUD 货道绑定/改库存 低库存查询共11 个接口本质是DeviceService的 HTTP 投影。五个核心方法各有门道create编号唯一校验、默认OFFLINEheartbeat翻ONLINE并刷新lastHeartbeatbindProduct同货道存在则覆盖、库存初始化为容量、售价回落标准价、默认启用deductStock是下单扣库存唯一正门带不允许负库存守卫listLowStock内存流过滤低库存是补货地基大数据量需下推 SQL。分页是 CRUD 标配pageNum-1 倒序 PageResult心跳与低库存才是物联网/零售灵魂前者管在线、后者管补货。设备域不止服务管理端还被订单域、补货域反向依赖——它是整个系统的地基。下一篇离开设备域跳到支付域的 Mock 实现不接微信凭什么也能把扣款跑通MockWechatPayScoreService如何用ConditionalOnProperty和内存Map演示整条支付闭环第 15 篇。
延伸阅读

更多相关文章

2026/10/23 10:04:53

Miniconda虚拟环境配置指南:conda/pip分工与VS Code避坑

1. 先想清楚:为什么是 Miniconda,而不是 Anaconda 或裸 Python1.1 一个常见的翻车现场我见过太多人装 Python 环境的流程是这样的:官网下个安装包,一路下一步,装完打开终端敲pip install,然后过两个月回头看…

2026/10/23 11:05:20

ModernWMS 指南:开源仓库管理系统的完整拆解

ModernWMS 指南:开源仓库管理系统的完整拆解 【免费下载链接】ModernWMS The open source simple and complete warehouse management system is derived from our many years of experience in implementing erp projects. We stripped the original commercial sy…

2026/10/23 11:05:20

Codex桌面版config.toml配置修复与robocopy精准清理指南

1. 项目概述:当Codex桌面版突然“失忆”,组织配置消失背后的真相Codex桌面版在某高校实验室的AI辅助编程教学场景中已稳定运行近两年,日常用于学生代码补全、函数注释生成和轻量级项目结构分析。但最近一批新部署的终端机频繁出现一个看似微小…

2026/10/23 11:05:20

Semantic IR:构建系统中自然语言转DSL的语义中间表示方案

1. 项目概述:当自然语言撞上构建脚本,为什么Gradle DSL总在报错?“Cannot resolve symbol ‘implementation’”、“Could not find method android() for arguments [...]”、“Expected method call here”——如果你写过Gradle脚本&#x…

2026/10/23 11:00:19

魔兽世界宏命令原理与实战:从条件反射到战斗建模

1. 为什么“宏命令”不是偷懒捷径,而是战斗节奏的神经中枢很多人第一次听说《魔兽世界》里的宏命令,脑子里浮现的是“一键秒杀”“全自动打怪”这类幻想。我见过太多新玩家在论坛发帖:“求一个无敌宏”“有没有自动开技能的宏”,结…

2026/10/22 11:29:28

金融AI落地指南:从调研报告到技术选型与工程实践

简介:这份PDF报告聚焦2025年金融服务业中国AI现状与趋势,面向金融科技从业者、AI战略规划人员及关注行业数字化转型的研究者,帮助读者把握生成式AI在银行、支付、交易等场景中的落地路径与投资方向。资源包内含1个PDF文件,大小约1…

2026/10/23 0:02:26

Verilog实现FPGA密码锁:矩阵键盘与状态机设计实战

说实话,这几年接触了不少想入门FPGA的朋友,如果你问哪个项目能一口气把Verilog的核心基本功串起来,我通常会推荐电子密码锁。这个项目看着不起眼,却把矩阵键盘扫描、按键消抖、状态机设计、动态显示刷新、外设控制这些数字逻辑里的…

2026/10/23 0:02:26

数据服务监控运维实战:从指标体系到告警治理与故障排查

周末凌晨两点,手机震了。第一反应是“数据服务挂了”。打开电脑看了下,果然,某张核心报表的数据产出时间已经超过SLA两小时,下游三个业务方连着发来问询。这种场景,做过大数据运维的人都不会陌生。数据服务的监控与运维…

2026/10/23 0:02:26

FreeRTOS任务通知实战详解:内核原理、API与避坑指南

裸机编程切到RTOS之后,任务之间的通信和同步永远是躲不开的坎。前面聊过队列、信号量、互斥锁、事件组,这次把FreeRTOS任务通知(Task Notification)单独拎出来讲透。原因很简单:任务通知是FreeRTOS里最容易用错、也最容…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑