1. 项目概述:SpringBoot旅游平台的设计初衷
去年帮学弟调试毕业设计时,发现市面上80%的旅游网站都存在功能割裂问题——用户查完景点要跳转订票,订完票又得重新登录酒店系统。这种碎片化体验正是我们做这个"一站式"智慧旅游平台的出发点。基于SpringBoot 2.7.12 + MyBatis-Plus 3.5.3的技术组合,我们实现了景点查询、票务预订、酒店入住、攻略社区等核心功能的有机整合。
关键设计原则:所有子系统的用户认证统一采用JWT+Redis方案,确保一次登录全站通行。实测单节点服务器(4核8G)在300并发用户压力下,平均响应时间保持在800ms以内。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心技术栈选型解析
2.1 为什么选择SpringBoot
对比传统SSM框架,SpringBoot的自动装配特性让我们的开发效率提升明显。比如集成MyBatis时,原本需要手动配置的SqlSessionFactoryBean现在只需一个@MapperScan注解。实测从零搭建基础框架仅需15分钟(含数据库连接池配置)。
java复制// 典型的主启动类配置
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@MapperScan("com.tourism.mapper")
public class TourismApplication {
public static void main(String[] args) {
SpringApplication.run(TourismApplication.class, args);
}
}
2.2 数据库设计要点
采用MySQL 8.0作为主库,重点解决旅游业务中的典型数据关系:
- 景点与门票的1:N关系(含不同票种价格策略)
- 用户订单的级联操作(删除用户时保留订单记录)
- 酒店房型的库存日历表设计
sql复制CREATE TABLE `scenic_spot` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`name` VARCHAR(100) NOT NULL COMMENT '景点名称',
`geo_point` POINT NOT NULL COMMENT 'GIS坐标',
`description` TEXT,
PRIMARY KEY (`id`),
SPATIAL INDEX `idx_geo` (`geo_point`)
) ENGINE=INNODB DEFAULT CHARSET=utf8mb4;
3. 核心功能模块实现
3.1 智能推荐系统
结合用户历史行为数据和协同过滤算法,实现三种推荐策略:
- 基于位置的周边推荐(使用Redis GEO)
- 基于用户画像的个性化推荐
- 热门榜单(定时任务每日凌晨更新)
java复制public List<ScenicSpot> recommendSpots(Long userId) {
// 获取用户最近浏览的5个景点坐标
List<Point> historyPoints = browseHistoryMapper.selectRecentPoints(userId);
// 半径50公里范围内的景点(Redis GEO查询)
RadiusQuery query = new RadiusQuery()
.within(historyPoints.get(0))
.withDistance(50, Metrics.KILOMETERS);
return redisTemplate.opsForGeo().radius(query);
}
3.2 分布式事务处理
门票预订涉及多个子系统操作:
- 扣减库存(商品服务)
- 创建订单(订单服务)
- 生成电子票(票务服务)
采用Seata 1.5.2的AT模式实现分布式事务,关键配置如下:
properties复制# application.properties
spring.cloud.alibaba.seata.tx-service-group=my_test_tx_group
seata.service.grouplist=192.168.1.100:8091
4. 性能优化实战记录
4.1 缓存策略设计
遇到过的典型问题:热门景点详情页QPS高峰时MySQL负载飙升到90%。最终采用三级缓存方案:
- 本地Caffeine缓存(有效期5分钟)
- Redis集群缓存(有效期30分钟)
- MySQL持久化存储
java复制@Cacheable(value = "scenicDetail", key = "#id",
cacheManager = "caffeineCacheManager")
public ScenicDetail getDetail(Long id) {
// 先查Redis
String redisKey = "scenic:" + id;
ScenicDetail detail = redisTemplate.opsForValue().get(redisKey);
if(detail == null) {
detail = scenicMapper.selectDetail(id);
// 写入Redis并设置过期时间
redisTemplate.opsForValue().set(redisKey, detail, 30, TimeUnit.MINUTES);
}
return detail;
}
4.2 图片存储方案
初期使用本地存储导致的问题:
- 服务器磁盘空间快速耗尽
- CDN加速效果差
迁移到MinIO对象存储后的改进:
- 图片上传速度提升3倍(内网传输)
- 结合Nginx实现动态缩略图生成
- 存储成本降低60%
5. 典型问题排查实录
5.1 JVM内存泄漏定位
现象:服务运行48小时后出现Full GC频繁。使用Arthas工具排查:
bash复制# 查看对象实例数排名
heapdump --live /tmp/heap.hprof
# 分析GC日志
jstat -gcutil pid 1000 10
最终定位到是未关闭的PDF导出流导致,修复方案:
java复制try (PDDocument doc = PDDocument.load(templateFile)) {
// 生成PDF逻辑
} // 自动关闭资源
5.2 分布式锁失效问题
在秒杀场景下发现超卖问题,原Redis锁实现存在缺陷:
java复制// 错误实现(非原子操作)
if(!redis.exists(key)) {
redis.set(key, value);
return true;
}
改用Redisson的RLock解决:
java复制RLock lock = redissonClient.getLock("ticket:" + ticketId);
try {
if(lock.tryLock(1, 10, TimeUnit.SECONDS)) {
// 业务逻辑
}
} finally {
lock.unlock();
}
6. 部署架构演进
6.1 从单机到集群
初期部署方案:
- 单台4核8G服务器
- MySQL主从复制
遇到瓶颈后的改进:
- 接入阿里云SLB实现负载均衡
- Redis集群(3主3从)
- MySQL读写分离(使用Sharding-JDBC)
6.2 监控体系搭建
必备的监控组件:
- Prometheus + Grafana(系统指标)
- SkyWalking(分布式追踪)
- ELK(日志分析)
关键监控指标告警阈值设置:
- CPU使用率 > 70%持续5分钟
- 接口99线 > 2s
- JVM Old区 > 80%
7. 安全防护实践
7.1 接口防刷策略
针对短信接口被恶意调用的问题,实现:
- IP限流(Guava RateLimiter)
- 图形验证码二次验证
- 业务参数签名校验
java复制@RateLimiter(value = 10, key = "#phone.substring(7)")
public Result sendVerifyCode(String phone) {
// 发送逻辑
}
7.2 SQL注入防护
除了常规的MyBatis参数化查询,额外措施:
- 启动时校验Mapper XML文件
- 定期执行SQL注入测试用例
- 使用Druid的WallFilter
xml复制<bean id="wallFilter" class="com.alibaba.druid.wall.WallFilter">
<property name="config" ref="wallConfig"/>
</bean>
8. 项目扩展方向
最近正在尝试的优化:
- 接入ChatGPT API实现智能客服
- 使用Elasticsearch重构搜索模块
- 开发微信小程序版本
在本地测试环境中,通过Docker Compose一键启动所有依赖服务:
yaml复制version: '3'
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: root
redis:
image: redis:6.2-alpine
ports:
- "6379:6379"
