2022年10月

与G银行对接时用到的

<?php
/**
 * Describe:
 * Author: liziyu
 * Date: 2022/10/31 11:10 AM
 */

namespace App\encrypt;

class MD5WithRSA
{
    /**
     * 利用约定数据和私钥生成数字签名
     * @param $data 待签数据
     * @return String 返回签名
     */
    public function sign($data='')
    {
        if (empty($data))
        {
            return false;
        }
        $private_key="MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgE==";
        $private_key = chunk_split($private_key, 64, "\n");
        $private_key = "-----BEGIN PRIVATE KEY-----\n$private_key-----END PRIVATE KEY-----";

        if (empty($private_key))
        {
            echo "私钥为空";
            return false;
        }
        // 生成密钥资源id
        $private_key_resource_id = openssl_get_privatekey($private_key);
        if (empty($private_key_resource_id))
        {
            echo "私钥资源id为空";
            return false;
        }

        $verify = openssl_sign($data, $signature, $private_key_resource_id, OPENSSL_ALGO_MD5);
        openssl_free_key($private_key_resource_id);
        // 二次Base64编码
        return base64_encode(base64_encode($signature));
    }

    /**
     * 利用公钥和数字签名以及约定数据验证合法性
     * @param string $data 待验证数据
     * @param string $signature 数字签名
     * @return  -1 验证错误;0 验证失败;1 验证成功
     */
    public function isValid($data='', $signature='')
    {
        if (empty($data) || empty($signature))
        {
            return false;
        }

        $public_key ="MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB";
        $public_key = chunk_split($public_key, 64, "\n");
        $public_key = "-----BEGIN PUBLIC KEY-----\n$public_key-----END PUBLIC KEY-----";

        if (empty($public_key))
        {
            echo "公钥为空";
            return false;
        }
        // 生成密钥资源id
        $public_key_resource_id = openssl_get_publickey($public_key);
        if (empty($public_key_resource_id))
        {
            echo "公钥资源id为空";
            return false;
        }

        // 对签名进行二次Base64解码
        $signature=base64_decode(base64_decode($signature));
        $ret = openssl_verify($data, $signature, $public_key_resource_id, OPENSSL_ALGO_MD5);
        openssl_pkey_free($public_key_resource_id);
        return $ret;
    }
}

步骤一:

先检测本机是否有gmp扩展在mamp下,命令如下:

/Applications/MAMP/bin/php/php8.0.8/bin/php -i | grep gmp

如果没有此扩展,此时什么也不会出现,如果已经成功安装了扩展,将会出现如下所示的提示:

PWD => /Applications/MAMP/bin/php/php8.0.8/include/php/ext/gmp
$_SERVER['PWD'] =>
/Applications/MAMP/bin/php/php8.0.8/include/php/ext/gmp $_ENV['PWD']
=> /Applications/MAMP/bin/php/php8.0.8/include/php/ext/gmp

步骤二:

php官网下载其源码包php-8.0.8.tar.gz
https://www.php.net/releases/

注意:一定要与你的现在版本一致辞,必须,必须,必须!

可以下载到任意目录,下载完成以后,解压。找到目录.
111.png

步骤三:

将目录/Users/liziyu/Downloads/php-8.0.8/ext/,将gmp目录整拷贝到/Applications/MAMP/bin/php/php8.0.8/include/php/ext目录下,如图:
222.png

步骤四:

同时进入该目录下:

cd /Applications/MAMP/bin/php/php8.0.8/include/php/ext/gmp

执行命令:

/Applications/MAMP/bin/php/php8.0.8/bin/phpize

然后执行:

./configure --with-php-config=/Applications/MAMP/bin/php/php8.0.8/bin/php-config

再执行:

make

最后:

make install

3333.png

当出现如下图所示的提示时说明已经安装成功了。
此时重启php,然后执行

php -m 

成功的话会出现如下图的提示:
444.png

如果还没有此提示的话,则进入php的配置文件ini去修改,增加下面一行代码:

extension=gmp.so

再重启php查看。即可

本文参考:https://mycodde.blogspot.com/2020/01/install-php-gmp-extension-in-mamp-2020.html

<?php

namespace process;

use GuzzleHttp\Client;
use Workerman\Crontab\Crontab;

