生成支付二维码
需求分析
执行流程
再次打开课程支付引导界面,点击“支付宝支付”按钮系统该如何处理?

点击支付宝支付
此时打开支付二维码,用户扫码支付。
所以首先需要生成支付二维码,用户扫描二维码开始请求支付宝下单,在向支付宝下单前需要添加选课记录、创建商品订单、生成支付交易记录。
生成二维码执行流程如下:
sequenceDiagram
actor 用户
用户 ->> 前端: 点击支付
前端 ->> 学习服务中心: 添加选课记录
前端 ->> 订单服务: 创建商品订单
订单服务 ->> 订单服务: 生成支付记录
订单服务 ->> 订单服务: 生成二维码
订单服务 ->> 前端: 返回二维码
执行流程:
-
前端调用学习中心服务的添加选课接口。
-
添加选课成功请求订单服务生成支付二维码接口。
-
生成二维码接口:创建商品订单、生成支付交易记录、生成二维码。
-
将二维码返回到前端,用户扫码。
用户扫码支付流程如下:
sequenceDiagram
actor 用户
用户 ->> 前端: 支付宝沙箱扫码
前端 ->> 订单服务: 下单
订单服务 ->> 订单服务: 构造请求参数
订单服务 ->> 支付宝: 下单
订单服务 ->> 前端: 响应
用户 ->> 前端: 输入密码支付
前端 ->> 支付宝: 请求支付
支付宝 ->> 订单服务: 通知支付结果
订单服务 ->> 订单服务: 接收支付结果并验收
用户 ->> 前端: 查询支付结果
前端 ->> 订单服务: 查询支付结果
订单服务 ->> 支付宝: 查询支付结果
执行流程:
-
用户输入支付密码,支付成功。
-
接收第三方平台通知的支付结果。
-
根据支付结果更新支付交易记录的支付状态为支付成功。
数据模型
订单支付模式的核心由三张表组成:订单表、订单明细表、支付交易记录表。

