ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

SpringBoot2+Vue3全栈旅游网站开发实战

2026/8/3 10:59:28 拓冰建站 浏览量
SpringBoot2+Vue3全栈旅游网站开发实战 1. 项目概述现代全栈旅游网站的技术架构解析这个基于SpringBoot2Vue3MyBatis-PlusMySQL8.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?useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue 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连接必须添加allowPublicKeyRetrievaltrue参数否则可能报认证错误。这是新手常踩的坑。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 BaseMapperScenicSpot { Select(SELECT * FROM scenic_spot WHERE status 1 ORDER BY heat DESC LIMIT #{limit}) ListScenicSpot selectHotSpots(Param(limit) int limit); // 分页查询配合PageHelper IPageScenicSpot selectByCondition(PageScenicSpot 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) /* 全文检索 */ ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_0900_ai_ci;产品列表接口的实现体现了MyBatis-Plus的强大之处// TourProductController.java GetMapping(/list) public RPageResultTourProductVO list( RequestParam(required false) String keyword, RequestParam(required false) String city, RequestParam(defaultValue 1) Integer page, RequestParam(defaultValue 10) Integer size) { LambdaQueryWrapperTourProduct 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); PageTourProduct pageInfo new Page(page, size); productMapper.selectPage(pageInfo, wrapper); return R.success(new PageResult( pageInfo.getTotal(), productConvert.toVOList(pageInfo.getRecords()) )); }3.2 订单支付流程设计旅游网站的订单系统需要考虑库存锁定、超时取消等特殊场景。项目采用了状态机模式管理订单生命周期待支付 --支付成功-- 已支付 待支付 --超时未支付-- 已取消 已支付 --确认使用-- 已完成 已支付 --申请退款-- 退款中 退款中 --退款成功-- 已退款核心的订单创建逻辑包含以下关键步骤验证产品可用性库存、有效期计算实际价格考虑折扣、优惠券生成分布式订单ID雪花算法创建订单主表和明细表记录预扣库存Redis原子操作发送订单创建事件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 DefaultRedisScriptLong( 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) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_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-pluginmysql_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-pluginmysql_native_password否则可能导致旧版客户端连接失败。4.2 性能优化关键指标通过JMeter压测后我们针对以下瓶颈进行了优化N1查询问题使用MyBatis-Plus的TableField(select false)延迟加载大字段热点数据缓存Redis缓存策略设计一级缓存本地缓存(Caffeine) 有效期5分钟二级缓存Redis集群 有效期30分钟列表查询优化添加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 安全防护措施旅游网站尤其需要注意安全防护SQL注入防护始终使用MyBatis-Plus的条件构造器禁止直接拼接SQL语句XSS防护前端使用DOMPurify过滤富文本后端使用Jackson的JsonSerialize(using HtmlEscapeSerializer.class)CSRF防护启用Spring Security的CSRF保护前端在axios拦截器中添加XSRF-TOKEN敏感数据加密数据库字段使用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 如何添加新模块假设需要增加旅游保险模块推荐步骤如下数据库新建insurance表使用MyBatis-Plus代码生成器生成实体类、Mapper、Service创建Controller暴露REST API前端创建对应的页面和路由在订单流程中集成保险选项# 使用MyBatis-Plus代码生成器 mvn mybatis-plus:generate -DmybatisPlus.generator.packageNamecom.travel.insurance \ -DmybatisPlus.generator.tableNamesinsurance \ -DmybatisPlus.generator.outputDirsrc/main/java5.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 微服务改造建议当业务规模扩大时可考虑拆分为以下服务用户服务(user-service)产品服务(product-service)订单服务(order-service)支付服务(payment-service)评论服务(review-service)改造关键点使用Spring Cloud Alibaba套件数据库垂直拆分分布式事务采用Seata前端改用微前端架构// 产品服务Feign客户端示例 FeignClient(name product-service, path /api/product) public interface ProductFeignClient { GetMapping(/{id}) RProductDTO getById(PathVariable Long id); PostMapping(/reduce-stock) RVoid reduceStock(RequestBody StockReduceDTO dto); }6. 常见问题排查手册6.1 启动类报错排查问题现象SpringBoot应用启动时报Failed to configure a DataSource原因分析未正确配置数据库连接依赖了数据源但未配置解决方案检查application.yml中的datasource配置或排除自动配置SpringBootApplication(exclude { DataSourceAutoConfiguration.class, DataSourceTransactionManagerAutoConfiguration.class })6.2 MyBatis-Plus分页失效问题现象分页查询返回所有记录原因排查未注册分页插件自定义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热更新失效问题现象修改代码后页面不自动刷新解决方案检查vite.config.js配置export default defineConfig({ server: { hmr: { overlay: false } } })可能是某些插件冲突尝试rm -rf node_modules/.vite6.4 MySQL8.0连接问题常见错误Public Key Retrieval is not allowed解决方法在连接字符串添加参数jdbc:mysql://localhost:3306/db?allowPublicKeyRetrievaltrue或更新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的组合在快速迭代业务时优势明显。建议初次使用时先完整阅读文档中的架构说明章节理解作者的设计思路后再进行二次开发会事半功倍。