微信小程序?qū)崿F(xiàn)即時(shí)通信聊天功能的實(shí)例代碼
項(xiàng)目背景:小程序中實(shí)現(xiàn)實(shí)時(shí)聊天功能
一、服務(wù)器域名配置
配置流程

配置參考URL:https://developers.weixin.qq.com/miniprogram/dev/api/api-network.html
二、nginx中配置反向代理加密websocket(wss)
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;#這里是服務(wù)端證書路徑
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) {
#一級(jí)目錄
rewrite ^(.*)$ /index.php?s=$1 last;
break;
}
#wss配置
client_max_body_size 100m;
proxy_redirect off;
proxy_pass http://websocket;#反向代理轉(zhuǎn)發(fā)地址
proxy_set_header Host $host;# http請(qǐng)求的主機(jī)域名
proxy_set_header X-Real-IP $remote_addr;# 遠(yuǎn)程真實(shí)IP地址
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;#反向代理之后轉(zhuǎn)發(fā)之前的IP地址
proxy_read_timeout 604800s;#websocket心跳時(shí)間,默認(rèn)是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;
}
三、安裝swoole
編譯安裝:
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/; //進(jìn)入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 //查看是否安轉(zhuǎn)上了swoole.so (注意:此文件下邊都是你安裝的拓展) vim /phpstudy/server/php/etc/php.ini //在php.ini添加extension=swoole.so加入到文件最后一行 lnmp restart; //重啟nginx php -m; //查看phpinfo,這時(shí)候swoole拓展已經(jīng)裝上了
四、服務(wù)器端運(yùn)行程序
1、創(chuàng)建server.php放到項(xiàng)目的根目錄即可
<?php
//實(shí)例化一個(gè)swoole的websocket服務(wù)監(jiān)聽本機(jī)的9501端口
$server = new swoole_websocket_server("服務(wù)器IP", 9090);
//只需要綁定要監(jiān)聽的ip和端口。如果ip指定為127.0.0.1,則表示客戶端只能位于本機(jī)才能連接,其他計(jì)算機(jī)無法連接。
//端口這里指定為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();
?>
2、由于swoole_server只能運(yùn)行在CLI模式下,所以不要試圖通過瀏覽器進(jìn)行訪問,這樣是無效的,我們?cè)诿钚邢旅鎴?zhí)行,注意一定要找到php的絕對(duì)路徑php server.php (這行代碼的意思是,把程序在服務(wù)器跑起來)
注意:php server.php命令運(yùn)行后,下面的黑框關(guān)閉后將無法聊天。所以一般使用命令:nohup php server.php &

