1. Day13 微信支付
2. 微信支付快速入门
2.1. 申请微信支付
申请微信支付需要提交相关资料审核,比较麻烦
我们使用课程提供的微信支付账号,用于demo开发
appid:微信公众账号或开放平台APP的唯一标识 wx8397f8696b538317
mch_id:商户号 1473426802
key:商户密钥 T6m9iK73b0kn9g5v426MKfHQH7X8rKwb
2.2. 入门案例
由于V3 SDK封装不是特别完善 此处使用的是V2进行开发
导入微信支付api依赖
<dependency>
<groupId>com.github.wxpay</groupId>
<artifactId>wxpay-sdk</artifactId>
<version>3.0.9</version>
</dependency>
创建com.github.wxpay.sdk包,包下创建MyConfig类 ,继承自抽象类WXPayConfig
public class MyConfig extends WXPayConfig {
String getAppID() {
//微信公众账号或开放平台APP的唯一标识
return "wx8397f8696b538317";
}
String getMchID() {
//商户号
return "1473426802";
}
String getKey() {
//商户密钥
return "T6m9iK73b0kn9g5v426MKfHQH7X8rKwb";
}
InputStream getCertStream() {
return null;
}
IWXPayDomain getWXPayDomain() {
return new IWXPayDomain() {
public void report(String domain, long elapsedTimeMillis, Exception ex) {
}
public DomainInfo getDomain(WXPayConfig config) {
//请求路径域名
return new DomainInfo("api.mch.weixin.qq.com",true);
}
};
}
}
创建测试类,编写代码
MyConfig config=new MyConfig();
WXPay wxPay=new WXPay( config );
Map<String,String> map=new HashMap();
map.put("body","畅购");//商品描述
map.put("out_trade_no","55555211");//订单号
map.put("total_fee","1");//金额 以分为单位
map.put("spbill_create_ip","127.0.0.1");//终端IP
map.put("notify_url","http://www.baidu.com");//回调地址
map.put("trade_type","NATIVE");//交易类型
Map<String, String> result = wxPay.unifiedOrder( map );
System.out.println(result);
返回结果为
{nonce_str=fvMGIlLauUPNCtws, code_url=weixin://wxpay/bizpayurl?pr=I5sd2rc, appid=wx8397f8696b538317, sign=48B2938F70EDADC9CC235249BC085FD1D83456F67C46601FFD23B5AFBDA502D0, trade_type=NATIVE, return_msg=OK, result_code=SUCCESS, mch_id=1473426802, return_code=SUCCESS, prepay_id=wx17193859685440d561c4cef01259098400}
其中的code_url就是我们的支付URl ,我们可以根据这个URl 生成支付二维码
2.3. 二维码JS插件- QRcode.js
QRCode.js 是一个用于生成二维码的 JavaScript 库。主要是通过获取 DOM 的标签,再通过 HTML5 Canvas 绘制而成
<script src="js/plugins/qrcode.min.js" ></script>
<script type="text/javascript">
let qrcode = new QRCode(document.getElementById("qrcode"), {
width : 200,
height : 200
});
qrcode.makeCode("weixin://wxpay/bizpayurl?pr=I5sd2rc");
</script>
3. 微信支付二维码
3.1. 提交订单跳转支付页
更新changgou_service_order中add() ,设置返回值为订单Id
OrderServiceImpl
/**
* 增加
*
* @param order
*/
@Override
@GlobalTransactional(name = "order_add")
public String add(Order order) {
//1.获取购物车的数据 从redis中获取
Map cartMap = cartService.list(order.getUsername()); //调用cartService的方法
List<OrderItem> orderItemList = (List<OrderItem>) cartMap.get("orderItemList");
//2.统计计算总金额 和总数量
//3.填充订单数据并保存到tb_order中
order.setTotalNum((Integer) cartMap.get("totalNum")); //总数量
order.setTotalMoney((Integer) cartMap.get("totalMoney")); //总金额
order.setPayMoney((Integer) cartMap.get("totalMoney")); //总支付金额
order.setCreateTime(new Date()); //创建时间
order.setUpdateTime(new Date()); //更新时间
order.setBuyerRate("0"); //是否评价 0未评价 1已评价
order.setSourceType("1"); //来源端 1 web端
order.setOrderStatus("0"); //订单状态 0 未完成 1已完成 2已退货
order.setPayStatus("0"); //支付状态 0未支付 1已支付
order.setConsignStatus("0"); //发货状态 0未发货 1已发货
String orderId = String.valueOf(idWorker.nextId());
order.setId(orderId); //订单id
orderMapper.insert(order);
//4.填充订单项数据并保存到tb_order_item
for (OrderItem orderItem : orderItemList) {
orderItem.setId(String.valueOf(idWorker.nextId()));
orderItem.setIsReturn("0"); //是否退货 0未退货 1已退货
orderItem.setOrderId(orderId); //该商品是依赖订单id
orderItemMapper.insertSelective(orderItem); //向tb_order_item表 插入orderItem
}
//扣减库存并增加销量
skuFeign.decrCount(order.getUsername());
// int i = 1/0;
//添加任务数据
System.out.println("向订单数据库中的任务表添加数据");
Task task = new Task();
task.setCreateTime(new Date());
task.setUpdateTime(new Date());
task.setMqExchange(RabbitMQConfig.EX_BUYING_ADDPOINTUSER);
task.setMqRoutingkey(RabbitMQConfig.CG_BUYING_ADDPOINT_KEY);
Map map = new HashMap();
map.put("username", order.getUsername());
map.put("orderId", orderId);
map.put("point", order.getPayMoney());
task.setRequestBody(JSON.toJSONString(map)); //转为json字符串
taskMapper.insertSelective(task); //写入库中
//5.删除购物车在redis中的数据
redisTemplate.delete("cart_" + order.getUsername());
return orderId;
}
OrderService 接口 修改add接口的返回值
/***
* 新增
* @param order
*/
String add(Order order);
OrderController 返回result携带orderid
/***
* 新增数据
* @param order
* @return
*/
@PostMapping
public Result add(@RequestBody Order order){
//获取登陆人名称
String username = tokenDecode.getUserInfo().get("username");
order.setUsername(username);
String orderId = orderService.add(order);
return new Result(true,StatusCode.OK,"添加成功",orderId);
}
修改changgou_web_order 静态页面order下的add方法
add: function () {
axios.post('/api/worder/add', this.order).then(function (res) {
if (res.data.flag) {
//添加订单成功
alert("添加订单成功")
let orderId = res.data.data //获取orderId
location.href = "/api/worder/toPayPage?orderId=" + orderId // 跳转到选择支付方式并携带orderid
} else {
alert("添加订单失败")
}
})
}
将 fail pay paysuccess wxpay 这个4个页面放到changgou_web_order的template文件夹下
在changgou_service_order_api的OrderFeign新增接口定义
/***
* 根据ID查询数据
* @param id
* @return
*/
@GetMapping("/order/{id}")
public Result<Order> findById(@PathVariable("id") String id);
changgou_order_web中的orderController新增方法,跳转支付页
@GetMapping("/toPayPage")
public String toPayPage (String orderId,Model model){
//获取订单信息
Order order= (Order) orderFeign.findById(orderId).getData();
model.addAttribute("orderId",order); //订单id
model.addAttribute("payMoney",order.getPayMoney()); //支付金额
return "pay";
}
3.2. 支付微服务-下单
创建changgou_service_pay (支付微服务), pom.xml添加依赖
需要注意的是maven中央仓库上的wxpay-sdk 3.0.9版本已经没有了
- 下载sdk打包成jar,再用maven安装到本地仓库中
- 使用red.htt的wxpay-sdk
<dependencies>
<dependency>
<groupId>com.changgou</groupId>
<artifactId>changgou_common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.github.wxpay</groupId>
<artifactId>wxpay-sdk</artifactId>
<version>3.0.9</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
</dependencies>
排除log包,否则会因为包冲突无法正常启动
application
server:
port: 9010
spring:
application:
name: pay
rabbitmq:
host: 192.168.200.128
main:
allow-bean-definition-overriding: true #当遇到同样名字的时候,是否允许覆盖注册
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:6868/eureka
instance:
prefer-ip-address: true
创建com.github.wxpay.sdk包,包下创建MyConfig类
public class MyConfig extends WXPayConfig {
String getAppID() {
return "wx8397f8696b538317";
}
String getMchID() {
return "1473426802";
}
String getKey() {
return "T6m9iK73b0kn9g5v426MKfHQH7X8rKwb";
}
InputStream getCertStream() {
return null;
}
//使用red.htt的wxpay-sdk不需要重写此方法
IWXPayDomain getWXPayDomain() {
return new IWXPayDomain() {
public void report(String domain, long elapsedTimeMillis, Exception ex) {
}
public DomainInfo getDomain(WXPayConfig config) {
return new DomainInfo("api.mch.weixin.qq.com",true);
}
};
}
}
创建com.changgou包,包下创建类PayApplication
@SpringBootApplication
@EnableEurekaClient
public class PayApplication {
public static void main(String[] args) {
SpringApplication.run(PayApplication.class,args);
}
@Bean
public WXPay wxPay(){
try {
return new WXPay(new MyConfig());
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
创建com.changgou.pay.service包,包下创建接口WxPayService
public interface WxPayService {
/**
* 生成支付二维码url
* @param orderId
* @param money
* @return
*/
Map nativePay(String orderId, Integer money);
}
创建com.changgou.pay.service.impl 包 ,新增服务类WxPayServiceImpl
@Service
public class WxPayServiceImpl implements WxPayService {
@Autowired
private WXPay wxPay;
//统一下单接口调用
@Override
public Map nativePay(String orderId, Integer money) {
try {
//封装请求参数
Map<String, String> map = new HashMap<>();
map.put("body", "畅购商城");//商品描述
map.put("out_trade_no", orderId);//订单号
//map.put("total_fee",String.valueOf(money*100));//金额,以分为单位
BigDecimal payMoney = new BigDecimal("0.01");
BigDecimal fen = payMoney.multiply(new BigDecimal("100")); //1.00
fen = fen.setScale(0, BigDecimal.ROUND_UP); // 1
map.put("total_fee", String.valueOf(fen));
map.put("spbill_create_ip", "127.0.0.1");//终端IP
map.put("notify_url", "http://www.itcast.cn");//回调地址,先随便填一个
map.put("trade_type", "NATIVE");//交易类型
//通过wxpay 调用下单接口 并获取返回结果
Map<String, String> result = wxPay.unifiedOrder(map);
return result;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
创建com.changgou.pay.controller包 ,新增WxPayController
@RestController
@RequestMapping("/wxpay")
public class WxPayController {
@Autowired
private WxPayService wxPayService;
//下单
@GetMapping("/nativePay")
public Result nativePay(@RequestParam("orderId") String orderId, @RequestParam("money") Integer money){
Map map = wxPayService.nativePay(orderId, money);
return new Result(true, StatusCode.OK,"",map);
}
}
测试:地址栏输入http://localhost:9010/wxpay/nativePay?orderId=990099&money=1
3.3. 页面对接支付微服务
新增changgou_service_pay_api模块 ,并添加common工程依赖
<dependencies>
<dependency>
<groupId>com.changgou</groupId>
<artifactId>changgou_common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
新增com.changgou.pay.feign包,包下创建接口 PayFeign
@FeignClient(name = "pay")
public interface PayFeign {
//下单
@GetMapping("/wxpay/nativePay")
public Result nativePay(@RequestParam("orderId") String orderId, @RequestParam("money") Integer money);
}
在changgou_web_order 添加 pay_api feign的依赖
<dependency>
<groupId>com.changgou</groupId>
<artifactId>changgou_service_pay_api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
在changgou_web_order项目的启动类 @EnableFeignClients注解添加pay_api包引用
@EnableFeignClients(basePackages = {"com.changgou.order.feign","com.changgou.user.feign","com.changgou.pay.feign"})
在changgou_web_order项目的controller 创建PayController类
@Controller
@RequestMapping("/wxpay")
public class PayController {
@Autowired
private OrderFeign orderFeign;
@Autowired
private PayFeign payFeign;
//跳转到微博支付二维码页面
@GetMapping
public String wxPay(String orderId, Model model) {
//根据order查询订单 订单不存在 则跳转到错误页面
Result orderResult = orderFeign.findById(orderId);
if (orderResult.getData() == null) {
return "fail";
}
//根据订单支付状态进行判断 如果不是未支付的 则跳转到错误页面
Order order = (Order) orderResult.getData();
if (!"0".equals(order.getPayStatus())) {
return "fail";
}
//调用payFeign下单接口 获取二维码地址
Result payResult = payFeign.nativePay(orderId, order.getPayMoney());
if (payResult.getData() == null) {
return "fail";
}
Map payMap = (Map) payResult.getData();
payMap.put("orderId", orderId);
payMap.put("payMoney", order.getPayMoney());
model.addAllAttributes(payMap); //将map拆分再传递给model
return "wxpay";
}
}
将静态原型中wxpay.html拷贝到changgou_web_order的templates文件夹下作为模板,修改模板,部分代码如下:二维码地址渲染
let qrcode = new QRCode(document.getElementById("qrcode"), {
width : 240,
height : 240
});
qrcode.makeCode([[${code_url}]]);
回显示订单号与金额
第51行
<h4 class="fl tit-txt"><span class="success-icon"></span><span class="success-info" th:text="|订单提交成功,请您及时付款!订单号:${orderId}|"></span></h4>
<span class="fr"><em class="sui-lead">应付金额:</em><em class="orange money" th:text="${payMoney}/100"></em>元</span>
pay.html 设置支付跳转链接
第109行
<li><a th:href="|/api/wxpay?orderId=${orderId}|"><img src="/img/_/pay3.jpg"></a></li>
更新网关地址过滤器。添加wxpay路径的拦截
changgou_gateway_web下URLFilter 添加 /api/wxpay,/api/wxpay/**
public static String filterPath = "/api/worder/**,/api/wseckillorder,/api/seckill,/api/wxpay,/api/wxpay/**,/api/worder/**,/api/user/**,/api/address/**,/api/wcart/**,/api/cart/**,/api/categoryReport/**,/api/orderConfig/**,/api/order/**,/api/orderItem/**,/api/orderLog/**,/api/preferential/**,/api/returnCause/**,/api/returnOrder/**,/api/returnOrderItem/**";
同时更新网关服务的application.yml。添加地址路由标识
4. 支付回调逻辑处理
在完成支付后,修改订单状态为已支付,并记录订单日志。
在请求统一下单接口时,有个参数notify_url ,这个是回调地址,也就是在支付成功后微信支付会自动访问这个地址,通知业务系统支付完成。但这个地址必须是互联网可以访问的(也就是有域名的)。
在WxPayController新增notifyLogic方法
/**
* 回调
*/
@RequestMapping("/notify")
public void notifyLogic(){
System.out.println("支付成功回调。。。。");
}
添加公网地址 到支付微服务配置文件
wxpay:
notify_url: http://weizhaohui.cross.echosite.cn/wxpay/notify #回调地址 必须为公网的自己域名接口
修改WxPayServiceImpl ,引入
@Value( "${wxpay.notify_url}" )
private String notifyUrl;
并修改WxPayServiceImpl 的nativePay方法
map.put("notify_url",notifyUrl);//回调地址
1、同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。
2、后台通知交互时,如果微信收到商户的应答不符合规范或超时,微信会判定本次通知失败,重新发送通知,直到成功为止(在通知一直不成功的情况下,微信总共会发起10次通知,通知频率为15s/15s/30s/3m/10m/20m/30m/30m/30m/60m/3h/3h/3h/6h/6h - 总计 24h4m),但微信不保证通知最终一定能成功。
4.1. 回调消息接收并转换
微信支付平台发送给回调地址的数据是二进制流,我们需要提取二进制流转换为字符串,这个字符串就是xml格式。
在changgou_common工程添加工具类ConvertUtils
/**
* 转换工具类
*/
public class ConvertUtils {
/**
* 输入流转换为xml字符串
* @param inputStream
* @return
*/
public static String convertToString(InputStream inputStream) throws IOException {
ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = 0;
while ((len = inputStream.read(buffer)) != -1) {
outSteam.write(buffer, 0, len);
}
outSteam.close();
inputStream.close();
String result = new String(outSteam.toByteArray(), "utf-8");
return result;
}
}
修改notifyLogic方法
/**
* 回调
*/
@RequestMapping("/notify")
public void notifyLogic(HttpServletRequest request, HttpServletResponse response) {
System.out.println("支付成功回调。。。。");
try {
//输入流转换为字符串
String xml = ConvertUtils.convertToString(request.getInputStream());
System.out.println(xml);
//返回微信一个结果通知
response.setContentType("text/xml");
String data = "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";
response.getWriter().write(data);
} catch (Exception e) {
e.printStackTrace();
}
}
测试后,在控制台看到输出的消息
<xml><appid><![CDATA[wx8397f8696b538317]]></appid>
<bank_type><![CDATA[CFT]]></bank_type>
<cash_fee><![CDATA[1]]></cash_fee>
<fee_type><![CDATA[CNY]]></fee_type>
<is_subscribe><![CDATA[N]]></is_subscribe>
<mch_id><![CDATA[1473426802]]></mch_id>
<nonce_str><![CDATA[c6bea293399a40e0a873df51e667f45a]]></nonce_str>
<openid><![CDATA[oNpSGwbtNBQROpN_dL8WUZG3wRkM]]></openid>
<out_trade_no><![CDATA[1553063775279]]></out_trade_no>
<result_code><![CDATA[SUCCESS]]></result_code>
<return_code><![CDATA[SUCCESS]]></return_code>
<sign><![CDATA[DD4E5DF5AF8D8D8061B0B8BF210127DE]]></sign>
<time_end><![CDATA[20190320143646]]></time_end>
<total_fee>1</total_fee>
<trade_type><![CDATA[NATIVE]]></trade_type>
<transaction_id><![CDATA[4200000248201903206581106357]]></transaction_id>
</xml>
4.2. 查询订单验证通知
WxPayService新增方法定义
//微信查询订单
Map queryOrder(String orderId);
WxPayServiceImpl实现方法
@Override
public Map queryOrder(String orderId) {
try {
Map<String,String> map = new HashMap();
map.put("out_trade_no",orderId); //根据订单id查询
Map<String, String> resultMap = wxPay.orderQuery(map);
return resultMap;
}catch (Exception e){
e.printStackTrace();
return null;
}
}
修改notifyLogic方法
生产者
在changgou_service_pay模块com.changgou.pay创建config包 并创建RabbitMQconfig类
@Configuration
public class RabbitMQConfig {
public static final String ORDER_PAY="order_pay";
@Bean
public Queue queue(){
return new Queue(ORDER_PAY);
}
}
使用微信提供的工具类将xml转为map 再发送到消息队列中
/**
* 回调
*/
@RequestMapping("/notify")
public void notifyLogic(HttpServletRequest request, HttpServletResponse response) {
System.out.println("支付成功回调。。。。");
try {
//输入流转换为字符串
String xml = ConvertUtils.convertToString(request.getInputStream());
System.out.println(xml);
//基于微信发送的通知内容,完成后续的业务
Map<String, String> result = WXPayUtil.xmlToMap(xml);//将xml转为Map
if (result.get("result_code").equals("SUCCESS")) {
//查询订单
Map map = wxPayService.queryOrder(result.get("out_trade_no"));
System.out.println(map);
if ("SUCCESS".equals(map.get("result_code"))) {
//查询成功 将订单的消息发送mq
Map message = new HashMap();
message.put("orderId", map.get("out_trade_no"));//订单号
message.put("transactionId", map.get("transaction_id")); //微信支付订单号
//发送到mq
rabbitTemplate.convertAndSend("", RabbitMQConfig.ORDER_PAY, JSON.toJSONString(message));
} else {
//错误原因
System.out.println(result.get("err_code_des"));
}
} else {
//错误原因
System.out.println(result.get("err_code_des"));
}
//返回微信一个结果通知
response.setContentType("text/xml");
String data = "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";
response.getWriter().write(data);
} catch (Exception e) {
e.printStackTrace();
}
}
4.3. 接收订单验证通知
修改changgou_service_order项目config包下的RabbitMQConfig添加微信回调消息队列
//微信完成消息队列
public static final String ORDER_PAY="order_pay";
@Bean
public Queue queue(){
return new Queue(ORDER_PAY);
}
在listener包下新建OrderPayListener类
用于消费消息队列中的数据
@Component
public class OrderPayListener {
@Autowired
private OrderService orderService;
@RabbitListener(queues = RabbitMQConfig.ORDER_PAY)
public void receiverPayMessage(String message){
System.out.println("接收到了订单支付的消息"+message);
Map map = JSON.parseObject(message, Map.class);
//调用业务层 完成订单数据库的修改
orderService.updatePayStatus((String)map.get("orderId"),(String)map.get("transactionId"));
}
}
Service层 OrderService类添加updatePayStatus 用于修改订单支付状态
//修改订单的支付状态 并记录日志
void updatePayStatus(String orderId, String transactionId);
Impl 实现接口方法
@Autowired
private OrderLogMapper orderLogMapper;
@Override
@Transactional
public void updatePayStatus(String orderId, String transactionId) {
//查询订单
Order order = orderMapper.selectByPrimaryKey(orderId);
if (order != null && "0".equals(order.getPayStatus())) {
//修改订单的支付状态
order.setPayStatus("1");
order.setOrderStatus("1");
order.setUpdateTime(new Date());
order.setPayTime(new Date());
order.setTransactionId(transactionId); //微信交易流水号
orderMapper.updateByPrimaryKeySelective(order);
//记录订单日志
OrderLog orderLog = new OrderLog();
orderLog.setId(idWorker.nextId()+"");
orderLog.setOperater("system");
orderLog.setOperateTime(new Date());
orderLog.setOrderStatus("1");
orderLog.setPayStatus("1");
orderLog.setRemarks("交易流水号"+transactionId);
orderLog.setOrderId(orderId);
orderLogMapper.insert(orderLog);
}
}
5. 推送支付通知
我们需要将支付的结果通知前端页面,其实就是我们通过所说的服务器端推送,主要有三种实现方案
(1)Ajax 短轮询 Ajax 轮询主要通过页面端的 JS 定时异步刷新任务来实现数据的加载
如果我们使用ajax短轮询方式,需要后端提供方法,通过调用微信支付接口实现根据订单号查询支付状态的方法(参见查询订单API) 。 前端每间隔三秒查询一次,如果后端返回支付成功则执行页面跳转。
缺点:这种方式实时效果较差,而且对服务端的压力也较大。不建议使用
(2)长轮询
长轮询主要也是通过 Ajax 机制,但区别于传统的 Ajax 应用,长轮询的服务器端会在没有数据时阻塞请求直到有新的数据产生或者请求超时才返回,之后客户端再重新建立连接获取数据。
如果使用长轮询,也同样需要后端提供方法,通过调用微信支付接口实现根据订单号查询支付状态的方法,只不过循环是写在后端的。
缺点:长轮询服务端会长时间地占用资源,如果消息频繁发送的话会给服务端带来较大的压力。不建议使用
(3)WebSocket 双向通信 WebSocket 是 HTML5 中一种新的通信协议,能够实现浏览器与服务器之间全双工通信。如果浏览器和服务端都支持 WebSocket 协议的话,该方式实现的消息推送无疑是最高效、简洁的。并且最新版本的 IE、Firefox、Chrome 等浏览器都已经支持 WebSocket 协议,Apache Tomcat 7.0.27 以后的版本也开始支持 WebSocket。
5.1. RabbitMQ Web STOMP 插件
借助于 RabbitMQ 的 Web STOMP 插件,实现浏览器与服务端的全双工通信。从本质上说,RabbitMQ 的 Web STOMP 插件也是利用 WebSocket 对 STOMP 协议进行了一次桥接,从而实现浏览器与服务端的双向通信。
5.1.1. STOMP协议
STOMP即Simple (or Streaming) Text Orientated Messaging Protocol,简单(流)文本定向消息协议。前身是TTMP协议(一个简单的基于文本的协议),专为消息中间件设计。它提供了一个可互操作的连接格式,允许STOMP客户端与任意STOMP消息代理(Broker)进行交互。STOMP协议由于设计简单,易于开发客户端,因此在多种语言和多种平台上得到广泛地应用。
5.1.2. 插件安装
我们进入rabbitmq容器,执行下面的命令开启stomp插件
docker exec -it tensquare_rabbitmq bash
rabbitmq-plugins enable rabbitmq_web_stomp rabbitmq_web_stomp_examples
exit
将当前的容器提交为新的镜像
docker commit tensquare_rabbitmq rabbitmq:stomp
docker stop tensquare_rabbitmq
根据新的镜像创建容器 此处只是暂时测试用 宿主机重启后此容器并不会自动启动
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
启动changgou_rabbitmq
docker start changgou_rabbitmq
访问http://192.168.130.128:15670/ 可以看到WEB-STOMP已经开启
5.1.3. 消息推送测试
前端代码
let client = Stomp.client('ws://192.168.130.128:15674/ws');
let on_connect = function(x) {
id = client.subscribe("/exchange/paynotify", function(d) {
alert(d.body) //消息队列中消息
});
};
let on_error = function() {
console.log('error');
};
client.connect('webguest', 'webguest', on_connect, on_error, '/');
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 为。
访问MQ管理页面创建 paynotify交换机
http://192.168.130.128:15672/#/exchanges
为了安全,我们在页面上不能用我们的rabbitmq的超级管理员用户guest,所以我们需要在rabbitmq中新建一个普通用户webguest(普通用户无法登录管理后台)
http://192.168.130.128:15672/#/users
http://192.168.130.128:15672/#/exchanges/%2F/paynotify
测试发送消息 查看网页端是否收到消息
6. 代码实现
实现思路:后端在收到回调通知后发送订单号给mq(paynotify交换器),前端通过stomp连接到mq订阅paynotify交换器的消息,判断接收的订单号是不是当前页面的订单号,如果是则进行页面的跳转。
在WxPayController类的notifyLogic方法添加双向通信编码(生产者)
//完成双向通信
rabbitTemplate.convertAndSend("paynotify","",result.get("out_trade_no"));
在PayController 添加支付成功的页面跳转方法
//支付成功页面的跳转
@RequestMapping("/toPaySuccess")
public String toPaySuccess(Integer payMoney,Model model){
model.addAttribute("payMoney",payMoney);
return "paysuccess";
}
修改wxpay页面的RabbitMQ Stomp
let client = Stomp.client('ws://192.168.130.128:15674/ws');
let on_connect = function(x) {
id = client.subscribe("/exchange/paynotify", function(d) {
//回调方法
let orderId = [[${orderId}]]
if (b.body == orderId){
//跳转页面
location.href="/api/wxpay/toPaySuccess?payMoney="+[[${payMoney}]]
}
// alert(d.body) //消息队列中消息
});
};
let on_error = function() {
console.log('error');
};
client.connect('webguest', 'webguest', on_connect, on_error, '/');