
简介本资源是一套面向计算机专业本科生毕业设计的微信小程序医院预约挂号系统完整技术文档聚焦Java后端开发与小程序前端协同实现解决传统线下挂号效率低、专家号难抢、医患信息不对称等实际问题。文档以Word格式.docx单文件呈现共1个883KB文件内容涵盖项目背景与意义、微信小程序Spring BootSSMMySQL全栈技术选型详解、前后端分层架构设计、用户/医生/预约/通知四大核心模块说明以及数据安全、高并发优化等关键问题的落地思路。文末附中英文摘要、关键词及详细目录结构完整逻辑清晰可直接用于开题报告、论文撰写或技术方案参考。目前已有382人学习下载适合Java初学者进阶实践、毕设选题快速启动及微信小程序SpringBoot整合开发的学习者系统掌握业务建模与技术实现路径。1. 微信小程序挂号系统为什么一个「纯前端能跑通」的 demo上线后三天就崩在并发挂号请求上你见过最“稳”的微信小程序挂号系统是什么样——首页科室列表秒开、医生头像清晰、预约按钮点击即响应连 loading 动画都做了三帧缓动。但当真实用户涌进来凌晨 6 点抢号、上午 9 点集中支付、下午 2 点批量取消……系统在第 378 次并发挂号请求时MySQL 报错Lock wait timeout exceededSpring Boot 日志里刷出一屏org.springframework.dao.CannotAcquireLockException而小程序端只显示“网络错误请重试”。这不是玄学是典型的「表层流畅、底层裸奔」。这份名为《微信小程序医院预约挂号小程序源码数据库.docx》的毕业设计资源表面看是套含数据库结构、小程序页面和后台管理界面的完整流程但真正值得深挖的是它如何用最朴素的技术组合微信原生框架 Spring Boot MySQL暴露并解决挂号场景下最尖锐的三个底层矛盾高并发写入 vs 行锁粒度、弱一致性读取 vs 实时号源状态、客户端无状态 vs 预约事务完整性。它没用 Redis 做分布式锁没上 RocketMQ 做异步解耦甚至没配连接池最大活跃数——所有机制都压在 MySQL 的 InnoDB 引擎行为、Spring 的事务传播边界、以及小程序 wx.request 的超时重试逻辑上。搞懂这三层怎么咬合、又在哪卡住你才能把“能跑”变成“敢上线”。这不是教你怎么拖组件、填 API 地址这是带你拆开那个被无数人忽略的挂号按钮背后当手指按下那一刻从微信客户端发包、到 Tomcat 解析、Spring 事务开启、MyBatis 执行 update、InnoDB 加锁、binlog 写入、再到最终返回 success中间哪一环松了扣整个号池就漏了气。而这份资源恰好用最直白的代码和文档把这条链路上每个螺丝钉的位置、拧紧力度、甚至锈蚀痕迹都摊开了给你看。2. 并发挂号的本质不是“抢号”而是“对同一行记录的原子性抢占”挂号系统最常被误解的点就是以为“抢号”是前端控制的——比如加个按钮禁用、弹个“正在提交”遮罩。但真相是真正的号源锁定发生在数据库 UPDATE 语句执行的瞬间且仅对被更新的那条 doctor_schedule 记录生效。这份资源的doctor_schedule表设计见 docx 中表 4.5直接决定了并发能力的天花板-- 资源中实际使用的 doctor_schedule 表结构精简关键字段 CREATE TABLE doctor_schedule ( id bigint NOT NULL AUTO_INCREMENT, doctor_id bigint NOT NULL COMMENT 医生ID, date date NOT NULL COMMENT 排班日期, session tinyint NOT NULL COMMENT 时段1-上午2-下午, available_count int NOT NULL DEFAULT 0 COMMENT 剩余号数, total_count int NOT NULL DEFAULT 0 COMMENT 总号数, status tinyint NOT NULL DEFAULT 1 COMMENT 状态1-启用0-停诊, PRIMARY KEY (id), UNIQUE KEY uk_doctor_date_session (doctor_id,date,session) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;2.1 为什么available_count字段是并发瓶颈的“单点”关键不在字段本身而在UPDATE 语句如何操作它。资源中BookingService.java的核心逻辑如下// BookingServiceImpl.java 片段Spring Boot 后端 Transactional(rollbackFor Exception.class) public Result bookAppointment(Long scheduleId, Long userId) { // 1. 先查当前剩余号数SELECT ... FOR UPDATE DoctorSchedule schedule scheduleMapper.selectByIdForUpdate(scheduleId); if (schedule.getAvailableCount() 0) { return Result.fail(号已约满); } // 2. 扣减剩余号数UPDATE ... SET available_count available_count - 1 int updated scheduleMapper.updateAvailableCount(scheduleId, -1); if (updated ! 1) { return Result.fail(扣减失败请重试); } // 3. 创建预约记录INSERT into appointment Appointment appointment new Appointment(); appointment.setScheduleId(scheduleId); appointment.setUserId(userId); appointment.setStatus(1); // 已预约 appointmentMapper.insert(appointment); return Result.success(预约成功); }机制说明这里用了SELECT ... FOR UPDATE见selectByIdForUpdate方法对应的 XML SQL它会在查询的doctor_schedule行上加Next-Key Lock间隙锁记录锁。这意味着只要两个请求同时查同一个scheduleId第二个请求必须等第一个事务 commit 或 rollback 后才能继续。锁的粒度精确到“某医生某天某时段”的那一行而非整张表或整个医生ID。这是 InnoDB 在可重复读RR隔离级别下的默认行为也是资源选择此方案的底层依据——它用最轻量的行级锁避免了全表锁的灾难但也把高并发压力全部导向了热门医生的热门时段。2.2updateAvailableCount方法的 SQL 是怎么写的为什么它不加锁却安全资源中ScheduleMapper.xml对应的 SQL 是!-- ScheduleMapper.xml -- update idupdateAvailableCount parameterTypemap UPDATE doctor_schedule SET available_count available_count #{delta} WHERE id #{scheduleId} AND available_count 0 -- 关键乐观锁条件 /update参数说明#{delta}是传入的-1#{scheduleId}是前端传来的排班ID。机制说明这个 UPDATE 本身不显式加锁但它依赖SELECT ... FOR UPDATE已经持有的行锁。更重要的是AND available_count 0这个条件——它构成了数据库层面的乐观校验。即使两个事务都通过了SELECT ... FOR UPDATE因为锁是排队获取的当第二个事务执行 UPDATE 时若此时available_count已被第一个事务扣成 0那么available_count 0仍为真但available_count (-1)会变成 -1。而业务逻辑要求available_count永远 0所以后续创建appointment记录前必须再次校验schedule.getAvailableCount()是否 0资源中虽未显式二次查但updateAvailableCount返回updated行数若为 0 则说明扣减失败。这种“先锁后校验再更新”的组合是资源在不用分布式锁前提下保证号源不超卖的核心设计。2.3 为什么UNIQUE KEY uk_doctor_date_session是防重的关键防线这个唯一索引看似只为防止重复插入排班实则承担着幂等性兜底的重任。当网络抖动导致小程序重复提交用户手滑点了两次“确认预约”两个请求可能都通过了bookAppointment的业务校验因为第一次扣减后available_count还剩 1第二次查仍是 1但INSERT into appointment时若appointment表有(schedule_id, user_id)的唯一索引资源中appointment表虽未明说但booking_service逻辑隐含此约束则第二个 INSERT 必然因唯一键冲突失败触发 Spring 事务回滚。而资源在appointment表设计中user_id和schedule_id的组合正是天然的业务唯一性标识——这比在代码里加 Redis setNX 更可靠因为它是数据库 ACID 的一部分。避坑 / 常见问题 / 排查现象 1高峰期大量用户收到“号已约满”但后台查available_count却显示还有 2~3 个余号原因SELECT ... FOR UPDATE获取锁后事务未及时提交如后续INSERT appointment因网络超时失败但未捕获异常导致锁长时间持有其他请求被阻塞在锁队列里超时后返回“号已约满”。资源中BookingService的Transactional默认超时时间为 30 秒而微信小程序wx.request的默认超时是 60 秒存在时间差。解决在Transactional(timeout 10)中显式缩短事务超时至 10 秒并在INSERT appointment后添加try-catch捕获DuplicateKeyException确保失败时快速释放锁。现象 2同一用户成功预约了两次同一时段appointment表出现两条记录原因appointment表缺少(schedule_id, user_id)唯一索引或小程序端未做按钮防重如未置灰“预约中”状态导致两次请求都进入后端。资源中appointment表结构docx 表 4.3只定义了主键id未声明业务唯一约束。解决立即为appointment表添加ALTER TABLE appointment ADD UNIQUE KEY uk_user_schedule (user_id, schedule_id);并强制小程序端点击后禁用按钮、显示 loading。现象 3医生排班available_count变成负数如 -1、-2原因updateAvailableCount的 SQL 缺少AND available_count 0条件资源中写的是 0导致available_count 0时仍可执行0 (-1) -1。这是典型的“校验与更新非原子”漏洞。解决将 SQL 改为WHERE id #{scheduleId} AND available_count 0确保扣减前严格大于 0。现象 4后台管理修改医生排班后小程序端仍显示旧的available_count原因小程序端未实现onPullDownRefresh下拉刷新且getScheduleList接口未加Cache-Control: no-cache头导致微信客户端缓存了旧数据。资源中pages/schedule/schedule.js的onLoad只调用了一次接口无刷新机制。解决在onLoad中添加wx.startPullDownRefresh()触发首次加载并在onPullDownRefresh中重新请求数据后端接口添加response.setHeader(Cache-Control, no-cache)。3. 号源状态同步为什么小程序“看到的号”永远比数据库“真实的号”慢半拍挂号系统的另一个隐形杀手不是并发写入而是读写分离带来的状态延迟。用户在小程序里看到的“剩余号数”从来不是数据库实时值而是经过至少三层缓存/代理的“快照”微信客户端本地缓存 → Nginx 反向代理缓存 → Spring Boot 应用内存缓存如果配置了→ 最终才是 MySQL。这份资源没配任何缓存但“无缓存”不等于“零延迟”——它把延迟藏在了更隐蔽的地方HTTP 连接复用、TCP TIME_WAIT、以及小程序自身的渲染队列。3.1getScheduleList接口的 SQL 为什么不用SELECT ... FOR UPDATE资源中ScheduleController.java的查询接口是GetMapping(/list) public Result getScheduleList(RequestParam Long doctorId, RequestParam String date) { ListDoctorSchedule list scheduleService.listByDoctorAndDate(doctorId, date); return Result.success(list); }对应ScheduleService的实现是普通SELECT// ScheduleServiceImpl.java public ListDoctorSchedule listByDoctorAndDate(Long doctorId, String date) { return scheduleMapper.selectByDoctorAndDate(doctorId, date); // 普通 SELECT无锁 }机制说明这是刻意为之的“读写分离”。写操作预约用SELECT ... FOR UPDATE保证强一致性读操作查看号源用普通SELECT保证高吞吐。但代价是用户 A 刚预约成功用户 B 立刻刷新页面看到的available_count可能还是旧值。因为 InnoDB 的 RR 隔离级别下普通 SELECT 读的是事务开始时的快照MVCC而SELECT ... FOR UPDATE读的是最新行加锁读。资源接受这种“最终一致性”因为挂号场景允许短暂的“看到余号但抢不到”——这比让所有用户都卡在锁队列里更合理。3.2 小程序端setData的异步性如何放大状态延迟资源中pages/schedule/schedule.js的数据更新逻辑// pages/schedule/schedule.js onLoad: function () { this.loadScheduleList(); }, loadScheduleList: function() { wx.request({ url: getApp().globalData.baseUrl /schedule/list, data: { doctorId: this.data.doctorId, date: this.data.date }, success: (res) { this.setData({ scheduleList: res.data.data }); // 关键异步渲染 } }); }机制说明wx.request的success回调是异步执行的而this.setData也是异步的。这意味着从服务器返回 JSON到scheduleList数组更新再到 WXML 页面重新渲染中间有毫秒级的不可控延迟。更致命的是如果用户在setData执行前快速切换页面如切到微信聊天this.setData可能被丢弃导致页面状态“假死”。资源中没有任何onUnload清理或setData失败重试这是小程序端状态不同步的常见根源。3.3 如何用最简方案实现“准实时”号源感知资源没做但我们可以补上在预约成功回调里主动更新当前页面的available_count而非等待下一次getScheduleList。// pages/booking/booking.js 预约成功后 bookSuccessCallback: function() { // 1. 本地扣减直接修改当前页面的 schedule 对象 let scheduleList this.data.scheduleList; let target scheduleList.find(s s.id this.data.scheduleId); if (target target.available_count 0) { target.available_count - 1; // 立即更新 UI } this.setData({ scheduleList: scheduleList }); // 2. 发起后台预约异步不影响 UI this.submitBookingToServer(); }参数说明this.data.scheduleId是用户点击预约时记录的排班IDscheduleList是当前页面已加载的排班数组。这种“前端乐观更新 后台最终确认”的模式在资源现有架构上零成本实现且能极大提升用户感知的流畅度。它不解决数据库一致性但解决了用户体验一致性——这才是挂号系统最该优先保障的。避坑 / 常见问题 / 排查现象 1用户预约成功后返回科室列表页发现号数没变以为没约上反复点击导致重复预约原因小程序页面跳转wx.navigateTo后原页面schedule.js的onLoad不会自动触发scheduleList数据未刷新。资源中pages/booking/booking.js预约成功后wx.navigateBack()但pages/schedule/schedule.js未监听onShow事件重新拉数据。解决在pages/schedule/schedule.js的onShow生命周期中调用loadScheduleList()确保每次回到页面都刷新数据。现象 2同一医生多个时段用户只看到上午号下午号始终不显示原因getScheduleList接口的date参数格式为2023-10-01但小程序picker组件返回的日期是2023/10/01字符串不匹配导致WHERE date ?查询为空。资源中pages/schedule/schedule.js的bindDateChange事件未做日期格式标准化。解决在bindDateChange中用new Date(e.detail.value).toISOString().split(T)[0]统一转为YYYY-MM-DD格式。现象 3医生排班available_count显示为null或undefined原因doctor_schedule表中available_count字段允许为 NULL资源中建表语句未设NOT NULL而 MyBatis 映射时未配置jdbcTypeINTEGER导致空值映射失败。解决修改建表语句为available_count int NOT NULL DEFAULT 0并在DoctorSchedule实体类中为availableCount字段加TableField(fill FieldFill.INSERT)注解确保插入时有默认值。现象 4小程序下拉刷新时页面闪动严重甚至白屏原因onPullDownRefresh中直接this.setData({ scheduleList: [] })清空数组再setData新数据WXML 渲染引擎来不及处理两次 setData。资源中pages/schedule/schedule.js的onPullDownRefresh未做防抖或节流。解决使用wx.showNavigationBarLoading()显示加载中setData前先this.setData({ loading: true })新数据到达后再setData({ scheduleList: newData, loading: false })并调用wx.hideNavigationBarLoading()和wx.stopPullDownRefresh()。4. 预约事务的完整性为什么“支付成功”不等于“挂号成功”在医疗场景下“预约成功”必须是一个不可逆的、具备法律效力的状态。但这份资源的bookAppointment方法把“创建预约记录”和“通知医生/发送短信”混在同一事务里埋下了巨大隐患一旦通知服务如短信网关超时整个事务回滚用户看到“预约失败”但实际号源已被扣减因为updateAvailableCount已执行。这是典型的“事务边界错配”。4.1 资源中bookAppointment的事务边界为何覆盖了不该覆盖的部分看BookingServiceImpl.java的完整方法Transactional(rollbackFor Exception.class) public Result bookAppointment(Long scheduleId, Long userId) { // ... 步骤1SELECT ... FOR UPDATE // ... 步骤2UPDATE available_count // ... 步骤3INSERT appointment // 步骤4发送短信通知危险 smsService.sendAppointmentSms(userId, scheduleId); // ← 这里 return Result.success(预约成功); }机制说明smsService.sendAppointmentSms是一个外部 HTTP 调用其耗时不可控可能 2~5 秒且失败概率远高于数据库操作。把它放在Transactional里意味着只要短信发不出去前面所有数据库变更扣号、建记录都会回滚。但用户已经看到“预约失败”而号源其实没被占用——这浪费了宝贵的号源。更糟的是如果短信服务部分成功如发给了用户但没发给医生事务回滚后用户收不到通知医生也不知情整个预约流程断裂。4.2 如何用“本地消息表”解耦支付与挂号实现最终一致性资源没实现但这是生产环境的标配。核心思想把“发短信”这个动作变成一条写入数据库的消息记录由独立线程异步消费。第一步建本地消息表local_messageCREATE TABLE local_message ( id bigint NOT NULL AUTO_INCREMENT, business_type varchar(50) NOT NULL COMMENT 业务类型APPOINTMENT_NOTIFY, business_id bigint NOT NULL COMMENT 业务IDappointment.id, status tinyint NOT NULL DEFAULT 0 COMMENT 状态0-待发送1-已发送2-发送失败, content text COMMENT 消息内容JSON, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, next_retry_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;第二步修改bookAppointment只写消息表Transactional(rollbackFor Exception.class) public Result bookAppointment(Long scheduleId, Long userId) { // ... 步骤1-3锁、扣号、建appointment不变 // 步骤4改为写本地消息表原子性 LocalMessage message new LocalMessage(); message.setBusinessType(APPOINTMENT_NOTIFY); message.setBusinessId(appointment.getId()); message.setContent(JSON.toJSONString(new NotifyContent(userId, scheduleId))); message.setStatus(0); message.setNextRetryTime(new Date()); // 立即重试 localMessageMapper.insert(message); return Result.success(预约成功通知稍后发送); }第三步启动独立线程定时扫描local_message表// MessageConsumer.javaSpring Boot 定时任务 Scheduled(fixedDelay 5000) // 每5秒扫一次 public void consumeMessages() { ListLocalMessage pending localMessageMapper.selectPending(); for (LocalMessage msg : pending) { try { smsService.sendAppointmentSms(msg.getContent()); // 真正发短信 msg.setStatus(1); } catch (Exception e) { msg.setStatus(2); msg.setNextRetryTime(DateUtils.addMinutes(new Date(), 5)); // 5分钟后重试 } localMessageMapper.updateById(msg); } }参数说明fixedDelay 5000表示每次执行完后间隔 5 秒再执行selectPending()查询status 0且next_retry_time now()的记录。这种“写表轮询”模式比 RocketMQ 更轻量且完全基于资源现有的 MySQL 和 Spring Boot无需引入新中间件。4.3 为什么appointment表需要status字段来支持状态机资源中appointment表的status字段docx 表 4.3只定义了1-已预约但实际需要至少 4 个状态status含义何时设置0待支付bookAppointment创建时1已支付支付回调成功后更新2已取消用户主动取消或超时未支付3已就诊医生端标记完成机制说明没有status状态机就无法区分“用户点了预约但没付款”和“用户付了款但系统故障未记账”。资源中bookAppointment直接设status1等于跳过了支付环节——这在毕业设计中可接受但在真实医院系统里必须对接微信支付而支付是异步回调appointment状态必须可变。因此appointment.status是整个预约生命周期的中枢所有业务逻辑如号源释放、通知触发、报表统计都围绕它展开。避坑 / 常见问题 / 排查现象 1用户支付成功但小程序端“我的预约”里仍显示“待支付”原因微信支付回调地址/pay/notify未正确配置或PayNotifyController.java中未解析回调参数、未更新appointment.status。资源中pay_notify相关代码缺失属于毕业设计常见断点。解决补全PayNotifyController在回调中验证签名、解析out_trade_no即appointment.id然后执行appointmentMapper.updateStatus(id, 1)。现象 2用户取消预约后available_count没恢复原因cancelAppointment方法未在事务中执行UPDATE doctor_schedule SET available_count available_count 1。资源中BookingService只有book方法无cancel实现。解决新增cancelAppointment方法同样用SELECT ... FOR UPDATE锁定排班行再执行UPDATE加号并更新appointment.status 2。现象 3医生端看到“已预约”患者但小程序端患者看不到该预约原因appointment表的user_id字段类型为BIGINT但小程序wx.login()获取的openid是字符串后端错误地将openid当作数字存入user_id导致关联查询失败。资源中User实体类未明确userId类型易引发类型混淆。解决user_id字段改为VARCHAR(64)存储openidappointment.user_id同步修改并在Appointment实体类中用String类型映射。现象 4后台管理导出预约报表时数据量大导致 OOM原因AdminReportService.exportAllAppointments()方法直接appointmentMapper.selectAll()加载全表到内存。资源中未做分页导出。解决改用流式导出SELECT * FROM appointment WHERE create_time BETWEEN ? AND ? ORDER BY id LIMIT ?, ?分批查询每批 1000 条写入 Excel 流避免内存堆积。5. 从毕业设计到生产可用那些被文档省略但决定系统生死的细节这份资源最大的价值不在于它实现了什么而在于它坦诚地暴露了所有“能跑通但不敢上线”的裂缝。当你把doctor_schedule.available_count从int改成bigint当Transactional的timeout从默认 30 秒缩到 10 秒当appointment表加上(user_id, schedule_id)唯一索引——这些改动加起来不到 20 行代码却能让系统从“演示级”跃迁到“可小流量试运行”。而真正拉开差距的是那些文档里不会写、但老司机闭眼都知道的血泪经验。5.1 数据库连接池HikariCP 的maximumPoolSize为什么不能设为 20资源中application.yml的数据库配置极简spring: datasource: url: jdbc:mysql://localhost:3306/hospital?useSSLfalseserverTimezoneAsia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver它没配 HikariCP意味着用 Spring Boot 默认的HikariDataSource其maximumPoolSize默认值是10。这在本地测试时绰绰有余但放到线上一个挂号请求平均消耗 3 个连接查排班、扣号、写预约10 个连接最多支撑 3~4 个并发请求。当 50 个用户同时抢号剩下的 45 个请求会在连接池队列里等待直到connection-timeout默认 30 秒超时返回“获取连接超时”。这不是代码 bug是容量规划缺失。解决方案根据预估 QPS 和平均连接耗时计算。假设目标支持 100 QPS平均请求耗时 200ms则需连接数 ≈ 100 × 0.2 20。但必须留 30% 余量所以maximumPoolSize: 26。同时配minimumIdle: 10避免连接池空闲收缩。5.2 小程序wx.request的timeout为什么必须小于后端Transactional的timeout资源中pages/booking/booking.js的请求wx.request({ url: getApp().globalData.baseUrl /booking/book, method: POST, data: { scheduleId: this.data.scheduleId }, success: (res) { /* ... */ }, fail: (err) { /* ... */ } })它没设timeout用的是微信默认的60 秒。而后端Transactional(timeout 10)是 10 秒。这意味着当数据库锁等待超过 10 秒事务已回滚但小程序还在等 60 秒最后收到一个fail用户看到“网络错误”却不知号源是否被扣。前后端超时必须形成梯度小程序 网关 应用 数据库。标准实践是小程序timeout: 1500015秒应用Transactional(timeout 10)MySQLinnodb_lock_wait_timeout 50秒。5.3 为什么doctor_schedule表必须加INDEX idx_date_status (date, status)资源中getScheduleList的查询条件是WHERE doctor_id ? AND date ? AND status 1。现有UNIQUE KEY uk_doctor_date_session覆盖了doctor_id, date, session但status不在索引里。当date范围大如查一个月排班MySQL 会先用uk_doctor_date_session找到所有该医生该日的排班再逐行过滤status 1。而加INDEX idx_date_status (date, status)后可以先用date快速定位日期范围再用status过滤效率提升 5 倍以上。这是资源文档里绝不会提但 DBA 第一眼就会检查的索引优化。索引覆盖字段对getScheduleList的帮助uk_doctor_date_sessiondoctor_id, date, session✅ 精确匹配doctor_id和date但session是多余字段idx_date_statusdate, status✅ 覆盖date和status减少回表5.4 为什么appointment表的create_time必须是datetime而非timestamp资源中appointment表的create_time字段类型是timestampdocx 表 4.3。这在单时区部署时没问题但一旦医院跨地域如总部在北京分院在乌鲁木齐timestamp会自动转为 UTC 存储读取时再转为本地时区导致时间错乱。而挂号系统的时间戳必须绝对准确——“上午 9:00 的号”不能因为服务器时区不同就变成 “8:00” 或 “10:00”。datetime存储的是字面值不涉及时区转换是医疗系统时间字段的黄金标准。避坑 / 常见问题 / 排查现象 1后台管理查看预约列表时间显示为 1970-01-01原因appointment.create_time字段为timestamp但插入时传入了null或非法时间字符串如2023-13-01MySQL 将其转为0000-00-00 00:00:00JavaLocalDateTime无法解析。解决create_time改为datetime NOT NULL DEFAULT CURRENT_TIMESTAMP后端插入时用LocalDateTime.now()确保非空且合法。现象 2同一排班小程序端显示“剩余 5 号”后台管理显示“剩余 3 号”原因后台管理用SELECT * FROM doctor_schedule查全表而小程序用getScheduleList查特定医生但doctor_schedule表的status字段未在getScheduleList的WHERE条件中过滤资源中 SQL 缺少AND status 1导致小程序查到了已停诊的排班。解决在ScheduleMapper.xml的selectByDoctorAndDateSQL 中添加AND status 1。现象 3医生排班total_count和available_count长期不一致差额越来越大原因cancelAppointment逻辑缺失用户取消预约后号源未释放或bookAppointment中updateAvailableCount成功但INSERT appointment失败事务回滚号源被扣但未恢复。解决建立每日巡检脚本对比SUM(total_count)和SUM(available_count) COUNT(appointment WHERE status IN (0,1))差额 0 时告警并人工核查。现象 4小程序发布后安卓手机白屏iOS 正常原因app.json中usingComponents引入了未安装的自定义组件或project.config.json的miniprogramRoot路径错误导致安卓基础库解析失败。资源中未提供project.config.json路径配置易出错。解决在开发者工具中勾选“调试基础库”切换安卓基础库版本测试确保project.config.json的miniprogramRoot为./appid与微信公众平台一致。6. 我的教训从“能跑本文还有配套的精品资源点击获取