Day 13(07.10)登录前端登录成功之后页面显示数据实现过程调用接口登录返回token字符串把第一步返回字符串放到cookie里面创建前端拦截器,判断cookie中是否有字符串,有的话把token字符串放到header(请求头)中根据token值调用接口,根据token获取用户信息,为了页面显示,把调用接口返回用户信息放到cookie里面从第4步cookie获取用户信息,在首页面显示用户信息具体操作 在api文件夹创建login.js文件 import request from '@/utils/request'
export default {
//登录
submitLoginUser(userInfo) {
return request({
url: `/educenter/member/login`,
method: 'post',
data:userInfo
})
},
//根据token值获取用户信息
getLoginUserInfo(){
return request({
url: `/educenter/member/getMenberInfo`,
method: 'get'
})
}
}
在登录页面进行方法调用
下载插件 npm install js-cookie
调用过程
login.vue //登录的方法
//第一步 调用接口登录返回token字符串
submitLogin(){
loginApi.submitLoginUser(this.user)
.then(response =>{
//第二步 字符串放到cookie里面
//第一个参数:cookie名称,第二个参数:参数的值,第三个参数:参数的作用范围
cookie.set('guli_token',response.data.token,{domain:'localhost'})
//第四步调用接口,根据token获取用户信息,为了页面显示
loginApi.getLoginUserInfo()
.then( response =>{
//获取返回的用户信息,放到cookie中
cookie.set('guli_ucenter',response.data.userInfo,{domain:'localhost'})
})
})
},
在request.js添加拦截器,用于传递token信息 //第三步 创建拦截器 http request 拦截器
service.interceptors.request.use(
config => {
//debugger
//判断cookie里面是否有名称是guli_token的数据
if (cookie.get('guli_token')) {
//把获取cookie值放到header里面
config.headers['token'] = cookie.get('guli_token');
}
return config
},
err => {
return Promise.reject(err);
})
default.vue页面显示登录之后的用户信息
created() {//第五步
this.showInfo()
},
methods:{
//创建方法,从cookie获取用户信息
showInfo(){
//从cookie获取用户信息
var userStr = cookie.get("guli_ucenter");
//把字符串转成json对象形式
if (userStr) {
this.loginInfo = JSON.parse(userStr)
}
}
}
登录后的页面 退出功能//退出
logout(){
//清空cookie值
cookie.set('guli_ucenter', "", {domain: 'localhost'})
cookie.set('guli_token', "", {domain: 'localhost'})
//回到首页面
window.location.href = "/"
}
微信扫码登录OAuth2是针对特定问题的一种解决方案,不是协议。主要解决:分布式系统间访问,开放系统间授权开放系统间授权用户名密码复制通用开发者key办法令牌分布式访问(单点登录)OAuth2解决方案:令牌机制,按照一定规则生成字符串,字符串包含用户信息微信扫码登录 在service_ucenter模块配置文件 # 微信开放平台 appid
wx.open.app_id=wxed9954c01bb89b47
# 微信开放平台 appsecret
wx.open.app_secret=a7482517235173ddb4083788de60b90e
# 微信开放平台 重定向url
wx.open.redirect_url=http://guli.shop/api/ucenter/wx/callback
创建类读取配置文件内容 @Component
public class ConstantWxUtils implements InitializingBean {
@Value("${wx.open.app_id}")
private String appId;
@Value("${wx.open.app_secret}")
private String appSecret;
@Value("${wx.open.redirect_url}")
private String redirectUrl;
public static String WX_OPEN_APP_ID;
public static String WX_OPEN_APP_SECRET;
public static String WX_OPEN_REDIRECT_URL;
@Override
public void afterPropertiesSet() throws Exception {
WX_OPEN_APP_ID = appId;
WX_OPEN_APP_SECRET = appSecret;
WX_OPEN_REDIRECT_URL = redirectUrl;
}
}
生成微信二维码 直接请求微信提供的固定地址,向地址后面拼接参数 @CrossOrigin
@Controller//注意这里没有配置 @RestController
@RequestMapping("/api/ucenter/wx")
public class WxApiController {
//生成微信二维码
@GetMapping("login")
public String getXxCode(){
//固定地址,后面拼接参数
//微信开放平台授权baseUrl, %s相当于占位符?,这个位置要传参数
String baseUrl = "https://open.weixin.qq.com/connect/qrconnect" +
"?appid=%s" +
"&redirect_uri=%s" +
"&response_type=code" +
"&scope=snsapi_login" +
"&state=%s" +
"#wechat_redirect";
//对redirect_url进行URLEncoder编码
String redirectUrl=ConstantWxUtils.WX_OPEN_REDIRECT_URL;
try{
redirectUrl= URLEncoder.encode(redirectUrl,"utf-8");
}catch (Exception e){
e.printStackTrace();
}
//设置%s中的值
String url = String.format(
baseUrl,
ConstantWxUtils.WX_OPEN_APP_ID,
ConstantWxUtils.WX_OPEN_REDIRECT_URL,
"lujin");
//重定向到请求微信地址
return "redirect:"+url;
}
}
此时只是请求地址,不需要返回数据,所以用@Controller注解而不用@RestController注解
扫码之后跳转到本地方法
获取扫描人信息分析用到的技术点
httpclient,json转换工具(fastjson、gson、jackson)
扫描之后,执行本地callback方法,在callback获取两个值,在跳转的时候传递过来
state:原样传递
code:临时票据。类似于手机验证码,随机唯一值
拿第一步获取的code值,请求微信提供固定的地址,获取两个值
access_token:访问凭证
openid:每个微信唯一标识
拿第二步获取的两个值,再去请求一个微信提供的固定地址,最终获取到微信扫码人信息。
昵称、头像等
具体操作 引入依赖
org.apache.httpcomponents
httpclient
commons-io
commons-io
com.google.code.gson
gson
controller try{
//1 获取code值,临时票据,类似于验证码
//2 拿code值,请求微信提供固定的地址,获取两个值access_token,openid
String baseAccessTokenUrl = "https://api.weixin.qq.com/sns/oauth2/access_token" +
"?appid=%s" +
"&secret=%s" +
"&code=%s" +
"&grant_type=authorization_code";
//拼接三个参数:id
秘钥
code
String accessTokenUrl = String.format(
baseAccessTokenUrl,
ConstantWxUtils.WX_OPEN_APP_ID,
ConstantWxUtils.WX_OPEN_APP_SECRET,
code);
//请求这个拼接好的地址,获取两个值access_token,openid
//使用httpclient发送请求,得到返回结果
String accessTokenInfo = HttpClientUtils.get(accessTokenUrl);
//从accessTokenInfo获取access_token,openid
//将accessTokenInfo字符串转换成map集合,根据map里面的可以获取对应值
//使用gson
Gson gson=new Gson();
HashMap mapAccessToken = gson.fromJson(accessTokenInfo, HashMap.class);
String access_token=(String)mapAccessToken.get("access_token");
String openid=(String) mapAccessToken.get("openid");
//把扫码人信息添加到数据库
//判断数据库表是否存在相同扫码人信息,不存在,则添加,根据openid判断
UcenterMember member=memberService.getOpenIdMember(openid);
if (member==null){
//3 拿得到的access_token,openid,再去请求一个微信提供的固定地址,最终获取到微信扫码人信息。
//访问微信的资源服务器,获取用户信息
String baseUserInfoUrl = "https://api.weixin.qq.com/sns/userinfo" +
"?access_token=%s" +
"&openid=%s";
//拼接两个参数
String userInfoUrl = String.format(
baseUserInfoUrl,
access_token,
openid
);
//发送请求
String userInfo = HttpClientUtils.get(userInfoUrl);
//获取返回userInfo字符串扫码人的信息
HashMap userInfoMap = gson.fromJson(userInfo, HashMap.class);
String nickname = (String) userInfoMap.get("nickname");//昵称
String headimgurl = (String) userInfoMap.get("headimgurl");//头像
//添加
member=new UcenterMember();
member.setOpenid(openid);
member.setAvatar(headimgurl);
member.setNickname(nickname);
memberService.save(member);
}
//解决跨域问题
//使用JWT根据member对象生成token字符串
String jwtToken = JwtUtils.getJwtToken(member.getId(), member.getNickname());
//最后: 返回首页面,通过路径传递token字符串
return "redirect:http://localhost:3000?token="+jwtToken;
}catch (Exception e){
throw new GuliException(20001,"登录失败");
}
service //根据id判断数据库中是否有此用户
@Override
public UcenterMember getOpenIdMember(String openid) {
QueryWrapper
wrapper=new QueryWrapper<>();
wrapper.eq("openid",openid);
UcenterMember member = baseMapper.selectOne(wrapper);
return member;
}
5.前端页面显示 在首页面路径中获取token字符串
把获取的token放到cookie里面 有前端拦截器,判断cookie是否有token,若有,把cookie中的token取出来,放到header里
调用后端接口,根据token值获取用户信息
代码
created() {
//获取路径里面的token
this.token = this.$route.query.token
if (this.token) {
this.wxLogin()
}
this.showInfo()
},
methods:{
//微信登录显示方法
wxLogin() {
if (this.token == '') return
//把token存在cookie中、也可以放在localStorage中
cookie.set('guli_token', this.token, {domain: 'localhost'})
cookie.set('guli_ucenter', '', {domain: 'localhost'})
//调用接口,登录成功根据token获取用户信息
loginApi.getLoginUserInfo().then(response => {
this.loginInfo = response.data.data.userInfo
//将用户信息记录cookie
cookie.set('guli_ucenter', this.loginInfo, {domain: 'localhost'})
})
},
//创建方法,从cookie获取用户信息
showInfo(){
//从cookie获取用户信息
var userStr = cookie.get("guli_ucenter");
//把字符串转成json对象形式
if (userStr) {
this.loginInfo = JSON.parse(userStr)
}
},
//退出
logout(){
//清空cookie值
cookie.set('guli_ucenter', "", {domain: 'localhost'})
cookie.set('guli_token', "", {domain: 'localhost'})
//回到首页面
window.location.href = "/"
}
}
Day 14(07.11)前台名师列表功能后端 分页查询名师接口
controller @RestController
@RequestMapping("/eduservice/teacherfront")
@CrossOrigin
public class TeacherFrontController {
@Autowired
private EduTeacherService teacherService;
//1 分页查询讲师的方法
@PostMapping("getTeacherFrontList/{page}/{limit}")
public R getTeacherFrontList(@PathVariable long page,@PathVariable long limit){
Page pageTeacher = new Page<>(page,limit);
Map map=teacherService.getTeacherFrontList(pageTeacher);
//返回分页中的所有数据
return R.ok().data(map);
}
}
service //1 分页查询讲师的方法
@Override
public Map getTeacherFrontList(Page pageParam) {
QueryWrapper wrapper=new QueryWrapper<>();
wrapper.orderByDesc("id");
//把分页数据封装到pageTeacher对象
baseMapper.selectPage(pageParam,wrapper);
List records = pageParam.getRecords();
long current = pageParam.getCurrent();
long pages = pageParam.getPages();
long size = pageParam.getSize();
long total = pageParam.getTotal();
boolean hasNext = pageParam.hasNext();//是否有下一页
boolean hasPrevious = pageParam.hasPrevious();//是否有上一页
//把分页数据获取出来,放到map集合
Map 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;
swagger测试
前端 在api 创建teacher.js,定义接口地址 //分页查询讲师的方法
getTeacherList(page,limit) {
return request({
url: `/eduservice/teacherfront/getTeacherFrontList/${page}/${limit}`,
method: 'post'
})
},
在页面引入js文件,调用方法实现显示 //异步调用,调用一次
//params:相当于this.$toute.params.id 等价 params.id
asyncData({ params, error }) {
return teacher.getPageList(1, 8).then(response => {
return { data: response.data.data }
});
},
methods:{
//分页切换的方法
//参数是页码数
GotPage(page){
teacherApi.getTeacherList(page,8)
.then(response =>{
this.data=response.data.data
})
}
}
名师详情功能后端 controller //2 讲师详情功能
@GetMapping("getTeacherFrontInfo/{teacherId}")
public R getTeacherFrontInfo(@PathVariable String teacherId){
//1 根据讲师id查询讲师基本信息
EduTeacher eduTeacher=teacherService.getById(teacherId);
//2 根据讲师id查询所有课程
QueryWrapper wrapper=new QueryWrapper<>();
wrapper.eq("teacher_id",teacherId);
List courseList=courseService.list(wrapper);
return R.ok().data("teacher",eduTeacher).data("courseList",courseList);
}
前端 api/teacher.js
//查询讲师详情的方法
getTeacherInfo(id) {
return request({
url: `/eduservice/teacherfront/getTeacherFrontInfo/${id}`,
method: 'get'
})
},
pages/teacher/_id.vue asyncData({ params, error }) {
return teacher.getTeacherInfo(params.id)
.then(response => {
return {
teacher: response.data.data.teacher,
courseList: response.data.data.courseList
}
})
}
课程列表功能后端 创建vo对象,封装条件数据 @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 分页查询课程的方法
@PostMapping("getFrontCourseList/{page}/{limit}")
public R getFrontCourseList(@PathVariable long page, @PathVariable long limit,
@RequestBody(required = false) CourseFrontVo courseFrontVo){//required=false表示courseFrontVo可以为空
Page pageCourse = new Page<>(page,limit);
Map map=courseService.getCourseFrontList(pageCourse,courseFrontVo);
//返回分页中的所有数据
return R.ok().data(map);
}
service //1 分页查询课程的方法
@Override
public Map getCourseFrontList(Page pageParam, CourseFrontVo courseFrontVo) {
//2 根据讲师id查询所有课程
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("but_count");
}
if (!StringUtils.isEmpty(courseFrontVo.getGmtCreateSort())) {
//最新时间
wrapper.orderByDesc("gmt_create");
}
if (!StringUtils.isEmpty(courseFrontVo.getPriceSort())) {
//价格
wrapper.orderByDesc("price");
}
baseMapper.selectPage(pageParam,wrapper);
List records = pageParam.getRecords();
long current = pageParam.getCurrent();
long pages = pageParam.getPages();
long size = pageParam.getSize();
long total = pageParam.getTotal();
boolean hasNext = pageParam.hasNext();
boolean hasPrevious = pageParam.hasPrevious();
Map 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/course.js
//分页查询课程的方法
getCourseList(page,limit) {
return request({
url: `/eduservice/coursefront/getFrontCourseList/${page}/${limit}`,
method: 'post',
data:searchobj
})
},
//查询所有分类的方法
getAllSubject() {
return request({
url: `/eduservice/subject/getAllSubject`,
method: 'get'
})
},
页面调用接口pages/course/index.vue
created(){
//第一次查询
this.initCourseFirst()
//一级分类显示
this.initSubject()
},
methods:{
//1 查询第一页数据
initCourseFirst(){
courseApi.getCourseList(1,8,this.searchobj)
.then(response =>{
this.data=response.data.data
})
},
//2 查询所有一级分类(包含二级分类)
initSubject(){
courseApi.getAllSubject()
.then(response =>{
this.subjectNestedList=response.data.data.list
})
},
//3 分页切换方法
gotoPage(page){
courseApi.getCourserList(page,8,this.searchobj)
.then(response =>{
this.data=response.data.data
})
}
}
显示所有一级分类,点击某个一级分类,在下面显示对应二级分类
//4 点击某个一级分类,查询对应二级分类
searchOne(subjectParentId,index){
//把传递过来的index值赋值给oneIndex,为了active样式生效,显示选中效果
this.oneIndex=index
this.twoIndex=-1
this.searchObj.subjectId=""
this.subSubjectList=[]
//把一级分类点击id值,赋值给searchObj
this.searchObj.subjectParentId=subjectParentId
//点击某个一级分类进行条件查询
this.gotoPage(1)
//拿着点击的一级分类id和所有的一级分类id进行比较
//如果id相同,从一级分类中获取对应的二级分类
for(let i=0;i chapterVideoList = chapterService.getChapterVideoByCourseId(courseId);
return R.ok().data("courseWeVo",courseWebVo).data("chapterVideoList",chapterVideoList);
}
service //1 根据课程id,编写SQL语句查询课程信息
@Override
public CourseWebVo getBaseCourseInfo(String courseId) {
return baseMapper.getBaseCourseInfo(courseId);
}
在EduCourseMapper.xml中编写SQL语句,根据课程id查询课程信息
前端 api/course.js //课程详情的方法
getCourseInfo(id) {
return request({
url: `/eduservice/coursefront/getFrontCourseInfo/${id}`,
method: 'get'
})
},
pages/course/_id.vue
{{courseWebVo.description}}
把HTML标签翻译 整合阿里云播放器视频播放 创建接口,根据视频id获取视频播放凭证 //根据视频id获取视频凭证
@GetMapping("getPlayAuth/{id}")
public R getPlayAuth(@PathVariable String id) {
try {
//创建初始化对象
DefaultAcsClient client =
InitVodCilent.initVodClient(ConstantVodUtils.ACCESS_KEY_ID, ConstantVodUtils.ACCESS_KEY_SECRET);
//创建获取凭证request和response对象
GetVideoPlayAuthRequest request = new GetVideoPlayAuthRequest();
//向request设置视频id
request.setVideoId(id);
//调用方法得到凭证
GetVideoPlayAuthResponse response = client.getAcsResponse(request);
String playAuth = response.getPlayAuth();
return R.ok().data("playAuth",playAuth);
}catch(Exception e) {
throw new GuliException(20001,"获取凭证失败");
}
}
点击某个小节,打开新的页面进行播放视频 修改超链接地址 a :href="'/player/'+video.videoSourceId" target="_blank">//target在新页面打开
在pages创建文件夹和文件,使用动态路由方式 前端 创建api模块 api/vod.js,从后端获取播放凭证 import request from '@/utils/request'
export default {
getPlayAuth(vid) {
return request({
url: `/eduvod/video/getPlayAuth/${id}`,
method: 'get'
})
}
}
创建 pages/player/_vid.vue 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('播放器创建成功')
})
}
}
Day 15(07.12)课程评论功能分页查询评论列表 创建课程评论表
创建接口,创建两个方法 分页查询课程评论的方法 @RestController
@RequestMapping("/eduservice/comment")
@CrossOrigin
public class CommentFrontController {
@Autowired
private CommentService commentService;
@Autowired
private UcenterClient ucenterClient;
//根据课程id查询评论列表
@ApiOperation(value = "评论分页列表")
@GetMapping("{page}/{limit}")
public R index(
@ApiParam(name = "page", value = "当前页码", required = true)
@PathVariable Long page,
@ApiParam(name = "limit", value = "每页记录数", required = true)
@PathVariable Long limit,
@ApiParam(name = "courseQuery", value = "查询对象", required = false)
String courseId) {
Page pageParam = new Page<>(page, limit);
QueryWrapper wrapper = new QueryWrapper<>();
wrapper.eq("course_id",courseId);
commentService.page(pageParam,wrapper);
List commentList = pageParam.getRecords();
Map 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 R.ok().data(map);
}
添加评论 要添加的数据
课程评论内容:输入内容,提交到接口
课程id、讲师id:进入详情页面根据课程id查询
用户id、用户昵称、用户头像:做评论之前必须先登录,从header获取token字符串(从request获取),根据token字符串获取用户id(使用jwt获取),根据用户id查询用户表,把需要的数据获取出来
添加评论在edu模块,远程调用UCenter模块获取用户信息,进行添加评论
@ApiOperation(value = "添加评论")
@PostMapping("auth/save")
public R save(@RequestBody Comment comment, HttpServletRequest request) {
String memberId = JwtUtils.getMemberIdByJwtToken(request);
if(StringUtils.isEmpty(memberId)) {
return R.error().code(28004).message("请登录");
}
comment.setMemberId(memberId);
UcenterMemberPay ucenterInfo = ucenterClient.getUcenterPay(memberId);
comment.setNickname(ucenterInfo.getNickname());
comment.setAvatar(ucenterInfo.getAvatar());
commentService.save(comment);
return R.ok();
}
}
课程支付功能
课程免费:不需要支付,直接播放。点击免费课程,进入详情页面,在课程大纲列表位置可以视频观看
课程收费:点击免费课程,进入详情页面,点击购买,生成订单,向订单表添加一条记录,点击支付,生成支付 二维码,使用微信扫描二维码进行支付,支付后向支付日志表添加支付记录,回到课程详情页面显示 立即观看,小节可以点击视频播放。
使用代码生成器生成代码接口
生成订单接口、根据订单id查询订单信息接口、生成微信支付二维码接口、查询订单支付状态接口
生成订单接口远程调用 nacos
service_order生成订单接口需要课程信息和用户信息,需要远程调用service_edu的一个接口,该接口根据课程id返回课程信息,再远程调用service_ucenter模块的接口,该接口根据用户id,查询用户信息并返回。
service_order的controller @RestController
@RequestMapping("/eduorder/order")
@CrossOrigin
public class OrderController {
@Autowired
private OrderService orderService;
//1 生成订单方法
@PostMapping("createOrder/{courseID}")
public R saveOrder(@PathVariable String courseID, HttpServletRequest request){
//创建订单,返回订单号
String orderNo=orderService.createOrders(courseID, JwtUtils.getMemberIdByJwtToken(request));
return R.ok().data("orderId",orderNo);
}
}
创建两个interface,指定调用服务名称和接口地址
EduClient接口 @Component
@FeignClient("service-edu")//调用服务名字
public interface EduCLient {
//根据课程id获取课程信息
@PostMapping("/eduservice/coursefront/getCourseInfoOrder/{id}")//调用接口的完整地址
public CourseWebVoOrder getCourseInfoOrder(@PathVariable("id") String id);//注解后面必须加一个参数
}
UcenterClient接口 @Component
@FeignClient("service-ucenter")
public interface UcenterClient {
//根据用户id获取用户信息
@PostMapping("/educenter/member/getUserInfoOrder/{id}")
public UcenterMemberOrder getUserInfoOrder(@PathVariable("id") String id);
}
service_order的service @Autowired
private EduCLient eduCLient;
@Autowired
UcenterClient ucenterClient;
//1 生成订单方法
@Override
public String createOrders(String courseID, String memberId) {
//通过远程调用,根据用户id获取用户信息
UcenterMemberOrder userInfoOrder = ucenterClient.getUserInfoOrder(memberId);
//通过远程调用,根据课程id获取课程信息
CourseWebVoOrder courseInfoOrder = eduCLient.getCourseInfoOrder(courseID);
//创建Order对象,想Order对象里面设置需要数据
//添加到数据库
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);//支付状态,支付后为1
order.setPayType(1);//支付类型,微信1
baseMapper.insert(order);
//返回订单号
return order.getOrderNo();
}
service_ucenter的controller 需要创建一个公共的实体类,用来返回用户信息 //根据用户id获取用户信息
@PostMapping("getUserInfoOrder/{id}")
public UcenterMemberOrder getUserInfoOrder(@PathVariable String id){
UcenterMember member=new UcenterMember();
//把member对象里面的值赋值给UCenterMemberOrder对象
UcenterMemberOrder ucenterMemberOrder=new UcenterMemberOrder();
BeanUtils.copyProperties(member,ucenterMemberOrder);
return ucenterMemberOrder;
}
service_edu的controller //根据课程id获取课程信息
@PostMapping("getCourseInfoOrder/{id}")
public CourseWebVoOrder getCourseInfoOrder(@PathVariable String id){
CourseWebVo courseInfo = courseService.getBaseCourseInfo(id);
CourseWebVoOrder courseWebVoOrder=new CourseWebVoOrder();
BeanUtils.copyProperties(courseInfo,courseWebVoOrder);
return courseWebVoOrder;
}
根据订单id查询订单信息接口 controller //2 根据订单号查询订单信息
@GetMapping("getOrderInfo/{orderId}")
public R getOrderInfo(@PathVariable String orderId){
QueryWrapper wrapper=new QueryWrapper<>();
wrapper.eq("order_no",orderId);//订单号
Order order=orderService.getOne(wrapper);
return R.ok().data("item",order);
}
整合生成订单页面(前端) api/orders.js
//生成订单
createOrders(courseId) {
return request({
url: '/eduorder/order/createOrder/'+courseId,
method: 'post'
})
},
//根据订单id查询订单信息
getOrdersInfo(id) {
return request({
url: '/eduorder/order/getOrderInfo/'+id,
method: 'get'
})
},
页面调用,在“立即购买”位置添加事件
//生成订单
createOrders() {
ordersApi.createOrders(this.courseId)
.then(response => {
//获取返回订单号
//生成订单之后,跳转订单显示页面
this.$router.push({path:'/orders/'+response.data.data.orderId})
})
}
}
pages/order/_oid.vue syncData({ params, error }) {
return ordersApi.getOrdersInfo(params.oid)
.then(response => {
return {
order: response.data.data.item
}
})
},
生成微信支付二维码接口 controller @RestController
@RequestMapping("/eduorder/paylog")
@CrossOrigin
public class PayLogController {
@Autowired
private PayLogService payLogService;
//生成微信支付二维码接口
//参数是订单号
@GetMapping("crateNative/{orderNo}")
public R createNative(@PathVariable String orderNo){
//返回信息,包含二维码地址,还有其他信息
Map map=payLogService.createNative(orderNo);
return R.ok().data(map);
}
}
service @Autowired
private OrderService orderService;
//生成微信支付二维码接口
@Override
public Map createNative(String orderNo) {
try{
//1 根据订单号查询订单信息
QueryWrapper wrapper=new QueryWrapper<>();
wrapper.eq("order_no",orderNo);
Order order = orderService.getOne(wrapper);
//2 使用map设置生成二维码需要参数
Map m=new HashMap();
m.put("appid", "wx74862e0dfcf69954");//微信id
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");//支付的IP地址
m.put("notify_url", "http://guli.shop/api/order/weixinPay/weixinNotify\n");//支付后回调地址
m.put("trade_type", "NATIVE");//生成二维码支付类型
//3 发送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();
//4 得到发送请求返回结果
//返回内容,是使用xml格式返回
String xml = client.getContent();
//把xml格式转换成map集合,把map集合返回
Map 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 //查询订单支付状态
//参数:订单号,根据订单号查询支付状态
@GetMapping("queryPayStatus/{orderNo}")
public R queryPayStatus(@PathVariable String orderNo){
Map map=payLogService.queryPayStatus(orderNo);
if (map==null){
return R.error().message("支付出错了");
}
//如果返回map里面不为空,通过map查询订单状态
if (map.get("trade_state").equals("SUCCESS")){//支付成功
//添加记录到支付记录表,更新订单表订单状态
payLogService.updateOrderStatus(map);
return R.ok().message("支付成功");
}
return R.ok().code(25000).message("支付中");
}
service //根据订单号查询订单支付状态
@Override
public Map queryPayStatus(String orderNo) {
try {
//1、封装参数
Map 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();
Map resultMap = WXPayUtil.xmlToMap(xml);
//6、转成Map再返回
return resultMap;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
//向支付表中添加记录,更新订单状态
@Override
public void updateOrderStatus(Map map) {
//从map获取订单号
String orderNo=map.get("out_trade_no");
// 根据订单号查询订单信息
QueryWrapper wrapper=new QueryWrapper<>();
wrapper.eq("order_no",orderNo);
Order order = orderService.getOne(wrapper);
//更新订单表订单状态
if (order.getStatus()==1){
return;
}
order.setStatus(1);//1代表已支付
orderService.updateById(order);
//向支付表添加支付记录
PayLog payLog=new PayLog();
payLog.setOrderNo(order.getOrderNo());//支付订单号
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/orders.js
//生成二维码的方法
createNatvie(orderNo) {
return request({
url: '/eduorder/paylog/createNative/'+orderNo,
method: 'get'
})
},
//查询订单状态的方法
//生成二维码的方法
queryPayStatus(orderNo) {
return request({
url: '/eduorder/paylog/queryPayStatus/'+orderNo,
method: 'get'
})
}
页面调用,在“去支付”位置添加事件 methods:{
//去支付
toPay() {
this.$router.push({path:'/pay/'+this.order.orderNo})
}
}
pages/pay/_pid.vue syncData({ params, error }) {
return ordersApi.getOrdersInfo(params.oid)
.then(response => {
return {
order: response.data.data.item
}
})
},
data() {
return {
timer1:''
}
},
//每隔三秒调用一次查询订单状态的方法
mounted() {//页面渲染之后执行
this.timer1 = setInterval(() => {
this.queryOrderStatus(this.payObj.out_trade_no)
},3000);
},
methods:{
queryOrderStatus(orderNo) {
ordersApi.queryPayStatus(orderNo)
.then(response => {
if (response.data.success) {
//支付成功,清除定时器
clearInterval(this.timer1)
//提示
this.$message({
type: 'success',
message: '支付成功!'
})
//跳转回到课程详情页面
this.$router.push({path: '/course/' + this.payObj.course_id})
}
})
}
}
功能完善
如果课程是免费课程,按钮显示立即观看
如果课程是已经支付过,按钮显示立即观看
如果课程没有购买,或者不是免费课程,按钮显示立即购买
如何判断课程是否已经支付了根据课程id和用户id查询订单表,查询订单状态,如果状态为1就是支付了后端 创建接口,order模块的OrderController //根据课程id和用户id查询订单表中订单状态
@GetMapping("isBuyCourse/{courseId}/{memberId}")
public boolean isBuyCourse(@PathVariable String courseId,@PathVariable String memberId){
QueryWrapper wrapper=new QueryWrapper<>();
wrapper.eq("course_id",courseId);
wrapper.eq("member_id",memberId);
wrapper.eq("status",1);
int count = orderService.count(wrapper);
if (count>0){
//能查询到,表示已经支付
return true;
}else {
return false;
}
}
在课程详情页面显示立即观看或立即购买,需修改课程详情查询接口,添加返回值,返回当前显示详情的课程是否已经被购买过了
创建interface,为了服务调用 @Component
@FeignClient("service-order")
public interface OrdersClient {
//根据课程id和用户id查询订单表中订单状态
@GetMapping("eduorder/order/isBuyCourse/{courseId}/{memberId}")
public boolean isBuyCourse(@PathVariable("courseId") String courseId, @PathVariable("memberId") String memberId);
}
修改CourseFrontController //2 课程详情功能
@GetMapping("getFrontCourseInfo/{courseId}")
public R getFrontCourseInfo(@PathVariable String courseId, HttpServletRequest request){
//1 根据课程id,编写SQL语句查询课程信息
CourseWebVo courseWebVo=courseService.getBaseCourseInfo(courseId);
//2 根据课程id查询章节和小节
List chapterVideoList = chapterService.getChapterVideoByCourseId(courseId);
//修改部分
//根据课程id和用户id查询当前课程是否已经支付过了
boolean buyCourse = ordersClient.isBuyCourse(courseId, JwtUtils.getMemberIdByJwtToken(request));
return R.ok().data("courseWeVo",courseWebVo).data("chapterVideoList",chapterVideoList).data("isBuy",buyCourse);
}
前端
asyncData({ params, error }) {
return {courseId: params.id}
},
data() {
return {
courseWebVo: {},
chapterVideoList: [],
isbuy: false,
}
},
created() {//在页面渲染之前执行
this.initCourseInfo()
},
methods:{
//查询课程详情信息
initCourseInfo() {
courseApi.getCourseInfo(this.courseId)
.then(response => {
this.courseWebVo=response.data.data.courseWebVo,
this.chapterVideoList=response.data.data.chapterVideoList,
this.isbuy=response.data.data.isBuy
})
},
@GetMapping("eduorder/order/isBuyCourse/{courseId}/{memberId}")
public boolean isBuyCourse(@PathVariable("courseId") String courseId, @PathVariable("memberId") String memberId);
}
```
修改CourseFrontController //2 课程详情功能
@GetMapping("getFrontCourseInfo/{courseId}")
public R getFrontCourseInfo(@PathVariable String courseId, HttpServletRequest request){
//1 根据课程id,编写SQL语句查询课程信息
CourseWebVo courseWebVo=courseService.getBaseCourseInfo(courseId);
//2 根据课程id查询章节和小节
List chapterVideoList = chapterService.getChapterVideoByCourseId(courseId);
//修改部分
//根据课程id和用户id查询当前课程是否已经支付过了
boolean buyCourse = ordersClient.isBuyCourse(courseId, JwtUtils.getMemberIdByJwtToken(request));
return R.ok().data("courseWeVo",courseWebVo).data("chapterVideoList",chapterVideoList).data("isBuy",buyCourse);
}
前端
asyncData({ params, error }) {
return {courseId: params.id}
},
data() {
return {
courseWebVo: {},
chapterVideoList: [],
isbuy: false,
}
},
created() {//在页面渲染之前执行
this.initCourseInfo()
},
methods:{
//查询课程详情信息
initCourseInfo() {
courseApi.getCourseInfo(this.courseId)
.then(response => {
this.courseWebVo=response.data.data.courseWebVo,
this.chapterVideoList=response.data.data.chapterVideoList,
this.isbuy=response.data.data.isBuy
})
},
}