ARTICLE DETAIL

资讯详情

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

Vue+Django全栈实战:养老院服务推荐系统完整实现

Vue+Django全栈实战:养老院服务推荐系统完整实现 做养老院服务推荐系统是我去年带的一个全栈实战项目。当时花了不少时间调研走访了几家本地养老机构发现他们的服务管理基本还停留在纸质台账和口头传递的阶段——老人想找个康复理疗师、家属想了解有哪些文娱活动、护工排班调换全靠吼。技术栈上我选了Python系的Vue Django/Flask组合用PyCharm做主力开发环境整套下来从零到落地用了大概一个月。这篇内容就把整个设计和实现过程完整拆开讲从为什么选这套技术栈到数据库怎么建模、推荐算法怎么写、前后端怎么联调再到我实际踩过的坑都一次性说清楚。先给项目定个位这不是一个简单的CRUD管理系统核心价值在“服务推荐”。老人注册后填写基础健康信息、兴趣爱好、自理能力系统根据这些标签自动推荐合适的服务项目——比如有关节炎的推荐理疗套餐独居且喜欢下棋的推荐棋牌社交活动。这个推荐逻辑听起来简单但落到代码上要拆成用户画像建模、服务标签体系、相似度计算、冷启动处理好几层每一步都有讲究。适合看这篇内容的人一是正在做毕业设计、想找个既有业务深度又有技术含量的选题的同学二是想练全栈项目、把Vue和Python框架串起来的开发者。这个项目麻雀虽小五脏俱全前端有Vue组件化和路由后端有ORM建模和推荐算法部署上有跨域和静态文件处理的坑做完一遍基本就把Web全栈的完整流程走通了。1. 项目设计与技术选型1.1 养老院服务推荐的核心业务逻辑拆解先想清楚一件事这个系统到底在解决什么问题我调研时的核心发现是养老院的服务供给和老人的需求之间存在严重的信息不对称。院方有哪些服务、什么时间开放、适合什么样的人参加老人和家属很难快速搞清楚反过来老人的健康状况、兴趣偏好、过往参与记录院方也缺乏系统化的采集和分析手段。所以系统必须具备三个能力一是用户画像采集。老人注册时填写基础信息年龄、性别、入住日期、健康信息慢病标签、身体受限部位、用药情况、兴趣标签棋牌、书画、园艺、广场舞等、自理能力等级。这些是推荐系统的输入特征。二是服务资源标注。每项服务都要打上可计算的特征标签比如“康复理疗”适合“骨关节炎”人群、强度为“中”、地点在“三号楼一层”、时长“45分钟”。服务还必须支持多标签因为一个服务往往同时适合多种需求的老人。三是匹配和反馈闭环。系统根据画像和服务标签做匹配但不能只做一次就完事。老人点开、报名、评价、护工反馈参与度这些行为数据要回流到系统里让后续推荐越来越准。这是推荐系统区别于普通列表筛选的根本差异。这个逻辑拆清楚之后整个项目就变成了一条完整的数据流用户注册填表 → 构建画像标签 → 服务匹配计算 → 推荐结果展示 → 行为反馈采集 → 画像更新1.2 为什么选了Vue Django/Flask这套组合技术选型上没有搞花活就是奔着“成熟稳定、资料多、好招人好答辩”去的。前端选了Vue后端在Django和Flask之间根据项目规模做了取舍。后端框架的选择逻辑这个项目涉及用户体系、服务管理、预约记录、推荐计算、后台管理实体关系比较多属于典型的中型管理系统。直接选Django更省事——自带的Admin后台可以直接用来给管理员维护服务数据ORM写起来比Flask-SQLAlchemy更顺手自带的认证系统不用自己造轮子。但如果你只是做一个轻量演示版、服务就十来个、不需要后台管理页Flask完全够用启动快、代码量少、结构一目了然。我在项目中主用Django但也专门用Flask写了一个简版推荐接口做性能对比实践下来Flask确实更轻但Django的“全家桶”特性在项目后半段开始体现价值。前端选Vue的逻辑养老院管理系统的特点是页面多、状态复杂——用户要切换“推荐”“服务大厅”“我的预约”几个视图管理员要看数据看板和服务维护界面。用Vue的组件化结构把这些视图拆开再用Vue Router管理路由跳转配合Vuex/Pinia管理登录状态和用户画像数据开发体验和后期维护都比传统的Django模板渲染舒服太多。另外一个重要原因是Vue 3的组合式APIComposition API比Vue 2的选项式Options API在逻辑复用上强太多把推荐的业务逻辑抽成自定义函数不同组件里调用代码能少写三分之一。PyCharm在项目里的定位PyCharm Professional版自带Vue插件可以直接在一个IDE里同时编辑Python后端和Vue前端省去窗口切换的麻烦。调试接口的时候非常方便直接在编辑器里点行号打断点请求进来就能看到数据。如果你用的是社区版记住前端部分拆到VS Code里做各用各的顺手工具不丢人。1.3 推荐系统的技术方案定型和算法选型选题定了“推荐系统”就不可避免地要回答算法用哪种。实际调研发现养老院场景有三个特殊性数据量小一个养老院几百位老人用不了DeepFM那些重型模型服务数量有限几十项到上百项需要考虑扩展性但不焦虑冷启动严重大量新入住老人没有任何行为记录所以算法选型采用了两层策略有行为数据的用协同过滤没行为数据的用基于内容的标签匹配二者混合输出。协同过滤我用的是基于物品的算法Item-based CF——算服务之间的相似度矩阵推荐“和你参与过的服务相似的其他服务”。为什么不用基于用户的User-based CF因为老人的需求高度分化两个年龄差30岁的老人画像可能完全不一样找“相似用户”在几百人的样本里太稀疏而服务的相似关系相对稳定计算一次可以复用。基于内容的匹配则用的是标签向量加余弦相似度。后文会详细讲实现细节。2. 数据库建模与核心数据结构设计2.1 五张核心表如何支撑整个推荐流程数据库设计是这类系统最容易翻车的地方一开始很多人的第一版ER图就把所有字段堆到一张user表里后面扩展一个服务属性就得改表结构非常痛苦。我把核心模型拆成了五张表推荐流程能够跑通全靠它们各司其职表名职责关键字段说明User用户画像年龄、性别、自理能力等级、健康标签JSON、兴趣标签JSONService服务资源服务名称、分类、标签JSON、地点、时长、人数上限Behavior行为记录用户ID、服务ID、行为类型浏览/报名/评价、时间Rating评分数据用户ID、服务ID、分数1-5、评价内容Recommendation推荐结果缓存用户ID、服务ID列表JSON、算法类型、生成时间这个设计最核心的洞察是画像不要用固定字段硬编码。老人的健康情况千差万别如果设计一张表放“高血压”“糖尿病”各一列后面出现“痛风”“白内障”就得加字段。所以用JSON字段存标签数组查询时用Django的__contains做包含过滤既灵活又够用。2.2 Django模型的具体写法与字段设计逻辑我直接用Django的models来定义这些表。以User模型为例看代码from django.db import models class User(models.Model): ABILITY_CHOICES [ (full, 全自理), (half, 半自理), (none, 不能自理), ] name models.CharField(max_length50, verbose_name姓名) age models.IntegerField(verbose_name年龄) gender models.CharField(max_length10, choices[(male, 男), (female, 女)]) ability_level models.CharField(max_length10, choicesABILITY_CHOICES, defaultfull) health_tags models.JSONField(defaultlist, verbose_name健康标签) interest_tags models.JSONField(defaultlist, verbose_name兴趣标签) room_number models.CharField(max_length20, blankTrue, verbose_name房间号) created_at models.DateTimeField(auto_now_addTrue) class Meta: db_table user_profile def __str__(self): return self.name注意三个细节health_tags和interest_tags用了JSONField这是Django 3.1之后内置的字段类型底层对应MySQL的JSON类型存取都不用手动序列化list类型的默认值必须用defaultlist不能用[]否则会触发可变默认值的经典坑db_table指定了表名避免默认的app名_user那一长串blankTrue允许房间号为空因为有些老人还没分配床位。2.3 Service与Behavior模型推荐系统两侧的输入和反馈Service表是用来被推荐的“物品”必须打上机器可计算的标签。一个服务的标签体系我分成了三类健康适配标签匹配慢病、兴趣适配标签匹配爱好、体力消耗等级匹配自理能力。class Service(models.Model): CATEGORY_CHOICES [ (medical, 医疗护理), (rehab, 康复训练), (recreation, 文娱活动), (life, 生活照料), (nutrition, 营养膳食), ] name models.CharField(max_length100, verbose_name服务名称) category models.CharField(max_length20, choicesCATEGORY_CHOICES) health_tags models.JSONField(defaultlist, verbose_name适配健康标签) interest_tags models.JSONField(defaultlist, verbose_name适配兴趣标签) energy_level models.IntegerField(default3, help_text体力消耗等级1低-5高) location models.CharField(max_length100, verbose_name地点) duration_minutes models.IntegerField(default60) capacity models.IntegerField(default20, verbose_name人数上限) description models.TextField(blankTrue) is_active models.BooleanField(defaultTrue)Behavior表则记录了老人的每一次触点行为。这里我特别做了区分view(浏览详情)权重为1signup(报名参加)权重为3rating(给出评价)权重为5。后面计算用户偏好分数时加权求和就能得到一个比单纯二值“喜欢/不喜欢”更平滑的偏好值。class Behavior(models.Model): BEHAVIOR_TYPES [ (view, 浏览), (signup, 报名), (rating, 评分), ] user models.ForeignKey(User, on_deletemodels.CASCADE, related_namebehaviors) service models.ForeignKey(Service, on_deletemodels.CASCADE, related_namebehaviors) behavior_type models.CharField(max_length10, choicesBEHAVIOR_TYPES) weight models.FloatField(default1.0) created_at models.DateTimeField(auto_now_addTrue)做行为记录时有一个容易漏掉的点权重不能只存在代码里必须落库。因为后续你可能要调整权重策略或者做数据回溯分析如果权重只是散落在代码的if/else里改动成本高得吓人。2.4 用Flask实现轻量版时的表结构差异如果你选择用Flask表结构可以完全复用只是模型定义从Django的models换成SQLAlchemy。我对比过两者的体验以下是最核心的区别Django的JSONField是内置的直接可用Flask-SQLAlchemy在2.5.x版本里需要配合sqlalchemy_utils库的JSONType或者干脆用Text字段存序列化后的JSON字符串取用时手动json.loads。Django的choices会直接体现在表单和Admin里方便管理员筛选Flask里choices只是模型层的约束前端展示还得自己处理。Django自带迁移工具python manage.py makemigrations一句搞定Flask需要先安装flask-migrate配合alembic。如果你只做演示Flask完全够快但如果项目要正式交接给养老院使用、管理员需要维护服务数据Django的Admin后台能帮你省下写管理界面的时间强烈建议优先考虑Django。3. 推荐算法的设计、实现与调优3.1 冷启动阶段基于标签的候选集生成新用户没有任何行为数据协同过滤直接抓瞎。这个阶段我用基于内容的推荐算法扛住。逻辑非常直白把用户的健康标签、兴趣标签拼成一个查询条件和Service的health_tags、interest_tags做交集匹配取交集数量大于0的服务作为候选集再按三个维度排序匹配标签数多的排前面、体力消耗等级和用户自理能力匹配的排前面、最近新增的服务排前面。核心匹配函数我写成这样def content_based_recommend(user, top_n10): user_health set(user.health_tags) user_interest set(user.interest_tags) candidates [] for service in Service.objects.filter(is_activeTrue): service_health set(service.health_tags) service_interest set(service.interest_tags) health_match len(user_health service_health) interest_match len(user_interest service_interest) if health_match 0 and interest_match 0: continue # 匹配标签数 自理能力适配加分 score health_match * 2 interest_match * 1.5 # 体力等级适配用户不能自理但服务体力消耗高 - 惩罚 if user.ability_level none and service.energy_level 4: score - 2 elif user.ability_level full: score 0.5 candidates.append((score, service)) candidates.sort(keylambda x: x[0], reverseTrue) return [svc for _, svc in candidates[:top_n]]这里有个细节容易忽略健康标签的权重为什么比兴趣标签高因为对老人的健康风险控制是底线。一个膝盖退化的老人被推荐爬山郊游即使是有兴趣这个推荐也是失败且危险的。所以健康匹配分权重要高存在健康冲突时宁可牺牲兴趣匹配度。3.2 协同过滤阶段基于物品的相似度矩阵计算用户产生几条行为记录之后协同过滤开始介入。我的方案是先基于历史行为构建“用户-服务”隐式评分矩阵行为加权求和再算服务之间的余弦相似度矩阵。隐式评分矩阵构建对每个用户把其行为按服务分组加权累加得到该用户对该服务的偏好分。def build_user_service_matrix(): matrix {} behaviors Behavior.objects.all().select_related(user, service) for b in behaviors: uid b.user_id sid b.service_id if uid not in matrix: matrix[uid] {} matrix[uid][sid] matrix[uid].get(sid, 0) b.weight return matrix服务相似度计算把所有用户对两个服务的评分向量抽出来计算余弦相似度。但这里有个Fork in the road——要不要做评分归一化直接按加权和算有的老人行为非常多一个服务天然被多个行为堆高分数导致他喜欢的服务都被拔高。我先按用户做了均值中心化再拿中心化后的值算余弦相似度效果好了不少。from sklearn.metrics.pairwise import cosine_similarity import numpy as np def build_item_similarity_matrix(matrix): service_ids sorted({sid for u in matrix.values() for sid in u.keys()}) service_idx {sid: i for i, sid in enumerate(service_ids)} user_service_array np.zeros((len(matrix), len(service_ids))) user_idx {} for i, (uid, services) in enumerate(matrix.items()): user_idx[uid] i # 均值中心化 avg sum(services.values()) / max(len(services), 1) for sid, score in services.items(): user_service_array[i, service_idx[sid]] score - avg sim_matrix cosine_similarity(user_service_array.T) return service_ids, service_idx, sim_matrixcosine_similarity是sklearn.metrics.pairwise里现成的直接把用户-服务矩阵转置后喂进去就能得到服务-服务的余弦相似度矩阵。不用自己手写向量点积除以模长少写一堆容易出错的代码。矩阵算完了存到缓存或者直接存库不需要每次推荐都重算一遍因为服务数量和用户数量在短期内不会剧烈变化。3.3 两类算法怎么融合出最终结果两类结果不是二选一而是合并去重。我的融合策略是有行为记录的用户协同过滤结果占60%权重内容推荐结果占40%无行为记录的用户直接用内容推荐两类结果都有的按综合分排序截断前N项混合时还有一个细节协同过滤推荐出来的服务可能已经过期或下架了所以最终结果一定要做is_active过滤和名额校验。def hybrid_recommend(user, top_n10): cf_result item_cf_recommend(user, top_n5) cb_result content_based_recommend(user, top_n5) merged {} for score, svc in cf_result: merged[svc.id] score for score, svc in cb_result: if svc.id in merged: merged[svc.id] score * 0.8 else: merged[svc.id] score * 0.8 sorted_items sorted(merged.items(), keylambda x: x[1], reverseTrue) active_services Service.objects.filter(id__in[sid for sid, _ in sorted_items[:top_n]], is_activeTrue) return list(active_services)这个0.8的衰减系数是我在调优时试出来的不同项目数据分布不一样你可能需要自己调。原则很简单协同过滤用真实行为做基础可信度更高内容匹配更多是兜底和拓展不能喧宾夺主。3.4 推荐效果怎么评估毕业设计层面做不了在线AB测试就做离线评测。我把行为数据按时间留出最后20%作为测试集前面80%作为训练集算相似度矩阵然后对每个测试用户推荐10项看在测试集里有多少服务是被用户真实点过/报名过的命中率就是HR10 命中数 / 用户数。实测下来纯内容推荐的HitRate在22%左右混合推荐能到31%提升还是很明显的。如果想再往上走可以加一个简单的逻辑回归把用户年龄、自理能力、历史活跃度做成特征但养老院场景数据量小先把规则和矩阵方法吃透足够支撑整个选题。4. Django与Vue前后端分离实战4.1 创建Django项目与App的规范流程开发环境我用PyCharm建项目虚拟环境直接用venv。Django项目结构按标准方式拆django-admin startproject nursing_home cd nursing_home python manage.py startapp users python manage.py startapp services python manage.py startapp recommendations python manage.py startapp behaviors注意一个原则一个App负责一个完整的业务域。Users管用户画像和认证Services管服务资源Behaviors管行为记录Recommendations管推荐计算和API。千万别把什么逻辑都堆到一个App里项目刚开始觉得省事到后面改一个模型要翻几百行代码时就后悔了。新App创建完必须干两件事在nursing_home/settings.py的INSTALLED_APPS里注册然后执行python manage.py makemigrations python manage.py migrate。很多人第一步注册就漏了导致表建不出来迁移时报No changes detected排查半天原来App根本没被加载。4.2 用Django REST Framework搭建API接口前后端分离的要点是后端只出JSON不管页面渲染。我用DRFDjango REST Framework搭建API层写一个服务列表接口的序列化器# services/views.py from rest_framework import viewsets from .models import Service from .serializers import ServiceSerializer class ServiceViewSet(viewsets.ModelViewSet): queryset Service.objects.filter(is_activeTrue) serializer_class ServiceSerializer http_method_names [get, post, put, delete]路由注册在nursing_home/urls.py里用DRF的DefaultRouter自动生成CRUD路由这里是它的最大价值——一个ViewSet自动生成增删改查的全部接口URL不用再手动写五个视图函数。跨域问题是前后端联调的必经之路。前端跑在localhost:8080后端跑在localhost:8000端口不同就是跨域。装一个django-cors-headers在settings.py里做三处配置INSTALLED_APPS [corsheaders, ...] MIDDLEWARE [corsheaders.middleware.CorsMiddleware, ...] CORS_ALLOWED_ORIGINS [http://localhost:8080]注意中间件的位置有讲究CorsMiddleware要放在CommonMiddleware之前官方文档明确说的不然部分请求还是会被拦截。4.3 Vue 3项目初始化与核心页面设计Vue前端我用Vite构建工具初始化比webpack快太多了启动秒开。npm create vitelatest nursing_home_frontend -- --template vue cd nursing_home_frontend npm install axios vue-router4 pinia项目目录结构我这样规划src/views/HomeView.vue首页推荐服务流src/views/ServiceHall.vue服务大厅全量服务列表筛选src/views/ProfileView.vue个人画像编辑src/views/OrderView.vue预约记录src/components/ServiceCard.vue服务卡片组件src/components/RecommendList.vue推荐列表组件src/api/index.js所有API请求封装页面的核心是ServiceCard组件展示服务名、分类、适配标签、地点、时长、人数余量点击卡片跳转详情并调用行为上报接口。4.4 Axios请求封装与Pinia状态管理Axios封装时我统一在后端API响应里包了一层{code, data, message}前端axios拦截器统一判断code// src/api/index.js import axios from axios import { useUserStore } from ../stores/user const request axios.create({ baseURL: http://localhost:8000/api, timeout: 10000, }) request.interceptors.request.use(config { const userStore useUserStore() if (userStore.token) { config.headers.Authorization Token ${userStore.token} } return config }) request.interceptors.response.use( response { if (response.data.code 0) { return response.data.data } return Promise.reject(new Error(response.data.message || 请求失败)) }, error Promise.reject(error) ) export default requestPinia相比Vuex最大的改进是TypeScript友好、语法简洁、没有mutation和commit这一层级。这个项目里登录状态、当前用户画像、推荐结果列表都放Pinia里推荐结果跨页面保持不丢失。// src/stores/user.js import { defineStore } from pinia export const useUserStore defineStore(user, { state: () ({ token: localStorage.getItem(token) || , userInfo: JSON.parse(localStorage.getItem(userInfo) || {}), }), actions: { async login(username, password) { const data await request.post(/auth/login/, { username, password }) this.token data.token this.userInfo data.user localStorage.setItem(token, this.token) localStorage.setItem(userInfo, JSON.stringify(this.userInfo)) }, logout() { this.token this.userInfo {} localStorage.removeItem(token) localStorage.removeItem(userInfo) }, }, })4.5 Vue路由设计用户视角的页面导航Vue Router用的是4.x版本创建路由时核心是懒加载按页面拆代码块首屏加载速度能提升不少。// src/router/index.js import { createRouter, createWebHistory } from vue-router const routes [ { path: /, name: Home, component: () import(../views/HomeView.vue), meta: { requiresAuth: true }, }, { path: /hall, name: ServiceHall, component: () import(../views/ServiceHall.vue), meta: { requiresAuth: true }, }, { path: /profile, name: Profile, component: () import(../views/ProfileView.vue), meta: { requiresAuth: true }, }, { path: /login, name: Login, component: () import(../views/LoginView.vue), }, ] const router createRouter({ history: createWebHistory(), routes, }) router.beforeEach((to, from, next) { const userStore useUserStore() if (to.meta.requiresAuth !userStore.token) { next(/login) } else { next() } })路由守卫这里有个体验细节未登录跳转登录页时带上redirect参数登录成功再跳回原页面不然用户登录完还要重新找自己想去的地方。5. 开发踩坑记录与关键调试心得5.1 Vue依赖安装和环境配置的坑初始化Vue项目最容易出问题的就是npm。我当时用默认的npm源安装依赖速度极慢还经常报ETIMEDOUT。解决方案是换国内镜像源npm config set registry https://registry.npmmirror.com还有版本兼容的坑。Vite 5以上要求Node.js版本18如果你的电脑还是Node 16会直接报Error: Node version must be 18。用nvm install 18切一下版本再重新npm install。另外新手很容易卡在端口冲突上。Vite默认监听5173端口Django默认8000如果你电脑上有其他服务占用比如之前跑过的Python后端还在后台占用8000启动时会报Address already in useMac/Linux直接lsof -i :8000查PID干掉Windows用netstat -ano | findstr :8000加taskkill /PID。5.2 Django CORS跨域配置的迷惑行为跨域问题是我调试时花时间最长的一环。前端控制台报Access to XMLHttpRequest at http://localhost:8000/api/... has been blocked by CORS policy第一反应就是django-cors-headers没配好。我排查了三个层级的配置第一INSTALLED_APPS和MIDDLEWARE是否都加了少一个都不行。第二中间件顺序是否把CorsMiddleware放在了CommonMiddleware之前。第三CORS_ALLOWED_ORIGINS里写的是不是http://localhost:8080注意结尾不要带斜杠。这三个地方全做到位才行网上很多教程只贴了CORS_ALLOW_ALL_ORIGINSTrue这在开发环境确实简单粗暴但生产环境会被后端安全策略拦所以我在正式部署前换成了白名单模式。还有一个容易忽视的DRF的Session认证模式下跨域请求还要处理CSRF问题。我的解决办法是前端登录接口在axios实例里设置xsrfCookieName和xsrfHeaderName或者在DRF的Authentication里改用Token认证就不受CSRF影响。5.3 Django执行查询和删除对象时容易忽视的坑用Django ORM查询和删除对象有几个反直觉的点。举个最常见的例子用filter()拿到的QuerySet是惰性的只有真正迭代或者list()转列表时才执行SQL查询。写了个条件还修改了数据库结果查出来发现数据没变就是因为查询被缓存了。删除对象时Model.delete()和QuerySet.delete()有本质区别单个对象调用delete()走的是模型的delete()方法触发信号而QuerySet.delete()是直接批量删不走模型信号。如果你的逻辑依赖post_delete信号——比如删除服务后自动清理关联的推荐缓存——批量删除会把信号跳过缓存就残留了。还有一个ORM经典问题filter(userNone)不会查出来user_id IS NULL的记录需要写filter(user__isnullTrue)。这个坑几乎每个人都要踩一次。5.4 Flask部署与Django部署的差异对比我分别尝试把项目部署到本地生产环境。Django用gunicorn作为WSGI服务器一行命令就能跑gunicorn nursing_home.wsgi:application --bind 0.0.0.0:8000 --workers 3但静态文件是个大坑。Django开发环境的runserver会自动服务静态文件生产环境必须执行python manage.py collectstatic然后配置STATIC_ROOT和STATICFILES_DIRS再交给Nginx托管。我第一次部署时忘了collectstatic前端样式直接全丢。Flask部署相对轻gunicorn -w 2 app:app就完事但Flask没有自带Admin和元数据管理生产环境缺少后台很吃亏。这也是我前面强调“如果是真实落地项目优先Django”的原因——毕业答辩还看不出差别真正部署上线时Django的生态优势会比你想象的大得多。5.5 推荐结果为空时的兜底逻辑推荐系统最尴尬的情况就是推荐列表为空。老人更新画像后如果给的健康标签和兴趣标签没有对应服务匹配接口就返回空数组前端界面一片空白。我给接口加了级联兜底策略从宽到窄依次尝试完整标签匹配——精确匹配所有画像标签去掉健康标签只用兴趣标签匹配完全无匹配时返回该分类下最热门的前N个服务按报名人数排序再不行就返回所有is_activeTrue的服务按名称模糊匹配关键词不管命中哪一层都保证接口永远返回至少4条候选前端就永远不会出现空白页。实践下来这个兜底对用户体验的改善非常明显尤其新入住老人画像不全的情况热门服务的兜底策略基本能扛住。6. 项目扩展思路与个人复盘做完这套系统最大的体会是推荐算法本身并不神秘真正的壁垒在数据质量和业务理解。光把机器学习库的模型跑通没有意义你得懂养老院场景——哪些服务对半自理老人有风险、哪些文娱活动要分楼层安排、兴趣标签怎么维护才能不失控。算法只是把业务规则用代码表达出来的载体。如果你想在这个项目上继续扩展有几个明确的方向一是做“家属端”。现在的系统只覆盖了老人和院方管理员家属是养老服务的重要决策者可以做一个家属小程序查看老人的服务参与情况、健康评估、推荐计划部分服务允许家属代预约。这在业务流程上是自然的延伸技术上也只是多一个Vue页面和几套接口。二是做“个性化排班”。现在推荐系统只推荐“服务”但如果能结合“护工人力班次”把推荐结果与护工时间匹配就是典型的运筹优化课题能在答辩时多一个亮点。比如周一上午理疗室只有两个护工当班那同一时段康复理疗的推荐数量就要受限。三是引入“内容衰减机制”。现在协同过滤算法是全局矩阵没有时间窗。一个月前报名过瑜伽课的老人现在可能已经完全失去兴趣了。可以在Behavior模型里加时间衰减因子近期行为权重高、早期行为权重低这样推荐结果会更贴合老人当下的状态。最后给正在做这个选题的同学一个建议别把时间全耗在算法的炫技上。先把业务流程跑通、数据闭环完成、前后端联调顺畅这个项目就已经达到优秀的毕业设计标准了。推荐算法的优化是无止境的但整套系统的完整度和工程质量才是你真正能写在简历上的东西。我在实际开发中最深的一个体会是养老院服务推荐系统的难点不在于技术本身有多难而在于你需要真正理解“被推荐的人是谁”。一个简单的标签匹配算法只要数据建模贴合业务能根据健康状况规避风险就比一个华丽但没有业务约束的深度学习模型有用得多。整个项目做完我最大的收获不是学会了多少新框架而是建立了一种思维——好系统不是功能堆出来的是把每一个决策点都落在真实需求上的结果。
返回列表