1. 项目背景与需求分析
高校教务管理信息化建设已成为教育现代化的重要一环。传统的人工选课方式存在诸多痛点:选课高峰期系统崩溃频发、学生抢课体验差、教务数据统计滞后等问题日益突出。我在参与某高校智慧校园建设项目时,曾亲眼目睹学生凌晨排队选课的场景——服务器在开放选课后的瞬间崩溃,导致大量学生无法正常选课。这种低效的选课模式不仅浪费师生时间,更影响了教学秩序。
基于这些实际痛点,我们设计开发了这套基于SpringBoot+Vue的选课系统。系统需要满足以下核心需求:
-
高并发处理能力:必须能够承受选课开放瞬间的流量洪峰,避免系统崩溃。实测表明,一所万人规模的高校在选课开放时,瞬时并发请求可达5000+。
-
角色权限精细化:需要区分学生、教师、管理员三类角色:
- 学生:选课/退课、课表查询、成绩查看
- 教师:课程发布、学生管理、成绩录入
- 管理员:用户管理、课程管理、系统监控
-
数据一致性保障:选课过程中的超选、重复选课等异常情况必须得到妥善处理。
2. 技术架构设计
2.1 整体架构方案
系统采用前后端分离架构,这是现代Web开发的主流选择。这种架构的优势在于:
- 前后端可以并行开发,提高开发效率
- 前端专注于交互体验,后端专注于业务逻辑
- 更易于实现负载均衡和横向扩展
code复制[浏览器] ←HTTP→ [Nginx] ←HTTP→ [Vue前端]
←REST→ [SpringBoot后端]
←JDBC→ [MySQL]
←Jedis→ [Redis]
2.2 关键技术选型
后端技术栈:
- SpringBoot 2.7.x:简化Spring应用的初始搭建和开发
- Spring Security + JWT:实现安全的身份认证和授权
- MyBatis-Plus:增强型ORM框架,简化数据库操作
- Redis 6.x:缓存热点数据,提升系统响应速度
- Hutool:Java工具包,提供各种实用工具方法
前端技术栈:
- Vue 3.x:渐进式JavaScript框架
- Element Plus:基于Vue 3的组件库
- Axios:处理HTTP请求
- Vue Router:实现前端路由
- Pinia:状态管理库
数据库:
- MySQL 8.0:关系型数据库
- 数据库连接池:HikariCP
技术选型心得:在初期技术调研时,我们对比了多种方案。例如在ORM框架选择上,我们放弃了Hibernate而选择MyBatis-Plus,主要考虑到:1) 需要更灵活的SQL控制;2) MyBatis-Plus的Wrapper条件构造器能极大简化开发;3) 性能开销更小。
3. 核心功能实现
3.1 高并发选课设计
选课功能是系统的核心难点,我们采用了多级防护策略:
-
前端限流:
- 选课按钮点击后立即禁用,防止重复提交
- 采用倒计时控制,错峰提交请求
-
接口层防护:
java复制@RestController
@RequestMapping("/api/selection")
public class SelectionController {
@RateLimiter(value = 100, key = "'select:' + #studentId")
@PostMapping
public Result selectCourse(@RequestBody SelectionDTO dto) {
// 业务逻辑
}
}
- 数据库优化:
- 使用乐观锁控制选课人数更新:
sql复制UPDATE course
SET current_enroll = current_enroll + 1
WHERE course_id = ? AND current_enroll < max_capacity
- 缓存策略:
- 课程余量信息缓存到Redis,定期同步到数据库
- 使用Redis的DECR命令实现原子性减库存
3.2 权限系统实现
系统采用RBAC(基于角色的访问控制)模型,结合JWT实现无状态认证:
- 权限表设计:
sql复制CREATE TABLE sys_permission (
id BIGINT PRIMARY KEY,
name VARCHAR(50) NOT NULL,
code VARCHAR(50) NOT NULL,
type TINYINT NOT NULL COMMENT '0-菜单 1-按钮'
);
CREATE TABLE sys_role_permission (
role_id BIGINT,
permission_id BIGINT,
PRIMARY KEY (role_id, permission_id)
);
- JWT令牌生成:
java复制public 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))
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
- 权限拦截:
java复制@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
// 解析和验证JWT令牌
// 设置SecurityContextHolder
filterChain.doFilter(request, response);
}
}
4. 数据库设计与优化
4.1 核心表结构
课程表设计优化点:
- 将上课时间拆分为独立表,支持复杂排课场景
- 添加fulltext索引支持课程搜索
- 使用触发器自动更新选课人数统计
sql复制CREATE TABLE course_schedule (
id BIGINT PRIMARY KEY,
course_id VARCHAR(10),
day_of_week TINYINT COMMENT '1-7对应周一到周日',
start_section TINYINT COMMENT '开始节次',
end_section TINYINT COMMENT '结束节次',
classroom VARCHAR(50),
FOREIGN KEY (course_id) REFERENCES course(course_id)
);
ALTER TABLE course ADD FULLTEXT INDEX idx_search (course_name, teacher_name);
4.2 查询性能优化
- 课表查询优化:
sql复制-- 原始查询
EXPLAIN SELECT * FROM selection WHERE student_id = '20230001';
-- 优化后查询
EXPLAIN SELECT s.*, c.course_name, c.credit
FROM selection s
JOIN course c ON s.course_id = c.course_id
WHERE s.student_id = '20230001'
AND s.is_deleted = 0;
- 索引设计原则:
- 为所有外键字段建立索引
- 高频查询条件建立组合索引
- 避免过度索引,影响写入性能
5. 部署与运维方案
5.1 生产环境部署
推荐使用Docker Compose进行容器化部署:
yaml复制version: '3.8'
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
volumes:
- mysql_data:/var/lib/mysql
redis:
image: redis:6-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 性能监控配置
- SpringBoot Actuator:
properties复制management.endpoints.web.exposure.include=health,metrics,prometheus
management.metrics.export.prometheus.enabled=true
- Grafana监控面板:
- JVM内存使用
- 接口响应时间P99
- 数据库连接池状态
- Redis命中率
6. 常见问题排查
6.1 选课失败问题
现象:学生选课时提示"系统繁忙"
- 检查Redis连接是否正常
- 查看接口限流配置是否过小
- 监控数据库连接池使用情况
解决方案:
java复制// 调整HikariCP配置
spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.connection-timeout=30000
6.2 JWT令牌失效
现象:频繁提示登录过期
- 检查令牌过期时间配置
- 确保服务器时间同步
- 验证签名密钥一致性
优化方案:
java复制// 使用双令牌机制
@PostMapping("/refresh")
public Result refreshToken(@RequestHeader("Refresh-Token") String refreshToken) {
// 验证refreshToken并颁发新accessToken
}
7. 项目扩展方向
在实际使用中,我们发现系统还可以进一步优化:
- 引入消息队列:将选课成功通知、数据同步等操作异步化
- 添加分布式锁:防止集群环境下的超选问题
- 实现SSO集成:与学校统一身份认证系统对接
- 开发移动端APP:提供更便捷的移动访问体验
部署经验分享:在首次上线时,我们忽略了Nginx的客户端body大小限制,导致课程导入功能失败。解决方法是在nginx.conf中添加
client_max_body_size 20m;配置。这类细节在开发环境往往不会暴露,但在生产环境就可能成为致命问题。