class TaskSocial
{
    private $domain = '';
    public function onWorkerStart()
    {
        // 异步内容审核 每分钟执行一次
        new Crontab('0 */1 * * * *', function () {
            $client = new Client();
            $response = $client->get($this->domain . '/api_v5/crontab_api/crontab_audit');
            echo "异步内容审核 " . date('Y-m-d H:i:s') . "\n";
            echo $response->getBody() . "\n";
            //清除所有过期的心跳 统计等级 每分钟一次
            $response = $client->get($this->domain . '/api/crontab_api/service_crontab');
            echo "清除所有过期的心跳 统计等级 " . date('Y-m-d H:i:s') . "\n";
            echo $response->getBody() . "\n";
        });

        // 每5秒执行一次 自动打招呼
        new Crontab('*/5 * * * * *', function () {
            $client = new Client();
            $response = $client->get($this->domain . '/api/crontab_api/service_see_hi_crontab');
            echo "自动打招呼 " . date('Y-m-d H:i:s') . "\n";
            echo $response->getBody() . "\n";
        });

        // 财务记录 代理数据统计 每日统计一次
        new Crontab('50 0 * * *', function () {
            $client = new Client();
            $response = $client->get(($this->domain . '/api_v5/crontab_api/crontab_finance'));
            echo "财务记录 " . date('Y-m-d H:i:s') . "\n";
            echo $response->getBody() . "\n";
            $response = $client->get(($this->domain . '/api/crontab_api/add_channel_users'));
            echo "代理数据统计 " . date('Y-m-d H:i:s') . "\n";
            echo $response->getBody() . "\n";
            $response = $client->get(($this->domain . '/api/crontab_api/business_day'));
            echo "获取昨天的营业-每日凌晨统计 " . date('Y-m-d H:i:s') . "\n";
            echo $response->getBody() . "\n";
            // 聊天记录
            $response = $client->get(($this->domain . '/api_v5/crontab_api/chat_log'));
            echo "聊天记录保存文件 " . date('Y-m-d H:i:s') . "\n";
            echo $response->getBody() . "\n";
        });
    }
}

https://www.workerman.net/q/9516

HTML

<form class="layui-form" method="">
<input type="hidden" name="venue_info_id" value="1">
<div class="layui-row" style="padding-top: 40px">
    <div class="layui-form-item text-right">
       <button class="layui-btn" type="button" lay-filter="doCreateEvent" lay-submit>
          <i class="layui-icon">&#xe605;</i>提交保存
       </button>
     </div>
</div>
</form>


JS

form.on('submit(doCreateEvent)', function(data){
    layer.confirm('确定添加预约?', {
        skin: 'layui-layer-admin',
        shade: .1
    }, function (i) {
        layer.close(i);
        var loadIndex = layer.load(2);
        $.post("/api/xxx", data.field, function (res) {
            layer.close(loadIndex);
            if (200 === res.code) {
                layer.msg(res.msg, {icon: 1});
                setTimeout(function (){
                    parent.location.reload();
                }, 2500)
            } else {
                layer.msg(res.msg, {icon: 2});
            }
        });
    });
});



这几天和业务方有个签名验签的的需求,对方使用Java对业务数据进行签名,我方使用PHP验签,使用SHA256withRSA算法签名验签,PHP和Java单独签名和验签都没问题,但是由Java签名的数据请求到PHP端时一直验签不通过。
分别对比了Java和PHP生成的待验签字符串和生成的签名,都是一致的,同时也确认过私钥和公钥是一对,但是一到PHP的验签方法openssl_verify()时就是通不过,是在不知道原因出在哪。请求大神们帮忙看看是什么原因,万分感激。

PHP代码

class LubanPay{
    /**
     * 字符串签名
     * @param string $resource 经过urlencode处理过的字符串
     * @return string
     */
    public function SignStrMessage(string $resource):string {
        $privateKey = openssl_get_privatekey($this->GetPrivateKey());
        $res        = openssl_sign($resource, $signature, $privateKey,OPENSSL_ALGO_SHA256);
        openssl_free_key($privateKey);
        if ($res) {
            return base64_encode($signature);
        }else {
            throw new \Exception("String Sign Failed",10004);
        }
    }
    /**
     * 字符串验签
     * @param string $resource
     * @param string $signature
     * @return bool
     */
    public function VerifyStrMessage(string $resource,string $signature,string $mch_public_key):bool {
        $signature  = base64_decode($signature);
        $publicKey  = openssl_get_publickey($this->GetPublicKey($mch_public_key));
        $res        = openssl_verify($resource, $signature, $publicKey,OPENSSL_ALGO_SHA256);
        openssl_free_key($publicKey);
        return $res===1?true:false;
    }

