Building a Live Online Chat Room Based on Laravel + Swoole + Vue (P13)

Building a Live Online Chat Room Based on Laravel + Swoole + Vue (P13)

Building a Live Online Chat Room Based on Laravel + Swoole + Vue (P13): Send Text/Emotional Messages

Building a Live Online Chat Room Based on Laravel + Swoole + Vue (P13): Send Text/Emotional Messages

Functional Overview

Sending messages supports a variety of formats, including ordinary text, emoticons, pictures, etc. Today we will introduce the most basic text and emoticons (Emoji is essentially a text message). Sending text messages requires entering text in the bottom text input box And click the Send button to send the message:

This is image title

To send an emoticon (only Emoji is supported here) message, you need to click the emoticon icon to pop up a selection box, and then click to select an emoticon. The emoticon will be automatically rendered to the message text box and then sent with the text message:

This is image title

Front-end components

Next, let's first look at the front-end component implementation.

Message sending logic

The front end interface components in the chat room resources/js/pages/Chat.vue, the bottom of the corresponding transmitted message is a JavaScript code, click the send button, will call this submessmethod:

submess() {
    // Determine if the send message is empty
    if (this.chatValue !== '') {
      if (this.chatValue.length > 200) {
        Alert({
          content: 'Please enter less than 100 words'
        });
        return;
      }
      const msg = inHTMLData(this.chatValue); // Prevent xss

      const obj = {
        username: this.userid,
        src: this.src,
        img: '',
        msg,
        roomid: this.roomid,
        time: new Date(),
        api_token: this.auth_token
      };
      // Pass message
      socket.emit('message', obj);
      this.chatValue = '';
    } else {
      Alert({
        content: 'the content can not be blank'
      });
    }
}

There will be a basic check in it. For example, the message content cannot be empty or exceed 100 characters. In addition, the input information will be processed to avoid XSS attacks. After all the above processes are completed, the message object will be initialized, and then Call the following code to send a message object through WebSocket communication:

socket.emit('message', obj);

After sending, clear the contents of the text box.

Message rendering logic

Rendering logic embedded in the page message from the sub-assembly Message is achieved by two-way data binding:

<Message
    v-for="obj in getInfos"
    :key="obj._id"
    :is-self="obj.userid === userid"
    :name="obj.username"
    :head="obj.src"
    :msg="obj.msg"
    :img="obj.img"
    :mytime="obj.time"
    :container="container"
    ></Message>

Here we noticed obj.username === userid replaced with obj.userid === userid, because the original VueChat implementations userid and username are equivalent, and we are here userid and email equivalence, is-self attributes used to distinguish when rendering the message issued its own or someone else's hair (his own hair in the right Side, others posted on the left).

Emoji components

The corresponding implementation of the Emoji selection box is as follows:

<div class="fun-li emoji">
    <i class="icon iconfont icon-emoji"></i>
    <div class="emoji-content" v-show="getEmoji">
      <div class="emoji-tabs">
        <div class="emoji-container" ref="emoji">
          <div class="emoji-block" :style="{width: Math.ceil(emoji.people.length / 5) * 48 + 'px'}">
            <span v-for="(item, index) in emoji.people" :key="index">{{item}}</span>
          </div>
          <div class="emoji-block" :style="{width: Math.ceil(emoji.nature.length / 5) * 48 + 'px'}">
            <span v-for="(item, index) in emoji.nature" :key="index">{{item}}</span>
          </div>
          <div class="emoji-block" :style="{width: Math.ceil(emoji.items.length / 5) * 48 + 'px'}">
            <span v-for="(item, index) in emoji.items" :key="index">{{item}}</span>
          </div>
          <div class="emoji-block" :style="{width: Math.ceil(emoji.place.length / 5) * 48 + 'px'}">
            <span v-for="(item, index) in emoji.place" :key="index">{{item}}</span>
          </div>
          <div class="emoji-block" :style="{width: Math.ceil(emoji.single.length / 5) * 48 + 'px'}">
            <span v-for="(item, index) in emoji.single" :key="index">{{item}}</span>
          </div>
        </div>
        <div class="tab">
          <!-- <a href="#hot"><span>Hot</span></a>
          <a href="#people"><span>People</span></a> -->
        </div>
      </div>
    </div>
  </div>

