简介:预约管理系统在日常生活中无处不在,从健身房私教课到医疗挂号,其核心都离不开时间冲突检测与业务状态流转。如何保证多个用户同时抢约同一时段时数据不错乱?如何清晰表达预约从待确认到已完成的完整生命周期?这背后依赖的是状态机设计与乐观锁等工程化手段。本文基于SSM框架与微信小程序组合,从数据库表结构设计出发,解析时段管理、预约下单、取消释放等核心流程,并深入探讨事务控制、并发防超卖、登录鉴权等关键技术细节。该方案兼顾后端业务逻辑与前端交互体验,适用于校园毕设、企业级预约类应用的快速原型落地,也为开发者理解SpringMVC、MyBatis与小程序生态提供了完整实践参考。 做Java毕设这几年,被问到最多的就是“健身房私教预约系统”这类选题,其实它的本质就是一个带业务状态的预约管理系统,核心在“时间冲突检测”和“教练-学员-课程”的三角关系建模上。SSM + 微信小程序这套组合之所以常青,是因为它覆盖了后端ORM、前端交互、API对接三个维度,难度适中,答辩也有东西可讲。今天我把这套系统的完整设计思路、核心表结构、关键代码和踩坑记录一次说清楚。
1. 项目整体设计与技术选型思路
1.1 为什么是SSM + 微信小程序,而不是Spring Boot + Vue
先说结论:如果你是为了快速过毕设且要兼顾文档完整性,SSM其实比Spring Boot更“好写”。不是因为Spring Boot不好,而是SSM的面试和答辩点非常固定——Spring的IOC/AOP、SpringMVC的请求流转、MyBatis的SQL掌控力,这三个点你可以在文档里写得非常细,导师问起来你也有话说。
微信小程序端选原生而不是Uniapp,理由是:原生小程序对组件的调用、生命周期、分包加载的掌握度更直观,而且毕设文档里可以少写一层“框架封装”的复杂度。你只需要管好四个文件:.wxml(结构)、.wxss(样式)、.js(逻辑)、.json(配置),比Uniapp的Vue语法 + 编译踩坑要省心得多。
这套系统的业务边界很清晰:
- 用户端(微信小程序):注册登录、浏览教练、查看课程、发起预约、取消预约、查看我的课表。
- 管理端(Web页面用JSP或Layui):教练管理、课程管理、预约审核、会员管理、数据概览。
我见过很多人把管理端做成第二个小程序,这是没必要的。管理端用浏览器访问更符合健身房前台的操作习惯,这也是SSM + JSP的老本行。
1.2 预约系统的核心难点不在CRUD,而在“状态流”
很多同学拿到这个题目就开始写增删改查,写到预约表的时候发现不对劲:预约不是insert一下就完事的。
一个合格的预约单至少要有这些状态:
- 待确认:用户提交预约,等待教练/管理员确认。
- 已确认:教练同意带课,课程锁定。
- 已完成:课程时间已过,用户签到或自动完成。
- 已取消:用户或教练取消,课时释放。
- 已爽约:预约确认后未到课,记录违约次数。
这个状态流就是你的核心业务逻辑,BATJ面试题库里的“订单状态机”其实就是这么回事。你在文档里画一张状态流转图,再配合代码里的状态判断,导师会认为你有工程意识。
2. 数据库设计与核心表结构拆解
2.1 六张核心表,一张都不能少
我按实际开发顺序推荐大家这样建表:
第一张:用户表(user),字段必须有openid、nickname、avatar、phone、role(区分会员和教练)、status。openid是微信小程序用户的唯一标识,千万不能漏。
第二张:教练表(coach),如果教练也是平台用户,可以直接复用user表的role字段,另外建一张coach_info表存教龄、专长、评分、简介。我建议分两张表,因为教练的审核状态和管理员的审核状态单独管理会简单很多。
第三张:课程表(course),字段包含course_name、course_type(私教课/团课/康复课)、duration(课时长)、price、coach_id(关联教练)、max_students(约满人数)。
第四张:时段表(time_slot),这个表非常容易被忽略。预约系统的核心不是“课程”,而是“某天某个教练的某个时段”。时段表的字段:coach_id、slot_date、start_time、end_time、is_booked、version(乐观锁用)。
第五张:预约表(appointment),也就是订单表。字段包含appointment_no(订单号)、user_id、coach_id、course_id、slot_id(关联到具体时段)、status、create_time、cancel_reason。
第六张:评价表(review),字段包含appointment_id、rating、content、create_time。评价表会让你的系统在功能展示上多一个亮点,而且实现起来并不复杂。
2.2 为什么时段表要单独拆出来,而不是直接存预约时间
这是个经验问题。如果你把“预约时间”直接存在appointment表里,那当用户取消预约或者教练调整课程时,你需要把所有相关记录查出来再逐一修改,非常痛苦。
拆出时段表之后,你的业务逻辑变成这样:
- 教练上课 = 锁定一个时段(is_booked = 1)。
- 用户预约 = 在某个未被锁定的时段上创建预约单。
- 取消预约 = 释放时段(is_booked = 0),同时把预约单状态改为已取消。
这种设计还有一个额外的好处:你可以非常容易地查出“某个教练本周的可用时段”,小程序端直接select * from time_slot where coach_id = ? and is_booked = 0,前端渲染成时间选择器,用户体验很流畅。
我在实际项目中用到的核心建表SQL片段(简化版)如下:
CREATE TABLE `time_slot` ( `id` int(11) NOT NULL AUTO_INCREMENT, `coach_id` int(11) NOT NULL COMMENT '教练ID', `slot_date` date NOT NULL COMMENT '日期', `start_time` varchar(10) NOT NULL COMMENT '开始时间 HH:mm', `end_time` varchar(10) NOT NULL COMMENT '结束时间 HH:mm', `is_booked` tinyint(1) DEFAULT 0 COMMENT '0可约 1已约', `version` int(11) DEFAULT 0 COMMENT '乐观锁版本号', PRIMARY KEY (`id`), KEY `idx_date_coach` (`slot_date`, `coach_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;注意这里加了version字段,就是为了防止多个用户同时抢同一个时段造成超卖。后面代码部分我会专门讲乐观锁怎么写。
3. 后端核心功能实现:从Controller到Mapper的完整链路
3.1 SpringMVC三层结构怎么安排
SSM项目的包结构建议这样分:
- controller:接收前端请求,只做参数接收和结果封装。
- service:业务逻辑,包括事务控制、状态判断、时间冲突检测。
- dao/mapper:MyBatis对外接口,只写SQL。
- pojo/entity:实体类。
- common:统一返回结果、异常处理、工具类。
Controller层的代码风格保持一致很重要,我习惯统一返回一个Result对象:
public class Result<T> { private Integer code; // 200成功 500失败 private String message; private T data; }这样小程序端wx.request拿到响应后,直接判断code是否为200,再决定是否展示数据,避免前端处理各种奇奇怪怪的返回结构。
Controller示例:
@RestController @RequestMapping("/api/appointment") public class AppointmentController { @Resource private AppointmentService appointmentService; @PostMapping("/create") public Result create(@RequestBody AppointmentCreateDTO dto) { return appointmentService.createAppointment(dto); } @PostMapping("/cancel") public Result cancel(@RequestParam Long appointmentId, @RequestParam String openid) { return appointmentService.cancelAppointment(appointmentId, openid); } @GetMapping("/myList") public Result myList(@RequestParam String openid, @RequestParam(defaultValue = "1") Integer pageNum, @RequestParam(defaultValue = "10") Integer pageSize) { return appointmentService.getUserAppointments(openid, pageNum, pageSize); } }3.2 预约下单的核心Service逻辑:事务 + 乐观锁
这是整个系统最值得拿出来讲的部分。预约动作发生在两个表上:time_slot要改成已约状态,appointment要插入一条记录。这两步必须在一个事务里,否则会出现“时段锁了但没有订单”或者“订单创建成功但时段还是可约”的脏数据。
我给出的实现方案:
- 第一步,查询时段,校验状态。
- 第二步,用乐观锁更新时段(
update ... where id = ? and is_booked = 0)。 - 第三步,插入预约单。
- 第四步,返回预约结果。
注意,第二步必须判断更新影响的行数,如果影响行数为0,说明这个时段刚刚被别人抢了,直接抛出业务异常。
@Override @Transactional(rollbackFor = Exception.class) public Result createAppointment(AppointmentCreateDTO dto) { // 1. 校验时段存在且未被预约 TimeSlot slot = timeSlotMapper.selectById(dto.getSlotId()); if (slot == null) { return Result.error("时段不存在"); } if (slot.getIsBooked() == 1) { return Result.error("该时段已被预约,请选择其他时段"); } // 2. 乐观锁尝试锁定时段 int rows = timeSlotMapper.lockSlot(dto.getSlotId(), slot.getVersion()); if (rows == 0) { return Result.error("手慢了,该时段刚刚被约走了"); } // 3. 生成预约单 Appointment appointment = new Appointment(); appointment.setAppointmentNo(generateNo()); // 时间戳+随机数 appointment.setUserId(dto.getUserId()); appointment.setCoachId(slot.getCoachId()); appointment.setCourseId(dto.getCourseId()); appointment.setSlotId(slot.getId()); appointment.setStatus(0); // 待确认 appointment.setCreateTime(new Date()); appointmentMapper.insert(appointment); // 4. 新增待办消息(可选,通知教练) // messageService.sendToCoach(slot.getCoachId(), "您有新的预约申请"); return Result.success(appointment.getId()); }对应的MyBatis XML:
<update id="lockSlot"> UPDATE time_slot SET is_booked = 1, version = version + 1 WHERE id = #{id} AND is_booked = 0 AND version = #{version} </update>这套写法的核心就是“先更新后插入”,在并发量不高的毕设场景下是绝对够用的。如果在文档里想体现更进一步的水平,可以在第三步插入预约单之前再查一次教练当天是否存在时间重叠的预约,做一个业务层的二次校验。这部分我放在常见问题里展开。
3.3 取消预约的状态机和课时释放
取消预约相对容易,但状态判断不能省。用户只能取消“待确认”和“已确认”的预约,已完成和已取消的单子不能取消。
我建议的流程:
- 根据appointmentId查出订单,校验openid归属。
- 判断status是否为0或1,不是则返回“当前状态不可取消”。
- 修改订单状态为2(已取消)。
- 把对应的time_slot释放,is_booked改回0。
这里有个细节:释放时段的时候,不一定需要乐观锁,因为此时时段大概率是锁定状态,而且只有预约人自己才能取消自己的单子。不过为了安全起见,释放SQL的WHERE条件里加上is_booked = 1更稳妥。
@Override @Transactional(rollbackFor = Exception.class) public Result cancelAppointment(Long appointmentId, String openid) { Appointment appointment = appointmentMapper.selectById(appointmentId); if (appointment == null) { return Result.error("预约单不存在"); } // 校验归属(假设user表有openid关联) if (!appointment.getOpenid().equals(openid)) { return Result.error("无权操作该预约"); } if (appointment.getStatus() != 0 && appointment.getStatus() != 1) { return Result.error("当前状态不可取消"); } appointment.setStatus(2); appointment.setCancelReason("用户主动取消"); appointmentMapper.updateById(appointment); timeSlotMapper.releaseSlot(appointment.getSlotId()); return Result.success(); }写到这里可能有人问:为什么不在取消预约时直接删除time_slot记录?因为保留时段记录是必要的,这样可以统计教练的排班历史、被取消的次数,甚至可以分析哪些时段最容易被取消,帮助健身房优化排课。删除是不可逆操作,不做。
4. 微信小程序端实现:从登录到预约的完整交互
4.1 小程序登录态与后端会话校验
小程序端最容易被忽视的是登录态的建立。很多同学写小程序只关心页面跳转,忽略了后端如何识别“当前这个请求来自哪个用户”。
微信小程序的标准登录流程:
- 小程序端调用
wx.login()获取临时code。 - 把code传给后端。
- 后端拿code + appid + secret去微信接口换取openid。
- 后端用openid在user表查找用户,存在则返回登录成功,不存在则自动注册。
- 后端返回自定义登录态(我习惯用UUID或JWT),小程序存入storage。
- 后续请求带
Authorization: token,后端用拦截器解析。
这个流程不要省。如果你只是把openid存在小程序端然后每次请求都传openid,那别人抓包就能伪造成任意用户,答辩时这段会被导师质疑。
建议SSM里使用一个简单的HandlerInterceptor做登录校验:
public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token = request.getHeader("Authorization"); if (StringUtils.isBlank(token)) { response.setStatus(401); return false; } // 解析token,获取userId并存入ThreadLocal Long userId = JwtUtil.parseToken(token); if (userId == null) { response.setStatus(401); return false; } UserContext.setUserId(userId); return true; } }4.2 首页-教练列表-预约下单的页面逻辑
小程序端我建议三个核心页面:
- index:首页,展示推荐课程、热门教练。
- coachList:教练列表,支持按课程类型筛选。
- booking:预约页,选择教练、日期、时段,确认下单。
预约页的难点在于“时段组件的状态展示”。我的做法是后端提供一个接口:GET /api/coach/slots?coachId=1&date=2025-01-10,返回当日所有时段及状态,前端遍历渲染成时间块。
小程序端核心代码:
Page({ data: { coachId: null, date: '', slots: [], // [{id: 1, startTime: '10:00', endTime: '11:00', isBooked: 0}] selectedSlotId: null }, onLoad(options) { this.setData({ coachId: options.coachId }) this.loadSlots() }, loadSlots() { wx.request({ url: 'https://yourdomain.com/api/coach/slots', method: 'GET', data: { coachId: this.data.coachId, date: this.data.date }, header: { 'Authorization': wx.getStorageSync('token') }, success: (res) => { if (res.data.code === 200) { this.setData({ slots: res.data.data }) } } }) }, selectSlot(e) { // 已经约满不可选 const slot = e.currentTarget.dataset.slot if (slot.isBooked === 1) { wx.showToast({ title: '该时段已被约', icon: 'none' }) return } this.setData({ selectedSlotId: slot.id }) }, submitBooking() { if (!this.data.selectedSlotId) { wx.showToast({ title: '请选择时段', icon: 'none' }) return } wx.request({ url: 'https://yourdomain.com/api/appointment/create', method: 'POST', data: { slotId: this.data.selectedSlotId, courseId: this.data.courseId, userId: this.data.userId }, header: { 'Authorization': wx.getStorageSync('token') }, success: (res) => { if (res.data.code === 200) { wx.showToast({ title: '预约成功' }) wx.navigateTo({ url: '/pages/myOrder/myOrder' }) } else { wx.showModal({ title: '预约失败', content: res.data.message, showCancel: false }) // 刷新时段,把被别人抢走的时段标记出来 this.loadSlots() } } }) } })这段代码里有一个容易被忽略的细节:预约失败后一定要重新加载时段列表。否则用户看到的数据是旧的,可能会以为系统出bug了。这种交互上的小细节,建议写进文档的“界面优化”章节,是加分项。
4.3 管理端Web页面的模块划分
管理端用SSM + JSP或者SSM + Layui都可以。我建议用Layui,因为它的表格组件和表单组件能节省大量前端时间,而且Layui的文档在中文圈里非常友好。
管理端最少要含五个模块:
- 仪表盘:统计今日预约数、总会员数、课程总数。
- 教练管理:新增/编辑/删除教练,配置教练可预约时段。
- 课程管理:维护课程类型、价格、时长。
- 预约管理:查看所有预约,支持按状态筛选、确认/取消预约。
- 会员管理:查看用户列表,禁用/启用账号。
其中“教练可预约时段”的配置功能是你系统的亮点,因为很多基础毕设只是预置一批死数据,而你这个功能允许管理员动态为教练添加可约时段,会增加系统的完整度和灵活性。
5. 常见问题与排查技巧实录
5.1 “明明取消了预约,为什么时段还是不可选”
这个问题的原因通常是两种:
- 第一种:取消预约接口只更新了appointment表,没有释放time_slot。
- 第二种:释放时段的SQL条件写错,比如
where id = ?写成了where slot_id = ?。
排查方法很简单,打开数据库直接查两条记录:
- 看appointment表的status是否已变为2。
- 看time_slot表的is_booked是否已变为0。
如果appointment变了而time_slot没变,问题一定出在Service层的释放逻辑。我建议在Service里把释放时段的操作放在finally块或者事务内靠后的位置,保证它一定会执行。
5.2 “两个用户同时抢同一个时段,会不会超卖”
如果按照我上面写的乐观锁方案,是不会超卖的。但有一个隐藏坑:有些同学在事务里先查询再更新,按理说也是可以防住的,但前提是必须使用SELECT ... FOR UPDATE行锁,否则两个事务同时读到is_booked=0,就会重复预约。
如果你用的是MyBatis,实现行锁的姿势是:
<select id="selectSlotForUpdate" resultType="TimeSlot"> SELECT * FROM time_slot WHERE id = #{id} FOR UPDATE </select>然后在Service里先调用这个方法,再走更新逻辑。这样事务A查到记录并锁住,事务B必须等A提交或回滚之后才能查询。不过要注意,FOR UPDATE必须包裹在事务里才有效,否则锁会自动释放。
两种方案(乐观锁和悲观锁)任选其一即可,我的建议是毕设用乐观锁,代码简洁、好讲解,而且面试官问起来你能说出两种方案的优劣对比,已经超过很多人了。
5.3 “小程序端请求后端接口一直提示域名不合法”
这是微信开发者工具里最经典的问题。小程序生产环境要求所有请求域名必须为HTTPS,而且要在微信公众平台配置合法域名。
本地调试阶段可以这样处理:在微信开发者工具右上角“详情” -> “本地设置”里勾选“不校验合法域名、web-view(业务域名)、TLS版本以及HTTPS证书”。这样你的后端用HTTP + localhost或局域网IP也能调试。
但要注意,这只是本地调试,上线前一定要换成HTTPS的正式域名,否则真机预览会直接失败。
另外,如果你的后端部署在云服务器上,建议用Nginx做反向代理,把80端口转发到8080,同时配置SSL证书。这个内容你在教程里简单提一下,能帮用户规避最少半天的踩坑时间。
5.4 “微信小程序端能直接访问MySQL数据库吗”
绝对不能。小程序端运行在用户的微信客户端里,它不可能直连你的数据库。所有数据访问必须通过后端API完成。
有同学会想:“那我把数据库连接信息写在小程序代码里行不行?”答案是不行,而且这会带来严重的安全问题——任何人反编译你的小程序包都能看到数据库地址、账号、密码。正统做法就是后端SSM提供RESTful API,小程序只发HTTP请求。
这套架构说白了就是一次“前后端分离”的小型实践。前端只负责展示和交互,后端只负责业务逻辑和数据存储,中间通过JSON格式进行通信。这个设计思想在文档里一定单独写一节,叫“系统架构设计”,放一张架构图(用Visio或ProcessOn画),包括用户、小程序、服务器、数据库四层。
5.5 分页查询和模糊搜索的加分实现
很多毕设只做了列表展示,没做分页和搜索。导师一眼看过去就觉得功能不全。
预约管理列表至少要有三个筛选条件:按状态筛选、按教练名搜索、按日期范围搜索。
Controller参数这样设计:
@GetMapping("/admin/list") public Result page(@RequestParam(defaultValue = "1") Integer pageNum, @RequestParam(defaultValue = "10") Integer pageSize, @RequestParam(required = false) Integer status, @RequestParam(required = false) String coachName, @RequestParam(required = false) String beginDate, @RequestParam(required = false) String endDate) { return appointmentService.pageQuery(pageNum, pageSize, status, coachName, beginDate, endDate); }Service里用MyBatis的<where>标签动态拼接条件,既安全又简洁:
<select id="pageQuery" resultType="AppointmentVO"> SELECT a.*, c.name AS coachName, u.nickname AS userName FROM appointment a LEFT JOIN coach c ON a.coach_id = c.id LEFT JOIN user u ON a.user_id = u.id <where> <if test="status != null"> AND a.status = #{status} </if> <if test="coachName != null and coachName != ''"> AND c.name LIKE CONCAT('%', #{coachName}, '%') </if> <if test="beginDate != null and beginDate != ''"> AND a.create_time >= #{beginDate} </if> <if test="endDate != null and endDate != ''"> AND a.create_time <= #{endDate} </if> </where> ORDER BY a.create_time DESC LIMIT #{offset}, #{pageSize} </select>注意这里用LEFT JOIN关联查询教练名和用户名,这样前端表格可以直接展示文字而不是外键ID,人工审核预约单的时候体验会好很多。
6. 文档编写与答辩准备的实战建议
6.1 毕业设计文档的章节布局
含文档是这个项目的标配,很多人以为文档是最后写,我的建议是文档边写代码边穿插写。用四天的时间安排来说:
- 第一天:搭数据库 + 建工程 + 配置SSM,同步编写第三章“需求分析”和第四章“数据库设计”。
- 第二天:完成用户登录、教练列表和预约下单功能,同步写第五章“系统实现”的前半部分。
- 第三天:完成管理端和取消预约功能,补充“系统测试”章节。
- 第四天:整理“摘要”、“绪论”、“总结与展望”,美化UML图和截图。
这样做的好处是:代码跑通了文档细节顺手就写,不会出现代码和文档不一致的尴尬。
重点的UML图要求:
- 用例图:管理员、教练、用户三个角色各一张,或合并成一张大图。
- 类图:核心实体类和Service接口的UML类图。
- 时序图:用户预约下单的时序图,从用户点击到数据库提交。
- E-R图:六张表的实体关系图。
这些图用PowerDesigner、Visio或者StarUML都能画,不建议在Word里手绘,太痛苦了,而且画出来改起来也麻烦。
6.2 答辩可能会被追问的五个问题
导师们虽然不指望你做出工业级产品,但基本逻辑必须自洽。我整理了这五个高频问题:
第一个问题:“为什么用SSM而不用Spring Boot?” 正确思路:不是不会用Spring Boot,而是SSM的配置过程更透明,可以更清晰地展示Spring核心思想,比如IOC容器如何管理Bean、SpringMVC的DispatcherServlet如何分发请求、MyBatis如何通过动态代理生成Mapper实现类。
第二个问题:“如何防止同一个时段被多人同时预约?” 答乐观锁方案:更新时带上version,影响行数为0就提示失败。顺便补充悲观锁的对比,说明乐观锁更适合读多写少的场景。
第三个问题:“预约完成后如何通知用户或教练?” 如果系统里做了消息表或者利用小程序订阅消息,就如实说;如果没做,可以说“目前设计中预留了消息通知接口,后续可通过微信订阅消息实现”。诚实且给出改进方向,比吹牛强。
第四个问题:“如果教练临时请假,预约单如何处理?” 可以答:管理员在管理端取消该时段及相关预约单,系统自动释放时段,同时用户端可看到预约状态变为取消,并提示重新预约。这个逻辑要提前在代码里实现好,答复案更扎实。
第五个问题:“系统中哪些功能用了事务?” 至少要说:预约下单(更新时段+插入预约单)、取消预约(更新订单+释放时段)。顺带解释为什么这两个场景必须加@Transactional。
6.3 视频教程里可以额外展开的内容
含教程是这个项目的加分项。我建议教程视频不要超过5个,每个控制在15-20分钟,分别讲:
- 视频1:环境搭建(JDK、Maven、Tomcat、MySQL、微信开发者工具)。
- 视频2:数据库导入和SSM框架启动。
- 视频3:后端接口调试(Postman调用登录、教练列表、预约接口)。
- 视频4:小程序端功能演示与接口联调。
- 视频5:常见问题修复(502、404、跨域、端口占用)。
每个视频开头先把最终效果演示一遍,再进入操作。观众能看到希望才有动力跟着做,这也是我做教程一年多总结出来的经验。
7. 部署上线与后续扩展方向
7.1 从本地到服务器的部署清单
如果你有云服务器,建议部署一遍,哪怕只是为了答辩时秀一下“真机线上访问”。部署步骤没有特别复杂:
- 安装JDK 8 + Tomcat 8.5 + MySQL 5.7(或云数据库RDS)。
- 打包项目:用IDEA的Maven插件执行
clean package,打war包丢到Tomcat的webapps目录。 - 导入数据库脚本,修改jdbc.properties中的数据库连接信息。
- 配置Nginx反向代理和SSL证书,把443端口代理到8080。
- 小程序后台配置合法域名,request合法域名填你的HTTPS域名。
Tomcat下运行SSM项目要注意一个点:如果你的JSP放在webapp目录,可能因为Servlet版本不兼容导致404。建议打包前先确认pom.xml里的javax.servlet-api版本是3.1.0(对应Tomcat 8.5)。
7.2 如果想继续拔高,哪些功能值得加
如果你的进度比较快,或者想拿个高分,可以往这几个方向扩展:
- 教练端小程序:让教练用小程序的“工作台”接单和取消,而非登录Web管理端。本质上复用已有的API,只需要新增一个带“教练角色”判断的小程序端页面。
- 微信订阅消息通知:预约成功和取消时推送订阅消息给用户,需要在微信公众平台申请模板,前端用
wx.requestSubscribeMessage授权。 - 基于Redis的时段缓存:将未来7天的时段列表缓存到Redis,减少数据库压力。毕设能提到Redis已经是亮点,不要求你真正做得很完善。
- 数据可视化面板:管理端用ECharts展示每周预约趋势、热门教练排名,视觉效果非常炸裂。
这些扩展不用全部实现,选一两个做即可,关键是文档里把它们写成“系统特色”,再配合截图,导师的观感会完全不同。
7.3 我的最终建议
做毕设最忌讳的是贪多求全。一个私教预约系统,能把这六张表设计清楚、预约和取消这两个核心流程完整跑通、管理端能正常维护数据、文档能画出完整的UML图,这已经能评优秀了。剩下的时间宁可用来多刷几套微信小程序和SSM的面试题,也不要盲目堆砌功能。
如果你在做的过程中还有具体问题,记住先查日志。SSM项目定位问题最有效的方式就是看Tomcat的catalina.out日志,找到报错堆栈的第一行,Google也好,百度也好,把报错信息原样复制进去,绝大多数问题都能找到答案。自己动手排错的那几分钟,比看十遍教程都管用。
本文还有配套的精品资源,点击获取