硅谷课堂-智慧星球 Day 13~Day 14——尚硅谷项目笔记 2022 年
文章目录
Day 13-公众号点播课程和直播管理
一、实现公众号点播课程相关功能
1、课程列表和详情
1.1、需求说明
(1)点击课程中的分类,根据分类查询课程列表。
(2)点击去看看,进入课程详情页面。
1.2、编写课程列表和详情接口
(1)创建 CourseApiController。
package com.myxh.smart.planet.vod.api;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.myxh.smart.planet.model.vod.Course;
import com.myxh.smart.planet.result.Result;
import com.myxh.smart.planet.vo.vod.CourseQueryVo;
import com.myxh.smart.planet.vod.service.CourseService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
/**
* @author MYXH
* @date 2023/10/17
*/
@Tag(name = "课程 API", description = "课程 API 接口")
@RestController
@RequestMapping("/api/vod/course")
public class CourseApiController
{
@Autowired
private CourseService courseService;
/**
* 根据课程分类查询课程列表(分页)
*
* @param subjectParentId 课程一级分类 id
* @param current 当前页码
* @param limit 每页记录数
* @return Result 全局统一返回结果
*/
@Operation(summary = "根据课程分类查询课程列表(分页)", description = "根据课程分类查询课程列表(分页)")
@GetMapping("{subjectParentId}/{current}/{limit}")
public Result<Map<String, Object>> findCoursePage(@Parameter(name = "subjectParentId", description = "课程一级分类 ID", required = true) @PathVariable Long subjectParentId,
@Parameter(name = "current", description = "当前页码", required = true) @PathVariable Long current,
@Parameter(name = "limit", description = "每页记录数", required = true) @PathVariable Long limit)
{
// 封装条件
CourseQueryVo courseQueryVo = new CourseQueryVo();
courseQueryVo.setSubjectParentId(subjectParentId);
// 创建 page 对象
Page<Course> coursePageParam = new Page<>(current, limit);
Map<String, Object> coursePageMap = courseService.findCoursePage(coursePageParam, courseQueryVo);
return Result.ok(coursePageMap);
}
/**
* 根据课程 id 查询课程详情信息
*
* @param courseId 课程 id
* @return Result 全局统一返回结果
*/
@Operation(summary = "根据课程 id 查询课程详情信息", description = "根据课程 id 查询课程详情信息")
@GetMapping("get/info/{courseId}")
public Result<Map<String, Object>> getCourseInfo(
@Parameter(name = "courseId", description = "课程ID", required = true) @PathVariable Long courseId)
{
Map<String, Object> courseInfoMap = courseService.getInfoById(courseId);
return Result.ok(courseInfoMap);
}
}
(2)编写 CourseService。
package com.myxh.smart.planet.vod.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.myxh.smart.planet.model.vod.Course;
import com.myxh.smart.planet.vo.vod.CourseQueryVo;
import java.util.Map;
/**
* @author MYXH
* @date 2023/10/8
*
* <p>
* 课程 服务类
* </p>
*/
public interface CourseService extends IService<Course>
{
/**
* 根据课程分类查询课程列表(分页)
*
* @param coursePageParam 课程页面参数
* @param courseQueryVo 查询对象
* @return coursePageMap 课程页面
*/
Map<String, Object> findCoursePage(Page<Course> coursePageParam, CourseQueryVo courseQueryVo);
/**
* 根据课程 id 查询课程详情信息
*
* @param courseId 课程 id
* @return courseInfoMap 课程详情信息
*/
Map<String, Object> getInfoById(Long courseId);
}
(3)编写 CourseServiceImpl。
package com.myxh.smart.planet.vod.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.myxh.smart.planet.model.vod.Course;
import com.myxh.smart.planet.model.vod.CourseDescription;
import com.myxh.smart.planet.model.vod.Subject;
import com.myxh.smart.planet.model.vod.Teacher;
import com.myxh.smart.planet.vo.vod.ChapterVo;
import com.myxh.smart.planet.vo.vod.CourseQueryVo;
import com.myxh.smart.planet.vo.vod.CourseVo;
import com.myxh.smart.planet.vod.mapper.CourseMapper;
import com.myxh.smart.planet.vod.service.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author MYXH
* @date 2023/10/8
*
* <p>
* 课程 服务实现类
* </p>
*/
@Service
public class CourseServiceImpl extends ServiceImpl<CourseMapper, Course> implements CourseService
{
@Autowired
private SubjectService subjectService;
@Autowired
private TeacherService teacherService;
@Autowired
private CourseDescriptionService courseDescriptionService;
@Autowired
private ChapterService chapterService;
/**
* 根据课程分类查询课程列表(分页)
*
* @param coursePageParam 课程页面参数
* @param courseQueryVo 查询对象
* @return coursePageMap 课程页面
*/
@Override
public Map<String, Object> findCoursePage(Page<Course> coursePageParam, CourseQueryVo courseQueryVo)
{
// 获取条件值
// 课程名称
String title = courseQueryVo.getTitle();
// 二级分类
Long subjectId = courseQueryVo.getSubjectId();
// 一级分类
Long subjectParentId = courseQueryVo.getSubjectParentId();
// 教师
Long teacherId = courseQueryVo.getTeacherId();
// 判断条件值是否为空,封装条件
QueryWrapper<Course> wrapper = new QueryWrapper<>();
if (StringUtils.hasLength(title))
{
wrapper.like("title", title);
}
if (!ObjectUtils.isEmpty(subjectId))
{
wrapper.eq("subject_id", subjectId);
}
if (!ObjectUtils.isEmpty(subjectParentId))
{
wrapper.eq("subject_parent_id", subjectParentId);
}
if (!ObjectUtils.isEmpty(teacherId))
{
wrapper.eq("teacher_id", teacherId);
}
// 调用方法进行条件分页查询
Page<Course> pages = baseMapper.selectPage(coursePageParam, wrapper);
// 获取分页数据
// 总记录数
long totalCount = pages.getTotal();
// 总页数
long totalPage = pages.getPages();
// 每页数据集合
List<Course> coursePageRecords = pages.getRecords();
// 封装其他数据(获取教师名称和课程分类名称)
coursePageRecords.stream().forEach(this::getTeacherAndSubjectName);
Map<String, Object> coursePageMap = new HashMap<>();
coursePageMap.put("totalCount", totalCount);
coursePageMap.put("totalPage", totalPage);
coursePageMap.put("records", coursePageRecords);
return coursePageMap;
}
/**
* 获取教师和分类名称
*
* @param course 课程数据
* @return course 课程数据
*/
private Course getTeacherAndSubjectName(Course course)
{
// 教师名称
Long teacherId = course.getTeacherId();
Teacher teacher = teacherService.getById(teacherId);
if (teacher != null)
{
course.getParam().put("teacherName", teacher.getName());
}
// 课程分类名称
Long subjectParentId = course.getSubjectParentId();
Subject subjectOne = subjectService.getById(subjectParentId);
if (subjectOne != null)
{
course.getParam().put("subjectParentTitle", subjectOne.getTitle());
}
Long subjectId = course.getSubjectId();
Subject subjectTwo = subjectService.getById(subjectId);
if (subjectTwo != null)
{
course.getParam().put("subjectTitle", subjectTwo.getTitle());
}
return course;
}
/**
* 根据课程 id 查询课程详情信息
*
* @param courseId 课程 id
* @return courseInfoMap 课程详情信息
*/
@Override
public Map<String, Object> getInfoById(Long courseId)
{
// 更新浏览数量
Course course = baseMapper.selectById(courseId);
course.setViewCount(course.getViewCount() + 1);
baseMapper.updateById(course);
// 课程详情数据
CourseVo courseVo = baseMapper.selectCourseVoById(courseId);
// 课程章节小节数据
List<ChapterVo> chapterVoList = chapterService.getNestedTreeList(courseId);
// 课程描述信息
QueryWrapper<CourseDescription> wrapper = new QueryWrapper<>();
wrapper.eq("course_id", courseId);
CourseDescription courseDescription = courseDescriptionService.getOne(wrapper);
// 课程所属教师信息
Teacher teacher = teacherService.getById(course.getTeacherId());
// 是否购买,后续完善
Boolean isBuy = false;
// 封装 map 集合,返回数据
Map<String, Object> courseInfoMap = new HashMap<>();
courseInfoMap.put("courseVo", courseVo);
courseInfoMap.put("chapterVoList", chapterVoList);
courseInfoMap.put("description", courseDescription != null ? courseDescription.getDescription() : "");
courseInfoMap.put("teacher", teacher);
// 是否购买
courseInfoMap.put("isBuy", isBuy);
return courseInfoMap;
}
}
(4)编写 CourseMapper。
package com.myxh.smart.planet.vod.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.myxh.smart.planet.model.vod.Course;
import com.myxh.smart.planet.vo.vod.CourseVo;
/**
* @author MYXH
* @date 2023/10/8
*
* <p>
* 课程 Mapper 接口
* </p>
*/
public interface CourseMapper extends BaseMapper<Course>
{
/**
* 根据课程 id 查询课程详情信息
*
* @param courseId 课程 id
* @return courseInfoMap 课程详情信息
*/
CourseVo selectCourseVoById(Long courseId);
}
(5)编写 CourseMapper.xml。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.myxh.smart.planet.vod.mapper.CourseMapper">
<select id="selectCourseVoById" resultType="com.myxh.smart.planet.vo.vod.CourseVo">
SELECT
<include refid="columns"/>
FROM
<include refid="tables"/>
WHERE c.id = #{id}
</select>
<sql id="columns">
c.id,
c.teacher_id as teacherId,
c.title,
c.price,
c.lesson_num AS lessonNum,
c.cover,
c.buy_count AS buyCount,
c.view_count AS viewCount,
c.status,
c.publish_time AS publishTime,
t.name AS teacherName,
s1.title AS subjectParentTitle,
s2.title AS subjectTitle
</sql>
<sql id="tables">
`course` AS c
LEFT OUTER JOIN `teacher` AS t ON c.teacher_id = t.id
LEFT OUTER JOIN `subject` AS s1 ON c.subject_parent_id = s1.id
LEFT OUTER JOIN `subject` AS s2 ON c.subject_id = s2.id
</sql>
</mapper>
1.3、整合课程列表和详情前端
(1)查看路由文件。
(2)创建 js 文件定义接口。
import request from "@/utils/request";
const COURSE_API = "/api/vod/course";
export default {
/**
* 课程分页列表
*
* @param {number} subjectParentId 课程专业父级 id
* @param {number} pageNo 页面号
* @param {number} pageSize 页面大小
* @returns {Promise} 返回一个 Promise 对象,表示操作的异步结果
*/
findPage(subjectParentId, pageNo, pageSize) {
return request({
url: `${COURSE_API}/${subjectParentId}/${pageNo}/${pageSize}`,
method: "get",
});
},
/**
* 课程详情
*
* @param {number} courseId 课程 id
* @returns {Promise} 返回一个 Promise 对象,表示操作的异步结果
*/
getInfo(courseId) {
return request({
url: `${COURSE_API}/get/info/${courseId}`,
method: "get",
});
},
};
(3)编写页面。
course.vue
<template>
<div>
<head>
<!-- 设置 referrer 为 no-referrer,用于绕过防盗链限制,从而正常显示图片 -->
<meta name="referrer" content="no-referrer" />
</head>
<van-image
width="100%"
height="200"
src="https://img-blog.csdnimg.cn/b9f45932f6cf471aa53ee24e5555d9c2.png"
/>
<van-pull-refresh v-model="refreshing" @refresh="onRefresh">
<!-- offset:滚动条与底部距离小于 offset 时触发load事件 默认300,因此要改小,否则首次进入一直触发 -->
<van-list
v-model="loading"
:finished="finished"
finished-text="没有更多了"
offset="10"
@load="onLoad"
>
<van-card
v-for="(item, index) in list"
:key="index"
:price="item.price"
:title="item.title"
:thumb="item.cover"
>
<template #tags>
<van-tag round plain color="#ffe1e1" text-color="#ad0000"
>课时数:{{ item.lessonNum }}</van-tag
>
<br />
<van-tag round plain color="#ffe1e1" text-color="#ad0000"
>购买数:{{ item.buyCount }}</van-tag
>
<br />
<van-tag round plain color="#ffe1e1" text-color="#ad0000"
>访问量:{{ item.viewCount }}</van-tag
>
</template>
<template #footer>
<van-button size="mini" @click="info(item.id)">去看看</van-button>
</template>
</van-card>
</van-list>
</van-pull-refresh>
</div>
</template>
<script>
import courseAPI from "@/api/course";
export default {
name: "Course",
data() {
return {
subjectParentId: 1,
loading: false,
finished: false,
refreshing: false,
pageNo: 1,
pageSize: 5,
pages: 1,
list: [],
};
},
created() {
this.subjectParentId = this.$route.params.subjectId;
},
methods: {
onLoad() {
this.fetchData();
},
onRefresh() {
// 清空列表数据
this.finished = false;
this.pageNo = 1;
// 重新加载数据
// 将 loading 设置为 true,表示处于加载状态
this.loading = true;
this.fetchData();
},
fetchData() {
courseAPI
.findPage(this.subjectParentId, this.pageNo, this.pageSize)
.then((response) => {
console.log(response.data);
if (this.refreshing) {
this.list = [];
this.refreshing = false;
}
for (let i = 0; i < response.data.records.length; i++) {
this.list.push(response.data.records[i]);
}
this.pages = response.data.totalPage;
this.loading = false;
if (this.pageNo >= this.pages) {
this.finished = true;
}
this.pageNo++;
});
},
info(id) {
this.$router.push({ path: "/course/info/" + id });
},
},
};
</script>
<style lang="scss" scoped>
.list {
li {
margin: 10px;
padding-bottom: 5px;
border-bottom: 1px solid #e5e5e5;
h1 {
font-size: 20px;
}
.list-box {
display: flex;
font-size: 14px;
ul {
flex: 1;
margin: 0;
li {
margin: 0;
border-bottom: none;
}
}
p {
margin: 0;
width: 50px;
align-items: center;
align-self: flex-end;
}
}
}
}
</style>
courseInfo.vue
<template>
<div>
<van-image width="100%" height="200" :src="courseVo.cover" />
<van-row>
<van-col span="8">
<div class="course_count">
<h1>购买数</h1>
<p>{{ courseVo.buyCount }}</p>
</div>
</van-col>
<van-col span="8">
<div class="course_count">
<h1>课时数</h1>
<p>{{ courseVo.lessonNum }}</p>
</div>
</van-col>
<van-col span="8">
<div class="course_count">
<h1>浏览数</h1>
<p>{{ courseVo.viewCount }}</p>
</div>
</van-col>
</van-row>
<h1 class="van-ellipsis course_title">{{ courseVo.title }}</h1>
<div class="course_teacher_price_box">
<div class="course_teacher_price">
<div class="course_price">价格:</div>
<div class="course_price_number">¥{{ courseVo.price }}</div>
</div>
<div>
<van-button
@click="see()"
v-if="isBuy || courseVo.price == '0.00'"
plain
type="warning"
size="mini"
>立即观看</van-button
>
<van-button @click="buy" v-else plain type="warning" size="mini"
>立即购买</van-button
>
</div>
</div>
<div class="course_teacher_price_box">
<div class="course_teacher_box">
<div class="course_teacher">教师: {{ teacher.name }}</div>
<van-image :src="teacher.avatar" round width="50px" height="50px" />
</div>
</div>
<div class="course_contents">
<div class="course_title_font">课程详情</div>
<van-divider :style="{ margin: '5px 0 ' }" />
<div class="course_content" v-html="description"></div>
<div class="course_title_font">课程大纲</div>
<div class="gap"></div>
<van-collapse v-model="activeNames">
<van-collapse-item
:title="item.title"
:name="item.id"
v-for="item in chapterVoList"
:key="item.id"
>
<ul
class="course_chapter_list"
v-for="child in item.children"
:key="child.id"
>
<h2>{{ child.title }}</h2>
<p v-if="child.isFree == 1">
<van-button @click="play(child)" type="warning" size="mini" plain
>免费观看</van-button
>
</p>
<p v-else>
<van-button @click="play(child)" type="warning" size="mini" plain
>观看</van-button
>
</p>
</ul>
</van-collapse-item>
</van-collapse>
</div>
<van-loading vertical="true" v-show="loading">加载中...</van-loading>
</div>
</template>
<script>
import courseAPI from "@/api/course";
import shareAPI from "@/api/share";
import wxShare from "@/utils/wxShare";
export default {
data() {
return {
loading: false,
courseId: null,
courseVo: {},
description: "",
teacher: {},
chapterVoList: [],
isBuy: false,
activeNames: ["1"],
};
},
created() {
this.courseId = this.$route.params.courseId;
this.fetchData();
},
methods: {
fetchData() {
this.loading = true;
courseAPI.getInfo(this.courseId).then((response) => {
console.log(response.data);
this.courseVo = response.data.courseVo;
this.description = response.data.description;
this.isBuy = response.data.isBuy;
this.chapterVoList = response.data.chapterVoList;
this.teacher = response.data.teacher;
this.loading = false;
//分享注册
this.wxRegister();
});
},
buy() {
this.$router.push({ path: "/trade/" + this.courseId });
},
play(video) {
let videoId = video.id;
let isFree = video.isFree;
if (isFree === 1 || this.isBuy || this.courseVo.price == "0.00") {
this.$router.push({
path: "/play/" + this.courseId + "/" + videoId,
});
} else {
// this.$router.push({ path: "/play/" + this.courseId + "/" + videoId });
if (window.confirm("购买了才可以观看, 是否继续?")) {
this.buy();
}
}
},
see() {
this.$router.push({ path: "/play/" + this.courseId + "/0" });
},
wxRegister() {
// 说明:后台加密 url 必须与当前页面 url 一致
let url = window.location.href.replace("#", "smartplanet");
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);
});
},
},
};
</script>
<style lang="scss" scoped>
.gap {
height: 10px;
}
::v-deep.van-image {
display: block;
}
.course_count {
background-color: #82848a;
color: white;
padding: 5px;
text-align: center;
border-right: 1px solid #939393;
h1 {
font-size: 14px;
margin: 0;
}
p {
margin: 0;
font-size: 16px;
}
}
.course_title {
font-size: 20px;
margin: 10px;
}
.course_teacher_price_box {
margin: 10px;
display: flex;
justify-content: space-between;
align-items: center;
.course_teacher_price {
display: flex;
font-size: 14px;
align-items: center;
.course_price_number {
color: red;
font-size: 18px;
font-weight: bold;
}
.course_teacher {
margin-left: 20px;
}
}
.course_teacher_box {
display: flex;
justify-content: center;
align-items: center;
.course_teacher {
margin-right: 20px;
}
}
}
.course_contents {
margin: 10px;
.course_title_font {
color: #68cb9b;
font-weight: bold;
}
.course_content {
margin-bottom: 20px;
}
}
.course_chapter_list {
display: flex;
justify-content: space-between;
align-items: center;
h2 {
font-size: 14px;
}
p {
margin: 0;
}
}
</style>
2、点播视频播放
1.1、获取视频播放参数
(1)创建 VodApiController。
package com.myxh.smart.planet.vod.api;
import com.myxh.smart.planet.result.Result;
import com.myxh.smart.planet.vod.service.VodService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
/**
* @author MYXH
* @date 2023/10/20
*/
@Tag(name = "腾讯云点播 API", description = "腾讯云点播 API 接口")
@RestController
@RequestMapping("/api/vod")
public class VodApiController
{
@Autowired
private VodService vodService;
/**
* 获取播放凭证
*
* @param courseId 课程 id
* @param videoId 视频 id
* @return Result 全局统一返回结果
*/
@Operation()
@GetMapping("get/play/auth/{courseId}/{videoId}")
public Result<Map<String, Object>> getPlayAuth(
@Parameter(name = "courseId", description = "课程ID", required = true)
@PathVariable("courseId") Long courseId,
@Parameter(name = "videoId", description = "视频ID", required = true)
@PathVariable("videoId") Long videoId)
{
Map<String, Object> playAuthMap = vodService.getPlayAuth(courseId, videoId);
return Result.ok(playAuthMap);
}
}
(2)application.properties 添加。
# 设置点播账号的子应用 appID
tencent.video.appid=1315007088
(3)service-vod 引入依赖。
<!-- java-jwt -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.10.0</version>
</dependency>
(4)创建 Psign 类。
package com.myxh.smart.planet.vod.utils;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import java.util.HashMap;
/**
* @author MYXH
* @date 2023/10/21
*/
public class Psign
{
public static String getPsign(Integer appId, String fileId)
{
// 点播应用 appId
Integer AppId = appId;
// 点播文件 ID
String FileId = fileId;
// 播放的音视频类型,可选值
// RawAdaptive:未加密的,转自适应码流,输出。
// ProtectedAdaptive:私有加密或 DRM 保护的,转自适应码流,输出
// Transcode:转码后输出
// Original:上传的原始音视频
String AudioVideoType = "RawAdaptive";
Integer RawAdaptiveDefinition = 10;
Integer ImageSpriteDefinition = 10;
// 派发签名当前 Unix 时间戳
Integer CurrentTime = Math.toIntExact(System.currentTimeMillis() / 1000);
// 播放密钥
String PlayKey = "cw8EdW6d9indO6f3Uk6s";
HashMap<String, Object> urlAccessInfo = new HashMap<>();
HashMap<String, Object> contentInfo = new HashMap<>();
contentInfo.put("audioVideoType", AudioVideoType);
contentInfo.put("rawAdaptiveDefinition", RawAdaptiveDefinition);
contentInfo.put("imageSpriteDefinition", ImageSpriteDefinition);
Algorithm algorithm = Algorithm.HMAC256(PlayKey);
String token = JWT.create()
.withClaim("appId", AppId)
.withClaim("fileId", FileId)
.withClaim("contentInfo", contentInfo)
.withClaim("currentTimeStamp", CurrentTime)
.withClaim("urlAccessInfo", urlAccessInfo).sign(algorithm);
return token;
}
}
(5)VodService 创建方法。
package com.myxh.smart.planet.vod.service;
import java.util.Map;
/**
* @author MYXH
* @date 2023/10/11
*/
public interface VodService
{
/**
* 获取播放凭证
*
* @param courseId 课程 id
* @param videoId 视频 id
* @return playAuthMap 播放凭证
*/
Map<String, Object> getPlayAuth(Long courseId, Long videoId);
}
(6)VodServiceImpl 实现方法。
package com.myxh.smart.planet.vod.service.impl;
import com.myxh.smart.planet.exception.SmartPlanetException;
import com.myxh.smart.planet.model.vod.Video;
import com.myxh.smart.planet.vod.service.VideoService;
import com.myxh.smart.planet.vod.service.VodService;
import com.myxh.smart.planet.vod.utils.Psign;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
/**
* @author MYXH
* @date 2023/10/11
*/
@Service
public class VodServiceImpl implements VodService
{
// @Lazy 注解可以延迟 Bean 的初始化,缓解循环引用
@Lazy
@Autowired
private VideoService videoService;
@Value("${tencent.video.appid}")
private String appId;
/**
* 获取播放凭证
*
* @param courseId 课程 id
* @param videoId 视频 id
* @return playAuthMap 播放凭证
*/
@Override
public Map<String, Object> getPlayAuth(Long courseId, Long videoId)
{
// 根据小节 id 获取小节对象,获取腾讯云视频 id
Video video = videoService.getById(videoId);
if (video == null)
{
throw new SmartPlanetException(20001, "课程视频信息不存在");
}
String videoSourceId = video.getVideoSourceId();
Integer id = Integer.valueOf(appId);
String psign = Psign.getPsign(id, videoSourceId);
Map<String, Object> playAuthMap = new HashMap<>();
playAuthMap.put("videoSourceId", videoSourceId);
playAuthMap.put("appId", appId);
playAuthMap.put("psign", psign);
return playAuthMap;
}
}
1.2、整合点播视频播放前端
(1)创建 js 定义接口。
import request from "@/utils/request";
const api_name = "/api/vod";
export default {
// 获取播放凭证
getPlayAuth(courseId, videoId) {
return request({
url: `${api_name}/getPlayAuth/${courseId}/${videoId}`,
method: "get",
});
},
};
(2)courseInfo.vue 修改 play 方法。
<template>
<div>
<van-image width="100%" height="200" :src="courseVo.cover" />
<van-row>
<van-col span="8">
<div class="course_count">
<h1>购买数</h1>
<p>{{ courseVo.buyCount }}</p>
</div>
</van-col>
<van-col span="8">
<div class="course_count">
<h1>课时数</h1>
<p>{{ courseVo.lessonNum }}</p>
</div>
</van-col>
<van-col span="8">
<div class="course_count">
<h1>浏览数</h1>
<p>{{ courseVo.viewCount }}</p>
</div>
</van-col>
</van-row>
<h1 class="van-ellipsis course_title">{{ courseVo.title }}</h1>
<div class="course_teacher_price_box">
<div class="course_teacher_price">
<div class="course_price">价格:</div>
<div class="course_price_number">¥{{ courseVo.price }}</div>
</div>
<div>
<van-button
@click="see()"
v-if="isBuy || courseVo.price == '0.00'"
plain
type="warning"
size="mini"
>立即观看</van-button
>
<van-button @click="buy" v-else plain type="warning" size="mini"
>立即购买</van-button
>
</div>
</div>
<div class="course_teacher_price_box">
<div class="course_teacher_box">
<div class="course_teacher">教师: {{ teacher.name }}</div>
<van-image :src="teacher.avatar" round width="50px" height="50px" />
</div>
</div>
<div class="course_contents">
<div class="course_title_font">课程详情</div>
<van-divider :style="{ margin: '5px 0 ' }" />
<div class="course_content" v-html="description"></div>
<div class="course_title_font">课程大纲</div>
<div class="gap"></div>
<van-collapse v-model="activeNames">
<van-collapse-item
:title="item.title"
:name="item.id"
v-for="item in chapterVoList"
:key="item.id"
>
<ul
class="course_chapter_list"
v-for="child in item.children"
:key="child.id"
>
<h2>{{ child.title }}</h2>
<p v-if="child.isFree == 1">
<van-button @click="play(child)" type="warning" size="mini" plain
>免费观看</van-button
>
</p>
<p v-else>
<van-button @click="play(child)" type="warning" size="mini" plain
>观看</van-button
>
</p>
</ul>
</van-collapse-item>
</van-collapse>
</div>
<van-loading vertical="true" v-show="loading">加载中...</van-loading>
</div>
</template>
<script>
import courseAPI from "@/api/course";
import shareAPI from "@/api/share";
import wxShare from "@/utils/wxShare";
export default {
data() {
return {
loading: false,
courseId: null,
courseVo: {},
description: "",
teacher: {},
chapterVoList: [],
isBuy: false,
activeNames: ["1"],
};
},
created() {
this.courseId = this.$route.params.courseId;
this.fetchData();
},
methods: {
fetchData() {
this.loading = true;
courseAPI.getInfo(this.courseId).then((response) => {
console.log(response.data);
this.courseVo = response.data.courseVo;
this.description = response.data.description;
this.isBuy = response.data.isBuy;
this.chapterVoList = response.data.chapterVoList;
this.teacher = response.data.teacher;
this.loading = false;
//分享注册
this.wxRegister();
});
},
buy() {
this.$router.push({ path: "/trade/" + this.courseId });
},
play(video) {
let videoId = video.id;
let isFree = video.isFree;
if (isFree === 1 || this.isBuy || this.courseVo.price == "0.00") {
this.$router.push({ path: "/play/" + this.courseId + "/" + videoId });
} else {
// this.$router.push({ path: "/play/" + this.courseId + "/" + videoId });
if (window.confirm("购买了才可以观看, 是否继续?")) {
this.buy();
}
}
},
see() {
this.$router.push({ path: "/play/" + this.courseId + "/0" });
},
wxRegister() {
// 说明:后台加密 url 必须与当前页面 url 一致
let url = window.location.href.replace("#", "smartplanet");
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);
});
},
},
};
</script>
<style lang="scss" scoped>
.gap {
height: 10px;
}
::v-deep.van-image {
display: block;
}
.course_count {
background-color: #82848a;
color: white;
padding: 5px;
text-align: center;
border-right: 1px solid #939393;
h1 {
font-size: 14px;
margin: 0;
}
p {
margin: 0;
font-size: 16px;
}
}
.course_title {
font-size: 20px;
margin: 10px;
}
.course_teacher_price_box {
margin: 10px;
display: flex;
justify-content: space-between;
align-items: center;
.course_teacher_price {
display: flex;
font-size: 14px;
align-items: center;
.course_price_number {
color: red;
font-size: 18px;
font-weight: bold;
}
.course_teacher {
margin-left: 20px;
}
}
.course_teacher_box {
display: flex;
justify-content: center;
align-items: center;
.course_teacher {
margin-right: 20px;
}
}
}
.course_contents {
margin: 10px;
.course_title_font {
color: #68cb9b;
font-weight: bold;
}
.course_content {
margin-bottom: 20px;
}
}
.course_chapter_list {
display: flex;
justify-content: space-between;
align-items: center;
h2 {
font-size: 14px;
}
p {
margin: 0;
}
}
</style>
(3)index.html 引入文件。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,,chrome=1" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, shrink-to-fit=no"
/>
<title>智慧星球</title>
<link rel="icon" href="<%= BASE_URL %>favicon.ico" />
<script
src="http://res2.wx.qq.com/open/js/jweixin-1.6.0.js"
type="text/javascript"
></script>
<!-- 引入播放器 css 文件 -->
<link
href="https://web.sdk.qcloud.com/player/tcplayer/release/v4.7.2/tcplayer.min.css"
rel="stylesheet"
/>
<!-- 如果需要在 Chrome 和 Firefox 等现代浏览器中通过 H5 播放 HLS 协议的视频,需要在 tcplayer.vx.x.x.min.js 之前引入 hls.min.x.xx.xm.js。 -->
<script src="https://web.sdk.qcloud.com/player/tcplayer/release/v4.7.2/libs/hls.min.1.1.6.js"></script>
<!-- 如果需要在 Chrome 和 Firefox 等现代浏览器中通过 H5 播放 FLV 格式的视频,需要在 tcplayer.vx.x.x.min.js 之前引入 flv.min.x.x.x.js。 -->
<script src="https://web.sdk.qcloud.com/player/tcplayer/release/v4.7.2/libs/flv.min.1.6.3.js"></script>
<!-- 如果需要在 Chrome 和 Firefox 等现代浏览器中通过 H5 播放 DASH 视频,需要在 tcplayer.vx.x.x.min.js 之前引入 dash.min.x.x.x.js。 -->
<script src="https://web.sdk.qcloud.com/player/tcplayer/release/v4.7.2/libs/dash.all.min.4.5.2.js"></script>
<!-- 引入播放器 js 文件 -->
<script src="https://web.sdk.qcloud.com/player/tcplayer/release/v4.7.2/tcplayer.v4.7.2.min.js"></script>
</head>
<body>
<script
type="text/javascript"
src="https://static-1.talk-fun.com/open/TalkFun_SDK_Pack/v7.0/TalkFunWebSDK-7.7.min.js"
></script>
<noscript>
<strong
>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work
properly without JavaScript enabled. Please enable it to
continue.</strong
>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
(4)创建 play.vue 页面。
<template>
<div>
<video
id="player-container-id"
preload="auto"
width="400"
height="280"
playsinline
webkit-playsinline
x5-playsinline
></video>
<h1 class="van-ellipsis course_title">{{ courseVo.title }}</h1>
<div class="course_teacher_price_box">
<div class="course_teacher_price">
<div class="course_price">价格:</div>
<div class="course_price_number">¥{{ courseVo.price }}</div>
<div class="course_teacher">教师: {{ courseVo.teacherName }}</div>
</div>
<div>
<van-button
@click="getPlayAuth('0')"
v-if="isBuy || courseVo.price == '0.00'"
plain
type="warning"
size="mini"
>立即观看</van-button
>
<van-button @click="buy" v-else plain type="warning" size="mini"
>立即购买</van-button
>
</div>
</div>
<div class="course_contents">
<div class="course_title_font">课程大纲</div>
<div class="gap"></div>
<van-collapse v-model="activeNames">
<van-collapse-item
:title="item.title"
:name="item.id"
v-for="item in chapterVoList"
:key="item.id"
>
<ul
class="course_chapter_list"
v-for="child in item.children"
:key="child.id"
>
<h2 :style="activeVideoId == child.id ? 'color:blue' : ''">
{{ child.title }}
</h2>
<p v-if="child.isFree == 1">
<van-button @click="see(child)" type="warning" size="mini" plain
>免费观看</van-button
>
</p>
<p v-else>
<van-button @click="see(child)" type="warning" size="mini" plain
>观看</van-button
>
</p>
</ul>
</van-collapse-item>
</van-collapse>
</div>
<van-loading vertical="true" v-show="loading">加载中...</van-loading>
</div>
</template>
<script>
import courseAPI from "@/api/course";
import vodAPI from "@/api/vod";
import videoVisitorAPI from "@/api/videoVisitor";
export default {
data() {
return {
loading: false,
courseId: null,
videoId: null,
courseVo: {},
description: "",
chapterVoList: [],
isBuy: false,
// firstVideo: null,
activeNames: ["1"],
// 记录当前正在播放的视频
activeVideoId: 0,
player: null,
};
},
created() {
this.courseId = this.$route.params.courseId;
this.videoId = this.$route.params.videoId || "0";
this.fetchData();
this.getPlayAuth(this.videoId);
},
methods: {
fetchData() {
this.loading = true;
courseAPI.getInfo(this.courseId).then((response) => {
console.log(response.data);
this.courseVo = response.data.courseVo;
this.description = response.data.description;
this.isBuy = response.data.isBuy;
this.chapterVoList = response.data.chapterVoList;
this.loading = false;
});
},
see(video) {
let videoId = video.id;
let isFree = video.isFree;
this.getPlayAuth(videoId);
if (isFree === 1 || this.isBuy || this.courseVo.price == "0.00") {
this.getPlayAuth(videoId);
} else {
if (window.confirm("购买了才可以观看, 是否继续?")) {
this.buy();
}
}
},
buy() {
this.$router.push({ path: "/trade/" + this.courseId });
},
getPlayAuth(videoId) {
if (this.player != null) {
// 是销毁之前的视频,不销毁的话,它会一直存在
this.player.dispose();
}
vodAPI.getPlayAuth(this.courseId, videoId).then((response) => {
console.log(response.data);
this.play(response.data);
// 展开章节
this.activeNames = [response.data.chapterId];
// 选中播放视频
this.activeVideoId = response.data.videoId;
});
},
// 视频播放
play(data) {
// window.location = './video.html?fileID='+data.videoSourceId+'&appID='+data.appId;
var player = TCPlayer("player-container-id", {
// player-container-id 为播放器容器 ID,必须与 html 中一致
// 请传入需要播放的视频 fileID 必须
fileID: data.videoSourceId,
// 请传入点播账号的子应用 appID 必须
appID: data.appId,
psign: data.psign,
// 其他参数请在开发文档中查看
});
},
},
};
</script>
<style lang="scss" scoped>
.gap {
height: 10px;
}
::v-deep.van-image {
display: block;
}
.course_count {
background-color: #82848a;
color: white;
padding: 5px;
text-align: center;
border-right: 1px solid #939393;
h1 {
font-size: 14px;
margin: 0;
}
p {
margin: 0;
font-size: 16px;
}
}
.course_title {
font-size: 20px;
margin: 10px;
}
.course_teacher_price_box {
margin: 10px;
display: flex;
justify-content: space-between;
align-items: center;
.course_teacher_price {
display: flex;
font-size: 14px;
align-items: center;
.course_price_number {
color: red;
font-size: 18px;
font-weight: bold;
}
.course_teacher {
margin-left: 20px;
}
}
}
.course_contents {
margin: 10px;
.course_title_font {
color: #68cb9b;
font-weight: bold;
}
.course_content {
margin-bottom: 20px;
}
}
.course_chapter_list {
display: flex;
justify-content: space-between;
align-items: center;
h2 {
font-size: 14px;
}
p {
margin: 0;
}
}
</style>
3、付费观看点播课程接口
3.1、需求介绍
(1)点击课程详情页立即购买。
(2)点击确认下单,生成课程订单。
3.2、编写创建订单接口
(1)创建 OrderInfoApiController。
package com.myxh.smart.planet.order.api;
import com.myxh.smart.planet.order.service.OrderInfoService;
import com.myxh.smart.planet.result.Result;
import com.myxh.smart.planet.vo.order.OrderFormVo;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author MYXH
* @date 2023/10/21
*/
@Tag(name = "订单信息 API", description = "订单信息 API 接口")
@RestController
@RequestMapping("api/order/order/info")
public class OrderInfoApiController
{
@Autowired
private OrderInfoService orderInfoService;
/**
* 新增点播课程订单
*
* @param orderFormVo 订单信息
* @param request 请求
* @return Result 全局统一返回结果
*/
@Operation(summary = "新增点播课程订单", description = "新增点播课程订单")
@PostMapping("submit/order")
public Result<Long> submitOrder(@RequestBody OrderFormVo orderFormVo, HttpServletRequest request)
{
// 返回订单 id
Long orderId = orderInfoService.submitOrder(orderFormVo);
return Result.ok(orderId);
}
}
(2)编写 Service。
OrderInfoService
package com.myxh.smart.planet.order.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.myxh.smart.planet.model.order.OrderInfo;
import com.myxh.smart.planet.vo.order.OrderFormVo;
/**
* @author MYXH
* @date 2023/10/14
*
* <p>
* 订单信息 服务类
* </p>
*/
public interface OrderInfoService extends IService<OrderInfo>
{
/**
* 新增点播课程订单
*
* @param orderFormVo 订单信息
* @return orderId 订单 id
*/
Long submitOrder(OrderFormVo orderFormVo);
}
3.3、创建获取课程信息接口
操作 service-vod 模块。
(1)CourseApiController 添加方法。
package com.myxh.smart.planet.vod.api;
import com.myxh.smart.planet.model.vod.Course;
import com.myxh.smart.planet.vod.service.CourseService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author MYXH
* @date 2023/10/17
*/
@Tag(name = "课程 API", description = "课程 API 接口")
@RestController
@RequestMapping("/api/vod/course")
public class CourseApiController
{
@Autowired
private CourseService courseService;
/**
* 根据课程 id 查询课程信息
*
* @param courseId 课程 id
* @return 课程信息
*/
@Operation(summary = "根据课程 id 查询课程信息", description = "根据课程 id 查询课程信息")
@GetMapping("inner/get/by/id/{courseId}")
public Course getById(@Parameter(name = "courseId", description = "课程ID", required = true) @PathVariable("courseId") Long courseId)
{
Course course = courseService.getById(courseId);
return course;
}
}
(2)service-course-client 定义方法。
package com.myxh.smart.planet.client.course;
import com.myxh.smart.planet.model.vod.Course;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
/**
* @author MYXH
* @date 2023/10/17
*/
@FeignClient("service-vod")
public interface CourseFeignClient
{
@Operation(summary = "根据课程 id 查询课程信息", description = "根据课程 id 查询课程信息")
@GetMapping("/api/vod/course/inner/get/by/id/{courseId}")
Course getById(@Parameter(name = "courseId", description = "课程ID", required = true) @PathVariable("courseId") Long courseId);
}
3.4、创建获取优惠券接口
操作 service-activity 模块。
(1)创建 CouponInfoApiController。
package com.myxh.smart.planet.activity.api;
import com.myxh.smart.planet.activity.service.CouponInfoService;
import com.myxh.smart.planet.model.activity.CouponInfo;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author MYXH
* @date 2023/10/21
*/
@Tag(name = "优惠券信息 API", description = "优惠券信息 API 接口")
@RestController
@RequestMapping("/api/activity/coupon/info")
public class CouponInfoApiController
{
@Autowired
private CouponInfoService couponInfoService;
/**
* 根据优惠券 id 查询优惠券
*
* @param couponId 优惠券 id
* @return 优惠券信息
*/
@Operation(summary = "根据优惠券 id 查询优惠券", description = "根据优惠券 id 查询优惠券")
@GetMapping(value = "inner/get/by/id/{couponId}")
public CouponInfo getById(@PathVariable("couponId") Long couponId)
{
CouponInfo couponInfo = couponInfoService.getById(couponId);
return couponInfo;
}
/**
* 更新优惠券使用状态
*
* @param couponUseId 优惠券使用 id
* @param orderId 订单 id
* @return true 优惠券使用状态
*/
@Operation(summary = "更新优惠券使用状态", description = "更新优惠券使用状态")
@GetMapping(value = "inner/update/coupon/info/use/status/{couponUseId}/{orderId}")
public Boolean updateCouponInfoUseStatus(@PathVariable("couponUseId") Long couponUseId, @PathVariable("orderId") Long orderId)
{
couponInfoService.updateCouponInfoUseStatus(couponUseId, orderId);
return true;
}
}
(2)编写 CouponInfoService。
package com.myxh.smart.planet.activity.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.myxh.smart.planet.activity.mapper.CouponInfoMapper;
import com.myxh.smart.planet.activity.service.CouponInfoService;
import com.myxh.smart.planet.activity.service.CouponUseService;
import com.myxh.smart.planet.model.activity.CouponInfo;
import com.myxh.smart.planet.model.activity.CouponUse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Date;
/**
* @author MYXH
* @date 2023/10/15
*
* <p>
* 优惠券信息 服务实现类
* </p>
*/
@Service
public class CouponInfoServiceImpl extends ServiceImpl<CouponInfoMapper, CouponInfo> implements CouponInfoService
{
@Autowired
private CouponUseService couponUseService;
/**
* 更新优惠券使用状态
*
* @param couponUseId 优惠券使用 id
* @param orderId 订单 id
*/
@Override
public void updateCouponInfoUseStatus(Long couponUseId, Long orderId)
{
CouponUse couponUse = new CouponUse();
couponUse.setId(couponUseId);
couponUse.setOrderId(orderId);
couponUse.setCouponStatus("1");
couponUse.setUsingTime(new Date());
couponUseService.updateById(couponUse);
}
}
(3)创建 service-activity-client 模块定义接口。
package com.myxh.smart.planet.client.activity;
import com.myxh.smart.planet.model.activity.CouponInfo;
import io.swagger.v3.oas.annotations.Operation;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
/**
* @author MYXH
* @date 2023/10/21
*/
@FeignClient("service-activity")
public interface CouponInfoFeignClient
{
@Operation(summary = "根据优惠券 id 查询优惠券", description = "根据优惠券 id 查询优惠券")
@GetMapping(value = "/api/activity/coupon/info/inner/get/by/id/{couponId}")
CouponInfo getById(@PathVariable("couponId") Long couponId);
@Operation(summary = "更新优惠券使用状态", description = "更新优惠券使用状态")
@GetMapping(value = "/api/activity/coupon/info/inner/update/coupon/info/use/status/{couponUseId}/{orderId}")
Boolean updateCouponInfoUseStatus(@PathVariable("couponUseId") Long couponUseId, @PathVariable("orderId") Long orderId);
}
3.5、获取当前用户 id
(1)common 模块引入依赖。
<!-- Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- Spring 3.X 集成 Redis 所需 common-pool2 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.12.0</version>
</dependency>
(2)复制工具类到 common 下的 service-utils 模块。
(3)前端实现方式。
import axios from "axios";
// 创建 axios 实例
const service = axios.create({
// api 的 base_url
baseURL: "http://smartplanet.free.idcfengye.com",
// 请求超时时间
timeout: 30000,
});
// http request 拦截器
service.interceptors.request.use(
(config) => {
// 获取 localStorage 里面的 token 值
let token = window.localStorage.getItem("token") || "";
if (token != "") {
// 把 token 值放到 header 里面
config.headers["token"] = token;
// cookie.get('SmartPlanet');
}
return config;
},
(err) => {
return Promise.reject(err);
}
);
// http response 拦截器
service.interceptors.response.use(
(response) => {
if (response.data.code == 208) {
// 替换 #,后台获取不到 # 后面的参数
let url = window.location.href.replace("#", "smartplanet");
window.location =
"http://smartplanet.free.idcfengye.com/api/user/wechat/authorize?returnUrl=" +
url;
} else {
if (response.data.code == 20000) {
return response.data;
} else {
console.log("response.data:" + JSON.stringify(response.data));
alert(response.data.message || "error");
return Promise.reject(response);
}
}
},
(error) => {
// 返回接口返回的错误信息
return Promise.reject(error.response);
}
);
export default service;
3.6、生成订单 Service
(1)service-order 引入依赖。
<!-- service-activity-client -->
<dependency>
<groupId>com.myxh.smart.planet</groupId>
<artifactId>service-activity-client</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!-- service-course-client -->
<dependency>
<groupId>com.myxh.smart.planet</groupId>
<artifactId>service-course-client</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!-- service-user-client -->
<dependency>
<groupId>com.myxh.smart.planet</groupId>
<artifactId>service-user-client</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
(2)OrderInfoServiceImpl
package com.myxh.smart.planet.order.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.myxh.smart.planet.client.activity.CouponInfoFeignClient;
import com.myxh.smart.planet.client.course.CourseFeignClient;
import com.myxh.smart.planet.client.user.UserInfoFeignClient;
import com.myxh.smart.planet.exception.SmartPlanetException;
import com.myxh.smart.planet.model.activity.CouponInfo;
import com.myxh.smart.planet.model.order.OrderDetail;
import com.myxh.smart.planet.model.order.OrderInfo;
import com.myxh.smart.planet.model.user.UserInfo;
import com.myxh.smart.planet.model.vod.Course;
import com.myxh.smart.planet.order.AuthContextHolder;
import com.myxh.smart.planet.order.OrderNoUtils;
import com.myxh.smart.planet.order.mapper.OrderInfoMapper;
import com.myxh.smart.planet.order.service.OrderDetailService;
import com.myxh.smart.planet.order.service.OrderInfoService;
import com.myxh.smart.planet.result.ResultCodeEnum;
import com.myxh.smart.planet.vo.order.OrderFormVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
/**
* @author MYXH
* @date 2023/10/14
*
* <p>
* 订单信息 服务实现类
* </p>
*/
@Service
public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo> implements OrderInfoService
{
@Autowired
private OrderDetailService orderDetailService;
@Autowired
private CouponInfoFeignClient couponInfoFeignClient;
@Autowired
private CourseFeignClient courseFeignClient;
@Autowired
private UserInfoFeignClient userInfoFeignClient;
/**
* 新增点播课程订单
*
* @param orderFormVo 订单信息
* @return orderId 订单 id
*/
@Override
public Long submitOrder(OrderFormVo orderFormVo)
{
// 1、获取生成订单条件值
Long userId = AuthContextHolder.getUserId();
Long courseId = orderFormVo.getCourseId();
Long couponId = orderFormVo.getCouponId();
// 2、判断当前用户是否已有当前课程的订单
LambdaQueryWrapper<OrderDetail> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(OrderDetail::getUserId, userId);
queryWrapper.eq(OrderDetail::getCourseId, courseId);
OrderDetail orderDetailExist = orderDetailService.getOne(queryWrapper);
if (orderDetailExist != null)
{
// 如果订单已存在,则直接返回订单 id
return orderDetailExist.getId();
}
// 3、根据课程 id 查询课程信息
Course course = courseFeignClient.getById(courseId);
if (course == null)
{
throw new SmartPlanetException(ResultCodeEnum.DATA_ERROR.getCode(), ResultCodeEnum.DATA_ERROR.getMessage());
}
// 4、根据用户 id 查询用户信息
UserInfo userInfo = userInfoFeignClient.getUserInfoById(userId);
if (userInfo == null)
{
throw new SmartPlanetException(ResultCodeEnum.DATA_ERROR.getCode(), ResultCodeEnum.DATA_ERROR.getMessage());
}
// 5、根据优惠券 id 查询优惠券信息
// 优惠券金额
BigDecimal couponReduce = new BigDecimal(0);
if (null != couponId)
{
CouponInfo couponInfo = couponInfoFeignClient.getById(couponId);
couponReduce = couponInfo.getAmount();
}
// 6、封装订单生成需要数据到对象,完成添加订单
// 6.1、封装数据到 orderInfo 对象里面,添加订单基本信息表
OrderInfo orderInfo = new OrderInfo();
orderInfo.setUserId(userId);
orderInfo.setNickName(userInfo.getNickName());
orderInfo.setPhone(userInfo.getPhone());
orderInfo.setProvince(userInfo.getProvince());
orderInfo.setOriginAmount(course.getPrice());
orderInfo.setCouponReduce(couponReduce);
orderInfo.setFinalAmount(orderInfo.getOriginAmount().subtract(orderInfo.getCouponReduce()));
orderInfo.setOutTradeNo(OrderNoUtils.getOrderNo());
orderInfo.setTradeBody(course.getTitle());
orderInfo.setOrderStatus("0");
baseMapper.insert(orderInfo);
// 6.2、封装数据到 orderDetail 对象里面,添加订单详情信息表
OrderDetail orderDetail = new OrderDetail();
orderDetail.setOrderId(orderInfo.getId());
orderDetail.setUserId(userId);
orderDetail.setCourseId(courseId);
orderDetail.setCourseName(course.getTitle());
orderDetail.setCover(course.getCover());
orderDetail.setOriginAmount(course.getPrice());
orderDetail.setCouponReduce(new BigDecimal(0));
orderDetail.setFinalAmount(orderDetail.getOriginAmount().subtract(orderDetail.getCouponReduce()));
orderDetailService.save(orderDetail);
// 7、更新优惠券状态
if (null != orderFormVo.getCouponUseId())
{
couponInfoFeignClient.updateCouponInfoUseStatus(orderFormVo.getCouponUseId(), orderInfo.getId());
}
// 8、返回订单 id
return orderInfo.getId();
}
}
4、微信支付
4.1、微信支付
接口文档:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_1
4.2、公众号配置
(1)绑定域名。
与微信分享一致
先登录微信公众平台进入“设置与开发”,“公众号设置”的“功能设置”里填写“JS 接口安全域名”。
说明:因为测试号不支持支付功能,需要使用正式号才能进行测试。
(2)商户平台配置支付目录。
4.3、创建订单支付接口
(1)创建 WXPayController。
package com.myxh.smart.planet.order.api;
import com.myxh.smart.planet.order.service.WXPayService;
import com.myxh.smart.planet.result.Result;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
/**
* @author MYXH
* @date 2023/10/21
*/
@Tag(name = "微信支付 API", description = "微信支付 API 接口")
@RestController
@RequestMapping("/api/order/wx/pay")
public class WXPayController
{
@Autowired
private WXPayService wxPayService;
/**
* 微信下单支付
*
* @param orderNo 订单号
* @return Result 全局统一返回结果
*/
@Operation(summary = "微信下单支付", description = "微信下单支付")
@GetMapping("/create/js/api/{orderNo}")
public Result<Map<String, Object>> createJsapi(@Parameter(name = "orderNo", description = "订单号", required = true)
@PathVariable("orderNo") String orderNo)
{
Map<String, Object> map = wxPayService.createJsapi(orderNo);
return Result.ok(map);
}
}
(2)创建 WXPayService。
package com.myxh.smart.planet.order.service;
import java.util.Map;
/**
* @author MYXH
* @date 2023/10/21
*/
public interface WXPayService
{
/**
* 微信下单支付
*
* @param orderNo 订单号
* @return result 结果
*/
Map<String, Object> createJsapi(String orderNo);
}
(3)service-order 引入依赖。
<!-- wxpay-sdk -->
<dependency>
<groupId>com.github.wxpay</groupId>
<artifactId>wxpay-sdk</artifactId>
<version>0.0.3</version>
</dependency>
(4)创建 WXPayServiceImpl。
package com.myxh.smart.planet.order.service.impl;
import com.github.wxpay.sdk.WXPayUtil;
import com.myxh.smart.planet.client.user.UserInfoFeignClient;
import com.myxh.smart.planet.exception.SmartPlanetException;
import com.myxh.smart.planet.order.HttpClientUtils;
import com.myxh.smart.planet.order.service.OrderInfoService;
import com.myxh.smart.planet.order.service.WXPayService;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* @author MYXH
* @date 2023/10/21
*/
@Service
public class WXPayServiceImpl implements WXPayService
{
@Autowired
private OrderInfoService orderInfoService;
@Resource
private UserInfoFeignClient userInfoFeignClient;
/**
* 微信下单支付
*
* @param orderNo 订单号
* @return result 结果
*/
@Override
public Map<String, Object> createJsapi(String orderNo)
{
try
{
// 封装微信支付需要参数,使用 map 集合
Map<String, String> paramMap = new HashMap<>();
// 1、设置参数
// 正式服务公众号商户id = wxf913bfa3a2c7eeeb(固定值)
paramMap.put("appid", "wxf913bfa3a2c7eeeb");
// 正式服务公众号商户号 = 1481962542(固定值)
paramMap.put("mch_id", "1481962542");
paramMap.put("nonce_str", WXPayUtil.generateNonceStr());
paramMap.put("body", "test");
paramMap.put("out_trade_no", orderNo);
// 为了参数,支付 0.01 元
paramMap.put("total_fee", "1");
paramMap.put("spbill_create_ip", "127.0.0.1");
paramMap.put("notify_url", "http://smartplanet.free.idcfengye.com/api/order/wx/pay/notify");
// 支付类型,按照生成固定金额支付
paramMap.put("trade_type", "JSAPI");
/*
设置参数值当前微信用户 openid
现实实现逻辑:第一步、根据订单号获取 userid
第二步、根据 userid 获取 openid
因为当前使用测试号,测试号不支持支付功能,为了使用正式服务公众号进行测试,使用下面写法
获取正式服务公众号微信 openid
通过其他方式获取正式服务公众号 openid,直接设置
*/
// 正式服务公众号 openid = oQTXC56lAy3xMOCkKCImHtHoLL(不定值)
paramMap.put("openid", "oQTXC56lAy3xMOCkKCImHtHoLL");
// 2、HTTPClient 来根据 URL 访问第三方接口并且传递参数,调用微信支付接口
HttpClientUtils client = new HttpClientUtils("https://api.mch.weixin.qq.com/pay/unifiedorder");
// client 设置请求参数
// 正式服务公众号商户 key = MXb72b9RfshXZD4FRGV5KLqmv5bx9LT9
String paramXml = WXPayUtil.generateSignedXml(paramMap, "MXb72b9RfshXZD4FRGV5KLqmv5bx9LT9");
client.setXmlParam(paramXml);
client.setHttps(true);
// 发送请求
client.post();
// 3、微信支付接口返回第三方的数据
String contentXml = client.getContent();
System.out.println("contentXml = " + contentXml);
Map<String, String> resultMap = WXPayUtil.xmlToMap(contentXml);
if ((resultMap.get("result_code") != null) && !"SUCCESS".equals(resultMap.get("result_code")))
{
throw new SmartPlanetException(20001, "支付失败");
}
// 4、再次封装参数
Map<String, String> parameterMap = new HashMap<>();
String prepayId = String.valueOf(resultMap.get("prepay_id"));
String packages = "prepay_id=" + prepayId;
parameterMap.put("appId", "wxf913bfa3a2c7eeeb");
parameterMap.put("nonceStr", resultMap.get("nonce_str"));
parameterMap.put("package", packages);
parameterMap.put("signType", "MD5");
parameterMap.put("timeStamp", String.valueOf(new Date().getTime()));
String sign = WXPayUtil.generateSignature(parameterMap, "MXb72b9RfshXZD4FRGV5KLqmv5bx9LT9");
// 返回结果
Map<String, Object> result = new HashMap<>();
result.put("appId", "wxf913bfa3a2c7eeeb");
result.put("timeStamp", parameterMap.get("timeStamp"));
result.put("nonceStr", parameterMap.get("nonceStr"));
result.put("signType", "MD5");
result.put("paySign", sign);
result.put("package", packages);
System.out.println("result = " + result);
return result;
}
catch (Exception e)
{
e.printStackTrace();
return new HashMap<>();
}
}
}
4.4、服务号测试过程
(1)修改 service-user 模块配置文件。
# 公众号 id 和秘钥
# 智慧星球微信公众平台 appId
# wechat.appId=wxc23b80b9ffaac7bd
# 智慧星球微信公众平台 api 秘钥
# wechat.appSecret=5c0271622c4271753310c436b5cd3532
# 公众号 id 和秘钥(测试微信支付功能)
# 硅谷课堂微信公众平台 appId
wechat.appId=wxf913bfa3a2c7eeeb
# 硅谷课堂微信公众平台 api 秘钥
wechat.appSecret=cd360d429e5c8db0c638d5ef9df74f6d
(2)service-user 模块创建 controller。
package com.myxh.smart.planet.user.api;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import me.chanjar.weixin.common.api.WxConsts;
import me.chanjar.weixin.common.bean.oauth2.WxOAuth2AccessToken;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.mp.api.WxMpService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
/**
* @author MYXH
* @date 2023/10/21
*/
@Tag(name = "微信用户 API(测试微信支付功能)", description = "微信用户 API 接口(测试微信支付功能)")
@Controller
@RequestMapping("/api/user/openid")
public class GetOpenIdController
{
@Autowired
private WxMpService wxMpService;
/**
* 授权跳转
*
* @param returnUrl 返回 url
* @param request 请求
* @return redirectURL 重定向 url
*/
@GetMapping("/authorize")
public String authorize(@RequestParam("returnUrl") String returnUrl, HttpServletRequest request)
{
String userInfoUrl = "http://smartplanet.free.idcfengye.com/api/user/openid/user/info";
String redirectURL = wxMpService
.getOAuth2Service().buildAuthorizationUrl(userInfoUrl,
WxConsts.OAuth2Scope.SNSAPI_USERINFO,
URLEncoder.encode(returnUrl.replace("#", "smartplanet"), StandardCharsets.UTF_8));
return "redirect:" + redirectURL;
}
/**
* 获取用户信息
*
* @param code 密码
* @param returnUrl 返回 url
* @return redirectURL 重定向 url
*/
@GetMapping("/user/info")
@ResponseBody
public String userInfo(@RequestParam("code") String code,
@RequestParam("state") String returnUrl)
{
try
{
// 拿着 code 发送请求
WxOAuth2AccessToken wxOAuth2AccessToken = null;
wxOAuth2AccessToken = this.wxMpService.getOAuth2Service().getAccessToken(code);
// 获取 openId
String openId = wxOAuth2AccessToken.getOpenId();
System.out.println("正式服务公众号微信网页授权 openId = " + openId);
return openId;
}
catch (WxErrorException e)
{
e.printStackTrace();
}
return null;
}
}
(3)修改前端 App.vue。
<template>
<div id="app">
<div id="nav">
<!-- <router-link to="/">列表页</router-link> | -->
<!-- <router-link to="/info">详情页</router-link> | -->
<!-- <router-link to="/list">列表页</router-link> | -->
<!-- <router-link to="/order">下单页</router-link> -->
<van-button round block type="info" @click="clearData"
>清空 localStorage</van-button
>
</div>
<router-view />
</div>
</template>
<script>
import userInfoAPI from "@/api/userInfo";
export default {
data() {
return {
show: true,
};
},
created() {
// 处理微信授权登录
this.wechatLogin();
},
methods: {
wechatLogin() {
// 处理微信授权登录
let token = this.getQueryString("token") || "";
if (token != "") {
window.localStorage.setItem("token", token);
}
// 所有页面都必须登录,两次调整登录,这里与接口返回 208 状态
token = window.localStorage.getItem("token") || "";
if (token == "") {
let url = window.location.href.replace("#", "smartplanet");
/*
window.location =
"http://smartplanet.free.idcfengye.com/api/user/wechat/authorize?returnUrl=" +
url;
*/
// 测试微信支付功能
window.location =
"http://smartplanet.free.idcfengye.com/api/user/openid/authorize?returnUrl=" +
url;
}
console.log("token:" + window.localStorage.getItem("token"));
//绑定手机号处理
/*
if (token != "") {
this.bindPhone();
}
*/
},
bindPhone() {
let userInfoString = window.localStorage.getItem("userInfo") || "";
alert("userInfoString:" + userInfoString);
if (userInfoString != "") {
alert("userInfoString:" + userInfoString);
let userInfo = JSON.parse(userInfoString);
let phone = userInfo.phone || "";
if (phone == "") {
this.$router.push({ path: "/bind/first" });
}
} else {
alert("userInfoString:" + userInfoString);
userInfoAPI.getCurrentUserInfo().then((response) => {
window.localStorage.setItem(
"userInfo",
JSON.stringify(response.data)
);
alert("data:" + JSON.stringify(response.data));
let phone = response.data.phone || "";
console.log("phone:" + phone);
if (phone == "") {
this.$router.push({ path: "/bind/first" });
}
});
}
},
getQueryString(paramName) {
if (window.location.href.indexOf("?") == -1) return "";
let searchString = window.location.href.split("?")[1];
let i,
val,
params = searchString.split("&");
for (i = 0; i < params.length; i++) {
val = params[i].split("=");
if (val[0] == paramName) {
return val[1];
}
}
return "";
},
clearData() {
window.localStorage.setItem("token", "");
window.localStorage.setItem("userInfo", "");
let token = window.localStorage.getItem("token");
alert("token:" + token);
},
},
};
</script>
<style lang="scss">
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: #2c3e50;
}
</style>
(4)复制返回的 openid 到支付接口中测试。
4.5、整合点播视频支付前端
(1)trade.vue
<template>
<template>
<div>
<van-image width="100%" height="200" :src="courseVo.cover" />
<h1 class="van-ellipsis course_title">{{ courseVo.title }}</h1>
<div class="course_teacher_price_box">
<div class="course_teacher_price">
<div class="course_price">价格:</div>
<div class="course_price_number">¥{{ courseVo.price }}</div>
</div>
</div>
<div class="course_teacher_price_box">
<div class="course_teacher_box">
<div class="course_teacher">教师: {{ teacher.name }}</div>
<van-image :src="teacher.avatar" round width="50px" height="50px" />
</div>
</div>
<van-loading vertical="true" v-show="loading">加载中...</van-loading>
<div
style="
position: fixed;
left: 0px;
bottom: 50px;
width: 100%;
height: 50px;
z-index: 999;
"
>
<!-- 优惠券单元格 -->
<van-coupon-cell
:coupons="coupons"
:chosen-coupon="chosenCoupon"
@click="showList = true"
/>
<!-- 优惠券列表 -->
<van-popup
v-model="showList"
round
position="bottom"
style="height: 90%; padding-top: 4px"
>
<van-coupon-list
:coupons="coupons"
:chosen-coupon="chosenCoupon"
:disabled-coupons="disabledCoupons"
@change="onChange"
/>
</van-popup>
</div>
<van-goods-action>
<van-submit-bar
:price="finalAmount"
button-text="确认下单"
@submit="sureOrder"
/>
</van-goods-action>
</div>
</template>
<script>
import courseAPI from "@/api/course";
import orderAPI from "@/api/order";
import couponAPI from "@/api/coupon";
export default {
data() {
return {
loading: false,
courseId: null,
courseVo: {},
teacher: {},
orderId: null,
showList: false,
chosenCoupon: -1,
coupons: [],
disabledCoupons: [],
couponId: null,
couponUseId: null,
couponReduce: 0,
finalAmount: 0,
};
},
created() {
this.courseId = this.$route.params.courseId;
this.fetchData();
this.getCouponInfo();
},
methods: {
onChange(index) {
debugger;
this.showList = false;
this.chosenCoupon = index;
this.couponId = this.coupons[index].id;
this.couponUseId = this.coupons[index].couponUseId;
this.couponReduce = this.coupons[index].value;
this.finalAmount =
parseFloat(this.finalAmount) - parseFloat(this.couponReduce);
},
fetchData() {
this.loading = true;
courseAPI.getInfo(this.courseId).then((response) => {
// console.log(response.data);
this.courseVo = response.data.courseVo;
this.teacher = response.data.teacher;
// 转换为分
this.finalAmount = parseFloat(this.courseVo.price) * 100;
this.loading = false;
});
},
getCouponInfo() {
couponAPI.findCouponInfo().then((response) => {
// console.log(response.data);
this.coupons = response.data.abledCouponsList;
this.disabledCoupons = response.data.disabledCouponsList;
});
},
sureOrder() {
this.loading = true;
let orderFormVo = {
courseId: this.courseId,
couponId: this.couponId,
couponUseId: this.couponUseId,
};
orderAPI.submitOrder(orderFormVo).then((response) => {
console.log(response.data);
this.$router.push({ path: "/pay/" + response.data });
});
},
},
};
</script>
<style lang="scss" scoped>
.gap {
height: 10px;
}
::v-deep.van-image {
display: block;
}
.course_count {
background-color: #82848a;
color: white;
padding: 5px;
text-align: center;
border-right: 1px solid #939393;
h1 {
font-size: 14px;
margin: 0;
}
p {
margin: 0;
font-size: 16px;
}
}
.course_title {
font-size: 20px;
margin: 10px;
}
.course_teacher_price_box {
margin: 10px;
display: flex;
justify-content: space-between;
align-items: center;
.course_teacher_price {
display: flex;
font-size: 14px;
align-items: center;
.course_price_number {
color: red;
font-size: 18px;
font-weight: bold;
}
}
.course_teacher_box {
display: flex;
justify-content: center;
align-items: center;
.course_teacher {
margin-right: 20px;
}
}
}
.course_contents {
margin: 10px;
.course_title_font {
color: #68cb9b;
font-weight: bold;
}
.course_content {
margin-bottom: 20px;
}
}
.course_chapter_list {
display: flex;
justify-content: space-between;
align-items: center;
h2 {
font-size: 14px;
}
p {
margin: 0;
}
}
</style></template
>
(2)pay.vue
<template>
<div>
<head>
<!-- 设置 referrer 为 no-referrer,用于绕过防盗链限制,从而正常显示图片 -->
<meta name="referrer" content="no-referrer" />
</head>
<van-image
width="100%"
height="200"
src="https://img-blog.csdnimg.cn/b9f45932f6cf471aa53ee24e5555d9c2.png"
/>
<h1 class="van-ellipsis course_title">
课程名称: {{ orderInfo.courseName }}
</h1>
<div class="course_teacher_price_box">
<div class="course_price">订单号:{{ orderInfo.outTradeNo }}</div>
</div>
<div class="course_teacher_price_box">
<div class="course_price">下单时间:{{ orderInfo.createTime }}</div>
</div>
<div class="course_teacher_price_box">
<div class="course_price">
支付状态:{{ orderInfo.orderStatus == "0" ? "未支付" : "已支付" }}
</div>
</div>
<div class="course_teacher_price_box" v-if="orderInfo.orderStatus == '1'">
<div class="course_price">支付时间:{{ orderInfo.payTime }}</div>
</div>
<van-divider />
<div class="course_teacher_price_box">
<div class="course_price">
订单金额:<span style="color: red">¥{{ orderInfo.originAmount }}</span>
</div>
</div>
<div class="course_teacher_price_box">
<div class="course_price">
优惠券金额:<span style="color: red"
>¥{{ orderInfo.couponReduce }}</span
>
</div>
</div>
<div class="course_teacher_price_box">
<div class="course_price">
支付金额:<span style="color: red">¥{{ orderInfo.finalAmount }}</span>
</div>
</div>
<van-goods-action>
<van-goods-action-button
type="danger"
text="支付"
@click="pay"
v-if="orderInfo.orderStatus == '0'"
/>
<van-goods-action-button
type="warning"
text="去观看"
@click="see"
v-else
/>
</van-goods-action>
<van-loading vertical="true" v-show="loading">加载中...</van-loading>
</div>
</template>
<script>
import orderAPI from "@/api/order";
export default {
data() {
return {
loading: false,
orderId: null,
orderInfo: {},
showList: false,
chosenCoupon: -1,
coupons: [],
disabledCoupons: [],
couponReduce: 0,
finalAmount: 0,
};
},
created() {
this.orderId = this.$route.params.orderId;
this.fetchData();
},
methods: {
fetchData() {
this.loading = true;
orderAPI.getInfo(this.orderId).then((response) => {
this.orderInfo = response.data;
// alert("orderStatus:" + this.orderInfo.orderStatus);
this.finalAmount = parseFloat(this.orderInfo.finalAmount) * 100;
this.loading = false;
});
},
pay() {
this.loading = true;
orderAPI.createJsapi(this.orderInfo.outTradeNo).then((response) => {
console.log(response.data);
this.loading = false;
this.onBridgeReady(response.data);
});
},
onBridgeReady(data) {
let that = this;
console.log(data);
WeixinJSBridge.invoke(
"getBrandWCPayRequest",
{
//公众号 ID,由商户传入
appId: data.appId,
// 时间戳,自 1970 年以来的秒数
timeStamp: data.timeStamp,
// 随机串
nonceStr: data.nonceStr,
package: data.package,
// 微信签名方式
signType: data.signType,
// 微信签名
paySign: data.paySign,
},
function (res) {
if (res.err_msg == "get_brand_wcpay_request:ok") {
// 使用以上方式判断前端返回,微信团队郑重提示:
// res.err_msg 将在用户支付成功后返回 ok,但并不保证它绝对可靠。
console.log("支付成功");
that.queryPayStatus();
}
}
);
},
queryPayStatus() {
// 回调查询
orderAPI.queryPayStatus(this.orderInfo.outTradeNo).then((response) => {
console.log(response.data);
this.fetchData();
});
},
see() {
this.$router.push({ path: "/course/info/" + this.orderInfo.courseId });
},
},
};
</script>
<style lang="scss" scoped>
.gap {
height: 10px;
}
::v-deep.van-image {
display: block;
}
.course_count {
background-color: #82848a;
color: white;
padding: 5px;
text-align: center;
border-right: 1px solid #939393;
h1 {
font-size: 14px;
margin: 0;
}
p {
margin: 0;
font-size: 16px;
}
}
.course_title {
font-size: 20px;
margin: 10px;
}
.course_teacher_price_box {
margin: 10px;
display: flex;
justify-content: space-between;
align-items: center;
.course_teacher_price {
display: flex;
font-size: 14px;
align-items: center;
.course_price_number {
color: red;
font-size: 18px;
font-weight: bold;
}
}
.course_teacher_box {
display: flex;
justify-content: center;
align-items: center;
.course_teacher {
margin-right: 20px;
}
}
}
.course_contents {
margin: 10px;
.course_title_font {
color: #68cb9b;
font-weight: bold;
}
.course_content {
margin-bottom: 20px;
}
}
.course_chapter_list {
display: flex;
justify-content: space-between;
align-items: center;
h2 {
font-size: 14px;
}
p {
margin: 0;
}
}
</style>
4.6、订单详情接口
(1)OrderInfoApiController 添加方法。
package com.myxh.smart.planet.order.api;
import com.myxh.smart.planet.order.service.OrderInfoService;
import com.myxh.smart.planet.result.Result;
import com.myxh.smart.planet.vo.order.OrderInfoVo;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author MYXH
* @date 2023/10/21
*/
@Tag(name = "订单信息 API", description = "订单信息 API 接口")
@RestController
@RequestMapping("api/order/order/info")
public class OrderInfoApiController
{
@Autowired
private OrderInfoService orderInfoService;
/**
* 根据订单 id 获取订单信息
*
* @param id 订单 id
* @return Result 全局统一返回结果
*/
@Operation(summary = "根据订单 id 获取订单信息", description = "根据订单 id 获取订单信息")
@GetMapping("get/info/{id}")
public Result<OrderInfoVo> getInfo(@PathVariable("id") Long id)
{
OrderInfoVo orderInfoVo = orderInfoService.getOrderInfoVoById(id);
return Result.ok(orderInfoVo);
}
}
(2)OrderInfoServiceImpl 实现方法。
package com.myxh.smart.planet.order.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.myxh.smart.planet.model.order.OrderDetail;
import com.myxh.smart.planet.model.order.OrderInfo;
import com.myxh.smart.planet.order.mapper.OrderInfoMapper;
import com.myxh.smart.planet.order.service.OrderDetailService;
import com.myxh.smart.planet.order.service.OrderInfoService;
import com.myxh.smart.planet.vo.order.OrderInfoVo;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* @author MYXH
* @date 2023/10/14
*
* <p>
* 订单信息 服务实现类
* </p>
*/
@Service
public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo> implements OrderInfoService
{
@Autowired
private OrderDetailService orderDetailService;
/**
* 根据订单 id 获取订单信息
*
* @param id 订单 id
* @return orderInfoVo 订单信息
*/
@Override
public OrderInfoVo getOrderInfoVoById(Long id)
{
//根据订单 id 查询订单基本信息和详情信息
OrderInfo orderInfo = baseMapper.selectById(id);
OrderDetail orderDetail = orderDetailService.getById(id);
// 封装 OrderInfoVo
OrderInfoVo orderInfoVo = new OrderInfoVo();
BeanUtils.copyProperties(orderInfo, orderInfoVo);
orderInfoVo.setCourseId(orderDetail.getCourseId());
orderInfoVo.setCourseName(orderDetail.getCourseName());
return orderInfoVo;
}
}
4.7、查询支付结果
(1)WXPayController 添加方法。
package com.myxh.smart.planet.order.api;
import com.myxh.smart.planet.order.service.OrderInfoService;
import com.myxh.smart.planet.order.service.WXPayService;
import com.myxh.smart.planet.result.Result;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
/**
* @author MYXH
* @date 2023/10/21
*/
@Tag(name = "微信支付 API", description = "微信支付 API 接口")
@RestController
@RequestMapping("/api/order/wx/pay")
public class WXPayController
{
@Autowired
private WXPayService wxPayService;
@Autowired
private OrderInfoService orderInfoService;
/**
* 查询支付状态
*
* @param orderNo 订单号
* @return Result 全局统一返回结果
*/
@Operation(summary = "查询支付状态", description = "查询支付状态")
@GetMapping("/query/pay/status/{orderNo}")
public Result<Object> queryPayStatus(@Parameter(name = "orderNo", description = "订单号", required = true)
@PathVariable("orderNo") String orderNo)
{
// 根据订单号调用微信接口查询支付状态
Map<String, String> resultMap = wxPayService.queryPayStatus(orderNo);
// 判断支付是否成功,根据微信支付状态接口判断
if (resultMap == null)
{
// 出错
return Result.fail(null).message("支付出错");
}
if ("SUCCESS".equals(resultMap.get("trade_state")))
{
// 成功
// 更改订单状态,处理支付结果
String outTradeNo = resultMap.get("out_trade_no");
System.out.println("out_trade_no = " + outTradeNo);
orderInfoService.updateOrderStatus(outTradeNo);
return Result.ok(null).message("支付成功");
}
return Result.ok(null).message("支付中");
}
}
(2)WXPayServiceImpl 实现方法。
package com.myxh.smart.planet.order.service.impl;
import com.github.wxpay.sdk.WXPayUtil;
import com.myxh.smart.planet.client.user.UserInfoFeignClient;
import com.myxh.smart.planet.order.HttpClientUtils;
import com.myxh.smart.planet.order.service.OrderInfoService;
import com.myxh.smart.planet.order.service.WXPayService;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
/**
* @author MYXH
* @date 2023/10/21
*/
@Service
public class WXPayServiceImpl implements WXPayService
{
@Autowired
private OrderInfoService orderInfoService;
@Resource
private UserInfoFeignClient userInfoFeignClient;
/**
* 根据订单号调用微信接口查询支付状态
*
* @param orderNo 订单号
* @return resultMap 结果
*/
@Override
public Map<String, String> queryPayStatus(String orderNo)
{
try
{
// 1、封装微信接口需要参数,使用 map
Map paramMap = new HashMap<>();
paramMap.put("appid", "wxf913bfa3a2c7eeeb");
paramMap.put("mch_id", "1481962542");
paramMap.put("out_trade_no", orderNo);
paramMap.put("nonce_str", WXPayUtil.generateNonceStr());
// 2、调用接口 Httpclient,设置请求
HttpClientUtils client = new HttpClientUtils("https://api.mch.weixin.qq.com/pay/orderquery");
client.setXmlParam(WXPayUtil.generateSignedXml(paramMap, "MXb72b9RfshXZD4FRGV5KLqmv5bx9LT9"));
client.setHttps(true);
client.post();
// 3、封装返回第三方的数据
String xml = client.getContent();
Map<String, String> resultMap = WXPayUtil.xmlToMap(xml);
// 5、返回结果
return resultMap;
}
catch (Exception e)
{
e.printStackTrace();
}
return null;
}
}
(3)OrderInfoServiceImpl 实现方法。
package com.myxh.smart.planet.order.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.myxh.smart.planet.model.order.OrderInfo;
import com.myxh.smart.planet.order.mapper.OrderInfoMapper;
import com.myxh.smart.planet.order.service.OrderInfoService;
import org.springframework.stereotype.Service;
/**
* @author MYXH
* @date 2023/10/14
*
* <p>
* 订单信息 服务实现类
* </p>
*/
@Service
public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo> implements OrderInfoService
{
/**
* 更改订单状态,处理支付结果
*
* @param outTradeNo 场外交易编号
*/
@Override
public void updateOrderStatus(String outTradeNo)
{
// 根据 订单号查询订单
LambdaQueryWrapper<OrderInfo> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(OrderInfo::getOutTradeNo, outTradeNo);
OrderInfo orderInfo = baseMapper.selectOne(wrapper);
// 更新订单状态,1 已经支付
orderInfo.setOrderStatus("1");
// 调用方法更新
baseMapper.updateById(orderInfo);
}
}
二、直播介绍
1、项目需求
智慧星球会定期推出直播课程,方便学生与名师之间的交流互动,在直播间老师可以推荐点播课程(类似直播带货),学生可以点赞交流,购买推荐的点播课程。
2、了解直播
一个完整直播实现流程:
1.采集、2.滤镜处理、3.编码、4.推流、5.CDN 分发、6.拉流、7.解码、8.播放、9.聊天互动。
2.1、通用直播模型
-
首先是主播方,它是产生视频流的源头,由一系列流程组成:第一,通过一定的设备来采集数据;第二,将采集的这些视频进行一系列的处理,比如水印、美颜和特效滤镜等处理;第三,将处理后的结果视频编码压缩成可观看可传输的视频流;第四,分发推流,即将压缩后的视频流通过网络通道传输出去。
-
其次是播放端,播放端功能有两个层面,第一个层面是关键性的需求;另一层面是业务层面的。先看第一个层面,它涉及到一些非常关键的指标,比如秒开,在很多场景当中都有这样的要求,然后是对于一些重要内容的版权保护。为了达到更好的效果,还需要配合服务端做智能解析,这在某些场景下也是关键性需求。再来看第二个层面也即业务层面的功能,对于一个社交直播产品来说,在播放端,观众希望能够实时的看到主播端推过来的视频流,并且和主播以及其他观众产生一定的互动,因此它可能包含一些像点赞、聊天和弹幕这样的功能,以及礼物这样更高级的道具。
-
要知道,内容产生方和消费方一般都不是一一对应的。对于一个直播产品来讲,最直观的体现就是一个主播可能会有很多粉丝。因此,不能直接让主播端和所有播放端进行点对点通信,这在技术上是做不到或者很有难度。主播方播出的视频到达播放端之前,需要经过一系列的中间环节,也就是这里讲的直播服务器端。
-
直播服务器端提供的最核心功能是收集主播端的视频推流,并将其放大后推送给所有观众端。除了这个核心功能,还有很多运营级别的诉求,比如鉴权认证,视频连线和实时转码,自动鉴黄,多屏合一,以及云端录制存储等功能。另外,对于一个主播端推出的视频流,中间需要经过一些环节才能到达播放端,因此对中间环节的质量进行监控,以及根据这些监控来进行智能调度,也是非常重要的诉求。
-
实际上无论是主播端还是播放端,他们的诉求都不会仅仅是拍摄视频和播放视频这么简单。在这个核心诉求被满足之后,还有很多关键诉求需要被满足。比如,对于一个消费级的直播产品来说,除了这三大模块之外,还需要实现一个业务服务端来进行推流和播放控制,以及所有用户状态的维持。如此,就构成了一个消费级可用的直播产品。
2.2、如何快速开发完整直播
2.2.1、利用第三方 SDK 开发
-
七牛云:七牛直播云是专为直播平台打造的全球化直播流服务和一站式实现 SDK 端到端直播场景的企业级直播云服务平台。
- 熊猫 TV,龙珠 TV 等直播平台都是用的七牛云。
-
网易视频云:基于专业的跨平台视频编解码技术和大规模视频内容分发网络,提供稳定流畅、低延时、高并发的实时音视频服务,可将视频直播无缝对接到自身 App。
-
欢拓云直播平台:欢拓是一家以直播技术为核心的网络平台,旨在帮助人们通过网络也能实现真实互动通讯。
2.2.2、第三方 SDK 好处
-
降低成本:
- 使用好的第三方企业服务,将不用再花费大量的人力物力去研发。
-
提升效率:
- 第三方服务的专注与代码集成所带来的方便,所花费的时间可能仅仅是 1-2 个小时
,节约近 99% 的时间,足够换取更多的时间去和竞争对手斗智斗勇,增加更大的成功可能性。
- 第三方服务的专注与代码集成所带来的方便,所花费的时间可能仅仅是 1-2 个小时
-
降低风险:
- 借助专业的第三方服务,由于它的快速、专业、稳定等特点,能够极大地加强产品的
竞争能力(优质服务、研发速度等),缩短试错时间,必将是创业中保命的手段之一。
- 借助专业的第三方服务,由于它的快速、专业、稳定等特点,能够极大地加强产品的
-
专业的事,找专业的人来做。
- 服务最少是 10-20 人的团队专注地解决同一个问题,做同一件事情。
3、欢拓云直播
根据上面的综合对比和调研,最终选择了“欢拓与直播平台”,它提供了完整的可以直接使用的示例代码,方便开发对接。
欢拓是一家以直播技术为核心的网络平台,旨在帮助人们通过网络也能实现真实互动通讯。从 2010 年开始,欢拓就专注于音频、视频的采样、编码、后处理及智能传输研究,并于 2013 年底正式推出了针对企业/开发者的直播云服务系统,帮助开发者轻松实现真人互动。该系统适用场景包括在线教育、游戏语音、娱乐互动、远程会议(PC、移动均可)等等。针对应用场景,采用先进技术解决方案和产品形态,让客户和客户的用户满意!
官网:https://www.talk-fun.com/
接口文档地址:http://open.talk-fun.com/docs/getstartV2/document.html
三、直播对接
1、直播体验
1.1、开通账号
通过官网:https://www.talk-fun.com/ ,联系客户或 400 电话开通账号,开通 “生活直播” 权限。开通后注意使用有效期,一般一周左右,可以再次申请延期。
说明:官网免费试用,功能有限制,不建议使用
1.2、创建直播
1、在直播管理创建直播。
2、创建直播,选择主播模式。
3、配置直播,可以自行查看。
1.3、开始直播
1、在直播列表,点击“直播入口”。
主播端下载“云直播客户端”,“频道 id 与密码”为直播客户端的登录账号;
下面还有管理员,主播进行直播时,助教可以在聊天时与观众互动。
2、网页端可以“一键开播”。
3、使用“频道 id 与密码”登录。
4、点击“开始直播”,打开摄像头即可开始直播。
1.4、用户观看
1、在直播列表,点击“直播入口”。
2、在观众一栏点击进入,即可在网页端观看直播。
1.5、体验总结
上面的体验完全能够满足业务的需要,智慧星球的需求是定期推出直播课程,方便学生与名师之间的交流互动,在直播间老师可以推荐点播课程(类似直播带货),学生可以点赞交流,购买推荐的点播课程。
直播平台只是做了直播相关的业务,不能与业务进行衔接,期望的是在智慧星球的管理后台管理直播相关的业务,那么怎么做呢?对接直播业务接口,直播平台有对应的直播接口,直接对接即可。
Day 14-直播管理模块
一、后台系统-直播管理
上面已经开通了“生活类直播”。
1、获取 openId 与 openToken
登录进入开放后台,后台首页即可获取 openId 与 openToken。
2、对接说明
1、使用 HTTP 协议进行信息交互,字符编码统一采用 UTF-8。
2、除非特殊说明,接口地址统一为:https://api.talk-fun.com/portal.php
3、除非特殊说明,同时支持 GET 和 POST 两种参数传递方式。
4、除非特殊说明,返回信息支持 JSON 格式。
5、除了 sign 外,其余所有请求参数值都需要进行 URL 编码。
6、参数表中,类型一栏声明的定义为:int 代表整数类型;string 代表字符串类型,如果后面有括号,括号中的数字代表该参数的最大长度;array / object 表示数组类型。
7、openID、openToken 参数的获取见对接流程说明。
3、了解接口文档
接口文档地址:https://open.talk-fun.com/docs/getstartV2/api/live_dir.html
3.1、了解接口文档
根据接口文档,了解需要对接哪些接口。
3.1.1、添加直播
api 名称:course.add
,SDK 对应方法:courseAdd
。
添加直播是一定需要的。
3.1.2、更新直播信息
api 名称:course.update
,SDK 对应方法courseUpdate
。
3.1.3、删除直播信息
api 名称:course.delete
,SDK 对应方法:courseDelete
。
3.1.4、修改生活直播相关配置
api 名称:course.updateLifeConfig
,SDK 对应方法:updateLifeConfig
。
设置功能很多,但是只需要几个即可,这个接口需要做如下设置:
1、界面模式:pageViewMode 界面模式,1 全屏模式 0 二分屏 2 课件模式。
2、观看人数开关:number 观看人数开关;number.enable 是否开启;观看人数,0 否 1 是;示例:{“enable”:“1”}。
3、商城开关(直播推荐课程):goodsListEdit 商品列表编辑,状态 goodsListEdit.status,0 覆盖,1 追加,不传默认为 0;示例:{“status”:1}。
直播设置最终效果:
3.1.5、按照课程 ID 获取访客列表
改接口在:"访客/管理员列表"下面。
通过该接口统计课程观看人数信息。
直播访客 api 名称:course.visitor.list
,SDK 对应方法:courseVisitorList
。
3.2、下载 SDK
直播平台准备了 SDK,直接使用。
下载地址:https://open.talk-fun.com/docs/getstartV2/api/introduce/sdkdownload.html
已下载:当前目录/MTCloud-java-sdk-1.6.zip
5、搭建 service-live 模块
5.1、创建 service-live 模块
5.2、添加依赖
添加直播 SDK 需要的依赖。
<!-- mybatis-plus-generator -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.5.3.1</version>
</dependency>
<!-- freemarker -->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.32</version>
</dependency>
<!-- commons-httpclient -->
<dependency>
<groupId>commons-httpclient</groupId>
<artifactId>commons-httpclient</artifactId>
<version>3.0.1</version>
</dependency>
<!-- json-lib -->
<dependency>
<groupId>net.sf.json-lib</groupId>
<artifactId>json-lib</artifactId>
<version>2.4</version>
<classifier>jdk15</classifier>
</dependency>
5.3、集成代码
解压 MTCloud-java-sdk-1.6.zip,复制 MTCloud-java-sdk-1.6\MTCloud_java\src\com\mtcloud\sdk 下面的 java 文件到 com.myxh.smart.planet.live.mtcloud 包下,如图。
5.4、更改配置
更改 MTCloud 类配置。
说明:
1、更改 openID 与 openToken。
2、该类官方已经做了接口集成,可以直接使用。
/**
* Copyright www.talk-fun.com
*/
package com.myxh.smart.planet.live.mtcloud;
import org.springframework.beans.factory.annotation.Value;
/**
* @author MYXH
* @date 2023/10/23
*/
public class MTCloud
{
/**
* 合作方 ID:合作方在欢拓平台的唯一 ID
*/
@Value("${mtcloud.openId}")
public String openID = "";
/**
* 合作方秘钥:合作方 ID 对应的参数加密秘钥
*/
@Value("${mtcloud.openToken}")
public String openToken = "";
}
5.5、创建配置文件和启动类
(1)application.properties
# 服务端口
server.port=8306
# 服务名
spring.application.name=service-live
# 环境设置:dev、test、prod
spring.profiles.active=dev
# MySQL 数据库连接
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/smart_planet_live?characterEncoding=utf-8&useSSL=false
spring.datasource.username=MYXH
spring.datasource.password=520.ILY!
# 返回 Json 的全局时间格式
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8
# MyBatis 日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
# 设置 mapper.xml 的路径
mybatis-plus.mapper-locations=classpath:com/myxh/smart/planet/live/mapper/xml/*.xml
# nacos 服务地址
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
# 设置 MTCloud 类的配置 openId 与 openToken
mtcloud.openId=61237
mtcloud.openToken=c2c502add3dabfbbacae160432ccb783
(2)启动类。
package com.myxh.smart.planet.live;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.transaction.annotation.EnableTransactionManagement;
/**
* @author MYXH
* @date 2023/10/26
*/
@SpringBootApplication
@EnableTransactionManagement
@EnableDiscoveryClient
@EnableFeignClients(basePackages = "com.myxh.smart.planet")
@MapperScan("com.myxh.smart.planet.live.mapper")
public class ServiceLiveApplication
{
public static void main(String[] args)
{
SpringApplication.run(ServiceLiveApplication.class, args);
}
}
5.6、生成相关代码
6、功能实现-直播课程列表接口
根据直播平台与自身业务设计直播相关的业务表,如:smart_planet_live。
6.1、LiveCourseController 类
package com.myxh.smart.planet.live.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.myxh.smart.planet.live.service.LiveCourseService;
import com.myxh.smart.planet.model.live.LiveCourse;
import com.myxh.smart.planet.result.Result;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author MYXH
* @date 2023/10/26
*
* <p>
* 直播课程 前端控制器
* </p>
*/
@Tag(name = "直播课程管理", description = "直播课程管理接口")
@RestController
@RequestMapping("/admin/live/live/course")
public class LiveCourseController
{
@Autowired
private LiveCourseService liveCourseService;
/**
* 条件查询直播课程分页列表
*
* @param current 当前页码
* @param limit 每页记录数
* @return Result 全局统一返回结果
*/
@Operation(summary = "条件查询直播课程分页列表", description = "条件查询直播课程分页列表")
@GetMapping("find/query/page/{current}/{limit}")
public Result<IPage<LiveCourse>> index(
@Parameter(name = "current", description = "当前页码", required = true)
@PathVariable("current") Long current,
@Parameter(name = "limit", description = "每页记录数", required = true)
@PathVariable("limit") Long limit)
{
Page<LiveCourse> LiveCoursePageParam = new Page<>(current, limit);
IPage<LiveCourse> LiveCoursePageModel = liveCourseService.selectPage(LiveCoursePageParam);
return Result.ok(LiveCoursePageModel);
}
}
6.2、LiveCourseService 接口
package com.myxh.smart.planet.live.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.myxh.smart.planet.model.live.LiveCourse;
/**
* @author MYXH
* @date 2023/10/26
*
* <p>
* 直播课程 服务类
* </p>
*/
public interface LiveCourseService extends IService<LiveCourse>
{
/**
* 条件查询直播课程分页列表
*
* @param liveCoursePageParam 直播课程页面参数
* @return LiveCoursePageModel 直播课程页面
*/
IPage<LiveCourse> selectPage(Page<LiveCourse> liveCoursePageParam);
}
6.3、service-vod 模块创建接口
(1)获取讲师信息。
package com.myxh.smart.planet.vod.controller;
import com.myxh.smart.planet.model.vod.Teacher;
import com.myxh.smart.planet.vod.service.TeacherService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author MYXH
* @date 2023/9/29
*
* <p>
* 教师 前端控制器
* </p>
*/
@Tag(name = "教师接口", description = "教师管理接口")
@RestController
@RequestMapping("/admin/vod/teacher")
public class TeacherController
{
@Autowired
private TeacherService teacherService;
/**
* 根据 id 查询教师
*
* @param id id
* @return teacher 教师
*/
@Operation(summary = "查询", description = "根据 id 查询教师")
@GetMapping("inner/get/teacher/{id}")
public Teacher getTeacherLive(@Parameter(name = "id", description = "ID", required = true) @PathVariable("id") Long id)
{
Teacher teacher = teacherService.getById(id);
return teacher;
}
}
(2)service-course-client 定义接口。
package com.myxh.smart.planet.client.course;
import com.myxh.smart.planet.model.vod.Teacher;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
/**
* @author MYXH
* @date 2023/10/17
*/
@FeignClient("service-vod")
public interface CourseFeignClient
{
@Operation(summary = "查询", description = "根据 id 查询教师")
@GetMapping("admin/vod/teacher/inner/get/teacher/{id}")
Teacher getTeacherLive(@Parameter(name = "id", description = "ID", required = true) @PathVariable("id") Long id);
}
6.4、service-live 引入依赖
<!-- service-course-client -->
<dependency>
<groupId>com.myxh.smart.planet</groupId>
<artifactId>service-course-client</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!-- service-user-client -->
<dependency>
<groupId>com.myxh.smart.planet</groupId>
<artifactId>service-user-client</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
6.5、LiveCourseServiceImpl 实现
package com.myxh.smart.planet.live.service.impl;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.myxh.smart.planet.client.course.CourseFeignClient;
import com.myxh.smart.planet.live.mapper.LiveCourseMapper;
import com.myxh.smart.planet.live.service.LiveCourseService;
import com.myxh.smart.planet.model.live.LiveCourse;
import com.myxh.smart.planet.model.vod.Teacher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* @author MYXH
* @date 2023/10/26
*
* <p>
* 直播课程 服务实现类
* </p>
*/
@Service
public class LiveCourseServiceImpl extends ServiceImpl<LiveCourseMapper, LiveCourse> implements LiveCourseService
{
@Autowired
private CourseFeignClient teacherFeignClient;
/**
* 条件查询直播课程分页列表
*
* @param liveCoursePageParam 直播课程页面参数
* @return LiveCoursePageModel 直播课程页面
*/
@Override
public IPage<LiveCourse> selectPage(Page<LiveCourse> liveCoursePageParam)
{
// 分页查询
IPage<LiveCourse> LiveCoursePageModel = baseMapper.selectPage(liveCoursePageParam, null);
// 获取课程教师信息
List<LiveCourse> liveCourseList = LiveCoursePageModel.getRecords();
// 遍历获取直播课程 List 集合
for (LiveCourse liveCourse : liveCourseList)
{
// 获取每个课程教师 id
Long teacherId = liveCourse.getTeacherId();
// 根据教师 id 查询教师信息
Teacher teacher = teacherFeignClient.getTeacherLive(teacherId);
// 进行封装
liveCourse.getParam().put("teacherName", teacher.getName());
liveCourse.getParam().put("teacherLevel", teacher.getLevel());
}
return LiveCoursePageModel;
}
}
7、功能实现-直播课程添加接口
7.1、添加工具类
(1)MTCloudAccountConfig 类。
package com.myxh.smart.planet.live.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* @author MYXH
* @date 2023/10/26
*/
@Data
@Component
@ConfigurationProperties(prefix = "mtcloud")
public class MTCloudAccountConfig
{
private String openId;
private String openToken;
}
(2)MTCloudConfig 类。
package com.myxh.smart.planet.live.config;
import com.myxh.smart.planet.live.mtcloud.MTCloud;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
/**
* @author MYXH
* @date 2023/10/26
*/
@Component
public class MTCloudConfig
{
@Autowired
private MTCloudAccountConfig mtCloudAccountConfig;
@Bean
public MTCloud mtCloudClient()
{
return new MTCloud(mtCloudAccountConfig.getOpenId(), mtCloudAccountConfig.getOpenToken());
}
}
7.2、LiveCourseController 类
package com.myxh.smart.planet.live.controller;
import com.myxh.smart.planet.live.service.LiveCourseService;
import com.myxh.smart.planet.result.Result;
import com.myxh.smart.planet.vo.live.LiveCourseFormVo;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author MYXH
* @date 2023/10/26
*
* <p>
* 直播课程 前端控制器
* </p>
*/
@Tag(name = "直播课程管理", description = "直播课程管理接口")
@RestController
@RequestMapping("/admin/live/live/course")
public class LiveCourseController
{
@Autowired
private LiveCourseService liveCourseService;
/**
* 添加直播课程
*
* @param liveCourseFormVo 直播课程
* @return Result 全局统一返回结果
*/
@Operation(summary = "添加直播课程", description = "添加直播课程")
@PostMapping("save")
public Result<Void> save(@RequestBody LiveCourseFormVo liveCourseFormVo)
{
liveCourseService.saveLive(liveCourseFormVo);
return Result.ok(null);
}
}
7.3、LiveCourseService 接口
package com.myxh.smart.planet.live.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.myxh.smart.planet.model.live.LiveCourse;
import com.myxh.smart.planet.vo.live.LiveCourseFormVo;
/**
* @author MYXH
* @date 2023/10/26
*
* <p>
* 直播课程 服务类
* </p>
*/
public interface LiveCourseService extends IService<LiveCourse>
{
/**
* 添加直播课程
*
* @param liveCourseFormVo 直播课程
*/
void saveLive(LiveCourseFormVo liveCourseFormVo);
}
7.4、LiveCourseServiceImpl 实现
package com.myxh.smart.planet.live.service.impl;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.myxh.smart.planet.client.course.CourseFeignClient;
import com.myxh.smart.planet.exception.SmartPlanetException;
import com.myxh.smart.planet.live.mapper.LiveCourseMapper;
import com.myxh.smart.planet.live.mtcloud.CommonResult;
import com.myxh.smart.planet.live.mtcloud.MTCloud;
import com.myxh.smart.planet.live.service.LiveCourseAccountService;
import com.myxh.smart.planet.live.service.LiveCourseDescriptionService;
import com.myxh.smart.planet.live.service.LiveCourseService;
import com.myxh.smart.planet.model.live.LiveCourse;
import com.myxh.smart.planet.model.live.LiveCourseAccount;
import com.myxh.smart.planet.model.live.LiveCourseDescription;
import com.myxh.smart.planet.model.vod.Teacher;
import com.myxh.smart.planet.vo.live.LiveCourseFormVo;
import lombok.SneakyThrows;
import org.joda.time.DateTime;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.HashMap;
/**
* @author MYXH
* @date 2023/10/26
*
* <p>
* 直播课程 服务实现类
* </p>
*/
@Service
public class LiveCourseServiceImpl extends ServiceImpl<LiveCourseMapper, LiveCourse> implements LiveCourseService
{
@Autowired
private CourseFeignClient teacherFeignClient;
@Autowired
private MTCloud mtCloudClient;
@Autowired
private LiveCourseDescriptionService liveCourseDescriptionService;
@Autowired
private LiveCourseAccountService liveCourseAccountService;
/**
* 添加直播课程
*
* @param liveCourseFormVo 直播课程
*/
@SneakyThrows
@Transactional(rollbackFor = {Exception.class})
@Override
public void saveLive(LiveCourseFormVo liveCourseFormVo)
{
LiveCourse liveCourse = new LiveCourse();
BeanUtils.copyProperties(liveCourseFormVo, liveCourse);
// 获取教师信息
Teacher teacher = teacherFeignClient.getTeacherLive(liveCourseFormVo.getTeacherId());
// 调用方法添加直播课程
// 创建 map 集合,封装直播课程其他参数
HashMap<Object, Object> options = new HashMap<>();
// 直播类型,1:教育直播,2:生活直播
// 默认 1,说明:根据平台开通的直播类型填写
options.put("scenes", 1);
options.put("password", liveCourseFormVo.getPassword());
/*
course_name 课程名称
bid 发起直播课程的主播账号
start_time 课程开始时间,格式:2015-01-10 12:00:00
end_time 课程结束时间,格式:2015-01-10 13:00:00
nickname 昵称
accountIntro 主播介绍
options 其他参数
*/
String res = mtCloudClient.courseAdd(liveCourse.getCourseName(),
teacher.getId().toString(),
new DateTime(liveCourse.getStartTime()).toString("yyyy-MM-dd HH:mm:ss"),
new DateTime(liveCourse.getEndTime()).toString("yyyy-MM-dd HH:mm:ss"),
teacher.getName(),
teacher.getIntro(),
options);
System.out.println("res = " + res);
CommonResult<JSONObject> commonResult = JSON.parseObject(res, CommonResult.class);
// 把创建之后返回结果进行判断
if (Integer.parseInt(commonResult.getCode()) == MTCloud.CODE_SUCCESS)
{
// 添加直播基本信息
JSONObject object = commonResult.getData();
// 直播课程 id
Long courseId = object.getLong("course_id");
liveCourse.setCourseId(courseId);
baseMapper.insert(liveCourse);
// 添加直播描述信息
LiveCourseDescription liveCourseDescription = new LiveCourseDescription();
liveCourseDescription.setLiveCourseId(liveCourse.getId());
liveCourseDescription.setDescription(liveCourseFormVo.getDescription());
liveCourseDescriptionService.save(liveCourseDescription);
// 添加直播账号信息
LiveCourseAccount liveCourseAccount = new LiveCourseAccount();
liveCourseAccount.setLiveCourseId(liveCourse.getId());
liveCourseAccount.setAnchorAccount(object.getString("bid"));
liveCourseAccount.setAnchorPassword(liveCourseFormVo.getPassword());
liveCourseAccount.setAdminKey(object.getString("admin_key"));
liveCourseAccount.setUserKey(object.getString("user_key"));
liveCourseAccount.setAnchorKey(object.getString("zhubo_key"));
liveCourseAccountService.save(liveCourseAccount);
}
else
{
String msg = commonResult.getMsg();
System.out.println("msg = " + msg);
throw new SmartPlanetException(20001, "直播创建失败!" + msg);
}
}
}
8、功能实现-直播课程删除接口
8.1、LiveCourseController 类
package com.myxh.smart.planet.live.controller;
import com.myxh.smart.planet.live.service.LiveCourseService;
import com.myxh.smart.planet.result.Result;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author MYXH
* @date 2023/10/26
*
* <p>
* 直播课程 前端控制器
* </p>
*/
@Tag(name = "直播课程管理", description = "直播课程管理接口")
@RestController
@RequestMapping("/admin/live/live/course")
public class LiveCourseController
{
@Autowired
private LiveCourseService liveCourseService;
/**
* 删除直播课程
*
* @param id id
* @return Result 全局统一返回结果
*/
@Operation(summary = "删除直播课程", description = "删除直播课程")
@DeleteMapping("remove/{id}")
public Result<Void> remove(@PathVariable("id") Long id)
{
liveCourseService.removeLive(id);
return Result.ok(null);
}
}
8.2、LiveCourseService 接口
package com.myxh.smart.planet.live.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.myxh.smart.planet.model.live.LiveCourse;
/**
* @author MYXH
* @date 2023/10/26
*
* <p>
* 直播课程 服务类
* </p>
*/
public interface LiveCourseService extends IService<LiveCourse>
{
/**
* 删除直播课程
*
* @param id id
*/
void removeLive(Long id);
}
8.3、LiveCourseServiceImpl 实现
package com.myxh.smart.planet.live.service.impl;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.myxh.smart.planet.exception.SmartPlanetException;
import com.myxh.smart.planet.live.mapper.LiveCourseMapper;
import com.myxh.smart.planet.live.mtcloud.CommonResult;
import com.myxh.smart.planet.live.mtcloud.MTCloud;
import com.myxh.smart.planet.live.service.LiveCourseAccountService;
import com.myxh.smart.planet.live.service.LiveCourseDescriptionService;
import com.myxh.smart.planet.live.service.LiveCourseService;
import com.myxh.smart.planet.model.live.LiveCourse;
import com.myxh.smart.planet.model.live.LiveCourseAccount;
import com.myxh.smart.planet.model.live.LiveCourseDescription;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* @author MYXH
* @date 2023/10/26
*
* <p>
* 直播课程 服务实现类
* </p>
*/
@Service
public class LiveCourseServiceImpl extends ServiceImpl<LiveCourseMapper, LiveCourse> implements LiveCourseService
{
@Autowired
private MTCloud mtCloudClient;
@Autowired
private LiveCourseDescriptionService liveCourseDescriptionService;
@Autowired
private LiveCourseAccountService liveCourseAccountService;
/**
* 删除直播课程
*
* @param id id
*/
@SneakyThrows
@Transactional(rollbackFor = {Exception.class})
@Override
public void removeLive(Long id)
{
// 根据 id 查询直播课程信息
LiveCourse liveCourse = baseMapper.selectById(id);
if (liveCourse != null)
{
// 获取直播 courseId
Long courseId = liveCourse.getCourseId();
// 调用方法删除平台直播课程
String res = mtCloudClient.courseDelete(courseId.toString());
System.out.println("res = " + res);
CommonResult<JSONObject> commonResult = JSON.parseObject(res, CommonResult.class);
// 把删除之后返回结果进行判断
if (Integer.parseInt(commonResult.getCode()) == MTCloud.CODE_SUCCESS)
{
// 删除直播基本信息
baseMapper.deleteById(id);
// 删除直播描述信息
liveCourseDescriptionService.remove(new LambdaQueryWrapper<LiveCourseDescription>().ge(LiveCourseDescription::getLiveCourseId, id));
// 删除直播账号信息
liveCourseAccountService.remove(new LambdaQueryWrapper<LiveCourseAccount>().ge(LiveCourseAccount::getLiveCourseId, id));
}
else
{
String msg = commonResult.getMsg();
System.out.println("msg = " + msg);
throw new SmartPlanetException(20001, "删除直播课程失败!" + msg);
}
}
}
}
9、功能实现-直播课程修改接口
9.1、LiveCourseController 类
package com.myxh.smart.planet.live.controller;
import com.myxh.smart.planet.live.service.LiveCourseService;
import com.myxh.smart.planet.model.live.LiveCourse;
import com.myxh.smart.planet.result.Result;
import com.myxh.smart.planet.vo.live.LiveCourseFormVo;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author MYXH
* @date 2023/10/26
*
* <p>
* 直播课程 前端控制器
* </p>
*/
@Tag(name = "直播课程管理", description = "直播课程管理接口")
@RestController
@RequestMapping("/admin/live/live/course")
public class LiveCourseController
{
@Autowired
private LiveCourseService liveCourseService;
/**
* 根据 id 查询直播课程基本信息
*
* @param id id
* @return Result 全局统一返回结果
*/
@Operation(summary = "根据 id 查询直播课程基本信息", description = "根据 id 查询直播课程基本信息")
@GetMapping("get/{id}")
public Result<LiveCourse> get(@PathVariable("id") Long id)
{
LiveCourse liveCourse = liveCourseService.getById(id);
return Result.ok(liveCourse);
}
/**
* 根据 id 查询直播课程基本信息和描述信息
*
* @param id id
* @return Result 全局统一返回结果
*/
@Operation(summary = "根据 id 查询直播课程基本信息和描述信息", description = "根据 id 查询直播课程基本信息和描述信息")
@GetMapping("get/info/{id}")
public Result<LiveCourseFormVo> getInfo(@PathVariable("id") Long id)
{
LiveCourseFormVo liveCourseFormVo = liveCourseService.getLiveCourseFormVo(id);
return Result.ok(liveCourseFormVo);
}
/**
* 更新直播课程
*
* @param liveCourseFormVo 直播课程
* @return Result 全局统一返回结果
*/
@Operation(summary = "更新直播课程", description = "更新直播课程")
@PutMapping("update")
public Result<Void> updateById(@RequestBody LiveCourseFormVo liveCourseFormVo)
{
liveCourseService.updateLiveById(liveCourseFormVo);
return Result.ok(null);
}
}
9.2、LiveCourseService 接口
package com.myxh.smart.planet.live.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.myxh.smart.planet.model.live.LiveCourse;
import com.myxh.smart.planet.vo.live.LiveCourseFormVo;
/**
* @author MYXH
* @date 2023/10/26
*
* <p>
* 直播课程 服务类
* </p>
*/
public interface LiveCourseService extends IService<LiveCourse>
{
/**
* 根据 id 查询直播课程基本信息和描述信息
*
* @param id id
* @return liveCourseFormVo 直播课程
*/
LiveCourseFormVo getLiveCourseFormVo(Long id);
/**
* 更新直播课程
*
* @param liveCourseFormVo 直播课程
*/
void updateLiveById(LiveCourseFormVo liveCourseFormVo);
}
9.3、LiveCourseServiceImpl 实现
package com.myxh.smart.planet.live.service.impl;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.myxh.smart.planet.client.course.CourseFeignClient;
import com.myxh.smart.planet.exception.SmartPlanetException;
import com.myxh.smart.planet.live.mapper.LiveCourseMapper;
import com.myxh.smart.planet.live.mtcloud.CommonResult;
import com.myxh.smart.planet.live.mtcloud.MTCloud;
import com.myxh.smart.planet.live.service.LiveCourseAccountService;
import com.myxh.smart.planet.live.service.LiveCourseDescriptionService;
import com.myxh.smart.planet.live.service.LiveCourseService;
import com.myxh.smart.planet.model.live.LiveCourse;
import com.myxh.smart.planet.model.live.LiveCourseAccount;
import com.myxh.smart.planet.model.live.LiveCourseDescription;
import com.myxh.smart.planet.model.vod.Teacher;
import com.myxh.smart.planet.vo.live.LiveCourseFormVo;
import lombok.SneakyThrows;
import org.joda.time.DateTime;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.HashMap;
/**
* @author MYXH
* @date 2023/10/26
*
* <p>
* 直播课程 服务实现类
* </p>
*/
@Service
public class LiveCourseServiceImpl extends ServiceImpl<LiveCourseMapper, LiveCourse> implements LiveCourseService
{
@Autowired
private CourseFeignClient teacherFeignClient;
@Autowired
private MTCloud mtCloudClient;
@Autowired
private LiveCourseDescriptionService liveCourseDescriptionService;
@Autowired
private LiveCourseAccountService liveCourseAccountService;
/**
* 根据 id 查询直播课程基本信息和描述信息
*
* @param id id
* @return liveCourseFormVo 直播课程
*/
@Override
public LiveCourseFormVo getLiveCourseFormVo(Long id)
{
// 获取直播课程基本信息
LiveCourse liveCourse = this.getById(id);
// 获取直播课程描述信息
LiveCourseDescription liveCourseDescription = liveCourseDescriptionService.getLiveCourseDescriptionByLiveCourseId(id);
// 封装数据
LiveCourseFormVo liveCourseFormVo = new LiveCourseFormVo();
BeanUtils.copyProperties(liveCourse, liveCourseFormVo);
liveCourseFormVo.setDescription(liveCourseDescription.getDescription());
return liveCourseFormVo;
}
/**
* 更新直播课程
*
* @param liveCourseFormVo 直播课程
*/
@SneakyThrows
@Transactional(rollbackFor = {Exception.class})
@Override
public void updateLiveById(LiveCourseFormVo liveCourseFormVo)
{
// 根据 id 获取直播课程基本信息
LiveCourse liveCourse = baseMapper.selectById(liveCourseFormVo.getId());
BeanUtils.copyProperties(liveCourseFormVo, liveCourse);
// 获取教师信息
Teacher teacher = teacherFeignClient.getTeacherLive(liveCourseFormVo.getTeacherId());
/*
course_id 课程ID
course_name 课程名称
bid 发起直播课程的主播账号
start_time 课程开始时间,格式:2015-01-10 12:00:00
end_time 课程结束时间,格式:2015-01-10 13:00:00
nickname 昵称
accountIntro 主播介绍
options 其他参数
*/
HashMap<Object, Object> options = new HashMap<>();
String res = mtCloudClient.courseUpdate(liveCourse.getCourseId().toString(),
teacher.getId().toString(),
liveCourse.getCourseName(),
new DateTime(liveCourse.getStartTime()).toString("yyyy-MM-dd HH:mm:ss"),
new DateTime(liveCourse.getEndTime()).toString("yyyy-MM-dd HH:mm:ss"),
teacher.getName(),
teacher.getIntro(),
options);
System.out.println("res = " + res);
// 返回结果转换,判断是否成功
CommonResult<JSONObject> commonResult = JSON.parseObject(res, CommonResult.class);
if (Integer.parseInt(commonResult.getCode()) == MTCloud.CODE_SUCCESS)
{
JSONObject object = commonResult.getData();
// 更新直播课程基本信息
liveCourse.setCourseId(object.getLong("course_id"));
baseMapper.updateById(liveCourse);
// 直播课程描述信息更新
LiveCourseDescription liveCourseDescription = liveCourseDescriptionService.getLiveCourseDescriptionByLiveCourseId(liveCourse.getId());
liveCourseDescription.setDescription(liveCourseFormVo.getDescription());
liveCourseDescriptionService.update(liveCourseDescription, new LambdaQueryWrapper<LiveCourseDescription>().ge(LiveCourseDescription::getLiveCourseId, liveCourse.getId()));
// 更新直播账号信息
LiveCourseAccount liveCourseAccount = new LiveCourseAccount();
liveCourseAccount.setLiveCourseId(liveCourse.getId());
liveCourseAccount.setAnchorAccount(object.getString("bid"));
liveCourseAccount.setAnchorPassword(liveCourseFormVo.getPassword());
liveCourseAccount.setAdminKey(object.getString("admin_key"));
liveCourseAccount.setUserKey(object.getString("user_key"));
liveCourseAccount.setAnchorKey(object.getString("zhubo_key"));
liveCourseAccountService.update(liveCourseAccount, new LambdaUpdateWrapper<LiveCourseAccount>().ge(LiveCourseAccount::getLiveCourseId, liveCourse.getId()));
}
else
{
String msg = commonResult.getMsg();
System.out.println("msg = " + msg);
throw new SmartPlanetException(20001, "修改直播课程失败!" + msg);
}
}
}
9.4、LiveCourseDescriptionService 添加方法
package com.myxh.smart.planet.live.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.myxh.smart.planet.model.live.LiveCourseDescription;
/**
* @author MYXH
* @date 2023/10/26
*
* <p>
* 直播课程简介 服务类
* </p>
*/
public interface LiveCourseDescriptionService extends IService<LiveCourseDescription>
{
/**
* 获取直播课程描述信息
*
* @param id id
* @return liveCourseDescription 直播课程描述信息
*/
LiveCourseDescription getLiveCourseDescriptionByLiveCourseId(Long id);
}
9.5、LiveCourseDescriptionServiceImpl 实现方法
package com.myxh.smart.planet.live.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.myxh.smart.planet.live.mapper.LiveCourseDescriptionMapper;
import com.myxh.smart.planet.live.service.LiveCourseDescriptionService;
import com.myxh.smart.planet.model.live.LiveCourseDescription;
import org.springframework.stereotype.Service;
/**
* @author MYXH
* @date 2023/10/26
*
* <p>
* 直播课程简介 服务实现类
* </p>
*/
@Service
public class LiveCourseDescriptionServiceImpl extends ServiceImpl<LiveCourseDescriptionMapper, LiveCourseDescription> implements LiveCourseDescriptionService
{
/**
* 获取直播课程描述信息
*
* @param id id
* @return liveCourseDescription 直播课程描述信息
*/
@Override
public LiveCourseDescription getLiveCourseDescriptionByLiveCourseId(Long id)
{
LambdaQueryWrapper<LiveCourseDescription> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(LiveCourseDescription::getLiveCourseId, id);
LiveCourseDescription liveCourseDescription = baseMapper.selectOne(wrapper);
return liveCourseDescription;
}
}
10、功能实现-查看账号接口
10.1、LiveCourseController 类
package com.myxh.smart.planet.live.controller;
import com.myxh.smart.planet.live.service.LiveCourseAccountService;
import com.myxh.smart.planet.model.live.LiveCourseAccount;
import com.myxh.smart.planet.result.Result;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author MYXH
* @date 2023/10/26
*
* <p>
* 直播课程 前端控制器
* </p>
*/
@Tag(name = "直播课程管理", description = "直播课程管理接口")
@RestController
@RequestMapping("/admin/live/live/course")
public class LiveCourseController
{
@Autowired
private LiveCourseAccountService liveCourseAccountService;
/**
* 获取直播账号信息
*
* @param id id
* @return Result 全局统一返回结果
*/
@Operation(summary = "获取直播账号信息", description = "获取直播账号信息")
@GetMapping("get/live/course/account/{id}")
public Result<LiveCourseAccount> getLiveCourseAccount(@PathVariable("id") Long id)
{
LiveCourseAccount liveCourseAccount = liveCourseAccountService.getLiveCourseAccountByLiveCourseId(id);
return Result.ok(liveCourseAccount);
}
}
10.2、LiveCourseAccountService 接口
package com.myxh.smart.planet.live.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.myxh.smart.planet.model.live.LiveCourseAccount;
/**
* @author MYXH
* @date 2023/10/26
*
* <p>
* 直播课程账号(受保护信息) 服务类
* </p>
*/
public interface LiveCourseAccountService extends IService<LiveCourseAccount>
{
/**
* 获取直播账号信息
*
* @param id id
* @return liveCourseAccount 直播账号信息
*/
LiveCourseAccount getLiveCourseAccountByLiveCourseId(Long id);
}
10.3、LiveCourseAccountServiceImpl 实现
package com.myxh.smart.planet.live.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.myxh.smart.planet.live.mapper.LiveCourseAccountMapper;
import com.myxh.smart.planet.live.service.LiveCourseAccountService;
import com.myxh.smart.planet.model.live.LiveCourseAccount;
import org.springframework.stereotype.Service;
/**
* @author MYXH
* @date 2023/10/26
*
* <p>
* 直播课程账号(受保护信息) 服务实现类
* </p>
*/
@Service
public class LiveCourseAccountServiceImpl extends ServiceImpl<LiveCourseAccountMapper, LiveCourseAccount> implements LiveCourseAccountService
{
/**
* 获取直播账号信息
*
* @param id id
* @return liveCourseAccount 直播账号信息
*/
@Override
public LiveCourseAccount getLiveCourseAccountByLiveCourseId(Long id)
{
LambdaQueryWrapper<LiveCourseAccount> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(LiveCourseAccount::getLiveCourseId, id);
LiveCourseAccount liveCourseAccount = baseMapper.selectOne(wrapper);
return liveCourseAccount;
}
}
11、功能实现-配置和观看记录接口
11.1、查看配置信息
(1)LiveCourseController 类。
package com.myxh.smart.planet.live.controller;
import com.myxh.smart.planet.live.service.LiveCourseService;
import com.myxh.smart.planet.result.Result;
import com.myxh.smart.planet.vo.live.LiveCourseConfigVo;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author MYXH
* @date 2023/10/26
*
* <p>
* 直播课程 前端控制器
* </p>
*/
@Tag(name = "直播课程管理", description = "直播课程管理接口")
@RestController
@RequestMapping("/admin/live/live/course")
public class LiveCourseController
{
@Autowired
private LiveCourseService liveCourseService;
/**
* 获取直播配置信息
*
* @param id id
* @return Result 全局统一返回结果
*/
@Operation(summary = "获取直播配置信息", description = "获取直播配置信息")
@GetMapping("get/course/config/{id}")
public Result<LiveCourseConfigVo> getCourseConfig(@PathVariable("id") Long id)
{
LiveCourseConfigVo liveCourseConfigVo = liveCourseService.getCourseConfig(id);
return Result.ok(liveCourseConfigVo);
}
}
(2)LiveCourseService 添加方法。
package com.myxh.smart.planet.live.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.myxh.smart.planet.model.live.LiveCourse;
import com.myxh.smart.planet.vo.live.LiveCourseConfigVo;
/**
* @author MYXH
* @date 2023/10/26
*
* <p>
* 直播课程 服务类
* </p>
*/
public interface LiveCourseService extends IService<LiveCourse>
{
/**
* 获取直播配置信息
*
* @param id id
* @return liveCourseConfigVo 直播配置信息
*/
LiveCourseConfigVo getCourseConfig(Long id);
}
(3)LiveCourseServiceImpl 实现。
package com.myxh.smart.planet.live.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.myxh.smart.planet.live.mapper.LiveCourseMapper;
import com.myxh.smart.planet.live.service.LiveCourseConfigService;
import com.myxh.smart.planet.live.service.LiveCourseGoodsService;
import com.myxh.smart.planet.live.service.LiveCourseService;
import com.myxh.smart.planet.model.live.LiveCourse;
import com.myxh.smart.planet.model.live.LiveCourseConfig;
import com.myxh.smart.planet.model.live.LiveCourseGoods;
import com.myxh.smart.planet.vo.live.LiveCourseConfigVo;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* @author MYXH
* @date 2023/10/26
*
* <p>
* 直播课程 服务实现类
* </p>
*/
@Service
public class LiveCourseServiceImpl extends ServiceImpl<LiveCourseMapper, LiveCourse> implements LiveCourseService
{
@Autowired
private LiveCourseConfigService liveCourseConfigService;
@Autowired
private LiveCourseGoodsService liveCourseGoodsService;
/**
* 获取直播配置信息
*
* @param id id
* @return liveCourseConfigVo 直播配置信息
*/
@Override
public LiveCourseConfigVo getCourseConfig(Long id)
{
LiveCourseConfigVo liveCourseConfigVo = new LiveCourseConfigVo();
// 根据课程 id 查询直播课程配置信息
LiveCourseConfig liveCourseConfig = liveCourseConfigService.getLiveCourseConfigByLiveCourseId(id);
if (liveCourseConfig != null)
{
// 查询直播课程商品列表
List<LiveCourseGoods> liveCourseGoodsList = liveCourseGoodsService.getLiveCourseGoodsByLiveCourseId(id);
// 封装 LiveCourseConfigVo
BeanUtils.copyProperties(liveCourseConfig, liveCourseConfigVo);
// 封装商品列表
liveCourseConfigVo.setLiveCourseGoodsList(liveCourseGoodsList);
}
return liveCourseConfigVo;
}
}
(4)LiveCourseConfigService 添加方法。
package com.myxh.smart.planet.live.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.myxh.smart.planet.model.live.LiveCourseConfig;
/**
* @author MYXH
* @date 2023/10/26
*
* <p>
* 直播课程配置 服务类
* </p>
*/
public interface LiveCourseConfigService extends IService<LiveCourseConfig>
{
/**
* 根据课程 id 查询直播课程配置信息
*
* @param id id
* @return liveCourseConfig 直播课程配置信息
*/
LiveCourseConfig getLiveCourseConfigByLiveCourseId(Long id);
}
(5)LiveCourseConfigServiceImpl 实现方法。
package com.myxh.smart.planet.live.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.myxh.smart.planet.live.mapper.LiveCourseConfigMapper;
import com.myxh.smart.planet.live.service.LiveCourseConfigService;
import com.myxh.smart.planet.model.live.LiveCourseConfig;
import org.springframework.stereotype.Service;
/**
* @author MYXH
* @date 2023/10/26
*
* <p>
* 直播课程配置 服务实现类
* </p>
*/
@Service
public class LiveCourseConfigServiceImpl extends ServiceImpl<LiveCourseConfigMapper, LiveCourseConfig> implements LiveCourseConfigService
{
/**
* 根据课程 id 查询直播课程配置信息
*
* @param id id
* @return liveCourseConfig 直播课程配置信息
*/
@Override
public LiveCourseConfig getLiveCourseConfigByLiveCourseId(Long id)
{
LambdaQueryWrapper<LiveCourseConfig> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(LiveCourseConfig::getLiveCourseId, id);
LiveCourseConfig liveCourseConfig = baseMapper.selectOne(wrapper);
return liveCourseConfig;
}
}
(6)LiveCourseGoodsService 添加方法。
package com.myxh.smart.planet.live.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.myxh.smart.planet.model.live.LiveCourseGoods;
import java.util.List;
/**
* @author MYXH
* @date 2023/10/26
*
* <p>
* 直播课程关联推荐 服务类
* </p>
*/
public interface LiveCourseGoodsService extends IService<LiveCourseGoods>
{
/**
* 查询直播课程商品列表
*
* @param id id
* @return liveCourseGoodsList 直播课程商品列表
*/
List<LiveCourseGoods> getLiveCourseGoodsByLiveCourseId(Long id);
}
(7)LiveCourseGoodsServiceImpl 实现方法。
package com.myxh.smart.planet.live.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.myxh.smart.planet.live.mapper.LiveCourseGoodsMapper;
import com.myxh.smart.planet.live.service.LiveCourseGoodsService;
import com.myxh.smart.planet.model.live.LiveCourseGoods;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* @author MYXH
* @date 2023/10/26
*
* <p>
* 直播课程关联推荐 服务实现类
* </p>
*/
@Service
public class LiveCourseGoodsServiceImpl extends ServiceImpl<LiveCourseGoodsMapper, LiveCourseGoods> implements LiveCourseGoodsService
{
/**
* 查询直播课程商品列表
*
* @param id id
* @return liveCourseGoodsList 直播课程商品列表
*/
@Override
public List<LiveCourseGoods> getLiveCourseGoodsByLiveCourseId(Long id)
{
LambdaQueryWrapper<LiveCourseGoods> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(LiveCourseGoods::getLiveCourseId, id);
List<LiveCourseGoods> liveCourseGoodsList = baseMapper.selectList(wrapper);
return liveCourseGoodsList;
}
}
11.2、修改直播配置信息
(1)LiveCourseController 添加方法。
package com.myxh.smart.planet.live.controller;
import com.myxh.smart.planet.live.service.LiveCourseService;
import com.myxh.smart.planet.result.Result;
import com.myxh.smart.planet.vo.live.LiveCourseConfigVo;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author MYXH
* @date 2023/10/26
*
* <p>
* 直播课程 前端控制器
* </p>
*/
@Tag(name = "直播课程管理", description = "直播课程管理接口")
@RestController
@RequestMapping("/admin/live/live/course")
public class LiveCourseController
{
@Autowired
private LiveCourseService liveCourseService;
/**
* 修改直播配置信息
*
* @param liveCourseConfigVo 直播配置信息
* @return Result 全局统一返回结果
*/
@Operation(summary = "修改直播配置信息", description = "修改直播配置信息")
@PutMapping("update/config")
public Result<Void> updateConfig(@RequestBody LiveCourseConfigVo liveCourseConfigVo)
{
liveCourseService.updateCourseConfig(liveCourseConfigVo);
return Result.ok(null);
}
}
(2)LiveCourseService 添加方法。
package com.myxh.smart.planet.live.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.myxh.smart.planet.model.live.LiveCourse;
import com.myxh.smart.planet.vo.live.LiveCourseConfigVo;
/**
* @author MYXH
* @date 2023/10/26
*
* <p>
* 直播课程 服务类
* </p>
*/
public interface LiveCourseService extends IService<LiveCourse>
{
/**
* 修改直播配置信息
*
* @param liveCourseConfigVo 直播配置信息
*/
void updateCourseConfig(LiveCourseConfigVo liveCourseConfigVo);
}
(3)LiveCourseServiceImpl 实现方法。
package com.myxh.smart.planet.live.service.impl;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.myxh.smart.planet.exception.SmartPlanetException;
import com.myxh.smart.planet.live.mapper.LiveCourseMapper;
import com.myxh.smart.planet.live.mtcloud.CommonResult;
import com.myxh.smart.planet.live.mtcloud.MTCloud;
import com.myxh.smart.planet.live.service.LiveCourseConfigService;
import com.myxh.smart.planet.live.service.LiveCourseGoodsService;
import com.myxh.smart.planet.live.service.LiveCourseService;
import com.myxh.smart.planet.model.live.LiveCourse;
import com.myxh.smart.planet.model.live.LiveCourseConfig;
import com.myxh.smart.planet.model.live.LiveCourseGoods;
import com.myxh.smart.planet.vo.live.LiveCourseConfigVo;
import com.myxh.smart.planet.vo.live.LiveCourseGoodsView;
import lombok.SneakyThrows;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
/**
* @author MYXH
* @date 2023/10/26
*
* <p>
* 直播课程 服务实现类
* </p>
*/
@Service
public class LiveCourseServiceImpl extends ServiceImpl<LiveCourseMapper, LiveCourse> implements LiveCourseService
{
@Autowired
private MTCloud mtCloudClient;
@Autowired
private LiveCourseConfigService liveCourseConfigService;
@Autowired
private LiveCourseGoodsService liveCourseGoodsService;
/**
* 修改直播配置信息
*
* @param liveCourseConfigVo 直播配置信息
*/
@Override
@Transactional(rollbackFor = {Exception.class})
public void updateCourseConfig(LiveCourseConfigVo liveCourseConfigVo)
{
// 1、修改直播配置表
LiveCourseConfig liveCourseConfig = new LiveCourseConfig();
BeanUtils.copyProperties(liveCourseConfigVo, liveCourseConfig);
if (liveCourseConfigVo.getId() == null)
{
liveCourseConfigService.save(liveCourseConfig);
}
else
{
liveCourseConfigService.update(liveCourseConfig, new LambdaUpdateWrapper<LiveCourseConfig>().eq(LiveCourseConfig::getLiveCourseId, liveCourseConfigVo.getLiveCourseId()));
}
// 2、修改直播商品表
// 根据课程 id 批量删除直播商品列表
liveCourseGoodsService.remove(new LambdaQueryWrapper<LiveCourseGoods>().eq(LiveCourseGoods::getLiveCourseId, liveCourseConfigVo.getLiveCourseId()));
// 添加商品列表
if (!CollectionUtils.isEmpty(liveCourseConfigVo.getLiveCourseGoodsList()))
{
liveCourseGoodsService.saveBatch(liveCourseConfigVo.getLiveCourseGoodsList());
}
// 3、修改在线直播平台中的直播配置信息
this.updateLiveConfig(liveCourseConfigVo);
}
/**
* 上传直播配置,修改在线直播平台中的直播配置信息
*
* @param liveCourseConfigVo 直播配置信息
*/
@SneakyThrows
private void updateLiveConfig(LiveCourseConfigVo liveCourseConfigVo)
{
LiveCourse liveCourse = baseMapper.selectById(liveCourseConfigVo.getLiveCourseId());
// 封装平台方法需要的参数
// 参数设置
HashMap<Object, Object> options = new HashMap<>();
// 界面模式
options.put("pageViewMode", liveCourseConfigVo.getPageViewMode());
// 观看人数开关
JSONObject number = new JSONObject();
number.put("enable", liveCourseConfigVo.getNumberEnable());
options.put("number", number.toJSONString());
// 观看人数开关
JSONObject store = new JSONObject();
number.put("enable", liveCourseConfigVo.getStoreEnable());
number.put("type", liveCourseConfigVo.getStoreType());
options.put("store", number.toJSONString());
// 商城列表
List<LiveCourseGoods> liveCourseGoodsList = liveCourseConfigVo.getLiveCourseGoodsList();
if (!CollectionUtils.isEmpty(liveCourseGoodsList))
{
List<LiveCourseGoodsView> liveCourseGoodsViewList = new ArrayList<>();
for (LiveCourseGoods liveCourseGoods : liveCourseGoodsList)
{
LiveCourseGoodsView liveCourseGoodsView = new LiveCourseGoodsView();
BeanUtils.copyProperties(liveCourseGoods, liveCourseGoodsView);
liveCourseGoodsViewList.add(liveCourseGoodsView);
}
JSONObject goodsListEdit = new JSONObject();
goodsListEdit.put("status", "0");
options.put("goodsListEdit ", goodsListEdit.toJSONString());
options.put("goodsList", JSON.toJSONString(liveCourseGoodsViewList));
}
String res = mtCloudClient.courseUpdateConfig(liveCourse.getCourseId().toString(), options);
System.out.println("res = " + res);
CommonResult<JSONObject> commonResult = JSON.parseObject(res, CommonResult.class);
if (Integer.parseInt(commonResult.getCode()) != MTCloud.CODE_SUCCESS)
{
String msg = commonResult.getMsg();
System.out.println("msg = " + msg);
throw new SmartPlanetException(20001, "修改配置信息失败!" + msg);
}
}
}
11.3、获取最近直播课程
(1)LiveCourseController 添加方法。
package com.myxh.smart.planet.live.controller;
import com.myxh.smart.planet.live.service.LiveCourseService;
import com.myxh.smart.planet.result.Result;
import com.myxh.smart.planet.vo.live.LiveCourseVo;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* @author MYXH
* @date 2023/10/26
*
* <p>
* 直播课程 前端控制器
* </p>
*/
@Tag(name = "直播课程管理", description = "直播课程管理接口")
@RestController
@RequestMapping("/admin/live/live/course")
public class LiveCourseController
{
@Autowired
private LiveCourseService liveCourseService;
/**
* 获取最近的直播
*
* @return Result 全局统一返回结果
*/
@Operation(summary = "获取最近的直播", description = "获取最近的直播")
@GetMapping("find/lately/list")
public Result<List<LiveCourseVo>> findLatelyList()
{
List<LiveCourseVo> liveCourseVoList = liveCourseService.getLatelyList();
return Result.ok(liveCourseVoList);
}
}
(2)LiveCourseService 添加方法。
package com.myxh.smart.planet.live.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.myxh.smart.planet.model.live.LiveCourse;
import com.myxh.smart.planet.vo.live.LiveCourseVo;
import java.util.List;
/**
* @author MYXH
* @date 2023/10/26
*
* <p>
* 直播课程 服务类
* </p>
*/
public interface LiveCourseService extends IService<LiveCourse>
{
/**
* 获取最近的直播
*
* @return liveCourseVoList 直播课程列表
*/
List<LiveCourseVo> getLatelyList();
}
(3)LiveCourseServiceImpl 实现方法。
package com.myxh.smart.planet.live.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.myxh.smart.planet.client.course.CourseFeignClient;
import com.myxh.smart.planet.live.mapper.LiveCourseMapper;
import com.myxh.smart.planet.live.service.LiveCourseService;
import com.myxh.smart.planet.model.live.LiveCourse;
import com.myxh.smart.planet.model.vod.Teacher;
import com.myxh.smart.planet.order.DateUtil;
import com.myxh.smart.planet.vo.live.LiveCourseVo;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Date;
import java.util.List;
/**
* @author MYXH
* @date 2023/10/26
*
* <p>
* 直播课程 服务实现类
* </p>
*/
@Service
public class LiveCourseServiceImpl extends ServiceImpl<LiveCourseMapper, LiveCourse> implements LiveCourseService
{
@Autowired
private CourseFeignClient teacherFeignClient;
/**
* 获取最近的直播
*
* @return liveCourseVoList 直播课程列表
*/
@Override
public List<LiveCourseVo> getLatelyList()
{
List<LiveCourseVo> liveCourseVoList = baseMapper.findLatelyList();
for (LiveCourseVo liveCourseVo : liveCourseVoList)
{
// 封装开始和结束时间
liveCourseVo.setStartTimeString(new DateTime(liveCourseVo.getStartTime()).toString("yyyy年MM月dd HH:mm:ss"));
liveCourseVo.setEndTimeString(new DateTime(liveCourseVo.getEndTime()).toString("yyyy年MM月dd HH:mm:ss"));
// 封装教师
Long teacherId = liveCourseVo.getTeacherId();
Teacher teacher = teacherFeignClient.getTeacherLive(teacherId);
liveCourseVo.setTeacher(teacher);
// 封装直播状态
liveCourseVo.setLiveStatus(this.getLiveStatus(liveCourseVo));
}
return liveCourseVoList;
}
/**
* 直播状态,0:未开始,1:直播中,2:直播结束
*
* @param liveCourse 直播课程
* @return liveStatus 直播状态
*/
private int getLiveStatus(LiveCourse liveCourse)
{
// 直播状态,0:未开始,1:直播中,2:直播结束
int liveStatus;
Date curTime = new Date();
if (DateUtil.dateCompare(curTime, liveCourse.getStartTime()))
{
liveStatus = 0;
}
else if (DateUtil.dateCompare(curTime, liveCourse.getEndTime()))
{
liveStatus = 1;
}
else
{
liveStatus = 2;
}
return liveStatus;
}
}
(4)LiveCourseMapper 添加方法。
package com.myxh.smart.planet.live.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.myxh.smart.planet.model.live.LiveCourse;
import com.myxh.smart.planet.vo.live.LiveCourseVo;
import java.util.List;
/**
* @author MYXH
* @date 2023/10/26
*
* <p>
* 直播课程 Mapper 接口
* </p>
*/
public interface LiveCourseMapper extends BaseMapper<LiveCourse>
{
/**
* 获取最近的直播
*
* @return liveCourseVoList 直播课程列表
*/
List<LiveCourseVo> findLatelyList();
}
(5)LiveCourseMapper.xml 编写 sql 语句。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.myxh.smart.planet.live.mapper.LiveCourseMapper">
<resultMap id="liveCourseMap" type="com.myxh.smart.planet.vo.live.LiveCourseVo" autoMapping="true">
</resultMap>
<!-- 用于 select 查询公用抽取的列 -->
<sql id="columns">
id, course_id, course_name, start_time, end_time, teacher_id, cover, create_time, update_time, is_deleted
</sql>
<select id="findLatelyList" resultMap="liveCourseMap">
SELECT
<include refid="columns"/>
FROM `live_course`
WHERE DATE(start_time) >= CURDATE()
ORDER BY id
LIMIT 5
</select>
</mapper>