苏州作为中国历史文化名城,拥有2500多年的建城史,素有"人间天堂"的美誉。这座城市以园林艺术闻名于世,拙政园、留园等9座古典园林被列入世界文化遗产名录。同时,苏州还保留着完整的古城格局和丰富的水乡风貌,平江路、山塘街等历史街区展现了典型的江南水乡特色。
然而,在实际旅游体验中,游客常常面临三大痛点:
本项目正是针对这些痛点,采用SpringBoot框架构建的一站式苏州旅游服务平台。系统通过数字化手段整合苏州文旅资源,提供从信息查询到服务预订的完整闭环体验。相比传统旅游网站,本系统具有三个显著优势:
系统采用主流Java技术栈构建,主要基于以下考量:
后端框架选择:
前端技术栈:
数据库选型:
系统采用分层架构设计,各层职责明确:
code复制表现层:用户界面(Web/App)
↓
应用层:Controller(接收请求) → Service(业务逻辑)
↓
数据层:Mapper(数据访问) → MySQL/Redis
关键设计决策:
景点表(scenic_spot):
sql复制CREATE TABLE `scenic_spot` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(100) NOT NULL COMMENT '景点名称',
`category_id` int NOT NULL COMMENT '分类ID',
`level` varchar(20) COMMENT '景点等级',
`address` varchar(200) NOT NULL,
`open_time` varchar(100) COMMENT '开放时间',
`price` decimal(10,2) COMMENT '门票价格',
`description` text COMMENT '详细描述',
`culture` text COMMENT '文化背景',
`cover_image` varchar(255) COMMENT '封面图片',
`click_count` int DEFAULT 0 COMMENT '点击量',
`status` tinyint DEFAULT 1 COMMENT '状态',
PRIMARY KEY (`id`),
KEY `idx_category` (`category_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
订单表(order_info):
sql复制CREATE TABLE `order_info` (
`id` varchar(32) NOT NULL COMMENT '订单号',
`user_id` bigint NOT NULL,
`scenic_id` bigint COMMENT '景点ID',
`goods_name` varchar(100) NOT NULL,
`goods_image` varchar(255),
`quantity` int NOT NULL DEFAULT 1,
`unit_price` decimal(10,2) NOT NULL,
`total_price` decimal(10,2) NOT NULL,
`order_status` tinyint NOT NULL DEFAULT 0 COMMENT '0未支付 1已支付 2已完成 3已取消',
`create_time` datetime NOT NULL,
`update_time` datetime NOT NULL,
PRIMARY KEY (`id`),
KEY `idx_user` (`user_id`),
KEY `idx_status` (`order_status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
实现代码示例:
java复制@GetMapping("/scenic/list")
public Result listScenicSpots(
@RequestParam(required = false) String keyword,
@RequestParam(required = false) Integer categoryId,
@RequestParam(required = false) String level,
@RequestParam(defaultValue = "0") BigDecimal minPrice,
@RequestParam(defaultValue = "1000") BigDecimal maxPrice,
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer size) {
QueryWrapper<ScenicSpot> wrapper = new QueryWrapper<>();
if (StringUtils.isNotBlank(keyword)) {
wrapper.like("name", keyword);
}
if (categoryId != null) {
wrapper.eq("category_id", categoryId);
}
// 其他条件处理...
Page<ScenicSpot> pageResult = scenicSpotService.page(
new Page<>(page, size), wrapper);
return Result.success(pageResult);
}
采用三级缓存策略:
缓存更新策略:
java复制@Cacheable(value = "scenicDetail", key = "#id")
public ScenicSpotDetailVO getDetailById(Long id) {
// 1. 查询数据库获取基础信息
ScenicSpot spot = getById(id);
// 2. 异步更新点击量
asyncUpdateClickCount(id);
// 3. 组装VO对象
return convertToDetailVO(spot);
}
@Async
public void asyncUpdateClickCount(Long id) {
// 使用Redis原子操作增加点击量
redisTemplate.opsForValue().increment("scenic:click:" + id);
// 定时任务将Redis数据同步到数据库
}
基于协同过滤的混合推荐:
python复制# 伪代码示例
def hybrid_recommend(user_id):
# 基于内容的推荐
content_based = get_content_based_recommendations(user_id)
# 协同过滤推荐
cf_based = get_cf_recommendations(user_id)
# 热门景点补充
hot_items = get_hot_items()
# 混合推荐结果
recommendations = merge_recommendations(
content_based, cf_based, hot_items)
return recommendations
使用Redis有序集合存储推荐结果:
java复制// 存储用户推荐列表
public void cacheUserRecommendations(Long userId, List<Long> scenicIds) {
String key = "user:recommend:" + userId;
redisTemplate.delete(key);
Map<String, Double> scoreMap = new HashMap<>();
for (int i = 0; i < scenicIds.size(); i++) {
scoreMap.put(scenicIds.get(i).toString(), (double)(scenicIds.size() - i));
}
redisTemplate.opsForZSet().add(key, scoreMap);
redisTemplate.expire(key, 2, TimeUnit.HOURS);
}
使用状态模式实现订单流程:
java复制public interface OrderState {
void pay(Order order);
void cancel(Order order);
void complete(Order order);
void refund(Order order);
}
@Component
@Scope("prototype")
public class UnpaidState implements OrderState {
@Override
public void pay(Order order) {
// 支付逻辑
order.setState(OrderConstant.PAID);
orderService.updateById(order);
// 发送支付成功通知
notifyService.sendPaySuccessNotify(order);
}
// 其他方法实现...
}
使用Seata处理分布式事务:
java复制@GlobalTransactional
public String createOrder(OrderCreateDTO dto) {
// 1. 扣减库存
stockService.reduceStock(dto.getScenicId(), dto.getQuantity());
// 2. 创建订单
Order order = buildOrder(dto);
orderMapper.insert(order);
// 3. 记录订单日志
orderLogService.recordCreateLog(order);
return order.getId();
}
JWT令牌实现方案:
java复制public class JwtTokenProvider {
private String secretKey = "your-secret-key";
private long validityInMilliseconds = 3600000; // 1h
public String createToken(String username, List<String> roles) {
Claims claims = Jwts.claims().setSubject(username);
claims.put("roles", roles);
Date now = new Date();
Date validity = new Date(now.getTime() + validityInMilliseconds);
return Jwts.builder()
.setClaims(claims)
.setIssuedAt(now)
.setExpiration(validity)
.signWith(SignatureAlgorithm.HS256, secretKey)
.compact();
}
// 其他方法...
}
数据脱敏处理:
java复制public class DataMaskingUtil {
public static String maskPhone(String phone) {
if (StringUtils.isBlank(phone) || phone.length() < 7) {
return phone;
}
return phone.substring(0, 3) + "****" + phone.substring(7);
}
public static String maskIdCard(String idCard) {
if (StringUtils.isBlank(idCard) || idCard.length() < 15) {
return idCard;
}
return idCard.substring(0, 6) + "********" + idCard.substring(14);
}
}
慢查询优化前后对比:
优化前:
sql复制SELECT * FROM scenic_spot
WHERE category_id IN (
SELECT id FROM category WHERE parent_id = 1
)
ORDER BY click_count DESC;
优化后:
sql复制SELECT s.* FROM scenic_spot s
JOIN category c ON s.category_id = c.id
WHERE c.parent_id = 1
ORDER BY s.click_count DESC;
建立联合索引:
sql复制ALTER TABLE scenic_spot ADD INDEX idx_category_click (category_id, click_count);
多级缓存架构:
缓存击穿解决方案:
java复制public ScenicSpot getScenicSpotWithCache(Long id) {
// 1. 尝试从缓存获取
String cacheKey = "scenic:" + id;
ScenicSpot spot = redisTemplate.opsForValue().get(cacheKey);
if (spot != null) {
return spot;
}
// 2. 获取分布式锁
String lockKey = "lock:scenic:" + id;
boolean locked = redisTemplate.opsForValue()
.setIfAbsent(lockKey, "1", 30, TimeUnit.SECONDS);
if (locked) {
try {
// 3. 再次检查缓存(双重检查)
spot = redisTemplate.opsForValue().get(cacheKey);
if (spot != null) {
return spot;
}
// 4. 查询数据库
spot = scenicSpotMapper.selectById(id);
if (spot != null) {
redisTemplate.opsForValue().set(
cacheKey, spot, 1, TimeUnit.HOURS);
} else {
// 缓存空对象防止穿透
redisTemplate.opsForValue().set(
cacheKey, new ScenicSpot(), 5, TimeUnit.MINUTES);
}
return spot;
} finally {
// 释放锁
redisTemplate.delete(lockKey);
}
} else {
// 等待重试或返回默认值
return new ScenicSpot();
}
}
Docker Compose部署文件示例:
yaml复制version: '3.8'
services:
mysql:
image: mysql:8.0
container_name: mysql
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: suzhou_tour
ports:
- "3306:3306"
volumes:
- ./mysql/data:/var/lib/mysql
- ./mysql/conf:/etc/mysql/conf.d
redis:
image: redis:6.2
container_name: redis
ports:
- "6379:6379"
volumes:
- ./redis/data:/data
app:
build: .
container_name: app
ports:
- "8080:8080"
depends_on:
- mysql
- redis
environment:
- SPRING_PROFILES_ACTIVE=prod
Prometheus监控指标配置:
yaml复制scrape_configs:
- job_name: 'spring_app'
metrics_path: '/actuator/prometheus'
static_configs:
- targets: ['app:8080']
relabel_configs:
- source_labels: [__address__]
target_label: instance
regex: '(.*):\d+'
replacement: '${1}'
关键监控指标:
GitLab CI配置示例:
yaml复制stages:
- build
- test
- deploy
variables:
MAVEN_OPTS: "-Dmaven.repo.local=.m2/repository"
build:
stage: build
image: maven:3.8.4-jdk-11
script:
- mvn clean package -DskipTests
artifacts:
paths:
- target/*.jar
test:
stage: test
image: maven:3.8.4-jdk-11
script:
- mvn test
deploy:
stage: deploy
image: docker:20.10.12
services:
- docker:20.10.12-dind
script:
- docker build -t suzhou-tour .
- echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
- docker push suzhou-tour:latest
only:
- master
在开发苏州旅游指南网站的过程中,我们积累了一些宝贵的经验教训:
文化内容建设:旅游类系统不同于普通电商,需要特别注重文化内容的专业性和准确性。我们专门聘请了苏州地方文化专家作为顾问,确保所有景点介绍的权威性。
高并发场景设计:在旅游旺季,系统可能面临突发流量。我们通过以下措施应对:
地图集成痛点:初期使用第三方地图API时遇到坐标偏移问题。解决方案:
支付对接经验:与多个支付渠道对接时,抽象出统一的支付网关层,关键设计:
java复制public interface PaymentGateway {
PaymentResult pay(PaymentRequest request);
PaymentResult query(String orderId);
RefundResult refund(RefundRequest request);
}
// 支付宝实现
@Service
public class AlipayGateway implements PaymentGateway {
// 实现方法...
}
// 微信支付实现
@Service
public class WechatpayGateway implements PaymentGateway {
// 实现方法...
}
对于类似旅游类系统的开发者,我的建议是: