☰
SpringBoot+Vue+MyBatis+MySQL医院后台管理系统完整部署实战
2026/10/9 3:13:16 网站建设 项目流程

年初我把一个前后端分离的医院后台管理系统完整部署了一遍,SpringBoot+Vue+MyBatis+MySQL这个组合看着眼熟,真正跑起来还是会碰到不少课本上没写的坑。网上这类源码很多,但多数只停留在“能打开登录页、能增删改查”的程度,离一个真正可用的医院后台还有不少距离。我拿这套系统当自己的项目重新搭了一遍:从数据库建模、后端接口、前端页面到Nginx上线,一条线走完整。文章会把项目拆开讲清楚,附上可以直接照着做的部署过程。适合正在做毕业设计、想练全栈项目,或者刚入职需要快速上手前后端分离这套技术栈的同学。

1. 项目到底在做什么:先拆掉“医院后台系统”这层皮

对一个项目动手之前,第一步不是打开IDE敲代码,而是把业务场景想明白。医院后台管理系统,表面上是对患者、医生、药品、挂号这些数据进行增删改查,实际上它要解决的是一家中小型医院或诊所日常运营里的核心问题:患者来了怎么挂号,医生怎么开单子,药房怎么发药,收费怎么记账,管理员怎么给不同角色分配权限。这些环节不是孤立的,挂号单会影响门诊记录,处方会影响药品库存,每笔操作背后都有状态流转。如果不先想清楚这些,写出来的后台系统就是一堆孤立的CRUD页面,根本无法在实际场景里用。

1.1 这不是普通增删改查,而是一套业务流程闭环

判断一个后台系统是否有真实价值,就看你能否跑完一条完整业务链路。以这套项目为例,核心链路是:管理员先在系统里维护科室和医生排班,挂号员录入患者信息并完成挂号,医生在门诊模块看到候诊列表、书写病历并开出处方,药房收到处方后发药扣减库存,收费处根据处方和挂号类型完成结算,最终数据汇总到统计报表。每一环的数据都要能被下一环读取,任何一环缺失,业务就算不上完整。

项目里常见角色包括系统管理员、挂号员、门诊医生、药房管理员、收费员,每个角色看到的菜单不一样,操作的接口权限也不一样。这就是为什么权限设计在这个项目里不是附加功能,而是核心功能。很多初学者把权限当成“登录后根据角色显示不同菜单”的小事,实际上权限控制要落在菜单、按钮、接口三个层面,稍有不慎就会出现越权漏洞。这也是医院后台管理系统相比普通进销存系统更值得学习的地方,它的角色边界足够清晰,非常适合用来演示RBAC权限模型。

1.2 为什么选前后端分离,而不是传统单体渲染

这套项目没有选择SpringBoot加Thymeleaf那种传统单体渲染,而是前端完全独立出来的前后端分离架构。分离后最直接的变化是后端不再返回带HTML的页面,只返回JSON数据,前端拿到JSON再去渲染用户界面。收益在多人协作时非常明显:后端同学只需要定义好接口文档,前端同学可以并行写页面。对于医院后台这种需要长期迭代的系统,后端接口可以被复用,比如同一套用户查询接口,既能支撑管理后台,也能支撑后续的大屏展示或移动端,不用重复开发。

前后端分离也意味着部署方式变了。以前的单体项目打一个WAR包扔进Tomcat就完事,现在相当于两个独立应用:后端是一个SpringBoot服务,前端是一套静态资源。上线时后端用java -jar启动,前端把构建后的静态文件丢给Nginx托管。开发环境下还需要处理跨域问题,因为前端跑在5173或8080端口,后端跑在9090端口,浏览器会拦截跨域请求。这个架构上的变化,恰恰是这套项目最有教学价值的部分,它把“开发”和“部署”两个环节拆开了,你要同时懂后端启动、前端构建和服务器转发才能把它完整跑起来。

当然不是所有系统都适合前后端分离。如果是内部极简工具、没有多端复用需求,传统模板渲染也能用。但如果你希望项目能作为毕业设计、简历项目,前后端分离是当前主流,面试时也更容易展开讲,所以这套技术栈的选择是合理的。

