1. 项目概述与背景
在线教育平台已经成为当下技术赋能教育的重要载体。这套基于SpringBoot2+Vue3的在线互动学习系统,是我在指导毕业设计过程中沉淀下来的典型解决方案。它采用前后端分离架构,后端使用SpringBoot2+MyBatis-Plus技术栈,前端基于Vue3组合式API开发,数据库选用MySQL8.0,完整实现了课程管理、实时讨论、作业批改等核心教学场景。
在实际开发中发现,许多同类项目存在三个共性问题:WebSocket连接不稳定、JWT令牌刷新机制缺失、MyBatis-Plus多表联查性能低下。本系统针对这些痛点做了专项优化:
- 采用STOMP协议增强WebSocket通信可靠性
- 设计双Token机制解决JWT过期问题
- 使用MyBatis-Plus的@TableField注解优化关联查询
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术架构解析
2.1 后端技术栈设计
SpringBoot2.7.12作为基础框架,这是经过多个生产环境验证的稳定版本。与新版SpringBoot3相比,它对Java8的支持更成熟,且社区生态更完善。关键配置如下:
yaml复制spring:
websocket:
stomp:
endpoint: /ws-edu
broker:
enable: true
simple:
relay-host: 127.0.0.1
relay-port: 61613
MyBatis-Plus 3.5.3版本提供了最佳的性能平衡。特别注意分页插件配置:
java复制@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
2.2 前端技术选型
Vue3组合式API相比Options API更适合复杂教育场景:
- 使用Pinia替代Vuex进行状态管理
- Element Plus按需引入减小打包体积
- 封装WebSocket组件实现断线重连
关键WebSocket封装代码:
javascript复制const useWsStore = defineStore('ws', {
state: () => ({
socket: null,
reconnectAttempts: 0
}),
actions: {
init(url) {
this.socket = new SockJS(url);
this.stompClient = Stomp.over(this.socket);
this.stompClient.connect({}, () => {
this.reconnectAttempts = 0;
}, (error) => {
this.handleError(error);
});
},
handleError(error) {
if(this.reconnectAttempts < 5) {
setTimeout(() => {
this.init();
this.reconnectAttempts++;
}, 3000);
}
}
}
})
3. 核心功能实现
3.1 双Token认证机制
传统JWT方案存在过期时间两难问题:设置太短影响体验,太长则不安全。本系统采用AccessToken+RefreshToken方案:
- AccessToken有效期2小时
- RefreshToken有效期7天
- 使用Redis存储Token关系
认证流程图解:
code复制用户登录 → 签发双Token → 前端存储
↓
API请求携带AccessToken → 过期时用RefreshToken获取新Token
↓
RefreshToken也过期 → 强制重新登录
关键代码实现:
java复制public TokenPair generateToken(UserDetails user) {
String accessToken = Jwts.builder()
.setSubject(user.getUsername())
.setExpiration(new Date(System.currentTimeMillis() + 7200000)) // 2小时
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
String refreshToken = UUID.randomUUID().toString();
redisTemplate.opsForValue().set(
"REFRESH:"+user.getUsername(),
refreshToken,
7, TimeUnit.DAYS);
return new TokenPair(accessToken, refreshToken);
}
3.2 课程讨论区设计
采用多级评论结构实现师生互动:
- 使用parent_id字段构建评论树
- 添加@提及功能自动通知用户
- 实时推送新评论到前端
数据库查询优化方案:
java复制@Select("SELECT d.*, u.username as author_name " +
"FROM discussion d " +
"LEFT JOIN user u ON d.user_id = u.user_id " +
"WHERE d.course_id = #{courseId} " +
"ORDER BY d.publish_time DESC")
@Results({
@Result(property = "replies", column = "discussion_id",
many = @Many(select = "findRepliesByParentId"))
})
List<DiscussionDTO> findCourseDiscussions(@Param("courseId") Long courseId);
4. 性能优化实践
4.1 MySQL查询优化
针对课程列表页的N+1查询问题,采用三种解决方案对比:
| 方案 | 查询次数 | 代码复杂度 | 适用场景 |
|---|---|---|---|
| 原生联查 | 1 | 高 | 简单关联 |
| @TableField注解 | N+1 → 1+1 | 中 | 多层级关联 |
| 手动JOIN | 1 | 高 | 复杂统计 |
最终选择@TableField方案:
java复制@Data
public class CourseVO {
private Long courseId;
private String courseName;
@TableField(exist = false)
private User teacher;
@TableField(exist = false)
private Integer studentCount;
}
4.2 Redis缓存策略
设计三级缓存体系:
- 本地Caffeine缓存:高频访问数据
- Redis集群:共享会话数据
- MySQL:持久化存储
缓存更新策略采用:
java复制@CacheEvict(value = "courses", key = "#course.courseId")
public void updateCourse(Course course) {
courseMapper.updateById(course);
messageTemplate.convertAndSend("/topic/course-update", course.getCourseId());
}
5. 部署与监控
5.1 容器化部署
Docker Compose编排方案:
yaml复制version: '3.8'
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: edu@1234
volumes:
- mysql_data:/var/lib/mysql
redis:
image: redis:6
ports:
- "6379:6379"
backend:
build: ./backend
ports:
- "8080:8080"
depends_on:
- mysql
- redis
5.2 Prometheus监控
关键监控指标配置:
yaml复制- name: edu_application
metrics:
- name: active_users
type: counter
help: "当前活跃用户数"
- name: course_operations
type: summary
help: "课程操作耗时统计"
labels: [operation]
6. 踩坑实录
-
WebSocket断连问题
- 现象:移动端网络切换导致连接中断
- 解决:添加心跳检测机制
javascript复制setInterval(() => { if(store.stompClient.connected) { store.stompClient.send('/heartbeat'); } }, 30000); -
MyBatis-Plus批量插入性能
- 错误做法:循环调用insert()
- 正确方案:使用saveBatch()并配置rewriteBatchedStatements=true
-
Vue3响应式丢失
- 典型场景:解构props对象
- 修复方案:使用toRefs保持响应式
javascript复制const { title } = toRefs(props)
这套系统在多个高校的毕业设计中得到验证,最关键的收获是:教育类系统必须平衡实时性和数据一致性。比如讨论区采用最终一致性而非强一致性,允许短暂的数据延迟以换取更好的响应速度。对于需要开发类似系统的同学,建议先从核心教学流程入手,再逐步扩展辅助功能。
