socket.io前后端联通,建立聊天室

node.js 提供了高效的服务端运行环境,但是由于浏览器端对HTML5的支持不一,为了兼容所有浏览器,提供卓越的实时的用户体验,并且为程序员提供客户端与服务端一致的编程体验,于是socket.io诞生。Socket.io将Websocket和轮询 (Polling)机制以及其它的实时通信方式封装成了通用的接口,并且在服务端实现了这些实时机制的相应代码。

Socket.IO 实现了实时双向的基于事件的通讯机制。旨在让各种浏览器与移动设备上实现实时app功能,模糊化各种传输机制。Socket.IO 是跨平台,多种连接方式自动切换,做即时通讯方面的开发很方便,而且能和 expressjs 提供的传统请求方式很好的结合。


代码运行环境

前端后端数据库
ReactExpressMongoDB

主要依赖版本

"express": "^4.17.1",
"socket.io": "^3.1.0",
"socket.io-client": "^3.1.0",

在这里插入图片描述

后端

(聊天室是由用户发起的,所以在建立聊天室模型的同时,也应该建立用户模型)

1.server.js

(项目入口文件,socket.io 配置)

const express = require('express')
const bodyParser = require('body-parser')
const cookieParser = require('cookie-parser')
const model = require('./model')
const Chat = model.getModel('chat')
const app = express()const server = require('http').Server(app)// { cors: true },使得 socket.io 支持跨域
const io = require('socket.io')(server, { cors: true })// 监听用户连接
io.on('connection', function(socket) {// 数据连接socket.on('sendmsg', function(data) {const { from, to, msg } = data// 每个聊天都有唯一的 idconst chatid = [from, to].sort().join('_')// 每一条聊天记录在数据库中创建一个记录Chat.create({chatid, from, to, content: msg}, function(err, doc) {// 广播(全局)事件io.emit('recvmsg', Object.assign({}, doc._doc))})})
})const userRouter = require('./user')app.use(cookieParser())
app.use(bodyParser.json())
app.use('/user', userRouter)
server.listen(9093, function() {console.log('server port is 9093')
})
2.model.js
const mongoose = require('mongoose')
const DB_URL = 'mongodb://localhost:27017/goodwork'
mongoose.connect(DB_URL)const models = {user:{'user':{type:String, 'require':true},'pwd':{type:String, 'require':true},'type':{'type':String, 'require':true},//头像'avatar':{'type':String},// 个人简介或者职位简介'desc':{'type':String},'title':{'type':String},'company':{'type':String},'money':{'type':String}},chat:{'chatid': {type: String, require: true},'from': {type: String, require: true},'to': {type: String, require: true},'read': {type: Boolean, default: false},'content': {type: String, require: true, deafult: ''},'create_time': {type: Number, deafult: new Date().getTime()}}
}for(let m in models){mongoose.model(m, new mongoose.Schema(models[m]))
}module.exports = {getModel:function(name){return mongoose.model(name)}
}
3.user.js

(路由文件,编写消息列表路由和已读消息列表路由)

const { json } = require('body-parser')
const express = require('express')
const utils = require('utility')
const Router = express.Router()
const model = require('./model')
const User = model.getModel('user')
const Chat = model.getModel('chat')const _filter = { 'pwd': 0, '__v': 0 }/*** 获取聊天信息*/
Router.get('/getmsglist', function(req, res) {// 使用 cookies 进行登录验证const user = req.cookies.useridUser.find({}, function(e, userdoc) {let users = {}userdoc.forEach(v => {users[v._id] = { name: v.user, avatar: v.avatar }})Chat.find({'$or': [{ from: user}, { to: user }]}, function(err, doc) {if (!err) {return res.json({code: 0,msgs: doc,users: users})}})})
})
/*** 已读消息*/
Router.post('/readmsg', function (req, res) {const userid = req.cookies.useridconst { from } = req.bodyconsole.log(userid, from)Chat.updateMany({ from, to: userid },{ '$set': { read: true } },{ 'multi': true },function (err, doc) {console.log(doc)if (!err) {return res.json({code: 0,num: doc.nModified})}return res.json({code: 1,msg: '出错了'})})
})module.exports = Router

前端

(前端编写较为复杂,涉及各个页面之间的跳转,redux 之间的数据管理,这里笔者无法将所有细节描述清楚,请各位见谅)

前端页面使用 redux 进行数据管理

1.redux / chat.redux.js
import axios from 'axios'
import io from 'socket.io-client'
const socket = io('ws://localhost:9093')// 聊天列表
const MSG_LIST = 'MSG_LIST'
// 读取信息
const MSG_RECV = 'MSG_RECV'
// 标识已读
const MSG_READ = 'MSG_READ'const initState = {chatmsg: [],users: {},unread: 0
}export function chat(state = initState, action) {switch (action.type) {// 聊天列表case MSG_LIST:return { ...state,users: action.payload.users,chatmsg: action.payload.msgs,unread: action.payload.msgs.filter(v => !v.read && v.to === action.payload.userid).length }// 读取信息case MSG_RECV:const n = action.payload.to === action.userid ? 1 : 0return {...state,chatmsg: [...state.chatmsg, action.payload],unread: state.unread + n}// 标识已读case MSG_READ:const { from } = action.payloadreturn {...state, chatmsg: state.chatmsg.map(v => ({...v, read: from === v.from ? true : v.read})),unread: state.unread.num ? state.unread.num : 0}default:return state}
}function msgRecv(msg, userid) {return { userid, type: MSG_RECV, payload: msg }
}/*** 监听广播*/
export function recvMsg() {return (dispatch, getState) => {// 监听广播socket.on('recvmsg', function(data) {const userid = getState().user._iddispatch(msgRecv(data, userid))})}
}function msgList(msgs, users, userid) {return { type: MSG_LIST, payload: {msgs, users, userid} }
}/*** 获取消息列表*/
export function getMsgList() {return (dispatch, getState) => {axios.get('/user/getmsglist').then(res => {console.log(res)if (res.status === 200 && res.data.code === 0) {const userid = getState().user._iddispatch(msgList(res.data.msgs, res.data.users, userid))}})}
}/*** 发送消息* @param {*} from 发送者, to 接收者* */
export function sendMsg({ from, to, msg }) {return dispatch => {socket.emit('sendmsg', { from, to, msg })}
}function msgRead({ from, userid, num }) {return { type: MSG_READ, payload: { from, userid, num } }
}export function readMsg(from) {return (dispatch, getState) => {axios.post('/user/readmsg', {from}).then(res => {const userid = getState().user._idif (res.status === 200 && res.data.code === 0) {const num = res.data.numdispatch(msgRead({ userid, from, num }))}})}
}

2.chat.js

import { Grid, Icon, InputItem, List, NavBar } from 'antd-mobile';
import React, { Component } from 'react';
import { connect } from 'react-redux'
import { getMsgList, sendMsg, recvMsg, readMsg } from '../../redux/chat.redux'
import { getChatId } from '../../util'@connect(state => state,{ getMsgList, sendMsg, recvMsg, readMsg }
)
class Chat extends Component {constructor(props) {super(props);this.state = {text: '',msg: []}}componentDidMount() {// 获取消息if (!this.props.chat.chatmsg.length) {this.props.getMsgList()this.props.recvMsg()}}// 将消息标为已读componentWillUnmount() {const to = this.props.match.params.userthis.props.readMsg(to)}fixCarousel() {setTimeout(function () {window.dispatchEvent(new Event('resize'))}, 0)}handleSubmit() {// 发送者const from = this.props.user._id// 接收者const to = this.props.match.params.user// 发送的消息const msg = this.state.textthis.props.sendMsg({ from, to, msg })this.setState({text: '',showEmoji: false})}render() {const emoji = '😀 😁 😂 😍 😘 😝 😬 🤮 🤟 🤏 👌 🤜 🙏'.split(' ').filter(v => v).map(v => ({ text: v }))const userid = this.props.match.params.userconst Item = List.Itemconst users = this.props.chat.usersif (!users[userid]) {return null}const chatid = getChatId(userid, this.props.user._id)const chatmsgs = this.props.chat.chatmsg.filter(v => v.chatid === chatid)return (<div id="chat-page"><div style={{ position: "fixed", zIndex: 999, width: '100%' }}><NavBarmode="dark"icon={<Icon type="left"></Icon>}onLeftClick={() => {this.props.history.goBack()}}>{users[userid].name}</NavBar></div>{/* 聊天列表 */}{chatmsgs.map(v => {const avatar = require(`../img/${users[v.from].avatar}.png`).defaultreturn v.from === userid ? (<List key={v._id}><Itemthumb={avatar}multipleLine={true}wrap={true}platform="android">{v.content}</Item></List>) : (<List key={v._id}><ItemmultipleLine={true}wrap={true}platform="android"extra={<img src={avatar} alt="" />}className="chat-me">{v.content}</Item></List>)})}{/* 聊天列表 */}<div className="stick-fooetr"><List><InputItemplaceholder='请输入'value={this.state.text}onChange={v => {this.setState({ text: v })}}extra={<div><spanstyle={{ marginRight: 15 }}onClick={() => {this.setState({showEmoji: !this.state.showEmoji})this.fixCarousel()}}>😀</span><span onClick={() => this.handleSubmit()}>发送</span></div>}></InputItem>{this.state.showEmoji ? <Griddata={emoji}columnNum={9}carouselMaxRow={2}isCarousel={true}onClick={el => {this.setState({text: this.state.text + el.text})console.log(el)}}/> : null}</List></div></div>)}
}export default Chat

在这里插入图片描述


具体代码可以访问笔者的 gitee
也可以关注笔者的公众号:大明贵妇,(回复:socket.io)获取学习资料


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部