SpringBoot2+Vue3全栈旅游网站开发实战
2026/8/3 10:59:26 网站建设 项目流程

1. 项目概述:现代全栈旅游网站的技术架构解析

这个基于SpringBoot2+Vue3+MyBatis-Plus+MySQL8.0的旅游网站系统源码,代表了当前Java Web全栈开发的主流技术组合。作为一套完整的企业级解决方案,它涵盖了从后端服务到前端展示的全链路实现,特别适合需要快速构建高可用旅游电商平台的开发团队。

我在实际部署和二次开发过程中发现,这套架构有几个显著优势:SpringBoot2提供了稳定的微服务基础,Vue3的Composition API让前端逻辑组织更灵活,MyBatis-Plus的ActiveRecord模式大幅简化了数据层操作,而MySQL8.0的窗口函数和CTE特性则让复杂旅游数据查询变得高效。文档齐全这一点尤其值得称赞,包含了从环境搭建到API调试的完整指引,新手也能快速上手。

2. 技术栈深度解析

2.1 SpringBoot2的核心配置要点

项目采用的是SpringBoot 2.7.x版本,这个长期支持版(LTS)在自动配置方面做了重要优化。我在实际部署时特别注意了几个关键配置:

# application-prod.yml 生产环境配置示例 spring: datasource: url: jdbc:mysql://localhost:3306/travel_db?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true username: travel_user password: ${DB_PASSWORD} hikari: maximum-pool-size: 20 connection-timeout: 30000 redis: host: 127.0.0.1 port: 6379 password: ${REDIS_PASSWORD} lettuce: pool: max-active: 8

特别注意:MySQL8.0连接必须添加allowPublicKeyRetrieval=true参数,否则可能报认证错误。这是新手常踩的坑。

2.2 Vue3前端架构设计

前端采用Vue3 + Element Plus的组合,项目结构清晰体现了现代前端工程化思想:

src/ ├── api/ # 接口请求封装 ├── assets/ # 静态资源 ├── components/ # 公共组件 ├── composables/ # 组合式函数 ├── router/ # 路由配置 ├── stores/ # Pinia状态管理 ├── utils/ # 工具函数 └── views/ # 页面组件

旅游网站特有的功能组件如景点卡片(ScenicCard.vue)、日期选择器(DateRangePicker.vue)都采用了Composition API编写,逻辑复用性极佳。比如这个景点筛选逻辑:

// composables/useScenicFilter.js export function useScenicFilter() { const filterScenics = (scenics, filters) => { return scenics.filter(item => { return ( (!filters.price || item.price <= filters.price) && (!filters.rating || item.rating >= filters.rating) && (!filters.keyword || item.name.includes(filters.keyword)) ) }) } return { filterScenics } }

2.3 MyBatis-Plus的高效应用

MyBatis-Plus 3.5.x版本在本项目中展现了强大的CRUD能力。通过继承BaseMapper,基础数据操作几乎零编码:

// ScenicSpotMapper.java public interface ScenicSpotMapper extends BaseMapper<ScenicSpot> { @Select("SELECT * FROM scenic_spot WHERE status = 1 ORDER BY heat DESC LIMIT #{limit}") List<ScenicSpot> selectHotSpots(@Param("limit") int limit); // 分页查询(配合PageHelper) IPage<ScenicSpot> selectByCondition(Page<ScenicSpot> page, @Param("condition") ScenicCondition condition); }

经验分享:对于旅游网站常见的地理位置查询,建议添加@InterceptorIgnore注解排除分页拦截器,直接使用MySQL的空间函数ST_Distance_Sphere计算距离。

3. 核心功能模块实现

3.1 旅游产品管理系统

后台管理模块采用经典的RBAC权限控制,核心表设计如下:

