一、课程列表
与讲师列表类似
有查询表单和分页条
每条数据之后有编辑课程基本信息、编辑课程大纲、删除按钮
后端
1、创建CourseQuery类,用于封装多条件组合查询课程信息的条件数据
public class CourseQuery {@ApiModelProperty(value = "课程标题,模糊查询")private String title;@ApiModelProperty(value = "状态 Draft未发布 Normal已发布")private String status;@ApiModelProperty(value = "查询开始时间", example = "2019-01-01 10:10:10")private String begin;@ApiModelProperty(value = "查询结束时间", example = "2019-12-01 10:10:10")private String end;
}
2、EduCourseController创建查询课程列表接口、分页查询课程列表接口、带多条件组合的分页查询课程列表接口
@ApiOperation(value = "所有课程列表")
@GetMapping("findAll")
public R findCourseById() {List<EduCourse> courses = eduCourseService.list(null);return R.ok().data("courses", courses);
}
@ApiOperation(value = "分页查询课程列表")
@GetMapping("pageCourse/{current}/{size}")
public R pageListTeacher(@ApiParam(name = "current", value = "课程列表当前页码", required = true) @PathVariable long current,@ApiParam(name = "size", value = "课程列表每页讲师数", required = true) @PathVariable long size) {Page<EduCourse> pageCourse = new Page<EduCourse>(current , size);eduCourseService.page(pageCourse, null);return R.ok().data("total", pageCourse.getTotal()).data("courses", pageCourse.getRecords());
}
@ApiOperation(value = "带多条件组合的分页查询课程列表")
@PostMapping("pageCourseCondition/{current}/{size}")
public R pageTeacherCondition(@ApiParam(name = "current", value = "课程列表当前页码", required = true) @PathVariable long current,@ApiParam(name = "size", value = "课程列表每页课程数", required = true) @PathVariable long size,@RequestBody(required = false) CourseQuery condition) {Page<EduCourse> pageCourse = new Page<EduCourse>(current, size);QueryWrapper<EduCourse> wrapper = new QueryWrapper<EduCourse>();String title = condition.getTitle();String status = condition.getStatus();String begin = condition.getBegin();String end = condition.getEnd();if (!StringUtils.isEmpty(title)) {wrapper.like("title", title);}if (!StringUtils.isEmpty(status)) {wrapper.eq("status", status);}if (!StringUtils.isEmpty(begin)) {wrapper.ge("gmt_create", begin);}if (!StringUtils.isEmpty(end)) {wrapper.le("gmt_create", end);}wrapper.orderByDesc("gmt_create");eduCourseService.page(pageCourse, wrapper);return R.ok().data("total", pageCourse.getTotal()).data("courses", pageCourse.getRecords());
}
前端
1、course文件中加上查询课程列表的接口
getList(current, size, condition) {return request({url:`/eduservice/course/pageCourseCondition/${current}/${size}`,method: 'post',data: condition })
}
2、views/edu/course/list.vue
参考讲师列表<template><div class="app-container"><!-- 条件查询表单 --><el-form :inline="true" class="demo-form-inline" align="center"><el-form-item label="课程标题"><el-input v-model="condition.title" placeholder="课程标题"></el-input></el-form-item><el-form-item label="课程状态"><el-select v-model="condition.status" placeholder="课程状态"><el-option label="未发布" :value="'Draft'"></el-option><el-option label="已发布" :value="'Normal'"></el-option></el-select></el-form-item><el-form-item label="创建时间"><el-date-picker v-model="condition.begin" type="datetime" placeholder="选择开始时间" value-format="yyyy-MM-dd HH:mm:ss" default-time="00:00:00" /></el-form-item><el-form-item><el-date-picker v-model="condition.end" type="datetime" placeholder="选择截止时间" value-format="yyyy-MM-dd HH:mm:ss" default-time="00:00:00" /></el-form-item><el-form-item><el-button type="primary" @click="getList()">查询</el-button><el-button type="default" @click="resetData()">清空</el-button></el-form-item></el-form><!-- 课程列表 --><el-table:data="list"style="width: 100%"><el-table-columnprop="sort"label="序号"width="180"align="center"><template slot-scope="scope"><!-- 计算序号 -->{{ (current - 1) * size + scope.$index + 1 }}</template></el-table-column><el-table-columnlabel="封面"width="180"align="center"><template scope="scope"><img :src="scope.row.cover" width="40" height="40" class="cover"/></template></el-table-column><el-table-columnprop="id"label="id"width="180"align="center"></el-table-column><el-table-columnprop="title"label="标题"width="180"align="center"></el-table-column><el-table-columnprop="lessonNum"label="课时数"width="180"align="center"></el-table-column><el-table-columnprop="price"label="价格"width="180"align="center"></el-table-column><el-table-columnlabel="状态"width="180"align="center"><template slot-scope="scope"><!-- scope表示整个表格域 row表示每行===比较数值和类型 -->{{ scope.row.status === 'Draft' ? '未发布' : '已发布' }}</template></el-table-column><el-table-columnprop="gmtCreate"label="创建时间"width="180"align="center"><template scope="scope">{{ scope.row.gmtCreate }}</template></el-table-column><el-table-column label="操作" align="center"><template slot-scope="scope"><!-- 通过路由跳转进入回显页面 --><router-link :to="'/course/updateCourseInfo/' + scope.row.id"><el-button size="mini" type="primary">编辑课程基本信息</el-button></router-link><router-link :to="'/course/updateChapter/' + scope.row.id"><el-button size="mini" type="primary">编辑课程大纲</el-button></router-link><el-button size="mini" type="danger" @click="removeDataById(scope.row.id)">删除</el-button></template></el-table-column></el-table><!-- 分页条 --><el-paginationbackgroundlayout="total, prev, pager, next, jumper":current-page="current":page-size="size":total="total"@current-change="getList"><!-- 事件绑定,每次页面切换都调用getList 会自动将点击的页码作为参数传入--></el-pagination></div></template><script>import course from '@/api/edu/course'export default {data() {return {list: null, total: 0, current: 1, size: 3, condition: {}}},created() {this.getList()},methods: {getList(current = 1) {this.current = currentcourse.getList(this.current, this.size, this.condition).then(response => {console.log(response)this.list = response.data.coursesthis.total = response.data.total}).catch(error => {console.log(error)})},resetData() {this.condition = {}this.getList()},removeDataById(id) {this.$confirm('此操作将永久删除该课程记录, 是否继续?', '提示', {confirmButtonText: '确定',cancelButtonText: '取消',type: 'warning'}).then(() => {course.deleteCourseById(id).then(response => {this.$message({type: 'success',message: '删除成功!'});this.getList()})}).catch(() => {this.$message({type: 'info',message: '已取消删除'});});}}}</script>
二、删除课程
删除课程时,同时删除课程本身、描述、章节、小节、视频
外键约束两张表如果是一对多的关系,在多的那张表上加上一个字段,存储一的那张表的主键,作为外键一般不建议声明外键,就直接使用就可以了,否则由于要保持两张表的一致性,不能随便删除数据,但还是建议按照顺序删除数据,先删除小节,再删除章节,再删除课程简介,最后删除课程
后端
1、EduCourseController创建删除课程的接口
@DeleteMapping("deleteCourseById/{id}")
public R deleteCourseById(@PathVariable String id) {eduCourseService.deleteCourseById(id);return R.ok();
}
2、EduCourseServiceImpl
注入章节小节简介的service@Autowiredprivate EduCourseDescriptionService eduCourseDescriptionService;@Autowiredprivate EduChapterService eduChapterService;@Autowiredprivate EduVideoService eduVideoService;
创建删除课程的方法删除小节->章节->简介->课程@Overridepublic void deleteCourseById(String courseId) {eduVideoService.removeVideoByCourseId(courseId);eduChapterService.removeChapterByCourseId(courseId);eduCourseDescriptionService.removeById(courseId);int affectRows = baseMapper.deleteById(courseId);if (affectRows == 0) {throw new OrangeException(20001, "删除课程失败");}}
3、章节和小节的service中分别创建根据课程id删除章节和小节的方法
@Override
public void removeVideoByCourseId(String courseId) {QueryWrapper<EduVideo> wrapper = new QueryWrapper<>();wrapper.eq("course_id", courseId);baseMapper.delete(wrapper);
}
public void removeChapterByCourseId(String courseId) {QueryWrapper<EduChapter> wrapper = new QueryWrapper<>();wrapper.eq("course_id", courseId);baseMapper.delete(wrapper);
}
前端
1、course文件中加上删除课程的接口
deleteCourseById(id) {return request({url: `/eduservice/course/deleteCourseById/${id}`,method: 'delete'})
}
2、list页面中创建删除课程的方法,调用course文件中的方法
removeDataById(id) {this.$confirm('此操作将永久删除该课程记录, 是否继续?', '提示', {confirmButtonText: '确定',cancelButtonText: '取消',type: 'warning'}).then(() => {course.deleteCourseById(id).then(response => {this.$message({type: 'success',message: '删除成功!'});this.getList()})}).catch(() => {this.$message({type: 'info',message: '已取消删除'});});
}
三、阿里云视频点播
1、开通选择按流量计费
0-10TB 0.24/GB
视频存储 0-50GB 0元
还具有视频转码功能,可以将普通视频转为高清视频;视频采集;视频编辑;智能审核;分发加速
2、管理控制台使用
媒资库-音视频上传视频上传的视频会有自动生成的ID和视频地址,通过地址可以直接播放管理视频
图片放视频封面
默认不开启审核,否则收费
配置管理存储管理 实际上视频还是存在oss中,开通了视频点播后会在oss中分配一块空间专门用于视频点播的存储分类管理将视频放在不同的文件夹中转码模板组给视频转码或加密参数编码格式 H264/H265码率 视频的画质,视频音频的同步度分辨率 视频大小帧率 越大视频越清晰关键帧最大间隔 视频切换响应时间水印将封装格式改为hls,可以选择高级参数进行加密,通过视频地址不能直接播放先在管理存储中启用存储地址,才能上传视频转码之后的视频有两个地址,一个是加密后的地址,不能直接播放要播放加密后的视频必须有域名
3、文档
服务端SDK服务端:后端接口客户端:调用接口的部分,浏览器、Android、iOSAPI:阿里云提供的固定地址http://vod.cn-shanghai.aliyuncs.com,只需要调用该地址,向地址传递参数(参数拼接在地址后面),实现功能,可以返回json或xml数据使用httpclient技术调用地址,可以模拟浏览器发送请求和得到响应SDK:底层是API,对API的方式进行了封装,操作更方便引入依赖,使用阿里云提供的类或接口里的方法实现功能,类似EasyExcel的使用
上传SDK
4、使用
下载根据文档引入Java SDK的依赖,之前已经引入
获取Access Key之前使用oss时已经获得Access Key
创建DefaultAcsClient对象String regionId = "cn-shanghai";//点播服务接入区域 不能改变DefaultProfile profile = DefaultProfile.getProfile(regionId, accessKeyId, accessKeySecret);DefaultAcsClient client = new DefaultAcsClient(profile);
调用对象中的方法初始化DefaultAcsClient对象创建GetPlayInfoRequest和GetPlayInfoResponse对象设置request对象的值调用DefaultAcsClient对象的方法得到响应数据
5、测试
获取视频播放地址
如果视频没有加密,可以使用地址直接播放;否则不可以
实际工作中视频是一定要进行加密的,所以在数据库中存储加密地址没有意义,数据库要存储的是视频的id
根据视频id可以获得视频地址和播放凭证
创建service_vod模块,引入依赖<dependencies><dependency><groupId>com.aliyun</groupId><artifactId>aliyun-java-sdk-core</artifactId></dependency><dependency><groupId>com.aliyun.oss</groupId><artifactId>aliyun-sdk-oss</artifactId></dependency><dependency><groupId>com.aliyun</groupId><artifactId>aliyun-java-sdk-vod</artifactId></dependency><dependency><groupId>com.aliyun</groupId><artifactId>aliyun-sdk-vod-upload</artifactId></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId></dependency><dependency><groupId>org.json</groupId><artifactId>json</artifactId></dependency><dependency><groupId>com.google.code.gson</groupId><artifactId>gson</artifactId></dependency><dependency><groupId>joda-time</groupId><artifactId>joda-time</artifactId></dependency></dependencies>问题:aliyun-sdk-vod-upload报红解决:https://help.aliyun.com/document_detail/51992.html?spm=a2c4g.11186623.6.1029.2dab6cecZfMGvO下载父工程中指定版本的aliyun-sdk-vod-upload解压进入解压后的bin目录,地址栏cmd执行mvn install:install-file -DgroupId=com.aliyun -DartifactId=aliyun-sdk-vod-upload -Dversion=1.4.11 -Dpackaging=jar -Dfile=aliyun-java-vod-upload-1.4.11.jar如果提示mvn不是可执行的命令,可以将环境变量中的MAVEN_HOME写为安装地址加上\bin,然后PATH中加上%MAVEN_HOME%
初始化,创建DefaultAcsClient对象创建一个创建DefaultAcsClient对象的类public class InitObject {public static DefaultAcsClient initVodClient(String accessKeyId, String accessKeySecret) {String regionId = "cn-shanghai";DefaultProfile profile = DefaultProfile.getProfile(regionId, accessKeyId, accessKeySecret);DefaultAcsClient client = new DefaultAcsClient(profile);return client;}}
根据视频id获取视频播放地址@Testpublic void testGetAddress() throws ClientException {DefaultAcsClient client = InitObject.initVodClient("", "");GetPlayInfoRequest request = new GetPlayInfoRequest();request.setVideoId("");GetPlayInfoResponse response = client.getAcsResponse(request);List<GetPlayInfoResponse.PlayInfo> playInfoList = response.getPlayInfoList();for (GetPlayInfoResponse.PlayInfo playInfo : playInfoList) {System.out.println("PlayInfo.PlayURL = " + playInfo.getPlayURL() + "\n");}System.out.println("VideoBase.Title = " + response.getVideoBase().getTitle() + "\n");}
获取视频播放凭证
@Test
public void testGetProof() throws ClientException {DefaultAcsClient client = InitObject.initVodClient("", "");GetVideoPlayAuthRequest request = new GetVideoPlayAuthRequest();request.setVideoId("");GetVideoPlayAuthResponse response = client.getAcsResponse(request);System.out.println("PlayAuth = " + response.getPlayAuth());
}
上传视频
@Test
public void testUpload() {UploadVideoRequest request = new UploadVideoRequest("", "", "test", "");request.setPartSize(2 * 1024 * 1024L);request.setTaskNum(1);UploadVideoImpl uploader = new UploadVideoImpl();UploadVideoResponse response = uploader.uploadVideo(request);if (response.isSuccess()) {System.out.println("VideoId = " + response.getVideoId());} else {System.out.println("VideoId = " + response.getVideoId() + "\n");System.out.println("ErrorCode = " + response.getCode() + "\n");System.out.println("ErrorMessage = " + response.getMessage() + "\n");}
}
问题:ErrorMessage = sun.misc.BASE64Encoder
解决:引入的依赖版本参考阿里云视频点播的文档的依赖版本,aliyun-sdk-vod-upload也使用最新的版本<dependency><groupId>com.aliyun</groupId><artifactId>aliyun-java-sdk-core</artifactId><version>4.5.1</version></dependency><dependency><groupId>com.aliyun.oss</groupId><artifactId>aliyun-sdk-oss</artifactId><version>3.10.2</version></dependency><dependency><groupId>com.aliyun</groupId><artifactId>aliyun-java-sdk-vod</artifactId><version>2.15.11</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.28</version></dependency><dependency><groupId>org.json</groupId><artifactId>json</artifactId><version>20170516</version></dependency><dependency><groupId>com.google.code.gson</groupId><artifactId>gson</artifactId><version>2.8.2</version></dependency>
四、添加小节时上传视频
nginx.conf
配置edu_vod模块的转发server {listen 9001;server_name localhost;location ~ /eduservice/ {proxy_pass http://localhost:8001;}location ~ /eduoss/ {proxy_pass http://localhost:8002;}location ~ /eduvod/ {proxy_pass http://localhost:8003;}}
配置上传限制的文件大小之前在application.properties中的配置只是解除了tomcat对于文件上传大小的限制http {include mime.types;default_type application/octet-stream;client_max_body_size 1024m;否则会报错:POST 413 (Request Entity Too Large)
修改后需要重启nginx
后端
引入依赖,上面已经引入,要注意依赖版本的对应关系,参考阿里云提供的SDK文档
1、创建application.properties文件
# 服务端口
server.port=8003
# 服务名
spring.application.name=service-vod
# 环境设置 dev test prod
spring.profiles.active=dev
# 阿里云 vod
# 不同的服务器,地址不同
aliyun.vod.file.keyid=
aliyun.vod.file.keysecret=
# 最大上传单个文件大小,默认1M
spring.servlet.multipart.max-file-size=1024MB
# 最大总上传的数据大小,默认10M
spring.servlet.multipart.max-request-size=1024MB
2、创建启动类VodApplication
加上注解@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)因为在vod这个模块里只处理视频,不操作数据库,所以在application.properties中没有对数据库进行配置,如果不加上括号里的内容会报错,加上后默认不加载数据库的相关配置
加上注解@ComponentScan(basePackages = {"com.xm"})定义包的扫描规则,因为要使用其它包中的swagger、自动填充、R
3、创建controller和service
VodController加上注解@RestController@RequestMapping("/eduvod/video")@CrossOrigin注入service@Autowiredprivate VodService vodService;创建方法uploadVideo,调用service中的方法uploadVideo,实现视频上传@PostMapping("uploadVideo")public R uploadVideo(MultipartFile file) {String videoId = vodService.uploadVideo(file);return R.ok().data("videoId", videoId);}
VodService和VodServiceImplVodServiceImpl加上注解@Service创建方法uploadVideo
创建常量工具类ConstantVodUtils来获取配置文件中的参数加上注解@Component 实现了接口InitializingBean定义变量,加上注解@Value,获取配置文件中的属性值@Value("${aliyun.vod.file.keyid}")private String keyid;@Value("${aliyun.vod.file.keysecret}")private String keysecret;定义常量public static String KEY_ID;public static String KEY_SECRET;重写方法afterPropertiesSet,将变量的值赋值给常量@Overridepublic void afterPropertiesSet() throws Exception {KEY_ID = keyid;KEY_SECRET = keysecret;}
VodServiceImpluploadVideo方法,参数是MultipartFile来获取要上传的文件,使用流式上传接口,返回视频id使用流式上传接口需要传入accessKeyId accessKeySecret title fileName inputStreamtitle是上传后的文件名fileName是上传前的文件名inputStream是上传文件的输入流,可以通过file.getInputStream()来获得使用常量工具类ConstantVodUtils来获取配置文件中的参数
前端
1、添加课时/小节的弹框中加上上传视频的组件
<el-form-item label="上传视频"><el-upload:on-success="videoUploadSuccess":on-remove="videoRemove":before-remove="beforeRemove":on-exceed="videoUploadExceed":file-list="fileList":action="BASE_API + '/eduvod/video'":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"></i></el-tooltip></el-upload>
</el-form-item>
2、data中定义变量
fileList: [],
BASE_API: process.env.BASE_API
3、创建上传之前调用的方法和上传成功调用的方法
videoUploadExceed() {this.$message.warning("想要重新上传视频,请先删除已经删除的视频")
},
videoUploadSuccess(response) {this.video.videoSourceId = response.data.videoId
}
4、小节中上传视频在数据库中同时存储视频的名称
data中的video加上属性videoOriginalName: ''
videoUploadSuccess中参数file的属性name赋值给data中的video的属性videoOriginalNamevideoUploadSuccess(response, file, fileList) {this.video.videoSourceId = response.data.videoIdthis.video.videoOriginalName = file.name}
五、删除视频
后端
1、将InitObject类复制到utils包中
2、VodController
@DeleteMapping("deleteVideoById/{id}")
public R deleteVideoById(@PathVariable String id) {try {DefaultAcsClient client = InitClient.initVodClient(ConstantVodUtils.KEY_ID, ConstantVodUtils.KEY_SECRET);DeleteVideoRequest request = new DeleteVideoRequest();request.setVideoIds(id);client.getAcsResponse(request);return R.ok();} catch (ClientException e) {e.printStackTrace();throw new OrangeException(20001, "删除视频失败");}
}
前端
1、video文件中创建接口
deleteAliVideoById(id) {return request({url: `/eduvod/video/deleteVideoById/${id}`,method: 'delete'})
}
2、chapter页面创建方法
beforeVideoRemove(file, fileList) {return this.$confirm("确定移除视频${file.name}?")
},
videoRemove() {video.deleteAliVideoById(this.video.videoSourceId).then(response => {this.$message({type: 'success',message: '移除视频成功!'});this.fileList = []this.video.videoSourceId = ''this.video.videoOriginalName = ''})
}
3、在添加小节的方法中将文件列表清空,否则删除小节后再添加,还会显示刚才上传的视频
addVideo() {this.fileList = []
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!