ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

基于SSM的医院体检管理系统毕设资源解析:JWT登录与权限控制

基于SSM的医院体检管理系统毕设资源解析:JWT登录与权限控制

简介:基于SSM框架的医院体检管理系统完整毕业设计项目,面向计算机专业学生与Java Web学习者,可满足毕业设计、课程设计或期末大作业需求。系统涵盖体检套餐管理、预约登记、项目检查、报告录入等核心业务,采用Spring、SpringMVC、MyBatis整合架构,配合Vue前端实现前后端分离开发。代码完整且可运行,配有SQL数据库脚本和启动脚本,经导师指导评审获高分,适合直接作为项目模板或实战练手素材,也可快速修改用于其他管理类系统。

压缩包共855个文件,大小仅25.46MB,文件类型涵盖Java源码、Vue组件、JavaScript逻辑、SQL数据库脚本、XML配置,以及png/jpg图片素材、mp4演示视频等,覆盖从后端逻辑到前端页面、从环境部署到运行演示的完整链路。资源附数据库结构说明、部署文档和操作视频,可帮助快速搭建环境并理解SSM项目开发流程。已有101人学习下载,适合需要参考真实完整项目的学生,是毕业设计答辩与SSM实战入门的高性价比资料。

1. 基于SSM的医院体检管理系统:这份毕设资源值不值得下

先给个明确结论:如果你正在用 Java 做毕业设计或课程设计,且选题范围锁定在 Web 管理系统,这套基于 SSM 的医院体检管理系统,属于典型"拿来就能跑、改改就能交"的资源。项目采用 Spring + SpringMVC + MyBatis 组合,前端保留了传统 JSP 页面,另外带了一套 Vue 改造版页面,两者通过同一套后端接口联动。资源里包含完整源码和数据库脚本,启动入口做成三个 bat 文件,1-install.bat 装依赖、2-run.bat 起服务、3-build.bat 打包,基本把从零到运行的过程压成了三下双击。对"导师催进度、自己还没把 SSM 串明白"的计算机专业学生来说,这套代码的价值不在于功能有多炫,而在于它把登录鉴权、角色权限、体检订单流转、检查项管理这些毕设高频模块都串好了,能让你把省下来的时间花在论文和答辩 PPT 上。全文我会按源码结构、启动流程、登录链路、数据库设计、踩坑记录、二次开发顺序拆完,读完你不仅知道它怎么跑起来,还知道改哪里能变成你自己的东西。

2. 项目结构与启动链路:从源码目录到本地跑起来

拿到资源压缩包,先别急着双击任何 bat 文件。先花五分钟把目录结构看一遍,搞清楚每个文件是干什么的,后面排错能少走一半弯路。

2.1 从残留的 .bak 文件反推项目改造轨迹

解压后你会看到一批.bak后缀文件,比如main.css.bak、update-password.vue.bak、IndexAsideStatic.vue.bak。这些不是垃圾文件,也不是打包失误,它们是上一轮开发者修改前端时留下的备份。.bak的存在本身说明两件事:这个项目经历过从纯 JSP 到 JSP + Vue 混合的改造过程;改造不是一次推翻重来,而是直接在原文件上改完备份一份。对我这类习惯"改前先备份"的人来说,看到.bak文件反而觉得踏实。

具体到功能归属:

  • update-password.vue.bak对应 Vue 端的修改密码页面,说明前端菜单里"个人中心 > 修改密码"这个入口是后补的;
  • IndexAsideStatic.vue.bak对应左侧菜单栏组件,带 Static 后缀意味着这个版本的菜单是静态写死配置的,不是从后端接口动态加载的;
  • BreadCrumbs.vue.bak和IndexHeader.vue.bak分别是面包屑导航和顶部栏组件,这两个是管理后台的通用骨架部分。

