【Java】携程美食数据推荐系统 SpringBoot+Vue框架 计算机毕业设计项目 Idea+Navicat+MySQL安装 附源码+文档+讲解

54 阅读6分钟

前言

💖💖作者:计算机程序员小杨 💙💙个人简介:我是一名计算机相关专业的从业者,擅长Java、微信小程序、Python、Golang、安卓Android等多个IT方向。会做一些项目定制化开发、代码讲解、答辩教学、文档编写、也懂一些降重方面的技巧。热爱技术,喜欢钻研新工具和框架,也乐于通过代码解决实际问题,大家有技术代码这一块的问题可以问我! 💛💛想说的话:感谢大家的关注与支持! 💕💕文末获取源码联系 计算机程序员小杨 💜💜 网站实战项目 安卓/小程序实战项目 大数据实战项目 深度学习实战项目 计算机毕业设计选题 💜💜

一.开发工具简介

开发语言:Java+Python(两个版本都支持) 后端框架:Spring Boot(Spring+SpringMVC+Mybatis)+Django(两个版本都支持) 前端:Vue+ElementUI+HTML 数据库:MySQL 系统架构:B/S 开发工具:IDEA(Java的)或者PyCharm(Python的)

二.系统内容简介

本系统是一个基于Spring Boot和Vue技术架构开发的携程美食数据推荐系统,采用B/S模式实现美食信息的智能化推荐与社区化管理。系统运用MySQL数据库进行数据存储,通过前后端分离的开发方式,为美食爱好者和平台管理方提供全方位的美食发现与交流服务支持。平台核心功能涵盖用户账号的注册与个性化偏好管理、美食信息的多维度录入与展示、价格预测的算法模型应用与趋势分析、举报记录的违规内容审核与处理、论坛分类的主题化组织与标签管理、美食论坛的互动交流与内容发布以及系统管理的权限控制与数据维护。系统通过整合这些业务模块,构建起一个信息丰富、推荐精准、社区活跃的美食服务生态,特别是利用机器学习算法对历史美食价格数据进行训练,建立价格预测模型来预估餐厅未来的价格走势和优惠时段,帮助用户在合适的时机选择性价比更高的用餐方案,再结合美食论坛的用户评价、打卡分享、美食攻略等社区内容,以及智能推荐算法根据用户浏览历史、口味偏好、地理位置等因素进行个性化美食推荐,有效解决了传统美食平台信息过载、选择困难、价格不透明、缺乏互动交流等实际问题,为用户的美食探索和餐厅的数字化运营提供了实用的技术平台,推动美食服务向智能化、社交化方向发展。

三.系统功能演示

携程美食数据推荐系统

四.系统界面展示

在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述

五.系统源码展示


/**
 * 核心功能一: 美食智能推荐与个性化匹配
 * 处理用户画像构建、协同过滤推荐、内容推荐、热度排序等核心业务
 */
