1. 项目背景与核心价值
长春作为东北地区的重要城市,拥有丰富多样的地方美食文化。从传统的锅包肉、白肉血肠到新兴的网红餐厅,美食资源呈现爆发式增长。但与此同时,游客和本地居民面临着"选择困难"——如何从众多餐饮选项中快速找到符合个人口味和需求的就餐地点?
这个基于Spring Boot的美食推荐管理系统正是为解决这一痛点而生。我在实际开发中发现,传统的美食推荐主要存在三个问题:
- 信息更新不及时,很多新开店铺无法及时收录
- 推荐维度单一,大多仅基于地理位置或评分
- 缺乏个性化,无法根据用户历史行为调整推荐策略
本系统通过三个核心技术点解决这些问题:
- 采用Spring Boot的自动配置机制快速搭建服务框架
- 整合协同过滤算法实现个性化推荐
- 利用Redis缓存提升推荐响应速度
提示:系统特别设计了"时令推荐"模块,会根据季节变化自动调整推荐权重。比如冬季会提高火锅、炖菜等暖身食物的推荐优先级。
2. 系统架构设计
2.1 技术栈选型
经过对比测试,最终确定的技术方案如下表所示:
| 组件类型 | 技术选型 | 选择理由 |
|---|---|---|
| 后端框架 | Spring Boot 2.7 | 快速启动、丰富的starter支持 |
| 数据库 | MySQL 8.0 + Redis 7.0 | 事务支持+高性能缓存 |
| 推荐算法 | 混合推荐(基于内容+协同过滤) | 平衡准确性和多样性 |
| 前端 | Thymeleaf + Bootstrap 5 | 服务端渲染+响应式布局 |
| 部署 | Docker Compose | 环境一致性保障 |
特别要说明的是算法选择。纯协同过滤在冷启动阶段表现不佳,而纯内容推荐又缺乏惊喜感。最终采用的混合方案在新用户阶段主要依赖:
- 基于餐厅标签的内容匹配
- 基于地域的热门榜单
当用户积累足够行为数据后,逐步过渡到: - 用户-物品矩阵的协同过滤
- 基于时间上下文的加权推荐
2.2 核心模块划分
系统主要包含以下功能模块:
- 用户中心:处理注册登录、偏好设置、收藏夹
- 餐厅管理:商家信息维护、菜单更新、促销活动
- 推荐引擎:实时计算推荐列表
- 评价系统:用户反馈收集与情感分析
- 后台管理:数据统计、内容审核
其中推荐引擎的工作流程特别值得关注:
- 接收用户请求(含位置、历史行为等上下文)
- 检查Redis缓存是否有预计算结果
- 若无缓存则调用算法服务实时计算
- 对结果进行多样性调整(避免连续推荐同类餐厅)
- 返回排序后的推荐列表
3. 关键实现细节
3.1 Spring Boot自动装配实践
在整合MyBatis-Plus时遇到了典型配置冲突问题。解决方案是创建自定义starter:
java复制@AutoConfiguration
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
@EnableConfigurationProperties(MybatisPlusProperties.class)
public class MybatisPlusAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return interceptor;
}
}
这个配置类实现了:
- 自动检测类路径下的MyBatis组件
- 仅在缺少PaginationInterceptor时注册新实例
- 通过@EnableConfigurationProperties绑定配置参数
3.2 推荐算法实现
核心算法类结构如下:
java复制public class HybridRecommender {
// 内容推荐权重(冷启动阶段较高)
private double contentWeight = 0.7;
// 协同过滤权重(随用户行为数据增加而提升)
private double cfWeight = 0.3;
public List<Restaurant> recommend(User user) {
List<Restaurant> contentBased = contentRecommender.recommend(user);
List<Restaurant> cfBased = cfRecommender.recommend(user);
return mergeResults(contentBased, cfBased);
}
private List<Restaurant> mergeResults(List<Restaurant> list1,
List<Restaurant> list2) {
// 使用加权混合算法合并结果
// 同时保证结果多样性(同类型餐厅不超过3家)
}
}
实际测试中发现两个关键参数需要动态调整:
- 内容推荐权重应随用户活跃度增加而递减
- 协同过滤的邻居数量(k值)设为15-20时效果最佳
4. 部署与运维实践
4.1 生产环境部署方案
推荐使用Docker Compose编排服务,示例配置:
yaml复制version: '3'
services:
app:
image: food-recommend:1.0
ports:
- "8080:8080"
depends_on:
- redis
- mysql
redis:
image: redis:7.0-alpine
ports:
- "6379:6379"
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
volumes:
- mysql_data:/var/lib/mysql
部署时特别注意:
- MySQL需要预先创建schema并导入初始数据
- Redis建议配置持久化策略
- 应用服务应设置健康检查探针
4.2 性能优化经验
在高并发测试中发现三个性能瓶颈及解决方案:
-
推荐结果计算耗时:
- 问题:每次请求都实时计算导致RT过高
- 解决:引入两级缓存(Redis+本地Caffeine)
- 效果:P99从1200ms降至200ms
-
数据库连接泄漏:
- 现象:运行一段时间后出现ConnectionTimeout
- 排查:通过Micrometer监控连接池状态
- 修复:配置合理的连接超时和回收策略
-
GC停顿明显:
- 表现:每2小时出现约500ms的STW
- 优化:调整JVM参数(-XX:+UseG1GC)
- 结果:最大GC时间降至200ms以内
5. 典型问题排查案例
5.1 推荐结果重复问题
现象:用户反馈连续多次收到相同餐厅推荐。
排查过程:
- 检查缓存机制 - 正常
- 验证算法随机种子 - 无异常
- 最终发现是多样性控制参数配置错误
根本原因:
properties复制# 错误配置(导致同类型餐厅不限制)
recommend.diversity.threshold=-1
修正方案:
properties复制# 调整为每种类型最多推荐2家
recommend.diversity.threshold=2
5.2 新餐厅曝光不足
用户反馈:新开业的高质量餐厅很难被推荐。
解决方案:
- 在推荐公式中加入时间衰减因子:
java复制double timeWeight = Math.log10(now - openTime) / 10.0; score = baseScore * (1 + timeWeight); - 后台添加"新店扶持"人工加权功能
- 每周自动扫描开业不足30天的餐厅
6. 扩展与演进方向
当前系统已经支持基础推荐功能,但根据实际运营反馈,还可以在以下方向进行增强:
-
多模态搜索:
- 支持通过菜品图片查找餐厅
- 实现"拍立得"式的视觉搜索
-
社交化推荐:
- 整合微信好友的打卡数据
- 开发"朋友常去"推荐频道
-
实时个性化:
- 基于当次用餐人数调整推荐
- 根据实时天气适配推荐(如雨天增加火锅权重)
在技术架构层面,计划逐步:
- 将推荐服务迁移到Spring WebFlux
- 试用GraalVM原生镜像提升启动速度
- 引入Feature Store统一管理推荐特征
