谷粒学院项目

154 阅读6分钟

谷粒学院项目

项目后台设计---课程管理

九: 课程最终发布

1.后端接口

捕获.PNG

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:课程列表显示

页面效果:

捕获.PNG

(1)后端接口实现

捕获.PNG

捕获.PNG

(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:课程删除

捕获.PNG

(1)后端接口

controller:

捕获.PNG

service:

捕获.PNG

捕获.PNG

(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)获取视频播放地址和视频播放凭证

捕获.PNG

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对象

捕获.PNG

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.整合到项目当中

捕获.PNG

(1)配置nginx反向代理

捕获.PNG

(2)后端接口

捕获.PNG

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)前端页面实现

捕获.PNG

chapter.vue

下一个章节完善

十二: 删除阿里云视频

1.后端接口

捕获.PNG

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>

十三: 微服务

捕获.PNG

SpringCloud:

捕获.PNG

SpringCloud和SpringBoot的关系:

捕获.PNG

Spring Cloud相关基础服务组件:

捕获.PNG

Spring Cloud版本介绍:

捕获.PNG

捕获.PNG

1.使用springcloud实现删除小节删除视频功能

捕获.PNG

(1)nacos(注册中心)

捕获.PNG

捕获.PNG

捕获.PNG

捕获.PNG

(2)nacos使用流程

1.安装nacos

2.启动:

捕获.PNG

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.最终效果

捕获.PNG

(4)微服务调用组件---Feign

前提条件:把互相调用的微服务在nacos中进行注册

1.依赖引入

<!--服务调用-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

2.在调用端(service_edu)的启动类上加上服务调用注解

捕获.PNG

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.实现代码删除小节并且删除阿里云视频

捕获.PNG

2.使用springcloud实现删除课程删除视频功能

捕获.PNG

(1)在service-vod中创建接口,删除多个视频

controller:

捕获.PNG

service:

捕获.PNG

(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);
    }
}

3.Hystrix基本概念

捕获.PNG

捕获.PNG

捕获.PNG

(1)Hystrix基本使用

捕获.PNG

捕获.PNG

捕获.PNG

捕获.PNG