实现微信支付(Native支付),使用WebSocket进行推送 ——4.配置SpringBoot支持WebSocket,推送结果
实现微信支付(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,推送结果相关推荐
- PC网站微信扫码支付,Native支付,“当前商户号暂不支持关联该类型的appid“,“签名错误,请检查后再试““springBoot 微信支付“
springBoot 微信支付 PC网站微信扫码支付-Native支付 一.采坑大合集 1.当前商户号暂不支持关联该类型的appid 2.签名错误,请检查后再试 二.springboot集成微信支付D ...
- 微信支付(一)SpringBoot 实现微信扫码支付/Native支付
SpringBoot 实现微信扫码支付/Native支付 一.背景 在开发一个捐赠项目时须在pc端接入微信扫码支付(Native 扫码支付),在微信端接入微信公众号支付(Jsapi 支付).后端使用的 ...
- 微信支付之扫码支付Native支付 模式二
一.使用场景 1.福袋机上的屏幕是一个Android平板,相当于一个Android手机 2.需要给用户生成一张二维码,让用户扫描付款 3.得到用户付款的消息后,转动对应的电机,给用户掉落福袋 二.准备 ...
- spring boot微信h5,native支付 APIV3
因为本人比较懒 不想写太过于理论的东西 所以直接开始上操作 微信APIV3支付 开始上操作了 支付前准备 APP_ID:应用id MchId : 商户号 privateKey :私钥字符串 mchSe ...
- WebSocket API使用篇检测浏览器是否支持WebSocket(4)
WebSocket API是下一代客户端-服务器的异步通信方法.前面有三篇文章已经对WebSocket有了一些介绍,这里我总结了一下.我在使用WebSockets API过程中遇到的问题. 1.检测浏 ...
- 微信pc Native支付 支付宝pc扫码支付 Demo PHP tp5
官方sdk Demo 支付宝 请求支付代码 public function alipay(){$arr =input('param.');vendor('alipay-trade-page-pay/a ...
- 微信的NATIVE支付提示201商户订单号重复的解决方案
无论采取模式一还是模式二,进行预支付ID获取的时候应当确保订单号的唯一性,否则就会造成第二次扫码后的重复提醒. 解决方案: 以预支付ID处理为例: 商城: 1.创建log_id数据表如:out_tra ...
- 实现微信支付(Native支付),使用WebSocket进行推送——3.创建支付订单,接收付款结果
实现微信支付(Native支付),使用WebSocket进行推送--3.创建支付订单,接收付款结果 注:本实验使用springboot框架 一.创建订单 1.流程 2.创建支付订单所需参数 2. AP ...
- 微信支付,JSAPI支付,APP支付,H5支付,Native支付,小程序支付功能详情以及回调处理
一.支付相关文档地址 支付wiki:https://pay.weixin.qq.com/wiki/doc/apiv3/index.shtml 支付api: https://pay.weixin.qq. ...
最新文章
- swoole client php,Swoole Client
- Django REST framework+Vue 打造生鲜超市(五)
- textedit怎么插入数据_还在手动插入Excel交叉空白行?这个小技巧10秒搞定
- java thread 输出结果_大神来看看这段程序输出结果是什么?
- 怎样安装php5_如何安装php5.3
- bower——库管理工具
- redis 哨兵的原理
- Eclipse简单字体设置
- 缺少比较器,运放来救场!(运放当做比较器电路记录)
- 虚拟机Linux IP地址更改
- 安卓桌面壁纸_苹果iPhone全面屏桌面壁纸 第88期 苹果x手机专用高清壁纸
- linux 下配置可视化git 冲突合并工具kdiff3
- java mov 转 mp4 视频格式
- IOS 中json解析异常Domain=NSCocoaErrorDomain Code=3840之诡异事件
- Could not resolve hostname github: Name or service
- flutter安装及过程中遇到的问题解决
- FPGA-10:设计个简单的cpu(真的简单!)
- Ambiguous method overloading for method ****** 异常的解决办法
- 中国RV减速器行业竞争格局与发展趋势预测报告2022-2028年
- 编写一个“绘画系统”
热门文章
- 大学计算机专业分流考试,【SHU·分流季】历时四周,只为你!(附智科专业分流信息)...
- java中double和Double的区别
- DISTINCT和GROUP BY的区别
- Java中的“君不君,臣不臣,父不父,子不子”
- 炫界 (392) -(持法延迟)_Apex英雄支持NVIDIA Reflex 华硕显卡低延迟利器
- Java错误与问题合集
- 前端基础 - 案例二:酱品购电商网站首页(精简版)
- 城市轨道交通用不用计算机,四川男生学城市轨道交通运营管理在哪里
- 西门子S7-200SMART 通信组态
- hiredis和rapidjson库的使用小结