58-硅谷课堂4-腾讯云点播管理模块
58-硅谷课堂4-腾讯云点播管理模块-- 笔记
笔记内容来源与尚硅谷教学视频
文章目录
- 58-硅谷课堂4-腾讯云点播管理模块-- 笔记
- 笔记中涉及资源:
- 一、后台管理系统-点播管理模块
- ①:点播管理 模块需求
- 1. 创建课程相关表
- ②:环境搭建
- 1. 生成相关代码
- ③:功能实现-课程列表
- 1. 开发课程列表接口
- 2. 开发课程列表前端
- 二、发布课程-填写课程基本信息
- ①:界面效果
- ②:添加课程基本信息接口
- 1. 创建课程描述的service和mapper
- 2. 创建添加课程基本信息接口
- ③:添加课程基本信息前端
- 1. 课程列表list.vue添加方法
- 2. course.js定义接口
- 3. 创建课程基本信息添加页面
- 三、发布课程-修改课程基本信息
- ③:修改课程基本信息接口
- 1. CourseService定义方法
- 2. CourseServiceImpl实现方法
- 3. CourseController实现方法
- ②:修改课程基本信息前端
- 1. course.js定义方法
- 2. 修改Info.vue页面
- 3. 创建Chapter-index.vue页面
- 四、发布课程-创建课程大纲
- ①:课程章节接口
- 1. 编写章节Controller
- 2. 编写章节Service
- ②:课程小节接口
- 1. 编写VideoController
- ③:课程大纲前端
- 1. 定义接口
- 2. 编写章节页面
- 3. 编写小节(课时)页面
- ④:测试
- 五、发布课程-课程最终发布
- ①:课程最终发布接口
- 1. 编写CourseController
- 2. 编写CourseService
- 3. 编写CourseServiceImpl
- 4. 编写CourseMapper
- 5. 编写CourseMapper.xml
- 6. 添加配置
- ②:课程最终发布前端
- 1. course.js定义接口
- 2. 编写Publish.vue
- 六、功能实现-课程删除
- ①:课程删除接口
- 1. 编写课程Controller
- 2. 编写课程Service
- 3. 编写VideoService
- 4. 编写ChapterService
- ②:课程删除前端
- 1. course.js定义接口
- 2. course -> list.vue添加方法
- 七、点播管理模块-课程统
- ①:课程统计需求
- ②:课程统计接
- 1. 创建相关代码
- 2. 编写Controller
- 3. 编写Service和实现
- 4. 编写Mapper
- ③:课程统计前
- 1. 定义接口
- 2. 安装ECharts组件
- 3. 编写页面
- 八、整合腾讯云点
- ①:功能需求介
- 1. 上传视频
- 2. 删除视频
- 3. 视频播放(后续完成)
- ②:腾讯云点播介绍
- 1. 开通"云点播"服务
- 2. 管理控制台
- 3. 上传视频
- 4. 前端集成
- ③:编写视频点播接
- 1. 创建相关类
- 2. 引入相关依赖
- 3. 编写Controller
- 4. 编写Service
- ④:完善上传视频功
- 1. 定义接口
- 2.测试
- ⑤:腾讯云上传视频其他方
- 1. 客户端上传视频
- 2. 操作步骤一(申请上传签名
- (1)找到Java签名示例
- (2)VodController编写签名接口
- 3. 操作步骤二(SDK上传)
- 5.4、下载Demo源码修改
- (1)html文件测试上传
- ⑥:完善删除视频功
- 1. 修改VideoController方法
- 2. 修改Service方法
- 3. 测试
笔记中涉及资源:
一、后台管理系统-点播管理模块
①:点播管理 模块需求
添加点播课程,包含课程基本信息,课程章节,课程小结和最终发布
1. 创建课程相关表
②:环境搭建
1. 生成相关代码
③:功能实现-课程列表
实现分页条件查询点播课程功能
1. 开发课程列表接口
编写CourseController
@Api(tags = "课程管理接口")
@RestController
@RequestMapping(value="/admin/vod/course")
public class CourseController {@Autowiredprivate CourseService courseService;@ApiOperation(value = "获取分页列表")@GetMapping("{page}/{limit}")public Result index(@ApiParam(name = "page", value = "当前页码", required = true)@PathVariable Long page,@ApiParam(name = "limit", value = "每页记录数", required = true)@PathVariable Long limit,@ApiParam(name = "courseVo", value = "查询对象", required = false)CourseQueryVo courseQueryVo) {Page<Course> pageParam = new Page<>(page, limit);Map<String,Object> map = courseService.findPage(pageParam, courseQueryVo);return Result.ok(map);}
}
编写CourseService
public interface CourseService extends IService<Course> {//课程列表Map<String,Object> findPage(Page<Course> pageParam, CourseQueryVo courseQueryVo);
}
编写CourseServiceImpl
- 方法一
@Service
public class CourseServiceImpl extends ServiceImpl<CourseMapper, Course> implements CourseService {@Autowiredprivate TeacherService teacherService;@Autowiredprivate SubjectService subjectService;//课程列表@Overridepublic Map<String,Object> findPage(Page<Course> pageParam, CourseQueryVo courseQueryVo) {//获取条件值String title = courseQueryVo.getTitle();//名称Long subjectId = courseQueryVo.getSubjectId();//二级分类Long subjectParentId = courseQueryVo.getSubjectParentId();//一级分类Long teacherId = courseQueryVo.getTeacherId();//讲师//封装条件QueryWrapper<Course> wrapper = new QueryWrapper<>();if(!StringUtils.isEmpty(title)) {wrapper.like("title",title);}if(!StringUtils.isEmpty(subjectId)) {wrapper.eq("subject_id",subjectId);}if(!StringUtils.isEmpty(subjectParentId)) {wrapper.eq("subject_parent_id",subjectParentId);}if(!StringUtils.isEmpty(teacherId)) {wrapper.eq("teacher_id",teacherId);}//调用方法查询Page<Course> pages = baseMapper.selectPage(pageParam, wrapper);long totalCount = pages.getTotal();//总记录数long totalPage = pages.getPages();//总页数long currentPage = pages.getCurrent();//当前页long size = pages.getSize();//每页记录数//每页数据集合List<Course> records = pages.getRecords();//遍历封装讲师和分类名称records.stream().forEach(item -> {this.getTeacherOrSubjectName(item);});//封装返回数据Map<String,Object> map = new HashMap<>();map.put("totalCount",totalCount);map.put("totalPage",totalPage);map.put("records",records);return map;}//获取讲师和分类名称private Course getTeacherOrSubjectName(Course course) {//查询讲师名称Teacher teacher = teacherService.getById(course.getTeacherId());if(teacher != null) {course.getParam().put("teacherName",teacher.getName());}//查询分类名称Subject subjectOne = subjectService.getById(course.getSubjectParentId());if(subjectOne != null) {course.getParam().put("subjectParentTitle",subjectOne.getTitle());}Subject subjectTwo = subjectService.getById(course.getSubjectId());if(subjectTwo != null) {course.getParam().put("subjectTitle",subjectTwo.getTitle());}return course;}
}
- 方法二
/*** 点播课程列表* @param coursePage* @param courseQueryVo* @return*/@Overridepublic Map<String, Object> findPageCourse(Page<Course> coursePage, CourseQueryVo courseQueryVo) {// 1. 获取条件// 讲师IdLong teacherId = courseQueryVo.getTeacherId();// 课程标题String title = courseQueryVo.getTitle();// 课程专业IDLong subjectId = courseQueryVo.getSubjectId();// 课程专业父级IDLong subjectParentId = courseQueryVo.getSubjectParentId();// 2. 封装查询条件LambdaQueryWrapper<Course> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.like(!StringUtils.isEmpty(title), Course::getTitle, title).eq(!StringUtils.isEmpty(teacherId), Course::getTeacherId, teacherId).eq(!StringUtils.isEmpty(subjectId), Course::getSubjectId, subjectId).eq(!StringUtils.isEmpty(subjectParentId), Course::getSubjectParentId, subjectParentId);// 3. 调用方法查询Page<Course> selectPage = baseMapper.selectPage(coursePage, queryWrapper);selectPage.getRecords().forEach(this::getTeacherOrSubjectName);// 4. 封装返回数据HashMap<String, Object> map = new HashMap<>();map.put("totalCount", selectPage.getTotal());map.put("totalPage", selectPage.getPages());map.put("records", selectPage.getRecords());return map;}private void getTeacherOrSubjectName(Course course) {//查询讲师名称Teacher teacher = teacherService.getById(course.getTeacherId());if (teacher != null) {course.getParam().put("teacherName",teacher.getName());}//查询分类名称Subject subjectOne = subjectService.getById(course.getSubjectParentId());if(subjectOne != null) {course.getParam().put("subjectParentTitle",subjectOne.getTitle());}Subject subjectTwo = subjectService.getById(course.getSubjectId());if(subjectTwo != null) {course.getParam().put("subjectTitle",subjectTwo.getTitle());}}
2. 开发课程列表前端
(1)src目录下index.js文件添加路由
// 课程管理{path: '/vod',component: Layout,redirect: '/vod/course/list',name: 'Vod',meta: {title: '点播管理',icon: 'el-icon-bank-card'},alwaysShow: true,children: [{path: 'course/list',name: 'CourseList',component: () => import('@/views/vod/course/list'),meta: { title: '课程列表' }},{path: 'course/info',name: 'CourseInfo',component: () => import('@/views/vod/course/form'),meta: { title: '发布课程' },hidden: true},{path: 'course/info/:id',name: 'CourseInfoEdit',component: () => import('@/views/vod/course/form'),meta: { title: '编辑课程' },hidden: true},{path: 'course/chapter/:id',name: 'CourseChapterEdit',component: () => import('@/views/vod/course/form'),meta: { title: '编辑大纲' },hidden: true},{path: 'course/chart/:id',name: 'CourseChart',component: () => import('@/views/vod/course/chart'),meta: { title: '课程统计' },hidden: true}]},
(2)创建vue页面
(3)在api目录创建course.js文件
import request from '@/utils/request'const api_name = '/admin/vod/course'export default {//课程列表getPageList(page, limit, searchObj) {return request({url: `${api_name}/${page}/${limit}`,method: 'get',params: searchObj})},
}
(4)在api目录teacher.js文件定义接口
import request from '@/utils/request'const api_name = '/admin/vod/teacher'export default {//......//所有讲师list() {return request({url: `${api_name}/findAll`,method: `get`})}
}
(5)编写list.vue页面
<template><div class="app-container"><!--查询表单--><el-card class="operate-container" shadow="never"><el-form :inline="true" class="demo-form-inline"><!-- 所属分类:级联下拉列表 --><!-- 一级分类 --><el-form-item label="课程类别"><el-selectv-model="searchObj.subjectParentId"placeholder="请选择"@change="subjectLevelOneChanged"><el-optionv-for="subject in subjectList":key="subject.id":label="subject.title":value="subject.id"/></el-select><!-- 二级分类 --><el-select v-model="searchObj.subjectId" placeholder="请选择"><el-optionv-for="subject in subjectLevelTwoList":key="subject.id":label="subject.title":value="subject.id"/></el-select></el-form-item><!-- 标题 --><el-form-item label="标题"><el-input v-model="searchObj.title" placeholder="课程标题"/></el-form-item><!-- 讲师 --><el-form-item label="讲师"><el-selectv-model="searchObj.teacherId"placeholder="请选择讲师"><el-optionv-for="teacher in teacherList":key="teacher.id":label="teacher.name":value="teacher.id"/></el-select></el-form-item><el-button type="primary" icon="el-icon-search" @click="fetchData()">查询</el-button><el-button type="default" @click="resetData()">清空</el-button></el-form></el-card><!-- 工具按钮 --><el-card class="operate-container" shadow="never"><i class="el-icon-tickets" style="margin-top: 5px"></i><span style="margin-top: 5px">数据列表</span><el-button class="btn-add" @click="add()">添加</el-button></el-card><!-- 表格 --><el-table :data="list" border stripe><el-table-column label="#" width="50"><template slot-scope="scope">{{ (page - 1) * limit + scope.$index + 1 }}</template></el-table-column><el-table-column label="封面" width="200" align="center"><template slot-scope="scope"><img :src="scope.row.cover" alt="scope.row.title" width="100%"></template></el-table-column><el-table-column label="课程信息"><template slot-scope="scope"><a href="">{{ scope.row.title }}</a><p>分类:{{ scope.row.param.subjectParentTitle }} > {{ scope.row.param.subjectTitle }}</p><p>课时:{{ scope.row.lessonNum }} /浏览:{{ scope.row.viewCount }} /付费学员:{{ scope.row.buyCount }}</p></template></el-table-column><el-table-column label="讲师" width="100" align="center"><template slot-scope="scope">{{ scope.row.param.teacherName }}</template></el-table-column><el-table-column label="价格(元)" width="100" align="center" ><template slot-scope="scope"><!-- {{ typeof '0' }} {{ typeof 0 }} {{ '0' == 0 }} --><!-- {{ typeof scope.row.price }}{{ typeof Number(scope.row.price) }}{{ typeof Number(scope.row.price).toFixed(2) }} --><el-tag v-if="Number(scope.row.price) === 0" type="success">免费</el-tag><!-- 前端解决保留两位小数的问题 --><!-- <el-tag v-else>{{ Number(scope.row.price).toFixed(2) }}</el-tag> --><!-- 后端解决保留两位小数的问题,前端不用处理 --><el-tag v-else>{{ scope.row.price }}</el-tag></template></el-table-column><el-table-column prop="status" label="课程状态" width="100" align="center" ><template slot-scope="scope"><el-tag :type="scope.row.status === 0 ? 'warning' : 'success'">{{ scope.row.status === 0 ? '未发布' : '已发布' }}</el-tag></template></el-table-column><el-table-column label="发布时间" width="140" align="center"><template slot-scope="scope">{{ scope.row.createTime ? scope.row.createTime.substr(0, 16) : '' }}</template></el-table-column><el-table-column label="操作" width="210" align="center"><template slot-scope="scope"><router-link :to="'/vodcourse/course/info/'+scope.row.id"><el-button type="text" icon="el-icon-edit" >修改</el-button></router-link><router-link :to="'/vodcourse/course/chapter/'+scope.row.id"><el-button type="text" icon="el-icon-edit" >编辑大纲</el-button></router-link><router-link :to="'/vodcourse/course/chart/'+scope.row.id"><el-button type="text" icon="el-icon-edit">课程统计</el-button></router-link><el-button type="text" icon="el-icon-delete" @click="removeById(scope.row.id)" >删除</el-button></template></el-table-column></el-table><!-- 分页组件 --><el-pagination:current-page="page":total="total":page-size="limit":page-sizes="[5, 10, 20, 30, 40, 50, 100]"style="padding: 30px 0; text-align: center;"layout="total, sizes, prev, pager, next, jumper"@size-change="changePageSize"@current-change="changeCurrentPage"/></div>
</template><script>
import courseApi from '@/api/vod/course'
import teacherApi from '@/api/vod/teacher'
import subjectApi from '@/api/vod/subject'export default {data() {return {list: [], // 课程列表total: 0, // 总记录数page: 1, // 页码limit: 10, // 每页记录数searchObj: {subjectId: ''// 解决查询表单无法选中二级类别}, // 查询条件teacherList: [], // 讲师列表subjectList: [], // 一级分类列表subjectLevelTwoList: [] // 二级分类列表,}},created() {this.fetchData()// 初始化分类列表this.initSubjectList()// 获取讲师列表this.initTeacherList()},methods: {fetchData() {courseApi.getPageList(this.page, this.limit, this.searchObj).then(response => {this.list = response.data.recordsconsole.log(this.list)this.total = response.data.totalCount})},initTeacherList() {teacherApi.list().then(response => {this.teacherList = response.data})},initSubjectList() {subjectApi.getChildList(0).then(response => {this.subjectList = response.data})},subjectLevelOneChanged(value) {subjectApi.getChildList(value).then(response => {this.subjectLevelTwoList = response.datathis.searchObj.subjectId = ''})},add() {this.$router.push({ path: '/vod/course/info' })},// 每页记录数改变,size:回调参数,表示当前选中的“每页条数”changePageSize(size) {this.limit = sizethis.fetchData()},// 改变页码,page:回调参数,表示当前选中的“页码”changeCurrentPage(page) {this.page = pagethis.fetchData()},// 重置表单resetData() {this.searchObj = {}this.subjectLevelTwoList = [] // 二级分类列表this.fetchData()}}
}
</script>
二、发布课程-填写课程基本信息
①:界面效果
②:添加课程基本信息接口
1. 创建课程描述的service和mapper
2. 创建添加课程基本信息接口
(1)controller
//添加课程基本信息
@ApiOperation(value = "新增")
@PostMapping("save")
public Result save(@RequestBody CourseFormVo courseFormVo) {Long courseId = courseService.saveCourseInfo(courseFormVo);return Result.ok(courseId);
}
(2)service
/*** 添加课程信息* @param courseFormVo* @return*/@Overridepublic Long saveCourseInfo(CourseFormVo courseFormVo) {// 1. 保存课程的基本信息Course course = new Course();BeanUtils.copyProperties(courseFormVo, course);baseMapper.insert(course);// 2. 保存课程信息CourseDescription courseDescription = new CourseDescription();courseDescription.setDescription(courseFormVo.getDescription());courseDescription.setId(course.getId());descriptionService.save(courseDescription);return course.getId();}
③:添加课程基本信息前端
1. 课程列表list.vue添加方法
add() {this.$router.push({ path: '/vod/course/info' })
},
2. course.js定义接口
//添加课程基本信息
saveCourseInfo(courseInfo) {return request({url: `${api_name}/save`,method: 'post',data: courseInfo})
},
3. 创建课程基本信息添加页面
(1)form.vue
<template><div class="app-container"><h2 style="text-align: center;">发布新课程</h2><el-steps :active="active" finish-status="success" simple style="margin-bottom: 40px"><el-step title="填写课程基本信息" /><el-step title="创建课程大纲" /><el-step title="发布课程" /></el-steps><!-- 填写课程基本信息 --><info v-if="active === 0" /><!-- 创建课程大纲 --><chapter v-if="active === 1" /><!-- 发布课程 --><Publish v-if="active === 2 || active === 3" /></div>
</template><script>
// 引入子组件
import Info from '@/views/vod/course/components/Info'
import Chapter from '@/views/vod/course/components/Chapter'
import Publish from '@/views/vod/course/components/Publish'export default {components: { Info, Chapter, Publish }, // 注册子组件data() {return {active: 0,courseId: null}},created() {// 获取路由idif (this.$route.params.id) {this.courseId = this.$route.params.id}if (this.$route.name === 'CourseInfoEdit') {this.active = 0}if (this.$route.name === 'CourseChapterEdit') {this.active = 1}}
}
</script>
(2)Info.vue
<template><div class="app-container"><!-- 课程信息表单 --><el-form label-width="120px"><el-form-item label="课程标题"><el-input v-model="courseInfo.title" placeholder=" 示例:机器学习项目课:从基础到搭建项目视频课程。专业名称注意大小写"/></el-form-item><!-- 课程讲师 --><el-form-item label="课程讲师"><el-selectv-model="courseInfo.teacherId"placeholder="请选择"><el-optionv-for="teacher in teacherList":key="teacher.id":label="teacher.name":value="teacher.id"/></el-select></el-form-item><!-- 所属分类:级联下拉列表 --><el-form-item label="课程类别"><!-- 一级分类 --><el-selectv-model="courseInfo.subjectParentId"placeholder="请选择"@change="subjectChanged"><el-optionv-for="subject in subjectList":key="subject.id":label="subject.title":value="subject.id"/></el-select><!-- 二级分类 --><el-select v-model="courseInfo.subjectId" placeholder="请选择"><el-optionv-for="subject in subjectLevelTwoList":key="subject.id":label="subject.title":value="subject.id"/></el-select></el-form-item><el-form-item label="总课时"><el-input-number :min="0" v-model="courseInfo.lessonNum" controls-position="right" placeholder="请填写课程的总课时数"/></el-form-item><!-- 课程简介--><el-form-item label="课程简介"><el-input v-model="courseInfo.description" type="textarea" rows="5"/></el-form-item><!-- 课程封面 --><el-form-item label="课程封面"><el-upload:show-file-list="false":on-success="handleCoverSuccess":before-upload="beforeCoverUpload":on-error="handleCoverError":action="BASE_API+'/admin/vod/file/upload?module=cover'"class="cover-uploader"><img v-if="courseInfo.cover" :src="courseInfo.cover"><i v-else class="el-icon-plus avatar-uploader-icon"/></el-upload></el-form-item><el-form-item label="课程价格"><el-input-number :min="0" v-model="courseInfo.price" controls-position="right" placeholder="免费课程请设置为0元"/> 元</el-form-item></el-form><div style="text-align:center"><el-button :disabled="saveBtnDisabled" type="primary" @click="saveAndNext()">保存并下一步</el-button></div></div>
</template>
<script>
import courseApi from '@/api/vod/course'
import teacherApi from '@/api/vod/teacher'
import subjectApi from '@/api/vod/subject'export default {data() {return {BASE_API: 'http://localhost:8301',saveBtnDisabled: false, // 按钮是否禁用courseInfo: {// 表单数据price: 0,lessonNum: 0,// 以下解决表单数据不全时insert语句非空校验teacherId: '',subjectId: '',subjectParentId: '',cover: '',description: ''},teacherList: [], // 讲师列表subjectList: [], // 一级分类列表subjectLevelTwoList: []// 二级分类列表}},created() {// 初始化分类列表this.initSubjectList()// 获取讲师列表this.initTeacherList()},methods: {// 获取讲师列表initTeacherList() {teacherApi.list().then(response => {this.teacherList = response.data})},// 初始化分类列表initSubjectList() {subjectApi.getChildList(0).then(response => {this.subjectList = response.data})},// 选择一级分类,切换二级分类subjectChanged(value) {subjectApi.getChildList(value).then(response => {this.courseInfo.subjectId = ''this.subjectLevelTwoList = response.data})},// 上传成功回调handleCoverSuccess(res, file) {this.courseInfo.cover = res.data},// 上传校验beforeCoverUpload(file) {const isJPG = file.type === 'image/jpeg'const isLt2M = file.size / 1024 / 1024 < 2if (!isJPG) {this.$message.error('上传头像图片只能是 JPG 格式!')}if (!isLt2M) {this.$message.error('上传头像图片大小不能超过 2MB!')}return isJPG && isLt2M},// 错误处理handleCoverError() {console.log('error')this.$message.error('上传失败2')},// 保存并下一步saveAndNext() {this.saveBtnDisabled = trueif (!this.$parent.courseId) {this.saveData()} else {//this.updateData()}},// 保存saveData() {courseApi.saveCourseInfo(this.courseInfo).then(response => {this.$message.success(response.message)this.$parent.courseId = response.data // 获取courseIdthis.$parent.active = 1 // 下一步})}}
}
</script>
<style scoped>
.tinymce-container {position: relative;line-height: normal;
}
.cover-uploader .avatar-uploader-icon {border: 1px dashed #d9d9d9;border-radius: 6px;cursor: pointer;position: relative;overflow: hidden;font-size: 28px;color: #8c939d;width: 640px;height: 357px;line-height: 357px;text-align: center;
}
.cover-uploader .avatar-uploader-icon:hover {border-color: #409EFF;
}
.cover-uploader img {width: 640px;height: 357px;display: block;
}
</style>
三、发布课程-修改课程基本信息
③:修改课程基本信息接口
1. CourseService定义方法
//根据id获取课程信息CourseFormVo getCourseFormVoById(Long id);//根据id修改课程信息void updateCourseById(CourseFormVo courseFormVo);
2. CourseServiceImpl实现方法
//根据id获取课程信息@Overridepublic CourseFormVo getCourseFormVoById(Long id) {//从course表中取数据Course course = baseMapper.selectById(id);if(course == null){return null;}//从course_description表中取数据CourseDescription courseDescription = descriptionService.getById(id);//创建courseInfoForm对象CourseFormVo courseFormVo = new CourseFormVo();BeanUtils.copyProperties(course, courseFormVo);if(courseDescription != null){courseFormVo.setDescription(courseDescription.getDescription());}return courseFormVo;}//根据id修改课程信息@Overridepublic void updateCourseById(CourseFormVo courseFormVo) {//修改课程基本信息Course course = new Course();BeanUtils.copyProperties(courseFormVo, course);baseMapper.updateById(course);//修改课程详情信息CourseDescription courseDescription = descriptionService.getById(course.getId());courseDescription.setDescription(courseFormVo.getDescription());description.setId(course.getId());descriptionService.updateById(courseDescription);}
3. CourseController实现方法
@ApiOperation(value = "获取")@GetMapping("get/{id}")public Result get(@PathVariable Long id) {CourseFormVo course = courseService.getCourseFormVoById(id);return Result.ok(course);}@ApiOperation(value = "修改")@PutMapping("update")public Result updateById(@RequestBody CourseFormVo courseFormVo) {courseService.updateCourseById(courseFormVo);return Result.ok();}
②:修改课程基本信息前端
1. course.js定义方法
//id获取课程信息getCourseInfoById(id) {return request({url: `${api_name}/get/${id}`,method: 'get'})},//修改课程信息updateCourseInfoById(courseInfo) {return request({url: `${api_name}/update`,method: 'put',data: courseInfo})},
2. 修改Info.vue页面
<script>
....
created() {if (this.$parent.courseId) { // 回显this.fetchCourseInfoById(this.$parent.courseId)} else { // 新增// 初始化分类列表this.initSubjectList()}// 获取讲师列表this.initTeacherList()},methods: {......// 获取课程信息fetchCourseInfoById(id) {courseApi.getCourseInfoById(id).then(response => {this.courseInfo = response.data// 初始化分类列表subjectApi.getChildList(0).then(response => {this.subjectList = response.data// 填充二级菜单:遍历一级菜单列表,this.subjectList.forEach(subject => {// 找到和courseInfo.subjectParentId一致的父类别记录if (subject.id === this.courseInfo.subjectParentId) {// 拿到当前类别下的子类别列表,将子类别列表填入二级下拉菜单列表subjectApi.getChildList(subject.id).then(response => {this.subjectLevelTwoList = response.data})}})})})},// 保存并下一步saveAndNext() {this.saveBtnDisabled = trueif (!this.$parent.courseId) {this.saveData()} else {this.updateData()}},// 修改updateData() {courseApi.updateCourseInfoById(this.courseInfo).then(response => {this.$message.success(response.message)this.$parent.courseId = response.data // 获取courseIdthis.$parent.active = 1 // 下一步})},......}
}
</script>
3. 创建Chapter-index.vue页面
<template><div class="app-container"><div style="text-align:center"><el-button type="primary" @click="prev()">上一步</el-button><el-button type="primary" @click="next()">下一步</el-button></div></div>
</template>
<script>
export default {data() {return {}},created() {},methods: {// 上一步prev() {this.$parent.active = 0},// 下一步next() {this.$parent.active = 2}}
}
</script>
<style scoped>
.chapterList{position: relative;list-style: none;margin: 0;padding: 0;
}
.chapterList li{position: relative;
}
.chapterList p{float: left;font-size: 20px;margin: 10px 0;padding: 10px;height: 70px;line-height: 50px;width: 100%;border: 1px solid #DDD;
}
.chapterList .acts {float: right;font-size: 14px;
}
.videoList{padding-left: 50px;
}
.videoList p{float: left;font-size: 14px;margin: 10px 0;padding: 10px;height: 50px;line-height: 30px;width: 100%;border: 1px dashed #DDD;
}
</style>
- 测试(可以修改成功)
四、发布课程-创建课程大纲
①:课程章节接口
实现课程章节的列表、添加、修改和删除功能
1. 编写章节Controller
@RestController
@RequestMapping(value="/admin/vod/chapter")
@CrossOrigin
public class ChapterController {@Autowiredprivate ChapterService chapterService;//获取章节小结列表@ApiOperation("嵌套章节数据列表")@GetMapping("getNestedTreeList/{courseId}")public Result getNestedTreeList(@ApiParam(value = "课程ID", required = true)@PathVariable Long courseId){List<ChapterVo> chapterVoList = chapterService.getNestedTreeList(courseId);return Result.ok(chapterVoList);}//2 添加章节@PostMapping("save")public Result save(@RequestBody Chapter chapter) {chapterService.save(chapter);return Result.ok(null);}//3 修改-根据id查询@GetMapping("get/{id}")public Result get(@PathVariable Long id) {Chapter chapter = chapterService.getById(id);return Result.ok(chapter);}//4 修改-最终实现@PostMapping("update")public Result update(@RequestBody Chapter chapter) {chapterService.updateById(chapter);return Result.ok(null);}//5 删除章节@DeleteMapping("remove/{id}")public Result remove(@PathVariable Long id) {chapterService.removeById(id);return Result.ok(null);}
}
2. 编写章节Service
(1)ChapterService
public interface ChapterService extends IService<Chapter> {//章节小结列表List<ChapterVo> getNestedTreeList(Long courseId);
}
(2)ChapterServiceImpl
@Service
public class ChapterServiceImpl extends ServiceImpl<ChapterMapper, Chapter> implements ChapterService {@Autowiredprivate VideoService videoService;//章节小结列表封装@Overridepublic List<ChapterVo> getNestedTreeList(Long courseId) {List<ChapterVo> chapterVoList = new ArrayList<>();//获取章信息LambdaQueryWrapper<Chapter> queryWrapperChapter = new LambdaQueryWrapper<>();queryWrapperChapter.eq(Chapter::getCourseId, courseId);queryWrapperChapter.orderByAsc(Chapter::getSort, Chapter::getId);List<Chapter> chapterList = baseMapper.selectList(queryWrapperChapter);//获取课时信息LambdaQueryWrapper<Video> queryWrapperVideo = new LambdaQueryWrapper<>();queryWrapperVideo.eq(Video::getCourseId, courseId);queryWrapperVideo.orderByAsc(Video::getSort, Video::getId);List<Video> videoList = videoService.list(queryWrapperVideo);//填充列表数据:Chapter列表for (int i = 0; i < chapterList.size(); i++) {Chapter chapter = chapterList.get(i);//创建ChapterVo对象ChapterVo chapterVo = new ChapterVo();BeanUtils.copyProperties(chapter, chapterVo);chapterVoList.add(chapterVo);//填充列表数据:Video列表List<VideoVo> videoVoList = new ArrayList<>();for (int j = 0; j < videoList.size(); j++) {Video video = videoList.get(j);if(chapter.getId().equals(video.getChapterId())){VideoVo videoVo = new VideoVo();BeanUtils.copyProperties(video, videoVo);videoVoList.add(videoVo);}}chapterVo.setChildren(videoVoList);}return chapterVoList;}
}
②:课程小节接口
1. 编写VideoController
@Api(tags = "课程小结(课时)")
@RestController
@RequestMapping(value="/admin/vod/video")
@CrossOrigin
public class VideoController {@Autowiredprivate VideoService videoService;@ApiOperation(value = "获取")@GetMapping("get/{id}")public Result get(@PathVariable Long id) {Video video = videoService.getById(id);return Result.ok(video);}@ApiOperation(value = "新增")@PostMapping("save")public Result save(@RequestBody Video video) {videoService.save(video);return Result.ok(null);}@ApiOperation(value = "修改")@PutMapping("update")public Result updateById(@RequestBody Video video) {videoService.updateById(video);return Result.ok(null);}@ApiOperation(value = "删除")@DeleteMapping("remove/{id}")public Result remove(@PathVariable Long id) {videoService.removeById(id);return Result.ok(null);}
}
③:课程大纲前端
1. 定义接口
(1)chapter.js
import request from '@/utils/request'const api_name = '/admin/vod/chapter'export default {getNestedTreeList(courseId) {return request({url: `${api_name}/getNestedTreeList/${courseId}`,method: 'get'})},removeById(id) {return request({url: `${api_name}/remove/${id}`,method: 'delete'})},save(chapter) {return request({url: `${api_name}/save`,method: 'post',data: chapter})},getById(id) {return request({url: `${api_name}/get/${id}`,method: 'get'})},updateById(chapter) {return request({url: `${api_name}/update`,method: 'put',data: chapter})}
}
(2)创建video.js
import request from '@/utils/request'const api_name = '/admin/vod/video'export default {save(video) {return request({url: `${api_name}/save`,method: 'post',data: video})},getById(id) {return request({url: `${api_name}/get/${id}`,method: 'get'})},updateById(video) {return request({url: `${api_name}/update`,method: 'put',data: video})},removeById(id) {return request({url: `${api_name}/remove/${id}`,method: 'delete'})}
}
2. 编写章节页面
(1)Chapter -> index.vue
<template><div class="app-container"><!-- 添加章节按钮 --><div><el-button type="primary" @click="addChapter()">添加章节</el-button></div><!-- 章节列表 --><ul class="chapterList"><liv-for="chapter in chapterList":key="chapter.id"><p>{{ chapter.title }}<span class="acts"><el-button type="text" @click="addVideo(chapter.id)">添加课时</el-button><el-button type="text" @click="editChapter(chapter.id)">编辑</el-button><el-button type="text" @click="removeChapterById(chapter.id)">删除</el-button></span></p><!-- 视频 --><ul class="chapterList videoList"><liv-for="video in chapter.children":key="video.id"><p>{{ video.title }}<el-tag v-if="!video.videoSourceId" size="mini" type="danger">{{ '尚未上传视频' }}</el-tag><span class="acts"><el-tag v-if="video.isFree" size="mini" type="success">{{ '免费观看' }}</el-tag><el-button type="text" @click="editVideo(chapter.id, video.id)">编辑</el-button><el-button type="text" @click="removeVideoById(video.id)">删除</el-button></span></p></li></ul></li></ul><!-- 章节表单对话框 --><chapter-form ref="chapterForm" /><!-- 课时表单对话框 --><video-form ref="videoForm" /><div style="text-align:center"><el-button type="primary" @click="prev()">上一步</el-button><el-button type="primary" @click="next()">下一步</el-button></div></div>
</template>
<script>
import chapterApi from '@/api/vod/chapter'
import videoApi from '@/api/vod/video'// 引入组件
import ChapterForm from '@/views/vod/course/components/Chapter/Form'
import VideoForm from '@/views/vod/course/components/Video/Form'export default {// 注册组件components: { ChapterForm, VideoForm },data() {return {chapterList: [] // 章节嵌套列表}},created() {this.fetchNodeList()},methods: {// 获取章节小节数据fetchNodeList() {chapterApi.getNestedTreeList(this.$parent.courseId).then(response => {this.chapterList = response.data})},//删除章节removeChapterById(chapterId) {this.$confirm('此操作将永久删除该章节,是否继续?', '提示', {confirmButtonText: '确定',cancelButtonText: '取消',type: 'warning'}).then(() => {return chapterApi.removeById(chapterId)}).then(response => {this.fetchNodeList()this.$message.success(response.message)}).catch((response) => {if (response === 'cancel') {this.$message.info('取消删除')}})},// 添加章节addChapter() {this.$refs.chapterForm.open()},// 编辑章节editChapter(chapterId) {this.$refs.chapterForm.open(chapterId)},// 添加课时addVideo(chapterId) {this.$refs.videoForm.open(chapterId)},// 编辑课时editVideo(chapterId, videoId) {this.$refs.videoForm.open(chapterId, videoId)},// 删除课时removeVideoById(videoId) {this.$confirm('此操作将永久删除该课时, 是否继续?', '提示', {confirmButtonText: '确定',cancelButtonText: '取消',type: 'warning'}).then(() => {return videoApi.removeById(videoId)}).then(response => {this.fetchNodeList()this.$message.success(response.message)}).catch((response) => {if (response === 'cancel') {this.$message.info('取消删除')}})},// 上一步prev() {this.$parent.active = 0},// 下一步next() {this.$parent.active = 2}}
}
</script>
<style scoped>
.chapterList{position: relative;list-style: none;margin: 0;padding: 0;
}
.chapterList li{position: relative;
}
.chapterList p{float: left;font-size: 20px;margin: 10px 0;padding: 10px;height: 70px;line-height: 50px;width: 100%;border: 1px solid #DDD;
}
.chapterList .acts {float: right;font-size: 14px;
}.videoList{padding-left: 50px;
}
.videoList p{float: left;font-size: 14px;margin: 10px 0;padding: 10px;height: 50px;line-height: 30px;width: 100%;border: 1px dashed #DDD;
}
</style>
(2)Chapter -> Form.vue
<template><!-- 添加和修改章节表单 --><el-dialog :visible="dialogVisible" title="添加章节" @close="close()"><el-form :model="chapter" label-width="120px"><el-form-item label="章节标题"><el-input v-model="chapter.title"/></el-form-item><el-form-item label="章节排序"><el-input-number v-model="chapter.sort" :min="0"/></el-form-item></el-form><div slot="footer" class="dialog-footer"><el-button @click="close()">取 消</el-button><el-button type="primary" @click="saveOrUpdate()">确 定</el-button></div></el-dialog>
</template><script>
import chapterApi from '@/api/vod/chapter'
export default {data() {return {dialogVisible: false,chapter: {sort: 0}}},methods: {open(chapterId) {this.dialogVisible = trueif (chapterId) {chapterApi.getById(chapterId).then(response => {this.chapter = response.data})}},close() {this.dialogVisible = false// 重置表单this.resetForm()},resetForm() {this.chapter = {sort: 0}},saveOrUpdate() {if (!this.chapter.id) {this.save()} else {this.update()}},save() {this.chapter.courseId = this.$parent.$parent.courseIdchapterApi.save(this.chapter).then(response => {this.$message.success(response.message)// 关闭组件this.close()// 刷新列表this.$parent.fetchNodeList()})},update() {chapterApi.updateById(this.chapter).then(response => {this.$message.success(response.message)// 关闭组件this.close()// 刷新列表this.$parent.fetchNodeList()})}}
}
</script>
3. 编写小节(课时)页面
(1)Video -> Form.vue
<template><!-- 添加和修改课时表单 --><el-dialog :visible="dialogVisible" title="添加课时" @close="close()"><el-form :model="video" label-width="120px"><el-form-item label="课时标题"><el-input v-model="video.title"/></el-form-item><el-form-item label="课时排序"><el-input-number v-model="video.sort" :min="0" /></el-form-item><el-form-item label="是否免费"><el-radio-group v-model="video.isFree"><el-radio :label="0">免费</el-radio><el-radio :label="1">默认</el-radio></el-radio-group></el-form-item><!-- 上传视频 --><el-form-item label="上传视频"><el-uploadref="upload":auto-upload="false":on-success="handleUploadSuccess":on-error="handleUploadError":on-exceed="handleUploadExceed":file-list="fileList":limit="1":before-remove="handleBeforeRemove":on-remove="handleOnRemove":action="BASE_API+'/admin/vod/upload'"><el-button slot="trigger" size="small" type="primary">选择视频</el-button><el-button:disabled="uploadBtnDisabled"style="margin-left: 10px;"size="small"type="success"@click="submitUpload()">上传</el-button></el-upload></el-form-item></el-form><div slot="footer" class="dialog-footer"><el-button @click="close()">取 消</el-button><el-button type="primary" @click="saveOrUpdate()">确 定</el-button></div></el-dialog>
</template><script>
import videoApi from '@/api/vod/video'
//import vodApi from '@/api/vod/vod'
export default {data() {return {BASE_API: 'http://localhost:8301',dialogVisible: false,video: {sort: 0,free: false},fileList: [], // 上传文件列表uploadBtnDisabled: false}},methods: {open(chapterId, videoId) {this.dialogVisible = truethis.video.chapterId = chapterIdif (videoId) {videoApi.getById(videoId).then(response => {this.video = response.data// 回显if (this.video.videoOriginalName) {this.fileList = [{ 'name': this.video.videoOriginalName }]}})}},close() {this.dialogVisible = false// 重置表单this.resetForm()},resetForm() {this.video = {sort: 0,free: false}this.fileList = [] // 重置视频上传列表},saveOrUpdate() {if (!this.video.id) {this.save()} else {this.update()}},save() {this.video.courseId = this.$parent.$parent.courseIdvideoApi.save(this.video).then(response => {this.$message.success(response.message)// 关闭组件this.close()// 刷新列表this.$parent.fetchNodeList()})},update() {videoApi.updateById(this.video).then(response => {this.$message.success(response.message)// 关闭组件this.close()// 刷新列表this.$parent.fetchNodeList()})},// 上传多于一个视频handleUploadExceed(files, fileList) {this.$message.warning('想要重新上传视频,请先删除已上传的视频')},// 上传submitUpload() {this.uploadBtnDisabled = truethis.$refs.upload.submit() // 提交上传请求},// 视频上传成功的回调handleUploadSuccess(response, file, fileList) {this.uploadBtnDisabled = falsethis.video.videoSourceId = response.datathis.video.videoOriginalName = file.name},// 失败回调handleUploadError() {this.uploadBtnDisabled = falsethis.$message.error('上传失败2')},// 删除视频文件确认handleBeforeRemove(file, fileList) {return this.$confirm(`确定移除 ${file.name}?`)},// 执行视频文件的删除handleOnRemove(file, fileList) {if (!this.video.videoSourceId) {return}}}
}
</script>
④:测试
1.添加新的课程
2.添加章节
3. 添加小节
五、发布课程-课程最终发布
①:课程最终发布接口
1. 编写CourseController
添加方法
@ApiOperation("根据id获取课程发布信息")@GetMapping("getCoursePublishVo/{id}")public Result getCoursePublishVoById(@ApiParam(value = "课程ID", required = true)@PathVariable Long id){CoursePublishVo coursePublishVo = courseService.getCoursePublishVo(id);return Result.ok(coursePublishVo);}@ApiOperation("根据id发布课程")@PutMapping("publishCourseById/{id}")public Result publishCourseById(@ApiParam(value = "课程ID", required = true)@PathVariable Long id){boolean result = courseService.publishCourseById(id);return Result.ok();}
2. 编写CourseService
//根据id获取课程发布信息
CoursePublishVo getCoursePublishVo(Long id);//根据id发布课程
boolean publishCourseById(Long id);
3. 编写CourseServiceImpl
//根据id获取课程发布信息
@Override
public CoursePublishVo getCoursePublishVo(Long id) {return courseMapper.selectCoursePublishVoById(id);
}//根据id发布课程
@Override
public boolean publishCourseById(Long id) {Course course = new Course();course.setId(id);course.setPublishTime(new Date());course.setStatus(1);return this.updateById(course);
}
4. 编写CourseMapper
public interface CourseMapper extends BaseMapper<Course> {//根据id获取课程发布信息 CoursePublishVo selectCoursePublishVoById(Long id);
}
5. 编写CourseMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.atguigu.ggkt.vod.mapper.CourseMapper"><select id="selectCoursePublishVoById" resultType="com.atguigu.ggkt.vo.vod.CoursePublishVo">SELECTc.id,c.title,c.cover,c.lesson_num AS lessonNum,c.price,t.name AS teacherName,s1.title AS subjectParentTitle,s2.title AS subjectTitleFROM course cLEFT OUTER JOIN teacher t ON c.teacher_id=t.idLEFT OUTER JOIN `subject` s1 ON c.subject_parent_id=s1.idLEFT OUTER JOIN `subject` s2 ON c.subject_id=s2.idWHERE c.id=#{id}</select>
</mapper>
6. 添加配置
(1)application.properties添加
mybatis-plus.mapper-locations=classpath:com/it/vod/mapper/xml/*.xml
(2)service模块pom.xml添加
<build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins><resources><resource><directory>src/main/java</directory><includes><include>**/*.yml</include><include>**/*.properties</include><include>**/*.xml</include></includes><filtering>false</filtering></resource><resource><directory>src/main/resources</directory><includes> <include>**/*.yml</include><include>**/*.properties</include><include>**/*.xml</include></includes><filtering>false</filtering></resource></resources>
</build>
- 测试
②:课程最终发布前端
1. course.js定义接口
//获取发布课程信息
getCoursePublishById(id) {return request({url: `${api_name}/getCoursePublishVo/${id}`,method: 'get'})
},
//发布课程
publishCourseById(id) {return request({url: `${api_name}/publishCourseById/${id}`,method: 'put'})
},
2. 编写Publish.vue
<template><div class="app-container"><!--课程预览--><div class="ccInfo"><img :src="coursePublish.cover"><div class="main"><h2>{{ coursePublish.title }}</h2><p class="gray"><span>共{{ coursePublish.lessonNum }}课时</span></p><p><span>所属分类:{{ coursePublish.subjectParentTitle }} — {{ coursePublish.subjectTitle }}</span></p><p>课程讲师:{{ coursePublish.teacherName }}</p><h3 class="red">¥{{ coursePublish.price }}</h3></div></div><div style="text-align:center"><el-button type="primary" @click="prev()">上一步</el-button><el-button :disabled="publishBtnDisabled" type="primary" @click="publish()">发布课程</el-button></div></div>
</template><script>
import courseApi from '@/api/vod/course'export default {data() {return {publishBtnDisabled: false, // 按钮是否禁用coursePublish: {}}},created() {if (this.$parent.courseId) {this.fetchCoursePublishById(this.$parent.courseId)}},methods: {// 获取课程发布信息fetchCoursePublishById(id) {courseApi.getCoursePublishById(id).then(response => {this.coursePublish = response.data})},// 上一步prev() {this.$parent.active = 1},// 下一步publish() {this.publishBtnDisabled = truecourseApi.publishCourseById(this.$parent.courseId).then(response => {this.$parent.active = 3this.$message.success(response.message)this.$router.push({ path: '/vodcourse/course/list' })})}}
}
</script>
<style scoped>
.ccInfo {background: #f5f5f5;padding: 20px;overflow: hidden;border: 1px dashed #DDD;margin-bottom: 40px;position: relative;
}
.ccInfo img {background: #d6d6d6;width: 500px;height: 278px;display: block;float: left;border: none;
}
.ccInfo .main {margin-left: 520px;
}
.ccInfo .main h2 {font-size: 28px;margin-bottom: 30px;line-height: 1;font-weight: normal;
}
.ccInfo .main p {margin-bottom: 10px;word-wrap: break-word;line-height: 24px;max-height: 48px;overflow: hidden;
}
.ccInfo .main p {margin-bottom: 10px;word-wrap: break-word;line-height: 24px;max-height: 48px;overflow: hidden;
}
.ccInfo .main h3 {left: 540px;bottom: 20px;line-height: 1;font-size: 28px;color: #d32f24;font-weight: normal;position: absolute;
}
</style>
- 测试(课程发布成功)
六、功能实现-课程删除
①:课程删除接口
1. 编写课程Controller
@ApiOperation(value = "删除课程")
@DeleteMapping("remove/{id}")
public Result remove(@PathVariable Long id) {courseService.removeCourseById(id);return Result.ok();
}
2. 编写课程Service
- 方法一:
/*** 课程删除功能* @param id*/@Overridepublic void removeCourseById(Long id) {// 1. 删除课程小节LambdaUpdateWrapper<Video> updateWrap = new LambdaUpdateWrapper<>();updateWrap.eq(Video ::getCourseId,id);videoService.remove(updateWrap);// 2. 删除课程章节LambdaUpdateWrapper<Chapter> wrapper = new LambdaUpdateWrapper<>();wrapper.eq(Chapter::getCourseId,id);chapterService.remove(wrapper);// 3. 删除课程描述LambdaUpdateWrapper<CourseDescription> updateWrapper = new LambdaUpdateWrapper<>();updateWrapper.eq(CourseDescription :: getCourseId,id);descriptionService.remove(updateWrapper);// 4. 删除课程baseMapper.deleteById(id);}
- 方法二:
//删除课程@Overridepublic void removeCourseById(Long id) {//根据课程id删除小节videoService.removeVideoByCourseId(id);//根据课程id删除章节chapterService.removeChapterByCourseId(id);//根据课程id删除描述descriptionService.removeById(id);//根据课程id删除课程baseMapper.deleteById(id);}
3. 编写VideoService
@Service
public class VideoServiceImpl extends ServiceImpl<VideoMapper, Video> implements VideoService {//根据课程id删除小节@Overridepublic void removeVideoByCourseId(Long id) {QueryWrapper<Video> wrapper = new QueryWrapper<>();wrapper.eq("course_id",id);baseMapper.delete(wrapper);}
}
4. 编写ChapterService
//根据课程id删除章节@Overridepublic void removeChapterByCourseId(Long id) {QueryWrapper<Chapter> wrapper = new QueryWrapper<>();wrapper.eq("course_id",id);baseMapper.delete(wrapper);}
②:课程删除前端
1. course.js定义接口
removeById(id) {return request({url: `${api_name}/remove/${id}`,method: 'delete'})},
2. course -> list.vue添加方法
methods: {......// 根据id删除数据removeById(id) {this.$confirm('此操作将永久删除该课程,以及该课程下的章节和视频,是否继续?', '提示', {confirmButtonText: '确定',cancelButtonText: '取消',type: 'warning'}).then(() => {return courseApi.removeById(id)}).then(response => {this.fetchData()this.$message.success(response.message)}).catch((response) => { // 失败if (response === 'cancel') {this.$message.info('取消删除')}})}
}
- 经过测试可以正常删除
七、点播管理模块-课程统
①:课程统计需求
②:课程统计接
1. 创建相关代码
2. 编写Controller
VideoVisitorController添加
@Api(value = "VideoVisitor管理", tags = "VideoVisitor管理")
@RestController
@RequestMapping(value="/admin/vod/videoVisitor")
@CrossOrigin
public class VideoVisitorController {@Autowiredprivate VideoVisitorService videoVisitorService;@ApiOperation("显示统计数据")@GetMapping("findCount/{courseId}/{startDate}/{endDate}")public Result showChart(@ApiParam("开始时间") @PathVariable Long courseId,@ApiParam("开始时间") @PathVariable String startDate,@ApiParam("结束时间") @PathVariable String endDate){Map<String, Object> map = videoVisitorService.findCount(courseId, startDate, endDate);return Result.ok(map);}
}
3. 编写Service和实现
VideoVisitorService和VideoVisitorServiceImpl
@Service
public class VideoVisitorServiceImpl extends ServiceImpl<VideoVisitorMapper, VideoVisitor> implements VideoVisitorService {//课程统计的接口@Overridepublic Map<String, Object> findCount(Long courseId, String startDate, String endDate) {//调用mapper的方法List<VideoVisitorCountVo> videoVisitorVoList =baseMapper.findCount(courseId,startDate,endDate);//创建map集合Map<String, Object> map = new HashMap<>();//创建两个list集合,一个代表所有日期,一个代表日期对应数量//封装数据 代表所有日期List<String> dateList =videoVisitorVoList.stream().map(VideoVisitorCountVo::getJoinTime).collect(Collectors.toList());//代表日期对应数量List<Integer> countList = videoVisitorVoList.stream().map(VideoVisitorCountVo::getUserCount).collect(Collectors.toList());//放到map集合map.put("xData", dateList);map.put("yData", countList);return map;}
}
4. 编写Mapper
(1)VideoVisitorMapper
@Mapper
public interface VideoVisitorMapper extends BaseMapper<VideoVisitor> {// 课程统计List<VideoVisitorCountVo> finCount(@Param("courseId") Long courseId,@Param("startDate") String startDate,@Param("endDate") String endDate);
}
(2)VideoVisitorMapper.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.it.vod.mapper.VideoVisitorMapper"><select id="finCount" resultType="com.it.vo.vod.VideoVisitorCountVo">select date(join_time) as joinTime,count(*) as userCountfrom video_visitor<where><if test="startDate != null and startDate != '' ">and date(join_time) >= #{startDate}</if><if test="endDate != null and endDate != ''">and date(join_time) <= #{endDate}</if>and course_id = #{courseId}</where>group by date(join_time)order by date(join_time)</select></mapper>
- 测试
③:课程统计前
1. 定义接口
创建videoVisitor.js定义接口
import request from '@/utils/request'const api_name = '/admin/vod/videoVisitor'export default {findCount(courseId, startDate, endDate) {return request({url: `${api_name}/findCount/${courseId}/${startDate}/${endDate}`,method: 'get'})}
}
2. 安装ECharts组件
ECharts是百度的一个项目,后来百度把Echart捐给apache,用于图表展示,提供了常规的折线图、柱状图、散点图、饼图、K线图,用于统计的盒形图,用于地理数据可视化的地图、热力图、线图,用于关系数据可视化的关系图、treemap、旭日图,多维数据可视化的平行坐标,还有用于 BI 的漏斗图,仪表盘,并且支持图与图之间的混搭。
官方网站:https://echarts.apache.org/zh/index.html
npm install --save echarts@4.1.0
3. 编写页面
创建chart.vue页面
<template><div class="app-container"><!--表单--><el-form :inline="true" class="demo-form-inline"><el-form-item><el-date-pickerv-model="startDate"type="date"placeholder="选择开始日期"value-format="yyyy-MM-dd" /></el-form-item><el-form-item><el-date-pickerv-model="endDate"type="date"placeholder="选择截止日期"value-format="yyyy-MM-dd" /></el-form-item><el-button:disabled="btnDisabled"type="primary"icon="el-icon-search"@click="showChart()">查询</el-button></el-form><div id="chart" class="chart" style="height:500px;" /></div>
</template>
<script>
import echarts from 'echarts'
import api from '@/api/vod/videoVisitor'export default {data() {return {courseId: '',startDate: '',endDate: '',btnDisabled: false}},created() {this.courseId = this.$route.params.id// 初始化最近十天数据let currentDate = new Date();this.startDate = this.dateFormat(new Date(currentDate.getTime()-7*24*3600*1000))this.endDate = this.dateFormat(currentDate)this.showChart()},methods: {showChart() {api.findCount(this.courseId, this.startDate, this.endDate).then(response => {this.setChartData(response.data)})},setChartData(data) {// 基于准备好的dom,初始化echarts实例var myChart = echarts.init(document.getElementById('chart'))// 指定图表的配置项和数据var option = {title: {text: '观看课程人数统计'},xAxis: {data: data.xData},yAxis: {minInterval: 1},series: [{type: 'line',data: data.yData}]}// 使用刚指定的配置项和数据显示图表。myChart.setOption(option)},dateFormat(date) {let fmt = 'YYYY-mm-dd'let ret;const opt = {"Y+": date.getFullYear().toString(), // 年"m+": (date.getMonth() + 1).toString(), // 月"d+": date.getDate().toString(), // 日"H+": date.getHours().toString(), // 时"M+": date.getMinutes().toString(), // 分"S+": date.getSeconds().toString() // 秒// 有其他格式化字符需求可以继续添加,必须转化成字符串};for (let k in opt) {ret = new RegExp("(" + k + ")").exec(fmt);if (ret) {fmt = fmt.replace(ret[1], (ret[1].length == 1) ? (opt[k]) : (opt[k].padStart(ret[1].length, "0")))};};return fmt;}}
}
</script>
- 测试
八、整合腾讯云点
①:功能需求介
1. 上传视频
在发布课程时候,需要添加课时并且上传课程视频,这个时候需要使用到腾讯云点播服务进行上传视频管理
2. 删除视频
(1)添加小节,上传课程视频
(2)删除小节时候,需要删除视频
(3)删除课程时候,需要删除课程,章节,小节和视
3. 视频播放(后续完成)
②:腾讯云点播介绍
腾讯云点播(Video on Demand,VOD)基于腾讯多年技术积累与基础设施建设,为有音视频应用相关需求的客户提供包括音视频存储管理、音视频转码处理、音视频加速播放和音视频通信服务的一站式解决方案。
文档中心:https://cloud.tencent.com/document/product/26
1. 开通"云点播"服务
2. 管理控制台
3. 上传视频
上传视频可将视频上传到云点播的存储中,以进行后续的处理和分发等。
- 单击左侧菜单栏【媒资管理 > 视频管理】,默认展示【已上传】标签页;
- 点击【上传视频】按钮;
- 单击【选择视频】,选择本地视频文件;
- 单击【开始上传】;
- 页面将自动跳转至【正在上传】标签页, 本地文件所在行【状态】栏为“上传成功”时,单击【已上传】标签页,可见完成上传的视频;
单击【管理】,可以查看视频详情;
4. 前端集成
前端集成有两种方式,使用“超级播放器预览”与“web播放器预览”,或者代码已经不更新,推荐使用前者,因此“web播放器预览”仅做了解。
1、查看“web播放器预览”;
2、复制代码index.html到项目,即可播放
③:编写视频点播接
官方文档:https://cloud.tencent.com/document/product/266/10276
1. 创建相关类
2. 引入相关依赖
(1)在service_vod模块引入
<dependency><groupId>com.qcloud</groupId><artifactId>vod_api</artifactId><version>2.1.4</version><exclusions><exclusion><groupId>org.slf4j</groupId><artifactId>slf4j-log4j12</artifactId></exclusion></exclusions>
</dependency>
3. 编写Controller
官方文档:https://cloud.tencent.com/document/product/266/10276
(1)上传视频集成方案:
(2)删除视频
可在线生成代码:
地址:https://console.cloud.tencent.com/api/explorer?Product=vod&Version=2018-07-17&Action=DescribeMediaInfos&SignVersion=
@Api(tags = "腾讯云点播")
@RestController
@RequestMapping("/admin/vod")
@CrossOrigin
public class VodController {@Autowiredprivate VodService vodService;//上传视频@PostMapping("upload")public Result uploadVideo(@ApiParam(name = "file", value = "文件", required = true)@RequestParam("file") MultipartFile file) throws IOException {InputStream inputStream = file.getInputStream();String originalFilename = file.getOriginalFilename();String videoId = vodService.uploadVideo(inputStream, originalFilename);return Result.ok(videoId);}//删除视频@DeleteMapping("remove/{videoSourceId}")public Result removeVideo( @PathVariable String videoSourceId) {vodService.removeVideo(videoSourceId);return Result.ok();}
}
4. 编写Service
(1)VodService定义方法
public interface VodService {//上传视频String uploadVideo(InputStream inputStream, String originalFilename);//删除视频void removeVideo(String videoSourceId);
}
(2)VodServiceImpl实现方法
@Service
public class VodServiceImpl implements VodService {//上传视频@Overridepublic String uploadVideo(InputStream inputStream, String originalFilename) {try {// 指定当前腾旭云的账号id和keyVodUploadClient client =new VodUploadClient(ConstantPropertiesUtil.ACCESS_KEY_ID,ConstantPropertiesUtil.ACCESS_KEY_SECRET);VodUploadRequest request = new VodUploadRequest();//视频本地地址request.setMediaFilePath("D:\\001.mp4");//指定任务流request.setProcedure("LongVideoPreset");//调用上传方法,传入接入点地域及上传请求。VodUploadResponse response = client.upload("ap-guangzhou", request);//返回文件id保存到业务表,用于控制视频播放String fileId = response.getFileId();System.out.println("Upload FileId = {}"+response.getFileId());return fileId;} catch (Exception e) {System.out.println(e.toString());}return null;}//删除视频@Overridepublic void removeVideo(String videoSourceId) {try{// 实例化一个认证对象,入参需要传入腾讯云账户secretId,secretKey,此处还需注意密钥对的保密Credential cred = new Credential(ConstantPropertiesUtil.ACCESS_KEY_ID, ConstantPropertiesUtil.ACCESS_KEY_SECRET);// 实例化要请求产品的client对象,clientProfile是可选的VodClient client = new VodClient(cred, "");// 实例化一个请求对象,每个接口都会对应一个request对象DeleteMediaRequest req = new DeleteMediaRequest();req.setFileId(videoSourceId);// 返回的resp是一个DeleteMediaResponse的实例,与请求对象对应DeleteMediaResponse resp = client.DeleteMedia(req);// 输出json格式的字符串回包System.out.println(DeleteMediaResponse.toJsonString(resp));} catch (TencentCloudSDKException e) {System.out.println(e.toString());}}
}
④:完善上传视频功
1. 定义接口
创建vod.js定义接口
import request from '@/utils/request'export default {//删除视频removeByVodId(id) {return request({url: `/admin/vod/remove/${id}`,method: 'delete'})}
}
``### 4.1、添加上传视频**(1)修改Video -> Form.vue页面**[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TBQIWiSE-1666665940385)(.\images\image-20220226144609946.png)]```html
<template><!-- 添加和修改课时表单 --><el-dialog :visible="dialogVisible" title="添加课时" @close="close()"><el-form :model="video" label-width="120px"><el-form-item label="课时标题"><el-input v-model="video.title"/></el-form-item><el-form-item label="课时排序"><el-input-number v-model="video.sort" :min="0" /></el-form-item><el-form-item label="是否免费"><el-radio-group v-model="video.isFree"><el-radio :label="0">免费</el-radio><el-radio :label="1">默认</el-radio></el-radio-group></el-form-item><!-- 上传视频 --><el-form-item label="上传视频"><el-uploadref="upload":auto-upload="false":on-success="handleUploadSuccess":on-error="handleUploadError":on-exceed="handleUploadExceed":file-list="fileList":limit="1":before-remove="handleBeforeRemove":on-remove="handleOnRemove":action="BASE_API+'/admin/vod/upload'"><el-button slot="trigger" size="small" type="primary">选择视频</el-button><el-button:disabled="uploadBtnDisabled"style="margin-left: 10px;"size="small"type="success"@click="submitUpload()">上传</el-button></el-upload></el-form-item></el-form><div slot="footer" class="dialog-footer"><el-button @click="close()">取 消</el-button><el-button type="primary" @click="saveOrUpdate()">确 定</el-button></div></el-dialog>
</template>
<script>
import videoApi from '@/api/vod/video'
import vodApi from '@/api/vod/vod'
export default {data() {return {BASE_API: 'http://localhost:8301',dialogVisible: false,video: {sort: 0,free: false},fileList: [], // 上传文件列表uploadBtnDisabled: false}},methods: {open(chapterId, videoId) {this.dialogVisible = truethis.video.chapterId = chapterIdif (videoId) {videoApi.getById(videoId).then(response => {this.video = response.data// 回显if (this.video.videoOriginalName) {this.fileList = [{ 'name': this.video.videoOriginalName }]}})}},close() {this.dialogVisible = false// 重置表单this.resetForm()},resetForm() {this.video = {sort: 0,free: false}this.fileList = [] // 重置视频上传列表},saveOrUpdate() {if (!this.video.id) {this.save()} else {this.update()}},save() {this.video.courseId = this.$parent.$parent.courseIdvideoApi.save(this.video).then(response => {this.$message.success(response.message)// 关闭组件this.close()// 刷新列表this.$parent.fetchNodeList()})},update() {videoApi.updateById(this.video).then(response => {this.$message.success(response.message)// 关闭组件this.close()// 刷新列表this.$parent.fetchNodeList()})},// 上传多于一个视频handleUploadExceed(files, fileList) {this.$message.warning('想要重新上传视频,请先删除已上传的视频')},// 上传submitUpload() {this.uploadBtnDisabled = truethis.$refs.upload.submit() // 提交上传请求},// 视频上传成功的回调handleUploadSuccess(response, file, fileList) {this.uploadBtnDisabled = falsethis.video.videoSourceId = response.datathis.video.videoOriginalName = file.name},// 失败回调handleUploadError() {this.uploadBtnDisabled = falsethis.$message.error('上传失败2')},// 删除视频文件确认handleBeforeRemove(file, fileList) {return this.$confirm(`确定移除 ${file.name}?`)},// 执行视频文件的删除handleOnRemove(file, fileList) {if (!this.video.videoSourceId) {return}vodApi.removeByVodId(this.video.videoSourceId).then(response => {this.video.videoSourceId = ''this.video.videoOriginalName = ''videoApi.updateById(this.video)this.$message.success(response.message)})}}
}
</script>
2.测试
注意
⑤:腾讯云上传视频其他方
官方文档:https://cloud.tencent.com/document/product/266/9219
1. 客户端上传视频
2. 操作步骤一(申请上传签名
(1)找到Java签名示例
(2)VodController编写签名接口
Signature类
import java.util.Random;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import sun.misc.BASE64Encoder;public class Signature {private String secretId;private String secretKey;private long currentTime;private int random;private int signValidDuration;private static final String HMAC_ALGORITHM = "HmacSHA1"; //签名算法private static final String CONTENT_CHARSET = "UTF-8";public static byte[] byteMerger(byte[] byte1, byte[] byte2) {byte[] byte3 = new byte[byte1.length + byte2.length];System.arraycopy(byte1, 0, byte3, 0, byte1.length);System.arraycopy(byte2, 0, byte3, byte1.length, byte2.length);return byte3;}// 获取签名public String getUploadSignature() throws Exception {String strSign = "";String contextStr = "";// 生成原始参数字符串long endTime = (currentTime + signValidDuration);contextStr += "secretId=" + java.net.URLEncoder.encode(secretId, "utf8");contextStr += "¤tTimeStamp=" + currentTime;contextStr += "&expireTime=" + endTime;contextStr += "&random=" + random;//设置转码任务流contextStr += "&procedure=LongVideoPreset";try {Mac mac = Mac.getInstance(HMAC_ALGORITHM);SecretKeySpec secretKey = new SecretKeySpec(this.secretKey.getBytes(CONTENT_CHARSET), mac.getAlgorithm());mac.init(secretKey);byte[] hash = mac.doFinal(contextStr.getBytes(CONTENT_CHARSET));byte[] sigBuf = byteMerger(hash, contextStr.getBytes("utf8"));strSign = base64Encode(sigBuf);strSign = strSign.replace(" ", "").replace("\n", "").replace("\r", "");} catch (Exception e) {throw e;}return strSign;}private String base64Encode(byte[] buffer) {BASE64Encoder encoder = new BASE64Encoder();return encoder.encode(buffer);}public void setSecretId(String secretId) {this.secretId = secretId;}public void setSecretKey(String secretKey) {this.secretKey = secretKey;}public void setCurrentTime(long currentTime) {this.currentTime = currentTime;}public void setRandom(int random) {this.random = random;}public void setSignValidDuration(int signValidDuration) {this.signValidDuration = signValidDuration;}
}
VodController类
@GetMapping("sign")
public Result sign() {Signature sign = new Signature();// 设置 App 的云 API 密钥sign.setSecretId(ConstantPropertiesUtil.ACCESS_KEY_ID);sign.setSecretKey(ConstantPropertiesUtil.ACCESS_KEY_SECRET);sign.setCurrentTime(System.currentTimeMillis() / 1000);sign.setRandom(new Random().nextInt(java.lang.Integer.MAX_VALUE));sign.setSignValidDuration(3600 * 24 * 2); // 签名有效期:2天try {String signature = sign.getUploadSignature();System.out.println("signature : " + signature);return Result.ok(signature);} catch (Exception e) {System.out.print("获取签名失败");e.printStackTrace();return Result.fail(null);}
}
3. 操作步骤二(SDK上传)
5.4、下载Demo源码修改
(1)html文件测试上传
<!DOCTYPE html>
<html>
<head><meta charset="utf-8"><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1"><title>QCloud VIDEO UGC UPLOAD SDK</title><link href="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet"><style type="text/css">.text-danger {color: red;}.control-label {text-align: left !important;}#resultBox {width: 100%;height: 300px;border: 1px solid #888;padding: 5px;overflow: auto;margin-bottom: 20px;}.uploaderMsgBox {width: 100%;border-bottom: 1px solid #888;}.cancel-upload {text-decoration: none;cursor: pointer;}</style>
</head>
<body><div id="content"><div class="container"><h1>UGC-Uploader</h1></div></div><div class="container" id="main-area"><div class="row" style="padding:10px;"><p>示例1点击“直接上传视频”按钮即可上传视频。<br>。</p></div><form ref="vExample"><input type="file" style="display:none;" ref="vExampleFile" @change="vExampleUpload" /></form><div class="row" style="padding:10px;"><h4>示例1:直接上传视频</h4><a href="javascript:void(0);" class="btn btn-default" @click="vExampleAdd">直接上传视频</a></div><!-- 上传信息组件 --><div class="uploaderMsgBox" v-for="uploaderInfo in uploaderInfos"><div v-if="uploaderInfo.videoInfo">视频名称:{{uploaderInfo.videoInfo.name + '.' + uploaderInfo.videoInfo.type}};上传进度:{{Math.floor(uploaderInfo.progress * 100) + '%'}};fileId:{{uploaderInfo.fileId}};上传结果:{{uploaderInfo.isVideoUploadCancel ? '已取消' : uploaderInfo.isVideoUploadSuccess ? '上传成功' : '上传中'}};<br>地址:{{uploaderInfo.videoUrl}};<a href="javascript:void(0);" class="cancel-upload" v-if="!uploaderInfo.isVideoUploadSuccess && !uploaderInfo.isVideoUploadCancel" @click="uploaderInfo.cancel()">取消上传</a><br></div><div v-if="uploaderInfo.coverInfo">封面名称:{{uploaderInfo.coverInfo.name}};上传进度:{{Math.floor(uploaderInfo.coverProgress * 100) + '%'}};上传结果:{{uploaderInfo.isCoverUploadSuccess ? '上传成功' : '上传中'}};<br>地址:{{uploaderInfo.coverUrl}};<br></div></div></div><script src="https://cdn.jsdelivr.net/npm/es6-promise@4/dist/es6-promise.auto.js"></script><script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.5.21/vue.js"></script><script src="//cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.js"></script><script src="https://cdn-go.cn/cdn/vod-js-sdk-v6/latest/vod-js-sdk-v6.js"></script><script type="text/javascript">;(function () {/*** 计算签名。调用签名接口获取**/function getSignature() {return axios.get("http://localhost:8301/admin/vod/user/sign").then(response =>{return response.data.data})};var app = new Vue({el: '#main-area',data: {uploaderInfos: [],vcExampleVideoName: '',vcExampleCoverName: '',cExampleFileId: '',},created: function () {this.tcVod = new TcVod.default({getSignature: getSignature})},methods: {/*** vExample示例。添加视频**/vExampleAdd: function () {this.$refs.vExampleFile.click()},/*** vExample示例。上传视频过程。**/vExampleUpload: function () {var self = this;var mediaFile = this.$refs.vExampleFile.files[0]var uploader = this.tcVod.upload({mediaFile: mediaFile,})uploader.on('media_progress', function (info) {uploaderInfo.progress = info.percent;})uploader.on('media_upload', function (info) {uploaderInfo.isVideoUploadSuccess = true;})console.log(uploader, 'uploader')var uploaderInfo = {videoInfo: uploader.videoInfo,isVideoUploadSuccess: false,isVideoUploadCancel: false,progress: 0,fileId: '',videoUrl: '',cancel: function() {uploaderInfo.isVideoUploadCancel = true;uploader.cancel()},}this.uploaderInfos.push(uploaderInfo)uploader.done().then(function(doneResult) {console.log('doneResult', doneResult)uploaderInfo.fileId = doneResult.fileId;return doneResult.video.url;}).then(function (videoUrl) {uploaderInfo.videoUrl = videoUrlself.$refs.vExample.reset();})},// cExample 上传过程cExampleUpload: function() {var self = this;var coverFile = this.$refs.cExampleCover.files[0];var uploader = this.tcVod.upload({fileId: this.cExampleFileId,coverFile: coverFile,})uploader.on('cover_progress', function(info) {uploaderInfo.coverProgress = info.percent;})uploader.on('cover_upload', function(info) {uploaderInfo.isCoverUploadSuccess = true;})console.log(uploader, 'uploader')var uploaderInfo = {coverInfo: uploader.coverInfo,isCoverUploadSuccess: false,coverProgress: 0,coverUrl: '',cancel: function () {uploader.cancel()},}this.uploaderInfos.push(uploaderInfo)uploader.done().then(function (doneResult) {console.log('doneResult', doneResult)uploaderInfo.coverUrl = doneResult.cover.url;self.$refs.cExample.reset();})},},})})();</script><!-- Global site tag (gtag.js) - Google Analytics --><script async src="https://www.googletagmanager.com/gtag/js?id=UA-26476625-7"></script><script>// add by alsotang@gmail.comwindow.dataLayer = window.dataLayer || [];function gtag(){dataLayer.push(arguments);}gtag('js', new Date());gtag('config', 'UA-26476625-7');</script>
</body>
</html>
⑥:完善删除视频功
1. 修改VideoController方法
修改删除小节方法
@ApiOperation(value = "删除")@DeleteMapping("remove/{id}")public Result remove(@PathVariable Long id) {videoService.removeVideoById(id);return Result.ok();}
2. 修改Service方法
- 方法一:
修改VideoService
@Service
public class VideoServiceImpl extends ServiceImpl<VideoMapper, Video> implements VideoService {@Autowiredprivate VodService vodService;/*** 删除小节 同时删除视频* @param id*/@Overridepublic void removeVideoById(Long id) {// 查询小节Video video = baseMapper.selectById(id);String videoSourceId = video.getVideoSourceId();// 判断视频id是否为空if (!StringUtils.isEmpty(videoSourceId)){vodService.removeVideo(videoSourceId);}// 根据id删除小节baseMapper.deleteById(id);}
}
修改CourseServiceImpl
/*** 课程删除功能* @param id*/@Overridepublic void removeCourseById(Long id) {// 1. 删除课程小节QueryWrapper<Video> wrapper = new QueryWrapper<>();wrapper.eq("course_id",id);List<Video> videoList = videoService.list(wrapper);//遍历获取每个小节中的视频idfor(Video video:videoList) {String videoSourceId = video.getVideoSourceId();//如果视频id不为空,调用方法删除if(!StringUtils.isEmpty(videoSourceId)) {vodService.removeVideo(videoSourceId);}}//2 根据课程id删除小节videoService.remove(wrapper);// 3. 删除课程章节LambdaUpdateWrapper<Chapter> wrapper2 = new LambdaUpdateWrapper<>();wrapper2.eq(Chapter::getCourseId,id);chapterService.remove(wrapper2);// 4. 删除课程描述LambdaUpdateWrapper<CourseDescription> updateWrapper = new LambdaUpdateWrapper<>();updateWrapper.eq(CourseDescription :: getCourseId,id);descriptionService.remove(updateWrapper);// 5. 删除课程baseMapper.deleteById(id);}
- 方法二:
修改VideoService和VideoServiceImpl
@Service
public class VideoServiceImpl extends ServiceImpl<VideoMapper, Video> implements VideoService {@Autowiredprivate VodService vodService;//根据课程id删除小节@Overridepublic void removeVideoByCourseId(Long id) {//1 删除小节中的视频//根据课程id获取课程里面所有小节QueryWrapper<Video> wrapper = new QueryWrapper<>();wrapper.eq("course_id",id);List<Video> videoList = baseMapper.selectList(wrapper);//遍历获取每个小节中的视频idfor(Video video:videoList) {String videoSourceId = video.getVideoSourceId();//如果视频id不为空,调用方法删除if(!StringUtils.isEmpty(videoSourceId)) {vodService.removeVideo(videoSourceId);}}//2 根据课程id删除小节baseMapper.delete(wrapper);}//根据小节id删除小节删除视频@Overridepublic void removeVideoById(Long id) {//1 删除视频Video video = baseMapper.selectById(id);//获取视频idString videoSourceId = video.getVideoSourceId();//如果视频id不为空,调用方法删除if(!StringUtils.isEmpty(videoSourceId)) {vodService.removeVideo(videoSourceId);}//2 删除小节baseMapper.deleteById(id);}
}
3. 测试
1.上传一个新的视频
2.数据库和腾讯云上都上传成功
3. 删除
58-硅谷课堂4-腾讯云点播管理模块相关推荐
- 直播课堂系统11--腾讯云点播管理模块(三)
目录 点播管理模块-课程统计 需求 编写mapper 编写Service controller Echarts组建 前端 效果展示 整合腾讯云点播 点播管理模块-课程统计 需求 编写mapper 课程 ...
- 【硅谷课堂】腾讯云点播WEB上传方式代码(第二种方式)
下载依赖 npm i vod-js-sdk-v6 前端 /vod/course/Video/Form.vue <template><!-- 添加和修改课时表单 --><e ...
- day09-硅谷课堂-腾讯云点播管理模块(三)
硅谷课堂第九天-点播管理模块(三) 文章目录 硅谷课堂第九天-点播管理模块(三) 一.点播管理模块-课程统计 1.课程统计需求 2.课程统计接口 2.1.创建相关代码 videoVisitor表: 2 ...
- 硅谷课堂 16_腾讯云部署
硅谷课堂第十六天-腾讯云部署 文章目录 硅谷课堂第十六天-腾讯云部署 一.项目部署方案 1.原始部署方式 2.整合Jenkins 3.整合CODING 二.腾讯云CODING DevOps概述 1.产 ...
- 硅谷课堂 09_点播管理模块
硅谷课堂第九天-点播管理模块(三) 文章目录 硅谷课堂第九天-点播管理模块(三) 一.点播管理模块-课程统计 1.课程统计需求 2.课程统计接口 2.1.创建相关代码 2.2.编写Controller ...
- getvod.php_php腾讯云点播视频加密
php腾讯云点播视频加密,ProcessFile(案例为laravel) 路由: //视频详情 Route::get('video/info/{id}','Api\ProcessFileControl ...
- 腾讯云点播视频存储(Web端视频上传)
官方文档 前言 所谓视频上传,是指开发者或其用户将视频文件上传到点播的视频存储中,以便进行视频处理.分发等. 一.简介 腾讯云点播支持如下几种视频上传方式: 控制台上传:在点播控制台上进行操作,将本地 ...
- 用Python实现腾讯云点播VOD
腾讯云点播VOD主要用于视频资源的上传和在线播放,腾讯云官方文档也有许多相关操作的介绍,但是关于Python的文档相对较少,这里我想分享一下在Python方面自己对腾讯云VOD的研究(记得看注释). ...
- qq视频转码失败怎么办_腾讯云点播视频转码需要注意的问题
上传视频是否可以选择不转码? 老版腾讯云点播在控制台上传是必须选择转码模版的,但使用api上传的时候可以选择不转码,在申请媒资的时候, 指定mode参数为 no_transcoding就不会进行转码, ...
最新文章
- 0-1背包使用一维dp数组时为何v要从大到小枚举
- jQuery插件开发学习笔记
- Earth Mover's Distance (EMD)距离
- 查找最晚入职员工的所有信息---牛客网SQL实战篇
- Linux软件安装的几种常见方式介绍
- SEO笔记—网页结构优化(四)
- flavr—超级漂亮的jQuery扁平弹出对话框
- 大数据处理工具Kafka、Zk、Spark
- java时间日期类(Date、DateFormat、Calendar)学习
- 中国海洋大学c语言期末题库,中国海洋大学C语言期末笔试2010年秋A试题.doc
- 哈尔滨矢量地图_哈尔滨地图,哈尔滨电子地图,哈尔滨地图查询,哈尔滨街景地图 - 城市吧街景地图...
- Cradle CFD—专业热流场分析工具
- VirtualBox安装win10虚拟机
- 制作MHDD启动U盘
- 外贸企业邮箱注册申请,阿里qq腾讯邮箱对比选择
- 电视台工作计算机管理,电视台工作岗位有哪些
- 龙蜥社区第十次运营委员会议顺利召开!
- 黑苹果 macOS 无法修复磁盘 XXX 已修复
- javac不是内部或外部命令,也不是可运行的程序 或批处理文件的细节问题(window10)
- 常用的RTMP、RTSP、HTTP协议流直播流地址
热门文章
- 《中国脱发地图》来袭!最秃的省是?
- 最全RAID( RAID 0、RAID 1、RAID 5、RAID 10 ······) 以及它们的优缺点以及原理解析
- H5项目适配系统深色模式方案
- 双重游标的使用以及动态游标的使用
- java多态优化多个if_脑壳疼!代码中那么多“烦人”的if else
- 利用Termux在手机上运行Linux系统
- LightGBM是个什么东东?好像很耳熟啊,怎么玩啊?如何调参?一文搞定!
- “Big O”符号的简单英文解释是什么?
- Arthas(阿尔萨斯)简记
- 电信计算机知识考试,2019中国电信计算机专业知识模拟试卷 --- 答案解析(九)...