订单号注意唯一性、安全性、尽量短等特点,生成方案常用的如下:
时间戳+随机数
年月日时分秒毫秒+随机数
高并发场景
年月日时分秒毫秒+随机数+redis自增序列
订单号中加上业务标识
订单号加上业务标识方便客服,比如:第10位是业务类型,第11位是用户类型等。
雪花算法
雪花算法是推特内部
使用的分布式环境下的唯一ID生成算法,它基于时间戳生成,保证有序递增,加以入计算机硬件等元素,可以满足高并发环境下ID不重复。
本项目订单号生成采用雪花算法。
接口开发
保存商品订单
- 定义保存订单信息接口:
1 2 3 4 5 6 7 8 9
| public interface OrderService {
PayRecordDto createOrder(String userId, AddOrderDto addOrderDto); }
|
- 在保存订单接口中需要完成创建商品订单、创建支付交易记录,接口实现方法如下:
1 2 3 4 5 6 7 8 9 10 11 12
| @Transactional @Override public PayRecordDto createOrder(String userId, AddOrderDto addOrderDto) {
return null; }
|
- 编写创建商品订单方法,商品订单的数据来源于选课记录,在订单表需要存入选课记录的ID,这里需要作好幂等处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| public XcOrders saveXcOrders(String userId, AddOrderDto addOrderDto) {
XcOrders xcOrdersByBusinessId = getXcOrdersByBusinessId(addOrderDto.getOutBusinessId()); if(xcOrdersByBusinessId != null){ return xcOrdersByBusinessId; } XcOrders xcOrders = new XcOrders(); xcOrders.setId(IdWorkerUtils.getInstance().nextId()); xcOrders.setTotalPrice(addOrderDto.getTotalPrice()); xcOrders.setCreateDate(LocalDateTime.now()); xcOrders.setStatus("600001"); xcOrders.setUserId(userId); xcOrders.setOrderType("60201"); xcOrders.setOrderName(addOrderDto.getOrderName()); xcOrders.setOrderDescrip(addOrderDto.getOrderDescrip()); xcOrders.setOrderDetail(addOrderDto.getOrderDetail()); xcOrders.setOutBusinessId(addOrderDto.getOutBusinessId()); int insert = xcOrdersMapper.insert(xcOrders); if(insert <= 0){ XueChengPlusException.cast("插入订单失败"); } Long orderId = xcOrders.getId(); String orderDetailJSON = addOrderDto.getOrderDetail(); List<XcOrdersGoods> xcOrdersGoods = JSON.parseArray(orderDetailJSON, XcOrdersGoods.class); for (XcOrdersGoods xcOrdersGoods1 : xcOrdersGoods) { xcOrdersGoods1.setOrderId(orderId); int insert1 = xcOrdersGoodsMapper.insert(xcOrdersGoods1); if(insert1 <= 0){ XueChengPlusException.cast("插入订单明细失败"); } } return xcOrders; }
|
- 插入交易记录表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
|
private XcPayRecord createPayRecord(XcOrders orders) { Long orderId = orders.getId(); XcOrders xcOrders = xcOrdersMapper.selectById(orderId); if(xcOrders == null){ XueChengPlusException.cast("订单不存在"); } String status = xcOrders.getStatus(); if("601002".equals(status)){ XueChengPlusException.cast("订单已支付"); } XcPayRecord xcPayRecord = new XcPayRecord(); xcPayRecord.setPayNo(IdWorkerUtils.getInstance().nextId()); xcPayRecord.setOrderId(orderId); xcPayRecord.setOrderName(xcOrders.getOrderName()); xcPayRecord.setTotalPrice(xcOrders.getTotalPrice()); xcPayRecord.setCurrency("CNY"); xcPayRecord.setCreateDate(LocalDateTime.now()); xcPayRecord.setStatus("601001"); xcPayRecord.setUserId(xcOrders.getUserId()); int insert = xcPayRecordMapper.insert(xcPayRecord); if(insert <= 0){ XueChengPlusException.cast("插入支付记录失败"); } return xcPayRecord; }
|
创建支付交易记录
为什么创建支付交易记录?
在请求微信或支付宝下单接口时需要传入 商品订单号,在与第三方支付平台对接时发现,当用户支付失败或因为其它原因最终该订单没有支付成功,此时再次调用第三方支付平台的下单接口发现报错“订单号已存在”,此时如果我们传入一个没有使用过的订单号就可以解决问题,但是商品订单已经创建,因为没有支付成功重新创建一个新订单是不合理的。
解决以上问题的方案是:
-
用户每次发起都创建一个新的支付交易记录 ,此交易记录与商品订单关联。
-
将支付交易记录的流水号传给第三方支付系统下单接口,这样就即使没有支付成功就不会出现上边的问题。
-
需要提醒用户不要重复支付。
编写创建支付交易记录的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public XcPayRecord createPayRecord(XcOrders orders){ if(order==null){ XueChengPlusException.cast("订单不存在"); } if(orders.getStatus().equals("600002")){ XueChengPlusException.cast("订单已支付"); } XcPayRecord payRecord = new XcPayRecord(); long payNo = IdWorkerUtils.getInstance().nextId(); payRecord.setPayNo(payNo); payRecord.setOrderId(orders.getId()); payRecord.setOrderName(orders.getOrderName()); payRecord.setTotalPrice(orders.getTotalPrice()); payRecord.setCurrency("CNY"); payRecord.setCreateDate(LocalDateTime.now()); payRecord.setStatus("601001"); payRecord.setUserId(orders.getUserId()); payRecordMapper.insert(payRecord); return payRecord; }
|
生成支付二维码
- 在nacos中order-service-dev.yaml配置二维码url
1 2
| pay: qrcodeurl: http://192.168.101.1/api/orders/requestpay?payNo=%s
|
- 完善创建订单service方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| @Value("${pay.qrcodeurl}") private String qrcodeUrl;
@Transactional @Override public PayRecordDto createOrder(String userId, AddOrderDto addOrderDto) {
XcOrders xcOrders = saveXcOrders(userId, addOrderDto);
XcPayRecord xcPayRecord = createPayRecord(xcOrders); Long payNo = xcPayRecord.getPayNo();
QRCodeUtil qrCodeUtil = new QRCodeUtil(); String url = String.format(qrcodeUrl, payNo); String qrCode = null; try { qrCode = qrCodeUtil.createQRCode(url, 200, 200); } catch (IOException e) { XueChengPlusException.cast("生成支付二维码失败"); } PayRecordDto payRecordDto = new PayRecordDto(); BeanUtils.copyProperties(xcPayRecord, payRecordDto); payRecordDto.setQrcode(qrCode);
return payRecordDto; }
|
生成二维码接口完善
完善生成支付二维码controller接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @Autowired OrderService orderService;
@ApiOperation("生成支付二维码") @PostMapping("/generatepaycode") @ResponseBody public PayRecordDto generatePayCode(@RequestBody AddOrderDto addOrderDto) { SecurityUtil.XcUser user = SecurityUtil.getUser(); if(user == null){ XueChengPlusException.cast("请登录后继续选课"); } return orderService.createOrder(user.getId(), addOrderDto); }
|
扫码下单接口完善
生成了支付二维码,用户扫码请求第三方支付平台下单、支付。
- 定义查询支付交易记录的Service接口与实现方法
1 2 3 4 5 6
|
XcPayRecord getPayRecordByPayno(String payNo);
|
实现方法:
1 2 3 4 5
| @Override public XcPayRecord getPayRecordByPayno(String payNo) { return xcPayRecordMapper.selectOne(new LambdaQueryWrapper<XcPayRecord>().eq(XcPayRecord::getPayNo, payNo)); }
|
- 下单接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| @ApiOperation("扫码下单接口") @GetMapping("/requestpay") public void requestpay(String payNo, HttpServletResponse httpResponse) throws IOException, AlipayApiException { XcPayRecord xcPayRecordByPayNo = orderService.getPayRecordByPayno(payNo); if ( xcPayRecordByPayNo == null ) { XueChengPlusException.cast("支付记录不存在"); } if ( "601002".equals(xcPayRecordByPayNo.getStatus()) ) { XueChengPlusException.cast("支付已经成功,无需重复支付"); } AlipayClient alipayClient = new DefaultAlipayClient(AlipayConfig.URL, APP_ID, APP_PRIVATE_KEY, AlipayConfig.FORMAT, AlipayConfig.CHARSET, ALIPAY_PUBLIC_KEY, AlipayConfig.SIGNTYPE); AlipayTradeWapPayRequest alipayRequest = new AlipayTradeWapPayRequest(); alipayRequest.setBizContent("{" + " \"out_trade_no\":\"" + payNo + "\"," + " \"total_amount\":" + xcPayRecordByPayNo.getTotalPrice() + "," + " \"subject\":\"" + xcPayRecordByPayNo.getOrderName() + "\"," + " \"product_code\":\"QUICK_WAP_WAY\"" + " }"); String form = alipayClient.pageExecute(alipayRequest).getBody(); httpResponse.setContentType("text/html;charset=" + AlipayConfig.CHARSET); httpResponse.getWriter().write(form); httpResponse.getWriter().flush();
}
|
支付测试
测试准备:
-
启动网关服务、认证服务、验证码服务、学习中心服务、订单服务、内容管理服务。
-
发布一门收费课程。
-
使用资料目录中的新模板course_template.ftl
测试流程:
-
进入收费课程详细页面,点击马上学习。
-
跟踪浏览器及微服务,观察选课记录是否创建成功、商品订单是否创建成功、支付交易记录是否创建成功。
-
观察生成二维码是否成功
-
使用模拟器扫码测试,是否可以正常支付。
最终结果