五、客戶端
1、網(wǎng)頁代碼
<!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">
內(nèi)容:<textarea id="content"></textarea>
收信人:<input type="text" id="touser">
<input type="button" id="send-btn" value="發(fā)送">
<input type="button" id="close-btn" value="關(guān)閉">
</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>
2、小程序端的代碼
Uitls/websocket.js:
var url = 'wss://www.xxx.cn';//服務(wù)器地址
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
})
//接受服務(wù)器消息
wx.onSocketMessage(func);//func回調(diào)可以拿到服務(wù)器返回的數(shù)據(jù)
});
wx.onSocketError(function (res) {
wx.showToast({
title: 'websocket連接失敗,請(qǐng)檢查!',
icon: "none",
duration: 2000
})
})
}
//發(fā)送消息
function send(msg) {
wx.sendSocketMessage({
data: msg
});
}
module.exports = {
connect: connect,
send: send
}
JS:
// pages/socks/socks.js
const app = getApp()
var websocket = require('../../utils/websocket.js');
var utils = require('../../utils/util.js');
Page({
/**
* 頁面的初始數(shù)據(jù)
*/
data: {
newslist: [],
userInfo: {},
scrollTop: 0,
increase: false,//圖片添加區(qū)域隱藏
aniStyle: true,//動(dòng)畫效果
message: "",
previewImgList: []
},
/**
* 生命周期函數(shù)--監(jiān)聽頁面加載
*/
onLoad: function () {
var that = this
if (app.globalData.userInfo) {
this.setData({
userInfo: app.globalData.userInfo
})
}
//調(diào)通接口
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
})
},
//事件處理函數(shù)
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()
}
},
//監(jiān)聽input值的改變
bindChange(res) {
this.setData({
message: res.detail.value
})
},
cleanInput() {
//button會(huì)自動(dòng)清空,所以不能再次清空而是應(yīng)該給他設(shè)置目前的input值
this.setData({
message: this.data.message
})
},
increase() {
this.setData({
increase: true,
aniStyle: true
})
},
//點(diǎn)擊空白隱藏message下選框
outbtn() {
this.setData({
increase: false,
aniStyle: true
})
},
//發(fā)送圖片
chooseImage() {
var that = this
wx.chooseImage({
count: 1, // 默認(rèn)9
sizeType: ['original', 'compressed'], // 可以指定是原圖還是壓縮圖,默認(rèn)二者都有
sourceType: ['album', 'camera'], // 可以指定來源是相冊(cè)還是相機(jī),默認(rèn)二者都有
success: function (res) {
// 返回選定照片的本地文件路徑列表,tempFilePath可以作為img標(biāo)簽的src屬性顯示圖片
var tempFilePaths = res.tempFilePaths
// console.log(tempFilePaths)
wx.uploadFile({
url: 'wss://www.xxx.cn', //服務(wù)器地址
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()
}
}
})
}
})
},
//圖片預(yù)覽
previewImg(e) {
var that = this
//必須給對(duì)應(yīng)的wxml的image標(biāo)簽設(shè)置data-set=“圖片路徑”,否則接收不到
var res = e.target.dataset.src
var list = this.data.previewImgList //頁面的圖片集合數(shù)組
//判斷res在數(shù)組中是否存在,不存在則push到數(shù)組中, -1表示res不存在
if (list.indexOf(res) == -1) {
this.data.previewImgList.push(res)
}
wx.previewImage({
current: res, // 當(dāng)前顯示圖片的http鏈接
urls: that.data.previewImgList // 需要預(yù)覽的圖片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節(jié)點(diǎn)的下邊界坐標(biāo)
})
res[1].scrollTop // 顯示區(qū)域的豎直滾動(dòng)位置
})
},
})
WXML:
<!--pages/socks/socks.wxml-->
<view class="news" bindtap='outbtn'>
<view class="chat-notice" wx:if="{{userInfo}}">系統(tǒng)消息: 歡迎 {{ 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="請(qǐng)輸入聊天內(nèi)容.." value="{{massage}}" bindinput='bindChange'></input>
<view class="add" bindtap='increase'>+</view>
<button type="primary" bindtap='send' formType="reset" size="small" button-hover="blue">發(fā)送</button>
</form>
<view class='increased {{aniStyle?"slideup":"slidedown"}}' wx:if="{{increase}}">
<view class="image" bindtap='chooseImage'>相冊(cè) </view>
</view>
</view>
WXSS:
/* pages/socks/socks.wxss */
page {
background-color: #f7f7f7;
height: 100%;
}
/* 聊天內(nèi)容 */
.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;
}
/* 信息輸入?yún)^(qū)域 */
.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);
}
}
總結(jié)
以上所述是小編給大家介紹的微信小程序?qū)崿F(xiàn)即時(shí)通信聊天功能的實(shí)例代碼,希望對(duì)大家有所幫助,如果大家有任何疑問請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
相關(guān)文章
JS簡單實(shí)現(xiàn)移動(dòng)端日歷功能示例
這篇文章主要介紹了JS簡單實(shí)現(xiàn)移動(dòng)端日歷功能的方法,涉及javascript針對(duì)日期與時(shí)間的操作及顯示相關(guān)技巧,需要的朋友可以參考下2016-12-12
???????基于el-table和el-pagination實(shí)現(xiàn)數(shù)據(jù)的分頁效果流程詳解
本文主要介紹了???????基于el-table和el-pagination實(shí)現(xiàn)數(shù)據(jù)的分頁效果,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-11-11
event.keyCode鍵碼值表 附只能輸入特定的字符串代碼
非常不錯(cuò)的應(yīng)用,讓文本框里只能輸入money大家看下具體的實(shí)現(xiàn)代碼,真是只有想到,原理很簡單。2009-05-05
原生JS封裝animate運(yùn)動(dòng)框架的實(shí)例
下面小編就為大家?guī)硪黄鶭S封裝animate運(yùn)動(dòng)框架的實(shí)例。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-10-10
javascript+xml技術(shù)實(shí)現(xiàn)分頁瀏覽
基于web的技術(shù)中,分頁是一個(gè)老的不能再老的,但大家津津樂道的問題,隨著xml技術(shù)的日漸應(yīng)用,把xml應(yīng)用到分頁當(dāng)中,也是一種可能,當(dāng)然網(wǎng)上的教程很多,當(dāng)我都是看得稀里糊涂,索性自己寫一個(gè),與大家分享、指正。2008-07-07
使用JavaScript實(shí)現(xiàn)按鈕的漣漪效果實(shí)例代碼
近來看到個(gè)不錯(cuò)的按鈕點(diǎn)擊效果,當(dāng)點(diǎn)擊時(shí)產(chǎn)生一次水波漣漪效果,挺好玩的,下面這篇文章主要給大家介紹了關(guān)于使用JavaScript實(shí)現(xiàn)按鈕漣漪效果的相關(guān)資料,需要的朋友可以參考下2022-11-11
用showModalDialog彈出頁面后,提交表單總是彈出一個(gè)新窗口
用showModalDialog彈出頁面后,提交表單總是彈出一個(gè)新窗口,其實(shí)解決方法很簡單如下。2009-07-07
JavaScript實(shí)現(xiàn)div的鼠標(biāo)拖拽效果
這篇文章主要為大家詳細(xì)介紹了JavaScript實(shí)現(xiàn)div的鼠標(biāo)拖拽效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-11-11
javascript實(shí)現(xiàn)的使用方向鍵控制光標(biāo)在table單元格中切換
最近公司開發(fā)ERP項(xiàng)目,要求商品入庫選擇貨架號(hào)時(shí)支持使用方向鍵快速選擇,以提高入庫效率。2010-11-11

