天天看點

SpringBoot + mybatis-plus整合webscoket

webscoket原理:請參考WebSocket的實作原理

webscoket一開始我隻是簡單會用,但是我覺得掌握webscoket原理是很有必要,他會加深我們對計網的了解。

一、永恒第一步:導入pom依賴

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
   <groupId>org.webjars</groupId>
   <artifactId>stomp-websocket</artifactId>
   <version>2.3.3</version>
</dependency>
           

二、添加配置檔案

對應部分的說明都寫在了注釋裡面

package com.easy.config;

import com.mbyte.easy.webscoket.consts.GlobalConsts;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;

/**
 * @Author zte
 * @Description Webscoket配置類
 * @Date 21:33 2019/5/6
 **/
@Configuration
@EnableWebSocketMessageBroker
@EnableCaching
@CrossOrigin("*")
public class WebSocketStompConfig implements WebSocketMessageBrokerConfigurer {
    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        /**
         * 配置消息代理
         * 啟動簡單Broker,消息的發送的位址符合配置的字首來的消息才發送到這個broker
         */
        config.enableSimpleBroker(GlobalConsts.TOPICPATH, GlobalConsts.P2PPUSHBASEPATH);
        config.setUserDestinationPrefix(GlobalConsts.P2PPUSHBASEPATH);
        config.setApplicationDestinationPrefixes(GlobalConsts.APP_PREFIX);
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        /**
         * 注冊 Stomp的端點
         * addEndpoint:添加STOMP協定的端點。這個HTTP URL是供WebSocket或SockJS用戶端通路的位址
         * setAllowedOrigins("*") 允許跨域
         * withSockJS:指定端點使用SockJS協定
         */
        registry.addEndpoint(GlobalConsts.ENDPOINT)
                .setAllowedOrigins("*")
                .withSockJS();
    }
}

           

如果需要跨域,則需要添加跨域設定

package com.easy.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;

import javax.annotation.Resource;

/**
 * @className: WebSocketConfig
 * @description:
 * @author: zte
 * @create: 2020-06-09 16:17
 **/
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {


    @Resource
    private MyHandShakeInterceptor handshake;

    @Resource
    private MyHandler handler;

    /**
     * 實作 WebSocketConfigurer 接口,重寫 registerWebSocketHandlers 方法,這是一個核心實作方法,配置 websocket 入口,允許通路的域、注冊 Handler、SockJs 支援和攔截器。
     * <p>
     * registry.addHandler()注冊和路由的功能,當用戶端發起 websocket 連接配接,把 /path 交給對應的 handler 處理,而不實作具體的業務邏輯,可以了解為收集和任務分發中心。
     * <p>
     * addInterceptors,顧名思義就是為 handler 添加攔截器,可以在調用 handler 前後加入我們自己的邏輯代碼。
     * <p>
     * setAllowedOrigins(String[] domains),允許指定的域名或 IP (含端口号)建立長連接配接,如果隻允許自家域名通路,這裡輕松設定。如果不限時使用”*”号,如果指定了域名,則必須要以 http 或 https 開頭。
     *
     */
    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        //部分 支援websocket 的通路連結,允許跨域
        registry.addHandler(handler, "/websocket").addInterceptors(handshake).setAllowedOrigins("*");
        //部分 不支援websocket的通路連結,允許跨域
//        registry.addHandler(handler, "/sockjs/echo").addInterceptors(handshake).setAllowedOrigins("*").withSockJS();
    }
}
           

三、WebScoket常量配置

package com.easy.webscoket.consts;

/**
 * @program: easy
 * @description: WebScoket常量配置
 * @author: zte
 * @create: 2019-05-06 10:11
 **/
public class GlobalConsts {
    public static final String TOPIC = "/topic/greetings";

    public static final String ENDPOINT = "/gs-guide-websocket";

    public static final String APP_PREFIX = "/app";

    public static final String HELLO_MAPPING = "/hello";

    //點對點消息推送位址字首
    public static final String P2PPUSHBASEPATH = "/user";
    //點對點消息推送位址字尾,最後的位址為/user/使用者識别碼/msg
    public static final String P2PPUSHPATH = "/msg";


    /**
     * @Description 接收消息位址
     **/
    public static final String RECEIVE_MAPPING = "/receive";