    /**
     * 获取平台私钥
     * @return string
     */
    private function GetPrivateKey():string {
        $privateKey     = Config::$luban_private_key;
        $privateKey     = chunk_split($privateKey, 64, "\n");
        $privateKey = "-----BEGIN RSA PRIVATE KEY-----\n$privateKey-----END RSA PRIVATE KEY-----\n";
        return $privateKey;
    }

    /**
     * 获取商户公钥
     * @param string $type
     * @return string
     */
    private function GetPublicKey($mer_public_key):string{
        $publicKey               = chunk_split($mer_public_key, 64, "\n");
        $publicKey = "-----BEGIN PUBLIC KEY-----\n$publicKey-----END PUBLIC KEY-----\n";
        return $publicKey;
    }

}

Java代码

public static String private_key = "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC4RPxDH2GdsIcyDq5ApUfLU0+Gst6aJZwjoqvVxxGSRTlKx10VZHHZ7goqrNSNWVK+WPbz6vCItSgHb1t3eRMkwPEaGcstvcf7krEGOH6CWYXe74OTbFet9Oq1Jdzz7UHNNeRyHO/JnNbVh8kPSqIL2368g+lII0u7zP4OWGw+qht75VY0vKI3dQdK2jFidkRHtEmV0+ui+9RVEtIlURYxkaAHWoUNWov8505cAc+pmKrUYOJf7Q50t+AlClIgngzF2yqT5HAKKYbQ9R6CgKvB9JcAXL0nXi2bExjOQsQa3SalLXxjeNKpjJKtytUAEVaehtA5PCi0VV1jZSGKpTxlAgMBAAECggEAGIJsf00UQdIyGVFkkgqp4vyAzmzKOPyZqQ/BBV1GFAuLFEwyMF882XzU81orp2VjIRhaOJVeSwC1g0+nfdun1TKonw0hPkNI70hSrX4kLZhUuxNmj9xQST4TXebcXcGICBCMAzWgG1P2K061Sohlx2f5kn+FLugq8Z7Rh/zw4OCrFeDZ0YJZKpNk1JwBp3arbSphlbw3ZzTF6g7kukfilXuWNnoqDf341H5rVUY7k4cbej8x6pc6Zrnla33VM5m37+5bXnGnE1Sx7PjVgn+2ZADIhTn8e94bLZacJofrF1kCcScO1TRH4eaqlyhiXsjMy0Cm4OxCY+Uydj3NtOFvHQKBgQD5Nsr83bfAuHCA3dEaclQoBbAD1Qiz3rQPvHh2Zt7WIzKJ4NWVnypp22eCYTgft/X7cXXF7D2yokfOCc9DMfLVOOZzcRSwifsakPWXWKAtcNZZW+1ffgObq4I4tNO9Llb6hIN3pcxf7yBtrMib9C/cqwUAQM3k5bsFTZgjCZI5qwKBgQC9SXsJel+FDFlT4jfda/9fEx8uSOP4jflSn83CLI7UQ9/utR5ldJbllwWwrS5k6nqgi5K9AzOhxxfQ3m6FGJRDzqwyfK05uvuM1CqRMpe5FyQKTpPodWJ3+8oIcluRNaWwuAp+FNWPiGSNnhksS7i54jJAOh9nIdB1aSseq9vyLwKBgFJENinW/wuNVwYTMy2pxAIaLop1TpQh1grDyng7aSADKnG9WIQ1sIiVNswhT6eY0IiaYaheXdeUHmPzdQnXeTPNvrUpBQ1p3wxcAdZeGTIm53tED03QiVxf93LErojqvSehisx6XMbmZywNN4PTzeDoS5RT0CPZei07+hbG2BBVAoGAW6HszAPPper6e18xyCD1+SKan59trO+d2N+/jdZgNmW9TCOl2Vt9iRt5B7Ruly/juUCYAqRAJHrrDpP/ULM7Yy/zsGUmvqHEEMLM8IlbZaDMM6kidRAOYSMlBL3Hkh40Xb5aZfrT/635b40vhoAJpwLXbLw2Y4i9D3mgBDMSQMUCgYBLkj6pnTvJtgR7xCAKaVoERYXmsDjP/gIhSrmnqfO8EZDheVoh+XbN5o3D27aBmWiWTwgJoMuF5GxEL2tSbQBvkT6smTrVBDF8Q8+tgBvP2CV0gwto+Spv5b9h7dNWk5UI0ZOWnlF1aI37UJBKmyp2kYCoDwnA1vtFpXncmaNHxw==";

