谷粒学院
讲师详细页
一、后端部分
1、TeacherFrontController
@RestController
@CrossOrigin
@RequestMapping("/eduservice/teacherFront")
public class TeacherFrontController {
@Autowired
private EduTeacherService eduTeacherService;
@Autowired
private EduCourseService eduCourseService;
//根据id查询讲师信息(讲师本身信息+讲师所讲课程信息)
@GetMapping("/getTeacherInfo/{id}")
public R getTeacherInfo(@PathVariable String id){
//查询讲师信息
EduTeacher teacher = eduTeacherService.getById(id);
//查询讲师所讲课程信息
QueryWrapper<EduCourse> wrapper = new QueryWrapper<>();
wrapper.eq("teacher_id",id);
List<EduCourse> courseList = eduCourseService.list(wrapper);
return R.ok().data("teacher",teacher).data("courseList",courseList);
}
}
这里不够严谨,其实想要再创service,把业务逻辑写在service中
2、swagger测试
二、前端部分
1、teacher api
guli-front\api\teacher.js
import request from '@/utils/request'
export default{
......
//根据ID查询讲师本身信息+课程信息
getTeacherInfoByid(id){
return request({
url: `/eduservice/teacherFront/getTeacherInfo/${
id}`,
method: `get`
})
},
}
2、讲师详情页中调用api
<script>
import teacherApi from '@/api/teacher'
export default {
created() {
this.teacherId = this.$route.params.id
this.getByid()
},
data() {
return {
teacher:{
name:'',
intro:'',
career:'',
level:'',
},
courseList:[],
teacherId:'',
}
},
methods: {
getByid(){
teacherApi.getTeacherInfoByid(this.teacherId).then(resp=>{
this.teacher = resp.data.data.teacher
this.courseList = resp.data.data.courseList
})
}
},
};
</script>
三、页面渲染
1、讲师基本信息模板
<template>
<div id="aCoursesList" class="bg-fa of">
<!-- 讲师介绍 开始 -->
<section class="container">
<header class="comm-title">
<h2 class="fl tac">
<span class="c-333">讲师介绍</span>
</h2>
</header>
<div class="t-infor-wrap">
<!-- 讲师基本信息 -->
<section class="fl t-infor-box c-desc-content">
<div class="mt20 ml20">
<section class="t-infor-pic">
<img :src="teacher.avatar" />
</section>
<h3 class="hLh30">
<span class="fsize24 c-333">{
{teacher.name}}
{
{teacher.level ===1?'高级讲师':'首席讲师'}}
</span>
</h3>
<section class="mt10">
<span class="t-tag-bg">{
{teacher.career}}</span>
</section>
<section class="t-infor-txt">
<p class="mt20">
{
{teacher.intro}}
</p>
</section>
<div class="clear"></div>
</div>
</section>
<div class="clear"></div>
</div>
<section class="mt30">
<div>
<header class="comm-title all-teacher-title c-course-content">
<h2 class="fl tac">
<span class="c-333">主讲课程</span>
</h2>
<section class="c-tab-title">
<a href="javascript: void(0)"> </a>
</section>
</header>
<!-- /无数据提示 开始-->
<section class="no-data-wrap" v-if="courseList.length==0">
<em class="icon30 no-data-ico"> </em>
<span class="c-666 fsize14 ml10 vam"
>没有相关数据,小编正在努力整理 中...</span
>
</section>
<!-- /无数据提示 结束-->
<article class="comm-course-list">
<ul class="of">
<li v-for="course in courseList" :key="course.id">
<div class="cc-l-wrap">
<section class="course-img">
<img
:src="course.cover"
class="img-responsive"
/>
<div class="cc-mask">
<a
:href="'/course/'+course.id"
title="开始学习"
target="_blank"
class="comm- btn c-btn-1"
>开始学习</a
>
</div>
</section>
<h3 class="hLh30 txtOf mt10">
<a
:href="'/course/'+course.id"
:title="course.title"
target="_blank"
class="course-title fsize18 c-333"
>{
{course.title}}</a
>
</h3>
</div>
</li>
</ul>
<div class="clear"></div>
</article>
</div>
</section>
</section>
<!-- /讲师介绍 结束 -->
</div>
</template>
四、测试
- 有课程
- 如果没课程
课程列表页面
一、后端接口
- com.achang.eduservice.entity.frontVo.CourseFrontVo
@Data
public class CourseFrontVo implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "课程名称")
private String title;
@ApiModelProperty(value = "讲师id")
private String teacherId;
@ApiModelProperty(value = "一级类别id")
private String subjectParentId;
@ApiModelProperty(value = "二级类别id")
private String subjectId;
@ApiModelProperty(value = "销量排序")
private String buyCountSort;
@ApiModelProperty(value = "最新时间排序")
private String gmtCreateSort;
@ApiModelProperty(value = "价格排序")
private String priceSort;
}
- service
接口
public interface EduCourseService extends IService<EduCourse> {
......
//前台多条件分页查询
Map<String, Object> getCourseFrontInfo(Page<EduCourse> pageCourse, CourseFrontVo courseFrontVo);
}
Impl
@Service
public class EduCourseServiceImpl extends ServiceImpl<EduCourseMapper, EduCourse> implements EduCourseService {
//前台多条件分页查询
@Override
public Map<String, Object> getCourseFrontInfo(Page<EduCourse> pageCourse, CourseFrontVo courseFrontVo) {
String title = null;
String subjectId = null;
String subjectParentId = null;
String gmtCreateSort = null;
String buyCountSort = null;
String priceSort = null;
String teacherId = null;
if (!StringUtils.isEmpty(courseFrontVo)){
title = courseFrontVo.getTitle();
subjectId = courseFrontVo.getSubjectId();
subjectParentId = courseFrontVo.getSubjectParentId();
gmtCreateSort = courseFrontVo.getGmtCreateSort();
buyCountSort = courseFrontVo.getBuyCountSort();
priceSort = courseFrontVo.getPriceSort();
teacherId = courseFrontVo.getTeacherId();
}
QueryWrapper<EduCourse> wrapper = new QueryWrapper<>();
//判断条件值是否为空,不为空拼接条件
if (!StringUtils.isEmpty(subjectParentId)){
//一级分类
wrapper.eq("subject_parent_id",subjectParentId);
}
if (!StringUtils.isEmpty(subjectId)){
//二级分类
wrapper.eq("subject_id",subjectId);
}
if (!StringUtils.isEmpty(buyCountSort)){
//关注度
wrapper.orderByDesc("buy_count");
}
if (!StringUtils.isEmpty(priceSort)){
//价格
wrapper.orderByDesc("price");
}
if (!StringUtils.isEmpty(gmtCreateSort)){
//最新,创建时间
wrapper.orderByDesc("gmt_create");
}
baseMapper.selectPage(pageCourse, wrapper);
long total = pageCourse.getTotal();//总记录数
List<EduCourse> courseList = pageCourse.getRecords();//数据集合
long size = pageCourse.getSize();//每页记录数
long current = pageCourse.getCurrent();//当前页
long pages = pageCourse.getPages();//总页数
boolean hasPrevious = pageCourse.hasPrevious();//是否有上一页
boolean hasNext = pageCourse.hasNext();//是否有下一页
HashMap<String, Object> map = new HashMap<>();
map.put("total",total);
map.put("list",courseList);
map.put("size",size);
map.put("current",current);
map.put("pages",pages);
map.put("hasPrevious",hasPrevious);
map.put("hasNext",hasNext);
return map;
}
}
二、前端部分
1、定义api
api/course.js
import request from '@/utils/request'
export default{
//前台多条件分页查询
getConditionPage(page,limit,searchObj){
return request({
url: `/eduservice/courseFront/getConditionPage/${
page}/${
limit}`,
method: 'post',
data: searchObj
})
},
//查询所有分类(一级分类、二级分类)的方法
getAllSubject(){
return request({
url: `/eduservice/edu-subject/getAllSubject`,
method: 'get'
})
}
}
2、页面调用接口
pages/course/index.vue
<script>
import courseApi from "@/api/course";
export default {
data() {
return {
page: 1, //当前页
data: {
}, //课程列表
subjectNestedList: [], // 一级分类列表
subSubjectList: [], // 二级分类列表
searchObj: {
}, // 查询表单对象
oneIndex: -1,
twoIndex: -1,
buyCountSort: "",
gmtCreateSort: "",
priceSort: "",
};
},
methods: {
//根据价格进行排序
searchPrice(){
//设置对应变量值,为了样式生效
this.buyCountSort=''
this.gmtCreateSort=''
this.priceSort='1'
//把值赋值到searchObj中
this.searchObj.buyCountSort = this.buyCountSort
this.searchObj.priceSort = this.priceSort
this.searchObj.gmtCreateSort = this.gmtCreateSort
//调用方法查询
this.gotoPage(1)
},
//根据最新进行排序
searchGmtCreate(){
//设置对应变量值,为了样式生效
this.buyCountSort=''
this.gmtCreateSort='1'
this.priceSort=''
//把值赋值到searchObj中
this.searchObj.buyCountSort = this.buyCountSort
this.searchObj.priceSort = this.priceSort
this.searchObj.gmtCreateSort = this.gmtCreateSort
//调用方法查询
this.gotoPage(1)
},
//根据销量排序
searchBuyCount(){
//设置对应变量值,为了样式生效
this.buyCountSort='1'
this.gmtCreateSort=''
this.priceSort=''
//把值赋值到searchObj中
this.searchObj.buyCountSort = this.buyCountSort
this.searchObj.priceSort = this.priceSort
this.searchObj.gmtCreateSort = this.gmtCreateSort
//调用方法查询
this.gotoPage(1)
},
//课程第一次查询
initCourseFirst() {
courseApi.getConditionPage(1, 8, this.searchObj).then((resp) => {
this.data = resp.data.data;
});
},
//查询所有一级分类
initSubject() {
courseApi.getAllSubject().then((resp) => {
this.subjectNestedList = resp.data.data.list;
// this.subSubjectList = resp.data.data.list.children
});
},
//分页切换方法
gotoPage(page) {
courseApi.getConditionPage(page, 8, this.searchObj).then((resp) => {
this.data = resp.data.data;
});
},
//点击某个一级分类,查询对应的二级分类
seacherOne(subjectParentId, index) {
//把传递来的index赋值给ondex,为了active样式生效
// this.oneIndex = index
// this.twoIndex = -1
// this.searchObj.subjectId=''
// this.subSubjectList=[]
//把一级分类点击的id值,赋值给searchObj
this.searchObj.subjectParentId = subjectParentId;
//点击某个一级分类进行条件查询
this.gotoPage(1);
//拿着点击的一级分类id 和 所有一级分类id进行比较
//如果id相同,从那个一级分类中获取他的二级分类
for (let i = 0; i < this.subjectNestedList.length; i++) {
//获取每个一级分类
var oneSubject = this.subjectNestedList[i];
//比较id是否相同
if (oneSubject.id == subjectParentId) {
this.subSubjectList = oneSubject.children;
}
}
},
//点击某个二级分类实现查询
searchTwo(subjectId, index) {
//把index赋值,为了样式生效
this.twoIndex = index;
//把二级分类点击id值,赋给searchObj
this.searchObj.subjectId = subjectId;
//点击某个二级分类进行调节查询
this.gotoPage(1);
},
},
created() {
//课程第一次查询
this.initCourseFirst();
//一级分类显示
this.initSubject();
},
};
</script>
<style scoped>
.active {
background: #bdbdbd;
}
.hide {
display: none;
}
.show {
display: block;
}
</style>
3、页面
<template>
<div id="aCoursesList" class="bg-fa of">
<!-- /课程列表 开始 -->
<section class="container">
<header class="comm-title">
<h2 class="fl tac">
<span class="c-333">全部课程</span>
</h2>
</header>
<section class="c-sort-box">
<section class="c-s-dl">
<dl>
<dt>
<span class="c-999 fsize14">课程类别</span>
</dt>
<dd class="c-s-dl-li">
<ul class="clearfix">
<li>
<a title="全部" href="#">全部</a>
</li>
<li
v-for="subjectNested in subjectNestedList"
:key="subjectNested.id"
>
<a
:title="subjectNested.title"
@click="seacherOne(subjectNested.id, index)"
href="#"
>{
{ subjectNested.title }}</a
>
</li>
</ul>
</dd>
</dl>
<dl>
<dt>
<span class="c-999 fsize14"></span>
</dt>
<dd class="c-s-dl-li">
<ul class="clearfix">
<li v-for="subject in subSubjectList" :key="subject.id">
<a
:title="subject.title"
@click="searchTwo(subject.id, index)"
href="#"
>{
{ subject.title }}</a
>
</li>
</ul>
</dd>
</dl>
<div class="clear"></div>
</section>
<div class="js-wrap">
<section class="fr">
<span class="c-ccc">
<i class="c-master f-fM">1</i>/
<i class="c-666 f-fM">1</i>
</span>
</section>
<section class="fl">
<ol class="js-tap clearfix">
<li :class="{ 'current bg-orange': buyCountSort != '' }">
<a
title="销量"
href="javascript:void(0);"
@click="searchBuyCount()"
>销量
<span :class="{ hide: buyCountSort == '' }">↓</span>
</a>
</li>
<li :class="{ 'current bg-orange': gmtCreateSort != '' }">
<a
title="最新"
href="javascript:void(0);"
@click="searchGmtCreate()"
>最新
<span :class="{ hide: gmtCreateSort == '' }">↓</span>
</a>
</li>
<li :class="{ 'current bg-orange': priceSort != '' }">
<a
title="价格"
href="javascript:void(0);"
@click="searchPrice()"
>价格
<span :class="{ hide: priceSort == '' }">↓</span>
</a>
</li>
</ol>
</section>
</div>
<div class="mt40">
<!-- /无数据提示 开始-->
<section class="no-data-wrap" v-if="data.total == 0">
<em class="icon30 no-data-ico"> </em>
<span class="c-666 fsize14 ml10 vam"
>没有相关数据,小编正在努力整理 中...</span
>
</section>
<!-- /无数据提示 结束-->
<article class="comm-course-list" v-if="data.total > 0">
<ul class="of" id="bna">
<li v-for="item in data.list" :key="item.id">
<div class="cc-l-wrap">
<section class="course-img">
<img
:src="item.cover"
class="img-responsive"
:alt="item.title"
/>
<div class="cc-mask">
<a
href="/course/1"
title="开始学习"
class="comm-btn c- btn-1"
>开始学习</a
>
</div>
</section>
<h3 class="hLh30 txtOf mt10">
<a
href="/course/1"
:title="item.title"
class="course-title fsize18 c-333"
>{
{ item.title }}</a
>
</h3>
<section class="mt10 hLh20 of">
<span
class="fr jgTag bg-green"
v-if="Number(item.price) === 0"
>
<i class="c-fff fsize12 f-fA">免费</i>
</span>
<span class="fl jgAttr c-ccc f-fA">
<i class="c-999 f-fA">9634人学习</i>
|
<i class="c-999 f-fA">9634评论</i>
</span>
</section>
</div>
</li>
</ul>
<div class="clear"></div>
</article>
</div>
<!-- 公共分页 开始 -->
<div>
<div class="paging">
<!-- undisable这个class是否存在,取决于数据属性hasPrevious -->
<a
:class="{ undisable: !data.hasPrevious }"
href="#"
title="首页"
@click.prevent="gotoPage(1)"
>首</a
>
<a
:class="{ undisable: !data.hasPrevious }"
href="#"
title="前一页"
@click.prevent="gotoPage(data.current - 1)"
><</a
>
<a
v-for="page in data.pages"
:key="page"
:class="{
current: data.current == page,
undisable: data.current == page,
}"
:title="'第' + page + '页'"
href="#"
@click.prevent="gotoPage(page)"
>{
{ page }}</a
>
<a
:class="{ undisable: !data.hasNext }"
href="#"
title="后一页"
@click.prevent="gotoPage(data.current + 1)"
>></a
>
<a
:class="{ undisable: !data.hasNext }"
href="#"
title="末页"
@click.prevent="gotoPage(data.pages)"
>末</a
>
<div class="clear" />
</div>
</div>
<!-- 公共分页 结束 -->
</section>
</section>
<!-- /课程列表 结束 -->
</div>
</template>
4、测试
课程详情页
一、后端部分
- controller
com.achang.eduservice.controller.front.CourseFrontController
@RestController
@CrossOrigin
@RequestMapping("/eduservice/courseFront")
public class CourseFrontController {
@Autowired
private EduCourseService eduCourseService;
@Autowired
private EduChapterService eduChapterService;
.......
//课程详情的方法
@GetMapping("/getFrontCourseInfo/{courseId}")
public R getFrontCourseInfo(@PathVariable String courseId){
//根据课程id,编写sql语句查询课程信息
CourseWebVo courseWebVo = eduCourseService.getBaseCourseInfo(courseId);
//根据课程id,查询章节和小节信息
List<ChapterVo> chapterVideoList = eduChapterService.getChapterVideoByCourseId(courseId);
return R.ok().data("courseWebVo",courseWebVo).data("chapterVideoList",chapterVideoList);
}
}
- service
接口
//前台根据课程id,查询课程基础信息
CourseWebVo getBaseCourseInfo(String courseId);
Impl
//前台根据课程id,查询课程基础信息
@Override
public CourseWebVo getBaseCourseInfo(String courseId) {
return baseMapper.getBaseCourseInfo(courseId);
}
- dao
EduCourseMapper接口
@Component
public interface EduCourseMapper extends BaseMapper<EduCourse> {
......
//前台根据课程id,查询课程基础信息
CourseWebVo getBaseCourseInfo(String courseId);
}
Impl实现类xml文件
<!--前台根据课程id,查询课程基础信息-->
<select id="getBaseCourseInfo" resultType="com.achang.eduservice.entity.frontVo.CourseWebVo">
SELECT
ec.id,
ec.title,
ec.cover,
ec.lesson_num AS lessonNum,
ec.price,
ec.cover,
ec.buy_count as buyCount,
ec.view_count as viewCount,
esd.description,
s1.title AS subjectLevelOne,
s1.id as subjectLevelOneId,
s2.id as subjectLevelTwoId,
s2.title AS subjectLevelTwo,
t.name AS teacherName,
t.id as teacherId,
t.avatar,
t.intro
FROM
edu_course ec
LEFT JOIN edu_teacher t ON ec.teacher_id = t.id
LEFT JOIN edu_subject s1 ON ec.subject_parent_id = s1.id
LEFT JOIN edu_subject s2 ON ec.id = s2.id
left join edu_course_description esd on ec.id = esd.id
WHERE
ec.id = #{id}
</select>
- 记得要指定xml配置文件配置,在properties配置文件中
#配置mapper xml文件的路径
mybatis-plus.mapper-locations=classpath:com/achang/eduservice/mapper/xml/*.xml
- 测试
二、前端部分
- api
guli-front\api\course.js
import request from '@/utils/request'
export default{
........
//根据课程id,查询课程详细信息
getFrontCourseInfo(courseId){
return request({
url: `/eduservice/courseFront/getFrontCourseInfo/${
courseId}`,
method: 'get'
})
}
}
- 页面js脚本
pages/course/_id.vue
<script>
import courseApi from "@/api/course";
export default {
methods: {
//获取课程详细信息
getCourseInfo() {
courseApi.getFrontCourseInfo(this.course.courseId).then((resp) => {
this.chapterList = resp.data.data.chapterVideoList;
this.course = resp.data.data.courseWebVo;
});
},
},
data() {
return {
chapterList: [],
course: {
courseId: "",
},
};
},
created() {
this.course.courseId = this.$route.params.id;
获取课程详细信息
this.getCourseInfo();
},
};
</script>
- 页面html
<template>
<div id="aCoursesList" class="bg-fa of">
<!-- /课程详情 开始 -->
<section class="container">
<section class="path-wrap txtOf hLh30">
<a href="/" title class="c-999 fsize14">首页</a>
\
<a href="/course" title class="c-999 fsize14">{
{
course.subjectLevelOne
}}</a>
\
<span class="c-333 fsize14">{
{ course.subjectLevelTwo }}</span>
</section>
<div>
<article class="c-v-pic-wrap" style="height: 357px">
<section class="p-h-video-box" id="videoPlay">
<img
:src="course.cover"
:alt="course.title"
class="dis c-v-pic"
height="355px"
width="630px"
/>
</section>
</article>
<aside class="c-attr-wrap">
<section class="ml20 mr15">
<h2 class="hLh30 txtOf mt15">
<span class="c-fff fsize24">{
{ course.title }}</span>
</h2>
<section class="c-attr-jg">
<span class="c-fff">价格:</span>
<b class="c-yellow" style="font-size: 24px"
>¥{
{ course.price }}</b
>
</section>
<section class="c-attr-mt c-attr-undis">
<span class="c-fff fsize14"
>主讲: {
{ course.teacherName }} </span
>
</section>
<section class="c-attr-mt of">
<span class="ml10 vam">
<em class="icon18 scIcon"></em>
<a class="c-fff vam" title="收藏" href="#">收藏</a>
</span>
</section>
<section class="c-attr-mt">
<a href="#" title="立即观看" class="comm-btn c-btn-3"
>立 即 观 看</a
>
</section>
</section>
</aside>
<aside class="thr-attr-box">
<ol class="thr-attr-ol clearfix">
<li>
<p> </p>
<aside>
<span class="c-fff f-fM">销量</span>
<br />
<h6 class="c-fff f-fM mt10">{
{ course.buyCount }}</h6>
</aside>
</li>
<li>
<p> </p>
<aside>
<span class="c-fff f-fM">课时数</span>
<br />
<h6 class="c-fff f-fM mt10">{
{ course.lessonNum }}</h6>
</aside>
</li>
<li>
<p> </p>
<aside>
<span class="c-fff f-fM">浏览数</span>
<br />
<h6 class="c-fff f-fM mt10">{
{ course.viewCount }}</h6>
</aside>
</li>
</ol>
</aside>
<div class="clear"></div>
</div>
<!-- /课程封面介绍 -->
<div class="mt20 c-infor-box">
<article class="fl col-7">
<section class="mr30">
<div class="i-box">
<div>
<section
id="c-i-tabTitle"
class="c-infor-tabTitle c-tab- title"
>
<a name="c-i" class="current" title="课程详情">课 程 详 情</a>
</section>
</div>
<article class="ml10 mr10 pt20">
<div>
<h6 class="c-i-content c-infor-title">
<span>课程介绍</span>
</h6>
<div class="course-txt-body-wrap">
<section class="course-txt-body">
<p v-html="course.description">
{
{ course.description }}
</p>
</section>
</div>
</div>
<!-- /课程介绍 -->
<div class="mt50">
<h6 class="c-g-content c-infor-title">
<span>课程大纲</span>
</h6>
<section class="mt20">
<div class="lh-menu-wrap">
<menu id="lh-menu" class="lh-menu mt10 mr10">
<ul>
<!-- 文件目录 -->
<li class="lh-menu-stair" v-for="chapter in chapterList" :key="chapter.id">
<a
href="javascript: void(0)"
:title="chapter.title"
class="current-1"
>
<em class="lh-menu-i-1 icon18 mr10"></em>{
{chapter.title}}
</a>
<ol class="lh-menu-ol" style="display: block">
<li class="lh-menu-second ml30" v-for="video in chapter.children" :key="video.id">
<a href="#" title>
<span class="fr" v-if="video.free=== true">
<i class="free-icon vam mr10">免费试听</i>
</span>
<em class="lh-menu-i-2 icon16 mr5"> </em
>{
{video.title}}
</a>
</li>
</ol>
</li>
</ul>
</menu>
</div>
</section>
</div>
<!-- /课程大纲 -->
</article>
</div>
</section>
</article>
<aside class="fl col-3">
<div class="i-box">
<div>
<section class="c-infor-tabTitle c-tab-title">
<a title href="javascript:void(0)">主讲讲师</a>
</section>
<section class="stud-act-list">
<ul style="height: auto">
<li>
<div class="u-face">
<a href="#">
<img
:src="course.avatar"
width="50"
height="50"
alt
/>
</a>
</div>
<section class="hLh30 txtOf">
<a class="c-333 fsize16 fl" href="#">{
{course.teacherName}}</a>
</section>
<section class="hLh20 txtOf">
<span class="c-999">{
{course.intro}}</span>
</section>
</li>
</ul>
</section>
</div>
</div>
</aside>
<div class="clear"></div>
</div>
</section>
<!-- /课程详情 结束 -->
</div>
</template>
- 测试效果
阿里云视频播放测试
一、获取播放地址播放
获取播放地址
参考文档: https://help.aliyun.com/document_detail/61064.html
前面的 03-使用服务端SDK 介绍了如何获取非加密视频的播放地址。直接使用03节的例子获取加密视频播放地址会返回如下错误信息
因此在testGetPlayInfo 测试方法中 添加 ResultType 参数,并设置为true
privateParams.put("ResultType", "Multiple");
此种方式获取的视频文件不能直接播放,必须使用阿里云播放器播放
二、视频播放器
参考文档: https://help.aliyun.com/document_detail/61109.html
1 、视频播放器介绍
阿里云播放器 SDK ( ApsaraVideo Player SDK )是阿里视频服务的重要一环,除了支持点播和直播的基
础播放功能外,深度融合视频云业务,如支持视频的加密播放、安全下载、清晰度切换、直播答题等业
务场景,为用户提供简单、快速、安全、稳定的视频播放服务。
2 、集成视频播放器
参考文档: https://help.aliyun.com/document_detail/51991.html
参考 【播放器简单使用说明】一节
引入脚本文件和 css 文件
<link rel="stylesheet"
href="https://g.alicdn.com/de/prismplayer/2.8.1/skins/default/aliplayer-min.css" />
<script charset="utf-8" type="text/javascript"
src="https://g.alicdn.com/de/prismplayer/2.8.1/aliplayer-min.js"></script>
初始化视频播放器
<body>
<div class="prism-player" id="J_prismPlayer"></div>
<script>
var player = new Aliplayer({
id: 'J_prismPlayer',
width: '100%',
autoplay: false,
cover: 'http://liveroom-img.oss-cn-qingdao.aliyuncs.com/logo.png',
//播放配置
},function(player){
console.log('播放器创建好了。')
});
</script>
</body>
3、播放地址播放
在 Aliplayer 的配置参数中添加如下属性
//播放方式一:支持播放地址播放,此播放优先级最高,此种方式不能播放加密视频
source : '你的视频播放地址',
启动浏览器运行,测试视频的播放
- 效果
4 、播放凭证播放(推荐)
阿里云播放器支持通过播放凭证自动换取播放地址进行播放,接入方式更为简单,且安全性更高。播放
凭证默认时效为100秒(最大为3000秒),只能用于获取指定视频的播放地址,不能混用或重复使用。
如果凭证过期则无法获取播放地址,需要重新获取凭证。
encryptType:'1',//如果播放加密视频,则需设置encryptType=1,非加密视频无需设置此项
vid : '视频id',
playauth : '视频授权码',
注意:播放凭证有过期时间,默认值:100秒 。取值范围:100~3000。
设置播放凭证的有效期
在获取播放凭证的测试用例中添加如下代码
request.setAuthInfoTimeout(200L);
在线配置参考:https://player.alicdn.com/aliplayer/setting/setting.html
整合阿里云视频播放器
一、后端接口
- VodController
//根据视频id获取视频凭证
@GetMapping("/getPlayAuth/{id}")
public R getPlayAuth(@PathVariable String id){
try {
String playAuth = vodService.getPlayAuth(id);
return R.ok().data("PlayAuth",playAuth);
} catch (Exception e) {
e.printStackTrace();
throw new AchangException(20001,"获取视频凭证失败");
}
}
- service
接口
//根据视频id获取视频凭证
String getPlayAuth(String id);
impl
//根据视频id获取视频凭证
@Override
public String getPlayAuth(String id) {
String accesskeyId = ConstantVodUtils.ACCESSKEY_ID;
String accesskeySecret = ConstantVodUtils.ACCESSKEY_SECRET;
try {
//创建初始化对象
DefaultAcsClient cl = InitObject.initVodClient(accesskeyId,accesskeySecret);
//创建获取视频地址request对象和response对象
GetVideoPlayAuthRequest request = new GetVideoPlayAuthRequest();
//向request对象设置视频id值
request.setVideoId(id);
GetVideoPlayAuthResponse response = cl.getAcsResponse(request);
//获取视频播放凭证
return response.getPlayAuth();
} catch (ClientException e) {
e.printStackTrace();
throw new AchangException(20001,"获取视频凭证失败");
}
}
- ConstantVodUtils
@Component
public class ConstantVodUtils implements InitializingBean {
@Value("${aliyun.vod.file.keyid}")
private String accessKeyId;
@Value("${aliyun.vod.file.keysecret}")
private String accessKeySecret;
public static String ACCESSKEY_ID;
public static String ACCESSKEY_SECRET;
@Override
public void afterPropertiesSet() throws Exception {
ACCESSKEY_ID = this.accessKeyId;
ACCESSKEY_SECRET = this.accessKeySecret;
}
}
- InitObject
//初始化类
public class InitObject {
public static DefaultAcsClient initVodClient(String accessKeyId, String accessKeySecret) throws ClientException {
String regionId = "cn-shanghai"; // 点播服务接入区域
DefaultProfile profile = DefaultProfile.getProfile(regionId, accessKeyId, accessKeySecret);
DefaultAcsClient client = new DefaultAcsClient(profile);
return client;
}
}
- 测试
二、前端部分
- api
guli-front\api\vod.js
import request from '@/utils/request'
export default{
//根据视频id,获取视频凭证
getPlayAuthById(id){
return request({
url:`/eduvod/video/getPlayAuth/${
id}`,
method: 'get'
})
}
}
- 页面js脚本
阿昌用之前的js写法不能用,在最后生命中mounted()中视频凭证的数据丢失了,不知道为什么
然后换成老师的这种异步调用一次的就成功了
<script>
import vodApi from "@/api/vod";
export default {
layout: "video", //应用video布局
// created() {
// this.vid = this.$route.params.vid;
// this.getPlayAuth();
// },
// methods: {
// getPlayAuth() {
// vodApi.getPlayAuthById(this.videoSourceId).then((resp) => {
// this.playAuth = resp.data.data.playAuth;
// });
// },
// },
// data() {
// return {
// playAuth: "",
// vid: "",
// };
// },
asyncData({
params, error }) {
return vodApi.getPlayAuthById(params.vid).then((response) => {
return {
playAuth: response.data.data.playAuth,
vid: params.vid,
};
});
},
mounted() {
//页面渲染之后 created
new Aliplayer(
{
id: "J_prismPlayer",
vid: this.vid, // 视频id
playauth: this.playAuth, // 播放凭证
encryptType: "1", // 如果播放加密视频,则需设置encryptType=1,非加密视频无需设置此项
width: "100%",
height: "500px",
// 以下可选设置
cover: "http://guli.shop/photo/banner/1525939573202.jpg", // 封面
qualitySort: "asc", // 清晰度排序
mediaType: "video", // 返回音频还是视频
autoplay: false, // 自动播放
isLive: false, // 直播
rePlay: false, // 循环播放
preload: true,
controlBarVisibility: "hover", // 控制条的显示方式:鼠标悬停
useH5Prism: true, // 播放器类型:html5
},
function (player) {
console.log("播放器创建成功");
}
);
},
};
</script>
- 页面标签
<template>
<div>
<!-- 阿里云视频播放器样式 -->
<link
rel="stylesheet"
href="https://g.alicdn.com/de/prismplayer/2.8.1/skins/default/aliplayer-min.css"
/>
<!-- 阿里云视频播放器脚本 -->
<script
charset="utf-8"
type="text/javascript"
src="https://g.alicdn.com/de/prismplayer/2.8.1/aliplayer-min.js"
/>
<!-- 定义播放器dom -->
<div id="J_prismPlayer" class="prism-player" />
</div>
</template>
- 测试
- 其他常见的可选配置
// 以下可选设置
cover: 'http://guli.shop/photo/banner/1525939573202.jpg', // 封面
qualitySort: 'asc', // 清晰度排序
mediaType: 'video', // 返回音频还是视频
autoplay: false, // 自动播放
isLive: false, // 直播
rePlay: false, // 循环播放
preload: true,
controlBarVisibility: 'hover', // 控制条的显示方式:鼠标悬停
useH5Prism: true, // 播放器类型:html5
- 可选择的加入播放组件,如广告等。。。
功能展示:https://player.alicdn.com/aliplayer/presentation/index.html