即时通讯-单聊(四)


前言

已经实现了服务端的配置以及客户端的展示,下面需要进行单聊和群聊功能的实现,以下为具体的实现方式和代码。

单聊

LayIM官网文档对发送消息所包含的信息

这是我自己完成单聊后,发送消息的时候包含的信息

根据以上信息可知,在消息发送的时候会消息体中会包发送者(mine)的结构和接受者(to)的结构,其中

  1. 发送者(mine)包含了发送人的头像、id、发送内容等
  2. 接受者(to)包含了接受者的头像、id、聊天类型等

    其中聊天类型(type)一般分friend和group两种,friend为单聊,group为群聊
    但是这么多的信息没必要全部获取,值需要取需要用到的内容,接受者的ID和发送者发送的内容即可。因为后面需要区分单聊、群聊、离线消息等其他的消息类型,
    所以在发送消息的结构体中,需要带有消息的类型
    注意:消息的类型与聊天类型不同,消息类型后台程序会根据消息类型,进行不同的消息处理程序。而聊天类型(type)是前台页面就行不同的消息窗口展示,而且friend和group消息结构不同

前端页面发送时候的消息体结构

1
2
3
msgtype:(status=="offline"?msgType.offline:msgType.chatfriend),
content:data.mine.content,
toid:data.to.id

  1. msgtype(消息类型,后台程序处理的类)
  2. content(发送者发送的内容)
  3. toid(接受者的id)