    /**
     * @Description 訂閱消息推送位址字首
     **/
    public static final String TOPICPATH = "/group";

    /**
     * 統一字首
     */
    public static final String URL_PREFIX = "/rest/";

}

           

四、controller層代碼

這裡包含了點對點聊天以及群聊的配置

package com.easy.webscoket.controller;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.mbyte.easy.common.web.AjaxResult;
import com.mbyte.easy.detailed_info_log.entity.DetailedInfoLog;
import com.mbyte.easy.talk_socket.entity.Message;
import com.mbyte.easy.talk_socket.service.IMessageService;
import com.mbyte.easy.webscoket.consts.GlobalConsts;
import com.mbyte.easy.webscoket.vo.ClientMessage;
import com.mbyte.easy.webscoket.vo.ServerMessage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.util.HtmlUtils;

import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import java.awt.event.MouseWheelEvent;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @program: easy
 * @description: webscoket測試controller
 * @author: zte
 * @create: 2019-05-06 10:26
 **/
@Controller
@RequestMapping("/audience/index")
public class GreetingController {

    @Autowired
    private SimpMessagingTemplate template;

    @Autowired
    private IMessageService messageService;

    /**
     * 線上使用者的Map集合,key:使用者名,value:Session對象
     */
    private static Map<String, Session> sessionMap = new ConcurrentHashMap<>();

    /**
     * 連接配接建立成功調用的方法
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("username") String username) {
        //在webSocketMap新增上線使用者
        sessionMap.put(username, session);

        System.out.println(username);

    }


    @RequestMapping
    public String index(Model model) {

        return "webscoket/greeting";
    }

    @RequestMapping("/index2")
    public String index2(Model model) {

        return "webscoket/greeting2";
    }

    @MessageMapping(GlobalConsts.HELLO_MAPPING)
    @SendTo(GlobalConsts.TOPIC)
    public ServerMessage greeting(ClientMessage message) throws Exception {
        // 模拟延時,以便測試用戶端是否在異步工作
//        Thread.sleep(1000);
        template.convertAndSendToUser(message.getId() + "", GlobalConsts.P2PPUSHPATH, JSON.toJSON(new ServerMessage("Hello, " + HtmlUtils.htmlEscape(message.getName()) + "!")));
        return new ServerMessage("Hello, " + HtmlUtils.htmlEscape(message.getName()) + "!");
    }

    /**
     * 群聊
     *
     * @param message WebMessage
     */
    @MessageMapping("/group")
    public void group(Message message) {
        messageService.save(message);
        template.convertAndSend("/group/"  + message.getGroupPrefix(), JSON.toJSON(message));
    }

    @GetMapping("/groupGetMessage")
    public AjaxResult groupGetMessage(String groupPrefix,Integer groupFlag) {
        List<Message> messages = messageService.list(new LambdaQueryWrapper<Message>().eq(Message::getGroupPrefix,groupPrefix).eq(Message::getGroupFlag,groupFlag).orderByDesc(Message::getSendTime));
        return AjaxResult.success(messages);
    }

}

           

擷取群聊消息(曆史消息)的接口

package com.mbyte.easy.rest.message;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.mbyte.easy.talk_socket.entity.Message;
import com.mbyte.easy.talk_socket.service.IMessageService;
import com.mbyte.easy.common.controller.BaseController;
import com.mbyte.easy.common.web.AjaxResult;
import com.mbyte.easy.util.PageInfo;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;

/**
* <p>
* 前端控制器
* </p>
* @author zte
* @since 2019-03-11
*/
@Api(tags = "讨論聊天記錄接口")
@RestController
@RequestMapping("rest/message")
public class RestMessageController extends BaseController  {

    @Autowired
    private IMessageService messageService;

