文章目录

  • 1.背景
  • 2.服务端实现
    • 2.1 服务端pom依赖和yml配置
    • 2.2 tio服务端WsMsgHandlerServer
    • 2.3 xxl-job定时任务扫描客户端是否在线然后发钉钉群告警消息
  • 3.客户端实现
    • 3.1 客户端的pom依赖和yml配置
    • 3.2 客户端重试和心跳实现
  • 4.客户端和服务端的demo分享

1.背景

  Tio的官网

https://www.tiocloud.com/tio/index.html

​   由于之前做了一个停车缴费的系统,停车场终端的设备和控制开闸的程序是海康的,公司想节省那点钱要自己搞一个停车缴费的替换海康的那个平台,在这个项目中最主要的一个环节就是对车场的车道终端的开关等指令如何下达且让终端正确执行?所以我们自己写了一个java应用程序部署在车场终端的电脑上,技术选型采用半开源的Tio,采用websocket协议通过客户端向服务端发送心跳来维持客户端的在线,服务端存在一个心跳检测的线程每隔2秒检测一次最后收到客户端心跳包的时间是否超过心跳检测时长,如果超过心跳检测超时时长则会把客户端踢下线,举个栗子:当前时间-最后一次收到心跳包的时间>心跳检测时长(默认是2分钟,该参数可以灵活配置),那么改客户端就会被强制的下线,这个心跳检测的tio的服务端依赖的源码里面有相关的代码,有兴趣的可以去阅读下Tio的源码;服务端可以通过给在线的客户端发送一些指令消息给客户端然后客户端在本地通过httpUtils直接调用海康的终端设备的api接口操作车道的开关锁等一系列的操作,发送给客户端的消息采用AES加密防止被攻击和消息明文传输,基本的原理大概就是这种,在该项目中如果客户端掉线了服务端感知不到,用户支付了停车费后下发的消息就会失败,车场车道出口的杆就不会抬起,所以就需要一个客户端掉线预警的功能通知到技术人员如果有某个终端掉线了10分钟内收到10次相同的告警消息就需要去排查车场的终端程序是否掉线或者网络出现抖动,重启车场终端应用程序后确保客户端能重新上线,不会影响用户支付抬杆出去。

​   Tio的底层是基于Aio的异步io实现了websocket协议,虽然说学习难度没有netty那么的难,但是这种半开源半商业化的产品开源出来的东西真的是有很大的坑在里面的,用一个简单的词汇来形容“阉割版”,就比如:

​   1.服务端没有知道客户端是否在线还是没有在线的功能?如果服务端是单节点部署可以修改下服务端的源码获取到Users对象轻松知道客户端是否在线,但是如果服务端是多节点部署,那么客户端是会在服务端的一个节点上上线和超时后被踢下线然后另一个节点又上线,所以服务端多节点单节点方式修改源码的方式就搞不了。

​   2.服务端的消息是采用的是redis的发布订阅使用的是一个订阅的topic主题,多个业务系统都使用一个消息主题不太好?修改源码可以配置topic避免各个业务系统的消息相互影响。

​    3.提供的客户端的依赖中有心跳、重试的代码,但是基本上是用不成的,我是修改调试过它的源码的,会出一些莫名其妙的错误,服务端和客户端的注释需要使用者自行实现心跳和重试机制?

​    以上这几点是Tio存在的问题,有问题那只能给使用者来填坑了,决绝不了就只能重新技术选型了,没有技术选的只能自己造了,那难度和时间成本就大了。

2.服务端实现

​   服务端采用的技术有:Tio的服务端依赖+xxl-job+dingding发送消息http接口封装+redis

​   服务端demo工程目录结构如下:

​   大致思路如下:客户端上线在服务端完成握手逻辑时候将客户端的信息保存在redis中,过期时间设置为两分钟,客户端的心跳间隔时长是2s,xxl-job任务每隔1分钟扫描一次redis中指定key中是否包含已经接入的车场id配置,如果redis中没有指定的车场的id说明车场的程序已经下线,此时需要发送消息到钉钉群中告警,问题来了,如果某个客户端一直掉线,那不是每隔一分钟钉钉群里都会收到告警消息,这就会造成了钉钉群的消息轰炸,所以需要一个扫描掉线的发消息的次数限制,掉线次数超过10就不会在向钉钉群发送告警消息,当客户端重新上线握手的时候把redis中的掉线扫描次数清空;该功能会有一定的误判产生,扫描任务刚好扫的时候,客户端刚好在服务端的一个节点下线然后在服务端的另外一个节点上线这种情况就还是会发送几次钉钉告警的消息,这种是避免不了的,可以归结为正常的情况忽略钉钉告警消息,不正常的是10分钟内收到同一个终端的10告警消息这种就是终端确实是掉线的告警了。

2.1 服务端pom依赖和yml配置

​    pom依赖

<?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 https://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.3.2.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.dytz.barrier.gate.server</groupId><artifactId>barrier-gate-server</artifactId><version>0.0.1-SNAPSHOT</version><name>barrier-gate-server</name><packaging>jar</packaging><properties><java.version>1.8</java.version><java.version>1.8</java.version><spring-cloud.version>Hoxton.SR6</spring-cloud.version><mybatis-plus-generator.version>3.3.2</mybatis-plus-generator.version><spring-cloud-alibaba.version>2.2.1.RELEASE</spring-cloud-alibaba.version><commons-collections4.vsersion>4.1</commons-collections4.vsersion></properties><repositories><repository><id>nexus</id><url>xxxxxxx</url><releases><enabled>true</enabled><updatePolicy>always</updatePolicy></releases><snapshots><enabled>true</enabled><updatePolicy>always</updatePolicy></snapshots></repository></repositories><distributionManagement><repository><id>nexus-snapshots</id><name>Nexus snapshots</name><url>xxxxxxxx</url></repository><snapshotRepository><id>nexus-snapshots</id><url>xxxxxxxx</url></snapshotRepository></distributionManagement><profiles><profile><id>uat</id><properties><env>uat</env></properties><activation><activeByDefault>true</activeByDefault></activation></profile><profile><id>prod</id><properties><env>prod</env></properties></profile></profiles><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><!-- 常用JSON工具包 --><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.83_noneautotype</version></dependency><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.4</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><!-- Tio服务端依赖集成 --><dependency><groupId>org.t-io</groupId><artifactId>tio-websocket-spring-boot-starter</artifactId><version>3.6.0.v20200315-RELEASE</version></dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-collections4</artifactId><version>4.1</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-test</artifactId></dependency><dependency><groupId>org.testng</groupId><artifactId>testng</artifactId><version>RELEASE</version><scope>compile</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId></dependency><dependency><groupId>com.xuxueli</groupId><artifactId>xxl-job-core</artifactId><version>2.2.0</version></dependency><dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>3.3.0</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-config</artifactId><version>3.1.5</version></dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-collections4</artifactId><version>${commons-collections4.vsersion}</version></dependency><!--nacos--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-context</artifactId><version>2.2.3.RELEASE</version></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-bootstrap</artifactId><version>3.0.1</version></dependency></dependencies><dependencyManagement><dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>${spring-cloud.version}</version><type>pom</type><scope>import</scope></dependency><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-alibaba-dependencies</artifactId><version>${spring-cloud-alibaba.version}</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement><build><resources><resource><directory>src/main/resources</directory><excludes><exclude>application-*.yml</exclude></excludes></resource><resource><directory>src/main/resources</directory><filtering>true</filtering><includes><include>application.yml</include><include>application-${env}*.yml</include></includes></resource></resources><plugins><!-- 跳过deploy --><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-deploy-plugin</artifactId><version>2.8.2</version><configuration><skip>true</skip></configuration></plugin><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-surefire-plugin</artifactId><version>2.22.2</version><configuration><skipTests>true</skipTests></configuration></plugin><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><version>2.3.7.RELEASE</version><executions><execution><goals><goal>repackage</goal></goals></execution></executions></plugin></plugins></build></project>

​    yml配置

