前台讲师列表和详情(后端+前端) 讲师分页列表(后端) controller 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @RestController @RequestMapping("/eduservice/teacherfront") @CrossOrigin public class TeacherController { @Autowired private EduTeacherService teacherService; @GetMapping("getTeacherFront/{pageNo}/{limit}") public R getTeacherFront (@PathVariable Long limit, @PathVariable Long pageNo) { Page<EduTeacher> pageTeacher = new Page<>(pageNo, limit); Map<String, Object> map = teacherService.getTeacherFront(pageTeacher); return R.ok().data(map); } }
service 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @Service public class EduTeacherServiceImpl extends ServiceImpl <EduTeacherMapper , EduTeacher > implements EduTeacherService { @Override @Cacheable(key = "'indexTeacherList'", value = "teacher") public List<EduTeacher> getIndexList () { return baseMapper.selectIndexList(); } @Override public Map<String, Object> getTeacherFront (Page<EduTeacher> pageTeacher) { QueryWrapper wrapper = new QueryWrapper<>(); wrapper.orderByDesc("id" ); baseMapper.selectPage(pageTeacher, wrapper); Map<String, Object> map = new HashMap<>(); map.put("records" , pageTeacher.getRecords()); map.put("total" , pageTeacher.getTotal()); map.put("size" , pageTeacher.getSize()); map.put("current" , pageTeacher.getCurrent()); map.put("pages" , pageTeacher.getPages()); map.put("hasNext" , pageTeacher.hasNext()); map.put("hasPrevious" , pageTeacher.hasPrevious()); return map; } }
讲师分页列表(前端) api 1 2 3 4 5 6 7 8 9 10 import request from '@/utils/request' export default { getTeacherList (pageNo, limit ) { return request({ url : `/eduservice/teacherfront/getTeacherFront/${pageNo} /${limit} ` , method : 'get' , }) } }
vue 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 <template> <div id="aCoursesList" class="bg-fa of"> <!-- 讲师列表 开始 --> <section class="container"> <header class="comm-title all-teacher-title"> <h2 class="fl tac"> <span class="c-333">全部讲师</span> </h2> <section class="c-tab-title"> <a id="subjectAll" title="全部" href="#">全部</a> </section> </header> <section class="c-sort-box unBr"> <div> <!-- /无数据提示 开始--> <section class="no-data-wrap" v-if="listMap.total == 0"> <em class="icon30 no-data-ico"> </em> <span class="c-666 fsize14 ml10 vam" >没有相关数据,小编正在努力整理中...</span > </section> <!-- /无数据提示 结束--> <article class="i-teacher-list" v-if="listMap.total > 0"> <ul class="of"> <li v-for="teacher in listMap.records" :key="teacher.id"> <section class="i-teach-wrap"> <div class="i-teach-pic"> <a :href="'/teacehr/'+teacher.id" :title="teacher.name" target="_blank"> <img :src="teacher.avatar" alt /> </a> </div> <div class="mt10 hLh30 txtOf tac"> <a :href="'/teacehr/'+teacher.id" :title="teacher.name" target="_blank" class="fsize18 c-666" >{{ teacher.name }}</a > </div> <div class="hLh30 txtOf tac"> <span class="fsize14 c-999">{{ teacher.intro }}</span> </div> <div class="mt15 i-q-txt"> <p class="c-999 f-fA">{{ teacher.career }}</p> </div> </section> </li> </ul> <div class="clear"></div> </article> </div> </section> </section> <!-- /讲师列表 结束 --> </div> </template> <script> import teacherApi from '@/api/teacher' export default { // 这个页面模板没有使用element-ui的分页条,所以写法有所变化 // 使用异步调用(this.$route.param == params获取url中的参数值) // 该方法只调用一次 asyncData({ params, error }) { return teacherApi.getTeacherList(1, 8).then(result => { // 这里是赋值的简写方式 return { listMap: result.data.data } }) } } </script>
分页条 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 <template> <div id="aCoursesList" class="bg-fa of"> <!-- 公共分页 开始 --> <div> <div class="paging"> <!-- undisable这个class是否存在,取决于数据属性hasPrevious --> <a :class="{ undisable: !listMap.hasPrevious }" href="#" title="首页" @click.prevent="gotoPage(1)" >首页</a > <a :class="{ undisable: !listMap.hasPrevious }" href="#" title="前一页" @click.prevent="gotoPage(listMap.current - 1)" ><</a > <a v-for="page in listMap.pages" :key="page" :class="{ current: listMap.current == page, undisable: listMap.current == page, }" :title="'第' + page + '页'" href="#" @click.prevent="gotoPage(page)" >{{ page }}</a > <a :class="{ undisable: !listMap.hasNext }" href="#" title="后一页" @click.prevent="gotoPage(listMap.current + 1)" >></a > <a :class="{ undisable: !listMap.hasNext }" href="#" title="末页" @click.prevent="gotoPage(listMap.pages)" >末页</a > <div class="clear" /> </div> </div> <!-- 公共分页 结束 --> </div> </template> <script> import teacherApi from '@/api/teacher' export default { methods: { // 分页切换 gotoPage(page) { teacherApi.getTeacherList(page, 8).then(result => { this.listMap = result.data.data }) } } } </script>
讲师详情(后端) 1 2 3 4 5 6 7 8 9 @GetMapping("getTeacherInfoFront/{teacherId}") public R getTeacherInfoFront (@PathVariable String teacherId) { EduTeacher teacherInfo = teacherService.getById(teacherId); QueryWrapper wrapper = new QueryWrapper<>(); wrapper.eq("teacher_id" , teacherId); List<EduCourse> courseList = courseService.list(wrapper); return R.ok().data("teacherInfo" , teacherInfo).data("courseList" , courseList); }
讲师详情(前端) api 1 2 3 4 5 6 7 getTeacherInfo (teacherId ) { return request({ url : `/eduservice/teacherfront/getTeacherInfoFront/${teacherId} ` , method : 'get' , }) }
vue
注意NUXT的动态变量的页面为_xxx.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 <template> <div id="aCoursesList" class="bg-fa of"> <!-- 讲师介绍 开始 --> <section class="container"> <header class="comm-title"> <h2 class="fl tac"> <span class="c-333">讲师介绍</span> </h2> </header> <div class="t-infor-wrap"> <!-- 讲师基本信息 --> <section class="fl t-infor-box c-desc-content"> <div class="mt20 ml20"> <section class="t-infor-pic"> <img :src="teacherInfo.avatar" /> </section> <h3 class="hLh30"> <span class="fsize24 c-333" >{{ teacherInfo.name }} {{ teacherInfo.level === 1 ? "高级讲师" : "首席讲师" }}</span > </h3> <section class="mt10"> <span class="t-tag-bg">{{ teacherInfo.career }}</span> </section> <section class="t-infor-txt"> <p class="mt20"> {{ teacherInfo.intro }} </p> </section> <div class="clear"></div> </div> </section> <div class="clear"></div> </div> <section class="mt30"> <div> <header class="comm-title all-teacher-title c-course-content"> <h2 class="fl tac"> <span class="c-333">主讲课程</span> </h2> <section class="c-tab-title"> <a href="javascript: void(0)"> </a> </section> </header> <!-- /无数据提示 开始--> <section class="no-data-wrap" v-if="courseList.length == 0"> <em class="icon30 no-data-ico"> </em> <span class="c-666 fsize14 ml10 vam" >没有相关数据,小编正在努力整理中...</span > </section> <!-- /无数据提示 结束--> <article class="comm-course-list" v-if="courseList.length > 0"> <ul class="of"> <li v-for="course in courseList" :key="course.id"> <div class="cc-l-wrap"> <section class="course-img"> <img :src="course.cover" class="img-responsive" /> <div class="cc-mask"> <a href="#" title="开始学习" target="_blank" class="comm-btn c-btn-1" >开始学习</a > </div> </section> <h3 class="hLh30 txtOf mt10"> <a href="#" :title="course.title" target="_blank" class="course-title fsize18 c-333" >{{ course.title }}</a > </h3> </div> </li> </ul> <div class="clear"></div> </article> </div> </section> </section> <!-- /讲师介绍 结束 --> </div> </template> <script> import teacherApi from '@/api/teacher' export default { asyncData({ params, error }) { // 注意这里的params后跟的名字要和vue文件名对应(所以他是只能传一个参数吗?) return teacherApi.getTeacherInfo(params.id).then(result => { return { teacherInfo: result.data.data.teacherInfo, courseList: result.data.data.courseList } }) } } </script>
前台课程列表和详情(后端+前端) 课程列表(后端) vo类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @Data public class CourseFrontVo { @ApiModelProperty(value = "课程名称") private String title; @ApiModelProperty(value = "讲师id") private String teacherId; @ApiModelProperty(value = "一级类别id") private String subjectParentId; @ApiModelProperty(value = "二级类别id") private String subjectId; @ApiModelProperty(value = "销量排序") private String buyCountSort; @ApiModelProperty(value = "最新时间排序") private String gmtCreateSort; @ApiModelProperty(value = "价格排序") private String priceSort; }
controller 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @RestController @CrossOrigin @RequestMapping("/eduservice/coursefront") public class CourseController { @Autowired private EduCourseService courseService; @PostMapping("getCourseList/{pageNo}/{limit}") public R getCourseListCondition (@PathVariable Long limit, @PathVariable Long pageNo, @RequestBody(required = false) CourseFrontVo courseFrontVo) { Page<EduCourse> pageCourse = new Page<>(pageNo, limit); Map<String, Object> map = courseService.getCOurseFrontList(pageCourse, courseFrontVo); return R.ok().data(map); } }
service 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 @Override public Map<String, Object> getCourseFrontList (Page<EduCourse> pageCourse, CourseFrontVo courseFrontVo) { QueryWrapper wrapper = new QueryWrapper(); if (!StringUtils.isEmpty(courseFrontVo.getSubjectParentId())) { wrapper.eq("subject_parent_id" , courseFrontVo.getSubjectParentId()); } if (!StringUtils.isEmpty(courseFrontVo.getSubjectId())) { wrapper.eq("subject_id" , courseFrontVo.getSubjectId()); } if (!StringUtils.isEmpty(courseFrontVo.getBuyCountSort())) { wrapper.orderByDesc("buy_count" ); } if (!StringUtils.isEmpty(courseFrontVo.getGmtCreateSort())) { wrapper.orderByDesc("gmt_create" ); } if (!StringUtils.isEmpty(courseFrontVo.getPriceSort())) { wrapper.orderByDesc("price" ); } baseMapper.selectPage(pageCourse, wrapper); List<EduCourse> records = pageCourse.getRecords(); long current = pageCourse.getCurrent(); long pages = pageCourse.getPages(); long size = pageCourse.getSize(); long total = pageCourse.getTotal(); boolean hasNext = pageCourse.hasNext(); boolean hasPrevious = pageCourse.hasPrevious(); Map<String, Object> map = new HashMap<>(); map.put("items" , records); map.put("current" , current); map.put("pages" , pages); map.put("size" , size); map.put("total" , total); map.put("hasNext" , hasNext); map.put("hasPrevious" , hasPrevious); return map; }
课程列表(前端) api 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import request from '@/utils/request' export default { getCourseList (pageNo, limit, queryCourse ) { return request({ url : `/eduservice/coursefront/getCourseList/${pageNo} /${limit} ` , method : 'post' , data : queryCourse }) }, getSubjectList ( ) { return request({ url : `/eduservice/subject/getAllSubjects` , method : 'get' }) } }
vue
这一块内容挺多的,有二级联动查询,有分页条,有条件排序等
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 <template> <div id="aCoursesList" class="bg-fa of"> <!-- /课程列表 开始 --> <section class="container"> <header class="comm-title"> <h2 class="fl tac"> <span class="c-333">全部课程</span> </h2> </header> <section class="c-sort-box"> <section class="c-s-dl"> <dl> <dt> <span class="c-999 fsize14">课程类别</span> </dt> <dd class="c-s-dl-li"> <ul class="clearfix"> <li> <a title="全部" href="#">全部</a> </li> <li v-for="(item, index) in subjectNestedList" :key="index" :class="{ active: oneIndex == index }" > <a :title="item.title" href="#" @click="searchOne(item.id, index)" >{{ item.title }}</a > </li> </ul> </dd> </dl> <dl> <dt> <span class="c-999 fsize14"></span> </dt> <dd class="c-s-dl-li"> <ul class="clearfix"> <li v-for="(item, index) in subSubjectList" :key="index" :class="{ active: twoIndex == index }" > <a :title="item.title" href="#" @click="searchTwo(item.id, index)" >{{ item.title }}</a > </li> </ul> </dd> </dl> <div class="clear"></div> </section> <div class="js-wrap"> <section class="fr"> <span class="c-ccc"> <i class="c-master f-fM">1</i>/ <i class="c-666 f-fM">1</i> </span> </section> <section class="fl"> <ol class="js-tap clearfix"> <li :class="{ 'current bg-orange': buyCountSort != '' }"> <a title="销量" href="javascript:void(0);" @click="searchBuyCount()" >销量 <span :class="{ hide: buyCountSort == '' }">↓</span> </a> </li> <li :class="{ 'current bg-orange': gmtCreateSort != '' }"> <a title="最新" href="javascript:void(0);" @click="searchGmtCreate()" >最新 <span :class="{ hide: gmtCreateSort == '' }">↓</span> </a> </li> <li :class="{ 'current bg-orange': priceSort != '' }"> <a title="价格" href="javascript:void(0);" @click="searchPrice()" >价格 <span :class="{ hide: priceSort == '' }">↓</span> </a> </li> </ol> </section> </div> <div class="mt40"> <!-- /无数据提示 开始--> <section class="no-data-wrap" v-if="data.total == 0"> <em class="icon30 no-data-ico"> </em> <span class="c-666 fsize14 ml10 vam" >没有相关数据,小编正在努力整理中...</span > </section> <!-- /无数据提示 结束--> <article v-if="data.total > 0" class="comm-course-list"> <ul class="of" id="bna"> <li v-for="item in data.items" :key="item.id"> <div class="cc-l-wrap"> <section class="course-img"> <img :src="item.cover" class="img-responsive" :alt="item.title" /> <div class="cc-mask"> <a :href="'/course/' + item.id" title="开始学习" class="comm-btn c-btn-1" >开始学习</a > </div> </section> <h3 class="hLh30 txtOf mt10"> <a :href="'/course/' + item.id" :title="item.title" class="course-title fsize18 c-333" >{{ item.title }}</a > </h3> <section class="mt10 hLh20 of"> <span v-if="Number(item.price) === 0" class="fr jgTag bg-green" > <i class="c-fff fsize12 f-fA">免费</i> </span> <span class="fl jgAttr c-ccc f-fA"> <i class="c-999 f-fA">9634人学习</i> | <i class="c-999 f-fA">9634评论</i> </span> </section> </div> </li> </ul> <div class="clear"></div> </article> </div> <!-- 公共分页 开始 --> <div> <div class="paging"> <!-- undisable这个class是否存在,取决于数据属性hasPrevious --> <a :class="{ undisable: !data.hasPrevious }" href="#" title="首页" @click.prevent="gotoPage(1)" >首</a > <a :class="{ undisable: !data.hasPrevious }" href="#" title="前一页" @click.prevent="gotoPage(data.current - 1)" ><</a > <a v-for="page in data.pages" :key="page" :class="{ current: data.current == page, undisable: data.current == page, }" :title="'第' + page + '页'" href="#" @click.prevent="gotoPage(page)" >{{ page }}</a > <a :class="{ undisable: !data.hasNext }" href="#" title="后一页" @click.prevent="gotoPage(data.current + 1)" >></a > <a :class="{ undisable: !data.hasNext }" href="#" title="末页" @click.prevent="gotoPage(data.pages)" >末</a > <div class="clear" /> </div> </div> </section> </section> <!-- /课程列表 结束 --> </div> </template> <script> import courseApi from '@/api/course' export default { data() { return { page: 1, // 当前页 data: {}, // 课程列表 subjectNestedList: [], // 一级分类列表 subSubjectList: [], // 二级分类列表 queryObj: {}, // 查询表单对象 oneIndex: -1, twoIndex: -1, buyCountSort: '', gmtCreateSort: '', priceSort: '' } }, created() { // 课程第一次查询 this.initCourseFirst() // 一级分类显示 this.initSubject() }, methods: { // 1 查询第一页数据 initCourseFirst() { courseApi.getCourseList(1, 8, this.queryObj).then(response => { this.data = response.data.data }) }, // 2 查询所有一级分类 initSubject() { courseApi.getSubjectList().then(response => { this.subjectNestedList = response.data.data.list }) }, // 3 分页切换的方法 gotoPage(page) { courseApi.getCourseList(page, 8, this.queryObj).then(response => { this.data = response.data.data }) }, // 4 点击某个一级分类,查询对应二级分类 searchOne(subjectParentId, index) { // 把传递index值赋值给oneIndex,为了active样式生效 this.oneIndex = index this.twoIndex = -1 this.queryObj.subjectId = '' this.subSubjectList = [] // 把一级分类点击id值,赋值给queryObj this.queryObj.subjectParentId = subjectParentId // 点击某个一级分类进行条件查询 this.gotoPage(1) // 拿着点击一级分类id 和 所有一级分类id进行比较, // 如果id相同,从一级分类里面获取对应的二级分类 for (let i = 0; i < this.subjectNestedList.length; i++) { // 获取每个一级分类 var oneSubject = this.subjectNestedList[i] // 比较id是否相同 if (subjectParentId == oneSubject.id) { // 从一级分类里面获取对应的二级分类 this.subSubjectList = oneSubject.children } } }, // 5 点击某个二级分类实现查询 searchTwo(subjectId, index) { // 把index赋值,为了样式生效 this.twoIndex = index // 把二级分类点击id值,赋值给queryObj this.queryObj.subjectId = subjectId // 点击某个二级分类进行条件查询 this.gotoPage(1) }, // 6 根据销量排序 searchBuyCount() { // 设置对应变量值,为了样式生效 this.buyCountSort = '1' this.gmtCreateSort = '' this.priceSort = '' // 把值赋值到queryObj this.queryObj.buyCountSort = this.buyCountSort this.queryObj.gmtCreateSort = this.gmtCreateSort this.queryObj.priceSort = this.priceSort // 调用方法查询 this.gotoPage(1) }, // 7 最新排序 searchGmtCreate() { // 设置对应变量值,为了样式生效 this.buyCountSort = '' this.gmtCreateSort = '1' this.priceSort = '' // 把值赋值到queryObj this.queryObj.buyCountSort = this.buyCountSort this.queryObj.gmtCreateSort = this.gmtCreateSort this.queryObj.priceSort = this.priceSort // 调用方法查询 this.gotoPage(1) }, // 8 价格排序 searchPrice() { // 设置对应变量值,为了样式生效 this.buyCountSort = '' this.gmtCreateSort = '' this.priceSort = '1' // 把值赋值到queryObj this.queryObj.buyCountSort = this.buyCountSort this.queryObj.gmtCreateSort = this.gmtCreateSort this.queryObj.priceSort = this.priceSort // 调用方法查询 this.gotoPage(1) } } } </script> <style scoped> .active { background: #bdbdbd; } .hide { display: none; } .show { display: block; } </style>
课程详情(后端) vo类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 @Data public class CourseFrontInfoVo { private String id; @ApiModelProperty(value = "课程标题") private String title; @ApiModelProperty(value = "课程销售价格,设置为0则可免费观看") private BigDecimal price; @ApiModelProperty(value = "总课时") private Integer lessonNum; @ApiModelProperty(value = "课程封面图片路径") private String cover; @ApiModelProperty(value = "销售数量") private Long buyCount; @ApiModelProperty(value = "浏览数量") private Long viewCount; @ApiModelProperty(value = "课程简介") private String description; @ApiModelProperty(value = "讲师ID") private String teacherId; @ApiModelProperty(value = "讲师姓名") private String teacherName; @ApiModelProperty(value = "讲师资历,一句话说明讲师") private String intro; @ApiModelProperty(value = "讲师头像") private String avatar; @ApiModelProperty(value = "课程一级类别ID") private String subjectLevelOneId; @ApiModelProperty(value = "类别一级名称") private String subjectLevelOne; @ApiModelProperty(value = "课程二级类别ID") private String subjectLevelTwoId; @ApiModelProperty(value = "类别二级名称") private String subjectLevelTwo; }
controller 1 2 3 4 5 6 7 @GetMapping("getCourseInfo/{courseId}") public R getCourseInfo (@PathVariable String courseId) { CourseFrontInfoVo courseInfo = courseService.getBaseCourseInfo(courseId); List<ChapterVo> chapterList = chapterService.getChapterVideoByCourseId(courseId); return R.ok().data("courseInfo" , courseInfo).data("chapterList" , chapterList); }
service 1 2 3 4 5 @Override public CourseFrontInfoVo getBaseCourseInfo (String courseId) { return baseMapper.getBaseCourseInfo(courseId); }
mapper 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <select id ="getBaseCourseInfo" resultType ="com.atguigu.eduservice.entity.frontVo.CourseFrontInfoVo" > select ec.id, ec.title, ec.price, ec.cover, ec.lesson_num AS lessonNum, ec.buy_count AS buyCount, ec.view_count AS viewCount, ecd.description, et.id AS teacherId, et.name AS teacherName, et.intro, et.avatar, es1.id AS subjectLevelOneId, es1.title AS subjectLevelOne, es2.id AS subjectLevelTwoId, es2.title AS subjectLevelTwo from edu_course ec left join edu_course_description ecd on ec.id = ecd.id left join edu_teacher et on ec.teacher_id = et.id left join edu_subject es1 on ec.subject_parent_id = es1.id left join edu_subject es2 on ec.subject_id = es2.id where ec.id = #{courseId} </select >
课程详情(前端) api 1 2 3 4 5 6 7 getCourseInfo (courseId ) { return request({ url : `/eduservice/coursefront/getCourseInfo/${courseId} ` , method : 'get' }) }
vue
注意这里的简介显示,需要使用v-html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 <template> <div id="aCoursesList" class="bg-fa of"> <!-- /课程详情 开始 --> <section class="container"> <!-- 这里是一个面包屑导航 --> <section class="path-wrap txtOf hLh30"> <a href="#" title class="c-999 fsize14">首页</a> \ <a href="#" title class="c-999 fsize14">{{ courseInfo.subjectLevelOne }}</a> \ <span class="c-333 fsize14">{{ courseInfo.subjectLevelTwo }}</span> </section> <div> <article class="c-v-pic-wrap" style="height: 357px"> <section class="p-h-video-box" id="videoPlay"> <img :src="courseInfo.cover" :alt="courseInfo.title" class="dis c-v-pic" /> </section> </article> <aside class="c-attr-wrap"> <section class="ml20 mr15"> <h2 class="hLh30 txtOf mt15"> <span class="c-fff fsize24">{{ courseInfo.title }}</span> </h2> <section class="c-attr-jg"> <span class="c-fff">价格:</span> <b class="c-yellow" style="font-size: 24px" >¥{{ courseInfo.price }}</b > </section> <section class="c-attr-mt c-attr-undis"> <span class="c-fff fsize14" >主讲: {{ courseInfo.teacherName }} </span > </section> <section class="c-attr-mt of"> <span class="ml10 vam"> <em class="icon18 scIcon"></em> <a class="c-fff vam" title="收藏" href="#">收藏</a> </span> </section> <section class="c-attr-mt"> <a href="#" title="立即观看" class="comm-btn c-btn-3">立即观看</a> </section> </section> </aside> <aside class="thr-attr-box"> <ol class="thr-attr-ol clearfix"> <li> <p> </p> <aside> <span class="c-fff f-fM">购买数</span> <br /> <h6 class="c-fff f-fM mt10">{{ courseInfo.buyCount }}</h6> </aside> </li> <li> <p> </p> <aside> <span class="c-fff f-fM">课时数</span> <br /> <h6 class="c-fff f-fM mt10">{{ courseInfo.lessonNum }}</h6> </aside> </li> <li> <p> </p> <aside> <span class="c-fff f-fM">浏览数</span> <br /> <h6 class="c-fff f-fM mt10">{{ courseInfo.viewCount }}</h6> </aside> </li> </ol> </aside> <div class="clear"></div> </div> <!-- /课程封面介绍 --> <div class="mt20 c-infor-box"> <article class="fl col-7"> <section class="mr30"> <div class="i-box"> <div> <section id="c-i-tabTitle" class="c-infor-tabTitle c-tab-title"> <a name="c-i" class="current" title="课程详情">课程详情</a> </section> </div> <article class="ml10 mr10 pt20"> <div> <h6 class="c-i-content c-infor-title"> <span>课程介绍</span> </h6> <div class="course-txt-body-wrap"> <section class="course-txt-body"> <!-- 由于当初编写的时候使用的富文本编辑器,所以这里显示要使用v-html --> <p v-html="courseInfo.description"> {{ courseInfo.description }} </p> </section> </div> </div> <!-- /课程介绍 --> <div class="mt50"> <h6 class="c-g-content c-infor-title"> <span>课程大纲</span> </h6> <section class="mt20"> <div class="lh-menu-wrap"> <menu id="lh-menu" class="lh-menu mt10 mr10"> <ul> <!-- 文件目录 --> <li class="lh-menu-stair" v-for="chapter in chapterList" :key="chapter.id" > <a href="javascript: void(0)" :title="chapter.title" class="current-1" > <em class="lh-menu-i-1 icon18 mr10"></em >{{ chapter.title }} </a> <ol class="lh-menu-ol" style="display: block"> <li class="lh-menu-second ml30" v-for="video in chapter.children" :key="video.id" > <a href="#" :title="video.title"> <span class="fr"> <i class="free-icon vam mr10">免费试听</i> </span> <em class="lh-menu-i-2 icon16 mr5"> </em >{{video.title}} </a> </li> </ol> </li> </ul> </menu> </div> </section> </div> <!-- /课程大纲 --> </article> </div> </section> </article> <aside class="fl col-3"> <div class="i-box"> <div> <section class="c-infor-tabTitle c-tab-title"> <a title href="javascript:void(0)">主讲讲师</a> </section> <section class="stud-act-list"> <ul style="height: auto"> <li> <div class="u-face"> <a href="#"> <img :src="courseInfo.avatar" width="50" height="50" /> </a> </div> <section class="hLh30 txtOf"> <a class="c-333 fsize16 fl" href="#">{{ courseInfo.teacherName }}</a> </section> <section class="hLh20 txtOf"> <span class="c-999">{{ courseInfo.intro }}</span> </section> </li> </ul> </section> </div> </div> </aside> <div class="clear"></div> </div> </section> <!-- /课程详情 结束 --> </div> </template> <script> import courseApi from '@/api/course' export default { asyncData({ params, error }) { return courseApi.getCourseInfo(params.id).then(result => { return { courseInfo: result.data.data.courseInfo, chapterList: result.data.data.chapterList } }) } } </script>
TODO:里面还有一些跳转还要完善
视频播放器
阿里云视频播放器,使用播放凭证播放
注意这里要写到service_vod模块,因为是和阿里云视频相关
后端 controller 1 2 3 4 5 6 7 8 9 10 11 12 13 @GetMapping("getPlayAuth/{id}") public R getPlayAuth (@PathVariable String id) throws ClientException { DefaultAcsClient client = InitVodClient.initVodClient(ConstantVodUtils.ACCESS_KEY_ID, ConstantVodUtils.ACCESS_KEY_SECRET); GetVideoPlayAuthRequest request = new GetVideoPlayAuthRequest(); request.setVideoId(id); GetVideoPlayAuthResponse acsResponse = client.getAcsResponse(request); String playAuth = acsResponse.getPlayAuth(); return R.ok().data("playAuth" , playAuth); }
前端
先去修改a标签的href值
:href="'/video_player/' + video.videoSourceId" target="_blank"
然后前端在pages文件夹下编写一个动态路由的vue视图,可以命名为_vid.vue,里面编写播放器的模板,这里还专门写了一个video的布局layout,cv即可
layout 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 <template> <div class="guli-player"> <div class="head"> <a href="#" title="谷粒学院"> <img class="logo" src="~/assets/img/logo.png" lt="谷粒学院"> </a></div> <div class="body"> <div class="content"><nuxt/></div> </div> </div> </template> <script> export default {} </script> <style> html,body{ height:100%; } </style> <style scoped> .head { height: 50px; position: absolute; top: 0; left: 0; width: 100%; } .head .logo{ height: 50px; margin-left: 10px; } .body { position: absolute; top: 50px; left: 0; right: 0; bottom: 0; overflow: hidden; } </style>
api 1 2 3 4 5 6 7 8 9 10 import request from '@/utils/request' export default { getPlayAuth (id ) { return request({ url : `/eduvod/video/getPlayAuth/${id} ` , method : 'get' , }) }, }
vue视图 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 <template> <div> <!-- 阿里云视频播放器样式 --> <link rel="stylesheet" href="https://g.alicdn.com/de/prismplayer/2.8.1/skins/default/aliplayer-min.css" > <!-- 阿里云视频播放器脚本 --> <script charset="utf-8" type="text/javascript" src="https://g.alicdn.com/de/prismplayer/2.8.1/aliplayer-min.js" /> <!-- 定义播放器dom --> <div id="J_prismPlayer" class="prism-player" /> </div> </template> <script> import vod from '@/api/vod' export default { layout: 'video',//应用video布局 asyncData({ params, error }) { return vod.getPlayAuth(params.vid) .then(response => { return { playAuth: response.data.data.playAuth, vid: params.vid } }) }, mounted() { //页面渲染之后 created new Aliplayer({ id: 'J_prismPlayer', vid: this.vid, // 视频id playauth: this.playAuth, // 播放凭证 encryptType: '1', // 如果播放加密视频,则需设置encryptType=1,非加密视频无需设置此项 width: '100%', height: '500px', // 以下可选设置 cover: 'http://guli.shop/photo/banner/1525939573202.jpg', // 封面 qualitySort: 'asc', // 清晰度排序 mediaType: 'video', // 返回音频还是视频 autoplay: false, // 自动播放 isLive: false, // 直播 rePlay: false, // 循环播放 preload: true, controlBarVisibility: 'hover', // 控制条的显示方式:鼠标悬停 useH5Prism: true, // 播放器类型:html5 }, function(player) { console.log('播放器创建成功') }) } } </script>
更多功能
阿里云Aliplayer播放器 (alicdn.com)
详见文档
视频评论(后端+前端)
具体实现分析:
创建课程评论表
创建后端接口
分页查询对应课程评论
添加评论
课程评论内容:输入,调用接口
课程id:进入详情页面就可以查询到课程id
用户id:根据cookie中的token查询用户信息
远程调用(edu -> ucenter)
订单与支付(后端+前端)
创建子模块service_order, application.proerties里大差不差
代码生成器常规操作
后端基础搭建 启动类
这个模块需要调用edu和UCenter模块中的方法查询课程和用户信息
1 2 3 4 5 6 7 8 9 10 @SpringBootApplication @ComponentScan(basePackages = {"com.atguigu"}) @MapperScan("com.atguigu.eduorder.mapper") @EnableDiscoveryClient @EnableFeignClients public class OrderApplication { public static void main (String[] args) { SpringApplication.run(OrderApplication.class, args); } }
依赖
微信支付接口的依赖
1 2 3 4 5 6 7 8 9 10 11 12 <dependencies > <dependency > <groupId > com.github.wxpay</groupId > <artifactId > wxpay-sdk</artifactId > <version > 0.0.3</version > </dependency > <dependency > <groupId > com.alibaba</groupId > <artifactId > fastjson</artifactId > </dependency > </dependencies >
生成订单(后端) OrderController 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @RestController @RequestMapping("/eduorder/order") @CrossOrigin public class OrderController { @Autowired private OrderService orderService; @PostMapping("createOrder/{courseId}") public R createOrder (@PathVariable String courseId, HttpServletRequest request) { String memberId = JwtUtils.getMemberIdByJwtToken(request); String orderNo = orderService.createOrder(courseId, memberId); return R.ok().data("orderNo" , orderNo); } }
实体类
注意这里要额外写一个vo类用于远程调用的时候返回
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 @Data @EqualsAndHashCode(callSuper = false) @Accessors(chain = true) @ApiModel(value = "UcenterMember对象", description = "会员表") public class UcenterMemberForOrder implements Serializable { private static final long serialVersionUID = 1L ; @ApiModelProperty(value = "会员id") @TableId(value = "id", type = IdType.ID_WORKER_STR) private String id; @ApiModelProperty(value = "微信openid") private String openid; @ApiModelProperty(value = "手机号") private String mobile; @ApiModelProperty(value = "密码") private String password; @ApiModelProperty(value = "昵称") private String nickname; @ApiModelProperty(value = "性别 1 女,2 男") private Integer sex; @ApiModelProperty(value = "年龄") private Integer age; @ApiModelProperty(value = "用户头像") private String avatar; @ApiModelProperty(value = "用户签名") private String sign; @ApiModelProperty(value = "是否禁用 1(true)已禁用, 0(false)未禁用") private Boolean isDisabled; @ApiModelProperty(value = "逻辑删除 1(true)已删除, 0(false)未删除") @TableLogic private Boolean isDeleted; @ApiModelProperty(value = "创建时间") @TableField(fill = FieldFill.INSERT) private Date gmtCreate; @ApiModelProperty(value = "更新时间") @TableField(fill = FieldFill.INSERT_UPDATE) private Date gmtModified; }
这个实体类和UCenterMember无差别,但是放在了common模块当中,所以不同模块中的controller都可以使用该类
另一个课程相关实体类与这个类似,放在common模块中即可
UcenterMemberController
编写一个被远程调用的方法,通过id获取用户信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @RestController @RequestMapping("/educenter/member") @CrossOrigin public class UcenterMemberController { @Autowired private UcenterMemberService memberService; @GetMapping("getUserInfoOrder/{id}") public UcenterMemberForOrder getUserInfoOrder (@PathVariable String id) { UcenterMember member = memberService.getById(id); UcenterMemberForOrder memberForOrder = new UcenterMemberForOrder(); BeanUtils.copyProperties(member, memberForOrder); return memberForOrder; } }
CourseFrontController 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @RestController @CrossOrigin @RequestMapping("/eduservice/coursefront") public class CourseController { @Autowired private EduCourseService courseService; @GetMapping("getCourseInfoOrder/{id}") public CourseForOrder getCourseInfoOrder (@PathVariable String id) { CourseInfoVo course = courseService.getCourseInfoById(id); CourseForOrder courseForOrder = new CourseForOrder(); BeanUtils.copyProperties(course, courseForOrder); return courseForOrder; } }
远程调用配置接口 1 2 3 4 5 6 7 @Component @FeignClient("service-edu") public interface EduClient { @GetMapping("/eduservice/coursefront/getCourseInfoOrder/{id}") public CourseForOrder getCourseInfoOrder (@PathVariable("id") String id) ; }
1 2 3 4 5 6 7 @Component @FeignClient("service-ucenter") public interface UcenterClient { @GetMapping("/educenter/member/getUserInfoOrder/{id}") public UcenterMemberForOrder getUserInfoOrder (@PathVariable("id") String id) ; }
OrderService 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 @Service public class OrderServiceImpl extends ServiceImpl <OrderMapper , Order > implements OrderService { @Autowired private EduClient eduClient; @Autowired private UcenterClient ucenterClient; @Override public String createOrder (String courseId, String memberId) { UcenterMemberForOrder userInfoOrder = ucenterClient.getUserInfoOrder(memberId); CourseForOrder courseInfoOrder = eduClient.getCourseInfoOrder(courseId); Order order = new Order(); order.setOrderNo(OrderNoUtil.getOrderNo()); order.setCourseId(courseId); order.setCourseTitle(courseInfoOrder.getTitle()); order.setCourseCover(courseInfoOrder.getCover()); order.setTeacherName(courseInfoOrder.getTeacherName()); order.setTotalFee(courseInfoOrder.getPrice()); order.setMemberId(memberId); order.setMobile(userInfoOrder.getMobile()); order.setNickname(userInfoOrder.getNickname()); order.setStatus(0 ); order.setPayType(1 ); baseMapper.insert(order); return order.getOrderNo(); } }
查询订单(后端) 1 2 3 4 5 6 7 8 @GetMapping("getOrderInfo/{orderNo}") public R getOrderInfo (@PathVariable String orderNo) { QueryWrapper<Order> wrapper = new QueryWrapper<>(); wrapper.eq("order_no" , orderNo); Order order = orderService.getOne(wrapper); return R.ok().data("orderInfo" , order); }
生成订单(前端) api 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import request from '@/utils/request' export default { createOrder (courseId ) { return request({ url : `/eduorder/order/createOrder/${courseId} ` , method : 'get' , }) }, getOrderInfo (orderNo ) { return request({ url : `/eduorder/order/getOrderInfo/${orderNo} ` , method : 'get' , }) } }
课程详情页面跳转 1 2 3 4 5 6 7 8 9 methods: { createOrder ( ) { orderApi.createOrder(this .courseId).then(result => { this .$router.push({ path : '/order/' + result.data.data.orderNo }) }) } }
动态路由页面模板
创建一个动态路由页面,_ono.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 <template> <div class="Page Confirm"> <div class="Title"> <h1 class="fl f18">订单确认</h1> <img src="~/assets/img/cart_setp2.png" class="fr" /> <div class="clear"></div> </div> <form name="flowForm" id="flowForm" method="post" action=""> <table class="GoodList"> <tbody> <tr> <th class="name">商品</th> <th class="price">原价</th> <th class="priceNew">价格</th> </tr> </tbody> <tbody> <!-- <tr> <td colspan="3" class="Title red f18 fb"><p>限时折扣</p></td> </tr> --> <tr> <td colspan="3" class="teacher">讲师:{{ order.teacherName }}</td> </tr> <tr class="good"> <td class="name First"> <a target="_blank" :href="'https://localhost:3000/course/' + order.courseId" > <img :src="order.courseCover" /></a> <div class="goodInfo"> <input type="hidden" class="ids ids_14502" value="14502" /> <a target="_blank" :href="'https://localhost:3000/course/' + order.courseId" >{{ order.courseTitle }}</a > </div> </td> <td class="price"> <p> ¥<strong>{{ order.totalFee }}</strong> </p> <!-- <span class="discName red">限时8折</span> --> </td> <td class="red priceNew Last"> ¥<strong>{{ order.totalFee }}</strong> </td> </tr> <tr> <td class="Billing tr" colspan="3"> <div class="tr"> <p> 共 <strong class="red">1</strong> 件商品,合计<span class="red f20" >¥<strong>{{ order.totalFee }}</strong></span > </p> </div> </td> </tr> </tbody> </table> <div class="Finish"> <div class="fr" id="AgreeDiv"> <label for="Agree" ><p class="on"> <input type="checkbox" checked="checked" />我已阅读并同意<a href="javascript:" target="_blank" >《谷粒学院购买协议》</a > </p></label > </div> <div class="clear"></div> <div class="Main fl"> <div class="fl"> <a :href="'/course/' + order.courseId">返回课程详情页</a> </div> <div class="fr"> <p> 共 <strong class="red">1</strong> 件商品,合计<span class="red f20" >¥<strong id="AllPrice">{{ order.totalFee }}</strong></span > </p> </div> </div> <input name="score" value="0" type="hidden" id="usedScore" /> <button class="fr redb" type="button" id="submitPay" @click="toPay()"> 去支付 </button> <div class="clear"></div> </div> </form> </div> </template> <script> import ordersApi from '@/api/order' export default { methods: {} } </script>
方法 1 2 3 4 5 6 7 8 asyncData ({ params, error } ) { return ordersApi.getOrderInfo(params.ono) .then(response => { return { orderInfo : response.data.data.orderInfo } }) },
测试
这里一直在调试,发现我的courseInfo对象为null,debug了一会发现是courseController当中的被远程调用的方法中,返回的对象vo类内的值不一致,导致BeanUtils赋值的时候赋值不了,然后改一下重启就行
生成支付二维码(后端)
好家伙又要企业认证
controller 1 2 3 4 5 6 7 8 9 10 11 12 13 14 @RestController @RequestMapping("/eduorder/pay-log") public class PayLogController { @Autowired private PayLogService logService; @GetMapping("createNative/{orderNo}") public R createNative (@PathVariable String orderNo) { Map<String, Object> map = logService.createNative(orderNo); return R.ok().data(map); } }
service主要业务逻辑 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 @Service public class PayLogServiceImpl extends ServiceImpl <PayLogMapper , PayLog > implements PayLogService { @Autowired private OrderService orderService; @Override public Map<String, Object> createNative (String orderNo) { try { QueryWrapper<Order> wrapper = new QueryWrapper<>(); wrapper.eq("order_no" , orderNo); Order order = orderService.getOne(wrapper); Map<String, String> m = new HashMap(); m.put("appid" , "wx74862e0dfcf69954" ); m.put("mch_id" , "1558950191" ); m.put("nonce_str" , WXPayUtil.generateNonceStr()); m.put("body" , order.getCourseTitle()); m.put("out_trade_no" , orderNo); m.put("total_fee" , order.getTotalFee().multiply(new BigDecimal("100" )).longValue() + "" ); m.put("spbill_create_ip" , "127.0.0.1" ); m.put("notify_url" , "http://guli.shop/api/order/weixinPay/weixinNotify\n" ); m.put("trade_type" , "NATIVE" ); HttpClient client = new HttpClient("https://api.mch.weixin.qq.com/pay/unifiedorder" ); client.setXmlParam(WXPayUtil.generateSignedXml(m, "T6m9iK73b0kn9g5v426MKfHQH7X8rKwb" )); client.setHttps(true ); client.post(); String xml = client.getContent(); Map<String, String> resultMap = WXPayUtil.xmlToMap(xml); Map map = new HashMap(); map.put("out_trade_no" , orderNo); map.put("course_id" , order.getCourseId()); map.put("total_fee" , order.getTotalFee()); map.put("result_code" , resultMap.get("result_code" )); map.put("code_url" , resultMap.get("code_url" )); return map; } catch (Exception e) { throw new GuliException(20001 , "生成二维码失败" ); } } }
查询支付状态(后端) controller 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @GetMapping("getPayStatus/{orderNo}") public R getPayStatus (@PathVariable String orderNo) { Map<String, String> map = logService.getPayStatus(orderNo); if (map == null ) { return R.error().message("支付出错了" ); } if (map.get("trade_state" ).equals("SUCCESS" )) { logService.updateOrderStatus(map); return R.ok(); } return R.ok().code(25000 ).message("支付中……" ); }
service 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 @Override public Map<String, String> getPayStatus (String orderNo) { try { Map<String, String> m = new HashMap<>(); m.put("appid" , "wx74862e0dfcf69954" ); m.put("mch_id" , "1558950191" ); m.put("out_trade_no" , orderNo); m.put("nonce_str" , WXPayUtil.generateNonceStr()); HttpClient client = new HttpClient("https://api.mch.weixin.qq.com/pay/orderquery" ); client.setXmlParam(WXPayUtil.generateSignedXml(m, "T6m9iK73b0kn9g5v426MKfHQH7X8rKwb" )); client.setHttps(true ); client.post(); String xml = client.getContent(); return WXPayUtil.xmlToMap(xml); } catch (Exception e) { e.printStackTrace(); return null ; } } @Override public void updateOrderStatus (Map<String, String> map) { String orderNo = map.get("out_trade_no" ); QueryWrapper<Order> wrapper = new QueryWrapper<>(); wrapper.eq("order_no" , orderNo); Order order = orderService.getOne(wrapper); if (order.getStatus().intValue() == 1 ) { return ; } order.setStatus(1 ); orderService.updateById(order); PayLog payLog = new PayLog(); payLog.setOrderNo(orderNo); payLog.setPayTime(new Date()); payLog.setPayType(1 ); payLog.setTotalFee(order.getTotalFee()); payLog.setTradeState(map.get("trade_state" )); payLog.setTransactionId(map.get("transaction_id" )); payLog.setAttr(JSONObject.toJSONString(map)); baseMapper.insert(payLog); }
支付(前端) api 1 2 3 4 5 6 7 8 9 10 11 12 13 14 createNative (orderNo ) { return request({ url : `/eduorder/paylog/createNative/${orderNo} ` , method : 'get' , }) }, getPayStatus (orderNo ) { return request({ url : `/eduorder/paylog/getPayStatus/${orderNo} ` , method : 'get' , }) },
跳转至页面方法 1 2 3 4 toPay ( ) { this .$router.push({ path : '/pay/' + this .orderInfo.orderNo }) }
动态路由模板 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 <template> <div class="cart py-container"> <!--主内容--> <div class="checkout py-container pay"> <div class="checkout-tit"> <h4 class="fl tit-txt"> <span class="success-icon"></span ><span class="success-info" >订单提交成功,请您及时付款!订单号:{{ payObj.out_trade_no }}</span > </h4> <span class="fr" ><em class="sui-lead">应付金额:</em ><em class="orange money">¥{{ payObj.total_fee }}</em></span > <div class="clearfix"></div> </div> <div class="checkout-steps"> <div class="fl weixin">微信支付</div> <div class="fl sao"> <p class="red">请使用微信扫一扫。</p> <div class="fl code"> <!-- <img id="qrious" src="~/assets/img/erweima.png" alt=""> --> <!-- <qriously value="weixin://wxpay/bizpayurl?pr=R7tnDpZ" :size="338"/> --> <qriously :value="payObj.code_url" :size="338" /> <div class="saosao"> <p>请使用微信扫一扫</p> <p>扫描二维码支付</p> </div> </div> </div> <div class="clearfix"></div> <!-- <p><a href="pay.html" target="_blank">> 其他支付方式</a></p> --> </div> </div> </div> </template> <script> import orderApi from '@/api/order' export default { asyncData({ params, error }) { return orderApi.createNatvie(params.pid).then(response => { return { payObj: response.data.data } }) }, data() { return { timer1: '' } }, methods: {} } </script>
注意这里需要安装vue-qriously组件
response拦截器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 service.interceptors.response.use( response => { if (response.data.code == 28004 ) { console .log("response.data.resultCode是28004" ) window .location.href="/login" return }else { if (response.data.code !== 20000 ) { if (response.data.code != 25000 ) { Message({ message : response.data.message || 'error' , type : 'error' , duration : 5 * 1000 }) } } else { return response; } } }, error => { return Promise .reject(error.response) });
调用方法
这里需要用到response拦截器,后端支付中的返回码为25000,需要根据返回码判断是否支付成功
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 data ( ) { return { timer1 : '' } }, mounted ( ) { this .timer1 = setInterval (() => { this .getPayStatus(this .payObj.out_trade_no) }, 3000 ) }, methods : { getPayStatus (orderNo ) { orderApi.getPayStatus(this .orderNo).then(result => { if (result.data.success) { clearInterval (this .timer1) this .$message({ type : 'success' , message : '支付成功!' }) this .$router.push({ path : '/course/' + this .payObj.course_id }) } }) } }
测试
测了一次,没跳转但是状态改了,懒得测了,不想花钱
debug了一下,发现后端没啥问题,也执行了,付钱了之后也返回了R.ok,但是前端就是显示result为undefined,不理解
课程信息显示完善
免费和不免费,是否购买等页面显示需要区别
查询用户购买课程信息方法 1 2 3 4 5 6 7 8 9 10 11 @GetMapping("getUserCourseStatus/{cid}/{uid}") public boolean getUserCourseStatus (@PathVariable String cid, @PathVariable String uid) { QueryWrapper<Order> wrapper = new QueryWrapper<>(); wrapper.eq("course_id" , cid); wrapper.eq("member_id" , uid); wrapper.eq("status" , 1 ); int count = orderService.count(wrapper); return count > 0 ; }
需要修改课程详情接口的返回值,加上课程的支付状态(远程调用上述方法)
配置接口 1 2 3 4 5 6 7 8 9 @FeignClient(name = "service-order") @Component public interface orderClient { @GetMapping("/eduorder/order/getUserCourseStatus/{cid}/{uid}") public boolean getUserCourseStatus (@PathVariable("cid") String cid, @PathVariable("uid") String uid) ; }
远程调用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @RestController @CrossOrigin @RequestMapping("/eduservice/coursefront") public class CourseController { @Autowired private EduCourseService courseService; @Autowired private OrderClient orderClient; @GetMapping("getCourseInfo/{courseId}") public R getCourseInfo (@PathVariable String courseId, HttpServletRequest request) { CourseFrontInfoVo courseInfo = courseService.getBaseCourseInfo(courseId); List<ChapterVo> chapterList = chapterService.getChapterVideoByCourseId(courseId); boolean status = orderClient.getUserCourseStatus(courseId, JwtUtils.getMemberIdByJwtToken(request)); return R.ok().data("courseInfo" , courseInfo).data("chapterList" , chapterList).data("isBuy" , status); } }
前端 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 <template> <div id="aCoursesList" class="bg-fa of"> <section class="c-attr-mt" v-if="isBuy || Number(courseInfo.price === 0)" > <a href="#" title="立即观看" class="comm-btn c-btn-3" @click="createOrder()" >立即观看</a > </section> <section class="c-attr-mt" v-else> <a href="#" title="立即购买" class="comm-btn c-btn-3" @click="createOrder()" >立即购买</a > </section> </div> </template> <script> import courseApi from '@/api/course' import orderApi from '@/api/order' export default { // asyncData({ params, error }) { // return courseApi.getCourseInfo(params.id).then(result => { // return { // courseInfo: result.data.data.courseInfo, // chapterList: result.data.data.chapterList, // courseId: params.id // } // }) // }, asyncData({ params, error }) { return { courseId: params.id } }, data() { return { courseInfo: {}, chapterList: [], isBuy: false } }, created() { // 这里不适用异步调用,而是created this.initCourseInfo() }, methods: { // 查询课程详情信息 initCourseInfo() { courseApi.getCourseInfo(this.courseId).then(result => { (this.courseInfo = result.data.data.courseInfo), (this.chapterList = result.data.data.chapterList), (this.isBuy = result.data.data.isBuy) }) } } } </script>