用socket做一个聊天室

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"> &lt;</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>

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值