做无人零售这些年我经手过饮料机、零食柜、盲盒机但广州这个“无人共享羽毛球售卖”项目算是最有意思的一个。羽毛球在广州的群众基础不用多说天河、海珠、番禺随便一个区都有几十家球馆但你去实地转一圈就会发现绝大多数球馆还停留在“前台放一筐球、老板在就卖、老板不在就抓瞎”的阶段。这套软件源码要解决的就是球馆非营业时段的卖球痛点、球友临时没球的尴尬以及运营方想降低人工成本的需求。这个项目的完整形态是一个三端系统用户扫码用的微信小程序、设备端的智能控制板固件、运营侧的管理后台服务端则是统一API加消息中间件。整篇复盘我会从方案选型、架构拆解、核心流程、部署联调、源码解读到现场踩坑一条线写下来内容包括订单和库存的数据模型、MQTT设备通信协议、押金退还风控这类硬核细节。适合三类人看想给自家球馆做无人售卖的运营者、正在找无人零售项目练手的后端开发者以及打算把共享租赁模式复制到其他品类的创业者。1. 项目定位与整体设计思路1.1 “售卖”和“共享”到底是哪两种业务逻辑很多人第一次听到“无人共享羽毛球售卖”会犯迷糊这到底是卖球还是租球我直接说结论这个方案里两种业务都有而且必须在一套系统里同时支持。我根据实际运营场景把它拆成三种模式讲。第一种是纯售卖。设备放在球馆走廊、前台旁边或者休息区用户扫码选一桶训练球或比赛球付款后设备像自动售货机一样出货。一桶训练球的市场价在60到100元之间夜间场馆前台下班后这就是纯增量收入。第二种是共享租球这是这个项目区别于普通自动贩卖机的核心差异点。球友晚上去打球没带球又不愿意花一桶的钱买断那就走“押金按时计费”的逻辑扫码付20到30元押金取一筒球打完以后归还设备自动检测回筒并退押金按小时或按次扣租金。第三种是混合模式球馆可以在后台配置哪些SKU走购买、哪些SKU走租赁甚至可以按时间段动态切换比如白天只售不租、晚上开放共享租球。这三种模式的业务逻辑完全不一样所以在设计软件时我强烈建议订单表一开始就要支持“购买单”和“租借单”两种类型。购买单的生命周期很简单支付、出球、完成。租借单则复杂得多押金支付、出球、用户取走、归还、结算租金、退还押金。如果前期偷懒只做一种单据后面想加共享模式基本等于把整个订单模块重写这个教训我是真踩过的。1.2 为什么技术栈锁定“小程序 物联网柜机 Web后台”技术选型方面这套项目的标准交付形态是三端加一服务用户侧微信小程序设备侧WiFi或4G通信的智能控制板运营侧Web管理后台服务端是统一业务API。这个组合我反复权衡过最终认为最贴合中小型场馆的实际条件。用户端选小程序而不是独立App原因很简单体育场馆的用户习惯就是扫码即用没人愿意为了买一筒球专门下载一个App。小程序零安装、微信授权一键完成身份绑定和微信支付的打通也是最顺滑的开发成本比双端App低一个量级。对于运动场景这种低频次轻交易小程序是唯一理性的选择没有之一。设备端是整个系统里最需要花心思的部分。市面上的智能柜多数是格子柜适合卖零食卖盲盒但羽毛球是圆柱形桶装物格子太深取不出来太浅又容易滚落。所以这个项目的设备控制板一般对接的是螺旋货道或抽屉式货仓出货逻辑不是简单的电磁锁开门而是“电机转动加多路传感器确认出货”。主控建议用ESP32或STM32级别的板子加继电器输出通信协议用MQTT。这条链路在无人零售行业里已经被验证得足够成熟断线重连、消息幂等这些坑都有成熟方案新项目没必要再发明轮子。后端没有太多花哨的东西Spring Boot写的业务API配合MySQL存订单和SKURedis做热点缓存和分布式锁管理后台用Vue写一套响应式PC端页面就够用了。整套工程拆成六个模块用户中心、设备管理、商品库存、订单交易、支付路由、数据统计。后续所有功能演进都在这六个模块边界内做基本不会失控。2. 系统架构与核心模块深度拆解2.1 六个核心模块的职责边界与调用关系先说清楚这六个模块怎么分工因为后面讲的所有流程都是基于这个边界在跑。用户中心负责微信登录态管理包括openid绑定、会员等级、押金账户、历史订单查询。这个模块不直接参与交易但它决定了用户在小程序里看到的价格是否含会员折扣以及租球时信用免押有没有开通。设备管理模块维护设备档案、在线状态、货道配置和远程升级记录简单说就是让运营方在后台能随时知道每台设备活没活着、每个货道装的是什么货。商品库存模块管SKU和库存但它的库存不只是数字增减每一桶球从入库到出库都要有流水记录这是后面做损耗分析的基础。订单交易模块是系统的心脏所有购买单、租借单的创建、状态流转、结算都归它管它不直接去调设备也不直接去调支付而是通过事件驱动的方式协调其他模块。支付路由模块封装了微信支付和支付宝的统一接口包括下单、回调验签、退款、账单对账。数据统计模块负责把订单、设备、库存的数据汇总成看板给球馆老板看每天卖了多少球、哪台设备出货失败最多、哪个时段租球需求最旺。六个模块之间的调用链路上最关键的一条是订单交易模块和支付路由、设备管理之间的解耦。我见过很多项目直接把支付回调逻辑写在订单接口里结果支付掉单以后排查起来非常痛苦。正确的做法是支付回调只管更新支付单状态然后发一个paid事件出去订单模块监听这个事件去推进订单状态机设备模块监听同一个事件去触发出货指令。这样每一环都能独立重试、独立排查。2.2 关键数据模型设计实战数据模型直接决定后面业务能不能灵活扩展。我给出这套系统里最核心的几张表结构都是实际跑过线上流量的设计不是PPT层面的东西。订单主表是重中之重关键字段包括order_no、order_typePURCHASE表示购买RENT表示租借、device_id、sku_id、amount、deposit、status、channel_openid、create_time、finish_time。租借单还需要单独一张rent_order扩展表额外记录押金单号、取球时间、归还时间、实际扣费金额、押金退款单号。购买和租赁共用主表、扩展信息分表既能保证订单号全局唯一又不会让纯购买订单被一堆空字段拖累。库存流水表是第二张不能省的表字段是id、sku_id、change_typeINBOUND、OUTBOUND、SOLD、RENT_OUT、LOCKED、RETURNED、delta、order_no、operator、created_at。为什么一定要流水表因为共享球的库存状态比普通售卖复杂得多。一桶球被用户租走之后它只是从“在场库存”变成“租赁中”并没有真的消耗掉归还之后它又回到可租库存。如果不记录每次变化月底盘点时根本对不上账。设备货道表和SKU关联表也不可或缺。每个货道要记录当前装载的SKU、容量上限、当前存量、最后补货时间、出货成功次数、卡货次数。这些数据是运营判断“哪个货道容易卡球”“哪种球卖得最快”的依据。卡货次数高的货道我会在后台单独标记安排补货员优先检查。2.3 MQTT设备通信协议约定设备通信这块我直接给出一个经过线上验证的主题约定新项目可以直接抄作业。通信协议统一走MQTTQoS设成1保证消息至少送达一次同时用请求序列号做幂等控制。设备心跳和设备状态上报用device/{deviceId}/heartbeat这个主题设备每隔30秒上报一次在线状态内容包括电量、信号强度、当前温度、货道传感器状态。后端收到心跳后更新设备在线时间超过90秒没心跳就判定设备离线在管理后台亮红牌。出货指令下发用device/{deviceId}/command后端向设备推送JSON指令重点字段包括cmd_id全局唯一指令号、cmd_typeDELIVER表示出货、RETURN_OPEN表示打开归还仓门、sku_id、slot_no货道号。设备执行完成后通过device/{deviceId}/result上报执行结果同样带上cmd_id来对应是哪条指令的结果。这里的关键在于出货结果不能只报“成功”或“失败”还要带上传感器的具体状态码。比如货道电机转了但出料口传感器没检测到球状态码就是DELIVER_TIMEOUT系统遇到这个码会主动触发卡货补偿流程而不只是给用户一个干巴巴的失败提示。归还仓门的控制也走同样的协议。共享球模式有一个专门的回筒舱舱门上带光电传感器和限位开关。用户扫码还球时后端下发RETURN_OPEN指令设备打开舱门用户把球桶放进去传感器确认球桶进入后自动关门设备上报RETURN_CLOSED事件后端收到后才开始走结算和退押金流程。如果关门时检测到异物卡住舱门会立刻重新开门的防夹逻辑这条安全细节一定要在固件里实现现场运营时真能碰到小孩把手伸进去的情况。3. 从扫码到出货核心流程与实现细节3.1 支付流程设计与资金安全底层逻辑支付是无人零售里事故率最高的环节所以我单独拿出来重点讲。完整的购买链路是这样跑的用户在小程序选择商品发起下单后端创建购买订单状态为INIT然后调用微信支付统一下单接口拿到prepay_id返回给小程序小程序调起支付用户输入密码完成支付微信服务器异步回调后端接口后端验签通过后把支付单状态改成PAID同时发一个paid事件出去订单模块监听到这个事件后把订单状态改成PAID并调用设备管理模块下发出货指令设备上报出货成功订单状态更新为COMPLETED。这套链路里最容易出问题的点是支付回调和出货结果之间的关系。如果微信回调到了但设备出货失败后端必须启动自动退款流程绝不能把用户的资金挂着不管。如果支付回调延迟了用户在设备前等了好几分钟没反应用户端小程序大概率会点击“重新查询订单状态”所以后端必须提供queryOrder接口通过查微信支付单状态来补偿回调延迟查到已支付就直接推进出货。这里的底层逻辑是资金和货物不能有任何一方悬空。我的做法是引入一个payment_ticket的概念支付单、业务订单、出货指令三者通过ticket_no关联。任何一次对账只要发现某个订单处于“已支付但未出货且未退款”的中间态超过5分钟系统自动触发异常告警运营人员和用户都能看到明确的状态提示。3.2 出货控制与防差错机制出货控制是整个方案里硬件和软件结合最紧密的部分。命令下发之后设备控制板要执行的是这样一段逻辑先检查目标货道传感器是否空闲如果货道被卡住或上一次出货还没完成直接返回BUSY状态然后锁定货道启动电机旋转预设圈数同时打开出料口挡板光电传感器持续监测是否有球桶通过超过5秒没有检测到落料就判定出货失败出货成功后电机反转复位关闭挡板上报成功码。为了实现“出货成功”的可信判断数据上要做双重校验传感器电平判断加电机电流峰值检测。球桶从货道滑落时会给传感器一个短暂遮挡信号同时电机在推动球桶时电流会有一个明显的尖峰。两个信号都满足才算出货成功能极大减少误判。如果只依赖光电传感器羽毛球桶表面光滑反光偶尔会出现传感器反射误触发把空货道当成正常出货。还有一个人性化的细节设备出货口设计成感应取货门。电机把球桶推到取货仓后取货门自动打开用户拿走球桶后门关闭。如果球桶在出货后一段时间内没被取走系统会认为用户可能没注意或设备故障自动重新入柜并创建一个待取货订单用户回来扫同一二维码可以再次取货。这个设计在球馆环境里特别实用因为经常有人扫完码去接电话货出来了人不在。3.3 共享球归还流程与押金风控共享球的归还流程如果在设计时稍微偷懒后面押金纠纷会让人崩溃。我的实现标准是“全程可追踪异常可举证”。用户在小程序点击“归还”时后端生成一条归还任务下发RETURN_OPEN指令打开归还舱。设备端内置摄像头和重量传感器球桶放进舱后重量传感器先判断重量是否在合理范围同时摄像头拍一张照片并上传OCR识别球桶上的二维码标签确认回筒的是本系统借出的球。确认无误后关闭舱门后端发起租金结算按实际租用时长扣费然后原路退回押金。押金风控这块有两个关键点。第一是要对“长时间不归还”的情况自动升级处理。我的规则是租借超过24小时如果没有归还系统先以App推送和短信提醒用户超过72小时仍未归还押金自动转入“待处理”设备会将对应openid加入限制名单限制再次租球。这个规则在源码里做成可配置参数每个球馆可以根据自己的客群调整。第二是退款失败的重试机制。微信退款偶尔会出现余额不足或者银行通道故障后端必须对退款失败的单子自动重试并记录原因不能让押金卡在一笔失败退款里不管了。实践中我建议退款状态至少要做到REFUNDING、SUCCEEDED、FAILED三个状态运营后台对这些状态要能一键筛选。4. 部署落地与联调实操4.1 设备安装与网络环境要求设备真正进场的时候很多问题不是软件问题而是现场环境问题。我把联调过程中总结出来的硬性要求列成一张清单准备部署这套方案的人可以直接照做。网络方面球馆的WiFi环境通常比预想中复杂。场馆面积大、墙壁厚、人多人杂普通家用路由器根本扛不住长时间高并发连接。现场最稳的方案是给每台设备配一张物联网SIM卡走4G虽然多一点点流量费但稳定性完胜蹭场馆WiFi。如果要复用场馆WiFi至少要保证信号强度在-65dBm以上并配置独立的SSID和静态IP分配杜绝DHCP地址漂移导致设备离线。网络出问题时的兜底方案是设备本地缓存订单号和指令号断网期间不能出货但至少要保证扫码后能弹出“设备当前网络异常请稍后再试”的提示而不是让用户干等。设备安装位置也直接影响出货成功率。羽毛球是圆柱形桶装物出货通道对水平度要求很高。安装时要用水平尺校准机柜地面有轻微不平要及时垫平否则货道里的球桶重心偏移出货时容易卡在挡板处。摆放位置要避开空调出风口和直射阳光因为温度变化会影响传感器灵敏度尤其是光电传感器在强光干扰下会出现误触发。4.2 管理后台配置与商品上架步骤管理后台的配置逻辑其实就三步但每一步都有细节。第一步是设备绑定。运营方在后台添加设备录入设备编号并绑定所属球馆然后设备上电联网后会自动注册到后台管理端显示设备在线。这一步要特别注意的是设备第一次连接时用的鉴权凭证要一次性使用防止设备被重复绑定。第二步是货道配置。后台进入设备详情页对每个货道执行“装载SKU”操作选择商品并录入当前库存。这里的库存量要和实际补货时一致不要凭感觉填否则会出现在线库存和实物对不上的情况。第三步是商品上架。商品的销售价格、租金价格、押金金额、折扣规则、上下架状态都在商品管理里配置。球馆常见的运营套路是把“训练球”设为可售可租把“比赛球”设为仅售卖比赛球押金高且不开放租用避免损耗影响比赛用球品质。平台上线前一定要做一次全链路“模拟交易”测试。我的做法是在每个球馆选一台设备后台把价格设置成0.01元的测试价然后真实走一遍“购买下单—支付—出货”“租球—归还—退押金”两条链路。测试不仅能验证支付和出货还能顺带把设备出货口、传感器等硬件的问题暴露掉。线上正式运营后没人愿意拿真金白银去陪你测流程。4.3 广州球馆场景的运营节奏与数据观察这个项目在广州落地后的数据d让我对无人零售有了更直观的认识。羽毛球馆的客流有明显的波峰波谷晚上7点到11点是黄金时段周末全天都不错。销售数据跟着球馆的订场率走订场率高的球馆一台设备一个晚上卖出十几桶球很正常共享租球的周转率可以达到每桶每天3到4次。广州这种城市还有一个特殊点天气潮湿回南天的时候空气湿度极高设备内部如果密封不好传感器引脚容易氧化出货故障率会上升。针对这一点设备内部要加干燥包同时控制板要做三防漆处理。这些不是软件源码能解决的但如果做整体方案一定要提醒硬件供应商注意。运营节奏上我建议球馆每周做两次库存巡检后台看板可以自动列出“库存低于阈值”的SKU清单。补货员带球过去后在后台做“补货确认”操作库存流水自动增加。每周五晚上是补货最关键的节点因为周末的消耗量通常是工作日的两到三倍。5. 源码工程解读与二次开发方向5.1 源码目录结构与启动顺序这套源码工程的交付形态是一个前后端分离的完整仓库目录结构我会在下面做一个走读方便接手的人快速上手。根目录下分成server、miniprogram、admin、device-firmware四个子目录。server是后端服务采用Spring Boot结构包名按controller、service、mapper、entity、mqtt、config划分。mqtt包底下沉了所有设备通信逻辑包括主题路由、消息解析、指令下发。miniprogram是微信小程序前端页面核心在pages/index设备码扫码页、pages/product商品列表页、pages/order订单详情页、pages/rental租球归还页。admin是PC管理后台基于Vue和Element UI搭建路由按设备管理、商品管理、订单管理、数据统计划分。device-firmware是设备端固件基于ESP-IDF框架核心逻辑在main/delivery.c和main/mqtt_client.c。启动顺序上我建议按这个顺序来先启动MySQL和Redis再启动MQTT Broker然后启动后端服务最后才是管理后台和小程序前端。后端服务的配置文件里包含了数据库连接、Redis连接、支付商户号、MQTT地址等参数首次启动前全部要替换成自己的。小程序前端在微信开发者工具里导入工程后要修改config.js中的API基地址和公众号AppID。5.2 基于源码做业务定制的三个典型入口很多找我拿源码的人问的都是同一个问题“我拿到手之后怎么改成自己的业务”我建议先从三个入口切入。第一是商品品类的扩展。系统现在内置的是羽毛球SKU但数据模型完全是通用的。想改成卖篮球、排球或者网球只需要在管理后台新增分类和商品前端商品列表页面会自动渲染。后端唯一要注意的是不同球类的外形尺寸不同货道参数比如电机圈数、挡板延时需要在设备货道配置里重新校准否则出货会失败。这个参数在源码里是slot_config表的一个motor_rounds字段不需要改代码。第二是计费规则的可配置化。租球模式目前的计费是“按小时计费加封顶价格”源码里封装了RentBillingStrategy接口实现类可以扩展按次计费、按时段计费、按局计费。接手的开发者如果熟悉策略模式加新的计费规则不需要动订单主流程只要实现接口并注入Spring容器就行。第三是信用免押扩展。现在的押金流程是硬性押金所有用户取球都必须冻结押金。如果要接入微信支付分或芝麻信用做信用免押需要在用户中心和押金账户模块加一个信用评估的钩子。源码里在创建租借单之前预留了CreditCheckService接口默认实现是直接返回“需押金”替换成微信支付分的实现即可。我实际接过的项目里免押额度设成350分以上租球转化率能提升20%以上。5.3 二次开发中的性能与安全红线二次开发最忌讳的是把后端接口直接暴露给小程序而不做防护。源码里已经内置了基于openid的登录态校验和基于用户身份的接口鉴权新加接口时务必沿用这套鉴权体系。管理后台的接口全部要求管理员登录态且关键操作改价、退款、强制出货要有操作日志记录这些在源码里都有但改代码时要保持住。性能方面要注意的是Redis缓存的设置。SKU信息这种变化不频繁的数据建议缓存10分钟设备在线状态这种实时数据缓存不能超过30秒订单号查询不建议加缓存直接走数据库因为订单写入频繁缓存命中率低还容易读到脏数据。MQTT的消息并发量不大单台Broker扛几千台设备没问题但如果是多地部署Broker集群要提前规划好。6. 常见问题与排查技巧实录6.1 支付成功但设备不出货怎么定位这个故障几乎每个无人零售项目都会碰到处理思路比处理方式更重要。我给的排查顺序是先查支付回调是否成功到达后端再查订单状态是否已经从PAID变成DELIVERING然后查MQTT主题device/{deviceId}/command是否成功推送最后看设备执行结果码。实际案例里八成的“支付成功不出货”都卡在MQTT推送环节。设备离线是最常见原因处理手段是设备端固件实现断线重连机制同时做指令持久化。我的推荐方案是设备端即使离线也要把用户订单信息缓存到本地等网络恢复后主动拉取未执行的出货指令。这个功能在源码里是开箱即用的但如果接手的人在下发指令时走了异步队列务必确认队列消费失败后有没有自动重试没有重试机制就要补上。用户侧的补偿操作是提供“扫码重新检测订单”按钮小程序调用后端queryOrder接口根据订单状态给用户明确提示。6.2 设备频繁离线与消息积压设备离线不要只盯网络还要排查设备固件本身。最常见的原因是MQTT客户端的心跳间隔设置太短和主控芯片的低功耗策略冲突。芯片在深度睡眠时MCU暂停工作MQTT连接被服务端断开唤醒后重连需要时间这个间隙里订单就丢了。解决方法是把设备的心跳间隔设为30秒到60秒之间并使用MQTT的Last Will遗嘱消息让服务端能感知设备异常下线。另外WiFi环境差的球馆固件里要实现“WiFi信号低于阈值自动切4G”的逻辑这是我在现场摸爬滚打总结出来的经验没有之一。消息积压主要出现在断网恢复瞬间。设备重连后一次性收到大量指令处理不过来会导致串货。处理办法是设备端维护一个“当前任务号”同一时间只处理一个指令其他指令排队。服务端下发指令时也要判断设备在线状态设备离线时不下发指令而是标记为“待执行”等设备心跳恢复后再推送。这种“服务端控制节奏”的策略比让设备端疯狂接收可靠得多。6.3 卡货与传感器误判的处理方案卡货在羽毛球售卖设备里发生率不低因为球桶是软性包装不像饮料瓶那样棱角分明。轻微卡货可以通过电机正反转抖动解决严重卡货需要人工处理。软件层面要做两件事一是自动重试机制出货失败先自动反转电机重试两次每次间隔3秒二是自动告警和退款补偿重试仍失败的订单自动进入退款流程同时给补货员推送卡货工单让设备进入“该货道维护中”状态不再接新订单。传感器误判的问题主要在强光和灰尘环境。光电传感器在阳光直射下容易误触发改善方案是加遮光罩同时软件里对传感器信号做时间窗口过滤——遮挡信号最短持续50毫秒以上才认为是有效信号低于这个宽度的脉冲一律当成干扰忽略。这套滤波逻辑在固件里已经有实现如果现场还是频繁误报优先检查传感器表面是否有灰尘覆盖不要一上来就调代码。6.4 押金纠纷怎么用数据自证共享模式做得越久押金纠纷就越躲不掉。最常见的纠纷是用户说“我还球了但你们没退押金”或者“我根本没借过球怎么扣了我押金”。软件系统能做的就是用一条完整的数据链来自证。归还环节的每一笔操作都要留痕归还舱门开关时间、重量传感器读数、摄像头抓拍图片、OCR识别出的球桶标签、当时的订单状态截图。当用户投诉时运营后台输入订单号把这些数据一键导出绝大多数纠纷都能在用户看到抓拍图片后自行解决。押金冻结和扣费的账目要能对平。每天凌晨跑一次对账任务核对押金冻结总额、实际扣费总额、退款成功总额三者加退款中的金额应该等于押金总收入。对不上账的时候90%是退款失败的订单没有重试成功先筛REFUNDING状态超过24小时的单子。这个对账任务在源码里默认开启新接手的团队不要随手关掉。我个人在操作中最大的体会是无人零售软件说到底是“订单状态机”的工程支付、设备、库存、押金全都是围绕订单状态在转。把状态流转画清楚、把异常补偿机制做扎实比堆再多花哨功能都管用。这套羽毛球售卖源码后续如果要扩展往“预约取球”“团购批发”“会员月卡”方向做都是顺手的事但前提是把基础链路维护稳了。做无人零售没有捷径把每一条失败路径都想明白现场运营就轻松一大半。