微信小程序实现即时通讯聊天功能的实例代码

项目背景:小程序中实现实时聊天功能javascript

1、服务器域名配置php

配置流程css

配置参考URL:https://developers.weixin.qq.com/miniprogram/dev/api/api-network.htmlhtml

2、nginx中配置反向代理加密websocket(wss)java

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
upstream websocket{
  hash $remote_addr consistent;
  server 127.0.0.1:9090 weight=5 max_fails=3 fail_timeout=30s;
}
server {
  listen 80;
  server_name www.xxxx.cn;
  rewrite ^(.*)$ https: //$host$1 permanent;
}
server
  {
  listen 443;
   server_name www.xxxx.cn;
   ssl on;
   root /home/wwwroot/yzcp;
   index index.php index.html index.htm;
   ssl_certificate /usr/local/nginx/conf/cert/1526060965511.pem; #这里是服务端证书路径
   ssl_certificate_key /usr/local/nginx/conf/cert/1526060965511.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;
  #隐藏index.php
   location / {
  #index index.php;
  deny 127.0.0.1;
    if (!-e $request_filename) {
     #一级目录
     rewrite ^(.*)$ /index.php?s=$1 last;
     break ;
    }
    #wss配置
    client_max_body_size 100m;
    proxy_redirect off;
    proxy_pass http: //websocket;#反向代理转发地址
    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" ;
   }
   location ~ .+\.php {
    fastcgi_pass unix:/tmp/php-cgi.sock;
    fastcgi_index index.php;
    include fastcgi_params;
    set $path_info "" ;
    set $real_script_name $fastcgi_script_name;
     if ($fastcgi_script_name ~ "^(.+?\.php)(/.+)$" ) {
     set $real_script_name $1;
     set $path_info $2;
    }
    fastcgi_param SCRIPT_FILENAME $document_root$real_script_name;
    fastcgi_param SCRIPT_NAME $real_script_name;
    fastcgi_param PATH_INFO $path_info;
   }
  
   #防盗链开始
   location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$
  {
  expires  30d;
  }
  location ~ .*\.(js|css)?$
  {
  expires  12h;
  }
  access_log /home/wwwlogs/www1537ucn.log;
  }

3、安装swoolejquery

编译安装:
nginx

?
1
2
3
4
5
6
7
8
9
10
wget http: //pecl.php.net/get/swoole-1.9.3.tgz  //下载swoole
tar -zvxf swoole-1.9.3.tgz  //解压swoole
cd swoole-1.9.3/;  //进入swoole
/usr/local/php54/bin/phpize;  //生成configure
./configure -- with -php-config=/usr/local/php/bin/php-config
make && make install   //安装
cd /phpstudy/server/php/lib/php/extensions/no-debug-non-zts-20121212 //查看是否安转上了swoole.so (注意:此文件下边都是你安装的拓展)
vim /phpstudy/server/php/etc/php.ini  //在php.ini添加extension=swoole.so加入到文件最后一行
lnmp restart; //重启nginx
php -m; //查看phpinfo,这时候swoole拓展已经装上了

4、服务器端运行程序web

一、建立server.php放到项目的根目录便可json

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
//实例化一个swoole的websocket服务监听本机的9501端口
$server = new swoole_websocket_server( "服务器IP" , 9090);
//只须要绑定要监听的ip和端口。若是ip指定为127.0.0.1,则表示客户端只能位于本机才能链接,其余计算机没法链接。
//端口这里指定为9090,能够经过netstat查看下该端口是否被占用。若是该端口被占用,可更改成其余端口,如9502,9503等。
$server->on( 'open' , function (swoole_websocket_server $server, $request) {
  echo "你好链接成功{$request->fd}\n" ;
});
$server->on( 'message' , function (swoole_websocket_server $server, $frame) {
  foreach($server->connections as $key => $fd) {
   $user_message = $frame->data;
   $server->push($fd, $user_message);
  }
});
$server->on( 'close' , function ($ser, $fd) {
  echo "client {$fd} closed\n" ;
});
$server->start();
?>

二、因为swoole_server只能运行在CLI模式下,因此不要试图经过浏览器进行访问,这样是无效的,咱们在命令行下面执行,注意必定要找到php的绝对路径php  server.php  (这行代码的意思是,把程序在服务器跑起来)小程序

