微服务项目实战技术点汇总:“尚硅谷的谷粒在线教育”五、阿里云视频点播,完善课程管理模块,(集成富文本插件,大纲管理,信息确认回显,发布完成),手写sql

文章目录

一、课程发布完善

1、(前端)集成富文本插件

1、下载依赖

1、安装tinymce,不安装这个没法使用tinymce vue(你可以直接下载离线包)
npm install tinymce 
2、安装tinymce vue
npm install --save @tinymce/tinymce-vue

2、配置环境

1、安装之后,在 node_modules 中找到 tinymce目录,然后将目录拷贝到 static 目录下 如果是使用 vue-cli 3.x 构建的,就放到 public 目录下

在这里插入图片描述
2、下载语言包,默认英文,我们可以使用中文,放到我们刚复制的文件夹中,不过你可以先看看文件夹里面有没有,因为你可能下载的就是中文的语言包
3、主页面引入js文件,第二个是语言包,如果不需要就不用引入
在这里插入图片描述
4、需要使用富文本编辑组件的页面引入

<el-form-item label="课程简介">
        <Editor id="tinymce" v-model="courseInfo.description" :init="editorInit"></Editor>
      </el-form-item>

import Editor from '@tinymce/tinymce-vue'

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3、测试

在这里插入图片描述
在这里插入图片描述

4、添加插件

在这里插入图片描述
在这里插入图片描述

2、(后端)课程大纲列表(多级联动和课程分类相同,有章节和小节)

1、创建两个实体类分别表示章节和小节,每个章节包含多个小节

package com.yzpnb.eduservice.entity.chapter;

import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import java.util.ArrayList;
import java.util.List;

/**
 * 章节实体类
 */
@Data
public class ChapterVo {

    @ApiModelProperty("章节id")
    private String id;

    @ApiModelProperty("章节标题")
    private String title;

    @ApiModelProperty("每章节包含的小节,名称为children,前端需要对应")
    private List<VideoVo> children=new ArrayList<>();
}

package com.yzpnb.eduservice.entity.chapter;

import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

/**
 * 小节实体类
 */
@Data
public class VideoVo {

    @ApiModelProperty("小节id")
    private String id;

    @ApiModelProperty("小节标题")
    private String title;
}

在这里插入图片描述

2、controller层

package com.yzpnb.eduservice.controller;


import com.yzpnb.common_utils.Result;
import com.yzpnb.eduservice.entity.chapter.ChapterVo;
import com.yzpnb.eduservice.service.EduChapterService;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;


/**
 * <p>
 * 课程 前端控制器
 * </p>
 *
 * @author testjava
 * @since 2020-05-24
 */
@RestController
@RequestMapping("/eduservice/edu-chapter")
@CrossOrigin
public class EduChapterController {

    @Autowired
    private EduChapterService eduChapterService;

    /**
     *
     * 查询
     */
    @ApiOperation("查询指定的课程大纲列表")
    @GetMapping("selectTree/{courseId}")
    public Result selectTree(   @ApiParam(name = "courseId",value = "课程id")
                                @PathVariable String courseId){

        List<ChapterVo> list= eduChapterService.selectChapterVideoByCourseId(courseId);//根据课程id查询章节和小节
        return Result.ok().data("chapterAndVideo",list);
    }

}


在这里插入图片描述

3、service层

package com.yzpnb.eduservice.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.yzpnb.eduservice.entity.EduChapter;
import com.yzpnb.eduservice.entity.EduVideo;
import com.yzpnb.eduservice.entity.chapter.ChapterVo;
import com.yzpnb.eduservice.entity.chapter.VideoVo;
import com.yzpnb.eduservice.mapper.EduChapterMapper;
import com.yzpnb.eduservice.service.EduChapterService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yzpnb.eduservice.service.EduVideoService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

/**
 * <p>
 * 课程 服务实现类
 * </p>
 *
 * @author testjava
 * @since 2020-05-24
 */
@Service
public class EduChapterServiceImpl extends ServiceImpl<EduChapterMapper, EduChapter> implements EduChapterService {

    @Autowired
    private EduVideoService eduVideoService;
    @Override
    public List<ChapterVo> selectChapterVideoByCourseId(String courseId) {

        /**1、根据课程id查询对应所有章节**/
        QueryWrapper<EduChapter> queryWrapper=new QueryWrapper<>();
        queryWrapper.eq("course_id",courseId);
        List<EduChapter> eduChapters = baseMapper.selectList(queryWrapper);

        /**2、根据课程id和当前查询的章节id查出对应小节,然后封装到章节中**/
        //2.1先查询出当前课程id对应的所有小节
        QueryWrapper<EduVideo> queryWrapper2=new QueryWrapper<>();
        queryWrapper2.eq("course_id",courseId);
        List<EduVideo> eduVideos=eduVideoService.list(queryWrapper2);
        //2.2遍历集合,判断小节的章节id(chapter_id)是否等于查询到的章节id进行封装

        List<ChapterVo> list=new ArrayList<>();//保存最终封装号的对象

        for(EduChapter eduChapter:eduChapters)//遍历所有章节(总)
        {
            ChapterVo chapterVo=new ChapterVo();//创建我们封装用的章节对象
            BeanUtils.copyProperties(eduChapter,chapterVo);//使用工具类将章节(总)对象中我们需要的信息封装到章节对象中

            for (EduVideo eduVideo :eduVideos){//遍历小节(总)对象

                if(eduVideo.getChapterId().equals(chapterVo.getId())){//判断当前小节(总)对象的章节id是否等于章节对象的id(一定用equals)
                    VideoVo videoVo =new VideoVo();//创建小节对象
                    BeanUtils.copyProperties(eduVideo,videoVo);//封装信息
                    chapterVo.getChildren().add(videoVo);//将此小节封装当此章节中
                }
                //if结束
            }
            //内循环结束,将对象封装
            list.add(chapterVo);
        }

        return list;
    }
}

在这里插入图片描述

4、测试

在这里插入图片描述

3、(前端)课程大纲列表的显示

1、API接口

在这里插入图片描述

2、页面实现

<template>
  <div class="app-container">
    <!-- 导航-->
    <h2 style="text-align: center;">发布新课程</h2>
    <el-steps :active="2" process-status="wait" align-center style="margin-bottom: 40px;">
      <el-step title="填写课程基本信息"/>
      <el-step title="创建课程大纲"/>
      <el-step title="提交审核"/>
    </el-steps>

    <!-- 课程大纲 -->
    <ul class="chanpterList">
        <li
            v-for="chapter in list"
            :key="chapter.id">
            <p>
                {{ chapter.title }}
                <span class="acts">
                    <el-button type="text">添加课时</el-button>
                    <el-button style="" type="text">编辑</el-button>
                    <el-button type="text">删除</el-button>
                </span>
            </p>
            <!-- 视频 -->
            <ul class="chanpterList videoList">
                <li
                    v-for="video in chapter.children"
                    :key="video.id">
                    <p>{{ video.title }}
                        <span class="acts">
                            <el-button type="text">编辑</el-button>
                            <el-button type="text">删除</el-button>
                        </span>
                    </p>
                </li>
            </ul>
        </li>
    </ul>
    <!-- 按钮-->
    <el-form label-width="120px">
      <el-form-item>
        <el-button @click="previous">上一步</el-button>
        <el-button :disabled="saveBtnDisabled" type="primary" @click="next">下一步</el-button>
      </el-form-item>
    </el-form>
  </div>
</template>
<script>
  import eduCourse from '@/api/course/eduCourse.js'
export default {
  data() {
    return {
      saveBtnDisabled: false ,// 保存按钮是否禁用
      list:[],//数据
    }
  },
  created() {
    console.log('chapter created')
    if(this.$route.params.id && this.$route.params){//若路由中有参数,并且参数名为id就执行
      // this.getChapterAndVideoById(this.$route.params.id)
      this.getChapterAndVideoById('18')//这里先用假数据18作为参数,之后会完善
    }
  },
  methods: {
    /* 根据课程id查询章节和小节*/
    getChapterAndVideoById(id){
      eduCourse.getChapterAndVideoById(id)
      .then(response=>{
        this.list=response.data.chapterAndVideo
      })
    },
    previous() {
      console.log('previous')
      this.$router.push({name:"课程添加",params:{id:1}})
    },
    next() {
      console.log('next')
      this.$router.push({name:"课程添加3",params:{id:1}})
    }
  }
}
</script>
<style>
.chanpterList{
    position: relative;
    list-style: none;
    margin: 0;
    padding: 0;
}
.chanpterList li{
  position: relative;
}
.chanpterList p{
  float: left;
  font-size: 20px;
  margin: 10px 0;
  padding: 10px;
  height: 70px;
  line-height: 50px;
  width: 100%;
  border: 1px solid #DDD;
}
.chanpterList .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 dotted #DDD;
}
</style>

在这里插入图片描述

4、(后端)数据回显(点击上一步时,显示出原内容,之后根据请求类型执行不同操作put修改)

1、controller