public static void main(String[] args) throws UnsupportedEncodingException {
    test();
}


public static void test() throws UnsupportedEncodingException {
//构造待签名字符串  
    String string = "content=hello" + "&mch_id=1&method=pay&nonce_str=1&time_stamp=1";
    System.out.println("拼接字符串:" + string);
//加密字符串  
    String str = sign(string.getBytes("utf-8"));
    System.out.println("字符串加密:" + str);
    String authorization = "mch_id=1&method=pay&nonce_str=1&sign=" + URLEncoder.encode(str, "UTF-8") + "&time_stamp=1";
    System.out.println("请求头:" + authorization);
    System.out.println("请求结果:" + DemoCall.doPost("pay/api/v1", "hello", authorization));
}


/**
 * 将字节数组内信息使用签名进行加密
 *
 * @param message
 * @return
 */
public static String sign(byte[] message) {
    Signature sign;
    try {
        sign = Signature.getInstance("SHA256withRSA");
        sign.initSign(getPrivateKey());
        sign.update(message);
        return Base64.getEncoder().encodeToString(sign.sign());
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    } catch (SignatureException e) {
        e.printStackTrace();
    } catch (InvalidKeyException e) {
        e.printStackTrace();
    }
    return null;
}

/**
 * 获取私钥。
 *
 * @param filename 私钥文件路径 (required)
 * @return 私钥对象
 */
public static PrivateKey getPrivateKey() {
    String content = private_key;
    try {
        String privateKey = content.replace("-----BEGIN PRIVATE KEY-----", "")
                .replace("-----END PRIVATE KEY-----", "")
                .replaceAll("\\s+", "");
        KeyFactory kf = KeyFactory.getInstance("RSA");
        return kf.generatePrivate(
                new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey)));
    } catch (NoSuchAlgorithmException e) {
        throw new RuntimeException("当前Java环境不支持RSA", e);
    } catch (InvalidKeySpecException e) {
        throw new RuntimeException("无效的密钥格式");
    }
}

转载:https://segmentfault.com/q/1010000022260275

WXS

所有监听事件先在onload监听。
// pages/index/to_news/to_news.js  
var app = getApp();
var socketOpen = false;
var SocketTask = false;
var url = 'ws://192.168.0.120:7011';
Page({
  data: {
    inputValue: '',
    returnValue: '',
  },
  onLoad: function (options) {
  },
  onReady: function () {
    // 创建Socket
    SocketTask = wx.connectSocket({
      url: url,
      data: 'data',
      header: {
        'content-type': 'application/json'
      },
      method: 'post',
      success: function (res) {
        console.log('WebSocket连接创建', res)
      },
      fail: function (err) {
        wx.showToast({
          title: '网络异常!',
        })
        console.log(err)
      },
    })
    if (SocketTask) {
      SocketTask.onOpen(res => {
        console.log('监听 WebSocket 连接打开事件。', res)
      })
      SocketTask.onClose(onClose => {
        console.log('监听 WebSocket 连接关闭事件。', onClose)
      })
      SocketTask.onError(onError => {
        console.log('监听 WebSocket 错误。错误信息', onError)
      })
      SocketTask.onMessage(onMessage => {
        console.log('监听WebSocket接受到服务器的消息事件。服务器返回的消息', onMessage)
      })
    }
  },

  // 提交文字
  submitTo: function (e) {
    let that = this;
    that.data.allContentList.push({that.data.inputValue });
    that.setData({
      allContentList: that.data.allContentList
    })
    var data = {
      text: that.data.inputValue
    }
    if (socketOpen) {
      // 如果打开了socket就发送数据给服务器
      sendSocketMessage(data)
    }
  },
  bindKeyInput: function (e) {
    this.setData({
      inputValue: e.detail.value
    })
  },

  onHide: function () {
      SocketTask.close(function (close) {
        console.log('关闭 WebSocket 连接。', close)
      })
  },
})

