很多大四学生选毕设题目时,一看到"家教信息发布平台"这种题,第一反应是"简单",觉得无非是发布信息、查看信息、联系家长,十几天就能交差。等真正动手写代码就会发现:用户角色权限怎么分、预约时间怎么校验、同一个家教老师被两个家长抢时段怎么办、订单状态怎么流转不乱套——这些问题一个比一个扎心。我在带毕设的过程中见过太多"答辩前一天发现核心预约逻辑根本没法演示"的人,基本都是栽在业务设计没想透。
这篇就聊一下我自己用 SpringBoot 做家教信息高效对接平台时沉淀下来的设计思路和实现细节。项目本身不大,但角色划分、时间预约、消息通知、状态流转这些点很典型,拿来当毕设或者作为 SpringBoot 综合练习都很合适。全文不假设你已经很熟练,但也不会只写"点击创建工程、写个Controller"这种水文,而是从业务出发,告诉你每一步为什么这么设计,代码落地的依据是什么。
1. 这个平台到底在解决什么问题:先别碰代码,把业务画像画清楚
做任何系统,最忌讳的是上来就建表。家教平台表面上是"发信息和看信息",实际上牵扯到三类角色的协作,每一类人对系统的诉求完全不同。
- 学生/家长:想找到合适的家教,关注老师的资历、授课经验、距离远近、可预约的时间段和价格。他们要的是"快速筛选出靠谱的人"。
- 家教老师:想把自己的授课信息发布出去,管理空闲时段,跟进预约请求。他们要的是"单量管理方便,避免时间冲突"。
- 平台管理员:需要审核老师发布的信息,处理用户举报,维持信息的合法性和真实性。他们要的是"后台可见可控"。
如果把这三类诉求直接映射到代码层面,你就能推导出模块清单:用户模块(注册/登录/身份信息完善)、家教信息模块(发布/编辑/上下架)、在线预约模块(发起预约/确认/取消)、后台管理模块(用户审核/信息审核)。再往下拆就是表结构和接口。
所以"家教信息服务发布平台"这个题目,真正考察的点不是 CRUD,而是你有没有从需求里提炼出角色权限、业务状态、时间维度约束的能力。这也是答辩时老师最关心的:一个家教老师能不能把自己变成"可约状态"?改成不可约之后,已经发起的预约怎么处理?同一时间家长只能预约一个动作该落在哪一层?
建议你在需求分析文档里先把这几条理清楚,再进代码。画不画UML图不重要,脑子里这张业务图必须清晰。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术选型和搭建:SpringBoot版本不是越高越好,搭配才有价值
2.1 SpringBoot版本:2.7.18比3.x更适合毕设场景
标题里点名了 SpringBoot,但没说版本。我在实际项目中多次吃过版本选择的亏,这里直接给结论:如果目标是快速、稳定地完成毕设,SpringBoot 2.7.18 是首选,而不是最新的 SpringBoot 3.x。
原因很实在:
- SpringBoot 3.0 基于 Spring Framework 6,要求 JDK 17 起步。很多同学的电脑里环境还是 JDK 8,老师给的参考代码也是 JDK 8 写的。但 2.7.18 是 2.x 系列最后一个版本,既能 JDK 8 运行,也支持到 JDK 11/17,兼容度最高。
- 毕设常用的第三方组件版本,比如 MyBatis-Plus、Druid、Knife4j,绝大多数稳定文档和网上的解决方案都是基于 SpringBoot 2.x 的。升到 3.x 后遇到问题,搜出来的答案经常对不上。
- 大部分学校机房的项目运行环境、部署演示用的机器也是 JDK 8 居多,用 3.x 版本反而给自己制造部署障碍。
SpringBoot 3.x 当然好,但如果你只是想做平台功能演示,没必要跟版本较劲。版本号低一档,答辩不会扣分,跑不起来才是硬伤。
2.2 整套技术栈的搭配清单
下面是我在多个毕设和中小型项目中反复使用并验证稳定的一套组合:
| 层次 | 选型 | 说明 |
|---|---|---|
| 后端框架 | SpringBoot 2.7.18 | 稳定、资料多 |
| 持久层 | MyBatis-Plus 3.5.x | 单表CRUD不用写SQL,复杂查询用注解或XML |
| 数据库 | MySQL 8.0 | 功能全面,教学环境最常见 |
| 安全认证 | Sa-Token 或 JWT + 拦截器 | 毕设规模不需要引入Spring Security全家桶 |
| 接口文档 | Knife4j(基于Swagger2) | 生成可调试的在线文档,答辩演示加分 |
| 前端 | Vue 3 + Element Plus + Axios | 前后端分离,国内社区资料多 |
| 构建工具 | Maven 3.8+ | 不折腾,别用Gradle,除非你真的很熟 |
2.3 骨架式工程结构:按业务分包比按技术分包好用
网上很多教程喜欢把类都丢进 controller / service / mapper 三层。诚然这在代码量不大时没问题,但家教平台角色多、模块间有交叉操作,我更建议你按照 module 分包,一个业务域一个包,内聚性会好很多。
code复制com.example.tutor
├── common # 统一返回体、全局异常、公共工具类
├── config # 配置类(拦截器、跨域、MyBatis-Plus分页等)
├── security # 登录鉴权相关(JWT工具、拦截器、注解)
├── module
│ ├── user # 用户注册登录、个人信息
│ ├── tutor # 家教信息发布/展示
│ ├── order # 预约订单
│ ├── evaluation # 评价
│ └── admin # 后台管理
module 包里再各建 controller、service、mapper、entity、dto。这样哪怕后面想拆成独立的微服务模块,零件也是齐的。
3. 数据库设计:五张核心表决定整个平台的上限
为了说清楚业务,我把整套设计的核心表结构放到这一节展开。你需要关注的不只是字段名,还有每个字段为什么存在、怎么被使用。
3.1 用户表:用 user_type 区分角色,而不是建三张表
见过有些设计把家长表、老师表、管理员表分开建。从纯范式来说这没大问题,但在小程序/Web 端都要共用登录态的毕设场景里,维护成本很高。我的习惯是建一张 t_user,通过 user_type 区分角色,再补充一张 t_teacher_profile 保存老师特有的资质、教学年限、授课科目等扩展信息。
sql复制CREATE TABLE `t_user` (
`id` bigint NOT NULL AUTO_INCREMENT,
`phone` varchar(20) NOT NULL COMMENT '登录手机号',
`password` varchar(100) NOT NULL COMMENT 'BCrypt密文',
`user_type` tinyint NOT NULL DEFAULT 1 COMMENT '1-学生/家长 2-老师 3-管理员',
`nickname` varchar(50) DEFAULT NULL,
`real_name` varchar(50) DEFAULT NULL COMMENT '真实姓名,老师实名认证用',
`avatar` varchar(255) DEFAULT NULL,
`status` tinyint DEFAULT 1 COMMENT '1-正常 0-禁用',
`create_time` datetime DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_phone` (`phone`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
老师扩展表:
sql复制CREATE TABLE `t_teacher_profile` (
`id` bigint NOT NULL,
`user_id` bigint NOT NULL,
`subject` varchar(50) COMMENT '擅长科目',
`grade_range` varchar(50) COMMENT '可授课年级,如 小学-高一',
`hourly_rate` decimal(10,2) COMMENT '课时费/小时',
`experience` varchar(20) COMMENT '教龄',
`education` varchar(100) COMMENT '毕业院校/在读院校',
`introduction` text COMMENT '自我介绍',
`audit_status` tinyint DEFAULT 0 COMMENT '0-待审核 1-已通过 2-已拒绝',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
老师发布信息后,管理员不一定每次都要审核。更好的做法是平台建立"老师资质审核"机制,首次完善信息并申请成为老师时置为待审核,管理员通过后就可以在线发布课程信息。这个逻辑我在 section 4 会讲。
3.2 家教信息表:上架状态与"老师身份状态"要区分开
家教信息表是整个信息展示层的核心。注意状态字段不只一个:
status表示信息本身的上下架状态(草稿、上架、下架)delete_flag做逻辑删除
sql复制CREATE TABLE `t_tutor_info` (
`id` bigint NOT NULL AUTO_INCREMENT,
`teacher_id` bigint NOT NULL COMMENT '关联用户表ID',
`subject` varchar(50) NOT NULL COMMENT '科目',
`grade` varchar(50) NOT NULL COMMENT '年级',
`teaching_mode` tinyint DEFAULT 1 COMMENT '1-线下 2-线上 3-均可',
`city` varchar(50) DEFAULT NULL,
`district` varchar(50) DEFAULT NULL,
`address_detail` varchar(200) DEFAULT NULL COMMENT '线下授课区域',
`price` decimal(10,2) DEFAULT NULL COMMENT '课时费',
`duration` int DEFAULT 60 COMMENT '每课时分钟数',
`description` text COMMENT '详细描述',
`cover_image` varchar(500) DEFAULT NULL COMMENT '老师照片/资质图片',
`status` tinyint DEFAULT 0 COMMENT '0-草稿 1-上架 2-下架',
`schedule_rule` varchar(500) DEFAULT NULL COMMENT '可约时间规则(JSON扩展)',
`create_time` datetime DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
`delete_flag` tinyint DEFAULT 0,
PRIMARY KEY (`id`),
KEY `idx_subject_status` (`subject`, `status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
这里有个设计细节:明明老师发布了"小学五年级数学"的信息,而他的个人信息里擅长科目也可以"含数学",这两者什么关系?我的设计是 t_teacher_profile 里的 subject 表示"老师能教什么",t_tutor_info 里的 subject 表示"这条广告具体卖什么课"。如果同一老师能教几门课,就发多条 tutor_info,而非一条塞多个科目。这样搜索列表页能直接按 info 表的 subject 精确过滤。
3.3 预约订单表:订单是业务过程的快照
预约不是聊天的替代品,它是把"意向"变成"确定"的关键动作。那么订单表必须快照下单时的关键信息,而不是下单后再去关联家教信息表取数据。因为老师之后改价、改简介,不应该影响已经确认的订单金额,这在业务上叫快照。
sql复制CREATE TABLE `t_appointment` (
`id` bigint NOT NULL AUTO_INCREMENT,
`order_no` varchar(32) NOT NULL COMMENT '业务订单号',
`tutor_info_id` bigint NOT NULL,
`student_id` bigint NOT NULL COMMENT '下单家长/学生用户ID',
`teacher_id` bigint NOT NULL COMMENT '接单老师用户ID',
`subject` varchar(50) DEFAULT NULL,
`grade` varchar(50) DEFAULT NULL,
`price_snapshot` decimal(10,2) DEFAULT NULL COMMENT '下单时课时费快照',
`appoint_date` date DEFAULT NULL COMMENT '上课日期',
`start_time` time DEFAULT NULL COMMENT '开始时间,如 14:00',
`end_time` time DEFAULT NULL COMMENT '结束时间,如 16:00',
`status` tinyint DEFAULT 0 COMMENT '0-待确认 1-已确认 2-已完成 3-已取消 4-已拒绝 5-待支付',
`remark` varchar(500) DEFAULT NULL,
`cancel_reason` varchar(255) DEFAULT NULL,
`create_time` datetime DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_order_no` (`order_no`),
KEY `idx_teacher_time` (`teacher_id`, `appoint_date`, `start_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
为什么把 teacher_id、appoint_date、start_time 建成联合索引?因为后面做时间冲突检测会高频按这三个字段查询。没有这个索引,数据量一上来,每条预约都会慢。
3.4 时间规则表(可选但推荐加)
毕设如果想在答辩时演示"老师可配置每周固定可约时间,系统自动过滤掉已约时段"这种亮点,强烈建议把老师的可约时间从 TutorInfo 里的 schema_rule 字段升级成 t_teacher_schedule 表,一行代表一个周规则或一个排班片段。
sql复制CREATE TABLE `t_teacher_schedule` (
`id` bigint NOT NULL AUTO_INCREMENT,
`teacher_id` bigint NOT NULL,
`tutor_info_id` bigint DEFAULT NULL COMMENT '关联特定家教信息,null表示该老师全部课程通用',
`week_day` tinyint DEFAULT NULL COMMENT '1-7代表周一到周日',
`slot_date` date DEFAULT NULL COMMENT '如果有特定日期可用则填,否则按week_day循环',
`start_time` time NOT NULL,
`end_time` time NOT NULL,
`type` tinyint DEFAULT 1 COMMENT '1-固定周期 2-单次排班',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
我用它做过"老师勾选了每周六 09:00-12:00 可约,系统在前端只展示这一段的空闲时间"的功能,答辩演示效果特别好。
3.5 评价和消息:让系统形成闭环
每个订单完成后,家长可以评论一次,老师也可以回复。评价表关联订单ID,可以防止"没上过课的人乱评论"。另外,每当订单状态变化时应该产生站内消息通知对方,这个用一张简单的 t_message 表就够了,不需要引入消息队列。
sql复制CREATE TABLE `t_evaluation` (
`id` bigint NOT NULL AUTO_INCREMENT,
`appointment_id` bigint NOT NULL,
`student_id` bigint NOT NULL,
`teacher_id` bigint NOT NULL,
`rating` tinyint DEFAULT 5 COMMENT '1-5星',
`content` varchar(1000) DEFAULT NULL,
`create_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_appointment` (`appointment_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
4. 关键流程的落地实现:不止是CRUD,是把约束落进代码
4.1 统一接口风格与全局异常
不管页面多小,接口风格不一致会让自己后期非常痛苦。我在项目里先做了一个公共返回体:
java复制@Data
public class Result<T> {
private Integer code;
private String message;
private T data;
public static <T> Result<T> success(T data) { ... }
public static Result<?> error(Integer code, String message) { ... }
}
同时配一个全局异常处理器,把业务异常(例如预约冲突、用户被禁用、参数非法)用自定义异常抛出,统一封装成 code=500 或 code=400 的 JSON。这样 Controller 里的业务代码可以非常专注,不用每个方法 try-catch。
4.2 登录鉴权:用 Sa-Token 而不是手写 JWT
一说到登录鉴权,很多教程让你手写 JWT 工具类。但手写 JWT 只解决了"token 怎么生成、怎么解析",没有解决"token 怎么在服务端集中踢人、怎么判断权限是否足够"。Sa-Token 本身支持了登录会话管理、权限校验,内部默认实现也是基于 Token 的,但代码量和坑比 Spring Security 小得多。如果你是纯后端实现、不想写复杂的 SecurityConfig,用 Sa-Token 省一半时间。
如果演示环境不允许引入额外依赖,另一个可靠方案是传统 Spring Boot 拦截器 + Redis 缓存 Token。这里更推荐在 WebMvcConfigurer 里注册一个 HandlerInterceptor,对 /api/** 除登录接口外做 token 校验,然后从 Redis 或本地缓存读取当前用户信息存入 ThreadLocal。这样后端接口里通过 UserContext.getUserId() 就能取到当前登录人,不需要把参数传来传去。
4.3 信息发布后审核的状态机
毕设评审时最怕的问题是"如何保证信息真实有效"。这里可以设计两级状态:
- 用户申请成为老师并填写资料后,
teacher_profile.audit_status在 0(待审核) - 管理员在后台通过后,老师才具备"上架 tutor_info" 的资格
- 后续每条 tutor_info 如果老师想上架,还要走一遍
status = 0待发布 -> 1已发布
有同学问"这样会不会太复杂?"——不会。这就是平台型产品的核心价值点。管理员端只要做 3 个接口:列表、详情、审核。数据表已经设计好了,代码也不复杂。
java复制@PostMapping("/admin/teacher/{userId}/audit")
@RequiresRole("admin")
public Result<?> auditTeacher(@PathVariable Long userId,
@RequestParam Integer auditStatus,
@RequestParam(required = false) String rejectReason) {
// 1. 非管理员角色拦截
// 2. 将 user_type 从1改成2,并把 audit_status 改为通过/拒绝
}
4.4 搜索接口的取舍:一次性把筛选条件做对
家教平台首页最重要的接口是家教列表。常见设计是下面这样:
code复制POST /api/tutor/search
参数:
subject 科目
grade 年级
city 城市(可选)
district 区(可选)
minPrice/maxPrice 价格区间
teachingMode 线上/线下
sortBy 排序字段(price_asc/price_desc/rating_desc)
pageNum
pageSize
用 MyBatis-Plus 的 LambdaQueryWrapper 构造动态查询,加上条件拼接是最好维护的路径。需要注意:当搜索条件没传时,默认不能把全部失效记录也带出来,要强制拼上 eq(TutorInfo::getStatus, 1).eq(TutorInfo::getDeleteFlag, 0)。
还有一个小细节:搜索结果的"老师评分"不从 TutorInfo 表读,应该在返回 VO 时联查评价表 AVG(rating)。如果评价记录多,要单独做评价汇总字段;但毕设阶段实时 AVG 完全来得及。
4.5 预约单生成与时间冲突检测(核心中的核心)
预约是整个系统最容易出 bug 的地方。我把自己写过的一套可靠流程贴出来:
java复制@Transactional(rollbackFor = Exception.class)
public void createAppointment(CreateAppointmentRequest request) {
// 1. 拿到 tutorInfoId, 查询当前家教信息是否 status=1(上架)
// 2. 校验预约时间是否在老师 schedule 规则内
// 3. 时间冲突检测
// SELECT COUNT(*) FROM t_appointment
// WHERE teacher_id=?
// AND appoint_date=?
// AND end_time > ? -- 已约结束时间 > 新约开始时间
// AND start_time < ? -- 已约开始时间 < 新约结束时间
// AND status IN (0,1) -- 关心待确认和已确认的预约
// 4. 若 count>0 则抛出“该时间段已被预约”
// 5. 生成 orderNo(yyyyMMddHHmmss + 随机数)
// 6. 插入 appointment,status=0
// 7. 发送站内消息给老师
}
这段 SQL 里的交叉判断逻辑是决定正确性的关键。判断两个时间段是否重叠,不要写 start_time >= 已约start && end_time <= 已约end 这种只覆盖一种包含关系的条件。使用 end_time > 新开始 AND start_time < 新结束,就能覆盖相交、包含、被包含所有重叠情况。这是我写过很多查询总结出来的最小完备条件。
5. 前端页面与后端接口的联动细节
虽然项目标题是 java/SpringBoot 后端主导,但毕设通常要求前后端联调。聊几个能显著提升完成度、又不至于拖慢进度的思路。
5.1 前端不是从零手写,而是套用后台管理模板
很多同学一上来就要从 HTML/CSS 手搭前台门户页面。如果时间不紧张是很好的练习,但如果目标是写完并顺利答辩,我建议用 Vue3 + Element Plus 搭一个管理后台模板来覆盖系统的大部分页面,另外单独做一个面向家长的简易门户(首页搜索列表 + 详情 + 预约表单),门户引用 BootstrapVue 或 Vant 组件,不要重造轮子。
5.2 Axios 拦截器统一处理登录态和错误
在 request.js 里做一次 Axios 拦截器封装:
javascript复制service.interceptors.request.use(config => {
const token = localStorage.getItem('token')
if (token) config.headers['Authorization'] = token
return config
})
service.interceptors.response.use(res => {
const code = res.data.code
if (code === 401) {
router.push('/login')
}
return res.data
})
这个处理能避免每个页面都重复判断"token 是否过期",同时接口报错也能统一从 Message 组件弹出提示。
5.3 状态的页面表达
表格里不要让用户看到 0、1、2 这种数字,可以用后台管理的字典转换把它显示成"草稿/上架/下架",并在详情上用 Tag 标签的 type(info/success/danger)区分颜色。评分展示做成星星组件。这些细节会让整个项目看起来专业不少,代码量也很小。
6. 真实踩坑记录:从搭建到部署,最容易消耗两三个通宵的坑
最后把这几年带学生做 SpringBoot 项目时反复出现的坑做一次完整陈述。如果你能提前避掉,项目进度至少快一周。
6.1 JDK 与 SpringBoot 版本不匹配
第一个坑通常发生在新建工程时。比如用 IDEA 的原生 Spring Initializr,默认会生成 SpringBoot 2.7.15+ 或 3.x 的版本,如果你的电脑是 JDK 8,大概率启动时报:
code复制Error: Could not create the Java Virtual Machine
Error: A fatal exception has occurred. Program will exit.
这不是编码问题,是 Java 版本低于 SpringBoot 要求的 JDK 版本。解决办法:在 pom.xml 中右键刷新前检查环境变量 java -version,统一成 JDK 1.8(对应 SpringBoot 2.x)或 JDK 17(对应 SpringBoot 3.x)。千万不要出现"IDEA 里配的 JDK 是 17,命令行默认 JDK 是 8"这种混乱。
6.2 MySQL 8.x 驱动与时区
SpringBoot 2.7.18 默认数据库驱动版本能兼容 MySQL 8.0,但如果不带时区参数,会有这样一条报错:
code复制The server time zone value '�й���ʱ��' is unrecognized or represents more than one time zone.
所以 jdbc url 建议直接复制下面这个,别自己删参数:
yaml复制url: jdbc:mysql://localhost:3306/tutor_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true
注意 allowPublicKeyRetrieval=true 这一项,MySQL 8.0 默认 caching_sha2_password 认证方式下,某些客户端连接会需要它,否则第一次连接就会报 public key retrieval not allowed。
6.3 MyBatis-Plus 分页插件没写导致查列表返回全部记录
很多教程里用 MyBatis-Plus 的 Page<T> 会发现分页不生效,返回了全表数据。原因是新版 MP 需要主动注入分页插件,它不是自动注册的。
java复制@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
不要用旧版 mp 2.x 时代的 @Bean 配置方式去配 3.x,否则会出现类不存在。
6.4 前后端分离跨域踩坑
如果你把后端跑在 8080、前端在 5173,使用 Vue 脚手架或者 Vite 开发服务器时,页面访问接口直接请求 8080 会跨域。除了在后端加 @CrossOrigin 或全局 CORS 配置外,更推荐在 Vue/vite 的开发服务器配置 proxy:
javascript复制server: {
port: 5173,
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true
}
}
}
这样前端代码里所有请求路径写成 /api/xxx 而不是 http://localhost:8080/api/xxx,打包部署后也可以由 nginx 直接转发,不用改业务代码。
6.5 文件上传:图片路径不能随便写本地绝对路径
家教和老师资质图片上传,如果保存成 D:/upload/xxx.png 并在返回中拼完整磁盘路径,前端页面一旦用了 8080 以外的域名访问就完全无法加载。更稳的做法是:
- 后端用 MultipartFile 接收后保存到项目配置的上传目录(例如
/upload) - 把项目中的上传目录映射成静态资源 URL 前缀
/upload/**,通过自定义 ResourceHandler 指向真实路径
java复制@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/upload/**")
.addResourceLocations("file:" + uploadPath + "/");
}
这样前端保存 /upload/1.png 后自然通过站内 URL 访问,跟 IP/域名解耦。
6.6 首次运行前记得检查字段命名/下划线驼峰映射
MyBatis-Plus 默认 map-underscore-to-camel-case,所以数据库字段 create_time 能自动匹配 Java 属性 createTime。如果有的同学数据库字段用的命名不规范,比如 createtime 没有下划线没大写,Java 属性又是 createTime,就会查出 null。我的建议是实体类严格都按驼峰命名 + 数据库严格都按下划线命名,彻底回避这个问题。遇到一个改一个。
7. 再补充三个能提升毕设质量的扩展方向
核心流程做完,运行没问题,还剩下答辩前的加分空间。这三个方向不需要大的重构,但能让项目在答辩描述中显得更有思考深度。
第一,加上消息提醒功能。每次有新预约、预约确认或取消,系统自动给另一方生成站内消息,老师登录后看到一个红点。这个功能接口不难:给消息表插入一行,前端每次轮询或请求时检查未读数量即可。用它说明"我考虑了平台各方用户的协同沟通",比单纯说自己写了增删改查有说服力得多。
第二,给教师端增加"日程表"视图。基于 t_teacher_schedule、t_appointment 这两张表,用 FullCalendar 组件在老师端渲染出一个周视图日历。已被预约的时段用不同底色显示,空闲时段可直接点击发起占位。它没有增加新的 API 复杂度,只是把已有的时间字段用日历的方式呈现给用户。
第三,把密码从明文改成 BCrypt 加密。很多毕设直到交付还是明文密码存储,这不光是安全问题,一旦评委多问一句就很尴尬。Spring Security Crypto 可以只引一个小依赖:
xml复制<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-crypto</artifactId>
</dependency>
注册时 BCryptPasswordEncoder().encode(rawPassword),登录时 matches 校验。将原始密码字段在数据库里变成 $2a$10$...,看起来专业得多。
理论上到了这一步,首页信息流、基础搜索、筛选、家教主页详情、老师排期、家长发起预约、老师确认、订单状态流转、后台审核、评价发布,一条完整链路就通了。这也是"家教服务在线发布与预约管理系统"这个标题里所有关键词的组合落地。
我自己在实际做这些项目时最大的体会是:用好 SpringBoot 不是把 MVC 三层架子搭完就觉得结束,主动权在于业务规则能不能严谨落库、冲突校验能不能写对、状态流转会不会出岔子。建议你动手写代码前,先把本文提到的表结构和状态流用一张纸画一遍,遇到卡点直接拿这段业务去问搜索引擎,绝对比边写边改效率高。如果你准备拿它作为毕设,先把 2.7.18 的环境装好,数据库按 3.x 章节建好表,再到 4.x 章节一步步调接口,一周时间完整跑通是可期的。
