ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于SpringBoot+Vue+MyBatis的医院网站前后端分离实战详解

基于SpringBoot+Vue+MyBatis的医院网站前后端分离实战详解 前后端分离、SpringBoot、Vue、MyBatis、MySQL这几个关键词组合在一起就是当前中小型医院网站项目里最实用的一套技术栈。我最近把一个医院门户网站完整跑通从数据库建模、接口开发、Vue页面搭建到服务器部署整个过程踩了不少坑也沉淀出一套能直接复用的方案。这篇文章按实际开发顺序把项目拆分、表设计、核心接口、前后端联调、部署流程和常见问题完整复盘一遍。无论你是准备做毕业设计还是刚入职想独立负责一个JavaWeb项目这篇内容都能帮你少走弯路。1. 项目整体设计与技术选型1.1 项目定位中小型医院需要什么样的网站系统中小型医院需要的是“对外信息门户加基础预约服务”而不是大型HIS。它要解决的核心痛点是患者能查科室、查医生、看排班、提交在线预约管理员能维护内容。我在立项时没有盲目堆功能而是把主流程收敛为医院介绍、科室导航、专家团队、排班预约、新闻公告、留言反馈。主要页面和接口都围绕这几条线展开。技术选型最终锁定SpringBoot加Vue加MyBatis加MySQL原因很直接Java后端生态成熟Vue做单页应用体验好MyBatis对复杂查询可控MySQL完全能覆盖中小型医院的数据规模。这套组合在中小团队里也比较好招人、好维护。真正做下来我发现它最大的优势是职责清晰前端只负责页面和交互后端只负责提供接口和业务校验数据库只负责存储。排查问题时顺着“页面→接口→SQL”这条线走很快就能定位到问题在哪一层。1.2 为什么不用Thymeleaf或若依这类脚手架有人会问SpringBoot直接用Thymeleaf写页面不是更简单确实不分离的模式开发门槛低部署一个包就能跑。但真正遇到医院网站这种需要频繁调整首页样式、增加交互组件、未来可能再做小程序端或App端的场景Thymeleaf就会越改越累。前端页面每动一处都得重新编译重启后端接口联调效率也很低。Vue负责前端路由和组件化之后页面改动不再影响后端服务两边并行开发互不阻塞这是前后端分离最大的价值。我没用若依这类前后端分离脚手架不是说它不好而是它的代码生成器和内置权限模块对新手理解核心链路有干扰。写这个项目时我更希望让大家看清SpringBoot和Vue之间一条干净完整的通路所以去掉了一切多余的东西。如果你已经熟练掌握了这套核心链路再去用若依提升开发效率不迟但如果一开始就停留在脚手架生成代码很多底层问题反而会被掩盖遇到版本冲突或部署问题无从下手。1.3 适合谁参考这个项目适合三类人。第一类是正在选毕业设计题目的学生前后端分离、SpringBoot、Vue、MyBatis、MySQL这个技术组合本身就是高频评分点做出来的系统也容易演示第二类是初级Java开发想完整经历一遍从建表到上线的流程补上部署运维层面的经验第三类是医院信息科或集成商需要快速搭建一套门户网站给领导演示或内部试用。需要说明的是这个项目不涉及微服务、高并发、大数据它更强调业务闭环和工程规范。如果你想学分布式架构它不是合适的起点。但如果你想把一个中小型业务系统的“增删改查、登录鉴权、预约事务、部署发布”全链路吃透这套源码和教程的匹配度很高。2. 系统功能与数据库设计2.1 前台页面按患者动线拆解前台页面我按患者访问动线来拆。首页是门面放了轮播图、医院简介、重点科室导航、最新公告和出诊动态科室列表支持按分类筛选进入科室详情页能看到科室介绍、擅长领域和医生列表专家团队页展示医生头像、职称、擅长方向和出诊安排预约挂号页是核心用户先选科室再选医生再选日期和时段然后填写就诊人姓名、身份证号、手机号并提交系统返回一个预约编号。新闻公告页展示医院新闻和健康宣教内容最后是一个留言反馈入口方便患者提交建议。这些前台页面和后台管理共用一套登录用户体系只是在权限上做区分。这一点很多医院网站容易忽略前后台各自维护一套账号导致患者登录状态和管理员登录状态互相冲突。共用一套用户表用role字段区分角色逻辑最简单也最符合中小型医院的实际使用习惯。2.2 后台管理模块拆解后台管理按管理员工作习惯分模块。首页仪表盘统计今日预约量、新增留言数、公告数量让管理员一眼看到核心数据科室管理支持增删改查上传科室图片并设置排序医生管理录入医生姓名、职称、头像、擅长方向、简介同时关联所在科室排班管理非常关键可以按周设置每个医生的出诊时段设置号源总数预约管理查看所有预约支持确认、取消操作只有状态为“已确认”的预约才能进入线下就诊流程。公告管理发布新闻公告设置发布时间、发布状态和置顶留言管理负责回复患者留言。权限上后台模块需要管理员角色普通患者角色只能访问前台接口。后台功能不用做得很花哨但流程必须完整尤其是预约状态流转。我在项目里把预约状态定义为待确认、已确认、已完成、已取消后台任何操作都不允许跳状态比如已完成不能直接取消已取消不能改成已确认。这样哪怕操作失误数据也不会乱。2.3 核心表设计与字段说明数据库是整套系统的地基我总共建了八张核心表用户表、科室表、医生表、排班表、预约表、公告表、轮播图表、留言表。用户表存账号、密码、姓名、角色、手机号科室表存科室名称、简介、图片、排序医生表存姓名、职称、头像、擅长、简介和科室外键排班表存医生外键、星期、时段、号源总数、已约数量预约表存预约编号、用户外键、排班外键、就诊人信息、预约日期时段、状态。下面是一个带注释的医生表建表SQL其他表结构类似核心是我统一了字段命名风格主键id、外键xxx_id、时间字段create_time或update_time、状态字段status。CREATE TABLE doctor ( id int NOT NULL AUTO_INCREMENT, department_id int NOT NULL COMMENT 所属科室, name varchar(50) NOT NULL COMMENT 姓名, title varchar(30) DEFAULT NULL COMMENT 职称, avatar varchar(255) DEFAULT NULL COMMENT 头像地址, good_at varchar(500) DEFAULT NULL COMMENT 擅长领域, introduce text COMMENT 个人简介, sort_order int DEFAULT 0 COMMENT 排序, create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, PRIMARY KEY (id), KEY idx_dept (department_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT医生表;所有表的字符集统一使用utf8mb4时间字段统一用datetime状态字段统一用tinyint。这样可以避免中文乱码和状态判断混乱。很多新人喜欢在表里直接存中文状态比如“已确认”“已取消”看起来直观但统计和接口判断都很痛苦改状态时要精确匹配字符串一旦有人多打了个空格就查不到数据。用数字状态配合代码里的枚举常量才是稳妥做法。2.4 关联关系与索引设计要点表关系上科室和医生是一对多医生和排班是一对多预约既关联排班也关联用户。设计时注意不要冗余科室名称到医生表否则后续改科室名称要连带改医生数据。预约表上我建了两类索引一是排班ID加预约日期的联合索引用来支撑时段号源查询和防止重复预约二是预约编号的唯一索引方便用户凭编号查询订单。排班表上建了医生ID和星期几的联合索引支撑后台按医生排班查询。公告表的发布时间索引也很重要列表页默认按发布时间倒序没有索引时数据量到几万条就会开始变慢。虽然这些表数据量不大但规范索引能让系统在数据逐步增长时保持稳定。索引不是越多越好每张表保持在两到三个核心索引就够太多反而会拖慢插入和更新速度。3. SpringBoot后端实现与关键细节3.1 Maven工程结构和数据源配置后端工程用Maven搭建标准SpringBoot结构分为controller、service、mapper、entity、config、common几个包。所有Mapper接口和XML文件在mapper目录下启动类加MapperScan扫描。pom.xml里核心依赖是spring-boot-starter-web、mybatis-spring-boot-starter、mysql-connector-j、lombok。这里必须先说一个最容易踩的坑SpringBoot版本和MyBatis启动器版本必须匹配。SpringBoot 2.7.x配mybatis-spring-boot-starter 2.xSpringBoot 3.x必须用3.x的starter同时javax包要换成jakarta。我见过很多人启动时报“Property sqlSessionFactory or sqlSessionTemplate are required”十有八九就是版本错位。数据源配置用application.ymlurl里必须带serverTimezone、characterEncoding和useSSLfalse否则容易报时区或SSL错误。server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/hospital_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue username: root password: 123456 mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.hospital.entity configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImplMyBatis的map-underscore-to-camel-case一定要开数据库的下划线字段能自动映射到Java驼峰属性省掉大量ResultMap配置。不过连表查询出现字段名冲突时还是要显式写ResultMap别偷懒。Mapper XML文件要确保能被打包进classpath如果pom里没有配置resources默认是可以打包的但如果你在src/main/java下放了XML文件就必须额外配置。3.2 统一返回结果与全局异常处理前后端分离开发时前端需要明确知道请求成功还是失败。我定义了一个Result类包含code、message、data。Controller层不直接返回裸对象或Entity而是包装成Result返回。前端axios拦截器拿到响应后先判断codecode为200才处理数据code为401跳转登录code为500统一弹错误提示。这样页面代码非常干净不会到处判断一堆状态码。配合RestControllerAdvice全局异常处理器在Service里直接抛业务异常由全局处理器转换成Result返回。写这个项目时我一开始在Controller里try catch到处重复后来改成全局异常后Controller代码变得非常简洁只保留参数接收和调用Service。建议你也这么做后期加参数校验、权限校验都会轻松很多。全局异常处理器里至少要处理三种异常参数校验异常、业务异常、未知异常。未知异常不仅要返回统一错误信息还应该把详细堆栈打印到日志方便排查。3.3 登录鉴权与接口权限控制用户登录我使用JWT方案。密码用BCrypt加密存储数据库里不保存明文密码。登录成功后后端生成一个token返回给前端前端存在localStorage里之后每次请求在Authorization头里带上。后端用一个拦截器解析token判断当前用户是否登录。如果是后台管理接口还要额外判断role是否为管理员。拦截器的放行名单需要仔细配置。登录接口、科室列表、医生列表、预约提交等公开接口要放行后台管理接口全部拦截。这里有一个隐蔽坑跨域预检请求是OPTIONS方法如果不放行OPTIONS前端请求会因为CORS失败。很多前后端分离项目联调时遇到“接口报403”但接口本身没问题就是拦截器把OPTIONS请求也拦截了我后来在拦截器里统一放行OPTIONS方法问题才消失。3.4 预约挂号业务的核心实现预约挂号是核心重点在防止号源超卖。实现思路是前端提交科室、医生、日期、时段和就诊人信息后后端先根据排班ID用select ... for update锁住该排班记录检查已约数量是否小于号源总数如果够则插入预约记录并把已约数量加1整个过程放在一个事务里。这里我用悲观锁没有用乐观锁因为中小型医院同时间段预约并发量并不大行锁足够逻辑也最简单。取消预约时先校验预约状态必须是待确认或已确认然后更新排班表的已约数量减1。这个状态校验很重要如果不加已完成的历史预约也可能被取消号源会越减越乱。我在测试阶段就遇到过测试数据里有一条已完成预约点取消后号源数量加了回去导致当天号源超过总数。后来加上状态限定再也没出现过。3.5 MyBatis动态SQL、缓存和TypeHandlerMyBatis部分要想用得好动态SQL是必学技能。后台列表查询时科室、职称、状态都可能为空直接用where加if拼接条件避免出现多余的AND。批量插入排班可以用foreach一次插入一周的数据。持久层我在简单查询上使用注解复杂统计和动态SQL用XML配合map-underscore-to-camel-case自动转换下划线字段。关于缓存项目里我关闭了MyBatis二级缓存原因是医院数据要求实时准确二级缓存会让后台改完数据前台不能立刻看到排查还麻烦。MyBatis默认开启一级缓存SqlSession级别这个基本够用不用特意关。TypeHandler作为扩展点可以用来自定义特殊类型转换比如把List转成逗号分隔字符串、日期字符串和LocalDateTime互转面试和实际开发都能加分。知道这几个场景处理项目里的特殊字段类型就不会手足无措。4. Vue前端实现与联调要点4.1 项目搭建与路由设计前端使用Vue 3加Vite搭建目录划分成api、router、views、components、utils。Vite比Vue CLI启动快配置简单开发体验好。路由采用history模式页面包括首页、科室列表、科室详情、医生列表、预约挂号、新闻列表、留言反馈和后台管理。后台管理需要登录后进入通过路由守卫判断本地token是否存在不存在则跳转登录页。静态路由对这个项目就够用了我不建议一开始就上动态路由因为角色只有管理员和普通用户强制用动态路由只会增加维护成本。等以后要扩展多个角色和菜单权限再重构也不迟。页面UI用了Element Plus组件复用度高例如科室卡片、医生卡片、分页组件都在components目录里。组件拆分的原则是只要在两个以上页面出现的东西就抽成公共组件不让代码重复。4.2 axios封装与请求拦截axios封装是前端联调的基础。我在utils/request.js里创建axios实例设置baseURL为/api超时时间10秒请求拦截器从localStorage里取token放到Authorization头响应拦截器统一处理Result对象。code为200时直接返回data其余code抛异常给页面提示。这样前端业务代码不用关心token和响应结构只需写具体请求函数。例如预约提交接口是这样封装的export function submitAppointment(data) { return request({ url: /appointment/submit, method: post, data: data }) }页面里调用函数拿到预约编号后跳转成功页。表单校验用Element Plus的rules手机号和身份证号用正则校验能在前端拦住很多无效提交减少后端压力。特别是身份证号如果只靠前端提示而不做幂等处理用户重复点击提交会生成多条预约记录。我在提交按钮上加了loading状态提交中禁用按钮同时后端做防重复校验双保险才靠谱。4.3 跨域处理与代理配置跨域问题在开发阶段通过Vite的proxy解决。devServer里把/api代理到http://localhost:8080浏览器看到的请求是同源的不会触发CORS。但在发布阶段Nginx同样需要配置/api反向代理如果直接用前后端分离地址而没有代理跨域很难避免。后端我还是配了CorsFilter作为兜底但生产环境不要用*放行所有来源尤其涉及患者实名信息和预约数据来源白名单越严越好。还有一个细节请求头里自定义了Authorization后端跨域配置必须允许这个头否则预检请求会失败前端在Network里看到一堆OPTIONS请求报错。我查过几次这类问题每次都发现是没有把Authorization头加入allowed headers导致的。4.4 视频宣教与文件上传扩展医院网站的“健康宣教”板块经常需要放视频。Vue播放m3u8视频并不难引入video.js和videojs-contrib-hls插件就能在浏览器里直接播放HLS流不需要额外安装桌面播放器。视频文件建议用MinIO做对象存储SpringBoot集成MinIO只需要引入依赖、创建客户端、封装上传下载方法前端拿到返回的URL后直接给video标签。如果只是为了演示也可以把视频放到后端静态目录通过Nginx映射访问。不管用哪种方案都要注意Bucket的CORS配置否则浏览器播放视频时跨域取不到分片表现就是视频一直转圈或者只有声音没有画面。我在项目文档里专门把这一条写进去后来部署时没有再踩坑。图片资源也是同样思路不要塞进数据库数据库只存URL文件交给对象存储或静态目录。5. 项目部署实操与避坑指南5.1 本地环境准备JDK、Maven、MySQL、Node本地环境先准备好JDK 1.8或11Maven 3.6以上MySQL 5.7或8.0Node 14以上。MySQL安装是很多人第一道坎Windows下安装时选择utf8mb4字符集Linux下用RPM包安装后注意初始化密码和root远程访问配置。连接数据库时如果看到SSL连接相关报错优先在JDBC URL里加上useSSLfalse测试环境可以这样快速绕过生产环境如果安全要求高再研究证书配置。Node方面建议直接装Long Term Support版本避免版本过高导致前端依赖安装失败。Maven配置国内镜像能显著减少依赖下载时间下载依赖报错时先看本地仓库里有没有损坏的.lastUpdated文件把它删掉重新下载。很多Maven问题不是依赖真的下载不了而是本地仓库残留了失败记录Maven会认为这个依赖已经尝试过不会重新下载。5.2 后端打包启动脚本后端部署时我习惯在项目根目录执行mvn clean package -DskipTests生成jar包后放到服务器某个固定目录。用nohup java -jar hospital-server.jar app.log 21 启动日志写到app.log。为了重启方便我写了start.sh和stop.sh两个脚本stop.sh用jps获取进程ID后kill比直接找端口占用再kill省事。生产环境的数据库密码不要在jar里写死启动命令改用--spring.datasource.passwordxxxx传参或者放到环境变量里。如果服务器上8080端口被占修改application.yml的server.port就行。还要记得防火墙或云安全组放行对应端口否则服务起来外部照样访问不了。我踩过一次服务已经在8080监听但浏览器就是访问不了排查半天发现云安全组没放行。这种环境问题最浪费时间所以部署前把端口放行检查一下能省很多事。5.3 前端构建与两种部署方式前端构建执行npm install和npm run build生成dist目录。部署方式有两种。第一种是标准前后端分离dist给Nginx托管Nginx配置location /指向distlocation /api/反向代理到SpringBoot的8080端口。第二种是把dist目录复制到SpringBoot项目的resources/static下直接把前端页面打进jar包访问后端端口就能同时看到页面和接口。第二种方式最简单适合毕设演示或小带宽服务器但前后端耦合在一个进程里扩展不太灵活。我做项目演示时经常用第二种正规部署还是推荐第一种。无论用哪种都需要注意history模式刷新404的问题Nginx配置里必须加try_files $uri $uri/ /index.html;。否则首页能打开点路由内部跳转没问题一旦按F5刷新子路由就会404。server { listen 80; server_name hospital.example.com; root /opt/hospital/dist; index 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; } location / { try_files $uri $uri/ /index.html; } }5.4 生产环境初始化与备份首次上线前先执行项目里的init.sql脚本导入表结构和初始管理员账号。导入后不要急着完事我习惯逐页点一遍首页轮播图是否正常显示科室列表图片是否可访问医生排班是否覆盖本周新闻公告是否按时间倒序预约提交后后台管理能否看到。任何一个环节断掉都要回到底层排查。数据备份我用crontab每天凌晨备份一次MySQL备份文件保留最近7天。医院网站涉及预约和患者信息备份是底线。建议备份脚本用mysqldump加--single-transaction参数保证备份期间不锁表备份文件放到独立目录再异地同步一份双保险。6. 常见问题与排查记录6.1 启动时报Property sqlSessionFactory or sqlSessionTemplate are required这个问题几乎都出在MyBatis和SpringBoot版本不匹配或者Mapper接口没有被正确扫描。检查两步第一步确认pom里mybatis-spring-boot-starter版本和SpringBoot版本配套SpringBoot 3.x必须用MyBatis starter 3.x第二步确认启动类加了MapperScan或Mapper接口上有Mapper注解。还有一个隐蔽原因application.yml里的mapper-locations路径写错XML文件没被加载也会出现类似Mapper找不到的报错。如果XML文件放在src/main/java下面默认打包可能不会带上需要额外在pom里配置resources否则运行时就是找不到。这个问题报错不一定直接说XML而是Mapper方法找不到statement排查时方向要广一点。6.2 数据库中文乱码中文乱码问题优先检查三处数据库表字符集、JDBC连接URL参数、前端页面编码。我建议建表脚本里每张表都指定DEFAULT CHARSETutf8mb4数据库连接URL带characterEncodingutf8前端页面通过Vite默认输出UTF-8一般就没事了。如果已经出现乱码数据需要把数据和表结构一起迁移比较麻烦所以最好新建表时就把字符集写清楚。MySQL 8.0下如果直接用工具建库没指定字符集很可能会继承服务器的latin1默认值这是很多乱码问题的根源。我遇到过的情况是开发环境一切正常生产环境连的是旧的MySQL 5.5默认字符集不对页面中文全变成问号。后来在生产库执行了alter table改字符集才算彻底解决。6.3 接口返回404或403接口返回404或403先判断是哪个层面拦住的。404如果是后端接口检查Controller的RequestMapper路径和前端请求URL是否完全一致如果是Nginx层的404看静态文件和代理配置是否正确。403大多是权限拦截器或跨域配置问题。我在项目里加入了JWT拦截器放行名单里必须包含登录接口、科室列表、医生列表、预约提交等公开接口否则前端访问这些接口就会403。另外跨域预检请求是OPTIONS方法拦截器要放行OPTIONS否则浏览器能看到请求发出去了但CORS一直报错。排查403时我有个习惯直接拿postman请求一下后端接口如果postman正常但浏览器报错九成是跨域或预检拦截问题如果postman也一样403再去查拦截器逻辑。6.4 预约提交成功但号源数量没变这个坑我印象很深。原因是Service方法没加Transactional或者加了但方法被同类内部调用导致事务不生效。解决办法是事务注解加到Service入口方法并且保证该方法是由Controller调用的外部入口进入。排查时打开后端日志看预约插入和排班更新是否在一个事务里如果中间有异常回滚号源数量就不会变。还有一种情况是事务已经生效但排班表的主键查错了更新的不是同一个排班记录。比如前端传了医生ID和日期后台去更新医生表而不是排班表自然不会影响号源。解决方法是前端提交时把排班ID一起带上后端直接用排班ID定位记录避免二次查询时的脏数据问题。6.5 Vue打包后页面刷新404Vue项目打包部署后首页能打开点刷新或直接访问某个子路由却404。原因是history模式的路由交给前端但服务器不知道这个规则直接按路径找文件找不到。Nginx需要加try_files $uri $uri/ /index.html;让所有未命中静态文件的请求都回到index.html由前端路由接管。如果前端部署在Tomcat可以改成hash模式URL会带#号没那么好看但省配置。我在项目README里把Nginx配置示例、history模式坑写成单独一节后来项目交接给同事时省了很多沟通成本。这个问题几乎每个用history模式的前端项目都会遇到提前在部署文档里写明能避免上线当天手忙脚乱。6.6 播放m3u8视频只有声音没有画面健康宣教视频播放m3u8时只有声音没有画面第一排查video.js的hls插件是否引入第二看视频源和分片是否有跨域CORS配置。前端引入videojs-contrib-hls后设置autoplay和controls一般能正常播放。如果视频本身是多码率HLS可在播放器初始化时指定resolution不让它默认加载音频流。对象存储的CORS配置也很重要否则浏览器会拦截m3u8的分片请求播放器进度条一直转却出不来画面。这个问题在本地开发时很少遇到部署到线上才会暴露所以上线前一定要测一遍视频播放。我之前的做法是本地放一个测试m3u8文件先确认前端播放器没问题再切换到对象存储的线上地址。6.7 图片加载慢怎么优化图片加载慢尤其是科室图和医生头像不要把图片放进数据库。数据库里只存URL图片交给MinIO或Nginx静态目录。我在几个版本迭代里都遇到过图片加载慢的问题后来定了一个规范新增图片资源统一走MinIO数据库只保留对象地址页面通过对象存储或CDN访问。如果暂时没有条件部署MinIO也可以用Nginx的alias映射本地目录效果差别不大。医院网站图片数量不算特别大重点是把文件存储和数据库分离维护起来才不别扭。同时前端图片要做懒加载列表页几十张图一次性全加载首屏会非常慢。Element Plus提供了懒加载组件配合统一缩略图尺寸页面加载速度提升很明显。最后说一点个人经验做这类中小型医院网站技术点并不难难的是把业务链路走通尤其是排班、预约、确认这条主线。源码和部署教程再完整也不如你自己动手跑一遍、改一个接口、加一个字段来得实在。如果你正在拿这个项目做毕业设计或者练手建议第一天就把数据库表和预约流程画明白再动手写代码。遇到上面列的问题时不要急着搜一堆资料先按数据库、后端、前端、部署四个层面一层层排查大多数问题都是版本、路径、事务这三种原因引起的。这个项目后续还可以扩展体检预约、在线问诊、患者反馈统计等模块骨架搭好了扩展起来会顺手很多。
返回列表