个性化旅游攻略系统毕业设计指南:从Spring Boot后端到Vue前端的全栈实现
2026/8/23 4:04:34 网站建设 项目流程

大家好,我是专注于分享实战开发经验的博主。毕业设计是计算机专业学生综合能力的集中体现,选择一个既有技术深度又贴合实际应用场景的课题至关重要。今天,我将围绕“个性化旅游攻略定制系统”这个选题,为大家提供一份从选题分析、技术选型到核心模块实现的完整毕业设计指南。无论你是Java、Python、PHP、C#还是Node.js的开发者,都能从中找到适合自己技术栈的实现路径。本文将详细拆解系统需求、架构设计、核心功能实现以及部署上线的全流程,并提供可复用的代码片段和避坑指南,帮助你高效、高质量地完成毕业设计。

1. 项目背景与核心概念

1.1 为什么选择“个性化旅游攻略定制系统”?

在信息爆炸的时代,传统的“一刀切”式旅游攻略已无法满足用户日益增长的个性化需求。用户希望获得基于自身兴趣(如美食、摄影、历史、亲子)、预算、出行时间、同行人员等维度定制的专属旅行方案。这背后涉及用户画像分析、内容推荐算法、数据聚合与可视化等多项核心技术,是一个典型的“数据+算法+应用”的综合型项目。

选择此课题作为毕业设计,具有以下优势:

  1. 贴合热点:智慧旅游、个性化推荐是当前互联网和人工智能领域的热门应用方向。
  2. 技术综合性强:项目可以覆盖Web前后端开发、数据库设计、推荐算法、第三方API集成、移动端开发等多个技术栈,充分展示你的综合能力。
  3. 业务场景清晰:需求明确,功能模块易于划分,便于进行系统设计与实现。
  4. 扩展空间大:基础功能完成后,可以引入更复杂的算法(如协同过滤、深度学习)、集成更多数据源(如实时天气、交通)、或开发多端应用(小程序、APP),为论文的“创新点”和“未来展望”部分提供丰富素材。

1.2 系统核心功能模块拆解

一个完整的个性化旅游攻略定制系统通常包含以下核心模块:

  • 用户中心:注册、登录、个人资料管理、旅行偏好设置(标签化兴趣点)。
  • 目的地与景点管理:目的地城市信息、景点/餐厅/酒店等POI(兴趣点)数据的增删改查,包含详情、图片、标签、评分、价格等属性。
  • 攻略生成引擎:核心模块。根据用户输入的约束条件(时间、预算、兴趣标签)和用户历史行为,通过规则或算法,从POI库中筛选、排序、组合成合理的每日行程。
  • 攻略展示与交互:以日历或时间轴形式可视化展示生成的攻略,支持用户手动调整(增删行程、调整顺序)、收藏、分享。
  • 数据管理后台:供管理员使用的后台,用于管理POI数据、用户、生成的攻略等。

2. 技术选型与环境准备

针对标题中提到的多种语言,我们分别给出主流且适合毕业设计的技术栈建议。本文将以Java Spring Boot作为主要示例进行深入讲解,因为其在企业级应用开发中占比高,生态完善,资料丰富,非常适合作为毕业设计的“压舱石”技术。

2.1 各语言技术栈推荐

技术方向推荐技术栈说明
JavaSpring Boot + MyBatis-Plus / JPA + MySQL + Maven/Gradle企业级标准方案,结构清晰,文档丰富。可集成Spring Security做安全控制,Thymeleaf或前后端分离。
PythonDjango / Flask + SQLAlchemy + MySQL/PostgreSQL开发效率高,适合快速原型。Django自带强大后台,Flask更灵活。便于实现推荐算法(可用Surprise、scikit-learn库)。
PHPLaravel / ThinkPHP + MySQL传统Web开发利器,部署简单。Laravel框架优雅,ThinkPHP中文文档友好。
C#ASP.NET Core + Entity Framework Core + SQL Server微软技术栈,性能优秀,IDE(Visual Studio)强大,适合Windows环境开发。
Node.jsExpress / Koa + Sequelize / TypeORM + MySQL全栈JavaScript,适合熟悉前端的学生。异步特性好,生态活跃。
小程序微信小程序原生框架 / Uni-app + 任意后端前端展示层。需搭配上述任一后端提供API接口。
APPFlutter / React Native + 任意后端跨端移动应用。Flutter性能好,React Native生态成熟。