The specific rendering logic is not the focus of this project. Interested students can go through the source code by themselves.

Run npm run dev recompiled front-end resources for the changes to take effect.

Back-end implementation

Writing API resource classes

Since the message rendering components Message need to pass the message data will be rendered, and the front-end and back-end message object attribute messages table can not be one to one, so we can write an API resource to do automatic conversion data structures between the two.

Prior to this, we first Message define its relationship with the user model class:

<?php
namespace App;

use Illuminate\Database\Eloquent\Model;

class Message extends Model
{
    public $timestamps = false;

    public function user()
    {
        return $this->belongsTo(User::class);
    }
}

Then create by Artisan command Message API resource corresponding model class:

php artisan make:resource MessageResource

The path corresponding to the generated command app/Http/Resources/MessageResource.php to write conversion process toArray is as follows:

<?php
namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\JsonResource;

class MessageResource extends JsonResource
{
    /**
     * Transform the resource into an array.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return array
     */
    public function toArray($request)
    {
        return [
            'id' => $this->id,
            'userid' => $this->user->email,
            'username' => $this->user->name,
            'src' => $this->user->avatar,
            'msg' => $this->msg,
            'img' => $this->img,
            'roomid' => $this->room_id,
            'time' => $this->created_at
        ];
    }
}

The target structure we transform must be consistent with the front-end message object attribute field names, so that the message data returned by the back-end can be rendered normally on the front-end.

Modify history chat history interface

Next, we can write before the interface chat history applications above MessageResource do return JSON data structures automatically converted, open app/Http/Controllers/MessageController.php, amend history as follows:

use App\Http\Resources\MessageResource;

/**
 * Get historical chat history
 * @param Request $request
 * @return \Illuminate\Http\JsonResponse
 */
public function history(Request $request)
{
    ...
    // Paging query messages
    $messages = Message::where('room_id', $roomId)->skip($skip)->take($limit)->orderBy('created_at', 'asc')->get();
    $messagesData = [];
    if ($messages) {
        // Automatic transformation of JSON data structures based on API resource classes
        $messagesData = MessageResource::collection($messages);
    }
    // Return response
    return response()->json([
        'data' => [
            'errno' => 0,
            'data' => $messagesData,
            'total' => $messageTotal,
            'current' => $current
        ]
    ]);
}

Note: For the implementation principle of the API resource class, you can refer to the corresponding documents . We only use it here and do not introduce it in depth.

At this point, we are messages filling some test data in the table:

This is image title

Restart the Swoole HTTP server, and you can see the rendered historical chat records in the front-end chat room room 1:

This is image title

You can view all historical messages by scrolling up and down.

Implement message sending and broadcasting functions

Finally, we implement message sending and broadcasting functions based on Websocket.

Open the back-end Websocket routing file routes/websocket.php, write the implementation code that receives the message and broadcasts it to all online users in the chat room:

use App\Message;
use Carbon\Carbon;

