“家教服务平台”这个选题,在计算机毕业设计里几乎每个学期都能见到三五次。原因不外乎两点:一是业务场景贴近生活,需求不用费劲编造;二是技术栈正好卡在 Java Web 主线上,SpringBoot 配合 SSM 这套组合,既能展示基本功,又不会因为刻意追求“高大上”把自己埋进坑里。这篇文章我打算完整梳理一遍这类项目的落地全过程:从业务拆解、数据库设计、核心代码逻辑,到环境搭建、运行调试,再到论文结构安排和答辩准备,尽量把我踩过的坑和验证过可行的方案都写出来。
如果你的毕设选题正是这类平台,或者你想在 SpringBoot+SSM 这套技术组合上练手,这篇文章应该能帮你省下大量试错时间。我会按真实的开发推进顺序来讲,而不是教科书式的章节排列,建议按顺序往下看,卡住的时候跳到对应章节查问题就行。
1. 项目整体设计与核心业务拆解
1.1 这个平台解决的现实痛点
传统家教对接大多靠中介或者熟人介绍,问题非常典型:家长看不到老师的真实授课水平,老师也不清楚学生的具体基础和性格习惯,价格不透明,试讲环节没有保障,课后一旦出现纠纷更是各执一词没有依据。
把整个流程搬到线上之后,核心价值就出来了。老师入驻需要提交资质材料由管理员审核,家长可以按科目、地区、价格、评分筛选老师,线上发起预约、生成订单,课后双方互评。这样一条链路把“找老师—预约—上课—评价—复购”的闭环完整覆盖了。从毕设选题角度看,这个业务覆盖了用户管理、信息管理、订单交易、评价系统、搜索匹配五大模块,足够撑起一篇完整的毕业设计论文,又不会复杂到一个人做不完。
1.2 用户角色与权限边界
系统里的角色非常清晰,一共三类,管理员、家教老师、家长(学员)。在设计数据库和接口之前,先把每个角色能干什么、不能干什么划清楚,后面写代码才不会有权限漏洞。
| 角色 | 核心权限 | 关键操作 |
|---|---|---|
| 管理员 | 系统管理、审核、统计 | 审核老师入驻、管理科目分类、查看订单数据、处理投诉 |
| 家教老师 | 接单、维护信息 | 申请入驻、设置可授课时间、查看预约、确认订单、查看评价 |
| 家长/学员 | 找老师、下单 | 按条件搜索、发起预约、确认完成、评价老师 |
这里有一个很多人容易忽略的设计点:老师入驻后不能直接接单,必须先由管理员审核通过,状态从“待审核”变为“已通过”,否则在老师列表里不展示。这一步看似简单,却能让整个项目的角色权限逻辑显得完整,答辩时老师也喜欢问这块的设计思路。
1.3 订单状态机设计
订单是整个系统的核心数据,状态流转设计得好不好,直接决定代码的复杂度。我在这个项目里用的状态流如下:
待接单 → 已预约 → 已确认 → 已完成 ↘ 已取消展开解释一下。家长提交预约后订单是“待接单”状态,老师看到可以接受或拒绝;一旦老师接受,变为“已预约”,此时家长可以取消,取消后整个链路终止;到了约定时间上完课,家长确认后变为“已完成”,这时候才能发起评价。
每个状态能做什么操作必须边界清晰。比如“待接单”状态下只有老师能操作,“已预约”状态下只有家长能取消,“已完成”之后才开放评价入口。状态在数据库里用一个整型字段存,配上常量类统一管理,不要散落在业务代码里写魔法数字。这块我会在后面的核心实现部分给出具体代码思路。
2. 技术选型解析:为什么 SpringBoot+SSM 是经典组合
2.1 技术栈的定位与取舍
先从名字说起。很多同学第一次看到“SpringBoot+SSM”会觉得矛盾,SSM 不是 Spring + SpringMVC + MyBatis 吗,SpringBoot 本身不就把 SpringMVC 包含进去了吗?
确实如此。在实际项目中,SpringBoot 负责自动配置和内嵌容器,把传统 SSM 项目里那一堆繁琐的 XML 配置大幅缩减,底层仍然是 Spring 家族那一套。SpringMVC 仍然负责请求分发,MyBatis 仍然负责数据库持久层的 SQL 操作。所以“SpringBoot+SSM”本质上是一个面向现代开发习惯的组合:用 SpringBoot 做框架底座,用 MyBatis 做持久层,SpringMVC 的映射注解继续用,仅此而已。
为什么不直接用 SpringCloud 那套微服务全家桶?因为家教平台的规模和业务复杂度远没到需要拆微服务的程度,单体架构加一个 Tomcat 就完全跑得动。答辨时如果被问“为什么不做前后端分离”,老实说“考虑到毕设体量和管理员后台的快速开发需求,采用单体架构配合模板引擎/简单前端是合理选择”即可,关键是要展示出你思考过利弊,而不是只会无脑上新框架。
2.2 数据库表设计:六张核心表
数据库设计是整个项目的地基,我见过太多人一上来就写代码,写到订单模块才发现表结构缺字段,被迫返工。这里我列出经过验证的六张核心表,字段做适当精简,但足够覆盖所有功能需求。
用户表(user)
用户表保存三类账号的公共信息,用 role 字段区分身份。
| 字段 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键自增 |
| username | varchar(32) | 登录名,唯一 |
| password | varchar(128) | BCrypt 加密后的密码 |
| phone | varchar(20) | 手机号 |
| role | tinyint | 0 管理员,1 老师,2 家长 |
| avatar | varchar(255) | 头像地址 |
| create_time | datetime | 注册时间 |
关于密码加密多强调一句:千万不要用明文存密码,答辩时这是硬伤。用 Spring Security 里的 BCryptPasswordEncoder 或者 SpringBoot 自带的加密工具都行,加密后再入库。
老师信息表(teacher_info)
老师的画像信息单独放一张表,与 user 表一对一关联。
| 字段 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键自增 |
| user_id | bigint | 关联用户表 |
| real_name | varchar(32) | 真实姓名 |
| subject_id | bigint | 授课科目 |
| school_name | varchar(64) | 毕业院校/任职机构 |
| experience | varchar(255) | 教学经历简介 |
| price_per_hour | decimal(10,2) | 每小时课时费 |
| rating | decimal(2,1) | 平均评分,默认 5.0 |
| status | tinyint | 0 待审核,1 已通过,2 已拒绝 |
这里有个细节:老师列表页要展示评分,但评分来自评价表,如果在列表页实时聚合计算,数据量稍大就会慢。我在实际实现里选择在老师表冗余一个 rating 字段,每次新评价生成时同步更新,以牺牲一点点写性能换取查询性能。
科目表(subject)
科目表非常简单,id、name、sort_order 三个字段就够。为什么要单独建表而不是直接用一个字符串存科目?因为后期要做“按科目筛选老师”的查询,用外键关联比用字符串模糊匹配快得多,而且分类调整时改一处就能全局生效。
订单表(orders)
订单表是核心业务数据,字段要配套完整的状态流转逻辑。
| 字段 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键自增 |
| order_no | varchar(32) | 订单编号(展示用) |
| student_id | bigint | 家长用户 id |
| teacher_id | bigint | 老师用户 id |
| subject_id | bigint | 科目 |
| total_price | decimal(10,2) | 订单金额 |
| status | tinyint | 订单状态 |
| appoint_time | datetime | 预约上课时间 |
| address | varchar(255) | 上课地点 |
| create_time | datetime | 创建时间 |
订单号不能直接用自增 id,对外展示时最好生成一个带时间戳的业务编号,比如“202505131030 + 随机四位”,既好看又难猜。
评价表(comment)
评价表负责记录订单完成后的互评内容,核心字段包括 order_id、from_user_id、to_user_id、content、score、create_time。需要注意评价表必须关联订单,避免同一订单被重复评价,这里要靠一个唯一索引或者业务层判断来控制。
关系层面,我强烈建议只在表设计层面保留逻辑外键关系,不要建物理外键。物理外键在后期删除数据时极其麻烦,报错信息还不直观,业务代码里通过条件约束完全能保证数据一致性,这个取舍在答辩时也能讲出理由。
2.3 接口设计与登录认证方案
接口风格统一用 RESTful,前端通过 HTTP 方法区分操作语义。比如:
GET /api/teacher/list 老师分页列表(支持条件查询) POST /api/order/create 创建订单 POST /api/order/cancel 取消订单 POST /api/comment/submit 提交评价 GET /api/admin/teacher/audit 管理员:待审核老师列表 POST /api/admin/teacher/audit 管理员:审核通过/拒绝所有接口返回一个统一结构,方便前端统一处理错误状态:
public class Result<T> { private Integer code; // 200 成功,400 业务错误,401 未登录 private String msg; private T data; }登录认证这块,如果做的是前后端不分离的传统模式,用 Session 就够了,原理简单、跟踪调试方便,答辩也好讲。如果选择前后端分离,前端用 Vue,后端提供 JSON 接口,那建议用 JWT + 拦截器的方式。JWT 的完整链路是:用户登录后服务端生成 token,前端每次请求在 Header 携带,后端拦截器解析校验并取出用户信息放入 ThreadLocal 或请求域,后续业务代码直接取。
拦截器实现思路不复杂,preHandle 方法里放行登录接口和静态资源,其余接口从 Authorization 头取 token 校验,失败直接返回 401,不放行到 Controller。需要放行的路径用 excludePathPatterns 配置,别漏了 Swagger 文档路径,不然本地调试时会被自己拦截器拦住。
3. 从零到一:环境搭建与核心模块实操
3.1 环境准备与版本避坑
很多项目跑不起来不是代码问题,是环境版本不匹配。这里我给出实测稳定的组合方案。
| 组件 | 推荐版本 | 备注 |
|---|---|---|
| JDK | 1.8 | SpringBoot 2.x 系列最佳搭档,稳定性最高 |
| Maven | 3.6+ | 低于 3.6 可能有依赖解析问题 |
| MySQL | 5.7 或 8.0 | 8.0 注意驱动名和时区配置 |
| IDEA | 任意较新版本 | 需要安装 Lombok 插件 |
这里必须有说一个高频坑:SpringBoot 版本和 JDK 版本强相关。如果你用的是 SpringBoot 3.x,最低需要 JDK17,很多同学的机器上还是 JDK8,导入项目后直接编译失败,报错信息里会提示“invalid source release”。所以在新建项目时先确认:JDK8 配 SpringBoot 2.7.x,JDK17 配 SpringBoot 3.2.x,不要盲目追求最新版本。我见过太多同学因为“版本太高”卡在启动阶段,这真不是代码问题。
Maven 这边还要做一件事:配置阿里云镜像仓库,否则从中央仓库拉依赖的速度会让你怀疑人生。修改 Maven 的 settings.xml 文件里的 mirror 节点,加一段 mirror 配置,指向 aliyun 的 maven 仓库地址,具体坐标网上很容易找到,这里不重复贴了。
3.2 项目结构与应用配置解读
创建一个标准 Maven 项目后,目录结构是这样的:
src/main/java/com/example/tutor/ ├── TutorApplication.java 启动类 ├── controller/ 控制层 ├── service/ 业务层 ├── mapper/ MyBatis 持久层接口 ├── entity/ 数据库实体类 ├── dto/ 数据传输对象 ├── config/ 配置类(拦截器、WebMvc) └── common/ 通用类(Result、常量) src/main/resources/ ├── application.yml 应用配置 ├── mapper/ MyBatis XML 文件 └── static/templates 前端资源工程结构不要堆得太深,分层清晰就行。控制层只做参数接收和结果返回,业务逻辑写在 Service,SQL 写在 Mapper XML,这是 SSM 项目最经典的规范,也是答辩时加分的地方。
application.yml 里的几个关键配置项,我贴一段实测可用的完整配置:
server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/tutor_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver servlet: multipart: max-file-size: 10MB max-request-size: 20MB mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.tutor.entity configuration: map-underscore-to-camel-case: true看一眼就能明白的东西我不展开,但有两个点值得注意。一是 MySQL 8.0 的驱动类名是 com.mysql.cj.jdbc.Driver(带 cj),5.7 的是 com.mysql.jdbc.Driver,弄反会启动直接报错。二是 map-underscore-to-camel-case 必须设为 true,否则数据库下划线字段和实体类驼峰属性匹配不上,查出来全是 null,这是排查率极高的问题。
3.3 核心功能实现逻辑逐段拆解
登录注册模块
注册接口处理逻辑:前端提交用户名、密码、手机号、角色,后端先查重,用户名存在直接返回“账号已注册”,不存在则密码 BCrypt 加密后入库。登录接口处理逻辑:按用户名查用户,用 BCrypt 的 matches 方法比对密码,成功后把用户信息写入 Session 或生成 token 返回。
这里给一个实操建议:涉及密码错误提示时,不要区分“用户名不存在”和“密码错误”,统一返回“用户名或密码错误”,防止账号枚举风险。毕设项目也要养成这个安全意识,答辩老师问到也能说出个一二三。
家教匹配查询模块
老师在列表页展示,支持按科目筛选、按价格区间过滤、按评分排序。核心 SQL 用 MyBatis 动态 XML 实现:
<select id="selectTeacherPage" resultType="com.example.tutor.entity.TeacherInfo"> SELECT * FROM teacher_info WHERE status = 1 <if test="subjectId != null"> AND subject_id = #{subjectId} </if> <if test="minPrice != null"> AND price_per_hour >= #{minPrice} </if> <if test="maxPrice != null"> AND price_per_hour <= #{maxPrice} </if> ORDER BY <choose> <when test="sortType == 'rating'">rating DESC</when> <otherwise>create_time DESC</otherwise> </choose> </select>懂 MyBatis 的人一眼就能看明白,这里用 if 标签实现可选条件拼接,用 choose 实现动态排序字段,不需要手写字符串拼接 SQL,安全又灵活。
订单创建模块
订单创建是业务最集中的地方。我的实现里,Service 层按如下顺序处理:
- 校验当前登录用户角色必须是家长。
- 校验要预约的老师状态正常、老师存在。
- 校验预约时间不能早于当前时间。
- 查该老师在该时间段是否有冲突订单,有则拒绝。
- 生成订单号,组装订单实体,状态设为“待接单”,插入数据库。
有人会问,一个家教预约系统,并发场景下怎么保证同一时间段不被两个家长抢到同一个老师?回答有两种方案。简单方案是给 orders 表加一个唯一索引,字段组合是 teacher_id + appoint_time,数据库层面兜底;复杂方案是加乐观锁,更新时比较版本号。对毕设项目来说,唯一索引就够了,但如果你能在论文里把这个并发问题写清楚,会是个明显的加分点。
评价与评分更新模块
提交评价接口先校验订单存在,且状态为“已完成”,再校验当前用户确实是该订单的家长或老师,最后检查该订单是否已有评价。全部通过后插入评价记录,并重新计算该老师的平均评分:
UPDATE teacher_info SET rating = ( SELECT ROUND(AVG(score), 1) FROM comment WHERE to_user_id = #{teacherUserId} ) WHERE user_id = #{teacherUserId}这里注意一个细节:评价表里存的是用户 id,而评分更新要定位到老师信息表,两张表的关联字段是 user_id,别搞混了。我在第一次写这段逻辑的时候就因为关联字段弄错,导致评分一直更新不上去,排查了半天才发现两张表关联的字段选错了。
3.4 源码导入与调试部署全流程
拿到一个完整的源码包后,正确的上手顺序是这样的。
第一步,打开 IDEA,File 菜单选择 New,然后选择 Project from Existing Sources,定位到项目根目录,选择 Maven 的 pom.xml 文件,IDE 会自动识别并开始下载依赖。
第二步,在本地 MySQL 里新建一个数据库,名字和 application.yml 里配置保持一致,然后执行项目提供的 db.sql 脚本,导入建表语句和初始测试数据。
第三步,修改 application.yml 里数据库账号密码,确认端口没有被占用。
第四步,找到启动类,右键 Run,观察控制台日志。看到 “Started TutorApplication” 字样说明启动成功。
浏览器访问 http://localhost:8080 ,如果出现登录页,说明项目已经跑起来了。这一步是整个项目最让人有成就感的时刻,但后面才是真正的考验,因为运行起来之后会陆续冒出一堆莫名其妙的问题。
4. 常见问题与排错实录
4.1 环境与版本问题速查
这一段我必须说,十个学生找我调试,至少六成的报错都集中在环境问题上,代码本身的 bug 反而是少数。我挑几个高概率踩坑的场景整理成表,附上排查思路。
| 报错现象 | 原因分析 | 解决办法 |
|---|---|---|
| Tomcat 端口 8080 被占用 | 其他程序占用了端口 | 改 application.yml 里的 server.port,或者用系统命令查占用进程并结束它 |
| Access denied for user root | 数据库密码不对 | 检查配置文件密码与实际密码是否一致,注意别踩到 IDEA 环境变量覆盖 |
| Unknown database tutor_db | 数据库还没创建 | 先到 MySQL 里执行建库语句再跑程序 |
| 实体类字段全部为 null | 驼峰映射没开启 | 在配置中加 map-underscore-to-camel-case: true |
| 中文乱码 | URL 里缺编码参数 | 在 jdbc url 后加 useUnicode=true&characterEncoding=utf8 |
比如说这个中文乱码问题,我发现大家最容易忽略。初次连接数据库时 URL 只写了 serverTimezone,没有指定字符编码,那么从数据库读出来的中文可能全部变成问号。这不是代码问题,是连接参数问题,把 URL 改成上面配置里的完整写法就能解决。
端口占用这个事也有讲究。Windows 下先用命令行查 8080 端口的占用情况,找到占用进程的任务编号,再通过任务管理器结束对应进程。有时候是之前启动的项目没停干净,IDEA 里还会残留一个已经退出的运行实例,再次启动时就报端口冲突。最干脆的办法是把项目停干净再重新启动,或者改端口到 8081 避免和其他开发项目冲突。
4.2 MyBatis 与数据源高频坑
Mapper 接口和 XML 绑定失败是 MyBatis 项目的头号问题。现象是启动时报 Invalid bound statement,也就是调接口时找不到对应的 SQL 语句。排查思路分三步:第一步检查 XML 文件有没有放在 resources 目录下且路径与 mapper-locations 配置一致;第二步检查 XML 的 namespace 是否写全了接口的全限定名;第三步检查接口方法名和 XML 里的 id 是否一致。
这里还有个隐藏坑:IDEA 默认不会把 src/main/java 目录下的 XML 文件编译到 target 目录,如果你把 Mapper XML 文件放在 java 包下就会踩中。正确做法是把所有 XML 统一放在 src/main/resources/mapper 目录下,再用 mapper-locations 配置扫过去,省心。
数据源报错 Communications link failure,通常是 MySQL 服务没有启动,或者数据库连接超时。排查时先确认 MySQL 服务状态正常,再看数据库的 wait_timeout 配置。开发阶段建议把连接池的测试配置加上,Druid 或 HikariCP 都能配置连接测试,避免数据库长时间空闲后连接失效,点一下页面就报错的特尴尬情况。
4.3 调试技巧:日志与断点配合
日志文件同时输出到控制台和文件,这个需求我在带项目时经常被问到。SpringBoot 默认的 logback 配置就可以实现,在 application.yml 里配置如下:
logging: file: name: logs/tutor.log level: com.example.tutor: debug保存重启后,日志会同时打印到控制台和 logs 目录下的文件里。排查线上问题时,文件日志的价值极大,因为控制台历史记录会滚动丢失,文件里则完整保留了所有请求处理过程的痕迹。
另一个高频需求是看 SQL 执行的完整过程。打开 MyBatis 的 SQL 日志级别,在配置里加上:
logging: level: com.example.tutor.mapper: debug这样控制台就会打印每个 Mapper 接口执行的 SQL 语句和参数、结果集数量,排查“查出来是空但数据库里明明有数据”这类问题时,这个日志几乎是定位的第一手段。
IDEA 断点调试方面,说一个实用经验:如果怀疑某个条件分支走错了,别着急一行行跟,直接在 if 判断那一行打断点,用条件断点功能,设置条件表达式,只有满足时才暂停。比如排查“某个订单为什么状态不对”,直接给订单 id 加条件断点,不用反复手动跳过不相关的订单数据,效率高一个量级。
关于反编译还有一个真实场景:你在跑别人的源码时发现某个 class 文件行为很奇怪,但手头只有编译后的 jar,没有源码。IDEA 的依赖列表里直接展开外部库的 jar 包,点开 class 文件就会自动反编译出可读的 Java 代码,足以满足调试阶段查看依赖内部逻辑的诉求。
5. 论文写作与答辩实战经验(LW 篇)
5.1 论文结构安排建议
毕设论文和平时写的技术博客完全不是一个逻辑,它有固定的章节套路,按学校的要求来就行,但骨架基本逃不开这几块:
- 绪论:写背景、意义、国内外现状,这一章最好写,但要注意别全抄,查重会教你做人。
- 需求分析:画用例图,列功能需求表,写可行性分析。
- 系统概要设计:整体架构图、功能模块划分、数据库 ER 图。
- 系统详细设计与实现:这是核心章节,重点写核心模块的设计思路和关键代码,配页面截图和说明。
- 系统测试:写测试用例表,覆盖正常流程、异常输入、权限校验场景。
页数分配上,我建议需求分析和详细设计占大头,这两块有图有表看着充实,写起来也不费劲。系统测试不要只写“测试通过”,要给出具体的测试用例编号、输入数据、预期结果、实际结果,这份工作量是展示严谨性的关键点。
数据库 ER 图我是强烈建议画清楚的,不知道怎么画的话,可以用数据库设计工具根据表结构自动生成关系图,导出图片后适当调整一下布局就能放进论文。所有系统截图要注意统一浏览器窗口大小,截图的清晰度直接影响答辩老师的第一印象。
5.2 答辩高频问题与演示策略
答辩环节问的问题,翻来覆去就那么几个,我整理一下高频问题清单和应对思路。
核心业务类问题:
- 为什么选择这个课题?回答要落在“痛点真实、功能覆盖完整、技术栈匹配”三个点上。
- 系统有哪些角色,各自的权限边界是什么?结合我前面提到的三类角色来说即可。
- 订单状态是怎么设计的?把状态流转图直接画出来,讲清楚每个状态允许谁操作。
- 两个家长同时预约同一个老师怎么办?这是加分题,回答时提唯一索引或锁机制,展示你考虑过并发场景。
技术原理类问题:
- SpringBoot 相比传统 SSM 有什么优势?自动配置、内嵌容器、起步依赖,这三个点展开说。
- MyBatis 中 #{} 和 ${} 的区别?“#{}” 是预编译参数占位符,安全;“${}” 是字符串拼接,有注入风险。这句要背熟。
- 拦截器和过滤器的区别?一个是 SpringMVC 层面的拦截,一个是 Servlet 层面的过滤,执行时机和作用范围不同。
演示环节要给老师留好印象,有几件事必须提前准备。第一,数据库里准备好测试数据,至少要有三四个老师、十来个订单、一些评价,页面才不显得空。第二,演示流程要提前串一遍,从注册账号、登录、老师入住、管理员审核、搜索老师、下单、评价,在十分钟内完整走通,中途不要临时去数据库里改状态。第三,准备好一两个“小意外”的恢复方案,比如演示时接口报错,你要能冷静解释是测试环境参数问题,重启一下服务或者改个参数就好。
我给一个非常具体的建议:提前把自己的账号密码、演示用的测试数据清单、需要演示的完整路径写在一张纸上,答辩前对着走一遍。很多同学临场紧张,大脑一片空白,有清单在手就稳得多。
关于论文查重,有一个必须强调的坑:不要直接复制代码段到论文里凑字数,代码不算查重还行,但把别人类似项目的整段文字描述复制进来,查重率会直接飘红。正确的做法是把业务逻辑用自己的语言重新组织,代码只截取核心片段且做适当注释说明。
写在最后
带过几届做这个选题的同学,我发现一个规律:项目跑通的那一刻大家都特别兴奋,但答辩之后真正拉开差距的,是对业务逻辑的思考深度。能说清楚“订单状态为什么这么流转”“并发预约怎么防冲突”“评分为什么冗余存储”这几个问题的人,和只会把 CRUD 背一遍的人,最终成绩完全是两个档位。
我个人的建议是:代码量不够丰富不要慌,把核心模块的代码读透、能讲出设计理由,比堆砌一堆自己都没跑通的“高级功能”要有用得多。如果后续你有精力,可以在这个项目基础上加一个按距离排序的“附近家教”功能,或者用 WebSocket 做预约消息实时提醒,这两个方向都是安全的、有探索价值的扩展点,能让项目在答辩时更有亮点。先把手头的模块稳扎稳打地做完,比什么都重要。