在线教育平台
序言
由于在开发该系统时,并未有书写开发文档的经验,而仅仅是记录了每一天的开发日志,但开发日志多达21篇,因此把所有开发日志放在博客当中是影响读者观感的,因此本篇文章仅仅是作为总结,如果有具体需要,请联系我,或者访问我的GitHub仓库。
项目简介
在线教育平台采用B2C模式,Spring Cloud搭建整个微服务架构,后台采用Spring Boot+MySQL+MyBatis-Plus+Redis,并且结合Vue前端框架,采用Nuxt服务端渲染技术来优化前端页面,运用阿里云视频点播技术。在管理系统的后台中,运用Spring Security进行用户认证和授权,以确保对不同用户权限的细致划分。在用户的登录系统方面,则采纳了手机验证码注册和登录方式,并运用JWT生成Token以实现便捷的单点登录。此外,用户通过微信支付来进行课程购买。
技术栈
后端
- Spring Boot
- Spring Cloud
- MySQL
- MyBatis-Plus
- Redis
- Spring Security
- EasyExcel
前端
- Vue
- Nuxt
- ElementUI
- Axios
- ECharts
后台管理系统
在线教育平台后台管理系统的前端使用的是vue-admin-template模板
讲师管理
对讲师进行增删改查操作,后端集成了阿里云OSS,用于讲师头像的上传。
开发中值得一提的:
vue-router导航切换 时,如果两个路由都渲染同个组件,组件会重(chong)用,
组件的生命周期钩子(created)不会再被调用, 使得组件的一些数据无法根据 path的改变得到更新
因此:
- 我们可以在watch中监听路由的变化,当路由变化时,重新调用created中的内容;
- 在init方法中我们判断路由的变化,如果是修改路由,则从api获取表单数据。
如果是新增路由,则重新初始化表单数据1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21watch: { // 监听
$route(to, from) { // 路由变化方式,路由发生变化,方法就会执行
this.init()
}
},
created() { // 页面渲染之前执行
this.init()
},
methods: {
init() {
// 判断路径是否有id值
if (this.$route.params && this.$route.params.id) {
// 从路径获取id值
const id = this.$route.params.id
// 调用根据id查询的方法
this.getInfo(id)
} else { // 路径没有id值,做添加
// 清空表单
this.teacher = {}
}
},
课程分类管理
前端上传课程Excel表格,后端通过EasyExcel来处理表格并将其持久化存储于数据库中。
课程管理
可以查看课程详细信息并管理课程,如果是发布课程需要进行三个步骤,分别是“填写课程基本信息”、“创建课程大纲”、“最终发布”,需要按照该执行顺序去操作才能完整发布课程。
值得一提的是课程视频上传的实现
- 引入依赖
引入依赖存在问题
mvn需要配置环境变量,这样才能在命令行中使用mvn命令
上传视频
参考官网压缩包里面的sample示例代码改造1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25public static void main(String[] args) {
String accessKeyId = "";
String accessKeySecret = "";
String title = "6 - How Does Project Submission Work - upload by sdk"; // 上传之后文件名称
String fileName = "E:\\6 - What If I Want to Move Faster.mp4"; // 本地文件路径和名称
// 上传视频的方法
UploadVideoRequest request = new UploadVideoRequest(accessKeyId, accessKeySecret, title, fileName);
/* 可指定分片上传时每个分片的大小,默认为2M字节 */
request.setPartSize(2 * 1024 * 1024L);
/* 可指定分片上传时的并发线程数,默认为1,(注:该配置会占用服务器CPU资源,需根据服务器情况指定)*/
request.setTaskNum(1);
UploadVideoImpl uploader = new UploadVideoImpl();
UploadVideoResponse response = uploader.uploadVideo(request);
if (response.isSuccess()) {
System.out.print("VideoId=" + response.getVideoId() + "\n");
} else {
/* 如果设置回调URL无效,不影响视频上传,可以返回VideoId同时会返回错误码。其他情况上传失败时,VideoId为空,此时需要根据返回错误码分析具体错误原因 */
System.out.print("VideoId=" + response.getVideoId() + "\n");
System.out.print("ErrorCode=" + response.getCode() + "\n");
System.out.print("ErrorMessage=" + response.getMessage() + "\n");
}
} - 配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26# 服务端口
server:
port: 8082
spring:
application:
# 服务名
name: service-vod
profiles:
# 环境设置:dev、test、prod
active: dev
servlet:
multipart:
# 最大上传单个文件大小:默认1M
max-file-size: 1024MB
# 最大总上传的数据大小:默认10MB
max-request-size: 1024MB
# 阿里云 vod
# 不同的服务器,地址不同
aliyun:
vod:
file:
keyid:
keysecret: - VodApplication
1
2
3
4
5
6
7
8
public class VodApplication {
public static void main(String[] args) {
SpringApplication.run(VodApplication.class, args);
}
} - 工具类
常量读取工具类,读取配置文件的内容1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class ConstantVodUtils implements InitializingBean {
private String keyid;
private String keysecret;
// 定义公开常量
public static String ACCESS_KEY_ID;
public static String ACCESS_KEY_SECRET;
public void afterPropertiesSet() throws Exception {
ACCESS_KEY_ID = keyid;
ACCESS_KEY_SECRET = keysecret;
}
} - 控制器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class VodController {
private VodService vodService;
// 上传视频到阿里云
public Result uploadAlyVideo(MultipartFile file) {
// 返回上传视频id
String videoId = vodService.uploadVideoAly(file);
return Result.ok().data("videoId",videoId);
}
} - 服务实现类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class VodServiceImpl implements VodService {
// 上传视频到阿里云(采用流式上传接口)
public String uploadVideoAly(MultipartFile file) {
try {
// accessKeyId, accessKeySecret
// fileName: 上传文件原始名称
// 01.03.09.mp4
String fileName = file.getOriginalFilename();
// title: 上传之后显示名称
// 去除最后一个.
String title = fileName.substring(0, fileName.lastIndexOf("."));
// inputStream: 上传文件输入流
InputStream inputStream = file.getInputStream();
UploadStreamRequest request = new UploadStreamRequest(ConstantVodUtils.ACCESS_KEY_ID, ConstantVodUtils.ACCESS_KEY_SECRET, title, fileName, inputStream);
UploadVideoImpl uploader = new UploadVideoImpl();
UploadStreamResponse response = uploader.uploadStream(request);
String videoId = null;
if (response.isSuccess()) {
videoId = response.getVideoId();
} else { //如果设置回调URL无效,不影响视频上传,可以返回VideoId同时会返回错误码。其他情况上传失败时,VideoId为空,此时需要根据返回错误码分析具体错误原因
videoId = response.getVideoId();
}
return videoId;
} catch(Exception e) {
e.printStackTrace();
return null;
}
}
} - 前端
chapter.vue1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21<el-form-item label="上传视频">
<el-upload
:on-success="handleVodUploadSuccess"
:on-remove="handleVodRemove"
:before-remove="beforeVodRemove"
:on-exceed="handleUploadExceed"
:file-list="fileList"
:action="BASE_API + '/eduvod/video/uploadAlyVideo'"
:limit="1"
class="upload-demo">
<el-button size="small" type="primary">上传视频</el-button>
<el-tooltip placement="right-end">
<div slot="content">最大支持1G,<br>
支持3GP、ASF、AVI、DAT、DV、FLV、F4V、<br>
GIF、M2T、M4V、MJ2、MJPEG、MKV、MOV、MP4、<br>
MPE、MPG、MPEG、MTS、OGG、QT、RM、RMVB、<br>
SWF、TS、VOB、WMV、WEBM 等视频格式上传</div>
<i class="el-icon-question"/>
</el-tooltip>
</el-upload>
</el-form-item>1
2
3
4
5
6
7
8
9
10
11fileList: [], // 上传视频的列表
BASE_API: process.env.BASE_API // 接口API地址
// 成功回调
handleVodUploadSuccess(response, file, fileList) {
this.video.videoSourceId = response.data.videoId
},
// 视图上传多于一个视频
handleUploadExceed() {
this.$message.warning('想要重新上传视频,请先删除已上传的视频')
}, - nginx配置配置nginx上传文件大小,否则上传时会有 413 (Request Entity Too Large) 异常
1
2
3location ~ /eduvod/ {
proxy_pass http://localhost:8082;
}
打开nginx主配置文件nginx.conf,找到http{},添加1
client_max_body_size 1024m;
- 如果数据库没有视频名称
修改前端1
2
3
4
5
6
7// 上传视频成功调用的方法
handleVodUploadSuccess(response, file, fileList) {
// 上传视频id赋值
this.video.videoSourceId = response.data.videoId
// 上传视频名称赋值
this.video.videoOriginalName = file.name
},
统计分析
统计分析页面,前端页面使用Echarts组件库实现图表展示,用户可以选择指定日期范围生成统计数据,包括范围内的用户登录数和注册数,以及课程播放数等数据。
该模块使用了Feign远程调用
比如调用接口UcenterClient
1 |
|
StatisticsDailyServiceImpl服务实现类
1 |
|
除此之外,启用定时任务实现每天统计
启动类添加注释
1 | //定时任务注解 |
创建ScheduleTask类
1 |
|
前台用户系统
前端框架
Nuxt.js 是一个基于 Vue.js 的轻量级应用框架,可用来创建服务端渲染 (SSR) 应用,也可充当静态站点引擎生成静态站点应用,具有优雅的代码结构分层和热加载等特性。
官方网站
幻灯片插件:vue-awesome-swiper
首页
展示轮播图、热门课程等信息,然后对用户展示网站幻灯片、热门课程、名师等内容,为了提高访问速度使用了Redis缓存首页数据。
注册和登录
注册功能需要用户通过填写昵称、手机号,然后接收验证码的方式进行注册。如果使用手机号码注册,系统会通过阿里云短信服务向该用户发送短信验证码,后端保存该验证码来和用户输入的验证码进行比对。如果用户是以扫描微信二维码的方式进行注册,后端接收到该请求后会将页面重定向至二维码页面,扫码之后获得微信官方返回的临时票据,使用票据可以获得该用户微信账号的访问凭证和唯一标识,然后请求微信官方的接口地址得到该用户的账号信息,并将其持久化存储于数据库中,实现微信扫码注册功能。
值得一提的是使用Redis解决验证码有效时间问题
1 | // springboot整合的Redis模板对象 |
课程列表
课程列表,展示上架课程,对不同种类的课程进行了分类,可以按照销量、发布时间、售价来对课程列表进行排序。
后端处理条件分页
1 | // 1.条件查询带分页查询课程 |
课程详情
课程详情页,包含课程基本信息、分类、讲师等内容,课程分为免费和付费,如果是付费课程,那么前端的“立即观看”按钮会变为“立即购买”按钮,并且在该页面用户可以发表对该课程的评论。
视频播放
获取播放地址
参考文档
前面的 03-使用服务端SDK 介绍了如何获取非加密视频的播放地址。直接使用03节的例子获取加密视频播放地址会返回如下错误信息
Currently only the AliyunVoDEncryption stream exists, you must use the Aliyun player to play or set the value of ResultType to Multiple.
目前只有AliyunVoDEncryption流存在,您必须使用Aliyun player来播放或将ResultType的值设置为Multiple。
因此在testGetPlayInfo测试方法中添加 ResultType 参数,并设置为true
1 | privateParams.put("ResultType", "Multiple"); |
此种方式获取的视频文件不能直接播放,必须使用阿里云播放器播放
视频播放器
参考文档
视频播放器介绍
阿里云播放器SDK(ApsaraVideo Player SDK)是阿里视频服务的重要一环,除了支持点播和直播的基础播放功能外,深度融合视频云业务,如支持视频的加密播放、安全下载、清晰度切换、直播答题等业务场景,为用户提供简单、快速、安全、稳定的视频播放服务。
集成视频播放器
参考文档
参考 【播放器简单使用说明】一节
引入脚本文件和css文件
1 | <link rel="stylesheet" href="https://g.alicdn.com/de/prismplayer/2.8.1/skins/default/aliplayer-min.css" /> |
初始化视频播放器
1 | <body> |
1. 播放地址播放
在Aliplayer的配置参数中添加如下属性
1 | //播放方式一:支持播放地址播放,此播放优先级最高,此种方式不能播放加密视频 |
启动浏览器运行,测试视频的播放
2. 播放凭证播放(推荐)
阿里云播放器支持通过播放凭证自动换取播放地址进行播放,接入方式更为简单,且安全性更高。播放凭证默认时效为100秒(最大为3000秒),只能用于获取指定视频的播放地址,不能混用或重复使用。如果凭证过期则无法获取播放地址,需要重新获取凭证。
1 | encryptType:'1',//如果播放加密视频,则需设置encryptType=1,非加密视频无需设置此项 |
注意:播放凭证有过期时间,默认值:100秒 。取值范围:100~3000。
设置播放凭证的有效期
在获取播放凭证的测试用例中添加如下代码
1 | request.setAuthInfoTimeout(200L); |
后端获取播放凭证
整合阿里云视频播放器
后端
修改VideoVo
1 | public class VideoVo { |
VodController
1 | // 根据视频id获取视频凭证 |
前端
api
vod.js
1 | import request from '@/utils/request' |
创建新的layouts
video.vue
1 | <template> |
_id.vue
点击小节携带视频id跳转
1 | <a :href="'/player/'+video.videoSourceId" title target="_blank"> |
新建Page/player/_vid.vue
1 | <template> |
排错
先看看播放器的js有没有引入
摁下F12,在网络中(network)查看,如果没有可以尝试在nuxt.config.js文件中的head中添加。
不要删除原_vid.vue中的
1 | <script charset="utf-8" type="text/javascript" src="https://g.alicdn.com/de/prismplayer/2.8.1/aliplayer-min.js"/> |
名师列表
得到所有讲师信息,显示所有名师的头像、名称、简介内容。
讲师详情
在名师列表页可以选择不同讲师的卡片,通过携带讲师id请求后端接口来查询该讲师的信息和所授课程,页面中展示了名师的详细信息和所授课程。
订单模块
课程支付,用户只有登录后才能购买对应课程。购买会生成课程订单和微信支付的二维码,在此支付期间每隔3秒会查询支付状态,只有扫码成功后才更新数据库中该订单的支付状态,一旦查询支付状态为“已支付”才能为用户开通课程观看权限。
服务实现代码
1 |
|
项目仓库
- 标题: 在线教育平台
- 作者: 邱海梦旌
- 创建于 : 2024-05-27 19:04:00
- 更新于 : 2024-12-13 21:59:07
- 链接: https://blog.invictusqiu.top/2024/05/27/OnlineEducation/
- 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。