fastjson:parser:safeMode: true
# 日记配置
logging:level:com.dytz.barrier.gate.server: debugspring:main:allow-circular-references: trueapplication:name: barrier-gate-server2port: ${SERVER_PORT:8080}cloud:nacos:discovery:enabled: trueserver-addr: ${cloud.nacos_url}service: ${spring.application.name}namespace: ${cloud.nacos_namespace}config:server-addr: ${cloud.nacos_url}file-extension: yamlnamespace: ${cloud.nacos_namespace}redis:database: 6host: #redis地址port: 6389password: #redis密码jedis:pool:max-active: 200max-idle: 20max-wait: 2000min-idle: 5
#Tio配置
tio:websocket:server:port: 19009heartbeat-timeout: 20000cluster:enabled: trueredis:ip: #redis地址port: 6389pool-size: 5minimum-idle-size: 2password: #redis密码user: truegroup: true
xxl:job:admin:addresses: #xxl-job的服务端地址executor:appname: barrier-gate-server2 #xxl-job的执行器应用名称port: 10031 #xxl-job的执行器端口logpath: /logs/xxllogretentiondays: 5dingding:accessToekn: # 钉钉群的机器人的tokenbaseUrl: https://oapi.dingtalk.com/robot/sendsecret: # 钉钉群的机器人的秘钥cloud:nacos_url: #nacos地址nacos_namespace: #nacos的namespace的id

2.2 tio服务端WsMsgHandlerServer

  服务端需要在启动主类上加上:@EnableTioWebSocketServer注解,开启Tio的服务端功能

