推送支付通知
需求分析
当用户完成扫码支付后,跳转到支付成功页面
服务端推送方案
我们需要将支付的结果通知前端页面,其实就是我们通过所说的服务器端推送,主要有三种实现方案
(1)Ajax 短轮询 Ajax 轮询主要通过页面端的 JS 定时异步刷新任务来实现数据的加载
如果我们使用ajax短轮询方式,需要后端提供方法,通过调用微信支付接口实现根据订单号查询支付状态的方法(参见查询订单API) 。 前端每间隔三秒查询一次,如果后端返回支付成功则执行页面跳转。
缺点:这种方式实时效果较差,而且对服务端的压力也较大。不建议使用
(2)长轮询
长轮询主要也是通过 Ajax 机制,但区别于传统的 Ajax 应用,长轮询的服务器端会在没有数据时阻塞请求直到有新的数据产生或者请求超时才返回,之后客户端再重新建立连接获取数据。
如果使用长轮询,也同样需要后端提供方法,通过调用微信支付接口实现根据订单号查询支付状态的方法,只不过循环是写在后端的。
缺点:长轮询服务端会长时间地占用资源,如果消息频繁发送的话会给服务端带来较大的压力。不建议使用
(3)WebSocket 双向通信 WebSocket 是 HTML5 中一种新的通信协议,能够实现浏览器与服务器之间全双工通信。如果浏览器和服务端都支持 WebSocket 协议的话,该方式实现的消息推送无疑是最高效、简洁的。并且最新版本的 IE、Firefox、Chrome 等浏览器都已经支持 WebSocket 协议,Apache Tomcat 7.0.27 以后的版本也开始支持 WebSocket。
RabbitMQ Web STOMP 插件
借助于 RabbitMQ 的 Web STOMP 插件,实现浏览器与服务端的全双工通信。从本质上说,RabbitMQ 的 Web STOMP 插件也是利用 WebSocket 对 STOMP 协议进行了一次桥接,从而实现浏览器与服务端的双向通信。
STOMP协议
STOMP即Simple (or Streaming) Text Orientated Messaging Protocol,简单(流)文本定向消息协议。前身是TTMP协议(一个简单的基于文本的协议),专为消息中间件设计。它提供了一个可互操作的连接格式,允许STOMP客户端与任意STOMP消息代理(Broker)进行交互。STOMP协议由于设计简单,易于开发客户端,因此在多种语言和多种平台上得到广泛地应用。
插件安装
我们进入rabbitmq容器,执行下面的命令开启stomp插件
rabbitmq-plugins enable rabbitmq_web_stomp rabbitmq_web_stomp_examples
将当前的容器提交为新的镜像
docker commit 3989ec68bf3c rabbitmq:stomp
停止当前的容器
docker stop 3989ec68bf3c
根据新的镜像创建容器
docker run -di --name=changgou_rabbitmq -p 5671:5617 -p 5672:5672 -p 4369:4369 -p 15671:15671 -p 15672:15672 -p 25672:25672 -p 15670:15670 -p 15674:15674 rabbitmq:stomp
消息推送测试
我们在浏览器访问http://192.168.200.128:15670 可以看到stomp的例子代码 。
我们根据stomp的例子代码创建一个页面,内容如下:
<html> <head> <title>RabbitMQ Web STOMP Examples : Echo Server</title> <meta charset="UTF-8"> <script src="js/stomp.min.js"></script> </head> <script> var client = Stomp.client('ws://localhost:15674/ws'); var on_connect = function(x) { id = client.subscribe("/exchange/paynotify", function(d) { alert(d.body); }); }; var on_error = function() { console.log('error'); }; client.connect('guest', 'guest', on_connect, on_error, '/'); </script> </body> </html>
destination 在 RabbitMQ Web STOM 中进行了相关的定义,根据使用场景的不同,主要有以下 4 种:
- 1./exchange/
对于 SUBCRIBE frame,destination 一般为/exchange//[/pattern] 的形式。该 destination 会创建一个唯一的、自动删除的、名为的 queue,并根据 pattern 将该 queue 绑定到所给的 exchange,实现对该队列的消息订阅。 对于 SEND frame,destination 一般为/exchange//[/routingKey] 的形式。这种情况下消息就会被发送到定义的 exchange 中,并且指定了 routingKey。
- 2./queue/ 对于 SUBCRIBE frame,destination 会定义的共享 queue,并且实现对该队列的消息订阅。 对于 SEND frame,destination 只会在第一次发送消息的时候会定义的共享 queue。该消息会被发送到默认的 exchange 中,routingKey 即为。
- 3./amq/queue/ 这种情况下无论是 SUBCRIBE frame 还是 SEND frame 都不会产生 queue。但如果该 queue 不存在,SUBCRIBE frame 会报错。 对于 SUBCRIBE frame,destination 会实现对队列的消息订阅。 对于 SEND frame,消息会通过默认的 exhcange 直接被发送到队列中。
- 4./topic/ 对于 SUBCRIBE frame,destination 创建出自动删除的、非持久的 queue 并根据 routingkey 为绑定到 amq.topic exchange 上,同时实现对该 queue 的订阅。 对于 SEND frame,消息会被发送到 amq.topic exchange 中,routingKey 为。
我们在rabbitmq中创建一个叫paynotify的交换机(fanout类型)
测试,我们在rabbitmq管理界面中向paynotify交换机发送消息,页面就会接收这个消息。
为了安全,我们在页面上不能用我们的rabbitmq的超级管理员用户guest,所以我们需要在rabbitmq中新建一个普通用户webguest(普通用户无法登录管理后台)
设置虚拟目录权限
代码实现
实现思路:后端在收到回调通知后发送订单号给mq(paynotify交换器),前端通过stomp连接到mq订阅paynotify交换器的消息,判断接收的订单号是不是当前页面的订单号,如果是则进行页面的跳转。
(1)修改notifyLogic方法,在"SUCCESS".equals(map.get("result_code"))
后添加
rabbitTemplate.convertAndSend("paynotify","",map.get("out_trade_no"));
(2)修改wxpay.html ,渲染js代码订单号和支付金额部分
let client = Stomp.client('ws://192.168.200.128:15674/ws'); let on_connect = function(x) { id = client.subscribe("/exchange/paynotify", function(d) { let orderId=[[${orderId}]]; if(d.body==orderId){ location.href='/api/wxpay/topaysuccess?payMoney='+[[${payMoney}]]; //参数支付金额 } }); }; let on_error = function() { console.log('error'); }; client.connect('webguest', 'itcast', on_connect, on_error, '/');
(3)将paysuccess.html拷贝到static文件夹 。
(4)changgou_web_order的PayController中新增跳转支付成功接口
@GetMapping("/topaysuccess") public String topaysuccess(String payMoney,Model model){ model.addAttribute("payMoney",payMoney); return "paysuccess"; }