/**
     * 查询
     */
    @ApiOperation("根据id查询课程基本信息和简介")
    @GetMapping("selectById/{id}")
    public Result selectById(@ApiParam(name = "id",value = "课程id")
                             @PathVariable String id){
        CourseInfoVo courseInfoVo=eduCourseService.selectCourseInfo(id);
        return Result.ok().data("courseInfoVo",courseInfoVo);
    }
    /**
     * 修改
     */
    @ApiOperation("根据id修改课程信息")
    @PostMapping("updateCourseInfoVo")
    public Result updateCourseInfoVo(@RequestBody CourseInfoVo courseInfoVo){

        eduCourseService.updateCourseInfoVo(courseInfoVo);
        return Result.ok();
    }

在这里插入图片描述

2、service

/**
     * 根据课程id查询课程信息
     */
    @Override
    public CourseInfoVo selectCourseInfo(String id) {
        /**1、根据id查询课程基本信息*/
        EduCourse eduCourse=baseMapper.selectById(id);
        /**2、根据id查询课程简介*/
        EduCourseDescription eduCourseDescription =eduCourseDescriptionService.getById(id);
        /**3、将信息封装到信息汇总对象中*/
        CourseInfoVo courseInfoVo=new CourseInfoVo();
        BeanUtils.copyProperties(eduCourse,courseInfoVo);
        courseInfoVo.setDescription(eduCourseDescription.getDescription());

        return courseInfoVo;
    }

    /**
     * 根据id修改课程信息
     */
    @Override
    public void updateCourseInfoVo(CourseInfoVo courseInfoVo) {

        /**1、修改课程基本信息*/
        EduCourse eduCourse=new EduCourse();
        BeanUtils.copyProperties(courseInfoVo,eduCourse);
        int i=baseMapper.updateById(eduCourse);
        if(i==0){
            throw new CustomExceptionHandler(20001,"修改课程基本信息失败");
        }
        /**2、修改简介*/
        EduCourseDescription eduCourseDescription=new EduCourseDescription();
        eduCourseDescription.setId(courseInfoVo.getId());
        eduCourseDescription.setDescription(courseInfoVo.getDescription());
        boolean b=eduCourseDescriptionService.updateById(eduCourseDescription);
        if(!b){
            throw new CustomExceptionHandler(20001,"修改课程简介信息失败");
        }
    }

在这里插入图片描述

5、(前端)数据回显

1、api接口

在这里插入图片描述

2、回显实现

设置save2页面中,单击上一步按钮时的参数
在这里插入图片描述
save1中实现回显
在这里插入图片描述

//有值就说明是回显的,因为我们第一次添加课程时,是没有id参数的
        eduCourse.getCourseInfo(id)
        .then(response=>{
          this.courseInfo=response.data.courseInfoVo;

          //解决回显数据不显示内容只显示id值(只需要让二级分类有值就可以)
          eduSubject.getList()
          .then(response=>{
            this.oneSubject=response.data.allSubject;//获取一级分类
            for (let i = 0; i < this.oneSubject.length; i++) {//遍历一级分类
              if (this.oneSubject[i].id === this.courseInfo.subjectParentId) {
                  this.twoSubject = this.oneSubject[i].children//找到与回显的id对应的二级分类,赋值
              }
            }
          })
        })

3、修改实现(点击下一步保存的时候判断是添加还是修改操作)

在这里插入图片描述
在这里插入图片描述

6、(后端)章节的增删改查(记住写完代码,给实体类设置自动填充,然后重启服务器)

1、controller

在这里插入图片描述

@ApiOperation("根据Id查询章节信息")
    @GetMapping("selectById/{id}")
    public Result selectById(@ApiParam(name = "id",value = "章节id")
                             @PathVariable String id){

        EduChapter eduChapter = eduChapterService.getById(id);
        return Result.ok().data("chapter",eduChapter);
    }
    /**
     * 添加章节
     */
    @ApiOperation("添加章节")
    @PostMapping("insertChapter")
    public Result insertChapter(@ApiParam(name = "eduChapter",value = "章节信息数据")
                                @RequestBody EduChapter eduChapter){
        eduChapterService.save(eduChapter);
        return Result.ok();
    }
    /**
     * 修改
     */
    @ApiOperation("修改章节")
    @PostMapping("updateChapter")
    public Result updateChapter(@ApiParam(name = "eduChapter",value = "章节信息数据")
                                @RequestBody EduChapter eduChapter){
        eduChapterService.updateById(eduChapter);
        return Result.ok();
    }
    /**
     * 删除
     */
    @ApiOperation("删除章节")
    @DeleteMapping("{id}")
    public Result deleteById(@ApiParam(name = "id",value = "章节id")
                             @PathVariable String id) {
        eduChapterService.deleteChapterVideo(id);//删除章节后,需要对小节处理
        return Result.ok();
    }

2、service

/**
     * 删除章节,如果有小节,则不能删除章节
     */
    @Override
    public void deleteChapterVideo(String id) {
        /**1、根据章节id查小节*/
        QueryWrapper<EduVideo> queryWrapper=new QueryWrapper<>();
        queryWrapper.eq("chapter_id",id);
        int count = eduVideoService.count(queryWrapper);//返回当前查询结果的记录个数
        if(count>0){//如果不是0表示有小节
            throw new CustomExceptionHandler(20001,"章节中包含小节,请先删除小节");
        }else{
            baseMapper.deleteById(id);
        }

    }

在这里插入图片描述

7、(前端)章节的增删改

1、Api接口

在这里插入图片描述

2、代码实现(添加操作)

<template>
  <div class="app-container">
    <!-- 导航-->
    <h2 style="text-align: center;">发布新课程</h2>
    <el-steps :active="2" process-status="wait" align-center style="margin-bottom: 40px;">
      <el-step title="填写课程基本信息"/>
      <el-step title="创建课程大纲"/>
      <el-step title="提交审核"/>
    </el-steps>

    <!-- 课程大纲 -->
    <el-button type="text" @click="openDialog(0)">添加章节</el-button>

    <ul class="chanpterList">
        <li
            v-for="chapter in list"
            :key="chapter.id">
            <p>
                {{ chapter.title }}
                <span class="acts">
                    <el-button type="text">添加课时</el-button>
                    <el-button style="disabled=true" type="text" @click="openDialog(chapter.id)">编辑</el-button>
                    <el-button type="text" @click="deleteChapter(chapter.id)">删除</el-button>
                </span>
            </p>
            <!-- 视频 -->
            <ul class="chanpterList videoList">
                <li
                    v-for="video in chapter.children"
                    :key="video.id">
                    <p>{{ video.title }}
                        <span class="acts">
                            <el-button type="text">编辑</el-button>
                            <el-button type="text">删除</el-button>
                        </span>
                    </p>
                </li>
            </ul>
        </li>
    </ul>

    <!-- 添加和修改章节表单-->
    <el-dialog :visible.sync="dialogChapterFormVisible" title="添加章节">
        <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" controls-position="right"/>
            </el-form-item>
        </el-form>
        <div slot="footer" class="dialog-footer">
            <el-button @click="dialogChapterFormVisible = false">取 消</el-button>
            <el-button type="primary" @click="saveOrUpdate">确 定</el-button>
        </div>
    </el-dialog>

    <!-- 按钮-->
    <el-form label-width="120px">
      <el-form-item>
        <el-button @click="previous">上一步</el-button>
        <el-button :disabled="saveBtnDisabled" type="primary" @click="next">下一步</el-button>
      </el-form-item>
    </el-form>
  </div>
</template>
<script>
  import eduCourse from '@/api/course/eduCourse.js'
  const defaultChapter={//封装章节数据
        title:'', //章节名称
        sort:1,   //排序,默认为1
        courseId:0//课程id
      }
