1. 项目背景与核心需求
去年帮学弟调试毕业设计时,发现酒店管理系统这个选题每年都有大量学生选择,但真正能跑通全流程的不到三成。这个基于SSM框架的酒店预订系统,实际上涵盖了企业级应用开发的典型场景:多角色权限控制、复杂状态流转和前后端数据交互。传统酒店管理最大的痛点在于人工处理预订时,房态更新延迟导致超售,而纸质订单又难以追踪修改记录。这套系统通过三个创新设计解决了这些问题:
-
实时房态双向锁机制:在用户提交预订请求时,系统会先对房源记录加锁,避免并发操作导致的数据冲突。这个细节很多初学者会忽略,直接导致毕业答辩时演示出现"幽灵房源"。
-
操作日志全链路追踪:从预订、入住到退房,每个状态变更都会生成带时间戳的记录。我们在数据库设计中特别加入了operation_log表,用JSON格式存储变更前后的数据快照。
-
动态权限拦截器:不同于简单的角色判断,系统根据酒店实际业务流程,对前台人员的操作权限做了动态控制。例如退房操作需要关联该前台当班记录,防止越权操作。
2. 技术栈选型解析
2.1 为什么选择SSM框架
在2023年的技术环境下,SpringBoot+Vue确实是更时髦的选择。但作为教学项目,SSM(Spring+SpringMVC+MyBatis)组合有其独特优势:
-
分层结构清晰:MyBatis的XML映射文件强制学生理解SQL与Java对象的转换过程,这对夯实数据库操作基础很有帮助。我见过不少直接用JPA的学生,直到毕业都写不好复杂联表查询。
-
配置可见性强:手动配置DataSource和TransactionManager的过程,能让学生真正理解连接池和事务管理的原理。以下是核心配置片段:
xml复制<!-- 数据源配置 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="${jdbc.url}"/>
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="initialSize" value="5"/>
<property name="maxActive" value="20"/>
</bean>
<!-- 事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
2.2 数据库设计要点
酒店系统的核心在于状态管理,我们在MySQL中设计了几个关键表:
- room_info表:除了常规字段,特别加入了version字段实现乐观锁。当两个用户同时预订同一房间时,先提交的会成功,后提交的会因version不匹配而失败。
sql复制CREATE TABLE `room_info` (
`room_id` int(11) NOT NULL AUTO_INCREMENT,
`room_number` varchar(20) NOT NULL,
`room_type` varchar(50) NOT NULL,
`price` decimal(10,2) NOT NULL,
`status` tinyint(4) DEFAULT 0 COMMENT '0-空闲 1-已预订 2-已入住',
`version` int(11) DEFAULT 0,
PRIMARY KEY (`room_id`),
UNIQUE KEY `idx_room_number` (`room_number`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
- order_flow表:记录订单状态流转全过程,采用状态机模式设计。特别注意status字段的取值约束:
java复制public enum OrderStatus {
PENDING(0), // 待确认
CONFIRMED(1), // 已确认
CHECKED_IN(2), // 已入住
CANCELED(3), // 已取消
COMPLETED(4); // 已完成
private final int code;
// 构造方法和getter省略
}
3. 核心功能实现细节
3.1 预订业务流程实现
真正的难点不在于CRUD,而在于处理并发场景。下面是预订服务的核心代码逻辑:
java复制@Service
@Transactional
public class BookingServiceImpl implements BookingService {
@Autowired
private RoomInfoMapper roomInfoMapper;
@Autowired
private OrderMapper orderMapper;
@Override
public Result bookRoom(BookingDTO dto) {
// 1. 检查房态(带乐观锁)
RoomInfo room = roomInfoMapper.selectForUpdate(dto.getRoomId());
if (room == null || room.getStatus() != 0) {
return Result.error("房间不可预订");
}
// 2. 创建订单(状态为待确认)
Order order = new Order();
order.setUserId(dto.getUserId());
order.setRoomId(dto.getRoomId());
order.setStatus(OrderStatus.PENDING.getCode());
orderMapper.insert(order);
// 3. 更新房态
room.setStatus(1);
room.setVersion(room.getVersion() + 1);
int affected = roomInfoMapper.updateWithVersion(room);
if (affected == 0) {
throw new ConcurrentBookingException("预订冲突,请重试");
}
// 4. 记录操作日志
logService.recordLog(LogType.BOOKING, order.getOrderId());
return Result.success(order.getOrderId());
}
}
关键点:整个方法必须添加@Transactional注解保证原子性,updateWithVersion方法要在SQL中加上
where version=#{version}条件
3.2 权限控制方案
系统采用RBAC(基于角色的访问控制)模型,但增加了业务上下文判断:
java复制@RestController
@RequestMapping("/api/staff")
public class StaffController {
@PostMapping("/check-in")
@PreAuthorize("hasRole('FRONT_DESK')")
public Result checkIn(@RequestBody CheckInDTO dto,
@CurrentStaff Staff staff) {
// 检查当班记录
if (!shiftService.isOnDuty(staff.getStaffId())) {
throw new ForbiddenException("非当班时间无法办理入住");
}
// 业务逻辑...
}
}
配合Spring Security的配置:
java复制@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/api/admin/**").hasRole("ADMIN")
.antMatchers("/api/staff/**").hasRole("FRONT_DESK")
.antMatchers("/api/user/**").hasRole("USER")
.anyRequest().authenticated()
.and()
.addFilter(new JwtAuthenticationFilter(authenticationManager()))
.addFilter(new JwtAuthorizationFilter(authenticationManager()));
}
}
4. 典型问题排查实录
4.1 订单状态不同步问题
现象:前台办理入住后,房态显示"已入住",但用户端仍显示"已预订"
排查过程:
- 检查数据库事务日志,确认update语句已执行
- 查看Redis缓存,发现room:123缓存未更新
- 追踪代码发现未添加@CacheEvict注解
解决方案:
java复制@Transactional
@CacheEvict(value = "rooms", key = "#roomId")
public void confirmCheckIn(Long orderId, Long roomId) {
// 业务逻辑
}
4.2 高并发下的超售问题
压测现象:使用JMeter模拟100并发预订时,出现5%的超售
原因分析:
- 虽然用了乐观锁,但selectForUpdate实现有误
- 原SQL缺少FOR UPDATE子句
修正方案:
xml复制<select id="selectForUpdate" resultType="RoomInfo">
SELECT * FROM room_info
WHERE room_id = #{roomId} AND status = 0
FOR UPDATE
</select>
5. 项目优化建议
- 引入Redis缓存:将房态信息和热门客房放入Redis,查询性能提升8倍。特别注意缓存一致性问题:
java复制@Cacheable(value = "rooms", key = "#roomId")
public RoomInfo getRoomDetail(Long roomId) {
return roomInfoMapper.selectById(roomId);
}
@CacheEvict(value = "rooms", key = "#room.roomId")
public void updateRoom(RoomInfo room) {
roomInfoMapper.updateById(room);
}
- 添加分布式锁:对于连锁酒店场景,需要用Redisson实现跨JVM锁:
java复制public Result bookRoom(BookingDTO dto) {
RLock lock = redissonClient.getLock("room_lock:" + dto.getRoomId());
try {
boolean locked = lock.tryLock(3, 10, TimeUnit.SECONDS);
if (locked) {
// 业务逻辑
}
} finally {
lock.unlock();
}
}
- 完善监控体系:接入Prometheus监控关键指标:
java复制@RestController
@RequestMapping("/api")
public class BookingController {
private final Counter bookingCounter;
public BookingController(MeterRegistry registry) {
bookingCounter = registry.counter("hotel.booking.requests");
}
@PostMapping("/book")
public Result book(@RequestBody BookingDTO dto) {
bookingCounter.increment();
// 业务逻辑
}
}
这个项目最值得借鉴的是其完整的业务流程设计,特别是状态流转的严谨性。建议学生在理解基础功能后,可以尝试扩展钟点房预订、连住优惠等实际业务场景,这对面试时的项目阐述会很有帮助。