这套源码是我在上海跑了大半年场地、改了三版架构才跑通的。先交代一下背景:羽毛球馆夜场散客买不到球、前台下班没人卖货、社恐人士不想隔着窗口喊价,这三个痛点叠加起来,就是“无人共享羽毛球售卖”最真实的商业场景。所谓“软件源码”,交付的并不是一个简单的小程序壳子,而是用户端小程序、商家管理后台、设备控制端三大部分加一套云端服务的完整工程。这篇就把这套“上海无人共享羽毛球售卖软件源码”从业务模型、系统架构、核心模块到落地排障,完整拆开来讲。
1. 项目全景:这套源码到底解决什么问题
先别急着聊技术。无人共享球柜这个项目,本质上是“自助售货机+共享计费”的混合体。传统售货机是选品、付钱、出货,链条短,逻辑直白;但羽毛球售卖有个特别之处——散客买球往往发生在打球前五分钟,而夜场用户打完两个小时可能还要续球,如果只是“投币取球”,用户买早了没地方放,买晚了又耽误开局。所以真正的需求是:把球锁在柜子里,用户扫码开柜拿走,按占用时间和商品价值一起结算。这让产品逻辑从“售货”升级成了“售卖+共享储物”的组合。
1.1 用户槽点与功能需求
在龙华、张江、徐汇滨江这些羽毛球热度高的区域,我蹲过十几个球馆,用户侧的槽点高度集中:
- 夜场前台经常没人,或者只留一个保安,找半天没人卖球。
- 新球场没有小卖部,用户得跑到隔壁便利店买,球还常常不是打球人想要的磅数。
- 球馆里散客和包场客混在一起,前台记不清哪个柜子是哪个人的,常出现拿错球、扯皮的事。
- 年轻人普遍不想为买一桶球专门排一次队,扫码即得的体验阈值已经被外卖和共享单车抬得很高。
运营侧的槽点也差不多:球馆老板不想为了卖几桶球多雇一个人;连锁球馆想要看每个场地的消耗数据,但传统售货机给不到“哪个用户几点拿了球、拿了几桶”这种颗粒度。无人共享售卖柜要同时满足这两端,软件上就必须有清晰的模块边界。
1.2 软件交付范围:用户端+商户端+设备端
从源码交付的角度看,整套系统分三个终端的工程代码:
- 用户端:微信小程序,承载扫码、登录授权、柜格状态展示、开柜计费、支付结算、历史订单、余额和优惠券。
- 商户端:Web管理后台,承载设备管理、柜格库存、商品上下架、价格策略、补货任务、对账报表、异常工单。
- 设备端:单片机或嵌入式Linux上的控制程序,承载锁控指令执行、柜门状态采集、心跳上报、离线补单、蜂鸣告警。
三个终端共享同一套云端服务。云端服务是源码的“大脑”,负责订单状态机、计费引擎、支付通道、消息推送和设备指令下发。只看小程序源码是远远不够的,真正的复杂度都在云端怎么把这些端协调起来。
1.3 为什么选“小程序+4G+云端”而不是本地收银台
我在第一版设计时也犹豫过要不要做成“本地收银台+扫码付款码”的轻模式——就是柜子旁边放个Pad,用户扫码付款码后人工开柜。这个方案开发量小,但落地有一个致命问题:计费无法自动化。羽毛球柜的商业模式一半靠售卖商品,一半靠柜格占用费,人工记时既不准确,还会引发投诉。改成小程序扫码开柜后,开门事件、关门事件都自动上报云端,计费从秒级开始算,这才是真正意义上的“无人”。
通信上用4G而不是WiFi或蓝牙,原因也很实际:羽毛球馆的WiFi覆盖经常不稳定,用户手机连的WiFi和柜子连的WiFi未必互通;蓝牙则要用户走到柜子跟前靠近配对,体验完全不符合“共享”的高效预期。4G模块插一张物联网卡,上电即联网,跨场馆部署不用依赖任何现场IT配置,这是无人设备规模化复制的前提。
2. 总体架构与核心模块设计
这套源码的架构不算复杂,但每层都要为“无人”两个字服务。我把整体分成四层,每层之间通过接口严格隔离,这样任何一个场馆的设备出问题,都不会拖垮整个系统。
| 层级 | 组成 | 职责 |
|---|---|---|
| 设备层 | 电控锁、柜门磁簧开关、4G DTU、主控板 | 物理状态采集、锁控执行 |
| 接入层 | MQTT Broker、HTTP API网关 | 设备上报、指令下行、鉴权 |
| 业务层 | 订单服务、计费服务、支付服务、库存服务 | 核心业务逻辑、状态流转 |
| 管理端 | Web后台、小程序管理端 | 运营配置、数据看板、异常处理 |
2.1 技术栈与分层:设备层、接入层、业务层、管理端
设备端不推荐直接用带完整Linux系统的工控机做大批量部署,成本高、故障点多。实际项目里用ESP32或STM32主控加4G DTU透传模块就够,源码里设备端代码要写得尽量轻,只做三件事:上报状态、接收指令、回传结果。主控板通过GPIO控制电控锁继电器,通过光耦读取门磁开关信号,通过串口和4G模块交互。
接入层的核心是消息通道。有了MQTT,设备能保持长连接,云端可以随时下发开门指令。但这里有个经验:不要把关键业务指令完全依赖MQTT。我就遇到过一次EMQ集群抖动,结果所有柜子同时失联,现场用户全堵在柜前。后来设计成“MQTT做心跳和状态上报,HTTP接口做指令下发兜底”,设备会定时通过HTTP拉取“待执行指令”,相当于上了双保险。
业务层用Spring Boot是比较稳妥的选择,有成熟的生态,对支付SDK的兼容性也最好。如果整个项目是小团队启动,也可以考虑微信云开发或Serverless架构,成本更低,但要注意云开发对设备MQTT长连接的支持比较弱,最终还是要为设备接入单独买一台轻量服务器。
管理端用Vue或React其实没太大区别,关键是表格渲染性能和权限控制。库存、订单、对账页面都是大数据量的表格场景,建议用虚拟滚动,否则补货员在后台翻订单时页面会明显卡顿。
2.2 订单状态机:无人共享业务的中枢
如果你只是做个“扫码付款出货”的售货机,订单状态无非就是待支付、已支付、已完成。但多了柜门开合、计费结算这些物理动作后,状态必须细化到能表达“物理过程”。我把订单状态设计成这几个:
| 状态 | 触发事件 | 允许流转到 |
|---|---|---|
| CREATED(已创建) | 用户扫码选中柜格 | PENDING_PAY |
| PENDING_PAY(待支付押金/授权) | 前端调起支付 | OPENING(成功) |
| OPENING(开门中) | 云端下发开门指令 | OPENED(门磁确认) |
| OPENED(已开门使用中) | 门磁断开 | CHARGING(开始计时) |
| CHARGING(计费中) | 用户关闭柜门 | FINISHED(生成账单) |
| FINISHED(待支付尾款) | 门磁闭合确认 | PAID(完成扣款) |
| PAID(已完成) | 支付成功 | 终态 |
| ABNORMAL(异常) | 超时未关门/门未关好 | FINISHED(人工介入) |
为什么不直接“支付押金后开门,关门后扣款”一步到位?因为柜门物理状态不是瞬时稳定的——门磁闭合可能因为用户用力不够而虚接,也可能因为锁舌卡住而出现“看似关了实际没关”。状态机里加入OPENING和FINISHED这两个状态,本质上是在给物理世界留出“缓冲确认”的时间窗口。
2.3 通信协议与锁控指令:用什么保证不误开店门
柜子的锁是直接对应财产的,指令设计必须带防重放和签名。设备端和云端约定一套简单但有效的私有协议,我用的通信协议帧大致长这样:
{ "device_id": "SH-LH-001", "cmd": "open_cell", "cell_id": 3, "nonce": "7f9c3e2a", "timestamp": 1712102400, "sign": "a3f8...(HMAC-SHA256)" }这里有几个实战细节:
- nonce加时间戳:设备端会缓存最近两分钟内收到的nonce,如果云端下发的指令nonce重复,直接拒绝执行。没人会黑这种柜子,但防止错误重发比防黑客更重要——我就遇到过网络重试导致同一条开柜指令投递了两次,柜门开了两次。
- sign算法:用设备SN作为密钥的HMAC-SHA256,设备固件内置SN,云端存储SN哈希。这样即使有人截获了通信报文,也无法伪造其他设备的重开指令。
- 指令幂等:开柜指令必须带业务订单号,设备端存储最近十条已执行订单号,收到重复指令时返回“已执行”而不是再开一次门。
设备执行完指令后的回执也要规范,必须包含门磁的实际状态值。云端收到回执后,要拿这个值去比对预期的“开”状态,不一致就走异常流程。
3. 关键源码实现:计费、支付与设备联动
项目真正容易出bug的是计费引擎和异常状态恢复。这节我就把计费、支付、掉线重连三个核心代码模块的设计思路和实现要点讲透。
3.1 分时计费引擎:按分钟计费与封顶设计
羽毛球售卖柜的计费内容是“商品费用+柜格占用费”。商品费用比较简单,商品ID对应的价格表;柜格占用费则按分钟累加,但必须设置封顶。没有封顶逻辑的计费引擎,一定会出现用户忘记关门、第二天开机账单高到离谱的客诉。
计费引擎的核心抽象是计费规则接口,不同场馆可以配置不同费率。举个例子:
public class CellFeeEngine { // 费率配置:前30分钟1元/分钟,超过30分钟0.5元/分钟,单日封顶20元 private static final int FREE_MINUTES = 0; private static final int STAGE1_MINUTES = 30; private static final double STAGE1_PRICE = 1.0; private static final double STAGE2_PRICE = 0.5; private static final double DAILY_CAP = 20.0; public BigDecimal calc(long startTime, long endTime) { long totalMinutes = (endTime - startTime) / 60000; if (totalMinutes <= FREE_MINUTES) { return BigDecimal.ZERO; } long stage1Used = Math.min(totalMinutes, STAGE1_MINUTES); long stage2Used = totalMinutes - stage1Used; BigDecimal fee = BigDecimal.valueOf(stage1Used) .multiply(BigDecimal.valueOf(STAGE1_PRICE)) .add(BigDecimal.valueOf(stage2Used) .multiply(BigDecimal.valueOf(STAGE2_PRICE))); // 封顶判断 if (fee.compareTo(BigDecimal.valueOf(DAILY_CAP)) > 0) { fee = BigDecimal.valueOf(DAILY_CAP); } return fee; } }封顶逻辑不是简单算一个最大值就完了。真实落地时要处理跨天计费,我的做法是:每日凌晨三点跑一次批量任务,把所有仍在CHARGING状态的订单做一次“快照结算”,把已产生的费用累加进账单,然后把计费周期重置为新的一天。这样即使用户连续占用三天,账单也是一天一天累积的,而不是一个巨无霸数字。
计费引擎还有一个细节:时间以设备上报的门磁事件为准,不以前端展示时间或服务器本地时间直接算。门磁闭合事件上报可能有延迟,所以要记录两个时间点:门磁断开的上报时间和门磁闭合的上报时间。如果两次上报之间超过两小时,要自动标记订单为异常,推给人工确认。
3.2 支付与结算流程:押金模式与部分退款
无人共享设备最怕的就是“用户只付了货钱,柜子被人占着不还”。所以支付侧我采用“预授权或押金+结算退款”的模式,而不是简单的先付全款。具体流程是这样:
- 用户扫码选中柜格,后端先创建一笔PENDING_PAY订单。
- 用户调起微信支付,支付一笔押金(金额通常是商品最高价+单日封顶占用费,比如60元)。
- 支付回调确认后,云端下发开门指令。
- 用户拿走球,关闭柜门,门磁闭合上报,订单进入FINISHED。
- 计费引擎算出最终费用,云端调用支付平台的“部分退款”接口,把剩余押金退回用户微信。
- 退款到账后,订单置为PAID。
这套流程的关键是退款金额的精度。比如押金60元,商品费32.5元,占用费0.8元,应退26.7元。此时退款接口传的分单位必须是整数,数据库里金额字段统一用“分”存储,计算时用BigDecimal避免浮点误差。我就被“0.1+0.2不等于0.3”这类问题坑过,后来明确规定金额计算一律以分为单位转long。
如果用户的微信支付里开了免密支付,可以做成“免押金模式”:下单时不实际冻结资金,关门后直接扣款。这种模式用户体验更好,但需要在支付平台开通对应的能力,而且后端必须做好风控——比如同一用户短时间内多次免密下单,要触发人工审核。
3.3 设备掉线重连与指令幂等:容忍物理世界的不确定性
4G信号再稳定也会有抖动,尤其羽毛球馆很多在地下空间或钢结构场馆里,信号衰减明显。设备掉线是常态,不是异常。所以源码设计里必须做好两件事:心跳保活和离线补单。
心跳保活我用的方案是MQTT遗嘱消息加30秒心跳。设备每30秒上报一次心跳,云端记录lastSeen时间,超过90秒没收到心跳,云端标记设备离线。MQTT遗嘱消息这里很实用——设备突然断电时,遗嘱消息会立即发给云端,云端马上知道哪台柜子离线了,可以在后台弹告警。
离线补单则要处理“用户已经扫码开了门,但设备断网”的极端情景。这里的设计是:设备本地缓存订单编号和开锁记录,恢复网络后立即上报缓存事件。云端收到这些事件时,不能简单拒绝或接受,要检查本地订单状态:
if (order.getStatus() == Status.OPENING) { // 云端还没确认开门成功,设备补报“已开门”,正常流转 order.openConfirm(deviceReport); } else if (order.getStatus() == Status.CHARGING) { // 云端认为已经在计费中,设备补报“已关门”,结束计费 order.finish(deviceReport); } else { // 其他状态,直接推异常工单 notifyAdmin(order, deviceReport); }这种“对账式”的补单逻辑,比单纯重发指令靠谱得多。因为网络恢复瞬间,云端和设备端各自持有的状态可能已经不一样了,必须靠设备本地的物理事实(门开了、门关了)来对齐。
4. 管理后台、对账与无人化运维
门店里的售货机可以几天不管,但无人共享柜涉及“柜格存商品+用户拿走+占用计费”三层资产流转,后台如果做不好对账和库存管理,账目一定会乱。这一节讲管理后台和运维系统的核心设计。
4.1 库存与补货预警:每格商品都要和订单绑定
传统售货机的库存是货道级的,某货道剩几瓶水。羽毛球柜的库存必须做到“柜格级”——即每个格子里放的是什么商品、什么时候放的、保质期到什么时候,都要在后台看得清清楚。补货员补货时,打开柜格,放到对应的空位,可以扫码或者手动选择商品,后台自动更新库存。
库存预警不能只按“数量小于阈值”来触发,更有效的指标是“预计可售天数”。后台统计每个柜格近七天的平均出库速度,估算当前库存还能撑几天,低于三天就生成补货任务。这种指标对连锁运营特别重要——补货员跑一趟,要把即将缺货的柜子一次补完,而不是东一个西一个。
4.2 多渠道对账模型:微信+支付宝双收单
垄断单一支付渠道在无人共享领域是自找麻烦。用户微信里有钱就微信付,支付宝里有优惠就支付宝付,两个渠道都得支持。但双渠道会带来对账复杂度:微信支付账单、支付宝账单、我们自己的订单流水,三个数据源要能对上。
我的对账方案是:每日凌晨拉取两个支付平台的账单文件,和本地订单流水做逐笔比对。比对结果是三类差异:
| 差异类型 | 常见原因 | 处理方式 |
|---|---|---|
| 支付平台有记录,本系统无订单 | 支付回调网络异常,未通知到业务系统 | 自动补单,以支付平台为准 |
| 本系统有订单,支付平台无记录 | 风控拦截或用户取消,但本地状态未更新 | 自动关单,标记用户端异常 |
| 金额不一致 | 退款状态未同步,或订单被部分退款 | 拉取退款详情,二次同步 |
对账不用做到实时,T+1足够。但“差异账单”每个星期必须人工复核一次,长时间堆积会让后续审计很痛苦。我踩过的坑是退款接口偶发超时,本地显示退款成功,支付平台实际没退成功,这会造成押金悬空。后来加了“退款对账”的定时任务,每两小时扫一次所有已下单未退押金的订单,主动查询支付平台的退款状态,不一致就自动重试。
4.3 远程运维与告警:让设备问题在用户发现前暴露
无人共享设备最致命的评价就是“坏了没人管”。要解决这个问题,靠投诉驱动肯定不行,必须让系统主动发现问题。我在管理后台里做了三种层级的告警:
- 设备离线告警:超过10分钟没心跳,推送到运营者微信。
- 柜门状态异常告警:柜门已经处于CHARGING状态,但连续三小时没有门磁变化,这种情况大概率是用户走了没关门或门磁故障,需要现场查看。
- 指令执行超时告警:云端下发开柜指令后,设备60秒内没有回执,可能锁控板死机或4G模块假死,触发自动重启继电器电源,重启后重新下发指令。
还应该预留一个“设备远程调试”入口:允许运营人员通过后台向指定设备下发ping指令、查询当前固件版本、查看最近10条设备日志。这个功能前期不显眼,但等设备量过了20台以后,会发现它能节省大量现场跑腿的时间。
5. 实战复盘与避坑清单
最后一部分是压箱底的内容,全部来自实际调试和商用的真实案例。如果你是准备基于这套源码做二次开发或直接部署,这部分的每一句话都值得先看完再动工。
5.1 常见问题速查表:无人共享球柜的典型故障
| 现象 | 根本原因 | 排查与修复 |
|---|---|---|
| 用户扫码后无法开门,但支付成功 | 支付回调延迟,订单状态未流转到OPENING | 检查支付回调日志;临时让运营在后台手动把订单置为OPENING并触发重发指令 |
| 柜门显示开启,但订单一直不进入计费 | 门磁开关信号没上报 | 检查门磁模块是否松动,确认门磁事件上传的topic是否正确 |
| 用户关了门,但账单金额持续上涨 | 门磁虚接或锁舌弹回导致门状态伪闭合 | 增加“门磁状态连续N秒稳定后才算闭合”的防抖校验 |
| 设备在线但指令下发无响应 | 主控程序卡死或串口通信堵塞 | 远程重启设备,检查DTU供电是否稳定;优先加看门狗 |
| 押金退款迟迟不到账 | 支付平台退款单状态未同步 | 查退款对账任务是否正常,手动触发退款重查接口 |
这里挑一个最隐蔽的坑:门磁防抖。刚装上去时门磁一碰就上报,看似灵敏,但实际运行中,铁皮柜门在关门瞬间会因为震动产生几十毫秒的信号抖动,如果不加防抖逻辑,系统会收到“关-开-关”的连续状态变化,订单状态会被反复横跳。后来我加了150毫秒的软件防抖窗口,门磁事件必须稳定保持这个时间才上报,问题才彻底解决。
5.2 上海落地特有经验:场地、环境与运营细节
上海这个城市的项目环境和三四线城市完全不一样,光靠源码可跑通远远不够,落地时这几个点是一线城市特供的坑:
电源取电难。不少羽毛球馆位于商业综合体或社区体育中心,场馆的公共区域插座少,而且很多插座归属于物业不同的电表回路。装柜子前一定先去物业确认电容量和计价方式,不然季末对不上电费账单就尴尬了。我试过最快也要两个星期才能走完物业的用电申请流程,项目排期一定要预留。
地下场馆信号问题。上海的很多社区文体中心羽毛球馆建在地下或半地下,4G信号衰减明显,尤其人一多,信号更差。设备端选型的DTU模块要选支持外接天线的型号,把天线延长到场馆顶部或门口方向,信号质量能从两格提升到满格,这个投入几十块钱,收益非常明显。
防雨和防潮。上海的梅雨季和台风季不是开玩笑的。如果是室外半开放式场地,机柜必须做防水结构,门缝处加密封条,主板区域涂三防漆。我第一台室外样机就是梅雨天进水短路,换主板花了三天,损失比想象中大得多。
羽毛球商品的特殊性。羽毛球本身有易压变形的问题,柜格内部最好有海绵或卡扣固定,避免用户在开柜时球桶滚落摔压。另外,正品球和仿冒球的价差很大,后台要支持批次码管理,补货时扫码登记,出现客诉时能追溯到某一批货的来源。
5.3 从源码到商用的工程化路径建议
拿到这套“上海无人共享羽毛球售卖软件源码”后,别急着直接上生产。我建议按以下路径推进:
- 先跑通流程再谈优化。用一台测试柜加两个测试账号,把“下单→开门→关门→结算→退款”这条完整链路跑十遍以上,特别注意异常中断场景,比如开门过程中拔掉设备电源,等一会儿再恢复。
- 申请软著和技术备案。无人共享设备涉及支付、设备联网和数据处理,小程序上线需要软件著作权证明,服务器域名需要ICP备案,这些前置工作要提前规划。
- 小规模试点。找一家配合度高的球馆,放两台柜子试运行一个月,重点记录设备掉线率、客诉类型和退款成功率,用真实数据反过来优化源码里的阈值和流程。
- 再谈扩张。把试点稳定后的整套配置(设备硬件选型、后台参数、补货流程)做成标准化文档,再往其他场馆复制。复制时要特别注意每家场馆的网络环境和取电方式都不完全一样,给现场部署人员准备一张标准化的“现场勘测表”能省很多事情。
无人共享羽毛球柜这个细分赛道,技术门槛其实不高,真正的门槛在硬件稳定性和现场运维能力。源码只是起点,把设备、云端、人的协作理顺才是核心竞争力。
我个人在实际操作中的体会是,这一整套项目最难调试的不是某一个代码模块,而是软件逻辑和物理世界的对齐。你在代码里写“门已关闭”,现实里门可能就是虚掩着;你在后台看到“计费中”,现场可能连用户都已经走了。所以做这一类无人共享设备的开发,一定要养成“把每一次物理事件都当做不可靠输入”的习惯,所有关键状态都要加确认、加超时、加人工兜底。多留这些心眼,现场就会少很多麻烦。这套源码开发过程中沉淀下来的状态机设计、对账模型、设备补单策略,拿去做其他无人共享场景也完全通用。后续如果要做会员体系和包月打球服务,这套基础架构也能平滑扩展,算是当时留了个还不错的底子。