package com.dytz.barrier.gate.server.ws.handler;import com.alibaba.fastjson.JSON;
import jodd.util.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.tio.core.ChannelContext;
import org.tio.core.Tio;
import org.tio.http.common.HttpRequest;
import org.tio.http.common.HttpResponse;
import org.tio.websocket.common.WsRequest;
import org.tio.websocket.server.handler.IWsMsgHandler;import java.util.Objects;
import java.util.concurrent.TimeUnit;@Slf4j
@Component
public class WsMsgHandlerServer implements IWsMsgHandler {@Autowiredprivate RedisTemplate redisSSTemplate;@Autowiredprivate RedisTemplate redisTemplate;private final String CLINET_KEY = "PARK:";private final String CLINET_ERROR_COUNT_KEY = "PARK:ERROR:COUNT:";/*** ws 会话握手校验,权限登录检查,检查通过后会话绑定userId** @param httpRequest* @param httpResponse* @param channelContext* @return* @throws Exception*/@Overridepublic HttpResponse handshake(HttpRequest httpRequest, HttpResponse httpResponse, ChannelContext channelContext) throws Exception {String clientip = httpRequest.getClientIp();log.info(">>>>>>>>>> clientip : {}", clientip);log.info(">>>>>>>>>> getHeaders : {}", httpRequest.getHeaders());String parkId = httpRequest.getHeaders().get("parkid");System.out.println(parkId);if (Objects.nonNull(parkId)) {log.info("ws request parkId : {} ", parkId);String useridType = "PARK_" + parkId;//Tio.removeUser(channelContext.tioConfig, useridType, "重复登录,如果上次会话没断开主动下线上次会话!");channelContext.setUserid(useridType);Tio.bindUser(channelContext, useridType);log.debug("收到来自{}的ws握手包\r\nhttpRequest{}", clientip, httpRequest);if (redisSSTemplate.hasKey(CLINET_KEY + parkId)) {//String clientId = String.valueOf(redisSSTemplate.opsForValue().get(CLINET_KEY + parkId));//先删除/*RedisOperations<String, ?> ops1 = redisSSTemplate.boundHashOps(CLINET_KEY + parkId).getOperations();ValueOperations<String, ?> sv1 = ops1.opsForValue();String o1 = (String) sv1.get(CLINET_KEY + parkId);log.info("删除客户端前数据:{}", o1);Boolean f1 = ops1.delete(CLINET_KEY + parkId);log.info("删除客户端标志:{}", f1);String o2 = (String) sv1.get(CLINET_KEY + parkId);log.info("删除客户端后数据:{}", o2);redisSSTemplate.opsForValue().set(CLINET_KEY + parkId, parkId, 2, TimeUnit.MINUTES);*///redisSSTemplate.opsForValue().set(CLINET_KEY + parkId,parkId);//存在就不删除也不设置,掉线之后两分钟内key过期后在设置,然后定时任务每一分钟内检查一次终端是否在线,恰好是key过期时间的1/2,所以key检测存,掉线时长大于1分钟可以被检测到。} else {redisSSTemplate.opsForValue().set(CLINET_KEY + parkId, parkId, 2, TimeUnit.MINUTES);Boolean b = redisTemplate.opsForHash().hasKey(CLINET_ERROR_COUNT_KEY + parkId, "downLineCount");if (b) {redisTemplate.delete(CLINET_ERROR_COUNT_KEY + parkId);log.info("删除掉线次数成功,parkId:{}", parkId);}}} else {log.info("parkid 为空");//Tio.remove(channelContext, "parkid 为空!");return httpResponse;}return httpResponse;}@Overridepublic void onAfterHandshaked(HttpRequest httpRequest, HttpResponse httpResponse, ChannelContext channelContext) throws Exception {log.info("==========握手成功=====");}@Overridepublic Object onBytes(WsRequest wsRequest, byte[] bytes, ChannelContext channelContext) throws Exception {return null;}@Overridepublic Object onClose(WsRequest wsRequest, byte[] bytes, ChannelContext channelContext) throws Exception {log.info("==========关闭连接=====");Tio.remove(channelContext, "receive close flag");return null;}@Overridepublic Object onText(WsRequest wsRequest, String s, ChannelContext channelContext) throws Exception {log.info("收到ws消息:{}", s);if (StringUtil.isNotEmpty(s) && Objects.equals("007", JSON.parseObject(s).getString("code"))) {log.info("心跳,心跳车场 : {} ", channelContext.userid);//发送心跳消息给客户端String msg = null;//String msg = "{\"code\":\"008\",\"data\":\"你好,我是服务端!\"}";return msg;}return null;}
}

2.3 xxl-job定时任务扫描客户端是否在线然后发钉钉群告警消息

package com.dytz.barrier.gate.server.job;import com.dytz.barrier.gate.server.config.ClientsConfig;
import com.dytz.barrier.gate.server.utils.DingDingUtil;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.handler.annotation.XxlJob;
import com.xxl.job.core.log.XxlJobLogger;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;import java.util.Objects;@Slf4j
@Component
public class ClientOnlineJobHandler {@Autowiredprivate RedisTemplate redisSSTemplate;@Autowiredprivate RedisTemplate redisTemplate;@Autowiredprivate ClientsConfig clientConfig;private final String CLINET_KEY = "PARK:";private final String CLINET_ERROR_COUNT_KEY = "PARK:ERROR:COUNT:";@XxlJob("clientOnlineJobHandler")public ReturnT<String> clientOnlineJobHandler(String param) {if (clientConfig.getOpenFlag().equals("1")) {XxlJobLogger.log("===================clientOnlineJobHandler====================");for (String pkId : clientConfig.getClients()) {if (redisSSTemplate.hasKey(CLINET_KEY + pkId)) {String blrStr = String.valueOf(redisSSTemplate.opsForValue().get(CLINET_KEY + pkId));if (StringUtils.isNotEmpty(blrStr)) {log.info("clientId:{}在线", pkId);XxlJobLogger.log("============在线设备pkId:{}===============", pkId);continue;}}redisTemplate.opsForHash().increment(CLINET_ERROR_COUNT_KEY + pkId, "downLineCount", 1);if (this.checkFailCount(pkId)) {XxlJobLogger.log("============疑似掉线设备pkId:{}===============", pkId);StringBuffer sb = new StringBuffer("终端设备id【").append(pkId).append("】疑似掉线");DingDingUtil.sendDingDingMarkdownGroupMsgAtAll("终端设备在线检测", sb.toString());}}}return ReturnT.SUCCESS;}/*** 掉线次数统计限制发消息上限检查** @param parkId* @return*/private Boolean checkFailCount(String parkId) {Boolean b = redisTemplate.opsForHash().hasKey(CLINET_ERROR_COUNT_KEY + parkId, "downLineCount");if (b) {Long count = (Long) redisTemplate.opsForHash().get(CLINET_ERROR_COUNT_KEY + parkId, "downLineCount");log.info("=========failCount=========" + count);if (Objects.nonNull(count) && count > Integer.valueOf(clientConfig.getErrorCount())) {log.info("=========failCount大于10=========");//不删除该key//redisTemplate.delete(RefreshTokenConstants.BW_FAIL_COUNT + appNo);return Boolean.FALSE;}}return Boolean.TRUE;}}

3.客户端实现

3.1 客户端的pom依赖和yml配置

