目录

一、项目背景

二、技术介绍

三、功能介绍

四、代码设计

五、系统实现


一、项目背景

在数字化浪潮驱动下,智慧旅游已成为提升城市竞争力和游客满意度的关键抓手。目前主流OTA平台虽资源丰富,却普遍面临“信息噪音”干扰严重、交互方式刻板以及账号安全机制薄弱等共性问题。年轻一代用户不再满足于简单的价格排序,而是渴望获得“懂我”的即时反馈与沉浸式规划体验。

基于此,本项目开发了一款基于Springboot+Vue的轻量级旅游推荐管理系统。Springboot凭借其“开箱即用”的特性快速构建稳定后端,Vue则通过组件化开发渲染出高交互性的前端页面,二者结合构筑了流畅的系统基座。

针对推荐精度不足的行业通病,系统核心采用协同过滤算法,基于用户-项目评分矩阵进行相似度计算,主动过滤冗余信息,挖掘潜在兴趣点,实现从“人找服务”到“服务找人”的转变。同时,针对移动端场景下的账号安全痛点,系统采用人脸识别作为登录凭证,利用深度学习特征提取替代传统Session管理,在保障无感登录体验的同时杜绝了密码爆破风险。

为赋予系统更强的生命力和拓展性,本平台前瞻性地设计了可扩展AI大模型(LLM)交互模块。通过对接国内主流大模型API,系统能够理解用户模糊意图(如“适合亲子的雨天室内活动”),并生成富有逻辑的行程建议。这不仅是对推荐结果的有力补充,更是对“AI+文旅”落地场景的积极探索,旨在为游客打造一个集安全、精准与趣味于一体的新一代智能出行伴侣。

二、技术介绍

系统架构: B/S 架构,采用前后端分离设计模式
后端技术:
核心框架: Spring Boot
持久层框架: Mybatis/Mybatis-Plus
缓存数据库: Redis
数据库: MySQL 5.7 / 8.0
语言与环境: Java JDK 1.8, Maven IDEA (开发工具)
前端技术:
前端框架: Vue.js
开发模式: 前后端完全分离,通过API接口与后端交互。

本系统采用B/S架构前后端完全分离的设计模式,前端专注于视图渲染与交互逻辑,后端仅提供标准化API接口,有效降低系统耦合度,提升并行开发效率。

后端基于Spring Boot框架构建,利用其“开箱即用”的特性快速搭建核心业务层;MyBatis-Plus作为持久层框架,简化了数据访问操作,结合MySQL数据库实现高效的数据持久化存储;针对高频访问的热点数据(如推荐列表、用户缓存),引入Redis作为缓存中间件,显著降低数据库查询压力,保障系统在高并发场景下的响应速度。开发环境采用JDK 1.8Maven进行依赖管理,使用IDEA作为集成开发工具,确保项目构建的标准化与便捷性。

前端则采用Vue.js渐进式框架,通过组件化开发模式构建动态单页面应用(SPA)。前后端通过RESTful API进行数据交互,实现前后端逻辑的解耦与独立部署,为系统的迭代升级与横向扩展奠定了坚实的架构基础。

三、功能介绍

1. 用户认证模块
用户注册、登录与登出
忘记密码
2 .用户前台门户
首页: 轮播图广告、热门推荐展示(景点、酒店、线路)
信息浏览: 景点信息、旅游路线、景区酒店详情展示
核心交互: 景点预约、酒店预订、旅游资讯阅读
个性化功能:
我的收藏: 收藏感兴趣的景点、酒店、线路
我的预定: 查看和管理个人的所有预约订单
智能推荐 (核心亮点):
基于用户行为和偏好,利用协同过滤推荐算法,在首页或特定板块为用户生成个性化的景点、酒店、路线推荐列表。
人脸识别登录:用户可以使用密码以及录入人脸使用人脸登录,采用活体检测技术,图片是无法识别登录的。
3. 系统后台管理
·数据概览: 预约数据统计图表(如柱状图、饼图),直观掌握经营状况
内容管理:
景点信息管理(增删改查)
旅游路线管理
景区酒店管理
旅游资讯(文章)管理
轮播图管理
订单管理: 景点预约订单管理、酒店预订订单管理(审核、查看)

四、代码设计

package com.travel.service.impl;

import com.travel.entity.ScenicSpot;
import com.travel.mapper.UserBehaviorMapper;
import com.travel.util.SimilarityUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 推荐服务实现类
 * 采用基于用户的协同过滤算法(UserCF)
 */
@Service
public class RecommendServiceImpl {

    @Autowired
    private UserBehaviorMapper userBehaviorMapper;
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    private static final String RECOMMEND_CACHE_KEY = "recommend:user:";
    private static final int K_NEAREST_NEIGHBORS = 10;  // K近邻数
    private static final int TOP_N = 20;                // 推荐数量
    