    /**
     * 存儲聊天資料頁面
     * @return
     */
    @ApiOperation(value = "聊天記錄添加接口")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "userId", value = "發送人", required = true),
            @ApiImplicitParam(name = "content", value = "聊天内容", required = true),
            @ApiImplicitParam(name = "groupPrefix", value = "房間号", required = true),
            @ApiImplicitParam(name = "msgType", value = "消息類型", required = true),
            @ApiImplicitParam(name = "groupFlag", value = "房間類型", required = true),
    })
    @GetMapping("addSocketBefore")
    public AjaxResult addSocketBefore(String userId,String content,String groupPrefix,Integer msgType,Integer groupFlag){
        Message message = new Message();
        message.setFromUser(userId);
        message.setContent(content);
        message.setGroupPrefix(groupPrefix);
        message.setGroupFlag(groupFlag);
        message.setMsgType(msgType);
        return toAjax(messageService.save(message));
    }
    @ApiOperation(value = "聊天曆史記錄擷取接口")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "groupPrefix", value = "房間号", required = true),
            @ApiImplicitParam(name = "groupFlag", value = "房間類型", required = true),
    })
    @GetMapping("/groupGetMessage")
    public AjaxResult groupGetMessage(String groupPrefix,Integer groupFlag) {
        List<Message> messages = messageService.list(new LambdaQueryWrapper<Message>().eq(Message::getGroupPrefix,groupPrefix).eq(Message::getGroupFlag,groupFlag).orderByAsc(Message::getSendTime));
        return AjaxResult.success(messages);
    }

           

五、定義實體類

package com.easy.talk_socket.entity;

import com.baomidou.mybatisplus.annotation.TableName;
import com.mbyte.easy.common.entity.BaseEntity;
import java.time.LocalDateTime;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;

/**
 * <p>
 * 
 * </p>
 *
 * @author zte
 * @since 2020-08-28
 */
@Data
@EqualsAndHashCode(callSuper = true)
@Accessors(chain = true)
@TableName("t_message")
public class Message extends BaseEntity {

    private static final long serialVersionUID = 1L;

    /**
     * 發送人
     */
    private String fromUser;

    /**
     * 接收人
     */
    private String toUser;

    /**
     * 消息内容
     */
    private String content;

    /**
     * 消息類型:1 文字  ;2語音; 3圖檔; 4 視訊 5 禮物 6邀請入隊 預設為1
     */
    private Integer msgType;

    /**
     * 是否已讀, 0:未讀, 1:已讀
     */
    private Integer readFlag;

    /**
     *  正常為 0  删除為1
     */
    private Integer deleteFlag;

    /**
     * 發送時間
     */
    private LocalDateTime sendTime;

    /**
     * 語音時長
     */
    private Integer voiceTime;

    /**
     * 發送人昵稱
     */
    private String fromNick;

    /**
     * 接受人昵稱
     */
    private String toNick;

    /**
     * 組名稱   如:群聊編号, 直播編号, 話題編号
     */
    private String groupPrefix;

    /**
     * 組标記,0:是私聊 1:是多人聊天; 3是臨時會話;4:是客服聊天
     */
    private Integer groupFlag;

    /**
     * 發送人頭像
     */
    private String fromMsg;

    /**
     * 接受人頭像
     */
    private String toMsg;

    /**
     * 發送人的大v圖檔
     */
    private String fromMsgSignV;

    /**
     * 發送人的皇冠圖檔
     */
    private String fromMsgSignAnCrown;

    /**
     * 接受人大v圖檔
     */
    private String toMsgSignV;

    /**
     * 接受人皇冠圖檔
     */
    private String toMsgSignAnCrown;

    /**
     * 最佳吐槽   1為最佳吐槽 
     */
    private Integer bestBlowing;

    /**
     * 使用者類型  普通使用者:1;客服 2;會員:3 ;超級會員 4 ;直播人:5;  
     */
    private Integer userType;

    /**
     * 平台辨別
     */
    private String platCode;


}

           

六、定義Vo類(可選)

SpringBoot + mybatis-plus整合webscoket
package com.easy.webscoket.vo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @program: easy
 * @description: 用戶端發過來的消息
 * @author: zte
 * @create: 2019-05-06 10:22
 **/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ClientMessage {
    private int id;
    private String name;

}

           
package com.easy.webscoket.vo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @program: easy
 * @description: 服務端傳回消息
 * @author: zte
 * @create: 2019-05-06 10:25
 **/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ServerMessage {
    private String content;
}

           

七、點對點聊天(html)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="/webjars/jquery/jquery.min.js"></script>
    <script src="/webjars/sockjs-client/sockjs.min.js"></script>
    <script src="/webjars/stomp-websocket/stomp.min.js"></script>
