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:

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:

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:

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

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:

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.

Angular 9 Tutorial: Learn to Build a CRUD Angular App Quickly

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

What’s new in HTML6

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

What is new features in Javascript ES2020 ECMAScript 2020

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

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 (P14): Send a Picture Message

In the last tutorial we demonstrated the release of text/emotional messages in chat rooms. Today we will look at how to post picture messages.

Front-end interaction code

We started from the front end assembly, assembly in a chat room Chat.Vue, the client core logic to send pictures located fileup approach, we need to be adjusted to fit the rear end of the original code based interface Laravel + Swoole of:

fileup() {
    const that = this;
    const file1 = document.getElementById('inputFile').files[0];
    if (file1) {
      const formdata = new window.FormData();
      formdata.append('file', file1);
      formdata.append('api_token', this.auth_token);
      formdata.append('roomid', that.roomid);
      this.$store.dispatch('uploadImg', formdata);
      const fr = new window.FileReader();
      fr.onload = function () {
        const obj = {
          username: that.userid,
          src: that.src,
          img: fr.result,
          msg: '',
          roomid: that.roomid,
          time: new Date(),
          api_token: that.auth_token
        };
        socket.emit('message', obj);
      };
      fr.readAsDataURL(file1);
      this.$nextTick(() => {
        this.container.scrollTop = 10000;
      });
    } else {
      console.log('Must have file');
    }
},

When we click on the camera icon in the chat room, the image upload window will pop up:

After selecting the picture, it will call the above fileup method to upload pictures.

It involves two logic: first calls the back-end interface to upload pictures based on the HTTP protocol and save the message to the messages table, it will send a message to Websocket server after a successful upload, and then by Websocket server broadcasts a message to all online users.

The upload image corresponds to this line of code:

this.$store.dispatch('uploadImg', formdata);

The final call to the back-end interface code is located resources/js/api/server.js in:

// upload image
postUploadFile: data => Axios.post('/file/uploadimg', data, {
    headers: {
        'Content-Type': 'application/x-www-form-urlencoded'
    }
}),

We will write this interface in the backend shortly.

Sending a picture message based on Websocket corresponds to this line of code:

socket.emit('message', obj);

This is no different than sending a text message before the code is simply obj there to add a imgfield only.

Image upload interface

Next, we write an image upload interface on the Laravel backend.

In the routes/api.php new route file/uploadimg:

Route::middleware('auth:api')->group(function () {
    ...
    Route::post('/file/uploadimg', '[email protected]');
}

Then create the controller with Artisan commands FileController:

php artisan make:controller FileController

In the newly generated file controller app/Http/Controllers/FileController.php in preparation uploadImage codes are as follows:

<?php
namespace App\Http\Controllers;

use App\Message;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;

class FileController extends Controller
{
    public function uploadImage(Request $request)
    {
        if (!$request->hasFile('file') || !$request->file('file')->isValid() || !$request->has('roomid')) {
            return response()->json([
                'data' => [
                    'errno' => 500,
                    'msg'   => 'Invalid parameter (room number/picture file is empty or invalid)'
                ]
            ]);
        }
        $image = $request->file('file');
        $time = time();
        $filename = md5($time . mt_rand(0, 10000)) . '.' . $image->extension();
        $path = $image->storeAs('images/' . date('Y/m/d', $time), $filename, ['disk' => 'public']);
        if ($path) {
            // If the picture is uploaded successfully, the corresponding picture message is saved to the messages table
            $message = new Message();
            $message->user_id = auth('api')->id();
            $message->room_id = $request->post('roomid');
            $message->msg = '';  // Text message left blank
            $message->img = Storage::disk('public')->url($path);
            $message->created_at = Carbon::now();
            $message->save();
            return response()->json([
                'data' => [
                    'errno' => 200,
                    'msg'   => 'Saved successfully'
                ]
            ]);
        } else {
            return response()->json([
                'data' => [
                    'errno' => 500,
                    'msg'   => 'File upload failed, please try again'
                ]
            ]);
        }
    }
}

This mainly involves image upload and message saving logic. Because we will save the picture to the storage/public next directory, in order to let the picture can be requested through the Web URL, you need to storage create a soft catalog:

php artisan storage:link
Websocket server broadcast

Finally, we in routes/websocket.php the messagechannel complementary picture message processing logic:

WebsocketProxy::on('message', function (WebSocket $websocket, $data) {
    ...
    // Get message content
    $msg = $data['msg'];
    $img = $data['img'];
    $roomId = intval($data['roomid']);
    $time = $data['time'];
    // Message content (including pictures) or room number cannot be empty
    if((empty($msg)  && empty($img))|| empty($roomId)) {
        return;
    }
    // Record log
    Log::info($user->name . 'in the room' . $roomId . 'Post message: ' . $msg);
    // Save messages to the database (except for picture messages, because they were saved during the upload)
    if (empty($img)) {
        $message = new Message();
        $message->user_id = $user->id;
        $message->room_id = $roomId;
        $message->msg = $msg;  // Text message
        $message->img = '';  // Picture message left 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' => $img,
        'roomid' => $roomId,
        'time' => $time
    ];
    $websocket->to($room)->emit('message', $messageData);
    ...

Very simple, just add the picture message field uploaded by the client to the field of the previous broadcast message, without any other logic.

At this point, we can complete the front-end and back-end code for image message sending. Next, we test the sending of image messages on the chat room interface.

Test image message release

Before you start, recompile the front-end resources:

npm run dev

Make front-end code changes take effect. And restart Swoole HTTP and WebSocket server:

bin/laravels restart

Let the backend code changes take effect.

Then, open the chat room in Chrome and Firefox browsers, log in and enter the same room, you can send picture messages to each other in real time:

At this point, we have completed the main function of the chat room. Next, we will optimize the project code, especially the performance and elegance of the back-end WebSocket communication.

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

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 (P11): Get History Chat History After Entering The Chat Room

In the last tutorial, we showed you how to get unread messages after the user logs in. Today we enter the chat room to see what happened in the chat room.

Chat room page initialization logic

The corresponding Vue component of the chat room page is resources/js/pages/Chat.vue, we open this file and see the initial rendering logic of the page and the interaction with the back-end interface.

First look created and mounted hook function of these two pages initialization phase calls.

created hook function

In the created process, it will be from the current page URL query string in room obtain information (Tips: URL chat room is http://webchats.test/#/chat?roomId=1):

async created() {
  const roomId = queryString(window.location.href, 'roomId');
  this.roomid = roomId;
  if (!roomId) {
    this.$router.push({path: '/'});
  }
  if (!this.userid) {
    // Prevent unlogged
    this.$router.push({path: '/login'});
  }
  const res = await url.getNotice();
  this.noticeList = res.data.noticeList;
  if (res.data.version !== res.data.version) {
    this.noticeBar = false;
  }
  this.noticeVersion = res.data.version;
},

If the room information is empty, it will jump to the homepage, if the user is not logged in, it will jump to the login interface.

Then call url.getNotice() to obtain information bulletin, corresponding to the back-end interface call is located resources/js/api/server.js:

// Request announcement
getNotice: () => Axios.get('https://s3.qiufengh.com/config/notice-config.js'

This piece does not belong to the core logic, and the specific details are ignored.

mounted hook function

Next, look at mounted logic function:

async mounted() {
  // WeChat rebound bug
  ios();
  this.container = document.querySelector('.chat-inner');
  // socket internal,this pointer to question
  const that = this;
  await this.$store.commit('setRoomDetailInfos');
  await this.$store.commit('setTotal', 0);
  const obj = {
    name: this.userid,
    src: this.src,
    roomid: this.roomid
  };
  socket.emit('room', obj);
  socket.on('room', function (obj) {
    that.$store.commit('setUsers', obj);
  });
  socket.on('roomout', function (obj) {
    that.$store.commit('setUsers', obj);
  });
  loading.show();
  setTimeout(async () => {
    const data = {
      total: +this.getTotal,
      current: +this.current,
      roomid: this.roomid
    };
    this.isloading = true;
    await this.$store.dispatch('getAllMessHistory', data);
    this.isloading = false;
    loading.hide();
    this.$nextTick(() => {
      this.container.scrollTop = 10000;
    });
  }, 500);

  this.container.addEventListener('scroll', debounce(async (e) => {
    if (e.target.scrollTop >= 0 && e.target.scrollTop < 50) {
      this.$store.commit('setCurrent', +this.getCurrent + 1);
      const data = {
        total: +this.getTotal,
        current: +this.getCurrent,
        roomid: this.roomid
      };
      this.isloading = true;
      await this.$store.dispatch('getAllMessHistory', data);
      this.isloading = false;
    }
  }, 50));

  this.$refs.emoji.addEventListener('click', function(e) {
    var target = e.target || e.srcElement;
    if (!!target && target.tagName.toLowerCase() === 'span') {
      that.chatValue = that.chatValue + target.innerHTML;
    }
    e.stopPropagation();
  });
},

Here is first initialized room total number of messages and information, and then Websocket channel routing server roominitiated the request, the server has to inform the user enters the room, and by listening to the client roomand roomoutreturns the server to receive the event message into the room and left the room:

socket.emit('room', obj);
socket.on('room', function (obj) {
    that.$store.commit('setUsers', obj);
});
socket.on('roomout', function (obj) {
    that.$store.commit('setUsers', obj);
});

In the callback function, we set the online user information of the room through Vuex.

In order to achieve this Laravel based authentication backend API interface, we are united in obj the new api_token field:

Of course, we need to calculate the property computed settings auth_token to make the above code to take effect:

computed: {
  ... // Other configurations
  ...mapState({
    userid: state => state.userInfo.userid,
    src: state => state.userInfo.src,
    auth_token: state => state.userInfo.token,
  })
},

Continue to look at mounted the logic followed by setTimeout the definition of a timer, mainly to get the chat room chat history from the server room, too, we have added in the Request field api_token fields:

const data = {
    total: +this.getTotal,
    current: +this.current,
    roomid: this.roomid,
    api_token: this.auth_token
};
this.isloading = true;
await this.$store.dispatch('getAllMessHistory', data);

getAllMessHistory The final request interface is defined in the rear end of resources/js/api/server.js the:

// Get all historical chats in the current room
RoomHistoryAll: data => Axios.get('/history/message', {
    params: data
}),

There is the request parameters of total the total number indicates that the message, current the current page (or the current screen news, because a large amount of information possible, here it is necessary to do the pagination process), roomid represents a room ID, api_token interface for authentication.

We will need to implement this historical chat history interface in the backend later, this is an HTTP request.

The last is the definition of event listening for two components: one is the processing of the chat window scroll operation, and the other is the processing after the Emoji icon is clicked. Note that when scrolling chat window also involves calling the chat history interfaces, which in fact we are very familiar pull-down refresh the phone App, we need here objparameter also joined api_token the field:

const data = {
    total: +this.getTotal,
    current: +this.getCurrent,
    roomid: this.roomid,
    api_token: this.auth_token
};

It can be seen that during the initialization phase of the chat room page, the two most important things are to establish a connection with the WebSocket server, tell it that the user has entered a certain room, and then obtain the historical chat history of this room and render it to the chat interface.

Next, we implement the corresponding Websocket interaction logic and HTTP routing interface on the back end.

History chat history backend interface

Let's first implement the interface for obtaining historical chat records from the backend.

Create the controller first MessageController:

php artisan make:controller MessageController

The controller then write app/Http/Controllers/MessageController.php codes are as follows:

<?php
namespace App\Http\Controllers;

use App\Message;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;

class MessageController extends Controller
{
    /**
     * Get historical chat history
     * @param Request $request
     * @return \Illuminate\Http\JsonResponse
     */
    public function history(Request $request)
    {
        $roomId = intval($request->get('roomid'));
        $current = intval($request->get('current'));
        $total = intval($request->get('total'));
        if ($roomId <= 0 || $current <= 0) {
            Log::error('Invalid room and page information');
            return response()->json([
                'data' => [
                    'errno' => 1
                ]
            ]);
        }
        // Get the total number of messages
        $messageTotal = Message::where('room_id', $roomId)->count();
        $limit = 20;  // 20 messages per page
        $skip = ($current - 1) * 20;  // From which message
        // Paging query messages
        $messageData = Message::where('room_id', $roomId)->skip($skip)->take($limit)->orderBy('created_at', 'desc')->get();
        // Return response
        return response()->json([
            'data' => [
                'errno' => 0,
                'data' => $messageData,
                'total' => $messageTotal,
                'current' => $current
            ]
        ]);
    }
}

This controller currently provides only history method that returns JSON formatted chat history.

Finally, we in the API route file routes/api.php definitions this route, the route after authentication is required to access, and because of this request will include api_token field, so certification will by auth:api done automatically Middleware:

Route::middleware('auth:api')->group(function () {
    ...
    Route::get('/history/message', '[email protected]');
});

Next, you can test the request on the front end. Since there is no message yet, the data returned is empty:

In the next tutorial, we will write the code for users to enter and exit the room on the back end of the Websocket server, and update the online user information after entering and exiting, and push it to all online users in 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)

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 (P9): Realizing the Chat Function of Customer Service Robots

Earlier we have completed the basic components of user authentication and Websocket server. Next, we formally started to build the core functions of the chat room. First, we will implement the robot chat function. The robot chat backend calls a third-party robot interface. Communication is not based on a Websocket server, but based on the HTTP protocol.

Before we start, we still need to adjust the front-end Muse UI components. Because the previous interface was built based on the Muse UI 2.0 version, there are some problems with the current component display.

Chat room related Muse UI component adjustment

Entry page

The first is the chat room entry page resources/js/pages/Loan.vue. In Muse UI 3.0, mu-list the implementation of the list component has been adjusted, so we need to adjust the template code of this Vue component as follows:

<template>
    <div class="hello">
        <div>
            <mu-list>
                <mu-sub-header>Recent chat history</mu-sub-header>
                <mu-list-item avatar button :ripple="false" @click="chatwindow('1')">
                    <mu-list-item-action>
                        <mu-avatar>
                            <img :src="house1">
                        </mu-avatar>
                    </mu-list-item-action>
                    <mu-list-item-title>Chat room 1</mu-list-item-title>
                    <mu-list-item-action>
                        <mu-icon value="chat_bubble"></mu-icon>
                    </mu-list-item-action>
                </mu-list-item>
                <mu-list-item avatar button :ripple="false" @click="chatwindow('2')">
                    <mu-list-item-action>
                        <mu-avatar>
                            <img :src="house2">
                        </mu-avatar>
                    </mu-list-item-action>
                    <mu-list-item-title>Chat room 2</mu-list-item-title>
                    <mu-list-item-action>
                        <mu-icon value="chat_bubble"></mu-icon>
                    </mu-list-item-action>
                </mu-list-item>
            </mu-list>
            <mu-divider></mu-divider>
            <mu-list>
                <mu-sub-header>Customer service</mu-sub-header>
                <mu-list-item avatar button :ripple="false" @click="chatRobot()">
                    <mu-list-item-action>
                        <mu-avatar>
                            <img :src="robot">
                        </mu-avatar>
                    </mu-list-item-action>
                    <mu-list-item-title>Customer Service Dabai (WeChat group, author contact information, find me)</mu-list-item-title>
                    <mu-list-item-action>
                        <mu-icon value="chat_bubble"></mu-icon>
                    </mu-list-item-action>
                </mu-list-item>
            </mu-list>
        </div>
    </div>
</template>

After the adjustment is complete, run npm run dev recompiled front-end resources, then visit the chat room home http://webchats.test/#/, you can see a list of chat rooms and customer service robot components can be displayed on:

Room Page

Next, let's chat room page components resources/js/pages/Chat.vue and resources/js/pages/Robot.vue that the two are used in page templates mu-icon-button and mu-raised-button button components, while in Muse UI 3.0, these two components to merge mu-button, so we need to adjust these two pages Muse UI button component code.

Revision of the chat room page resources/js/pages/Chat.vue template associated button assembly code is as follows:

<div class="title">
    <mu-appbar title="Title">
        <mu-button icon slot="left" @click="goback">
            <mu-icon value="chevron_left"></mu-icon>
        </mu-button>
      <div class="center">
        to chat with({{Object.keys(getUsers).length}})
      </div>
        <mu-button icon slot="right" @click="openSimpleDialog">
            <mu-icon value="people"></mu-icon>
        </mu-button>
    </mu-appbar>
</div>

...

<mu-button class="demo-raised-button" primary @click="submess">Send</mu-button>

Adjust the robot chat room page resources/js/pages/Robot.vue template associated button assembly code is as follows:

<div class="title">
        <mu-appbar title="Title">
            <mu-button icon slot="left" @click="goback">
                <mu-icon value="chevron_left"></mu-icon>
            </mu-button>
            <div class="center">
            </div>
            <mu-button icon slot="right">
                <mu-icon value="expand_more"></mu-icon>
            </mu-button>
        </mu-appbar>
</div>

...

<mu-button class="demo-raised-button" primary @click="sendmessage">Send</mu-button>

Re-run npm run dev the compiler front-end resources, from chat rooms gateway interface into the general chat rooms and chat room interface robot respectively, it can be seen, page components are properly rendered out:

The page component rendering problem is solved. Next, let's implement the robot chat function.

Robot chat function implementation

Turing Robot Interface

Here we choose the API interface provided by Turing Robot to implement the function of customer service robot. This API interface is http://www.tuling123.com/openapi/api. For detailed access guide of this interface, please refer to the official document introduction: http://www.tuling123.com/help/h_cent_webapi.jhtml , to access this API, you need to register an account on the Turing Robot website, create a robot, and then obtain the API KEY in the specific robot project, and bring this KEY every time you request an interface, otherwise the request will fail.

Note: The default number of test interface calls is very limited. You can choose to increase the number of times through personal certification.

For administrative purposes, we will define an API interface configuration config/services.php in which:

'turingapi' => [
    'url' => 'http://www.tuling123.com/openapi/api',
    'key' => env('ROBOT_KEY')
]

API KEY is sensitive data, the configuration to .env the ROBOT_KEY configuration items inside.

Define Laravel backend API interface

The process of the chat function of the customer service robot is like this:

  • The user initiates a conversation on the customer service chat room interface;
  • After Laravel receives the request in the background, it organizes the data format required by the Turing Robot API interface and initiates a request to the Turing API interface;
  • After receiving the data returned by the Turing API interface, the Laravel background organizes the data format that the front end can parse and send the response information to the user.

As you can see, the Laravel background interface is an intermediary role. In this interface, you need to send HTTP requests and process responses. Here we introduce the Guzzle library as a PHP HTTP client component. Before introducing, download it through Composer:

composer require guzzlehttp/guzzle

After the download is complete, in routes/api.php the definition robot(after user authentication is required to access) route is as follows:

Route::middleware('auth:api')->group(function () {
    Route::get('/user', function (Request $request) {
        return $request->user();
    });
    Route::get('/robot', function (Request $request) {
        $info = $request->input('info');
        $userid = $request->input('id');
        $key = config('services.robot.key');
        $url = config('services.robot.api');
        $client = new \GuzzleHttp\Client();
        $response = $client->request('POST', $url, [
            'json' => compact("info", "userid", "key")
        ]);
        return response()->json(['data' => $response->getBody()]);
    });
});

The business logic is very simple, it is to receive the front-end request parameters, and forward these parameters plus API KEY as request data to the Turing Robot Interface again, and finally forward the information returned by the Turing Robot Interface to the front-end user.

In this way, the back-end interface is written.

Front-end request parameter adjustment

Finally, we open the front of the robot chat interface file resources/js/pages/Robot.vue, where we need to click the send button to send information on the user, put api_token request field, so that the rear end through auth:api user authentication is completed automatically based middleware API interface:

methods: {
    ...
    sendmessage() {
        ...
        const id = this.userid;
        const api_token = this.auth_token;
        const data = {
            info,
            id,
            api_token
        };
        ...
    }
},
computed: {
    ...mapGetters(["getRobotMsg"]),
    ...mapState({
        userid: state => state.userInfo.userid,
        src: state => state.userInfo.src,
        auth_token: state => state.userInfo.token,
    })
},

The code tuning is complete, run npm run dev recompiled front-end resources, then we can test the robot chat.

Test customer service bot chat

Open the chat interface of the customer service robot http://webchats.test/#/robot and send the message as follows:

It can be seen white customer service can answer the message we are sending a very intelligent, the number of calls to test the API is very limited, if you see 暂不支持此类对话 this answer, most likely the number of requests has reached the limit, you can select individual authentication methods will be called every day The limit is increased to 100 times.