1. 旅游网站小系统开发全景解析
去年夏天,我接到了一个旅游网站小系统的开发需求。客户是本地一家刚起步的旅行社,希望建立一个能展示旅游产品、支持在线预订的基础平台。这类项目看似简单,实则涉及前后端协同、支付对接、数据安全等多个技术环节。经过两个月的开发迭代,系统最终顺利上线,日均UV稳定在3000左右。下面我就把这个项目的完整开发过程和经验教训做个复盘。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 需求分析与技术选型
2.1 核心功能拆解
客户提出的原始需求比较模糊,经过三次需求会议,我们最终确定了以下核心模块:
- 旅游产品展示(含图文详情、价格日历)
- 会员注册登录系统
- 在线预订与支付流程
- 后台订单管理系统
- 简单的数据统计看板
特别要注意的是,旅游产品具有明显的季节性特征。比如春节期间的海南线路和暑假的西北环线,价格和库存变化频繁,这直接影响了数据库设计。
2.2 技术栈选择
基于预算和开发周期考虑,我们选择了以下技术方案:
- 前端:Vue 3 + Element Plus(响应式布局适配移动端)
- 后端:Spring Boot 2.7 + MyBatis Plus
- 数据库:MySQL 8.0(配置了主从复制)
- 缓存:Redis 6(用于热门线路查询缓存)
- 支付:支付宝当面付API + 微信支付JSAPI
- 部署:Nginx + Docker(单机部署方案)
经验提示:小型旅游网站特别要注意支付接口的选择。我们最初考虑过银联接口,但对接复杂度较高,最终选择了更通用的方案。
3. 核心功能实现细节
3.1 旅游产品展示系统
产品展示是流量转化的第一关口,我们实现了:
- 动态价格日历组件
vue复制<template>
<el-calendar v-model="selectedDate">
<template #dateCell="{date, data}">
<div class="price-display">
<span v-if="data.price">¥{{data.price}}</span>
<span v-else class="no-stock">售罄</span>
</div>
</template>
</el-calendar>
</template>
- 图片懒加载与预加载策略
javascript复制// 首屏图片预加载
const preloadImages = (urls) => {
urls.forEach(url => {
new Image().src = url
})
}
- 库存实时检查接口
java复制@GetMapping("/checkStock")
public Result checkStock(@RequestParam Long productId,
@RequestParam String departDate) {
// 先查Redis缓存
String cacheKey = "stock:" + productId + ":" + departDate;
Integer cacheStock = redisTemplate.opsForValue().get(cacheKey);
if(cacheStock != null) {
return Result.success(cacheStock);
}
// 缓存未命中查数据库
int realStock = stockService.getRealStock(productId, departDate);
redisTemplate.opsForValue().set(cacheKey, realStock, 5, TimeUnit.MINUTES);
return Result.success(realStock);
}
3.2 预订业务流程设计
旅游产品的预订流程比普通电商更复杂,我们设计了六步状态机:
- 预订单生成(未支付)
- 支付中(等待支付回调)
- 待确认(需要人工核对证件信息)
- 已确认(出票中)
- 已完成
- 已取消
状态转换图如下(使用纯文字描述):
初始状态 -> 预订单生成 -> (支付成功) -> 支付中 -> (自动/人工检查) -> 待确认 -> (确认完成) -> 已确认 -> (出行完成) -> 已完成
同时在任何状态都可能 -> 已取消
4. 性能优化实践
4.1 缓存策略设计
针对旅游网站的特点,我们实施了三级缓存:
- 静态资源:Nginx缓存配置
nginx复制location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
expires 30d;
add_header Cache-Control "public";
}
- 热门线路:Redis缓存
- 使用ZSET维护热度排名
- 采用LFU淘汰策略
- 设置动态过期时间(旺季2分钟,淡季10分钟)
- 数据库查询:MyBatis二级缓存
xml复制<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
4.2 数据库优化
旅游产品表的关键索引设计:
sql复制CREATE TABLE `tour_product` (
`id` bigint NOT NULL AUTO_INCREMENT,
`title` varchar(100) NOT NULL,
`depart_city` varchar(50) NOT NULL,
`dest_city` varchar(50) NOT NULL,
`start_date` date NOT NULL,
`end_date` date NOT NULL,
PRIMARY KEY (`id`),
KEY `idx_city_date` (`depart_city`,`dest_city`,`start_date`),
KEY `idx_dest` (`dest_city`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
5. 踩坑记录与解决方案
5.1 支付回调处理
初期我们遇到了支付成功但订单状态未更新的问题,原因是:
- 支付宝回调有1-3秒延迟
- 用户可能在回调到达前就刷新了页面
- 网络抖动可能导致回调丢失
最终解决方案:
java复制// 支付结果查询补偿机制
@Scheduled(fixedRate = 60000)
public void checkPendingPayments() {
List<Order> pendingOrders = orderMapper.selectPendingOrders();
for (Order order : pendingOrders) {
PaymentStatus status = paymentService.queryPayment(order.getPaymentNo());
if(status == PaymentStatus.SUCCESS) {
orderService.confirmPayment(order.getId());
}
}
}
5.2 库存超卖问题
在促销期间出现过库存超卖,解决方案:
- 使用Redis分布式锁
java复制public boolean lockStock(Long productId, String date) {
String lockKey = "lock:stock:" + productId + ":" + date;
return redisTemplate.opsForValue()
.setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS);
}
- 数据库乐观锁
sql复制UPDATE tour_stock
SET remain = remain - 1
WHERE product_id = ? AND depart_date = ? AND remain > 0
6. 安全防护措施
6.1 常见攻击防护
- XSS防护:
java复制@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.headers()
.xssProtection()
.and()
.contentSecurityPolicy("script-src 'self'");
}
}
- CSRF防护:
vue复制// 前端axios配置
axios.defaults.xsrfCookieName = 'XSRF-TOKEN'
axios.defaults.xsrfHeaderName = 'X-XSRF-TOKEN'
6.2 数据安全策略
- 敏感信息加密:
java复制// 身份证号加密存储
public String encryptIdCard(String idCard) {
return AESUtil.encrypt(idCard, secretKey);
}
- 日志脱敏处理:
java复制@Bean
public PatternLayout patternLayout() {
PatternLayout layout = new PatternLayout();
layout.setPattern("%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{50} - %replace(%msg){'\\d{4}(?=\\d{4})', '****'} %n");
return layout;
}
7. 上线后运维监控
7.1 基础监控配置
使用Prometheus + Grafana搭建监控看板,关键指标包括:
- 接口响应时间P99
- 支付成功率
- 库存缓存命中率
- 订单状态转换耗时
7.2 日志分析方案
采用ELK栈处理日志,重点监控:
- 支付回调延迟
- 库存操作冲突
- 慢SQL查询
- 频繁重试请求
日志收集配置示例:
yaml复制filebeat.inputs:
- type: log
paths:
- /var/log/spring/*.log
fields:
app: tour-web
json.keys_under_root: true
json.add_error_key: true
这个项目给我的最大启示是:旅游系统看似业务简单,但涉及到的实时性要求和高并发场景比想象中复杂。特别是在节假日前后,流量会是平时的5-10倍,所有缓存和限流策略都要提前做好压力测试。后来我们通过增加预售机制和弹性扩容策略,平稳度过了春节流量高峰。
