接手这样一个医院后台管理系统,大多时候不是因为医院信息科主动找上门,而是培训机构、毕业设计、或者中小型诊所信息化升级的需求。项目本身不复杂,但胜在技术栈经典、业务边界清晰,作为前后端分离的练手项目非常合适。SpringBoot负责接口下沉,Vue3负责界面交互,MyBatis把数据库操作收敛到XML里,MySQL存储业务数据,这条链路几乎覆盖了Java后端开发日常工作的全部核心环节。基于这个项目的架构拆解和实操记录,整理一篇相对完整的经验总结,给正在做同类系统或者想拿这个项目练手的朋友做个参考。
1. 医院后台管理系统到底在做什么:业务模块与架构决策
1.1 一个真实医院项目的模块地图
医院后台管理系统,核心是替医院信息科或者管理者把门诊、药房、收费、医生排班这些日常运营动作从纸质记录搬到线上。很多人一听到"医院系统"就以为要做HIS(医院信息系统)那种庞然大物,实际上后台管理系统是HIS的简化版,或者说是医院内部管理使用的支撑系统,它的边界通常很清晰。
我经手过的这类项目,业务模块一般包含这几块:
- 系统管理:用户管理、角色管理、菜单权限管理,这是后台系统的地基。
- 医生排班管理:给医生配置出诊时间、门诊科室、号源数量。
- 门诊挂号管理:患者来院建档、挂号、候诊队列。
- 收费划价管理:根据医生开的处方或检查单,完成费用结算、退费。
- 药房库存管理:药品字典维护、入出库记录、库存预警。
- 患者档案管理:患者基本信息、历史就诊记录、过敏史标签。
- 数据统计看板:门诊量、收费金额、科室负载的图表统计。
从技术实现角度看,这些模块本质上就是"增删改查"的排列组合,真正的难点不在单个模块,而在于模块之间的数据关联。比如挂号要引用医生排班数据,收费要联动药房库存扣减,患者档案又被多个模块共享。模块边界划得越清楚,后面写代码、调接口就越省力。
1.2 为什么选前后端分离而不是传统单体页面
早期的医院管理系统,大多是JSP+Servlet,或者直接用PHP套模板,一个页面混着HTML、JS、后端代码,改一个按钮样式都要小心翼翼,生怕动到了业务逻辑。前后端分离的核心差异,是用HTTP接口把界面渲染和数据服务彻底拆开。
选前后端分离,有几个非常现实的原因:
- 团队分工清晰。后端专注接口的稳定性、数据的一致性,前端专注交互体验和页面效率,两边可以并行开发。
- 部署互不影响。前端构建产物是静态文件,扔到Nginx就能跑,后端是SpringBoot应用独立部署,接口升级不用拉着前端一起发布。
- 多端复用接口。后台管理系统、未来的患者小程序、医生App,都可以复用同一套后端接口。
代价也很明显:联调成本变高,跨域问题必须处理,权限控制要同时考虑前端路由和后端接口双门槛。但整体收益远大于成本。
1.3 技术选型背后的几个现实理由
这套技术栈不是你随便凑出来的,每条都有它的现实理由。
- SpringBoot:Spring社区的事实标准,自动配置机制帮我们省掉了大量XML配置。内置Tomcat,打出一个Jar包就能跑,部署心智负担很小。
- Vue3:组合式API配合setup语法糖,比Vue2的Options API写业务逻辑更内聚,同样功能的代码行数能少三分之一左右。配合Vite开发时的热更新速度远超Webpack。
- MyBatis:SQL让开发者完全掌控,复杂查询、报表统计这类场景用SQL直接写反而比ORM拼Criteria更直观。医院项目的报表查询逻辑天生复杂,这正好是MyBatis的主场。
- MySQL:开源数据库里综合成本最低的选择,医院后台管理系统的数据量级在百万级以内,MySQL的InnoDB引擎性能和可靠性完全够用。
提示:如果项目经费充足或者业务量上来,可以把MySQL换成PostgreSQL或者OceanBase,但核心流程不变,因为SQL标准是通用的。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 后端工程从零搭建:SpringBoot + MyBatis 的地基逻辑
2.1 项目结构和依赖设计
后端工程我习惯按"先分包、再分模块"的原则来组织。不要一上来就按三层架构堆目录,而是先想清楚每个包的业务语义。
一个典型的工程结构:
code复制com.hospital.admin
├── controller // 对外接口层,只做参数接收和结果封装
├── service // 业务逻辑层,事务边界在这一层控制
│ └── impl
├── mapper // MyBatis的Mapper接口,定义数据库操作方法
├── entity // 数据库表对应的实体类
├── dto // 前端交互的传输对象,避免实体直接暴露
├── vo // 视图对象,聚合后端返回给前端的展示数据
├── config // SpringBoot配置类,如跨域、拦截器、MyBatis配置
├── common // 通用返回结果、异常处理、常量类
└── utils // 工具类,如JWT工具、日期工具
依赖设计上,核心依赖就是:spring-boot-starter-web(Web能力)、mybatis-spring-boot-starter(MyBatis整合)、mysql-connector-j(MySQL驱动)、lombok(消除getter/setter样板代码)、spring-boot-starter-validation(参数校验)、jjwt(JWT令牌生成与校验)。
需要注意的是SpringBoot版本。如果你用的SpringBoot 3.x,JDK必须17以上,mybatis-spring-boot-starter要用2.3.x之后的版本;如果还在用JDK8,老老实实选SpringBoot 2.7.x。这个版本匹配问题在社区里太常见了,经常看到有人问"SpringBoot版本太高导致启动报错",九成是JDK版本没跟上。
2.2 application.yml 配置里最容易出问题的几个点
这部分踩过的坑值得单独记一笔。
数据库连接配置通常长这样:
yaml复制spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/hospital_admin?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
username: root
password: 123456
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: Asia/Shanghai
配置里有三个细节必须注意:
第一个是driver-class-name。MySQL 8.x的驱动类是com.mysql.cj.jdbc.Driver,老项目里常见的com.mysql.jdbc.Driver是MySQL 5.x时代的写法,用MySQL 8驱动还配旧类名,启动会直接报错。
第二个是url里的serverTimezone=Asia/Shanghai。不配置这个参数,Java 8及以上版本连接MySQL 8,时间字段会报The server time zone value的异常,因为你本机时区和数据库系统时区对不上。
第三个是characterEncoding=utf8。这里强烈建议在MySQL端直接把字符集设置为utf8mb4,比在连接池层面强制指定更彻底。utf8mb4是真正的完整UTF-8,MySQL 8默认就是utf8mb4,但如果你数据库建库时用了utf8,Emoji和生僻字的前端输入就会在入库时变问号。
MyBatis部分的配置:
yaml复制mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.hospital.admin.entity
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
map-underscore-to-camel-case是下划线转驼峰,数据库字段user_name映射实体属性userName,这个必须开,不然你每次查询都要手动写resultMap。log-impl配置成StdOutImpl,开发阶段能在控制台直接看到SQL和参数,排查问题非常方便。上线前记得把log-impl这行去掉,不然满屏SQL日志影响性能还泄漏数据。
2.3 数据库表设计:不要急着写代码,先画关系
后台管理系统的数据库表设计,是决定整个项目开发效率的关键。我在做这类项目时有一个习惯:先把表结构关系和ER图理清楚,再动代码。尤其对于医院这种业务实体较多的系统,表关系画错了,后期返工成本极高。
核心表我通常这样划分:
- 用户表 sys_user:用户ID、用户名、密码(BCrypt加密后)、姓名、手机号、状态、创建时间
- 角色表 sys_role:角色ID、角色名称、角色编码、备注
- 菜单权限表 sys_menu:菜单ID、父菜单ID、菜单名称、路由地址、权限标识
- 用户角色关联表 sys_user_role:用户ID、角色ID
- 角色菜单关联表 sys_role_menu:角色ID、菜单ID
- 科室表 department:科室ID、科室名称、科室类型(门诊/住院/辅助)、负责人、联系电话
- 医生表 doctor:医生ID、所属科室ID、姓名、职称、擅长领域、出诊状态
- 排班表 schedule:排班ID、医生ID、出诊日期、上午/下午号源数、已约号源数
- 患者表 patient:患者ID、姓名、性别、出生日期、身份证号、手机号、过敏史
- 挂号表 registration:挂号ID、患者ID、排班ID、医生ID、挂号类型、费用状态、挂号时间
- 药品表 drug:药品ID、药品名称、规格、生产厂家、价格、库存数量、预警值
- 收费表 charge:收费ID、患者ID、收费类型(挂号/药品/检查)、关联业务ID、金额、支付方式、收费时间
具体到每张表的主键,我统一用BIGINT自增主键,配合唯一索引去重。有些团队习惯用雪花ID,但在单库单表的后台管理系统里,自增主键在InnoDB的B+树索引里插入效率更高,性能也更稳定,没必要引入分布式ID的复杂度。
外键我几乎不建,只在业务代码层维护关联关系。原因很简单:外键约束会降低插入和删除性能,而且医院这种系统经常要做数据归档,外键会让归档脚本变得非常麻烦。数据一致性靠事务和业务代码去保证,这是企业级开发的主流做法。
3. 前端项目搭建与页面骨架:Vue3 + Element Plus 的组合之道
3.1 Vite初始化Vue3项目时的选择
前端我用的Vite来初始化Vue3项目,你可以在任意目录里执行命令快速创建一个工程:
bash复制npm create vite@latest hospital-admin-web -- --template vue
Vite是一个基于ES Module的构建工具,开发环境下依赖预构建和浏览器原生ES Module能力,冷启动速度比Webpack快好几个量级。做后台管理系统这种中大型项目,开发体验的提升非常明显。
创建完成后,安装核心依赖:
bash复制npm install vue-router@4 pinia element-plus axios sass
几个关键包的作用要清楚:
- vue-router@4:Vue3官方路由,负责页面跳转和路由守卫。
- pinia:Vue3官方推荐的状态管理库,比Vuex的API简洁太多,配合setup语法糖使用时,几乎不需要额外包装。
- element-plus:Element UI的Vue3版本,后台管理系统的UI组件库几乎没有比它更好上手的,表格、表单、弹窗、分页都内置好了。
- axios:HTTP请求库,统一封装请求头和响应拦截器。
- sass:样式预处理器,写嵌套CSS和变量的时候能省很多事。
后端接口地址前缀,我习惯在项目根目录建一个.env.development文件:
code复制VITE_API_BASE_URL=http://localhost:8080/api
这样开发环境和后端联调时,请求地址是写在一个环境变量里的,后面部署上线,只需要新增.env.production文件,把地址切换成生产环境的域名即可,不需要改任何代码。
3.2 路由与菜单:后台系统页面的组织方式
后台管理系统的路由设计,要跟业务菜单联动。Element Plus那套动态菜单方案,本质上思路是这样的:
- 用户登录成功后,后端返回当前用户有权限的菜单列表,这个列表是一个树形结构,每个节点包含路由路径、菜单名称、图标、组件路径等信息。
- 前端拿到菜单列表之后,动态注册路由,并渲染侧边栏菜单。
- 菜单点击之后,路由跳转,组件懒加载去加载对应的Vue页面文件。
路由配置的示例:
javascript复制import { createRouter, createWebHistory } from 'vue-router'
const routes = [
{
path: '/login',
component: () => import('@/views/Login.vue')
},
{
path: '/',
component: () => import('@/layout/MainLayout.vue'),
redirect: '/dashboard',
children: [
{
path: 'dashboard',
name: 'Dashboard',
component: () => import('@/views/Dashboard.vue'),
meta: { title: '数据看板', icon: 'Odometer' }
}
]
}
]
这里有一个实际开发中容易忽略的点:你需要在路由中加入meta.title和meta.icon字段,动态菜单渲染时就是根据这两个字段展示菜单名称和图标。如果后端菜单表里没有对应字段,菜单渲染出来就是一堆空白的文字列表,样式布局全乱了。
路由守卫主要用于登录验证。判断请求路径是否在白名单(如登录页),如果不在白名单且本地没有token,就强制跳转登录页。代码逻辑大概这样:
javascript复制router.beforeEach((to, from, next) => {
const token = localStorage.getItem('token')
if (!token && to.path !== '/login') {
next({ path: '/login' })
} else {
next()
}
})
注意:前端路由守卫只能作为交互层面的体验优化,真正的权限校验必须依赖后端接口返回的鉴权结果,因为前端的JS代码完全暴露在浏览器里,任何拦截逻辑都可以被绕过。
3.3 Pinia管理登录态和用户信息
Pinia在Vue3项目里的地位,相当于Vuex的加强版,但其设计思路更接近Composition API。一个典型的用户状态Store:
javascript复制import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', {
state: () => ({
token: localStorage.getItem('token') || '',
userInfo: {}
}),
actions: {
setToken(token) {
this.token = token
localStorage.setItem('token', token)
},
setUserInfo(info) {
this.userInfo = info
},
logout() {
this.token = ''
this.userInfo = {}
localStorage.removeItem('token')
}
}
})
用起来非常顺手:
vue复制<script setup>
import { useUserStore } from '@/stores/user'
import { computed } from 'vue'
const userStore = useUserStore()
const username = computed(() => userStore.userInfo.realName)
</script>
到这里,前端骨架和状态层就齐了。下一步开始写核心业务页面。
4. 核心业务实现:挂号、排班、收费这些功能怎么落代码
4.1 动态SQL是MyBatis的灵魂
MyBatis和普通ORM框架最大的差异化优势,就是动态SQL。医院后台管理系统的查询条件通常特别多,比如药品列表可能同时要按名称、分类、生产厂家、库存状态多个条件组合查询,这时候你用注解式SQL拼接会非常痛苦,但放在XML里配合动态标签就很优雅。
以挂号记录查询为例:
xml复制<select id="selectRegistrationList" resultType="com.hospital.admin.vo.RegistrationVO">
SELECT
r.id,
p.patient_name AS patientName,
d.doctor_name AS doctorName,
r.registration_type AS registrationType,
r.charge_status AS chargeStatus,
r.create_time AS createTime
FROM registration r
LEFT JOIN patient p ON r.patient_id = p.id
LEFT JOIN doctor d ON r.doctor_id = d.id
<where>
<if test="patientName != null and patientName != ''">
AND p.patient_name LIKE CONCAT('%', #{patientName}, '%')
</if>
<if test="doctorName != null and doctorName != ''">
AND d.doctor_name LIKE CONCAT('%', #{doctorName}, '%')
</if>
<if test="registrationType != null and registrationType != ''">
AND r.registration_type = #{registrationType}
</if>
<if test="chargeStatus != null and chargeStatus != ''">
AND r.charge_status = #{chargeStatus}
</if>
<if test="startDate != null and startDate != ''">
AND DATE(r.create_time) >= #{startDate}
</if>
<if test="endDate != null and endDate != ''">
AND DATE(r.create_time) <= #{endDate}
</if>
</where>
ORDER BY r.create_time DESC
</select>
需要注意这里XML中大于等于、小于等于必须写成>、<对应的转义字符,否则XML解析时会直接报错。新手第一次写动态SQL时,十个人里有七八个会卡在这个细节上。
再比如foreach批量插入,这是MyBatis处理批量数据标准姿势:
xml复制<insert id="batchInsertOrders">
INSERT INTO charge_item (charge_id, item_name, item_price, item_count)
VALUES
<foreach collection="list" item="item" separator=",">
(#{item.chargeId}, #{item.itemName}, #{item.itemPrice}, #{item.itemCount})
</foreach>
</insert>
批量插入的时候,MySQL的默认max_allowed_packet参数是64MB,如果一次插入的数据特别大,会报PacketTooBigException。这种场景要么分批插入,要么调整数据库参数,我自己一般控制在500到1000条一批,数据量大就循环提交。
4.2 JWT认证与权限控制
后台管理系统的登录鉴权,我推荐使用JWT(JSON Web Token)方案,会话状态不需要存在服务端内存里,集群部署时天然无状态,不用额外引入Redis来共享Session。
大致的认证流程:
- 用户提交用户名密码,后端校验通过后,用JWT工具类生成一个token,token的payload中包含用户ID和用户名。
- 前端拿到token后,存在localStorage里,每次axios请求通过请求拦截器加到Authorization请求头。
- 后端用拦截器统一拦截需要鉴权的接口,解析token并校验合法性,校验通过把当前用户信息放入请求上下文。
- 对于权限控制,在接口的Handler方法上加上权限注解,由拦截器比对当前用户拥有的权限码。
不过要特别注意token跨域的问题。拦截器处理时,为了让前端能读取自定义header,跨域配置要显式允许Authorization头。
Cookie跨域相比,localStorage+Authorization头的方式更常见,后者前端的代码里只需要一个请求拦截器就能全局加headers:
javascript复制service.interceptors.request.use(config => {
const token = localStorage.getItem('token')
if (token) {
config.headers.Authorization = 'Bearer ' + token
}
return config
})
4.3 联调中的坑:跨域、日期、金额格式化
前后端联调是这个项目里最消耗精力的阶段,几乎每天都有几个报错要查。比较有代表性的问题有三个。
跨域问题是第一个拦路虎。前端跑在5173端口,后端跑在8080端口,浏览器同源策略直接拦截,开发环境一个经典的处理方案是在SpringBoot里配置跨域:
java复制@Configuration
public class CorsConfig {
@Bean
public CorsFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOriginPattern("*");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
config.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}
需要注意如果allowCredentials(true)和addAllowedOriginPattern("*")是搭配使用的,不能换成addAllowedOrigin("*"),否则会被浏览器拒绝,因为通配符Origin和凭证模式互斥。这是一个真正在开发中困扰大量新手的问题。
日期格式是第二个坑。后端LocalDateTime默认序列化出来是一串数组:[2024, 12, 20, 10, 30, 15],前端根本没法直接用。所以application.yml里必须配置日期格式化:
yaml复制spring:
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: Asia/Shanghai
另外如果实体字段上是Java8时间类型LocalDateTime,光有date-format还不够,还需要加依赖jackson-datatype-jsr310,SpringBoot 2.x之后默认已经包含,但如果你用的老版本没引进来,也会出现序列化异常。
金额是第三个坑,一句话总结:数据库里用DECIMAL(10,2),Java实体里用BigDecimal,前端展示时用toFixed(2)。千万别用double来存金额,浮点数在二进制里无法精确表示,0.1加0.2等于0.30000000000000004,这在收费场景是完全不可接受的。
5. 部署与运维:从开发环境到真正上线要过的坎
5.1 服务器上MySQL的安装配置与字符集坑
开发环境跑通了,部署才是真正的考验。很多项目在开发环境好好的,一到服务器上就各种莫名其妙。MySQL安装是我见过踩坑最多的环节之一。
如果你在CentOS或Ubuntu上安装MySQL的话,不同系统的安装命令不一样,但装完之后有几个共通的必查项:
bash复制systemctl status mysqld
首次安装MySQL 8.x后,默认root账号的密码是随机生成的,留在/var/log/mysqld.log日志里。用临时密码登录后,必须立刻改密码,同时把密码校验策略调整到合适级别。
修改密码并允许远程访问:
sql复制ALTER USER 'root'@'localhost' IDENTIFIED BY 'YourPassw0rd';
CREATE USER 'hospital'@'%' IDENTIFIED BY 'Hosp#2024Pass';
GRANT ALL PRIVILEGES ON hospital_admin.* TO 'hospital'@'%';
FLUSH PRIVILEGES;
字符集调整可以在MySQL配置文件/etc/my.cnf中设置:
ini复制[mysqld]
character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci
设置完重启MySQL服务,然后去建库:
sql复制CREATE DATABASE hospital_admin DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
这里强烈建议:建库的时候就把字符集定死,不要等数据已经写进去之后再改。中途改库表字符集要重建表和索引,数据量一大,耗时很长,还有可能锁表影响业务。
5.2 SpringBoot应用低内存环境下的JVM参数调优
很多学生或者小型项目的服务器配置不高,一台2G内存的云主机,同时跑MySQL和SpringBoot应用,稍不注意就会遇到一个经典的报错:
code复制java.lang.OutOfMemoryError: Insufficient memory
这个报错经常出现在JVM启动阶段,因为默认的JVM内存设置是根据服务器物理内存的1/4来分配的,2G内存的机器,JVM默认堆就给了512M,加上元空间、线程栈、GC空间,容易瞬间把内存耗尽。
我自己的实践是,启动SpringBoot Jar包时直接用JVM参数显式指定内存:
bash复制java -Xms256m -Xmx512m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=128m -jar hospital-admin.jar
对于医院后台管理系统这种并发量不高的应用,256到512M的堆完全够用,给操作系统和MySQL留足空间,反而能让整体更稳定。
还有一个小经验:生产环境建议加-XX:+HeapDumpOnOutOfMemoryError参数,JVM内存溢出时自动导出堆转储文件,排查内存问题时能少走很多弯路。
5.3 用Docker部署前后端
如果服务器上装了Docker,部署可以更优雅一些。把SpringBoot的Jar包打成镜像,前端构建产物用Nginx镜像托管,再用docker-compose把MySQL、后端、前端组合起来。
前端项目的Dockerfile:
dockerfile复制FROM node:18-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
nginx.conf里除了托管前端静态文件之外,还要将/api路径的请求反向代理到后端的8080端口:
nginx复制server {
listen 80;
server_name localhost;
location / {
root /usr/share/nginx/html;
index index.html;
try_files $uri $uri/ /index.html;
}
location /api/ {
proxy_pass http://backend:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
这里try_files $uri $uri/ /index.html必须加上,否则前端Vue用的是history路由模式,用户手动刷新某个子页面时,Nginx会返回404。
后端镜像的Dockerfile更直接:
dockerfile复制FROM eclipse-temurin:17-jre
WORKDIR /app
COPY target/hospital-admin.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-Xms256m", "-Xmx512m", "-jar", "app.jar"]
用docker-compose统一编排:
yaml复制version: '3.8'
services:
mysql:
image: mysql:8.0
container_name: hospital-mysql
environment:
MYSQL_ROOT_PASSWORD: root123456
MYSQL_DATABASE: hospital_admin
ports:
- "3306:3306"
volumes:
- mysql-data:/var/lib/mysql
backend:
build: ./backend
container_name: hospital-backend
depends_on:
- mysql
ports:
- "8080:8080"
environment:
SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/hospital_admin?useUnicode=true&characterEncoding=utf8&useSSL=false
SPRING_DATASOURCE_USERNAME: root
SPRING_DATASOURCE_PASSWORD: root123456
frontend:
build: ./frontend
container_name: hospital-frontend
depends_on:
- backend
ports:
- "80:80"
volumes:
mysql-data:
后端容器里访问MySQL,数据库地址不能用localhost,因为容器和宿主机是隔离的网络空间,必须用docker-compose服务的名字mysql作为主机名。这是一个非常典型的部署坑。
6. 项目扩展方向与学习建议
6.1 从单体到微服务的演进思路
医院后台管理系统做完之后,很多人会问下一步怎么走。我建议先把单体架构下的工程结构、代码规范、MySQL索引优化这些基本功打扎实,再谈微服务。
微服务不是目的,是解决特定组织规模和流量场景的手段。如果医院业务增大,比如同时要支撑多个院区,或者要对接院外的预约平台、医保接口,可以从这几个点逐步演进:
- 把系统管理独立成服务:基于Spring Cloud Alibaba的Nacos注册中心分发模块,将系统管理模块单独拆出一个服务。
- 引入消息队列:挂号、收费这些并发量较高的写操作,用RabbitMQ或RocketMQ异步削峰。
- 引入Redis缓存:医生排班、药品字典这类读多写少的数据,用Redis做一级缓存,减轻MySQL压力。
只是这一切的前提是:单体架构下的模块划分已经清晰,数据库表已经按领域拆好了,否则贸然拆微服务只会把一个简单问题变复杂。
6.2 针对不同基础的开发者,技能学习路线
这个项目很适合做"学习型项目",我对不同基础的同学有不同的建议:
Java基础薄弱的:先把SpringBoot注解机制搞清楚。@RestController、@Service、@Mapper这些注解分别做了什么,为什么标注了@Transactional方法就能回滚事务。不懂这些,写出来的代码能跑,但出了问题无从下手。
刚接触Vue3的同学:重点掌握setup语法糖、组合式API(ref、reactive、computed)、Pinia这三大块,Element Plus组件库边用边查就行,不需要背组件API,善用官方文档的效率远高于硬记。
数据库基础较弱的:先把MySQL的索引底层原理过一遍,搞清楚InnoDB的B+树结构和最左前缀原则,不然你写的SQL在数据量到几十万的时候会突然变慢,却不知道问题出在哪。
项目本身是一个完美的学习载体,从需求到建表、从接口到前端页面、从联调到部署,全链路覆盖,几乎是Java后端入门到进阶的最优路径。
我在实际做这个项目的时候,最大的体会是:后台管理系统不存在很高深的技术难点,麻烦全部来自细节,时区、编码、跨域、版本、大小写,这些细节任何一个没处理到位,都会变成上线前的拦路虎。如果这个项目能跑通、部署上服务器、稳定运行一两个月,你对这套技术栈的理解会有质的飞跃。
