赞
踩
(1)点击课程中的分类,根据分类查询课程列表
(2)点击 去看看,进入课程详情页面
(1)创建CourseApiController
@Api(tags = "课程") @RestController @RequestMapping("/api/vod/course") public class CourseApiController { @Autowired private CourseService courseService; @Autowired private ChapterService chapterService; //根据课程分类查询课程列表(分页)用于微信公众号里的查询 @ApiOperation("根据课程分类查询课程列表") @GetMapping("{subjectParentId}/{page}/{limit}") public Result findPageCourse(@ApiParam(value = "课程一级分类ID", required = true) @PathVariable Long subjectParentId, @ApiParam(name = "page", value = "当前页码", required = true) @PathVariable Long page, @ApiParam(name = "limit", value = "每页记录数", required = true) @PathVariable Long limit) { //封装条件 CourseQueryVo courseQueryVo = new CourseQueryVo(); courseQueryVo.setSubjectParentId(subjectParentId); //创建page对象 Page<Course> pageParam = new Page<>(page,limit); Map<String, Object> map= courseService.findPage(pageParam,courseQueryVo); return Result.ok(map); } //根据课程ID查询课程详情信息 @ApiOperation("根据ID查询课程") @GetMapping("getInfo/{courseId}") public Result getInfo( @ApiParam(value = "课程ID", required = true) @PathVariable Long courseId){ //没有专门实体类或者不好确定返回什么类型的数据就返回Map<String, Object>这要取值放值方便 Map<String, Object> map = courseService.getInfoById(courseId); return Result.ok(map); } }
(2)编写CourseService
//根据课程分类查询课程列表(分页)用于微信公众号里的查询
Map<String, Object> findPage(Page<Course> pageParam, CourseQueryVo courseQueryVo);
//根据课程ID查询课程详情信息
Map<String, Object> getInfoById(Long courseId);
(3)编写CourseServiceImpl
//根据课程分类查询课程列表(分页)用于微信公众号里的查询 @Override public Map<String, Object> findPage(Page<Course> pageParam, CourseQueryVo courseQueryVo) { //获取条件值 String title = courseQueryVo.getTitle();//课程名称 Long subjectId = courseQueryVo.getSubjectId();//二级分类 Long subjectParentId = courseQueryVo.getSubjectParentId();//一级分类 Long teacherId = courseQueryVo.getTeacherId();//讲师 //判断条件值是否为空,封装条件(其实就传了一个subjectid大可不必判空) QueryWrapper<Course> wrapper = new QueryWrapper<>(); if(!StringUtils.isEmpty(title)) { wrapper.like("title",title); } if(!StringUtils.isEmpty(subjectId)) { wrapper.eq("subject_id",subjectId); } if(!StringUtils.isEmpty(subjectParentId)) { wrapper.eq("subject_parent_id",subjectParentId); } if(!StringUtils.isEmpty(teacherId)) { wrapper.eq("teacher_id",teacherId); } //调用方法进行条件分页查询 Page<Course> pages = baseMapper.selectPage(pageParam, wrapper); //获取分页数据 long totalCount = pages.getTotal();//总记录数 long totalPage = pages.getPages();//总页数 long currentPage = pages.getCurrent();//当前页 long size = pages.getSize();//每页记录数 //每页数据集合 List<Course> records = pages.getRecords(); //封装其他数据(获取讲师名称和课程分类名称) records.stream().forEach(item -> { this.getTeacherOrSubjectName(item);//调用下边方法获取讲师和分类名称 }); Map<String,Object> map = new HashMap<>(); map.put("totalCount",totalCount); map.put("totalPage",totalPage); map.put("records",records); return map; } //获取讲师和分类名称 private Course getTeacherOrSubjectName(Course course) { //获取讲师名称 Long teacherId=course.getTeacherId(); //根据id查询讲师信息 Teacher teacher = teacherService.getById(teacherId); if(teacher != null) { course.getParam().put("teacherName",teacher.getName()); } //获取课程一级分类名称 Long subjectParentId=course.getSubjectParentId(); //根据一级分类id查询一级分类信息 Subject subjectOne = subjectService.getById(subjectParentId); if(subjectOne != null) { course.getParam().put("subjectParentTitle",subjectOne.getTitle()); } //根据二级分类id查询二级分类信息 Long subjectId=course.getSubjectId(); Subject subjectTwo = subjectService.getById(subjectId); if(subjectTwo != null) { course.getParam().put("subjectTitle",subjectTwo.getTitle()); } return course; } //根据课程ID查询课程详情信息 @Override public Map<String, Object> getInfoById(Long courseId) { //更新浏览量 view_count表字段流量+1 Course course = baseMapper.selectById(courseId); course.setViewCount(course.getViewCount() + 1); baseMapper.updateById(course); //根据课程id查询课程详情数据(自己写的方法在自己的实现层就调用basemapper) CourseVo courseVo = baseMapper.selectCourseVoById(courseId); //课程章节小节数据(之前写的方法)大纲列表(章节和小节列表也叫课时)树形结构显示 List<ChapterVo> chapterVoList = chapterService.getTreeList(courseId); //课程描述信息 CourseDescription courseDescription = courseDescriptionService.getById(courseId); //课程所属信息讲师信息 Teacher teacher = teacherService.getById(course.getTeacherId()); //TODO后续完善 Boolean isBuy = false; //封装map集合,返回 Map<String, Object> map = new HashMap<>(); map.put("courseVo", courseVo); map.put("chapterVoList", chapterVoList); map.put("description", null != courseDescription ? courseDescription.getDescription() : ""); map.put("teacher", teacher); map.put("isBuy", isBuy);//是否购买 return map; }
(4)编写CourseMapper
public interface CourseMapper extends BaseMapper<Course> {
/**
* 根据课程id获取课程发布信息
* @param id
* @return
*/
CoursePublishVo selectCoursePublishVoById(Long id);
//根据课程id查询课程详情(与上面方法一致只不过所要返回字段变多了)
CourseVo selectCourseVoById(Long courseId);
}
coursevo
@ApiModel("课程对象") @Data public class CourseVo { @ApiModelProperty(value = "课程ID") private String id; @ApiModelProperty(value = "课程标题") private String title; @ApiModelProperty(value = "一级分类标题") private String subjectParentTitle; @ApiModelProperty(value = "二级分类标题") private String subjectTitle; @ApiModelProperty(value = "讲师id") private Long teacherId; @ApiModelProperty(value = "讲师姓名") private String teacherName; @ApiModelProperty(value = "总课时") private Integer lessonNum; @ApiModelProperty(value = "课程销售价格") private String price;//只用于显示 @ApiModelProperty(value = "课程封面图片路径") private String cover; @ApiModelProperty(value = "销售数量") private Long buyCount; @ApiModelProperty(value = "浏览数量") private Long viewCount; @ApiModelProperty(value = "课程状态") private String status; @ApiModelProperty(value = "课程发布时间") private String publishTime; }
(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.atguigu.ggkt.vod.mapper.CourseMapper"> <select id="selectCoursePublishVoById" resultType="com.atguigu.ggkt.vo.vod.CoursePublishVo"> SELECT c.id, c.title, c.cover, c.lesson_num AS lessonNum, c.price, t.name AS teacherName, s1.title AS subjectParentTitle, s2.title AS subjectTitle FROM <include refid="tables" /> WHERE c.id = #{id} </select> <select id="selectCourseVoById" resultType="com.atguigu.ggkt.vo.vod.CourseVo"> SELECT <include refid="columns" /> FROM <include refid="tables" /> WHERE c.id = #{id} </select> <sql id="columns"> c.id, c.title, c.lesson_num AS lessonNum, c.price, c.cover, c.buy_count AS buyCount, c.view_count AS viewCount, c.status, c.publish_time AS publishTime, c.teacher_id as teacherId, t.name AS teacherName, s1.title AS subjectParentTitle, s2.title AS subjectTitle </sql> <sql id="tables"> course c LEFT JOIN teacher t ON c.teacher_id = t.id LEFT JOIN subject s1 ON c.subject_parent_id = s1.id LEFT JOIN subject s2 ON c.subject_id = s2.id </sql> </mapper>
(1)查看路由文件
(2)创建js文件定义接口
import request from '@/utils/request' const api_name = '/api/vod/course' export default { // 课程分页列表 findPage(subjectParentId, pageNo, pageSize) { return request({ url: `${api_name}/${subjectParentId}/${pageNo}/${pageSize}`, method: 'get' }) }, // 课程详情 getInfo(courseId) { return request({ url: `${api_name}/getInfo/${courseId}`, method: 'get' }) } }
(3)编写页面
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4FTS9wZD-1677738012823)(.\images\image-20220304095631556.png)]
course.vue
<template> <div> <van-image width="100%" height="200" src="https://cdn.uviewui.com/uview/swiper/1.jpg"/> <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.pages; this.loading = false; if(this.pageNo >= this.pages) { this.finished = true; } this.pageNo++; }); }, info(id) { this.$router.push({path: '/courseInfo/' + 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' 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; }); }, buy() { this.$router.push({ path: '/trade/'+this.courseId }) }, play(video) { }, see() { this.$router.push({ path: '/play/'+this.courseId+'/0' }) } } }; </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
@Api(tags = "腾讯视频点播") @RestController @RequestMapping("/api/vod") public class VodApiController { @Autowired private VodService vodService; /** * 点播视频播放接口 * @param courseId * @param videoId * @return */ @GetMapping("getPlayAuth/{courseId}/{videoId}") public Result getPlayAuth( @ApiParam(value = "课程id", required = true) @PathVariable Long courseId, @ApiParam(value = "小节id", required = true) @PathVariable Long videoId) { Map<String,Object> map=vodService.getPlayAuth(courseId, videoId); return Result.ok(map); } }
(3)application.properties添加
tencent.video.appid=1312624373
(3)VodService创建方法
//获取视频播放凭证 点播视频播放接口
Map<String,Object> getPlayAuth(Long courseId, Long videoId);
(4)VodServiceImpl实现方法
@Value("${tencent.video.appid}")//读取配置文件定义的数据 private String appId; //点播视频播放接口 @Override public Map<String, Object> getPlayAuth(Long courseId, Long videoId) { //根据小节id获取小节对象,获取腾讯云视频id Video video = videoService.getById(videoId); if(video == null) { throw new GgktException(20001,"小节信息不存在"); } Map<String, Object> map = new HashMap<>(); map.put("videoSourceId",video.getVideoSourceId()); map.put("appId",appId);//配置文件中的appid return map; }
(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方法
play(video) {
let videoId = video.id;
let isFree = video.isFree;
this.$router.push({ path: '/play/'+this.courseId+'/'+videoId })
},
(3)index.html引入文件
<link href="//cloudcache.tencent-cloud.com/open/qcloud/video/tcplayer/tcplayer.css" rel="stylesheet">
<!-- 如需在IE8、9浏览器中初始化播放器,浏览器需支持Flash并在页面中引入 -->
<!--[if lt IE 9]>
<script src="//cloudcache.tencent-cloud.com/open/qcloud/video/tcplayer/ie8/videojs-ie8.js"></script>
<![endif]-->
<!-- 如果需要在 Chrome 和 Firefox 等现代浏览器中通过 H5 播放 HLS 格式的视频,需要在 tcplayer.v4.1.min.js 之前引入 hls.min.0.13.2m.js -->
<script src="//imgcache.qq.com/open/qcloud/video/tcplayer/libs/hls.min.0.13.2m.js"></script>
<!-- 引入播放器 js 文件 -->
<script src="//imgcache.qq.com/open/qcloud/video/tcplayer/tcplayer.v4.1.min.js"></script>
(4)创建play.vue页面
<template> <div> <video id="player-container-id" preload="auto" width="600" height="400" 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; //获取第一个播放视频id // this.firstVideo = this.chapterVoList[0].children[0] // if(this.videoSourceId == '0') { // this.see(this.firstVideo); // } this.loading = false; }); }, see(video) { let videoId = video.id; let isFree = video.isFree; //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) { var player = TCPlayer("player-container-id", { /**player-container-id 为播放器容器ID,必须与html中一致*/ fileID: data.videoSourceId, /**请传入需要播放的视频fileID 必须 */ appID: data.appId, /**请传入点播账号的子应用appID 必须 */ 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
@RestController
@RequestMapping("api/order/orderInfo")
public class OrderInfoApiController {
@Autowired
private OrderInfoService orderInfoService;
@ApiOperation("新增点播课程订单")
@PostMapping("submitOrder")
public Result submitOrder(@RequestBody OrderFormVo orderFormVo, HttpServletRequest request) {
//返回订单id
Long orderId = orderInfoService.submitOrder(orderFormVo);
return Result.ok(orderId);
}
}
OrderFormVo
@RestController @RequestMapping("api/order/orderInfo") public class OrderInfoApiController { @Autowired private OrderInfoService orderInfoService; /** * 生成订单方法 * @param orderFormVo * @param request * @return */ @ApiOperation("新增点播课程订单") @PostMapping("submitOrder") public Result submitOrder(@RequestBody OrderFormVo orderFormVo, HttpServletRequest request) { //返回订单id Long orderId = orderInfoService.submitOrder(orderFormVo); return Result.ok(orderId); } }
(2)编写Service
OrderInfoService
//生成点播课程订单
Long submitOrder(OrderFormVo orderFormVo);
实现类:
操作service_vod模块
(1)CourseApiController添加方法
//根据课程id查询课程信息(返回course不返回result是因为这要远程调用取值方便)
@ApiOperation("根据ID查询课程")
@GetMapping("inner/getById/{courseId}")
public Course getById(
@ApiParam(value = "课程ID", required = true)
@PathVariable Long courseId){
return courseService.getById(courseId);
}
(2)service_course_client定义方法
@ApiOperation("根据ID查询课程")
@GetMapping("/api/vod/course/inner/getById/{courseId}")
Course getById(@PathVariable Long courseId);
操作service_activity模块
(1)创建CouponInfoApiController
@Api(tags = "优惠券接口") @RestController @RequestMapping("/api/activity/couponInfo") public class CouponInfoApiController { @Autowired private CouponInfoService couponInfoService; //根据优惠券id查询(不返回reslut的原因是因为这要好取值) @ApiOperation(value = "获取优惠券") @GetMapping(value = "inner/getById/{couponId}") public CouponInfo getById(@PathVariable("couponId") Long couponId) { return couponInfoService.getById(couponId); } //更新优惠券使用状态 @ApiOperation(value = "更新优惠券使用状态") @GetMapping(value = "inner/updateCouponInfoUseStatus/{couponUseId}/{orderId}") public Boolean updateCouponInfoUseStatus(@PathVariable("couponUseId") Long couponUseId, @PathVariable("orderId") Long orderId) { couponInfoService. updateCouponInfoUseStatus(couponUseId, orderId); return true; } }
(2)编写CouponInfoService
//更新优惠券使用状态
@Override
public void updateCouponInfoUseStatus(Long couponUseId, Long orderId) {
CouponUse couponUse = new CouponUse();
couponUse.setId(couponUseId);
couponUse.setOrderId(orderId);
couponUse.setCouponStatus("1");//1表示已经使用
couponUse.setUsingTime(new Date());
couponUseService.updateById(couponUse);
}
(3)创建service-activity-client模块定义接口
@FeignClient(value = "service-activity")
public interface CouponInfoFeignClient {
@ApiOperation(value = "获取优惠券")
@GetMapping(value = "/api/activity/couponInfo/inner/getById/{couponId}")
CouponInfo getById(@PathVariable("couponId") Long couponId);
/**
* 更新优惠券使用状态
*/
@GetMapping(value = "/api/activity/couponInfo/inner/updateCouponInfoUseStatus/{couponUseId}/{orderId}")
Boolean updateCouponInfoUseStatus(@PathVariable("couponUseId") Long couponUseId, @PathVariable("orderId") Long orderId);
}
绿色部分为前端做法,localStorage相当于后端cookie
灰色部分为后端做法
(1)common模块引入依赖
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- spring2.X集成redis所需common-pool2-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.6.0</version>
</dependency>
(2)复制工具类到common下的service_utils模块
(3)前端实现方式
......
// http request 拦截器
service.interceptors.request.use(config => {
//获取localStorage里面的token值
let token = window.localStorage.getItem('token') || '';
if (token != '') {
//把token值放到header里面
config.headers['token'] = token;
}
return config
},
err => {
return Promise.reject(err);
})
.......
(1)service_order引入依赖(远程调用模块在此模块中引入进去)
<dependencies> <dependency> <groupId>com.atguigu</groupId> <artifactId>service_course_client</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> <dependency> <groupId>com.atguigu</groupId> <artifactId>service_user_client</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> <dependency> <groupId>com.atguigu</groupId> <artifactId>service_activity_client</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> </dependencies>
(2)OrderInfoServiceImpl
@Autowired private CourseFeignClient courseFeignClient; @Autowired private UserInfoFeignClient userInfoFeignClient; @Autowired private CouponInfoFeignClient couponInfoFeignClient; //生成订单方法 @Override public Long submitOrder(OrderFormVo orderFormVo) { //1获取生成订单条件值 Long courseId = orderFormVo.getCourseId(); Long couponId = orderFormVo.getCouponId(); //引入的那个工具类去获取用户id Long userId = AuthContextHolder.getUserId(); //2判断当前用户是否已经生成订单 LambdaQueryWrapper<OrderDetail> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(OrderDetail::getCourseId, courseId); queryWrapper.eq(OrderDetail::getUserId, userId); OrderDetail orderDetailExist = orderDetailService.getOne(queryWrapper); if(orderDetailExist != null){ return orderDetailExist.getId(); //如果订单已存在,则直接返回订单id } //3 根据课程id查询课程信息(远程调用) Course course = courseFeignClient.getById(courseId); if (course == null) { throw new GgktException(ResultCodeEnum.DATA_ERROR.getCode(), ResultCodeEnum.DATA_ERROR.getMessage()); } //4根据用户id查询用户信息(远程调用) UserInfo userInfo = userInfoFeignClient.getById(userId); if (userInfo == null) { throw new GgktException(ResultCodeEnum.DATA_ERROR.getCode(), ResultCodeEnum.DATA_ERROR.getMessage()); } //5根据优惠券id查询优惠券信息(远程调用) //涉及金额要用BigDecimal 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); //实际价格等于初始价格-优惠券价格 subtract为减 orderInfo.setFinalAmount(orderInfo.getOriginAmount().subtract(orderInfo.getCouponReduce())); //流水号(订单号) orderInfo.setOutTradeNo(OrderNoUtils.getOrderNo()); orderInfo.setTradeBody(course.getTitle()); orderInfo.setOrderStatus("0"); this.save(orderInfo); //6.1 封装数据到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)); //实际价格等于初始价格-优惠券价格 subtract为减 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(); }
AuthContextHolder工具类:
/** * 获取登录用户信息类 * */ public class AuthContextHolder { //后台管理用户id private static ThreadLocal<Long> adminId = new ThreadLocal<Long>(); //会员用户id private static ThreadLocal<Long> userId = new ThreadLocal<Long>(); public static Long getAdminId() { return adminId.get(); } public static void setAdminId(Long _adminId) { adminId.set(_adminId); } public static Long getUserId(){ return userId.get(); } public static void setUserId(Long _userId){ userId.set(_userId); } }
OrderNoUtils(订单号工具类)
/** * 订单号工具类 * * @author qy * @since 1.0 */ public class OrderNoUtils { /** * 获取订单号 * @return */ public static String getOrderNo() { SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss"); String newDate = sdf.format(new Date()); String result = ""; Random random = new Random(); for (int i = 0; i < 3; i++) { result += random.nextInt(10); } return newDate + result; } }
ResultCodeEnum(统一返回结果状态信息类)
/** * 统一返回结果状态信息类 * */ @Getter public enum ResultCodeEnum { SUCCESS(200,"成功"), FAIL(201, "失败"), SERVICE_ERROR(2012, "服务异常"), DATA_ERROR(204, "数据异常"), ILLEGAL_REQUEST(205, "非法请求"), REPEAT_SUBMIT(206, "重复提交"), LOGIN_AUTH(208, "未登陆"), PERMISSION(209, "没有权限"), PHONE_CODE_ERROR(211, "手机验证码错误"), MTCLOUD_ERROR(210, "直播接口异常"), COUPON_GET(220, "优惠券已经领取"), COUPON_LIMIT_GET(221, "优惠券已发放完毕"), FILE_UPLOAD_ERROR( 21004, "文件上传错误"), FILE_DELETE_ERROR( 21005, "文件刪除错误"), VOD_PALY_ERROR(209, "请购买后观看"),; private Integer code; private String message; private ResultCodeEnum(Integer code, String message) { this.code = code; this.message = message; } }
接口文档:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_1
(1)绑定域名
与微信分享一致
先登录微信公众平台进入“设置与开发”,“公众号设置”的“功能设置”里填写“JS接口安全域名”。
说明:因为测试号不支持支付功能,需要使用正式号才能进行测试。
(2)商户平台配置支付目录
(1)创建WXPayController
@Api(tags = "微信支付接口") @RestController @RequestMapping("/api/order/wxPay") public class WXPayController { @Autowired private WXPayService wxPayService; @ApiOperation(value = "下单 小程序支付") @GetMapping("/createJsapi/{orderNo}") public Result createJsapi( @ApiParam(name = "orderNo", value = "订单No", required = true) @PathVariable("orderNo") String orderNo) { return Result.ok(wxPayService.createJsapi(orderNo)); } }
(2)创建WXPayService
public interface WXPayService {
Map createJsapi(String orderNo);
}
(3)service_order引入依赖
<dependency>
<groupId>com.github.wxpay</groupId>
<artifactId>wxpay-sdk</artifactId>
<version>0.0.3</version>
</dependency>
(4)创建WXPayServiceImpl
@Service @Slf4j public class WXPayServiceImpl implements WXPayService { @Autowired private OrderInfoService orderInfoService; @Resource private UserInfoFeignClient userInfoFeignClient; //微信支付(要根据官方文档去开发这里写的只是一种场景) @Override public Map<String, String> createJsapi(String orderNo) { try { //封装微信支付需要参数,使用map集合 Map<String, String> paramMap = new HashMap(); //1、设置参数(最好写到配置文件,然后@Value去获取值) //正式服务号id:(根据自己的改) paramMap.put("appid", "wxf913bfa3a2c7eeeb"); //服务号商户号(根据自己的改) paramMap.put("mch_id", "1481962542"); //引入依赖WXPayUtil paramMap.put("nonce_str", WXPayUtil.generateNonceStr()); //支付时弹框微信显示的内容,这里写的是test根据实际场景自己定义去 paramMap.put("body", "test"); //订单号 paramMap.put("out_trade_no", orderNo); //支付金额为了测试支付0.01元 paramMap.put("total_fee", "1"); //支付客户端的id,这里是本地所以写的是本地根据实际场景写 paramMap.put("spbill_create_ip", "127.0.0.1"); //支付之后的跳转 paramMap.put("notify_url", "http://glkt.atguigu.cn/api/order/wxPay/notify"); //支付类型,按照生成固定金额支付,自己看文档去选择特定的支付类型 paramMap.put("trade_type", "JSAPI"); /** * 设置参数值当前微信用户openid * 目前实现逻辑:1 根据订单号获取userid 2根据userid获取openid(注释掉的那部分代码原理) * * * 因为当前使用测试号,测试号不支持支付功能为了使用正式服务号测试, * 采用下面写法(正式项目不这么写,是注释掉的那些代码写法)获取正式服务号微信openid * 通过其他方式获取正式服务号openid,直接设置 */ // paramMap.put("openid", "o1R-t5trto9c5sdYt6l1ncGmY5Y"); //UserInfo userInfo = userInfoFeignClient.getById(paymentInfo.getUserId()); // paramMap.put("openid", "oepf36SawvvS8Rdqva-Cy4flFFg"); paramMap.put("openid", "oQTXC56lAy3xMOCkKCImHtHoLL"); //2、HTTPClient来根据URL访问第三方接口并且传递参数 HttpClientUtils client = new HttpClientUtils("https://api.mch.weixin.qq.com/pay/unifiedorder"); //client设置参数(商户keyMXb72b9RfshXZD4FRGV5KLqmv5bx9LT9根据自己的改) //因为使用setXmlParam方法所以要把map集合转为xml格式WXPayUtil.generateSignedXml方法实现 client.setXmlParam(WXPayUtil.generateSignedXml(paramMap, "MXb72b9RfshXZD4FRGV5KLqmv5bx9LT9")); //表示支持https协议 client.setHttps(true); //请求 client.post(); //3、微信支付接口返回第三方的数据 String xml = client.getContent(); //WXPayUtil.xmlToMap转为map集合 Map<String, String> resultMap = WXPayUtil.xmlToMap(xml); if(null != resultMap.get("result_code") && !"SUCCESS".equals(resultMap.get("result_code"))) { System.out.println("error1"); } //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, String> 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); return result; } catch (Exception e) { e.printStackTrace(); return new HashMap<>(); } } }
HttpClientUtils(工具类)
/** * http请求客户端 */ public class HttpClientUtils { private String url; private Map<String, String> param; private int statusCode; private String content; private String xmlParam; private boolean isHttps; public boolean isHttps() { return isHttps; } public void setHttps(boolean isHttps) { this.isHttps = isHttps; } public String getXmlParam() { return xmlParam; } public void setXmlParam(String xmlParam) { this.xmlParam = xmlParam; } public HttpClientUtils(String url, Map<String, String> param) { this.url = url; this.param = param; } public HttpClientUtils(String url) { this.url = url; } public void setParameter(Map<String, String> map) { param = map; } public void addParameter(String key, String value) { if (param == null) param = new HashMap<String, String>(); param.put(key, value); } public void post() throws ClientProtocolException, IOException { HttpPost http = new HttpPost(url); setEntity(http); execute(http); } public void put() throws ClientProtocolException, IOException { HttpPut http = new HttpPut(url); setEntity(http); execute(http); } public void get() throws ClientProtocolException, IOException { if (param != null) { StringBuilder url = new StringBuilder(this.url); boolean isFirst = true; for (String key : param.keySet()) { if (isFirst) { url.append("?"); isFirst = false; }else { url.append("&"); } url.append(key).append("=").append(param.get(key)); } this.url = url.toString(); } HttpGet http = new HttpGet(url); execute(http); } /** * set http post,put param */ private void setEntity(HttpEntityEnclosingRequestBase http) { if (param != null) { List<NameValuePair> nvps = new LinkedList<NameValuePair>(); for (String key : param.keySet()) nvps.add(new BasicNameValuePair(key, param.get(key))); // 参数 http.setEntity(new UrlEncodedFormEntity(nvps, Consts.UTF_8)); // 设置参数 } if (xmlParam != null) { http.setEntity(new StringEntity(xmlParam, Consts.UTF_8)); } } private void execute(HttpUriRequest http) throws ClientProtocolException, IOException { CloseableHttpClient httpClient = null; try { if (isHttps) { SSLContext sslContext = new SSLContextBuilder() .loadTrustMaterial(null, new TrustStrategy() { // 信任所有 public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException { return true; } }).build(); SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory( sslContext); httpClient = HttpClients.custom().setSSLSocketFactory(sslsf) .build(); } else { httpClient = HttpClients.createDefault(); } CloseableHttpResponse response = httpClient.execute(http); try { if (response != null) { if (response.getStatusLine() != null) statusCode = response.getStatusLine().getStatusCode(); HttpEntity entity = response.getEntity(); // 响应内容 content = EntityUtils.toString(entity, Consts.UTF_8); } } finally { response.close(); } } catch (Exception e) { e.printStackTrace(); } finally { httpClient.close(); } } public int getStatusCode() { return statusCode; } public String getContent() throws ParseException, IOException { return content; } }
@Slf4j
(1)修改service-user模块配置文件(这是正式号的以前用的测试号,这里只是用正式号去测试一下所以改了)
wechat.mpAppId: wxf913bfa3a2c7eeeb
## 硅谷课堂微信公众平台api秘钥
wechat.mpAppSecret: cd360d429e5c8db0c638d5ef9df74f6d
(2)service-user模块创建controller(用来之前WXPayServiceImpl)
@Controller @RequestMapping("/api/user/openid") public class GetOpenIdController { @Autowired private WxMpService wxMpService; @GetMapping("/authorize") public String authorize(@RequestParam("returnUrl") String returnUrl, HttpServletRequest request) { String userInfoUrl = "http://ggkt.vipgz1.91tunnel.com/api/user/openid/userInfo"; String redirectURL = wxMpService .oauth2buildAuthorizationUrl(userInfoUrl, WxConsts.OAUTH2_SCOPE_USER_INFO, URLEncoder.encode(returnUrl.replace("guiguketan", "#"))); return "redirect:" + redirectURL; } @GetMapping("/userInfo") @ResponseBody public String userInfo(@RequestParam("code") String code, @RequestParam("state") String returnUrl) throws Exception { WxMpOAuth2AccessToken wxMpOAuth2AccessToken = this.wxMpService.oauth2getAccessToken(code); String openId = wxMpOAuth2AccessToken.getOpenId(); System.out.println("【微信网页授权】openId={}"+openId); return openId; } }
(3)修改前端App.vue
......
if (token == '') {
let url = window.location.href.replace('#', 'guiguketan')
//修改认证controller路径
window.location = 'http://ggkt.vipgz1.91tunnel.com/api/user/openid/authorize?returnUrl=' + url
}
......
(4)复制返回的openid到支付接口中测试
(1)trade.vue
<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() { debugger 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() { //debugger couponApi.findCouponInfo().then(response => { // console.log(response.data); this.coupons = response.data.abledCouponsList; this.disabledCoupons = response.data.disabledCouponsList; }); }, sureOrder() { //debugger 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>
(2)pay.vue
<template> <div> <van-image width="100%" height="200" src="https://cdn.uviewui.com/uview/swiper/1.jpg"/> <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 == 'UNPAID' ? '未支付' : '已支付' }}</div> </div> <div class="course_teacher_price_box" v-if="orderInfo.orderStatus == 'PAID'"> <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; 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', { 'appId': data.appId, //公众号ID,由商户传入 'timeStamp': data.timeStamp, //时间戳,自1970年以来的秒数 '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: '/courseInfo/' + 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添加方法
//根据订单id获取订单信息
@ApiOperation(value = "获取")
@GetMapping("getInfo/{id}")
public Result getInfo(@PathVariable Long id) {
OrderInfoVo orderInfoVo = orderInfoService.getOrderInfoVoById(id);
return Result.ok(orderInfoVo);
}
(2)OrderInfoServiceImpl实现方法
//根据订单id获取订单信息
@Override
public OrderInfoVo getOrderInfoVoById(Long id) {
//订单id查询订单基本信息和详情信息
OrderInfo orderInfo = this.getById(id);
OrderDetail orderDetail = orderDetailService.getById(id);
OrderInfoVo orderInfoVo = new OrderInfoVo();
BeanUtils.copyProperties(orderInfo, orderInfoVo);
orderInfoVo.setCourseId(orderDetail.getCourseId());
orderInfoVo.setCourseName(orderDetail.getCourseName());
return orderInfoVo;
}
OrderInfoVo:
@Data public class OrderInfoVo extends OrderInfo { @ApiModelProperty(value = "课程id") private Long courseId; @ApiModelProperty(value = "课程名称") private String courseName; @ApiModelProperty(value = "课程封面图片路径") private String cover; @ApiModelProperty(value = "总时长:分钟") private Integer durationSum; @ApiModelProperty(value = "观看进度总时长:分钟") private Integer progressSum; @ApiModelProperty(value = "观看进度") private Integer progress; }
orderinfo
@Data @ApiModel(description = "OrderInfo") @TableName("order_info") public class OrderInfo extends BaseEntity { private static final long serialVersionUID = 1L; @ApiModelProperty(value = "用户id") @TableField("user_id") private Long userId; @ApiModelProperty(value = "昵称") @TableField("nick_name") private String nickName; @TableField("phone") private String phone; @ApiModelProperty(value = "原始金额") @TableField("origin_amount") private BigDecimal originAmount; @ApiModelProperty(value = "优惠券减免") @TableField("coupon_reduce") private BigDecimal couponReduce; @ApiModelProperty(value = "最终金额") @TableField("final_amount") private BigDecimal finalAmount; @ApiModelProperty(value = "订单状态") @TableField("order_status") private String orderStatus; @ApiModelProperty(value = "订单交易编号(第三方支付用)") @TableField("out_trade_no") private String outTradeNo; @ApiModelProperty(value = "订单描述(第三方支付用)") @TableField("trade_body") private String tradeBody; @ApiModelProperty(value = "session id") @TableField("session_id") private String sessionId; @ApiModelProperty(value = "地区id") @TableField("province") private String province; @ApiModelProperty(value = "支付时间") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") @TableField("pay_time") private Date payTime; @ApiModelProperty(value = "失效时间") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") @TableField("expire_time") private Date expireTime; }
所以自己添加一个方法去验证
(1)WXPayController添加方法
/** * 查询支付状态 * @param orderNo * @return */ @ApiOperation(value = "查询支付状态") @GetMapping("/queryPayStatus/{orderNo}") public Result queryPayStatus( @ApiParam(name = "orderNo", value = "订单No", required = true) @PathVariable("orderNo") String orderNo) { System.out.println("orderNo:"+orderNo); //调用订单号调用微信接口查询支付状态 Map<String, String> resultMap = wxPayService.queryPayStatus(orderNo); //判断支付是否成功:根据微信支付状态接口判断 if (resultMap == null) {//出错 return Result.fail(null).message("支付出错"); } if ("SUCCESS".equals(resultMap.get("trade_state"))) {//如果成功 //更改订单状态(已经支付),处理支付结果 String out_trade_no = resultMap.get("out_trade_no"); System.out.println("out_trade_no:"+out_trade_no); orderInfoService.updateOrderStatus(out_trade_no); return Result.ok(null).message("支付成功"); } return Result.ok(null).message("支付中"); }
(2)WXPayServiceImpl实现方法
//更改订单状态(已经支付),处理支付结果 @Override public Map<String, String> queryPayStatus(String orderNo) { try { //1、封装微信接口需要参数,使用map Map paramMap = new HashMap<>(); //正式服务号id:(根据自己的改) paramMap.put("appid", "wxf913bfa3a2c7eeeb"); //服务号商户号(根据自己的改) paramMap.put("mch_id", "1481962542"); /** * 以下为推荐写法放入配置类读取 */ // //正式服务号id:(根据自己的改) // paramMap.put("appid", wxPayAccountConfig.getAppId()); // //服务号商户号(根据自己的改) // paramMap.put("mch_id", wxPayAccountConfig.getMchId()); 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"); //商户keyMXb72b9RfshXZD4FRGV5KLqmv5bx9LT9根据自己的改) WXPayUtil.generateSignature(paramMap, "MXb72b9RfshXZD4FRGV5KLqmv5bx9LT9"); /** * 以下为推荐写法放入配置类读取 */ //client.setXmlParam(WXPayUtil.generateSignedXml(paramMap, wxPayAccountConfig.getKey())); client.setHttps(true); client.post(); //3、返回第三方的数据(转成map) String xml = client.getContent(); Map<String, String> resultMap = WXPayUtil.xmlToMap(xml); return resultMap; } catch (Exception e) { e.printStackTrace(); } return null; }
(3)OrderInfoServiceImpl实现方法
//更改订单状态(已经支付),处理支付结果
@Override
public void updateOrderStatus(String out_trade_no) {
//根据out_trade_no查询订单
LambdaQueryWrapper<OrderInfo> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(OrderInfo::getOutTradeNo,out_trade_no);
OrderInfo orderInfo = baseMapper.selectOne(wrapper);
//更新订单状态 1 已经支付
orderInfo.setOrderStatus("1");
baseMapper.updateById(orderInfo);
}
硅谷课堂会定期推出直播课程,方便学员与名师之间的交流互动,在直播间老师可以推荐点播课程(类似直播带货),学员可以点赞交流,购买推荐的点播课程。
一个完整直播实现流程:
1.采集、2.滤镜处理、3.编码、4.推流、5.CDN分发、6.拉流、7.解码、8.播放、9.聊天互动。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6VYJfOl6-1677738012830)(./images/20200912172809543.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KX93WAVW-1677738012831)(./images/20200912172228526.png)]
根据上面的综合对比和调研,我们最终选择了“欢拓与直播平台”,它为我们提供了完整的可以直接使用的示例代码,方便我们开发对接。
欢拓是一家以直播技术为核心的网络平台,旨在帮助人们通过网络也能实现真实互动通讯。从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、在观众一栏点击进入,即可在网页端观看直播。
上面的体验完全能够满足我们业务的需要,硅谷课堂的需求是定期推出直播课程,方便学员与名师之间的交流互动,在直播间老师可以推荐点播课程(类似直播带货),学员可以点赞交流,购买推荐的点播课程。
直播平台只是做了直播相关的业务,不能与我们的业务进行衔接,我们期望是在硅谷课堂的管理后台管理直播相关的业务,那么怎么做呢?对接直播业务接口,直播平台有对应的直播接口,我们直接对接即可。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。