//通过 WebSocket 连接发送数据,需要先 wx.connectSocket,并在 wx.onSocketOpen 回调之后才能发送。
function sendSocketMessage(data) {
  console.log('通过 WebSocket 连接发送数据')
  if (socketOpen) {
    SocketTask.send({data: JSON.stringify(data)
    }, function (res) {
      console.log('已发送', res)
    })
  } else {
    socketMsgQueue.push(msg)
  }
} 

WXML

 <input type="text" bindinput="bindKeyInput" value='{
   {inputValue}}' placeholder="" />
  <button bindtap="submitTo" class='user_input_text'>发送</button>

app.js

在app.js下添加三个函数openSocket(), closeSocket(),sendMessage(),在app初始化的onLunch函数里面调用openSocket(),这样子用户一进入小程序就会自动连接websocket
App({
      globalData: {
        socketStatus: 'closed',
      },
    onLaunch: function() {   
        var that = this;
        if (that.globalData.socketStatus === 'closed') {
              that.openSocket();
        }
    }
    openSocket() {
       //打开时的动作
        wx.onSocketOpen(() => {
          console.log('WebSocket 已连接')
          this.globalData.socketStatus = 'connected';
          this.sendMessage();
        })
        //断开时的动作
        wx.onSocketClose(() => {
          console.log('WebSocket 已断开')
          this.globalData.socketStatus = 'closed'
        })
        //报错时的动作
        wx.onSocketError(error => {
          console.error('socket error:', error)
        })
        // 监听服务器推送的消息
        wx.onSocketMessage(message => {
          //把JSONStr转为JSON
          message = message.data.replace(" ", "");
          if (typeof message != 'object') {
            message = message.replace(/\ufeff/g, ""); //重点
            var jj = JSON.parse(message);
            message = jj;
          }
          console.log("【websocket监听到消息】内容如下:");
          console.log(message);
        })
        // 打开信道
        wx.connectSocket({
          url: "ws://" + "localhost" + ":8888",
        })
      },
        
    //关闭信道
      closeSocket() {
        if (this.globalData.socketStatus === 'connected') {
          wx.closeSocket({
            success: () => {
              this.globalData.socketStatus = 'closed'
            }
          })
        }
      },
        
     //发送消息函数
      sendMessage() {
        if (this.globalData.socketStatus === 'connected') {
        //自定义的发给后台识别的参数 ,我这里发送的是name
          wx.sendSocketMessage({
            data: "{\"name\":\"" + wx.getStorageSync('openid') + "\"}"  
          })
        }
      },
})



websocket API

wx.connectSocket:创建一个 WebSocket 连接 ;
sotk.onOpen:监听 WebSocket 连接打开事件 ;
sotk.onClose:监听 WebSocket 连接关闭事件 ;
sotk.onError:监听 WebSocket 错误事件 ;
sotk.onMessage:监听
WebSocket 接受到服务器的消息事件 ;(重要:负责监听接收数据)
sotk.send:通过
WebSocket 连接发送数据 。(重要:负责发送数据)
var sotk = null;
var socketOpen = false;
var wsbasePath = "ws://开发者服务器 wss 接口地址/";

//开始webSocket
  webSocketStart(e){
    sotk = wx.connectSocket({
      url: wsbasePath,
      success: res => {
        console.log('小程序连接成功:', res);
      },
      fail: err => {
        console.log('出现错误啦!!' + err);
        wx.showToast({
          title: '网络异常!',
        })
      }
    })

    this.webSokcketMethods();

  },

//监听指令
  webSokcketMethods(e){
    let that = this;
    sotk.onOpen(res => {
      socketOpen = true;
      console.log('监听 WebSocket 连接打开事件。', res);
    })
    sotk.onClose(onClose => {
      console.log('监听 WebSocket 连接关闭事件。', onClose)
      socketOpen = false;
    })
    sotk.onError(onError => {
      console.log('监听 WebSocket 错误。错误信息', onError)
      socketOpen = false
    })

    sotk.onMessage(onMessage => {
      var data = JSON.parse(onMessage.data);
      console.log('监听WebSocket接受到服务器的消息事件。服务器返回的消息',data);
     
    })
   
  },

