思路:
1、前端点击支付,使用websocket创建长双关通信通道。
2、调用Alipay(沙箱支付的接口)返回支付宝的支付链接,前端插件生成二维码
3、用户手机就行扫码支付,使用内网穿透(natapp)调用接口进行验证,响应给前端
4、前端通过ws协议响应,判断是否支付成功。(完结撒花)
Alipay(支付宝沙箱)
准备工作
沙箱支付宝地址:支付宝开放平台 (alipay.com)登录 - 支付宝支付宝开放平台 (alipay.com)
进入界面,下载手机板沙箱支付宝(使用账号密码进行登录,无法注册)
点击对应位置
复制号这三个密钥,后面我们需要使用,还有pid和appid
Alipay配置
Alipay的支付功能:我们使用的其实就是在调用第三方应用提供给我们的接口调用进行操作,从而实现我们的功能。
导入依赖
<!-- https://mvnrepository.com/artifact/com.alipay.sdk/alipay-sdk-java --> <!-- 沙箱支付 --> <dependency> <groupId>com.alipay.sdk</groupId> <artifactId>alipay-sdk-java</artifactId> <version>4.13.0.ALL</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency>
下项目的resources目录下创建配置文件alipayConfig.properties
# 支付宝网关名、partnerId、appId
alipayGateway=https://openapi-sandbox.dl.alipaydev.com/gateway.do
mcloudApi=http://mcloudmonitor.com/gateway.do
pid=20887210231756
appId=90210636452
# RSA 私钥、公钥、支付宝公钥
privateKey=MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCYomfcgjjGag4wlfAh9oJhrrHcVHmqHKOqdKzbP1P78Ao6yNxd5kLGBRUvWNzB0rTBfqBpg+W3EKRH5RvEW+fwjKtkW1c/YOReOxnx/rgPzecZ5RI/vWt0Z1nL3iy4sSYvF+YcFFVO2sXT+nn6IOBt+vv6xOXhIhCULKQs7xJKZL8Tbq9/Jt9cTh5AZAPlv2Jk1J8MduU+FFO+lzRl9ghhQ9BZhaWhrRECNeJ9pvYQoa9Tpo/Y2iBe2/a/iqYbI/K756WxCeUbXPz3BDHTjMVpWw9t1M6WALBChM2OYtJ53Jcq2mvSTCPxow6m9wQH3zebhGi9G3X7WHphsV5LDj1bAgMBAAECggEBAIUoOd+/o3RFlbeBNwsKGVjKpNQIxlNHxOix/RMQvl3uXZ5HGSi59sr2KDM0HPLitVqQ87TZopAAbrFiCMVXQJM0xVk57nWWO+SRPuNFSqJPCSwoEbGVuKbGeypF21INCbjP6qnYe0vdw/RYcg1qnSCVczqkh7/OjhQWleu1bYmD3GUjaVPY9P/qAPqK7K3hPDu/tibklkTBZta7vgOK+X85bj51SJ85MDmue0Sqk8Cotebyo48CB7CYLWkLWSWcK/I6DONzfQqIRcRT/h18SCWz+URMNx49gBj5k+DK0DSMBHQ+xvkRffQ7/6+POS3tU8NGlQ2SOVx/R7W5awXAUYECgYEA2Nbu53A2BkPeBpo4tiAMhonXiuARYTaOHP47UvD6UmYiXM8t8mgkZEMrLjcaeYpPao4qghJ4L+Q4bLNgIUNj7N20o2Hx5KAdM9jevbyKWXqolx9nTZM1q/TjM7/DaCPqB89S4o3fwydXagO8aYp2NrMnrVg+0LVrXiB34JW+r3sCgYEAtDMY3f4mEIPJjxlXZLGakCT53UadI8YU7KpDl0/QqLDRIp9YLchAoiFrak0YfSesMYT6etvb2esWvzDQ5S17Af9gw8J6kZTGOjxzlbyR5vU5kk1GXR3jceKJ1sx9AV3fLTea3Wa8vuaaUrokGJBZFS3qRCWdeNWpdmR8GtanU6ECgYEAikWsvHqqiJ44c59ecIzJT/WQM+ekTYhbYROhQseV6HtmiCY5F23fXuw4uKDtmvM5iReYCflnlf4HY3vzC1YsYvWOndFVXC29zhoCN+ZDfLSQWJYSjcxQAQnFTihK5pHTz5Jlns/RZ6zBZWQZVGxNwT2kUFvVUY/Gag3QcCgYEAntoQiNnbWmGi1Fgll2mNdJZ0AeGW8wtSNNNfpErDCYJdymSnyiwm9gX5+AqglOvdOwYb/SRFqdQ5CDATZoRyVG95MPkHLcD2Ai56QjyqbewtZVBjwAByGVn34vf/Fq5W6DiFd7lyl+MXlefrnA/bx/Ti+FIkgHnx2qF4WfxyIyECgYBOWSJ9gVP3mjQiGfusYm2o/GYI1PQ6hoeSiS2HpfyDzhIIMnQr73d61zLQSWEsDEGtyD5YbH91urKek0RJmRag/CLzmWAPfpBv5k8TBUzv978yMeH/IyEqGlzG9HlxevntsGTxNIZpF95BnlSUAOsK6qeqop91UZoa5+QfizT9JQ==
publicKey=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmKJn3II4xmoOMJXwIfaCYa6x3FR5qhyjqnSs2z9T+/AKOsjcXeZCxgUVL1jcwdK0wX6gaYPltxCkR+UbxFvn8IyrZFtXP2DkXjsZ8f64D83nGeUSP71rdGdZy94suLEmLxfmHBRVTbfr7+sTl4SIQlCykLO8SSmS/E26vfybfXE4eQGQD5b9iZNSfDHblPhRTvpc0ZfYIYUPQWYWloa0RAjXifab2EKGvU6aP2NogXtv2v4qmGyPyu+elsQnlG1z89wQx04zFaVsPbdTOlgCwQoTNjmLSedyXKtpr0kwj8aMOpvcEB983m4RovRt1+1h6YbFeSw49WwIDAQAB
alipayPublicKey=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAq4fn/Ipm//nKVhpH+7TPSETJsbh03NCeA8sfAjxZa3/SKykbezsVhwYMxY9xXtVkjbedPQhkTmiWC4gaMwZd+rspmTRt+xLiC9aTuLtslLs+yhFdnTjm17hkFuHHZDkwFLCEbGmCyOBMJivRoKFdNtqDD4zmbxnq7GjVf0rbxEsUWeqo4MBiZfgMzxF05QE17KOlRQG+gIV2PXfVKftkkMciTKt6Pp1JiNvxqomxTMpsuCBHrOGo6LrohCsA46dNyJ1KzX/MJS5+ZKVHl9WD6oXcgBoC6UqGkH23YZ+EUBfPwfhnfT16Pxbot8P8o6i90UXDgWJVLbZa9vYwIDAQAB
#内网穿刺路径
domain=http://rngnu3.natappfree.cc
# 服务器异步通知界面,公网能访问,不能带查询参数,域名采用 NatApp 随机生成,每次修改
notifyUrl=${domain}/api/ego/tradePayNotify
# 页面跳转同步通知页面路径,规则同上
returnUrl=${domain}/api/ego/tradePayReturn
# 签名类型: RSA->SHA1withRsa,RSA2->SHA256withRsa
signType=RSA2
# 当面付最大查询次数和查询间隔(毫秒)
maxQueryRetry=5
queryDuration=5000
# 当面付最大撤销次数和撤销间隔(毫秒)
maxCancelRetry=3
cancelDuration=2000
# 交易保障线程第一次调度延迟和调度间隔(秒)
heartbeatDelay=5
heartbeatDuration=900
#编码
charset=UTF-8
format=json
Alipay的配置类
package com.hqyj.config;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AlipayConfig {
@Autowired
private AlipayConfigBean alipayConfigBean;
@Bean
public AlipayClient alipayClient () {
return new DefaultAlipayClient(
alipayConfigBean.getAlipayGateway(),
alipayConfigBean.getAppId(),
alipayConfigBean.getPrivateKey(),
alipayConfigBean.getFormat(),
alipayConfigBean.getCharset(),
alipayConfigBean.getAlipayPublicKey(),
alipayConfigBean.getSignType());
}
}
读取配置文件的类alipayConfig.properties(我这里没有使用注解生成set/get方法,效果一样)
package com.hqyj.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;
/**
*@describe 沙箱配置类引入
*@author wzk
*/
@Component
@PropertySource("alipayConfig.properties")
public class AlipayConfigBean {
@Value("${alipayGateway}")
private String alipayGateway;
@Value("${mcloudApi}")
private String mcloudApi;
@Value("${pid}")
private String pid;
@Value("${appId}")
private String appId;
@Value("${privateKey}")
private String privateKey;
@Value("${publicKey}")
private String publicKey;
@Value("${alipayPublicKey}")
private String alipayPublicKey;
@Value("${notifyUrl}")
private String notifyUrl;
@Value("${returnUrl}")
private String returnUrl;
@Value("${signType}")
private String signType;
@Value("${maxQueryRetry}")
private int maxQueryRetry;
@Value("${queryDuration}")
private int queryDuration;
@Value("${maxCancelRetry}")
private int maxCancelRetry;
@Value("${cancelDuration}")
private int cancelDuration;
@Value("${heartbeatDelay}")
private int heartbeatDelay;
@Value("${heartbeatDuration}")
private int heartbeatDuration;
@Value("${charset}")
private String charset;
@Value("${format}")
private String format;
// 自行添加 get、set 方法
public String getAlipayGateway() {
return alipayGateway;
}
public void setAlipayGateway(String alipayGateway) {
this.alipayGateway = alipayGateway;
}
public String getMcloudApi() {
return mcloudApi;
}
public void setMcloudApi(String mcloudApi) {
this.mcloudApi = mcloudApi;
}
public String getPid() {
return pid;
}
public void setPid(String pid) {
this.pid = pid;
}
public String getAppId() {
return appId;
}
public void setAppId(String appId) {
this.appId = appId;
}
public String getPrivateKey() {
return privateKey;
}
public void setPrivateKey(String privateKey) {
this.privateKey = privateKey;
}
public String getPublicKey() {
return publicKey;
}
public void setPublicKey(String publicKey) {
this.publicKey = publicKey;
}
public String getAlipayPublicKey() {
return alipayPublicKey;
}
public void setAlipayPublicKey(String alipayPublicKey) {
this.alipayPublicKey = alipayPublicKey;
}
public String getNotifyUrl() {
return notifyUrl;
}
public void setNotifyUrl(String notifyUrl) {
this.notifyUrl = notifyUrl;
}
public String getReturnUrl() {
return returnUrl;
}
public void setReturnUrl(String returnUrl) {
this.returnUrl = returnUrl;
}
public String getSignType() {
return signType;
}
public void setSignType(String signType) {
this.signType = signType;
}
public int getMaxQueryRetry() {
return maxQueryRetry;
}
public void setMaxQueryRetry(int maxQueryRetry) {
this.maxQueryRetry = maxQueryRetry;
}
public int getQueryDuration() {
return queryDuration;
}
public void setQueryDuration(int queryDuration) {
this.queryDuration = queryDuration;
}
public int getMaxCancelRetry() {
return maxCancelRetry;
}
public void setMaxCancelRetry(int maxCancelRetry) {
this.maxCancelRetry = maxCancelRetry;
}
public int getCancelDuration() {
return cancelDuration;
}
public void setCancelDuration(int cancelDuration) {
this.cancelDuration = cancelDuration;
}
public int getHeartbeatDelay() {
return heartbeatDelay;
}
public void setHeartbeatDelay(int heartbeatDelay) {
this.heartbeatDelay = heartbeatDelay;
}
public int getHeartbeatDuration() {
return heartbeatDuration;
}
public void setHeartbeatDuration(int heartbeatDuration) {
this.heartbeatDuration = heartbeatDuration;
}
public String getCharset() {
return charset;
}
public void setCharset(String charset) {
this.charset = charset;
}
public String getFormat() {
return format;
}
public void setFormat(String format) {
this.format = format;
}
}
Alipay控制层代码(这里我们不进行跳转,使用tradePayQr方法返回支付链接,同时使用异步验签)
package com.hqyj.controller;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.hqyj.common.vo.Result;
import com.hqyj.config.AlipayConfigBean;
import com.hqyj.ego.Alipay;
import com.hqyj.service.AlipayService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
@Controller
@RequestMapping("/api/ego")
public class AlipayController {
@Autowired
private AlipayConfigBean alipayConfigBean;
@Autowired
private AlipayService alipayService;
/**
* 127.0.0.1/api/alipay/tradePayPage ---- post
*/
@PostMapping(value = "/tradePayPage", consumes = "application/x-www-form-urlencoded")
public void tradePayPage(HttpServletResponse response, @ModelAttribute Alipay alipay) {
response.setContentType("text/html;charset=" + alipayConfigBean.getCharset());
PrintWriter pw = null;
try {
pw = response.getWriter();
pw.write(alipayService.tradePayPage(alipay));
pw.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (pw != null) {
pw.close();
}
}
}
/**
* 127.0.0.1/api/alipay/tradePayQr ---- post
* 返回支付地址
* {"outTradeNo":"ORDER_1617684756223", "totalAmount":4.44, "subject":"川A44444 停车缴费", "body":"停车无忧"}
*/
@PostMapping(value = "/tradePayQr", consumes = "application/json")
@ResponseBody
public String tradePayQr(@RequestBody Alipay alipay) {
return alipayService.tradePayQr(alipay);
}
/**
* http://k2kkxw.natappfree.cc/api/ego/tradePayNotify ---- post
* - 该地址为公网地址,测试时使用 NotApp 内网穿刺模拟
*/
@PostMapping("/tradePayNotify")
@ResponseBody
public void tradePayNotify(HttpServletRequest request) {
alipayService.tradePayNotify(request);
}
/**
* http://k2kkxw.natappfree.cc/api/alipay/tradePayReturn ---- post
* - 该地址为公网地址,测试时使用 NotApp 内网穿刺模拟
*/
@GetMapping("/tradePayReturn")
@ResponseBody
public Result<Object> tradePayReturn(HttpServletRequest request) throws JsonProcessingException {
return alipayService.tradePayReturn(request);
}
}
service层代码实现(这里就不写出接口了,只有实现类,自己创建一下接口)
package com.hqyj.service.impl;
import com.alipay.api.AlipayClient;
import com.alipay.api.internal.util.AlipaySignature;
import com.alipay.api.request.AlipayTradePagePayRequest;
import com.alipay.api.request.AlipayTradePrecreateRequest;
import com.alipay.api.response.AlipayTradePrecreateResponse;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.hqyj.common.vo.Result;
import com.hqyj.config.AlipayConfigBean;
import com.hqyj.controller.WebSocketController;
import com.hqyj.ego.Alipay;
import com.hqyj.service.AlipayService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
@Service
public class AlipayServiceImpl implements AlipayService {
// private final static Logger LOGGER = (Logger) LoggerFactory.getLogger(AlipayServiceImpl.class);
@Autowired
private AlipayConfigBean alipayConfigBean;
@Autowired
private AlipayClient alipayClient;
@Autowired
private WebSocketController webSocketController;
@Override
public String tradePayPage(Alipay alipay) {
// LOGGER.debug("==== 请求支付宝扫码界面 ====");
try {
// 构造请求参数 Json 格式
Map<String, Object> map = new HashMap<>();
map.put("out_trade_no", alipay.getOutTradeNo());
map.put("product_code", "FAST_INSTANT_TRADE_PAY");
map.put("total_amount", alipay.getTotalAmount());
map.put("subject", alipay.getSubject());
map.put("body", alipay.getBody());
ObjectMapper objectMapper = new ObjectMapper();
String alipayJson = objectMapper.writeValueAsString(map);
// LOGGER.debug(alipayJson);
// 设置支付内容,发送请求,返回结果
AlipayTradePagePayRequest alipayRequest = new AlipayTradePagePayRequest();
//设置通知返回的url地址
alipayRequest.setReturnUrl(alipayConfigBean.getReturnUrl());
//设置通知返回地址
alipayRequest.setNotifyUrl(alipayConfigBean.getNotifyUrl());
alipayRequest.setBizContent(alipayJson);
//获取请求结果alipayRequest为参数,返回HTML页面并且包含订单信息信息
return alipayClient.pageExecute(alipayRequest).getBody();
} catch (Exception e) {
e.printStackTrace();
// LOGGER.debug(e.getMessage());
}
return "";
}
/**
*@describe 获取支付二维码,进行支付
*@author wzk
*/
@Override
public String tradePayQr(Alipay alipay) {
// LOGGER.debug("==== 请求支付宝支付二维码 ====");
try {
// 构造请求参数 Json 格式
Map<String, Object> map = new HashMap<>();
map.put("out_trade_no", alipay.getOutTradeNo()); //订单编号
map.put("total_amount", alipay.getTotalAmount()); //总金额
map.put("subject", alipay.getSubject()); //主题(商品名称)
map.put("body", alipay.getBody()); //订单描述
ObjectMapper objectMapper = new ObjectMapper(); //使用ObjectMapper类转换map变为json字符串
String alipayJson = objectMapper.writeValueAsString(map);
// LOGGER.debug(alipayJson);
// 设置支付内容,发送请求,返回结果
//1.创建字符宝请求信息
AlipayTradePrecreateRequest alipayRequest = new AlipayTradePrecreateRequest();
//设置返回的url
alipayRequest.setReturnUrl(alipayConfigBean.getReturnUrl());
//设置通知的url
alipayRequest.setNotifyUrl(alipayConfigBean.getNotifyUrl());
//设置订单信息
alipayRequest.setBizContent(alipayJson);
//2.构造支付响应信息
AlipayTradePrecreateResponse alipayResponse = alipayClient.execute(alipayRequest);
// LOGGER.debug(String.format("%s-%s-%s-%s", alipayResponse.getCode(), alipayResponse.getMsg(),
// alipayResponse.getSubCode(), alipayResponse.getSubMsg()));
//返回给前端一个支付链接
if (alipayResponse.isSuccess()) {
return alipayResponse.getQrCode();
}
} catch (Exception e) {
e.printStackTrace();
// LOGGER.debug(e.getMessage());
}
return "";
}
@Override
public void tradePayNotify(HttpServletRequest request) {
// LOGGER.debug("==== 支付异步回调,验签 ====");
Result<Object> result = null;
try {
verifySignature(request);
result = new Result<>(200, "Pay success.");
ObjectMapper objectMapper = new ObjectMapper();
// 调用 WebSocket 通知页面扫码结果
webSocketController.sendMessage(objectMapper.writeValueAsString(result));
} catch (Exception e) {
e.printStackTrace();
// LOGGER.debug(e.getMessage());
}
}
@Override
public Result<Object> tradePayReturn(HttpServletRequest request) {
// LOGGER.debug("==== 支付同步回调,验签 ====");
boolean signVerified = false;
Result<Object> result = null;
try {
signVerified = verifySignature(request);
if (signVerified) {
result = new Result<>(200, "Pay success.");
} else {
result = new Result<>(200, "Pay Failed.");
}
ObjectMapper objectMapper = new ObjectMapper();
// 调用 WebSocket 通知页面扫码结果
webSocketController.sendMessage(objectMapper.writeValueAsString(result));
} catch (Exception e) {
e.printStackTrace();
// LOGGER.debug(e.getMessage());
}
return result;
}
/**
* - 验签 && 业务处理
*/
@SuppressWarnings("unused")
private boolean verifySignature (HttpServletRequest request) throws Exception {
// 获取支付宝反馈信息
Map<String,String> params = new HashMap<String,String>();
Map<String,String[]> requestParams = request.getParameterMap();
for (Iterator<String> iter = requestParams.keySet().iterator(); iter.hasNext();) {
String name = (String) iter.next();
String[] values = (String[]) requestParams.get(name);
String valueStr = "";
for (int i = 0; i < values.length; i++) {
valueStr = (i == values.length - 1) ? valueStr + values[i]
: valueStr + values[i] + ",";
}
valueStr = new String(valueStr.getBytes("ISO-8859-1"), "utf-8");
params.put(name, valueStr);
}
// LOGGER.debug("params: " + params);
// SDK 验签
boolean signVerified = AlipaySignature.rsaCheckV1(
params,
alipayConfigBean.getAlipayPublicKey(),
alipayConfigBean.getCharset(),
alipayConfigBean.getSignType());
/**
* - 实际验证过程建议商户务必添加以下校验
* - app_id 是否为该商户本身
* - out_trade_no 是否为商户系统中创建的订单号
* - total_amount 是否确实为该订单的实际金额
* - seller_id 或者 seller_email 是否为 out_trade_no 这笔单据的对应的操作方
*/
if (signVerified) {
// LOGGER.debug("==== 验签成功 ====");
// 商户订单号
String outTradeNo = new String(request.getParameter("out_trade_no").getBytes("ISO-8859-1"),"UTF-8");
// 支付宝交易号
String tradeNo = new String(request.getParameter("trade_no").getBytes("ISO-8859-1"),"UTF-8");
// 处理业务逻辑
} else {
// LOGGER.debug("==== 验签失败 ====");
}
return signVerified;
}
}
WebSocket配置
什么是WebSocket 可以参考我的另一篇博客:
导入依赖
<!-- websocket --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency>
websocket配置类
package com.hqyj.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
@Configuration
public class WebSocketConfig {
/**
* - 自动注册使用 @ServerEndpoint 注解声明的 WebSocket Endpoint
* - 可理解为将之注册为控制器
*/
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
websocket控制层代码
package com.hqyj.controller; /**
* @Description: Web Socket Controller
* - 每一个 WebSocket 连接维持一个 Session 对象,用于服务器向客户端发送信息
* - 我们可以将 User-Session 装到 ConcurrentHashMap 中,给某一个客户端发送信息或群发消息,此案列使用群发信息
*/
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.CopyOnWriteArraySet;
@ServerEndpoint("/api/webSocket")
@Component
public class WebSocketController {
// private final static Logger LOGGER = LoggerFactory.getLogger(WebSocketController.class);
private static CopyOnWriteArraySet<Session> sessions = new CopyOnWriteArraySet<Session>();
/**
* - 创建连接
* - 在此使用 @PathParam 接受 Path 参数
*/
@OnOpen
public void onOpen(Session session) {
sessions.add(session);
// LOGGER.debug(String.format("新建连接,连接总数 %d。", sessions.size()));
}
// 关闭连接
@OnClose
public void onClose(Session session) {
sessions.remove(session);
// LOGGER.debug(String.format("断开连接,连接总数 %d。", sessions.size()));
}
// 发生错误
@OnError
public void onError(Throwable throwable){
// LOGGER.debug("发生错误");
throwable.printStackTrace();
}
// 接受客户端消息
@OnMessage
public void onMessage(String message) {
// LOGGER.debug(String.format("收到消息,%s,连接总数 %d。", message, sessions.size()));
sendMessage("客户端,你好!消息已经收到,现在群发消息……");
}
// 向客户端群发信息
public void sendMessage(String message) {
// LOGGER.debug(String.format("广播消息,%s,连接总数 %d。", message, sessions.size()));
for (Session session : sessions) {
try {
session.getBasicRemote().sendText(message);
} catch (IOException e) {
e.printStackTrace();
// LOGGER.debug(e.getMessage());
}
}
}
}
natapp(配网穿透)配置
什么是内网穿透:内网,也称为局域网,就是路由器搭建的网络,称为内网,比如需要访问别的站点,就要是公网。内网穿透的实质是利用路由器上的NAT 系统。NAT 是一种将私有(保留)地址转化为合法IP地址的转换技术,它被广泛应用于各种类型 Internet 接入方式和各种类型的网络中。NAT可以完成重用地址,并且对于内部的网络结构可以实现对外隐蔽。
简单理解:就是本机不在统一个局域网内,公网想要访问局域网的地址,就需要进行内网穿透。而支付宝回调的接口就是使用的公网地址,所以需要内网穿透工具继续突破,使支付宝回调的请求可以访问我们局域网的请求地址。
举例:
A在家上网,B也在家上网。
有一款局域网游戏,AB想要一起玩。由于是局域网游戏,那么就需要A或者B其中一位,穿透广域网,去到另外一位的家里的局域网,成为局域网的一员。我们称这种行为叫内网穿透。
具体实现方法,就是在双方,局域网访问外网的路由上,设置NAT,当访问路由虚拟的身份的时候,路由就会转发到外网,去到对方的路由,而对方的路由,也虚拟一个身份,进行信息的对接收发。
再简单一点就是,两位家里各开一扇门,建一条专用的路连接这两扇门,连接你家和他家。不过这扇门有妖术,会让你自动化妆为对方家人。
官网地址:NATAPP-内网穿透 基于ngrok的国内高速内网映射工具
点击登录后进行实名验证,点击购买通道的免费通道
点击过后直接修改你当点想要方位的本地接口,购买即可
购买成功之后,复制authtoken,我们需要使用
也可以点击配置按钮配置通道(我这里配置的是8888端口号,如果项目在本机就可以不用修改本机地址)
点击下载客户端下载你当前电脑的版本软件,解压
点击natapp.exe 打开输入 : natapp -authtoken=上面需要复制的authtoken 回车运行
出现这个页面表示运行成功 ,复制我打码的路径(http开始->后面不用)取Alipay配置文件中替换内网穿透的地址。后端我们就配置完成
前端:
//块引用
<script>
// 创建websocket let webSocket = null; import QRCode from 'qrcode'; </script>
支付模态框
<!-- 支付模态框 -->
<div>
<el-dialog :visible.sync="showModal" width="400px" :before-close="handleClose">
<div class="payment-dialog-content">
<p class="amount-text">您需要支付的金额为: {{alipay.totalAmount}} 元</p>
<canvas ref="canvas" class="qrcode-canvas"></canvas>
<p class="scan-text">请使用支付宝扫描二维码完成支付</p>
</div>
</el-dialog>
</div>
这是点击方法,点击进行连接ws协议;请求后端的支付路径,返回支付链接,使用插件生成二维码,进行扫码支付,支付成功后接收信息判断是否支付成功。
//webSocket请求支付宝页面,并且响应
webSocketSubmit(){
//websoket进行连接
const _this = this
const target = "ws://localhost:8005/api/webSocket";
if ("WebSocket" in window) { //判断浏览器是否支持,创建websocket对象
webSocket = new WebSocket(target);
} else {
alert("浏览器不支持websocket");
}
webSocket.onerror = function () {
alert("发生错误连接失败");
};
webSocket.onopen = function () {
console.log("连接成功");
//生成订单号
_this.getOrderCode()
// 支付
_this.alipay.outTradeNo = _this.order.orderCode
_this.alipay.totalAmount = _this.order.total
_this.$Request.fetch_("/api/ego/tradePayQr","post",_this.alipay)
.then(res=>{
if(res !== "" && res != null){
_this.paymentLink = res
//后端返回的支付链接
const value = res;
// console.log(value)
//先调用弹窗,弹窗内容才会被挂载
_this.showModal= true
// 获取canvas标签的DOM对象
_this.qrCoder(value);
}
// console.log("数据:",res)
})
.catch(error=>{
_this.$message.error(error)
})
};
//响应
webSocket.onmessage = (res) => {
//转换成对象
const result = JSON.parse(res.data)
// console.log(result)
if(result.status === 200){
_this.$message.success("支付成功")
_this.order.status = "未发货"
//提交订单
_this.insertOrder();
//删除购物车的内容
_this.deleteCart();
//关闭webSocket资源
webSocket.close();
//跳转到结算完成界面
_this.$router.replace("/ego/alipay")
}
};
webSocket.onclose = function () {
// this.sendMessage("Loc MSG:关闭连接");
console.log("关闭连接");
};
window.onbeforeunload = function () {
webSocket.close();
};
},