本文由下文改进而来:https://blog.csdn.net/qq_41463655/article/details/92410518
适合做简易聊天室,多聊天室,发送接收消息可添加分组id
适合做及时消息通知
另外提供 WebSocketController web接口
org.springframework.boot
spring-boot-starter-websocket
package com.ws.ldy.common.websocket.model.dto;
import com.ws.ldy.base.convert.Convert;
import lombok.Data;
/***
* TODO websocket客服端-发送消息-入参参数
* @author 王松
* @mail [email protected]
* @date 2020/6/30 0030 18:24
*/
@Data
public class SendMsgDTO extends Convert {
/**
* 接收人用户Id (目标ID,逗号分隔) (所有人使用-ALL)
*/
private String to;
/**
* 发送内容
*/
private String content;
/**
* @param to 接收人用户Id (目标ID,逗号分隔) (所有人使用-ALL)
* @param content 发送内容
*/
public SendMsgDTO(String to, String content) {
this.to = to;
this.content = content;
}
public SendMsgDTO() {
}
}
package com.ws.ldy.common.websocket.model.entity;
import com.ws.ldy.base.convert.Convert;
import com.ws.ldy.common.utils.LocalDateTimeUtils;
import lombok.AllArgsConstructor;
import lombok.Data;
import javax.websocket.Session;
import java.time.LocalDateTime;
/***
* TODO 当前在线列表信息
* @author 王松
* @mail [email protected]
* @date 2020/7/1 0001 11:58
*/
@Data
@AllArgsConstructor
public class OnlineUser extends Convert {
/**
* 用户ID
*/
private String userId;
/**
* 用户名称
*/
private String username;
/**
* 用户会话 (使用该对象进行消息发送)
*/
private Session session;
/**
* 连接时间
*/
private String createTime;
/**
* @param userId 用户id
* @param username 用户名称
* @param session 用户session 回话信息
*/
public OnlineUser(String userId, String username, Session session) {
this.userId = userId;
this.username = username;
this.session = session;
this.createTime = LocalDateTimeUtils.parse(LocalDateTime.now());
}
}
package com.ws.ldy.common.websocket.model.vo;
import com.ws.ldy.base.convert.Convert;
import lombok.Data;
/**
* TODO 在线列表信息返回
*/
@Data
public class OnlineUserVO extends Convert {
/**
* 用户ID
*/
private String userId;
/**
* 用户名称
*/
private String username;
/**
* 连接时间
*/
private String createTime;
}
package com.ws.ldy.common.websocket.model.vo;
import com.ws.ldy.base.convert.Convert;
import com.ws.ldy.common.websocket.service.impl.WebsocketServiceImpl;
import com.ws.ldy.common.utils.LocalDateTimeUtils;
import lombok.Data;
import java.time.LocalDateTime;
/**
* TODO websocket客户端-接收端-返回参数
*
* @author 王松
* @mail [email protected]
* @date 2020/6/30 0030 18:24
*/
@Data
public class SendMsgVO extends Convert {
/**
* 消息类型(1-上线通知 2-下线通知 3-在线名单通知 4-代表普通消息通知 )
*/
private Integer mesType;
/**
* 发送人用户Id(来源Id,上线为上线线人的用户Id)
*/
private String from;
/**
* 发送人用户名( 上下线为上线线人的用户名)
*/
private String username;
/**
* 接收人用户Id (目标ID,逗号分隔) (所有人使用-ALL,包括自己在内也能接收)
*/
private String to;
/**
* 发送内容
*/
private String content;
/**
* 扩展消息字段(json)
*/
private String extras;
/**
* 当前在线人数
*/
private Integer onlineNum;
/**
* 消息创建时间(YYYY-MM-DD )
*/
private String createTime;
// /**
// * 消息类型,int类型(0:text、1:image、2:voice、3:vedio、4:music、5:news)
// */
// private Integer msgType;
/**
* @param mesType 消息类型(1-上线通知 2-下线通知 3-在线名单通知 4-代表普通消息通知 )
* @param from 发送人Id(来源Id),上下线为上线人的用户id
* @param username 发送人姓名username,上下线为上线人的用户名
* @param to 接收人Id(目标ID,逗号分隔,所有人使用-ALL)
* @param content 发送消息内容
* @param extras 发送消息扩展字段
*/
public SendMsgVO(Integer mesType, String from, String username, String to, String content, String extras) {
this.mesType = mesType;
this.from = from;
this.username = username;
this.to = to;
this.content = content;
this.extras = extras;
this.onlineNum = WebsocketServiceImpl.onlineNumber.intValue();
this.createTime = LocalDateTimeUtils.parse(LocalDateTime.now());
}
}
package com.ws.ldy.common.websocket.service;
import com.ws.ldy.common.websocket.model.vo.OnlineUserVO;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import java.util.List;
/**
* TODO websocket 监听类(连接,断点,消息发送等)
*
* /websocket/{userId}/{username} = /websocket/用户Id/用户名 来连接websocket,该参数会带到每一个监听方法中
*
*/
public interface WebsocketService {
//================================================================================
//================================================================================
//=============================== 监听方法 =======================================
//================================================================================
//================================================================================
/**
* TODO 监听连接(有用户连接,立马到来执行这个方法),session 发生变化
*
* @param userId 用户id
* @param username 用户名
* @param session 当前用户会话
* @return void
* @date 2020/6/30 0030 9:28
*/
public void onOpen(@PathParam("userId") String userId, @PathParam("username") String username, Session session);
/**
* TODO 监听断开连接(有用户退出,会立马到来执行这个方法)
*
* @param userId 用户id
* @param username 用户名
* @param session 当前用户会话
*/
public void onClose(@PathParam("userId") String userId, @PathParam("username") String username, Session session);
/**
* TODO 异常停止
*
* @param userId 用户id
* @param username 用户名
* @param session 当前用户会话
* @param error 异常信息
*/
public void onError(@PathParam("userId") String userId, @PathParam("username") String username, Session session, Throwable error);
/**
* TODO 监听消息发送(收到客户端的消息立即执行)
*
* @param userId 用户id
* @param username 用户名
* @param message 传递的消息内容, json数据( to=接收人用户Id (目标ID,逗号分隔) || content=内容)
* @param session 当前用户会话
*
*
* // 前端发送内容格式
* ....
* // 拼接参数
* let message = { "content": content, "to": to };
* // 发送数据
* webSocket.send(JSON.stringify(message));
* ....
*
*/
public void onMessage(@PathParam("userId") String userId, @PathParam("username") String username, String message, Session session);
//================================================================================
//================================================================================
//======================= service方法(http接口调用操作) ============================
//================================================================================
//================================================================================
/**
* 获取当前在线人数
*
* @return
*/
public Integer getOnlineCount();
/**
* 获取当前在线用户列表
*
* @return
*/
public List getOnlineUsersList();
/**
* 发送消息
*
* @param form 发送人id
* @param username 发送人用户名
* @param to 接收人id(多个逗号分隔)
* @param content 发送内容
* @param extras 扩暂发送内容
* @return
*/
public List send(String form, String username, String to, String content, String extras);
}
package com.ws.ldy.common.websocket.service.impl;
import com.alibaba.fastjson.JSON;
import com.ws.ldy.common.utils.JsonUtils;
import com.ws.ldy.common.websocket.model.dto.SendMsgDTO;
import com.ws.ldy.common.websocket.model.entity.OnlineUser;
import com.ws.ldy.common.websocket.model.vo.OnlineUserVO;
import com.ws.ldy.common.websocket.model.vo.SendMsgVO;
import com.ws.ldy.common.websocket.service.WebsocketService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
/**
* TODO websocket 监听类(连接,断点,消息发送等)
*
* /websocket/{userId}/{username} = /websocket/用户Id/用户名 来连接websocket,该参数会带到每一个监听方法中
*
*
* @ServerEndpoint: socket链接地址
*/
@ServerEndpoint("/websocket/{userId}/{username}")
@Service
@Slf4j
public class WebsocketServiceImpl implements WebsocketService {
/**
* 在线人数 //使用原子类AtomicInteger, ---> static并发会产生线程安全问题, //public static Integer onlineNumber = 0;
*/
public static AtomicInteger onlineNumber = new AtomicInteger(0);
/**
* 所有用户信息(session + userId + username + createTime --> 以用户的id为key, 通过用户key来获取用户session进行消息发送)
*/
public static Map clients = new ConcurrentHashMap<>();
//================================================================================
//================================================================================
//=============================== 监听方法 =======================================
//================================================================================
//================================================================================
/**
* TODO 监听连接(有用户连接,立马到来执行这个方法),session 发生变化
*/
@OnOpen
public void onOpen(@PathParam("userId") String userId, @PathParam("username") String username, Session session) {
// 自增1
onlineNumber.getAndIncrement();
// 保存新用户id,用户名,session会话,登录时间
clients.put(userId, new OnlineUser(userId, username, session));
// 告诉所有人,我上线了
String content = "系统消息:" + username + " 上线了";
this.send(new SendMsgVO(1, userId, username, "ALL", content, null));
// 给自己发一条消息:告诉自己现在都有谁在线
this.send(new SendMsgVO(3, userId, username, userId, JSON.toJSONString(getOnlineUsers()), null));
log.info("有新连接加入!sessionId:{} userId:{} userName:{} 当前在线人数:{}", session.getId(), userId, username, onlineNumber);
}
/**
* TODO 监听断开连接(有用户退出,会立马到来执行这个方法)
*/
@OnClose
public void onClose(@PathParam("userId") String userId, @PathParam("username") String username, Session session) {
// 自减1
onlineNumber.getAndDecrement();
// 所有在线用户中去除下线用户
clients.remove(userId);
// 告诉所有人,我下线了
String content = "系统消息:" + username + " 下线了";
this.send(new SendMsgVO(2, userId, username, "ALL", content, null));
// 日志
log.info(username + ":已离线! 当前在线人数" + onlineNumber);
}
/**
* TODO 异常停止
*/
@OnError
public void onError(@PathParam("userId") String userId, @PathParam("username") String username, Session session, Throwable error) {
error.printStackTrace();
log.info("服务端发生了错误" + error.getMessage());
}
/**
* TODO 监听消息发送(收到客户端的消息立即执行)
*/
@OnMessage
public void onMessage(@PathParam("userId") String userId, @PathParam("username") String username, String message, Session session) {
log.info("服务器接收到发送消息请求,发送人id={},用户名={}, 接收发送消息={}", userId, username, message);
// 请求参数(接收人+发送内容)
SendMsgDTO sendMsgDTO = JsonUtils.parseEntity(message, SendMsgDTO.class);
// 发送消息
this.send(new SendMsgVO(4, userId, username, sendMsgDTO.getTo(), sendMsgDTO.getContent(), null));
}
/**
* 消息发送( 遍历用户Id , 在通过sendMsg方法发送消息)
*
* @param sendMsg:消息内容
*/
private void send(SendMsgVO sendMsg) {
if ("ALL".equals(sendMsg.getTo()) || "all".equals(sendMsg.getTo())) {
// 发送消息给所有人
Set userIds = clients.keySet();
for (String userId : userIds) {
this.sendMsg(userId, sendMsg);
}
} else {
//发送消息给指定人
String[] userIds = sendMsg.getTo().split(",");
for (String userId : userIds) {
this.sendMsg(userId, sendMsg);
}
}
}
/**
* 消息发送(最后发送, 在send方法中循环用户Id 列表依次发送消息给指定人)
*
* // 消息发送(同步:getBasicRemote 异步:getAsyncRemote)
*
*
* @param userId 消息接收人ID , onlineUsers 的 key
* @param sendMsg 消息内容
*/
private void sendMsg(String userId, SendMsgVO sendMsg) {
// 判断用户是否在线, 在线发送消息推送
if (clients.containsKey(userId)) {
try {
clients.get(userId).getSession().getBasicRemote().sendText(JSON.toJSONString(sendMsg));
} catch (IOException e) {
e.printStackTrace();
log.info(userId, sendMsg.getUsername() + "上线的时候通知所有人发生了错误");
}
}
}
/**
* 获取当前在线列表
*
* 获取当前在线列表, 把onlineUsers 转到 OnlineUsersVO返回
*
*
* @return
*/
public synchronized List getOnlineUsers() {
List onlineUsersVOList = new ArrayList<>();
for (OnlineUser onlineUsers : clients.values()) {
onlineUsersVOList.add(onlineUsers.convert(OnlineUserVO.class));
}
return onlineUsersVOList;
}
//================================================================================
//================================================================================
//======================= service方法(http接口调用操作) ============================
//================================================================================
//================================================================================
/**
* 获取当前在线人数
*
* @return
*/
public Integer getOnlineCount() {
return onlineNumber.get();
}
/**
* 获取当前在线用户信息
*
* @return
*/
public List getOnlineUsersList() {
return getOnlineUsers();
}
/**
* 发送消息 (单向通知发送,不可回复)
*
* @param form 发送人id
* @param username 发送人用户名
* @param to 接收人id(多个逗号分隔)
* @param content 发送内容
* @param content 发送内容
* @param content 扩暂发送内容
* @return
*/
public List send(String form, String username, String to, String content, String extras) {
// 发送消息
this.send(new SendMsgVO(4, form, username, to, content, extras));
return getOnlineUsers();
}
}
package com.ws.ldy.base.controller;
import com.ws.ldy.common.result.Result;
import com.ws.ldy.common.result.ResultEnum;
import com.ws.ldy.common.websocket.model.vo.OnlineUserVO;
import com.ws.ldy.common.websocket.service.WebsocketService;
import com.ws.ldy.config.error.ErrorException;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* websocket类
*
* @ServerEndpoint: socket链接地址
*/
@Api(value = "WebSocketController", tags = "websocket 相关通知/聊天")
@RequestMapping("/websocket")
@RestController
@Slf4j
public class WebSocketController {
/**
* websocket ip 或域名
*/
@Value("${websocket.ip}")
private String ip;
/**
* websocket 端口号
*/
@Value("${websocket.port}")
private String port;
/**
* websocket接口
*/
@Value("${websocket.interfaceName}")
private String interfaceName;
/**
* TODO 获取webSocket 连接地址, // 实际情况根据用户 token获取用户信息返回
* 获取socket地址
* 获取用户名
* 获取用户Id
*/
@RequestMapping(value = "/getPath", method = RequestMethod.GET)
@ApiOperation("游客登录获取websocket连接地址")
public Result
## websocket 配置
websocket:
# websocket 服务器部署地址的ip或者域名 --> 本地可以使用127.0.0.1 || localhost
ip: 127.0.0.1
# websocket 服务器端口,获取当前服务器端口
port: ${server.port}
# websocket 连接接口, WebsocketServiceImpl的 @ServerEndpoint 内参数
interfaceName: /websocket
/**
* TODO WebSocket 配置类
* @author 王松
* @mail [email protected]
* @date 2020/6/30 0030 9:26
*/
@Configuration
public class WebSocketConfig {
/**
* 服务器节点
*
* 如果使用独立的servlet容器,而不是直接使用springboot的内置容器,就不要注入ServerEndpointExporter,因为它将由容器自己提供和管理
*
* @return
*/
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
websocket
群聊信息
在线列表
消息发送至:
私聊信息
重点还是在与html 的js中获取到参数如何处理参数,以及发送消息处理,到此可以先线上完成websocket 聊天功能了,
以及直接使用web-api 接口获取到在线人数, 直接所有发送消息Api 接口向聊天室发送任意消息
本文到此结束,如果觉得有用,动动小手点赞或关注一下呗,将不定时持续更新更多的内容…,感谢大家的观看!