Skip to content
On this page

效果图

h5端

https://i.loli.net/2020/08/04/MosCFOHilfzUnae.gif

手机端

https://i.loli.net/2020/08/04/1sRLIhF9Yqd726a.gif

uni-app 即时聊天:实现私聊和群聊

https://i.loli.net/2020/08/04/yU5Mq8mREI3cLsk.gif

技术栈

前端

uni-app weapp.socket.io

后端

node koa2 socket.io

目录结构

前端

https://i.loli.net/2020/09/08/wcoCeAM7JFTRY8d.png

后端

https://i.loli.net/2020/09/08/FHoCnk4eZhlMrxT.png

socket.io 常用函数

socket.emit():向建立该连接的客户端发送消息

socket.on():监听客户端发送信息

io.to(socketid).emit():向指定客户端发送消息

socket.broadcast.emit():向除去建立该连接的客户端的所有客户端广播

io.sockets.emit():向所有客户端广播

实现

前端:weapp.socket.io 的使用

在这个项目里,weapp.socket.io的事件是定义在vuex 的actions 里的,这样是为了方便组件之间的通信,所以,如果具体定义在哪个文件里就要根据你自己的项目而定了。具体的代码如下

jsx
// actions.js

//引入weapp.socket.js
import io from "../js_sdk/socket-io/weapp.socket.io.js"
import request from "../api/request.js"
export default {
	// 连接socket.io 触发该函数的时机要根据项目的实际情况来看
	conenct(context, data) {
		let {
			state
		} = context

		// 连接后端的socket.io
		state.socket = io.connect("<http://localhost:3000>")
	}
}

后端:socket.io的使用

jsx
//chat.js

const Koa = require("koa")
const app = new Koa();
// socket.io处理逻辑
const { detail } = require("./controllers/socket")

// socket.io
const server = require('http').Server(app.callback());  //koa正确姿势
const io = require('socket.io')(server); //核心,实现客户端之间交互通信

// 与前端的weapp.socket.io建立连接
io.on("connection", socket => {
    detail(io, socket)
})

mongoose.connect("mongodb://localhost:27017/chat", {
    useNewUrlParser: true,
    useUnifiedTopology: true
}).then(() => {
    console.log("chat数据库已经连接")
    server.listen(3000, () => {
        console.log(`监听地址: http://localhost:3000`);
    })
    // app.listen("3000", function () {
    //     console.log("3000端口已经启动")
    // })
}).catch(() => {
    console.log("数据库连接失败")
})
jsx
// socket.js

exports.detail = (io, socket) => {
    // 将io和socket保存到全局变量中,方便在其他的文件中调用socket.io对象
    global.io = io
    global.socket = socket

    // 处理逻辑
}

实现私聊和群聊

原理

socket.io 在与客户端建立连接的时候 socket对象会生成一个 id (socket.id),我们可以存储好这个id,然后通过 io.to(socket.id).emit()方法向指定客户端发送消息,这样我们就可以实现私聊,群聊的原理跟私聊的原理是差不多的,只不过不同的就是群聊需要遍历群成员来执行io.to(socket.id).emit()方法

前端

jsx
// actions.js

import request from "../api/request.js"
export default {
	// 连接socket.io 触发该函数的时机要根据项目的实际情况来看
	conenct(context, data) {
		let {
			state
		} = context

		// 连接后端的socket.io
		state.socket = io.connect("<http://localhost:3000>")

		// 登陆  data的数据是用户的数据例如:账号信息等
		socket.emit("submit", data)

               // 发送信息 在指定页面发送信息时调用
	       sendMsg(context, data) {
		   let {
			state
		    } = context
                    // data :要发给谁(私聊:用户的ID。群聊:群ID),要发送的信息,聊天的类型(私聊,群聊),信息的类型,......
		   state.socket.emit("sendMsg", data)
               }

		// 更新聊天记录
		socket.on("updateChat", data => {
			// 当发送信息过来的用户id和当前token用户所处的chatId相同时才更新聊天记录
			// 如果不加入这个判断,token用户所处的聊天页面将会发生用户切换的后果
			//(跳转到与发送信息过来的用户的聊天界面)
			if(data.chatType == 'private'){
				if (data.belong == state.chatId) {
					this.commit("updateChatMsg",data)
				}
			}else{
				if (data.id == state.chatId) {
					state.friend_list.map((item)=>{
						if(item.user._id.toString() == data.belong.toString()){
							data.user.name = item.nickName
						}
						return item
					})
					this.commit("updateChatMsg",data)
				}
			}

			this.dispatch("getDialogue")
		})
	}
}

后端

jsx
// socket.js

const { verifyToken } = require("../tool/token")  // 解析token
const userSocket = require("../model/userSocketModel")  // 存储用户socket.id 的表
const User = require("../model/userModel")  // 用户表
const Group = require("../model/groupModel")  // 群表
const { saveChat } = require("../controllers/c_chat")  // 存储聊天记录的表

exports.detail = (io, socket) => {
    // 将io和socket保存到全局变量中
    global.io = io
    global.socket = socket

    // 登陆连接
    socket.on("submit", async (data) => {
        let { id } = verifyToken(data)  // 解析 token
        let result = await userSocket.findOne({ userId: id })  // 在userSocket查找该用户
        if (result) { // 该用户存在 则更新socket.id (socket.id在每次连接时都是会改变的)         // 更新socket.id
            let res = await userSocket.updateOne({ userId: id }, { socketId: socket.id })
        } else {
	    // 不存在就存储
            let res = await userSocket.create({
                userId: id,
                socketId: socket.id
            })
        }
    })
    //发送信息
    socket.on("sendMsg", async data => {
        let { id, token, type, chatType, date, message } = data
        let tokenRes = verifyToken(token) // 解析token
        let tokenUser = await User.findById(tokenRes.id)
        if (type == "text" || type == "location") {
            // 存储聊天记录
            let res = await saveChat(data)
        }
        if (chatType == "private") { //私聊通知
            let socketUser = await userSocket.findOne({ userId: id })
            io.to(socketUser.socketId).emit("updateChat", { belong: tokenRes.id, chatType, message, type, date })
        } else { //群聊通知
            let group = await Group.findById(id)  //查找群组
            let user_list = group.user_list  //群组成员
            // 遍历群组成员,通知用户更新信息
            let result = await Promise.all(user_list.map(async item => {
                if (item.user != tokenRes.id) {
                    let socketUser = await userSocket.findOne({ userId: item.user })
                    global.io.to(socketUser.socketId).emit("updateChat", {
                        id,
                        chatType: "group",
                        belong: tokenRes.id,
                        user: {
                            name: tokenUser.name,
                            avatars: tokenUser.avatars
                        },
                        type,
                        message,
                        date
                    })
                }
                return item
            }))
        }
    })
}