资讯动态

Java共享台球室无人系统与微信双端联动设计实战拆解

发布时间:2026/10/3 18:01:30 来源:尧图企业网站定制
项目拆解Java共享台球室的无人系统与微信双端联动做共享项目的朋友应该都有感触最近两年共享台球室、共享茶室、共享棋牌室这类“无人值守业态”突然多了起来。我一个做实体门店系统的朋友去年底接了个需求要给本地几家台球厅做一套“顾客自己开台、自己结账、老板远程看店”的无人化系统后端技术栈明确要求 Java前端必须跑在微信生态里——小程序做用户端企业微信或公众号后台做管理端两边数据实时联动。这套系统做完之后我把它拆开捋了一遍发现里头值得聊的东西其实不少从业务建模、计费结算、设备控制到微信生态的双端消息联动、订单状态机、异常恢复机制每一块都有坑。这篇就把整个项目的核心设计、关键代码逻辑和实操中踩过的坑完整梳理一遍给正在做或准备做同类共享业态的同学一个参考。1. 先从业务链路说起共享台球室到底要解决什么问题很多人一听到“共享台球室”就惯性思维地以为只是做个预约小程序其实远了。无人系统的核心矛盾在于没有服务员但顾客体验和经营安全都要有保障。所以第一步不是写代码而是把整个业务链路完整地跑一遍。1.1 一条完整的用户动线是系统设计的起点以我们这套系统为例我从顾客进店开始梳理用户扫码或搜索小程序进入场馆列表 → 选择球桌区分中式黑八、斯诺克、美式九球等→ 查看当前桌的状态空闲/使用中/已预约→ 选择计费模式按小时、按局、套餐券→ 下单支付押金或直接支付 → 系统下发指令给智能电控箱合闸通电 → 球桌灯光亮起、扫码器上电 → 计时开始 → 用户打球中途可临时暂停/续时/加购商品 → 结束离场时系统自动结算 → 多退少补 → 断电 → 生成账单。这条链路里每一个环节都对应着后台的一组合约逻辑下单时锁桌、支付时押金冻结、开台时设备指令、结束后清算退款。这本质上不是一个“页面开发”项目而是一个“状态流转资金安全硬件控制”的项目。1.2 双端联动中的两类用户、两种视角微信生态里做双端关键是分清两端用户各自关心什么。C端微信小程序用户旅程要尽量短最好打开就是“附近球房→选桌→开台”三步完成。实时状态、倒计时、结算金额是用户最敏感的信息。B端企业微信/管理后台老板要看的是门店实时占用情况、今日营收、异常订单、设备在线状态、远程强断电功能、会员储值流水。这里说的“双端联动”不只是两套界面而是同一套业务数据在两个端上以不同视角呈现并且状态变化能实时互相感知。比如用户在小程序上点击“结束计费”B端要立刻看到该桌变为空闲、订单变为已结算、营收增加一笔反过来老板在后台手动“强制断电”C端小程序要立刻收到“当前球桌已被管理员结束”的提示。这就对消息推送和状态同步提出了比较高的要求。1.3 功能优先级哪些是刚需哪些是自嗨做这种系统最怕功能堆砌。我整理了一个优先级矩阵跟做共享类项目的朋友分享优先级功能模块说明P0 必备在线选桌、按时计费、微信支付/退款、自动通断电、订单管理、异常订单处理没有这些门店开不了业P1 重要会员储值、优惠券、时段定价、临时暂停/续时、语音提醒直接关系到复购和客单价P2 增值在线预定、比赛模式、积分商城、数据大屏有精力再做不是冷启动的关键我见过一些同行一上来就做积分商城、社区约球核心的开台结账反而做得稀烂——设备指令超时没处理、断电失败被投诉、结算金额和用户预期不一致。共享项目做的是信任基础链路不稳一切增值都是负资产。2. 系统架构与微信双端联动的技术方案选型技术选型这个环节其实没有太多“炫技”空间核心诉求是稳定、可控、团队熟悉。Java 在这个场景里优势很明显生态成熟、事务和并发控制手段丰富、对支付这类资金敏感业务的支持资料极多。2.1 整体架构拆解我采用了经典的分层架构避免过度设计微信小程序C端 ↕ HTTPS/WebSocket Java后端服务Spring Boot ├─ 业务服务模块订单、会员、设备、计费、商品 ├─ 消息服务模块WebSocket推送、订阅消息、企微通知 ├─ 支付服务模块微信支付V3 └─ 设备控制模块电控箱指令下发与状态回调 ↕ MySQL业务数据 Redis分布式锁/热点数据/在线状态 ↕ 智能电控箱继电器→ 球桌灯光/扫码器电源之所以这么拆是因为每个模块的变更频率和稳定性要求不一样。设备控制模块可能因为硬件厂商不同而频繁调整协议支付模块则要绝对稳定消息模块面向双端联动体验。拆开之后某一个模块出问题不会波及其他模块运维也清晰。2.2 核心数据模型设计数据库设计是这类系统的地基我把最重要的几张表逻辑讲清楚球桌表table_infotable_id、store_id、table_name“A区1号桌”、table_type0普通/1斯诺克、hourly_price小时单价、device_sn关联电控箱端口、online_status、current_status空闲/开台中/暂停中。订单表order_infoorder_id、table_id、open_time、end_time、total_minutes、amount应收金额、status0待支付/1开台中/2暂停中/3待结算/4已完成/5已退款、cancel_reason。这里有个细节不要把“开台中”和“待结算”混成同一个状态因为中途结束和自然结束的资金流不一样。设备指令表device_commandcmd_id、table_id、commandON/OFF、status0待下发/1已下发/2成功/3失败/4超时、create_time、finish_time。这张表在做异常排查时特别重要后面会细说。押金流水表deposit_log用户开台时若开启押金模式这里记录每一笔冻结/解冻/扣款操作字段要包括frozen_amount、release_amount、deduct_amount。计费规则表charge_rule时段、节假日倍数、最小计费单位比如不足15分钟按15分钟算都放这里避免硬编码在业务代码里。设计这些表时我坚持了一个原则凡是涉及钱的字段全部用 int 存储分而不是用 decimal 存元。Java 的BigDecimal做金额运算虽然安全但存进数据库仍可能遇到精度和比较问题直接按“分”存储查询、统计、对账都简单得多。2.3 双端消息联动的三种方案对比双端联动的核心是状态实时同步。微信生态里做“服务端 → 用户端”的实时通知主流方案有三种方案实现成本实时性适用场景小程序轮询拉取低慢30秒级不推荐作为主方案WebSocket 长连接中毫秒级开台/结账/设备状态变更微信订阅消息低分钟级订单完成提醒、余额变动提醒我最终的方案是WebSocket 订阅消息结合小程序端在用户进入球桌页面时建立 WebSocket 连接后端把连接绑定到userId和tableId上。开台、暂停、结束、老板强停这些关键状态变化通过 WebSocket 秒级推过去小程序收到后刷新 UI。对于订阅消息只在订单完成、退款到账这类“用户离开小程序后仍然需要知道”的场景使用因为微信订阅消息的单次订阅限制决定了它不适合做高频状态同步。B端企业微信那侧则用企业微信应用消息或者普通 HTTP 长轮询。实际做下来B端对实时性的要求其实没那么苛刻3秒内的延迟完全可以接受所以我在管理端采用了“接口轮询 关键操作短信/企微通知”的组合反而比强行上 MQTT 更省事。3. 核心功能开发实战从扫码开台到自动结算这一节是全文最“干”的部分我把核心功能的业务代码思路和关键参数计算过程完整写出来。3.1 用户扫码开台的完整链路开台接口是并发要求最高的接口因为多个用户可能同时尝试开启同一张球桌。我用了 Redis 分布式锁避免超卖public OpenTableResult openTable(OpenTableRequest request) { String lockKey TABLE_LOCK: request.getTableId(); // 获取锁等待最多3秒防止多个用户同时抢同一张桌 boolean locked redisTemplate.opsForValue() .setIfAbsent(lockKey, 1, 3, TimeUnit.SECONDS); if (!locked) { throw new BizException(该球桌正在被其他人开启请稍后重试); } try { // 1. 校验球桌当前状态必须为空闲防止超卖和重复开台 TableInfo table tableMapper.selectById(request.getTableId()); if (table.getCurrentStatus() ! TableStatus.IDLE) { throw new BizException(该球桌当前不可用); } // 2. 创建订单状态为“开台中” OrderInfo order new OrderInfo(); order.setTableId(table.getTableId()); order.setStatus(OrderStatus.PLAYING); order.setOpenTime(LocalDateTime.now()); order.setCreateTime(LocalDateTime.now()); orderMapper.insert(order); // 3. 下发开台指令给智能电控箱 DeviceCommand cmd buildOnCommand(table.getDeviceSn()); deviceCommandMapper.insert(cmd); // 异步发送指令不阻塞用户等待 deviceControlService.sendCommandAsync(cmd); // 4. 更新球桌状态为使用中 table.setCurrentStatus(TableStatus.PLAYING); tableMapper.updateById(table); // 5. 通过 WebSocket 推送开台成功消息给用户端和管理端 wsService.notifyTableStatusChange(table.getTableId(), TableStatus.PLAYING); return buildResult(table, order); } finally { redisTemplate.delete(lockKey); } }这里有三个容易忽略的点下指令和更新状态是两步但必须一起成功。实际项目中我用了本地消息表 定时重试来保证最终一致。如果指令下发失败了但状态已经改成“开台中”顾客会以为自己开台成功了结果灯没亮这就是事故。开台时间的记录要以设备回调为准还是以数据库为准我的经验是为减少用户等待界面点击开台后先返回成功并开启计费但设备上电时间以电控箱的回调为准如果回调迟迟不来启动一个延迟任务去查询设备状态超时则触发自动退款。这是计时公允性的底线。分布式锁的粒度一定要到“球桌”而不是“门店”。一个门店几十张桌子锁整个门店会导致所有球桌的并发开台被串行化完全没必要。3.2 按时间计费的算法与精度控制按时计费看着简单实际有大量边界情况。我沉淀下来的规则是基础单价来自charge_rule表支持分时段定价例如工作日白天 28 元/小时周末晚上 58 元/小时。计费开始时间以设备上电回调为准结束时间以用户点击“结束计费”或设备主动断电上报的时间为准。最小计费单位设为15 分钟不足 15 分钟按 15 分钟计费超过部分按比例叠加。这是行业里常见的做法既能保证商家收益也不会让顾客觉得被坑。对“最后一分钟结束订单”要特别处理——很多用户会踩着整点结束导致结算金额比预期多一个计费单位。我的方案是预留 3 分钟“宽限期”即在不足一个最小计费单位且时间差额小于阈值时按上一计费单位结算。这个规则要提前写在用户协议里否则容易产生客诉。计算示例某桌定价 60 元/小时用户打球时长 23 分钟。最小计费单位 15 分钟23 分钟 1 个完整单位 8 分钟。如果 8 分钟超过“半个计费单位”即 ≥7.5 分钟按 2 个单位收不足则按 1 个单位收。应收金额 60 × 2 / 4 30 元因为 1 小时有 4 个 15 分钟。public long calcAmount(ChargeRule rule, int totalMinutes) { int unitMinutes rule.getMinBillingUnit(); // 15 int units totalMinutes / unitMinutes; int remain totalMinutes % unitMinutes; int actualUnits if (remain rule.getHalfUnitThreshold()) { units 1; } else { units; } // 金额单位存储为分 return rule.getUnitPriceFen() * actualUnits; }这里有个很重要的商业细节“自定义计费模式”一定要做成可配置不要写死在代码里。因为同一个老板可能在不同门店使用不同规则球房比赛期间按局计费、淡季按小时打折规则做成库表配置后运营同学自己就能改不用发版。3.3 临时暂停与续时的状态流转打球过程中用户可能会上厕所、买水这时候需要“临时暂停”功能。暂停的逻辑本质上是停止计时但保持设备通电。出现这个需求是因为有些球房有锁球功能比如打了 30 分钟暂停回来后还能继续用同一桌暂停期间不计费。实现上我加了一张pause_log表记录每次暂停的开始和结束时间订单的总计费时长 累计使用时长 - 累计暂停时长。但是我在实际运营中发现“暂停功能”容易被薅羊毛有用户开台后立即暂停隔了很久再回来继续打导致球桌长时间被占用但老板只收到很少的钱。所以后面加了两个限制单次暂停最长 30 分钟、每单累计暂停不超过 60 分钟超过后自动恢复计费。这属于运营规则同样做成可配置。3.4 结算退款与资金流动用户点击“结束计费”后系统计算出最终金额。如果之前冻结了押金按“多退少补”原则执行实付金额 ≤ 已支付金额退还差额实付金额 已支付金额生成补款单用户支付后才算彻底完成如果用户没补款就离开了这套系统有一个兜底设计记录 B 端“离场未结待追缴”列表同时通过微信订阅消息提醒用户补款。退款一定要走微信支付的“退款”接口而不是自己发红包这是合规底线。退款到账通常有 1~5 个工作日延迟需要在用户端明确提示“退款将在 1~5 个工作日内退回”减少客服压力。这里的核心逻辑用到了本地事务 消息补偿。我画了一个简化版状态机待支付 → 开台中 → 暂停中 → 开台中 → 待结算 → 已完成 ↓ ↓ 中途取消 用户补款/押金扣除 ↓ ↓ 已退款 已关闭/已追缴每个状态之间的流转都在代码里做了幂等保护防止 WebSocket 重推、用户重复点击导致重复扣款或重复退款。4. 微信双端联动的高级细节4.1 小程序端性能与体验的平衡小程序端虽然主要用官方生态但有几个细节直接决定体验档次WebSocket 断线自动重连。手机网络切换WiFi→4G会让 WebSocket 断开前端要在onUnload之前监听onSocketError和onSocketClose做指数退避重连退避策略我用的 1s、2s、4s、8s、16s、30s 封顶。状态角标实时刷新。球桌卡片上要展示“使用中/空闲/已预约”这个状态不能靠用户手动下拉刷新必须由 WebSocket 推送驱动。我在后端做了一个TableEventPublisher任何表的 status 变更都会发布事件事件被 WebSocket 推送给正在浏览该门店页面的所有用户。倒计时的精度。前端拿到后端下发的openTime后用服务器时间和本地时间差做校准避免用户手机时间不准导致倒计时混乱。后端在每次推送消息时带上serverTime前端每次收到就做一次差值更新。4.2 管理端企业微信消息模板的选型B 端老板不会一直盯着后台看所以管理端的“被动通知”很重要。企业微信提供了应用消息、群机器人 Webhook、智能表单等多种能力。我最终选择了关键资金事件新订单支付、退款成功、大额补款→ 应用消息模板推送到老板的企业微信设备异常事件电控箱掉线、指令发送失败→ 群机器人 Webhook 推到门店运维群订单超时未结算→ 每日汇总一次以工作台日报形式呈现。为什么不都用应用消息因为企业微信的主动推送数量有限而且高频推送会造成“消息疲劳”。分级后老板只看重要消息运维群处理噪音各司其职。这里有个小坑企业微信应用消息的touser是成员 ID 而非手机号第一次对接时容易搞混。需要在后台提前把老板的成员 ID 配到配置文件里或者通过通讯录 API 实时查询。4.3 微信支付 V3 接入的关键配置支付这一块我踩过不少坑重点提醒证书和密钥分离管理。微信支付 V3 使用 APIv3 密钥和商户私钥不要把私钥文件传到代码仓库里要用配置中心或环境变量。回调地址必须是 HTTPS而且回调处理函数必须返回“成功”应答给微信否则微信会一直重试造成重复通知。我做了幂等表用transactionId orderId唯一索引拦截重复回调。退款接口的金额单位是分不要传元否则退款金额会莫名大 100 倍这在测试环境就很容易发现但生产环境一旦发生就是事故。用户的微信openid和unionid要区分。同一用户在不同小程序下的openid不同如果要跨端例如一个小程序一个公众号识别唯一用户必须关联unionid前提是这两个应用绑定在同一个开放平台账号下。4.4 与硬件设备联动指令超时和重试机制电控箱是整个系统里最不可控的环节——网络抖动、硬件死机、固件 bug 都可能让指令石沉大海。我的处理是指令发送后 5 秒内没有收到 ACK标记为“待重试”重试策略间隔 2s、5s、10s 各重试一次期间若硬件恢复连通立即补发超过三次重试仍失败标记为“指令失败”同时触发 B 端告警 小程序端弹窗提示“设备响应超时请联系店员”。设备状态查询和心跳采用定时任务。如果某个球桌的电控箱掉线时长超过 5 分钟系统会将该桌标记为“离线不可用”避免用户下单后才发现开不了台。这个“先不可用、再恢复”的逻辑极大减少了客诉。5. 双端联动中的常见问题与排查技巧实录做这套系统的过程中我记录了一批极具代表性的问题这里按“现象→原因→解决方案”的思路整理成速查表。5.1 用户已在打球但 B 端看到球桌仍是空闲这是我第一次联调时遇到的问题。现象是 C 端一切正常B 端数据不对。排查后发现问题出在管理端使用的列表接口没有实时查数据库而是查了 Redis 的缓存副本而缓存失效时间设置为 10 分钟。解决方案短状态球桌占用状态不缓存直接查库长状态门店基本信息、价格表缓存 5 分钟即可用CaffeineCacheable做本地缓存加CacheEvict在状态变更时主动淘汰。注意共享系统的“实时在线状态”是这个行业的生命线。宁可牺牲一点性能也不要缓存状态数据。所有current_status的更新必须直接落库。5.2 结算金额偶尔偏差一分钱有一次用户反馈“多收了一分钱”。排查发现是BigDecimal转字符串格式化时精度丢失。生产事故级别不高但影响用户体验。解决方案并不是在展示层做四舍五入而是在金额计算层就统一规则所有金额计算先做分单位的整数运算最后仅允许在展示层执行格式化。我在计算金额的工具类里加了一行注释永远不要在计算过程中使用 double 或 float 表示金额如果需要除法先放大到分再整除最后再四舍五入到分。5.3 用户切换网络后收不到订单状态推送有用户反馈打球途中从 WiFi 切到流量结束计费后小程序没弹出账单。原因是 WebSocket 断开后没有自动重连后端推送的消息全丢了。解决方案小程序端在onShow生命周期里检查 WebSocket 连接状态断线则主动重连并拉取一次最新状态后端在 WebSocket 连接建立时将当前用户所有“进行中”的订单完整推送给客户端保证重连后状态自愈复杂方案还可以引入“离线消息队列”每条状态变更记录都存 Redis Stream客户端重连后按序列号补齐缺失消息。5.4 微信小程序审核被拒类目与内容合规问题共享台球室小程序首次提审时被拒原因是“涉及预约、点单、支付类服务需补充对应类目资质”。这是非常常见的坑解决方案小程序类目选择“生活服务 运动健身”而不是“工具”提前准备好《营业执照》和行业资质部分城市要求体育场馆备案隐私协议中必须声明收集用户位置信息、微信昵称头像的原因用于定位附近球房、展示用户身份。5.5 双端状态不一致误以为“结束计费”就会立刻断电有一次运营反馈用户点了结束计费但灯还亮着。排查后发现断电指令依赖电控箱的回调反馈而用户端展示“订单已结束”和物理断电之间存在 2~3 秒延时。如果用户在这几秒内走了灯泡会在之后自动熄灭但旁观者可能觉得系统有问题。解决方案在用户端展示“断电确认中请稍候”的过渡态后端增加设备断电进度轮询确认断电成功后才把订单状态完全置为“已完成”如果多次重试断电失败触发安全兜底后台手动断电 发工单给运维。这种细节很琐碎但对用户信任感的建立极其重要。共享系统不是“功能做完就行”而是所有状态都要经得起推敲。6. 运维与经营视角连着远程数据与实体门店系统上线只是第一步真正难的是稳定运维和经营数据的利用。这里必须从“技术人”切换到“经营者”视角来审视整套系统的价值。6.1 监控告警体系不被用户投诉时没人觉得有问题我一上线就配置了三套监控业务监控今日订单量、支付成功率、平均客单价、退款率。任何一项偏离历史 30% 就要告警比如退款率突然飙升极可能是某台控制器时序出了问题。设备监控电控箱在线率、指令下发成功率、平均响应时长。云端的 SRE 思维用来管实体设备一样有效。资金对账每日凌晨跑一次性对账任务比对微信支付账单、订单金额、退款金额确保三方数据一致。这是财务合规的基础也是避免“订单记账了但没扣钱”这类问题的最后一道防线。6.2 降本增效无人系统的实际运营账本从经营角度看一套无人系统给台球室带来的最大变化是“人力替代”。以一个 10 桌规模的中型球房为例项目传统模式无人模式服务人员2~3 人轮班1 人远程运维/保洁营业时间受限于人工通常到凌晨 2 点24 小时全天开放营收周期依赖人工结算现金风险高在线支付实时到账数据复盘纸质记录滞后系统自动生成报表我曾见过一个店主把原本 3 个人的门店缩到 1 个人值班后月人力成本直接省了两万多而夜间时段凌晨到早上 8 点的订单量反而因为“无人自取”模式上涨了 15%。技术投入的价值最终都要换算到门店的经营效率上。6.3 从单一球房到连锁 SaaS 的扩展想象这套系统做完单店版之后如果未来要做连锁还有几个点建议提前预留多门店数据权限隔离管理端要支持总店看所有分店数据分店店长只看自己的店。数据权限模型要在一开始就做不要后期补。总部统一运营优惠券、会员等级、计费规则模板可以由总部统一下发分店做局部调整。供应链对接球桌、球杆、商品库存可以和采购系统打通实现实时库存余额管理。这些听起来很“未来”但技术架构上只要做好“组织架构表”和“规则模板表”就能低成本支撑。别等到门店开到十家才回头补课。7. 写在最后关于这套系统的一些个人心得我做这套系统最大的体会是共享台球室的核心不在“共享”而在“无人可信”——顾客愿意在没有店员监督的环境下消费是因为系统给了他确定性扫码就能开、结束就结算、扣款有明细、退款能到账。任何一个环节出现“说不清楚”的情况信任就崩塌了。如果你准备上手做类似项目我建议先把最小闭环搭出来一张桌子、一个电控箱、一个下单接口、一个回调接口、一张订单表。跑通之后再逐步加会员、优惠券、多门店、数据报表。不要一上来就追求大而全否则光是联调成本和异常处理就会把你拖垮。另外微信生态的迭代速度比大多数后端技术栈都更快小程序接口、支付规则、订阅消息策略时常调整。上线之后要留出每周的“技术债清理时间”把版本升级、接口变更这些事纳入日常否则半年后你会发现旧接口已经不能用了。最后分享一个小技巧开发无人系统时自己先当一个月“云店长”。每天用小程序开台、结账、退款用管理端看报表、处理异常订单。很多设计缺陷都是在真实业务操作中自己暴露出来的比看任何代码 review 都有效。这套 Java 共享台球室无人系统从业务梳理到上线运行我前前后后打磨了差不多两个月。技术栈本身没有多高深真正的难点全在那些不起眼的边界情况和状态流转里。希望这篇拆解能帮你避开我踩过的坑少走几步弯路。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