赞
踩
(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)查看路由文件。
(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>
(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)创建 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>
(1)点击课程详情页立即购买。
(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); }
操作 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); }
操作 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); }
(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;
(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(); } }
接口文档:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_1
(1)绑定域名。
与微信分享一致
先登录微信公众平台进入“设置与开发”,“公众号设置”的“功能设置”里填写“JS 接口安全域名”。
说明:因为测试号不支持支付功能,需要使用正式号才能进行测试。
(2)商户平台配置支付目录。
(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<>(); } } }
(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 到支付接口中测试。
(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>
(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; } }
(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.滤镜处理、3.编码、4.推流、5.CDN 分发、6.拉流、7.解码、8.播放、9.聊天互动。
首先是主播方,它是产生视频流的源头,由一系列流程组成:第一,通过一定的设备来采集数据;第二,将采集的这些视频进行一系列的处理,比如水印、美颜和特效滤镜等处理;第三,将处理后的结果视频编码压缩成可观看可传输的视频流;第四,分发推流,即将压缩后的视频流通过网络通道传输出去。
其次是播放端,播放端功能有两个层面,第一个层面是关键性的需求;另一层面是业务层面的。先看第一个层面,它涉及到一些非常关键的指标,比如秒开,在很多场景当中都有这样的要求,然后是对于一些重要内容的版权保护。为了达到更好的效果,还需要配合服务端做智能解析,这在某些场景下也是关键性需求。再来看第二个层面也即业务层面的功能,对于一个社交直播产品来说,在播放端,观众希望能够实时的看到主播端推过来的视频流,并且和主播以及其他观众产生一定的互动,因此它可能包含一些像点赞、聊天和弹幕这样的功能,以及礼物这样更高级的道具。
要知道,内容产生方和消费方一般都不是一一对应的。对于一个直播产品来讲,最直观的体现就是一个主播可能会有很多粉丝。因此,不能直接让主播端和所有播放端进行点对点通信,这在技术上是做不到或者很有难度。主播方播出的视频到达播放端之前,需要经过一系列的中间环节,也就是这里讲的直播服务器端。
直播服务器端提供的最核心功能是收集主播端的视频推流,并将其放大后推送给所有观众端。除了这个核心功能,还有很多运营级别的诉求,比如鉴权认证,视频连线和实时转码,自动鉴黄,多屏合一,以及云端录制存储等功能。另外,对于一个主播端推出的视频流,中间需要经过一些环节才能到达播放端,因此对中间环节的质量进行监控,以及根据这些监控来进行智能调度,也是非常重要的诉求。
实际上无论是主播端还是播放端,他们的诉求都不会仅仅是拍摄视频和播放视频这么简单。在这个核心诉求被满足之后,还有很多关键诉求需要被满足。比如,对于一个消费级的直播产品来说,除了这三大模块之外,还需要实现一个业务服务端来进行推流和播放控制,以及所有用户状态的维持。如此,就构成了一个消费级可用的直播产品。
七牛云:七牛直播云是专为直播平台打造的全球化直播流服务和一站式实现 SDK 端到端直播场景的企业级直播云服务平台。
网易视频云:基于专业的跨平台视频编解码技术和大规模视频内容分发网络,提供稳定流畅、低延时、高并发的实时音视频服务,可将视频直播无缝对接到自身 App。
欢拓云直播平台:欢拓是一家以直播技术为核心的网络平台,旨在帮助人们通过网络也能实现真实互动通讯。
降低成本:
提升效率:
降低风险:
专业的事,找专业的人来做。
根据上面的综合对比和调研,最终选择了“欢拓与直播平台”,它提供了完整的可以直接使用的示例代码,方便开发对接。
欢拓是一家以直播技术为核心的网络平台,旨在帮助人们通过网络也能实现真实互动通讯。从 2010 年开始,欢拓就专注于音频、视频的采样、编码、后处理及智能传输研究,并于 2013 年底正式推出了针对企业/开发者的直播云服务系统,帮助开发者轻松实现真人互动。该系统适用场景包括在线教育、游戏语音、娱乐互动、远程会议(PC、移动均可)等等。针对应用场景,采用先进技术解决方案和产品形态,让客户和客户的用户满意!
官网:https://www.talk-fun.com/
接口文档地址:http://open.talk-fun.com/docs/getstartV2/document.html
通过官网:https://www.talk-fun.com/ ,联系客户或 400 电话开通账号,开通 “生活直播” 权限。开通后注意使用有效期,一般一周左右,可以再次申请延期。
说明:官网免费试用,功能有限制,不建议使用
1、在直播管理创建直播。
2、创建直播,选择主播模式。
3、配置直播,可以自行查看。
1、在直播列表,点击“直播入口”。
主播端下载“云直播客户端”,“频道 id 与密码”为直播客户端的登录账号;
下面还有管理员,主播进行直播时,助教可以在聊天时与观众互动。
2、网页端可以“一键开播”。
3、使用“频道 id 与密码”登录。
4、点击“开始直播”,打开摄像头即可开始直播。
1、在直播列表,点击“直播入口”。
2、在观众一栏点击进入,即可在网页端观看直播。
上面的体验完全能够满足业务的需要,智慧星球的需求是定期推出直播课程,方便学生与名师之间的交流互动,在直播间老师可以推荐点播课程(类似直播带货),学生可以点赞交流,购买推荐的点播课程。
直播平台只是做了直播相关的业务,不能与业务进行衔接,期望的是在智慧星球的管理后台管理直播相关的业务,那么怎么做呢?对接直播业务接口,直播平台有对应的直播接口,直接对接即可。
上面已经开通了“生活类直播”。
登录进入开放后台,后台首页即可获取 openId 与 openToken。
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 参数的获取见对接流程说明。
接口文档地址:https://open.talk-fun.com/docs/getstartV2/api/live_dir.html
根据接口文档,了解需要对接哪些接口。
api 名称:course.add
,SDK 对应方法:courseAdd
。
添加直播是一定需要的。
api 名称:course.update
,SDK 对应方法courseUpdate
。
api 名称:course.delete
,SDK 对应方法:courseDelete
。
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}。
直播设置最终效果:
改接口在:"访客/管理员列表"下面。
通过该接口统计课程观看人数信息。
直播访客 api 名称:course.visitor.list
,SDK 对应方法:courseVisitorList
。
直播平台准备了 SDK,直接使用。
下载地址:https://open.talk-fun.com/docs/getstartV2/api/introduce/sdkdownload.html
已下载:当前目录/MTCloud-java-sdk-1.6.zip
添加直播 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>
解压 MTCloud-java-sdk-1.6.zip,复制 MTCloud-java-sdk-1.6\MTCloud_java\src\com\mtcloud\sdk 下面的 java 文件到 com.myxh.smart.planet.live.mtcloud 包下,如图。
更改 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 = ""; }
(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); } }
根据直播平台与自身业务设计直播相关的业务表,如:smart_planet_live。
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); } }
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); }
(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); }
<!-- 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>
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; } }
(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()); } }
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); } }
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); }
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); } } }
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); } }
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); }
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); } } } }
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); } }
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); }
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); } } }
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); }
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; } }
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); } }
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); }
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; } }
(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; } }
(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); } } }
(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>
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。