新建微信支付模块
创建支付表数据
根据表信息生成代码
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.GlobalConfig;
import com.baomidou.mybatisplus.generator.config.PackageConfig;
import com.baomidou.mybatisplus.generator.config.StrategyConfig;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import org.junit.Test;
/**
* @author
* @since 2018/12/13
*/
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:\\Javas\\guli_parent\\service\\service_order" + "/src/main/java");
gc.setAuthor("zyfTest");
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?serverTimezone=Asia/Shanghai");
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("636474");
dsc.setDbType(DbType.MYSQL);
mpg.setDataSource(dsc);
// 4、包配置
PackageConfig pc = new PackageConfig();
//com.atguigu.eduservice
pc.setModuleName("eduorder"); //模块名
pc.setParent("com.atguigu");
//com.atguigu.eduservice.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(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();
}
}
配置文件
#项目端口号
server.port=8009
#项目名称
spring.application.name=service-order
# ?????dev(????)?test ?????prod ???? 项目运行环境
spring.profiles.active=dev
# mysql?????
spring.datasource.type=com.zaxxer.hikari.HikariDataSource
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#spring.datasource.url=jdbc:mysql://localhost:3306/guli?characterEncoding=utf-8&useSSL=false
spring.datasource.url=jdbc:mysql://localhost:3306/guli?serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=636474
#??json???????返回给前端的 json时间格式
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8
# 设置日志级别
#logging.level.root=INFO
#mybatis??
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
#??Swagger
spring.mvc.pathmatch.matching-strategy=ANT_PATH_MATCHER
#配置mapper xml文件的路径
mybatis-plus.mapper-locations=classpath:com/atguigu/eruorder/mapper/xml/*.xml
# nacos服务地址 127.0.0.1表示本机运行,部署服务器需要更改ip
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
#开启熔断机制
feign.hystrix.enabled=true
# 设置hystrix超时时间,默认1000ms
#hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=6000
生成订单
controller
@RestController
@RequestMapping("/eduorder/t-order")
@Api(description = "订单管理")
@CrossOrigin
public class TOrderController {
//注入订单管理service
@Autowired
private TOrderService tOrderService;
//根据课程id生成订单,返回订单id
@ApiOperation(value = "根据课程id生成订单")
@GetMapping("createOrder/{courseId}")
public R createOrder(@PathVariable String courseId, HttpServletRequest request){
//生成订单需要课程信息和用户信息,远程调用获取,用户id从token中取
String OrderId = tOrderService.saveOrder(courseId, JwtUtils.getMemberIdByJwtToken(request));
return R.ok().data("OrderId",OrderId);
}
}
service
package com.atguigu.eduorder.service.impl;
import com.atguigu.commonutils.orderVo.CourseWebVoOrder;
import com.atguigu.commonutils.orderVo.UcenterMemberOrderVo;
import com.atguigu.eduorder.clint.CourseClint;
import com.atguigu.eduorder.clint.UcenterClint;
import com.atguigu.eduorder.entity.TOrder;
import com.atguigu.eduorder.mapper.TOrderMapper;
import com.atguigu.eduorder.service.TOrderService;
import com.atguigu.eduorder.utils.OrderNoUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* <p>
* 订单 服务实现类
* </p>
*
* @author zyfTest
* @since 2023-02-08
*/
@Service
public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> implements TOrderService {
//注入两个远程调用的接口
@Autowired
private CourseClint courseClint;
@Autowired
private UcenterClint ucenterClint;
@Override
public String saveOrder(String courseId, String memberId) {
//根据课程id获取课程信息
CourseWebVoOrder courseWebVoOrder = courseClint.selectCourseByIdIsOrder(courseId);
//根据ucenterid获取用户信息
UcenterMemberOrderVo ucenterMemberOrderVo = ucenterClint.selectUcenterInfoById(memberId);
TOrder tOrder = new TOrder();
tOrder.setOrderNo(OrderNoUtil.getOrderNo());//订单号
tOrder.setCourseId(courseWebVoOrder.getId());//课程id
tOrder.setCourseTitle(courseWebVoOrder.getTitle());//课程名称
tOrder.setCourseCover(courseWebVoOrder.getCover());//课程封面
tOrder.setTeacherName(courseWebVoOrder.getTeacherName());//讲师名称
tOrder.setMemberId(ucenterMemberOrderVo.getId());//用户id
tOrder.setNickname(ucenterMemberOrderVo.getNickname());//用户昵称
tOrder.setMobile(ucenterMemberOrderVo.getMobile());//用户手机
tOrder.setStatus(0);//订单状态 0 未支付 1 已支付
tOrder.setPayType(1); //支付方式 1 微信支付 2 支付宝支付
//将信息封装到TOrder对象插入数据库返回订单号
baseMapper.insert(tOrder);
String orderNo = tOrder.getOrderNo();
return orderNo;
}
}
远程调用接口获取用户信息和课程信息
获取课程信息
//根据课程id查询课程信息
@ApiOperation("根据课程id查询课程信息")
@PostMapping("selectCourseByIdIsOrder/{id}")
public CourseWebVoOrder selectCourseByIdIsOrder(@PathVariable String id){
CourseWebVo courseWebVo = courseService.selectCourseById(id);
CourseWebVoOrder courseWebVoOrder = new CourseWebVoOrder();
BeanUtils.copyProperties(courseWebVo,courseWebVoOrder);
return courseWebVoOrder;
}
返回的实体类
package com.atguigu.commonutils.orderVo;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
/**
* 根据id查询课程详细信息的实体类对象
* 用于封装查询到的数据到这个实体类返回给前端
*/
@Data
public class CourseWebVoOrder implements Serializable {
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;
}
远程调用的接口
@Component //交给spring管理
//name="service-vod" 调用注册中心的哪个微服务 fallback=ClintVideoImpl.class 熔断后调用的实现类,执行实现类中的方法
@FeignClient(name="service-edu")
public interface CourseClint {
//根据课程id查询课程信息
@ApiOperation("根据课程id查询课程信息")
@PostMapping("/eduservice/coursefront/selectCourseByIdIsOrder/{id}")
public CourseWebVoOrder selectCourseByIdIsOrder(@PathVariable("id") String id);
}
获取用户信息
//根据用户id 查询用户信息
@ApiOperation("根据用户id 查询用户信息")
//这里需要进行远程调用,所有前端传过来的是一堆json数据所以用post,对json数据进行拆分请求
//或者是作为被调用端用post请求更合适,因为调用端是get请求,不能再通过get再次进行请求
@PostMapping("selectUcenterInfoById/{id}")
public UcenterMemberOrderVo selectUcenterInfoById(@PathVariable String id){
UcenterMember ucenterMember = memberService.getById(id);
UcenterMemberOrderVo ucenterMemberOrderVo = new UcenterMemberOrderVo();
BeanUtils.copyProperties(ucenterMember,ucenterMemberOrderVo);
return ucenterMemberOrderVo;
}
返回的实体类,调用端和被调用端都从这个实体类中拿数据,以免造成数据的不一致性
package com.atguigu.commonutils.orderVo;
import com.baomidou.mybatisplus.annotation.*;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.util.Date;
/**
* <p>
* 会员表
* </p>
*
* @author zyfTest
* @since 2023-01-29
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@ApiModel(value="UcenterMember对象", description="会员表")
public class UcenterMemberOrderVo 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;
@TableLogic //逻辑删除
@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;
}
远程调用接口
@Component //交给spring管理
//name="service-vod" 调用注册中心的哪个微服务 fallback=ClintVideoImpl.class 熔断后调用的实现类,执行实现类中的方法
@FeignClient(name="service-ucenter")
public interface UcenterClint {
//根据用户id 查询用户信息
@ApiOperation("根据用户id 查询用户信息")
//这里需要进行远程调用,所有前端传过来的是一堆json数据所以用post,对json数据进行拆分请求
//或者是作为被调用端用post请求更合适,因为调用端是get请求,不能再通过get再次进行请求
@PostMapping("/educenter/ucenter/selectUcenterInfoById/{id}")
public UcenterMemberOrderVo selectUcenterInfoById(@PathVariable("id") String id);
}
远程调用报 404 异常一定要检查远程接口路径是否正确
卡了3个小时
这里的 远程调用为什么使用 post请求
或者也可以理解为,get对应的是 查询 post 新增 delete 删除 update 修改,这里我们需要往数据库新增一条数据,就需要使用post请求了 。
前端api
当地址栏已经存在我们需要的id等数据,就需要考虑发送post请求了,发送psot请求,后端仍可以使用pathVvriable进行接收,但是当前端使用 post 请求发送很多数据 就需要使用@RequestParam进行接收了,当前端给调用者的是post,被调用着等接口和方法都应该与之保持一致。
查询订单
根据订单id查询订单信息
//根据订单id查询订单信息
@ApiOperation("根据订单id查询订单信息")
@GetMapping("getOrderInfo/{id}")
public R getOrderInfo(@PathVariable String id){
//不是使用表id而是字段 订单 id 所以不能用 id去查
//TOrder tOrder = tOrderService.getById(id);
QueryWrapper<TOrder> wrapper = new QueryWrapper<>();
wrapper.eq("order_no",id);
TOrder one = tOrderService.getOne(wrapper);
return R.ok().data("item",one);
}
根据订单号生成微信支付二维码
controller
*/
@RestController
@RequestMapping("/eduorder/paylog")
@CrossOrigin
@Api(description = "订单支付管理")
public class TPayLogController {
@Autowired
private TPayLogService tPayLogService;
//根据订单号生成订单二维码
@ApiOperation("根据订单号生成订单二维码")
@GetMapping("creatNative/{orderNo}")
public R saveTwoCode(@PathVariable String orderNo){
//返回信息有二维码还有其他信息所以使用map进行接收
Map map = tPayLogService.creatTwoCode(orderNo);
return R.ok().data(map);
}
service
@Service
public class TPayLogServiceImpl extends ServiceImpl<TPayLogMapper, TPayLog> implements TPayLogService {
@Autowired
private TOrderService tOrderService;
@Override
public Map creatTwoCode(String orderNo) {
try {
//1 根据订单号查询订单信息
QueryWrapper<TOrder> wrapper = new QueryWrapper<>();
wrapper.eq("order_no",orderNo);
TOrder order = tOrderService.getOne(wrapper);
//2 使用map设置生成二维码需要参数
Map m = new HashMap();
m.put("appid","wx74862e0dfcf69954"); //关联的公众号appid
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", "192.168.0.104"); //本机ip
m.put("notify_url", "http://guli.shop/api/order/weixinPay/weixinNotify\n"); //商户key
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<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
//查询订单支付状态
//参数:订单号,根据订单号查询 支付状态
@ApiOperation("查询订单支付状态")
@GetMapping("queryPayStatus/{orderNo}")
public R queryPayStatus(@PathVariable String orderNo) {
Map<String,String> map = tPayLogService.queryPayStatus(orderNo);
System.out.println("*****查询订单状态map集合:"+map);
if(map == null) {
return R.error().message("支付出错了");
}
//如果返回map里面不为空,通过map获取订单状态
if(map.get("trade_state").equals("SUCCESS")) {//支付成功
//添加记录到支付表,更新订单表订单状态
tPayLogService.updateOrdersStatus(map);
return R.ok().message("支付成功");
}
return R.ok().code(25000).message("支付中");
}
service
//查询订单支付状态
@Override
public Map<String, String> 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<String, String> resultMap = WXPayUtil.xmlToMap(xml);
//6、转成Map再返回
return resultMap;
}catch(Exception e) {
return null;
}
}
//添加支付记录和更新订单状态
@Override
public void updateOrdersStatus(Map<String, String> map) {
//从map获取订单号
String orderNo = map.get("out_trade_no");
//根据订单号查询订单信息
QueryWrapper<TOrder> wrapper = new QueryWrapper<>();
wrapper.eq("order_no",orderNo);
TOrder order = tOrderService.getOne(wrapper);
//更新订单表订单状态
if(order.getStatus().intValue() == 1) { return; }
order.setStatus(1);//1代表已经支付
tOrderService.updateById(order);
//向支付表添加支付记录
TPayLog payLog = new TPayLog();
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);
}
判断用户是否购买该课程
@ApiOperation("根据课程 id和用户 id判断是否购买了该课程")
@GetMapping("isBuyCourse/{courseId}/{memberId}")
public Boolean isBuyCourse(@PathVariable String courseId,@PathVariable String memberId){
QueryWrapper<TOrder> wrapper = new QueryWrapper<>();
wrapper.eq("course_id",courseId);
wrapper.eq("member_id",memberId);
wrapper.eq("status",1);
int count = tOrderService.count(wrapper);
if(count>0){
return true;
}else {
return false;
}
}
在前台课程某块远程调用这个方法
远程调用接口
@Component
@FeignClient(name="service-order")
public interface OrdersClint {
@ApiOperation("根据课程 id和用户 id判断是否购买了该课程")
@GetMapping("/eduorder/order/isBuyCourse/{courseId}/{memberId}")
public Boolean isBuyCourse(@PathVariable String courseId, @PathVariable String memberId);
}
注入接口并对调用者进行修改
修改后
@Autowired
private OrdersClint ordersClint;
//根据课程id查询课程详情信息
@ApiOperation(value = "根据课程id查询课程详情信息")
@GetMapping("queryCourseById/{id}")
public R queryCourseById(@PathVariable String id, HttpServletRequest request){
//根据id查询课程详细信息
CourseWebVo courseWebVo = eduCourseService.selectCourseById(id);
//根据id查询课程章节信息
List<ChapterVo> courseChapter = eduChapterService.getCourseChapter(id);
String memberIdByJwtToken = JwtUtils.getMemberIdByJwtToken(request);
//如果获取不到用户的 id 直接判没有购买该课程
Boolean buyCourse = false;
if(!StringUtils.isEmpty(memberIdByJwtToken)){
//根据课程 id和用户 id判断是否购买了该课程
buyCourse = ordersClint.isBuyCourse(id,memberIdByJwtToken);
}
return R.ok().data("courseWebVo",courseWebVo).data("chapterVideoList",courseChapter).data("isBuy",buyCourse);
}