ARTICLE DETAIL

资讯详情

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

基于SpringBoot+Vue的医院后台管理系统设计与实现

基于SpringBoot+Vue的医院后台管理系统设计与实现 1. 先把需求盘清楚医院后台管理系统的六个核心模块做毕设选医院后台管理系统几乎成了Java Web方向的一个经典款。每年都有人做每年答辩时老师问的问题也差不多你的系统到底管了什么哪些模块是真正跑通的业务逻辑哪些只是凑数的CRUD如果你手上也有一份类似的SpringBootVue完整项目源码我建议你别急着点运行先把需求层面的事情想明白。这个系统本质上不是给你练习增删改查的它要模拟的是一家医院门诊部在接待患者过程中的全部后台操作。我拆了一下核心模块大概六个系统管理、科室医生管理、患者档案管理、预约挂号管理、病历处方管理、药品库存与收费统计。下面这张表能比较清楚地看到每个模块对应的业务实体和主要页面动作模块核心实体主要页面功能关键业务动作系统管理用户、角色、菜单用户列表、角色分配、菜单权限登录鉴权、权限拦截科室与医生科室、医生科室列表、医生排班科室与医生的关联维护患者档案患者患者信息录入、查询患者ID生成、信息修改预约挂号挂号单号源管理、挂号记录排班号源校验、防重复挂号病历处方病历、处方病历创建、处方开立诊断记录与处方关联药品与收费药品、收费单药品库存管理、收费结算库存扣减、收费状态变更为什么要刻意把模块盘这么细因为很多毕设源码的问题恰恰出在这里菜单栏写了七八个入口点进去全是同一个表格模板没有真实的业务流转。比如预约挂号如果只是往表里insert一条记录不校验当天该医生还有没有号那答辩时老师一问就穿帮了。所以做这个项目时我给自己定的原则是模块可以不多但每个模块里必须有一条能走通的业务线。挂号要能挂满号源病历要能关联到处方处方要能扣减库存收费要能改变挂号单状态。系统管理模块单独做一套RBAC权限模型把用户、角色、菜单三张表通过关联表串起来这样答辩时也有得讲。2. 技术选型为什么不纠结SpringBootVue 各自解决什么先说结论SpringBootVueMySQL这组组合做Java Web毕设在现阶段几乎是最省心的方案没有之一。不是说它一定比别的方案更先进而是它把前后端开发过程中的烦心事儿压到了最低。后端用SpringBoot核心原因有三个。第一它内置了Tomcat打包成jar直接java -jar就能跑不用像传统SSM项目那样还得单独部署外部Tomcat这对环境配置能力偏弱的毕设学生太友好了。第二SpringBoot的起步依赖机制把你常用的框架组合都封装好了引入spring-boot-starter-web就自带了Spring MVC和Jackson引入mybatis-plus-boot-starter就自动配好了MyBatis Plus的数据源自动配置帮你省掉了大量XML配置。第三SpringBoot自带的application.yml把所有配置集中在一起数据库连接、端口、上传大小限制都清晰可见排查问题的时候非常直接。前端用Vue也不难理解。Vue的组件化开发方式天然适配后台管理这种页面高度相似的场景配合Element Plus组件库表格、表单、弹窗、分页这些管理后台的高频组件全部开箱即用。更重要的是Vue使用响应式数据绑定表单的各种联动、表格数据的实时刷新写起来非常顺手不用像jQuery时代那样手动操作DOM。但选型这件事不能只看单个框架好不好还要看前后端怎么协作。这套系统的开发期协作模式是这样的前端Vue开发服务器 http://localhost:8080 后端SpringBoot服务 http://localhost:8081Vue的dev server通过proxy配置把/api开头的请求转发到8081端口这样前端代码里所有请求都写相对路径不写完整域名。好处是本地联调时不用处理CORS跨域因为浏览器看到的请求是同源的部署生产环境时前端打包后的dist目录和后端jar包可以放在同一个服务器上由Nginx统一管理或者直接把dist复制到SpringBoot的static目录下改动极小。项目骨架方面我自己习惯把整个工程分成两个独立目录后端是一个标准的Maven项目前端是一个Vue3Vite项目。后端目录结构大致如下hospital-backend ├── src/main/java/com/hospital │ ├── controller # 控制层接收前端请求 │ ├── service # 业务层核心业务逻辑 │ ├── mapper # 数据访问层MyBatis Plus的Mapper接口 │ ├── entity # 实体类与数据库表对应 │ ├── common # 通用类统一返回结构、异常处理、工具类 │ ├── config # 配置类拦截器、CORS、Knife4j │ └── HospitalApplication.java ├── src/main/resources │ ├── mapper # 复杂的SQL语句XML │ └── application.yml └── pom.xml这个结构遵循的就是SpringBoot的分层约定控制层只管参数接收和结果返回业务层管真正的逻辑数据访问层用MyBatis Plus的BaseMapper接口搞定大部分单表操作只有统计报表这类复杂查询才写到XML里。分层干净了后续接口文档的编写和测试也会顺利很多。3. SQL脚本里的数据建模建表顺序与关键字段的取舍拿到这套项目的SQL脚本先别急着执行。我建议你先打开脚本文件从头到尾过一遍建表顺序因为建表顺序本身就反映了数据建模的思考逻辑。好的脚本一定是先建基础表再建业务表最后建关联表这样才能保证外键引用和业务数据有承载。我的建议顺序是这样基础表sys_user用户、sys_role角色、sys_menu菜单这是权限模型的地基。关联表sys_user_role、sys_role_menu把用户、角色、菜单串起来。业务主表department科室、doctor医生、patient患者、medicine药品。业务流水表appointment挂号单、medical_record病历、prescription处方。处方明细与收费prescription_item、payment。这里有一个很容易忽略的点顺序不是随便定的因为后面业务表的外键会引用前面的主键。比如doctor表里通常会有department_id字段引用科室IDpatient表里的create_by引用用户ID如果顺序反了创建表时就会报外键不存在。字段设计上我重点说一下几个最有讲究的地方。密码字段不能存明文。sys_user表里的密码我用的BCrypt加密存储登录时用BCryptPasswordEncoder.matches()校验。如果SQL脚本里看到的是明文密码那说明这个项目在安全维度上基本是不合格的答辩时老师一旦从浏览器开发者工具里看到返回的密码字段内容会非常尴尬。金额字段必须用decimal。medicine表里的单价、payment表里的金额我用的是decimal(10,2)禁止用float或double。浮点数在计算机里本身有精度问题金额计算出现0.10.2不等于0.3这种离谱结果的时候哭都来不及。预约挂号防重复需要唯一约束和状态校验配合。appointment表我加了一个(patient_id, doctor_id, visit_date)的联合唯一索引并且挂号状态字段cur_status有取值范围0已取消、1已预约、2已就诊、3已退号。插入挂号记录前业务层先查一次当天该医生号源是否已满再查patient_id和doctor_id当天是否存在有效记录如果已有1状态的记录直接抛业务异常。患者ID用业务规则生成。患者主键虽然是自增ID但医院场景的患者编号是给前台人员看的需要可读性。我用了P前缀加时间戳后六位比如P20240615000001这样不看数据库也能大致知道建档时间。核心的appointment建表脚本可以参考这个思路CREATE TABLE appointment ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT 自增主键, patient_id BIGINT NOT NULL COMMENT 患者ID, doctor_id BIGINT NOT NULL COMMENT 医生ID, visit_date DATE NOT NULL COMMENT 就诊日期, time_slot VARCHAR(20) NOT NULL COMMENT 时段AM/PM, cur_status TINYINT NOT NULL DEFAULT 1 COMMENT 状态0取消 1预约 2就诊 3退号, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_patient_doctor_date (patient_id, doctor_id, visit_date) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT预约挂号表;另外每张表我都加了create_time和update_time两个审计字段MyBatis Plus用MetaObjectHandler自动填充这样查问题或者做统计的时候能知道数据是什么时候产生的在后面做门诊量统计报表时会非常有用。网站上很多管理后台项目表里没有审计字段缺了它后续数据回溯基本无从下手。4. 接口文档怎么写才能让前后端不打架很多毕设项目都有接口文档但大多数只是从网上找的模板或者让AI生成一式两份的Swagger注解根本没有投入到实际开发中使用。接口文档这件事它的本质是前后端之间的契约前端根据文档确定请求参数和返回结构后端根据文档实现接口两边对这个契约有一致的理解联调时才不会你等我我等你你传参格式对不上他也没法接。这套系统的接口文档我是从统一返回结构开始定义的。所有接口都返回下面的结构{ code: 200, message: 操作成功, data: {} }code约定为200成功、400参数错误、401未登录或token过期、403无权限、500服务器异常。data字段可以是对象、数组或者空。前端axios的响应拦截器只用判断code不是200就提示message业务逻辑和错误处理完全解耦。接口路径遵循RESTful风格统一加/api/v1前缀后面跟资源名称。比如方法路径说明POST/api/v1/auth/login登录GET/api/v1/doctors?departmentId1按科室查医生列表POST/api/v1/appointments创建挂号单PUT/api/v1/appointments/{id}/status更新挂号状态GET/api/v1/statistics/outpatient门诊量统计接口文档中每个接口至少包含请求地址、请求方式、请求参数表、成功响应示例、失败响应示例五部分。比如登录接口POST /api/v1/auth/login Content-Type: application/json { username: root, password: 123456 }成功响应{ code: 200, message: 登录成功, data: { token: eyJhbGciOiJIUzI1NiJ..., userInfo: { id: 1, username: root, realName: 系统管理员, roles: [admin] } } }后端的登录Controller实现也很简单核心逻辑放在service层Controller只做参数接收和结果封装PostMapping(/auth/login) public ResultLoginVO login(RequestBody Valid LoginDTO dto) { LoginVO vo authService.login(dto.getUsername(), dto.getPassword()); return Result.ok(vo); }登录成功后后端返回JWT token前端把它存在localStorage里axios请求拦截器自动加上Authorization: Bearer token这个请求头后端再用拦截器统一校验token有效性有效才放行。拦截器逻辑大致是public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token request.getHeader(Authorization); if (token ! null token.startsWith(Bearer ) jwtUtil.validate(token)) { // 解析出用户信息放到ThreadLocal供后续使用 return true; } response.setStatus(401); response.setContentType(application/json;charsetUTF-8); response.getWriter().write({\code\:401,\message\:\未登录或登录已过期\}); return false; }接口文档编写还有个实用技巧如果项目里集成了Knife4jSwagger注解生成的在线文档到后期维护成本会越来越低代码里一改注释在线文档就同步更新。但手写的接口文档仍然是答辩时给老师翻阅的最直接材料两者建议同时维护。接口文档放在项目docs目录下和源码、SQL脚本打包在一起正好对应标题里完整项目源码SQL脚本接口文档的三件套。5. 前端页面怎么写才不像管理后台模板路由、请求与组件的组织逻辑Vue前端这部分是很多人眼中的体力活其实它也有自己的组织学问。直接照着管理后台模板套页面不难但要把页面组织得清晰、可维护、权限到位需要动点脑筋。先看前端目录结构hospital-frontend ├── src │ ├── api │ │ ├── auth.js │ │ ├── doctor.js │ │ ├── patient.js │ │ └── appointment.js │ ├── assets │ ├── components │ │ ├── Pagination.vue │ │ └── SearchForm.vue │ ├── layout │ │ └── MainLayout.vue │ ├── router │ │ └── index.js │ ├── store │ ├── views │ │ ├── login/index.vue │ │ ├── system │ │ ├── medical │ │ └── dashboard/index.vue │ ├── utils │ │ ├── request.js │ │ └── auth.js │ └── main.js这里要重点说两个东西路由设计、请求封装。路由设计上我采用的是静态路由加动态路由结合。登录页、404页是静态路由其余业务页面全部放在动态路由里。用户登录成功后后端根据角色返回该用户有权限的菜单列表前端拿到列表后用router.addRoute()动态添加路由。这样做的好处是没有权限的用户即使手动在地址栏输入/system/user路由表里根本没有这个路径自然就拦截住了。配合前端路由守卫beforeEach里校验token是否存在实现双重保护。动态路由的菜单结构从后端接口获取格式类似于[{ path: /system, title: 系统管理, icon: , children: [...] }]前端根据这个结构渲染侧边栏菜单成功做到了菜单跟着权限走。请求封装上request.js是所有接口请求的统一出口。我在axios实例上配置了baseURL为/api/v1设置了请求超时时间和请求拦截器添加token响应拦截器里做了一层统一的错误处理如果code是401清除本地用户信息并跳转到登录页如果code是403提示没有操作权限其他非200的code统一弹ElMessage.error。这样做的好处是业务代码里不用处理任何错误分支写接口请求时只需关心成功时的数据// api/doctor.js import request from /utils/request export function getDoctorList(params) { return request.get(/doctors, { params }) }页面组件里调用时拿到的就是response.data已经是data字段里的内容因为响应拦截器直接返回了解包后的数据。页面组件组织上管理后台最常见的形态是搜索条件 表格 分页 新增/编辑弹窗。这四件套在科室、医生、患者、药品等管理页面上高度重复。我将它抽象成几个通用组件后新写一个管理页面只需要写搜索字段配置和表格列配置加一个表单配置再绑上对应的增删改查接口页面的骨架代码量能少掉六成。但要提醒一句业务系统里翻来覆去的复用组件最怕的就是为了通用把组件配置写得无比复杂配置比页面本身还难懂。折中的做法是只在确实重复三遍以上的场景做抽象写的时候预留插槽和配置项不要一上来就是general目的的大一统组件。如果你拿到的是这份源码检验前端水平最好的方式就是看api目录和views目录里的业务代码是否规范而不是看用了多少高级语法。清晰的api函数封装、表格数据流、弹窗表单组件这套结构打通之后新增一个管理模块就是复制粘贴改配置的活儿效率非常可观。6. 联调阶段最容易翻车的三个地方跨域、token刷新、history模式404最后写写真实的项目落地环节。代码写完后从能跑通到能交付联调阶段往往是翻车重灾区。我在这套系统的调试过程中碰到过三个高频问题这里一个个说清楚。第一个是跨域问题。开发期Vue的dev server在8080端口后端在8081端口浏览器里前端页面发请求到8081天然跨域。解决方法是在vite.config.js里配置proxy代理把请求代理到后端端口而不是去后端写一堆CrossOrigin注解// vite.config.js server: { port: 8080, proxy: { /api: { target: http://localhost:8081, changeOrigin: true } } }注意这里changeOrigin必须设为true否则后端拿到请求的Host还是8080某些框架校验会出问题。很多新手在这里卡半天最后发现是少写了这一个配置项。第二个是token刷新问题。本地登录后token存到了localStorage但页面一刷新前端状态管理里的用户信息就丢了菜单也没了页面还报401。典型的坑是后端动态路由的addRoute过程只在登录后的初始化流程里执行了一次刷新页面后初始化流程没走路由没有被重新注册。解决思路是在beforeEach里判断当前是否已登录登录了但状态管理里没有用户信息时先调用获取当前用户信息接口拿到用户数据和权限菜单后重新注册动态路由再放行。更隐蔽的情况是后端拦截器校验token过期了前端收到401后跳转到登录页登录页又把用户带回了原来的页面。这里要注意跳转登录页时要带上redirect参数否则用户每次都回到首页体验很差。第三个是history模式刷新404。Vue Router默认用history模式时地址栏路径是真实的浏览器路径但静态服务器上根本不存在/system/user这个文件刷新时服务器直接返回404。两个选型要么把Vue Router改成hash模式路径带#号不需要服务器配合要么在Nginx里做try_files回退。如果你选择部署到Nginx配置基本是这个样子server { listen 80; server_name localhost; root /usr/share/nginx/html; index index.html; location / { try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://localhost:8081; } }try_files让所有不存在的路径都回到index.htmlVue Router接管后再根据路由渲染对应页面/api开头的请求反向代理到后端8081端口。这个配置同时解决了前端静态页面和历史模式刷新404的问题。另外还有一个小建议是部署方式上的。如果你不想多维护一个Nginx可以直接用SpringBoot来托管前端静态资源把前端build生成的dist目录里的内容复制到SpringBoot项目的src/main/resources/static目录下重新打jar包这样一个8081端口就同时提供前端页面和后端接口服务适合毕设演示场景只需要一条java -jar hospital-server.jar命令就能全部跑起来。我做完这套项目后最大的体会是严谨的数据建模、统一接口约定、清晰的前端分层这三件事比任何花哨的技术点都重要。代码能跑只是底线答辩时老师问到的数据流、设计决策、异常处理全都来自你实际做项目时考虑过的问题。愿你也能顺着这条思路把这个经典选题做出自己的亮点来。
返回列表