博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
SpringBoot实现微信支付流程+RabbitMQ消息推送
阅读量:3942 次
发布时间:2019-05-24

本文共 23145 字,大约阅读时间需要 77 分钟。

微信支付

整个流程使用到的组件代码:

链接:
提取码: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

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

二维码入门小demo

运行效果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UeK0QqoQ-1619103521443)(..\images\1549706445665.png)]

微信扫码支付

微信扫码支付申请

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

申请步骤:

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

请根据营业执照类型选择以下主体注册:| | | | 。

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

第二步:认证公众号

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

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

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

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

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

第五步:在线签署协议

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

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

开发文档

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

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

在线微信支付开发文档:

Native二维码扫码支付。

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

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

微信扫码支付模式介绍

模式一

商家二维码不过时。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-E5DwxYuF-1619103521445)(..\images\1558448158371.png)]

业务流程说明:

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.商户确认订单已支付后给用户发货。

模式二

商家二维码会过时。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nCBfveR9-1619103521447)(..\images\1558448510488.png)]

业务流程说明:

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

微信支付SDK

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-erQZ5LaT-1619103521450)(..\images\1537902584152.png)]

安装SDK,jar包。

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

0.0.3
com.github.wxpay
wxpay-sdk
${wxpay.version}

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

@Test    public void test() throws Exception {
//生成随机字符串 String s = WXPayUtil.generateNonceStr(); System.out.println("随机字符串:"+s); //将map转成xml字符串 Map
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字符串转成map Map
mapResult = WXPayUtil.xmlToMap(mapToXml); System.out.println("xml字符串转成map"+mapResult); }

项目实战

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v9409DXL-1619103521452)(..\images\46.png)]

依赖:

0.0.3
com.github.wxpay
wxpay-sdk
${wxpay.version}

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

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

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

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

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

notifyurl: 回调地址

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

统一下单api

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

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

官网文档:

二维码只能支付成功一次,成功后则失效,默认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
createNative(Map
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;@Servicepublic 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 */ @Override public Map
createNative(Map
parameterMap){
//客户端自定义订单编号;交易金额(单位:分) String outTradeNo = parameterMap.get("no"); String totalFee = parameterMap.get("money"); try {
//1、封装参数 Map
paramMap = new HashMap<>(); paramMap.put("appid", appid); //应用ID paramMap.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、执行请求,微信统一下单api HttpClient 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
stringMap = WXPayUtil.xmlToMap(content); System.out.println("stringMap:"+stringMap); //5、获取部分页面所需参数 Map
dataMap = new HashMap
(); 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")@CrossOriginpublic class WeixinPayController {
@Autowired private WeixinPayService weixinPayService; /*** * 创建二维码 * 参数是订单号和金额,分为单位 * @return */ @RequestMapping(value = "/create/native") public ResponseResult
> createNative(@RequestParam Map
paramMap){
Map
resultMap = weixinPayService.createNative(paramMap); return new ResponseResult
>(true, StatusCode.OK,"创建二维码预付订单成功!",resultMap); }}

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aatYVgTk-1619103521454)(..\images\41.png)]

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

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2kGKdfli-1619103521456)(..\images\42.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dYyeLRoB-1619103521457)(..\images\43.png)]

支付信息回调

接口分析

每次实现支付之后,微信支付都会将用户支付结果返回到指定路径,而指定路径是指创建二维码的时候填写的notifyurl参数,响应的数据以及相关文档参考一下地址:

返回参数分析

通知参数如下:

字段名 变量名 必填 类型 示例值 描述
返回状态码 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 请按示例值填写

举例如下:

回调接收数据实现

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
map = WXPayUtil.xmlToMap(result); //响应数据设置 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;}

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mt1Vp79W-1619103521459)(..\images\45.png)]

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

查询订单api

官网文档:

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

service:

/***     * 查询订单状态     * @param outTradeNo : 客户端自定义订单编号     * @return     */    Map
queryPayStatus(String outTradeNo); /*** * 查询订单状态 * @param outTradeNo : 客户端自定义订单编号 * @return */ @Override public Map
queryPayStatus(String outTradeNo) {
try {
//1.封装参数 Map
param = new HashMap<>(); param.put("appid",appid); //应用ID param.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、获取返回值,并将返回值转成Map String 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
> queryStatus(String outTradeNo){
Map
resultMap = weixinPayService.queryPayStatus(outTradeNo); return new ResponseResult
>(true,StatusCode.OK,"查询状态成功!",resultMap); }

测试:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TgOwLdYy-1619103521460)(..\images\44.png)]