   pom依赖

<?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 https://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.7.2</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.dytz.barrier.gate.client</groupId><artifactId>barrier-gate-client</artifactId><version>0.0.1-SNAPSHOT</version><name>barrier-gate-client</name><packaging>jar</packaging><properties><java.version>18</java.version><netty-all.version>4.1.79.Final</netty-all.version></properties><repositories><repository><id>nexus</id><url>xxxx</url><releases><enabled>true</enabled><updatePolicy>always</updatePolicy></releases><snapshots><enabled>true</enabled><updatePolicy>always</updatePolicy></snapshots></repository></repositories><distributionManagement><repository><id>nexus-snapshots</id><name>Nexus snapshots</name><url>xxxxxx</url></repository><snapshotRepository><id>nexus-snapshots</id><url>xxxxxxxx</url></snapshotRepository></distributionManagement><profiles><profile><id>uat</id><properties><env>uat</env></properties><activation><activeByDefault>true</activeByDefault></activation></profile><profile><id>prod</id><properties><env>prod</env></properties></profile></profiles><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><!--  常用JSON工具包 --><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.83_noneautotype</version></dependency><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.4</version></dependency><!--  Tio客户端工具包依赖 --><dependency><groupId>org.t-io</groupId><artifactId>tio-utils</artifactId><version>3.6.0.v20200315-RELEASE</version></dependency><dependency><groupId>org.t-io</groupId><artifactId>tio-core</artifactId><version>3.6.0.v20200315-RELEASE</version></dependency><dependency><groupId>org.t-io</groupId><artifactId>tio-websocket-client</artifactId><version>3.6.0.v20200315-RELEASE</version></dependency><!--  Tio客户端工具包依赖 --></dependencies><build><resources><resource><directory>src/main/resources</directory><excludes><exclude>application-*.yml</exclude></excludes></resource><resource><directory>src/main/resources</directory><filtering>true</filtering><includes><include>application.yml</include><include>application-${env}*.yml</include></includes></resource></resources><plugins><!-- 跳过deploy --><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-deploy-plugin</artifactId><version>2.8.2</version><configuration><skip>true</skip></configuration></plugin><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-surefire-plugin</artifactId><version>2.22.2</version><configuration><skipTests>true</skipTests></configuration></plugin><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.6.1</version><configuration><source>1.8</source><target>1.8</target><encoding>UTF-8</encoding></configuration></plugin><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><version>2.2.9.RELEASE</version></plugin></plugins></build>
</project>

