基于Spring Boot的智能推荐卫生健康系统:协同过滤与冷启动实战
2026/9/18 9:02:40 网站建设 项目流程

这个题目我太熟了。每年毕设季,总有一批同学来问“基于Spring Boot的xx系统”怎么做,而“智能推荐卫生健康系统”是其中问得最多的一类。选这个题的人,多半是看中了“Spring Boot + 推荐算法”两个标签——既能体现工程能力,又能带一点算法含量,答辩还好讲。但真正动手你会发现,推荐算法和业务系统怎么结合,才是这道题最磨人的地方。

这个系统本质上解决的是一个很具体的问题:卫健委、社区医院或健康管理平台里,资讯、食谱、运动方案越来越多,用户根本翻不过来,需要系统根据每个人的健康档案和行为习惯,把最适合的内容推送到首页。做得好的话,这套逻辑可以直接扩展成健康管理App的推荐服务。这篇文章我从选题、架构、算法、建库、接口到踩坑,完整过一遍,适合正在做同类毕设、或者想快速上手Spring Boot项目的同学直接参考。

1. 系统整体设计与开发思路

1.1 为什么会选这个题目

先说选题逻辑。一个能被答辩老师认可的毕设题目,通常要满足三个条件:第一,技术栈主流,不说多新,但必须是行业里正在用的;第二,业务场景清楚,能讲明白“解决了什么问题”;第三,有一定发挥空间,不能是纯增删改查。

“智能推荐卫生健康系统”恰好三点全占。Spring Boot是目前Java后端的事实标准,出去找工作面试问的也是这套;卫生健康领域有明确的目标用户和痛点;推荐算法可以浅做也可以深做,实在写不出复杂模型,一个协同过滤也能讲出完整的推荐链路。说白了,这是一个“进可攻退可守”的题目,普通的写法和进阶的写法都能落地。

我见过的做法里,有人只做健康档案管理和资讯浏览,推荐只是一个摆设,答辩时被老师问“推荐逻辑在哪”就卡住了。也有人认真做了物品协同过滤,配合健康档案冷启动,效果和论文深度都明显高一个档次。既然源码都拿出来了,我建议你至少把推荐这条链路跑通。

1.2 功能模块划分与业务流转

这系统拆开来看,主要分前台用户端和后台管理端两块。

前台用户端面向普通用户,功能可以这样规划:

  • 用户注册登录,以及个人中心的基础信息维护
  • 健康档案填写:身高、体重、年龄、既往病史、过敏史、血压血糖等
  • 健康资讯浏览:文章列表、分类筛选、关键词搜索、文章详情
  • 个性化推荐:首页推荐内容、猜你喜欢、相似推荐
  • 健康指标记录:手动录入血压、心率、体重、血糖等数据,系统生成趋势
  • 饮食与运动方案展示,按用户健康档案匹配

后台管理端则是管理员的操作入口:

  • 用户管理:查看用户列表、禁用/启用账号
  • 健康资讯管理:发布、编辑、下架文章
  • 健康档案审核与统计
  • 推荐参数配置:比如推荐条数、相似度阈值
  • 基础数据看板:用户数、文章数、行为数据量

模块拆完,业务流转也要理清楚。用户注册登录以后,第一步是引导填写健康档案,这一步非常关键,因为后续的冷启动推荐全靠这份档案来匹配内容标签。用户开始浏览资讯,每看一篇文章、收藏一条内容,系统都会记录一条行为数据。行为数据攒到一定量,推荐算法就开始工作:用协同过滤找出相似物品,再从物品池里挑出用户可能感兴趣的内容,最终生成推荐列表返回到首页。

整个链路就是:用户注册 → 完善档案 → 产生行为 → 行为入库 → 离线计算偏好 → 生成个性化推荐。把这段话记住,答辩的时候能顺下来,比背代码有用得多。

1.3 技术选型说明

技术选型这块,我直接给出一套经过验证的组合,能少踩很多坑。