我说一下这套结构里最常见的启动失败原因:JDK 版本不匹配。.classpath文件和.project文件是 Eclipse 的项目描述文件,里面记录了项目依赖的 JRE 版本。如果文件里写的是 JDK 1.8,而你机器装的是 JDK 11 甚至 17,MyEclipse 或 Eclipse 导入项目后会直接报Unsupported major.minor version的编译错。解决方式不是换 JDK,而是右键项目 > Properties > Java Build Path > 修改 JRE System Library 为你本机版本,再看 Compiler 级别是否对齐。

2.2 三个批处理脚本,先懂再跑

资源根目录下有1-install.bat、2-run.bat、3-build.bat三个脚本。很多人拿到手就双击,结果1-install.bat窗口一闪而过,什么也没发生。这不是脚本坏了,而是 bat 脚本里的 Maven 命令可能因为本地仓库路径、镜像源或 JDK 环境变量的问题静默失败了。建议你先用文本编辑器打开这些脚本,看里面的具体命令。

一个典型的1-install.bat内容大致如下:

mvn clean install -DskipTests echo "install done!" pause

逻辑很简单:先清理旧编译产物,再执行 Maven 打包并跳过单元测试,最后暂停等待用户按键。pause的作用是让窗口停在屏幕上,方便你截图看报错信息。如果双击窗口仍然闪退,说明mvn命令没有被系统识别,也就是 MAVEN_HOME 环境变量没配置或者 Maven 版本过老。我一般建议在命令行手动执行一次:

mvn -v

这一步能同时验证两件事:Maven 是否安装成功、当前生效的 JDK 是哪个版本。如果mvn -v输出的 Java version 和你预期不一致,大概率是JAVA_HOME指到了别的 JDK 安装目录。这种问题改脚本是没用的,去系统环境变量里把JAVA_HOME指正即可。

2-run.bat通常是启动 Tomcat 的命令,常见内容无非是:

mvn tomcat7:run

注意这里用的是tomcat7插件,不是 Spring Boot 那种内嵌容器。也就是说这个项目是一个传统 war 包项目,最终部署目标是外部 Servlet 容器。用tomcat7:run启动时,项目不会打包成 war 文件,而是直接把当前工程挂到插件内置的 Tomcat 7 容器上运行。这对大部分毕设场景够用了,但有一个隐患:如果你本机装了独立的 Tomcat 9,并且和这个项目里的 Servlet API 版本有冲突,运行期会抛NoSuchMethodError之类的问题。

3-build.bat对应打包操作:

mvn package -DskipTests

执行完后target目录下会生成 war 包,把这个 war 丢到独立 Tomcat 的webapps目录就能外部部署。这块我要强调一个细节:mvn package默认不打进源码和资源文件之外的依赖,依赖全部从 Maven 中央仓库拉取。如果有些依赖在国内网络环境下拉取超时,建议在pom.xml所在的目录下建一个settings.xml,配好阿里云镜像源再跑脚本。

2.3 导入 Eclipse / MyEclipse 的具体步骤

这套项目用的是 Eclipse 系列 IDE 的标准工程结构,不是 IDEA 的 Gradle 工程。如果你日常用的是 IntelliJ IDEA,导入方式要选File > New > Project from Existing Sources,然后选中pom.xml,让 IDEA 按 Maven 模型重新索引。这里有一个 ID 和 Eclipse 行为不一致的地方:IDEA 默认会自动下载依赖并建立索引,Eclipse 需要右键项目 > Maven > Update Project 才会强制刷新依赖。

我建议的操作顺序是:

mvn clean install -DskipTests

确认本地仓库已经拉齐依赖后,再打开 IDE 导入工程。这个顺序能避免 IDE 里一片红色报错却分不清是代码问题还是依赖没拉下来的问题。常见做法是先命令行验证编译通过,再导入 IDE。

2.4 启动后页面长什么样

默认端口是 8080,Tomcat 插件配置的 context path 一般在 pom.xml 里写死。启动完成后,浏览器访问的是:

http://localhost:8080/项目名/login.jsp

