先说一下这个项目的整体感受。做计算机毕业设计最怕的是什么?不是功能太难写不出来,而是题目太空、技术栈太老、做完没有东西可以讲。而“基于Spring Boot的培训机构课后服务管理平台小程序”这类题目,恰好踩中了当下毕设选题的热门方向:前后端分离、小程序端落地、真实业务场景。不管是本科还是专科,这套东西做完,答辩时能讲的东西非常多。
这篇文章我会把整个项目从选题逻辑、技术选型、功能拆解、数据库设计、核心代码实现,到部署上线、答辩准备、常见坑位,完整地过一遍。内容不是说明书式的罗列,而是站在“我做过、我踩过坑、我建议你怎么做”的角度来写。无论你是刚拿到题目还没开工,还是已经写了一半卡住了,这篇文章都能给你一些实打实的参考。
1. 核心思路拆解:为什么这个选题值得做
1.1 项目本质与业务场景还原
先说清楚这个平台到底解决什么问题。幼儿园和小学放学早,双职工家庭接不了,所以催生了“课后服务”这个业态。培训机构(或者学校引入的第三方机构)提供课后的托管、兴趣班、作业辅导等服务。但传统的运营方式痛点非常明显:家长不知道孩子上了什么课、课时还剩多少、老师是谁、临时调课通知传达不到位,机构这边排课靠Excel、消课靠手记、统计报表靠月底熬夜加班。
这个项目本质上就是给这类机构做一套数字化运营工具:家长端通过微信小程序选课、报名、查看课时、接收通知;机构端(或教师端)通过Web管理后台配置课程、安排课表、记录考勤、核销课时、生成统计报表。Spring Boot负责提供RESTful API,小程序负责消费这些API实现C端交互。
之所以选这个场景,是因为它比纯粹的“商城”“博客”类题目更有区分度。商城类项目答辩老师看腻了,而且业务逻辑相对标准化;课后服务平台的业务链条更长,涉及排课冲突检测、课时包扣减、考勤状态流转这些规则,这些恰恰是答辩时能体现“设计能力”的亮点。
1.2 技术选型背后的理由
后端用Spring Boot,基本是当前Java毕设的默认选项,没有太大争议。但这里有个关键点:Spring Boot版本的选择。我从热搜词里看到很多人搜“springboot版本太高”,这说明大家都被版本问题坑过。
Spring Boot 3.x要求JDK 17及以上,如果你电脑上装的是JDK 8,那直接创建Spring Boot 3.x项目会报错。我的建议非常明确:
- 如果你本机是JDK 8,选Spring Boot 2.7.x,这是2.x的最终维护版本,稳定、资料多、兼容性好。
- 如果你本机是JDK 17或更高,选Spring Boot 3.x也没问题,但注意MyBatis-Plus、Knife4j等第三方依赖需要选择支持Spring Boot 3的版本(比如MyBatis-Plus 3.5.3+,Knife4j 4.x)。
数据库用MySQL 8.x,ORM用MyBatis-Plus,权限认证用JWT或者Sa-Token,接口文档用Knife4j(Swagger增强版),这些组合是当前毕设项目最主流的搭配,网上资料多,遇到问题搜得到解决办法。
前端小程序部分,原生微信小程序开发就能满足需求,不必上uni-app。原因很简单:原生框架在微信开发者工具里调试最方便,社区资料最全,而且对于毕设来说功能完全够用。如果你要同时兼顾App端,再考虑uni-app不迟,但那个学习成本和调试成本会明显上升。
前端Web管理后台,推荐Vue 2 + Element UI或者Vue 3 + Element Plus。如果对前端不熟,直接用Thymeleaf模板引擎做服务端渲染也行,但体验会差一些。个人建议上Vue,因为前后端分离本身就是一个可以写进论文的技术亮点。
1.3 影响范围与应用场景推演
这个平台的用户角色很清晰,可以拆成四类:系统管理员、机构管理员(或校长)、教师、家长(学生)。每种角色对应的功能边界都不一样,这也是设计数据表时最重要的依据。
- 系统管理员:管理入驻的培训机构、审核课程、查看全局数据。
- 机构管理员:创建课程、安排课时、指定教师、查看营收和消课数据、发布通知。
- 教师:查看我的课表、上课签到、记录课后反馈。
- 家长:浏览课程、报名下单、查看孩子的课时剩余、请假申请、接收通知。
一个实际的培训机构,日常运营流程大概是这样的:机构管理员在后台创建“创意美术基础班”,每周二、周四下午16:30-18:00上课,一共16课时,定价1200元。家长在小程序里看到课程详情,下单购买后系统自动生成一个课时包(剩余16课时)。每上完一次课,教师签到确认,课时包扣减1课时。如果家长请假,可以申请课时顺延。学期结束后,机构后台能一键导出出勤统计表和营收明细。整条链路闭环,覆盖了业务核心,也覆盖了毕设论文里“需求分析”和“系统设计”章节的全部素材。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 系统功能模块与数据库设计
2.1 功能模块全景拆解
整个系统拆成两个端来看:小程序端(家长/教师)和Web管理后台(管理员/机构端)。
小程序端功能:
- 微信授权登录:通过wx.login获取code,后端调用微信接口换取openid,再生成JWT令牌返回前端。
- 课程浏览与搜索:按分类(美术、音乐、体育、编程、托管)、按机构、按年龄段筛选。
- 课程详情与报名:展示课程计划、剩余名额、价格,家长确认后提交订单并支付(毕设可以用模拟支付,或接入微信支付V3的JSAPI下单但用测试商户号,真正答辩演示时模拟支付更稳妥)。
- 我的课程与课时包:查看已购课程进度、剩余课时、上课记录。
- 请假与调课申请:提交请假,机构管理员审批,课时顺延或冻结。
- 消息通知:机构发布公告、课时变动提醒(服务通知模板消息,或个人中心内站内信)。
- 个人中心:学生信息管理(可绑定多个孩子)、联系客服、关于平台。
Web管理后台功能:
- 登录与权限管理:管理员、机构管理员、教师三种后台角色,按角色控制菜单和数据范围。
- 机构管理:审核入驻机构、查看机构列表、停用/启用账户。
- 教师管理:维护教师档案、排课关联、授课评价。
- 课程管理:创建课程、设置课时数、价格、适用年级、上课时间、封面图。
- 排课管理:生成每周上课计划(周课表),支持冲突检测(同一教师同一时间段不能同时上两门课)。
- 考勤管理:教师端签到,管理端可人工补签和核销。
- 订单管理:查看报名订单、退款处理、支付状态修改。
- 财务统计:课时消耗统计、机构营收报表、课程报名趋势、教师授课课时排行,用ECharts出图表。
- 通知公告管理:编辑并发布公告,推送到家长端。
2.2 数据库表结构设计的核心思路
数据库设计是答辩时老师一定会深挖的部分。我的经验是:不要设计一大堆表堆在那儿,而是把核心业务链路的表设计扎实,每张表都能讲清楚“为什么这么建”。
核心表有这些:
- user:用户主表,通用字段(id、phone、password、nickname、avatar、role、status),用role区分管理员/机构管理员/教师/家长。
- student:学生档案表,关联家长(user_id),一个家长可绑定多个孩子(常见字段:student_name、grade、school_name)。
- organization:培训机构表(org_name、logo、intro、address、contact、status)。
- course:课程表(course_name、org_id、category、grade_range、total_hours、price、cover、intro、status、schedule_type)。
- course_schedule:排课表(course_id、teacher_id、start_time、end_time、weekday、classroom、status)。这是排课冲突检测的关键表。
- order:订单表(order_no、user_id、student_id、course_id、amount、pay_status、pay_time、refund_status)。
- class_package:课时包表(order_id、course_id、student_id、total_count、used_count、remain_count、expire_date、status)。课时扣减就发生在这张表上。
- attendance:考勤表(schedule_id、student_id、status,status取值为:出勤、缺勤、请假)。
- leave_apply:请假申请表(student_id、schedule_id、reason、status、approve_time)。
- notice:公告表(title、content、target_role、org_id、create_time)。
两张关键表要额外说明:一个是class_package课时包表,为什么单独建表而不直接把剩余课时存在订单里?因为一个订单可能包含多期课程(比如报一学期送一期的促销),或者一次购买多个课程的情况,课时包独立出来可以灵活处理部分退费、课时冻结这些场景。另一个是course_schedule排课表,为什么要把每周的上课时间拆出来?因为如果不拆,教师维度的排课冲突检测做不了,也无法生成每周的课表视图。
补充一个实用建议:所有表都要带create_time、update_time、deleted这三个字段。前两个是数据审计需要,deleted是MyBatis-Plus逻辑删除的标准字段,避免物理删除导致关联数据断裂。这个细节在答辩时提出来,老师会觉得你考虑问题很全面。
2.3 角色权限模型
权限这块不用做太复杂,做成RBAC(基于角色的访问控制)即可。三张基础表:sys_role(角色表)、sys_menu(菜单表)、sys_role_menu(角色菜单关联表),再加上user表的role字段做粗粒度控制。
后台的菜单权限建议做成动态路由,登录时根据角色ID查询菜单列表返回前端,前端动态渲染侧边栏。这样管理员和机构管理员登录后看到的菜单天然就不一样,避免前端只做按钮隐藏的“假权限”。
接口权限这块,毕设阶段用拦截器校验JWT和角色就完全够用。写一个WebMvcConfigurer注册拦截器,在preHandle方法里从Header取出token,解析出用户ID和角色,再判断当前请求路径是否需要特定角色。需要放行的路径就白名单化:/api/auth/login、/api/auth/wx-login、/api/course/list这些公开查询接口可以直接放行;管理后台的接口统一用/admin/**前缀,只有管理员或对应机构角色才能访问。
3. 核心实现细节与实操代码走读
3.1 小程序端微信登录的完整链路
微信登录是每个小程序项目都绕不开的第一步,也是答辩时老师最容易追问的地方。我直接把完整的登录时序和关键代码写出来。
小程序端调用wx.login获取临时code,这个code有效期只有5分钟,且只能使用一次:
javascript复制// 小程序端
wx.login({
success: (res) => {
if (res.code) {
wx.request({
url: 'https://你的域名/api/auth/wx-login',
method: 'POST',
data: { code: res.code },
success: (resp) => {
const { token, userInfo } = resp.data.data
wx.setStorageSync('token', token)
wx.setStorageSync('userInfo', userInfo)
}
})
}
}
})
后端拿到code后,调用微信的jscode2session接口换取openid和session_key:
java复制// WxAuthService.java
public String code2Session(String code) {
String url = "https://api.weixin.qq.com/sns/jscode2session?appid=" + appid
+ "&secret=" + secret
+ "&js_code=" + code
+ "&grant_type=authorization_code";
RestTemplate restTemplate = new RestTemplate();
String result = restTemplate.getForObject(url, String.class);
// 解析返回值,拿到 openid
JSONObject json = JSON.parseObject(result);
String openid = json.getString("openid");
return openid;
}
拿到openid后,先去user表查是否存在该用户,不存在则自动注册(默认角色为家长),存在则直接生成JWT返回。这里有一个很容易踩的坑:不要拿前端传来的nickname、avatar直接去更新数据库,因为微信现在对getUserProfile进行了调整——用户主动点击按钮才能触发授权弹窗,wx.getUserProfile已经在基础库2.27.1版本后不再返回真实头像昵称,而是一个默认灰色头像和“微信用户”这样的默认名称。
所以头像和昵称的更新,应放在用户点击“编辑资料”时,让用户主动填写或上传头像,而不是在首次登录时静默获取。这也是很多同学调试时发现“明明授权了但没有拿到微信用户信息”的原因。热搜里那条“小程序获取登录后的微信用户失败”基本就是这个场景。
生成JWT的代码:
java复制// JwtUtil.java
public String generateToken(Long userId, String role) {
return Jwts.builder()
.setSubject(String.valueOf(userId))
.claim("role", role)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + 7 * 24 * 3600 * 1000L))
.signWith(SignatureAlgorithm.HS256, secretKey)
.compact();
}
JWT的过期时间设为7天,小程序端每次请求时在header里带Authorization: Bearer token,后端拦截器解析token获取用户信息存入ThreadLocal,方便后续业务方法直接取当前用户。
3.2 排课与冲突检测的实现
排课冲突检测是系统里最值得深入写的一个业务点。假设一个教师每周二16:30-18:00在机构A上美术课,同一时间就不能再有另一门课。同样的道理,一间教室同一时间也不能被两门课占用。
实现方案如下:新增排课记录时,先查出同一教师在同一weekday下的所有排课,然后判断时间段是否有重叠。时间重叠的判断条件其实是两个区间是否存在交集,用数学表达式描述:
新排课的start_time < 已有排课的end_time 且 新排课的end_time > 已有排课的start_time
SQL实现可以写成:
sql复制SELECT COUNT(*) FROM course_schedule
WHERE teacher_id = #{teacherId}
AND weekday = #{weekday}
AND deleted = 0
AND (
(start_time < #{endTime} AND end_time > #{startTime})
OR
(start_time >= #{startTime} AND start_time < #{endTime})
)
如果count大于0,直接返回“该教师当前时间段已有课”的提示。同理,教室冲突检测把teacher_id换成classroom_id即可。
顺带提一个细节:时间比较建议用LocalTime类型存储,不要用字符串。用字符串比较在“09:00”和“09:30”这种场景下能正常工作,但一旦出现跨天课程或格式不统一就会出问题。用LocalTime在MySQL里映射为time类型,实体类里直接用LocalTime字段,JPA和MyBatis-Plus都支持得很好。
3.3 课时包扣减与考勤闭环
课时包扣减是业务的核心闭环,必须保证原子性。推荐用数据库乐观锁或者直接把扣减写成一条update语句,避免并发问题:
sql复制UPDATE class_package
SET used_count = used_count + 1, remain_count = remain_count - 1
WHERE id = #{packageId} AND remain_count > 0
如果受影响行数为0,说明剩余课时不足或者数据异常,直接抛出业务异常。这样写的好处是数据库层面就保证了“扣减不会扣成负数”,不需要先把remain_count查出来在Java代码里判断,再执行update。那种“先查后改”在并发场景下会有超卖问题,毕设项目虽然不一定有高并发压力,但写法规范本身就是加分项。
考勤流程串起来是:教师在小程序端进入“我的课表” -> 选择某个已开始的课时 -> 点击“开始签到” -> 小程序调后端接口获取该课时下的报名学生列表 -> 教师勾选实际到场学生(默认全部勾选) -> 提交后批量写入attendance表,并将对应状态为“出勤”的学生课时包扣减1课时。
请假流程需要反向处理:家长提交请假申请 -> 机构管理员审批通过 -> 将该学生的考勤状态置为“请假”,同时课时包不做扣减,或者做一个“课后补课”的标记。这里有一个设计上的取舍:有的机构请假不扣课时,有的机构请假扣课时但允许补课。你的平台建议做成可配置项,在机构表里加一个字段allow_leave_refund(是否允许请假顺延课时),这样不同机构可以有自己的规则。
3.4 管理后台关键页面与接口设计
管理后台我建议做这几个页面:登录页、仪表盘、机构管理、教师管理、课程管理、排课管理、订单管理、考勤记录、财务报表。前端路由控制在登录后动态获取,接口全部走axios实例,在axios请求拦截器里统一携带token,响应拦截器里统一处理401(token过期)跳转登录。
课程管理的表单是整个后台最复杂的表单,因为课程和排课是分开的。创建一个课程时填基础信息(名称、分类、价格、总课时、适用年级、封面图、简介),然后进入排课页签,按“每周几+开始时间+结束时间+授课教师+教室”添加排课计划。课程创建完默认是草稿状态,机构管理员确认没问题后点击上架,家长端小程序才能看到。
接口设计遵循RESTful风格,几个核心接口列出来你们感受一下:
| 模块 | 接口路径 | 方法 | 说明 |
|---|---|---|---|
| 登录 | /api/auth/wx-login | POST | 微信登录 |
| 登录 | /api/auth/login | POST | 后台账号密码登录 |
| 课程 | /api/course/page | GET | 小程序端课程分页查询 |
| 课程 | /api/course/admin/page | GET | 后台课程分页(带状态筛选) |
| 课程 | /api/course/ | GET | 课程详情 |
| 排课 | /api/schedule/teacher/week | GET | 教师某周课表 |
| 排课 | /api/schedule/check | POST | 排课冲突检测 |
| 订单 | /api/order/pay | POST | 创建订单并模拟支付 |
| 考勤 | /api/attendance/save | POST | 教师提交考勤 |
| 课时包 | /api/package/my-list | GET | 家长查看剩余课时 |
| 报表 | /api/report/org-course-statistics | GET | 机构课程统计 |
分页查询统一封装成PageResult
3.5 Spring Boot版本过高引发的配置问题
热搜词里有一条“springboot版本太高”戳中了很多人的痛点。我详细说一下最常见的两个场景。
第一个场景:Spring Boot 3.x + MyBatis-Plus版本不兼容。Spring Boot 3基于Jakarta EE 9,javax包改名成了jakarta,所以MyBatis-Plus必须用3.5.3以上的版本才会适配。很多同学按老教程引入3.4.x版本的依赖,启动直接报错NoClassDefFoundError: javax/servlet/...。解决办法就是升级MyBatis-Plus到3.5.3+,或者干脆回到Spring Boot 2.7.x。
第二个场景:Swagger/Knife4j不兼容。Spring Boot 2.x搭配Knife4j 2.x没问题;Spring Boot 3.x则需要Knife4j 4.x,因为底层依赖从springfox切换到springdoc。如果你不想折腾文档工具的版本,可以直接用Spring Boot 2.7.x,这是最省心的方案。
给一个明确建议:毕设项目请优先Spring Boot 2.7.x + JDK 8。理由非常现实:你到时候部署的云服务器环境可能装的是JDK 8,你参考的大多数CSDN博客也是基于这个版本写的,如果遇到奇怪的问题,2.7的社区答案量是3.x的好几倍。这个选择不是技术上的退步,而是效率上的最优解。
4. 实操中的坑与排查技巧
4.1 微信小程序端“登录后获取不到用户信息”
这是新手最常见的问题,现象是:授权弹窗点了允许,但获取到的头像昵称是灰色的默认值。
原因在于微信官方调整了规则。之前的wx.getUserProfile接口在2022年10月25日之后,如果开发者调用时不带desc参数或者用户没有主动触发,会直接返回默认数据。而到了更晚的版本,wx.getUserProfile返回的nickName已经变成了“微信用户”,avatarUrl变成了默认灰色头像,这属于平台隐私保护策略的一部分,不是你的代码错了。
解决方案是:首次登录只调wx.login换取openid并自动注册,用户在“个人中心-编辑资料”页里主动填写昵称、上传头像,然后调用后端接口更新用户资料。这样既避开了微信的限制,也符合产品逻辑。如果你硬要在登录时拿真实头像昵称,在新版本基础库中确实是走不通的。
还有一个细节:在小程序开发者工具里,默认的“不校验合法域名...”选项一定要勾上,否则本地调试时请求http://localhost:8080会报“域名不合法”。这个选项的入口是:详情 -> 本地设置 -> 勾选“不校验合法域名、web-view(业务域名)、TLS版本以及HTTPS证书”。
4.2 小程序无法打开公众号文章
热搜里有一条“小程序无法打开公众号文章,需要配置什么”,这个在课后服务平台里也会遇到,因为你可能要推送一些教育类软文或公告详情。小程序里打开公众号文章,需要通过web-view组件加载,而web-view的域名必须是业务域名。配置路径:微信公众平台 -> 小程序账号 -> 开发管理 -> 开发设置 -> 业务域名。需要你先下载一个校验文件放到服务器项目根目录下,确保能通过https访问到,然后才能添加成功。
如果你只是想让用户查看文章详情,还有一个更简单的替代方案:文章内容直接存入数据库,小程序端用一个富文本组件展示详情页。这样完全绕开web-view的域名限制,体验和效果都不差。毕设阶段强烈推荐这种做法,少折腾。
4.3 Spring Boot项目部署到云服务器的注意事项
部署环节是很多人最后提交前最崩溃的环节。先说数据库:服务器上装MySQL后,记得把数据库的编码改成utf8mb4,否则存不了emoji字符。Navicat把数据库字符集设置为utf8mb4、排序规则utf8mb4_general_ci,然后在JDBC连接串里加上characterEncoding=utf8mb4。
然后说打包:Spring Boot项目用Maven打包成jar,执行mvn clean package -DskipTests。注意如果你本机是JDK 17打包,服务器上跑的也是JDK 17,那就没问题。但如果你用JDK 8开发,服务器上却是JDK 11,会出现UnsupportedClassVersionError。确保两边大版本一致,或者打包时配置maven.compiler.source/target为服务器上的JDK版本。
上传jar包到服务器,可以用宝塔面板或者直接用scp命令。启动命令推荐用nohup:
bash复制nohup java -jar babysitter-0.0.1-SNAPSHOT.jar --spring.profiles.active=prod > app.log 2>&1 &
把日志输出到app.log,出问题直接tail -f app.log看报错。千万不要关掉终端就退出,不挂nohup的话进程会直接死掉。
小程序端的请求地址,建议把所有API请求前缀抽成一个config.js文件,比如:
javascript复制const BASE_URL = 'https://你的域名/api'
开发环境可以改成http://localhost:8080/api,提交之前改成服务器地址。到时候服务器需要配置HTTPS域名,小程序上线必须要求HTTPS,而且域名必须备案。如果只是毕业答辩演示,用开发者工具勾选“不校验合法域名”就可以,不用真的买域名和证书。
4.4 部署Docker时遇到的问题
热搜里“springboot jdk1.8打包到docker desktop”这类问题也很常见。如果你打算用Docker部署毕设项目,注意Docker Desktop跑的是Linux容器,镜像需要包含JDK运行环境。最简单的做法是直接用官方openjdk镜像:
dockerfile复制FROM openjdk:8-jdk-alpine
COPY target/demo-0.0.1-SNAPSHOT.jar app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
如果你的jdk版本是17,就把基础镜像换成openjdk:17-jdk-alpine,镜像名可能因官方版本变动而不同,建议直接写eclipse-temurin:17-jdk-alpine更稳妥。构建完本地测试通过之后,用docker build -t after-service-platform:1.0 .构建,docker run -p 8080:8080启动。
这里有一个非常容易踩的坑:容器内的MySQL和宿主机上的MySQL连接。如果你在容器里运行Spring Boot,而MySQL是安装在宿主机上的,连接地址不能写localhost或127.0.0.1,因为容器内的localhost是容器自己。要写宿主机在Docker网桥上的IP,通常可以通过docker network inspect bridge查看,或者干脆把MySQL也做成容器,两个容器通过docker-compose编排,用服务名互相访问。推荐直接docker-compose,一次配好MySQL和App两个服务,比手动docker run好管理得多。
4.5 Spring Boot Banner生成器小工具
热搜里提了“springboot banner生成器”,这虽然不影响功能,但确实是个提升项目质感的小细节:在resource目录下放一个banner.txt,启动时就回显示ASCII Art风格的项目名或者个性化的文字。可以搜“Spring Boot Banner Generator”,在线选文字样式,生成后复制进banner.txt即可。演示的时候启动日志好看一点,也侧面说明你在细节上花过心思。
4.6 数据库事务失效的经典排查
做订单创建和课时包生成的时候,可能出现事务失效的情况。常见的失效场景有三个:
- 方法加了@Transactional但类没有被Spring管理(没加@Service/@Component注解)。
- 同类内部调用,比如OrderService里createOrder方法调用了本类的另一个事务方法,内部调用不会经过代理对象,事务就失效了。
- @Transactional加在非public方法上。
排查方法很简单:启动日志里如果出现“Self-invocation triggered”之类的提示就要注意;或者给方法里故意抛一个RuntimeException,看数据是否回滚。我建议事务方法直接写在Service层,Controller保持轻薄,跨Service调用时用注入的方式而不是同类互调。
4.7 常见问题速查表
下面这组问题是这个项目里最高频的,直接整理成表,遇到的时候自查。
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 启动报Unable to find main class | Maven没有正确扫描到启动类 | 检查spring-boot-maven-plugin配置,mvn clean之后再package |
| MySQL连接报Access denied | 用户名密码错误或host限制 | 确认创建了远程访问账号,GRANT ALL PRIVILEGES ON . TO 'root'@'%' |
| 中文乱码 | 数据库连接串没指定编码 | JDBC连接串加useUnicode=true&characterEncoding=utf8mb4 |
| 小程序请求接口报502 | 后端未启动或端口未开放 | 确认Java进程已启动,云服务器安全组和防火墙放行8080端口 |
| 课时扣减出现负数 | 并发扣减未做条件校验 | update语句加WHERE remain_count > 0限制 |
| 排课新增时不校验时间冲突 | 冲突检测SQL条件不完整 | 使用区间重叠判断条件,见上文SQL写法 |
| Knife4j页面打不开 | Spring Boot版本与Knife4j不匹配 | Spring Boot 2.x用Knife4j 2.x,3.x用4.x |
| 上传图片后访问404 | 没有配置静态资源映射或磁盘路径不存在 | 配置addResourceHandlers映射本地磁盘目录 |
| JWT解析报SignatureException | 密钥不一致或token被篡改 | 确认signWith和parseSigningKey用同一个secretKey |
5. 答辩准备与项目扩展建议
5.1 再谈一个关键工具:微信开发者工具的调试技巧
做小程序端开发,微信开发者工具是绕不开的。有几个调试技巧能极大提升效率。
第一,Network面板。小程序发起的每个请求在Network里都能看到,包括请求参数、响应结果、状态码。后端接口报了500一眼就能看到,直接切到后端日志查异常栈,比瞎猜快得多。
第二,真机调试。开发者工具模拟器上没有问题,不代表真机上没有问题。比如手机小程序里请求本地局域网IP的接口,需要手机和电脑连同一个WiFi,并且接口地址用电脑的局域网IP而不是localhost。有的电脑防火墙会拦截来自手机的请求,记得在防火墙里放行8080端口,或者直接关闭防火墙(答辩前临时用可以,平时不建议)。
第三,编译模式。小程序每次启动会默认进入首页,但你想调试“我的课程”页面,就要在编辑器右上角的“编译模式”里设置启动页面路径。这样可以快速跳到指定页面,省去反复从首页点进去的操作。
5.2 如何让论文与项目形成加分
项目做完了,论文和答辩PPT同样重要。很多同学功能做得很完整,但答辩时讲不出来,原因就是没有把“设计思路”提炼出来。
论文的核心章节我建议这样安排:
- 需求分析里画清楚用例图,把四种角色(管理员、机构、教师、家长)的用例分开画。
- 系统设计里把数据库ER图画出来,重点画出course、schedule、order、class_package、attendance这五张核心表的关联关系。
- 功能实现每个模块配2-3个关键代码截图+1-2个运行截图(小程序端配手机模拟器截图,后台配浏览器页面截图)。
- 系统测试部分,页面写一下测试环境,功能测试列一个测试用例表(用例编号、测试步骤、预期结果、实际结果),性能测试可以用JMeter简单测一下分页接口的响应时间,展示一下聚合报告。
答辩演示的顺序建议按业务流走:先用管理员身份创建一个机构 -> 机构管理员创建课程和排课 -> 家长小程序端浏览并报名 -> 教师端查看课表和签到 -> 后台查看订单和统计报表。整条链路走下来大概5分钟,但已经把系统设计的所有亮点全部展示完毕,比零散地展示单个功能效果强太多。
5.3 从毕设到上线的扩展方向
如果做完答辩后想让这个项目真正可用,或者放进简历作为项目经历,还有几个方向可以扩展:
- 接入微信支付V3,把模拟支付替换为真实支付链路。
- 增加消息推送,使用微信订阅消息(一次订阅一次推送),把请假审批结果、课时变动通知推送到家长小程序。
- 做多租户隔离:目前一个机构就是一套数据,如果想做成SaaS平台,需要在所有业务表里增加org_id字段,查询时做数据隔离。
- 增加培训机构的评价体系:家长课后对教师进行评分,机构后台能看到教师的综合评分趋势。
- 引入H5活动页:比如暑期招生报名专题页,H5里通过URL Scheme跳转到小程序对应课程页。
从实现难度来看,扩展方向的优先顺序是:订阅消息通知 > 多租户隔离改造 > 教师评价体系 > 真实支付。前三个属于在原有架构上的增量开发,第四个需要申请微信支付商户号,流程较长,适合真正有上线计划时再动。
6. 一些课外功课和避坑经验
最后说几个不常被提到,但实际操作中很重要的点。
第一,项目目录结构要清晰。这听起来像废话,但我见过太多同学代码全部塞在一个包下,Controller写了1000多行。建议按功能模块分包:controller一层、service接口+impl实现、mapper(dao)层、entity实体层、dto(接收参数)、vo(返回视图)、config(配置类)、common(统一返回体、异常处理、常量)、utils(工具类)。结构清晰的代码,你自己维护省心,答辩老师翻你代码的时候也会觉得专业。
第二,注释和命名规范。命名要见名知意:类名用名词,方法名用动词开头(get/list/save/update/delete),布尔类型用is前缀。关键业务方法上写两三行注释说明业务规则。不要用拼音命名变量,比如xingqi这种完全不符合规范。这个细节在答辩或面试评估代码质量时影响比想象中大。
第三,数据库脚本一定要保留一份从零创建到初始化数据的完整SQL文件。交付时,别人拿到项目能通过执行这个脚本把数据库全部跑起来。初始化数据里至少包含:一个管理员账号、两个机构账号、三个教师账号、两个家长账号、若干课程和排课。这样演示交接时不用从头录数据,直接登录即可看到界面效果。
第四,关于配置文件里的密钥。微信小程序的appid和secret、JWT的secretKey,这些不要硬编码在代码里,放到application.yml的配置项里,通过@Value注入。虽然毕设不一定有泄露风险,但良好的习惯会让你在后续职业面试的时候更有底气。
第五,文件上传的存储方案。课后服务平台需要上传课程封面图、教师头像、机构Logo。本地存储就行,在application.yml里配置一个upload.path的路径,用UUID重命名文件,避免同名覆盖。但文件上传接口要限制文件类型和大小,防止有人上传超大文件或者恶意脚本。图片类型检查可以用后缀+文件头双重校验,这部分代码量不大,加上的话系统安全性能上一个台阶。
这几个月如果你正在为毕业设计焦虑,我觉得不用太紧张。像“培训机构课后服务管理平台”这种项目,技术栈主流、业务场景真实、功能边界清晰,照着上面的设计一步步实现,进度推进起来会很快。写代码遇到问题的时候,先自己断点调试或者看日志,解决不了就去搜报错信息,基本都能在社区找到答案。真正重要的是把业务逻辑理解透彻,把从登录到课时扣减这条链路跑通,答辩的时候能讲清楚每一步的设计理由,这个项目就成了。
