博客
关于我
Springboot2模块系列:websocket(即时消息推送)
阅读量:253 次
发布时间:2019-03-01

本文共 7822 字,大约阅读时间需要 26 分钟。

1 配置

1.0 pom.xml

org.springframework.boot
spring-boot-starter-websocket
org.springframework.boot
spring-boot-starter-thymeleaf

1.2 静态文件加载

package com.company.system.config;import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;import org.springframework.web.servlet.config.annotation.InterceptorRegistry;import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;import org.springframework.context.annotation.Configuration;@Configurationpublic class StaticResourceLoadingConfig implements WebMvcConfigurer{       @Override     public void addResourceHandlers(ResourceHandlerRegistry registry){           registry.addResourceHandler("/**")        .addResourceLocations("classpath:/resources/")        .addResourceLocations("classpath:/static/")        .addResourceLocations("classpath:/css");    } }

1.2 websocket配置

package com.company.system.config;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.web.socket.server.standard.ServerEndpointExporter;/** * 开启WebSocket支持 */@Configuration  public class WebSocketConfig {        @Bean      public ServerEndpointExporter serverEndpointExporter() {             return new ServerEndpointExporter();    }  }

2 Websocket路由

package com.company.system.config;import java.io.IOException;import java.util.concurrent.ConcurrentHashMap;import javax.websocket.OnClose;import javax.websocket.OnError;import javax.websocket.OnMessage;import javax.websocket.OnOpen;import javax.websocket.Session;import javax.websocket.server.PathParam;import javax.websocket.server.ServerEndpoint;import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.JSONObject;import org.apache.commons.lang.StringUtils;import org.springframework.stereotype.Component;import org.slf4j.Logger;import org.slf4j.LoggerFactory;@ServerEndpoint("/imserver/{userId}")@Componentpublic class WebSocketServerConfig {       static Logger log=LoggerFactory.getLogger(WebSocketServerConfig.class);    //当前在线连接数    private static int onlineCount = 0;    //concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象    private static ConcurrentHashMap
webSocketMap = new ConcurrentHashMap<>(); //与某个客户端的连接会话,需要通过它来给客户端发送数据 private Session session; //接收userId private String userId=""; /** * 连接建立成功调用的方法*/ @OnOpen public void onOpen(Session session,@PathParam("userId") String userId) { this.session = session; this.userId=userId; if(webSocketMap.containsKey(userId)){ webSocketMap.remove(userId); webSocketMap.put(userId,this); //加入set中 }else{ webSocketMap.put(userId,this); //加入set中 addOnlineCount(); //在线数加1 } log.info("用户连接:"+userId+",当前在线人数为:" + getOnlineCount()); try { sendMessage("我在线上"); } catch (IOException e) { log.error("用户:"+userId+",网络异常!!!!!!"); } } /** * 连接关闭调用的方法 */ @OnClose public void onClose() { if(webSocketMap.containsKey(userId)){ webSocketMap.remove(userId); //从set中删除 subOnlineCount(); } log.info("用户退出:"+userId+",当前在线人数为:" + getOnlineCount()); } /** * 收到客户端消息后调用的方法 * @param message 发送的信息 * @param session 会话 */ @OnMessage public void onMessage(String message, Session session) { log.info("用户消息:"+userId+",报文:"+message); //可以群发消息 //消息保存到数据库、redis if(StringUtils.isNotBlank(message)){ try { //解析发送的报文 JSONObject jsonObject = JSON.parseObject(message); //追加发送人(防止串改) jsonObject.put("fromUserId",this.userId); String toUserId=jsonObject.getString("toUserId"); //传送给对应toUserId用户的websocket if(StringUtils.isNotBlank(toUserId)&&webSocketMap.containsKey(toUserId)){ webSocketMap.get(toUserId).sendMessage(jsonObject.toJSONString()); }else{ log.error("请求的userId:"+toUserId+"不在该服务器上"); //否则不在这个服务器上,发送到mysql或者redis } }catch (Exception e){ e.printStackTrace(); } } } /** * 数据传输错误 * @param session 会话 * @param error 错误 */ @OnError public void onError(Session session, Throwable error) { log.error("用户错误:"+this.userId+",原因:"+error.getMessage()); error.printStackTrace(); } /** * 服务端向客户端推送消息 * @param message 推送的消息 * @throws IOException 抛出异常 */ public void sendMessage(String message) throws IOException { this.session.getBasicRemote().sendText(message); } /** * 自定义发送消息 * @param message 发送的消息 * @param userId 用户ID * @throws IOException 抛出异常 */ public static void sendInfo(String message,@PathParam("userId") String userId) throws IOException { log.info("发送消息到:"+userId+",报文:"+message); if(StringUtils.isNotBlank(userId)&&webSocketMap.containsKey(userId)){ webSocketMap.get(userId).sendMessage(message); }else{ log.error("用户"+userId+",不在线!"); } } public static synchronized int getOnlineCount() { return onlineCount; } public static synchronized void addOnlineCount() { WebSocketServerConfig.onlineCount++; } public static synchronized void subOnlineCount() { WebSocketServerConfig.onlineCount--; }}

3 自定义回调接口

package com.company.system.controller;import java.util.Map;import java.util.HashMap;import com.company.system.config.WebSocketServerConfig;import org.springframework.http.ResponseEntity;import org.springframework.web.bind.annotation.CrossOrigin;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PathVariable;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestMethod;import org.springframework.web.bind.annotation.RequestBody;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;import org.springframework.stereotype.Controller;import org.springframework.web.servlet.ModelAndView;import java.io.IOException;@CrossOrigin(origins="*", maxAge=3600)@RequestMapping("/websocket")@Controllerpublic class MsgController {       /**     * web消息发送测试     * @return     */    @GetMapping("/instance/message")    public String page(){           return "websocket/chatpage";    }    /**     * 回调接口,向客户端推送实时消息     * @param params 消息数据结构     * @param toUserId 发动到用户ID     * @return     * @throws IOException 异常     */    @RequestMapping(value="/push/{toUserId}", method=RequestMethod.POST)    @ResponseBody    public Map pushToWeb(@RequestBody Map params,@PathVariable String toUserId) throws IOException {           String message = params.get("message").toString();        WebSocketServerConfig.sendInfo(message,toUserId);        Map returnMap = new HashMap();        returnMap.put("code", 200);        returnMap.put("msg", "成功发送消息");        return returnMap;    }}

4 对话消息展示

4.1 前端html

    
websocket即时消息

当前用户

好友

消息记录

操作

消息记录

4.2 对话展示

浏览器打开两个页面,分别访问:http://localhost:10106/websocket/instance/message

在这里插入图片描述

图4.1 用户A

在这里插入图片描述

图4.2 用户B

4.3 接口推送消息

通过接口给小红发送消息,在用户小红对话框历史中可以看到该条记录:测试消息.

若使用接口包含中文,提示Error: Request path contains unescaped characters,
将URL中的中文编码:
在这里插入图片描述

图4.3 修改数据编码

编码后的URL:http://localhost:10106/websocket/push/%E5%B0%8F%E8%8A%B1

在这里插入图片描述

图4.4 接口推送消息

在这里插入图片描述

图4.5 小红的历史消息记录

【参考文献】

[1]
[2]
[3]
[4]
[5]
[6]
[7]

转载地址:http://npht.baihongyu.com/

你可能感兴趣的文章
MySQL 是如何加锁的?
查看>>
MySQL 是怎样运行的 - InnoDB数据页结构
查看>>
mysql 更新子表_mysql 在update中实现子查询的方式
查看>>
MySQL 有什么优点?
查看>>
mysql 权限整理记录
查看>>
mysql 权限登录问题:ERROR 1045 (28000): Access denied for user ‘root‘@‘localhost‘ (using password: YES)
查看>>
MYSQL 查看最大连接数和修改最大连接数
查看>>
MySQL 查看有哪些表
查看>>
mysql 查看锁_阿里/美团/字节面试官必问的Mysql锁机制,你真的明白吗
查看>>
MySql 查询以逗号分隔的字符串的方法(正则)
查看>>
MySQL 查询优化:提速查询效率的13大秘籍(避免使用SELECT 、分页查询的优化、合理使用连接、子查询的优化)(上)
查看>>
mysql 查询数据库所有表的字段信息
查看>>
【Java基础】什么是面向对象?
查看>>
mysql 查询,正数降序排序,负数升序排序
查看>>
MySQL 树形结构 根据指定节点 获取其下属的所有子节点(包含路径上的枝干节点和叶子节点)...
查看>>
mysql 死锁 Deadlock found when trying to get lock; try restarting transaction
查看>>
mysql 死锁(先delete 后insert)日志分析
查看>>
MySQL 死锁了,怎么办?
查看>>
MySQL 深度分页性能急剧下降,该如何优化?
查看>>
MySQL 深度分页性能急剧下降,该如何优化?
查看>>