登录页属于传统 JSP 页面,用户体系分为管理员、医生、用户三种角色,对应的路由跳转逻辑会在后面讲 JWT 登录时展开。到这里,项目已经能跑起来了。但如果你在启动阶段就遇到各种报错,别急,我后面有一整章专门讲避坑,先把当前这条链路走通再往下看。

3. JWT 登录与权限控制:从表设计到拦截器校验

SSM 项目最常见的登录方案无非三种:Session 会话保持、Shiro 安全框架、JWT 令牌。这个项目用的是 JWT,这一点在数据结构上体现得很明显,也恰恰是它能从传统 JSP 页面平滑过渡到 Vue 改造版页面的原因。你可以理解为:后端签发一个加密令牌给前端,前端每次请求把令牌带在 Header 里,后端拦截器校验令牌合法性,不用在服务端存登录状态。

3.1 用户表与角色字段的设计逻辑

登录模块相关的核心表是用户表,我按常见字段结构还原一下(具体字段名请以资源里的 SQL 脚本为准):

CREATE TABLE `user` ( `id` INT(11) NOT NULL AUTO_INCREMENT, `username` VARCHAR(50) NOT NULL COMMENT '登录名', `password` VARCHAR(255) NOT NULL COMMENT '加密后的密码', `role` INT(11) NOT NULL COMMENT '角色:1管理员 2医生 3用户', `real_name` VARCHAR(50) DEFAULT NULL COMMENT '真实姓名', `phone` VARCHAR(20) DEFAULT NULL COMMENT '手机号', `status` INT(11) DEFAULT 1 COMMENT '1启用 0禁用', PRIMARY KEY (`id`), UNIQUE KEY `uk_username` (`username`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;

这个表有几个值得注意的设计点。password字段存储的不是纯文本,而是某种加密后的密文,常见的做法是 MD5 加盐或 BCrypt。如果是 MD5 加盐,盐值通常会拼接在密码字段内部,比如salt + "$" + md5(salt + password)的格式;如果是 BCrypt,密码本身就是一段带随机盐的哈希串,前端传明文,后端校验时用工具类比对。你拿到源码后第一件事应该去service层找到登录接口实现类,看密码比对用的哪个工具类,因为论文里的"系统安全性分析"部分要写这一段。

role字段直接决定登录后的跳转目标。管理员进后台管理页,医生进体检结果录入页,普通用户进预约和个人报告页。如果不想引入 Spring Security 或 Shiro 这类重量级框架,一个role字段配合前端路由判断就是最简单的权限控制方案。

3.2 JWT 的生成与校验核心代码

项目拦截器层通常叫JwtInterceptor或TokenInterceptor,看一下核心方法:

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 放行登录接口 if (request.getRequestURI().contains("/login")) { return true; } // 从请求头取 token,常见 key 是 Authorization 或 token String token = request.getHeader("Authorization"); if (token == null || token.isEmpty()) { response.getWriter().write("{\"code\":401,\"msg\":\"未登录\"}"); return false; } // 解析 token 并校验签名 try { Claims claims = JwtUtil.parseJWT(token); request.setAttribute("userId", claims.get("userId")); request.setAttribute("role", claims.get("role")); return true; } catch (Exception e) { response.getWriter().write("{\"code\":401,\"msg\":\"token无效或已过期\"}"); return false; } }

逻辑说明:第一步放行/login路径,否则登录接口自己都过不了拦截器会形成死循环;第二步从请求头取 token,这里有个前后端约定问题,用 Vue 改造版页面时,axios 拦截器里必须设置headers: {'Authorization': 'Bearer ' + token},大小写和前缀都要对齐,JWT 工具解析时会去掉前缀;第三步解析 token,成功就把用户 ID 和角色塞进 request 上下文,后续 Controller 直接@RequestAttribute取。

参数说明:JwtUtil.parseJWT底层依赖io.jsonwebtoken库,Claims对象里存的就是签发时放进 token 的自定义字段。token 的有效期在JwtUtil里会定义,常见值是EXPIRE_TIME = 1000 * 60 * 60 * 2,也就是两小时。如果测试时频繁掉登录状态,检查是不是这个过期时间设短了。

3.3 登录接口的完整调用链

Controller 层的登录接口大致流程是:接收用户名和密码 → 调 Service 层查询用户 → 校验密码 → 签发 token → 返回给前端。伪代码如下:

@PostMapping("/login") @ResponseBody public Result login(@RequestBody LoginForm form) { User user = userService.findByUsername(form.getUsername()); if (user == null) { return Result.error("用户不存在"); } // 这里用加密工具类校验密码 boolean ok = PasswordUtil.matches(form.getPassword(), user.getPassword()); if (!ok) { return Result.error("密码错误"); } if (user.getStatus() == 0) { return Result.error("账号已被禁用"); } // 签发 token,携带 userId 和 role String token = JwtUtil.createToken(user.getId(), user.getRole(), user.getUsername()); return Result.success(token, user); }

逻辑说明:Result是统一返回体,code=200表示成功,code=500表示业务错误。前端拿到 token 后存入localStorage,每次请求由 axios 拦截器自动附加。user.getRole()拿到的是 int 类型,前端根据这个值渲染不同的菜单项,这就实现了最基础的 RBAC 思路,虽不完整,但对付毕设答辩足够。

关于PasswordUtil有一个坑要提醒:如果数据库脚本里预置的种子账号密码和PasswordUtil的加密规则不一致,你会遇到"明明 SQL 里有这个账号,登录却一直提示密码错误"的怪事。排查方法很简单,写一个临时测试类调用PasswordUtil加密一次,看结果是否和数据库里的一致。不一致就说明 SQL 脚本里的种子密码是拿别的工具生成的,需要手动更新。

3.4 小程序 / 移动端的兼容性预判

这套后端登录接口是纯 REST 风格,返回 JSON。意味着如果答辩时导师要求"做一个简单的移动端页面",你可以不碰后端,直接把登录请求打到这个接口上,用wx.request或小程序原生请求库模拟。关键是把Content-Type: application/json设对,参数按LoginForm的字段名传。

4. 核心业务模块与数据库设计:体检订单流转和数据表关系

登录只是入口,体检管理系统真正撑起论文篇幅的是业务数据流:用户预约体检 → 管理员安排体检项目 → 医生录入各项指标 → 用户查看报告。这个流程对应到数据库,就是一张主订单表配合多张子表的设计模式。

4.1 预约订单表,状态字段决定界面按钮

订单相关的表通常叫health_order或appointment,关键字段如下:

CREATE TABLE `health_order` ( `id` INT(11) NOT NULL AUTO_INCREMENT, `order_no` VARCHAR(32) NOT NULL COMMENT '订单编号,生成策略:时间戳+随机数', `user_id` INT(11) NOT NULL COMMENT '预约用户', `package_id` INT(11) DEFAULT NULL COMMENT '体检套餐ID', `status` TINYINT(4) NOT NULL DEFAULT 0 COMMENT '0待确认 1已确认 2体检完成 3已取消', `appointment_date` DATETIME DEFAULT NULL COMMENT '预约体检时间', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

这个status字段是整个订单模块最容易扩展也最容易出 bug 的地方。前端页面会根据 status 渲染不同操作按钮:0显示"取消预约"按钮,1显示"确认报到"和"开始体检"按钮,2显示"查看报告"按钮。如果你在二次开发时新增了一个状态,比如"已退款",不能只改数据库注释,还要同步改前端 JSP 页面和 Vue 页面的状态判断逻辑,否则会出现后端状态已经变了、前端按钮还是旧行为的情况。数据字典在毕设论文里可以单开一节写,这是凑字数和加图的好素材。

4.2 体检套餐与检查项表的冗余设计

有一个常见的设计误区:把套餐和检查项直接合并成一张表,用逗号分隔存检查项 ID。这在毕设级别的系统里虽然能跑,但答辩时很容易被导师追问"第三范式你考虑了没有"。

合理的方案是三张表:package(套餐)、check_item(检查项)、package_item_rel(关联中间表)。我在源码里也能看到类似的建表痕迹,部分页面已经把套餐下的项目列表单独渲染成一列。关联表的字段设计可以参考:

CREATE TABLE `package_item_rel` ( `id` INT(11) NOT NULL AUTO_INCREMENT, `package_id` INT(11) NOT NULL, `item_id` INT(11) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

为什么不用中间表?两个原因:第一,套餐和检查项是多对多关系,中间表是标准解法;第二,后期如果要做"按检查项统计体检人数"的报表,直接 join 中间表比解析逗号分隔的字符串快得多。SQL 查询的典型写法:

SELECT ci.item_name, COUNT(*) FROM package_item_rel pir LEFT JOIN check_item ci ON pir.item_id = ci.id LEFT JOIN health_order ho ON ho.package_id = pir.package_id GROUP BY ci.item_name;

这段 SQL 的意思是:通过关联表把套餐类型、检查项名称和订单表串起来,再按检查项分组统计数量。这就是体检中心最基础的"哪些项目检查人数最多"统计报表。做这个功能可以在答辩时展示你对多表关联的掌握程度,远比 CURD 加分。

4.3 体检结果表:医生的录入与用户报告查看

体检结果表是整个系统中字段最复杂的一张表,因为不同检查项的结果数据类型不一样。身高体重是数字,血常规是字符串加单位,B 超结论是一段文字描述。横向建表会爆炸,所以通常的做法是拆成主表和明细表:

CREATE TABLE `exam_report` ( `id` INT(11) NOT NULL AUTO_INCREMENT, `order_id` INT(11) NOT NULL COMMENT '关联健康订单', `doctor_id` INT(11) NOT NULL COMMENT '录入医生', `summary` TEXT COMMENT '总检结论', `advice` TEXT COMMENT '健康建议', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

summary和advice字段用TEXT类型是因为内容长度不固定,VARCHAR(255) 不够用。这里有一个容易被忽略的编码问题:字段可以容纳 255 个字符,但如果前端提交了一个包含表情符号的字符串,数据库连接层没有设置characterEncoding=utf8mb4,emoji 会被存成乱码。常见的 MySQL 连接串应该写成:

jdbc:mysql://localhost:3306/health?useUnicode=true&characterEncoding=utf8mb4&serverTimezone=Asia/Shanghai

serverTimezone参数在 MySQL 8.x 版本下必须加,否则驱动会报时区错误。useSSL=false则是为了避免开发环境下 SSL 握手警告。

4.4 数据库脚本的使用细节

拿到sql文件后,不要直接用 Navicat 双击运行。我建议先在命令行里创建好 utf8mb4 编码的数据库,再导入脚本:

CREATE DATABASE `health_db` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;

然后执行source命令导入表结构和种子数据。这一套流程能避免 Navicat 图形界面导入时因客户端默认编码引发的中文乱码问题。种子数据里一般包含管理员的初始账号,常见的默认密码是admin或123456,以脚本里的 INSERT 语句为准。如果导入后发现中文全是问号,重建数据库,用命令行方式再试一遍,九成是编码问题不是脚本问题。

5. 毕设新手避坑:四类高频问题与排查路径

这一章是全文最杀时间的地方。我把在这个项目上最常见的四个坑按"现象 → 原因 → 解决"拆开写,每条都能对应到你双击 bat 或启动 Tomcat 后看到的某个具体报错。

5.1 报错一:tomcat7:run 启动后页面 404

现象:控制台显示 Tomcat 启动成功,浏览器访问http://localhost:8080/却是 404 或空白页。

原因:项目没有被挂载到根路径。tomcat7-maven-plugin默认的path是项目名,也就是说你要访问的是http://localhost:8080/项目名/而不是根路径。另外还有一种更隐蔽的情况:IDE 里没有把项目的 web 资源目录加入部署配置,Tomcat 启动时根本没加载到 JSP 页面。

解决:先在 pom.xml 里看tomcat7-maven-plugin配置:

<plugin> <groupId>org.apache.tomcat.maven</groupId> <artifactId>tomcat7-maven-plugin</artifactId> <version>2.2</version> <configuration> <port>8080</port> <path>/</path> </configuration> </plugin>

如果<path>/</path>已经设置成根路径,访问http://localhost:8080/login.jsp就是正确的。如果path没配,访问地址就要带上 project name。以后每次启动前先看一眼控制台第一行日志,Tomcat 会打印当前的 context path。

5.2 报错二:JDK 版本报 UnsupportedClassVersionError

现象:运行mvn clean install编译通过,但启动时抛java.lang.UnsupportedClassVersionError,后面跟着一串Class ... has been compiled by a more recent version of the Java Runtime。

原因:项目里有些依赖或编译产物是 JDK 11 编译的,你运行时用的是 JDK 8,或者反过来。Maven 编译时用的 JDK 版本和运行 Tomcat 的 JDK 版本不一致。

解决:统一 JDK 版本。检查顺序分三步。

java -version mvn -version echo %JAVA_HOME%

三步输出的 Java 版本如果不一致,就说明 IDE / Maven / 系统环境变量各自指向了不同的 JDK。我习惯在项目的pom.xml里显式声明编译版本:

<properties> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties>

这样 Maven 编译时会忽略系统默认 JDK 版本,强制按 1.8 编译。注意1.8和8是等价的写法,别纠结细节。

5.3 报错三:数据库连接 Access denied or Unknown database

现象:启动后第一次请求数据库相关的接口,控制台抛Access denied for user 'root'@'localhost'或Unknown database 'health_db'。

原因:jdbc.properties或applicationContext.xml里的数据库账号、密码、库名和本地环境不一致。这类问题完全是环境配置导致,和代码逻辑无关。

解决:打开jdbc.properties,核对三项配置:

jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/health_db?useUnicode=true&characterEncoding=utf8mb4&serverTimezone=Asia/Shanghai jdbc.username=root jdbc.password=123456

提示:com.mysql.jdbc.Driver是 MySQL 5.x 的驱动类名,MySQL 8.x 驱动改成com.mysql.cj.jdbc.Driver。如果报错信息里提了Loading class 'com.mysql.jdbc.Driver'相关的警告,把驱动坐标从mysql-connector-java:5.1.x升到8.0.x并改掉 URL 里的参数。

5.4 报错四:前端页面样式加载失败或接口报 CORS 跨域

现象:用 Vue 改造版前端页面单独启动时,页面能打开,但接口请求全部失败,控制台报跨域错误;或者 JSP 页面打开后没有样式,纯文本错乱。

原因:JSP 页面没样式通常是DispatcherServlet拦截了静态资源请求,web.xml里加了/拦截规则但没放行.css、.js。Vue 页面跨域则是前端服务端口和后端 8080 不一致。

解决:先处理 JSP 静态资源放行,在 SpringMVC 配置里加:

<mvc:resources mapping="/static/**" location="/static/"/>

然后处理 Vue 跨域。最简单的方案是在后端加一个全局 CORS 配置类:

@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOrigins("*") .allowedMethods("GET", "POST", "PUT", "DELETE") .allowedHeaders("*"); } }

allowedOrigins("*")表示允许所有来源访问,只适合开发调试,部署上线时应该改成具体的前端域名。allowedMethods列出的方法要和前端 axios 实际使用的方法对上,否则预检请求会失败。把这段配进去,Vue 页面里调接口就不会再报跨域了。真实场景里遇到跨域问题,先看请求头有没有Access-Control-Allow-Origin响应字段,没有就是后端没配跨域,不用去怀疑前端代码。

6. 答辩加分的二次开发:从菜单权限到体检报告导出

代码能跑只是及格线,答辩想要高分,还得让导师看到你"动了脑子改了代码"的痕迹。我在大量毕设项目里发现一个规律:被追问得最少的学生,不是在答辩时口若悬河讲架构的,而是当场演示了一个导师预期之外的细节功能。这一章给三个改动成本低、答辩效果好的切入方向。

6.1 前端菜单按角色动态过滤

原项目的IndexAsideStatic.vue静态菜单是写死的,所有角色登录后看到的菜单一样。这不太合理,因为普通用户不该看到"用户管理"和"医生管理"入口。改造方式是让登录接口把用户角色带回来,前端根据角色过滤菜单数组。核心改动在router/index.js或侧边栏组件的数据初始化:

const allMenus = [ { path: '/dashboard', name: '首页', role: ['1', '2', '3'] }, { path: '/user', name: '用户管理', role: ['1'] }, { path: '/doctor', name: '医生管理', role: ['1'] }, { path: '/order', name: '预约管理', role: ['1', '2', '3'] }, { path: '/report', name: '体检报告', role: ['2', '3'] } ]; // 根据当前用户的角色过滤菜单 function filterMenus(role) { return allMenus.filter(m => m.role.includes(String(role))); }

逻辑说明:allMenus里的role字段是字符串数组,和登录接口返回的role做匹配,决定最终渲染哪些菜单项。这个改动虽然简单,但答辩时你可以说"这是基于角色的访问控制,是对原有静态菜单的增强",比讲"我封装了 axios"更有区分度。

6.2 体检报告导出与 Sheet 命名修正

资源自带的前端页面里有一个体检报告下载入口,但实际执行时可能因为 POI 库版本问题导出乱码。这里先说出问题现象:导出文件能打开,但 Sheet 名因为是中文到了 Excel 里变成Sheet1或者报"文件名非法"。原因通常是 POI 版本对新特性支持不完整。

常见的导出逻辑代码放在 Service 层,用 Apache POI 操作XSSFWorkbook。如果版本太老,中文 Sheet 名会因编码格式问题被替换。修复方式是检查pom.xml中 POI 的版本,至少要用 4.1 以上,并且下载报告时前端 axios 要设置响应类型为 blob:

axios.get('/report/export', { responseType: 'blob' }).then(res => { const blob = new Blob([res.data]); const link = document.createElement('a'); link.href = URL.createObjectURL(blob); link.download = '体检报告.xlsx'; link.click(); });

逻辑说明:responseType: 'blob'是二进制流下载的关键,缺了这一行导出来的文件大概率是乱码或损坏。link.download的xlsx后缀要和后端生成的文件格式一致,否则 Excel 打开时提示格式不匹配。

6.3 后端对体检报告导出的校验

导出报告前后端一定要做权限校验。否则任何登录用户都能导出别人的体检报告,这在答辩演示时是会被追问的安全漏洞。在 Controller 层加一个简单的判断:

@GetMapping("/export/{orderId}") public void export(HttpServletResponse response, @PathVariable Integer orderId, @RequestAttribute Integer userId, @RequestAttribute Integer role) { HealthOrder order = orderService.getById(orderId); if (role != 1 && !order.getUserId().equals(userId)) { throw new BusinessException("无权导出该报告"); } // ... 生成 Excel }

这里的核心是@RequestAttribute Integer userId从 JWT 拦截器塞进请求上下文里取当前登录人。role为 1 的管理员可以导出任意报告,普通用户只能导出订单归属是自己的报告。这一行判断对论文里"系统安全性设计"的内容填充非常有用。

6.4 收尾的习惯

这套资源我前前后后拆过三遍,每一次翻车都出在环境不一致上,要么是 JDK 版本,要么是数据库编码,要么是前端静态资源被拦截。从那以后,我每次拿到新毕设项目的第一件事,不是打开代码读逻辑,而是按"看 pom.xml 依赖版本 → 看 jdbc.properties 连接串 → 看 web.xml 拦截规则"这个顺序走一遍。三个文件看完,这个项目能不能跑、哪里可能出问题,心里基本有数。希望这个排查顺序也帮到你,毕业设计这东西,跑起来一次,后面就顺了。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进