谷粒学院项目笔记9——课程发布功能、课程列表功能、视频点播、视频上传

 尚硅谷谷粒学院项目第九天内容之课程发布功能、课程列表功能、视频点播、视频上传

课程发布功能

后端

创建一个实体类,用于封装课程信息从而返回给前端

CoursePublishVo

@Data
public class CoursePublishVo {
    private static final long serialVersionUID = 1L;

    private String id;//课程id

    private String title; //课程名称

    private String cover; //封面

    private Integer lessonNum;//课时数

    private String subjectLevelOne;//一级分类

    private String subjectLevelTwo;//二级分类

    private String teacherName;//讲师名称

    private String price;//价格 ,只用于显示
}

controller

    //发布课程:其实就是设置一下课程状态
    @PostMapping("publishCourse/{courseId}")
    public R publishCourse(@PathVariable String courseId){
        EduCourse course = new EduCourse();
        course.setId(courseId);
        course.setStatus("Normal");
        courseService.updateById(course);
        return R.ok();
    }

service

CoursePublishVo getCoursePublish(String courseId);

serviceImpl 

    //获取CoursePublish(最终发布的课程信息)
    @Override
    public CoursePublishVo getCoursePublish(String courseId) {
        return baseMapper.getCoursePublish(courseId);
    }

前端

course.js

    //发布课程
    publishCourse(courseId){
        return request({
            url:`/eduservice/course/publishCourse/${courseId}`,
            method:'post'
        })
    },

publish.vue