手机沙箱测试成功
查询支付结果
接口定义
根据前边我们调研的获取支付结果的接口,包括:主动查询支付结果、被动接收支付结果。
这里先实现主动查询支付结果,当支付完成用户点击“支付结果”将请求第三方支付平台查询支付结果。
在OrderController
类中定义接口如下:
1 2 3 4 5 6 7
| @ApiOperation("查询支付结果") @GetMapping("/payresult") @ResponseBody public PayRecordDto payresult(String payNo) throws IOException { return null; }
|
接口实现
service
总体接口
1 2 3 4 5 6
|
PayRecordDto queryPayResult(String payNo);
|
service
实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| @Override public PayRecordDto queryPayResult(String payNo) throws AlipayApiException { XcPayRecord payRecord = getPayRecordByPayno(payNo); if(payRecord == null){ XueChengPlusException.cast("请重新点击获取二维码"); } PayStatusDto payStatusDto = queryPayResultFromAlipay(payNo); System.out.println("payStatusDto = " + payStatusDto); if(payStatusDto == null){ XueChengPlusException.cast("支付记录不存在"); }
currentProxy.saveAliPayStatus(payStatusDto);
XcPayRecord payRecordByPayno = getPayRecordByPayno(payNo); PayRecordDto payRecordDto = new PayRecordDto(); BeanUtils.copyProperties(payRecordByPayno, payRecordDto); return payRecordDto; }
|
查询支付结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
|
public PayStatusDto queryPayResultFromAlipay(String payNo) throws AlipayApiException { AlipayClient alipayClient = new DefaultAlipayClient("https://openapi-sandbox.dl.alipaydev.com/gateway.do", APP_ID, APP_PRIVATE_KEY, AlipayConfig.FORMAT, AlipayConfig.CHARSET, ALIPAY_PUBLIC_KEY, AlipayConfig.SIGNTYPE); AlipayTradeQueryRequest request = new AlipayTradeQueryRequest(); JSONObject bizContent = new JSONObject(); bizContent.put("out_trade_no", payNo); request.setBizContent(bizContent.toString()); String resultJson = null; try{ AlipayTradeQueryResponse response = alipayClient.execute(request); if(!response.isSuccess()){ XueChengPlusException.cast("交易不成功"); } resultJson = response.getBody(); }catch (AlipayApiException e){ e.printStackTrace(); XueChengPlusException.cast("调用支付宝查询接口失败"); } Map resultMap = JSON.parseObject(resultJson, Map.class); Map alipay_trade_query_response = (Map) resultMap.get("alipay_trade_query_response"); String trade_status = (String) alipay_trade_query_response.get("trade_status"); String total_amount = (String) alipay_trade_query_response.get("total_amount"); String trade_no = (String) alipay_trade_query_response.get("trade_no"); PayStatusDto payStatusDto = new PayStatusDto(); payStatusDto.setOut_trade_no(payNo); payStatusDto.setTrade_status(trade_status); payStatusDto.setApp_id(APP_ID); payStatusDto.setTrade_no(trade_no); payStatusDto.setTotal_amount(total_amount); return payStatusDto; }
|
保存支付结果
- 定义接口
1 2 3 4 5
|
void saveAliPayStatus(PayStatusDto payStatusDto);
|
- 实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| @Transactional public void saveAliPayStatus(PayStatusDto payStatusDto){
String payNO = payStatusDto.getOut_trade_no(); XcPayRecord payRecordByPayno = getPayRecordByPayno(payNO); if(payRecordByPayno == null){ XueChengPlusException.cast("找不到相关支付记录"); } Long orderId = payRecordByPayno.getOrderId(); XcOrders xcOrders = xcOrdersMapper.selectById(orderId); if(xcOrders == null){ XueChengPlusException.cast("找不到相关订单"); } String statusFromDb = payRecordByPayno.getStatus(); if("601002".equals(statusFromDb)){ return; } String tradeStatus = payStatusDto.getTrade_status(); if("TRADE_SUCCESS".equals(tradeStatus)){ payRecordByPayno.setStatus("601002"); payRecordByPayno.setOutPayNo(payStatusDto.getTrade_no()); payRecordByPayno.setOutPayChannel("Alipay"); payRecordByPayno.setPaySuccessTime(LocalDateTime.now()); int update = xcPayRecordMapper.updateById(payRecordByPayno); if(update <= 0){ XueChengPlusException.cast("更新支付记录失败"); } xcOrders.setStatus("600002");
int update1 = xcOrdersMapper.updateById(xcOrders); if(update1 <= 0){ XueChengPlusException.cast("更新订单失败"); } } }
|
接口测试
对界面扫码后查数据库验证即可