export default {
  data() {
    return {
      saveBtnDisabled: false ,// 保存按钮是否禁用
      list:[],//数据
      dialogChapterFormVisible:false,//弹窗初始不显示
      chapter:defaultChapter,//封装章节数据
      flag:0,//用于判断是添加还是修改操作,0为添加,其它值为修改(因为我会把需要修改的章节id传过来)
    }
  },
  created() {
    console.log('chapter created')
    if(this.$route.params.id && this.$route.params){//若路由中有参数,并且参数名为id就执行
      this.getChapterAndVideoById(this.$route.params.id)//现在可以使用完善的喽
    }
  },
  methods: {
    /* 根据课程id查询章节和小节*/
    getChapterAndVideoById(id){
      eduCourse.getChapterAndVideoById(id)
      .then(response=>{
        this.list=response.data.chapterAndVideo
       })
    },
    /* 开启弹窗*/
    openDialog(flag){
      this.dialogChapterFormVisible = true;
      //将chapter重新赋值
      this.chapter=defaultChapter;
      //判断是添加还是修改
      this.flag=flag;
      if(!(flag===0)){//如果是修改操作
        //乐观锁,先查数据
        eduCourse.selectById(flag)
        .then(response=>{
          this.chapter=response.data.chapter;//将查出的数据保存起来
        })
      }
    },
    /* 点击确定按钮后*/
    saveOrUpdate(){
      if(this.flag===0){//id
        this.insertChapter();
      }else{//其它值表示修改操作(传的就是章节id
        this.updateChapter(this.flag);
      }
      //3、刷新页面(为了保险,多刷一次)
      this.getChapterAndVideoById(this.$route.params.id)
    },
    /*添加操作 */
    insertChapter(){
      this.chapter.courseId=this.$route.params.id//将课程id给章节对象中
      eduCourse.insertChapter(this.chapter)
      .then(response=>{
        //1、关闭弹窗
        this.dialogChapterFormVisible = false;
        //2、提示信息
        this.$message({//提示
          type: 'success',
          message: '添加成功!'
        })
      })
      //刷新页面(为了保险,多刷一次)
      this.getChapterAndVideoById(this.$route.params.id)
    },
    /* 修改操作*/
    updateChapter(){
      eduCourse.updateChapter(this.chapter)
      .then(response=>{
        //1、关闭弹窗
        this.dialogChapterFormVisible = false;
        //2、提示信息
        this.$message({//提示
          type: 'success',
          message: '修改成功!'
        })
      })
      //刷新页面(为了保险,多刷一次)
      this.getChapterAndVideoById(this.$route.params.id)
    },
    /* 删除*/
    deleteChapter(id){
      this.$confirm('此操作将永久删除数据, 是否继续?', '提示', {
                confirmButtonText: '确定',
                cancelButtonText: '取消',
                type: 'warning'
              }).then(() => {
                eduCourse.deleteChapter(id)
                .then(response=>{
                  this.$message({//提示
                    type: 'success',
                    message: '删除成功!'
                  })
                })
              }).catch(() => {
                this.$message({
                  type: 'info',
                  message: '已取消删除'
                });
              });
    },
    previous() {
      console.log('previous')
      this.$router.push({name:"课程添加",params:{id:this.$route.params.id}})
    },
    next() {
      console.log('next')
      this.$router.push({name:"课程添加3",params:{id:1}})
    }
  }
}
</script>
<style>
.chanpterList{
    position: relative;
    list-style: none;
    margin: 0;
    padding: 0;
    border: 0;
}
.chanpterList li{
  position: relative;
}
.chanpterList p{
  /* float: left; */
  font-size: 20px;
  margin: 10px 0;
  padding: 10px;
  height: 70px;
  line-height: 50px;
  width: 100%;
  border: 1px solid #DDD;
}
.chanpterList .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 dotted #DDD;
}
</style>

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3、测试

在这里插入图片描述

8、(后端)小节的增删改查(只编写了controller层)

package com.yzpnb.eduservice.controller;


import com.yzpnb.common_utils.Result;
import com.yzpnb.eduservice.entity.EduVideo;
import com.yzpnb.eduservice.service.EduVideoService;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

/**
 * <p>
 * 课程视频 前端控制器
 * </p>
 *
 * @author testjava
 * @since 2020-05-24
 */
@RestController
@RequestMapping("/eduservice/edu-video")
public class EduVideoController {

    @Autowired
    private EduVideoService eduVideoService;

    /**
     * 查询
     */
    @ApiOperation("根据id查询小节")
    @GetMapping("selectById/{id}")
    public Result selectById(@ApiParam(name = "id",value = "小节id")
                             @PathVariable String id){
        EduVideo eduVideo = eduVideoService.getById(id);
        return Result.ok().data("video",eduVideo);
    }
    /**
     * 添加
     */
    @ApiOperation("添加小节")
    @PostMapping("insertVideo")
    public Result insertVideo(@ApiParam(name="eduVideo",value="小节对象json")
                              @RequestBody EduVideo eduVideo){
        eduVideoService.save(eduVideo);
        return Result.ok();
    }

    /**
     * 修改
     */
    @ApiOperation("根据id修改小节内容")
    @PostMapping("updateVideo")
    public Result updateVideo(@ApiParam(name = "eduVideo",value = "小节对象json")
                              @RequestBody EduVideo eduVideo){
        eduVideoService.updateById(eduVideo);
        return Result.ok();
    }
    /**
     * 删除
     * TODO 之后会需要添加视频,删除小节时,需要将视频也删除掉
     */
    @ApiOperation("根据id删除小节")
    @DeleteMapping("{id}")
    public Result deleteVideoById(@ApiParam(name="id",value="小节id")
                                  @PathVariable String id){
        eduVideoService.removeById(id);
        return Result.ok();
    }
}


在这里插入图片描述

9、(前端)小节的增删改查

1、api接口

在这里插入图片描述

2、添加组件

<!-- 添加和修改课时表单 -->
    <el-dialog :visible.sync="dialogVideoFormVisible" title="添加课时">
      <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" controls-position="right"/>
        </el-form-item>
        <el-form-item label="是否免费">
          <el-radio-group v-model="video.isFree">
            <el-radio :label="true">免费</el-radio>
            <el-radio :label="false">默认</el-radio>
          </el-radio-group>
        </el-form-item>
        <el-form-item label="上传视频">
          <!-- TODO -->
        </el-form-item>
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button @click="dialogVideoFormVisible = false">取 消</el-button>
        <el-button :disabled="saveVideoBtnDisabled" type="primary" @click="saveOrUpdateVideo">确 定</el-button>
      </div>
    </el-dialog>

在这里插入图片描述

3、代码实现,和章节的代码实现大同小异

const defaultVideo={  //封装小节默认数据
        title:'1',       //小节名称
        sort:1,
        courseId:0,     //课程id
        chapterId:0,    //章节id
        play_count:0,   //播放次数
        isFree:1,       //是否可以试听,0收费,1免费
        duration:0,     //视频时长
        status:'',      //视频转码
        size:0,         //视频源文件字节
  };

data() {
    return {
      saveBtnDisabled: false ,        // 保存按钮是否禁用
      list:[],                        //数据
      dialogChapterFormVisible:false, //章节弹窗初始不显示
      dialogVideoFormVisible:false,   //小节弹窗初始不显示
      saveVideoBtnDisabled:false,     //小节弹窗中确定按钮是否可用
      chapter:defaultChapter,         //封装章节数据
      video:defaultVideo,             //封装小节数据
      flag:0,                         //用于判断是添加还是修改操作,0为添加,其它值为修改(因为我会把需要修改的章节id传过来)
      videoFlag:0,                    //用于判断小节是添加还是修改操作
      dialogTitle:"添加章节",
      videoDialogTitle:"添加小节",
    }
  },

/**===================================小节操作函数==============================================**/
  /* 开启弹窗 */
  openVideoDialog(videoFlag,chapterId){
    this.dialogVideoFormVisible=true;//开启弹窗

    //将video重新赋值
    this.video={...defaultVideo};
    //获取当前章节id
    this.video.chapterId=chapterId;

    this.VideoDialogTitle="添加小节";
    //判断是添加还是修改
    this.videoFlag=videoFlag
    if(!(videoFlag===0)){//不等于0表示修改,根据小节id查出数据保存,实现回显
      this.VideoDialogTitle="修改小节";
      eduCourse.selectVideoById(videoFlag)
      .then(response=>{
        this.video=response.data.video;//将查出的数据保存
      })
    }
  },
  /* 点击弹窗确认按钮后*/
  saveOrUpdateVideo(){
    if(this.videoFlag===0){//为0表示添加
      this.insertVideo();
    }else{//表示为修改操作
      this.updateVideo();
    }
    //1、关闭弹窗(多关一次)
    this.dialogVideoFormVisible = false;
    //刷新页面(为了保险,多刷一次)
    this.getChapterAndVideoById(this.$route.params.id)
  },
  /* 添加小节*/
  insertVideo(){
    eduCourse.insertVideo(this.video)
    .then(response=>{
      //获取课程id
      this.video.courseId=this.$route.params.id;
      //执行添加操作
      eduCourse.insertVideo(this.video)
      .then(response=>{
        //1、关闭弹窗
        this.dialogVideoFormVisible = false;
        //2、提示信息
        this.$message({//提示
          type: 'success',
          message: '添加成功!'
        })
      })
      //刷新页面(为了保险,多刷一次)
      this.getChapterAndVideoById(this.$route.params.id)
    })
  },
  /* 修改小节*/
  updateVideo(){
    eduCourse.updateVideo(this.video)
    .then(response=>{
      //1、关闭弹窗
      this.dialogChapterFormVisible = false;
      //2、提示信息
      this.$message({//提示
        type: 'success',
        message: '修改成功!'
      })
    })
    //刷新页面(为了保险,多刷一次)
    this.getChapterAndVideoById(this.$route.params.id)
  },
  /* 删除小节*/
  deleteVideo(id){
    this.$confirm('此操作将永久删除数据, 是否继续?', '提示', {
              confirmButtonText: '确定',
              cancelButtonText: '取消',
              type: 'warning'
            }).then(() => {
              eduCourse.deleteVideo(id)
              .then(response=>{
                this.$message({//提示
                  type: 'success',
                  message: '删除成功!'
                })
              })
              //刷新页面(为了保险,多刷一次)
              this.getChapterAndVideoById(this.$route.params.id)
            }).catch(() => {
              this.$message({
                type: 'info',
                message: '已取消删除'
              })
              //刷新页面(为了保险,多刷一次)
              this.getChapterAndVideoById(this.$route.params.id)
            });
    //刷新页面(为了保险,多刷一次)
    this.getChapterAndVideoById(this.$route.params.id)
  },