消息体结构设计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
* 消息接收消息结构体
*/
public class BaseBody extends MsgType {
/**
* 接受者id
*/
@Getter
@Setter
private String toid;

/**
* 消息内容
*/
@Getter
@Setter
private String content;
/**
* 时间戳
*/
@Getter
@Setter
private long timestamp;

public long getTimestamp() {
if (timestamp == 0){
timestamp = System.currentTimeMillis();
}
return timestamp;
}

解析后的消息类型(MsgType.java)

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 解析后的消息类型
*/
public class MsgType {
/**
* 消息类型
*/
@Getter
@Setter
private byte msgtype;

}

其中时间戳在消息接收的时候会用到,用以显示发送or接收的时间。
前后端的消息结构体设计完成后,下面就需要对发送过来的消息进行接受、解析、处理
WesServerAioHandler类进行消息的处理

当客户端完成发送的消息后,服务端就进行消息的解析:WesServerAioHandler类的decode的方法。
对解析完成后的消息进行处理。
处理解析后的消息messageHandle方法
以下截取messageHandle方法中主要的地方

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//消息类型为TEXT
if (opcode == Opcode.TEXT) {
if (bytes != null && bytes.length != 0) {
//接收到的消息包
String text = new String(bytes, wesServerConfig.getCharset());
//获得消息类型
MsgType property = Json.toBean(text, MsgType.class);
//解析数据消息
log.info("解析后的消息类型",property.getMsgtype());
//消息转发处理
MsgHandler msgHandler = WebMsgHandler.getMsg(property.getMsgtype());
boolean unknownMsg = msgHandler == null;
if(!unknownMsg) {
//根据消息类型进行消息的处理
msgHandler.handler(websocketPacket, channelContext);
}
Object retObj = wesMsgHandler.onText(websocketPacket, text, channelContext);
String methodName = "onText";
wsResponse = this.processRetObj(retObj, methodName, channelContext);
return wsResponse;
} else {
Tio.remove(channelContext, "错误的websocket包,body为空");
return null;
}
}

其中MsgHandler类为消息的转发处理器,它在服务端初始化的时候,将各消息处理的类进行了实例化并添加到一个Map类型中,具体的代码不进行展示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//存储消息处理的类
private static Map<String, MsgHandler<?>> handlerMap = new HashMap<String, MsgHandler<?>>();
/**
* 初始化
*/
public static void init(){
//单聊 点对点
handlerMap.put("CLIENT_TO_CLIENT",new ClientToClientMsg());
//离线消息
handlerMap.put("OFF_LINE",new OfflineMsg());
//群消息
handlerMap.put("CLIENT_TO_GROUP",new ClientToGroupMsg());

}

通过反序列化得到具体的消息处理类

点对点消息处理类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
* 点对点单聊消息处理
*
*/
public class ClientToClientMsg extends MsgHandler<BaseBody> {

private static final Logger logger = LoggerFactory.getLogger(ClientToClientMsg.class);
@Override
public Class<BaseBody> bodyClass() {
return BaseBody.class;
}


@Override
public WsResponse handler(WsRequest wsRequest, BaseBody body, ChannelContext channelContext) throws Exception {
logger.info("构建消息体响应类点对点");
//客户端接收消息的组装
ChatMsgBody chatMsgBody= ConvertMsg.getInstance().convertMsgBody(body,channelContext);
logger.info("构建响应包点对点");
//消息包装,返回WsResponse
WsResponse toClientBody = ConvertMsg.getInstance().fromText(chatMsgBody);
//发送给对方
Tio.sendToUser(channelContext.tioConfig, body.getToid(), toClientBody);
return null;
}
}

Tio.sendToUser发送到指定人;

消息格式和相遇格式的组装 ConvertMsg.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
/**
* 响应消息组装
* @author
*/
public class ConvertMsg {
//声明此类型的变量,但没有实例化
private static ConvertMsg instance;
//双重检测
public static ConvertMsg getInstance(){
if (instance == null){
synchronized (ConvertMsg.class){
if(instance == null){
instance = new ConvertMsg();
}
}
}
return instance;
}

//组建即时聊天响应包
public WsResponse fromText(Object body) throws IOException{
WsResponse response = new WsResponse();
if(body != null) {
String json = Json.toJson(body);
//消息体
response.setBody(StringUtils.getBytes(json));
response.setWsBodyText(json);
response.setWsEof(true);
response.setWsOpcode(Opcode.TEXT);
}
return response;
}


/**
* 构建消息体响应类(Layim格式)
* @param requestBody
* @param channelContext
* @return
*/
public ChatMsgBody convertMsgBody(BaseBody requestBody, ChannelContext channelContext){
//服务端发送消息格式
ChatMsgBody msgBody = new ChatMsgBody();

/*
channelContext.userid---在握手成功后会将当前用户的id绑定到信道的上下文中,具体参考
WesMsgHandler类中的handshakeUser方法
根据绑定的id获得用户的信息
*/
Mine contextUser =(Mine)channelContext.getAttribute(channelContext.userid);
//当前用户名
msgBody.setUsername(contextUser.getUsername());
//用户头像
msgBody.setAvatar(contextUser.getAvatar());
//判断为单聊
if(requestBody.getMsgtype()== MsgType.CLIENT_TO_CLIENT){
//消息的来源ID(如果是私聊,则是用户id,如果是群聊,则是群组id)
msgBody.setId(channelContext.userid);
//聊天窗口来源类型---好友
msgBody.setType("friend");
//判断为群聊
}else if(requestBody.getMsgtype() == MsgType.CLIENT_TO_GROUP){
//消息的来源ID(如果是私聊,则是用户id,如果是群聊,则是群组id)
msgBody.setId(requestBody.getToid());
//聊天窗口来源类型---群组
msgBody.setType("group");
}
//消息内容
msgBody.setContent(requestBody.getContent());
//消息的发送者id(比如群组中的某个消息发送者),可用于自动解决浏览器多窗口时的一些问题
msgBody.setFromid(channelContext.userid);
//消息类型,用于前端页面的显示
msgBody.setMsgtype(requestBody.getMsgtype());
//发送时间
msgBody.setTimestamp(requestBody.getTimestamp());
return msgBody;
}

}

convertMsgBody方法构建消息体响应格式—客户端接收消息的格式
fromText方法为响应请求数据格式的组装

消息的接收

先看看LayIM官网对接收消息所包含的信息

其中1为接收消息的layim接口,2为消息内容的具体字段。
下面是为在HTML页面中,对单聊和群聊进行了判断,分别进行展示。

1
2
3
4
5
6
7
8
9
10
11
12
13
var msg=JSON.parse(e.data);

switch (msg.msgtype) {
//点对点
case msgType.chatfriend:
layim.getMessage(msg);
break;
//群组消息
case msgType.chatgroup:
layim.getMessage(msg);
break;

}

根据消息类型进行消息的展示

群聊

群聊的代码就不展示了,群聊原理和单聊一样。单聊和群聊区别为:调用的方法不同,和body.getToid()不同

区别

区别 单聊 群聊
调用方法 sendToUser sendToGroup
Toid 消息接收者的id 消息群组的id

注意

消息群组的id也是在握手成功后进行绑定
具体参考WesMsgHandler类中的handshakeUser方法

展示效果

结束

单聊和群聊已经结束了,但是如果你发送的用户不在线怎么办,离线消息功能也要进行实现,好友的上下线也要进行通知。

参考内容

https://gitee.com/cctvmfc/t-io
https://gitee.com/natral/tio-showcase
https://my.oschina.net/panzi1/blog/1577007#h1_3

-------------本文结束感谢您的阅读-------------