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

文章目录

  • 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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/19854.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

接入钉钉API发送企业消息

工作中有个需求&#xff0c;是要把录入的销售机会由系统自动分配给销售&#xff0c;然后对接钉钉给销售人员发送企业消息&#xff0c;通知他进行跟单。 1. 获取Access_Token Access_Token是企业访问钉钉开放平台全局接口的唯一凭证&#xff0c;即调用接口时需携带Access_Token…

钉钉通知-调用钉钉发送企业内部消息开发

首先&#xff0c;我们要明确需求&#xff1a;自己的系统需要发送通知消息到用户&#xff0c;接收消息用户为同一企业内的人员&#xff0c;选用短信可能涉及到费用问题&#xff0c;故可以选用钉钉或者企业微信&#xff0c;在此我使用钉钉进行发送消息。 调用钉钉发送企业内部消…

Prometheus+Alertmanager+webhook-dingtalk实现钉钉告警

文章目录 一、前提准备及规划二、安装及启动2.1 Prometheus安装启动2.2 Node_export安装启动2.3 Alertmanager安装启动2.4 Webhook-dingtalk安装启动 三、配置及测试3.1 Webhook-dingtalk配置钉钉webhook地址3.2 Alertmanager配置钉钉告警3.3 Prometheus集成Alertmanager及告警…

和chatgpt学架构02-环境搭建

目录 1 安装vs code2 vs code功能介绍3 安装nodejs4 安装vue5 在vs code打开工程总结 我们在上一篇 技术选型 里咨询了chatgpt前后端的框架选择和数据库的选择。有了框架之后就需要选择合适的开发工具了&#xff0c;继续咨询一下chatgpt 我现在选型&#xff0c;前端使用vue&am…

〖编程初学者的自我修养 - 职业规划篇①〗- 大学生选择职业前的自我认知与剖析

历时18个月&#xff0c;采访 850 得到的需求。 不管你是在校大学生、研究生、还是在职的小伙伴&#xff0c;该专栏有你想要的职业规划、简历、面试的答案。说明&#xff1a;该文属于 编程初学者的自我修养 专栏&#xff0c;购买任意白宝书体系化专栏可加入易编程社区&#xff0…

谷歌Bard被曝剽窃ChatGPT?BERT一作跳槽OpenAI,揭惊天内幕

【导读】谷歌有大麻烦了&#xff01;外媒爆料说&#xff0c;Bard的训练数据部分来自ChatGPT。谷歌可能跳到黄河里也洗不清了。 3月29日&#xff0c;外媒The Information曝出了一个惊天大瓜&#xff01; 谷歌的离职员工、已跳槽OpenAI的顶级研究员竟然曝出——Bard竟是用ChatG…

人工智能AIGC最新综述:从 GAN 到 ChatGPT 的AI生成历史

一句话总结 本综述全面回顾了生成模型的历史、基本模型组件、AIGC从单模态交互和多模态交互的最新进展&#xff0c;以及模态之间的交叉应用&#xff0c;最后讨论了AIGC中存在的开放问题和未来挑战。 摘要 最近&#xff0c;ChatGPT 与 DALL-E-2 和 Codex 一起受到了社会的广泛关…

ChatGPT is not all you need,一文综述6大公司9类生成式AI模型

关注并星标 从此不迷路 计算机视觉研究院 公众号ID&#xff5c;ComputerVisionGzq 学习群&#xff5c;扫码在主页获取加入方式 计算机视觉研究院专栏 作者&#xff1a;Edison_G 生成模型领域里&#xff0c;ChatGPT 并不是一切。 转自《机器之心》 过去两年&#xff0c;AI 领域里…

chatgpt赋能python:Python最简单的小游戏:猜数

Python 最简单的小游戏&#xff1a;猜数 作为一门高效、简单而且易于学习的编程语言&#xff0c;Python 受到了越来越多的开发者的喜爱。其中&#xff0c;编写小游戏是Python语言学习中一个很有趣的领域&#xff0c;因为它可以帮助你通过实践加深对Python语法和概念的理解。在…

chatgpt赋能Python-python3小游戏

Python3小游戏&#xff1a;为你的休闲时光增添乐趣 如果你正处于寻找一款简单好玩的小游戏&#xff0c;那么Python3小游戏将是你的不二之选。作为一名有10年Python编程经验的工程师&#xff0c;我可以说Python3小游戏是一款非常有趣、挑战性适中、易于上手的游戏。接下来&…