10、(后端)整体数据的回显(手写sql,除了需要自己写Mapper以外,其它和以前的内容一模一样)

1、编写sql语句

#查询出指定课程id的课程id,课程标题,课程价格,总课时,课程封面,课程讲师,课程简介,课程类别一级和二级

select
	课程基本信息.id as 课程id,
	课程基本信息.title as 课程标题,
	课程基本信息.price as 课程价格,
	课程基本信息.lesson_num as 总课时,
	课程基本信息.cover as 课程封面,
	课程基本信息.status as 课程发布状态,
	讲师.`name` as 讲师名,
	课程简介.description as 课程简介,
	一级课程分类.title as 一级分类,
	二级课程分类.title as 二级分类
from
	edu_course as 课程基本信息
left join
	edu_teacher as 讲师
on
	课程基本信息.teacher_id=讲师.id
left join
	edu_course_description as 课程简介
on
	课程简介.id=课程基本信息.id
left join
	edu_subject as 一级课程分类
on
	课程基本信息.subject_id=一级课程分类.id 
left join
	edu_subject as 二级课程分类
on
	课程基本信息.subject_parent_id=二级课程分类.id
where
	课程基本信息.id=18
		

在这里插入图片描述

2、创建实体类

package com.yzpnb.eduservice.entity.vo;


import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import java.math.BigDecimal;

@Data
public class CourseAllInfoVo {

    @ApiModelProperty(value = "课程id")
    private String id;

    @ApiModelProperty(value = "课程标题")
    private String title;

    @ApiModelProperty(value = "课程价格")
    private BigDecimal price;

    @ApiModelProperty(value = "总课时")
    private Integer lessonNum;

    @ApiModelProperty(value = "课程封面")
    private String cover;

	@ApiModelProperty(value = "课程发布状态")
    private String status;
    
    @ApiModelProperty(value = "讲师名")
    private String teacherName;

    @ApiModelProperty(value = "课程简介")
    private String description;

    @ApiModelProperty("一级分类")
    private String oneTitle;

    @ApiModelProperty("二级分类")
    private String twoTitle;
}

在这里插入图片描述

3、编写mapper映射文件

在这里插入图片描述

<?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.yzpnb.eduservice.mapper.EduCourseMapper"><!--mapper映射的接口-->
    <!--
        #查询出指定课程id的课程id,课程标题,课程价格,总课时,课程封面,课程讲师,课程简介,课程类别一级和二级

        select
            课程基本信息.id as 课程id,
            课程基本信息.title as 课程标题,
            课程基本信息.price as 课程价格,
            课程基本信息.lesson_num as 总课时,
            课程基本信息.cover as 课程封面,
            课程基本信息.status as 课程发布状态,
            讲师.`name` as 讲师名,
            课程简介.description as 课程简介,
            一级课程分类.title as 一级分类,
            二级课程分类.title as 二级分类
        from edu_course as 课程基本信息
        left join edu_teacher as 讲师 on 课程基本信息.teacher_id=讲师.id
        left join edu_course_description as 课程简介 on 课程简介.id=课程基本信息.id
        left join edu_subject as 一级课程分类 on 课程基本信息.subject_id=一级课程分类.id
        left join  edu_subject as 二级课程分类 on 课程基本信息.subject_parent_id=二级课程分类.id
        where 课程基本信息.id=18

    -->
    <select id="selectCourseAllInfoVo" parameterType="java.lang.String" resultType="com.yzpnb.eduservice.entity.vo.CourseAllInfoVo">
        select
            ec.id as id,
            ec.title as title,
            ec.price as price,
            ec.lesson_num as lessonNum,
            ec.cover as cover,
            ec.status as status,
            et.`name` as teacherName,
            ecd.description as description,
            es1.title as oneTitle,
            es2.title as twoTitle
        from
            edu_course as ec
        left join
            edu_teacher as et
        on
            ec.teacher_id=et.id
        left join
            edu_course_description as ecd
        on
            ecd.id=ec.id
        left join
            edu_subject as es1
        on
            ec.subject_id=es1.id
        left join
            edu_subject as es2
        on
            ec.subject_parent_id=es2.id
        where
            ec.id=#{id}
    </select>
</mapper>

在这里插入图片描述

4、service

在这里插入图片描述在这里插入图片描述

5、controller

在这里插入图片描述

6、解决百分百会遇到的错误

Invalid bound statement (not found):com.yzpnb.eduservice.mapper.EduCourseMapper.selectCourseAllInfoVo

这个错误很简单,就是找不到mapper.xml文件
我们可以将xml文件复制到能找到的地方,或者在编程时就按照官方规定将xml文件放在resource文件夹中
或者通过配置
1、配置pom.xml文件
2、配置application.yml文件

在这里插入图片描述

1、配置pom.xml让其编译指定包下的xml文件

<!-- 项目打包时会将java目录中的*.xml文件也进行打包 -->
    <build>
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.xml</include>
                </includes>
                <filtering>false</filtering>
            </resource>
        </resources>
    </build>

在这里插入图片描述

2、配置启动类

mybatis-plus:
  mapper-locations: classpath:com/yzpnb/eduservice/mapper/xml/*.xml #配置mapper xml文件的路径

在这里插入图片描述

7、测试

在这里插入图片描述

11、(前端)整体数据回显

1、api接口

在这里插入图片描述

2、代码实现

<template>
  <div class="app-container">
    <h2 style="text-align: center;">发布新课程</h2>
    <el-steps :active="3" process-status="wait" align-center style="margin-bottom: 40px;">
      <el-step title="填写课程基本信息"/>
      <el-step title="创建课程大纲"/>
      <el-step title="提交审核"/>
    </el-steps>
    <div class="ccInfo">
        <img :src="courseAllInfoVo.cover">
        <div class="main">
          <h2>{{ courseAllInfoVo.title }}</h2>
          <p class="gray"><span>{{ courseAllInfoVo.lessonNum }}课时</span></p>
          <p><span>所属分类:{{ courseAllInfoVo.oneTitle }}{{ courseAllInfoVo.twoTitle }}</span></p>
          <p>课程讲师:{{ courseAllInfoVo.teacherName }}</p>
          <h3 class="red">{{ courseAllInfoVo.price }}</h3>
        </div>
      </div>
      <ul class="chanpterList">
          <li
              v-for="chapter in list"
              :key="chapter.id">
              <p>{{ chapter.title }}</p>
              <!-- 视频 -->
              <ul class="chanpterList videoList">
                  <li
                      v-for="video in chapter.children"
                      :key="video.id">
                      <p>{{ video.title }}</p>
                  </li>
              </ul>
          </li>
      </ul>
    <div>
      <el-button @click="previous">返回修改</el-button>
      <el-button :disabled="saveBtnDisabled" type="primary" @click="publish">发布课程</el-button>
    </div>
  </div>
</template>
<script>
  import eduCourse from '@/api/course/eduCourse.js'
export default {
  data() {
    return {
      saveBtnDisabled: false ,// 保存按钮是否禁用
      courseAllInfoVo:{},             //初始化数据
      list:[],                        //数据
    }
  },
  created() {
    console.log('publish created')
    this.init()
  },
  methods: {
    /* 初始化*/
    init(){
      this.selectCourseAllInfoVoById(this.$route.params.id)
      this.getChapterAndVideoById(this.$route.params.id)
    },
    /* 获取所有数据回显*/
    selectCourseAllInfoVoById(id){
      eduCourse.selectCourseAllInfoVoById(id)
      .then(response=>{
        this.courseAllInfoVo=response.data.courseAllInfoVo
      })
    },
    /* 根据课程id查询章节和小节*/
    getChapterAndVideoById(id){
      eduCourse.getChapterAndVideoById(id)
      .then(response=>{
        this.list=response.data.chapterAndVideo
       })
    },
    previous() {
      console.log('previous')
      this.$router.push({name:"课程添加2",params:{id:this.$route.params.id}})
    },
    publish() {
      console.log('publish')
      this.$router.push({name:"课程展示"})
    }
  }
}
</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;
}
.chanpterList{
    position: relative;
    list-style: none;
    margin: 0;
    padding: 0;
    border: 0;
}
.chanpterList li{
  position: relative;
}
.videoList{
  padding-left: 50px;
}
.chanpterList p{
  float: left;
  font-size: 20px;
  margin: 10px 0;
  padding: 10px;
  height: 70px;
  line-height: 50px;
  width: 100%;
  border: 1px solid #DDD;
}
.videoList p{
  float: left;
  font-size: 14px;
  margin: 10px 0;
  padding: 10px;
  height: 50px;
  line-height: 30px;
  width: 100%;
  border: 1px dotted #DDD;
}
</style>

3、测试

在这里插入图片描述

12、完善最终发布

在这里插入图片描述

# 修改指定id的发布状态,如果是未发布Draft就改为Normal已发布,如果是已发布Normal,就改为Draft未发布
update 
	edu_course