//发送消息
  sendSocketMessage(msg) {
    let that = this;
    if (socketOpen){
      console.log('通过 WebSocket 连接发送数据', JSON.stringify(msg))
      sotk.send({
        data: JSON.stringify(msg)
      }, function (res) {
        console.log('已发送', res)
      })
    }
    
  },
 //关闭连接
  closeWebsocket(str){
    if (socketOpen) {
      sotk.close(
        {
          code: "1000",
          reason: str,
          success: function () {
            console.log("成功关闭websocket连接");
          }
        }
      )
    }
  },

实现逻辑

1、通过onMessage监听收到的信息,接收到的信息会返回一个type值,通过type值判断接收到的数据是什么数据,之后分别进行处理、赋值即可;
2、通过sendSocketMessage发送数据,根据后端定义的字段发送请求,字段中也包含type值,告诉websocket是处理什么类型的值。
webSokcketMethods(e) {
    this.websocket.onMessage(onMessage => {
      console.log(this.data)
      var data = JSON.parse(onMessage.data);
      console.log('监听WebSocket接受到服务器的消息事件。服务器返回的消息', data);
      if (data.client_id) {
        this.setData({
          client_id: data.client_id
        })
      }
      if (data.msg_type === 'init') {
        this.joinLive()
      }
      if (data.msg_type === 'join') {
        wx.hideLoading()
        this.endtrain(data.msg_content)
        this.setData({
          is_join: true,
          lookNumber: data.online_num
        })
        if (data.message_list) {
          this.setData({
            commentList: data.message_list,
            toLast: 'item' + data.message_list.length,
            lookNumber: data.online_num,
            followNumber: data.subscribe_num
          })
        }

      }
      if (data.msg_type == 'message') {
        var commentItem = {
          name: data.from_name,
          content: data.content
        }
        var commentArr = this.data.commentList
        console.log(commentArr)
        commentArr.push(commentItem)
        this.setData({
          commentList: commentArr,
          toLast: 'item' + commentArr.length,
        })

        if (this.data.platform === 'android') {
          this.messageScroll()
        }
      }
      if (data.msg_type == 'subscribe') {
        this.setData({
          followNumber: data.subscribe_num
        })
        this.endtrain(data.msg_content)

      }

      if (data.msg_type == 'leave') {
        this.endtrain(data.msg_content)
      }

      if (data.msg_type == 'buy') {
        this.endtrain(data.msg_content)
      }
    })
  },
   // 加入直播间
  joinLive() {
    var data = {
      msg_type: 'join',
      room_id: this.data.liveRoomId,
      live_id: this.data.liveId,
      from_uid: this.data.anchorUid,
      client_id: this.data.client_id,
      is_anchor: true,
      from_name: this.data.anchorName
    }
    console.log('通过 WebSocket 连接发送数据', JSON.stringify(data))
    this.websocket.send({
      data: JSON.stringify(data),
      success: function (res) {
        console.log(res)
      },
      fail: function (err) {
        console.log(err)
      }
    })
  },
  // 商品上架
  goodsShelfOn(goodsId, goodsName, goodsPrice, defaultImage, status) {
    var data = {
      msg_type: 'on_off_shelf',
      room_id: this.data.liveRoomId,
      live_id: this.data.liveId,
      from_uid: this.data.anchorUid,
      client_id: this.data.client_id,
      from_name: this.data.anchorName,
      goods_id: goodsId,
      goods_name: goodsName,
      goods_price: goodsPrice,
      default_image: defaultImage,
      status: 'on'
    }
    console.log('通过 WebSocket 连接发送数据', JSON.stringify(data))
    this.websocket.send({
      data: JSON.stringify(data),
      success: function (res) {
        console.log(res)
      },
      fail: function (err) {
        console.log(err)
      }
    })
  }

心跳检测、断线重连

