1. 项目概述
最近接手了一个公园管理系统的开发项目,用SpringBoot+Vue实现了一套完整的公园场地预约解决方案。这个系统主要解决三个核心问题:场地预约混乱、设施维护不及时、游客数据统计困难。作为城市公园数字化改造的一部分,这套系统已经在多个试点公园投入使用,反馈相当不错。
从技术架构来看,后端采用SpringBoot 2.7 + MyBatisPlus的组合,前端用Vue3 + Element Plus,数据库是MySQL 8.0。整个开发周期约3个月,期间踩过不少坑,特别是在高并发预约场景下的锁机制设计和设施状态实时同步这两个环节。下面我就把这套系统的设计思路和实现细节做个完整分享。
2. 技术架构设计
2.1 后端技术选型
选择SpringBoot主要基于三个考量:
- 快速启动:内嵌Tomcat省去服务器配置,通过spring-boot-starter-web一个依赖就搞定Web容器
- 自动配置:比如数据库连接池根据classpath自动选择HikariCP
- 生态丰富:整合MyBatisPlus、Redis、Quartz等只需简单配置
核心依赖配置示例:
xml复制<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
2.2 前端技术栈
Vue3的组合式API更适合复杂业务场景:
- 使用Pinia替代Vuex做状态管理
- Element Plus提供丰富的UI组件
- Axios封装了带Token验证的HTTP客户端
前端工程结构:
code复制src/
├── api/ # 接口定义
├── assets/ # 静态资源
├── components/ # 公共组件
├── router/ # 路由配置
├── stores/ # Pinia状态
└── views/ # 页面组件
2.3 数据库设计
主要表结构设计要点:
- 场地表:包含状态字段(0-可预约 1-已预约 2-维护中)
- 预约记录表:使用组合索引(场地ID+日期)
- 设施表:记录维护历史和当前状态
- 游客统计表:按小时/日/月多粒度存储
sql复制CREATE TABLE `park_space` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL COMMENT '场地名称',
`type` tinyint NOT NULL COMMENT '1-篮球场 2-网球场...',
`status` tinyint DEFAULT '0' COMMENT '0-可用 1-预约中 2-维护',
`maintain_info` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
3. 核心功能实现
3.1 预约业务流程
采用二级锁机制解决并发问题:
- 第一层:Redis分布式锁(防止超卖)
- 第二层:数据库乐观锁(保证最终一致性)
核心代码逻辑:
java复制@Transactional
public BookingResult bookSpace(Long spaceId, LocalDate date, Long userId) {
// 1. Redis加锁
String lockKey = "lock:space:" + spaceId + ":" + date;
boolean locked = redisTemplate.opsForValue()
.setIfAbsent(lockKey, "1", 30, TimeUnit.SECONDS);
if (!locked) {
return BookingResult.fail("当前预约人数过多,请稍后重试");
}
try {
// 2. 查询场地状态
ParkSpace space = spaceMapper.selectById(spaceId);
if (space.getStatus() != 0) {
return BookingResult.fail("该场地不可预约");
}
// 3. 创建预约记录
BookingRecord record = new BookingRecord();
record.setSpaceId(spaceId);
record.setUserId(userId);
record.setBookingDate(date);
recordMapper.insert(record);
// 4. 更新场地状态(乐观锁)
int updated = spaceMapper.updateStatus(spaceId, 0, 1);
if (updated == 0) {
throw new RuntimeException("场地状态变更冲突");
}
return BookingResult.success(record.getId());
} finally {
// 释放锁
redisTemplate.delete(lockKey);
}
}
3.2 设施维护模块
实现状态机模式管理设施生命周期:
code复制待检査 -> 正常
正常 -> 待维修 (当上报故障时)
待维修 -> 维修中 (分配工单后)
维修中 -> 正常 (维修完成后)
维护工单表设计:
sql复制CREATE TABLE `maintenance_order` (
`id` bigint NOT NULL AUTO_INCREMENT,
`facility_id` bigint NOT NULL,
`report_time` datetime NOT NULL,
`handler_id` bigint DEFAULT NULL,
`status` tinyint DEFAULT '0' COMMENT '0-待处理 1-处理中 2-已完成',
`complete_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_facility` (`facility_id`)
);
3.3 游客统计实现
采用多级缓存策略:
- 实时数据:Redis HyperLogLog统计UV
- 小时级:Redis定时持久化到MySQL
- 天/月级:MySQL定时聚合
统计服务核心逻辑:
java复制// 实时UV统计
public void recordVisitor(Long parkId, String userId) {
String key = "stats:uv:" + parkId + ":" + LocalDate.now();
redisTemplate.opsForHyperLogLog().add(key, userId);
}
// 定时任务(每小时执行)
@Scheduled(cron = "0 0 * * * ?")
public void syncVisitorStats() {
LocalDate today = LocalDate.now();
int hour = LocalTime.now().getHour();
parkMapper.listAll().forEach(park -> {
String key = "stats:uv:" + park.getId() + ":" + today;
Long uv = redisTemplate.opsForHyperLogLog().size(key);
VisitorStats stats = new VisitorStats();
stats.setParkId(park.getId());
stats.setStatsDate(today);
stats.setHour(hour);
stats.setUv(uv);
statsMapper.insert(stats);
});
}
4. 系统安全设计
4.1 权限控制方案
采用RBAC模型实现:
- 角色:管理员、维护人员、普通用户
- 权限:通过注解控制接口访问
权限校验拦截器:
java复制@Around("@annotation(requiresPermission)")
public Object checkPermission(ProceedingJoinPoint joinPoint,
RequiresPermission requiresPermission) {
String permission = requiresPermission.value();
User user = getCurrentUser();
if (!user.getPermissions().contains(permission)) {
throw new ApiException(403, "无操作权限");
}
return joinPoint.proceed();
}
4.2 Token认证机制
JWT Token设计要点:
- 有效期为2小时
- 刷新令牌机制
- 黑名单管理(用于主动注销)
Token生成逻辑:
java复制public String generateToken(User user) {
Map<String, Object> claims = new HashMap<>();
claims.put("userId", user.getId());
claims.put("role", user.getRole());
return Jwts.builder()
.setClaims(claims)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + 7200000))
.signWith(SignatureAlgorithm.HS256, secretKey)
.compact();
}
5. 性能优化实践
5.1 缓存策略
三级缓存架构:
- 本地缓存(Caffeine):高频访问的场地信息
- Redis缓存:预约状态等热数据
- MySQL:全量数据存储
缓存配置示例:
java复制@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
CaffeineCacheManager manager = new CaffeineCacheManager();
manager.setCaffeine(Caffeine.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES)
.maximumSize(1000));
return manager;
}
}
5.2 数据库优化
关键优化措施:
- 索引优化:为所有查询条件建立合适索引
- 分表策略:预约记录按月分表
- 读写分离:使用Sharding-JDBC实现
分表示例配置:
yaml复制spring:
shardingsphere:
datasource:
names: ds0,ds1
sharding:
tables:
booking_record:
actual-data-nodes: ds$->{0..1}.booking_record_$->{2023..2024}0$->{1..9}
table-strategy:
standard:
precise-algorithm-class-name: com.example.sharding.MonthShardingAlgorithm
6. 测试与部署
6.1 压力测试
使用JMeter模拟高并发场景:
- 1000并发用户持续5分钟
- 预约接口平均响应时间<500ms
- 错误率<0.1%
测试关键指标:
code复制预约成功率:99.8%
平均响应时间:342ms
最大TPS:1250
6.2 容器化部署
Docker Compose编排方案:
yaml复制version: '3'
services:
app:
image: park-system:1.0
ports:
- "8080:8080"
depends_on:
- redis
- mysql
redis:
image: redis:6
ports:
- "6379:6379"
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: root
ports:
- "3306:3306"
7. 踩坑经验
7.1 分布式事务问题
在初期版本中,Redis锁和数据库更新存在不一致问题。解决方案:
- 引入Redisson的看门狗机制延长锁时间
- 添加补偿任务定时检查不一致数据
7.2 缓存雪崩防护
预防措施:
- 缓存过期时间添加随机值
- 使用Hystrix做熔断降级
- 热点数据永不过期
7.3 前端性能优化
Vue项目优化点:
- 路由懒加载
- 组件按需引入
- 开启Gzip压缩
- 使用Webpack分包策略
8. 扩展方向
- 微信小程序接入:通过uni-app快速移植
- 智能推荐:基于用户历史预约推荐场地
- 物联网集成:对接智能门锁等硬件设备
- 数据分析平台:使用Flink做实时数据分析
这个项目让我深刻体会到,一个好的管理系统不仅要功能完善,更需要考虑实际运营场景。比如公园管理员多是中老年人,所以我们在UI设计上特别加大了字体和操作按钮。另外预约高峰期的系统稳定性也是需要重点关注的。