socket 是什么
socket是一个抽象层,是一个组合的api ,应用程序可以通过它发送或接收数据,可对其进行像对文件一样的打开、读写和关闭等操作。socket允许应用程序将I/O插入到网络中,并与网络中的其他应用程序进行通信。单聊其实就是实现端到服务器,再有服务器发送给另一端进行通信。
服务端相关逻辑的代码如下:
let socketio = {};
let sqlQuery = require("./module/XZJMysql");
function getSocket(server) {
socketio.io = require("socket.io")(server);
let io = socketio.io;
io.on("connection", function (socket) {
//服务器端接手登录时间
socket.on("login", async function (data) {
//先判断是否已经有人在登录,如果有人登录的话,那么将其断开连接
let sqlStr1 = "select * from user where isonline = ? and username = ?";
let result1 = await sqlQuery(sqlStr1, ["true", data.username]);
if (result1.length > 0) {
socket
.to(result1[0].socketid)
.emit("logout", { content: "有人登录进来,强制将你踢出去!" });
}
//接收登录事件
let sql = "update user set socketid=?,isonline=? where username =?";
let result = await sqlQuery(sql, [socket.id, "true", data.username]);
socket.emit("login", {
state: "ok",
content: "登陆成功",
});
//某用户上线之后获取最新的未读消息
let strSql3 ="select * from chatMsg where isRead=? and `to`=?";
let result3 =await sqlQuery(strSql3,['false',data.username])
socket.emit('unReadMsg', Array.from(result3))
//断开连接
socket.on("disconnect", async function () {
//修改数据库登录信息(socketid,isonline)
let sqlStr = "update user set socketid=?,isonline=? where socketid =?";
let result = await sqlQuery(sqlStr, [null, null, socket.id]);
});
//获取所有好友列表
socket.on("users", async function(){
let sqlStr1 = "select * from user";
let result1 = await sqlQuery(sqlStr1)
socket.emit("users",Array.from(result1))
});
//发送消息
socket.on("sendMsg", async function(msg){
//查询这个人在不在线
let strSql = 'select * from user where username=? and isonline = ?';
let result = await sqlQuery(strSql,[msg.to.username,'true']);
if(result.length>0){
//如果这个人在线, 那么直接发消息 并且将聊天内容放到数据库中
let toid = result[0].socketid;
socket.to(toid).emit('readMsg',msg)
let strSql1 = "insert into chatmsg (`from`,`to`,`content`,`time`,isRead) VALUES(?,?,?,?,?)";
sqlQuery(strSql1,[msg.from.username,msg.to.username,msg.content,msg.time,'true'])
}else{
let strSql1 = "insert into chatmsg (`from`,`to`,`content`,`time`,isRead) VALUES(?,?,?,?,?)";
sqlQuery(strSql1,[msg.from.username,msg.to.username,msg.content,msg.time,'false'])
}
})
//监听已读消息
socket.on('readMsg',async function(data){
let strSql = "UPDATE chatmsg SET isRead=? where `from` =? and `to` = ?";
let result = await sqlQuery(strSql,['true',data.from,data.self])
})
//加入群
// 1 首先获取所有的群
let strSql = "select * from user where isgroup = ?";
let result4 = await sqlQuery(strSql,['true'])
Array.from(result4).forEach((item,index)=>{
socket.join(item.socketid)
})
});
});
}
socketio.getSocket = getSocket;
module.exports = socketio;
前端使用的vue,在生命周期函数 beforeMount() 方法中去请求后端接口数据。获取数据后在挂载后进行进行相关操作
<template>
<div id="app">
<user-list v-if="$root.me == null" :userlist="userList"></user-list>
<single-chat v-if="(($root.me != null) && (isChatUser ==null) )" :unReadMsgList="unReadMsgList" :islogin="islogin" :users="users" @touser="toChatUser"> </single-chat>
<chat-input v-if="isChatUser !=null" :ischatuser="isChatUser"> </chat-input>
</div>
</template>
<script>
import userList from "./components/userlist.vue";
import singleChat from "./components/singleChat.vue";
import chatInput from './components/chatInput.vue'
import axios from "axios";
import socket from "../socket.js";
export default {
name: "App",
components: {
userList,
singleChat,
chatInput,
},
data() {
return {
//从这里选一个登录用户
userList: [],
islogin: false,
//获取所有好友列表
users:[],
//从列表中选取一个好友聊天
isChatUser:null,
//未读消息
unReadMsgList:[],
/*是否已读 */
unReadUser:[]
};
},
computed:{
usersObj: function () {
let obj = {};
this.userList.forEach((item, index) => {
obj[item.username] = item;
});
return obj;
},
},
//挂载前
async beforeMount() {
let result = await axios.get("/api/userlist");
this.userList = result.data;
},
//挂载后
mounted() {
//登录状态监听
socket.on("login", (data) => {
if ((data.state = "ok")) {
this.islogin = true;
socket.emit("users");
}
});
//强制下线监听
socket.on("logout", (data) => {
this.islogin = false;
socket.disconnect();
});
//断开连接事件
socket.on("disconnect", ()=> {
console.log("断开连接");
});
//获取所有好友列表
socket.on('users',(data)=>{
this.users = data;
})
//获取最新的未读消息
socket.on("unReadMsg", (data) => {
data.forEach((item,index)=>{
//将from/to改成由头像的对象
item.from = this.usersObj[item.from];
item.to = this.usersObj[item.to];
//设置未读的红点
//将聊天的内容分别添加到本地的存储
this.unReadMsgList.push(item);
let strKey = "chat-user-" + this.$root.me.username + "-" + item.from.username;
//先解析本地存储的数据,在添加
// console.log(localStorage[strKey])
localStorage[strKey] = localStorage[strKey] ? localStorage[strKey]: "[]";
let newArr = JSON.parse(localStorage[strKey]);
newArr.push(item);
localStorage[strKey] = JSON.stringify(newArr);
this.unReadMsgList.push(item.from.username)
})
});
},
methods:{
//监听子组件传过来的方法
toChatUser:function(data){
this.isChatUser = data
}
},
};
</script>
<style>
* {
margin: 0;
padding: 0;
}
</style>
单聊界面:这个界面显示所有的好友 点击之后进行聊天界面(chat-input)
<template>
<div class="userlist">
<div class="nav">
<div class="headerimg" :class="{ online: islogin }">
<img v-if="$root.me != null" :src="$root.me.headerimg" />
</div>
<div class="title">消 息</div>
<div class="headerimg"></div>
</div>
<div class="users">
<div
@click="chatUser(item)"
class="useritem"
v-for="(item, index) in friends"
:key="index"
>
<!-- ,onread:unReadMsg.indexof(item.username) !=-1 -->
<div
class="left"
:class="{
online: item.isonline != null,
unread: unReadMsgList.indexOf(item.username) != -1,
}"
>
<img :src="item.headerimg" />
</div>
<div class="right">
<span class="username"> {{ item.username }}</span>
<span class="msg"></span>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
props: ["islogin", "users", "unReadMsgList"],
computed: {
//显示除了登陆这个用户的其他好友
friends: function () {
let username = this.$root.me.username;
return this.users.filter((item, index) => {
return item.username != username;
});
},
},
methods: {
chatUser: function (data) {
this.$emit("touser", data);
},
},
mounted(){
}
};
</script>
<style scoped>
.unread {
position: relative;
}
.unread::before {
position: absolute;
content: "";
display: block;
width: 10px;
height: 10px;
border-radius: 5px;
background: red;
bottom: 5px;
right: 5px;
}
.useritem .left {
filter: grayscale(1);
}
.useritem .right {
padding: 0 10px;
}
.useritem {
display: flex;
height: 80px;
background: #eee;
border-bottom: 1px solid #ccc;
align-items: center;
padding: 0 10px;
}
.useritem .left img {
width: 60px;
height: 60px;
border-radius: 50%;
}
.headerimg {
height: 50px;
width: 50px;
filter: grayscale(1);
margin: 0 10px;
}
.nav .title {
font-weight: 900;
font-size: 18px;
}
.online {
filter: grayscale(0) !important;
}
.nav {
height: 80px;
width: 100vw;
background: skyblue;
display: flex;
align-items: center;
justify-content: space-between;
}
.headerimg img {
height: 50px;
width: 50px;
border-radius: 50%;
}
</style>
聊天界面:
<template>
<div class="chatuser">
<div class="header">
<span class="back"> <</span>
<div>{{ ischatuser.username }}</div>
</div>
<div class="chatlist" ref="chatlist">
<!-- 聊天记录 -->
<div class="chatItem" v-for="(item, index) in chatlist" :key="index" :class="{self:$root.me.username==item.from.username}">
<div class="header">
<img :src="item.from.headerimg" alt="" srcset="" />
</div>
<div class="before"></div>
<div class="chatContent">
<p>{{ item.content }}</p>
</div>
</div>
</div>
<div class="inputcom">
<el-input placeholder="请输入内容" v-model="inputValue" clearable @keydown.enter="sendMsg(inputValue)"> </el-input>
<el-button type="success" @click="sendMsg(inputValue)">发送</el-button>
</div>
</div>
</template>
<script>
import socket from '../../socket';
export default {
props: ["ischatuser"],
data() {
return {
inputValue: "",
chatlist: [],
};
},
methods: {
sendMsg: function (value) {
let msg = {
from: this.$root.me,
to: this.ischatuser,
content: value,
time: new Date().getTime(),
};
socket.emit("sendMsg",msg);
this.chatlist.push(msg);
//保存聊天记录到本地
this.saveStorange();
this.inputValue = "";
},
saveStorange(){
let key = 'chat-user-'+this.$root.me.username+'-'+ this.ischatuser.username;
localStorage[key] = JSON.stringify(this.chatlist);
},
getStorange(){
let key = 'chat-user-'+this.$root.me.username+'-'+ this.ischatuser.username;
localStorage[key] = localStorage[key] ? localStorage[key]: "[]";
this.chatlist = JSON.parse(localStorage[key])
},
tobuttom(){
let chatlist = this.$refs.chatlist
chatlist.scrollTop = chatlist.scrollHeight - chatlist.clientHeight;
}
},
//挂载前
beforeMount(){
this.getStorange();
socket.emit('readMsg',{
self:this.$root.me.username,
from:this.ischatuser.username
})
},
updated(){
this.tobuttom();
},
mounted(){
this.tobuttom();
socket.on('readMsg',(readMsg)=>{
console.log("------readMsg------");
console.log(readMsg);
this.chatlist.push(readMsg);
})
}
};
</script>
<style scoped>
.chatItem{
display: flex;
margin: 5px 10px;
}
.chatItem.self{
flex-direction: row-reverse;
justify-content: flex-start;
}
.chatItem .header img{
width: 50px;
height: 50px;
border-radius: 50%
}
.chatItem .chatContent{
background: #bbb;
border-radius: 5px;
padding: 8px 10px;
color: #fff;
margin: 0 0px 0px 20px;
line-height: 34px;
position: relative;
}
.chatItem.self .chatContent{
margin: 0 20px 0px 0px;
}
.chatItem .chatContent::before{
display: block;
content: "";
position: absolute;
width: 0;
height: 0;
border-right: 10px solid #bbb;
border-top: 10px solid transparent;
border-bottom: 5px solid transparent;
top: 20px;
left: -10px;
}
.chatItem.self .chatContent::before{
display: block;
content: "";
position: absolute;
width: 0;
height: 0;
border-left: 10px solid #bbb;
border-top: 10px solid transparent;
border-bottom: 5px solid transparent;
top: 20px;
right: -10px;
left: initial;
border-right:initial;
}
.chatuser{
width: 100vw;
height: 100vh;
display: flex;
flex-direction: column;
position: fixed;
top: 0;
left: 0;
background: #efefef;
}
.chatuser>.header{
position: relative;
}
.chatuser .back{
display: block;
width: 40px;
height: 40px;
line-height: 40px;
text-align:center;
position: absolute;
left: 0;
top: 0;
}
.chatuser>.header{
font-size: 18px;
font-weight: 900;
background: skyblue;
height: 40px;
text-align: center;
line-height: 40px;
}
.chatlist{
flex:1;
overflow: scroll;
}
.inputcom{
height: 50px;
display: flex;
background: #eee;
justify-content: space-around;
}
.inputcom input{
width: 270px;
height: 40px;
border-radius: 5px;
outline: none;
border: 1px solid #ccc;
margin: 0 5px;
}
.inputcom button{
width: 80px;
height: 40px;
border-radius: 5px;
outline: none;
border: 1px solid #ccc;
margin: 0 5px;
}
</style>