2. 技术栈选型:SpringBoot+Vue+MyBatis+MySQL到底怎么选

技术选型一般会考虑团队熟悉度、生态成熟度、招聘市场要求、学习成本。SpringBoot+Vue+MyBatis+MySQL能成为国内后台开发常见的“四件套”,不是因为它最先进,而是因为它平衡了开发效率和入门门槛。SpringBoot负责快速搭建接口服务,Vue负责前端交互,MyBatis负责数据库访问,MySQL负责数据存储。每个环节都有大量现成方案,遇到问题容易找到解决办法。对个人项目来说,这套组合的省心程度是第一位的。

2.1 SpringBoot版本别追新,先看依赖兼容

我特意把这个话题放在前面,因为“SpringBoot版本太高”已经是新版常见的坑。SpringBoot 3.x已经发布不少时间,但如果你直接下载最新版,很可能遇到两个麻烦:一是3.x要求JDK17,本机还是JDK8的话跑都跑不起来;二是3.x把javax命名空间改成了jakarta,很多老版本的MyBatis starter、Druid连接池、分页插件都需要同步升级,否则直接报ClassNotFoundError。作为医院后台管理系统,没有必要在版本上冒险。

我的建议是:如果是学习或者部署现有源码,优先选SpringBoot 2.7.x,搭配JDK8或JDK11,这个组合最保险,资料最多,各种第三方库都能找到对应版本。如果是从零开始新项目,并且机器已经是JDK17,再考虑SpringBoot 3.x,同时把MyBatis相关依赖换成新版本。项目里如果用MyBatis原生starter,SpringBoot 2.7对应的是mybatis-spring-boot-starter 2.3.x,SpringBoot 3.x需要换成mybatis-spring-boot-starter 3.0.x。版本对不上时,最常见的报错就是创建SqlSessionFactory失败,或者找不到MapperBean。

另一个容易踩的坑是MySQL驱动版本。SpringBoot 2.7.x默认管理的是mysql-connector-j 8.0.x,如果手动指定了过老的5.x驱动,连接数据库时会报时区或认证相关错误。建议不要手动指定驱动坐标,让SpringBoot统一管理版本,只在pom.xml里写依赖名即可。

2.2 MyBatis:真正要花心思的是Mapper层设计

MyBatis这层主要是数据库操作。很多人觉得MyBatis就是写写XML、Mapper接口,但实际上有几个细节直接影响项目能不能顺利跑起来。第一个是XML和注解的选择,项目里建议以XML为主,原因很简单:SQL复杂以后,XML比注解好维护,尤其是医院系统里带多条件筛选、动态拼SQL的场景,使用 标签比在Java里手动拼接字符串直观很多。第二个是Mapper扫描路径,启动类上要有@MapperScan,或者每个Mapper接口加@Mapper,否则Spring容器里找不到Bean,启动后接口调用会报“Invalid bound statement”。

第三个细节是下划线转驼峰。数据库字段如果叫user_name,Java属性叫userName,需要在配置文件里开启map-underscore-to-camel-case: true,否则查询结果会把user_name映射为null,接口返回的数据全是空字段。这个错误特别隐蔽,接口不报错,前端就是拿不到值。还有SQL打印配置,想让MyBatis在控制台输出SQL,可以在配置里指定mybatis.configuration.log-impl为StdOutImpl,联调排错时非常有用。如果不做这些基础配置,项目跑起来像一团黑盒,出了问题只能靠猜。

如果项目里用了MyBatis-Plus,又是另一套玩法。MyBatis-Plus确实能省掉大量单表CRUD代码,内置了BaseMapper和分页插件。但我的建议是:核心业务表不要完全依赖它自动生成的SQL,因为医院场景中很多查询要跨表关联,自动办法不好处理。最舒服的方式是单表操作用Plus,复杂统计和分析SQL用自定义XML,这样既有速度,也不牺牲灵活性。

2.3 Vue:工程化和权限路由才是重点

