Spring Boot实战:网吧管理系统设计与开发全流程详解
写这套系统的时候我心里其实挺有感触的。很多人一听到“网吧管理系统”第一反应是“这也太简单了吧不就是增删改查吗”。但真动手做的时候才发现里面全是细节计时怎么算、暂停怎么处理、会员余额和上机时长怎么联动、电脑状态怎么保证一致。恰恰是这种“看似简单、实则繁琐”的项目最适合用来吃透 Spring Boot 的全套开发流程。我见过不少新人拿商城、秒杀系统当练手项目结果被分布式、消息队列劝退反而是一个老老实实的网吧管理系统能让你把 MVC 分层、MyBatis 映射、事务控制、模板渲染这些基本功练扎实。这篇文章我就按实际开发的顺序把整个系统的设计和实现过程完整捋一遍。从需求分析到数据库设计从后端接口到前端页面再到打包部署和那些我踩过的坑全部写清楚。不管你是在准备课程设计、毕业设计还是想找个 Spring Boot 练手项目这篇内容都可以直接照着做。我也会把一些网上搜不到的细节比如计费策略的设计、接口放哪里的争论、IDEA 社区版怎么跑 Spring Boot 这类高频问题一并讲透。1. 做之前先把需求想明白很多人拿到这种题目第一件事就是打开 IDEA 新建项目然后开始写实体类。我劝你别急。网吧管理系统虽然小但它是一个典型的“状态密集型”业务系统机器状态、会员状态、上机记录状态三者必须保持严格一致。如果需求没想清楚代码写一半就会发现到处都是补丁改起来非常痛苦。1.1 网吧管理到底管什么你仔细想想网吧日常运营的场景核心就三件事人会员、机器电脑、钱计费与商品销售。所有功能都是从这三件事派生出来的。管人就是要处理会员的开卡、充值、资料维护。网吧的会员有两种常见形态一种是临时上机的散客来了直接开台按分钟计费另一种是办卡用户预存金额或者购买学时卡上机时从余额或学时里扣。这两种形态的计费逻辑不一样后续数据库设计的时候必须区分开。管机器就是要知道每台电脑当前处于什么状态。是空闲、使用中还是维护中谁在用从几点开始用的这个状态是整个系统的心脏所有上机和下机操作都在改这张表的状态。管钱除了上机计费还有商品销售。网吧的零食、饮料、点卡充值都是顺手就能做的生意。所以一个完整的系统还需要有商品管理、销售订单的功能跟会员余额联动扣款。1.2 三种用户角色的权限边界设计功能之前先划分角色权限清晰了功能边界自然就清楚了。这个系统面向三类使用者管理员管理会员资料、管理电脑信息、管理商品、查看所有经营数据。权限最大能看到全部菜单。收银员/前台日常操作最频繁的角色负责开卡、充值、上机、下机、商品销售。不需要管电脑的增删改也不需要看利润统计这类敏感数据。会员理论上会员是可以自己登录查看余额和消费记录的。但大部分课程设计里这一步会简化掉或者只做后台上机操作不开放会员自助入口。如果你想要功能更完整加一个会员登录页就行。这三类角色我用一张表来总结功能模块管理员收银员会员可选会员管理增删改查开卡、充值查看本人余额电脑管理增删改查查看状态不可见上机/下机全部权限日常操作不可见商品管理增删改查销售商品不可见数据统计全部可见部分可见不可见实际开发时用 Spring Security 或者 Shiro 做权限控制也行但单机版系统用拦截器根据 Session 里的角色字段控制菜单和接口权限就足够了别过度设计。2. 技术选型为什么选这套组合技术选型直接决定了你后面开发是顺风顺水还是四处踩坑。我基于这个项目的规模和特点给出了一套经过验证的组合并且把几个容易纠结的决策点单独拿出来分析。2.1 Spring Boot 版本与 JDK 的匹配陷阱这是我最想提醒你的一点。现在网上搜 Spring Boot 教程很容易搜到 Spring Boot 3.x 的内容但如果你用的是 JDK 8直接照抄 3.x 的配置项目根本起不来。Spring Boot 3.x 强制要求 JDK 17 及以上这是一个硬门槛。所以第一步要确认你本机的 JDK 版本。如果是 JDK 8老老实实用 Spring Boot 2.7.x这是 2.x 系列的最终版本稳定且资料多。如果是 JDK 17直接用 Spring Boot 3.x毕竟这是趋势。这里还牵扯到 MyBatis 的兼容问题。Spring Boot 2.x 对应的是mybatis-spring-boot-starter2.x 版本Spring Boot 3.x 对应的是 3.x 版本。很多新手就是栽在这里项目用的 Spring Boot 3却从旧教程里复制了 2.x 版本的 starter 依赖结果启动直接报NoSuchBeanDefinitionException折腾半天不知道是版本不匹配。2.2 MyBatis 还是 MyBatis-Plus这是个老生常谈的问题。对于这个项目我给你的建议是如果你是想练手、想搞懂 SQL 和映射关系用原生 MyBatis如果你想快速交差用 MyBatis-Plus。原生 MyBatis 的好处是你能看到每一条 SQL理解结果集是怎么映射成对象的。这对理解 ORM 的本质特别有帮助。缺点是单表 CRUD 要写大量重复的 XML 或注解。MyBatis-Plus 则直接把单表 CRUD 给你封装好了你甚至不用写 SQL直接用BaseMapper里的方法就能完成大部分操作。这个项目里会员表、商品表、电脑表都是典型的单表 CRUD用 Plus 能省一半功夫。我最终选择了MyBatis-Plus原因很实在项目规模小Plus 的复杂功能虽然用不上但基础的 CRUD 方法能让代码量骤减而且它的Wrapper查询条件构造器写动态查询比手拼 SQL 舒服得多。2.3 前端要不要前后端分离现在流行 Vue Spring Boot 的前后端分离架构但你要想清楚一个事网吧管理系统是典型的局域网单机部署系统用户就是网吧老板和收银员没有高并发没有多端适配需求。为了这种系统引入 Node.js、Vue CLI、Axios 那一整套工具链还要处理跨域问题纯属给自己找麻烦。我更推荐的方式是使用Thymeleaf 服务端模板引擎。Spring Boot 对 Thymeleaf 的支持是原生的你只需要在application.yml里配一下前缀后缀然后在templates目录下放 HTML 文件就行。页面里直接用th:each、th:if这些标签遍历数据控制器返回视图名浏览器直接看到渲染好的 HTML。整个过程少了一层 HTTP 接口调用也少了一半的 JavaScript 代码。有人会问那我想练前后端分离怎么办。我的回答是等这个项目跑通了你再去学 Vue 完全来得及而且到时候你有了后端基础学起来会快很多。2.4 别忘了内嵌 Tomcat 与端口修改Spring Boot 最大的便利之一就是内嵌了 Tomcat你不需要单独安装和配置 Servlet 容器打个 JAR 包就能直接运行。但这也带来一个高频问题——端口被占用。最常见的情况是你起了旧项目没停新项目起不来报端口 8080 被占用。解决方式有三种我推荐第一种修改application.yml设定server.port8081这是最直接的方式。启动命令追加参数java -jar xxx.jar --server.port8081适合部署时临时改端口不用动配置文件。设置环境变量SERVER_PORT8081适合容器化部署场景。另外提醒一句如果改完端口还连不上检查一下防火墙有没有放行对应端口特别是部署在云服务器上的时候。这个问题太常见了经常有人折腾半天代码结果是防火墙拦了。3. 数据库设计与计费模型数据库设计是这种业务系统的地基。我见过太多课程设计项目表建得随意字段类型不对等到写代码的时候发现处处别扭。下面我直接给出这套系统的核心表设计并且把关键字段的设计理由讲清楚。3.1 五张核心表的设计细节这个系统不需要十几张表围绕核心业务五张表就能覆盖所有功能member会员表id主键card_no会员卡号唯一name姓名phone手机号balance余额DECIMAL(10,2)card_type卡类型临时卡/学时卡/储值卡status状态正常/挂失/禁用create_time开卡时间这里最关键的是balance字段的类型必须用 DECIMAL绝不能用 DOUBLE 或 FLOAT。你想想余额是钱钱的计算容不得浮点误差。DOUBLE 在计算 0.1 0.2 的时候会得到 0.30000000000000004这种误差在金额系统里是不可接受的。computer电脑表idip_addr内网 IPposition区域位置比如“A区12号”status状态空闲/使用中/维护中current_member_id当前上机会员 ID空闲时为空current_member_id这个字段非常实用。它就是一个“占位标记”通过它你可以一眼看出每台电脑是谁在用不用每次去联查上机记录表。on_record上机记录表idmember_id会员 IDcomputer_id电脑 IDstart_time上机时间end_time下机时间duration_minutes上机时长分钟total_amount消费金额status状态进行中/已结束这张表是计费的核心。注意duration_minutes用整数存储不要用浮点数金额用 DECIMAL规则同上。goods商品表idname商品名price售价DECIMALstock库存sale_order销售订单表idmember_id会员 ID可空散客购买也可以goods_id商品 IDquantity数量amount总金额create_time下单时间这里 sale_order 和 goods 其实还可以用 order_detail 做拆开但为了保持简单我直接把商品快照信息冗余在订单里或者干脆关联 goods 表两种做法都可以。课程设计层面直接关联 goods 表加一个 quantity 就够用了。3.2 计费逻辑临时上机、学时卡与暂停计费是一套管理系统最容易被忽略、又最体现专业度的地方。我给你拆解三种计费场景临时上机散客来了选一台空闲电脑直接开始计时。下机时按“分钟 × 单价”算钱。这里有一个细节计费规则通常是不足一小时按一小时算也就是向上取整。比如你上机 61 分钟单价 4 元/小时实际上要按 2 小时收 8 元。这个规则在代码里要专门处理不能粗暴地“总分钟 × 每分钟单价”。学时卡用户用户购买 100 小时上机时直接扣减剩余学时不需要实时算钱。这种模式带来的问题是如果中间去上厕所暂停了半小时这半小时扣不扣合理的设计是提供一个“暂停”功能暂停期间不扣学时。暂停功能暂停的本质是锁定当前机器时间暂停累计。on_record表里需要增加pause_start_time字段暂停时记录时间恢复时把暂停时长累加到pause_duration里。下机时有效时长 总时长 - 暂停时长。考虑到这只是一个“简单”的网吧管理系统你可以先不做暂停功能但duration_minutes字段请务必设计好否则后续扩展非常麻烦。我在设计时就预留了这个字段后面想加暂停功能只需要多两张状态字段不用改表结构。4. 后端分层与核心接口实现后端代码的组织方式直接决定了项目可维护性。这个系统的后端我采用标准的三层结构Controller控制层→ Service业务层→ Mapper数据访问层。每一层职责单一代码会非常清爽。4.1 分层思想Controller-Service-Mapper很多新手容易犯一个错误把所有逻辑都堆在 Controller 里一个方法写几十行还直接操作 Mapper。这样做的问题是一旦业务复杂了一个 Controller 方法动辄改上百行排查问题非常痛苦。规范的写法是Controller 只做参数接收、参数校验、调用 Service、返回结果这四件事。它是“调度员”不关心业务怎么实现。Service 负责业务逻辑比如上机时要检查会员状态、检查电脑状态、创建上机记录、更新电脑状态这些步骤是一个完整的业务事务必须放在 Service 里。Mapper 只负责数据库交互。简单的单表操作用 MyBatis-Plus 的BaseMapper直接搞定复杂的多表查询用自定义 SQL。包结构我建议这样建com.example.netbar ├── controller # 控制器层 ├── service # 业务逻辑层 ├── mapper # 数据访问层 ├── entity # 实体类 ├── dto # 数据传输对象接收前端请求参数 ├── vo # 视图对象返回给前端的数据 ├── config # 配置类拦截器、跨域等 └── common # 公共类统一返回结果、异常处理user登录这种公用模块不需要直接放一个user表和对应模块就行。4.2 上机/下机接口的完整逻辑上机和下机是这套系统最核心的两个接口而且都涉及多表状态变更需要用事务来保证一致性。我以“上机”为例把完整逻辑写出来事务开始 1. 根据 memberId 查询会员信息 2. 校验会员状态是否正常未挂失、未禁用 3. 校验余额是否足够临时卡需要余额 0 4. 根据 computerId 查询电脑状态 5. 校验电脑状态是否为“空闲” 6. 检查该会员是否已在其他电脑上机防止重复上机 7. 创建上机记录start_time now状态 进行中 8. 更新电脑状态为“使用中”绑定 current_member_id 9. 事务提交第 6 步非常关键。如果没有这一步一个会员可以同时在两台电脑上“上机”这在网吧场景是不允许的。查询方式很简单查on_record表里是否存在member_id ? AND status 进行中的记录。下机逻辑对应如下事务开始 1. 根据 computerId 查询电脑信息 2. 查询当前进行中的上机记录 3. 计算上机时长结束时间 - 开始时间 4. 计算消费金额按计费规则 5. 更新会员余额临时卡扣款学时卡扣学时 6. 更新上机记录end_time、duration_minutes、total_amount、状态 7. 更新电脑状态为“空闲”清空 current_member_id 8. 事务提交有一个细节值得注意计算时长用Duration.between()。Java 8 的java.time包专门为这种场景设计了 API不要自己用毫秒差再除以 60000 这么麻烦。代码示例long minutes Duration.between(record.getStartTime(), LocalDateTime.now()).toMinutes(); // 向上取整不足一小时按一小时算 long billableHours (long) Math.ceil(minutes / 60.0); BigDecimal amount unitPrice.multiply(BigDecimal.valueOf(billableHours));用BigDecimal做乘法避免 DOUBLE 运算误差。这是金额计算的基本素养。4.3 对外接口应该放在哪里这是一个在网上被反复问的问题“Spring Boot 对外提供的接口给第三方应该放在哪里是单独的服务还是放在对应模块里”我明确告诉你取决于调用方是谁以及部署形态。如果第三方调用方比如手机 App、自助售货机和网吧系统部署在同一个局域网内而且是同一个团队维护的那直接在当前项目里单独建一个controller/api包用/api/前缀区分即可。Controller 内部继续复用 Service 层代码不重复。如果你是给外部合作方提供接口而且这几个合作方之间的鉴权规则、流量策略、发布节奏都不一样那我的建议是拆成独立服务。因为一旦共用一个进程任何一方的紧急变更都可能影响全部调用方风险不可控。对于这个网吧管理系统来说项目内部建一个api包就够了不需要搞微服务。乱上微服务反而会把简单的事情复杂化。5. 前端页面与交互实现前端部分可能是很多后端开发者最头疼的但用 Thymeleaf 之后压力小很多。页面就是普通 HTML数据用服务端渲染直接填充不需要写大量 JavaScript。5.1 Thymeleaf 片段复用一个管理系统通常有多个页面登录页、会员管理页、电脑管理页、上机操作页、商品销售页、统计页。如果每个页面都从零写完整的 HTML 结构sidebar、header 这些公共部分要复制粘贴好几遍后期改一个菜单就要改所有页面——这是很蠢的做法。Thymeleaf 提供了th:fragment片段机制完美解决这个问题。我把公共的侧边栏和头部抽离到一个layout.html文件里!-- layout.html -- nav classsidebar th:fragmentsidebar !-- 菜单项 -- /nav div classheader th:fragmentheader !-- 顶栏 -- /div然后在其他页面里引入!-- member.html -- div th:replace~{layout :: sidebar}/div关键是页面的权限控制。不同角色看到的菜单不一样我直接在侧边栏片段里用th:if${session.user.role} ADMIN判断。不需要引入复杂的安全框架拦截器加模板判断就足够。这种“服务端渲染 模板判断”的方式比前端拿到角色再动态渲染菜单要简单可靠得多。5.2 上机操作与轮询刷新电脑管理页是收银员使用频率最高的页面需要展示所有电脑的实时状态空闲 / 使用中 / 维护中。页面加载时后端把电脑列表和每台电脑的状态查询出来渲染到页面上。但状态是动态的比如 A 区 3 号机刚刚有人下机如果不刷新页面前台看到的状态就是错的。怎么解决两个思路WebSocket 实时推送状态一变服务端主动推给浏览器。这种方式技术含量高但对一个简单管理系统来说太重了。轮询刷新前端每隔几秒请求一次后端接口把computer列表以 JSON 形式返回前端 JS 渲染到表格里。我最终选择了轮询因为实现太简单了写一个ComputerController返回电脑列表 JSON前端用setInterval每 5 秒请求一次重新渲染表格。网吧规模一般不超过 200 台电脑5 秒一次的轻量请求完全没压力。如果你希望页面上的电脑状态是“格子”而不是表格可以用类似“机位图”的形式展示每个格子是一台电脑绿色空闲、红色使用中。这种展示方式对网吧老板来说特别直观而且实现起来也简单就是遍历computerList动态生成卡片。6. 本地运行、打包与监控项目写完了总要跑起来。这一部分我讲讲如何用 IDEA 社区版或 VSCode 把项目跑起来以及如何用 Actuator 做健康监控。6.1 IDEA 社区版与 VSCode 下如何跑起来这是一个高频问题“IntelliJ IDEA 社区版怎么用 Spring Boot”很多人以为社区版不能开发 Spring Boot其实是误解。社区版族缺的只是 Spring Initializr 的项目生成向导和部分企业级框架支持但 Spring Boot 项目本质是一个 Maven 项目直接用社区版打开就能识别运行。社区版跑 Spring Boot 的做法是打开 https://start.spring.io 在线生成 Spring Boot 项目压缩包并下载解压。用 IDEA 社区版直接File - Open选择解压后的目录IDEA 会识别pom.xml自动下载依赖。找到启动类NetbarApplication.java右键Run即可。如果你用的是 VSCode流程也类似安装 Java 扩展包和 Spring Boot 扩展然后在项目根目录下打开终端执行mvn spring-boot:run命令。这种方式和 IDEA 大同小异只是换了一个编辑器。6.2 健康检查用 Actuator 就够了有人问“Spring Boot 实现监控都有哪些需求和功能”。对这套系统来说最基本的监控需求有两个应用还活着吗内存和线程状况如何引入spring-boot-starter-actuator后这些信息直接通过端点暴露。management: endpoints: web: exposure: include: health,info,metrics配置完成后访问http://localhost:8080/actuator/health就能看到应用状态返回{status:UP}说明一切正常。这是部署后最简单的健康检查手段。如果你想要更友好的可视化界面可以再花 10 分钟把一个 Spring Boot Admin 服务端跑起来把项目注册进去就能看到 CPU、内存、线程数的图形化指标。但就网吧系统这个体量Actuator 自带的 health 端点真的够用了。我见过很多人给这种小项目加了一整套链路追踪、日志采集最后除了给自己增加维护负担没有产生任何实际价值。7. 常见问题与排查实录这一部分写写我在开发和部署这套系统时实际踩过的坑以及网上高频出现的报错问题帮你提前避雷。7.1 端口被占用现象启动报错Port 8080 was already in use。原因本机已有其他程序占用 8080 端口。排查Windows 执行netstat -ano | findstr 8080Mac 执行lsof -i :8080找到占用端口的进程 ID结束进程或者直接改项目端口即可。我建议直接把端口改成 8081 或者 9090省得每次启动都要检查。7.2 时区导致的时间差八小时现象数据库里存的时间和本地时间差了 8 个小时。原因JDBC 连接串没有指定时区默认用了数据库服务器的时区和本地东八区不一致。解决在application.yml的数据库连接 URL 上显式加serverTimezoneAsia/Shanghai完整连接串示例url: jdbc:mysql://localhost:3306/netbar?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai这个坑极其常见而且不容易发现因为不报错只是数据不对。7.3 MyBatis 常见报错这些报错我在刚开始用 MyBatis-Plus 时都遇到过报错 1Invalid bound statement (not found): com.xx.mapper.MemberMapper.selectById原因启动类没有扫描到 Mapper 接口。解决启动类加MapperScan(com.example.netbar.mapper)或者在每个 Mapper 接口上标Mapper。报错 2The error occurred while setting parameters原因SQL 里的参数和接口方法的参数对不上缺了Param注解。多参数时一定要写Param(xxx)别偷懒。报错 3数据库字段user_name映射不到实体类的userName原因开启了驼峰映射才自动转换没开的话字段名对不上。配置加一行mybatis-plus: configuration: map-underscore-to-camel-case: true7.4 多表查询 JSON 循环引用现象查询会员及其上机记录时接口报错或者返回的数据出现$ref这种奇怪的引用。原因实体类之间互相引用会员里有上机记录上机记录里有会员将实体直接序列化为 JSON 时就形成了循环。解决查询结果不要直接返回实体类而是新建 VO视图对象只包含页面需要的字段比如会员姓名、电话、上机时长、金额。这不仅是解决循环引用问题的最佳方案也是规范工程里推荐的写法。另外JsonIgnore可以快速忽略某个字段但那只是临时补丁正确做法依然是 VO 隔离。为了方便你排查我把这些问题整理成一份速查表问题核心原因解决方式端口占用其他程序占用 8080改端口或杀进程时间差八小时连接串缺时区参数加 serverTimezoneAsia/ShanghaiMapper 方法找不到未扫描 Mapper 接口加 MapperScan参数错误多参数缺 Param补充 Param 注解字段映射为 null未开启驼峰映射配置 map-underscore-to-camel-caseJSON 循环引用实体互相引用用 VO 隔离字段8. 扩展思路这个系统还能往哪走项目如果只是做完功能就停在这里说实话有点可惜。它已经具备了一个 Web 系统的完整骨架接下来按需扩展非常自然。第一个方向是加入定时任务。比如会员临时上机后老板要求“超过 12 小时必须自动下机”。这就需要在项目里加一个 Spring 的Scheduled定时任务定时扫描超时的上机记录并自动执行下机逻辑。这是我推荐你优先尝试的扩展点因为它能让你接触到任务调度的知识而且逻辑清晰、不容易失控。第二个方向是会员分级和优惠策略。普通会员 95 折黄金会员 9 折学时卡有额外赠送时长。这涉及计费策略的重构把单价计算抽出来作为一个策略类将来不管怎么改优惠规则都不会影响主流程。这个方向能让你练习设计模式的实际应用尤其是策略模式。第三个方向是自助上机。网吧门口放一台自助机用户刷身份证或者扫码选一台空闲电脑在线支付后自动开台。这就需要一个面向自助机的对外接口正是我在前面提到的api独立模块接口的典型应用场景。最后一个方向是把数据指标做厚。比如营业额趋势图、热门商品排行、上机高峰时段分析。技术上用RestController返回 JSON 给前端图表库比如 ECharts做数据可视化的练习。这个方向能让你熟悉聚合查询比如按天分组、按月分组的 SQL 写法。我的建议是你先把这个系统的核心流程跑通不要把扩展功能一开始就全加进去。项目规模越大报错越难排查一旦卡住很容易消磨掉信心。先有一个能跑起来的完整系统比一个装了半截子华丽功能但跑不起来的系统重要一万倍。我在实际操练这个项目的过程中最大的体会就是一个项目的技术难度不取决于用了多少高级框架而取决于你把业务细节想得多透。网吧管理系统看似简单但上机、计费、状态流转这些逻辑一旦认真做需要思考的边界情况非常多。把这些边界情况都处理好了你的编码能力和业务建模能力都会有一个肉眼可见的提升。如果你从这个项目开始老老实实走完一遍需求分析、数据库设计、后端开发、前端页面、测试部署的完整流程下一个项目再遇到什么管理系统你都会觉得驾轻就熟。