关闭订单api

官网文档:

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

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

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

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

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

/*** * 关闭微信支付 * @param orderId * @return * @throws Exception */@Overridepublic Map
closePay(Long orderId) throws Exception {
//参数设置 Map
paramMap = new HashMap
(); paramMap.put("appid",appid); //应用ID paramMap.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); //确定url String url = "https://api.mch.weixin.qq.com/pay/closeorder"; //发送请求 HttpClient httpClient = new HttpClient(url); //https httpClient.setHttps(true); //提交参数 httpClient.setXmlParam(xmlParam); //提交 httpClient.post(); //获取返回数据 String content = httpClient.getContent(); //将返回数据解析成Map return WXPayUtil.xmlToMap(content);}

controller:

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

MQ处理支付回调状态

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

发送MQ消息

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

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

org.springframework.boot
spring-boot-starter-amqp

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

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

spring:  rabbitmq:    host: 192.168.169.140 #ip地址    port: 5672 #端口    username: guest #账号    password: guest #密码    #设置order交换机和order队列名称 mq:  pay:    exchange:      order: exchange.order    queue:      order: queue.order    routing:      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 */@Configurationpublic class MQConfig {
//读取配置文件的内容 @Resource private Environment env; /*** * 创建DirectExchange交换机 * @return */ @Bean public 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 */ @Bean public 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;@Autowiredprivate 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
map = WXPayUtil.xmlToMap(result); //将消息发送给RabbitMQ rabbitTemplate.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中引入如下依赖:

org.springframework.boot
spring-boot-starter-amqp

在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 {
@Resource private OrderService orderService; /*** * 接收消息 */ @RabbitHandler public void consumeMessage(String msg){
//将数据转成Map Map
result = JSON.parseObject(msg,Map.class); //return_code=SUCCESS String 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接口,详看:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ToDYmGZW-1619103521462)(..\images\47.png)]

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发送

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YPCb5ySg-1619103521463)(..\images\1557396863944.png)]

DLX延时队列实现

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

配置队列
#设置交换机和队列名称mq:  pay:    exchange:      order: exchange.order      cancelOrder: dlx.exchange    queue:      order: queue.order      cancelOrder: queue.listener      cancelOrderDelay: queue.delay    routing:      orderkey: queue.order      cancelOrderkey: queue.listener
队列创建

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

/** * Title:延时队列配置 * Description:创建2个队列,利用rabbit队列超时机制 * @author WZQ * @version 1.0.0 * @date 2020/3/14 */@Configurationpublic class DelayQueueConfig {
//读取配置文件的内容 @Resource private Environment env; /** * 创建queue1,没有被监听,延时队列 * 消息过期了发送给死信队列,死信队列绑定queue2 */ @Bean public 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,普通有监听的队列 */ @Bean public Queue orderListenerQueue(){
return new Queue(Objects.requireNonNull(env.getProperty("mq.pay.queue.cancelOrder"),true); } /** * 创建交换机 */ @Bean public Exchange orderListenerExchange(){
return new DirectExchange(env.getProperty("mq.pay.exchange.cancelOrder")); } /** * 队列queue2绑定交换机 */ @Bean public Binding orderListenerBinding(){
return BindingBuilder.bind(orderListenerQueue()).to(orderListenerExchange()).with(Objects.requireNonNull(env.getProperty("mq.pay.queue.cancelOrderkey")).noargs(); }}
启动类添加

@EnableRabbit

发送消息

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

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

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

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

转载地址:http://wtiwi.baihongyu.com/

你可能感兴趣的文章
多维数组[:,0]和[:0:1]获取的区别
查看>>
复原Ip地址
查看>>
重建二叉树
查看>>
二叉树根节点到叶子节点的路径数字之和
查看>>
根节点到叶子节点的节点值之和等于 sum的路径
查看>>
判断二叉树是否有从根节点到叶子节点的节点值之和等于sum的路径
查看>>
反转字符串
查看>>
环形链表
查看>>
删除链表的倒数第N个节点
查看>>
回文链表
查看>>
容器盛水问题
查看>>
滑动窗口最大值
查看>>
win7 文件删除后要刷新后才会消失
查看>>
用ffmpeg转多音轨的mkv文件
查看>>
ubuntu12.04 安装VLC,在root用户下不能使用的问题
查看>>
简单而又完整的Makefile
查看>>
GNU/Linux下如何卸载源码安装的软件
查看>>
ffmpeg 常用 命令随手记
查看>>
av_seek_frame中flags值的意义
查看>>
git 学习笔记
查看>>