1、本文使用到netty-socketio开源库,,所以首先在build.gradle中添加相应的依赖库
compile("com.corundumstudio.socketio:netty-socketio:1.7.12")
2、在spring-boot资源配置文件application.yml或application.properties 配置socket的主机名和端口,本例中用的配置文件是application.yml
socket:
host: 172.18.8.12
port: 9999
1、在springboot的入口类(一般是Application.java)中,利用@Value方式读取配置文件中socket的主机名和端口
@Value("${socket.host}")
private String host;
@Value("${socket.port}")
private int port;
2 在Application.java 注册一个bean,并创建一个socket服务。
@Bean
public SocketIOServer socketIOServer()
{
Configuration config = new Configuration();
config.setHostname(host);
config.setPort(port);
final SocketIOServer server = new SocketIOServer(config);
return server;
}
3、socket自动扫描
@Bean
public SpringAnnotationScanner springAnnotationScanner(SocketIOServer socketServer) {
return new SpringAnnotationScanner(socketServer);
}
4、存储客户端socket消息
public class SocketClient {
/**
* 用户Id
**/
private String userId;
/**
* 用户状态 0为空闲 1为忙碌
**/
private String status;
/**
* 用户角色类型
*
**/
private String roleId;
/**
* 中心编码
**/
private String centerCode;
/**
* 服务类型
*/
private String serverType;
private SocketIOClient socketIOClient;
............
}
5、socket服务启动
@Component
public class ServerRunner implements CommandLineRunner {
private final SocketIOServer server;
/**
* Instantiates a new Server runner.
*
* @param server the server
*/
@Autowired
public ServerRunner(SocketIOServer server) {
this.server = server;
}
/**
* 服务启动
*/
@Override
public void run(String... args) throws Exception {
server.start();
}
}
6、添加消息处理类NoticeEventHandler.java,添加初始化监听.
@Component
public class NoticeEventHandler {
private Logger log = LoggerFactory.getLogger(NoticeEventHandler.class);
private final SocketIOServer server;
private SocketManager socketManager;
/**
* Instantiates a new Notice event handler.
*
* @param server the server
*
*/
@Autowired
public NoticeEventHandler(SocketIOServer server, @Qualifier("socketEventListenerManager") final SocketEventListenerManager socketEventListenerManager,
@Qualifier("socketManager") final SocketManager socketManager) {
this.server = server;
this.socketManager = socketManager;
initSocketEventListener(socketEventListenerManager);
}
/**
* 初始化 SocketEventListenerManager
* 1、先扫描SocketEventListenerManager文件中定义事件
* 2、遍历 将订阅事件添加客户端监听中
* 3、eventListener将订阅的消息 转化为消息对象 传到对应的noticeService的receive方法中
* */
private void initSocketEventListener(SocketEventListenerManager socketEventListenerManager) {
List eventListeners = socketEventListenerManager.getEventListener();
if (CollectionUtils.isEmpty(eventListeners))
return;
for (EventListener eventListener : eventListeners) {
if (eventListener != null) {
server.addEventListener(eventListener.getEventName(), java.lang.Object.class, (client, data, ackSender) -> {
if (eventListener.getNoticeService() != null) {
if (eventListener.getNoticeMessageMapperService() != null) {
eventListener.getNoticeService().receive(eventListener.getNoticeMessageMapperService().objectToMessage(data));
}
}
});
}
}
}
}
@Service
@ComponentScan(basePackages = {"com.gsafety.socket.notice.service"})
public class SocketEventListenerManager {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
@NoticeEvent(event = "chatEvent")
private BCANoticeServiceImpl bcaNoticeService;
@Autowired
@NoticeEvent(event = "chatEvent")
private BCANoticeMessageMapperImpl bcaNoticeMessageMapper;
@Autowired
@NoticeEvent(event = "checkEvent")
private CheckNoticeMessageMapperImpl checkNoticeMessageMapperImpl;
@Autowired
@NoticeEvent(event = "checkEvent")
private CheckNoticeMessageImpl checkNoticeMessageImpl;
/**
* 扫描当前所有的监听事件
* 1、获取当前类的所有含有@NoticeEvent注释的字段
* 2、map遍历 创建 一个EventListener对象 设置对象的名称
* 2.1获取申明时的类型对象(class对象 BCANoticeServiceImpl)并引用
* 2.2判断当前NoticeService 和 NoticeMessageMapperService 类型 是否包含interfaces的类型名称
* 3.3 若包含将fied的字段的值转化为NoticeService或NoticeMessageMapperService类型 set到eventListener中
* @return the event listener
*/
public List getEventListener() {
List eventListeners = new ArrayList<>();
try {
Map> filterClass = getNoticeField(this.getClass());
for (String event : filterClass.keySet()) {
EventListener eventListener = new EventListener();
eventListener.setEventName(event);
for (Field field : filterClass.get(event)) {
Class>[] interfaces = field.getType().getInterfaces();
if (!ArrayUtils.isEmpty(interfaces)) {
List> filterInterfaces = Arrays.stream(interfaces).filter(f -> f.getTypeName() == NoticeService.class.getTypeName()).collect(Collectors.toList());
if (!CollectionUtils.isEmpty(filterInterfaces)) {
field.setAccessible(true);
eventListener.setNoticeService((NoticeService) field.get(this));
}
filterInterfaces = Arrays.stream(interfaces).filter(f -> f.getTypeName() == NoticeMessageMapperService.class.getTypeName()).collect(Collectors.toList());
if (!CollectionUtils.isEmpty(filterInterfaces)) {
field.setAccessible(true);
eventListener.setNoticeMessageMapperService((NoticeMessageMapperService) field.get(this));
}
}
}
eventListeners.add(eventListener);
}
return eventListeners;
} catch (Exception e) {
logger.error("error", e.getMessage());
return eventListeners;
}
}
/** 获取当前类的所有含有@NoticeEvent注解的字释
* 1、获取所有声明的字段
* 2、判断这些申明中是否有 @NoticeEvent这样的注释
* 3、找到后将 @NoticeEvent 中event的值和 当前的字段以键值对的方式存储到Map中
* */
private Map> getNoticeField(Class currentClass) {
Field[] fields = currentClass.getDeclaredFields();
Map> filterClasses = new HashMap<>();
for (Field field : fields) {
if (field.getAnnotation(NoticeEvent.class) != null) {
String event = field.getAnnotation(NoticeEvent.class).event();
if (filterClasses.containsKey(event)) {
if (!filterClasses.get(event).contains(field)) {
filterClasses.get(event).add(field);
}
} else {
List eventClass = new ArrayList<>();
eventClass.add(field);
filterClasses.put(event, eventClass);
}
}
}
return filterClasses;
}
}
1、客户端发送连接
var socket = io.connect('http://172.18.8.12:9999?userId=yaly&status=0&role=2&serverType=crime');
2、服务端在消息处理类NoticeEventHandler.java 中接收连接。
@OnConnect
public void onConnect(SocketIOClient client) {
//连接的时候可以把用户信息传递到服务端
SocketClient socketClient = new SocketClient();
socketClient.setUserId(client.getHandshakeData().getSingleUrlParam("userId"));
socketClient.setStatus(StringUtils.isNotEmpty(client.getHandshakeData().getSingleUrlParam("status")) ? client.getHandshakeData().getSingleUrlParam("status") : "0");
socketClient.setSocketIOClient(client);
socketClient.setServerType(client.getHandshakeData().getSingleUrlParam("serverType"));
socketClient.setUerCode(client.getHandshakeData().getSingleUrlParam("userCode"));
socketClient.setRole(client.getHandshakeData().getSingleUrlParam("role"));
socketManager.register(socketClient);
}
3、当服务端检测客户端断开连接时,服务端关闭当前的连接。
/**
* 添加@OnDisconnect事件,客户端断开连接时调用,刷新客户端信息
*
* @param client the client
*/
@OnDisconnect
public void onDisconnect(SocketIOClient client) {
//失去连接的时候移除用户信息
socketManager.remove(client.getSessionId());
}
1、创建消息体
/**
* 通知消息
* Created by MyGirl on 2017/8/21.
*/
public class NoticeMessage {
/**
* id
*/
private String id;
/**
* 消息体
*/
private Object content;
/**
* 创建的时间
*/
private Date time;
/**
* 发送人
*/
private String senderId;
/**
* 接收人
*/
private String receiverId;
/**
* 订阅的事件名称
*/
private String event;
/**
* Gets event.
*
* @return the event
*/
public String getEvent() {
return event;
}
/**
* Sets event.
*
* @param event the event
*/
public void setEvent(String event) {
this.event = event;
}
/**
* Gets sender id.
*
* @return the sender id
*/
public String getSenderId() {
return senderId;
}
/**
* Sets sender id.
*
* @param senderId the sender id
*/
public void setSenderId(String senderId) {
this.senderId = senderId;
}
/**
* Gets receiver id.
*
* @return the receiver id
*/
public String getReceiverId() {
return receiverId;
}
/**
* Sets receiver id.
*
* @param receiverId the receiver id
*/
public void setReceiverId(String receiverId) {
this.receiverId = receiverId;
}
/**
* Gets id.
*
* @return the id
*/
public String getId() {
return id;
}
/**
* Sets id.
*
* @param id the id
*/
public void setId(String id) {
this.id = id;
}
public Object getContent() {
return content;
}
public void setContent(Object content) {
this.content = content;
}
/**
* Gets time.
*
* @return the time
*/
public Date getTime() {
return time;
}
/**
* Sets time.
*
* @param time the time
*/
public void setTime(Date time) {
this.time = time;
}
}
2、通过客户端订阅发送消息
socket.emit('chatEvent', jsonObject);
3、服务端接收并发送消息
- 接收消息
server.addEventListener(eventListener.getEventName(), java.lang.Object.class, (client, data, ackSender) -> {
if (eventListener.getNoticeService() != null) {
if (eventListener.getNoticeMessageMapperService() != null) {
eventListener.getNoticeService().receive(eventListener.getNoticeMessageMapperService().objectToMessage(data));
}
}
});
- 消息体转化
/**
* 将Object转化为 NoticeMessage对象类型
* 判断接收着是否登陆
* 发送通知 审核席位给操作员发送消息
*/
@Override
public void receive(Object message) {
String json = JSON.toJSONString(message);
SocketClient socketClient = socketManager.get(msg.getReceiverId());
msg.setEvent("checkEvent");
if (socketClient != null) {
socketMessageManager.sendMessage(json);
}
}
- 消息发送
public void sendMessage(String message) {
NoticeMessage noticeMessage = JsonUtil.fromJson(message, NoticeMessage.class);
if (noticeMessage != null) {
SocketClient socketClient = socketManager.get(noticeMessage.getReceiverId());
if (socketClient != null) {
socketIOClient.sendEvent(noticeMessage.getEvent(), noticeMessage);
}
}
}
1、发送消息包含发送单条消息和多条消息
- 在mesageController中添加发送单条消息和多条消息的接口
@ApiOperation(value = "message And Message controller", notes = "给单个用户发送消息")
//ApiResponses swagger相应注解
@ApiResponses(value = {
@ApiResponse(code = 500, message = "Internal Server Error", response = HttpError.class),
@ApiResponse(code = 406, message = "Not Acceptable", response = HttpError.class)})
@RequestMapping(value = "/message/send", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity sendMessage(@RequestBody MessageInfo messageInfo) {
if (StringUtils.isEmpty(messageInfo.getReceiverId()) || StringUtils.isEmpty(messageInfo.getEvent())) {
return new ResponseEntity<>(false, HttpStatus.OK);
}
boolean result = messageService.sendMessageOne(messageInfo);
return new ResponseEntity<>(result, HttpStatus.OK);
}
public boolean sendMessageOne(MessageInfo messageInfo) {
SocketClient socketClient = socketManager.findByUserIdAndRole(messageInfo.getReceiverId(), messageInfo.getRole());
if (socketClient == null) {
return false;
} else {
socketClient.setUserId(messageInfo.getReceiverId());
socketClient.setStatus(messageInfo.getStatus() != null ? messageInfo.getStatus() : "0");
socketManager.register(socketClient);
//发送消息
this.sendMessageToClient(messageInfo);
}
return true;
}
//ApiOperation swagger操作注解
@ApiOperation(value = "message And Message controller", notes = "给多个用户发送消息")
//ApiResponses swagger相应注解
@ApiResponses(value = {
@ApiResponse(code = 500, message = "Internal Server Error", response = HttpError.class),
@ApiResponse(code = 406, message = "Not Acceptable", response = HttpError.class)})
@RequestMapping(value = "/message/sendAll", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity sendMessageAll(@RequestBody List messageInfoList) {
if (messageInfoList.size() == 0) {
return new ResponseEntity<>(false, HttpStatus.OK);
}
boolean result = messageService.sendMessageALl(messageInfoList);
return new ResponseEntity<>(result, HttpStatus.OK);
}
- 在MessageServiceImpl类中添加方法
/**
*(单个) 发送消息
* */
public boolean sendMessageOne(MessageInfo messageInfo) {
SocketClient socketClient = socketManager.findByUserIdAndRole(messageInfo.getReceiverId(), messageInfo.getRole());
if (socketClient == null) {
return false;
} else {
socketClient.setUserId(messageInfo.getReceiverId());
socketClient.setStatus(messageInfo.getStatus() != null ? messageInfo.getStatus() : "0");
socketManager.register(socketClient);
//发送消息
this.sendMessageToClient(messageInfo);
}
return true;
}
/**
* 给(多个用户)发送消息
* */
public boolean sendMessageALl(List messageInfo) {
messageInfo.forEach(message -> {
SocketClient socketClient = socketManager.findByUserIdAndRole(message.getReceiverId(), message.getRole());
if (socketClient != null) {
//更改当前的忙碌状态
socketClient.setUserId(message.getReceiverId());
socketClient.setStatus(message.getStatus() != null ? message.getStatus() : "0");
socketManager.register(socketClient);
//给接收人发送通知
this.sendMessageToClient(message);
}
}
);
return true;
}
/**
* 给客户端用户发送消息
* */
private void sendMessageToClient(MessageInfo message){
NoticeMessage noticeMessage = new NoticeMessage();
noticeMessage.setEvent(message.getEvent());
noticeMessage.setReceiverId(message.getReceiverId());
noticeMessage.setContent(message.getContent());
noticeMessage.setTime(new Date());
socketMessageManager.sendMessage(noticeMessage);
}
- 通过调用api文档来获取指定状态 socket列表
1、创建搜索体
public class SearchInfo {
/**
* 服务类型
* */
private ServerType serverType;
public enum ServerType{
crime,
bdms
}
/**
* 角色
* */
public String roleId;
/**
* 角色列表
* */
public List roleList;
/**
* 状态
* */
public String status;
2、在SocketController中添加搜索的api接口
/*
* 获取空闲的用户列表(通过角色和服务类型)
* @params searchInfo (serverType 服务类型 role角色 roleList 角色列表)
**/
//ApiOperation swagger操作注解
@ApiOperation(value = "socket And Socket controller", notes = "获取空闲的用户列表")
//ApiResponses swagger相应注解
@ApiResponses(value = {
@ApiResponse(code = 500, message = "Internal Server Error", response = HttpError.class),
@ApiResponse(code = 406, message = "Not Acceptable", response = HttpError.class)})
@RequestMapping(value = "/socket/getAll//{search}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity> getUserList(@PathVariable String search) {
List result=new ArrayList<>();
if(StringUtils.isEmpty(search)){
return new ResponseEntity<>(result, HttpStatus.OK);
}
SearchInfo searchInfo = JSON.parseObject(search, SearchInfo.class);
result = socketService.getAllUserList(searchInfo);
return new ResponseEntity<>(result, HttpStatus.OK);
}
3、在SocketServiceImpl中添加搜索方法
/**
* 根据搜索条件获取指定类型的socket
* 分三种
* 第一种 服务类型查询
* 第二中 服务类型+角色
* 第三中 服务类型+角色列表
* */
public List getAllUserList(SearchInfo searchInfo) {
List socketList=new ArrayList<>();
if(StringUtils.isEmpty(searchInfo.getRole()) && CollectionUtils.isEmpty(searchInfo.getRoleList())){
socketList = socketManager.getSocketClientList(searchInfo.getServerType().toString());
}else if(StringUtils.isNotEmpty(searchInfo.getRole())){
socketList = socketManager.getSocketClientListByRole(searchInfo.getServerType().toString(),searchInfo.getRole());
}else if(searchInfo.getRoleList().size()>0){
socketList = socketManager.getSocketClientListByRoleList(searchInfo.getServerType().toString(),searchInfo.getRoleList());
}
List userList = new ArrayList<>();
socketList.forEach(s -> {
User u = new User();
u.setUserId(s.getUserId());
u.setStatus(s.getStatus());
u.setRole(s.getRole());
userList.add(u);
});
return userList;
}