一、 项目背景与意义
随着国民生活水平的提升和休闲观念的转变,旅游已成为人们生活中不可或缺的一部分。然而,在信息爆炸的时代,游客在规划行程时常常面临信息过载、质量参差不齐、个性化推荐不足等痛点。传统的旅游攻略平台多侧重于景点介绍和路线规划,对于“美食”这一旅游核心体验要素的深度挖掘和分享相对薄弱。
“金途”旅游美食攻略分享系统旨在解决这一市场空白,专注于打造一个以“美食”为核心的垂直旅游社区。其意义在于:
- 信息聚合与提纯:聚合用户真实分享的各地美食攻略,过滤广告和低质信息,为用户提供可信赖的参考。
- 个性化体验:通过用户行为分析和智能算法,为不同偏好的游客推荐契合其口味的美食路线。
- 社区化互动:构建美食爱好者交流社区,增强用户粘性,形成“发现-分享-互动”的良性循环。
- 技术实践价值:作为一个典型的全栈Web应用,其设计与实现涵盖了现代企业级应用开发的核心技术栈,具有很高的学习与参考价值。
二、 系统技术栈
本系统采用前后端分离的架构设计,技术选型兼顾成熟度、开发效率与性能。
2.1 后端技术栈
- 核心框架:Spring Boot 2.7+,提供快速启动和自动配置。
- Web层:Spring MVC,处理HTTP请求和RESTful API。
- 数据持久层:
- ORM框架:MyBatis-Plus,简化CRUD操作。
- 数据库:MySQL 8.0,存储核心业务数据。
- 缓存:Redis,用于会话管理、热点数据缓存。
- 安全与权限:Spring Security + JWT (JSON Web Token),实现用户认证与授权。
- 文档与测试:
- API文档:SpringDoc OpenAPI (Swagger UI)。
- 单元测试:JUnit 5, Mockito。
- 其他组件:
- 文件存储:本地存储或集成OSS(如阿里云OSS)用于图片上传。
- 搜索:Elasticsearch(可选),用于攻略内容的全文检索。
- 消息队列:RabbitMQ(可选),用于异步处理如邮件通知、数据同步等任务。
2.2 前端技术栈
- 框架:Vue 3 (Composition API) 或 React 18。
- 构建工具:Vite。
- UI组件库:Element Plus (Vue) 或 Ant Design (React)。
- 状态管理:Pinia (Vue) 或 Redux Toolkit (React)。
- 路由:Vue Router 或 React Router。
- HTTP客户端:Axios。
2.3 开发与部署
- 版本控制:Git。
- 项目管理:Maven 或 Gradle。
- 容器化:Docker, Docker Compose。
- 持续集成/部署 (CI/CD):Jenkins 或 GitLab CI。
三、 系统核心功能模块设计
系统主要分为前台用户端和后台管理端。
3.1 前台用户端
- 用户中心:注册、登录(含第三方登录)、个人信息管理。
- 攻略模块:
- 攻略发布:富文本编辑、多图上传、地理位置标记、标签分类。
- 攻略浏览:列表展示、详情查看、按城市/美食类型/标签筛选、排序。
- 攻略互动:点赞、收藏、评论、分享。
- 搜索模块:关键词搜索、智能补全、热门搜索推荐。
- 推荐模块:基于用户历史行为(浏览、点赞、收藏)的个性化美食攻略推荐。
- 消息模块:系统通知、评论回复提醒、私信(可选)。
3.2 后台管理端
- 内容管理:攻略审核、置顶、推荐、违规内容下架。
- 用户管理:用户列表查看、封禁/解封、角色权限分配。
- 数据统计:用户增长、攻略发布量、热门城市/美食分析报表。
- 系统配置:轮播图管理、热门标签管理、推荐规则配置。
四、 数据库核心表设计
-- 用户表 CREATE TABLE `sys_user` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键', `username` varchar(50) NOT NULL COMMENT '用户名', `password` varchar(255) NOT NULL COMMENT '加密后的密码', `nickname` varchar(50) DEFAULT NULL COMMENT '昵称', `avatar` varchar(500) DEFAULT NULL COMMENT '头像URL', `email` varchar(100) DEFAULT NULL COMMENT '邮箱', `status` tinyint DEFAULT '1' COMMENT '状态 (0:禁用,1:正常)', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_username` (`username`) ) ENGINE=InnoDB COMMENT='用户表'; -- 攻略表 CREATE TABLE `travel_strategy` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键', `user_id` bigint NOT NULL COMMENT '发布者ID', `title` varchar(200) NOT NULL COMMENT '攻略标题', `cover_image` varchar(500) DEFAULT NULL COMMENT '封面图', `content` longtext NOT NULL COMMENT '攻略内容(HTML)', `city` varchar(100) DEFAULT NULL COMMENT '城市', `location_tags` json DEFAULT NULL COMMENT '地理位置标签JSON', `food_tags` json DEFAULT NULL COMMENT '美食标签JSON', `view_count` int DEFAULT '0' COMMENT '浏览量', `like_count` int DEFAULT '0' COMMENT '点赞数', `collect_count` int DEFAULT '0' COMMENT '收藏数', `comment_count` int DEFAULT '0' COMMENT '评论数', `status` tinyint DEFAULT '0' COMMENT '状态 (0:待审核,1:已发布,2:审核失败,3:已下架)', `is_top` tinyint DEFAULT '0' COMMENT '是否置顶', `is_recommend` tinyint DEFAULT '0' COMMENT '是否推荐', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_user_id` (`user_id`), KEY `idx_city` (`city`), KEY `idx_status` (`status`) ) ENGINE=InnoDB COMMENT='旅游美食攻略表'; -- 攻略互动表(点赞、收藏) CREATE TABLE `strategy_interaction` ( `id` bigint NOT NULL AUTO_INCREMENT, `user_id` bigint NOT NULL, `strategy_id` bigint NOT NULL, `type` tinyint NOT NULL COMMENT '类型 (1:点赞,2:收藏)', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_user_strategy_type` (`user_id`,`strategy_id`,`type`) ) ENGINE=InnoDB COMMENT='攻略互动表'; -- 评论表 CREATE TABLE `comment` ( `id` bigint NOT NULL AUTO_INCREMENT, `strategy_id` bigint NOT NULL COMMENT '攻略ID', `user_id` bigint NOT NULL COMMENT '评论者ID', `content` text NOT NULL COMMENT '评论内容', `parent_id` bigint DEFAULT '0' COMMENT '父评论ID (0:顶级评论)', `reply_to_user_id` bigint DEFAULT NULL COMMENT '回复目标用户ID', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_strategy_id` (`strategy_id`) ) ENGINE=InnoDB COMMENT='评论表';五、 核心代码实现示例
5.1 用户登录与JWT生成 (Spring Security配置)
// JWT工具类 @Component public class JwtTokenUtil { @Value("${jwt.secret}") private String secret; @Value("${jwt.expiration}") private Long expiration; public String generateToken(UserDetails userDetails) { Map<String, Object> claims = new HashMap<>(); claims.put("username", userDetails.getUsername()); return Jwts.builder() .setClaims(claims) .setSubject(userDetails.getUsername()) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + expiration * 1000)) .signWith(SignatureAlgorithm.HS512, secret) .compact(); } // ... 其他方法:验证token、获取用户名等 } // 自定义UserDetailsService @Service public class UserDetailsServiceImpl implements UserDetailsService { @Autowired private SysUserMapper userMapper; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { SysUser user = userMapper.selectOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getUsername, username)); if (user == null) { throw new UsernameNotFoundException("用户不存在"); } if (user.getStatus() == 0) { throw new DisabledException("用户已被禁用"); } // 构建UserDetails对象 (此处简化,实际应查询用户角色权限) return new org.springframework.security.core.userdetails.User( user.getUsername(), user.getPassword(), Collections.emptyList() // 权限列表 ); } }5.2 攻略发布接口 (Controller & Service)
@RestController @RequestMapping("/api/strategy") public class TravelStrategyController { @Autowired private TravelStrategyService strategyService; @PostMapping("/publish") @PreAuthorize("hasRole('USER')") // 需要用户角色 public Result<Long> publishStrategy(@RequestBody @Valid StrategyPublishDTO dto, @AuthenticationPrincipal UserDetails user) { Long strategyId = strategyService.publishStrategy(dto, user.getUsername()); return Result.success(strategyId); } } @Service @Slf4j public class TravelStrategyServiceImpl implements TravelStrategyService { @Autowired private TravelStrategyMapper strategyMapper; @Autowired private SysUserMapper userMapper; @Transactional @Override public Long publishStrategy(StrategyPublishDTO dto, String username) { // 1. 获取当前用户 SysUser user = userMapper.selectOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getUsername, username)); // 2. DTO 转 Entity TravelStrategy strategy = new TravelStrategy(); BeanUtils.copyProperties(dto, strategy); strategy.setUserId(user.getId()); strategy.setStatus(0); // 待审核状态 // 3. 保存到数据库 strategyMapper.insert(strategy); log.info("用户 {} 发布攻略成功,ID: {}", username, strategy.getId()); return strategy.getId(); } }5.3 攻略列表分页查询 (MyBatis-Plus)
@Service public class TravelStrategyServiceImpl implements TravelStrategyService { // ... @Override public PageResult<StrategyVO> listStrategy(StrategyQueryDTO queryDTO) { // 构建查询条件 LambdaQueryWrapper<TravelStrategy> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(TravelStrategy::getStatus, 1); // 只查询已发布的 if (StringUtils.isNotBlank(queryDTO.getCity())) { wrapper.eq(TravelStrategy::getCity, queryDTO.getCity()); } if (StringUtils.isNotBlank(queryDTO.getKeyword())) { wrapper.like(TravelStrategy::getTitle, queryDTO.getKeyword()); } // 排序 wrapper.orderByDesc(TravelStrategy::getIsTop, TravelStrategy::getCreateTime); // 分页查询 Page<TravelStrategy> page = new Page<>(queryDTO.getPageNum(), queryDTO.getPageSize()); Page<TravelStrategy> strategyPage = strategyMapper.selectPage(page, wrapper); // 转换为VO列表 List<StrategyVO> voList = strategyPage.getRecords().stream() .map(this::convertToVO) .collect(Collectors.toList()); return new PageResult<>(voList, strategyPage.getTotal()); } }六、 总结与展望
“金途”旅游美食攻略分享系统基于Spring Boot等主流技术栈,实现了一个功能完整、可扩展性强的Web应用。系统核心在于以“美食”为切入点,通过社区化内容生产和智能推荐,提升旅游攻略的实用性和个性化体验。
未来可进一步拓展的方向包括:
- 智能化升级:引入更复杂的推荐算法(如协同过滤、深度学习模型),实现更精准的“千人千面”推荐。
- 内容形式丰富:支持短视频攻略、直播探店等更生动的表现形式。
- 商业化探索:在保障用户体验的前提下,探索与本地商家合作的商业模式。
- 性能优化:对图片、静态资源进行CDN加速,对数据库进行读写分离、分库分表等优化,以支撑更大的用户规模。
本项目不仅是一个实用的产品构想,也为学习Spring Boot全栈开发提供了完整的实践案例。