做这个预约挂号系统的时候,正好赶上前后端分离方案在校园项目里普及,SpringBoot2 配 Vue3 已经成了毕业生做课题的标配组合。整套系统跑下来,从数据库设计、后端接口、前端页面到最终部署,花了大半个月时间。今天把完整的实现过程、核心代码思路、踩过的坑一次性整理出来,给正在做类似 Java Web 项目的朋友一个参考。
这个项目全称是"文理医院预约挂号系统",前端 Vue3 + Element Plus,后端 SpringBoot2 + MyBatis-Plus,数据库用的 MySQL8.0,完全前后端分离。功能覆盖了患者端完整的预约流程:注册登录、科室科室列表、医生排班查看、在线选号预约、取消预约;管理端就是科室维护、医生信息管理、排班管理、预约记录统计。本文适合正在做毕业设计、课程设计,或者想练手完整前后端分离项目的人阅读。
1. 项目概览与技术选型:为什么是这套组合
1.1 系统需求拆解
医院预约挂号这件事,流程表面看着简单,真正拆开需求其实涉及三个角色:患者、医生、管理员。患者要能注册登录、按科室找医生、看医生某天的出诊排班、选择时间段锁定号源、在个人中心看到自己的预约记录并能取消预约。医生需要查看自己的排班表、查看哪些患者预约了自己。管理员则是维护科室信息、添加医生账号、给医生排班、统计每天的预约数量。
这三个角色的权限差异很明显,所以认证授权是必须考虑的,不能只做登录不管权限。我当时把用户表里加了一个 role 字段,0 表示患者,1 表示医生,2 表示管理员,后端接口统一做拦截校验,简单但也够用。
从开发角度来看,这个系统最核心的难点不是 CRUD,而是号源的处理。一个医生某天某个时段只有 20 个号,两个人同时点击预约,系统必须保证不会出现超卖。这个点在后面我会单独拿一节来讲,这是整个系统里唯一真正需要并发控制的地方。
1.2 技术栈选择背后的思考
先说后端。SpringBoot2 目前依然是大部分 Java Web 课程和企业存量项目的绝对主力,网上资料最全,遇到问题基本都能搜到解法。SpringBoot3 虽然也稳定了,但很多教程和插件还停留在 2.x,做校园项目没必要冒险。选 SpringBoot 2.7.x 版本比较稳妥,JDK 用 1.8 或者 11 都行。
MyBatis-Plus 的理由更实在。MyBatis 传统写法里,每个表的增删改查都要写 mapper 接口和 XML,项目做到一半你就会发现大量时间用在写重复代码上。MyBatis-Plus 内置了 BaseMapper,单表的 CRUD 直接调用现成方法,分页有插件,代码生成器可以秒出 entity、mapper、service 这些基础层代码。做预约挂号这种以单表操作为主的系统,它能省掉至少三分之一的工作量。
数据库选 MySQL8.0 不用多说,这是当前主流版本。但 MySQL8 有几个地方和 5.7 不一样,比如驱动名变了,默认密码加密方式是 caching_sha2_password,时区要求更严格,连接池的 URL 需要显式写 serverTimezone,这些我在后面部署环节会展开讲。
前端选 Vue3 而不是 Vue2,核心原因是 Element Plus 只支持 Vue3,而且 Vue3 的组合式 API 写业务代码确实比选项式 API 清爽。选题系统页面不算复杂,但涉及多角色不同视图,用组合式 API 按功能组织代码,后期维护起来比拆到 data/methods 里方便。状态管理用的 Pinia,比 Vuex 简单,没有 mutations 的概念,直接改 state 就行。
1.3 整体目录结构与模块划分
后端用传统的分层结构,controller、service、mapper、entity、config 五层。controller 只管接收参数和返回结果,业务逻辑全部放 service 层,mapper 层用 MyBatis-Plus 的 BaseMapper。另外加了一个 common 包放统一返回结果类 Result、自定义异常类、JWT 工具类、拦截器。这样项目虽然小,但结构清晰,后面加功能不会乱。
前端目录就是 Vue3 标准 Vite 工程结构:views 下面按 patient、admin、doctor 三个角色分目录,api 放 axios 请求封装,router 里写带角色守卫的路由配置,store 用两个 Pinia store 分别存用户信息和全局通用状态。
模块划分上,后端按业务域拆:auth(登录注册)、department(科室)、doctor(医生)、schedule(排班)、appointment(预约)、admin(管理端操作)。每个业务域保持独立的 controller、service、entity,这样接口路径也好规划,比如 /api/patient/appointment/list 和 /api/admin/schedule/physical 各自职责明确。
2. 数据库设计与后端核心逻辑
2.1 核心表结构与字段设计
数据库脚本我用 Navicat 手动建的,然后导出的 SQL,命名统一小写,一共五张表:sys_user、department、doctor、schedule、appointment,加上一张业务需要的字典表不是必须的,这里就不占篇幅了。先看用户表,它承载三种角色。
sys_user 表核心字段是 id、username、password、real_name、phone、role。password 存储的是 MD5 加密后的值,千万别存明文,这是最基本的底线。加密方式我用的是 Spring 自带的 DigestUtils.md5DigestAsHex,虽然安全性不算最强,但校园项目足够,真要商用可以换 BCrypt,思路是同样的,只是换一个工具类的事。role 用 tinyint 类型,0 患者、1 医生、2 管理员。
再说 schedule 排班表,它连接医生和患者,是预约系统的核心。字段包括 id、doctor_id、schedule_date、time_slot、total、remaining。time_slot 字段我用 int 值表示时段,因为不用数据库的 datetime 类型,前端会根据这个数字对应显示"上午/下午/晚间",这样编辑排班时方便,显示时也灵活。total 是这个时段总的号源数,remaining 是剩余号数,每次预约成功 remaining 就减一,取消预约就加一。这里特别设计了乐观锁控制,后面细说。
appointment 预约表要记录每一次成功的预约。字段有 id、patient_id、doctor_id、schedule_id、appointment_date、time_slot、status、create_time。这里有个细节需要注意,我在表里冗余了 doctor_id、appointment_date 和 time_slot,理论上通过 schedule_id 可以关联出来,但如果不冗余,每次查列表就要 join 两张表,而很多场景只需要简单展示,冗余字段反而更高效。status 字段用 varchar 存字符串,待就诊/已完成/已取消三种状态,可读性好,开发调试也直观。
2.2 预约挂号的并发控制:从一次超卖事故说起
预约系统最常见的坑就是超卖。初期我在 service 层写了一版最直观的逻辑:先查询 schedule 表的 remaining,判断大于 0,然后执行 update 语句减一。这个逻辑单机单线程跑没任何问题,但一旦有两个人同时点击预约,就会出现 A 查询到 remaining=1,B 也查询到 remaining=1,然后两个人都通过了判断,分别插入预约记录,号源变成 -1。这就是典型的并发超卖。
解决思路其实很经典,把判断和更新合并成一条 SQL 的原子操作。核心 SQL 是这样:
UPDATE schedule SET remaining = remaining - 1 WHERE id = #{scheduleId} AND remaining > 0这条语句利用了数据库行锁,只要 remaining 大于 0,更新就一定会成功,且影响行数为 1。如果影响行数等于 0,说明剩余号源已经没了,直接返回"该时段号源已约满"。这样就保证了不会出现剩余号数为负数的情况。
配合这个方案,还要加一张表级别的约束来防同一个患者重复预约同一 schedule。在 appointment 表上建唯一索引:
ALTER TABLE appointment ADD UNIQUE KEY uk_patient_schedule (patient_id, schedule_id);这样即使同一患者快速点了两次预约,第二次插入会因为唯一索引报错,代码捕获 DuplicateKeyException 后返回"您已预约过该时段"。这两种手段配合,预约逻辑就稳了。
取消预约的逻辑是反向的,要先把 appointment 记录的状态改成已取消,然后对 schedule 表执行 remaining = remaining + 1。这里要注意顺序,先改预约状态,再加号源,和新增预约时先减号源再插记录是一个道理,保持数据一致性。
这里还要提一下事务控制。预约方法我在 service 层加了 @Transactional 注解,否则中途抛异常时剩余号源已经减了,但预约记录没插进去,数据就不一致了。这个注解加在 public 方法上,默认遇到 RuntimeException 就回滚,足够覆盖业务场景。
2.3 JWT 认证与权限控制
因为系统有角色区分,接口必须知道请求方是谁、是什么角色。我用 JWT 做无状态认证,原理是把用户 id 和角色加密成一个 token 字符串,前端每次请求在 Header 里带上,后端在拦截器里解析校验。这个方案不需要在服务端存会话,前端拿到 token 想存哪都行,非常适合前后端分离。
JWT 实现用的是 java-jwt 库,生成 token 时设置过期时间,我设的 24 小时。拦截器继承 HandlerInterceptorAdapter,在 preHandle 里放行登录注册接口,其他接口统一从请求头取 token。取不到就返回 401,解析失败说明 token 过期或伪造,同样返回 401。
角色权限我用了登录后再校验的方式,在 controller 里通过自定义注解 @RequireRole 标记需要的角色,拦截器解析 token 里的 role 后判断是否匹配。比如医生查看排班接口标 @RequireRole(role = 1),患者登录后去访问就会被拦截。这样的好处是角色校验不会散落在业务代码里,每个接口的权限一眼就知道。
实际开发的时候需要注意,JWT 的 secret 不能写死在代码里,至少要放在 application.yml 配置里,正式项目应该用环境变量注入。还有 token 过期后前端要能自动跳转到登录页,这个由 axios 响应拦截器统一处理,收到 401 状态码就清除本地登录信息并跳转 /login。
2.4 MyBatis-Plus 提升开发效率的几个细节
MyBatis-Plus 让整个后端的开发节奏快了很多。team entity、mapper 接口、service 接口和实现类基本不用手写,直接用代码生成器一次性生成。然后针对每个表写条件构造器 QueryWrapper 查询。
比如科室列表查询所有医生,我的 service 实现里直接这样写:
LambdaQueryWrapper<Doctor> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(Doctor::getDepartmentId, deptId); return doctorMapper.selectList(wrapper);LambdaQueryWrapper 的好处是直接用方法的引用写字段,不会出现字符串拼错的情况。这是 MyBatis-Plus 使用中最值得推荐的习惯,所有条件字段都通过对应实体类的 lambda 方法引用,编译期就能检查出字段名错误。
分页查询是预约记录列表的必须功能。使用方式是在 config 里注册一个 MybatisPlusInterceptor,添加 PaginationInnerInterceptor,然后 service 里调用 PageHelper 那样的 selectPage 方法。注意 MySQL 的分页插件必须指定 DbType.MYSQL,不然分页语句生成会出错。刚开始我漏配了 MybatisPlusInterceptor,selectPage 方法返回的数据总数一直是 0,排查了半天才发现是分页插件没有注册。
自动填充功能也是个实用细节。appointment 表的 create_time 字段,我用了 MyBatis-Plus 的 MetaObjectHandler,插入时自动填充当前时间,代码里不用手动 set。这样所有表的创建时间字段都能统一维护,不用每次写重复代码。
3. Vue3 前端实现要点
3.1 前端工程结构与状态管理
前端用的是 Vite 创建的标准工程,目录结构在 src 下分 api、components、router、store、views。构建工具一定选 Vite 而不是 webpack,开发环境下热更新快到飞起,也是 Vue3 官方推荐的方案。
状态管理用 Pinia,理由前面说过了。我建了两个 store,user store 存用户基本信息和 token,登录成功后 setInfo,一些全局的常量也放这里。另一个 store 存预约流程中的选择状态,比如用户选择了科室、医生、日期、时段,这些是跨页面传递的数据,用 Pinia 管理比路由传参干净得多。
Pinia 的写法比 Vuex 直接很多,store 里的 state 可以像普通对象一样直接赋值修改,不需要写 mutation。这让我在做预约流程时省了不少代码量,直接在组件里调用 action 提交预约,然后组件里的状态即时响应。这里说明一下,Pinia 虽然允许直接修改 state,但规范上建议还是通过 action 去操作,保持逻辑可追踪。
路由配置上用了两个层级。第一层是普通路由,包含首页、登录、注册。第二层是带角色的嵌套路由,/patient 下的子路由对应患者中心,/admin 下的子路由对应管理后台。路由守卫在 beforeEach 里做两件事:判断没有 token 的跳转登录页,判断有 token 但路由 requireRole 和用户 role 不匹配的跳转 403 页面或重定向首页。
3.2 页面与交互:从选科室到挂号成功
预约流程的前端交互是整套系统最核心的部分。我分成四个步骤:选科室、选医生、选排班时段、确认并提交预约。
选科室页相对简单,从后端接口拉取科室列表卡片式展示,点击科室跳转到该科室的医生列表页。科室列表接口是 /api/patient/department/list,后端返回所有科室,前端按首字母排序好展示。科室页的几个字段,科室名称、科室简介、科室位置,这些都可以在后台维护。
医生列表页挂了科室 ID 参数,接口返回该科室下的所有医生信息,包括职称、专长简介,同时还会显示医生最近一周是否有排班。为了让用户一眼看到"这个医生今天还能不能约",前端会在医生卡片上做一个红点提示,有排班显示"可预约",没有排班显示"暂无排班"。实现逻辑是后端返回医生列表的同时带了排班数量字段,这是上面说的冗余设计的又一个体现。
选排班时段页是最关键的一步。页面顶部显示医生基本信息,下面是一周的排班日历,每个日期下面有三个时段:上午、下午、晚间,每个时段的卡片上会显示剩余号源。如果 remaining 为 0,卡片置灰并显示"已约满",用户点不了。这里有一个交互细节:前端在渲染时段卡片时,不能把剩余号数直接透传给用户点击前就禁用,因为用户看到有号到提交成功中间仍然可能被其他人抢完,所以最终提交还是要走后端校验,前端置灰只是提升体验的辅助手段。
确认页显示用户选择的医生、科室、日期、时段,用户确认无误后点提交,请求 /api/patient/appointment/submit。后端返回成功信息后跳转到我的预约列表页,这个页面会显示所有历史预约,每条记录上有当前状态,待就诊的预约可以取消,取消后号源自动加回来。整个流程走下来,用户操作的每一步都有明确反馈,不会出现点了没反应的情况。
3.3 Axios 封装与 Vite 代理联调
前端请求后端接口必须做统一封装。我在 utils 下建了 request.js,基于 axios 实例封装,配置了 baseURL、超时时间、请求拦截器、响应拦截器。请求拦截器在每次请求时从 localStorage 取 token,有就加到 header 的 Authorization 字段。响应拦截器统一处理后端返回的数据格式,后端统一返回 { code: 200, data: xxx, message: "success" },code 不是 200 时直接弹 ElMessage 错误提示,401 时清除登录信息跳登录页。
前后端联调时最容易出问题的是跨域。开发环境下我直接用 Vite 的 server.proxy 配置解决,不需要后端配 CORS。vite.config.js 里这样写:
server: { port: 5173, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } }这个配置的作用是,前端所有以 /api 开头请求都会被 Vite 开发服务器转发到 8080 端口,浏览器端看到的请求是同源的,后端也就不存在跨域问题。这个方法比在 SpringBoot 里加 CorsFilter 更干净,生产环境用 Nginx 也能做到一样的效果。
生产环境构建时,前端打包后的 dist 目录里所有接口请求还是 /api 开头的相对路径,部署到 Nginx 时需要配置 location /api 转发到后端服务。这里注意要关闭 changeOrigin 带来的 host 头问题,Nginx 里通过 proxy_set_header Host 处理。当时我第一次部署线上没配 Nginx 转发,页面能打开但所有接口 404,排查了很久才想到是前端请求转发没配置。
3.4 Element Plus 使用经验
Element Plus 是目前 Vue3 最成熟的中后台组件库,预约系统中绝大部分表单、表格、对话框都用它实现。我全量引入了 Element Plus,因为项目组件使用率高,按需引入反而要配置 unplugin-vue-components,对于中小型项目收益不大,全量引入还能省掉不少配置时间。
使用 Element Plus 要注意版本兼容问题,必须保证 element-plus 主版本和 vue 的 peer dependency 匹配。通常的做法是安装时 npm install element-plus --save,然后 main.js 里 use 一下就行。另外组件的样式主题定制走 CSS 变量,项目里做整体风格调整只需要覆盖 :root 里的几个变量就好,不用去改组件源码。
表格和表单是管理后台的主角。医生排班管理用 el-table 展示一周的排班明细,每行一个医生一个日期的数据,操作列提供编辑排班、删除排班的按钮。新增和编辑表单统一用 el-dialog 对话框,内嵌 el-form,表单校验规则用 rules 属性声明,比如时段必填、号源总数必须是正整数。这一套组合拳用熟了以后,后台管理的页面基本半天就能撸完。
4. 环境搭建、部署与文档交付
4.1 MySQL8.0 安装与初始化
MySQL8.0 的安装方安装方式和 5.7 相差不大,但有几个显著的差异点需要单独说明。Windows 下安装包下载的是 msi 格式,安装过程中会让你选加密方式,我第一次安装走了默认的 caching_sha2_password,后来 Java 连接时一直报 Public Key Retrieval is not allowed 错误。解决方法是 JDBC URL 上加上 allowPublicKeyRetrieval=true&useSSL=false,当然也可以在安装时选 Legacy Authentication。对于初学者,安装时直接选兼容的认证方式更省心。
MySQL8 的时区问题也是重点。8.0 默认时区是系统时区,但 JDBC 连接如果不指定 serverTimezone,SpringBoot 启动时会报时区异常。我在 application.yml 里这样配置连接串:
jdbc:mysql://localhost:3306/hospital_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true字符集选 utf8mb4,这是支持 emoji 和特殊字符的必要条件。数据库建库时也要指定 utf8mb4,我之前吃过亏,数据库建库时默认是 latin1,中文存进去全是乱码。
初始化数据库脚本的时候,先创建数据库,再运行项目里提供的 hospital_db.sql 脚本。脚本包含了建表语句和基础数据,基础数据里有管理员账号、演示科室、两个医生、一周的排班记录。确保脚本从头到尾执行成功没有报错,是部署阶段最容易忽略的一步。
4.2 后端配置与打包运行
SpringBoot 项目的配置集中在 application.yml。除了前面说的数据源配置,还有 MyBatis-Plus 的配置、JWT 的 secret、日志级别。MyBatis-Plus 配置项里我开了驼峰映射 map-underscore-to-camel-case: true,这个默认就是开启的,数据库字段的下划线会自动映射到实体类的驼峰属性,不用手动写 resultMap。
项目打包用的是 Maven,标准的 spring-boot-maven-plugin。在项目根目录执行 mvn clean package -DskipTests,构建出的 jar 包在 target 目录下。启动生产环境用 java -jar hospital-0.0.1-SNAPSHOT.jar 命令,默认端口 8080。如果遇到端口冲突,可以在启动命令加 --server.port=8081。
需要注意的是 SpringBoot2 打包时如果用了外置 Tomcat 部署,需要打成 war 包,但 SpringBoot 自带内嵌 Tomcat,打成 jar 包直接运行是更简单的方案。jar 包里包含了静态资源和所有依赖,只要服务器装了 JDK1.8 就能跑。
4.3 前端构建与生产部署
前端打包命令是 npm run build,输出 dist 目录。生产部署最简单的方案是用 Nginx 托管静态文件,同时配置 API 转发。我的 nginx.conf 里关键配置如下:
server { listen 80; server_name localhost; location / { root /var/www/hospital; index index.html; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }这里 attention 的点是 try_files 和 proxy_pass。try_files 是让 Vue Router 的 history 模式刷新时不至于 404,所有的前端路由都回退到 index.html。proxy_pass 负责把 /api 开头的请求转发给后端服务,这样前后端就联通了。生产环境下这样做,就不需要后端再开 CORS 了。
4.4 项目文档清单
这套项目的标题里特意提到了"含文档",我在交付时整理了四个文档。README 讲项目简介、技术栈、快速启动步骤,拿到源码的人跟着 README 就能把项目跑起来。数据库设计文档包含 ER 图、表结构说明、字段含义解释,这个文档对答辩时讲数据库设计很有用。接口文档列出了所有 API 的请求方法、路径、参数、返回示例,用 Apipost 导出的 Markdown 格式。部署文档包含了从零开始的完整部署步骤,包括软件安装、配置修改、常见问题。
写文档的过程其实就是项目复盘的过程,很多开发时不注意的细节在写文档时需要重新整理。包含文档,也是让这套项目能快速复现的重要保障。
5. 开发中踩过的坑与排查实录
5.1 常见问题速查表
记录一下整个开发过程中遇到频率最高的几个问题,每个问题我都先描述了现象,再给排查思路和解决方案,方便遇到同类问题对照解决。
| 问题现象 | 排查方向 | 解决方案 |
|---|---|---|
| 后端启动成功但前端接口全部 404 | 检查 Nginx 代理配置 | 在 nginx 中配置 location /api/ 转发到后端端口 |
| 页面打开正常但接口跨域报错 | 检查前后端端口是否同源 | 开发环境用 Vite proxy,生产环境用 Nginx 转发 |
| MyBatis-Plus 分页返回总数一直是 0 | 检查 MybatisPlusInterceptor 是否注册 | 在 config 类中注册 PaginationInnerInterceptor 并指定 DbType.MYSQL |
| 数据库中文乱码 | 检查数据库字符集和连接 URL | 库、表、连接 URL 全部统一 utf8mb4 |
| MySQL 连接报 Public Key Retrieval is not allowed | 8.0 认证方式变更 | JDBC URL 增加 allowPublicKeyRetrieval=true |
| Vue3 路由刷新后 404 | 路由模式是 history | Nginx 配置 try_files 回退 index.html |
| Element Plus 组件样式不生效 | 确认是否安装了样式依赖 | 确保 element-plus 已安装且 main.js 中挂载了 ElementPlus |
| 预约提交成功但 remaining 没减 | 检查事务是否生效 | service 方法加 @Transactional 注解 |
5.2 几个值得反复强调的细节
第一个是事务和锁的配合。很多人在 service 方法上加了 @Transactional,然后就以为万事大吉了。实际上面说的 UPDATE schedule SET remaining = remaining - 1 WHERE remaining > 0,这个语句在执行时会锁住命中的行,另一个事务的相同 UPDATE 会阻塞等待,配合成功后的 commit 才释放锁。如果没有加 @Transactional,Spring 默认是自动提交模式,每一条 SQL 单独事务,那扣减动作立即生效,但插入预约记录失败时扣减没法回滚,数据就错了。所以这两个东西必须同时存在。
第二个是前端禁用按钮的时机选择。我一开始是用户点击时段卡片后才去校验剩余号源,如果后端返回 0 就弹窗提示。后来优化成了卡片上直接显示剩余数,但这里就有一个体验问题,用户看到有号,点击时才发现已经被抢完,体验很差。前端可以做的优化是,页面加载时获取排班详情缓存起来,用户停留过程中不要频繁轮询,只在提交失败后刷新数据。校园项目并发量不高,这种取舍足够了。
第三个是删除排班时的级联处理。管理员把已有人预约的排班删掉了怎么办?我在删除前做了校验,如果 schedule 表存在 status 为待就诊的预约记录,就不允许删除,提示先处理已有预约。这个校验反而比删除时的级联操作更合理,能避免数据孤立。
还有一个很容易忽略的点是时间存储。排班的日期字段我用的 java.time.LocalDate,时段字段用的 int 表示上午下午晚间。如果一开始图省事用 java.util.Date 来存日期,在查询某一天的所有排班时会遇到时区偏移的诡异问题,排查起来很痛苦。SpringBoot 2.x 默认支持 LocalDate 的 JSON 序列化,配置好日期格式后前端传输是无障碍的。
5.3 这个系统还能怎么扩展
做完了基础版本,其实可以往几个方向继续深化。第一是加支付模块,预约成功后生成订单,模拟在线支付,支付成功后才真正占用号源,那这套系统的业务闭环就更完整了。第二是 Redis 缓存科室列表、医生信息、排班号源,把读多写少的接口缓存起来,也顺便把 Redis 的热点用上。第三是消息推送,预约成功、停诊通知这些场景可以对接短信或邮件服务,让系统更像一个真正运营的产品。
我当时在答辩时说的扩展方向就是这三个,面试官和老师都比较认可,因为这说明了你不只是会写 CRUD,还有对系统演进的理解。当然了,做项目最重要是把基础功能做扎实,扩展方向当做亮点提一下就好,不要为了扩展反而影响主体功能的稳定性。
最后聊两句
做这个预约挂号系统,最大的收获不是用了多新的技术,而是把一个现实的业务场景完整地落成了可运行的代码。从需求分析到数据库设计,从接口开发到页面联调,从本地开发到服务器部署,每一个环节都出现过问题,也都一一解决并沉淀成了经验和踩坑记录。特别是号源并发控制的实现,让"代码要考虑并发"有了切身的体会。
如果你正准备复现这个项目,建议按照我的顺序来:先装好 MySQL8 和数据库脚本,再把 SpringBoot 项目跑起来,用 Postman 把后端接口调通,再启动 Vue3 前端联调。前面把环境问题解决了,后面会顺利得多。另外,遇到问题先看日志,后端看控制台异常堆栈,前端看浏览器 Network 和 Console,大多数报错都能定位。这套系统本身只是一块敲门砖,把里面的设计思路和排错方法理解透了,才算真正拿到手。