讲真,每年计算机毕业设计里,"XX系统+微信小程序"这个组合都快被选烂了,但"被选烂"这件事本身是有原因的:这类题需求清楚、用户端和管理员端能形成完整闭环、技术点足够答辩撑场面,只要你愿意把细节做扎实,拿个不错的分数并不难。今天我想借着"基于微信小程序的缪氏诊所预约挂号系统"这个具体题目,把这套东西从需求分析、技术选型、数据库设计到核心代码链路、小程序端各种报错、最后论文怎么配合源码,完整地捋一遍。适合正在做类似选题、或者刚拿到源码和文档但不知道怎么讲清楚的人参考。
先给个总览:这个题目本质上做的不是"医院挂号系统",而是一个小体量诊所的预约业务闭环。用户在小程序里完成注册登录,浏览科室和医生,看排班选时段,提交预约;管理员在后台维护科室、医生、排班,查看预约记录和统计。听起来简单,但里面几乎每一个环节都有可以深挖的坑——尤其是医生排班的数据结构、同时段预约的并发控制、微信登录和订阅消息的调用姿势。下面我把整个过程按开发顺序拆开讲。
1. 为什么"小诊所预约"是毕设里被低估的稳妥题
1.1 需求看起来简单,做起来刚好卡在"不简单也不复杂"的红线
很多人选题目有一个误区:越复杂越好,最好造一个"智慧医疗云平台"。真上手就会发现,大医院系统里光一个号源池就涉及多院区共享、专家号二次分配、爽约惩罚、医保对接,任何一个模块都能把人拖垮。毕业设计最理想的状态是:业务闭环完整,但每个环节的规模都在自己能控制的范围内。
以缪氏诊所这类小型医疗机构为原型,需求非常清楚:一个诊所里有几个科室、几个医生、每天放号,用户在微信里看到排班,选一个时段预约。要素就五个:用户、科室、医生、排班、预约。而这五个要素正好是关系型数据库里最好表达的关系,也是后端增删改查最标准的练兵场。
1.2 这道题覆盖了答辩老师想看的全部技术点
我帮人梳理过不少毕设代码,一个项目能不能在答辩时撑住场子,看的不是功能列表有多长,而是有没有几个可以深挖的技术点。这个题目天然具备这些:
- 微信登录链路:wx.login 拿 code,后端调 code2Session 换 openid,再签发自定义 token,这是小程序项目都会问的。
- 数据库设计:科室/医生/排班/预约之间的关联,排班时段怎么建模,索引怎么建。
- 并发场景:同一个医生同一个时段只剩最后一个号,两个人同时点预约,怎么保证不超卖。这是整套系统里最值得讲的地方。
- 微信生态能力:订阅消息通知、用户信息授权、真机调试配置,每一个都有真实的坑。
这些点不是硬凑的,而是业务自然带出来的。答辩老师问"为什么这么设计"时,你能接得住。
1.3 先想清楚:哪些功能必须做,哪些可以明确不做
拿到题目别急着写代码,先把功能边界划好。我建议按下面这个清单来:
必须做(核心闭环)
- 微信登录/用户信息维护
- 科室列表与医生列表展示
- 按日期查看医生排班,选择时段
- 提交预约、查看我的预约、取消预约
- 管理端:科室管理、医生管理、排班管理、预约列表
- 简单的数据统计(各科室预约量等)
可以不做(避免被拖死)
- 在线支付:微信支付接口要企业资质+商户号,个人主体基本申请不了。做成"预约成功,到院支付"完全合理。
- 多院区、复杂的号源策略、专家线上问诊:这些不是诊所的真实需求。
- 短信通知:用微信订阅消息替代即可。
把范围先划清楚,后面每一步都会轻松很多。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术选型:照着这套组合不会翻车
2.1 小程序端用原生框架还是 uni-app
这是大家最先纠结的问题。我给的建议很直接:如果对 Vue 不熟,老老实实写原生小程序;如果已经用过 Vue,用 uni-app 开发会更快。
原生小程序的目录结构很简单,上手成本其实比想象中低,核心就几个文件:
code复制├── app.json // 全局配置:页面路径、窗口样式、tabBar
├── app.js // 全局逻辑:启动时调用登录接口、获取更新管理器
├── app.wxss // 全局样式
├── utils/
│ ├── request.js // 封装 wx.request 为 Promise
│ └── auth.js // token 存储与读取
├── pages/
│ ├── index/ // 首页:科室/医生入口
│ ├── doctor/ // 医生列表
│ ├── schedule/ // 排班与时段选择
│ ├── appointment/ // 提交预约
│ ├── my/ // 我的预约
│ └── login/ // 登录页
用 uni-app 的话,目录会变成 Vue 单文件组件的形态,pages 里放 .vue 文件,HBuilderX 里可以直接运行到微信开发者工具。两种方案都能做完,选一种自己熟悉的就好,别在选型上耗太久。
2.2 服务端选型与理由
服务端我推荐 Spring Boot 2.7 + MyBatis-Plus + MySQL 8.0。这套组合在毕设里几乎是最稳的:
- Spring Boot:不用自己搭 SSM 那套繁琐的 XML 配置,起步快。
- MyBatis-Plus:单表 CRUD 不用写 mapper XML,代码量至少省一半,还自带分页插件。
- MySQL 8.0:用 InnoDB 引擎,事务和行锁都支持,后面做预约锁号靠的就是这个。
如果你 Java 不太熟,也可以选择 Node.js(Express/Koa)或者 Python(FastAPI/Django),逻辑是一样的。但注意一点:不管用什么语言,一定要清楚自己写的每条 SQL 在干什么,答辩时老师大概率会问。
一个标准的 Spring Boot 工程目录可以这样组织:
code复制src/main/java/com/example/clinic/
├── controller/ // 接口层:UserController、DoctorController、ScheduleController、AppointmentController
├── service/ // 业务层:AppointmentService、ScheduleService
├── mapper/ // 数据访问层,继承 MyBatis-Plus 的 BaseMapper
├── entity/ // 数据库实体类
├── dto/ // 接口入参出参对象
├── common/ // 统一返回结果、异常处理、常量
├── config/ // 配置类:拦截器、跨域、微信参数
└── util/ // JwtUtil、DateUtil 等
controller → service → mapper 这个分层虽然在老手看来有点死板,但对毕设来说它清晰、稳定,写论文时也好画架构图。
2.3 两种被忽略的快速方案:微信云开发和内网穿透
除了前后端分离,还有一条路是 微信云开发:不用自己买服务器和配 HTTPS 域名,直接用云函数 + 云数据库,数据库操作、文件存储全在微信生态里。它的优势是省去了服务器部署和域名备案这两个最折磨人的环节,适合时间紧张的同学。
但云开发有个硬伤:代码和传统后端差别较大,查日志、调试没那么直观,而且答辩时如果老师问"这个接口的并发是怎么保证的",你得能讲清楚云数据库的事务机制。我的建议是:如果只是想要一个能展示的成品,云开发可以;如果想通过这个项目把 Spring Boot 这套后端基本功练扎实,还是走前后端分离。
顺带一提,开发小程序时经常需要调试本地后端接口。开发工具里可以勾选"不校验合法域名",但真机预览时访问的是局域网 IP,手机和电脑要在同一 WiFi 下,而且后端要监听 0.0.0.0 而不是 127.0.0.1。这个细节害了不少人,后面避坑部分我再展开。
2.4 接口设计的前置约定:统一返回结果和 token
动手写接口之前,先定几个规范,后面能省很多事。
统一返回结果,我习惯用这样的结构:
json复制{
"code": 200,
"message": "success",
"data": {}
}
前端封装 request.js 时,统一判断 code 和 HTTP 状态码,token 统一放在请求头 Authorization 字段里。登录成功后把 token 存进 wx.setStorageSync,每次请求前带上。这样无论是查询排班、提交预约还是取消预约,前端只需要关心业务数据,不用每个接口单独处理异常状态。
3. 数据库设计:预约系统的高分点全在表设计里
3.1 六张核心表的设计说明
这套系统核心用六张表就够了,其他功能表可以在此基础上扩展。我直接把建表要点列出来,照着设计就行:
| 表名 | 核心字段 | 说明 |
|---|---|---|
| department | id, name, intro, sort | 科室表,sort 控制展示顺序 |
| doctor | id, department_id, name, title, avatar, intro, status | 医生表,title 存职称如"主治医师" |
| user | id, openid, nickname, avatar, phone, create_time | 小程序用户表,openid 必须唯一索引 |
| schedule | id, doctor_id, work_date, time_start, time_end, total, used, status | 排班表,一条记录代表一个时段 |
| appointment | id, appointment_no, user_id, schedule_id, doctor_id, patient_name, patient_phone, status, create_time, cancel_time | 预约表,核心业务表 |
| admin | id, username, password, create_time | 后台管理员表 |
有几点必须注意:
- openid 要 varchar(64) 加唯一索引,一个微信号对应一条记录,别用 int 主键去唯一标识微信用户。
- appointment 表加 status 字段,用数字表示状态:0 已预约、1 已完成、2 已取消、3 已过期。状态机设计得越清楚,后面业务逻辑越不容易乱。
- 表之间不要建物理外键,用逻辑外键加索引就行。MyBatis-Plus 操作时更灵活,删数据也不会被外键绑架。
- 常用查询字段加上索引:schedule 表的 (doctor_id, work_date),appointment 表的 (user_id, status)。
3.2 排班表为什么是"时段+号源数",而不是"每个小时一条记录"
我见过不少设计,把号源拆成一条条独立的记录,比如"医生A 周一 08:30 一个号"占一条数据,"08:30 另一条"再占一条。这样设计在写入时看似灵活,但查询时段列表会非常别扭,还要额外维护"这个时段剩几个号"。
更合理的做法是:一条 schedule 记录代表"某医生某天某个时段",total 表示放号总数,used 表示已预约数,余号就是 total - used。 举个例子:
code复制id=1, doctor_id=3, work_date=2025-06-16, time_start=09:00, time_end=10:00, total=5, used=3
小程序端展示时,这个时段的余号就是 2。用户预约时,对这条记录做原子更新(后面讲锁号)。这样设计的好处是:表结构简单,排班展示一目了然,并发控制只需要维护一行数据。
3.3 排班生成的两种模式:手动添加与星期模板批量生成
排班数据从哪里来?两种方式可以都做,工作量不大但设计感很强:
- 手动添加:管理员在后台选择医生、日期、起止时间和号源数,插入一条 schedule。适合临时加号。
- 星期模板生成:给医生配置一个每周排班模板,比如"周一上午 5 个号,周三下午 8 个号",管理员点击"生成本周排班"时,系统根据模板批量生成一周的 schedule 记录。
第二种方式特别值得做,因为它的代码量不大,但在论文里可以单独画一个"排班生成流程图",回答"你的系统怎么减少管理员重复操作"时,这是一个非常加分的答案。
批量生成的逻辑其实很简单:查出所有医生的模板,遍历未来的日期范围,按星期匹配模板里配置的时段,逐条判断是否已经存在 schedule(防止重复生成),不存在才插入。
3.4 预约号与状态机怎么设计
用户提交预约后,需要生成一个预约号,方便线下核销。我一般生成 "YY + 年月日 + 随机数" 的格式,比如 YY202506160001,注意加唯一索引,防止并发下重复。
状态机的设计值得说一下。预约的核心状态流转是:
- 用户提交预约 → 状态变成"已预约"
- 管理员到院核销/用户就诊 → "已完成"
- 用户在就诊前取消 → "已取消"
- 就诊日期过了且没有核销 → "已过期"
后端写一个定时任务,每天凌晨把昨天还没核销的预约标记为"已过期"。定时任务可以用 Spring 的 @Scheduled 注解实现,虽然简单,但"定时任务闭环数据"这个点是很多同学会忽略的,写进论文里是个不错的细节。
4. 核心链路实现:登录、锁号、取消、通知
4.1 微信登录链路:code2Session 与自定义 token
小程序获取用户的唯一标识,靠的不是 wx.getUserInfo,而是 wx.login。它拿到的 code 是一次性的,5 分钟有效,而且只能用一次。完整流程是:
- 小程序端调用
wx.login()拿到 code。 - 把 code 传给后端
/api/auth/login。 - 后端拿 code + appid + secret 调微信的
code2Session接口,换取 openid。 - 根据 openid 查 user 表,不存在就自动注册一条新用户。
- 生成自定义 token(我用 JWT),返回给前端。
小程序端代码长这样:
javascript复制// pages/login/login.js
wx.login({
success: async (loginRes) => {
const resp = await request({
url: '/api/auth/login',
method: 'POST',
data: { code: loginRes.code }
});
wx.setStorageSync('token', resp.data.token);
wx.switchTab({ url: '/pages/index/index' });
},
fail: (err) => {
console.error('登录失败', err);
}
});
后端对应的逻辑用 Java 写大概是:
java复制@PostMapping("/login")
public Result login(@RequestBody LoginDTO dto) {
// 调微信接口换 openid
WxLoginResp wxResp = wxService.code2Session(dto.getCode());
if (wxResp.getOpenid() == null) {
throw new BusinessException("微信登录失败:" + wxResp.getErrmsg());
}
// 查用户,不存在则注册
User user = userMapper.selectOne(
new LambdaQueryWrapper<User>().eq(User::getOpenid, wxResp.getOpenid())
);
if (user == null) {
user = new User();
user.setOpenid(wxResp.getOpenid());
userMapper.insert(user);
}
// 签发 token
String token = JwtUtil.createToken(user.getId());
return Result.success(token);
}
这里有一个容易踩的坑:appid 和 secret 绝对不能写在小程序前端代码里,secret 一旦泄露,别人可以套用你的身份调接口。正确做法是把它们配在后端配置文件中,用的时候读配置。
还有两个微信返回的错误码要记住,答辩时随口说出来很加分:
- errcode 40029:code 无效,一般是 code 已经用过、过期了,或者小程序端和后端的 appid 不一致。
- errcode 45011:接口调用频率受限,多半是短时间内反复调 code2Session。
用户头像昵称那块,现在不能像以前那样直接弹窗拿到完整资料了。微信把"用户主动填写"改成了头像昵称填写能力:头像用 button 组件的 open-type="chooseAvatar",昵称用 <input type="nickname">。这块逻辑不复杂,但如果不清楚新版机制,跑到真机上就会出现"获取微信用户失败"或者拿到一堆空值的情况。
4.2 排班查询与号源展示
排班查询接口的入参设计直接影响前端页面的复杂度。我的建议是提供三个维度供前端组合查询:
code复制GET /api/schedule?departmentId=1&doctorId=3&date=2025-06-16
- 不传 departmentId 时,返回所有科室下的医生排班。
- 不传 doctorId 时,返回该科室所有医生的排班。
- 不传 date 时,默认只返回今天和未来 7 天的排班。
返回的数据结构里,每个时段要带上 computed 字段,比如 remaining = total - used。这个计算在 SQL 里直接查出来,前端不用自己算,减少出错机会:
sql复制SELECT s.*, (s.total - s.used) AS remaining
FROM schedule s
WHERE s.doctor_id = #{doctorId}
AND s.work_date >= #{today}
ORDER BY s.work_date, s.time_start
小程序端展示时,还要判断时段是否已经过期。比如现在时间是 10:30,那 09:00-10:00 的时段就不能再约了。这个判断放在后端接口里更安全,因为前端的时间可以被改。后端可以加一个条件:time_start > NOW() 才返回。
4.3 预约锁号:并发超卖怎么防
这是整套系统的核心难点,也是答辩时最容易问倒人的地方。场景是这样的:某个时段 total=5,used=4,只剩一个号。两个用户同时点提交预约,如果代码这样写:
java复制// 错误写法:先查再判断再更新
Schedule schedule = scheduleMapper.selectById(scheduleId);
if (schedule.getUsed() < schedule.getTotal()) {
schedule.setUsed(schedule.getUsed() + 1);
scheduleMapper.updateById(schedule);
// 插入预约记录
}
在并发情况下,两个请求可能同时查到 used=4,同时通过判断,然后都执行更新,最后数据库里 used=6,可总量只有 5,超卖了。
解决办法是把"判断余号+扣减号源"变成一条原子 SQL:
java复制@Transactional(rollbackFor = Exception.class)
public Long createAppointment(AppointmentCreateDTO dto) {
// 1. 原子扣减号源,成功才继续
int rows = scheduleMapper.increaseUsed(dto.getScheduleId());
if (rows == 0) {
throw new BusinessException("该时段号源已满,请选择其他时段");
}
// 2. 插入预约记录
Appointment appointment = buildAppointment(dto);
appointmentMapper.insert(appointment);
return appointment.getId();
}
对应的 mapper 方法就是一条条件更新 SQL:
sql复制UPDATE schedule
SET used = used + 1
WHERE id = #{scheduleId}
AND used < total
AND status = 0
这条 SQL 的关键在 WHERE used < total:只有当余号大于 0 时,更新才会成功,而且 used 的自增是数据库层面原子完成的。受影响行数 rows=1 说明扣号成功,rows=0 说明已经满了。这样一个 update 就把"先查后改"的并发窗口关掉了。
如果你想让代码在答辩时显得更有层次,还可以再加一个兜底:
- 在 appointment 表加一个唯一约束
(user_id, schedule_id),防止同一个用户对同一个时段重复提交。 - 用 Redis 的
setIfAbsent对"用户+时段"做幂等控制,防止用户连点两次按钮产生两条预约记录。没有 Redis 的话,在事务里用SELECT ... FOR UPDATE先锁住 schedule 行再判断也可以,效果等价。
无论用哪种方案,都要把扣号和插预约记录放在同一个事务里,否则会出现"号扣了但预约记录没插上"或者反过来"记录插上了但号没扣"的问题。这就是为什么方法上要加 @Transactional。
4.4 取消预约与号源回补
取消预约看起来简单,但很容易写漏。取消动作要同时做两件事:
- 把 appointment 的 status 改成"已取消",记录取消时间。
- 把 schedule 的 used 减一。
对应 SQL:
sql复制UPDATE schedule
SET used = used - 1
WHERE id = #{scheduleId} AND used > 0
和预约一样,取消也要在事务里,而且要注意业务规则:比如就诊当天不能取消,或者至少提前 2 小时才能取消。这个规则可以写在 service 层:
java复制if (today.isEqual(appointment.getAppointmentDate())) {
throw new BusinessException("就诊当天不能取消,请到院核销");
}
如果不想让用户随便预约又随便取消挤压真正有需求的人,还可以加一个限制:每个用户同一时刻最多持有 N 条"已预约"状态的记录。这些都是业务策略,加不加看你的系统定位,但在论文里写清楚总没错。
4.5 订阅消息:预约结果通知的完整流程
预约成功后给用户发一条微信通知,用的是"订阅消息"能力。流程是:
- 在微信公众平台申请订阅消息模板,比如"预约成功通知",里面配置科室、医生、时间、预约号等字段。
- 小程序端在提交预约时(或者预约成功后)调用
wx.requestSubscribeMessage,弹窗请求用户授权。 - 用户同意后,后端用
access_token调subscribeMessage.send把消息推给用户。
前端请求授权的代码:
javascript复制wx.requestSubscribeMessage({
tmplIds: ['模板ID'],
success(res) {
if (res['模板ID'] === 'accept') {
console.log('用户已同意接收订阅消息');
}
}
});
这里有个非常坑的机制:用户每次订阅只能接收一条消息。 换句话说,用户这次点了同意,你的系统只能给他推一条;下次想再推,得再让他点一次。所以成熟的方案是在每次关键操作时都请求订阅。毕设场景不需要做得很复杂,但要在文档里说明这个限制,否则答辩老师可能会问"为什么我预约了两次只收到一条通知"。
另外,后端发订阅消息需要一个 access_token,有效期 2 小时,要自己缓存刷新,不能在每次发消息时都去重新获取。这个细节在代码注释里标一下,显得你确实踩过坑。
5. 小程序端避坑实录:这几个报错我都能背下来了
5.1 获取微信用户失败:不是 appid 的问题,是授权逻辑的问题
你在搜索的时候大概率见过这个格式的报错:"小程序获取登录后的微信用户失败:wx1cb4398e1413d7ce"。这个数字串其实是 appid,看着像是 appid 导致的问题,但真相是:appid 只是标识,真正导致失败的是授权链路或者配置不一致。 我排过很多次这类问题,按下面顺序查一遍基本都能解决:
- 检查基础库版本:
getUserProfile在新版基础库上有行为变化,如果开发工具里本地调试正常、真机失败,先看真机上微信版本和基础库版本。 - 检查是否用了测试号:测试号的 appid 和正式小程序不同,code2Session 能否调通完全取决于代码里配置的 appid 和 secret 是否匹配。最常见的情况是代码里写了一个 appid,后端配置里放进去了另一个 appid。
- 检查用户是否拒绝授权:如果用户点了取消,
fail回调会返回一个拒绝信息。需要在界面上给出"重新授权"的入口,而不是直接卡死。 - 真机调试时清缓存:小程序代码缓存可能导致旧逻辑继续跑,微信开发者工具里"清缓存-清除全部缓存"是排查神器。
排除的顺序很重要,先本地模拟器,再换真机,最后查后端日志。比如真机被 err_connection_reset 拦住了(下面会说),那前端所有请求都会失败,表现出来就是"获取用户失败",但根因根本不在授权逻辑。
5.2 真机调试 failed:net::err_connection_reset 的完整排查链路
真机调试时最经典的报错就是 failed: net::err_connection_reset。它在开发工具里可能一切正常,一上手机就连接被重置。很多人第一反应是后端出问题了,其实大多数情况下是网络链路的问题。排查链路我整理成一张表:
| 现象 | 常见原因 | 解决办法 |
|---|---|---|
| 开发工具正常,真机请求失败 | 后端跑在 localhost,手机访问不到 | 电脑 127.0.0.1 换成本机局域网 IP(ipconfig 或 ifconfig 查看),且后端监听 0.0.0.0 |
| 局域网 IP 能通,但还是连接重置 | 手机和电脑不在同一 WiFi,或者开了代理 | 手机和电脑连同一个路由器,关闭系统代理/抓包软件 |
| iOS 真机连 HTTP 接口失败 | iOS 对明文 HTTP 有限制 | 上线必须用 HTTPS;本地调试可临时通过小程序后台配置跳过,或使用调试工具辅助 |
| 上线后仍连接重置 | 接口域名没备案、没配 HTTPS、证书不完整 | 微信要求请求域名必须是已备案的 HTTPS 域名,并在小程序后台"开发管理-服务器域名"里配置 request 合法域名 |
我见过最典型的案例:代码里写的是 http://localhost:8080,开发工具里勾了"不校验合法域名"所以一切正常,真机一跑就 err_connection_reset。这种不是玄学,就是手机根本访问不到电脑的 localhost。把地址改成电脑的局域网 IP,再把电脑防火墙放行 8080 端口,问题立刻消失。
另外一个常见问题是 hbuilderx 或微信开发者工具里的"URL 校验"设置。开发阶段可以在详情-本地设置里勾选"不校验合法域名、web-view(业务域名)、TLS 版本以及 HTTPS 证书",但这只是开发阶段的开关,上线前必须改成真实域名。
5.3 开发中频繁踩到的基础细节:单选框、导航栏高度、页面更新
这几个问题比较小,但几乎每个做小程序的人都会碰到。
预约时段选择,如果直接用 radio-group,注意 bindchange 拿到的是一个字符串,不是对象,要自己根据 index 去匹配时段列表。否则拿到一个 "0" 结果去数组里取元素,取出来是 undefined,页面直接白屏。
顶部导航栏高度,不同机型的刘海屏高度不一样,如果做自定义导航栏,不能写死一个数值。正确姿势是用 wx.getWindowInfo() 获取状态栏高度,再用 wx.getMenuButtonBoundingClientRect() 获取胶囊按钮的位置,动态算出导航栏高度:
javascript复制const { statusBarHeight } = wx.getWindowInfo();
const menu = wx.getMenuButtonBoundingClientRect();
const navHeight = (menu.top - statusBarHeight) * 2 + menu.height;
小程序更新,很多同学做完后总是发现"用户看到的还是旧版本",这需要在 app.js 的 onLaunch 里接入 wx.getUpdateManager(),检测到新版本时弹窗提示用户重启应用。代码不复杂,但属于"你不提老师也会觉得无所谓、你提了就是加分项"的细节。
6. 源码有了,LW文档怎么和代码配合才加分
6.1 毕业设计说明书的标准篇章结构
"LW文档"里的 LW 是论文的缩写,也就是毕业设计说明书。很多同学代码写完了,文档不知道怎么组织。我建议按下面这个结构来写,和代码一一对应:
- 摘要/Abstract:一两句话说明系统解决的问题和用的技术。中文摘要 300 字左右,英文摘要别用翻译软件硬翻,重点词汇要对。
- 绪论:背景与意义、国内外现状、主要研究内容。这部分不用写太长,但现状部分要引用真实存在的系统,比如各大医院公众号和小程序预约。
- 需求分析:功能需求、非功能需求、用例图。用例图不要画得太复杂,用户端一个图、管理员端一个图就够了。
- 系统设计:总体架构、技术选型、功能模块设计、数据库设计(E-R 图 + 核心表结构)。
- 系统实现:界面截图 + 核心代码片段。每个页面配一张截图,关键代码只贴有技术含量的,不要整段贴。
- 系统测试:测试环境、功能测试用例表、测试结果。这个环节是很多人的薄弱项,后面细说。
- 总结与展望:总结完成的工作,再说几个没做的功能作为"展望",比如在线支付、排队叫号。
6.2 截图与核心代码怎么选,不要贴整段没有重点的代码
文档里贴代码的原则是选有代表性的、能说明设计思想的,而不是把 controller/service/mapper 全部堆进去。我建议只挑四个地方:
- 微信登录的 code2Session 调用换 openid 逻辑。
- 注册预约时的事务 + 条件更新 SQL(锁号那段)。
- 取消预约的号源回补逻辑。
- 排班模板批量生成的实现。
每个代码块配 2-3 句话解释"这段代码做了什么、为什么这样写"。截图也是同理,不要截一整张页面,截关键区域,旁边画圈标注。
6.3 答辩高频问题:这些地方一定要能讲透
根据我带毕设的经验,下面这些问题是答辩时几乎必问的,提前把答案准备好:
- 为什么选微信小程序而不是 App? 答:成本低、免安装、微信生态内传播方便,对诊所这种线下场景来说用户触达效率更高。
- 多用户同时预约一个时段怎么保证不超卖? 答:条件更新 SQL + 事务,把判断和扣减合并成一条原子操作。如果用了 Redis 幂等,也一并讲出来。
- 用户取消预约之后号源怎么处理? 答:把预约状态改为已取消,同时回补 schedule 的 used 字段,事务保证一致性。
- 微信登录的 openid 和 unionid 有什么区别? 答:openid 在同一个小程序内唯一,unionid 在同一个微信开放平台账号下的多个应用间唯一。毕设只需要 openid。
- 测试用例怎么设计的? 答:针对每个功能模块列一条条用例,包括正常流程和异常流程(号源已满、重复预约、取消已取消的预约)。
测试用例表可以做成这样:
| 用例编号 | 测试项 | 前置条件 | 操作步骤 | 预期结果 | 实际结果 |
|---|---|---|---|---|---|
| TC-001 | 预约号源已满 | 某时段 total=5, used=5 | 用户提交预约 | 提示"号源已满",不生成预约记录 | 与预期一致 |
| TC-002 | 重复预约同一时段 | 用户已预约该时段 | 再次提交同一时段 | 提示"请勿重复预约" | 与预期一致 |
| TC-003 | 取消预约后号源回补 | 某时段 used=3 | 取消一条预约 | 该时段 used=2,预约状态为已取消 | 与预期一致 |
写完测试表,把测试结果截图放上去,整个论文的完整度立刻上一个台阶。
最后说一点个人体会:这类预约挂号系统的代码难度其实中等,真正的挑战在于把每个设计决策讲清楚。源码能跑通只是第一步,答辩时能说清楚"为什么排班表要这样建""为什么预约扣号要用条件更新""为什么微信登录要分两步走",才是让人相信这确实是你自己做的关键。别图快,把每个模块从头到尾自己写一遍、调一遍,踩过的坑在文档里如实写出来,反而比一份毫无瑕疵的完美作业更有说服力。
