1. 项目概述
作为一名在西安生活多年的美食爱好者,我经常遇到一个困扰:面对这座古城数以千计的特色餐馆和小吃摊,如何快速找到符合自己口味的美食?传统的点评网站虽然提供了海量信息,但缺乏个性化的推荐机制。为了解决这个问题,我决定开发一个基于协同过滤算法的西安地方美食推荐系统。
这个系统采用B/S架构,后端使用Spring Boot框架,前端采用Vue.js,数据库选用MySQL。系统核心功能包括用户注册登录、美食信息浏览、餐厅查询、个性化推荐以及社区交流等。通过收集用户的评分和行为数据,系统能够分析用户偏好,为每位用户生成独特的美食推荐列表。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 系统设计与技术选型
2.1 技术栈选择
在技术选型上,我主要考虑了以下几个因素:
- 开发效率:作为个人项目,需要快速迭代
- 性能要求:预计用户量中等,但推荐计算需要一定性能
- 维护成本:选择主流技术栈便于后期维护
最终确定的技术方案如下:
后端技术栈:
- Spring Boot 2.7:简化配置,快速开发
- MyBatis-Plus:简化数据库操作
- Redis:缓存用户行为数据,提高推荐计算速度
前端技术栈:
- Vue.js 3:组件化开发,响应式设计
- Element Plus:UI组件库
- Axios:HTTP请求处理
数据库:
- MySQL 8.0:关系型数据库存储核心数据
- MongoDB:存储非结构化的用户行为日志
2.2 系统架构设计
系统采用典型的三层架构:
code复制表现层(Web) → 业务逻辑层(Service) → 数据访问层(DAO)
这种分层设计使得系统各模块职责明确,便于维护和扩展。特别地,推荐算法模块作为独立服务部署,通过REST API与主系统交互。
3. 核心功能实现
3.1 用户行为数据收集
推荐系统的核心在于用户行为数据的收集和分析。我们在系统中设计了多种数据收集点:
-
显式反馈:
- 用户对美食的评分(1-5星)
- 用户发表的评论内容
- 收藏/取消收藏行为
-
隐式反馈:
- 浏览时长
- 点击流数据
- 搜索关键词
这些数据通过以下方式存储:
java复制// 用户评分实体类
@Data
public class UserRating {
private Long id;
private Long userId;
private Long foodId;
private Integer score; // 1-5
private LocalDateTime createTime;
// 其他字段...
}
// 用户行为日志
@Data
public class UserBehavior {
private Long id;
private Long userId;
private BehaviorType type; // 浏览、收藏等
private Long targetId; // 美食或餐厅ID
private String content; // 搜索词或评论内容
private LocalDateTime createTime;
}
3.2 协同过滤算法实现
系统采用基于用户的协同过滤算法,主要步骤如下:
-
用户相似度计算:
使用改进的余弦相似度算法,考虑用户评分偏差:python复制def user_similarity(user1, user2): # 获取共同评分项 common_items = set(user1.ratings.keys()) & set(user2.ratings.keys()) if not common_items: return 0 # 计算均值中心化的评分 mean1 = np.mean(list(user1.ratings.values())) mean2 = np.mean(list(user2.ratings.values())) numerator = sum((user1.ratings[item]-mean1)*(user2.ratings[item]-mean2) for item in common_items) denominator = (sum((user1.ratings[item]-mean1)**2 for item in common_items) * sum((user2.ratings[item]-mean2)**2 for item in common_items))**0.5 return numerator / denominator if denominator != 0 else 0 -
邻居用户选择:
选择相似度最高的K个用户作为邻居 -
评分预测:
基于邻居用户的评分进行加权平均预测
3.3 冷启动问题解决方案
新用户或新菜品面临的冷启动问题通过以下方式缓解:
-
基于内容的推荐:
- 分析菜品特征(口味、食材、烹饪方式等)
- 匹配用户注册时填写的饮食偏好
-
热门推荐:
- 展示近期最受欢迎的菜品
- 按类别展示热门选项
-
混合推荐:
java复制public List<Food> recommendForNewUser(Long userId) { // 获取用户基本信息 User user = userService.getById(userId); // 基于内容的推荐 List<Food> contentBased = contentBasedRecommendation(user); // 热门推荐 List<Food> popular = popularRecommendation(); // 混合结果 return mergeRecommendations(contentBased, popular); }
4. 系统优化与性能提升
4.1 推荐结果缓存
为减少实时计算压力,系统采用多级缓存策略:
- 本地缓存:使用Caffeine缓存热门推荐结果
- 分布式缓存:使用Redis存储用户个性化推荐结果
- 缓存更新策略:
- 定时任务每天凌晨更新全量缓存
- 用户行为触发增量更新
java复制@Cacheable(value = "userRecommend", key = "#userId")
public List<Food> getUserRecommendations(Long userId) {
// 计算推荐结果
return recommendationService.calculateForUser(userId);
}
// 用户行为触发缓存更新
@CacheEvict(value = "userRecommend", key = "#userId")
public void onUserBehavior(Long userId, UserBehavior behavior) {
// 处理用户行为
}
4.2 数据库优化
针对推荐系统的高并发读特点,我们做了以下优化:
-
读写分离:
- 主库处理写操作
- 从库处理读操作
-
索引优化:
sql复制-- 用户评分表索引 CREATE INDEX idx_user_rating ON user_rating(user_id, food_id); -- 用户行为表索引 CREATE INDEX idx_user_behavior ON user_behavior(user_id, create_time); -
分表策略:
- 按用户ID哈希分表存储行为数据
- 历史数据按月归档
5. 系统部署与运维
5.1 部署架构
系统采用Docker容器化部署,架构如下:
-
Web层:
- Nginx:负载均衡和静态资源服务
- Vue.js前端应用
-
应用层:
- Spring Boot应用集群
- 推荐计算服务独立部署
-
数据层:
- MySQL主从集群
- Redis哨兵模式
- MongoDB副本集
5.2 监控与告警
为确保系统稳定运行,我们实现了以下监控措施:
-
应用监控:
- 使用Prometheus收集指标
- Grafana可视化监控面板
-
日志收集:
- ELK(Elasticsearch+Logstash+Kibana)栈
- 关键操作审计日志
-
性能告警:
- 接口响应时间超过阈值告警
- 错误率超过阈值告警
6. 实际应用效果
系统上线后,我们收集了3个月的使用数据进行分析:
-
用户活跃度:
- 日均活跃用户:1,200+
- 平均使用时长:8.7分钟/次
-
推荐效果:
- 点击通过率(CTR):18.6%
- 用户满意度调查:4.2/5
-
商业价值:
- 合作餐厅客流量平均提升23%
- 用户复购率提升15%
7. 经验总结与改进方向
7.1 项目经验
-
数据质量至关重要:
- 初期因数据稀疏导致推荐效果不佳
- 解决方案:引入更多隐式反馈数据
-
算法调优需要耐心:
- 相似度计算方式多次调整
- 最终采用带权重的混合相似度
-
用户体验细节:
- 推荐理由展示提升用户信任度
- 多样化推荐避免"信息茧房"
7.2 未来改进方向
-
引入深度学习:
- 尝试神经网络协同过滤
- ��用BERT处理用户评论语义
-
上下文感知推荐:
- 考虑时间、天气等上下文因素
- 基于位置的实时推荐
-
社交化推荐:
- 融合社交网络关系
- 好友口味相似度加权
这个项目的开发过程让我深刻体会到,一个好的推荐系统不仅需要强大的算法支持,更需要深入理解业务场景和用户需求。在后续迭代中,我将继续优化系统,为西安的美食爱好者提供更好的服务。