后端就用Spring Boot,版本选2.7.x就行了。别一上来追新上3.x,3.x基于Jakarta命名空间,很多老教程和依赖配置对不上,毕设本来就赶时间,犯不上和版本问题死磕。ORM框架我建议用MyBatis Plus而不是JPA,原因是MyBatis Plus的代码生成器、分页插件、条件构造器写起来非常快,国内公司用MyBatis的也更多,答辩说出去也接地气。

数据库选MySQL 8.0是标配。JDK用8或11,这俩版本跑Spring Boot 2.7都没问题。开发工具就用IDEA,社区版免费版完全够用。前端部分不必单独搞前后端分离的Vue项目,除非你本身对前端很熟,否则直接用Thymeleaf模板引擎加Bootstrap或Layui,把页面渲染出来,代码量少很多,部署也更省事。如果你确实想用Vue做前后端分离,那就要处理好跨域,这部分后面我会讲到。

辅助工具类库推荐三个:Lombok省掉getter/setter,Hutool提供各种工具方法(日期、加密、字符串处理),MyBatis Plus Generator用来生成实体和Mapper层代码。这三个组合起来,能省掉至少三分之一的无聊代码。

2. 推荐算法的选型与核心实现

2.1 三种主流推荐算法在毕设里的取舍

推荐算法这个部分,是整个系统的点睛之笔,也是很多同学卡住的地方。其实在毕设场景里,主流选择就三种:基于内容的推荐、协同过滤、混合推荐。

基于内容的推荐,逻辑最简单:看用户A浏览过的资讯都带什么标签,然后推荐同样标签的其他资讯。优点是冷启动问题好解决,用户只要有行为,立刻能推荐;缺点是推荐结果太“死板”,翻来覆去都是同一个主题,个性化程度不高。在健康领域里,“内容相似”本身是很有意义的,比如高血压用户看了一篇《高血压饮食指南》,再推一篇《高血压运动注意事项》,都是围绕“高血压”这个主题,听起来就很合理。

协同过滤又分基于用户的(UserCF)和基于物品的(ItemCF)。UserCF的思路是“找和你相似的人,把他们喜欢的内容推荐给你”;ItemCF则是“找和你看过的内容相似的内容推荐给你”。在毕设这种数据量级下,我会优先推荐ItemCF,原因是健康资讯的数量通常远小于用户数量,资讯之间的相似度矩阵计算量小,而且资讯是相对稳定的物品,相似度矩阵可以离线算好存起来,在线请求的时候直接查结果,速度快,技术上也更好解释。

至于混合推荐,就是把上面两种方法结合:用基于内容的匹配解决冷启动,用协同过滤解决个性化,最后按比例加权合并,或者用规则切换。论文里写混合推荐是加分项,因为它解决了一个真实存在的问题——新用户没有行为数据时协同过滤是无法工作的。实操中用“规则”做混合就是非常简单有效的方式。

2.2 相似度计算与协同过滤代码实现

相似度计算是协同过滤的核心。最常见的做法是余弦相似度。拿资讯推荐举例,我们把每个用户的浏览行为抽象成一个向量,向量的维度是资讯数量,某个用户浏览过第i篇文章,第i个分量就是1,否则为0。两篇资讯之间的相似度,就用“同时浏览过这两篇资讯的用户数”除以各自的“用户向量模长”的乘积来算。

数学公式是长这样的:

similarity(itemA, itemB) = count(usersWhoViewedBoth) / (sqrt(usersWhoViewedA) * sqrt(usersWhoViewedB))

这个公式其实就是余弦相似度在0/1向量上的等价形式。为什么不用皮尔逊系数或者杰卡德系数?余弦相似度在稀疏行为数据下表现稳定,计算直观,代码只要十几行,答辩时你能把它解释清楚,已经超过一半人了。

核心的推荐模块我用Java实现过一版,思路是分三步:

