項目背景:小程序中實現實時聊天功能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"
>
$(
"#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
|
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({
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