
简介面向教师群体的健康管理平台毕业设计资源基于Java与SSH框架Spring、Struts、Hibernate开发采用典型J2EE三层结构涵盖教师饮食管理、健康日志、体检管理、疾病预测评估等核心功能模块。资源包约143.18MB包含项目报告、开题报告、答辩PPT、源代码、数据库及讲解视频既可作为毕业设计参考范文也可用于学习SSH框架整合与健康管理业务逻辑。目前已有323人学习下载。项目报告与开题报告展示了从选题背景、需求分析到系统设计、测试验收的完整过程答辩PPT便于直观呈现项目亮点源代码与数据库脚本可支撑环境搭建、功能调试及二次开发讲解视频则配合演示关键实现思路帮助理解三层架构中表现层、业务逻辑层与数据服务层的交互方式以及高血压、糖尿病风险评估的实际落地。1. 基于Java的健康管理系统毕业设计六件套交付物怎么拆才不会做到一半想换题打开毕设选题库满屏不是图书管理就是学生选课直到看到这一题——基于Java的健康管理系统设计与实现并附项目报告、开题报告、答辩PPT、源代码、数据库和讲解视频。很多同学一开始会犹豫健康管理是不是要懂医学知识其实不是。这个选题本质上是一个带用户、带数据、带业务规则的Web信息管理系统技术栈正好落在Spring Boot、MyBatis、MySQL这条Java主流路线上比起算法题和纯前端页面题它对新手更友好也更容易在答辩时讲出完整的业务闭环。健康档案要建、体检记录要增删改查、指标异常要能自动判断、趋势要能画图、报告要能导出每一个功能都能对应到具体的类、表和SQL验收标准一目了然。这篇文章按照我习惯的落地顺序——需求拆解、数据库设计、后端实现、前端可视化、排错、答辩材料——把整套方案从头到尾讲清楚新手能照着跑通老手也能在参数设置和避坑点上省点时间。2. 从开题报告到数据库设计角色、业务规则与8张核心表一次讲清2.1 开题报告里必须讲清的需求边界健康档案、体检记录和慢病随访很多同学拿到题第一件事就是开写代码代码写完了再回头补开题报告结果答辩时一问业务场景就支支吾吾。正确顺序是反过来的先用开题报告逼自己把业务边界想清楚。“健康管理”这个词范围很大如果把它理解成医院里的HIS系统表没建完人先绕晕了。毕业设计语境下的健康管理通常指面向个人或社区的健康档案电子化管理用户注册登录后维护自己的基本档案录入每次体检的生理指标系统给出BMI、血压、血糖等基础判断管理员能做数据统计和随访提醒。按这个边界拆角色就三类普通用户、健康管理员、系统管理员。功能模块对应为——用户端注册登录、个人健康档案维护、体检记录添加与修改、历史记录查看、健康趋势图表管理员端用户列表管理、体检数据总览、异常指标筛选、随访记录登记系统端登录鉴权、分页搜索、Word报告导出、统计分析。下面这张角色功能矩阵可以直接用在开题报告里。角色核心功能数据对象普通用户档案维护、体检记录录入、趋势查看、报告导出health_profile, health_exam健康管理员异常指标筛选、随访记录、统计视图health_exam, health_followup系统管理员用户管理、权限控制、数据总览sys_user, sys_role写开题报告时把系统目标浓缩成一句话就可以实现个人健康数据的电子化存储、指标异常自动判断和趋势可视化为慢病随访提供数据支撑。这句话写进去后面所有设计都有抓手了。2.2 技术栈选型Spring Boot MyBatis MySQL 还是 SSM技术栈不是越新越好也不是越简单越好关键看答辩场景和后续扩展。常见组合有三套。第一套是Spring Boot 2.x MyBatis MySQL Vue前后端分离接口清晰ECharts渲染趋势图非常顺手也是目前企业里最主流的组合第二套是Spring Boot MyBatis Thymeleaf服务端渲染不需要Node环境部署成一个jar包就能跑适合时间特别紧、只求完整可演示的情况第三套是SSM也就是Spring加Spring MVC加MyBatis很多学校教材还在讲但XML配置量偏大版本冲突排查起来也折腾。我的建议是选第一套。不只是因为技术新而是答辩现场提问面好控制问前端就说Vue组件从接口拿数据渲染问后端就说是Controller-Service-Mapper三层结构。如果你正准备Java面试题里的设计模式相关环节这个项目的分层就是现成例子——Service层用模板方法模式把“新增体检记录—更新档案身高体重—判断指标异常”定义成稳定骨架子类只需要重写异常判定规则。这一句话能让答辩的技术含量上一个台阶。环境版本我一般固定为JDK 8或11、Spring Boot 2.7.x、MyBatis 2.3.x、MySQL 5.7或8.0、Node 16以上、Vue 2.6或3.2。不要一上来就追Spring Boot 33.x要求JDK 17部分旧驱动和教程会出现兼容性问题对毕业设计来说冒这个险不值得。2.3 数据库设计8张核心表的关联关系与字段取舍数据库是这套系统的地基答辩被问最多的地方也在这。按最常见的业务需求拆成8张核心表sys_user用户表、health_profile健康档案表、health_exam体检记录表、health_followup随访记录表、health_remind提醒计划表、sys_role角色表、sys_user_role用户角色关联表、sys_notice系统公告表。核心三张表的建表SQL可以直接用。CREATE TABLE sys_user ( id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 主键, username varchar(32) NOT NULL COMMENT 用户名, password varchar(128) NOT NULL COMMENT BCrypt加密后的密码, real_name varchar(32) DEFAULT NULL COMMENT 真实姓名, phone varchar(20) DEFAULT NULL COMMENT 手机号, role_type tinyint(4) NOT NULL DEFAULT 1 COMMENT 1普通用户 2健康管理员 3系统管理员, status tinyint(4) NOT NULL DEFAULT 1 COMMENT 1启用 0禁用, create_time datetime DEFAULT NULL, update_time datetime DEFAULT NULL, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB AUTO_INCREMENT1 DEFAULT CHARSETutf8mb4 COMMENT用户表; CREATE TABLE health_profile ( id bigint(20) NOT NULL AUTO_INCREMENT, user_id bigint(20) NOT NULL COMMENT 关联sys_user.id, gender tinyint(4) DEFAULT NULL COMMENT 0女 1男, birth_date date DEFAULT NULL COMMENT 出生日期, height_cm decimal(5,1) DEFAULT NULL COMMENT 当前身高cm, weight_kg decimal(5,1) DEFAULT NULL COMMENT 当前体重kg, blood_type varchar(8) DEFAULT NULL COMMENT 血型, past_history varchar(500) DEFAULT NULL COMMENT 既往病史, family_history varchar(500) DEFAULT NULL COMMENT 家族病史, create_time datetime DEFAULT NULL, update_time datetime DEFAULT NULL, PRIMARY KEY (id), KEY idx_user_id (user_id) ) ENGINEInnoDB AUTO_INCREMENT1 DEFAULT CHARSETutf8mb4 COMMENT健康档案表; CREATE TABLE health_exam ( id bigint(20) NOT NULL AUTO_INCREMENT, profile_id bigint(20) NOT NULL COMMENT 关联health_profile.id, exam_date date NOT NULL COMMENT 体检日期, height_cm decimal(5,1) DEFAULT NULL COMMENT 本次身高cm, weight_kg decimal(5,1) DEFAULT NULL COMMENT 本次体重kg, systolic_pressure int(11) DEFAULT NULL COMMENT 收缩压mmHg, diastolic_pressure int(11) DEFAULT NULL COMMENT 舒张压mmHg, fasting_glucose decimal(4,1) DEFAULT NULL COMMENT 空腹血糖mmol/L, total_cholesterol decimal(4,1) DEFAULT NULL COMMENT 总胆固醇mmol/L, is_abnormal tinyint(4) NOT NULL DEFAULT 0 COMMENT 0正常 1异常, remark varchar(500) DEFAULT NULL COMMENT 备注, create_time datetime DEFAULT NULL, update_time datetime DEFAULT NULL, PRIMARY KEY (id), KEY idx_profile_id (profile_id), KEY idx_exam_date (exam_date) ) ENGINEInnoDB AUTO_INCREMENT1 DEFAULT CHARSETutf8mb4 COMMENT体检记录表;字段设计里有一个关键取舍要说清楚height_cm和weight_kg在health_exam表里重复出现不是冗余失误。健康档案表里的身高体重代表“当前档案值”体检记录表里代表“体检当时测得的值”两者用途不同。趋势图需要还原历史体检时的体重判断BMI需要最新档案值如果只存一边后面画体重趋势图就会发现数据对不上。另外is_abnormal字段我建议在写入时就算好存库而不是查询时临时算这样管理员端做异常筛选可以直接走WHERE is_abnormal 1数据量大了也比全表扫一遍快得多。外键我一般不用数据库级约束只在逻辑上通过user_id、profile_id关联。理由很简单毕设阶段删除记录、批量导入测试数据时物理外键会频繁触发约束报错处理起来又麻烦又容易在演示时翻车。逻辑外键配合索引和SQL注释信息完整性足够操作自由度还高。3. Spring Boot后端落地登录鉴权、分页查询和指标计算的完整写法3.1 pom.xml与application.yml依赖、Druid连接池和时区参数后端工程用Spring Initializr生成骨架就行Java版本压到8或11用Maven管理依赖。pom.xml里最核心的依赖如下parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version relativePath/ /parent dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-validation/artifactId /dependency dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version2.3.2/version /dependency dependency groupIdcom.alibaba/groupId artifactIddruid-spring-boot-starter/artifactId version1.2.20/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version5.1.49/version /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-api/artifactId version0.11.5/version /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-impl/artifactId version0.11.5/version scoperuntime/scope /dependency /dependencies数据库连接池这里我用的Druid原因很简单答辩时需要把连接池参数讲清楚Druid的监控页面和文档更直观被问到“你的池最大连接数为什么是20”这类问题时有话讲。application.yml里的核心参数如下spring: datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/health_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse username: root password: 123456 type: com.alibaba.druid.pool.DruidDataSource druid: initial-size: 5 min-idle: 5 max-active: 20 max-wait: 60000 servlet: multipart: max-file-size: 10MB max-request-size: 100MB mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.health.entity configuration: map-underscore-to-camel-case: truemap-underscore-to-camel-case设为trueMyBatis会自动把数据库的下划线字段映射成Java驼峰属性不用在Mapper XML里写一大堆resultMap。Druid的initial-size和min-idle设成5、max-active设成20对毕设规模完全够用max-wait设60000毫秒含义是拿不到连接时最多等60秒就抛异常系统不会无限期卡死。有一个容易被忽略的点MySQL 8.x要把驱动类改成com.mysql.cj.jdbc.Driver连接URL必须带serverTimezone否则驱动拿JVM默认时区去连数据库日期会莫名差8小时这个坑在第5章会详细讲。3.2 JWT登录鉴权token过期时间和拦截器的设计细节登录鉴权是答辩必问点一定要自己理清楚。方案是用户提交用户名密码后端查sys_user表用BCrypt校验密码成功后签发JWT字符串前端存localStorage后续请求在Authorization请求头带token后端用拦截器统一校验。Component public class JwtTokenUtil { Value(${jwt.secret}) private String secret; // 签名密钥长度至少32字符 Value(${jwt.expiration}) private Long expiration; // token过期时间单位毫秒 public String generateToken(Long userId, String username, Integer roleType) { return Jwts.builder() .setSubject(username) .claim(userId, userId) .claim(roleType, roleType) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() expiration)) .signWith(Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8)), SignatureAlgorithm.HS256) .compact(); } public Claims parseToken(String token) { return Jwts.parserBuilder() .setSigningKey(Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8))) .build() .parseClaimsJws(token) .getBody(); } }关键参数是jwt.expiration。很多模板工程设成24小时甚至7天听起来省事但答辩演示时有个尴尬场景你上午验证完登录下午换到教室电脑继续演示token还没过期想演示“登录过期被拦截”的效果就得改配置重启。我习惯设2小时足够支撑一场完整演示又能现场演示登录过期跳转逻辑。refresh token机制在毕业设计里不必要单token讲清楚就够。拦截器实现HandlerInterceptor的preHandle方法放行登录接口和静态资源其余请求统一解析token解析失败返回401。这里最容易出的错是不放行OPTIONS预检请求导致前端跨域调用时后端报“No Access-Control-Allow-Origin header”后面避坑章节会专门说。3.3 健康档案分页与多条件查询MyBatis分页插件两个必调参数健康列表页和体检记录列表页都需要分页。手写LIMIT加COUNT既繁琐又容易出边界问题我用MyBatis官方分页插件PageHelper。引入依赖后在配置类里加一个BeanConfiguration public class MybatisConfig { Bean public PageInterceptor pageInterceptor() { PageInterceptor interceptor new PageInterceptor(); Properties properties new Properties(); properties.setProperty(helperDialect, mysql); properties.setProperty(reasonable, true); properties.setProperty(supportMethodsArguments, true); interceptor.setProperties(properties); return interceptor; } }helperDialect告诉插件方言是MySQLreasonable设为true表示页码越界时自动修正到首页或末页而不是报错supportMethodsArguments表示支持从方法参数里直接取pageNum和pageSize。Service层用法如下public PageInfoHealthExamVO queryExamPage(ExamQueryDTO dto, int pageNum, int pageSize) { PageHelper.startPage(pageNum, pageSize); ListHealthExamVO list examMapper.selectByCondition(dto); return new PageInfo(list); }注意PageHelper.startPage必须紧跟在查询语句之前中间不能穿插别的SQL查询否则分页会作用到错误的语句上。这是PageHelper最常见的误用答辩现场分页不生效时先从这一条排查。Mapper里的多条件查询用动态SQL实现select idselectByCondition resultTypecom.example.health.entity.HealthExam SELECT * FROM health_exam where if testprofileId ! null and profileId ! AND profile_id #{profileId} /if if teststartDate ! null AND exam_date gt; #{startDate} /if if testendDate ! null AND exam_date lt; #{endDate} /if if testisAbnormal ! null AND is_abnormal #{isAbnormal} /if /where ORDER BY exam_date DESC /selectwhere标签会自动去掉第一个条件前面的AND这是MyBatis的惯用写法。日期区间用大于等于和小于等于而不是between是因为between在两个边界值同时为NULL时容易写出意料之外的结果前者逻辑更直观也好讲。3.4 健康指标判定逻辑BMI、血压和血糖的分级代码怎么写指标判定是健康管理系统区别于普通CRUD的核心点。判定规则放在独立的HealthIndicatorUtil工具类里不写在Controller或Service内部方便单测答辩时也能说这是可复用的业务工具。public class HealthIndicatorUtil { public static final double BMI_LOW 18.5; public static final double BMI_HIGH 24.0; public static final double BMI_OBESE 28.0; public static String classifyBmi(double heightCm, double weightKg) { if (heightCm 0 || weightKg 0) return 数据异常; double bmi weightKg / Math.pow(heightCm / 100.0, 2); if (bmi BMI_LOW) return 偏瘦; if (bmi BMI_HIGH) return 正常; if (bmi BMI_OBESE) return 超重; return 肥胖; } public static String classifyBloodPressure(int systolic, int diastolic) { if (systolic 90 || diastolic 60) return 低血压; if (systolic 140 diastolic 90) return 正常血压; if (systolic 160 diastolic 100) return 一级高血压; return 二级及以上高血压; } public static String classifyFastingGlucose(double glucose) { if (glucose 3.9) return 低血糖; if (glucose 6.1) return 空腹血糖正常; if (glucose 7.0) return 空腹血糖受损; return 疑似糖尿病需复查; } public static boolean isAbnormal(String... results) { for (String r : results) { if (r null || r.contains(异常) || r.contains(肥胖) || r.contains(高血压) || r.contains(糖尿病)) { return true; } } return false; } }注意BMI判定用了中国成人标准18.5以下偏瘦、24以上超重、28以上肥胖和世界卫生组织标准不同写进报告要注明依据。血压和血糖分级参考国家基层高血压防治指南和糖尿病防治指南的区间但毕设不需要做到临床级精确报告里写明参考依据就行。Service层调用方式String bmiResult HealthIndicatorUtil.classifyBmi(exam.getHeightCm(), exam.getWeightKg()); String bpResult HealthIndicatorUtil.classifyBloodPressure(exam.getSystolicPressure(), exam.getDiastolicPressure()); String glucoseResult HealthIndicatorUtil.classifyFastingGlucose(exam.getFastingGlucose()); exam.setIsAbnormal(HealthIndicatorUtil.isAbnormal(bmiResult, bpResult, glucoseResult) ? (byte) 1 : 0);这里把多个指标结果通过可变参数统一传给isAbnormal比在业务代码里写一长串if-else清晰。以后如果要加入尿酸、血脂等指标只需要扩展参数个数这个设计在答辩讲课时提一句印象分会明显不一样。4. Vue前端与数据可视化ECharts趋势图和Word报告导出的最小实现4.1 初始化Vue工程并封装axios统一拦截器和登录态处理前端工程用Vue CLI或Vite创建都行电脑有Node 16以上建议直接用Vite启动快配置少。前后端接口约定统一返回结构{ code: 200, message: 成功, data: { } }后端定义对应的Result类前端axios封装时统一拦截import axios from axios const request axios.create({ baseURL: /api, timeout: 10000 }) request.interceptors.request.use(config { const token localStorage.getItem(health_token) if (token) { config.headers.Authorization Bearer token } return config }) request.interceptors.response.use( response { const res response.data if (res.code 401) { localStorage.removeItem(health_token) window.location.href /login return Promise.reject(new Error(登录过期)) } if (res.code ! 200) { return Promise.reject(new Error(res.message || 请求失败)) } return res.data }, error { if (error.response error.response.status 401) { localStorage.removeItem(health_token) window.location.href /login } return Promise.reject(error) } ) export default request封装axios的好处是登录态检查和过期跳转集中在一处每个页面不用重复处理。这里有个前后端约定问题必须对齐后端到底返回HTTP 401状态码还是返回HTTP 200但code字段是401。两种都行但前后端必须一致否则后端返了401状态码前端却在response分支里检查res.code结果永远发现不了这是最常见的联调翻车点。4.2 ECharts折线图展示体检趋势从后端接口到前端渲染的完整链路趋势图是健康管理系统的亮点功能适合作为答辩PPT里的大图展示。最常画的是体重趋势、收缩压和舒张压趋势、空腹血糖趋势。后端提供接口返回某用户全部体检记录的日期和指标值前端拿到数据渲染ECharts。后端VO如下public class TrendVO { private ListString dates; // [2023-01-01, 2023-06-01] private ListDouble weights; // [70.5, 68.0] private ListInteger systolic; // [130, 125] private ListInteger diastolic; // [85, 80] private ListDouble glucose; // [5.6, 5.2] }前端初始化ECharts并配置optionimport * as echarts from echarts const chartDom document.getElementById(trendChart) const myChart echarts.init(chartDom) const option { tooltip: { trigger: axis }, legend: { data: [体重, 收缩压, 舒张压] }, xAxis: { type: category, data: trendData.dates }, yAxis: [ { type: value, name: 体重kg }, { type: value, name: 血压mmHg } ], series: [ { name: 体重, type: line, data: trendData.weights, smooth: true }, { name: 收缩压, type: line, data: trendData.systolic, smooth: true }, { name: 舒张压, type: line, data: trendData.diastolic, smooth: true } ] } myChart.setOption(option) window.addEventListener(resize, () myChart.resize())两个容易踩的细节一是在Vue组件里用echarts.init之前必须确保chartDom对应的DOM节点已经渲染完成否则拿到的是undefined报Init failed。解决是在mounted生命周期或nextTick回调里初始化。二是图表容器必须有明确高度ECharts容器高度为0时图是空白CSS里要给#trendChart设一个height属性比如400px。4.3 Apache POI导出Word体检报告段落、表格和结论的生成方式报告导出是体现系统完整度的功能。后端用Apache POI生成Word思路是创建XWPFDocument对象往里加段落和表格最后写回响应流让前端触发下载。public void exportReport(Long examId, HttpServletResponse response) { HealthExam exam examMapper.selectById(examId); HealthProfile profile profileMapper.selectById(exam.getProfileId()); try (XWPFDocument doc new XWPFDocument()) { XWPFParagraph title doc.createParagraph(); title.setAlignment(ParagraphAlignment.CENTER); XWPFRun run title.createRun(); run.setText(个人体检健康报告); run.setFontSize(18); run.setBold(true); XWPFParagraph info doc.createParagraph(); info.createRun().setText(姓名 profile.getRealName() 体检日期 exam.getExamDate()); XWPFTable table doc.createTable(4, 2); String[][] data { {身高(cm), exam.getHeightCm() null ? - : exam.getHeightCm().toString()}, {体重(kg), exam.getWeightKg() null ? - : exam.getWeightKg().toString()}, {收缩压/舒张压(mmHg), exam.getSystolicPressure() / exam.getDiastolicPressure()}, {空腹血糖(mmol/L), exam.getFastingGlucose() null ? - : exam.getFastingGlucose().toString()} }; for (int i 0; i data.length; i) { table.getRow(i).getCell(0).setText(data[i][0]); table.getRow(i).getCell(1).setText(data[i][1]); } XWPFParagraph conclusion doc.createParagraph(); conclusion.createRun().setText(健康评估结论 buildConclusion(exam)); response.setContentType(application/vnd.openxmlformats-officedocument.wordprocessingml.document); response.setHeader(Content-Disposition, attachment;filenamehealth_report_ examId .docx); doc.write(response.getOutputStream()); } catch (IOException e) { log.error(导出报告失败, e); } }XWPFDocument用try-with-resources保证输出流一定关闭不泄漏文件句柄。response里如果文件名带中文需要做URL编码兼容不同浏览器简单起见直接用英文文件名加examId省去这一层麻烦。buildConclusion方法把第3章的指标判定结果拼成一段文字比如“当前体质指数属超重血压属一级高血压建议低盐饮食并每周测量血压”。验证导出功能时有个经验不要用浏览器预览插件打开docx验证最好用WPS或Office实际打开因为预览插件对POI生成的表格布局偶尔渲染失真容易误判是代码写错这是导出功能调试里最常见的假故障。5. 避坑与排错毕业设计演示现场最容易翻车的5个问题答辩演示和上线项目最大的区别是环境不受控制。教室电脑可能是Windows 7可能是别人的笔记本MySQL可能没装端口可能被占用。按我实际踩过的坑整理出5条对照记录每条都是现象、原因、解决的格式。5.1 数据库中文乱码插入中文变问号现象系统跑起来后插入用户名“张三”数据库里存的却是三个问号备注字段全是乱码。原因MySQL数据库、表、JDBC连接三层至少有一层字符集不是utf8mb4。最常见的是建库时用了默认latin1或者JDBC连接URL少了characterEncodingutf8。解决建库时显式指定字符集CREATE DATABASE health_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;。建表语句在ENGINEInnoDB前加DEFAULT CHARSETutf8mb4JDBC URL加useUnicodetrue和characterEncodingutf8。已经乱掉的数据只能删除重录所以越早发现越好。5.2 登录接口报401前端一直跳回登录页现象用户名密码正确Swagger里调接口正常返回token前端登录成功后紧接着的第一个业务请求就401页面闪一下又回到登录页。原因最常见两种。一是JWT密钥太短HS256要求密钥至少32字节短密钥直接抛异常二是前端请求头带了token但后端拦截器没有放行预检的OPTIONS请求浏览器CORS机制把响应拦截前端看到的表现就是401。解决密钥改成一个至少32位的字符串比如health-system-jwt-secret-key-2024。然后在后端跨域配置类里放行所有OPTIONS请求或者让拦截器判断OPTIONS.equals(request.getMethod())时直接返回true。5.3 日期差8小时体检日期总是前一天现象前端选择2024-05-20保存数据库里存的却是2024-05-19T16:00:00Z或查询出来显示凌晨4点、8点等奇怪时间。原因MySQL驱动连接URL没有指定serverTimezone驱动拿JVM默认时区去解析数据库的DATETIME字段也可能服务端时区设成了UTC两边不匹配就差出8小时。解决连接URL统一加serverTimezoneAsia/Shanghai。如果已经加了还是差8小时检查MySQL服务端时区执行SELECT NOW();看返回是否正常不正常就执行SET time_zone 8:00;。这个坑在导出Word报告时最隐蔽因为打印出来的日期从表面看正常只有对比数据库里的原始值才看得出来。5.4 换机器演示时数据库连不上端口被防火墙拦现象自己电脑一切正常答辩当天用别人笔记本跑Spring Boot启动报Communications link failure或Access denied for user。原因目标机器上MySQL没启动、3306端口被占用或防火墙拦截、root账号密码和配置文件不一致。更隐蔽的情况是两台机器MySQL版本不同应用里带的驱动版本和数据库不匹配。解决答辩前一天做一次冷启动排练。换一台机器从零启动MySQL、导入health_db.sql、改好application.yml里的用户名密码、跑通一个核心流程。application.yml的密码不要留成别人的123456可以写一个简短的启动脚本双击自动启动MySQL并导入数据。这一步比练PPT值钱得多。5.5 答辩PPT里的图表和现场演示的数据对不上现象PPT放了一张分页列表截图显示有5页数据现场演示时只有2页PPT写支持报告导出演示时导出按钮点不动。原因写文档和写代码不是同一时间完成的。中期检查后改过数据库字段或删过测试数据PPT截图和最终代码不一致。答辩老师一旦发现这个差异印象分比功能缺失还伤。解决我的习惯是功能冻结之后再做PPT和拍视频。PPT所有截图从当前运行版本现截字段名、按钮位置、数据条数和现场严格一致。讲解视频最后录录制前先完整跑一遍流程把登录、建档、录体检、看趋势、导出报告五个镜头一次录完。宁可少放图不放错的图。6. 把CRUD讲成业务闭环项目报告结构与答辩演示视频的编排技巧系统代码写到这里你已经拥有一个可运行的健康管理系统但答辩的评判标准不只是能跑通而是你能不能有条理地讲清楚系统为什么值得做、数据怎么流动、技术难点在哪。项目报告按五章排比较稳绪论和背景、需求分析、系统设计、系统实现、测试与总结。开题报告重点放在背景和创新点创新点不需要硬凹写“结合ECharts实现健康指标趋势可视化采用JWT无状态鉴权提升系统扩展性”这类务实的点就能成立。答辩PPT控制在10到14页顺序为选题背景、需求分析、系统架构图、数据库设计、核心功能实现截图、技术难点和解决方案、测试结果、总结展望。每页放一个主题展示页面时用当前版本现截的图。讲解视频是六件套里最容易被轻视的部分。时长一般5到10分钟建议录屏而不是用手机对着屏幕拍。流程固定为启动后端、启动前端、登录、录入一条体检记录、打开趋势图、导出Word报告、演示异常数据预警。录制过程中不要剪掉等待过程如果接口响应慢宁可PPT里说明是测试环境网络问题也不要视频里卡顿几秒再快进评委一眼就能看出拼接感。视频配上简单的操作说明字幕就够了不用完整画外音反而显得真实。这个选题最大的价值不是代码量多而是够常规、够完整、能讲清楚来龙去脉。我建议动手前先把六件套的产出顺序排一个计划先建库再写后端核心接口前端配合联调功能冻结后再写报告和拍视频。这样每一步的产物都有迹可循不会出现代码和文档两张皮的情况。希望我梳理的这条落地路径能帮到你祝答辩顺利。本文还有配套的精品资源点击获取