❤ 作者主页:Java技术一点通的博客
❀ 个人介绍:大家好,我是Java技术一点通!( ̄▽ ̄)~*
🍊 记得关注、点赞、收藏、评论⭐️⭐️⭐️
📣 认真学习,共同进步!!!🎉🎉
课程评论功能
一、数据库设计
- 数据库
edu_comment
数据表
CREATE TABLE `edu_comment` ( `id` char(19) NOT NULL COMMENT '讲师ID', `course_id` varchar(19) NOT NULL DEFAULT '' COMMENT '课程id', `teacher_id` char(19) NOT NULL DEFAULT '' COMMENT '讲师id', `member_id` varchar(19) NOT NULL DEFAULT '' COMMENT '会员id', `nickname` varchar(50) DEFAULT NULL COMMENT '会员昵称', `avatar` varchar(255) DEFAULT NULL COMMENT '会员头像', `content` varchar(500) DEFAULT NULL COMMENT '评论内容', `is_deleted` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '逻辑删除 1(true)已删除, 0(false)未删除', `gmt_create` datetime NOT NULL COMMENT '创建时间', `gmt_modified` datetime NOT NULL COMMENT '更新时间', PRIMARY KEY (`id`), KEY `idx_course_id` (`course_id`), KEY `idx_teacher_id` (`teacher_id`), KEY `idx_member_id` (`member_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='评论'; # # Data for table "edu_comment" # INSERT INTO `edu_comment` VALUES ('1194499162790211585','1192252213659774977','1189389726308478977','1','小三123','http://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83eoj0hHXhgJNOTSOFsS4uZs8x1ConecaVOB8eIl115xmJZcT4oCicvia7wMEufibKtTLqiaJeanU2Lpg3w/132','课程很好',0,'2019-11-13 14:16:08','2019-11-13 14:16:08'),('1194898406466420738','1192252213659774977','1189389726308478977','1','小三123','http://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83eoj0hHXhgJNOTSOFsS4uZs8x1ConecaVOB8eIl115xmJZcT4oCicvia7wMEufibKtTLqiaJeanU2Lpg3w/132','11',0,'2019-11-14 16:42:35','2019-11-14 16:42:35'),('1194898484388200450','1192252213659774977','1189389726308478977','1','小三123','http://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83eoj0hHXhgJNOTSOFsS4uZs8x1ConecaVOB8eIl115xmJZcT4oCicvia7wMEufibKtTLqiaJeanU2Lpg3w/132','111',0,'2019-11-14 16:42:53','2019-11-14 16:42:53'),('1195251020861317122','1192252213659774977','1189389726308478977','1',NULL,NULL,'2233',0,'2019-11-15 16:03:45','2019-11-15 16:03:45'),('1195251382720700418','1192252213659774977','1189389726308478977','1',NULL,NULL,'4455',0,'2019-11-15 16:05:11','2019-11-15 16:05:11'),('1195252819177570306','1192252213659774977','1189389726308478977','1','小三1231','http://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83eoj0hHXhgJNOTSOFsS4uZs8x1ConecaVOB8eIl115xmJZcT4oCicvia7wMEufibKtTLqiaJeanU2Lpg3w/132','55',0,'2019-11-15 16:10:53','2019-11-15 16:10:53'),('1195252899448160258','1192252213659774977','1189389726308478977','1','小三1231','http://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83eoj0hHXhgJNOTSOFsS4uZs8x1ConecaVOB8eIl115xmJZcT4oCicvia7wMEufibKtTLqiaJeanU2Lpg3w/132','55',0,'2019-11-15 16:11:13','2019-11-15 16:11:13'),('1195252920587452417','1192252213659774977','1189389726308478977','1','小三1231','http://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83eoj0hHXhgJNOTSOFsS4uZs8x1ConecaVOB8eIl115xmJZcT4oCicvia7wMEufibKtTLqiaJeanU2Lpg3w/132','223344',0,'2019-11-15 16:11:18','2019-11-15 16:11:18'),('1195262128095559681','14','1189389726308478977','1','小三1231','http://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83eoj0hHXhgJNOTSOFsS4uZs8x1ConecaVOB8eIl115xmJZcT4oCicvia7wMEufibKtTLqiaJeanU2Lpg3w/132','11',0,'2019-11-15 16:47:53','2019-11-15 16:47:53'),('1196264505170767874','1192252213659774977','1189389726308478977','1','小三1231','http://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83eoj0hHXhgJNOTSOFsS4uZs8x1ConecaVOB8eIl115xmJZcT4oCicvia7wMEufibKtTLqiaJeanU2Lpg3w/132','666666',0,'2019-11-18 11:10:58','2019-11-18 11:10:58');
二、课程评论后端整合
1. 在service-edu模块,生成课程评论代码
使用mp代码生成器生成:
public class CodeGenerator {
@Test
public void run() {
// 1、创建代码生成器
AutoGenerator mpg = new AutoGenerator();
// 2、全局配置
GlobalConfig gc = new GlobalConfig();
String projectPath = System.getProperty("user.dir");
gc.setOutputDir("D:\\IDEA\\guli_parent\\service\\service_edu" + "/src/main/java"); //输出目录
gc.setAuthor("jyu_zwy"); //作者名
gc.setOpen(false); //生成后是否打开资源管理器
gc.setFileOverride(false); //重新生成时文件是否覆盖
gc.setServiceName("%sService"); //去掉Service接口的首字母I
gc.setIdType(IdType.ID_WORKER_STR); //主键策略
gc.setDateType(DateType.ONLY_DATE);//定义生成的实体类中日期类型
gc.setSwagger2(true);//开启Swagger2模式
mpg.setGlobalConfig(gc);
// 3、数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://localhost:3306/guli?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8");
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("abc123");
dsc.setDbType(DbType.MYSQL);
mpg.setDataSource(dsc);
// 4、包配置
PackageConfig pc = new PackageConfig();
//生成包:com.atguigu.eduservice
pc.setModuleName("eduservice"); //模块名
pc.setParent("com.atguigu");
//生成包:com.atguigu.controller
pc.setController("controller");
pc.setEntity("entity");
pc.setService("service");
pc.setMapper("mapper");
mpg.setPackageInfo(pc);
// 5、策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setInclude("edu_comment");//根据数据库哪张表生成,有多张表就加逗号继续填写
strategy.setNaming(NamingStrategy.underline_to_camel);//数据库表映射到实体的命名策略
strategy.setTablePrefix(pc.getModuleName() + "_"); //生成实体时去掉表前缀
strategy.setColumnNaming(NamingStrategy.underline_to_camel);//数据库表字段映射到实体的命名策略
strategy.setEntityLombokModel(true); // lombok 模型 @Accessors(chain = true) setter链式操作
strategy.setRestControllerStyle(true); //restful api风格控制器
strategy.setControllerMappingHyphenStyle(true); //url中驼峰转连字符
mpg.setStrategy(strategy);
// 6、执行
mpg.execute();
}
}
2. 在service-ucenter模块,创建接口
UcenterMemberController
:
//根据用户id查询用户信息
@PostMapping("getMemberInfoById{memberId}")
public UcenterMemberVo getMemberInfoById(@PathVariable String memberId){
UcenterMember member = memberService.getById(memberId);
UcenterMemberVo memberVo = new UcenterMemberVo();
BeanUtils.copyProperties(member, memberVo);
return memberVo;
}
3. 创建课程评论controller
- 在service-edu模块创建client,实现微服务调用
VodClient
:
@PostMapping("/educenter/member/getMemberInfoById/{memberId}")
public UcenterMemberVo getMemberInfoById(@PathVariable String memberId);
`VodFileDegradeFeignClient`:
```
@Override
public UcenterMemberVo getMemberInfoById(String memberId) {
return null;
}
```
- 创建评论列表和添加评论接口
CommentFrontController
@RestController
@CrossOrigin
@RequestMapping({"/eduservice/comment"})
public class CommentFrontController {
@Autowired
private EduCommentService commentService;
@Autowired
private VodClient vodClient;
//根据课程id分页查询课程评论的方法
@GetMapping("/getCommentPage/{page}/{limit}")
public R getCommentPage(@PathVariable Long page, @PathVariable Long limit, String courseId) {
Page<EduComment> commentPage = new Page<>(page, limit);
QueryWrapper<EduComment> wrapper = new QueryWrapper<>();
//判断课程id是否为空
if (!StringUtils.isEmpty(courseId)){
wrapper.eq("course_id",courseId);
}
//按最新排序
wrapper.orderByDesc("gmt_create");
//数据会被封装到commentPage中
commentService.page(commentPage,wrapper);
List<EduComment> commentList = commentPage.getRecords();
long current = commentPage.getCurrent();//当前页
long size = commentPage.getSize();//一页记录数
long total = commentPage.getTotal();//总记录数
long pages = commentPage.getPages();//总页数
boolean hasPrevious = commentPage.hasPrevious();//是否有上页
boolean hasNext = commentPage.hasNext();//是否有下页
HashMap<String, Object> map = new HashMap<>();
map.put("current",current);
map.put("size",size);
map.put("total",total);
map.put("pages",pages);
map.put("hasPrevious",hasPrevious);
map.put("hasNext",hasNext);
map.put("list",commentList);
return R.ok().data(map);
}
//添加评论
@PostMapping("/auth/addComment")
public R addComment(HttpServletRequest request, @RequestBody EduComment eduComment){
String memberId = JwtUtils.getMemberIdByJwtToken(request);
//判断用户是否登录
if (StringUtils.isEmpty(memberId)){
throw new GuliException(20001,"请先登录");
}
eduComment.setMemberId(memberId);
//远程调用ucenter根据用户id获取用户信息
UcenterMemberVo memberVo = vodClient.getMemberInfoById(memberId);
eduComment.setAvatar(memberVo.getAvatar());
eduComment.setNickname(memberVo.getNickname());
//保存评论
commentService.save(eduComment);
return R.ok();
}
}
三、课程评论前端整合
1. 定义api
commonedu.js
import request from '@/utils/request'
export default {
getPageList(page, limit, courseId) {
return request({
url: `/eduservice/comment/getCommentPage/${page}/${limit}`,
method: 'get',
params: courseId
})
},
addComment(comment) {
return request({
url: `/eduservice/comment/auth/addComment`,
method: 'post',
data: comment
})
}
}
2. 调用自定义方法
<script>
import courseApi from "@/api/course"
import comment from "@/api/commonedu"
export default {
// asyncData({ params, error }) {
// return courseApi.getCourseInfo(params.id).then(response => {
// return {
// courseWebVo: response.data.data.courseWebVo,
// chapterVideoList: response.data.data.chapterVideoList
// }
// })
// },
data() {
return{
chapterVideoList: [],
courseWebVo: {
courseId: ""
},
commentList: {},
page: 1,
limit: 4,
total: 10,
comment: {
content: "",
courseId: "",
teacherId: "",
},
isbuyCourse: false,
}
},
created() {
this.courseWebVo.courseId = this.$route.params.id;
//获取课程详细信息
this.getCourseInfo();
this.initComment();
},
methods: {
//获取课程详细信息
getCourseInfo() {
courseApi.getCourseInfo(this.courseWebVo.courseId).then(response=> {
this.chapterVideoList = response.data.data.chapterVideoList
this.courseWebVo = response.data.data.courseWebVo
})
},
initComment(){
comment.getPageList(this.page, this.limit,this.courseWebVo.courseId)
.then(response=> {
this.commentList = response.data.data
})
},
addComment() {
this.comment.courseId = this.courseWebVo.courseId
this.comment.teacherId = this.courseWebVo.teacherId
comment.addComment(this.comment).then(response => {
if (response.data.success) {
this.$message({
message: "评论成功",
type: "success",
});
this.comment.content = "";
this.initComment();
}
});
},
gotoPage(page) {
comment.getPageList(page, this.limit, this.courseId).then((response) => {
this.commentList = response.data.data
})
},
}
};
</script>
3. 页面
<div class="mt50 commentHtml">
<div>
<h6 class="c-c-content c-infor-title" id="i-art-comment">
<span class="commentTitle">课程评论</span>
</h6>
<section class="lh-bj-list pr mt20 replyhtml">
<ul>
<li class="unBr">
<aside class="noter-pic">
<img
width="50"
height="50"
class="picImg"
src="~/assets/img/avatar-boy.gif"
/>
</aside>
<div class="of">
<section class="n-reply-wrap">
<fieldset>
<textarea
name=""
v-model="comment.content"
placeholder="输入您要评论的文字"
id="commentContent"
></textarea>
</fieldset>
<p class="of mt5 tar pl10 pr10">
<span class="fl"
><tt
class="c-red commentContentmeg"
style="display: none"
></tt
></span>
<input
type="button"
@click="addComment()"
value="回复"
class="lh-reply-btn"
/>
</p>
</section>
</div>
</li>
</ul>
</section>
<section class="">
<section class="question-list lh-bj-list pr">
<ul class="pr10">
<li v-for="comment in commentList.list" :key="comment.id">
<aside class="noter-pic">
<img
width="50"
height="50"
class="picImg"
:src="comment.avatar"
/>
</aside>
<div class="of">
<span class="fl">
<font class="fsize12 c-blue">{{ comment.nickname }}</font>
<font class="fsize12 c-999 ml5">评论:</font></span
>
</div>
<div class="noter-txt mt5">
<p>{{ comment.content }}</p>
</div>
<div class="of mt5">
<span class="fr"
><font class="fsize12 c-999ml5">{{
comment.gmtCreate
}}</font></span
>
</div>
</li>
</ul>
</section>
</section>
<!-- 公共分页 开始 -->
<div class="paging">
<!-- undisable这个class是否存在,取决于数据属性hasPrevious -->
<a
:class="{ undisable: !commentList.hasPrevious }"
href="#"
title="首页"
@click.prevent="gotoPage(1)"
>首</a
>
<a
:class="{ undisable: !commentList.hasPrevious }"
href="#"
title="前一页"
@click.prevent="gotoPage(commentList.current - 1)"
><</a
>
<a
v-for="page in commentList.pages"
:key="page"
:class="{
current: commentList.current == page,
undisable: commentList.current == page,
}"
:title="'第' + page + '页'"
href="#"
@click.prevent="gotoPage(page)"
>{{ page }}</a
>
<a
:class="{ undisable: !commentList.hasNext }"
href="#"
title="后一页"
@click.prevent="gotoPage(commentList.current + 1)"
>></a
>
<a
:class="{ undisable: !commentList.hasNext }"
href="#"
title="末页"
@click.prevent="gotoPage(commentList.pages)"
>末</a
>
<div class="clear" />
</div>
<!-- 公共分页 结束 -->
</div>
</div>
4. 测试
*
# 课程支付功能需求描述
## 一. 课程支付说明
(1)课程分为免费课程和付费课程,如果是免费课程可以直接观看,如果是付费观看的课程,用户需下单支付后才可以观看。
(2) 如果是免费课程,在用户选择课程,进入到课程详情页面时候,直接显示 “立即观看”,用户点击立即观看,可以切换到播放列表进行视频播放。
二、付费课程流程
(1)如果是付费课程,在用户选择课程,进入到课程详情页面时候,会显示 “立即购买”。
(2) 点击“立即购买”,会生成课程的订单,跳转到订单页面。
(3) 点击“去支付”,会跳转到支付页面,生成微信扫描的二维码。
(4) 使用微信扫描支付后,会跳转回到课程详情页面,同时显示“立即观看”。
创建支付模块和开发订单接口
一、创建支付模块和准备
- 在service模块下创建子模块service_order
- 创建支付相关的表
数据库表: t_order
、 t_pay_log
CREATE TABLE `t_order` (
`id` char(19) NOT NULL DEFAULT '',
`order_no` varchar(20) NOT NULL DEFAULT '' COMMENT '订单号',
`course_id` varchar(19) NOT NULL DEFAULT '' COMMENT '课程id',
`course_title` varchar(100) DEFAULT NULL COMMENT '课程名称',
`course_cover` varchar(255) DEFAULT NULL COMMENT '课程封面',
`teacher_name` varchar(20) DEFAULT NULL COMMENT '讲师名称',
`member_id` varchar(19) NOT NULL DEFAULT '' COMMENT '会员id',
`nickname` varchar(50) DEFAULT NULL COMMENT '会员昵称',
`mobile` varchar(11) DEFAULT NULL COMMENT '会员手机',
`total_fee` decimal(10,2) DEFAULT '0.01' COMMENT '订单金额(分)',
`pay_type` tinyint(3) DEFAULT NULL COMMENT '支付类型(1:微信 2:支付宝)',
`status` tinyint(3) DEFAULT NULL COMMENT '订单状态(0:未支付 1:已支付)',
`is_deleted` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '逻辑删除 1(true)已删除, 0(false)未删除',
`gmt_create` datetime NOT NULL COMMENT '创建时间',
`gmt_modified` datetime NOT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `ux_order_no` (`order_no`),
KEY `idx_course_id` (`course_id`),
KEY `idx_member_id` (`member_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单';
#
# Data for table "t_order"
#
INSERT INTO `t_order` VALUES ('1195693605513891841','1195693605555834880','18','Java精品课程','http://guli-file.oss-cn-beijing.aliyuncs.com/cover/2019/03/06/866e9aca-b530-4f71-a690-72d4a4bfd1e7.jpg','晴天','1','小三1231','13700000001',1,NULL,0,0,'2019-11-16 21:22:25','2019-11-16 21:22:25'),('1195694200178118657','1195694200186507264','18','Java精品课程','http://guli-file.oss-cn-beijing.aliyuncs.com/cover/2019/03/06/866e9aca-b530-4f71-a690-72d4a4bfd1e7.jpg','晴天','1','小三1231','13700000001',1,NULL,0,0,'2019-11-16 21:24:47','2019-11-16 21:24:47'),('1196264007411744769','1196264005255872512','1192252213659774977','java基础课程:test','https://guli-file-190513.oss-cn-beijing.aliyuncs.com/cover/default.gif','晴天','1','小三1231','13700000001',1,NULL,1,0,'2019-11-18 11:09:00','2019-11-18 11:10:14'),('1196265495278174209','1196265495273979904','18','Java精品课程','http://guli-file.oss-cn-beijing.aliyuncs.com/cover/2019/03/06/866e9aca-b530-4f71-a690-72d4a4bfd1e7.jpg','晴天','1','小三1231','13700000001',1,NULL,0,0,'2019-11-18 11:14:54','2019-11-18 11:14:54');
#
# Structure for table "t_pay_log"
#
CREATE TABLE `t_pay_log` (
`id` char(19) NOT NULL DEFAULT '',
`order_no` varchar(20) NOT NULL DEFAULT '' COMMENT '订单号',
`pay_time` datetime DEFAULT NULL COMMENT '支付完成时间',
`total_fee` decimal(10,2) DEFAULT '0.01' COMMENT '支付金额(分)',
`transaction_id` varchar(30) DEFAULT NULL COMMENT '交易流水号',
`trade_state` char(20) DEFAULT NULL COMMENT '交易状态',
`pay_type` tinyint(3) NOT NULL DEFAULT '0' COMMENT '支付类型(1:微信 2:支付宝)',
`attr` text COMMENT '其他属性',
`is_deleted` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '逻辑删除 1(true)已删除, 0(false)未删除',
`gmt_create` datetime NOT NULL COMMENT '创建时间',
`gmt_modified` datetime NOT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_order_no` (`order_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='支付日志表';
#
# Data for table "t_pay_log"
#
INSERT INTO `t_pay_log` VALUES ('1194498446013001730','1194498300579704832','2019-11-13 14:13:17',1,'4200000469201911130676624386','SUCCESS',1,'{\"transaction_id\":\"4200000469201911130676624386\",\"nonce_str\":\"2Lc23ILl231It53M\",\"trade_state\":\"SUCCESS\",\"bank_type\":\"CFT\",\"openid\":\"oNpSGwR-QGG5DaZtDkh2UZlsFDQE\",\"sign\":\"5404850AA3ED0E844DE104651477F07A\",\"return_msg\":\"OK\",\"fee_type\":\"CNY\",\"mch_id\":\"1473426802\",\"cash_fee\":\"1\",\"out_trade_no\":\"1194498300579704832\",\"cash_fee_type\":\"CNY\",\"appid\":\"wx8397f8696b538317\",\"total_fee\":\"1\",\"trade_state_desc\":\"支付成功\",\"trade_type\":\"NATIVE\",\"result_code\":\"SUCCESS\",\"attach\":\"\",\"time_end\":\"20191113141314\",\"is_subscribe\":\"N\",\"return_code\":\"SUCCESS\"}',0,'2019-11-13 14:13:17','2019-11-13 14:13:17'),('1195253787449430017','1195253049260314624','2019-11-15 16:14:44',1,'4200000454201911150981874895','SUCCESS',1,'{\"transaction_id\":\"4200000454201911150981874895\",\"nonce_str\":\"MAM5UM4Xhv1lItvO\",\"trade_state\":\"SUCCESS\",\"bank_type\":\"CFT\",\"openid\":\"oNpSGwR-QGG5DaZtDkh2UZlsFDQE\",\"sign\":\"7DBDCAF4A078B30BB3EF073E6A238C20\",\"return_msg\":\"OK\",\"fee_type\":\"CNY\",\"mch_id\":\"1473426802\",\"cash_fee\":\"1\",\"out_trade_no\":\"1195253049260314624\",\"cash_fee_type\":\"CNY\",\"appid\":\"wx8397f8696b538317\",\"total_fee\":\"1\",\"trade_state_desc\":\"支付成功\",\"trade_type\":\"NATIVE\",\"result_code\":\"SUCCESS\",\"attach\":\"\",\"time_end\":\"20191115161440\",\"is_subscribe\":\"N\",\"return_code\":\"SUCCESS\"}',0,'2019-11-15 16:14:44','2019-11-15 16:14:44'),('1196264321397342210','1196264005255872512','2019-11-18 11:10:14',1,'4200000453201911184025781554','SUCCESS',1,'{\"transaction_id\":\"4200000453201911184025781554\",\"nonce_str\":\"D1dHexCLIFIxAAg2\",\"trade_state\":\"SUCCESS\",\"bank_type\":\"CFT\",\"openid\":\"oNpSGwR-QGG5DaZtDkh2UZlsFDQE\",\"sign\":\"C9F5CA1EE49EA7891736D73BEB423962\",\"return_msg\":\"OK\",\"fee_type\":\"CNY\",\"mch_id\":\"1473426802\",\"cash_fee\":\"1\",\"out_trade_no\":\"1196264005255872512\",\"cash_fee_type\":\"CNY\",\"appid\":\"wx8397f8696b538317\",\"total_fee\":\"1\",\"trade_state_desc\":\"支付成功\",\"trade_type\":\"NATIVE\",\"result_code\":\"SUCCESS\",\"attach\":\"\",\"time_end\":\"20191118111011\",\"is_subscribe\":\"N\",\"return_code\":\"SUCCESS\"}',0,'2019-11-18 11:10:14','2019-11-18 11:10:14');
- 使用代码生成器生成相关代码
public class CodeGenerator {
@Test
public void run() {
// 1、创建代码生成器
AutoGenerator mpg = new AutoGenerator();
// 2、全局配置
GlobalConfig gc = new GlobalConfig();
String projectPath = System.getProperty("user.dir");
gc.setOutputDir("D:\\IDEA\\guli_parent\\service\\service_order" + "/src/main/java"); //输出目录
gc.setAuthor("jyu_zwy"); //作者名
gc.setOpen(false); //生成后是否打开资源管理器
gc.setFileOverride(false); //重新生成时文件是否覆盖
gc.setServiceName("%sService"); //去掉Service接口的首字母I
gc.setIdType(IdType.ID_WORKER_STR); //主键策略
gc.setDateType(DateType.ONLY_DATE);//定义生成的实体类中日期类型
gc.setSwagger2(true);//开启Swagger2模式
mpg.setGlobalConfig(gc);
// 3、数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://localhost:3306/guli?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8");
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("abc123");
dsc.setDbType(DbType.MYSQL);
mpg.setDataSource(dsc);
// 4、包配置
PackageConfig pc = new PackageConfig();
//生成包:com.atguigu.eduservice
pc.setModuleName("eduorder"); //模块名
pc.setParent("com.atguigu");
//生成包:com.atguigu.controller
pc.setController("controller");
pc.setEntity("entity");
pc.setService("service");
pc.setMapper("mapper");
mpg.setPackageInfo(pc);
// 5、策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setInclude("t_order", "t_pay_log");//根据数据库哪张表生成,有多张表就加逗号继续填写
strategy.setNaming(NamingStrategy.underline_to_camel);//数据库表映射到实体的命名策略
strategy.setTablePrefix("t" + "_"); //生成实体时去掉表前缀
strategy.setColumnNaming(NamingStrategy.underline_to_camel);//数据库表字段映射到实体的命名策略
strategy.setEntityLombokModel(true); // lombok 模型 @Accessors(chain = true) setter链式操作
strategy.setRestControllerStyle(true); //restful api风格控制器
strategy.setControllerMappingHyphenStyle(true); //url中驼峰转连字符
mpg.setStrategy(strategy);
// 6、执行
mpg.execute();
}
}
- 在service_order模块中引入依赖
<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>
- 编写application.properties配置文件
# 服务端口
server.port=8007
# 服务名
spring.application.name=service-order
# mysql数据库连接
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/guli?serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=123456
# 返回json的全局时间格式
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8
# 配置mapper xml文件路径
mybatis-plus.mapper-locations=classpath:com/atguigu/eduorder/mapper.xml/*.xml
# mybatis日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
# nacos注册中心
spring.cloud.nacos.discovery.server-addr= localhost:8848
# 开启熔断机制
feign.hystrix.enabled=true
# 设置hystrix超时时间,默认1000ms
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=3000
- 启动类
@SpringBootApplication
@EnableDiscoveryClient
@ComponentScan(basePackages = {"com.atguigu"})
@MapperScan("com.atguigu.eduorder.mapper")
public class OrdersApplication {
public static void main(String[] args) {
SpringApplication.run(OrdersApplication.class, args);
}
}
二、开发创建订单接口
1. 编写订单controller
@RestController
@RequestMapping("/eduorder/order")
@CrossOrigin
public class OrderController {
@Autowired
private OrderService orderService;
// 生成订单的方法
@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);
}
}
2. 远程调用、订单公共返回类Vo
在common模块中建立用于远程接口调用后返回给订单接口的统一返回类:
CourseWebVoOrder
@Data public class CourseWebVoOrder { private static final long serialVersionUID = 1L; 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; }
UcenterMemberOrder
@Data @EqualsAndHashCode(callSuper = false) @Accessors(chain = true) @ApiModel(value="UcenterMember对象", description="会员表") public class UcenterMemberOrder 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)未删除") private Boolean isDeleted; @ApiModelProperty(value = "创建时间") @TableField(fill = FieldFill.INSERT) private Date gmtCreate; @ApiModelProperty(value = "更新时间") @TableField(fill = FieldFill.INSERT_UPDATE) private Date gmtModified; }
3. 在service_edu创建接口
CourseFrontController
@PostMapping("getCourseInfoOrder/{id}")
public CourseWebVoOrder getCourseInfoOrder(@PathVariable String id) {
CourseWebVo courseInfo = courseService.getBaseCourseInfo(id);
CourseWebVoOrder courseWebVoOrder = new CourseWebVoOrder();
BeanUtils.copyProperties(courseInfo, courseWebVoOrder);
return courseWebVoOrder;
}
4. 在service_ucenter创建接口
UcenterMemberController
//根据用户id获取用户信息【订单模块】
@PostMapping("getUserInfoOrder/{id}")
public UcenterMemberOrder getUserInfoOrder(@PathVariable String id) {
UcenterMember member = new UcenterMember();
UcenterMemberOrder ucenterMemberOrder = new UcenterMemberOrder();
BeanUtils.copyProperties(member, ucenterMemberOrder);
return ucenterMemberOrder;
}
5. 编写订单service
(1) 在service_order模块创建接口,实现远程调用
EduClient
:@Component @FeignClient(name = "service-edu") public interface EduClient { //根据课程id查询课程信息 @PostMapping("/eduservice/coursefront/getCourseInfoOrder/{id}") public CourseWebVoOrder getCourseInfoOrder(@PathVariable("id") String id); }
UcenterClient
@Component @FeignClient(name = "service-ucenter") public interface UcenterClient { //根据用户id获取用户信息【订单模块】 @PostMapping("/educenter/member/getUserInfoOrder/{id}") public UcenterMemberOrder getUserInfoOrder(@PathVariable("id") String id); }
(2)在service_order模块编写创建订单service
OrderService
public interface OrderService extends IService<Order> { // 生成订单的方法 String createOrders(String courseId, String memberId); }
OrderServiceImpl
@Service public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService { @Autowired private EduClient eduClient; @Autowired private UcenterClient ucenterClient; //生成订单的方法 @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.setMobile(userInfoOrder.getMobile()); order.setNickname(userInfoOrder.getNickname()); order.setMemberId(memberId); order.setCourseCover(courseInfoOrder.getCover()); order.setCourseId(courseId); order.setCourseTitle(courseInfoOrder.getTitle()); order.setTeacherName(courseInfoOrder.getTeacherName()); order.setTotalFee(courseInfoOrder.getPrice()); order.setStatus(0);//支付状态:( 0:已支付,1:未支付 ) order.setPayType(1);//支付类型: 1:微信 , 2:支付宝 //返回订单号 return order.getOrderNo(); } }
三、开发获取订单接口
在订单controller创建根据id获取订单信息接口:
//2.根据订单id查询订单信息
@GetMapping("getOrderInfo/{orderId}")
public R getOrderInfo(@PathVariable String orderId) {
QueryWrapper<Order> wrapper = new QueryWrapper<>();
wrapper.eq("order_id", orderId);
Order order = orderService.getOne(wrapper);
return R.ok().data("item", order);
}
开发微信支付接口
一、生成微信支付二维码
1. 编写controller
PayLogController
@RestController
@CrossOrigin
@RequestMapping("/eduorder/paylog")
public class PayLogController {
@Autowired
private PayLogService payLogService;
//生成微信支付二维码接口
//参数是订单号
@GetMapping("createNative/{orderNo}")
public R createNative(@PathVariable String orderNo) {
//返回信息,包含二维码地址,还有其他需要的信息
Map map = payLogService.createNative(orderNo);
return R.ok().data(map);
}
}
2. 编写service
PayLogService
public interface PayLogService extends IService<PayLog> { //生成微信支付二维码接口 Map createNative(String orderNo); }
PayLogServiceImpl
@Service public class PayLogServiceImpl extends ServiceImpl<PayLogMapper, PayLog> implements PayLogService { @Autowired private OrderService orderService; //生成微信支付二维码接口 @Override public Map createNative(String orderNo) { try { //1.根据订单号查询订单信息 QueryWrapper<Order> wrapper = new QueryWrapper<>(); wrapper.eq("order_no",orderNo); Order Order = orderService.getOne(wrapper); //2. 使用map设置生成二维码需要参数 Map map = new HashMap(); map.put("appid","wx74862e0dfcf69954"); //支付id map.put("mch_id", "1558950191"); //商户号 map.put("nonce_str", WXPayUtil.generateNonceStr()); //生成随机的字符串,让每次生成的二维码不一样 map.put("body", Order.getCourseTitle()); //生成二维码的名字 map.put("out_trade_no", orderNo); //二维码唯一的标识 map.put("total_fee", Order.getTotalFee().multiply(new BigDecimal("100")).longValue()+""); //支付金额 map.put("spbill_create_ip", "127.0.0.1"); //现在进行支付的ip地址,实际项目使用项目的域名 map.put("notify_url", "http://guli.shop/api/order/weixinPay/weixinNotify"); //支付后回调地址 map.put("trade_type", "NATIVE"); //支付类型,NATIVE:根据价格生成二维码 //3. 发送httpclient请求,传递参数xml格式,微信支付提供的固定地址 HttpClient client = new HttpClient("https://api.mch.weixin.qq.com/pay/unifiedorder"); //设置xml格式的参数 //参数1:要转换为xml格式的map //参数2:商户的key,用于加密二维码中的信息 client.setXmlParam(WXPayUtil.generateSignedXml(map,"T6m9iK73b0kn9g5v426MKfHQH7X8rKwb")); client.setHttps(true);//上面发送请求的是https。默认支持,需要设置为true支持 //执行请求发送 client.post(); //4.得到发送请求返回结果 //返回的结果是xml格式的 String content = client.getContent(); //把xml格式转换为map集合,他里面的数据不全 Map<String, String> resultMap = WXPayUtil.xmlToMap(content); //最后返回数据的封装 HashMap hashMap = new HashMap<>(); hashMap.put("out_trade_no", orderNo); hashMap.put("course_id", Order.getCourseId()); hashMap.put("total_fee", Order.getTotalFee()); hashMap.put("result_code", resultMap.get("result_code")); //二维码操作状态码 hashMap.put("code_url", resultMap.get("code_url")); //二维码地址 //微信支付二维码2小时过期,可采取2小时未支付取消订单 //redisTemplate.opsForValue().set(orderNo, hashMap, 120,TimeUnit.MINUTES); return hashMap; } catch (Exception e) { e.printStackTrace(); throw new GuliException(20001, "生成二维码失败"); } } }
二、获取支付状态接口
1. 编写controller
@RestController
@CrossOrigin
@RequestMapping("/eduorder/paylog")
public class PayLogController {
@Autowired
private PayLogService payLogService;
........
//查询订单支付状态
//参数:订单号,根据订单号查询支付状态
@GetMapping("queryPayStatus/{orderNo}")
public R queryPayStatus(@PathVariable String orderNo) {
Map<String, String> map = payLogService.queryPagStatus(orderNo);
if (map == null) {
return R.ok().message("支付出错了");
}
//如果返回map里面不为空,通过map获取订单状态
if (map.get("trade_state").equals("SUCCESS")) { //支付成功
//添加记录倒支付表,更新订单表订单状态
payLogService.updateOrdersStatus(map);
return R.ok().message("支付成功");
}
return R.ok().message("支付中");
}
}
2. 编写service
PayLogService
public interface PayLogService extends IService<PayLog> { //生成微信支付二维码接口 Map createNative(String orderNo); //根据订单号查询支付状态 Map<String, String> queryPagStatus(String orderNo); //向支付表添加记录,更新订单状态 void updateOrdersStatus(Map<String, String> map); }
PayLogServiceImpl
@Service public class PayLogServiceImpl extends ServiceImpl<PayLogMapper, PayLog> implements PayLogService { @Autowired private OrderService orderService; ...... //根据订单号查询支付状态 @Override public Map<String, String> queryPagStatus(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.setHttps(true); client.setXmlParam(WXPayUtil.generateSignedXml(m, "T6m9iK73b0kn9g5v426MKfHQH7X8rKwb"));//通过商户key加密 client.post(); //3、返回第三方的数据 String xml = client.getContent(); //4、转成Map Map<String, String> resultMap = WXPayUtil.xmlToMap(xml); //5、返回 return resultMap; } catch (Exception e) { return null; } } //向支付表添加记录,更新订单状态 @Override public void updateOrdersStatus(Map<String, String> map) { //获取前一步生成二维码中的订单号 String orderNo = map.get("out_trade_no"); //根据订单号,查询订单信息 QueryWrapper<Order> wrapper = new QueryWrapper<>(); wrapper.eq("order_no",orderNo); Order order = orderService.getOne(wrapper); //判断订单状态是否为1,为1就是支付过了 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); //支付类型 payLog.setTotalFee(order.getTotalFee()); //总金额(分) payLog.setTradeState(map.get("trade_state")); //支付状态 payLog.setTransactionId(map.get("transaction_id")); //订单流水号 payLog.setAttr(JSONObject.toJSONString(map)); baseMapper.insert(payLog); } }
课程支付前端整合
一、页面样式修改
- 复制样式文件到assets
- 修改default.vue页面
import '~/assets/css/reset.css'
import '~/assets/css/theme.css'
import '~/assets/css/global.css'
import '~/assets/css/web.css'
import '~/assets/css/base.css'
import '~/assets/css/activity_tab.css'
import '~/assets/css/bottom_rec.css'
import '~/assets/css/nice_select.css'
import '~/assets/css/order.css'
import '~/assets/css/swiper-3.3.1.min.css'
import "~/assets/css/pages-weixinpay.css"
二、课程支付前端
1. 在api文件夹下创建order.js文件
import request from '@/utils/request'
export default{
//生成订单
createOrders(courseId){
return request({
url: '/eduorder/order/createOrder/'+courseId,
method: 'post'
})
},
//根据订单id查询订单信息
getOrdersInfo(id){
return request({
url: `/eduorder/order/getOrderInfo/${id}`,
method: 'get'
})
},
//生成二维码的方法
createNatvie(orderNo) {
return request({
url: '/eduorder/paylog/createNative/'+orderNo,
method: 'get'
})
},
//查询订单状态的方法
//生成二维码的方法
queryPayStatus(orderNo) {
return request({
url: '/eduorder/paylog/queryPayStatus/'+orderNo,
method: 'get'
})
}
}
2. 在课程详情页面中添加创建订单方法
//生成订单的方法
createOrders(){
ordersApi.createOrders(this.courseWebVo.courseId).then(response => {
//获取返回订单号
// response.data.data.orderId
//跳转订单显示页面
this.$router.push({path:'/orders/'+response.data.data.orderId})
})
},
3. 创建订单页面,显示订单信息
在pages下面创建order文件夹,创建_oid.vue页面
在_oid.vue页面调用方法,获取订单信息:
页面部分
<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/orders' export default { asyncData({ params, error }) { return ordersApi.getOrdersInfo(params.oid) .then(response => { return { order: response.data.data.item } }) }, methods: { //去支付 toPay() { this.$router.push({path:'/pay/'+this.order.orderNo}) } } } </script>
4. 创建支付页面,生成二维码完成支付
页面部分
<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 ordersApi from '@/api/orders' export default { // asyncData({ params, error }) { // return ordersApi.createNatvie(params.pid) // .then(response => { // return { // payObj: response.data.data // } // }) // }, created() { this.orderNo = this.$route.params.orderNo; //生成二维码 this.createQRcode(); }, data() { return { orderNo: "", payObj: {}, timer1: "", } }, //每隔三秒调用一次查询订单状态的方法 mounted() { //页面渲染之后执行 this.timer1 = setInterval(() => { this.queryOrderStatus(this.payObj.out_trade_no) },3000); }, methods:{ //生成二维码 createQRcode() { ordersApi.createNatvie(this.orderNo).then((resp) => { this.payObj = resp.data.data; }); }, 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}) } }) } } } </script>
5. 拦截器
utils\request.js
// 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) // 返回接口返回的错误信息
})
7. 测试
创作不易,如果有帮助到你,请给文章==点个赞和收藏==,让更多的人看到!!!
==关注博主==不迷路,内容持续更新中。