实现微信支付(Native支付),使用WebSocket进行推送

——4.配置SpringBoot支持WebSocket,推送结果

依赖

        <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId></dependency>

一、配置WebSocket

@Configuration
public class WebSocketConfig {@Beanpublic ServerEndpointExporter serverEndpointExporter() {return new ServerEndpointExporter();}
}

二、创建WebSocketService

2.1 session缓存问题

向客户端发送消息,需要使用Session对象。但是这些生命周期函数都由于客户端某种操作,而触发执行的。如果客户端不触发操作,那么后端是无法主动给客户端发送消息的。所以我们要把Session对象缓存起来。需要的时候,我们提取缓存的Session,主动向客户端发送消息

因为后端的WebSocket服务类是多例的,所以我们想要全局共享缓存,要么用Redis,要么声明静态的HashMap对象。如果选用Redis,那么保存Session对象要用到序列化,会消耗一定的时间,所以不建议使用。如果全局共享使用HashMap,又会存在并发读写的问题,最终我们选择ConcurrentHashMap类

2.2 token问题

WebSocket不支持Cookie,所以我们要自己把Token字符串上传给服务端,我们要自己从Token字符串中提取UserId出来。

2.3 数据格式

在WebSocket中,我们要约定跟客户端传递数据的格式。为了能让数据看起来格式规整,我采用传递JSON字符串的方式。

参数 含义 例子
opt 操作行为 ping
token 令牌字符串 eyJzb2Z0d2FyZV9pZCI6IjROUkIxLTBYWkFCWkk5RTYtNVNNM1IiLCJjbGll
其他参数 其他参数 (略)
@Slf4j
@ServerEndpoint(value = "/socket")
@Component
public class WebSocketService {//用于保存WebSocket连接对象public static ConcurrentHashMap<String, Session> sessionMap = new ConcurrentHashMap<>();/*** 连接建立成功调用的方法*/@OnOpenpublic void onOpen(Session session) {}/*** 连接关闭调用的方法*/@OnClosepublic void onClose(Session session) {Map map = session.getUserProperties();if (map.containsKey("userId")) {String userId = MapUtil.getStr(map, "userId");sessionMap.remove(userId);}}/*** 接收消息** @param message* @param session*/@OnMessagepublic void onMessage(String message, Session session) {//把字符串转换成JSONJSONObject json = JSONUtil.parseObj(message);String opt = json.getStr("opt");if("ping".equals(opt)){return;}//从JSON中取出TokenString token = json.getStr("token");//从Token取出userIdString userId = StpUtil.stpLogic.getLoginIdByToken(token).toString();//取出Session绑定的属性Map map = session.getUserProperties();//如果没有userId属性,就给Session绑定userId属性,关闭连接的时候会用到if (!map.containsKey(userId)) {map.put("userId", userId);}//把Session缓存起来if (sessionMap.containsKey(userId)) {//替换缓存中的SessionsessionMap.replace(userId, session);} else {//向缓存添加SessionsessionMap.put(userId, session);}sendInfo("ok",userId);}@OnErrorpublic void onError(Session session, Throwable error) {log.error("发生错误", error);}/*** 发送消息给客户端*/public static void sendInfo(String message, String userId) {if (StrUtil.isNotBlank(userId) && sessionMap.containsKey(userId)) {//从缓存中查找到Session对象Session session = sessionMap.get(userId);//发送消息sendMessage(message, session);}}/*** 封装发送消息给客户端*/private static void sendMessage(String message, Session session) {try {session.getBasicRemote().sendText(message);} catch (Exception e) {log.error("执行异常", e);}}
}

三、推送付款结果

之前我们已经实现了:后端接收到微信平台发送的付款成功通知,接下来只需要用WebSocket技术把结果推送给前端页面。

3.1 持久层

因为微信平台发送过来的XML数据中,没有userId,所以我们想根据userId查找缓存的Session,就有问题。所以要先查出userId才能发起推送。

<select id="searchUserIdByUUID" parameterType="String" resultType="Integer">SELECT user_id AS userIdFROM tb_amectWHERE uuid = #{uuid}
</select>
public interface TbAmectDao {……public int searchUserIdByUUID(String uuid);
}

3.2 业务层代码

public interface AmectService {……public int searchUserIdByUUID(String uuid);
}
public class AmectServiceImpl implements AmectService {……@Overridepublic int searchUserIdByUUID(String uuid) {int userId = amectDao.searchUserIdByUUID(uuid);return userId;}
}

3.3 Web层

