1. 前言

上篇主要说了准备阶段和要学的基本知识,当然学的知识还是死的,还是要敲代码,下篇主要就是用上编学的知识实现本次课设任务

WebSocket之仿QQWeb即时聊天系统(上)
WebSocket之仿QQWeb即时聊天系统(下)
源码:
链接:https://pan.baidu.com/s/1xmCzP0TTLkWqgHnp62K9sA
提取码:Lin2
CSDN: https://download.csdn.net/download/RongLin02/19762352

本文原创,创作不易,转载请注明!!!
本文链接
个人博客:https://ronglin.fun/archives/238
PDF链接:见博客网站
CSDN: https://blog.csdn.net/RongLin02/article/details/117984160

白嫖容易,创作不易,希望能让大家有所收获。
所有copy到的代码均是度娘、csdn等可以查到的,我的课设项目绝大部分都是原创,只有一些特定的、不懂的用法是copy
基本上我查询的资料(例如API等)都会在博客中列出网站,有需要的童鞋可以直接点击查看
本博客原创,转载请注明!

仿QQWeb即时聊天系统
功能要求:
实现Web的点对点即时的文本消息聊天功能。
实现Web的表情的发送、接收和显示功能。
实现Web的图片的发送、接收和显示功能。
实现本地消息的存储,在离线的时候也能加载和查看历史消息;
要求使用WebSocket;

2. 服务器

服务器的代码逻辑比较简单,而且比较固定,所以先说服务器的代码逻辑
基本功能和上篇的那个一样,主要就是用socket.onsocket.emit,需要注意的是,所有用到这个两个方法的地方都要写到

io.on('connection', function(socket){
//这里
});

io.on的方法体中,应该是表示服务器和客户端建立好连接的部分。

2.1. 数据库

2.1.1. 配置代码

数据库是个好东西,存储数据可以帮我们省下很多的麻烦,接下来就说用nodejs如何连接数据库
要先安装模块

npm install mysql;

JavaScript代码

var mysql = require('mysql');
var connection = mysql.createConnection({host     : 'localhost',user     : 'root',password : '123456',database : 'test'
});

这些就是配置数据,先用require导包,然后创建连接,host是地址,端口如果不设置默认是3306,user,password就是登录数据库的账号密码,database就是要用到的数据库,模板性很强,就这么用就行了。

数据库建表


数据库名称为test
字段名称一共两个,一个是id另一个是password,类型都是varchar类型

2.1.2. 回调函数

用到数据库不得不提一个概念就是回调函数,因为JavaScript访问数据库是一个异步的过程,要用回调函数完成异步查询
彻底理解 Node.js 中的回调(Callback)函数
这是菜鸟教程有关回调函数的解释,如果想要熟练运用js完成复杂的功能,一定要好好理解回调函数
简单地说,就是当异步调用时,数据还没返回给调用者,但是调用者直接继续执行了。
举个例子,假设有个getValue()方法,它因为效率很低,需要10s才能将结果返回给result,在js中,这样写就错了

var value;
getValue(function(result){value = result;console.log(value);    //①
});
console.log(value);  //②

在①号的输出是有值的,而在②的输出就是一个空。原因是在执行完getValue方法之后,它会立即执行②的代码,但是这时候value的值还没得到,而在function内部,value的值已经得到了,输出出来的就是有结果的。
这样就需要一个回调函数callback,来告诉调用者:“我执行完了,我把结果返回给你,你可以继续执行需要部分了”所以案例代码要想用回调函数可以这样写

function getValuePlus(callback){getValue(function(result){callback(result);
});
}
//这样调用
getValuePlus(result =>{console.log(result)
})

由于时间紧迫,我并没深入的去学习回调函数的机制,只是简单的理解了一下,如有问题,请多多指点。

2.1.3. 数据库代码

说了这么多,其实就是数据库的查询过程是一个异步过程,调用者想要获取结果就得等数据返回。贴上代码