Github推荐--PC版微信/QQ/TIM防撤回补丁(我已经看到了,撤回也没用了)

逛Github的时候会遇到一些很好玩的项目 今天分享一个可以在PC端实现QQ防撤回功能的项目。&#xff08;安卓手机有Xposed框架&#xff09; 首先是项目地址&#xff1a; 我已经看到了&#xff0c;撤回也没用了 使用方法&#xff1a; 下载后解压&#xff0c;会出现RevokeMsgPatche…

微信防撤回功能修改

今天无意之中看到了一个帖子&#xff0c;谈到了有关微信消息撤回的。突发奇想实现一下&#xff0c;以后就不怕错过朋友的消息了。 首先介绍一下基本思路&#xff0c;由于微信采用的是CS端原理&#xff0c;所有的数据请求均通过服务器&#xff0c;客户端只是响应指令而已。 A向…

Git 如何撤回已经push到远端上的代码

首先 git log&#xff0c;目的是找到这次想要撤回的提交的上一次提交&#xff0c;并记录下红框中的commit id 回退代码&#xff0c;输入 git reset --soft commit id 回退代码, 回退完成后代码相当于刚写完的状态&#xff0c;即还没有进行add、commit、push…

微信多开防撤回工具再也不用担心好友撤回消息和登录多个账号了

微信&#xff0c;大家工作中生活中用的最多的一款应用&#xff1b;很多公司喜欢用微信来作为工作沟通的工具&#xff0c;官方原版只支持登陆一个微信&#xff0c;这对于需要在电脑上登陆多个微信账号的朋友来说肯定是极其的不方便。另外有的时候别人撤回了一些重要消息&#xf…

Python神级操作,还原已撤回的微信消息

项目环境 语言&#xff1a;Python3 编辑器&#xff1a;Pycharm 导包效果展示 以下截图显示的撤回消息类型依次是文字消息、微信自带表情、图片、语音、定位地图、名片、公众号文章、音乐、视频。有群里撤回的&#xff0c;也有个人号撤回的。 图文来源&#xff1a;http://kk…

利用Python查看微信好友撤回的消息

效果图如下&#xff1a; 不仅可以查看微信好友撤回的文字消息&#xff0c;如位置、视频、音频、图片等等都可以查看。 直接上源代码&#xff1a; # Python查看微信撤回消息 import re import os import time import itchat import platform from itchat.content import TEXT …

用Python实现微信撤回消息还原

在使用微信过程中&#xff0c;有时候我们会撤回一些发错或者不该发的一些信息&#xff0c;今天我就用一段代码实现将撤回的消息还原。。。 从此再也不怕别人撤回消息&#xff0c;自己不能看的尴尬了 import osimport reimport shutilimport timeimport itchatfrom itchat.con…

python学习 -对象把微信消息撤回后好慌,有了这个你就能看到撤回的消息了(超详解)

目录​​​​​​​ 一.简介 普通人 python技术人员 1、准备环境 2、itchat介绍 3、itchat使用 4、pycharm解释器写入代码 一.简介 当我们与朋友&#xff0c;亲人&#xff0c;爱人聊天的时候&#xff0c;我估计每个人都经理过&#xff0c;那就是微信撤回功能中所提到的…

微信能自定义“撤回消息”? QQ 笑了!

大家有没有遇到过和小伙伴聊天&#xff0c;结果输入法突然崛起&#xff0c;等到你撤回的时候尴尬到不行的窘境&#xff1f; 没办法&#xff0c;只好撤回...&#xff08;咳咳&#xff0c;只能说我的输入法已经妖魔化了 但是小伙伴的&#xff1f;号已经紧随其后&#xff0c;要是…

Telegram防撤回工具(Windows)

使用方法 Telegram v2.8.5 及之后版本 使用最新版本补丁将 TAR-Resources文件夹 、TAR-Launcher-x64.exe 、TAR-Launcher-x86.exe 全部解压到Telegram根目录 通过 TAR-Launcher-x64.exe 或 TAR-Launcher-x86.exe&#xff08;取决于你安装的Telegram版本&#xff0c;如果不知道…