毕业设计:基于springboot+vue前后端分离的美食推荐系统(源码)
目录
一、项目背景
在移动互联网与社交媒体深度渗透人们日常生活的当下,美食文化已成为现代城市生活中不可或缺的重要组成部分。随着大众消费水平的不断提升与生活节奏的日益加快,外出就餐、美食打卡已成为年轻人社交娱乐与品质生活的主流方式。据统计,我国餐饮市场规模已突破4万亿元,餐饮用户数量持续攀升,消费者对于美食信息获取的需求呈现出多元化、个性化、即时化的显著特征。
然而,面对海量的餐饮信息与层出不穷的网红餐厅,消费者在美食选择过程中普遍面临着“信息过载”的困境。一方面,传统的美食推荐平台多采用大众评分、距离排序等单一维度进行筛选,难以精准匹配用户的个性化口味偏好、消费预算、用餐场景等差异化需求;另一方面,大量优质的本土特色餐厅因缺乏有效的曝光渠道,难以触达目标消费群体,导致供需双方信息不对称问题突出。此外,现有平台普遍存在推荐结果同质化严重、评价真实性难以保障、交互体验欠佳等痛点,无法为用户提供真正贴心、高效的美食发现体验。
随着大数据技术与人工智能算法的快速发展,个性化推荐系统已成为解决信息过载问题的有效手段。通过分析用户的历史行为、口味偏好、社交关系等多维数据,构建用户画像与菜品特征模型,能够实现“千人千面”的精准推荐,显著提升用户的美食探索体验。同时,前后端分离的架构模式使得系统开发更加灵活高效,前端Vue框架能够构建流畅友好的用户交互界面,后端SpringBoot框架则提供了稳定可靠的服务支撑。
基于此,本课题拟设计并实现一套基于SpringBoot+Vue前后端分离架构的美食推荐系统。该系统将集成协同过滤推荐算法与内容推荐算法,结合用户的地理位置、消费记录、口味标签等信息,为用户提供个性化的餐厅与菜品推荐服务。系统还将包含餐厅信息管理、用户评价、美食社区、收藏分享等核心功能模块,致力于打造一个集美食发现、社交互动、信息管理于一体的综合性美食服务平台。该系统的开发与应用,对于提升用户美食消费体验、助力中小餐饮商家精准获客、推动餐饮行业数字化转型具有重要的理论价值与现实意义。
二、技术介绍
本系统采用前后端分离的架构模式进行设计与开发,前端基于Vue.js框架构建用户界面,后端基于SpringBoot框架提供RESTful API服务,前后端通过JSON格式进行数据交互,实现界面与业务逻辑的解耦,提高系统的可维护性与扩展性。
后端技术栈方面,系统以SpringBoot为核心框架,利用其自动配置与起步依赖特性,简化项目搭建与开发流程。SpringBoot提供了内嵌的Servlet容器,使得应用可以独立运行,无需部署WAR包。持久层选用MyBatis作为ORM框架,通过XML或注解的方式编写SQL语句,实现灵活的数据库操作。MyBatis支持定制化SQL、存储过程以及高级映射,能够满足复杂业务场景下的数据访问需求。数据库采用MySQL关系型数据库,MySQL具有性能稳定、开源免费、支持海量数据存储等优势,适合作为中小型Web应用的数据存储方案。
前端采用Vue.js渐进式JavaScript框架进行开发。Vue采用MVVM设计模式,通过响应式的数据绑定与组件化开发机制,能够高效构建交互丰富的单页面应用。Vue Router负责前端路由管理,实现页面间的无刷新跳转;Vuex作为状态管理工具,统一维护用户登录状态、全局数据等共享信息,确保数据流向清晰可控。
在系统架构上,严格遵循MVC分层设计原则,将Controller控制层、Service业务层、Mapper持久层各司其职,代码结构清晰规范。通过合理的分层设计,系统具备良好的可测试性与可维护性,为后续功能扩展与迭代升级奠定了坚实基础。
三、功能介绍
1. 用户认证与个人中心
登录注册:用户登录、注册
个人中心:用户信息管理、个人资料维护
2. 美食资讯与社区
美食资讯:查看最新美食相关文章
论坛交流:用户发帖、回帖、讨论美食话题
3. 美食商城
美食分类:按分类浏览美食商品
商品展示:查看美食商品详情
购物车:添加商品、管理购物车
订单管理:确认订单、查看订单列表
地址管理:收货地址的增删改查
4. 社交功能
收藏功能:收藏喜欢的商品或内容
好友聊天:添加好友、实时聊天
客服系统:与客服沟通
5. 系统功能
操作日志:查看用户操作记录
友情链接:合作伙伴链接展示
二、后台管理系统
1. 系统管理
管理员管理:管理员账号的增删改查
操作日志:系统操作记录查看
轮播图配置:首页轮播图管理
2. 用户管理
用户管理:注册用户信息管理
管理员个人中心:修改密码、个人信息
3. 内容管理
美食资讯管理:资讯文章的发布与管理
论坛管理:用户发帖内容管理
评论管理:论坛和商城的评论审核
4. 商品管理
美食分类管理:商品分类的维护
美食商城管理:商品上下架、信息编辑
订单管理:订单处理、状态更新
5. 系统配置
友情链接管理:合作伙伴链接维护
客服聊天管理:客服对话监控
四、代码设计
// RecommendationService.java
package com.food.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.food.dto.RecommendRequestDTO;
import com.food.entity.Recommendation;
import com.food.entity.Restaurant;
import com.food.entity.UserBehavior;
import com.food.mapper.RecommendationMapper;
import com.food.mapper.RestaurantMapper;
import com.food.mapper.UserBehaviorMapper;
import com.food.vo.RestaurantVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
@Slf4j
@Service
@RequiredArgsConstructor
public class RecommendationService {
private final RestaurantMapper restaurantMapper;
private final UserBehaviorMapper userBehaviorMapper;
private final RecommendationMapper recommendationMapper;
// 行为类型权重
private static final Map<Integer, Double> BEHAVIOR_WEIGHT = new HashMap<>();
static {
BEHAVIOR_WEIGHT.put(1, 0.5); // 浏览
BEHAVIOR_WEIGHT.put(2, 2.0); // 收藏
BEHAVIOR_WEIGHT.put(3, 1.5); // 点赞
BEHAVIOR_WEIGHT.put(4, 3.0); // 评分
BEHAVIOR_WEIGHT.put(5, 1.2); // 分享
}
/**
* 混合推荐算法:协同过滤 + 基于内容推荐 + 热门推荐
*/
public List<RestaurantVO> getHybridRecommendations(RecommendRequestDTO request) {
Long userId = request.getUserId();
String city = request.getCity();
int limit = request.getPageSize();
// 1. 获取用户已有行为数据
List<Long> viewedRestaurants = userBehaviorMapper.selectUserViewedRestaurants(userId);
List<Long> favoriteRestaurants = userBehaviorMapper.selectUserFavoriteRestaurants(userId, 10);
Map<Long, Double> candidateScores = new HashMap<>();
// 2. 协同过滤推荐(基于用户相似度)
if (!favoriteRestaurants.isEmpty()) {
Map<Long, Double> cfScores = collaborativeFiltering(userId, favoriteRestaurants);
cfScores.forEach((id, score) ->
candidateScores.merge(id, score * 0.4, Double::sum));
}
// 3. 基于内容的推荐(基于用户偏好标签)
Map<Long, Double> contentScores = contentBasedRecommendation(userId, viewedRestaurants);
contentScores.forEach((id, score) ->
candidateScores.merge(id, score * 0.3, Double::sum));
// 4. 热门推荐(基于评分和评论数)
Map<Long, Double> hotScores = hotRecommendation(city);
hotScores.forEach((id, score) ->
candidateScores.merge(id, score * 0.2, Double::sum));
// 5. 地理位置推荐
Map<Long, Double> locationScores = locationBasedRecommendation(userId, city);
locationScores.forEach((id, score) ->
candidateScores.merge(id, score * 0.1, Double::sum));
// 6. 过滤已浏览过的餐厅
Set<Long> viewedSet = new HashSet<>(viewedRestaurants);
candidateScores.entrySet().removeIf(entry -> viewedSet.contains(entry.getKey()));
// 7. 排序并返回Top N
List<Long> recommendedIds = candidateScores.entrySet().stream()
.sorted(Map.Entry.<Long, Double>comparingByValue().reversed())
.limit(limit)
.map(Map.Entry::getKey)
.collect(Collectors.toList());
// 8. 保存推荐记录
saveRecommendations(userId, recommendedIds, candidateScores);
// 9. 获取餐厅详情并返回
return getRestaurantDetails(recommendedIds);
}
/**
* 协同过滤推荐:找到相似用户喜欢的餐厅
*/
private Map<Long, Double> collaborativeFiltering(Long userId, List<Long> favoriteRestaurants) {
Map<Long, Double> scores = new HashMap<>();
// 查询与该用户有相似喜好的其他用户
LambdaQueryWrapper<UserBehavior> wrapper = new LambdaQueryWrapper<>();
wrapper.in(UserBehavior::getRestaurantId, favoriteRestaurants)
.ne(UserBehavior::getUserId, userId)
.eq(UserBehavior::getBehaviorType, 2); // 收藏行为
List<UserBehavior> similarBehaviors = userBehaviorMapper.selectList(wrapper);
// 统计相似用户喜欢的餐厅
Map<Long, Integer> restaurantCount = new HashMap<>();
for (UserBehavior behavior : similarBehaviors) {
restaurantCount.merge(behavior.getRestaurantId(), 1, Integer::sum);
}
// 计算推荐分数
for (Map.Entry<Long, Integer> entry : restaurantCount.entrySet()) {
double score = entry.getValue() / (double) favoriteRestaurants.size();
scores.put(entry.getKey(), score);
}
return scores;
}
/**
* 基于内容的推荐:根据用户历史偏好的标签和菜系
*/
private Map<Long, Double> contentBasedRecommendation(Long userId, List<Long> viewedRestaurants) {
Map<Long, Double> scores = new HashMap<>();
if (viewedRestaurants.isEmpty()) {
return scores;
}
// 获取用户历史浏览的餐厅
List<Restaurant> viewed = restaurantMapper.selectBatchIds(viewedRestaurants);
// 统计用户偏好的菜系和标签
Map<String, Integer> categoryPreference = new HashMap<>();
Map<String, Integer> tagPreference = new HashMap<>();
for (Restaurant restaurant : viewed) {
categoryPreference.merge(restaurant.getCategory(), 1, Integer::sum);
if (restaurant.getTags() != null) {
String[] tags = restaurant.getTags().split(",");
for (String tag : tags) {
tagPreference.merge(tag.trim(), 1, Integer::sum);
}
}
}
// 获取所有餐厅进行匹配
List<Restaurant> allRestaurants = restaurantMapper.selectList(
new LambdaQueryWrapper<Restaurant>().eq(Restaurant::getStatus, 1)
);
for (Restaurant restaurant : allRestaurants) {
double score = 0.0;
// 菜系匹配度
Integer categoryCount = categoryPreference.getOrDefault(restaurant.getCategory(), 0);
score += categoryCount * 0.6;
// 标签匹配度
if (restaurant.getTags() != null) {
String[] tags = restaurant.getTags().split(",");
for (String tag : tags) {
Integer tagCount = tagPreference.getOrDefault(tag.trim(), 0);
score += tagCount * 0.4;
}
}
if (score > 0) {
scores.put(restaurant.getId(), score);
}
}
return scores;
}
/**
* 热门推荐:基于综合评分和评论数量
*/
private Map<Long, Double> hotRecommendation(String city) {
Map<Long, Double> scores = new HashMap<>();
LambdaQueryWrapper<Restaurant> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(Restaurant::getStatus, 1);
if (city != null && !city.isEmpty()) {
wrapper.eq(Restaurant::getCity, city);
}
wrapper.orderByDesc(Restaurant::getScore, Restaurant::getReviewCount);
wrapper.last("LIMIT 50");
List<Restaurant> hotRestaurants = restaurantMapper.selectList(wrapper);
for (Restaurant restaurant : hotRestaurants) {
double score = restaurant.getScore().doubleValue() * 0.6 +
Math.log(restaurant.getReviewCount() + 1) * 0.4;
scores.put(restaurant.getId(), score);
}
return scores;
}
/**
* 地理位置推荐
*/
private Map<Long, Double> locationBasedRecommendation(Long userId, String city) {
Map<Long, Double> scores = new HashMap<>();
// 获取用户所在城市的餐厅
LambdaQueryWrapper<Restaurant> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(Restaurant::getStatus, 1);
if (city != null && !city.isEmpty()) {
wrapper.eq(Restaurant::getCity, city);
}
wrapper.orderByDesc(Restaurant::getScore);
wrapper.last("LIMIT 30");
List<Restaurant> nearby = restaurantMapper.selectList(wrapper);
for (Restaurant restaurant : nearby) {
// 基于评分的简单地理位置推荐
double score = restaurant.getScore().doubleValue() / 5.0;
scores.put(restaurant.getId(), score);
}
return scores;
}
/**
* 保存推荐记录
*/
@Transactional
public void saveRecommendations(Long userId, List<Long> restaurantIds,
Map<Long, Double> scores) {
// 先删除旧的未点击推荐记录
LambdaQueryWrapper<Recommendation> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(Recommendation::getUserId, userId)
.eq(Recommendation::getIsClick, 0);
recommendationMapper.delete(wrapper);
// 保存新的推荐记录
for (Long restaurantId : restaurantIds) {
Recommendation recommendation = new Recommendation();
recommendation.setUserId(userId);
recommendation.setRestaurantId(restaurantId);
recommendation.setRecommendType(1); // 混合推荐
recommendation.setScore(BigDecimal.valueOf(scores.getOrDefault(restaurantId, 0.0))
.setScale(4, RoundingMode.HALF_UP));
recommendation.setIsClick(0);
recommendation.setCreateTime(LocalDateTime.now());
recommendationMapper.insert(recommendation);
}
}
/**
* 记录用户点击推荐
*/
@Transactional
public void recordClick(Long recommendationId) {
recommendationMapper.markAsClicked(recommendationId);
}
/**
* 获取餐厅详情(包含推荐菜品)
*/
private List<RestaurantVO> getRestaurantDetails(List<Long> restaurantIds) {
if (restaurantIds.isEmpty()) {
return new ArrayList<>();
}
List<Restaurant> restaurants = restaurantMapper.selectBatchIds(restaurantIds);
return restaurants.stream()
.map(this::convertToVO)
.sorted((a, b) -> b.getScore().compareTo(a.getScore()))
.collect(Collectors.toList());
}
private RestaurantVO convertToVO(Restaurant restaurant) {
RestaurantVO vo = new RestaurantVO();
vo.setId(restaurant.getId());
vo.setName(restaurant.getName());
vo.setCategory(restaurant.getCategory());
vo.setPriceLevel(restaurant.getPriceLevel());
vo.setAvgPrice(restaurant.getAvgPrice());
vo.setAddress(restaurant.getAddress());
vo.setCity(restaurant.getCity());
vo.setPhone(restaurant.getPhone());
vo.setBusinessHours(restaurant.getBusinessHours());
vo.setDescription(restaurant.getDescription());
vo.setImageUrl(restaurant.getImageUrl());
vo.setTags(restaurant.getTags());
vo.setScore(restaurant.getScore());
vo.setReviewCount(restaurant.getReviewCount());
// 设置价格等级描述
String[] levels = {"", "平价", "中等", "中高档", "高档"};
vo.setPriceLevelDesc(levels[restaurant.getPriceLevel()]);
return vo;
}
}
五、系统实现









更多推荐



所有评论(0)