|
@@ -0,0 +1,409 @@
|
|
|
+package cn.hh.hhface.service;
|
|
|
+
|
|
|
+import cn.hh.common.constants.CommonConstants;
|
|
|
+import cn.hh.common.enumeration.UpgradeTypeEnum;
|
|
|
+import cn.hh.common.utils.Preconditions;
|
|
|
+import cn.hh.hhface.bean.FilePO;
|
|
|
+import cn.hh.hhface.bean.device.DeviceBizDataDto;
|
|
|
+import cn.hh.hhface.bean.device.DeviceVo;
|
|
|
+import cn.hh.hhface.bean.device.DownloadLogDto;
|
|
|
+import cn.hh.hhface.bean.device.FetchLogDto;
|
|
|
+import cn.hh.hhface.bean.log.AppLogReportDto;
|
|
|
+import cn.hh.hhface.bean.task.DeviceTaskStatusVo;
|
|
|
+import cn.hh.hhface.bean.websocket.WebsocketSenderDto;
|
|
|
+import cn.hh.hhface.config.NioWebSocketChannelPool;
|
|
|
+import cn.hutool.http.HttpUtil;
|
|
|
+import com.alibaba.fastjson.JSONObject;
|
|
|
+import io.netty.channel.Channel;
|
|
|
+import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
+import org.springframework.beans.factory.annotation.Value;
|
|
|
+import org.springframework.data.redis.core.RedisTemplate;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+
|
|
|
+import javax.annotation.Resource;
|
|
|
+import java.util.List;
|
|
|
+
|
|
|
+/**
|
|
|
+ * @author shisl
|
|
|
+ * @package cn.hh.hhface.service
|
|
|
+ * @class WebsockerService
|
|
|
+ * @date 2023/12/13 下午4:04
|
|
|
+ * @description
|
|
|
+ */
|
|
|
+@Service
|
|
|
+@Slf4j
|
|
|
+public class WebsocketService {
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private RedisTemplate<String, String> redisTemplate;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private NioWebSocketChannelPool webSocketChannelPool;
|
|
|
+
|
|
|
+ @Value("${websocket.httpApiSwitch}")
|
|
|
+ private Boolean httpApiSwitch;
|
|
|
+
|
|
|
+ @Value("${websocket.httpApi}")
|
|
|
+ private String httpApiUrl;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 关闭应用
|
|
|
+ *
|
|
|
+ * @param sn
|
|
|
+ */
|
|
|
+ public void close(String sn, String md5Token, String bizAppApkName) {
|
|
|
+ String toChannelId = redisTemplate.opsForValue().get(String.format(CommonConstants.WEBSOCKET_SN_KEY, sn));
|
|
|
+ if (Preconditions.isBlank(toChannelId)) {
|
|
|
+ log.error("设备【{}】未连接websocket服务", sn);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ Channel toChannel = webSocketChannelPool.findChannel(toChannelId);
|
|
|
+ if (Preconditions.isBlank(toChannel)) {
|
|
|
+ log.error("设备【{}】未连接websocket服务", sn);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ //获取发送者
|
|
|
+ String fromChannelId = null;
|
|
|
+ if (Preconditions.isNotBlank(md5Token)) {
|
|
|
+ fromChannelId = redisTemplate.opsForValue().get(String.format(CommonConstants.WEBSOCKET_PC_TOKEN_KEY, md5Token));
|
|
|
+ } //TODO 组装业务参数
|
|
|
+ JSONObject obj = new JSONObject();
|
|
|
+ obj.put("sn", sn);
|
|
|
+ obj.put("packageName", bizAppApkName);
|
|
|
+ obj.put(CommonConstants.WEBSOCKET_SOURCE, CommonConstants.WEBSOCKET_SOURCE_PC);
|
|
|
+ obj.put(CommonConstants.WEBSOCKET_OP, CommonConstants.WEBSOCKET_OP_CLOSE_APP);
|
|
|
+ obj.put("msg", "关闭应用");
|
|
|
+ obj.put("sender", fromChannelId);
|
|
|
+ log.info("PC端发送的消息内容:{}", obj.toJSONString());
|
|
|
+ toChannel.writeAndFlush(new TextWebSocketFrame(obj.toJSONString()));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 卸载应用
|
|
|
+ *
|
|
|
+ * @param sn
|
|
|
+ * @param md5Token
|
|
|
+ * @param bizAppApkName
|
|
|
+ */
|
|
|
+ public void uninstall(String sn, String md5Token, String bizAppApkName) {
|
|
|
+ String toChannelId = redisTemplate.opsForValue().get(String.format(CommonConstants.WEBSOCKET_SN_KEY, sn));
|
|
|
+ if (Preconditions.isBlank(toChannelId)) {
|
|
|
+ log.error("设备【{}】未连接websocket服务", sn);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ Channel toChannel = webSocketChannelPool.findChannel(toChannelId);
|
|
|
+ if (Preconditions.isBlank(toChannel)) {
|
|
|
+ log.error("设备【{}】未连接websocket服务", sn);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ //获取发送者
|
|
|
+ String fromChannelId = null;
|
|
|
+ if (Preconditions.isNotBlank(md5Token)) {
|
|
|
+ fromChannelId = redisTemplate.opsForValue().get(String.format(CommonConstants.WEBSOCKET_PC_TOKEN_KEY, md5Token));
|
|
|
+ } //TODO 组装业务参数
|
|
|
+ JSONObject obj = new JSONObject();
|
|
|
+ obj.put("sn", sn);
|
|
|
+ obj.put("packageName", bizAppApkName);
|
|
|
+ obj.put(CommonConstants.WEBSOCKET_SOURCE, CommonConstants.WEBSOCKET_SOURCE_PC);
|
|
|
+ obj.put(CommonConstants.WEBSOCKET_OP, CommonConstants.WEBSOCKET_OP_UNINSTALL_APP);
|
|
|
+ obj.put("msg", "卸载应用");
|
|
|
+ obj.put("sender", fromChannelId);
|
|
|
+ log.info("PC端发送的消息内容:{}", obj.toJSONString());
|
|
|
+ toChannel.writeAndFlush(new TextWebSocketFrame(obj.toJSONString()));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 重启设备
|
|
|
+ *
|
|
|
+ * @param sn
|
|
|
+ */
|
|
|
+ public void reboot(String sn, String md5Token) {
|
|
|
+ String toChannelId = redisTemplate.opsForValue().get(String.format(CommonConstants.WEBSOCKET_SN_KEY, sn));
|
|
|
+ if (Preconditions.isBlank(toChannelId)) {
|
|
|
+ log.error("设备【{}】未连接websocket服务", sn);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ Channel toChannel = webSocketChannelPool.findChannel(toChannelId);
|
|
|
+ if (Preconditions.isBlank(toChannel)) {
|
|
|
+ log.error("设备【{}】未连接websocket服务", sn);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ //获取发送者
|
|
|
+ String fromChannelId = null;
|
|
|
+ if (Preconditions.isNotBlank(md5Token)) {
|
|
|
+ fromChannelId = redisTemplate.opsForValue().get(String.format(CommonConstants.WEBSOCKET_PC_TOKEN_KEY, md5Token));
|
|
|
+ }
|
|
|
+ //TODO 组装业务参数
|
|
|
+ JSONObject obj = new JSONObject();
|
|
|
+ obj.put("sn", sn);
|
|
|
+ obj.put("sender", fromChannelId);
|
|
|
+ obj.put(CommonConstants.WEBSOCKET_SOURCE, CommonConstants.WEBSOCKET_SOURCE_PC);
|
|
|
+ obj.put(CommonConstants.WEBSOCKET_OP, CommonConstants.WEBSOCKET_OP_REBOOT);
|
|
|
+ obj.put("msg", "重启设备");
|
|
|
+ log.info("PC端发送的消息内容:{}", obj.toJSONString());
|
|
|
+ toChannel.writeAndFlush(new TextWebSocketFrame(obj.toJSONString()));
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 停止下载
|
|
|
+ *
|
|
|
+ * @param sn
|
|
|
+ */
|
|
|
+ public void stopDownload(String sn, Long taskId, String md5Token) {
|
|
|
+ String toChannelId = redisTemplate.opsForValue().get(String.format(CommonConstants.WEBSOCKET_SN_KEY, sn));
|
|
|
+ if (Preconditions.isBlank(toChannelId)) {
|
|
|
+ log.error("设备【{}】未连接websocket服务", sn);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ Channel toChannel = webSocketChannelPool.findChannel(toChannelId);
|
|
|
+ if (Preconditions.isBlank(toChannel)) {
|
|
|
+ log.error("设备【{}】未连接websocket服务", sn);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ //获取发送者
|
|
|
+ String fromChannelId = null;
|
|
|
+ if (Preconditions.isNotBlank(md5Token)) {
|
|
|
+ fromChannelId = redisTemplate.opsForValue().get(String.format(CommonConstants.WEBSOCKET_PC_TOKEN_KEY, md5Token));
|
|
|
+ }
|
|
|
+ //TODO 组装业务参数
|
|
|
+ JSONObject obj = new JSONObject();
|
|
|
+ obj.put("sn", sn);
|
|
|
+ obj.put("taskId", taskId);
|
|
|
+ obj.put(CommonConstants.WEBSOCKET_SOURCE, CommonConstants.WEBSOCKET_SOURCE_PC);
|
|
|
+ obj.put(CommonConstants.WEBSOCKET_OP, CommonConstants.WEBSOCKET_OP_STOP_DOWNLOAD);
|
|
|
+ obj.put("msg", "停止下载");
|
|
|
+ obj.put("sender", fromChannelId);
|
|
|
+ log.info("PC端发送的消息内容:{}", obj.toJSONString());
|
|
|
+ toChannel.writeAndFlush(new TextWebSocketFrame(obj.toJSONString()));
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 取消安装升级
|
|
|
+ *
|
|
|
+ * @param sn
|
|
|
+ * @param taskId
|
|
|
+ * @param md5Token
|
|
|
+ */
|
|
|
+ public void cancelUpgrade(String apkPath, String packageName, String versionName, String sn, Long taskId, String md5Token) {
|
|
|
+ String toChannelId = redisTemplate.opsForValue().get(String.format(CommonConstants.WEBSOCKET_SN_KEY, sn));
|
|
|
+ if (Preconditions.isBlank(toChannelId)) {
|
|
|
+ log.error("设备【{}】未连接websocket服务", sn);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ Channel toChannel = webSocketChannelPool.findChannel(toChannelId);
|
|
|
+ if (Preconditions.isBlank(toChannel)) {
|
|
|
+ log.error("设备【{}】未连接websocket服务", sn);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ //获取发送者
|
|
|
+ String fromChannelId = null;
|
|
|
+ if (Preconditions.isNotBlank(md5Token)) {
|
|
|
+ fromChannelId = redisTemplate.opsForValue().get(String.format(CommonConstants.WEBSOCKET_PC_TOKEN_KEY, md5Token));
|
|
|
+ }
|
|
|
+ //TODO 组装业务参数
|
|
|
+ JSONObject obj = new JSONObject();
|
|
|
+ obj.put("sn", sn);
|
|
|
+ obj.put("taskId", taskId);
|
|
|
+ obj.put("filePath", apkPath);
|
|
|
+ obj.put("packageName", packageName);
|
|
|
+ obj.put("versionName", versionName);
|
|
|
+ obj.put(CommonConstants.WEBSOCKET_SOURCE, CommonConstants.WEBSOCKET_SOURCE_PC);
|
|
|
+ obj.put(CommonConstants.WEBSOCKET_OP, CommonConstants.WEBSOCKET_OP_CANCEL_UPGRADE);
|
|
|
+ obj.put("msg", "取消安装升级");
|
|
|
+ obj.put("sender", fromChannelId);
|
|
|
+ log.info("PC端发送的消息内容:{}", obj.toJSONString());
|
|
|
+ toChannel.writeAndFlush(new TextWebSocketFrame(obj.toJSONString()));
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 日志抓取
|
|
|
+ *
|
|
|
+ * @param dto
|
|
|
+ */
|
|
|
+ public void fetchLog(FetchLogDto dto, String md5Token) {
|
|
|
+ String sn = dto.getSn();
|
|
|
+ String toChannelId = redisTemplate.opsForValue().get(String.format(CommonConstants.WEBSOCKET_SN_KEY, sn));
|
|
|
+ if (Preconditions.isBlank(toChannelId)) {
|
|
|
+ log.error("设备【{}】未连接websocket服务", sn);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ Channel toChannel = webSocketChannelPool.findChannel(toChannelId);
|
|
|
+ if (Preconditions.isBlank(toChannel)) {
|
|
|
+ log.error("设备【{}】未连接websocket服务", sn);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ //获取发送者
|
|
|
+ String fromChannelId = null;
|
|
|
+ if (Preconditions.isNotBlank(md5Token)) {
|
|
|
+ fromChannelId = redisTemplate.opsForValue().get(String.format(CommonConstants.WEBSOCKET_PC_TOKEN_KEY, md5Token));
|
|
|
+ }
|
|
|
+ //TODO 组装业务参数
|
|
|
+ JSONObject obj = new JSONObject();
|
|
|
+ obj.put("sn", sn);
|
|
|
+ obj.put(CommonConstants.WEBSOCKET_SOURCE, CommonConstants.WEBSOCKET_SOURCE_PC);
|
|
|
+ obj.put(CommonConstants.WEBSOCKET_OP, CommonConstants.WEBSOCKET_OP_FETCH_LOG);
|
|
|
+ obj.put("logType", dto.getLogType());
|
|
|
+ obj.put("logDirPath", dto.getPathUri());
|
|
|
+ obj.put("sender", fromChannelId);
|
|
|
+ obj.put("msg", "拉取日志");
|
|
|
+ log.info("PC端发送的消息内容:{}", obj.toJSONString());
|
|
|
+ toChannel.writeAndFlush(new TextWebSocketFrame(obj.toJSONString()));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 下载日志
|
|
|
+ *
|
|
|
+ * @param dto
|
|
|
+ */
|
|
|
+ public void downloadLog(DownloadLogDto dto, String md5Token) {
|
|
|
+ String sn = dto.getSn();
|
|
|
+ String toChannelId = redisTemplate.opsForValue().get(String.format(CommonConstants.WEBSOCKET_SN_KEY, sn));
|
|
|
+ if (Preconditions.isBlank(toChannelId)) {
|
|
|
+ log.error("设备【{}】未连接websocket服务", sn);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ Channel toChannel = webSocketChannelPool.findChannel(toChannelId);
|
|
|
+ if (Preconditions.isBlank(toChannel)) {
|
|
|
+ log.error("设备【{}】未连接websocket服务", sn);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ //获取发送者
|
|
|
+ String fromChannelId = null;
|
|
|
+ if (Preconditions.isNotBlank(md5Token)) {
|
|
|
+ fromChannelId = redisTemplate.opsForValue().get(String.format(CommonConstants.WEBSOCKET_PC_TOKEN_KEY, md5Token));
|
|
|
+ }
|
|
|
+ //TODO 组装业务参数
|
|
|
+ JSONObject obj = new JSONObject();
|
|
|
+ obj.put("sn", sn);
|
|
|
+ obj.put(CommonConstants.WEBSOCKET_SOURCE, CommonConstants.WEBSOCKET_SOURCE_PC);
|
|
|
+ obj.put(CommonConstants.WEBSOCKET_OP, CommonConstants.WEBSOCKET_OP_DOWNLOAD_LOG);
|
|
|
+ obj.put("logType", dto.getLogType());
|
|
|
+ obj.put("sender", fromChannelId);
|
|
|
+ obj.put("paths", dto.getLogUris());
|
|
|
+ obj.put("msg", "下载日志");
|
|
|
+ log.info("PC端发送的消息内容:{}", obj.toJSONString());
|
|
|
+ toChannel.writeAndFlush(new TextWebSocketFrame(obj.toJSONString()));
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 处理接收到的app websocket消息
|
|
|
+ *
|
|
|
+ * @param object
|
|
|
+ * @param text
|
|
|
+ * @param op
|
|
|
+ */
|
|
|
+ public void handleAppWebsocketOp(JSONObject object, String text, String op) {
|
|
|
+ String sn = object.getString(CommonConstants.WEBSOCKET_CONNECT_APP);
|
|
|
+ if (Preconditions.isBlank(sn)) {
|
|
|
+ log.error("设备号为空");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (CommonConstants.WEBSOCKET_OP_FETCH_LOG.equals(op) || CommonConstants.WEBSOCKET_OP_DOWNLOAD_LOG.equals(op)) { //拉取日志 下载日志
|
|
|
+ //发送websocket给pc端 WebsocketSenderDto
|
|
|
+ WebsocketSenderDto sender = object.toJavaObject(WebsocketSenderDto.class);
|
|
|
+ if (Preconditions.isNotBlank(sender.getSender())) {
|
|
|
+ sendAppWebsocketMsg2Pc(sender.getSender(), text);
|
|
|
+ } else {
|
|
|
+ sendAppWebsocketMsg2AllPc(text);
|
|
|
+ }
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ private void sendAppWebsocketMsg2Pc(String channelId, String text) {
|
|
|
+ Channel channel = webSocketChannelPool.findChannel(channelId);
|
|
|
+ if (Preconditions.isBlank(channel)) {
|
|
|
+ log.error("此PC端未连接websocket服务");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ channel.writeAndFlush(new TextWebSocketFrame(text));
|
|
|
+ }
|
|
|
+
|
|
|
+ public void sendAppWebsocketMsg2AllPc(String text) {
|
|
|
+ if (!redisTemplate.hasKey(CommonConstants.WEBSOCKET_PC_KEY)) {
|
|
|
+ log.error("未找到连接websocket服务的PC端");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ String s = redisTemplate.opsForValue().get(CommonConstants.WEBSOCKET_PC_KEY);
|
|
|
+ List<String> channels = JSONObject.parseArray(s, String.class);
|
|
|
+ if (Preconditions.isBlank(channels)) {
|
|
|
+ log.error("未找到连接websocket服务的PC端");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ for (String channelId : channels) {
|
|
|
+ Channel channel = webSocketChannelPool.findChannel(channelId);
|
|
|
+ if (Preconditions.isBlank(channel)) {
|
|
|
+ log.error("此PC端未连接websocket服务");
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ channel.writeAndFlush(new TextWebSocketFrame(text));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ //处理塘栖服务端发送来的消息
|
|
|
+ public void handleTqServerWebsocketOp(JSONObject object, String text, String op) {
|
|
|
+ String sn = object.getString(CommonConstants.WEBSOCKET_CONNECT_APP);
|
|
|
+ if (Preconditions.isBlank(sn)) {
|
|
|
+ log.error("设备号为空");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ String toChannelId = redisTemplate.opsForValue().get(String.format(CommonConstants.WEBSOCKET_SN_SOURCE_KEY, "HHFACE", sn));
|
|
|
+ if (Preconditions.isBlank(toChannelId)) {
|
|
|
+ log.error("设备【{}】未连接websocket服务", sn);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ Channel toChannel = webSocketChannelPool.findChannel(toChannelId);
|
|
|
+ if (Preconditions.isBlank(toChannel)) {
|
|
|
+ log.error("设备【{}】未连接websocket服务", sn);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ toChannel.writeAndFlush(new TextWebSocketFrame(object.toJSONString()));
|
|
|
+ }
|
|
|
+
|
|
|
+ //TODO 处理HHFace发送来的消息 以及http协议请求
|
|
|
+ public void handleHhFaceAppWebsocketOp(JSONObject object, String text, String op) {
|
|
|
+ if (httpApiSwitch) {
|
|
|
+ String respStr = HttpUtil.post(httpApiUrl, object.toJSONString(), 60000);
|
|
|
+ log.info("--->http请求callback api接口:{}", respStr);
|
|
|
+// RestResult result = JSONObject.parseObject(respStr, RestResult.class);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ sendHhFaceAppWebsocketMsg2AllTq(text);
|
|
|
+ }
|
|
|
+
|
|
|
+ //向所有塘栖平台广播消息
|
|
|
+ public void sendHhFaceAppWebsocketMsg2AllTq(String text) {
|
|
|
+ if (!redisTemplate.hasKey(CommonConstants.WEBSOCKET_PC_SOURCE_KEY)) {
|
|
|
+ log.error("未找到连接websocket服务的PC端");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ String s = redisTemplate.opsForValue().get(CommonConstants.WEBSOCKET_PC_SOURCE_KEY);
|
|
|
+ List<String> channels = JSONObject.parseArray(s, String.class);
|
|
|
+ if (Preconditions.isBlank(channels)) {
|
|
|
+ log.error("未找到连接websocket服务的PC端");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ for (String channelId : channels) {
|
|
|
+ Channel channel = webSocketChannelPool.findChannel(channelId);
|
|
|
+ if (Preconditions.isBlank(channel)) {
|
|
|
+ log.error("此PC端未连接websocket服务");
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ channel.writeAndFlush(new TextWebSocketFrame(text));
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+}
|