Vue的入门很简单,写个列表、表单、弹窗都不难。但后台管理系统真正考验的是工程化组织能力。首先要明确Vue版本选择:这套项目如果基于Vue2,路由和组件库生态非常成熟,配合Element UI,网上案例最多;如果基于Vue3,配合Element Plus、Pinia、Vue Router 4,是当前新项目的默认选择。无论哪个版本,都需要先把Node环境搞定。我部署时被卡住最多的就是Node版本太新导致依赖兼容问题。Vue2项目建议Node 14到16之间,Vue3项目建议Node 16到18之间,版本太新在某些老项目里会出现OpenSSL相关错误。

一进入Vue项目,第一件事是npm install。这一步通过网络下载依赖,在国内经常很慢。我的做法是先把npm源切到国内源,比如registry.npmmirror.com,然后再执行安装。依赖装完后,需要检查项目的vite.config.js或vue.config.js,尤其是开发环境的转发配置。前端本地跑在5173或8080,后端接口跑在9090,浏览器请求/api/xxx会跨域,所以开发环境通常要配置转发:把/api开头的请求转发到localhost:9090。如果你没配这一步,前端页面能打开,但所有接口都请求失败。

Vue项目里面的权限路由值得单独说:登录成功后,前端不能直接把所有菜单都渲染出来,而是要根据当前用户角色从后端拿到可访问的菜单列表,再动态添加路由。动态路由的常见做法是后端在登录接口里返回当前用户的菜单树,前端使用router.addRoute逐个注册页面路由,同时配合左侧菜单组件v-for渲染。按钮级别权限则用自定义指令控制,比如“新增”“删除”按钮根据权限标识决定是否显示。这属于越往后越值钱的知识点,面试时能讲清楚这一段,项目含金量完全不一样。

2.4 MySQL:表设计决定项目天花板

数据库设计是所有环节里最不能偷懒的部分。一旦上线后才发现字段缺失,改表的成本非常高。这套医院后台系统一般会包含这样几类表:系统权限表(用户、角色、菜单、用户角色关联、角色菜单关联)、基础档案表(患者、科室、医生、药品)、业务流水表(挂号、门诊病历、处方、收费、库存变更)。系统表负责定义“谁能干什么”,基础档案表负责定义“有哪些资源”,业务流水表负责记录“发生了什么”。

我的设计建议是:每个表都必须有主键id,建议用bigint自增,不要用UUID当主键,因为UUID在InnoDB里会引发随机写入,数据量大了之后索引效率下降。业务流水表必须包含create_time和update_time字段,用datetime类型。所有金额字段用decimal(10,2),不要用float,因为浮点数计算会产生精度误差,这在收费场景里不能接受。患者和医生之间通过字段关联,不要用外键约束,外键会拖慢写入性能,并且在分表后很难维护,关联关系靠代码层维护就够了。

字符集方面,统一使用utf8mb4,不要用utf8,因为utf8在MySQL里存不了emoji,而且utf8mb4才是真正的完整字符集。排序规则一般用utf8mb4_general_ci。存储引擎必须InnoDB,支持事务,在医院这种有资金和库存操作的场景里,事务是底线。如果部署在MySQL 8.0上,还要注意时区参数,连接串里建议加上serverTimezone=Asia/Shanghai,否则Java侧和数据库侧的日期时间可能出现8小时偏差。

3. 核心模块与实现细节(实操重点)

业务功能可以拆成几大块来讲:登录鉴权、权限控制、以及挂号到收费的核心链路。每个模块都有经典实现套路,也有容易偷懒的点。

3.1 登录鉴权:JWT加拦截器,别再用Session了

传统项目用Session保存登录状态,但在前后端分离架构下,Session天然有跨域和集群问题,所以这套项目普遍使用JWT。流程是这样的:用户提交用户名密码,后端校验通过后生成一个Token,Token里通常包含用户id、用户名、过期时间,使用密钥签名;前端把Token存到localStorage或状态管理库里,之后每次请求都在Header里带上Authorization: Bearer xxx;后端写一个拦截器或过滤器,拦截除登录接口以外的所有请求,解析Token,解析成功就放行,解析失败返回401。