//从数据库中查询
function select_user(data,callback){//连接数据库开始查询let sql = 'SELECT * FROM user where id = \''+data.username+'\';';connection.query(sql,function (err, result) {if(err){console.log('[SELECT ERROR] - ',err.message);callback(null)}//用回调函数告诉调用者执行完了callback(result);});
}
//插入数据库
function insert_user(data){let sql ='INSERT INTO user VALUES (\''+data.username+'\',\''+data.password+'\');';connection.query(sql,(err,result)=>{if(err){console.log('[INSERT ERROR] - ',err.message);return;};});
}
//调用者
//从数据库中寻找
select_user(data,result=>{if(result.length){if(result[0].password != data.password){console.log('loginFail','密码错误!');return ;}}else{insert_user(data);}});

2.2. 常用方法

这个部分主要是根据需求,设计常用方法。

2.2.1. 主要逻辑方法

然后就是设计常用的方法了。
根据需求,要有收发文本数据的的方法,还要有收发图片的方法,验证登陆的方法,离线方法
基本的都是用socket.onsocket.emit实现

// 用户断开连接的功能
socket.on('disconnect', () => {});
//服务器向所有客户端广播的方法
io.emit('key', value)

2.2.2. 服务器和特定的客户端交互

因为好像只会群聊,但是如果和特定的客户端交互应该 怎么办呢
先上我查询的资料:
node如何使用socket.io向指定客户端发送消息
socket.io发送给指定的客户端
分享一个大佬的成品仿微信聊天室

当客户端成功连接服务器的时候,我会给它添加一个username的属性。
服务器对于每一个已经连接的客户端,会用存储在io.sockets.sockets数组里,然后根据我设置的username的属性去找到这个客户端
然后用socket.to(toSocket)方法单独给特定的客户端发消息。

//给客户端添加一个属性作为识别标记
socket.username = data.username;socket.on('sendMessage',data=>{// console.log(data.msg);// console.log(data.toName);// console.log(data.name);var toSocket = null//console.log(io.sockets.sockets)for (const key in io.sockets.sockets) {if (io.sockets.sockets[key].username == data.toName) {toSocket = keybreak}}if (toSocket) {// 发送给指定用户socket.to(toSocket).emit('receiveMessage', {msg:data.msg,fromName:data.name,time : data.time,name:data.toName})}
});

2.2.3. nodejs读取本地文件

在前端,一般来说,JavaScript读取文件相当麻烦而且还有限制。但是在服务器中,nodejs读取文件已经比较成熟了。
查询的资料:
NodeJS 文件操作 —— fs 基本使用
appendFile函数的基本用法
安装插件

npm install body-parser

贴代码

const url = require('url');
const fs = require('fs');
app.use(express.json())
app.use(express.urlencoded({ extended: false }))let url = './history/'+data.name+'To'+data.toName+'.txt';fs.appendFile(url,'\n'+JSON.stringify(data), err => {if (err) {console.log(err)}});if(data.toName != data.name){let t_url = './history/'+data.toName+'To'+data.name+'.txt';fs.appendFile(t_url,'\n'+JSON.stringify(data), err => {if (err) {console.log(err)}});}

主要是一个方法,就是appendFile()方法,它可以将新的内容追加到已有的文件中,如果文件不存在,则会创建一个新的文件。
fs.appendFile(文件名,数据,编码,回调函数(err));

3. 客户端

设计重点来了,接下来是对我的成果的各个部分的解释。
这部分主要是针对各个功能部分对于HTML、CSS、JavaScript代码的讲解,以及一些好用但是不好搜的用法
主要界面展示
界面1:

界面2:

3.1. 界面设计

3.1.1. 界面1

对于第一个界面是从网上参考的样式表,实在抱歉,我当时没保存下来网站,如有侵权联系我删除,本博客仅用于学习,完成课程设计。
这个主要是有两个CSS属性要提一下

