☰
微信小程序+Java后端琴房系统毕业设计源码解析与避坑指南
2026/10/1 4:43:49 网站建设 项目流程

简介:面向高校毕业设计场景的琴房管理系统完整项目包,基于微信小程序与Java后端实现,覆盖学生、管理员两类角色。学生端支持琴房信息浏览、在线留言、登录预约与个人中心;管理端提供轮播公告、师生信息、留言审核及琴房类型与预约管理,适合作为JavaWeb或微信小程序方向课程设计与毕业设计的改造参考。压缩包共1098个文件,以png图片、java与class源码、vue前端页面、js脚本、xml配置及sql数据库脚本为主,同时包含bat启动脚本、md说明与演示视频,整体压缩包体积约18.76MB,便于直接导入开发者工具运行。目前已吸引175人学习与浏览。资料内附完整数据库脚本、前后端源码、环境构建说明及操作演示,可帮助快速梳理预约流程与管理后台逻辑,是完成课题答辩和功能扩展的实用素材。

1. 琴房管理系统毕业设计:微信小程序+Java后端这套源码包到底解决什么问题

很多人下载“基于微信小程序+java后端的琴房管理系统毕业设计(源码+数据库+说明+演示视频).rar”都是因为毕设选题卡住了。这套东西解决的是琴房预约这个高频场景——学生打开微信小程序查看空闲琴房、选时间段、下单预约,管理员登录Java后端维护琴房、处理订单和余额。交付给用户的不只是代码,还有建库脚本、说明文档和演示视频,理论上解压后按步骤就能跑起来。适合正在做毕设的本科生,也适合想给自家琴行或培训教室搭一个预约小程序的人。下面从选型、目录结构、启动流程、核心代码改造和容易翻车的地方逐一拆。

2. 微信小程序+Java后端的选型逻辑和数据库落地:把地基先铺对

2.1 为什么是微信小程序+Java后端:毕设验收和简历面试的双重考量

这些年还能看到大量“微信小程序+Java”标题的毕设包,不是因为旧,而是这个组合在高校验收和企业招聘两个维度上都站得住。前端小程序在微信开发者工具里直接跑,安卓、iOS都能用同一个入口,不必单独打包;后端用Java,答辩时老师一听Spring Boot、MySQL、MyBatis,默认你走的是主流技术栈。有人会问为什么不用uniapp跨一端,毕设场景下小程序原生写起来更直接,uniapp反而要多处理一层编译差异。

常见后端结构是Spring Boot + MyBatis + MySQL,也有用MyBatis Plus的。谈不上谁更优,MyBatis Plus减少单表增删改查的重复代码,订单时段冲突这类复杂查询还是原生MyBatis更好控制SQL。如果源码包里的Mapper XML结构完整,Controller层很薄,基本就是Controller→Service→Mapper三层,小程序端对接REST接口。下面都按这个结构来聊。

2.2 琴房预约系统的四张核心表:字段设计、索引和一只可导入的SQL

多数琴房毕设包里的MySQL脚本有4到6张表。命名习惯差异很大,用户表可能是user、sys_user或t_user,琴房表常见practice_room、room、qinfang,预约表常见reservation、reserve、order。拿到包第一件事是打开SQL脚本确认有哪几张核心表。

完整预约闭环需要用户、琴房、预约、充值记录四张表,余额扣款才能串起来。下面这版按常规做法设计:

-- 用户表 CREATE TABLE `sys_user` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键', `wx_openid` varchar(64) DEFAULT NULL COMMENT '微信openid,小程序登录后写入', `nickname` varchar(32) DEFAULT NULL COMMENT '昵称', `phone` varchar(20) DEFAULT NULL COMMENT '手机号', `role` tinyint(4) NOT NULL DEFAULT 0 COMMENT '0-普通用户 1-管理员', `balance` decimal(10,2) NOT NULL DEFAULT 0.00 COMMENT '账户余额', `create_time` datetime DEFAULT NULL COMMENT '注册时间', PRIMARY KEY (`id`), UNIQUE KEY `uk_openid` (`wx_openid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表'; -- 琴房表 CREATE TABLE `practice_room` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键', `room_name` varchar(50) NOT NULL COMMENT '琴房名称,如A101', `location` varchar(100) DEFAULT NULL COMMENT '所在楼层位置', `price_per_hour` decimal(10,2) NOT NULL DEFAULT 0.00 COMMENT '每小时价格', `equipment` varchar(255) DEFAULT NULL COMMENT '钢琴型号、音响、空调等设备描述', `status` tinyint(4) NOT NULL DEFAULT 0 COMMENT '0-空闲 1-维护中 2-禁用', `image_url` varchar(255) DEFAULT NULL COMMENT '琴房实拍图', `create_time` datetime DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='琴房表'; -- 预约订单表 CREATE TABLE `reservation` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键', `user_id` int(11) NOT NULL COMMENT '下单用户id', `room_id` int(11) NOT NULL COMMENT '琴房id', `reserve_date` date NOT NULL COMMENT '预约日期', `start_time` time NOT NULL COMMENT '开始时间,如09:00', `end_time` time NOT NULL COMMENT '结束时间,如10:00', `amount` decimal(10,2) NOT NULL DEFAULT 0.00 COMMENT '订单金额', `status` tinyint(4) NOT NULL DEFAULT 0 COMMENT '0-待支付 1-已支付 2-已取消 3-已完成 4-已退款', `create_time` datetime DEFAULT NULL COMMENT '下单时间', PRIMARY KEY (`id`), KEY `idx_room_date` (`room_id`, `reserve_date`), KEY `idx_user` (`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='预约订单表'; -- 充值记录表 CREATE TABLE `recharge_record` ( `id` int(11) NOT NULL AUTO_INCREMENT, `user_id` int(11) NOT NULL, `amount` decimal(10,2) NOT NULL, `pay_method` varchar(20) DEFAULT 'wechat', `create_time` datetime DEFAULT NULL, PRIMARY KEY (`id`), KEY `idx_user_id` (`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='充值记录表';

设计理由要讲清楚。用户表里的wx_openid承担了账号职责,微信小程序没有传统密码注册,用户第一次登录时后端通过wx.login换来的code向微信服务器换openid,再把它当作唯一身份。加UNIQUE KEY uk_openid,防止同一微信号刷出多个账号。琴房表的status是停用或维修状态,不是动态空闲状态——琴房是否有空时段要查预约表才能确定。预约表是绝对核心,reserve_date、start_time、end_time必须拆开存,否则后面冲突查询没法写。索引idx_room_date直接服务时段冲突判断,几千行数据时没有这个索引会明显变慢。

充值记录不是必须有,但几乎所有琴房毕设都带上,因为“虚拟余额”模式比真实微信支付简单得多:用户先充值,预约时从余额扣,不需要商户号,不需要支付回调,答辩演示完全够用。源码包如果只有三张表没有余额,大概率就是没做扣款闭环。

2.3 后端接口清单和统一返回体:让小程序端少写一套if-else

数据库建好后,接口不需要多,完整琴房系统后端接口一般15个上下。常见这一组:

模块接口方法作用
登录/api/user/loginPOST微信code换openid,返回token
用户/api/user/infoGET获取昵称、余额、角色
琴房/api/room/listGET分页查询琴房列表
琴房/api/room/detailGET琴房详情和当日占用时段
预约/api/reserve/addPOST提交预约订单
预约/api/reserve/listGET我的预约列表
预约/api/reserve/cancelPOST取消未开始的预约
余额/api/recharge/addPOST模拟充值入账
管理/api/admin/room/savePOST新增/编辑琴房
管理/api/admin/room/deletePOST删除琴房
管理/api/admin/reserve/listGET管理员查全部预约

后端返回体统一成下面这种结构,小程序端处理起来才省心:

{ "code": 0, "msg": "success", "data": {} }

code为0表示成功,非零值携带错误文案。不要用HTTP状态码承载业务错误,比如400、500,wx.request在非2xx状态时走的是fail回调,业务错误统一走success回调再判断code,页面代码能少一半嵌套。

这套约定就是前后端的最小成本契约。后端如果没有统一返回体类,自己封装一个成本很低。另一件常被忽略的事是token校验:很多毕设包在登录接口里返回了token,但后续接口根本没实现拦截器,前端把token带上了也没人校验。拿到源码先去WebMvcConfigurer里看有没有注册拦截器,没有就自己补上,否则换一个用户就能互相查订单,答辩演示会十分出戏。

3. 从.rar压缩包到本地跑通:源码、数据库、说明文档的完整配置流程

3.1 解压.rar之后:压缩包里的内容各自扮演什么角色

多数交付包的目录长这样:

琴房管理系统/ ├── frontend(微信小程序源码,用微信开发者工具打开) │ ├── app.js │ ├── app.json │ ├── pages/ │ │ ├── index/ │ │ ├── room/ │ │ ├── reserve/ │ │ └── user/ │ ├── utils/ │ └── project.config.json ├── backend(Java后端工程,IDEA直接打开) │ ├── pom.xml │ ├── src/main/java/com/example/qinfang/ │ ├── src/main/resources/ │ │ ├── application.yml │ │ └── mapper/xxx.xml ├── database(SQL脚本文件) │ └── qinfang.sql ├── 说明文档.doc └── 演示视频.mp4

先别急着打开演示视频。按顺序做三件事:确认Java版本、确认MySQL版本、确认SQL脚本能否被当前MySQL执行。这三个变量和源码环境不一致,后面代码再对也跑不起来。

打开说明文档重点看作者写的环境版本:JDK 1.8还是17,MySQL 5.7还是8.0,Spring Boot 2.x还是3.x。见过最多的翻车现场是拿MySQL 8.0去跑按MySQL 5.7语法写的脚本,字符集设置特殊一点就报错;反过来也一样。省事做法是装一个与文档同版本的MySQL单机实例,不要拿公司或实验室已有环境的库硬套。

3.2 后端本地启动:JDK、MySQL、IDEA到application.yml的连贯配置

后端启动链条不长:装JDK → 导入IDEA → 建库导入SQL脚本 → 改配置 → 启动服务。最容易出错的就在改配置。我一般先把application.yml里用户名、密码、库名填对,再一次性启动:

server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/qinfang?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false username: root password: 123456 hikari: minimum-idle: 5 maximum-pool-size: 15 connection-timeout: 30000 jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8 mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.qinfang.entity wx: appid: wx1234567890abcdef secret: abcdefghijklmnopqrstuvwxyz123456

参数说明:url这一行有三个关键参数。useUnicode=true和characterEncoding=utf8解决后续中文乱码,serverTimezone=Asia/Shanghai解决时间差8小时,这三个在MySQL 8.0驱动下基本是必须项,少一个后面要花两小时查一个看似无解的问题。useSSL=false去掉无意义的安全警告。driver-class-name用com.mysql.cj.jdbc.Driver,这是MySQL 8.0驱动对应的类名;老项目里写com.mysql.jdbc.Driver在8.0下会提示已过时,但仍能跑。

HikariCP四个参数是常见默认值。minimum-idle不要设成0,毕设场景频繁起停服务,空闲连接被回收后第一次请求会明显慢。maximum-pool-size本地开发15足够,部署到服务器时结合MySQL的max_connections再调,别超过数据库连接数的三分之一。connection-timeout默认30秒,本地数据库连不上时不到30秒就会抛异常,这个参数更多对生产有意义。

wx.appid和wx.secret可以先用占位符。登录和充值接口会用到,但如果暂时不跑完整登录流程,先留空不影响普通接口联调。等到3.3节配小程序端时,再把微信公众平台里的真实AppID填回来。

配置改完,在IDEA里直接运行启动类。日志最后出现“Started Application in xx seconds”算成功。如果卡在Mapper扫描,检查启动类上有没有@MapperScan("com.example.qinfang.mapper"),或者每个Mapper接口加@Mapper,两者存在其一即可。

3.3 小程序端导入:AppID、baseUrl、真机预览的三角关系

后端起来后,前端配置简单很多。微信开发者工具导入frontend目录,填入自己的测试号AppID,或者选“测试号”也能预览。有一个细节必须改:小程序源码里的请求地址写的是开发者的IP,通常是http://localhost:8080。真机预览时localhost指向的是手机自己,所以要用电脑局域网IP:

// utils/config.js module.exports = { baseUrl: 'http://192.168.1.100:8080/api' }

改完baseUrl,还要在微信开发者工具“详情 → 本地设置”勾选“不校验合法域名、web-view(业务域名)、TLS版本以及HTTPS证书”。这个勾选只对开发调试有效。真机预览要保证手机和电脑连同一个Wi-Fi,并确认Windows防火墙允许Java进程监听8080端口对外访问。

如果列表还是空白,先去Console面板看有没有“不在以下合法域名列表中”的红字,有就说明没勾选不校验合法域名,或baseUrl写错。再看Network面板里请求返回多少:请求返回400或500,把后端控制台异常栈贴出来查,多数是Mapper XML里查询的列名和数据库脚本建出来的列名对不上。MyBatis默认开启驼峰映射,数据库的price_per_hour映射到实体pricePerHour,如果实体字段写的是price,查询结果会静默变成null。这类问题后端不报错、前端显示0,排查最快的方式是在日志里直接打印查询结果。

4. 预约流程的核心代码改造:冲突检测、订单状态机和小程序端落单

4.1 预约冲突检测:为什么跨时段判断远比等值判断容易漏

琴房预约最核心的业务规则:一个琴房同一时间段只能被一个人预约。新手最容易写成“查一下有没有相同开始时间和结束时间”,但用户预约09:30到10:30时,系统已存在09:00到10:00的订单,按等值判断查不出来,结果就是重复预约。正确判断重叠区间的条件:新预约的开始时间小于已存在的结束时间,且新预约的结束时间大于已存在的开始时间。

MyBatis的Mapper XML里,对应一段不算复杂的SQL:

<select id="selectExistReserve" resultType="com.example.qinfang.entity.Reservation"> SELECT id, user_id, room_id, reserve_date, start_time, end_time, status FROM reservation WHERE room_id = #{roomId} AND reserve_date = #{reserveDate} AND status IN (0, 1) AND start_time &lt; #{endTime} AND end_time &gt; #{startTime} LIMIT 1 </select>

XML里的小于号和大于号必须转义成&lt;和&gt;,直接写<会被当成XML标签开头导致启动报错。状态过滤条件status IN (0, 1)表示待支付和已支付的预约都占着时间;已取消订单不占时间,已完成历史记录也不影响未来时段。

业务方法里配合事务再叠加一次校验,防止两个用户同时提交万事冲突数据写入:

@Transactional public ReserveResult createReserve(ReserveRequest request, Long userId) { // 1. 查该琴房该日期是否存在时间重叠的预约 Reservation existed = reservationMapper.selectExistReserve( request.getRoomId(), request.getReserveDate(), request.getStartTime(), request.getEndTime()); if (existed != null) { throw new BusinessException("该时段已被预约,请换个时间"); } // 2. 查余额够不够 User user = userMapper.selectById(userId); BigDecimal hours = calcHours(request.getStartTime(), request.getEndTime()); BigDecimal amount = roomMapper.selectById(request.getRoomId()) .getPricePerHour().multiply(hours); if (user.getBalance().compareTo(amount) < 0) { throw new BusinessException("余额不足,请先充值"); } // 3. 插入订单并扣减余额 Reservation reservation = new Reservation(); reservation.setUserId(userId); reservation.setRoomId(request.getRoomId()); reservation.setReserveDate(request.getReserveDate()); reservation.setStartTime(request.getStartTime()); reservation.setEndTime(request.getEndTime()); reservation.setAmount(amount); reservation.setStatus(0); reservationMapper.insert(reservation); userMapper.deductBalance(userId, amount); return ReserveResult.of(reservation.getId()); }

逻辑说明:方法加@Transactional,从查冲突到扣款插单,任何一步抛异常都会回滚,避免出现“订单没插进去但余额扣了”或“余额扣了但订单重复”。校验余额时用compareTo而不是subtract后判断是否小于0,后者会多一次BigDecimal对象创建,这在金额计算里属于不必要的操作。

这个方案在极高并发下仍有小概率两个请求同时通过校验。答辩被问如何防止超卖,可以回答在reservation表加唯一索引兜底,或者用SELECT ... FOR UPDATE锁行。真实项目更推荐加一个room_date字段并建唯一索引,包含琴房ID、日期、开始时间三列,让数据库做最后一道防线。

时间段生成的另一处细节是统一单位。后端接口返回startTime=09:00、endTime=10:00时,前端生成时间段列表和后端解析都不要用字符串截取比较。Java里用LocalTime.parse转成时间对象再比较,前端也统一24小时制,避免“09:00”和“9:00”这两个相同时间不同字符串的坑。

4.2 订单状态机:取消、失效、退款的状态流转设计

预约订单的status字段决定取消、支付、管理端操作如何流转。常规状态机是四态循环:0-待支付 → 1-已支付 → 3-已完成;用户主动取消走0或1 → 2;退款从1 → 4。最容易设计乱的就是“取消”这个动作。

铁律是:未开始的预约可以取消,已开始或已完成的不允许取消。合法性判断可以简化为“当前时间小于预约的开始时间”,但start_time不包含日期,必须把reserve_date拼成完整的LocalDateTime再比较:

public void cancelReserve(Long reserveId, Long userId) { Reservation reservation = reservationMapper.selectByIdForUpdate(reserveId); if (reservation == null || !reservation.getUserId().equals(userId) || !Arrays.asList(0, 1).contains(reservation.getStatus())) { throw new BusinessException("订单状态不允许取消"); } // reserve_date与start_time拼成完整时间再比较 LocalDateTime reserveStart = LocalDateTime.of( reservation.getReserveDate(), reservation.getStartTime().toLocalTime()); if (LocalDateTime.now().isAfter(reserveStart)) { throw new BusinessException("预约已经开始,无法取消"); } // 先记住原状态,再改成已取消 int oldStatus = reservation.getStatus(); reservation.setStatus(2); reservationMapper.updateById(reservation); // 原状态是已支付才退款 if (oldStatus == 1) { userMapper.addBalance(reservation.getUserId(), reservation.getAmount()); } }

两个要点:selectByIdForUpdate会把该记录行锁住,防止同一用户连点多次取消导致退款重复执行;退款判断必须用改动前保存的oldStatus,如果先setStatus(2)再判断getStatus() == 1,判断结果永远是false,退款永远不会发生。代码里这个顺序错了,功能会在答辩当天翻车。

待支付订单一直不付款,会一直占着琴房时段。成熟做法是定时任务扫表,@Scheduled(fixedDelay = 60000)每分钟把创建超15分钟且状态为0的订单改成已取消。毕设不一定要求做定时任务,但至少要给管理端一个手动将过期订单置为取消的按钮。答辩时主动提这个设计,比被动说没考虑过要稳很多。

4.3 小程序端预约提交:从选琴房、选时段到收到响应的完整走查

小程序预约页面交互一般分三步:选琴房、选日期和时段、确认提交。页面数据源是琴房列表接口和当日可用时段接口。技术难点在时间段生成——毕设包里常常写死一个字符串数组,比如["09:00","10:00","11:00"],真实需求很难这么简单。更可持续的做法是后端给“起始时间+时长分钟数”,前端循环生成时段列表,并把已占用的时段标记为不可预约。

提交订单的核心代码:

// pages/reserve/submit.js const config = require('../../utils/config.js'); Page({ data: { roomId: null, date: '', startTime: '', endTime: '', amount: 0 }, submitReserve() { const { roomId, date, startTime, endTime } = this.data; if (!roomId || !date || !startTime || !endTime) { wx.showToast({ title: '请补全预约信息', icon: 'none' }); return; } const token = wx.getStorageSync('token'); wx.request({ url: config.baseUrl + '/reserve/add', method: 'POST', header: { 'Authorization': token }, data: { roomId, date, startTime, endTime }, success: (res) => { const body = res.data; if (body.code === 0) { wx.showToast({ title: '预约成功', icon: 'success' }); wx.navigateTo({ url: '/pages/order/order' }); } else { wx.showToast({ title: body.msg || '预约失败', icon: 'none' }); } }, fail: () => { wx.showToast({ title: '网络异常', icon: 'none' }); } }); } });

参数说明:header里的Authorization是登录后存入Storage的token,每次请求带给后端,后端拦截器解析出userId,实现“用户只能操作自己的订单”。data中所有字段必须在data()里先声明,否则this.data.roomId永远是undefined,点击提交永远停在“请补全预约信息”。

更完整一点的体验是用户选了时段后临时想改,页面加一个“重新选择”按钮,重置date、startTime、endTime即可,琴房不用重新选。后端返回“余额不足”或“时段被占用”时,前端不应只弹一个toast,最好重新拉取琴房详情接口,把新被占的时段用disabled样式置灰,这是答辩时老师常追问的交互细节。

5. 常见问题和避坑排查:启动失败、白屏、乱码的高频事故

5.1 后端启动报错:端口占用、Maven依赖超时、驱动类找不到

现象:IDEA启动日志最后出现APPLICATION FAILED TO START,提示Port 8080 was already in use。原因:本机某个软件或上一次没关掉的Java进程占了8080。解决:先执行netstat -ano | findstr 8080找到PID,在任务管理器结束进程;或改server.port: 8081。毕设阶段改端口更省事,但小程序端baseUrl要同步改。

现象:Maven导入依赖时一直卡在Downloading from central然后超时。原因:默认的中央仓库下载速度很不稳定,超时很常见。解决:在~/.m2/settings.xml里配置镜像仓库,再把IDEA的Maven设置指向这个settings文件,依赖下载速度会明显改善。

现象:启动后抛ClassNotFoundException: com.mysql.cj.jdbc.Driver,或日志提示Loading class com.mysql.jdbc.Driver... deprecated。原因:pom.xml里MySQL驱动版本过老或依赖缺失。解决:Spring Boot 2.7左右的项目使用mysql-connector-j8.0.33,并保持driver-class-name和artifactId匹配。

5.2 小程序白屏或请求卡死:域名校验、baseUrl指向localhost、导航栏安全区

现象:Console报“不在以下request合法域名列表中”。原因:微信平台限制request只能访问配置过的HTTPS域名,本地开发没有合法域名。解决:在开发者工具“详情 → 本地设置”勾选“不校验合法域名”,这设置只管开发调试。

现象:开发者工具里请求能通,真机预览不通。原因:baseUrl写的是localhost或某台开发机IP,手机访问不到。解决:改成电脑当前局域网IP,例如http://192.168.1.100:8080/api,并保证手机与电脑在同一Wi-Fi。不在同一子网时,还要检查Windows防火墙是否放行了8080。

现象:页面加载出来但顶部内容被刘海屏挡住。原因:项目用了navigationStyle: custom但没适配安全区。解决:

// 获取状态栏和安全区信息,动态计算顶部内边距 const info = wx.getWindowInfo(); const navHeight = 44 + info.statusBarHeight; const topPadding = (info.safeArea.top || info.statusBarHeight) + 'px'; this.setData({ topPadding });

页面根节点加padding-top: {{topPadding}}px。注意wx.getWindowInfo在基础库2.20.0以上才可用,旧项目用getSystemInfoSync兼容。默认导航栏不需要处理,只有自定义导航栏才需要。

5.3 中文乱码和时间差8小时:字符集、时区与表编码的三处配置

现象:后端返回中文正常,但数据库存进去变成??。原因:表结构和连接串字符集不一致,典型是表用了latin1而连接串没有characterEncoding=utf8。解决:建库时显式指定字符集。

CREATE DATABASE qinfang DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;

application.yml的url里加characterEncoding=utf8。已建错的库可以执行ALTER TABLE reservation CONVERT TO CHARACTER SET utf8mb4;补救,但已经存坏的乱码不会恢复,所以要先改配置再继续开发。

现象:数据库时间比北京少8小时。原因:Java默认时区与MySQL驱动时区不一致。解决:JDBC url加serverTimezone=Asia/Shanghai,同时把jackson时区设为GMT+8。只配一处,后端返回给前端的JSON字符串仍可能带错偏移量,两处配置要对齐。

5.4 说明文档与演示视频对不上源码:按文档跑不通的典型现场

现象:按说明文档写的MySQL账号、库名导入SQL脚本后启动后端,仍然连不上库;仔细比对发现文档里的URL和application.yml里的实际库名完全不同。原因:交付包里的说明文档、演示视频和源码经常不是同一时间同一机器上做的,文档可能是早期模板。解决:一切以源码和SQL脚本为准,文档只作参考。先打开SQL脚本看建库名,再打开application.yml看连接串,把这两处弄一致。演示视频的作用是让你知道最终效果长什么样,而不是教你怎么配环境。

注意:同一套交付包里的三个产物未必在同一时刻生成,看到三者矛盾时,源码和SQL脚本的优先级最高。

5.5 HikariCP连接池参数乱调:死等与连接爆掉的两种异常表现

现象:后端启动后第一次访问接口卡住20秒,然后报Connection is not available, request timed out。原因:minimum-idle设成0后连接池里没有空闲连接,第一次请求要新建连接,恰好MySQL慢一点,等待就超过了connection-timeout。解决:本地开发把minimum-idle设成5,连接超时保持默认30秒,不要为了追求“快速失败”把它改成1秒。

现象:前端并发点了几次预约,后端日志出现Too many connections。原因:maximum-pool-size设得太夸张,比如500,而MySQL默认max_connections只有151,连接池把所有连接都建出来把数据库压垮了。解决:本地把maximum-pool-size控制在20以内;生产环境先查show variables like 'max_connections',再按总连接数的五分之一到三分之一设置。

连接池默认参数就是一个风险很低的组合,真不需要把它当玄学调来调去。遇到连接不够用,先看show processlist确认是不是真的不够,再决定改参数,不要一上来就放大连接数。

6. 答辩前的最后一轮自测:用边界用例把琴房预约系统验一遍

整套功能跑通之后,答辩前最值得做的不是加新功能,而是拿几个边界用例验证核心逻辑。第一,开两个开发者工具或两个浏览器窗口,同时提交同一个琴房同一时段,看是否只有一个成功,这验证冲突检测的有效性。第二,在凌晨零点前后连续操作,看跨天日期处理是否正确,很多毕设把日期当字符串拼接,凌晨之后预约时间全错。第三,把余额扣到接近零再下单,看后端是否拒绝欠费预约。这三个用例全过,预约闭环基本没有大问题。

另一个容易忽视的自测点是取消后退费。先用余额下单,再取消,看余额是否回到原值;然后取消一个待支付订单,看余额是否被错误增加。这两个小操作比完整演示一遍预约更能展示对业务的理解。顺便检查一下“可选时段”在后端被占用后,前端是否更新了可预约列表——这个联动经常被写成只弹提示不刷新页面的半成品。

我自己的一个习惯是给小程序端留一个“环境切换”入口,平时开发指到测试IP,答辩现场指到演示IP,避免临场改代码。把这个细节写进说明文档,答辩当天会顺很多。一个技术方案能不能被信任,往往就体现在这些边界处理上。希望这份启动步骤、参数说明和踩坑记录能帮到你,也帮你少熬几个晚上。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询