在实际应用中,需要不断判断websocket处于连接阶段。
wechat-websocket.js文件
export default class websocket {
  constructor({ heartCheck, isReconnection }) {
    // 是否连接
    this._isLogin = false;
    // 当前网络状态
    this._netWork = true;
    // 是否人为退出
    this._isClosed = false;
    // 心跳检测频率
    this._timeout = 3000;
    this._timeoutObj = null;
    // 当前重连次数
    this._connectNum = 0;
    // 心跳检测和断线重连开关,true为启用,false为关闭
    this._heartCheck = heartCheck;
    this._isReconnection = isReconnection;
    this._onSocketOpened();
  }
  // 心跳重置
  _reset() {
    clearTimeout(this._timeoutObj);
    return this;
  }
  // 心跳开始
  _start() {
    let _this = this;
    this._timeoutObj = setInterval(() => {
      wx.sendSocketMessage({
        // 心跳发送的信息应由前后端商量后决定
        data: JSON.stringify({
          "msg_type": 'ping'
        }),
        success(res) {
          console.log(res)
          console.log("发送心跳成功");
        },
        fail(err) {
          console.log(err)
          // wx.showToast({
          //   title: "websocket已断开",
          //   icon: 'none'
          // })
          _this._reset()
        }
      });
    }, this._timeout);
  }
  // 监听websocket连接关闭
  onSocketClosed(options) {
    wx.onSocketClose(err => {
      console.log('当前websocket连接已关闭,错误信息为:' + JSON.stringify(err));
      // 停止心跳连接
      if (this._heartCheck) {
        this._reset();
      }
      // 关闭已登录开关
      this._isLogin = false;
      // 检测是否是用户自己退出小程序
      console.log(this._isClosed)
      if (!this._isClosed) {
        // 进行重连
        if (this._isReconnection) {
          this._reConnect(options)
        }
      }
      
    })
  }
  // 检测网络变化
  onNetworkChange(options) {
    wx.onNetworkStatusChange(res => {
      console.log('当前网络状态:' + res.isConnected);
      if (!this._netWork) {
        this._isLogin = false;
        // 进行重连
        if (this._isReconnection) {
          this._reConnect(options)
        }
      }
    })
  }
  _onSocketOpened() {
    wx.onSocketOpen(res => {
      console.log('websocket已打开');
      // 打开已登录开关
      this._isLogin = true;
      // 发送心跳
      if (this._heartCheck) {
        this._reset()._start();
      }
      // 发送登录信息
      wx.sendSocketMessage({
        // 这里是第一次建立连接所发送的信息,应由前后端商量后决定
        data: JSON.stringify({
          "key": 'value'
        })
      })
      // 打开网络开关
      this._netWork = true;
    })
  }
  // 接收服务器返回的消息
  onReceivedMsg(callBack) {
    wx.onSocketMessage(msg => {
      if (typeof callBack == "function") {
        callBack(msg)
      } else {
        console.log('参数的类型必须为函数')
      }
    })
  }
 
  // 建立websocket连接
  initWebSocket(options) {
    let _this = this;
    if (this._isLogin) {
      console.log("您已经登录了");
    } else {
      console.log('未登录')
      // 检查网络
      wx.getNetworkType({
        success(result) {
          if (result.networkType != 'none') {
            console.log(result.networkType)
            // 开始建立连接
            wx.connectSocket({
              url: options.url,
              success(res) {
                if (typeof options.success == "function") {
                  options.success(res)
                } else {
                  console.log('参数的类型必须为函数')
                }
              },
              fail(err) {
                if (typeof options.fail == "function") {
                  options.fail(err)
                } else {
                  console.log('参数的类型必须为函数')
                }
              }
            })
          } else {
            console.log('网络已断开');
            _this._netWork = false;
            // 网络断开后显示model
            wx.showModal({
              title: '网络错误',
              content: '请重新打开网络',
              showCancel: false,
              success: function (res) {
                if (res.confirm) {
                  console.log('用户点击确定')
                }
              }
            })
          }
        }
      })
    }
  }
  // 发送websocket消息
  sendWebSocketMsg(options) {
    wx.sendSocketMessage({
      data: options.data,
      success(res) {
        if (typeof options.success == "function") {
          options.success(res)
        } else {
          console.log('参数的类型必须为函数')
        }
      },
      fail(err) {
        if (typeof options.fail == "function") {
          options.fail(err)
        } else {
          console.log('参数的类型必须为函数')
        }
      }
    })
  }
  // 重连方法,会根据时间频率越来越慢
  _reConnect(options) {
    let timer, _this = this;
    if (this._connectNum < 20) {
      timer = setTimeout(() => {
        this.initWebSocket(options)
      }, 3000)
      this._connectNum += 1;
    } else if (this._connectNum < 50) {
      timer = setTimeout(() => {
        this.initWebSocket(options)
      }, 10000)
      this._connectNum += 1;
    } else {
      timer = setTimeout(() => {
        this.initWebSocket(options)
      }, 450000)
      this._connectNum += 1;
    }
  }
  // 关闭websocket连接
  closeWebSocket(){
    wx.closeSocket();
    this._isClosed = true;
  }
}