set
	status=if(status='Draft',
		'Normal',
		'Draft'
	)
where
	id=18

1、后端

mapper

 <!--修改指定id的发布状态,如果是未发布Draft就改为Normal已发布,如果是已发布Normal,就改为Draft未发布-->
    <update id="updateStatus" >
        update
            edu_course
        set
            status=if(status='Draft',
                'Normal',
                'Draft'
            )
        where
            id=#{id}
    </update>

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2、前端

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

二、课程列表增删改查

1、(后端)实现分页展示课程信息(手写sql)

1、代码

一、controller
@ApiOperation("分页查询课程信息")
    @GetMapping("limitSelectCourseAllInfoVo/{current}/{size}")
    public Result limitSelect(@ApiParam(name = "current",value = "当前页")
                              @PathVariable Long current,
                              @ApiParam(name = "size",value = "每页记录数")
                              @PathVariable Long size){
        List<CourseAllInfoVo> courseAllInfoVoList= eduCourseService.limitSelectCourseAllInfoVo(current,size);
        Map<String,Object> map=new HashMap<>();
        map.put("courseAllInfoVoList",courseAllInfoVoList);
        map.put("tatol",eduCourseService.count());//添加数据总量
        return Result.ok().data("courseAllInfoVoMap",map);
    }
二、service
	/**
     * 分页查询课程信息
     * @param current 当前页
     * @param size  每页记录
     * @return  每页数据
     */
    @Override
    public List<CourseAllInfoVo> limitSelectCourseAllInfoVo(Long current, Long size) {
        //获取每页起始索引
        Long index=(current-1)*size;
        return eduCourseMapper.limitSelectCourseAllInfoVo(index,size);
    }
三、mapper
/**
     * 分页查询课程信息
     * @param index 起始索引
     * @param size  每页记录数
     * @return
     */
    public List<CourseAllInfoVo> limitSelectCourseAllInfoVo(Long index, Long size);
四、xml
<!--分页查询课程信息-->
    <select id="limitSelectCourseAllInfoVo" resultType="com.yzpnb.eduservice.entity.vo.CourseAllInfoVo">
        select
            ec.id as id,
            ec.title as title,
            ec.price as price,
            ec.lesson_num as lessonNum,
            ec.cover as cover,
            ec.status as status,
            et.`name` as teacherName,
            ecd.description as description,
            es1.title as oneTitle,
            es2.title as twoTitle
        from
            edu_course as ec
        left join
            edu_teacher as et
        on
            ec.teacher_id=et.id
        left join
            edu_course_description as ecd
        on
            ecd.id=ec.id
        left join
            edu_subject as es1
        on
            ec.subject_id=es1.id
        left join
            edu_subject as es2
        on
            ec.subject_parent_id=es2.id
        limit #{index},#{size}
    </select>

在这里插入图片描述

2、测试

在这里插入图片描述

2、(后端)实现删除课程

在这里插入图片描述

1、controller(执需要编写controller即可)

/**
     * 删除课程,需要删除课程信息表,简介表,章节表,小节表
     */
    @Autowired
    EduCourseDescriptionService eduCourseDescriptionService;
    @Autowired
    EduChapterService eduChapterService;
    @Autowired
    EduVideoService eduVideoService;
    @ApiOperation("删除课程")
    @DeleteMapping("deleteCourseById/{id}")
    public Result deleteCourse(@ApiParam(name = "id",value = "课程id")
                               @PathVariable String id ){
        QueryWrapper queryWrapper=new QueryWrapper();
        queryWrapper.eq("course_id",id);
        
        //删除小节
        eduVideoService.remove(queryWrapper);
        //删除章节
        eduChapterService.remove(queryWrapper);
        //删除简介
        eduCourseDescriptionService.removeById(id);
        //删除课程信息
        eduCourseService.removeById(id);
        
        return Result.ok();
    }

2、(前端)实现分页展示课程信息

1、api接口

在这里插入图片描述

2、组件

<template>
  <div>
    <!-- 折叠栏-->
    <div class="app-container">
     <el-collapse @change="handleChange" accordion>
       <el-collapse-item v-for="courseAllInfoVo in courseAllInfoVoList" :title="courseAllInfoVo.title" :name="courseAllInfoVo.id">
         <!-- 折叠内容-->
         <div class="ccInfo">
             <img :src="courseAllInfoVo.cover">
             <div class="main">
               <h2>{{ courseAllInfoVo.title}}&nbsp;&nbsp;&nbsp;&nbsp;
                    <el-switch
                      v-model="courseAllInfoVo.status==='Normal'?true:false"
                      active-color="#13ce66"
                      inactive-color="#ff4949"
                      @change="switchChange(courseAllInfoVo.id)">
                    </el-switch>{{courseAllInfoVo.status==='Normal'?"已发布":"未发布"}}
               </h2>
               <p class="gray"><span>{{ courseAllInfoVo.lessonNum }}课时</span></p>
               <p><span>所属分类:{{ courseAllInfoVo.oneTitle }}{{ courseAllInfoVo.twoTitle }}</span></p>
               <p>课程讲师:{{ courseAllInfoVo.teacherName }}</p>
               <h3 class="red">{{ courseAllInfoVo.price }}</h3>
             </div>

           </div>
          <!-- 操作按钮-->
          <el-button type="primary" @click="updateCourse(courseAllInfoVo.id)">修改信息</el-button>
          <el-button type="success" @click="updateChapterVideo(courseAllInfoVo.id)">编辑课程大纲</el-button>
          <el-button type="danger" @click="deleteCourse(courseAllInfoVo.id)">删除课程</el-button>
       </el-collapse-item>
     </el-collapse>
    </div>
      <!-- 分页-->
    <div class="block">
        <el-pagination
          @current-change="limitSelectCourseAllInfoVo"
          :current-page="current"
          :page-size="size"
          layout=" total ,prev, pager, next, jumper"
          :total="total">
        </el-pagination>
      </div>
  </div>
</template>

3、代码实现

<script>
import eduCourse from '@/api/course/eduCourse.js'
export default {
  data() {
    return {
      courseAllInfoVoList:null,    //数据
      /* 初始化变量*/
      current:1,//表示当前页
      size:5,   //每页记录数
      total:0,
      search:"",//搜索内容
    }
  },
  watch: {
  },
  created() {
    console.log('info created')
    this.init()
  },
  methods: {
    init() {
      this.limitSelectCourseAllInfoVo();
    },
    /* 分页查询课程信息*/
    limitSelectCourseAllInfoVo(current =1){
      this.current=current;
      eduCourse.limitSelectCourseAllInfoVo(this.current,this.size)
      .then(response=>{
          this.courseAllInfoVoList=response.data.courseAllInfoVoMap.courseAllInfoVoList;
          this.total=response.data.courseAllInfoVoMap.total;
      })
    },
    /* 点击折叠面板触发改变事件*/
    handleChange(){
      console.log("11");
    },
    /* 当改变课程发布状态是*/
    switchChange(id){
      this.updateStatus(id)
    },
    /* 根据id修改发布状态*/
    updateStatus(id){
      eduCourse.updateStatus(id)
      .then(response=>{
        //修改成功重新获取信息
        this.limitSelectCourseAllInfoVo();

        this.$message({//提示
          type: 'success',
          message: '修改发布状态!'
        })
      })
      //为了保险,多获取一次
      this.limitSelectCourseAllInfoVo();
    },
    /**=========================按钮==========================================**/
    /* 单击修改信息按钮后*/
    updateCourse(cid){
      this.$router.push({name:"课程添加",params:{id:cid}})
    },
    /* 单击编辑课程大纲按钮后*/
    updateChapterVideo(cid){
      this.$router.push({name:"课程添加2",params:{id:cid}})
    },
    /* 删除课程接口 and 单击删除课程按钮后*/
    deleteCourse(cid){
      this.$confirm('此操作将永久删除数据, 是否继续?', '提示', {
                confirmButtonText: '确定',
                cancelButtonText: '取消',
                type: 'warning'
              }).then(() => {
                eduCourse.deleteCourseById(cid)
                .then(response=>{
                  this.$message({//提示
                    type: 'success',
                    message: '删除成功!'
                  })
                })
                //刷新页面(为了保险,多刷一次)
                this.limitSelectCourseAllInfoVo();
              }).catch(() => {
                this.$message({
                  type: 'info',
                  message: '已取消删除'
                })
              });
      //刷新页面(为了保险,多刷一次)
      this.limitSelectCourseAllInfoVo();
    }
},
}
</script>
<style>

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

4、测试

在这里插入图片描述

三、完善小节视频功能(阿里云视频点播)

1、阿里云视频点播

1、申请服务(请先登录,推荐使用支付宝扫码登录)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2、获取密钥(和OSS头像存储相同,上次已经有的就用上次的)

在这里插入图片描述

3、文档(不推荐使用API,使用SDK更方便,因为是已经封装好的)

在这里插入图片描述
在这里插入图片描述

4、开发分析

1、获取视频播放地址
	用于测试,可通过视频id获取
2、获取视频播放凭证
	如何不花钱申请域名也能播放加密视频?
	拥有凭证即可,可通过视频id获取
