效果图
h5端
手机端
uni-app 即时聊天:实现私聊和群聊
技术栈
前端
uni-app weapp.socket.io
后端
node koa2 socket.io
目录结构
前端
后端
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
}))
}
})
}