谷粒学院项目
项目后台设计---课程管理
九: 课程最终发布
1.后端接口
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="publishCourse.cover">
<div class="main">
<h2>{{ publishCourse.title }}</h2>
<p class="gray"><span>共{{ publishCourse.lessonNum }}课时</span></p>
<p><span>所属分类:{{ publishCourse.subjectLevelOne }} — {{publishCourse.subjectLevelTwo }}</span></p>
<p>课程讲师:{{ publishCourse.teacherName }}</p>
<h3 class="red">¥{{ publishCourse.price }}</h3>
</div>
</div>
<div>
<el-button @click="previous">返回修改</el-button>
<el-button :disabled="saveBtnDisabled" type="primary" @click="publish">发布课程</el-button>
</div>
</div>
</template>
<script>
import course from "@/api/edu/course"
export default {
data() {
return {
saveBtnDisabled: false, // 保存按钮是否禁用
courseId:'', // 课程id
publishCourse: {},
}
},
created() {
// 获取路由中课程的id值
if(this.$route.params && this.$route.params.id){
this.courseId = this.$route.params.id
// 根据课程id值来查询显示数据
this.getPublishCourse()
}
},
methods: {
// 根据课程id值来查询显示数据
getPublishCourse(){
course.getPublishCourseInfo(this.courseId)
.then(response => {
this.publishCourse = response.data.publishCourse
})
},
previous() {
console.log('previous')
this.$router.push({ path: '/course/chapter/' + this.courseId})
},
publish() {
this.$confirm('此操作将发布课程, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() =>{
course.PublishCourse(this.courseId)
.then(response => {
// 提示信息
this.$message({
type: 'success',
message: '发布成功!'
});
// 跳转到课程列表页面
this.$router.push({path: '/course/list'})
})
})
this.$router.push({ path: '/course/list' })
}
}
}
</script>
<style scoped>
.ccInfo {
background: #f5f5f5;
padding: 20px;
overflow: hidden;
border: 1px dashed #DDD;
margin-bottom: 40px;
position: relative;
}
.ccInfo img {
background: #d6d6d6;
width: 500px;
height: 278px;
display: block;
float: left;
border: none;
}
.ccInfo .main {
margin-left: 520px;
}
.ccInfo .main h2 {
font-size: 28px;
margin-bottom: 30px;
line-height: 1;
font-weight: normal;
}
.ccInfo .main p {
margin-bottom: 10px;
word-wrap: break-word;
line-height: 24px;
max-height: 48px;
overflow: hidden;
}
.ccInfo .main p {
margin-bottom: 10px;
word-wrap: break-word;
line-height: 24px;
max-height: 48px;
overflow: hidden;
}
.ccInfo .main h3 {
left: 540px;
bottom: 20px;
line-height: 1;
font-size: 28px;
color: #d32f24;
font-weight: normal;
position: absolute;
}
</style>
十: 课程列表
1:课程列表显示
页面效果:
(1)后端接口实现
(2)前端页面显示
<template>
<div class="app-container">
<!--查询表单-->
<el-form :inline="true" class="demo-form-inline">
<el-form-item>
<el-input v-model="courseQuery.title" placeholder="课程名称"/>
</el-form-item>
<el-form-item>
<el-select v-model="courseQuery.status" clearable placeholder="课程状态">
<el-option :value="'Normal'" label="已发布"/>
<el-option :value="'Draft'" label="未发布"/>
</el-select>
</el-form-item>
<el-button type="primary" icon="el-icon-search" @click="getList()">查询</el-button>
<el-button type="default" @click="resetData()">清空</el-button>
</el-form>
<!-- 表格 -->
<el-table v-loading="listLoading" :data="list" element-loading-text="数据加载中" border fit highlight-current-row>
<el-table-column label="序号" width="70" align="center">
<template slot-scope="scope">
{{ (page - 1) * limit + scope.$index + 1 }}
</template>
</el-table-column>
<el-table-column prop="title" label="名称" width="80" />
<!-- 进行一个判断,然后显示 -->
<el-table-column label="状态" width="80">
<template slot-scope="scope">
<!-- 3个等号判断类型和值 -->
{{ scope.row.status==='Draft'?'未发布':'已发布' }}
</template>
</el-table-column>
<el-table-column prop="lessonNum" label="课时数"/>
<el-table-column prop="gmtCreate" label="添加时间" width="160"/>
<el-table-column prop="viewCount" label="浏览数量" width="60" />
<el-table-column label="操作" width="200" align="center">
<template slot-scope="scope">
<!-- 重定向到 “/teacher/edit/:id” 页面-->
<router-link :to="'/teacher/edit/'+scope.row.id">
<el-button type="primary" size="mini" icon="el-icon-edit">编辑课程基本信息</el-button>
</router-link>
<router-link :to="'/teacher/edit/'+scope.row.id">
<el-button type="primary" size="mini" icon="el-icon-edit">编辑课程大纲</el-button>
</router-link>
<el-button type="danger" size="mini" icon="el-icon-delete" @click="removeDataById(scope.row.id)">删除课程信息</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<el-pagination
:current-page="page"
:page-size="limit"
:total="total"
style="padding: 30px 0; text-align: center;"
layout="total, prev, pager, next, jumper"
@current-change="getList"
/>
</div>
</template>
<script>
// 引入调用teacher.js文件
import course from '@/api/edu/course'
export default {
// 写核心代码位置
// data:{
// },
data(){ // 定义变量和初始值
return {
list:null, // 查询之后接口返回集合
page:1, // 当前页
limit:5, // 每页记录数
total:0, // 总记录数
courseQuery:{} // 条件封装对象
}
},
created(){ // 页面渲染之前执行,一般调用methods定义的方法。
// 调用
this.getList()
},
methods:{ //创建具体的方法,调用teacher.js定义的方法
// 讲师列表的方法
getList(page = 1){
this.page = page
// axios.post("").then().catch()
course.getCourseListPage(this.page, this.limit, this.courseQuery)
.then(response =>{ // 请求成功
// response接口返回的数据
// console.log(response)
this.list = response.data.eduCourses
this.total = response.data.total
})
},
// 清空的方法
resetData(){
// 把表单输入项数据清空
// 因为是双向绑定,所以我们只需清空teacherQuery即可
this.courseQuery = {}
// 查询所有讲师数据
this.getList()
},
// 删除讲师的方法
removeDataById(id){
// 删除提示框
this.$confirm('此操作将永久删除讲师记录, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
// 点击确定以后,要执行的操作
teacher.deleteTeacherId(id)
.then(response =>{
// 提示删除成功信息
this.$message({
type: 'success',
message: '删除成功!'
});
// 回到列表页面
this.getList()
})
})
}
}
}
</script>
2:课程删除
(1)后端接口
controller:
service:
(2)前端实现
<template>
<div class="app-container">
<!--查询表单-->
<el-form :inline="true" class="demo-form-inline">
<el-form-item>
<el-input v-model="courseQuery.title" placeholder="课程名称"/>
</el-form-item>
<el-form-item>
<el-select v-model="courseQuery.status" clearable placeholder="课程状态">
<el-option :value="'Normal'" label="已发布"/>
<el-option :value="'Draft'" label="未发布"/>
</el-select>
</el-form-item>
<el-button type="primary" icon="el-icon-search" @click="getList()">查询</el-button>
<el-button type="default" @click="resetData()">清空</el-button>
</el-form>
<!-- 表格 -->
<el-table v-loading="listLoading" :data="list" element-loading-text="数据加载中" border fit highlight-current-row>
<el-table-column label="序号" width="70" align="center">
<template slot-scope="scope">
{{ (page - 1) * limit + scope.$index + 1 }}
</template>
</el-table-column>
<el-table-column prop="title" label="名称" width="80" />
<!-- 进行一个判断,然后显示 -->
<el-table-column label="状态" width="80">
<template slot-scope="scope">
<!-- 3个等号判断类型和值 -->
{{ scope.row.status==='Draft'?'未发布':'已发布' }}
</template>
</el-table-column>
<el-table-column prop="lessonNum" label="课时数"/>
<el-table-column prop="gmtCreate" label="添加时间" width="160"/>
<el-table-column prop="viewCount" label="浏览数量" width="60" />
<el-table-column label="操作" width="200" align="center">
<template slot-scope="scope">
<!-- 重定向到 “/teacher/edit/:id” 页面-->
<router-link :to="'/teacher/edit/'+scope.row.id">
<el-button type="primary" size="mini" icon="el-icon-edit">编辑课程基本信息</el-button>
</router-link>
<router-link :to="'/teacher/edit/'+scope.row.id">
<el-button type="primary" size="mini" icon="el-icon-edit">编辑课程大纲</el-button>
</router-link>
<el-button type="danger" size="mini" icon="el-icon-delete" @click="removeDataById(scope.row.id)">删除课程信息</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<el-pagination
:current-page="page"
:page-size="limit"
:total="total"
style="padding: 30px 0; text-align: center;"
layout="total, prev, pager, next, jumper"
@current-change="getList"
/>
</div>
</template>
<script>
// 引入调用teacher.js文件
import course from '@/api/edu/course'
export default {
// 写核心代码位置
// data:{
// },
data(){ // 定义变量和初始值
return {
list:null, // 查询之后接口返回集合
page:1, // 当前页
limit:5, // 每页记录数
total:0, // 总记录数
courseQuery:{} // 条件封装对象
}
},
created(){ // 页面渲染之前执行,一般调用methods定义的方法。
// 调用
this.getList()
},
methods:{ //创建具体的方法,调用teacher.js定义的方法
// 讲师列表的方法
getList(page = 1){
this.page = page
// axios.post("").then().catch()
course.getCourseListPage(this.page, this.limit, this.courseQuery)
.then(response =>{ // 请求成功
// response接口返回的数据
// console.log(response)
this.list = response.data.eduCourses
this.total = response.data.total
})
},
// 清空的方法
resetData(){
// 把表单输入项数据清空
// 因为是双向绑定,所以我们只需清空teacherQuery即可
this.courseQuery = {}
// 查询所有讲师数据
this.getList()
},
// 删除讲师的方法
removeDataById(id){
// 删除提示框
this.$confirm('此操作将永久删除讲师记录, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
// 点击确定以后,要执行的操作
course.DeleteCourseByCourseId(id)
.then(response =>{
// 提示删除成功信息
this.$message({
type: 'success',
message: '删除成功!'
});
// 回到列表页面
this.getList()
})
})
}
}
}
</script>
十一: 阿里云视频点播功能
引入依赖
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-core</artifactId>
<version>4.6.0</version>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-vod</artifactId>
<version>2.16.5</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.83</version>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-kms</artifactId>
<version>2.10.1</version>
</dependency>
1.具体案例
(1)获取视频播放地址和视频播放凭证
1.新建service_vod模块
2.pom文件中引入依赖
<dependencies>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-core</artifactId>
</dependency>
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-vod</artifactId>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-sdk-vod-upload</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
</dependency>
</dependencies>
3.初始化操作,创建DefaultAcsClient对象
4.根据视频id,获取视频播放地址 视频播放凭证
package com.atguigu.vodtest;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.vod.model.v20170321.GetPlayInfoRequest;
import com.aliyuncs.vod.model.v20170321.GetPlayInfoResponse;
import com.aliyuncs.vod.model.v20170321.GetVideoPlayAuthRequest;
import com.aliyuncs.vod.model.v20170321.GetVideoPlayAuthResponse;
import org.junit.Test;
import java.util.List;
import static com.atguigu.vodtest.InitObject.initVodClient;
public class TestVod {
/*获取播放地址函数*/
public static GetPlayInfoResponse getPlayInfo(DefaultAcsClient client) throws Exception {
GetPlayInfoRequest request = new GetPlayInfoRequest();
request.setVideoId("7ad662b9cff64542aa488ff4083fb533");
return client.getAcsResponse(request);
}
/*获取播放凭证函数*/
public static GetVideoPlayAuthResponse getVideoPlayAuth(DefaultAcsClient client) throws Exception {
GetVideoPlayAuthRequest request = new GetVideoPlayAuthRequest();
request.setVideoId("7ad662b9cff64542aa488ff4083fb533");
return client.getAcsResponse(request);
}
// 根据视频id获取视频播放地址
@Test
public static void getPlayUrl() throws ClientException {
DefaultAcsClient client = initVodClient("LTAI5tPzJst9s2G1vfeCW8LF", "EyLbcoIk1Mcrk9o42VNlYw1wJ4XMYG");
GetPlayInfoResponse response = new GetPlayInfoResponse();
try {
response = getPlayInfo(client);
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");
}
// 根据视频id获取视频凭证
@Test
public static void getPlayAuth() {
DefaultAcsClient client = initVodClient("LTAI5tPzJst9s2G1vfeCW8LF", "EyLbcoIk1Mcrk9o42VNlYw1wJ4XMYG");
GetVideoPlayAuthResponse response = new GetVideoPlayAuthResponse();
try {
response = getVideoPlayAuth(client);
//播放凭证
System.out.print("PlayAuth = " + response.getPlayAuth() + "\n");
//VideoMeta信息
System.out.print("VideoMeta.Title = " + response.getVideoMeta().getTitle() + "\n");
} catch (Exception e) {
System.out.print("ErrorMessage = " + e.getLocalizedMessage());
}
System.out.print("RequestId = " + response.getRequestId() + "\n");
}
}
(2)上传视频到阿里云视频点播服务
/**
* 本地文件上传接口
*
* @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);
/* 可指定分片上传时每个分片的大小,默认为2M字节 */
request.setPartSize(2 * 1024 * 1024L);
/* 可指定分片上传时的并发线程数,默认为1,(注:该配置会占用服务器CPU资源,需根据服务器情况指定)*/
request.setTaskNum(1);
UploadVideoImpl uploader = new UploadVideoImpl();
UploadVideoResponse response = uploader.uploadVideo(request);
if (response.isSuccess()) {
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");
}
}
2.整合到项目当中
(1)配置nginx反向代理
(2)后端接口
1.引入依赖
2.编写配置文件
3.创建启动类
4.编写controller和service接口
controller:
package com.atguigu.vod.controller;
import com.atguigu.commonutils.R;
import com.atguigu.vod.service.VodService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
@RestController
@RequestMapping("/eduvod/video")
@CrossOrigin
public class VodController {
@Autowired
private VodService vodService;
// 上传视频到阿里云
@PostMapping("uploadAlyunVideo")
public R uploadAlyunVideo(MultipartFile multipartFile) {
// 返回上传视频的id
String videoId = vodService.uploadVideoAly(multipartFile);
return R.ok().data("videoId", videoId);
}
}
service:
package com.atguigu.vod.service.impl;
import com.aliyun.vod.upload.impl.UploadVideoImpl;
import com.aliyun.vod.upload.req.UploadStreamRequest;
import com.aliyun.vod.upload.resp.UploadStreamResponse;
import com.atguigu.vod.service.VodService;
import com.atguigu.vod.utils.ConstantVodUtils;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.InputStream;
@Service
public class VodServiceImpl implements VodService {
@Override
public String uploadVideoAly(MultipartFile multipartFile) {
try {
// fileName:上传的文件的原始名称
String filename = multipartFile.getOriginalFilename();
// title:阿里云里面显示的文件名称
String title = filename.substring(0, filename.lastIndexOf("."));
// inputStream:上传文件输入流
InputStream inputStream = multipartFile.getInputStream();
UploadStreamRequest request = new UploadStreamRequest(ConstantVodUtils.KEY_ID, ConstantVodUtils.KEY_SECRET, title, filename, inputStream);
UploadVideoImpl uploader = new UploadVideoImpl();
UploadStreamResponse response = uploader.uploadStream(request);
String videoId = "";
if (response.isSuccess()) {
videoId = response.getVideoId();
} else { //如果设置回调URL无效,不影响视频上传,可以返回VideoId同时会返回错误码。其他情况上传失败时,VideoId为空,此时需要根据返回错误码分析具体错误原因
videoId = response.getVideoId();
}
return videoId;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
读取配置文件的实体类:
package com.atguigu.vod.utils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.stereotype.Component;
@Component
public class ConstantVodUtils implements InitializingBean {
@Value("${aliyun.vod.file.keyid}")
private String keyid;
@Value("${aliyun.vod.file.keysecret}")
private String keysecret;
public static String KEY_ID;
public static String KEY_SECRET;
@Override
public void afterPropertiesSet() throws Exception {
KEY_ID = keyid;
KEY_SECRET = keysecret;
}
}
(3)前端页面实现
chapter.vue
下一个章节完善
十二: 删除阿里云视频
1.后端接口
2.前端页面
chapter.vue
<template>
<div class="app-container">
<h2 style="text-align: center;">发布新课程</h2>
<el-steps :active="2" process-status="wait" align-center style="marginbottom: 40px;">
<el-step title="填写课程基本信息"/>
<el-step title="创建课程大纲"/>
<el-step title="提交审核"/>
</el-steps>
<!-- 自己编写的列表显示功能
<ul>
<li v-for="chapter in chapterVideoList" :key="chapter.id">
{{chapter.title}}
<ul>>
<li v-for="video in chapter.children" :key="video.id">
{{video.title}}
</li>
</ul>
</li>
</ul> -->
<el-button type="text" @click="dialogChapter">添加章节</el-button>
<!-- 章节 -->
<ul class="chanpterList">
<li v-for="chapter in chapterVideoList" :key="chapter.id">
<p>
{{ chapter.title }}
<span class="acts">
<el-button type="text" @click="openVideo(chapter.id)">添加小节</el-button>
<el-button style="" type="text" @click="openEditChapter(chapter.id)">编辑</el-button>
<el-button type="text" @click="removeChapter(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 style="" type="text" @click="openEditVideo(video.id)">编辑</el-button>
<el-button type="text" @click="removeVideo(video.id)">删除</el-button>
</span>
</p>
</li>
</ul>
</li>
</ul>
<div>
<el-button @click="previous">上一步</el-button>
<el-button :disabled="saveBtnDisabled" type="primary" @click="next">下一步</el-button>
</div>
<!-- 添加和修改章节表单 -->
<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-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.free">
<el-radio :label="true">免费</el-radio>
<el-radio :label="false">默认</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="上传视频">
<el-upload
:on-success="handleVodUploadSuccess"
:on-remove="handleVodRemove"
:before-remove="beforeVodRemove"
:on-exceed="handleUploadExceed"
:file-list="fileList"
:action="BASE_API+'/eduvod/video/uploadAlyunVideo'"
: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>
</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>
</div>
</template>
<script>
import chapter from '@/api/edu/chapter'
import video from '@/api/edu/video'
export default{
data(){
return{
saveBtnDisabled:false,
courseId:'', // 课程id
chapterVideoList:[],
dialogChapterFormVisible:false, // 表示章节弹框的值
dialogVideoFormVisible:false, // 表示小节弹框的值
chapter:{ // 封装章节数据
courseId:'',
title:'',
sort:0
},
video:{ // 封装小节数据
chapterId:'',
courseId:'',
title:'',
sort:0,
free:0,
videoSourceId:'',
videoOriginalName:'' // 上传视频名称
},
fileList: [],//上传文件列表
BASE_API: process.env.BASE_API // 接口API地址
}
},
created(){
// 获取到路由里面的课程id值
if(this.$route.params && this.$route.params.id){
this.courseId = this.$route.params.id
this.getChapterVideo()
}
},
methods:{
// 点击x的时候,调用这个方法
beforeRemove(file, fileList) {
return this.$confirm(`确定移除 ${ file.name }?`);
},
// 点击确定时,调用这个方法
handleVodRemove(){
// 调用接口里面删除视频的方法
video.deleteAliyVideoById(this.video.videoSourceId)
.then(response =>{
// 提示删除成功信息
this.$message({
type: 'success',
message: '删除成功!'
});
// 把文件列表,视频id,视频名称清空
this.fileList = []
this.video.videoSourceId = ''
this.video.videoOriginalName = ''
})
},
//成功回调
handleVodUploadSuccess(response, file, fileList) {
// 上传成功以后的视频id赋值到video中
this.video.videoSourceId = response.data.videoId
// 上传成功以后的视频名称赋值到video中
this.video.videoOriginalName = file.name
},
//视图上传多于一个视频
handleUploadExceed(files, fileList) {
this.$message.warning('想要重新上传视频,请先删除已上传的视频')
},
//**************************************针对小节的操作**********************************/
// 点击删除按钮后,调用的方法
removeVideo(videoId){
// 删除提示框
this.$confirm('此操作将永久删除小节记录, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
// 点确定以后执行的操作
video.deleteVideo(videoId)
.then(response => {
// 提示删除成功信息
this.$message({
type: 'success',
message: '删除成功!'
});
// 返回页面
// 根据课程id获取章节和小节数据列表
// 这里我们已经有了课程id了,所以不需要在重新赋值了
this.getChapterVideo()
})
})
},
// 点击编辑按钮后,调用的方法
openEditVideo(videoId){
// 弹出修改框 --- 和添加框是一个
this.dialogVideoFormVisible = true
// 根据小节id查询出数据,进行回显
video.getVideoById(videoId)
.then(response => {
this.video = response.data.eduVideo
})
},
// 点击添加小节,调用这个方法
openVideo(chapterId){
// 弹出框
this.dialogVideoFormVisible = true
// 给小节数据添加上章节id
this.video.chapterId = chapterId
// 小节数据清空
this.video.courseId = ''
this.video.title = ''
this.video.sort = 0
this.free = 0
},
// 添加小节的方法
addVideo(){
// 设置课程id到video对象当中去
this.video.courseId = this.courseId
// 调用api接口,往数据库中添加小节
video.addVideo(this.video)
.then(response => {
// 关闭弹窗
this.dialogVideoFormVisible = false
// 提示成功信息
this.$message({
type: 'success',
message: '添加成功!'
});
// 刷新页面
this.getChapterVideo()
})
},
// 修改小节的方法
updateVideo(){
video.updateVideo(this.video)
.then(response => {
// 关闭弹窗
this.dialogVideoFormVisible = false
// 提示成功信息
this.$message({
type: 'success',
message: '修改成功!'
});
// 刷新页面
this.getChapterVideo()
})
},
// 点击小节弹框的确定以后,要执行的方法
saveOrUpdateVideo(){
// 根据小节里面是否有课程id来进行判断是修改还是添加
if(this.video.courseId){
// 有课程id 说明是修改小节
this.updateVideo()
} else {
// 添加小节
this.addVideo()
}
},
//**************************************针对章节的操作**********************************/
// 点击删除按钮以后,调用的方法
removeChapter(chapterId){
// 删除提示框
this.$confirm('此操作将永久删除章节记录, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() =>{
// 点击确定以后要执行的操作
// 调用后端api接口,进行删除操作
chapter.deleteChapter(chapterId)
.then(response => {
// 提示删除成功信息
this.$message({
type: 'success',
message: '删除成功!'
});
// 返回页面
// 根据课程id获取章节和小节数据列表
// 这里我们已经有了课程id了,所以不需要在重新赋值了
this.getChapterVideo()
})
})
},
// 点击修改按钮后,调用的方法
openEditChapter(chapterId){
// 弹出修改框---和保存框是一个
this.dialogChapterFormVisible = true
// 根据chapterid查询出数据,进行回显
chapter.getChapterById(chapterId)
.then(response => {
this.chapter = response.data.eduChapter
})
},
// 点击添加章节按钮后,执行的方法
dialogChapter(){
this.dialogChapterFormVisible = true
// 章节数据清空
this.chapter = {}
},
// 添加章节
addChapter(){
// 设置课程id到chapter对象当中去
this.chapter.courseId = this.courseId
chapter.addChapter(this.chapter)
.then(response =>{
// 关闭弹窗
this.dialogChapterFormVisible = false
// 提示成功信息
this.$message({
type: 'success',
message: '操作成功!'
});
// 刷新页面
this.getChapterVideo()
})
},
// 修改章节的方法
updateChapter(){
chapter.updateChapter(this.chapter)
.then(response =>{
// 关闭弹窗
this.dialogChapterFormVisible = false
// 提示成功信息
this.$message({
type: 'success',
message: '修改成功!'
});
// 刷新页面
this.getChapterVideo()
})
},
// 添加/修改 章节
saveOrUpdate(){
// 根据章节里面是否有课程id来进行判断是修改还是添加
if(this.chapter.courseId){
// 有课程id
// 修改
this.updateChapter()
} else {
// 添加
this.addChapter()
}
},
//根据课程id获取章节和小节数据列表
getChapterVideo(){
chapter.getAllChapterVideo(this.courseId)
.then(response =>{
this.chapterVideoList = response.data.allChapterVideo
})
},
previous(){
// 跳转到第一步
this.$router.push({path:'/course/info/'+this.courseId})
},
next(){
// 跳转到第二步()
this.$router.push({path:'/course/publish/'+this.courseId})
}
}
}
</script>
<style scoped>
.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>
十三: 微服务
SpringCloud:
SpringCloud和SpringBoot的关系:
Spring Cloud相关基础服务组件:
Spring Cloud版本介绍:
1.使用springcloud实现删除小节删除视频功能
(1)nacos(注册中心)
(2)nacos使用流程
1.安装nacos
2.启动:
3.访问nacos后端页面:
(3)注册微服务
1.引入依赖(service中的pom文件)
<!--服务注册-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
2.在要注册的服务的配置文件中(application.properties)进行配置
# nacos服务地址 spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
3.在要注册的服务的启动类上加上注解
package com.atguigu.eduservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication
@ComponentScan(basePackages = {"com.atguigu"})
@EnableDiscoveryClient // 进行nacos注册的注解
public class EduApplication {
public static void main(String[] args) {
SpringApplication.run(EduApplication.class, args);
}
}
4.最终效果
(4)微服务调用组件---Feign
前提条件:把互相调用的微服务在nacos中进行注册
1.依赖引入
<!--服务调用-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2.在调用端(service_edu)的启动类上加上服务调用注解
3.在调用端(service_edu)中创建接口
package com.atguigu.eduservice.client;
import com.atguigu.commonutils.R;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
@Component
@FeignClient("service-vod")
public interface VodClient {
// 定义调用的方法路径
// 根据视频id删除视频(阿里云)
@DeleteMapping("/eduvod/video/removeAliyVideo/{id}")
public R removeAliyVideo(@PathVariable("id") String id);
}
4.实现代码删除小节并且删除阿里云视频
2.使用springcloud实现删除课程删除视频功能
(1)在service-vod中创建接口,删除多个视频
controller:
service:
(2)在service-edu中调用service-vod接口实现删除多个阿里云视频的功能
接口定义
package com.atguigu.eduservice.client;
import com.atguigu.commonutils.R;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
@Component
@FeignClient("service-vod")
public interface VodClient {
// 定义调用的方法路径
// 根据视频id删除视频(阿里云)
@DeleteMapping("/eduvod/video/removeAliyVideo/{id}")
public R removeAliyVideo(@PathVariable("id") String id);
// 删除多个阿里云视频
// 参数是多个视频id
@DeleteMapping("/eduvod/video/delete-batch")
public R deleteBatch(@RequestParam("videoIdList") List<String> videoIdList);
}
EduVideoServiceImpl
package com.atguigu.eduservice.service.impl;
import com.atguigu.eduservice.client.VodClient;
import com.atguigu.eduservice.entity.EduVideo;
import com.atguigu.eduservice.mapper.EduVideoMapper;
import com.atguigu.eduservice.service.EduVideoService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.ArrayList;
import java.util.List;
/**
* <p>
* 课程视频 服务实现类
* </p>
*
* @author testjava
* @since 2022-10-05
*/
@Service
public class EduVideoServiceImpl extends ServiceImpl<EduVideoMapper, EduVideo> implements EduVideoService {
@Autowired
private VodClient vodClient;
// 根据课程id删除小节
// TODO 删除小节时,删除对应视频文件
@Override
public void removeVideoByCourseId(String id) {
// 1.根据课程id查询出课程里面的所有视频id
QueryWrapper<EduVideo> wrapperVideo = new QueryWrapper<>();
wrapperVideo.eq("course_id", id);
wrapperVideo.select("video_source_id");
List<EduVideo> eduVideos = baseMapper.selectList(wrapperVideo);
// 转化eduVideos集合
List<String> VideoIds = new ArrayList<>();
for (int i = 0; i < eduVideos.size(); i++) {
EduVideo eduVideo = eduVideos.get(i);
if (!StringUtils.isEmpty(eduVideo.getVideoSourceId())) {
VideoIds.add(eduVideo.getVideoSourceId());
}
}
// 调用方法实现删除操作
if (VideoIds.size() > 0) {
vodClient.deleteBatch(VideoIds);
}
QueryWrapper<EduVideo> wrapper = new QueryWrapper<>();
wrapper.eq("course_id", id);
baseMapper.delete(wrapper);
}
}