3、上传视频到阿里云点播
	我们上传的视频,如果你设置转码,它是会加密的。
	加密和未加密的区别在于,加密的视频url地址,是不能直接播放的,而未加密的视频地址是可以播放的
	而存储在阿里云中的每个视频都有唯一的一个id值
	所以我们在数据库中存这个id值,不存储视频地址,因为我们的视频不可以随便让别人看

2、阿里云视频点播环境搭建(此小节仅用于讲解)

1、引入依赖

<!--阿里云视频点播-->
 <dependency>
     <groupId>com.aliyun</groupId>
     <artifactId>aliyun-java-sdk-core</artifactId>
     <version>4.5.1</version>
 </dependency>
 <dependency>
     <groupId>com.aliyun</groupId>
     <artifactId>aliyun-java-sdk-vod</artifactId>
     <version>2.15.8</version>
 </dependency>
 <dependency>
     <groupId>com.google.code.gson</groupId>
     <artifactId>gson</artifactId>
     <version>2.8.6</version>
 </dependency>
 <!--阿里云OSS依赖 这个还非得3.1.0版本的-->
 <dependency>
     <groupId>com.aliyun.oss</groupId>
     <artifactId>aliyun-sdk-oss</artifactId>
     <version>3.1.0</version>
 </dependency>
 <!--阿里云视频上传-->
 <dependency>
     <groupId>com.aliyun</groupId>
     <artifactId>aliyun-java-vod-upload</artifactId>
     <version>1.4.12</version>
 </dependency>
 <!--阿里巴巴fastjson-->
 <dependency>
     <groupId>com.alibaba</groupId>
     <artifactId>fastjson</artifactId>
     <version>1.2.68</version>
 </dependency>
 <dependency>
     <groupId>org.json</groupId>
     <artifactId>json</artifactId>
     <version>20200518</version>
 </dependency>

在这里插入图片描述

2、阿里云视频点播基本使用方法介绍(先了解即可,之后会进入到实战)

1、初始化

在这里插入图片描述
在这里插入图片描述

2、获取视频播放地址

在这里插入图片描述

package com.test;

import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.vod.model.v20170321.GetPlayInfoRequest;
import com.aliyuncs.vod.model.v20170321.GetPlayInfoResponse;

import java.util.*;
public class Test {
/**我这里不使用这个方法**/
//    /*获取播放地址函数*/
//    public static GetPlayInfoResponse getPlayInfo(DefaultAcsClient client) throws Exception {
//        GetPlayInfoRequest request = new GetPlayInfoRequest();
//        request.setVideoId("视频ID");
//        return client.getAcsResponse(request);
//    }

    public static void main(String[] args) throws ClientException {
        /**1、根据视频id获取视频播放地址**/
        //1、创建初始化对象
        DefaultAcsClient client = InitVodClient.initVodClient("你的id", "你的密钥");

        //2、创建获取视频地址request请求对象和response响应对象
        GetPlayInfoRequest request=new GetPlayInfoRequest();
        GetPlayInfoResponse response = new GetPlayInfoResponse();

        //3、设置请求的视频id(从你的阿里云中找)
        request.setVideoId("你视频的id");

        //4、 根据初始化对象,获取数据,返回一个响应体对象
        response = client.getAcsResponse(request);//返回GetPlayInfoResponse对象response,里面封装了视频的所有信息

        try {
            List<GetPlayInfoResponse.PlayInfo> playInfoList = response.getPlayInfoList();
            //播放地址
            for (GetPlayInfoResponse.PlayInfo playInfo : playInfoList) {
                System.out.print("PlayInfo.PlayURL = " + playInfo.getPlayURL() + "\n");
            }
            //Base信息
            System.out.print("VideoBase.Title = " + response.getVideoBase().getTitle() + "\n");
        } catch (Exception e) {
            System.out.print("ErrorMessage = " + e.getLocalizedMessage());
        }
        System.out.print("RequestId = " + response.getRequestId() + "\n");
    }
}

在这里插入图片描述

3、获取视频凭证

在这里插入图片描述

/***获取播放凭证***/
        GetVideoPlayAuthRequest requestAuth = new GetVideoPlayAuthRequest();
        GetVideoPlayAuthResponse responseAuth = new GetVideoPlayAuthResponse();

        requestAuth.setVideoId("你的视频id");

        responseAuth=client.getAcsResponse(requestAuth);
        //播放凭证
        System.out.println("PlayAuth = " + responseAuth.getPlayAuth() + "\n");

4、删除视频

在这里插入图片描述

/**
     * 删除视频
     * @param client 发送请求客户端
     * @param idList 用户要删除的视频id,可以传多个
     * @throws Exception
     */
    public static void deleteVideo(DefaultAcsClient client,String ...idList) throws Exception {
        DeleteVideoRequest request = new DeleteVideoRequest();
        DeleteVideoResponse response = new DeleteVideoResponse();
        StringBuffer stringBuffer=new StringBuffer();

        for (String id:idList) {
            stringBuffer.append(id + ",");//将所有视频id用逗号拼接
        }
        stringBuffer.deleteCharAt(stringBuffer.length()-1);//删除最后多余的逗号

        //支持传入多个视频ID,多个用逗号分隔 request.setVideoIds("VideoId1,VideoId2");
        request.setVideoIds(stringBuffer.toString());

        try {
            response=client.getAcsResponse(request);
        } catch (Exception e) {
            System.out.print("ErrorMessage = " + e.getLocalizedMessage());
        }
        System.out.print("RequestId = " + response.getRequestId() + "\n");

    }

5、上传视频到阿里云(这里你需要学会如何引入jar包依赖)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

上传需要的依赖
<!--阿里云OSS依赖 这个还非得3.1.0版本的-->
        <dependency>
            <groupId>com.aliyun.oss</groupId>
            <artifactId>aliyun-sdk-oss</artifactId>
            <version>3.1.0</version>
        </dependency>
        <!--阿里云视频上传-->
        <dependency>
            <groupId>com.aliyun</groupId>
            <artifactId>aliyun-java-vod-upload</artifactId>
            <version>1.4.12</version>
        </dependency>
        <!--阿里巴巴fastjson-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.68</version>
        </dependency>
        <dependency>
            <groupId>org.json</groupId>
            <artifactId>json</artifactId>
            <version>20200518</version>
        </dependency>

在这里插入图片描述

代码测试
package com.test;

import com.aliyun.vod.upload.impl.UploadVideoImpl;
import com.aliyun.vod.upload.req.UploadVideoRequest;
import com.aliyun.vod.upload.resp.UploadVideoResponse;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.vod.model.v20170321.*;

import java.util.*;
public class Test {



    public static void main(String[] args) throws Exception {
        String accessKeyId="你的id";//id
        String accessKeySecret="你的密钥";//密钥


        /**1、根据视频id获取视频播放地址**/
        //1、创建初始化对象
        DefaultAcsClient client = InitVodClient.initVodClient(accessKeyId, accessKeySecret);
        //2、调用方法获取播放地址
//        Test.getPlayInfo(client,"11e7ce8051b948ddba24bd39d73a41ae");
        //3、调用方法获取播放凭证
//        Test.getVideoPlayAuth(client,"11e7ce8051b948ddba24bd39d73a41ae");
        //4、调用方法上传视频
        Test.testUploadVideo(accessKeyId, accessKeySecret,"testVideo","E:/flash/课程资料/1-阿里云上传测试视频/6 - What If I Want to Move Faster.mp4");
        //5、调用删除视频方法
        // Test.deleteVideo(client,"f296d3de53904917b5656db7320872f2");
    }
    /**
     * 本地文件上传接口
     *
     * @param accessKeyId
     * @param accessKeySecret
     * @param title 上传之后文件名
     * @param fileName  本地文件的路径和名称,就是你要上传的文件路径
     */
    private static void testUploadVideo(String accessKeyId, String accessKeySecret, String title, String fileName) {
        UploadVideoRequest request = new UploadVideoRequest(accessKeyId, accessKeySecret, title, fileName);
        /* 可指定分片上传时每个分片的大小,默认为1M字节 */
        request.setPartSize(1 * 1024 * 1024L);
        /* 可指定分片上传时的并发线程数,默认为1,(注:该配置会占用服务器CPU资源,需根据服务器情况指定)*/
        request.setTaskNum(1);
        /* 是否开启断点续传, 默认断点续传功能关闭。当网络不稳定或者程序崩溃时,再次发起相同上传请求,可以继续未完成的上传任务,适用于超时3000秒仍不能上传完成的大文件。
        注意: 断点续传开启后,会在上传过程中将上传位置写入本地磁盘文件,影响文件上传速度,请您根据实际情况选择是否开启*/
        request.setEnableCheckpoint(false);


        UploadVideoImpl uploader = new UploadVideoImpl();
        UploadVideoResponse response = uploader.uploadVideo(request);
        System.out.print("RequestId=" + response.getRequestId() + "\n");  //请求视频点播服务的请求ID

        if (response.isSuccess()) {
            /****如果上传成功就将视频id返回****/
            System.out.print("VideoId=" + response.getVideoId() + "\n");
        } else {
            /* 如果设置回调URL无效,不影响视频上传,可以返回VideoId同时会返回错误码。其他情况上传失败时,VideoId为空,此时需要根据返回错误码分析具体错误原因 */
            System.out.print("VideoId=" + response.getVideoId() + "\n");
            System.out.print("ErrorCode=" + response.getCode() + "\n");
            System.out.print("ErrorMessage=" + response.getMessage() + "\n");
        }
    }

