//發送圖片
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;
that.setData({
loading: true,
increase: false
});
wx.uploadFile({
url: utils.basePath + '/users/upload_avatar',
filePath: tempFilePaths[0],
name: 'avatar',
headers: {
'Content-Type': 'form-data'
},
success: function(res) {
that.setData({
loading: false
});
var result = JSON.parse(res.data);
if (result.status == 200) {
//圖片上傳成功,將聊天記錄保存數據庫
var chatInfo = that.data.chatInfo;
chatInfo.chat_content = result.payload.avatar_path;
chatInfo.chat_type = 1;
chatInfo = JSON.stringify(chatInfo);
websocket.send(chatInfo);
//接受服務器消息
wx.onSocketMessage(function(res) {
var data = JSON.parse(res.data).data;
data[0].flagtime = true;
for (var i = 1; i < data.length; i++) {
var currenttime = new Date(data[i].created_date).getTime();
var begintime = new Date(data[i - 1].created_date).getTime();
if (currenttime - begintime > 1000 * 60) {
data[i].flagtime = true;
} else {
data[i].flagtime = false;
}
}
that.setData({
newslist: data
});
//將聊天界面定位到最新的聊天記錄
that.bottom();
});
} else {
$Toast({
content: result.err,
type: 'error'
});
}
}
});
}
});
}
視頻發送實際上和圖片發送幾乎一致,就是將wx.chooseImage換成wx.chooseVideo就可以,但是視頻上傳這裏面有幾個坑需要逃避一下:
wx.chooseVideo有個屬性compressed參數可以設置視頻是否需要壓縮,默認是true,視頻會經過壓縮上傳,經過實測發現視頻經過壓縮清晰度極低,所以可以攜帶compressed參數關閉視頻壓縮。
視頻大小實際上和微信是保持一致的,無法發送超過24M的視頻,但是我測試的時候發現超過1M的服務器一直報413狀態碼提示視頻過大,實際上就是我們後端沒有設置body最大的長度,比如我是Nginx對上傳的域名pic.niyueling.cn增加了client_max_body_size實行,設置爲25M,就可以躲過413狀態這個坑。
接下來我們一樣看下代碼:
//發送視頻
chooseVideo() {
var that = this;
wx.chooseVideo({
sourceType: ['album', 'camera'],
maxDuration: 60,
compressed: false,
camera: 'back',
success: function(res) {
var tempFilePaths = res.tempFilePath;
that.setData({
loading: true,
increase: false
});
wx.uploadFile({
url: utils.basePath + '/users/upload_video',
filePath: tempFilePaths,
name: 'mp4_url',
headers: {
'Content-Type': 'form-data'
},
success: function(res) {
if (res.statusCode == 413) {
that.setData({
loading: false
});
$Toast({
content: '視頻過大,請重新上傳',
type: 'error'
});
} else {
that.setData({
loading: false
});
var result = JSON.parse(res.data);
if (result.status == 200) {
//上傳視頻操作
var chatInfo = that.data.chatInfo;
chatInfo.chat_content = result.payload;
chatInfo.chat_type = 2;
chatInfo = JSON.stringify(chatInfo);
websocket.send(chatInfo);
//接受服務器消息
wx.onSocketMessage(function(res) {
var data = JSON.parse(res.data).data;
data[0].flagtime = true;
for (var i = 1; i < data.length; i++) {
var currenttime = new Date(data[i].created_date).getTime();
var begintime = new Date(data[i - 1].created_date).getTime();
if (currenttime - begintime > 1000 * 60) {
data[i].flagtime = true;
} else {
data[i].flagtime = false;
}
}
that.setData({
newslist: data
});
that.bottom();
});
} else {
$Toast({
content: result.err,
type: 'error'
});
}
}
}
});
}
});
}
const util = require('./util.js');
//發起websocket連接
function connect(user, func) {
wx.connectSocket({
url: util.wssPath + '/chat/v1/message?friendphone=' + user.friendInfo.account + '&userphone=' + user.userInfo.account + '&app_sid=' + user.userInfo.app_sid,
header: {
'content-type': 'application/json'
},
success: function (res) {
console.log(res)
},
fail: function (res) {
console.log(res);
}
});
wx.onSocketOpen(function (res) {
//接受服務器消息
wx.onSocketMessage(func);//func回調可以拿到服務器返回的數據
});
wx.onSocketError(function (res) {
wx.showToast({
title: res.errMsg,
icon: "none",
duration: 1000
});
});
}
//發送消息
function send(msg) {
wx.sendSocketMessage({
data: msg
});
}
module.exports = {
connect: connect,
send: send
}
然後在會話界面的onLoad方法連接websocket,連接成功接口會返回歷史聊天記錄,可以渲染出聊天界面。我們可以看下onLoad的關鍵代碼:
websocket.connect(this.data.chatInfo, function(res) {
if (JSON.parse(res.data).data.length == 0) {
that.setData({
newslist: []
});
} else {
var data = JSON.parse(res.data).data;
data[0].flagtime = true;
for (var i = 1; i < data.length; i++) {
var currenttime = new Date(data[i].created_date).getTime();
var begintime = new Date(data[i - 1].created_date).getTime();
if (currenttime - begintime > 1000 * 60) {
data[i].flagtime = true;
} else {
data[i].flagtime = false;
}
}
that.setData({
newslist: data
});
that.bottom();
}
});
//封裝聊天記錄參數
var chatInfo = that.data.chatInfo;
chatInfo.chat_content = that.data.message;
chatInfo.chat_type = 0;
chatInfo = JSON.stringify(chatInfo);
websocket.send(chatInfo);
//接受服務器消息
wx.onSocketMessage(function(res) {
var data = JSON.parse(res.data).data;
data[0].flagtime = true;
for (var i = 1; i < data.length; i++) {
var currenttime = new Date(data[i].created_date).getTime();
var begintime = new Date(data[i - 1].created_date).getTime();
if (currenttime - begintime > 1000 * 60) {
data[i].flagtime = true;
} else {
data[i].flagtime = false;
}
}
that.setData({
newslist: data
});
that.bottom();
});
upstream backend_chatws {
server 127.0.0.1:3001 weight=10;
}
server {
listen 443 ssl;
server_name ws.niyueling.cn;
ssl_certificate /etc/nginx/ctr/ws_niyueling_cn.crt;
ssl_certificate_key /etc/nginx/ctr/ws_niyueling_cn.key;
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 5m;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
server_tokens off;
access_log /var/log/nginx/api.log main;
location / {
client_max_body_size 100m;
proxy_redirect off;
proxy_pass http://backend_chatws;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_read_timeout 604800s;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
剛纔已經說過了本篇文章使用express-ws庫來封裝websocket,接下來我們看看express-ws庫的基本使用。因爲我們正式開發一般後端不可能把所有接口寫在同一個文件中,所以我們這邊也通過分文件來實現。首先npm安裝express-ws依賴,然後在app.js引入express-ws:
var express = require('express');
var app = express();
var expressWs = require('express-ws')(app);
var chat = require('./routes/chat');
app.use('/chat/v1', chat);
app.listen(3001);
router.ws('/message', function (ws, req) {
var par = paramAll(req);
if (!par.friendphone || !par.userphone || !par.app_sid) {
return ws.send(JSON.stringify({ code: 0, msg: '參數不全!' }));
}
//查詢用戶歷史記錄
chatDao.getOnlineChat(par, function (err, data) {
if (err) {
return ws.send(JSON.stringify({
code: 0,
msg: err
}));
}
return ws.send(JSON.stringify({
code: 1,
data: data
}));
});
ws.on('message', function (msg) {
par.msg = JSON.parse(msg);
//將記錄添加到數據庫,並返回最新記錄列表
chatDao.saveOnlineChat(par.msg, function (err, data) {
if (err) {
return ws.send(JSON.stringify({
code: 0,
msg: err
}));
}
return ws.send(JSON.stringify({
code: 1,
data: data
}));
});
});
});
數據庫操作邏輯實際上也分別對應兩部分,websocket連接時會返回兩個好友間的歷史聊天記錄:
async.waterfall([
function (callback) {
connection.beginTransaction(function (err) {
return callback(err);
});
},
//通過friendphone查詢好友信息
function (callback) {
var sql = 'select username, avatar from users where account = ? and app_sid = ?';
var value = [data.friendphone, data.app_sid];
connection.query(sql, value, function (err, result) {
if (err) {
return callback(err);
}
if (!result[0]) {
return callback('用戶不存在!');
}
data.friendname = result[0].username;
data.friendavatar = result[0].avatar;
return callback(null, 200);
});
},
//通過userphone查詢好友信息
function (info, callback) {
var sql = 'select username, avatar from users where account = ? and app_sid = ?';
var value = [data.userphone, data.app_sid];
connection.query(sql, value, function (err, result) {
if (err) {
return callback(err);
}
if (!result[0]) {
return callback('用戶不存在!');
}
data.username = result[0].username;
data.useravatar = result[0].avatar;
return callback(null, 200);
});
},
function (release_info, callback) {
var sql = 'select id, friendphone, friendname, friendavatar, app_sid, DATE_FORMAT(created_date, "%Y-%m-%d %H:%i:%s") as created_date, userphone, username, useravatar, content, chat_type from online_chat ' +
'where (friendphone = ? and userphone = ?) or (friendphone = ? and userphone = ?)';
var value = [data.friendphone, data.userphone, data.userphone, data.friendphone];
connection.query(sql, value, function (err, result) {
if (err) {
return callback(err);
}
var del_info = result && result.length > 0 ? result : null;
if (!del_info) {
return callback(null, true, []);
}
return callback(null, true, del_info);
});
}
], function (DbErr, isSuccess, uidOrInfo) {
if (DbErr || !isSuccess) {
connection.rollback(function () {
connection.release();
});
return cb(DbErr);
}
connection.commit(function (e) {
if (e) {
connection.rollback(function () {
connection.release();
});
return cb(e);
}
connection.release();
cb(null, uidOrInfo);
});
});
async.waterfall([
function (callback) {
connection.beginTransaction(function (err) {
return callback(err);
});
},
function (callback) {
var sql = 'insert into online_chat set ?';
var value = {
friendphone: data.friendInfo.account,
friendname: data.friendInfo.username,
friendavatar: data.friendInfo.avatar,
app_sid: data.friendInfo.app_sid,
userphone: data.userInfo.account,
username: data.userInfo.username,
useravatar: data.userInfo.avatar,
created_date: new Date(),
status: 1,
content: data.chat_content,
chat_type: data.chat_type
};
connection.query(sql, value, function (err, result) {
if (err) {
return callback(err);
}
if (result.affectedRows == 0) {
return callback('聊天出現故障!');
}
return callback(null, '保存聊天記錄成功!');
});
},
function (release_info, callback) {
var sql = 'select id, friendphone, friendname, friendavatar, app_sid, DATE_FORMAT(created_date, "%Y-%m-%d %H:%i:%s") as created_date, userphone, username, useravatar, content, chat_type from online_chat ' +
'where (friendphone = ? and userphone = ?) or (friendphone = ? and userphone = ?)';
var value = [data.friendInfo.account, data.userInfo.account, data.userInfo.account, data.friendInfo.account];
connection.query(sql, value, function (err, result) {
if (err) {
return callback(err);
}
var del_info = result && result.length > 0 ? result : null;
if (!del_info) {
return callback(null, true, []);
}
return callback(null, true, del_info);
});
}
], function (DbErr, isSuccess, uidOrInfo) {
if (DbErr || !isSuccess) {
connection.rollback(function () {
connection.release();
});
return cb(DbErr);
}
connection.commit(function (e) {
if (e) {
connection.rollback(function () {
connection.release();
});
return cb(e);
}
connection.release();
cb(null, uidOrInfo);
});
});
到這裏我們使用express-ws改寫會話系統就完成了,我們可以測試下:
https://gitee.com/mqzuimeng_admin/wx_blog.git
本文分享自微信公衆號 - 程序猿周先森(zhanyue_org)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。