Webpack 4.X + React + Node + Mongodb Build a chat room from scratch (2)

In the last article, we basically built the frame. In this article, we implement the functional logic in detail.

Completed features:

  • User registration, login
  • When the user enters/leaves the chat room, all users in the current chat room are notified.
  • When a single user adds a group chat, all users can see it.
  • Users can chat with anyone in real time
  • Users can keep chat lists and chat records offline
  • Click on the user's avatar to add a private chat
  • Users can chat privately with each other in real time
  • Chat rooms record unread messages from users
  • User is entering function

Resource link : https://github.com/zhangyongwnag/chat_room



1. Establish socket connection

In the last article, we basically set up the background service, and everyone who has used socket.iosocketthe server to start the service basically understands that it must be used in conjunction with the client.Insert image description here
socket.iosocket.io-client

1. Download

npm i socket.io-client -S

2. The client creates a connection
import React, {
    
    Component} from 'react'

let socket = require('socket.io-client')('http://127.0.0.1:3001')

We can see that the connection has been created successfully
Insert image description here

3. Test interaction

Next, our client interacts with the server

// 客户端
socket.emit('init','客户端发送了消息')

// 服务端
io.on('connection', socket => {
    
    
  socket.on('init',data => {
    
    
    console.log(data)
  })
})

Test effect: We can see that the control prints out the message sent by the client
Insert image description here

2. Add status management on the client side

Client: We use reduxand react-reduxto implement state management

1. Download

npm i redux react-redux -S // reduxState management
npm i redux-logger redux-thunk -S // reduxMiddleware

2. Use

index.jsRoot directory

import React from 'react'
import ReactDOM from 'react-dom'
import App from './pages/App'
import './assets/css/index.css'
import {
    
    Provider} from 'react-redux' // Provider组件用来注入store对象
import {
    
    createStore,applyMiddleware,compose} from 'redux' // 挂在中间件
import reducer from './store' // 引入reducer
import thunk from "redux-thunk"; // 改造store.dispatch
import logger from 'redux-logger' // 控制台打印reducer日志

// 创建store对象
const store = createStore(
  reducer,
  compose(
    applyMiddleware(thunk),
    applyMiddleware(logger)
  )
)

ReactDOM.render(
  <Provider store={
    
    store}>
    <App/>
  </Provider>,
  document.getElementById('app'))

if (module.hot){
    
    
  module.hot.accept(() => {
    
    

  })
}

Create storedirectories and connect multiplereducer

store/index.jsConnect multiplereducer

import {
    
    combineReducers} from 'redux' // 引入中间件

import room from './reducer/room' // 引入聊天室列表 reducer
import records from "./reducer/records"; // 引入聊天记录 reducer

export default combineReducers({
    
    
  room, // 聊天室列表
  records, // 聊天记录
})

store/module/room.jsChat room listreducer

/**
 * @description 聊天术列表
 * @param {
 *   {Object} state {
 *     {Object} room:当前所在的聊天室信息
 *     {Array} room_list:聊天室列表
 *   }
 * }
 */
export default function (state = {
    
    }, action) {
    
    
  switch (action.type) {
    
    
    case 'get':
      return [...state]
    case 'add':
      return [...state, action.data]
    case 'set':
      let result = Object.assign(state, action.data)
      return {
    
    
        room: result.room, // 当前所处的聊天室
        room_list: [...result.room_list] // 聊天室列表
      }
    default:
   		return state
  }
}

recordsSame thing

3. Inject into store
import React, {
    
    Component} from 'react'
import {
    
    connect} from 'react-redux' // connect中间件,用来绑定store对象于props

class App extends Component {
    
    
  constructor(props) {
    
    
    super(props)
  }
  render(){
    
    
    return (<div>init</div>) 
  }
}

function mapStateToProps(state) {
    
    
  // 注册state
  return {
    
    
    room: state.room.room,
    records: state.records
  }
}

export default connect(mapStateToProps)(App) // 注入props
4. Test effect
...
  componentDidMount() {
  	 this.props.dispatch('set', {
  	  data:{
 	      room: {
            room_id: '1',
            room_item: {}
          },
          room_list:[1,2,3,4]
      }
	})
  }