WebsocketProxy::on('message', function (WebSocket $websocket, $data) {
    if (!empty($data['api_token']) && ($user = User::where('api_token', $data['api_token'])->first())) {
        // Get message content
        $msg = $data['msg'];
        $roomId = intval($data['roomid']);
        $time = $data['time'];
        // Message content or room number cannot be empty
        if(empty($msg) || empty($roomId)) {
            return;
        }
        // Record log
        Log::info($user->name . 'in the room' . $roomId . 'Post message: ' . $msg);
        // Save message to database
        $message = new Message();
        $message->user_id = $user->id;
        $message->room_id = $roomId;
        $message->msg = $msg;
        $message->img = ''; // The picture field is temporarily blank
        $message->created_at = Carbon::now();
        $message->save();
        // Broadcast messages to all users in the room
        $room = Count::$ROOMLIST[$roomId];
        $messageData = [
            'userid' => $user->email,
            'username' => $user->name,
            'src' => $user->avatar,
            'msg' => $msg,
            'img' => '',
            'roomid' => $roomId,
            'time' => $time
        ];
        $websocket->to($room)->emit('message', $messageData);
        // Update the number of unread messages in this room for all users
        $userIds = Redis::hgetall('socket_id');
        foreach ($userIds as $userId => $socketId) {
            // Update the number of unread messages per user and send them to the corresponding online users
            $result = Count::where('user_id', $userId)->where('room_id', $roomId)->first();
            if ($result) {
                $result->count += 1;
                $result->save();
                $rooms[$room] = $result->count;
            } else {
                // If a record of the number of unread messages for a user does not exist, initialize it
                $count = new Count();
                $count->user_id = $user->id;
                $count->room_id = $roomId;
                $count->count = 1;
                $count->save();
                $rooms[$room] = 1;
            }
            $websocket->to($socketId)->emit('count', $rooms);
        }
    } else {
        $websocket->emit('login', 'Login to enter chat room');
    }
});

Implementation logic is very simple, to ensure that the user is authenticated, room number and content of the message is not empty premise, after obtaining the text message sent by the client (including Emoji expression), save it to the messages table, and then broadcast to All users can be in the room, where we did not use MessageResource to do automatically translate data structure, because the server can not get WebSocket Illuminate\Http\Request instance, which can lead to JSON serialization error.

Note: The picture sending is also based on this message channel. We will implement the corresponding processing code in the next article.

Finally, we also updated the number of unread messages for users, stored them in a database, and sent them to all online users.

Test live chat

At this point, we have completed all the coding work, restart the Swoole server:

bin/laravels restart

Log in to different users in Chrome and Firefox browsers and enter the same chat room to start a live chat online:

This is image title

Because it is based on Websocket communication, the page does not need to be refreshed to instantly get messages sent by the other party.

In the next tutorial, we will introduce the implementation of picture message sending. The source code for this project has been submitted to Github: https://github.com/nonfu/webchat.

laravel swoole vue online-chat

What's new in Bootstrap 5 and when Bootstrap 5 release date?

How to Build Progressive Web Apps (PWA) using Angular 9

What is new features in Javascript ES2020 ECMAScript 2020

Deno Crash Course: Explore Deno and Create a full REST API with Deno

How to Build a Real-time Chat App with Deno and WebSockets

Convert HTML to Markdown Online

HTML entity encoder decoder Online

Random Password Generator Online

HTML Color Picker online | HEX Color Picker | RGB Color Picker

Building a Live Online Chat Room Based on Laravel + Swoole + Vue (P14)

Building a Live Online Chat Room Based on Laravel + Swoole + Vue (P14): Send a Picture Message

Building a Live Online Chat Room Based on Laravel + Swoole + Vue (P11)

Building a Live Online Chat Room Based on Laravel + Swoole + Vue (P11): Get History Chat History After Entering The Chat Room

Building a Live Online Chat Room Based on Laravel + Swoole + Vue (P9)

Building a Live Online Chat Room Based on Laravel + Swoole + Vue (P9): Realizing the Chat Function of Customer Service Robots

Building a Live Online Chat Room Based on Laravel + Swoole + Vue (P12)

Building a Live Online Chat Room Based on Laravel + Swoole + Vue (P12): Join and Exit Chat Room Function Implementation

Building a Live Online Chat Room Based on Laravel + Swoole + Vue (P6)

Building a Live Online Chat Room Based on Laravel + Swoole + Vue (P6): Establish a Connection Between the Socket.io Client and The Swoole Websocket server