一、讲师
所需功能确认
分页显示数据,每页8名讲师,点击讲师进入详情页面
进入到详情页面后,显示对应讲师详细信息,和关于他的课程
1、分页查询接口(后端)
1、controller
package com. yzpnb. eduservice. controller. api;
import com. baomidou. mybatisplus. extension. plugins. pagination. Page;
import com. yzpnb. common_utils. Result;
import com. yzpnb. eduservice. entity. EduTeacher;
import com. yzpnb. eduservice. service. EduTeacherService;
import io. swagger. annotations. ApiOperation;
import io. swagger. annotations. ApiParam;
import org. springframework. beans. factory. annotation. Autowired;
import org. springframework. web. bind. annotation. GetMapping;
import org. springframework. web. bind. annotation. PathVariable;
import org. springframework. web. bind. annotation. RequestMapping;
import org. springframework. web. bind. annotation. RestController;
import java. util. Map;
@RestController
@RequestMapping ( "/eduservice/api/teacher" )
@CrossOrigin
public class TeacherApiController {
@Autowired
private EduTeacherService eduTeacherService;
@ApiOperation ( "分页查询讲师" )
@GetMapping ( "limitSelectTeacher/{page}/{limit}" )
public Result limitSelectTeacher ( @ApiParam ( name= "page" , value = "当前页" )
@PathVariable Long page,
@ApiParam ( name = "limit" , value = "每页记录数" )
@PathVariable Long limit) {
Page< EduTeacher> pageTeacher= new Page < > ( page, limit) ;
Map< String, Object> map= eduTeacherService. limitSelectTeacher ( pageTeacher) ;
return Result. ok ( ) . data ( map) ;
}
}
2、service
package com. yzpnb. eduservice. service. impl;
import com. baomidou. mybatisplus. core. conditions. query. QueryWrapper;
import com. baomidou. mybatisplus. extension. plugins. pagination. Page;
import com. yzpnb. eduservice. entity. EduTeacher;
import com. yzpnb. eduservice. mapper. EduTeacherMapper;
import com. yzpnb. eduservice. service. EduTeacherService;
import com. baomidou. mybatisplus. extension. service. impl. ServiceImpl;
import org. springframework. stereotype. Service;
import java. util. HashMap;
import java. util. Map;
@Service
public class EduTeacherServiceImpl extends ServiceImpl < EduTeacherMapper, EduTeacher> implements EduTeacherService {
@Override
public Map< String, Object> limitSelectTeacher ( Page< EduTeacher> pageTeacher) {
QueryWrapper< EduTeacher> wrapper= new QueryWrapper < > ( ) ;
wrapper. orderByDesc ( "id" ) ;
baseMapper. selectPage ( pageTeacher, wrapper) ;
Map< String, Object> map= new HashMap < > ( ) ;
map. put ( "allLimitTeacher" , pageTeacher. getRecords ( ) ) ;
map. put ( "current" , pageTeacher. getCurrent ( ) ) ;
map. put ( "pages" , pageTeacher. getPages ( ) ) ;
map. put ( "size" , pageTeacher. getSize ( ) ) ;
map. put ( "total" , pageTeacher. getTotal ( ) ) ;
map. put ( "hasNext" , pageTeacher. hasNext ( ) ) ;
map. put ( "hasPrevious" , pageTeacher. hasPrevious ( ) ) ;
return map;
}
}
3、测试
2、分页显示讲师(前端)
编写api接口,调用方法得到数据,然后v-for遍历,都是换汤不换药,参考GitHub源码即可
就是将静态页面换成动态页面,将数据与数据库联系起来而已
3、讲师详情页(后端)
1、controller
@Autowired
EduCourseService eduCourseService;
@ApiOperation ( "根据id查询讲师信息和他讲的课程" )
@GetMapping ( "selectTeacherAndCourse/{id}" )
public Result selectTeacherAndCourse ( @ApiParam ( name = "id" , value = "讲师id" )
@PathVariable String id) {
EduTeacher eduTeacher = eduTeacherService. getById ( id) ;
QueryWrapper< EduCourse> queryWrapper= new QueryWrapper < > ( ) ;
queryWrapper. eq ( "teacher_id" , id) ;
List< EduCourse> eduCourseList = eduCourseService. list ( queryWrapper) ;
Map< String, Object> map= new HashMap < > ( ) ;
map. put ( "teacher" , eduTeacher) ;
map. put ( "courseList" , eduCourseList) ;
return Result. ok ( ) . data ( map) ;
}
2、测试
4、讲师详情页(前端)
二、课程
功能确认
根据一级二级分类筛选课程,根据不同规则排序
分页查询课程,每页8个课程,点击课程进入详情页
进入详情页后,显示课程信息,课程简介,课程大纲,主讲教师等信息
1、分页条件查询排序接口(后端)
1、创建vo对象
package com. yzpnb. eduservice. entity. vo;
import io. swagger. annotations. ApiModel;
import io. swagger. annotations. ApiModelProperty;
import lombok. Data;
@ApiModel ( value = "课程查询对象" , description = "课程查询对象封装" )
@Data
public class CourseApiVo {
private static final long serialVersionUID = 1 L;
@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;
}
2、controller
package com. yzpnb. eduservice. controller. api;
import com. baomidou. mybatisplus. extension. plugins. pagination. Page;
import com. yzpnb. common_utils. Result;
import com. yzpnb. eduservice. entity. EduCourse;
import com. yzpnb. eduservice. entity. vo. CourseApiVo;
import com. yzpnb. eduservice. service. EduCourseService;
import io. swagger. annotations. ApiOperation;
import io. swagger. annotations. ApiParam;
import org. springframework. beans. factory. annotation. Autowired;
import org. springframework. web. bind. annotation. *;
import java. util. Map;
@RestController
@RequestMapping ( "/eduservice/api/course" )
@CrossOrigin
public class CourseApiController {
@Autowired
private EduCourseService eduCourseService;
@ApiOperation ( "条件分页查询" )
@PostMapping ( "selectIfLimitCourse/{page}/{limit}" )
public Result selectIfLimitCourse ( @ApiParam ( name = "page" , value = "当前页" )
@PathVariable Long page,
@ApiParam ( name = "limit" , value= "每页记录数" )
@PathVariable Long limit,
@ApiParam ( name = "courseApiVo" , value = "课程和课程条件对象,没有值也可以,不报错" , required = false )
@RequestBody ( required = false ) CourseApiVo courseApiVo) {
Page< EduCourse> pageCourse= new Page < > ( page, limit) ;
Map< String, Object> map= eduCourseService. selectIfLimitCourse ( pageCourse, courseApiVo) ;
return Result. ok ( ) . data ( map) ;
}
}
3、service
@Override
public Map< String, Object> selectIfLimitCourse ( Page< EduCourse> pageCourse, CourseApiVo courseApiVo) {
QueryWrapper< EduCourse> queryWrapper= new QueryWrapper < > ( ) ;
if ( ! StringUtils. isEmpty ( courseApiVo. getSubjectParentId ( ) ) ) {
queryWrapper. eq ( "subject_parent_id" , courseApiVo. getSubjectParentId ( ) ) ;
}
if ( ! StringUtils. isEmpty ( courseApiVo. getSubjectId ( ) ) ) {
queryWrapper. eq ( "subject_id" , courseApiVo. getSubjectId ( ) ) ;
}
if ( ! StringUtils. isEmpty ( courseApiVo. getBuyCountSort ( ) ) ) {
queryWrapper. orderByDesc ( "buy_count" ) ;
}
if ( ! StringUtils. isEmpty ( courseApiVo. getGmtCreateSort ( ) ) ) {
queryWrapper. orderByDesc ( "gmt_create" ) ;
}
if ( ! StringUtils. isEmpty ( courseApiVo. getPriceSort ( ) ) ) {
queryWrapper. orderByDesc ( "price" ) ;
}
pageCourse= baseMapper. selectPage ( pageCourse, queryWrapper) ;
Map< String, Object> map= new HashMap < > ( ) ;
map. put ( "allLimitTeacher" , pageCourse. getRecords ( ) ) ;
map. put ( "current" , pageCourse. getCurrent ( ) ) ;
map. put ( "pages" , pageCourse. getPages ( ) ) ;
map. put ( "size" , pageCourse. getSize ( ) ) ;
map. put ( "total" , pageCourse. getTotal ( ) ) ;
map. put ( "hasNext" , pageCourse. hasNext ( ) ) ;
map. put ( "hasPrevious" , pageCourse. hasPrevious ( ) ) ;
return map;
}
4、测试
2、条件分页显示讲师(前端)
3、课程详情页(后端)
1、创建Vo对象
package com. yzpnb. eduservice. entity. vo;
import io. swagger. annotations. ApiModel;
import io. swagger. annotations. ApiModelProperty;
import lombok. Data;
import java. io. Serializable;
import java. math. BigDecimal;
@ApiModel ( value= "课程信息" , description= "网站课程详情页需要的相关字段" )
@Data
public class CourseApiInfoVo implements Serializable {
private static final long serialVersionUID = 1 L;
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;
}
2、controller
@Autowired
private EduChapterService eduChapterService;
@ApiOperation ( "根据id获取课程详细信息,包括讲师和课程大纲" )
@GetMapping ( value = "selectCourserApiInfoVoById/{id}" )
public Result selectCourserApiInfoVoById ( @ApiParam ( name = "id" , value = "课程ID" , required = true )
@PathVariable String id) {
CourseApiInfoVo courseApiInfoVo = eduCourseService. selectCourserApiInfoVoById ( id) ;
List< ChapterVo> chapterVoList = eduChapterService. selectChapterVideoByCourseId ( id) ;
Map< String, Object> map= new HashMap < > ( ) ;
map. put ( "course" , courseApiInfoVo) ;
map. put ( "chapterVoList" , chapterVoList) ;
return Result. ok ( ) . data ( map) ;
}
3、service
@Override
public CourseApiInfoVo selectCourserApiInfoVoById ( String id) {
EduCourse eduCourse = baseMapper. selectById ( id) ;
eduCourse. setViewCount ( eduCourse. getViewCount ( ) + 1 ) ;
baseMapper. updateById ( eduCourse) ;
return eduCourseMapper. selectCourserApiInfoVoById ( id) ;
}
4、Mapper和sql
< ! -- 根据id获取课程详细信息,包括讲师-- >
< select id= "selectCourserApiInfoVoById" resultType= "com.yzpnb.eduservice.entity.vo.CourseApiInfoVo" >
SELECT
c. id,
c. title,
c. cover,
CONVERT ( c. price, DECIMAL ( 8 , 2 ) ) AS price,
c. lesson_num AS lessonNum,
c. cover,
c. buy_count AS buyCount,
c. view_count AS viewCount,
cd. description,
t. id AS teacherId,
t. name AS teacherName,
t. intro,
t. avatar,
s1. id AS subjectLevelOneId,
s1. title AS subjectLevelOne,
s2. id AS subjectLevelTwoId,
s2. title AS subjectLevelTwo
FROM
edu_course c
LEFT JOIN edu_course_description cd ON c. id = cd. id
LEFT JOIN edu_teacher t ON c. teacher_id = t. id
LEFT JOIN edu_subject s1 ON c. subject_parent_id = s1. id
LEFT JOIN edu_subject s2 ON c. subject_id = s2. id
WHERE
c. id = #{ id}
< / select>
4、测试
4、课程详情页(前端)
三、阿里云播放器在线播放视频
方式
通过视频地址播放(只能播放不加密视频)
通过凭证播放(可以播放加密试视频)
需要引入:
< 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" > < / script>
初始化视频播放器
< body>
< div class = "prism-player" id= "J_prismPlayer" > < / div>
< script>
var player = new Aliplayer ( {
id: 'J_prismPlayer' ,
width: '100%' ,
autoplay: false ,
cover: 'http://liveroom-img.oss-cn-qingdao.aliyuncs.com/logo.png' ,
source : '你的视频播放地址' ,
encryptType: '1' ,
vid : '视频id' ,
playauth : '视频授权码' ,
} , function ( player) {
console. log ( '播放器创建好了。' )
} ) ;
< / script>
< / body>
步骤分析
通过我们点击小节视频,获取小节对象中的视频id
通过视频id获取播放凭证,视频地址等内容
在播放器中播放
1、后端
1、修改工具类
public String getPlayInfo ( String id) throws Exception {
client= initVodClient ( accessKeyId, accessKeySecret) ;
GetPlayInfoRequest request = new GetPlayInfoRequest ( ) ;
GetPlayInfoResponse response = new GetPlayInfoResponse ( ) ;
request. setVideoId ( id) ;
response= client. getAcsResponse ( request) ;
try {
List< GetPlayInfoResponse. PlayInfo> playInfoList = response. getPlayInfoList ( ) ;
for ( GetPlayInfoResponse. PlayInfo playInfo : playInfoList) {
System. out. print ( "PlayInfo.PlayURL = " + playInfo. getPlayURL ( ) + "\n" ) ;
}
System. out. print ( "VideoBase.Title = " + response. getVideoBase ( ) . getTitle ( ) + "\n" ) ;
} catch ( Exception e) {
System. out. print ( "ErrorMessage = " + e. getLocalizedMessage ( ) ) ;
}
System. out. print ( "RequestId = " + response. getRequestId ( ) + "\n" ) ;
return response. getRequestId ( ) ;
}
public String getVideoPlayAuth ( String id) throws Exception {
client= initVodClient ( accessKeyId, accessKeySecret) ;
GetVideoPlayAuthRequest requestAuth = new GetVideoPlayAuthRequest ( ) ;
GetVideoPlayAuthResponse responseAuth = new GetVideoPlayAuthResponse ( ) ;
requestAuth. setVideoId ( id) ;
responseAuth= client. getAcsResponse ( requestAuth) ;
System. out. println ( "PlayAuth = " + responseAuth. getPlayAuth ( ) + "\n" ) ;
return responseAuth. getPlayAuth ( ) ;
}
2、编写controller
@ApiOperation ( "根据视频id获取视频凭证" )
@GetMapping ( "getVideoPlayAuth/{id}" )
public Result getVideoPlayAuth ( @ApiParam ( name = "id" , value = "视频id" )
@PathVariable String id) {
try {
String playAuth= new AliyunVideoUtil ( ) . getVideoPlayAuth ( id) ;
return Result. ok ( ) . message ( "视频凭证获取成功" ) . data ( "playAuth" , playAuth) ;
} catch ( Exception e) {
return Result. error ( ) . message ( "视频凭证获取失败" ) ;
}
}
3、测试
2、前端
1、播放页面的布局(源码GitHub)
2、api接口
3、播放页面中引入文件
4、获取播放凭证
5、初始化播放器
6、测试
3、更多功能
四、课程评论功能
功能需求
当用户进入详情页面后,分页查询课程评论
评论中要显示评论人的头像,昵称
输入框中输入评论并提交后,提交评论内容,并将课程id,讲师id,以及当前登录账号的id,昵称和头像存入数据库
评论时,需要先判断用户是否登录,没登录先登录,登录了才能评论
登陆以后我们就可以从header中获取token字符串,然后获取用户id,然后查询出昵称和头像
1、后端
1、使用代码生成器生成相关代码(注意是在我们处理讲师,课程,章节小节的微服务中)
2、编写根据会员id查询会员信息接口
3、评论微服务,编写Feign调用接口
package com. yzpnb. eduservice. feign;
import com. yzpnb. eduservice. entity. ucenter_member. UcenterMember;
import com. yzpnb. eduservice. feign. impl. FeignToUcenterClientImpl;
import io. swagger. annotations. ApiOperation;
import io. swagger. annotations. ApiParam;
import org. springframework. cloud. openfeign. FeignClient;
import org. springframework. stereotype. Component;
import org. springframework. web. bind. annotation. GetMapping;
import org. springframework. web. bind. annotation. PathVariable;
@FeignClient ( name = "service-ucenter" , fallback = FeignToUcenterClientImpl. class )
@Component
public interface FeignToUcenterClient {
@ApiOperation ( "根据id获取用户信息" )
@GetMapping ( "/ucenter_service/ucenter-member/selectById/{id}" )
public UcenterMember selectById ( @ApiParam ( name = "id" , value = "用户id" )
@PathVariable ( value = "id" ) String id) ;
}
package com. yzpnb. eduservice. feign. impl;
import com. yzpnb. eduservice. entity. ucenter_member. UcenterMember;
import com. yzpnb. eduservice. feign. FeignToUcenterClient;
import org. springframework. stereotype. Component;
@Component
public class FeignToUcenterClientImpl implements FeignToUcenterClient {
@Override
public UcenterMember selectById ( String id) {
return null;
}
}
4、复制ucenter中的用户实体类
5、分页查询评论,添加评论接口
package com. yzpnb. eduservice. controller;
import com. baomidou. mybatisplus. core. conditions. query. QueryWrapper;
import com. baomidou. mybatisplus. extension. plugins. pagination. Page;
import com. yzpnb. common_utils. JwtUtils;
import com. yzpnb. common_utils. Result;
import com. yzpnb. eduservice. entity. EduComment;
import com. yzpnb. eduservice. entity. ucenter_member. UcenterMember;
import com. yzpnb. eduservice. feign. FeignToUcenterClient;
import com. yzpnb. eduservice. service. EduCommentService;
import io. swagger. annotations. ApiOperation;
import io. swagger. annotations. ApiParam;
import org. springframework. beans. factory. annotation. Autowired;
import org. springframework. util. StringUtils;
import org. springframework. web. bind. annotation. *;
import javax. servlet. http. HttpServletRequest;
import java. util. *;
@RestController
@RequestMapping ( "/eduservice/edu-comment" )
@CrossOrigin
public class EduCommentController {
@Autowired
private EduCommentService eduCommentService;
@Autowired
private FeignToUcenterClient feignToUcenterClient;
@ApiOperation ( value = "根据课程id分页查询评论" )
@GetMapping ( "limitSelectComment/{page}/{limit}" )
public Result limitSelectComment ( @ApiParam ( name = "page" , value = "当前页码" , required = true )
@PathVariable Long page,
@ApiParam ( name = "limit" , value = "每页记录数" , required = true )
@PathVariable Long limit,
@ApiParam ( name = "courseQuery" , value = "课程id" , required = false ) String courseId) {
Page< EduComment> pageParam = new Page < > ( page, limit) ;
QueryWrapper< EduComment> wrapper = new QueryWrapper < > ( ) ;
wrapper. eq ( "course_id" , courseId) ;
eduCommentService. page ( pageParam, wrapper) ;
List< EduComment> commentList = pageParam. getRecords ( ) ;
Map< String, Object> map = new HashMap < > ( ) ;
map. put ( "items" , commentList) ;
map. put ( "current" , pageParam. getCurrent ( ) ) ;
map. put ( "pages" , pageParam. getPages ( ) ) ;
map. put ( "size" , pageParam. getSize ( ) ) ;
map. put ( "total" , pageParam. getTotal ( ) ) ;
map. put ( "hasNext" , pageParam. hasNext ( ) ) ;
map. put ( "hasPrevious" , pageParam. hasPrevious ( ) ) ;
return Result. ok ( ) . data ( map) ;
}
@ApiOperation ( value = "添加评论" )
@PostMapping ( "insertComment" )
public Result save ( @RequestBody EduComment comment, HttpServletRequest request) {
String memberId = JwtUtils. getMemberIdByJwtToken ( request) ;
if ( StringUtils. isEmpty ( memberId) ) {
return Result. error ( ) . code ( 28004 ) . message ( "请登录" ) ;
}
comment. setMemberId ( memberId) ;
UcenterMember ucenterMember = feignToUcenterClient. selectById ( memberId) ;
comment. setNickname ( ucenterMember. getNickname ( ) ) ;
comment. setAvatar ( ucenterMember. getAvatar ( ) ) ;
eduCommentService. save ( comment) ;
return Result. ok ( ) ;
}
}
2、前端
1、api接口
2、编写详情页