58-硅谷课堂4-腾讯云点播管理模块-- 笔记
笔记内容来源与尚硅谷教学视频
文章目录
笔记中涉及资源:
链接:https://pan.baidu.com/s/1fNdmp8CS9gx5K9QHoItsfQ
提取码:Coke
一、后台管理系统-点播管理模块
①:点播管理 模块需求
添加点播课程,包含课程基本信息,课程章节,课程小结和最终发布
1. 创建课程相关表
②:环境搭建
1. 生成相关代码
③:功能实现-课程列表
实现分页条件查询点播课程功能
1. 开发课程列表接口
编写CourseController
@Api(tags = "课程管理接口")
@RestController
@RequestMapping(value="/admin/vod/course")
public class CourseController {
@Autowired
private 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 {
@Autowired
private TeacherService teacherService;
@Autowired
private SubjectService subjectService;
//课程列表
@Override
public 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
*/
@Override
public Map<String, Object> findPageCourse(Page<Course> coursePage, CourseQueryVo courseQueryVo) {
// 1. 获取条件
// 讲师Id
Long teacherId = courseQueryVo.getTeacherId();
// 课程标题
String title = courseQueryVo.getTitle();
// 课程专业ID
Long subjectId = courseQueryVo.getSubjectId();
// 课程专业父级ID
Long 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-select
v-model="searchObj.subjectParentId"
placeholder="请选择"
@change="subjectLevelOneChanged">
<el-option
v-for="subject in subjectList"
:key="subject.id"
:label="subject.title"
:value="subject.id"/>
</el-select>
<!-- 二级分类 -->
<el-select v-model="searchObj.subjectId" placeholder="请选择">
<el-option
v-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-select
v-model="searchObj.teacherId"
placeholder="请选择讲师">
<el-option
v-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.records
console.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.data
this.searchObj.subjectId = ''
})
},
add() {
this.$router.push({ path: '/vod/course/info' })
},
// 每页记录数改变,size:回调参数,表示当前选中的“每页条数”
changePageSize(size) {
this.limit = size
this.fetchData()
},
// 改变页码,page:回调参数,表示当前选中的“页码”
changeCurrentPage(page) {
this.page = page
this.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
*/
@Override
public 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() {
// 获取路由id
if (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-select
v-model="courseInfo.teacherId"
placeholder="请选择">
<el-option
v-for="teacher in teacherList"
:key="teacher.id"
:label="teacher.name"
:value="teacher.id"/>
</el-select>
</el-form-item>
<!-- 所属分类:级联下拉列表 -->
<el-form-item label="课程类别">
<!-- 一级分类 -->
<el-select
v-model="courseInfo.subjectParentId"
placeholder="请选择"
@change="subjectChanged">
<el-option
v-for="subject in subjectList"
:key="subject.id"
:label="subject.title"
:value="subject.id"/>
</el-select>
<!-- 二级分类 -->
<el-select v-model="courseInfo.subjectId" placeholder="请选择">
<el-option
v-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 < 2
if (!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 = true
if (!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 // 获取courseId
this.$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获取课程信息
@Override
public 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修改课程信息
@Override
public 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 = true
if (!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 // 获取courseId
this.$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 {
@Autowired
private 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 {
@Autowired
private VideoService videoService;
//章节小结列表封装
@Override
public 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 {
@Autowired
private 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">
<li
v-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">
<li
v-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 = true
if (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.courseId
chapterApi.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-upload
ref="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 = true
this.video.chapterId = chapterId
if (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.courseId
videoApi.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 = true
this.$refs.upload.submit() // 提交上传请求
},
// 视频上传成功的回调
handleUploadSuccess(response, file, fileList) {
this.uploadBtnDisabled = false
this.video.videoSourceId = response.data
this.video.videoOriginalName = file.name
},
// 失败回调
handleUploadError() {
this.uploadBtnDisabled = false
this.$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">
SELECT
c.id,
c.title,
c.cover,
c.lesson_num AS lessonNum,
c.price,
t.name AS teacherName,
s1.title AS subjectParentTitle,
s2.title AS subjectTitle
FROM course c
LEFT OUTER JOIN teacher t ON c.teacher_id=t.id
LEFT OUTER JOIN `subject` s1 ON c.subject_parent_id=s1.id
LEFT OUTER JOIN `subject` s2 ON c.subject_id=s2.id
WHERE 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 = true
courseApi.publishCourseById(this.$parent.courseId).then(response => {
this.$parent.active = 3
this.$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
*/
@Override
public 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);
}
- 方法二:
//删除课程
@Override
public 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删除小节
@Override
public void removeVideoByCourseId(Long id) {
QueryWrapper<Video> wrapper = new QueryWrapper<>();
wrapper.eq("course_id",id);
baseMapper.delete(wrapper);
}
}
4. 编写ChapterService
//根据课程id删除章节
@Override
public 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 {
@Autowired
private 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 {
//课程统计的接口
@Override
public 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 userCount
from 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-picker
v-model="startDate"
type="date"
placeholder="选择开始日期"
value-format="yyyy-MM-dd" />
</el-form-item>
<el-form-item>
<el-date-picker
v-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)删除视频
可在线生成代码:
@Api(tags = "腾讯云点播")
@RestController
@RequestMapping("/admin/vod")
@CrossOrigin
public class VodController {
@Autowired
private 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 {
//上传视频
@Override
public String uploadVideo(InputStream inputStream, String originalFilename) {
try {
// 指定当前腾旭云的账号id和key
VodUploadClient 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;
}
//删除视频
@Override
public 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-upload
ref="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 = true
this.video.chapterId = chapterId
if (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.courseId
videoApi.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 = true
this.$refs.upload.submit() // 提交上传请求
},
// 视频上传成功的回调
handleUploadSuccess(response, file, fileList) {
this.uploadBtnDisabled = false
this.video.videoSourceId = response.data
this.video.videoOriginalName = file.name
},
// 失败回调
handleUploadError() {
this.uploadBtnDisabled = false
this.$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 = videoUrl
self.$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.com
window.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 {
@Autowired
private VodService vodService;
/**
* 删除小节 同时删除视频
* @param id
*/
@Override
public 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
*/
@Override
public void removeCourseById(Long id) {
// 1. 删除课程小节
QueryWrapper<Video> wrapper = new QueryWrapper<>();
wrapper.eq("course_id",id);
List<Video> videoList = videoService.list(wrapper);
//遍历获取每个小节中的视频id
for(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 {
@Autowired
private VodService vodService;
//根据课程id删除小节
@Override
public void removeVideoByCourseId(Long id) {
//1 删除小节中的视频
//根据课程id获取课程里面所有小节
QueryWrapper<Video> wrapper = new QueryWrapper<>();
wrapper.eq("course_id",id);
List<Video> videoList = baseMapper.selectList(wrapper);
//遍历获取每个小节中的视频id
for(Video video:videoList) {
String videoSourceId = video.getVideoSourceId();
//如果视频id不为空,调用方法删除
if(!StringUtils.isEmpty(videoSourceId)) {
vodService.removeVideo(videoSourceId);
}
}
//2 根据课程id删除小节
baseMapper.delete(wrapper);
}
//根据小节id删除小节删除视频
@Override
public void removeVideoById(Long id) {
//1 删除视频
Video video = baseMapper.selectById(id);
//获取视频id
String videoSourceId = video.getVideoSourceId();
//如果视频id不为空,调用方法删除
if(!StringUtils.isEmpty(videoSourceId)) {
vodService.removeVideo(videoSourceId);
}
//2 删除小节
baseMapper.deleteById(id);
}
}
3. 测试
1.上传一个新的视频
2.数据库和腾讯云上都上传成功
3. 删除