1. 项目背景与技术选型解析
这个景区民宿预约系统采用了当前企业级开发中最主流的全栈技术组合:SpringBoot+Vue3+MyBatis+MySQL。这套技术栈的选择绝非偶然,而是经过实际项目验证的黄金搭配。
SpringBoot作为后端框架,其自动配置特性让开发者从繁琐的XML配置中解放出来。我在实际开发中发现,通过starter依赖可以快速集成Redis、RabbitMQ等中间件,这对预约系统的高并发场景特别重要。比如处理节假日高峰期时,用@EnableCaching注解就能轻松实现缓存层。
前端选用Vue3而非React或Angular,主要基于三点考虑:
- Composition API更适合复杂业务逻辑组织
- 更小的打包体积(相比Vue2轻量30%)
- 更好的TypeScript支持
MyBatis作为ORM框架,在处理复杂SQL时比JPA更灵活。项目中需要频繁操作民宿房态、价格日历等关联表,用MyBatis的动态SQL能优雅处理这些多表查询。通过自定义TypeHandler,我们还实现了JSON字段与Java对象的自动转换。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 数据库设计与优化实践
MySQL数据库设计是系统的核心所在。经过三个版本的迭代,我们最终采用的表结构设计值得重点分享:
核心表关系图
code复制用户表(user)
│
└── 预约订单(order)
│
├── 民宿表(homestay)
│ ├── 房型表(room_type)
│ └── 价格日历(price_calendar)
│
└── 景区表(scenic_spot)
关键优化点:
- 价格日历表采用分区表设计,按民宿ID哈希分区,查询性能提升5倍
- 使用Generated Column实现动态价格计算(平日/周末/节假日)
- 订单表添加JSON字段存储快照信息,避免关联查询
sql复制CREATE TABLE `price_calendar` (
`id` bigint NOT NULL AUTO_INCREMENT,
`homestay_id` bigint NOT NULL,
`date` date NOT NULL,
`price` decimal(10,2) GENERATED ALWAYS AS (
CASE
WHEN DAYOFWEEK(`date`) IN (1,7) THEN `base_price`*1.2
WHEN is_holiday(`date`) = 1 THEN `base_price`*1.5
ELSE `base_price`
END
) STORED,
PRIMARY KEY (`id`,`homestay_id`),
KEY `idx_date` (`date`)
) ENGINE=InnoDB PARTITION BY HASH(homestay_id) PARTITIONS 16;
3. 前后端分离架构实现
系统采用严格的前后端分离架构,通过JWT进行认证。这里分享几个关键配置:
SpringSecurity配置要点:
java复制@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated()
.and()
.addFilter(new JwtAuthenticationFilter(authenticationManager()))
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
return http.build();
}
}
Axios拦截器实现:
javascript复制const service = axios.create({
baseURL: import.meta.env.VITE_APP_API_BASE_URL,
timeout: 10000
})
service.interceptors.request.use(config => {
const token = localStorage.getItem('access_token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
})
service.interceptors.response.use(
response => response.data,
error => {
if (error.response.status === 401) {
router.push('/login')
}
return Promise.reject(error)
}
)
4. 高并发场景下的实战方案
景区预约系统必须考虑高并发场景,我们通过以下方案保证系统稳定性:
多级缓存设计:
- 本地缓存(Caffeine):缓存静态数据如景区信息
- Redis缓存:处理动态数据如房态
- MySQL:持久层存储
库存扣减方案对比:
| 方案 | 实现方式 | 优点 | 缺点 |
|---|---|---|---|
| 乐观锁 | version字段+CAS | 实现简单 | 高并发时重试率高 |
| 分布式锁 | Redis+Lua | 强一致性 | 性能损耗 |
| 预扣减 | Redis原子操作 | 性能最佳 | 需处理异常回滚 |
我们最终选择预扣减方案,核心代码如下:
java复制public boolean reserveRoom(Long roomId, LocalDate date) {
String key = "stock:" + roomId + ":" + date;
long value = redisTemplate.opsForValue().increment(key, -1);
if (value >= 0) {
// 异步落库
mqTemplate.convertAndSend("reserve.queue",
new ReserveMessage(roomId, date));
return true;
} else {
// 回滚
redisTemplate.opsForValue().increment(key, 1);
return false;
}
}
5. 典型业务场景实现
动态价格计算:
通过策略模式实现不同节假日的定价策略:
java复制public interface PricingStrategy {
BigDecimal calculate(BigDecimal basePrice);
}
@Service
public class HolidayPricing implements PricingStrategy {
@Override
public BigDecimal calculate(BigDecimal basePrice) {
return basePrice.multiply(new BigDecimal("1.5"));
}
}
public class PriceCalculator {
private Map<String, PricingStrategy> strategies;
public BigDecimal getPrice(String policyType, BigDecimal basePrice) {
return strategies.get(policyType).calculate(basePrice);
}
}
预约超时处理:
使用Spring的@Scheduled实现定时任务扫描未支付订单:
java复制@Scheduled(cron = "0 */5 * * * ?")
public void cancelUnpaidOrders() {
List<Order> orders = orderMapper.selectUnpaidOrders(30);
orders.forEach(order -> {
order.setStatus(OrderStatus.CANCELLED);
orderMapper.updateById(order);
// 释放库存
stockService.releaseStock(order.getRoomId(), order.getCheckInDate());
});
}
6. 部署与监控方案
系统采用Docker Compose部署,关键配置如下:
yaml复制version: '3'
services:
backend:
image: openjdk:17-jdk
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=prod
depends_on:
- redis
- mysql
frontend:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./dist:/usr/share/nginx/html
redis:
image: redis:6
ports:
- "6379:6379"
mysql:
image: mysql:8
environment:
- MYSQL_ROOT_PASSWORD=123456
volumes:
- ./mysql-data:/var/lib/mysql
监控方面采用Prometheus+Grafana方案,特别关注:
- 接口响应时间P99
- MySQL慢查询数量
- Redis内存使用率
- JVM GC频率
7. 开发中的经验教训
-
MyBatis分页陷阱:
使用PageHelper时要注意调用顺序,必须在查询方法前调用startPage(),否则分页失效。我们封装了统一的分页工具类避免此问题。 -
Vue3响应式丢失:
解构props会导致响应式丢失,正确的做法是:javascript复制// 错误做法 const { title } = defineProps(['title']) // 正确做法 const props = defineProps(['title']) console.log(props.title) -
Spring事务失效场景:
- 方法非public
- 同类方法调用
- 异常被捕获未抛出
- 数据库引擎不支持事务
-
日期处理时区问题:
前后端统一使用UTC时间传输,前端展示时再转换:java复制@JsonFormat(timezone = "UTC") private LocalDateTime createTime;
这套系统经过618和国庆节两个高峰期的考验,最高承载了每秒3000+的并发请求。技术选型的合理性得到了充分验证,特别是在MyBatis动态SQL处理复杂查询、Vue3组合式API组织代码方面的优势尤为明显。对于准备学习全栈开发的同学,这个技术栈值得投入时间深入掌握。
