去年接手了一个高校在线教育平台的项目重构需求,原系统采用传统的JSP+Servlet架构,面临着性能瓶颈和扩展性差的问题。经过技术选型,我们最终决定采用SpringBoot+Vue的前后端分离架构,在三个月内完成了从零到上线的全过程。这个项目后来成为了多个院校的毕设参考模板,今天我就把其中的核心设计和实现细节分享给大家。
在线教育平台的核心价值在于打破时空限制,我们设计的系统包含三大核心模块:课程管理系统实现教学资源的数字化管理,实时交流系统支持师生即时互动,作业评测系统则完成了学习闭环。与市面上通用平台不同,我们的设计特别强调"轻量化"和"教学场景适配",比如针对编程类课程集成了在线代码运行环境,对文科类课程则强化了批注协作功能。
技术栈选择上,后端采用SpringBoot 2.7 + MyBatis Plus的组合,前端使用Vue 3 + Element Plus,数据库是MySQL 8.0。这套技术组合的优势在于:
我们采用典型的前后端分离架构,通过RESTful API进行数据交互。这种架构的最大优势是前后端可以并行开发,我在项目初期就使用Swagger编写了详细的API文档,前后端团队基于文档约定进行开发,最后联调时接口对接非常顺畅。
后端服务分层设计:
code复制controller层:定义REST端点
↓
service层:业务逻辑处理
↓
dao层:基于MyBatis Plus的数据访问
↓
MySQL数据库
前端采用模块化设计:
code复制src/
├── api/ # 封装所有后端接口调用
├── router/ # 定义Vue路由
├── store/ # Pinia状态管理
├── views/ # 页面组件
└── utils/ # 工具函数
用户表设计中有一个值得分享的细节:我们使用role_type字段区分用户角色(1学生,2教师,3管理员),但实际权限控制采用RBAC模型。这样设计的好处是既保持了简单性,又为后期权限扩展留有余地。
课程表的核心字段说明:
sql复制CREATE TABLE `course_info` (
`course_id` bigint NOT NULL AUTO_INCREMENT,
`course_name` varchar(100) NOT NULL COMMENT '课程名称',
`teacher_id` bigint NOT NULL COMMENT '教师ID',
`description` text COMMENT '课程详情(富文本)',
`cover_url` varchar(255) DEFAULT NULL COMMENT '封面图OSS地址',
`status` tinyint NOT NULL DEFAULT '1' COMMENT '1未开始 2进行中 3已结束',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`course_id`),
KEY `idx_teacher` (`teacher_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
注意:description字段存储富文本内容,前端展示时需要做XSS过滤。我们最终采用了js-xss库进行内容消毒处理。
教师端采用"分步表单"设计,将课程创建流程分为:
后端接口特别注意了幂等性设计,防止重复提交。核心代码如下:
java复制@PostMapping("/courses")
public Result createCourse(@Valid @RequestBody CourseCreateDTO dto) {
// 幂等控制:相同教师在5秒内重复提交相同课程名视为无效请求
String lockKey = "course:create:" + getCurrentUserId() + ":" + dto.getCourseName();
if (redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 5, TimeUnit.SECONDS)) {
return courseService.createCourse(dto);
}
throw new BusinessException("请勿重复提交课程");
}
讨论区采用WebSocket+消息队列的方案:
这个设计解决了两个关键问题:
前端消息处理核心逻辑:
javascript复制// 建立WebSocket连接
const socket = new WebSocket(`wss://api.example.com/chat?courseId=${courseId}&token=${token}`)
// 接收消息处理
socket.onmessage = (event) => {
const message = JSON.parse(event.data)
if (message.type === 'NEW_REPLY') {
store.commit('addDiscussion', message.data)
}
}
// 发送消息
function sendMessage(content, replyTo) {
socket.send(JSON.stringify({
courseId,
content,
replyTo
}))
}
初期采用直接上传到应用服务器的方案,当并发上传大文件时经常导致磁盘IO瓶颈。我们最终改造为:
关键配置项:
yaml复制# application.yml
aliyun:
oss:
endpoint: https://oss-cn-hangzhou.aliyuncs.com
bucket: edu-platform
max-size: 500MB # 单文件上限
thread-size: 4 # 分片上传线程数
最初在拦截器中简单判断role_type导致权限漏洞,比如教师可以访问其他教师的课程管理接口。改进方案:
改进后的权限校验示例:
java复制@GetMapping("/courses/{courseId}/students")
@PreAuthorize(hasRole="TEACHER")
public Result getCourseStudents(@PathVariable Long courseId) {
// 校验当前教师是否是该课程的任教老师
if (!courseService.isCourseTeacher(courseId, getCurrentUserId())) {
throw new ForbiddenException("无权访问该课程数据");
}
return courseService.getCourseStudents(courseId);
}
我们使用Docker Compose进行服务编排,主要包含以下服务:
docker-compose.yml关键配置:
yaml复制services:
app:
image: edu-platform-backend:1.0
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=prod
depends_on:
- mysql
- redis
web:
image: nginx:1.21
ports:
- "80:80"
volumes:
- ./dist:/usr/share/nginx/html
- ./nginx.conf:/etc/nginx/conf.d/default.conf
使用Spring Boot Actuator暴露监控端点,配合Prometheus+Grafana搭建监控看板,重点关注以下指标:
关键Actuator配置:
properties复制management.endpoints.web.exposure.include=health,metrics,prometheus
management.metrics.tags.application=edu-platform
management.endpoint.health.show-details=always
在实际使用中,我们发现几个有价值的扩展点:
以直播集成为例,技术方案可选:
这个项目从技术选型到最终上线,最大的体会是:教育类系统要特别注意教学场景的真实需求,不能简单照搬通用架构。比如我们最初设计的讨论区是按时间倒序排列,但实际使用中发现教师更需要"按问题主题聚合"的视图,这个改动让讨论区的使用率提升了40%。