    /*获取播放地址函数*/
    public static void getPlayInfo(DefaultAcsClient client,String id) throws Exception {
        GetPlayInfoRequest request = new GetPlayInfoRequest();
        GetPlayInfoResponse response = new GetPlayInfoResponse();
        request.setVideoId(id);
        response=client.getAcsResponse(request);
        try {
            List<GetPlayInfoResponse.PlayInfo> playInfoList = response.getPlayInfoList();
            //播放地址
            for (GetPlayInfoResponse.PlayInfo playInfo : playInfoList) {
                System.out.print("PlayInfo.PlayURL = " + playInfo.getPlayURL() + "\n");
            }
            //Base信息
            System.out.print("VideoBase.Title = " + response.getVideoBase().getTitle() + "\n");

        } catch (Exception e) {
            System.out.print("ErrorMessage = " + e.getLocalizedMessage());
        }
        System.out.print("RequestId = " + response.getRequestId() + "\n");
    }
    /*获取播放凭证函数*/
    public static void getVideoPlayAuth(DefaultAcsClient client,String id) throws Exception {
        /***获取播放凭证***/
        GetVideoPlayAuthRequest requestAuth = new GetVideoPlayAuthRequest();
        GetVideoPlayAuthResponse responseAuth = new GetVideoPlayAuthResponse();

        requestAuth.setVideoId(id);

        responseAuth=client.getAcsResponse(requestAuth);
        //播放凭证
        System.out.println("PlayAuth = " + responseAuth.getPlayAuth() + "\n");
    }
    /**
     * 删除视频
     * @param client 发送请求客户端
     * @return DeleteVideoResponse 删除视频响应数据
     * @throws Exception
     */
    public static void deleteVideo(DefaultAcsClient client,String ...idList) throws Exception {
        DeleteVideoRequest request = new DeleteVideoRequest();
        DeleteVideoResponse response = new DeleteVideoResponse();
        StringBuffer stringBuffer=new StringBuffer();

        for (String id:idList) {
            stringBuffer.append(id + ",");//将所有视频id用逗号拼接
        }
        stringBuffer.deleteCharAt(stringBuffer.length()-1);//删除最后多余的逗号

        //支持传入多个视频ID,多个用逗号分隔 request.setVideoIds("VideoId1,VideoId2");
        request.setVideoIds(stringBuffer.toString());

        try {
            response=client.getAcsResponse(request);
        } catch (Exception e) {
            System.out.print("ErrorMessage = " + e.getLocalizedMessage());
        }
        System.out.print("RequestId = " + response.getRequestId() + "\n");

    }
}

在这里插入图片描述
在这里插入图片描述

6、已上传视频转码(注意是给已经上传的视频,而我们上传的时候,可以通过设置参数直接上传转码视频)

/**
     * 提交媒体处理作业
     */
    public static SubmitTranscodeJobsResponse submitTranscodeJobs(DefaultAcsClient client,String id) throws Exception {
        SubmitTranscodeJobsRequest request = new SubmitTranscodeJobsRequest();
        //需要转码的视频ID
        request.setVideoId(id);
        //转码模板ID
        request.setTemplateGroupId("fc4c9920e7332c6c8d9518d4c00aaf54");
        return client.getAcsResponse(request);
    }

在这里插入图片描述
在这里插入图片描述

3、(后端)小节视频上传(实现转码上传,并返回视频id)

1、搭建微服务环境(根据图片创建目录结构)

在这里插入图片描述
在这里插入图片描述

2、视频点播工具类

package com.yzpnb.video.util;


import com.aliyun.vod.upload.impl.UploadVideoImpl;
import com.aliyun.vod.upload.req.UploadStreamRequest;
import com.aliyun.vod.upload.resp.UploadStreamResponse;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.profile.DefaultProfile;
import com.aliyuncs.vod.model.v20170321.*;

import java.io.InputStream;
import java.util.List;

public class AliyunVideoUtil {
    private String accessKeyId="LTAI4GGf4cjh4zZdNeKqUUGw";//id
    private String accessKeySecret="oQpda38y0cCUql5TOmaYiSJYDz32OP";//密钥
    private DefaultAcsClient client;
    /**
     * 初始化方法
     * @param accessKeyId 你的证书id
     * @param accessKeySecret   你的密钥
     * @return
     * @throws ClientException
     */
    public DefaultAcsClient initVodClient(String accessKeyId, String accessKeySecret) throws ClientException {
        String regionId = "cn-shanghai";  // 点播服务接入区域
        DefaultProfile profile = DefaultProfile.getProfile(regionId, accessKeyId, accessKeySecret);
        DefaultAcsClient client = new DefaultAcsClient(profile);
        return client;
    }

    /**
     * 本地文件上传接口,并使用模板组转码(流形式)
     *
     * @paramT accessKeyId 已经封装到工具类,无需声明
     * @paramT accessKeySecret 已经封装到工具类,无需声明
     * @param title 上传之后文件名
     * @param fileName  本地文件的路径和名称,就是你要上传的文件路径
     * @param inputStream 上传文件流
     */
    public String uploadVideo(String title, String fileName, InputStream inputStream) {
        /**设置上传请求头,包含转码*/
        UploadStreamRequest request = new UploadStreamRequest(accessKeyId, accessKeySecret, title, fileName,inputStream);
        /* 视频分类ID(可选) */
        //request.setCateId(0);
        /* 模板组ID(可选) */
        request.setTemplateGroupId("fc4c9920e7332c6c8d9518d4c00aaf54");

        /**执行流上传,获取响应体*/
        UploadVideoImpl uploader = new UploadVideoImpl();
        UploadStreamResponse response = uploader.uploadStream(request);


        if (response.isSuccess()) {
            /* 如果设置回调URL无效,不影响视频上传,可以返回VideoId同时会返回错误码。其他情况上传失败时,VideoId为空,此时需要根据返回错误码分析具体错误原因 */
            if(response.getVideoId()!=null) return response.getVideoId();
        }
            /****如果上传成功就将视频id返回****/
            return response.getVideoId();
    }

    /**
     * 提交媒体处理作业,视频转码
     */
    public SubmitTranscodeJobsResponse submitTranscodeJobs(String id) throws Exception {
        client=initVodClient(accessKeyId,accessKeySecret);

        SubmitTranscodeJobsRequest request = new SubmitTranscodeJobsRequest();
        //需要转码的视频ID
        request.setVideoId(id);
        //转码模板ID
        request.setTemplateGroupId("fc4c9920e7332c6c8d9518d4c00aaf54");
        return client.getAcsResponse(request);
    }

    /*获取播放地址函数*/
    public static void getPlayInfo(DefaultAcsClient client,String id) throws Exception {
        GetPlayInfoRequest request = new GetPlayInfoRequest();
        GetPlayInfoResponse response = new GetPlayInfoResponse();
        request.setVideoId(id);
        response=client.getAcsResponse(request);
        try {
            List<GetPlayInfoResponse.PlayInfo> playInfoList = response.getPlayInfoList();
            //播放地址
            for (GetPlayInfoResponse.PlayInfo playInfo : playInfoList) {
                System.out.print("PlayInfo.PlayURL = " + playInfo.getPlayURL() + "\n");
            }
            //Base信息
            System.out.print("VideoBase.Title = " + response.getVideoBase().getTitle() + "\n");

        } catch (Exception e) {
            System.out.print("ErrorMessage = " + e.getLocalizedMessage());
        }
        System.out.print("RequestId = " + response.getRequestId() + "\n");
    }
    /*获取播放凭证函数*/
    public static void getVideoPlayAuth(DefaultAcsClient client,String id) throws Exception {
        /***获取播放凭证***/
        GetVideoPlayAuthRequest requestAuth = new GetVideoPlayAuthRequest();
        GetVideoPlayAuthResponse responseAuth = new GetVideoPlayAuthResponse();

        requestAuth.setVideoId(id);

        responseAuth=client.getAcsResponse(requestAuth);
        //播放凭证
        System.out.println("PlayAuth = " + responseAuth.getPlayAuth() + "\n");
    }
    /**
     * 删除视频
     * @param client 发送请求客户端
     * @return DeleteVideoResponse 删除视频响应数据
     * @throws Exception
     */
    public static void deleteVideo(DefaultAcsClient client,String ...idList) throws Exception {
        DeleteVideoRequest request = new DeleteVideoRequest();
        DeleteVideoResponse response = new DeleteVideoResponse();
        StringBuffer stringBuffer=new StringBuffer();

        for (String id:idList) {
            stringBuffer.append(id + ",");//将所有视频id用逗号拼接
        }
        stringBuffer.deleteCharAt(stringBuffer.length()-1);//删除最后多余的逗号

        //支持传入多个视频ID,多个用逗号分隔 request.setVideoIds("VideoId1,VideoId2");
        request.setVideoIds(stringBuffer.toString());

        try {
            response=client.getAcsResponse(request);
        } catch (Exception e) {
            System.out.print("ErrorMessage = " + e.getLocalizedMessage());
        }
        System.out.print("RequestId = " + response.getRequestId() + "\n");

    }

}