接口问题
由于雪花ID发送到前端会产生精度丢失,所以我们需要在后端的base包下处理格式
日后在遇到相同问题百度可以搜到相应答案
接收支付通知
接口定义
支付完成后第三方支付系统会主动通知支付结果,要实现主动通知需要在请求支付系统下单时传入NotifyUrl,这里有两个url:NotifyUrl和ReturnUrl,ReturnUrl是支付完成后支付系统携带支付结果重定向到ReturnUrl地址,NotifyUrl是支付完成后支付系统在后台定时去通知,使用NotifyUrl比使用ReturnUrl有保证。
根据接口描述:https://opendocs.alipay.com/open/203/105286的内容下边在订单服务定义接收支付结果通知的接口。
首先在下单时指定NotifyUrl:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| @ApiOperation("接收支付结果通知") @PostMapping("/receivenotify") public void receivenotify(HttpServletRequest request, HttpServletResponse response) throws IOException, AlipayApiException { Map<String, String> params = new HashMap<>(); Map requestParams = request.getParameterMap(); for ( Iterator iter = requestParams.keySet().iterator(); iter.hasNext(); ) { String name = (String) iter.next(); String[] values = (String[]) requestParams.get(name); String valueStr = ""; for ( int i = 0; i < values.length; i++ ) { valueStr = (i == values.length - 1) ? valueStr + values[i] : valueStr + values[i] + ","; } params.put(name, valueStr); }
boolean verify_result = AlipaySignature.rsaCheckV1(params, ALIPAY_PUBLIC_KEY, AlipayConfig.CHARSET, "RSA2");
if ( verify_result ) {
String out_trade_no = new String(request.getParameter("out_trade_no").getBytes("ISO-8859-1"), "UTF-8");
String trade_no = new String(request.getParameter("trade_no").getBytes("ISO-8859-1"), "UTF-8");
String trade_status = new String(request.getParameter("trade_status").getBytes("ISO-8859-1"), "UTF-8"); String total_amount = new String(request.getParameter("total_amount").getBytes("ISO-8859-1"), "UTF-8"); if ( trade_status.equals("TRADE_SUCCESS") ) { PayStatusDto payStatusDto = new PayStatusDto(); payStatusDto.setTrade_status(trade_status); payStatusDto.setTrade_no(trade_no); payStatusDto.setOut_trade_no(out_trade_no); payStatusDto.setTotal_amount(total_amount); payStatusDto.setApp_id(APP_ID); orderService.saveAliPayStatus(payStatusDto); } response.getWriter().write("success"); } else { response.getWriter().write("fail"); } }
|
Service接口:
1 2 3 4 5
|
void saveAliPayStatus(PayStatusDto payStatusDto);
|
Service实现类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| @Transactional public void saveAliPayStatus(PayStatusDto payStatusDto){
String payNO = payStatusDto.getOut_trade_no(); XcPayRecord payRecordByPayno = getPayRecordByPayno(payNO); if(payRecordByPayno == null){ XueChengPlusException.cast("找不到相关支付记录"); } Long orderId = payRecordByPayno.getOrderId(); XcOrders xcOrders = xcOrdersMapper.selectById(orderId); if(xcOrders == null){ XueChengPlusException.cast("找不到相关订单"); } String statusFromDb = payRecordByPayno.getStatus(); if("601002".equals(statusFromDb)){ return; } String tradeStatus = payStatusDto.getTrade_status(); if("TRADE_SUCCESS".equals(tradeStatus)){ payRecordByPayno.setStatus("601002"); payRecordByPayno.setOutPayNo(payStatusDto.getTrade_no()); payRecordByPayno.setOutPayChannel("Alipay"); payRecordByPayno.setPaySuccessTime(LocalDateTime.now()); int update = xcPayRecordMapper.updateById(payRecordByPayno); if(update <= 0){ XueChengPlusException.cast("更新支付记录失败"); } xcOrders.setStatus("600002");
int update1 = xcOrdersMapper.updateById(xcOrders); if(update1 <= 0){ XueChengPlusException.cast("更新订单失败"); } } }
|
接口测试
测试准备:
-
启动网关服务、认证服务、验证码服务、学习中心服务、内容管理服务。
-
发布一门收费课程。
测试流程:
-
对选课进行支付
-
支付成功跟踪service方法的日志,支付成功需要更新支付交易表记录的状态、通知时间、支付宝交易号、支付渠道(Alipay)
支付成功更新订单表的状态为空。
支付通知
需求分析
订单服务作为通用服务在订单支付成功后需要将支付结果异步通知给其它微服务。
下图使用了消息队列完成支付结果通知:

学习中心服务:对收费课程选课需要支付,与订单服务对接完成支付。
学习资源服务:对收费的学习资料需要购买后下载,与订单服务对接完成支付。
订单服务完成支付后将支付结果发给每一个与订单服务对接的微服务,订单服务将消息发给交换机,由交换机广播消息,每个订阅消息的微服务都可以接收到支付结果.
微服务收到支付结果根据订单的类型去更新自己的业务数据。
技术方案
使用消息队列进行异步通知需要保证消息的可靠性,即生产端将消息成功通知到消费端。
消息从生产端发送到消费端经历了如下过程:
-
消息发送到交换机
-
消息由交换机发送到队列
-
消息者收到消息进行处理
保证消息的可靠性需要保证以上过程的可靠性,本项目使用RabbitMQ可以通过如下方面保证消息的可靠性。
- 生产者确认机制
发送消息前使用数据库事务将消息保证到数据库表中
成功发送到交换机将消息从数据库中删除
- mq持久化
mq收到消息进行持久化,当mq重启即使消息没有消费完也不会丢失。
需要配置交换机持久化、队列持久化、发送消息时设置持久化。
3、消费者确认机制
消费者消费成功自动发送ack,否则重试消费。
发送支付结果
订单集成服务MQ
订单服务通过消息队列将支付结果发给学习中心服务,消息队列采用发布订阅模式。
-
订单服务创建支付结果通知交换机。
-
学习中心服务绑定队列到交换机。
项目使用RabbitMQ作为消息队列,在课前下发的虚拟上已经安装了RabbitMQ.
执行docker start rabbitmq 启动RabbitMQ。访问:http://192.168.101.65:15672/
账户密码:guest/guest
交换机为Fanout广播模式。
首先需要在学习中心服务和订单服务工程配置连接消息队列。
- 首先在订单服务添加消息队列依赖
1 2 3 4
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency>
|
- 在nacos配置rabbitmq-dev.yaml为通用配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| spring: rabbitmq: host: 192.168.101.65 port: 5672 username: guest password: guest virtual-host: / publisher-confirm-type: correlated publisher-returns: false template: mandatory: false listener: simple: acknowledge-mode: none retry: enabled: true initial-interval: 1000ms multiplier: 1 max-attempts: 3 stateless: true
|
- 在订单服务接口工程引入rabbitmq-dev.yaml配置文件
1 2 3 4
| shared-configs: - data-id: rabbitmq-${spring.profiles.active}.yaml group: xuecheng-plus-common refresh: true
|
- 在订单服务service工程编写MQ配置类,配置交换机
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| @Slf4j @Configuration public class PayNotifyConfig implements ApplicationContextAware {
public static final String PAYNOTIFY_EXCHANGE_FANOUT = "paynotify_exchange_fanout"; public static final String MESSAGE_TYPE = "payresult_notify"; public static final String PAYNOTIFY_QUEUE = "paynotify_queue";
@Bean(PAYNOTIFY_EXCHANGE_FANOUT) public FanoutExchange paynotify_exchange_fanout() { return new FanoutExchange(PAYNOTIFY_EXCHANGE_FANOUT, true, false); } @Bean(PAYNOTIFY_QUEUE) public Queue course_publish_queue() { return QueueBuilder.durable(PAYNOTIFY_QUEUE).build(); }
@Bean public Binding binding_course_publish_queue(@Qualifier(PAYNOTIFY_QUEUE) Queue queue, @Qualifier(PAYNOTIFY_EXCHANGE_FANOUT) FanoutExchange exchange) { return BindingBuilder.bind(queue).to(exchange); }
@Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { RabbitTemplate rabbitTemplate = applicationContext.getBean(RabbitTemplate.class); MqMessageService mqMessageService = applicationContext.getBean(MqMessageService.class); rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> { log.info("消息发送失败,应答码{},原因{},交换机{},路由键{},消息{}", replyCode, replyText, exchange, routingKey, message.toString()); MqMessage mqMessage = JSON.parseObject(message.toString(), MqMessage.class); mqMessageService.addMessage(mqMessage.getMessageType(),mqMessage.getBusinessKey1(),mqMessage.getBusinessKey2(),mqMessage.getBusinessKey3());
}); } }
|
发送支付结果
在OrderService中定义接口:
1 2 3 4 5
|
public void notifyPayResult(MqMessage message);
|
编写接口实现方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| @Override public void notifyPayResult(MqMessage message) {
String jsonString = JSON.toJSONString(message);
Message messageObj = MessageBuilder.withBody(jsonString.getBytes(StandardCharsets.UTF_8)) .setDeliveryMode(MessageDeliveryMode.PERSISTENT).build(); Long id = message.getId(); CorrelationData correlationData = new CorrelationData(); correlationData.getFuture().addCallback( result -> { if(result.isAck()){ log.info("消息成功发送到交换机:{}",jsonString); mqMessageService.completed(id); }else{ log.info("消息发送到交换机失败:{}",jsonString); } }, ex -> { log.info("消息发送到交换机异常:{}",jsonString); } ); rabbitTemplate.convertAndSend(PayNotifyConfig.PAYNOTIFY_EXCHANGE_FANOUT, "", messageObj, correlationData); }
|
订单服务收到第三方平台的支付结果时,在saveAliPayStatus方法中添加代码,向数据库消息表添加消息并进行发送消息,如下所示:
1 2 3 4 5 6 7
| MqMessage payresultNotify = mqMessageService.addMessage("payresult_notify", xcOrders.getOutBusinessId(), xcOrders.getOrderType(), null);
notifyPayResult(payresultNotify);
|
接收支付结果
学习中心服务集成MQ
- 在学习中心服务添加消息队列依赖
1 2 3 4
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency>
|
- 在学习中心服务接口工程引入rabbitmq-dev.yaml配置文件
1 2 3 4
| shared-configs: - data-id: rabbitmq-${spring.profiles.active}.yaml group: xuecheng-plus-common refresh: true
|
- 添加配置类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| @Slf4j @Configuration public class PayNotifyConfig {
public static final String PAYNOTIFY_EXCHANGE_FANOUT = "paynotify_exchange_fanout"; public static final String MESSAGE_TYPE = "payresult_notify"; public static final String PAYNOTIFY_QUEUE = "paynotify_queue";
@Bean(PAYNOTIFY_EXCHANGE_FANOUT) public FanoutExchange paynotify_exchange_fanout() { return new FanoutExchange(PAYNOTIFY_EXCHANGE_FANOUT, true, false); } @Bean(PAYNOTIFY_QUEUE) public Queue course_publish_queue() { return QueueBuilder.durable(PAYNOTIFY_QUEUE).build(); }
@Bean public Binding binding_course_publish_queue(@Qualifier(PAYNOTIFY_QUEUE) Queue queue, @Qualifier(PAYNOTIFY_EXCHANGE_FANOUT) FanoutExchange exchange) { return BindingBuilder.bind(queue).to(exchange); }
}
|
接收支付结果
- 新建Service来接收支付结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| package com.xuecheng.learning.service.impl;
import com.alibaba.fastjson.JSON; import com.xuecheng.base.exception.XueChengPlusException; import com.xuecheng.learning.config.PayNotifyConfig; import com.xuecheng.learning.service.MyCourseTableService; import com.xuecheng.messagesdk.model.po.MqMessage; import lombok.extern.slf4j.Slf4j; import org.springframework.amqp.core.Message; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service;
@Service @Slf4j public class ReceivePayNotifyService {
@Autowired private MyCourseTableService myCourseTableService; @RabbitListener(queues = PayNotifyConfig.PAYNOTIFY_QUEUE) public void receivePayNotify(Message message) { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } log.info("接收到支付通知消息:{}", message); byte[] body = message.getBody(); String msg = new String(body); MqMessage mqMessage = JSON.parseObject(msg, MqMessage.class);
String chooseCourseId = mqMessage.getBusinessKey1();
String orderType = mqMessage.getBusinessKey2();
if("60201".equals(orderType)){ boolean b = myCourseTableService.saveChooseCourseSuccess(chooseCourseId); if(!b){ XueChengPlusException.cast("保存选课记录失败"); } } } }
|
- 在MyCourseTableService中添加
1 2 3 4 5 6
|
boolean saveChooseCourseSuccess(String courseId);
|
实现方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| @Override public boolean saveChooseCourseSuccess(String courseId) { XcChooseCourse xcChooseCourse = xcChooseCourseMapper.selectById(courseId); if ( xcChooseCourse == null ) { log.debug("接收到购买课程信息,但选课记录不存在,选课ID:{}", courseId); return false; } String status = xcChooseCourse.getStatus(); if ( status.equals("701002") ) { xcChooseCourse.setStatus("701001"); int insert = xcChooseCourseMapper.updateById(xcChooseCourse); if(insert <= 0){ log.debug("更新选课记录失败,选课ID:{}", courseId); XueChengPlusException.cast("更新选课记录失败"); } XcCourseTables xcCourseTables = addCourseTabls(xcChooseCourse); if(xcCourseTables == null){ log.debug("向我的课程表插入数据失败,选课ID:{}", courseId); XueChengPlusException.cast("向我的课程表插入数据失败"); } } return true; }
|
通知支付结果测试
通知支付结果测试
测试准备:
-
找一门已发布的收费课程。
-
如果在我的课程表存储则删除。
-
删除此课程的选课记录及订单信息。
测试流程:
-
进入课程详细页面,点击马上学习,生成二维码进行支付。
-
支付完成点击“支付完成”,观察订单服务控制台是否发送消息。
-
观察学习中心服务控制台是否接收到消息。
-
观察数据库中的消息表的相应记录是否已删除。
消费重试测试:
-
在学习中心服务接收支付结果方法中制造异常。
-
重新执行上边的测试流程,观察是否消费重试。