1. 项目概述与核心价值
汽车票网上预订系统是传统客运行业数字化转型的典型应用场景。这个基于SpringBoot+Vue的全栈解决方案,完美融合了后端业务处理能力与前端交互体验,为客运企业提供了一套完整的在线售票管理平台。我去年参与过某省级客运集团的系统升级项目,深刻体会到这类系统在实际运营中的关键作用——它不仅减少了窗口排队压力,还能通过数据分析优化班次调度。
系统采用前后端分离架构,后端使用SpringBoot 3.1.5构建RESTful API,前端采用Vue 3组合式API开发响应式界面。数据持久层选用MyBatis-Plus 3.5.3增强CRUD操作,MySQL 8.0作为主数据库。这种技术组合既保证了开发效率,又能应对高并发售票场景。实测在4核8G服务器上,系统可稳定处理每秒300+的订票请求。
2. 系统架构设计解析
2.1 技术栈选型依据
选择SpringBoot而非传统SSM框架,主要基于其自动配置特性和内嵌Tomcat支持。在汽车票务场景中,班次查询需要快速响应,SpringBoot的启动速度比传统Spring快40%以上。Vue 3的Composition API相比Options API更适合处理复杂的票务状态管理,比如同时处理成人票、儿童票的优惠计算。
数据库方面,MySQL 8.0的窗口函数和CTE特性可以高效处理如下复杂查询:
sql复制-- 查询热门线路的余票情况
WITH route_rank AS (
SELECT route_id, COUNT(*) as order_count
FROM ticket_orders
WHERE depart_date BETWEEN ? AND ?
GROUP BY route_id
)
SELECT r.route_name, s.depart_time,
(s.total_seats - IFNULL(SUM(o.seat_count),0)) as remaining_seats
FROM bus_schedule s
JOIN bus_routes r ON s.route_id = r.route_id
LEFT JOIN ticket_orders o ON o.schedule_id = s.schedule_id
WHERE s.depart_date = ?
GROUP BY s.schedule_id
ORDER BY s.depart_time;
2.2 微服务化改造空间
虽然当前版本采用单体架构,但我们在代码中预留了微服务扩展点。例如:
- 将票务核心模块独立为ticket-service
- 支付功能拆分为payment-service
- 使用Spring Cloud Gateway作为API网关
这种设计使得未来升级时,只需在application.yml中配置Nacos注册中心地址即可平滑迁移:
yaml复制spring:
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
3. 核心业务模块实现
3.1 票务管理子系统
票务状态机是系统的核心逻辑,我们采用状态模式实现:
java复制public interface TicketState {
void reserve(TicketContext context);
void pay(TicketContext context);
void cancel(TicketContext context);
}
@Component
@Scope("prototype")
public class AvailableState implements TicketState {
@Override
public void reserve(TicketContext context) {
context.setReservedTime(LocalDateTime.now());
context.setState(new ReservedState());
// 记录座位锁定日志
seatLockService.lockSeat(context.getScheduleId(), context.getSeatNo());
}
// 其他方法实现...
}
3.2 分布式锁解决方案
针对高并发抢票场景,我们实现了三级锁机制:
- 前端防重复提交(按钮禁用+Token验证)
- 应用层Redis分布式锁(Redisson实现)
- 数据库乐观锁(version字段)
关键实现代码:
java复制public boolean lockSeat(Long scheduleId, String seatNo) {
String lockKey = "seat_lock:" + scheduleId + ":" + seatNo;
RLock lock = redissonClient.getLock(lockKey);
try {
return lock.tryLock(2, 10, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
4. 数据库设计与优化
4.1 关键表结构设计
车次表采用纵表设计提高扩展性:
sql复制CREATE TABLE `bus_schedule` (
`schedule_id` BIGINT PRIMARY KEY AUTO_INCREMENT,
`route_id` INT NOT NULL COMMENT '线路ID',
`bus_id` INT NOT NULL COMMENT '车辆ID',
`depart_date` DATE NOT NULL COMMENT '发车日期',
`depart_time` TIME NOT NULL COMMENT '发车时间',
`driver_id` INT COMMENT '司机ID',
`total_seats` SMALLINT NOT NULL COMMENT '总座位数',
`price` DECIMAL(10,2) NOT NULL COMMENT '基准票价',
`status` TINYINT DEFAULT 1 COMMENT '1-待发车 2-已发车 3-已取消',
`version` INT DEFAULT 0 COMMENT '乐观锁版本',
INDEX `idx_route_date` (`route_id`, `depart_date`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
4.2 查询性能优化实践
针对班次查询的热点场景,我们采取以下措施:
- 使用覆盖索引避免回表
- 对历史数据按月分表(schedule_202501)
- 热门线路缓存到Redis,TTL设置为5分钟
示例缓存结构:
json复制{
"route:1001": {
"schedule:5001": {
"depart_time": "08:00",
"remaining": 28,
"price": 65.00
},
// 其他班次...
}
}
5. 前端工程化实践
5.1 Vuex状态管理设计
票务状态树示例:
javascript复制const store = createStore({
modules: {
ticket: {
namespaced: true,
state: () => ({
selectedSchedule: null,
passengers: [],
contactInfo: {}
}),
mutations: {
SET_SCHEDULE(state, payload) {
state.selectedSchedule = payload
// 清空之前选择的乘客
state.passengers = []
}
}
}
}
})
5.2 移动端适配方案
采用vw+rem弹性布局:
scss复制// 基准尺寸375px(iPhone6)
@function vw($px) {
@return ($px / 375) * 100vw;
}
.ticket-card {
width: vw(345);
padding: vw(15);
margin-bottom: vw(10);
.price {
font-size: vw(16);
}
}
6. 安全防护体系
6.1 防黄牛技术措施
- 行为验证码(滑动拼图+短信二次验证)
- 购票频率限制(同一IP/账号5分钟内最多3次)
- 人机识别(鼠标移动轨迹分析)
Spring Security配置示例:
java复制@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/api/ticket/submit").access("@rateLimitService.check(authentication)")
// 其他配置...
}
}
6.2 敏感数据保护
- 密码使用BCrypt加密
- 身份证号加密存储(AES-256-GCM)
- 日志脱敏处理
加密工具类示例:
java复制public class IdCardEncryptor {
private static final String KEY = "your-32-byte-key";
public static String encrypt(String idCard) {
// AES加密实现...
}
public static String decrypt(String cipherText) {
// 解密实现...
}
}
7. 部署与监控方案
7.1 容器化部署
Docker Compose编排示例:
yaml复制version: '3.8'
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
volumes:
- ./mysql-data:/var/lib/mysql
redis:
image: redis:6.2-alpine
ports:
- "6379:6379"
backend:
build: ./backend
ports:
- "8080:8080"
depends_on:
- mysql
- redis
7.2 Prometheus监控配置
SpringBoot Actuator集成示例:
yaml复制management:
endpoints:
web:
exposure:
include: health,metrics,prometheus
metrics:
tags:
application: ${spring.application.name}
8. 典型问题排查实录
8.1 超卖问题解决方案
通过Redis Lua脚本实现原子化库存扣减:
lua复制local key = KEYS[1]
local quantity = tonumber(ARGV[1])
local remaining = tonumber(redis.call('GET', key))
if remaining >= quantity then
redis.call('DECRBY', key, quantity)
return 1
else
return 0
end
8.2 分布式事务处理
使用Seata处理跨服务事务:
java复制@GlobalTransactional
public void completeOrder(Long orderId) {
ticketService.updateStatus(orderId, PAID);
paymentService.confirmPayment(orderId);
smsService.sendConfirm(orderId);
}
9. 扩展功能实现思路
9.1 电子客票PDF生成
使用Flying Saucer+Thymeleaf模板:
java复制@GetMapping("/ticket/pdf/{orderNo}")
public void generatePdf(@PathVariable String orderNo,
HttpServletResponse response) throws Exception {
Context ctx = new Context();
ctx.setVariable("order", orderService.getByNo(orderNo));
String html = templateEngine.process("ticket-template", ctx);
OutputStream os = response.getOutputStream();
ITextRenderer renderer = new ITextRenderer();
renderer.setDocumentFromString(html);
renderer.layout();
renderer.createPDF(os);
os.close();
}
9.2 微信小程序集成
通过WxJava实现通知推送:
java复制@Autowired
private WxMaService wxMaService;
public void pushPaySuccess(String openid, Order order) {
WxMaSubscribeMessage message = new WxMaSubscribeMessage();
message.setTemplateId("支付成功通知模板ID");
message.setToUser(openid);
message.addData(new WxMaSubscribeMessage.MsgData("character_string1", order.getOrderNo()));
// 其他字段...
wxMaService.getMsgService().sendSubscribeMsg(message);
}
10. 性能调优实战记录
10.1 Nginx配置优化
针对静态资源和高并发接口的配置:
nginx复制server {
gzip on;
gzip_types text/plain application/json application/javascript;
location ~ ^/api/ticket/query {
limit_req zone=ticket_burst burst=20 nodelay;
proxy_pass http://backend;
}
location /static {
expires 1y;
add_header Cache-Control "public";
}
}
10.2 JVM参数调整
生产环境GC配置示例:
bash复制java -jar -Xms2g -Xmx2g -XX:+UseG1GC \
-XX:MaxGCPauseMillis=200 \
-XX:InitiatingHeapOccupancyPercent=35 \
-XX:+ExplicitGCInvokesConcurrent \
-Dspring.profiles.active=prod \
your-application.jar
11. 项目二次开发建议
- 多租户支持:通过TenantFilter实现租户隔离
- 票价动态计算:引入规则引擎处理节假日溢价
- 智能排班:结合历史数据预测最佳发车班次
- 人脸核验:集成活体检测技术实现刷脸乘车
租户隔离实现示例:
java复制public class TenantFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
String tenantId = req.getHeader("X-Tenant-ID");
TenantContext.setCurrentTenant(tenantId);
try {
chain.doFilter(request, response);
} finally {
TenantContext.clear();
}
}
}
12. 测试策略与案例
12.1 压力测试方案
使用JMeter模拟高峰购票场景:
code复制Thread Group: 500并发用户
Ramp-up: 60秒
循环次数: 永远
Sampler:
- 登录接口(20%)
- 班次查询(40%)
- 提交订单(30%)
- 支付接口(10%)
12.2 幂等性测试用例
订单重复提交测试:
java复制@Test
public void testIdempotentOrder() {
String orderToken = UUID.randomUUID().toString();
// 第一次请求
ResponseEntity<String> r1 = restTemplate.postForEntity(
"/api/order", buildRequest(orderToken), String.class);
// 相同token第二次请求
ResponseEntity<String> r2 = restTemplate.postForEntity(
"/api/order", buildRequest(orderToken), String.class);
assertEquals(200, r1.getStatusCodeValue());
assertEquals(409, r2.getStatusCodeValue()); // 应返回冲突状态
}
13. 项目文档规范
13.1 Swagger接口文档
SpringDoc配置示例:
java复制@Bean
public OpenAPI customOpenAPI() {
return new OpenAPI()
.info(new Info().title("汽车票务系统API")
.version("1.0")
.contact(new Contact().name("技术支持")))
.addSecurityItem(new SecurityRequirement().addList("JWT"));
}
13.2 数据库变更管理
采用Flyway进行版本控制:
sql复制-- V1__Initial_schema.sql
CREATE TABLE bus_routes (...);
-- V2__Add_indexes.sql
CREATE INDEX idx_route_name ON bus_routes(route_name);
14. 持续集成实践
GitLab CI/CD配置示例:
yaml复制stages:
- test
- build
- deploy
unit-test:
stage: test
script:
- mvn test
package:
stage: build
script:
- mvn package -DskipTests
artifacts:
paths:
- target/*.jar
deploy-prod:
stage: deploy
script:
- scp target/*.jar user@prod-server:/app
- ssh user@prod-server "systemctl restart ticket-service"
when: manual
only:
- master
15. 项目升级路线图
-
近期(3个月):
- 增加微信/支付宝小程序端
- 实现电子发票功能
-
中期(6个月):
- 引入RabbitMQ处理异步任务
- 增加BI数据分析看板
-
长期(1年):
- 微服务化改造
- 智能调度算法优化
消息队列集成示例:
java复制@RabbitListener(queues = "ticket.payment.success")
public void handlePaymentSuccess(PaymentMessage message) {
ticketService.updateStatus(message.getOrderId(), PAID);
smsService.sendPaymentSuccess(message.getPhone());
}