2.2 Spring Boot 开发环境准备(示例)

我们将以Spring Boot为例,演示后端核心功能的搭建。

环境要求:

  • 操作系统:Windows 10/11, macOS, Linux (Ubuntu/CentOS)
  • JDK:版本 8、11 或 17(推荐 11 或 17,LTS版本)
  • IDE:IntelliJ IDEA (推荐) 或 Eclipse with STS
  • 构建工具:Maven 3.6+ 或 Gradle
  • 数据库:MySQL 5.7+ 或 8.0
  • 其他:Git, Postman (用于API测试)

初始化项目:使用 Spring Initializr 或 IDE 内置工具创建项目。

依赖选择 (pom.xml 关键部分):

<dependencies> <!-- Web 支持 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- 数据访问 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <!-- MySQL 驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <!-- 简化实体类开发 --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!-- 参数校验 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> <!-- 单元测试 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>

数据库配置 (application.yml):

spring: datasource: url: jdbc:mysql://localhost:3306/travel_guide_db?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai username: root password: your_password driver-class-name: com.mysql.cj.jdbc.Driver jpa: hibernate: ddl-auto: update # 开发环境可用update,生产环境应为validate或none show-sql: true properties: hibernate: dialect: org.hibernate.dialect.MySQL8Dialect

3. 数据库设计与核心实体建模

系统的核心是数据。良好的数据库设计是项目成功的基础。

3.1 核心实体关系分析

主要实体包括:User(用户),Destination(目的地),POI(兴趣点,如景点、餐厅、酒店),Tag(标签),UserPreference(用户偏好),Itinerary(行程/攻略),ItineraryItem(行程项)。

关系简述:

  • 一个User可以有多个UserPreference(偏好标签)。
  • 一个Destination包含多个POI
  • 一个POI可以拥有多个Tag(如“历史”、“美食”、“亲子”)。
  • 一个User可以创建多个Itinerary
  • 一个Itinerary包含多个ItineraryItem,每个Item关联一个POI,并有序号、日期、时间段等属性。

3.2 关键表结构SQL示例(MySQL)