</head>
<body onload="disconnect()">
<div>
    <div>
        <button id="connect" onclick="connect();">連接配接</button>
        <button id="disconnect" disabled="disabled" onclick="disconnect();">斷開連接配接</button>
    </div>
    <div id="conversationDiv">
        <label>輸入你的名字</label><input type="text" id="name" />
        <button id="sendName" onclick="sendName();">發送</button>
        <p id="response"></p>
    </div>
</div>

<script type="text/javascript">
    var stompClient = null;

    function setConnected(connected) {
        document.getElementById('connect').disabled = connected;
        document.getElementById('disconnect').disabled = !connected;
        document.getElementById('conversationDiv').style.visibility = connected ? 'visible' : 'hidden';
        $('#response').html();
    }

    function connect() {
        // websocket的連接配接位址,此值等于WebSocketMessageBrokerConfigurer中registry.addEndpoint("/websocket-simple").withSockJS()配置的位址
        var socket = new SockJS('/gs-guide-websocket');
        stompClient = Stomp.over(socket);
        stompClient.connect({}, function(frame) {
            setConnected(true);
            console.log('Connected: ' + frame);
            // 用戶端訂閱消息的目的位址:此值BroadcastCtl中被@SendTo("/topic/getResponse")注解的裡配置的值
            stompClient.subscribe('/user/' + 123 + '/msg', function(respnose){
                showResponse(JSON.parse(respnose.body).content);
            });
        });
    }


    function disconnect() {
        if (stompClient != null) {
            stompClient.disconnect();
        }
        setConnected(false);
        console.log("Disconnected");
    }

    function sendName() {
        var name = $('#name').val();
        // 用戶端消息發送的目的:服務端使用BroadcastCtl中@MessageMapping("/receive")注解的方法來處理發送過來的消息
        stompClient.send("/app/hello", {}, JSON.stringify({ 'id': 321, 'name': name }));
    }

    function showResponse(message) {
        var response = $("#response");
        response.html(message + "\r\n" + response.html());
    }
</script>
</body>
</html>
           

八、群聊(html)

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">

<head>
    <title>群聊</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=0.7, user-scalable=no, shrink-to-fit=no">
    <link rel="stylesheet" href="/webrtc/css/bootstrap-material-design.min.css">
    <script src="/audience/js/sockjs.min.js" defer></script>
    <script src="/audience/js/stomp.min.js" defer></script>
</head>
<body>
<!-- 使用者名 房間号 登入按鈕-->
<div class="col-div" style="width: 320px;align-items: center;">
    <div class="form-group bmd-form-group is-filled"
        style="width: 100%; height: 80px;align-items: center;">
        <label for="userId" class="bmd-label-floating">使用者名:</label>
        <input th:value="${traName}" type="text" class="form-control" name="userId" id="userId"
            maxlength="18">
    </div>
    <div class="form-group bmd-form-group is-filled"
        style="width: 100%; height: 80px;align-items: center;display: none;">
        <label for="roomId" class="bmd-label-floating">房間号:</label>
        <input type="text" th:value="${roomId}" class="form-control" name="roomId" id="roomId"
            maxlength="18">
    </div>
    <div style="height: 40px"></div>
    <!-- 登入 -->
    <button id="login-btn" type="button" class="btn btn-raised btn-primary"
        style="width: 100%; height: 40px" onclick="connect($('#roomId').val(),$('#userId').val());">進入房間
        <div class="ripple-container"></div>
    </button>
</div>
<div style="width: 20%;height: 100%;align-items: initial !important;background: #fff;">
    <div class="layui-tab layui-tab-brief" lay-filter="docDemoTabBrief"
        style="width: 100%;display: block;margin: 0;">
        <ul class="layui-tab-title" style="display: flex;justify-content: center"
            lay-filter="queSearch">
            <li class="layui-this">簡介</li>
            <li>讨論區</li>
        </ul>
        <div class="layui-tab-content" style="display: block;width: 100%;">
            <div th:utext="${introduction}" class="layui-tab-item layui-show"
                style="color: #333;line-height: 20px;font-size: 12px;"></div>
            <div class="layui-tab-item" style="width: 100%;">
                <div class="commentCon">
                    <div id="response"></div>
                </div>
                <div class="inputbottom">
                    <input type="text" id="yourMessage" placeholder="說點兒什麼" maxlength="200">
                    <button onclick="sendAll();" id="sendAll" type="button"
                        class="layui-btn layui-btn-normal"
                        style="background-color: #38AAFF !important;border-radius: 5px">發送</button>
                </div>
                <!--                                <button type="button" class="layui-btn layui-btn-danger" style="background-color: #FF5722  !important;float: right;width: 45%;border-radius: 10px">重置</button>-->
            </div>
        </div>
    </div>
