1. 项目背景与核心需求
船票信息管理系统是航运公司、码头管理机构不可或缺的业务支撑平台。传统的人工记录和Excel表格管理方式存在数据易丢失、查询效率低下、票务状态更新不及时等问题。尤其在旅游旺季,面对激增的票务订单,手工操作模式更是捉襟见肘。
基于Java技术栈开发的船票管理系统,主要解决以下核心痛点:
- 票务信息集中化管理:实现船次、舱位、乘客信息的数字化存储
- 动态库存控制:实时监控各航次余票情况,避免超售
- 多维度查询统计:支持按时间、航线、乘客类型等条件快速检索
- 业务流程自动化:从订票、改签到退票的全流程电子化处理
提示:系统设计时需要特别注意并发订票场景下的数据一致性问题,避免出现"超卖"现象。这是课程设计中常被忽略但实际业务中至关重要的技术点。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术选型与架构设计
2.1 基础技术栈组合
本系统采用当前Java企业级开发的主流技术组合:
- 后端框架:Spring Boot 2.7 + Spring MVC + MyBatis Plus
- 数据库:MySQL 8.0(InnoDB引擎)
- 前端技术:Thymeleaf模板引擎 + Bootstrap 5
- 构建工具:Maven 3.8
- 版本控制:Git + GitHub
选择这套技术栈主要基于以下考虑:
- Spring Boot的自动配置特性大幅简化了项目初始化工作
- MyBatis Plus提供的CRUD接口能快速实现基础数据操作
- MySQL的事务支持能确保票务操作的原子性
- Bootstrap的响应式布局适配不同终端设备
2.2 系统分层架构
采用经典的三层架构设计:
code复制表示层(Web) → 业务逻辑层(Service) → 数据访问层(DAO)
↑ ↑
前端验证 事务控制
关键类设计示例:
java复制// 船次实体类
public class ShipSchedule {
private Long id;
private String shipNumber; // 船次编号
private String departurePort;
private String arrivalPort;
private LocalDateTime departureTime;
private LocalDateTime arrivalTime;
private Integer totalSeats; // 总座位数
private Integer availableSeats; // 剩余座位数
// getters & setters
}
// 票务服务接口
public interface TicketService {
Ticket bookTicket(Long scheduleId, Passenger passenger);
boolean cancelTicket(Long ticketId);
List<Ticket> queryTickets(TicketQueryCriteria criteria);
}
3. 核心功能模块实现
3.1 船次管理模块
作为系统基础数据,船次信息管理需实现:
- 船次信息的CRUD操作
- 船期日历视图展示
- 座位库存初始化与更新
关键技术点:
- 使用MySQL的DATETIME类型存储精确到分钟的发船时间
- 通过AJAX实现无刷新分页加载
- 采用乐观锁控制座位库存更新:
java复制@Transactional
public boolean bookSeat(Long scheduleId) {
ShipSchedule schedule = scheduleMapper.selectById(scheduleId);
if(schedule.getAvailableSeats() <= 0){
return false;
}
schedule.setAvailableSeats(schedule.getAvailableSeats() - 1);
int updated = scheduleMapper.updateById(schedule);
return updated > 0; // 返回是否更新成功
}
3.2 票务交易模块
包含订票、退票、改签三大核心功能:
- 订票流程:
- 选择船次 → 选择舱位 → 填写乘客信息 → 支付 → 出票
- 退票规则:
- 离港24小时前:全额退款
- 24小时内:收取20%手续费
- 改签逻辑:
- 仅允许改签到有余票的航次
- 差价多退少补
事务处理示例:
java复制@Transactional(rollbackFor = Exception.class)
public Ticket bookTicket(TicketOrder order) {
// 1. 扣减库存
boolean stockUpdated = seatService.reduceStock(order.getScheduleId());
if(!stockUpdated){
throw new BusinessException("座位已售罄");
}
// 2. 生成票务记录
Ticket ticket = new Ticket();
BeanUtils.copyProperties(order, ticket);
ticket.setTicketNo(generateTicketNo());
ticket.setStatus(TicketStatus.PAID);
ticketMapper.insert(ticket);
// 3. 记录交易流水
paymentService.recordPayment(ticket);
return ticket;
}
4. 数据库设计与优化
4.1 主要表结构设计
ship_schedule(船次表)
sql复制CREATE TABLE `ship_schedule` (
`id` bigint NOT NULL AUTO_INCREMENT,
`ship_number` varchar(32) NOT NULL COMMENT '船次编号',
`departure_port` varchar(50) NOT NULL,
`arrival_port` varchar(50) NOT NULL,
`departure_time` datetime NOT NULL,
`arrival_time` datetime NOT NULL,
`total_seats` int NOT NULL DEFAULT '0',
`available_seats` int NOT NULL DEFAULT '0',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_ship_number` (`ship_number`),
KEY `idx_departure_time` (`departure_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
ticket(票务表)
sql复制CREATE TABLE `ticket` (
`id` bigint NOT NULL AUTO_INCREMENT,
`ticket_no` varchar(32) NOT NULL COMMENT '票号',
`schedule_id` bigint NOT NULL,
`passenger_name` varchar(50) NOT NULL,
`passenger_id` varchar(18) NOT NULL COMMENT '身份证号',
`seat_class` varchar(10) NOT NULL COMMENT '舱位等级',
`seat_number` varchar(10) DEFAULT NULL,
`price` decimal(10,2) NOT NULL,
`status` varchar(20) NOT NULL COMMENT 'UNPAID/PAID/CANCELLED',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_ticket_no` (`ticket_no`),
KEY `idx_schedule_id` (`schedule_id`),
KEY `idx_passenger_id` (`passenger_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
4.2 查询性能优化
针对高频查询场景的优化策略:
- 为船次表的departure_time字段添加索引,加速按日期范围查询
- 使用覆盖索引优化票务查询:
sql复制-- 优化前
SELECT * FROM ticket WHERE passenger_id = '身份证号';
-- 优化后
CREATE INDEX idx_passenger_cover ON ticket(passenger_id, status, schedule_id);
SELECT id, passenger_id, status, schedule_id FROM ticket
WHERE passenger_id = '身份证号';
- 对大表实施水平分表:按月份拆分票务表,减少单表数据量
5. 典型问题与解决方案
5.1 并发订票问题
当多个用户同时预订同一航次的最后几张票时,可能出现超卖。解决方案对比:
| 方案 | 实现方式 | 优点 | 缺点 |
|---|---|---|---|
| 悲观锁 | SELECT FOR UPDATE | 保证强一致性 | 并发性能差 |
| 乐观锁 | 版本号控制 | 并发性好 | 需要重试机制 |
| Redis原子操作 | DECR + WATCH | 性能最佳 | 需要维护缓存一致性 |
推荐实现(乐观锁方案):
java复制public boolean bookTicketWithOptimisticLock(Long scheduleId) {
int retryTimes = 3;
while(retryTimes-- > 0){
ShipSchedule schedule = scheduleMapper.selectById(scheduleId);
if(schedule.getAvailableSeats() <= 0){
return false;
}
int originalSeats = schedule.getAvailableSeats();
schedule.setAvailableSeats(originalSeats - 1);
int updated = scheduleMapper.update(
schedule,
Wrappers.<ShipSchedule>lambdaUpdate()
.eq(ShipSchedule::getId, scheduleId)
.eq(ShipSchedule::getAvailableSeats, originalSeats)
);
if(updated > 0){
return true;
}
}
throw new BusinessException("订票失败,请重试");
}
5.2 定时任务设计
系统需要处理以下定时任务:
- 自动取消未支付订单(30分钟未支付)
- 每日凌晨生成船次报表
- 出发前2小时停止网上售票
使用Spring Boot的@Scheduled实现:
java复制@Component
public class TicketJob {
private static final Logger logger = LoggerFactory.getLogger(TicketJob.class);
@Scheduled(cron = "0 */5 * * * ?") // 每5分钟执行一次
public void cancelUnpaidTickets() {
logger.info("开始扫描未支付订单...");
LocalDateTime deadline = LocalDateTime.now().minusMinutes(30);
List<Ticket> unpaidTickets = ticketMapper.selectList(
Wrappers.<Ticket>lambdaQuery()
.eq(Ticket::getStatus, "UNPAID")
.lt(Ticket::getCreateTime, deadline)
);
unpaidTickets.forEach(ticket -> {
ticket.setStatus("CANCELLED");
ticketMapper.updateById(ticket);
seatService.increaseStock(ticket.getScheduleId());
});
}
}
6. 系统安全设计
6.1 接口安全防护
-
SQL注入防护:
- 使用MyBatis的#{}参数绑定
- 禁止拼接SQL语句
- 集成SQL防火墙插件
-
XSS防护:
- 前端使用Vue/React时自动转义
- 后端对用户输入进行HTML标签过滤
-
CSRF防护:
- 启用Spring Security的CSRF保护
- 敏感操作要求二次验证
6.2 敏感数据保护
- 身份证号等敏感信息加密存储:
java复制public class IdCardEncryptor {
private static final String KEY = "secureKey123";
public static String encrypt(String idCard) {
// AES加密实现
return "encrypted_" + idCard;
}
public static String decrypt(String encrypted) {
// AES解密实现
return encrypted.substring(10);
}
}
- 数据库连接池配置SSL加密:
properties复制spring.datasource.hikari.data-source-properties=useSSL=true
spring.datasource.hikari.data-source-properties=requireSSL=true
7. 扩展功能建议
对于希望进一步提升项目的同学,可以考虑实现:
-
微服务化改造:
- 拆分为船次服务、订单服务、支付服务
- 使用Spring Cloud Alibaba套件
- 引入Nacos服务发现
-
数据分析看板:
- 集成ECharts实现可视化
- 关键指标:上座率、航线热度、退票率
-
移动端适配:
- 开发微信小程序版本
- 采用uni-app跨平台方案
-
智能推荐:
- 基于用户历史订单推荐相似航线
- 使用协同过滤算法
实际开发中遇到的典型问题:在实现舱位库存管理时,最初采用简单的UPDATE语句直接扣减库存,在压力测试时出现了超卖现象。后来通过引入Redis分布式锁+数据库乐观锁的双重校验机制,最终在保证性能的同时解决了并发问题。这个案例告诉我们,在高并发场景下,不能依赖单一的技术方案,需要根据业务特点采用组合策略。