修改之前的recieveMessage,在 //TODO 向前端页面推送付款结果 处添加代码

public class AmectController {……@Operation(summary = "接收消息通知")@RequestMapping("/recieveMessage")public void recieveMessage(HttpServletRequest request, HttpServletResponse response) throws Exception {……int rows = amectService.updateStatus(param);if (rows == 1) {//根据罚款单ID查询用户IDint userId = amectService.searchUserIdByUUID(outTradeNo);//向用户推送结果WebSocketService.sendInfo("收款成功", userId + "");……}……}
}

四、前端接收数据

1.在前端项目启动是建立WebSocket连接

//使用WebSocket,后端项目给前端页面推送通知更
import VueNativeSock from "vue-native-websocket-vue3";
app.use(VueNativeSock,"ws://192.168.99.216:8090/emos-api/socket",{ "format": "json"
});

2.发送轮询请求

连接创建之后,真正开始使用是在用户登陆系统之后,也就是进入到首页。在该页面的created()函数中,添加了轮询的ping请求,防止WebSocket连接因为超时被切断。

重建连接不如轮询维持连接,这样资源浪费会小一点,当然你也可以按自己的想法来

created() {    let that = this;    that.routeHandle(that.$route);    //当WebSocket连接创建成功之后,会触发这个回调函数的运行    that.$options.sockets.onopen = function(resp) {        //发送心跳检测,避免超时后服务端切断连接        setInterval(function() {            that.$socket.sendObj({ opt: 'ping' });        }, 60 * 1000);    };},

3.接收付款结果

在之前创建订单的前端代码之前,调用webSocket接收回调,使用$nextTick接收异步回调

需要特别注意,在接收消息之前,我们要先想后端发送一条消息。因为后端onMessage()方法,遇到ping请求,是不会把Session缓存起来的。所以我们要随便发一个不是ping的请求给后端,这样它才能缓存Session,将来才可以给我推送消息。

下面为核心代码

         that.$nextTick(()=>{// 利用WebSocket接受后端推送的付款结果//从浏览器localStorage中获取Token令牌let token = localStorage.getItem("token");//向WebSocket服务类发送消息,让服务类缓存Session对象,可以推送消息给当前页面that.$socket.sendObj({opt:'pay_amect',token:token});//接收服务端推送的消息that.$options.sockets.onmessage = function(resp){//console.log("reps",resp);let data = resp.data;if(data == '收款成功'){that.result = true;}}that.$http('amect/createNativeAmectPayOrder', 'POST', { amectId: id }, true, function(resp) {that.qrCode = resp.qrCodeBase64;});})},

到此,微信支付功能已基本实现,但这还不够完善,如果后端或用户没有收到微信服务器发来的付款成功的消息的话,可能存在隐患,所以我们还要添加一个主动查询付款是否成功的功能。

五、主动查询付款结果

5.1 微信官方API

微信官方的API接口( https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_2) 给我们提供了查询付款结果的功能

参数 含义 类型 示例
appid 开发者账号的APPID String wxd678efh567hg6787
mch_id 商户号ID String 1230000109
transaction_id 支付订单ID String 1009660380201506130
out_trade_no 商品订单ID String 20150806125346
nonce_str 随机字符串 String C380BEC2BFD727A4B6845133519F3AD6
sign 数字签名 String 5K8264ILTKCH16CQ2502SI8ZNMTM67VS

返回的响应,我们只需要看4个参数就可以了。

参数 含义 类型 示例
return_code 通信状态码 String SUCCESS
result_code 业务状态码 String SUCCESS
trade_state 交易状态码 String SUCCESS
sign 数字签名 String 5K8264ILTKCH16CQ2502SI8ZNMTM67VS

5.2 业务层代码

public interface AmectService {……public void searchNativeAmectPayResult(HashMap param);
}

注意:修改一处代码

把wxPay.isPayResultNotifySignatureValid(result))
改为WXPayUtil.isSignatureValid(result,myWXPayConfig.getKey(), WXPayConstants.SignType.HMACSHA256)原因是isPayResultNotifySignatureValid中是根据微信服务器
返回的sign_type,来判断签名类型的,如果没有返回sign_type参数(即为null),
则默认为MD5,最重要的是我发现吧,查询支付是否成功微信服务器返回的结果里
还真就没有sign_type,你可以自己尝试一下或者查看微信官网的API接口,
所以调用这个方法验证签名就是MD5,但微信支付使用的是HMACSHA256,
可想而知怎么验证都不正确。之前我没考虑到这个,以为是只能使用MD5,所以我直接修改了微信支付工具类,
直接改为用MD5,后来觉得使用MD5不需要密钥很不安全,重新debug后发现原来
是这个原因。现在改回用HMACSHA256,需要修改三处代码,这是其中一处源代码如下:

另外两处:
实现微信支付(Native支付),使用WebSocket进行推送——2.微信支付工具类:中的二、官网给的工具类 的 2.1 WXPay类 核心类

以及:实现微信支付(Native支付),使用WebSocket进行推送——3.创建支付订单,接收付款结果:中的二、接收付款结果 的 3.编写Web层代码

这是微信支付的官方文档:微信支付官网API查询支付结果返回参数

    public boolean isPayResultNotifySignatureValid(Map<String, String> reqData) throws Exception {String signTypeInData = reqData.get(WXPayConstants.FIELD_SIGN_TYPE);SignType signType;if (signTypeInData == null) {signType = SignType.MD5;} else {signTypeInData = signTypeInData.trim();if (signTypeInData.length() == 0) {signType = SignType.MD5;} else if (WXPayConstants.MD5.equals(signTypeInData)) {signType = SignType.MD5;} else if (WXPayConstants.HMACSHA256.equals(signTypeInData)) {signType = SignType.HMACSHA256;} else {throw new Exception(String.format("Unsupported sign_type: %s", signTypeInData));}}return WXPayUtil.isSignatureValid(reqData, this.config.getKey(), signType);}
public class AmectServiceImpl implements AmectService {……@Overridepublic void searchNativeAmectPayResult(HashMap param) {HashMap map = amectDao.searchAmectByCondition(param);if(MapUtil.isNotEmpty(map)){String uuid = MapUtil.getStr(map,"uuid");param.clear();param.put("appid", myWXPayConfig.getAppID());param.put("mch_id", myWXPayConfig.getMchID());param.put("out_trade_no", uuid);param.put("nonce_str", WXPayUtil.generateNonceStr());try {String sign = WXPayUtil.generateSignature(param, myWXPayConfig.getKey());param.put("sign",sign);WXPay wxPay = new WXPay(myWXPayConfig);Map<String,String> result = wxPay.orderQuery(param);//修改这段代码,把wxPay.isPayResultNotifySignatureValid(result))改为WXPayUtil.isSignatureValid(result,myWXPayConfig.getKey(), WXPayConstants.SignType.HMACSHA256)if(WXPayUtil.isSignatureValid(result,myWXPayConfig.getKey(), WXPayConstants.SignType.HMACSHA256)){String resultCode = result.get("result_code");String returnCode = result.get("return_code");if("SUCCESS".equals(returnCode) && "SUCCESS".equals(resultCode)){String tradeState = result.get("trade_state");//查询到订单支付成功if("SUCCESS".equals(tradeState)){//更新订单状态amectDao.updateStatus(new HashMap(){{put("uuid",uuid);put("status",2);}});}}}else {log.error("数字签名异常");throw new EmosException("数字签名异常");}} catch (Exception e) {log.error("执行异常", e);throw new EmosException("执行异常");}}}
}

5.3 WEB层

@Data
@Schema(description = "查询Navtive支付罚款单支付结果表单")
public class SearchNativeAmectPayResultForm {@NotNull(message = "amectId不能为空")@Min(value = 1, message = "amectId不能小于1")@Schema(description = "罚款单ID")private Integer amectId;
}
public class AmectController {……@PostMapping("/searchNativeAmectPayResult")@Operation(summary = "查询Native支付罚款订单结果")@SaCheckLoginpublic R searchNativeAmectPayResult(@Valid @RequestBody SearchNativeAmectPayResultForm form) {int userId = StpUtil.getLoginIdAsInt();int amectId = form.getAmectId();HashMap param = new HashMap() {{put("amectId", amectId);put("userId", userId);put("status", 1);}};amectService.searchNativeAmectPayResult(param);return R.ok();}
}

5.4 前端

前端只要提供方法,发送ajax访问该API即可

微信支付系列其他部分

实现微信支付(Native支付),使用WebSocket进行推送 ——4.配置SpringBoot支持WebSocket,推送结果相关推荐

  1. PC网站微信扫码支付,Native支付,“当前商户号暂不支持关联该类型的appid“,“签名错误,请检查后再试““springBoot 微信支付“

    springBoot 微信支付 PC网站微信扫码支付-Native支付 一.采坑大合集 1.当前商户号暂不支持关联该类型的appid 2.签名错误,请检查后再试 二.springboot集成微信支付D ...

  2. 微信支付(一)SpringBoot 实现微信扫码支付/Native支付

    SpringBoot 实现微信扫码支付/Native支付 一.背景 在开发一个捐赠项目时须在pc端接入微信扫码支付(Native 扫码支付),在微信端接入微信公众号支付(Jsapi 支付).后端使用的 ...

  3. 微信支付之扫码支付Native支付 模式二

    一.使用场景 1.福袋机上的屏幕是一个Android平板,相当于一个Android手机 2.需要给用户生成一张二维码,让用户扫描付款 3.得到用户付款的消息后,转动对应的电机,给用户掉落福袋 二.准备 ...

  4. spring boot微信h5,native支付 APIV3

    因为本人比较懒 不想写太过于理论的东西 所以直接开始上操作 微信APIV3支付 开始上操作了 支付前准备 APP_ID:应用id MchId : 商户号 privateKey :私钥字符串 mchSe ...

  5. WebSocket API使用篇检测浏览器是否支持WebSocket(4)

    WebSocket API是下一代客户端-服务器的异步通信方法.前面有三篇文章已经对WebSocket有了一些介绍,这里我总结了一下.我在使用WebSockets API过程中遇到的问题. 1.检测浏 ...

  6. 微信pc Native支付 支付宝pc扫码支付 Demo PHP tp5

    官方sdk Demo 支付宝 请求支付代码 public function alipay(){$arr =input('param.');vendor('alipay-trade-page-pay/a ...

  7. 微信的NATIVE支付提示201商户订单号重复的解决方案

    无论采取模式一还是模式二,进行预支付ID获取的时候应当确保订单号的唯一性,否则就会造成第二次扫码后的重复提醒. 解决方案: 以预支付ID处理为例: 商城: 1.创建log_id数据表如:out_tra ...

  8. 实现微信支付(Native支付),使用WebSocket进行推送——3.创建支付订单,接收付款结果

    实现微信支付(Native支付),使用WebSocket进行推送--3.创建支付订单,接收付款结果 注:本实验使用springboot框架 一.创建订单 1.流程 2.创建支付订单所需参数 2. AP ...

  9. 微信支付,JSAPI支付,APP支付,H5支付,Native支付,小程序支付功能详情以及回调处理

    一.支付相关文档地址 支付wiki:https://pay.weixin.qq.com/wiki/doc/apiv3/index.shtml 支付api: https://pay.weixin.qq. ...

最新文章

  1. swoole client php,Swoole Client
  2. Django REST framework+Vue 打造生鲜超市(五)
  3. textedit怎么插入数据_还在手动插入Excel交叉空白行?这个小技巧10秒搞定
  4. java thread 输出结果_大神来看看这段程序输出结果是什么?
  5. 怎样安装php5_如何安装php5.3
  6. bower——库管理工具
  7. redis 哨兵的原理
  8. Eclipse简单字体设置
  9. 缺少比较器,运放来救场!(运放当做比较器电路记录)
  10. 虚拟机Linux IP地址更改
  11. 安卓桌面壁纸_苹果iPhone全面屏桌面壁纸 第88期 苹果x手机专用高清壁纸
  12. linux 下配置可视化git 冲突合并工具kdiff3
  13. java mov 转 mp4 视频格式
  14. IOS 中json解析异常Domain=NSCocoaErrorDomain Code=3840之诡异事件
  15. Could not resolve hostname github: Name or service
  16. flutter安装及过程中遇到的问题解决
  17. FPGA-10:设计个简单的cpu(真的简单!)
  18. Ambiguous method overloading for method ****** 异常的解决办法
  19. 中国RV减速器行业竞争格局与发展趋势预测报告2022-2028年
  20. 编写一个“绘画系统”

热门文章

  1. 大学计算机专业分流考试,【SHU·分流季】历时四周,只为你!(附智科专业分流信息)...
  2. java中double和Double的区别
  3. DISTINCT和GROUP BY的区别
  4. Java中的“君不君,臣不臣,父不父,子不子”
  5. 炫界 (392) -(持法延迟)_Apex英雄支持NVIDIA Reflex 华硕显卡低延迟利器
  6. Java错误与问题合集
  7. 前端基础 - 案例二:酱品购电商网站首页(精简版)
  8. 城市轨道交通用不用计算机,四川男生学城市轨道交通运营管理在哪里
  9. 西门子S7-200SMART 通信组态
  10. hiredis和rapidjson库的使用小结