express+vue实现一个在线im

2024-06-10 22:20

本文主要是介绍express+vue实现一个在线im,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

在线体验地址

需要用邮箱注册一个账号
在线链接

目前实现的功能

1、在线聊天(群聊)
2、实时监控成员状态
3、历史聊天,下拉加载
4、有新消息,自动滚动到最新消息,如果自己在查看历史记录,不会强行滚动

exprees部分

创建几个表来记录相关数据

  • 下面这些都不是必须的,如果你只是生成一个临时聊天室,这些表也可以直接用个对象直接保存
    AuthorInfo 成员详细信息
    ImRoom 聊天房间
    ImRoomSys 聊天信息
    ImRoomMember 聊天成员

简单启动一个服务器,并创建基本的事件监听

socket.js

const express = require("express");
const app = express(); //创建网站服务器
const server = require("http").createServer(app);
const io = require('socket.io')(server,{ cors: true });
module.exports = {app,io,express,server,
};

具体的事件

const { io } = require("../../tool/socket.js");
const { AuthorInfo } = require("../../mod/author/author_info");
const { ImRoom } = require("../../mod/game/im_room.js");
const { ImRoomSys } = require("../../mod/game/im_room_sys.js");
const { ImRoomMember } = require("../../mod/game/im_room_member.js");
const { Game } = require("../../mod/game/game.js");
const { GameList } = require("../../mod/game/game_list.js");let allSocket = {};// 监听客户端的连接
io.on("connection", function (socket) {allSocket[socket.id] = socket;// 监听用户掉线socket.on("disconnect", async () => {// 更新用户状态let user = await ImRoomMember.findOneAndUpdate({ socket_id: socket.id },{ status: "2" });if (user) {delete allSocket[user.im_room_id];// 向房间的用户同步信息sendMsgToRoom(user.im_room_id);}});// 监听加入房间socket.on("join_room", async (data) => {if (!global.isObject(data)) {resMsg("加入房间参数错误", 400);return;}let { user_id, room_id } = data;if (!user_id) {resMsg("用户id不能为空", 400);return;}let user = await AuthorInfo.findOne({ _id: user_id });if (!user) {resMsg("用户不存在", 400);return;}if (!room_id) {resMsg("房间id不能为空", 400);return;}let room = await ImRoom.findOne({ _id: room_id, status: "1" });if (!room) {resMsg("房间不存在", 400);return;}let { max, status } = room;if (+status !== 1) {resMsg("房间未开放", 300);return;}// 查找所有加入该房间,并且状态为在线的用户let members = await ImRoomMember.find({im_room_id: room_id,status: 1,}).countDocuments();if (members >= max) {resMsg("房间已满", 300);return;}// 查找用户是否yin'jinlet oldUser = await ImRoomMember.findOne({im_room_id: room_id,author_id: user_id,});if (!oldUser) {let res = await new ImRoomMember({im_room_id: room_id,author_id: user_id,author_type: 2,created_time: getCurrentTimer(),updated_time: getCurrentTimer(),status: 1,socket_id: socket.id,}).save();if (!res) {resMsg("加入房间失败", 400);return;}} else {await ImRoomMember.updateOne({ im_room_id: room_id, author_id: user_id },{ socket_id: socket.id, status: 1 });}// 房间信息改变,向房间内所有在线用户推送房间信息sendMsgToRoom(room_id);});// 主动推出登录socket.on("live_room", async (data) => {let { room_id, user_id } = data;// 更新用户状态let user = await ImRoomMember.findOneAndUpdate({ im_room_id: room_id, author_id: user_id },{ status: "2" });if (user) {delete allSocket[user.socket_id];// 向房间的用户同步信息sendMsgToRoom(room_id);}});// 发送消息socket.on("send_msg", async (data) => {if (!global.isObject(data)) return;let { room_id, author_id, content } = data;// 判断用户是否存在if (!author_id) {resMsg("用户id不能为空", 400);return;}let user = await AuthorInfo.findOne({ _id: author_id });if (!user) {resMsg("用户id不能为空", 400);return;}// 判断房间是否存在if (!room_id) {resMsg("房间id不能为空", 400);return;}let room = await ImRoom({ _id: room_id, status: "1" });if (!room) {resMsg("房间未开放", 400);return;}if (!content) {resMsg("消息内容不能为空", 400);return;}// 保存消息let params = {im_room_id: room_id,author_id: author_id,content: content,created_time: getCurrentTimer(),updated_time: getCurrentTimer(),};let room_sys = await new ImRoomSys(params).save();if (!room_sys) {resMsg("保存消息失败", 400);return;}// 找出对应的成员信息let userinfo = await AuthorInfo.findOne({ _id: author_id },{username: 1,header_img: 1,});if (!userinfo) {resMsg("用户信息不存在", 400);return;}room_sys.author_id = userinfo;sendMsgToRoom(room_id, room_sys);});// 向一个房间内的所有在线用户推送房间的基本信息async function sendMsgToRoom(room_id, row = null) {if (!room_id) return;let members = await ImRoomMember.find({im_room_id: room_id,status: 1,},{ socket_id: 1 });if (!members || members.length === 0) return;let sockets = members.map((item) => item.socket_id);// 查出房间的基本信息// 额外查在线人数let room = (await ImRoom.findOne({ _id: room_id, status: "1" })) || {};let roomMembers = await ImRoomMember.find({ im_room_id: room_id },{ author_id: 1, status: 1 }).populate("author_id", "username").exec();// 查找出当前房间的总消息数let roomSysCount = await ImRoomSys.find({im_room_id: room_id}).countDocuments() || 0sockets.forEach((item) => {let socket = allSocket[item];let res = {data: room,roomMembers,roomSysCount,msg: "房间信息已更新",};if (global.isObject(row)) {res.content = row;}if (socket) {resMsg(res, 200, "room_baseinfo", socket);}});}// 获取当前时间戳function getCurrentTimer() {return Date.now();}// 统一返回消息function resMsg(msg, code = 400, name = "err", _socket) {let obj = {code,};if (code === 200) {obj.msg = "操作成功";obj.data = msg;} else {obj.msg = msg;}socket = _socket ? _socket : socket;socket.emit(name, obj);}
});