@PostMapping("/getPersonalizedRecommend")
public Result getPersonalizedRecommend(@RequestParam(required = false) Long userId,
                                        @RequestParam(required = false) String city,
                                        @RequestParam(required = false) String cuisine,
                                        @RequestParam(required = false) BigDecimal maxPrice,
                                        @RequestParam(defaultValue = "1") Integer page,
                                        @RequestParam(defaultValue = "10") Integer size) {
    
    List<FoodInfo> recommendList = new ArrayList<>();
    
    // 如果用户已登录,进行个性化推荐
    if (userId != null) {
        User user = userMapper.selectById(userId);
        if (user != null) {
            // 构建用户画像:分析浏览历史和收藏记录
            QueryWrapper<BrowseHistory> browseWrapper = new QueryWrapper<>();
            browseWrapper.eq("user_id", userId)
                         .orderByDesc("browse_time")
                         .last("LIMIT 30");
            
            List<BrowseHistory> browseList = browseHistoryMapper.selectList(browseWrapper);
            
            // 统计用户偏好的菜系类型
            Map<String, Integer> cuisinePreference = new HashMap<>();
            Map<String, Integer> tastePreference = new HashMap<>();
            BigDecimal avgPricePreference = BigDecimal.ZERO;
            int priceCount = 0;
            
            for (BrowseHistory history : browseList) {
                FoodInfo food = foodInfoMapper.selectById(history.getFoodId());
                if (food != null) {
                    // 统计菜系偏好
                    cuisinePreference.put(food.getCuisine(), 
                        cuisinePreference.getOrDefault(food.getCuisine(), 0) + 1);
                    
                    // 统计口味偏好
                    if (food.getTaste() != null) {
                        String[] tastes = food.getTaste().split(",");
                        for (String taste : tastes) {
                            tastePreference.put(taste.trim(), 
                                tastePreference.getOrDefault(taste.trim(), 0) + 1);
                        }
                    }
                    
                    // 计算平均价格偏好
                    if (food.getAvgPrice() != null) {
                        avgPricePreference = avgPricePreference.add(food.getAvgPrice());
                        priceCount++;
                    }
                }
            }
            
            // 找出最偏好的菜系和口味
            String preferredCuisine = cuisinePreference.entrySet().stream()
                .max(Map.Entry.comparingByValue())
                .map(Map.Entry::getKey)
                .orElse(null);
            
            String preferredTaste = tastePreference.entrySet().stream()
                .max(Map.Entry.comparingByValue())
                .map(Map.Entry::getKey)
                .orElse(null);
            
            BigDecimal avgPrice = priceCount > 0 ? 
                avgPricePreference.divide(BigDecimal.valueOf(priceCount), 2, RoundingMode.HALF_UP) : 
                BigDecimal.valueOf(100);
            
            // 构建基于内容的推荐查询
            QueryWrapper<FoodInfo> contentWrapper = new QueryWrapper<>();
            contentWrapper.eq("status", 1);
            
            // 优先推荐偏好菜系
            if (cuisine != null) {
                contentWrapper.eq("cuisine", cuisine);
            } else if (preferredCuisine != null) {
                contentWrapper.eq("cuisine", preferredCuisine);
            }
            
            // 地理位置筛选
            if (city != null) {
                contentWrapper.eq("city", city);
            }
            
            // 价格区间筛选(用户偏好价格的±30%)
            BigDecimal minPrice = avgPrice.multiply(BigDecimal.valueOf(0.7));
            BigDecimal maxPriceLimit = maxPrice != null ? maxPrice : avgPrice.multiply(BigDecimal.valueOf(1.3));
            contentWrapper.between("avg_price", minPrice, maxPriceLimit);
            
            // 口味匹配
            if (preferredTaste != null) {
                contentWrapper.like("taste", preferredTaste);
            }
            
            // 排除已浏览过的美食
            List<Long> browsedFoodIds = browseList.stream()
                .map(BrowseHistory::getFoodId)
                .collect(Collectors.toList());
            
            if (!browsedFoodIds.isEmpty()) {
                contentWrapper.notIn("id", browsedFoodIds);
            }
            
            // 综合评分排序(评分*0.5 + 评论数*0.0001 + 收藏数*0.0002)
            contentWrapper.orderByDesc("(rating * 0.5 + review_count * 0.0001 + collect_count * 0.0002)");
            
            Page<FoodInfo> foodPage = new Page<>(page, size);
            Page<FoodInfo> result = foodInfoMapper.selectPage(foodPage, contentWrapper);
            recommendList = result.getRecords();
            
            // 协同过滤补充推荐
            if (recommendList.size() < size) {
                List<FoodInfo> collaborativeList = findCollaborativeRecommend(
                    userId, browsedFoodIds, size - recommendList.size());
                recommendList.addAll(collaborativeList);
            }
            
            // 计算推荐理由
            for (FoodInfo food : recommendList) {
                String reason = generateRecommendReason(food, preferredCuisine, preferredTaste, avgPrice);
                food.setRecommendReason(reason);
            }
            
        }
    } else {
        // 未登录用户返回热门美食
        QueryWrapper<FoodInfo> hotWrapper = new QueryWrapper<>();
        hotWrapper.eq("status", 1);
        
        if (cuisine != null) {
            hotWrapper.eq("cuisine", cuisine);
        }
        
        if (city != null) {
            hotWrapper.eq("city", city);
        }
        
        if (maxPrice != null) {
            hotWrapper.le("avg_price", maxPrice);
        }
        
        // 按热度排序(浏览量 + 收藏量*2 + 评论数*1.5)
        hotWrapper.orderByDesc("(view_count + collect_count * 2 + review_count * 1.5)");
        
        Page<FoodInfo> foodPage = new Page<>(page, size);
        Page<FoodInfo> result = foodInfoMapper.selectPage(foodPage, hotWrapper);
        recommendList = result.getRecords();
    }
    
    // 添加用户收藏状态标识
    if (userId != null) {
        for (FoodInfo food : recommendList) {
            QueryWrapper<UserCollect> collectWrapper = new QueryWrapper<>();
            collectWrapper.eq("user_id", userId)
                         .eq("food_id", food.getId());
            food.setIsCollected(userCollectMapper.selectCount(collectWrapper) > 0);
        }
    }
    
    return Result.success("推荐美食查询成功", recommendList);
}

