微信支付

整个流程使用到的组件代码:
链接:https://pan.baidu.com/s/1v5415tEtetxdsp4o7HMy5A
提取码:ys87

二维码创建

利用qrious制作二维码插件。

qrious是一款基于HTML5 Canvas的纯JS二维码生成插件。通过qrious.js可以快速生成各种二维码,你可以控制二维码的尺寸颜色,还可以将生成的二维码进行Base64编码。

qrious.js二维码插件的可用配置参数如下:

参数 类型 默认值 描述
background String “white” 二维码的背景颜色。
foreground String “black” 二维码的前景颜色。
level String “L” 二维码的误差校正级别(L, M, Q, H)。
mime String “image/png” 二维码输出为图片时的MIME类型。
size Number 100 二维码的尺寸,单位像素。
value String “” 需要编码为二维码的值,一般是跳转url

下面的代码即可生成一张二维码

<html>
<head>
<title>二维码入门小demo</title>
</head>
<body>
<img id="qrious">
<script src="qrious.js"></script>
<script>var qr = new QRious({element:document.getElementById('qrious'),size:250,      level:'H',    value:'http://www.itheima.com'});
</script>
</body>
</html>

运行效果:

微信扫码支付

微信扫码支付申请

微信扫码支付是商户系统按微信支付协议生成支付二维码,用户再用微信“扫一扫”完成支付的模式。该模式适用于PC网站支付、实体店单品或订单支付、媒体广告支付等场景。

申请步骤:

第一步:注册公众号(类型须为:服务号)

请根据营业执照类型选择以下主体注册:个体工商户| 企业/公司| 政府| 媒体| 其他类型。

需要企业,才可微信支付。

第二步:认证公众号

公众号认证后才可申请微信支付,认证费:300元/次。

第三步:提交资料申请微信支付

登录公众平台,点击左侧菜单【微信支付】,开始填写资料等待审核,审核时间为1-5个工作日内。

第四步:开户成功,登录商户平台进行验证

资料审核通过后,请登录联系人邮箱查收商户号和密码,并登录商户平台填写财付通备付金打的小额资金数额,完成账户验证。

第五步:在线签署协议

本协议为线上电子协议,签署后方可进行交易及资金结算,签署完立即生效。

有“传智播客”的微信支付账号,学员无需申请。

开发文档

微信支付接口调用的整体思路:

按API要求组装参数,以XML方式发送POST)给微信支付接口(URL),微信支付接口也是以XML方式给予响应。程序根据返回的结果(其中包括支付URL)生成二维码或判断订单状态。

在线微信支付开发文档:

https://pay.weixin.qq.com/wiki/doc/api/index.html

Native二维码扫码支付。

”统一下单”和”查询订单”两组API

1. appid:微信公众账号或开放平台APP的唯一标识
2. mch_id:商户号  (配置文件中的partner)
3. partnerkey:商户密钥
4. sign:数字签名, 根据微信官方提供的密钥和一套算法生成的一个加密信息, 就是为了保证交易的安全性

微信扫码支付模式介绍

模式一

商家二维码不过时。

业务流程说明:

1.商户后台系统根据微信支付规定格式生成二维码(规则见下文),展示给用户扫码。
2.用户打开微信“扫一扫”扫描二维码,微信客户端将扫码内容发送到微信支付系统。
3.微信支付系统收到客户端请求,发起对商户后台系统支付回调URL的调用。调用请求将带productid和用户的openid等参数,并要求商户系统返回交数据包,详细请见"本节3.1回调数据输入参数"
4.商户后台系统收到微信支付系统的回调请求,根据productid生成商户系统的订单。
5.商户系统调用微信支付【统一下单API】请求下单,获取交易会话标识(prepay_id)
6.微信支付系统根据商户系统的请求生成预支付交易,并返回交易会话标识(prepay_id)。
7.商户后台系统得到交易会话标识prepay_id(2小时内有效)。
8.商户后台系统将prepay_id返回给微信支付系统。返回数据见"本节3.2回调数据输出参数"
9.微信支付系统根据交易会话标识,发起用户端授权支付流程。
10.用户在微信客户端输入密码,确认支付后,微信客户端提交支付授权。
11.微信支付系统验证后扣款,完成支付交易。
12.微信支付系统完成支付交易后给微信客户端返回交易结果,并将交易结果通过短信、微信消息提示用户。微信客户端展示支付交易结果页面。
13.微信支付系统通过发送异步消息通知商户后台系统支付结果。商户后台系统需回复接收情况,通知微信后台系统不再发送该单的支付通知。
14.未收到支付通知的情况,商户后台系统调用【查询订单API】。
15.商户确认订单已支付后给用户发货。

