文章目录
- 那么上篇讲到spring集成websocket的简单使用,这篇则将使用layim框架实现websocket即时通讯功能
一、layim即时通讯的认识
官方文档 https://www.layui.com/doc/modules/layim.html
由于layim是官方收费的,这里不便附上layim源码
- 准备阶段
- 集成ssm框架,spring集成websocket
- 引入layim
1.1、layim基础配置
1.1.1、初始化(init)
需要从后台去找好友分组数据以及群的信息
需要返回的json数据格式为:
{
"code": 0
,"msg": ""
,"data": {
"mine": {
"username": "纸飞机"
,"id": "100000"
,"status": "online"
,"sign": "在深邃的编码世界,做一枚轻盈的纸飞机"
,"avatar": "http://cdn.firstlinkapp.com/upload/2016_6/1465575923433_33812.jpg"
}
,"friend": [{
"groupname": "我心中的女神"
,"id": 3
,"online": 1
,"list": [{
"username": "林心如"
,"id": "76543"
,"avatar": "http://tp3.sinaimg.cn/1223762662/180/5741707953/0"
,"sign": "我爱贤心"
}]
}
}
- 创建ChatInfo、FriendInfo、GroupInfo类封装需要的json数据
public class ChatInfo {
/**
* 响应码
*/
private Integer code;
/**
* 响应消息
*/
private String msg;
/**
* 数据集合:接收mine(我的信息),friend(好友的信息)
*/
private Map data = new HashMap();
....
getter/setter
}
/**
* 好友列表信息
*/
public class FriendInfo {
//群名
private String groupname;
//群id
private Integer id;
//list集合接收多个群
private List<EmpInfo> list;
...
getter/setter
}
/**
* /群组列表信息
*/
public class GroupInfo {
//群id(部门id)
private Integer id;
//群名
private String groupname;
//群头像
private String avatar;
...
getter/setter
}
- controller层返回json数据
@RequestMapping("/findChatInfo")
@ResponseBody
public ChatInfo findChatInfo(Integer currentUserId) {
List<FriendInfo> friendInfos = friendInfoService.findFriendInfos();
EmpInfo mine = empInfoService.findOne(currentUserId);
ChatInfo chatInfo = new ChatInfo();
chatInfo.setCode(0);
chatInfo.setMsg("ok");
Map map = chatInfo.getData();
map.put("mine", mine);
map.put("friend", friendInfos);
List<GroupInfo> list = Arrays.asList(groupInfoService.findGroupInfo(currentUserId));
map.put("group", list);
return chatInfo;
}
- 显示结果为
1.1.2、群员接口(members)
需要从后台根据群id来查找群成员
返回的json数据格式如下
{
"code": 0
,"msg": ""
,"data": {
"owner": {
"username": "贤心"
,"id": "100001"
,"avatar": "http://tp1.sinaimg.cn/1571889140/180/40030060651/1"
,"sign": "这些都是测试数据,实际使用请严格按照该格式返回"
}
,"members": 12
,"list": [{
"username": "贤心"
,"id": "100001"
,"avatar": "http://tp1.sinaimg.cn/1571889140/180/40030060651/1"
,"sign": "这些都是测试数据,实际使用请严格按照该格式返回"
}]
}
}
- 创建EmpInfo类封装群成员信息封装需要的json数据
/**
* 成员信息
*/
public class EmpInfo{
//消息的来源id
private Integer id;
//消息接收方的id
private Integer toId;
private String username;
private String status;
//签名
private String sign;
//头像
private String avatar;
//登陆状态
private Integer isLogin;
//消息内容
private String content;
//消息的来源类型
private String type;
//是否是本人
private Boolean mine = false;
//发送消息的时间
private Date timestamp = new Date();
//历史消息时间
private Date historyTime = new Date();
...
getter/setter
}
- controller层返回json数据
@RequestMapping("/getMembers")
@ResponseBody
public String getMembers(Integer id) {
PageTable<Map<String, Object>> pageTable = new PageTable<Map<String, Object>>();
//查询当前用户所在群的所有人员
Map groupEmps = groupInfoService.findGroupEmps(id);
//设置code
pageTable.setCode(WebConstant.PAGESUCCESSCODE);
//设置msg
pageTable.setMsg("获取成功");
//设置data
pageTable.setData(groupEmps);
//转成字符串形式返回给前台
return responseAPI.getJsonString(pageTable);
}
- findGroupEmps方法代码
@Override
public Map findGroupEmps(Integer groupId) {
Map map = new HashMap();
map.put("owner", "");
//查询当前用户所在群成员的人数--value类型 Integer
map.put("members", groupInfoDao.findGroupEmpsNum(groupId));
//查询当前用户所在群的所有人员--value类型 List<EmpInfo>
map.put("list", groupInfoDao.findGroupEmps(groupId));
return map;
}
- 效果图如下
1.1.3、layim基础配置代码
layim.config({
//初始化接口
init: {
url: '/chatController/findChatInfo?currentUserId=' +${currentUserId} //从后台去找好友分组数据
, data: {}
}
, min: true
//查看群员接口
, members: {
url: '/chatController/getMembers'
, data: {}
}
//上传图片接口
, uploadImage: {
url: '/upload/image' //(返回的数据格式见下文)
, type: '' //默认post
}
//上传文件接口
, uploadFile: {
url: '/upload/file' //(返回的数据格式见下文)
, type: '' //默认post
}
, isAudio: true //开启聊天工具栏音频
, isVideo: true //开启聊天工具栏视频
//扩展工具栏
, tool: [{
alias: 'code'
, title: '代码'
, icon: ''
}]
//,brief: true //是否简约模式(若开启则不显示主面板)
, title: xxx //自定义主面板最小化时的标题
//,right: '100px' //主面板相对浏览器右侧距离
//,minRight: '90px' //聊天面板最小化时相对浏览器右侧距离
, initSkin: '5.jpg' //1-5 设置初始背景
//,skin: ['aaa.jpg'] //新增皮肤
// ,isfriend: false //是否开启好友
//,isgroup: false //是否开启群组
//,min: true //是否始终最小化主面板,默认false
, notice: true //是否开启桌面消息提醒,默认false
//,voice: false //声音提醒,默认开启,声音文件为:default.mp3
, msgbox: layui.cache.dir + 'css/modules/layim/html/msgbox.html' //消息盒子页面地址,若不开启,剔除该项即可
, find: layui.cache.dir + 'css/modules/layim/html/find.html' //发现页面地址,若不开启,剔除该项即可
, chatLog: "/msg/showHistoryMsg" //聊天记录页面地址,若不开启,剔除该项即可
});
二、WebSocket搭建
与前篇基本相同
2.1、创建消息处理类
//消息处理类
@Component
public class MyHandler extends TextWebSocketHandler {
// 在线用户列表
public static final Map<Integer, WebSocketSession> userSocketSessionMap = new HashMap<Integer, WebSocketSession>();
// 用户连接成功 就被调用
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
//获取传来的登录用户的id
String idstr = session.getUri().toString().split("=")[1];
int id = Integer.parseInt(idstr);
System.err.println("用户连接成功");
//保存对应的WebSocketSession
userSocketSessionMap.put(id, session);
}
// 消息处理方法
@Override
public void handleTextMessage(WebSocketSession session, TextMessage message) {
try {
ObjectMapper objectMapper = new ObjectMapper();
EmpInfo friendInfo = objectMapper.readValue(message.getPayload(), EmpInfo.class);
//session.sendMessage(message);//发送给当前人
if(friendInfo.getType().equals("friend")) {
sendMessageToUser(message,friendInfo );
}else if(friendInfo.getType().equals("group")) {
sendMessagesToUsers(message,friendInfo);//给所有的用户发送消息
}
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 给所有的用户发送消息
*/
public void sendMessagesToUsers(TextMessage message,EmpInfo friendInfo) {
Set<Integer> clientIds = userSocketSessionMap.keySet();
WebSocketSession session = null;
for (Integer clientId : clientIds) {
session = userSocketSessionMap.get(clientId);
System.out.println(clientId);
try {
if(session.isOpen()&&!friendInfo.getId().equals(clientId)){
session.sendMessage(message);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 给所有的用户发送消息
*/
public void sendMessageToUser(TextMessage message,EmpInfo friendInfo) throws IOException {
Set<Integer> clientIds = userSocketSessionMap.keySet();
WebSocketSession session = null;
for (Integer clientId : clientIds) {
session = userSocketSessionMap.get(clientId);
System.out.println(session);
try {
if(session.isOpen()&&friendInfo.getToId().equals(clientId)){
System.out.println("回复消息");
session.sendMessage(message);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
//用户退出后的处理,退出之后,要将用户信息从websocket的session中remove掉,这样用户就处于离线状态了,也不会占用系统资源
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) {
try {
if(session.isOpen()){
session.close();
}
userSocketSessionMap.remove(session.getId());
System.out.println("退出系统");
}catch (Exception e){
System.out.println("用户非正常关闭");
}
}
}
2.2、webSocket配置类
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(myHandler(), "/myHandler");
}
@Bean
public WebSocketHandler myHandler() {
return new MyHandler();
}
}
2.3、前台jsp页面建立websocket连接
var socket = null;
//连接websocket的ip地址
var ip = "输入你的ip地址";//比如localhost
//动态修改查
var im = {
init: function () {
if ('WebSocket' in window) {
//var socketUrl = 'ws://localhost:80/myHandler?myid=' +${currentUserId};
var socketUrl = 'ws://'+ip+'/myHandler?myid='+${currentUserId};
socket = new WebSocket(socketUrl);
im.startListener();
} else {
alert('当前浏览器不支持WebSocket功能,请更换浏览器访问。');
}
},
startListener: function () {
if (socket) {
// 连接发生错误的回调方法
socket.onerror = function () {
console.log("通讯连接失败!");
};
// 连接成功建立的回调方法
socket.onopen = function (event) {
console.log("通讯连接成功");
}
// 接收到消息的回调方法
socket.onmessage = function (event) {
console.log("通讯接收到消息");
im.handleMessage(event.data);
}
// 连接关闭的回调方法
socket.onclose = function () {
console.log("通讯关闭连接!!");
}
}
},
handleMessage: function (msg) {
msg = JSON.parse(msg)
//如果是群消息,转换一下id,返回群id的的字段必须是id
if (msg.type == "group") {
var temId = msg.id;
msg.id = msg.toId;
msg.toId = temId;
}
//console.log(msg)
layim.getMessage(msg);
}
};
im.init();
四、附上完整jsp代码
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>LayIM 3.x PC版本地演示</title>
<link rel="stylesheet" href="/lib/layMI/css/layui.css">
<style>
</style>
</head>
<body style="margin:0; padding:0;">
<iframe id="barrage" src="/barrageController/showBarrage?currentUserId=${currentUserId}" "changeFrameHeight()" frameBorder="0" height="580px" width="100%"
scrolling="no"></iframe>
<script src="/lib/layMI/layui.js"></script>
<script>
function changeFrameHeight(){
var ifm= document.getElementById("barrage");
ifm.height=document.documentElement.clientHeight;
}
window.onresize=function(){
changeFrameHeight();
}
var barrage = document.getElementById("barrage");
var system ={};
var p = navigator.platform;
system.win = p.indexOf("Win") == 0;
system.mac = p.indexOf("Mac") == 0;
system.x11 = (p == "X11") || (p.indexOf("Linux") == 0);
if (system.win||system.mac||system.xll) {
var socket = null;
//连接websocket的ip地址
var ip = "crm.natapp1.cc";
//动态修改查
var im = {
init: function () {
if ('WebSocket' in window) {
//var socketUrl = 'ws://localhost:80/myHandler?myid=' +${currentUserId};
var socketUrl = 'ws://'+ip+'/myHandler?myid='+${currentUserId};
socket = new WebSocket(socketUrl);
im.startListener();
} else {
alert('当前浏览器不支持WebSocket功能,请更换浏览器访问。');
}
},
startListener: function () {
if (socket) {
// 连接发生错误的回调方法
socket.onerror = function () {
console.log("通讯连接失败!");
};
// 连接成功建立的回调方法
socket.onopen = function (event) {
console.log("通讯连接成功");
}
// 接收到消息的回调方法
socket.onmessage = function (event) {
console.log("通讯接收到消息");
im.handleMessage(event.data);
}
// 连接关闭的回调方法
socket.onclose = function () {
console.log("通讯关闭连接!!");
}
}
},
handleMessage: function (msg) {
msg = JSON.parse(msg)
//如果是群消息,转换一下id,返回群id的的字段必须是id
if (msg.type == "group") {
var temId = msg.id;
msg.id = msg.toId;
msg.toId = temId;
}
//console.log(msg)
layim.getMessage(msg);
}
};
im.init();
layui.use('layim', function (layim) {
var $ = layui.jquery;
window.layim = layim;
//演示自动回复
var autoReplay = [
'您好,我现在有事不在,一会再和您联系。',
'你没发错吧?face[微笑] ',
'洗澡中,请勿打扰,偷窥请购票,个体四十,团体八折,订票电话:一般人我不告诉他!face[哈哈] ',
'你好,我是主人的美女秘书,有什么事就跟我说吧,等他回来我会转告他的。face[心] face[心] face[心] ',
'face[威武] face[威武] face[威武] face[威武] ',
'<(@ ̄︶ ̄@)>',
'你要和我说话?你真的要和我说话?你确定自己想说吗?你一定非说不可吗?那你说吧,这是自动回复。',
'face[黑线] 你慢慢说,别急……',
'(*^__^*) face[嘻嘻] ,是贤心吗?'
];
//基础配置
layim.config({
//初始化接口
init: {
url: '/chatController/findChatInfo?currentUserId=' +${currentUserId} //从后台去找好友分组数据
, data: {}
}
, min: true
//查看群员接口
, members: {
url: '/chatController/getMembers'
, data: {}
}
//上传图片接口
, uploadImage: {
url: '/upload/image' //(返回的数据格式见下文)
, type: '' //默认post
}
//上传文件接口
, uploadFile: {
url: '/upload/file' //(返回的数据格式见下文)
, type: '' //默认post
}
, isAudio: true //开启聊天工具栏音频
, isVideo: true //开启聊天工具栏视频
//扩展工具栏
, tool: [{
alias: 'code'
, title: '代码'
, icon: ''
}]
//,brief: true //是否简约模式(若开启则不显示主面板)
, title: 'xxx' //自定义主面板最小化时的标题
//,right: '100px' //主面板相对浏览器右侧距离
//,minRight: '90px' //聊天面板最小化时相对浏览器右侧距离
, initSkin: '5.jpg' //1-5 设置初始背景
//,skin: ['aaa.jpg'] //新增皮肤
// ,isfriend: false //是否开启好友
//,isgroup: false //是否开启群组
//,min: true //是否始终最小化主面板,默认false
, notice: true //是否开启桌面消息提醒,默认false
//,voice: false //声音提醒,默认开启,声音文件为:default.mp3
, msgbox: layui.cache.dir + 'css/modules/layim/html/msgbox.html' //消息盒子页面地址,若不开启,剔除该项即可
, find: layui.cache.dir + 'css/modules/layim/html/find.html' //发现页面地址,若不开启,剔除该项即可
, chatLog: "/msg/showHistoryMsg" //聊天记录页面地址,若不开启,剔除该项即可
});
//监听在线状态的切换事件
layim.on('online', function (data) {
//console.log(data);
});
//监听签名修改
layim.on('sign', function (value) {
//console.log(value);
});
//监听自定义工具栏点击,以添加代码为例
layim.on('tool(code)', function (insert) {
layer.prompt({
title: '插入代码'
, formType: 2
, shade: 0
}, function (text, index) {
layer.close(index);
insert('[pre class=layui-code]' + text + '[/pre]'); //将内容插入到编辑器
});
});
//监听layim建立就绪
layim.on('ready', function (res) {
layim.msgbox(5); //模拟消息盒子有新消息,实际使用时,一般是动态获得
});
//监听发送消息
layim.on('sendMessage', function (data) {
//接收消息人员信息
var To = data.to;
var mine = data.mine;
if (To.type === 'friend') {
//layim.setChatStatus('<span style="color:#FF5722;">对方正在输入。。。</span>');
}
//添加接收消息方的id
mine.toId = To.id;
//添加接收方与己方的关系(Friend 或 group)
mine.type = To.type;
// 设置显示在接收方消息的显示位置在左
mine["mine"] = false;
var msg = JSON.stringify(mine);
//保存消息
$.ajax({
url: '/msg/saveMsg',
data: {"msg": msg},
type: 'post',
dataType: 'json',
success: function (json) {
if (json && json.code == 200) {
//执行重载
} else {
layer.msg('数据有误');
}
},
error: function () {
layer.msg('系统出错,请联系管理员');
}
})
socket.send(msg);
});
//监听查看群员
layim.on('members', function (data) {
//console.log(data);
});
//监听聊天窗口的切换
layim.on('chatChange', function (res) {
var type = res.data.type;
//选择发送的好友或群的信息
//console.log(res.data)
if (type === 'friend') {
//模拟标注好友状态
//layim.setChatStatus('<span style="color:#FF5722;">在线</span>');
//friendId = res.data.id;
} else if (type === 'group') {
//groupId = res.data.id;
/* //模拟系统消息
layim.getMessage({
system: true
,id: res.data.id
,type: "group"
,content: '模拟群员'+(Math.random()*100|0) + '加入群聊'
});*/
}
});
});
} else {
barrage.height = 530;
}
</script>
</body>
</html>
注意:必须在登录的时候传一个登录用户的id到消息处理类中作为用户列表的key
- MyHandler类
// 用户连接成功 就被调用
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
//获取传来的登录用户的id
String idstr = session.getUri().toString().split("=")[1];
int id = Integer.parseInt(idstr);
System.err.println("用户连接成功");
//保存对应的WebSocketSession
userSocketSessionMap.put(id, session);
}
1.4、效果图
谷歌浏览
火狐浏览器