1. 项目概述
作为一名有10年Java全栈开发经验的工程师,最近我完成了一个基于Spring Boot的校园台球厅管理系统。这个系统专为大学校园内的台球厅设计,旨在解决传统人工管理模式下效率低下、数据统计困难等问题。系统采用当前主流的技术栈,包括Spring Boot、Vue.js和MySQL,实现了从用户预约、设备管理到消费记录统计的全流程数字化管理。
在实际开发过程中,我发现校园娱乐场所的管理系统有几个独特的需求:首先,需要适应学生群体的使用习惯,界面要简洁直观;其次,要考虑校园场景下的特殊计费规则,比如学生优惠、时段定价等;最后,系统需要具备良好的扩展性,以便未来增加新的功能模块。
2. 系统架构设计
2.1 技术选型与考量
在技术选型阶段,我主要考虑了以下几个因素:
- 开发效率:校园项目通常周期短、预算有限,需要快速交付
- 维护成本:系统需要易于维护,适合学生管理员操作
- 性能要求:校园场景并发量适中,但对响应速度有要求
基于这些考虑,最终选择了以下技术栈:
- 后端:Spring Boot 2.7 + MyBatis Plus
- 前端:Vue 3 + Element Plus
- 数据库:MySQL 8.0
- 缓存:Redis(用于高频访问数据)
- 安全框架:Spring Security
技术选型心得:Spring Boot的自动配置特性大大减少了初始配置工作,MyBatis Plus的代码生成器可以快速生成基础CRUD代码,这两个组合让开发效率提升了至少30%。
2.2 系统架构图
系统采用经典的三层架构设计:
code复制┌───────────────────────────────────────┐
│ 客户端层 │
│ ┌───────────┐ ┌───────────┐ │
│ │ Web浏览器 │ │ 移动端APP │ │
│ └───────────┘ └───────────┘ │
└───────────────────┬───────────────────┘
│ HTTP/HTTPS
▼
┌───────────────────────────────────────┐
│ 表现层 │
│ ┌───────────┐ ┌───────────┐ │
│ │ Controller │ │ API网关 │ │
│ └───────────┘ └───────────┘ │
└───────────────────┬───────────────────┘
│ 服务调用
▼
┌───────────────────────────────────────┐
│ 业务逻辑层 │
│ ┌───────────┐ ┌───────────┐ │
│ │ Service │ │ Utils │ │
│ └───────────┘ └───────────┘ │
└───────────────────┬───────────────────┘
│ 数据访问
▼
┌───────────────────────────────────────┐
│ 数据访问层 │
│ ┌───────────┐ ┌───────────┐ │
│ │ DAO │ │ Redis │ │
│ └───────────┘ └───────────┘ │
└───────────────────┬───────────────────┘
│ JDBC/ORM
▼
┌───────┐
│ MySQL │
└───────┘
2.3 数据库设计
数据库设计遵循第三范式,主要包含以下核心表:
- 用户表(user):存储系统用户信息
- 台球桌表(pool_table):记录台球厅设备信息
- 预约记录表(reservation):管理用户预约信息
- 消费记录表(consumption):记录消费明细
- 价格规则表(price_rule):定义不同时段的定价策略
sql复制CREATE TABLE `user` (
`id` int NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL,
`password` varchar(100) NOT NULL,
`real_name` varchar(50) DEFAULT NULL,
`student_id` varchar(20) DEFAULT NULL,
`phone` varchar(20) DEFAULT NULL,
`role` enum('admin','student','staff') NOT NULL DEFAULT 'student',
`status` tinyint NOT NULL DEFAULT '1',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_username` (`username`),
UNIQUE KEY `idx_student_id` (`student_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `pool_table` (
`id` int NOT NULL AUTO_INCREMENT,
`table_no` varchar(10) NOT NULL,
`type` enum('standard','competition') NOT NULL DEFAULT 'standard',
`status` enum('available','maintenance','reserved') NOT NULL DEFAULT 'available',
`location` varchar(100) DEFAULT NULL,
`description` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_table_no` (`table_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
数据库设计技巧:为常用查询字段添加索引,如username和student_id;使用ENUM类型限定取值范围,避免脏数据;所有表都添加create_time字段便于后期数据分析。
3. 核心功能实现
3.1 用户认证与授权
系统采用基于角色的访问控制(RBAC)模型,用户分为三类角色:
- 管理员:拥有全部权限
- 工作人员:可以处理预约和消费记录
- 学生用户:只能进行预约和个人信息管理
认证流程采用JWT(JSON Web Token)方案:
- 用户登录成功后,后端生成JWT令牌返回给前端
- 前端将令牌存储在localStorage中
- 后续请求都在Authorization头中携带令牌
- 后端验证令牌有效性并解析用户角色
java复制// JWT工具类核心代码
public class JwtUtil {
private static final String SECRET_KEY = "your-secret-key";
private static final long EXPIRATION_TIME = 86400000; // 24小时
public static String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
claims.put("roles", userDetails.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toList()));
return Jwts.builder()
.setClaims(claims)
.setSubject(userDetails.getUsername())
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.signWith(SignatureAlgorithm.HS256, SECRET_KEY)
.compact();
}
public static Boolean validateToken(String token, UserDetails userDetails) {
final String username = extractUsername(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
}
3.2 台球桌预约系统
预约功能是系统的核心,主要流程如下:
- 用户查询可预约的台球桌
- 选择时间段并提交预约
- 系统检查时间冲突并确认预约
- 生成预约记录并更新台球桌状态
关键业务逻辑代码:
java复制@Service
@RequiredArgsConstructor
public class ReservationServiceImpl implements ReservationService {
private final ReservationMapper reservationMapper;
private final PoolTableMapper poolTableMapper;
@Transactional
@Override
public Reservation createReservation(ReservationDTO dto) {
// 检查台球桌是否存在且可用
PoolTable table = poolTableMapper.selectById(dto.getTableId());
if (table == null || !"available".equals(table.getStatus())) {
throw new BusinessException("台球桌不可用");
}
// 检查时间冲突
List<Reservation> conflicts = reservationMapper.findConflicts(
dto.getTableId(), dto.getStartTime(), dto.getEndTime());
if (!conflicts.isEmpty()) {
throw new BusinessException("该时间段已被预约");
}
// 创建预约记录
Reservation reservation = new Reservation();
BeanUtils.copyProperties(dto, reservation);
reservation.setStatus("reserved");
reservation.setCreateTime(LocalDateTime.now());
reservationMapper.insert(reservation);
// 更新台球桌状态
table.setStatus("reserved");
poolTableMapper.updateById(table);
return reservation;
}
}
3.3 动态定价策略
校园台球厅通常有复杂的定价规则,系统实现了灵活的定价策略:
- 时段定价:不同时间段价格不同(如白天/晚上)
- 会员折扣:学生证享受优惠
- 节假日特价:特殊日期特别定价
价格计算服务实现:
java复制@Service
public class PricingServiceImpl implements PricingService {
private final PriceRuleMapper priceRuleMapper;
@Override
public BigDecimal calculatePrice(LocalDateTime start, LocalDateTime end, boolean isStudent) {
// 获取适用价格规则
List<PriceRule> rules = priceRuleMapper.findApplicableRules(start.toLocalTime(), end.toLocalTime());
// 计算总时长(分钟)
long duration = Duration.between(start, end).toMinutes();
// 分段计算价格
BigDecimal total = BigDecimal.ZERO;
LocalDateTime current = start;
while (current.isBefore(end)) {
// 找到当前时间适用的规则
PriceRule applicableRule = findApplicableRule(rules, current.toLocalTime());
// 计算本段结束时间
LocalDateTime segmentEnd = applicableRule.getEndTime().atDate(current.toLocalDate());
if (segmentEnd.isAfter(end)) {
segmentEnd = end;
}
// 计算本段时长和价格
long segmentMinutes = Duration.between(current, segmentEnd).toMinutes();
BigDecimal segmentPrice = applicableRule.getPricePerHour()
.divide(BigDecimal.valueOf(60), 2, RoundingMode.HALF_UP)
.multiply(BigDecimal.valueOf(segmentMinutes));
// 应用学生折扣
if (isStudent && applicableRule.getStudentDiscount() != null) {
segmentPrice = segmentPrice.multiply(
BigDecimal.ONE.subtract(applicableRule.getStudentDiscount()));
}
total = total.add(segmentPrice);
current = segmentEnd;
}
return total.setScale(2, RoundingMode.HALF_UP);
}
}
4. 系统特色与优化
4.1 实时状态看板
为了方便管理人员掌握台球厅运营情况,系统开发了实时状态看板功能:
- 当前使用情况:显示每张台球桌的状态和使用时长
- 今日营收统计:实时更新当日收入数据
- 热门时段分析:基于历史数据的可视化展示
前端使用Vue配合ECharts实现数据可视化:
vue复制<template>
<div class="dashboard">
<el-row :gutter="20">
<el-col :span="12">
<div class="chart-container">
<h3>台球桌状态分布</h3>
<div ref="statusChart" style="width: 100%; height: 300px;"></div>
</div>
</el-col>
<el-col :span="12">
<div class="chart-container">
<h3>今日营收趋势</h3>
<div ref="revenueChart" style="width: 100%; height: 300px;"></div>
</div>
</el-col>
</el-row>
</div>
</template>
<script>
import * as echarts from 'echarts';
export default {
data() {
return {
statusData: [],
revenueData: []
};
},
mounted() {
this.fetchData();
// 每5分钟自动刷新数据
this.timer = setInterval(this.fetchData, 300000);
},
methods: {
async fetchData() {
const res = await this.$http.get('/dashboard/data');
this.statusData = res.statusData;
this.revenueData = res.revenueData;
this.renderCharts();
},
renderCharts() {
// 渲染状态分布图
const statusChart = echarts.init(this.$refs.statusChart);
statusChart.setOption({
tooltip: { trigger: 'item' },
series: [{
type: 'pie',
data: this.statusData,
radius: ['50%', '70%'],
label: { show: false },
emphasis: {
label: { show: true }
}
}]
});
// 渲染营收趋势图
const revenueChart = echarts.init(this.$refs.revenueChart);
revenueChart.setOption({
xAxis: {
type: 'category',
data: this.revenueData.map(item => item.hour)
},
yAxis: { type: 'value' },
series: [{
data: this.revenueData.map(item => item.amount),
type: 'line',
smooth: true,
areaStyle: {}
}]
});
}
},
beforeDestroy() {
clearInterval(this.timer);
}
};
</script>
4.2 性能优化措施
在实际运行中,我们针对以下方面进行了性能优化:
-
数据库查询优化:
- 为高频查询添加适当索引
- 使用MyBatis二级缓存
- 复杂报表查询使用定时任务预计算
-
接口响应优化:
- 使用Spring Cache注解缓存热点数据
- 分页查询默认限制每页20条记录
- 长时间操作改为异步处理
-
前端性能优化:
- 组件懒加载
- API请求合并
- 静态资源CDN加速
缓存配置示例:
java复制@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
return new CaffeineCacheManager() {
@Override
protected Cache<Object, Object> createNativeCaffeineCache(String name) {
return Caffeine.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES)
.maximumSize(1000)
.recordStats()
.build();
}
};
}
}
@Service
public class TableServiceImpl implements TableService {
@Cacheable(value = "tables", key = "#status")
@Override
public List<PoolTable> findByStatus(String status) {
return poolTableMapper.selectList(new QueryWrapper<PoolTable>().eq("status", status));
}
@CacheEvict(value = "tables", allEntries = true)
@Override
public void updateTableStatus(Integer id, String status) {
PoolTable table = new PoolTable();
table.setId(id);
table.setStatus(status);
poolTableMapper.updateById(table);
}
}
5. 部署与运维
5.1 系统部署方案
系统支持多种部署方式,适合不同规模的校园环境:
-
单机部署:适合小型台球厅
- 一台服务器同时运行前后端
- 使用内嵌Tomcat运行Spring Boot应用
- MySQL和Redis安装在同一台机器
-
分布式部署:适合大型校园多台球厅
- 前后端分离部署
- 数据库单独服务器
- 使用Nginx做负载均衡
Docker部署示例:
dockerfile复制# 后端Dockerfile
FROM openjdk:11-jre
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
# 前端Dockerfile
FROM nginx:alpine
COPY dist/ /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
# docker-compose.yml
version: '3'
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: poolhall
volumes:
- mysql_data:/var/lib/mysql
ports:
- "3306:3306"
redis:
image: redis:alpine
ports:
- "6379:6379"
backend:
build: ./backend
ports:
- "8080:8080"
depends_on:
- mysql
- redis
frontend:
build: ./frontend
ports:
- "80:80"
volumes:
mysql_data:
5.2 监控与日志
为了保证系统稳定运行,我们实现了以下监控措施:
- Spring Boot Actuator:提供健康检查、性能指标等端点
- Prometheus + Grafana:监控系统各项指标
- ELK Stack:集中管理日志,便于问题排查
Actuator配置示例:
yaml复制# application.yml
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
endpoint:
health:
show-details: always
prometheus:
enabled: true
metrics:
export:
prometheus:
enabled: true
日志收集配置:
java复制@Configuration
public class LogbackConfig {
@Bean
public LoggerContext loggerContext() {
return (LoggerContext) LoggerFactory.getILoggerFactory();
}
@Bean(initMethod = "start", destroyMethod = "stop")
public SocketAppender logstashAppender() {
SocketAppender appender = new SocketAppender();
appender.setName("LOGSTASH");
appender.setContext(loggerContext());
appender.setRemoteHost("logstash-host");
appender.setPort(5044);
appender.setReconnectionDelay(5000);
appender.setQueueSize(500);
appender.setIncludeCallerData(true);
return appender;
}
}
6. 开发经验分享
6.1 项目开发中的关键决策
在开发过程中,我们面临了几个关键决策点:
-
技术栈选择:考虑过使用Python+Django快速开发,但最终选择Java+Spring Boot,主要考虑校园IT部门更熟悉Java技术栈,便于后期维护。
-
认证方案:比较了Session、JWT和OAuth2,最终选择JWT因为:
- 无状态,适合分布式部署
- 移动端支持友好
- 实现简单,适合校园场景的安全要求
-
预约冲突处理:采用了乐观锁机制处理并发预约,相比悲观锁性能更好:
java复制@Transactional public Reservation createReservation(ReservationDTO dto) { // 检查冲突时使用数据库当前时间 LocalDateTime now = LocalDateTime.now(); if (dto.getStartTime().isBefore(now)) { throw new BusinessException("不能预约过去的时间"); } // 其他业务逻辑... }
6.2 遇到的典型问题及解决方案
问题1:高峰期预约响应慢
现象:中午12点学生集中预约时,系统响应时间明显变长。
分析:通过APM工具发现数据库连接池被耗尽,大量请求在等待连接。
解决方案:
- 增加数据库连接池大小
- 引入Redis缓存可预约台球桌列表
- 预约操作添加限流
问题2:时间计算不准确
现象:跨天的预约时长计算错误。
分析:直接使用LocalDateTime计算跨天时长会忽略日期变化。
解决方案:
java复制// 修正后的时长计算方法
public long calculateDuration(LocalDateTime start, LocalDateTime end) {
return Duration.between(start, end).toMinutes();
}
// 处理跨天情况
public BigDecimal calculatePrice(LocalDateTime start, LocalDateTime end) {
List<PriceSegment> segments = new ArrayList<>();
LocalDateTime current = start;
while (current.isBefore(end)) {
LocalDate currentDate = current.toLocalDate();
LocalTime ruleStart = current.toLocalTime();
LocalTime ruleEnd = LocalTime.MAX;
if (!currentDate.equals(end.toLocalDate())) {
ruleEnd = LocalTime.MAX;
} else {
ruleEnd = end.toLocalTime();
}
// 查找适用的价格规则...
current = current.with(ruleEnd).plusNanos(1);
}
// 计算总价...
}
6.3 给其他开发者的建议
基于本项目经验,给类似校园管理系统开发者的建议:
-
需求分析阶段:
- 充分了解校园特殊规则(如学生证优惠、学期时段变化)
- 与管理人员深入沟通,理解他们的工作流程
-
技术实现方面:
- 选择校园IT部门熟悉的技术栈
- 预留API接口方便与校园其他系统集成
- 设计时要考虑寒暑假等特殊时段的运营模式
-
部署运维方面:
- 提供详细的部署文档和运维手册
- 设计简单的数据备份和恢复方案
- 考虑校园网络环境,优化离线操作体验
-
用户体验方面:
- 界面设计要简洁,考虑非技术人员使用
- 提供多种预约状态通知方式(短信/微信/邮件)
- 关键操作要有确认提示和操作日志
这个项目从技术角度不算复杂,但真正挑战在于如何适应校园特殊环境和管理需求。我们在开发过程中不断与台球厅管理人员和学生用户沟通调整,最终交付的系统在实际运行中获得了90%以上的用户满意度。