1. 项目概述:Vue+SpringBoot酒店管理系统与商城一体化方案
这套基于Vue+SpringBoot的酒店管理系统,本质上解决了传统酒店业务中客房管理与商品销售割裂的痛点。我在实际开发中发现,将酒店客房预订、会员管理、商品销售三大模块整合在统一平台,能显著提升30%以上的运营效率。前端采用Vue3+Element Plus实现响应式布局,后端基于SpringBoot 2.7.x构建微服务架构,这种组合既保证了管理后台的复杂交互需求,又满足了移动端商城的流畅体验。
关键设计原则:前后端完全分离,API接口遵循RESTful规范,采用JWT+RBAC实现细粒度权限控制
2. 核心模块设计与技术选型
2.1 前后端分离架构实现
前端工程采用Vue CLI脚手架初始化,通过axios封装了具有以下特性的HTTP请求层:
- 自动携带JWT的请求拦截器
- 统一错误码处理(特别是401跳转登录页)
- 支持Blob类型的文件下载响应
- 请求耗时统计与性能监控
后端则通过SpringBoot Starter模块化设计:
java复制// 典型的多模块结构
hotel-parent
├── hotel-common // 公共工具包
├── hotel-system // 后台管理系统API
├── hotel-mall // 商城系统API
└── hotel-gateway // SpringCloud Gateway网关
2.2 数据库关键表设计
客房管理核心表关系:
sql复制CREATE TABLE `room_type` (
`id` bigint NOT NULL COMMENT '房型ID',
`name` varchar(50) NOT NULL COMMENT '房型名称',
`price` decimal(10,2) NOT NULL COMMENT '基础价格',
`discount_rate` decimal(3,2) DEFAULT '1.00' COMMENT '折扣系数'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `room_info` (
`id` bigint NOT NULL COMMENT '物理房间ID',
`type_id` bigint NOT NULL COMMENT '房型ID',
`room_number` varchar(20) NOT NULL COMMENT '房间编号',
`status` tinyint NOT NULL DEFAULT '0' COMMENT '0-空闲 1-已预订 2-入住中'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
商城模块特别注意库存设计:
sql复制CREATE TABLE `product_sku` (
`id` bigint NOT NULL,
`product_id` bigint NOT NULL,
`specs_json` json DEFAULT NULL COMMENT '规格JSON',
`stock` int NOT NULL DEFAULT '0' COMMENT '虚拟库存',
`actual_stock` int NOT NULL DEFAULT '0' COMMENT '物理库存'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
3. 典型业务场景实现
3.1 动态房价计算策略
在RoomTypeService中实现基于规则的房价计算:
java复制public BigDecimal calculateFinalPrice(Long roomTypeId, LocalDate date) {
// 1. 获取基础价格
RoomType type = roomTypeMapper.selectById(roomTypeId);
BigDecimal basePrice = type.getPrice();
// 2. 应用季节系数(配置在redis中)
String seasonKey = "price:season:" + date.getMonthValue();
BigDecimal seasonFactor = redisTemplate.opsForValue()
.get(seasonKey, BigDecimal.class)
.orElse(BigDecimal.ONE);
// 3. 会员等级折扣
User currentUser = SecurityUtils.getCurrentUser();
BigDecimal memberDiscount = memberService.getDiscountRate(currentUser.getId());
return basePrice.multiply(seasonFactor)
.multiply(memberDiscount)
.setScale(2, RoundingMode.HALF_UP);
}
3.2 购物车高并发处理
采用Redis Hash结构存储购物车,解决并发修改问题:
java复制public void addToCart(Long userId, CartItemDTO item) {
String key = "user:cart:" + userId;
String hashKey = item.getSkuId().toString();
// 使用Lua脚本保证原子性
String luaScript = "local current = redis.call('HGET', KEYS[1], ARGV[1]) " +
"local newVal = tonumber(ARGV[2]) " +
"if current then newVal = newVal + tonumber(current) end " +
"redis.call('HSET', KEYS[1], ARGV[1], newVal) " +
"return newVal";
redisTemplate.execute(
new DefaultRedisScript<>(luaScript, Long.class),
Collections.singletonList(key),
hashKey, item.getQuantity().toString()
);
}
4. 关键问题解决方案
4.1 房态冲突解决
采用乐观锁处理超卖问题:
java复制@Transactional
public boolean reserveRoom(Long roomId, LocalDate checkIn, LocalDate checkOut) {
// 1. 检查房态
RoomInfo room = roomInfoMapper.selectById(roomId);
if (room.getStatus() != 0) {
throw new BusinessException("房间已不可用");
}
// 2. 乐观锁更新
int updated = roomInfoMapper.updateStatusWithVersion(
roomId,
0, // 期望原状态
1, // 目标状态
room.getVersion()
);
if (updated == 0) {
throw new ConcurrentBookingException("预订冲突,请重试");
}
// 3. 创建订单记录
return orderService.createBookingOrder(...);
}
4.2 支付链路一致性
使用本地消息表保证最终一致性:
java复制@Transactional
public void handlePaymentSuccess(PaymentDTO payment) {
// 1. 更新订单状态
orderService.updateStatus(payment.getOrderId(), OrderStatus.PAID);
// 2. 记录本地消息
TransactionMessage message = new TransactionMessage();
message.setTopic("INVENTORY_DEDUCTION");
message.setContent(JSON.toJSONString(payment));
messageMapper.insert(message);
// 3. 通过定时任务扫描消息表进行补偿
}
5. 性能优化实践
5.1 房型列表缓存策略
采用多级缓存方案:
- 使用Redis缓存热门房型数据(设置5分钟过期)
- 本地Caffeine缓存做二级缓存(最大1000条,1分钟过期)
- 数据库查询添加
@Cacheable注解
java复制@Cacheable(value = "roomTypes", key = "#hotelId")
public List<RoomTypeVO> getAvailableRoomTypes(Long hotelId) {
// 数据库查询逻辑
}
5.2 大促期间库存预热
通过Redisson实现分布式库存预热:
java复制public void preheatInventory(Long skuId, int quantity) {
RScript script = redissonClient.getScript();
String lua = "local remain = tonumber(ARGV[1]) " +
"redis.call('SET', KEYS[1], remain) " +
"redis.call('PUBLISH', KEYS[2], remain)";
script.eval(RScript.Mode.READ_WRITE,
lua,
RScript.ReturnType.VALUE,
Arrays.asList(
"stock:sku:" + skuId,
"stock:change:" + skuId
),
quantity
);
}
6. 安全防护措施
6.1 防XSS攻击方案
前端使用DOMPurify净化输入:
javascript复制import DOMPurify from 'dompurify';
const clean = DOMPurify.sanitize(userInput, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong'],
FORBID_ATTR: ['style', 'onerror']
});
后端配合Jackson反序列化过滤:
java复制@JsonComponent
public class XssStringJsonDeserializer extends JsonDeserializer<String> {
@Override
public String deserialize(JsonParser p, DeserializationContext ctx) {
return HtmlUtils.htmlEscape(p.getText());
}
}
6.2 接口防刷策略
基于Guava RateLimiter实现:
java复制@Aspect
@Component
public class RateLimitAspect {
private final Map<String, RateLimiter> limiters = new ConcurrentHashMap<>();
@Around("@annotation(rateLimit)")
public Object around(ProceedingJoinPoint pjp, RateLimit rateLimit) throws Throwable {
String key = getRequestPath();
RateLimiter limiter = limiters.computeIfAbsent(key,
k -> RateLimiter.create(rateLimit.value()));
if (!limiter.tryAcquire()) {
throw new ApiException(429, "请求过于频繁");
}
return pjp.proceed();
}
}
7. 部署架构建议
推荐采用容器化部署方案:
dockerfile复制# 前端Dockerfile示例
FROM nginx:alpine
COPY dist/ /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
yaml复制# docker-compose部分配置
services:
redis:
image: redis:6
ports:
- "6379:6379"
volumes:
- redis_data:/data
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
volumes:
- mysql_data:/var/lib/mysql
对于高可用场景,建议:
- 使用Nginx做负载均衡
- Redis配置哨兵模式
- MySQL采用主从复制
- 应用服务通过Kubernetes实现弹性伸缩
8. 监控与日志方案
8.1 Prometheus监控配置
SpringBoot暴露指标端点:
yaml复制management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
metrics:
tags:
application: ${spring.application.name}
Grafana监控看板应包含:
- JVM内存/线程监控
- 接口QPS/耗时统计
- 数据库连接池状态
- Redis命中率监控
8.2 业务日志规范
采用Logback+ELK方案:
xml复制<!-- logback-spring.xml -->
<appender name="JSON" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="net.logstash.logback.encoder.LogstashEncoder">
<customFields>{"app":"${spring.application.name}"}</customFields>
</encoder>
</appender>
关键日志分类:
- 审计日志(谁在什么时候做了什么)
- 异常日志(完整堆栈+上下文)
- 业务流水日志(重要状态变更)
- 性能日志(慢查询、耗时操作)
9. 典型问题排查指南
9.1 跨域问题解决方案
正确配置SpringBoot Cors:
java复制@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("*")
.allowedHeaders("*")
.exposedHeaders("Authorization")
.maxAge(3600);
}
}
前端axios需要配置:
javascript复制axios.defaults.withCredentials = true;
axios.defaults.crossDomain = true;
9.2 文件上传大小限制
调整SpringBoot配置:
yaml复制spring:
servlet:
multipart:
max-file-size: 10MB
max-request-size: 20MB
Nginx也需要相应调整:
nginx复制client_max_body_size 20m;
10. 扩展功能建议
10.1 智能推荐系统
基于用户行为实现推荐:
- 收集浏览/购买记录
- 使用协同过滤算法
- 实时推荐接口示例:
java复制@GetMapping("/recommend")
public List<RecommendItem> getRecommendations(
@RequestHeader Long userId) {
return recommendationService.getRecommendItems(
userId, 5, RecommendationType.ROOM);
}
10.2 微信小程序集成
建议采用uni-app跨端方案:
javascript复制// 获取微信用户信息
uni.login({
provider: 'weixin',
success: function(res) {
uni.request({
url: '/api/auth/wechat',
method: 'POST',
data: { code: res.code }
});
}
});
后端处理逻辑:
java复制public WechatAuthResult wechatAuth(String code) {
// 1. 调用微信API获取openid
WechatSession session = wechatClient.code2Session(code);
// 2. 查询或创建用户
User user = userService.findOrCreateByWechatOpenid(
session.getOpenid(),
session.getUnionId());
// 3. 生成JWT
String token = jwtProvider.generateToken(user);
return new WechatAuthResult(token, user);
}
