WebSocket + Alipay支付宝沙箱支付

        思路:

                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();
                };
            },

  • 47
    点赞
  • 47
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值