实现目标
第一步:聊天实现原理
第二步:服务端基础代码
public class UserChatCommand {
private String name;
private String chatContent;
private String coordinationId;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getChatContent() {
return chatContent;
}
public void setChatContent(String chatContent) {
this.chatContent = chatContent;
}
public String getCoordinationId() {
return coordinationId;
}
public void setCoordinationId(String coordinationId) {
this.coordinationId = coordinationId;
}
@Override
public String toString() {
return "UserChatCommand{" +
"name='" + name + '\'' +
", chatContent='" + chatContent + '\'' +
", coordinationId='" + coordinationId + '\'' +
'}';
}
}
通过这个bean来接收到web端发送的消息,然后在服务端转发,接下来就是转发的逻辑了,不过首先需要介绍一下Spring WebSocket的一个annotation。
/**
* WebSocket聊天的相应接收方法和转发方法
*
* @param userChat 关于用户聊天的各个信息
*/
@MessageMapping("/userChat")
public void userChat(UserChatCommand userChat) {
//找到需要发送的地址
String dest = "/userChat/chat" + userChat.getCoordinationId();
//发送用户的聊天记录
this.template.convertAndSend(dest, userChat);
}
怎么这么简单?呵呵,能够这么简单的实现后台代码,全是Spring的功劳。首先,我们约定好发送地址的规则,就是chat后面跟上之前发送过来的id,然后通过这个“template”来进行转发,这个“template”是Spring实现的一个发送模板类:SimpMessagingTemplate,在我们定义controller的时候,可以在构造方法中进行注入:
@Controller
public class CoordinationController {
......
//用于转发数据(sendTo)
private SimpMessagingTemplate template;
@Autowired
public CoordinationController(SimpMessagingTemplate t) {
template = t;
}
.....
}
现在就已经将用户发送过来的聊天信息转发到了一个约定的空间内,只要web端的用户订阅的是这个空间的地址,那么就会收到转发过来的json。现在来看看web端需要做什么吧。
第三步:Web端代码
),并且绑定到发送按钮onclick事件。我们要做的事情大概是以下几步://发送聊天信息
function sendName() {
var input = $('#chat_input');
var inputValue = input.val();
input.val("");
stompClient.send("/app/userChat", {}, JSON.stringify({
'name': encodeURIComponent(name),
'chatContent': encodeURIComponent(inputValue),
'coordinationId': coordinationId
}));
}
其中,name和coordinationId是相应的用户信息,可以通过ajax或者jsp获取,这里就不多说了。
//用户聊天订阅
stompClient.subscribe('/userChat/chat' + coordinationId, function (chat) {
showChat(JSON.parse(chat.body));
});
将消息体转为json,再写一个显示聊天信息的方法就可以了,显示聊天信息的方法不再解释,如下:
//显示聊天信息
function showChat(message) {
var response = document.getElementById('chat_content');
response.value += decodeURIComponent(message.name) + ':' + decodeURIComponent(message.chatContent) + '\n';
}
因为之前处理中文问题,所以发到后台的数据是转码了的,从后台发回来之后,也需要将编码转回来。
第四步:聊天记录缓存实现
private MapcoordinationCache = new HashMap ();
这里我存的是一个Object数组,是因为我写的程序中,除了聊天信息的缓存,还有很多东西要缓存,只是将聊天信息的缓存放在了这个数组中的一个位置里。
我反正没在jdk中看到),所以我就自己实现了这样的一个队列,实现非常的简单,类名叫LimitQueue,使用泛型,继承自Queue,类中定义两个成员变量:private int limit; private Queuequeue;
limit代表队列的上限,queue是真正使用的队列。创建一个由这两个参数形成的构造方法,并且实现Queue的所有方法,所有的方法都由queue对象去完成,比如:
@Override
public int size() {
return queue.size();
}
@Override
public boolean isEmpty() {
return queue.isEmpty();
}
其中,有一个方法需要做处理:
@Override
public boolean offer(E e) {
if (queue.size() >= limit) {
queue.poll();
}
return queue.offer(e);
}
加入元素的时候,判断是否达到了上限,达到了的话就先出队列,再入队列。这样,就实现了固定大小的队列,并且总是保持最新的记录。
/**
* WebSocket聊天的相应接收方法和转发方法
*
* @param userChat 关于用户聊天的各个信息
*/
@MessageMapping("/userChat")
public void userChat(UserChatCommand userChat) {
//找到需要发送的地址
String dest = "/userChat/chat" + userChat.getCoordinationId();
//发送用户的聊天记录
this.template.convertAndSend(dest, userChat);
//获取缓存,并将用户最新的聊天记录存储到缓存中
Object[] cache = coordinationCache.get(Integer.parseInt(userChat.getCoordinationId()));
try {
userChat.setName(URLDecoder.decode(userChat.getName(), "utf-8"));
userChat.setChatContent(URLDecoder.decode(userChat.getChatContent(), "utf-8"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
((LimitQueue) cache[1]).offer(userChat);
}
已经有缓存了,只要在页面上取出缓存就能显示聊天记录了,可以通过ajax或者jsp等方法,不过,WebSocket也有方法可以实现,因为Spring WebSocket提供了一个叫SubscribeMapping的annotation,这个annotation标记的方法,是在订阅的时候调用的,也就是说,基本是只执行一次的方法,很适合我们来初始化聊天记录。所以,在订阅聊天信息的代码下面,可以增加一个初始化聊天记录的方法。我们先写好web端的代码:
//初始化
stompClient.subscribe('/app/init/' + coordinationId, function (initData) {
console.log(initData);
var body = JSON.parse(initData.body);
var chat = body.chat;
chat.forEach(function(item) {
showChat(item);
});
});
这次订阅的地址是init,还是加上coordinationId来区分空间,发送过来的数据是一个聊天记录的数组,循环显示在对话框中。有了web端代码的约束,后台代码也基本出来了,只要使用SubscribeMapping,再组装一下数据就完成了,后台代码如下:
/**
* 初始化,初始化聊天记录
*
* @param coordinationId 协同空间的id
*/
@SubscribeMapping("/init/{coordinationId}")
public Map init(@DestinationVariable("coordinationId") int coordinationId) {
System.out.println("------------新用户进入,空间初始化---------");
Map document = new HashMap();
document.put("chat",coordinationCache.get(coordinationId)[1]);
return document;
}
就这样,缓存聊天记录也实现了。
结语

1 条评论
吴彦祖 · 2017年12月21日 20:02
全文没有提netty 差评