1. 项目概述与技术选型
这个前后端分离的汽车票网上预订系统,是我去年带队为某省客运集团开发的线上售票平台。系统上线后日均处理订单量突破3万单,经受住了春运高峰期的考验。整个技术栈采用SpringBoot+Vue3+MyBatis的经典组合,数据库选用MySQL 8.0,下面我会详细拆解这个项目的技术实现要点。
为什么选择这个技术栈?经过多轮技术评估我们发现:
- SpringBoot的自动配置特性让后端服务搭建效率提升40%
- Vue3的Composition API相比Options API更适应复杂业务场景
- MyBatis的动态SQL能力完美匹配票务系统的多条件查询需求
- MySQL在事务处理和数据一致性方面表现稳定
提示:实际开发中发现Vue3的ref和reactive在复杂表单场景下的性能差异明显,建议根据数据复杂度合理选择
2. 后端架构设计与核心实现
2.1 SpringBoot工程结构
采用典型的三层架构,但针对票务业务做了特殊优化:
code复制src/
├── main/
│ ├── java/
│ │ └── com/
│ │ └── bus/
│ │ ├── config/ # 特殊配置类
│ │ ├── controller/ # 带@Validated参数校验
│ │ ├── service/ # 业务逻辑层
│ │ │ └── impl/ # 实现类
│ │ ├── dao/ # MyBatis Mapper接口
│ │ ├── entity/ # 实体类
│ │ └── util/ # 工具包
│ └── resources/
│ ├── mapper/ # XML映射文件
│ ├── application.yml # 多环境配置
│ └── application-dev.yml
2.2 MyBatis动态SQL实践
票务查询涉及10+个动态条件,我们采用MyBatis的动态SQL特性:
xml复制<select id="queryTickets" resultType="Ticket">
SELECT * FROM bus_ticket
<where>
<if test="departureCity != null">
AND departure_city = #{departureCity}
</if>
<if test="arrivalCity != null">
AND arrival_city = #{arrivalCity}
</if>
<if test="departureDate != null">
AND DATE(departure_time) = #{departureDate}
</if>
<!-- 其他条件 -->
</where>
ORDER BY departure_time ASC
</select>
注意:避免使用${}防止SQL注入,所有参数必须用#{}方式传递
2.3 事务管理与并发控制
针对高并发售票场景,我们采用以下方案:
- 分布式锁(Redis实现)保证座位分配的原子性
- @Transactional注解配合传播机制控制事务边界
- 乐观锁解决并发修改问题:
java复制@Update("UPDATE bus_seat SET version=version+1, status=#{status}
WHERE seat_id=#{seatId} AND version=#{version}")
int updateSeatStatusWithVersion(Seat seat);
3. 前端Vue3关键技术实现
3.1 组合式API实践
使用setup语法糖重构传统Options API:
javascript复制// 车次查询逻辑
const searchParams = reactive({
departureCity: '',
arrivalCity: '',
date: dayjs().format('YYYY-MM-DD')
})
const { data: tickets, execute: search } = useAsyncData(
async () => {
const params = { ...searchParams }
return await $fetch('/api/tickets', { params })
},
{ immediate: false }
)
3.2 状态管理方案
采用Pinia替代Vuex管理全局状态:
typescript复制// stores/ticket.ts
export const useTicketStore = defineStore('ticket', {
state: () => ({
selectedTicket: null as Ticket | null,
passengerList: [] as Passenger[]
}),
actions: {
async selectTicket(ticket: Ticket) {
this.selectedTicket = ticket
await this.loadPassengers()
}
}
})
3.3 性能优化技巧
- 虚拟滚动处理长列表(1000+车次)
- Web Worker处理票价计算
- 动态导入非首屏组件
- 使用keep-alive缓存常用页面
4. 数据库设计与优化
4.1 核心表结构
sql复制CREATE TABLE `bus_ticket` (
`ticket_id` BIGINT NOT NULL AUTO_INCREMENT,
`bus_id` BIGINT NOT NULL COMMENT '关联车辆',
`departure_city` VARCHAR(50) NOT NULL,
`arrival_city` VARCHAR(50) NOT NULL,
`departure_time` DATETIME NOT NULL,
`arrival_time` DATETIME NOT NULL,
`price` DECIMAL(10,2) NOT NULL,
`remaining_seats` INT DEFAULT 0,
PRIMARY KEY (`ticket_id`),
INDEX `idx_route` (`departure_city`, `arrival_city`),
INDEX `idx_time` (`departure_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
4.2 查询优化方案
- 为高频查询字段建立组合索引
- 使用EXPLAIN分析慢查询
- 大表分库分表策略(按线路+月份拆分)
- 冷热数据分离存储
5. 安全防护措施
5.1 防SQL注入
- 严格使用MyBatis参数绑定
- 自定义拦截器过滤危险字符
- 定期使用SQLMap进行漏洞扫描
5.2 接口安全
- JWT令牌认证
- 敏感数据加密传输
- 接口限流(Guava RateLimiter)
- 关键操作日志审计
6. 部署与监控
6.1 容器化部署
Docker Compose编排文件示例:
yaml复制version: '3'
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
volumes:
- mysql_data:/var/lib/mysql
backend:
build: ./backend
ports:
- "8080:8080"
depends_on:
- mysql
6.2 监控方案
- SpringBoot Actuator暴露健康指标
- Prometheus + Grafana监控体系
- ELK日志分析系统
- 自定义业务指标监控(如售票成功率)
7. 典型业务场景实现
7.1 余票实时更新
采用WebSocket实现:
java复制@GetMapping("/ticket-updates")
public SseEmitter streamTicketUpdates() {
SseEmitter emitter = new SseEmitter(180_000L);
ticketUpdateEmitters.add(emitter);
emitter.onCompletion(() -> ticketUpdateEmitters.remove(emitter));
return emitter;
}
// 当余票变化时
for (SseEmitter emitter : ticketUpdateEmitters) {
try {
emitter.send(
SseEmitter.event()
.data(updateInfo)
.id(String.valueOf(System.currentTimeMillis()))
);
} catch (IOException e) {
emitter.complete();
ticketUpdateEmitters.remove(emitter);
}
}
7.2 订单超时处理
使用延迟队列方案:
- 订单创建时发送延迟消息
- RabbitMQ死信队列处理超时
- 分布式锁防止重复处理
8. 踩坑与解决方案
8.1 MyBatis缓存问题
现象:更新操作后查询结果未刷新
解决:在Mapper接口添加@CacheNamespaceRef注解
8.2 Vue3响应式丢失
现象:解构后响应式失效
正确做法:
javascript复制const { x, y } = toRefs(props)
8.3 MySQL死锁
优化方案:
- 调整事务隔离级别
- 统一SQL执行顺序
- 添加合适的索引
这个项目让我深刻体会到,技术选型必须紧密结合业务特点。比如在票务系统中,事务一致性和并发控制比极致性能更重要。我们在灰度发布阶段发现,当并发量超过5000QPS时,单纯的乐观锁方案会出现较多冲突回滚,最终采用Redis分布式锁+数据库乐观锁的混合方案才完美解决。