    /**
     * 为用户生成个性化推荐列表
     * @param userId 用户ID
     * @return 推荐景点列表
     */
    public List<ScenicSpot> getRecommendations(Integer userId) {
        // 1. 先从缓存获取推荐结果
        String cacheKey = RECOMMEND_CACHE_KEY + userId;
        List<ScenicSpot> cachedResult = (List<ScenicSpot>) redisTemplate.opsForValue().get(cacheKey);
        if (cachedResult != null && !cachedResult.isEmpty()) {
            return cachedResult;
        }
        
        // 2. 缓存未命中,执行推荐算法
        List<ScenicSpot> recommendations = generateRecommendations(userId);
        
        // 3. 将结果存入缓存,有效期1小时
        redisTemplate.opsForValue().set(cacheKey, recommendations, 1, TimeUnit.HOURS);
        
        return recommendations;
    }
    
    /**
     * 异步预计算推荐结果(定时任务触发)
     */
    @Async
    public void precomputeRecommendations() {
        List<Integer> allUserIds = userBehaviorMapper.findAllUserIds();
        for (Integer userId : allUserIds) {
            generateRecommendations(userId);
        }
    }
    
    /**
     * 核心推荐算法
     */
    private List<ScenicSpot> generateRecommendations(Integer userId) {
        // 1. 获取所有用户的评分数据
        Map<Integer, Map<Integer, Double>> allUserRatings = userBehaviorMapper.getAllUserRatings();
        
        // 2. 获取当前用户的评分数据
        Map<Integer, Double> currentUserRatings = allUserRatings.get(userId);
        if (currentUserRatings == null || currentUserRatings.isEmpty()) {
            // 冷启动:返回热门景点
            return getHotSpotList();
        }
        
        // 3. 计算当前用户与其他所有用户的相似度
        Map<Integer, Double> similarities = new HashMap<>();
        for (Map.Entry<Integer, Map<Integer, Double>> entry : allUserRatings.entrySet()) {
            Integer otherUserId = entry.getKey();
            if (otherUserId.equals(userId)) {
                continue;
            }
            Map<Integer, Double> otherUserRatings = entry.getValue();
            double similarity = SimilarityUtil.pearsonCorrelation(currentUserRatings, otherUserRatings);
            if (similarity > 0) {
                similarities.put(otherUserId, similarity);
            }
        }
        
        // 4. 筛选出K个最近邻
        List<Map.Entry<Integer, Double>> sortedNeighbors = similarities.entrySet().stream()
                .sorted(Map.Entry.<Integer, Double>comparingByValue().reversed())
                .limit(K_NEAREST_NEIGHBORS)
                .collect(Collectors.toList());
        
        // 5. 预测当前用户对未评分物品的评分
        Map<Integer, Double> predictedRatings = new HashMap<>();
        Set<Integer> currentUserItems = currentUserRatings.keySet();
        
        // 获取所有景点ID
        Set<Integer> allItemIds = getAllItemIds();
        
        for (Integer itemId : allItemIds) {
            // 跳过已评分的景点
            if (currentUserItems.contains(itemId)) {
                continue;
            }
            
            double numerator = 0.0;
            double denominator = 0.0;
            
            for (Map.Entry<Integer, Double> neighbor : sortedNeighbors) {
                Integer neighborId = neighbor.getKey();
                double similarity = neighbor.getValue();
                
                Map<Integer, Double> neighborRatings = allUserRatings.get(neighborId);
                if (neighborRatings != null && neighborRatings.containsKey(itemId)) {
                    double neighborRating = neighborRatings.get(itemId);
                    numerator += similarity * neighborRating;
                    denominator += Math.abs(similarity);
                }
            }
            
            if (denominator != 0) {
                double predictedRating = numerator / denominator;
                predictedRatings.put(itemId, predictedRating);
            }
        }
        
        // 6. 按预测评分排序,取Top-N
        List<Integer> recommendedItemIds = predictedRatings.entrySet().stream()
                .sorted(Map.Entry.<Integer, Double>comparingByValue().reversed())
                .limit(TOP_N)
                .map(Map.Entry::getKey)
                .collect(Collectors.toList());
        
        // 7. 根据景点ID获取完整信息
        return userBehaviorMapper.getScenicSpotsByIds(recommendedItemIds);
    }
    
    /**
     * 获取热门景点列表(冷启动策略)
     */
    private List<ScenicSpot> getHotSpotList() {
        return userBehaviorMapper.getHotSpots(TOP_N);
    }
    
    /**
     * 获取所有景点ID
     */
    private Set<Integer> getAllItemIds() {
        return userBehaviorMapper.getAllItemIds();
    }
}

五、系统实现

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