模式二

商家二维码会过时。

业务流程说明:

1.商户后台系统根据用户选购的商品生成订单。
2.用户确认支付后调用微信支付【统一下单API】生成预支付交易;
3.微信支付系统收到请求后生成预支付交易单,并返回交易会话的二维码链接code_url。
4.商户后台系统根据返回的code_url生成二维码。
5.用户打开微信“扫一扫”扫描二维码,微信客户端将扫码内容发送到微信支付系统。
6.微信支付系统收到客户端请求,验证链接有效性后发起用户支付,要求用户授权。
7.用户在微信客户端输入密码,确认支付后,微信客户端提交授权。
8.微信支付系统根据用户授权完成支付交易。
9.微信支付系统完成支付交易后给微信客户端返回交易结果,并将交易结果通过短信、微信消息提示用户。微信客户端展示支付交易结果页面。
10.微信支付系统通过发送异步消息通知商户后台系统支付结果。商户后台系统需回复接收情况,通知微信后台系统不再发送该单的支付通知。
11.未收到支付通知的情况,商户后台系统调用【查询订单API】。
12.商户确认订单已支付后给用户发货。

微信支付SDK

微信支付提供了SDK, 下载后打开源码,install到本地仓库。

安装SDK,jar包。

使用微信支付SDK(开发工具包), 在maven工程中引入依赖

<!-- 微信支付 -->
<wxpay.version>0.0.3</wxpay.version>
<!-- 微信支付 -->
<dependency><groupId>com.github.wxpay</groupId><artifactId>wxpay-sdk</artifactId><version>${wxpay.version}</version>
</dependency>