</div>
<script src="/webrtc/js/jquery-3.2.1.min.js"></script>
<script src="/layui/layui.all.js"></script>
</script>
    <script>
        $(document).ready(function () {
            $('body').bootstrapMaterialDesign();
            // $('#login-btn').trigger("click");
        });
    </script>
  
    <script type="text/javascript">
        layui.use('element', function () {
            var $ = layui.jquery
                , element = layui.element;
            $('.layui-tab-title').on('click', function (title) {
                if (title.toElement.textContent == "讨論區") {
                    setTimeout(() => {
                        selle();
                    }, 500)
                }
            });
        })

        function getGroupGetMessage() {
            $.ajax({
                url: '/rest/message/groupGetMessage',
                type: 'GET',
                data: {
                    groupFlag: 1,
                    groupPrefix: $('#roomId').val()
                },
                success: function (data) {
                    // console.log(data);
                    for (let i = 0; i < data.data.length; i++) {
                        let time = changTime(data.data[i].sendTime);
                        $("#response").append(
                            '<div style="display:block">' +
                            '<div style="justify-content: space-between;">' +
                            '<div class="nickname"><span>' + data.data[i].fromUser + '</span></div>' +
                            '<div class="nickname" style="text-align: right;padding-right: 10px;display: block">' + time + '</div>' +
                            '</div>' +
                            '<div class="content"><span >' + data.data[i].content + '</span></div>' +
                            '</div>'
                        );
                    }
                },
                error: function () {
                }
            })
        }
        getGroupGetMessage();

        function changTime(time, format = 'M-D h:m') {
            let timestamp;
            if (!time) {
                return false
            }
            time = time.replace("T", " ");
            timestamp = new Date(time.replace(/-/g, '/'));
            const formatNumber = n => {
                n = n.toString()
                return n[1] ? n : '0' + n
            }
            const formateArr = ['Y', 'M', 'D', 'h', 'm', 's'];
            let returnArr = [];
            let date = new Date(timestamp);
            let year = date.getFullYear()
            let month = date.getMonth() + 1
            let day = date.getDate()
            let hour = date.getHours()
            let minute = date.getMinutes()
            let second = date.getSeconds()
            returnArr.push(year, month, day, hour, minute, second);
            returnArr = returnArr.map(formatNumber);
            for (var i in returnArr) {
                format = format.replace(formateArr[i], returnArr[i]);
            }
            return format;
        }

        var stompClient = null;

        function setConnected(connected) {
            $('#response').html();
        }

        function connect(a, b) {
           // websocket的連接配接位址,此值等于WebSocketMessageBrokerConfigurer中registry.addEndpoint("/websocket-simple").withSockJS()配置的位址
            var socket = new SockJS('/gs-guide-websocket');
            var userId = a;
            console.log("userId:" + userId);
            // sessionStorage.setItem("token", data.data.token);
            stompClient = Stomp.over(socket);
            stompClient.connect({}, function (frame) {
                setConnected(true);
                console.log('Connected: ' + frame);
                // alert(123)
                // 訂閱 /group/to-all 實作公告
                stompClient.subscribe('/group/' + userId + '', function (respnose) {
                    var id = JSON.parse(respnose.body).fromUser;
                    var groupFlag = JSON.parse(respnose.body).groupFlag;
                    if (id != b && groupFlag == 1) {
                        showResponse(JSON.parse(respnose.body).content, JSON.parse(respnose.body).fromUser, 2);
                    }
                });
            })
        }
        function sendAll() {
            var userId = $("#userId").val();
            var content = $('#yourMessage').val();
            con = content.replace(/(^\s+)|(\s+$)/g, "")
            if (con == '') {
                layer.msg("發送内容不能為空");
            } else {
                // 用戶端消息發送的目的:服務端使用BroadcastCtl中@MessageMapping("/receive")注解的方法來處理發送過來的消息
                stompClient.send("/app/group", {}, JSON.stringify({
                    'groupFlag': 1,
                    'groupPrefix': $("#roomId").val(),
                    'fromUser': userId,
                    'content': content,
                    'msgType': 1
                }));
                showResponse(content, userId, 1);
            }
        }

        function num(num){
            if(num < 10){
                num = '0' + num;
            }
            return num
        }

        function showResponse(content, userId, type) {
            var response = $("#response");
            var length = content.length * 15 + 'px';
            // if (type == 1){
            let time = num(new Date().getMonth() + 1) + '-' + num(new Date().getDate()) + ' ' + num(new Date().getHours()) + ':' + num(new Date().getMinutes());

            let data = '<div style="display:block">' +
                '<div style="justify-content: space-between;">' +
                '<div class="nickname"><span>' + userId + '</span></div>' +
                '<div class="nickname"  style="text-align: right;padding-right: 10px;display: block">' + time + '</div>' +
                '</div>' +
                '<div class="content"><span >' + content + '</span></div>' +
                '</div>'
            $("#response").append(data);          
            if (type == 1) {
                $('#yourMessage').val('');
            }
            selle();
        }

        function selle() {
            var showContent = $(".commentCon");
            showContent[0].scrollTop = showContent[0].scrollHeight;
        }

    </script>
    <script>

        $("#invite").bind("click", function () {
            var id = $("#getRoomId").val();
           layer.confirm('/traine/traine/audience?id=' + id, {
                btn: ['複制', '确定'] //按鈕
            }, function () {
                $('.layui-layer-btn0').attr('data-clipboard-text', 'https://medical.yinqianshan.org/traine/traine/audience?id=' + id);
                var clipboard = new ClipboardJS('.layui-layer-btn0');
                clipboard.on('success', function (e) {
                    //成功後執行這裡
                    layer.msg('複制成功');
                });
                clipboard.on('error', function (e) {
                    console.log(e);
                });
            }, function () {
            });
        });

        document.onkeydown = function (event) {
            var e = event || window.event || arguments.callee.caller.arguments[0];
            if (e && e.keyCode == 13) {
                sendAll();
            }
        };
    </script>
