简介:微信小程序与SSM后台结合的医院挂号系统毕业设计资源,面向计算机及相关专业毕业生、需要快速搭建完整项目的开发者。项目覆盖患者、医生、科室、专家、预约与取消预约申请等核心模块,后台基于SSM框架,数据库采用MySQL,后台管理员可在浏览器中登录管理数据,前端使用微信开发者工具,整体具备界面清晰、操作简单、功能完整的特点。压缩包共858个文件,包含java后台源码、vue前端页面、JavaScript逻辑、SQL脚本与数据库备份、毕业论文文档及mp4视频演示,主要文件类型涉及png/svg/jpg图片素材、json/xml/wxml/wxss配置与页面文件等,包体约46.83MB。资源已有180人学习下载。下载后可直接对照源码、数据库脚本与论文说明,完成环境部署、模块调试和答辩展示,适合作为毕业设计选题参考或二次开发基础。
1. 医院挂号小程序:这套 SSM + MySQL 毕业设计到底能跑成什么样
医院挂号小程序在毕业设计里出现频率一直很高,但多数能找到的源码要么缺数据库脚本,要么后端接口是写死的假数据,真正能完整跑通的并不多。这套基于微信小程序、SSM 和 MySQL 的项目,胜在把「患者—科室—医生—预约—取消申请—审核」这条链路做完整了:小程序端承担患者操作,SSM 后台负责接口和管理页面,MySQL 存业务数据,还带一份毕业论文和视频演示,适合两类人——准备做医疗类课设的学生,以及想快速拿一个前后端闭环练手的 Java 初学者。我按拆包的习惯来写,先把技术选型讲透,再落到表结构、核心接口和避坑点。
2. 技术栈拆解:为什么微信小程序、SSM 和 MySQL 能搭起挂号全流程
2.1 小程序端:为什么是微信小程序而不是 H5 或 App
医院挂号这种场景有个特点:使用频率低、操作流程短、用户不愿意为它单独装一个 App。微信小程序天然契合这个场景,用户扫码或搜索就能进,看完号源、预约、取消,关掉就走,不需要下载安装,也不占用桌面空间。
从开发角度看,微信小程序的技术栈对前端新手也友好。页面由 WXML、WXSS 和 JS 组成,写过 Vue 的人几乎零成本迁移,组件化思路也一致。登录环节是另一个加分项,小程序可以直接调用wx.login()拿 code,后端拿 code 换 openid,省掉了手机号验证码这套注册逻辑。毕业设计答辩时,评委问「用户身份怎么来的」,答一句「微信授权 openid」就足够清楚。
这套项目里小程序端承担的是患者侧操作,包含首页科室列表、专家信息、预约提交、我的预约、取消预约申请几个核心页面。管理后台是另外一套 Web 页面,由 Vue 构建后输出静态文件,后端用 SSM 提供接口。小程序和后台共用同一套数据库和接口,只是角色不同。
2.2 后端:SSM 这套老组合为什么反而好上手
SSM 是 Spring、SpringMVC、MyBatis 三个框架的组合。它不算新,但在毕业设计场景里反而是最稳的选择,原因有三点。
第一,Spring 的容器管理和事务控制特别适合预约这类有状态流转的业务。一个预约从提交到就诊、取消、审核,中间涉及多次数据库读写,用@Transactional把几步操作包在一个事务里,要么全成功要么全回滚,不会出现「号源减了但预约记录没写进去」这种半截数据。
第二,MyBatis 把 SQL 明明白白写在 Mapper 的 XML 或注解里,不像 JPA 那样靠方法名推导。答辩时被问「某个统计怎么查的」,直接指 SQL 就能讲清楚。这套项目里患者管理、医生管理、科室管理、预约信息管理这些模块,本质都是增删改查,MyBatis 写起来最直白。
第三,SSM 的参考资料最多。@Controller、@Service、@Autowired、@Transactional这几个注解的用法网上随便搜都是完整案例,真卡住了也容易找到人问。相比之下,用 Spring Boot 的人虽然多,但毕业设计如果导师指定了 SSM 场景,或者论文框架已经按 SSM 搭好,临时换框架的成本反而更高。
2.3 工程结构的正确认识:bat 脚本、Vue 管理后台与备份文件
拿到这套源码,先别急着找application.yml,我一般是先扫一遍文件列表。这类带 Windows 部署脚本的工程,文件结构往往比想象中更有信息量。
工程里能看到1-install.bat、2-run.bat、3-build.bat三个脚本,命名顺序就是部署顺序。按常见约定,1-install.bat负责初始化环境,包括检查 MySQL 是否启动、导入数据库脚本、把 Maven 依赖拉全;2-run.bat启动后端服务和 Tomcat;3-build.bat构建管理后台的前端静态文件。实际跑的时候,我会先打开脚本看一眼里面的路径写的是绝对路径还是相对路径,很多机器上跑不起来都是因为脚本里的盘符和实际解压目录对不上。
管理后台是 Vue 工程构建后的产物,所以能看到app.26c1910f.css这种带 hash 的静态文件,还有一批.bak结尾的备份文件,比如main.css.bak、IndexAsideStatic.vue.bak、update-password.vue.bak、BreadCrumbs.vue.bak、IndexHeader.vue.bak。这些.bak是改动前的留底。我的习惯是,凡是准备动管理后台的样式或页面逻辑,先把对应文件复制一份.bak再动手,这个习惯在这套工程里已经替你做好了。
MySQL 版本也要提前确认。如果本机装的是 MySQL 8.x,连接驱动要配com.mysql.cj.jdbc.Driver,URL 还得加serverTimezone=Asia/Shanghai,否则一启动就报时区错误。装 5.7 的话用老驱动com.mysql.jdbc.Driver就行,这块坑我放在第 5 章细说。
3. 数据库设计:预约、取消、审核背后要用到的五张核心表
3.1 需求拆解:从功能列表到实体关系
摘要里列的功能是患者管理、医生管理、科室管理、专家信息管理、预约信息管理、取消预约申请管理和系统管理。这七个功能拆到数据库层面,可以收敛成五个核心实体。
患者和医生都属于用户,但角色不同,所以放在一张用户表里用角色字段区分,还是拆成两张表,这是个设计取舍。我拆过的同类项目里,毕业设计一般倾向一张用户表加role字段(1 管理员、2 医生、3 患者),代码里用role控制菜单和接口权限,少建一张表,论文里也好描述。科室和专家(医生)是独立实体,科室一对多医生。预约记录是核心业务表,取消申请则是预约的附属流程表。
这五个实体之间的关系很清晰:患者选科室,科室下有医生,患者对医生发起预约,预约可以发起取消申请,管理员审核取消申请。对应到表就是sys_user、department、doctor_info、appointment、cancel_application五张核心表,另外再加一张schedule排班表来约束医生的出诊日期和号源上限。下面先看预约这条主线。
3.2 预约表设计:状态机、时段字段与唯一索引
预约表是全系统最关键的一张表,设计好了,后面的接口写起来会顺很多。核心字段通常包括患者 ID、医生 ID、预约日期、时段、状态、创建时间。其中三个设计点值得展开说。
第一个是时段字段。很多同类项目把「上午 / 下午」直接存成字符串'上午'、'下午',这能跑,但查询和统计的时候 SQL 写起来别扭。我一般建议存TINYINT,0代表上午、1代表下午,页面展示时再映射成中文。这样按时段统计号源、判断某个时段是否约满都比字符串匹配快。
第二个是状态字段。预约状态不是只有「已预约」和「已取消」两个值,完整的流转是这样的:患者提交预约后状态为0待就诊;医生完成接诊后变为1已完成;患者发起取消申请时先变为2待取消;管理员审核通过后才变为3已取消。中间那个「待取消」是为了让管理员的审核动作有地方落笔,如果患者一点取消就直接变已取消,那取消申请表就失去了存在意义。
第三个是唯一索引。为了防止同一个患者在同一天重复预约同一时段,可以在patient_id + appointment_date + time_slot上建唯一索引。这个规则一旦定下来,代码里就算忘了查重,数据库层也能兜底拦截。实际医院里更合理的约束是「同一患者同一天不能挂同一个医生的号」,所以唯一索引的字段组合要根据你的业务规则调整,答辩时能说清楚这个设计就加分。
3.3 建表 SQL 与初始化数据
五张核心表的建表 SQL 大致如下,字段命名和源码 Mapper 里的映射保持一致,直接导入就能用:
CREATE DATABASE IF NOT EXISTS hospital_booking DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE hospital_booking; CREATE TABLE sys_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL COMMENT '登录账号', password VARCHAR(100) NOT NULL COMMENT 'MD5加密后的密码', real_name VARCHAR(50) COMMENT '真实姓名', role TINYINT NOT NULL DEFAULT 3 COMMENT '1=管理员 2=医生 3=患者', phone VARCHAR(20) COMMENT '手机号', openid VARCHAR(64) COMMENT '微信openid,小程序登录后回填', create_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_username (username) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表'; CREATE TABLE department ( id INT PRIMARY KEY AUTO_INCREMENT, dept_name VARCHAR(50) NOT NULL COMMENT '科室名称', intro VARCHAR(500) COMMENT '科室介绍', sort_order INT DEFAULT 0 COMMENT '排序权重' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='科室表'; CREATE TABLE doctor_info ( id INT PRIMARY KEY AUTO_INCREMENT, doctor_name VARCHAR(50) NOT NULL, department_id INT NOT NULL COMMENT '所属科室ID', title VARCHAR(30) COMMENT '职称,如主任医师', specialty VARCHAR(200) COMMENT '擅长领域', intro TEXT COMMENT '医生简介', max_appointments INT NOT NULL DEFAULT 20 COMMENT '每时段号源上限' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='专家信息表'; CREATE TABLE appointment ( id INT PRIMARY KEY AUTO_INCREMENT, patient_id INT NOT NULL COMMENT '患者用户ID', doctor_id INT NOT NULL COMMENT '医生ID', appointment_date DATE NOT NULL COMMENT '预约日期', time_slot TINYINT NOT NULL DEFAULT 0 COMMENT '0=上午 1=下午', status TINYINT NOT NULL DEFAULT 0 COMMENT '0=待就诊 1=已完成 2=待取消 3=已取消', create_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_patient_slot (patient_id, appointment_date, time_slot), KEY idx_doctor_date (doctor_id, appointment_date, time_slot) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='预约记录表'; CREATE TABLE cancel_application ( id INT PRIMARY KEY AUTO_INCREMENT, appointment_id INT NOT NULL COMMENT '关联的预约ID', patient_id INT NOT NULL COMMENT '发起取消的患者ID', reason VARCHAR(500) COMMENT '取消原因', audit_status TINYINT NOT NULL DEFAULT 0 COMMENT '0=待审核 1=通过 2=驳回', audit_time DATETIME COMMENT '审核时间', create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='取消预约申请表';SQL 里有几个值得注意的点。全库统一用utf8mb4,不是utf8,否则小程序端提交的 emoji 表情和生僻字存入数据库会变成问号,这是老生常谈但每次都能踩到。唯一索引uk_patient_slot防重复预约,idx_doctor_date是给「按医生和日期查号源」这个高频查询用的,没有这个索引,预约量一大查询就会慢。max_appointments字段放在doctor_info表里,表示这个医生每个时段最多接多少患者,这个值决定了余号判断的阈值。
初始化数据至少要有一条管理员账号和几个科室记录。管理员账号密码一般用 MD5 加密存储,初始密码常见的是123456,MD5 值可以直接在 SQL 里写死插入。科室数据按医院常见的几个科室插入几条即可,比如内科、外科、儿科、妇产科,每个科室再配一两名医生,不然小程序端点进去全是空列表,演示效果会打折扣。
4. SSM 后端接口:从登录鉴权到预约状态机,核心业务怎么写
4.1 Controller 层只做参数校验,业务逻辑下沉到 Service
看这套后端代码时,先抓住分层结构。Controller 层只接收前端传来的参数,做最基础的校验(非空、格式),然后调 Service 层方法,拿到结果返回 JSON。业务逻辑一律不写在 Controller 里,这是 SSM 项目的基本约定,也是答辩时容易被打分老师追问的点。
Controller 返回的统一结构一般是这样:code表示业务状态码(200 成功、401 未登录、500 异常)、msg是提示信息、data是业务数据。小程序端和后端约定好这套结构,前端封装的wx.request才能统一处理错误。
路由设计上,患者相关的接口一般放在/patient/**路径下,管理端接口放在/admin/**下,用拦截器做登录态校验。@RequestMapping是 SSM 里最常用的一组注解,类上写路径前缀,方法上写具体路径和请求方式。拦截器里校验每个请求带不带合法 token,不带就返回 401,小程序端收到 401 后自动跳登录页,这个闭环比在每个 Controller 方法里手动判断要省事得多。
4.2 预约的核心逻辑:事务、余号检查与状态机流转
提交预约是整个系统最核心的一个接口,也是答辩时最容易问到的业务点。这个接口要考虑的不只是 insert 一条记录,还包括三个前置条件:患者存在、医生存在、当前时段有余号。
先看 Service 层的核心代码,我按这套项目的典型写法整理过:
@Service public class AppointmentService { @Autowired private AppointmentMapper appointmentMapper; @Autowired private DoctorInfoMapper doctorInfoMapper; @Autowired private UserMapper userMapper; @Transactional(rollbackFor = Exception.class) public AppointmentResult submitAppointment(AppointmentRequest request) { // 1. 校验患者身份,role 必须是 3(患者) User patient = userMapper.selectById(request.getPatientId()); if (patient == null || !Objects.equals(patient.getRole(), 3)) { return AppointmentResult.fail("患者账号不存在"); } // 2. 校验医生是否存在 DoctorInfo doctor = doctorInfoMapper.selectById(request.getDoctorId()); if (doctor == null) { return AppointmentResult.fail("所选医生不存在"); } // 3. 余号检查:统计该医生当天该时段已预约数量 int used = appointmentMapper.countByDoctorAndSlot( request.getDoctorId(), request.getAppointmentDate(), request.getTimeSlot()); if (used >= doctor.getMaxAppointments()) { return AppointmentResult.fail("该时段号源已满,请选择其他时段"); } // 4. 写预约记录,初始状态为 0 待就诊 Appointment appointment = new Appointment(); appointment.setPatientId(request.getPatientId()); appointment.setDoctorId(request.getDoctorId()); appointment.setAppointmentDate(request.getAppointmentDate()); appointment.setTimeSlot(request.getTimeSlot()); appointment.setStatus(0); appointmentMapper.insert(appointment); return AppointmentResult.ok(appointment.getId()); } }这段代码的逻辑说明分四步。第一步和第二步是基础校验,防止前端传了不存在的 ID 导致外键错误。第三步是余号检查,countByDoctorAndSlot这条 SQL 统计的是该医生、该日期、该时段所有状态不是「已取消」的预约数量,数量达到max_appointments就返回号源已满。
@Transactional(rollbackFor = Exception.class)的作用是把校验和插入包在同一个事务里。注意rollbackFor指定了只对Exception.class回滚,如果不写这个参数,Spring 默认只在抛出 RuntimeException 时才回滚,checked exception 是不会触发的。很多同学在这个细节上翻过车。
这里有个需要认清的边界:先查后插这种写法在并发高的时候会有超卖风险,两个请求同时查到used = 19、max = 20,然后都走到插入,号源就变成 21 个了。毕业设计答辩如果被问到,可以如实说这是课设级别的方案,生产环境需要加数据库行锁或乐观锁。进阶做法在第 6 章讲。
4.3 取消预约和管理员审核的状态推进逻辑
取消预约不是直接把状态改成「已取消」,而是先进入「待取消」状态,等管理员审核。对应的逻辑是:患者发起取消申请,写入cancel_application表,同时把appointment的状态从0改为2。管理员在后台审核,审核通过就把预约状态改成3,驳回则改回0。
@Transactional(rollbackFor = Exception.class) public AppointmentResult cancelAppointment(CancelRequest request) { // 1. 查预约记录,校验归属 Appointment appointment = appointmentMapper.selectById(request.getAppointmentId()); if (appointment == null) { return AppointmentResult.fail("预约记录不存在"); } if (appointment.getPatientId() != request.getPatientId()) { return AppointmentResult.fail("只能取消自己的预约"); } // 2. 判断状态是否允许取消:只有待就诊状态才能发起 if (appointment.getStatus() != 0) { return AppointmentResult.fail("当前状态不允许取消"); } // 3. 写入取消申请,审核状态为待审核 CancelApplication application = new CancelApplication(); application.setAppointmentId(appointment.getId()); application.setPatientId(request.getPatientId()); application.setReason(request.getReason()); cancelApplicationMapper.insert(application); // 4. 预约状态改为 2(待取消) appointment.setStatus(2); appointmentMapper.updateById(appointment); return AppointmentResult.ok(); }这个接口的逻辑要点是状态机约束。第 2 步的判断非常关键,如果患者已经完成就诊(状态 1)或已经在待取消(状态 2),就不能再次发起取消申请。状态机的好处就在这里,每一步流转都有前置状态校验,数据不会乱跳。
管理员的审核接口逻辑类似,区别是操作主体是管理员,审核通过就把预约状态从2改成3,同时记录审核时间到cancel_application表。审核驳回则把预约状态从2改回0,让这个号源重新释放给其他患者。
接口联调阶段,我一般会先用 Postman 把接口逐个跑通,再连小程序。Postman 里只需要注意两点:一是登录接口拿到的 token 要填在请求头的Authorization字段,二是预约接口的日期参数格式要和后端约定一致,用yyyy-MM-dd字符串,避免前端传时间戳后端解析不了。先跑了接口,再回头看小程序端的报错,问题定位会快很多。
5. 避坑经验:从拉取源码到跑通业务流程,最常见的四类问题
5.1 MySQL 连接与驱动版本:启动就报错的两个高频场景
现象:运行1-install.bat导入数据库时报Communications link failure,或者启动后端程序时报The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized,还有一部分项目报Public Key Retrieval is not allowed。
原因:这三种报错本质是一个问题——本机装的是 MySQL 8.x,但项目配置文件里写的还是 5.x 时代的连接参数。MySQL 8.x 的驱动类改成了com.mysql.cj.jdbc.Driver,而且强制要求时区参数。密码加密方式也变了,没有allowPublicKeyRetrieval=true时,部分连接工具会拒绝第一次的密钥请求。
解决:打开后端的 JDBC 配置文件,把 URL 改成下面这种写法:
jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://127.0.0.1:3306/hospital_booking?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&characterEncoding=utf8 jdbc.username=root jdbc.password=你自己的密码改完重启服务。如果还想用 5.7,那就把 MySQL 版本降回去,驱动保持不变。我一般建议直接对着 8.x 调,因为新装的机器大概率是 8.x,让项目适配新版本比要求评委换环境靠谱。
5.2 微信开发者工具的请求问题:本地能通、真机不通
现象:在微信开发者工具里点「编译」,小程序能打开页面,但所有接口都请求失败,控制台报url not in domain list或者request:fail。用开发者工具里的「预览」功能在真机上跑,接口照样不通。
原因:微信小程序有域名白名单校验,正式发布的小程序只能请求 HTTPS 且配置在小程序后台的合法域名。本地开发时没有配域名,就必须在开发者工具右上角的「详情—本地设置」里勾选「不校验合法域名、web-view(业务域名)、TLS 版本以及 HTTPS 证书」。这是本地联调的前提。真机预览时还要注意另一个问题:127.0.0.1在手机上指向的是手机自己,不是你的电脑,所以真机调试必须把请求地址改成电脑的局域网 IP。
解决:本地开发时勾选「不校验合法域名」;真机预览时,把请求封装里的BASE_URL从127.0.0.1改成电脑在局域网里的 IP,比如http://192.168.1.23:8080/hospital,手机和电脑连同一个 Wi-Fi 后再预览。这个 IP 我之前改的时候踩过一次坑,忘了删掉旧构建缓存,结果改了代码没生效,确认一下开发者工具是否重新编译了。
5.3 日期序列化:小程序端显示的预约日期变成一串数字
现象:小程序端拿到预约记录后,日期字段显示成1715673600000这种长数字,或者显示时间只有日期没有时分秒,跟数据库里存的不一样。
原因:后端返给前端的是 JSON,Java 的Date类型经过 Jackson 序列化时,如果没配格式,默认输出时间戳。前端拿到时间戳直接渲染在页面上,自然就是一串数字。如果之前配置过部分字段的@JsonFormat,但新加的字段忘了标,也会出现部分日期正常、部分不正常的情况。
解决:在application.yml里配全局日期格式,比每个字段单独加注解省事得多:
spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8如果项目用的不是 Spring Boot,而是原生 SSM 配置,就在 SpringMVC 的配置文件里加一个全局的 Jackson 格式化器。注意appointmentDate这种只存日期不存时刻的字段,可以用yyyy-MM-dd单独格式化,否则后端Date类型带出来的00:00:00会一并显示,看着别扭。
5.4 管理后台改样式不生效:改了.vue文件但页面没变化
现象:管理后台是 Vue 构建后的静态资源,直接改了源码里的.vue文件,刷新浏览器发现页面还是老样子。
原因:浏览器加载的是构建产物,不是源码。Vue 源码经过 webpack 打包后变成了app.26c1910f.css、app.js这类带 hash 的文件,你改的.vue文件根本没参与构建,或者构建后没有替换到对应的静态资源目录。
解决:如果需要改管理后台页面,先找到 Vue 源码工程,改完跑3-build.bat重新构建,构建产物会覆盖旧的静态资源。改之前一定先把要改的文件复制一份.bak——这套工程里已经存在的IndexAsideStatic.vue.bak这类文件就是这么来的。直接改.bak文件是无效的,它只是留底,浏览器不认识它。另外一个常被忽略的点是浏览器缓存,构建后如果页面没变,Ctrl + Shift + R 强刷一次,不然很容易误判是构建失败了。
6. 部署验证与扩展:按一条完整链路跑通后,再往里加真实号源
整套工程拿到手,我建议按顺序做三件事。第一步,打开1-install.bat看它初始化了哪些东西,确认 MySQL 版本、数据库脚本路径、Maven 仓库路径没问题,然后执行。第二步,执行2-run.bat启动后端,留意 Tomcat 启动日志里有没有报错,端口是不是 8080。第三步,执行3-build.bat构建管理后台,构建完把静态资源部署到后端能访问到的目录。三步走完,系统应该是「管理后台浏览器可登录、小程序端可请求接口」的状态。
跑通之后,手动走一遍完整业务链路来验证系统没有隐藏问题。用患者账号登录小程序,从首页选科室、选医生、选日期时段,提交预约;然后到「我的预约」里看到这条记录,发起取消申请;再到管理后台的「取消预约申请管理」里审核这条申请。走完这轮,核心的状态流转就全部覆盖了。
验证完基础流程,再考虑把项目往外扩一步。号源管理目前是每个时段一个固定总数,实际医院是医生排班后才放号,可以加一张排班表,让每个医生每周的号源单独配置;预约并发的问题,可以在appointment表加一个version字段做乐观锁,update ... where version = ?,更新失败就提示用户稍后重试;小程序端要正式上线,还需要把测试用的 appid 换成自己注册的小程序账号,并配置合法域名。这三个方向都是答辩时能讲出深度的加分点,工作量也都在可控范围内。
从那以后我每次拿到陌生毕设工程,都会强制走一遍「看数据库脚本 → 看配置文件 → 看启动脚本」的流程,日志打开成 debug 级别再动手跑,而不是直接双击 bat 看它转圈。这套流程帮我少踩了大半的坑。希望帮到你。
本文还有配套的精品资源,点击获取