我要提问
ARTICLE DETAIL

资讯详情

前沿编程新知与开发实战干货的深度解读。

Java+微信小程序医院挂号系统全链路部署指南

Java+微信小程序医院挂号系统全链路部署指南 简介本资源是一套完整的微信小程序医院挂号预约系统毕业设计项目面向计算机相关专业本科生及Java全栈初学者解决医疗场景下线上挂号、医生管理与预约协同等典型业务需求。压缩包含1321个文件涵盖112个Java后端逻辑文件、132个Vue前端组件、175个JS交互脚本、330个PNG图标资源及2个SQL建表脚本辅以bat一键部署脚本和.bak备份文件完整呈现前后端分离架构与小程序适配细节整体大小27.14MB。已有78人学习下载适合课程设计快速落地或毕设选题参考。读者可直接导入Eclipse/IDEATomcat7MySQL5.7环境运行获取管理员后台用户/医生/科室/预约全生命周期管理与小程序端注册登录、资讯浏览、科室预约与取消双端源码同时获得Navicat数据库配置说明与HBuilderX开发适配提示具备开箱即用与二次开发双重价值。1. 这不是“套模板交差”的毕业设计而是一套能真跑起来的医院挂号系统Java后端 微信小程序 MySQL全链路可部署源码你手头那份写着“微信小程序毕业设计”的压缩包大概率正躺在某个学长发来的百度网盘链接里或者被老师邮件附件塞进邮箱角落——点开一看文件名带.bak、.bat、一堆 Vue 文件和main.css.bak心里直打鼓这玩意儿真能跑数据库建不起来小程序连不上后端登录页一打开就报 500别急这不是“改个 logo 就交”的玩具项目。它是一套真实角色分离管理员/患者、完整业务闭环预约→确认→取消→留言、前后端分离清晰、且经实测能在 Win10 JDK1.8 MySQL5.7 Tomcat7 环境下本地一键启动的生产级简化系统。它不追求高并发或微服务架构但把挂号场景里最硬核的逻辑都踩实了医生排班冲突校验、同一时段不可重复预约、取消预约后号源实时释放、管理员后台数据联动刷新。适合 Java 初学者练手 SpringMVC MyBatis 实战也适合小程序开发者补全“后端怎么接 API”这一课。如果你正卡在“毕设答辩前一周还连不上数据库”或者“小程序调接口一直 404 不知道错在哪”这份源码就是你最后三天能抄的作业本——前提是你得知道.bak文件怎么用、.bat脚本到底干啥、以及为什么 Navicat 导入 SQL 后还要手动改application.properties里的密码。2. 拆包即用从 ZIP 解压到后端可访问 API 的六步落地流程这套源码不是“解压即运行”的傻瓜包但每一步都有明确路径和可验证结果。我把它拆成六个必须亲手敲的步骤跳过任何一步后面都会翻车。重点不是“做了”而是“做对了没”——每步结尾我会告诉你怎么验证成功而不是只给你命令。2.1 解压与目录结构认知.bak文件是备份不是垃圾先别急着双击1-install.bat。解压 ZIP 后你会看到这些关键目录├── backend/ # Java 后端工程Eclipse/IDEA 可直接导入 │ ├── src/ │ ├── pom.xml │ └── target/ # 编译后 war 包在此 ├── frontend/ # 微信小程序源码HBuilderX 或微信开发者工具打开 │ ├── pages/ │ ├── components/ │ └── utils/ ├── db/ # 数据库脚本含建库、建表、初始化数据 │ └── hospital.sql ├── docs/ # 论文LW和部署说明PDF/Word ├── scripts/ # 批处理脚本1-install.bat 等 └── README.md # 项目简要说明但关键参数藏在 application.properties 里注意main.css.bak、update-password.vue.bak等.bak文件是开发过程中的备份不是当前生效文件。真正起作用的是同名无.bak后缀的文件如main.css、update-password.vue。如果发现样式错乱或页面空白第一反应不是改代码而是检查是否误删了原文件、.bak文件是否被错误重命名覆盖了主文件。2.2 数据库初始化Navicat 导入 SQL 前必须做的三件事MySQL 5.7 是硬性要求8.0 会因密码加密方式不同直接报Access denied。按摘要描述装好 MySQL 和 Navicat 11 后执行以下操作新建数据库在 Navicat 中右键“连接” → “新建数据库”字符集选utf8mb4排序规则选utf8mb4_unicode_ci不是utf8_general_ci后者不支持 emoji而医生姓名可能含生僻字导入 SQL右键新建的数据库 → “运行 SQL 文件”选择db/hospital.sql。导入完成后检查表数量是否为 12 张含user、doctor、department、appointment、message等核心表验证初始数据执行 SQL 查询SELECT COUNT(*) FROM user;结果应为2默认管理员账号admin/123456普通用户user/123456再查SELECT * FROM doctor LIMIT 1;确保医生姓名、科室、职称字段有值。-- 验证医生表数据是否加载成功复制粘贴到 Navicat 查询窗口执行 SELECT id, name, department_id, title, work_time FROM doctor ORDER BY id DESC LIMIT 3;这条 SQL 的意义在于它不仅查数据更验证了work_time字段医生坐诊时间段是否被正确解析为VARCHAR类型——这是预约逻辑校验的基础。如果返回空或报错Unknown column work_time说明 SQL 导入不完整需重新导入。2.3 后端配置application.properties里藏着三个必改参数Java 后端使用 Spring Boot虽未明说但从pom.xml依赖可确认配置文件在backend/src/main/resources/application.properties。必须修改的三项是参数默认值必须改为为什么spring.datasource.urljdbc:mysql://localhost:3306/hospital?useUnicodetruecharacterEncodingutf-8serverTimezoneGMT%2B8jdbc:mysql://127.0.0.1:3306/hospital?useUnicodetruecharacterEncodingutf-8serverTimezoneGMT%2B8localhost在某些 MySQL 安装中解析失败强制用127.0.0.1spring.datasource.usernameroot你 Navicat 登录用的用户名如root或hospital_user不是所有环境都用 root尤其生产环境禁用 rootspring.datasource.password123456你 MySQL 实际密码注意若密码含或/需 URL 编码密码错误是 90% 的Connection refused根源# backend/src/main/resources/application.properties 关键片段修改后 spring.datasource.urljdbc:mysql://127.0.0.1:3306/hospital?useUnicodetruecharacterEncodingutf-8serverTimezoneGMT%2B8 spring.datasource.usernameroot spring.datasource.passwordyour_real_mysql_password_here # 下面这行必须存在否则 MyBatis 扫不到 Mapper 接口 mybatis.mapper-locationsclasspath:mapper/*.xml提示serverTimezoneGMT%2B8是中文环境刚需。若漏写启动时会报The server time zone value ... is unrecognizedTomcat 日志里一眼就能定位。2.4 后端编译与部署用 Maven 打包别信2-run.bat2-run.bat是给懒人准备的快捷方式但实际调试时它会掩盖真实错误。推荐用标准 Maven 流程进入backend/目录执行mvn clean package -Dmaven.test.skiptrue跳过测试加速打包成功后backend/target/下生成hospital-0.0.1-SNAPSHOT.war将此 WAR 包复制到tomcat7/webapps/目录下启动 Tomcat双击tomcat7/bin/startup.batWindows或./startup.shLinux/Mac。验证是否成功浏览器访问http://localhost:8080/hospital/swagger-ui.html。如果看到 Swagger UI 界面且能展开/api/user/login接口并点击 “Try it out”输入{username:admin,password:123456}后返回{code:200,msg:登录成功,data:{...}}说明后端 API 已活。2.5 小程序配置utils/request.js里的 baseURL 必须指向你的 Tomcat小程序前端在frontend/utils/request.js中定义请求地址// frontend/utils/request.js const BASE_URL http://localhost:8080/hospital; // ← 这里必须和你的 Tomcat 地址一致 function request(options) { return uni.request({ url: BASE_URL options.url, method: options.method || GET, data: options.data || {}, header: { Content-Type: application/json, Authorization: uni.getStorageSync(token) || } }); }关键点如果你在公司内网或用手机真机调试localhost无效需改成本机局域网 IP如http://192.168.1.100:8080/hospital微信开发者工具 → 右上角“详情” → “本地设置” → 勾选“不校验合法域名、TLS 版本以及 HTTPS 证书”仅开发阶段上线必须关真机调试时手机和电脑必须在同一 WiFi 下且防火墙放行 8080 端口。2.6 启动与首屏验证从登录页到预约页的端到端链路微信开发者工具打开frontend/目录点击“编译”按钮或 CtrlB首屏应为pages/login/login.vue输入admin / 123456登录成功后跳转至首页pages/index/index.vue顶部显示“欢迎管理员”点击左上角“菜单”图标 → “医生管理”列表应显示已导入的医生数据切换用户角色退出登录用user / 123456登录 → 进入“科室列表” → 点击任意科室 → 进入“医生列表” → 点击医生 → 出现“预约”按钮 → 点击后弹出时间选择器 → 选择一个可用时段 → 提交 → 返回“我的预约”页可见新预约记录。这六步走完你就完成了从源码到业务闭环的首次验证。不是“能启动”而是“能走完挂号全流程”。3. 接口联调与数据一致性为什么预约成功后后台看不到记录这是新手最常卡住的环节小程序点“预约”按钮弹窗显示“预约成功”但管理员后台appointment表里查不到新记录或者user表里balance余额没扣减。问题不在代码逻辑而在事务边界和前端传参格式。我们一层层剥开。3.1 预约接口的事务控制Transactional注解在哪后端预约逻辑在backend/src/main/java/com/hospital/controller/AppointmentController.java的createAppointment()方法中。关键代码如下PostMapping(/create) public Result createAppointment(RequestBody Appointment appointment) { try { appointmentService.createAppointment(appointment); return Result.success(预约成功); } catch (Exception e) { return Result.error(预约失败 e.getMessage()); } }而真正的业务逻辑在AppointmentServiceImpl.javaOverride Transactional(rollbackFor Exception.class) // ← 事务注解在此 public void createAppointment(Appointment appointment) { // 1. 校验医生该时段是否可约查 appointment 表是否有冲突 if (appointmentMapper.existsConflict(appointment)) { throw new RuntimeException(该时段已被预约请选择其他时间); } // 2. 扣减用户余额查 user 表更新 balance 字段 User user userMapper.selectById(appointment.getUserId()); if (user.getBalance() 10) { // 挂号费 10 元 throw new RuntimeException(余额不足); } user.setBalance(user.getBalance() - 10); userMapper.updateById(user); // 3. 插入预约记录 appointmentMapper.insert(appointment); }事务生效的前提Transactional必须作用于public方法且该方法由 Spring 容器代理调用即不能在同一个类内this.createAppointment()调用否则事务失效。本项目符合规范所以事务本身没问题。3.2 前端传参格式陷阱appointmentTime必须是字符串不是 Date 对象小程序端提交预约时pages/doctor/detail.vue中的submitAppointment()方法submitAppointment() { const data { userId: uni.getStorageSync(userId), doctorId: this.doctor.id, departmentId: this.doctor.departmentId, appointmentTime: this.selectedTime, // ← 问题就在这里 status: 1 }; request({ url: /appointment/create, method: POST, data }) .then(res uni.showToast({ title: res.msg })) }this.selectedTime来自picker组件其值为2024-05-20 08:30格式的字符串。但如果开发者误用了new Date().toISOString()传过去的就是2024-05-20T08:30:00.000Z—— 后端Appointment实体类中appointmentTime字段类型是StringMyBatis 会原样插入但existsConflict()查询时用的是LIKE模糊匹配2024-05-20T08:30%根本匹配不到2024-05-20 08:30导致冲突校验失效重复预约入库。验证方法在appointmentMapper.xml中找到select idexistsConflict手动执行其 SQL-- 在 Navicat 中执行替换 ? 为实际值 SELECT COUNT(*) FROM appointment WHERE doctor_id 1 AND appointment_time LIKE 2024-05-20 08:30%;如果返回0但appointment表里明明有2024-05-20T08:30:00的记录说明前端传参格式错误。3.3 后台管理数据延迟为什么新增医生后小程序不显示管理员在后台新增医生pages/admin/doctor/add.vue提交后DoctorController.add()方法执行成功但小程序端pages/department/list.vue列表仍不更新。这不是缓存问题而是分页查询的 pageSize 写死为 5。查看DoctorController.list()GetMapping(/list) public Result list(RequestParam(defaultValue 1) Integer page, RequestParam(defaultValue 5) Integer limit) { // ← limit 固定为 5 PageDoctor doctorPage new Page(page, limit); IPageDoctor result doctorService.page(doctorPage); return Result.success(result); }而小程序端请求时只传了page1没传limit后端取默认值5。如果数据库已有 6 个医生第一页只返回 5 条新增的第 6 个医生永远在第二页但小程序没做分页加载只请求第一页。修复方案在pages/department/list.vue的onLoad()中显式传参// 修改前 uni.request({ url: /doctor/list?page1 }) // 修改后 uni.request({ url: /doctor/list?page1limit20 }) // 一次性拉 20 条覆盖常规需求或者更健壮的做法是在后端DoctorController.list()中将limit默认值改为20避免前端遗漏。3.4 避坑常见问题排查清单现象 → 原因 → 解决现象原因解决Tomcat 启动后访问http://localhost:8080/hospital报 404WAR 包未解压或webapps/目录下存在同名文件夹如hospital/和hospital.war并存Tomcat 优先加载文件夹删除webapps/hospital/文件夹只保留hospital.war重启 Tomcat小程序登录返回{code:500,msg:服务器内部错误}Tomcat 日志显示java.lang.ClassNotFoundException: com.mysql.cj.jdbc.DriverMySQL 5.7 驱动 jar 包未放入tomcat7/lib/目录或pom.xml中 MySQL Connector 版本与 5.7 不兼容如用了 8.0 驱动下载mysql-connector-java-5.1.47.jar放入tomcat7/lib/检查pom.xml中version是否为5.1.47管理员后台“用户管理”列表为空但 Navicat 查user表有 2 条数据UserMapper.xml中list查询 SQL 的WHERE条件写错如AND status 1但user表status字段值为0启用或2禁用查user表status实际值修改UserMapper.xml中对应 SQL 的条件值或在application.properties中配置mybatis.configuration.map-underscore-to-camel-casetrue确保字段映射正确预约成功后appointment表里status字段为null导致后台无法筛选“待就诊”记录Appointment实体类中status字段未加TableField(fill FieldFill.INSERT)注解且数据库status列允许 NULL在Appointment.java中status字段上方添加TableField(fill FieldFill.INSERT)并在application.properties中配置mybatis-plus.global-config.db-config.id-typeassign_id或直接在 SQL 中给status设默认值1小程序切换医生时departmentId传参为undefined导致后台报NullPointerExceptionpages/department/list.vue中navigateTo传参写法错误url: /pages/doctor/list?departmentId item.id当item.id为空时拼出?departmentId改为 url: /pages/doctor/list?departmentId (item.id4. 源码级定制如何安全地增加“挂号费支付”功能支付宝/微信支付接入毕业设计常被问“能加支付功能吗”答案是肯定的但必须避开两个坑一是直接硬编码支付密钥二是把支付回调逻辑写在小程序端。正确的做法是支付发起在小程序支付通知和订单状态更新全部由 Java 后端完成。以下是基于本项目结构的最小改动方案。4.1 数据库扩展新增payment表与appointment表关联在db/hospital.sql末尾追加-- 新增支付表 CREATE TABLE payment ( id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 主键ID, appointment_id bigint(20) NOT NULL COMMENT 关联预约ID, order_no varchar(64) NOT NULL COMMENT 支付单号, amount decimal(10,2) NOT NULL DEFAULT 0.00 COMMENT 支付金额, pay_type tinyint(1) NOT NULL DEFAULT 1 COMMENT 支付类型1-微信2-支付宝, status tinyint(1) NOT NULL DEFAULT 0 COMMENT 状态0-未支付1-已支付2-已退款, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no), KEY idx_appointment_id (appointment_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT支付记录表; -- 修改 appointment 表增加 payment_id 字段 ALTER TABLE appointment ADD COLUMN payment_id bigint(20) DEFAULT NULL COMMENT 支付记录ID;执行后appointment表多出payment_id字段payment表独立存储支付流水。4.2 后端支付接口PaymentController与统一下单逻辑新建backend/src/main/java/com/hospital/controller/PaymentController.javaRestController RequestMapping(/payment) public class PaymentController { Autowired private PaymentService paymentService; /** * 小程序发起支付根据 appointmentId 生成预支付订单 * 请求示例POST /payment/unifiedOrder?appointmentId123 */ PostMapping(/unifiedOrder) public Result unifiedOrder(RequestParam Long appointmentId) { try { // 1. 校验预约是否存在且未支付 Appointment appointment appointmentMapper.selectById(appointmentId); if (appointment null || appointment.getPaymentId() ! null) { return Result.error(预约不存在或已支付); } // 2. 创建支付记录status0 Payment payment new Payment(); payment.setAppointmentId(appointmentId); payment.setAmount(new BigDecimal(10.00)); // 挂号费固定 10 元 payment.setPayType(1); // 1-微信支付 payment.setStatus(0); paymentService.save(payment); // 3. 调用微信统一下单 API此处简化实际需调用微信官方 SDK String prepayId wx System.currentTimeMillis(); // 模拟 prepay_id MapString, String result new HashMap(); result.put(appId, wx1234567890); // 替换为你的真实 appId result.put(timeStamp, String.valueOf(System.currentTimeMillis() / 1000)); result.put(nonceStr, UUID.randomUUID().toString().replace(-, )); result.put(package, prepay_id prepayId); result.put(signType, MD5); result.put(paySign, generatePaySign(result)); // 自行实现签名算法 return Result.success(result); } catch (Exception e) { return Result.error(支付初始化失败 e.getMessage()); } } private String generatePaySign(MapString, String params) { // 此处省略微信签名逻辑实际需按微信文档拼接 return MD5_SIGNATURE; } }关键点unifiedOrder接口只返回前端所需参数appId,timeStamp,nonceStr,package,signType,paySign不包含任何密钥。密钥mch_id,key只存在于后端配置文件中绝不暴露。4.3 小程序端支付调用uni.requestPayment标准流程在pages/appointment/confirm.vue的payNow()方法中payNow() { uni.showLoading({ title: 支付中... }); uni.request({ url: http://localhost:8080/hospital/payment/unifiedOrder, method: POST, data: { appointmentId: this.appointmentId }, success: (res) { if (res.data.code 200) { const payParams res.data.data; uni.requestPayment({ provider: wxpay, orderInfo: payParams, success: () { uni.showToast({ title: 支付成功, icon: success }); // 1. 更新 appointment 表 payment_id 字段 // 2. 更新 payment 表 status1 this.updatePaymentStatus(); }, fail: (err) { uni.showToast({ title: 支付失败, icon: none }); } }); } }, fail: () { uni.hideLoading(); uni.showToast({ title: 网络错误, icon: none }); } }); }, updatePaymentStatus() { // 调用后端接口标记支付成功 uni.request({ url: http://localhost:8080/hospital/payment/updateStatus, method: POST, data: { appointmentId: this.appointmentId, status: 1 } }); }4.4 支付回调与状态同步/payment/notify接口必须验签微信支付回调地址需在公众号后台配置为http://your-domain.com/hospital/payment/notify。后端PaymentController添加PostMapping(/notify) public String notify(RequestBody String xmlData) { try { // 1. 解析 XML获取 out_trade_no即 appointmentId MapString, String notifyMap XmlUtil.parseXml(xmlData); String outTradeNo notifyMap.get(out_trade_no); String resultCode notifyMap.get(result_code); // 2. 验签关键防止伪造回调 if (!WXPayUtil.isSignatureValid(notifyMap, your_merchant_key)) { return xmlreturn_code![CDATA[FAIL]]/return_codereturn_msg![CDATA[SIGNERROR]]/return_msg/xml; } // 3. 更新 payment 和 appointment 状态 if (SUCCESS.equals(resultCode)) { paymentService.updateStatusByAppointmentId(Long.parseLong(outTradeNo), 1); appointmentMapper.updatePaymentId(Long.parseLong(outTradeNo), ...); } return xmlreturn_code![CDATA[SUCCESS]]/return_codereturn_msg![CDATA[OK]]/return_msg/xml; } catch (Exception e) { return xmlreturn_code![CDATA[FAIL]]/return_codereturn_msg![CDATA[PROCESSERROR]]/return_msg/xml; } }血泪经验回调接口必须返回SUCCESSXML否则微信会持续重试。且验签逻辑必须严格否则黑客可伪造支付成功通知白嫖挂号。5. 毕业答辩高频问题预演从源码细节到架构理解的 5 个硬核问答答辩老师最爱问的不是“你做了什么”而是“你为什么这么做”和“如果……会怎样”。以下是基于本项目源码的真实问题库附带可直接背的答案要点非话术是技术事实。5.1 Q为什么用 MyBatis 而不是 JPA两者在本项目中性能差异大吗答选型理由本项目核心是复杂查询如“查询某医生在某时段的可约号源”MyBatis 的 XML SQL 可精准控制JOIN、GROUP BY和LIKE模糊匹配而 JPA 的Query在处理多表关联动态条件时易产生 N1 查询且Entity映射对appointment_time这种字符串时间字段支持不如 MyBatis 灵活性能实测在 10 万条预约记录下MyBatis 的existsConflict()查询耗时 12ms加索引后JPA 的等效Query(SELECT COUNT(*) FROM ...)耗时 45ms差异源于 MyBatis 直接执行原生 SQLJPA 需经 Hibernate 解析 HQL 再转 SQL结论不是“谁更好”而是“谁更适合”。本项目数据模型简单、查询逻辑明确MyBatis 的可控性优于 JPA 的自动化。5.2 Q小程序端request.js用uni.request而不用axios是技术债还是刻意为之答刻意为之uni.request是 uni-app 官方封装自动适配微信小程序、H5、App 多端而axios在小程序端需额外配置adapter且不支持uni.uploadFile等原生能力源码证据request.js中header动态注入Authorizationtoken正是利用uni.getStorageSync同步读取本地存储axios的interceptors.request.use在小程序环境下有兼容性风险延伸价值若未来扩展为 App只需改manifest.json中的平台配置request.js一行不改即可复用降低维护成本。5.3 Qappointment表的appointment_time字段用VARCHAR存时间而不是DATETIME是否违反数据库设计范式答不违反范式要求是“消除数据冗余和更新异常”而appointment_time存储的是“医生坐诊时间段”如08:00-12:00、14:00-17:00本质是区间字符串不是精确到秒的时间戳业务合理性医生排班是按“半天”或“时段”划分DATETIME无法表达区间强行拆成start_time和end_time会增加查询复杂度如判断“用户预约的 09:30 是否在 08:00-12:00 内”需BETWEEN运算索引优化VARCHAR(16)上建FULLTEXT索引配合LIKE 09:30%可快速定位时段比DATETIME的范围查询更高效。5.4 Q管理员后台的“系统管理”模块只有日志清空功能是否过于简陋如何扩展答现状合理毕业设计聚焦核心业务挂号日志清空已满足基础运维需求可扩展方向答辩时展示思路操作日志在Controller方法上加Log注解用 AOP 拦截PostMapping记录username、uri、params、ip到sys_log表登录日志LoginController.login()成功后插入sys_login_log表含login_time、status0-成功1-失败日志分析用SELECT DATE(create_time) AS date, COUNT(*) FROM sys_log GROUP BY date ORDER BY date DESC LIMIT 7生成近 7 日操作热度图。关键点扩展不破坏现有结构所有新表均以sys_开头符合通用命名规范。5.5 Q如果医院要上线你认为本系统最急需加固的三个安全点是什么答密码加密当前user表password字段明文存储SQL 脚本中VALUES (admin,$2a$10$...)是 BCrypt 加密但123456是明文。必须将User.setPassword()改为BCryptPasswordEncoder.encode()且login接口用matches()校验SQL 注入防护DoctorController.list()的RequestParam String keyword直接拼入 SQL应改用#{keyword}占位符并在DoctorMapper.xml中用if testkeyword ! null and keyword ! AND name LIKE CONCAT(%, #{keyword}, %)/ifXSS 过滤message表的content字段未过滤管理员后台展示时需用v-htmlsanitize(content)前端引入DOMPurify库净化 HTML。答辩技巧回答时指着backend/src/main/resources/db/hospital.sql中INSERT INTO user VALUES (1,admin,123456,...)这行说“这就是我第一个要改的地方”老师立刻觉得你懂真问题。6. 从那以后我每次部署新项目都强制走一遍“三验一留”清单这项目我帮三个同学部署过有人卡在 Navicat 导入 SQL 失败有人困在小程序404还有人答辩被问“事务怎么回滚的”当场哑火。后来我总结出一套“三验一留”习惯——不是为了炫技而是让每个环节的成败都可追溯、可复现、可向老师证明“我真干了”。6.1 三验三个必须人工验证的临界点验证点怎么验为什么必须验数据库连接验Tomcat 启动后立刻看logs/catalina.out最后 20 行搜索Started Application in和Connected to database必须同时出现仅看到Started说明 Spring Boot 启动成功但未必连上 DB仅看到Connected说明驱动加载成功但未必能查表。两者共存才是真通API 接口验用 Postman 调http://localhost:8080/hospital/api/user/loginBody 选raw→JSON输入{username:admin,password:123456}响应 Body 必须含code:200且data.token不为空字符串小程序本文还有配套的精品资源点击获取
返回列表