   yml配置

fastjson:parser:safeMode: true
# 日记配置
logging:level:com.dytz.barrier.gate.client: debugwsServerUrl: ws://127.0.0.1:19009/ws

3.2 客户端重试和心跳实现

package com.dytz.barrier.gate.client.ws;import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.dytz.barrier.gate.client.msg.SendMsg;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.StringUtils;
import org.tio.client.ClientChannelContext;
import org.tio.core.ChannelContext;
import org.tio.websocket.client.WebSocket;
import org.tio.websocket.client.WsClient;
import org.tio.websocket.client.config.WsClientConfig;
import org.tio.websocket.common.WsPacket;import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;/*** @author zlf* @description:* @time: 2022/8/19 15:24*/
@Slf4j
@Configuration
public class MyWsClientConfig {@Value("${wsServerUrl}")private String wsServerUrl;@Autowiredprivate ApplicationArguments applicationArguments;private WsClient wsClient;private ClientChannelContext cxt;private WebSocket ws;private static String parkId;@PostConstructpublic void init() {try {Map<String, String> additionalHttpHeaders = new HashMap<>();List<String> nonOptionArgs = applicationArguments.getNonOptionArgs();if (!nonOptionArgs.isEmpty()) {parkId = nonOptionArgs.get(0);}if (StringUtils.isEmpty(parkId)) {throw new RuntimeException("请配置parkId之后在启动!");}log.info("parkId {}", parkId);additionalHttpHeaders.put("parkid", parkId);SendMsg sendMsg = new SendMsg();sendMsg.setCode("2000");sendMsg.setParkId(parkId);String msg = JSON.toJSONString(sendMsg);additionalHttpHeaders.put("parkid", "13");WsClientConfig wsClientConfig = new WsClientConfig(e -> {log.info("emit open");},e -> {WsPacket data = e.data;if (Objects.nonNull(data)) {String dataStr = data.getWsBodyText();log.info("recv " + dataStr);JSONObject jsonObject = JSON.parseObject(dataStr);String code = jsonObject.getString("code");String dataJson = jsonObject.getString("data");log.info("收到服务端消息数据:{}", jsonObject.toJSONString());if ("008".equals(code)) { //心跳数据包log.info("收到心跳数据:{}", jsonObject.toJSONString());} else { //非心跳数据//业务处理}}},e -> {log.info(String.format("emit close: %d, %s, %s", e.code, e.reason, e.wasClean));wsClient.close();wsClient = null;},e -> {log.info(String.format("emit error: %s", e.msg));wsClient.close();wsClient = null;},Throwable::printStackTrace);wsClient = WsClient.create(wsServerUrl, additionalHttpHeaders, wsClientConfig);ws = wsClient.connect();Thread thread = new Thread(() -> {while (true) {if (Objects.isNull(wsClient)) {try {wsClient = WsClient.create(wsServerUrl, additionalHttpHeaders, wsClientConfig);ws = wsClient.connect();} catch (Exception e) {e.printStackTrace();try {Thread.sleep(1000L);} catch (InterruptedException e2) {e2.printStackTrace();}try {ws = wsClient.connect();} catch (Exception e1) {e1.printStackTrace();}}}if (Objects.isNull(wsClient)) {continue;}cxt = wsClient.getClientChannelContext();if (Objects.isNull(cxt)) {continue;}ChannelContext.CloseCode closeCode = cxt.getCloseCode();if (2 == closeCode.getValue() || cxt.isClosed || cxt.isRemoved) {wsClient.close();wsClient = null;if (Objects.isNull(wsClient)) {continue;}}if (Objects.nonNull(ws)) {//发送心跳ws.send("{\"code\":\"007\",\"data\":\"你好,我是客户端!\"}");try {Thread.sleep(2000L);} catch (InterruptedException e) {e.printStackTrace();}}}});thread.start();} catch (Exception e) {e.printStackTrace();wsClient.close();wsClient = null;}}
}

4.客户端和服务端的demo分享

链接:https://pan.baidu.com/s/16jlic4LEu5RE7-nMsWfupA
提取码:0965

Tio实现检测客户端是否在线发送钉钉群消息相关推荐

  1. PHP文件在线检测病毒,VIRSCAN 在线病毒检测客户端

    /**** 本程序以及提供的源码仅供技术交流使用. 作者不作任何类型担保,在任何情况下都不对使用本软件造成的任何损失或任何相应而生. 间接.附带的损失承担任何责任. ****/importwin.ui ...

  2. 钉钉如何群里定时发送文件_使用钉钉APP实现多人在线协同编辑文档

    钉钉是阿里系综合智能移动办公平台软件,利用钉钉平台,可以完成基本的线上办公功能. 使用钉钉进行对文档的多人在线协同编辑是一种非常便捷的办公应用. 钉钉支持电脑端.手机端等多平台操作. 下面以手机端钉钉 ...

  3. Python+Selenium检测TAPD是否有未读消息,有则通过DingtalkChatbot发送钉钉机器人

    文章目录 1.需求场景 2.实现思路 3.代码实现 4.踩到的坑 5.拓展资料 1.需求场景 今天产品来跟我抱怨,说最近好忙啊,忙的总是忘了去处理TAPD更新的任务,虽然TAPD绑定了QQ邮箱,但是公 ...

  4. 仿钉钉时事通讯IM客户端

    钉钉的功能集成的很多,这里仿用了最核心的聊天功能,包括表情,截图,图片编辑,图片查看器,浏览器等.windows平台+Qt技术 崩溃dump生成,浏览器集成,socket,h5混合编程,Qt. 先做个 ...

  5. #yyds盘点#如何用Python发送告警通知到钉钉?

    如何用Python发送告警通知到钉钉? 一.前言 前不久,看到了明哥写的如何用Python发送警告通知到企业微信,想起来之前写过用Pytho发送指定格式数据到钉钉的服务,本文将之前的代码重构下,变成一 ...

  6. 钉钉老版本下载3.31_钉钉旧版pc下载-钉钉旧版pc客户端下载-西西软件下载

    钉钉旧版pc版客户端是为了大家需要准备的,不习惯使用新版本的新版本的小伙伴们,想要使用旧版本的,小编就给大家提供旧版本的软件,让小伙伴们可以放心的进行使用,还等什么,赶紧试试吧. 软件介绍: 钉钉,是 ...

  7. php--api发送钉钉消息

    php--api发送钉钉消息 一.钉钉开发者配置 登录钉钉开发管理后台 https://open-dev.dingtalk.com/#/corpeapp 进入应用开发,根据需求选择应用创建类型,此处以 ...

  8. 使用钉钉发送消息(可用于 服务异常通知、定时任务异常通知 等等...)

    一.前言 服务器上有时 定时任务.重要接口 等出现异常,导致数据不正常,不能及时通知到服务负责人,及时处理问题.所以引入"钉钉"作为通知工具,当服务出现异常便可立即收到通知,及时处 ...

  9. 钉钉开放平台-小程序开发实战(钉钉小程序客户端)

    文章目录 钉钉小程序客户端 关于钉钉开放平台 一.小程序基础 1. 基本概念 2. 小程序页面基础 页面运行机制 页面生命周期 页面栈 页面跳转 怎么使用小程序的data-*属性? 3. 小程序常用布 ...

最新文章

  1. Javascript社区是时候接受async/await语法了
  2. python手机版下载苹果版-Pyto-Python3
  3. 华南理工计算机考试题,华南理工考研计算机历年真题
  4. 项目管理论坛_【项目管理论坛】 第15期:如何做一名优秀的项目经理
  5. python3扫雷代码_python3 命令行 扫雷
  6. Stm32之通用定时器复习
  7. Office 2003 打开RMS权限保护文档时出错
  8. java jsp输出乱码,这是小弟我的JSP文件,输出后,汉字显示为乱码,怎么解决
  9. office工具包开源了,使用Excel导入导出非常方便
  10. Migrate Project to Gradle? This project does not use the Gradle build system
  11. 3-unit4 postfix+mysql
  12. 小菜找实习——阿里3月27日场笔试第一题
  13. 山海经异兽录找不到服务器,星辰山海经异兽录
  14. opengl——贴图
  15. Windows Xp SP3 chs 简体中文版下载
  16. python中ospathjoin_python-在Windows上与os.path.join混合斜杠
  17. 视频图像传输与显示(3)——复合视频信号CVBS解析
  18. python中年月日时分秒格式
  19. 中国移动支付全球领先!成中国“新四大发明“
  20. 免费的网页原型制作工具

热门文章

  1. python文档整理,Python官方文档内置函数整理Word版
  2. houdini 解决opengl 3.3 远程桌面错误
  3. 基础篇章:React Native之 Image 的讲解
  4. 【【重要通知】HCIE-Routing Switching切换HCIE-Datacom补充公告】
  5. 【异常检测】DAGMM:结合深度自编码器器和GMM的端到端无监督网络(二):代码实战(PyTorch)...
  6. 唐僧是怎么管理孙悟空的?
  7. 武汉理工校训计算机工程学,"厚德博学,追求卓越"武汉理工大学校训
  8. 三个死刑犯的自白,哪个最牛?
  9. QT 小游戏 : 别踩白块儿~
  10. 艾美捷CpG ODN系列——ODN 2006 (TLRGRADE)说明