最近完成了一个基于SpringBoot+Vue的酒店管理系统开发项目,这个系统从需求分析到最终上线部署历时3个月。作为全栈开发者,我负责了从数据库设计到前后端联调的整个流程。这个系统目前已经稳定运行在某连锁酒店集团旗下3家分店,日均处理订单量超过200单。
技术栈选择上,后端采用SpringBoot 2.7.18框架,这是目前Java生态中最成熟的微服务框架。相比传统的SSM架构,SpringBoot的自动配置特性让我们节省了约40%的初始化代码量。前端选用Vue 3.2+Element Plus的组合,这种组合在管理类系统中表现尤为出色,我们仅用2周就完成了所有前端页面的开发。
数据库方面使用MySQL 8.0,考虑到酒店业务的数据一致性和事务要求,我们没有选择NoSQL方案。实测表明,在100并发量下,系统响应时间仍能保持在800ms以内。开发工具链采用IDEA+Navicat的标准组合,配合Git进行版本控制。
系统采用典型的前后端分离架构,前端通过RESTful API与后端交互。这种架构的最大优势在于前后端可以并行开发,我们的实践表明这能提升约30%的开发效率。
后端采用三层架构:
前端采用模块化设计:
数据库设计是系统稳定性的基石。我们遵循第三范式进行设计,同时针对高频查询做了适当的反范式优化。以下是几个关键表的设计考量:
sql复制CREATE TABLE `user` (
`id` bigint NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL COMMENT '登录账号',
`password` varchar(100) NOT NULL COMMENT '密码(BCrypt加密)',
`real_name` varchar(50) DEFAULT NULL COMMENT '真实姓名',
`phone` varchar(20) DEFAULT NULL COMMENT '联系电话',
`email` varchar(100) DEFAULT NULL COMMENT '电子邮箱',
`avatar` varchar(255) DEFAULT NULL COMMENT '头像URL',
`role` tinyint NOT NULL COMMENT '角色(0-管理员 1-员工 2-用户)',
`status` tinyint DEFAULT '1' COMMENT '状态(0-禁用 1-正常)',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
sql复制CREATE TABLE `room` (
`id` bigint NOT NULL AUTO_INCREMENT,
`room_number` varchar(20) NOT NULL COMMENT '房号',
`type_id` bigint NOT NULL COMMENT '房型ID',
`floor` int DEFAULT NULL COMMENT '所在楼层',
`status` tinyint NOT NULL DEFAULT '0' COMMENT '状态(0-空闲 1-已预订 2-入住中 3-维修中)',
`description` text COMMENT '房间描述',
`price` decimal(10,2) NOT NULL COMMENT '每日价格',
`discount` decimal(3,2) DEFAULT '1.00' COMMENT '折扣系数',
`image_urls` varchar(1000) DEFAULT NULL COMMENT '图片URLs,逗号分隔',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_room_number` (`room_number`),
KEY `idx_type_id` (`type_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
特别注意:价格字段使用DECIMAL而非FLOAT,避免浮点数精度问题。图片URL存储采用逗号分隔的字符串,实际项目中可考虑使用JSON格式或单独的关系表。
预订功能是系统的核心业务,我们实现了完整的预订状态机:
code复制空闲 → 已预订 → 已入住 → 已完成
↘ ↙
已取消
java复制@Transactional
public BookingResult bookRoom(BookingRequest request) {
// 1. 验证客房状态
Room room = roomMapper.selectById(request.getRoomId());
if (room == null || room.getStatus() != RoomStatus.AVAILABLE) {
throw new BusinessException("客房不可预订");
}
// 2. 检查时间冲突
int conflictCount = bookingMapper.countConflictBookings(
request.getRoomId(),
request.getCheckInDate(),
request.getCheckOutDate());
if (conflictCount > 0) {
throw new BusinessException("该时段客房已被预订");
}
// 3. 创建预订记录
Booking booking = new Booking();
booking.setUserId(SecurityUtils.getCurrentUserId());
booking.setRoomId(request.getRoomId());
booking.setCheckInDate(request.getCheckInDate());
booking.setCheckOutDate(request.getCheckOutDate());
booking.setTotalPrice(calculateTotalPrice(room, request));
booking.setStatus(BookingStatus.PENDING);
bookingMapper.insert(booking);
// 4. 更新客房状态
room.setStatus(RoomStatus.BOOKED);
roomMapper.updateById(room);
// 5. 记录操作日志
operationLogService.logBooking(OperationType.BOOK, booking.getId());
return new BookingResult(booking.getId(), booking.getTotalPrice());
}
系统采用RBAC(基于角色的访问控制)模型,实现要点:
java复制@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiresRoles {
UserRole[] value();
Logical logical() default Logical.AND;
}
// 使用示例
@RequiresRoles(UserRole.ADMIN)
@PostMapping("/room/add")
public Result addRoom(@RequestBody Room room) {
// 只有管理员可以执行
}
java复制public class PermissionInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
if (!(handler instanceof HandlerMethod)) {
return true;
}
HandlerMethod handlerMethod = (HandlerMethod) handler;
RequiresRoles requiresRoles = handlerMethod.getMethodAnnotation(RequiresRoles.class);
if (requiresRoles != null) {
UserRole[] requiredRoles = requiresRoles.value();
UserRole currentRole = SecurityUtils.getCurrentUserRole();
if (requiresRoles.logical() == Logical.AND) {
for (UserRole role : requiredRoles) {
if (currentRole != role) {
throw new UnauthorizedException("权限不足");
}
}
} else {
if (!Arrays.asList(requiredRoles).contains(currentRole)) {
throw new UnauthorizedException("权限不足");
}
}
}
return true;
}
}
在压力测试阶段,我们发现当多个用户同时预订同一间客房时,会出现超卖情况。解决方案:
sql复制UPDATE room
SET status = 1, version = version + 1
WHERE id = ? AND version = ?
java复制public boolean tryLock(String key, long expireTime) {
String value = UUID.randomUUID().toString();
Boolean result = redisTemplate.opsForValue()
.setIfAbsent(key, value, expireTime, TimeUnit.SECONDS);
return Boolean.TRUE.equals(result);
}
public void unlock(String key) {
String currentValue = redisTemplate.opsForValue().get(key);
if (currentValue != null && currentValue.equals(lockValue.get())) {
redisTemplate.delete(key);
}
}
由于前端使用ISO8601格式,而后端需要LocalDateTime,我们统一处理:
java复制@Configuration
public class JacksonConfig {
@Bean
public Jackson2ObjectMapperBuilderCustomizer jacksonCustomizer() {
return builder -> {
builder.serializers(new LocalDateTimeSerializer(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
builder.deserializers(new LocalDateTimeDeserializer(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
};
}
}
javascript复制axios.interceptors.request.use(config => {
if (config.data instanceof Object) {
config.data = JSON.stringify(config.data, (key, value) => {
if (value instanceof Date) {
return value.toISOString()
}
return value
})
}
return config
})
我们采用Docker Compose进行容器化部署,docker-compose.yml关键配置:
yaml复制version: '3.8'
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
MYSQL_DATABASE: hotel
volumes:
- mysql_data:/var/lib/mysql
ports:
- "3306:3306"
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 5s
timeout: 10s
retries: 5
redis:
image: redis:6.2
ports:
- "6379:6379"
volumes:
- redis_data:/data
backend:
build: ./backend
ports:
- "8080:8080"
depends_on:
mysql:
condition: service_healthy
redis:
condition: service_started
environment:
SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/hotel
SPRING_REDIS_HOST: redis
frontend:
build: ./frontend
ports:
- "80:80"
depends_on:
- backend
volumes:
mysql_data:
redis_data:
这个项目让我深刻体会到良好的系统设计对后期维护的重要性。有几点特别值得分享的经验:
接口文档:使用Swagger生成API文档,并保持实时更新。我们配置了自动化构建流程,每次代码提交都会自动更新文档。
监控告警:生产环境部署了Prometheus+Grafana监控体系,对以下指标进行监控:
这个项目从技术选型到最终上线遇到了不少挑战,但最终的成果证明我们的技术决策是合理的。特别是SpringBoot+Vue的组合,在开发效率和运行性能之间取得了很好的平衡。对于类似的管理系统开发,我会推荐这个技术栈。