webSocket基本概念
WebSocket是一种网络通信协议,它在单个TCP连接上进行全双工通信。它允许服务器主动向客户端推送信息,客户端也能实时接收服务器的响应。WebSocket通信过程中,只需要一次握手,就可以在客户端和服务器之间建立持久性的连接,并进行双向数据传输。
三、WebSocket工作原理
WebSocket的工作原理基于HTTP协议进行握手,建立TCP连接后,双方通过帧(Frame)进行数据通信。以下是WebSocket建立连接和通信的简要步骤:
- 握手阶段:客户端通过发送一个带有特定请求头的HTTP请求来启动WebSocket连接。请求头中包含了WebSocket协议的版本号、子协议名称、是否携带凭证等信息。服务器收到请求后,如果同意建立连接,会返回一个状态码为101 Switching Protocols的响应,表示协议切换成功,WebSocket连接建立。
- 数据传输阶段:连接建立后,客户端和服务器之间就可以通过WebSocket帧进行双向数据传输。WebSocket帧包含了操作码(opcode)、负载数据(payload data)和掩码(mask)等信息。操作码用于标识帧的类型,如文本帧、二进制帧、控制帧等;负载数据是实际传输的数据;掩码用于保护客户端发送的数据,防止缓存污染。
四、WebSocket应用场景
WebSocket因其全双工通信和实时性强的特点,被广泛应用于以下场景: - 实时聊天应用:WebSocket可以实现实时在线聊天功能,如在线聊天室、即时通讯软件等。
- 在线游戏:WebSocket可以实现游戏数据的实时传输和同步,提升游戏体验。
- 实时数据监控:WebSocket可以用于实时数据监控系统的后端推送,如股票价格、天气信息、交通状况等。
- 协同办公:WebSocket可以实现多人在线协同办公功能,如在线文档编辑、实时会议等。
- 物联网(IoT):WebSocket可以用于物联网设备的远程监控和控制,实现设备数据的实时传输和响应。
五、WebSocket实现方法
WebSocket的实现通常依赖于客户端和服务器端的编程语言和库。以下是一些常见的实现方法: - 客户端实现:浏览器端可以通过JavaScript的WebSocket API来实现WebSocket连接和数据传输。WebSocket API提供了创建连接、发送数据、接收数据等方法。
- 服务器端实现:服务器端可以使用多种编程语言和框架来实现WebSocket服务器。常见的WebSocket服务器实现包括Node.js的socket.io库、Java的Netty框架、Python的websockets库等。这些库和框架提供了WebSocket协议的解析、连接管理、数据传输等功能。
六、总结
WebSocket作为一种网络通信协议,具有实时性强、全双工通信的特点,被广泛应用于实时聊天、在线游戏、实时数据监控、协同办公和物联网等场景。开发者可以通过JavaScript的WebSocket API和服务器端的编程语言和库来实现WebSocket连接和数据传输。掌握WebSocket技术将有助于提高Web应用的实时性和交互性。
具体方法实践如下 :
在此之前我们首先要了解下,http的三次握手四次挥手,长连接与短连接;
1.引入依赖包:
java
<dependency>
<groupId>org.java-websocket</groupId>
<artifactId>Java-WebSocket</artifactId>
<version>1.5.3</version>
</dependency>
2.编写创建Websocket链接工具
java
package com.zfsw.spzx.device.manage.zfsw.service.ws;
import com.zfsw.spzx.device.manage.zfsw.constant.StrConstant;
import com.zfsw.spzx.device.manage.zfsw.enums.ProxyApiEnum;
import com.zfsw.spzx.device.manage.zfsw.model.ServerInfo;
import com.zfsw.spzx.device.manage.zfsw.model.WebsocketAccessKey;
import com.zfsw.spzx.device.manage.zfsw.utils.ProxyHttpUtils;
import com.zfsw.spzx.proxy.device.manage.enums.ManufacturerEnum;
import lombok.extern.slf4j.Slf4j;
import org.java_websocket.enums.ReadyState;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
@Slf4j
@Component
public class CreateWebSocketConnectorUtil {
@Resource
private ProxyHttpUtils proxyHttpUtils;
@Resource
private WebSocketHandler webSocketHandler;
public static final Map<String, Boolean> connectPool = new ConcurrentHashMap<>();
private static final Map<String, Thread> socketConnectPool = new ConcurrentHashMap<>();
private static final Map<String, Thread> socketListenerPool = new ConcurrentHashMap<>();
public void create(ServerInfo serverInfo) {
try {
ManufacturerEnum manufacturerEnum = ManufacturerEnum.findByCodeOrDesc(serverInfo.getType());
if (manufacturerEnum != null && manufacturerEnum.getNeedWsConnect()) {
connectPool.put(serverInfo.getType(), false);
WebsocketAccessKey key = proxyHttpUtils.sendRequest(ProxyApiEnum.WEBSOCKET_KEY_GET, "{}", serverInfo, WebsocketAccessKey.class);
WebSocketHandlerClient webSocketHandlerClient = new WebSocketHandlerClient(serverInfo, key.getKey(),
StrConstant.WS_PROTOCOL + serverInfo.getIp() + StrConstant.COLON_SIGN + serverInfo.getPort() + proxyHttpUtils.getVersionUrl(ProxyApiEnum.WS_URL, serverInfo),
webSocketHandler);
webSocketHandlerClient.setConnectionLostTimeout(360);
webSocketHandlerClient.connect();
if (Objects.nonNull(socketConnectPool.get(serverInfo.getType()))) {
socketConnectPool.get(serverInfo.getType()).interrupt();
socketConnectPool.remove(serverInfo.getType());
}
WebSocketSessionClientManager.add(serverInfo.getType(), webSocketHandlerClient);
this.addClientListener(serverInfo);
connectPool.put(serverInfo.getType(), true);
} else {
log.info("{}服务不需要websocket连接", serverInfo.getType());
}
} catch (Exception e) {
if (Objects.isNull(socketConnectPool.get(serverInfo.getType()))) {
socketConnectPool.put(serverInfo.getType(), new Thread(() -> {
while (!connectPool.get(serverInfo.getType())) {
log.info("websocket开始重连:" + serverInfo.getName());
this.create(serverInfo);
try {
Thread.sleep(5000);
} catch (InterruptedException ex) {
log.info("执行终止,连接成功后正常终止");
}
}
}));
socketConnectPool.get(serverInfo.getType()).start();
}
}
}
public void addClientListener(ServerInfo serverInfo) {
if (Objects.isNull(socketListenerPool.get(serverInfo.getType()))) {
socketListenerPool.put(serverInfo.getType(), new Thread(() -> {
while (true) {
if (!connectPool.get(serverInfo.getType())) {
WebSocketHandlerClient client = WebSocketSessionClientManager.CLIENT_POOL.get(serverInfo.getType());
if (Objects.nonNull(client)) {
if (client.getConnection().getReadyState().equals(ReadyState.CLOSED)) {
log.info("websocket重连");
this.create(serverInfo);
}
}
}
try {
Thread.sleep(5000);
log.debug("websocket{}:{}监听中..............", serverInfo.getIp(), serverInfo.getPort());
} catch (InterruptedException e) {
log.error("线程终止");
}
}
}));
socketListenerPool.get(serverInfo.getType()).start();
}
}
}
3.编写代理http工具
java
package com.zfsw.spzx.device.manage.zfsw.utils;
import cn.gookit.core.Assert;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import cn.hutool.http.Method;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.zfsw.spzx.device.manage.zfsw.constant.StrConstant;
import com.zfsw.spzx.device.manage.zfsw.enums.ProxyApiEnum;
import com.zfsw.spzx.device.manage.zfsw.model.ServerInfo;
import com.zfsw.spzx.enums.ServiceType;
import com.zfsw.spzx.journal.enums.ApiStatus;
import com.zfsw.spzx.journal.model.system.SystemLogAddDto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;
@Component
@Slf4j
public class ProxyHttpUtils {
@Resource
RedisUtils redisUtils;
public String getVersionUrl(ProxyApiEnum proxyApiEnum, ServerInfo subSystem) {
return proxyApiEnum.getUrl().replace("{version}", subSystem.getVersion());
}
public String getFullUrl(ProxyApiEnum proxyApiEnum, ServerInfo subSystem) {
return getBaseUrl(subSystem) + getVersionUrl(proxyApiEnum, subSystem);
}
private String getBaseUrl(ServerInfo serverInfo) {
log.info("请求服务信息:{}", JSONObject.toJSONString(serverInfo));
return StrConstant.HTTP_PROTOCOL + serverInfo.getIp() + StrConstant.COLON_SIGN + serverInfo.getPort();
}
public <E> E sendRequest(ProxyApiEnum proxyApiEnum, String param, String deviceId, Class<E> tClass) {
Assert.throwWhenEmpty(deviceId, "设备id不能为空");
ResponseRes responseRes = this.send(proxyApiEnum, param, deviceId, null);
return ProxyJsonUtils.parseObject(responseRes.body, tClass).getData();
}
public <E> List<E> sendRequestForList(ProxyApiEnum proxyApiEnum, String param, String deviceId, Class<E> tClass) {
ResponseRes responseRes = this.send(proxyApiEnum, param, deviceId, null);
return ProxyJsonUtils.parseList(responseRes.body, tClass).getData();
}
public <E> E sendRequest(ProxyApiEnum proxyApiEnum, String param, ServerInfo subSystem, Class<E> tClass) {
ResponseRes responseRes = this.send(proxyApiEnum, param, null, subSystem);
return ProxyJsonUtils.parseObject(responseRes.body, tClass).getData();
}
public <E> List<E> sendRequestForList(ProxyApiEnum proxyApiEnum, String param, ServerInfo subSystem, Class<E> tClass) {
ResponseRes responseRes = this.send(proxyApiEnum, param, null, subSystem);
return ProxyJsonUtils.parseList(responseRes.body, tClass).getData();
}
private ResponseRes send(ProxyApiEnum proxyApiEnum, String param, String deviceId, ServerInfo subSystem) {
ServerInfo serverInfo = StringUtils.isEmpty(deviceId) ? subSystem : redisUtils.getSubsystemByDeviceIdFromCache(deviceId);
String fullUrl = getFullUrl(proxyApiEnum, serverInfo);
log.info("{},请求参数,{}", fullUrl, param);
HttpRequest request = this.createRequest(proxyApiEnum.getMethod(), fullUrl, param, serverInfo);
ResponseRes res = new ResponseRes();
res.setStartTime(System.currentTimeMillis());
res.setHttpRequest(request);
try {
HttpResponse response = request.execute();
res.setBody(response.body());
Integer code = JSON.parseObject(response.body()).getInteger("code");
boolean success = Objects.equals(code, 0);
res.setIsSuccess(success);
res.setErrorMes(success ? null : JSON.parseObject(response.body()).getString("cnDescribe"));
} catch (Exception e) {
res.setIsSuccess(false);
res.setErrorMes(e.getCause() + StrConstant.COLON_SIGN + e.getMessage() + StrConstant.F_ONE);
}
log.info("{}响应结果,{}", fullUrl, res.getIsSuccess() ? res.getBody() : res.getErrorMes());
if (StringUtils.isEmpty(res.body)) {
ProxyResponse response = new ProxyResponse(500, res.getErrorMes());
res.body = JSON.toJSONString(response);
}
String apiName = param.contains("PtzGotoPreset") ? "预置位转动" : proxyApiEnum.getDesc();
this.setLog(res.startTime, res.httpRequest, param, res.body, apiName,
res.errorMes, res.isSuccess);
return res;
}
@Data
private static class ResponseRes {
private String body;
private Boolean isSuccess = true;
private String errorMes;
private long startTime;
private HttpRequest httpRequest;
}
@Data
@AllArgsConstructor
private static class ProxyResponse {
private Integer code;
private String data;
}
private HttpRequest createRequest(Method method, String url, String body, ServerInfo subSystem) {
HttpRequest request = HttpUtil.createRequest(method, url);
request.basicAuth(subSystem.getUserName(), subSystem.getPassword());
request.header("Content-Type", "application/json");
request.body(body);
request.setReadTimeout(subSystem.getReadTimeout());
request.setConnectionTimeout(subSystem.getConnectTimeout());
return request;
}
@Resource
RabbitTemplate rabbitTemplate;
@Value("${spring.rabbitmq.producer.system-log-topic:system-log-topic}")
private String topic;
private void setLog(long startTime, HttpRequest request, String param, String body, String apiName, String errorMsg, Boolean isSuccess) {
long endTime = System.currentTimeMillis();
LocalDateTime now = LocalDateTime.now();
SystemLogAddDto logRequest = new SystemLogAddDto();
logRequest.setElapsedTime(endTime - startTime);
logRequest.setMethod(request.getMethod().name());
logRequest.setHeader(JSON.toJSONString(request.headers()));
logRequest.setParam(param);
//设置接口名称
logRequest.setName(apiName);
logRequest.setSourceSystem(ServiceType.MS_DEVOPS_DATA_WEB_SERVICE);
logRequest.setTargetSystem(ServiceType.MS_PROXY_DEVICE_MANAGE_SERVICE);
logRequest.setUrl(request.getUrl());
logRequest.setCreateTime(now);
logRequest.setStatus(isSuccess ? ApiStatus.SUCCESS : ApiStatus.FAIL);
logRequest.setResult(body == null ? "接口失败,接口返回null" : body);
if (StringUtils.isNotEmpty(errorMsg)) {
logRequest.setResult(logRequest.getResult() + StrConstant.COMMA + errorMsg);
}
try {
rabbitTemplate.convertAndSend(topic, JSON.toJSONString(logRequest));
} catch (Exception e) {
log.warn("add log failed");
}
}
}
4.继承websocet的客户端
java
package com.zfsw.spzx.device.manage.zfsw.service.ws;
import com.alibaba.fastjson2.JSONException;
import cn.gookit.core.Assert;
import com.zfsw.spzx.device.manage.zfsw.model.ServerInfo;
import lombok.extern.slf4j.Slf4j;
import org.java_websocket.WebSocket;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.framing.Framedata;
import org.java_websocket.handshake.ServerHandshake;
import java.net.URI;
@Slf4j
public class WebSocketHandlerClient extends WebSocketClient {
private final String connectKey;
private final ServerInfo serverInfo;
private final WebSocketHandler webSocketHandler;
public WebSocketHandlerClient(ServerInfo serverInfo, String connectKey, String uri, WebSocketHandler webSocketHandler) {
super(URI.create(uri));
this.serverInfo = serverInfo;
this.connectKey = connectKey;
this.webSocketHandler = webSocketHandler;
}
@Override
public void onOpen(ServerHandshake handshakedata) {
log.info("onOpen write1");
send(connectKey);
}
@Override
public void onMessage(String message) {
log.debug("webSocket接收到消息{}", message);
if (!message.startsWith("{")) {
return;
}
try {
webSocketHandler.handler(message);
} catch (JSONException e) {
Assert.throwException(e.getMessage());
}
}
@Override
public void onClose(int code, String reason, boolean remote) {
log.info("onClose:code:{},reason:{},remote:{}", code, reason, remote);
CreateWebSocketConnectorUtil.connectPool.put(serverInfo.getType(), false);
}
@Override
public void onError(Exception ex) {
log.info("onError", ex);
}
@Override
public void onWebsocketPing(WebSocket conn, Framedata f) {
super.onWebsocketPing(conn, f);
}
}
5.编写websocket处理器
java
package com.zfsw.spzx.device.manage.zfsw.service.ws;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.TypeReference;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.zfsw.spzx.device.manage.zfsw.constant.StrConstant;
import com.zfsw.spzx.device.manage.zfsw.service.DeviceSubsystemService;
import com.zfsw.spzx.device.manage.zfsw.service.entity.DeviceSubsystem;
import com.zfsw.spzx.device.manage.zfsw.utils.RedisUtils;
import com.zfsw.spzx.proxy.device.manage.model.ws.CataLogNotify;
import com.zfsw.spzx.proxy.device.manage.model.ws.WebSocketNotify;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
@Slf4j
@Component
public class WebSocketHandler {
@Resource
RabbitTemplate rabbitTemplate;
@Resource
DeviceSubsystemService deviceSubsystemService;
@Resource
RedisUtils redisUtils;
@Value("${spring.rabbitmq.producer.proxy-device-manage}")
private String producer;
ExecutorService executorService = Executors.newFixedThreadPool(5);
@PreDestroy
public void shutdown() {
executorService.shutdown();
}
public void handler(String msg) {
try {
executorService.execute(() -> this.handlerDataSubsystem(msg));
rabbitTemplate.convertAndSend(producer, msg);
} catch (AmqpException e) {
log.error("发送websocket消息失败!", e);
}
}
void handlerDataSubsystem(String msg) {
try {
JSONObject object = JSON.parseObject(msg);
String action = object.getString("action");
if ("Catalog".equals(action)) {
WebSocketNotify<List<CataLogNotify>> catalogNotify = JSON.parseObject(msg,
new TypeReference<WebSocketNotify<List<CataLogNotify>>>() {
}.getType());
List<CataLogNotify> catalogNotifies = catalogNotify.getData();
Set<String> pIds = catalogNotifies.stream().map(item -> item.getId().split(StrConstant.EMAIL_SIGN)[0]).collect(Collectors.toSet());
List<DeviceSubsystem> deviceSubsystems = deviceSubsystemService.lambdaQuery().in(DeviceSubsystem::getId, pIds).list();
Map<String, String> devSubMap = deviceSubsystems.stream().collect(Collectors.toMap(DeviceSubsystem::getId, DeviceSubsystem::getServerType));
List<DeviceSubsystem> list = catalogNotifies.stream().map(item -> {
DeviceSubsystem deviceSubsystem = new DeviceSubsystem();
deviceSubsystem.setId(item.getId());
deviceSubsystem.setServerType(devSubMap.get(item.getId().split(StrConstant.EMAIL_SIGN)[0]));
return deviceSubsystem;
}).collect(Collectors.toList());
if (CollectionUtil.isNotEmpty(list)) {
deviceSubsystemService.saveOrUpdateBatch(list);
redisUtils.setDeviceSubSystemToCache(list);
}
}
} catch (Exception e) {
log.error(e.getCause() + StrConstant.COLON_SIGN + e.getMessage());
}
}
}
6.代理JSON工具编写
java
package com.zfsw.spzx.device.manage.zfsw.utils;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.TypeReference;
import cn.gookit.core.Assert;
import cn.gookit.core.util.ModelMapperUtil;
import com.zfsw.spzx.device.manage.zfsw.constant.StrConstant;
import com.zfsw.spzx.device.manage.zfsw.model.ProxyApiRes;
import java.util.List;
public class ProxyJsonUtils {
public static <E> ProxyApiRes<E> parseObject(String result, Class<E> tClass) {
ProxyApiRes<E> proxyApiRes = new ProxyApiRes<>();
try {
if (tClass == Boolean.class) {
// 兼容底层接口问题,同一接口返回参数不一样
ProxyApiRes<?> apiRes = JSON.parseObject(result, new TypeReference<ProxyApiRes<?>>() {
});
ModelMapperUtil.mapperTo(apiRes, proxyApiRes);
proxyApiRes.setData((E) apiRes.getIsSuccess());
} else {
proxyApiRes = JSON.parseObject(result, new TypeReference<ProxyApiRes<E>>(tClass) {
});
}
} catch (Exception e) {
Assert.throwException(e.getCause() + "," + e.getMessage() + StrConstant.F_ONE);
}
Assert.throwWhen(!proxyApiRes.getIsSuccess(), proxyApiRes.getCnDescribe() + StrConstant.F_ONE);
return proxyApiRes;
}
public static <E> ProxyApiRes<List<E>> parseList(String result, Class<E> tClass) {
ProxyApiRes<List<E>> proxyApiRes = new ProxyApiRes<>();
try {
proxyApiRes = JSON.parseObject(result, new TypeReference<ProxyApiRes<List<E>>>(tClass) {
});
} catch (Exception e) {
Assert.throwException(e.getCause() + "," + e.getMessage() + StrConstant.F_ONE);
}
Assert.throwWhen(!proxyApiRes.getIsSuccess(), proxyApiRes.getCnDescribe() + StrConstant.F_ONE);
return proxyApiRes;
}
}
3.编写代理http工具
java
package com.zfsw.spzx.device.manage.zfsw.utils;
import cn.gookit.core.Assert;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import cn.hutool.http.Method;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.zfsw.spzx.device.manage.zfsw.constant.StrConstant;
import com.zfsw.spzx.device.manage.zfsw.enums.ProxyApiEnum;
import com.zfsw.spzx.device.manage.zfsw.model.ServerInfo;
import com.zfsw.spzx.enums.ServiceType;
import com.zfsw.spzx.journal.enums.ApiStatus;
import com.zfsw.spzx.journal.model.system.SystemLogAddDto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;
@Component
@Slf4j
public class ProxyHttpUtils {
@Resource
RedisUtils redisUtils;
public String getVersionUrl(ProxyApiEnum proxyApiEnum, ServerInfo subSystem) {
return proxyApiEnum.getUrl().replace("{version}", subSystem.getVersion());
}
public String getFullUrl(ProxyApiEnum proxyApiEnum, ServerInfo subSystem) {
return getBaseUrl(subSystem) + getVersionUrl(proxyApiEnum, subSystem);
}
private String getBaseUrl(ServerInfo serverInfo) {
log.info("请求服务信息:{}", JSONObject.toJSONString(serverInfo));
return StrConstant.HTTP_PROTOCOL + serverInfo.getIp() + StrConstant.COLON_SIGN + serverInfo.getPort();
}
public <E> E sendRequest(ProxyApiEnum proxyApiEnum, String param, String deviceId, Class<E> tClass) {
Assert.throwWhenEmpty(deviceId, "设备id不能为空");
ResponseRes responseRes = this.send(proxyApiEnum, param, deviceId, null);
return ProxyJsonUtils.parseObject(responseRes.body, tClass).getData();
}
public <E> List<E> sendRequestForList(ProxyApiEnum proxyApiEnum, String param, String deviceId, Class<E> tClass) {
ResponseRes responseRes = this.send(proxyApiEnum, param, deviceId, null);
return ProxyJsonUtils.parseList(responseRes.body, tClass).getData();
}
public <E> E sendRequest(ProxyApiEnum proxyApiEnum, String param, ServerInfo subSystem, Class<E> tClass) {
ResponseRes responseRes = this.send(proxyApiEnum, param, null, subSystem);
return ProxyJsonUtils.parseObject(responseRes.body, tClass).getData();
}
public <E> List<E> sendRequestForList(ProxyApiEnum proxyApiEnum, String param, ServerInfo subSystem, Class<E> tClass) {
ResponseRes responseRes = this.send(proxyApiEnum, param, null, subSystem);
return ProxyJsonUtils.parseList(responseRes.body, tClass).getData();
}
private ResponseRes send(ProxyApiEnum proxyApiEnum, String param, String deviceId, ServerInfo subSystem) {
ServerInfo serverInfo = StringUtils.isEmpty(deviceId) ? subSystem : redisUtils.getSubsystemByDeviceIdFromCache(deviceId);
String fullUrl = getFullUrl(proxyApiEnum, serverInfo);
log.info("{},请求参数,{}", fullUrl, param);
HttpRequest request = this.createRequest(proxyApiEnum.getMethod(), fullUrl, param, serverInfo);
ResponseRes res = new ResponseRes();
res.setStartTime(System.currentTimeMillis());
res.setHttpRequest(request);
try {
HttpResponse response = request.execute();
res.setBody(response.body());
Integer code = JSON.parseObject(response.body()).getInteger("code");
boolean success = Objects.equals(code, 0);
res.setIsSuccess(success);
res.setErrorMes(success ? null : JSON.parseObject(response.body()).getString("cnDescribe"));
} catch (Exception e) {
res.setIsSuccess(false);
res.setErrorMes(e.getCause() + StrConstant.COLON_SIGN + e.getMessage() + StrConstant.F_ONE);
}
log.info("{}响应结果,{}", fullUrl, res.getIsSuccess() ? res.getBody() : res.getErrorMes());
if (StringUtils.isEmpty(res.body)) {
ProxyResponse response = new ProxyResponse(500, res.getErrorMes());
res.body = JSON.toJSONString(response);
}
String apiName = param.contains("PtzGotoPreset") ? "预置位转动" : proxyApiEnum.getDesc();
this.setLog(res.startTime, res.httpRequest, param, res.body, apiName,
res.errorMes, res.isSuccess);
return res;
}
@Data
private static class ResponseRes {
private String body;
private Boolean isSuccess = true;
private String errorMes;
private long startTime;
private HttpRequest httpRequest;
}
@Data
@AllArgsConstructor
private static class ProxyResponse {
private Integer code;
private String data;
}
private HttpRequest createRequest(Method method, String url, String body, ServerInfo subSystem) {
HttpRequest request = HttpUtil.createRequest(method, url);
request.basicAuth(subSystem.getUserName(), subSystem.getPassword());
request.header("Content-Type", "application/json");
request.body(body);
request.setReadTimeout(subSystem.getReadTimeout());
request.setConnectionTimeout(subSystem.getConnectTimeout());
return request;
}
@Resource
RabbitTemplate rabbitTemplate;
@Value("${spring.rabbitmq.producer.system-log-topic:system-log-topic}")
private String topic;
private void setLog(long startTime, HttpRequest request, String param, String body, String apiName, String errorMsg, Boolean isSuccess) {
long endTime = System.currentTimeMillis();
LocalDateTime now = LocalDateTime.now();
SystemLogAddDto logRequest = new SystemLogAddDto();
logRequest.setElapsedTime(endTime - startTime);
logRequest.setMethod(request.getMethod().name());
logRequest.setHeader(JSON.toJSONString(request.headers()));
logRequest.setParam(param);
//设置接口名称
logRequest.setName(apiName);
logRequest.setSourceSystem(ServiceType.MS_DEVOPS_DATA_WEB_SERVICE);
logRequest.setTargetSystem(ServiceType.MS_PROXY_DEVICE_MANAGE_SERVICE);
logRequest.setUrl(request.getUrl());
logRequest.setCreateTime(now);
logRequest.setStatus(isSuccess ? ApiStatus.SUCCESS : ApiStatus.FAIL);
logRequest.setResult(body == null ? "接口失败,接口返回null" : body);
if (StringUtils.isNotEmpty(errorMsg)) {
logRequest.setResult(logRequest.getResult() + StrConstant.COMMA + errorMsg);
}
try {
rabbitTemplate.convertAndSend(topic, JSON.toJSONString(logRequest));
} catch (Exception e) {
log.warn("add log failed");
}
}
}
4.继承websocet的客户端
java
package com.zfsw.spzx.device.manage.zfsw.service.ws;
import com.alibaba.fastjson2.JSONException;
import cn.gookit.core.Assert;
import com.zfsw.spzx.device.manage.zfsw.model.ServerInfo;
import lombok.extern.slf4j.Slf4j;
import org.java_websocket.WebSocket;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.framing.Framedata;
import org.java_websocket.handshake.ServerHandshake;
import java.net.URI;
@Slf4j
public class WebSocketHandlerClient extends WebSocketClient {
private final String connectKey;
private final ServerInfo serverInfo;
private final WebSocketHandler webSocketHandler;
public WebSocketHandlerClient(ServerInfo serverInfo, String connectKey, String uri, WebSocketHandler webSocketHandler) {
super(URI.create(uri));
this.serverInfo = serverInfo;
this.connectKey = connectKey;
this.webSocketHandler = webSocketHandler;
}
@Override
public void onOpen(ServerHandshake handshakedata) {
log.info("onOpen write1");
send(connectKey);
}
@Override
public void onMessage(String message) {
log.debug("webSocket接收到消息{}", message);
if (!message.startsWith("{")) {
return;
}
try {
webSocketHandler.handler(message);
} catch (JSONException e) {
Assert.throwException(e.getMessage());
}
}
@Override
public void onClose(int code, String reason, boolean remote) {
log.info("onClose:code:{},reason:{},remote:{}", code, reason, remote);
CreateWebSocketConnectorUtil.connectPool.put(serverInfo.getType(), false);
}
@Override
public void onError(Exception ex) {
log.info("onError", ex);
}
@Override
public void onWebsocketPing(WebSocket conn, Framedata f) {
super.onWebsocketPing(conn, f);
}
}
5.编写websocket处理器
java
package com.zfsw.spzx.device.manage.zfsw.service.ws;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.TypeReference;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.zfsw.spzx.device.manage.zfsw.constant.StrConstant;
import com.zfsw.spzx.device.manage.zfsw.service.DeviceSubsystemService;
import com.zfsw.spzx.device.manage.zfsw.service.entity.DeviceSubsystem;
import com.zfsw.spzx.device.manage.zfsw.utils.RedisUtils;
import com.zfsw.spzx.proxy.device.manage.model.ws.CataLogNotify;
import com.zfsw.spzx.proxy.device.manage.model.ws.WebSocketNotify;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
@Slf4j
@Component
public class WebSocketHandler {
@Resource
RabbitTemplate rabbitTemplate;
@Resource
DeviceSubsystemService deviceSubsystemService;
@Resource
RedisUtils redisUtils;
@Value("${spring.rabbitmq.producer.proxy-device-manage}")
private String producer;
ExecutorService executorService = Executors.newFixedThreadPool(5);
@PreDestroy
public void shutdown() {
executorService.shutdown();
}
public void handler(String msg) {
try {
executorService.execute(() -> this.handlerDataSubsystem(msg));
rabbitTemplate.convertAndSend(producer, msg);
} catch (AmqpException e) {
log.error("发送websocket消息失败!", e);
}
}
void handlerDataSubsystem(String msg) {
try {
JSONObject object = JSON.parseObject(msg);
String action = object.getString("action");
if ("Catalog".equals(action)) {
WebSocketNotify<List<CataLogNotify>> catalogNotify = JSON.parseObject(msg,
new TypeReference<WebSocketNotify<List<CataLogNotify>>>() {
}.getType());
List<CataLogNotify> catalogNotifies = catalogNotify.getData();
Set<String> pIds = catalogNotifies.stream().map(item -> item.getId().split(StrConstant.EMAIL_SIGN)[0]).collect(Collectors.toSet());
List<DeviceSubsystem> deviceSubsystems = deviceSubsystemService.lambdaQuery().in(DeviceSubsystem::getId, pIds).list();
Map<String, String> devSubMap = deviceSubsystems.stream().collect(Collectors.toMap(DeviceSubsystem::getId, DeviceSubsystem::getServerType));
List<DeviceSubsystem> list = catalogNotifies.stream().map(item -> {
DeviceSubsystem deviceSubsystem = new DeviceSubsystem();
deviceSubsystem.setId(item.getId());
deviceSubsystem.setServerType(devSubMap.get(item.getId().split(StrConstant.EMAIL_SIGN)[0]));
return deviceSubsystem;
}).collect(Collectors.toList());
if (CollectionUtil.isNotEmpty(list)) {
deviceSubsystemService.saveOrUpdateBatch(list);
redisUtils.setDeviceSubSystemToCache(list);
}
}
} catch (Exception e) {
log.error(e.getCause() + StrConstant.COLON_SIGN + e.getMessage());
}
}
}
7.websocket服务端的session管理器
java
package com.zfsw.spzx.device.manage.zfsw.service.ws;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ConcurrentHashMap;
@Slf4j
public class WebSocketSessionClientManager {
//保存现存信息
public static ConcurrentHashMap<String, WebSocketHandlerClient> CLIENT_POOL = new ConcurrentHashMap<>();
public static void add(String subSystemID, WebSocketHandlerClient webSocketHandlerClient) {
// 添加 session
CLIENT_POOL.put(subSystemID, webSocketHandlerClient);
}
public static WebSocketHandlerClient remove(String subSystem) {
// 删除 session
return CLIENT_POOL.remove(subSystem);
}
public static void removeAndClose(String subSystem) {
WebSocketHandlerClient webSocketHandlerClient = remove(subSystem);
if (webSocketHandlerClient != null) {
// 关闭连接
webSocketHandlerClient.close();
}
}
public static WebSocketHandlerClient get(String subSystem) {
// 获得 session
return CLIENT_POOL.get(subSystem);
}
}