Compare commits
149 Commits
2025-08
...
89667367ea
| Author | SHA1 | Date | |
|---|---|---|---|
| 89667367ea | |||
| 92b95dd86d | |||
|
|
6b7e38fef6 | ||
|
|
f10debe2f2 | ||
|
|
61f149b562 | ||
|
|
57ee3a4d43 | ||
| 563eb80b65 | |||
|
|
dae10378dd | ||
|
|
af4f000b13 | ||
|
|
321ec97130 | ||
|
|
0dd3502942 | ||
|
|
00ba09faae | ||
|
|
40e39d651b | ||
|
|
02c8164b7e | ||
|
|
8c598aec1e | ||
|
|
2c59defdc2 | ||
|
|
5642bf2b31 | ||
|
|
ba76df66b0 | ||
|
|
57c419eb70 | ||
|
|
98f4ecef6c | ||
|
|
20e07712cb | ||
|
|
ac1d98efdc | ||
|
|
5d161acfad | ||
|
|
1534327f6f | ||
|
|
04ada8740a | ||
|
|
98cca582f6 | ||
|
|
a2de4b80a7 | ||
|
|
071c6e3d64 | ||
| b4878d4a25 | |||
|
|
b0a4458c56 | ||
| 6357cde72b | |||
| dddffe43cb | |||
|
|
51fdf6bf59 | ||
|
|
b53ef274cf | ||
|
|
90d618b66f | ||
| e81413eaa9 | |||
| 33cefdd0b1 | |||
| 80a886a5eb | |||
| 0a85b433ba | |||
|
|
66786200bd | ||
| 15e93b6734 | |||
|
|
6843497908 | ||
|
|
cd486f419f | ||
|
|
7394762e28 | ||
|
|
2f75fe062b | ||
|
|
d4e09a09cf | ||
|
|
d58452012d | ||
|
|
014ac7931e | ||
| 3d4fb44b3a | |||
|
|
1abba4a528 | ||
|
|
d06fa8476c | ||
|
|
20cb78eb06 | ||
|
|
f3e9cb7171 | ||
|
|
6f5746861f | ||
|
|
dc34bd9b44 | ||
|
|
2dcf90d6c7 | ||
|
|
ac1c5fd43e | ||
|
|
dc0244d81d | ||
| 449de8bbc5 | |||
| 8caaf95427 | |||
| 81df650d09 | |||
|
|
69503c7ca9 | ||
| e42121ba4c | |||
|
|
35e52e0722 | ||
|
|
80c383a746 | ||
|
|
84f9e61e57 | ||
|
|
d18e84159f | ||
|
|
1ea09cc52c | ||
|
|
f0540d4c92 | ||
|
|
04299007d0 | ||
|
|
947ee4f771 | ||
|
|
c3c46b5257 | ||
|
|
213d940ff1 | ||
|
|
d220ee0187 | ||
|
|
f56c1086b9 | ||
|
|
4532f2242b | ||
|
|
9deaea9e81 | ||
|
|
44738f2878 | ||
|
|
3f12758d1b | ||
|
|
fd7ffbe87e | ||
|
|
12edb57581 | ||
|
|
a163150fec | ||
|
|
46e8811e59 | ||
| 90eb90554b | |||
|
|
694f12bc29 | ||
| 2121a293cb | |||
|
|
6c6bed4b46 | ||
|
|
7f2a61ba21 | ||
|
|
ecafa996a3 | ||
| e11107eb8f | |||
| c414f1a42a | |||
|
|
7c18d0038a | ||
|
|
b09438e29d | ||
|
|
efe92e28b1 | ||
|
|
e3f682212e | ||
|
|
a7f3925a2f | ||
|
|
4ecec5e6ef | ||
| da3373c710 | |||
|
|
8963b20dd3 | ||
| 0977a77eed | |||
|
|
3951b71fff | ||
|
|
52b99c2669 | ||
|
|
760db06120 | ||
|
|
7e66b67cde | ||
|
|
5422340dd3 | ||
|
|
d5e550a8f4 | ||
|
|
4128b21da8 | ||
|
|
6ae9037a47 | ||
|
|
d0f6cc46ad | ||
|
|
d5f22c4147 | ||
|
|
cd41320032 | ||
|
|
415fc0c129 | ||
| d6108967ce | |||
| b074ba29fc | |||
|
|
d020890639 | ||
|
|
7b9954f1fe | ||
|
|
b4bd2e6d1d | ||
|
|
ddf6da0855 | ||
| cd7ae5d06c | |||
|
|
e1872d030a | ||
|
|
c9bf604a33 | ||
|
|
257d0b3af8 | ||
| a25febc245 | |||
| db992ec790 | |||
|
|
c9fb9db0c0 | ||
| a35ee521b6 | |||
| c91b3bdc51 | |||
|
|
ac31267eb9 | ||
|
|
50d626f563 | ||
|
|
c1d2160335 | ||
| 9ad02d45f4 | |||
| f26e4f0e11 | |||
| 541a707570 | |||
| 13fd6f11ae | |||
| fe5040c7af | |||
|
|
09d2911c4c | ||
|
|
f1777f8bf1 | ||
|
|
10729c44fc | ||
| 1a0227620f | |||
| c73e062109 | |||
| df83f65328 | |||
|
|
c16c1f8e1d | ||
| f520234e55 | |||
| e7598137ae | |||
| 7078a5efe5 | |||
| 1c741d6406 | |||
|
|
b10700d976 | ||
| 71083101ae | |||
| ca1b1661d1 |
28
.claude/settings.local.json
Normal file
28
.claude/settings.local.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(dir:*)",
|
||||
"Bash(ls:*)",
|
||||
"Bash(find:*)",
|
||||
"Bash(mvn clean:*)",
|
||||
"Bash(rm:*)",
|
||||
"Bash(grep:*)",
|
||||
"Bash(mkdir:*)",
|
||||
"Read(/F:\\gitea\\fusionForce\\CN_Gather\\tools\\wave-comtrade\\src\\main\\java\\com\\njcn\\gather\\tools\\comtrade\\comparewave/**)",
|
||||
"Read(/F:\\gitea\\fusionForce\\CN_Gather\\tools\\wave-comtrade\\src\\main\\java\\com\\njcn\\gather\\tools\\comtrade\\comparewave/**)",
|
||||
"Read(/F:\\gitea\\fusionForce\\CN_Gather\\tools\\wave-comtrade\\src\\main\\java\\com\\njcn\\gather\\tools\\comtrade\\comparewave\\service\\impl/**)",
|
||||
"mcp__exa__web_search_exa",
|
||||
"WebSearch",
|
||||
"Bash(mvn compile:*)",
|
||||
"Bash(git checkout:*)",
|
||||
"Bash(mvn install:*)",
|
||||
"WebFetch(domain:officeopenxml.com)",
|
||||
"Bash(systeminfo)",
|
||||
"Bash(findstr:*)",
|
||||
"Bash(ver)",
|
||||
"Bash(git add:*)"
|
||||
],
|
||||
"deny": []
|
||||
},
|
||||
"outputStyle": "engineer-professional"
|
||||
}
|
||||
4
.vscode/settings.json
vendored
Normal file
4
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"java.compile.nullAnalysis.mode": "automatic",
|
||||
"java.configuration.updateBuildConfiguration": "automatic"
|
||||
}
|
||||
1675
CN_Gather_Detection_Netty架构详细分析文档.md
Normal file
1675
CN_Gather_Detection_Netty架构详细分析文档.md
Normal file
File diff suppressed because it is too large
Load Diff
@@ -86,6 +86,7 @@
|
||||
<artifactId>jakarta.xml.bind-api</artifactId>
|
||||
<version>2.3.3</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.glassfish.jaxb</groupId>
|
||||
<artifactId>jaxb-runtime</artifactId>
|
||||
@@ -124,6 +125,26 @@
|
||||
<version>3.10.0</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!--波形工具模块-->
|
||||
<dependency>
|
||||
<groupId>com.njcn.gather</groupId>
|
||||
<artifactId>wave-comtrade</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<!--报告工具模块-->
|
||||
<dependency>
|
||||
<groupId>com.njcn.gather</groupId>
|
||||
<artifactId>report-generator</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</dependency>
|
||||
<!--激活工具-->
|
||||
<dependency>
|
||||
<groupId>com.njcn.gather</groupId>
|
||||
<artifactId>activate-tool</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
package com.njcn.gather.detection.controller;
|
||||
|
||||
import com.njcn.common.pojo.annotation.OperateInfo;
|
||||
import com.njcn.common.pojo.constant.OperateType;
|
||||
import com.njcn.common.pojo.enums.common.LogEnum;
|
||||
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
|
||||
import com.njcn.common.pojo.response.HttpResult;
|
||||
import com.njcn.common.utils.LogUtil;
|
||||
import com.njcn.gather.detection.pojo.param.ContrastDetectionParam;
|
||||
import com.njcn.gather.detection.pojo.param.PreDetectionParam;
|
||||
import com.njcn.gather.detection.pojo.param.SimulateDetectionParam;
|
||||
@@ -142,4 +145,14 @@ public class PreDetectionController extends BaseController {
|
||||
preDetectionService.startContrastTest(param);
|
||||
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, null, methodDescribe);
|
||||
}
|
||||
|
||||
|
||||
@OperateInfo(info = LogEnum.SYSTEM_COMMON, operateType = OperateType.DOWNLOAD)
|
||||
@PostMapping("/exportAlignData")
|
||||
@ApiOperation("实时对齐数据导出为csv文件")
|
||||
public void exportAlignData() {
|
||||
String methodDescribe = getMethodDescribe("exportAlignData");
|
||||
LogUtil.njcnDebug(log, "{}", methodDescribe);
|
||||
preDetectionService.exportAlignData();
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,5 @@
|
||||
package com.njcn.gather.detection.handler;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.NumberUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
@@ -19,13 +18,16 @@ import com.njcn.gather.detection.pojo.vo.*;
|
||||
import com.njcn.gather.detection.service.impl.DetectionServiceImpl;
|
||||
import com.njcn.gather.detection.util.DetectionUtil;
|
||||
import com.njcn.gather.detection.util.socket.*;
|
||||
import com.njcn.gather.detection.util.socket.websocket.WebServiceManager;
|
||||
import com.njcn.gather.device.pojo.enums.CommonEnum;
|
||||
import com.njcn.gather.device.pojo.enums.PatternEnum;
|
||||
import com.njcn.gather.device.pojo.po.PqDevSub;
|
||||
import com.njcn.gather.device.pojo.vo.PreDetection;
|
||||
import com.njcn.gather.device.service.IPqDevService;
|
||||
import com.njcn.gather.device.service.IPqDevSubService;
|
||||
import com.njcn.gather.plan.pojo.po.AdPlan;
|
||||
import com.njcn.gather.plan.service.IAdPlanService;
|
||||
import com.njcn.gather.result.pojo.enums.ResultUnitEnum;
|
||||
import com.njcn.gather.script.pojo.param.PqScriptCheckDataParam;
|
||||
import com.njcn.gather.script.pojo.param.PqScriptIssueParam;
|
||||
import com.njcn.gather.script.pojo.po.SourceIssue;
|
||||
@@ -36,43 +38,33 @@ import com.njcn.gather.storage.pojo.po.SimAndDigHarmonicResult;
|
||||
import com.njcn.gather.storage.pojo.po.SimAndDigNonHarmonicResult;
|
||||
import com.njcn.gather.storage.service.DetectionDataDealService;
|
||||
import com.njcn.gather.storage.service.SimAndDigHarmonicService;
|
||||
import com.njcn.gather.system.cfg.service.ISysTestConfigService;
|
||||
import com.njcn.gather.system.dictionary.pojo.enums.DictDataEnum;
|
||||
import com.njcn.gather.system.dictionary.pojo.po.DictData;
|
||||
import com.njcn.gather.system.dictionary.service.IDictDataService;
|
||||
import com.njcn.gather.system.pojo.enums.DicDataEnum;
|
||||
import com.njcn.gather.system.reg.service.ISysRegResService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static com.njcn.gather.detection.util.socket.FormalTestManager.harmonicRelationMap;
|
||||
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class SocketDevResponseService {
|
||||
// ISO 8601格式
|
||||
private final DateTimeFormatter formatter = DateTimeFormatter.ISO_DATE_TIME;
|
||||
|
||||
private List<String> dataTypeList;
|
||||
private Set<String> dataTypeList;
|
||||
|
||||
private List<String> icdTypeList;
|
||||
|
||||
private final List<String> nonHarmonicList = Stream.of(DicDataEnum.FREQ.getCode(), DicDataEnum.V.getCode(), DicDataEnum.I.getCode(), DicDataEnum.IMBV.getCode(), DicDataEnum.IMBA.getCode(), DicDataEnum.VOLTAGE.getCode(), DicDataEnum.F.getCode()).collect(Collectors.toList());
|
||||
private final List<String> harmonicList = Stream.of(DicDataEnum.HV.getCode(), DicDataEnum.HI.getCode(), DicDataEnum.HP.getCode(), DicDataEnum.HSV.getCode(), DicDataEnum.HSI.getCode()).collect(Collectors.toList());
|
||||
|
||||
|
||||
private final IPqDevService iPqDevService;
|
||||
private final IPqDevSubService iPqDevSubService;
|
||||
@@ -80,24 +72,16 @@ public class SocketDevResponseService {
|
||||
private final IPqScriptDtlsService pqScriptDtlsService;
|
||||
private final DetectionServiceImpl detectionServiceImpl;
|
||||
private final DetectionDataDealService detectionDataDealService;
|
||||
private final ISysRegResService iSysRegResService;
|
||||
private final IPqScriptCheckDataService iPqScriptCheckDataService;
|
||||
private final ISysTestConfigService sysTestConfigService;
|
||||
private final SimAndDigHarmonicService adHarmonicService;
|
||||
private final IAdPlanService adPlanService;
|
||||
private final IPqScriptCheckDataService pqScriptCheckDataService;
|
||||
private final IDictDataService dictDataService;
|
||||
@Value("${phaseAngle.isEnable}")
|
||||
private Boolean isPhaseAngle;
|
||||
|
||||
|
||||
/**
|
||||
* 存储的装置相序数据
|
||||
*/
|
||||
List<DevData> devInfo = new ArrayList<>();
|
||||
|
||||
//Map<String, DevData> devDataMap = new HashMap<>();
|
||||
|
||||
/**
|
||||
* 成功结束的测点
|
||||
*/
|
||||
@@ -128,7 +112,7 @@ public class SocketDevResponseService {
|
||||
switch (Objects.requireNonNull(sourceOperateCodeEnum)) {
|
||||
//设备通讯校验
|
||||
case YJC_SBTXJY:
|
||||
devComm(socketDataMsg, param, msg);
|
||||
devComm(socketDataMsg, param);
|
||||
break;
|
||||
//协议校验
|
||||
case YJC_XYJY:
|
||||
@@ -167,6 +151,9 @@ public class SocketDevResponseService {
|
||||
break;
|
||||
case YXT:
|
||||
break;
|
||||
default:
|
||||
// todo... 要日志记录或者websocket送到前端友好提示用户
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -307,7 +294,7 @@ public class SocketDevResponseService {
|
||||
}
|
||||
break;
|
||||
default:
|
||||
CnSocketUtil.sendUnSocket(param.getUserPageId());
|
||||
WebServiceManager.sendUnknownErrorMessage(param.getUserPageId());
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -409,7 +396,7 @@ public class SocketDevResponseService {
|
||||
issueParam.setDevIds(param.getDevIds());
|
||||
issueParam.setScriptId(param.getScriptId());
|
||||
|
||||
if (param.getOperateType().equals(SourceOperateCodeEnum.RE_ERROR_TEST.getValue())) {
|
||||
if (param.getReCheckType().equals(SourceOperateCodeEnum.RE_ERROR_TEST.getValue())) {
|
||||
//不合格项复检
|
||||
Set<Integer> indexes = new HashSet<>();
|
||||
StorageParam storageParam = new StorageParam();
|
||||
@@ -450,13 +437,20 @@ public class SocketDevResponseService {
|
||||
Map<String, Long> sourceIssueMap = sourceIssues.stream().collect(Collectors.groupingBy(SourceIssue::getType, Collectors.counting()));
|
||||
SocketManager.initMap(sourceIssueMap);
|
||||
|
||||
socketMsg.setData(JSON.toJSONString(sourceIssues.get(0)));
|
||||
socketMsg.setRequestId(SourceOperateCodeEnum.FORMAL_REAL.getValue() + CnSocketUtil.STEP_TAG + sourceIssues.get(0).getType());
|
||||
SocketManager.sendMsg(param.getUserPageId() + CnSocketUtil.SOURCE_TAG, JSON.toJSONString(socketMsg));
|
||||
|
||||
//告诉前端当前项开始了
|
||||
WebSocketVO<Object> webSocketVO = new WebSocketVO<>();
|
||||
webSocketVO.setRequestId(sourceIssues.get(0).getType() + CnSocketUtil.START_TAG);
|
||||
FormalTestManager.currentIssue = sourceIssues.get(0);
|
||||
String type = sourceIssues.get(0).getType();
|
||||
if (ResultUnitEnum.P.getCode().equals(type)) {
|
||||
sourceIssues.get(0).setType(ResultUnitEnum.V_ABSOLUTELY.getCode());
|
||||
webSocketVO.setRequestId(ResultUnitEnum.P.getCode() + CnSocketUtil.START_TAG);
|
||||
} else {
|
||||
webSocketVO.setRequestId(sourceIssues.get(0).getType() + CnSocketUtil.START_TAG);
|
||||
}
|
||||
socketMsg.setData(JSON.toJSONString(sourceIssues.get(0)));
|
||||
socketMsg.setRequestId(SourceOperateCodeEnum.FORMAL_REAL.getValue() + CnSocketUtil.STEP_TAG + type);
|
||||
SocketManager.sendMsg(param.getUserPageId() + CnSocketUtil.SOURCE_TAG, JSON.toJSONString(socketMsg));
|
||||
|
||||
webSocketVO.setDesc(null);
|
||||
WebServiceManager.sendMsg(param.getUserPageId(), JSON.toJSONString(webSocketVO));
|
||||
} else {
|
||||
@@ -549,7 +543,7 @@ public class SocketDevResponseService {
|
||||
DevXiNumData.GF gfItem = createGFItem(monitorId, F);
|
||||
gf.add(gfItem);
|
||||
//表格数据
|
||||
CnSocketUtil.sendToWebSocket(param.getUserPageId(), SourceOperateCodeEnum.Coefficient_Check.getValue(), SourceOperateCodeEnum.DATA_CHNFACTOR$02.getValue(), coefficientVO, null);
|
||||
WebServiceManager.sendDetectionMessage(param.getUserPageId(), SourceOperateCodeEnum.Coefficient_Check.getValue(), SourceOperateCodeEnum.DATA_CHNFACTOR$02.getValue(), coefficientVO, null);
|
||||
});
|
||||
DevXiNumData devXiNumData = createDevXiNumData(devIp, gf, xiFlag.get());
|
||||
saveDevXiNumData(devIp, devXiNumData);
|
||||
@@ -619,7 +613,7 @@ public class SocketDevResponseService {
|
||||
|
||||
private void assemblyEntity(List<DevData> deList, DevXiNumData.F F, DevXiNumData.GF startF, CoefficientVO coefficientVO, CoefficientVO.DevParameter devParameter) {
|
||||
//表示接收完成,必须保证3个数
|
||||
if (deList.size() >= 3) {
|
||||
if (deList.size() >= 7) {
|
||||
List<Double> aList = deList.stream().map(it -> it.getSqlData().get(0).getList().getA()).collect(Collectors.toList());
|
||||
List<Double> bList = deList.stream().map(it -> it.getSqlData().get(0).getList().getB()).collect(Collectors.toList());
|
||||
List<Double> cList = deList.stream().map(it -> it.getSqlData().get(0).getList().getC()).collect(Collectors.toList());
|
||||
@@ -694,7 +688,7 @@ public class SocketDevResponseService {
|
||||
*/
|
||||
private Double reduceList(List<Double> valList) {
|
||||
// valList.subList(0, 5).clear();
|
||||
// valList.subList(valList.size() - 3, valList.size() - 1).clear();
|
||||
valList.subList(valList.size() - 2, valList.size()).clear();
|
||||
return valList.stream().mapToDouble(Double::doubleValue).average().getAsDouble();
|
||||
}
|
||||
|
||||
@@ -702,7 +696,7 @@ public class SocketDevResponseService {
|
||||
/**
|
||||
* 装置通讯检测
|
||||
*/
|
||||
private void devComm(SocketDataMsg socketDataMsg, PreDetectionParam param, String msg) {
|
||||
private void devComm(SocketDataMsg socketDataMsg, PreDetectionParam param) {
|
||||
SourceResponseCodeEnum dictDataEnumByCode = SourceResponseCodeEnum.getDictDataEnumByCode(socketDataMsg.getCode());
|
||||
String s = param.getUserPageId() + CnSocketUtil.DEV_TAG;
|
||||
SocketMsg<String> socketMsg = new SocketMsg<>();
|
||||
@@ -712,12 +706,11 @@ public class SocketDevResponseService {
|
||||
successComm.add(result);
|
||||
//单个测点通讯成功
|
||||
WebServiceManager.sendMsg(param.getUserPageId(), MsgUtil.msgToWebData(socketDataMsg, FormalTestManager.devNameMapComm, 1));
|
||||
|
||||
System.out.println("设备通讯校验!" + successComm.size() + "=====" + FormalTestManager.monitorIdListComm.size());
|
||||
if (successComm.size() == FormalTestManager.monitorIdListComm.size()) {
|
||||
// 通知前端整个装置通讯检测过程成功
|
||||
SocketDataMsg temMsg = new SocketDataMsg();
|
||||
temMsg.setCode(SourceResponseCodeEnum.DEV_COMM_ALL_SUCCESS.getCode());
|
||||
temMsg.setCode(SourceResponseCodeEnum.ALL_SUCCESS.getCode());
|
||||
temMsg.setOperateCode(SourceOperateCodeEnum.DEV_INIT_GATHER_01.getValue());
|
||||
temMsg.setRequestId(SourceOperateCodeEnum.YJC_SBTXJY.getValue());
|
||||
WebServiceManager.sendMsg(param.getUserPageId(), JSON.toJSONString(temMsg));
|
||||
@@ -754,14 +747,12 @@ public class SocketDevResponseService {
|
||||
case RE_OPERATE:
|
||||
//出现已经初始化情况,发送用户用户确认是否继续检测
|
||||
WebServiceManager.sendMsg(param.getUserPageId(), JSON.toJSONString(socketDataMsg));
|
||||
CnSocketUtil.quitSend(param);
|
||||
break;
|
||||
case NO_INIT_DEV:
|
||||
//发起关闭操作
|
||||
CnSocketUtil.quitSend(param);
|
||||
break;
|
||||
default:
|
||||
CnSocketUtil.sendUnSocket(param.getUserPageId());
|
||||
WebServiceManager.sendUnknownErrorMessage(param.getUserPageId());
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -793,7 +784,6 @@ public class SocketDevResponseService {
|
||||
String s = param.getUserPageId() + CnSocketUtil.DEV_TAG;
|
||||
switch (Objects.requireNonNull(dictDataEnumByCode)) {
|
||||
case SUCCESS:
|
||||
|
||||
if (socketDataMsg.getOperateCode().equals(SourceOperateCodeEnum.DEV_INIT_GATHER_02.getValue())) {
|
||||
successComm.add(socketDataMsg.getData());
|
||||
WebServiceManager.sendMsg(param.getUserPageId(), MsgUtil.msgToWebData(socketDataMsg, FormalTestManager.devNameMapComm, 1));
|
||||
@@ -830,51 +820,73 @@ public class SocketDevResponseService {
|
||||
icdCheckDataMap.clear();
|
||||
|
||||
dataTypeList = pqScriptDtlsService.getScriptToIcdCheckInfo(param);
|
||||
icdTypeList = FormalTestManager.devList.stream().map(PreDetection::getIcdType).distinct().collect(Collectors.toList());
|
||||
icdTypeList = FormalTestManager.devList.stream().map(PreDetection::getIcdType).collect(Collectors.toList());
|
||||
PreDetection preDetection = FormalTestManager.devList.stream().filter(obj -> obj.getIcdType().equals(icdTypeList.get(0))).findFirst().orElse(null);
|
||||
boolean angleCheck = false;
|
||||
if (ObjectUtil.isNotNull(preDetection)) {
|
||||
angleCheck = preDetection.getAngle() == 1 ? true : false;
|
||||
}
|
||||
if (angleCheck) {
|
||||
dataTypeList.add(DetectionCodeEnum.REAL_PREFIX.getCode() + DetectionCodeEnum.VA.getCode());
|
||||
dataTypeList.add(DetectionCodeEnum.REAL_PREFIX.getCode() + DetectionCodeEnum.IA.getCode());
|
||||
} else {
|
||||
dataTypeList.remove(DetectionCodeEnum.REAL_PREFIX.getCode() + DetectionCodeEnum.VA.getCode());
|
||||
dataTypeList.remove(DetectionCodeEnum.REAL_PREFIX.getCode() + DetectionCodeEnum.IA.getCode());
|
||||
}
|
||||
|
||||
socketMsg.setRequestId(SourceOperateCodeEnum.YJC_XYJY.getValue());
|
||||
socketMsg.setOperateCode(SourceOperateCodeEnum.VERIFY_MAPPING$01.getValue());
|
||||
Map<String, Object> map = new HashMap<>(2);
|
||||
map.put("dataType", dataTypeList);
|
||||
map.put("dataType", new ArrayList<>(dataTypeList));
|
||||
map.put("icdType", icdTypeList.get(0));
|
||||
socketMsg.setData(JSON.toJSONString(map));
|
||||
System.out.println("开始脚本与icd校验:++++++++++");
|
||||
SocketManager.sendMsg(s, JSON.toJSONString(socketMsg));
|
||||
}
|
||||
|
||||
completeJudgment(param);
|
||||
} else if (socketDataMsg.getOperateCode().equals(SourceOperateCodeEnum.VERIFY_MAPPING$01.getValue())) {
|
||||
String data = socketDataMsg.getData();
|
||||
IcdCheckData icdCheckData = JSON.parseObject(data, IcdCheckData.class);
|
||||
|
||||
boolean isContinue = true;
|
||||
for (int i = 0; i < icdCheckData.getResultData().size(); i++) {
|
||||
IcdCheckData.ResultData item = icdCheckData.getResultData().get(i);
|
||||
Integer errorType = getErrorType(item.getDesc(), item.getPhaseResult());
|
||||
// 校验脚本与icd校验失败
|
||||
if (errorType.equals(0)) {
|
||||
isContinue = false;
|
||||
Map<String, Object> map = new HashMap<>(2);
|
||||
map.put("icdType", icdCheckData.getIcdType());
|
||||
DetectionCodeEnum anEnum = DetectionCodeEnum.getDetectionCodeByCode(item.getDesc());
|
||||
map.put("dataType", anEnum.getMessage());
|
||||
WebSocketVO<String> webSocketVO = new WebSocketVO<>();
|
||||
webSocketVO.setData(JSON.toJSONString(map));
|
||||
webSocketVO.setRequestId(SourceOperateCodeEnum.YJC_XYJY.getValue());
|
||||
webSocketVO.setOperateCode(SourceOperateCodeEnum.VERIFY_MAPPING$01.getValue());
|
||||
webSocketVO.setCode(SourceResponseCodeEnum.SUCCESS.getCode());
|
||||
WebServiceManager.sendMsg(param.getUserPageId(), JSON.toJSONString(webSocketVO));
|
||||
CnSocketUtil.quitSend(param);
|
||||
break;
|
||||
List<String> descList = icdCheckData.getResultData().stream().map(
|
||||
obj -> {
|
||||
if (DetectionCodeEnum.MAG.getCode().equals(obj.getDesc()) || DetectionCodeEnum.DUR.getCode().equals(obj.getDesc()) || DetectionCodeEnum.PST.getCode().equals(obj.getDesc())) {
|
||||
return DetectionCodeEnum.AVG_PREFIX.getCode() + obj.getDesc();
|
||||
} else {
|
||||
return DetectionCodeEnum.REAL_PREFIX.getCode() + obj.getDesc();
|
||||
}
|
||||
}
|
||||
).collect(Collectors.toList());
|
||||
if (descList.containsAll(dataTypeList)) {
|
||||
for (int i = 0; i < icdCheckData.getResultData().size(); i++) {
|
||||
IcdCheckData.ResultData item = icdCheckData.getResultData().get(i);
|
||||
Integer errorType = getErrorType(item.getDesc(), item.getPhaseResult());
|
||||
// 校验脚本与icd校验失败
|
||||
if (errorType.equals(0)) {
|
||||
isContinue = false;
|
||||
Map<String, Object> map = new HashMap<>(2);
|
||||
map.put("icdType", icdCheckData.getIcdType());
|
||||
DetectionCodeEnum anEnum = DetectionCodeEnum.getDetectionCodeByCode(item.getDesc());
|
||||
map.put("dataType", anEnum.getMessage());
|
||||
WebSocketVO<String> webSocketVO = new WebSocketVO<>();
|
||||
webSocketVO.setData(JSON.toJSONString(map));
|
||||
webSocketVO.setRequestId(SourceOperateCodeEnum.YJC_XYJY.getValue());
|
||||
webSocketVO.setOperateCode(SourceOperateCodeEnum.VERIFY_MAPPING$01.getValue());
|
||||
webSocketVO.setCode(SourceResponseCodeEnum.FAIL.getCode());
|
||||
WebServiceManager.sendMsg(param.getUserPageId(), JSON.toJSONString(webSocketVO));
|
||||
} else {
|
||||
icdCheckDataMap.put(icdCheckData.getIcdType(), icdCheckData);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
isContinue = false;
|
||||
}
|
||||
|
||||
icdCheckDataMap.put(icdCheckData.getIcdType(), icdCheckData);
|
||||
if (isContinue) {
|
||||
//System.out.println("icdCheckDataMap.size()="+icdCheckDataMap.size()+",icdTypeList.size()="+icdTypeList.size());
|
||||
if (icdCheckDataMap.size() == icdTypeList.size()) {
|
||||
SocketDataMsg temMsg = new SocketDataMsg();
|
||||
temMsg.setCode(SourceResponseCodeEnum.DEV_COMM_ALL_SUCCESS.getCode());
|
||||
temMsg.setCode(SourceResponseCodeEnum.ALL_SUCCESS.getCode());
|
||||
temMsg.setOperateCode(SourceOperateCodeEnum.VERIFY_MAPPING$01.getValue());
|
||||
temMsg.setRequestId(SourceOperateCodeEnum.YJC_XYJY.getValue());
|
||||
WebServiceManager.sendMsg(param.getUserPageId(), JSON.toJSONString(temMsg));
|
||||
@@ -897,7 +909,7 @@ public class SocketDevResponseService {
|
||||
SocketManager.sendMsg(param.getUserPageId() + CnSocketUtil.SOURCE_TAG, JSON.toJSONString(socketMsg));
|
||||
} else if (param.getTestItemList().get(2)) {
|
||||
// 后续做正式检测
|
||||
if (param.getOperateType().equals(SourceOperateCodeEnum.RE_ERROR_TEST.getValue())) {
|
||||
if (param.getReCheckType().equals(SourceOperateCodeEnum.RE_ERROR_TEST.getValue())) {
|
||||
//不合格项复检
|
||||
Set<Integer> indexes = new HashSet<>();
|
||||
StorageParam storageParam = new StorageParam();
|
||||
@@ -933,35 +945,43 @@ public class SocketDevResponseService {
|
||||
Map<String, Long> sourceIssueMap = sourceIssues.stream().collect(Collectors.groupingBy(SourceIssue::getType, Collectors.counting()));
|
||||
SocketManager.initMap(sourceIssueMap);
|
||||
|
||||
socketMsg.setData(JSON.toJSONString(sourceIssues.get(0)));
|
||||
socketMsg.setRequestId(SourceOperateCodeEnum.FORMAL_REAL.getValue() + CnSocketUtil.STEP_TAG + sourceIssues.get(0).getType());
|
||||
SocketManager.sendMsg(param.getUserPageId() + CnSocketUtil.SOURCE_TAG, JSON.toJSONString(socketMsg));
|
||||
|
||||
//告诉前端当前项开始了
|
||||
WebSocketVO<Object> webSocketVO = new WebSocketVO<>();
|
||||
webSocketVO.setRequestId(sourceIssues.get(0).getType() + CnSocketUtil.START_TAG);
|
||||
String type = sourceIssues.get(0).getType();
|
||||
if (ResultUnitEnum.P.getCode().equals(type)) {
|
||||
sourceIssues.get(0).setType(ResultUnitEnum.V_ABSOLUTELY.getCode());
|
||||
webSocketVO.setRequestId(ResultUnitEnum.P.getCode() + CnSocketUtil.START_TAG);
|
||||
} else {
|
||||
webSocketVO.setRequestId(sourceIssues.get(0).getType() + CnSocketUtil.START_TAG);
|
||||
}
|
||||
FormalTestManager.currentIssue = sourceIssues.get(0);
|
||||
socketMsg.setData(JSON.toJSONString(sourceIssues.get(0)));
|
||||
socketMsg.setRequestId(SourceOperateCodeEnum.FORMAL_REAL.getValue() + CnSocketUtil.STEP_TAG + type);
|
||||
socketMsg.setOperateCode(SourceOperateCodeEnum.OPER_GATHER.getValue());
|
||||
SocketManager.sendMsg(param.getUserPageId() + CnSocketUtil.SOURCE_TAG, JSON.toJSONString(socketMsg));
|
||||
|
||||
webSocketVO.setDesc(null);
|
||||
WebServiceManager.sendMsg(param.getUserPageId(), JSON.toJSONString(webSocketVO));
|
||||
}
|
||||
// if (SourceOperateCodeEnum.TEST_TEM_START.getValue().equals(param.getOperateType())) {
|
||||
// //暂停检测后的继续检测
|
||||
// System.out.println("进入暂停后的继续检测》》》》》》》》》》》》》》》》》》》》》》》》》》》" + "剩余检测小项" + SocketManager.getSourceList().size());
|
||||
// if (CollUtil.isNotEmpty(SocketManager.getSourceList())) {
|
||||
// SourceIssue sourceIssue = SocketManager.getSourceList().get(0);
|
||||
// socketMsg.setRequestId(SourceOperateCodeEnum.FORMAL_REAL.getValue() + CnSocketUtil.STEP_TAG + sourceIssue.getType());
|
||||
// socketMsg.setOperateCode(SourceOperateCodeEnum.OPER_GATHER.getValue());
|
||||
// socketMsg.setData(JSON.toJSONString(sourceIssue));
|
||||
// SocketManager.sendMsg(param.getUserPageId() + CnSocketUtil.SOURCE_TAG, JSON.toJSONString(socketMsg));
|
||||
// }
|
||||
// } else {
|
||||
//
|
||||
// }
|
||||
} else {
|
||||
// 发送下一个脚本与icd校验
|
||||
String icdType = icdTypeList.stream().filter(it -> !icdCheckDataMap.containsKey(it)).findFirst().orElse(null);
|
||||
if (ObjectUtil.isNotNull(icdType)) {
|
||||
PreDetection preDetection = FormalTestManager.devList.stream().filter(obj -> obj.getIcdType().equals(icdType)).findFirst().orElse(null);
|
||||
boolean angleCheck = false;
|
||||
if (ObjectUtil.isNotNull(preDetection)) {
|
||||
angleCheck = preDetection.getAngle() == 1 ? true : false;
|
||||
}
|
||||
if (angleCheck) {
|
||||
dataTypeList.add(DetectionCodeEnum.REAL_PREFIX.getCode() + DetectionCodeEnum.VA.getCode());
|
||||
dataTypeList.add(DetectionCodeEnum.REAL_PREFIX.getCode() + DetectionCodeEnum.IA.getCode());
|
||||
} else {
|
||||
dataTypeList.remove(DetectionCodeEnum.REAL_PREFIX.getCode() + DetectionCodeEnum.VA.getCode());
|
||||
dataTypeList.remove(DetectionCodeEnum.REAL_PREFIX.getCode() + DetectionCodeEnum.IA.getCode());
|
||||
}
|
||||
|
||||
Map<String, Object> map = new HashMap<>(2);
|
||||
map.put("dataType", dataTypeList);
|
||||
map.put("dataType", new ArrayList<>(dataTypeList));
|
||||
map.put("icdType", icdType);
|
||||
socketMsg.setData(JSON.toJSONString(map));
|
||||
socketMsg.setRequestId(SourceOperateCodeEnum.YJC_XYJY.getValue());
|
||||
@@ -970,6 +990,23 @@ public class SocketDevResponseService {
|
||||
SocketManager.sendMsg(s, JSON.toJSONString(socketMsg));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
WebSocketVO<String> webSocketVO = new WebSocketVO<>();
|
||||
webSocketVO.setRequestId(SourceOperateCodeEnum.YJC_XYJY.getValue());
|
||||
webSocketVO.setOperateCode(SourceOperateCodeEnum.VERIFY_MAPPING$01.getValue());
|
||||
webSocketVO.setCode(SourceResponseCodeEnum.FAIL.getCode());
|
||||
|
||||
dataTypeList.removeAll(descList);
|
||||
for (String dataType : dataTypeList) {
|
||||
Map<String, Object> map = new HashMap<>(2);
|
||||
map.put("icdType", icdCheckData.getIcdType());
|
||||
DetectionCodeEnum anEnum = DetectionCodeEnum.getDetectionCodeByCode(dataType.replace(DetectionCodeEnum.REAL_PREFIX.getCode(), ""));
|
||||
map.put("dataType", anEnum.getMessage());
|
||||
webSocketVO.setData(JSON.toJSONString(map));
|
||||
WebServiceManager.sendMsg(param.getUserPageId(), JSON.toJSONString(webSocketVO));
|
||||
}
|
||||
|
||||
CnSocketUtil.quitSend(param);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -996,7 +1033,8 @@ public class SocketDevResponseService {
|
||||
CnSocketUtil.quitSend(param);
|
||||
break;
|
||||
default:
|
||||
CnSocketUtil.sendUnSocket(param.getUserPageId());
|
||||
WebServiceManager.sendUnknownErrorMessage(param.getUserPageId());
|
||||
// todo... 这种情况是报文的状态码不一致,需要记录到日志表,以便问题追踪
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1022,11 +1060,13 @@ public class SocketDevResponseService {
|
||||
}
|
||||
} else {
|
||||
if (ObjectUtil.isNotNull(list.getA()) && list.getA().equals(1.0) && ObjectUtil.isNotNull(list.getB()) && list.getB().equals(1.0) && ObjectUtil.isNotNull(list.getC()) && list.getC().equals(1.0) || ObjectUtil.isNotNull(list.getT()) && list.getT().equals(1.0)) {
|
||||
return 1; // 装置上送错误
|
||||
// 装置上送错误
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0; // icd文件与脚本不匹配
|
||||
// icd文件与脚本不匹配
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -1076,8 +1116,8 @@ public class SocketDevResponseService {
|
||||
WebSocketVO<String> webSocketVO = new WebSocketVO<>();
|
||||
webSocketVO.setRequestId(SourceOperateCodeEnum.YJC_XUJY.getValue());
|
||||
webSocketVO.setOperateCode(SourceOperateCodeEnum.DEV_DATA_REQUEST_02.getValue());
|
||||
webSocketVO.setCode(SourceResponseCodeEnum.PHASE_CHECK_FAIL.getCode());
|
||||
webSocketVO.setData(SourceResponseCodeEnum.PHASE_CHECK_FAIL.getMessage());
|
||||
webSocketVO.setCode(SourceResponseCodeEnum.ALL_FAIL.getCode());
|
||||
webSocketVO.setData(SourceResponseCodeEnum.ALL_FAIL.getMessage());
|
||||
WebServiceManager.sendMsg(param.getUserPageId(), JSON.toJSONString(webSocketVO));
|
||||
|
||||
CnSocketUtil.quitSend(param);
|
||||
@@ -1087,7 +1127,7 @@ public class SocketDevResponseService {
|
||||
|
||||
//向前端推送消息
|
||||
SocketDataMsg temMsg = new SocketDataMsg();
|
||||
temMsg.setCode(SourceResponseCodeEnum.DEV_COMM_ALL_SUCCESS.getCode());
|
||||
temMsg.setCode(SourceResponseCodeEnum.ALL_SUCCESS.getCode());
|
||||
temMsg.setOperateCode(SourceOperateCodeEnum.DEV_DATA_REQUEST_02.getValue());
|
||||
temMsg.setRequestId(SourceOperateCodeEnum.YJC_XUJY.getValue());
|
||||
WebServiceManager.sendMsg(param.getUserPageId(), JSON.toJSONString(temMsg));
|
||||
@@ -1127,7 +1167,7 @@ public class SocketDevResponseService {
|
||||
issueParam.setDevIds(param.getDevIds());
|
||||
issueParam.setScriptId(param.getScriptId());
|
||||
|
||||
if (param.getOperateType().equals(SourceOperateCodeEnum.RE_ERROR_TEST.getValue())) {
|
||||
if (param.getReCheckType().equals(SourceOperateCodeEnum.RE_ERROR_TEST.getValue())) {
|
||||
//不合格项复检
|
||||
Set<Integer> indexes = new HashSet<>();
|
||||
StorageParam storageParam = new StorageParam();
|
||||
@@ -1163,14 +1203,21 @@ public class SocketDevResponseService {
|
||||
Map<String, Long> sourceIssueMap = sourceIssues.stream().collect(Collectors.groupingBy(SourceIssue::getType, Collectors.counting()));
|
||||
SocketManager.initMap(sourceIssueMap);
|
||||
|
||||
//告诉前端当前项开始了
|
||||
WebSocketVO<Object> webSocketVO = new WebSocketVO<>();
|
||||
String type = sourceIssues.get(0).getType();
|
||||
if (ResultUnitEnum.P.getCode().equals(type)) {
|
||||
sourceIssues.get(0).setType(ResultUnitEnum.V_ABSOLUTELY.getCode());
|
||||
webSocketVO.setRequestId(ResultUnitEnum.P.getCode() + CnSocketUtil.START_TAG);
|
||||
} else {
|
||||
webSocketVO.setRequestId(sourceIssues.get(0).getType() + CnSocketUtil.START_TAG);
|
||||
}
|
||||
FormalTestManager.currentIssue = sourceIssues.get(0);
|
||||
socketMsg.setData(JSON.toJSONString(sourceIssues.get(0)));
|
||||
socketMsg.setRequestId(SourceOperateCodeEnum.FORMAL_REAL.getValue() + CnSocketUtil.STEP_TAG + sourceIssues.get(0).getType());
|
||||
socketMsg.setRequestId(SourceOperateCodeEnum.FORMAL_REAL.getValue() + CnSocketUtil.STEP_TAG + type);
|
||||
socketMsg.setOperateCode(SourceOperateCodeEnum.OPER_GATHER.getValue());
|
||||
SocketManager.sendMsg(param.getUserPageId() + CnSocketUtil.SOURCE_TAG, JSON.toJSONString(socketMsg));
|
||||
|
||||
//告诉前端当前项开始了
|
||||
WebSocketVO<Object> webSocketVO = new WebSocketVO<>();
|
||||
webSocketVO.setRequestId(sourceIssues.get(0).getType() + CnSocketUtil.START_TAG);
|
||||
webSocketVO.setDesc(null);
|
||||
WebServiceManager.sendMsg(param.getUserPageId(), JSON.toJSONString(webSocketVO));
|
||||
} else {
|
||||
@@ -1206,7 +1253,7 @@ public class SocketDevResponseService {
|
||||
CnSocketUtil.quitSend(param);
|
||||
break;
|
||||
default:
|
||||
CnSocketUtil.sendUnSocket(param.getUserPageId());
|
||||
WebServiceManager.sendUnknownErrorMessage(param.getUserPageId());
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1257,8 +1304,14 @@ public class SocketDevResponseService {
|
||||
System.out.println("当前小项结束进行删除============" + sourceIssue.getType() + CnSocketUtil.SPLIT_TAG + sourceIssue.getIndex());
|
||||
|
||||
//小项检测完后小项数减一,并更新map
|
||||
long residueCount = SocketManager.getSourceTarget(sourceIssue.getType()) - 1;
|
||||
SocketManager.addTargetMap(sourceIssue.getType(), residueCount);
|
||||
long residueCount = 0;
|
||||
if (sourceIssue.getIsP()) {
|
||||
residueCount = SocketManager.getSourceTarget(ResultUnitEnum.P.getCode()) - 1;
|
||||
SocketManager.addTargetMap(ResultUnitEnum.P.getCode(), residueCount);
|
||||
} else {
|
||||
residueCount = SocketManager.getSourceTarget(sourceIssue.getType()) - 1;
|
||||
SocketManager.addTargetMap(sourceIssue.getType(), residueCount);
|
||||
}
|
||||
System.out.println("该大项还有" + residueCount + "个小项没有进行检测!!!!!!!!");
|
||||
|
||||
//当该大项中小项数量变为0,则任务该大项检测结束
|
||||
@@ -1282,7 +1335,7 @@ public class SocketDevResponseService {
|
||||
resultList.add(devTem);
|
||||
});
|
||||
allDevTestList.clear();
|
||||
CnSocketUtil.sendToWebSocket(param.getUserPageId(), socketDataMsg.getRequestId().split(CnSocketUtil.STEP_TAG)[1] + CnSocketUtil.END_TAG, null, resultList, null);
|
||||
WebServiceManager.sendDetectionMessage(param.getUserPageId(), socketDataMsg.getRequestId().split(CnSocketUtil.STEP_TAG)[1] + CnSocketUtil.END_TAG, null, resultList, null);
|
||||
}
|
||||
//在这一步判断是否已经触发暂停按钮
|
||||
if (FormalTestManager.stopFlag && CollUtil.isNotEmpty(SocketManager.getSourceList())) {
|
||||
@@ -1300,14 +1353,19 @@ public class SocketDevResponseService {
|
||||
SourceIssue sourceIssues = SocketManager.getSourceList().get(0);
|
||||
// 如果上一个大项检测完成,则检测下一个大项,并向前端推送消息
|
||||
if (residueCount == 0) {
|
||||
CnSocketUtil.sendToWebSocket(param.getUserPageId(), sourceIssues.getType() + CnSocketUtil.START_TAG, null, new ArrayList<>(), null);
|
||||
WebServiceManager.sendDetectionMessage(param.getUserPageId(), sourceIssues.getType() + CnSocketUtil.START_TAG, null, new ArrayList<>(), null);
|
||||
}
|
||||
String type = sourceIssues.getType();
|
||||
if (sourceIssues.getIsP()) {
|
||||
sourceIssues.setType(ResultUnitEnum.V_ABSOLUTELY.getCode());
|
||||
type = ResultUnitEnum.P.getCode();
|
||||
}
|
||||
|
||||
//控源下发下一个小项脚本
|
||||
SocketMsg<String> xuMsg = new SocketMsg<>();
|
||||
xuMsg.setOperateCode(SourceOperateCodeEnum.OPER_GATHER.getValue());
|
||||
xuMsg.setData(JSON.toJSONString(sourceIssues));
|
||||
xuMsg.setRequestId(SourceOperateCodeEnum.FORMAL_REAL.getValue() + CnSocketUtil.STEP_TAG + sourceIssues.getType());
|
||||
xuMsg.setRequestId(SourceOperateCodeEnum.FORMAL_REAL.getValue() + CnSocketUtil.STEP_TAG + type);
|
||||
SocketManager.sendMsg(param.getUserPageId() + CnSocketUtil.SOURCE_TAG, JSON.toJSONString(xuMsg));
|
||||
} else {
|
||||
//TODO 是否最终检测完成需要推送给用户
|
||||
@@ -1351,7 +1409,7 @@ public class SocketDevResponseService {
|
||||
case MESSAGE_PARSING_ERROR:
|
||||
break;
|
||||
default:
|
||||
CnSocketUtil.sendUnSocket(param.getUserPageId());
|
||||
WebServiceManager.sendUnknownErrorMessage(param.getUserPageId());
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1453,7 +1511,7 @@ public class SocketDevResponseService {
|
||||
case QUIT_INIT_01:
|
||||
//关闭所有
|
||||
SocketManager.removeUser(s);
|
||||
// CnSocketUtil.quitSendSource(param);
|
||||
CnSocketUtil.quitSendSource(param);
|
||||
break;
|
||||
case QUIT_INIT_02:
|
||||
socketMsg.setRequestId(SourceOperateCodeEnum.QUITE.getValue());
|
||||
@@ -1490,7 +1548,7 @@ public class SocketDevResponseService {
|
||||
}
|
||||
break;
|
||||
default:
|
||||
CnSocketUtil.sendUnSocket(param.getUserPageId());
|
||||
WebServiceManager.sendUnknownErrorMessage(param.getUserPageId());
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1522,9 +1580,10 @@ public class SocketDevResponseService {
|
||||
|
||||
private String devMessage(String type,
|
||||
List<DevData.SqlDataDTO> data,
|
||||
List<DevData.SqlDataDTO> dataPhase) {
|
||||
List<DevData.SqlDataDTO> dataPhase,
|
||||
boolean angleCheck) {
|
||||
StringBuffer str = new StringBuffer();
|
||||
if (isPhaseAngle) {
|
||||
if (angleCheck) {
|
||||
if (CollUtil.isNotEmpty(data) && CollUtil.isNotEmpty(dataPhase)) {
|
||||
if (data.size() == dataPhase.size()) {
|
||||
DevData.SqlDataDTO.ListDTO dto = data.get(0).getList();
|
||||
@@ -1589,7 +1648,13 @@ public class SocketDevResponseService {
|
||||
}
|
||||
compareDev.setDevName(devName);
|
||||
compareDev.setLineNum(split[1]);
|
||||
getSourceCompareDev(type.get(2), dataV, dataVPhase, compareDev, sourceMessageMap.get(type.get(3)), sourceV, type.get(4));
|
||||
|
||||
boolean angleCheck = false;
|
||||
PreDetection preDetection = FormalTestManager.devList.stream().filter(x -> x.getDevIP().equals(split[0])).findFirst().orElse(null);
|
||||
if (ObjectUtil.isNotNull(preDetection)) {
|
||||
angleCheck = preDetection.getAngle() == 1 ? true : false;
|
||||
}
|
||||
getSourceCompareDev(type.get(2), dataV, dataVPhase, compareDev, sourceMessageMap.get(type.get(3)), sourceV, type.get(4), angleCheck);
|
||||
info.add(compareDev);
|
||||
}
|
||||
|
||||
@@ -1608,20 +1673,19 @@ public class SocketDevResponseService {
|
||||
SourceCompareDev compareDev,
|
||||
String desc,
|
||||
List<SourceIssue.ChannelListDTO> channelList,
|
||||
String name
|
||||
|
||||
) {
|
||||
String name,
|
||||
boolean angleCheck) {
|
||||
//源信息
|
||||
Map<String, SourceIssue.ChannelListDTO> sourceMap = channelList.stream()
|
||||
.collect(Collectors.toMap(x -> x.getChannelType(), Function.identity()));
|
||||
Boolean a = getaBoolean(sourceMap.get(type + "a"), CollUtil.isNotEmpty(data) ? data.get(0).getList().getA() : null,
|
||||
CollUtil.isNotEmpty(dataPhase) ? dataPhase.get(0).getList().getA() : null);
|
||||
CollUtil.isNotEmpty(dataPhase) ? dataPhase.get(0).getList().getA() : null, angleCheck);
|
||||
Boolean b = getaBoolean(sourceMap.get(type + "b"), CollUtil.isNotEmpty(data) ? data.get(0).getList().getB() : null,
|
||||
CollUtil.isNotEmpty(dataPhase) ? dataPhase.get(0).getList().getB() : null);
|
||||
CollUtil.isNotEmpty(dataPhase) ? dataPhase.get(0).getList().getB() : null, angleCheck);
|
||||
Boolean c = getaBoolean(sourceMap.get(type + "c"), CollUtil.isNotEmpty(data) ? data.get(0).getList().getC() : null,
|
||||
CollUtil.isNotEmpty(dataPhase) ? dataPhase.get(0).getList().getC() : null);
|
||||
CollUtil.isNotEmpty(dataPhase) ? dataPhase.get(0).getList().getC() : null, angleCheck);
|
||||
compareDev.setIsQualified(a && b && c);
|
||||
compareDev.setDesc(name + (compareDev.getIsQualified() ? "合格->" : "不合格->") + CnSocketUtil.STEP_TAG + desc + CnSocketUtil.STEP_TAG + devMessage(type, data, dataPhase));
|
||||
compareDev.setDesc(name + (compareDev.getIsQualified() ? "合格->" : "不合格->") + CnSocketUtil.STEP_TAG + desc + CnSocketUtil.STEP_TAG + devMessage(type, data, dataPhase, angleCheck));
|
||||
return compareDev;
|
||||
}
|
||||
|
||||
@@ -1633,7 +1697,7 @@ public class SocketDevResponseService {
|
||||
* @param devPhase 装置返回相角数据
|
||||
* @return
|
||||
*/
|
||||
private Boolean getaBoolean(SourceIssue.ChannelListDTO channelListDTO, Double devData, Double devPhase) {
|
||||
private Boolean getaBoolean(SourceIssue.ChannelListDTO channelListDTO, Double devData, Double devPhase, boolean angleCheck) {
|
||||
Boolean isDev = false;
|
||||
Boolean isPhase = false;
|
||||
|
||||
@@ -1642,7 +1706,7 @@ public class SocketDevResponseService {
|
||||
BigDecimal.valueOf(channelListDTO.getFAmp() * 0.95),
|
||||
BigDecimal.valueOf(channelListDTO.getFAmp() * 1.05));
|
||||
}
|
||||
if (isPhaseAngle) {
|
||||
if (angleCheck) {
|
||||
if (ObjectUtil.isNotNull(devPhase)) {
|
||||
isPhase = phaseBoolean(channelListDTO, devPhase);
|
||||
}
|
||||
@@ -1707,7 +1771,8 @@ public class SocketDevResponseService {
|
||||
FormalTestManager.monitorIdListComm = pqDevList.stream().flatMap(x -> x.getMonitorList().stream()).map(PreDetection.MonitorListDTO::getLineId).collect(Collectors.toList());
|
||||
|
||||
FormalTestManager.devNameMapComm = pqDevList.stream().collect(Collectors.toMap(PreDetection::getDevIP, PreDetection::getDevName));
|
||||
FormalTestManager.devIdMapComm = pqDevList.stream().collect(Collectors.toMap(PreDetection::getDevIP, PreDetection::getDevId));
|
||||
FormalTestManager.devIdMapComm.clear();
|
||||
FormalTestManager.devIdMapComm.putAll(pqDevList.stream().collect(Collectors.toMap(PreDetection::getDevIP, PreDetection::getDevId)));
|
||||
|
||||
//初始化有效数据数
|
||||
//Map<String, SysRegResVO> sysRegResMap = iSysRegResService.listRegRes();
|
||||
@@ -1720,15 +1785,16 @@ public class SocketDevResponseService {
|
||||
} else {
|
||||
dataRule = DictDataEnum.SECTION_VALUE;
|
||||
}
|
||||
FormalTestManager.currentTestPlan = plan;
|
||||
|
||||
String code = dictDataService.getDictDataById(plan.getPattern()).getCode();
|
||||
FormalTestManager.patternEnum = PatternEnum.getEnum(code);
|
||||
//字典树
|
||||
SocketManager.valueTypeMap = iPqScriptCheckDataService.getValueTypeMap(param.getScriptId());
|
||||
|
||||
if (param.getTestItemList().get(1)) {
|
||||
initXiManager(param);
|
||||
}
|
||||
|
||||
harmonicRelationMap.put(DetectionCodeEnum.V2_50.getCode(), DetectionCodeEnum.U1.getCode());
|
||||
harmonicRelationMap.put(DetectionCodeEnum.I2_50.getCode(), DetectionCodeEnum.I1.getCode());
|
||||
}
|
||||
|
||||
//初始化系数校验参数
|
||||
@@ -1793,8 +1859,11 @@ public class SocketDevResponseService {
|
||||
System.out.println("原始数据插入数据库开始执行=========================================");
|
||||
List<SimAndDigNonHarmonicResult> simAndDigNonHarmonicResultList = new ArrayList<>();
|
||||
List<SimAndDigHarmonicResult> adHarmonicResultList = new ArrayList<>();
|
||||
Map<String, String> harmonicRelationMap = new HashMap<>();
|
||||
harmonicRelationMap.put(DetectionCodeEnum.V2_50.getCode(), DetectionCodeEnum.U1.getCode());
|
||||
harmonicRelationMap.put(DetectionCodeEnum.I2_50.getCode(), DetectionCodeEnum.I1.getCode());
|
||||
for (DevData data : devDataList) {
|
||||
LocalDateTime localDateTime = DetectionUtil.timeFormat(data.getTime(), formatter);
|
||||
LocalDateTime localDateTime = DetectionUtil.timeFormat(data.getTime(), DetectionUtil.FORMATTER);
|
||||
if (Objects.nonNull(localDateTime)) {
|
||||
|
||||
String[] splitArr = data.getId().split(CnSocketUtil.SPLIT_TAG);
|
||||
@@ -1802,10 +1871,13 @@ public class SocketDevResponseService {
|
||||
|
||||
if (nonHarmonicList.contains(sourceIssue.getType())) {
|
||||
for (DevData.SqlDataDTO sqlDataDTO : data.getSqlData()) {
|
||||
if (sqlDataDTO.getDesc().equals("PF")) {
|
||||
continue;
|
||||
}
|
||||
DevData.SqlDataDTO.ListDTO listDTO = sqlDataDTO.getList();
|
||||
SimAndDigNonHarmonicResult adNonHarmonicResult = new SimAndDigNonHarmonicResult();
|
||||
adNonHarmonicResult.setTimeId(localDateTime);
|
||||
adNonHarmonicResult.setMonitorId(temId);
|
||||
adNonHarmonicResult.setDevMonitorId(temId);
|
||||
adNonHarmonicResult.setScriptId(param.getScriptId());
|
||||
adNonHarmonicResult.setSort(sourceIssue.getIndex());
|
||||
|
||||
@@ -1838,7 +1910,7 @@ public class SocketDevResponseService {
|
||||
SimAndDigHarmonicResult adHarmonicResult = new SimAndDigHarmonicResult();
|
||||
adHarmonicResult.setTimeId(localDateTime);
|
||||
|
||||
adHarmonicResult.setMonitorId(temId);
|
||||
adHarmonicResult.setDevMonitorId(temId);
|
||||
adHarmonicResult.setScriptId(param.getScriptId());
|
||||
adHarmonicResult.setSort(sourceIssue.getIndex());
|
||||
adHarmonicResult.setAdType(checkDataMap.get(sqlDataDTO.getDesc()));
|
||||
@@ -1911,7 +1983,7 @@ public class SocketDevResponseService {
|
||||
}
|
||||
|
||||
if (CollUtil.isNotEmpty(simAndDigNonHarmonicResultList)) {
|
||||
Map<String, List<SimAndDigNonHarmonicResult>> map = simAndDigNonHarmonicResultList.stream().collect(Collectors.groupingBy(x -> x.getMonitorId() + x.getTimeId() + x.getScriptId() + x.getSort() + x.getAdType() + x.getDataType()));
|
||||
Map<String, List<SimAndDigNonHarmonicResult>> map = simAndDigNonHarmonicResultList.stream().collect(Collectors.groupingBy(x -> x.getDevMonitorId() + x.getTimeId() + x.getScriptId() + x.getSort() + x.getAdType() + x.getDataType()));
|
||||
List<SimAndDigNonHarmonicResult> info = new ArrayList<>();
|
||||
map.forEach((key, value) -> {
|
||||
if (value.size() > 1) {
|
||||
@@ -1921,10 +1993,10 @@ public class SocketDevResponseService {
|
||||
}
|
||||
});
|
||||
|
||||
detectionDataDealService.acceptAdNon(info, param.getCode());
|
||||
detectionDataDealService.acceptNonHarmonic(info, param.getCode());
|
||||
}
|
||||
if (CollUtil.isNotEmpty(adHarmonicResultList)) {
|
||||
Map<String, List<SimAndDigHarmonicResult>> map = adHarmonicResultList.stream().collect(Collectors.groupingBy(x -> x.getMonitorId() + x.getTimeId() + x.getScriptId() + x.getSort() + x.getAdType() + x.getDataType()));
|
||||
Map<String, List<SimAndDigHarmonicResult>> map = adHarmonicResultList.stream().collect(Collectors.groupingBy(x -> x.getDevMonitorId() + x.getTimeId() + x.getScriptId() + x.getSort() + x.getAdType() + x.getDataType()));
|
||||
List<SimAndDigHarmonicResult> info = new ArrayList<>();
|
||||
map.forEach((key, value) -> {
|
||||
if (value.size() > 1) {
|
||||
@@ -1933,7 +2005,7 @@ public class SocketDevResponseService {
|
||||
info.addAll(value);
|
||||
}
|
||||
});
|
||||
detectionDataDealService.acceptAd(info, param.getCode());
|
||||
detectionDataDealService.acceptHarmonic(info, param.getCode());
|
||||
}
|
||||
System.out.println("原始数据插入数据库执行成功=========================================");
|
||||
// };
|
||||
@@ -1958,7 +2030,8 @@ public class SocketDevResponseService {
|
||||
}
|
||||
|
||||
public void backCheckState(PreDetectionParam param) {
|
||||
if (CollUtil.isNotEmpty(param.getDevIds()) && StrUtil.isNotBlank(param.getPlanId()) && "1".equals(param.getOperateType())) {
|
||||
// if (CollUtil.isNotEmpty(param.getDevIds()) && StrUtil.isNotBlank(param.getPlanId()) && "1".equals(param.getOperateType())) {
|
||||
if (CollUtil.isNotEmpty(param.getDevIds()) && StrUtil.isNotBlank(param.getPlanId())) {
|
||||
adPlanService.updateBackTestState(param.getPlanId(), param.getDevIds());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package com.njcn.gather.detection.handler;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.njcn.gather.detection.pojo.enums.SourceOperateCodeEnum;
|
||||
import com.njcn.gather.detection.pojo.enums.SourceResponseCodeEnum;
|
||||
import com.njcn.gather.detection.pojo.param.DevPhaseSequenceParam;
|
||||
@@ -12,130 +12,268 @@ import com.njcn.gather.detection.pojo.vo.SocketDataMsg;
|
||||
import com.njcn.gather.detection.pojo.vo.SocketMsg;
|
||||
import com.njcn.gather.detection.pojo.vo.WebSocketVO;
|
||||
import com.njcn.gather.detection.util.socket.*;
|
||||
import com.njcn.gather.detection.util.socket.cilent.NettyClient;
|
||||
import com.njcn.gather.detection.util.socket.cilent.NettyDevClientHandler;
|
||||
import com.njcn.gather.detection.util.socket.websocket.WebServiceManager;
|
||||
import com.njcn.gather.device.pojo.vo.PreDetection;
|
||||
import com.njcn.gather.device.service.IPqDevService;
|
||||
import com.njcn.gather.plan.pojo.po.AdPlanSource;
|
||||
import com.njcn.gather.plan.service.IAdPlanSourceService;
|
||||
import com.njcn.gather.result.pojo.enums.ResultUnitEnum;
|
||||
import com.njcn.gather.script.pojo.po.SourceIssue;
|
||||
import com.njcn.gather.source.pojo.po.SourceInitialize;
|
||||
import com.njcn.gather.source.service.IPqSourceService;
|
||||
import com.njcn.gather.system.pojo.enums.DicDataEnum;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 程控源Socket响应处理服务
|
||||
* <p>
|
||||
* 该服务类负责处理来自程控源设备的Socket消息响应,包括:
|
||||
* - 源初始化响应处理
|
||||
* - 检测流程控制和状态管理
|
||||
* - 相序检测、系数校验等特定检测类型的响应处理
|
||||
* - WebSocket消息推送给前端用户
|
||||
* - 错误处理和连接管理
|
||||
* </p>
|
||||
* <p>
|
||||
* 主要处理的操作类型:
|
||||
* - YJC_YTXJY: 源通信校验/源初始化
|
||||
* - YJC_XUJY: 相序检测
|
||||
* - FORMAL_REAL: 正式检测
|
||||
* - Coefficient_Check: 系数校验
|
||||
* - QUITE_SOURCE: 退出源连接
|
||||
* </p>
|
||||
*
|
||||
* @author CN_Gather Detection Team
|
||||
* @version 1.0
|
||||
* @since 2023
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class SocketSourceResponseService {
|
||||
|
||||
/**
|
||||
* 向webSocket客户端发送消息
|
||||
* 设备信息服务,提供设备基础信息查询功能
|
||||
*/
|
||||
private final SocketDevResponseService socketDevResponseService;
|
||||
private final IPqDevService iPqDevService;
|
||||
private final IAdPlanSourceService adPlanSourceService;
|
||||
private final IPqSourceService pqSourceService;
|
||||
|
||||
@Value("${socket.device.ip}")
|
||||
private String ip;
|
||||
/**
|
||||
* Socket连接管理器,负责管理设备和源的Socket连接
|
||||
*/
|
||||
private final SocketManager socketManager;
|
||||
|
||||
@Value("${socket.device.port}")
|
||||
private Integer port;
|
||||
/**
|
||||
* 发送WebSocket消息到指定用户页面
|
||||
* <p>
|
||||
* 将数据对象转换为JSON字符串并通过WebSocket推送给前端用户,
|
||||
* 用于实时通知用户检测进度、状态变化或错误信息。
|
||||
* </p>
|
||||
*
|
||||
* @param userPageId 用户页面ID,用于标识消息接收方
|
||||
* @param data 要发送的数据对象,将被转换为JSON格式
|
||||
*/
|
||||
private void sendWebSocketMessage(String userPageId, Object data) {
|
||||
WebServiceManager.sendMsg(userPageId, JSON.toJSONString(data));
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送错误消息并退出源连接
|
||||
* <p>
|
||||
* 当检测过程中发生错误时,执行以下操作:
|
||||
* 1. 主动断开与程控源的连接
|
||||
* 2. 构造包含错误信息的Socket消息
|
||||
* 3. 通过WebSocket将错误信息推送给前端用户
|
||||
* </p>
|
||||
*
|
||||
* @param param 检测参数,包含用户页面ID等信息
|
||||
* @param socketDataMsg 原始Socket消息,用于构造响应消息
|
||||
* @param errorMessage 具体的错误描述信息
|
||||
*/
|
||||
private void sendErrorAndQuit(PreDetectionParam param, SocketDataMsg socketDataMsg, String errorMessage) {
|
||||
CnSocketUtil.quitSendSource(param);
|
||||
SocketMsg<String> socketMsg = new SocketMsg<>();
|
||||
socketMsg.setRequestId(socketDataMsg.getRequestId());
|
||||
socketMsg.setOperateCode(socketDataMsg.getOperateCode());
|
||||
socketMsg.setData(errorMessage);
|
||||
sendWebSocketMessage(param.getUserPageId(), socketMsg);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送错误消息并退出源连接(使用枚举消息)
|
||||
* <p>
|
||||
* 重载方法,使用预定义的错误码枚举来获取标准化的错误消息。
|
||||
* 确保错误信息的一致性和规范性。
|
||||
* </p>
|
||||
*
|
||||
* @param param 检测参数
|
||||
* @param socketDataMsg 原始Socket消息
|
||||
* @param errorCode 错误码枚举,包含标准化的错误描述
|
||||
*/
|
||||
private void sendErrorAndQuit(PreDetectionParam param, SocketDataMsg socketDataMsg, SourceResponseCodeEnum errorCode) {
|
||||
sendErrorAndQuit(param, socketDataMsg, errorCode.getMessage());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 当前检测会话中的设备列表
|
||||
* <p>
|
||||
* 存储正在进行检测的设备信息,包含设备基本信息和监测点配置。
|
||||
* 注意:该字段存在线程安全问题,建议后续重构为线程安全的设计。
|
||||
* </p>
|
||||
*/
|
||||
private List<PreDetection> devList = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 当前检测会话中的监测点ID列表
|
||||
* <p>
|
||||
* 从设备列表中提取的所有监测点ID集合,用于向设备发送数据请求时指定监测范围。
|
||||
* 与devList字段保持同步更新。
|
||||
* </p>
|
||||
*/
|
||||
private List<String> monitorIdList = new ArrayList<>();
|
||||
|
||||
|
||||
/**
|
||||
* 程控源响应消息处理主入口
|
||||
* <p>
|
||||
* 根据消息中的操作码,分发到相应的处理方法:
|
||||
* - 解析Socket消息,提取操作码
|
||||
* - 根据操作码类型调用对应的处理方法
|
||||
* - 支持检测计划模式和模拟测试模式的区分处理
|
||||
* </p>
|
||||
* <p>
|
||||
* 支持的操作类型:
|
||||
* - YJC_YTXJY: 源通信校验/源初始化
|
||||
* - YJC_XUJY: 相序检测
|
||||
* - FORMAL_REAL: 正式检测
|
||||
* - Coefficient_Check: 系数校验
|
||||
* - QUITE_SOURCE: 退出源连接
|
||||
* </p>
|
||||
*
|
||||
* @param param 检测参数,包含用户ID、设备ID、计划ID等关键信息
|
||||
* @param msg 从程控源接收的原始Socket消息
|
||||
* @throws Exception 当消息解析失败或处理过程中发生异常时抛出
|
||||
*/
|
||||
public void deal(PreDetectionParam param, String msg) throws Exception {
|
||||
// 解析接收到的Socket消息
|
||||
SocketDataMsg socketDataMsg = MsgUtil.socketDataMsg(msg);
|
||||
|
||||
// 从requestId中提取操作码,requestId格式为:操作码_步骤标识
|
||||
String[] tem = socketDataMsg.getRequestId().split(CnSocketUtil.STEP_TAG);
|
||||
SourceOperateCodeEnum enumByCode = SourceOperateCodeEnum.getDictDataEnumByCode(tem[0]);
|
||||
|
||||
if (ObjectUtil.isNotNull(enumByCode)) {
|
||||
switch (enumByCode) {
|
||||
//源初始化
|
||||
// 源初始化处理:根据是否有计划ID判断是正式检测还是模拟检测
|
||||
case YJC_YTXJY:
|
||||
if (ObjectUtil.isNotNull(param.getPlanId())) {
|
||||
// 有计划ID:正式检测模式,源初始化成功后启动设备检测
|
||||
detectionDev(param, socketDataMsg);
|
||||
} else {
|
||||
// 程控源-源通信校验
|
||||
// 无计划ID:模拟检测模式,仅进行源通信校验
|
||||
handleYtxjySimulate(param, socketDataMsg);
|
||||
}
|
||||
break;
|
||||
//相序检测
|
||||
|
||||
// 相序检测:检测设备的相序是否正确
|
||||
case YJC_XUJY:
|
||||
phaseSequenceDev(param, socketDataMsg);
|
||||
break;
|
||||
//正式检测
|
||||
|
||||
// 正式检测:根据是否有计划ID选择不同的处理方式
|
||||
case FORMAL_REAL:
|
||||
if (ObjectUtil.isNotNull(param.getPlanId())) {
|
||||
// 有计划ID:向设备发送检测参数
|
||||
senParamToDev(param, socketDataMsg);
|
||||
} else {
|
||||
// 无计划ID:模拟测试模式
|
||||
handleSimulateTest(param, socketDataMsg);
|
||||
}
|
||||
break;
|
||||
//系数校验
|
||||
|
||||
// 系数校验:验证设备的计量系数是否准确
|
||||
case Coefficient_Check:
|
||||
coefficient(param, socketDataMsg);
|
||||
break;
|
||||
|
||||
// 退出源连接:清理资源并关闭连接
|
||||
case QUITE_SOURCE:
|
||||
quitDeal(socketDataMsg, param);
|
||||
break;
|
||||
|
||||
// YXT操作:暂未实现具体功能
|
||||
case YXT:
|
||||
// TODO: 实现YXT操作的具体逻辑
|
||||
break;
|
||||
|
||||
default:
|
||||
// TODO: 记录未知操作码到日志,并向前端发送友好提示
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
System.out.println("fggggggggggggggggggggg" + enumByCode);
|
||||
// TODO: 向前端发送错误提示
|
||||
log.error("程控源响应消息操作码解析失败,原始消息: {}, 解析结果: {}", msg, enumByCode);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理模拟检测中的源通信校验响应
|
||||
* <p>
|
||||
* 在模拟检测模式下(非计划检测),处理程控源的通信校验响应:
|
||||
* - 成功时:根据参数决定是否向前端发送WebSocket消息
|
||||
* - 业务未处理:直接转发消息给前端
|
||||
* - 各种错误情况:统一处理为退出源连接并通知前端
|
||||
* </p>
|
||||
* <p>
|
||||
* 支持的错误类型包括:
|
||||
* - 源连接错误、程控源错误、测试项解析错误
|
||||
* - 源控制错误、目标源错误、未初始化错误
|
||||
* - 未知错误、无法响应错误
|
||||
* </p>
|
||||
*
|
||||
* @param param 检测参数,包含用户信息和WebSocket消息发送控制
|
||||
* @param socketDataMsg 程控源返回的响应消息
|
||||
*/
|
||||
private void handleYtxjySimulate(PreDetectionParam param, SocketDataMsg socketDataMsg) {
|
||||
|
||||
SourceResponseCodeEnum dictDataEnumByCode = SourceResponseCodeEnum.getDictDataEnumByCode(socketDataMsg.getCode());
|
||||
if (ObjectUtil.isNotNull(dictDataEnumByCode)) {
|
||||
switch (dictDataEnumByCode) {
|
||||
case SUCCESS:
|
||||
// 源初始化成功:根据参数控制是否发送WebSocket消息
|
||||
if (param.getSendWebMsg()) {
|
||||
WebServiceManager.sendMsg(param.getUserPageId(), JSON.toJSONString(socketDataMsg));
|
||||
sendWebSocketMessage(param.getUserPageId(), socketDataMsg);
|
||||
}
|
||||
System.out.println(param.getSendWebMsg() + "模拟检测-源初始化成功");
|
||||
log.info("模拟检测源初始化成功,用户: {}, WebSocket发送: {}",
|
||||
param.getUserPageId(), param.getSendWebMsg());
|
||||
break;
|
||||
case UNPROCESSED_BUSINESS:
|
||||
WebServiceManager.sendMsg(param.getUserPageId(), JSON.toJSONString(socketDataMsg));
|
||||
// 业务暂未处理:直接转发消息给前端等待处理
|
||||
sendWebSocketMessage(param.getUserPageId(), socketDataMsg);
|
||||
break;
|
||||
// 各种错误情况:源连接错误、程控源控制错误、测试项解析错误等
|
||||
case SOURCE_CONNECTION_ERROR:
|
||||
CnSocketUtil.quitSendSource(param);
|
||||
WebServiceManager.sendMsg(param.getUserPageId(), JSON.toJSONString(socketDataMsg));
|
||||
break;
|
||||
case CONTROLLED_SOURCE_ERROR:
|
||||
CnSocketUtil.quitSendSource(param);
|
||||
WebServiceManager.sendMsg(param.getUserPageId(), JSON.toJSONString(socketDataMsg));
|
||||
break;
|
||||
case TEST_ITEM_PARSING_ERROR:
|
||||
CnSocketUtil.quitSendSource(param);
|
||||
WebServiceManager.sendMsg(param.getUserPageId(), JSON.toJSONString(socketDataMsg));
|
||||
break;
|
||||
case SOURCE_CONTROL_ERROR:
|
||||
CnSocketUtil.quitSendSource(param);
|
||||
WebServiceManager.sendMsg(param.getUserPageId(), JSON.toJSONString(socketDataMsg));
|
||||
break;
|
||||
case TARGET_SOURCE_ERROR:
|
||||
CnSocketUtil.quitSendSource(param);
|
||||
WebServiceManager.sendMsg(param.getUserPageId(), JSON.toJSONString(socketDataMsg));
|
||||
break;
|
||||
case NOT_INITIALIZED:
|
||||
CnSocketUtil.quitSendSource(param);
|
||||
WebServiceManager.sendMsg(param.getUserPageId(), JSON.toJSONString(socketDataMsg));
|
||||
break;
|
||||
case UNKNOWN_ERROR:
|
||||
CnSocketUtil.quitSendSource(param);
|
||||
WebServiceManager.sendMsg(param.getUserPageId(), JSON.toJSONString(socketDataMsg));
|
||||
break;
|
||||
case UNABLE_TO_RESPOND:
|
||||
// 所有错误情况统一处理:退出源连接并通知前端
|
||||
CnSocketUtil.quitSendSource(param);
|
||||
WebServiceManager.sendMsg(param.getUserPageId(), JSON.toJSONString(socketDataMsg));
|
||||
sendWebSocketMessage(param.getUserPageId(), socketDataMsg);
|
||||
break;
|
||||
default:
|
||||
CnSocketUtil.sendUnSocket(param.getUserPageId());
|
||||
// 未识别的响应码:发送通用错误消息
|
||||
WebServiceManager.sendUnknownErrorMessage(param.getUserPageId());
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -152,19 +290,15 @@ public class SocketSourceResponseService {
|
||||
if (ObjectUtil.isNotNull(dictDataEnumByCode)) {
|
||||
switch (dictDataEnumByCode) {
|
||||
case SUCCESS:
|
||||
WebServiceManager.sendMsg(param.getUserPageId(), JSON.toJSONString(socketDataMsg));
|
||||
System.out.println("模拟检测-源成功执行脚本" + JSON.toJSONString(socketDataMsg));
|
||||
sendWebSocketMessage(param.getUserPageId(), socketDataMsg);
|
||||
log.info("模拟检测源成功执行脚本,用户: {}, 响应消息: {}",
|
||||
param.getUserPageId(), JSON.toJSONString(socketDataMsg));
|
||||
break;
|
||||
case UNPROCESSED_BUSINESS:
|
||||
WebServiceManager.sendMsg(param.getUserPageId(), JSON.toJSONString(socketDataMsg));
|
||||
sendWebSocketMessage(param.getUserPageId(), socketDataMsg);
|
||||
break;
|
||||
default:
|
||||
CnSocketUtil.quitSendSource(param);
|
||||
SocketMsg<String> socketMsg = new SocketMsg<>();
|
||||
socketMsg.setRequestId(socketDataMsg.getRequestId());
|
||||
socketMsg.setOperateCode(socketDataMsg.getOperateCode());
|
||||
socketMsg.setData(dictDataEnumByCode.getMessage());
|
||||
WebServiceManager.sendMsg(param.getUserPageId(), JSON.toJSONString(socketMsg));
|
||||
sendErrorAndQuit(param, socketDataMsg, dictDataEnumByCode);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -173,6 +307,23 @@ public class SocketSourceResponseService {
|
||||
|
||||
/**
|
||||
* 系数校验源数据返回处理
|
||||
* <p>
|
||||
* 处理系数校验阶段程控源的响应消息:
|
||||
* 1. 成功时:向前端推送响应信息,然后向设备发送数据请求
|
||||
* 2. 构造设备数据请求参数,包含监测点列表和数据类型
|
||||
* 3. 设置固定的读取参数:读取3次数据,忽略前4次
|
||||
* 4. 请求的数据类型:实时电压有效值(real$VRMS)和实时电流有效值(real$IRMS)
|
||||
* </p>
|
||||
* <p>
|
||||
* 数据请求配置:
|
||||
* - 监测点:使用当前会话的monitorIdList
|
||||
* - 数据类型:["real$VRMS", "real$IRMS"]
|
||||
* - 读取次数:3次
|
||||
* - 忽略次数:4次(预热数据)
|
||||
* </p>
|
||||
*
|
||||
* @param param 检测参数
|
||||
* @param socketDataMsg 程控源响应消息
|
||||
*/
|
||||
private void coefficient(PreDetectionParam param, SocketDataMsg socketDataMsg) {
|
||||
SourceResponseCodeEnum dictDataEnumByCode = SourceResponseCodeEnum.getDictDataEnumByCode(socketDataMsg.getCode());
|
||||
@@ -181,29 +332,28 @@ public class SocketSourceResponseService {
|
||||
switch (dictDataEnumByCode) {
|
||||
case SUCCESS:
|
||||
//向前端推送信息
|
||||
WebServiceManager.sendMsg(param.getUserPageId(), JSON.toJSONString(socketDataMsg));
|
||||
sendWebSocketMessage(param.getUserPageId(), socketDataMsg);
|
||||
|
||||
String s = param.getUserPageId() + CnSocketUtil.DEV_TAG;
|
||||
socketMsg.setRequestId(SourceOperateCodeEnum.Coefficient_Check.getValue());
|
||||
socketMsg.setOperateCode(SourceOperateCodeEnum.DEV_DATA_REQUEST_02.getValue());
|
||||
DevPhaseSequenceParam phaseSequenceParam = new DevPhaseSequenceParam();
|
||||
phaseSequenceParam.setMoniterIdList(monitorIdList);
|
||||
// 系数校验固定检测项:实时电压有效值和实时电流有效值
|
||||
phaseSequenceParam.setDataType(Arrays.asList("real$VRMS", "real$IRMS"));
|
||||
phaseSequenceParam.setReadCount(3);
|
||||
phaseSequenceParam.setIgnoreCount(4);
|
||||
// 读取3次数据用于系数计算
|
||||
phaseSequenceParam.setReadCount(7); //3
|
||||
// 忽略前4次数据,等待测量稳定
|
||||
phaseSequenceParam.setIgnoreCount(3); //4
|
||||
socketMsg.setData(JSON.toJSONString(phaseSequenceParam));
|
||||
SocketManager.sendMsg(s, JSON.toJSONString(socketMsg));
|
||||
|
||||
break;
|
||||
case UNPROCESSED_BUSINESS:
|
||||
WebServiceManager.sendMsg(param.getUserPageId(), JSON.toJSONString(socketDataMsg));
|
||||
sendWebSocketMessage(param.getUserPageId(), socketDataMsg);
|
||||
break;
|
||||
default:
|
||||
CnSocketUtil.quitSendSource(param);
|
||||
socketMsg.setRequestId(socketDataMsg.getRequestId());
|
||||
socketMsg.setOperateCode(socketDataMsg.getOperateCode());
|
||||
socketMsg.setData(dictDataEnumByCode.getMessage());
|
||||
WebServiceManager.sendMsg(param.getUserPageId(), JSON.toJSONString(socketMsg));
|
||||
sendErrorAndQuit(param, socketDataMsg, dictDataEnumByCode);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -219,63 +369,56 @@ public class SocketSourceResponseService {
|
||||
SourceResponseCodeEnum dictDataEnumByCode = SourceResponseCodeEnum.getDictDataEnumByCode(socketDataMsg.getCode());
|
||||
if (ObjectUtil.isNotNull(dictDataEnumByCode)) {
|
||||
SocketMsg<String> socketMsg = new SocketMsg<>();
|
||||
|
||||
switch (dictDataEnumByCode) {
|
||||
case SUCCESS:
|
||||
//todo 前端推送收到的消息暂未处理好
|
||||
WebServiceManager.sendMsg(param.getUserPageId(), JSON.toJSONString(socketDataMsg));
|
||||
String s = param.getUserPageId() + CnSocketUtil.DEV_TAG;
|
||||
//开始设备通讯检测(发送设备初始化)
|
||||
//List<PreDetection> devList = iPqDevService.getDevInfo(param.getDevIds());
|
||||
Map<String, List<PreDetection>> map = new HashMap<>(1);
|
||||
map.put("deviceList", FormalTestManager.devList);
|
||||
String jsonString = JSON.toJSONString(map);
|
||||
socketMsg.setRequestId(SourceOperateCodeEnum.YJC_SBTXJY.getValue());
|
||||
socketMsg.setOperateCode(SourceOperateCodeEnum.DEV_INIT_GATHER_01.getValue());
|
||||
socketMsg.setData(jsonString);
|
||||
String json = JSON.toJSONString(socketMsg);
|
||||
// SocketManager.sendMsg(s,json);
|
||||
NettyClient.socketClient(ip, port, param, json, new NettyDevClientHandler(param, socketDevResponseService));
|
||||
if (FormalTestManager.unknownError) {
|
||||
FormalTestManager.unknownError = false;
|
||||
|
||||
//重新下发脚本
|
||||
String type = FormalTestManager.currentIssue.getType();
|
||||
if (ResultUnitEnum.P.getCode().equals(type)) {
|
||||
FormalTestManager.currentIssue.setType(ResultUnitEnum.V_ABSOLUTELY.getCode());
|
||||
}
|
||||
socketMsg.setOperateCode(SourceOperateCodeEnum.OPER_GATHER.getValue());
|
||||
socketMsg.setData(JSON.toJSONString(FormalTestManager.currentIssue));
|
||||
socketMsg.setRequestId(SourceOperateCodeEnum.FORMAL_REAL.getValue() + CnSocketUtil.STEP_TAG + type);
|
||||
SocketManager.sendMsg(param.getUserPageId() + CnSocketUtil.SOURCE_TAG, JSON.toJSONString(socketMsg));
|
||||
} else {
|
||||
//todo 前端推送收到的消息暂未处理好
|
||||
sendWebSocketMessage(param.getUserPageId(), socketDataMsg);
|
||||
//开始设备通讯检测(发送设备初始化)
|
||||
Map<String, List<PreDetection>> map = new HashMap<>(1);
|
||||
map.put("deviceList", FormalTestManager.devList);
|
||||
String jsonString = JSON.toJSONString(map);
|
||||
socketMsg.setRequestId(SourceOperateCodeEnum.YJC_SBTXJY.getValue());
|
||||
socketMsg.setOperateCode(SourceOperateCodeEnum.DEV_INIT_GATHER_01.getValue());
|
||||
socketMsg.setData(jsonString);
|
||||
String json = JSON.toJSONString(socketMsg);
|
||||
// 使用智能发送工具类,自动管理设备连接
|
||||
socketManager.smartSendToDevice(param, json);
|
||||
}
|
||||
break;
|
||||
case UNPROCESSED_BUSINESS:
|
||||
WebServiceManager.sendMsg(param.getUserPageId(), JSON.toJSONString(socketDataMsg));
|
||||
sendWebSocketMessage(param.getUserPageId(), socketDataMsg);
|
||||
break;
|
||||
case SOURCE_CONNECTION_ERROR:
|
||||
CnSocketUtil.quitSendSource(param);
|
||||
WebServiceManager.sendMsg(param.getUserPageId(), JSON.toJSONString(socketDataMsg));
|
||||
break;
|
||||
case CONTROLLED_SOURCE_ERROR:
|
||||
CnSocketUtil.quitSendSource(param);
|
||||
WebServiceManager.sendMsg(param.getUserPageId(), JSON.toJSONString(socketDataMsg));
|
||||
break;
|
||||
case TEST_ITEM_PARSING_ERROR:
|
||||
CnSocketUtil.quitSendSource(param);
|
||||
WebServiceManager.sendMsg(param.getUserPageId(), JSON.toJSONString(socketDataMsg));
|
||||
break;
|
||||
case SOURCE_CONTROL_ERROR:
|
||||
CnSocketUtil.quitSendSource(param);
|
||||
WebServiceManager.sendMsg(param.getUserPageId(), JSON.toJSONString(socketDataMsg));
|
||||
break;
|
||||
case TARGET_SOURCE_ERROR:
|
||||
CnSocketUtil.quitSendSource(param);
|
||||
WebServiceManager.sendMsg(param.getUserPageId(), JSON.toJSONString(socketDataMsg));
|
||||
break;
|
||||
case NOT_INITIALIZED:
|
||||
CnSocketUtil.quitSendSource(param);
|
||||
WebServiceManager.sendMsg(param.getUserPageId(), JSON.toJSONString(socketDataMsg));
|
||||
break;
|
||||
case UNKNOWN_ERROR:
|
||||
CnSocketUtil.quitSendSource(param);
|
||||
WebServiceManager.sendMsg(param.getUserPageId(), JSON.toJSONString(socketDataMsg));
|
||||
break;
|
||||
case UNABLE_TO_RESPOND:
|
||||
CnSocketUtil.quitSendSource(param);
|
||||
WebServiceManager.sendMsg(param.getUserPageId(), JSON.toJSONString(socketDataMsg));
|
||||
sendWebSocketMessage(param.getUserPageId(), socketDataMsg);
|
||||
break;
|
||||
default:
|
||||
CnSocketUtil.sendUnSocket(param.getUserPageId());
|
||||
// todo... 这种情况是报文的状态码不一致,需要记录到日志表,以便问题追踪
|
||||
WebServiceManager.sendUnknownErrorMessage(param.getUserPageId());
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// todo... 这种情况是报文的状态码不一致,需要记录到日志表,以便问题追踪
|
||||
}
|
||||
}
|
||||
|
||||
@@ -292,7 +435,7 @@ public class SocketSourceResponseService {
|
||||
switch (dictDataEnumByCode) {
|
||||
case SUCCESS:
|
||||
//向前端推送信息
|
||||
WebServiceManager.sendMsg(param.getUserPageId(), JSON.toJSONString(socketDataMsg));
|
||||
sendWebSocketMessage(param.getUserPageId(), socketDataMsg);
|
||||
|
||||
String s = param.getUserPageId() + CnSocketUtil.DEV_TAG;
|
||||
socketMsg.setRequestId(SourceOperateCodeEnum.YJC_XUJY.getValue());
|
||||
@@ -304,24 +447,23 @@ public class SocketSourceResponseService {
|
||||
|
||||
DevPhaseSequenceParam phaseSequenceParam = new DevPhaseSequenceParam();
|
||||
phaseSequenceParam.setMoniterIdList(moniterIdList);
|
||||
// 相序检测项:电压有效值、电压角度、电流有效值、电流角度
|
||||
phaseSequenceParam.setDataType(Arrays.asList("real$VRMS", "real$VA", "real$IRMS", "real$IA"));
|
||||
// 相序检测只需要读取1次数据
|
||||
phaseSequenceParam.setReadCount(1);
|
||||
// 忽略前10次数据,确保相序稳定
|
||||
phaseSequenceParam.setIgnoreCount(10);
|
||||
socketMsg.setData(JSON.toJSONString(phaseSequenceParam));
|
||||
SocketManager.sendMsg(s, JSON.toJSONString(socketMsg));
|
||||
break;
|
||||
case UNPROCESSED_BUSINESS:
|
||||
WebServiceManager.sendMsg(param.getUserPageId(), JSON.toJSONString(socketDataMsg));
|
||||
sendWebSocketMessage(param.getUserPageId(), socketDataMsg);
|
||||
break;
|
||||
case MESSAGE_PARSING_ERROR:
|
||||
CnSocketUtil.quitSendSource(param);
|
||||
break;
|
||||
default:
|
||||
CnSocketUtil.quitSendSource(param);
|
||||
socketMsg.setRequestId(socketDataMsg.getRequestId());
|
||||
socketMsg.setOperateCode(socketDataMsg.getOperateCode());
|
||||
socketMsg.setData(dictDataEnumByCode.getMessage());
|
||||
WebServiceManager.sendMsg(param.getUserPageId(), JSON.toJSONString(socketMsg));
|
||||
sendErrorAndQuit(param, socketDataMsg, dictDataEnumByCode);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -329,10 +471,28 @@ public class SocketSourceResponseService {
|
||||
|
||||
|
||||
/**
|
||||
* 组装和装置要数据
|
||||
* 正式检测时向设备发送参数请求
|
||||
* <p>
|
||||
* 当程控源成功执行脚本后,根据检测项目类型向设备发送相应的数据请求:
|
||||
* 1. 获取源脚本信息,确定检测类型和数据类型
|
||||
* 2. 根据检测类型设置不同的读取参数:
|
||||
* - 闪变(F):忽略1次,读取2次,使用DEV_DATA_REQUEST_01
|
||||
* - 暂态(VOLTAGE):忽略5次,读取1次,使用DEV_DATA_REQUEST_03
|
||||
* - 其他类型:忽略5次,读取5次,根据数据类型选择操作码
|
||||
* 3. 构造设备数据请求并发送
|
||||
* 4. 向前端推送检测开始信息
|
||||
* </p>
|
||||
* <p>
|
||||
* 检测参数配置:
|
||||
* - 闪变检测:ignoreCount=1, readCount=2
|
||||
* - 暂态检测:ignoreCount=5, readCount=1
|
||||
* - 常规检测:ignoreCount=5, readCount=5
|
||||
* - 实时数据:使用DEV_DATA_REQUEST_02
|
||||
* - 分钟数据:使用DEV_DATA_REQUEST_01
|
||||
* </p>
|
||||
*
|
||||
* @param param
|
||||
* @param socketDataMsg
|
||||
* @param param 检测参数,包含用户和设备信息
|
||||
* @param socketDataMsg 程控源成功响应消息
|
||||
*/
|
||||
private void senParamToDev(PreDetectionParam param, SocketDataMsg socketDataMsg) {
|
||||
SourceResponseCodeEnum dictDataEnumByCode = SourceResponseCodeEnum.getDictDataEnumByCode(socketDataMsg.getCode());
|
||||
@@ -343,42 +503,74 @@ public class SocketSourceResponseService {
|
||||
//向前端推送信息
|
||||
// webSocketHandler.sendMsgToUser(param.getUserPageId(), JSON.toJSONString(socketDataMsg));
|
||||
|
||||
// 构造设备通道标识:用户ID + 设备标签
|
||||
String s = param.getUserPageId() + CnSocketUtil.DEV_TAG;
|
||||
|
||||
// 获取当前检测的源脚本信息,包含检测类型和数据要求
|
||||
SourceIssue sourceIssue = SocketManager.getSourceList().get(0);
|
||||
List<String> comm = sourceIssue.getDevValueTypeList(); //形如:类型&小项code这种形式。例如:real$VRMS、real$IRMS
|
||||
System.out.println("向装置下发的参数>>>>>>>>" + comm);
|
||||
socketMsg.setRequestId(SourceOperateCodeEnum.FORMAL_REAL.getValue() + CnSocketUtil.STEP_TAG + sourceIssue.getType());
|
||||
// 数据类型列表,格式:real$VRMS、real$IRMS
|
||||
List<String> comm = sourceIssue.getDevValueTypeList();
|
||||
log.debug("向设备下发检测参数,用户: {}, 数据类型: {}", param.getUserPageId(), comm);
|
||||
|
||||
// 设置请求ID:正式检测操作码 + 步骤标识 + 检测类型
|
||||
socketMsg.setRequestId(socketDataMsg.getRequestId());
|
||||
|
||||
// 根据检测类型设置不同的读取参数和操作码
|
||||
int ignoreCount;
|
||||
int readData;
|
||||
|
||||
if (DicDataEnum.F.getCode().equals(sourceIssue.getType())) {
|
||||
// 闪变检测:数据变化较慢,只需少量预热和读取
|
||||
// 闪变测量稳定性好,预热1次即可
|
||||
ignoreCount = 1;
|
||||
// 读取2次数据计算闪变值
|
||||
readData = 2;
|
||||
socketMsg.setOperateCode(SourceOperateCodeEnum.DEV_DATA_REQUEST_01.getValue());
|
||||
} else if (DicDataEnum.VOLTAGE.getCode().equals(sourceIssue.getType())) {
|
||||
// 暂态电压检测:需要更多预热时间,但只读取一次快照
|
||||
// 暂态事件需要5次预热确保触发稳定
|
||||
ignoreCount = 5;
|
||||
// 暂态检测只需要捕获一次事件
|
||||
readData = 1;
|
||||
socketMsg.setOperateCode(SourceOperateCodeEnum.DEV_DATA_REQUEST_03.getValue());
|
||||
} else {
|
||||
// 常规检测(谐波、不平衡度等):需要多次采样以提高精度
|
||||
// 常规检测预热5次等待稳定
|
||||
ignoreCount = 5;
|
||||
// 读取5次数据进行统计分析
|
||||
readData = 5;
|
||||
//区分实时数据还是分钟数据
|
||||
|
||||
// 根据数据类型选择相应的请求操作码
|
||||
if ("real".equals(sourceIssue.getDataType())) {
|
||||
// 实时数据:瞬时值或有效值
|
||||
socketMsg.setOperateCode(SourceOperateCodeEnum.DEV_DATA_REQUEST_02.getValue());
|
||||
} else {
|
||||
// 分钟数据:统计周期内的平均值或累计值
|
||||
socketMsg.setOperateCode(SourceOperateCodeEnum.DEV_DATA_REQUEST_01.getValue());
|
||||
}
|
||||
}
|
||||
System.out.println("devList is empty:" + CollectionUtils.isEmpty(devList));
|
||||
log.debug("检测设备列表状态检查,用户: {}, 设备列表为空: {}",
|
||||
param.getUserPageId(), CollectionUtils.isEmpty(devList));
|
||||
|
||||
//List<String> moniterIdList = devList.stream().flatMap(x -> x.getMonitorList().stream()).map(PreDetection.MonitorListDTO::getLineId).collect(Collectors.toList());
|
||||
// 构造设备数据请求参数
|
||||
DevPhaseSequenceParam phaseSequenceParam = new DevPhaseSequenceParam();
|
||||
// 设置监测点ID列表
|
||||
phaseSequenceParam.setMoniterIdList(monitorIdList);
|
||||
if (socketDataMsg.getRequestId().equals(SourceOperateCodeEnum.FORMAL_REAL.getValue() + CnSocketUtil.STEP_TAG + "P")) {
|
||||
comm.add("real$PF");
|
||||
}
|
||||
// 设置数据类型列表
|
||||
phaseSequenceParam.setDataType(comm);
|
||||
// 设置读取次数
|
||||
phaseSequenceParam.setReadCount(readData);
|
||||
// 设置忽略次数
|
||||
phaseSequenceParam.setIgnoreCount(ignoreCount);
|
||||
socketMsg.setData(JSON.toJSONString(phaseSequenceParam));
|
||||
|
||||
// 向设备发送数据请求
|
||||
SocketManager.sendMsg(s, JSON.toJSONString(socketMsg));
|
||||
|
||||
// 构造前端显示的设备列表,只包含设备ID和名称
|
||||
List<DevLineTestResult> devListRes = new ArrayList<>();
|
||||
devList.forEach(item -> {
|
||||
DevLineTestResult devLineTestResult = new DevLineTestResult();
|
||||
@@ -387,21 +579,25 @@ public class SocketSourceResponseService {
|
||||
devListRes.add(devLineTestResult);
|
||||
});
|
||||
|
||||
// 构造WebSocket消息并推送给前端,通知检测开始
|
||||
WebSocketVO<Object> webSocketVO = new WebSocketVO<>();
|
||||
// 设置请求ID:检测类型 + 开始标识
|
||||
webSocketVO.setRequestId(socketDataMsg.getRequestId().split(CnSocketUtil.STEP_TAG)[1] + CnSocketUtil.START_TAG);
|
||||
// 检测描述信息
|
||||
webSocketVO.setDesc(SocketManager.getSourceList().get(0).getDesc());
|
||||
// 参与检测的设备列表
|
||||
webSocketVO.setData(devListRes);
|
||||
WebServiceManager.sendMsg(param.getUserPageId(), JSON.toJSONString(webSocketVO));
|
||||
sendWebSocketMessage(param.getUserPageId(), webSocketVO);
|
||||
break;
|
||||
case UNPROCESSED_BUSINESS:
|
||||
WebServiceManager.sendMsg(param.getUserPageId(), JSON.toJSONString(socketDataMsg));
|
||||
sendWebSocketMessage(param.getUserPageId(), socketDataMsg);
|
||||
break;
|
||||
case UNKNOWN_ERROR: //-1源未知异常
|
||||
CnSocketUtil.quitSendSource(param);
|
||||
FormalTestManager.unknownError = true;
|
||||
break;
|
||||
default:
|
||||
CnSocketUtil.quitSendSource(param);
|
||||
socketMsg.setRequestId(socketDataMsg.getRequestId());
|
||||
socketMsg.setOperateCode(socketDataMsg.getOperateCode());
|
||||
socketMsg.setData(dictDataEnumByCode.getMessage());
|
||||
WebServiceManager.sendMsg(param.getUserPageId(), JSON.toJSONString(socketMsg));
|
||||
sendErrorAndQuit(param, socketDataMsg, dictDataEnumByCode);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -409,15 +605,45 @@ public class SocketSourceResponseService {
|
||||
|
||||
|
||||
/**
|
||||
* 退出检测返回
|
||||
* 处理退出检测的响应
|
||||
* <p>
|
||||
* 当用户主动退出检测或系统需要终止检测时,处理程控源的退出响应:
|
||||
* - 成功退出:移除Socket连接管理中的用户信息,向前端发送成功消息
|
||||
* - 业务未处理:不做特殊处理
|
||||
* - 消息解析错误/无法响应:移除用户连接信息
|
||||
* - 其他错误:调用退出源连接方法
|
||||
* </p>
|
||||
* <p>
|
||||
* 退出流程:
|
||||
* 1. 解析响应状态码
|
||||
* 2. 根据状态码执行相应的清理操作
|
||||
* 3. 确保Socket连接资源得到正确释放
|
||||
* </p>
|
||||
*
|
||||
* @param socketDataMsg 程控源退出响应消息
|
||||
* @param param 检测参数,包含用户信息
|
||||
*/
|
||||
private void quitDeal(SocketDataMsg socketDataMsg, PreDetectionParam param) {
|
||||
SourceResponseCodeEnum dictDataEnumByCode = SourceResponseCodeEnum.getDictDataEnumByCode(socketDataMsg.getCode());
|
||||
switch (Objects.requireNonNull(dictDataEnumByCode)) {
|
||||
case SUCCESS:
|
||||
//通讯校验成功
|
||||
SocketManager.removeUser(param.getUserPageId() + CnSocketUtil.SOURCE_TAG);
|
||||
WebServiceManager.sendMsg(param.getUserPageId(), JSON.toJSONString(socketDataMsg));
|
||||
if (FormalTestManager.unknownError) {
|
||||
//获取源初始化参数
|
||||
AdPlanSource planSource = adPlanSourceService.getOne(new LambdaQueryWrapper<AdPlanSource>().eq(AdPlanSource::getPlanId, param.getPlanId()));
|
||||
SourceInitialize sourceParam = pqSourceService.getSourceInitializeParam(planSource.getSourceId());
|
||||
if (ObjectUtil.isNotNull(sourceParam)) {
|
||||
SocketMsg<String> socketMsg1 = new SocketMsg<>();
|
||||
socketMsg1.setRequestId(SourceOperateCodeEnum.YJC_YTXJY.getValue());
|
||||
socketMsg1.setOperateCode(SourceOperateCodeEnum.INIT_GATHER.getValue());
|
||||
socketMsg1.setData(JSON.toJSONString(sourceParam));
|
||||
//使用智能发送工具类,自动管理与源控程序的socket连接
|
||||
socketManager.smartSendToSource(param, JSON.toJSONString(socketMsg1));
|
||||
}
|
||||
} else {
|
||||
//通讯校验成功
|
||||
SocketManager.removeUser(param.getUserPageId() + CnSocketUtil.SOURCE_TAG);
|
||||
sendWebSocketMessage(param.getUserPageId(), socketDataMsg);
|
||||
}
|
||||
break;
|
||||
case UNPROCESSED_BUSINESS:
|
||||
break;
|
||||
@@ -435,14 +661,40 @@ public class SocketSourceResponseService {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 初始化检测设备和监测点列表
|
||||
* <p>
|
||||
* 在开始检测前,根据检测参数初始化当前会话的设备信息:
|
||||
* 1. 清空之前的设备列表和监测点列表
|
||||
* 2. 根据设备ID列表查询设备详细信息
|
||||
* 3. 从设备信息中提取所有监测点的线路ID
|
||||
* 4. 同步更新XiNumberManager中的设备列表
|
||||
* </p>
|
||||
* <p>
|
||||
* 该方法通常在检测开始前调用,确保后续的检测流程能够获取到正确的
|
||||
* 设备配置和监测点信息。
|
||||
* </p>
|
||||
*
|
||||
* @param param 检测参数,包含要检测的设备ID列表
|
||||
*/
|
||||
public void initList(PreDetectionParam param) {
|
||||
// 清空现有列表,为新的检测会话做准备
|
||||
devList.clear();
|
||||
monitorIdList.clear();
|
||||
|
||||
// 查询设备详细信息,包含监测点配置
|
||||
this.devList = iPqDevService.getDevInfo(param.getDevIds());
|
||||
this.monitorIdList = devList.stream().flatMap(x -> x.getMonitorList().stream())
|
||||
|
||||
// 提取所有设备的监测点线路ID
|
||||
this.monitorIdList = devList.stream()
|
||||
.flatMap(x -> x.getMonitorList().stream())
|
||||
.map(PreDetection.MonitorListDTO::getLineId)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 同步更新系数管理器中的设备列表
|
||||
XiNumberManager.xiDevList = devList;
|
||||
FormalTestManager.unknownError = false;
|
||||
FormalTestManager.currentIssue = null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.njcn.gather.detection.mapper;
|
||||
|
||||
import com.github.yulichang.base.MPJBaseMapper;
|
||||
import com.njcn.gather.detection.pojo.po.AdPair;
|
||||
|
||||
/**
|
||||
* @author caozehui
|
||||
* @data 2025-08-18
|
||||
*/
|
||||
public interface AdPairMapper extends MPJBaseMapper<AdPair> {
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.njcn.gather.detection.pojo.dto;
|
||||
|
||||
import com.njcn.gather.err.pojo.po.PqErrSysDtls;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author caozehui
|
||||
* @data 2025-08-12
|
||||
*/
|
||||
@Data
|
||||
public class ConditionDataDTO {
|
||||
|
||||
/**
|
||||
* 某一相别且某一个误差条件范围内的被检色设备数据
|
||||
*/
|
||||
private Double devData;
|
||||
|
||||
/**
|
||||
* 某一相别且某一个误差条件范围内的标准设备数据
|
||||
*/
|
||||
private Double stdDevData;
|
||||
|
||||
|
||||
/**
|
||||
* 与上面数据所对应的误差体系详情
|
||||
*/
|
||||
private PqErrSysDtls pqErrSysDtls;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.njcn.gather.detection.pojo.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author caozehui
|
||||
* @data 2025-08-12
|
||||
*/
|
||||
@Data
|
||||
public class HarmonicConditionDataDTO extends ConditionDataDTO {
|
||||
|
||||
/**
|
||||
* (间谐波)谐波次数
|
||||
*/
|
||||
private Double harmonicNum;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.njcn.gather.detection.pojo.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author caozehui
|
||||
* @data 2025-09-01
|
||||
*/
|
||||
@Data
|
||||
public class WaveCommandDTO {
|
||||
private String ip;
|
||||
|
||||
private Integer port;
|
||||
|
||||
private String oper;
|
||||
|
||||
private Integer line;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.njcn.gather.detection.pojo.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author caozehui
|
||||
* @data 2025-09-01
|
||||
*/
|
||||
@Data
|
||||
public class WaveResultDTO {
|
||||
private String id;
|
||||
|
||||
private String path;
|
||||
}
|
||||
@@ -13,28 +13,47 @@ public enum DetectionCodeEnum {
|
||||
|
||||
FREQ("FREQ", "频率"),
|
||||
VRMS("VRMS", "相电压有效值"),
|
||||
PVRMS("PVRMS", "线电压有效值"),
|
||||
DELTA_V("DELTA_V", "电压偏差"),
|
||||
VA("VA", "电压相角"),
|
||||
U1A("U1A", "相电压基波有效值角度值"),
|
||||
PU1A("PU1A", "线电压基波有效值角度值"),
|
||||
U1("U1", "基波电压"),
|
||||
PU1("PU1", "线电压基波电压"),
|
||||
V2_50("V2-50", "谐波电压"),
|
||||
PV2_50("PV2-50", "线电压谐波电压"),
|
||||
I2_50("I2-50", "谐波电流"),
|
||||
P2_50("P2-50", "谐波有功功率"),
|
||||
SV_1_49("SV_1-49", "间谐波电压"),
|
||||
PSV_1_49("PSV_1-49", "线电压间谐波电压"),
|
||||
SI_1_49("SI_1-49", "间谐波电流"),
|
||||
MAG("MAG", "电压幅值"),
|
||||
DUR("DUR", "持续时间"),
|
||||
IRMS("IRMS", "电流有效值"),
|
||||
IA("IA", "电流相角"),
|
||||
I1A("I1A", "电流基波角度值"),
|
||||
V_UNBAN("V_UNBAN", "三相电压负序不平衡度"),
|
||||
I_UNBAN("I_UNBAN", "三相电流负序不平衡度"),
|
||||
PST("PST", "短时间闪变"),
|
||||
P_FUND("P_FUND", "功率"),
|
||||
W("W", "有功功率"),
|
||||
VARW("VARW", "无功功率"),
|
||||
VAW("VAW", "视在功率"),
|
||||
// PF("PF", "功率因数"),
|
||||
// P_FUND("P_FUND", "基波有功功率"),
|
||||
// P_HVAR("P_HVAR", "基波无功功率"),
|
||||
// P_HVA("P_HVA", "基波视在功率"),
|
||||
I1("I1", "基波电流"),
|
||||
UNKNOWN_ERROR("-1", "未知异常"),
|
||||
|
||||
|
||||
STAR("Star","星型接线"),
|
||||
DELTA("Delta","角型接线");
|
||||
STAR("Star", "星型接线"),
|
||||
DELTA("Delta", "角型接线"),
|
||||
|
||||
REAL_PREFIX("real$", "实时数据前缀"),
|
||||
CP_95_PREFIX("cp95$", "分钟统计-CP95值数据前缀"),
|
||||
MAX_PREFIX("max$", "分钟统计-最大值数据前缀"),
|
||||
MIN_PREFIX("min$", "分钟统计-最小值数据前缀"),
|
||||
AVG_PREFIX("avg$", "分钟统计-平均值数据前缀");
|
||||
|
||||
private final String code;
|
||||
private final String message;
|
||||
@@ -43,6 +62,7 @@ public enum DetectionCodeEnum {
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public static DetectionCodeEnum getDetectionCodeByCode(String code) {
|
||||
for (DetectionCodeEnum detectionCodeEnum : DetectionCodeEnum.values()) {
|
||||
if (ObjectUtil.equals(code, detectionCodeEnum.getCode())) {
|
||||
|
||||
@@ -9,6 +9,7 @@ import lombok.Getter;
|
||||
@Getter
|
||||
public enum DetectionResponseEnum {
|
||||
PLAN_PATTERN_NOT("A020001", "计划模式查询为空"),
|
||||
PLAN_NOT_EXIST("A020001", "计划信息缺失"),
|
||||
SCRIPT_PATTERN_NOT("A020001", "检测脚本查询为空"),
|
||||
SOURCE_INFO_NOT("A020002", "源表信息不存在"),
|
||||
PLAN_AND_SOURCE_NOT("A020003", "计划和源关系不存在"),
|
||||
@@ -17,7 +18,8 @@ public enum DetectionResponseEnum {
|
||||
SOURCE_NOT_CONNECT("A020006", "源未连接"),
|
||||
|
||||
|
||||
SCRIPT_CHECK_DATA_NOT_EXIST("A020040","测试脚本项暂无配置" );
|
||||
SCRIPT_CHECK_DATA_NOT_EXIST("A020040","测试脚本项暂无配置" ),
|
||||
EXCEED_MAX_TIME("A020041","检测次数超出最大限制!" );
|
||||
|
||||
private final String code;
|
||||
private final String message;
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.njcn.gather.detection.pojo.enums;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* @author caozehui
|
||||
* @data 2025-08-13
|
||||
*/
|
||||
@Getter
|
||||
public enum ResultEnum {
|
||||
QUALIFIED(1, "符合"),
|
||||
NOT_QUALIFIED(2, "不符合"),
|
||||
NETWORK_TIMEOUT(3, "网络超时"),
|
||||
NO_ERROR_SYS(4, "不在误差条件范围内"),
|
||||
NO_COMPARE_ERROR_SYS(5, "不参与误差比较");
|
||||
|
||||
private int value;
|
||||
private String msg;
|
||||
|
||||
ResultEnum(int value, String msg) {
|
||||
this.value = value;
|
||||
this.msg = msg;
|
||||
}
|
||||
}
|
||||
@@ -48,9 +48,11 @@ public enum SourceOperateCodeEnum {
|
||||
YJC_SBTXJY("yjc_sbtxjy", "设备通讯检测"),
|
||||
YJC_XYJY("yjc_xyjy", "协议校验"),
|
||||
YJC_XUJY("YJC_xujy", "相序校验"),
|
||||
YJC_ALIGN("YJC_align","实时数据对齐校验"),
|
||||
YJC_MXYZXJY("YJC_mxyzxjy", "模型一致性校验"),
|
||||
YJC_ALIGN("yjc_align","实时数据对齐校验"),
|
||||
YJC_MXYZXJY("yjc_mxyzxjy", "模型一致性校验"),
|
||||
FORMAL_REAL("formal_real","正式检测"),
|
||||
RECORD_WAVE_STEP1("record_wave_step1","启动录波_step1"),
|
||||
// RECORD_WAVE_STEP2("record_wave_step2","启动录波_step2"),
|
||||
// SIMULATE_REAL("simulate_real","模拟检测"),
|
||||
Coefficient_Check("Coefficient_Check","系数校验"),
|
||||
QUITE("quit","关闭设备通讯初始化"),
|
||||
@@ -65,6 +67,8 @@ public enum SourceOperateCodeEnum {
|
||||
SERVER_ERROR("server_error","服务端主动关闭连接,请稍后再试"),
|
||||
DEVICE_ERROR("device_error","设备主动关闭连接,请稍后再试"),
|
||||
|
||||
FLICKER_DATA_CHECK("flicker_data_check","闪变数据校验"),
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -100,7 +104,8 @@ public enum SourceOperateCodeEnum {
|
||||
/**
|
||||
* ftp文件传送指令
|
||||
*/
|
||||
FTP_SEND_01("FTP_SEND$01", "发送文件"),;
|
||||
FTP_SEND_01("FTP_SEND$01", "发送文件"),
|
||||
RDRE$01("RDRE$01", "启动录波");
|
||||
|
||||
private final String value;
|
||||
private final String msg;
|
||||
|
||||
@@ -14,6 +14,7 @@ public enum SourceResponseCodeEnum {
|
||||
UNPROCESSED_BUSINESS(10201, "立即响应,业务还未处理,类似肯定应答"),
|
||||
NORMAL_RESPONSE(10202, "正常响应中间状态码"),
|
||||
ICD_NOT_FOUND(10500, "未找到对应ICD"),
|
||||
RECORD_WAVE_FAILED(10501, "录波失败"),
|
||||
MESSAGE_PARSING_ERROR(10520, "报文解析有误"),
|
||||
CONTROLLED_SOURCE_ERROR(10521, "程控源参数有误"),
|
||||
TEST_ITEM_PARSING_ERROR(10522, "测试项解析有误"),
|
||||
@@ -37,10 +38,13 @@ public enum SourceResponseCodeEnum {
|
||||
|
||||
//自定义前端展示消息
|
||||
SOCKET_ERROR(25000,"服务端连接失败"),
|
||||
DEV_COMM_ALL_SUCCESS(25001,"校验成功"),
|
||||
DEV_COMM_TEST_FAIL(25002,"设备通讯校验失败"),
|
||||
PHASE_CHECK_FAIL(25003,"相序校验未通过"),
|
||||
|
||||
ALL_SUCCESS(25001,"校验成功"),
|
||||
FAIL(25002,"失败"),
|
||||
ALL_FAIL(25003,"校验失败"),
|
||||
RECEIVE_DATA_TIME_OUT(25004,"接收数据超时"),
|
||||
REAL_DATA_CHECK_FAIL(25005,"实时数据不符合"),
|
||||
STATISTICS_DATA_CHECK_FAIL(25006,"统计数据不符合"),
|
||||
FLICKER_DATA_START(25007,"开始接收闪变数据")
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ public class ContrastDetectionParam {
|
||||
private String planId;
|
||||
|
||||
@ApiModelProperty("用户ID,唯一标识")
|
||||
private String userId;
|
||||
private String loginName;
|
||||
|
||||
@ApiModelProperty("被检设备ID列表")
|
||||
@NotEmpty(message = DetectionValidMessage.DEV_IDS_NOT_EMPTY)
|
||||
@@ -32,13 +32,15 @@ public class ContrastDetectionParam {
|
||||
private List<String> standardDevIds;
|
||||
|
||||
/**
|
||||
* key为 标准设备ID_检测点序号、value为 被检设备ID_检测点序号
|
||||
* key为被检设备ID_检测点序号、value为 标准设备ID_检测点序号
|
||||
*/
|
||||
@ApiModelProperty("配对关系")
|
||||
@NotEmpty(message = DetectionValidMessage.PAIRS_NOT_EMPTY)
|
||||
private Map<String,String> pairs;
|
||||
private Map<String, String> pairs;
|
||||
/**
|
||||
* 检测项列表。第一个元素为预检测、第二个元素为系数校准、第三个元素为正式检测
|
||||
*/
|
||||
private List<Boolean> testItemList;
|
||||
|
||||
private String userId;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import lombok.Data;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author wr
|
||||
@@ -14,7 +15,8 @@ import java.util.List;
|
||||
@Data
|
||||
public class PreDetectionParam {
|
||||
|
||||
private String operateType;
|
||||
// "1"-"全部检测" , "2"-"不合格项复检"
|
||||
private String reCheckType;
|
||||
|
||||
/**
|
||||
* 检测计划id
|
||||
@@ -23,11 +25,6 @@ public class PreDetectionParam {
|
||||
private String planId;
|
||||
|
||||
|
||||
/**
|
||||
* 数字、模拟、比对
|
||||
*/
|
||||
private String pattern;
|
||||
|
||||
/**
|
||||
* 用户功能组成唯一标识 zhangsan_test
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.njcn.gather.detection.pojo.po;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @author caozehui
|
||||
* @data 2025-08-18
|
||||
*/
|
||||
@Data
|
||||
@TableName("ad_pair")
|
||||
public class AdPair implements Serializable {
|
||||
private String id;
|
||||
|
||||
private String planId;
|
||||
|
||||
private Integer num;
|
||||
|
||||
private String devMonitorId;
|
||||
|
||||
private String stdDevMonitorId;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.njcn.gather.detection.pojo.vo;
|
||||
|
||||
import cn.afterturn.easypoi.excel.annotation.Excel;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author caozehui
|
||||
* @data 2025-08-07
|
||||
*/
|
||||
@Data
|
||||
public class AlignDataExcel {
|
||||
|
||||
|
||||
@Excel(name = "时间", orderNum = "1", width = 40, groupName = "标准设备")
|
||||
private String timeStdDev;
|
||||
|
||||
@Excel(name = "Ua", orderNum = "2", groupName = "标准设备")
|
||||
private Double uaStdDev;
|
||||
|
||||
@Excel(name = "Ub", orderNum = "3", groupName = "标准设备")
|
||||
private Double ubStdDev;
|
||||
|
||||
@Excel(name = "Uc", orderNum = "4", groupName = "标准设备")
|
||||
private Double ucStdDev;
|
||||
|
||||
@Excel(name = "时间", orderNum = "5", width = 40, groupName = "被检设备")
|
||||
private String timeDev;
|
||||
|
||||
@Excel(name = "Ua", orderNum = "6", groupName = "被检设备")
|
||||
private Double uaDev;
|
||||
|
||||
@Excel(name = "Ub", orderNum = "7", groupName = "被检设备")
|
||||
private Double ubDev;
|
||||
|
||||
@Excel(name = "Uc", orderNum = "8", groupName = "被检设备")
|
||||
private Double ucDev;
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package com.njcn.gather.detection.pojo.vo;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author caozehui
|
||||
* @data 2025-08-06
|
||||
*/
|
||||
@Data
|
||||
public class AlignDataVO {
|
||||
|
||||
private String stdDevName;
|
||||
|
||||
private List<ChannelData> channelDataList;
|
||||
|
||||
|
||||
// 通道数据
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public static class ChannelData {
|
||||
|
||||
// 标准设备通道号
|
||||
private String stdDevNum;
|
||||
|
||||
// 与之对应的被检设备名称_通道号
|
||||
private String devInfo;
|
||||
|
||||
// 数据
|
||||
private List<RawData> dataList;
|
||||
}
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public static class RawData {
|
||||
private String timeDev;
|
||||
|
||||
private Double uaDev;
|
||||
|
||||
private Double ubDev;
|
||||
|
||||
private Double ucDev;
|
||||
|
||||
private Double utDev;
|
||||
|
||||
private String timeStdDev;
|
||||
|
||||
private Double uaStdDev;
|
||||
|
||||
private Double ubStdDev;
|
||||
|
||||
private Double ucStdDev;
|
||||
|
||||
private Double utStdDev;
|
||||
|
||||
private String unit;
|
||||
}
|
||||
}
|
||||
@@ -48,4 +48,9 @@ public class DetectionData {
|
||||
* 单位
|
||||
*/
|
||||
private String unit;
|
||||
|
||||
/**
|
||||
* 误差体系详情ID(比对式使用)
|
||||
*/
|
||||
private String errorDtlId;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
package com.njcn.gather.detection.pojo.vo;
|
||||
|
||||
import io.swagger.models.auth.In;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @Author: cdf
|
||||
* @CreateTime: 2024-12-26
|
||||
@@ -13,6 +10,11 @@ import java.util.List;
|
||||
@Data
|
||||
public class DevLineTestResult {
|
||||
|
||||
/**
|
||||
* 检测项code
|
||||
*/
|
||||
private String scriptName;
|
||||
|
||||
private String deviceId;
|
||||
|
||||
private String deviceName;
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.njcn.gather.detection.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.njcn.gather.detection.pojo.po.AdPair;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author caozehui
|
||||
* @data 2025-08-18
|
||||
*/
|
||||
public interface IAdPariService extends IService<AdPair> {
|
||||
|
||||
/**
|
||||
* 获取最大的检测次数
|
||||
*
|
||||
* @param devMonitorId
|
||||
* @return
|
||||
*/
|
||||
Integer getMaxNum(String devMonitorId);
|
||||
|
||||
/**
|
||||
* 根据设备id查询配对关系
|
||||
*
|
||||
* @param devIds
|
||||
* @return
|
||||
*/
|
||||
List<AdPair> listByDevIds(List<String> devIds);
|
||||
}
|
||||
@@ -4,6 +4,8 @@ import com.njcn.gather.detection.pojo.param.ContrastDetectionParam;
|
||||
import com.njcn.gather.detection.pojo.param.PreDetectionParam;
|
||||
import com.njcn.gather.detection.pojo.param.SimulateDetectionParam;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
/**
|
||||
* @author wr
|
||||
@@ -56,4 +58,9 @@ public interface PreDetectionService {
|
||||
* @param param
|
||||
*/
|
||||
void startContrastTest(ContrastDetectionParam param);
|
||||
|
||||
/**
|
||||
* 导出实时数据对齐过程中的数据
|
||||
*/
|
||||
void exportAlignData();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.njcn.gather.detection.service.impl;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.njcn.gather.detection.mapper.AdPairMapper;
|
||||
import com.njcn.gather.detection.pojo.po.AdPair;
|
||||
import com.njcn.gather.detection.service.IAdPariService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author caozehui
|
||||
* @data 2025-08-18
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class AdPairServiceImpl extends ServiceImpl<AdPairMapper, AdPair> implements IAdPariService {
|
||||
|
||||
@Override
|
||||
public Integer getMaxNum(String devMonitorId) {
|
||||
List<AdPair> adPairList = this.lambdaQuery().select(AdPair::getNum)
|
||||
.eq(AdPair::getDevMonitorId, devMonitorId)
|
||||
.orderByDesc(AdPair::getNum)
|
||||
.last("LIMIT 1").list();
|
||||
if (CollUtil.isNotEmpty(adPairList)) {
|
||||
return adPairList.get(0).getNum();
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AdPair> listByDevIds(List<String> devIds) {
|
||||
if (CollUtil.isNotEmpty(devIds)) {
|
||||
QueryWrapper<AdPair> wrapper = new QueryWrapper<>();
|
||||
devIds.forEach(devId -> wrapper.likeRight("ad_pair.Dev_Monitor_Id", devId));
|
||||
return this.list(wrapper);
|
||||
} else {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,6 @@
|
||||
package com.njcn.gather.detection.service.impl;
|
||||
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
@@ -22,17 +21,16 @@ import com.njcn.gather.detection.util.business.DetectionCommunicateUtil;
|
||||
import com.njcn.gather.detection.util.socket.CnSocketUtil;
|
||||
import com.njcn.gather.detection.util.socket.FormalTestManager;
|
||||
import com.njcn.gather.detection.util.socket.SocketManager;
|
||||
import com.njcn.gather.detection.util.socket.WebServiceManager;
|
||||
import com.njcn.gather.detection.util.socket.cilent.NettyClient;
|
||||
import com.njcn.gather.detection.util.socket.cilent.NettyContrastClientHandler;
|
||||
import com.njcn.gather.detection.util.socket.cilent.NettySourceClientHandler;
|
||||
import com.njcn.gather.detection.util.socket.websocket.WebServiceManager;
|
||||
import com.njcn.gather.device.pojo.po.PqDev;
|
||||
import com.njcn.gather.device.pojo.vo.PreDetection;
|
||||
import com.njcn.gather.device.service.IPqDevService;
|
||||
import com.njcn.gather.plan.pojo.enums.DataSourceEnum;
|
||||
import com.njcn.gather.plan.pojo.po.AdPlan;
|
||||
import com.njcn.gather.plan.pojo.po.AdPlanSource;
|
||||
import com.njcn.gather.plan.service.IAdPlanService;
|
||||
import com.njcn.gather.plan.service.IAdPlanSourceService;
|
||||
import com.njcn.gather.result.pojo.enums.ResultUnitEnum;
|
||||
import com.njcn.gather.script.pojo.param.PqScriptCheckDataParam;
|
||||
import com.njcn.gather.script.pojo.param.PqScriptIssueParam;
|
||||
import com.njcn.gather.script.pojo.po.SourceIssue;
|
||||
@@ -42,17 +40,21 @@ import com.njcn.gather.source.pojo.po.SourceInitialize;
|
||||
import com.njcn.gather.source.service.IPqSourceService;
|
||||
import com.njcn.gather.system.dictionary.pojo.enums.DictDataEnum;
|
||||
import com.njcn.gather.system.dictionary.service.IDictDataService;
|
||||
import com.njcn.web.utils.HttpServletUtil;
|
||||
import com.njcn.web.utils.RequestUtil;
|
||||
import io.netty.channel.Channel;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
||||
@@ -61,9 +63,6 @@ import java.util.stream.Collectors;
|
||||
@Slf4j
|
||||
public class PreDetectionServiceImpl implements PreDetectionService {
|
||||
|
||||
private final String stepTag = "&&";
|
||||
private final String handlerSourceStr = "_Source";
|
||||
|
||||
private final IPqDevService iPqDevService;
|
||||
private final IDictDataService dictDataService;
|
||||
private final IAdPlanService iAdPlanService;
|
||||
@@ -75,14 +74,10 @@ public class PreDetectionServiceImpl implements PreDetectionService {
|
||||
private final SocketSourceResponseService socketSourceResponseService;
|
||||
private final SocketContrastResponseService socketContrastResponseService;
|
||||
private final IPqScriptCheckDataService iPqScriptCheckDataService;
|
||||
private final SocketManager socketManager;
|
||||
|
||||
@Value("${socket.source.ip:192.168.1.138}")
|
||||
private String ip;
|
||||
|
||||
@Value("${socket.source.port:61000}")
|
||||
private Integer port;
|
||||
|
||||
//private final SocketSourceResponseService sourceResponseService;
|
||||
@Value("${log.homeDir}")
|
||||
private String alignDataFilePath;
|
||||
|
||||
|
||||
@Override
|
||||
@@ -92,9 +87,9 @@ public class PreDetectionServiceImpl implements PreDetectionService {
|
||||
//用于处理异常导致的socket通道未关闭,socket交互异常
|
||||
DetectionCommunicateUtil.checkCommunicateChannel(param);
|
||||
/*
|
||||
先组装源通讯协议
|
||||
查询计划什么模式的(除了对比式,其他都是一个计划对应一个源)
|
||||
*/
|
||||
* 先组装源通讯协议
|
||||
* 查询计划什么模式的(除了对比式,其他都是一个计划对应一个源)
|
||||
*/
|
||||
AdPlan plan = iAdPlanService.getById(param.getPlanId());
|
||||
param.setScriptId(plan.getScriptId());
|
||||
param.setErrorSysId(plan.getErrorSysId());
|
||||
@@ -102,21 +97,30 @@ public class PreDetectionServiceImpl implements PreDetectionService {
|
||||
if (ObjectUtil.isNotNull(plan)) {
|
||||
String code = dictDataService.getDictDataById(plan.getPattern()).getCode();
|
||||
DictDataEnum dictDataEnumByCode = DictDataEnum.getDictDataEnumByCode(code);
|
||||
switch (dictDataEnumByCode) {
|
||||
case DIGITAL:
|
||||
case SIMULATE:
|
||||
sendYtxSocket(param);
|
||||
break;
|
||||
case CONTRAST:
|
||||
break;
|
||||
default:
|
||||
throw new BusinessException(DetectionResponseEnum.PLAN_PATTERN_NOT);
|
||||
if (Objects.nonNull(dictDataEnumByCode)) {
|
||||
switch (dictDataEnumByCode) {
|
||||
case DIGITAL:
|
||||
case SIMULATE:
|
||||
sendYtxSocket(param);
|
||||
break;
|
||||
case CONTRAST:
|
||||
break;
|
||||
default:
|
||||
throw new BusinessException(DetectionResponseEnum.PLAN_PATTERN_NOT);
|
||||
}
|
||||
} else {
|
||||
throw new BusinessException(DetectionResponseEnum.PLAN_PATTERN_NOT);
|
||||
}
|
||||
} else {
|
||||
throw new BusinessException(DetectionResponseEnum.PLAN_NOT_EXIST);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 原本系数校准单独写的,现在合并了,该方法过期了,没有调用了
|
||||
*/
|
||||
@Deprecated
|
||||
@Override
|
||||
public void coefficientCheck(PreDetectionParam param) {
|
||||
// 检测是否存在连接的通道,后期需要做成动态,如果组合中不是第一位,则不需要关闭,也不用初始化 todo....
|
||||
@@ -135,8 +139,8 @@ public class PreDetectionServiceImpl implements PreDetectionService {
|
||||
msg.setOperateCode(SourceOperateCodeEnum.INIT_GATHER.getValue());
|
||||
msg.setData(JSON.toJSONString(sourceParam));
|
||||
param.setSourceId(sourceParam.getSourceId());
|
||||
// NettyClient.socketClient(ip, port, param, JSON.toJSONString(msg), new NettySourceClientHandler(param, sourceResponseService));
|
||||
NettyClient.socketClient(ip, port, param, JSON.toJSONString(msg), new NettySourceClientHandler(param, socketSourceResponseService));
|
||||
// 使用智能发送工具类,自动管理连接
|
||||
socketManager.smartSendToSource(param, JSON.toJSONString(msg));
|
||||
} else {
|
||||
throw new BusinessException(DetectionResponseEnum.SOURCE_INFO_NOT);
|
||||
}
|
||||
@@ -146,10 +150,29 @@ public class PreDetectionServiceImpl implements PreDetectionService {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 发送源通信校验Socket连接(数字式和模拟式检测模式)
|
||||
*
|
||||
* <p>该方法用于建立与程控源设备的Socket连接,进行源通信校验。主要流程:</p>
|
||||
* <ul>
|
||||
* <li>1. 存储检测参数到全局管理器</li>
|
||||
* <li>2. 根据计划ID获取计划源信息</li>
|
||||
* <li>3. 获取源设备初始化参数</li>
|
||||
* <li>4. 初始化设备和源响应服务列表</li>
|
||||
* <li>5. 组装Socket请求报文</li>
|
||||
* <li>6. 建立Netty客户端连接</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param param 预检测参数,包含计划ID、用户ID等信息
|
||||
* @throws BusinessException 当计划源信息不存在或源初始化参数为空时抛出
|
||||
* @see SourceOperateCodeEnum#YJC_YTXJY 源通信校验操作码
|
||||
* @see SourceOperateCodeEnum#INIT_GATHER 初始化采集操作码
|
||||
*/
|
||||
private void sendYtxSocket(PreDetectionParam param) {
|
||||
WebServiceManager.addPreDetectionParam(param);
|
||||
AdPlanSource planSource = adPlanSourceService.getOne(new LambdaQueryWrapper<AdPlanSource>().eq(AdPlanSource::getPlanId, param.getPlanId()));
|
||||
param.setSourceId(planSource.getSourceId());
|
||||
String loginName = RequestUtil.getLoginNameByToken();
|
||||
WebServiceManager.addPreDetectionParam(loginName, param);
|
||||
if (ObjectUtil.isNotNull(planSource)) {
|
||||
//获取源初始化参数
|
||||
SourceInitialize sourceParam = pqSourceService.getSourceInitializeParam(planSource.getSourceId());
|
||||
@@ -161,9 +184,8 @@ public class PreDetectionServiceImpl implements PreDetectionService {
|
||||
socketMsg.setRequestId(SourceOperateCodeEnum.YJC_YTXJY.getValue());
|
||||
socketMsg.setOperateCode(SourceOperateCodeEnum.INIT_GATHER.getValue());
|
||||
socketMsg.setData(JSON.toJSONString(sourceParam));
|
||||
//建立与源控程序的socket连接,
|
||||
// NettyClient.socketClient(ip, port, param, JSON.toJSONString(socketMsg), new NettySourceClientHandler(param, sourceResponseService));
|
||||
NettyClient.socketClient(ip, port, param, JSON.toJSONString(socketMsg), new NettySourceClientHandler(param, socketSourceResponseService));
|
||||
//使用智能发送工具类,自动管理与源控程序的socket连接
|
||||
socketManager.smartSendToSource(param, JSON.toJSONString(socketMsg));
|
||||
} else {
|
||||
throw new BusinessException(DetectionResponseEnum.SOURCE_INFO_NOT);
|
||||
}
|
||||
@@ -172,16 +194,34 @@ public class PreDetectionServiceImpl implements PreDetectionService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送源通信校验Socket连接(仿真模式)
|
||||
*
|
||||
* <p>该方法专门用于仿真检测模式下的源通信校验。与普通模式的区别:</p>
|
||||
* <ul>
|
||||
* <li>直接使用传入的sourceId获取源初始化参数</li>
|
||||
* <li>不需要通过计划ID查询计划源信息</li>
|
||||
* <li>适用于独立的源设备通信测试</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param param 预检测参数,必须包含sourceId和userPageId
|
||||
* @throws BusinessException 当源初始化参数为空时抛出
|
||||
* @see #sendYtxSocket(PreDetectionParam) 普通检测模式的源通信校验
|
||||
* @see SourceOperateCodeEnum#YJC_YTXJY 源通信校验操作码
|
||||
* @see SourceOperateCodeEnum#INIT_GATHER 初始化采集操作码
|
||||
*/
|
||||
private void sendYtxSocketSimulate(PreDetectionParam param) {
|
||||
SourceInitialize sourceParam = pqSourceService.getSourceInitializeParam(param.getSourceId());
|
||||
param.setSourceId(sourceParam.getSourceId());
|
||||
WebServiceManager.addPreDetectionParam(param);
|
||||
String loginName = RequestUtil.getLoginNameByToken();
|
||||
WebServiceManager.addPreDetectionParam(loginName, param);
|
||||
if (ObjectUtil.isNotNull(sourceParam)) {
|
||||
SocketMsg<String> socketMsg = new SocketMsg<>();
|
||||
socketMsg.setRequestId(SourceOperateCodeEnum.YJC_YTXJY.getValue());
|
||||
socketMsg.setOperateCode(SourceOperateCodeEnum.INIT_GATHER.getValue());
|
||||
socketMsg.setData(JSON.toJSONString(sourceParam));
|
||||
NettyClient.socketClient(ip, port, param, JSON.toJSONString(socketMsg), new NettySourceClientHandler(param, socketSourceResponseService));
|
||||
// 使用智能发送工具类,自动管理连接
|
||||
socketManager.smartSendToSource(param, JSON.toJSONString(socketMsg));
|
||||
} else {
|
||||
throw new BusinessException(DetectionResponseEnum.SOURCE_INFO_NOT);
|
||||
}
|
||||
@@ -205,14 +245,15 @@ public class PreDetectionServiceImpl implements PreDetectionService {
|
||||
xuMsg.setData(JSON.toJSONString(sourceIssues));
|
||||
xuMsg.setRequestId(SourceOperateCodeEnum.FORMAL_REAL.getValue() + "&&" + sourceIssues.getType());
|
||||
SocketManager.sendMsg(param.getUserPageId() + DetectionCommunicateConstant.SOURCE, JSON.toJSONString(xuMsg));
|
||||
// Resume_Success
|
||||
} else {
|
||||
//TODO 是否最终检测完成需要推送给用户
|
||||
//TODO 是否最终检测完成需要推送给用户 检测完成
|
||||
PqScriptCheckDataParam checkDataParam = new PqScriptCheckDataParam();
|
||||
checkDataParam.setScriptId(param.getScriptId());
|
||||
checkDataParam.setIsValueTypeName(false);
|
||||
List<String> valueType = iPqScriptCheckDataService.getValueType(checkDataParam);
|
||||
List<String> adType = iPqScriptCheckDataService.getValueType(checkDataParam);
|
||||
|
||||
iPqDevService.updateResult(param.getDevIds(), valueType, param.getCode(), param.getUserId(), param.getTemperature(), param.getHumidity());
|
||||
iPqDevService.updateResult(param.getDevIds(), adType, param.getCode(), param.getUserId(), param.getTemperature(), param.getHumidity());
|
||||
CnSocketUtil.quitSend(param);
|
||||
}
|
||||
|
||||
@@ -254,14 +295,20 @@ public class PreDetectionServiceImpl implements PreDetectionService {
|
||||
.collect(Collectors.toList());
|
||||
|
||||
SourceIssue sourceIssue = sourceIssues.get(0);
|
||||
String type = sourceIssue.getType();
|
||||
if (sourceIssue.getIsP()) {
|
||||
sourceIssue.setType(ResultUnitEnum.V_ABSOLUTELY.getCode());
|
||||
type = ResultUnitEnum.P.getCode();
|
||||
}
|
||||
|
||||
List<String> comm = sourceIssue.getDevValueTypeList();
|
||||
System.out.println("向装置下发的参数ddd>>>>>>>>" + comm);
|
||||
|
||||
SocketMsg<String> socketMsg = new SocketMsg<>();
|
||||
socketMsg.setOperateCode(SourceOperateCodeEnum.OPER_GATHER.getValue());
|
||||
socketMsg.setRequestId(SourceOperateCodeEnum.FORMAL_REAL.getValue() + stepTag + sourceIssue.getType());
|
||||
socketMsg.setRequestId(SourceOperateCodeEnum.FORMAL_REAL.getValue() + CnSocketUtil.STEP_TAG + type);
|
||||
socketMsg.setData(JSON.toJSONString(sourceIssues.get(0)));
|
||||
SocketManager.sendMsg(param.getUserPageId() + handlerSourceStr, JSON.toJSONString(socketMsg));
|
||||
SocketManager.sendMsg(param.getUserPageId() + CnSocketUtil.SOURCE_TAG, JSON.toJSONString(socketMsg));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -282,51 +329,80 @@ public class PreDetectionServiceImpl implements PreDetectionService {
|
||||
public void startContrastTest(ContrastDetectionParam param) {
|
||||
// 参数校验,目前仅检查IP是否重复,后续可在里面扩展
|
||||
checkDevIp(param.getDevIds());
|
||||
//用于处理异常导致的socket通道未关闭,socket交互异常
|
||||
DetectionCommunicateUtil.checkContrastCommunicateChannel(param.getUserId());
|
||||
socketContrastResponseService.init(param);
|
||||
|
||||
// 和通信模块进行连接
|
||||
this.sendContrastSocket(param);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exportAlignData() {
|
||||
String fileName = "实时数据.xlsx";
|
||||
HttpServletResponse response = HttpServletUtil.getResponse();
|
||||
response.reset();
|
||||
response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
|
||||
response.setContentType("application/octet-stream;charset=UTF-8");
|
||||
try {
|
||||
InputStream inputStream = new FileInputStream(alignDataFilePath + "\\" + fileName);
|
||||
byte[] buffer = new byte[1024];
|
||||
int len = 0;
|
||||
ServletOutputStream outputStream = response.getOutputStream();
|
||||
while ((len = inputStream.read(buffer)) != -1) {
|
||||
outputStream.write(buffer, 0, len);
|
||||
}
|
||||
outputStream.flush();
|
||||
outputStream.close();
|
||||
inputStream.close();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 比对式-与通信模块进行连接
|
||||
*
|
||||
* @param param
|
||||
*/
|
||||
private void sendContrastSocket(ContrastDetectionParam param) {
|
||||
String s = param.getUserId() + CnSocketUtil.DEV_TAG;
|
||||
|
||||
Map<String, List<PreDetection>> map = new HashMap<>(1);
|
||||
List<PreDetection> preDetections = BeanUtil.copyToList(FormalTestManager.devList, PreDetection.class);
|
||||
preDetections.addAll(BeanUtil.copyToList(FormalTestManager.standardDevList, PreDetection.class));
|
||||
List<PreDetection> preDetections = new ArrayList<>();
|
||||
preDetections.addAll(FormalTestManager.devList);
|
||||
preDetections.addAll(FormalTestManager.standardDevList);
|
||||
|
||||
preDetections.forEach(x -> {
|
||||
x.setDevType(x.getIcdType());
|
||||
x.getMonitorList().forEach(y -> {
|
||||
|
||||
String pt = y.getPt();
|
||||
int i = pt.indexOf(":");
|
||||
y.setPt(BigDecimal.valueOf(Double.parseDouble(pt.substring(0, i))).divide(BigDecimal.valueOf(Double.parseDouble(pt.substring(i + 1))), 5, BigDecimal.ROUND_HALF_UP) + "");
|
||||
String ptStr = y.getPtStr();
|
||||
int i = ptStr.indexOf(":");
|
||||
y.setPt(BigDecimal.valueOf(Double.parseDouble(ptStr.substring(0, i))).divide(BigDecimal.valueOf(Double.parseDouble(ptStr.substring(i + 1))), 5, BigDecimal.ROUND_HALF_UP).doubleValue());
|
||||
|
||||
String ct = y.getCt();
|
||||
i = ct.indexOf(":");
|
||||
y.setCt(BigDecimal.valueOf(Double.parseDouble(ct.substring(0, i))).divide(BigDecimal.valueOf(Double.parseDouble(ct.substring(i + 1))), 5, BigDecimal.ROUND_HALF_UP) + "");
|
||||
String ctStr = y.getCtStr();
|
||||
i = ctStr.indexOf(":");
|
||||
y.setCt(BigDecimal.valueOf(Double.parseDouble(ctStr.substring(0, i))).divide(BigDecimal.valueOf(Double.parseDouble(ctStr.substring(i + 1))), 5, BigDecimal.ROUND_HALF_UP).doubleValue());
|
||||
});
|
||||
});
|
||||
map.put("deviceList", preDetections);
|
||||
String jsonString = JSON.toJSONString(map);
|
||||
|
||||
SocketMsg<String> socketMsg = new SocketMsg<>();
|
||||
socketMsg.setRequestId(SourceOperateCodeEnum.YJC_SBTXJY.getValue());
|
||||
socketMsg.setOperateCode(SourceOperateCodeEnum.DEV_INIT_GATHER_01.getValue());
|
||||
AdPlan currentTestPlan = FormalTestManager.currentTestPlan;
|
||||
String[] datasource = currentTestPlan.getDatasourceId().split(",");
|
||||
if (Arrays.stream(datasource).anyMatch(x -> x.equals(DataSourceEnum.REAL_DATA.getValue()))) {
|
||||
socketMsg.setOperateCode(SourceOperateCodeEnum.DEV_INIT_GATHER_02.getValue());
|
||||
} else if (Arrays.stream(datasource).anyMatch(x -> x.equals(DataSourceEnum.MINUTE_STATISTICS_AVG.getValue()) || x.equals(DataSourceEnum.MINUTE_STATISTICS_MAX.getValue()) || x.equals(DataSourceEnum.MINUTE_STATISTICS_MIN.getValue()) || x.equals(DataSourceEnum.MINUTE_STATISTICS_CP95.getValue()))) {
|
||||
socketMsg.setOperateCode(SourceOperateCodeEnum.DEV_INIT_GATHER_01.getValue());
|
||||
} else if (Arrays.stream(datasource).anyMatch(x -> x.equals(DataSourceEnum.WAVE_DATA.getValue()))) {
|
||||
socketMsg.setOperateCode(SourceOperateCodeEnum.DEV_INIT_GATHER_02.getValue());
|
||||
}
|
||||
socketMsg.setData(jsonString);
|
||||
String json = JSON.toJSONString(socketMsg);
|
||||
SocketManager.sendMsg(s, json);
|
||||
|
||||
PreDetectionParam preDetectionParam = new PreDetectionParam();
|
||||
preDetectionParam.setUserPageId(param.getUserId());
|
||||
NettyClient.socketClient(ip, port, preDetectionParam, json, new NettyContrastClientHandler(preDetectionParam, socketContrastResponseService));
|
||||
preDetectionParam.setUserPageId(param.getLoginName());
|
||||
preDetectionParam.setTestItemList(param.getTestItemList());
|
||||
preDetectionParam.setDevIds(param.getDevIds());
|
||||
preDetectionParam.setUserId(param.getUserId());
|
||||
WebServiceManager.addPreDetectionParam(param.getLoginName(), preDetectionParam);
|
||||
socketManager.smartSendToContrast(param, JSON.toJSONString(socketMsg));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,217 +3,438 @@ package com.njcn.gather.detection.util;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.njcn.gather.detection.pojo.po.DevData;
|
||||
import com.njcn.gather.detection.service.impl.DetectionServiceImpl;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.io.File;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.util.Arrays;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* 检测工具类
|
||||
* <p>
|
||||
* 提供电能质量检测相关的数据处理、时间转换、统计计算等工具方法。
|
||||
* 主要功能包括:
|
||||
* <ul>
|
||||
* <li>相角矫正和标准化</li>
|
||||
* <li>设备数据时间对齐判断</li>
|
||||
* <li>时间格式转换和毫秒数计算</li>
|
||||
* <li>数值零值判断</li>
|
||||
* <li>统计数据处理(CP95分位数、部分值、平均值等)</li>
|
||||
* <li>数据排序和索引计算</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author caozehui
|
||||
* @data 2025-07-28
|
||||
* @version 1.0
|
||||
* @since 2025-07-28
|
||||
*/
|
||||
@Slf4j
|
||||
public class DetectionUtil {
|
||||
/**
|
||||
* 相角矫正到统一个区间
|
||||
* ISO 8601日期时间格式化器
|
||||
* 用于解析和格式化符合ISO 8601标准的日期时间字符串
|
||||
*/
|
||||
public static final DateTimeFormatter FORMATTER = DateTimeFormatter.ISO_DATE_TIME;
|
||||
|
||||
|
||||
/**
|
||||
* 毫秒转秒的转换因子
|
||||
*/
|
||||
private static final long MILLIS_TO_SECONDS = 1000L;
|
||||
|
||||
/**
|
||||
* 毫秒转分钟的转换因子
|
||||
*/
|
||||
private static final long MILLIS_TO_MINUTES = 1000 * 60L;
|
||||
|
||||
/**
|
||||
* 时间对齐判断的容差毫秒数
|
||||
* 当两个时间戳差值小于此值时,认为时间是对齐的
|
||||
*/
|
||||
private static final long TIME_ALIGNMENT_TOLERANCE_MS = 100L;
|
||||
|
||||
/**
|
||||
* 角度相关常量
|
||||
*/
|
||||
private static final double ANGLE_180 = 180.0;
|
||||
private static final double ANGLE_360 = 360.0;
|
||||
private static final double ANGLE_MINUS_180 = -180.0;
|
||||
|
||||
/**
|
||||
* CP95算法相关常量
|
||||
*/
|
||||
private static final int CP95_DATA_SIZE_THRESHOLD = 21;
|
||||
private static final double CP95_PERCENTILE = 0.05;
|
||||
private static final int CP95_SMALL_DATA_INDEX = 1;
|
||||
|
||||
/**
|
||||
* 数据处理相关常量
|
||||
*/
|
||||
private static final int MIN_DATA_SIZE_FOR_SECTION_VALUE = 2;
|
||||
|
||||
|
||||
/**
|
||||
* 相角矫正到统一区间[-180°, 180°]
|
||||
* <p>
|
||||
* 将任意角度值标准化到[-180°, 180°]范围内,便于相角比较和计算。
|
||||
* 使用模运算处理任意大小的角度值,能正确处理超出多个360°范围的情况。
|
||||
* <p>
|
||||
* 示例:
|
||||
* <ul>
|
||||
* <li>-1080° → 0°</li>
|
||||
* <li>-900° → 180°</li>
|
||||
* <li>720° → 0°</li>
|
||||
* <li>450° → 90°</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param phase
|
||||
* @return
|
||||
* @param phase 待矫正的相角值(单位:度)
|
||||
* @return 矫正后的相角值,范围在[-180°, 180°]内
|
||||
*/
|
||||
public static Double adjustPhase(Double phase) {
|
||||
if (phase < -180) {
|
||||
return phase + 360;
|
||||
if (phase == null) {
|
||||
return null;
|
||||
}
|
||||
if (phase > 180) {
|
||||
return phase - 360;
|
||||
// 使用模运算将角度标准化到[-180, 180]范围
|
||||
double normalizedPhase = phase % ANGLE_360;
|
||||
// 处理超出[-180, 180]范围的情况
|
||||
if (normalizedPhase > ANGLE_180) {
|
||||
normalizedPhase -= ANGLE_360;
|
||||
} else if (normalizedPhase < ANGLE_MINUS_180) {
|
||||
normalizedPhase += ANGLE_360;
|
||||
}
|
||||
return phase;
|
||||
|
||||
return normalizedPhase;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断数据是否对齐
|
||||
* 判断被检设备数据与标准设备数据的时间是否对齐
|
||||
* <p>
|
||||
* 数据对齐的判断标准(满足任一条件即可):
|
||||
* 1. 将时间戳四舍五入到秒级精度后完全相等(处理跨秒边界情况)
|
||||
* 2. 两个时间戳的毫秒差值小于容差值(处理同秒内的精确对齐)
|
||||
* <p>
|
||||
* 示例:499ms vs 509ms → 四舍五入后不同秒,但差值仅10ms < 100ms → 对齐
|
||||
*
|
||||
* @param devData 被检设备数据
|
||||
* @param standardDevData 标准设备数据
|
||||
* @return
|
||||
* @param devData 被检设备数据,包含时间戳信息
|
||||
* @param standardDevData 标准设备数据,包含时间戳信息
|
||||
* @return true表示数据时间对齐,false表示不对齐
|
||||
*/
|
||||
public static boolean isAlignData(DevData devData, DevData standardDevData) {
|
||||
public static boolean isAlignData(DevData devData, DevData standardDevData, int type) {
|
||||
if (ObjectUtil.isNotNull(devData) && ObjectUtil.isNotNull(standardDevData)) {
|
||||
|
||||
// 获取两个设备数据的时间戳(毫秒)
|
||||
long devMillis = getMillis(devData.getTime());
|
||||
long standardMillis = getMillis(standardDevData.getTime());
|
||||
if (BigDecimal.valueOf(devMillis).divide(BigDecimal.valueOf(10), 0, BigDecimal.ROUND_HALF_UP).compareTo(BigDecimal.valueOf(standardMillis).divide(BigDecimal.valueOf(10), 0, BigDecimal.ROUND_HALF_UP)) == 0) {
|
||||
return true;
|
||||
} else if (Math.abs(devMillis - standardMillis) < 100) {
|
||||
return true;
|
||||
if (type == 1) { // 实时数据
|
||||
// 方式1:将时间戳转换为秒级精度进行比较(处理跨秒边界情况)
|
||||
BigDecimal devSeconds = BigDecimal.valueOf(devMillis).divide(BigDecimal.valueOf(MILLIS_TO_SECONDS), 0, RoundingMode.HALF_UP);
|
||||
BigDecimal standardSeconds = BigDecimal.valueOf(standardMillis).divide(BigDecimal.valueOf(MILLIS_TO_SECONDS), 0, RoundingMode.HALF_UP);
|
||||
if (devSeconds.compareTo(standardSeconds) == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 方式2:毫秒级时间差小于容差值也认为是对齐的(处理精确对齐)
|
||||
if (Math.abs(devMillis - standardMillis) < TIME_ALIGNMENT_TOLERANCE_MS) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (type == 2) { //统计数据
|
||||
BigDecimal devSeconds = BigDecimal.valueOf(devMillis).divide(BigDecimal.valueOf(MILLIS_TO_MINUTES), 0, RoundingMode.HALF_UP);
|
||||
BigDecimal standardSeconds = BigDecimal.valueOf(standardMillis).divide(BigDecimal.valueOf(MILLIS_TO_MINUTES), 0, RoundingMode.HALF_UP);
|
||||
if (devSeconds.compareTo(standardSeconds) == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将字符串日期时间转换为指定格式的LocalDateTime
|
||||
* 将字符串日期时间转换为LocalDateTime对象
|
||||
* <p>
|
||||
* 使用指定的格式解析时间字符串,支持带时区的ISO 8601格式。
|
||||
* 解析时会将时间统一转换为UTC时区的LocalDateTime。
|
||||
*
|
||||
* @param dateTimeStr
|
||||
* @param formatter
|
||||
* @return
|
||||
* @param dateTimeStr 日期时间字符串,应符合指定格式
|
||||
* @param formatter 时间格式化器,用于解析字符串
|
||||
* @return 解析成功返回LocalDateTime对象,解析失败返回null
|
||||
*/
|
||||
public static LocalDateTime timeFormat(String dateTimeStr, DateTimeFormatter formatter) {
|
||||
try {
|
||||
// 使用UTC时区解析时间字符串
|
||||
ZonedDateTime zonedDateTime = ZonedDateTime.parse(dateTimeStr, formatter.withZone(ZoneId.of("UTC")));
|
||||
LocalDateTime localDateTime = zonedDateTime.toLocalDateTime();
|
||||
return localDateTime;
|
||||
// 转换为LocalDateTime对象
|
||||
return zonedDateTime.toLocalDateTime();
|
||||
} catch (DateTimeParseException e) {
|
||||
System.err.println("日期时间字符串格式错误: " + e.getMessage());
|
||||
log.error("日期时间字符串格式错误: {}", e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字符串日期时间对应的毫秒数
|
||||
* 获取字符串日期时间对应的UTC毫秒时间戳
|
||||
* <p>
|
||||
* 使用默认的ISO_DATE_TIME格式解析时间字符串,
|
||||
* 并转换为UTC时区的毫秒时间戳。
|
||||
*
|
||||
* @param dateTimeStr
|
||||
* @return
|
||||
* @param dateTimeStr ISO 8601格式的日期时间字符串
|
||||
* @return UTC时区的毫秒时间戳
|
||||
*/
|
||||
public static long getMillis(String dateTimeStr) {
|
||||
LocalDateTime localDateTime = timeFormat(dateTimeStr, DateTimeFormatter.ISO_DATE_TIME);
|
||||
if (dateTimeStr == null || dateTimeStr.trim().isEmpty()) {
|
||||
throw new IllegalArgumentException("日期时间字符串不能为空");
|
||||
}
|
||||
LocalDateTime localDateTime = timeFormat(dateTimeStr, FORMATTER);
|
||||
if (localDateTime == null) {
|
||||
throw new IllegalArgumentException("无法解析日期时间字符串: " + dateTimeStr);
|
||||
}
|
||||
return getMillis(localDateTime);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取LocalDateTime的所对应的毫秒数
|
||||
* 获取LocalDateTime对应的UTC毫秒时间戳
|
||||
* <p>
|
||||
* 将LocalDateTime对象转换为UTC时区的毫秒时间戳。
|
||||
*
|
||||
* @param localDateTime
|
||||
* @return
|
||||
* @param localDateTime 本地日期时间对象
|
||||
* @return UTC时区的毫秒时间戳
|
||||
*/
|
||||
public static long getMillis(LocalDateTime localDateTime) {
|
||||
if (localDateTime == null) {
|
||||
throw new IllegalArgumentException("LocalDateTime参数不能为null");
|
||||
}
|
||||
return localDateTime.atZone(ZoneId.of("UTC")).toInstant().toEpochMilli();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断value是否为0
|
||||
* @param value
|
||||
* @return
|
||||
* 判断数值是否为零(在容差范围内)
|
||||
* <p>
|
||||
* 使用BigDecimal进行精确计算,避免浮点数精度问题。
|
||||
* 当数值的绝对值小于预设阈值(0.01)时,认为该数值为零。
|
||||
* 主要用于电流等物理量的零值判断。
|
||||
*
|
||||
* @param value 待判断的数值,null值被认为是零
|
||||
* @param ratedCurrent 额定电流,用于计算阈值
|
||||
* @return true表示数值为零(在容差范围内),false表示非零
|
||||
*/
|
||||
public static boolean isZero(Double value) {
|
||||
BigDecimal bd = BigDecimal.valueOf(value);
|
||||
if(bd.subtract(BigDecimal.ZERO).abs().compareTo(BigDecimal.valueOf(0.001)) < 0){
|
||||
public static boolean isZero(Double value, Double ratedCurrent) {
|
||||
if (value == null) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
double threshold = 0.01 * ratedCurrent;
|
||||
BigDecimal bd = BigDecimal.valueOf(value);
|
||||
return bd.subtract(BigDecimal.ZERO).abs().compareTo(BigDecimal.valueOf(threshold)) < 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取CP95值
|
||||
* 获取CP95分位数值
|
||||
* <p>
|
||||
* CP95表示95%分位数,即有95%的数据小于等于此值。
|
||||
* 算法逻辑:
|
||||
* <ul>
|
||||
* <li>数据量=1时:返回该数据</li>
|
||||
* <li>数据量<21时:返回第2个数据(索引1)</li>
|
||||
* <li>数据量≥21时:计算5%位置的数据(适用于从大到小排序的数据)</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param t
|
||||
* @return
|
||||
* @param t 已排序的数据列表(从大到小排序)
|
||||
* @return CP95分位数值列表,包含单个元素
|
||||
*/
|
||||
public static List<Double> getCP95Doubles(List<Double> t) {
|
||||
if (CollUtil.isNotEmpty(t)) {
|
||||
if (t.size() < 21) {
|
||||
if (t.size() == 1) {
|
||||
return t;
|
||||
}
|
||||
if (t.size() > 1) {
|
||||
return t.subList(1, 2);
|
||||
}
|
||||
} else {
|
||||
int v = (int) (t.size() * 0.5);
|
||||
return t.subList(v, v + 1);
|
||||
}
|
||||
if (CollUtil.isEmpty(t)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
return t;
|
||||
|
||||
// 单个数据直接返回
|
||||
if (t.size() == 1) {
|
||||
return new ArrayList<>(t);
|
||||
}
|
||||
|
||||
// 数据量较少时,取第2个数据作为CP95值
|
||||
if (t.size() < CP95_DATA_SIZE_THRESHOLD) {
|
||||
return t.subList(CP95_SMALL_DATA_INDEX, CP95_SMALL_DATA_INDEX + 1);
|
||||
}
|
||||
|
||||
// 数据量充足时,计算真正的95%分位数
|
||||
// 由于数据已从大到小排序,95%分位数位于5%位置
|
||||
int cp95Index = (int) Math.ceil(t.size() * CP95_PERCENTILE) - 1;
|
||||
return t.subList(cp95Index, cp95Index + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取CP95值所在索引
|
||||
* 获取CP95分位数值在列表中的索引位置
|
||||
* <p>
|
||||
* 计算CP95分位数在已排序列表中的索引位置。
|
||||
* 索引计算规则与getCP95Doubles方法保持一致。
|
||||
*
|
||||
* @param t
|
||||
* @return
|
||||
* @param t 已排序的数据列表(从大到小排序)
|
||||
* @return CP95分位数的索引位置,列表为空时返回-1
|
||||
*/
|
||||
public static int getCP95Idx(List<Double> t) {
|
||||
if (CollUtil.isNotEmpty(t)) {
|
||||
if (t.size() < 21) {
|
||||
if (t.size() == 1) {
|
||||
return 0;
|
||||
}
|
||||
if (t.size() > 1) {
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
int v = (int) (t.size() * 0.5);
|
||||
return v;
|
||||
}
|
||||
if (CollUtil.isEmpty(t)) {
|
||||
return -1;
|
||||
}
|
||||
return -1;
|
||||
|
||||
// 单个数据返回索引0
|
||||
if (t.size() == 1) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 数据量较少时,返回索引1
|
||||
if (t.size() < CP95_DATA_SIZE_THRESHOLD) {
|
||||
return CP95_SMALL_DATA_INDEX;
|
||||
}
|
||||
|
||||
// 数据量充足时,计算95%分位数的索引位置
|
||||
// 由于数据已从大到小排序,95%分位数索引为5%位置
|
||||
return (int) Math.ceil(t.size() * CP95_PERCENTILE) - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取部分值
|
||||
* 获取部分值(去除最大最小值后的数据)
|
||||
* <p>
|
||||
* 用于数据预处理,去除可能的异常值。
|
||||
* 算法逻辑:
|
||||
* <ul>
|
||||
* <li>数据量≤2时:返回原数据副本</li>
|
||||
* <li>数据量>2时:移除一个最大值和一个最小值后返回剩余数据</li>
|
||||
* </ul>
|
||||
* 注意:该方法不会修改原始列表,而是返回新的列表。
|
||||
*
|
||||
* @param t
|
||||
* @return
|
||||
* @param t 原始数据列表
|
||||
* @return 去除最大最小值后的数据列表副本
|
||||
*/
|
||||
public static List<Double> getSectionValueDoubles(List<Double> t) {
|
||||
if (CollUtil.isNotEmpty(t)) {
|
||||
if (t.size() > 2) {
|
||||
Double max = Collections.max(t);
|
||||
Double min = Collections.min(t);
|
||||
t.remove(max);
|
||||
t.remove(min);
|
||||
}
|
||||
if (CollUtil.isEmpty(t) || t.size() <= MIN_DATA_SIZE_FOR_SECTION_VALUE) {
|
||||
return new ArrayList<>(t);
|
||||
}
|
||||
return t;
|
||||
|
||||
// 创建副本避免修改原始列表
|
||||
List<Double> result = new ArrayList<>(t);
|
||||
Double max = Collections.max(result);
|
||||
Double min = Collections.min(result);
|
||||
result.remove(max);
|
||||
result.remove(min);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取平均值
|
||||
* 计算数据列表的算术平均值
|
||||
* <p>
|
||||
* 对输入的数值列表计算算术平均值,并以单元素列表形式返回。
|
||||
* 空列表会返回空列表。
|
||||
*
|
||||
* @param t
|
||||
* @return
|
||||
* @param t 数值列表
|
||||
* @return 包含平均值的单元素列表,输入为空时返回空列表
|
||||
*/
|
||||
public static List<Double> getAvgDoubles(List<Double> t) {
|
||||
if (CollUtil.isNotEmpty(t)) {
|
||||
t = Arrays.asList(t.stream().mapToDouble(Double::doubleValue).average().orElse(0.0));
|
||||
if (CollUtil.isEmpty(t)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
return t;
|
||||
|
||||
// 计算列表中所有数值的算术平均值
|
||||
double average = t.stream().mapToDouble(Double::doubleValue).average().orElse(0.0);
|
||||
// 将平均值包装为单元素列表返回
|
||||
return Collections.singletonList(average);
|
||||
}
|
||||
|
||||
/**
|
||||
* 对list进行从大到小排序,并返回排序后的索引序列
|
||||
* 对数据列表进行排序并返回原始索引序列
|
||||
* <p>
|
||||
* 使用选择排序算法对列表进行排序,同时跟踪每个元素的原始索引位置。
|
||||
* 这样可以在数据排序后仍然知道每个数据在原始列表中的位置。
|
||||
*
|
||||
* @param list
|
||||
* @return
|
||||
* <b>注意:</b>该方法会直接修改输入的列表。
|
||||
*
|
||||
* @param list 待排序的数据列表(会被直接修改)
|
||||
* @param isAsc 排序方式,true为升序,false为降序
|
||||
* @return 排序后各元素在原始列表中的索引位置
|
||||
*/
|
||||
public static List<Integer> sort(List<Double> list) {
|
||||
public static List<Integer> sort(List<Double> list, Boolean isAsc) {
|
||||
if (CollUtil.isEmpty(list)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
if (isAsc == null) {
|
||||
throw new IllegalArgumentException("排序方式参数不能为null");
|
||||
}
|
||||
// 创建索引列表,记录每个元素的原始位置
|
||||
List<Integer> indexList = Stream.iterate(0, i -> i + 1).limit(list.size()).collect(Collectors.toList());
|
||||
// 使用选择排序算法,同时维护索引映射
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
int maxIdx = i;
|
||||
// 当前轮次要放置的目标位置
|
||||
int targetIdx = i;
|
||||
// 在未排序部分寻找最值
|
||||
for (int j = i + 1; j < list.size(); j++) {
|
||||
if (list.get(j) > list.get(maxIdx)) {
|
||||
maxIdx = j;
|
||||
if (isAsc) {
|
||||
// 升序:寻找最小值
|
||||
if (list.get(j) < list.get(targetIdx)) {
|
||||
targetIdx = j;
|
||||
}
|
||||
} else {
|
||||
// 降序:寻找最大值
|
||||
if (list.get(j) > list.get(targetIdx)) {
|
||||
targetIdx = j;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (maxIdx != i) {
|
||||
// 交换数据值和对应的索引
|
||||
if (targetIdx != i) {
|
||||
// 交换数据值
|
||||
double temp = list.get(i);
|
||||
list.set(i, list.get(maxIdx));
|
||||
list.set(maxIdx, temp);
|
||||
list.set(i, list.get(targetIdx));
|
||||
list.set(targetIdx, temp);
|
||||
|
||||
// 交换对应的原始索引
|
||||
int tempIdx = indexList.get(i);
|
||||
indexList.set(i, indexList.get(maxIdx));
|
||||
indexList.set(maxIdx, tempIdx);
|
||||
indexList.set(i, indexList.get(targetIdx));
|
||||
indexList.set(targetIdx, tempIdx);
|
||||
}
|
||||
}
|
||||
return indexList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据idxList索引列表从oldList中获取新数组
|
||||
*
|
||||
* @param oldList
|
||||
* @param idxList
|
||||
* @return
|
||||
*/
|
||||
public static List<Double> getNewArray(List<Double> oldList, List<Integer> idxList) {
|
||||
if (CollUtil.isNotEmpty(oldList) && CollUtil.isNotEmpty(idxList)) {
|
||||
if (CollUtil.max(idxList) > oldList.size() - 1 || CollUtil.min(idxList) < 0) {
|
||||
return null;
|
||||
}
|
||||
List<Double> newList = new ArrayList<>();
|
||||
for (int i = 0; i < idxList.size(); i++) {
|
||||
newList.add(oldList.get(idxList.get(i)));
|
||||
}
|
||||
return newList;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查文件是否存在
|
||||
*/
|
||||
public static void checkFileExists(String filePath, String description) {
|
||||
File file = new File(filePath);
|
||||
if (!file.exists()) {
|
||||
System.err.println("警告: " + description + " 不存在: " + filePath);
|
||||
System.err.println("请确保文件路径正确,或修改测试中的文件路径");
|
||||
} else {
|
||||
System.out.println(description + " 存在: " + filePath);
|
||||
System.out.println(" 文件大小: " + file.length() + " bytes");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,56 +28,52 @@ public class DetectionCommunicateUtil {
|
||||
Channel channelSource = SocketManager.getChannelByUserId(param.getUserPageId() + DetectionCommunicateConstant.SOURCE);
|
||||
Channel channelDev = SocketManager.getChannelByUserId(param.getUserPageId() + DetectionCommunicateConstant.DEV);
|
||||
|
||||
if (Objects.nonNull(channelSource) && channelSource.isActive()) {
|
||||
System.out.println("发送关闭源指令。。。。。。。。");
|
||||
CnSocketUtil.quitSendSource(param);
|
||||
}
|
||||
if (Objects.nonNull(channelDev) && channelDev.isActive()) {
|
||||
System.out.println("发送关闭设备通讯指令。。。。。。。。");
|
||||
CnSocketUtil.quitSend(param);
|
||||
boolean channelSourceActive = channelSource != null && channelSource.isActive();
|
||||
boolean channelDevActive = channelDev != null && channelDev.isActive();
|
||||
if(channelSourceActive || channelDevActive){
|
||||
if(channelSourceActive){
|
||||
System.out.println("发送关闭源指令。。。。。。。。");
|
||||
CnSocketUtil.quitSendSource(param);
|
||||
}
|
||||
|
||||
if(channelDevActive){
|
||||
System.out.println("发送关闭设备通讯指令。。。。。。。。");
|
||||
CnSocketUtil.quitSend(param);
|
||||
}
|
||||
// 休眠4秒
|
||||
try {
|
||||
Thread.sleep(4000);
|
||||
} catch (InterruptedException e) {
|
||||
log.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
Thread.sleep(4000);
|
||||
} catch (InterruptedException e) {
|
||||
log.error(e.getMessage());
|
||||
}
|
||||
|
||||
SocketManager.removeUser(param.getUserPageId() + DetectionCommunicateConstant.SOURCE);
|
||||
SocketManager.removeUser(param.getUserPageId() + DetectionCommunicateConstant.DEV);
|
||||
|
||||
try {
|
||||
Thread.sleep(2000);
|
||||
} catch (InterruptedException e) {
|
||||
log.error(e.getMessage());
|
||||
}
|
||||
// SocketManager.removeUser(param.getUserPageId() + DetectionCommunicateConstant.SOURCE);
|
||||
// SocketManager.removeUser(param.getUserPageId() + DetectionCommunicateConstant.DEV);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 比对式-检测是否存在已有的Socket通道,有则强行关闭
|
||||
*
|
||||
* @param userId
|
||||
* @param loginName
|
||||
*/
|
||||
public static void checkContrastCommunicateChannel(String userId) {
|
||||
Channel channel = SocketManager.getChannelByUserId(userId);
|
||||
|
||||
if (Objects.nonNull(channel) && channel.isActive()) {
|
||||
System.out.println("存在已有的Socket通道,强行关闭。。。。。。。。");
|
||||
CnSocketUtil.contrastSendquit(userId);
|
||||
}
|
||||
|
||||
try {
|
||||
Thread.sleep(4000);
|
||||
} catch (InterruptedException e) {
|
||||
log.error(e.getMessage());
|
||||
}
|
||||
|
||||
SocketManager.removeUser(userId);
|
||||
try {
|
||||
Thread.sleep(2000);
|
||||
} catch (InterruptedException e) {
|
||||
log.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
// public static void checkContrastCommunicateChannel(String loginName) {
|
||||
// Channel channel = SocketManager.getChannelByUserId(loginName + CnSocketUtil.CONTRAST_DEV_TAG);
|
||||
//
|
||||
// if (Objects.nonNull(channel) && channel.isActive()) {
|
||||
// System.out.println("存在已有的Socket通道,强行关闭。。。。。。。。");
|
||||
// CnSocketUtil.contrastSendquit(loginName);
|
||||
// SocketManager.removeUser(loginName + CnSocketUtil.CONTRAST_DEV_TAG);
|
||||
// try {
|
||||
// Thread.sleep(4000);
|
||||
// } catch (InterruptedException e) {
|
||||
// log.error(e.getMessage());
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import com.alibaba.fastjson.JSONObject;
|
||||
import com.njcn.gather.detection.pojo.enums.SourceOperateCodeEnum;
|
||||
import com.njcn.gather.detection.pojo.param.PreDetectionParam;
|
||||
import com.njcn.gather.detection.pojo.vo.SocketMsg;
|
||||
import com.njcn.gather.detection.pojo.vo.WebSocketVO;
|
||||
import com.njcn.gather.detection.util.socket.websocket.WebServiceManager;
|
||||
|
||||
/**
|
||||
* @Author: cdf
|
||||
@@ -16,6 +16,8 @@ public class CnSocketUtil {
|
||||
|
||||
public final static String DEV_TAG = "_Dev";
|
||||
|
||||
public final static String CONTRAST_DEV_TAG = "_Contrast_Dev";
|
||||
|
||||
public final static String SOURCE_TAG = "_Source";
|
||||
|
||||
public final static String START_TAG = "_Start";
|
||||
@@ -34,7 +36,7 @@ public class CnSocketUtil {
|
||||
socketMsg.setRequestId(SourceOperateCodeEnum.QUITE.getValue());
|
||||
socketMsg.setOperateCode(SourceOperateCodeEnum.QUIT_INIT_03.getValue());
|
||||
SocketManager.sendMsg(param.getUserPageId() + DEV_TAG, JSON.toJSONString(socketMsg));
|
||||
WebServiceManager.removePreDetectionParam();
|
||||
WebServiceManager.removePreDetectionParam(param.getUserPageId());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -48,43 +50,21 @@ public class CnSocketUtil {
|
||||
jsonObject.put("sourceId", param.getSourceId());
|
||||
socketMsg.setData(jsonObject.toJSONString());
|
||||
SocketManager.sendMsg(param.getUserPageId() + SOURCE_TAG, JSON.toJSONString(socketMsg));
|
||||
WebServiceManager.removePreDetectionParam();
|
||||
}
|
||||
|
||||
/**
|
||||
* 推送webSocket数据
|
||||
*/
|
||||
public static void sendToWebSocket(String userId, String requestId, String operatorType, Object data, String desc) {
|
||||
WebSocketVO<Object> webSocketVO = new WebSocketVO<>();
|
||||
webSocketVO.setRequestId(requestId);
|
||||
webSocketVO.setOperateCode(operatorType);
|
||||
webSocketVO.setData(data);
|
||||
webSocketVO.setDesc(desc);
|
||||
WebServiceManager.sendMessage(userId, webSocketVO);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 推送未知异常的webSocket数据
|
||||
*/
|
||||
public static void sendUnSocket(String userId) {
|
||||
WebSocketVO<Object> webSocketVO = new WebSocketVO<>();
|
||||
webSocketVO.setRequestId(SourceOperateCodeEnum.UNKNOWN_OPERATE.getValue());
|
||||
webSocketVO.setData(SourceOperateCodeEnum.UNKNOWN_OPERATE.getMsg());
|
||||
webSocketVO.setOperateCode(SourceOperateCodeEnum.UNKNOWN_OPERATE.getMsg());
|
||||
WebServiceManager.sendMessage(userId, webSocketVO);
|
||||
WebServiceManager.removePreDetectionParam(param.getUserPageId());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 比对式-退出检测
|
||||
*/
|
||||
public static void contrastSendquit(String userId) {
|
||||
System.out.println("比对式-发送关闭备通讯模块指令。。。。。。。。");
|
||||
public static void contrastSendquit(String loginName, SourceOperateCodeEnum operateCode, boolean isRemoveSocket) {
|
||||
System.out.println("比对式-发送" + operateCode.getMsg() + "指令。。。。。。。。");
|
||||
SocketMsg<String> socketMsg = new SocketMsg<>();
|
||||
socketMsg.setRequestId(SourceOperateCodeEnum.QUITE.getValue());
|
||||
socketMsg.setOperateCode(SourceOperateCodeEnum.QUIT_INIT_03.getValue());
|
||||
SocketManager.sendMsg(userId + DEV_TAG, JSON.toJSONString(socketMsg));
|
||||
WebServiceManager.removePreDetectionParam();
|
||||
socketMsg.setOperateCode(operateCode.getValue());
|
||||
SocketManager.sendMsg(loginName + CONTRAST_DEV_TAG, JSON.toJSONString(socketMsg));
|
||||
// WebServiceManager.removePreDetectionParam();
|
||||
FormalTestManager.isRemoveSocket = isRemoveSocket;
|
||||
FormalTestManager.currentStep = SourceOperateCodeEnum.QUITE;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
package com.njcn.gather.detection.util.socket;
|
||||
|
||||
import com.google.common.collect.HashBiMap;
|
||||
import com.njcn.gather.detection.pojo.dto.WaveResultDTO;
|
||||
import com.njcn.gather.detection.pojo.enums.SourceOperateCodeEnum;
|
||||
import com.njcn.gather.detection.pojo.po.DevData;
|
||||
import com.njcn.gather.device.pojo.po.PqStandardDev;
|
||||
import com.njcn.gather.detection.pojo.vo.DevLineTestResult;
|
||||
import com.njcn.gather.device.pojo.enums.PatternEnum;
|
||||
import com.njcn.gather.device.pojo.vo.PreDetection;
|
||||
import com.njcn.gather.plan.pojo.enums.DataSourceEnum;
|
||||
import com.njcn.gather.plan.pojo.po.AdPlan;
|
||||
import com.njcn.gather.plan.pojo.po.AdPlanTestConfig;
|
||||
import com.njcn.gather.script.pojo.po.SourceIssue;
|
||||
import com.njcn.gather.system.dictionary.pojo.enums.DictDataEnum;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -12,6 +18,8 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
|
||||
/**
|
||||
* @Author: cdf
|
||||
@@ -20,9 +28,11 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
*/
|
||||
public class FormalTestManager {
|
||||
|
||||
// 当前步骤
|
||||
public static SourceOperateCodeEnum currentStep;
|
||||
|
||||
/**
|
||||
* key:设备ip,value:当前设备下面的通道号
|
||||
* key:设备ip,value:当前设备下面的监测点ID(ip_通道号)
|
||||
*/
|
||||
public static Map<String, List<String>> devMapMonitorNum = new ConcurrentHashMap<>();
|
||||
|
||||
@@ -42,7 +52,7 @@ public class FormalTestManager {
|
||||
public static List<String> monitorIdListComm = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 所有参与检测的监测点。key:监测点id(ip_通道号),value:检测点实体
|
||||
* 所有参与检测的监测点。key:监测点(ip_通道号),value:检测点实体
|
||||
*/
|
||||
public static Map<String, PreDetection.MonitorListDTO> monitorMap = new HashMap<>();
|
||||
|
||||
@@ -59,7 +69,7 @@ public class FormalTestManager {
|
||||
/**
|
||||
* key:设备ip,value:装置id
|
||||
*/
|
||||
public static Map<String, String> devIdMapComm = new HashMap<>();
|
||||
public static HashBiMap<String, String> devIdMapComm = HashBiMap.create();
|
||||
|
||||
/**
|
||||
* 停止触发标识
|
||||
@@ -76,20 +86,33 @@ public class FormalTestManager {
|
||||
*/
|
||||
public static Integer stopTime = 0;
|
||||
|
||||
/**
|
||||
* 强行赋值关系
|
||||
*/
|
||||
public static Map<String, String> harmonicRelationMap = new HashMap<>();
|
||||
|
||||
/**
|
||||
* 当前正在检测的计划
|
||||
*/
|
||||
public static AdPlan currentTestPlan;
|
||||
|
||||
/**
|
||||
* 比对式检测-检测项。
|
||||
* 非录波数据源枚举
|
||||
*/
|
||||
public static List<String> testItemCodeList = new ArrayList<>();
|
||||
public static DataSourceEnum nonWaveDataSourceEnum;
|
||||
|
||||
|
||||
/**
|
||||
* 统计数据最大超时时间
|
||||
*/
|
||||
public static Long maxTime;
|
||||
|
||||
public static AdPlanTestConfig curretntTestPlanConfig;
|
||||
|
||||
/**
|
||||
* 当前正在检测的模式
|
||||
*/
|
||||
public static PatternEnum patternEnum;
|
||||
|
||||
/**
|
||||
* 比对式检测-检测项。key为检测项code,value为检测项id
|
||||
*/
|
||||
public static Map<String, String> testItemMap = new HashMap<>();
|
||||
|
||||
/**
|
||||
* 数据处理原则
|
||||
@@ -97,9 +120,14 @@ public class FormalTestManager {
|
||||
public static DictDataEnum dataRule;
|
||||
|
||||
/**
|
||||
* 所有参与比对式检测的被检设备、标准设备配对关系。key:标准设备id_通道号,value:被检设备id_通道号
|
||||
* 所有参与比对式检测的被检设备、标准设备配对关系。key:被检设备ip_通道号,value:标准设备ip_通道号
|
||||
*/
|
||||
public static Map<String, String> pairsMap = HashBiMap.create();
|
||||
public static HashBiMap<String, String> pairsIpMap = HashBiMap.create();
|
||||
|
||||
/**
|
||||
* 所有参与比对式检测的被检设备、标准设备配对关系。key:被检设备id_通道号,value:标准设备id_通道号
|
||||
*/
|
||||
public static HashBiMap<String, String> pairsIdMap = HashBiMap.create();
|
||||
|
||||
/**
|
||||
* 被检设备的数据。key:设备ip_通道号,value:DevData数据集合
|
||||
@@ -110,4 +138,69 @@ public class FormalTestManager {
|
||||
* 标准设备的数据。key:设备ip_通道号,value:DevData数据集合
|
||||
*/
|
||||
public static Map<String, List<DevData>> standardDevDataMap = new HashMap<>();
|
||||
|
||||
/**
|
||||
* 是否要移除和通信模块的socket连接
|
||||
*/
|
||||
public static Boolean isRemoveSocket;
|
||||
|
||||
/**
|
||||
* 录波功能校验
|
||||
*/
|
||||
public static Boolean waveCheckFlag;
|
||||
|
||||
/**
|
||||
* 第几次监测 key为设备监测点id,value为第几次监测
|
||||
*/
|
||||
public static Map<String, Integer> numMap = new HashMap<>();
|
||||
|
||||
/**
|
||||
* 存放录波相关数据。key:设备ip_通道号,value:WaveResultDTO数据
|
||||
*/
|
||||
public static Map<String, WaveResultDTO> waveResultDTOMap = new HashMap<>();
|
||||
|
||||
/**
|
||||
* 录波组数
|
||||
*/
|
||||
public static Integer waveNum;
|
||||
|
||||
/**
|
||||
* 每次录波检测结果
|
||||
*/
|
||||
public static List<DevLineTestResult> preNumTestResultList = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 定时器
|
||||
*/
|
||||
public static ScheduledExecutorService scheduler;
|
||||
|
||||
/**
|
||||
* 定时器任务
|
||||
*/
|
||||
public static ScheduledFuture<?> scheduledFuture;
|
||||
|
||||
|
||||
public static boolean isWaveCheck;
|
||||
|
||||
public static List<String> pstDataType;
|
||||
|
||||
public static boolean isPstData;
|
||||
|
||||
/**
|
||||
* 是否在检测中
|
||||
*/
|
||||
public static boolean isTesting;
|
||||
|
||||
public static boolean realProtocol;
|
||||
|
||||
public static boolean statisticsProtocol;
|
||||
|
||||
public static boolean voltageProtocol;
|
||||
|
||||
public static boolean unknownError;
|
||||
|
||||
/**
|
||||
* 当前下发的脚本
|
||||
*/
|
||||
public static SourceIssue currentIssue;
|
||||
}
|
||||
|
||||
@@ -65,4 +65,44 @@ public class MsgUtil {
|
||||
}
|
||||
return JSON.toJSONString(socketDataMsg);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取一组监测点配对的字符串
|
||||
*
|
||||
* @param devMonitorId
|
||||
* @param standardDevId
|
||||
* @param devMap key为设备ip,value为设备名称
|
||||
* @return
|
||||
*/
|
||||
public static String getPairStr(String devMonitorId, String standardDevId, Map<String, String> devMap) {
|
||||
if (StrUtil.isBlank(devMonitorId) || StrUtil.isBlank(standardDevId)) {
|
||||
return "";
|
||||
} else {
|
||||
String[] split1 = devMonitorId.split("_");
|
||||
String[] split2 = standardDevId.split("_");
|
||||
return "被检设备\"" + devMap.get(split1[0]) + CnSocketUtil.SPLIT_TAG + split1[1] + "路\"" + " -> 标准设备\"" + devMap.get(split2[0]) + CnSocketUtil.SPLIT_TAG + split2[1] + "路\"";
|
||||
}
|
||||
}
|
||||
|
||||
public static String getMonitorInfo(String monitorId, Map<String, String> devMap) {
|
||||
if (StrUtil.isBlank(monitorId)) {
|
||||
return "";
|
||||
} else {
|
||||
String[] split1 = monitorId.split("_");
|
||||
return devMap.get(split1[0]) + CnSocketUtil.SPLIT_TAG + split1[1] + "路\"";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取消息
|
||||
*
|
||||
* @param monitorId 监测点id
|
||||
* @param devMap key为设备ip,value为设备名称
|
||||
* @param appendMsg 附加的消息
|
||||
* @return
|
||||
*/
|
||||
public static String getMsg(String monitorId, Map<String, String> devMap, String appendMsg) {
|
||||
String[] split1 = monitorId.split("_");
|
||||
return "\"" + devMap.get(split1[0]) + CnSocketUtil.SPLIT_TAG + "第" + split1[1] + "路\"" + appendMsg;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,50 @@
|
||||
package com.njcn.gather.detection.util.socket;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.njcn.gather.detection.pojo.param.ContrastDetectionParam;
|
||||
import com.njcn.gather.detection.pojo.param.PreDetectionParam;
|
||||
import com.njcn.gather.detection.util.socket.cilent.NettyClient;
|
||||
import com.njcn.gather.detection.util.socket.cilent.NettyContrastClientHandler;
|
||||
import com.njcn.gather.detection.util.socket.config.SocketConnectionConfig;
|
||||
import com.njcn.gather.plan.pojo.enums.DataSourceEnum;
|
||||
import com.njcn.gather.script.pojo.po.SourceIssue;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
/**
|
||||
* Socket连接管理器
|
||||
* 提供Socket连接的生命周期管理、消息发送、检测任务管理等功能
|
||||
* <p>
|
||||
* 包含以下主要功能:
|
||||
* 1. 基础连接管理:addUser, removeUser, sendMsg等
|
||||
* 2. 智能消息发送:smartSendToSource, smartSendToDevice等(新增)
|
||||
* 3. 检测任务管理:targetMap, sourceIssueList等管理
|
||||
* 4. 连接状态监控:getConnectionStatus等(新增)
|
||||
*
|
||||
* @Description: webSocket存储的通道
|
||||
* @Author: wr
|
||||
* @Author: wr, hongawen
|
||||
* @Date: 2024/12/11 13:04
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class SocketManager {
|
||||
|
||||
@Resource
|
||||
private SocketConnectionConfig socketConnectionConfig;
|
||||
|
||||
/**
|
||||
* key为userId(xxx_Source、xxx_Dev),value为channel
|
||||
*/
|
||||
@@ -34,24 +60,25 @@ public class SocketManager {
|
||||
}
|
||||
|
||||
public static void addGroup(String userId, NioEventLoopGroup group) {
|
||||
socketGroup.put(userId, group);
|
||||
socketGroup.put(userId, group);
|
||||
}
|
||||
|
||||
public static void removeUser(String userId) {
|
||||
Channel channel = socketSessions.get(userId);
|
||||
if(ObjectUtil.isNotNull(channel)){
|
||||
if (ObjectUtil.isNotNull(channel)) {
|
||||
try {
|
||||
channel.close().sync();
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
NioEventLoopGroup eventExecutors = socketGroup.get(userId);
|
||||
if(ObjectUtil.isNotNull(channel)){
|
||||
if (ObjectUtil.isNotNull(eventExecutors)) {
|
||||
eventExecutors.shutdownGracefully();
|
||||
System.out.println(userId+"__"+channel.id()+"关闭了客户端");
|
||||
System.out.println(userId + "__" + channel.id() + "关闭了客户端");
|
||||
}
|
||||
}
|
||||
socketSessions.remove(userId);
|
||||
socketGroup.remove(userId);
|
||||
}
|
||||
|
||||
public static Channel getChannelByUserId(String userId) {
|
||||
@@ -62,15 +89,170 @@ public class SocketManager {
|
||||
return socketGroup.get(userId);
|
||||
}
|
||||
|
||||
public static void sendMsg(String userId,String msg) {
|
||||
public static void sendMsg(String userId, String msg) {
|
||||
Channel channel = socketSessions.get(userId);
|
||||
if(ObjectUtil.isNotNull(channel)){
|
||||
channel.writeAndFlush(msg+'\n');
|
||||
System.out.println(userId+"__"+channel.id()+"往"+channel.remoteAddress()+"发送数据:"+msg);
|
||||
}else{
|
||||
System.out.println(userId+"__发送数据:失败通道不存在"+msg);
|
||||
if (ObjectUtil.isNotNull(channel)) {
|
||||
channel.writeAndFlush(msg + '\n');
|
||||
log.info("{}__{}往{}发送数据:{}", userId, channel.id(), channel.remoteAddress(), msg);
|
||||
} else {
|
||||
log.warn("{}__发送数据:失败通道不存在{}", userId, msg);
|
||||
}
|
||||
}
|
||||
|
||||
// =================== 智能发送功能 ===================
|
||||
|
||||
/**
|
||||
* 智能发送消息到程控源设备
|
||||
* 自动从配置文件读取IP和PORT,开发者无需关心网络配置
|
||||
* 如果连接不存在且requestId需要建立连接,会自动建立连接后发送
|
||||
*
|
||||
* @param param 检测参数,包含用户页面ID等信息
|
||||
* @param msg 要发送的消息内容(JSON格式,包含requestId字段)
|
||||
*/
|
||||
public void smartSendToSource(PreDetectionParam param, String msg) {
|
||||
String requestId = extractRequestId(msg);
|
||||
String userId = param.getUserPageId() + CnSocketUtil.SOURCE_TAG;
|
||||
// 检查是否需要建立连接
|
||||
if (SocketConnectionConfig.needsSourceConnection(requestId)) {
|
||||
String ip = socketConnectionConfig.getSource().getIp();
|
||||
Integer port = socketConnectionConfig.getSource().getPort();
|
||||
// 检查连接是否存在且活跃
|
||||
if (!isChannelActive(userId)) {
|
||||
log.info("程控源连接不存在,自动建立连接: userId={}, requestId={}", userId, requestId);
|
||||
// 异步建立程控源连接并发送消息
|
||||
CompletableFuture.runAsync(() -> {
|
||||
NettyClient.connectToSourceStatic(ip, port, param, msg);
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 连接已存在或不需要建立连接,直接发送消息
|
||||
log.info("直接发送消息到程控源: userId={}, requestId={}", userId, requestId);
|
||||
sendMsg(userId, msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* 智能发送消息到被检设备
|
||||
* 自动从配置文件读取IP和PORT,开发者无需关心网络配置
|
||||
* 如果连接不存在且requestId需要建立连接,会自动建立连接后发送
|
||||
*
|
||||
* @param param 检测参数,包含用户页面ID等信息
|
||||
* @param msg 要发送的消息内容(JSON格式,包含requestId字段)
|
||||
*/
|
||||
public void smartSendToDevice(PreDetectionParam param, String msg) {
|
||||
String requestId = extractRequestId(msg);
|
||||
String userId = param.getUserPageId() + CnSocketUtil.DEV_TAG;
|
||||
// 检查是否需要建立连接
|
||||
if (SocketConnectionConfig.needsDeviceConnection(requestId)) {
|
||||
String ip = socketConnectionConfig.getDevice().getIp();
|
||||
Integer port = socketConnectionConfig.getDevice().getPort();
|
||||
// 检查连接是否存在且活跃
|
||||
if (!isChannelActive(userId)) {
|
||||
log.info("被检设备连接不存在,自动建立连接: userId={}, requestId={}", userId, requestId);
|
||||
// 异步建立被检设备连接并发送消息
|
||||
CompletableFuture.runAsync(() -> {
|
||||
NettyClient.connectToDeviceStatic(ip, port, param, msg);
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 连接已存在或不需要建立连接,直接发送消息
|
||||
log.info("直接发送消息到被检设备: userId={}, requestId={}", userId, requestId);
|
||||
sendMsg(userId, msg);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 比对智能发送消息到被检设备
|
||||
* 自动从配置文件读取IP和PORT,开发者无需关心网络配置
|
||||
* 如果连接不存在且requestId需要建立连接,会自动建立连接后发送
|
||||
*
|
||||
* @param param 检测参数,包含用户页面ID等信息
|
||||
* @param msg 要发送的消息内容(JSON格式,包含requestId字段)
|
||||
*/
|
||||
public void smartSendToContrast(ContrastDetectionParam param, String msg) {
|
||||
String requestId = extractRequestId(msg);
|
||||
String userId = param.getLoginName() + CnSocketUtil.CONTRAST_DEV_TAG;
|
||||
// 检查是否需要建立连接
|
||||
if (SocketConnectionConfig.needsDeviceConnection(requestId)) {
|
||||
String ip = socketConnectionConfig.getDevice().getIp();
|
||||
Integer port = socketConnectionConfig.getDevice().getPort();
|
||||
// 检查连接是否存在且活跃
|
||||
if (!isChannelActive(userId)) {
|
||||
log.info("比对被检设备连接不存在,自动建立连接: userId={}, requestId={}", userId, requestId);
|
||||
// 异步建立比对被检设备连接并发送消息
|
||||
CompletableFuture.runAsync(() -> {
|
||||
NettyClient.connectToContrastDeviceStatic(ip, port, param, msg);
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
PreDetectionParam preDetectionParam = new PreDetectionParam();
|
||||
preDetectionParam.setUserPageId(param.getLoginName());
|
||||
preDetectionParam.setTestItemList(param.getTestItemList());
|
||||
preDetectionParam.setDevIds(param.getDevIds());
|
||||
preDetectionParam.setUserId(param.getUserId());
|
||||
NettyContrastClientHandler.param = preDetectionParam;
|
||||
}
|
||||
}
|
||||
|
||||
// 连接已存在或不需要建立连接,直接发送消息
|
||||
log.info("直接发送消息到比对被检设备: userId={}, requestId={}", userId, requestId);
|
||||
sendMsg(userId, msg);
|
||||
}
|
||||
|
||||
|
||||
// =================== 私有工具方法 ===================
|
||||
|
||||
/**
|
||||
* 从消息中提取requestId
|
||||
* 支持JSON格式的消息解析
|
||||
*
|
||||
* @param msg 消息内容
|
||||
* @return String requestId,如果解析失败返回"unknown"
|
||||
*/
|
||||
private static String extractRequestId(String msg) {
|
||||
try {
|
||||
if (StrUtil.isNotBlank(msg)) {
|
||||
// 尝试解析JSON格式消息
|
||||
JSONObject jsonObject = JSON.parseObject(msg);
|
||||
String requestId = jsonObject.getString("requestId");
|
||||
if (StrUtil.isNotBlank(requestId)) {
|
||||
return requestId;
|
||||
}
|
||||
|
||||
// 如果没有requestId字段,尝试解析request_id字段
|
||||
requestId = jsonObject.getString("request_id");
|
||||
if (StrUtil.isNotBlank(requestId)) {
|
||||
return requestId;
|
||||
}
|
||||
|
||||
// 如果没有JSON字段,尝试从普通字符串中匹配
|
||||
if (msg.contains("requestId=")) {
|
||||
String[] parts = msg.split("requestId=");
|
||||
if (parts.length > 1) {
|
||||
String idPart = parts[1].split("[,\\s&]")[0];
|
||||
return idPart.trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("解析消息中的requestId失败: msg={}, error={}", msg, e.getMessage());
|
||||
}
|
||||
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查指定用户的Channel是否活跃
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return boolean true:连接活跃, false:连接不存在或不活跃
|
||||
*/
|
||||
public static boolean isChannelActive(String userId) {
|
||||
Channel channel = getChannelByUserId(userId);
|
||||
return ObjectUtil.isNotNull(channel) && channel.isActive();
|
||||
}
|
||||
|
||||
|
||||
@@ -93,7 +275,7 @@ public class SocketManager {
|
||||
/**
|
||||
* 用于存储每个测试小项超时时长key key:检测项 value:时间秒
|
||||
*/
|
||||
public static volatile Map<Integer,Long> clockMap = new ConcurrentHashMap<>();
|
||||
public static volatile Map<Integer, Long> clockMap = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* 用于存储比对式测试时间。
|
||||
@@ -101,7 +283,6 @@ public class SocketManager {
|
||||
public static volatile Map<DataSourceEnum, Long> contrastClockMap = new ConcurrentHashMap<>();
|
||||
|
||||
|
||||
|
||||
public static void addSourceList(List<SourceIssue> sList) {
|
||||
sourceIssueList = sList;
|
||||
}
|
||||
@@ -124,8 +305,8 @@ public class SocketManager {
|
||||
targetMap = map;
|
||||
}
|
||||
|
||||
public static void addTargetMap(String scriptType,Long count) {
|
||||
targetMap.put(scriptType,count);
|
||||
public static void addTargetMap(String scriptType, Long count) {
|
||||
targetMap.put(scriptType, count);
|
||||
}
|
||||
|
||||
public static Long getSourceTarget(String scriptType) {
|
||||
@@ -133,10 +314,5 @@ public class SocketManager {
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
package com.njcn.gather.detection.util.socket;
|
||||
|
||||
import com.njcn.gather.detection.pojo.enums.DetectionCodeEnum;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* @author wr
|
||||
* @description
|
||||
* @date 2025/3/27 14:58
|
||||
*/
|
||||
public class UnitUtil {
|
||||
// public static String unit(String code, Integer fly) {
|
||||
// String unit = "";
|
||||
// if (Arrays.asList(0, 1).contains(fly)) {
|
||||
// if (DetectionCodeEnum.FREQ.getCode().equals(code)) {
|
||||
// unit = "Hz";
|
||||
// }
|
||||
// if (DetectionCodeEnum.VRMS.getCode().equals(code)) {
|
||||
// unit = "V";
|
||||
// }
|
||||
// if (DetectionCodeEnum.IRMS.getCode().equals(code)) {
|
||||
// unit = "A";
|
||||
// }
|
||||
// if (DetectionCodeEnum.V2_50.getCode().equals(code) ||
|
||||
// DetectionCodeEnum.SV_1_49.getCode().equals(code)||
|
||||
// DetectionCodeEnum.V_UNBAN.getCode().equals(code) ||
|
||||
// DetectionCodeEnum.I_UNBAN.getCode().equals(code)
|
||||
// ) {
|
||||
// unit = "%";
|
||||
// }
|
||||
// if (DetectionCodeEnum.I2_50.getCode().equals(code) ||
|
||||
// DetectionCodeEnum.SI_1_49.getCode().equals(code)
|
||||
// ) {
|
||||
// unit = "A";
|
||||
// }
|
||||
// if (DetectionCodeEnum.P2_50.getCode().equals(code)) {
|
||||
// unit = "W";
|
||||
// }
|
||||
// if (DetectionCodeEnum.P.getCode().equals(code)) {
|
||||
// unit = "P";
|
||||
// }
|
||||
// if (DetectionCodeEnum.MAG.getCode().equals(code)) {
|
||||
// unit = "V";
|
||||
// }
|
||||
// if (DetectionCodeEnum.DUR.getCode().equals(code)) {
|
||||
// unit = "s";
|
||||
// }
|
||||
// if (DetectionCodeEnum.VA.getCode().equals(code) ||
|
||||
// DetectionCodeEnum.IA.getCode().equals(code)
|
||||
// ) {
|
||||
// unit = "°";
|
||||
// }
|
||||
// if (DetectionCodeEnum.DELTA_V.getCode().equals(code)
|
||||
// ) {
|
||||
// unit = "%";
|
||||
// }
|
||||
// }else{
|
||||
// unit = "%";
|
||||
// }
|
||||
// return unit;
|
||||
// }
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
package com.njcn.gather.detection.util.socket;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.njcn.gather.detection.pojo.param.PreDetectionParam;
|
||||
import com.njcn.gather.detection.pojo.vo.WebSocketVO;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.poi.ss.formula.functions.T;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* @Description: webSocket存储的通道
|
||||
* @Author: wr
|
||||
* @Date: 2024/12/11 13:04
|
||||
*/
|
||||
@Slf4j
|
||||
public class WebServiceManager {
|
||||
|
||||
//key:页面 value:channel
|
||||
private static final Map<String, Channel> userSessions = new ConcurrentHashMap<>();
|
||||
|
||||
// 检测参数。key固定为preDetectionParam, value:检测参数
|
||||
private static final Map<String, PreDetectionParam> preDetectionParamMap = new ConcurrentHashMap<>();
|
||||
|
||||
public static void addUser(String userId, Channel channel) {
|
||||
userSessions.put(userId, channel);
|
||||
}
|
||||
|
||||
|
||||
public static void removeChannel(String channelId) {
|
||||
// 遍历并删除
|
||||
Iterator<Map.Entry<String, Channel>> iterator = userSessions.entrySet().iterator();
|
||||
while (iterator.hasNext()) {
|
||||
Map.Entry<String, Channel> entry = iterator.next();
|
||||
if (entry.getValue().id().toString().equals(channelId)) {
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Channel getChannelByUserId(String userId) {
|
||||
return userSessions.get(userId);
|
||||
}
|
||||
|
||||
public static void sendMsg(String userId,String msg) {
|
||||
Channel channel = userSessions.get(userId);
|
||||
if(Objects.nonNull(channel) && channel.isActive()){
|
||||
TextWebSocketFrame wd = new TextWebSocketFrame(msg);
|
||||
channel.writeAndFlush(wd);
|
||||
}else {
|
||||
log.error("{}-websocket推送消息失败;当前用户-{}-客户端已经断开连接", LocalDateTime.now(),userId);
|
||||
// PreDetectionParam param = preDetectionParamMap.get("preDetectionParam");
|
||||
// CnSocketUtil.quitSend(param);
|
||||
// CnSocketUtil.quitSendSource(param);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static void sendMessage(String userId, WebSocketVO<Object> webSocketVO) {
|
||||
Channel channel = userSessions.get(userId);
|
||||
if(Objects.nonNull(channel) && channel.isActive()){
|
||||
TextWebSocketFrame wd = new TextWebSocketFrame(JSON.toJSONString(webSocketVO));
|
||||
channel.writeAndFlush(wd);
|
||||
}else {
|
||||
log.error("{}-websocket推送消息失败;当前用户-{}-客户端已经断开连接", LocalDateTime.now(),userId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static void addPreDetectionParam(PreDetectionParam preDetectionParam) {
|
||||
preDetectionParamMap.put("preDetectionParam", preDetectionParam);
|
||||
}
|
||||
public static PreDetectionParam getPreDetectionParam() {
|
||||
return preDetectionParamMap.get("preDetectionParam");
|
||||
}
|
||||
public static void removePreDetectionParam() {
|
||||
preDetectionParamMap.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,56 +6,152 @@ import com.njcn.gather.detection.pojo.param.PreDetectionParam;
|
||||
import com.njcn.gather.detection.pojo.vo.SocketDataMsg;
|
||||
import com.njcn.gather.detection.pojo.vo.SocketMsg;
|
||||
import com.njcn.gather.detection.util.socket.CnSocketUtil;
|
||||
import com.njcn.gather.detection.util.socket.FormalTestManager;
|
||||
import com.njcn.gather.detection.util.socket.MsgUtil;
|
||||
import com.njcn.gather.detection.util.socket.SocketManager;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
/**
|
||||
* @Author: cdf
|
||||
* @CreateTime: 2025-02-11
|
||||
* @Description: 心跳处理类
|
||||
* Netty心跳处理器
|
||||
* <p>
|
||||
* 负责维护Socket长连接的心跳检测机制,通过定期发送心跳包来检测连接状态,
|
||||
* 当连续多次未收到心跳响应时自动断开连接并清理相关资源。
|
||||
* </p>
|
||||
*
|
||||
* <h3>核心功能:</h3>
|
||||
* <ul>
|
||||
* <li>定时发送心跳包 (默认10秒间隔,3秒后开始)</li>
|
||||
* <li>监听心跳响应,重置失败计数器</li>
|
||||
* <li>连续失败超过阈值时触发断开逻辑 (默认3次)</li>
|
||||
* <li>异步处理断开操作,避免阻塞心跳线程</li>
|
||||
* <li>优雅关闭资源,防止内存泄漏</li>
|
||||
* </ul>
|
||||
*
|
||||
* <h3>心跳机制流程:</h3>
|
||||
* <pre>
|
||||
* 连接建立 → 启动心跳定时任务(3秒后开始,每10秒执行)
|
||||
* ↓
|
||||
* 发送心跳包 → 等待响应 → 收到响应(重置计数器) / 未收到响应(递增计数器)
|
||||
* ↓
|
||||
* 连续3次失败 → 异步执行断开逻辑 → 发送退出指令 → 延迟清理连接
|
||||
* ↓
|
||||
* 连接断开 → 优雅关闭定时任务和线程池
|
||||
* </pre>
|
||||
*
|
||||
* <h3>线程安全设计:</h3>
|
||||
* <p>
|
||||
* 使用单线程的ScheduledExecutorService处理心跳发送,避免并发问题。
|
||||
* 超时处理使用CompletableFuture异步执行,不阻塞心跳发送线程。
|
||||
* Future引用使用volatile修饰,确保多线程环境下的可见性。
|
||||
* </p>
|
||||
*
|
||||
* <h3>设备类型支持:</h3>
|
||||
* <ul>
|
||||
* <li>程控源设备 (CnSocketUtil.SOURCE_TAG): "_Source"</li>
|
||||
* <li>被检设备 (CnSocketUtil.DEV_TAG): "_Dev"</li>
|
||||
* </ul>
|
||||
*
|
||||
* <h3>使用示例:</h3>
|
||||
* <pre>{@code
|
||||
* // 创建心跳处理器
|
||||
* HeartbeatHandler handler = new HeartbeatHandler(param, CnSocketUtil.SOURCE_TAG);
|
||||
*
|
||||
* // 添加到Netty管道中
|
||||
* pipeline.addLast(handler);
|
||||
* }</pre>
|
||||
*
|
||||
* @author cdf
|
||||
* @version 1.2
|
||||
* @since 2025-02-11
|
||||
*/
|
||||
|
||||
@Slf4j
|
||||
public class HeartbeatHandler extends SimpleChannelInboundHandler<String> {
|
||||
|
||||
/**
|
||||
* 心跳定时任务执行器,使用单线程池避免并发问题
|
||||
*/
|
||||
private final ScheduledExecutorService heartbeatExecutor = Executors.newScheduledThreadPool(1);
|
||||
|
||||
/**
|
||||
* 检测参数,包含用户页面ID等信息
|
||||
*/
|
||||
private final PreDetectionParam param;
|
||||
|
||||
/**
|
||||
* 处理器类型标识("_Source" 或 "_Dev")
|
||||
*/
|
||||
private final String handlerType;
|
||||
|
||||
// 允许连续未收到心跳响应的最大次数
|
||||
/**
|
||||
* 保存定时任务的Future引用,便于取消和管理
|
||||
*/
|
||||
private ScheduledFuture<?> heartbeatFuture;
|
||||
|
||||
/**
|
||||
* 允许连续未收到心跳响应的最大次数
|
||||
*/
|
||||
private static final int MAX_HEARTBEAT_MISSES = 3;
|
||||
// 连续未收到心跳响应的次数
|
||||
|
||||
/**
|
||||
* 连续未收到心跳响应的次数
|
||||
*/
|
||||
private int consecutiveHeartbeatMisses = 0;
|
||||
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*
|
||||
* @param param 检测参数,包含用户页面ID等信息
|
||||
* @param type 处理器类型(CnSocketUtil.SOURCE_TAG 或 CnSocketUtil.DEV_TAG)
|
||||
*/
|
||||
public HeartbeatHandler(PreDetectionParam param, String type) {
|
||||
this.param = param;
|
||||
this.handlerType = type;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 通道激活时的回调方法
|
||||
* 在Socket连接建立成功后被调用,启动心跳机制
|
||||
*
|
||||
* @param ctx Netty的通道上下文对象
|
||||
*/
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) {
|
||||
log.info("心跳处理器启动 - 设备类型: {}", handlerType);
|
||||
// 启动心跳定时任务
|
||||
scheduleHeartbeat(ctx);
|
||||
// 传播事件给管道中的后续处理器
|
||||
ctx.fireChannelActive();
|
||||
}
|
||||
|
||||
/**
|
||||
* 通道断开时的回调方法
|
||||
* 在Socket连接断开时被调用,负责清理相关资源
|
||||
*
|
||||
* @param ctx Netty的通道上下文对象
|
||||
* @throws Exception 异常情况
|
||||
*/
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||
heartbeatExecutor.shutdown();
|
||||
log.info("心跳处理器开始清理资源 - 设备类型: {}", handlerType);
|
||||
shutdownExecutorGracefully();
|
||||
super.channelInactive(ctx);
|
||||
}
|
||||
|
||||
// 每10秒发送一次心跳
|
||||
/**
|
||||
* 启动心跳定时任务
|
||||
* 每10秒发送一次心跳包,3秒后开始执行
|
||||
*
|
||||
* @param ctx Netty的通道上下文对象
|
||||
*/
|
||||
private void scheduleHeartbeat(ChannelHandlerContext ctx) {
|
||||
heartbeatExecutor.scheduleAtFixedRate(() -> {
|
||||
heartbeatFuture = heartbeatExecutor.scheduleAtFixedRate(() -> {
|
||||
if (ctx.channel().isActive()) {
|
||||
// 发送心跳包
|
||||
SocketMsg<String> msg = new SocketMsg<>();
|
||||
@@ -64,48 +160,154 @@ public class HeartbeatHandler extends SimpleChannelInboundHandler<String> {
|
||||
msg.setData("");
|
||||
ctx.channel().writeAndFlush(JSON.toJSONString(msg) + "\n");
|
||||
|
||||
System.out.println(handlerType + "♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥send" + LocalDateTime.now());
|
||||
log.debug("心跳发送 - 设备类型: {}, 时间: {}", handlerType, LocalDateTime.now());
|
||||
consecutiveHeartbeatMisses++;
|
||||
if (consecutiveHeartbeatMisses >= MAX_HEARTBEAT_MISSES) {
|
||||
// 连续三次未收到心跳响应,断开连接
|
||||
System.out.println(handlerType + "连续三次未收到心跳响应,断开连接");
|
||||
if (CnSocketUtil.DEV_TAG.equals(handlerType)) {
|
||||
//CnSocketUtil.sendToWebSocket(param.getUserPageId(),);
|
||||
CnSocketUtil.quitSend(param);
|
||||
} else {
|
||||
CnSocketUtil.quitSendSource(param);
|
||||
}
|
||||
try {
|
||||
Thread.sleep(3000);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
System.err.println("线程中断异常: " + e.getMessage());
|
||||
}
|
||||
String key = CnSocketUtil.DEV_TAG.equals(handlerType) ? param.getUserPageId() + CnSocketUtil.DEV_TAG : param.getUserPageId() + CnSocketUtil.SOURCE_TAG;
|
||||
SocketManager.removeUser(key);
|
||||
// 连续三次未收到心跳响应,异步处理断开逻辑,避免阻塞心跳线程
|
||||
log.warn("心跳响应超时 - 设备类型: {}, 连续失败次数: {}/{}, 执行断开连接",
|
||||
handlerType, consecutiveHeartbeatMisses, MAX_HEARTBEAT_MISSES);
|
||||
handleHeartbeatTimeoutAsync();
|
||||
consecutiveHeartbeatMisses = 0; // 重置连续心跳丢失次数
|
||||
}
|
||||
}
|
||||
}, 3, 10, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步处理心跳超时断开逻辑
|
||||
* <p>
|
||||
* 使用CompletableFuture避免阻塞心跳发送线程,确保心跳机制不受影响。
|
||||
* 处理流程:
|
||||
* 1. 异步发送退出指令
|
||||
* 2. 延迟3秒后清理Socket连接
|
||||
* 3. 记录处理过程和异常
|
||||
* </p>
|
||||
*/
|
||||
private void handleHeartbeatTimeoutAsync() {
|
||||
CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
log.info("开始执行心跳超时断开处理 - 设备类型: {}", handlerType);
|
||||
// 根据设备类型发送对应的退出指令
|
||||
if (CnSocketUtil.DEV_TAG.equals(handlerType)) {
|
||||
CnSocketUtil.quitSend(param);
|
||||
} else if (CnSocketUtil.SOURCE_TAG.equals(handlerType)) {
|
||||
CnSocketUtil.quitSendSource(param);
|
||||
} else {
|
||||
if (FormalTestManager.currentStep == SourceOperateCodeEnum.RECORD_WAVE_STEP1) {
|
||||
CnSocketUtil.contrastSendquit(param.getUserPageId(), SourceOperateCodeEnum.QUIT_INIT_03, true);
|
||||
} else {
|
||||
CnSocketUtil.contrastSendquit(param.getUserPageId(), SourceOperateCodeEnum.QUIT_INIT_02, true);
|
||||
}
|
||||
}
|
||||
log.debug("退出指令已发送,等待3秒后清理连接 - 设备类型: {}", handlerType);
|
||||
} catch (Exception e) {
|
||||
log.error("心跳超时处理发送退出指令异常 - 设备类型: {}", handlerType, e);
|
||||
}
|
||||
}).thenRunAsync(() -> {
|
||||
try {
|
||||
// 延迟3秒后清理连接,给退出指令留出处理时间
|
||||
Thread.sleep(3000);
|
||||
// 构建连接Key并从SocketManager中移除
|
||||
String key = CnSocketUtil.DEV_TAG.equals(handlerType) ?
|
||||
param.getUserPageId() + CnSocketUtil.DEV_TAG :
|
||||
param.getUserPageId() + CnSocketUtil.SOURCE_TAG;
|
||||
SocketManager.removeUser(key);
|
||||
log.info("心跳超时断开处理完成 - 设备类型: {}, 连接已清理", handlerType);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
log.error("心跳超时处理等待过程中被中断 - 设备类型: {}", handlerType);
|
||||
} catch (Exception e) {
|
||||
log.error("心跳超时处理清理连接异常 - 设备类型: {}", handlerType, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 优雅关闭线程池执行器
|
||||
* <p>
|
||||
* 确保资源的完全清理,避免内存泄漏:
|
||||
* 1. 取消当前的心跳定时任务
|
||||
* 2. 关闭线程池并等待正在执行的任务完成
|
||||
* 3. 如果等待超时则强制关闭
|
||||
* 4. 处理中断异常并恢复中断状态
|
||||
* </p>
|
||||
*/
|
||||
private void shutdownExecutorGracefully() {
|
||||
try {
|
||||
// 1. 取消心跳定时任务
|
||||
if (heartbeatFuture != null && !heartbeatFuture.isCancelled()) {
|
||||
boolean cancelled = heartbeatFuture.cancel(false);
|
||||
log.debug("心跳定时任务取消结果: {} - 设备类型: {}", cancelled, handlerType);
|
||||
}
|
||||
// 2. 关闭线程池,不再接收新任务
|
||||
heartbeatExecutor.shutdown();
|
||||
// 3. 等待已提交的任务完成,最多等待5秒
|
||||
if (!heartbeatExecutor.awaitTermination(5, TimeUnit.SECONDS)) {
|
||||
log.warn("心跳线程池未能在5秒内正常关闭,执行强制关闭 - 设备类型: {}", handlerType);
|
||||
heartbeatExecutor.shutdownNow();
|
||||
// 再次等待强制关闭完成,最多等待2秒
|
||||
if (!heartbeatExecutor.awaitTermination(2, TimeUnit.SECONDS)) {
|
||||
log.error("心跳线程池强制关闭失败 - 设备类型: {}", handlerType);
|
||||
}
|
||||
} else {
|
||||
log.debug("心跳线程池已优雅关闭 - 设备类型: {}", handlerType);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
// 如果等待过程中被中断,立即强制关闭
|
||||
log.warn("心跳线程池关闭过程中被中断,执行强制关闭 - 设备类型: {}", handlerType);
|
||||
heartbeatExecutor.shutdownNow();
|
||||
// 恢复中断状态,遵循Java并发编程最佳实践
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 消息接收处理方法
|
||||
* <p>
|
||||
* 负责处理从服务端接收到的消息:
|
||||
* 1. 过滤心跳响应包,重置失败计数器
|
||||
* 2. 业务消息传递给后续处理器
|
||||
* </p>
|
||||
*
|
||||
* @param ctx Netty的通道上下文对象
|
||||
* @param msg 接收到的消息内容
|
||||
* @throws Exception 异常情况
|
||||
*/
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
|
||||
// 过滤心跳包,避免进入业务逻辑
|
||||
if (isHeartbeatPacket(msg)) {
|
||||
System.out.println(handlerType + "♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥response" + LocalDateTime.now());
|
||||
log.debug("心跳响应 - 设备类型: {}, 时间: {}", handlerType, LocalDateTime.now());
|
||||
// 重置连续失败计数器,表示连接正常
|
||||
consecutiveHeartbeatMisses = 0;
|
||||
return;
|
||||
}
|
||||
// 处理业务数据
|
||||
// 业务消息传递给管道中的后续处理器
|
||||
ctx.fireChannelRead(msg);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为心跳数据包
|
||||
* <p>
|
||||
* 通过解析消息的operateCode字段来判断是否为心跳响应。
|
||||
* 心跳包的操作码为SourceOperateCodeEnum.HEARTBEAT。
|
||||
* </p>
|
||||
*
|
||||
* @param msg 需要判断的消息内容
|
||||
* @return true:心跳包, false:业务消息
|
||||
*/
|
||||
private boolean isHeartbeatPacket(String msg) {
|
||||
// 判断是否为心跳包
|
||||
SocketDataMsg socketDataMsg = MsgUtil.socketDataMsg(msg);
|
||||
return !Objects.isNull(socketDataMsg.getOperateCode()) && socketDataMsg.getOperateCode().equals(SourceOperateCodeEnum.HEARTBEAT.getValue());
|
||||
try {
|
||||
// 解析消息为SocketDataMsg对象
|
||||
SocketDataMsg socketDataMsg = MsgUtil.socketDataMsg(msg);
|
||||
// 检查操作码是否为心跳类型
|
||||
return socketDataMsg != null &&
|
||||
socketDataMsg.getOperateCode() != null &&
|
||||
socketDataMsg.getOperateCode().equals(SourceOperateCodeEnum.HEARTBEAT.getValue());
|
||||
} catch (Exception e) {
|
||||
// 消息解析失败,可能不是标准格式的心跳包
|
||||
log.debug("消息解析失败,可能不是心跳包: {}", msg, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
package com.njcn.gather.detection.util.socket.cilent;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.njcn.gather.detection.handler.SocketContrastResponseService;
|
||||
import com.njcn.gather.detection.handler.SocketDevResponseService;
|
||||
import com.njcn.gather.detection.handler.SocketSourceResponseService;
|
||||
import com.njcn.gather.detection.pojo.enums.SourceResponseCodeEnum;
|
||||
import com.njcn.gather.detection.pojo.param.ContrastDetectionParam;
|
||||
import com.njcn.gather.detection.pojo.param.PreDetectionParam;
|
||||
import com.njcn.gather.detection.pojo.vo.SocketDataMsg;
|
||||
import com.njcn.gather.detection.util.socket.CnSocketUtil;
|
||||
import com.njcn.gather.detection.util.socket.SocketManager;
|
||||
import com.njcn.gather.detection.util.socket.WebServiceManager;
|
||||
import com.njcn.gather.detection.util.socket.websocket.WebServiceManager;
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.channel.*;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
@@ -18,140 +21,486 @@ import io.netty.handler.codec.string.StringDecoder;
|
||||
import io.netty.handler.codec.string.StringEncoder;
|
||||
import io.netty.handler.timeout.IdleStateHandler;
|
||||
import io.netty.util.CharsetUtil;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.Resource;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Netty客户端工具类
|
||||
* 用于建立与检测设备和程控源设备的Socket通信连接
|
||||
* 支持心跳检测、断线重连和异常处理
|
||||
*
|
||||
* @Description: 心跳检测服务端 对应的服务端在netty-server 包下的NettyClient
|
||||
* @Author: wr
|
||||
* @Date: 2024/12/10 14:16
|
||||
*/
|
||||
|
||||
@Getter
|
||||
@Slf4j
|
||||
@Component
|
||||
public class NettyClient {
|
||||
|
||||
public static void socketClient(String ip, Integer port, PreDetectionParam param, String msg, SimpleChannelInboundHandler<String> handler) {
|
||||
NioEventLoopGroup group = new NioEventLoopGroup();
|
||||
Bootstrap bootstrap = new Bootstrap();
|
||||
try {
|
||||
bootstrap.group(group)
|
||||
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
|
||||
.channel(NioSocketChannel.class)
|
||||
.handler(new ChannelInitializer<NioSocketChannel>() {
|
||||
@Override
|
||||
protected void initChannel(NioSocketChannel ch) {
|
||||
if (handler instanceof NettySourceClientHandler) {
|
||||
ch.pipeline()
|
||||
//空闲状态的handler
|
||||
// 添加LineBasedFrameDecoder来按行分割数据
|
||||
.addLast(new LineBasedFrameDecoder(10240))
|
||||
// .addLast(new IdleStateHandler(0, 10, 0, TimeUnit.SECONDS))
|
||||
.addLast(new StringDecoder(CharsetUtil.UTF_8))
|
||||
.addLast(new StringEncoder(CharsetUtil.UTF_8))
|
||||
.addLast(new HeartbeatHandler(param, CnSocketUtil.SOURCE_TAG))
|
||||
.addLast(handler);
|
||||
} else {
|
||||
ch.pipeline()
|
||||
@Resource
|
||||
private SocketSourceResponseService socketSourceResponseService;
|
||||
|
||||
// 添加LineBasedFrameDecoder来按行分割数据
|
||||
.addLast(new LineBasedFrameDecoder(10240))
|
||||
.addLast(new StringDecoder(CharsetUtil.UTF_8))
|
||||
.addLast(new StringEncoder(CharsetUtil.UTF_8))
|
||||
.addLast(new HeartbeatHandler(param, CnSocketUtil.DEV_TAG))
|
||||
//空闲状态的handler
|
||||
//readerIdleTimeSeconds:在指定的秒数内如果没有读取到任何数据,则触发IdleState.READER_IDLE事件。
|
||||
//writerIdleTimeSeconds:在指定的秒数内如果没有写入任何数据,则触发IdleState.WRITER_IDLE事件。
|
||||
//allIdleTimeSeconds:在指定的秒数内如果没有发生任何读取或写入操作,则触发IdleState.ALL_IDLE事件。
|
||||
.addLast(new IdleStateHandler(60, 0, 0, TimeUnit.SECONDS))
|
||||
.addLast(handler);
|
||||
}
|
||||
@Resource
|
||||
private SocketDevResponseService socketDevResponseService;
|
||||
|
||||
}
|
||||
});
|
||||
ChannelFuture channelFuture = bootstrap.connect(ip, port).sync();
|
||||
channelFuture.addListener((ChannelFutureListener) ch -> {
|
||||
if (!ch.isSuccess()) {
|
||||
System.out.println("链接服务端失败...");
|
||||
// 连接失败时关闭 group
|
||||
group.shutdownGracefully();
|
||||
} else {
|
||||
System.out.println("链接服务端成功...");
|
||||
if (handler instanceof NettySourceClientHandler) {
|
||||
NioEventLoopGroup groupByUserId = SocketManager.getGroupByUserId(param.getUserPageId() + CnSocketUtil.SOURCE_TAG);
|
||||
if (ObjectUtil.isNotNull(groupByUserId)) {
|
||||
groupByUserId.shutdownGracefully().sync();
|
||||
}
|
||||
SocketManager.addGroup(param.getUserPageId() + CnSocketUtil.SOURCE_TAG, group);
|
||||
} else {
|
||||
NioEventLoopGroup groupByUserId = SocketManager.getGroupByUserId(param.getUserPageId() + CnSocketUtil.DEV_TAG);
|
||||
if (ObjectUtil.isNotNull(groupByUserId)) {
|
||||
groupByUserId.shutdownGracefully().sync();
|
||||
}
|
||||
SocketManager.addGroup(param.getUserPageId() + CnSocketUtil.DEV_TAG, group);
|
||||
}
|
||||
@Resource
|
||||
private SocketContrastResponseService socketContrastResponseService;
|
||||
|
||||
System.out.println("客户端向服务端发送消息:" + port + msg);
|
||||
channelFuture.channel().writeAndFlush(msg + "\n");
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
System.out.println("连接socket服务端发送异常............" + e.getMessage());
|
||||
group.shutdownGracefully();
|
||||
//TODO 通知页面
|
||||
SocketDataMsg socketDataMsg = new SocketDataMsg();
|
||||
socketDataMsg.setType("aaa");
|
||||
socketDataMsg.setCode(SourceResponseCodeEnum.SOCKET_ERROR.getCode());
|
||||
socketDataMsg.setData(SourceResponseCodeEnum.SOCKET_ERROR.getMessage());
|
||||
socketDataMsg.setRequestId("connect");
|
||||
if (handler instanceof NettySourceClientHandler) {
|
||||
socketDataMsg.setOperateCode("Source");
|
||||
} else if (handler instanceof NettyDevClientHandler) {
|
||||
CnSocketUtil.quitSendSource(param);
|
||||
socketDataMsg.setOperateCode("Dev");
|
||||
} else {
|
||||
socketDataMsg.setOperateCode("Dev");
|
||||
CnSocketUtil.quitSend(param);
|
||||
}
|
||||
WebServiceManager.sendMsg(param.getUserPageId(), JSON.toJSONString(socketDataMsg));
|
||||
} finally {
|
||||
// System.out.println("进入clientSocket最后步骤---------------------");
|
||||
/**
|
||||
* 静态实例,用于保持向后兼容
|
||||
*/
|
||||
private static NettyClient instance;
|
||||
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
instance = this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建程控源Handler实例(Spring管理方式)
|
||||
* 自动注入SocketSourceResponseService,统一使用CnSocketUtil.SOURCE_TAG
|
||||
*
|
||||
* @param param 检测参数
|
||||
* @return NettySourceClientHandler 程控源处理器实例
|
||||
*/
|
||||
public NettySourceClientHandler createSourceHandler(PreDetectionParam param) {
|
||||
return new NettySourceClientHandler(param, socketSourceResponseService);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建被检设备Handler实例(Spring管理方式)
|
||||
* 自动注入SocketDevResponseService
|
||||
*
|
||||
* @param param 检测参数
|
||||
* @return NettyDevClientHandler 被检设备处理器实例
|
||||
*/
|
||||
public NettyDevClientHandler createDeviceHandler(PreDetectionParam param) {
|
||||
return new NettyDevClientHandler(param, socketDevResponseService);
|
||||
}
|
||||
|
||||
/**
|
||||
* 智能连接程控源设备(新增方法)
|
||||
* 自动创建Handler并建立连接
|
||||
*
|
||||
* @param ip 程控源IP地址
|
||||
* @param port 程控源端口
|
||||
* @param param 检测参数
|
||||
* @param msg 初始消息
|
||||
*/
|
||||
public void connectToSource(String ip, Integer port, PreDetectionParam param, String msg) {
|
||||
NettySourceClientHandler handler = createSourceHandler(param);
|
||||
executeSocketConnection(ip, port, param, msg, handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* 智能连接被检设备(新增方法)
|
||||
* 自动创建Handler并建立连接
|
||||
*
|
||||
* @param ip 被检设备IP地址
|
||||
* @param port 被检设备端口
|
||||
* @param param 检测参数
|
||||
* @param msg 初始消息
|
||||
*/
|
||||
public void connectToDevice(String ip, Integer port, PreDetectionParam param, String msg) {
|
||||
NettyDevClientHandler handler = createDeviceHandler(param);
|
||||
executeSocketConnection(ip, port, param, msg, handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* 智能连接比对被检设备(新增方法)
|
||||
* 自动创建Handler并建立连接
|
||||
*
|
||||
* @param ip 被检设备IP地址
|
||||
* @param port 被检设备端口
|
||||
* @param param 检测参数
|
||||
* @param msg 初始消息
|
||||
* 静态方法:智能连接程控源设备(兼容性包装)
|
||||
*/
|
||||
private void connectToContrast(String ip, Integer port, ContrastDetectionParam param, String msg) {
|
||||
PreDetectionParam preDetectionParam = new PreDetectionParam();
|
||||
preDetectionParam.setUserPageId(param.getLoginName());
|
||||
preDetectionParam.setTestItemList(param.getTestItemList());
|
||||
preDetectionParam.setDevIds(param.getDevIds());
|
||||
preDetectionParam.setUserId(param.getUserId());
|
||||
NettyContrastClientHandler handler = new NettyContrastClientHandler();
|
||||
handler.param = preDetectionParam;
|
||||
handler.socketContrastResponseService = socketContrastResponseService;
|
||||
executeSocketConnection(ip, port, preDetectionParam, msg, handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* 静态方法:智能连接程控源设备(兼容性包装)
|
||||
*/
|
||||
public static void connectToContrastDeviceStatic(String ip, Integer port, ContrastDetectionParam param, String msg) {
|
||||
if (instance != null) {
|
||||
instance.connectToContrast(ip, port, param, msg);
|
||||
} else {
|
||||
log.error("NettyClient未初始化,无法创建比对设备通讯连接");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 静态方法:智能连接程控源设备(兼容性包装)
|
||||
*/
|
||||
public static void connectToSourceStatic(String ip, Integer port, PreDetectionParam param, String msg) {
|
||||
if (instance != null) {
|
||||
instance.connectToSource(ip, port, param, msg);
|
||||
} else {
|
||||
log.error("NettyClient未初始化,无法创建程控源连接");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重连方法
|
||||
* 静态方法:智能连接被检设备(兼容性包装)
|
||||
*/
|
||||
public static void connect(Bootstrap bootstrap, String msg) {
|
||||
try {
|
||||
bootstrap.connect("127.0.0.1", 8787).sync()
|
||||
.addListener((ChannelFutureListener) ch -> {
|
||||
if (!ch.isSuccess()) {
|
||||
ch.channel().close();
|
||||
final EventLoop loop = ch.channel().eventLoop();
|
||||
loop.schedule(() -> {
|
||||
System.err.println("服务端链接不上,开始重连操作...");
|
||||
//重连
|
||||
connect(bootstrap, msg);
|
||||
}, 3L, TimeUnit.SECONDS);
|
||||
} else {
|
||||
if (StrUtil.isNotBlank(msg)) {
|
||||
ch.channel().writeAndFlush(msg);
|
||||
}
|
||||
System.out.println("服务端链接成功...");
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
System.out.println(e.getMessage());
|
||||
try {
|
||||
Thread.sleep(3000L);
|
||||
} catch (InterruptedException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
//再重连
|
||||
connect(bootstrap, msg);
|
||||
public static void connectToDeviceStatic(String ip, Integer port, PreDetectionParam param, String msg) {
|
||||
if (instance != null) {
|
||||
instance.connectToDevice(ip, port, param, msg);
|
||||
} else {
|
||||
log.error("NettyClient未初始化,无法创建被检设备连接");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 内部重构后的实现 - 拆分职责但不暴露给外部
|
||||
* 执行完整的Socket连接建立流程:
|
||||
* 1. 创建事件循环组
|
||||
* 2. 配置Bootstrap启动器
|
||||
* 3. 设置管道处理链
|
||||
* 4. 建立连接并处理结果
|
||||
*
|
||||
* @param ip 目标服务器IP地址
|
||||
* @param port 目标服务器端口号
|
||||
* @param param 检测参数对象
|
||||
* @param msg 连接成功后发送的初始消息
|
||||
* @param handler 业务处理器(区分程控源和被检设备)
|
||||
*/
|
||||
private static void executeSocketConnection(String ip, Integer port,
|
||||
PreDetectionParam param, String msg, SimpleChannelInboundHandler<String> handler) {
|
||||
// 创建NIO事件循环组,用于处理网络I/O事件
|
||||
NioEventLoopGroup group = createEventLoopGroup();
|
||||
|
||||
try {
|
||||
// 配置客户端启动器
|
||||
Bootstrap bootstrap = configureBootstrap(group);
|
||||
// 创建管道初始化器,配置编解码和业务处理链
|
||||
ChannelInitializer<NioSocketChannel> initializer = createChannelInitializer(param, handler);
|
||||
bootstrap.handler(initializer);
|
||||
// 同步连接到目标服务器
|
||||
ChannelFuture channelFuture = bootstrap.connect(ip, port).sync();
|
||||
// 处理连接结果(成功或失败)
|
||||
handleConnectionResult(channelFuture, param, handler, group, msg);
|
||||
} catch (Exception e) {
|
||||
// 处理连接过程中的异常
|
||||
handleConnectionException(e, param, handler, group);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建NIO事件循环组
|
||||
* 用于管理网络I/O操作的线程池,处理连接、读写等异步事件
|
||||
*
|
||||
* @return NioEventLoopGroup 事件循环组实例
|
||||
*/
|
||||
private static NioEventLoopGroup createEventLoopGroup() {
|
||||
return new NioEventLoopGroup();
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置Bootstrap客户端启动器
|
||||
* 设置连接超时、通道类型等基础参数
|
||||
*
|
||||
* @param group 事件循环组
|
||||
* @return Bootstrap 配置好的启动器
|
||||
*/
|
||||
private static Bootstrap configureBootstrap(NioEventLoopGroup group) {
|
||||
return new Bootstrap()
|
||||
// 绑定事件循环组
|
||||
.group(group)
|
||||
// 连接超时5秒
|
||||
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
|
||||
// 使用NIO Socket通道
|
||||
.channel(NioSocketChannel.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建通道初始化器
|
||||
* 当新连接建立时,初始化该连接的处理管道
|
||||
*
|
||||
* @param param 检测参数,用于配置心跳处理器
|
||||
* @param handler 业务处理器
|
||||
* @return ChannelInitializer 通道初始化器
|
||||
*/
|
||||
private static ChannelInitializer<NioSocketChannel> createChannelInitializer(
|
||||
PreDetectionParam param, SimpleChannelInboundHandler<String> handler) {
|
||||
return new ChannelInitializer<NioSocketChannel>() {
|
||||
@Override
|
||||
protected void initChannel(NioSocketChannel ch) {
|
||||
setupPipeline(ch.pipeline(), param, handler);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置管道处理链
|
||||
* 按顺序添加各种处理器,构成完整的数据处理流水线:
|
||||
* 1. LineBasedFrameDecoder:按行分割数据,解决TCP粘包拆包问题
|
||||
* 2. StringDecoder/StringEncoder:字符串编解码器
|
||||
* 3. HeartbeatHandler:心跳处理器,维持连接活跃
|
||||
* 4. IdleStateHandler:空闲检测器(仅被检设备需要)
|
||||
* 5. 业务处理器:具体的业务逻辑处理
|
||||
*
|
||||
* @param pipeline 管道对象
|
||||
* @param param 检测参数
|
||||
* @param handler 业务处理器
|
||||
*/
|
||||
private static void setupPipeline(ChannelPipeline pipeline,
|
||||
PreDetectionParam param, SimpleChannelInboundHandler<String> handler) {
|
||||
// 基础编解码器:处理数据格式转换和粘包拆包
|
||||
// 按行分割,最大20KB
|
||||
pipeline.addLast(new LineBasedFrameDecoder(10240 * 2))
|
||||
// 字节转字符串
|
||||
.addLast(new StringDecoder(CharsetUtil.UTF_8))
|
||||
// 字符串转字节
|
||||
.addLast(new StringEncoder(CharsetUtil.UTF_8));
|
||||
|
||||
// 心跳处理器:根据设备类型选择不同的标签
|
||||
String tag = getDeviceTag(handler);
|
||||
pipeline.addLast(new HeartbeatHandler(param, tag));
|
||||
// 空闲检测器:仅被检设备和比对被检设备需要,60秒无读操作触发空闲事件
|
||||
if (!isSourceHandler(handler)) {
|
||||
pipeline.addLast(new IdleStateHandler(60, 0, 0, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
// 业务处理器:处理具体的检测业务逻辑
|
||||
pipeline.addLast(handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为程控源处理器
|
||||
* 程控源设备和被检设备使用不同的处理器和配置
|
||||
*
|
||||
* @param handler 业务处理器
|
||||
* @return boolean true:程控源处理器, false:被检设备处理器
|
||||
*/
|
||||
private static boolean isSourceHandler(SimpleChannelInboundHandler<String> handler) {
|
||||
return handler instanceof NettySourceClientHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取设备标签
|
||||
* 用于在SocketManager中区分不同类型的设备连接
|
||||
*
|
||||
* @param handler 业务处理器
|
||||
* @return String 设备标签("_Source" 或 "_Dev")
|
||||
*/
|
||||
private static String getDeviceTag(SimpleChannelInboundHandler<String> handler) {
|
||||
String tag;
|
||||
if (handler instanceof NettySourceClientHandler) {
|
||||
tag = CnSocketUtil.SOURCE_TAG;
|
||||
} else if (handler instanceof NettyDevClientHandler) {
|
||||
tag = CnSocketUtil.DEV_TAG;
|
||||
} else {
|
||||
tag = CnSocketUtil.CONTRAST_DEV_TAG;
|
||||
}
|
||||
return tag;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取名称
|
||||
* 用于在SocketManager中区分不同类型的设备连接
|
||||
*
|
||||
* @param handler 业务处理器
|
||||
* @return String 设备标签("程控源设备" 或 "被检设备")
|
||||
*/
|
||||
private static String getDeviceType(SimpleChannelInboundHandler<String> handler) {
|
||||
String deviceType;
|
||||
if (handler instanceof NettySourceClientHandler) {
|
||||
deviceType = "程控源设备";
|
||||
} else if (handler instanceof NettyDevClientHandler) {
|
||||
deviceType = "被检设备";
|
||||
} else {
|
||||
deviceType = "比对被检设备";
|
||||
}
|
||||
return deviceType;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理连接结果
|
||||
* 为连接Future添加监听器,异步处理连接成功或失败的情况
|
||||
*
|
||||
* @param channelFuture 连接Future对象
|
||||
* @param param 检测参数
|
||||
* @param handler 业务处理器
|
||||
* @param group 事件循环组
|
||||
* @param msg 初始消息
|
||||
*/
|
||||
private static void handleConnectionResult(ChannelFuture channelFuture,
|
||||
PreDetectionParam param, SimpleChannelInboundHandler<String> handler,
|
||||
NioEventLoopGroup group, String msg) {
|
||||
channelFuture.addListener((ChannelFutureListener) ch -> {
|
||||
if (!ch.isSuccess()) {
|
||||
onConnectionFailure(handler, group);
|
||||
} else {
|
||||
onConnectionSuccess(channelFuture, param, handler, group, msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 连接失败处理
|
||||
* 输出失败信息并优雅关闭事件循环组
|
||||
*
|
||||
* @param handler 业务处理器,用于区分设备类型
|
||||
* @param group 事件循环组
|
||||
*/
|
||||
private static void onConnectionFailure(SimpleChannelInboundHandler<String> handler, NioEventLoopGroup group) {
|
||||
String deviceType = getDeviceType(handler);
|
||||
log.info("连接{}服务端失败...", deviceType);
|
||||
group.shutdownGracefully();
|
||||
}
|
||||
|
||||
/**
|
||||
* 连接成功处理
|
||||
* 执行连接成功后的初始化操作:
|
||||
* 1. 管理Socket连接会话(注册EventLoopGroup到SocketManager)
|
||||
* 2. 注册Channel到SocketManager,实现统一的连接管理
|
||||
* 3. 通过SocketManager发送初始消息,统一消息发送入口
|
||||
*
|
||||
* @param channelFuture 连接Future对象
|
||||
* @param param 检测参数
|
||||
* @param handler 业务处理器
|
||||
* @param group 事件循环组
|
||||
* @param msg 初始消息
|
||||
*/
|
||||
private static void onConnectionSuccess(ChannelFuture channelFuture,
|
||||
PreDetectionParam param, SimpleChannelInboundHandler<String> handler,
|
||||
NioEventLoopGroup group, String msg) {
|
||||
String deviceType = getDeviceType(handler);
|
||||
log.info("连接{}服务端成功...", deviceType);
|
||||
// 管理连接会话,将EventLoopGroup注册到SocketManager
|
||||
manageSocketConnection(param, handler, group);
|
||||
|
||||
// 将Channel也注册到SocketManager,便于统一消息发送
|
||||
String userId = param.getUserPageId() + getDeviceTag(handler);
|
||||
SocketManager.addUser(userId, channelFuture.channel());
|
||||
|
||||
// 通过SocketManager发送初始消息,统一消息发送入口
|
||||
SocketManager.sendMsg(userId, msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* 管理Socket连接会话
|
||||
* 将新建立的连接注册到SocketManager中进行统一管理:
|
||||
* 1. 检查并关闭同用户同设备类型的旧连接,避免资源泄露
|
||||
* 2. 将新连接注册到SocketManager,便于后续管理和查找
|
||||
* <p>
|
||||
* 连接Key格式:{userPageId}_{deviceTag}
|
||||
* 例如:zhangsan_test_Source(程控源) / zhangsan_test_Dev(被检设备)
|
||||
*
|
||||
* @param param 检测参数,包含用户页面ID
|
||||
* @param handler 业务处理器,用于区分设备类型
|
||||
* @param group 事件循环组,表示具体的连接资源
|
||||
*/
|
||||
private static void manageSocketConnection(PreDetectionParam param,
|
||||
SimpleChannelInboundHandler<String> handler, NioEventLoopGroup group) {
|
||||
// 构建连接标识:用户ID + 设备标签
|
||||
String key = param.getUserPageId() + getDeviceTag(handler);
|
||||
|
||||
// 关闭旧连接:同一用户同一设备类型只能有一个活跃连接
|
||||
NioEventLoopGroup existingGroup = SocketManager.getGroupByUserId(key);
|
||||
if (ObjectUtil.isNotNull(existingGroup)) {
|
||||
try {
|
||||
existingGroup.shutdownGracefully().sync();
|
||||
} catch (InterruptedException e) {
|
||||
// 恢复中断状态
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
// 注册新连接到SocketManager
|
||||
SocketManager.addGroup(key, group);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理连接异常
|
||||
* 当连接建立过程中发生异常时的统一处理流程:
|
||||
* 1. 关闭相关资源,防止资源泄露
|
||||
* 2. 执行设备相关的退出操作
|
||||
* 3. 通过WebSocket向前端通知错误信息
|
||||
*
|
||||
* @param e 异常对象
|
||||
* @param param 检测参数
|
||||
* @param handler 业务处理器
|
||||
* @param group 事件循环组
|
||||
*/
|
||||
private static void handleConnectionException(Exception e, PreDetectionParam param,
|
||||
SimpleChannelInboundHandler<String> handler, NioEventLoopGroup group) {
|
||||
log.info("连接socket服务端发送异常: {}", e.getMessage());
|
||||
|
||||
// 关闭事件循环组资源
|
||||
group.shutdownGracefully();
|
||||
|
||||
// 执行设备相关的退出操作
|
||||
executeQuitOperations(param, handler);
|
||||
|
||||
// 通过WebSocket通知前端页面
|
||||
notifyFrontendError(param, handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行退出操作
|
||||
* 根据不同的设备处理器类型执行相应的退出指令:
|
||||
* - NettyDevClientHandler:被检设备处理器,需要发送程控源退出指令
|
||||
* - 其他非程控源处理器:发送通用退出指令
|
||||
* - NettySourceClientHandler:程控源处理器,无需额外退出操作
|
||||
*
|
||||
* @param param 检测参数
|
||||
* @param handler 业务处理器
|
||||
*/
|
||||
private static void executeQuitOperations(PreDetectionParam param,
|
||||
SimpleChannelInboundHandler<String> handler) {
|
||||
if (handler instanceof NettyDevClientHandler) {
|
||||
// 被检设备异常时,发送程控源退出指令
|
||||
CnSocketUtil.quitSendSource(param);
|
||||
}
|
||||
// 程控源处理器异常时无需额外操作
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知前端错误信息
|
||||
* 构建错误消息对象并通过WebSocket发送给前端页面
|
||||
* 前端可以根据操作码(Source/Dev)显示相应的错误提示
|
||||
*
|
||||
* @param param 检测参数,包含用户页面ID
|
||||
* @param handler 业务处理器,用于确定操作码
|
||||
*/
|
||||
private static void notifyFrontendError(PreDetectionParam param,
|
||||
SimpleChannelInboundHandler<String> handler) {
|
||||
// 构建错误消息对象
|
||||
SocketDataMsg socketDataMsg = new SocketDataMsg();
|
||||
// 消息类型
|
||||
socketDataMsg.setType("aaa");
|
||||
// 错误码
|
||||
socketDataMsg.setCode(SourceResponseCodeEnum.SOCKET_ERROR.getCode());
|
||||
// 错误消息
|
||||
socketDataMsg.setData(SourceResponseCodeEnum.SOCKET_ERROR.getMessage());
|
||||
// 请求ID标识
|
||||
socketDataMsg.setRequestId("connect");
|
||||
// 设置操作码:程控源为"Source",被检设备为"Dev"
|
||||
String devTag = getDeviceTag(handler).substring(1);
|
||||
socketDataMsg.setOperateCode(devTag);
|
||||
// 通过WebSocket发送错误信息到前端页面
|
||||
WebServiceManager.sendMsg(param.getUserPageId(), JSON.toJSONString(socketDataMsg));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
package com.njcn.gather.detection.util.socket.cilent;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.njcn.gather.detection.handler.SocketContrastResponseService;
|
||||
import com.njcn.gather.detection.pojo.enums.SourceOperateCodeEnum;
|
||||
import com.njcn.gather.detection.pojo.enums.SourceResponseCodeEnum;
|
||||
import com.njcn.gather.detection.pojo.param.PreDetectionParam;
|
||||
import com.njcn.gather.detection.pojo.vo.SocketDataMsg;
|
||||
import com.njcn.gather.detection.util.socket.CnSocketUtil;
|
||||
import com.njcn.gather.detection.util.socket.FormalTestManager;
|
||||
import com.njcn.gather.detection.util.socket.MsgUtil;
|
||||
import com.njcn.gather.detection.util.socket.SocketManager;
|
||||
import com.njcn.gather.detection.util.socket.websocket.WebServiceManager;
|
||||
import com.njcn.gather.plan.pojo.enums.DataSourceEnum;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
@@ -13,6 +19,7 @@ import io.netty.handler.timeout.IdleState;
|
||||
import io.netty.handler.timeout.IdleStateEvent;
|
||||
import io.netty.handler.timeout.TimeoutException;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.ConnectException;
|
||||
@@ -24,17 +31,18 @@ import java.util.Objects;
|
||||
* @author caozehui
|
||||
* @data 2025-07-25
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class NettyContrastClientHandler extends SimpleChannelInboundHandler<String> {
|
||||
|
||||
|
||||
private final PreDetectionParam param;
|
||||
private final SocketContrastResponseService socketContrastResponseService;
|
||||
public static PreDetectionParam param;
|
||||
public static SocketContrastResponseService socketContrastResponseService;
|
||||
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
||||
System.out.println("客户端通道已建立" + ctx.channel().id());
|
||||
Channel channel = SocketManager.getChannelByUserId(param.getUserPageId() + CnSocketUtil.DEV_TAG);
|
||||
Channel channel = SocketManager.getChannelByUserId(param.getUserPageId() + CnSocketUtil.CONTRAST_DEV_TAG);
|
||||
if (Objects.nonNull(channel)) {
|
||||
try {
|
||||
channel.close().sync();
|
||||
@@ -42,17 +50,24 @@ public class NettyContrastClientHandler extends SimpleChannelInboundHandler<Stri
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
SocketManager.addUser(param.getUserPageId() + CnSocketUtil.DEV_TAG, ctx.channel());
|
||||
SocketManager.addUser(param.getUserPageId() + CnSocketUtil.CONTRAST_DEV_TAG, ctx.channel());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws InterruptedException {
|
||||
System.out.println("contrastClientHandler接收server端数据>>>>>>" + msg);
|
||||
log.info("contrastdevhandler接收server端数据: {}", msg);
|
||||
try {
|
||||
socketContrastResponseService.deal(param, msg);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
CnSocketUtil.quitSend(param);
|
||||
log.error("处理服务端消息异常", e);
|
||||
// if (FormalTestManager.currentStep == SourceOperateCodeEnum.RECORD_WAVE_STEP1) {
|
||||
// CnSocketUtil.contrastSendquit(param.getUserPageId(), SourceOperateCodeEnum.QUIT_INIT_03, true);
|
||||
// } else {
|
||||
// CnSocketUtil.contrastSendquit(param.getUserPageId(), SourceOperateCodeEnum.QUIT_INIT_02, true);
|
||||
// }
|
||||
CnSocketUtil.contrastSendquit(param.getUserPageId(), SourceOperateCodeEnum.QUIT_INIT_01, false);
|
||||
CnSocketUtil.contrastSendquit(param.getUserPageId(), SourceOperateCodeEnum.QUIT_INIT_02, false);
|
||||
CnSocketUtil.contrastSendquit(param.getUserPageId(), SourceOperateCodeEnum.QUIT_INIT_03, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +75,7 @@ public class NettyContrastClientHandler extends SimpleChannelInboundHandler<Stri
|
||||
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||
System.out.println("与通信模块端断线");
|
||||
ctx.close();
|
||||
SocketManager.removeUser(param.getUserPageId() + CnSocketUtil.DEV_TAG);
|
||||
SocketManager.removeUser(param.getUserPageId() + CnSocketUtil.CONTRAST_DEV_TAG);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -73,71 +88,37 @@ public class NettyContrastClientHandler extends SimpleChannelInboundHandler<Stri
|
||||
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
|
||||
if (evt instanceof IdleStateEvent) { //IdleState.在一段时间内没有收到任何消息时,会触发该事件
|
||||
if (((IdleStateEvent) evt).state() == IdleState.READER_IDLE) {
|
||||
System.out.println(LocalDateTime.now() + "contrastClientHandler触发读超时函数**************************************");
|
||||
SocketManager.contrastClockMap.put(DataSourceEnum.REAL_DATA, SocketManager.contrastClockMap.get(DataSourceEnum.REAL_DATA) + 60L);
|
||||
if (FormalTestManager.isTesting) {
|
||||
System.out.println(LocalDateTime.now() + "contrastClientHandler触发读超时函数**************************************");
|
||||
|
||||
//实时数据
|
||||
if (SocketManager.contrastClockMap.get(DataSourceEnum.REAL_DATA) >= 60) {
|
||||
CnSocketUtil.quitSend(param);
|
||||
System.out.println("超时处理-----》" + "实时数据已超时----------------关闭");
|
||||
timeoutSend();
|
||||
if (!FormalTestManager.isRemoveSocket && ObjectUtil.isNotNull(FormalTestManager.nonWaveDataSourceEnum)) {
|
||||
long time = SocketManager.contrastClockMap.get(FormalTestManager.nonWaveDataSourceEnum) + 60L;
|
||||
SocketManager.contrastClockMap.put(FormalTestManager.nonWaveDataSourceEnum, time);
|
||||
|
||||
if (FormalTestManager.isPstData) {
|
||||
if (time > 60 * 10) {
|
||||
if (DataSourceEnum.REAL_DATA == FormalTestManager.nonWaveDataSourceEnum) {
|
||||
CnSocketUtil.contrastSendquit(param.getUserPageId(), SourceOperateCodeEnum.QUIT_INIT_02, true);
|
||||
}
|
||||
if (FormalTestManager.isWaveCheck) {
|
||||
CnSocketUtil.contrastSendquit(param.getUserPageId(), SourceOperateCodeEnum.QUIT_INIT_03, true);
|
||||
}
|
||||
CnSocketUtil.contrastSendquit(param.getUserPageId(), SourceOperateCodeEnum.QUIT_INIT_01, true);
|
||||
timeoutSend(SourceOperateCodeEnum.QUIT_INIT_01);
|
||||
}
|
||||
} else if (DataSourceEnum.REAL_DATA == FormalTestManager.nonWaveDataSourceEnum) {
|
||||
if (time >= 60) {
|
||||
CnSocketUtil.contrastSendquit(param.getUserPageId(), SourceOperateCodeEnum.QUIT_INIT_02, true);
|
||||
timeoutSend(SourceOperateCodeEnum.QUIT_INIT_02);
|
||||
}
|
||||
} else if (time >= FormalTestManager.maxTime) {
|
||||
CnSocketUtil.contrastSendquit(param.getUserPageId(), SourceOperateCodeEnum.QUIT_INIT_01, true);
|
||||
timeoutSend(SourceOperateCodeEnum.QUIT_INIT_01);
|
||||
}
|
||||
}
|
||||
}
|
||||
// if (!FormalTestManager.hasStopFlag) {
|
||||
// if (CollUtil.isNotEmpty(SocketManager.getSourceList())) {
|
||||
// SourceIssue sourceIssue = SocketManager.getSourceList().get(0);
|
||||
// if (SocketManager.clockMap.containsKey(sourceIssue.getIndex())) {
|
||||
// SocketManager.clockMap.put(sourceIssue.getIndex(), SocketManager.clockMap.get(sourceIssue.getIndex()) + 60L);
|
||||
// } else {
|
||||
// SocketManager.clockMap.put(sourceIssue.getIndex(), 60L);
|
||||
// }
|
||||
//
|
||||
// if (sourceIssue.getType().equals(DicDataEnum.F.getCode())) {
|
||||
// //闪变,正常抛一轮最大等待20分钟超时
|
||||
// if (SocketManager.clockMap.get(sourceIssue.getIndex()) > 1300) {
|
||||
// fly = true;
|
||||
// System.out.println("超时处理-----》" + sourceIssue.getType() + "已超时----------------关闭");
|
||||
// CnSocketUtil.quitSend(param);
|
||||
// timeoutSend(sourceIssue);
|
||||
// }
|
||||
// } else if (sourceIssue.getType().equals(DicDataEnum.VOLTAGE.getCode()) || sourceIssue.getType().equals(DicDataEnum.HP.getCode())) {
|
||||
// //统计数据项,正常抛一轮数据,超时
|
||||
// if (SocketManager.clockMap.get(sourceIssue.getIndex()) > 180) {
|
||||
// fly = true;
|
||||
// CnSocketUtil.quitSend(param);
|
||||
// System.out.println("超时处理-----》" + sourceIssue.getType() + "已超时----------------关闭");
|
||||
// timeoutSend(sourceIssue);
|
||||
// }
|
||||
// } else {
|
||||
// //实时数据
|
||||
// if (SocketManager.clockMap.get(sourceIssue.getIndex()) > 60) {
|
||||
// fly = true;
|
||||
// CnSocketUtil.quitSend(param);
|
||||
// System.out.println("超时处理-----》" + sourceIssue.getType() + "已超时----------------关闭");
|
||||
// timeoutSend(sourceIssue);
|
||||
// }
|
||||
// }
|
||||
// } else {
|
||||
// fly = true;
|
||||
// //为空则认为是常规步骤,设定一分钟超时
|
||||
// CnSocketUtil.quitSend(param);
|
||||
// CnSocketUtil.sendToWebSocket(param.getUserPageId(), SourceOperateCodeEnum.SOCKET_TIMEOUT.getValue(), SourceOperateCodeEnum.SOCKET_TIMEOUT.getValue(), SourceOperateCodeEnum.SOCKET_TIMEOUT.getMsg(), null);
|
||||
// }
|
||||
// if (fly) {
|
||||
// socketContrastResponseService.backCheckState(param);
|
||||
// }
|
||||
// } else {
|
||||
// //如果是暂停操作后
|
||||
// FormalTestManager.stopTime += 60;
|
||||
// System.out.println("当前进入暂停操作超时函数-----------------" + FormalTestManager.stopTime);
|
||||
// if (FormalTestManager.stopTime > 600) {
|
||||
// CnSocketUtil.quitSend(param);
|
||||
// CnSocketUtil.sendToWebSocket(param.getUserPageId(), SourceOperateCodeEnum.FORMAL_REAL.getValue(), SourceOperateCodeEnum.STOP_TIMEOUT.getValue(), SourceOperateCodeEnum.STOP_TIMEOUT.getMsg(), null);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -155,7 +136,7 @@ public class NettyContrastClientHandler extends SimpleChannelInboundHandler<Stri
|
||||
System.out.println("连接socket服务端异常");
|
||||
} else if (cause instanceof IOException) {
|
||||
System.out.println("IOException caught: There was an I/O error.");
|
||||
CnSocketUtil.sendToWebSocket(param.getUserPageId(), SourceOperateCodeEnum.DEVICE_ERROR.getValue(), SourceOperateCodeEnum.DEVICE_ERROR.getValue(), SourceOperateCodeEnum.DEVICE_ERROR.getMsg(), null);
|
||||
WebServiceManager.sendDetectionErrorMessage(param.getUserPageId(), SourceOperateCodeEnum.DEVICE_ERROR);
|
||||
} else if (cause instanceof TimeoutException) {
|
||||
System.out.println("TimeoutException caught: Operation timed out.");
|
||||
} else if (cause instanceof ProtocolException) {
|
||||
@@ -163,10 +144,11 @@ public class NettyContrastClientHandler extends SimpleChannelInboundHandler<Stri
|
||||
} else {
|
||||
// 处理其他类型的异常
|
||||
System.out.println("Unknown exception caught: " + cause.getMessage());
|
||||
CnSocketUtil.sendToWebSocket(param.getUserPageId(), SourceOperateCodeEnum.DEVICE_ERROR.getValue(), SourceOperateCodeEnum.DEVICE_ERROR.getValue(), SourceOperateCodeEnum.DEVICE_ERROR.getMsg(), null);
|
||||
WebServiceManager.sendDetectionErrorMessage(param.getUserPageId(), SourceOperateCodeEnum.DEVICE_ERROR);
|
||||
}
|
||||
CnSocketUtil.quitSend(param);
|
||||
// socketContrastResponseService.backCheckState(param);
|
||||
CnSocketUtil.contrastSendquit(param.getUserPageId(), SourceOperateCodeEnum.QUIT_INIT_01, false);
|
||||
CnSocketUtil.contrastSendquit(param.getUserPageId(), SourceOperateCodeEnum.QUIT_INIT_02, false);
|
||||
CnSocketUtil.contrastSendquit(param.getUserPageId(), SourceOperateCodeEnum.QUIT_INIT_03, true);
|
||||
ctx.close();
|
||||
}
|
||||
|
||||
@@ -174,8 +156,14 @@ public class NettyContrastClientHandler extends SimpleChannelInboundHandler<Stri
|
||||
/**
|
||||
* 接收数据超时处理
|
||||
*/
|
||||
private void timeoutSend() {
|
||||
|
||||
private void timeoutSend(SourceOperateCodeEnum sourceOperateCodeEnum) {
|
||||
System.out.println("超时处理-----》" + "统计数据已超时----------------关闭");
|
||||
// 向前端推送超时消息
|
||||
SocketDataMsg webSend = new SocketDataMsg();
|
||||
webSend.setRequestId(sourceOperateCodeEnum.getValue());
|
||||
webSend.setData(sourceOperateCodeEnum.getMsg() + SourceResponseCodeEnum.RECEIVE_DATA_TIME_OUT.getMessage());
|
||||
webSend.setCode(SourceResponseCodeEnum.RECEIVE_DATA_TIME_OUT.getCode());
|
||||
WebServiceManager.sendMsg(param.getUserPageId(), MsgUtil.msgToWebData(webSend, FormalTestManager.devNameMapComm, 0));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.njcn.gather.detection.util.socket.cilent;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.njcn.gather.detection.handler.SocketDevResponseService;
|
||||
import com.njcn.gather.detection.pojo.enums.ResultEnum;
|
||||
import com.njcn.gather.detection.pojo.enums.SourceOperateCodeEnum;
|
||||
import com.njcn.gather.detection.pojo.param.PreDetectionParam;
|
||||
import com.njcn.gather.detection.pojo.vo.DevLineTestResult;
|
||||
@@ -10,7 +11,7 @@ import com.njcn.gather.detection.pojo.vo.WebSocketVO;
|
||||
import com.njcn.gather.detection.util.socket.CnSocketUtil;
|
||||
import com.njcn.gather.detection.util.socket.FormalTestManager;
|
||||
import com.njcn.gather.detection.util.socket.SocketManager;
|
||||
import com.njcn.gather.detection.util.socket.WebServiceManager;
|
||||
import com.njcn.gather.detection.util.socket.websocket.WebServiceManager;
|
||||
import com.njcn.gather.device.pojo.vo.PreDetection;
|
||||
import com.njcn.gather.script.pojo.po.SourceIssue;
|
||||
import com.njcn.gather.system.pojo.enums.DicDataEnum;
|
||||
@@ -21,227 +22,325 @@ import io.netty.handler.timeout.IdleState;
|
||||
import io.netty.handler.timeout.IdleStateEvent;
|
||||
import io.netty.handler.timeout.TimeoutException;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.ConnectException;
|
||||
import java.net.ProtocolException;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
||||
/**
|
||||
* @Description: 源客户端业务处理(示例)
|
||||
* Netty设备客户端处理器
|
||||
* <p>负责处理与被检测设备的Socket通信,包括:</p>
|
||||
* <ul>
|
||||
* <li>通道生命周期管理(建立、断开)</li>
|
||||
* <li>消息接收和处理</li>
|
||||
* <li>心跳超时处理</li>
|
||||
* <li>异常处理和恢复</li>
|
||||
* </ul>
|
||||
*
|
||||
* @Description: 设备客户端业务处理器
|
||||
* @Author: wr
|
||||
* @Date: 2024/12/10 14:16
|
||||
*/
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class NettyDevClientHandler extends SimpleChannelInboundHandler<String> {
|
||||
|
||||
/**
|
||||
* 闪变检测超时时间:20分钟(1300秒)
|
||||
*/
|
||||
private static final long FLICKER_TIMEOUT = 1300L;
|
||||
|
||||
/**
|
||||
* 统计数据检测超时时间:3分钟(180秒)
|
||||
*/
|
||||
private static final long STATISTICS_TIMEOUT = 180L;
|
||||
|
||||
/**
|
||||
* 实时数据检测超时时间:1分钟(60秒)
|
||||
*/
|
||||
private static final long REALTIME_TIMEOUT = 60L;
|
||||
|
||||
/**
|
||||
* 暂停操作超时时间:10分钟(600秒)
|
||||
*/
|
||||
private static final long STOP_TIMEOUT = 600L;
|
||||
|
||||
private final PreDetectionParam param;
|
||||
|
||||
private final SocketDevResponseService socketResponseService;
|
||||
|
||||
|
||||
/**
|
||||
* 当通道进行连接时推送消息
|
||||
* 当通道连接建立时的处理逻辑
|
||||
* <p>将关闭原有连接,并将新连接注册到SocketManager中</p>
|
||||
*
|
||||
* @param ctx
|
||||
* @param ctx 通道上下文
|
||||
* @throws Exception 连接异常
|
||||
*/
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
||||
System.out.println("客户端通道已建立" + ctx.channel().id());
|
||||
log.info("客户端通道已建立: {}", ctx.channel().id());
|
||||
|
||||
// 检查是否存在同一用户的老连接
|
||||
Channel channel = SocketManager.getChannelByUserId(param.getUserPageId() + CnSocketUtil.DEV_TAG);
|
||||
if (Objects.nonNull(channel)) {
|
||||
try {
|
||||
// 关闭老连接避免连接泄漏
|
||||
channel.close().sync();
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
log.error("关闭通道异常", e);
|
||||
}
|
||||
}
|
||||
// 注册新的连接到用户管理器
|
||||
SocketManager.addUser(param.getUserPageId() + CnSocketUtil.DEV_TAG, ctx.channel());
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理服务端消息消息信息
|
||||
*/
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws InterruptedException {
|
||||
System.out.println("devhandler接收server端数据>>>>>>" + msg);
|
||||
try {
|
||||
socketResponseService.deal(param, msg);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
CnSocketUtil.quitSend(param);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 当通道断线时,支持重连
|
||||
* 当通道断开时的清理工作
|
||||
* <p>关闭连接,清理用户映射,退出源设备发送</p>
|
||||
*
|
||||
* @param ctx
|
||||
* @param ctx 通道上下文
|
||||
* @throws Exception 关闭异常
|
||||
*/
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||
System.out.println("设备通讯客户端断线");
|
||||
log.warn("设备通讯客户端断线");
|
||||
ctx.close();
|
||||
SocketManager.removeUser(param.getUserPageId() + CnSocketUtil.DEV_TAG);
|
||||
CnSocketUtil.quitSendSource(param);
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户事件的回调方法(自定义事件用于心跳机制)
|
||||
* 处理从服务端接收到的消息
|
||||
* <p>将消息交给SocketDevResponseService进行具体处理</p>
|
||||
*
|
||||
* @param ctx
|
||||
* @param evt
|
||||
* @param ctx 通道上下文
|
||||
* @param msg 接收到的消息
|
||||
*/
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, String msg) {
|
||||
log.info("devhandler接收server端数据: {}", msg);
|
||||
try {
|
||||
socketResponseService.deal(param, msg);
|
||||
} catch (Exception e) {
|
||||
log.error("处理服务端消息异常", e);
|
||||
CnSocketUtil.quitSend(param);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 用户事件回调方法,主要用于处理心跳超时
|
||||
* <p>当触发READER_IDLE事件时,根据当前状态进行超时处理</p>
|
||||
*
|
||||
* @param ctx 通道上下文
|
||||
* @param evt 用户事件对象
|
||||
*/
|
||||
@Override
|
||||
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
|
||||
Boolean fly = false;
|
||||
if (evt instanceof IdleStateEvent) { //IdleState.在一段时间内没有收到任何消息时,会触发该事件
|
||||
if (((IdleStateEvent) evt).state() == IdleState.READER_IDLE) {
|
||||
System.out.println(LocalDateTime.now() + "devHandler触发读超时函数**************************************");
|
||||
if (!FormalTestManager.hasStopFlag) {
|
||||
if (CollUtil.isNotEmpty(SocketManager.getSourceList())) {
|
||||
SourceIssue sourceIssue = SocketManager.getSourceList().get(0);
|
||||
if (SocketManager.clockMap.containsKey(sourceIssue.getIndex())) {
|
||||
SocketManager.clockMap.put(sourceIssue.getIndex(), SocketManager.clockMap.get(sourceIssue.getIndex()) + 60L);
|
||||
} else {
|
||||
SocketManager.clockMap.put(sourceIssue.getIndex(), 60L);
|
||||
}
|
||||
|
||||
if (sourceIssue.getType().equals(DicDataEnum.F.getCode())) {
|
||||
//闪变,正常抛一轮最大等待20分钟超时
|
||||
if (SocketManager.clockMap.get(sourceIssue.getIndex()) > 1300) {
|
||||
fly = true;
|
||||
System.out.println("超时处理-----》" + sourceIssue.getType() + "已超时----------------关闭");
|
||||
CnSocketUtil.quitSend(param);
|
||||
timeoutSend(sourceIssue);
|
||||
}
|
||||
} else if (sourceIssue.getType().equals(DicDataEnum.VOLTAGE.getCode()) || sourceIssue.getType().equals(DicDataEnum.HP.getCode())) {
|
||||
//统计数据项,正常抛一轮数据,超时
|
||||
if (SocketManager.clockMap.get(sourceIssue.getIndex()) > 180) {
|
||||
fly = true;
|
||||
CnSocketUtil.quitSend(param);
|
||||
System.out.println("超时处理-----》" + sourceIssue.getType() + "已超时----------------关闭");
|
||||
timeoutSend(sourceIssue);
|
||||
}
|
||||
} else {
|
||||
//实时数据
|
||||
if (SocketManager.clockMap.get(sourceIssue.getIndex()) > 60) {
|
||||
fly = true;
|
||||
CnSocketUtil.quitSend(param);
|
||||
System.out.println("超时处理-----》" + sourceIssue.getType() + "已超时----------------关闭");
|
||||
timeoutSend(sourceIssue);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fly = true;
|
||||
//为空则认为是常规步骤,设定一分钟超时
|
||||
CnSocketUtil.quitSend(param);
|
||||
CnSocketUtil.sendToWebSocket(param.getUserPageId(), SourceOperateCodeEnum.SOCKET_TIMEOUT.getValue(), SourceOperateCodeEnum.SOCKET_TIMEOUT.getValue(), SourceOperateCodeEnum.SOCKET_TIMEOUT.getMsg(), null);
|
||||
}
|
||||
if (fly) {
|
||||
socketResponseService.backCheckState(param);
|
||||
}
|
||||
} else {
|
||||
//如果是暂停操作后
|
||||
FormalTestManager.stopTime += 60;
|
||||
System.out.println("当前进入暂停操作超时函数-----------------" + FormalTestManager.stopTime);
|
||||
if (FormalTestManager.stopTime > 600) {
|
||||
CnSocketUtil.quitSend(param);
|
||||
CnSocketUtil.sendToWebSocket(param.getUserPageId(), SourceOperateCodeEnum.FORMAL_REAL.getValue(), SourceOperateCodeEnum.STOP_TIMEOUT.getValue(), SourceOperateCodeEnum.STOP_TIMEOUT.getMsg(), null);
|
||||
}
|
||||
}
|
||||
// 检查是否为读取空闲事件(心跳超时)
|
||||
if (evt instanceof IdleStateEvent && ((IdleStateEvent) evt).state() == IdleState.READER_IDLE) {
|
||||
log.warn("devHandler触发读超时函数: {}", LocalDateTime.now());
|
||||
// 根据是否有停止标志采取不同的超时处理策略
|
||||
if (!FormalTestManager.hasStopFlag) {
|
||||
// 正常检测中的超时处理
|
||||
handleReadTimeout();
|
||||
} else {
|
||||
// 暂停状态下的超时处理
|
||||
handleStopTimeout();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 处理器被添加到管道时的回调
|
||||
*
|
||||
* @param ctx 通道上下文
|
||||
*/
|
||||
@Override
|
||||
public void handlerAdded(ChannelHandlerContext ctx) {
|
||||
System.out.println("有通道准备接入" + ctx.channel());
|
||||
log.info("有通道准备接入: {}", ctx.channel());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 异常捕获处理
|
||||
* <p>捕获并处理各种类型的异常,执行清理工作</p>
|
||||
*
|
||||
* @param ctx 通道上下文
|
||||
* @param cause 异常原因
|
||||
*/
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||
System.out.println("捕获到设备服务异常。。。。。。。");
|
||||
// 处理异常,例如记录日志、关闭连接等
|
||||
cause.printStackTrace();
|
||||
// 根据异常类型进行不同的处理
|
||||
if (cause instanceof ConnectException) {
|
||||
// 处理连接异常,例如重试连接或记录特定的连接错误信息
|
||||
System.out.println("连接socket服务端异常");
|
||||
|
||||
} else if (cause instanceof IOException) {
|
||||
// 处理I/O异常,例如读写错误
|
||||
System.out.println("IOException caught: There was an I/O error.");
|
||||
CnSocketUtil.sendToWebSocket(param.getUserPageId(), SourceOperateCodeEnum.DEVICE_ERROR.getValue(), SourceOperateCodeEnum.DEVICE_ERROR.getValue(), SourceOperateCodeEnum.DEVICE_ERROR.getMsg(), null);
|
||||
|
||||
// 例如,可以记录更详细的I/O错误信息
|
||||
} else if (cause instanceof TimeoutException) {
|
||||
// 处理超时异常
|
||||
System.out.println("TimeoutException caught: Operation timed out.");
|
||||
// 可以根据业务逻辑决定是否重试或记录超时信息
|
||||
} else if (cause instanceof ProtocolException) {
|
||||
// 处理协议异常,例如消息格式不正确
|
||||
System.out.println("ProtocolException caught: Invalid protocol message.");
|
||||
// 可以记录协议错误信息或向客户端发送错误响应
|
||||
} else {
|
||||
// 处理其他类型的异常
|
||||
System.out.println("Unknown exception caught: " + cause.getMessage());
|
||||
CnSocketUtil.sendToWebSocket(param.getUserPageId(), SourceOperateCodeEnum.DEVICE_ERROR.getValue(), SourceOperateCodeEnum.DEVICE_ERROR.getValue(), SourceOperateCodeEnum.DEVICE_ERROR.getMsg(), null);
|
||||
// 可以记录未知异常信息
|
||||
}
|
||||
log.error("捕获到设备服务异常", cause);
|
||||
handleSpecificException(cause);
|
||||
// 统一清理工作
|
||||
CnSocketUtil.quitSend(param);
|
||||
CnSocketUtil.quitSendSource(param);
|
||||
socketResponseService.backCheckState(param);
|
||||
ctx.close();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 发送业务消息时候开启计时器,
|
||||
* @param requestId
|
||||
* 处理特定类型的异常
|
||||
* <p>根据异常类型进行相应的日志记录和错误通知</p>
|
||||
*
|
||||
* @param cause 异常对象
|
||||
*/
|
||||
/* private void scheduleTimeoutTask(String requestId) {
|
||||
ScheduledFuture<?> future = scheduler.schedule(() -> {
|
||||
if (requestTimeoutTasks.containsKey(requestId)) {
|
||||
// 处理超时逻辑
|
||||
System.out.println("Business request with ID " + requestId + " timed out.");
|
||||
requestTimeoutTasks.remove(requestId);
|
||||
ctx.close();
|
||||
}
|
||||
}, BUSINESS_REQUEST_TIMEOUT, TimeUnit.MILLISECONDS);
|
||||
requestTimeoutTasks.put(requestId, future);
|
||||
}*/
|
||||
private void handleSpecificException(Throwable cause) {
|
||||
if (cause instanceof ConnectException) {
|
||||
log.error("连接socket服务端异常");
|
||||
} else if (cause instanceof IOException) {
|
||||
log.error("IO异常: {}", cause.getMessage());
|
||||
WebServiceManager.sendDetectionErrorMessage(param.getUserPageId(), SourceOperateCodeEnum.DEVICE_ERROR);
|
||||
} else if (cause instanceof TimeoutException) {
|
||||
log.error("操作超时: {}", cause.getMessage());
|
||||
} else if (cause instanceof ProtocolException) {
|
||||
log.error("协议异常: {}", cause.getMessage());
|
||||
} else {
|
||||
log.error("未知异常: {}", cause.getMessage());
|
||||
WebServiceManager.sendDetectionErrorMessage(param.getUserPageId(), SourceOperateCodeEnum.DEVICE_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 超时后的处理
|
||||
* 处理读取超时事件
|
||||
* <p>检查源列表,更新超时计数器,判断是否超时并处理</p>
|
||||
*/
|
||||
private void handleReadTimeout() {
|
||||
if (CollUtil.isNotEmpty(SocketManager.getSourceList())) {
|
||||
// 获取当前正在检测的源问题(取第一个)
|
||||
SourceIssue sourceIssue = SocketManager.getSourceList().get(0);
|
||||
// 更新该源问题的超时计数器
|
||||
updateTimeoutCounter(sourceIssue);
|
||||
// 根据检测类型判断是否已超时
|
||||
if (isTimeout(sourceIssue)) {
|
||||
handleTimeout(sourceIssue);
|
||||
}
|
||||
} else {
|
||||
// 源列表为空,认为是常规步骤的超时,默认一分钟超时
|
||||
CnSocketUtil.quitSend(param);
|
||||
WebServiceManager.sendDetectionErrorMessage(param.getUserPageId(), SourceOperateCodeEnum.SOCKET_TIMEOUT);
|
||||
socketResponseService.backCheckState(param);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理暂停操作的超时事件
|
||||
* <p>当检测被暂停时,统计暂停时间,超过限制后发送超时通知</p>
|
||||
*/
|
||||
private void handleStopTimeout() {
|
||||
FormalTestManager.stopTime += 60;
|
||||
log.warn("当前进入暂停操作超时函数,停止时间: {}", FormalTestManager.stopTime);
|
||||
if (FormalTestManager.stopTime > STOP_TIMEOUT) {
|
||||
CnSocketUtil.quitSend(param);
|
||||
WebServiceManager.sendDetectionErrorMessage(param.getUserPageId(), SourceOperateCodeEnum.FORMAL_REAL.getValue(), SourceOperateCodeEnum.STOP_TIMEOUT);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新指定源问题的超时计数器
|
||||
* <p>每次调用时增加60秒,用于统计累计超时时间</p>
|
||||
*
|
||||
* @param sourceIssue 源问题对象
|
||||
*/
|
||||
private void updateTimeoutCounter(SourceIssue sourceIssue) {
|
||||
Integer index = sourceIssue.getIndex();
|
||||
if (SocketManager.clockMap.containsKey(index)) {
|
||||
SocketManager.clockMap.put(index, SocketManager.clockMap.get(index) + 60L);
|
||||
} else {
|
||||
SocketManager.clockMap.put(index, 60L);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据检测类型判断是否已超时
|
||||
* <p>不同检测类型有不同的超时阈值:</p>
|
||||
* <ul>
|
||||
* <li>闪变检测:20分钟</li>
|
||||
* <li>统计数据:3分钟</li>
|
||||
* <li>实时数据:1分钟</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param sourceIssue 源问题对象
|
||||
* @return true 如果已超时,false 否则
|
||||
*/
|
||||
private boolean isTimeout(SourceIssue sourceIssue) {
|
||||
long currentTime = SocketManager.clockMap.get(sourceIssue.getIndex());
|
||||
String type = sourceIssue.getType();
|
||||
|
||||
// 根据不同检测类型使用不同的超时阈值
|
||||
if (DicDataEnum.F.getCode().equals(type)) {
|
||||
// 闪变检测:需要更长时间,20分钟超时
|
||||
return currentTime >= FLICKER_TIMEOUT;
|
||||
} else if (DicDataEnum.VOLTAGE.getCode().equals(type) || DicDataEnum.HP.getCode().equals(type)) {
|
||||
// 统计数据类型(电压、谐波):中等时间,3分钟超时
|
||||
return currentTime >= STATISTICS_TIMEOUT;
|
||||
} else {
|
||||
// 实时数据类型:短时间,1分钟超时
|
||||
return currentTime >= REALTIME_TIMEOUT;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行超时处理操作
|
||||
* <p>记录超时日志,退出发送,发送超时结果,恢复检测状态</p>
|
||||
*
|
||||
* @param sourceIssue 源问题对象
|
||||
*/
|
||||
private void handleTimeout(SourceIssue sourceIssue) {
|
||||
log.warn("超时处理 - {} 已超时,关闭连接", sourceIssue.getType());
|
||||
CnSocketUtil.quitSend(param);
|
||||
timeoutSend(sourceIssue);
|
||||
socketResponseService.backCheckState(param);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送超时结果
|
||||
* <p>为所有设备创建超时的检测结果,并通过WebSocket发送给客户端</p>
|
||||
*
|
||||
* @param sourceIssue 源问题对象
|
||||
*/
|
||||
private void timeoutSend(SourceIssue sourceIssue) {
|
||||
List<DevLineTestResult> devListRes = new ArrayList<>();
|
||||
FormalTestManager.devList.forEach(dev -> {
|
||||
DevLineTestResult devLineTestResult = new DevLineTestResult();
|
||||
devLineTestResult.setDeviceId(dev.getDevId());
|
||||
devLineTestResult.setDeviceName(dev.getDevName());
|
||||
List<Integer> resultFlagList = new ArrayList<>();
|
||||
List<PreDetection.MonitorListDTO> monitorListDTOList = dev.getMonitorList();
|
||||
monitorListDTOList.forEach(i -> resultFlagList.add(3));
|
||||
devLineTestResult.setChnResult(resultFlagList.toArray(new Integer[monitorListDTOList.size()]));
|
||||
devListRes.add(devLineTestResult);
|
||||
});
|
||||
List<DevLineTestResult> devListRes = FormalTestManager.devList.stream()
|
||||
.map(this::createTimeoutResult)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
WebSocketVO<List<DevLineTestResult>> socketVO = new WebSocketVO<>();
|
||||
socketVO.setRequestId(sourceIssue.getType() + CnSocketUtil.END_TAG);
|
||||
socketVO.setOperateCode(sourceIssue.getType());
|
||||
socketVO.setData(devListRes);
|
||||
|
||||
WebServiceManager.sendMsg(param.getUserPageId(), JSON.toJSONString(socketVO));
|
||||
}
|
||||
|
||||
/**
|
||||
* 为指定设备创建超时的检测结果
|
||||
* <p>将所有监测点的结果设置为超时标志值</p>
|
||||
*
|
||||
* @param dev 设备对象
|
||||
* @return 设备检测结果
|
||||
*/
|
||||
private DevLineTestResult createTimeoutResult(PreDetection dev) {
|
||||
DevLineTestResult devLineTestResult = new DevLineTestResult();
|
||||
devLineTestResult.setDeviceId(dev.getDevId());
|
||||
devLineTestResult.setDeviceName(dev.getDevName());
|
||||
|
||||
Integer[] resultFlags = dev.getMonitorList().stream()
|
||||
.map(monitor -> ResultEnum.NETWORK_TIMEOUT)
|
||||
.toArray(Integer[]::new);
|
||||
devLineTestResult.setChnResult(resultFlags);
|
||||
|
||||
return devLineTestResult;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,135 +5,136 @@ import com.njcn.gather.detection.pojo.enums.SourceOperateCodeEnum;
|
||||
import com.njcn.gather.detection.pojo.param.PreDetectionParam;
|
||||
import com.njcn.gather.detection.util.socket.CnSocketUtil;
|
||||
import com.njcn.gather.detection.util.socket.SocketManager;
|
||||
import com.njcn.gather.detection.util.socket.websocket.WebServiceManager;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import io.netty.handler.timeout.IdleState;
|
||||
import io.netty.handler.timeout.IdleStateEvent;
|
||||
import io.netty.handler.timeout.TimeoutException;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.ConnectException;
|
||||
import java.net.ProtocolException;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
|
||||
/**
|
||||
* @Description: 源客户端业务处理(示例)
|
||||
* @Author: wr
|
||||
* @Date: 2024/12/10 14:16
|
||||
* 源设备Netty客户端通道处理器
|
||||
* 负责处理程控源设备的Socket通信
|
||||
*
|
||||
* @author wr
|
||||
* @since 2024/12/10
|
||||
*/
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class NettySourceClientHandler extends SimpleChannelInboundHandler<String> {
|
||||
|
||||
/** 检测参数对象,包含用户页面ID等信息 */
|
||||
private final PreDetectionParam webUser;
|
||||
private final String sourceTag = "_Source";
|
||||
/** 源设备响应处理服务 */
|
||||
private final SocketSourceResponseService sourceResponseService;
|
||||
|
||||
/**
|
||||
* 当通道进行连接时推送消息
|
||||
*
|
||||
* @param ctx
|
||||
* 通道激活回调,将通道注册到SocketManager
|
||||
*/
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
||||
System.out.println("客户端通道已建立" + ctx.channel().id());
|
||||
|
||||
SocketManager.addUser(webUser.getUserPageId() + sourceTag, ctx.channel());
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理服务端消息信息
|
||||
*/
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws InterruptedException {
|
||||
System.out.println("source接收server端数据>>>>>>" + msg);
|
||||
try {
|
||||
sourceResponseService.deal(webUser, msg);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
CnSocketUtil.quitSend(webUser);
|
||||
// 验证webUser参数有效性
|
||||
if (webUser == null) {
|
||||
log.warn("源设备客户端通道已建立但webUser为空, channelId: {}", ctx.channel().id());
|
||||
return;
|
||||
}
|
||||
|
||||
String userId = webUser.getUserPageId();
|
||||
log.info("源设备客户端通道已建立, channelId: {}, userId: {}", ctx.channel().id(), userId);
|
||||
|
||||
// 将通道注册到Socket管理器,便于后续消息推送
|
||||
if (StrUtil.isNotBlank(userId)) {
|
||||
SocketManager.addUser(userId + CnSocketUtil.SOURCE_TAG, ctx.channel());
|
||||
} else {
|
||||
log.warn("源设备userId为空或空白,跳过通道注册, channelId: {}", ctx.channel().id());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 当通道断线时,支持重连
|
||||
*
|
||||
* @param ctx
|
||||
* 通道断开回调,清理资源
|
||||
*/
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||
System.out.println("源通讯客户端断线");
|
||||
String userId = webUser != null ? webUser.getUserPageId() : "unknown";
|
||||
log.warn("源通讯客户端断线, channelId: {}, userId: {}", ctx.channel().id(), userId);
|
||||
// 关闭通道连接
|
||||
ctx.close();
|
||||
SocketManager.removeUser(webUser.getUserPageId() + sourceTag);
|
||||
// System.out.println("断线了......" + ctx.channel());
|
||||
// ctx.channel().eventLoop().schedule(() -> {
|
||||
// System.out.println("断线重连......");
|
||||
// //重连
|
||||
// NettyClient.connect();
|
||||
// }, 3L, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户事件的回调方法(自定义事件用于心跳机制)
|
||||
*
|
||||
* @param ctx
|
||||
* @param evt
|
||||
*/
|
||||
@Override
|
||||
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
|
||||
//如果是空闲状态事件
|
||||
if (evt instanceof IdleStateEvent) {
|
||||
if (((IdleStateEvent) evt).state() == IdleState.WRITER_IDLE) {
|
||||
//发送ping 保持心跳链接
|
||||
/* SocketMsg<String> msg = new SocketMsg<>();
|
||||
msg.setRequestId("yxt");
|
||||
msg.setOperateCode(SourceOperateCodeEnum.HEARTBEAT.getValue());
|
||||
msg.setData("");
|
||||
ctx.writeAndFlush(JSON.toJSONString(msg)+"\n");*/
|
||||
}
|
||||
} else {
|
||||
//防止堆栈溢出
|
||||
//userEventTriggered(ctx, evt);
|
||||
// 从Socket管理器中移除用户通道映射
|
||||
if (webUser != null && StrUtil.isNotBlank(userId)) {
|
||||
SocketManager.removeUser(userId + CnSocketUtil.SOURCE_TAG);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 处理源设备响应消息
|
||||
*/
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws InterruptedException {
|
||||
// 验证用户参数
|
||||
if (webUser == null) {
|
||||
log.warn("源设备消息处理失败: webUser为空, message: {}", msg);
|
||||
return;
|
||||
}
|
||||
|
||||
String userId = webUser.getUserPageId();
|
||||
log.debug("源设备接收服务端数据, userId: {}, message: {}", userId, msg);
|
||||
|
||||
try {
|
||||
// 委托给专门的响应处理服务处理业务逻辑
|
||||
sourceResponseService.deal(webUser, msg);
|
||||
} catch (Exception e) {
|
||||
log.error("源设备消息处理异常, userId: {}, message: {}", userId, msg, e);
|
||||
// 发生异常时退出发送,避免后续问题
|
||||
CnSocketUtil.quitSend(webUser);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void handlerAdded(ChannelHandlerContext ctx) {
|
||||
System.out.println("有通道准备接入" + ctx.channel().id());
|
||||
// 记录处理器添加事件,用于调试
|
||||
String userId = webUser != null ? webUser.getUserPageId() : "unknown";
|
||||
log.debug("源设备通道准备接入, channelId: {}, userId: {}", ctx.channel().id(), userId);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||
// 处理异常,例如记录日志、关闭连接等
|
||||
System.out.println("捕获到源异常。。。。。。。");
|
||||
cause.printStackTrace();
|
||||
// 根据异常类型进行不同的处理
|
||||
String userId = webUser != null ? webUser.getUserPageId() : "unknown";
|
||||
String channelId = ctx.channel().id().toString();
|
||||
|
||||
// 根据异常类型进行分类处理和日志记录
|
||||
if (cause instanceof ConnectException) {
|
||||
// 处理连接异常,例如重试连接或记录特定的连接错误信息
|
||||
System.out.println("连接socket服务端异常");
|
||||
|
||||
// 连接异常:网络连接失败
|
||||
log.error("连接源设备Socket服务端异常, channelId: {}, userId: {}", channelId, userId, cause);
|
||||
} else if (cause instanceof IOException) {
|
||||
// 处理I/O异常,例如读写错误
|
||||
CnSocketUtil.sendToWebSocket(webUser.getUserPageId(), SourceOperateCodeEnum.SERVER_ERROR.getValue(), SourceOperateCodeEnum.SERVER_ERROR.getValue(), SourceOperateCodeEnum.SERVER_ERROR.getMsg(), null);
|
||||
|
||||
// 例如,可以记录更详细的I/O错误信息
|
||||
// IO异常:数据传输错误,需通知前端
|
||||
log.error("源设备IO异常, channelId: {}, userId: {}", channelId, userId, cause);
|
||||
// 向前端发送服务器错误消息
|
||||
if (StrUtil.isNotBlank(userId) && !"unknown".equals(userId)) {
|
||||
WebServiceManager.sendDetectionErrorMessage(userId, SourceOperateCodeEnum.SERVER_ERROR);
|
||||
}
|
||||
} else if (cause instanceof TimeoutException) {
|
||||
// 处理超时异常
|
||||
System.out.println("TimeoutException caught: Operation timed out.");
|
||||
// 可以根据业务逻辑决定是否重试或记录超时信息
|
||||
// 超时异常:通信响应超时
|
||||
log.warn("源设备通信超时, channelId: {}, userId: {}", channelId, userId, cause);
|
||||
} else if (cause instanceof ProtocolException) {
|
||||
// 处理协议异常,例如消息格式不正确
|
||||
System.out.println("ProtocolException caught: Invalid protocol message.");
|
||||
// 可以记录协议错误信息或向客户端发送错误响应
|
||||
// 协议异常:数据格式不符合协议规范
|
||||
log.error("源设备协议异常, channelId: {}, userId: {}", channelId, userId, cause);
|
||||
} else {
|
||||
// 处理其他类型的异常
|
||||
System.out.println("Unknown exception caught: " + cause.getMessage());
|
||||
// 可以记录未知异常信息
|
||||
// 其他未知异常
|
||||
log.error("源设备未知异常, channelId: {}, userId: {}, message: {}", channelId, userId, cause.getMessage(), cause);
|
||||
}
|
||||
|
||||
// 发生异常时关闭通道
|
||||
ctx.close();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
package com.njcn.gather.detection.util.socket.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Socket连接配置管理类
|
||||
* 定义哪些requestId需要建立通道连接,以及IP/PORT配置
|
||||
*
|
||||
* @Author: hongawen
|
||||
* @Date: 2024/12/10
|
||||
*/
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "socket")
|
||||
public class SocketConnectionConfig {
|
||||
|
||||
/**
|
||||
* 程控源设备配置
|
||||
*/
|
||||
private SourceConfig source = new SourceConfig();
|
||||
|
||||
/**
|
||||
* 被检设备配置
|
||||
*/
|
||||
private DeviceConfig device = new DeviceConfig();
|
||||
|
||||
@Data
|
||||
public static class SourceConfig {
|
||||
/**
|
||||
* 程控源IP地址
|
||||
*/
|
||||
private String ip;
|
||||
|
||||
/**
|
||||
* 程控源端口号
|
||||
*/
|
||||
private Integer port;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class DeviceConfig {
|
||||
/**
|
||||
* 被检设备IP地址
|
||||
*/
|
||||
private String ip;
|
||||
|
||||
/**
|
||||
* 被检设备端口号
|
||||
*/
|
||||
private Integer port;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取程控源配置
|
||||
*/
|
||||
public SourceConfig getSource() {
|
||||
return source;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取被检设备配置
|
||||
*/
|
||||
public DeviceConfig getDevice() {
|
||||
return device;
|
||||
}
|
||||
|
||||
/**
|
||||
* 需要建立程控源通道的requestId集合
|
||||
* 这些requestId在发送消息时,如果程控源通道不存在,会自动建立连接
|
||||
*/
|
||||
private static final Set<String> SOURCE_CONNECTION_REQUEST_IDS = new HashSet<>(Arrays.asList(
|
||||
// 源通讯检测
|
||||
"yjc_ytxjy"
|
||||
// 可以根据实际业务需求添加更多requestId
|
||||
));
|
||||
|
||||
/**
|
||||
* 需要建立被检设备通道的requestId集合
|
||||
* 这些requestId在发送消息时,如果被检设备通道不存在,会自动建立连接
|
||||
*/
|
||||
private static final Set<String> DEVICE_CONNECTION_REQUEST_IDS = new HashSet<>(Arrays.asList(
|
||||
// 连接建立
|
||||
"yjc_sbtxjy",
|
||||
// ftp文件传送指令
|
||||
"FTP_SEND$01"
|
||||
// 可以根据实际业务需求添加更多requestId
|
||||
));
|
||||
|
||||
/**
|
||||
* 检查指定的requestId是否需要建立程控源连接
|
||||
*
|
||||
* @param requestId 请求ID
|
||||
* @return boolean true:需要建立连接, false:不需要建立连接
|
||||
*/
|
||||
public static boolean needsSourceConnection(String requestId) {
|
||||
return SOURCE_CONNECTION_REQUEST_IDS.contains(requestId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查指定的requestId是否需要建立被检设备连接
|
||||
*
|
||||
* @param requestId 请求ID
|
||||
* @return boolean true:需要建立连接, false:不需要建立连接
|
||||
*/
|
||||
public static boolean needsDeviceConnection(String requestId) {
|
||||
return DEVICE_CONNECTION_REQUEST_IDS.contains(requestId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加需要建立程控源连接的requestId
|
||||
* 支持运行时动态添加
|
||||
*
|
||||
* @param requestId 请求ID
|
||||
*/
|
||||
public static void addSourceConnectionRequestId(String requestId) {
|
||||
SOURCE_CONNECTION_REQUEST_IDS.add(requestId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加需要建立被检设备连接的requestId
|
||||
* 支持运行时动态添加
|
||||
*
|
||||
* @param requestId 请求ID
|
||||
*/
|
||||
public static void addDeviceConnectionRequestId(String requestId) {
|
||||
DEVICE_CONNECTION_REQUEST_IDS.add(requestId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除程控源连接requestId
|
||||
*
|
||||
* @param requestId 请求ID
|
||||
*/
|
||||
public static void removeSourceConnectionRequestId(String requestId) {
|
||||
SOURCE_CONNECTION_REQUEST_IDS.remove(requestId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除被检设备连接requestId
|
||||
*
|
||||
* @param requestId 请求ID
|
||||
*/
|
||||
public static void removeDeviceConnectionRequestId(String requestId) {
|
||||
DEVICE_CONNECTION_REQUEST_IDS.remove(requestId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有需要建立程控源连接的requestId集合(只读)
|
||||
*
|
||||
* @return Set<String> requestId集合
|
||||
*/
|
||||
public static Set<String> getSourceConnectionRequestIds() {
|
||||
return new HashSet<>(SOURCE_CONNECTION_REQUEST_IDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有需要建立被检设备连接的requestId集合(只读)
|
||||
*
|
||||
* @return Set<String> requestId集合
|
||||
*/
|
||||
public static Set<String> getDeviceConnectionRequestIds() {
|
||||
return new HashSet<>(DEVICE_CONNECTION_REQUEST_IDS);
|
||||
}
|
||||
}
|
||||
@@ -60,7 +60,7 @@ public class NettyServer {
|
||||
ch.pipeline()
|
||||
//空闲状态的handler
|
||||
// 添加LineBasedFrameDecoder来按行分割数据
|
||||
.addLast(new LineBasedFrameDecoder(10240))
|
||||
.addLast(new LineBasedFrameDecoder(10240*2))
|
||||
.addLast(new StringDecoder(CharsetUtil.UTF_8))
|
||||
.addLast(new StringEncoder(CharsetUtil.UTF_8))
|
||||
.addLast(new DevNettyServerHandler());
|
||||
@@ -107,7 +107,7 @@ public class NettyServer {
|
||||
ch.pipeline()
|
||||
//空闲状态的handler
|
||||
// 添加LineBasedFrameDecoder来按行分割数据
|
||||
.addLast(new LineBasedFrameDecoder(10240))
|
||||
.addLast(new LineBasedFrameDecoder(10240*2))
|
||||
.addLast(new StringDecoder(CharsetUtil.UTF_8))
|
||||
.addLast(new StringEncoder(CharsetUtil.UTF_8))
|
||||
.addLast(new SourceNettyServerHandler());
|
||||
|
||||
@@ -1,169 +0,0 @@
|
||||
package com.njcn.gather.detection.util.socket.web;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.njcn.gather.detection.pojo.param.PreDetectionParam;
|
||||
import com.njcn.gather.detection.util.socket.CnSocketUtil;
|
||||
import com.njcn.gather.detection.util.socket.WebServiceManager;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
|
||||
import io.netty.handler.timeout.IdleStateEvent;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
|
||||
/**
|
||||
* @Description: 泛型 代表的是处理数据的单位
|
||||
* TextWebSocketFrame : 文本信息帧
|
||||
* @Author: wr
|
||||
* @Date: 2024/12/10 13:56
|
||||
*/
|
||||
|
||||
|
||||
@Slf4j
|
||||
public class WebSocketHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
|
||||
|
||||
private int times;
|
||||
|
||||
private final static String QUESTION_MARK = "?";
|
||||
private final static String EQUAL_TO = "=";
|
||||
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
||||
System.out.println("webSocket服务端通道已建立" + ctx.channel().id());
|
||||
super.channelActive(ctx);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||
//首次连接是FullHttpRequest,把用户id和对应的channel对象存储起来
|
||||
if (null != msg && msg instanceof FullHttpRequest) {
|
||||
FullHttpRequest request = (FullHttpRequest) msg;
|
||||
String uri = request.uri();
|
||||
String userId = getUrlParams(uri);
|
||||
WebServiceManager.addUser(userId, ctx.channel());
|
||||
log.info("登录的用户id是:{}", userId);
|
||||
//如果url包含参数,需要处理
|
||||
if (uri.contains(QUESTION_MARK)) {
|
||||
String newUri = uri.substring(0, uri.indexOf(QUESTION_MARK));
|
||||
request.setUri(newUri);
|
||||
}
|
||||
|
||||
} else if (msg instanceof TextWebSocketFrame) {
|
||||
//正常的TEXT消息类型
|
||||
TextWebSocketFrame frame = (TextWebSocketFrame) msg;
|
||||
//log.info("webSocket服务器收到客户端心跳信息:{}", frame.text());
|
||||
if ("alive".equals(frame.text())) {
|
||||
//System.out.println("webSocket心跳收到时间………………………………………………………………"+LocalDateTime.now());
|
||||
times = 0;
|
||||
TextWebSocketFrame wd = new TextWebSocketFrame("over");
|
||||
ctx.channel().writeAndFlush(wd);
|
||||
}
|
||||
}
|
||||
super.channelRead(ctx, msg);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据用户地址获取用户名 ws://127.0.0.1:7777/hello?name=aa
|
||||
*
|
||||
* @param url
|
||||
* @return
|
||||
*/
|
||||
private static String getUrlParams(String url) {
|
||||
if (!url.contains(EQUAL_TO)) {
|
||||
return null;
|
||||
}
|
||||
String userId = url.substring(url.indexOf(EQUAL_TO) + 1);
|
||||
return userId;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) {
|
||||
/* System.out.println("服务端消息 == " + msg.text());
|
||||
if(msg.text().equals("下发指令")){
|
||||
*//**
|
||||
* 处理对应消息
|
||||
* 1.先下发所要操作的流程信息
|
||||
* 2.组装对应的入参信息
|
||||
* 3.再用socket信息返回结束
|
||||
*//*
|
||||
//NettyClient.socketClient(msg.text(),new NettySourceClientHandler());
|
||||
|
||||
}*/
|
||||
|
||||
//可以直接调用text 拿到文本信息帧中的信息
|
||||
/* Channel channel = ctx.channel();
|
||||
TextWebSocketFrame resp = new TextWebSocketFrame(msg.text());
|
||||
channel.writeAndFlush(resp);*/
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handlerAdded(ChannelHandlerContext ctx) {
|
||||
//WebServiceManager.addUser(userId, ctx.channel());
|
||||
System.out.println("webSocket有新的连接接入:" + ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handlerRemoved(ChannelHandlerContext ctx) {
|
||||
// 假设用户 ID 是从某个地方获取的,这里简单示例为 "userId"
|
||||
System.out.println("weoSocket客户端退出: " + ctx.channel().id());
|
||||
WebServiceManager.removeChannel(ctx.channel().id().toString());
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext ctx) {
|
||||
System.out.println("weoSocket断线" + ctx.channel().id());
|
||||
ctx.close();
|
||||
|
||||
PreDetectionParam preDetectionParam = WebServiceManager.getPreDetectionParam();
|
||||
if (ObjectUtil.isNotNull(preDetectionParam)) {
|
||||
CnSocketUtil.quitSendSource(preDetectionParam); // 能否在这里关闭源socket连接?
|
||||
CnSocketUtil.quitSend(preDetectionParam);
|
||||
} else {
|
||||
preDetectionParam = new PreDetectionParam();
|
||||
preDetectionParam.setUserPageId("cdf");
|
||||
CnSocketUtil.quitSend(preDetectionParam);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
|
||||
IdleStateEvent event = (IdleStateEvent) evt;
|
||||
String eventDesc = null;
|
||||
switch (event.state()) {
|
||||
case READER_IDLE:
|
||||
eventDesc = "读空闲";
|
||||
System.out.println("c端心跳检测发生超时事件--" + eventDesc);
|
||||
times++;
|
||||
if (times > 3) {
|
||||
System.out.println("c端心跳检测空闲次数超过三次 关闭连接");
|
||||
ctx.channel().close();
|
||||
WebServiceManager.removeChannel(ctx.channel().id().toString());
|
||||
}
|
||||
break;
|
||||
case WRITER_IDLE:
|
||||
eventDesc = "写空闲";
|
||||
break;
|
||||
case ALL_IDLE:
|
||||
eventDesc = "读写空闲";
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
//super.userEventTriggered(ctx, evt);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||
cause.printStackTrace();
|
||||
ctx.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
package com.njcn.gather.detection.util.socket.web;
|
||||
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import io.netty.handler.codec.http.HttpObjectAggregator;
|
||||
import io.netty.handler.codec.http.HttpResponseDecoder;
|
||||
import io.netty.handler.codec.http.HttpServerCodec;
|
||||
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
|
||||
import io.netty.handler.stream.ChunkedWriteHandler;
|
||||
import io.netty.handler.timeout.IdleStateHandler;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* @Description: webSocket服务端自定义配置
|
||||
* @Author: wr
|
||||
* @Date: 2024/12/10 14:20
|
||||
*/
|
||||
|
||||
public class WebSocketInitializer extends ChannelInitializer<SocketChannel> {
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
protected void initChannel(SocketChannel ch) throws Exception {
|
||||
ChannelPipeline pipeline = ch.pipeline();
|
||||
//设置心跳机制
|
||||
// ch.pipeline().addLast(new IdleStateHandler(5, 0, 0));
|
||||
//增加编解码器 的另一种方式
|
||||
pipeline.addLast(new HttpServerCodec());
|
||||
pipeline.addLast(new HttpResponseDecoder());
|
||||
//块方式写的处理器 适合处理大数据
|
||||
pipeline.addLast(new ChunkedWriteHandler());
|
||||
//聚合
|
||||
pipeline.addLast(new HttpObjectAggregator(512 * 1024));
|
||||
/*
|
||||
* 这个时候 我们需要声明我们使用的是 websocket 协议
|
||||
* netty为websocket也准备了对应处理器 设置的是访问路径
|
||||
* 这个时候我们只需要访问 ws://127.0.0.1:7777/hello 就可以了
|
||||
* 这个handler是将http协议升级为websocket 并且使用 101 作为响应码
|
||||
* */
|
||||
pipeline.addLast(new IdleStateHandler(13, 0, 0, TimeUnit.SECONDS));
|
||||
pipeline.addLast(new WebSocketHandler());
|
||||
pipeline.addLast(new WebSocketServerProtocolHandler("/hello"));
|
||||
|
||||
pipeline.addLast(new ChannelInboundHandlerAdapter() {
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||
// 处理异常,例如记录日志、关闭连接等
|
||||
System.out.println("进入异常++++++++++++++++++");
|
||||
cause.printStackTrace();
|
||||
ctx.close();
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
package com.njcn.gather.detection.util.socket.web;
|
||||
|
||||
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import io.netty.handler.logging.LoggingHandler;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.PreDestroy;
|
||||
|
||||
|
||||
/**
|
||||
* @Description: websocket服务端
|
||||
* @Author: wr
|
||||
* @Date: 2024/12/10 13:59
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class WebSocketService {
|
||||
|
||||
|
||||
/**
|
||||
* 端口号
|
||||
*/
|
||||
@Value("${webSocket.port:7777}")
|
||||
int port;
|
||||
|
||||
|
||||
EventLoopGroup bossGroup;
|
||||
EventLoopGroup workerGroup;
|
||||
|
||||
|
||||
|
||||
@PostConstruct
|
||||
public void start() {
|
||||
new Thread(() -> {
|
||||
//可以自定义线程的数量
|
||||
bossGroup = new NioEventLoopGroup();
|
||||
// 默认创建的线程数量 = CPU 处理器数量 *2
|
||||
workerGroup = new NioEventLoopGroup();
|
||||
try {
|
||||
ServerBootstrap serverBootstrap = new ServerBootstrap();
|
||||
serverBootstrap.group(bossGroup, workerGroup)
|
||||
.channel(NioServerSocketChannel.class)
|
||||
.handler(new LoggingHandler())
|
||||
//当前连接被阻塞的时候,BACKLOG代表的事 阻塞队列的长度
|
||||
.option(ChannelOption.SO_BACKLOG, 128)
|
||||
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
|
||||
|
||||
//设置连接保持为活动状态
|
||||
.childOption(ChannelOption.SO_KEEPALIVE, true)
|
||||
.childHandler(new WebSocketInitializer());
|
||||
ChannelFuture future = serverBootstrap.bind(port).sync();
|
||||
future.addListener(f -> {
|
||||
if (future.isSuccess()) {
|
||||
System.out.println("webSocket服务启动成功");
|
||||
} else {
|
||||
System.out.println("webSocket服务启动失败");
|
||||
}
|
||||
});
|
||||
future.channel().closeFuture().sync();
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
bossGroup.shutdownGracefully();
|
||||
workerGroup.shutdownGracefully();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 释放资源
|
||||
*/
|
||||
@PreDestroy
|
||||
public void destroy() throws InterruptedException {
|
||||
if (bossGroup != null) {
|
||||
bossGroup.shutdownGracefully().sync();
|
||||
}
|
||||
if (workerGroup != null) {
|
||||
workerGroup.shutdownGracefully().sync();
|
||||
}
|
||||
|
||||
System.out.println("webSocket销毁---------------");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,318 @@
|
||||
package com.njcn.gather.detection.util.socket.websocket;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.njcn.gather.detection.pojo.param.PreDetectionParam;
|
||||
import com.njcn.gather.detection.pojo.vo.WebSocketVO;
|
||||
import com.njcn.gather.detection.pojo.enums.SourceOperateCodeEnum;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* WebSocket会话管理器
|
||||
*
|
||||
* <p>负责管理电能质量检测系统中的WebSocket连接会话和检测参数,主要功能包括:</p>
|
||||
* <ul>
|
||||
* <li>WebSocket连接会话的添加、删除和管理</li>
|
||||
* <li>向指定用户推送实时消息(文本消息和结构化消息)</li>
|
||||
* <li>全局检测参数的存储和管理</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p><b>线程安全性:</b></p>
|
||||
* 使用ConcurrentHashMap确保在高并发环境下的线程安全。
|
||||
*
|
||||
* <p><b>使用场景:</b></p>
|
||||
* <ul>
|
||||
* <li>检测进度实时推送</li>
|
||||
* <li>检测结果数据推送</li>
|
||||
* <li>设备状态变更通知</li>
|
||||
* <li>异常信息推送</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p><b>消息推送方式:</b></p>
|
||||
* <ul>
|
||||
* <li>{@link #sendMsg(String, String)} - 发送纯文本消息</li>
|
||||
* <li>{@link #sendMessage(String, WebSocketVO)} - 发送结构化JSON消息</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author wr
|
||||
* @version 1.0
|
||||
* @date 2024/12/11 13:04
|
||||
* @see com.njcn.gather.detection.util.socket.websocket.WebSocketHandler WebSocket处理器
|
||||
* @see com.njcn.gather.detection.pojo.vo.WebSocketVO WebSocket消息对象
|
||||
* @since 检测系统 v2.3.12
|
||||
*/
|
||||
@Slf4j
|
||||
public class WebServiceManager {
|
||||
|
||||
/**
|
||||
* WebSocket用户会话存储
|
||||
* key: 用户ID, value: WebSocket连接通道
|
||||
*/
|
||||
private static final Map<String, Channel> userSessions = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* 检测参数存储
|
||||
* key: 用户ID(userPageId), value: 检测参数对象
|
||||
* 支持多用户并发检测,每个用户的检测参数独立存储
|
||||
*/
|
||||
private static final Map<String, PreDetectionParam> preDetectionParamMap = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* 添加用户WebSocket会话
|
||||
*
|
||||
* @param userId 用户ID,不能为null
|
||||
* @param channel WebSocket连接通道,不能为null
|
||||
*/
|
||||
public static void addUser(String userId, Channel channel) {
|
||||
userSessions.put(userId, channel);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据用户ID移除会话(推荐使用)
|
||||
* 时间复杂度:O(1)
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return 被移除的Channel,如果不存在则返回null
|
||||
*/
|
||||
public static Channel removeByUserId(String userId) {
|
||||
return userSessions.remove(userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据channelId移除会话(兼容老版本)
|
||||
* 时间复杂度:O(n),建议使用removeByUserId替代
|
||||
*
|
||||
* @param channelId 通道ID
|
||||
* @deprecated 建议使用 {@link #removeByUserId(String)} 替代
|
||||
*/
|
||||
@Deprecated
|
||||
public static void removeChannel(String channelId) {
|
||||
// 遍历并删除
|
||||
Iterator<Map.Entry<String, Channel>> iterator = userSessions.entrySet().iterator();
|
||||
while (iterator.hasNext()) {
|
||||
Map.Entry<String, Channel> entry = iterator.next();
|
||||
if (entry.getValue().id().toString().equals(channelId)) {
|
||||
iterator.remove();
|
||||
break; // 找到后立即退出,避免继续遍历
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 发送纯文本消息给指定用户
|
||||
*
|
||||
* @param userId 目标用户ID
|
||||
* @param msg 要发送的文本消息
|
||||
*/
|
||||
public static void sendMsg(String userId, String msg) {
|
||||
Channel channel = userSessions.get(userId);
|
||||
if (Objects.nonNull(channel) && channel.isActive()) {
|
||||
TextWebSocketFrame frame = new TextWebSocketFrame(msg);
|
||||
channel.writeAndFlush(frame);
|
||||
} else {
|
||||
log.error("WebSocket推送消息失败,用户连接已断开,时间: {}, userId: {}", LocalDateTime.now(), userId);
|
||||
WebSocketHandler.cleanupSocketResources(userId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送结构化消息给指定用户
|
||||
* 消息会被序列化为JSON格式后发送
|
||||
*
|
||||
* @param userId 目标用户ID
|
||||
* @param webSocketVO 要发送的结构化消息对象
|
||||
*/
|
||||
public static void sendMessage(String userId, WebSocketVO<Object> webSocketVO) {
|
||||
Channel channel = userSessions.get(userId);
|
||||
if (Objects.nonNull(channel) && channel.isActive()) {
|
||||
TextWebSocketFrame frame = new TextWebSocketFrame(JSON.toJSONString(webSocketVO));
|
||||
channel.writeAndFlush(frame);
|
||||
} else {
|
||||
log.error("WebSocket推送结构化消息失败,用户连接已断开,时间: {}, userId: {}", LocalDateTime.now(), userId);
|
||||
WebSocketHandler.cleanupSocketResources(userId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 存储检测参数(基于用户ID)
|
||||
* 支持多用户并发检测,每个用户的检测参数独立存储
|
||||
*
|
||||
* @param userId 用户ID(登录名)
|
||||
* @param preDetectionParam 检测参数对象
|
||||
* @throws IllegalArgumentException 当userId或检测参数为空时抛出
|
||||
*/
|
||||
public static void addPreDetectionParam(String userId, PreDetectionParam preDetectionParam) {
|
||||
if (userId == null || userId.trim().isEmpty()) {
|
||||
throw new IllegalArgumentException("用户ID不能为空");
|
||||
}
|
||||
if (preDetectionParam == null) {
|
||||
throw new IllegalArgumentException("检测参数不能为空");
|
||||
}
|
||||
preDetectionParamMap.put(userId, preDetectionParam);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取指定用户的检测参数
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return 检测参数对象,如果不存在则返回null
|
||||
*/
|
||||
public static PreDetectionParam getPreDetectionParam(String userId) {
|
||||
return preDetectionParamMap.get(userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前检测参数(兼容老版本)
|
||||
* 注意:该方法已废弃,建议使用 {@link #getPreDetectionParam(String)}
|
||||
*
|
||||
* @return 检测参数对象,如果不存在则返回null
|
||||
* @deprecated 多用户并发场景下该方法不安全,请使用 {@link #getPreDetectionParam(String)}
|
||||
*/
|
||||
@Deprecated
|
||||
public static PreDetectionParam getPreDetectionParam() {
|
||||
if (preDetectionParamMap.size() == 1) {
|
||||
return preDetectionParamMap.values().iterator().next();
|
||||
}
|
||||
log.warn("存在多个检测参数,无法确定返回哪个,当前参数数量: {}", preDetectionParamMap.size());
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除指定用户的检测参数
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return 被移除的检测参数,如果不存在则返回null
|
||||
*/
|
||||
public static PreDetectionParam removePreDetectionParam(String userId) {
|
||||
return preDetectionParamMap.remove(userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空所有检测参数
|
||||
*/
|
||||
public static void removeAllPreDetectionParam() {
|
||||
preDetectionParamMap.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空所有检测参数(兼容老版本)
|
||||
*
|
||||
* @deprecated 建议使用 {@link #removeAllPreDetectionParam()} 或 {@link #removePreDetectionParam(String)}
|
||||
*/
|
||||
@Deprecated
|
||||
public static void removePreDetectionParam() {
|
||||
removeAllPreDetectionParam();
|
||||
}
|
||||
|
||||
// ================================ 实用功能方法 ================================
|
||||
|
||||
/**
|
||||
* 获取当前在线用户数量
|
||||
*
|
||||
* @return 在线用户数量
|
||||
*/
|
||||
public static int getOnlineUserCount() {
|
||||
return userSessions.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查指定用户是否在线
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return true如果用户在线且连接活跃,否则返回false
|
||||
*/
|
||||
public static boolean isUserOnline(String userId) {
|
||||
Channel channel = userSessions.get(userId);
|
||||
return Objects.nonNull(channel) && channel.isActive();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有在线用户ID集合
|
||||
*
|
||||
* @return 在线用户ID集合的快照
|
||||
*/
|
||||
public static java.util.Set<String> getOnlineUserIds() {
|
||||
return new java.util.HashSet<>(userSessions.keySet());
|
||||
}
|
||||
|
||||
// ================================ 检测消息推送方法 ================================
|
||||
|
||||
/**
|
||||
* 发送检测相关消息给指定用户
|
||||
* <p>用于推送检测状态、进度、结果等结构化消息</p>
|
||||
*
|
||||
* @param userId 目标用户ID
|
||||
* @param requestId 请求ID,用于标识消息类型和流程
|
||||
* @param operateCode 操作代码,标识具体的操作类型
|
||||
* @param data 数据载荷,可以是任意类型的数据
|
||||
* @param desc 描述信息
|
||||
* @since v2.3.12 重构版本
|
||||
*/
|
||||
public static void sendDetectionMessage(String userId, String requestId, String operateCode, Object data, String desc) {
|
||||
WebSocketVO<Object> webSocketVO = new WebSocketVO<>();
|
||||
webSocketVO.setRequestId(requestId);
|
||||
webSocketVO.setOperateCode(operateCode);
|
||||
webSocketVO.setData(data);
|
||||
webSocketVO.setDesc(desc);
|
||||
sendMessage(userId, webSocketVO);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送未知错误消息给指定用户
|
||||
* <p>用于处理系统无法识别的操作或未知异常情况</p>
|
||||
*
|
||||
* @param userId 目标用户ID
|
||||
* @since v2.3.12 重构版本
|
||||
*/
|
||||
public static void sendUnknownErrorMessage(String userId) {
|
||||
WebSocketVO<Object> webSocketVO = new WebSocketVO<>();
|
||||
webSocketVO.setRequestId(SourceOperateCodeEnum.UNKNOWN_OPERATE.getValue());
|
||||
webSocketVO.setData(SourceOperateCodeEnum.UNKNOWN_OPERATE.getMsg());
|
||||
webSocketVO.setOperateCode(SourceOperateCodeEnum.UNKNOWN_OPERATE.getMsg());
|
||||
sendMessage(userId, webSocketVO);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送检测错误消息给指定用户
|
||||
* <p>用于推送特定类型的检测错误信息</p>
|
||||
*
|
||||
* @param userId 目标用户ID
|
||||
* @param errorType 错误类型枚举
|
||||
* @since v2.3.12 重构版本
|
||||
*/
|
||||
public static void sendDetectionErrorMessage(String userId, SourceOperateCodeEnum errorType) {
|
||||
WebSocketVO<Object> webSocketVO = new WebSocketVO<>();
|
||||
webSocketVO.setRequestId(errorType.getValue());
|
||||
webSocketVO.setData(errorType.getMsg());
|
||||
webSocketVO.setOperateCode(errorType.getValue());
|
||||
sendMessage(userId, webSocketVO);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送检测错误消息给指定用户
|
||||
* <p>用于推送特定类型的检测错误信息</p>
|
||||
*
|
||||
* @param userId 目标用户ID
|
||||
* @param requestId 请求ID
|
||||
* @param errorType 错误类型枚举
|
||||
* @since v2.3.12 重构版本
|
||||
*/
|
||||
public static void sendDetectionErrorMessage(String userId, String requestId, SourceOperateCodeEnum errorType) {
|
||||
WebSocketVO<Object> webSocketVO = new WebSocketVO<>();
|
||||
webSocketVO.setRequestId(requestId);
|
||||
webSocketVO.setData(errorType.getMsg());
|
||||
webSocketVO.setOperateCode(errorType.getValue());
|
||||
sendMessage(userId, webSocketVO);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.njcn.gather.detection.util.socket.websocket;
|
||||
|
||||
/**
|
||||
* WebSocket常量管理类
|
||||
*
|
||||
* @author wr
|
||||
* @date 2024/12/10
|
||||
*/
|
||||
public final class WebSocketConstants {
|
||||
|
||||
/**
|
||||
* URL参数分隔符
|
||||
*/
|
||||
public static final String QUESTION_MARK = "?";
|
||||
|
||||
/**
|
||||
* URL参数等号分隔符
|
||||
*/
|
||||
public static final String EQUAL_TO = "=";
|
||||
|
||||
/**
|
||||
* 客户端心跳消息
|
||||
*/
|
||||
public static final String HEARTBEAT_PING = "alive";
|
||||
|
||||
/**
|
||||
* 服务端心跳响应
|
||||
*/
|
||||
public static final String HEARTBEAT_PONG = "over";
|
||||
|
||||
/**
|
||||
* 心跳超时最大次数
|
||||
*/
|
||||
public static final int MAX_HEARTBEAT_MISS_COUNT = 3;
|
||||
|
||||
/**
|
||||
* WebSocket握手失败状态码
|
||||
*/
|
||||
public static final int HANDSHAKE_FAILED_STATUS = 4000;
|
||||
|
||||
/**
|
||||
* WebSocket握手失败原因
|
||||
*/
|
||||
public static final String HANDSHAKE_FAILED_REASON = "Missing required userId parameter";
|
||||
|
||||
private WebSocketConstants() {
|
||||
// 私有构造函数,防止实例化
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,424 @@
|
||||
package com.njcn.gather.detection.util.socket.websocket;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.njcn.gather.detection.pojo.enums.SourceOperateCodeEnum;
|
||||
import com.njcn.gather.detection.pojo.param.PreDetectionParam;
|
||||
import com.njcn.gather.detection.util.socket.CnSocketUtil;
|
||||
import com.njcn.gather.detection.util.socket.FormalTestManager;
|
||||
import com.njcn.gather.device.pojo.enums.PatternEnum;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import io.netty.handler.codec.CorruptedFrameException;
|
||||
import io.netty.handler.codec.DecoderException;
|
||||
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
|
||||
import io.netty.handler.codec.http.websocketx.WebSocketHandshakeException;
|
||||
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
|
||||
import io.netty.handler.timeout.IdleStateEvent;
|
||||
import io.netty.util.AttributeKey;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static com.njcn.gather.detection.util.socket.websocket.WebSocketConstants.*;
|
||||
|
||||
|
||||
/**
|
||||
* WebSocket消息处理器
|
||||
*
|
||||
* <p>负责处理电能质量检测系统中的WebSocket连接和消息通信,主要功能包括:</p>
|
||||
* <ul>
|
||||
* <li>WebSocket连接的建立、维护和断开</li>
|
||||
* <li>用户身份验证和会话管理</li>
|
||||
* <li>心跳检测和连接保活</li>
|
||||
* <li>检测状态和结果的实时推送</li>
|
||||
* <li>异常处理和资源清理</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p><b>通信协议:</b></p>
|
||||
* <pre>
|
||||
* 连接URL: ws://host:port/path?name=userId
|
||||
* 心跳消息: "alive" -> "over"
|
||||
* 业务消息: JSON格式的检测数据和状态信息
|
||||
* </pre>
|
||||
*
|
||||
* <p><b>安全策略:</b></p>
|
||||
* <ul>
|
||||
* <li>连接时必须提供有效的userId参数,否则拒绝连接</li>
|
||||
* <li>支持心跳超时检测,超时3次自动断开连接</li>
|
||||
* <li>连接断开时自动清理相关Socket资源</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p><b>使用场景:</b></p>
|
||||
* 主要用于前端实时接收检测进度、检测结果、设备状态等信息的推送,
|
||||
* 配合detection模块的ResponseService类实现完整的实时通信链路。
|
||||
*
|
||||
* @author wr
|
||||
* @version 1.0
|
||||
* @date 2024/12/10 13:56
|
||||
* @see WebServiceManager 会话管理器
|
||||
* @see WebSocketConstants 常量定义
|
||||
* @see com.njcn.gather.detection.handler.SocketDevResponseService 设备响应处理
|
||||
* @see com.njcn.gather.detection.handler.SocketSourceResponseService 源响应处理
|
||||
* @since 检测系统 v2.3.12
|
||||
*/
|
||||
|
||||
|
||||
@Slf4j
|
||||
public class WebSocketHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
|
||||
|
||||
// ================================ 字段定义 ================================
|
||||
|
||||
/**
|
||||
* 心跳超时计数器
|
||||
*/
|
||||
private int times;
|
||||
|
||||
/**
|
||||
* 当前WebSocket连接对应的用户ID
|
||||
* 在首次HTTP握手时从URL参数中提取并存储
|
||||
* 用于后续的Socket连接管理和资源清理
|
||||
*/
|
||||
private String userId;
|
||||
|
||||
/**
|
||||
* 心跳响应内容常量
|
||||
* 注意:不能预创建TextWebSocketFrame对象,因为ByteBuf状态会改变
|
||||
*/
|
||||
private static final String HEARTBEAT_RESPONSE_TEXT = HEARTBEAT_PONG;
|
||||
|
||||
// ================================ Netty生命周期方法 ================================
|
||||
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
||||
log.info("webSocket服务端通道已建立,channelId: {}", ctx.channel().id());
|
||||
super.channelActive(ctx);
|
||||
}
|
||||
|
||||
// HTTP握手处理已移至WebSocketPreprocessor,这里只处理WebSocket帧
|
||||
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) {
|
||||
handleWebSocketMessage(ctx, msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handlerAdded(ChannelHandlerContext ctx) {
|
||||
log.info("webSocket有新的连接接入,channelId: {}", ctx.channel().id());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handlerRemoved(ChannelHandlerContext ctx) {
|
||||
log.info("webSocket客户端退出,channelId: {}, userId: {}", ctx.channel().id(), this.userId);
|
||||
if (this.userId != null) {
|
||||
WebServiceManager.removeByUserId(this.userId);
|
||||
} else {
|
||||
// 备用方案:如果userId为空,使用传统方法
|
||||
WebServiceManager.removeChannel(ctx.channel().id().toString());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext ctx) {
|
||||
log.info("webSocket连接断线,channelId: {}, userId: {}", ctx.channel().id(), this.userId);
|
||||
|
||||
// 确保通道关闭
|
||||
if (ctx.channel() != null && ctx.channel().isActive()) {
|
||||
ctx.close();
|
||||
}
|
||||
|
||||
// 使用Handler实例中保存的userId进行资源清理
|
||||
cleanupSocketResources(this.userId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
|
||||
// 处理WebSocket握手完成事件
|
||||
if (evt instanceof WebSocketServerProtocolHandler.HandshakeComplete) {
|
||||
WebSocketServerProtocolHandler.HandshakeComplete handshakeComplete =
|
||||
(WebSocketServerProtocolHandler.HandshakeComplete) evt;
|
||||
|
||||
// 从Channel属性获取userId(由WebSocketPreprocessor设置)
|
||||
this.userId = ctx.channel().attr(AttributeKey.<String>valueOf("userId")).get();
|
||||
|
||||
log.info("WebSocket协议升级完成,userId: {}, channelId: {}, requestUri: {}",
|
||||
this.userId, ctx.channel().id(), handshakeComplete.requestUri());
|
||||
|
||||
// 握手完成后建立用户会话
|
||||
if (this.userId != null) {
|
||||
WebServiceManager.addUser(this.userId, ctx.channel());
|
||||
log.info("WebSocket用户会话已建立,userId: {}, channelId: {}", this.userId, ctx.channel().id());
|
||||
}
|
||||
|
||||
// 发送连接成功消息给前端
|
||||
sendConnectionSuccessMessage(ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
// 处理心跳超时事件
|
||||
if (evt instanceof IdleStateEvent) {
|
||||
IdleStateEvent event = (IdleStateEvent) evt;
|
||||
String eventDesc;
|
||||
switch (event.state()) {
|
||||
case READER_IDLE:
|
||||
eventDesc = "读空闲";
|
||||
log.warn("客户端心跳检测发生超时事件: {},channelId: {}", eventDesc, ctx.channel().id());
|
||||
times++;
|
||||
if (times > MAX_HEARTBEAT_MISS_COUNT) {
|
||||
log.error("客户端心跳检测空闲次数超过{}次,关闭连接,channelId: {}, userId: {}", MAX_HEARTBEAT_MISS_COUNT, ctx.channel().id(), this.userId);
|
||||
ctx.channel().close();
|
||||
if (this.userId != null) {
|
||||
WebServiceManager.removeByUserId(this.userId);
|
||||
} else {
|
||||
WebServiceManager.removeChannel(ctx.channel().id().toString());
|
||||
}
|
||||
}
|
||||
break;
|
||||
case WRITER_IDLE:
|
||||
log.debug("webSocket写空闲事件,channelId: {}", ctx.channel().id());
|
||||
break;
|
||||
case ALL_IDLE:
|
||||
log.debug("webSocket读写空闲事件,channelId: {}", ctx.channel().id());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 其他事件传递给父类处理
|
||||
super.userEventTriggered(ctx, evt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||
String channelId = ctx.channel().id().toString();
|
||||
try {
|
||||
// 1. 异常分类记录
|
||||
logExceptionByType(channelId, cause);
|
||||
// 2. 业务清理
|
||||
cleanupOnException(ctx, cause);
|
||||
// 3. 连接处理决策
|
||||
handleConnectionByExceptionType(ctx, cause);
|
||||
} catch (Exception e) {
|
||||
// 防止异常处理本身出错
|
||||
log.error("异常处理过程中发生错误,强制关闭连接,channelId: {}", channelId, e);
|
||||
ctx.close();
|
||||
}
|
||||
}
|
||||
|
||||
// ================================ HTTP握手处理已移至WebSocketPreprocessor ================================
|
||||
|
||||
// ================================ WebSocket消息处理 ================================
|
||||
|
||||
/**
|
||||
* 发送连接成功消息给前端
|
||||
* WebSocket握手完成后立即调用,通知前端连接建立成功
|
||||
*
|
||||
* @param ctx Netty通道上下文
|
||||
*/
|
||||
private void sendConnectionSuccessMessage(ChannelHandlerContext ctx) {
|
||||
if (ctx == null || ctx.channel() == null || !ctx.channel().isActive()) {
|
||||
log.warn("无法发送连接成功消息:通道不可用, userId: {}", this.userId);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 构建连接成功消息
|
||||
String welcomeMessage = String.format("{\"type\":\"connection\",\"status\":\"success\",\"message\":\"WebSocket连接建立成功\",\"userId\":\"%s\",\"timestamp\":%d}",
|
||||
this.userId, System.currentTimeMillis());
|
||||
|
||||
TextWebSocketFrame frame = new TextWebSocketFrame(welcomeMessage);
|
||||
ctx.channel().writeAndFlush(frame);
|
||||
|
||||
log.info("已发送连接成功消息给前端, userId: {}, channelId: {}", this.userId, ctx.channel().id());
|
||||
} catch (Exception e) {
|
||||
log.error("发送连接成功消息失败, userId: {}, channelId: {}", this.userId, ctx.channel().id(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理WebSocket文本消息
|
||||
* 这里是所有WebSocket文本消息的统一处理入口
|
||||
*
|
||||
* @param ctx Netty通道上下文
|
||||
* @param frame WebSocket文本帧
|
||||
*/
|
||||
private void handleWebSocketMessage(ChannelHandlerContext ctx, TextWebSocketFrame frame) {
|
||||
String messageText = frame.text();
|
||||
// 处理心跳消息
|
||||
if (HEARTBEAT_PING.equals(messageText)) {
|
||||
handleHeartbeat(ctx);
|
||||
} else {
|
||||
// 处理业务消息
|
||||
handleBusinessMessage(ctx, frame, messageText);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理心跳消息
|
||||
* 重置超时计数器并回复心跳响应
|
||||
*
|
||||
* @param ctx Netty通道上下文
|
||||
*/
|
||||
private void handleHeartbeat(ChannelHandlerContext ctx) {
|
||||
if (ctx == null || ctx.channel() == null || !ctx.channel().isActive()) {
|
||||
log.warn("心跳处理失败:通道不可用,userId: {}", this.userId);
|
||||
return;
|
||||
}
|
||||
log.debug("收到心跳消息,userId: {}, channelId: {}", this.userId, ctx.channel().id());
|
||||
// 重置心跳超时计数器
|
||||
times = 0;
|
||||
// 每次创建新的心跳响应帧,确保内容正确
|
||||
TextWebSocketFrame heartbeatFrame = new TextWebSocketFrame(HEARTBEAT_RESPONSE_TEXT);
|
||||
ctx.channel().writeAndFlush(heartbeatFrame);
|
||||
log.debug("发送心跳响应,userId: {}, channelId: {}", this.userId, ctx.channel().id());
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理业务消息
|
||||
* 可以在这里扩展具体的业务逻辑处理
|
||||
*
|
||||
* @param ctx Netty通道上下文
|
||||
* @param frame WebSocket文本帧
|
||||
* @param messageText 消息文本内容
|
||||
*/
|
||||
private void handleBusinessMessage(ChannelHandlerContext ctx, TextWebSocketFrame frame, String messageText) {
|
||||
log.debug("收到WebSocket业务消息,userId: {}, channelId: {}, message: {}",
|
||||
this.userId, ctx.channel().id(), messageText);
|
||||
// TODO: 根据业务需要扩展消息处理逻辑
|
||||
// 例如:
|
||||
// - 解析JSON消息
|
||||
// - 根据消息类型分发到不同的处理器
|
||||
// - 调用业务服务处理具体逻辑
|
||||
}
|
||||
|
||||
// ================================ 异常处理 ================================
|
||||
|
||||
/**
|
||||
* 根据异常类型记录不同级别的日志
|
||||
*/
|
||||
private void logExceptionByType(String channelId, Throwable cause) {
|
||||
if (cause instanceof IOException) {
|
||||
log.info("webSocket网络异常,客户端可能异常断开,channelId: {}, 异常: {}", channelId, cause.getMessage());
|
||||
} else if (cause instanceof WebSocketHandshakeException) {
|
||||
log.warn("webSocket握手异常,channelId: {}, 异常: {}", channelId, cause.getMessage());
|
||||
} else if (cause instanceof DecoderException || cause instanceof CorruptedFrameException) {
|
||||
log.error("webSocket协议解码异常,可能是恶意请求,channelId: {}, 异常: {}", channelId, cause.getMessage(), cause);
|
||||
} else if (cause instanceof IllegalArgumentException) {
|
||||
log.warn("webSocket参数异常,channelId: {}, 异常: {}", channelId, cause.getMessage());
|
||||
} else {
|
||||
log.error("webSocket未分类异常,channelId: {}, 类型: {}, 异常: {}",
|
||||
channelId, cause.getClass().getSimpleName(), cause.getMessage(), cause);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 异常发生时的业务清理工作
|
||||
*/
|
||||
private void cleanupOnException(ChannelHandlerContext ctx, Throwable cause) {
|
||||
if (ctx == null || ctx.channel() == null) {
|
||||
log.warn("异常处理:通道上下文为空,无法进行清理");
|
||||
return;
|
||||
}
|
||||
|
||||
String channelId = ctx.channel().id().toString();
|
||||
|
||||
// 清理会话
|
||||
if (this.userId != null) {
|
||||
WebServiceManager.removeByUserId(this.userId);
|
||||
log.debug("已清理WebSocket会话,userId: {}, channelId: {}", this.userId, channelId);
|
||||
} else {
|
||||
WebServiceManager.removeChannel(channelId);
|
||||
log.debug("已清理WebSocket会话(使用channelId),channelId: {}", channelId);
|
||||
}
|
||||
|
||||
// 清理检测相关资源
|
||||
cleanupSocketResources(this.userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据异常类型决定连接处理策略
|
||||
*/
|
||||
private void handleConnectionByExceptionType(ChannelHandlerContext ctx, Throwable cause) {
|
||||
String channelId = ctx.channel().id().toString();
|
||||
// URL参数异常但连接本身可能正常,尝试保持连接
|
||||
if (cause instanceof IllegalArgumentException &&
|
||||
cause.getMessage() != null && cause.getMessage().contains("URL")) {
|
||||
log.info("URL参数异常,尝试保持连接,channelId: {}", channelId);
|
||||
return;
|
||||
}
|
||||
// 其他情况都关闭连接
|
||||
log.debug("关闭WebSocket连接,channelId: {}", channelId);
|
||||
ctx.close();
|
||||
}
|
||||
|
||||
// ================================ 资源清理 ================================
|
||||
|
||||
/**
|
||||
* 清理Socket相关资源
|
||||
*
|
||||
* @param userId 用户ID
|
||||
*/
|
||||
public static void cleanupSocketResources(String userId) {
|
||||
if (userId == null || userId.trim().isEmpty()) {
|
||||
log.warn("userId为空,无法进行Socket连接清理");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
PreDetectionParam preDetectionParam = WebServiceManager.getPreDetectionParam(userId);
|
||||
if (ObjectUtil.isNotNull(preDetectionParam)) {
|
||||
// 使用该用户的检测参数关闭Socket连接
|
||||
log.info("使用用户检测参数关闭Socket连接,userId: {}", userId);
|
||||
if (FormalTestManager.patternEnum.equals(PatternEnum.CONTRAST)) {
|
||||
CnSocketUtil.contrastSendquit(preDetectionParam.getUserPageId(), SourceOperateCodeEnum.QUIT_INIT_01, false);
|
||||
CnSocketUtil.contrastSendquit(preDetectionParam.getUserPageId(), SourceOperateCodeEnum.QUIT_INIT_02, false);
|
||||
CnSocketUtil.contrastSendquit(preDetectionParam.getUserPageId(), SourceOperateCodeEnum.QUIT_INIT_03, false);
|
||||
// if (FormalTestManager.isRemoveSocket) {
|
||||
// boolean channelActive = SocketManager.isChannelActive(preDetectionParam.getUserPageId() + CnSocketUtil.CONTRAST_DEV_TAG);
|
||||
// if (channelActive) {
|
||||
// SocketManager.removeUser(preDetectionParam.getUserPageId() + CnSocketUtil.CONTRAST_DEV_TAG);
|
||||
// }
|
||||
// } else {
|
||||
// if (FormalTestManager.currentStep == SourceOperateCodeEnum.RECORD_WAVE_STEP1) {
|
||||
// CnSocketUtil.contrastSendquit(preDetectionParam.getUserPageId(), SourceOperateCodeEnum.QUIT_INIT_03, false);
|
||||
// } else if (FormalTestManager.currentStep != SourceOperateCodeEnum.QUITE) {
|
||||
// if (ObjectUtil.isNotNull(FormalTestManager.nonWaveDataSourceEnum)) {
|
||||
// if (FormalTestManager.nonWaveDataSourceEnum == DataSourceEnum.REAL_DATA) {
|
||||
// CnSocketUtil.contrastSendquit(preDetectionParam.getUserPageId(), SourceOperateCodeEnum.QUIT_INIT_02, false);
|
||||
// if (FormalTestManager.statisticsProtocol) {
|
||||
// CnSocketUtil.contrastSendquit(preDetectionParam.getUserPageId(), SourceOperateCodeEnum.QUIT_INIT_01, false);
|
||||
// }
|
||||
// } else {
|
||||
// CnSocketUtil.contrastSendquit(preDetectionParam.getUserPageId(), SourceOperateCodeEnum.QUIT_INIT_01, false);
|
||||
// }
|
||||
// if (FormalTestManager.isWaveCheck) {
|
||||
// CnSocketUtil.contrastSendquit(preDetectionParam.getUserPageId(), SourceOperateCodeEnum.QUIT_INIT_03, false);
|
||||
// }
|
||||
// } else {
|
||||
// if (FormalTestManager.statisticsProtocol) {
|
||||
// CnSocketUtil.contrastSendquit(preDetectionParam.getUserPageId(), SourceOperateCodeEnum.QUIT_INIT_01, false);
|
||||
// }
|
||||
// if (FormalTestManager.isWaveCheck) {
|
||||
// CnSocketUtil.contrastSendquit(preDetectionParam.getUserPageId(), SourceOperateCodeEnum.QUIT_INIT_02, false);
|
||||
// CnSocketUtil.contrastSendquit(preDetectionParam.getUserPageId(), SourceOperateCodeEnum.QUIT_INIT_03, false);
|
||||
// }
|
||||
// }
|
||||
// } else {
|
||||
// SocketManager.removeUser(preDetectionParam.getUserPageId() + CnSocketUtil.CONTRAST_DEV_TAG);
|
||||
// }
|
||||
// }
|
||||
} else {
|
||||
CnSocketUtil.quitSendSource(preDetectionParam);
|
||||
CnSocketUtil.quitSend(preDetectionParam);
|
||||
}
|
||||
// 清理完成后移除该用户的检测参数
|
||||
WebServiceManager.removePreDetectionParam(userId);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("清理Socket连接时发生异常,userId: {}", userId, e);
|
||||
}
|
||||
}
|
||||
|
||||
// ================================ URL解析工具已移至WebSocketPreprocessor ================================
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
package com.njcn.gather.detection.util.socket.websocket;
|
||||
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import io.netty.handler.codec.http.*;
|
||||
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
|
||||
import io.netty.handler.stream.ChunkedWriteHandler;
|
||||
import io.netty.handler.timeout.IdleStateHandler;
|
||||
import io.netty.util.AttributeKey;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* WebSocket服务端管道初始化器
|
||||
*
|
||||
* 职责:
|
||||
* 1. 为每个新的WebSocket连接配置处理器链(Pipeline)
|
||||
* 2. 按正确顺序添加各种Handler,确保数据流正确处理
|
||||
* 3. 配置HTTP到WebSocket的协议升级
|
||||
* 4. 设置心跳检测和异常处理机制
|
||||
*
|
||||
* 处理流程:
|
||||
* HTTP请求 → HTTP编解码 → 分块处理 → 消息聚合 → 协议升级 → 心跳检测 → 业务处理 → 异常处理
|
||||
*
|
||||
* @Description: webSocket服务端自定义配置
|
||||
* @Author: wr
|
||||
* @Date: 2024/12/10 14:20
|
||||
*/
|
||||
@Slf4j
|
||||
public class WebSocketInitializer extends ChannelInitializer<SocketChannel> {
|
||||
|
||||
/**
|
||||
* WebSocket访问路径
|
||||
*/
|
||||
private static final String WEBSOCKET_PATH = "/hello";
|
||||
|
||||
/**
|
||||
* HTTP消息最大聚合大小:512KB
|
||||
* 用于WebSocket握手和消息传输
|
||||
*/
|
||||
private static final int MAX_CONTENT_LENGTH = 512 * 1024;
|
||||
|
||||
/**
|
||||
* 心跳检测间隔:13秒
|
||||
* 13秒内没有收到客户端消息则触发空闲事件
|
||||
*/
|
||||
private static final int READER_IDLE_TIME_SECONDS = 13;
|
||||
|
||||
/**
|
||||
* 为每个新连接初始化处理器管道
|
||||
* 注意:Handler的添加顺序非常重要,决定了数据的处理流向
|
||||
*
|
||||
* @param ch 新建立的Socket通道
|
||||
* @throws Exception 初始化过程中的异常
|
||||
*/
|
||||
@Override
|
||||
protected void initChannel(SocketChannel ch) throws Exception {
|
||||
ChannelPipeline pipeline = ch.pipeline();
|
||||
|
||||
// 1. HTTP协议处理器
|
||||
// HttpServerCodec = HttpRequestDecoder + HttpResponseEncoder
|
||||
// 负责HTTP请求解码和HTTP响应编码
|
||||
pipeline.addLast("http-codec", new HttpServerCodec());
|
||||
|
||||
// 2. 分块写入处理器
|
||||
// 用于处理大文件的分块传输,防止内存溢出
|
||||
// 支持ChunkedInput,如ChunkedFile、ChunkedNioFile等
|
||||
pipeline.addLast("chunked-write", new ChunkedWriteHandler());
|
||||
|
||||
// 3. HTTP消息聚合器
|
||||
// 将分片的HTTP消息重新组装成完整的FullHttpRequest或FullHttpResponse
|
||||
// WebSocket握手需要完整的HTTP请求,所以这个Handler必须添加
|
||||
pipeline.addLast("http-aggregator", new HttpObjectAggregator(MAX_CONTENT_LENGTH));
|
||||
|
||||
// 4. WebSocket URL预处理器
|
||||
// 在WebSocket握手之前处理URL参数,验证用户ID
|
||||
pipeline.addLast("websocket-preprocessor", new WebSocketPreprocessor());
|
||||
|
||||
// 5. WebSocket协议升级处理器
|
||||
// 处理WebSocket握手,将HTTP协议升级为WebSocket协议
|
||||
// 只有访问指定路径(WEBSOCKET_PATH)的请求才会被升级
|
||||
// 升级后会移除HTTP相关的Handler,添加WebSocket相关的Handler
|
||||
pipeline.addLast("websocket-protocol", new WebSocketServerProtocolHandler(WEBSOCKET_PATH));
|
||||
|
||||
// 6. 空闲状态检测器
|
||||
// 检测连接的空闲状态,用于心跳机制
|
||||
// readerIdleTime: 读空闲时间,writerIdleTime: 写空闲时间,allIdleTime: 读写空闲时间
|
||||
pipeline.addLast("idle-state", new IdleStateHandler(READER_IDLE_TIME_SECONDS, 0, 0, TimeUnit.SECONDS));
|
||||
|
||||
// 7. 自定义WebSocket业务处理器
|
||||
// 处理WebSocket帧,实现具体的业务逻辑
|
||||
// 包括心跳处理、消息路由、连接管理等
|
||||
pipeline.addLast("websocket-handler", new WebSocketHandler());
|
||||
|
||||
// 7. 全局异常处理器
|
||||
// 处理整个管道中未被捕获的异常,作为最后的异常处理兜底
|
||||
pipeline.addLast("exception-handler", new GlobalExceptionHandler());
|
||||
}
|
||||
|
||||
/**
|
||||
* WebSocket预处理器
|
||||
* 在WebSocket握手之前验证URL参数并清理URL
|
||||
*/
|
||||
private static class WebSocketPreprocessor extends ChannelInboundHandlerAdapter {
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||
if (msg instanceof FullHttpRequest) {
|
||||
FullHttpRequest request = (FullHttpRequest) msg;
|
||||
String uri = request.uri();
|
||||
|
||||
log.debug("WebSocket预处理器收到HTTP请求:{}", uri);
|
||||
|
||||
// 验证并提取userId
|
||||
String userId = extractUserId(uri);
|
||||
if (userId == null || userId.trim().isEmpty()) {
|
||||
log.warn("WebSocket连接被拒绝:缺少userId参数, uri: {}", uri);
|
||||
FullHttpResponse response = new DefaultFullHttpResponse(
|
||||
HttpVersion.HTTP_1_1,
|
||||
HttpResponseStatus.BAD_REQUEST
|
||||
);
|
||||
ctx.writeAndFlush(response).addListener(f -> ctx.close());
|
||||
return;
|
||||
}
|
||||
|
||||
// 将userId存储到Channel属性中
|
||||
ctx.channel().attr(AttributeKey.<String>valueOf("userId")).set(userId);
|
||||
|
||||
// 清理URL参数
|
||||
if (uri.contains("?")) {
|
||||
String cleanUri = uri.substring(0, uri.indexOf("?"));
|
||||
request.setUri(cleanUri);
|
||||
log.debug("URL已清理,原始: {}, 清理后: {}, userId: {}", uri, cleanUri, userId);
|
||||
}
|
||||
}
|
||||
|
||||
// 继续传递给下一个Handler
|
||||
super.channelRead(ctx, msg);
|
||||
}
|
||||
|
||||
private String extractUserId(String uri) {
|
||||
if (!uri.contains("name=")) {
|
||||
return null;
|
||||
}
|
||||
int start = uri.indexOf("name=") + 5;
|
||||
int end = uri.indexOf("&", start);
|
||||
if (end == -1) {
|
||||
return uri.substring(start);
|
||||
} else {
|
||||
return uri.substring(start, end);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 全局异常处理器
|
||||
* 作为管道中的最后一个Handler,捕获所有未处理的异常
|
||||
*/
|
||||
private static class GlobalExceptionHandler extends ChannelInboundHandlerAdapter {
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||
// 记录异常详情,便于问题排查
|
||||
log.error("WebSocket连接发生未处理异常,远程地址:{},异常信息:{}",
|
||||
ctx.channel().remoteAddress(), cause.getMessage(), cause);
|
||||
|
||||
// 优雅关闭连接
|
||||
if (ctx.channel().isActive()) {
|
||||
ctx.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||
log.debug("WebSocket连接断开,远程地址:{}", ctx.channel().remoteAddress());
|
||||
super.channelInactive(ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,237 @@
|
||||
package com.njcn.gather.detection.util.socket.websocket;
|
||||
|
||||
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import io.netty.handler.logging.LoggingHandler;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.ApplicationArguments;
|
||||
import org.springframework.boot.ApplicationRunner;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.PreDestroy;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
|
||||
/**
|
||||
* WebSocket服务端核心类
|
||||
*
|
||||
* 职责:
|
||||
* 1. 启动基于Netty的WebSocket服务器
|
||||
* 2. 管理服务器生命周期(启动/关闭)
|
||||
* 3. 提供高性能的WebSocket通信支持
|
||||
*
|
||||
* 特性:
|
||||
* - 使用ApplicationRunner确保在Spring容器完全启动后再启动WebSocket服务
|
||||
* - 使用CompletableFuture异步启动,避免阻塞Spring Boot主线程
|
||||
* - 支持优雅关闭,确保资源正确释放
|
||||
* - 完善的异常处理和日志记录
|
||||
*
|
||||
* @Description: websocket服务端
|
||||
* @Author: wr
|
||||
* @Date: 2024/12/10 13:59
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class WebSocketService implements ApplicationRunner {
|
||||
|
||||
/**
|
||||
* WebSocket服务器监听端口
|
||||
* 默认7777端口,可通过配置文件webSocket.port自定义
|
||||
* 客户端连接地址:ws://host:port/hello?name=userId
|
||||
*/
|
||||
@Value("${webSocket.port:7777}")
|
||||
int port;
|
||||
|
||||
/**
|
||||
* Netty Boss线程组
|
||||
* 专门负责接受新的客户端连接请求
|
||||
* 通常配置1个线程即可,因为接受连接的操作相对简单
|
||||
*/
|
||||
EventLoopGroup bossGroup;
|
||||
|
||||
/**
|
||||
* Netty Worker线程组
|
||||
* 专门负责处理已建立连接的I/O操作和业务逻辑
|
||||
* 默认线程数 = CPU核心数 * 2,用于并发处理多个客户端
|
||||
*/
|
||||
EventLoopGroup workerGroup;
|
||||
|
||||
/**
|
||||
* 服务器通道引用
|
||||
* 保存绑定端口后的Channel,用于服务器关闭时释放资源
|
||||
*/
|
||||
private Channel serverChannel;
|
||||
|
||||
/**
|
||||
* 异步启动任务的Future对象
|
||||
* 用于管理WebSocket服务器的异步启动过程
|
||||
* 可以用来取消启动任务或检查启动状态
|
||||
*/
|
||||
private CompletableFuture<Void> serverFuture;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Spring Boot应用启动完成后自动调用此方法
|
||||
* 使用ApplicationRunner确保在所有Bean初始化完成后再启动WebSocket服务
|
||||
*/
|
||||
@Override
|
||||
public void run(ApplicationArguments args){
|
||||
// 使用CompletableFuture异步启动WebSocket服务,避免阻塞Spring Boot主线程
|
||||
// 这样可以让应用快速启动完成,WebSocket服务在后台异步启动
|
||||
serverFuture = CompletableFuture.runAsync(this::startWebSocketServer)
|
||||
.exceptionally(throwable -> {
|
||||
// 如果启动过程中发生异常,记录日志但不影响应用启动
|
||||
log.error("WebSocket服务启动异常", throwable);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动WebSocket服务器的核心方法
|
||||
* 此方法会一直阻塞直到服务器关闭,所以需要在异步线程中执行
|
||||
*/
|
||||
private void startWebSocketServer() {
|
||||
try {
|
||||
// 1. 创建线程组
|
||||
// bossGroup: 专门负责接受新的客户端连接请求
|
||||
// 可以自定义线程的数量,这里使用默认值(通常为1个线程)
|
||||
bossGroup = new NioEventLoopGroup(1);
|
||||
|
||||
// workerGroup: 专门负责处理已建立连接的I/O操作
|
||||
// 默认创建的线程数量 = CPU 处理器数量 * 2,用于处理业务逻辑
|
||||
workerGroup = new NioEventLoopGroup();
|
||||
|
||||
// 2. 配置服务器启动参数
|
||||
ServerBootstrap serverBootstrap = new ServerBootstrap();
|
||||
serverBootstrap.group(bossGroup, workerGroup)
|
||||
.channel(NioServerSocketChannel.class)
|
||||
.handler(new LoggingHandler())
|
||||
// 网络配置参数
|
||||
.option(ChannelOption.SO_BACKLOG, 128)
|
||||
// TCP连接建立超时时间5秒
|
||||
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
|
||||
// 子通道配置(针对每个客户端连接)
|
||||
// 启用TCP keepalive机制,检测死连接
|
||||
.childOption(ChannelOption.SO_KEEPALIVE, true)
|
||||
.childHandler(new WebSocketInitializer());
|
||||
|
||||
// 3. 绑定端口并启动服务器
|
||||
ChannelFuture future = serverBootstrap.bind(port).sync();
|
||||
// 保存服务器通道引用,用于后续关闭操作
|
||||
serverChannel = future.channel();
|
||||
// 4. 监听绑定结果并记录日志
|
||||
future.addListener(f -> {
|
||||
if (future.isSuccess()) {
|
||||
log.info("webSocket服务启动成功,端口:{}", port);
|
||||
} else {
|
||||
log.error("webSocket服务启动失败,端口:{}", port);
|
||||
}
|
||||
});
|
||||
|
||||
// 5. 等待服务器关闭
|
||||
// 这里会一直阻塞,直到serverChannel被外部关闭
|
||||
// 这就是为什么需要在异步线程中执行此方法的原因
|
||||
future.channel().closeFuture().sync();
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
// 如果线程被中断(比如应用关闭),记录日志并恢复中断状态
|
||||
log.error("WebSocket服务启动过程中被中断", e);
|
||||
Thread.currentThread().interrupt(); // 恢复中断状态
|
||||
} catch (Exception e) {
|
||||
// 捕获其他所有异常,记录日志并抛出运行时异常
|
||||
log.error("WebSocket服务启动失败", e);
|
||||
throw new RuntimeException("WebSocket服务启动失败", e);
|
||||
} finally {
|
||||
// 无论成功还是失败,都要清理资源
|
||||
shutdownGracefully();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 优雅关闭Netty线程组资源
|
||||
* 私有方法,用于在服务器启动异常时清理资源
|
||||
*/
|
||||
private void shutdownGracefully() {
|
||||
// 优雅关闭接收连接的线程组
|
||||
if (bossGroup != null) {
|
||||
bossGroup.shutdownGracefully();
|
||||
}
|
||||
// 优雅关闭处理I/O的线程组
|
||||
if (workerGroup != null) {
|
||||
workerGroup.shutdownGracefully();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Spring容器销毁时自动调用此方法释放资源
|
||||
* 使用@PreDestroy确保在应用关闭时优雅地关闭WebSocket服务
|
||||
*/
|
||||
@PreDestroy
|
||||
public void destroy() throws InterruptedException {
|
||||
log.info("正在关闭WebSocket服务...");
|
||||
|
||||
// 步骤1: 首先关闭服务器通道,停止接受新的连接请求
|
||||
// 这样可以确保不会有新的客户端连接进来
|
||||
if (serverChannel != null) {
|
||||
try {
|
||||
// 等待最多5秒让服务器通道关闭
|
||||
serverChannel.close().awaitUninterruptibly(5, TimeUnit.SECONDS);
|
||||
log.debug("服务器通道已关闭");
|
||||
} catch (Exception e) {
|
||||
log.warn("关闭服务器通道时发生异常", e);
|
||||
}
|
||||
}
|
||||
|
||||
// 步骤2: 关闭bossGroup线程组
|
||||
// bossGroup负责接受连接,现在可以安全关闭了
|
||||
if (bossGroup != null) {
|
||||
try {
|
||||
// 优雅关闭:静默期0秒,超时时间5秒
|
||||
// 静默期0秒意味着立即开始关闭,超时5秒后强制关闭
|
||||
bossGroup.shutdownGracefully(0, 5, TimeUnit.SECONDS).sync();
|
||||
log.debug("bossGroup线程组已关闭");
|
||||
} catch (InterruptedException e) {
|
||||
log.warn("关闭bossGroup时被中断", e);
|
||||
Thread.currentThread().interrupt(); // 恢复中断状态
|
||||
}
|
||||
}
|
||||
|
||||
// 步骤3: 关闭workerGroup线程组
|
||||
// workerGroup负责处理I/O,需要等待现有连接处理完成
|
||||
if (workerGroup != null) {
|
||||
try {
|
||||
// 等待现有任务完成,但最多等待5秒
|
||||
workerGroup.shutdownGracefully(0, 5, TimeUnit.SECONDS).sync();
|
||||
log.debug("workerGroup线程组已关闭");
|
||||
} catch (InterruptedException e) {
|
||||
log.warn("关闭workerGroup时被中断", e);
|
||||
Thread.currentThread().interrupt(); // 恢复中断状态
|
||||
}
|
||||
}
|
||||
|
||||
// 步骤4: 取消异步启动任务(如果还在运行)
|
||||
// 这可以避免在应用关闭后还有线程在后台运行
|
||||
if (serverFuture != null && !serverFuture.isDone()) {
|
||||
// true表示允许中断正在执行的任务
|
||||
boolean cancelled = serverFuture.cancel(true);
|
||||
if (cancelled) {
|
||||
log.debug("异步启动任务已取消");
|
||||
}
|
||||
}
|
||||
|
||||
log.info("webSocket服务已销毁");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.njcn.gather.device.controller;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.njcn.common.pojo.annotation.OperateInfo;
|
||||
@@ -10,12 +9,9 @@ import com.njcn.common.pojo.enums.response.CommonResponseEnum;
|
||||
import com.njcn.common.pojo.exception.BusinessException;
|
||||
import com.njcn.common.pojo.response.HttpResult;
|
||||
import com.njcn.common.utils.LogUtil;
|
||||
import com.njcn.gather.device.pojo.enums.CommonEnum;
|
||||
import com.njcn.gather.device.pojo.param.PqDevParam;
|
||||
import com.njcn.gather.device.pojo.vo.PqDevVO;
|
||||
import com.njcn.gather.device.service.IPqDevService;
|
||||
import com.njcn.gather.plan.pojo.po.AdPlan;
|
||||
import com.njcn.gather.type.pojo.po.DevType;
|
||||
import com.njcn.gather.type.service.IDevTypeService;
|
||||
import com.njcn.web.controller.BaseController;
|
||||
import com.njcn.web.utils.FileUtil;
|
||||
@@ -104,7 +100,7 @@ public class PqDevController extends BaseController {
|
||||
@OperateInfo(operateType = OperateType.DELETE)
|
||||
@PostMapping("/delete")
|
||||
@ApiOperation("删除被检设备")
|
||||
@ApiImplicitParam(name = "ids", value = "被检设备id", required = true)
|
||||
@ApiImplicitParam(name = "param", value = "删除参数", required = true)
|
||||
public HttpResult<Boolean> delete(@RequestBody @Validated PqDevParam.DeleteParam param) {
|
||||
String methodDescribe = getMethodDescribe("delete");
|
||||
LogUtil.njcnDebug(log, "{},删除ID数据为:{}", methodDescribe, String.join(StrUtil.COMMA, param.getIds()));
|
||||
@@ -140,22 +136,17 @@ public class PqDevController extends BaseController {
|
||||
@ApiImplicitParam(name = "file", value = "被检设备数据文件", required = true),
|
||||
@ApiImplicitParam(name = "patternId", value = "模式id", required = true)
|
||||
})
|
||||
public HttpResult<Boolean> importDev(@RequestParam("file") MultipartFile file, @RequestParam("patternId") String patternId, @RequestParam("planId") String planId, HttpServletResponse response) {
|
||||
public HttpResult importDev(@RequestParam("file") MultipartFile file, @RequestParam("patternId") String patternId, @RequestParam("planId") String planId, @RequestParam(value = "cover", defaultValue = "0") Integer cover, HttpServletResponse response) {
|
||||
String methodDescribe = getMethodDescribe("importDev");
|
||||
LogUtil.njcnDebug(log, "{},上传文件为:{}", methodDescribe, file.getOriginalFilename());
|
||||
boolean fileType = FileUtil.judgeFileIsExcel(file.getOriginalFilename());
|
||||
if (!fileType) {
|
||||
throw new BusinessException(CommonResponseEnum.FILE_XLSX_ERROR);
|
||||
}
|
||||
if("null".equals(planId)){
|
||||
if ("null".equals(planId)) {
|
||||
planId = null;
|
||||
}
|
||||
Boolean result = pqDevService.importDev(file, patternId, planId, response);
|
||||
if (result) {
|
||||
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, true, methodDescribe);
|
||||
} else {
|
||||
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.FAIL, false, methodDescribe);
|
||||
}
|
||||
return pqDevService.importDev(file, patternId, planId, response, cover);
|
||||
}
|
||||
|
||||
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
|
||||
@@ -171,10 +162,31 @@ public class PqDevController extends BaseController {
|
||||
|
||||
@OperateInfo
|
||||
@GetMapping("/getSelectOptions")
|
||||
@ApiOperation("根据历史记录信息来获取下拉框内容")
|
||||
@ApiOperation("从历史数据中查询下拉框选项")
|
||||
@ApiImplicitParam(name = "pattern", value = "模式id", required = true)
|
||||
public HttpResult<Map<String, List<String>>> getSelectOptions(@RequestParam("pattern") String pattern) {
|
||||
String methodDescribe = getMethodDescribe("getSelectOptions");
|
||||
Map<String, List<String>> result = pqDevService.listSelectOptions(pattern);
|
||||
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe);
|
||||
}
|
||||
|
||||
@OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.UPLOAD)
|
||||
@PostMapping(value = "/ttt")
|
||||
@ApiOperation("批量导入被检设备")
|
||||
@ApiImplicitParams({
|
||||
@ApiImplicitParam(name = "file", value = "被检设备数据文件", required = true),
|
||||
@ApiImplicitParam(name = "patternId", value = "模式id", required = true)
|
||||
})
|
||||
public HttpResult ttt(@RequestParam("file") MultipartFile file, @RequestParam("patternId") String patternId, @RequestParam("planId") String planId, @RequestParam(value = "cover", defaultValue = "0") Integer cover, HttpServletResponse response) {
|
||||
String methodDescribe = getMethodDescribe("ttt");
|
||||
LogUtil.njcnDebug(log, "{},上传文件为:{}", methodDescribe, file.getOriginalFilename());
|
||||
boolean fileType = FileUtil.judgeFileIsExcel(file.getOriginalFilename());
|
||||
if (!fileType) {
|
||||
throw new BusinessException(CommonResponseEnum.FILE_XLSX_ERROR);
|
||||
}
|
||||
if ("null".equals(planId)) {
|
||||
planId = null;
|
||||
}
|
||||
return pqDevService.importDev(file, patternId, planId, response, cover);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,16 +149,28 @@ public class PqStandardDevController extends BaseController {
|
||||
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe);
|
||||
}
|
||||
|
||||
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
|
||||
@PostMapping("/listByPlanId")
|
||||
@ApiOperation("查询出指定计划已关联的标准设备")
|
||||
@ApiImplicitParam(name = "planId", value = "计划id", required = true)
|
||||
public HttpResult<List<PqStandardDev>> listByPlanId(@RequestParam("planId") String planId) {
|
||||
String methodDescribe = getMethodDescribe("listByPlanId");
|
||||
LogUtil.njcnDebug(log, "{},查询数据为:{}", methodDescribe, planId);
|
||||
List<PqStandardDev> pqDevVOList = pqStandardDevService.listByPlanId(planId);
|
||||
// @OperateInfo(info = LogEnum.BUSINESS_COMMON)
|
||||
// @PostMapping("/listByPlanId")
|
||||
// @ApiOperation("查询出指定计划已关联的标准设备")
|
||||
// @ApiImplicitParam(name = "planId", value = "计划id", required = true)
|
||||
// public HttpResult<List<PqStandardDev>> listByPlanId(@RequestParam("planId") String planId) {
|
||||
// String methodDescribe = getMethodDescribe("listByPlanId");
|
||||
// LogUtil.njcnDebug(log, "{},查询数据为:{}", methodDescribe, planId);
|
||||
// List<PqStandardDev> pqDevVOList = pqStandardDevService.listByPlanId(planId);
|
||||
//
|
||||
// return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, pqDevVOList, methodDescribe);
|
||||
// }
|
||||
|
||||
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, pqDevVOList, methodDescribe);
|
||||
|
||||
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
|
||||
@GetMapping("/canBindingList")
|
||||
@ApiOperation("查询可绑定的标准设备")
|
||||
public HttpResult<List<PqStandardDev>> canBindingList() {
|
||||
String methodDescribe = getMethodDescribe("canBindingList");
|
||||
LogUtil.njcnDebug(log, "{},查询可绑定的标准设备", methodDescribe);
|
||||
List<PqStandardDev> result = pqStandardDevService.canBindingList();
|
||||
|
||||
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,6 @@ import java.util.List;
|
||||
*/
|
||||
public interface PqStandardDevMapper extends MPJBaseMapper<PqStandardDev> {
|
||||
|
||||
List<PreDetection> listStandardDevPreDetection(@Param("devIds") List<String> ids);
|
||||
List<PreDetection> listStandardDevPreDetection(@Param("ids") List<String> ids);
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,9 @@
|
||||
<result column="Dev_Chns" property="devChns"/>
|
||||
<result column="Dev_Volt" property="devVolt"/>
|
||||
<result column="Dev_Curr" property="devCurr"/>
|
||||
<result column="Angle" property="angle"/>
|
||||
<result column="Use_Phase_Index" property="usePhaseIndex"/>
|
||||
<result column="Wave_Cmd" property="waveCmd"/>
|
||||
|
||||
<collection
|
||||
property="monitorList"
|
||||
@@ -35,7 +38,10 @@
|
||||
p.name as icdType,
|
||||
t.Dev_Chns,
|
||||
t.Dev_Volt,
|
||||
t.Dev_Curr
|
||||
t.Dev_Curr,
|
||||
p.Angle,
|
||||
p.Use_Phase_Index,
|
||||
t.Wave_Cmd
|
||||
FROM
|
||||
pq_dev d
|
||||
inner join pq_dev_type t on d.Dev_Type = t.id
|
||||
@@ -128,6 +134,7 @@
|
||||
#{item}
|
||||
</foreach>
|
||||
</if>
|
||||
order by dev.Create_Time DESC, dev.Name ASC
|
||||
</select>
|
||||
</mapper>
|
||||
|
||||
|
||||
@@ -15,13 +15,9 @@
|
||||
<result column="Dev_Chns" property="devChns"/>
|
||||
<result column="Dev_Volt" property="devVolt"/>
|
||||
<result column="Dev_Curr" property="devCurr"/>
|
||||
|
||||
<!-- <collection-->
|
||||
<!-- property="monitorList"-->
|
||||
<!-- column="{ devId = Id}"-->
|
||||
<!-- select="com.njcn.gather.monitor.mapper.PqMonitorMapper.selectMonitorInfo"-->
|
||||
<!-- >-->
|
||||
<!-- </collection>-->
|
||||
<result column="Angle" property="angle"/>
|
||||
<result column="Use_Phase_Index" property="usePhaseIndex"/>
|
||||
<result column="Wave_Cmd" property="waveCmd"/>
|
||||
</resultMap>
|
||||
|
||||
<select id="listStandardDevPreDetection" resultMap="standardDevResultMap">
|
||||
@@ -30,15 +26,19 @@
|
||||
standard_dev.Name,
|
||||
standard_dev.IP,
|
||||
standard_dev.Port,
|
||||
standard_dev.Dev_Type,
|
||||
dev_type.name as Dev_Type,
|
||||
standard_dev.Series,
|
||||
standard_dev.Dev_Key,
|
||||
dev_type.icdType,
|
||||
icd_path.Name as icdType,
|
||||
dev_type.Dev_Chns,
|
||||
dev_type.Dev_Volt,
|
||||
dev_type.Dev_Curr
|
||||
dev_type.Dev_Curr,
|
||||
icd_path.Angle,
|
||||
icd_path.Use_Phase_Index,
|
||||
dev_type.Wave_Cmd
|
||||
from pq_standard_dev standard_dev
|
||||
inner join pq_dev_type dev_type on standard_dev.Dev_Type = dev_type.id
|
||||
inner join pq_icd_path icd_path on dev_type.icd = icd_path.id
|
||||
where standard_dev.Id in
|
||||
<foreach collection="ids" item="id" open="(" separator="," close=")">
|
||||
#{id}
|
||||
|
||||
@@ -19,4 +19,13 @@ public enum PatternEnum {
|
||||
this.value = value;
|
||||
this.msg = msg;
|
||||
}
|
||||
|
||||
public static PatternEnum getEnum(String value) {
|
||||
for (PatternEnum patternEnum : PatternEnum.values()) {
|
||||
if (patternEnum.getValue().equals(value)) {
|
||||
return patternEnum;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,14 +6,12 @@ import com.njcn.gather.pojo.constant.DetectionValidMessage;
|
||||
import com.njcn.web.pojo.annotation.DateTimeStrValid;
|
||||
import com.njcn.web.pojo.param.BaseParam;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import io.swagger.models.auth.In;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.hibernate.validator.constraints.Range;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import javax.validation.constraints.*;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -137,6 +135,7 @@ public class PqDevParam {
|
||||
|
||||
@ApiModelProperty("是否为导入设备")
|
||||
private Integer importFlag;
|
||||
|
||||
/**
|
||||
* 更新操作实体
|
||||
*/
|
||||
@@ -197,6 +196,12 @@ public class PqDevParam {
|
||||
|
||||
@ApiModelProperty("是否分配")
|
||||
private Integer assign;
|
||||
@ApiModelProperty("关键词")
|
||||
private String keywords;
|
||||
@ApiModelProperty("主计划ID")
|
||||
private String planId;
|
||||
@ApiModelProperty("是否分配子计划")
|
||||
private Integer assignSub;
|
||||
}
|
||||
|
||||
@Data
|
||||
|
||||
@@ -9,11 +9,13 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
|
||||
import com.njcn.db.mybatisplus.bo.BaseEntity;
|
||||
import com.njcn.gather.monitor.pojo.po.PqMonitor;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author caozehui
|
||||
@@ -186,5 +188,8 @@ public class PqDev extends BaseEntity implements Serializable {
|
||||
* 状态:0-删除 1-正常
|
||||
*/
|
||||
private Integer state;
|
||||
|
||||
@TableField(exist = false)
|
||||
private List<PqMonitor> monitorList;
|
||||
}
|
||||
|
||||
|
||||
@@ -50,21 +50,6 @@ public class PqDevSub {
|
||||
*/
|
||||
private Integer factorCheckResult;
|
||||
|
||||
/**
|
||||
* 实时数据结果 0:不合格,1:合格,2:未检
|
||||
*/
|
||||
private Integer realtimeResult;
|
||||
|
||||
/**
|
||||
* 统计数据结果 0:不合格,1:合格,2:未检
|
||||
*/
|
||||
private Integer statisticsResult;
|
||||
|
||||
/**
|
||||
* 录波数据结果 0:不合格,1:合格,2:未检
|
||||
*/
|
||||
private Integer recordedResult;
|
||||
|
||||
/**
|
||||
* 检测人
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.njcn.gather.device.pojo.po;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.njcn.db.mybatisplus.bo.BaseEntity;
|
||||
import lombok.Data;
|
||||
@@ -72,5 +73,8 @@ public class PqStandardDev extends BaseEntity implements Serializable {
|
||||
private String devKey;
|
||||
|
||||
private Integer state;
|
||||
|
||||
@TableField(exist = false)
|
||||
private boolean disabled;
|
||||
}
|
||||
|
||||
|
||||
@@ -38,8 +38,8 @@ public class ContrastDevExcel implements Serializable {
|
||||
@Pattern(regexp = PatternRegex.DEV_NAME_REGEX, message = DetectionValidMessage.NAME_FORMAT_ERROR)
|
||||
private String name;
|
||||
|
||||
@Excel(name = "设备序列号*", width = 20, needMerge = true, orderNum = "5")
|
||||
@NotBlank(message = DetectionValidMessage.FACTORYNO_NOT_BLANK)
|
||||
@Excel(name = "设备序列号", width = 20, needMerge = true, orderNum = "5")
|
||||
// @NotBlank(message = DetectionValidMessage.FACTORYNO_NOT_BLANK)
|
||||
private String createId;
|
||||
|
||||
@Excel(name = "设备类型*", width = 20, needMerge = true, orderNum = "6")
|
||||
@@ -91,8 +91,8 @@ public class ContrastDevExcel implements Serializable {
|
||||
@NotNull(message = DetectionValidMessage.INSPECT_DATE_NOT_NULL)
|
||||
private LocalDate inspectDate;
|
||||
|
||||
@Excel(name = "谐波系统设备id*", width = 30, needMerge = true, orderNum = "18")
|
||||
@NotBlank(message = DetectionValidMessage.HARM_SYS_ID_NOT_BLANK)
|
||||
@Excel(name = "谐波系统设备id", width = 30, needMerge = true, orderNum = "18")
|
||||
// @NotBlank(message = DetectionValidMessage.HARM_SYS_ID_NOT_BLANK)
|
||||
private String harmSysId;
|
||||
|
||||
@ExcelCollection(name = "监测点信息", orderNum = "19")
|
||||
|
||||
@@ -111,4 +111,9 @@ public class PqDevVO extends PqDev {
|
||||
* 是否已经分配。0-未分配、1-已分配、2-所有
|
||||
*/
|
||||
private Integer assign;
|
||||
|
||||
/**
|
||||
* 检测点结果
|
||||
*/
|
||||
private List<Integer> monitorResults;
|
||||
}
|
||||
|
||||
@@ -54,6 +54,18 @@ public class PreDetection {
|
||||
@JSONField(name = "icdType")
|
||||
private String icdType;
|
||||
|
||||
/**
|
||||
* 是否支持相角。0:不支持,1:支持
|
||||
*/
|
||||
@JSONField(serialize = false)
|
||||
private Integer angle;
|
||||
|
||||
/**
|
||||
* 角型接线时是否使用相别的指标来进行检测,0表示否,1表示是
|
||||
*/
|
||||
@JSONField(serialize = false)
|
||||
private Integer usePhaseIndex;
|
||||
|
||||
/**
|
||||
* 装置识别码(3ds加密)
|
||||
*/
|
||||
@@ -72,6 +84,9 @@ public class PreDetection {
|
||||
private Double devVolt;
|
||||
|
||||
private Double devCurr;
|
||||
|
||||
@JSONField(serialize = false)
|
||||
private String waveCmd;
|
||||
/**
|
||||
* 监测点信息
|
||||
*/
|
||||
@@ -97,25 +112,37 @@ public class PreDetection {
|
||||
/**
|
||||
* pt
|
||||
*/
|
||||
@JSONField(name = "pt")
|
||||
private String pt;
|
||||
@JSONField(serialize = false)
|
||||
private String ptStr;
|
||||
|
||||
/**
|
||||
* ct
|
||||
*/
|
||||
@JSONField(name = "ct") //todo 是否改为ct
|
||||
private String ct;
|
||||
@JSONField(serialize = false)
|
||||
private String ctStr;
|
||||
|
||||
/**
|
||||
* pt
|
||||
*/
|
||||
@JSONField(name = "pt")
|
||||
private Double pt;
|
||||
|
||||
/**
|
||||
* ct
|
||||
*/
|
||||
@JSONField(name = "ct")
|
||||
private Double ct;
|
||||
|
||||
/**
|
||||
* 统计间隔
|
||||
*/
|
||||
@JSONField(name = "statInterval")
|
||||
@JSONField(serialize = false)
|
||||
private Integer statInterval;
|
||||
|
||||
/**
|
||||
* 接线方式
|
||||
*/
|
||||
@JSONField(name = "connection")
|
||||
@JSONField(serialize = false)
|
||||
private String connection;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.njcn.gather.device.service;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.njcn.common.pojo.poi.PullDown;
|
||||
import com.njcn.common.pojo.response.HttpResult;
|
||||
import com.njcn.gather.device.pojo.enums.TimeCheckResultEnum;
|
||||
import com.njcn.gather.device.pojo.param.PqDevParam;
|
||||
import com.njcn.gather.device.pojo.po.PqDev;
|
||||
@@ -109,14 +110,22 @@ public interface IPqDevService extends IService<PqDev> {
|
||||
* 正式监测完成,修改中断状态
|
||||
*
|
||||
* @param ids
|
||||
* @param valueType
|
||||
* @param adType
|
||||
* @param code
|
||||
* @param userId
|
||||
* @param temperature
|
||||
* @param humidity
|
||||
* @return
|
||||
*/
|
||||
boolean updateResult(List<String> ids, List<String> valueType, String code, String userId, Float temperature, Float humidity);
|
||||
boolean updateResult(List<String> ids, List<String> adType, String code, String userId, Float temperature, Float humidity);
|
||||
|
||||
/**
|
||||
* 比对式-修改设备状态
|
||||
*
|
||||
* @param devId
|
||||
* @param userId
|
||||
*/
|
||||
void updateResult(String devId, String userId);
|
||||
|
||||
void updatePqDevReportState(String devId, int i);
|
||||
|
||||
@@ -152,7 +161,7 @@ public interface IPqDevService extends IService<PqDev> {
|
||||
* @param planId 计划Id
|
||||
* @param response 响应
|
||||
*/
|
||||
boolean importDev(MultipartFile file, String patternId, String planId, HttpServletResponse response);
|
||||
HttpResult importDev(MultipartFile file, String patternId, String planId, HttpServletResponse response, Integer cover);
|
||||
|
||||
/**
|
||||
* 导入灿能二楼设备数据
|
||||
@@ -246,7 +255,7 @@ public interface IPqDevService extends IService<PqDev> {
|
||||
* @param planId 计划Id
|
||||
* @param response 响应
|
||||
*/
|
||||
boolean importContrastDev(MultipartFile file, String patternId, String planId, HttpServletResponse response);
|
||||
HttpResult importContrastDev(MultipartFile file, String patternId, String planId, HttpServletResponse response, Integer cover);
|
||||
|
||||
/**
|
||||
* 导入比对式设备数据
|
||||
@@ -254,7 +263,7 @@ public interface IPqDevService extends IService<PqDev> {
|
||||
* @param contrastDevExcelList
|
||||
* @param patternId
|
||||
*/
|
||||
boolean importContrastDev(List<ContrastDevExcel> contrastDevExcelList, String patternId, String planId);
|
||||
HttpResult importContrastDev(List<ContrastDevExcel> contrastDevExcelList, String patternId, String planId, Integer cover);
|
||||
|
||||
/**
|
||||
* 获取比对式设备导出、导出文件模板的下拉列表
|
||||
|
||||
@@ -82,7 +82,7 @@ public interface IPqStandardDevService extends IService<PqStandardDev> {
|
||||
* @param planId
|
||||
* @return
|
||||
*/
|
||||
List<PqStandardDev> listByPlanId(String planId);
|
||||
// List<PqStandardDev> listByPlanId(String planId);
|
||||
|
||||
/**
|
||||
* 查询出标准设备所需的检测信息
|
||||
@@ -91,4 +91,12 @@ public interface IPqStandardDevService extends IService<PqStandardDev> {
|
||||
* @return
|
||||
*/
|
||||
List<PreDetection> listStandardDevPreDetection(List<String> ids);
|
||||
|
||||
|
||||
/**
|
||||
* 查询可绑定的标准设备列表
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
List<PqStandardDev> canBindingList();
|
||||
}
|
||||
|
||||
@@ -11,15 +11,19 @@ import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.Wrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.github.yulichang.wrapper.MPJLambdaWrapper;
|
||||
import com.njcn.common.pojo.constant.PatternRegex;
|
||||
import com.njcn.common.pojo.enums.common.DataStateEnum;
|
||||
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
|
||||
import com.njcn.common.pojo.exception.BusinessException;
|
||||
import com.njcn.common.pojo.poi.PullDown;
|
||||
import com.njcn.common.pojo.response.HttpResult;
|
||||
import com.njcn.common.utils.EncryptionUtil;
|
||||
import com.njcn.db.mybatisplus.constant.DbConstant;
|
||||
import com.njcn.gather.device.mapper.PqDevMapper;
|
||||
@@ -33,6 +37,7 @@ import com.njcn.gather.device.service.IPqDevSubService;
|
||||
import com.njcn.gather.monitor.pojo.po.PqMonitor;
|
||||
import com.njcn.gather.monitor.pojo.vo.PqMonitorExcel;
|
||||
import com.njcn.gather.monitor.service.IPqMonitorService;
|
||||
import com.njcn.gather.plan.pojo.enums.DataSourceEnum;
|
||||
import com.njcn.gather.pojo.enums.DetectionResponseEnum;
|
||||
import com.njcn.gather.storage.service.DetectionDataDealService;
|
||||
import com.njcn.gather.system.cfg.pojo.enums.SceneEnum;
|
||||
@@ -44,9 +49,11 @@ import com.njcn.gather.system.dictionary.service.IDictDataService;
|
||||
import com.njcn.gather.system.dictionary.service.IDictTypeService;
|
||||
import com.njcn.gather.type.pojo.po.DevType;
|
||||
import com.njcn.gather.type.service.IDevTypeService;
|
||||
import com.njcn.gather.user.user.pojo.po.SysUser;
|
||||
import com.njcn.gather.user.user.service.ISysUserService;
|
||||
import com.njcn.web.factory.PageFactory;
|
||||
import com.njcn.web.utils.ExcelUtil;
|
||||
import com.njcn.web.utils.HttpResultUtil;
|
||||
import com.njcn.web.utils.PoiUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -57,11 +64,11 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* @author caozehui
|
||||
@@ -210,8 +217,9 @@ public class PqDevServiceImpl extends ServiceImpl<PqDevMapper, PqDev> implements
|
||||
public boolean deletePqDev(PqDevParam.DeleteParam param) {
|
||||
if (PatternEnum.CONTRAST.getValue().equals(dictDataService.getDictDataById(param.getPattern()).getCode())) {
|
||||
for (String id : param.getIds()) {
|
||||
if (ObjectUtils.isNotEmpty(pqMonitorService.listPqMonitorByDevId(id))) {
|
||||
throw new BusinessException(DetectionResponseEnum.PQ_DEV_HAS_MONITOR);
|
||||
if (ObjectUtils.isNotEmpty(pqMonitorService.listPqMonitorByDevIds(Collections.singletonList(id)))) {
|
||||
// throw new BusinessException(DetectionResponseEnum.PQ_DEV_HAS_MONITOR);
|
||||
pqMonitorService.removeByDevId(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -269,6 +277,11 @@ public class PqDevServiceImpl extends ServiceImpl<PqDevMapper, PqDev> implements
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("id", pqDev.getId());
|
||||
map.put("name", pqDev.getName());
|
||||
map.put("devType", pqDev.getDevType());
|
||||
map.put("manufacturer", pqDev.getManufacturer());
|
||||
map.put("cityName", pqDev.getCityName());
|
||||
map.put("gdName", pqDev.getGdName());
|
||||
map.put("subName", pqDev.getSubName());
|
||||
return map;
|
||||
}).collect(Collectors.toList());
|
||||
return result;
|
||||
@@ -280,6 +293,33 @@ public class PqDevServiceImpl extends ServiceImpl<PqDevMapper, PqDev> implements
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<PqDevVO> pqDevList = this.baseMapper.selectByQueryParam(param);
|
||||
pqDevList.forEach(pqDev -> {
|
||||
List<PqMonitor> monitorList = pqMonitorService.listPqMonitorByDevIds(Collections.singletonList(pqDev.getId()));
|
||||
List<PqMonitor> enabledMonitorList = monitorList.stream().filter(x -> x.getCheckFlag() == 1).collect(Collectors.toList());
|
||||
pqDev.setMonitorResults(enabledMonitorList.stream().map(x -> {
|
||||
if (ObjectUtil.isNull(x.getResultType())) {
|
||||
return CheckResultEnum.UNCHECKED.getValue();
|
||||
} else {
|
||||
DataSourceEnum dataSourceEnum = DataSourceEnum.ofByValue(x.getResultType());
|
||||
|
||||
switch (dataSourceEnum) {
|
||||
case REAL_DATA:
|
||||
return x.getRealtimeResult();
|
||||
case MINUTE_STATISTICS_AVG:
|
||||
case MINUTE_STATISTICS_CP95:
|
||||
case MINUTE_STATISTICS_MIN:
|
||||
case MINUTE_STATISTICS_MAX:
|
||||
return x.getStatisticsResult();
|
||||
case WAVE_DATA:
|
||||
return x.getRecordedResult();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return CheckResultEnum.UNCHECKED.getValue();
|
||||
}).collect(Collectors.toList()));
|
||||
});
|
||||
|
||||
return pqDevList;
|
||||
}
|
||||
|
||||
@@ -349,9 +389,13 @@ public class PqDevServiceImpl extends ServiceImpl<PqDevMapper, PqDev> implements
|
||||
pqDevVO.setDevKey(EncryptionUtil.decoderString(1, pqDevVO.getDevKey()));
|
||||
}
|
||||
if (StrUtil.isNotBlank(pqDevVO.getCheckBy())) {
|
||||
pqDevVO.setCheckBy(userService.getById(pqDevVO.getCheckBy()).getName());
|
||||
SysUser sysUser = userService.getById(pqDevVO.getCheckBy());
|
||||
if (ObjectUtil.isNotNull(sysUser)) {
|
||||
pqDevVO.setCheckBy(sysUser.getName());
|
||||
} else {
|
||||
pqDevVO.setCheckBy(pqDevVO.getCheckBy());
|
||||
}
|
||||
}
|
||||
|
||||
DevType devType = devTypeService.getById(pqDevVO.getDevType());
|
||||
if (ObjectUtil.isNotNull(devType)) {
|
||||
pqDevVO.setDevChns(devType.getDevChns());
|
||||
@@ -359,7 +403,7 @@ public class PqDevServiceImpl extends ServiceImpl<PqDevMapper, PqDev> implements
|
||||
pqDevVO.setDevVolt(devType.getDevVolt());
|
||||
}
|
||||
|
||||
List<PqMonitor> monitorList = pqMonitorService.listPqMonitorByDevId(id);
|
||||
List<PqMonitor> monitorList = pqMonitorService.listPqMonitorByDevIds(Collections.singletonList(id));
|
||||
if (ObjectUtil.isNotEmpty(monitorList)) {
|
||||
pqDevVO.setMonitorList(monitorList);
|
||||
}
|
||||
@@ -386,12 +430,21 @@ public class PqDevServiceImpl extends ServiceImpl<PqDevMapper, PqDev> implements
|
||||
.in(CollectionUtil.isNotEmpty(queryParam.getPlanIdList()), "pq_dev.Plan_Id", queryParam.getPlanIdList())
|
||||
.isNotNull(DataStateEnum.ENABLE.getCode().equals(queryParam.getAssign()), "pq_dev.Plan_Id")
|
||||
.isNull(DataStateEnum.DELETED.getCode().equals(queryParam.getAssign()), "pq_dev.Plan_Id")
|
||||
.eq(DataStateEnum.DELETED.getCode().equals(queryParam.getAssignSub()), "pq_dev.Plan_Id", queryParam.getPlanId())
|
||||
.ne(DataStateEnum.ENABLE.getCode().equals(queryParam.getAssignSub()), "pq_dev.Plan_Id", queryParam.getPlanId())
|
||||
.between(ObjectUtil.isAllNotEmpty(queryParam.getSearchBeginTime(), queryParam.getSearchEndTime()), "pq_dev.Create_Date", queryParam.getSearchBeginTime(), queryParam.getSearchEndTime());
|
||||
if (StrUtil.isNotBlank(queryParam.getRegion())) {
|
||||
queryWrapper.and(w -> w.like(StrUtil.isNotBlank(queryParam.getRegion()), "pq_dev.City_Name", queryParam.getRegion())
|
||||
.or().like(StrUtil.isNotBlank(queryParam.getRegion()), "pq_dev.Gd_Name", queryParam.getRegion())
|
||||
.or().like(StrUtil.isNotBlank(queryParam.getRegion()), "pq_dev.Sub_Name", queryParam.getRegion()));
|
||||
}
|
||||
if (StrUtil.isNotBlank(queryParam.getKeywords())) {
|
||||
queryWrapper.and(w -> w.like(StrUtil.isNotBlank(queryParam.getKeywords()), "pq_dev.City_Name", queryParam.getKeywords())
|
||||
.or().like(StrUtil.isNotBlank(queryParam.getKeywords()), "pq_dev.Gd_Name", queryParam.getKeywords())
|
||||
.or().like(StrUtil.isNotBlank(queryParam.getKeywords()), "pq_dev.Sub_Name", queryParam.getKeywords())
|
||||
.or().like(StrUtil.isNotBlank(queryParam.getKeywords()), "pq_dev.name", queryParam.getKeywords())
|
||||
);
|
||||
}
|
||||
//排序
|
||||
if (ObjectUtil.isAllNotEmpty(queryParam.getSortBy(), queryParam.getOrderBy())) {
|
||||
queryWrapper.orderBy(true, queryParam.getOrderBy().equals(DbConstant.ASC), StrUtil.toUnderlineCase(queryParam.getSortBy()));
|
||||
@@ -418,8 +471,8 @@ public class PqDevServiceImpl extends ServiceImpl<PqDevMapper, PqDev> implements
|
||||
monitorListDTO = new PreDetection.MonitorListDTO();
|
||||
monitorListDTO.setLineId(preDetection.getDevIP() + "_" + i);
|
||||
monitorListDTO.setLine(i);
|
||||
monitorListDTO.setPt("1");
|
||||
monitorListDTO.setCt("1");
|
||||
monitorListDTO.setPt(1.0);
|
||||
monitorListDTO.setCt(1.0);
|
||||
monitorList.add(monitorListDTO);
|
||||
}
|
||||
preDetection.setMonitorList(monitorList);
|
||||
@@ -430,10 +483,11 @@ public class PqDevServiceImpl extends ServiceImpl<PqDevMapper, PqDev> implements
|
||||
|
||||
|
||||
@Override
|
||||
public boolean updateResult(List<String> ids, List<String> valueType, String code, String userId, Float temperature, Float humidity) {
|
||||
public boolean updateResult(List<String> ids, List<String> adType, String code, String userId, Float temperature, Float humidity) {
|
||||
if (CollUtil.isNotEmpty(ids)) {
|
||||
|
||||
SysTestConfig config = sysTestConfigService.getOneConfig();
|
||||
Map<String, Integer> result = detectionDataDealService.devResult(ids, valueType, code);
|
||||
Map<String, Integer> result = detectionDataDealService.devResult(false, ids, adType, code);
|
||||
List<PqDevVO> list = new ArrayList<>();
|
||||
if (CollUtil.isNotEmpty(ids)) {
|
||||
list.addAll(this.baseMapper.listByDevIds(ids));
|
||||
@@ -503,6 +557,71 @@ public class PqDevServiceImpl extends ServiceImpl<PqDevMapper, PqDev> implements
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void updateResult(String devId, String userId) {
|
||||
PqDev dev = this.getById(devId);
|
||||
Integer checkState = pqMonitorService.getDevCheckState(devId);
|
||||
Integer checkResult = pqMonitorService.getDevCheckResult(devId);
|
||||
if (checkResult == 1) {
|
||||
checkResult = CheckResultEnum.ACCORD.getValue();
|
||||
}
|
||||
if (checkResult == 2) {
|
||||
checkResult = CheckResultEnum.NOT_ACCORD.getValue();
|
||||
}
|
||||
|
||||
|
||||
SysUser user = userService.getById(userId);
|
||||
|
||||
LambdaUpdateChainWrapper<PqDevSub> w = pqDevSubService.lambdaUpdate()
|
||||
.set(PqDevSub::getCheckState, checkState)
|
||||
.set(PqDevSub::getCheckResult, checkResult)
|
||||
.set(PqDevSub::getReportState, DevReportStateEnum.NOT_GENERATED.getValue())
|
||||
.set(PqDevSub::getCheckTime, LocalDateTime.now())
|
||||
.eq(PqDevSub::getDevId, devId);
|
||||
|
||||
if (ObjectUtil.isNotNull(user)) {
|
||||
w.set(PqDevSub::getCheckBy, user.getName());
|
||||
}
|
||||
if (checkState.equals(CheckStateEnum.CHECKED.getValue())) {
|
||||
w.set(PqDevSub::getReportState, DevReportStateEnum.NOT_GENERATED.getValue());
|
||||
}
|
||||
w.update();
|
||||
|
||||
PqDevParam.QueryParam param = new PqDevParam.QueryParam();
|
||||
String planId = dev.getPlanId();
|
||||
param.setPlanIdList(Arrays.asList(planId));
|
||||
List<PqDevVO> pqDevVOList = this.baseMapper.selectByQueryParam(param);
|
||||
|
||||
if (CollUtil.isNotEmpty(pqDevVOList)) {
|
||||
Set<Integer> set = pqDevVOList.stream().filter(obj -> CheckStateEnum.CHECKED.getValue().equals(obj.getCheckState()) || CheckStateEnum.DOCUMENTED.getValue().equals(obj.getCheckState())).map(PqDevVO::getCheckResult).collect(Collectors.toSet());
|
||||
if (checkState == CheckStateEnum.CHECKED.getValue()) {
|
||||
set.add(checkResult);
|
||||
}
|
||||
if (CollUtil.isEmpty(set)) {
|
||||
this.baseMapper.updatePlanCheckResult(planId, CheckResultEnum.UNCHECKED.getValue());
|
||||
} else {
|
||||
if (set.contains(CheckResultEnum.NOT_ACCORD.getValue())) {
|
||||
this.baseMapper.updatePlanCheckResult(planId, CheckResultEnum.NOT_ACCORD.getValue());
|
||||
} else if (set.contains(CheckResultEnum.UNCHECKED.getValue())) {
|
||||
this.baseMapper.updatePlanCheckResult(planId, CheckResultEnum.UNCHECKED.getValue());
|
||||
} else {
|
||||
this.baseMapper.updatePlanCheckResult(planId, CheckResultEnum.ACCORD.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
set = pqDevVOList.stream().map(PqDevVO::getCheckState).collect(Collectors.toSet());
|
||||
if (set.contains(CheckStateEnum.UNCHECKED.getValue())) {
|
||||
this.baseMapper.updatePlanTestState(planId, CheckStateEnum.CHECKING.getValue());
|
||||
} else if (set.contains(CheckStateEnum.CHECKING.getValue())) {
|
||||
this.baseMapper.updatePlanTestState(planId, CheckStateEnum.CHECKING.getValue());
|
||||
} else {
|
||||
this.baseMapper.updatePlanTestState(planId, CheckStateEnum.CHECKED.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updatePqDevReportState(String devId, int reportState) {
|
||||
LambdaUpdateWrapper<PqDevSub> updateWrapper = new LambdaUpdateWrapper<>();
|
||||
@@ -574,25 +693,31 @@ public class PqDevServiceImpl extends ServiceImpl<PqDevMapper, PqDev> implements
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean importDev(MultipartFile file, String patternId, String planId, HttpServletResponse response) {
|
||||
public HttpResult importDev(MultipartFile file, String patternId, String planId, HttpServletResponse response, Integer cover) {
|
||||
DictData dictData = dictDataService.getDictDataById(patternId);
|
||||
if (PatternEnum.CONTRAST.getValue().equals(dictData.getCode())) {
|
||||
return this.importContrastDev(file, patternId, planId, response);
|
||||
return this.importContrastDev(file, patternId, planId, response, cover);
|
||||
} else {
|
||||
String currrentScene = sysTestConfigService.getCurrrentScene();
|
||||
SceneEnum sceneEnum = SceneEnum.getSceneEnum(currrentScene);
|
||||
switch (sceneEnum) {
|
||||
case PROVINCE_PLATFORM:
|
||||
return this.importProvinceDev(file, patternId, planId, response);
|
||||
boolean result = this.importProvinceDev(file, patternId, planId, response);
|
||||
if (result) {
|
||||
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, "导入成功");
|
||||
}
|
||||
case LEAVE_FACTORY_TEST:
|
||||
return this.importCNDev(file, patternId, planId, response);
|
||||
result = this.importCNDev(file, patternId, planId, response);
|
||||
if (result) {
|
||||
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, "导入成功");
|
||||
}
|
||||
case SELF_TEST:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.FAIL, false, "导入失败");
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -719,26 +844,20 @@ public class PqDevServiceImpl extends ServiceImpl<PqDevMapper, PqDev> implements
|
||||
* @param isExcludeSelf 是否排除自己
|
||||
*/
|
||||
private void checkRepeat(PqDevParam param, boolean isExcludeSelf) {
|
||||
QueryWrapper<PqDev> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper
|
||||
.eq("state", DataStateEnum.ENABLE.getCode())
|
||||
.eq(StrUtil.isNotBlank(param.getDevType()), "Dev_Type", param.getDevType())
|
||||
.eq(StrUtil.isNotBlank(param.getPattern()), "pattern", param.getPattern())
|
||||
.eq(StrUtil.isNotBlank(param.getCityName()), "City_Name", param.getCityName())
|
||||
.eq(StrUtil.isNotBlank(param.getGdName()), "Gd_Name", param.getGdName())
|
||||
.eq(StrUtil.isNotBlank(param.getSubName()), "Sub_Name", param.getSubName())
|
||||
.and(q -> q.eq(StrUtil.isNotBlank(param.getName()), "name", param.getName()).or()
|
||||
.eq(StrUtil.isNotBlank(param.getCreateId()), "Create_Id", param.getCreateId())); //设备序列号重复
|
||||
// .eq("manufacturer", param.getManufacturer())
|
||||
// .eq("Dev_Type", param.getDevType()).or()
|
||||
LambdaQueryWrapper<PqDev> queryWrapper = new LambdaQueryWrapper<PqDev>()
|
||||
.eq(PqDev::getState, DataStateEnum.ENABLE.getCode())
|
||||
.eq(StrUtil.isNotBlank(param.getIp()), PqDev::getIp, param.getIp())
|
||||
.eq(ObjectUtil.isNotNull(param.getPort()), PqDev::getPort, param.getPort())
|
||||
.eq(StrUtil.isNotBlank(param.getPattern()), PqDev::getPattern, param.getPattern())
|
||||
.eq(StrUtil.isNotBlank(param.getCreateId()), PqDev::getCreateId, param.getCreateId());
|
||||
if (isExcludeSelf) {
|
||||
if (param instanceof PqDevParam.UpdateParam) {
|
||||
queryWrapper.ne("id", ((PqDevParam.UpdateParam) param).getId());
|
||||
queryWrapper.ne(PqDev::getId, ((PqDevParam.UpdateParam) param).getId());
|
||||
}
|
||||
}
|
||||
int count = this.count(queryWrapper);
|
||||
if (count > 0) {
|
||||
throw new BusinessException(DetectionResponseEnum.PQ_DEV_REPEAT);
|
||||
throw new BusinessException(DetectionResponseEnum.PQ_DEV_REPEAT, "【" + param.getName() + "】被检设备已存在");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1112,7 +1231,7 @@ public class PqDevServiceImpl extends ServiceImpl<PqDevMapper, PqDev> implements
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean importContrastDev(MultipartFile file, String patternId, String planId, HttpServletResponse response) {
|
||||
public HttpResult importContrastDev(MultipartFile file, String patternId, String planId, HttpServletResponse response, Integer cover) {
|
||||
ImportParams params = new ImportParams();
|
||||
params.setStartSheetIndex(0);
|
||||
params.setSheetNum(1);
|
||||
@@ -1132,126 +1251,201 @@ public class PqDevServiceImpl extends ServiceImpl<PqDevMapper, PqDev> implements
|
||||
} catch (Exception e) {
|
||||
throw new BusinessException(DetectionResponseEnum.IMPORT_DATA_FAIL);
|
||||
}
|
||||
return this.importContrastDev(contrastDevExcelList, patternId, planId);
|
||||
return this.importContrastDev(contrastDevExcelList, patternId, planId, cover);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public boolean importContrastDev(List<ContrastDevExcel> contrastDevExcelList, String patternId, String planId) {
|
||||
public HttpResult importContrastDev(List<ContrastDevExcel> contrastDevExcelList, String patternId, String planId, Integer cover) {
|
||||
if (CollUtil.isNotEmpty(contrastDevExcelList)) {
|
||||
List<PqMonitor> monitorList = new ArrayList<>();
|
||||
List<PqDev> oldDevList = contrastDevExcelList.stream().map(devExcel -> {
|
||||
PqDev pqDev = BeanUtil.copyProperties(devExcel, PqDev.class);
|
||||
// 根据设备名称分组
|
||||
Map<String, List<ContrastDevExcel>> listMap = contrastDevExcelList.stream()
|
||||
.collect(Collectors.groupingBy(ContrastDevExcel::getName, LinkedHashMap::new, Collectors.toList()));
|
||||
List<PqDev> newDevList = new ArrayList<>(listMap.size());
|
||||
List<PqDev> updateDevList = new ArrayList<>();
|
||||
List<PqDev> finalUpdateDevList = new ArrayList<>();
|
||||
for (Map.Entry<String, List<ContrastDevExcel>> entry : listMap.entrySet()) {
|
||||
String name = entry.getKey();
|
||||
|
||||
if (pqDev.getEncryptionFlag() == 1) {
|
||||
if (StrUtil.isNotBlank(pqDev.getSeries()) && StrUtil.isNotBlank(pqDev.getDevKey())) {
|
||||
pqDev.setSeries(EncryptionUtil.encodeString(1, pqDev.getSeries()));
|
||||
pqDev.setDevKey(EncryptionUtil.encodeString(1, pqDev.getDevKey()));
|
||||
List<ContrastDevExcel> devExcelList = entry.getValue();
|
||||
// 监测点数据
|
||||
List<PqMonitorExcel> pqMonitorExcelList = devExcelList.stream()
|
||||
.map(ContrastDevExcel::getPqMonitorExcelList)
|
||||
.filter(Objects::nonNull)
|
||||
.flatMap(List::stream)
|
||||
// 过滤掉没有线路号的数据
|
||||
.filter(item -> ObjectUtil.isNotNull(item.getNum()))
|
||||
.collect(Collectors.toList());
|
||||
// 取第一条为设备基本信息
|
||||
ContrastDevExcel devExcel = devExcelList.get(0);
|
||||
PqDev importPqDev = BeanUtil.copyProperties(devExcel, PqDev.class);
|
||||
if (importPqDev.getEncryptionFlag() == 1) {
|
||||
if (StrUtil.isNotBlank(importPqDev.getSeries()) && StrUtil.isNotBlank(importPqDev.getDevKey())) {
|
||||
importPqDev.setSeries(EncryptionUtil.encodeString(1, importPqDev.getSeries()));
|
||||
importPqDev.setDevKey(EncryptionUtil.encodeString(1, importPqDev.getDevKey()));
|
||||
} else {
|
||||
throw new BusinessException(DetectionResponseEnum.SERIES_AND_DEVKEY_NOT_BLANK);
|
||||
}
|
||||
}
|
||||
DevType devType = devTypeService.getByName(pqDev.getDevType());
|
||||
DevType devType = devTypeService.getByName(importPqDev.getDevType());
|
||||
if (ObjectUtil.isNull(devType)) {
|
||||
throw new BusinessException(DetectionResponseEnum.DEV_TYPE_NOT_EXIST);
|
||||
}
|
||||
// 校验监测点数量
|
||||
int devChns = devType.getDevChns();
|
||||
// if (pqMonitorExcelList.size() != devChns) {
|
||||
// throw new BusinessException(DetectionResponseEnum.IMPORT_DATA_FAIL, "【" + name + "】的设备类型必须具备" + devChns + "个监测点信息!");
|
||||
// }
|
||||
List<Integer> numList = pqMonitorExcelList.stream().map(PqMonitorExcel::getNum).collect(Collectors.toList());
|
||||
// 判断是否有重复的num
|
||||
Set<Integer> uniqueNumSet = new HashSet<>(numList);
|
||||
if (uniqueNumSet.size() != numList.size()) {
|
||||
throw new BusinessException(DetectionResponseEnum.MONITOR_NUM_REPEAT, "【" + name + "】该被检设备下存在相同线路号的监测点!");
|
||||
}
|
||||
Integer max = CollectionUtil.max(numList);
|
||||
Integer min = CollectionUtil.min(numList);
|
||||
if (min < 1 || max > devChns) {
|
||||
throw new BusinessException(DetectionResponseEnum.MONITOR_NUM_OUT_OF_RANGE);
|
||||
}
|
||||
|
||||
importPqDev.setDevType(devType.getId());
|
||||
importPqDev.setImportFlag(1);
|
||||
importPqDev.setId(UUID.randomUUID().toString().replaceAll("-", ""));
|
||||
|
||||
if (StrUtil.isEmpty(importPqDev.getCreateId())) {
|
||||
// 与设备名称相同
|
||||
importPqDev.setCreateId(importPqDev.getName());
|
||||
}
|
||||
// 根据num排序
|
||||
pqMonitorExcelList.sort(Comparator.comparingInt(PqMonitorExcel::getNum));
|
||||
StringBuilder inspectChannelBuilder = new StringBuilder();
|
||||
for (int i = 0; i < numList.size(); i++) {
|
||||
inspectChannelBuilder.append(numList.get(i));
|
||||
if (i < numList.size() - 1) {
|
||||
inspectChannelBuilder.append(StrUtil.COMMA);
|
||||
}
|
||||
}
|
||||
importPqDev.setInspectChannel(inspectChannelBuilder.toString());
|
||||
List<PqMonitor> monitorList = new ArrayList<>();
|
||||
// 1、先判断是否有谐波系统ID
|
||||
boolean hasHarmSys = false;
|
||||
if (StrUtil.isNotBlank(importPqDev.getHarmSysId())) {
|
||||
// 1.1、如果有则判断是否已存在
|
||||
List<PqDev> hasList = this.lambdaQuery()
|
||||
.eq(PqDev::getState, DataStateEnum.ENABLE.getCode())
|
||||
.eq(PqDev::getPattern, patternId)
|
||||
.eq(PqDev::getHarmSysId, importPqDev.getHarmSysId())
|
||||
.isNull(PqDev::getPlanId)
|
||||
.orderByDesc(PqDev::getCreateTime).list();
|
||||
// 1.2、 存在则放入强制更新list
|
||||
if (CollUtil.isNotEmpty(hasList)) {
|
||||
importPqDev.setId(hasList.get(0).getId());
|
||||
for (PqMonitorExcel pqMonitorExcel : pqMonitorExcelList) {
|
||||
PqMonitor monitor = BeanUtil.copyProperties(pqMonitorExcel, PqMonitor.class);
|
||||
monitor.setDevId(importPqDev.getId());
|
||||
monitor.setPt(pqMonitorExcel.getPt1() + StrUtil.C_COLON + pqMonitorExcel.getPt2());
|
||||
monitor.setCt(pqMonitorExcel.getCt1() + StrUtil.C_COLON + pqMonitorExcel.getCt2());
|
||||
monitorList.add(monitor);
|
||||
}
|
||||
importPqDev.setMonitorList(monitorList);
|
||||
finalUpdateDevList.add(importPqDev);
|
||||
hasHarmSys = true;
|
||||
}
|
||||
|
||||
}
|
||||
if (!hasHarmSys) {
|
||||
// 2、查询 ip + 端口 + 序列号 + patternId + Plan_Id为空 是否存在
|
||||
List<PqDev> hasList = this.lambdaQuery()
|
||||
.eq(PqDev::getState, DataStateEnum.ENABLE.getCode())
|
||||
.eq(PqDev::getIp, devExcel.getIp())
|
||||
.eq(PqDev::getPort, devExcel.getPort())
|
||||
.eq(PqDev::getPattern, patternId)
|
||||
.eq(PqDev::getCreateId, devExcel.getCreateId())
|
||||
.isNull(PqDev::getPlanId)
|
||||
.orderByDesc(PqDev::getCreateTime).list();
|
||||
// 2.1、存在则放入更新list
|
||||
if (CollUtil.isNotEmpty(hasList)) {
|
||||
importPqDev.setId(hasList.get(0).getId());
|
||||
for (PqMonitorExcel pqMonitorExcel : pqMonitorExcelList) {
|
||||
PqMonitor monitor = BeanUtil.copyProperties(pqMonitorExcel, PqMonitor.class);
|
||||
monitor.setDevId(importPqDev.getId());
|
||||
monitor.setPt(pqMonitorExcel.getPt1() + StrUtil.C_COLON + pqMonitorExcel.getPt2());
|
||||
monitor.setCt(pqMonitorExcel.getCt1() + StrUtil.C_COLON + pqMonitorExcel.getCt2());
|
||||
monitorList.add(monitor);
|
||||
}
|
||||
importPqDev.setMonitorList(monitorList);
|
||||
updateDevList.add(importPqDev);
|
||||
} else {
|
||||
//2.2、不存在则放入新增list
|
||||
for (PqMonitorExcel pqMonitorExcel : pqMonitorExcelList) {
|
||||
PqMonitor monitor = BeanUtil.copyProperties(pqMonitorExcel, PqMonitor.class);
|
||||
monitor.setDevId(importPqDev.getId());
|
||||
monitor.setPt(pqMonitorExcel.getPt1() + StrUtil.C_COLON + pqMonitorExcel.getPt2());
|
||||
monitor.setCt(pqMonitorExcel.getCt1() + StrUtil.C_COLON + pqMonitorExcel.getCt2());
|
||||
monitorList.add(monitor);
|
||||
}
|
||||
importPqDev.setMonitorList(monitorList);
|
||||
newDevList.add(importPqDev);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 3、是否有重复的设备
|
||||
if (CollUtil.isNotEmpty(updateDevList)) {
|
||||
// 3.1、有则让用户确认是否覆盖
|
||||
if (cover == 0) {
|
||||
List<String> existDevList = new ArrayList<>();
|
||||
updateDevList.forEach(pqDev -> {
|
||||
existDevList.add(pqDev.getName() + "(" + pqDev.getIp() + ":" + pqDev.getPort() + ")");
|
||||
});
|
||||
return HttpResultUtil.assembleResult(DetectionResponseEnum.DEV_IP_PORT_EXIST.getCode(), existDevList, "请确认是否覆盖");
|
||||
|
||||
} else {
|
||||
pqDev.setDevType(devType.getId());
|
||||
|
||||
Integer devChns = devType.getDevChns();
|
||||
List<Integer> numList = devExcel.getPqMonitorExcelList().stream().map(monitorExcel -> monitorExcel.getNum()).collect(Collectors.toList());
|
||||
if (CollUtil.isNotEmpty(numList)) {
|
||||
Integer max = CollectionUtil.max(numList);
|
||||
Integer min = CollectionUtil.min(numList);
|
||||
if (min < 1 || max > devChns) {
|
||||
throw new BusinessException(DetectionResponseEnum.MONITOR_NUM_OUT_OF_RANGE);
|
||||
}
|
||||
if (min == max && numList.size() > 1) {
|
||||
throw new BusinessException(DetectionResponseEnum.MONITOR_NUM_REPEAT);
|
||||
}
|
||||
}
|
||||
// 3.2、确认覆盖则放入强制更新list
|
||||
finalUpdateDevList.addAll(updateDevList);
|
||||
}
|
||||
|
||||
pqDev.setImportFlag(1);
|
||||
pqDev.setId(UUID.randomUUID().toString().replaceAll("-", ""));
|
||||
pqDev.setCreateId(pqDev.getName()); //导入时设备序列号默认与设备名称相同
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < devExcel.getPqMonitorExcelList().size(); i++) {
|
||||
PqMonitor monitor = BeanUtil.copyProperties(devExcel.getPqMonitorExcelList().get(i), PqMonitor.class);
|
||||
if (StrUtil.isBlank(monitor.getName())) {
|
||||
continue;
|
||||
}
|
||||
sb.append(monitor.getNum() + StrUtil.COMMA);
|
||||
monitor.setDevId(pqDev.getId());
|
||||
monitorList.add(monitor);
|
||||
}
|
||||
if (sb.length() > 0) {
|
||||
pqDev.setInspectChannel(sb.replace(sb.length() - 1, sb.length(), "").toString());
|
||||
}
|
||||
return pqDev;
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
//逆向可视化
|
||||
this.reverseVisualizeProvinceDev(oldDevList, patternId);
|
||||
|
||||
List<PqDev> newDevList = new ArrayList<>();
|
||||
oldDevList.forEach(pqDev -> {
|
||||
PqDevParam param = BeanUtil.copyProperties(pqDev, PqDevParam.class);
|
||||
this.checkRepeat(param, false);
|
||||
long count = newDevList.stream().filter(dev ->
|
||||
dev.getDevType().equals(pqDev.getDevType())
|
||||
&& dev.getCityName().equals(pqDev.getCityName())
|
||||
&& dev.getGdName().equals(pqDev.getGdName())
|
||||
&& dev.getSubName().equals(pqDev.getSubName())
|
||||
&& (dev.getName().equals(pqDev.getName()) || dev.getCreateId().equals(pqDev.getCreateId())))
|
||||
.count();
|
||||
if (count == 0) {
|
||||
pqDev.setPlanId(planId);
|
||||
newDevList.add(pqDev);
|
||||
}
|
||||
});
|
||||
QueryWrapper<PqDev> wrapper1 = new QueryWrapper<PqDev>()
|
||||
.eq("pq_dev.State", DataStateEnum.ENABLE.getCode())
|
||||
.in("pq_dev.Harm_Sys_Id", newDevList.stream().map(PqDev::getHarmSysId).collect(Collectors.toList()));
|
||||
List<PqDev> oldDevList1 = this.list(wrapper1);
|
||||
if (CollUtil.isNotEmpty(oldDevList1)) {
|
||||
oldDevList1.stream().forEach(oldDev -> {
|
||||
PqDev newDev = newDevList.stream().filter(dev -> dev.getHarmSysId().equals(oldDev.getHarmSysId())).findFirst().orElse(null);
|
||||
if (ObjectUtil.isNotNull(newDev)) {
|
||||
newDevList.remove(newDev);
|
||||
monitorList.stream()
|
||||
.filter(monitor -> monitor.getDevId().equals(newDev.getId()))
|
||||
.forEach(monitor -> monitor.setDevId(oldDev.getId()));
|
||||
BeanUtil.copyProperties(newDev, oldDev, "id");
|
||||
}
|
||||
});
|
||||
this.updateBatchById(oldDevList1);
|
||||
}
|
||||
this.saveBatch(newDevList);
|
||||
List<PqDevSub> pqDevSubList = new ArrayList<>();
|
||||
for (PqDev dev : newDevList) {
|
||||
PqDevSub pqDevSub = new PqDevSub();
|
||||
pqDevSub.setDevId(dev.getId());
|
||||
pqDevSub.setCheckState(CheckStateEnum.UNCHECKED.getValue());
|
||||
pqDevSub.setCheckResult(CheckResultEnum.UNCHECKED.getValue());
|
||||
pqDevSub.setReportState(DevReportStateEnum.UNCHECKED.getValue());
|
||||
pqDevSub.setTimeCheckResult(TimeCheckResultEnum.UNKNOWN.getValue());
|
||||
pqDevSub.setFactorCheckResult(FactorCheckResultEnum.UNKNOWN.getValue());
|
||||
pqDevSubList.add(pqDevSub);
|
||||
}
|
||||
pqDevSubService.saveBatch(pqDevSubList);
|
||||
|
||||
List<String> devIdList = oldDevList1.stream().map(PqDev::getId).collect(Collectors.toList());
|
||||
if (CollUtil.isNotEmpty(devIdList)) {
|
||||
QueryWrapper<PqMonitor> wrapper = new QueryWrapper<PqMonitor>()
|
||||
.in("pq_monitor.Dev_Id", devIdList);
|
||||
pqMonitorService.remove(wrapper);
|
||||
|
||||
// 4、新增list
|
||||
if (CollUtil.isNotEmpty(newDevList)) {
|
||||
//逆向可视化
|
||||
this.reverseVisualizeProvinceDev(newDevList, patternId);
|
||||
this.saveBatch(newDevList);
|
||||
List<PqDevSub> pqDevSubList = new ArrayList<>();
|
||||
List<PqMonitor> newMonitorList = new ArrayList<>();
|
||||
for (PqDev dev : newDevList) {
|
||||
PqDevSub pqDevSub = new PqDevSub();
|
||||
pqDevSub.setDevId(dev.getId());
|
||||
pqDevSub.setCheckState(CheckStateEnum.UNCHECKED.getValue());
|
||||
pqDevSub.setCheckResult(CheckResultEnum.UNCHECKED.getValue());
|
||||
pqDevSub.setReportState(DevReportStateEnum.UNCHECKED.getValue());
|
||||
pqDevSub.setTimeCheckResult(TimeCheckResultEnum.UNKNOWN.getValue());
|
||||
pqDevSub.setFactorCheckResult(FactorCheckResultEnum.UNKNOWN.getValue());
|
||||
pqDevSubList.add(pqDevSub);
|
||||
newMonitorList.addAll(dev.getMonitorList());
|
||||
}
|
||||
pqMonitorService.reverseVisualizeMonitor(newMonitorList);
|
||||
pqMonitorService.saveBatch(newMonitorList);
|
||||
pqDevSubService.saveBatch(pqDevSubList);
|
||||
}
|
||||
pqMonitorService.reverseVisualizeMonitor(monitorList);
|
||||
pqMonitorService.saveBatch(monitorList);
|
||||
return true;
|
||||
// 5、更新list
|
||||
if (CollUtil.isNotEmpty(finalUpdateDevList)) {
|
||||
//逆向可视化
|
||||
this.reverseVisualizeProvinceDev(finalUpdateDevList, patternId);
|
||||
this.updateBatchById(finalUpdateDevList);
|
||||
List<PqMonitor> updateMonitorList = new ArrayList<>();
|
||||
for (PqDev dev : finalUpdateDevList) {
|
||||
updateMonitorList.addAll(dev.getMonitorList());
|
||||
}
|
||||
pqMonitorService.reverseVisualizeMonitor(updateMonitorList);
|
||||
for (PqMonitor monitor : updateMonitorList) {
|
||||
pqMonitorService.update(monitor, new LambdaUpdateWrapper<PqMonitor>().eq(PqMonitor::getDevId, monitor.getDevId()));
|
||||
}
|
||||
}
|
||||
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, true, "导入成功");
|
||||
}
|
||||
return false;
|
||||
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.FAIL, false, "导入失败");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1309,13 +1503,19 @@ public class PqDevServiceImpl extends ServiceImpl<PqDevMapper, PqDev> implements
|
||||
if (ObjectUtil.isNotNull(dictType)) {
|
||||
dictDataList = dictDataService.getDictDataByTypeId(dictType.getId());
|
||||
pullDown = new PullDown();
|
||||
pullDown.setFirstCol(startCol + 24);
|
||||
pullDown.setLastCol(startCol + 24);
|
||||
pullDown.setFirstCol(startCol + 26);
|
||||
pullDown.setLastCol(startCol + 26);
|
||||
|
||||
pullDown.setStrings(dictDataList.stream().map(DictData::getName).collect(Collectors.toList()));
|
||||
pullDowns.add(pullDown);
|
||||
}
|
||||
|
||||
pullDown = new PullDown();
|
||||
pullDown.setFirstCol(startCol + 27);
|
||||
pullDown.setLastCol(startCol + 27);
|
||||
pullDown.setStrings(Stream.iterate(1, x -> x + 1).limit(10).map(String::valueOf).collect(Collectors.toList()));
|
||||
pullDowns.add(pullDown);
|
||||
|
||||
return pullDowns;
|
||||
}
|
||||
|
||||
@@ -1364,11 +1564,6 @@ public class PqDevServiceImpl extends ServiceImpl<PqDevMapper, PqDev> implements
|
||||
pqDev.setDelegate(delegateDictData.getId());
|
||||
}
|
||||
}
|
||||
// pqDev.setTimeCheckResult(TimeCheckResultEnum.UNKNOWN.getValue());
|
||||
// pqDev.setFactorCheckResult(FactorCheckResultEnum.UNKNOWN.getValue());
|
||||
// pqDev.setCheckState(CheckStateEnum.UNCHECKED.getValue());
|
||||
// pqDev.setReportState(DevReportStateEnum.UNCHECKED.getValue());
|
||||
// pqDev.setCheckResult(CheckResultEnum.UNCHECKED.getValue());
|
||||
pqDev.setState(DataStateEnum.ENABLE.getCode());
|
||||
});
|
||||
}
|
||||
@@ -1380,9 +1575,24 @@ public class PqDevServiceImpl extends ServiceImpl<PqDevMapper, PqDev> implements
|
||||
this.visualizeProvinceDev(pqDevVOList);
|
||||
contrastDevExcels.addAll(BeanUtil.copyToList(pqDevVOList, ContrastDevExcel.class));
|
||||
contrastDevExcels.forEach(contrastDevExcel -> {
|
||||
List<PqMonitor> monitorList = pqMonitorService.listPqMonitorByDevId(contrastDevExcel.getId());
|
||||
List<PqMonitor> monitorList = pqMonitorService.listPqMonitorByDevIds(Collections.singletonList(contrastDevExcel.getId()));
|
||||
pqMonitorService.visualizeMonitor(monitorList);
|
||||
List<PqMonitorExcel> pqMonitorExcelList = BeanUtil.copyToList(monitorList, PqMonitorExcel.class);
|
||||
List<PqMonitorExcel> pqMonitorExcelList = new ArrayList<>();
|
||||
for (int i = 0; i < monitorList.size(); i++) {
|
||||
PqMonitor pqMonitor = monitorList.get(i);
|
||||
PqMonitorExcel pqMonitorExcel = BeanUtil.copyProperties(pqMonitor, PqMonitorExcel.class);
|
||||
String pt = pqMonitor.getPt();
|
||||
String[] split1 = pt.split(String.valueOf(StrUtil.C_COLON));
|
||||
pqMonitorExcel.setPt1(split1[0]);
|
||||
pqMonitorExcel.setPt2(split1[1]);
|
||||
|
||||
String ct = pqMonitor.getCt();
|
||||
String[] split2 = ct.split(String.valueOf(StrUtil.C_COLON));
|
||||
pqMonitorExcel.setCt1(split2[0]);
|
||||
pqMonitorExcel.setCt2(split2[1]);
|
||||
|
||||
pqMonitorExcelList.add(pqMonitorExcel);
|
||||
}
|
||||
contrastDevExcel.setPqMonitorExcelList(pqMonitorExcelList);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import com.njcn.gather.device.pojo.vo.PqStandardDevExcel;
|
||||
import com.njcn.gather.device.pojo.vo.PreDetection;
|
||||
import com.njcn.gather.device.service.IPqStandardDevService;
|
||||
import com.njcn.gather.plan.mapper.AdPlanStandardDevMapper;
|
||||
import com.njcn.gather.plan.service.IAdPlanStandardDevService;
|
||||
import com.njcn.gather.pojo.enums.DetectionResponseEnum;
|
||||
import com.njcn.gather.system.dictionary.pojo.po.DictData;
|
||||
import com.njcn.gather.system.dictionary.pojo.po.DictType;
|
||||
@@ -58,6 +59,7 @@ public class PqStandardDevServiceImpl extends ServiceImpl<PqStandardDevMapper, P
|
||||
private final IDictDataService dictDataService;
|
||||
private final IDictTypeService dictTypeService;
|
||||
private final AdPlanStandardDevMapper adPlanStandardDevMapper;
|
||||
private final IAdPlanStandardDevService adPlanStandardDevService;
|
||||
|
||||
@Override
|
||||
public Page<PqStandardDev> listPqStandardDevs(PqStandardDevParam.QueryParam queryParam) {
|
||||
@@ -65,7 +67,8 @@ public class PqStandardDevServiceImpl extends ServiceImpl<PqStandardDevMapper, P
|
||||
wrapper.like(StrUtil.isNotBlank(queryParam.getName()), "name", queryParam.getName())
|
||||
.eq(StrUtil.isNotBlank(queryParam.getManufacturer()), "manufacturer", queryParam.getManufacturer())
|
||||
.eq(StrUtil.isNotBlank(queryParam.getDevType()), "dev_type", queryParam.getDevType())
|
||||
.eq("state", DataStateEnum.ENABLE.getCode());
|
||||
.eq("state", DataStateEnum.ENABLE.getCode())
|
||||
.orderByDesc("create_time");
|
||||
return this.page(new Page<>(PageFactory.getPageNum(queryParam), PageFactory.getPageSize(queryParam)), wrapper);
|
||||
}
|
||||
|
||||
@@ -151,10 +154,10 @@ public class PqStandardDevServiceImpl extends ServiceImpl<PqStandardDevMapper, P
|
||||
this.importData(contrastDevExcelList);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PqStandardDev> listByPlanId(String planId) {
|
||||
return adPlanStandardDevMapper.listByPlanId(Collections.singletonList(planId));
|
||||
}
|
||||
// @Override
|
||||
// public List<PqStandardDev> listByPlanId(String planId) {
|
||||
// return adPlanStandardDevMapper.listByPlanId(Collections.singletonList(planId));
|
||||
// }
|
||||
|
||||
@Override
|
||||
public List<PreDetection> listStandardDevPreDetection(List<String> ids) {
|
||||
@@ -300,4 +303,55 @@ public class PqStandardDevServiceImpl extends ServiceImpl<PqStandardDevMapper, P
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PqStandardDev> canBindingList() {
|
||||
/*List<String> excludeStandardDevIds = new ArrayList<>();
|
||||
// 获取所有已绑定的标准设备
|
||||
List<AdPlanStandardDev> boundList = adPlanStandardDevService.list();
|
||||
if (CollectionUtil.isNotEmpty(boundList)) {
|
||||
// 获取对应检测计划
|
||||
List<String> planIds = boundList.stream().map(AdPlanStandardDev::getPlanId).collect(Collectors.toList());
|
||||
IAdPlanService adPlanService = SpringUtil.getBean(IAdPlanService.class);
|
||||
List<AdPlan> planList = adPlanService.lambdaQuery().in(AdPlan::getId, planIds).eq(AdPlan::getState, DataStateEnum.ENABLE.getCode()).list();
|
||||
// 区分主计划和子计划
|
||||
List<AdPlan> mainPlanList = planList.stream().filter(plan -> plan.getFatherPlanId().equals(CommonEnum.FATHER_ID.getValue())).collect(Collectors.toList());
|
||||
List<AdPlan> subPlanList = planList.stream().filter(plan -> !plan.getFatherPlanId().equals(CommonEnum.FATHER_ID.getValue())).collect(Collectors.toList());
|
||||
List<String> excludePlanIds = new ArrayList<>();
|
||||
|
||||
// 主计划未完成直接排除
|
||||
if (CollectionUtil.isNotEmpty(mainPlanList)) {
|
||||
List<String> excludeMainPlanIds = mainPlanList.stream().filter(plan -> !plan.getTestState().equals(CheckStateEnum.CHECKED.getValue())).map(AdPlan::getId).collect(Collectors.toList());
|
||||
if (CollectionUtil.isNotEmpty(excludeMainPlanIds)) {
|
||||
excludePlanIds.addAll(excludeMainPlanIds);
|
||||
}
|
||||
|
||||
}
|
||||
// 子计划需要判断其主计划, 如果主计划未完成则排除
|
||||
if (CollectionUtil.isNotEmpty(subPlanList)) {
|
||||
List<String> fatherPlanIds = subPlanList.stream().map(AdPlan::getFatherPlanId).distinct().collect(Collectors.toList());
|
||||
List<AdPlan> fatherPlanList = adPlanService.listByIds(fatherPlanIds);
|
||||
List<String> excludeFatherPlanIds = fatherPlanList.stream()
|
||||
.filter(plan -> !plan.getTestState().equals(CheckStateEnum.CHECKED.getValue()))
|
||||
.map(AdPlan::getId).collect(Collectors.toList());
|
||||
List<String> excludeSubPlanIds = subPlanList.stream()
|
||||
.filter(plan -> excludeFatherPlanIds.contains(plan.getFatherPlanId()))
|
||||
.map(AdPlan::getId).collect(Collectors.toList());
|
||||
excludePlanIds.addAll(excludeSubPlanIds);
|
||||
}
|
||||
if (CollectionUtil.isNotEmpty(excludePlanIds)) {
|
||||
List<AdPlanStandardDev> excludeBoundList = boundList.stream()
|
||||
.filter(bound -> excludePlanIds.contains(bound.getPlanId()))
|
||||
.collect(Collectors.toList());
|
||||
excludeStandardDevIds = excludeBoundList.stream().map(AdPlanStandardDev::getStandardDevId).collect(Collectors.toList());
|
||||
}
|
||||
}*/
|
||||
List<PqStandardDev> list = this.lambdaQuery()
|
||||
.eq(PqStandardDev::getState, DataStateEnum.ENABLE.getCode())
|
||||
.list();
|
||||
/*for (PqStandardDev pqStandardDev : list) {
|
||||
pqStandardDev.setDisabled(excludeStandardDevIds.contains(pqStandardDev.getId()));
|
||||
}*/
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,10 +144,10 @@ public class PqErrSysController extends BaseController {
|
||||
@GetMapping("/getTestItems")
|
||||
@ApiOperation("根据误差体系id获取测试项")
|
||||
@ApiImplicitParam(name = "id", value = "误差体系id", required = true)
|
||||
public HttpResult<List<Map<String, String>>> getTestItems(@RequestParam("id") String id) {
|
||||
public HttpResult<Map<String, String>> getTestItems(@RequestParam("id") String id) {
|
||||
String methodDescribe = getMethodDescribe("getTestItems");
|
||||
LogUtil.njcnDebug(log, "{},获取测试项ID为:{}", methodDescribe, id);
|
||||
List<Map<String, String>> result = pqErrSysService.getTestItems(id);
|
||||
Map<String, String> result = pqErrSysService.getTestItems(id);
|
||||
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
</foreach>
|
||||
</where>
|
||||
</if>
|
||||
and State = 1
|
||||
</select>
|
||||
</mapper>
|
||||
|
||||
|
||||
@@ -86,5 +86,5 @@ public interface IPqErrSysService extends IService<PqErrSys> {
|
||||
* @param id 误差体系id
|
||||
* @return
|
||||
*/
|
||||
List<Map<String, String>> getTestItems(String id);
|
||||
Map<String, String> getTestItems(String id);
|
||||
}
|
||||
|
||||
@@ -162,7 +162,7 @@ public class PqErrSysDtlsServiceImpl extends ServiceImpl<PqErrSysDtlsMapper, PqE
|
||||
wrapper.selectAll(PqErrSysDtls.class)
|
||||
.leftJoin(DictTree.class, DictTree::getId, PqErrSysDtls::getScriptType)
|
||||
.eq(PqErrSysDtls::getErrorSysId, errSysId)
|
||||
.eq(DictTree::getId, scriptType);
|
||||
.eq(DictTree::getCode, scriptType);
|
||||
return this.list(wrapper);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.njcn.gather.err.service.impl;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.text.StrPool;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
@@ -8,6 +9,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.njcn.common.pojo.enums.common.DataStateEnum;
|
||||
import com.njcn.common.pojo.exception.BusinessException;
|
||||
import com.njcn.gather.detection.pojo.enums.DetectionCodeEnum;
|
||||
import com.njcn.gather.err.mapper.PqErrSysMapper;
|
||||
import com.njcn.gather.err.pojo.param.PqErrSysDtlsParam;
|
||||
import com.njcn.gather.err.pojo.param.PqErrSysParam;
|
||||
@@ -17,6 +19,7 @@ import com.njcn.gather.err.pojo.vo.PqErrSysDtlsVO;
|
||||
import com.njcn.gather.err.service.IPqErrSysDtlsService;
|
||||
import com.njcn.gather.err.service.IPqErrSysService;
|
||||
import com.njcn.gather.pojo.enums.DetectionResponseEnum;
|
||||
import com.njcn.gather.report.pojo.enums.PowerIndexEnum;
|
||||
import com.njcn.gather.system.dictionary.pojo.po.DictData;
|
||||
import com.njcn.gather.system.dictionary.pojo.po.DictTree;
|
||||
import com.njcn.gather.system.dictionary.service.IDictDataService;
|
||||
@@ -128,7 +131,7 @@ public class PqErrSysServiceImpl extends ServiceImpl<PqErrSysMapper, PqErrSys> i
|
||||
@Override
|
||||
public List<Map<String, Object>> listAllPqErrSys() {
|
||||
List<PqErrSys> pqErrSysList = this.lambdaQuery()
|
||||
.eq(PqErrSys::getEnable,DataStateEnum.ENABLE.getCode())
|
||||
.eq(PqErrSys::getEnable, DataStateEnum.ENABLE.getCode())
|
||||
.eq(PqErrSys::getState, DataStateEnum.ENABLE.getCode()).list();
|
||||
List<Map<String, Object>> result = pqErrSysList.stream().map(pqErrSys -> {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
@@ -209,24 +212,38 @@ public class PqErrSysServiceImpl extends ServiceImpl<PqErrSysMapper, PqErrSys> i
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Map<String, String>> getTestItems(String id) {
|
||||
public Map<String, String> getTestItems(String id) {
|
||||
List<PqErrSysDtls> pqErrSysDtls = pqErrSysDtlsService.listPqErrSysDtlsByPqErrSysId(id);
|
||||
List<String> scriptTypeList = pqErrSysDtls.stream().map(PqErrSysDtls::getScriptType).distinct().collect(Collectors.toList());
|
||||
List<DictTree> dictTreeList = dictTreeService.lambdaQuery().in(CollUtil.isNotEmpty(scriptTypeList), DictTree::getId, scriptTypeList).list();
|
||||
if (CollUtil.isNotEmpty(dictTreeList)) {
|
||||
List<String> pids = dictTreeList.stream().map(DictTree::getPid).collect(Collectors.toList());
|
||||
List<DictTree> parentDictTreeList = dictTreeService.listByIds(pids);
|
||||
Map<String, String> map = new HashMap<>();
|
||||
parentDictTreeList.forEach(dictTree -> {
|
||||
if(!dictTree.getCode().equals(PowerIndexEnum.VOLTAGE.getKey())&&!dictTree.getCode().equals(PowerIndexEnum.P.getKey())){
|
||||
map.put(dictTree.getId(), dictTree.getName());
|
||||
}
|
||||
});
|
||||
return map;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查重复
|
||||
*
|
||||
* @param param 检测参数
|
||||
* @param param 检测参数
|
||||
* @param isExcludeSelf 是否排除自己
|
||||
*/
|
||||
private void checkRepeat(PqErrSysParam param, boolean isExcludeSelf) {
|
||||
QueryWrapper<PqErrSys> wrapper = new QueryWrapper<>();
|
||||
wrapper.eq("Standard_Name", param.getStandardName())
|
||||
.eq("Standard_Time",param.getStandardTime()+"-01-01")
|
||||
.eq("Dev_Level",param.getDevLevel())
|
||||
.eq("Standard_Time", param.getStandardTime() + "-01-01")
|
||||
.eq("Dev_Level", param.getDevLevel())
|
||||
.eq("state", DataStateEnum.ENABLE.getCode());
|
||||
if (isExcludeSelf) {
|
||||
if(param instanceof PqErrSysParam.UpdateParam){
|
||||
if (param instanceof PqErrSysParam.UpdateParam) {
|
||||
wrapper.ne("Id", ((PqErrSysParam.UpdateParam) param).getId());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,12 @@ public class PqIcdPathParam {
|
||||
@Pattern(regexp = PatternRegex.ICD_PATH_REGEX, message = DetectionValidMessage.ICD_PATH_FORMAT_ERROR)
|
||||
private String path;
|
||||
|
||||
@ApiModelProperty(value = "是否支持电压相角、电流相角指标,0表示否,1表示是", required = true)
|
||||
private Integer angle;
|
||||
|
||||
@ApiModelProperty(value = "角型接线时是否使用相别的指标来进行检测,0表示否,1表示是", required = true)
|
||||
private Integer usePhaseIndex;
|
||||
|
||||
/**
|
||||
* 分页查询实体
|
||||
*/
|
||||
|
||||
@@ -36,5 +36,15 @@ public class PqIcdPath extends BaseEntity implements Serializable {
|
||||
* 状态:0-删除 1-正常
|
||||
*/
|
||||
private Integer state;
|
||||
|
||||
/**
|
||||
* 是否支持电压相角、电流相角指标,0表示否,1表示是
|
||||
*/
|
||||
private Integer angle;
|
||||
|
||||
/**
|
||||
* 角型接线时是否使用相别的指标来进行检测,0表示否,1表示是
|
||||
*/
|
||||
private Integer usePhaseIndex;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@@ -41,7 +42,7 @@ public class PqMonitorController extends BaseController {
|
||||
public HttpResult<List<PqMonitor>> list(@RequestBody PqMonitorParam.QueryParam queryParam) {
|
||||
String methodDescribe = getMethodDescribe("list");
|
||||
LogUtil.njcnDebug(log, "{},查询数据为:{}", methodDescribe, queryParam);
|
||||
List<PqMonitor> result = pqMonitorService.listPqMonitorByDevId(queryParam.getDevId());
|
||||
List<PqMonitor> result = pqMonitorService.listPqMonitorByDevIds(queryParam.getDevIds());
|
||||
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package com.njcn.gather.monitor.mapper;
|
||||
|
||||
import com.github.yulichang.base.MPJBaseMapper;
|
||||
import com.njcn.gather.device.pojo.po.PqDevSub;
|
||||
import com.njcn.gather.device.pojo.vo.PreDetection;
|
||||
import com.njcn.gather.monitor.pojo.po.PqMonitor;
|
||||
import com.njcn.gather.plan.pojo.po.AdPlan;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
@@ -15,11 +17,39 @@ public interface PqMonitorMapper extends MPJBaseMapper<PqMonitor> {
|
||||
|
||||
/**
|
||||
* 根据终端id获取监测点集合
|
||||
*
|
||||
* @param devId
|
||||
* @return: java.util.List<com.njcn.gather.device.pojo.vo.PreDetection.MonitorListDTO>
|
||||
* @Author: wr
|
||||
* @Date: 2024/12/12 13:15
|
||||
*/
|
||||
List<PreDetection.MonitorListDTO> selectMonitorInfo(@Param("devId") String devId);
|
||||
|
||||
/**
|
||||
* 根据被检设备id和线路号获取监测点信息
|
||||
*
|
||||
* @param devId 被检设备id
|
||||
* @param num 线路号
|
||||
* @return
|
||||
*/
|
||||
PqMonitor getByDevIdAndNum(@Param("devId") String devId, @Param("num") Integer num);
|
||||
|
||||
List<PqMonitor> listByDevIds(@Param("devIds") List<String> devIds);
|
||||
|
||||
void updateDeviceCheckState(@Param("devId")String devId, @Param("value") Integer value);
|
||||
|
||||
void updateDeviceCheckResult(@Param("devId")String devId, @Param("value") Integer value);
|
||||
|
||||
void updateDeviceReportRState(@Param("devId")String devId, @Param("value") Integer value);
|
||||
|
||||
AdPlan getPlanByDevId(@Param("devId") String devId);
|
||||
|
||||
List<PqDevSub> listDevSubByPlanId(@Param("planId") String planId);
|
||||
|
||||
void updatePlanCheckState(@Param("planId") String planId, @Param("value") Integer value);
|
||||
|
||||
void updatePlanCheckResult(@Param("planId") String planId, @Param("value") Integer value);
|
||||
|
||||
void updatePlanReportRState(@Param("planId") String planId, @Param("value") Integer value);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,12 +2,13 @@
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.njcn.gather.monitor.mapper.PqMonitorMapper">
|
||||
|
||||
|
||||
<select id="selectMonitorInfo"
|
||||
resultType="com.njcn.gather.device.pojo.vo.PreDetection$MonitorListDTO">
|
||||
SELECT CONCAT(pq_dev.IP, '_', Num) as lineId,
|
||||
Num as line,
|
||||
pt as pt,
|
||||
ct as ct,
|
||||
pt as ptStr,
|
||||
ct as ctStr,
|
||||
Stat_Interval,
|
||||
sys_dict_data.Code as `Connection`
|
||||
FROM pq_monitor
|
||||
@@ -16,5 +17,80 @@
|
||||
WHERE Dev_Id = #{devId}
|
||||
</select>
|
||||
|
||||
<select id="getByDevIdAndNum" resultType="com.njcn.gather.monitor.pojo.po.PqMonitor">
|
||||
select pq_monitor.Id,
|
||||
pq_monitor.Dev_Id,
|
||||
pq_monitor.Name,
|
||||
pq_monitor.Busbar,
|
||||
pq_monitor.Num,
|
||||
pq_monitor.Pt,
|
||||
pq_monitor.Ct,
|
||||
pq_monitor.Stat_Interval,
|
||||
pq_monitor.Harm_Sys_Id,
|
||||
sys_dict_data.Code as `Connection`
|
||||
from pq_monitor
|
||||
inner join sys_dict_data on pq_monitor.Connection = sys_dict_data.id
|
||||
where Dev_Id = #{devId}
|
||||
and Num = #{num}
|
||||
</select>
|
||||
<select id="listByDevIds" resultType="com.njcn.gather.monitor.pojo.po.PqMonitor">
|
||||
select *
|
||||
from pq_monitor
|
||||
where Dev_Id in
|
||||
<foreach collection="devIds" item="devId" open="(" separator="," close=")">
|
||||
#{devId}
|
||||
</foreach>
|
||||
order by Num
|
||||
</select>
|
||||
|
||||
<update id="updateDeviceCheckState">
|
||||
update pq_dev_sub
|
||||
set Check_State = #{value}
|
||||
where dev_Id = #{devId}
|
||||
</update>
|
||||
|
||||
<update id="updateDeviceCheckResult">
|
||||
update pq_dev_sub
|
||||
set Check_Result = #{value}
|
||||
where dev_Id = #{devId}
|
||||
</update>
|
||||
|
||||
<update id="updateDeviceReportRState">
|
||||
update pq_dev_sub
|
||||
set Report_State = #{value}
|
||||
where dev_Id = #{devId}
|
||||
</update>
|
||||
|
||||
<select id="getPlanByDevId" resultType="com.njcn.gather.plan.pojo.po.AdPlan">
|
||||
select *
|
||||
from ad_plan
|
||||
inner join pq_dev on ad_plan.id = pq_dev.Plan_Id
|
||||
where pq_dev.Id = #{devId}
|
||||
</select>
|
||||
|
||||
<select id="listDevSubByPlanId" resultType="com.njcn.gather.device.pojo.po.PqDevSub">
|
||||
select *
|
||||
from pq_dev_sub
|
||||
inner join pq_dev on pq_dev_sub.dev_Id = pq_dev.Id
|
||||
where pq_dev.Plan_Id = #{planId}
|
||||
</select>
|
||||
|
||||
<update id="updatePlanCheckState">
|
||||
update ad_plan
|
||||
set Test_State = #{value}
|
||||
where id = #{planId}
|
||||
</update>
|
||||
|
||||
<update id="updatePlanCheckResult">
|
||||
update ad_plan
|
||||
set Result = #{value}
|
||||
where id = #{planId}
|
||||
</update>
|
||||
|
||||
<update id="updatePlanReportRState">
|
||||
update ad_plan
|
||||
set Report_State = #{value}
|
||||
where id = #{planId}
|
||||
</update>
|
||||
</mapper>
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import lombok.EqualsAndHashCode;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.validation.constraints.Pattern;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author caozehui
|
||||
@@ -51,9 +52,12 @@ public class PqMonitorParam {
|
||||
private Integer statInterval;
|
||||
|
||||
@ApiModelProperty(value = "谐波系统监测点id")
|
||||
@NotBlank(message = DetectionValidMessage.MONITOR_ID_NOT_BLANK)
|
||||
// @NotBlank(message = DetectionValidMessage.MONITOR_ID_NOT_BLANK)
|
||||
private String harmSysId;
|
||||
|
||||
@ApiModelProperty(value = "是否做检测")
|
||||
private Integer checkFlag;
|
||||
|
||||
|
||||
/**
|
||||
* 分页查询实体
|
||||
@@ -62,18 +66,6 @@ public class PqMonitorParam {
|
||||
public static class QueryParam {
|
||||
@ApiModelProperty(value = "所属设备id")
|
||||
@NotBlank(message = DetectionValidMessage.DEVICE_ID_NOT_BLANK)
|
||||
private String devId;
|
||||
private List<String> devIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改实体
|
||||
*/
|
||||
// @Data
|
||||
// @EqualsAndHashCode(callSuper = true)
|
||||
// public static class UpdateParam extends PqMonitorParam {
|
||||
// @ApiModelProperty(value = "监测点id", required = true)
|
||||
// @NotBlank(message = DetectionValidMessage.ID_NOT_BLANK)
|
||||
// @Pattern(regexp = PatternRegex.SYSTEM_ID, message = DetectionValidMessage.ID_FORMAT_ERROR)
|
||||
// private String id;
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.njcn.gather.monitor.pojo.po;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.FieldStrategy;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
@@ -62,5 +64,51 @@ public class PqMonitor implements Serializable {
|
||||
* 谐波系统监测点id
|
||||
*/
|
||||
private String harmSysId;
|
||||
|
||||
/**
|
||||
* 实时数据结果 0:不合格,1:合格,2:未检
|
||||
*/
|
||||
@TableField(updateStrategy = FieldStrategy.IGNORED)
|
||||
private Integer realtimeResult;
|
||||
|
||||
/**
|
||||
* 统计数据结果 0:不合格,1:合格,2:未检
|
||||
*/
|
||||
@TableField(updateStrategy = FieldStrategy.IGNORED)
|
||||
private Integer statisticsResult;
|
||||
|
||||
/**
|
||||
* 录波数据结果 0:不合格,1:合格,2:未检
|
||||
*/
|
||||
@TableField(updateStrategy = FieldStrategy.IGNORED)
|
||||
private Integer recordedResult;
|
||||
|
||||
/**
|
||||
* 实时数据使用哪一次的检测数据来表示结论
|
||||
*/
|
||||
@TableField(updateStrategy = FieldStrategy.IGNORED)
|
||||
private String realtimeNum;
|
||||
|
||||
/**
|
||||
* 统计数据使用哪一次的检测数据来表示结论
|
||||
*/
|
||||
@TableField(updateStrategy = FieldStrategy.IGNORED)
|
||||
private String statisticsNum;
|
||||
|
||||
/**
|
||||
* 录波数据使用哪一次、哪一组的检测数据来表示结论 例如 3_2,第三次检测第二组
|
||||
*/
|
||||
@TableField(updateStrategy = FieldStrategy.IGNORED)
|
||||
private String recordedNum;
|
||||
|
||||
/**
|
||||
* 整个通道的结果使用那种数据。real、wave_data、statistics
|
||||
*/
|
||||
@TableField(updateStrategy = FieldStrategy.IGNORED)
|
||||
private String resultType;
|
||||
|
||||
private Integer qualifiedNum; // 合格次数
|
||||
|
||||
private Integer checkFlag; // 是否做检测
|
||||
}
|
||||
|
||||
|
||||
@@ -14,11 +14,11 @@ import javax.validation.constraints.NotNull;
|
||||
@Data
|
||||
public class PqMonitorExcel {
|
||||
|
||||
@Excel(name = "谐波系统监测点ID*", width = 20, orderNum = "1")
|
||||
@NotBlank(message = DetectionValidMessage.MONITOR_ID_NOT_BLANK)
|
||||
@Excel(name = "谐波系统监测点ID", width = 20, orderNum = "1")
|
||||
// @NotBlank(message = DetectionValidMessage.MONITOR_ID_NOT_BLANK)
|
||||
private String harmSysId;
|
||||
|
||||
@Excel(name = "所属母线*", width = 20, orderNum = "2")
|
||||
@Excel(name = "母线名称*", width = 20, orderNum = "2")
|
||||
@NotBlank(message = DetectionValidMessage.BELONG_LINE_NOT_BLANK)
|
||||
private String busbar;
|
||||
|
||||
@@ -30,19 +30,27 @@ public class PqMonitorExcel {
|
||||
@NotNull(message = DetectionValidMessage.MONITOR_NUM_NOT_NULL)
|
||||
private Integer num;
|
||||
|
||||
@Excel(name = "PT变比(pt1:pt2)*", width = 20, orderNum = "5")
|
||||
@Excel(name = "PT一次变比*", width = 20, orderNum = "5")
|
||||
@NotBlank(message = DetectionValidMessage.PT_NOT_BLANK)
|
||||
private String pt;
|
||||
private String pt1;
|
||||
|
||||
@Excel(name = "CT变比(ct1:ct2)*", width = 20, orderNum = "6")
|
||||
@Excel(name = "PT二次变比*", width = 20, orderNum = "6")
|
||||
@NotBlank(message = DetectionValidMessage.PT_NOT_BLANK)
|
||||
private String pt2;
|
||||
|
||||
@Excel(name = "CT一次变比*", width = 20, orderNum = "7")
|
||||
@NotBlank(message = DetectionValidMessage.CT_NOT_BLANK)
|
||||
private String ct;
|
||||
private String ct1;
|
||||
|
||||
@Excel(name = "接线方式*", width = 20, orderNum = "7")
|
||||
@Excel(name = "CT二次变比*", width = 20, orderNum = "8")
|
||||
@NotBlank(message = DetectionValidMessage.CT_NOT_BLANK)
|
||||
private String ct2;
|
||||
|
||||
@Excel(name = "接线方式*", width = 20, orderNum = "9")
|
||||
@NotBlank(message = DetectionValidMessage.CONNECTION_NOT_BLANK)
|
||||
private String connection;
|
||||
|
||||
@Excel(name = "统计间隔*", width = 10, orderNum = "8")
|
||||
@Excel(name = "统计间隔*", width = 20, orderNum = "10")
|
||||
@NotNull(message = DetectionValidMessage.STAT_INTERVAL_NOT_NULL)
|
||||
private Integer statInterval;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package com.njcn.gather.monitor.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.njcn.gather.monitor.pojo.param.PqMonitorParam;
|
||||
import com.njcn.gather.monitor.pojo.po.PqMonitor;
|
||||
import com.njcn.gather.plan.pojo.enums.DataSourceEnum;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -14,14 +14,13 @@ import java.util.List;
|
||||
public interface IPqMonitorService extends IService<PqMonitor> {
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 根据设备id获取所有监测点信息
|
||||
*
|
||||
* @param devId 被检设备id
|
||||
* @param devIds 被检设备id列表
|
||||
* @return 监测点信息列表
|
||||
*/
|
||||
List<PqMonitor> listPqMonitorByDevId(String devId);
|
||||
List<PqMonitor> listPqMonitorByDevIds(List<String> devIds);
|
||||
|
||||
/**
|
||||
* 根据设备id批量新增监测点信息
|
||||
@@ -62,4 +61,68 @@ public interface IPqMonitorService extends IService<PqMonitor> {
|
||||
* @param monitorList
|
||||
*/
|
||||
void reverseVisualizeMonitor(List<PqMonitor> monitorList);
|
||||
|
||||
/**
|
||||
* 根据被检设备监测点id获取监测点信息
|
||||
*
|
||||
* @param devMonitorId 被检设备监测点id
|
||||
* @return
|
||||
*/
|
||||
PqMonitor getByDevMonitorId(String devMonitorId);
|
||||
|
||||
/**
|
||||
* 根据被检设备id获取额定电流
|
||||
*
|
||||
* @param devMonitorId 被检设备监测点id
|
||||
* @return 额定电流
|
||||
*/
|
||||
Double getRatedCurrent(String devMonitorId);
|
||||
|
||||
/**
|
||||
* 根据被检设备id获取额定电压
|
||||
*
|
||||
* @param devMonitorId 被检设备监测点id
|
||||
* @return 额定电压
|
||||
*/
|
||||
Double getRatedVoltage(String devMonitorId);
|
||||
|
||||
/**
|
||||
* @param monitorId 监测点id
|
||||
* @param adTypes 检测项指标id列表
|
||||
* @param dataSourceEnum 那种数据源
|
||||
* @param code 结果表后缀
|
||||
* @return
|
||||
*/
|
||||
boolean updateMonitorResult(String monitorId, List<String> adTypes, DataSourceEnum dataSourceEnum, Integer num, Integer waveNum, String code);
|
||||
|
||||
/**
|
||||
* 查询设备的检测状态
|
||||
*
|
||||
* @param devId
|
||||
* @return
|
||||
*/
|
||||
Integer getDevCheckState(String devId);
|
||||
|
||||
/**
|
||||
* 查询设备的检测结果
|
||||
*
|
||||
* @param devId
|
||||
* @return
|
||||
*/
|
||||
Integer getDevCheckResult(String devId);
|
||||
|
||||
/**
|
||||
* 根据被检设备id删除监测点信息
|
||||
*
|
||||
* @param devId
|
||||
* @return
|
||||
*/
|
||||
boolean removeByDevId(String devId);
|
||||
|
||||
/**
|
||||
* 根据被检设备id和监测点编号获取监测点信息
|
||||
* @param id 被检设备id
|
||||
* @param monitorNum 监测点编号
|
||||
*/
|
||||
PqMonitor getByDevAndNum(String id, int monitorNum);
|
||||
}
|
||||
|
||||
@@ -1,15 +1,27 @@
|
||||
package com.njcn.gather.monitor.service.impl;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.njcn.common.pojo.exception.BusinessException;
|
||||
import com.njcn.gather.detection.util.socket.CnSocketUtil;
|
||||
import com.njcn.gather.device.pojo.enums.CheckResultEnum;
|
||||
import com.njcn.gather.device.pojo.enums.CheckStateEnum;
|
||||
import com.njcn.gather.device.pojo.enums.DevReportStateEnum;
|
||||
import com.njcn.gather.device.pojo.po.PqDevSub;
|
||||
import com.njcn.gather.monitor.mapper.PqMonitorMapper;
|
||||
import com.njcn.gather.monitor.pojo.param.PqMonitorParam;
|
||||
import com.njcn.gather.monitor.pojo.po.PqMonitor;
|
||||
import com.njcn.gather.monitor.service.IPqMonitorService;
|
||||
import com.njcn.gather.plan.pojo.enums.DataSourceEnum;
|
||||
import com.njcn.gather.plan.pojo.enums.PlanReportStateEnum;
|
||||
import com.njcn.gather.plan.pojo.po.AdPlan;
|
||||
import com.njcn.gather.pojo.enums.DetectionResponseEnum;
|
||||
import com.njcn.gather.storage.service.DetectionDataDealService;
|
||||
import com.njcn.gather.storage.service.impl.DetectionDataServiceImpl;
|
||||
import com.njcn.gather.system.dictionary.pojo.po.DictData;
|
||||
import com.njcn.gather.system.dictionary.service.IDictDataService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@@ -17,7 +29,12 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author caozehui
|
||||
@@ -29,10 +46,14 @@ import java.util.List;
|
||||
public class PqMonitorServiceImpl extends ServiceImpl<PqMonitorMapper, PqMonitor> implements IPqMonitorService {
|
||||
|
||||
private final IDictDataService dictDataService;
|
||||
private final DetectionDataDealService detectionDataDealService;
|
||||
|
||||
@Override
|
||||
public List<PqMonitor> listPqMonitorByDevId(String devId) {
|
||||
return this.lambdaQuery().eq(PqMonitor::getDevId, devId).list();
|
||||
public List<PqMonitor> listPqMonitorByDevIds(List<String> devIds) {
|
||||
if (CollUtil.isNotEmpty(devIds)) {
|
||||
return this.baseMapper.listByDevIds(devIds);
|
||||
}
|
||||
return CollUtil.empty(PqMonitor.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -46,11 +67,104 @@ public class PqMonitorServiceImpl extends ServiceImpl<PqMonitorMapper, PqMonitor
|
||||
@Transactional
|
||||
public boolean updatePqMonitorByDevId(String devId, List<PqMonitorParam> paramList) {
|
||||
// 先删除原有数据
|
||||
this.deletePqMonitorByDevId(devId);
|
||||
// 再添加新数据
|
||||
// this.deletePqMonitorByDevId(devId);
|
||||
|
||||
// 添加新数据
|
||||
List<PqMonitor> pqMonitorList = BeanUtil.copyToList(paramList, PqMonitor.class);
|
||||
pqMonitorList.forEach(pqMonitor -> pqMonitor.setDevId(devId));
|
||||
return this.saveBatch(pqMonitorList);
|
||||
|
||||
List<PqMonitor> existedMonitorList = this.listPqMonitorByDevIds(Collections.singletonList(devId));
|
||||
|
||||
Map<Integer, List<PqMonitor>> map = pqMonitorList.stream().collect(Collectors.groupingBy(PqMonitor::getNum));
|
||||
|
||||
List<PqMonitor> newMonitorList = new ArrayList<>();
|
||||
map.forEach((num, monitorList) -> {
|
||||
PqMonitor pqMonitor = existedMonitorList.stream().filter(monitor -> monitor.getNum() == num).findFirst().orElse(null);
|
||||
|
||||
if (ObjectUtil.isNotNull(pqMonitor)) {
|
||||
BeanUtil.copyProperties(monitorList.get(0), pqMonitor, "id", "realtimeResult", "statisticsResult", "recordedResult", "realtimeNum", "statisticsNum", "recordedNum", "resultType", "qualifiedNum");
|
||||
newMonitorList.add(pqMonitor);
|
||||
} else {
|
||||
newMonitorList.addAll(monitorList);
|
||||
}
|
||||
});
|
||||
|
||||
// 同步更新设备的状态
|
||||
List<PqMonitor> enableCheckMonitorList = newMonitorList.stream().filter(pqMonitor -> pqMonitor.getCheckFlag() == 1).collect(Collectors.toList());
|
||||
List<PqMonitor> checkedMonitorList = enableCheckMonitorList.stream().filter(pqMonitor -> ObjectUtil.isNotNull(pqMonitor.getResultType())).collect(Collectors.toList());
|
||||
|
||||
if (enableCheckMonitorList.size() == checkedMonitorList.size() && checkedMonitorList.size() > 0) {
|
||||
this.baseMapper.updateDeviceCheckState(devId, CheckStateEnum.CHECKED.getValue());
|
||||
this.baseMapper.updateDeviceReportRState(devId, DevReportStateEnum.NOT_GENERATED.getValue());
|
||||
}
|
||||
if (enableCheckMonitorList.size() > checkedMonitorList.size() && checkedMonitorList.size() > 0) {
|
||||
this.baseMapper.updateDeviceCheckState(devId, CheckStateEnum.CHECKING.getValue());
|
||||
this.baseMapper.updateDeviceReportRState(devId, DevReportStateEnum.NOT_GENERATED.getValue());
|
||||
}
|
||||
if (enableCheckMonitorList.size() > 0 && checkedMonitorList.size() == 0) {
|
||||
this.baseMapper.updateDeviceCheckState(devId, CheckStateEnum.UNCHECKED.getValue());
|
||||
}
|
||||
|
||||
List<Integer> monitorResultList = checkedMonitorList.stream().map(pqMonitor -> {
|
||||
String resultType = pqMonitor.getResultType();
|
||||
DataSourceEnum dataSourceEnum = DataSourceEnum.ofByValue(resultType);
|
||||
switch (dataSourceEnum) {
|
||||
case REAL_DATA:
|
||||
return pqMonitor.getRealtimeResult();
|
||||
case WAVE_DATA:
|
||||
return pqMonitor.getRecordedResult();
|
||||
case MINUTE_STATISTICS_MAX:
|
||||
case MINUTE_STATISTICS_MIN:
|
||||
case MINUTE_STATISTICS_AVG:
|
||||
case MINUTE_STATISTICS_CP95:
|
||||
return pqMonitor.getStatisticsResult();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return null;
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
long qualifiedCount = monitorResultList.stream().filter(result -> result != null && result == 1).count();
|
||||
if (qualifiedCount == monitorResultList.size() && monitorResultList.size() > 0) {
|
||||
this.baseMapper.updateDeviceCheckResult(devId, CheckResultEnum.ACCORD.getValue());
|
||||
} else if (monitorResultList.size() > 0) {
|
||||
this.baseMapper.updateDeviceCheckResult(devId, CheckResultEnum.NOT_ACCORD.getValue());
|
||||
} else {
|
||||
this.baseMapper.updateDeviceCheckResult(devId, CheckResultEnum.UNCHECKED.getValue());
|
||||
}
|
||||
|
||||
// 同步更新计划的状态
|
||||
AdPlan plan = this.baseMapper.getPlanByDevId(devId);
|
||||
List<PqDevSub> devSubList = this.baseMapper.listDevSubByPlanId(plan.getId());
|
||||
|
||||
List<PqDevSub> checkedDevSubList = devSubList.stream().filter(pqDevSub -> pqDevSub.getCheckState() == CheckStateEnum.CHECKED.getValue()).collect(Collectors.toList());
|
||||
List<PqDevSub> checkingDevSubList = devSubList.stream().filter(pqDevSub -> pqDevSub.getCheckState() == CheckStateEnum.CHECKING.getValue()).collect(Collectors.toList());
|
||||
if (checkedDevSubList.size() == devSubList.size() && devSubList.size() > 0) {
|
||||
this.baseMapper.updatePlanCheckState(plan.getId(), CheckStateEnum.CHECKED.getValue());
|
||||
} else if (checkedDevSubList.size() > 0 || checkingDevSubList.size() > 0) {
|
||||
this.baseMapper.updatePlanCheckState(plan.getId(), CheckStateEnum.CHECKING.getValue());
|
||||
} else {
|
||||
this.baseMapper.updatePlanCheckState(plan.getId(), CheckStateEnum.UNCHECKED.getValue());
|
||||
}
|
||||
|
||||
List<PqDevSub> accordDevSubList = checkedDevSubList.stream().filter(pqDevSub -> pqDevSub.getCheckResult() == CheckResultEnum.ACCORD.getValue()).collect(Collectors.toList());
|
||||
if (accordDevSubList.size() == checkedDevSubList.size() && checkedDevSubList.size() > 0) {
|
||||
this.baseMapper.updatePlanCheckResult(plan.getId(), CheckResultEnum.ACCORD.getValue());
|
||||
} else if (accordDevSubList.size() > 0) {
|
||||
this.baseMapper.updatePlanCheckResult(plan.getId(), CheckResultEnum.NOT_ACCORD.getValue());
|
||||
} else {
|
||||
this.baseMapper.updatePlanCheckResult(plan.getId(), CheckResultEnum.UNCHECKED.getValue());
|
||||
}
|
||||
|
||||
List<PqDevSub> generatedDevSubList = checkedDevSubList.stream().filter(pqDevSub -> pqDevSub.getReportState() == DevReportStateEnum.GENERATED.getValue()).collect(Collectors.toList());
|
||||
if (generatedDevSubList.size() == checkedDevSubList.size() && checkedDevSubList.size() > 0) {
|
||||
this.baseMapper.updatePlanReportRState(plan.getId(), PlanReportStateEnum.REPORT_STATE_ALL_GENERATED.getValue());
|
||||
} else if (generatedDevSubList.size() > 0) {
|
||||
this.baseMapper.updatePlanReportRState(plan.getId(), PlanReportStateEnum.REPORT_STATE_PARTIALLY_GENERATED.getValue());
|
||||
} else {
|
||||
this.baseMapper.updatePlanReportRState(plan.getId(), PlanReportStateEnum.REPORT_STATE_NOT_GENERATED.getValue());
|
||||
}
|
||||
return this.saveOrUpdateBatch(newMonitorList);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -84,4 +198,250 @@ public class PqMonitorServiceImpl extends ServiceImpl<PqMonitorMapper, PqMonitor
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public PqMonitor getByDevMonitorId(String devMonitorId) {
|
||||
String[] split = devMonitorId.split("_");
|
||||
return this.baseMapper.getByDevIdAndNum(split[0], Integer.valueOf(split[1]));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double getRatedCurrent(String devMonitorId) {
|
||||
PqMonitor pqMonitor = this.getByDevMonitorId(devMonitorId);
|
||||
if (ObjectUtil.isNotNull(pqMonitor)) {
|
||||
String ct = pqMonitor.getCt();
|
||||
if (StrUtil.isNotBlank(ct) && ct.contains(StrUtil.COLON)) {
|
||||
String[] ctArray = ct.split(StrUtil.COLON);
|
||||
return Double.parseDouble(ctArray[1]);
|
||||
}
|
||||
}
|
||||
throw new BusinessException(DetectionResponseEnum.RATED_CT_ERROR);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double getRatedVoltage(String devMonitorId) {
|
||||
PqMonitor pqMonitor = this.getByDevMonitorId(devMonitorId);
|
||||
if (ObjectUtil.isNotNull(pqMonitor)) {
|
||||
String pt = pqMonitor.getPt();
|
||||
if (StrUtil.isNotBlank(pt) && pt.contains(StrUtil.COLON)) {
|
||||
String[] ptArray = pt.split(StrUtil.COLON);
|
||||
return Double.parseDouble(ptArray[1]);
|
||||
}
|
||||
}
|
||||
throw new BusinessException(DetectionResponseEnum.RATED_PT_ERROR);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public boolean updateMonitorResult(String monitorId, List<String> adTypes, DataSourceEnum dataSourceEnum, Integer num, Integer waveNum, String code) {
|
||||
String[] split = monitorId.split(CnSocketUtil.SPLIT_TAG);
|
||||
QueryWrapper<PqMonitor> wrapper = new QueryWrapper<>();
|
||||
wrapper.eq("pq_monitor.Dev_Id", split[0])
|
||||
.eq("pq_monitor.Num", split[1])
|
||||
.last("LIMIT 1");
|
||||
PqMonitor monitor = this.getOne(wrapper);
|
||||
String resultType = monitor.getResultType();
|
||||
Integer qualifiedNum = monitor.getQualifiedNum();
|
||||
|
||||
Integer newMonitorResult = CheckResultEnum.UNCHECKED.getValue();
|
||||
AtomicReference<Integer> newWaveNum = new AtomicReference<>(-1);
|
||||
switch (dataSourceEnum) {
|
||||
case WAVE_DATA:
|
||||
Map<Integer, Integer> waveNumResultMap = detectionDataDealService.getWaveNumResultMap(monitorId, adTypes, num, code);
|
||||
if (CollUtil.isEmpty(waveNumResultMap)) {
|
||||
return true;
|
||||
}
|
||||
waveNumResultMap.forEach((key, value) -> {
|
||||
if (CheckResultEnum.ACCORD.getValue().equals(value)) {
|
||||
newWaveNum.set(key);
|
||||
}
|
||||
});
|
||||
if (!newWaveNum.get().equals(-1)) {
|
||||
newMonitorResult = CheckResultEnum.ACCORD.getValue();
|
||||
} else {
|
||||
newWaveNum.set(waveNum);
|
||||
newMonitorResult = waveNumResultMap.get(waveNum);
|
||||
if (newMonitorResult == 2) {
|
||||
newMonitorResult = CheckResultEnum.NOT_ACCORD.getValue();
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
newMonitorResult = detectionDataDealService.getMonitorResult(monitorId, adTypes, dataSourceEnum.getValue(), num, null, code);
|
||||
if (newMonitorResult == 2) {
|
||||
newMonitorResult = CheckResultEnum.NOT_ACCORD.getValue();
|
||||
}
|
||||
if (newMonitorResult == 1) {
|
||||
newMonitorResult = CheckResultEnum.ACCORD.getValue();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
boolean updateFlag = false;
|
||||
if (StrUtil.isBlank(resultType)) {
|
||||
updateFlag = true;
|
||||
if (newMonitorResult.equals(CheckResultEnum.ACCORD.getValue())) {
|
||||
qualifiedNum += 1;
|
||||
}
|
||||
} else {
|
||||
Integer oldMonitorResult = null;
|
||||
if (DataSourceEnum.REAL_DATA.getValue().equals(resultType)) {
|
||||
oldMonitorResult = monitor.getRealtimeResult();
|
||||
} else if (DataSourceEnum.WAVE_DATA.getValue().equals(resultType)) {
|
||||
oldMonitorResult = monitor.getRecordedResult();
|
||||
} else {
|
||||
oldMonitorResult = monitor.getStatisticsResult();
|
||||
}
|
||||
|
||||
if (CheckResultEnum.ACCORD.getValue().equals(oldMonitorResult)) {
|
||||
if (CheckResultEnum.ACCORD.getValue().equals(newMonitorResult)) {
|
||||
String oldNum = "";
|
||||
if (DataSourceEnum.REAL_DATA.getValue().equals(resultType)) {
|
||||
oldNum = monitor.getRealtimeNum();
|
||||
} else if (DataSourceEnum.WAVE_DATA.getValue().equals(resultType)) {
|
||||
oldNum = monitor.getRecordedNum();
|
||||
} else {
|
||||
oldNum = monitor.getStatisticsNum();
|
||||
}
|
||||
String[] split1 = oldNum.split(CnSocketUtil.SPLIT_TAG);
|
||||
if (!split1[0].equals(num.toString())) {
|
||||
qualifiedNum += 1;
|
||||
}
|
||||
updateFlag = true;
|
||||
}
|
||||
} else {
|
||||
updateFlag = true;
|
||||
if (CheckResultEnum.ACCORD.getValue().equals(newMonitorResult)) {
|
||||
qualifiedNum += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (updateFlag) {
|
||||
monitor.setResultType(dataSourceEnum.getValue());
|
||||
|
||||
switch (dataSourceEnum) {
|
||||
case REAL_DATA:
|
||||
monitor.setRealtimeNum(String.valueOf(num));
|
||||
monitor.setRealtimeResult(newMonitorResult);
|
||||
monitor.setRecordedNum(null);
|
||||
monitor.setRecordedResult(null);
|
||||
monitor.setStatisticsNum(null);
|
||||
monitor.setStatisticsResult(null);
|
||||
break;
|
||||
case WAVE_DATA:
|
||||
monitor.setRealtimeNum(null);
|
||||
monitor.setRealtimeResult(null);
|
||||
monitor.setRecordedNum(num + CnSocketUtil.SPLIT_TAG + newWaveNum.get());
|
||||
monitor.setRecordedResult(newMonitorResult);
|
||||
monitor.setStatisticsNum(null);
|
||||
monitor.setStatisticsResult(null);
|
||||
break;
|
||||
default:
|
||||
monitor.setRealtimeResult(null);
|
||||
monitor.setRealtimeNum(null);
|
||||
monitor.setRecordedResult(null);
|
||||
monitor.setRecordedNum(null);
|
||||
monitor.setStatisticsNum(String.valueOf(num));
|
||||
monitor.setStatisticsResult(newMonitorResult);
|
||||
break;
|
||||
}
|
||||
monitor.setQualifiedNum(qualifiedNum);
|
||||
return this.updateById(monitor);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Integer getDevCheckState(String devId) {
|
||||
QueryWrapper<PqMonitor> wrapper = new QueryWrapper<>();
|
||||
wrapper.eq("pq_monitor.Dev_Id", devId)
|
||||
.eq("pq_monitor.Check_Flag", 1);
|
||||
List<PqMonitor> monitorList = this.list(wrapper);
|
||||
if (CollUtil.isNotEmpty(monitorList)) {
|
||||
List<Integer> allNumList = monitorList.stream().map(PqMonitor::getNum).collect(Collectors.toList());
|
||||
List<Integer> checkedNumList = monitorList.stream().filter(obj -> ObjectUtil.isNotNull(obj.getResultType())).map(PqMonitor::getNum).collect(Collectors.toList());
|
||||
|
||||
if (checkedNumList.containsAll(allNumList)) {
|
||||
return CheckStateEnum.CHECKED.getValue();
|
||||
} else {
|
||||
if (checkedNumList.size() > 0) {
|
||||
return CheckStateEnum.CHECKING.getValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
return CheckStateEnum.UNCHECKED.getValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getDevCheckResult(String devId) {
|
||||
QueryWrapper<PqMonitor> wrapper = new QueryWrapper<>();
|
||||
wrapper.eq("pq_monitor.Dev_Id", devId)
|
||||
.eq("pq_monitor.Check_Flag", 1);
|
||||
List<PqMonitor> monitorList = this.list(wrapper);
|
||||
|
||||
List<Integer> allResultFlags = new ArrayList<>();
|
||||
for (PqMonitor monitor : monitorList) {
|
||||
String resultType = monitor.getResultType();
|
||||
if (StrUtil.isNotBlank(resultType)) {
|
||||
if (DataSourceEnum.REAL_DATA.getValue().equals(resultType)) {
|
||||
//allResultFlags.add(monitor.getRealtimeResult());
|
||||
if (monitor.getRealtimeResult() == 0) {
|
||||
allResultFlags.add(2);
|
||||
}
|
||||
if (monitor.getRealtimeResult() == 1) {
|
||||
allResultFlags.add(1);
|
||||
}
|
||||
if (monitor.getRealtimeResult() == 4) {
|
||||
allResultFlags.add(4);
|
||||
}
|
||||
} else if (DataSourceEnum.WAVE_DATA.getValue().equals(resultType)) {
|
||||
//allResultFlags.add(monitor.getRecordedResult());
|
||||
if (monitor.getRecordedResult() == 0) {
|
||||
allResultFlags.add(2);
|
||||
}
|
||||
if (monitor.getRecordedResult() == 1) {
|
||||
allResultFlags.add(1);
|
||||
}
|
||||
if (monitor.getRecordedResult() == 4) {
|
||||
allResultFlags.add(4);
|
||||
}
|
||||
} else {
|
||||
//allResultFlags.add(monitor.getStatisticsResult());
|
||||
if (monitor.getStatisticsResult() == 0) {
|
||||
allResultFlags.add(2);
|
||||
}
|
||||
if (monitor.getStatisticsResult() == 1) {
|
||||
allResultFlags.add(1);
|
||||
}
|
||||
if (monitor.getStatisticsResult() == 4) {
|
||||
allResultFlags.add(4);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return DetectionDataServiceImpl.isResultFlag(allResultFlags);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeByDevId(String devId) {
|
||||
QueryWrapper<PqMonitor> wrapper = new QueryWrapper<>();
|
||||
wrapper.eq("pq_monitor.Dev_Id", devId);
|
||||
return this.remove(wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PqMonitor getByDevAndNum(String devId, int monitorNum) {
|
||||
QueryWrapper<PqMonitor> wrapper = new QueryWrapper<>();
|
||||
wrapper.eq("pq_monitor.Dev_Id", devId)
|
||||
.eq("pq_monitor.Num", monitorNum);
|
||||
List<PqMonitor> pqMonitors = this.list(wrapper);
|
||||
if(CollUtil.isNotEmpty(pqMonitors)){
|
||||
return pqMonitors.get(0);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package com.njcn.gather.plan.controller;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.njcn.common.pojo.annotation.OperateInfo;
|
||||
import com.njcn.common.pojo.constant.OperateType;
|
||||
@@ -13,15 +15,19 @@ import com.njcn.common.pojo.response.HttpResult;
|
||||
import com.njcn.common.utils.LogUtil;
|
||||
import com.njcn.gather.device.pojo.enums.CommonEnum;
|
||||
import com.njcn.gather.device.pojo.param.PqDevParam;
|
||||
import com.njcn.gather.device.pojo.po.PqDev;
|
||||
import com.njcn.gather.device.pojo.po.PqStandardDev;
|
||||
import com.njcn.gather.device.pojo.vo.PqDevVO;
|
||||
import com.njcn.gather.device.service.IPqDevService;
|
||||
import com.njcn.gather.plan.pojo.param.AdPlanParam;
|
||||
import com.njcn.gather.plan.pojo.po.AdPlan;
|
||||
import com.njcn.gather.plan.pojo.vo.AdPlanVO;
|
||||
import com.njcn.gather.plan.service.AsyncPlanHandler;
|
||||
import com.njcn.gather.plan.service.IAdPlanService;
|
||||
import com.njcn.gather.type.pojo.po.DevType;
|
||||
import com.njcn.gather.type.service.IDevTypeService;
|
||||
import com.njcn.gather.user.user.pojo.po.SysUser;
|
||||
import com.njcn.gather.user.user.service.ISysUserService;
|
||||
import com.njcn.web.controller.BaseController;
|
||||
import com.njcn.web.utils.FileUtil;
|
||||
import com.njcn.web.utils.HttpResultUtil;
|
||||
@@ -36,10 +42,15 @@ import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.njcn.web.utils.RequestUtil.getUserId;
|
||||
|
||||
/**
|
||||
* @author caozehui
|
||||
* @date 2024-12-09
|
||||
@@ -54,6 +65,8 @@ public class AdPlanController extends BaseController {
|
||||
private final IAdPlanService adPlanService;
|
||||
private final IPqDevService pqDevService;
|
||||
private final IDevTypeService devTypeService;
|
||||
private final ISysUserService sysUserService;
|
||||
private final AsyncPlanHandler asyncPlanHandler;
|
||||
|
||||
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
|
||||
@PostMapping("/list")
|
||||
@@ -167,18 +180,18 @@ public class AdPlanController extends BaseController {
|
||||
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
|
||||
@PostMapping("/getBigTestItem")
|
||||
@ApiOperation("获取检测大项数据")
|
||||
@ApiImplicitParam(name = "id", value = "检测计划id", required = true)
|
||||
@ApiImplicitParam(name = "checkParam", value = "检测计划id", required = true)
|
||||
public HttpResult<List<Map<String, String>>> getBigTestItem(@RequestBody AdPlanParam.CheckParam checkParam) {
|
||||
String methodDescribe = getMethodDescribe("getBigTestItem");
|
||||
LogUtil.njcnDebug(log, "{},查询数据为:{}", methodDescribe, checkParam);
|
||||
List<Map<String, String>> result = adPlanService.getBigTestItem(checkParam.getReCheckType(), checkParam.getPlanId(), checkParam.getDevIds());
|
||||
List<Map<String, String>> result = adPlanService.getBigTestItem(checkParam.getReCheckType(), checkParam.getPlanId(), checkParam.getDevIds(), checkParam.getPatternId(), checkParam.getScriptType());
|
||||
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe);
|
||||
}
|
||||
|
||||
@OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.DOWNLOAD)
|
||||
@PostMapping("/analyse")
|
||||
@ApiOperation("检测数据分析")
|
||||
@ApiImplicitParam(name = "planId", value = "检测计划id", required = true)
|
||||
@ApiImplicitParam(name = "ids", value = "检测计划id", required = true)
|
||||
public void analyse(@RequestBody List<String> ids) {
|
||||
String methodDescribe = getMethodDescribe("analyse");
|
||||
LogUtil.njcnDebug(log, "{},查询数据为:{}", methodDescribe, String.join(StrUtil.COMMA, ids));
|
||||
@@ -188,7 +201,7 @@ public class AdPlanController extends BaseController {
|
||||
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
|
||||
@PostMapping("/listByPlanId")
|
||||
@ApiOperation("查询出所有已绑定的设备")
|
||||
@ApiImplicitParam(name = "planId", value = "计划id", required = true)
|
||||
@ApiImplicitParam(name = "param", value = "查询参数", required = true)
|
||||
public HttpResult<List<PqDevVO>> listByPlanId(@RequestBody @Validated PqDevParam.QueryParam param) {
|
||||
String methodDescribe = getMethodDescribe("listByPlanId");
|
||||
LogUtil.njcnDebug(log, "{},查询数据为:{}", methodDescribe, param);
|
||||
@@ -228,7 +241,7 @@ public class AdPlanController extends BaseController {
|
||||
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
|
||||
@PostMapping("/listDevByPlanId")
|
||||
@ApiOperation("根据计划id分页查询被检设备")
|
||||
@ApiImplicitParam(name = "queryParam", value = "查询参数", required = true)
|
||||
@ApiImplicitParam(name = "param", value = "查询参数", required = true)
|
||||
public HttpResult<Page<PqDevVO>> listDevByPlanId(@RequestBody @Validated PqDevParam.QueryParam param) {
|
||||
String methodDescribe = getMethodDescribe("listDevByPlanId");
|
||||
LogUtil.njcnDebug(log, "{},查询数据为:{}", methodDescribe, param);
|
||||
@@ -239,6 +252,7 @@ public class AdPlanController extends BaseController {
|
||||
List<String> planIdList = planList.stream().map(AdPlan::getId).collect(Collectors.toList());
|
||||
param.getPlanIdList().addAll(planIdList);
|
||||
}
|
||||
param.setPlanId(plan.getId());
|
||||
Page<PqDevVO> pqDevVOPage = pqDevService.listPqDevs(param);
|
||||
|
||||
List<AdPlan> planList = adPlanService.listByIds(param.getPlanIdList());
|
||||
@@ -291,11 +305,14 @@ public class AdPlanController extends BaseController {
|
||||
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
|
||||
@GetMapping("/getBoundStandardDev")
|
||||
@ApiOperation("根据计划ID获取已绑定的标准设备")
|
||||
@ApiImplicitParam(name = "planId", value = "计划ID", required = true)
|
||||
public HttpResult<List<PqStandardDev>> getBoundStandardDev(@RequestParam("planId") String planId) {
|
||||
@ApiImplicitParams({
|
||||
@ApiImplicitParam(name = "planId", value = "计划ID", required = true),
|
||||
@ApiImplicitParam(name = "all", value = "是否获取所有")
|
||||
})
|
||||
public HttpResult<List<PqStandardDev>> getBoundStandardDev(@RequestParam("planId") String planId, @RequestParam(name = "all", required = false) Integer all) {
|
||||
String methodDescribe = getMethodDescribe("getUnBoundStandardDev");
|
||||
LogUtil.njcnDebug(log, "{},计划ID为:{}", methodDescribe, planId);
|
||||
List<PqStandardDev> result = adPlanService.getBoundStandardDev(planId);
|
||||
LogUtil.njcnDebug(log, "{},计划ID为:{},获取所有:{}", methodDescribe, planId, all);
|
||||
List<PqStandardDev> result = adPlanService.getBoundStandardDev(planId, all);
|
||||
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe);
|
||||
}
|
||||
|
||||
@@ -348,12 +365,108 @@ public class AdPlanController extends BaseController {
|
||||
|
||||
@OperateInfo(operateType = OperateType.DOWNLOAD)
|
||||
@PostMapping("/exportSubPlan")
|
||||
@ApiOperation("导出子计划")
|
||||
@ApiOperation("导出子计划元信息")
|
||||
@ApiImplicitParam(name = "planId", value = "子计划id", required = true)
|
||||
public void exportSubPlan(@RequestParam("planId") String planId, HttpServletResponse response) {
|
||||
String methodDescribe = getMethodDescribe("exportSubPlan");
|
||||
LogUtil.njcnDebug(log, "{},导出ID数据为:{}", methodDescribe, planId);
|
||||
adPlanService.exportSubPlan(planId, response);
|
||||
adPlanService.exportSubPlanDataZip(planId, response);
|
||||
}
|
||||
|
||||
@OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.UPLOAD)
|
||||
@PostMapping(value = "/importSubPlan")
|
||||
@ApiOperation("导入子计划元信息")
|
||||
@ApiImplicitParams({
|
||||
@ApiImplicitParam(name = "file", value = "检测计划数据文件", required = true),
|
||||
@ApiImplicitParam(name = "patternId", value = "模式Id", required = true)
|
||||
})
|
||||
public HttpResult<Boolean> importSubPlan(@RequestParam("file") MultipartFile file, @RequestParam("patternId") String patternId, HttpServletResponse response) {
|
||||
String methodDescribe = getMethodDescribe("importSubPlan");
|
||||
LogUtil.njcnDebug(log, "{},上传文件为:{}", methodDescribe, file.getOriginalFilename());
|
||||
boolean fileType = FileUtil.judgeFileIsZip(file.getOriginalFilename());
|
||||
if (!fileType) {
|
||||
CommonResponseEnum fileTypeError = CommonResponseEnum.FILE_XLSX_ERROR;
|
||||
fileTypeError.setMessage("请上传zip文件");
|
||||
throw new BusinessException(fileTypeError);
|
||||
}
|
||||
adPlanService.importSubPlanDataZip(file, patternId, response);
|
||||
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, true, methodDescribe);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@OperateInfo(operateType = OperateType.DOWNLOAD)
|
||||
@PostMapping("/exportPlanCheckData")
|
||||
@ApiOperation("导出计划检测结果数据")
|
||||
@ApiImplicitParams({
|
||||
@ApiImplicitParam(name = "planId", value = "计划id", required = true),
|
||||
@ApiImplicitParam(name = "devIds", value = "被检设备ids", required = true),
|
||||
@ApiImplicitParam(name = "report", value = "是否导出报告, 0 否,1 是", required = true)
|
||||
})
|
||||
public HttpResult<Boolean> exportPlanCheckData(@RequestParam("planId") String planId, @RequestParam("devIds") String devIds, @RequestParam("report") Integer report) {
|
||||
String methodDescribe = getMethodDescribe("exportPlanCheckData");
|
||||
LogUtil.njcnDebug(log, "{},导出计划ID数据为:{} {} {}", methodDescribe, planId, devIds, report);
|
||||
// 获取检测计划绑定的被检设备数据
|
||||
List<PqDev> devList = pqDevService.list(new LambdaQueryWrapper<PqDev>().eq(PqDev::getPlanId, planId).in(PqDev::getId, StrUtil.split(devIds, StrUtil.COMMA)));
|
||||
if (CollUtil.isEmpty(devList)) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "选择的被检设备不存在");
|
||||
}
|
||||
asyncPlanHandler.exportPlanCheckDataZip(getUserId(), planId, devList, report);
|
||||
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, true, methodDescribe);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
|
||||
@GetMapping("/getMemberList")
|
||||
@ApiOperation("根据计划ID获取项目成员")
|
||||
@ApiImplicitParam(name = "planId", value = "计划ID", required = true)
|
||||
public HttpResult<List<SysUser>> getMemberList(@RequestParam("planId") String planId) {
|
||||
String methodDescribe = getMethodDescribe("getMemberList");
|
||||
LogUtil.njcnDebug(log, "{},计划ID为:{},获取项目成员", methodDescribe, planId);
|
||||
AdPlan plan = adPlanService.getById(planId);
|
||||
List<SysUser> result = new ArrayList<>();
|
||||
if (plan != null) {
|
||||
String members = plan.getMembers();
|
||||
if (StrUtil.isNotBlank(members)) {
|
||||
List<String> memberIds = StrUtil.split(members, StrUtil.COMMA);
|
||||
result = sysUserService.listByIds(memberIds);
|
||||
result.forEach(member -> member.setPassword(null));
|
||||
}
|
||||
}
|
||||
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe);
|
||||
}
|
||||
|
||||
|
||||
@OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.UPLOAD)
|
||||
@PostMapping(value = "/importAndMergePlanCheckData")
|
||||
@ApiOperation("合并计划检测结果数据")
|
||||
@ApiImplicitParams({
|
||||
@ApiImplicitParam(name = "file", value = "检测计划检测结果数据文件", required = true),
|
||||
@ApiImplicitParam(name = "planId", value = "计划Id", required = true)
|
||||
})
|
||||
public HttpResult<Boolean> importAndMergePlanCheckData(@RequestParam("file") MultipartFile file, @RequestParam("planId") String planId) {
|
||||
String methodDescribe = getMethodDescribe("importAndMergePlanCheckData");
|
||||
LogUtil.njcnDebug(log, "{},合并计划ID检测数据为:{}", methodDescribe, planId);
|
||||
boolean fileType = FileUtil.judgeFileIsZip(file.getOriginalFilename());
|
||||
if (!fileType) {
|
||||
CommonResponseEnum fileTypeError = CommonResponseEnum.FILE_XLSX_ERROR;
|
||||
fileTypeError.setMessage("请上传zip文件");
|
||||
throw new BusinessException(fileTypeError);
|
||||
}
|
||||
// 创建临时文件
|
||||
File tempFile = cn.hutool.core.io.FileUtil.createTempFile();
|
||||
// 将MultipartFile内容写入临时文件
|
||||
try {
|
||||
file.transferTo(tempFile);
|
||||
} catch (IOException e) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "文件保存失败");
|
||||
}
|
||||
// 获取文件路径
|
||||
String filePath = tempFile.getAbsolutePath();
|
||||
asyncPlanHandler.importAndMergePlanCheckData(filePath, getUserId(), planId);
|
||||
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, true, methodDescribe);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.njcn.gather.plan.controller;
|
||||
|
||||
import com.njcn.gather.plan.service.SseClient;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
|
||||
import static com.njcn.web.utils.RequestUtil.getUserId;
|
||||
|
||||
/**
|
||||
* SSE控制器,用于处理SSE相关的请求。
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@RequestMapping("/sse")
|
||||
@RestController
|
||||
public class SseController {
|
||||
|
||||
private final SseClient sseClient;
|
||||
|
||||
|
||||
/**
|
||||
* 创建SSE连接。
|
||||
*
|
||||
* @return SseEmitter对象,用于与客户端建立SSE连接。
|
||||
*/
|
||||
@GetMapping("/createSse")
|
||||
public SseEmitter createConnect() {
|
||||
String uid = getUserId();
|
||||
SseEmitter sse = sseClient.createSse(uid);
|
||||
log.info("为用户UID: {} 创建SSE连接", uid);
|
||||
return sse;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.njcn.gather.plan.mapper;
|
||||
|
||||
import com.github.yulichang.base.MPJBaseMapper;
|
||||
import com.njcn.gather.plan.pojo.po.AdPlanTestConfig;
|
||||
|
||||
/**
|
||||
* @author stary
|
||||
* @date 2025-08-25
|
||||
*/
|
||||
public interface AdPlanTestConfigMapper extends MPJBaseMapper<AdPlanTestConfig> {
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -2,19 +2,21 @@ package com.njcn.gather.plan.pojo.enums;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* @author caozehui
|
||||
* @data 2024-12-12
|
||||
*/
|
||||
@Getter
|
||||
public enum DataSourceEnum {
|
||||
REAL_DATA("real", "3s实时数据"),
|
||||
REAL_DATA("real", "3s数据(150周波数据)"),
|
||||
|
||||
MINUTE_STATISTICS_MAX("max", "分钟统计数据-最大"),
|
||||
MINUTE_STATISTICS_MIN("min", "分钟统计数据-最小"),
|
||||
MINUTE_STATISTICS_AVG("avg", "分钟统计数据-平均"),
|
||||
MINUTE_STATISTICS_CP95("cp95", "分钟统计数据-CP95"),
|
||||
RECORDED_DATA("Recorded_data", "录播数据");
|
||||
MINUTE_STATISTICS_MAX("max", "分钟统计数据-最大值"),
|
||||
MINUTE_STATISTICS_MIN("min", "分钟统计数据-最小值"),
|
||||
MINUTE_STATISTICS_AVG("avg", "分钟统计数据-平均值"),
|
||||
MINUTE_STATISTICS_CP95("cp95", "分钟统计数据-CP95值"),
|
||||
WAVE_DATA("wave_data", "录波数据");
|
||||
|
||||
private String value;
|
||||
private String msg;
|
||||
@@ -24,21 +26,29 @@ public enum DataSourceEnum {
|
||||
this.msg = msg;
|
||||
}
|
||||
|
||||
public static String getMsgByValue(String value) {
|
||||
public static DataSourceEnum ofByValue(String value) {
|
||||
for (DataSourceEnum dataSourceEnum : DataSourceEnum.values()) {
|
||||
if (dataSourceEnum.getValue().equals(value)) {
|
||||
return dataSourceEnum.getMsg();
|
||||
return dataSourceEnum;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String getValueByMsg(String msg) {
|
||||
public static DataSourceEnum ofByMsg(String msg) {
|
||||
for (DataSourceEnum dataSourceEnum : DataSourceEnum.values()) {
|
||||
if (dataSourceEnum.getMsg().equals(msg)) {
|
||||
return dataSourceEnum.getValue();
|
||||
return dataSourceEnum;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String getMsgByValue(String value) {
|
||||
return Objects.requireNonNull(ofByValue(value)).getMsg();
|
||||
}
|
||||
|
||||
public static String getValueByMsg(String msg) {
|
||||
return Objects.requireNonNull(ofByMsg(msg)).getMsg();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.njcn.gather.plan.pojo.param;
|
||||
|
||||
import com.njcn.common.pojo.constant.PatternRegex;
|
||||
import com.njcn.gather.plan.pojo.po.AdPlanTestConfig;
|
||||
import com.njcn.gather.pojo.constant.DetectionValidMessage;
|
||||
import com.njcn.web.pojo.param.BaseParam;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
@@ -69,8 +70,16 @@ public class AdPlanParam {
|
||||
private List<String> standardDevIds;
|
||||
|
||||
@ApiModelProperty(value = "测试项ID列表")
|
||||
private List<@Pattern(regexp = PatternRegex.SYSTEM_ID, message = DetectionValidMessage.SOURCE_ID_FORMAT_ERROR)String> testItems;
|
||||
private List<@Pattern(regexp = PatternRegex.SYSTEM_ID, message = DetectionValidMessage.SOURCE_ID_FORMAT_ERROR) String> testItems;
|
||||
|
||||
@ApiModelProperty(value = "检测配置")
|
||||
private AdPlanTestConfig testConfig;
|
||||
|
||||
@ApiModelProperty(value = "检测负责人")
|
||||
private String leader;
|
||||
|
||||
@ApiModelProperty(value = "检测成员")
|
||||
private List<String> memberIds;
|
||||
|
||||
/**
|
||||
* 分页查询实体
|
||||
@@ -118,5 +127,7 @@ public class AdPlanParam {
|
||||
private Integer reCheckType;
|
||||
private String planId;
|
||||
private List<String> devIds;
|
||||
private String patternId;
|
||||
private String scriptType;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,6 +81,9 @@ public class AdPlan extends BaseEntity implements Serializable {
|
||||
|
||||
/**
|
||||
* 是否关联报告,0-不关联 1-关联
|
||||
* 目前这个阶段
|
||||
* 0-不关联的是采用替换占位符的方式生成测试报告
|
||||
* 1-关联的是采用模板生成测试报告
|
||||
*/
|
||||
private Integer associateReport;
|
||||
|
||||
@@ -110,5 +113,22 @@ public class AdPlan extends BaseEntity implements Serializable {
|
||||
* 来源
|
||||
*/
|
||||
private String origin;
|
||||
|
||||
/**
|
||||
* 是否为导入(比对式使用) 0-否 1-是
|
||||
*/
|
||||
private Integer importFlag;
|
||||
|
||||
/**
|
||||
* 检测负责人
|
||||
*/
|
||||
private String leader;
|
||||
|
||||
/**
|
||||
* 检测成员
|
||||
*/
|
||||
private String members;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.njcn.gather.plan.pojo.po;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @author stary
|
||||
* @date 2025-08-25
|
||||
*/
|
||||
@Data
|
||||
@TableName("ad_plan_test_config")
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class AdPlanTestConfig implements Serializable {
|
||||
private static final long serialVersionUID = -796292730578249530L;
|
||||
/**
|
||||
* 检测计划表Id
|
||||
*/
|
||||
private String planId;
|
||||
|
||||
/**
|
||||
* 录波数据有效组数
|
||||
*/
|
||||
private Integer waveRecord;
|
||||
|
||||
/**
|
||||
* 实时数据有效组数
|
||||
*/
|
||||
private Integer realTime;
|
||||
|
||||
/**
|
||||
* 统计数据有效组数
|
||||
*/
|
||||
private Integer statistics;
|
||||
|
||||
/**
|
||||
* 短闪数据有效组数
|
||||
*/
|
||||
private Integer flicker;
|
||||
|
||||
/**
|
||||
* 最大检测次数,默认3次
|
||||
*/
|
||||
private Integer maxTime;
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.njcn.gather.plan.pojo.vo;
|
||||
|
||||
import com.njcn.gather.detection.pojo.po.AdPair;
|
||||
import com.njcn.gather.device.pojo.po.PqDev;
|
||||
import com.njcn.gather.device.pojo.po.PqDevSub;
|
||||
import com.njcn.gather.monitor.pojo.po.PqMonitor;
|
||||
import com.njcn.gather.plan.pojo.po.AdPlan;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
public class AdPlanCheckDataVO {
|
||||
|
||||
private AdPlan plan;
|
||||
private List<PqDev> devList;
|
||||
private List<PqDevSub> devSubList;
|
||||
private List<AdPair> pairList;
|
||||
private List<PqMonitor> monitorList;
|
||||
private List<String> devMonitorIds;
|
||||
private int dataBatch;
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.njcn.gather.plan.pojo.vo;
|
||||
|
||||
import com.njcn.gather.device.pojo.vo.PqDevVO;
|
||||
import com.njcn.gather.plan.pojo.po.AdPlanTestConfig;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
@@ -149,4 +150,22 @@ public class AdPlanVO {
|
||||
* 来源
|
||||
*/
|
||||
private String origin;
|
||||
|
||||
private AdPlanTestConfig testConfig;
|
||||
/**
|
||||
* 是否导入,0-否 1-是
|
||||
*/
|
||||
private Integer importFlag;
|
||||
|
||||
/**
|
||||
* 检测负责人
|
||||
*/
|
||||
private String leader;
|
||||
private String leaderName;
|
||||
|
||||
/**
|
||||
* 检测成员
|
||||
*/
|
||||
private String members;
|
||||
private String membersName;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.njcn.gather.plan.pojo.vo;
|
||||
|
||||
import com.njcn.gather.device.pojo.po.PqDev;
|
||||
import com.njcn.gather.device.pojo.po.PqStandardDev;
|
||||
import com.njcn.gather.err.pojo.po.PqErrSys;
|
||||
import com.njcn.gather.err.pojo.po.PqErrSysDtls;
|
||||
import com.njcn.gather.icd.pojo.po.PqIcdPath;
|
||||
import com.njcn.gather.plan.pojo.po.AdPlan;
|
||||
import com.njcn.gather.plan.pojo.po.AdPlanTestConfig;
|
||||
import com.njcn.gather.report.pojo.po.PqReport;
|
||||
import com.njcn.gather.system.dictionary.pojo.po.DictData;
|
||||
import com.njcn.gather.system.dictionary.pojo.po.DictTree;
|
||||
import com.njcn.gather.system.dictionary.pojo.po.DictType;
|
||||
import com.njcn.gather.type.pojo.po.DevType;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
public class AdSubPlanMetaDataVO {
|
||||
|
||||
private Dict dict;
|
||||
private AdPlan plan;
|
||||
private AdPlanTestConfig testConfig;
|
||||
private List<PqDev> devList;
|
||||
private List<PqStandardDev> standardDevList;
|
||||
private List<DevType> devTypeList;
|
||||
private List<PqIcdPath> icdPathList;
|
||||
private List<PqErrSys> errSysList;
|
||||
private List<PqErrSysDtls> errSysDtlsList;
|
||||
private PqReport reportTemplate;
|
||||
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
public static class Dict{
|
||||
private List<DictType> typeList;
|
||||
|
||||
private List<DictData> dataList;
|
||||
|
||||
private List<DictTree> treeList;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,530 @@
|
||||
package com.njcn.gather.plan.service;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.core.util.ZipUtil;
|
||||
import cn.hutool.json.JSONConfig;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.njcn.common.pojo.enums.common.DataStateEnum;
|
||||
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
|
||||
import com.njcn.gather.detection.pojo.po.AdPair;
|
||||
import com.njcn.gather.detection.service.IAdPariService;
|
||||
import com.njcn.gather.device.pojo.enums.CheckStateEnum;
|
||||
import com.njcn.gather.device.pojo.po.PqDev;
|
||||
import com.njcn.gather.device.pojo.po.PqDevSub;
|
||||
import com.njcn.gather.device.service.IPqDevService;
|
||||
import com.njcn.gather.device.service.IPqDevSubService;
|
||||
import com.njcn.gather.monitor.pojo.po.PqMonitor;
|
||||
import com.njcn.gather.monitor.service.IPqMonitorService;
|
||||
import com.njcn.gather.plan.pojo.po.AdPlan;
|
||||
import com.njcn.gather.plan.pojo.vo.AdPlanCheckDataVO;
|
||||
import com.njcn.gather.plan.service.util.BatchFileReader;
|
||||
import com.njcn.gather.system.config.handler.NonWebAutoFillValueHandler;
|
||||
import com.njcn.gather.tools.report.model.constant.ReportConstant;
|
||||
import com.njcn.gather.type.pojo.po.DevType;
|
||||
import com.njcn.gather.type.service.IDevTypeService;
|
||||
import com.njcn.web.utils.HttpResultUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.io.File;
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Slf4j
|
||||
@EnableAsync
|
||||
@RequiredArgsConstructor
|
||||
@Component
|
||||
public class AsyncPlanHandler {
|
||||
|
||||
private final SseClient sseClient;
|
||||
private final IAdPlanService adPlanService;
|
||||
private final IPqDevService pqDevService;
|
||||
private final IDevTypeService devTypeService;
|
||||
private final IPqDevSubService pqDevSubService;
|
||||
private final IPqMonitorService pqMonitorService;
|
||||
|
||||
private final IAdPariService adPairService;
|
||||
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
|
||||
|
||||
@Value("${report.reportDir}")
|
||||
private String reportPath;
|
||||
@Value("${data.homeDir}")
|
||||
private String dataPath;
|
||||
|
||||
private static final int BATCH_SIZE = 10000;
|
||||
private static final int FINAL_STEP = 85;
|
||||
private static final String TEST_DATA_DIR = "plan_test_data";
|
||||
|
||||
|
||||
@Async
|
||||
public void exportPlanCheckDataZip(String uid, String planId, List<PqDev> devList, Integer report) {
|
||||
NonWebAutoFillValueHandler.setCurrentUserId(uid);
|
||||
LocalDateTime startTime = LocalDateTime.now();
|
||||
AdPlanCheckDataVO planCheckDataVO = new AdPlanCheckDataVO();
|
||||
AtomicInteger progress = new AtomicInteger(0);
|
||||
AtomicInteger currentProgress = new AtomicInteger(0);
|
||||
sseClient.sendMessage(uid, planId, HttpResultUtil.assembleResult(CommonResponseEnum.SUCCESS.getCode(), progress, "开始导出文件"));
|
||||
|
||||
// 获取检测计划基本数据
|
||||
AdPlan plan = adPlanService.getById(planId);
|
||||
planCheckDataVO.setPlan(plan);
|
||||
planCheckDataVO.setDevList(devList);
|
||||
List<String> devIdList = devList.stream().map(PqDev::getId).collect(Collectors.toList());
|
||||
// 被检设备状态统计
|
||||
List<PqDevSub> devSubList = pqDevSubService.list(new LambdaQueryWrapper<PqDevSub>().in(PqDevSub::getDevId, devIdList));
|
||||
for (PqDevSub devSub : devSubList) {
|
||||
// 不导出报告,设置报告状态为未生成
|
||||
if (ObjectUtil.isNull(report) || report.equals(DataStateEnum.DELETED.getCode())) {
|
||||
devSub.setReportState(DataStateEnum.DELETED.getCode());
|
||||
}
|
||||
}
|
||||
planCheckDataVO.setDevSubList(devSubList);
|
||||
// 被检设备监测点信息
|
||||
List<PqMonitor> monitorList = pqMonitorService.list(new LambdaQueryWrapper<PqMonitor>().in(PqMonitor::getDevId, devIdList));
|
||||
planCheckDataVO.setMonitorList(monitorList);
|
||||
// devMonitorId = 被检设备ID+通道号
|
||||
List<String> devMonitorIds = new ArrayList<>();
|
||||
for (PqDev dev : devList) {
|
||||
List<String> channelNoList = StrUtil.split(dev.getInspectChannel(), StrUtil.COMMA);
|
||||
for (String channelNo : channelNoList) {
|
||||
devMonitorIds.add(dev.getId() + StrUtil.UNDERLINE + channelNo);
|
||||
}
|
||||
}
|
||||
planCheckDataVO.setDevMonitorIds(devMonitorIds);
|
||||
|
||||
// 设备通道匹对关系
|
||||
List<AdPair> pairList = adPairService.list(new LambdaQueryWrapper<AdPair>().eq(AdPair::getPlanId, planId).in(AdPair::getDevMonitorId, devMonitorIds));
|
||||
planCheckDataVO.setPairList(pairList);
|
||||
progress.addAndGet(1);
|
||||
sseClient.sendMessage(uid, planId, HttpResultUtil.assembleResult(CommonResponseEnum.SUCCESS.getCode(), progress, "生成检测计划基本信息数据文件中,请耐心等待..."));
|
||||
|
||||
// 获取计划检测结果数据表以及数据
|
||||
Integer code = plan.getCode();
|
||||
List<String> dataTableNames = CollUtil.newArrayList("ad_harmonic_" + code, "ad_non_harmonic_" + code, "ad_harmonic_result_" + code, "ad_non_harmonic_result_" + code);
|
||||
|
||||
// 创建临时目录用于存储文件
|
||||
File tempDataDir = FileUtil.mkdir(FileUtil.getTmpDirPath() + "plan_test_data_" + System.currentTimeMillis() + "/");
|
||||
int dataBatch = 0;
|
||||
int current = 0;
|
||||
if (CollUtil.isNotEmpty(pairList)) {
|
||||
for (String dataTableName : dataTableNames) {
|
||||
// 创建数据文件
|
||||
String fileName = dataTableName.replace("_" + code, "") + ".txt";
|
||||
File dataFile = FileUtil.file(tempDataDir, fileName);
|
||||
// 确保文件存在
|
||||
FileUtil.touch(dataFile);
|
||||
|
||||
// 初始化写入标志,用于判断是否已写入字段名
|
||||
boolean isFirstWrite = true;
|
||||
|
||||
// 分页查询,避免一次性加载大量数据
|
||||
int pageSize = BATCH_SIZE; // 每页查询10000条记录
|
||||
int offset = 0;
|
||||
List<Map<String, Object>> pageData;
|
||||
do {
|
||||
dataBatch += 1;
|
||||
if (current < FINAL_STEP + 5) {
|
||||
current = Math.min(current + 1, FINAL_STEP + 5);
|
||||
}
|
||||
currentProgress.set(current);
|
||||
sseClient.sendMessage(uid, planId, HttpResultUtil.assembleResult(CommonResponseEnum.SUCCESS.getCode(), progress.get() + currentProgress.get(), "生成检测结果数据文件中,请耐心等待..."));
|
||||
|
||||
String paginatedSql = buildPaginatedQuery(dataTableName, devMonitorIds, pageSize, offset);
|
||||
pageData = jdbcTemplate.queryForList(paginatedSql);
|
||||
|
||||
// 将当前页数据追加到文件中
|
||||
if (CollUtil.isNotEmpty(pageData)) {
|
||||
StringBuilder content = new StringBuilder();
|
||||
|
||||
// 如果是第一次写入,先写入字段名
|
||||
if (isFirstWrite) {
|
||||
// 获取字段名
|
||||
Map<String, Object> firstRow = pageData.get(0);
|
||||
List<String> fieldNames = new ArrayList<>(firstRow.keySet());
|
||||
|
||||
// 写入字段名作为第一行
|
||||
content.append(StrUtil.join("\t", fieldNames)).append(System.lineSeparator());
|
||||
isFirstWrite = false;
|
||||
}
|
||||
|
||||
// 写入数据行
|
||||
for (Map<String, Object> data : pageData) {
|
||||
List<Object> values = new ArrayList<>(data.values());
|
||||
content.append(StrUtil.join("\t", values)).append(System.lineSeparator());
|
||||
}
|
||||
|
||||
// 追加内容到文件
|
||||
FileUtil.appendUtf8String(content.toString(), dataFile);
|
||||
}
|
||||
offset += pageSize;
|
||||
|
||||
} while (pageData.size() == pageSize); // 如果查询结果少于pageSize,说明已经查询完所有数据
|
||||
}
|
||||
}
|
||||
planCheckDataVO.setDataBatch(dataBatch);
|
||||
int currentVal = progress.get() + currentProgress.get();
|
||||
if (currentVal < FINAL_STEP + 5) {
|
||||
progress.addAndGet(FINAL_STEP + 5);
|
||||
} else {
|
||||
progress.set(currentVal);
|
||||
}
|
||||
sseClient.sendMessage(uid, planId, HttpResultUtil.assembleResult(CommonResponseEnum.SUCCESS.getCode(), progress, "压缩检测结果数据文件中,请耐心等待..."));
|
||||
|
||||
// 导出数据.zip文件
|
||||
String jsonStr = JSONUtil.toJsonStr(planCheckDataVO, new JSONConfig().setIgnoreNullValue(false));
|
||||
try {
|
||||
// 创建 JSON 文件
|
||||
String jsonFileName = plan.getName() + ".json";
|
||||
File jsonFile = FileUtil.file(tempDataDir, jsonFileName);
|
||||
FileUtil.writeUtf8String(jsonStr, jsonFile);
|
||||
|
||||
// 创建 ZIP 文件
|
||||
String zipFileName = plan.getName() + "_检测数据包.zip";
|
||||
File zipFile = FileUtil.file(dataPath + File.separator + TEST_DATA_DIR + File.separator, zipFileName);
|
||||
|
||||
// 添加检测报告文件
|
||||
if (ObjectUtil.isNotNull(report) && report.equals(1)) {
|
||||
for (PqDev dev : devList) {
|
||||
DevType devType = devTypeService.getById(dev.getDevType());
|
||||
String dirPath = reportPath.concat(File.separator).concat(devType.getName());
|
||||
File reportFile = new File(dirPath.concat(File.separator).concat(dev.getCreateId()).concat(ReportConstant.DOCX));
|
||||
// 如果reportFile存在,则将reportFile中的文件添加到已有的zip文件中
|
||||
if (FileUtil.exist(reportFile)) {
|
||||
// 复制reportFile到临时目录
|
||||
FileUtil.copy(reportFile, tempDataDir, true);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// 创建zip文件,包含所有文件
|
||||
ZipUtil.zip(tempDataDir.getAbsolutePath(), zipFile.getAbsolutePath());
|
||||
// 删除临时目录
|
||||
FileUtil.del(tempDataDir);
|
||||
LocalDateTime endTime = LocalDateTime.now();
|
||||
log.info("生成数据包完成,耗时: {}s", Duration.between(startTime, endTime).getSeconds());
|
||||
progress.set(100);
|
||||
sseClient.sendMessage(uid, planId, HttpResultUtil.assembleResult(CommonResponseEnum.SUCCESS.getCode(), progress, zipFile.getAbsolutePath()));
|
||||
} catch (Exception e) {
|
||||
log.error("生成数据包失败", e);
|
||||
sseClient.sendMessage(uid, planId, HttpResultUtil.assembleResult(CommonResponseEnum.FAIL.getCode(), progress.get() + currentProgress.get(), "生成数据包失败"));
|
||||
} finally {
|
||||
NonWebAutoFillValueHandler.clearCurrentUserId();
|
||||
}
|
||||
|
||||
sseClient.closeSse(uid);
|
||||
}
|
||||
|
||||
|
||||
@Async
|
||||
public void importAndMergePlanCheckData(String zipFilePath, String uid, String planId) {
|
||||
importAndMergePlanCheckDataLogic(zipFilePath, uid, planId);
|
||||
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void importAndMergePlanCheckDataLogic(String zipFilePath, String uid, String planId) {
|
||||
NonWebAutoFillValueHandler.setCurrentUserId(uid);
|
||||
LocalDateTime startTime = LocalDateTime.now();
|
||||
AtomicInteger progress = new AtomicInteger(0);
|
||||
AtomicInteger currentProgress = new AtomicInteger(0);
|
||||
AtomicInteger dataCount = new AtomicInteger(0);
|
||||
try {
|
||||
// 创建临时目录用于解压文件
|
||||
File tempDir = FileUtil.mkdir(FileUtil.getTmpDirPath() + "import_plan_check_data_" + System.currentTimeMillis() + "/");
|
||||
progress.addAndGet(1);
|
||||
sseClient.sendMessage(uid, planId, HttpResultUtil.assembleResult(CommonResponseEnum.SUCCESS.getCode(), progress, "开始解压文件,请耐心等待..."));
|
||||
|
||||
// 解压zip文件
|
||||
File unzipDir = FileUtil.mkdir(FileUtil.file(tempDir, "unzip"));
|
||||
ZipUtil.unzip(zipFilePath, unzipDir.getAbsolutePath());
|
||||
|
||||
// 查找解压目录中的json文件
|
||||
File[] files = unzipDir.listFiles();
|
||||
AdPlanCheckDataVO planCheckDataVO = null;
|
||||
List<File> dataFiles = new ArrayList<>();
|
||||
List<File> docxFiles = new ArrayList<>();
|
||||
if (files != null) {
|
||||
for (File f : files) {
|
||||
if (f.isFile()) {
|
||||
// 读取json文件内容
|
||||
if (f.getName().endsWith(".json")) {
|
||||
String jsonStr = FileUtil.readUtf8String(f);
|
||||
planCheckDataVO = JSONUtil.toBean(jsonStr, AdPlanCheckDataVO.class);
|
||||
} else if (f.getName().endsWith(".docx")) {
|
||||
docxFiles.add(f);
|
||||
} else if (f.getName().endsWith(".txt")) {
|
||||
dataFiles.add(f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (planCheckDataVO == null) {
|
||||
FileUtil.del(tempDir);
|
||||
sseClient.sendMessage(uid, planId, HttpResultUtil.assembleResult(CommonResponseEnum.FAIL.getCode(), progress, "ZIP文件中未找到JSON数据文件"));
|
||||
return;
|
||||
}
|
||||
AdPlan checkPlan = planCheckDataVO.getPlan();
|
||||
if (checkPlan == null) {
|
||||
FileUtil.del(tempDir);
|
||||
sseClient.sendMessage(uid, planId, HttpResultUtil.assembleResult(CommonResponseEnum.FAIL.getCode(), progress, "ZIP文件中未找到检测计划信息"));
|
||||
return;
|
||||
}
|
||||
|
||||
List<PqDev> devList = planCheckDataVO.getDevList();
|
||||
if (CollUtil.isEmpty(devList)) {
|
||||
FileUtil.del(tempDir);
|
||||
sseClient.sendMessage(uid, planId, HttpResultUtil.assembleResult(CommonResponseEnum.FAIL.getCode(), progress, "ZIP文件中未找到被检设备信息"));
|
||||
return;
|
||||
}
|
||||
|
||||
AdPlan subPlan = adPlanService.getById(checkPlan.getId());
|
||||
if (subPlan == null) {
|
||||
FileUtil.del(tempDir);
|
||||
sseClient.sendMessage(uid, planId, HttpResultUtil.assembleResult(CommonResponseEnum.FAIL.getCode(), progress, "该子计划不存在"));
|
||||
return;
|
||||
}
|
||||
if (!StrUtil.equals(planId, subPlan.getFatherPlanId())) {
|
||||
FileUtil.del(tempDir);
|
||||
sseClient.sendMessage(uid, planId, HttpResultUtil.assembleResult(CommonResponseEnum.FAIL.getCode(), progress, "非当前检修计划的子计划"));
|
||||
return;
|
||||
}
|
||||
progress.addAndGet(1);
|
||||
sseClient.sendMessage(uid, planId, HttpResultUtil.assembleResult(CommonResponseEnum.SUCCESS.getCode(), progress, "开始同步计划基本信息,请耐心等待..."));
|
||||
// 更新检测计划几个状态字段
|
||||
subPlan.setTestState(checkPlan.getTestState());
|
||||
subPlan.setReportState(checkPlan.getReportState());
|
||||
subPlan.setResult(checkPlan.getResult());
|
||||
adPlanService.updateById(subPlan);
|
||||
progress.addAndGet(1);
|
||||
sseClient.sendMessage(uid, planId, HttpResultUtil.assembleResult(CommonResponseEnum.SUCCESS.getCode(), progress, "开始同步计划设备信息,请耐心等待..."));
|
||||
// 更新监测点数据
|
||||
List<PqMonitor> monitorList = planCheckDataVO.getMonitorList();
|
||||
pqMonitorService.updateBatchById(monitorList);
|
||||
// 批量更新被检设备信息,不需要更新
|
||||
// 不更新导入标志
|
||||
/*devList.forEach(dev -> dev.setImportFlag(null));
|
||||
pqDevService.updateBatchById(devList);*/
|
||||
|
||||
List<PqDevSub> devSubList = planCheckDataVO.getDevSubList();
|
||||
for (PqDevSub devSub : devSubList) {
|
||||
pqDevSubService.update(devSub, new LambdaUpdateWrapper<PqDevSub>().eq(PqDevSub::getDevId, devSub.getDevId()));
|
||||
}
|
||||
progress.addAndGet(1);
|
||||
sseClient.sendMessage(uid, planId, HttpResultUtil.assembleResult(CommonResponseEnum.SUCCESS.getCode(), progress, "开始同步通道配对信息,请耐心等待..."));
|
||||
|
||||
// 同步检测数据
|
||||
List<AdPair> pairList = planCheckDataVO.getPairList();
|
||||
adPairService.saveOrUpdateBatch(pairList);
|
||||
// 主计划
|
||||
AdPlan plan = adPlanService.getById(planId);
|
||||
if (CollUtil.isNotEmpty(docxFiles)) {
|
||||
progress.addAndGet(1);
|
||||
sseClient.sendMessage(uid, planId, HttpResultUtil.assembleResult(CommonResponseEnum.SUCCESS.getCode(), progress, "开始同步检测报告文件"));
|
||||
for (File docx : docxFiles) {
|
||||
for (PqDev dev : devList) {
|
||||
DevType devType = devTypeService.getById(dev.getDevType());
|
||||
String dirPath = reportPath.concat(File.separator).concat(devType.getName());
|
||||
File reportFile = new File(dirPath.concat(File.separator).concat(dev.getCreateId()).concat(ReportConstant.DOCX));
|
||||
// 文件名匹配,复制到对应目录下
|
||||
if (docx.getName().equals(reportFile.getName())) {
|
||||
File parentDir = FileUtil.mkParentDirs(reportFile);
|
||||
FileUtil.copy(docx, parentDir, true);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
if (CollUtil.isNotEmpty(dataFiles)) {
|
||||
Integer planCode = plan.getCode();
|
||||
progress.addAndGet(1);
|
||||
sseClient.sendMessage(uid, planId, HttpResultUtil.assembleResult(CommonResponseEnum.SUCCESS.getCode(), progress, "开始同步检测数据信息,请耐心等待..."));
|
||||
// 合并前清除相关表数据
|
||||
String mainHarmonicTableName = "ad_harmonic_" + planCode;
|
||||
String mainNonHarmonicTableName = "ad_non_harmonic_" + planCode;
|
||||
String mainHarmonicResultTableName = "ad_harmonic_result_" + planCode;
|
||||
String mainNonHarmonicResultTableName = "ad_non_harmonic_result_" + planCode;
|
||||
List<String> devMonitorIds = planCheckDataVO.getDevMonitorIds();
|
||||
if (CollUtil.isNotEmpty(devMonitorIds)) {
|
||||
// 使用 StringBuilder 构建带引号的ID列表,防止SQL注入
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < devMonitorIds.size(); i++) {
|
||||
if (i > 0) sb.append(",");
|
||||
sb.append("'").append(devMonitorIds.get(i)).append("'");
|
||||
}
|
||||
String devMonitorIdsStr = sb.toString();
|
||||
|
||||
jdbcTemplate.update("DELETE FROM " + mainHarmonicTableName + " WHERE dev_monitor_id IN (" + devMonitorIdsStr + ")");
|
||||
jdbcTemplate.update("DELETE FROM " + mainNonHarmonicTableName + " WHERE dev_monitor_id IN (" + devMonitorIdsStr + ")");
|
||||
jdbcTemplate.update("DELETE FROM " + mainHarmonicResultTableName + " WHERE dev_monitor_id IN (" + devMonitorIdsStr + ")");
|
||||
jdbcTemplate.update("DELETE FROM " + mainNonHarmonicResultTableName + " WHERE dev_monitor_id IN (" + devMonitorIdsStr + ")");
|
||||
}
|
||||
int dataBatch = planCheckDataVO.getDataBatch();
|
||||
int stepCount = dataBatch * BATCH_SIZE / FINAL_STEP;
|
||||
|
||||
for (File dataFile : dataFiles) {
|
||||
// 直接插入主计划表中
|
||||
String fileName = FileUtil.mainName(dataFile);
|
||||
|
||||
String tableName = fileName + StrUtil.UNDERLINE + planCode;
|
||||
|
||||
// 使用BatchFileReader分批处理文件
|
||||
final boolean[] isFirstBatch = {true};
|
||||
final String[] headers = {null};
|
||||
|
||||
BatchFileReader.readLinesInBatches(dataFile, BATCH_SIZE, lines -> {
|
||||
dataCount.addAndGet(lines.size());
|
||||
// 计算当前进度
|
||||
int current = dataCount.get() / stepCount;
|
||||
|
||||
// 确保进度不超过finalStep
|
||||
if (current > FINAL_STEP) {
|
||||
current = FINAL_STEP;
|
||||
}
|
||||
currentProgress.set(current);
|
||||
sseClient.sendMessage(uid, planId, HttpResultUtil.assembleResult(CommonResponseEnum.SUCCESS.getCode(), progress.get() + currentProgress.get(), "同步检测数据信息中,请耐心等待..."));
|
||||
|
||||
|
||||
if (CollUtil.isNotEmpty(lines)) {
|
||||
if (isFirstBatch[0]) {
|
||||
// 第一批次的第一行为字段名
|
||||
headers[0] = lines.get(0);
|
||||
// 处理剩余行作为数据
|
||||
if (lines.size() > 1) {
|
||||
List<String> dataLines = lines.subList(1, lines.size());
|
||||
processBatchData(tableName, headers[0], dataLines);
|
||||
}
|
||||
isFirstBatch[0] = false;
|
||||
} else {
|
||||
// 后续批次全部为数据行
|
||||
processBatchData(tableName, headers[0], lines);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
progress.addAndGet(1);
|
||||
sseClient.sendMessage(uid, planId, HttpResultUtil.assembleResult(CommonResponseEnum.SUCCESS.getCode(), progress.get() + currentProgress.get(), "表 " + tableName + " 数据同步完成"));
|
||||
}
|
||||
}
|
||||
|
||||
// 删除临时目录
|
||||
FileUtil.del(tempDir);
|
||||
// 更新主计划状态
|
||||
List<String> planIds = adPlanService.lambdaQuery().eq(AdPlan::getFatherPlanId, planId).list().stream().map(AdPlan::getId).collect(Collectors.toList());
|
||||
planIds.add(planId);
|
||||
List<String> devIds = pqDevService.lambdaQuery().in(PqDev::getPlanId, planIds).list().stream().map(PqDev::getId).collect(Collectors.toList());
|
||||
List<PqDevSub> devSubs = pqDevSubService.lambdaQuery().in(PqDevSub::getDevId, devIds).list();
|
||||
long checkedCount = devSubs.stream().filter(sub -> sub.getCheckState().equals(CheckStateEnum.CHECKED.getValue())).count();
|
||||
if (checkedCount > 0) {
|
||||
plan.setTestState(CheckStateEnum.CHECKING.getValue());
|
||||
// 都已检测完成
|
||||
if (checkedCount == devSubs.size()) {
|
||||
plan.setTestState(CheckStateEnum.CHECKED.getValue());
|
||||
}
|
||||
} else {
|
||||
plan.setTestState(CheckStateEnum.UNCHECKED.getValue());
|
||||
// 是否有检测中
|
||||
long checkingCount = devSubs.stream().filter(sub -> sub.getCheckState().equals(CheckStateEnum.CHECKING.getValue())).count();
|
||||
if (checkingCount > 0) {
|
||||
plan.setTestState(CheckStateEnum.CHECKING.getValue());
|
||||
}
|
||||
}
|
||||
adPlanService.updateById(plan);
|
||||
LocalDateTime endTime = LocalDateTime.now();
|
||||
log.info("数据合并完成,耗时:{}s", Duration.between(startTime, endTime).getSeconds());
|
||||
progress.set(100);
|
||||
sseClient.sendMessage(uid, planId, HttpResultUtil.assembleResult(CommonResponseEnum.SUCCESS.getCode(), progress, "数据合并完成"));
|
||||
|
||||
} catch (Exception e) {
|
||||
|
||||
log.error("导入数据失败", e);
|
||||
sseClient.sendMessage(uid, planId, HttpResultUtil.assembleResult(CommonResponseEnum.FAIL.getCode(), progress.get() + currentProgress.get(), "导入失败"));
|
||||
} finally {
|
||||
NonWebAutoFillValueHandler.clearCurrentUserId();
|
||||
}
|
||||
FileUtil.del(zipFilePath);
|
||||
sseClient.closeSse(uid);
|
||||
}
|
||||
|
||||
// 构建分页查询SQL
|
||||
private String buildPaginatedQuery(String tableName, List<String> devMonitorIds, int limit, int offset) {
|
||||
StringBuilder sql = new StringBuilder("SELECT * FROM " + tableName);
|
||||
|
||||
sql.append(" WHERE Dev_Monitor_Id IN (");
|
||||
for (int i = 0; i < devMonitorIds.size(); i++) {
|
||||
sql.append("'").append(devMonitorIds.get(i)).append("'");
|
||||
if (i < devMonitorIds.size() - 1) {
|
||||
sql.append(",");
|
||||
}
|
||||
}
|
||||
sql.append(")");
|
||||
sql.append(" ORDER BY Id");
|
||||
|
||||
// 添加分页限制
|
||||
sql.append(" LIMIT ").append(limit).append(" OFFSET ").append(offset);
|
||||
|
||||
return sql.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理数据行
|
||||
*
|
||||
* @param tableName 表名
|
||||
* @param headers 表头
|
||||
* @param lines 数据行
|
||||
*/
|
||||
private void processBatchData(String tableName, String headers, List<String> lines) {
|
||||
if (lines == null || lines.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
// 构建INSERT语句
|
||||
String[] headerArray = headers.split("\t");
|
||||
String columnNames = String.join(",", headerArray);
|
||||
String placeholders = String.join(",", java.util.Collections.nCopies(headerArray.length, "?"));
|
||||
String sql = "INSERT INTO " + tableName + " (" + columnNames + ") VALUES (" + placeholders + ")";
|
||||
|
||||
// 批量插入数据
|
||||
jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
|
||||
@Override
|
||||
public void setValues(java.sql.PreparedStatement ps, int i) throws java.sql.SQLException {
|
||||
String line = lines.get(i);
|
||||
String[] fields = line.split("\t", -1); // 使用-1限制以保留空值
|
||||
|
||||
for (int j = 0; j < fields.length && j < headerArray.length; j++) {
|
||||
String value = fields[j];
|
||||
if (StrUtil.isEmpty(value) || StrUtil.equals(value, StrUtil.NULL)) {
|
||||
ps.setNull(j + 1, java.sql.Types.VARCHAR);
|
||||
} else {
|
||||
ps.setString(j + 1, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBatchSize() {
|
||||
return lines.size();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,10 @@
|
||||
package com.njcn.gather.plan.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.njcn.gather.device.pojo.param.PqDevParam;
|
||||
import com.njcn.gather.device.pojo.po.PqStandardDev;
|
||||
import com.njcn.gather.plan.pojo.param.AdPlanParam;
|
||||
import com.njcn.gather.plan.pojo.po.AdPlan;
|
||||
import com.njcn.gather.plan.pojo.vo.AdPlanExcel;
|
||||
import com.njcn.gather.plan.pojo.vo.AdPlanVO;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
@@ -47,12 +45,11 @@ public interface IAdPlanService extends IService<AdPlan> {
|
||||
/**
|
||||
* 删除检测计划
|
||||
*
|
||||
* @param ids 检测计划id列表
|
||||
* @param ids 检测计划id列表
|
||||
* @param pattern 模式Id
|
||||
*
|
||||
* @return 删除成功则返回true,否则返回false
|
||||
*/
|
||||
boolean deleteAdPlan(List<String> ids,String pattern);
|
||||
boolean deleteAdPlan(List<String> ids, String pattern);
|
||||
|
||||
/**
|
||||
* 根据模式查询检测计划
|
||||
@@ -72,12 +69,14 @@ public interface IAdPlanService extends IService<AdPlan> {
|
||||
/**
|
||||
* 获取检测大项
|
||||
*
|
||||
* @param reCheckType 0:不合格项复检 1:全部复检
|
||||
* @param planId 检测计划Id
|
||||
* @param devIds 设备Id列表
|
||||
* @param reCheckType 0:不合格项复检 1:全部复检
|
||||
* @param planId 检测计划Id
|
||||
* @param devIds 设备Id列表
|
||||
* @param patternId 模式Id
|
||||
* @param scriptType 脚本类型
|
||||
* @return
|
||||
*/
|
||||
List<Map<String, String>> getBigTestItem(Integer reCheckType, String planId, List<String> devIds);
|
||||
List<Map<String, String>> getBigTestItem(Integer reCheckType, String planId, List<String> devIds, String patternId, String scriptType);
|
||||
|
||||
/**
|
||||
* 修改计划状态
|
||||
@@ -142,16 +141,16 @@ public interface IAdPlanService extends IService<AdPlan> {
|
||||
* 根据计划Id获取已绑定标准设备
|
||||
*
|
||||
* @param planId
|
||||
* @param all
|
||||
* @return
|
||||
*/
|
||||
List<PqStandardDev> getBoundStandardDev(String planId);
|
||||
List<PqStandardDev> getBoundStandardDev(String planId, Integer all);
|
||||
|
||||
/**
|
||||
* 修改子计划名称
|
||||
*
|
||||
* @param planId
|
||||
* @param name
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
boolean updateSubPlanName(String planId, String name);
|
||||
@@ -159,8 +158,7 @@ public interface IAdPlanService extends IService<AdPlan> {
|
||||
/**
|
||||
* 子计划绑定/解绑被检设备
|
||||
*
|
||||
* @param planId
|
||||
* @param pqDevIds
|
||||
* @param param
|
||||
* @return
|
||||
*/
|
||||
boolean updateBindDev(PqDevParam.BindPlanParam param);
|
||||
@@ -175,10 +173,36 @@ public interface IAdPlanService extends IService<AdPlan> {
|
||||
boolean updateBindStandardDev(String planId, List<String> standardDevIds);
|
||||
|
||||
/**
|
||||
* 根据计划Id导出子计划数据
|
||||
* 项目负责人导出子计划元信息
|
||||
*
|
||||
* @param planId
|
||||
* @param response
|
||||
*/
|
||||
void exportSubPlan(String planId, HttpServletResponse response);
|
||||
void exportSubPlanDataZip(String planId, HttpServletResponse response);
|
||||
|
||||
/**
|
||||
* 项目成员导入子计划元信息
|
||||
*
|
||||
* @param file
|
||||
* @param patternId
|
||||
* @param response
|
||||
*/
|
||||
boolean importSubPlanDataZip(MultipartFile file, String patternId, HttpServletResponse response);
|
||||
|
||||
/**
|
||||
* 导出计划检测结果数据
|
||||
*
|
||||
* @param planId
|
||||
* @param devIds
|
||||
* @param report
|
||||
* @param response
|
||||
*/
|
||||
void exportPlanCheckDataZip(String planId, List<String> devIds, Integer report, HttpServletResponse response);
|
||||
|
||||
/**
|
||||
* 比对模式下计划的检测大项获取
|
||||
* @param planId 计划ID
|
||||
* @return 检测项集合
|
||||
*/
|
||||
List<String> getScriptListContrast(String planId);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.njcn.gather.plan.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.njcn.gather.plan.pojo.po.AdPlanTestConfig;
|
||||
|
||||
/**
|
||||
* @author stary
|
||||
* @date 2025-08-25
|
||||
*/
|
||||
public interface IAdPlanTestConfigService extends IService<AdPlanTestConfig> {
|
||||
/**
|
||||
* 根据计划id获取测试配置
|
||||
*
|
||||
* @param planId 计划id
|
||||
* @return
|
||||
*/
|
||||
AdPlanTestConfig getByPlanId(String planId);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
package com.njcn.gather.plan.service;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class SseClient {
|
||||
private static final Map<String, SseEmitter> sseEmitterMap = new ConcurrentHashMap<>();
|
||||
private static final int RECONNECT_DELAY = 5000; // 重连延迟时间,5秒
|
||||
|
||||
/**
|
||||
* 创建SSE连接
|
||||
*
|
||||
* @param uid 用户唯一标识
|
||||
* @return SseEmitter 实例
|
||||
*/
|
||||
public SseEmitter createSse(String uid) {
|
||||
// 创建一个永不超时的SseEmitter
|
||||
SseEmitter sseEmitter = new SseEmitter(0L);
|
||||
sseEmitterMap.put(uid, sseEmitter);
|
||||
|
||||
// 完成后的回调,记录日志并从map中移除
|
||||
sseEmitter.onCompletion(() -> {
|
||||
log.info("[{}]结束连接...................", uid);
|
||||
sseEmitterMap.remove(uid);
|
||||
});
|
||||
|
||||
// 超时回调,记录日志
|
||||
sseEmitter.onTimeout(() -> {
|
||||
log.info("[{}]连接超时,准备重连...................", uid);
|
||||
scheduleReconnect(uid);
|
||||
});
|
||||
|
||||
// 异常回调,记录日志并发送错误事件,然后重试
|
||||
sseEmitter.onError(throwable -> {
|
||||
log.error("[{}]连接异常,{}", uid, throwable.toString(), throwable);
|
||||
try {
|
||||
sseEmitter.send(SseEmitter.event()
|
||||
.id(uid)
|
||||
.name("发生异常!")
|
||||
.data("发生异常请重试!")
|
||||
.reconnectTime(RECONNECT_DELAY));
|
||||
} catch (IOException e) {
|
||||
log.error("[{}]发送错误事件失败", uid, e);
|
||||
}
|
||||
scheduleReconnect(uid);
|
||||
});
|
||||
|
||||
try {
|
||||
// 发送初始化事件,设置重连时间
|
||||
sseEmitter.send(SseEmitter.event().reconnectTime(RECONNECT_DELAY));
|
||||
} catch (IOException e) {
|
||||
log.error("[{}]发送初始化事件失败", uid, e);
|
||||
}
|
||||
|
||||
log.info("[{}]创建sse连接成功!", uid);
|
||||
return sseEmitter;
|
||||
}
|
||||
|
||||
/**
|
||||
* 给指定用户发送消息(可以定时或在事件发生时调用sseEmitter.send()方法来发送事件。)
|
||||
*
|
||||
* @param uid 用户唯一标识
|
||||
* @param messageId 消息ID
|
||||
* @param message 消息内容
|
||||
* @return 是否发送成功
|
||||
*/
|
||||
public boolean sendMessage(String uid, String messageId, Object message) {
|
||||
if (ObjectUtil.isEmpty(message)) {
|
||||
log.warn("参数异常,message 为null");
|
||||
return false;
|
||||
}
|
||||
SseEmitter sseEmitter = sseEmitterMap.get(uid);
|
||||
if (sseEmitter == null) {
|
||||
log.info("消息推送失败uid:[{}],没有创建连接,请重试。", uid);
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
sseEmitter.send(SseEmitter.event().id(messageId).reconnectTime(1 * 60 * 1000L).data(message));
|
||||
// log.info("用户{},消息id:{},推送成功:{}", uid, messageId, message);
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
log.error("用户{},消息id:{},推送IO异常:{}", uid, messageId, e.getMessage());
|
||||
// 客户端断开连接属于正常情况,不需要重连,直接移除连接
|
||||
sseEmitterMap.remove(uid);
|
||||
sseEmitter.complete();
|
||||
return false;
|
||||
} catch (Exception e) {
|
||||
log.error("用户{},消息id:{},推送其他异常:{}", uid, messageId, e.getMessage(), e);
|
||||
sseEmitter.complete();
|
||||
scheduleReconnect(uid);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 断开SSE连接
|
||||
*
|
||||
* @param uid 用户唯一标识
|
||||
*/
|
||||
public void closeSse(String uid) {
|
||||
SseEmitter sseEmitter = sseEmitterMap.remove(uid);
|
||||
if (sseEmitter != null) {
|
||||
sseEmitter.complete();
|
||||
log.info("用户{} 连接已关闭", uid);
|
||||
} else {
|
||||
log.info("用户{} 连接已关闭,或连接不存在", uid);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 计划重连
|
||||
*
|
||||
* @param uid 用户唯一标识
|
||||
*/
|
||||
private void scheduleReconnect(String uid) {
|
||||
// 这里可以添加一个定时任务来尝试重连
|
||||
// 例如使用Spring的@Scheduled注解或者ScheduledExecutorService
|
||||
log.info("[{}]计划在{}毫秒后重连", uid, RECONNECT_DELAY);
|
||||
// 模拟重连操作,实际应用中应根据业务需求实现
|
||||
try {
|
||||
Thread.sleep(RECONNECT_DELAY);
|
||||
createSse(uid);
|
||||
} catch (InterruptedException e) {
|
||||
log.error("[{}]重连操作被中断", uid, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user