代码实现上,JWT依赖通常用io.jsonwebtoken这个库。要注意两点:第一,Token过期时间不要设太长,也不要太短,后台系统一般2到8小时,要结合实际使用场景;第二,密钥不要写在代码里,放配置文件,后续改密钥不用重新编译上线。密码存储方面,不要明文存密码,统一用BCrypt加密,常见的是BCryptPasswordEncoder。登录接口返回Token的同时,建议返回用户基本信息,比如用户名、角色标识、权限列表,前端可以用这些数据初始化界面。

我的实操经验是:拦截器写好之后,一定要在WebMvcConfig里注册拦截路径,一般写成拦截“/**”但排除登录接口。如果不注册或者路径写错,Token校验就是不生效。另一个容易踩的坑是跨域预检请求OPTIONS也会被拦截,导致前端提示CORS错误,需要在拦截器里判断请求方法为OPTIONS时直接放行,否则联调阶段会被这个卡半天。

3.2 权限模型:RBAC三张表,从菜单到接口都要管

这套系统的权限模型基本按RBAC设计:用户表、角色表、菜单表,外加用户角色关联表、角色菜单关联表。用户不直接跟菜单挂钩,而是通过角色间接关联。这个设计的好处是方便批量赋权,比如新来一个医生,只要给他分配医生角色,他就能看到医生相关的菜单和接口,不用一个一个勾选权限。

菜单表需要设计成树形结构,字段通常包括menu_id、parent_id、menu_name、path、component、perms、icon、sort。path是前端路由路径,component是Vue页面组件路径,perms是权限标识,比如sys:user:list。后端在登录成功后查询当前用户的perms列表,返回给前端。前端根据perms列表控制按钮显隐,后端在接口层用AOP注解或拦截器校验权限标识。

有同学会问,前端隐藏了按钮,后端是不是就不用校验了?绝对不能这么想。前端隐藏只是体验,真正的安全边界在后端。后端每个接口都应该能够根据当前登录用户判断权限,最轻量级的做法是自定义一个权限注解,配合AOP切面,在进入Controller方法前检查当前用户是否有对应权限标识。这个做法不依赖SpringSecurity,也够用,适合这种教学型和中小型管理系统。

3.3 业务闭环:挂号、处方、库存、收费怎么串起来

业务模块如果要做扎实,最好以一次完整就诊为主线。以挂号为例,这里最容易出现并发问题。典型场景是:某个医生上午的号源共30个,两位患者同时点击挂号操作,如果后端只是先查询余号,判断大于0再插入,两个请求同时通过,号源就超了。解决办法是给号源表加版本号或条件更新:执行update号源表 set remaining = remaining - 1 where id = ? and remaining > 0,通过受影响行数判断是否成功,如果为0说明号源已被抢完。

处方和药品库存也是一样的套路。医生开处方时,前端提交的是处方主表和多个药品明细,后端必须开启事务,要么全部成功,要么全部回滚。在Service方法上加上@Transactional(rollbackFor = Exception.class),注意rollbackFor别省,因为Spring默认只对RuntimeException和Error回滚,如果业务方法抛的是普通Exception,事务不会回滚,库存就会发出去但流水没记上。另外同一个类内部方法调用事务不生效,如果你在ServiceA里调用同类中的另一个方法,那个方法上的@Transactional不会起作用,需要把事务方法拆分到另一个Service或者通过代理调用。

收费模块则要处理好状态机。挂号有待接诊、就诊中、已完成、已退号等状态,处方有待收费、已收费、已发药等状态。每一次状态变更都需要校验前置状态,比如已收费的处方不能再次收费,已发药的药品不能随意删改。状态字段建议用int类型配合枚举类,不要直接存中文,方便后续扩展。这些状态流转逻辑在面试时讲出来,比单纯说写了增删改查有说服力得多。

4. 完整部署教程:从源码到能访问

拿到一份源码,能不能快速跑起来,考查的是处理环境问题的能力。我把整套流程分为三步:初始化数据库、启动后端、构建并部署前端。以下步骤基于Windows本机开发环境,Linux服务器上的区别我会在关键处说明。

4.1 后端启动:先建库,再改配置,最后java -jar

第一步是准备MySQL。安装MySQL之后,建议先在命令行里验证能连上,然后在数据库工具里执行项目提供的sql脚本。注意有些源码的sql文件名包含版本信息,比如his_db.sql,直接在MySQL中执行即可,执行时选择utf8mb4字符集。如果导入报错,多半是sql文件编码不对,把文件另存为UTF-8再执行。导入完成后,用show tables确认表已经生成。

第二步是修改后端配置文件。SpringBoot项目的配置一般在src/main/resources/application.yml,主要改数据库连接串、用户名、密码和端口。这里有一个常见的错误:数据库密码里如果含有特殊字符,在yml中必须加引号,比如password: "abc@123",否则启动时连接失败。配置改完后,用Maven打包:mvn clean package -DskipTests。构建成功后,用java -jar target/xxx.jar启动。控制台里看到Tomcat started on port 9090,说明后端起来了。如果你想在Linux服务器上跑,记得在服务器安全组或防火墙中放行9090端口。MySQL允许远程连接的话,url里的localhost要改成服务器IP,且MySQL账号要允许远程访问,这个问题经常导致“本地能跑,服务器连不上数据库”。

后端配置文件可参考下面这段,具体参数以你的环境为准:

server: port: 9090 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/his_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false username: root password: 你的密码 jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: Asia/Shanghai mybatis: mapper-locations: classpath:mapper/*.xml configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

4.2 前端启动:Node环境、依赖安装与本地联调

前端建议先在本机跑起来联调,再考虑打包。进入前端目录,先确认node -v和npm -v。如果项目是Vue3加Vite,Node版本低于16会直接报错。然后执行npm install。这一步如果很慢或者卡在某个包上,先换源再试:

npm config set registry https://registry.npmmirror.com npm install npm run dev

依赖安装完成后,检查vite.config.ts或vue.config.js。以Vite为例,转发配置大概长这样:

server: { host: '0.0.0.0', port: 5173, proxy: { '/api': { target: 'http://localhost:9090', changeOrigin: true } } }

这样前端请求/api/login时,Vite会把请求转发到后端的9090端口,解决跨域问题。配置好以后,npm run dev打开页面,输入管理员账号密码,能登录进去并且菜单正常加载,说明本地联调通了。如果接口请求失败,优先看浏览器控制台,确认是网络层没连通还是后端返回了异常,不要一开始就怀疑代码写错。

4.3 生产环境部署:前端构建产物加Nginx转发

本地联调通过后,前端执行npm run build,会在dist目录生成静态文件。把dist目录上传到服务器,例如放到/opt/his-web。后端jar包上传到服务器,例如/opt/his-server,然后启动:

nohup java -jar his-server.jar > his.log 2>&1 &

接着需要让Nginx托管前端静态文件,并把/api请求转发给后端9090端口。Nginx配置里至少要有一个静态站点和一条接口转发规则,静态站点指向dist目录,接口转发指向后端服务。这里有个细节:如果Vue路由用的是history模式,需要配置try_files,刷新某个子页面时不至于404;如果项目用的路由是hash模式,这个配置可以省略。配置完执行nginx -t检查语法,然后nginx -s reload。这时候直接访问服务器IP,就能看到后台登录页,说明部署成功。

生产部署还有几个容易忽略的点:第一,前端和后端在同一台机器上,后端要监听能被Nginx访问到的地址,不要只绑定127.0.0.1;第二,Nginx转发地址中的端口要和后端实际端口一致,如果改过后端端口,这里也要同步改;第三,项目日志很重要,后端启动日志、Nginx错误日志翻一翻,大部分部署问题都能在里面找到答案。

5. 常见问题与排查实录(附速查表)

跑项目最费时间的往往不是写代码,而是排查环境问题。下面几个问题是我在部署这套医院后台系统时遇到过的,整理出来可以帮你少走弯路。

5.1 后端启动阶段高频问题

先看一个最常见的报错:Invalid bound statement (not found)。这个一看就是Mapper接口和XML映射没关联上。先确认application.yml里mapper-locations的路径是不是正确指向了xml所在目录,再确认XML文件的namespace是否和Mapper接口的全限定名一致,最后看方法id和接口方法名是否一致。三个点逐一排查,大部分情况都能解决。

还有Access denied for user,这是MySQL账号密码错误,或者账号没有远程权限。如果本机连接,先检查密码是否写错;如果远程连接,需要执行授权语句,允许指定账号从任意主机访问目标库。Unknown database则说明数据库还没有创建,或者执行sql时连接的不是同一个库,先执行建库语句,再导入脚本。端口被占用也很常见,本地排错时优先用命令查看端口占用,结束进程或更换端口。日期时间比正常少了8小时,检查JDBC连接串是否加了serverTimezone=Asia/Shanghai,加了重启即可。

5.2 前端构建阶段高频问题

npm install失败、提示Error code ELIFECYCLE,通常是安装依赖时中断,先删除node_modules和package-lock.json,重新执行npm install。Vue3项目报错failed to load tsconfig,多半是编辑器还没识别项目里的tsconfig配置,或者子项目引用父级tsconfig路径不对,可以看看构建配置,必要时升级相关工具链版本。npm run build之后页面是空白,大概率是静态资源路径问题,Vite项目需要设置base: './',否则资源加载的是绝对路径,部署到子路径时找不到文件。接口404或CORS error,开发环境看转发配置是否生效,生产环境看Nginx接口转发是否配置正确,前后端接口前缀是否一致。

5.3 前后端联调与业务逻辑高频问题

接口返回的JSON字段名和前端对不上,先检查下划线转驼峰配置是否开启,再确认前后端字段命名约定是否统一,联调前把接口文档看一遍。新增数据成功后页面列表不刷新,前端需要在接口回调里重新拉取列表,或者用状态管理库更新列表数据,不要只依赖表格组件内部缓存。事务不生效的情况,检查方法是否public、是否从外部调用、异常是否被捕获后吞掉、@Transactional注解是否加了rollbackFor = Exception.class。并发挂号出现超卖,检查号源扣减是否有乐观锁,对应SQL中是否包含remaining > 0条件。

我把这些常见问题整理成速查表:

场景常见报错或现象最可能的解决方案
后端启动Invalid bound statement检查XML namespace与Mapper路径
后端启动Access denied for user检查账号密码或远程授权
数据库连接Unknown database先建库再导SQL
时区问题日期相差8小时连接串加serverTimezone
npm安装依赖卡住或报错换国内npm源后重装
前端构建页面空白Vite设置base: './'
生产部署刷新404Nginx配置try_files
业务联调字段值为null开启下划线转驼峰
并发业务号源超卖扣减SQL加remaining大于0条件

这套排查思路不局限在医院后台这个业务里。任何SpringBoot+Vue+MyBatis+MySQL项目,大多数报错都能归到上面这些类别。遇到问题先分阶段定位:是前端没起来、后端没起来、接口通了但数据不对,还是部署后访问不到。一次只处理一个问题,不要同时改一堆配置。

最后再说几句

把整套源码从零到一复现一遍之后,我最大体会是:一个项目能不能算完整,不在于功能页面多不多,而在于你有没有从头到尾把它跑通。我见过很多同学下载了源码,连上数据库能看到数据就觉得自己会了,结果换一台机器就不知道怎么部署。只有亲手改过配置文件、踩过时区坑、处理过跨域、打包上过服务器,才会真正理解这套架构里每个组件是干什么的。

如果你后续想扩展,我会建议先加两块:一是导出报表功能,把统计模块和Excel导出结合起来,实际医院管理里很常用;二是对接微信小程序或移动端,验证后端接口的复用能力。到时候你会发现,当初选择前后端分离带来的好处,会在这些场景里被进一步放大。这个项目算是不错的起点,把它理解透了,再去看大型企业级项目心里踏实很多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询