抱歉,您的浏览器无法访问本站

本页面需要浏览器支持(启用)JavaScript


了解详情 >

Hello world!

前台讲师列表和详情(后端+前端)

讲师分页列表(后端)

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对象
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") // redis
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">&nbsp;</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)"
>&lt;</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)"
>&gt;</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 }}&nbsp;{{
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)">&nbsp;</a>
</section>
</header>
<!-- /无数据提示 开始-->
<section class="no-data-wrap" v-if="courseList.length == 0">
<em class="icon30 no-data-ico">&nbsp;</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集合
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);

//map返回
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()"
>价格&nbsp;
<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">&nbsp;</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)"
>&lt;</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)"
>&gt;</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
/**
* 课程基础信息VO
*/
@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
// 根据课程id查询课程基本信息以及其章节列表
@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
<!-- 根据课程id查询课程基本信息 -->
<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 }}&nbsp;&nbsp;&nbsp;</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>&nbsp;</p>
<aside>
<span class="c-fff f-fM">购买数</span>
<br />
<h6 class="c-fff f-fM mt10">{{ courseInfo.buyCount }}</h6>
</aside>
</li>
<li>
<p>&nbsp;</p>
<aside>
<span class="c-fff f-fM">课时数</span>
<br />
<h6 class="c-fff f-fM mt10">{{ courseInfo.lessonNum }}</h6>
</aside>
</li>
<li>
<p>&nbsp;</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">&nbsp;</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
// 根据视频id获取视频凭证
@GetMapping("getPlayAuth/{id}")
public R getPlayAuth(@PathVariable String id) throws ClientException {
// 创建初始化对象
DefaultAcsClient client = InitVodClient.initVodClient(ConstantVodUtils.ACCESS_KEY_ID, ConstantVodUtils.ACCESS_KEY_SECRET);
// 创建获取凭证的request和response
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)

详见文档

视频评论(后端+前端)

具体实现分析:

  1. 创建课程评论表
  2. 创建后端接口
    1. 分页查询对应课程评论
    2. 添加评论
      1. 课程评论内容:输入,调用接口
      2. 课程id:进入详情页面就可以查询到课程id
      3. 用户id:根据cookie中的token查询用户信息
    3. 远程调用(edu -> ucenter)

订单与支付(后端+前端)

  1. 创建子模块service_order, application.proerties里大差不差
  2. 代码生成器常规操作

后端基础搭建

启动类

这个模块需要调用edu和UCenter模块中的方法查询课程和用户信息

1
2
3
4
5
6
7
8
9
10
@SpringBootApplication
@ComponentScan(basePackages = {"com.atguigu"})
@MapperScan("com.atguigu.eduorder.mapper")
@EnableDiscoveryClient // nacos注册
@EnableFeignClients // 使用Feign
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;
// 根据用户id获取用户信息
@GetMapping("getUserInfoOrder/{id}")
public UcenterMemberForOrder getUserInfoOrder(@PathVariable String id) {
// 注意这里的返回类型不要R,而是另一个统一类型
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;
// 根据课程id查询课程信息
@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 {
// 根据课程id查询课程信息
@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 {
// 根据用户id获取用户信息
@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); //课程id
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); //订单状态(0:未支付 1:已支付)
order.setPayType(1); //支付类型 ,微信1
baseMapper.insert(order);
//返回订单号
return order.getOrderNo();
}
}

查询订单(后端)

1
2
3
4
5
6
7
8
// 根据订单id查询订单信息
@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设置生成二维码所需的参数
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请求,传递参数xml格式
HttpClient client = new HttpClient("https://api.mch.weixin.qq.com/pay/unifiedorder");
//设置xml格式的参数
client.setXmlParam(WXPayUtil.generateSignedXml(m, "T6m9iK73b0kn9g5v426MKfHQH7X8rKwb"));
client.setHttps(true);
//执行post请求发送
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 {
//1、封装参数
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());

//2 发送httpclient
HttpClient client = new HttpClient("https://api.mch.weixin.qq.com/pay/orderquery");
client.setXmlParam(WXPayUtil.generateSignedXml(m, "T6m9iK73b0kn9g5v426MKfHQH7X8rKwb"));
client.setHttps(true);
client.post();

//3 得到请求返回内容
String xml = client.getContent();
//6、转成Map再返回
return WXPayUtil.xmlToMap(xml);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}

//添加支付记录和更新订单状态
@Override
public void updateOrderStatus(Map<String, String> map) {
//从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);//1代表已经支付
orderService.updateById(order);

//向支付表添加支付记录
PayLog payLog = new PayLog();
payLog.setOrderNo(orderNo); //订单号
payLog.setPayTime(new Date()); //订单完成时间
payLog.setPayType(1);//支付类型 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
// http response 拦截器
service.interceptors.response.use(
response => {
//debugger
if (response.data.code == 28004) {
console.log("response.data.resultCode是28004")
// 返回 错误代码-1 清除ticket信息并跳转到登录页面
//debugger
window.location.href="/login"
return
}else{
if (response.data.code !== 20000) {
//25000:订单支付中,不做任何提示
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: ''
}
},
// 注意这里不适用created,因为使用了异步初始化数据,mounted在页面渲染完成后执行
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
// 根绝用户id和课程id查询 用户课程支付情况
@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
// fallback = "实现类的class",熔断器
@FeignClient(name = "service-order")
// 调用服务(服务名),这里的服务名要和对应服务的配置文件中的spring.application.name对应
@Component
public interface orderClient {
// 根绝用户id和课程id查询 用户课程支付情况
@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);
// 根据课程用户id查询订单状态
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>

评论




🧡💛💚💙💜🖤🤍