/* 渐变函数 从右到左 */
background-image: linear-gradient(to right, #fbc2eb, #a6c1ee);
/*定义 2D 转换*/
transform: translate(-50%, -50%);

一个是渐变函数,一个是移动元素的,将元素块向上下左右移动,还是很使用的。

3.1.2. 界面切换

然后切换界面有两个方法
第一个方法就是设置两个块的display属性
第一个界面的display属性设置为none,然后把第二个界面的display属性设置为inline
第二个方法就是用Jquery语句,我就是用的这个

//隐藏登录窗口
$('.container').hide();
// 显示聊天窗口
$('.chat_container').fadeIn();

括号里的是要操作的div的类名,这个元素选择器的规则和CSS是一样的,$('#chat')就是选中id为chat的元素

3.1.3. 界面2

第二个界面盒装设计图

虽然丑了点,但是作为草图足够了,我的界面2也是这样设计的。
左侧是用户列表,中间是聊天信息,然后是功能栏,下方是一个可编辑的div,最下方是一个发送按钮。
可编辑的div的属性是

<div class="text_view" contenteditable="true" tabindex="0" id="_text_view"></div>

contenteditable是控制可编辑的,tabindex是控制tab键次序。

3.2. 逻辑设计

在HTML和CSS中将款式设计好之后,就该在js文件中设计动态逻辑了。
这部分是本次课设的重难点 也是查询资料时间最长的部分

3.2.1. 登录界面

首先是注册/登录按钮,这个btn就是将用户填写的数据提交给服务器,服务器是这样判断的逻辑,如果用户名存在就对比密码,密码正确,返回登录成功;如果密码错误,返回密码错误提示;如果用户名不存在就直接创建一个新用户。
返回登录成功之后,界面切换到界面2

界面2要说的点太多了,综合性很强,就简单的阐述一下逻辑吧

3.2.2. 用户列表

这个对应的是最左侧那一栏,要完成的逻辑是
文字居中,可点击,鼠标为点击样式
点击之后
背景颜色改变同时其他用户块背景颜色复原,标题框名称改为用户名
还有一个比较麻烦的部分就是聊天内容的切换,点击左侧不同的用户,要切换不同的聊天内容
我的解决方案是,给每一个用户一个聊天内容块,点击的时候就显示它的聊天内容块,隐藏其他内容块。

还有一个就是用户列表的更新,当有用户登陆或离线的时候,要能自动刷新用户列表。
这个功能我是用服务器,服务器给客户端一个消息,刷新用户列表,同时把当前在线的人名单传给客户端
当前用户列表和传回来的名单经行对比
有一个新用户的时候,就增加一个块,同时添加点击事件

var cell = document.createElement('div');//创建元素
cell.textContent = cilents[i];//设置div的文本内容
cell.style=userCellStyle;    //设置css样式
cell.classList.add('userCell');//添加类名
cell.id='userCell_'+cilents[i];//设置Id名
document.getElementById('list_user').appendChild(cell); //将这个块添加到父块中
$('.'+cell.className).on("click",function(){.......}); //添加点击事件

当有用户离线的话,就删除它的块

let t_cell = document.getElementById(cells[i].id);
t_cell.parentNode.removeChild(t_cell);

3.2.3. 发送按钮

发送功能就算是比较重要的功能了,它的逻辑是将当前的文本框(可编辑div)下的文本数据发送给服务器,然后清空文本框,再将数据添加到聊天内容块中,聊天内容块中要添加两个小div,一个是标题显示发送人和时间,另一个就是要发送的数据了。
数据格式是这样的

var data = {msg: myselfDiv.innerHTML,name: _cilent.username,time: formatDateTime(new Date()),toName: document.getElementById('name_user').textContent
}

然后聊天内容块也要监听服务器的消息,如果有消息了就显示到聊天内容块中
然后有一个点要注意,就是让滚动条保持在最下方
让DIV的滚动条自动滚动到最底部的3种方法
这里博客给了三种方法,惭愧的是,三个方法我都没成功,后来参考了一个大佬的代码,才解决

$('#'+myChatId).children(':last').get(0).scrollIntoView(false)

3.2.4. emoji

核心功能之一,用的大佬的模板,但是用的过程中远没有想的那么简单。
先上学习资料:jQuery-emoji
我用的就是这个大佬的模板,调用起来有点麻烦,虽然是一个中文官方API,但是,用起来还是有点麻烦,简单说一下调用过程吧
首先是先下载大佬的安装包,然后就是配置一下参数,然后可能还需要再修改一下大佬代码
导包一共需要导入四个js文件和两个css文件

<link type="text/css" href="./css/emoji/jquery.mCustomScrollbar.min.css" rel="stylesheet">
<link type="text/css" href="./css/emoji/jquery.emoji.css" rel="stylesheet"><script src="http://cdn.bootcss.com/jquery/1.11.3/jquery.js"></script>
<script src="./js/emoji/jquery.mousewheel-3.0.6.min.js"></script>
<script src="./js/emoji/jquery.mCustomScrollbar.min.js"></script>
<script src="./js/emoji/jquery.emoji.js"></script>

我把大佬的文件都放在了/js/emoji/文件夹下,然后直接导入
然后还要写一个js文件用来配置和调用emoji

var options = {button : document.getElementById('btn_emoji'),showTab: true,animation: 'fade',position:'bottomRight',icons: [{name: "贴吧表情",path: "./images/tieba/",maxNum: 10,file: ".png",placeholder: ":{alias}:",alias: {1: "hehe",2: "haha",3: "tushe",},title: {1: "呵呵",2: "哈哈",3: "吐舌",}},{name: "QQ高清",path: "./images/qq/",maxNum: 90,excludeNums: [41, 45, 54],file: ".png",placeholder: "#qq_{alias}#"}, {name: "emoji高清",path: "./images/emoji/",maxNum: 84,file: ".png",placeholder: "#emoji_{alias}#"}]
}$(".text_view").emoji(options);

简单的解释一下,主要写配置信息。
我们主要写的有这几个配置信息
button:用来显示和隐藏表情面板的按钮
path:emoji图片存储的路径
position:emoji面板的显示位置,相对于按钮的位置
maxNum:同类emoji的数量
然后最后一行$(".text_view").emoji(options);用来设置哪些块需要显示emoji
然后如果显示不出来,可能还要去修改css文件中的display的属性,改成inline或者block之类的,可能还有修改jquery.emoji.js文件中的部分属性.
大佬的代码写的真好,我没怎么看明白,但是我大概看了一下,好像是做了一个映射,点击之后,会把图片添加到块中.

所以,处理信息就方便了,每一条信息中的一个emoji显示在代码中其实就是img标签,我将这条信息用_text_view.innerHTML把其中的HTML代码提取出来,然后连同标签文字直接发给服务器,因为是HTML,然后emoji的显示是用img标签然后用src链接显示图片的,因为每个客户端的emoji的位置都是固定的,所以只传输HTML代码,放在另一个客户端中也能显示.

大佬写的emoji模块还是比较完善的,调用起来也比较简单.

3.2.5. 上传图片

图片不像emoji,emoji可以事先存储在客户端的文件夹中,位置可以固定,直接用HTML链接文件就行了.
图片在用户的电脑上的位置是随机的,样子也是随机的,有点犯难.
老规矩,先上查询的资料:
FIleReader
JS读取本地文件
FileReader - Web API 接口参考 | MDN
主要用到的就是一个叫FileReader的对象,对应到HTML中是input标签type是file的.

var imageFile = document.getElementById('_picture').files[0];
var fr = new window.FileReader()
fr.readAsDataURL(imageFile);
fr.onload = function () {console.log(fr.result);
}

输出数据是一大大大大大段字符,这是用的base64编码,浏览器自己会识别的,如果想要这个代码显示图片

div.innerHTML ='<img src=\"'+ fr.result +'\">';

就是用img的src属性,直接将那段base64编码赋给src属性就可以显示了.
图片的显示也搞定了.

file标签的样式
有关于<input type="file">这个标签倒是要注意一下样式,原版样式很丑,而且看了很多资料,网络上也没有啥很好看的样式,不过查到了一个思路就是用一个button用来显示,然后点击这个button,然后触发file,不过用JavaScript设置click方法,不知道为啥没显示,也试过用了Jquery的().on注册事件也不行.还是我太菜了,对js的语法不太熟,最终只能放弃.
然后我又刚好查到可以用一个label标签和一个元素绑定,然后点击label标签就相当于点击那个绑定的元素,发现了新世界,用法如下

<label for="_picture" class="picture"></label>
<input type="file" id="_picture" style="display:none;" multiple accept="image/*" onchange="sendImage()">

这样input type="file"就不显示了,只显示label,这样就只需要修改label的样式就行了,太方便了.

3.2.6. 历史记录

这部分是最难的部分,因为它要实现本地消息的存储,然后浏览器本身对于文件的操作有很有限.太头秃了
先上资料:
在node.js下浅谈前端下载文件的方法
js发送get 、post请求的方法简介
jQuery - AJAX get() 和 post() 方法
nodejs:下载文件到服务器或客户端
HTML5 FileReader 读取txt文件
js实现base64转换
js正则表达式获取字符串中多个大括号{}中的内容

我是这样实现的,就是客户端从服务器中下载聊天记录.txt,然后用户自己选择这个文件,然后js把文件内容读出来,把记录显示到聊天框中.
服务器提供下载服务:

const url = require('url');
const fs = require('fs');app.get('/download', function(req, res) {let url = 'history/'+ req.query.name + 'To' + req.query.toName + '.txt';res.download(__dirname+'\\'+url);
});

先导包,我的聊天记录放在根目录下的history文件夹下,数据保存的格式是xxxToxxx.txt,内容是JSON格式数据.
实现的功能就是让nodejs处理客户端的get请求,然后把文件传回去,用req.query.属性名来获取属性值,然后返回正确的文件给客户端

然后就是客户端怎么向服务器发出post/get请求
第一种方法就是比较简单的就是用js创建一个虚拟form,然后click,就会发出一个请求
第二个方法是用Jquery的get() 和 post()方法,用法很简单

$.get(URL,callback);
$.post(URL,data,callback);

第三个方法就是,上面第一个资料的用iframe提交,而且还没有闪现,大佬请收下我的膝盖

//本方法copy自网络
function downloadByIframe(url){var iframe = document.getElementById("myIframe");if(iframe){iframe.src = url;}else{iframe = document.createElement("iframe");iframe.style.display = "none";iframe.src = url;iframe.id = "myIframe";document.body.appendChild(iframe);}
}

代码复制自
在node.js下浅谈前端下载文件的方法
如有侵权请联系我删除!

然后就是本地读取文件

var history = document.getElementById('_history').files[0];
var fr = new window.FileReader()
fr.readAsDataURL(history);fr.onload = function () {//编码base64 Base64.encode//解码base64var texts = Base64.decode(fr.result).match(/{(.*?)}/g);//console.log(texts);var t_meg = JSON.parse(texts[0]);console.log(t_meg);if(t_meg.name != _cilent.username && t_meg.toName != _cilent.username){alert('这不是你的聊天记录!!!');return ;}}

简单的说一下,还是用<input type="file">这个标签,然后js中用FileReader读取,但是有一个问题就是读取的文件数据是base64编码之后的,我们需要先解码,方法是参考js实现base64转换的.
先安装base64包

<script src="https://cdn.jsdelivr.net/npm/js-base64@3.6.1/base64.min.js"></script>

然后,就可以用了,两个方法

Base64.decode()    //解码base64
Base64.encode()    //编码base64

解码之后数据是很多很多的JSON数据,我们还要用正则表达式将JSON数据提取出来。
就是匹配{}中的数据。用的正则表达式是/{(.*?)}/g,然后将提取的JSON串转化成js对象JSON.parse()
然后数据就读出来。

总结

这次课设收获了太多的东西,了解了nodejs、socket.io等技术,熟悉了JavaScript的语法,查询了太多了资料,收获满满。
=w=

WebSocket之仿QQWeb即时聊天系统(下)相关推荐

  1. WebSocket之仿QQWeb即时聊天系统(上)

    1. 前言 Java web学完了,到了学期末,开始课设了,一共给了几个题目,大部分都是 JSP+servlet题目,当然我们也是主要学习的这些,一般都是什么什么管理系统,没啥意思,看到一个仿QQWe ...

  2. 高仿QQ即时聊天软件开发系列之三登录窗口用户选择下拉框

    上一篇高仿QQ即时聊天软件开发系列之二登录窗口界面写了一个大概的布局和原理 这一篇详细说下拉框的实现原理 先上最终效果图 一开始其实只是想给下拉框加一个placeholder效果,让下拉框在未选择未输 ...

  3. java websocket实现即时聊天系统

    即时聊天系统 后台代码 package com.wyn.pro.wynpro.customermanager.test; import java.io.IOException; import java ...

  4. 基于环信的仿QQ即时通讯的简单实现

    代码地址如下: http://www.demodashi.com/demo/11645.html 我的博客地址 之前一直想实现聊天的功能,但是感觉有点困难,今天看了环信的API,就利用下午的时间动手试 ...

  5. 基于php和服务器推技术的web即时聊天系统,基于php和服务器推技术的Web即时聊天系统...

    基于php和服务器推技术的Web即时聊天系统① 王振兴, 黄静 [摘要]摘要: 基于http协议应用于Web端, 实现一个浏览器无关的.便于移植的.高性能的Web即时聊天系统. 系统使用服务器推技术中 ...

  6. 基于php的简单聊天系统,基于PHP的网页即时聊天系统的设计与实现

    物联网技术 2015年 / 第10期 可靠传输 Reliable Transmission 40 0 引 言 互联网技术飞速发展,Instant Messaging(即时通讯)的 出现,通过互联网技术 ...

  7. winform服务器消息推送,winform项目——仿QQ即时通讯程序12:服务端程序补充及优化...

    原标题:winform项目--仿QQ即时通讯程序12:服务端程序补充及优化 上一篇文章大概完成了服务端程序,今天继续做项目的时候发现还有一些功能没有做,还有几处地方不够完善.不做好就会影响客户端程序的 ...

  8. 基于WebSocket的网页端即时通讯

    基于WebSocket的网页端即时通讯 最近项目中需要用到一些即时通讯的相关技术,查阅了一些资料后发现有些示例不是让人很满意,所以博主写了一个demo,就怕以后会忘掉,也方便博友查看. 由于博主用的是 ...

  9. 仿基金查询输入框下拉筛选值效果(JavaScript)

    http://www.weste.net/2007/11-24/19463143437.html 仿基金查询输入框下拉筛选值效果(JavaScript)

最新文章

  1. mysql引号问题_MySQL中引号的问题
  2. 4.2 路由算法与路由协议概述
  3. 每次都能遇到的莫名其妙问题,谨记,速查手册
  4. Eclipse启动时布局不合理调整
  5. python面试题之有没有一个工具可以帮助查找python的bug和进行静态的代码分析?
  6. AcWing 891. Nim游戏(nim博弈)
  7. 深度学习:Keras入门(二)之卷积神经网络(CNN)【转】
  8. 精益软件开发(Lean Software Development)
  9. 分享几个免费的开源邮件服务器软件
  10. html中怎么给文字设置动画效果,文字效果怎么设置?
  11. 持久层框架JPA与Mybatis该如何选型
  12. ie不能加载flash html,IE浏览器无法显示Flash怎么解决?解决的方法介绍
  13. 以互联网公司的经验告诉大家,架构师究竟比高级开发厉害在哪?
  14. 美还是丑?这有一个CNN开发的颜值评分器 | 实战
  15. 私有化完成 360回归A股还有哪些障碍?
  16. 我理解的myisam引擎之二 MyISAM表(MYD)存储格式
  17. 回调函数基本介绍和基本使用场景
  18. [usaco 2009 dec]游荡的奶牛
  19. MCDF实验——Lab5
  20. js序列化与反序列化和C#中json序列化与反序列化

热门文章

  1. 急诊与灾难医学-重点以及习题
  2. 基于51单片机的脉搏测量仪protues仿真设计
  3. CentOS7.6搭建开源WCP知识管理系统
  4. STM32F0 ADC学习
  5. 卡尔曼滤波(Kalman filter)算法以及Arduino应用-mpu6050(导航贴)
  6. windows中的一些小技巧
  7. 历史小故事----BUG的来源
  8. vue2[初级]事件处理器
  9. Hdoj 2190.悼念512汶川大地震遇难同胞——重建希望小学 题解
  10. 线性代数复习笔记——第一章