精华内容
下载资源
问答
  • 微信小程序聊天功能(仿微信)- - -附效果图
    千次阅读
    2020-05-09 17:27:52

    效果图如下所示

    在这里插入图片描述
    这两天比较闲就写了个聊天功能,语音功能因为是在电脑上所以不能使用,图片最大只能上传5M所有很多功能没有完全展示,想看完整手机版的视频可以加
    QQ:1010753897 查看完整视频
    下载地址:https://download.csdn.net/download/qq_43764578/12404008

    有什么问题欢迎评论留言,我会及时回复你的

    更多相关内容
  • 主要介绍了微信小程序聊天功能的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
  • 今天给大家分享一下本人做小程序使用websocket的一点小经验,希望对大家有所帮助。 使用之前肯定首先要了解一下websocket是什么,简单来讲websocket就是客户端与服务器之间专门建立的一条特殊通道,请求只需要请求一...
  • (此图片来源于网络,如有侵权,请联系删除! ) 下面是完整代码,可以实现发送以后自动把屏幕的焦点放在最后一条信息\nclass='tab'> class='lan'>{{tabdata.title}} class='tent'> {{tabdata.attribute_attribute}}...
  • 本文实例为大家分享了微信小程序WebSocket实现聊天对话功能的具体代码,供大家参考,具体内容如下 js var app = getApp(); var socketOpen = false; var frameBuffer_Data, session, SocketTask; var url = 'ws://...
  • 项目背景:小程序中实现实时聊天功能 一、服务器域名配置 配置流程 配置参考URL:https://developers.weixin.qq.com/miniprogram/dev/api/api-network.html 二、nginx中配置反向代理加密websocket(wss) upstream ...
  • 完整的微信小程序实现 WebSocket 长链接聊天代码,包括聊天室界面的样式。可直接使用,有疑问可加微信13977284413询问。有时间的话我会第一时间回复。
  • uniapp实现使用微信小程序云数据,微信云开发环境进行实时聊天!!暂支持群聊天,暂支持群聊天,暂支持群聊天,暂支持群聊天,暂支持群聊天,暂支持群聊天
  • 主要为大家详细介绍了微信小程序websocket实现聊天功能,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
  • 三、使用APPID登录进去你的小程序;输入button按钮(必须是button),如果不想显示button按钮,可以将按钮设置成透明,大小和div一样大。 <view class="container"> <button class="kefu" open-type=...
    一、搜索微信公众平台:https://mp.weixin.qq.com/

    用绑定的小程序登录(千万不要进公众号平台)添加客服人员
    在这里插入图片描述

    二、找到APPID

    在这里插入图片描述

    三、使用APPID登录进去你的小程序;输入button按钮(必须是button),如果不想显示button按钮,可以将按钮设置成透明,大小和div一样大。

    在这里插入图片描述

    <view class="container">
      <button class="kefu"  open-type="contact">联系卖家</button>
    </view>
    
    四、必须进行真机测试,之后就可以测试了。用设置的客服扫码登录进去,就可以收到消息了,客服后台:https://mpkf.weixin.qq.com/cgi-bin/kfindex?token=1428780213

    在这里插入图片描述uniapp中没测试出来(待定)

    展开全文
  • 微信小程序-实现图灵机器人聊天功能
  • 主要为大家详细介绍了微信小程序实现聊天对话功能,可以发送文本、图片,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
  • 5.微信小程序聊天功能 WebSocket 实现发送文字,图片,语音以及WebSocket 常见问题解决方案 6.[微信小程序]聊天对话(文本,图片)的功能(完整代码附效果图) 如果有个性化的需要改造,修改样式就行,可以帮助修改。 ...
  • 微信小程序环信即时聊天
  • 在微信这个聊天工具里的小程序上实现聊天功能,总觉得很诧异,今天小编给大家带来了使用node.js实现微信小程序实时聊天功能,感兴趣的朋友一起看看吧
  • 最近做了一个微信小程序的即时通讯功能,之前我也做过node.js的websocket服务,不过是在web端应用的socket.io服务。小程序本身对http、websocket等连接均有诸多限制,所以这次项目选择了node.js自带的ws模块。 ...
  • 主要介绍了微信小程序 聊天室简单实现的相关资料,需要的朋友可以参考下
  • 一个简单的在线聊天功能演示;   免责声明:本站所有文章和图片均来自用户分享和网络收集,文章和图片版权归原作者及原出处所有,仅供学习与参考,请勿用于商业用途,如果损害了您的权利,请联系网站客服处理。
  • } 四、小程序端配置 登录mp.weixin.qq.com 开发=>开发管理=>开发设置,完成合法域名设置 五、小程序端代码 /pages/chat/chat.js const app = getApp() var websocket = require('../../utils/websocket.js');...

    一、宝塔安装Swoole环境

    二、自定义安装swoole环境

    mkdir /src
    
    cd /src
    
    # 下载
    wget https://pecl.php.net/get/swoole-4.4.4.tgz
    
    # 解压
    
    tar zxf swoole-4.4.4.tgz
    
    # 编译安装扩展
    
    # 进入目录
    
    cd swoole-4.4.4 
    
    # 执行phpize命令,产生出configure可执行文件
    
    /usr/bin/phpize  
    
    # 进行配置
    
    ./configure --with-php-config=/usr/bin/php-config   
    
    # 编译和安装
    
    make && make install 
    
    vi /etc/php.ini
    
    复制如下代码
    
    extension=swoole.so
    
    放到你所打开或新建的文件中即可,无需重启任何服务
    
    # 查看扩展是否安装成功
    
    php -m|grep swoole

     三、宝塔配置nginx反向代理

    upstream websocket{
     hash $remote_addr consistent;
     server 127.0.0.1:9501 weight=5 max_fails=3 fail_timeout=30s;
    }
    
    server {
     listen 80;
     server_name wss.51chow.com;
     rewrite ^(.*)$ https://$host$1 permanent;
    }
    
    server
    {
        listen 443 ssl;
        server_name wss.51chow.com;
        index index.php index.html index.htm default.php default.htm default.html;
        root /www/wwwroot/swoole_1909a;
        
        ssl_certificate /www/server/keys/7248556_wss.51chow.com.pem;
        ssl_certificate_key /www/server/keys/7248556_wss.51chow.com.key;
        ssl_session_timeout 5m;
        ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
        ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
        ssl_prefer_server_ciphers on;
        ssl_verify_client off;
       
       location / {
        	if (!-e $request_filename) {
        	#一级目录
        	rewrite ^(.*)$ /index.php?s=$1 last;
        	break;
        	}
        	#wss配置
        	client_max_body_size 100m;
        	proxy_redirect off;
        	proxy_set_header Host $host;# http请求的主机域名
        	proxy_set_header X-Real-IP $remote_addr;# 远程真实IP地址
        	proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;#反向代理之后转发之前的IP地址
        	proxy_read_timeout 604800s;#websocket心跳时间,默认是60s
        	proxy_http_version 1.1;
        	proxy_set_header Upgrade $http_upgrade;
        	proxy_set_header Connection "Upgrade";
        	
        	proxy_pass http://websocket;#反向代理转发地址
        }
        #SSL-START SSL相关配置,请勿删除或修改下一行带注释的404规则
        #error_page 404/404.html;
        #SSL-END
        
        #ERROR-PAGE-START  错误页配置,可以注释、删除或修改
        #error_page 404 /404.html;
        #error_page 502 /502.html;
        #ERROR-PAGE-END
        
        #PHP-INFO-START  PHP引用配置,可以注释或修改
        include enable-php-00.conf;
        #PHP-INFO-END
        
        #REWRITE-START URL重写规则引用,修改后将导致面板设置的伪静态规则失效
        include /www/server/panel/vhost/rewrite/wss.51chow.com.conf;
        #REWRITE-END
        
        #禁止访问的文件或目录
        location ~ ^/(\.user.ini|\.htaccess|\.git|\.svn|\.project|LICENSE|README.md)
        {
            return 404;
        }
        
        #一键申请SSL证书验证目录相关设置
        location ~ \.well-known{
            allow all;
        }
        
        location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$
        {
            expires      30d;
            error_log /dev/null;
            access_log /dev/null;
        }
        location ~ .*\.(js|css)?$
        {
            expires      12h;
            error_log /dev/null;
            access_log /dev/null;
        }
    	access_log  /www/wwwlogs/wss.51chow.com.log;
        error_log  /www/wwwlogs/wss.51chow.com.error.log;
    }

    四、小程序端配置

    登录mp.weixin.qq.com 

    开发=>开发管理=>开发设置,完成合法域名设置

    五、小程序端代码

    /pages/chat/chat.js

    const app = getApp()
    var websocket = require('../../utils/websocket.js');
    var utils = require('../../utils/util.js');
    
    import {HTTP_REQUEST_URL, HEADER, USER_ID, OPEN_ID} from "../../utils/config.js"
    
    Page({
      /**
      * 页面的初始数据
      */
      data: {
        newslist: [],
        userInfo: {},
        scrollTop: 0,
        increase: false,//图片添加区域隐藏
        aniStyle: true,//动画效果
        message: "",
        previewImgList: []
      },
      /**
      * 生命周期函数--监听页面加载
      */
      onLoad: function () {
        var that = this
        if (app.globalData.userInfo) {
          this.setData({
            userInfo: app.globalData.userInfo
          })
        }
        //调通接口
        websocket.connect(this.data.userInfo, function (res) {
          console.log(res)
          var list = []
          list = that.data.newslist
          let data = JSON.parse(res.data)
          if(data.type == 'open') {
            list = data.content
          } else {
            list.push(data)
          }
          that.setData({
            newslist: list
          })
          that.bottom()
        })
      },
      // 页面卸载
      onUnload() {
        wx.closeSocket();
        wx.showToast({
          title: '连接已断开~',
          icon: "none",
          duration: 2000
        })
      },
      //事件处理函数
      send: function () {
        var flag = this
        let uid = wx.getStorageSync(USER_ID)
        if (this.data.message.trim() == "") {
          wx.showToast({
            title: '消息不能为空哦~',
            icon: "none",
            duration: 2000
          })
        } else {
          setTimeout(function () {
            flag.setData({
              increase: false
            })
          }, 500)
    
          let msg = {
              content:this.data.message,
              date:utils.formatTime(new Date()),
              type:'ask',//咨询
              fid:uid,
              tid:100,
              avatarUrl:this.data.userInfo.avatar,
              nickName:this.data.userInfo.nickname
          };
          websocket.send(JSON.stringify(msg))
          /*
          websocket.send('{ "content": "' + this.data.message + '", "date": "' + utils.formatTime(new Date()) + '","type":"text", "nickName": "' + this.data.userInfo.nickName + '", "avatarUrl": "' + this.data.userInfo.avatarUrl + '" }')
          */
    
          this.bottom()
        }
      },
      //监听input值的改变
      bindChange(res) {
        this.setData({
          message: res.detail.value
        })
      },
      cleanInput() {
        //button会自动清空,所以不能再次清空而是应该给他设置目前的input值
        this.setData({
          message: this.data.message
        })
      },
      increase() {
        this.setData({
          increase: true,
          aniStyle: true
        })
      },
      //点击空白隐藏message下选框
      outbtn() {
        this.setData({
          increase: false,
          aniStyle: true
        })
      },
      //发送图片
      chooseImage() {
        var that = this
        wx.chooseImage({
          count: 1, // 默认9
          sizeType: ['original', 'compressed'], // 可以指定是原图还是压缩图,默认二者都有
          sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有
          success: function (res) {
            // 返回选定照片的本地文件路径列表,tempFilePath可以作为img标签的src属性显示图片
            var tempFilePaths = res.tempFilePaths
            // console.log(tempFilePaths)
            wx.uploadFile({
              url: 'wss://www.xxx.cn', //服务器地址
              filePath: tempFilePaths[0],
              name: 'file',
              headers: {
                'Content-Type': 'form-data'
              },
              success: function (res) {
                if (res.data) {
                  that.setData({
                    increase: false
                  })
                  websocket.send('{"images":"' + res.data + '","date":"' + utils.formatTime(new Date()) + '","type":"image","nickName":"' + that.data.userInfo.nickName + '","avatarUrl":"' + that.data.userInfo.avatarUrl + '"}')
                  that.bottom()
                }
              }
            })
          }
        })
      },
      //图片预览
      previewImg(e) {
        var that = this
        //必须给对应的wxml的image标签设置data-set=“图片路径”,否则接收不到
        var res = e.target.dataset.src
        var list = this.data.previewImgList //页面的图片集合数组
        //判断res在数组中是否存在,不存在则push到数组中, -1表示res不存在
        if (list.indexOf(res) == -1) {
          this.data.previewImgList.push(res)
        }
        wx.previewImage({
          current: res, // 当前显示图片的http链接
          urls: that.data.previewImgList // 需要预览的图片http链接列表
        })
      },
      //聊天消息始终显示最底端
      bottom: function () {
        var query = wx.createSelectorQuery()
        query.select('#flag').boundingClientRect()
        query.selectViewport().scrollOffset()
        query.exec(function (res) {
          wx.pageScrollTo({
            scrollTop: res[0].bottom // #the-id节点的下边界坐标
          })
          res[1].scrollTop // 显示区域的竖直滚动位置
        })
      }
    })

     /pages/chat/chat.wxml

    <view class="news" bindtap='outbtn'>
      <view class="chat-notice" wx:if="{{userInfo}}">系统消息: 欢迎 {{ userInfo.nickname }} 加入聊天室</view>
      <view class="historycon">
        <scroll-view scroll-y="true" class="history" scroll-top="{{scrollTop}}">
          <block wx:for="{{newslist}}" wx:key="index">
            <!-- 历史消息 -->
            <!-- <view class="chat-news">
    <view style="text-align: left;padding-left: 20rpx;">
    <image class='new_img' src="{{item.avatarUrl? item.avatarUrl:'images/avator.png'}}"></image>
    <text class="name">{{ item.nickName }}{{item.date}}</text>
    </view>
    <view class='you_left'>
    <block wx:if="{{item.type=='text'}}">
    <view class='new_txt'>{{item.content}}</view>
    </block>
    <block wx:if="{{item.type=='image'}}">
    <image class="selectImg" src="{{item.images}}"></image>
    </block>
    </view>
    </view> -->
            <view>{{item.date}}</view>
            <!--自己的消息 -->
            <view class="chat-news" wx:if="{{item.fid == userInfo.id}}">
              <view style="text-align: right;padding-right: 20rpx;">
                <text class="name">{{ item.nickName }}</text>
                <image class='new_img' src="{{item.avatarUrl?item.avatarUrl:'/images/avator.png'}}"></image>
              </view>
              <view class='my_right'>
                <view class='new_txt'>{{item.content}}</view>
                <!--block wx:if="{{item.type=='text'}}">
                  <view class='new_txt'>{{item.content}}</view>
                </block>
                <block wx:if="{{item.type=='image'}}">
                  <image class="selectImg" src="{{item.images}}" data-src="{{item.images}}" lazy-load="true" bindtap="previewImg"></image>
                </block-->
              </view>
            </view>
            <!-- 别人的消息 -->
            <view class="chat-news" wx:else>
              <view style="text-align: left;padding-left: 20rpx;">
                <image class='new_img' src="{{item.avatarUrl? item.avatarUrl:'/images/avator.png'}}"></image>
                <text class="name">{{ item.nickName }}</text>
              </view>
              <view class='you_left'>
                <view class='new_txt'>{{item.content}}</view>
                <!--block wx:if="{{item.type=='text'}}">
                  <view class='new_txt'>{{item.content}}</view>
                </block>
                <block wx:if="{{item.type=='image'}}">
                  <image class="selectImg" src="{{item.images}}" data-src="{{item.images}}" lazy-load="true" bindtap="previewImg"></image>
                </block-->
              </view>
            </view>
          </block>
        </scroll-view>
      </view>
    </view>
    <view id="flag"></view>
    <!-- 聊天输入 -->
    <view class="message">
      <form bindreset="cleanInput" class="sendMessage">
        <input type="text" placeholder="请输入聊天内容.." value="{{massage}}" bindinput='bindChange'></input>
        <!--view class="add" bindtap='increase'>+</view-->
        <button type="primary" bindtap='send' formType="reset" size="small" button-hover="blue">发送</button>
      </form>
      <!--view class='increased {{aniStyle?"slideup":"slidedown"}}' wx:if="{{increase}}">
        <view class="image" bindtap='chooseImage'>相册 </view>
      </view-->
    </view>


    /pages/chat/chat.wxss

    /* pages/socks/socks.wxss */
    page {
      background-color: #f7f7f7;
      height: 100%;
    }
    
    /* 聊天内容 */
    .news {
      padding-top: 30rpx;
      text-align: center;
      /* height:100%; */
      box-sizing: border-box;
    }
    
    #flag {
      margin-bottom: 100rpx;
      height: 30rpx;
    }
    
    .chat-notice {
      text-align: center;
      font-size: 30rpx;
      padding: 10rpx 0;
      color: #666;
    }
    
    .historycon {
      height: 100%;
      width: 100%;
      /* flex-direction: column; */
      display: flex;
      border-top: 0px;
    }
    
    /* 聊天 */
    .chat-news {
      width: 100%;
      overflow: hidden;
    }
    
    .chat-news .my_right {
      float: right;
      /* right: 40rpx; */
      padding: 10rpx 10rpx;
    }
    
    .chat-news .name {
      margin-right: 10rpx;
    }
    
    .chat-news .you_left {
      float: left;
      /* left: 5rpx; */
      padding: 10rpx 10rpx;
    }
    
    .selectImg {
      display: inline-block;
      width: 150rpx;
      height: 150rpx;
      margin-left: 50rpx;
    }
    
    .my_right .selectImg {
      margin-right: 80rpx;
    }
    
    .new_img {
      width: 60rpx;
      height: 60rpx;
      border-radius: 50%;
      vertical-align: middle;
      margin-right: 10rpx;
    }
    
    .new_txt {
      max-width: 300rpx;
      display: inline-block;
      border-radius: 6rpx;
      line-height: 60rpx;
      background-color: #95d4ff;
      padding: 5rpx 20rpx;
      margin: 0 10rpx;
      margin-left: 50rpx;
    }
    
    .my_right .new_txt {
      margin-right: 60rpx;
    }
    
    .you {
      background-color: lightgreen;
    }
    
    .my {
      border-color: transparent transparent transparent #95d4ff;
    }
    
    .you {
      border-color: transparent #95d4ff transparent transparent;
    }
    
    .hei {
      margin-top: 50px;
      height: 20rpx;
    }
    
    .history {
      height: 100%;
      margin-top: 15px;
      padding: 10rpx;
      font-size: 14px;
      line-height: 40px;
      word-break: break-all;
    }
    
    ::-webkit-scrollbar {
      width: 0;
      height: 0;
      color: transparent;
      z-index: -1;
    }
    
    /* 信息输入区域 */
    .message {
      position: fixed;
      bottom: 0;
      width: 100%;
    }
    
    .sendMessage {
      
      height: 80rpx;
      padding: 10rpx 10rpx;
      background-color: #fff;
      border-top: 2rpx solid #eee;
      border-bottom: 2rpx solid #eee;
      /*z-index: 3;*/
    }
    
    .sendMessage input {
      float: left;
      height: 42px;
      line-height: 100%;
      border-bottom: 1rpx solid #ccc;
      padding: 0 10rpx;
      font-size: 35rpx;
      color: #666;
    }
    
    .sendMessage button {
      float: right;
      font-size: 35rpx;
    }
    
    .sendMessage view {
      display: inline-block;
      width: 80rpx;
      height: 80rpx;
      line-height: 80rpx;
      font-size: 60rpx;
      text-align: center;
      color: #999;
      border: 1rpx solid #ccc;
      border-radius: 50%;
      margin-left: 10rpx;
    }
    
    
    
    .increased {
      width: 100%;
      /* height: 150rpx; */
      padding: 40rpx 30rpx;
      background-color: #fff;
    }
    
    .increased .image {
      width: 100rpx;
      height: 100rpx;
      border: 3rpx solid #ccc;
      line-height: 100rpx;
      text-align: center;
      border-radius: 8rpx;
      font-size: 35rpx;
    }
    
    @keyframes slidedown {
      from {
        transform: translateY(0);
      }
    
      to {
        transform: translateY(100%);
      }
    }
    
    .slidedown {
      animation: slidedown 0.5s linear;
    }
    
    .slideup {
      animation: slideup 0.5s linear;
    }
    
    @keyframes slideup {
      from {
        transform: translateY(100%);
      }
    
      to {
        transform: translateY(0);
      }
    }

    /utils/websocket.js

    import {WSS_SERVER_URL} from "config.js"
    
    //定时标识
    let timing = false
    
    function connect(user, func) {
      wx.connectSocket({
        url: `${WSS_SERVER_URL}?type=ask&fid=${user.id}&tid=100`,
        header: { 'content-type': 'application/json' },
        success: function () {
          console.log('websocket连接成功~')
        },
        fail: function () {
          console.log('websocket连接失败~')
        }
      })
     wx.onSocketOpen(function (res) {
        wx.showToast({
          title: 'websocket已开通~',
          icon: "success",
          duration: 2000
        })
        //接受服务器消息
        wx.onSocketMessage(func);//func回调可以拿到服务器返回的数据
     });
    
     //启动心跳包
     linkWebsocketXin(40000, true)
    
     wx.onSocketError(function (res) {
        wx.showToast({
          title: 'websocket连接失败,请检查!',
          icon: "none",
          duration: 2000
        })
     })
    }
    //心跳包
    function linkWebsocketXin(time, status) {
      if (status == true) {
        timing = setInterval(function () {
          console.log("当前心跳已重新连接");
          //循环执行代码
          wx.sendSocketMessage({
            data: JSON.stringify({
              type: 'active'
            }),
            fail(res) {
              // console.log(res)
            }
          });
        }, time) //循环时间,注意不要超过1分钟  
      } else {
        //关闭定时器
        clearInterval(timing);
        console.log("当前心跳已关闭");
      }
    }
    //发送消息
    function send(msg) {
      //关闭心跳包定时器
      linkWebsocketXin(40000, false)
      wx.sendSocketMessage({
        data: msg,
        success:res=>{
          //重启心跳包
          linkWebsocketXin(40000, true)
        }
      });
    }
    module.exports = {
     connect: connect,
     send: send,
     linkWebsocketXin:linkWebsocketXin
    }

    /utils/config.js

    module.exports =  {
    
        // 请求域名 格式: https://您的域名
        HTTP_REQUEST_URL:'http://www.skill.com',
        // Socket链接 暂不做配置
        WSS_SERVER_URL:'wss://wss.51chow.com',
        //JWT token 名称
        TOKEN_NAME:'token',
        //用户注册id 名称
        USER_ID:'uid',
        //用户注册openid 名称
        OPEN_ID:'openid',
        // 以下配置非开发者,无需修改
        // 请求头
        HEADER:{
          'content-type': 'application/json'
        },
    
    }

    /utils/util.js

    const formatTime = date => {
      const year = date.getFullYear()
      const month = date.getMonth() + 1
      const day = date.getDate()
      const hour = date.getHours()
      const minute = date.getMinutes()
      const second = date.getSeconds()
    
      return `${[year, month, day].map(formatNumber).join('/')} ${[hour, minute, second].map(formatNumber).join(':')}`
    }
    
    const formatNumber = n => {
      n = n.toString()
      return n[1] ? n : `0${n}`
    }
    
    module.exports = {
      formatTime
    }
    

    六、服务端PHP代码

    chat.php

    <?php
    
    require_once __DIR__ . '/libs/RedisLib.php';
    
    //聊天内容
    $chatMessagesKey = "swoole:message:%s";
    //房间用户
    $roomUserKey = "swoole:room:%s";
    //所有在线用户
    $roomOnlinesKey = "swoole:onlines";
    
    //实例化一个swoole的websocket服务监听本机的9501端口
    $server = new swoole_websocket_server("0.0.0.0", 9501);
    
    $server->set([
        // 虚拟目录的只想位置,只针对静态的资源  html css js 图片 视频
        'document_root' => '/www/wwwroot/swoole_1909a/web', // v4.4.0以下版本, 此处必须为绝对路径
        'enable_static_handler' => true,
    ]);
    
    $server->on('Request', function ($request, $response) {
        $response->header('Content-Type', 'text/html; charset=utf-8');
        $response->end('<h1>Hello Swoole. #' . rand(1000, 9999) . '</h1>');
    });
    
    //只需要绑定要监听的ip和端口。如果ip指定为127.0.0.1,则表示客户端只能位于本机才能连接,其他计算机无法连接。
    //端口这里指定为9501,可以通过netstat查看下该端口是否被占用。如果该端口被占用,可更改为其他端口,如9502,9503等。
    $server->on('open', function (swoole_websocket_server $server, $request) use ($chatMessagesKey, $roomUserKey, $roomOnlinesKey) {
        $fid = $request->get['fid'];
        $tid = $request->get['tid'];
        $type = $request->get['type'];
    
        if($fid && $type) {
    
            //存储在线用户
            RedisLib::getInstance()->getRedis()->hSet($roomOnlinesKey, $request->fd, $fid);
    
            //咨询问题
            if($type == 'ask') {
                $roomUserKey = sprintf($roomUserKey, $fid);
                $chatMessagesKey = sprintf($chatMessagesKey, $fid);
                //上线进入某个房间
                RedisLib::getInstance()->getRedis()->hSet($roomUserKey, $fid, $request->fd);
                //历史聊天内容
                $data = [];
                $contents = RedisLib::getInstance()->getRedis()->lRange($chatMessagesKey, 0, -1);
    
                if($contents) {
                    foreach ($contents as $content) {
                        $data[] = json_decode($content, true);
                    }
                }
                $msg = [
                    'type' => 'open',
                    'fid' => $fid,
                    'tid' => $tid,
                    'content' => $data
                ];
    
                $server->push($request->fd, json_encode($msg));
            }
            //回复问题
            elseif ($type == 'reply') {
                //上线进入某个房间
                $roomUserKey = sprintf($roomUserKey, $tid);
                $chatMessagesKey = sprintf($chatMessagesKey, $tid);
                RedisLib::getInstance()->getRedis()->hSet($roomUserKey, $tid, $request->fd);
    
                //历史聊天内容
                $contents = RedisLib::getInstance()->getRedis()->lRange($chatMessagesKey, 0, -1);
                $data = [];
                if($contents) {
                    foreach ($contents as $content) {
                        $data[] = json_decode($content, true);
                    }
                }
                $msg = [
                    'type' => 'open',
                    'fid' => $fid,
                    'tid' => $tid,
                    'content' => $data
                ];
    
                $server->push($request->fd, json_encode($msg));
            }
    
            echo "你好连接成功{$request->fd}\n";
    
        } else {
            echo "非法请求,连接成功{$request->fd}\n";
        }
    
    });
    
    $server->on('message', function (swoole_websocket_server $server, $frame) use ($chatMessagesKey, $roomUserKey) {
    
        echo $frame->data, "\r\n";
    
        $msg = json_decode($frame->data, true);
        if(!empty($msg) && isset($msg['fid'])) {
            //咨询问题
            if($msg['type'] == 'ask') {
                $chatMessagesKey = sprintf($chatMessagesKey, $msg['fid']);
            }
            //回复问题
            elseif ($msg['type'] == 'reply') {
                $chatMessagesKey = sprintf($chatMessagesKey, $msg['tid']);
            }
    
            //保存聊天记录
            RedisLib::getInstance()->getRedis()->rPush($chatMessagesKey, $frame->data);
    
            foreach ($server->connections as $key => $fd) {
                if($fd) {
                    $server->push($fd, $frame->data);
                }
            }
        }
    
        if($msg['type'] == 'active') {
            echo '我是心跳包, 我还活着', $frame->fd, "\r\n";
        }
    
    });
    
    $server->on('close', function ($ser, $fd) use($roomOnlinesKey) {
    
        //用户下线了
        if(RedisLib::getInstance()->getRedis()->hExists($roomOnlinesKey, $fd)) {
            RedisLib::getInstance()->getRedis()->hdel($roomOnlinesKey, $fd);
        }
    
        /*$is_websocket = $ser->getClientInfo($fd)['websocket_status'];
        if($is_websocket) {
            echo "client {$fd} closed  websocket status is {$is_websocket}\n";
        } else {
            echo "client {$fd} closed  is not valid websocket connection\n";
        }*/
    });
    
    $server->start();
    

    可以通过nohup php chat.php >> chat.log & 来常驻

    展开全文
  • 编了个java 聊天小程序 供大家分享 用MYEclipse打开即可
  • 微信小程序学习用demo推荐:聊天室+音视频+添加内容+用图灵API实现了机器人聊天功能,另外实现了一些其他功能,可用于学习研究;
  • 微信小程序开发交流qq群 173683895 承接微信小程序开发。扫码加微信。 小程序WebSocket 常见问题:(本文已解决的) 1.自动断开链接,重连但是只能存在两个WebSocket 的问题。 ---1兼容情况:1.1 正常聊天过一...

    微信小程序开发交流qq群   173683895

       承接微信小程序开发。扫码加微信。

    小程序 WebSocket 常见问题:本文已解决的

    1.自动断开链接,重连但是只能存在两个 WebSocket 的问题。

      ---1兼容情况:1.1 正常聊天过一段时间 WebSocket 自动断开后重新链接,并且保存之前的聊天记录

      ---1兼容情况:1.2 在用户黑屏但是没退出小程序过一段时间时 WebSocket 自动断开链接后重新链接,并且保存之前的聊天记录

      ---1兼容情况:1.3 在聊天室的页面,点击右上角返回按钮,页面会自动执行卸载,这个时候 WebSocket 是没有销毁的,再次进入时会同时存在两个WebSocket,第三次进入就会报错了(只能同时存在两个 WebSocket )。

    解决方案:因为需要兼容的情况比较多,解决方案可详见代码。具体思路就是,添加一个自动重连的开关。(必须WebSocket 销毁以后才能新建WebSocket )根据情况判断是否重连 WebSocket 。

    2. 录音成功,但是发送给后端接收不到语音文件。

    解决办法:在已录制完指定帧大小的事件回调函数中, 使用 wx.arrayBufferToBase64(res.frameBuffer),把得到的arrayBuffer 转为 Base64 再传给后端,同时设置 signType: 'BASE64'。

    3.录音传输给后端时,后端BASE64 解码失败的问题。

    解决方案:与后端确认 录音文件的采样率、编码码率、音频格式、帧大小是否一致。

    推荐设置:

        var recorder = wx.getRecorderManager();
        const options = {
          duration: 10000, //指定录音的时长,单位 ms
          sampleRate: 16000, //采样率
          numberOfChannels: 1, //录音通道数
          encodeBitRate: 24000, //编码码率
          format: 'mp3', //音频格式,有效值 aac/mp3
          frameSize: 12, //指定帧大小,单位 KB
        }
        recorder.start(options) //开始录音

    4.如何自动把页面聚焦在最新的聊天信息

    效果图:

    解决方案:在聊天信息的list 赋值成功后执行一次公共页面聚焦的方法:

      // 公共聚焦方法,方法比较笨,但是过度效果平滑流畅
      bottom: function() {
        var that = this;
        this.setData({
          scrollTop: 100000
        })
      },
    
    //调用示例:
          this.setData({
            allContentList: that.data.allContentList,
          })
          this.bottom();

    5.用户输入空格并发送,这个时候聊天冒泡框样式肯定会变形,因为没有空格行高。

    解决方案: css 设置冒泡框最低高度  ----  min-height: 80rpx;(数值根据需求自定义)

    6.聊天冒泡框的小三角形怎么实现?三角形还想要给它边框,又如何实现?

    效果图:

    实现步骤:设置文字冒泡框的样式-在文字冒泡框内新建一个盒子设置相对定位,里面放一个em和一个span标签,设置绝对定位,利用边框设置透明色,示例代码:

    <view class='new_txt_ai'>
      <view class='arrow'>
         <em></em>
         <span></span>
      </view>
      <view class='ai_content'>
        121(一百二十一)是 120与122之间的一个自然数。它也是奇数、合数、平方数
      </view>
    </view>
    .new_txt_ai {
      width: 460rpx;
      border-radius: 7rpx;
      left: 20rpx;
      background-color: #fff;
      position: relative;
      border: 1px solid #d0d0d0;
      float: left;
    }
    
    .new_txt_ai .arrow {
      position: relative;
      width: 40rpx;
      left: -30rpx;
    }
    
    .new_txt_ai .arrow em {
      position: absolute;
      border-style: solid;
      border-width: 15rpx;
      top: 20rpx;
      border-color: transparent #d0d0d0 transparent transparent;
    }
    
    .new_txt_ai .arrow span {
      position: absolute;
      top: 20rpx;
      border-style: solid;
      border-width: 15rpx;
      border-color: transparent #fff transparent transparent;
      left: 2rpx;
    }
    
    .ai_content {
      word-break: break-all;
      padding: 17rpx 30rpx 17rpx 30rpx;
    }

    还有很多常见问题就不多啰嗦了,直接上代码吧。因业务原因,发送图片和录音功能的代码暂时注释,注释打开可用。

    聊天室实现效果图:

     

    全部相关代码,代码逻辑比较多但是思路清晰,可塑性较强。提供借鉴参考。

    js源码

    // pages/index/to_news/to_news.js  
    var app = getApp();
    var util = require("../../utils/util.js");
    var socketOpen = false;
    var uuid = '',
      time_ = "1";
    var recorder = wx.getRecorderManager();
    const innerAudioContext = wx.createInnerAudioContext() //获取播放对象
    var frameBuffer_Data, session, SocketTask, string_base64, open_num = 0, submitTo_string,
      onUnload_num = 0,
      autoRestart, onHide_s = false;
    Page({
      data: {
        listCustmerServiceBanner: [],
        indicatorDots: false,
        autoplay: false,
        interval: 5000,
        duration: 1000,
        user_input_text: '', //用户输入文字
        inputValue: '',
        time: '',
        returnValue: '',
        if_send: false,
        add: true,
        cross: false,
        // is_my: true, text: '12432'
        allContentList: [{}, {
          is_ai: []
        }],
        num: 0
      },
      // 页面加载
      onLoad: function(e) {
        autoRestart = true; //是否重启
        console.log('onLoad')
        if (e && e.ofOperatorType) {
          this.setData({
            ofOperatorType: e.ofOperatorType
          })
        } else {
          this.setData({
            ofOperatorType: 2
          })
        }
        // if (onUnload_num < 1) {
        this.webSocket_open()
        // }
      },
      onShow: function(e) {
        onHide_s = false
      },
    
      onHide: function() {
        autoRestart = false;
        onHide_s = true
        console.log('onHide')
      },
      onUnload: function() {
        onUnload_num++;
        autoRestart = false;
        console.log('onUnload')
        this.close();
      },
      // 页面加载完成
      onReady: function() {
        var that = this;
        this.on_recorder();
        this.bottom()
      },
      // 创建websocket
      webSocket_open: function () {
        var that = this;
        console.log('开始创建')
        // 创建Socket
        SocketTask = wx.connectSocket({
          url: app.webS_url,
          header: {
            'content-type': 'application/json'
          },
          method: 'post',
          success: function(res) {
            console.log('WebSocket连接创建', res)
          },
          fail: function(err) {
            wx.showToast({
              title: '网络异常!',
            })
            console.log(err)
          },
        })
        that.initSocket();
      },
    
      // 提交文字
      submitTo: function(e) {
        submitTo_string =false
        console.log('提交文字')
        console.log("SocketTask", SocketTask)
        let that = this;
        if (that.data.inputValue == "") {
          return;
        }
        var data = {
          cmd: 1,
          type: 1,
          signType: 'BASE64',
          session: session,
          body: that.data.inputValue,
        }
        console.log('提交文字data:', socketOpen, data)
        if (socketOpen) {
          // 如果打开了socket就发送数据给服务器
          sendSocketMessage(data)
          if (session != undefined && session != null) {
            this.data.allContentList.push({
              is_my: true,
              text: this.data.inputValue
            });
            this.setData({
              allContentList: this.data.allContentList,
              if_send: false,
              inputValue: ''
            })
    
          }
          that.bottom()
        } else {
          submitTo_string=true;
          this.webSocket_open()
        }
      },
    
      // socket监听事件
      initSocket: function () {
        var that = this;
        console.log("aaa", SocketTask)
        SocketTask.onOpen(res => {
          socketOpen = true;
          open_num++
          if (session == undefined || session == null) {
            // repositoryType = 1 联通 2 移动 3 电信
            // ofType	int		进入客服小程序类型 1, 小程序跳转 2,搜索
            //  ofOperatorType	int	否	运营商类型1,移动 2,联通 3,电信
            //  wy_appid	String	否	小程序appid
            if (app.appid) {
              var data = {
                cmd: 2,
                ofType: 1,
                wy_appid: app.appid
              }
            } else {
              var data = {
                cmd: 2,
                ofType: 2,
                ofOperatorType: that.data.ofOperatorType
                // ofOperatorType: 1
              }
            }
            sendSocketMessage(data)
          }
          console.log('监听 WebSocket 连接打开事件。', res)
        })
        SocketTask.onClose(onClose => {
          console.log('监听 WebSocket 连接关闭事件。', onClose)
          session = null;
          SocketTask = false;
          socketOpen = false;
          // if (!autoRestart && onHide_s) {
          //   this.webSocket_open()
          // }
          // if (autoRestart) {
          //   this.webSocket_open()
          // }
        })
        SocketTask.onError(onError => {
          console.log('监听 WebSocket 错误。错误信息', onError)
          session = null;
        })
        SocketTask.onMessage(onMessage => {
          var onMessage_data = JSON.parse(onMessage.data);
          console.log("onMessage:", onMessage_data)
          // if (onMessage_data == 'session为空') {
          //   if (submitTo_string) {
          //     console.log('submitTo_string2222222222')
          //     that.submitTo()
          //   }
          //   return;
          // }
          if (onMessage_data.minipTitle) {
            wx.setTopBarText({
              text: onMessage_data.minipTitle,
            })
          }
          let is_ai_arr = onMessage_data.body;
          // 登录。默认发送一条消息给用户展示,不展示已解决未解决
          if (onMessage_data.cmd == 3) {
            that.session_pro = new Promise(function (resolve) {
              session = onMessage_data.session;
              if (submitTo_string) {
                console.log('submitTo_string11111111')
                that.submitTo()
              }
              resolve(session)
            })
            var messageTime = util.formatTime(onMessage_data.messageTime);
            // if (open_num < 2){
    
            if (is_ai_arr.length == 1) {
              that.data.allContentList.push({
                is_ai: is_ai_arr,
                solve_show: false,
                show_answer: true,
                messageTime: messageTime
              });
            } else {
                console.log('is_ai_arr:', is_ai_arr)
                that.data.allContentList.push({
                  is_ai: is_ai_arr,
                  show_answer: false,
                  solve_show: false,
                  messageTime: messageTime
                });
              }
            // }
            this.setData({
              listCustmerServiceBanner: onMessage_data.listCustmerServiceBanner,
              staffServicePhone: onMessage_data.staffServicePhone,
              allContentList: that.data.allContentList
            })
          } else {
            // 正常接收消息
            uuid = onMessage_data.messageRecordUuid;
            var messageTime;
            time_ = onMessage_data.messageTime;
            if (time_ + 1000 * 60 * 10 > onMessage_data.messageTime) {
              messageTime = 0;
            } else {
              messageTime = util.formatTime(onMessage_data.messageTime);
            }
            let arr_list = that.data.allContentList
            if (is_ai_arr.length == 1) {
              arr_list.push({
                show_answer: true,
                is_ai: is_ai_arr,
                messageTime: messageTime,
                solve_show: true,
                no_problem: false,
                yse_problem: false
              });
            } else {
              arr_list.push({
                show_answer: false,
                is_ai: is_ai_arr,
                messageTime: messageTime,
                solve_show: true,
                no_problem: false,
                yse_problem: false
              });
            }
            that.setData({
              allContentList: arr_list
            })
          }
          that.bottom();
        })
      },
      // 点击轮播图
      swiper_item_click: function (e) {
        var id = e.target.id
        console.log(id);
        var item_banners = this.data.listCustmerServiceBanner[id];
        var page = item_banners.page;
        // 类型1、自己小程序、2、其它小程序 3、H5
        switch (item_banners.type) {
          case 1:
            wx.navigateTo({
              url: page,
            })
            break;
          case 2:
            wx.navigateToMiniProgram({
              appId: item_banners.appid,
              path: page,
              extraData: {},
              envVersion: 'release',
              success(res) {
                // 打开成功
              }
            })
            break;
          case 3:
            wx.navigateTo({
              url: web + '?url=' + page,
            })
            break;
        }
      },
      // 关闭
      close: function (e) {
        if (SocketTask) {
          SocketTask.close(function (close) {
            console.log('关闭 WebSocket 连接。', close)
          })
        }
      },
      // 解决问题
      is_problem: function(e) {
        console.log('e.target.id', e.currentTarget.dataset.id)
        console.log('item', e.currentTarget.dataset.item)
        var id = e.currentTarget.dataset.id;
        var item = e.currentTarget.dataset.item;
        // id=1 已解决  0 未解决
        var yse_problem = this.data.allContentList[item].yse_problem;
        var no_problem = this.data.allContentList[item].no_problem;
        if (yse_problem || no_problem) {
          console.log(12)
          return
        } else {
          if (id == 1) {
            this.setData({
              ['allContentList[' + item + '].yse_problem']: true
            })
          } else if (id == 0) {
            this.setData({
              ['allContentList[' + item + '].no_problem']: true
            })
          }
          console.log(this.data.allContentList[item].yse_problem, this.data.allContentList[item].no_problem)
          this.bottom();
        }
        var url = app.httpUrl + '/v1/userFeedbackResult.do'
        var data = {
          'session': app.http_session,
          'type': id,
          'uuid': uuid
        }
        console.log('userFeedbackResult提交的数据:', data)
        util.request(url, 'POST', data, '', function(res) {
          console.log('userFeedbackResult返回的数据:', res.data)
    
        }, function(err) {
          console.log(err)
        })
      },
      // 跳转小程
      minip: function(e) {
        console.log(e)
        wx.navigateToMiniProgram({
          appId: e.target.dataset.appid,
          path: e.target.dataset.path,
          extraData: {},
          envVersion: 'develop',
          success(res) {
            // 打开成功
          }
        })
      },
      // 跳转WEB
      link: function(e) {
        console.log(e.target.id)
        wx.navigateTo({
          url: '../web/web?link=' + e.target.id,
        })
      },
      // 点击加号
      add_icon_click: function(e) {
        console.log(e.target.id)
        // e.target.id == 1 点击加号   ==2  点击 X
        if (e.target.id == 2) {
          this.setData({
            add: true,
            cross: false,
            input_bottom: 0
          })
        } else if (e.target.id == 1) {
          this.setData({
            add: false,
            cross: true,
            input_bottom: 240
          })
        }
      },
      // 自动添加问题答案
      add_question: function(e) {
        var that = this;
        let answer = e.currentTarget.dataset.answer;
        let messageTime = e.currentTarget.dataset.messagetime;
        let question = e.currentTarget.dataset.question;
        console.log('question:', question, 'answer:', answer, 'messageTime', messageTime);
        this.data.allContentList.push({
          is_my: true,
          text: question
        });
        this.setData({
          allContentList: this.data.allContentList,
          if_send: false,
          inputValue: ''
        })
        that.bottom();
        setTimeout(function() {
          that.data.allContentList.push({
            is_ai: [{
              answer: answer,
              type: 1
            }],
            solve_show: true,
            show_answer: true,
            messageTime: false,
            text: question
          });
          that.setData({
            allContentList: that.data.allContentList,
          })
          that.bottom();
        }, 1000)
      },
      // 拨打电话
      phone_click: function() {
        var that = this;
        wx.showModal({
          title: '',
          content: '是否拨打' + that.data.staffServicePhone + '人工客服电话',
          success: function(res) {
            if (res.confirm) {
              wx.makePhoneCall({
                phoneNumber: that.data.staffServicePhone //仅为示例,并非真实的电话号码  
              })
            } else if (res.cancel) {
              console.log('用户点击取消')
            }
          }
        })
      },
      // 输入框
      bindKeyInput: function(e) {
        console.log(e.detail.value)
        if (e.detail.value == "") {
          this.setData({
            if_send: false,
            inputValue: e.detail.value
          })
        } else {
          this.setData({
            if_send: true,
            inputValue: e.detail.value
          })
        }
      },
      // 获取到焦点
      focus: function(e) {
        var that = this;
        console.log(e.detail.height)
        this.setData({
          focus: true,
          add: true,
          cross: false,
          input_bottom: e.detail.height
        })
      },
      // 失去焦点
      no_focus: function(e) {
        if (this.data.cross) {
          this.setData({
            focus: false,
            input_bottom: 240,
          })
        } else {
          this.setData({
            focus: false,
            input_bottom: 0
          })
        }
      },
      // 获取hei的id节点然后屏幕焦点调转到这个节点  
      bottom: function() {
        var that = this;
        this.setData({
          scrollTop: 100000
        })
      },
      hide_bg: function() {
        this.setData({
          block: false
        })
      },
      // 点击录音事件
      my_audio_click: function(e) {
        console.log('my_audio_click执行了', e)
        var index = e.currentTarget.dataset.id;
        console.log('url地址', this.data.allContentList[index].audio);
        innerAudioContext.src = this.data.allContentList[index].audio
        innerAudioContext.seek(0);
        innerAudioContext.play();
      },
      // 手指点击录音
      voice_ing_start: function() {
        var that = this;
        this.setData({
          voice_ing_start_date: new Date().getTime(), //记录开始点击的时间
        })
        const options = {
          duration: 10000, //指定录音的时长,单位 ms
          sampleRate: 16000, //采样率
          numberOfChannels: 1, //录音通道数
          encodeBitRate: 24000, //编码码率
          format: 'mp3', //音频格式,有效值 aac/mp3
          frameSize: 12, //指定帧大小,单位 KB
        }
        recorder.start(options) //开始录音
    
        this.animation = wx.createAnimation({
          duration: 1200,
        }) //播放按钮动画
        that.animation.scale(0.8, 0.8); //还原
        that.setData({
    
          spreakingAnimation: that.animation.export()
        })
      },
      // 录音监听事件
      on_recorder: function() {
        var that = this;
        recorder.onStart((res) => {
          console.log('开始录音');
        })
        recorder.onStop((res) => {
          console.log('停止录音,临时路径', res.tempFilePath);
          // _tempFilePath = res.tempFilePath;
          var x = new Date().getTime() - this.data.voice_ing_start_date
          if (x > 1000) {
            that.data.allContentList.push({
              is_my: true,
              audio: res.tempFilePath,
              length: x / 1000 * 30
            });
            that.setData({
              allContentList: that.data.allContentList
            })
          }
        })
        recorder.onFrameRecorded((res) => {
          var x = new Date().getTime() - this.data.voice_ing_start_date
          if (x > 1000) {
            console.log('onFrameRecorded  res.frameBuffer', res.frameBuffer);
            string_base64 = wx.arrayBufferToBase64(res.frameBuffer)
    
            // console.log('string_base64--', wx.arrayBufferToBase64(string_base64))
            if (res.isLastFrame) {
              that.session_pro.then(function(session) {
                var data = {
                  audioType: 3,
                  cmd: 1,
                  type: 2,
                  signType: 'BASE64',
                  session: session,
                  body: string_base64,
                }
                console.log('that.data.allContentList', that.data.allContentList)
                sendSocketMessage(data)
              })
              // 进行下一步操作
            } else {
              that.session_pro.then(function(session) {
                var data = {
                  cmd: 1,
                  audioType: 2,
                  type: 2,
                  signType: 'BASE64',
                  session: session,
                  body: string_base64
                }
                console.log('录音上传的data:', data)
                sendSocketMessage(data)
              })
            }
          }
        })
      },
      // 手指松开录音
      voice_ing_end: function() {
        var that = this;
        that.setData({
          voice_icon_click: false,
          animationData: {}
        })
        this.animation = "";
        var x = new Date().getTime() - this.data.voice_ing_start_date
        if (x < 1000) {
          console.log('录音停止,说话小于1秒!')
          wx.showModal({
            title: '提示',
            content: '说话要大于1秒!',
          })
          recorder.stop();
        } else {
          // 录音停止,开始上传
          recorder.stop();
        }
      },
      // 点击语音图片
      voice_icon_click: function() {
        this.setData({
          voice_icon_click: !this.data.voice_icon_click
        })
      },
    })
    //通过 WebSocket 连接发送数据,需要先 wx.connectSocket,并在 wx.onSocketOpen 回调之后才能发送。
    function sendSocketMessage(msg) {
      var that = this;
      if (app.http_session != "") {
        msg.http_session = app.http_session
        console.log('通过 WebSocket 连接发送数据', JSON.stringify(msg))
        SocketTask.send({
          data: JSON.stringify(msg)
        }, function(res) {
          console.log('已发送', res)
        })
      } else {
        app.promise.then(function(http_session) {
          msg.http_session = http_session;
          console.log('通过 WebSocket 连接发送数据', JSON.stringify(msg));
          SocketTask.send({
            data: JSON.stringify(msg)
          }, function(res) {
            console.log('已发送', res);
          })
    
        })
    
      }
    
    }

    wxml源码

    <!-- <button bindtap='close'>关闭</button>
    <button bindtap='open'>打开</button> -->
    <!-- <swiper indicator-dots="{{indicatorDots}}" autoplay="{{autoplay}}" interval="{{interval}}" duration="{{duration}}">
      <block wx:for="{{listCustmerServiceBanner}}" wx:key=''>
        <swiper-item>
          <image src="{{item.picUrl}}" bindtap='swiper_item_click' id='{{index}}' class="slide-image" />
        </swiper-item>
      </block>
    </swiper> -->
    <view class='page_bg' wx:if='{{block}}' bindtap='hide_bg' />
    <view class='btn_bg' wx:if='{{block}}'>
      <view wx:for="{{link_list}}" wx:key='index'>
        <button class="sp_tit" id='{{index}}' bindtap='list_item'>查看详情 {{item}} </button>
      </view>
    </view>
    <scroll-view class="history" scroll-y="true" scroll-with-animation scroll-top="{{scrollTop}}">
    
      <block wx:key="{{index}}" wx:for="{{allContentList}}">
        <block wx:if="{{item.is_my}}">
          <view class='my_right new_txt'>
            <view class='time' wx:if='{{item.messageTime&&item.messageTime!=0}}'>
              {{item.messageTime}}
            </view>
            <view class='p_r page_r' style='margin-right: 25rpx;' wx:if='{{item.text}}'>
              <view class='new_txt'>
                <view class='new_txt_my'>
                  <view class='arrow'>
                    <em></em>
                    <span></span>
                  </view>
                  <text decode="true">{{item.text}}</text>
                </view>
              </view>
              <open-data class='new_img' type="userAvatarUrl"></open-data>
            </view>
            <view class='p_r page_r' style='margin-right: 25rpx;' wx:if='{{item.audio}}' bindtap='my_audio_click' data-id='{{index}}'>
              <view class='new_txt_my_2' style=' width:{{item.length}}px'>
                <image class='my_audio' src='/images/yuyin_icon.png'></image>
              </view>
              <span class='_span'></span>
              <open-data class='new_img' type="userAvatarUrl"></open-data>
            </view>
          </view>
        </block>
        <!-- <view class='you_left' id='id_{{allContentList.length}}'> -->
        <block wx:if="{{item.is_ai&&item.is_ai!=''}}">
          <view class='you_left' style='width:100%;' id='id_{{allContentList.length}}' wx:key="{{index}}">
            <view class='time' wx:if='{{item.messageTime}}'>
              {{item.messageTime}}
            </view>
            <view class='p_r' style='margin-left: 20rpx;'>
              <image class='new_img' src='/images/top_img.png'></image>
              <view class='new_txt'>
                <view class='new_txt_ai'>
                  <view class='arrow'>
                    <em></em>
                    <span></span>
                  </view>
                  <!-- {{item.text}} -->
                  <view class='ai_content'>
                    <block wx:for='{{item.is_ai}}' wx:for-item='itt' wx:for-index='indexi'  wx:key=''>
                      <text wx:if='{{itt.type=="1"&&item.show_answer}}' decode="true" >{{itt.answer}}</text>
                      <block wx:if='{{itt.type=="1"&&!item.show_answer}}'>
                      <text decode="true" wx:if='{{indexi==0}}'>{{itt.answer}}</text>
                      <view  decode="true" style='color:#0000EE' bindtap='add_question' data-messagetime='{{itt.messageTime}}' data-question='{{itt.question}}' data-answer='{{itt.answer}}'>· {{itt.question}}?</view>
                      </block>
                      <text wx:if='{{item.type=="2"}}' decode="true" style='color:#0000EE' bindtap='link' id='{{item.link}}'>{{item.text}}</text>
                      <image wx:if='{{item.type=="3"}}' style='width:{{item.w}}rpx;height:{{item.h}}rpx;' src='{{item.src}}'></image>
                      <text wx:if='{{item.type=="10"}}' decode="true" data-path='{{item.path}}' data-appid='{{item.appId}}' bindtap='minip'>{{item.text}}</text>
                    </block>
                  </view>
                  <!-- <view class='is_ai_btn' wx:if='{{item.solve_show&&item.is_ai[0].answer!="我不明白"}}'>
                    <view bindtap='is_problem' data-id='1' data-item='{{index}}' style=' {{item.yse_problem?"color: red;":""}}'>
    
                      <image src='{{item.yse_problem?"/images/in_zan.png":"/images/zan.png"}}' /> 已解决
                    </view>
                    <view bindtap='is_problem' data-id='0' data-item='{{index}}' class='two' style=' {{item.no_problem?"color: #00B1FF;":""}}'>
                      <image src='{{item.no_problem?"/images/in_zan_no.png":"/images/zan_no.png"}}' /> 未解决
                    </view>
                  </view> -->
                  <view class='yes_problem_log' wx:if="{{item.yse_problem&&item.solve_show}}" style=''>感谢您的反馈,我们会再接再厉!</view>
                  <view class='yes_problem_log' style='color:#32CF3C' wx:if="{{item.no_problem&&item.solve_show}}" bindtap='phone_click'>拨打人工客服</view>
    
                </view>
              </view>
            </view>
          </view>
        </block>
      </block>
    </scroll-view>
    <!-- 遮罩 -->
    <view class='zezhao' wx:if='{{cross}}' bindtap='add_icon_click' id='2'></view>
    <!-- 输入框 -->
    <view class='{{cross?"in_voice_icon":""}}'>
      <view class="sendmessage" wx:if='{{!cross}}' style='bottom:{{input_bottom}}px'>
        <input type="text" style='{{focus?"border-bottom: 1px solid #88DD4B;":""}}' adjust-position='{{false}}' cursor-spacing='5' bindinput="bindKeyInput" value='{{inputValue}}' focus='{{focus}}' bindblur='no_focus' bindfocus="focus" confirm-type="done" placeholder="请输入您要咨询的问题"/>
        <button wx:if='{{if_send&&inputValue!=""}}' bindtap="submitTo" class='user_input_text'>发送</button>
        <image class='add_icon' bindtap='add_icon_click' id='1' wx:if='{{add&&!if_send&&inputValue==""}}' src='/images/jia_img.png'></image>
        <image class='add_icon' bindtap='add_icon_click' id='2' wx:if='{{cross}}' src='/images/audio/cross37.png'></image>
    
      </view>
      <view wx:if='{{cross}}' class='item' bindtap='phone_click'>
        <image class='img' src='/images/yuyin_icon.png'></image>
        <view class='text'>人工客服</view>
      </view>
    </view>
    
    <!-- <view class='zezhao' wx:if='{{add_icon_click}}' bindtap='add_icon_click'></view> -->
    <!-- <view class='in_voice_icon'>
      <view class="sendmessage_2">
        <input type="text" bindinput="bindKeyInput" adjust-position='{{false}}' value='{{inputValue}}' focus='{{focus}}' bindfocus="focus" confirm-type="done" placeholder="" />
        <image class='add_icon' bindtap='add_icon_click' src='/images/audio/cross37.png'></image>
      </view>
      <view class='item' bindtap='phone_click'>
        <image class='img' src='/images/yuyin_icon.png'></image>
        <view class='text'>人工客服</view>
      </view>
    </view> -->

    wxss源码

    page {
      background-color: #f2f2f2;
      height: 100%;
      padding: 0 auto;
      margin: 0 auto;
    }
    
    swiper {
      height: 180rpx;
    }
    
    swiper swiper-item .slide-image {
      width: 100%;
      height: 180rpx;
    }
    
    .jia_img {
      height: 80rpx;
      width: 90rpx;
    }
    
    .time {
      text-align: center;
      padding: 5rpx 20rpx 5rpx 20rpx;
      border-radius: 10rpx;
      display: block;
      height: 38rpx;
      line-height: 38rpx;
      position: relative;
      margin: 0 auto;
      margin-bottom: 20rpx;
      width: 90rpx;
      color: white;
      font-size: 26rpx;
      background-color: #dedede;
    }
    
    .tab {
      bottom: 120rpx;
    }
    
    .tab_1 {
      position: fixed;
      bottom: 50rpx;
      width: 200rpx;
      font-size: 26rpx;
      left: 50%;
      margin-left: -45rpx;
      height: 100rpx;
    }
    
    .tab_2 {
      right: 30rpx;
      position: fixed;
    }
    
    /* 聊天 */
    
    .my_right {
      float: right;
      margin-top: 30rpx;
      position: relative;
    }
    
    .my_audio {
      height: 60rpx;
      width: 60rpx;
      z-index: 2;
      position: relative;
      top: 10rpx;
      left: 20rpx;
    }
    
    .you_left {
      margin-top: 30rpx;
      float: left;
      position: relative;
      padding-left: 5rpx;
    }
    
    .new_img {
      width: 85rpx;
      height: 85rpx;
      overflow: hidden;
    }
    
    .page_r {
      float: right;
    }
    
    .new_txt {
      min-width: 380rpx;
      width: 460rpx;
      word-break: break-all;
    }
    
    .new_txt_my {
      border-radius: 7rpx;
      background: #9fe75a;
      position: relative;
      right: 30rpx;
      min-height: 50rpx;
      padding: 17rpx 30rpx 17rpx 30rpx;
      float: right;
      border: 1px solid #d0d0d0;
    }
    
    .new_txt_my .arrow {
      position: absolute;
      z-index: 2;
      width: 40rpx;
      right: -38rpx;
    }
    
    .new_txt_my .arrow em {
      position: absolute;
      border-style: solid;
      border-width: 15rpx;
      border-color: transparent transparent transparent #d0d0d0;
      top: 1rpx;
    }
    
    .new_txt_my .arrow span {
      position: absolute;
      top: 5rpx;
      border-style: solid;
      border-width: 15rpx;
      border-color: transparent transparent transparent #9fe75a;
    }
    
    .new_txt_my_2 {
      word-break: break-all;
      border-radius: 7rpx;
      background: #9fe75a;
      min-width: 330rpx;
      max-width: 530rpx;
      padding: 17rpx 30rpx 17rpx 30rpx;
      float: right;
    }
    
    .new_txt_ai {
      width: 460rpx;
      border-radius: 7rpx;
      left: 20rpx;
      background-color: #fff;
      position: relative;
      border: 1px solid #d0d0d0;
      float: left;
    }
    
    .new_txt_ai .arrow {
      position: relative;
      width: 40rpx;
      left: -30rpx;
    }
    
    .new_txt_ai .arrow em {
      position: absolute;
      border-style: solid;
      border-width: 15rpx;
      top: 20rpx;
      border-color: transparent #d0d0d0 transparent transparent;
    }
    
    .new_txt_ai .arrow span {
      position: absolute;
      top: 20rpx;
      border-style: solid;
      border-width: 15rpx;
      border-color: transparent #fff transparent transparent;
      left: 2rpx;
    }
    
    .ai_content {
      word-break: break-all;
      padding: 17rpx 30rpx 17rpx 30rpx;
    }
    
    .sanjiao {
      top: 25rpx;
      position: relative;
      width: 0px;
      height: 0px;
      border-width: 15rpx;
      border-style: solid;
    }
    
    .my {
      border-color: transparent transparent transparent #9fe75a;
    }
    
    .you {
      border-color: transparent #fff transparent transparent;
    }
    
    ._span {
      border-color: #fff transparent transparent;
      top: -17px;
    }
    
    .is_ai_btn {
      border-radius: 0 0 7px 7px;
      border-top: 1px solid #d0d0d0;
      background: white;
      position: relative;
      bottom: 0;
      left: 0;
      width: 100%;
      height: 80rpx;
      line-height: 80rpx;
      display: flex;
      flex-direction: row;
      text-align: center;
    }
    
    .is_ai_btn view {
      width: 50%;
    }
    
    .is_ai_btn image {
      width: 32rpx;
      position: relative;
      top: 4rpx;
      height: 32rpx;
    }
    
    .is_ai_btn .two {
      border-left: 1px solid #d0d0d0;
    }
    
    .yes_problem_log {
      border-top: 1px solid #d0d0d0;
      height: 80rpx;
      text-align: center;
      line-height: 80rpx;
    }
    
    .voice_icon {
      width: 60rpx;
      height: 60rpx;
      margin: 0 auto;
      padding: 10rpx 10rpx 10rpx 10rpx;
    }
    
    .add_icon {
      width: 70rpx;
      height: 70rpx;
      margin: 0 auto;
      padding: 20rpx 10rpx 10rpx 15rpx;
    }
    
    .voice_ing {
      width: 90%;
      height: 75rpx;
      line-height: 85rpx;
      text-align: center;
      border-radius: 15rpx;
      border: 1px solid #d0d0d0;
    }
    
    .zezhao {
      height: 100%;
      position: absolute;
      top: 0;
      left: 0;
      z-index: 2;
      width: 100%;
      background: rgba(0, 0, 0, 0.5);
    }
    
    .in_voice_icon {
      z-index: 3;
      left: 0;
      bottom: 0;
      width: 100%;
      position: absolute;
      height: 500rpx;
      background: #f8f8f8;
    }
    
    .in_voice_icon .item {
      position: relative;
      left: 50%;
      margin-left: -60rpx;
      margin-top: 180rpx;
      text-align: center;
      width: 120rpx;
    }
    
    .in_voice_icon .img {
      width: 120rpx;
      height: 120rpx;
      border-radius: 15rpx;
    }
    
    .in_voice_icon .text {
      font-size: 32rpx;
      margin-top: 20rpx;
      background: white;
      width: 200rpx;
      margin-left: -40rpx;
      border-radius: 15rpx;
      height: 80rpx;
      line-height: 80rpx;
    }
    
    .sendmessage {
      width: 100%;
      z-index: 2;
      display: flex;
      position: fixed;
      bottom: 0px;
      background-color: #f8f8f8;
      flex-direction: row;
      height: 100rpx;
    }
    
    .sendmessage input {
      width: 78%;
      height: 80rpx;
      line-height: 80rpx;
      font-size: 28rpx;
      margin-top: 10rpx;
      margin-left: 20rpx;
      border-bottom: 1px solid #d0d0d0;
      padding-left: 20rpx;
    }
    
    .sendmessage button {
      border: 1px solid white;
      width: 18%;
      height: 80rpx;
      background: #0c0;
      color: white;
      line-height: 80rpx;
      margin-top: 10rpx;
      font-size: 28rpx;
    }
    
    .hei {
      height: 20rpx;
    }
    
    .history {
      /* height: 73%; */
      height: 88%;
      display: flex;
      font-size: 14px;
      line-height: 50rpx;
      position: relative;
      top: 20rpx;
    }
    
    .icno_kf {
      position: fixed;
      bottom: 160rpx;
      margin: 0 auto;
      text-align: center;
      left: 50%;
      margin-left: -40rpx;
      width: 100rpx;
      height: 100rpx;
      border-radius: 50%;
    }
    

    引用的util文件源码:

    
    // 手机号码验证
    function isUnicoms(mobileNo) {
        //移动:134(0 - 8) 、135、136、137、138、139、147、150、151、152、157、158、159、178、182、183、184、187、188、198 
        //联通:130、131、132、145、155、156、175、176、185、186、166
        //电信:133、153、173、177、180、181、189、199 
      // 1,移动 2,联通 3,电信
      var move = /^((134)|(135)|(136)|(137)|(138)|(139)|(147)|(150)|(151)|(152)|(157)|(158)|(159)|(178)|(182)|(183)|(184)|(187)|(188)|(198))\d{8}$/g;
      var link = /^((130)|(131)|(132)|(155)|(156)|(145)|(185)|(186)|(176)|(175)|(170)|(171)|(166))\d{8}$/g;
      var telecom = /^((133)|(153)|(173)|(177)|(180)|(181)|(189)|(199))\d{8}$/g;
      if (move.test(mobileNo)) {
        return '1';
      } else if (link.test(mobileNo)) {
        return '2';
      } else if (telecom.test(mobileNo)) {
        return '3';
      } else {
        return '非三网号段';
      }
    }
    // 网络请求
    function request(url, method, data, message, _success, _fail) {
      wx.showNavigationBarLoading()
      if (message != "") {
        wx.showLoading({
          title: message
        })
      }
      wx.request({
        url: url,
        data: data,
        header: {
          'content-type': 'application/x-www-form-urlencoded'
        },
        method: method,
        success: function (res) {
          _success(res)
          wx.hideNavigationBarLoading()
          if (message != "") {
            wx.hideLoading()
          }
        },
        fail: function (err) {
          if (err) {
            _fail(err)
          }
          wx.hideNavigationBarLoading()
          if (message != "") {
            wx.hideLoading()
          }
        },
      })
    }
    
    //上传语音
    function up_audio(url, audioSrc,name, data, _succ, _fail) {
      const uploadTask = wx.uploadFile({
          url: url, //仅为示例,非真实的接口地址
          filePath: audioSrc,
          name: name,
          formData: data,
          header: {
            "content-type": "multipart/form-data"
          },
          success: function (res) {
            _succ(res)
          },fail:function(err){
            _fail(err)
          }
        })
        uploadTask.onProgressUpdate((res) => {
          console.log('audio上传进度', res.progress)
          console.log('audio已经上传的数据长度', res.totalBytesSent)
          console.log('audio预期需要上传的数据总长度', res.totalBytesExpectedToSend)
        })
    }
    
    function formatTime(unixtime) {
      var dateTime = new Date(parseInt(unixtime))
      var year = dateTime.getFullYear();
      var month = dateTime.getMonth() + 1;
      var day = dateTime.getDate();
      var hour = dateTime.getHours();
      var minute = dateTime.getMinutes();
      var second = dateTime.getSeconds();
      var now = new Date();
      var now_new = Date.parse(now.toDateString());  //typescript转换写法
      var milliseconds = now_new - dateTime;
      var timeSpanStr =  hour + ':' + minute;
      // var timeSpanStr = year + '-' + month + '-' + day + ' ' + hour + ':' + minute;
      return timeSpanStr;
    }
    module.exports = {
      request: request,
      isUnicoms: isUnicoms,
      formatTime:formatTime,
      up_audio: up_audio
    }

    引用的app.js

    //app.js
    var util = require('utils/util.js');
    App({
      onLaunch: function () {
        var that = this;
        that.http_session = '';
        return that.promise = new Promise(function (resolve) {
          // that.webS_url = 'ws://192.168.199.147:7041';//填你请求的地址
          // that.httpUrl = 'http://192.168.199.147:7051'//填你请求的测试
          wx.login({
            success: function (res) {
              var data = {
                code: res.code
              }
              if (res.code) {
                //发起网络请求
                var url = that.httpUrl + '/v1/user/login.do';
                util.request(url, 'POST', data, '', function (res) {
                  console.log(res);
                  that.http_session = res.data.body;
                  resolve(that.http_session);
                }, function (err) {
                  console.log(err);
                })
              } else {
                console.log('登录失败!' + res.errMsg)
              }
            }
          });
        })
      },
      // 提交formid
      form_id_bg: function (formId) {
        console.log('form_id_bg执行了')
        let url = this.httpUrl + '/v1/formid/saveFormid.do';
        this.promise.then(function (http_session) {
          let data = {
            session: http_session,
            // minipid: '10000',
            formId: formId
          }
          util.request(url, 'post', data, '', function (res) {
          })
        })
      },
      onShow: function (even) {
        var e;
        // if (even.referrerInfo.extraData && even.referrerInfo.extraData.foo) {
        //   e = even.referrerInfo.extraData.foo
        // }
        if (e && e.appid) {
          this.appid = e.appid;
        }
      }
    })

    群聊的链接:https://blog.csdn.net/qq_35713752/article/details/78688311

    展开全文
  • 我想用signalR开发微信小程序聊天功能 小程序有websocket API,我想大致流程应该是小程序端用websocket连接服务器端的signalR,但是具体的流程我又不清楚,因为我其他的功能都是http访问,使用wx.request这样的...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 86,375
精华内容 34,550
关键字:

小程序聊天功能

友情链接: ERP进销存.rar