CREATE TABLE `tour_product` ( `id` bigint NOT NULL AUTO_INCREMENT, `title` varchar(100) NOT NULL COMMENT '产品名称', `cover_image` varchar(255) NOT NULL COMMENT '封面图', `price` decimal(10,2) NOT NULL COMMENT '基础价格', `discount` decimal(3,2) DEFAULT '1.00' COMMENT '折扣率', `departure_city` varchar(50) NOT NULL COMMENT '出发城市', `destination_city` varchar(50) NOT NULL COMMENT '目的地城市', `days` int NOT NULL COMMENT '行程天数', `tags` json DEFAULT NULL COMMENT '标签数组', `status` tinyint NOT NULL DEFAULT '1' COMMENT '状态', `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), FULLTEXT KEY `ft_title_city` (`title`,`destination_city`) /* 全文检索 */ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

产品列表接口的实现体现了MyBatis-Plus的强大之处:

// TourProductController.java @GetMapping("/list") public R<PageResult<TourProductVO>> list( @RequestParam(required = false) String keyword, @RequestParam(required = false) String city, @RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "10") Integer size) { LambdaQueryWrapper<TourProduct> wrapper = Wrappers.lambdaQuery(); wrapper.eq(TourProduct::getStatus, 1) .like(StringUtils.isNotBlank(keyword), TourProduct::getTitle, keyword) .eq(StringUtils.isNotBlank(city), TourProduct::getDestinationCity, city) .orderByDesc(TourProduct::getCreatedAt); Page<TourProduct> pageInfo = new Page<>(page, size); productMapper.selectPage(pageInfo, wrapper); return R.success(new PageResult<>( pageInfo.getTotal(), productConvert.toVOList(pageInfo.getRecords()) )); }

3.2 订单支付流程设计

旅游网站的订单系统需要考虑库存锁定、超时取消等特殊场景。项目采用了状态机模式管理订单生命周期:

待支付 --支付成功--> 已支付 待支付 --超时未支付--> 已取消 已支付 --确认使用--> 已完成 已支付 --申请退款--> 退款中 退款中 --退款成功--> 已退款

核心的订单创建逻辑包含以下关键步骤:

  1. 验证产品可用性(库存、有效期)
  2. 计算实际价格(考虑折扣、优惠券)
  3. 生成分布式订单ID(雪花算法)
  4. 创建订单主表和明细表记录
  5. 预扣库存(Redis原子操作)
  6. 发送订单创建事件(RabbitMQ)
// OrderServiceImpl.java @Transactional public String createOrder(OrderCreateDTO dto) { // 1. 验证产品 TourProduct product = validateProduct(dto.getProductId()); // 2. 计算价格 BigDecimal actualPrice = calculatePrice(product, dto.getCouponId()); // 3. 生成订单号 String orderNo = IdWorker.get32UUID(); // 4. 创建订单 Order order = new Order(); order.setOrderNo(orderNo); order.setUserId(SecurityUtils.getUserId()); order.setTotalAmount(actualPrice); // ...其他字段设置 orderMapper.insert(order); // 5. 扣减库存 redisTemplate.execute(new DefaultRedisScript<Long>( STOCK_DEDUCT_SCRIPT, Long.class ), Collections.singletonList("stock:" + product.getId()), "1"); // 6. 发送延迟消息(30分钟未支付自动取消) rabbitTemplate.convertAndSend( "order.delay.exchange", "order.cancel", orderNo, message -> { message.getMessageProperties() .setDelay(30 * 60 * 1000); // 30分钟 return message; } ); return orderNo; }

3.3 旅游攻略社区模块

采用MySQL8.0的JSON特性存储攻略内容的多媒体资源:

CREATE TABLE `travel_strategy` ( `id` bigint NOT NULL AUTO_INCREMENT, `title` varchar(100) NOT NULL, `content` text NOT NULL, `resources` json DEFAULT NULL COMMENT '{"images":[...], "videos":[...]}', `view_count` int DEFAULT '0', `like_count` int DEFAULT '0', `user_id` bigint NOT NULL, `created_at` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_user` (`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

攻略详情接口展示了MySQL8.0窗口函数的妙用:

@Select("SELECT s.*, " + "ROW_NUMBER() OVER(PARTITION BY s.destination_city ORDER BY s.like_count DESC) AS city_rank, " + "COUNT(*) OVER(PARTITION BY s.destination_city) AS city_total " + "FROM travel_strategy s WHERE s.id = #{id}") @ResultMap("strategyResultMap") TravelStrategyDetailVO selectStrategyDetail(Long id);

4. 部署与性能优化实战

4.1 生产环境部署方案

推荐使用Docker Compose编排服务,以下是docker-compose.yml的关键配置:

version: '3.8' services: mysql: image: mysql:8.0 container_name: travel_mysql environment: MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD} MYSQL_DATABASE: travel_db MYSQL_USER: travel_user MYSQL_PASSWORD: ${DB_PASSWORD} ports: - "3306:3306" volumes: - ./mysql/data:/var/lib/mysql - ./mysql/conf:/etc/mysql/conf.d command: --default-authentication-plugin=mysql_native_password redis: image: redis:6 container_name: travel_redis ports: - "6379:6379" volumes: - ./redis/data:/data backend: build: ./backend container_name: travel_backend ports: - "8080:8080" depends_on: - mysql - redis environment: SPRING_PROFILES_ACTIVE: prod frontend: build: ./frontend container_name: travel_frontend ports: - "80:80" depends_on: - backend

部署提示:MySQL8容器必须设置--default-authentication-plugin=mysql_native_password,否则可能导致旧版客户端连接失败。

4.2 性能优化关键指标

通过JMeter压测后,我们针对以下瓶颈进行了优化:

  1. N+1查询问题:使用MyBatis-Plus的@TableField(select = false)延迟加载大字段
  2. 热点数据缓存:Redis缓存策略设计
    • 一级缓存:本地缓存(Caffeine) 有效期5分钟
    • 二级缓存:Redis集群 有效期30分钟
  3. 列表查询优化
    • 添加covering index
    • 使用MySQL8.0的倒序索引
// 缓存配置示例 @Cacheable(value = "product", key = "#id", unless = "#result == null") public ProductDetailVO getProductDetail(Long id) { return productMapper.selectDetailById(id); } @CacheEvict(value = "product", key = "#id") public void updateProduct(ProductUpdateDTO dto) { // 更新逻辑 }

4.3 安全防护措施

旅游网站尤其需要注意安全防护:

  1. SQL注入防护

    • 始终使用MyBatis-Plus的条件构造器
    • 禁止直接拼接SQL语句
  2. XSS防护

    • 前端使用DOMPurify过滤富文本
    • 后端使用Jackson的@JsonSerialize(using = HtmlEscapeSerializer.class)
  3. CSRF防护

    • 启用Spring Security的CSRF保护
    • 前端在axios拦截器中添加XSRF-TOKEN
  4. 敏感数据加密

    • 数据库字段使用AES加密
    • 日志脱敏处理
// 敏感数据加密示例 public class User { @ColumnEncrypt(algorithm = Algorithm.AES, password = "${encrypt.password}") private String idCard; @ColumnEncrypt(algorithm = Algorithm.AES, password = "${encrypt.password}") private String phone; }

5. 二次开发指南

5.1 如何添加新模块

假设需要增加"旅游保险"模块,推荐步骤如下:

  1. 数据库新建insurance表
  2. 使用MyBatis-Plus代码生成器生成实体类、Mapper、Service
  3. 创建Controller暴露REST API
  4. 前端创建对应的页面和路由
  5. 在订单流程中集成保险选项
# 使用MyBatis-Plus代码生成器 mvn mybatis-plus:generate -DmybatisPlus.generator.packageName=com.travel.insurance \ -DmybatisPlus.generator.tableNames=insurance \ -DmybatisPlus.generator.outputDir=src/main/java

5.2 多语言支持方案

旅游网站通常需要国际化,推荐采用以下方案:

后端:

  • 使用Spring的MessageSource
  • 通过Accept-Language头部识别语言

前端:

  • Vue I18n插件
  • 语言包按模块拆分懒加载
// frontend/src/lang/en.js export default { product: { title: 'Tour Products', filter: { city: 'Destination City', price: 'Price Range' } } } // frontend/src/lang/zh-CN.js export default { product: { title: '旅游产品', filter: { city: '目的地城市', price: '价格区间' } } }

5.3 微服务改造建议

当业务规模扩大时,可考虑拆分为以下服务:

  1. 用户服务(user-service)
  2. 产品服务(product-service)
  3. 订单服务(order-service)
  4. 支付服务(payment-service)
  5. 评论服务(review-service)

改造关键点:

  • 使用Spring Cloud Alibaba套件
  • 数据库垂直拆分
  • 分布式事务采用Seata
  • 前端改用微前端架构
// 产品服务Feign客户端示例 @FeignClient(name = "product-service", path = "/api/product") public interface ProductFeignClient { @GetMapping("/{id}") R<ProductDTO> getById(@PathVariable Long id); @PostMapping("/reduce-stock") R<Void> reduceStock(@RequestBody StockReduceDTO dto); }

6. 常见问题排查手册

6.1 启动类报错排查

问题现象:SpringBoot应用启动时报"Failed to configure a DataSource"

原因分析

  • 未正确配置数据库连接
  • 依赖了数据源但未配置

解决方案

  1. 检查application.yml中的datasource配置
  2. 或排除自动配置:
@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class, DataSourceTransactionManagerAutoConfiguration.class })

6.2 MyBatis-Plus分页失效

问题现象:分页查询返回所有记录

原因排查

  1. 未注册分页插件
  2. 自定义SQL未使用Page参数

正确配置

@Configuration public class MyBatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }

6.3 Vue3热更新失效

问题现象:修改代码后页面不自动刷新

解决方案

  1. 检查vite.config.js配置:
export default defineConfig({ server: { hmr: { overlay: false } } })
  1. 可能是某些插件冲突,尝试:
rm -rf node_modules/.vite

6.4 MySQL8.0连接问题

常见错误:Public Key Retrieval is not allowed

解决方法

  1. 在连接字符串添加参数:
jdbc:mysql://localhost:3306/db?allowPublicKeyRetrieval=true
  1. 或更新MySQL驱动到最新版

6.5 跨域问题处理

前端报错:CORS policy blocked request

后端解决方案

@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOrigins("*") .allowedMethods("*") .allowedHeaders("*") .maxAge(3600); } }

前端代理方案(开发环境):

// vite.config.js export default defineConfig({ server: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } } })

这套旅游网站系统源码我已在三个实际项目中成功实施,最大的体会是:文档齐全真的能节省大量摸索时间。特别是MyBatis-Plus与Vue3的组合,在快速迭代业务时优势明显。建议初次使用时先完整阅读文档中的"架构说明"章节,理解作者的设计思路后再进行二次开发,会事半功倍。

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

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

立即咨询