3、application.yml

server:
  port: 8003 #微服务端口号
spring:
  application:
    name: service-video #服务名
  profiles:
    active: dev #环境
  servlet:
    multipart:
      max-file-size: 1024MB #设置单个文件最大上传大小限制
      max-request-size: 1024MB #设置总体文件最大上传大小限制

4、启动类

package com.yzpnb.video;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)//这里不使用数据库,所以需要取消自动扫描数据源
@ComponentScan(basePackages = {"com.yzpnb"})//自动扫描,扫描的包下必须有子目录
public class VideoApplication {
    public static void main(String[] args) {
        SpringApplication.run(VideoApplication.class);
    }
}

5、service(这是实现类(需要使用注解注入),需要你自己在接口中补上定义)

package com.yzpnb.video.service.impl;

import com.yzpnb.video.service.VideoService;
import com.yzpnb.video.util.AliyunVideoUtil;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.io.InputStream;

@Service
public class VideoServiceImpl implements VideoService {
    /**
     *
     * 上传本地视屏到阿里云
     * @param videoFile
     * @return
     */
    @Override
    public String uploadVideo(MultipartFile videoFile) {
        /**1、上传**/
        //1、创建封装阿里云视频操作的对象
        AliyunVideoUtil aliyunVideoUtil=new AliyunVideoUtil();
        
        String videoId = null;
        //2、获取输入流,文件名等等
        try {
            InputStream inputStream = videoFile.getInputStream();
            String fileName=videoFile.getOriginalFilename();//获取文件名
            /**
             * 设置上传文件名,获取文件名中第一个字符到“.”的前一个字符
             * substring:字符串截取函数,截取起始索引到末尾索引的前一个字符
             * lastIndexOf:从字符串最后面往前找,找我们指定的字符,找到的第一匹配的字符,返回字符的下标
             */
            String title=fileName.substring(0,fileName.lastIndexOf("."));
            videoId=aliyunVideoUtil.uploadVideo(title,fileName,inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return videoId;
    }
}

6、controller

package com.yzpnb.video.controller;

import com.yzpnb.common_utils.Result;
import com.yzpnb.video.service.VideoService;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

@RestController
@RequestMapping("/videoservice/")
@CrossOrigin
public class VideoController {

    @Autowired
    VideoService videoService;

    @ApiOperation("上传视频(以流形式上传,并且直接转码)")
    @PostMapping("uploadVideo")
    public Result uploadVideo(@ApiParam(name = "videoFile",value = "用户上传的文件") MultipartFile videoFile){
        String videoId=videoService.uploadVideo(videoFile);
        return  Result.ok().data("videoId",videoId);
    }
}

7、测试

在这里插入图片描述

4、(前端)小节视频上传

如果你配置了nginx,需要先配置代理
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1、组件

<el-form-item label="上传视频">
            <el-upload
                   :on-success="handleVodUploadSuccess"
                   :on-remove="handleVodRemove"
                   :before-remove="beforeVodRemove"
                   :on-exceed="handleUploadExceed"
                   :file-list="fileList"
                   :action="BASE_API+'/videoservice/uploadVideo'"
                   :limit="1"
                   class="upload-demo">
            <el-button size="small" type="primary">上传视频</el-button>
            <el-tooltip placement="right-end">
                <div slot="content">最大支持1G,<br>
                    支持3GP、ASFAVIDATDVFLVF4V<br>
                    GIFM2TM4VMJ2MJPEGMKVMOVMP4<br>
                    MPEMPGMPEGMTSOGGQTRMRMVB<br>
                    SWFTSVOBWMVWEBM 等视频格式上传</div>
                <i class="el-icon-question"/>
            </el-tooltip>
            </el-upload>
        </el-form-item>

在这里插入图片描述

2、代码实现

const defaultVideo={  //封装小节默认数据
        title:'1',       //小节名称
        sort:1,
        courseId:0,     //课程id
        chapterId:0,    //章节id
        play_count:0,   //播放次数
        isFree:1,       //是否可以试听,0收费,1免费
        duration:0,     //视频时长
        status:'',      //视频转码 Transcoding转码中  Normal正常
        size:0,         //视频源文件字节
        videoSourceId:'',//阿里云视频id
        videoOriginalName:'',//源文件名
  };
  data() {
    return {
      saveBtnDisabled: false ,        // 保存按钮是否禁用
      list:[],                        //数据
      dialogChapterFormVisible:false, //章节弹窗初始不显示
      dialogVideoFormVisible:false,   //小节弹窗初始不显示
      saveVideoBtnDisabled:false,     //小节弹窗中确定按钮是否可用
      chapter:defaultChapter,         //封装章节数据
      video:defaultVideo,             //封装小节数据
      flag:0,                         //用于判断是添加还是修改操作,0为添加,其它值为修改(因为我会把需要修改的章节id传过来)
      videoFlag:0,                    //用于判断小节是添加还是修改操作
      dialogTitle:"添加章节",
      videoDialogTitle:"添加小节",
      BASE_API:process.env.VUE_APP_BASE_API,//将http://localhost:9001赋值给BASE_API
      fileList:[],
    }

  },
  /**===================================视频上传操作==============================================**/
  //上传成功回调
  handleVodUploadSuccess(response, file, fileList) {
    this.video.videoSourceId = response.data.videoId//为数据库的阿里云视频id赋值
    this.video.videoOriginalName=file.name;//将组件中上传文件的名称给video中赋值
  },
  //上传开始之前调用,视图上传多于一个视频
  handleUploadExceed(files, fileList) {
    this.$message.warning('想要重新上传视频,请先删除已上传的视频')
  },
  /* 删除视频时调用*/
  handleVodRemove(){},
  /* 删除之前调用*/
  beforeVodRemove(){},

在这里插入图片描述
在这里插入图片描述

3、测试报错

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述在这里插入图片描述
在这里插入图片描述

5、删除视频以及删除小节时删除对应视频

1、后端

一、工具类:


/**
     * 删除视频
     * @paramT client 发送请求客户端
     * @return DeleteVideoResponse 删除视频响应数据
     * @throws Exception
     */
    public void deleteVideo(String ...idList) throws Exception {
        client=initVodClient(accessKeyId,accessKeySecret);
        DeleteVideoRequest request = new DeleteVideoRequest();
        DeleteVideoResponse response = new DeleteVideoResponse();
        StringBuffer stringBuffer=new StringBuffer();

        for (String id:idList) {
            stringBuffer.append(id + ",");//将所有视频id用逗号拼接
        }
        stringBuffer.deleteCharAt(stringBuffer.length()-1);//删除最后多余的逗号

        //支持传入多个视频ID,多个用逗号分隔 request.setVideoIds("VideoId1,VideoId2");
        request.setVideoIds(stringBuffer.toString());

        try {
            response=client.getAcsResponse(request);
        } catch (Exception e) {
            System.out.print("ErrorMessage = " + e.getLocalizedMessage());
        }
        System.out.print("RequestId = " + response.getRequestId() + "\n");

    }

二、controller


@DeleteMapping("{videoId}")
    public Result removeVideo(@ApiParam(name = "videoId", value = "云端视频id", required = true)
                              @PathVariable String videoId){
        videoService.removeVideo(videoId);
        return Result.ok().message("视频删除成功");
    }

三、service

 /**
     * 根据id删除视频
     * @param videoId
     */
    @Override
    public void removeVideo(String videoId) {
        //1、创建封装阿里云视频操作的对象
        AliyunVideoUtil aliyunVideoUtil=new AliyunVideoUtil();
        //2、删除视频
        try {
            aliyunVideoUtil.deleteVideo(videoId);
        } catch (Exception e) {
            throw new CustomExceptionHandler(20001,"删除失败");
        }
    }

在这里插入图片描述

2、前端

一、api接口
/* 删除小节视频*/
  removeById(id) {
      return request({
        url: `/videoservice/${id}`,
        method: 'delete'
      })
    },
二、写方法
/* 删除之前调用,也就是点击了删除按钮,这时弹窗提示用户是否确定删除*/
  beforeVodRemove(file, fileList) {
    return this.$confirm(`确定移除 ${file.name}?`)
  },
  /* 删除视频时调用,当用户点击了删除按钮后,又点击了弹窗中的确定按钮,则调用此方法*/
  handleVodRemove(file, fileList) {
    console.log(file)
    eduCourse.removeById(this.video.videoSourceId).then(response=>{
      this.$message({
        type: 'success',
        message: response.message
      })
      this.video.videoSourceId = ''//为数据库的阿里云视频id赋值
      this.video.videoOriginalName=''//将组件中上传文件的名称给video中赋值
    })
  },

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/grd_java/article/details/106317706
今日推荐