/**
 * 核心功能二: 美食价格预测与趋势分析
 * 处理历史价格数据分析、时间序列预测、优惠时段识别、价格波动计算等业务
 */
@PostMapping("/predictFoodPrice")
@Transactional(rollbackFor = Exception.class)
public Result predictFoodPrice(@RequestParam Long foodId, @RequestParam Integer days) {
    // 验证美食信息
    FoodInfo food = foodInfoMapper.selectById(foodId);
    if (food == null) {
        return Result.error("美食信息不存在");
    }
    
    // 查询历史价格数据(最近180天)
    QueryWrapper<PriceHistory> historyWrapper = new QueryWrapper<>();
    historyWrapper.eq("food_id", foodId)
                  .ge("record_date", DateUtil.getDateBefore(180))
                  .orderByAsc("record_date");
    
    List<PriceHistory> historyList = priceHistoryMapper.selectList(historyWrapper);
    
    if (historyList.size() < 30) {
        return Result.error("历史价格数据不足,至少需要30天数据才能进行预测");
    }
    
    // 提取价格序列数据
    List<BigDecimal> priceSequence = historyList.stream()
        .map(PriceHistory::getPrice)
        .collect(Collectors.toList());
    
    // 计算价格统计特征
    BigDecimal avgPrice = priceSequence.stream()
        .reduce(BigDecimal.ZERO, BigDecimal::add)
        .divide(BigDecimal.valueOf(priceSequence.size()), 2, RoundingMode.HALF_UP);
    
    BigDecimal maxPrice = priceSequence.stream()
        .max(BigDecimal::compareTo)
        .orElse(BigDecimal.ZERO);
    
    BigDecimal minPrice = priceSequence.stream()
        .min(BigDecimal::compareTo)
        .orElse(BigDecimal.ZERO);
    
    // 计算价格波动率
    BigDecimal variance = BigDecimal.ZERO;
    for (BigDecimal price : priceSequence) {
        BigDecimal diff = price.subtract(avgPrice);
        variance = variance.add(diff.multiply(diff));
    }
    variance = variance.divide(BigDecimal.valueOf(priceSequence.size()), 4, RoundingMode.HALF_UP);
    double volatility = Math.sqrt(variance.doubleValue());
    
    // 使用移动平均法进行价格预测
    int windowSize = 7; // 7天移动平均窗口
    List<Map<String, Object>> predictions = new ArrayList<>();
    
    for (int i = 1; i <= days; i++) {
        Date predictDate = DateUtil.getDateAfter(i);
        
        // 计算移动平均价格
        int startIdx = Math.max(0, priceSequence.size() - windowSize);
        List<BigDecimal> window = priceSequence.subList(startIdx, priceSequence.size());
        
        BigDecimal movingAvg = window.stream()
            .reduce(BigDecimal.ZERO, BigDecimal::add)
            .divide(BigDecimal.valueOf(window.size()), 2, RoundingMode.HALF_UP);
        
        // 考虑周期性因素(周末价格通常更高)
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(predictDate);
        int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK);
        
        BigDecimal weekendFactor = BigDecimal.ONE;
        if (dayOfWeek == Calendar.SATURDAY || dayOfWeek == Calendar.SUNDAY) {
            weekendFactor = BigDecimal.valueOf(1.1); // 周末价格上浮10%
        }
        
        // 考虑节假日因素
        boolean isHoliday = checkHoliday(predictDate);
        BigDecimal holidayFactor = isHoliday ? BigDecimal.valueOf(1.15) : BigDecimal.ONE;
        
        // 计算预测价格
        BigDecimal predictedPrice = movingAvg
            .multiply(weekendFactor)
            .multiply(holidayFactor)
            .setScale(2, RoundingMode.HALF_UP);
        
        // 计算置信区间
        BigDecimal upperBound = predictedPrice.add(BigDecimal.valueOf(volatility * 1.96));
        BigDecimal lowerBound = predictedPrice.subtract(BigDecimal.valueOf(volatility * 1.96));
        
        // 判断是否为优惠时段
        boolean isDiscountPeriod = predictedPrice.compareTo(avgPrice.multiply(BigDecimal.valueOf(0.9))) < 0;
        
        Map<String, Object> prediction = new HashMap<>();
        prediction.put("date", DateUtil.format(predictDate, "yyyy-MM-dd"));
        prediction.put("predictedPrice", predictedPrice);
        prediction.put("upperBound", upperBound);
        prediction.put("lowerBound", lowerBound);
        prediction.put("isDiscountPeriod", isDiscountPeriod);
        prediction.put("dayOfWeek", getDayOfWeekName(dayOfWeek));
        prediction.put("isHoliday", isHoliday);
        
        predictions.add(prediction);
        
        // 将预测价格加入序列用于下一次预测
        priceSequence.add(predictedPrice);
    }
    
    // 保存价格预测记录
    PricePrediction pricePrediction = new PricePrediction();
    pricePrediction.setFoodId(foodId);
    pricePrediction.setPredictDays(days);
    pricePrediction.setAvgPrice(avgPrice);
    pricePrediction.setMaxPrice(maxPrice);
    pricePrediction.setMinPrice(minPrice);
    pricePrediction.setVolatility(BigDecimal.valueOf(volatility));
    pricePrediction.setPredictionData(JSON.toJSONString(predictions));
    pricePrediction.setCreateTime(new Date());
    pricePredictionMapper.insert(pricePrediction);
    
    // 分析最佳用餐时机
    List<String> bestDates = predictions.stream()
        .filter(p -> (Boolean) p.get("isDiscountPeriod"))
        .map(p -> (String) p.get("date"))
        .limit(3)
        .collect(Collectors.toList());
    
    Map<String, Object> resultData = new HashMap<>();
    resultData.put("foodName", food.getName());
    resultData.put("currentPrice", food.getAvgPrice());
    resultData.put("avgHistoryPrice", avgPrice);
    resultData.put("priceRange", minPrice + " - " + maxPrice);
    resultData.put("volatility", BigDecimal.valueOf(volatility).setScale(2, RoundingMode.HALF_UP));
    resultData.put("predictions", predictions);
    resultData.put("bestDates", bestDates);
    resultData.put("recommendation", generatePriceRecommendation(predictions, avgPrice));
    
    return Result.success("价格预测完成", resultData);
}


六.系统文档展示

在这里插入图片描述

结束

💕💕文末获取源码联系 计算机程序员小杨