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类(可选)
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;