Cut the crap and go straight to the renderings

rendering

The flow chart

Technology stack

IO front-end: vuejs, VUE-socket. IO,better-scroll back-end: egg,egg-socket. IO database: redis

The implementation process

The socket connection

1. Vuex defines the socket module and default socket events

Const state = {socketState: false,// connection state chat_list: GetChatList (),// Chat list} const getters = {unread_num(state) {let count = 0; for (var i = 0; i < state.chat_list.length; i++) { count += state.chat_list[i].unread_num || 0; } return count; Const mutations = {socket_connect(state) {console.log(" connect successfully "); state.socketState = true; }, socket_reconnect(state, data) {console.log(" reconnect "+ data); }, socket_reconnecting(state, data) {console.log(" connecting "+ data); Toast(' reconnecting ')}, socket_disconnect(state) {console.log(" disconnecting "); state.socketState = false; }},Copy the code

2. The client initiates a socket connection (initializes the socket)

if (! store.state.socket.socketState) { Vue.use( new VueSocketIO({ debug: true, connection: ip + "? token=" + window.localStorage.getItem("token"), vuex: { store, actionPrefix: "socket_", mutationPrefix: "socket_" } }) ); }Copy the code

3. The server responds to the socket connection

const { app, socket } = ctx; const token = ctx.request.query.token; const id = socket.id; let username = ''; try { username = (await ctx.app.jwt.verify(token, ctx.app.config.jwt.secret)).username; let data = { id, username }; If (await app.redis. Exists (username)) {let receive = await app.redis. Get (username); receive = JSON.parse(receive); Console. log(' someone is already online '); ctx.socket.to(receive.id).emit('client_logout'); } // Store online information in redis to await app.redis. Set (username, json.stringify (data)); } catch (error) {console.log(error) socket.emit('connect_deny'); socket.disconnect(); return; }Copy the code

Const {app, socket} = CTX; The socket object is generated by each client connection. The socketid object contains a socketid id that is the unique identifier of each client. We can imagine that the client knows the user’s account (username) and the server knows the socketid, so we can bind the two identifiers together with username as the key and value {username: ‘XXXX ‘,socketid:’ XXXX’} stored in redis. In the case of private chat push, the front end knows the target user username, and the back end Redis also caches the information of each login user, so that we can push messages to the specified user through username.

The implementation logic is roughly as follows: The server verifies the validity of the socket client of the client, and the verified connection reads the record with the key of USERNAME from the Redis cache. If the record exists, the socket disconnect event is triggered. (Single login)

Being pushed

Client push

Send (data, type) {this.$socket.emit("chat", data, () => {this.chat_item.chat_list.push(data); This.$nextTick(() => {this.$refs.wrapper.refresh(); this.$refs.wrapper.scrollToEnd(); }); }); },Copy the code

This.$socket.emit(“chat”, data, cb) can be used to push messages since the front-end has mounted the socket to vuex. Chat is the event type, corresponding to the socket route defined on the server. Data is the data to be pushed, including the ID of the target user; Cb is the callback function for successful push. After a successful push, the better event is called in this.nextTick to pull the chat window to the bottom. (The better scroll bar is pulled to the bottom based on selectors, which are DOM element dependent, and vueDOM updates are asynchronous and need to be called after this.$nextTick.)

The server defines the route

io.route('chat', app.io.controller.chat.index); // Receive client emit('chat') eventsCopy the code

The server receives and pushes

Const {CTX} = this const message = this.ctx.args[0] const cb = this.ctx.args[1] // Determine whether the target user is online if (await) app.redis.exists(message.receive_id)) { let receive = await app.redis.get(message.receive_id) receive = Parse (receive) // Send ctx.socket.to(receive.id). Emit ('client_receive_msg', Message)} else {console.log(' not online ') // Message_ +username // Insert database await app.redis. Lpush ('message_' + message.receive_id, Json.stringify (message))} cb && cb(' push successfully ')Copy the code

The server gets the pushed message in the index method of chat.js and pushes it to the target user using ctx.socket.to(receiv.id). Search for the socketid of the target user in Redis according to username (if there is no record with key username as the key, it means that the target user is not online. Cache the offline information and push it to the target user when they are online)

Client receiving

Socket_client_receive_msg (state, data) {// Check whether the page where the current user resides is the page of the current chat user. If yes, 1 let flag = false is not added to unread messages. if (router.currentRoute.name == "SoloChat" && router.currentRoute.query.username == data.send_info.username) { // The user is in the receiver's chat window at the time of the received message flag = true; } for (var i = 0; i < state.chat_list.length; I ++) {if (state.chat_list[I].username == data.send_info.username) {// If the chat dialog box already exists if (! flag) { state.chat_list[i].unread_num++; } state.chat_list[i].chat_list.push(data); State.chat_list. unshift(state.chat_list.splice(I, 1)[0]); }}},Copy the code

The simplest version of instant messaging is as follows:

Full version git address

The front end