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. 测试

笔记中涉及资源:

链接:https://pan.baidu.com/s/1fNdmp8CS9gx5K9QHoItsfQ
提取码:Coke

一、后台管理系统-点播管理模块

①:点播管理 模块需求

添加点播课程,包含课程基本信息,课程章节,课程小结和最终发布

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) &lt;= #{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 += "&currentTimeStamp=" + 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-腾讯云点播管理模块相关推荐

  1. 直播课堂系统11--腾讯云点播管理模块(三)

    目录 点播管理模块-课程统计 需求 编写mapper 编写Service controller Echarts组建 前端 效果展示 整合腾讯云点播 点播管理模块-课程统计 需求 编写mapper 课程 ...

  2. 【硅谷课堂】腾讯云点播WEB上传方式代码(第二种方式)

    下载依赖 npm i vod-js-sdk-v6 前端 /vod/course/Video/Form.vue <template><!-- 添加和修改课时表单 --><e ...

  3. day09-硅谷课堂-腾讯云点播管理模块(三)

    硅谷课堂第九天-点播管理模块(三) 文章目录 硅谷课堂第九天-点播管理模块(三) 一.点播管理模块-课程统计 1.课程统计需求 2.课程统计接口 2.1.创建相关代码 videoVisitor表: 2 ...

  4. 硅谷课堂 16_腾讯云部署

    硅谷课堂第十六天-腾讯云部署 文章目录 硅谷课堂第十六天-腾讯云部署 一.项目部署方案 1.原始部署方式 2.整合Jenkins 3.整合CODING 二.腾讯云CODING DevOps概述 1.产 ...

  5. 硅谷课堂 09_点播管理模块

    硅谷课堂第九天-点播管理模块(三) 文章目录 硅谷课堂第九天-点播管理模块(三) 一.点播管理模块-课程统计 1.课程统计需求 2.课程统计接口 2.1.创建相关代码 2.2.编写Controller ...

  6. getvod.php_php腾讯云点播视频加密

    php腾讯云点播视频加密,ProcessFile(案例为laravel) 路由: //视频详情 Route::get('video/info/{id}','Api\ProcessFileControl ...

  7. 腾讯云点播视频存储(Web端视频上传)

    官方文档 前言 所谓视频上传,是指开发者或其用户将视频文件上传到点播的视频存储中,以便进行视频处理.分发等. 一.简介 腾讯云点播支持如下几种视频上传方式: 控制台上传:在点播控制台上进行操作,将本地 ...

  8. 用Python实现腾讯云点播VOD

    腾讯云点播VOD主要用于视频资源的上传和在线播放,腾讯云官方文档也有许多相关操作的介绍,但是关于Python的文档相对较少,这里我想分享一下在Python方面自己对腾讯云VOD的研究(记得看注释). ...

  9. qq视频转码失败怎么办_腾讯云点播视频转码需要注意的问题

    上传视频是否可以选择不转码? 老版腾讯云点播在控制台上传是必须选择转码模版的,但使用api上传的时候可以选择不转码,在申请媒资的时候, 指定mode参数为 no_transcoding就不会进行转码, ...

最新文章

  1. 0-1背包使用一维dp数组时为何v要从大到小枚举
  2. jQuery插件开发学习笔记
  3. Earth Mover's Distance (EMD)距离
  4. 查找最晚入职员工的所有信息---牛客网SQL实战篇
  5. Linux软件安装的几种常见方式介绍
  6. SEO笔记—网页结构优化(四)
  7. flavr—超级漂亮的jQuery扁平弹出对话框
  8. 大数据处理工具Kafka、Zk、Spark
  9. java时间日期类(Date、DateFormat、Calendar)学习
  10. 中国海洋大学c语言期末题库,中国海洋大学C语言期末笔试2010年秋A试题.doc
  11. 哈尔滨矢量地图_哈尔滨地图,哈尔滨电子地图,哈尔滨地图查询,哈尔滨街景地图 - 城市吧街景地图...
  12. Cradle CFD—专业热流场分析工具
  13. VirtualBox安装win10虚拟机
  14. 制作MHDD启动U盘
  15. 外贸企业邮箱注册申请,阿里qq腾讯邮箱对比选择
  16. 电视台工作计算机管理,电视台工作岗位有哪些
  17. 龙蜥社区第十次运营委员会议顺利召开!
  18. 黑苹果 macOS 无法修复磁盘 XXX 已修复
  19. javac不是内部或外部命令,也不是可运行的程序 或批处理文件的细节问题(window10)
  20. 常用的RTMP、RTSP、HTTP协议流直播流地址

热门文章

  1. 《中国脱发地图》来袭!最秃的省是?
  2. 最全RAID( RAID 0、RAID 1、RAID 5、RAID 10 ······) 以及它们的优缺点以及原理解析
  3. H5项目适配系统深色模式方案
  4. 双重游标的使用以及动态游标的使用
  5. java多态优化多个if_脑壳疼!代码中那么多“烦人”的if else
  6. 利用Termux在手机上运行Linux系统
  7. LightGBM是个什么东东?好像很耳熟啊,怎么玩啊?如何调参?一文搞定!
  8. “Big O”符号的简单英文解释是什么?
  9. Arthas(阿尔萨斯)简记
  10. 电信计算机知识考试,2019中国电信计算机专业知识模拟试卷 --- 答案解析(九)...