尧图精选

SpringBoot+Vue3美食推荐系统架构与实现

🕒 发布时间:2026/9/16 16:08:57 📁 来源:尧图网络
1. 项目概述与背景作为一个长期混迹在餐饮和互联网交叉领域的开发者我发现现代人对美食的需求早已超越了简单的吃饱层面。周末约朋友吃饭时经常面临去哪吃、吃什么的灵魂拷问。传统的美食平台虽然信息量大但缺乏个性化推荐能力这正是我开发这套美食推荐系统的初衷。这个基于SpringBoot2Vue3的全栈项目核心目标是解决三个实际问题通过用户行为分析实现千人千面的美食推荐为中小餐饮商家提供数字化运营工具构建一个可扩展的美食数据中台技术选型上后端采用SpringBoot2MyBatis-Plus的组合看中的是SpringBoot的快速开发能力和MyBatis-Plus对单表操作的极致简化。前端选择Vue3不仅因为其响应式特性优秀更是看中Composition API对复杂交互场景的支持。数据库使用MySQL8.0则是考虑到JSON字段支持和窗口函数等高级特性。2. 系统架构设计2.1 整体技术架构系统采用经典的前后端分离架构分为四个核心层次[前端层] Vue3 Element Plus Axios ↓ HTTP/HTTPS [API网关层] Spring Cloud Gateway ↓ [业务逻辑层] SpringBoot2 MyBatis-Plus ↓ [数据存储层] MySQL8.0 Redis缓存这种分层设计带来了三个显著优势前后端可以并行开发通过Swagger文档定义接口规范网关层统一处理鉴权、限流等横切关注点数据库访问通过MyBatis-Plus抽象减少样板代码2.2 数据库设计精要用户表核心字段设计CREATE TABLE user ( user_id BIGINT PRIMARY KEY COMMENT 雪花算法ID, username VARCHAR(50) UNIQUE COMMENT 登录账号, password_hash VARCHAR(255) COMMENT BCrypt加密, food_preference JSON COMMENT 偏好数据示例 {avoid:[香菜,内脏],favored:[川菜,烧烤]} ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这里特别说明几个设计考量使用雪花算法替代自增ID方便分布式扩展password_hash采用BCrypt加密安全性优于MD5/SHAfood_preference使用JSON类型灵活存储复杂结构行为日志表优化方案CREATE TABLE user_behavior ( id BIGINT PRIMARY KEY, user_id BIGINT NOT NULL, dish_id BIGINT NOT NULL, behavior_time DATETIME(3) NOT NULL COMMENT 精确到毫秒, behavior_type ENUM(VIEW,COLLECT,RATE) NOT NULL, INDEX idx_user_dish (user_id, dish_id), INDEX idx_time (behavior_time) ) PARTITION BY RANGE (TO_DAYS(behavior_time)) ( PARTITION p2023 VALUES LESS THAN (TO_DAYS(2024-01-01)), PARTITION p2024 VALUES LESS THAN (MAXVALUE) );这个设计有几点值得注意使用ENUM替代简单的TINYINT提高可读性采用复合索引提升查询效率按时间范围分区便于历史数据归档3. 核心功能实现3.1 推荐算法实现系统采用混合推荐策略核心算法包括基于用户的协同过滤(UB-CF)public ListLong recommendByUserCF(Long userId, int size) { // 1. 找出相似用户 ListUserSimilarity similars userBehaviorMapper.findSimilarUsers( userId, 10, 0.7); // 2. 提取推荐候选集 SetLong candidateDishes new HashSet(); for(UserSimilarity similar : similars) { ListLong likedDishes userBehaviorMapper.findUserLikedDishes( similar.getUserId(), 20); candidateDishes.addAll(likedDishes); } // 3. 过滤已交互菜品 ListLong interacted userBehaviorMapper.findUserInteractedDishes(userId); candidateDishes.removeAll(interacted); // 4. 按热度排序返回 return dishMapper.sortByPopularity(new ArrayList(candidateDishes)) .stream().limit(size).collect(Collectors.toList()); }实时兴趣加权算法public ListDishVO hybridRecommend(Long userId) { // 基础权重分配 MapString, Double weights Map.of( cf, 0.6, // 协同过滤 hot, 0.2, // 热门推荐 nearby, 0.2 // 附近商家 ); // 实时行为影响 ListUserBehavior recentBehaviors getRecentBehaviors(userId); if(recentBehaviors.stream().anyMatch(b - b.getType() BehaviorType.COLLECT)) { weights.put(cf, weights.get(cf) 0.1); weights.put(hot, weights.get(hot) - 0.1); } // 多路召回加权融合 return mergeResults( cfRecommend(userId, weights.get(cf)), hotRecommend(weights.get(hot)), nearbyRecommend(userId, weights.get(nearby)) ); }3.2 性能优化实践缓存设计三原则高频读取的数据使用Redis缓存如菜品基础信息Cacheable(value dish, key #dishId, unless #result null) public Dish getDishById(Long dishId) { return dishMapper.selectById(dishId); }复杂计算结果缓存5-10分钟如推荐结果Cacheable(value recommend, key user:#userId, cacheManager recommendCache) public ListDishVO getRecommendForUser(Long userId) { // 耗时计算逻辑... }使用多级缓存策略减轻数据库压力用户请求 → 浏览器缓存 → Nginx缓存 → Redis → 数据库分库分表方案当用户行为记录超过500万条时我们实施了以下优化按user_id哈希分片到3个物理库每个库内按月份分表behavior_202401使用ShardingSphere实现透明访问4. 关键问题与解决方案4.1 冷启动问题新用户没有行为数据时我们采用三级降级策略首先尝试基于注册时填写的口味偏好推荐其次使用同城同龄人的热门选择最后回退到全网热门榜单实现代码示例public ListDishVO handleColdStart(Long userId) { User user getUser(userId); // 第一级偏好匹配 if(user.getFoodPreference() ! null) { ListDishVO byPreference matchByPreference(user); if(!byPreference.isEmpty()) return byPreference; } // 第二级人群特征 ListDishVO byDemographic recommendByDemographic( user.getCity(), user.getAgeGroup()); if(!byDemographic.isEmpty()) return byDemographic; // 第三级全局热门 return globalHotRecommend(20); }4.2 实时性挑战为了平衡推荐结果的新鲜度和系统性能我们设计了两层更新机制短期兴趣最近2小时行为使用Redis实时更新// 用户行为处理流水线 public void processBehavior(UserBehavior behavior) { // 实时处理 redisTemplate.opsForZSet().incrementScore( user:recent: behavior.getUserId(), behavior.getDishId(), getBehaviorWeight(behavior.getType())); // 异步落库 kafkaTemplate.send(user_behavior, JSON.toJSONString(behavior)); }长期偏好每天凌晨通过Spark批处理计算更新5. 部署与运维实践5.1 容器化部署方案我们使用Docker Compose定义全套环境version: 3.8 services: app: image: food-recommend:${TAG:-latest} deploy: resources: limits: cpus: 2 memory: 2G healthcheck: test: [CMD, curl, -f, http://localhost:8080/actuator/health] interval: 30s timeout: 5s retries: 3 redis: image: redis:6-alpine ports: - 6379:6379 volumes: - redis_data:/data volumes: redis_data:关键配置说明限制容器资源防止OOM健康检查确保服务可用性Redis数据持久化卷5.2 监控指标体系通过PrometheusGrafana构建监控看板核心指标包括推荐点击率CTR接口响应时间P99缓存命中率MySQL连接池使用率对应的SpringBoot配置示例management.endpoints.web.exposure.include* management.metrics.export.prometheus.enabledtrue management.metrics.tags.application${spring.application.name}6. 扩展与演进方向当前系统已经支持了基础的推荐场景后续计划在三个方向深化视觉推荐增强# 使用ResNet提取菜品图像特征 def extract_image_features(image_path): model tf.keras.applications.ResNet50(include_topFalse) img load_and_preprocess(image_path) features model.predict(np.array([img])) return features.flatten()知识图谱构建-- 建立菜品关系图 CREATE TABLE dish_relation ( dish_id BIGINT, related_id BIGINT, relation_type ENUM(SIMILAR,COMPLEMENT,CONFLICT), strength FLOAT, PRIMARY KEY (dish_id, related_id) );在线学习架构用户行为 → Kafka → Flink实时处理 → 更新推荐模型 ↓ 离线备份到HDFS这个项目从最初的简单查询系统发展到现在的智能推荐平台让我深刻体会到架构设计需要预留扩展空间的重要性。特别是在数据量快速增长后早期做的分库分表方案省去了很多迁移成本。如果让我重新设计可能会更早引入特征存储(Feature Store)的概念为后续的机器学习场景做好准备。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →