赞
踩
(1)CourseController添加方法
@GetMapping("findAll")
public Result findAll() {
List<Course> list = courseService.findlist();
return Result.ok(list);
}
(2)CourseService实现方法
@Override
public List<Course> findlist() {
List<Course> list = baseMapper.selectList(null);
list.stream().forEach(item -> {
this.getTeacherOrSubjectName(item);
});
return list;
}
{ path: '/live', component: Layout, redirect: '/live/liveCourse/list', name: 'Live', meta: { title: '直播管理', icon: 'el-icon-bangzhu' }, alwaysShow: true, children: [ { path: 'liveCourse/list', name: 'liveCourseList', component: () => import('@/views/live/liveCourse/list'), meta: { title: '直播列表' } }, { path: 'liveCourse/config/:id', name: 'liveCourseConfig', component: () => import('@/views/live/liveCourse/config'), meta: { title: '直播配置' }, hidden: true }, { path: 'liveVisitor/list/:id', name: 'liveVisitor', component: () => import('@/views/live/liveVisitor/list'), meta: { title: '观看记录' }, hidden: true } ] },
import request from '@/utils/request' const api_name = '/admin/live/liveCourse' export default { getPageList(page, limit) { return request({ url: `${api_name}/${page}/${limit}`, method: 'get' }) }, findLatelyList() { return request({ url: `${api_name}/findLatelyList`, method: 'get' }) }, getById(id) { return request({ url: `${api_name}/getInfo/${id}`, method: 'get' }) }, getLiveCourseAccount(id) { return request({ url: `${api_name}/getLiveCourseAccount/${id}`, method: 'get' }) }, save(liveCourse) { return request({ url: `${api_name}/save`, method: 'post', data: liveCourse }) }, updateById(liveCourse) { return request({ url: `${api_name}/update`, method: 'put', data: liveCourse }) }, removeById(id) { return request({ url: `${api_name}/remove/${id}`, method: 'delete' }) }, removeRows(idList) { return request({ url: `${api_name}/batchRemove`, method: 'delete', data: idList }) }, getCourseConfig(id) { return request({ url: `${api_name}/getCourseConfig/${id}`, method: 'get' }) }, updateConfig(liveCourseConfigVo) { return request({ url: `${api_name}/updateConfig`, method: 'put', data: liveCourseConfigVo }) }, }
findAll() {
return request({
url: `${api_name}/findAll`,
method: 'get'
})
},
<template> <div class="app-container"> <!-- 工具条 --> <el-card class="operate-container" shadow="never"> <i class="el-icon-tickets" style="margin-top: 5px"></i> <span style="margin-top: 5px">数据列表</span> <el-button class="btn-add" size="mini" @click="add">添 加</el-button> </el-card> <!-- banner列表 --> <el-table v-loading="listLoading" :data="list" stripe border style="width: 100%;margin-top: 10px;"> <el-table-column label="序号" width="50" align="center"> <template slot-scope="scope"> {{ (page - 1) * limit + scope.$index + 1 }} </template> </el-table-column> <el-table-column label="封面" width="200" align="center"> <template slot-scope="scope"> <img :src="scope.row.cover" width="100%"> </template> </el-table-column> <el-table-column prop="courseName" label="直播名称" /> <el-table-column prop="startTime" label="直播时间"> <template slot-scope="scope"> {{ scope.row.param.startTimeString }}至{{ scope.row.param.endTimeString }} </template> </el-table-column> <el-table-column prop="endTime" label="直播结束时间" /> <el-table-column prop="param.teacherName" label="直播老师" /> <el-table-column label="头衔" width="90"> <template slot-scope="scope"> <el-tag v-if="scope.row.param.teacherLevel === 1" type="success" size="mini">高级讲师</el-tag> <el-tag v-if="scope.row.param.teacherLevel === 0" size="mini">首席讲师</el-tag> </template> </el-table-column> <el-table-column prop="createTime" label="创建时间" /> <el-table-column label="操作" width="200" align="center"> <template slot-scope="scope"> <el-button type="text" size="mini" @click="edit(scope.row.id)">修改</el-button> <el-button type="text" size="mini" @click="removeDataById(scope.row.id)">删除</el-button> <el-button type="text" size="mini" @click="showAccount(scope.row)">查看账号</el-button> <router-link :to="'/live/liveCourse/config/'+scope.row.id"> <el-button type="text" size="mini">配置</el-button> </router-link> <router-link :to="'/live/liveVisitor/list/'+scope.row.id"> <el-button type="text" size="mini">观看记录</el-button> </router-link> </template> </el-table-column> </el-table> <!-- 分页组件 --> <el-pagination :current-page="page" :total="total" :page-size="limit" :page-sizes="[5, 10, 20, 30, 40, 50, 100]" style="padding: 30px 0; text-align: center;" layout="sizes, prev, pager, next, jumper, ->, total, slot" @current-change="fetchData" @size-change="changeSize" /> <el-dialog title="添加/修改" :visible.sync="dialogVisible" width="60%" > <el-form ref="flashPromotionForm" label-width="150px" size="small" style="padding-right: 40px;"> <!-- 课程讲师 --> <el-form-item label="直播讲师"> <el-select v-model="liveCourse.teacherId" placeholder="请选择"> <el-option v-for="teacher in teacherList" :key="teacher.id" :label="teacher.name" :value="teacher.id"/> </el-select> </el-form-item> <el-form-item label="直播讲师登录密码" v-if="liveCourse.id === ''"> <el-input v-model="liveCourse.password"/> </el-form-item> <el-form-item label="直播名称"> <el-input v-model="liveCourse.courseName"/> </el-form-item> <el-form-item label="直播开始时间"> <el-date-picker v-model="liveCourse.startTime" type="datetime" placeholder="选择开始日期" value-format="yyyy-MM-dd HH:mm:ss" /> </el-form-item> <el-form-item label="直播结束时间"> <el-date-picker v-model="liveCourse.endTime" type="datetime" placeholder="选择结束日期" value-format="yyyy-MM-dd HH:mm:ss" /> </el-form-item> <el-form-item label="直播封面"> <el-upload :show-file-list="false" :on-success="handleCoverSuccess" :before-upload="beforeCoverUpload" :on-error="handleCoverError" :action="BASE_API+'/admin/vod/file/upload?module=cover'" class="cover-uploader"> <img v-if="liveCourse.cover" :src="liveCourse.cover" width="60%"> <i v-else class="el-icon-plus avatar-uploader-icon"/> </el-upload> </el-form-item> <el-form-item label="直播详情"> <el-input v-model="liveCourse.description" type="textarea" rows="5"/> </el-form-item> </el-form> <span slot="footer" class="dialog-footer"> <el-button @click="dialogVisible = false" size="small">取 消</el-button> <el-button type="primary" @click="saveOrUpdate()" size="small">确 定</el-button> </span> </el-dialog> <el-dialog title="查看账号" :visible.sync="accountDialogVisible" width="60%" > <el-form ref="accountForm" label-width="150px" size="small" style="padding-right: 40px;"> <div style="margin-left: 40px;"> <h4>主播帮助信息</h4> <el-row style="height:35px;"> <el-co > <span class="spd-info">主播登录链接:</span> <span class="spd-info">https://live.zhibodun.com/live/courseLogin.php?course_id={{ liveCourseAccount.courseId }}&role=admin</span> </el-co> </el-row> <el-row style="height:35px;"> <el-col > <span class="spd-info">主播登录密码:{{ liveCourseAccount.zhuboKey }}</span> </el-col> </el-row> </div> <div style="margin-left: 40px;"> <h4>主播客户端账号信息</h4> <el-row style="height:35px;"> <el-col > <span class="spd-info">主播登录账户:{{ liveCourseAccount.zhuboAccount }}</span> </el-col> </el-row> <el-row style="height:35px;"> <el-col > <span class="spd-info">主播登录密码:{{ liveCourseAccount.zhuboPassword }}</span> </el-col> </el-row> </div> <div style="margin-left: 40px;"> <h4>助教信息</h4> <el-row style="height:35px;"> <el-co > <span class="spd-info">助教登录连接:</span> <span class="spd-info">https://live.zhibodun.com/live/courseLogin.php?course_id={{ liveCourseAccount.courseId }}&role=admin</span> </el-co> </el-row> <el-row style="height:35px;"> <el-col> <span class="spd-info">主播登录密码:{{ liveCourseAccount.adminKey }}</span> </el-col> </el-row> </div> <div style="margin-left: 40px;"> <h4>学生观看信息</h4> <el-row style="height:35px;"> <el-co > <span class="spd-info">观看连接:</span> <span class="spd-info">http://glkt-api.atguigu.cn/#/liveInfo/{{ liveCourseAccount.courseId }}</span> </el-co> </el-row> <el-row style="height:35px;"> <el-col> <span class="spd-info">观看二维码:<img src="@/styles/qrcode.png" width="80px"/></span> </el-col> </el-row> </div> </el-form> <span slot="footer" class="dialog-footer"> <el-button @click="accountDialogVisible = false" size="small">关 闭</el-button> </span> </el-dialog> </div> </template> <script> import api from '@/api/live/liveCourse' import teacherApi from '@/api/vod/teacher' const defaultForm = { id: '', courseName: '', startTime: '', endTime: '', teacherId: '', password: '', description: '', cover: 'https://cdn.uviewui.com/uview/swiper/1.jpg' } export default { data() { return { BASE_API: 'http://localhost:8333', listLoading: true, // 数据是否正在加载 list: null, // banner列表 total: 0, // 数据库中的总记录数 page: 1, // 默认页码 limit: 10, // 每页记录数 searchObj: {}, // 查询表单对象 teacherList: [], // 讲师列表 dialogVisible: false, liveCourse: defaultForm, saveBtnDisabled: false, accountDialogVisible: false, liveCourseAccount: { courseId: '' } } }, // 生命周期函数:内存准备完毕,页面尚未渲染 created() { console.log('list created......') this.fetchData() // 获取讲师列表 this.initTeacherList() }, // 生命周期函数:内存准备完毕,页面渲染成功 mounted() { console.log('list mounted......') }, methods: { // 当页码发生改变的时候 changeSize(size) { console.log(size) this.limit = size this.fetchData(1) }, // 加载banner列表数据 fetchData(page = 1) { console.log('翻页。。。' + page) // 异步获取远程数据(ajax) this.page = page api.getPageList(this.page, this.limit).then( response => { this.list = response.data.records this.total = response.data.total // 数据加载并绑定成功 this.listLoading = false } ) }, // 获取讲师列表 initTeacherList() { teacherApi.list().then(response => { this.teacherList = response.data }) }, // 重置查询表单 resetData() { console.log('重置查询表单') this.searchObj = {} this.fetchData() }, // 根据id删除数据 removeDataById(id) { // debugger this.$confirm('此操作将永久删除该记录, 是否继续?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { // promise // 点击确定,远程调用ajax return api.removeById(id) }).then((response) => { this.fetchData(this.page) if (response.code) { this.$message({ type: 'success', message: '删除成功!' }) } }).catch(() => { this.$message({ type: 'info', message: '已取消删除' }) }) }, // ------------- add(){ this.dialogVisible = true this.liveCourse = Object.assign({}, defaultForm) }, edit(id) { this.dialogVisible = true this.fetchDataById(id) }, fetchDataById(id) { api.getById(id).then(response => { this.liveCourse = response.data }) }, saveOrUpdate() { this.saveBtnDisabled = true // 防止表单重复提交 if (!this.liveCourse.id) { this.saveData() } else { this.updateData() } }, // 新增 saveData() { api.save(this.liveCourse).then(response => { if (response.code) { this.$message({ type: 'success', message: response.message }) this.dialogVisible = false this.fetchData(this.page) } }) }, // 根据id更新记录 updateData() { api.updateById(this.liveCourse).then(response => { if (response.code) { this.$message({ type: 'success', message: response.message }) this.dialogVisible = false this.fetchData(this.page) } }) }, // 根据id查询记录 fetchDataById(id) { api.getById(id).then(response => { this.liveCourse = response.data }) }, showAccount(row) { this.accountDialogVisible = true api.getLiveCourseAccount(row.id).then(response => { this.liveCourseAccount = response.data this.liveCourseAccount.courseId = row.courseId }) }, // ------------upload------------ // 上传成功回调 handleCoverSuccess(res, file) { this.liveCourse.cover = res.data }, // 上传校验 beforeCoverUpload(file) { const isJPG = file.type === 'image/jpeg' const isLt2M = file.size / 1024 / 1024 < 2 if (!isJPG) { this.$message.error('上传头像图片只能是 JPG 格式!') } if (!isLt2M) { this.$message.error('上传头像图片大小不能超过 2MB!') } return isJPG && isLt2M }, // 错误处理 handleCoverError() { console.log('error') this.$message.error('上传失败2') }, } } </script> <style scoped> .cover-uploader .avatar-uploader-icon { border: 1px dashed #d9d9d9; border-radius: 6px; cursor: pointer; position: relative; overflow: hidden; font-size: 28px; color: #8c939d; width: 450px; height: 200px; line-height: 200px; text-align: center; } .cover-uploader .avatar-uploader-icon:hover { border-color: #409EFF; } .cover-uploader img { width: 450px; height: 200px; display: block; } </style>
<template> <div class="app-container"> <el-form label-width="120px" size="small"> <div style="background-color:#E0E0E0;width: 100%;padding: 1px 10px;margin: 10px 0;"><h3> 功能设置 </h3></div> <el-form-item label="界面模式"> <el-radio-group v-model="liveCourseConfigVo.pageViewMode"> <el-radio :label="1">全屏模式</el-radio> <el-radio :label="0">二分屏</el-radio> <el-radio :label="2">课件模式</el-radio> </el-radio-group> </el-form-item> <el-form-item label="观看人数开关"> <el-radio-group v-model="liveCourseConfigVo.numberEnable"> <el-radio :label="1">是</el-radio> <el-radio :label="0">否</el-radio> </el-radio-group> </el-form-item> <el-form-item label="商城开关:"> <el-radio-group v-model="liveCourseConfigVo.storeEnable"> <el-radio :label="1">是</el-radio> <el-radio :label="0">否</el-radio> </el-radio-group> </el-form-item> <div style="background-color:#E0E0E0;width: 100%;padding: 1px 10px;margin: 10px 0;"><h3> 商品列表 <el-button type="" size="mini" @click="addCourse()">添加</el-button> </h3></div> <el-table v-loading="listLoading" :data="liveCourseConfigVo.liveCourseGoodsList" stripe border style="width: 100%;margin-top: 10px;"> <el-table-column label="序号" width="70" align="center"> <template slot-scope="scope"> {{ scope.$index + 1 }} </template> </el-table-column> <el-table-column label="商品图片" width="120" align="center"> <template slot-scope="scope"> <img :src="scope.row.img" width="80px"> </template> </el-table-column> <el-table-column prop="name" label="名称" width="100"/> <el-table-column prop="price" label="价格" width="100"/> <el-table-column prop="originalPrice" label="原价"/> <el-table-column label="操作" width="100" align="center"> <template slot-scope="scope"> <el-button type="text" size="mini" @click="removeCourseById(scope.$index)">删除</el-button> </template> </el-table-column> </el-table> <el-dialog title="添加课程" :visible.sync="dialogVisible" width="50%"> <el-form :inline="true" label-width="150px" size="small" style="padding-right: 40px;"> <el-table v-loading="listLoading" :data="courseList" stripe border style="width: 100%;margin-top: 10px;" @selection-change="handleSelectionChange"> <el-table-column type="selection" width="55" /> <el-table-column label="序号" width="70" align="center"> <template slot-scope="scope"> {{ scope.$index + 1 }} </template> </el-table-column> <el-table-column label="分类"> <template slot-scope="scope"> {{ scope.row.param.subjectParentTitle }} > {{ scope.row.param.subjectTitle }} </template> </el-table-column> <el-table-column prop="title" label="课程名称" width="150"/> <el-table-column prop="lessonNum" label="课时" width="100"/> <el-table-column prop="param.teacherName" label="讲师"/> </el-table> <el-form-item style="margin-top: 10px;"> <el-button type="" @click="dialogVisible = false">取消</el-button> <el-button type="" @click="selectCourse()">保存</el-button> </el-form-item> </el-form> </el-dialog> <br/><br/> <el-form-item> <el-button type="primary" @click="saveOrUpdate">保存</el-button> <el-button @click="back">返回</el-button> </el-form-item> </el-form> </div> </template> <script> import api from '@/api/live/liveCourse' import courseApi from '@/api/vod/course' const defaultForm = { id: '', liveCourseId: '', pageViewMode: 1, numberEnable: 1, storeEnable: 1, storeType: 1, liveCourseGoodsList: [] } export default { data() { return { listLoading: true, // 数据是否正在加载 liveCourseConfigVo: defaultForm, saveBtnDisabled: false, dialogVisible: false, courseList: [], multipleSelection: [] // 批量选择中选择的记录列表 } }, // 监听器 watch: { $route(to, from) { console.log('路由变化......') console.log(to) console.log(from) this.init() } }, // 生命周期方法(在路由切换,组件不变的情况下不会被调用) created() { console.log('form created ......') this.init() }, methods: { // 表单初始化 init() { this.liveCourseConfigVo.liveCourseId = this.$route.params.id this.fetchDataById(this.liveCourseConfigVo.liveCourseId) this.fetchCourseList() }, back() { this.$router.push({ path: '/live/liveCourse/list' }) }, // 根据id查询记录 fetchDataById(id) { api.getCourseConfig(id).then(response => { if(null !== response.data.id) { this.liveCourseConfigVo = response.data } this.listLoading = false }) }, fetchCourseList() { courseApi.findAll().then(response => { //debugger this.courseList = response.data }) }, handleSelectionChange(selection) { console.log(selection) this.multipleSelection = selection }, addCourse() { this.dialogVisible = true }, selectCourse() { if (this.multipleSelection.length === 0) { this.$message({ type: 'warning', message: '请选择对应课程!' }) return } var list = [] this.multipleSelection.forEach(item => { var obj = { liveCourseId: this.liveCourseConfigVo.liveCourseId, goodsId: item.id, name: item.title, img: item.cover, price: item.price, originalPrice: item.price, tab: '1', url: 'http://glkt-api.atguigu.cn/#/courseInfo/'+item.id, putaway: '1', pay: '1', qrcode: '' } list.push(obj) }) this.liveCourseConfigVo.liveCourseGoodsList = list this.dialogVisible = false }, removeCourseById(index) { this.liveCourseConfigVo.liveCourseGoodsList.splice(index, 1) }, saveOrUpdate() { api.updateConfig(this.liveCourseConfigVo).then(response => { this.$message({ type: 'success', message: response.message }) this.$router.push({ path: '/live/liveCourse/list' }) }) } } } </script> <style scoped> .littleMarginTop { margin-top: 10px; } .paramInput { width: 250px; } .paramInputLabel { display: inline-block; width: 100px; text-align: right; padding-right: 10px } .cardBg { background: #F8F9FC; } </style>
接口文档:https://open.talk-fun.com/docs/js/index.html
用户要观看直播,必须获取对应的用户access_token,通过access_token 获取观看的直播课程;
接口参数:直播id,用户id
(1)创建LiveCourseApiController
@RestController
@RequestMapping("api/live/liveCourse")
public class LiveCourseApiController {
@Resource
private LiveCourseService liveCourseService;
@ApiOperation(value = "获取用户access_token")
@GetMapping("getPlayAuth/{id}")
public Result<JSONObject> getPlayAuth(@PathVariable Long id) {
JSONObject object = liveCourseService.getPlayAuth(id, AuthContextHolder.getUserId());
return Result.ok(object);
}
}
(2)LiveCourseService添加方法
JSONObject getPlayAuth(Long id, Long userId);
引入用户远程调用模块
<dependency>
<groupId>com.atguigu</groupId>
<artifactId>service_user_client</artifactId>
<version>0.0.1-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
(3)LiveCourseServiceImpl实现方法
@Autowired private UserInfoFeignClient userInfoFeignClient; //获取用户access_token(根据MTCloud写,他是封装好的工具类) @Override public JSONObject getAccessToken(Long id, Long userId) { //根据课程id获取直播课程信息 LiveCourse liveCourse = baseMapper.selectById(id); //根据用户id获取用户信息 UserInfo userInfo = userInfoFeignClient.getById(userId); //封装需要的参数 /** * 进入一个课程 * * course_id 课程ID * uid 用户唯一ID * nickname 用户昵称 * role 用户角色,枚举见:ROLE 定义 * expire 有效期,默认:3600(单位:秒) * options 可选项,包括:gender:枚举见上面GENDER定义,avatar:头像地址 */ HashMap<Object,Object> options = new HashMap<Object, Object>(); String res = null; try { res = mtCloudClient.courseAccess(liveCourse.getCourseId().toString(), userId.toString(), userInfo.getNickName(), MTCloud.ROLE_USER, 80*80*80, options); } catch (Exception e) { throw new RuntimeException(e); } CommonResult<JSONObject> commonResult = JSON.parseObject(res, CommonResult.class); if(Integer.parseInt(commonResult.getCode()) == MTCloud.CODE_SUCCESS) { JSONObject object = commonResult.getData(); System.out.println("access::"+object.getString("access_token")); return object; } else { throw new GgktException(20001,"获取失败"); } }
下载地址:https://open.talk-fun.com/docs/js/download.html
下载模板,修改token获取方式
var url = window.location.search
var token = url.split("=")[1]
(1)创建直播播放页面 live.html
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" /> <title>TalkFun Live QuickStart v2.2</title> <style type="text/css"> * { margin: 0; padding: 0; list-style-type: none; font-family: "Microsoft YaHei", "STHeiti"; } .flash-wran { display: none; position: absolute; top: 0; width: 100%; padding: 5px 0; text-align: center; background: #fff6a2; border: 1px solid #ffd913; } .flash-wran a { color: red; } .wrapper { /*display: flex;*/ padding: 10px; } #cameraPlayer, #pptPlayer { height: auto; flex: 1; text-align: center; font-size: 12px; overflow: hidden; } #pptPlayer { height: 300px; width: 100%; } #modPptPlayer, #modCameraPlayer { margin-top: 10px; border: 1px solid #c7c7c7; } .chat-wrap { padding: 5px; margin: 10px; border: 2px solid #cccccc; background: #f1f1f1 } .mod-chat-list { margin: 20px 0; border: 1px solid #CCCCCC; min-height: 100px; font-size: 12px; background: #dedede; padding: 5px; max-height: 200px; overflow-y: scroll; } .mod-chat-list li { padding: 2px 0; margin-bottom: 5px; border-bottom: 1px dotted #CCCCCC; } input { display: inline-block; width: 200px; padding: 5px; } button { display: inline-block; padding: 5px; margin-left: 5px; } #toast { padding: 20px; position: fixed; z-index: 100; display: none; background: rgba(212, 28, 28, 0.8); left: 50%; top: 30%; border-radius: 50em; font-size: 14px; color: #FFFFFF; box-shadow: 0 0 6px 0px #bb2d2d; } #talkfun-video-wrap, #talkfun-camera-wrap { position: relative; background: #000; } </style> <!-- #SDK版本 --> <!-- #获取最新版本 ==> http://open.talk-fun.com/docs/js/changelog/live.html --> <script type="text/javascript" src="https://static-1.talk-fun.com/open/TalkFun_SDK_Pack/v6.0/TalkFunWebSDK-6.2-2.min.js"></script> </head> <body> <!-- #toast --> <div id="toast"></div> <!-- #wrap --> <div class="wrapper"> <!-- #画板播放器 --> <div id="pptPlayer"> <p id="loaddoc">播放器 Loading...</p> </div> <!-- #摄像头模式 --> <div id="cameraPlayer"> <p id="loadcam">摄像头 Loading...</p> </div> <!-- #桌面分享|视频插播模式 --> <div id="videoPlayer"> <p id="loadplayer">视频播放器 Loading...</p> </div> </div> <!-- #chat --> <div class="chat-wrap"> <h4>聊天模块</h4> <ul id="chat-list" class="mod-chat-list"></ul> <label> <input id="chatVal" type="text" /><button id="chatSubmit">发送聊天</button> </label> </div> <script> // [第一步] 如何获取 access_token => http://open.talk-fun.com/docs/getstartV2/access_token.html // [第二步] 根据Api文档方法 监听 / 调用方法 JS Api => http://open.talk-fun.com/docs/js/sdk.js.getstart.html var url = window.location.search var token = url.split("=")[1] // 更多配置项 => https://open.talk-fun.com/docs/js/sdk.js.getstart.html?h=%E9%85%8D%E7%BD%AE%E9%A1%B9 var HT = new MT.SDK.main(token, { config: { techOrder: 'FLV' // Safari 浏览器建议设置为 HLS } }, function (data) { console.warn('sdk加载完成', data) }) // 连接状态 HT.on('connect', function () { console.log('TalkFun通信 => 连接成功...') }) // 课件播放器 HT.whiteboardPlayer('pptPlayer', 'docPlayer', function (player) { console.log('课件播放器 => 初始化成功') document.querySelector('#loadplayer').innerHTML = '画板模块加载完成' }) // 视频插播 | 桌面分享 HT.videoPlayer('videoPlayer', 'modVideoplayer', function (player) { console.log('视频播放器 => 初始化成功') document.querySelector('#loadplayer').innerHTML = '视频插播加载完成' }) // 摄像头播放器 HT.camera('cameraPlayer', 'modCameraPlayer', function () { console.log('摄像头播放器 => 初始化成功') document.querySelector('#loadcam').innerHTML = '摄像头模块加载完成' }) // 接收聊天 var receivedChat = function (chat) { var tpl = chat.nickname + ': ' + chat.msg var chatItem = document.createElement('li') chatItem.innerHTML = tpl chatItem.className = 'chat-' + chat.xid document.querySelector('#chat-list').appendChild(chatItem) } // 接收聊天信息 HT.on('chat:send', function (chat) { receivedChat(chat) }) // 发送聊天信息 document.querySelector('#chatSubmit').addEventListener('click', function () { var chatIpt = document.querySelector('#chatVal') var chatValue = chatIpt.value HT.emit('chat:send', { msg: chatValue }, function (res) { // 发送成功 if (Number(res.code) === 0) { receivedChat(res.data) chatIpt.value = '' } // 发送失败 else { console.warn(res.msg) } }) }, false) // Flash插件异常 HT.on('flash:load:error', function (obj) { if (!obj.flash) { document.querySelector('#flashTip').style.display = 'block' } }) // 课程错误信息 HT.on('live:course:access:error', function (res) { console.error('错误信息 ==>', res) }) // 课程错误信息 HT.on('system:room:error', function (res) { var toast = document.querySelector('#toast') if (typeof res === 'string') { toast.innerHTML = res.msg } else if (res.msg) { toast.innerHTML = res.msg } toast.style.display = 'block' var _left = toast.clientWidth / 2 toast.style.marginLeft = -_left + 'px' }) </script> </body> </html>
观众在直播详情页面点击观看,获取通过接口获取access_token,然后带上access_token参数跳转到直播观看页面即可,关键代码:
liveInfo.vue
play() {
api.getPlayAuth(this.liveCourseId).then(response => {
console.log(response.data);
window.location = './live.html?token='+response.data.access_token;
this.finished = true;
});
},
http://localhost:8080/live.html为直播观看访问方式
(1)LiveCourseApiController类
@ApiOperation("根据ID查询课程")
@GetMapping("getInfo/{courseId}")
public Result getInfo(
@ApiParam(value = "课程ID", required = true)
@PathVariable Long courseId){
Map<String, Object> map = liveCourseService.getInfoById(courseId);
return Result.ok(map);
}
(2)LiveCourseServiceImpl实现
//根据ID查询课程 @Override public Map<String, Object> getInfoById(Long id) { LiveCourse liveCourse = this.getById(id); liveCourse.getParam().put("startTimeString", new DateTime(liveCourse.getStartTime()).toString("yyyy年MM月dd HH:mm")); liveCourse.getParam().put("endTimeString", new DateTime(liveCourse.getEndTime()).toString("yyyy年MM月dd HH:mm")); Teacher teacher = courseFeignClient.getTeacherLive(liveCourse.getTeacherId()); LiveCourseDescription liveCourseDescription = liveCourseDescriptionService.getLiveCourseById(id); Map<String, Object> map = new HashMap<>(); map.put("liveCourse", liveCourse); map.put("liveStatus", this.getLiveStatus(liveCourse)); map.put("teacher", teacher); if(null != liveCourseDescription) { map.put("description", liveCourseDescription.getDescription()); } else { map.put("description", ""); } return map; }
1、点播课程详情页面分享
参考文档:https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html
先登录微信公众平台进入“设置与开发”,“公众号设置”的“功能设置”里填写“JS接口安全域名”。
说明:本地测试设置内网穿透地址。
<script src="http://res.wx.qq.com/open/js/jweixin-1.4.0.js" type="text/javascript"></script>
引入前端项目/public/index.html文件
参考官方文档封装接口
我们需要分享的页面有直播详情页、点播课程详情页等,因此我们把分享代码封装后,在对应的页面直接引入与调用即可
新建src/util/wxShare.js文件
/** * 微信js-sdk * 参考文档:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115 */ const wxShare = { /** * [wxRegister 微信Api初始化] * @param {Function} callback [ready回调函数] */ wxRegister(data,option) { //data是微信配置信息,option是分享的配置内容 wx.config({ debug: true, // 开启调试模式 appId: data.appId, // 必填,公众号的唯一标识 timestamp: data.timestamp, // 必填,生成签名的时间戳 nonceStr: data.nonceStr, // 必填,生成签名的随机串 signature: data.signature,// 必填,签名,见附录1 jsApiList: [ 'onMenuShareAppMessage' ] // 必填,需要使用的JS接口列表,所有JS接口列表见附录2 }); wx.ready(function(){ wx.onMenuShareAppMessage({ title: option.title, // 分享标题 desc: option.desc, // 分享描述 link: option.link, // 分享链接 imgUrl: option.imgUrl, // 分享图标 success() { // 用户成功分享后执行的回调函数 // option.success() console.log('ok'); }, cancel() { // 用户取消分享后执行的回调函数 // option.error() console.log('cancel'); } }); }); wx.error(function(res){ // config信息验证失败会执行error函数,如签名过期导致验证失败,具体错误信息可以打开config的debug模式查看,也可以在返回的res参数中查看,对于SPA可以在这里更新签名。 //alert('error:'+JSON.stringify(res)); }); } } export default wxShare
新增ShareController类
说明:微信分享要对当前url加密处理,由于我们的url路由都是带“#”符号,服务器端接收不到,因此通过“guiguketan”单词代替了“#”。
@RestController @RequestMapping("/api/wechat/share") @Slf4j public class ShareController { @Autowired private WxMpService wxMpService; @GetMapping("/getSignature") public Result getSignature(@RequestParam("url") String url) throws WxErrorException { String currentUrl = url.replace("guiguketan", "#"); WxJsapiSignature jsapiSignature = wxMpService.createJsapiSignature(currentUrl); WxJsapiSignatureVo wxJsapiSignatureVo = new WxJsapiSignatureVo(); BeanUtils.copyProperties(jsapiSignature, wxJsapiSignatureVo); wxJsapiSignatureVo.setUserEedId(Base64Util.base64Encode(AuthContextHolder.getUserId()+"")); return Result.ok(wxJsapiSignatureVo); } }
@ApiModel("分享签名对象")
@Data
public class WxJsapiSignatureVo {
private String appId;
private String nonceStr;
private long timestamp;
private String url;
private String signature;
//加密用户id
private String userEedId;
}
页面:courseInfo.vue
(1)引入分享
import wxShare from '@/utils/wxShare'
(2)代码实现
关键代码
wxRegister() { //说明:后台加密url必须与当前页面url一致 let url = window.location.href.replace('#', 'guiguketan') shareApi.getSignature(url).then(response => { console.log(response.data); //记录分享用户 let link = ''; if(window.location.href.indexOf('?') != -1) { link = window.location.href + '&recommend=' + response.data.userEedId; } else { link = window.location.href + '?recommend=' + response.data.userEedId; } let option = { 'title': this.courseVo.title, 'desc': this.description, 'link': link, 'imgUrl': this.courseVo.cover } wxShare.wxRegister(response.data, option); }); }
(1)使用手机测试,其他端测试可能会出现错误问题
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。