简介:微信小程序医院预约挂号系统是一份完整项目开发资料包,面向计算机专业学生、毕业设计选题者及小程序学习者,解决线上预约挂号平台的搭建问题。系统涵盖注册登录、科室医生查询、挂号预约、支付取号及取消预约等功能,并配备后台管理、数据统计模块,可直接作为毕业设计参考或课程实践项目。
压缩包共2000个文件、约19.82MB,以php文件、png图标、js脚本及vue组件为主,同时包含数据库备份、wxml页面结构、wxss样式、sql数据文件及bat运行脚本等,覆盖前端页面、后端接口、数据库设计与部署全流程。目前已有372人学习下载,适合需要完整微信支付与接口调用验证链路的在校学生。
通过这些源码可系统掌握小程序开发环境搭建、API接口调用、微信支付集成及数据库表结构设计等关键技能,适合需要完整项目实战经历的开发者;配套的安装、启动与构建脚本及备份文件,也有助于快速还原运行环境并开展二次开发。
1. 微信小程序医院预约挂号系统:一个毕设题目背后的完整技术栈
每年毕业季都会看到大量同学在找“微信小程序医院预约挂号系统源码”,但这恰恰是个容易翻车的需求——你拿到的源码要么跑不起来,要么数据库表结构一塌糊涂,要么论文和代码根本对不上。这个项目本质上是三个独立系统拼在一起:微信小程序前端负责展示和交互、后端接口处理预约业务逻辑、MySQL数据库存医生排班和号源数据,再配上一篇能自圆其说的毕业论文。它适合计算机相关专业做毕业设计,也适合刚入门微信小程序开发的人练手,因为你从一个熟悉的线下场景出发,能自然地把小程序生命周期、网络请求、数据库设计、事务处理这些知识点全部串起来。
这里有一个反直觉的结论:预约挂号系统真正的技术难点不在小程序界面,而在于“号源不超卖”和“排班与医生状态的同步”。界面列表谁都能写,但两个人同时挂最后一个号时怎么保证只有一个成功,才是你要在论文里写清楚的核心切入点。本文按“技术选型 → 数据库设计 → 核心代码 → 踩坑记录 → 论文写作”这条路径展开,全程给你可直接复现的操作和参数。
2. 技术选型为什么是微信小程序 + Spring Boot + MySQL:能力和边界先说清楚
2.1 小程序端选型:原生还是 uniapp
常见做法是用微信小程序原生框架或者 uniapp。原生框架的优点是调试直接、小程序 API 覆盖完整,对毕设和中小型项目足够;缺点是只有一套代码,将来想发到支付宝小程序或抖音小程序得重写。uniapp 的好处是一套 Vue 语法编译到多端,社区生态也成熟,但编译链路多一层,遇到问题排错时间会变长。
如果你只做微信小程序这一个端,我建议就直接用原生。理由很实际:你需要展示的小程序页面无非是首页、科室列表、医生排班、预约确认、个人中心、订单记录这几个页面,原生开发的代码结构反而更容易让论文里的截图和代码片段一一对应。uniapp 的价值在你同时要 Android、iOS、鸿蒙等多端时才真正体现出来,具体对比可以参考热词里提到的“uniapp 开发 微信小程序 vs android / ios / 鸿蒙”这类讨论,但毕设场景通常没必要把战线拉那么长。
2.2 后端选型:SSM 还是 Spring Boot
后端最常见的两个选择是 SSM(Spring + Spring MVC + MyBatis)和 Spring Boot。老牌毕设项目大量是 SSM,因为学校课程还在教;Spring Boot 更接近企业现状,而且内嵌 Tomcat、自动配置这些特性让部署省很多事。
如果时间紧张,我建议直接上 Spring Boot 2.7.x + MyBatis,理由有三点:
- Spring Boot 的 starter 机制让你不用手工配一堆 XML,集成 MyBatis 只需要加依赖和写 Mapper 接口;
- 内嵌 Tomcat 让部署变成 java -jar 一条命令,论文里写“部署过程”会简单很多;
- 网上现成的 Spring Boot 项目模板远多于 SSM,遇到问题更容易搜到解决方案。
2.3 数据库为什么选 MySQL 而不是其他
这个项目的数据量很小,MySQL 完全够用。选择 MySQL 的核心原因在于:它的事务支持成熟,InnoDB 引擎的行级锁可以解决号源并发问题;它的 SQL 语法和教程资源最丰富,团队里任何人都能快速上手;加上 Navicat、DataGrip 这类可视化工具对 MySQL 支持最好,导出数据库脚本给导师检查也更方便。
有人会问要不要用 Redis 做缓存来抗高并发,答案是现阶段不需要。医院预约挂号的并发规模和学习项目能模拟出来的压力完全在两个量级,用 MySQL 自身的行锁加事务就能解决超卖问题。引入 Redis 等于给自己的毕设增加了一个“需要解释清楚为什么用”的负担,而且论文评委大概率会追问缓存和数据库的一致性你怎么保证——这个坑不要自己给自己挖。
2.4 整体系统架构:三个端各自管什么
系统按“小程序端 → 后端接口层 → 数据库层”三级划分。小程序端负责页面渲染、用户登录获取 openid、请求后端接口并处理返回数据;后端接口层负责接收小程序请求、校验参数、调用 Service 层业务逻辑、跟数据库交互;数据库层只做数据存储和事务保障。业务上还有一个伪装成“第四端”的管理后台——也就是医院端排班管理,毕设里通常用一个 Web 页面或者直接在数据库里改数据来模拟。如果你想在论文里突出完整性,可以给管理员做一套简单的 Web 管理页面,用 Spring Boot + Thymeleaf 或者单独的 Vue 项目都可以,但注意不要抢了主项目的篇幅。
3. 数据库表设计:预约挂号系统的关键不是用户表而是号源表
3.1 核心表结构:六张表建立业务骨架
数据库设计是这个项目的门面,论文里 ER 图占一整页。给出一份可直接执行的 MySQL 建表脚本,覆盖用户端和管理端:
-- 用户表:存小程序用户的 openid 和基本信息 CREATE TABLE `user` ( `id` INT NOT NULL AUTO_INCREMENT, `openid` VARCHAR(64) NOT NULL COMMENT '微信小程序用户唯一标识', `nickname` VARCHAR(64) DEFAULT NULL, `phone` VARCHAR(20) DEFAULT NULL, `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_openid` (`openid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 科室表 CREATE TABLE `department` ( `id` INT NOT NULL AUTO_INCREMENT, `name` VARCHAR(64) NOT NULL COMMENT '科室名称', `intro` TEXT COMMENT '科室介绍', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 医生表 CREATE TABLE `doctor` ( `id` INT NOT NULL AUTO_INCREMENT, `department_id` INT NOT NULL, `name` VARCHAR(32) NOT NULL, `title` VARCHAR(32) COMMENT '职称:主任医师/副主任医师/主治医师', `avatar` VARCHAR(128) DEFAULT NULL, `intro` TEXT, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 排班表:核心表,存医生某天某时段的号源总量 CREATE TABLE `schedule` ( `id` INT NOT NULL AUTO_INCREMENT, `doctor_id` INT NOT NULL, `work_date` DATE NOT NULL COMMENT '出诊日期', `period` TINYINT NOT NULL COMMENT '时段:1上午 2下午', `total_slots` INT NOT NULL DEFAULT 20 COMMENT '总号源数', `booked_slots` INT NOT NULL DEFAULT 0 COMMENT '已预约数', `status` TINYINT NOT NULL DEFAULT 1 COMMENT '1正常 0已停诊', PRIMARY KEY (`id`), UNIQUE KEY `uk_doctor_date_period` (`doctor_id`, `work_date`, `period`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 预约订单表 CREATE TABLE `appointment` ( `id` INT NOT NULL AUTO_INCREMENT, `order_no` VARCHAR(32) NOT NULL COMMENT '订单号', `user_id` INT NOT NULL, `schedule_id` INT NOT NULL, `patient_name` VARCHAR(32) NOT NULL COMMENT '就诊人姓名', `patient_phone` VARCHAR(20) NOT NULL, `status` TINYINT NOT NULL DEFAULT 0 COMMENT '0已预约 1已取消 2已完成', `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_order_no` (`order_no`), KEY `idx_user_id` (`user_id`), KEY `idx_schedule_id` (`schedule_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;这个设计里最关键的两个细节是schedule表上的唯一约束uk_doctor_date_period和预约订单表里的idx_schedule_id索引。唯一约束保证同一个医生同一天同一个时段只有一条排班记录,业务上排班不会重复;索引保证按排班查预约时不会全表扫描。
3.2 号源状态字段为什么设计成 booked_slots 而不是剩余号数
很多初学会把排班表设计成remaining_slots(剩余号数),一次预约就减 1。这个设计有一个隐患:如果业务需要区分“已预约、已取消、已过期未就诊”,剩余号数的计算逻辑会变得很混乱。比如一个号被预约后患者取消,剩余号数要不要加回去?医生停诊后号源怎么处理?用total_slots减去booked_slots随时计算可约号数,状态变更只增加booked_slots,取消时再减一,逻辑就清晰了——号源总量不变,只是已占用的数量在变化。
3.3 预约流程的数据状态流转:从可约到完成要经过几个状态
一个完整的预约流程在数据层是这样流转的:
用户进入医生排班页看到的是schedule.status = 1且booked_slots < total_slots的记录;点击预约后后端开启事务,执行“检查排班状态 → 检查是否还有余号 → 插入 appointment 记录 → 更新 schedule.booked_slots = booked_slots + 1”这几步;用户取消时反向操作,状态改为 1(已取消),排班的 booked_slots 减一;就诊时间过了之后由管理端批量把对应预约置为 2(已完成)。这套状态机要画进论文里,用一张表格把每个状态下用户能做什么、不能做什么写清楚。
3.4 就诊人管理:一个用户多个就诊人的冗余设计
现实里一个人帮父母挂号很常见,所以需要一个patient表和user表做一对多关联。就诊人表存姓名、身份证、手机号、关系等字段,预约时从用户的就诊人列表里选一个。这里有个小坑:就诊人手机号可以跟用户手机号不一样,很多源码在这一步偷懒只存一个 phone 字段,导致论文里写“支持多就诊人”实际却实现不了。热词里提到的“微信小程序单选框”正好可以用在这里——选择就诊人时用单选框组件展示列表,这个交互细节可以在论文里当作前端功能点来写。
4. 核心代码落地:预约业务的事务控制和小程序端请求封装
4.1 预约接口:事务 + 行锁,用一条 UPDATE 语句防超卖
预约挂号最怕两个用户同时抢最后一个号。常见方案是“先 SELECT 再 UPDATE”,但这个方案在并发场景下会出问题——两个请求同时 SELECT 到余号 1,然后同时 INSERT 预约记录,最后都执行 UPDATE,结果超卖。正确做法是用一条 UPDATE 语句做条件更新,影响行数为 0 则说明没抢到号。给出核心 Service 代码:
@Transactional(rollbackFor = Exception.class) public Appointment book(AppointmentRequest request) { // 1. 锁定排班记录并检查余号 Schedule schedule = scheduleMapper.selectByIdForUpdate(request.getScheduleId()); if (schedule == null || schedule.getStatus() != 1) { throw new BusinessException("排班不存在或已停诊"); } if (schedule.getBookedSlots() >= schedule.getTotalSlots()) { throw new BusinessException("号源已约满"); } // 2. 原子更新已约数量,防止超卖 int rows = scheduleMapper.increaseBookedSlots(schedule.getId()); if (rows == 0) { throw new BusinessException("号源已约满,请选择其他时段"); } // 3. 生成预约记录 Appointment appointment = new Appointment(); appointment.setOrderNo(generateOrderNo()); appointment.setUserId(request.getUserId()); appointment.setScheduleId(request.getScheduleId()); appointment.setPatientName(request.getPatientName()); appointment.setPatientPhone(request.getPatientPhone()); appointment.setStatus(0); appointmentMapper.insert(appointment); return appointment; }对应的 Mapper XML 里最关键的一段:
<update id="increaseBookedSlots"> UPDATE schedule SET booked_slots = booked_slots + 1 WHERE id = #{id} AND booked_slots < total_slots </update>这里第一处selectByIdForUpdate加了行级锁,把这条排班记录锁住,后续事务必须等当前事务提交。第二处 UPDATE 语句的条件booked_slots < total_slots是兜底防御——即使前面的锁没生效,数据库层面也会阻止超卖。两处结合是业内处理库存扣减的常规手法,论文里如果你能把这个“select for update + 条件更新”的双保险讲清楚,答辩基本不会被难住。
4.2 微信小程序端请求封装:登录态过期自动处理
小程序端请求后端接口,必须有一个统一的请求工具,这是热词里“微信小程序 请求封装”对应的实践。要注意小程序是没有 cookie 的,登录态靠自定义 header 传递 token,而且微信登录成功后的 code 换 openid 接口必须放在后端调用,不能暴露在小程序代码里。用 JavaScript 写一个小程序的请求封装:
const request = (url, method = 'GET', data = {}) => { return new Promise((resolve, reject) => { wx.request({ url: getApp().globalData.baseUrl + url, method, data, header: { 'Content-Type': 'application/json', 'Authorization': wx.getStorageSync('token') }, success: (res) => { if (res.statusCode === 401) { // token 过期,重新登录 wx.removeStorageSync('token'); wx.navigateTo({ url: '/pages/login/login' }); return; } if (res.statusCode === 200 && res.data.code === 0) { resolve(res.data.data); } else { wx.showToast({ title: res.data.msg || '请求失败', icon: 'none' }); reject(res.data); } }, fail: (err) => { wx.showToast({ title: '网络异常', icon: 'none' }); reject(err); } }); }); }; module.exports = { request };这段代码做了三件事:统一拼接 baseUrl 和管理 token;根据 HTTP 状态码 401 自动清理登录态并踢回登录页;把后端返回的业务码统一处理,页面层只关心成功的数据,不用每个页面自己写 Toast。一个常见问题是后端同学返回的 code 不统一,有的接口返回 200 表示成功,有的是 0,这会导致请求封装没法复用——建议在项目一开始就约定:HTTP 状态码只管网络层,业务层统一用code === 0表示成功,msg携带错误信息。
4.3 微信登录流程:wx.login 换 code,后端换 openid
前端在 onLoad 里调用 wx.login 拿 code,把 code 发给后端,后端调 jscode2session 接口换 openid,这是微信小程序的固定登录套路。代码实现分别在两端,关键是理解“前端绝对不能用 appid 和 secret”。
前端登录页或启动页的核心逻辑:
wx.login({ success: (res) => { if (res.code) { request('/api/auth/login', 'POST', { code: res.code }) .then((data) => { wx.setStorageSync('token', data.token); wx.setStorageSync('userId', data.userId); wx.switchTab({ url: '/pages/index/index' }); }); } } });后端的 controller 接住 code 后调微信接口,拿到 openid 后先去 user 表查一下有没有记录,没有就自动注册。这个“自动注册”逻辑一定要写在登录接口里,用户根本感觉不到注册过程,体验上就是微信一键登录。要注意的是微信官方对wx.login返回的 code 有效期是 5 分钟,且只能用一次,所以 session 信息建议后端自己维护一份,不要每次请求都重新调 jscode2session。
4.4 排班列表页的下拉刷新和触底加载
预约系统的医生排班列表按日期聚合,日期多了以后需要分页。小程序端做触底加载的标准写法是 onReachBottom 触发下一页请求,下拉刷新用 enablePullDownRefresh 配合 onPullDownRefresh。一个容易被忽略的点是:翻页时如果用户正在快速滑动,上一页请求还没回来又发了下一页请求,会造成数据错乱。处理方式是加一个loading状态锁,当前页请求没有完成时直接 return。
分页接口的参数设计要为论文里的“性能优化”章节服务:页码 pageNum 从 1 开始,pageSize 固定为 10 或 20,返回结果里带上 total 字段。后端的 Mapper 用 LIMIT #{offset}, #{pageSize} 实现分页,如果数据量超过一万条,可以讲一讲用游标分页还是 offset 分页的取舍,但就毕设的数据量来说,offset 分页完全够用,不要过度设计。
5. 避坑与排查:预约挂号系统从零到跑通的五个真实坑
5.1 手机号授权改版:wx.getUserProfile 只能拿昵称头像,拿不到手机号
很多源码里的“手机号一键获取”还在用 wx.getUserProfile 或者 wx.getPhoneNumber 的旧实现。现象是点击授权按钮没有任何反应,或者接口报错说没有权限。原因是微信官方在基础库调整后,获取手机号必须通过 button 组件的 open-type 属性为 getPhoneNumber,且要求小程序已认证,个人主体的小程序无法获得手机号。实际开发中我的做法是:手机号不作为登录必须项,用户可以在预约时手动填写就诊人手机号,这样不仅绕开资质限制,而且符合“就诊人手机号和用户手机号可以不同”的业务模型。
5.2 日期传参时区问题:小程序传 2025-06-01 到后端变成 2025-05-31
现象是用户在客户端选择的就诊日期,提交后发现排班匹配不上,查数据库发现日期少了一天。原因是小程序端日期字符串在传输过程中被转成 UTC 时间,然后后端解析时按服务器时区转回本地时间,跨时区后发生了偏移。解决方式有两种:前端直接传字符串2025-06-01,后端用@DateTimeFormat(pattern = "yyyy-MM-dd")配合 LocalDate 接收;或者在传输层统一用时间戳毫秒值。我更推荐第一种,因为排班日期本质上是一个“日期”而不是“时刻”,LocalDate 天然契合这个语义,还能避免在代码里反复做时区换算。
5.3 排班日期过期了还能预约:缺少定时任务清理
现象是用户在前端还能看到过去的排班,点击预约也提示成功,但去医院发现医生根本不出诊。根本原因是排班记录只在后台新增或修改时被激活,没有机制把已过期且未就诊的排班自动置为失效。解决方式是加一个定时任务,每天凌晨把work_date小于当天日期的排班状态批量更新为 0(已停诊),同时把未就诊的预约标记为“爽约”或者“已完成”。Spring Boot 里可以用@Scheduled(cron = "0 0 2 * * ?")实现,注意定时任务类要加上@EnableScheduling注解,这个细节有很多人忽略,导致注解加上了但任务根本没执行。
5.4 就诊人列表删不掉:预约历史还关联着关联数据
用户想要删除一个曾经挂过号的就诊人,前端点了删除没有反应。原因是有外键关联或者业务代码里边删边报错。解决思路是逻辑删除而不是物理删除,给 patient 表加一个is_deleted字段,删除时置为 1,查询列表时过滤。这样既保留历史预约数据的完整性,又能满足用户“看不到这个就诊人”的需求。如果论文里用物理外键,删除就诊人时还要检查 appointment 表有没有关联记录,有就拒绝删除,提示“该就诊人有未完成的预约”——这种设计不算错,但对用户不友好。
5.5 源码拿回来连不上数据库:字符集和时区配置不一致
从网上下的源码,修改了配置文件里的数据库地址但启动仍然报错,常见的报错内容是Unknown database或者 SQL 执行到一半说乱码。前者的原因是 MySQL 里根本没有创建对应的数据库,而不是连不上,运行CREATE DATABASE hospital CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;就能解决;后者的原因一般是数据库连接串里没指定characterEncoding=utf8和serverTimezone=Asia/Shanghai,导致中文写入变成问号。建议拿到任何源码的第一件事,就是检查 JDBC 连接串里有没有这两个参数,没有就补上,这是毕设阶段的必修课。
6. 从能跑到能答辩:论文怎么写、系统怎么验证、还有三个进阶优化
预约挂号系统的论文结构通常是这样几条主线:绪论里写背景和国内外现状;技术选型章节把第二章的选型理由展开;需求分析写用例图和数据流图;系统设计章节里画数据库 ER 图、接口列表、页面原型;系统实现章节用截图加代码片段说明每一个功能;系统测试章节给出测试用例表和结果截图。
有一个容易踩的低级错误是:论文里的数据库表结构和你实际写的代码不一致。因为很多人先写论文再补代码,或者从网上找了一份论文改个题目,答辩时评委打开你的数据库一看,表名对不上、字段对不上,直接被认定抄袭或造假。正确做法是等代码和数据库都稳定之后再回头同步到论文里,宁可花两天时间改文档,也不要冒这个风险。
验证系统不是“启动后点一遍没问题”就完了。建议至少做三件事:第一,用 Postman 或不写代码的 ApiPost 把注册、登录、排班查询、预约、取消预约这几个接口按顺序执行一遍,把接口文档截图放进论文;第二,用模拟并发请求检查超卖,比如在测试环境用 Jmeter 或简单脚本对同一个排班同时发起 50 个预约请求,验证成功数不超过排班余号数,这个截图对论文的“高性能设计”很有说服力;第三,把数据库中的预约记录手动改坏一条,比如把 appointment 的 schedule_id 改成不存在的值,然后跑一遍查询接口,确认系统不会直接崩溃,而是返回友好错误。这一条能体现你对异常处理的重视,答辩时评委很喜欢追问这个点。
进阶优化有三个方向,你可以根据精力挑一两个写进论文的“展望”章节:一是用 Redis 缓存科室列表和医生排班信息,减少数据库压力,但讲清楚缓存失效和更新的方案;二是给小程序端加上预约提醒功能,用微信订阅消息在就诊前一天给用户推送;三是做一个简单的医院端管理页面,替代直接改数据库的土办法,让排班、停诊、预约记录管理都可视化。任何一个方向做进去,都足够让论文的深度上一个台阶。
回到标题本身——微信小程序医院预约挂号系统的真正价值,在于它把“用户端小程序、管理端 Web、数据库事务、第三方登录”这四件事完整串了一遍。我自己做完这个人项目时最大的教训是:先设计数据表再写业务代码,先写清楚状态机再动手做页面,这个顺序反了的话,后面每个功能都要返工。你如果正打算做,按本文的步骤从建表开始,跑通主流程后再考虑优化,希望帮到你。
本文还有配套的精品资源,点击获取