微信官方文档

微信小程序心跳

微信websocket基础用法

转自:https://hehuiyun.github.io/2021/01/22/微信小程序websocket的用法/

webSocketInit () {
    const vm = this
    let token = ''
    let socketUrl = 'wss://websocket.test'
    // 建立 WebSocket 连接
    vm.socket = wx.connectSocket({
      url: socketUrl,
      header: {
        'content-type': 'application/json',
        'authorization': 'Bearer' + token,
        'x-wxapp-sockettype': 'ordertips'
      },
      success (res) {
        console.log('WebSocket 连接成功: ', res)
      },
      fail (err) {
        console.log('WebSocket 连接失败: ', err)
      }
    })
    // onOpen
    vm.socket.onOpen((ret) => {
      console.log('打开 WebSocket 连接')
    })
    // onMessage
    vm.socket.onMessage((ret) => {
      if (ret.data == 'pong') {
        return;
      }
      let data = JSON.parse(ret.data)
      wx.showToast({
        title: data.message,
        icon: 'none',
        duration: 3000
      })
    })
    // onError
    vm.socket.onError((err) => {
      console.log('WebSocket 连接失败:', err)
    })
    // onClose
    vm.socket.onClose((ret) => {
      console.log('断开 WebSocket 连接', ret)
    })
  },
  // send message
  send (msg) {
    const vm  = this
    vm.socket.send({
      data: msg,
      success (res) {
        console.log('WebSocket 消息发送成功', res)
      },
      fail (err) {
        console.log('WebSocket 消息发送失败', err)
      }
    })
  },
  // 心跳,由客户端发起
  ping () {
    const vm = this
    let times = 0
    // 每 10 秒钟由客户端发送一次心跳
    this.interval = setInterval(function () {
      if (vm.socket.readyState == 1) {
        vm.send('ping')
      } else if (vm.socket.readyState == 3) {
        times += 1
        // 超时重连,最多尝试 10 次
        if (times >= 10) {
          wx.showToast({
            title: 'WebSocket 连接已断开~',
            icon: 'none',
            duration: 3000
          })
          clearInterval(vm.interval)
        }
        vm.reconnect()
      }
    }, 10000)
  },
  // WebSocket 断线重连
  reconnect () {
    const vm = this
    vm.webSocketInit()
  },

HTML

<van-uploader file-list="{{ fileList }}" bind:after-read="uploadImage" accept="image" max-count="6" bind:delete="onDelImage" />

JS

  uploadImage(event) {
    const { file } = event.detail;
    // 当设置 mutiple 为 true 时, file 为数组格式,否则为对象格式
    wx.uploadFile({
      url: '后台真实upload地址', // 仅为示例,非真实的接口地址
      filePath: file.url,
      name: 'file',
      formData: { authorize: app.getToken() },
      success:(res) => { //这里要用箭头函数,否则报变量为定义的错
        // 上传完成需要更新 fileList
        const { fileList = [] } = this.data;
        const resData = JSON.parse(res.data);
        fileList.push({ ...file, url: resData.path });
        this.setData({ fileList });
      },
    });
  },

  //点击删除图片
  onDelImage(event) {
    let id = event.detail.index //能获取到对应的下标
    let fileList = this.data.fileList //这里是前端页面展示的数组
    fileList.splice(id, 1) //删除1张刚刚点击的那图
    this.setData({
      fileList: fileList, //在这里进行重新赋值  删除后 图片剩几张就相当于给后台传几张
    })
  },