SpringBoot+Mybatis-Plus使用webSocket实现一对一聊天(第三次修改)
上一版代码链接:
这一次的发出来的文章可能有一点点乱,希望不会被嫌弃。
代码还在一步一步更新,如果有幸被大佬看到,恳求指点一二。
一、问题背景
之前代码完成以后去写群聊天功能,发现新的问题
一对一聊天:
草丛伦 对 菊花信 说:“咱们要藏在哪个草里。”
这时 五秒真男人 对 草丛伦 说:“五秒太快啦,救命啊!~~~~”
对于 五秒真男人 来说 草丛伦 现在的状态是对方已经断开链接
草丛伦 不能实时的接收到 五秒真男人 的求救
只有 草丛伦 打开跟 五秒真男人 的聊天界面以后
才会把 五秒真男人 之前发来的消息推送给 草丛伦
但此时 五秒真男人 早已在温泉中默默的画圈圈
好吧确实有点乱,我也没办法,反正就是这么个意思,嘿嘿。
二、解决办法
经过反复思考,目前想到了两种解决办法:
1、加一个长轮询
长轮询(comet):当服务器收到客户端发来的请求后,服务器端不会直接进行响应,而是先将这个请求挂起,
然后判断服务器端数据是否有更新。如果有更新,则进行响应,如果一直没有数据,则到达一定的时间限制(服
务器端设置)才返回。 。 客户端JavaScript响应处理函数会在处理完服务器返回的信息后,再次发出请求,
重新建立连接。
结合之前想用Redis存储聊天记录(无奈只能存储最后一条记录而放弃),用一个方法去Redis中查询数据就否发生改变,如果有的话获取最后一条记录回显在聊列表,提示有新消息,打开该聊天记录之后建立新的WebSocekt连接调用离线消息方法推送新消息 。
第一种方法我没有用代码实现,感觉有点浪费资源,重点是第二种方法,代码贴出来,以便各位指正。
2、去除RoomId改用SessionId
1) 一对一聊天
/**
* @description:一对一聊天
* 注解是一个类层次的注解,它的功能主要是将目前的类定义成一个websocket服务器端,
* 注解的值将被用于监听用户连接的终端访问URL地址,客户端可以通过这个URL来连接到WebSocket服务器端
*
* @author 火烛
* @since 2020-9-17
*/
@RestController
@ServerEndpoint(value = "/webSocketOneToOne/{userId}")
public class WebSocketOneToOne {
// 这里使用静态,让 service 属于类
private static UsersServiceImpl userService;
// 注入的时候,给类的 service 注入
@Autowired
public void setUserService(UsersServiceImpl userService) {
WebSocketOneToOne.userService = userService;
}
private static MessageServiceImpl messageService;
@Autowired
public void setChatMsgService(MessageServiceImpl messageService) {
WebSocketOneToOne.messageService = messageService;
}
// 静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
private static int onlineCount;
//实现服务端与单一客户端通信的话,可以使用Map来存放,其中Key为用户标识
private static final Map<String,Session> connections = new ConcurrentHashMap<>();
// 与某个客户端的连接会话,需要通过它来给客户端发送数据
private String sessionId;
private String sendId;
/**
* 连接建立成功调用的方法
*
* @param session
* 可选的参数。session为与某个客户端的连接会话,需要通过它来给客户端发送数据
*/
@OnOpen
public void onOpen(@PathParam("userId") String userId, Session session) {
this.sessionId = session.getId();
this.sendId = userId;
connections.put(sendId,session); //添加到map中
addOnlineCount(); //在线数加
System.out.println(userId);
System.out.println("--------------连接-------------" + this.sessionId);
System.out.println("有新连接加入!新用户:"+sendId+",当前在线人数为" + getOnlineCount());
List<Message> messageList = messageService.queryByType(sendId);
for (Message message : messageList){
downSend(message.getContent(), message.getSendId(), message.getReceiveId(), message.getType(), sessionId);
}
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose() {
Users users = new Users();
users.setId(sendId);
users.setDownTime(String.valueOf(LocalDateTime.now().toEpochSecond(ZoneOffset.ofHours(8))));
userService.updateById(users);
connections.remove(sendId); // 从map中移除
subOnlineCount(); // 在线数减
System.out.println("有一连接关闭!当前在线人数为" + getOnlineCount());
}
/**
* 收到客户端消息后调用的方法
*
* @param message
* 客户端发送过来的消息
* @param session
* 可选的参数
*/
@OnMessage
public void onMessage(String message, Session session) {
System.out.println("--------------接收消息-------------" + session.getId());
System.out.println("来自客户端的消息:" + message);
JSONObject json= JSON.parseObject(message);
String msg = (String) json.get("message"); //需要发送的信息
String receiveId = (String) json.get("receiveId"); //发送对象的用户标识(接收者)
String type = (String) json.get("type"); //发送对象的用户标识(接收者)
send(msg,sendId,receiveId,type, session.getId());
}
/**
* 发生错误时调用
*
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error) {
System.out.println("发生错误");
error.printStackTrace();
}
//发送给指定角色
public void send(String msg,String sendId,String receiveId,String type, String sessionId){
System.out.println("--------------推送消息-------------" + sessionId);
Message message = new Message();
message.setId(MyUtils.getRandomString(10));
message.setContent(msg);
message.setCreateTime(LocalDateTime.now());
//时间格式化
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
message.setCreateTimeM(String.valueOf(dateTimeFormatter.format(LocalDateTime.now())));
message.setReceiveId(receiveId);
message.setSendId(sendId);
message.setType(type);
try {
Users u = userService.getById(sendId);
//from具体用户
Session confrom = connections.get(sendId);
if(confrom!=null){
if(sessionId.equals(confrom.getId())){
System.out.println("confrom.getId()-------" + confrom.getId());
Map map = MapUnite.getMap(message);
messageService.save(message);
map.put("avatar",u.getIcon());
confrom.getBasicRemote().sendText(JSON.toJSONString(map));
}
}
//to指定用户
Session con = connections.get(receiveId);
if(con!=null){
System.out.println("con.getId()--------" + con.getId());
if (sessionId.equals(this.sessionId)) {
Map map = MapUnite.getMap(message);
map.put("avatar", u.getIcon());
HashMap<String, Map<String, String>> stringMapHashMap = new HashMap<>();
stringMapHashMap.put(sendId, map);
con.getBasicRemote().sendText(JSON.toJSONString(stringMapHashMap));
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
//发送离线消息给指定角色
public void downSend(String msg,String sendId,String receiveId,String type, String sessionId){
Message message = new Message();
message.setId(MyUtils.getRandomString(10));
message.setContent(msg);
message.setCreateTime(LocalDateTime.now());
//时间格式化
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
message.setCreateTimeM(String.valueOf(dateTimeFormatter.format(LocalDateTime.now())));
message.setReceiveId(receiveId);
message.setSendId(sendId);
message.setType(type);
try {
Users u = userService.getById(sendId);
System.out.println(u);
//to指定用户
Session con = connections.get(receiveId);
if(con!=null){
System.out.println("con.getId()--------" + con.getId());
if (sessionId.equals(this.sessionId)) {
Map map = MapUnite.getMap(message);
map.put("avatar", u.getIcon());
HashMap<String, Map<String, String>> stringMapHashMap = new HashMap<>();
stringMapHashMap.put(sendId, map);
con.getBasicRemote().sendText(JSON.toJSONString(stringMapHashMap));
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static synchronized int getOnlineCount() {
return onlineCount;
}
public static synchronized void addOnlineCount() {
WebSocketOneToOne.onlineCount++;
}
public static synchronized void subOnlineCount() {
WebSocketOneToOne.onlineCount--;
}
}
MessageController
/**
* 聊天记录标记为已读
*
* @param sendId
* @param receiveId
* @return
*/
@RequestMapping(value = "updateType", method = RequestMethod.PUT)
@ApiOperation(value = "聊天记录标记为已读")
public responseVo updateType(String sendId, String receiveId){
responseVo result = messageService.updateType(sendId, receiveId);
return result;
}
}
SQL也稍微修改了一下(type = 0为未读,1为已读):UPDATE message SET type = '1' WHERE send_id = #{sendId} AND receive_id = #{receiveId}