<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="coursePublish.cover">
      <div class="main">
        <h2>{{ coursePublish.title }}</h2>
        <p class="gray"><span>共{{ coursePublish.lessonNum }}课时</span></p>
        <p><span>所属分类:{{ coursePublish.subjectLevelOne }} — {{ coursePublish.subjectLevelTwo }}</span></p>
        <p>课程讲师:{{ coursePublish.teacherName }}</p>
        <h3 class="red">¥{{ coursePublish.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:'',
      coursePublish:{}
    }
  },

  created() {
    //获取路由课程id值
    if(this.$route.params && this.$route.params.id) {
      this.courseId = this.$route.params.id
      //调用接口方法根据课程id查询
      this.getCoursePublish()
    }
  },

  methods: {

    //根据课程id查询
    getCoursePublish() {
      course.getCoursePublihInfo(this.courseId)
        .then(response => {
          this.coursePublish = response.data.coursePublish
        })
    },
    previous() {
      console.log('previous')
      this.$router.push({ path: '/course/chapter/'+this.courseId })
    },

    publish() {
      course.publishCourse(this.courseId)
        .then(response => {
          //提示
          this.$message({
              type: 'success',
              message: '课程发布成功!'
          });
          //跳转课程列表页面
          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>

课程列表功能

后端

controller

    //分页多条件查询课程列表
    @PostMapping("pageConditionQueryCourse/{current}/{size}")
    public R pageConditionQueryCourse(@PathVariable long current, @PathVariable long size, @RequestBody(required=false) CourseCondition courseCondition){
        Page<EduCourse> page = new Page<>(current,size);

        QueryWrapper<EduCourse> queryWrapper = new QueryWrapper<>();
        String title = courseCondition.getTitle();
        Integer status = courseCondition.getStatus();
        String subjectParentId = courseCondition.getSubjectParentId();
        String teacherId = courseCondition.getTeacherId();
        String minPrice = courseCondition.getMinPrice();
        String maxPrice = courseCondition.getMaxPrice();
        if(!StringUtils.isEmpty(title)){
            queryWrapper.eq("title",title);
        }
        if(!StringUtils.isEmpty(status)){

            queryWrapper.eq("status",status==0 ? "Draft":"Normal");//0是未发布,1是已发布
        }

        if(!StringUtils.isEmpty(subjectParentId)){
            queryWrapper.eq("subject_parent_id",subjectParentId);
        }
        if(!StringUtils.isEmpty(teacherId)){
            queryWrapper.eq("teacher_id",teacherId);
        }
        if(!StringUtils.isEmpty(minPrice)){
            queryWrapper.gt("price",minPrice);
        }
        if(!StringUtils.isEmpty(maxPrice)){
            queryWrapper.eq("price",maxPrice);
        }
        queryWrapper.orderByDesc("gmt_create");
        courseService.page(page,queryWrapper);

        long total = page.getTotal();//总记录数
        List<EduCourse> list = page.getRecords();//所有数据
        return R.ok().data("list",list).data("total",total);
    }

    //删除课程
    @DeleteMapping("deleteCourse/{courseId}")
    public R deleteCourse(@PathVariable String courseId){
        courseService.deleteCourse(courseId);
        return R.ok();
    }

serviceImpl

    //分页多条件查询课程列表
    @PostMapping("pageConditionQueryCourse/{current}/{size}")
    public R pageConditionQueryCourse(@PathVariable long current, @PathVariable long size, @RequestBody(required=false) CourseCondition courseCondition){
        Page<EduCourse> page = new Page<>(current,size);

        QueryWrapper<EduCourse> queryWrapper = new QueryWrapper<>();
        String title = courseCondition.getTitle();
        Integer status = courseCondition.getStatus();
        String subjectParentId = courseCondition.getSubjectParentId();
        String teacherId = courseCondition.getTeacherId();
        String minPrice = courseCondition.getMinPrice();
        String maxPrice = courseCondition.getMaxPrice();
        if(!StringUtils.isEmpty(title)){
            queryWrapper.eq("title",title);
        }
        if(!StringUtils.isEmpty(status)){

            queryWrapper.eq("status",status==0 ? "Draft":"Normal");//0是未发布,1是已发布
        }
        if(!StringUtils.isEmpty(minPrice)){
            queryWrapper.gt("price",minPrice);
        }
        if(!StringUtils.isEmpty(maxPrice)){
            queryWrapper.eq("price",maxPrice);
        }
        queryWrapper.orderByDesc("gmt_create");
        courseService.page(page,queryWrapper);

        long total = page.getTotal();//总记录数
        List<EduCourse> list = page.getRecords();//所有数据
        return R.ok().data("list",list).data("total",total);
    }

    //删除课程
    @DeleteMapping("deleteCourse/{courseId}")
    public R deleteCourse(@PathVariable String courseId){
        courseService.deleteCourse(courseId);
        return R.ok();
    }

前端

course.js

    //分页多条件查询课程
    getCourseListPage(current,limit,courseQuery){
        return request({
            url:`/eduservice/course/pageConditionQueryCourse/${current}/${limit}`,
            method:'post',
            data:courseQuery //后端requestBody要json字符串,data能把techerQuery对象转换成json字符串
        })
    },
    //删除课程
    deleteCourseById(courseId){
        return request({
            url:`/eduservice/course/deleteCourse/${courseId}`,
            method:'post',
            data:courseQuery //后端requestBody要json字符串,data能把techerQuery对象转换成json字符串
        })
    }

list.vue

<template>
  <div class="login-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=1 label="已发布"/>
          <el-option :value=0 label="未发布"/>
        </el-select>
      </el-form-item>

      <el-form-item>
        <el-input v-model="courseQuery.minPrice" placeholder="最低价格"/>
      </el-form-item>
      <el-form-item>
        <el-input v-model="courseQuery.maxPrice" placeholder="最高价格"/>
      </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
      :data="list"
      style="width: 100%"
      border
      fit
      highlight-current-row
      element-loading-text="数据加载中"
      v-loading="listLoading">
      <el-table-column prop="date" label="序号" width="100" align="center">
        <template slot-scope="scope">
          {{ (currentPage - 1) * limit + scope.$index + 1 }}
        </template>
      </el-table-column>
      <el-table-column prop="title" label="名称" width="230"> </el-table-column>
      <el-table-column label="状态" width="90">
        <template slot-scope="scope">
          {{ scope.row.status === 'Normal' ? "已发布" : "未发布" }}
        </template>
      </el-table-column>
      <el-table-column prop="lessonNum" label="课程数" width="120" />
      <el-table-column prop="price" label="价格" />
      <el-table-column prop="buyCount" label="购买量" width="120" />
      <el-table-column prop="viewCount" label="浏览量" width="120" />
      <el-table-column label="操作" width="530" align="center">
        <template slot-scope="scope">
            <el-button type="primary" size="mini" icon="el-icon-edit" @click="editCourseInfo(scope.row.id)">编辑课程基本信息</el-button>
            <el-button type="primary" size="mini" icon="el-icon-edit" @click="editChapterVideo(scope.row.id)">编辑课程大纲</el-button>
          <el-button
            type="danger"
            size="mini"
            icon="el-icon-delete"
            @click="deleteTeacherById(scope.row.id)">
            删除课程
          </el-button>
        </template>
      </el-table-column>
    </el-table>
    <!-- 分页 -->
    <el-pagination
      :current-page="currentPage"
      :page-size="limit"
      :total="total"
      style="padding: 30px 0; text-align: center;"
      layout="total, prev, pager, next, jumper"
      @current-change="getList"
    />
  </div>
</template>

<script>

import course from '@/api/edu/course.js'

export default{
    data() {
        return {
            list:null,
            currentPage:1,
            limit:11,
            total:0,
            courseQuery:{},
            listLoading:''
        }
    },
    created(){
        this.getList()
    },
    methods:{
        //编辑课程基本信息
        editCourseInfo(courseId){
             //路由跳转
            this.$router.push({path:'/course/info/'+courseId})
        },
        //编辑课程大纲(编辑章节小节)
        editChapterVideo(courseId){
            this.$router.push({path:'/course/chapter/'+courseId})
        },
        //课程列表
        getList(currentPage=1){
            this.currentPage=currentPage
            course.getCourseListPage(this.currentPage,this.limit,this.courseQuery)
                .then(response=>{ //getList()方法调用成功时执行
                    this.list=response.data.list
                })
                .catch(error=>{//getList()方法调用失败时执行
                    console.log(error);
                })
        },
        //清空查询条件
        resetData(){
            this.courseQuery={}
            this.getList()
        },
        //删除讲师
        deleteCourseById(id){
            this.$confirm('此操作将永久删除讲师记录, 是否继续?', '提示', {
                confirmButtonText: '确定',
                cancelButtonText: '取消',
                type: 'warning'
            }).then(() => {  //点击confirmButton后执行
                    course.deleteCourseById(id)
                        .then(response=>{  //提示信息
                            this.$message({
                                type:'success',
                                message:'删除成功!'
                            })
                        })
                    this.getList(this.currentPage)
               })
        }
    }
}

</script>

视频点播

阿里云官网——产品——企业服务与媒体服务——视频点播——管理控制台

我们上传的视频依然会存入oss,视频点播帮我们管理这些视频

api:阿里云提供的很底层的接口

sdk:sdk是api的封装,我们用sdk调用api

httpclient技术可以不使用浏览器发送请求,并得到响应

测试

创建子模块service_vod

引入依赖

    <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>joda-time</groupId>
            <artifactId>joda-time</artifactId>
        </dependency>
    </dependencies>

InitObject.java

public class InitObject {

    //填入AccessKey信息
    public static DefaultAcsClient initVodClient(String accessKeyId,String accessKeySecret) throws ClientException {
        String regionId = "cn-shanghai";  // 点播服务接入地域
        DefaultProfile profile = DefaultProfile.getProfile(regionId, accessKeyId, accessKeySecret);
        return new DefaultAcsClient(profile);
    }
}

VodTest.java

public class VodTest {
    public static void main(String[] args) throws ClientException {
        uploadVideo("E:\\photo\\temp\\11.mp4");
    }

    //上传视频
    public static void uploadVideo(String fileName) throws ClientException {
        String accessKeyId="LTAI5t6R5cWusp7MeWpKgT1W";
        String accessKeySecret="Dd6Ku0j5dQja9jh4d7cl8ULepWcNE7";
        String title="111";
        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);
        System.out.print("RequestId=" + response.getRequestId() + "\n");  //请求视频点播服务的请求ID
        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");
        }
    }
    
    //根据视频id获取视频url
    public static void getVideoUrl() throws ClientException {
        DefaultAcsClient client = InitObject.initVodClient("LTAI5t6R5cWusp7MeWpKgT1W","Dd6Ku0j5dQja9jh4d7cl8ULepWcNE7");
        GetPlayInfoRequest request = new GetPlayInfoRequest();
        request.setVideoId("4b0d8f1102f34e84a9c7b4e239deff2c");

        GetPlayInfoResponse response = client.getAcsResponse(request);
        List<GetPlayInfoResponse.PlayInfo> list = response.getPlayInfoList();
        for(GetPlayInfoResponse.PlayInfo playInfo:list){
            System.out.println("playurl:"+playInfo.getPlayURL());
        }
        System.out.println("VideoBase.Title:"+response.getVideoBase().getTitle());
    }
    //根据视频id获取视频播放凭证
    public static void getVideoAuth() throws ClientException {
        DefaultAcsClient client = InitObject.initVodClient("LTAI5t6R5cWusp7MeWpKgT1W", "Dd6Ku0j5dQja9jh4d7cl8ULepWcNE7");
        GetVideoPlayAuthRequest request = new GetVideoPlayAuthRequest();
        request.setVideoId("4b0d8f1102f34e84a9c7b4e239deff2c");
        GetVideoPlayAuthResponse response = client.getAcsResponse(request);
        System.out.println(response.getPlayAuth());
    }
}

上传视频功能

后端

在service_vod模块

创建application.properties

# 服务端口
server.port=8003
# 服务名
spring.application.name=service-vod

# 环境设置:dev、test、prod
spring.profiles.active=dev

#阿里云 vod
#不同的服务器,地址不同
aliyun.vod.file.keyid=LTAI5t6R5cWusp7MeWpKgT1W
aliyun.vod.file.keysecret=Dd6Ku0j5dQja9jh4d7cl8ULepWcNE7

# 最大上传单个文件大小:默认1M
spring.servlet.multipart.max-file-size=1024MB
# 最大置总上传的数据大小 :默认10M
spring.servlet.multipart.max-request-size=1024MB

创建启动类com.jiabei.vod.VodApplication

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@ComponentScan(basePackages = {"com.jiabei"})
public class VodApplication {
    public static void main(String[] args) {
        SpringApplication.run(VodApplication.class, args);
    }
}

 VodController

@RestController
@RequestMapping("vodservice")
@CrossOrigin
public class VodController {

    @Autowired
    private VodService vodService;
    //上传视频
    @PostMapping("uploadVideo")
    public R uploadVideo(MultipartFile file){
        String videoId=vodService.uploadVideo(file);
        return R.ok().data("videoId",videoId);
    }
}

VodService

@Service
public interface VodService {
    String uploadVideo(MultipartFile file);
}

VodServiceImpl

@Component
public class VodServiceImpl implements VodService {
    @Override
    public String uploadVideo(MultipartFile file) {
       try {
           String fileName=file.getOriginalFilename(); //原名
           InputStream is = file.getInputStream();
           String title =null;
           if(fileName!=null){
                title=fileName.substring(0, fileName.lastIndexOf("."));//上传后的名称
           }

           UploadStreamRequest request = new UploadStreamRequest(ConstantUtils.ACCESS_KEY_ID,ConstantUtils.ACCESS_KEY_SECRET, title, fileName, is);
           UploadVideoImpl uploader = new UploadVideoImpl();
           UploadStreamResponse response = uploader.uploadStream(request);
           String videoId=null;
           if (response.isSuccess()) {
               videoId=response.getVideoId();
           } else { //如果设置回调URL无效,不影响视频上传,可以返回VideoId同时会返回错误码。其他情况上传失败时,VideoId为空,此时需要根据返回错误码分析具体错误原因
               videoId=response.getVideoId();
           }
           return videoId;
       }catch (Exception e){
           e.printStackTrace();
           return null;
       }
    }
}

ConstanUtils

@Component
public class ConstantUtils implements InitializingBean {

    @Value("${aliyun.vod.file.keyid}")
    private  String keyid;

    @Value("${aliyun.vod.file.keysecret}")
    private  String keysecret;

    public static String ACCESS_KEY_ID;
    public static String ACCESS_KEY_SECRET;

    @Override
    public void afterPropertiesSet() throws Exception {
        ACCESS_KEY_ID=keyid;
        ACCESS_KEY_SECRET=keysecret;
    }
}

swagger测试,能将视频上传到oss中

前端

chapter.vue中添加上传视频的组件

        <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/uploadAlyiVideo'"
                :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>

声明变量

            fileList: [],//上传文件列表
            BASE_API: process.env.BASE_API+'vodservice/uploadVideo' // 接口API地址

声明方法

        //上传视频成功调用的方法
        handleVodUploadSuccess(response, file, fileList) {
            this.video.videoSourceId = response.data.videoId
            this.video.videoOriginalName = file.name
        },
        handleUploadExceed() {
            this.$message.warning('想要重新上传视频,请先删除已上传的视频')
        },

在nginx中配置端口转发规则

nginx.conf 

 此时测试,发现上传的视频一闪而过,传不上去,原因:视频超过了nginx允许的大小

在nginx.conf里添加

client_max_body_size  1024m;

 重启nginx

 此时,测试,videoSourceId就能添加到数据库里了,视频能成功上传到阿里云了

后端

VodController

    //根据视频id删除视频
    @DeleteMapping("deleteVideo/{id}")
    public R deleteVideo(@PathVariable String id){
        vodService.deleteVideo(id);
        return R.ok();
    }

VodServiceImpl

    //删除视频
    @Override
    public void deleteVideo(String id) {
        try{
            DefaultAcsClient client = InitClient.initVodClient(ConstantUtils.ACCESS_KEY_ID, ConstantUtils.ACCESS_KEY_SECRET);
            DeleteVideoRequest request = new DeleteVideoRequest();
            request.setVideoIds(id);
            client.getAcsResponse(request);
        }catch (Exception e){
            throw new GuliException(20001,"删除视频失败");
        }
    }

前端

video.js

     //删除视频
     deleteAlyVideo(videoId){
        return request({
            url:`vodservice/deleteVideo/${videoId}`,
            method:'delete'
        })
    }

chapter.vue

        //点击×调用这个方法:弹出确认框
        beforeVodRemove(file,fileList) {
            return this.$confirm(`确定移除 ${ file.name }?`);
        },
        //点击确认调用的方法:删除阿里云中的视频
        handleVodRemove(){
            //调用接口的删除视频的方法
            video.deleteAlyVideo(this.video.videoSourceId)
                .then(response => {
                    //提示信息
                    this.$message({
                        type: 'success',
                        message: '删除视频成功!'
                    });
                    //把文件列表清空
                    this.fileList = []
                    //把video视频id和视频名称值清空
                    //上传视频id赋值
                    this.video.videoSourceId = ''
                    //上传视频名称赋值
                    this.video.videoOriginalName = ''
                })
        },

测试

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值