1、安装tinymce,不安装这个没法使用tinymce vue(你可以直接下载离线包)
npm install tinymce
2、安装tinymce vue
npm install --save @tinymce/tinymce-vue
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'
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;
}
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;
/**
*
* 课程 前端控制器
*
*
* @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);
}
}
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;
/**
*
* 课程 服务实现类
*
*
* @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;
}
}
<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>
/**
* 查询
*/
@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();
}
/**
* 根据课程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,"修改课程简介信息失败");
}
}
设置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对应的二级分类,赋值
}
}
})
})
@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();
}
/**
* 删除章节,如果有小节,则不能删除章节
*/
@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);
}
}
<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>
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.*;
/**
*
* 课程视频 前端控制器
*
*
* @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();
}
}
<!-- 添加和修改课时表单 -->
<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>
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)
},
#查询出指定课程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
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;
}
<?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>
Invalid bound statement (not found):com.yzpnb.eduservice.mapper.EduCourseMapper.selectCourseAllInfoVo
这个错误很简单,就是找不到mapper.xml文件
我们可以将xml文件复制到能找到的地方,或者在编程时就按照官方规定将xml文件放在resource文件夹中
或者通过配置
1、配置pom.xml文件
2、配置application.yml文件
<!-- 项目打包时会将java目录中的*.xml文件也进行打包 -->
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml
false
mybatis-plus:
mapper-locations: classpath:com/yzpnb/eduservice/mapper/xml/*.xml #配置mapper xml文件的路径
<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>
# 修改指定id的发布状态,如果是未发布Draft就改为Normal已发布,如果是已发布Normal,就改为Draft未发布
update
edu_course
set
status=if(status='Draft',
'Normal',
'Draft'
)
where
id=18
mapper
<!--修改指定id的发布状态,如果是未发布Draft就改为Normal已发布,如果是已发布Normal,就改为Draft未发布-->
<update id="updateStatus" >
update
edu_course
set
status=if(status='Draft',
'Normal',
'Draft'
)
where
id=#{id}
</update>
一、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>
/**
* 删除课程,需要删除课程信息表,简介表,章节表,小节表
*/
@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();
}
<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}}
<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>
<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>
1、获取视频播放地址
用于测试,可通过视频id获取
2、获取视频播放凭证
如何不花钱申请域名也能播放加密视频?
拥有凭证即可,可通过视频id获取
3、上传视频到阿里云点播
我们上传的视频,如果你设置转码,它是会加密的。
加密和未加密的区别在于,加密的视频url地址,是不能直接播放的,而未加密的视频地址是可以播放的
而存储在阿里云中的每个视频都有唯一的一个id值
所以我们在数据库中存这个id值,不存储视频地址,因为我们的视频不可以随便让别人看
<!--阿里云视频点播-->
<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>
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");
}
}
/***获取播放凭证***/
GetVideoPlayAuthRequest requestAuth = new GetVideoPlayAuthRequest();
GetVideoPlayAuthResponse responseAuth = new GetVideoPlayAuthResponse();
requestAuth.setVideoId("你的视频id");
responseAuth=client.getAcsResponse(requestAuth);
//播放凭证
System.out.println("PlayAuth = " + responseAuth.getPlayAuth() + "\n");
/**
* 删除视频
* @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");
}
<!--阿里云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");
}
}
/**
* 提交媒体处理作业
*/
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);
}
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");
}
}
server:
port: 8003 #微服务端口号
spring:
application:
name: service-video #服务名
profiles:
active: dev #环境
servlet:
multipart:
max-file-size: 1024MB #设置单个文件最大上传大小限制
max-request-size: 1024MB #设置总体文件最大上传大小限制
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);
}
}
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;
}
}
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);
}
}
<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、ASF、AVI、DAT、DV、FLV、F4V、<br>
GIF、M2T、M4V、MJ2、MJPEG、MKV、MOV、MP4、<br>
MPE、MPG、MPEG、MTS、OGG、QT、RM、RMVB、<br>
SWF、TS、VOB、WMV、WEBM 等视频格式上传</div>
<i class="el-icon-question"/>
</el-tooltip>
</el-upload>
</el-form-item>
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(){},
一、工具类:
/**
* 删除视频
* @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,"删除失败");
}
}
一、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中赋值
})
},