任务
仿QQWeb即时聊天系统
一.功能要求
实现Web的点对点即时的文本消息聊天功能。
实现Web的表情的发送、接收和显示功能。
实现Web的图片的发送、接收和显示功能。
实现本地消息的存储,在离线的时候也能加载和查看历史消息;
要求使用WebSocket;
前期准备
客户端
websocket
websocket是实现服务器与客户端快速便捷连接,只需一次握手即可建立浏览器与服务器的通道持久协议 客户端可以主动给浏览器发送消息(传统HTTP不行)
HTML5已经提供了websocket的api,可以直接使用
连接的url是什么—>wensocket的服务端
流程:创建websocket(new)------->建立连接(open)------>向服务器发送消息(send)------>接收服务器的消息message------>(断开连接)
socket.io
Socket.io是一个WebSocket库
在使用前先在命令行中加载这些
npm install jquery
npm install socketio
npm install express
用管理员身份运行命令行才行,遇到了和上次一样的问题
浏览器与服务器的通信,用socket的emit和on方法即可
参数1:事件名
emit 参数2:
//可以理解为接收信息(监听到参数1后,触发参数2
on(参数1, 参数2)
//可以理解为发送信息(触发参数1,并且传递参数2
emit(参数1, 参数2)
css
- position:fixed
- div>p 与 div p
- hover
jQuery
jQuery 极大地简化了 JavaScript 编程,由于之前对js有了解不再多加赘述,在此只涉及与课程设计相关的知识
jQuery 库是一个 JavaScript 文件,可以使用 HTML 的 <script> 标签引用它:
<script src="jquery-1.10.2.min.js"></script>
元素选取
/*
使用符号$选取html元素
*/
$("p") //选取所有<p>元素
$(".classs") //选取所有class="classs"的元素
$("#ids") //选取所有id="ids"的元素
操作
?增删改查
-
获取:
三个简单实用的用于 DOM 操作的 jQuery 方法:
text() : 设置或返回所选元素的文本内容
html() :设置或返回所选元素的内容(包括 HTML 标记)
val() :设置或返回表单字段的值
attr() :方法用于获取属性值。eg:$(#idd).text()
-
append
-
empty
文件
js对文件的操作非常非常有限
事件方法
-
页面中指定一个点击事件:
$("p").click();
-
隐藏元素
可以使用 toggle() 方法来切换 hide() 和 show() 方法$("#hide").click(function(){ $("p").hide(); }); $("#show").click(function(){ $("p").show(); }); $("button").click(function(){ $("p").toggle(); });
-
鼠标移入,移出
//入 $("p").mouseenter(function(){ $("p").css("background-color","yellow"); }); //出 $("p").mouseleave(function(){ $("p").css("background-color","gray"); });
数组
-
查找
//获取下标为1的元素 username.slice(1); //删除最后一个元素 username.pop(); //找出最后一个元素并修改 usernames[usernames.length-1]=-1;
-
循环
var arr = [1, 2, 3, 4, 5]; arr.forEach(function (item) { if (item === 3) { return; } console.log(item); }); //上下两者一样 arr.forEach(item=>{ if (item === 3) { return; } console.log(item); }); 注意:forEach不改变原数组
服务端
node.js
之前课设安装过,直接用就行
node.js是什么:node.js是后台语言,是运行在服务端的JavaScript。
var mysql = require('mysql');
var connection = mysql.createConnection({
host : 'localhost',
user : 'root',
password : '123456',
database : 'websocket'
//自己创建数据库的名字!!
});
connection.connect();
connection.query('SELECT 1 + 1 AS solution', function (error, results, fields) {
if (error) throw error;
console.log('The solution is: ', results[0].solution);
});
用法
//为指定事件添加一个监听器到监听器数组的尾部
addListener(event, listener)
//on 函数用于绑定,监听事件函数(为指定事件注册一个监听器
on(event, listener)
//emit 属性用于触发一个事件(按监听器的顺序执行执行每个监听器,如果事件有注册监听返回 true,否则返回 false
emit(event, [arg1], [arg2], [...])
//返回指定事件的监听器数量
listenerCount(emitter, event)
写的时候遇到的问题
app.js
1.io与socket
客户端与服务器建立链接会触发connection事件
io.on("connection",function(socket){
//其他操作
});
socket.emit:触发当前用户(给当前用户发送消息
只有当前用户页面改变
io.emit:给所有用户发送(广播事件
所有用户页面改变
2.data
data可以理解为接收数据的结构体
socket.emit('send',{
name:'zt',
age: '18'
})
socket.on('send', data => {
console.log(data)
})
!!! 3.出现了很严重的问题
问题描述:只有新用户连接时,消息才会弹出
错误原因:逻辑错误
逻辑:服务器从客户端接收后就立刻返回给浏览器,所以需要服务器主动去发送消息
错误方法:
io.on('connection', function(socket){
socket.on('send_app.js_text',function(data){
/* 测试 :console.log(message); */
//块作用域
let message = {
time:getNowTime(),
name:name,
text:data
}
console.log(message);
});
socket.emit('send_message',message);
//写在这里,逻辑变成有用户连接时才会send消息给浏览器
});
修改:
socket.on('send_app.js_text',function(data){
/* 测试 :console.log(message); */
//块作用域
let message = {
time:getNowTime(),
name:name,
text:data
}
console.log(message);
io.emit('send_message',message);
//写在这里才是服务器主动给客户端发送消息
});
4.问题描述:1 2 3 4用户,2退出但是列表去除的是4
原因:逻辑错误
socket.on('disconnect',function(data){
//找到该用户并移出数组(删除用户)
for(i=0;i<usernames.length;i++){
if(usernames[i] == name)
usernames[i]=-1;
}
usernames.forEach(item=>{
console.log(item);
});
//向所有用户发送离开信息
io.emit('send_names',usernames);
});
解决方法 利用socket.name
socket.on('disconnect',function(data){
//找到该用户并移出数组(删除用户)
for(i=0;i<usernames.length;i++){
if(usernames[i] == socket.name)
usernames[i]=-1;
}
usernames.forEach(item=>{
console.log(item);
});
//向所有用户发送离开信息
io.emit('send_names',usernames);
});
index.js
1.清空聊天框内容
其实很简单,只要$("#text").val("");
就行,
但是尝试了remove和empty方法不行
开始还特别蠢的用了$("#text").val()=" "
2.let与var
ES6 可以使用 let 关键字来实现块级作用域。
let 声明的变量只在 let 命令所在的代码块 {} 内有效,在 {} 之外不能访问。
eg
var x = 10;
{
var x = 2;
}
x
这里 x 为 2
var x = 10;
{
let x = 2;
}
x
这里 x 为 10
3.参数引用
例如引用变量a要用${a}
在append后面加入html元素时使用 ` 符号 例:
$(".body-right").append(`
<p style="text-align:center;">${item.time}</p>
<p>用户${item.name}</p>
<p>${item.text}</p>
`);
但是在函数传递时又不用了 例:下述的n
scrollIntoView('.body-right');
function scrollIntoView(n) {
$(n).children(':last').get(0).scrollIntoView(false);
//$('.body-right')
}
4.在图片上传时遇到问题(label标签
input标签就提供了文件上传功能,开始还特别sb的想去用a标签去打开文件hhhhh
问题描述:点击input标签没有反应
原因:没点上
解决办法:将input标签伪造成图标,这里还要用到a标签去调用js,label标签确保能点上
<a href="javascript:;" class="file">
<label for="file"></label>
<input type="file" id="file" style="display: none;">
//javascript:;是一个伪协议,作用是让a标签的超链接变成一个js方法的调用
</a>
//检测到使用了input上传文件后,对文件进行js操作
$('#file').on('change', function() {
//文件操作
});
tip:label标签
label 元素不会向用户呈现任何特殊效果。如果在 label 元素内点击文本,就会触发此控件。就是说,当用户选择该标签时,浏览器就会自动将焦点转到和标签相关的表单控件上。
<label>
标签的 for 属性应当与相关元素的 id 属性相同。
5.在图片发送时遇到问题(
图片发送和消息发送其实是一个逻辑,但是图片消息的标签是img,而文字是p,所以需要设置一个type来判断输出的标签
data.forEach(item =>{
$(".body-right").append(`
<p style="text-align:center;color:grey">${item.time}</p>
<p>用户${item.name}</p>
`);
if(item.type === "txt")
$(".body-right").append(`<p>${item.text}</p>`);
else
$(".body-right").append(`<img src="${item.text}" alt="" />`);
});
md删的时候删漏了个括号,找了八百年的错
6.emj表情框的放置
如何让emj的div悬浮在body中成为了一个问题
1.fixed
.emj{
position: fixed;
top: 50%;
left: 16rem;
}
实现了悬浮,因为fix是独立一层,但是在放大缩小界面时会跑偏
查阅资料后
2.relative
//父样式中用yellow
.body-right{
border: 1px solid yellow;
float: right;
}
//子样式中用relative
.emj{
position: relative;
}
但是会把body-right(黄色)的框挤出去
改了一圈发现最开始其实就是对的,我是sb,会跑的原因是因为top设定成百分比,如果设置为数值就不会跑
错的:
.emj{
/* display: none; */
position: fixed;
top: 50%;
left: 16rem;
width: 12.5rem;
height:12.5rem;;
border: 1px solid red;
}
改正:
.emj{
/* display: none; */
position: fixed;
top: 400px;
left: 16rem;
width: 12.5rem;
height:12.5rem;;
border: 1px solid red;
}
悬浮成功
用到的事件
框:hidden,show
鼠标:mouseenter,mouseleave
//鼠标移入表情包悬浮框显示
$('a.img').on('mouseenter', function() {
$(".emj").show();
});
//鼠标移出,表情包悬浮框消失
$('div.emj').on('mouseleave', function() {
$("div.emj").hide();
});
表情的选取
1.问题描述:emmm报错了,可能不能用this?
//图片触发
$("div.emj").on('click',function(){
alert(this.attr("src"));
换用其他的选择器试试
2.问题描述:无论点哪个,出来的都是第一个表情????
推测是选取元素出现了问题
$("div.emj").on('click',function(){
alert($("img").attr("src"));
因为img元素都相同,所以无法分辨,可以换用id,但是这样一来每一个id都要绑定一个click事件,会很麻烦。试着用函数或者其他方法解决
用this就解决了
(操!this没有加括号所以报错,我还以为是this不能用,找了一大圈资料 操! 白给!
$("div.emj img").on('click',function(){
alert($(this).attr("src"));
});
连接数据库,存储信息
let insertData = (table,datas,callback)=>{
var fields='';
var values='';
for( var k in datas){
fields+=k+',';
values=values+"'"+datas[k]+"',"
}
fields=fields.slice(0,-1);
values=values.slice(0,-1);
console.log(fields,values);
var sql="INSERT INTO "+table+'('+fields+') VALUES('+values+')';
connection.query(sql,callback);
}
!!!!!!!!感天动地,存进来了
但是在传历史信息的时候出现问题,测试发现问题出现在 socket.emit这一步,应该是是res中多了一个数据库的主键id,而message中没有,但是id又不能重复,所以要使用一个数去记录id并传值
emmmm后面发现其实是调用历史信息的数组和发送信息冲突了,干脆把数组取消,全部用成数据库
db.insertData('websocket', message, (e, r) => {
//测试
console.log('消息存入成功')
console.log(message);
})
io.emit('send_message',message);
socket.on('send_message', function(data) {
/* 测试 :alert(data); */
$(".body-right").append(`
<p style="text-align:center;color:grey">${data.time}</p>
<p>用户${data.name}</p>
`);
if (data.type === "txt")
$(".body-right").append(`<p>${data.text}</p>`);
else if (data.type == "img")
$(".body-right").append(`<img src="${data.text}" height=200px
width=200px alt="" />`);
else
$(".body-right").append(`<img src="${data.text}" />`);
scrollIntoView('.body-right');
});
调用数据库信息
问题描述:1用户发送的消息 2用户却看不到
emmm很离谱的原因,可能是因为图片文件太大(五十多万长的base64编码),所以数据库卡bug了,所以这里试着存储文件的地址而不是图片本身(但是目前还没能实现
c,原来,js不能获取.的文件路径,怪不得改了好久都不行。 js文件操作好坑,
ps:不过老师还提供了两个思路,一个是换用可以专门存储图片的数据库。还有一个是数据库中有一个专门存储这种的数据类型LONGBLOB 或者longtext
$('#file').on('change', function() {
var file = this.files[0]
//需要把这个文件发送到服务器,借助于H5新增的fileReader
var fr = new FileReader()
fr.readAsDataURL(file);
fr.onload = function() {
/* 测试:alert(fr.result); */
socket.emit('send_app.js_text', {
name: name,
text: fr.result,
type: "img"
});
}
});
使用数组messages去记录历史信息并且下载
//加载历史消息
function initMessage(socket) {
db.selectAll('select * from websocket order by id asc', (e, res) => {
for (var i = 0; i < res.length; i++) {
socket.emit('send_message', res[i]);
//将历史消息加入messages中,便于下载
终于成功了,改死我了
文件
本来想用js去实现本地消息的存储,但是查阅资料使用ActiveXObject 后会报错 ActiveXObject is not defined
,很坑,是因为只有ie浏览器能用 ActiveXObjec
后面在服务器上实现了文件的下载:参考文章
socket.on('download',function(){
console.log("开始下载");
const fs = require("fs");
messages.forEach(item=>{
fs.appendFileSync("test.txt", "用户"+item.name+":\r\n"+item.text+"\r\n\r\n");
let data = fs.readFileSync("test.txt", "utf8");
console.log(data);
})
});
动态生成的DOM不会触发onclick事件
好啊,好,找了一晚上的错,原来是这样
$('a.li').on('click',function(){
alert($(this);
});
noclick等事件不能绑定dom动态元素,只能绑定静态元素
$('.card1').on('click',function(){
alert($(this);
});
但是这样就不能选择出每个用户,而是整个用户列表
查到资料大佬的博客后修改
提过父元素card1,成功绑定了动态dmo的a标签
$('.card1').on('click','a.li',function(){
alert($(this).attr('id'));
});
float
刚开始把名字和内容没有放在一个div里,会造成重叠,后面放到了一个div里面
$(".body-right1").append(`
<p style="text-align:center;color:grey">${data.time}</p>`);
if (data.type === "txt")
$(".body-right1").append(`
<div class="right">
<p style="text-align:${direction} ">用户${data.save_name}</p>
<p>${data.text}</p>
</div>`);
不清除浮动的后果hhh
修改后
.right{
/*消息排版会乱掉
1.float: right;
2.position:absolute;
right:50px;
*/
float: right;
clear:both;
}
记录
day1完成的:
前端的页面代码
初步实现了用户加入聊天室时更新左侧用户列表,
初步实现发送消息,以及发送消息时提示时间
index.html
<!DOCTYPE html>
<html >
<head>
<meta charset="UTF-8">
<title>聊天室</title>
<link rel="stylesheet" type="text/css" href="index.css">
<script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
</head>
<body>
<div class="box">
<div class="head">
聊天室
</div>
<div class="body">
<div class="body-left">
<div class="card">
<h3>公告栏</h3>
<p>请文明发言</p>
<p class="p1">欢迎加入聊天,用户</p>
</div>
<h4>聊天室成员</h4>
<div class="card1">
<ul>
</ul>
</div>
</div>
<div class="body-right">
</div>
</div>
<div class="foot">
<div class="foot-tool">
<img src="img/emj.png" />
<img src="img/img.png" />
</div>
<div class="foot-text">
<textarea rows="6" cols="200" id="text">
</textarea>
</div>
<div class="foot-send">
<button type="button" id="button">发 送</button>
</div>
</div>
</div>
<script src="./socket.io/socket.io.js"></script>
<script src="./js/index.js"></script>
</body>
</html>
index.css
* {
box-sizing: border-box;
/* background-color: darkgrey; */
}
/* 聊天框整体 */
.box {
display: block;
border: 1px solid black;
margin: 5rem 15rem;
width: 60rem;
height: 40rem;
background-color: aliceblue;
}
.head{
border: 1px solid skyblue;
background-color: #87CEEB;
height: 5%;
text-align: center;
}
/* 对话框 */
.body{
background-color: #F0F8FF;
border: 1px solid blueviolet;
height: 70%;
}
.body-left{
background-color: white;
border: 1px solid blue;
float: left;
width: 20%;
height: 100%;
}
.body-right{
border: 1px solid blue;
float: right;
width: 80%;
height: 100%;
overflow: scroll;
}
.card{
height: 30%;
border: 1px solid black;
}
.card1{
height: 55%;
border: 1px solid black;
overflow: scroll;
}
/* 输入框 */
.foot{
background-color: whitesmoke;
border: 1px solid blueviolet;
height: 25%;
}
.foot-tool{
border: 1px solid black;
height: 15%
width: 100%;
}
.foot-text{
border: 1px solid black;
height: 50%;
width: 100%;
overflow: hidden;
}
.foot-send{
border: 1px solid brown;
height: 20%;
width: 100%;
overflow: hidden;
}
/* 清除默认格式 */
ul{
list-style-type: none;
margin: 0;
padding: 0.3125rem;
}
/* 设置按钮大小 */
button{
background-color: dodgerblue;
padding: 0.25rem 2.5rem;
float: right;
}
/* 当鼠标移点击页面时自动对焦到第一行 */
textarea{
}
index.js
/*
聊天室的功能
*/
/* 1.连接服务器 */
var socket = io();
/* 2.用户信息 */
//创建全局变量
var name;
// 接收服务器创建的用户
socket.on('send_name', function(data) {
name = data;
$("p.p1").append(data);
});
//接收服务器所有用户的信息
socket.on('send_names', function(data) {
/*测试: alert(data); */
//统计聊天室成员
data.forEach(item =>{
$("ul").append("<li>用户"+item+"在线<li>");
});
});
//接收服务器销毁用户信息
//数组清空
//name=0
/* 3.用户发送消息 */
//用户向服务器发送
$("#button").click(function() {
var text = $("#text").val();
/* 测试:alert(text); */
//向服务器发送信息
socket.emit('send_app.js_text', text);
//发送后内容清空
$("#text").val("");
});
//接收服务器发送的消息
socket.on('send_message',function(data){
/* 测试 :alert(data); */
//避免重复
$(".body-right").empty();
//循环输出数组
data.forEach(item =>{
$(".body-right").append(`
<p style="text-align:center;">${item.time}</p>
<p>用户${item.name}</p>
<p>${item.text}</p>
`);
});
});
app.js
const { data } = require('jquery');
// 通过express创建了http服务器
var app = require('express')();
var http = require('http').Server(app);//httpServer要绑定的服务器
var io = require('socket.io')(http);
//Express中static方法可以处理静态资源
//将public目录设置为静态资源目录
app.use(require('express').static('public'))
app.get('/', function(req, res){
res.redirect('index.html')
});
/*获取当前时间*/
function getNowTime() {
var date = new Date();
//年 getFullYear():四位数字返回年份
var year = date.getFullYear(); //getFullYear()代替getYear()
//月 getMonth():0 ~ 11
var month = date.getMonth() + 1;
//日 getDate():(1 ~ 31)
var day = date.getDate();
//时 getHours():(0 ~ 23)
var hour = date.getHours();
//分 getMinutes(): (0 ~ 59)
var minute = date.getMinutes();
//秒 getSeconds():(0 ~ 59)
var second = date.getSeconds();
var time = ' ' + year + '-' + addZero(month) + '-' + addZero(day) + ' ' + addZero(hour) + ':' + addZero(minute) + ':' + addZero(second);
return time;
}
function addZero(s) {
return s < 10 ? ('0' + s) : s;
}
/* 定义全局变量 */
//用户名
var name = 0;
//总用户
var usernames = [];
//消息记录
var messages =[];
//监听了用户的连接事件
io.on('connection', function(socket){
/* 2.用户信息 */
//创建用户
name = name+1;
//将该用户加入登录用户中
usernames.push(name);
// 向浏览器发送该用户
socket.emit('send_name',name);
// 向浏览器发送所有用户
socket.emit('send_names',usernames);
/* 3.用户发送消息 */
// 接收浏览器信息
socket.on('send_app.js_text',function(data){
/* 测试 :console.log(message); */
//块作用域
let message = {
time:getNowTime(),
name:name,
text:data
}
messages.push(message)
console.log(messages);
io.emit('send_message',messages);
});
});
//监听用户断开事件
//用户数--
//向浏览器发送信息
/* */
//指定端口号为3000
http.listen(3000, function(){
console.log('listening on *:3000');
});
day2完成的:
完善了用户断开连接的逻辑(用户离开时io消息,用户列表更新)
新增了上传图片功能
index.html
<!DOCTYPE html>
<html >
<head>
<meta charset="UTF-8">
<title>聊天室</title>
<link rel="stylesheet" type="text/css" href="index.css">
<script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
</head>
<body>
<div class="box">
<div class="head">
聊天室
</div>
<div class="body">
<div class="body-left">
<div class="card">
<h3>公告栏</h3>
<p class="p1">欢迎加入聊天,用户</p>
</div>
<h4>聊天室成员</h4>
<div class="card1">
<ul>
</ul>
</div>
</div>
<div class="body-right">
</div>
</div>
<div class="foot">
<div class="foot-tool">
<img src="img/emj.png" />
<a href="javascript:;" class="file">
<label for="file"></label>
<input type="file" id="file" style="display: none;">
</a>
<!-- <img id="file" src="img/img.png" /> -->
<!-- <input type="file"> -->
</div>
<div class="foot-text">
<textarea rows="6" cols="200" id="text">
</textarea>
</div>
<div class="foot-send">
<button type="button" id="button">发 送</button>
</div>
</div>
</div>
<script src="./socket.io/socket.io.js"></script>
<script src="./js/index.js"></script>
</body>
</html>
index.css
* {
box-sizing: border-box;
/* background-color: darkgrey; */
}
/* 聊天框整体 */
.box {
display: block;
border: 1px solid black;
margin: 5rem 15rem;
width: 60rem;
height: 40rem;
background-color: aliceblue;
}
.head{
border: 1px solid skyblue;
background-color: #87CEEB;
height: 5%;
text-align: center;
}
/* 对话框 */
.body{
background-color: #F0F8FF;
border: 1px solid blueviolet;
height: 70%;
}
.body-left{
background-color: white;
border: 1px solid blue;
float: left;
width: 20%;
height: 100%;
}
.body-right{
border: 1px solid blue;
float: right;
width: 80%;
height: 100%;
overflow: scroll;
}
/* 利用css给连接伪造一个图片 */
.foot .foot-tool .file {
display: inline-block;
vertical-align: middle;
width: 40px;
height: 45px;
background: url('img/img.png') no-repeat ;
}
.foot .foot-tool .file label {
width: 100%;
height: 100%;
display: block;
}
.card{
height: 30%;
border: 1px solid black;
}
.card1{
height: 55%;
border: 1px solid black;
overflow: scroll;
}
/* 输入框 */
.foot{
background-color: whitesmoke;
border: 1px solid blueviolet;
height: 25%;
}
.foot-tool{
border: 1px solid black;
height: 15%
width: 100%;
}
.foot-text{
border: 1px solid black;
height: 50%;
width: 100%;
overflow: hidden;
}
.foot-send{
border: 1px solid brown;
height: 20%;
width: 100%;
overflow: hidden;
}
/* 清除默认格式 */
ul{
list-style-type: none;
margin: 0;
padding: 0.3125rem;
}
/* 设置按钮大小 */
button{
background-color: dodgerblue;
padding: 0.25rem 2.5rem;
float: right;
}
/* 当鼠标移点击页面时自动对焦到第一行 */
textarea{
}
index.js
/*
聊天室的功能
1.连接服务器
2.用户信息
//创建用户 (服务器创建用户,发送给该页面
//销毁用户 (服务器销毁用户,发送给该页面
3.用户发送消息
//个人用户向服务器发送
//浏览器io给所有用户
*/
/* 1.连接服务器 */
var socket = io();
//当前元素(最近一条消息)底部滚动到可视区
function scrollIntoView(n) {
$(n).children(':last').get(0).scrollIntoView(false);
}
/* 2.用户信息 */
//创建全局变量
var name;
//接收服务器创建的用户
socket.on('send_name', function(data) {
$("p.p1").empty();
name = data;
$("p.p1").append(`
<p>请文明发言</p>
<p class="p1">欢迎加入聊天,用户${data}</p>
`);
});
//接收服务器销毁的用户,并且在.body-right输出
socket.on('remove_names', function(data) {
$(".body-right").append(`<p style="text-align:center;color:grey">用户${data}偷偷离开了聊天室</p>`);
scrollIntoView('.body-right');
});
//在.body-left输出所有用户的信息
socket.on('send_names', function(data) {
//先清空
$("ul").empty();
/*测试: alert(data); */
//统计聊天室成员
data.forEach(item =>{
if(item>0)
$("ul").append("<li>用户"+item+"在线<li>");
});
scrollIntoView('.body-left');
});
/* 3.用户发送消息 */
//用户向服务器发送消息
$("#button").click(function() {
var text = $("#text").val();
/* 测试:alert(text); */
//向服务器发送文字信息,type为txt
socket.emit('send_app.js_text', {
name:name,
text:text,
type:"txt"
});
//发送后内容清空
$("#text").val("");
});
//用户向服务器发送图片消息,type为img
$('#file').on('change', function () {
var file = this.files[0]
//需要把这个文件发送到服务器,借助于H5新增的fileReader
var fr = new FileReader()
fr.readAsDataURL(file)
fr.onload = function () {
alert(fr.result);
socket.emit('send_app.js_text', {
name:name,
text:fr.result,
type:"img"
});
}
})
//接收服务器发送的消息
socket.on('send_message',function(data){
/* 测试 :alert(data); */
//避免重复
$(".body-right").empty();
//循环输出数组
data.forEach(item =>{
$(".body-right").append(`
<p style="text-align:center;color:grey">${item.time}</p>
<p>用户${item.name}</p>
`);
if(item.type === "txt")
$(".body-right").append(`<p>${item.text}</p>`);
else
$(".body-right").append(`<img src="${item.text}" alt="" />`);
});
scrollIntoView('.body-right');
});
app.js
const { data } = require('jquery');
// 通过express创建了http服务器
var app = require('express')();
var http = require('http').Server(app);//httpServer要绑定的服务器
var io = require('socket.io')(http);
//Express中static方法可以处理静态资源
//将public目录设置为静态资源目录
app.use(require('express').static('public'))
app.get('/', function(req, res){
res.redirect('index.html')
});
/*获取当前时间*/
function getNowTime() {
var date = new Date();
//年 getFullYear():四位数字返回年份
var year = date.getFullYear(); //getFullYear()代替getYear()
//月 getMonth():0 ~ 11
var month = date.getMonth() + 1;
//日 getDate():(1 ~ 31)
var day = date.getDate();
//时 getHours():(0 ~ 23)
var hour = date.getHours();
//分 getMinutes(): (0 ~ 59)
var minute = date.getMinutes();
//秒 getSeconds():(0 ~ 59)
var second = date.getSeconds();
var time = ' ' + year + '-' + addZero(month) + '-' + addZero(day) + ' ' + addZero(hour) + ':' + addZero(minute) + ':' + addZero(second);
return time;
}
function addZero(s) {
return s < 10 ? ('0' + s) : s;
}
/* 定义全局变量 */
//用户名
var name = 0;
//总用户
var usernames = [];
//消息记录
var messages =[];
//监听了用户的连接事件
io.on('connection', function(socket){
/* 2.用户信息 */
//创建用户
name = name+1;
socket.name = name;
//将该用户加入登录用户中
usernames.push(name);
// 向浏览器发送该用户
socket.emit('send_name',name);
// 向所有用户发送所有用户
io.emit('send_names',usernames);
/* 3.用户发送消息 */
// 接收浏览器信息
socket.on('send_app.js_text',function(data){
/* 测试 :console.log(message); */
//块作用域
let message = {
time:getNowTime(),
name:data.name,
text:data.text,
//通过type来区别信息
type:data.type
}
messages.push(message)
console.log(messages);
io.emit('send_message',messages);
});
//监听某位用户断开连接事件(自带功能,不需要浏览器emit)
socket.on('disconnect',function(data){
//找到该用户并移出数组(删除用户)
for(i=0;i<usernames.length;i++){
if(usernames[i] == socket.name)
usernames[i]=-1;
}
/* 测试
usernames.forEach(item=>{
console.log(item);
}); */
//向所有用户发送离开信息
io.emit('send_names',usernames);
io.emit('remove_names',socket.name);
});
});
/* */
//指定端口号为3000
http.listen(3000, function(){
console.log('listening on *:3000');
});
day3
完成了表情的发送
index.html
<!DOCTYPE html>
<html >
<head>
<meta charset="UTF-8">
<title>聊天室</title>
<link rel="stylesheet" type="text/css" href="index.css">
<script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
</head>
<body>
<div class="box">
<div class="head">
聊天室
</div>
<div class="body">
<!-- 用户信息栏 -->
<div class="body-left">
<div class="card">
<h3>公告栏</h3>
<p class="p1">欢迎加入聊天,用户</p>
</div>
<h4>聊天室成员</h4>
<div class="card1">
<ul>
</ul>
</div>
</div>
<!-- 聊天信息框 -->
<div class="body-right">
</div>
</div>
<!-- 表情框,初始为隐藏 -->
<div class="emj">
<div> <img src="emj/1.png" id="1"/> </div>
<div> <img src="emj/2.png" id="2"/> </div>
<div> <img src="emj/3.png" /> </div>
<div> <img src="emj/4.png" /> </div>
<div> <img src="emj/5.png" /> </div>
<div> <img src="emj/6.png" /> </div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
</div>
<div class="foot">
<div class="foot-tool">
<a href="javascript:;" class="img"></a>
<a href="javascript:;" class="file">
<label for="file"></label>
<input type="file" id="file" style="display: none;">
</a>
<!-- <img id="file" src="img/img.png" /> -->
<!-- <input type="file"> -->
</div>
<div class="foot-text">
<textarea rows="6" cols="200" id="text">
</textarea>
</div>
<div class="foot-send">
<button type="button" id="button">发 送</button>
</div>
</div>
</div>
<script src="./socket.io/socket.io.js"></script>
<script src="./js/index.js"></script>
</body>
</html>
index.css
* {
box-sizing: border-box;
/* background-color: darkgrey; */
}
/* 聊天框整体 */
.box {
display: block;
border: 1px solid black;
margin: 5rem 15rem;
width: 60rem;
height: 40rem;
background-color: aliceblue;
}
.head{
border: 1px solid skyblue;
background-color: #87CEEB;
height: 5%;
text-align: center;
}
/* 对话框 */
.body{
/* 悬浮emj表情 父元素*/
position: relative;
background-color: #F0F8FF;
border: 1px solid blueviolet;
height: 70%;
}
.body-left{
background-color: white;
border: 1px solid green;
float: left;
width: 20%;
height: 100%;
}
.body-right{
border: 1px solid yellow;
float: right;
width: 80%;
height: 100%;
overflow: scroll;
}
.card{
height: 30%;
border: 1px solid black;
}
.card1{
height: 55%;
border: 1px solid black;
overflow: scroll;
}
/* 输入框 */
.foot{
background-color: whitesmoke;
border: 1px solid blueviolet;
height: 25%;
}
.foot-tool{
border: 1px solid black;
height: 15%
width: 100%;
}
.foot-text{
border: 1px solid black;
height: 50%;
width: 100%;
overflow: hidden;
}
.foot-send{
border: 1px solid brown;
height: 20%;
width: 100%;
overflow: hidden;
}
/* 清除默认格式 */
ul{
list-style-type: none;
margin: 0;
padding: 0.3125rem;
}
/* 设置按钮大小 */
button{
background-color: dodgerblue;
padding: 0.25rem 2.5rem;
float: right;
}
/* 利用css给连接伪造一个图片 */
.foot .foot-tool .file {
display: inline-block;
vertical-align: middle;
width: 40px;
height: 45px;
background: url('img/img.png') no-repeat ;
}
.foot .foot-tool .img {
display: inline-block;
vertical-align: middle;
width: 40px;
height: 45px;
background: url('img/emj.png') no-repeat ;
}
.foot .foot-tool .file label {
width: 100%;
height: 100%;
display: block;
}
/* 表情框 */
.emj{
/* display: none; */
display: grid;
grid-gap: 1px;
grid-template-columns: auto auto auto auto;
position: fixed;
top: 350px;
left: 16rem;
width: 12.5rem;
height:12.5rem;
border: 1px solid grey;
background-color: gainsboro;
}
/* 规定网格大小 */
.emj > div{
width: 3rem;
height: 3rem;
}
/* 规定图片大小 */
div.emj img{
width: 2.7rem;
height: 2.7rem
}
/* 图片选择效果 */
div.emj img:hover{
border: 1px solid grey;
width: 3rem;
height: 3rem
}
/* 当鼠标移点击页面时自动对焦到第一行 */
textarea{
}
index.js
/*
聊天室的功能
1.连接服务器
2.用户信息
//创建用户 (服务器创建用户,发送给该页面
//销毁用户 (服务器销毁用户,发送给该页面
3.用户发送消息
//个人用户向服务器发送
//浏览器io给所有用户
*/
/* 1.连接服务器 */
var socket = io();
/* if(socket===null) */
//隐藏表情框
$("div.emj").hide();
//当前元素(最近一条消息)底部滚动到可视区
function scrollIntoView(n) {
$(n).children(':last').get(0).scrollIntoView(false);
}
/* 2.用户信息 */
//创建全局变量
var name;
//接收服务器创建的用户
socket.on('send_name', function(data) {
$("p.p1").empty();
name = data;
$("p.p1").append(`
<p>请文明发言</p>
<p class="p1">欢迎加入聊天,用户${data}</p>
`);
});
//接收服务器销毁的用户,并且在.body-right输出
socket.on('remove_names', function(data) {
$(".body-right").append(`<p style="text-align:center;color:grey">用户${data}偷偷离开了聊天室</p>`);
scrollIntoView('.body-right');
});
//在.body-left输出所有用户的信息
socket.on('send_names', function(data) {
//先清空
$("ul").empty();
/*测试: alert(data); */
//统计聊天室成员
data.forEach(item => {
if (item > 0)
$("ul").append("<li>用户" + item + "在线<li>");
});
scrollIntoView('.body-left');
});
/* 3.用户发送消息 */
/*
用户向服务器发送文字消息
*/
$("#button").click(function() {
var text = $("#text").val();
/* 测试:alert(text); */
//向服务器发送文字信息,type为txt
socket.emit('send_app.js_text', {
name: name,
text: text,
type: "txt"
});
//发送后内容清空
$("#text").val("");
});
/*
用户向服务器发送图片消息,type为img
*/
$('#file').on('change', function() {
var file = this.files[0]
//需要把这个文件发送到服务器,借助于H5新增的fileReader
var fr = new FileReader()
fr.readAsDataURL(file)
fr.onload = function() {
/* 测试:alert(fr.result); */
socket.emit('send_app.js_text', {
name: name,
text: fr.result,
type: "img"
});
}
});
/*
用户向服务器发送表情包消息,type为emj
*/
//表情选取
//鼠标移入表情包悬浮框显示
$('a.img').on('mouseenter', function() {
$(".emj").show();
});
//鼠标移出,表情包悬浮框消失
$('div.emj').on('mouseleave', function() {
$("div.emj").hide();
});
//点击表情获取src属性
$("div.emj img").on('click',function(){
/* 测试: alert($(this).attr("src")); */
//加入聊天框
socket.emit('send_app.js_text', {
name: name,
text: $(this).attr("src"),
type: "emj"
});
});
/* 接收服务器发送的消息 */
socket.on('send_message', function(data) {
/* 测试 :alert(data); */
//避免重复
$(".body-right").empty();
//循环输出数组
data.forEach(item => {
$(".body-right").append(`
<p style="text-align:center;color:grey">${item.time}</p>
<p>用户${item.name}</p>
`);
if (item.type === "txt")
$(".body-right").append(`<p>${item.text}</p>`);
else if (item.type == "img")
$(".body-right").append(`<img src="${item.text}" height=200px
width=200px alt="" />`);
else
$(".body-right").append(`<img src="${item.text}" />`);
});
scrollIntoView('.body-right');
});
/* 下载消息 */
/* socket.on('download', function(data) {
var
}); */
app.js
const { data } = require('jquery');
// 通过express创建了http服务器
var app = require('express')();
var http = require('http').Server(app);//httpServer要绑定的服务器
var io = require('socket.io')(http);
//Express中static方法可以处理静态资源
//将public目录设置为静态资源目录
app.use(require('express').static('public'))
app.get('/', function(req, res){
res.redirect('index.html')
});
/*获取当前时间*/
function getNowTime() {
var date = new Date();
//年 getFullYear():四位数字返回年份
var year = date.getFullYear(); //getFullYear()代替getYear()
//月 getMonth():0 ~ 11
var month = date.getMonth() + 1;
//日 getDate():(1 ~ 31)
var day = date.getDate();
//时 getHours():(0 ~ 23)
var hour = date.getHours();
//分 getMinutes(): (0 ~ 59)
var minute = date.getMinutes();
//秒 getSeconds():(0 ~ 59)
var second = date.getSeconds();
var time = ' ' + year + '-' + addZero(month) + '-' + addZero(day) + ' ' + addZero(hour) + ':' + addZero(minute) + ':' + addZero(second);
return time;
}
function addZero(s) {
return s < 10 ? ('0' + s) : s;
}
/* 定义全局变量 */
//用户名
var name = 0;
//总用户
var usernames = [];
//消息记录
var messages =[];
//监听了用户的连接事件
io.on('connection', function(socket){
/* 2.用户信息 */
//创建用户
name = name+1;
socket.name = name;
//将该用户加入登录用户中
usernames.push(name);
// 向浏览器发送该用户
socket.emit('send_name',name);
// 向所有用户发送所有用户
io.emit('send_names',usernames);
/* 3.用户发送消息 */
// 接收浏览器信息
socket.on('send_app.js_text',function(data){
/* 测试 :console.log(message); */
//块作用域
let message = {
time:getNowTime(),
name:data.name,
text:data.text,
//通过type来区别信息
type:data.type
}
messages.push(message)
console.log(messages);
io.emit('send_message',messages);
});
//监听某位用户断开连接事件(自带功能,不需要浏览器emit)
socket.on('disconnect',function(data){
//找到该用户并移出数组(删除用户)
for(i=0;i<usernames.length;i++){
if(usernames[i] == socket.name)
usernames[i]=-1;
}
/* 测试
usernames.forEach(item=>{
console.log(item);
}); */
//向所有用户发送离开信息
io.emit('send_names',usernames);
io.emit('remove_names',socket.name);
/* //下载消息
socket.emit('download',messages);
messages */
});
});
/* */
//指定端口号为3000
http.listen(3000, function(){
console.log('listening on *:3000');
});
day4
把数据库连上了,以及消息存储到本地(但是图片的存储存在问题,老师说可以通过mongodb或者存路径
messages数组变成了存储历史消息并放入txt
导入信息则完全交给了数据库
index.html
<!DOCTYPE html>
<html >
<head>
<meta charset="UTF-8">
<title>聊天室</title>
<link rel="stylesheet" type="text/css" href="index.css">
<script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
</head>
<body>
<div class="box">
<div class="head">
聊天室
</div>
<div class="body">
<!-- 用户信息栏 -->
<div class="body-left">
<div class="card">
<h3>公告栏</h3>
<p class="p1">欢迎加入聊天,用户</p>
</div>
<h4>聊天室成员</h4>
<div class="card1">
<ul>
</ul>
</div>
</div>
<!-- 聊天信息框 -->
<div class="body-right">
</div>
</div>
<!-- 表情框,初始为隐藏 -->
<div class="emj">
<div> <img src="emj/1.png" id="1"/> </div>
<div> <img src="emj/2.png" id="2"/> </div>
<div> <img src="emj/3.png" /> </div>
<div> <img src="emj/4.png" /> </div>
<div> <img src="emj/5.png" /> </div>
<div> <img src="emj/6.png" /> </div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
</div>
<div class="foot">
<div class="foot-tool">
<a href="javascript:;" class="img"></a>
<a href="javascript:;" class="file">
<label for="file"></label>
<input type="file" id="file" style="display: none;">
</a>
<a href="javascript:;" class="download"></a>
<!-- <img id="file" src="img/img.png" /> -->
<!-- <input type="file"> -->
</div>
<div class="foot-text">
<textarea rows="6" cols="200" id="text">
</textarea>
</div>
<div class="foot-send">
<button type="button" id="button">发 送</button>
</div>
</div>
</div>
<script src="./socket.io/socket.io.js"></script>
<script src="./js/index.js"></script>
</body>
</html>
index.css
* {
box-sizing: border-box;
/* background-color: darkgrey; */
}
/* 聊天框整体 */
.box {
display: block;
border: 1px solid black;
margin: 5rem 15rem;
width: 60rem;
height: 40rem;
background-color: aliceblue;
}
.head{
border: 1px solid skyblue;
background-color: #87CEEB;
height: 5%;
text-align: center;
}
/* 对话框 */
.body{
/* 悬浮emj表情 父元素*/
position: relative;
background-color: #F0F8FF;
border: 1px solid blueviolet;
height: 70%;
}
.body-left{
background-color: white;
border: 1px solid green;
float: left;
width: 20%;
height: 100%;
}
.body-right{
border: 1px solid yellow;
float: right;
width: 80%;
height: 100%;
overflow: scroll;
}
.card{
height: 30%;
border: 1px solid black;
}
.card1{
height: 55%;
border: 1px solid black;
overflow: scroll;
}
/* 输入框 */
.foot{
background-color: whitesmoke;
border: 1px solid blueviolet;
height: 25%;
}
.foot-tool{
border: 1px solid black;
height: 15%
width: 100%;
}
.foot-text{
border: 1px solid black;
height: 50%;
width: 100%;
overflow: hidden;
}
.foot-send{
border: 1px solid brown;
height: 20%;
width: 100%;
overflow: hidden;
}
/* 清除默认格式 */
ul{
list-style-type: none;
margin: 0;
padding: 0.3125rem;
}
/* 设置按钮大小 */
button{
background-color: dodgerblue;
padding: 0.25rem 2.5rem;
float: right;
}
/* 利用css给连接伪造一个图片 */
.foot .foot-tool .file {
display: inline-block;
vertical-align: middle;
width: 40px;
height: 45px;
background: url('img/img.png') no-repeat ;
}
.foot .foot-tool .download {
display: inline-block;
vertical-align: middle;
width: 40px;
height: 45px;
background: url('img/download.png') no-repeat ;
}
.foot .foot-tool .img {
display: inline-block;
vertical-align: middle;
width: 40px;
height: 45px;
background: url('img/emj.png') no-repeat ;
}
.foot .foot-tool .file label {
width: 100%;
height: 100%;
display: block;
}
/* 表情框 */
.emj{
/* display: none; */
display: grid;
grid-gap: 1px;
grid-template-columns: auto auto auto auto;
position: fixed;
top: 21.875rem;
left: 16rem;
width: 12.5rem;
height:12.5rem;
border: 1px solid grey;
background-color: gainsboro;
}
/* 规定网格大小 */
.emj > div{
width: 3rem;
height: 3rem;
}
/* 规定图片大小 */
div.emj img{
width: 2.7rem;
height: 2.7rem
}
/* 图片选择效果 */
div.emj img:hover{
border: 1px solid grey;
width: 3rem;
height: 3rem
}
index.js
/*
聊天室的功能
1.连接服务器
2.用户信息
//创建用户 (服务器创建用户,发送给该页面
//销毁用户 (服务器销毁用户,发送给该页面
3.用户发送消息
//个人用户向服务器发送
//浏览器io给所有用户
*/
/* 1.连接服务器 */
var socket = io();
/* if(socket===null) */
//隐藏表情框
$("div.emj").hide();
//当前元素(最近一条消息)底部滚动到可视区
function scrollIntoView(n) {
$(n).children(':last').get(0).scrollIntoView(false);
}
//查看历史消息
/* 2.用户信息 */
//创建全局变量
var name;
//接收服务器创建的用户
socket.on('send_name', function(data) {
$("p.p1").empty();
name = data;
$("p.p1").append(`
<p>请文明发言</p>
<p class="p1">欢迎加入聊天,用户${data}</p>
`);
});
//接收服务器销毁的用户,并且在.body-right输出
socket.on('remove_names', function(data) {
$(".body-right").append(`<p style="text-align:center;color:grey">用户${data}偷偷离开了聊天室</p>`);
scrollIntoView('.body-right');
});
//在.body-left输出所有用户的信息
socket.on('send_names', function(data) {
//先清空
$("ul").empty();
/*测试: alert(data); */
//统计聊天室成员
data.forEach(item => {
if (item > 0)
$("ul").append("<li>用户" + item + "在线<li>");
});
scrollIntoView('.body-left');
});
/* 3.用户发送消息 */
/*
用户向服务器发送文字消息
*/
$("#button").click(function() {
var text = $("#text").val();
/* 测试:alert(text); */
//向服务器发送文字信息,type为txt
socket.emit('send_app.js_text', {
name: name,
text: text,
type: "txt"
});
//发送后内容清空
$("#text").val("");
});
/*
用户向服务器发送图片消息,type为img
*/
$('#file').on('change', function() {
var file = this.files[0]
//需要把这个文件发送到服务器,借助于H5新增的fileReader
var fr = new FileReader()
fr.readAsDataURL(file)
fr.onload = function() {
/* 测试:alert(fr.result); */
socket.emit('send_app.js_text', {
name: name,
text: fr.result,
type: "img"
});
}
});
/*
用户向服务器发送表情包消息,type为emj
*/
//表情选取
//鼠标移入表情包悬浮框显示
$('a.img').on('mouseenter', function() {
$(".emj").show();
});
//鼠标移出,表情包悬浮框消失
$('div.emj').on('mouseleave', function() {
$("div.emj").hide();
});
//点击表情获取src属性
$("div.emj img").on('click',function(){
/* 测试: alert($(this).attr("src")); */
//加入聊天框
socket.emit('send_app.js_text', {
name: name,
text: $(this).attr("src"),
type: "emj"
});
});
/* 4. 接收服务器发送的消息 */
//用户消息
socket.on('send_message', function(data) {
/* 测试 :alert(data); */
$(".body-right").append(`
<p style="text-align:center;color:grey">${data.time}</p>
<p>用户${data.name}</p>
`);
if (data.type === "txt")
$(".body-right").append(`<p>${data.text}</p>`);
else if (data.type == "img")
$(".body-right").append(`<img src="${data.text}" height=200px
width=200px alt="" />`);
else
$(".body-right").append(`<img src="${data.text}" />`);
scrollIntoView('.body-right');
});
//历史信息
socket.on('history_message', function(data) {
$(".body-right").append(`
<p style="text-align:center;color:grey">${data.time}</p>
<p>用户${data.name}</p>
`);
//判断消息类型
if (data.type === "txt")
$(".body-right").append(`<p>${data.text}</p>`);
else if (data.type == "img")
$(".body-right").append(`<img src="${data.text}" height=200px
width=200px alt="" />`);
else
$(".body-right").append(`<img src="${data.text}" />`);
});
历史信息数
socket.on('history_num', function(data) {
$(".body-right").append(`<p style="text-align:center;color:grey">-------------------有${data}条历史消息记录------------</p>`);
scrollIntoView('.body-right');
});
/* 下载消息 */
$('a.download').on('click',function(){
socket.emit('download');
alert("downlod");
/* socket.on('download', function(data) {
var file = new ActiveXObject("Scripting.FileSystemObject");
var fopen = file.CreateTextFile("../text.txt",true);
data.forEach(item=>{
alert("downlod");
if(data.type === "txt")
{
alert("downlod");
fopen.Write ("用户"+data.name+":");
fopen.WriteLine (data.text);
}
});
tf.Close();
}); */
});
app.js
const { data } = require('jquery');
// 通过express创建了http服务器
var app = require('express')();
var http = require('http').Server(app);//httpServer要绑定的服务器
var io = require('socket.io')(http);
const db = require('./db/db.js');
//Express中static方法可以处理静态资源
//将public目录设置为静态资源目录
app.use(require('express').static('public'))
app.get('/', function(req, res){
res.redirect('index.html')
});
/*获取当前时间*/
function getNowTime() {
var date = new Date();
//年 getFullYear():四位数字返回年份
var year = date.getFullYear(); //getFullYear()代替getYear()
//月 getMonth():0 ~ 11
var month = date.getMonth() + 1;
//日 getDate():(1 ~ 31)
var day = date.getDate();
//时 getHours():(0 ~ 23)
var hour = date.getHours();
//分 getMinutes(): (0 ~ 59)
var minute = date.getMinutes();
//秒 getSeconds():(0 ~ 59)
var second = date.getSeconds();
var time = ' ' + year + '-' + addZero(month) + '-' + addZero(day) + ' ' + addZero(hour) + ':' + addZero(minute) + ':' + addZero(second);
return time;
}
function addZero(s) {
return s < 10 ? ('0' + s) : s;
}
//加载历史消息
function initMessage(socket) {
db.selectAll('select * from websocket order by id asc', (e, res) => {
for (var i = 0; i < res.length; i++) {
socket.emit('history_message', res[i]);
/* 测试
console.log('历史消息:');
console.log(res[i]); */
//将历史消息加入messages中
if(res[i].type === "img")
{
messages.push({
name:res[i].name,
text:"图片消息",
});
}
else
{
{
messages.push({
name:res[i].name,
text:res[i].text,
});
}
}
}
//发送历史消息数
socket.emit('history_num',id);
/* console.log('messages历史消息:');
console.log(messages); */
})
}
/* 定义全局变量 */
//用户名
var name = 0;
//总用户
var usernames = [];
//消息记录
var messages =[];
//用户连接事件
io.on('connection', function(socket){
//加载数据库历史消息
db.selectAll('select count(*) as sum from websocket', (e, r) => {
console.log('数据库共有' + r[0].sum + '条历史消息记录')
id = r[0].sum;
//发送历史信息
initMessage(socket);
});
/* 2 .用户信息 */
//创建用户
name = name+1;
socket.name = name;
//将该用户加入登录用户中
usernames.push(name);
// 向浏览器发送该用户
socket.emit('send_name',name);
// 向所有用户发送所有用户
io.emit('send_names',usernames);
/* 3.给用户发送消息 */
// 接收浏览器信息
socket.on('send_app.js_text',function(data){
/* 测试 :console.log(message); */
let message = {
name:data.name,
time:getNowTime(),
text:data.text,
//通过type来区别信息
type:data.type
}
//计入数组
//传入数据库
db.insertData('websocket', message, (e, r) => {
//测试
console.log('消息存入成功')
console.log(message);
})
io.emit('send_message',message);
});
/* 下载消息 */
socket.on('download',function(){
console.log("开始下载");
const fs = require("fs");
messages.forEach(item=>{
fs.appendFileSync("test.txt", "用户"+item.name+":\r\n"+item.text+"\r\n\r\n");
let data = fs.readFileSync("test.txt", "utf8");
console.log(data); // Hello world
})
});
/* 监听某位用户断开连接事件(自带功能,不需要浏览器emit) */
socket.on('disconnect',function(data){
//找到该用户并移出数组(删除用户)
for(i=0;i<usernames.length;i++){
if(usernames[i] == socket.name)
usernames[i]=-1;
}
/* 测试
usernames.forEach(item=>{
console.log(item);
}); */
//向所有用户发送离开信息
io.emit('send_names',usernames);
//在列表移除用户
io.emit('remove_names',socket.name);
});
});
/* */
//指定端口号为3000
http.listen(3000, function(){
console.log('listening on *:3000');
});
db.js
const conn = require('./config');
const connection = conn();
// 查询所有数据
let selectAll = (sql,callback)=>{
connection.query(sql,(err,result)=>{
if(err){
console.log('错误信息-',err.sqlMessage);
let errNews = err.sqlMessage;
callback(errNews,'');
return;
}
var string=JSON.stringify(result);
var data = JSON.parse(string);
callback('',data);
// console.log(string);
})
}
// 插入一条数据
let insertData = (table,datas,callback)=>{
var fields='';
var values='';
for( var k in datas){
fields+=k+',';
values=values+"'"+datas[k]+"',"
}
fields=fields.slice(0,-1);
values=values.slice(0,-1);
console.log(fields,values);
var sql="INSERT INTO "+table+'('+fields+') VALUES('+values+')';
connection.query(sql,callback);
}
// 删除一条数据
let deleteData=function(table,where,callback){
var _WHERE='';
for(var k2 in where){
//多个筛选条件使用 _WHERE+=k2+"='"+where[k2]+"' AND ";
_WHERE+= k2+"="+where[k2];
}
// DELETE FROM user WHERE UserId=12 注意UserId的数据类型要和数据库一致
var sql="DELETE FROM "+table+' WHERE '+_WHERE;
connection.query(sql,callback);
}
exports.selectAll = selectAll;
exports.insertData = insertData;
exports.deleteData = deleteData;
config.js
const mysql = require('mysql')
const connectdb=()=>{
var connection = mysql.createConnection({
host : 'localhost',
user : 'root',
password : '123456',
port: '3306',
database: 'websocket'
})
return connection;
}
module.exports=connectdb;
至此,此次课设的全部任务以及完成了,下面是拓展功能
最终界面
本来想使用,但是没找到具体用法,自己想了一个其他办法
利用点击事件选取要发送的用户并且获取id,传值,在服务器中判断是否id有值,如果有则是私聊,没有就是公聊。再在浏览器判断当前用户是否为传值中的两个用户,如果是则显示私聊界面。
html
<!DOCTYPE html>
<html >
<head>
<meta charset="UTF-8">
<title>聊天室</title>
<link rel="stylesheet" type="text/css" href="index.css">
<script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
</head>
<body>
<div class="box">
<div class="head">
聊天室
<button class="but2">X</button>
</div>
<div class="body">
<!-- 用户信息栏 -->
<div class="body-left">
<div class="card">
<h3>公告栏</h3>
<p class="p1">欢迎加入聊天,用户</p>
</div>
<h3>聊天室成员</h3>
<div class="card1">
<!--
$(".card1").append(`<a href="javascript:;" class="li">用户 ${item} 在线</a>`);
-->
</div>
</div>
<!-- 聊天信息框 -->
<div class="body-right1">
<p style="text-align:center;color:grey">----------此界面为临时聊天框,离开消息即清空---------</p>
</div>
<div class="body-right">
</div>
</div>
<!-- 表情框,初始为隐藏 -->
<div class="emj">
<div> <img src="emj/1.png" /> </div>
<div> <img src="emj/2.png" /> </div>
<div> <img src="emj/3.png" /> </div>
<div> <img src="emj/4.png" /> </div>
<div> <img src="emj/5.png" /> </div>
<div> <img src="emj/6.png" /> </div>
<div> <img src="emj/7.png" /> </div>
<div> <img src="emj/8.png" /> </div>
<div> <img src="emj/9.png" /> </div>
<div> <img src="emj/10.png" /> </div>
<div> <img src="emj/11.png" /> </div>
<div> <img src="emj/12.png" /> </div>
<div> <img src="emj/13.png" /> </div>
<div> <img src="emj/14.png" /> </div>
<div> <img src="emj/15.png" /> </div>
<div> <img src="emj/16.png" /> </div>
</div>
<div class="foot">
<div class="foot-tool">
<a href="javascript:;" class="img"></a>
<a href="javascript:;" class="file">
<label for="file"></label>
<input type="file" id="file" style="display: none;">
</a>
<a href="javascript:;" class="download"></a>
<!-- <img id="file" src="img/img.png" /> -->
<!-- <input type="file"> -->
</div>
<div class="foot-text">
<textarea rows="6" cols="200" id="text">
</textarea>
</div>
<div class="foot-send">
<button type="button" class="but1">发 送</button>
</div>
</div>
</div>
<script src="./socket.io/socket.io.js"></script>
<script src="./js/index.js"></script>
</body>
</html>
css
::-webkit-scrollbar {
/*隐藏滚轮*/
display: none;
}
* {
box-sizing: border-box;
/* background-color: darkgrey; */
}
body{
background-color: darkseagreen;
}
/* 聊天框整体 */
.box {
display: block;
border: 1px solid black;
margin: 5rem 15rem;
width: 60rem;
height: 40rem;
background-color: aliceblue;
}
.head{
border: 1px solid skyblue;
background-color: #87CEEB;
height: 7%;
text-align: center;
}
/* 对话框 */
.body{
/* 悬浮emj表情 父元素*/
position: relative;
background-color: #F0F8FF;
/* border: 1px solid blueviolet; */
height: 70%;
}
.body-left{
background-color: white;
/* border: 1px solid green; */
float: left;
width: 20%;
height: 100%;
}
.body-right1{
position: fixed;
border: 1px solid black;
float: right;
width: 59.95rem;
height: 27.9rem;
overflow: scroll;
background: url('picture/background.jpg' ) no-repeat;
background-size: 100%;
opacity:0.8;
}
.body-right{
border: 1px solid black;
float: right;
width: 80%;
height: 100%;
overflow: scroll;
}
.card{
height: 30%;
border: 1px solid black;
}
.card1{
height: 56%;
border: 1px solid black;
overflow: scroll;
}
/* 输入框 */
.foot{
background-color: whitesmoke;
/* border: 1px solid blueviolet; */
height: 22%;
}
.foot-tool{
background-color: #DCDCDC;
border: 1px solid black;
height: 15%
width: 100%;
}
.foot-text{
/* border: 1px solid black; */
background-color: white;
height: 50%;
width: 100%;
overflow: hidden;
}
.foot-send{
/* border: 1px solid brown; */
background-color: white;
height: 20%;
width: 100%;
overflow: hidden;
}
/* 设置按钮大小 */
.but1{
background-color: dodgerblue;
padding: 0.25rem 2.5rem;
float: right;
}
.but2{
background-color: red;
float: right;
}
/* 利用css给连接伪造一个图片 */
.foot .foot-tool .file {
display: inline-block;
vertical-align: middle;
width: 40px;
height: 45px;
background: url('picture/img.png') no-repeat ;
}
.foot .foot-tool .download {
display: inline-block;
vertical-align: middle;
width: 40px;
height: 45px;
background: url('picture/download.png') no-repeat ;
}
.foot .foot-tool .img {
display: inline-block;
vertical-align: middle;
width: 40px;
height: 45px;
background: url('picture/emj.png') no-repeat ;
}
.foot .foot-tool .file label {
width: 100%;
height: 100%;
display: block;
}
/* 表情框 */
.emj{
/* display: none; */
display: grid;
grid-gap: 1px;
grid-template-columns: auto auto auto auto;
position: fixed;
top: 21.875rem;
left: 16rem;
width: 12.5rem;
height:12.5rem;
border: 1px solid grey;
background-color: gainsboro;
}
/* 规定网格大小 */
.emj > div{
width: 3rem;
height: 3rem;
background-color:#F0F8FF;
}
/* 规定图片大小 */
div.emj img{
width: 2.7rem;
height: 2.7rem
}
/* 图片选择效果 */
div.emj img:hover{
border: 1px solid grey;
width: 3rem;
height: 3rem
}
/* 当鼠标移点击页面时自动对焦到第一行 */
/* 清除默认样式 */
.body .body-left a {
display: block;
text-decoration:none;
padding: 0.3125rem;
color: #000000;
}
.right{
/*消息排版会乱掉
1.float: right;
2.position:absolute;
right:50px;
*/
float: right;
clear:both;
}
.left{
/* float: left; */
clear:both;
}
index.js
/*
聊天室的功能
1.连接服务器
2.用户信息
//创建用户 (服务器创建用户,发送给该页面
//销毁用户 (服务器销毁用户,发送给该页面
3.用户发送消息
//个人用户向服务器发送
//浏览器io给所有用户
*/
/* 1.连接服务器 */
var socket = io();
/* if(socket===null) */
//隐藏表情框
$("div.emj").hide();
//隐藏返回按钮
$(".but2").hide();
//隐藏对话框
$(".body-right1").hide();
//当前元素(最近一条消息)底部滚动到可视区
function scrollIntoView(n) {
$(n).children(':last').get(0).scrollIntoView(false);
}
//查看历史消息
socket.emit('history');
/* 2.用户信息 */
//创建全局变量
var name;
//被选中的用户(只有点击后才不为0)
var p_id=0;
//接收服务器创建的用户
socket.on('send_name', function(data) {
$("p.p1").empty();
name = data;
$("p.p1").append(`
<p>请文明发言</p>
<p class="p1">欢迎加入聊天,用户${data}</p>
`);
});
//接收服务器销毁的用户,并且在.body-right输出
socket.on('remove_names', function(data) {
$(".body-right").append(`<p style="text-align:center;color:grey">用户${data}偷偷离开了聊天室</p>`);
scrollIntoView('.body-right');
});
//在.body-left输出所有用户的信息
socket.on('send_names', function(data) {
//先清空
$(".card1").empty();
/*测试: alert(data); */
//统计聊天室成员
data.forEach(item => {
if (item > 0)
$(".card1").append(`<a href="javascript:;" class="li" id="${item}">用户 ${item} 在线</a>`);
});
scrollIntoView('.body-left');
});
/* 3.用户发送消息 */
/*
用户向服务器发送文字消息
*/
//
//点击发送
$("button.but1").click(function() {
var text = $("#text").val();
/* 测试:alert(text); */
//向服务器发送文字信息,type为txt
socket.emit('send_app.js_text', {
name: name,
text: text,
type: "txt"
},p_id);
//发送后内容清空
$("#text").val("");
});
/*
用户向服务器发送图片消息,type为img
*/
$('#file').on('change', function() {
var file = this.files[0]
//需要把这个文件发送到服务器,借助于H5新增的fileReader
var fr = new FileReader()
fr.readAsDataURL(file);
fr.onload = function() {
/* 测试:alert(fr.result); */
socket.emit('send_app.js_text', {
name: name,
text: fr.result,
type: "img"
},p_id);
}
});
/*
用户向服务器发送表情包消息,type为emj
*/
//表情选取
//鼠标移入表情包悬浮框显示
$('a.img').on('mouseenter', function() {
$(".emj").show();
});
//鼠标移出,表情包悬浮框消失
$('div.emj').on('mouseleave', function() {
$("div.emj").hide();
});
//点击表情获取src属性
$("div.emj img").on('click',function(){
/* 测试: alert($(this).attr("src")); */
//加入聊天框
socket.emit('send_app.js_text', {
name: name,
text: $(this).attr("src"),
type: "emj"
},p_id);
});
/* 4. 接收服务器发送的消息 */
//用户消息
socket.on('send_message', function(data) {
/* 测试 :alert(data); */
$(".body-right").append(`
<p style="text-align:center;color:grey">${data.time}</p>
<p>用户${data.name}</p>
`);
if (data.type === "txt")
$(".body-right").append(`<p>${data.text}</p>`);
else if (data.type == "img")
$(".body-right").append(`<img src="${data.text}" height=200px
width=200px alt="" />`);
else
$(".body-right").append(`<img src="${data.text}" />`);
scrollIntoView('.body-right');
});
//历史信息
//历史信息数
socket.on('history_num', function(data) {
$(".body-right").append(`<p style="text-align:center;color:grey">-------------------有${data}条历史消息记录------------</p>`);
scrollIntoView('.body-right');
});
/* 下载消息 */
$('a.download').on('click',function(){
socket.emit('download');
alert("下载成功");
});
/* 私聊功能 */
//双击左边聊天室成员,和其私聊( .body-right1悬浮窗显示,显示返回按钮
$('.card1').on('click','a.li',function(){
/* alert($(this).attr('id')); */
p_id=$(this).attr('id');
if(p_id===name){
alert("不能与自己聊天")
//flag判断是否与自己聊天
var flag=-1;
}
if(flag!=-1){
$(".body-right1").empty();
$(".body-left").hide();
$(".body-right").hide();
$(".but2").show();
$(".body-right1").show();
}
flag=0;
});
//只能这两个用户可以看到消息
socket.on('private_message', function(data) {
/* $(".body-right1").empty(); */
if(data.send_name===name||data.save_name===name){
$(".body-left").hide();
$(".body-right").hide();
$(".but2").show();
$(".body-right1").show();
//如果是自己发信息,在右边;别人的在左边
if(data.send_name===name)
{
//!!!!!!!!!!!!!!!!!!!!更换p_id
p_id=data.save_name;
direction="left"
}
else
direction="right";
$(".body-right1").append(`
<p style="text-align:center;color:grey">${data.time}</p>`);
if (data.type === "txt")
$(".body-right1").append(`
<div class="${direction}">
<p style="text-align:${direction} ">用户${data.save_name}</p>
<p>${data.text}</p>
</div>`);
else if (data.type == "img")
$(".body-right1").append(`
<div class="{direction}">
<p style="text-align:${direction} ">用户${data.save_name}</p>
<img src="${data.text}" height=200px width=200px alt="" />
</div>`);
else
$(".body-right1").append(`
<div class="{direction}">
<p style="text-align:${direction} ">用户${data.save_name}</p>
<img src="${data.text}" />
</div>`);
scrollIntoView('.body-right1');
//
}
});
//返回界面
$('.head').on('click','button.but2',function(){
$(".body-right").empty();
$(".body-right1").empty();
$(".but2").hide();
$(".body-right1").hide();
$(".body-left").show();
$(".body-right").show();
//判断条件恢复
p_id=0;
//调用历史消息
socket.emit('history');
//
socket.emit('leave');
});
socket.on('leaves', function() {
$(".body-right1").append(`<p style="text-align:center;color:grey">另一位用户已离开私聊界面</p>`);
scrollIntoView('.body-right1');
});
app.js
const { data } = require('jquery');
// 通过express创建了http服务器
var app = require('express')();
var http = require('http').Server(app);//httpServer要绑定的服务器
var io = require('socket.io')(http);
const db = require('./db/db.js');
//Express中static方法可以处理静态资源
//将public目录设置为静态资源目录
app.use(require('express').static('public'))
app.get('/', function(req, res){
res.redirect('index.html')
});
/*获取当前时间*/
function getNowTime() {
var date = new Date();
//年 getFullYear():四位数字返回年份
var year = date.getFullYear(); //getFullYear()代替getYear()
//月 getMonth():0 ~ 11
var month = date.getMonth() + 1;
//日 getDate():(1 ~ 31)
var day = date.getDate();
//时 getHours():(0 ~ 23)
var hour = date.getHours();
//分 getMinutes(): (0 ~ 59)
var minute = date.getMinutes();
//秒 getSeconds():(0 ~ 59)
var second = date.getSeconds();
var time = ' ' + year + '-' + addZero(month) + '-' + addZero(day) + ' ' + addZero(hour) + ':' + addZero(minute) + ':' + addZero(second);
return time;
}
function addZero(s) {
return s < 10 ? ('0' + s) : s;
}
//加载历史消息
function initMessage(socket) {
db.selectAll('select * from websocket order by id asc', (e, res) => {
for (var i = 0; i < res.length; i++) {
socket.emit('send_message', res[i]);
/* 测试
console.log('历史消息:');
console.log(res[i]); */
//将历史消息加入messages中
if(res[i].type === "img")
{
messages.push({
name:res[i].name,
text:"图片消息",
});
}
else
{
messages.push({
name:res[i].name,
text:res[i].text,
});
}
}
//发送历史消息数
socket.emit('history_num',id);
/* console.log('messages历史消息:');
console.log(messages); */
})
}
/* 定义全局变量 */
//用户名
var name = 0;
//总用户
var usernames = [];
//消息记录
var messages =[];
var private_message=[];
//用户连接事件
io.on('connection', function(socket){
//加载数据库历史消息
socket.on('history',function(){
db.selectAll('select count(*) as sum from websocket', (e, r) => {
console.log('数据库共有' + r[0].sum + '条历史消息记录')
id = r[0].sum;
//发送历史信息
initMessage(socket);
});
})
/* 2 .用户信息 */
//创建用户
name = name+1;
socket.name = name;
//将该用户加入登录用户中
usernames.push(name);
// 向浏览器发送该用户
socket.emit('send_name',name);
// 向所有用户发送所有用户
io.emit('send_names',usernames);
/* 3.给用户发送消息 */
// 接收浏览器信息
socket.on('send_app.js_text',function(data,id){
/* console.log(id); */
//聊天室
if (id===0)
{
let message = {
name: data.name,
time: getNowTime(),
text: data.text,
//通过type来区别信息
type: data.type
}
//当前消息存入数组
if (data.type === "img") {
messages.push({
name: data.name,
text: "图片消息",
});
} else {
messages.push({
name: data.name,
text: data.text,
});
}
//传入数据库
db.insertData('websocket', message, (e, r) => {
//测试
console.log('消息存入成功')
console.log(message);
})
io.emit('send_message', message);
}
//私聊,通过send_name和save_name实现
//数据使用数组保存,可以不存入数据库
else
{
console.log(id)
private_message = {
send_name:id,
save_name:data.name,
time: getNowTime(),
text: data.text,
//通过type来区别信息
type: data.type
}
/* console.log(private_message.send_name);
console.log(private_message.save_name); */
io.emit('private_message', private_message);
}
});
/* 下载消息 */
socket.on('download',function(){
console.log("开始下载");
const fs = require("fs");
messages.forEach(item=>{
fs.appendFileSync("test.txt", "用户"+item.name+": "+item.text+"\r\n\r\n");
let data = fs.readFileSync("test.txt", "utf8");
console.log(data);
})
});
/* 监听某位用户断开连接事件(自带功能,不需要浏览器emit) */
socket.on('disconnect',function(data){
//找到该用户并移出数组(删除用户)
for(i=0;i<usernames.length;i++){
if(usernames[i] == socket.name)
usernames[i]=-1;
}
/* 测试
usernames.forEach(item=>{
console.log(item);
}); */
//向所有用户发送离开信息
io.emit('send_names',usernames);
//在列表移除用户
io.emit('remove_names',socket.name);
//私聊界面
socket.on('leave',function(){
io.emit('leaves');
});
});
/* 私聊 */
/* if (id===0)
公共
else
私聊
*/
// 一个用户退出,告知另一个
socket.on('leave',function(){
io.emit('leaves');
});
});
/* */
//指定端口号为3000
http.listen(3000, function(){
console.log('listening on *:3000');
});