</body>
</html>
           

九、對應sql

/*
 Navicat Premium Data Transfer

 Source Server         : hfky
 Source Server Type    : MySQL
 Source Server Version : 50723
 Source Host           : 49.233.66.170:3306
 Source Schema         : hfky

 Target Server Type    : MySQL
 Target Server Version : 50723
 File Encoding         : 65001

 Date: 24/09/2020 17:10:39
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for t_message
-- ----------------------------
DROP TABLE IF EXISTS `t_message`;
CREATE TABLE `t_message`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `from_user` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '發送人',
  `to_user` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '接收人',
  `content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '消息内容',
  `msg_type` int(4) NULL DEFAULT 1 COMMENT '消息類型:1 文字  ;2圖檔; 預設為1',
  `read_flag` int(2) NULL DEFAULT 0 COMMENT '是否已讀, 0:未讀, 1:已讀',
  `delete_flag` int(2) NULL DEFAULT 0 COMMENT ' 正常為 0  删除為1',
  `send_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '發送時間',
  `voice_time` int(4) NULL DEFAULT NULL COMMENT '語音時長',
  `from_nick` varchar(80) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '發送人昵稱',
  `to_nick` varchar(80) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '接受人昵稱',
  `group_prefix` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '組名稱   如:群聊編号, 直播編号, 話題編号',
  `group_flag` int(2) NULL DEFAULT 0 COMMENT '組标記,1:教育訓練讨論;2:會診直播',
  `from_msg` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '發送人頭像',
  `to_msg` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '接受人頭像',
  `from_msg_sign_v` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '發送人的大v圖檔',
  `from_msg_sign_an_crown` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '發送人的皇冠圖檔',
  `to_msg_sign_v` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '接受人大v圖檔',
  `to_msg_sign_an_crown` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '接受人皇冠圖檔',
  `best_blowing` int(11) NULL DEFAULT 0 COMMENT '最佳吐槽   1為最佳吐槽 ',
  `user_type` int(11) NULL DEFAULT NULL COMMENT '使用者類型  普通使用者:1;客服 2;會員:3 ;超級會員 4 ;直播人:5;  ',
  `plat_code` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '平台辨別',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4044 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = COMPACT;

SET FOREIGN_KEY_CHECKS = 1;