十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

SpringBoot3+Vue3实现乒乓球馆预约系统:从数据库到部署的完整实战

SpringBoot3+Vue3实现乒乓球馆预约系统:从数据库到部署的完整实战 想快速落地一个前后端分离的预约类管理系统很多同学第一反应是搜开源项目但找到的仓库要么版本太老要么业务太简单根本撑不起毕设或课程设计的完整度。这次我们直接按“JAVA SpringBoot3 Vue.js3 MySQL”这套当前主流技术栈把乒乓球馆预约系统从数据库设计、后端接口、前端页面到部署验证完整拆一遍。它的核心价值不在于“能预约”这三个字而在于这套骨架可以直接迁移到羽毛球馆、健身房、会议室预约等几乎所有类似业务上。先说重点这个项目的前后端完全分离后端是SpringBoot3 MyBatis-Plus Spring Security/JWT前端是Vue3 Vite Element Plus Pinia数据层用MySQL 8.x。JAVA 17 是 SpringBoot3 的硬性前置要求需要用 JDK 17 而不是 JDK 8前端 Node.js 建议 18 以上Vite 6/7 都行。功能上至少包含用户注册登录、场地管理、场次预约、取消预约、个人订单、管理后台这几个主模块。文章会带你完成四件事第一把环境版本和对齐关系搞清楚避免 SpringBoot3 和旧版依赖冲突的坑第二按“建库 → 后端 → 前端”的顺序把项目跑起来第三实际测试一次“用户登录 → 查看场次 → 预约场地 → 取消预约”的完整链路第四给你一组接口设计和常见的排查清单方便后面自己扩展功能。1. 核心能力速览整个预约系统的设计目标很明确普通用户能查到当天哪些乒乓球台空闲、几点到几点能约管理员能维护场地和场次所有操作都有记录。从软件工程角度看它覆盖了完整的用户体系、资源管理、订单状态机和 REST API 设计。能力项说明项目类型前后端分离的预约类管理系统适合毕设、课设、Java Web 练手后端技术SpringBoot3、Spring MVC、MyBatis-Plus、Spring Security、JWT前端技术Vue3、Vite、Element Plus、Pinia、Axios、Vue Router数据库MySQL 8.x配合 Navicat 或命令行完成建库与数据初始化JDK 版本JDK 17SpringBoot3 强制要求不能使用 JDK 8核心功能用户登录注册、场地管理、场次生成、在线预约、取消预约、订单查询、后台管理启动方式后端 Maven 命令启动 前端 npm 命令启动最终通过浏览器访问前端页面接口认证JWT Token 方式登录后请求头携带Authorization: Bearer token角色权限普通用户和管理员通过角色区分可访问接口是否支持批量任务场景预订支持按时间段批量生成Excel 批量导入场地可按需开发适合场景体育馆资源管理、俱乐部预约、Java Web 全栈学习、毕业设计二次开发这套系统最大的特征是可解释性好。比起网上那些“整包下载后双击运行”的教程从数据库起步写出来的预约系统每个模块你都能说清楚“为什么这么设计”这是毕设答辩最看重的东西。2. 适用场景与使用边界预约系统的业务边界比一般 CRUD 项目更值得分析。适合的使用场景包括校体育馆乒乓球室的在线预约社区球馆的时段管理Java 全栈项目的学习与毕设二次开发。用户端需要解决的核心痛点是“不知道哪个台子空闲、怎么预约、怎么取消”管理端解决的是“场地数据怎么维护、预约记录怎么统计、时间冲突怎么避免”。从业务角度说有三个使用边界需要提前确认否则系统开发到一半会改需求改到崩溃第一预约的计时单位。是按小时切分场次比如 14:00-15:00、15:00-16:00还是允许用户自定义任意起止时间。按小时切分场次数据库设计简单前端展示直观冲突判断也容易自定义时间段体验更好但写冲突检测时要处理“部分重叠”的情况实现复杂度明显上升。建议第一版先做固定场次这也是多数预约类项目的主流做法。第二收费还是免费。如果只是课外活动室或练习室预约不需要订单金额字段如果需要支付则必须引入支付渠道这类项目一般把“订单金额”和“支付状态”先设计好实际对接第三方支付再扩展。第三是否需要邮件/短信通知。很多毕设项目的通知功能只是“系统内消息”也就是登录后能看到预约成功提示就够了不需要真的接入阿里云短信或邮件服务。材料里没有提及第三方通知所以内容先在站内消息与订单状态上做闭环不强行扩展。使用边界上必须强调版权与数据合规。如果系统后续要上线给真实球馆使用用户的手机号、微信身份等个人信息应做脱敏和授权采集预约记录不要保留不必要的时间长度场地照片如果来自网络需要确认版权。这套系统的能力边界是“业务资源的管理与预约”不包含支付、退款、短信网关等强外部依赖模块需要的话可以作为二期功能扩展。3. 环境准备与前置条件写代码之前先把环境版本对齐这一步经常决定后面能否顺利启动。下面是基于 SpringBoot3 Vue3 的技术栈推荐环境清单实际版本可以根据机器情况做小范围浮动但大版本不要动。软件推荐版本用途与说明JDK17 或 21SpringBoot3 基于 Jakarta EE 9最低需要 JDK 17Maven3.8管理后端依赖IDEA 自带也可以Node.js18 或 20 LTS运行 Vue3 前端构建工具 ViteMySQL8.0业务数据库存储用户、场地、场次、订单Navicat / DataGrip任意数据库可视化操作也可以用命令行IDEA2023后端开发主工具社区版足够VSCode / WebStorm任意前端开发工具视个人习惯选择检查 Java 版本在命令行执行java -version输出中包含17或21即可。如果本机装了多个 JDK务必确保 IDEA 的 Project SDK 和 Maven 的 Java 版本都指向 JDK 17。SpringBoot3 项目里常见的报错Unsupported class file major version 61.0就是因为运行时 JVM 低于 17。检查 Node.js 和 npmnode -v npm -vMySQL 安装完成后需要确认服务已经启动。以命令行方式测试登录mysql -uroot -p输入密码后能进入mysql提示符说明数据库连接正常。如果遇到ERROR 2002 (HY000): Cant connect to local MySQL server through socket /tmp/mysql.sock通常表示 MySQL 服务没有启动。Windows 环境可以在“服务”里启动 MySQL80Linux 环境用 systemctl 启动macOS 用 brew services 启动。# Linux 启动 MySQL 示例 sudo systemctl start mysqld sudo systemctl status mysqld还有一个容易忽略的前置条件后端默认端口和 MySQL 端口不要冲突项目配置里指定后端端口为 8080、MySQL 为 3306。前端 Vite 默认使用 5173 端口开发阶段通过 Vite 代理把/api转发到后端 8080这样可以避免跨域问题。4. 数据库设计预约系统的表结构预约系统的核心是“场地 — 场次 — 订单”三条主链再加上“用户”和“角色”两条支撑链。数据库设计是否合理直接决定后面冲突判断和统计报表好不好写。先建库CREATE DATABASE IF NOT EXISTS pingpong_reservation DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE pingpong_reservation;需要使用的核心表如下用户表sys_user用于存放登录账号、密码、手机号、角色。密码字段不要存明文使用 BCrypt 加密后存储。role字段区分普通用户和管理员简单起见也可以用单独的sys_role表做多角色设计但预约系统用字符串角色足够。CREATE TABLE sys_user ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 用户ID, username VARCHAR(50) NOT NULL UNIQUE COMMENT 用户名, password VARCHAR(100) NOT NULL COMMENT BCrypt加密密码, nickname VARCHAR(50) COMMENT 昵称, phone VARCHAR(20) COMMENT 手机号, role VARCHAR(20) NOT NULL DEFAULT USER COMMENT 角色: USER/ADMIN, status TINYINT DEFAULT 1 COMMENT 状态: 1启用 0禁用, created_time DATETIME DEFAULT CURRENT_TIMESTAMP, updated_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) COMMENT 用户表;场地表venue存乒乓球台的基础信息。包括台号、位置、是否可用、描述。有些项目把场地和乒乓球台拆成两层如果场馆本身有多个分区则需要增加一个场馆层这里为保持清晰场地表直接对应每一张乒乓球台。CREATE TABLE venue ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 场地ID, venue_name VARCHAR(100) NOT NULL COMMENT 场地名称例如 1号台, location VARCHAR(200) COMMENT 位置描述, status TINYINT DEFAULT 1 COMMENT 状态: 1可用 0停用, description VARCHAR(500) COMMENT 场地描述, created_time DATETIME DEFAULT CURRENT_TIMESTAMP ) COMMENT 乒乓球场地表;场次表venue_slot场次表是预约系统的关键。一个场地一天拆成多个时间段比如 10:00-11:00、11:00-12:00。slot_date存场次日期start_time和end_time存时间。为避免每个场地每天手写十几条数据可以在管理员操作里提供“按日期批量生成场次”的功能。一个场地在某一天的某个时间段只能对应一条预约订单所以场次和订单是一对一的关系。设计中也可以把“预约状态”直接放在场次表上待预约/已预约/已取消/已完成但更规范的做法是场次表只描述时间资源订单表记录谁约了这样支持取消后重新预约。CREATE TABLE venue_slot ( id BIGINT PRIMARY KEY AUTO_INCREMENT, venue_id BIGINT NOT NULL COMMENT 所属场地, slot_date DATE NOT NULL COMMENT 场次日期, start_time TIME NOT NULL COMMENT 开始时间, end_time TIME NOT NULL COMMENT 结束时间, created_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_venue_slot (venue_id, slot_date, start_time) ) COMMENT 场次表;这里的唯一键uk_venue_slot很重要它从数据库层杜绝同一个场地同一时间重复生成场次是防止后期预约冲突的第一道防线。预约订单表reservation_order订单表保存一次完整的预约行为。字段包含哪个用户预约了哪个场次、预约时间、状态、备注。由于场次表已经绑定了场地和时间订单表不需要再冗余场地名称、开始时间等字段查询时通过关联查询拿到。CREATE TABLE reservation_order ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(64) NOT NULL UNIQUE COMMENT 订单编号, user_id BIGINT NOT NULL COMMENT 预约用户, slot_id BIGINT NOT NULL COMMENT 场次ID, status VARCHAR(20) NOT NULL DEFAULT RESERVED COMMENT 状态: RESERVED/COMPLETED/CANCELLED, remark VARCHAR(255) COMMENT 备注, created_time DATETIME DEFAULT CURRENT_TIMESTAMP, updated_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) COMMENT 预约订单表;订单状态建议用字符串枚举而不是数字这样读代码时一眼能看出RESERVED是已预约、CANCELLED是已取消不需要再对状态字典。5. 后端项目搭建与核心代码后端采用 SpringBoot3 MyBatis-Plus 搭建。MyBatis-Plus 能让单表 CRUD 代码量大幅减少BaseMapper直接提供 insert、selectById、updateById 等基础能力适合预约系统这种以单表操作和简单关联为主的场景。5.1 Maven 依赖创建一个 SpringBoot 项目pom.xml中核心依赖如下parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.2.5/version relativePath/ /parent properties java.version17/java.version /properties dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-spring-boot3-starter/artifactId version3.5.7/version /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-validation/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-security/artifactId /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-api/artifactId version0.11.5/version /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-impl/artifactId version0.11.5/version scoperuntime/scope /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-jackson/artifactId version0.11.5/version scoperuntime/scope /dependency /dependencies这里使用的mybatis-plus-spring-boot3-starter是专门适配 SpringBoot3 的版本不要误引旧版mybatis-plus-boot-starter否则会出现自动配置不生效的问题。5.2 配置文件application.yml中配置数据源、MyBatis-Plus 和 JWTserver: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/pingpong_reservation?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue username: root password: your_mysql_password jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: Asia/Shanghai mybatis-plus: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: id-type: auto jwt: secret: your-secret-key-at-least-256-bits-long-change-me expire-hours: 24注意allowPublicKeyRetrievaltrue是 MySQL 8 连接时常见的参数不配置可能遇到公钥检索异常。5.3 登录认证与 JWT由于涉及用户登录和角色权限使用 Spring Security JWT 实现无状态认证。核心思路用户提交用户名密码后端校验 BCrypt 密码后生成 JWT Token。后续请求带着 Token后端通过过滤器解析用户信息存入 SecurityContext。工具类生成 TokenComponent public class JwtUtil { Value(${jwt.secret}) private String secret; Value(${jwt.expire-hours}) private Long expireHours; public String generateToken(Long userId, String username, String role) { return Jwts.builder() .setSubject(String.valueOf(userId)) .claim(username, username) .claim(role, role) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() expireHours * 3600 * 1000)) .signWith(SignatureAlgorithm.HS256, secret) .compact(); } public Claims parseToken(String token) { return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody(); } }JWT 过滤器需要在 Spring Security 配置中注册这里不展开完整代码思路就是把Authorization头的 Token 解析出来放行到 Controller 层。5.4 预约接口设计与防冲突实现预约系统的核心业务逻辑是“用户预约某个场次”这一步必须做冲突校验。先看 ControllerRestController RequestMapping(/api/reservation) public class ReservationController { Autowired private ReservationService reservationService; PostMapping public Result? create(RequestBody ReservationCreateDTO dto) { reservationService.reserve(dto.getSlotId()); return Result.success(预约成功); } DeleteMapping(/{orderId}) public Result? cancel(PathVariable Long orderId) { reservationService.cancel(orderId); return Result.success(取消成功); } GetMapping(/my) public Result? myReservations(RequestParam(defaultValue 1) Integer page, RequestParam(defaultValue 10) Integer size) { return Result.success(reservationService.pageMyOrders(page, size)); } }Service 层中预约的核心逻辑需要两步判断场次是否仍然有效也就是关联的场地状态是可用的。场次当前是否没有被别人预约。最简单的方式是在reservation_order表上增加slot_id的唯一索引同时代码里先查一次状态。从数据库层面看更稳妥的方案是给reservation_order.slot_id加唯一索引保证一个场次只能有一条有效订单不过需要注意“已取消”的订单也需要保留记录所以有效订单唯一索引需要设计成“针对未取消状态的唯一约束”。MySQL 8 的生成列或者单独的状态表可以支持这种需求但作为课程设计或毕设数据库层加唯一索引 Service 层状态判断已经足够核心是防止并发场景下“同一个场次被预约两次”。Service public class ReservationServiceImpl implements ReservationService { Override Transactional(rollbackFor Exception.class) public void reserve(Long slotId) { VenueSlot slot slotMapper.selectById(slotId); if (slot null) { throw new BusinessException(场次不存在); } Venue venue venueMapper.selectById(slot.getVenueId()); if (venue null || venue.getStatus() ! 1) { throw new BusinessException(场地不可用); } if (slot.getSlotDate().isBefore(LocalDate.now())) { throw new BusinessException(不能预约过去的场次); } long count orderMapper.selectCount(new LambdaQueryWrapperReservationOrder() .eq(ReservationOrder::getSlotId, slotId) .eq(ReservationOrder::getStatus, RESERVED)); if (count 0) { throw new BusinessException(该场次已被预约请选择其他时间); } ReservationOrder order new ReservationOrder(); order.setOrderNo(generateOrderNo()); order.setUserId(CurrentUserHolder.getUserId()); order.setSlotId(slotId); order.setStatus(RESERVED); orderMapper.insert(order); } }这段代码演示了预约的主流程。CurrentUserHolder是从 JWT 解析出的当前登录用户信息实际项目可封装为 ThreadLocal 工具类。5.5 管理端批量生成场次管理端需要支持按日期批量生成场次。比如管理员选择 1 号台和 2 号台、日期范围、开始时间 10:00、结束时间 22:00、时间段长度 60 分钟系统自动生成所有不重复的场次。Override Transactional(rollbackFor Exception.class) public void batchGenerateSlots(BatchSlotGenerateDTO dto) { ListVenueSlot slots new ArrayList(); for (Long venueId : dto.getVenueIds()) { LocalDate startDate dto.getStartDate(); while (!startDate.isAfter(dto.getEndDate())) { LocalTime startTime dto.getStartTime(); while (startTime.isBefore(dto.getEndTime())) { VenueSlot slot new VenueSlot(); slot.setVenueId(venueId); slot.setSlotDate(startDate); slot.setStartTime(startTime); slot.setEndTime(startTime.plusMinutes(dto.getSlotMinutes())); slots.add(slot); startTime startTime.plusMinutes(dto.getSlotMinutes()); } startDate startDate.plusDays(1); } } saveBatch(slots); }批量生成时要注意“跨天覆盖”的问题如果结束时间是 22:00最后一次生成应为 21:00-22:00。如果设置 23:00 后跨天一般到 23:00 截止不放跨天场次。数据库的唯一索引uk_venue_slot会拦截重复插入的场次Service 也可以先删除日期范围内的旧场次再批量插入给管理员一个重建操作。6. 前端项目搭建与页面设计前端使用 Vue3 Vite 创建。npm create vitelatest reservation-web -- --template vue cd reservation-web npm install安装 UI 组件库、路由和状态管理npm install element-plus axios pinia vue-router目录结构建议src/ ├── api/ # Axios 接口封装 │ ├── auth.js │ ├── venue.js │ └── reservation.js ├── router/index.js # 路由配置 ├── store/user.js # 用户状态 ├── views/ │ ├── Login.vue │ ├── Register.vue │ ├── Home.vue │ ├── SlotList.vue # 场次选择页面 │ ├── MyOrder.vue # 我的预约 │ └── admin/ │ ├── VenueManage.vue │ └── SlotManage.vue └── main.jsAxios 封装时统一加 Token 请求头。核心拦截器代码如下import axios from axios const request axios.create({ baseURL: /api, timeout: 10000 }) request.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization Bearer ${token} } return config }) request.interceptors.response.use( response response.data, error { if (error.response error.response.status 401) { localStorage.removeItem(token) window.location.href /login } return Promise.reject(error) } ) export default requestVite 开发代理配置在vite.config.js里添加export default defineConfig({ plugins: [vue()], server: { port: 5173, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } })通过代理前端请求/api/reservation/list时Vite 会自动转发到后端8080端口避免开发阶段的跨域报错。页面设计上用户端核心是“场地选择 日期间选择 场次预约”。首页可以展示所有可用乒乓球台点击某个球台后进入按日期查看场次的页面。场次卡片有三种状态可预约、已约满、已过期。管理员端展示场地管理表格可以新增场地、停用场地、生成场次、查看某天的所有订单。7. 系统联调与效果验证启动顺序建议是先启动 MySQL再启动后端最后启动前端。后端启动在 IDEA 中运行PingpongApplication.java或者在项目根目录执行mvn spring-boot:run看到类似下图的日志代表启动成功Tomcat started on port(s): 8080 (http) Started PingpongApplication in 3.2 seconds前端启动cd reservation-web npm run dev浏览器访问http://localhost:5173。接下来按下面的流程验证系统是否可用。测试用例 1注册与登录打开首页进入注册页。输入用户名、密码、手机号注册成功后自动跳转登录页。输入正确账号密码登录观察浏览器 localStorage 是否写入 token。输入错误密码预期返回“用户名或密码错误”。测试用例 2查看场次与预约登录后进入场地列表。选择“1号乒乓球台”。日期选择“明天”后台管理端提前生成了场次页面会展示 10:00-11:00 到 21:00-22:00 的所有可用场次。点击“预约”按钮弹出确认框点击确认。页面提示“预约成功”该场次的按钮变为“已预约”并禁用。测试用例 3重复预约拦截使用另一个用户账号登录。选择同一个场次。点击预约预期后端返回“该场次已被预约”的提示而不是成功创建订单。测试用例 4取消预约登录用户 A进入“我的预约”。找到一条已预约订单点击“取消预约”。刷新场次页面该场次重新变为“可预约”。测试用例 5管理员查看订单使用管理员账号登录。进入管理后台选择某一天。页面展示当天所有场次的订单列表包括用户名、场地编号、时间段、订单状态。测试完成后可以打开后端控制台观察 MyBatis-Plus 输出的 SQL 日志。这里有一个比较实用的排查技巧如果预约成功但页面状态没刷新优先检查前端调用的接口返回是否正常再看reservation_order表的slot_id字段是否和venue_slot的 id 完整对应。8. 接口 API 设计参考预约系统的后端接口按资源维度可以划分为以下几组。接口路径使用 RESTful 风格请求和响应均为 JSON。方法路径说明权限POST/api/auth/register用户注册匿名POST/api/auth/login登录并获取 JWT Token匿名GET/api/venue/list获取可预约场地列表登录用户POST/api/venue新增场地管理员PUT/api/venue/{id}修改场地信息管理员DELETE/api/venue/{id}删除或停用场地管理员GET/api/slot/list按场地和日期查询场次登录用户POST/api/slot/batch批量生成场次管理员POST/api/reservation创建预约登录用户DELETE/api/reservation/{id}取消预约登录用户GET/api/reservation/my当前用户预约列表登录用户GET/api/reservation/admin/list管理端预约列表管理员以登录接口为例请求curl -X POST http://localhost:8080/api/auth/login \ -H Content-Type: application/json \ -d {username:test,password:123456}响应示例{ code: 200, message: 登录成功, data: { token: eyJhbGciOiJIUzI1NiJ9.xxxxx.yyyyy, username: test, role: USER } }创建预约时请求头带上 Tokencurl -X POST http://localhost:8080/api/reservation \ -H Content-Type: application/json \ -H Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.xxxxx.yyyyy \ -d {slotId: 123}如果 Token 缺失或过期预期返回 401 状态码。这种接口设计的好处是职责清晰。前端页面能对应到具体的 API不会出现一个接口干好几件事的情况。功能扩展时比如要增加“收藏场地”只需要新增 venue_favorite 表和对应接口不需要动预约主链路。9. SpringBoot3 与 Vue3 常见问题排查开发过程中最容易踩的坑集中在版本不匹配、依赖冲突、跨域和数据库连接上。这里整理一份排查清单。问题现象可能原因排查方式解决方案SpringBoot3 项目无法启动报Unsupported class file major version使用了 JDK 8 或 11 运行命令行执行java -version安装并切换 JDK 17/21IDEA 中重新设置 Project SDK引入 MyBatis-Plus 后 Mapper 无法注入使用了旧版mybatis-plus-boot-starter而非 SpringBoot3 starter查看 Maven 依赖树使用mybatis-plus-spring-boot3-starter并配置MapperScan前端请求接口报 CORS 错误后端未配置跨域或前端没有走代理查看浏览器 Network 请求地址使用 Vite proxy 将/api转发到后端 8080登录接口正常其他接口返回 401JWT Token 未解析或过滤链配置错误在过滤器中打印 Token 解析异常检查Authorization头格式是否为Bearer tokenMySQL 连接报Public Key Retrieval is not allowedMySQL 8 缓存认证机制问题查看完整异常信息数据库 URL 增加allowPublicKeyRetrievaltrue参数点击“预约”后没有反应场次列表接口未返回该场次或后端报错打开浏览器开发者工具查看 Console/Network后端查看控制台 SQL 日志确认场次是否存在前端页面中文乱码数据库连接 characterEncoding 配置错误查看页面显示情况URL 增加characterEncodingutf8确认建表时使用 utf8mb4预约提示“该场次已被预约”但数据表中没有订单Service 查询条件只查了状态字段但取消后仍保留记录查看 SQL where 条件确认查询条件同时筛选statusRESERVED而非查所有状态相同场次重复生成批量生成场次时没有先删除旧数据查看数据库唯一键约束数据库增加uk_venue_slot唯一索引Service 层先删除日期范围内旧场次前端执行npm run dev提示 Node 版本过低Node.js 版本太老无法支持 Vite 6node -v检查版本使用 Node.js 18 或 20 LTS后端启动成功但数据库表不存在没有执行建表 SQL或表名与实体映射不一致在 Navicat 中查看数据库表执行设计章节的建表 SQL并检查实体类TableName请求预约接口很慢数据库连接池配置不合理或 N1 查询观察日志耗时关联查询使用 MyBatis-Plus 分页插件必要时开启 SQL 分析出现问题时推荐排查顺序固定是“看日志 → 看请求路径 → 看 SQL”。后端先看 IDEA 控制台有没有异常堆栈和 MyBatis 打印的 SQL前端先看浏览器 Network 面板中请求地址、状态码、响应体。这样基本能定位 80% 的问题。10. 资源占用与性能观察预约系统不像 AI 模型那样有显存和 GPU 需求但部署环境仍然有一些需要关注的资源指标。开发环境下后端 SpringBoot3 进程默认 JVM 堆内存可能在 256M 到 512M 之间具体取决于机器配置。前端npm run dev属于 Node.js 开发服务内存占用约 300M 到 800M。MySQL 8 是资源占用的大头平均空闲状态占用 300M 到 600M 内存如果机器内存只有 8G建议给 MySQL 单独分配 1G 到 2G 的缓冲池。生产部署或性能测试时可以重点观察以下四个指标资源/指标建议观察方式优化方向MySQL 连接数SHOW STATUS LIKE Threads_connected;配置 HikariCP 最大连接数默认 10 够用后端接口响应时间JMeterv/Postman 压测对场地查询接口增加 Redis 缓存JVM 内存IDEA VisualVM 或jstat设置-Xms256m -Xmx512m前端打包体积npm run build后看 dist 目录路由懒加载、Element Plus 按需引入预约系统的并发场景有一个特点写多读多但并发度不会特别高。一个普通球馆一天可预约的场次大约几十到上百个同时发起预约的用户数量通常很低。所以初期不需要引入 Redis 分布式锁和消息队列使用 MySQL 事务和唯一索引就可以保证数据一致性。只有在用户量大到每天上千次预约请求需要一个热点场次倒计时抢约功能时才需要考虑 Redis 预扣库存和异步队列。11. 测试工具与演示数据准备为了让系统演示时效果更好可以准备一组标准的测试数据和管理员账号。先创建管理员账号密码使用 BCrypt 加密。一种简单的方式是写一个DataInitializer组件在应用启动时自动插入管理员用户Component public class DataInitializer implements CommandLineRunner { Autowired private UserMapper userMapper; Override public void run(String... args) { if (userMapper.selectCount(null) 0) { SysUser admin new SysUser(); admin.setUsername(admin); admin.setPassword(new BCryptPasswordEncoder().encode(123456)); admin.setNickname(管理员); admin.setRole(ADMIN); userMapper.insert(admin); } } }场地数据可以直接在管理后台新增。推荐插入 4 到 6 个乒乓球台比如“1号台”“2号台”“VIP 台”等方便演示场地列表页的分页效果。场次数据可以通过管理后台的“批量生成场次”功能生成未来 7 天的数据也可以直接执行 SQL 测试插入INSERT INTO venue_slot (venue_id, slot_date, start_time, end_time) SELECT v.id, CURDATE() INTERVAL 1 DAY, 10:00:00, 11:00:00 FROM venue v WHERE v.venue_name 1号台;在验证预约流程时建议至少使用两个账号一个用于前台预约一个用于验证“已被预约”的冲突提示。12. 后续扩展方向与二次开发建议如果这套乒乓球馆预约系统要作为毕业设计、课程设计或面试项目继续深化下面几个方向非常值得做第一增加 Redis 缓存场次与热榜。当场地数量较多用户频繁按日期查看场次时MySQL 的压力会越来越大。可以使用 Redis 缓存“某场地某天的可约场次列表”缓存 key 设置为venue:slot:detail:{venueId}:{date}预约成功后删除对应缓存。这是一个在简历上很容易解释清楚的优化点。第二增加统计报表。管理端每天查看预约列表不够直观可以增加“近 7 天预约趋势”“场地利用率排名”“高峰时段分析”。后端使用 MyBatis 写聚合查询 SQL前端使用 ECharts 画折线图和柱状图。这部分能充分展示 SQL 聚合能力也能体现产品思维。第三引入定时任务处理过期场次。比如场次结束后系统自动把对应订单状态从RESERVED改为COMPLETED。SpringBoot3 中开启定时任务只需在启动类加EnableScheduling然后用Scheduled(cron 0 0 2 * * ?)每天凌晨执行一次批量更新。这里要提醒的是定时批量更新一定要控制批次大小避免一次性 UPDATE 全表给数据库造成压力。第四前后端打包合并部署。开发阶段前端的npm run dev和后端的8080是两个独立的服务。如果要把系统部署到一台服务器可以先在项目目录执行npm run buildVue3 会生成dist静态目录再把dist文件复制到 SpringBoot 的src/main/resources/static目录下。重新打包运行后直接访问后端 8080 端口即可看到前端页面不需要再启动 Node 服务。13. 总结与关键建议这套乒乓球馆预约系统真正值得动手实践的地方是预约冲突的处理、场次批量生成逻辑和 JWT 权限控制这三个模块。项目的难度曲线并不陡峭如果你已经用过 SpringBoot2 和 Vue2切换到 SpringBoot3 Vue3 需要注意的差异其实主要体现在 JDK 版本和依赖坐标上。最开始验证的时候务必按“最小链路”来先注册一个用户手动往数据库插入一条场次再调用预约接口。不要一上来就做批量生成和管理后台链路长了你反而不好判断问题出在哪。最容易踩的坑整体上集中在四块SpringBoot3配错了 JDK 版本MyBatis-Plus还没有用对 SpringBoot3 专用 starterMySQL 连接配置没有加allowPublicKeyRetrievaltrue以及预约重复提交没有做唯一性约束。把这四个坑提前避开开发速度会快很多。建议后续按这个顺序扩展先做报表统计再接入 Redis 缓存然后处理订单超时自动取消。这三个方向按顺序能锻炼聚合 SQL 能力、缓存一致性思维和定时任务实践经验比加一堆花哨页面更有含金量。
返回列表