简介基于微信平台的医院预约挂号系统小程序源码与说明文档面向需要开发微信小程序挂号系统的开发者、在校生及毕业设计人员。整套包包含前端小程序页面与后台管理功能覆盖用户预约、管理员维护、医生管理、数据库设计及系统测试等完整流程可作为同类项目从需求分析到编码落地的综合参考。资料包共2244个文件约23.93MB核心类型包括951个PHP后台脚本、148个JS逻辑文件、118个Vue后台视图、64个WXML与66个WXSS小程序页面样式、76个JSON配置及SQL数据库脚本另有PNG/JPG图片素材与说明文档结构清晰便于按模块查阅。目前已有4395人在CSDN学习浏览适合希望快速搭建预约挂号系统或撰写相关毕业设计课题的读者。说明文档包含可行性分析、总体设计、数据库概念结构设计、前后台具体实现与测试章节能够辅助理解系统架构并进行二次开发。1. 微信小程序预约挂号先想清楚边界再动手医院预约挂号系统最容易翻车的地方不是界面而是把“小程序前端”和“后台管理端”当成两个项目做。拿到这套源码时我注意到目录里同时存在IndexMain.vue.bak、IndexHeader.vue.bak这类 Vue 组件备份文件说明后台管理端是基于 Vue 生态构建的而小程序端走的是微信原生或 uni-app 路线。前端和后端之间只靠 JSON 接口通信数据落在 MySQL 里整体是非常典型的 B/S 架构。这套系统的价值点在于“双端权限分离”患者通过微信小程序完成注册、科室查询、号源预约与取消管理员和医生分别通过 Web 后台管理排班、处理就诊记录。单纯写一个能跑通的 CRUD 不难难的是把号源状态、预约锁定、医生排班这些时序关系处理干净。本文会从数据模型、接口契约、PHP 后端实现、Windows 一键部署四个角度拆开讲最后给出小程序审核和并发验证的实战建议。适合正在做课程设计、毕设或者想快速搭一套医疗预约 Demo 后二次开发的从业者。2. 数据表设计与接口契约先把号源逻辑钉死2.1 为什么预约系统必须做“号源快照”很多课程设计把预约表设计成简单的appointment(id, doctor_id, patient_id, time)上线后就会遇到两个经典问题第一医生排班表改了已预约记录却还指向旧时间第二两个患者同时抢最后一个号后提交的请求把前一个覆盖掉。这套源码里的做法是把排班表和预约明细分开排班表保存“某医生某时段剩余号数”预约表保存“哪个患者占用了哪个排班片段”。这样退号、改签都只需要操作两张表的事务。数据库里核心表建议按下面这套结构来设计与源码的doc_hospital数据库对应 -- 排班表 CREATE TABLE schedule ( id int(11) NOT NULL AUTO_INCREMENT, doctor_id int(11) NOT NULL COMMENT 医生ID, work_date date NOT NULL COMMENT 出诊日期, time_slot varchar(20) NOT NULL COMMENT 时段 如 08:00-08:30, total_num int(11) NOT NULL DEFAULT 10 COMMENT 总号数, remain_num int(11) NOT NULL DEFAULT 10 COMMENT 剩余号数, status tinyint(1) NOT NULL DEFAULT 1 COMMENT 1启用 0停用, PRIMARY KEY (id), KEY idx_doctor_date (doctor_id,work_date) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 预约表 CREATE TABLE appointment ( id int(11) NOT NULL AUTO_INCREMENT, order_no varchar(32) NOT NULL COMMENT 订单号, schedule_id int(11) NOT NULL COMMENT 排班ID, patient_id int(11) NOT NULL COMMENT 患者用户ID, appt_status tinyint(1) NOT NULL DEFAULT 0 COMMENT 0待就诊 1已完成 2已取消, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_schedule_patient (schedule_id,patient_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;uk_schedule_patient唯一索引是防止同一患者重复预约同一时段的关键接口层还要用事务包裹“扣号 插入预约”两步操作否则并发下会出现超卖。注意remain_num不要设计成负数更新时加上WHERE remain_num 0条件做乐观锁。2.2 小程序端需要哪几个核心接口前后端分离的核心是接口契约先行。我从源码的 PHP 控制器里整理出下列必选接口状态码统一为0成功 1参数错误 2无权限 3号源不足返回格式全部为{code, msg, data}接口方法参数返回说明科室列表GET/api/dept/list无返回科室树医生排班GET/api/schedule/listdoctor_id, date返回某医生某天所有时段提交预约POST/api/appointment/addschedule_id, patient_id, openid扣号并生成订单号我的预约GET/api/appointment/minepatient_id分页返回预约记录取消预约POST/api/appointment/cancelappointment_id释放号源医生登录POST/api/admin/loginusername, password返回 token小程序端拿到schedule_id后要在前端先做一次本地校验判断remain_num 0才允许用户点击“确认预约”这能减少无效请求。但真正的可靠性仍然要依赖后端事务前端校验只承担体验优化职责。2.3 数据库连接与编码陷阱源码里mysql相关配置集中在config/database.php如果你用 PHP 5.6 的老代码注意mysql_*函数已经废弃建议改成 PDO。我一般这样封装?php $dsn mysql:hostlocalhost;dbnamedoc_hospital;charsetutf8mb4; $options [ PDO::ATTR_ERRMODE PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE PDO::FETCH_ASSOC, ]; $pdo new PDO($dsn, root, your_password, $options);这里的charsetutf8mb4必须显式声明否则微信小程序端提交的 emoji 昵称或特殊符号会报Incorrect string value错误。另外 MySQL 连接时区也要和 PHP 保持一致否则create_time字段会出现 8 小时偏差影响预约记录展示。3. PHP 后端与小程序端联调从 TCP 到弹窗的完整链路3.1 预约接口的事务实现预约挂号最核心的动作是“扣号 写预约记录”。下面是去掉业务冗余后的核心代码我习惯把它放在AppointmentService类里而不是直接写在控制器中?php class AppointmentService { private $pdo; public function __construct($pdo) { $this-pdo $pdo; } public function create($scheduleId, $patientId) { try { $this-pdo-beginTransaction(); // 悲观锁锁定排班行防止并发扣号 $sql SELECT id, remain_num FROM schedule WHERE id ? FOR UPDATE; $stmt $this-pdo-prepare($sql); $stmt-execute([$scheduleId]); $schedule $stmt-fetch(); if (!$schedule || $schedule[remain_num] 0) { throw new Exception(号源不足, 3); } // 扣减号源 $updateSql UPDATE schedule SET remain_num remain_num - 1 WHERE id ?; $this-pdo-prepare($updateSql)-execute([$scheduleId]); // 生成订单号日期 随机串避免主键冲突 $orderNo date(YmdHis) . mt_rand(1000, 9999); // 插入预约记录 $insertSql INSERT INTO appointment (order_no, schedule_id, patient_id) VALUES (?, ?, ?); $this-pdo-prepare($insertSql)-execute([$orderNo, $scheduleId, $patientId]); $this-pdo-commit(); return [order_no $orderNo]; } catch (Exception $e) { $this-pdo-rollBack(); throw $e; } } }这段代码用了SELECT ... FOR UPDATE悲观锁把排班行锁住之后再扣号防止两个请求同时读到remain_num1导致超卖。如果你的并发量不大也可以用UPDATE schedule SET remain_num remain_num - 1 WHERE id ? AND remain_num 0代替后者在单条 SQL 层面保证原子性性能更好。注意订单号不要用uniqid()直接做线上环境容易重复建议加上随机后缀或使用 Redis 自增序列。3.2 小程序端请求封装与登录态微信小程序的wx.request是异步回调风格直接散落在页面里会非常难维护。我习惯先封装一个request.js把 baseURL、token、错误码统一处理// utils/request.js const BASE_URL https://your-domain.com/api; function request(path, method GET, data {}) { return new Promise((resolve, reject) { wx.request({ url: BASE_URL path, method: method, data: data, header: { Content-Type: application/json, Authorization: wx.getStorageSync(token) || }, success: (res) { if (res.data.code 0) { resolve(res.data.data); } else if (res.data.code 2) { wx.navigateTo({ url: /pages/login/login }); reject(res.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 };这里统一拦截了 code2 的无权限状态直接跳转登录页。Authorization头里的 token 是调用wx.login后把code发给后端换取 session_key再按照md5(openid secret)生成的签名。为了快速跑通源码里常常直接把这个签名算法写在UserController里你可以改成 JWT但注意微信小程序端不需要 refresh_token过期后重新走wx.login即可。3.3 预约页面状态机预约页面的按钮状态必须和排班数据强绑定。我建议在onLoad里请求排班接口后用一个selectedSlot对象保存当前选中时段点击“确认预约”时先检查本地时间是否早于排班开始时间然后再调接口// pages/appointment/appointment.js Page({ data: { scheduleList: [], selectedSlot: null, submitting: false }, onLoad(query) { const doctorId query.doctorId; this.fetchSchedule(doctorId, this.getToday()); }, fetchSchedule(doctorId, date) { request(/schedule/list?doctor_id${doctorId}date${date}) .then(list { this.setData({ scheduleList: list }); }); }, selectSlot(e) { const index e.currentTarget.dataset.index; const slot this.data.scheduleList[index]; if (slot.remain_num 0) { wx.showToast({ title: 该时段已约满, icon: none }); return; } this.setData({ selectedSlot: slot }); }, submitOrder() { if (!this.data.selectedSlot) { wx.showToast({ title: 请选择就诊时段, icon: none }); return; } if (this.data.submitting) return; this.setData({ submitting: true }); request(/appointment/add, POST, { schedule_id: this.data.selectedSlot.id, patient_id: wx.getStorageSync(patient_id) }).then(res { wx.showToast({ title: 预约成功, icon: success }); wx.redirectTo({ url: /pages/order-detail/order-detail?order_no${res.order_no} }); }).finally(() { this.setData({ submitting: false }); }); } });submitting标志位防止用户双击按钮产生重复预约。后端虽然已经做了唯一索引兜底但前端这个防抖能减少无谓的报错弹窗。另外注意finally回调在小程序基础库 2.x 之后才支持如果兼容老版本需要改用complete字段。4. 源码里的三个 bat 脚本Windows 环境一键部署是怎么工作的4.1 1-install.bat依赖安装与配置文件初始化源码目录下的1-install.bat、2-run.bat、3-build.bat是典型的 Windows 批处理部署流程。1-install.bat做三件事安装 PHP 依赖、初始化数据库、生成本地配置文件。简化后核心内容如下echo off REM 1-install.bat - 项目依赖安装脚本 cd /d %~dp0 echo [STEP 1] 安装 PHP 扩展依赖... REM 如果项目里有 composer.json 就执行没有则跳过 if exist composer.json ( call composer install --no-dev ) echo [STEP 2] 导入数据库... set DB_HOST127.0.0.1 set DB_USERroot set DB_PASS123456 set DB_NAMEdoc_hospital mysql -h%DB_HOST% -u%DB_USER% -p%DB_PASS% -e CREATE DATABASE IF NOT EXISTS %DB_NAME% DEFAULT CHARSET utf8mb4; mysql -h%DB_HOST% -u%DB_USER% -p%DB_PASS% %DB_NAME% sql/init.sql echo [STEP 3] 复制配置文件... if not exist config/config.php ( copy config/config.sample.php config/config.php ) echo install finished. pause%~dp0表示当前脚本所在目录这样不管从哪个路径调用都能定位到项目根目录。数据库账号密码直接硬编码在 bat 里省事但正式环境建议改为从config.ini读取否则部署到服务器时容易漏改。4.2 2-run.bat启动 PHP 内置服务器课程设计阶段没必要配置 Nginx 或 ApachePHP 内置的 Web Server 足够调试。2-run.bat通常长这样echo off REM 2-run.bat - 启动开发服务器 cd /d %~dp0 set HOST127.0.0.1 set PORT8080 echo Starting PHP server at http://%HOST%:%PORT% php -S %HOST%:%PORT% -t public-t public把 Web 根目录指向public子目录这样index.php可以放在里面而项目代码控制器、模型放在其上一级目录避免被外部直接访问。注意微信小程序真机调试时wx.request不能访问127.0.0.1必须把这里的HOST改成局域网 IP并且手机和电脑连同一个 Wi-Fi。4.3 3-build.bat后台管理端打包与资源清理后台管理端是 Vue 项目3-build.bat其实是调用了npm run build。源码中留下的*.bak文件如IndexMain.vue.bak是开发过程中手动备份的旧版本打包前应当删除否则会混入无用代码echo off REM 3-build.bat - 前端资源构建 cd /d %~dp0\admin call npm install call npm run build REM 清理备份文件避免发布时泄露旧代码 del /q /s *.bak del /q /s *.orig echo build complete. copy /y dist\*.* ..\public\admin\ pausedel /q /s *.bak会递归删除所有行尾为.bak的文件这在交付源码时很重要。很多课程设计项目就是忘记清理这类备份文件导致老师或评审看到前后不一致的半成品代码。如果你需要保留备份建议在打包前拷到其他目录不要留在项目内。5. 并发下单与小程序审核最后一道关5.1 用 ab 模拟并发预约请求医院预约系统最怕的是放号瞬间的高并发。在 Windows 上可以用 Apache 自带的ab工具测试预约接口的极限ab -n 200 -c 20 -p post_data.json -T application/json http://127.0.0.1:8080/api/appointment/add-n 200表示总请求数 200-c 20表示并发数 20。post_data.json里放{schedule_id:1,patient_id:1}。跑完之后重点看两个指标Failed requests是否为 0以及Non-2xx responses的数量。如果出现Failed requests多半是事务回滚或唯一索引冲突需要回看schedule表的remain_num能否保持不为负。更精准的验证是检查数据库一致性SELECT s.id, s.total_num, s.remain_num, COUNT(a.id) AS booked_count FROM schedule s LEFT JOIN appointment a ON a.schedule_id s.id AND a.appt_status ! 2 GROUP BY s.id HAVING s.total_num ! s.remain_num booked_count;如果HAVING语句查出任何一行说明扣号与预约记录对不上事务逻辑有漏洞。正常情况这个查询应该返回空集。注意appt_status ! 2是为了排除已取消的号因为取消预约时remain_num已经加回去了。5.2 小程序真机预览的局域网穿坑开发阶段经常遇到“电脑上接口好好的手机上一片白”的情况。除了把 HOST 改成局域网 IP还要检查微信开发者工具的“不校验合法域名”开关。更隐蔽的问题是微信小程序的请求头里默认带RefererPHP 端如果做了来源域名校验会直接拒绝请求。解决办法是在服务端白名单里加上servicewechat.com后缀或者在调试阶段临时注释掉校验逻辑。5.3 审核避免“测试账号无法打开”的驳回提交微信审核时审核员会用普通微信号打开小程序。如果你的登录逻辑依赖wx.getUserProfile或手机号授权审核环境很可能拿不到真实手机号。源码里建议预置一个“游客模式”未登录用户可以浏览科室和医生排班但点击预约时提醒去登录。这样审核员能顺利走完页面浏览流程通过率明显提高。另外预约成功后的跳转页面一定要能通过 Android 和 iOS 的返回手势回到首页否则会被判为“交互流程中断”。上线前还应该把2-run.bat中的127.0.0.1全部替换为服务器域名或 HTTPS 公网 IP并确认 PHP 日志文件可写——很多新手在服务器上看到500却不知为何就是在php.ini里没开display_errors还不如把这行配置直接写到2-run.bat里php -d display_errors1 -S 0.0.0.0:8080 -t public这样本地排错时能直接看到 PHP 报错信息。本文还有配套的精品资源点击获取