...

We can see that the console prints out the log of our operations
Insert image description here

3. Implement functions

①: User registration and login
Sorting process
1. The user enters the client. If the local user information exists, log in, otherwise register.
2. The client initiates a registration application and carries the user registration name and sends it to the server.
3. The server determines whether the user has been registered. If not, insert the user name (unique constraint) into the user table. Otherwise, if the registration fails, a failure message is returned and the user is prompted to re-register.
4. After each user successfully registers, insert a chat room with the default owner into the chat room list.
5. Return the chat room list of the currently registered user

Client:

If the local user information does not exist, go to register, otherwise log in directly.

...
 let socket = require('socket.io-client')('http://127.0.0.1:3001') // 创建socket连接

 class App extends Component {
    
    
  constructor(props) {
    
    
    super(props)
    this.state = {
    
    
      userInfo: {
    
    }, // 用户信息
    }
  }

  componentDidMount() {
    
    
    // 如果本地信息不存在,则去注册,反之获取聊天列表
    if (localStorage.getItem('userInfo')) {
    
    
      let userInfo = JSON.parse(localStorage.getItem('userInfo'))
      socket.emit('login', userInfo._id)
    } else {
    
    
      this.register()
    }
  }
  
  // 注册用户
  register = () => {
    
    
    let name = prompt('请输入用户名')
    // 如果输入去掉空格
    name != null ? name = name.replace(/\s+/g, "") : ''
    if (name == null || !name) {
    
    
      this.register()
    } else if (name.length > 6) {
    
    
      alert('用户名不得超过6位')
      this.register()
    } else if (name) {
    
    
      // 去注册
      socket.emit('chat_reg', name)
    }
  }
...

Server side: Process user login | User registration | Obtain a single user's chat room list

...
let User = require('./module/User') // 用户model
let Room = require('./module/Room') // 聊天室model
let Records = require('./module/Records') // 聊天记录model

io.on('connection', socket => {
    
    
  /**
   * @description 用户静默登录
   * @param {String | ObjectId} userId:登录的用户id
   */
  socket.on('login', userId => {
    
    
    // 更新用户列表socketId
    User.updateOne({
    
    _id: ObjectId(userId)}, {
    
    $set: {
    
    socket_id: socket.id}}, function (err, result) {
    
    
      socket.emit('login', socket.id)
    })
  })

  /**
   * @description 用户注册
   * @param {String} username:要注册的用户名称
   */
  socket.on('chat_reg', username => {
    
    
    let user = new User({
    
    
      user_name: username,
      current_room_id: '',
      socket_id: socket.id
    })
    // 注册用户插入数据库
    user.save()
      .then(res => {
    
    
        // 注册事件
        socket.emit('chat_reg', createResponse(true, res))
        let room = new Room({
    
    
          user_id: res._id.toString(),
          user_name: username,
          room_name: '所有人',
          status: 0,
          num: 0,
          badge_number: 0,
          current_status: false
        })
        // 默认所有人聊天室插入数据库
        room.save()
          .then(response => {
    
    
            // 首次发送用户聊天室列表
            socket.emit('get_room_list', createResponse(true, {
    
    once: true, data: [response]}))
          })
      })
      .catch(err => {
    
    
        // 注册失败
        socket.emit('chat_reg', createResponse(false, '注册失败,用户已注册'))
      })
  })

  /**
   * @description 请求聊天列表
   * @param {String | ObjectId} userId:用户ID
   */
  socket.on('get_room_list', userId => {
    
    
    Room.find({
    
    user_id: userId})
      .then(data => socket.emit('get_room_list', createResponse(true, {
    
    once: true, data})))
  })
...

You may notice here that there is an createResponseevent used to uniformly process the data return format, 200whether it is success or 100failure .

/**
 * @description 创建响应体
 * @param {Boolean} status : 是否成功
 * @param {String | Array | Object | Boolean | Number | Symbol} data : 返回的数据
 */
function createResponse(status, data) {
    
    
  return {
    
    
    code: status ? 200 : 100,
    data,
    msg: status ? 'success' : 'error'
  }
}

Client:

emitNext, we respond to the event message from the server on the client side

We write a method to uniformly manage socketevent callbacks

...
	componentDidMount () {
    
    
		...
	    // 开启监听socket事件回调
	    this.socketEvent()
	}
	
	socketEvent = () => {
    
    
	  // 获取注册结果,如果成功保存用户信息,获取聊天室列表,反之继续去注册
	  socket.on('chat_reg', apply => {
    
    
	     if (apply.code == 200) {
    
    
	       localStorage.setItem('userInfo', JSON.stringify(apply.data))
	       this.setState({
    
    
	         userInfo: apply.data
	       }, () => {
    
    
	         socket.emit('get_room_list', this.state.userInfo._id)
	       })
	     } else {
    
    
	       alert(apply.data)
	       this.register()
	     }
	   })
	  // 获取聊天列表
      socket.on('get_room_list', apply => {
    
    ...})
	}
...
②: When a user enters/leaves the chat room, all users in the current chat room are notified.
Sorting process
1. After the user successfully registers, the chat room list is obtained, and the first chat room is selected by default.
2. After the user joins the chat room, notify all users in the chat room: xxx joins the chat. Otherwise, notify all users in the chat room: xxx leaves the chat room

Server: Successful registration or login returns the chat room list, carrying a special identifier onceto identify the user's initial connection and joining the chat room by default.

 核心代码:

  /**
   * @description 请求聊天列表
   * @param {String | ObjectId} userId:用户ID
   */
  socket.on('get_room_list', userId => {
    
    
    Room.find({
    
    user_id: userId})
      .then(data => socket.emit('get_room_list', createResponse(true, {
    
    once: true, data})))
  })

Client: After successfully registering or logging in again, obtain the chat room list and oncechoose to join the default first chat room based on the identification.

  核心代码:
  
  // 获取聊天列表
  socket.on('get_room_list', apply => {
    
    
     let room_list = apply.data.data.filter(item => item.user_id == this.state.userInfo._id)
     let room_id = room_list[0]._id.toString()
     let room_item = room_list[0]
     // 保存用户聊天室信息、列表
     this.props.dispatch({
    
    
       type: 'set',
       data: {
    
    
         room: {
    
    
           room_id,
           room_item,
         },
         room_list
       }
     })
     // 如果存在首次获取标识once,用户加入聊天室
     if (apply.data.once) {
    
    
       // 加入某个聊天室
       socket.emit('join', {
    
    
         roomName: this.props.room.room_item.room_name,
         roomId: this.props.room.room_id,
         userId: this.state.userInfo._id,
         userName: this.state.userInfo.user_name
       })
     }
   })

Next, the server handles the logic of the user joining the chat room

Sort out the logic

1. User joinchat room, create oneroom

2. Verify the user's current chat room to determine whether to join again

3. If there is no duplicate addition

  • Update the chat room the current user is in ID( userstable- current_room_idfield)
  • Update the status of the current user's chat room ( roomstable- current_statusfield)
  • Clear the unread messages of the current user's current chat room ( roomstable- badge_numberfield)
  • Get the chat history under the current chat room ( recordstable- room_namefield)
  • Update the number of people online in the current chat room ( roomstable- numfield)
  • Push service message to everyone in the current chat room: xxx joins the chat room

4. Join the chat room successfully

5. The system service messages here are not stored in the database.

Server core code:

  /**
   * @description 用户退出/加入聊天室
   * @param data {
   *   {String | ObjectId} userId:当前离线用户ID
   *   {String | ObjectId} roomId:当前用户所处聊天室ID
   *   {String} roomName:当前用户所处聊天室名称
   * }
   */
  socket.on('join', data => {
    
    
    // 创建room
    socket.join(data.roomName)
    // 找到用户的当前所在聊天室
    User.findOne({
    
    _id: ObjectId(data.userId)}, function (error, user_data) {
    
    
      // 如果用户的前后俩次聊天室一致,则不更新,反之加入成功
      if (user_data.current_room_id != data.roomId) {
    
    
      	...
      	// 对所有用户发送消息,这里
        io.sockets.in(data.roomName).emit('chat_message', createResponse(true, {
    
    
           action: 'add', // 添加聊天消息
           data: {
    
    
              user_id: data.userId,
              user_name: data.userName,
              room_name: data.roomName,
              chat_content: `${
      
      data.userName}加入了聊天室`,
              status: 0 // 0代表系统服务消息,1代表用户消息
            }
         }))
      }
  })

The client processes the messages pushed by the server and renders them on the page:

...
	// 获取聊天消息
	socket.on('chat_message', data => {
    
    
	  if (data.data.action == 'set') {
    
    
	    this.props.dispatch({
    
    
	      type: 'set_records',
	      data: data.data.data
	    })
	  } else if (data.data.action == 'add') {
    
    
	    this.props.dispatch({
    
    
	      type: 'add_record',
	      data: data.data.data
	    })
	 }
      // 聊天置底
	  this.updatePosition()
	  // 桌面消息通知有新的消息,这里因为安全问题,https可使用
	  // this.Notification()
	})
	
	// 聊天记录到达底部
    updatePosition = () => {
    
    
      let ele = document.getElementsByClassName('chat_body_room_content_scroll')[0]
      ele.scrollTop = ele.scrollHeight
    }
    
    // 新消息通知
    Notification = () => {
    
    
      let n = new Notification('会话服务提醒', {
    
    
        body: '您有新的消息哦,请查收',
        tag: 'linxin',
        icon: require('../assets/img/chat_head_img.jpg'),
        requireInteraction: true
      })
    }
...

Let’s take a look at the basic implementation:
Insert image description here

③: A single user adds a group chat, which can be seen by all users
Sorting process
1. Add a group chat with unique name constraints
2. Add this group chat to the currently used user chat room list
3. After successful addition
  • Leave the current chat room, send a leave message to the online users in the current chat room (excluding yourself), the number of people in the current chat room- 1
  • Enter the newly added chat room, the number of people online in the new chat+ 1
4. Leave the current chat room (socket.leave)! ! !

Server core code

// 更新离开的聊天室在线人数
Room.updateMany({
    
    room_name: data.leaveRoom.roomName}, {
    
    $inc: {
    
    num: -1}}, function () {
    
    })
// 给当前聊天室用户发送离开信息,不包括自己
socket.broadcast.to(data.leaveRoom.roomName).emit('chat_message', createResponse(true, {
    
    
  action: 'add',
  data: {
    
    
    user_id: data.leaveRoom.userId,
    user_name: data.leaveRoom.userName,
    room_name: data.leaveRoom.roomName,
    chat_content: `${
      
      data.leaveRoom.userName}离开了聊天室`,
    status: 0
  }
}));
// 离开聊天室
socket.leave(data.leaveRoom.roomName)

Let’s take a look at the effect achieved:
Insert image description here

④: Users can chat with everyone in real time
Sorting process
1. The user sends a message to the server and pushes the message to the users currently online in the chat room.
2. The number of unread messages for users who are currently offline in the chat room + 1
3. Chat message storage and processing

client
Html

...
   <div className='chat_body_room_input'>
      <div className='chat_body_room_input_warp'>
        <input id='message' type="text" placeholder='请输入聊天内容'
               onKeyUp={() => event.keyCode == '13' ? this.sendMessage() : ''}/>
      </div>
      <div className='chat_body_room_input_button' onClick={this.sendMessage}>点击发送</div>
    </div>
...

App.js

// 发送消息
sendMessage = () => {
    
    
  let ele = document.getElementById('message')
  if (!ele.value) return
  socket.emit('chat_message', {
    
    
    roomName: this.props.room.room_item.room_name,
    userId: this.state.userInfo._id,
    userName: this.state.userInfo.user_name,
    chat_content: ele.value
  })
  ele.value = ''
}

Server core code:

/**
 * @description 处理聊天信息
 * @param data {
 *   {String | ObjectId} userId:当前离线用户ID
 *   {String} username:当前用户名称
 *   {String} roomName:当前用户所处聊天室名称
 *   {String} chat_content:聊天内容
 * }
 */
socket.on('chat_message', data => {
    
    
  // 更新当前聊天室不在线用户的未读消息数量
  Room.updateMany({
    
    room_name: data.roomName, current_status: false}, {
    
    $inc: {
    
    badge_number: 1}})
    .then(res => {
    
    
      // 更新聊天列表
      updateRoomList()
      // 消息入库处理,并且发送消息至在线用户
      insertChatMessage({
    
    
        user_id: data.userId,
        user_name: data.userName,
        room_name: data.roomName,
        chat_content: data.chat_content,
        status: 1
      })
    })
})

/**
 * @description 消息入库
 * @param data {
 *   {String | ObjectId} userId:用户ID
 *   {String} username:用户名称
 *   {String} roomName:聊天室名称
 *   {String} chat_content:;聊天内容
 *   {Number} status:0是系统消息,其他代表用户消息
 * }
 */
function insertChatMessage(data) {
    
    
  let record = new Records(data)
  record.save()
    .then(res => {
    
    
      sendMessageRoom(data)
    })
    .catch(err => {
    
    
      console.log('插入失败')
    })
}

/**
 * @description 给当前聊天室用户发消息
 * @param {Object} data:插入的聊天记录
 */
function sendMessageRoom(data) {
    
    
  io.sockets.in(data.room_name).emit('chat_message', createResponse(true, {
    
    
    action: 'add',
    data,
  }))
}

Let’s take a look at the effect achieved:
Insert image description here

⑤: Users can keep chat lists and chat records offline

When the user closes the browser, monitor the user's departure and set the user's offline status.
Sorting process
: 1. Update the ID of the chat room the user is currently in ( userstable- current_room_idfield)
: 2. Update the status of all the user's chat rooms ( roomstable - current_statusfield)
: 3. Update the number of people online in the current chat room - 1
: 4. Push user offline messages to the online users in the current chat room (excluding yourself)
: 5. The user leaves the room ( socket.leave)! ! !

Client:

// 监听浏览器刷新/关闭事件
listenClose = () => {
    
    
  if (navigator.userAgent.indexOf('Firefox')) {
    
    
    window.onbeforeunload = () => {
    
    
      socket.emit('off_line', {
    
    
        userName: this.state.userInfo.user_name,
        userId: this.state.userInfo._id,
        roomName: this.props.room.room_item.room_name,
      })
    }
  } else {
    
    
    window.onunload = () => {
    
    
      socket.emit('off_line', {
    
    
        userName: this.state.userInfo.user_name,
        userId: this.state.userInfo._id,
        roomName: this.props.room.room_item.room_name,
      })
    }
  }
}

Server:

/**
 * @description 用户离线
 * @param data {
 *   {String | ObjectId} userId:当前离线用户ID
 *   {String} roomName:当前用户所处聊天室名称
 * }
 */
socket.on('off_line', data => {
    
    
  // 更新当前离线用户所处的聊天室
  User.updateOne({
    
    _id: ObjectId(data.userId)}, {
    
    $set: {
    
    current_room_id: ''}})
    .then(res => {
    
    
       // 更新当前用户所有聊天室的所处状态
       Room.updateMany({
    
    user_id: data.userId}, {
    
    $set: {
    
    current_status: false}})
      	.then(res => {
    
    ...})
   })
})

Effect demonstration
Insert image description here

⑥: Click on the user’s avatar to add a private chat
Sorting process
1. The user clicks on a picture other than his own in the chat history and adds a private chat
2. The user leaves the previous chat room and enters a new chat room
  • Before leaving the chat room, if it is a group chat, the number of people online in the group chat - 1, if it is a private chat, the user is offline
  • When the user enters a new chat room, if it is a group chat, the number of people online will be + 1. If it is a private chat, no action will be taken.
  • If it is a private chat, the name of the chat room is 发起聊天的用户名-私聊的对方用户名, so we should pay attention to the judgment here, use inthe keyword here
  • For example: A initiates a chat with B, then the chat room name is AB, otherwise the chat room name is BA

I won’t introduce much about the client here. Click on the avatar to add a private chat. Here we mainly introduce the server:

Core code:

...
	/**
	 * @description 新增私聊
	 * @param data {
	 *   {String | ObjectId} userId:当前用户ID
	 *   {String} username:当前用户名称
	 *   {String} userOtherId:与之聊天的用户ID
	 *   {String} userOtherName:与之聊天的用户名称
	 * }
	 */
	socket.on('add_private_chat', data => {
    
    
	  // 新增私聊聊天室
	  addPrivateRoom(socket, data)
	})

	/**
	 * @description 新增私聊用户
	 * @param {Object} socket:socket对象
	 * @param {Object} data:新增私聊用户信息
	 */
	function addPrivateRoom(socket, data) {
    
    
	  // 如果数据库不存在则添加,反之加入房间
	  Room.find({
    
    user_id: data.userId}).where('room_name').in([`${
      
      data.userName}-${
      
      data.userOtherName}`, `${
      
      data.userOtherName}-${
      
      data.userName}`]).exec((err, roomList) => {
    
    
	    if (err) return
	    if (roomList.length) {
    
    
	      socket.emit('add_private_chat', createResponse(true, roomList[0]))
	    } else {
    
    
	      let room = new Room({
    
    
	        user_id: data.userId.toString(),
	        user_name: data.userName,
	        room_name: `${
      
      data.userName}-${
      
      data.userOtherName}`,
	        status: 1,
	        num: 0,
	        badge_number: 0,
	        current_status: false
	      })
	      room.save()
	        .then(res => {
    
    
	          Room.find({
    
    user_id: data.userId})
	            .then(result => {
    
    
	              socket.emit('room_list_all', createResponse(true, result))
	              socket.emit('add_private_chat', createResponse(true, result.filter(item => item.room_name == `${
      
      data.userName}-${
      
      data.userOtherName}`)[0]))
	            })
	        })
	    }
	  })
	}
...
⑦: Users can have real-time private chats with one person
Sorting process
1. Users can add/join private chats by clicking on an avatar other than their own.
2. Determine whether the user owns this chat room. If so, join the chat room. Otherwise, create a chat room and join it.
3. If the user does not have a chat number, add a chat room
  • Leave the current chat room, send a leave message to the online users in the current chat room (excluding yourself), the number of people in the current chat room- 1
  • Join a newly added private chat room
  • When the current user sends a chat message in a newly added chat room, determine whether the other party in the private chat owns the chat room.
  • If you don't have it, the person you are chatting with in private adds the current chat room (belonging to 2 people) and joins! ! !
  • If you have it, but you are not online (the focus is not in the chat room), the number of unread messages of the other party in the private chat +1
4. If the user owns this chat room, just join the chat room directly

Server core code:

/**
 * @description 处理聊天信息
 * @param data {
 *   {String | ObjectId} userId:当前离线用户ID
 *   {String} username:当前用户名称
 *   {String} roomName:当前用户所处聊天室名称
 *   {String} chat_content:聊天内容
 *   {Number} status:0为群聊,其他为私聊
 * }
 */
socket.on('chat_message', data => {
    
    
  // 如果是群聊
  if (data.status == '0') {
    
    
  	// 更新当前聊天室不在线用户的未读消息数量
  	...
  	// 给所有在线用户发消息
  	...
  }else if (data.status == '1'){
    
    
  	// 如果当前用户不存在聊天室,添加聊天室并且加入聊天室
  	...
  	// 如果当前用户存在聊天室,判断当前用户是否在线,如果不在线,未读消息数量 + 1
  	...
  }

Effect demonstration:
Insert image description here

⑧: The user is typing
Sorting process
1. If it is a private chat, monitor user input
2. The client monitors the input in the user input box and tells the server that the user starts typing.
3. If the user continues to input within a certain period of time (here 500 milliseconds), no stop message will be sent, otherwise the user will be told to stop typing on the server.
4. Here we mainly use function anti-shake to push the input completion message to the server to inform the server that the user has completed the input.

Client:

// html
...
<input id='message' type="text" placeholder='请输入聊天内容' onInput={
    
    this.inputting} 
onKeyUp={
    
    () => event.keyCode == '13' ? this.sendMessage() : ''}/>
...

---------------------------------------------------------------------------
// 正在输入
inputting = () => {
    
    
  // 如果是私聊,告诉服务端用户正在输入
  if (this.props.room.room_item.status == '1') {
    
    
    socket.emit('inputting', {
    
    
      userName: this.state.userInfo.user_name,
      roomName: this.props.room.room_item.room_name,
      status: true
    })
    // 500秒后,告诉用户输入完毕
    this.debounce(this.inputtingEnd, 500)
  }
}

// 用户结束输入
inputtingEnd = () => {
    
    
  socket.emit('inputting', {
    
    
    userName: this.state.userInfo.user_name,
    roomName: this.props.room.room_item.room_name,
    status: false
  })
}

// 函数防抖
debounce = (fun, delay) => {
    
    
  clearTimeout(fun.timer)
  fun.timer = setTimeout(() => {
    
    
    fun()
  }, delay)
}

Server:

...
	/**
	 * @description 私聊监听用户输入
	 * @param {String} username:当前用户名称
	 * @param {String} roomName:当前聊天室名称
	 * @param {Boolean} status: 用户是否正在输入
	 */
	socket.on('inputting', data => {
    
    
	  User.findOne({
    
    user_name: data.roomName.replace(data.userName, '').replace('-', '')})
	    .then(res => {
    
    
	      // 如果用户存在
	      if (res != null) {
    
    
	        res.roomName = data.roomName
	        res.status = data.status
	        // 给某个用户发消息
	        sendMessageSingleUser(res)
	      }
	    })
	})

	/**
	 * @description 给某个用户发消息
	 * @param user:用户信息
	 */
	function sendMessageSingleUser(user) {
    
    
	  // 如果用户不在线的话,不推送(我们在用户离线时,把他的current_room_id置为空)
	  if (user.current_room_id) {
    
    
	    Room.find({
    
    user_id: user._id}, function (err, data) {
    
    
	      io.sockets.sockets[user.socket_id].emit('inputting', createResponse(user.status, user))
	    })
      }
	}
...

Everyone can see how to send a message to a certain user (here you can send the message to the current chat room, and the client can judge by itself)

Used here io.sockets.sockets, represents the current socket connection service, which is stored Objectin the formkey-value

Because the user's information is different every time he connects socket, we need to update socket_idhis information after the user establishes a connection.socket_id

Client:

...
	componentDidMount() {
    
    
	  // 如果本地信息不存在,则去注册,反之获取聊天列表
	  if (localStorage.getItem('userInfo')) {
    
    
	    let userInfo = JSON.parse(localStorage.getItem('userInfo'))
	    socket.emit('login', userInfo._id)
	  } else {
    
    
	    this.register()
	  }
	}
...
	// 获取登录结果
	socket.on('login', socket_id => {
    
    
	  userInfo.socket_id = socket_id
	  localStorage.setItem('userInfo', JSON.stringify(userInfo))
	  this.setState({
    
    
	    userInfo
	  }, () => {
    
    
	    socket.emit('get_room_list', userInfo._id)
	  })
	})
...

Server:

...
	/**
	 * @description 用户静默登录
	 * @param {String | ObjectId} userId:登录的用户id
	 */
	socket.on('login', userId => {
    
    
	  // 更新用户列表socketId
	  User.updateOne({
    
    _id: ObjectId(userId)}, {
    
    $set: {
    
    socket_id: socket.id}}, function (err, result) {
    
    
	    socket.emit('login', socket.id)
	  })
	})
...

Effect demonstration:
Insert image description here
At this point, we have completed all functions! ! !


4. Related articles

Guess you like

Origin blog.csdn.net/Vue2018/article/details/107533478