public class RecommendService { private final ArticleMapper articleMapper; private final UserBehaviorMapper behaviorMapper; private final Map<Integer, Map<Integer, Double>> similarityCache = new ConcurrentHashMap<>(); // 1. 构建“用户-资讯”行为矩阵 public Map<Integer, Set<Integer>> buildUserItemMatrix() { List<UserBehavior> behaviors = behaviorMapper.selectList(null); Map<Integer, Set<Integer>> userItemMatrix = new HashMap<>(); for (UserBehavior behavior : behaviors) { userItemMatrix .computeIfAbsent(behavior.getUserId(), k -> new HashSet<>()) .add(behavior.getArticleId()); } return userItemMatrix; } // 2. 根据行为矩阵计算资讯间余弦相似度 public void computeItemSimilarity() { Map<Integer, Set<Integer>> userItemMatrix = buildUserItemMatrix(); List<Integer> articleIds = articleMapper.selectAllIds(); int n = articleIds.size(); for (int i = 0; i < n; i++) { for (int j = i + 1; j < n; j++) { int itemA = articleIds.get(i); int itemB = articleIds.get(j); double similarity = calcCosineSimilarity(itemA, itemB, userItemMatrix); // 只保留相似度较高的,降低内存占用 if (similarity > 0.1) { similarityCache.computeIfAbsent(itemA, k -> new HashMap<>()).put(itemB, similarity); similarityCache.computeIfAbsent(itemB, k -> new HashMap<>()).put(itemA, similarity); } } } } // 3. 为目标用户生成推荐列表 public List<Article> recommendForUser(Integer userId) { Map<Integer, Set<Integer>> userItemMatrix = buildUserItemMatrix(); Set<Integer> viewedItems = userItemMatrix.getOrDefault(userId, Collections.emptySet()); Map<Integer, Double> scoreMap = new HashMap<>(); // 遍历用户看过的东西 for (Integer viewedId : viewedItems) { Map<Integer, Double> simMap = similarityCache.getOrDefault(viewedId, Collections.emptyMap()); for (Map.Entry<Integer, Double> entry : simMap.entrySet()) { Integer candidateId = entry.getKey(); if (viewedItems.contains(candidateId)) { continue; // 看过的就不再推荐 } scoreMap.merge(candidateId, entry.getValue(), Double::sum); } } List<Integer> candidateIds = scoreMap.entrySet().stream() .sorted(Map.Entry.<Integer, Double>comparingByValue().reversed()) .limit(10) .map(Map.Entry::getKey) .collect(Collectors.toList()); if (candidateIds.isEmpty()) { return Collections.emptyList(); } return articleMapper.selectBatchIds(candidateIds); } }

这一步有个关键细节:在线计算时,把用户看过的物品集合和候选物品集合算交集,看过的直接过滤掉,避免推荐冗余内容。排序时用分值累加的方式聚合,最终取Top N。这套逻辑性能足够应对几千用户和几百篇文章的小型系统。

如果你希望代码更严谨,可以提前把相似度矩阵算完存到Redis或MySQL表里,启动时加载到内存缓存,避免每次请求都重新计算。对毕设来说,用ConcurrentHashMap做进程内缓存已经够用,Redis属于锦上添花,有时间再加。

2.3 解决冷启动:健康档案与内容标签的匹配

冷启动是推荐领域的老大难,也是答辩老师最喜欢追问的点。好在卫生健康系统有个天然优势:每个用户注册后都会填写健康档案,档案里的字段就是最好的个性化特征。

我给系统设计了这样一套规则:每篇健康资讯在上架时打上标签,标签从“慢性病类型”“饮食主题”“运动类型”“人群分类”这几个维度取,比如“高血压”“低盐饮食”“有氧运动”“老年人”。用户填完健康档案后,系统把档案字段映射成同样的标签集合,然后计算用户标签和资讯标签的重合度。重合度数越高,意味着这篇资讯对用户越相关。

举个例子:某用户档案里填了“高血压”和“超重”,那系统会优先推荐文章库中带“高血压”和“减重”标签的内容。等用户产生浏览、点赞、收藏等行为之后,协同过滤逐渐接管推荐权重,冷启动期自动结束。

public List<Article> coldStartRecommend(Integer userId) { HealthProfile profile = healthProfileMapper.selectByUserId(userId); // 将用户档案关键词与资讯标签做交集匹配 List<String> userTags = analyzeProfileTags(profile); if (userTags.isEmpty()) { return articleMapper.selectHotArticles(10); // 兜底返回热门文章 } QueryWrapper<Article> wrapper = new QueryWrapper<>(); for (String tag : userTags) { wrapper.like("tags", tag).or(); } wrapper.last("limit 10"); return articleMapper.selectList(wrapper); }

注意看这个兜底逻辑:如果用户档案为空,或者一个标签都匹配不上,就返回热度最高的文章。这个设计在真实系统里叫“热门推荐兜底”,属于非常成熟的降级方案,保证任何时候首页都有内容可展示,不会一片空白。

答辩的时候这一块话术我给你准备好:先用健康档案做基于规则的冷启动,积累到一定行为量级后再切换为协同过滤,实现从冷启动到个性化推荐的平滑过渡。这句话既展示了算法理解,又展示了你考虑了工程落地中的真实问题。

3. 数据库设计与核心接口实现

3.1 核心表结构设计

数据库设计是整个系统的地基,设计得好,后面写代码会非常顺畅。表结构我建议按这个思路来:用户域、档案域、内容域、行为域、推荐域。五个域各自独立又通过外键关联,清晰、可扩展。

给你们整理了一份核心表清单:

表名所属域关键字段说明
sys_user用户域id, username, password, gender, age, phone用户基本信息
health_profile档案域id, user_id, height, weight, blood_pressure, blood_sugar, disease_history与sys_user一对一
health_article内容域id, title, summary, content, tags, category, view_count, status健康资讯内容
user_behavior行为域id, user_id, article_id, behavior_type, create_time记录浏览/收藏/点赞
health_indicator档案域id, user_id, indicator_type, indicator_value, record_time血压、心率等指标记录
recommend_result推荐域id, user_id, article_id, score, create_time存储推荐结果,可后续分析

这里重点说一下user_behavior表,它的behavior_type字段我推荐用字符串而不是数字,取值包括view、collect、like、share,这样代码可读性更好,排查问题时一眼能看懂。create_time和user_id一定要建联合索引,因为推荐算法和用户行为分析都要按这个维度去查数据。数据量不大时建表可以不那么讲究,但索引这个动作建议养成习惯。

health_profile表里disease_history这种字段建议用逗号分隔的字符串存储多个值,比如“高血压,糖尿病”,查询时用FIND_IN_SET或者LIKE都能匹配。如果你用MyBatis Plus,实体类里就直接定义一个String字段,存取都非常方便,比单独建一张疾病关联表更适合毕设的体量。

3.2 后端核心接口实现

后端接口这块,我挑了几个最核心的来讲,登录、健康档案保存、行为上报和推荐查询。

登录注册用JWT方案比较多,Spring Boot里引入jjwt依赖,登录成功后签发一个token,前端后续请求头带上token,后端通过拦截器解析。如果项目时间紧张,用简单的Session方案也完全可以,毕竟毕设的重点在推荐系统本身,不要上来就碰Spring Security那么重的东西,很容易陷入配置地狱。

健康档案的保存接口用一个简单的Controller实现:

@RestController @RequestMapping("/api/profile") public class HealthProfileController { @Autowired private HealthProfileService profileService; @PostMapping("/save") public Result saveProfile(@RequestBody HealthProfile profile) { // 简单的参数校验 if (profile.getUserId() == null) { return Result.error("用户ID不能为空"); } boolean saved = profileService.saveOrUpdate(profile); return saved ? Result.success() : Result.error("保存失败"); } }

行为上报接口是整个推荐系统的数据来源,逻辑很简单但非常关键:

@PostMapping("/behavior") public Result recordBehavior(@RequestBody UserBehavior behavior) { behavior.setCreateTime(new Date()); behaviorMapper.insert(behavior); // 异步触发相似度矩阵的增量更新(简化处理) recommendService.rebuildCacheIfNecessary(); return Result.success(); }

真正的项目里会把这部分做成异步消息,但对毕设而言,插入行为数据后触发一次缓存刷新已经足够。这里有个思路要讲清楚:推荐系统的质量高度依赖行为数据的完整性,所以前端点击文章详情时一定要上报view行为,收藏按钮上报collect行为,这些数据越全,推荐越准。

推荐查询接口是最后一步,把推荐结果返回前端:

@GetMapping("/recommend") public Result getRecommendList(@RequestParam Integer userId) { List<Article> articles = recommendService.recommendForUser(userId); if (articles.isEmpty()) { articles = recommendService.coldStartRecommend(userId); } return Result.success(articles); }

注意这个接口里的降级顺序:先尝试协同过滤,拿不到结果就切到冷启动规则,还是拿不到就返回热门兜底。三层逻辑层层递进,保证接口永远有数据返回。这段代码你在答辩时可以大大方方讲,真实系统里也是这样设计的。

3.3 前端页面与接口联调

前端我推荐直接用Thymeleaf加Bootstrap,原因前面提过,就是省事。页面结构大概这样:首页展示推荐资讯列表,健康档案页是一个表单页,资讯详情页带上报行为的JS代码,个人中心展示历史记录。

如果用Thymeleaf,Controller里返回视图:

@GetMapping("/home") public String home(Model model, @RequestParam Integer userId) { List<Article> recommendList = recommendService.recommendForUser(userId); model.addAttribute("recommendList", recommendList); return "home"; }

页面里用Thymeleaf语法遍历列表,加上Bootstrap的卡片样式,展示效果就不会太差。如果你想在前端独立调后端接口,也可以用Vue加Axios,但那样要处理跨域。Spring Boot里解决跨域非常简单,写一个配置类:

@Configuration public class CorsConfig { @Bean public WebMvcConfigurer corsConfigurer() { return new WebMvcConfigurer() { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/api/**") .allowedOrigins("http://localhost:5173") .allowedMethods("GET", "POST", "PUT", "DELETE"); } }; } }

这部分我特别提醒一下,前后端分离的项目十个里有五个是挂在跨域上。你本地前端跑5173端口,后端跑8080端口,端口不同就必然跨域,要么配CorsConfig,要么在Controller上加@CrossOrigin注解。别等到浏览器控制台报错才想起这回事。

4. 实操过程中最容易踩的坑

4.1 项目初始化和环境配置的坑

先讲一个我当年自己踩过的坑。Spring Boot版本不对,会导致一堆依赖死活引不进来。有同学用了Spring Boot 3.2,然后照着一个旧教程引入MyBatis Plus,结果启动直接报错,翻来覆去查了一天才发现是版本兼容问题。MyBatis Plus对Spring Boot 3的适配和Spring Boot 2完全不同,所以还是那句话:毕设老老实实选Spring Boot 2.7.x,教程多,依赖兼容性好,网上的资料随便搜都能用。

再说配置文件的坑。application.yml里最核心的三处配置要写对:

spring: datasource: url: jdbc:mysql://localhost:3306/health_recommend?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: 你自己的密码 driver-class-name: com.mysql.cj.jdbc.Driver jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8 mybatis-plus: mapper-locations: classpath*:mapper/**/*.xml configuration: map-underscore-to-camel-case: true

url里的serverTimezone=Asia/Shanghai非常重要,不写的话MySQL 8.0经常报时区错误。编码一定用utf8,不然存中文信息进去全变问号。这些都是一行配置的问题,但每一个都能让你调试半小时起步。

Lombok也是一个经典坑源。IDEA里必须安装Lombok插件,同时pom.xml里引入依赖,否则编译直接报“找不到getter/setter”。如果你用的JDK 11以上的版本,Lombok版本太老也会出问题。稳妥的组合是JDK 8加Lombok 1.18.20以上。

4.2 推荐模块的Bug现场

推荐模块最容易出的问题有三个,我都排一下,方便你直接对照自己的项目。

第一个是用户没有行为数据时,协同过滤返回空列表。这个就是我前面讲的冷启动问题,解决办法就是调用推荐接口前先判断列表是否为空,空就走规则推荐。这个逻辑一定要放在Service层处理,别让Controller层去判断,保持各层职责清晰。

第二个是行为数据重复上报导致计算偏差。用户重复刷新一个页面,前端可能连续上报三四条view行为,如果不做去重,这个物品的权重就会被异常抬高。解决方案是:前端在每次会话里只上报一次同一文章的view,或者后端在一个时间窗口内只接受一次重复行为。做毕设的话,后端判断一下同一用户同一文章短时间内是否重复上报,简单可靠。

第三个是缓存导致推荐结果不更新。我刚写完推荐模块时,明明新增了行为数据,推荐结果却跟之前一模一样。排查半天发现是相似度矩阵只在项目启动时计算了一次。解决方式也很简单:每次写入行为数据后,把相似度矩阵重新计算一遍,或者设定一个定时任务,每小时重算一次。对毕设体量来说,直接在行为上报接口里触发重新计算完全够用,代码就一行调用,副作用可以忽略。

4.3 答辩高频问题整理

答辩环节,老师们对推荐系统往往格外“照顾”,因为推荐在他们眼里属于有故事可讲的部分。我整理了三个高频问题和参考回答思路。

第一个问题:为什么选协同过滤而不是其他推荐模型?回答思路是:毕设系统面向健康资讯推荐场景,数据规模有限,协同过滤实现简单、可解释性强,无需大量标注数据。深度学习模型更适合海量数据和复杂特征场景,但在小规模系统里收益不明显。这番话既回答了选择原因,又展示了你了解更广阔的推荐技术方向。

第二个问题:冷启动问题怎么解决?回答思路:系统先让用户填写健康档案,将档案字段映射为内容标签,运用基于内容的规则推荐完成冷启动。用户产生足够多行为后,系统自动切换为协同过滤,实现个性化推荐的平滑过渡。这段内容在2.3已经讲过,这里再背一遍就是。

第三个问题:推荐效果怎么评估?如果你系统里已经预留了行为数据,就答:以用户点击率作为效果指标,系统上线后持续记录推荐位文章的点击行为,对比推荐位点击率和全局点击率,评估个性化推荐的提升效果。如果时间允许,还可以做一个简单的离线实验,把行为数据按时间分成训练集和测试集,计算精确率或者召回率。哪怕没有真实用户,你也要把这个指标体系说清楚,证明你的设计不是拍脑袋。

4.4 源码使用建议与后续扩展方向

拿到这套源码之后,我不建议照搬提交。既然学校要求“基于Spring Boot的智能推荐卫生健康系统”,而且明确写了“智能推荐”,那推荐就得做出真实存在感。至少要做到:首页确实能根据档案和行为给出不同的推荐结果,管理后台能对资讯进行管理并配置标签。

后面想扩展的话,方向也很多:

  • 增加每日饮食推荐模块,结合用户档案中的忌口和慢性病情况,按天生成食谱计划
  • 增加运动方案推荐,根据健康档案中的身高体重和运动习惯匹配合适的运动计划
  • 用ECharts展示用户健康指标的趋势图,血压、血糖、心率的变化一目了然
  • 把推荐结果存入recommend_result表,积累数据后分析推荐位的点击转化率,形成完整的评估闭环
  • 有条件的话部署到云服务器,用Nginx做反向代理,域名访问,答辩现场直接展示线上效果

这些方向都属于在原项目基础上的合理延展,工作量可控,但呈现出来的完整度会高很多。

我个人在实际操作中最强烈的感受是:推荐系统的精髓不在算法多高深,而在“数据—算法—业务”这条链路是否闭环。论文里写完算法公式,代码里必须让推荐结果真实可见、可验证,答辩现场演示三分钟,胜过你背十页稿子。最后再分享一个很实用的建议:做任何毕设系统,先把基础CRUD跑通,再把推荐链路接上,最后才考虑界面和细节打磨。顺序不要搞反,先有核心功能,再谈锦上添花。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询