ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

无人自助台球厅管理系统开发实战:从数据库设计到Spring Boot实现

无人自助台球厅管理系统开发实战:从数据库设计到Spring Boot实现 毕业设计做到无人自助台球厅管理系统这个题目这两年是真的不少。原因不复杂这个题一面是真实的商业场景——共享经济、无人值守、物联网联动都是答辩老师喜欢听的东西另一面它的技术栈又非常正统Spring Boot MyBatis MySQL加一个前端页面难度可控、工作量清晰认真做三个月完全能成型。我这次就把整套系统的设计思路、数据库结构、核心代码和踩坑记录完整捋一遍给正在做这个题或者准备选这个题但还没确定技术路线的同学一条不用绕路的参考路线。这篇文章不写虚的所有讲到的模块和问题都是我在实际开发和调试中真正遇到过的你照着做至少能少踩一半的坑。1. 这个课题到底在解决什么问题1.1 无人自助台球厅的核心痛点传统台球厅最大的成本不是房租和球桌是人力。前台要有人收银服务员要盯着客人离场、计算超时、清理桌面夜场还得通宵轮班一个月光工资就压得小店喘不过气。无人自助模式把这些动作全部交给系统和硬件顾客扫码开台、系统自动计时计费、到点自动断电、线上支付结账老板只需要远程看后台数据就行。这个逻辑和共享自习室、共享茶室一模一样。它解决的问题不是省一个服务员而是让一家台球厅从必须有人值守变成全天候无人也能营业营业时长从12小时拉长到24小时边际成本几乎为零。对毕设来说这个业务背景非常容易讲清楚答辩开场的选题意义部分基本不用发愁。1.2 系统功能边界怎么划才合理毕设最怕两件事一是功能太少答辩时说不出东西二是功能堆太多做到一半发现根本做不完。无人自助台球厅这个题目天然有个好处它的功能边界非常清晰按角色拆就够用了用户端微信小程序或者H5二选一即可负责注册登录、扫码查看球桌、预付费开台、查看实时计费、续费加钟、在线支付、充值、申请退款。管理端Vue Element Plus的Web后台负责球桌管理增删改查和状态维护、计费规则配置不同桌型、不同时段的价格、订单管理进行中订单、历史订单、异常订单处理、会员管理、营业收入统计。再加上一个可选加分项硬件联动。通过智能电控插座控制球桌灯的电源下单成功后自动通电订单超时未续费自动断电。这个设计一放出来整个系统的无人就名副其实了答辩老师会觉得你不只做了个CRUD而是真的思考过落地场景。1.3 技术选型为什么是Spring Boot我见过不少学生纠结框架想用Go、想用Python FastAPI甚至想上微服务。我的建议非常直接毕设就老老实实用Spring Boot。原因有三个。一是生态成熟。Spring Boot MyBatis Plus MySQL Redis这套组合的教程、踩坑记录、面试题多到数不清你遇到任何问题搜一下基本都有现成答案不像冷门框架报错折腾三天没人理。二是边界清晰。Spring Boot的自动配置和起步依赖把大部分基础设施工作做完了你能把精力集中在业务逻辑上。对于开台计费这种核心业务你需要的是写清楚状态机、算清楚金额而不是跟框架本身死磕。三是答辩安全。Java在后端领域的地位摆在那里答辩老师几乎不可能对你选Spring Boot提出质疑反而会顺着这个选型问你Spring Boot的自动配置原理、依赖注入、事务管理这些都是有标准答案的你准备一下就能应对。2. 整体架构与数据库建模2.1 前后端分离的总体架构整个系统我采用的是前后端分离的结构后端独立提供RESTful API前端管理后台使用Vue3 Element Plus用户端使用微信小程序。这样做的好处是职责清晰后端只做数据校验、业务逻辑和持久化前端只管页面交互两边通过JSON格式的数据通信接口联调用Swagger文档对齐。部署形态上Spring Boot内置Tomcat后端打一个jar包就能跑前端构建完的静态文件由Nginx托管也可以直接把dist目录扔到Spring Boot的static目录里实现单端口部署演示的时候最省事。这里补充说明一下各层职责Controller层只负责参数接收和结果封装不写业务Service层承载所有核心逻辑比如开台、计费、结账必须用事务管理Mapper层用MyBatis Plus封装好的BaseMapper做基础CRUD复杂的统计查询用XML或注解SQL完成。层与层之间单向依赖这样的结构答辩时画架构图特别清晰。2.2 数据库表设计八张核心表一次说清数据库设计是整个项目的地基我实际设计的时候一共用了八张核心表每张表的用途和关键字段我列一下用户表sys_useropenid微信用户唯一标识、nickname、phone、balance余额、status0正常/1冻结、create_time。桌型表table_typetype_name普通台/赛台/VIP包间、is_vip、price_per_hour标准小时价、deposit押金。桌型独立建表而不是直接写在球桌表里是为了后续调价和扩展新桌型方便。球桌表billiard_tabletable_no桌号如A01、table_type_id关联桌型、location位置描述、status0空闲/1使用中/2维护中。桌号建议用区域编号的字符串格式扫码时直接通过桌号定位。订单表billiard_orderorder_no业务订单号、user_id、table_id、start_time实际开台时间、plan_end_time预计结束时间、actual_end_time实际结束时间、pre_amount预收金额、final_amount实际应收金额、refund_amount退款金额、status0待支付/1进行中/2已完成/3已取消/4异常。支付流水表payment_recordpayment_no、order_id、pay_type微信/余额、amount、status0待支付/1成功/2失败、transaction_id微信支付单号、callback_time回调时间。支付流水独立建表方便对账后面讲幂等处理会用到。充值记录表recharge_recorduser_id、amount、balance_before、balance_after、pay_type、create_time。计费规则表price_ruletable_type_id、period_start、period_end时段区间、price_per_hour该时段小时单价。有了这张表就可以实现闲时8点前打折、周末上浮这类灵活的计费策略比死写单价高级得多。管理员表sys_adminusername、passwordBCrypt加密存储、role。管理端登录单独建表和C端用户隔离权限也更清晰。2.3 核心业务的状态流转设计这个系统里含金量最高的设计是订单和球桌的状态流转。开台的完整流程是这样的用户扫描球桌上的二维码小程序拿到桌号跳转到详情页用户看到当前单价和押金选择按小时预付费或者先充值后结算系统校验球桌状态为空闲后扣款创建订单并把球桌状态改成使用中紧接着调用智能插座接口给球桌灯通电订单进入计费中状态。计费过程中定时任务每分钟扫描一次进行中的订单随时可以算出当前费用供用户端实时展示。到达预计结束时间前10分钟系统通过微信订阅消息或小程序内轮询给用户推送续费提醒。用户可以选择加钟续费也可以主动点击结束如果一直没人操作到点后系统自动断电、强制结账把订单置为异常状态等待管理员处理。为什么一定要设计成预付费多退少补而不是玩完再结账因为无人场景下没有人能约束顾客离场先收钱是唯一能跑通的商业闭环。用户提前结束时按实际时长结算并把剩余金额退回超时未处理的从押金里扣超时费。这套逻辑想清楚之后整个系统的代码实现就顺了。3. 核心模块的关键实现细节3.1 用户认证与登录拦截用户端我采用的是JWT无状态登录方案。微信小程序端先调用wx.login拿到code后端再通过code换取openid查到用户则直接签发JWT查不到就自动注册一个账号再签发。管理端则是传统的用户名密码登录密码用BCrypt加密登录成功后也签发JWT。关键点在于全局拦截器。我实现了一个HandlerInterceptor在preHandle里解析请求头中的Authorization字段校验JWT签名和有效期然后把用户信息放进ThreadLocalController里通过工具类直接拿当前登录用户。要注意放行路径白名单小程序登录接口、支付回调接口、球桌列表这些不需要登录就能访问的接口必须在拦截器里排除否则会出现诡异的未登录报错。public class AuthInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (request.getMethod().equals(OPTIONS)) { return true; } String token request.getHeader(Authorization); if (token null || !token.startsWith(Bearer )) { throw new BusinessException(401, 未登录或登录已过期); } // 解析JWT放入ThreadLocal String openid JwtUtil.parseToken(token.replace(Bearer , )); UserContext.set(openid); return true; } }3.2 扫码开台的并发安全处理开台是整个系统最核心的接口因为并发场景最恶劣两个人同时扫同一张桌子的码如果代码不做并发控制就会出现双开。我第一个版本就吃过这个亏测试时两个账号同时点开台数据库里插了两条进行中的订单球桌状态也乱了。解决办法是两层保护。第一层在数据库层面用MyBatis Plus的乐观锁更新执行UPDATE billiard_table SET status 1 WHERE id ? AND status 0这条SQL的影响行数为1才说明抢桌成功为0就返回手慢了桌子已被占用。这是原子操作天然防并发。第二层在应用层用Redis的setIfAbsent方法给tableId加一把分布式锁防止同一毫秒内多条请求同时进入开台逻辑。Transactional(rollbackFor Exception.class) public Result openTable(OpenTableDTO dto) { // 校验用户余额/押金是否足够 // 乐观锁抢占球桌 int update billiardTableMapper.updateStatusById(dto.getTableId(), 0, 1); if (update 0) { return Result.error(该球桌已被占用); } // 创建订单预扣金额 // 推送硬件指令给智能插座通电 return Result.success(orderNo); }这里还有个容易遗漏的细节锁定球桌、扣减用户余额、创建订单这三个操作必须在同一个事务里任何一个失败都要回滚否则会出现扣了钱但没开台成功或者开台成功但没扣钱的数据错乱。我建议在Service方法上加Transactional(rollbackFor Exception.class)注意默认只回滚RuntimeException检查异常不回滚所以还是显式指定一下更稳妥。3.3 计费逻辑与定时任务计费的核心是实时金额计算。最简单的实现方式是不落库每次用户查进度的时候现算用当前时间减去开始时间得到已用分钟数再按对应的时段单价计算费用。但这里有一个业务歧义需要提前定清楚时长按分钟精确计算还是按最小计费单元算我采用的是分钟级精确计费首次开台最少1小时。这样做的好处是用户端展示的金额和最终扣费一致少了很多投诉和纠纷。计算时还要处理跨时段问题比如18:59开台到了19:30就前1分钟按闲时单价、之后按高峰单价来算。实现上建议按分钟循环遍历把每分钟归属到对应时段累加即可订单量不大的话性能完全没问题。定时任务我用的是Spring自带的Scheduled注解每分钟跑一个扫描方法找出所有status为进行中且plan_end_time小于当前时间的订单调用硬件接口断电然后把这些订单标记为待结算状态。这个方案比引入消息队列做延迟消息简单得多对毕设完全够用而且逻辑直观、好答辩。Scheduled(cron 0 * * * * ?) public void autoSettleExpiredOrders() { ListOrder expiredOrders orderMapper.selectExpiredRunningOrders(); for (Order order : expiredOrders) { // 1. 调用硬件接口断电 hardwareClient.powerOff(tableMapper.selectById(order.getTableId()).getDeviceCode()); // 2. 更新球桌状态为空闲 tableMapper.updateStatus(order.getTableId(), 1, 0); // 3. 计算超时费用从押金中扣除生成退款单 settleOrder(order); } }3.4 微信支付接入与回调幂等微信支付是无人自助系统绕不开的一环。毕设阶段不需要真商户号申请一个微信支付沙箱环境或者用第三方测试网关甚至直接用余额支付模拟完整流程都可以。但如果想完整实现我建议用Native支付系统生成二维码用户扫码付款支付成功后微信服务器向回调地址发起异步通知。这里最重要的坑是回调幂等。微信支付为了保证通知可靠会多次发送回调如果你的代码收到一次回调就更新订单状态、加一次余额必然会出现充一百到账三百的事故。处理方式是在支付流水表上建唯一索引order_id pay_type处理回调时先查流水状态如果已经是成功状态就直接返回SUCCESS不再重复更新。同时修改订单状态SQL要带上状态条件比如UPDATE payment_record SET status 1 WHERE order_id ? AND status 0。3.5 后台数据统计怎么做管理端的营收统计是答辩时的加分项但实现起来不需要很复杂。我使用了MyBatis Plus的聚合查询配合Java 8 Stream做分组和汇总就能输出日报、周报、桌台使用率这些基础指标。比如统计今日营收核心SQL就是按订单状态过滤后SUM(final_amount)比如统计各球桌的使用时长排名就是按table_id分组后SUM(TIMESTAMPDIFF(MINUTE, start_time, actual_end_time))。图表展示随便用ECharts柱状图、折线图、饼图各配两个页面视觉上就很完整了。4. 从源码到跑通全流程的实操经验4.1 环境准备清单动手之前先把环境一次性装好省得后面被各种版本问题折磨。我个人推荐的组合是JDK 1.8不要一上来就装JDK 21兼容性问题会让你怀疑人生、Maven 3.6、MySQL 8.05.7也行、IDEA最新社区版足够。Redis如果不想装项目里可以先用本地缓存放一边但如果有余力还是建议装一个后面讲锁和缓存都会用到。数据库初始化不要手动逐条建表太容易出错。我一般在项目里放一份sql脚本包含建库语句和全部建表语句再插入一些测试数据比如三张球桌、两种桌型、一条闲时计费规则。这样别人拿到源码后执行一遍sql脚本就能跑起来也是源码交付的一部分。4.2 项目结构与包名规划一个清晰的包结构能让你写代码的时候少走很多弯路。我建议包名统一以com.xxx.billiard开头内部按功能模块分包而不是按三层架构分包。所谓按功能分包就是每个业务模块建一个包里面再放controller、service、mapper、entity。比如booking包放开台和订单相关的所有类payment包放支付和退款相关的所有类。对毕设代码来说这种组织方式比三层大平铺好理解得多自己维护和答辩展示都方便。4.3 核心配置文件的几个关键项application.yml里除了常规的数据源配置有三个地方容易出错提醒一下。第一spring.jackson.date-format要设置成yyyy-MM-dd HH:mm:ss否则前端拿到的日期是带T的国际格式看着非常奇怪。第二mybatis-plus的map-underscore-to-camel-case默认是true数据库下划线字段能自动映射到驼峰属性别手贱改掉。第三Redis配置的host和端口记得区分本地和服务器环境我建议用application-dev.yml和application-prod.yml分环境管理。4.4 本地联调与演示注意事项联调阶段推荐用Swagger或者Knife4j直接在浏览器里调试所有接口不用前端写好才能测后端。前端和后端的联调方式开发时可以通过Vite的proxy配置转发到localhost:8080避免跨域问题。部署演示时如果现场只有一台电脑把前端dist文件拷到后端static目录跑一个jar包就完事网络配置少出错的概率也低。演示前的数据准备特别重要。我会在演示环境里预置几个测试账号账上充好余额并且有一张球桌保持空闲状态便于现场演示开台。我还习惯在演示前手动跑一遍核心流程把订单清一清避免现场打开后台发现一堆测试垃圾数据观感很差。5. 常见问题与避坑实录5.1 并发开台导致的一桌两单出现这个问题的根本原因是先查询后更新的非原子操作。很多人写的代码是先select球桌状态判断是0再update成1。这个流程在并发下必然出问题两个请求都查询到状态为0然后双双更新成功。这就是为什么我在前面强调必须用UPDATE ... WHERE status 0这种条件更新而不是先查再改的原则。遇到这个问题先检查所有状态变更是否都用了带条件更新的SQL再检查是否有Redis锁兜底。5.2 计费金额对不上账金额不平的问题90%出在跨时段计费或者退款计算上。比如用户开台1小时预付了高峰价但实际打了45分钟就结账走人中间跨越了闲时时段退款金额怎么算我踩过一次坑之后总结出一套规则创建订单时先按预计时段计算预收按整小时预收、押金补差的方式实际结算时重新按分钟计算一遍真实费用多退少补。退款金额必须在事务里重算绝不能简单用预收减去固定单价乘时间去算。5.3 Spring Boot版本过高引发的连锁报错这是我见过最多的新手问题。去网上搜教程资料里用的是Spring Boot 2.x的写法自己却装了Spring Boot 3.x结果发现javax.servlet变成了jakarta.servlet很多第三方starter不兼容代码就是编译不过。我的建议非常明确毕设老老实实用Spring Boot 2.7.x JDK 1.8这个组合的资料最多、兼容性最好、报错一搜全有答案。不要为了追求新版本给自己添堵稳定跑通比版本号漂亮重要得多。5.4 微信支付回调重复导致重复入账前面讲过幂等这里再补充一个排查技巧。如果你已经加了唯一索引但还是出现重复入账很可能是没有处理全表约束或者回调处理逻辑里先查了流水再发起了加余额操作两步之间存在并发窗口。正确做法是把查询流水状态 更新流水 更新订单/余额全部包在一个事务里并且数据库表加上唯一索引双保险。我实际测试中这种设计下即使回调重发十几次数据也纹丝不动。5.5 答辩高频问题怎么准备根据我观察到的答辩现场这个题的高频问题基本集中在这几个方向为什么选择Spring Boot而不是其他框架答自动配置、生态、适合快速开发并发开台怎么解决的答乐观锁Redis锁配合代码演示用户一直不结账怎么办答定时任务强制断电押金扣款退款怎么保证准确答事务重算支付流水记录数据库表为什么这么设计答第三范式订单和流水分离方便对账。这些问题提前准备好答辩基本就稳了。6. 个人体会与扩展建议最后说一点我实际做完这个项目之后的体会。第一系统的核心不是前端页面做得有多炫而是业务闭环能不能完整走通从扫码、开台、计费到结算这个闭环里每一个环节的数据一致性都要认真对待。第二源码交付时一定要附带一份清晰的README写清楚环境要求、数据库初始化方式、启动步骤、测试账号别让拿到源码的人对着报错猜半天。这是很多同学忽略的细节但非常加分。如果做完基础功能还有余力我建议往这三个方向扩展一下一是接入真实微信支付和真实智能插座做一个可以真正试运营的版本二是增加会员营销模块比如充值满赠、优惠时段套餐商业价值立刻提升三是加一个基于ECharts的大屏数据看板把实时订单、营收趋势、桌台利用率投到大厅屏幕上整个项目的完成度会再上一个台阶。这个题目能做到这个深度无论是答辩还是写进简历都是拿得出手的。
返回列表