前端部分

需要用到下面这个插件

https://cdn.socket.io/3.1.2/socket.io.js

具体代码
index.vue

<template><div class="flex-wrap"><!-- 用户个人信息 --><leftUserInfo v-if="isShowLeft" /><!-- 聊天列表 --><centerList v-if="isShowCenter" :chatRoomList="chatInfo.chatList" /><!-- 具体聊天信息 --><rightChatv-loading="isLoading"ref="rightChatRef"v-if="isShowRight":isLoading="isLoading":socket="socket":chatInfo="chatInfo"@loadPrev="loadPrev"@postComment="postComment"/></div>
</template><script>
import { baseURL } from '@/plugins/config.js'
import { get_room, get_chat_list } from '@/api/data.js'
const plugins = [{js: 'https://cdn.socket.io/3.1.2/socket.io.js',},
]
import leftUserInfo from '@/views/blog/im/leftUserInfo.vue'
import centerList from '@/views/blog/im/centerList.vue'
import rightChat from '@/views/blog/im/rightChat.vue'
export default {components: {leftUserInfo,centerList,rightChat,},props: {isShowLeft: {type: Boolean,default: true,},isShowCenter: {type: Boolean,default: false,},isShowRight: {type: Boolean,default: true,},},data() {return {isUserActive:false,isLoadMore:false,isLoading: true,socket: {},chatInfo: {roomInfo: {roomMembers: [],im_name: '',},chatList: [],},pages: {page: 1,limit: 10,total: 0,},}},computed: {...Vuex.mapState(['userdata', 'userTags']),...Vuex.mapGetters(['isAdmin']),islogin() {return this.isLogin()},room_id() {return this.chatInfo?.roomInfo?._id || ''},baseParams() {return {user_id: this.userdata._id,room_id: this.room_id,}},},created() {this.getIndexDBJS(plugins).finally((res) => {this.init()})},beforeDestroy() {this.delPageScript(plugins)this.socket.emit('live_room', this.baseParams)},methods: {// 主动发送消息postComment(val = false){this.isUserActive = val},// 拉取上一页loadPrev() {let {room_id} = thisif(this.isLoadMore) returnlet { page, limit, total } = this.pagesif (this.chatInfo.chatList.length >= total) return// 找出第一条的idlet chat_id = this.chatInfo.chatList[0]?._id || ''if (!chat_id) returnthis.isLoadMore = trueget_chat_list({ room_id,chat_id,limit }).then(res=>{if (res.data && this.isArrayLength(res.data.data)) {let { data } = res.datathis.chatInfo.chatList = [...data.reverse(),...this.chatInfo.chatList]this.$refs.rightChatRef.reloadScrollPostion()}}).catch(()=>{}).finally(()=>{this.isLoadMore = false})},scrollToBottom() {let { rightChatRef } = this.$refsif (rightChatRef) {rightChatRef.scrollToBottom(this.isUserActive)}},async init() {// 获取房间信息let res = await get_room({ type: 1 }).catch(() => {})if (!res.data && !this.isArrayLength(res.data.data)) returnObject.assign(this.chatInfo.roomInfo, { _id: res.data.data[0]._id })let { room_id } = thisif (!room_id) return// 拉取最近的10条聊天记录let syss = await get_chat_list({ room_id })if (syss.data && this.isArrayLength(syss.data.data)) {// 倒序this.chatInfo.chatList = syss.data.data.reverse()}// 连接iothis.socket = io.connect(baseURL)// 加入房间this.socket.emit('join_room', { room_id, user_id: this.userdata._id })// 监听错误事件this.socket.on('err', (err) => {console.log('err', err)})// 监听房间基本信息this.socket.on('room_baseinfo', (res) => {console.log('room_baseinfo', res.data)if (res.data) {let { data, content, roomMembers,roomSysCount } = res.dataObject.assign(this.chatInfo.roomInfo, data, {roomMembers,})this.pages.total = roomSysCountif (content) {this.chatInfo.chatList.push(content)}// 将房间消息滚动到底部this.scrollToBottom()this.isLoading = false}})},},
}
</script><style lang="scss" scoped></style>

rightChat.vue

<template><div class="flex-1 flex-column-wrap"><!-- 聊天室信息 --><div class="flex-justify-between flex-wrap flex-center-wrap h-80 p-l-20 p-r-20 b-b-1"><div>{{ chatInfo.roomInfo.im_name || '默认聊天室' }}<span v-if="onLine"> ({{ onLine }}) </span></div><i class="el-icon-s-tools f-24"></i></div><!-- 聊天信息列表 --><div:class="['room-container p-l-20 p-r-20 b-b-1 p-t-10 p-b-10',pageClass,isLoading ? 'op-0' : '',]"@scroll="scrollEvent"><template v-if="chatInfo.chatList.length"><chatItem:class="[index !== 0 ? 'm-t-20' : '']"v-for="(item, index) in chatInfo.chatList":key="item._id":row="item":roomMembers="chatInfo.roomInfo.roomMembers"></chatItem></template><div v-else>暂无</div><div class="tip-new" v-if="false">有新消息</div></div><!-- 底部发送消息区域 --><div class="im-send-continer h-100 p-l-20 flex-center-wrap p-r-20"><kl-emoji ref="pushCommentRef" type="2" @postComment="postComment" /></div></div>
</template><script>
import chatItem from '@/views/blog/im/chatItem.vue'
export default {components: {chatItem,},props: {isLoading: {type: Boolean,default: true,},socket: {type: Object,default: () => {return {}},},chatInfo: {type: Object,default: () => {return {roomInfo: {roomMembers: [],},chatList: [],}},},},data() {return {pageClass: this.createId(),isBottom: true,}},computed: {...Vuex.mapState(['userdata']),onLine() {let count = 0this.chatInfo.roomInfo.roomMembers.forEach((item) => {if (item.status == 1) {count++}})return count},},methods: {// 重新定位async reloadScrollPostion() {let el = document.querySelector(`.${this.pageClass}`)if (el) {let oldHeight = el.scrollHeightawait this.$nextTick()let newHeight = document.querySelector(`.${this.pageClass}`).scrollHeight// 计算出滚动的高度let scrollHeight = newHeight - oldHeight// 滚动到原来的位置el.scrollTop = scrollHeight}},// 监听滚动,判断用户是否在底部scrollEvent(e) {let el = $(`.${this.pageClass}`)if (el) {this.isBottom = el.scrollTop() + el.innerHeight() >= el[0].scrollHeight - 50// 判断是否触顶,加载上一页if (el.scrollTop() <= 50) {this.$emit('loadPrev')}}},// 滚动规则: 1、第一次进入 2、新消息来之前,用户就是停在底部 3、用户主动发送了消息async scrollToBottom(val) {if (!this.isBottom && !val) returnawait this.$nextTick()let el = $(`.${this.pageClass}`)if (el) {el.scrollTop(el[0].scrollHeight)}this.$emit('postComment',false)},postComment(content) {this.$emit('postComment',true)this.socket.emit('send_msg', {room_id: this.chatInfo.roomInfo._id,author_id: this.userdata._id,content,})},},
}
</script><style lang="scss" scoped>
.room-container {height: calc(100vh - 80px - 100px);overflow-y: auto;
}
.b-b-1 {border-bottom: 1px solid #aaa;
}
</style>

如果有道友有需要的也可以私聊我,我看到会回的

这篇关于express+vue实现一个在线im的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1049433

相关文章

C++对象布局及多态实现探索之内存布局(整理的很多链接)

本文通过观察对象的内存布局,跟踪函数调用的汇编代码。分析了C++对象内存的布局情况,虚函数的执行方式,以及虚继承,等等 文章链接:http://dev.yesky.com/254/2191254.shtml      论C/C++函数间动态内存的传递 (2005-07-30)   当你涉及到C/C++的核心编程的时候,你会无止境地与内存管理打交道。 文章链接:http://dev.yesky

轻量级在线服装3D定制引擎Myway简介

我写的面向web元宇宙轻量级系列引擎中的另外一个,在线3D定制引擎Myway 3D。 用于在线商品定制,比如个性化服装的定制、日常用品(如杯子)、家装(被套)等物品的在线定制。 特性列表: 可更换衣服款式,按需定制更换模型可实时更改材质颜色可实时添加文本,并可实时修改大小、颜色和角度,支持自定义字体可实时添加艺术图标,并可实时修改大小、颜色和角度,支持翻转、各种对齐可更改衣服图案,按需求定制

vue, 左右布局宽,可拖动改变

1:建立一个draggableMixin.js  混入的方式使用 2:代码如下draggableMixin.js  export default {data() {return {leftWidth: 330,isDragging: false,startX: 0,startWidth: 0,};},methods: {startDragging(e) {this.isDragging = tr

在线装修管理系统的设计

管理员账户功能包括:系统首页,个人中心,管理员管理,装修队管理,用户管理,装修管理,基础数据管理,论坛管理 前台账户功能包括:系统首页,个人中心,公告信息,论坛,装修,装修队 开发系统:Windows 架构模式:B/S JDK版本:Java JDK1.8 开发工具:IDEA(推荐) 数据库版本: mysql5.7 数据库可视化工具: navicat 服务器:SpringBoot自带 ap

通过SSH隧道实现通过远程服务器上外网

搭建隧道 autossh -M 0 -f -D 1080 -C -N user1@remotehost##验证隧道是否生效,查看1080端口是否启动netstat -tuln | grep 1080## 测试ssh 隧道是否生效curl -x socks5h://127.0.0.1:1080 -I http://www.github.com 将autossh 设置为服务,隧道开机启动

时序预测 | MATLAB实现LSTM时间序列未来多步预测-递归预测

时序预测 | MATLAB实现LSTM时间序列未来多步预测-递归预测 目录 时序预测 | MATLAB实现LSTM时间序列未来多步预测-递归预测基本介绍程序设计参考资料 基本介绍 MATLAB实现LSTM时间序列未来多步预测-递归预测。LSTM是一种含有LSTM区块(blocks)或其他的一种类神经网络,文献或其他资料中LSTM区块可能被描述成智能网络单元,因为

vue项目集成CanvasEditor实现Word在线编辑器

CanvasEditor实现Word在线编辑器 官网文档:https://hufe.club/canvas-editor-docs/guide/schema.html 源码地址:https://github.com/Hufe921/canvas-editor 前提声明: 由于CanvasEditor目前不支持vue、react 等框架开箱即用版,所以需要我们去Git下载源码,拿到其中两个主

React+TS前台项目实战(十七)-- 全局常用组件Dropdown封装

文章目录 前言Dropdown组件1. 功能分析2. 代码+详细注释3. 使用方式4. 效果展示 总结 前言 今天这篇主要讲全局Dropdown组件封装,可根据UI设计师要求自定义修改。 Dropdown组件 1. 功能分析 (1)通过position属性,可以控制下拉选项的位置 (2)通过传入width属性, 可以自定义下拉选项的宽度 (3)通过传入classN

DDei在线设计器-API-DDeiSheet

DDeiSheet   DDeiSheet是代表一个页签,一个页签含有一个DDeiStage用于显示图形。   DDeiSheet实例包含了一个页签的所有数据,在获取后可以通过它访问其他内容。DDeiFile中的sheets属性记录了当前文件的页签列表。   一个DDeiFile实例至少包含一个DDeiSheet实例。   本篇最后提供的示例可以在DDei文档直接预览 属性 属性名说明数

android一键分享功能部分实现

为什么叫做部分实现呢,其实是我只实现一部分的分享。如新浪微博,那还有没去实现的是微信分享。还有一部分奇怪的问题:我QQ分享跟QQ空间的分享功能,我都没配置key那些都是原本集成就有的key也可以实现分享,谁清楚的麻烦详解下。 实现分享功能我们可以去www.mob.com这个网站集成。免费的,而且还有短信验证功能。等这分享研究完后就研究下短信验证功能。 开始实现步骤(新浪分享,以下是本人自己实现