项目需求
后台管理系统用户小铃铛,消息推送功能并展示有多少条消息或者小红点
代码展示
客户端代码
1.引入 socketIo.js
<script src="/js/socket.io/socket.io.js"></script>
2. 自定义 socket.io 的请求处理
<script src="/js/socket.io/socket-tools.js?t=<%=date.getTime()%>"></script>
主要代码
// 创建socket 在后端项目中配置的socket 的端口
var socket = io("http://localhost:9098");// 用于连接后端的服务
/*
* 创建自定义事件 'news' 作用:接受服务端 socket.emit('news', 数据); 发出的数据
*/
socket.on('connect', function() {
socket.emit('accept_send', JSON.stringify({
//登录用户id和客户机的ip 作为请求socekt.io 的一个表示
sysUserId : sysUserId
}));
});
socket.on('accept_response', function(data) {
var obj = JSON.parse(data);
// 输出服务端响应了数据
if (obj.length > 99) {
$("#sys_message_count").html("99+");
} else {
$("#sys_message_count").html(obj.length);
}
var html="";
for (var i=0 ;i< obj.length && i<4;i++) {
//小铃铛内容设置
html +="<li class=\"msg-list\">" +
"<a οnclick=\"Detail(\'"+obj[i].id+"\',\'"+obj[i].type+"\')\" target=\"toolsAdmin\">"+
"<p class=\"msg-title\">" +obj[i].title+"</p>"+
"<p class=\"msg-content\">" +obj[i].content.substr(0,20)+"..."+"</p>"+
"<p class=\"msg-time\">"+obj[i].createTimeTo+"</p></a></li>";
}
html+="";
console.log(html);
//删除ui 内容重新 然后重新添加
$("#message_show").find("li").remove();
$("#message_show").append(html);
});
服务端代码
服务端pom 主要是 netty-socketio
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.4.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<groupId>org.example</groupId>
<artifactId>SOC_IM</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<!-- <scope>runtime</scope> -->
</dependency>
<!-- https://github.com/mrniko/netty-socketio -->
<dependency>
<groupId>com.corundumstudio.socketio</groupId>
<artifactId>netty-socketio</artifactId>
<version>1.7.7</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.72</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.7</version>
</dependency>
</dependencies>
</project>
application.properties 文件 配置也是从别的地方拿的 没有深究每一项的配置
server.port=8084
spring.datasource.driverClassName=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://local:3306/xkb?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&autoReconnect=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=123456
# 搜索指定包别名
#mybatis.typeAliasesPackage=com.xkb.ctc
# 配置mapper的扫描,找到所有的mapper.xml映射文件
#mybatis.mapperLocations=classpath*:mybatis/mapper/**/*Mapper.xml
# 加载全局的配置文件
#mybatis.configLocation=classpath:mybatis/mapper/mybatis-config.xml
#============================================================================
# netty socket io setting
#============================================================================
# host在本地测试可以设置为localhost或者本机IP,在Linux服务器跑可换成服务器IP
socketio.host=localhost
socketio.port=9098
# 设置最大每帧处理数据的长度,防止他人利用大数据来攻击服务器
socketio.maxFramePayloadLength=1048576
# 设置http交互最大内容长度
socketio.maxHttpContentLength=1048576
# socket连接数大小(如只监听一个端口boss线程组为1即可)
socketio.bossCount=1
socketio.workCount=100
socketio.allowCustomRequests=true
# 协议升级超时时间(毫秒),默认10秒。HTTP握手升级为ws协议超时时间
socketio.upgradeTimeout=1000000
# Ping消息超时时间(毫秒),默认60秒,这个时间间隔内没有接收到心跳消息就会发送超时事件
socketio.pingTimeout=6000000
# Ping消息间隔(毫秒),默认25秒。客户端向服务器发送一条心跳消息间隔
socketio.pingInterval=25000
Application.java
@SpringBootApplication
@EnableAutoConfiguration
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
import java.util.Collection;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* 存放前端链接用户ip 与sysUserId
* 原版本只保存了客户端连接的ip 会导致推送 错误,需要绑定到对应的用户
* @author K
*
*/
public class UserInfoMapUtil {
public static ConcurrentMap<String, Object> webUserInfoMap = new ConcurrentHashMap<>();
public static void put(String key,Object object) {
webUserInfoMap.put(key, object);
}
public static Object get(String key) {
return webUserInfoMap.get(key);
}
public static void remove(String key) {
webUserInfoMap.remove(key);
}
public static Collection<Object> getValues(){
return webUserInfoMap.values();
}
public static ConcurrentMap<String, Object> getWebUserInfoMap(){
return webUserInfoMap;
}
}
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import com.corundumstudio.socketio.SocketIOServer;
/**
* 服务启动执行,SpringBoot启动之后执行
* @author K
*
*/
@Primary
@Component
public class ServerRunner implements CommandLineRunner{
private final Logger log = LoggerFactory.getLogger(getClass());
@Autowired
private SocketIOServer socketIOServer;
@Override
public void run(String... args) throws Exception
{
socketIOServer.start();
log.info("socket.io启动成功!");
}
}
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.corundumstudio.socketio.SocketConfig;
import com.corundumstudio.socketio.SocketIOServer;
import com.corundumstudio.socketio.annotation.SpringAnnotationScanner;
@Configuration
public class SocketIOConfig {
@Value("${socketio.host}")
private String host;
@Value("${socketio.port}")
private Integer port;
@Value("${socketio.bossCount}")
private int bossCount;
@Value("${socketio.workCount}")
private int workCount;
@Value("${socketio.allowCustomRequests}")
private boolean allowCustomRequests;
@Value("${socketio.upgradeTimeout}")
private int upgradeTimeout;
@Value("${socketio.pingTimeout}")
private int pingTimeout;
@Value("${socketio.pingInterval}")
private int pingInterval;
/**
* 以下配置在上面的application.properties中已经注明
* @return
*/
@Bean
public SocketIOServer socketIOServer()
{
SocketConfig socketConfig = new SocketConfig();
socketConfig.setTcpNoDelay(true);
socketConfig.setSoLinger(0);
com.corundumstudio.socketio.Configuration config = new com.corundumstudio.socketio.Configuration();
config.setSocketConfig(socketConfig);
config.setHostname(host);
config.setPort(port);
socketConfig.setReuseAddress(true);//添加配置
config.setBossThreads(bossCount);
config.setWorkerThreads(workCount);
config.setAllowCustomRequests(allowCustomRequests);
config.setUpgradeTimeout(upgradeTimeout);
config.setPingTimeout(pingTimeout);
config.setPingInterval(pingInterval);
return new SocketIOServer(config);
}
@Bean
public SpringAnnotationScanner springAnnotationScanner(SocketIOServer socketServer) {
return new SpringAnnotationScanner(socketServer);
}
}
package com.soc.im.service;
import com.alibaba.fastjson.JSONObject;
import com.corundumstudio.socketio.AckRequest;
import com.corundumstudio.socketio.SocketIOClient;
import com.corundumstudio.socketio.annotation.OnConnect;
import com.corundumstudio.socketio.annotation.OnDisconnect;
import com.corundumstudio.socketio.annotation.OnEvent;
import com.soc.im.utils.UserInfoMapUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.stereotype.Component;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Component
@EnableScheduling // 开启定时任务
public class SocketIoServer {
private static final Logger log= LoggerFactory.getLogger(SocketIoServer.class);
private static Map<String, SocketIOClient> clientMap = new ConcurrentHashMap<>();
@OnConnect
public void onConnect(SocketIOClient client) {
String address = client.getSessionId().toString();
log.info(address + "-------------------------" + "客户端已连接");
clientMap.put(address, client);
UserInfoMapUtil.put(address,client);
}
@OnDisconnect
public void onDisconnect(SocketIOClient client) {
String address = client.getSessionId().toString();
log.info(address + "-------------------------" + "客户端已断开连接");
clientMap.remove(address);
UserInfoMapUtil.remove(address);
}
@OnEvent(value = "accept_send")
public void onEvent(SocketIOClient client, AckRequest ackRequest, String data) {
/**
1.客户端推送advert_info事件时,onData接受数据,
2.这里是string类型的json数据,还可以为Byte[],object其他类型
3.获取客户端连接的ip
4. sysUserId与 前端页面socket.io 请求 传参属性名称一致
*/
String address = client.getSessionId().toString();
log.info("address:{}",address);
log.info(address + ":客户端:************" + data);
JSONObject gpsData = (JSONObject) JSONObject.parse(data);
String sysUserId = gpsData.get("sysUserId") + "";
UserInfoMapUtil.put(address, sysUserId);
clientMap.put(address, client);
// 这里处理相关业务然后返回 String message 给前端
log.info("返回客户端IP:" + address+"ID: "+sysUserId+"message: ");
client.sendEvent("accept_response", "message");
}
/**
1.获取全部客户端
2.获取客户端连接的ip
定时更新小铃铛信息
*/
@Scheduled(cron = "0 0/30 * * * ?")
public void pushMessageAllClients() {
logger.info("开始-------->pushMessageAllClients...{}",System.currentTimeMillis());
clientMap.forEach((address, client)->{
Object object = UserInfoMapUtil.get(address);
logger.info("用户信息...{}",String.valueOf(object));
String message = sysMessage(String.valueOf(object));
client.sendEvent("accept_response", message);
logger.info("sendEvent:{}",client);
});
}
public static Map<String, SocketIOClient> getClientMap() {
return clientMap;
}
public static void setClientMap(Map<String, SocketIOClient> clientMap) {
SocketIoServer.clientMap = clientMap;
}
}