首页 / 资讯中心 / 文章详情

无人共享羽毛球售卖软件源码实战:从架构设计到核心代码

无人共享羽毛球售卖软件源码实战:从架构设计到核心代码 ★ FEATURED ARTICLE
很多人第一次听到“无人共享羽毛球售卖软件源码”这个项目第一反应都是这不就是个自动售货机吗哪有什么技术含量。但真把项目拆开你会发现它完全不是超市门口那种投币买饮料的机器能比的。羽毛球这个品类很特殊单桶价格高、体积大、容易被人为顺走更重要的是球友们往往是在球馆里打到一半发现球没了这时候要的不是一台机器而是一个能在30秒内完成扫码、开柜、取球、自动扣款的完整闭环。围绕这个需求整套软件源码的复杂度上来了它要管设备、管支付、管库存、管异常订单还要能扛住晚高峰十几个球友同时开柜的压力。这篇文章我就把整个项目从设计到落地的全过程拆给你看包含核心源码思路、表结构、接口设计和踩坑记录希望能帮你少走弯路。1. 这个项目的本质无人共享售卖到底在卖什么1.1 一个羽毛球爱好者的真实痛点先讲一个场景。周末晚上八点上海某羽毛球馆四片场地全满。A场地的球友打了三局一筒羽毛球已经打废了大半有人提议再买一筒但球馆前台的小卖部九点关门自动贩售机里只有饮料附近的便利店走过去要十五分钟。这时候如果场馆角落有一台羽毛球自助售卖柜扫码开柜拿一筒球系统自动扣款关柜走人全程不到半分钟这个痛点就直接被解决了。但这里有一个关键点羽毛球不是标准化的日用品它有明显的“临时性”和“冲动性”消费特征。球友不会专门为了买一筒球跑一趟但会在急需的时候为了一筒球支付溢价。所以这个售卖设备不能像传统售货机那样把商品摆在货道里每一桶球占一个货道一个柜子只能放十几桶。更合理的方案是做成智能柜形态柜门打开用户自己拿系统根据拿走的数量自动计费。这种模式在运营上有一个专有名词叫“开柜自取”它能让一个普通大小的柜子装下四五十筒球同时设备成本还比弹簧货道机便宜。正因为是“开柜自取”软件系统的核心逻辑就和传统售货机完全不同了。传统售货机需要精确控制电机出货系统关心的是货道状态而智能柜方案系统关心的是柜门状态、拿取动作和库存变化。这直接决定了后面的架构设计和数据库表结构也决定了源码里哪些模块最值得写。1.2 从需求倒推软件系统要解决哪三件事当你把一个无人共享羽毛球售卖项目从商业想法转成技术方案时会发现所有需求最终都收敛到三件事上点位运营、交易过程、库存损耗。先说点位运营。你的设备分布在不同球馆、社区运动中心、园区每台设备有自己的编号、地理位置、负责人、补货周期。软件后台必须能看到每台设备的实时状态、今日营业额、剩余库存否则运营人员根本不知道哪台柜子该补货了、哪台柜子离线了。这块对应的是设备管理模块和运营管理后台。然后是交易过程。用户扫柜身上的二维码小程序弹出当前柜子的商品信息用户确认后柜门弹开用户拿走商品关上门系统自动从微信支付扣款。整个过程涉及小程序端、服务端、硬件设备端三方通讯任何一个环节超时、失败、断网都会产生脏数据。这块是整套源码中最核心的部分也是我后面会重点拆解的内容。最后是库存损耗。无人值守意味着你没办法百分百防止有人多拿、错拿、故意不关门。所以系统必须有异常订单机制超时未关门自动告警、库存与实际盘点不符自动生成差异单、黑名单用户限制购买。这些功能看起来不起眼但直接决定项目能不能持续运营因为无人售卖行业的利润本来就薄一次人为损耗可能吃掉好几单的利润。明确了这三件事再去选架构、写源码思路会清晰得多。很多人在网上找“无人共享售卖软件源码”上来就盯着支付对接、小程序界面看其实真正的核心竞争力恰恰在设备调度和异常处理上。2. 整体方案从硬件到云端的一次性打通2.1 端到端的系统架构分层这套系统我从上到下分成五层用户触达层、业务服务层、设备接入层、硬件控制层、数据存储层。用户触达层主要是微信小程序承担扫码、商品展示、支付确认、订单查询的功能。为什么选微信小程序而不是独立App原因很简单用户不需要安装任何东西微信扫一扫就能用转化路径最短而且微信支付和小程序天然打通免去了绑定银行卡、充值的步骤。羽毛球球友的年龄跨度很大从大学生到四十多岁的企业高管都有小程序对这类非互联网核心人群的友好度是最高的。业务服务层是整套系统的中枢我用的是Spring Boot框架单体应用起步。可能有朋友会问为什么不直接上微服务我的观点是一个区域内的几百台设备、每天几千单的交易量单体应用完全撑得住拆分微服务反而引入分布式事务和运维复杂度对早期项目来说是负资产。服务层内部按业务域分成几个模块设备管理、商品库存、订单中心、支付网关、用户会员、运营后台每个模块之间通过内部接口调用保持逻辑清晰。设备接入层承担着业务服务和硬件设备之间的通信任务。智能柜的主控板通过4G网络连接到服务端的设备网关服务端下发开锁指令设备上报状态和心跳。这里我选择了MQTT协议理由是它能够满足大量设备的长连接维护需求同时支持离线消息的缓存。设备端用一个简单的JSON协议指令包含指令ID、设备编号、指令类型、时间戳防止指令重复执行。底层就是MySQL数据库加Redis缓存。MySQL存订单、设备、商品、用户等核心业务数据Redis用来处理高并发场景下的库存扣减、分布式锁、接口幂等等问题。羽毛球售卖的高峰期非常集中一般是晚上七点到十点这个时段可能出现多个用户同时开同一台柜子的情况Redis在这里发挥的作用比数据库大得多。2.2 技术栈怎么选才省心直接给出一份经过验证的技术栈清单都是我实测可用、社区活跃度高的组合。层次技术选型说明后端框架Spring Boot 2.7 MyBatis-Plus开发效率高生态成熟招人容易数据库MySQL 8.0稳定可靠事务支持好适合订单类场景缓存Redis 6.x库存扣减、幂等控制、分布式锁消息队列RabbitMQ异步处理支付回调、告警通知可替换为RocketMQ设备通信MQTTEMQX海量设备长连接支持遗嘱消息和离线消息小程序端uni-app一套代码可编译到微信、支付宝多个端管理后台Vue 3 Element Plus运营人员使用报表和基础管理功能这套组合最大的优势是每个环节都有大量现成案例。比如你在网上搜“Spring Boot智能柜源码”能搜到很多参考实现MyBatis-Plus把单表CRUD的代码量压缩到极低开发一个后台的时间能从两周缩短到三天。MQTT这块用EMQX做broker它是开源的单机可以扛十万设备连接对早期项目来说绰绰有余。有一个选型上的坑要提醒你不要选过于冷门或者过于“高大上”的组件。我看到有人为了追求性能在设备端引入Tars或者是gRPC结果写起来费劲调试还找不到参考资料。无人售卖系统的瓶颈从来不在框架本身的性能而在业务流程的健壮性。框架越主流你能找到的踩坑经验越多项目推进越快。2.3 设备端通讯与心跳保活设备端是很多软件开发者容易忽略的环节。因为你写服务端代码跑在云服务器上写小程序跑在用户手机里都能直接看日志但设备端是跑了几个月才会发现问题的。我在设计设备通讯时第一个原则就是心跳必须短、指令必须带ID。心跳间隔我设置为30秒设备上报当前状态包括柜门开关状态、温度、信号强度、电量服务端收到心跳后更新设备的在线状态和最后上报时间。如果服务端连续三分钟没收到某台设备的心跳就判定设备离线运营后台自动告警并且在小程序端对这台设备标记为不可用。这里有一个容易被忽略的细节由于羽毛球场馆的4G信号通常不稳定设备主控板在信号弱的时候会出现心跳上报的延迟或者丢失。所以我要求设备端做“心跳补偿”每次上报失败就在下一次上报前补发一次服务端通过设备编号和时间戳去重保证状态不误判。指令下发的设计更关键。服务端开具一个“开锁命令”命令里携带一个全局唯一的指令ID通过MQTT发送给设备。设备收到后执着地执行执行完把结果上报。如果网络异常导致上报失败设备端会缓存最近一百条未确认的执行结果网络恢复后批量上报。服务端通过指令ID做幂等重复上报不影响业务状态。这套机制看似简单但真正避免了大量“设备执行了但系统以为没执行”的事故。3. 核心业务闭环从扫码到关柜的完整链路3.1 主流程用户买一桶球要经过哪些环节整个购买流程我按十二个步骤拆解每一步都在源码里有对应的模块和状态记录。第一步用户用微信扫描柜身上的二维码二维码里面包含了设备编号和柜子所属的商户号。第二步小程序调用服务端的商品查询接口获取当前柜子的商品列表、库存数量和售价展示给用户。第三步用户点击“立即购买”选择商品数量这里服务端会先做一次库存预占防止用户下单之后发现没货。第四步用户确认支付方式这里我采用的是“先下单后支付”模式用户先锁定库存再发起微信支付支付成功后服务端才下发开锁指令。第五步服务端接收微信支付回调验证签名、核对金额、确认订单状态然后触发设备开锁。第六步到第九步是硬件交互环节。服务端调用设备接入层下发开锁指令指令到达设备后电磁锁打开柜门弹开。设备端检测到柜门状态变化上报“已开门”。用户拿走羽毛球桶后手动关门设备检测到关门状态上报“已关门”。如果用户超过三分钟没有关门设备会每隔二十秒上报一次警告提醒现场人员处理。第十步服务端根据柜门关闭信号将订单状态改为“待核销”并触发库存变更操作将商品从锁定状态转为售出状态。第十一步是结算。订单完成库存核销后系统发起最终的订单确认操作从用户微信支付账户中完成扣款。这一步我把它设计成在下发开锁指令前先完成扣款其背后有理无人值守场景下钱先到了才给货才能把跑单风险降到最低。第十二步用户在小程序收到购买成功通知包含扣款金额、商品信息、取货时间同时用户可以在订单中心查看历史订单、申请售后、反馈问题。整个过程看起来顺畅但每一个步骤都可能出现异常。真正能让这套系统在无人值守的环境里跑得稳的不是主流程本身而是主流程边上的那些“岔路”支付成功了但设备没开门、用户拿了球但是没扣款、网络断了订单状态丢失。这些情况我在第四章节会专门讲这里先记在心里。3.2 数据库设计订单、设备、库存、流水数据库是整套源码的地基我这里直接给出4张核心表的建表思路你可以直接用也可以根据业务调整。第一张是设备表device字段包括设备ID、设备编号、所属场地名称、场地地址、设备状态在线/离线/故障、柜门状态、信号强度、最后心跳时间、创建时间。设备编号是唯一索引后续所有的指令下发、订单记录、库存记录都通过这个编号关联设备。第二张是商品表product字段包括商品ID、商品名称、规格一桶12只装还是散装、指导售价、成本价、图片地址、状态。这里有一个很关键的设计每个商品的库存不是由商品表管理的而是由设备跟商品的关联表 device_product 管理里面有设备ID、商品ID、库存数量、警戒库存。这样做的好处是一台设备可以动态调整售卖哪些商品运营人员后台改一下关联配置就可以不需要改代码。第三张是订单表orders这是整个系统最核心的表字段包括订单ID、订单编号、设备编号、用户OpenID、商品ID、商品数量、应付金额、实付金额、订单状态创建/支付成功/开锁中/已开门/待关闭/已完成/已关闭/售后退款、指令ID、支付流水号、创建时间、支付时间、关门时间。订单状态字段一定要设计成数字枚举后面做统计查询的效率会高很多。第四张是资金流水表payment_log字段包括流水ID、订单编号、用户OpenID、支付渠道微信/支付宝、支付方式JSAPI/小程序支付、交易金额、商户单号、回调通知内容、回调处理状态、处理时间。所有和微信支付相关的数据都记录在这张表里方便对账和排查问题。注意这个表是按月增长比较快的建议在创建的时候就设计好按月分表方案。下面是建表SQL的核心片段我把订单表和设备商品关联表的建表语句直接列出来CREATE TABLE orders ( id bigint(20) NOT NULL AUTO_INCREMENT, order_no varchar(64) NOT NULL COMMENT 订单编号, device_no varchar(32) NOT NULL COMMENT 设备编号, openid varchar(64) NOT NULL COMMENT 用户微信openid, product_id bigint(20) NOT NULL COMMENT 商品ID, quantity int(11) NOT NULL DEFAULT 1 COMMENT 数量, pay_amount decimal(10,2) NOT NULL DEFAULT 0.00 COMMENT 应付金额, real_amount decimal(10,2) NOT NULL DEFAULT 0.00 COMMENT 实付金额, status tinyint(4) NOT NULL DEFAULT 0 COMMENT 订单状态, cmd_id varchar(64) DEFAULT NULL COMMENT 开锁指令ID, payment_no varchar(64) DEFAULT NULL COMMENT 支付流水号, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, pay_time datetime DEFAULT NULL, close_time datetime DEFAULT NULL, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no), KEY idx_device_no (device_no), KEY idx_openid (openid) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT购买订单表; CREATE TABLE device_product ( id bigint(20) NOT NULL AUTO_INCREMENT, device_no varchar(32) NOT NULL, product_id bigint(20) NOT NULL, stock int(11) NOT NULL DEFAULT 0, warning_stock int(11) NOT NULL DEFAULT 5, status tinyint(4) NOT NULL DEFAULT 1, PRIMARY KEY (id), UNIQUE KEY uk_device_product (device_no, product_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT设备商品库存表;3.3 订单状态机与超时兜底订单状态机是无人共享售卖系统里最容易写崩的部分我见过很多新手直接把状态写成一堆if-else最后找bug找到崩溃。正解是先梳理一张完整的状态流转图再把流转规则固化到代码里。订单初始状态是CREATED用户下单成功但还未支付。支付回调进来后校验通过状态转为PAID这时候服务端开始下发开锁指令状态随之转为UNLOCKING等待设备回报。设备上报已开门后状态转为OPENED此时用户实际可以取货了。设备上报已关门后状态转为CLOSED服务端完成库存核销后状态转为FINISHED订单结束。中途如果用户主动取消支付、或者超过十五分钟未支付状态转为CLOSED订单关闭。围绕状态机我设置了三个兜底任务。第一个是支付超时关闭任务每五分钟扫描一次超过十五分钟未支付的订单将其关闭并释放锁定的库存。第二个是开锁超时处理任务对于已经支付成功但设备迟迟不上报开锁成功的订单服务端会自动重试三次下发开锁指令中间间隔十秒如果仍然失败就自动发起退款同时生成设备故障工单。第三个是关门超时告警任务设备开门后超过三分钟仍未关门服务端会推送告警信息给运营人员同时发送短信给现场联系人员。这三个兜底任务在源码里对应的是三个定时任务用Spring的Scheduled注解就可以实现。要注意的是定时任务要加分布式锁避免多实例部署时同一个订单被多个实例重复处理。我在实际项目里用Redis的setnx命令做分布式锁锁的key是订单号加任务类型过期时间设为三十秒任务执行完主动释放锁。4. 源码模块拆解四个关键代码片段直接抄4.1 开锁命令服务端怎么控制硬件先看一下服务端下发开锁指令的核心方法这是整个无人售卖系统的关键接口之一。用户支付成功后前端小程序会轮询订单状态当订单状态变成PAID前端会调用一个确认开锁的接口服务端收到请求后生成开锁指令并投递到MQTT队列。Service public class DeviceCommandService { Autowired private MqttGateway mqttGateway; Autowired private StringRedisTemplate stringRedisTemplate; public String sendUnlockCommand(String deviceNo, String orderNo) { String cmdId UUID.randomUUID().toString().replace(-, ); JSONObject payload new JSONObject(); payload.put(cmdId, cmdId); payload.put(deviceNo, deviceNo); payload.put(orderNo, orderNo); payload.put(type, UNLOCK); payload.put(timestamp, System.currentTimeMillis()); String topic device/command/ deviceNo; mqttGateway.sendToMqtt(topic, payload.toJSONString()); // 记录指令ID后续设备上报开锁结果时用来匹配 stringRedisTemplate.opsForValue().set( device:cmd: cmdId, orderNo, Duration.ofMinutes(10) ); return cmdId; } }这段代码的核心思路是下发指令后立即返回不等待设备回复采用异步确认的模式。指令ID在Redis缓存了十分钟设备上报执行结果时用cmdId查询对应的订单号再更新订单状态。这里要注意一下topic的设计我采用的是“设备维度”的topic每台设备一个topic避免多台设备混淆。设备端订阅自己的专属topic不会收到无关指令。设备端上报开锁结果时服务端有一个对应的处理器伪代码如下public void handleDeviceReport(JSONObject report) { String cmdId report.getString(cmdId); String deviceNo report.getString(deviceNo); Integer eventType report.getInteger(eventType); // 1 表示开锁成功2 表示已开门3 表示已关门 String orderNo stringRedisTemplate.opsForValue().get(device:cmd: cmdId); if (orderNo null) { log.warn(cmdId:{} 已过期或不存在, cmdId); return; } // 根据eventType更新订单状态 orderService.updateOrderStatusByEvent(orderNo, eventType); }4.2 支付回调验签、防重、幂等三件套微信支付回调是无人售卖系统里最容易出安全事故的接口处理不好要么被伪造通知刷单要么因为重复通知导致订单状态错乱。我总结为“验签、防重、幂等”三件事。验签环节微信支付回调通知里有headers中的Wechatpay-Signature、Wechatpay-Timestamp、Wechatpay-Nonce等参数服务端需要用微信支付平台证书对回调消息体进行验签。Spring Boot里我用wxsdk的Java库核心代码是初始化一个Config对象传入商户号、API密钥、商户证书序列号、私钥再调用通知解析函数。这里提醒一句验签失败一定直接拒绝处理返回给微信一个失败应答微信会稍后重试。防重环节微信支付官方承诺回调通知会多次发送如果不做去重订单金额可能会被重复处理。我的做法是在处理回调之前先去Redis里查一下这个订单号是否已经处理过没有处理过才继续同时用setnx加锁锁的时间是十秒确保同一时间只有一个线程在处理同一个小程序订单。处理完成后将订单号写入Redis的已处理集合过期时间设置为二十四小时。幂等环节我要求订单表里的payment_no字段要有唯一索引即使极端情况下防重锁失效数据库的唯一索引也能挡住重复流水。这是最后一道防线非常管用。一个完整回调处理的核心结构如下RestController public class PayNotifyController { PostMapping(/api/pay/notify) public String payNotify(HttpServletRequest request) throws Exception { // 1.验签使用微信平台证书验证回调签名 WxPayService wxPayService new WxPayServiceImpl(); WxPayNotifyResult notifyResult wxPayService.parseOrderNotifyResult( request.getParameter(body), request.getHeader(Wechatpay-Signature), request.getHeader(Wechatpay-Timestamp), request.getHeader(Wechatpay-Nonce) ); String orderNo notifyResult.getOutTradeNo(); // 2.防重Redis锁 已处理标记 boolean lock stringRedisTemplate.opsForValue() .setIfAbsent(pay:lock: orderNo, 1, Duration.ofSeconds(10)); if (!lock) { return 正在处理中; } Object processed stringRedisTemplate.opsForValue().get(pay:done: orderNo); if (processed ! null) { return SUCCESS; } // 3.更新订单状态记录支付流水 orderService.markPaid(orderNo, notifyResult.getTransactionId()); // 4.标记已处理 stringRedisTemplate.opsForValue().set(pay:done: orderNo, 1, Duration.ofHours(24)); return SUCCESS; } }4.3 库存扣减一个原子化SQL解决超卖库存扣减是买卖类系统的高频考点无人售卖系统也不例外。多个用户同时下单数据库里的库存数据不能减成负数否则就会出现超卖。我使用的方案是“Redis预扣 SQL原子扣减”兼顾性能和准确性。具体流程是用户点击购买时先用Redis的decr命令将设备商品的库存key减1如果减完后结果小于0说明库存不足回滚库存并提示用户如果库存充足就创建订单。到了支付回调完成、订单进入已完成状态时再执行一次数据库层面的原子更新将device_product表的stock字段减掉实际下单数量同时更新销售流水表。数据库层面的原子更新SQL如下UPDATE device_product SET stock stock - #{quantity} WHERE device_no #{deviceNo} AND product_id #{productId} AND stock #{quantity}注意这个SQL里加了条件stock quantity所以即使Redis预扣失败、或者并发场景下数据异常数据库层面依然能拦住超卖。执行后如果影响行数为0说明库存不足需要走回滚流程。这个方案还有一个细节值得注意Redis预扣的库存是在用户下单时减掉的不是支付成功时。如果用户下单后不支付订单超时关闭需要在订单关闭任务里把Redis库存加回去。同样的支付成功但设备没开锁最终退款了也要加回去。这个补库存的动作是异步的通过消息队列发送防止主流程里因为补库存失败而阻塞其他操作。4.4 小程序端扫码开柜的交互逻辑小程序端麻雀虽小五脏俱全这里我重点讲扫码进入后的完整逻辑链代码结构可以直接照搬。小程序通过uni-app的uni.scanCode获取到二维码内容二维码内容是一个URL或者一个自定义的scheme比如“smartbox://device/0001”。在小程序捕获后解析出deviceNo然后调用服务端接口获取设备状态和商品列表。如果设备离线直接提示“设备暂不可用请更换设备”如果在线渲染商品列表。用户点击购买按钮后小程序先调用统一下单接口服务端返回支付参数然后调用uni.requestPayment拉起微信支付用户完成支付。支付成功后页面进入等待开锁状态每两秒轮询一次订单状态接口直到订单状态变为CLOSED表示已开门页面弹出“柜门已打开请取走商品”的提示。同时页面打开一个定时器如果超过三分钟没有关门页面会持续提示用户“请尽快关门”。这里有一个交互层面的坑要提醒很多开发者会在支付成功后直接调用开锁接口但微信支付的回调是异步的服务端开锁指令下发前必须确认支付回调已经处理完成。否则用户在支付成功后立刻点“开锁”服务端查订单状态发现还是CREATED就会拒绝开锁。我的解决方法是支付成功后小程序不立即调开锁接口而是进入轮询由服务端在支付回调处理完成后再主动下发开锁指令小程序端只用轮询的方式感知结果。这样既简化了小程序端逻辑也保证了不会出现“钱扣了没开门”的中间态。核心的小程序轮询代码结构如下let timer setInterval(async () { const resp await api.getOrderStatus(orderNo); if (resp.data.status 3) { // 3表示已开门 clearInterval(timer); uni.showToast({ title: 柜门已打开请取走商品 }); startCloseMonitor(); } else if (resp.data.status 9) { // 9表示订单已退款关闭 clearInterval(timer); uni.showToast({ title: 订单已取消款项已退回 }); } }, 2000);5. 部署运行与踩坑实录5.1 最低成本部署方案无人共享售卖项目在早期没有大规模铺开的时候没必要上太贵的云资源。我推荐一套低成本起步方案一台2核4G的云服务器运行Spring Boot应用和RabbitMQ一台1核2G的云服务器运行MySQL和RedisEMQX部署在同一个内网环境或用云厂商的MQTT插件。数据库读写分离、集群部署这些等设备数量超过五百台再考虑。服务器买好后部署顺序我建议是先MySQL和Redis再EMQX再RabbitMQ最后才是Spring Boot。Spring Boot的配置文件里把各个组件的连接地址区分开为了后期扩容方便数据源、Redis、MQ的连接参数全部放在application.yml里用环境变量引用。日常发布直接用Docker Compose编排一条命令部署所有服务升级时先备份数据库再滚动发布后端服务避免长时间停机。有一点特别提醒设备端主控板连接的MQTT broker地址必须是公网可达的不建议用内网穿透工具去暴露内网端口这样稳定性太差。EMQX如果用Docker部署记得把1883端口MQTT TCP端口和18083端口控制台端口映射出来并在云安全组里放通入方向规则。而且EMQX控制台的默认账号密码一定要改掉这个端口暴露在公网上不改密码等于把设备控制权送给别人。5.2 常见问题与排查速查表运营过程中最容易出现的问题就那么几类我直接整理成一张速查表方便现场对照排查。现象可能原因排查方法与处理方案用户支付成功但柜门没开设备离线或MQTT连接断开查看设备心跳上报时间若超三分钟未上报远程重启设备检查EMQX在线设备列表柜门开了但订单一直显示待关闭设备关门状态未上报检查关门传感器是否卡住优先触发手动关门指令再做订单状态修正用户扫码显示设备不可用设备离线或被运营后台人工下线登录管理后台查看设备状态确认设备在线后再重新上线订单重复支付扣款小程序端重复点击支付或支付回调重复处理检查orderNo幂等标记是否写入Redis核对订单表是否有多个支付流水后台库存和实物不符用户多拿、漏拿或设备开关门信号异常发起盘点任务以实物数量修正库存同时生成损耗工单支付回调迟迟没收到回调地址配置错误或防火墙拦截检查商户平台回调URL确认服务器出网入网策略放通443端口定时任务重复执行多实例部署未加分布式锁给定时任务添加Redis setnx锁或改用xxl-job集群模式这张表里的每一个问题我都实际遇到过。比如“支付成功但柜门没开”这个问题最让人头疼的地方在于它不是常态而是隔几天出现一次概率很低但每次出现都要人工介入。后来我把排查思路从“设备坏了”转到“MQTT连接被EMQX踢掉”才意识到是设备端长连接有内存泄漏运行几天后socket异常关闭EMQX端把旧连接清理掉了。设备端加了一个断线重连机制问题就彻底解决了。5.3 现场运营的几条经验源码之外我想分享几条运营层面的真实经验这些内容写代码的人容易忽略但直接决定项目能不能活。第一条经验是补货员一定要有自己的管理端小程序不能只依赖PC后台。补货员是在球馆现场工作的手机操作是最顺手的。我在管理端小程序里做了一个快速盘点模式补货员打开柜门清点实际库存输入实点数量系统自动生成库存差异单多出来或缺少的数量一目了然。这样做之后补货员更愿意执行盘点流程因为不用拿笔记录再回办公室录入电脑。第二条经验是异常订单的处理不能让运营人员手动操作数据库一定要在管理后台做出对应的操作界面。运营遇到退款问题直接在后台点“退款”按钮即可服务端会调用退款接口并自动更新所有状态全程无SQL操作。原因很简单运营人员不一定懂SQL让他们写SQL改数据改错一次造成的损失可能很大。第三条经验和商品结构有关。羽毛球售卖不要只卖筒装球建议搭配手胶、袜子这类低成本高毛利的小件商品。无人柜的容量是有限的筒装球占用的体积大毛利也不算特别高加上损耗单靠卖球可能撑不起一个点位的租金。我见过运营得好的点位都是把智能柜当成“球场邊的小卖部”来经营商品SKU在十种左右羽毛球是引流品手胶、护腕、运动饮料这些是利润品。这个结构调整之后单柜月营收能提升三成以上。另外一点是价格策略。无人值守场景的定价不必和便利店完全一致因为用户的核心诉求是“马上能拿到”而不是“便宜几块钱”。我采用的策略是比电商平台同规格的羽毛球贵两到三元这样既覆盖了设备折旧和损耗成本用户也不会觉得离谱。反而如果把价格压得太低利润被摊薄设备维护和点位拓展都会受限。最后我想说说对源码选型的看法。市面上确实能搜到很多“无人售卖软件源码”但不少是传统的弹簧货道机方案和智能柜的开柜自取模式在逻辑上有很大差异。你在选源码的时候重点看三个地方第一代码里有没有完整的设备指令下发与心跳上报机制第二订单状态机是否覆盖了支付成功未开锁、未关门、退款等异常分支第三库存扣减是否处理了超卖和释放的场景。这三条都满足的源码改造成羽毛球售卖场景会很快缺了任何一条你后面都要花大把时间去补坑。这套系统目前最让我满意的地方是它的故障自愈能力。设备离线了会自动告警支付异常自动退款库存差异自动生成工单运营人员在后台看到的就是一张清晰的任务清单而不是一堆需要人工判断的原始数据。做一个无人值守的项目软件的价值不在于实现了多少人能看到的界面而在于把那些没人盯着的时候可能发生的意外一件一件兜住。
阅读完成 · 觉得有帮助?
咨询建站