我们主要会用到微信支付SDK的以下功能:

    @Testpublic void test() throws Exception {//生成随机字符串String s = WXPayUtil.generateNonceStr();System.out.println("随机字符串:"+s);//将map转成xml字符串Map<String, String> param = new HashMap<>();param.put("id","1");param.put("title","吴泽强");String mapToXml = WXPayUtil.mapToXml(param);System.out.println("map转成xml字符串:\n"+mapToXml);//将map转成字符串,并且生成签名String partnerKey = "wzq"; //私钥String generateSignedXml = WXPayUtil.generateSignedXml(param, partnerKey);System.out.println("xml字符串带有签名:\n"+generateSignedXml);//将XML字符串转成mapMap<String,String> mapResult = WXPayUtil.xmlToMap(mapToXml);System.out.println("xml字符串转成map"+mapResult);}

项目实战

支付可单独是一个微服务。

依赖:

<!-- 微信支付 -->
<wxpay.version>0.0.3</wxpay.version>
<!-- 微信支付 -->
<dependency><groupId>com.github.wxpay</groupId><artifactId>wxpay-sdk</artifactId><version>${wxpay.version}</version>
</dependency>

商户到腾讯官网申请才有的配置application.yml:

#微信支付信息配置,这里是黑马提供的
weixin:#应用idappid: wx8397f8696b538317#商户idpartner: 1473426802#私钥partnerkey: T6m9iK73b0kn9g5v426MKfHQH7X8rKwb#支付回调地址,自己本机模拟,花生壳notifyurl: http://c3009841m5.zicp.vip:动态端口/weixin/pay/notify/url

appid: 微信公众账号或开放平台APP的唯一标识

partner:财付通平台的商户账号

partnerkey:财付通平台的商户密钥

notifyurl: 回调地址

默认二维码2小时失效,具体官网文档有说。

统一下单api

在支付页面上生成支付二维码,并显示订单号和金额

用户拿出手机,打开微信扫描页面上的二维码,然后在微信中完成支付

官网文档:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_1

二维码只能支付成功一次,成功后则失效,默认2小时未支付,二维码无效,具体修改无效时长看文档。结合HttpClient工具类实现。我们传给微信服务器和微信服务器传过来都是xml格式的,有工具类可以转。

dto:

参数dto和返回dto,看情况使用。

/*** Title:* Description:创建二维码需要的参数* @author WZQ* @version 1.0.0* @date 2020/3/13*/
public class NativeParamDto implements Serializable {private static final long serialVersionUID = -2376427360982551378L;//客户端自定义订单编号private String outTradeNo;//交易金额(单位:分)private String totalFee;//set.get..
}/*** Title:* Description:创建二维码成功的结果* @author WZQ* @version 1.0.0* @date 2020/3/13*/
public class NativeDto implements Serializable {private static final long serialVersionUID = -2376427360982551378L;//客户端自定义订单编号private String outTradeNo;//交易金额(单位:分)private String totalFee;//二维码地址private String codeUrl//set.get..
}

servie:

    /*** 创建二维码* @param paramMap 客户端自定义订单编号;交易金额,单位:分* @return*/Map<String, String> createNative(Map<String, String> paramMap);
import com.changgou.commons.utils.HttpClient;
import com.changgou.service.pay.service.WeixinPayService;
import com.github.wxpay.sdk.WXPayUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;import java.util.HashMap;
import java.util.Map;@Service
public class WeixinPayServiceImpl implements WeixinPayService {@Value("${weixin.appid}")private String appid;@Value("${weixin.partner}")private String partner;@Value("${weixin.partnerkey}")private String partnerkey;@Value("${weixin.notifyurl}")private String notifyurl;/*** 创建二维码* @param parameterMap 客户端自定义订单编号;交易金额(单位:分)* @return*/@Overridepublic Map<String,String> createNative(Map<String, String> parameterMap){//客户端自定义订单编号;交易金额(单位:分)String outTradeNo = parameterMap.get("no");String totalFee = parameterMap.get("money");try {//1、封装参数Map<String,String> paramMap = new HashMap<>();paramMap.put("appid", appid);                              //应用IDparamMap.put("mch_id", partner);                           //商户ID号paramMap.put("nonce_str", WXPayUtil.generateNonceStr());   //随机数paramMap.put("body", "畅购");                                //订单描述paramMap.put("out_trade_no", outTradeNo);                   //商户订单号paramMap.put("total_fee", totalFee);                       //交易金额,单位分paramMap.put("spbill_create_ip", "127.0.0.1");            //终端IP,请求的地址paramMap.put("notify_url", notifyurl);                    //回调地址paramMap.put("trade_type", "NATIVE");                     //交易类型,扫码//2、将参数转成xml字符,并携带签名String paramXml = WXPayUtil.generateSignedXml(paramMap, partnerkey);///3、执行请求,微信统一下单apiHttpClient httpClient = new HttpClient("https://api.mch.weixin.qq.com/pay/unifiedorder");httpClient.setHttps(true);httpClient.setXmlParam(paramXml);httpClient.post(); //执行//4、获取参数String content = httpClient.getContent();Map<String, String> stringMap = WXPayUtil.xmlToMap(content);System.out.println("stringMap:"+stringMap);//5、获取部分页面所需参数Map<String,String> dataMap = new HashMap<String,String>();dataMap.put("code_url", stringMap.get("code_url"));dataMap.put("out_trade_no", outTradeNo);dataMap.put("total_fee", totalFee);return dataMap;} catch (Exception e) {e.printStackTrace();}return null;}
}

controller:

@RestController
@RequestMapping(value = "/weixin/pay")
@CrossOrigin
public class WeixinPayController {@Autowiredprivate WeixinPayService weixinPayService;/**** 创建二维码* 参数是订单号和金额,分为单位* @return*/@RequestMapping(value = "/create/native")public ResponseResult<Map<String,String>> createNative(@RequestParam Map<String, String> paramMap){Map<String,String> resultMap = weixinPayService.createNative(paramMap);return new ResponseResult<Map<String,String>>(true, StatusCode.OK,"创建二维码预付订单成功!",resultMap);}
}

测试 http://localhost:18092/weixin/pay/create/native?no=1714080902133&money=1:

这里是表单传参的,json的话也可以,自定义好dto即可。

把code_url的地址显示到qrious组件的前端页面,存放到value对应的值即是一个二维码

支付信息回调

接口分析

每次实现支付之后,微信支付都会将用户支付结果返回到指定路径,而指定路径是指创建二维码的时候填写的notifyurl参数,响应的数据以及相关文档参考一下地址:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_7&index=8

返回参数分析

通知参数如下:

字段名 变量名 必填 类型 示例值 描述
返回状态码 return_code String(16) SUCCESS SUCCESS
返回信息 return_msg String(128) OK OK

以下字段在return_code为SUCCESS的时候有返回

字段名 变量名 必填 类型 示例值 描述
公众账号ID appid String(32) wx8888888888888888 微信分配的公众账号ID(企业号corpid即为此appId)
业务结果 result_code String(16) SUCCESS SUCCESS/FAIL
商户订单号 out_trade_no String(32) 1212321211201407033568112322 商户系统内部订单号
微信支付订单号 transaction_id String(32) 1217752501201407033233368018 微信支付订单号

响应分析

回调地址接收到数据后,需要响应信息给微信服务器,告知已经收到数据,不然微信服务器会再次发送4次请求推送支付信息。

字段名 变量名 必填 类型 示例值 描述
返回状态码 return_code String(16) SUCCESS 请按示例值填写
返回信息 return_msg String(128) OK 请按示例值填写

举例如下:

<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg>
</xml>

回调接收数据实现

WeixinPayController, 添加回调方法,代码如下:

/**** 支付回调,数据流数据* @param request* @return*/
@RequestMapping(value = "/notify/url")
public String notifyUrl(HttpServletRequest request){InputStream inStream;try {//读取支付回调数据inStream = request.getInputStream();ByteArrayOutputStream outSteam = new ByteArrayOutputStream();byte[] buffer = new byte[1024]; //缓存区int len = 0;while ((len = inStream.read(buffer)) != -1) {outSteam.write(buffer, 0, len);}outSteam.close();inStream.close();// 将支付回调数据转换成xml字符串String result = new String(outSteam.toByteArray(), "utf-8");//将xml字符串转换成Map结构Map<String, String> map = WXPayUtil.xmlToMap(result);//响应数据设置Map<String,String> respMap = new HashMap<>();respMap.put("return_code","SUCCESS");respMap.put("return_msg","OK");return WXPayUtil.mapToXml(respMap);} catch (Exception e) {e.printStackTrace();//记录错误日志}return null;
}

本地机可以使用花生壳来模拟外网访问我们的本地机服务。花生壳服务器通过该软件和账号把一个域名和本地机的ip绑定在一起,实现内网穿透。通过随机域名可外网访问本地机。

外网访问:http://c3009841m5.zicp.vip:动态端口/weixin/pay/notify/url

查询订单api

官网文档:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_2

通过HttpClient工具类实现对远程支付接口的调用。具体参数参见“查询订单”API, 我们在controller方法中轮询调用查询订单(间隔3秒),当返回状态为SUCCESS时,我们会在controller方法返回结果。前端代码收到结果后跳转到成功页面。默认统一下单api是有回调地址notifyurl,我们自己设定的地址,一般微信服务器支付成功后,会回调该地址过来,默认5次,该回调带有订单状态。如果出事故,则需要我们自己用查询订单api来查询订单的状态。

service:

    /**** 查询订单状态* @param outTradeNo : 客户端自定义订单编号* @return*/Map<String, String> queryPayStatus(String outTradeNo);/**** 查询订单状态* @param outTradeNo : 客户端自定义订单编号* @return*/@Overridepublic Map<String,String> queryPayStatus(String outTradeNo) {try {//1.封装参数Map<String,String> param = new HashMap<>();param.put("appid",appid);                            //应用IDparam.put("mch_id",partner);                         //商户号param.put("out_trade_no",outTradeNo);              //商户订单编号param.put("nonce_str",WXPayUtil.generateNonceStr()); //随机字符//2、将参数转成xml字符,并携带签名String paramXml = WXPayUtil.generateSignedXml(param,partnerkey);//3、发送请求HttpClient httpClient = new HttpClient("https://api.mch.weixin.qq.com/pay/orderquery");httpClient.setHttps(true);httpClient.setXmlParam(paramXml);httpClient.post();//4、获取返回值,并将返回值转成MapString content = httpClient.getContent();return WXPayUtil.xmlToMap(content);} catch (Exception e) {e.printStackTrace();}return null;}

controller:

    /**** 查询支付状态* @param outTradeNo 自定义订单id,不是微信的id* @return*/@GetMapping(value = "/status/query")public ResponseResult<Map<String,String>> queryStatus(String outTradeNo){Map<String,String> resultMap = weixinPayService.queryPayStatus(outTradeNo);return new ResponseResult<Map<String,String>>(true,StatusCode.OK,"查询状态成功!",resultMap);}

测试:

关闭订单api

官网文档:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_3

用户如果一直未支付,或者取消订单,我们除了要对自定义数据库取消支付状态回滚库存外,还需要向微信服务器请求关闭订单api,让二维码失效(默认2小时失效)。

关闭支付订单,但在关闭之前,需要先关闭微信支付,防止中途用户支付。

修改支付微服务的WeixinPayService,添加关闭支付方法,代码如下:

/**** 关闭支付* @param orderId* @return*/
Map<String,String> closePay(Long orderId) throws Exception;

修改WeixinPayServiceImpl,实现关闭微信支付方法,代码如下:

/**** 关闭微信支付* @param orderId* @return* @throws Exception*/
@Override
public Map<String, String> closePay(Long orderId) throws Exception {//参数设置Map<String,String> paramMap = new HashMap<String,String>();paramMap.put("appid",appid); //应用IDparamMap.put("mch_id",partner);    //商户编号paramMap.put("nonce_str",WXPayUtil.generateNonceStr());//随机字符paramMap.put("out_trade_no",String.valueOf(orderId));   //商家的唯一编号//将Map数据转成XML字符String xmlParam = WXPayUtil.generateSignedXml(paramMap,partnerkey);//确定urlString url = "https://api.mch.weixin.qq.com/pay/closeorder";//发送请求HttpClient httpClient = new HttpClient(url);//httpshttpClient.setHttps(true);//提交参数httpClient.setXmlParam(xmlParam);//提交httpClient.post();//获取返回数据String content = httpClient.getContent();//将返回数据解析成Mapreturn  WXPayUtil.xmlToMap(content);
}

controller:

    /**** 关闭订单状态* @param outTradeNo 自定义订单id,不是微信订单的id* @return*/@GetMapping(value = "/status/close")public ResponseResult<Map<String,String>> closePay(String outTradeNo){Map<String,String> resultMap = weixinPayService.closePay(outTradeNo);return new ResponseResult<Map<String,String>>(true,StatusCode.OK,"关闭订单成功!",resultMap);}

MQ处理支付回调状态

支付系统是独立于其他系统的服务,不做相关业务逻辑操作,只做支付处理,所以回调地址接收微信服务返回的支付状态后,立即将消息发送给RabbitMQ,订单系统再监听支付状态数据,根据状态数据做出修改订单状态或者删除订单操作。

发送MQ消息

支付微服务回调后发送消息

支付微服务集成RabbitMQ,添加依赖:

<!--加入ampq-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

在后台手动创建队列,并绑定队列(进入rabbitmq主页,手动创建)。如果使用程序创建队列,可以按照如下方式实现。

修改application.yml,配置支付队列和交换机信息,代码如下:

spring:rabbitmq:host: 192.168.169.140 #ip地址port: 5672 #端口username: guest #账号password: guest #密码#设置order交换机和order队列名称
mq:pay:exchange:order: exchange.orderqueue:order: queue.orderrouting:orderkey: queue.order

创建队列以及交换机并让队列和交换机绑定,添加到MQConfig,添加如下代码:

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;import javax.annotation.Resource;
import java.util.Objects;/*** Title:rabbitmq配置* Description:创建队列和交换机* @author WZQ* @version 1.0.0* @date 2020/3/13*/
@Configuration
public class MQConfig {//读取配置文件的内容@Resourceprivate Environment env;/**** 创建DirectExchange交换机* @return*/@Beanpublic DirectExchange basicExchange(){//true:是否实体化,false:是否自动删除return new DirectExchange(env.getProperty("mq.pay.exchange.order"), true,false);}/**** 创建队列* @return*/@Bean(name = "queueOrder")public Queue queueOrder(){return new Queue(Objects.requireNonNull(env.getProperty("mq.pay.queue.order")), true);}/***** 队列绑定到交换机上* @return*/@Beanpublic Binding basicBinding(){return BindingBuilder.bind(queueOrder()).to(basicExchange()).with(env.getProperty("mq.pay.routing.orderkey"));}
}

使用:

@Value("${mq.pay.exchange.order}")
private String exchange;
@Value("${mq.pay.queue.order}")
private String queue;
@Value("${mq.pay.routing.key}")
private String routing;@Autowired
private RabbitTemplate rabbitTemplate;/**** 支付回调* @param request* @return*/
@RequestMapping(value = "/notify/url")
public String notifyUrl(HttpServletRequest request){InputStream inStream;try {//读取支付回调数据inStream = request.getInputStream();ByteArrayOutputStream outSteam = new ByteArrayOutputStream();byte[] buffer = new byte[1024];int len = 0;while ((len = inStream.read(buffer)) != -1) {outSteam.write(buffer, 0, len);}outSteam.close();inStream.close();// 将支付回调数据转换成xml字符串String result = new String(outSteam.toByteArray(), "utf-8");//将xml字符串转换成Map结构Map<String, String> map = WXPayUtil.xmlToMap(result);//将消息发送给RabbitMQrabbitTemplate.convertAndSend(exchange,routing, JSON.toJSONString(map));//响应数据设置Map respMap = new HashMap();respMap.put("return_code","SUCCESS");respMap.put("return_msg","OK");return WXPayUtil.mapToXml(respMap);} catch (Exception e) {e.printStackTrace();//记录错误日志}return null;
}

监听MQ消息

处理订单状态。在订单微服务中,我们需要监听MQ支付状态消息,并实现订单数据操作。

订单微服务集成RabbitMQ,再监听队列消息。

在pom.xml中引入如下依赖:

<!--加入ampq-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

在application.yml中加入配置,代码如下:

spring:rabbitmq:host: 192.168.169.140 #ip地址port: 5672 #端口username: guest #账号password: guest #密码#设置队列名称,跟发送消息的队列名字一致
mq:pay:queue:order: queue.order

监听消息修改订单

在订单微服务于中创建consumer.OrderPayMessageListener,在该类中consumeMessage方法,用于监听消息,并根据支付状态处理订单,代码如下:

@Component
@RabbitListener(queues = {"${mq.pay.queue.order}"})
public class OrderPayMessageListener {@Resourceprivate OrderService orderService;/**** 接收消息*/@RabbitHandlerpublic void consumeMessage(String msg){//将数据转成MapMap<String,String> result = JSON.parseObject(msg,Map.class);//return_code=SUCCESSString return_code = result.get("return_code");//业务结果String result_code = result.get("result_code");//状态码 return_code=SUCCESS/FAIL,修改订单状态if(return_code.equalsIgnoreCase("success") ){//获取订单号String outTradeNo = result.get("out_trade_no");//业务结果,支付成功的if(result_code.equalsIgnoreCase("success")){if(outTradeNo!=null){//修改订单状态根据out_trade_no,具体逻辑业务,看情况//transaction_id微信支付的订单号,time_end是结算时间orderService.updateStatus(outTradeNo, result.get("time_end"),result.get("transaction_id"));}}else{//需要调用微信服务器关闭订单//订单删除,具体逻辑业务,看情况orderService.deleteOrder(outTradeNo);}}}
}

其它微信api接口,详看:https://pay.weixin.qq.com/wiki/doc/api/native.php

RabbitMQ延时消息队列

延时队列介绍

延时队列即放置在该队列里面的消息是不需要立即消费的,而是等待一段时间之后取出消费。
那么,为什么需要延迟消费呢?我们来看以下的场景:

网上商城下订单后30分钟后没有完成支付,取消订单(如:淘宝、去哪儿网);
系统创建了预约之后,需要在预约时间到达前一小时提醒被预约的双方参会;
系统中的业务失败之后,需要重试;
这些场景都非常常见,我们可以思考,比如第二个需求,系统创建了预约之后,需要在预约时间到达前一小时提醒被预约的双方参会。那么一天之中肯定是会有很多个预约的,时间也是不一定的,假设现在有1点 2点 3点 三个预约,如何让系统知道在当前时间等于0点 1点 2点给用户发送信息呢,是不是需要一个轮询,一直去查看所有的预约,比对当前的系统时间和预约提前一小时的时间是否相等呢?这样做非常浪费资源而且轮询的时间间隔不好控制。如果我们使用延时消息队列呢,我们在创建时把需要通知的预约放入消息中间件中,并且设置该消息的过期时间,等过期时间到达时再取出消费即可。

Rabbitmq实现延时队列一般而言有两种形式:
第一种方式:利用两个特性: Time To Live(TTL)、Dead Letter Exchanges(DLX)[A队列过期->转发给B队列]

第二种方式:利用rabbitmq中的插件x-delay-message

TTL DLX实现延时队列

TTL DLX介绍

TTL
RabbitMQ可以针对队列设置x-expires(则队列中所有的消息都有相同的过期时间)或者针对Message设置x-message-ttl(对消息进行单独设置,每条消息TTL可以不同),来控制消息的生存时间,如果超时(两者同时设置以最先到期的时间为准),则消息变为dead letter(死信)

Dead Letter Exchanges(DLX)
RabbitMQ的Queue可以配置x-dead-letter-exchange和x-dead-letter-routing-key(可选)两个参数,如果队列内出现了dead letter,则按照这两个参数重新路由转发到指定的队列。
x-dead-letter-exchange:出现dead letter之后将dead letter重新发送到指定exchange

x-dead-letter-routing-key:出现dead letter之后将dead letter重新按照指定的routing-key发送

DLX延时队列实现

还是在订单微服务中,当用户生成订单支付状态后,如果半小时未支付,则取消支付,向微信服务器请求关闭订单api。

配置队列
#设置交换机和队列名称
mq:pay:exchange:order: exchange.ordercancelOrder: dlx.exchangequeue:order: queue.ordercancelOrder: queue.listenercancelOrderDelay: queue.delayrouting:orderkey: queue.ordercancelOrderkey: queue.listener
队列创建

创建2个队列,用于接收消息的叫延时队列DelayQueue,用于转发消息的队列叫ListenerQueue,同时创建一个交换机,代码如下:

/*** Title:延时队列配置* Description:创建2个队列,利用rabbit队列超时机制* @author WZQ* @version 1.0.0* @date 2020/3/14*/
@Configuration
public class DelayQueueConfig {//读取配置文件的内容@Resourceprivate Environment env;/*** 创建queue1,没有被监听,延时队列* 消息过期了发送给死信队列,死信队列绑定queue2*/@Beanpublic Queue orderDelayQueue(){return QueueBuilder.durable(Objects.requireNonNull(env.getProperty("mq.pay.queue.cancelOrderDelay"))// 延时队列过期的消息会进入给死信队列(没有被读的消息),死信队列的消息就发送到queue2中// 这里就需要死信队列的交换机和队列,规定是死信路由key的就路由到queue2.withArgument("x-dead-letter-exchange", env.getProperty("mq.pay.exchange.cancelOrder")) //死信交换机.withArgument("x-dead-letter-routing-key", Objects.requireNonNull(env.getProperty("mq.pay.queue.cancelOrder")) //交换机绑定路由消息到queue2.build();}/*** 创建queue2,普通有监听的队列*/@Beanpublic Queue orderListenerQueue(){return new Queue(Objects.requireNonNull(env.getProperty("mq.pay.queue.cancelOrder"),true);}/*** 创建交换机*/@Beanpublic Exchange orderListenerExchange(){return new DirectExchange(env.getProperty("mq.pay.exchange.cancelOrder"));}/*** 队列queue2绑定交换机*/@Beanpublic Binding orderListenerBinding(){return BindingBuilder.bind(orderListenerQueue()).to(orderListenerExchange()).with(Objects.requireNonNull(env.getProperty("mq.pay.queue.cancelOrderkey")).noargs();}}
启动类添加

@EnableRabbit

发送消息

在生成订单,获取支付状态的service中,加入发送消息代码

@Resource
private RabbitTemplate rabbitTemplate;//读取配置文件的内容
@Resource
private Environment env;//传订单id过去
rabbitTemplate.convertAndSend(env.getProperty("mq.pay.queue.cancelOrderkey"),(Object) order.getId(), new MessagePostProcessor() {@Overridepublic Message postProcessMessage(Message message) throws AmqpException {//设置延时message.getMessageProperties().setExpiration("1800000");//30*60*1000,半小时return message;}});
监听实现

订单微服务中监听订单支付状态

@Component
@RabbitListener(queues = DelayQueueConfig.LISTENER_QUEUE)
public class OrderDelayMessageListener {@Autowiredprivate OrderService orderService;@Autowiredprivate WeixinPayFeign weixinPayFeign;/**** 读取消息,消息是id* 关闭支付,再关闭订单* @param message*/@RabbitHandlerpublic void consumeMessage(String message){//@Payload Object message消息是实体类的json数据//读取消息,消息封装类,自定义,订单号//MessageStatus messageStatus = JSON.parseObject(message,MessageStatus.class); //查询订单状态,确定未支付selectById(message);//如果订单未支付//关闭支付,feign调用支付微服务的关闭订单接口Result closeResult = weixinPayFeign.closePay(message);Map<String,String> closeMap = (Map<String, String>) closeResult.getData();if(closeMap!=null && closeMap.get("return_code").equalsIgnoreCase("success") &&closeMap.get("result_code").equalsIgnoreCase("success") ){//关闭订单OrderService.closeOrder(username);//库存回滚//...}}
}

SpringBoot实现微信支付流程+RabbitMQ消息推送相关推荐

  1. 微信小程序开发—消息推送

    微信小程序的消息推送简单的说就是发送一条微信通知给用户,用户点开消息可以查看消息内容,可以链接进入到小程序的指定页面. 微信小程序消息推送需要用户触发动作才能发送消息,比如用户提交订单.支付成功.一次 ...

  2. 微信小程序开发消息推送配置教程

    微信小程序开发消息推送配置教程 微信小程序开发消息推送配置这一块网上都是PHP居多,由于用egg.js写了一套验证方法. 第一步:填写服务器配置 登录微信小程序官网后,在小程序官网的"设置- ...

  3. 微信企业号下的消息推送接口

    一般在微信企业号下做软件开发,基本都会用到消息推送,用户在完成一个操作之后,会在企业号中推送一条消息,这条消息可能是文本.图文等不同类型,在具有审批流程的消息推送中,下一级人员审批完成会给上一级推送一 ...

  4. JAVA对接企业微信,实现文本消息推送

    对接企业微信,实现文本消息推送,可分为以下两部: 1.根据企业ID+应用的凭证密钥,获取Token 2.根据Token+要传输的body,实现文本消息推送 1.根据企业ID+应用的凭证密钥,获取Tok ...

  5. 实现微信公众号H5消息推送的超级详细步骤

    前言 前段时间在项目中做了一个给H5消息推送的功能,特此记录一下,感兴趣或者有需要的小伙伴可以查阅一下,因为其实代码并不难,我觉得对于初学者来说难的是一些概念和具体实现的过程,所以我会先使用微信提供的 ...

  6. java推送微信消息换行_5行代码实现微信小程序模版消息推送 (含推送后台和小程序源码)...

    我们在做小程序开发时,消息推送是不可避免的.今天就来教大家如何实现小程序消息推送的后台和前台开发.源码会在文章末尾贴出来. 其实我之前有写过一篇:<springboot实现微信消息推送,java ...

  7. Java对接微信公众号模板消息推送(架包WxJava)

    内容有点多,请耐心! 最近公司的有这个业务需求,又很凑巧让我来完成: 首先想要对接,先要一个公众号,再就是开发文档了:https://developers.weixin.qq.com/doc/offi ...

  8. Java对接微信公众号模板消息推送

    最近公司的有这个业务需求,又很凑巧让我来完成: 首先想要对接,先要一个公众号,再就是开发文档了:https://developers.weixin.qq.com/doc/offiaccount/Get ...

  9. 【Node.js】实现微信小程序订阅消息推送功能

    实战项目名称:实现微信小程序订阅消息通知 文章目录 一.实战步骤 1. 登录微信小程序管理端,添加订阅消息模板 2. 定义好需要发送的消息 3.获取小程序的access_token 4. 发起请求,向 ...

最新文章

  1. 一文读懂卷积神经网络
  2. python装饰器执行顺序_python unittest单元测试框架-3用例执行顺序、多级目录、装饰器、fixtures...
  3. Golang新开发者要注意的陷阱和常见错误
  4. aspects to consider for a recommendation letter
  5. python绘制指数函数图像及性质_指数函数图像及其性质正式版
  6. leetcode 回文数
  7. 操作系统(7)-进程、线程、协程的区别
  8. Monte Carlo采样
  9. 【设计模式】代理模式
  10. BDA,CDA,CPDA哪个证相对可靠?哪个含金量高?具体考试内容?
  11. 倾斜摄影计算机配置,2019年倾斜摄影三维建模-台式、便携、多机集群配置推荐...
  12. 解决大部分win10软件字体模糊的问题
  13. Matlab中的对数使用
  14. 默写人体的方法有哪些?如何默写好画人体?
  15. 小码哥教育Vuejs笔记
  16. java 子类属性覆盖_java子类和父类属性重复问题
  17. 【金猿案例展】上海市儿童基金会——管理会计云平台建设
  18. 854. Floyd求最短路
  19. 华为鸿蒙系统让国产手机用,鸿蒙系统不支持华为手机 系统还是国产手机的命门...
  20. 串行器 MAX96717F 芯片介绍

热门文章

  1. 第五届趣味编程大赛——F 苗童大作战之爱洗澡的鳄鱼
  2. 传智教育“大同互联网职业技术学院”奠基仪式盛大举行,开拓高等职业教育发展新版图...
  3. MAC下的word里面照片突然不能在线裁剪,裁剪框变成了灰色;
  4. 利用 Windows Vista 和 WCF 中强大的 P2P 通信功能 [转]
  5. 【DB笔试面试713】在Oracle中,如何将一个数据库添加到CRS中?
  6. linux下mnt命令怎么用,在终端用 代码如下: mount -o loop /mnt/*/1.iso /mnt/cdrom 命令
  7. 扬帆起航,再踏征程(一)
  8. linux windows文件映射,WINDOWS的共享文件夹映射到linux上
  9. 库存管理之调拨、盘点、报损
  10. springboot集成mqtt(超级无敌详细)