From 0758a0da23cc7f11c6c89ee8226678e15fae18f1 Mon Sep 17 00:00:00 2001 From: terrfly Date: Tue, 22 Jun 2021 14:56:34 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=94=AF=E4=BB=98=E6=B5=8B?= =?UTF-8?q?=E8=AF=95ctrl;=20=E6=B7=BB=E5=8A=A0webSocket=E7=9B=91=E5=90=AC?= =?UTF-8?q?=E6=8E=A8=E9=80=81=E5=88=B0=E7=AB=AF=E7=A8=8B=E5=BA=8F=EF=BC=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jeepay-merchant/pom.xml | 6 + .../mch/ctrl/paytest/PaytestController.java | 128 ++++++++++++++ .../ctrl/paytest/PaytestNotifyController.java | 79 +++++++++ .../mch/websocket/config/WebSocketConfig.java | 36 ++++ .../websocket/server/WsPayOrderServer.java | 161 ++++++++++++++++++ 5 files changed, 410 insertions(+) create mode 100644 jeepay-merchant/src/main/java/com/jeequan/jeepay/mch/ctrl/paytest/PaytestController.java create mode 100644 jeepay-merchant/src/main/java/com/jeequan/jeepay/mch/ctrl/paytest/PaytestNotifyController.java create mode 100644 jeepay-merchant/src/main/java/com/jeequan/jeepay/mch/websocket/config/WebSocketConfig.java create mode 100644 jeepay-merchant/src/main/java/com/jeequan/jeepay/mch/websocket/server/WsPayOrderServer.java diff --git a/jeepay-merchant/pom.xml b/jeepay-merchant/pom.xml index ffd3afd..4978135 100644 --- a/jeepay-merchant/pom.xml +++ b/jeepay-merchant/pom.xml @@ -95,6 +95,12 @@ jeepay-sdk-java + + + org.springframework + spring-websocket + + diff --git a/jeepay-merchant/src/main/java/com/jeequan/jeepay/mch/ctrl/paytest/PaytestController.java b/jeepay-merchant/src/main/java/com/jeequan/jeepay/mch/ctrl/paytest/PaytestController.java new file mode 100644 index 0000000..b09ccc6 --- /dev/null +++ b/jeepay-merchant/src/main/java/com/jeequan/jeepay/mch/ctrl/paytest/PaytestController.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2021-2031, 河北计全科技有限公司 (https://www.jeequan.com & jeequan@126.com). + *

+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.jeequan.jeepay.mch.ctrl.paytest; + +import com.alibaba.fastjson.JSONObject; +import com.jeequan.jeepay.JeepayClient; +import com.jeequan.jeepay.core.constants.CS; +import com.jeequan.jeepay.core.entity.MchApp; +import com.jeequan.jeepay.core.entity.MchPayPassage; +import com.jeequan.jeepay.core.exception.BizException; +import com.jeequan.jeepay.core.model.ApiRes; +import com.jeequan.jeepay.exception.JeepayException; +import com.jeequan.jeepay.mch.ctrl.CommonCtrl; +import com.jeequan.jeepay.model.PayOrderCreateReqModel; +import com.jeequan.jeepay.request.PayOrderCreateRequest; +import com.jeequan.jeepay.response.PayOrderCreateResponse; +import com.jeequan.jeepay.service.impl.MchAppService; +import com.jeequan.jeepay.service.impl.MchPayPassageService; +import com.jeequan.jeepay.service.impl.SysConfigService; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import java.util.HashSet; +import java.util.Set; + +/* +* 支付测试类 +* +* @author terrfly +* @site https://www.jeepay.vip +* @date 2021/6/22 9:43 +*/ +@RestController +@RequestMapping("/api/paytest") +public class PaytestController extends CommonCtrl { + + @Autowired private MchAppService mchAppService; + @Autowired private MchPayPassageService mchPayPassageService; + @Autowired private SysConfigService sysConfigService; + + /** 查询商户对应应用下支持的支付方式 **/ + @PreAuthorize("hasAuthority('ENT_MCH_PAY_TEST_PAYWAY_LIST')") + @GetMapping("/payways/{appId}") + public ApiRes payWayList(@PathVariable("appId") String appId) { + + Set payWaySet = new HashSet<>(); + mchPayPassageService.list( + MchPayPassage.gw().select(MchPayPassage::getWayCode) + .eq(MchPayPassage::getMchNo, getCurrentMchNo()) + .eq(MchPayPassage::getAppId, appId) + .eq(MchPayPassage::getState, CS.PUB_USABLE) + ).stream().forEach(r -> payWaySet.add(r.getWayCode())); + + return ApiRes.ok(payWaySet); + } + + + /** 调起下单接口 **/ + @PreAuthorize("hasAuthority('ENT_MCH_PAY_TEST_DO')") + @PostMapping("/payOrders") + public ApiRes doPay() { + + //获取请求参数 + String appId = getValStringRequired("appId"); + Long amount = getRequiredAmountL("amount"); + String mchOrderNo = getValStringRequired("mchOrderNo"); + String wayCode = getValStringRequired("wayCode"); + + // 前端明确了支付参数的类型 payDataType + String payDataType = getValString("payDataType"); + String authCode = getValString("authCode"); + + + MchApp mchApp = mchAppService.getById(appId); + if(mchApp == null || mchApp.getState() != CS.PUB_USABLE || !mchApp.getAppId().equals(appId)){ + throw new BizException("商户应用不存在或不可用"); + } + + PayOrderCreateRequest request = new PayOrderCreateRequest(); + PayOrderCreateReqModel model = new PayOrderCreateReqModel(); + request.setBizModel(model); + + model.setMchNo(getCurrentMchNo()); // 商户号 + model.setAppId(appId); + model.setMchOrderNo(mchOrderNo); + model.setWayCode(wayCode); + model.setAmount(amount); + model.setCurrency("CNY"); + model.setClientIp(getClientIp()); + model.setSubject(getCurrentMchNo() + "商户联调"); + model.setBody(getCurrentMchNo() + "商户联调"); + model.setNotifyUrl(sysConfigService.getDBApplicationConfig().getMchSiteUrl() + "/api/anon/paytestNotify/payOrder"); //回调地址 + + //设置扩展参数 + JSONObject extParams = new JSONObject(); + if(StringUtils.isNotEmpty(payDataType)) extParams.put("payDataType", payDataType.trim()); + if(StringUtils.isNotEmpty(authCode)) extParams.put("authCode", authCode.trim()); + model.setChannelExtra(extParams.toString()); + + JeepayClient jeepayClient = new JeepayClient(sysConfigService.getDBApplicationConfig().getPaySiteUrl(), mchApp.getAppSecret()); + + try { + PayOrderCreateResponse response = jeepayClient.execute(request); + if(response.getCode() != 0){ + throw new BizException(response.getMsg()); + } + return ApiRes.ok(response.get()); + } catch (JeepayException e) { + throw new BizException(e.getMessage()); + } + } + +} diff --git a/jeepay-merchant/src/main/java/com/jeequan/jeepay/mch/ctrl/paytest/PaytestNotifyController.java b/jeepay-merchant/src/main/java/com/jeequan/jeepay/mch/ctrl/paytest/PaytestNotifyController.java new file mode 100644 index 0000000..d2e6e8b --- /dev/null +++ b/jeepay-merchant/src/main/java/com/jeequan/jeepay/mch/ctrl/paytest/PaytestNotifyController.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2021-2031, 河北计全科技有限公司 (https://www.jeequan.com & jeequan@126.com). + *

+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.jeequan.jeepay.mch.ctrl.paytest; + +import com.alibaba.fastjson.JSONObject; +import com.jeequan.jeepay.core.entity.MchApp; +import com.jeequan.jeepay.core.model.OriginalRes; +import com.jeequan.jeepay.mch.ctrl.CommonCtrl; +import com.jeequan.jeepay.mch.websocket.server.WsPayOrderServer; +import com.jeequan.jeepay.service.impl.MchAppService; +import com.jeequan.jeepay.service.impl.MchPayPassageService; +import com.jeequan.jeepay.service.impl.SysConfigService; +import com.jeequan.jeepay.util.JeepayKit; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.io.IOException; + +/* +* 支付测试 - 回调函数 +* +* @author terrfly +* @site https://www.jeepay.vip +* @date 2021/6/22 14:22 +*/ +@RestController +@RequestMapping("/api/anon/paytestNotify") +public class PaytestNotifyController extends CommonCtrl { + + @Autowired private MchAppService mchAppService; + + @RequestMapping("/payOrder") + public void payOrderNotify() throws IOException { + + //请求参数 + JSONObject params = getReqParamJSON(); + + String mchNo = params.getString("mchNo"); + String appId = params.getString("appId"); + String sign = params.getString("sign"); + MchApp mchApp = mchAppService.getById(appId); + if(mchApp == null || !mchApp.getMchNo().equals(mchNo)){ + response.getWriter().print("app is not exists"); + return; + } + + params.remove("sign"); + if(!JeepayKit.getSign(params, mchApp.getAppSecret()).equalsIgnoreCase(sign)){ + response.getWriter().print("sign fail"); + return; + } + + JSONObject msg = new JSONObject(); + msg.put("state", params.getIntValue("state")); + msg.put("errCode", params.getString("errCode")); + msg.put("errMsg", params.getString("errMsg")); + + //推送到前端 + WsPayOrderServer.sendMsgByOrderId(params.getString("payOrderId"), msg.toJSONString()); + + response.getWriter().print("SUCCESS"); + } + +} diff --git a/jeepay-merchant/src/main/java/com/jeequan/jeepay/mch/websocket/config/WebSocketConfig.java b/jeepay-merchant/src/main/java/com/jeequan/jeepay/mch/websocket/config/WebSocketConfig.java new file mode 100644 index 0000000..afdd988 --- /dev/null +++ b/jeepay-merchant/src/main/java/com/jeequan/jeepay/mch/websocket/config/WebSocketConfig.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2021-2031, 河北计全科技有限公司 (https://www.jeequan.com & jeequan@126.com). + *

+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.jeequan.jeepay.mch.websocket.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.socket.server.standard.ServerEndpointExporter; + +/* +* 开启WebSocket支持 +* +* @author terrfly +* @site https://www.jeepay.vip +* @date 2021/6/22 12:57 +*/ +@Configuration +public class WebSocketConfig { + + @Bean + public ServerEndpointExporter serverEndpointExporter() { + return new ServerEndpointExporter(); + } +} diff --git a/jeepay-merchant/src/main/java/com/jeequan/jeepay/mch/websocket/server/WsPayOrderServer.java b/jeepay-merchant/src/main/java/com/jeequan/jeepay/mch/websocket/server/WsPayOrderServer.java new file mode 100644 index 0000000..963a68c --- /dev/null +++ b/jeepay-merchant/src/main/java/com/jeequan/jeepay/mch/websocket/server/WsPayOrderServer.java @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2021-2031, 河北计全科技有限公司 (https://www.jeequan.com & jeequan@126.com). + *

+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.jeequan.jeepay.mch.websocket.server; + +import com.alibaba.fastjson.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +import javax.websocket.OnClose; +import javax.websocket.OnError; +import javax.websocket.OnOpen; +import javax.websocket.Session; +import javax.websocket.server.PathParam; +import javax.websocket.server.ServerEndpoint; +import java.io.IOException; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArraySet; + +/* + * WebSocket服务类 + * /ws/payOrder/{訂單ID}/{客戶端自定義ID} + * + * @author terrfly + * @site https://www.jeepay.vip + * @date 2021/6/22 12:57 + */ +@ServerEndpoint("/api/anon/ws/payOrder/{payOrderId}/{cid}") +@Component +public class WsPayOrderServer { + + private final static Logger logger = LoggerFactory.getLogger(WsPayOrderServer.class); + + //当前在线客户端 数量 + private static int onlineClientSize = 0; + + // payOrderId 与 WsPayOrderServer 存储关系, ConcurrentHashMap保证线程安全 + private static Map> wsOrderIdMap = new ConcurrentHashMap<>(); + + //与某个客户端的连接会话,需要通过它来给客户端发送数据 + private Session session; + + //客户端自定义ID + private String cid = ""; + + //支付订单号 + private String payOrderId = ""; + + /** + * 连接建立成功调用的方法 + */ + @OnOpen + public void onOpen(Session session, @PathParam("payOrderId") String payOrderId, @PathParam("cid") String cid) { + + try { + //设置当前属性 + this.cid = cid; + this.payOrderId = payOrderId; + this.session = session; + + Set wsServerSet = wsOrderIdMap.get(payOrderId); + if(wsServerSet == null) wsServerSet = new CopyOnWriteArraySet<>(); + wsServerSet.add(this); + wsOrderIdMap.put(payOrderId, wsServerSet); + + addOnlineCount(); //在线数加1 + logger.info("cid[{}],payOrderId[{}]连接开启监听!当前在线人数为{}", cid, payOrderId, onlineClientSize); + + } catch (Exception e) { + logger.error("ws监听异常cid[{}],payOrderId[{}]", cid, payOrderId, e); + } + } + + /** + * 连接关闭调用的方法 + */ + @OnClose + public void onClose() { + + Set wsSet = wsOrderIdMap.get(this.payOrderId); + wsSet.remove(this); + if(wsSet.isEmpty()) wsOrderIdMap.remove(this.payOrderId); + + subOnlineCount(); //在线数减1 + logger.info("cid[{}],payOrderId[{}]连接关闭!当前在线人数为{}", cid, payOrderId, onlineClientSize); + } + + /** + * @param session + * @param error + */ + @OnError + public void onError(Session session, Throwable error) { + logger.error("ws发生错误", error); + } + + /** + * 实现服务器主动推送 + */ + public void sendMessage(String message) throws IOException { + this.session.getBasicRemote().sendText(message); + } + + /** + * 根据订单ID,推送消息 + * 捕捉所有的异常,避免影响业务。 + * @param payOrderId + */ + public static void sendMsgByOrderId(String payOrderId, String msg) { + + try { + logger.info("推送ws消息到浏览器, payOrderId={},msg={}", payOrderId, msg); + + + Set wsSet = wsOrderIdMap.get(payOrderId); + if(wsSet == null || wsSet.isEmpty()){ + logger.info("payOrderId[{}] 无ws监听客户端", payOrderId); + return ; + } + + for (WsPayOrderServer item : wsSet) { + try { + item.sendMessage(msg); + } catch (Exception e) { + logger.info("推送设备消息时异常,payOrderId={}, cid={}", payOrderId, item.cid, e); + continue; + } + } + } catch (Exception e) { + logger.info("推送消息时异常,payOrderId={}", payOrderId, e); + } + } + + public static synchronized int getOnlineClientSize() { + return onlineClientSize; + } + + public static synchronized void addOnlineCount() { + onlineClientSize++; + } + + public static synchronized void subOnlineCount() { + onlineClientSize--; + } + +} -- GitLab