做毕设辅导这些年,SpringBoot的选题我接过太多,说实话十个里面有八个是“XX管理系统”,数据库一建、CRUD一写、页面套个模板,完事。但这套“糖尿病人健康饮食计划平台”不一样,它表面看也是管理平台,核心技术栈同样是SpringBoot,可真正值钱的地方在于它把营养学规则和工程代码揉在了一起——你要懂一点健康领域的知识,才能把表结构和推荐逻辑设计得合理。这篇就用这个项目做完整拆解,从需求分析到数据库设计,从核心代码到远程部署上线,一条线讲清楚。
这个平台解决的实际问题很具体:糖尿病患者最头疼的不是吃药打针,而是“今天到底该吃什么”。医生说的“低GI、控热量、均衡营养”普通人很难换算成一日三餐。平台就是把食物库、营养数据、个人健康档案和饮食计划串联起来,让患者能查、能算、能跟着计划吃。它适合三类人参考:做Java毕设的学生(源码+论文都有落地场景)、刚入门SpringBoot想做一个完整全栈项目的开发者、以及想做健康垂直领域MVP产品的产品经理。我下面所有内容都按可以直接复现的标准来写。
1. 这个项目到底解决什么问题:需求拆解与技术选型
1.1 糖尿病饮食管理的真正痛点
很多做系统的人上来就建表、写接口,结果做出来的东西没人用。原因很简单——没弄明白业务场景的真实约束。糖尿病饮食管理有三个绕不开的痛点:
第一,营养计算门槛高。一个患者每天应该摄入多少千卡热量,取决于他的性别、年龄、身高、体重、活动强度,甚至糖尿病分型。这些参数组合起来,普通人是算不明白的。平台要做的第一件事,就是把这个计算过程封装成自动逻辑,用户填几个基础指标,系统直接给出每天的热量总预算。
第二,低GI(血糖生成指数)食物选择困难。GI值低于55属于低GI食物,适合糖尿病患者,但市面上常见食物的GI值普通人根本记不住。平台需要一个结构化的食物库,把每样东西的热量、GI、蛋白质、脂肪、碳水都存进去,按条件筛选。
第三,饮食计划难以坚持。光告诉用户“你要控制饮食”没用,得直接给出“早餐吃什么、午餐吃什么、晚餐吃什么”的可执行方案,而且最好是能根据用户在平台上的健康档案自动生成的。如果你把AI说得太玄,毕设阶段反而不实际,完全可以先用规则引擎实现一套确定性的推荐逻辑,效果也不差。
1.2 为什么选SpringBoot而不是SSH或微服务
这个项目的技术选型,我建议就是SpringBoot + MyBatis-Plus + MySQL,前端用Vue或者Thymeleaf都可以。很多人会问:现在都在说微服务、分布式,毕设要不要上Spring Cloud Alibaba、Nacos那一套?
我的意见很直接:不要。这个项目的数据量级和业务复杂度,单体架构完全扛得住,微服务只会把问题复杂化。SpringBoot的核心价值是“约定大于配置”,内嵌Tomcat,一个jar包就能跑,这正好符合课程设计/毕设场景里“快速开发、清晰交付、方便部署”的诉求。
版本选择上要特别注意,这也是搜“springboot版本太高”的原因。SpringBoot 2.7.x配JDK 8或11,是当前最稳的组合。别一上来就SpringBoot 3.x——它能跑,但很多老牌的依赖(比如一些MyBatis的早期整合包、旧版JWT库)在Jakarta命名空间迁移后会报ClassNotFoundException,排查起来极其痛苦。如果一个项目的目标是稳定出成果,就用成熟的版本组合。
1.3 整体架构:清晰三层,别玩花活
这个项目的架构我建议就是标准的三层架构加一个DTO/VO转换层:
- Controller层:接收请求、参数校验、返回统一结果结构
- Service层:业务逻辑(热量计算、计划推荐、统计)都在这里
- Mapper层:MyBatis-Plus的BaseMapper,单表CRUD基本不用写SQL
另外加一个包叫common,放统一返回体Result、全局异常处理器、JWT工具类、跨域配置。这样分层的好处是论文好写、答辩好讲、代码好改。真不用设计什么“领域驱动设计”那套,那种复杂度对于这类系统是过度的。
2. 数据库设计:这套系统的地基
2.1 三类用户角色与权限模型
数据库设计是整个项目里最见功底的部分。先把用户模型定下来:患者(普通用户)、营养师/医生(管理员)、平台管理员。三张表的做法是一种方案,但更简洁的做法是放在一张user表里,用role字段区分:
CREATE TABLE `user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL COMMENT '登录名', `password` varchar(100) NOT NULL COMMENT '加密后密码', `nickname` varchar(50) DEFAULT NULL, `role` tinyint(4) NOT NULL DEFAULT '1' COMMENT '1患者 2营养师 3管理员', `gender` tinyint(4) DEFAULT NULL COMMENT '0女 1男', `age` int(11) DEFAULT NULL, `height` decimal(5,2) DEFAULT NULL COMMENT 'cm', `weight` decimal(5,2) DEFAULT NULL COMMENT 'kg', `diabetes_type` tinyint(4) DEFAULT NULL COMMENT '1型/2型', `activity_level` tinyint(4) DEFAULT NULL COMMENT '1少动 2轻度 3中度 4重度', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';为什么把患者的基础健康指标直接放在user表而不是单独建一张detail表?因为这个项目里没有“用户信息变更历史”这类复杂需求,患者档案信息就是最新的那一份,一行放得下就不拆表。保持合理冗余,减少联表查询,毕设阶段这是更好的设计策略。
权限控制用SpringBoot拦截器做就够了,不需要引入Spring Security(如果你想加分,引入也完全可以,但不引入节省的时间能让你把前后端联调做扎实)。拦截器里判断请求头里token解析出的role,和当前访问路径做匹配,不匹配就返回403。
2.2 食物库与营养成分表
这是整个平台的数据底座,没有食物数据的饮食推荐就是空中楼阁。食物表的设计一定要把营养学里最常用的几个指标放全:
CREATE TABLE `food` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `name` varchar(100) NOT NULL COMMENT '食物名', `category` varchar(50) DEFAULT NULL COMMENT '分类:主食/肉类/蔬菜/水果/乳制品', `calories` decimal(7,2) DEFAULT NULL COMMENT '每100g热量(kcal)', `protein` decimal(7,2) DEFAULT NULL COMMENT '蛋白质(g/100g)', `fat` decimal(7,2) DEFAULT NULL COMMENT '脂肪(g/100g)', `carb` decimal(7,2) DEFAULT NULL COMMENT '碳水(g/100g)', `gi_value` int(11) DEFAULT NULL COMMENT 'GI值 0-100', `unit` varchar(20) DEFAULT NULL COMMENT '常见计量份量,如1碗/1个/100g', `is_common` tinyint(4) DEFAULT '1' COMMENT '是否常用食物', `status` tinyint(4) DEFAULT '1' COMMENT '0下架 1上架', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='食物表';字段设计有三个细节要注意。一是单位的问题,营养表里的热量和营养素通常按每100克算,但用户在选“一个苹果”的时候不会想它是多少克,所以还需要一个“常见份量”字段,比如一个中等苹果约200克、一碗米饭约150克,推荐时换算才准确。二是GI值只存整数就行,专业食物GI表本身也精确到整数。三是预留status字段做上下架,营养师可以维护食物库,这个功能在论文里能作为一个管理模块亮点。
提前准备食物数据时,重点整理常见食物就够了,二三十种就能让推荐逻辑跑起来,不必追求数据量。查数据用《中国食物成分表》或者公开的低GI食物表,换算要自己算清楚。
2.3 饮食计划与计划明细:一主多从的表结构
饮食计划不是一张表能搞定的。一个用户一天有三餐(或加餐),每一餐里又包含多种食物,这是典型的一对多关系。设计成主表和明细表,维度清晰、扩展方便:
CREATE TABLE `diet_plan` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `user_id` bigint(20) NOT NULL, `plan_date` date NOT NULL COMMENT '计划对应日期', `meal_type` tinyint(4) NOT NULL COMMENT '1早餐 2午餐 3晚餐 4加餐', `total_calories` decimal(7,2) DEFAULT NULL COMMENT '本餐总热量', `total_gi` decimal(7,2) DEFAULT NULL COMMENT '本餐加权GI', `status` tinyint(4) DEFAULT '1', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_user_date` (`user_id`,`plan_date`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='饮食计划主表'; CREATE TABLE `diet_plan_detail` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `plan_id` bigint(20) NOT NULL COMMENT '关联主表', `food_id` bigint(20) DEFAULT NULL, `food_name` varchar(100) NOT NULL COMMENT '冗余食物名', `servings` decimal(5,2) DEFAULT NULL COMMENT '份数', `grams` decimal(7,2) DEFAULT NULL COMMENT '实际克数', `calories` decimal(7,2) DEFAULT NULL COMMENT '本条目热量', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='饮食计划明细表';主表记录“哪一天哪一餐的总量”,明细表记录“这一餐具体包含哪些食物、各多少克”。food_name冗余存储是故意为之,因为食物表数据可能被营养师修改(比如修正热量),而历史计划应该保留当时的快照信息,这种适度冗余在业务上是合理的。
用user_id和plan_date建联合索引,是因为最频繁的查询就是“查某用户某一天的全部计划”,有索引能保证这个查询走索引。这个细节可以在数据库设计说明里写出来,是论文的好素材。
2.4 血糖记录与健康档案
血糖记录表是这个平台里另一个核心业务数据,结构相对简单,但要注意测量时机的区分:
CREATE TABLE `blood_sugar` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `user_id` bigint(20) NOT NULL, `measure_time` datetime NOT NULL COMMENT '测量时间', `measure_type` tinyint(4) NOT NULL COMMENT '1空腹 2餐后2小时 3随机', `value` decimal(5,2) NOT NULL COMMENT '血糖值mmol/L', `remark` varchar(200) DEFAULT NULL, PRIMARY KEY (`id`), KEY `idx_user_time` (`user_id`,`measure_time`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='血糖记录表';注意,空腹和餐后2小时的正常范围完全不一样,如果不区分type就存一起,后续做趋势分析时数据就乱了。查询的时候拿用户最近7次空腹血糖或者最近7天的记录,可以做折线图展示,也能计算平均血糖。这个模块虽然简单,但它是“健康平台”属性的直接体现,答辩时能讲清楚“这个数据怎么指导饮食调整”,整体项目的高度就不一样了。
3. 核心功能实现:认证、推荐算法、统计报表
3.1 登录与JWT认证:代码不多,坑不少
任何一个系统都逃不掉认证这关。这个项目用JWT做无状态认证,SpringBoot里实现起来很轻。
JWT的实现逻辑是:用户登录成功,后端根据用户名和角色生成一个token(过期时间通常设24小时),返回给前端;前端每次请求在header里带Authorization: Bearer ,后端拦截器校验token的合法性,解析出用户和角色,放行或拦截。
核心代码分三块。第一块是JWT工具类,负责生成和解析token:
@Component public class JwtUtil { // HS256的密钥长度必须大于等于32字节,否则运行报错 private static final String SECRET = "DiabetesPlatformSecretKey_2024_YourName"; private static final long EXPIRE = 24 * 60 * 60 * 1000L; public String generateToken(Long userId, String username, Integer role) { return Jwts.builder() .setSubject(username) .claim("userId", userId) .claim("role", role) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + EXPIRE)) .signWith(SignatureAlgorithm.HS256, SECRET.getBytes(StandardCharsets.UTF_8)) .compact(); } public Claims parseToken(String token) { return Jwts.parser() .setSigningKey(SECRET.getBytes(StandardCharsets.UTF_8)) .parseClaimsJws(token) .getBody(); } }这里有个新手的重灾区:HS256签名时密钥字节数必须不少于256位(32字节)。如果你写一个很短的SECRET,程序启动登录一次就抛WeakKeyException,折腾一下午。用jjwt 0.9.1这个版本的话,默认是HS256,创建JwtBuilder时不需要指定签名算法也行。
然后是拦截器:
@Component public class AuthInterceptor implements HandlerInterceptor { @Autowired private JwtUtil jwtUtil; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if ("OPTIONS".equals(request.getMethod())) { return true; // 放行跨域预检请求 } String auth = request.getHeader("Authorization"); if (auth == null || !auth.startsWith("Bearer ")) { response.setStatus(401); return false; } try { Claims claims = jwtUtil.parseToken(auth.substring(7)); request.setAttribute("userId", claims.get("userId")); request.setAttribute("role", claims.get("role")); return true; } catch (Exception e) { response.setStatus(401); return false; } } }注意这里对OPTIONS请求放行。前后端分离开发时,浏览器跨域会先发一个OPTIONS预检请求,这个请求不带Authorization头,如果拦截器直接拦截,前端所有接口调用都会报401,这种坑属于“看起来是前端问题,其实是后端拦截器忘了放行”的典型情况。跨域配置用WebMvcConfigurer实现CorsRegistry,把allowedOriginPatterns设为*,allowedMethods设为GET/POST/PUT/DELETE/OPTIONS,开发阶段足够。
3.2 饮食推荐的核心算法:从热量需求到三餐计划
这就是这个项目和普通管理系统拉开差距的地方。推荐算法不需要机器学习,但要把营养学规则工程化。算法分三步。
第一步,基于Harris-Benedict公式计算每日基础代谢率BMR:
public double calcBmr(User user) { double bmr; if (user.getGender() == 1) { // 男 bmr = 88.362 + (13.397 * user.getWeight()) + (4.799 * user.getHeight()) - (5.677 * user.getAge()); } else { // 女 bmr = 447.593 + (9.247 * user.getWeight()) + (3.098 * user.getHeight()) - (4.330 * user.getAge()); } return bmr; }第二步,乘以活动系数得到每日总消耗TDEE,再根据糖尿病控制目标决定热量缺口。活动系数分四档:久坐1.2、轻度活动1.375、中度活动1.55、重度活动1.725。对于需要控制体重的2型糖尿病患者,建议摄入量在TDEE基础上减10%~20%,但不低于BMR,这一点在代码里要有判断逻辑。
第三步,按热量比例拆到三餐,再从食物库里筛选食物组成计划。常规分配是早餐30%、午餐40%、晚餐30%,如果有加餐可以从晚餐里分出来10%。比如某用户TDEE是2000千卡,控糖减脂期打8折,每日摄入目标1600千卡,那么早餐目标480千卡、午餐640千卡、晚餐480千卡。
接下来是食物选择。优先从低GI(GI≤55)食物里选,兼顾三大营养素比例(碳水50%~60%、蛋白质15%~20%、脂肪20%~30%)。简单的实现是:根据目标热量和食物类别,主食类提供一半热量,剩下的由肉蛋奶和蔬菜瓜果分摊。每选定一个食物,按比例计算克数,注意每个食物的“常见份量”字段,换算成份数写进计划明细。
这套逻辑够实用,代码写起来也不复杂,核心就是一个PlanGenerator类,输入User对象,输出当天三餐的DietPlan列表。推荐结果不追求“最优解”,而追求“合理且能解释”,因为答辩时老师一定会问“你的推荐依据是什么”,你只要能把公式和规则讲清楚,这个项目的技术深度就立住了。
3.3 营养计算与低GI筛选逻辑
热量和GI的计算要分层,不能全部堆在Service里变成一个长方法。我建议做一个NutritionCalculator类来封装:
@Component public class NutritionCalculator { // 根据食物每100g的营养数据与实际克数,算出实际摄入 public NutritionValue calcActual(Food food, double grams) { NutritionValue v = new NutritionValue(); v.setCalories(food.getCalories() * grams / 100.0); v.setProtein(food.getProtein() * grams / 100.0); v.setFat(food.getFat() * grams / 100.0); v.setCarb(food.getCarb() * grams / 100.0); // GI值不做线性换算,取食物的原始GI,但整餐加权GI要另算 v.setGi(food.getGiValue()); return v; } // 一餐的加权GI = Σ(单个食物GI × 该食物碳水克数) / Σ碳水克数 public double calcMealGi(List<DietPlanDetail> details) { double totalCarb = 0, weightedGi = 0; for (DietPlanDetail d : details) { totalCarb += d.getGrams() * ?; // 需要查食物表获取每100g碳水 weightedGi += d.getGi() * carbOfThisFood; } return totalCarb == 0 ? 0 : weightedGi / totalCarb; } }加权GI这个细节很多人会写错。整餐的GI不是各个食物GI的算术平均,而是以碳水化合物含量为权重求加权平均。因为一个菜的GI高但它碳水少,对餐后血糖的真实影响未必大,用一个数字直观展示给患者看,比罗列一堆食物GI值有用得多。
3.4 血糖趋势分析:一周数据可视化怎么做
血糖模块前半段是数据录入,后半段是趋势展示。后端接口需要返回两类数据:一是最近7次测量的原始记录列表(表格展示);二是按日期聚合的平均血糖(前端画折线图)。
SQL层面用MyBatis-Plus也可以写,但更清晰的是一条自定义SQL:
SELECT DATE_FORMAT(measure_time, '%Y-%m-%d') as day, ROUND(AVG(value), 2) as avgValue, MAX(value) as maxValue, MIN(value) as minValue FROM blood_sugar WHERE user_id = #{userId} AND measure_time >= DATE_SUB(CURDATE(), INTERVAL 7 DAY) GROUP BY DATE_FORMAT(measure_time, '%Y-%m-%d') ORDER BY day;这个接口返回的数据前端直接塞进ECharts的line图就完事。要注意时间字段的时区问题,后面部署章节会重点说。
4. 远程部署全程实录:从本地到公网可访问
4.1 云服务器选型与环境初始化
项目的交付说明里写了“远程部署”,这块必须讲透。买一台最便宜的云服务器,2核4G、带宽3Mbps起步就够了,学生机一年也就百来块钱。操作系统选CentOS 7.9或者Ubuntu 20.04都行,我习惯用Ubuntu,软件源更新方便。
环境安装按顺序来:
# 1. 更新系统包 sudo apt update && sudo apt upgrade -y # 2. 安装JDK 11 sudo apt install openjdk-11-jdk -y java -version # 3. 安装MySQL 8.0 sudo apt install mysql-server -y sudo systemctl status mysql # 4. 安装Nginx(前端部署用) sudo apt install nginx -y装MySQL之后记得执行安全初始化脚本:
sudo mysql_secure_installation然后创建一个业务库和专用的应用账号,不要用root直接连应用:
CREATE DATABASE diabetes_platform DEFAULT CHARACTER SET utf8mb4; CREATE USER 'app_user'@'localhost' IDENTIFIED BY 'YourStrongPass123'; GRANT ALL PRIVILEGES ON diabetes_platform.* TO 'app_user'@'localhost'; FLUSH PRIVILEGES;如果你用的是云数据库,那就省略本地安装这一步,直接用云厂商给的连接串。但要注意:学生项目完全可以自己装MySQL,不额外花钱,还能体会完整的运维流程。
4.2 后端打包、配置与systemd托管
本地代码通过Git推送到服务器,或者直接用scp把项目源码/压缩包传上去。推荐在本地执行Maven打包,把产物jar传上去,服务器上不需要装Maven,省时间:
# 本地执行 mvn clean package -DskipTests # 构建产物在 target/ 目录下然后写生产环境的配置文件。杀掉application.yml里的本地数据源,新建一个application-prod.yml:
server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/diabetes_platform?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai username: app_user password: YourStrongPass123 jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8 # MyBatis-Plus配置 mybatis-plus: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl启动方式有两种。简单的方式是直接:
nohup java -jar diabetes-0.0.1-SNAPSHOT.jar --spring.profiles.active=prod > app.log 2>&1 &但这种直接进程方式不考虑开机自启和崩溃重启。稍微规范一点,写一个systemd服务:
[Unit] Description=Diabetes Platform After=network.target [Service] User=root WorkingDirectory=/opt/diabetes ExecStart=/usr/bin/java -jar /opt/diabetes/diabetes.jar --spring.profiles.active=prod Restart=on-failure RestartSec=10 [Install] WantedBy=multi-user.target把配置文件放到/etc/systemd/system/diabetes.service,然后:
sudo systemctl daemon-reload sudo systemctl enable diabetes sudo systemctl start diabetes sudo systemctl status diabetes这样用journalctl -u diabetes可以实时看日志,出错排查方便,比nohup那套体验好太多。远程部署这件事,能做到“服务崩溃自动拉起、开机自启、日志可查”,交付质量就完全不一样了。
4.3 前端打包与Nginx反向代理
如果你的前端是Vue项目,打包之前要改一下接口地址。在.env.production里配接口基础路径:
VUE_APP_BASE_API = 'http://你的服务器IP:8080/api'然后构建:
npm run build # 产物在 dist/ 目录把dist目录里的静态文件上传到服务器的/var/www/diabetes/,然后配置Nginx站点:
server { listen 80; server_name 你的域名或IP; root /var/www/diabetes; index index.html; location / { try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080/api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }try_files那一行是为了解决Vue路由history模式下刷新404的问题,这个坑几乎人人都会踩。配置完执行nginx -t检查语法,然后systemctl reload nginx。
如果你想省事,也可以不做前后端分离,前端用Thymeleaf模板放在SpringBoot的resources/templates下,打包时一起打进jar,那就不用Nginx了,直接访问IP:8080就行。但考虑到市面上多数毕设用的是Vue+SpringBoot前后端分离,我还是把Nginx的配置给全。
4.4 数据库初始化与数据脚本迁移
本地开发完的数据库要整体搬到服务器。最省事的是用mysqldump导出:
# 本地导出 mysqldump -u root -p diabetes_platform > diabetes.sql # 传到服务器后导入 mysql -u app_user -p diabetes_platform < diabetes.sql注意导出和导入的字符集要一致,尽量在命令里加--default-character-set=utf8mb4,否则Windows本地导出的UTF-8内容到Linux可能中文乱码。这个乱码问题是部署阶段出现频率最高的,我见得太多了。
导入之后,一定要把食物表、用户表的数据清点一遍,生产环境初始数据里不要有测试垃圾数据。如果管理员账号密码是明文存进去的,记得使用BCrypt重新加密后再替换。
5. 常见问题与避坑指南
5.1 一张表说清高频报错
这一节是我这几年帮人看项目、做远程部署时遇到的高频问题,直接整理成速查表:
| 现象 | 根本原因 | 解决办法 |
|---|---|---|
| 本地启动正常,部署服务器连不上数据库 | MySQL只监听了127.0.0.1 | 修改/etc/mysql/mysql.conf.d/mysqld.cnf,bind-address改为0.0.0.0(但应用和数据库同机则不必改) |
| 接口返回时间比北京时间早8小时 | 数据库连接串没指定serverTimezone | 在JDBC URL加serverTimezone=Asia/Shanghai,Jackson配置time-zone=GMT+8 |
| 访问接口报401,前端却明明带了token | 拦截器拦截了OPTIONS预检请求 | 在拦截器的preHandle里对OPTIONS请求直接放行 |
| JWT启动报WeakKeyException | HS256密钥太短 | 密钥至少32字节以上 |
| MySQL中文乱码显示问号 | 数据库字符集不是utf8mb4 | 建库语句指定DEFAULT CHARACTER SET utf8mb4,导入时加--default-character-set=utf8mb4 |
| Vue前端刷新页面404 | 没有history模式兜底 | Nginx配置try_files $uri $uri/ /index.html |
| Maven打包下载依赖失败 | 中央仓库网络差 | 在settings.xml配置阿里云镜像 |
| 项目用SpringBoot 3.x,启动报NoClassDefFoundError | 老依赖没适配Jakarta命名空间 | 换回SpringBoot 2.7.x最稳妥 |
每条都是我实测过的,照着做基本能消灭80%的部署问题。
5.2 部署与联调中的实战经验
再说几个不太容易查到但很实用的经验。
第一个是服务器防火墙和安全组。云服务商的安全组默认只开22端口,你要去控制台把8080和80端口放行,否则程序起了、Nginx也配置了,外部就是访问不通。我见过好几个同学在服务器上折腾了两三个小时,最后发现是安全组没放行端口。
第二个是日志的用法。用systemd托管服务后,java进程的日志默认进journald,用journalctl -u diabetes -f实时跟踪很舒服。但如果你的服务是用nohup方式启动的,日志写到nohup.out文件里,排查问题就靠tail -f nohup.out。养成看日志的习惯,遇到错误先看最后20行,比乱猜管用得多。
第三个是数据库备份。虽然毕设项目数据量不大,但养成备份习惯没坏处。写个简单cron任务每天凌晨3点备份一次数据库:
0 3 * * * mysqldump -u app_user -pYourStrongPass123 diabetes_platform > /backup/diabetes_$(date +\%Y\%m\%d).sql 2>&1留到论文的“系统维护”章节里也是加分项。
第四个是关于前后端联调。开发阶段你很可能遇到CORS跨域问题,后端记得在配置类里加CorsRegistry映射,允许前端开发服务器(比如localhost:8081)调用。但部署到生产环境后,走Nginx同域反代就不存在跨域问题了,所以上线后可以把CORS限制收窄,这是安全意识的体现。
6. 写在最后的一点体会
做这个项目的过程中,我觉得最有价值的不是CRUD代码本身,而是把“营养学规则”翻译成“程序逻辑”的那个过程。很多人做管理系统做到最后,代码写了一堆,但问起来“这个系统到底帮用户做了什么实质性的决策支持”,答不上来。而这个糖尿病饮食平台,你是真的能用BMR公式、GI加权、三餐热量配比这些具体的、可解释的规则,让用户得到一个“今天按这个吃就行”的确定性结果——这就是系统存在的意义。
如果后续有条件,这个项目也留了很好的扩展口子:把规则推荐升级成基于用户血糖反馈的个性化推荐、加入食物图片识别、做移动端适配,都是在现有表结构和架构上能顺势延伸的方向。但现阶段,把SpringBoot这套工程骨架、数据设计和部署链路吃透,已经足够支撑一门毕设或者一次完整的全栈实战了。