-- 用户表 CREATE TABLE `user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL COMMENT '用户名', `password` varchar(255) NOT NULL COMMENT '加密后的密码', `email` varchar(100) DEFAULT NULL, `avatar` varchar(500) DEFAULT NULL COMMENT '头像URL', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_username` (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表'; -- 标签表 CREATE TABLE `tag` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(20) NOT NULL COMMENT '标签名,如美食、历史', `type` varchar(20) DEFAULT NULL COMMENT '标签类型', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='兴趣标签表'; -- 用户偏好表 (用户与标签的多对多关系) CREATE TABLE `user_preference` ( `user_id` bigint(20) NOT NULL, `tag_id` int(11) NOT NULL, `weight` float DEFAULT 1.0 COMMENT '偏好权重,可用于推荐算法', PRIMARY KEY (`user_id`,`tag_id`), KEY `fk_preference_tag` (`tag_id`), CONSTRAINT `fk_preference_tag` FOREIGN KEY (`tag_id`) REFERENCES `tag` (`id`), CONSTRAINT `fk_preference_user` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户偏好表'; -- POI兴趣点表 CREATE TABLE `poi` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `name` varchar(100) NOT NULL COMMENT '名称', `destination_id` bigint(20) NOT NULL COMMENT '所属目的地', `type` varchar(20) NOT NULL COMMENT '类型: ATTRACTION, RESTAURANT, HOTEL', `description` text COMMENT '描述', `address` varchar(200) DEFAULT NULL, `latitude` decimal(10,8) DEFAULT NULL COMMENT '纬度', `longitude` decimal(11,8) DEFAULT NULL COMMENT '经度', `avg_cost` decimal(10,2) DEFAULT NULL COMMENT '人均消费', `suggested_duration` int(11) DEFAULT NULL COMMENT '建议游玩时长(分钟)', `opening_hours` varchar(200) DEFAULT NULL COMMENT '开放时间', `rating` decimal(3,2) DEFAULT NULL COMMENT '评分', PRIMARY KEY (`id`), KEY `fk_poi_destination` (`destination_id`), CONSTRAINT `fk_poi_destination` FOREIGN KEY (`destination_id`) REFERENCES `destination` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='兴趣点表'; -- POI与标签关联表 CREATE TABLE `poi_tag` ( `poi_id` bigint(20) NOT NULL, `tag_id` int(11) NOT NULL, PRIMARY KEY (`poi_id`,`tag_id`), KEY `fk_poitag_tag` (`tag_id`), CONSTRAINT `fk_poitag_poi` FOREIGN KEY (`poi_id`) REFERENCES `poi` (`id`), CONSTRAINT `fk_poitag_tag` FOREIGN KEY (`tag_id`) REFERENCES `tag` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='POI标签关联表'; -- 行程表 CREATE TABLE `itinerary` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `user_id` bigint(20) NOT NULL, `title` varchar(100) NOT NULL COMMENT '行程标题', `destination_id` bigint(20) NOT NULL, `start_date` date NOT NULL, `end_date` date NOT NULL, `total_budget` decimal(10,2) DEFAULT NULL, `status` varchar(20) DEFAULT 'DRAFT' COMMENT '状态: DRAFT, FINAL', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `fk_itinerary_user` (`user_id`), KEY `fk_itinerary_destination` (`destination_id`), CONSTRAINT `fk_itinerary_destination` FOREIGN KEY (`destination_id`) REFERENCES `destination` (`id`), CONSTRAINT `fk_itinerary_user` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='行程主表'; -- 行程项表 CREATE TABLE `itinerary_item` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `itinerary_id` bigint(20) NOT NULL, `poi_id` bigint(20) NOT NULL, `day_number` int(11) NOT NULL COMMENT '第几天', `start_time` time DEFAULT NULL COMMENT '开始时间', `end_time` time DEFAULT NULL COMMENT '结束时间', `order_in_day` int(11) NOT NULL COMMENT '当天的顺序', `notes` varchar(500) DEFAULT NULL COMMENT '用户备注', PRIMARY KEY (`id`), KEY `fk_item_itinerary` (`itinerary_id`), KEY `fk_item_poi` (`poi_id`), CONSTRAINT `fk_item_itinerary` FOREIGN KEY (`itinerary_id`) REFERENCES `itinerary` (`id`), CONSTRAINT `fk_item_poi` FOREIGN KEY (`poi_id`) REFERENCES `poi` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='行程项表';

4. Spring Boot 后端核心功能实现

我们聚焦于最核心的“攻略生成引擎”和“行程管理”模块。

4.1 实体类与Repository层

首先,根据数据库表创建JPA实体类。

POI实体类示例:

// 文件路径:src/main/java/com/example/travelguide/entity/Poi.java package com.example.travelguide.entity; import lombok.Data; import javax.persistence.*; import java.math.BigDecimal; import java.util.HashSet; import java.util.Set; @Entity @Table(name = "poi") @Data public class Poi { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private String type; // ATTRACTION, RESTAURANT, HOTEL private String description; private String address; private BigDecimal latitude; private BigDecimal longitude; private BigDecimal avgCost; private Integer suggestedDuration; // 分钟 private String openingHours; private BigDecimal rating; @ManyToOne @JoinColumn(name = "destination_id") private Destination destination; // 多对多关联标签 @ManyToMany @JoinTable( name = "poi_tag", joinColumns = @JoinColumn(name = "poi_id"), inverseJoinColumns = @JoinColumn(name = "tag_id") ) private Set<Tag> tags = new HashSet<>(); }

创建对应的Repository接口:

// 文件路径:src/main/java/com/example/travelguide/repository/PoiRepository.java package com.example.travelguide.repository; import com.example.travelguide.entity.Poi; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.stereotype.Repository; import java.util.List; @Repository public interface PoiRepository extends JpaRepository<Poi, Long>, JpaSpecificationExecutor<Poi> { // 根据目的地和标签查找POI List<Poi> findByDestinationIdAndTags_Id(Long destinationId, Integer tagId); // 根据类型查找 List<Poi> findByDestinationIdAndType(Long destinationId, String type); }

4.2 攻略生成服务(核心业务逻辑)

这是系统的“大脑”。我们实现一个基于规则的简单推荐引擎。更复杂的算法(如协同过滤)可以作为升级点。

服务接口与实现:

// 文件路径:src/main/java/com/example/travelguide/service/ItineraryGenerationService.java package com.example.travelguide.service; import com.example.travelguide.entity.*; import com.example.travelguide.repository.PoiRepository; import com.example.travelguide.repository.TagRepository; import com.example.travelguide.repository.UserPreferenceRepository; import com.example.travelguide.vo.ItineraryGenerationRequest; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import javax.transaction.Transactional; import java.time.LocalDate; import java.time.LocalTime; import java.util.*; import java.util.stream.Collectors; @Service @Slf4j public class ItineraryGenerationService { @Autowired private PoiRepository poiRepository; @Autowired private UserPreferenceRepository userPreferenceRepository; @Autowired private TagRepository tagRepository; /** * 生成行程的核心方法 * @param request 生成请求,包含目的地、日期、预算、用户ID等 * @return 生成的行程实体(尚未保存) */ @Transactional public Itinerary generateItinerary(ItineraryGenerationRequest request) { // 1. 创建行程主对象 Itinerary itinerary = new Itinerary(); itinerary.setUser(new User(request.getUserId())); itinerary.setDestination(new Destination(request.getDestinationId())); itinerary.setTitle(request.getTitle()); itinerary.setStartDate(request.getStartDate()); itinerary.setEndDate(request.getEndDate()); itinerary.setTotalBudget(request.getTotalBudget()); itinerary.setStatus("DRAFT"); List<ItineraryItem> items = new ArrayList<>(); LocalDate currentDate = request.getStartDate(); int dayNumber = 1; // 2. 获取用户偏好标签 List<UserPreference> preferences = userPreferenceRepository.findByUserId(request.getUserId()); List<Integer> preferredTagIds = preferences.stream() .sorted(Comparator.comparing(UserPreference::getWeight).reversed()) // 按权重降序 .map(up -> up.getTag().getId()) .limit(3) // 取前3个偏好标签 .collect(Collectors.toList()); // 3. 按天生成行程 while (!currentDate.isAfter(request.getEndDate())) { // 3.1 获取该目的地所有POI(可根据偏好过滤) List<Poi> candidatePois = new ArrayList<>(); if (!preferredTagIds.isEmpty()) { for (Integer tagId : preferredTagIds) { candidatePois.addAll(poiRepository.findByDestinationIdAndTags_Id(request.getDestinationId(), tagId)); } } else { // 如果没有偏好,获取所有景点类型POI candidatePois.addAll(poiRepository.findByDestinationIdAndType(request.getDestinationId(), "ATTRACTION")); } // 去重 candidatePois = candidatePois.stream().distinct().collect(Collectors.toList()); // 3.2 简单的规则:每天安排2-4个POI,考虑建议时长和开放时间 LocalTime dayStart = LocalTime.of(9, 0); // 假设每天9点开始 int order = 1; for (Poi poi : candidatePois) { if (order > 4) break; // 每天最多4个 ItineraryItem item = new ItineraryItem(); item.setItinerary(itinerary); item.setPoi(poi); item.setDayNumber(dayNumber); item.setStartTime(dayStart); // 结束时间 = 开始时间 + 建议时长 if (poi.getSuggestedDuration() != null) { item.setEndTime(dayStart.plusMinutes(poi.getSuggestedDuration())); dayStart = dayStart.plusMinutes(poi.getSuggestedDuration() + 30); // 加30分钟交通/缓冲 } else { item.setEndTime(dayStart.plusHours(2)); dayStart = dayStart.plusHours(2).plusMinutes(30); } item.setOrderInDay(order); items.add(item); order++; } currentDate = currentDate.plusDays(1); dayNumber++; } itinerary.setItems(items); // 4. 简单预算校验(示例) double estimatedCost = items.stream() .mapToDouble(item -> item.getPoi().getAvgCost() != null ? item.getPoi().getAvgCost().doubleValue() : 0) .sum(); log.info("行程预估费用:{}, 用户预算:{}", estimatedCost, request.getTotalBudget()); // 可以在这里添加预算超支的提示或调整逻辑 return itinerary; } } // 请求参数VO // 文件路径:src/main/java/com/example/travelguide/vo/ItineraryGenerationRequest.java package com.example.travelguide.vo; import lombok.Data; import java.math.BigDecimal; import java.time.LocalDate; @Data public class ItineraryGenerationRequest { private Long userId; private Long destinationId; private String title; private LocalDate startDate; private LocalDate endDate; private BigDecimal totalBudget; // 可以扩展更多参数,如同行人数、特殊需求等 }

4.3 控制器层(REST API)

提供生成攻略和获取攻略的API接口。

// 文件路径:src/main/java/com/example/travelguide/controller/ItineraryController.java package com.example.travelguide.controller; import com.example.travelguide.entity.Itinerary; import com.example.travelguide.service.ItineraryGenerationService; import com.example.travelguide.service.ItineraryService; import com.example.travelguide.vo.ItineraryGenerationRequest; import com.example.travelguide.vo.ApiResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import javax.validation.Valid; @RestController @RequestMapping("/api/itinerary") public class ItineraryController { @Autowired private ItineraryGenerationService generationService; @Autowired private ItineraryService itineraryService; // 负责CRUD的服务 @PostMapping("/generate") public ApiResponse<Itinerary> generate(@Valid @RequestBody ItineraryGenerationRequest request) { try { Itinerary itinerary = generationService.generateItinerary(request); // 通常生成后需要保存到数据库 Itinerary savedItinerary = itineraryService.saveDraft(itinerary); return ApiResponse.success(savedItinerary, "行程生成成功"); } catch (Exception e) { return ApiResponse.error(500, "行程生成失败: " + e.getMessage()); } } @GetMapping("/{id}") public ApiResponse<Itinerary> getDetail(@PathVariable Long id) { Itinerary itinerary = itineraryService.findById(id); if (itinerary == null) { return ApiResponse.error(404, "行程不存在"); } return ApiResponse.success(itinerary); } @PutMapping("/{id}/adjust") public ApiResponse<Itinerary> adjustItem(@PathVariable Long id, @RequestBody AdjustRequest adjustRequest) { // 实现用户手动调整行程项的逻辑 // 例如,交换两个item的顺序,或替换某个item return ApiResponse.success(null, "调整成功"); } }

5. 前端展示与交互(Vue.js + Element UI 示例)

后端提供API后,前端负责展示和交互。这里给出一个基于Vue 3和Element Plus的简单示例。

5.1 攻略生成表单组件

<!-- 文件路径:src/components/ItineraryGenerator.vue --> <template> <el-card class="generator-card"> <template #header> <span>生成我的个性化攻略</span> </template> <el-form :model="form" label-width="100px" @submit.prevent="handleGenerate"> <el-form-item label="目的地"> <el-select v-model="form.destinationId" placeholder="请选择目的地" filterable> <el-option v-for="dest in destinationList" :key="dest.id" :label="dest.name" :value="dest.id" /> </el-select> </el-form-item> <el-form-item label="行程标题"> <el-input v-model="form.title" placeholder="例如:我的北京五日游" /> </el-form-item> <el-form-item label="出行日期"> <el-date-picker v-model="dateRange" type="daterange" range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期" @change="handleDateChange" /> </el-form-item> <el-form-item label="总预算(元)"> <el-input-number v-model="form.totalBudget" :min="0" :step="100" /> </el-form-item> <el-form-item> <el-button type="primary" :loading="generating" @click="handleGenerate">一键生成攻略</el-button> </el-form-item> </el-form> </el-card> </template> <script setup> import { ref, reactive, onMounted } from 'vue' import { ElMessage } from 'element-plus' import axios from '@/utils/axios' const generating = ref(false) const destinationList = ref([]) const dateRange = ref([]) const form = reactive({ userId: 1, // 实际应从登录状态获取 destinationId: null, title: '', startDate: null, endDate: null, totalBudget: 3000 }) const handleDateChange = (val) => { if (val && val.length === 2) { form.startDate = val[0] form.endDate = val[1] } } const loadDestinations = async () => { try { const res = await axios.get('/api/destinations') destinationList.value = res.data.data } catch (error) { ElMessage.error('加载目的地失败') } } const handleGenerate = async () => { if (!form.destinationId || !form.startDate) { ElMessage.warning('请填写完整信息') return } generating.value = true try { const res = await axios.post('/api/itinerary/generate', form) ElMessage.success('攻略生成成功!') // 跳转到攻略详情页,或更新父组件数据 emit('generated', res.data.data) } catch (error) { ElMessage.error('生成失败:' + error.response?.data?.message || error.message) } finally { generating.value = false } } onMounted(() => { loadDestinations() }) </script>

5.2 攻略详情与可视化展示组件

<!-- 文件路径:src/components/ItineraryDetail.vue --> <template> <div class="itinerary-detail"> <h2>{{ itinerary.title }}</h2> <el-timeline> <el-timeline-item v-for="day in groupedItems" :key="day.dayNumber" :timestamp="`第${day.dayNumber}天`" placement="top" > <el-card v-for="item in day.items" :key="item.id" class="day-item"> <div class="item-header"> <span class="time">{{ item.startTime }} - {{ item.endTime }}</span> <el-tag size="small">{{ item.poi.type }}</el-tag> </div> <h4>{{ item.poi.name }}</h4> <p>{{ item.poi.description }}</p> <div class="item-footer"> <span><i class="el-icon-location"></i> {{ item.poi.address }}</span> <span><i class="el-icon-coin"></i> 人均¥{{ item.poi.avgCost }}</span> <el-button size="small" @click="handleReplace(item)">换一个</el-button> </div> </el-card> </el-timeline-item> </el-timeline> </div> </template> <script setup> import { computed } from 'vue' import { groupBy } from 'lodash-es' const props = defineProps({ itinerary: { type: Object, required: true } }) // 按天分组行程项 const groupedItems = computed(() => { const groups = groupBy(props.itinerary.items, 'dayNumber') return Object.keys(groups).map(dayNum => ({ dayNumber: parseInt(dayNum), items: groups[dayNum].sort((a, b) => a.orderInDay - b.orderInDay) })).sort((a, b) => a.dayNumber - b.dayNumber) }) const handleReplace = (item) => { // 弹出对话框,让用户选择替换的POI console.log('替换项目:', item) } </script>

6. 部署与上线基础

毕业设计答辩通常需要演示可运行的系统。本地运行和简单部署是必备技能。

6.1 后端Spring Boot应用打包与运行

  1. 打包:在项目根目录执行mvn clean package,会在target目录生成travel-guide-0.0.1-SNAPSHOT.jar
  2. 运行
    # 确保数据库已启动并配置正确 java -jar target/travel-guide-0.0.1-SNAPSHOT.jar
  3. 指定配置文件运行(生产环境)
    java -jar -Dspring.profiles.active=prod travel-guide-0.0.1-SNAPSHOT.jar
    需要在src/main/resources下创建application-prod.yml文件,配置生产环境的数据库连接等。

6.2 前端Vue项目构建与部署

  1. 构建:在前端项目根目录执行npm run build,生成dist目录。
  2. 部署
    • 方式一(前后端分离):将dist目录下的静态文件部署到Nginx或Apache服务器上。后端API部署在另一台服务器或同服务器的不同端口。需要配置Nginx反向代理,将API请求转发到后端。
    • 方式二(简化部署):将dist目录的内容复制到Spring Boot项目的src/main/resources/static目录下,重新打包。这样前后端就在同一个Jar包里了,适合演示。

6.3 使用Docker容器化(加分项)

编写Dockerfiledocker-compose.yml可以极大简化环境部署,体现工程能力。

后端Dockerfile示例:

# 文件路径:Dockerfile FROM openjdk:11-jre-slim VOLUME /tmp ARG JAR_FILE=target/*.jar COPY ${JAR_FILE} app.jar ENTRYPOINT ["java","-jar","/app.jar"]

docker-compose.yml (整合MySQL和App):

version: '3.8' services: mysql: image: mysql:8.0 container_name: travel-mysql environment: MYSQL_ROOT_PASSWORD: root123456 MYSQL_DATABASE: travel_guide_db ports: - "3306:3306" volumes: - mysql-data:/var/lib/mysql command: --default-authentication-plugin=mysql_native_password backend: build: . container_name: travel-backend depends_on: - mysql environment: SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/travel_guide_db?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai SPRING_DATASOURCE_USERNAME: root SPRING_DATASOURCE_PASSWORD: root123456 ports: - "8080:8080" restart: on-failure volumes: mysql-data:

运行docker-compose up -d即可一键启动整个系统。

7. 常见问题与排查思路

在开发过程中,你可能会遇到以下典型问题:

问题现象可能原因解决思路
应用启动失败,报DataSource相关错误1. 数据库连接URL、用户名、密码错误。
2. MySQL服务未启动。
3. 数据库驱动版本不匹配。
1. 检查application.yml配置。
2. 运行mysql -u root -p测试连接。
3. 确认pom.xml中MySQL驱动版本与安装的MySQL版本兼容。
调用生成攻略API返回空行程或数据不对1. 数据库中没有对应的POI数据。
2. 用户偏好标签未设置或与POI标签不匹配。
3. 算法逻辑有bug,候选集为空。
1. 检查数据库poi表和tag表是否有数据,并确保关联正确。
2. 调试服务层generateItinerary方法,打印candidatePois的值。
3. 检查preferredTagIds是否成功获取。
前端访问后端API出现CORS错误浏览器跨域策略限制。前端(localhost:8081)访问后端(localhost:8080)属于跨域。在后端Spring Boot应用中配置全局CORS。添加一个配置类:
@Bean
public CorsFilter corsFilter() { ... }允许前端域名。
打包成Jar后运行,找不到前端静态资源前端dist文件未正确复制到static目录,或路径不对。1. 确认构建命令正确。
2. 检查Jar包内容:jar tf travel-guide.jar | grep static
3. 考虑使用外部目录映射静态资源。
生成攻略算法性能慢,数据量大时卡顿1. 数据库查询未优化(如N+1问题)。
2. 算法复杂度高,循环嵌套多。
1. 使用JPA的@EntityGraph或写JOIN FETCH的JPQL语句一次性拉取关联数据。
2. 对候选POI进行分页或限制数量。
3. 考虑引入缓存(如Redis)存储热门目的地POI数据。

8. 项目优化与扩展方向(毕业设计亮点)

完成基础功能后,以下方向可以提升项目深度,成为论文的亮点:

  1. 推荐算法升级
    • 基于内容的推荐:根据POI的属性(标签、描述)与用户偏好进行余弦相似度计算。
    • 协同过滤:收集更多用户对攻略/POI的评分(收藏、点赞、实际出行),实现“喜欢A的人也喜欢B”的推荐。
    • 混合推荐:结合多种算法结果,加权输出最终推荐列表。
  2. 数据丰富与实时性
    • 集成第三方API,如高德/百度地图API获取实时路线规划、距离和交通时间。
    • 集成天气API,在生成攻略时考虑天气因素。
    • 爬取或接入旅游平台(如马蜂窝、携程)的评论数据,用于计算POI的动态评分和热度。
  3. 移动端与多端体验
    • 使用Uni-app或Flutter将Web项目快速编译成小程序和APP,实现一套代码多端发布。
    • 利用移动端特性,如GPS定位自动推荐附近POI、扫码分享攻略。
  4. 系统性能与架构
    • 引入Redis缓存热点数据和用户会话,减轻数据库压力。
    • 使用Elasticsearch实现POI和攻略的全文检索。
    • 将攻略生成服务拆分为独立微服务,使用消息队列(如RabbitMQ)处理异步生成任务。
  5. 智能化与交互
    • 加入聊天机器人接口(如集成大语言模型API),允许用户通过自然语言描述需求(“我想找一个适合带孩子去的、不那么累的北京三日游”),生成更人性化的攻略。
    • 实现攻略的“一键优化”功能,根据用户反馈(“太累”、“预算超了”)自动调整行程。

这个“个性化旅游攻略定制系统”项目涵盖了从需求分析、数据库设计、后端开发、前端交互到部署上线的完整流程,并且有充足的深度和广度供你发挥。选择自己熟悉或想挑战的技术栈,踏实地完成每个模块,你不仅能交出一份出色的毕业设计,更能收获一套宝贵的全栈项目经验。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询