注意:php server.php命令运行后,下面的黑框关闭后将没法聊天。因此通常使用命令:nohup php server.php &

5、客户端

一、网页代码

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
<!DOCTYPE html>
<html>
<head>
  <meta charset= "utf-8" >
  <title>聊天</title>
  <style type= "text/css" >
   #show{
    width: 600px;
    height: 300px;
    overflow-y: scroll;
   }
   .my-message{
    background-color: rgba(105, 105, 105, 0.64);
    color: #9e0505;
    width: 200px;
    float: right;
    padding: 10px;
   }
   .other-message{
    background-color: rgba(105, 105, 105, 0.64);
    color: #9e0505;
    width: 200px;
    float: left;
    padding: 10px;
   }
  </style>
</head>
<body>
  <div id= "show" ></div>
  <div class= "panel" >
   内容:<textarea id= "content" ></textarea>
   收信人:<input type= "text" id= "touser" >
   <input type= "button" id= "send-btn" value= "发送" >
   <input type= "button" id= "close-btn" value= "关闭" >
  </div>
</body>
<script src= "__PUBLIC__/js/jquery-1.10.2.min.js" charset= "utf-8" ></script>
<script type= "text/javascript" >
  var socket = new WebSocket( "wss://域名" );
  $( "#close-btn" ).click( function () {
   socket.close();
  })
  $( "#send-btn" ).click( function () {
   var touser = $( "#touser" ).val();
   var content = $( "#content" ).val();
   var htmlstr = "<div><p class='my-message'>我:" +content+ "</p></div>" ;
   $( "#show" ).append(htmlstr);
   socket.send(content+ "@" +touser);
  })
  socket.onmessage = function (p1) {
   var htmlstr = "<div><p class='other-message'>" +p1.data+ "</p></div>" ;
   $( "#show" ).append(htmlstr);
  }
</script>
</html>

二、小程序端的代码

Uitls/websocket.js:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
var url = 'wss://www.xxx.cn' ;//服务器地址
function connect(user, func) {
  wx.connectSocket({
  url: url,
  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回调能够拿到服务器返回的数据
  });
  wx.onSocketError( function (res) {
  wx.showToast({
   title: 'websocket链接失败,请检查!' ,
   icon: "none" ,
   duration: 2000
  })
  })
}
//发送消息
function send(msg) {
  wx.sendSocketMessage({
  data: msg
  });
}
module.exports = {
  connect: connect,
  send: send
}

JS:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
// pages/socks/socks.js
const app = getApp()
var websocket = require( '../../utils/websocket.js' );
var utils = require( '../../utils/util.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(JSON.parse(res.data))
   var list = []
   list = that.data.newslist
   list.push(JSON.parse(res.data))
   that.setData({
   newslist: list
   })
  })
  },
  // 页面卸载
  onUnload() {
  wx.closeSocket();
  wx.showToast({
   title: '链接已断开~' ,
   icon: "none" ,
   duration: 2000
  })
  },
  //事件处理函数
  send: function () {
  var flag = this
  if ( this .data.message.trim() == "" ) {
   wx.showToast({
   title: '消息不能为空哦~' ,
   icon: "none" ,
   duration: 2000
   })
  } else {
   setTimeout( function () {
   flag.setData({
    increase: false
   })
   }, 500)
   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 // 显示区域的竖直滚动位置
  })
  },
})

WXML:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
<!--pages/socks/socks.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>
  <!-- 历史消息 -->
<!-- <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.nickName == userInfo.nickName}}" >
<view style= "text-align: right;padding-right: 20rpx;" >
<text class= "name" >{{ item.nickName }}</text>
<image class= 'new_img' src= "{{userInfo.avatarUrl}}" ></image>
</view>
<view class= 'my_right' >
<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' >
<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>

WXSS:

?

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
/* 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{
display: block;
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;
width: 66%;
height: 100%;
line-height: 80rpx;
border-bottom: 1rpx solid #ccc;
padding:0 10rpx;
font-size: 35rpx;
color: #666;
}
.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;
}
.sendMessage button {
float: right;
font-size: 35rpx;
}
.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);
}
}

转载:https://www.jb51.net/article/145846.htm

相关文章
相关标签/搜索